[Core] Refactor ChibiOS USB endpoints to be fully async (#21656)

This commit is contained in:
Stefan Kerkmann
2024-02-28 12:00:27 +01:00
committed by GitHub
parent b43f6cb7ef
commit 0e02b0c41e
12 changed files with 1311 additions and 1134 deletions

View File

@ -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