[Core] Refactor ChibiOS USB endpoints to be fully async (#21656)
This commit is contained in:
@ -1,177 +1,209 @@
|
||||
/*
|
||||
ChibiOS - Copyright (C) 2006..2016 Giovanni Di Sirio
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
/**
|
||||
* @file usb_driver.h
|
||||
* @brief Usb driver suitable for both packet and serial formats
|
||||
*
|
||||
* @addtogroup SERIAL_USB
|
||||
* @{
|
||||
*/
|
||||
// Copyright 2023 Stefan Kerkmann (@KarlK90)
|
||||
// Copyright 2020 Ryan (@fauxpark)
|
||||
// Copyright 2016 Fredizzimo
|
||||
// Copyright 2016 Giovanni Di Sirio
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later OR Apache-2.0
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <hal_usb_cdc.h>
|
||||
|
||||
/*===========================================================================*/
|
||||
/* Driver constants. */
|
||||
/*===========================================================================*/
|
||||
|
||||
/*===========================================================================*/
|
||||
/* Derived constants and error checks. */
|
||||
/*===========================================================================*/
|
||||
#include <hal_buffers.h>
|
||||
#include "usb_descriptor.h"
|
||||
#include "chibios_config.h"
|
||||
#include "usb_report_handling.h"
|
||||
#include "string.h"
|
||||
#include "timer.h"
|
||||
|
||||
#if HAL_USE_USB == FALSE
|
||||
# error "The USB Driver requires HAL_USE_USB"
|
||||
#endif
|
||||
|
||||
/*===========================================================================*/
|
||||
/* Driver data structures and types. */
|
||||
/*===========================================================================*/
|
||||
/* USB Low Level driver specific endpoint fields */
|
||||
#if !defined(usb_lld_endpoint_fields)
|
||||
# define usb_lld_endpoint_fields \
|
||||
2, /* IN multiplier */ \
|
||||
NULL, /* SETUP buffer (not a SETUP endpoint) */
|
||||
#endif
|
||||
|
||||
/**
|
||||
* @brief Driver state machine possible states.
|
||||
/*
|
||||
* Implementation notes:
|
||||
*
|
||||
* USBEndpointConfig - Configured using explicit order instead of struct member name.
|
||||
* This is due to ChibiOS hal LLD differences, which is dependent on hardware,
|
||||
* "USBv1" devices have `ep_buffers` and "OTGv1" have `in_multiplier`.
|
||||
* Given `USBv1/hal_usb_lld.h` marks the field as "not currently used" this code file
|
||||
* makes the assumption this is safe to avoid littering with preprocessor directives.
|
||||
*/
|
||||
typedef enum {
|
||||
QMKUSB_UNINIT = 0, /**< Not initialized. */
|
||||
QMKUSB_STOP = 1, /**< Stopped. */
|
||||
QMKUSB_READY = 2 /**< Ready. */
|
||||
} qmkusbstate_t;
|
||||
#define QMK_USB_ENDPOINT_IN(mode, ep_size, ep_num, _buffer_capacity, _usb_requests_cb, _report_storage) \
|
||||
{ \
|
||||
.usb_requests_cb = _usb_requests_cb, .report_storage = _report_storage, \
|
||||
.ep_config = \
|
||||
{ \
|
||||
mode, /* EP Mode */ \
|
||||
NULL, /* SETUP packet notification callback */ \
|
||||
usb_endpoint_in_tx_complete_cb, /* IN notification callback */ \
|
||||
NULL, /* OUT notification callback */ \
|
||||
ep_size, /* IN maximum packet size */ \
|
||||
0, /* OUT maximum packet size */ \
|
||||
NULL, /* IN Endpoint state */ \
|
||||
NULL, /* OUT endpoint state */ \
|
||||
usb_lld_endpoint_fields /* USB driver specific endpoint fields */ \
|
||||
}, \
|
||||
.config = { \
|
||||
.usbp = &USB_DRIVER, \
|
||||
.ep = ep_num, \
|
||||
.buffer_capacity = _buffer_capacity, \
|
||||
.buffer_size = ep_size, \
|
||||
.buffer = (_Alignas(4) uint8_t[BQ_BUFFER_SIZE(_buffer_capacity, ep_size)]){0}, \
|
||||
} \
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Structure representing a serial over USB driver.
|
||||
*/
|
||||
typedef struct QMKUSBDriver QMKUSBDriver;
|
||||
#if !defined(USB_ENDPOINTS_ARE_REORDERABLE)
|
||||
|
||||
# define QMK_USB_ENDPOINT_OUT(mode, ep_size, ep_num, _buffer_capacity) \
|
||||
{ \
|
||||
.ep_config = \
|
||||
{ \
|
||||
mode, /* EP Mode */ \
|
||||
NULL, /* SETUP packet notification callback */ \
|
||||
NULL, /* IN notification callback */ \
|
||||
usb_endpoint_out_rx_complete_cb, /* OUT notification callback */ \
|
||||
0, /* IN maximum packet size */ \
|
||||
ep_size, /* OUT maximum packet size */ \
|
||||
NULL, /* IN Endpoint state */ \
|
||||
NULL, /* OUT endpoint state */ \
|
||||
usb_lld_endpoint_fields /* USB driver specific endpoint fields */ \
|
||||
}, \
|
||||
.config = { \
|
||||
.usbp = &USB_DRIVER, \
|
||||
.ep = ep_num, \
|
||||
.buffer_capacity = _buffer_capacity, \
|
||||
.buffer_size = ep_size, \
|
||||
.buffer = (_Alignas(4) uint8_t[BQ_BUFFER_SIZE(_buffer_capacity, ep_size)]){0} \
|
||||
} \
|
||||
}
|
||||
|
||||
#else
|
||||
|
||||
# define QMK_USB_ENDPOINT_IN_SHARED(mode, ep_size, ep_num, _buffer_capacity, _usb_requests_cb, _report_storage) \
|
||||
{ \
|
||||
.usb_requests_cb = _usb_requests_cb, .is_shared = true, .report_storage = _report_storage, \
|
||||
.ep_config = \
|
||||
{ \
|
||||
mode, /* EP Mode */ \
|
||||
NULL, /* SETUP packet notification callback */ \
|
||||
usb_endpoint_in_tx_complete_cb, /* IN notification callback */ \
|
||||
usb_endpoint_out_rx_complete_cb, /* OUT notification callback */ \
|
||||
ep_size, /* IN maximum packet size */ \
|
||||
ep_size, /* OUT maximum packet size */ \
|
||||
NULL, /* IN Endpoint state */ \
|
||||
NULL, /* OUT endpoint state */ \
|
||||
usb_lld_endpoint_fields /* USB driver specific endpoint fields */ \
|
||||
}, \
|
||||
.config = { \
|
||||
.usbp = &USB_DRIVER, \
|
||||
.ep = ep_num, \
|
||||
.buffer_capacity = _buffer_capacity, \
|
||||
.buffer_size = ep_size, \
|
||||
.buffer = (_Alignas(4) uint8_t[BQ_BUFFER_SIZE(_buffer_capacity, ep_size)]){0}, \
|
||||
} \
|
||||
}
|
||||
|
||||
/* The current assumption is that there are no standalone OUT endpoints, so the
|
||||
* OUT endpoint is always initialized by the IN endpoint. */
|
||||
# define QMK_USB_ENDPOINT_OUT(mode, ep_size, ep_num, _buffer_capacity) \
|
||||
{ \
|
||||
.ep_config = \
|
||||
{ \
|
||||
0 /* Already defined in the IN endpoint */ \
|
||||
}, \
|
||||
.config = { \
|
||||
.usbp = &USB_DRIVER, \
|
||||
.ep = ep_num, \
|
||||
.buffer_capacity = _buffer_capacity, \
|
||||
.buffer_size = ep_size, \
|
||||
.buffer = (_Alignas(4) uint8_t[BQ_BUFFER_SIZE(_buffer_capacity, ep_size)]){0} \
|
||||
} \
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
/**
|
||||
* @brief Serial over USB Driver configuration structure.
|
||||
* @details An instance of this structure must be passed to @p sduStart()
|
||||
* in order to configure and start the driver operations.
|
||||
*/
|
||||
typedef struct {
|
||||
/**
|
||||
* @brief USB driver to use.
|
||||
*/
|
||||
USBDriver *usbp;
|
||||
/**
|
||||
* @brief Bulk IN endpoint used for outgoing data transfer.
|
||||
*/
|
||||
usbep_t bulk_in;
|
||||
/**
|
||||
* @brief Bulk OUT endpoint used for incoming data transfer.
|
||||
*/
|
||||
usbep_t bulk_out;
|
||||
/**
|
||||
* @brief Interrupt IN endpoint used for notifications.
|
||||
* @note If set to zero then the INT endpoint is assumed to be not
|
||||
* present, USB descriptors must be changed accordingly.
|
||||
*/
|
||||
usbep_t int_in;
|
||||
|
||||
/**
|
||||
* @brief The number of buffers in the queues
|
||||
* @brief Endpoint used for data transfer
|
||||
*/
|
||||
size_t in_buffers;
|
||||
size_t out_buffers;
|
||||
usbep_t ep;
|
||||
|
||||
/**
|
||||
* @brief The size of each buffer in the queue, typically the same as the endpoint size
|
||||
* @brief The number of buffers in the queue
|
||||
*/
|
||||
size_t in_size;
|
||||
size_t out_size;
|
||||
size_t buffer_capacity;
|
||||
|
||||
/**
|
||||
* @brief Always send full buffers in_size (the rest is filled with zeroes)
|
||||
* @brief The size of each buffer in the queue, same as the endpoint size
|
||||
*/
|
||||
bool fixed_size;
|
||||
size_t buffer_size;
|
||||
|
||||
/* Input buffer
|
||||
* @note needs to be initialized with a memory buffer of the right size
|
||||
/**
|
||||
* @brief Buffer backing storage
|
||||
*/
|
||||
uint8_t *ib;
|
||||
/* Output buffer
|
||||
* @note needs to be initialized with a memory buffer of the right size
|
||||
*/
|
||||
uint8_t *ob;
|
||||
} QMKUSBConfig;
|
||||
uint8_t *buffer;
|
||||
} usb_endpoint_config_t;
|
||||
|
||||
/**
|
||||
* @brief @p SerialDriver specific data.
|
||||
*/
|
||||
#define _qmk_usb_driver_data \
|
||||
_base_asynchronous_channel_data /* Driver state.*/ \
|
||||
qmkusbstate_t state; \
|
||||
/* Input buffers queue.*/ \
|
||||
input_buffers_queue_t ibqueue; \
|
||||
/* Output queue.*/ \
|
||||
output_buffers_queue_t obqueue; \
|
||||
/* End of the mandatory fields.*/ \
|
||||
/* Current configuration data.*/ \
|
||||
const QMKUSBConfig *config;
|
||||
typedef struct {
|
||||
output_buffers_queue_t obqueue;
|
||||
USBEndpointConfig ep_config;
|
||||
USBInEndpointState ep_in_state;
|
||||
#if defined(USB_ENDPOINTS_ARE_REORDERABLE)
|
||||
USBOutEndpointState ep_out_state;
|
||||
bool is_shared;
|
||||
#endif
|
||||
usb_endpoint_config_t config;
|
||||
usbreqhandler_t usb_requests_cb;
|
||||
bool timed_out;
|
||||
usb_report_storage_t *report_storage;
|
||||
} usb_endpoint_in_t;
|
||||
|
||||
/**
|
||||
* @brief @p SerialUSBDriver specific methods.
|
||||
*/
|
||||
#define _qmk_usb_driver_methods _base_asynchronous_channel_methods
|
||||
|
||||
/**
|
||||
* @extends BaseAsynchronousChannelVMT
|
||||
*
|
||||
* @brief @p SerialDriver virtual methods table.
|
||||
*/
|
||||
struct QMKUSBDriverVMT {
|
||||
_qmk_usb_driver_methods
|
||||
};
|
||||
|
||||
/**
|
||||
* @extends BaseAsynchronousChannel
|
||||
*
|
||||
* @brief Full duplex serial driver class.
|
||||
* @details This class extends @p BaseAsynchronousChannel by adding physical
|
||||
* I/O queues.
|
||||
*/
|
||||
struct QMKUSBDriver {
|
||||
/** @brief Virtual Methods Table.*/
|
||||
const struct QMKUSBDriverVMT *vmt;
|
||||
_qmk_usb_driver_data
|
||||
};
|
||||
|
||||
/*===========================================================================*/
|
||||
/* Driver macros. */
|
||||
/*===========================================================================*/
|
||||
|
||||
/*===========================================================================*/
|
||||
/* External declarations. */
|
||||
/*===========================================================================*/
|
||||
typedef struct {
|
||||
input_buffers_queue_t ibqueue;
|
||||
USBEndpointConfig ep_config;
|
||||
USBOutEndpointState ep_out_state;
|
||||
usb_endpoint_config_t config;
|
||||
bool timed_out;
|
||||
} usb_endpoint_out_t;
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
void qmkusbInit(void);
|
||||
void qmkusbObjectInit(QMKUSBDriver *qmkusbp, const QMKUSBConfig *config);
|
||||
void qmkusbStart(QMKUSBDriver *qmkusbp, const QMKUSBConfig *config);
|
||||
void qmkusbStop(QMKUSBDriver *qmkusbp);
|
||||
void qmkusbSuspendHookI(QMKUSBDriver *qmkusbp);
|
||||
void qmkusbWakeupHookI(QMKUSBDriver *qmkusbp);
|
||||
void qmkusbConfigureHookI(QMKUSBDriver *qmkusbp);
|
||||
bool qmkusbRequestsHook(USBDriver *usbp);
|
||||
void qmkusbSOFHookI(QMKUSBDriver *qmkusbp);
|
||||
void qmkusbDataTransmitted(USBDriver *usbp, usbep_t ep);
|
||||
void qmkusbDataReceived(USBDriver *usbp, usbep_t ep);
|
||||
void qmkusbInterruptTransmitted(USBDriver *usbp, usbep_t ep);
|
||||
|
||||
void usb_endpoint_in_init(usb_endpoint_in_t *endpoint);
|
||||
void usb_endpoint_in_start(usb_endpoint_in_t *endpoint);
|
||||
void usb_endpoint_in_stop(usb_endpoint_in_t *endpoint);
|
||||
|
||||
bool usb_endpoint_in_send(usb_endpoint_in_t *endpoint, const uint8_t *data, size_t size, sysinterval_t timeout, bool buffered);
|
||||
void usb_endpoint_in_flush(usb_endpoint_in_t *endpoint, bool padded);
|
||||
bool usb_endpoint_in_is_inactive(usb_endpoint_in_t *endpoint);
|
||||
|
||||
void usb_endpoint_in_suspend_cb(usb_endpoint_in_t *endpoint);
|
||||
void usb_endpoint_in_wakeup_cb(usb_endpoint_in_t *endpoint);
|
||||
void usb_endpoint_in_configure_cb(usb_endpoint_in_t *endpoint);
|
||||
void usb_endpoint_in_tx_complete_cb(USBDriver *usbp, usbep_t ep);
|
||||
|
||||
void usb_endpoint_out_init(usb_endpoint_out_t *endpoint);
|
||||
void usb_endpoint_out_start(usb_endpoint_out_t *endpoint);
|
||||
void usb_endpoint_out_stop(usb_endpoint_out_t *endpoint);
|
||||
|
||||
bool usb_endpoint_out_receive(usb_endpoint_out_t *endpoint, uint8_t *data, size_t size, sysinterval_t timeout);
|
||||
|
||||
void usb_endpoint_out_suspend_cb(usb_endpoint_out_t *endpoint);
|
||||
void usb_endpoint_out_wakeup_cb(usb_endpoint_out_t *endpoint);
|
||||
void usb_endpoint_out_configure_cb(usb_endpoint_out_t *endpoint);
|
||||
void usb_endpoint_out_rx_complete_cb(USBDriver *usbp, usbep_t ep);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
Reference in New Issue
Block a user