From 0e02b0c41e47d5f5ad799a9860869b9d30ab881a Mon Sep 17 00:00:00 2001 From: Stefan Kerkmann Date: Wed, 28 Feb 2024 12:00:27 +0100 Subject: [PATCH] [Core] Refactor ChibiOS USB endpoints to be fully async (#21656) --- tmk_core/protocol/chibios/chibios.c | 16 +- tmk_core/protocol/chibios/chibios.mk | 2 + tmk_core/protocol/chibios/usb_driver.c | 586 +++++-------- tmk_core/protocol/chibios/usb_driver.h | 312 ++++--- tmk_core/protocol/chibios/usb_endpoints.c | 152 ++++ tmk_core/protocol/chibios/usb_endpoints.h | 137 +++ tmk_core/protocol/chibios/usb_main.c | 816 +++++------------- tmk_core/protocol/chibios/usb_main.h | 41 +- .../protocol/chibios/usb_report_handling.c | 296 +++++++ .../protocol/chibios/usb_report_handling.h | 77 ++ tmk_core/protocol/report.h | 8 +- tmk_core/protocol/usb_descriptor.h | 2 + 12 files changed, 1311 insertions(+), 1134 deletions(-) create mode 100644 tmk_core/protocol/chibios/usb_endpoints.c create mode 100644 tmk_core/protocol/chibios/usb_endpoints.h create mode 100644 tmk_core/protocol/chibios/usb_report_handling.c create mode 100644 tmk_core/protocol/chibios/usb_report_handling.h diff --git a/tmk_core/protocol/chibios/chibios.c b/tmk_core/protocol/chibios/chibios.c index 91bb252c7c3..76a37ae538a 100644 --- a/tmk_core/protocol/chibios/chibios.c +++ b/tmk_core/protocol/chibios/chibios.c @@ -192,15 +192,18 @@ void protocol_pre_task(void) { /* Remote wakeup */ if ((USB_DRIVER.status & USB_GETSTATUS_REMOTE_WAKEUP_ENABLED) && suspend_wakeup_condition()) { usbWakeupHost(&USB_DRIVER); - restart_usb_driver(&USB_DRIVER); +# if USB_SUSPEND_WAKEUP_DELAY > 0 + // Some hubs, kvm switches, and monitors do + // weird things, with USB device state bouncing + // around wildly on wakeup, yielding race + // conditions that can corrupt the keyboard state. + // + // Pause for a while to let things settle... + wait_ms(USB_SUSPEND_WAKEUP_DELAY); +# endif } } /* Woken up */ - // variables has been already cleared by the wakeup hook - send_keyboard_report(); -# ifdef MOUSEKEY_ENABLE - mousekey_send(); -# endif /* MOUSEKEY_ENABLE */ } #endif } @@ -218,4 +221,5 @@ void protocol_post_task(void) { #ifdef RAW_ENABLE raw_hid_task(); #endif + usb_idle_task(); } diff --git a/tmk_core/protocol/chibios/chibios.mk b/tmk_core/protocol/chibios/chibios.mk index 8eaf5b10d29..aee3f5f0564 100644 --- a/tmk_core/protocol/chibios/chibios.mk +++ b/tmk_core/protocol/chibios/chibios.mk @@ -6,6 +6,8 @@ SRC += $(CHIBIOS_DIR)/usb_main.c SRC += $(CHIBIOS_DIR)/chibios.c SRC += usb_descriptor.c SRC += $(CHIBIOS_DIR)/usb_driver.c +SRC += $(CHIBIOS_DIR)/usb_endpoints.c +SRC += $(CHIBIOS_DIR)/usb_report_handling.c SRC += $(CHIBIOS_DIR)/usb_util.c SRC += $(LIBSRC) diff --git a/tmk_core/protocol/chibios/usb_driver.c b/tmk_core/protocol/chibios/usb_driver.c index ad45f9b1daa..7c3ce446876 100644 --- a/tmk_core/protocol/chibios/usb_driver.c +++ b/tmk_core/protocol/chibios/usb_driver.c @@ -1,127 +1,51 @@ -/* - 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 hal_serial_usb.c - * @brief Serial over USB Driver code. - * - * @addtogroup SERIAL_USB - * @{ - */ +// Copyright 2023 Stefan Kerkmann (@KarlK90) +// Copyright 2021 Purdea Andrei +// Copyright 2021 Michael Stapelberg +// Copyright 2020 Ryan (@fauxpark) +// Copyright 2016 Fredizzimo +// Copyright 2016 Giovanni Di Sirio +// SPDX-License-Identifier: GPL-3.0-or-later OR Apache-2.0 #include -#include "usb_driver.h" #include -/*===========================================================================*/ -/* Driver local definitions. */ -/*===========================================================================*/ - -/*===========================================================================*/ -/* Driver exported variables. */ -/*===========================================================================*/ - -/*===========================================================================*/ -/* Driver local variables and types. */ -/*===========================================================================*/ - -/* - * Current Line Coding. - */ -static cdc_linecoding_t linecoding = {{0x00, 0x96, 0x00, 0x00}, /* 38400. */ - LC_STOP_1, - LC_PARITY_NONE, - 8}; +#include "usb_driver.h" +#include "util.h" /*===========================================================================*/ /* Driver local functions. */ /*===========================================================================*/ -static bool qmkusb_start_receive(QMKUSBDriver *qmkusbp) { - uint8_t *buf; - +static void usb_start_receive(usb_endpoint_out_t *endpoint) { /* If the USB driver is not in the appropriate state then transactions must not be started.*/ - if ((usbGetDriverStateI(qmkusbp->config->usbp) != USB_ACTIVE) || (qmkusbp->state != QMKUSB_READY)) { - return true; + if ((usbGetDriverStateI(endpoint->config.usbp) != USB_ACTIVE)) { + return; } /* Checking if there is already a transaction ongoing on the endpoint.*/ - if (usbGetReceiveStatusI(qmkusbp->config->usbp, qmkusbp->config->bulk_out)) { - return true; + if (usbGetReceiveStatusI(endpoint->config.usbp, endpoint->config.ep)) { + return; } /* Checking if there is a buffer ready for incoming data.*/ - buf = ibqGetEmptyBufferI(&qmkusbp->ibqueue); - if (buf == NULL) { - return true; + uint8_t *buffer = ibqGetEmptyBufferI(&endpoint->ibqueue); + if (buffer == NULL) { + return; } /* Buffer found, starting a new transaction.*/ - usbStartReceiveI(qmkusbp->config->usbp, qmkusbp->config->bulk_out, buf, qmkusbp->ibqueue.bsize - sizeof(size_t)); - - return false; + usbStartReceiveI(endpoint->config.usbp, endpoint->config.ep, buffer, endpoint->ibqueue.bsize - sizeof(size_t)); } -/* - * Interface implementation. - */ - -static size_t _write(void *ip, const uint8_t *bp, size_t n) { - return obqWriteTimeout(&((QMKUSBDriver *)ip)->obqueue, bp, n, TIME_INFINITE); -} - -static size_t _read(void *ip, uint8_t *bp, size_t n) { - return ibqReadTimeout(&((QMKUSBDriver *)ip)->ibqueue, bp, n, TIME_INFINITE); -} - -static msg_t _put(void *ip, uint8_t b) { - return obqPutTimeout(&((QMKUSBDriver *)ip)->obqueue, b, TIME_INFINITE); -} - -static msg_t _get(void *ip) { - return ibqGetTimeout(&((QMKUSBDriver *)ip)->ibqueue, TIME_INFINITE); -} - -static msg_t _putt(void *ip, uint8_t b, sysinterval_t timeout) { - return obqPutTimeout(&((QMKUSBDriver *)ip)->obqueue, b, timeout); -} - -static msg_t _gett(void *ip, sysinterval_t timeout) { - return ibqGetTimeout(&((QMKUSBDriver *)ip)->ibqueue, timeout); -} - -static size_t _writet(void *ip, const uint8_t *bp, size_t n, sysinterval_t timeout) { - return obqWriteTimeout(&((QMKUSBDriver *)ip)->obqueue, bp, n, timeout); -} - -static size_t _readt(void *ip, uint8_t *bp, size_t n, sysinterval_t timeout) { - return ibqReadTimeout(&((QMKUSBDriver *)ip)->ibqueue, bp, n, timeout); -} - -static const struct QMKUSBDriverVMT vmt = {0, _write, _read, _put, _get, _putt, _gett, _writet, _readt}; - /** * @brief Notification of empty buffer released into the input buffers queue. * * @param[in] bqp the buffers queue pointer. */ static void ibnotify(io_buffers_queue_t *bqp) { - QMKUSBDriver *qmkusbp = bqGetLinkX(bqp); - (void)qmkusb_start_receive(qmkusbp); + usb_endpoint_out_t *endpoint = bqGetLinkX(bqp); + usb_start_receive(endpoint); } /** @@ -130,22 +54,22 @@ static void ibnotify(io_buffers_queue_t *bqp) { * @param[in] bqp the buffers queue pointer. */ static void obnotify(io_buffers_queue_t *bqp) { - size_t n; - QMKUSBDriver *qmkusbp = bqGetLinkX(bqp); + usb_endpoint_in_t *endpoint = bqGetLinkX(bqp); - /* If the USB driver is not in the appropriate state then transactions + /* If the USB endpoint is not in the appropriate state then transactions must not be started.*/ - if ((usbGetDriverStateI(qmkusbp->config->usbp) != USB_ACTIVE) || (qmkusbp->state != QMKUSB_READY)) { + if ((usbGetDriverStateI(endpoint->config.usbp) != USB_ACTIVE)) { return; } /* Checking if there is already a transaction ongoing on the endpoint.*/ - if (!usbGetTransmitStatusI(qmkusbp->config->usbp, qmkusbp->config->bulk_in)) { + if (!usbGetTransmitStatusI(endpoint->config.usbp, endpoint->config.ep)) { /* Trying to get a full buffer.*/ - uint8_t *buf = obqGetFullBufferI(&qmkusbp->obqueue, &n); - if (buf != NULL) { + size_t n; + uint8_t *buffer = obqGetFullBufferI(&endpoint->obqueue, &n); + if (buffer != NULL) { /* Buffer found, starting a new transaction.*/ - usbStartTransmitI(qmkusbp->config->usbp, qmkusbp->config->bulk_in, buf, n); + usbStartTransmitI(endpoint->config.usbp, endpoint->config.ep, buffer, n); } } } @@ -154,264 +78,149 @@ static void obnotify(io_buffers_queue_t *bqp) { /* Driver exported functions. */ /*===========================================================================*/ -/** - * @brief Serial Driver initialization. - * @note This function is implicitly invoked by @p halInit(), there is - * no need to explicitly initialize the driver. - * - * @init - */ -void qmkusbInit(void) {} +void usb_endpoint_in_init(usb_endpoint_in_t *endpoint) { + usb_endpoint_config_t *config = &endpoint->config; + endpoint->ep_config.in_state = &endpoint->ep_in_state; -/** - * @brief Initializes a generic full duplex driver object. - * @details The HW dependent part of the initialization has to be performed - * outside, usually in the hardware initialization code. - * - * @param[out] qmkusbp pointer to a @p QMKUSBDriver structure - * - * @init - */ -void qmkusbObjectInit(QMKUSBDriver *qmkusbp, const QMKUSBConfig *config) { - qmkusbp->vmt = &vmt; - osalEventObjectInit(&qmkusbp->event); - qmkusbp->state = QMKUSB_STOP; - // Note that the config uses the USB direction naming - ibqObjectInit(&qmkusbp->ibqueue, true, config->ob, config->out_size, config->out_buffers, ibnotify, qmkusbp); - obqObjectInit(&qmkusbp->obqueue, true, config->ib, config->in_size, config->in_buffers, obnotify, qmkusbp); +#if defined(USB_ENDPOINTS_ARE_REORDERABLE) + if (endpoint->is_shared) { + endpoint->ep_config.out_state = &endpoint->ep_out_state; + } +#endif + obqObjectInit(&endpoint->obqueue, true, config->buffer, config->buffer_size, config->buffer_capacity, obnotify, endpoint); } -/** - * @brief Configures and starts the driver. - * - * @param[in] qmkusbp pointer to a @p QMKUSBDriver object - * @param[in] config the serial over USB driver configuration - * - * @api - */ -void qmkusbStart(QMKUSBDriver *qmkusbp, const QMKUSBConfig *config) { - USBDriver *usbp = config->usbp; +void usb_endpoint_out_init(usb_endpoint_out_t *endpoint) { + usb_endpoint_config_t *config = &endpoint->config; + endpoint->ep_config.out_state = &endpoint->ep_out_state; + ibqObjectInit(&endpoint->ibqueue, true, config->buffer, config->buffer_size, config->buffer_capacity, ibnotify, endpoint); +} - osalDbgCheck(qmkusbp != NULL); +void usb_endpoint_in_start(usb_endpoint_in_t *endpoint) { + osalDbgCheck(endpoint != NULL); osalSysLock(); - osalDbgAssert((qmkusbp->state == QMKUSB_STOP) || (qmkusbp->state == QMKUSB_READY), "invalid state"); - usbp->in_params[config->bulk_in - 1U] = qmkusbp; - usbp->out_params[config->bulk_out - 1U] = qmkusbp; - if (config->int_in > 0U) { - usbp->in_params[config->int_in - 1U] = qmkusbp; - } - qmkusbp->config = config; - qmkusbp->state = QMKUSB_READY; + osalDbgAssert((usbGetDriverStateI(endpoint->config.usbp) == USB_STOP) || (usbGetDriverStateI(endpoint->config.usbp) == USB_READY), "invalid state"); + endpoint->config.usbp->in_params[endpoint->config.ep - 1U] = endpoint; + endpoint->timed_out = false; osalSysUnlock(); } -/** - * @brief Stops the driver. - * @details Any thread waiting on the driver's queues will be awakened with - * the message @p MSG_RESET. - * - * @param[in] qmkusbp pointer to a @p QMKUSBDriver object - * - * @api - */ -void qmkusbStop(QMKUSBDriver *qmkusbp) { - USBDriver *usbp = qmkusbp->config->usbp; - - osalDbgCheck(qmkusbp != NULL); +void usb_endpoint_out_start(usb_endpoint_out_t *endpoint) { + osalDbgCheck(endpoint != NULL); osalSysLock(); + osalDbgAssert((usbGetDriverStateI(endpoint->config.usbp) == USB_STOP) || (usbGetDriverStateI(endpoint->config.usbp) == USB_READY), "invalid state"); + endpoint->config.usbp->out_params[endpoint->config.ep - 1U] = endpoint; + endpoint->timed_out = false; + osalSysUnlock(); +} - osalDbgAssert((qmkusbp->state == QMKUSB_STOP) || (qmkusbp->state == QMKUSB_READY), "invalid state"); +void usb_endpoint_in_stop(usb_endpoint_in_t *endpoint) { + osalDbgCheck(endpoint != NULL); - /* Driver in stopped state.*/ - usbp->in_params[qmkusbp->config->bulk_in - 1U] = NULL; - usbp->out_params[qmkusbp->config->bulk_out - 1U] = NULL; - if (qmkusbp->config->int_in > 0U) { - usbp->in_params[qmkusbp->config->int_in - 1U] = NULL; + osalSysLock(); + endpoint->config.usbp->in_params[endpoint->config.ep - 1U] = NULL; + + bqSuspendI(&endpoint->obqueue); + obqResetI(&endpoint->obqueue); + if (endpoint->report_storage != NULL) { + endpoint->report_storage->reset_report(endpoint->report_storage->reports); } - qmkusbp->config = NULL; - qmkusbp->state = QMKUSB_STOP; - - /* Enforces a disconnection.*/ - chnAddFlagsI(qmkusbp, CHN_DISCONNECTED); - ibqResetI(&qmkusbp->ibqueue); - obqResetI(&qmkusbp->obqueue); osalOsRescheduleS(); - osalSysUnlock(); } -/** - * @brief USB device suspend handler. - * @details Generates a @p CHN_DISCONNECT event and puts queues in - * non-blocking mode, this way the application cannot get stuck - * in the middle of an I/O operations. - * @note If this function is not called from an ISR then an explicit call - * to @p osalOsRescheduleS() in necessary afterward. - * - * @param[in] qmkusbp pointer to a @p QMKUSBDriver object - * - * @iclass - */ -void qmkusbSuspendHookI(QMKUSBDriver *qmkusbp) { - chnAddFlagsI(qmkusbp, CHN_DISCONNECTED); - bqSuspendI(&qmkusbp->ibqueue); - bqSuspendI(&qmkusbp->obqueue); +void usb_endpoint_out_stop(usb_endpoint_out_t *endpoint) { + osalDbgCheck(endpoint != NULL); + + osalSysLock(); + osalDbgAssert((usbGetDriverStateI(endpoint->config.usbp) == USB_STOP) || (usbGetDriverStateI(endpoint->config.usbp) == USB_READY), "invalid state"); + + bqSuspendI(&endpoint->ibqueue); + ibqResetI(&endpoint->ibqueue); + osalOsRescheduleS(); + osalSysUnlock(); } -/** - * @brief USB device wakeup handler. - * @details Generates a @p CHN_CONNECT event and resumes normal queues - * operations. - * - * @note If this function is not called from an ISR then an explicit call - * to @p osalOsRescheduleS() in necessary afterward. - * - * @param[in] qmkusbp pointer to a @p QMKUSBDriver object - * - * @iclass - */ -void qmkusbWakeupHookI(QMKUSBDriver *qmkusbp) { - chnAddFlagsI(qmkusbp, CHN_CONNECTED); - bqResumeX(&qmkusbp->ibqueue); - bqResumeX(&qmkusbp->obqueue); -} +void usb_endpoint_in_suspend_cb(usb_endpoint_in_t *endpoint) { + bqSuspendI(&endpoint->obqueue); + obqResetI(&endpoint->obqueue); -/** - * @brief USB device configured handler. - * - * @param[in] qmkusbp pointer to a @p QMKUSBDriver object - * - * @iclass - */ -void qmkusbConfigureHookI(QMKUSBDriver *qmkusbp) { - ibqResetI(&qmkusbp->ibqueue); - bqResumeX(&qmkusbp->ibqueue); - obqResetI(&qmkusbp->obqueue); - bqResumeX(&qmkusbp->obqueue); - chnAddFlagsI(qmkusbp, CHN_CONNECTED); - (void)qmkusb_start_receive(qmkusbp); -} - -/** - * @brief Default requests hook. - * @details Applications wanting to use the Serial over USB driver can use - * this function as requests hook in the USB configuration. - * The following requests are emulated: - * - CDC_GET_LINE_CODING. - * - CDC_SET_LINE_CODING. - * - CDC_SET_CONTROL_LINE_STATE. - * . - * - * @param[in] usbp pointer to the @p USBDriver object - * @return The hook status. - * @retval true Message handled internally. - * @retval false Message not handled. - */ -bool qmkusbRequestsHook(USBDriver *usbp) { - if ((usbp->setup[0] & USB_RTYPE_TYPE_MASK) == USB_RTYPE_TYPE_CLASS) { - switch (usbp->setup[1]) { - case CDC_GET_LINE_CODING: - usbSetupTransfer(usbp, (uint8_t *)&linecoding, sizeof(linecoding), NULL); - return true; - case CDC_SET_LINE_CODING: - usbSetupTransfer(usbp, (uint8_t *)&linecoding, sizeof(linecoding), NULL); - return true; - case CDC_SET_CONTROL_LINE_STATE: - /* Nothing to do, there are no control lines.*/ - usbSetupTransfer(usbp, NULL, 0, NULL); - return true; - default: - return false; - } - } - return false; -} - -/** - * @brief SOF handler. - * @details The SOF interrupt is used for automatic flushing of incomplete - * buffers pending in the output queue. - * - * @param[in] qmkusbp pointer to a @p QMKUSBDriver object - * - * @iclass - */ -void qmkusbSOFHookI(QMKUSBDriver *qmkusbp) { - /* If the USB driver is not in the appropriate state then transactions - must not be started.*/ - if ((usbGetDriverStateI(qmkusbp->config->usbp) != USB_ACTIVE) || (qmkusbp->state != QMKUSB_READY)) { - return; - } - - /* If there is already a transaction ongoing then another one cannot be - started.*/ - if (usbGetTransmitStatusI(qmkusbp->config->usbp, qmkusbp->config->bulk_in)) { - return; - } - - /* Checking if there only a buffer partially filled, if so then it is - enforced in the queue and transmitted.*/ - if (obqTryFlushI(&qmkusbp->obqueue)) { - size_t n; - uint8_t *buf = obqGetFullBufferI(&qmkusbp->obqueue, &n); - - /* For fixed size drivers, fill the end with zeros */ - if (qmkusbp->config->fixed_size) { - memset(buf + n, 0, qmkusbp->config->in_size - n); - n = qmkusbp->config->in_size; - } - - osalDbgAssert(buf != NULL, "queue is empty"); - - usbStartTransmitI(qmkusbp->config->usbp, qmkusbp->config->bulk_in, buf, n); + if (endpoint->report_storage != NULL) { + endpoint->report_storage->reset_report(endpoint->report_storage->reports); } } -/** - * @brief Default data transmitted callback. - * @details The application must use this function as callback for the IN - * data endpoint. - * - * @param[in] usbp pointer to the @p USBDriver object - * @param[in] ep IN endpoint number - */ -void qmkusbDataTransmitted(USBDriver *usbp, usbep_t ep) { - uint8_t * buf; - size_t n; - QMKUSBDriver *qmkusbp = usbp->in_params[ep - 1U]; +void usb_endpoint_out_suspend_cb(usb_endpoint_out_t *endpoint) { + bqSuspendI(&endpoint->ibqueue); + ibqResetI(&endpoint->ibqueue); +} - if (qmkusbp == NULL) { +void usb_endpoint_in_wakeup_cb(usb_endpoint_in_t *endpoint) { + bqResumeX(&endpoint->obqueue); +} + +void usb_endpoint_out_wakeup_cb(usb_endpoint_out_t *endpoint) { + bqResumeX(&endpoint->ibqueue); +} + +void usb_endpoint_in_configure_cb(usb_endpoint_in_t *endpoint) { + usbInitEndpointI(endpoint->config.usbp, endpoint->config.ep, &endpoint->ep_config); + obqResetI(&endpoint->obqueue); + bqResumeX(&endpoint->obqueue); +} + +void usb_endpoint_out_configure_cb(usb_endpoint_out_t *endpoint) { + /* The current assumption is that there are no standalone OUT endpoints, + * therefore if we share an endpoint with an IN endpoint, it is already + * initialized. */ +#if !defined(USB_ENDPOINTS_ARE_REORDERABLE) + usbInitEndpointI(endpoint->config.usbp, endpoint->config.ep, &endpoint->ep_config); +#endif + ibqResetI(&endpoint->ibqueue); + bqResumeX(&endpoint->ibqueue); + (void)usb_start_receive(endpoint); +} + +void usb_endpoint_in_tx_complete_cb(USBDriver *usbp, usbep_t ep) { + usb_endpoint_in_t *endpoint = usbp->in_params[ep - 1U]; + size_t n; + uint8_t * buffer; + + if (endpoint == NULL) { return; } osalSysLockFromISR(); - /* Signaling that space is available in the output queue.*/ - chnAddFlagsI(qmkusbp, CHN_OUTPUT_EMPTY); + /* Sending succeded, so we can reset the timed out state. */ + endpoint->timed_out = false; /* Freeing the buffer just transmitted, if it was not a zero size packet.*/ - if (usbp->epc[ep]->in_state->txsize > 0U) { - obqReleaseEmptyBufferI(&qmkusbp->obqueue); + if (!obqIsEmptyI(&endpoint->obqueue) && usbp->epc[ep]->in_state->txsize > 0U) { + /* Store the last send report in the endpoint to be retrieved by a + * GET_REPORT request or IDLE report handling. */ + if (endpoint->report_storage != NULL) { + buffer = obqGetFullBufferI(&endpoint->obqueue, &n); + endpoint->report_storage->set_report(endpoint->report_storage->reports, buffer, n); + } + obqReleaseEmptyBufferI(&endpoint->obqueue); } /* Checking if there is a buffer ready for transmission.*/ - buf = obqGetFullBufferI(&qmkusbp->obqueue, &n); + buffer = obqGetFullBufferI(&endpoint->obqueue, &n); - if (buf != NULL) { + if (buffer != NULL) { /* The endpoint cannot be busy, we are in the context of the callback, so it is safe to transmit without a check.*/ - usbStartTransmitI(usbp, ep, buf, n); - } else if ((usbp->epc[ep]->in_state->txsize > 0U) && ((usbp->epc[ep]->in_state->txsize & ((size_t)usbp->epc[ep]->in_maxsize - 1U)) == 0U)) { + usbStartTransmitI(usbp, ep, buffer, n); + } else if ((usbp->epc[ep]->ep_mode == USB_EP_MODE_TYPE_BULK) && (usbp->epc[ep]->in_state->txsize > 0U) && ((usbp->epc[ep]->in_state->txsize & ((size_t)usbp->epc[ep]->in_maxsize - 1U)) == 0U)) { /* Transmit zero sized packet in case the last one has maximum allowed - size. Otherwise the recipient may expect more data coming soon and - not return buffered data to app. See section 5.8.3 Bulk Transfer - Packet Size Constraints of the USB Specification document.*/ - if (!qmkusbp->config->fixed_size) { - usbStartTransmitI(usbp, ep, usbp->setup, 0); - } - + * size. Otherwise the recipient may expect more data coming soon and + * not return buffered data to app. See section 5.8.3 Bulk Transfer + * Packet Size Constraints of the USB Specification document. */ + usbStartTransmitI(usbp, ep, usbp->setup, 0); } else { /* Nothing to transmit.*/ } @@ -419,47 +228,114 @@ void qmkusbDataTransmitted(USBDriver *usbp, usbep_t ep) { osalSysUnlockFromISR(); } -/** - * @brief Default data received callback. - * @details The application must use this function as callback for the OUT - * data endpoint. - * - * @param[in] usbp pointer to the @p USBDriver object - * @param[in] ep OUT endpoint number - */ -void qmkusbDataReceived(USBDriver *usbp, usbep_t ep) { - QMKUSBDriver *qmkusbp = usbp->out_params[ep - 1U]; - if (qmkusbp == NULL) { +void usb_endpoint_out_rx_complete_cb(USBDriver *usbp, usbep_t ep) { + usb_endpoint_out_t *endpoint = usbp->out_params[ep - 1U]; + if (endpoint == NULL) { return; } osalSysLockFromISR(); - /* Signaling that data is available in the input queue.*/ - chnAddFlagsI(qmkusbp, CHN_INPUT_AVAILABLE); + size_t size = usbGetReceiveTransactionSizeX(usbp, ep); + if (size > 0) { + /* Posting the filled buffer in the queue.*/ + ibqPostFullBufferI(&endpoint->ibqueue, usbGetReceiveTransactionSizeX(endpoint->config.usbp, endpoint->config.ep)); + } - /* Posting the filled buffer in the queue.*/ - ibqPostFullBufferI(&qmkusbp->ibqueue, usbGetReceiveTransactionSizeX(qmkusbp->config->usbp, qmkusbp->config->bulk_out)); - - /* The endpoint cannot be busy, we are in the context of the callback, - so a packet is in the buffer for sure. Trying to get a free buffer - for the next transaction.*/ - (void)qmkusb_start_receive(qmkusbp); + /* The endpoint cannot be busy, we are in the context of the callback, so a + * packet is in the buffer for sure. Trying to get a free buffer for the + * next transaction.*/ + usb_start_receive(endpoint); osalSysUnlockFromISR(); } -/** - * @brief Default data received callback. - * @details The application must use this function as callback for the IN - * interrupt endpoint. - * - * @param[in] usbp pointer to the @p USBDriver object - * @param[in] ep endpoint number - */ -void qmkusbInterruptTransmitted(USBDriver *usbp, usbep_t ep) { - (void)usbp; - (void)ep; +bool usb_endpoint_in_send(usb_endpoint_in_t *endpoint, const uint8_t *data, size_t size, sysinterval_t timeout, bool buffered) { + osalDbgCheck((endpoint != NULL) && (data != NULL) && (size > 0U) && (size <= endpoint->config.buffer_size)); + + osalSysLock(); + if (usbGetDriverStateI(endpoint->config.usbp) != USB_ACTIVE) { + osalSysUnlock(); + return false; + } + + /* Short circuit the waiting if this endpoint timed out before, e.g. if + * nobody is listening on this endpoint (is disconnected) such as + * `hid_listen`/`qmk console` or we are in an environment with a very + * restricted USB stack. The reason is to not introduce micro lock-ups if + * the report is send periodically. */ + if (endpoint->timed_out && timeout != TIME_INFINITE) { + timeout = TIME_IMMEDIATE; + } + osalSysUnlock(); + + while (true) { + size_t sent = obqWriteTimeout(&endpoint->obqueue, data, size, timeout); + + if (sent < size) { + osalSysLock(); + endpoint->timed_out |= sent == 0; + bqSuspendI(&endpoint->obqueue); + obqResetI(&endpoint->obqueue); + bqResumeX(&endpoint->obqueue); + osalOsRescheduleS(); + osalSysUnlock(); + continue; + } + + if (!buffered) { + obqFlush(&endpoint->obqueue); + } + + return true; + } } -/** @} */ +void usb_endpoint_in_flush(usb_endpoint_in_t *endpoint, bool padded) { + osalDbgCheck(endpoint != NULL); + + output_buffers_queue_t *obqp = &endpoint->obqueue; + + if (padded && obqp->ptr != NULL) { + ptrdiff_t bytes_left = (size_t)obqp->top - (size_t)obqp->ptr; + while (bytes_left > 0) { + // Putting bytes into a buffer that has space left should never + // fail and be instant, therefore we don't check the return value + // for errors here. + obqPutTimeout(obqp, 0, TIME_IMMEDIATE); + bytes_left--; + } + } + + obqFlush(obqp); +} + +bool usb_endpoint_in_is_inactive(usb_endpoint_in_t *endpoint) { + osalDbgCheck(endpoint != NULL); + + osalSysLock(); + bool inactive = obqIsEmptyI(&endpoint->obqueue) && !usbGetTransmitStatusI(endpoint->config.usbp, endpoint->config.ep); + osalSysUnlock(); + + return inactive; +} + +bool usb_endpoint_out_receive(usb_endpoint_out_t *endpoint, uint8_t *data, size_t size, sysinterval_t timeout) { + osalDbgCheck((endpoint != NULL) && (data != NULL) && (size > 0U)); + + osalSysLock(); + if (usbGetDriverStateI(endpoint->config.usbp) != USB_ACTIVE) { + osalSysUnlock(); + return false; + } + + if (endpoint->timed_out && timeout != TIME_INFINITE) { + timeout = TIME_IMMEDIATE; + } + osalSysUnlock(); + + const size_t received = ibqReadTimeout(&endpoint->ibqueue, data, size, timeout); + endpoint->timed_out = received == 0; + + return received == size; +} diff --git a/tmk_core/protocol/chibios/usb_driver.h b/tmk_core/protocol/chibios/usb_driver.h index f94387debdc..26787e1eba0 100644 --- a/tmk_core/protocol/chibios/usb_driver.h +++ b/tmk_core/protocol/chibios/usb_driver.h @@ -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 - -/*===========================================================================*/ -/* Driver constants. */ -/*===========================================================================*/ - -/*===========================================================================*/ -/* Derived constants and error checks. */ -/*===========================================================================*/ +#include +#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 diff --git a/tmk_core/protocol/chibios/usb_endpoints.c b/tmk_core/protocol/chibios/usb_endpoints.c new file mode 100644 index 00000000000..37ec3c722f6 --- /dev/null +++ b/tmk_core/protocol/chibios/usb_endpoints.c @@ -0,0 +1,152 @@ +// Copyright 2023 Stefan Kerkmann (@KarlK90) +// SPDX-License-Identifier: GPL-3.0-or-later + +#include +#include + +#include "usb_main.h" +#include "usb_driver.h" +#include "usb_endpoints.h" +#include "report.h" + +usb_endpoint_in_t usb_endpoints_in[USB_ENDPOINT_IN_COUNT] = { +// clang-format off +#if defined(SHARED_EP_ENABLE) + [USB_ENDPOINT_IN_SHARED] = QMK_USB_ENDPOINT_IN(USB_EP_MODE_TYPE_INTR, SHARED_EPSIZE, SHARED_IN_EPNUM, SHARED_IN_CAPACITY, NULL, + QMK_USB_REPORT_STORAGE( + &usb_shared_get_report, + &usb_shared_set_report, + &usb_shared_reset_report, + &usb_shared_get_idle_rate, + &usb_shared_set_idle_rate, + &usb_shared_idle_timer_elapsed, + (REPORT_ID_COUNT + 1), +#if defined(KEYBOARD_SHARED_EP) + QMK_USB_REPORT_STROAGE_ENTRY(REPORT_ID_KEYBOARD, sizeof(report_keyboard_t)), +#endif +#if defined(MOUSE_SHARED_EP) + QMK_USB_REPORT_STROAGE_ENTRY(REPORT_ID_MOUSE, sizeof(report_mouse_t)), +#endif +#if defined(EXTRAKEY_ENABLE) + QMK_USB_REPORT_STROAGE_ENTRY(REPORT_ID_SYSTEM, sizeof(report_extra_t)), + QMK_USB_REPORT_STROAGE_ENTRY(REPORT_ID_CONSUMER, sizeof(report_extra_t)), +#endif +#if defined(PROGRAMMABLE_BUTTON_ENABLE) + QMK_USB_REPORT_STROAGE_ENTRY(REPORT_ID_PROGRAMMABLE_BUTTON, sizeof(report_programmable_button_t)), +#endif +#if defined(NKRO_ENABLE) + QMK_USB_REPORT_STROAGE_ENTRY(REPORT_ID_NKRO, sizeof(report_nkro_t)), +#endif +#if defined(JOYSTICK_SHARED_EP) + QMK_USB_REPORT_STROAGE_ENTRY(REPORT_ID_JOYSTICK, sizeof(report_joystick_t)), +#endif +#if defined(DIGITIZER_SHARED_EP) + QMK_USB_REPORT_STROAGE_ENTRY(REPORT_ID_DIGITIZER, sizeof(report_digitizer_t)), +#endif + ) + ), +#endif +// clang-format on + +#if !defined(KEYBOARD_SHARED_EP) + [USB_ENDPOINT_IN_KEYBOARD] = QMK_USB_ENDPOINT_IN(USB_EP_MODE_TYPE_INTR, KEYBOARD_EPSIZE, KEYBOARD_IN_EPNUM, KEYBOARD_IN_CAPACITY, NULL, QMK_USB_REPORT_STORAGE_DEFAULT(sizeof(report_keyboard_t))), +#endif + +#if defined(MOUSE_ENABLE) && !defined(MOUSE_SHARED_EP) + [USB_ENDPOINT_IN_MOUSE] = QMK_USB_ENDPOINT_IN(USB_EP_MODE_TYPE_INTR, MOUSE_EPSIZE, MOUSE_IN_EPNUM, MOUSE_IN_CAPACITY, NULL, QMK_USB_REPORT_STORAGE_DEFAULT(sizeof(report_mouse_t))), +#endif + +#if defined(JOYSTICK_ENABLE) && !defined(JOYSTICK_SHARED_EP) + [USB_ENDPOINT_IN_JOYSTICK] = QMK_USB_ENDPOINT_IN(USB_EP_MODE_TYPE_INTR, JOYSTICK_EPSIZE, JOYSTICK_IN_EPNUM, JOYSTICK_IN_CAPACITY, QMK_USB_REPORT_STORAGE_DEFAULT(sizeof(report_joystick_t))), +#endif + +#if defined(DIGITIZER_ENABLE) && !defined(DIGITIZER_SHARED_EP) + [USB_ENDPOINT_IN_JOYSTICK] = QMK_USB_ENDPOINT_IN(USB_EP_MODE_TYPE_INTR, DIGITIZER_EPSIZE, DIGITIZER_IN_EPNUM, DIGITIZER_IN_CAPACITY, QMK_USB_REPORT_STORAGE_DEFAULT(sizeof(report_digitizer_t))), +#endif + +#if defined(CONSOLE_ENABLE) +# if defined(USB_ENDPOINTS_ARE_REORDERABLE) + [USB_ENDPOINT_IN_CONSOLE] = QMK_USB_ENDPOINT_IN_SHARED(USB_EP_MODE_TYPE_INTR, CONSOLE_EPSIZE, CONSOLE_IN_EPNUM, CONSOLE_IN_CAPACITY, NULL, QMK_USB_REPORT_STORAGE_DEFAULT(CONSOLE_EPSIZE)), +# else + [USB_ENDPOINT_IN_CONSOLE] = QMK_USB_ENDPOINT_IN(USB_EP_MODE_TYPE_INTR, CONSOLE_EPSIZE, CONSOLE_IN_EPNUM, CONSOLE_IN_CAPACITY, NULL, QMK_USB_REPORT_STORAGE_DEFAULT(CONSOLE_EPSIZE)), +# endif +#endif + +#if defined(RAW_ENABLE) +# if defined(USB_ENDPOINTS_ARE_REORDERABLE) + [USB_ENDPOINT_IN_RAW] = QMK_USB_ENDPOINT_IN_SHARED(USB_EP_MODE_TYPE_INTR, RAW_EPSIZE, RAW_IN_EPNUM, RAW_IN_CAPACITY, NULL, QMK_USB_REPORT_STORAGE_DEFAULT(RAW_EPSIZE)), +# else + [USB_ENDPOINT_IN_RAW] = QMK_USB_ENDPOINT_IN(USB_EP_MODE_TYPE_INTR, RAW_EPSIZE, RAW_IN_EPNUM, RAW_IN_CAPACITY, NULL, QMK_USB_REPORT_STORAGE_DEFAULT(RAW_EPSIZE)), +# endif +#endif + +#if defined(MIDI_ENABLE) +# if defined(USB_ENDPOINTS_ARE_REORDERABLE) + [USB_ENDPOINT_IN_MIDI] = QMK_USB_ENDPOINT_IN_SHARED(USB_EP_MODE_TYPE_BULK, MIDI_STREAM_EPSIZE, MIDI_STREAM_IN_EPNUM, MIDI_STREAM_IN_CAPACITY, NULL, NULL), +# else + [USB_ENDPOINT_IN_MIDI] = QMK_USB_ENDPOINT_IN(USB_EP_MODE_TYPE_BULK, MIDI_STREAM_EPSIZE, MIDI_STREAM_IN_EPNUM, MIDI_STREAM_IN_CAPACITY, NULL, NULL), +# endif +#endif + +#if defined(VIRTSER_ENABLE) +# if defined(USB_ENDPOINTS_ARE_REORDERABLE) + [USB_ENDPOINT_IN_CDC_DATA] = QMK_USB_ENDPOINT_IN_SHARED(USB_EP_MODE_TYPE_BULK, CDC_EPSIZE, CDC_IN_EPNUM, CDC_IN_CAPACITY, virtser_usb_request_cb, NULL), +# else + [USB_ENDPOINT_IN_CDC_DATA] = QMK_USB_ENDPOINT_IN(USB_EP_MODE_TYPE_BULK, CDC_EPSIZE, CDC_IN_EPNUM, CDC_IN_CAPACITY, virtser_usb_request_cb, NULL), +# endif + [USB_ENDPOINT_IN_CDC_SIGNALING] = QMK_USB_ENDPOINT_IN(USB_EP_MODE_TYPE_INTR, CDC_NOTIFICATION_EPSIZE, CDC_NOTIFICATION_EPNUM, CDC_SIGNALING_DUMMY_CAPACITY, NULL, NULL), +#endif +}; + +usb_endpoint_in_lut_t usb_endpoint_interface_lut[TOTAL_INTERFACES] = { +#if !defined(KEYBOARD_SHARED_EP) + [KEYBOARD_INTERFACE] = USB_ENDPOINT_IN_KEYBOARD, +#endif + +#if defined(RAW_ENABLE) + [RAW_INTERFACE] = USB_ENDPOINT_IN_RAW, +#endif + +#if defined(MOUSE_ENABLE) && !defined(MOUSE_SHARED_EP) + [MOUSE_INTERFACE] = USB_ENDPOINT_IN_MOUSE, +#endif + +#if defined(SHARED_EP_ENABLE) + [SHARED_INTERFACE] = USB_ENDPOINT_IN_SHARED, +#endif + +#if defined(CONSOLE_ENABLE) + [CONSOLE_INTERFACE] = USB_ENDPOINT_IN_CONSOLE, +#endif + +#if defined(MIDI_ENABLE) + [AS_INTERFACE] = USB_ENDPOINT_IN_MIDI, +#endif + +#if defined(VIRTSER_ENABLE) + [CCI_INTERFACE] = USB_ENDPOINT_IN_CDC_SIGNALING, + [CDI_INTERFACE] = USB_ENDPOINT_IN_CDC_DATA, +#endif + +#if defined(JOYSTICK_ENABLE) && !defined(JOYSTICK_SHARED_EP) + [JOYSTICK_INTERFACE] = USB_ENDPOINT_IN_JOYSTICK, +#endif + +#if defined(DIGITIZER_ENABLE) && !defined(DIGITIZER_SHARED_EP) + [DIGITIZER_INTERFACE] = USB_ENDPOINT_IN_DIGITIZER, +#endif +}; + +usb_endpoint_out_t usb_endpoints_out[USB_ENDPOINT_OUT_COUNT] = { +#if defined(RAW_ENABLE) + [USB_ENDPOINT_OUT_RAW] = QMK_USB_ENDPOINT_OUT(USB_EP_MODE_TYPE_INTR, RAW_EPSIZE, RAW_OUT_EPNUM, RAW_OUT_CAPACITY), +#endif + +#if defined(MIDI_ENABLE) + [USB_ENDPOINT_OUT_MIDI] = QMK_USB_ENDPOINT_OUT(USB_EP_MODE_TYPE_BULK, MIDI_STREAM_EPSIZE, MIDI_STREAM_OUT_EPNUM, MIDI_STREAM_OUT_CAPACITY), +#endif + +#if defined(VIRTSER_ENABLE) + [USB_ENDPOINT_OUT_CDC_DATA] = QMK_USB_ENDPOINT_OUT(USB_EP_MODE_TYPE_BULK, CDC_EPSIZE, CDC_OUT_EPNUM, CDC_OUT_CAPACITY), +#endif +}; diff --git a/tmk_core/protocol/chibios/usb_endpoints.h b/tmk_core/protocol/chibios/usb_endpoints.h new file mode 100644 index 00000000000..a4e5ed88fce --- /dev/null +++ b/tmk_core/protocol/chibios/usb_endpoints.h @@ -0,0 +1,137 @@ +// Copyright 2023 Stefan Kerkmann (@KarlK90) +// SPDX-License-Identifier: GPL-3.0-or-later + +#pragma once + +#include "usb_descriptor.h" + +#if !defined(USB_DEFAULT_BUFFER_CAPACITY) +# define USB_DEFAULT_BUFFER_CAPACITY 4 +#endif + +#if !defined(KEYBOARD_IN_CAPACITY) +# define KEYBOARD_IN_CAPACITY USB_DEFAULT_BUFFER_CAPACITY +#endif +#if !defined(SHARED_IN_CAPACITY) +# define SHARED_IN_CAPACITY USB_DEFAULT_BUFFER_CAPACITY +#endif +#if !defined(MOUSE_IN_CAPACITY) +# define MOUSE_IN_CAPACITY USB_DEFAULT_BUFFER_CAPACITY +#endif + +#if !defined(JOYSTICK_IN_CAPACITY) +# define JOYSTICK_IN_CAPACITY USB_DEFAULT_BUFFER_CAPACITY +#endif + +#if !defined(DIGITIZER_IN_CAPACITY) +# define DIGITIZER_IN_CAPACITY USB_DEFAULT_BUFFER_CAPACITY +#endif + +#if !defined(CONSOLE_IN_CAPACITY) +# define CONSOLE_IN_CAPACITY USB_DEFAULT_BUFFER_CAPACITY +#endif + +#if !defined(CONSOLE_OUT_CAPACITY) +# define CONSOLE_OUT_CAPACITY USB_DEFAULT_BUFFER_CAPACITY +#endif + +#if !defined(RAW_IN_CAPACITY) +# define RAW_IN_CAPACITY USB_DEFAULT_BUFFER_CAPACITY +#endif + +#if !defined(RAW_OUT_CAPACITY) +# define RAW_OUT_CAPACITY USB_DEFAULT_BUFFER_CAPACITY +#endif + +#if !defined(MIDI_STREAM_IN_CAPACITY) +# define MIDI_STREAM_IN_CAPACITY USB_DEFAULT_BUFFER_CAPACITY +#endif + +#if !defined(MIDI_STREAM_OUT_CAPACITY) +# define MIDI_STREAM_OUT_CAPACITY USB_DEFAULT_BUFFER_CAPACITY +#endif + +#if !defined(CDC_IN_CAPACITY) +# define CDC_IN_CAPACITY USB_DEFAULT_BUFFER_CAPACITY +#endif + +#if !defined(CDC_OUT_CAPACITY) +# define CDC_OUT_CAPACITY USB_DEFAULT_BUFFER_CAPACITY +#endif + +#define CDC_SIGNALING_DUMMY_CAPACITY 1 + +typedef enum { +#if defined(SHARED_EP_ENABLE) + USB_ENDPOINT_IN_SHARED, +#endif + +#if !defined(KEYBOARD_SHARED_EP) + USB_ENDPOINT_IN_KEYBOARD, +#endif + +#if defined(MOUSE_ENABLE) && !defined(MOUSE_SHARED_EP) + USB_ENDPOINT_IN_MOUSE, +#endif + +#if defined(JOYSTICK_ENABLE) && !defined(JOYSTICK_SHARED_EP) + USB_ENDPOINT_IN_JOYSTICK, +#endif + +#if defined(DIGITIZER_ENABLE) && !defined(DIGITIZER_SHARED_EP) + USB_ENDPOINT_IN_DIGITIZER, +#endif + +#if defined(CONSOLE_ENABLE) + USB_ENDPOINT_IN_CONSOLE, +#endif + +#if defined(RAW_ENABLE) + USB_ENDPOINT_IN_RAW, +#endif + +#if defined(MIDI_ENABLE) + USB_ENDPOINT_IN_MIDI, +#endif + +#if defined(VIRTSER_ENABLE) + USB_ENDPOINT_IN_CDC_DATA, + USB_ENDPOINT_IN_CDC_SIGNALING, +#endif + USB_ENDPOINT_IN_COUNT, +/* All non shared endpoints have to be consequtive numbers starting from 0, so + * that they can be used as array indices. The shared endpoints all point to + * the same endpoint so they have to be defined last to not reset the enum + * counter. */ +#if defined(SHARED_EP_ENABLE) +# if defined(KEYBOARD_SHARED_EP) + USB_ENDPOINT_IN_KEYBOARD = USB_ENDPOINT_IN_SHARED, +# endif +# if defined(MOUSE_SHARED_EP) + USB_ENDPOINT_IN_MOUSE = USB_ENDPOINT_IN_SHARED, +# endif +# if defined(JOYSTICK_SHARED_EP) + USB_ENDPOINT_IN_JOYSTICK = USB_ENDPOINT_IN_SHARED, +# endif +# if defined(DIGITIZER_SHARED_EP) + USB_ENDPOINT_IN_DIGITIZER = USB_ENDPOINT_IN_SHARED, +# endif +#endif +} usb_endpoint_in_lut_t; + +#define IS_VALID_USB_ENDPOINT_IN_LUT(i) ((i) >= 0 && (i) < USB_ENDPOINT_IN_COUNT) + +usb_endpoint_in_lut_t usb_endpoint_interface_lut[TOTAL_INTERFACES]; + +typedef enum { +#if defined(RAW_ENABLE) + USB_ENDPOINT_OUT_RAW, +#endif +#if defined(MIDI_ENABLE) + USB_ENDPOINT_OUT_MIDI, +#endif +#if defined(VIRTSER_ENABLE) + USB_ENDPOINT_OUT_CDC_DATA, +#endif + USB_ENDPOINT_OUT_COUNT, +} usb_endpoint_out_lut_t; diff --git a/tmk_core/protocol/chibios/usb_main.c b/tmk_core/protocol/chibios/usb_main.c index 7b1e6412131..ced5fd4fc27 100644 --- a/tmk_core/protocol/chibios/usb_main.c +++ b/tmk_core/protocol/chibios/usb_main.c @@ -1,45 +1,32 @@ -/* - * (c) 2015 flabberast - * - * Based on the following work: - * - Guillaume Duc's raw hid example (MIT License) - * https://github.com/guiduc/usb-hid-chibios-example - * - PJRC Teensy examples (MIT License) - * https://www.pjrc.com/teensy/usb_keyboard.html - * - hasu's TMK keyboard code (GPL v2 and some code Modified BSD) - * https://github.com/tmk/tmk_keyboard/ - * - ChibiOS demo code (Apache 2.0 License) - * http://www.chibios.org - * - * Since some GPL'd code is used, this work is licensed under - * GPL v2 or later. - */ - -/* - * 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. - */ +// Copyright 2023 Stefan Kerkmann +// Copyright 2020-2021 Ryan (@fauxpark) +// Copyright 2020 Nick Brassel (@tzarc) +// Copyright 2020 a-chol +// Copyright 2020 xyzz +// Copyright 2020 Joel Challis (@zvecr) +// Copyright 2020 George (@goshdarnharris) +// Copyright 2018 James Laird-Wah +// Copyright 2018 Drashna Jaelre (@drashna) +// Copyright 2016 Fredizzimo +// Copyright 2016 Giovanni Di Sirio +// SPDX-License-Identifier: GPL-3.0-or-later OR Apache-2.0 #include #include #include #include "usb_main.h" +#include "usb_report_handling.h" #include "host.h" -#include "chibios_config.h" -#include "debug.h" #include "suspend.h" +#include "timer.h" #ifdef SLEEP_LED_ENABLE # include "sleep_led.h" # include "led.h" #endif #include "wait.h" +#include "usb_endpoints.h" #include "usb_device_state.h" #include "usb_descriptor.h" #include "usb_driver.h" @@ -51,11 +38,6 @@ extern keymap_config_t keymap_config; #endif -#if defined(CONSOLE_ENABLE) -# define RBUF_SIZE 256 -# include "ring_buffer.h" -#endif - /* --------------------------------------------------------- * Global interface variables and declarations * --------------------------------------------------------- @@ -69,33 +51,16 @@ extern keymap_config_t keymap_config; # define usb_lld_disconnect_bus(usbp) #endif -uint8_t keyboard_idle __attribute__((aligned(2))) = 0; -uint8_t keyboard_protocol __attribute__((aligned(2))) = 1; -uint8_t keyboard_led_state = 0; -volatile uint16_t keyboard_idle_count = 0; -static virtual_timer_t keyboard_idle_timer; +extern usb_endpoint_in_t usb_endpoints_in[USB_ENDPOINT_IN_COUNT]; +extern usb_endpoint_out_t usb_endpoints_out[USB_ENDPOINT_OUT_COUNT]; -static void keyboard_idle_timer_cb(struct ch_virtual_timer *, void *arg); +uint8_t _Alignas(2) keyboard_idle = 0; +uint8_t _Alignas(2) keyboard_protocol = 1; +uint8_t keyboard_led_state = 0; -report_keyboard_t keyboard_report_sent = {0}; -report_mouse_t mouse_report_sent = {0}; - -union { - uint8_t report_id; - report_keyboard_t keyboard; -#ifdef EXTRAKEY_ENABLE - report_extra_t extra; -#endif -#ifdef MOUSE_ENABLE - report_mouse_t mouse; -#endif -#ifdef DIGITIZER_ENABLE - report_digitizer_t digitizer; -#endif -#ifdef JOYSTICK_ENABLE - report_joystick_t joystick; -#endif -} universal_report_blank = {0}; +static bool __attribute__((__unused__)) send_report_buffered(usb_endpoint_in_lut_t endpoint, void *report, size_t size); +static void __attribute__((__unused__)) flush_report_buffered(usb_endpoint_in_lut_t endpoint, bool padded); +static bool __attribute__((__unused__)) receive_report(usb_endpoint_out_lut_t endpoint, void *report, size_t size); /* --------------------------------------------------------- * Descriptors and USB driver objects @@ -109,6 +74,11 @@ union { NULL, /* SETUP buffer (not a SETUP endpoint) */ #endif +/* + * Handles the GET_DESCRIPTOR callback + * + * Returns the proper descriptor + */ static const USBDescriptor *usb_get_descriptor_cb(USBDriver *usbp, uint8_t dtype, uint8_t dindex, uint16_t wIndex) { usb_control_request_t *setup = (usb_control_request_t *)usbp->setup; @@ -123,299 +93,6 @@ static const USBDescriptor *usb_get_descriptor_cb(USBDriver *usbp, uint8_t dtype return &descriptor; } -/* - * USB notification callback that does nothing. Needed to work around bugs in - * some USB LLDs that fail to resume the waiting thread when the notification - * callback pointer is NULL. - */ -static void dummy_usb_cb(USBDriver *usbp, usbep_t ep) { - (void)usbp; - (void)ep; -} - -#ifndef KEYBOARD_SHARED_EP -/* keyboard endpoint state structure */ -static USBInEndpointState kbd_ep_state; -/* keyboard endpoint initialization structure (IN) - see USBEndpointConfig comment at top of file */ -static const USBEndpointConfig kbd_ep_config = { - USB_EP_MODE_TYPE_INTR, /* Interrupt EP */ - NULL, /* SETUP packet notification callback */ - dummy_usb_cb, /* IN notification callback */ - NULL, /* OUT notification callback */ - KEYBOARD_EPSIZE, /* IN maximum packet size */ - 0, /* OUT maximum packet size */ - &kbd_ep_state, /* IN Endpoint state */ - NULL, /* OUT endpoint state */ - usb_lld_endpoint_fields /* USB driver specific endpoint fields */ -}; -#endif - -#if defined(MOUSE_ENABLE) && !defined(MOUSE_SHARED_EP) -/* mouse endpoint state structure */ -static USBInEndpointState mouse_ep_state; - -/* mouse endpoint initialization structure (IN) - see USBEndpointConfig comment at top of file */ -static const USBEndpointConfig mouse_ep_config = { - USB_EP_MODE_TYPE_INTR, /* Interrupt EP */ - NULL, /* SETUP packet notification callback */ - dummy_usb_cb, /* IN notification callback */ - NULL, /* OUT notification callback */ - MOUSE_EPSIZE, /* IN maximum packet size */ - 0, /* OUT maximum packet size */ - &mouse_ep_state, /* IN Endpoint state */ - NULL, /* OUT endpoint state */ - usb_lld_endpoint_fields /* USB driver specific endpoint fields */ -}; -#endif - -#ifdef SHARED_EP_ENABLE -/* shared endpoint state structure */ -static USBInEndpointState shared_ep_state; - -/* shared endpoint initialization structure (IN) - see USBEndpointConfig comment at top of file */ -static const USBEndpointConfig shared_ep_config = { - USB_EP_MODE_TYPE_INTR, /* Interrupt EP */ - NULL, /* SETUP packet notification callback */ - dummy_usb_cb, /* IN notification callback */ - NULL, /* OUT notification callback */ - SHARED_EPSIZE, /* IN maximum packet size */ - 0, /* OUT maximum packet size */ - &shared_ep_state, /* IN Endpoint state */ - NULL, /* OUT endpoint state */ - usb_lld_endpoint_fields /* USB driver specific endpoint fields */ -}; -#endif - -#if defined(JOYSTICK_ENABLE) && !defined(JOYSTICK_SHARED_EP) -/* joystick endpoint state structure */ -static USBInEndpointState joystick_ep_state; - -/* joystick endpoint initialization structure (IN) - see USBEndpointConfig comment at top of file */ -static const USBEndpointConfig joystick_ep_config = { - USB_EP_MODE_TYPE_INTR, /* Interrupt EP */ - NULL, /* SETUP packet notification callback */ - dummy_usb_cb, /* IN notification callback */ - NULL, /* OUT notification callback */ - JOYSTICK_EPSIZE, /* IN maximum packet size */ - 0, /* OUT maximum packet size */ - &joystick_ep_state, /* IN Endpoint state */ - NULL, /* OUT endpoint state */ - usb_lld_endpoint_fields /* USB driver specific endpoint fields */ -}; -#endif - -#if defined(DIGITIZER_ENABLE) && !defined(DIGITIZER_SHARED_EP) -/* digitizer endpoint state structure */ -static USBInEndpointState digitizer_ep_state; - -/* digitizer endpoint initialization structure (IN) - see USBEndpointConfig comment at top of file */ -static const USBEndpointConfig digitizer_ep_config = { - USB_EP_MODE_TYPE_INTR, /* Interrupt EP */ - NULL, /* SETUP packet notification callback */ - dummy_usb_cb, /* IN notification callback */ - NULL, /* OUT notification callback */ - DIGITIZER_EPSIZE, /* IN maximum packet size */ - 0, /* OUT maximum packet size */ - &digitizer_ep_state, /* IN Endpoint state */ - NULL, /* OUT endpoint state */ - usb_lld_endpoint_fields /* USB driver specific endpoint fields */ -}; -#endif - -#ifdef CONSOLE_ENABLE -/* Console endpoint state structure */ -static USBInEndpointState console_ep_state; - -/* Console endpoint initialization structure (IN) - see USBEndpointConfig comment at top of file */ -static const USBEndpointConfig console_ep_config = { - USB_EP_MODE_TYPE_INTR, /* Interrupt EP */ - NULL, /* SETUP packet notification callback */ - dummy_usb_cb, /* IN notification callback */ - NULL, /* OUT notification callback */ - CONSOLE_EPSIZE, /* IN maximum packet size */ - 0, /* OUT maximum packet size */ - &console_ep_state, /* IN Endpoint state */ - NULL, /* OUT endpoint state */ - usb_lld_endpoint_fields /* USB driver specific endpoint fields */ -}; -#endif - -#ifdef USB_ENDPOINTS_ARE_REORDERABLE -typedef struct { - size_t queue_capacity_in; - size_t queue_capacity_out; - USBInEndpointState in_ep_state; - USBOutEndpointState out_ep_state; - USBInEndpointState int_ep_state; - USBEndpointConfig inout_ep_config; - USBEndpointConfig int_ep_config; - const QMKUSBConfig config; - QMKUSBDriver driver; -} usb_driver_config_t; -#else -typedef struct { - size_t queue_capacity_in; - size_t queue_capacity_out; - USBInEndpointState in_ep_state; - USBOutEndpointState out_ep_state; - USBInEndpointState int_ep_state; - USBEndpointConfig in_ep_config; - USBEndpointConfig out_ep_config; - USBEndpointConfig int_ep_config; - const QMKUSBConfig config; - QMKUSBDriver driver; -} usb_driver_config_t; -#endif - -#ifdef USB_ENDPOINTS_ARE_REORDERABLE -/* Reusable initialization structure - see USBEndpointConfig comment at top of file */ -# define QMK_USB_DRIVER_CONFIG(stream, notification, fixedsize) \ - { \ - .queue_capacity_in = stream##_IN_CAPACITY, .queue_capacity_out = stream##_OUT_CAPACITY, \ - .inout_ep_config = \ - { \ - stream##_IN_MODE, /* Interrupt EP */ \ - NULL, /* SETUP packet notification callback */ \ - qmkusbDataTransmitted, /* IN notification callback */ \ - qmkusbDataReceived, /* OUT notification callback */ \ - stream##_EPSIZE, /* IN maximum packet size */ \ - stream##_EPSIZE, /* OUT maximum packet size */ \ - NULL, /* IN Endpoint state */ \ - NULL, /* OUT endpoint state */ \ - usb_lld_endpoint_fields /* USB driver specific endpoint fields */ \ - }, \ - .int_ep_config = \ - { \ - USB_EP_MODE_TYPE_INTR, /* Interrupt EP */ \ - NULL, /* SETUP packet notification callback */ \ - qmkusbInterruptTransmitted, /* IN notification callback */ \ - NULL, /* OUT notification callback */ \ - CDC_NOTIFICATION_EPSIZE, /* 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, \ - .bulk_in = stream##_IN_EPNUM, \ - .bulk_out = stream##_OUT_EPNUM, \ - .int_in = notification, \ - .in_buffers = stream##_IN_CAPACITY, \ - .out_buffers = stream##_OUT_CAPACITY, \ - .in_size = stream##_EPSIZE, \ - .out_size = stream##_EPSIZE, \ - .fixed_size = fixedsize, \ - .ib = (__attribute__((aligned(4))) uint8_t[BQ_BUFFER_SIZE(stream##_IN_CAPACITY, stream##_EPSIZE)]){}, \ - .ob = (__attribute__((aligned(4))) uint8_t[BQ_BUFFER_SIZE(stream##_OUT_CAPACITY, stream##_EPSIZE)]){}, \ - } \ - } -#else -/* Reusable initialization structure - see USBEndpointConfig comment at top of file */ -# define QMK_USB_DRIVER_CONFIG(stream, notification, fixedsize) \ - { \ - .queue_capacity_in = stream##_IN_CAPACITY, .queue_capacity_out = stream##_OUT_CAPACITY, \ - .in_ep_config = \ - { \ - stream##_IN_MODE, /* Interrupt EP */ \ - NULL, /* SETUP packet notification callback */ \ - qmkusbDataTransmitted, /* IN notification callback */ \ - NULL, /* OUT notification callback */ \ - stream##_EPSIZE, /* 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 */ \ - }, \ - .out_ep_config = \ - { \ - stream##_OUT_MODE, /* Interrupt EP */ \ - NULL, /* SETUP packet notification callback */ \ - NULL, /* IN notification callback */ \ - qmkusbDataReceived, /* OUT notification callback */ \ - 0, /* IN maximum packet size */ \ - stream##_EPSIZE, /* OUT maximum packet size */ \ - NULL, /* IN Endpoint state */ \ - NULL, /* OUT endpoint state */ \ - usb_lld_endpoint_fields /* USB driver specific endpoint fields */ \ - }, \ - .int_ep_config = \ - { \ - USB_EP_MODE_TYPE_INTR, /* Interrupt EP */ \ - NULL, /* SETUP packet notification callback */ \ - qmkusbInterruptTransmitted, /* IN notification callback */ \ - NULL, /* OUT notification callback */ \ - CDC_NOTIFICATION_EPSIZE, /* 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, \ - .bulk_in = stream##_IN_EPNUM, \ - .bulk_out = stream##_OUT_EPNUM, \ - .int_in = notification, \ - .in_buffers = stream##_IN_CAPACITY, \ - .out_buffers = stream##_OUT_CAPACITY, \ - .in_size = stream##_EPSIZE, \ - .out_size = stream##_EPSIZE, \ - .fixed_size = fixedsize, \ - .ib = (__attribute__((aligned(4))) uint8_t[BQ_BUFFER_SIZE(stream##_IN_CAPACITY, stream##_EPSIZE)]){}, \ - .ob = (__attribute__((aligned(4))) uint8_t[BQ_BUFFER_SIZE(stream##_OUT_CAPACITY, stream##_EPSIZE)]){}, \ - } \ - } -#endif - -typedef struct { - union { - struct { -#ifdef RAW_ENABLE - usb_driver_config_t raw_driver; -#endif -#ifdef MIDI_ENABLE - usb_driver_config_t midi_driver; -#endif -#ifdef VIRTSER_ENABLE - usb_driver_config_t serial_driver; -#endif - }; - usb_driver_config_t array[0]; - }; -} usb_driver_configs_t; - -static usb_driver_configs_t drivers = { -#ifdef RAW_ENABLE -# ifndef RAW_IN_CAPACITY -# define RAW_IN_CAPACITY 4 -# endif -# ifndef RAW_OUT_CAPACITY -# define RAW_OUT_CAPACITY 4 -# endif -# define RAW_IN_MODE USB_EP_MODE_TYPE_INTR -# define RAW_OUT_MODE USB_EP_MODE_TYPE_INTR - .raw_driver = QMK_USB_DRIVER_CONFIG(RAW, 0, false), -#endif - -#ifdef MIDI_ENABLE -# define MIDI_STREAM_IN_CAPACITY 4 -# define MIDI_STREAM_OUT_CAPACITY 4 -# define MIDI_STREAM_IN_MODE USB_EP_MODE_TYPE_BULK -# define MIDI_STREAM_OUT_MODE USB_EP_MODE_TYPE_BULK - .midi_driver = QMK_USB_DRIVER_CONFIG(MIDI_STREAM, 0, false), -#endif - -#ifdef VIRTSER_ENABLE -# define CDC_IN_CAPACITY 4 -# define CDC_OUT_CAPACITY 4 -# define CDC_IN_MODE USB_EP_MODE_TYPE_BULK -# define CDC_OUT_MODE USB_EP_MODE_TYPE_BULK - .serial_driver = QMK_USB_DRIVER_CONFIG(CDC, CDC_NOTIFICATION_EPNUM, false), -#endif -}; - -#define NUM_USB_DRIVERS (sizeof(drivers) / sizeof(usb_driver_config_t)) - /* --------------------------------------------------------- * USB driver functions * --------------------------------------------------------- @@ -507,36 +184,11 @@ static void usb_event_cb(USBDriver *usbp, usbevent_t event) { case USB_EVENT_CONFIGURED: osalSysLockFromISR(); - /* Enable the endpoints specified into the configuration. */ -#ifndef KEYBOARD_SHARED_EP - usbInitEndpointI(usbp, KEYBOARD_IN_EPNUM, &kbd_ep_config); -#endif -#if defined(MOUSE_ENABLE) && !defined(MOUSE_SHARED_EP) - usbInitEndpointI(usbp, MOUSE_IN_EPNUM, &mouse_ep_config); -#endif -#ifdef SHARED_EP_ENABLE - usbInitEndpointI(usbp, SHARED_IN_EPNUM, &shared_ep_config); -#endif -#if defined(JOYSTICK_ENABLE) && !defined(JOYSTICK_SHARED_EP) - usbInitEndpointI(usbp, JOYSTICK_IN_EPNUM, &joystick_ep_config); -#endif -#if defined(DIGITIZER_ENABLE) && !defined(DIGITIZER_SHARED_EP) - usbInitEndpointI(usbp, DIGITIZER_IN_EPNUM, &digitizer_ep_config); -#endif -#ifdef CONSOLE_ENABLE - usbInitEndpointI(usbp, CONSOLE_IN_EPNUM, &console_ep_config); -#endif - for (int i = 0; i < NUM_USB_DRIVERS; i++) { -#ifdef USB_ENDPOINTS_ARE_REORDERABLE - usbInitEndpointI(usbp, drivers.array[i].config.bulk_in, &drivers.array[i].inout_ep_config); -#else - usbInitEndpointI(usbp, drivers.array[i].config.bulk_in, &drivers.array[i].in_ep_config); - usbInitEndpointI(usbp, drivers.array[i].config.bulk_out, &drivers.array[i].out_ep_config); -#endif - if (drivers.array[i].config.int_in) { - usbInitEndpointI(usbp, drivers.array[i].config.int_in, &drivers.array[i].int_ep_config); - } - qmkusbConfigureHookI(&drivers.array[i].driver); + for (int i = 0; i < USB_ENDPOINT_IN_COUNT; i++) { + usb_endpoint_in_configure_cb(&usb_endpoints_in[i]); + } + for (int i = 0; i < USB_ENDPOINT_OUT_COUNT; i++) { + usb_endpoint_out_configure_cb(&usb_endpoints_out[i]); } osalSysUnlockFromISR(); if (last_suspend_state) { @@ -550,22 +202,25 @@ static void usb_event_cb(USBDriver *usbp, usbevent_t event) { /* Falls into.*/ case USB_EVENT_RESET: usb_event_queue_enqueue(event); - for (int i = 0; i < NUM_USB_DRIVERS; i++) { - chSysLockFromISR(); - /* Disconnection event on suspend.*/ - qmkusbSuspendHookI(&drivers.array[i].driver); - chSysUnlockFromISR(); + chSysLockFromISR(); + for (int i = 0; i < USB_ENDPOINT_IN_COUNT; i++) { + usb_endpoint_in_suspend_cb(&usb_endpoints_in[i]); } + for (int i = 0; i < USB_ENDPOINT_OUT_COUNT; i++) { + usb_endpoint_out_suspend_cb(&usb_endpoints_out[i]); + } + chSysUnlockFromISR(); return; case USB_EVENT_WAKEUP: - // TODO: from ISR! print("[W]"); - for (int i = 0; i < NUM_USB_DRIVERS; i++) { - chSysLockFromISR(); - /* Disconnection event on suspend.*/ - qmkusbWakeupHookI(&drivers.array[i].driver); - chSysUnlockFromISR(); + chSysLockFromISR(); + for (int i = 0; i < USB_ENDPOINT_IN_COUNT; i++) { + usb_endpoint_in_wakeup_cb(&usb_endpoints_in[i]); } + for (int i = 0; i < USB_ENDPOINT_OUT_COUNT; i++) { + usb_endpoint_out_wakeup_cb(&usb_endpoints_out[i]); + } + chSysUnlockFromISR(); usb_event_queue_enqueue(USB_EVENT_WAKEUP); return; @@ -587,7 +242,7 @@ static void usb_event_cb(USBDriver *usbp, usbevent_t event) { * Other Device Required Optional Optional Optional Optional Optional */ -static uint8_t set_report_buf[2] __attribute__((aligned(4))); +static uint8_t _Alignas(4) set_report_buf[2]; static void set_led_transfer_cb(USBDriver *usbp) { usb_control_request_t *setup = (usb_control_request_t *)usbp->setup; @@ -611,41 +266,7 @@ static bool usb_requests_hook_cb(USBDriver *usbp) { case USB_RTYPE_DIR_DEV2HOST: switch (setup->bRequest) { case HID_REQ_GetReport: - switch (setup->wIndex) { -#ifndef KEYBOARD_SHARED_EP - case KEYBOARD_INTERFACE: - usbSetupTransfer(usbp, (uint8_t *)&keyboard_report_sent, KEYBOARD_REPORT_SIZE, NULL); - return TRUE; - break; -#endif -#if defined(MOUSE_ENABLE) && !defined(MOUSE_SHARED_EP) - case MOUSE_INTERFACE: - usbSetupTransfer(usbp, (uint8_t *)&mouse_report_sent, sizeof(mouse_report_sent), NULL); - return TRUE; - break; -#endif -#ifdef SHARED_EP_ENABLE - case SHARED_INTERFACE: -# ifdef KEYBOARD_SHARED_EP - if (setup->wValue.lbyte == REPORT_ID_KEYBOARD) { - usbSetupTransfer(usbp, (uint8_t *)&keyboard_report_sent, KEYBOARD_REPORT_SIZE, NULL); - return true; - } -# endif -# ifdef MOUSE_SHARED_EP - if (setup->wValue.lbyte == REPORT_ID_MOUSE) { - usbSetupTransfer(usbp, (uint8_t *)&mouse_report_sent, sizeof(mouse_report_sent), NULL); - return true; - } -# endif -#endif /* SHARED_EP_ENABLE */ - default: - universal_report_blank.report_id = setup->wValue.lbyte; - usbSetupTransfer(usbp, (uint8_t *)&universal_report_blank, setup->wLength, NULL); - return true; - } - break; - + return usb_get_report_cb(usbp); case HID_REQ_GetProtocol: if (setup->wIndex == KEYBOARD_INTERFACE) { usbSetupTransfer(usbp, &keyboard_protocol, sizeof(uint8_t), NULL); @@ -654,10 +275,8 @@ static bool usb_requests_hook_cb(USBDriver *usbp) { break; case HID_REQ_GetIdle: - usbSetupTransfer(usbp, &keyboard_idle, sizeof(uint8_t), NULL); - return true; + return usb_get_idle_cb(usbp); } - break; case USB_RTYPE_DIR_HOST2DEV: switch (setup->bRequest) { @@ -671,38 +290,15 @@ static bool usb_requests_hook_cb(USBDriver *usbp) { return true; } break; - case HID_REQ_SetProtocol: if (setup->wIndex == KEYBOARD_INTERFACE) { keyboard_protocol = setup->wValue.word; -#ifdef NKRO_ENABLE - if (!keyboard_protocol && keyboard_idle) { -#else /* NKRO_ENABLE */ - if (keyboard_idle) { -#endif /* NKRO_ENABLE */ - /* arm the idle timer if boot protocol & idle */ - osalSysLockFromISR(); - chVTSetI(&keyboard_idle_timer, 4 * TIME_MS2I(keyboard_idle), keyboard_idle_timer_cb, (void *)usbp); - osalSysUnlockFromISR(); - } } usbSetupTransfer(usbp, NULL, 0, NULL); return true; - case HID_REQ_SetIdle: keyboard_idle = setup->wValue.hbyte; - /* arm the timer */ -#ifdef NKRO_ENABLE - if (!keymap_config.nkro && keyboard_idle) { -#else /* NKRO_ENABLE */ - if (keyboard_idle) { -#endif /* NKRO_ENABLE */ - osalSysLockFromISR(); - chVTSetI(&keyboard_idle_timer, 4 * TIME_MS2I(keyboard_idle), keyboard_idle_timer_cb, (void *)usbp); - osalSysUnlockFromISR(); - } - usbSetupTransfer(usbp, NULL, 0, NULL); - return true; + return usb_set_idle_cb(usbp); } break; } @@ -719,52 +315,40 @@ static bool usb_requests_hook_cb(USBDriver *usbp) { return true; } - for (int i = 0; i < NUM_USB_DRIVERS; i++) { - if (drivers.array[i].config.int_in) { - // NOTE: Assumes that we only have one serial driver - return qmkusbRequestsHook(usbp); + for (int i = 0; i < USB_ENDPOINT_IN_COUNT; i++) { + if (usb_endpoints_in[i].usb_requests_cb != NULL) { + if (usb_endpoints_in[i].usb_requests_cb(usbp)) { + return true; + } } } return false; } -static void usb_sof_cb(USBDriver *usbp) { - osalSysLockFromISR(); - for (int i = 0; i < NUM_USB_DRIVERS; i++) { - qmkusbSOFHookI(&drivers.array[i].driver); - } - osalSysUnlockFromISR(); +static __attribute__((unused)) void dummy_cb(USBDriver *usbp) { + (void)usbp; } -/* USB driver configuration */ static const USBConfig usbcfg = { usb_event_cb, /* USB events callback */ usb_get_descriptor_cb, /* Device GET_DESCRIPTOR request callback */ usb_requests_hook_cb, /* Requests hook callback */ - usb_sof_cb /* Start Of Frame callback */ +#if STM32_USB_USE_OTG1 == TRUE || STM32_USB_USE_OTG2 == TRUE + dummy_cb, /* Workaround for OTG Peripherals not servicing new interrupts + after resuming from suspend. */ +#endif }; -/* - * Initialize the USB driver - */ void init_usb_driver(USBDriver *usbp) { - for (int i = 0; i < NUM_USB_DRIVERS; i++) { -#ifdef USB_ENDPOINTS_ARE_REORDERABLE - QMKUSBDriver *driver = &drivers.array[i].driver; - drivers.array[i].inout_ep_config.in_state = &drivers.array[i].in_ep_state; - drivers.array[i].inout_ep_config.out_state = &drivers.array[i].out_ep_state; - drivers.array[i].int_ep_config.in_state = &drivers.array[i].int_ep_state; - qmkusbObjectInit(driver, &drivers.array[i].config); - qmkusbStart(driver, &drivers.array[i].config); -#else - QMKUSBDriver *driver = &drivers.array[i].driver; - drivers.array[i].in_ep_config.in_state = &drivers.array[i].in_ep_state; - drivers.array[i].out_ep_config.out_state = &drivers.array[i].out_ep_state; - drivers.array[i].int_ep_config.in_state = &drivers.array[i].int_ep_state; - qmkusbObjectInit(driver, &drivers.array[i].config); - qmkusbStart(driver, &drivers.array[i].config); -#endif + for (int i = 0; i < USB_ENDPOINT_IN_COUNT; i++) { + usb_endpoint_in_init(&usb_endpoints_in[i]); + usb_endpoint_in_start(&usb_endpoints_in[i]); + } + + for (int i = 0; i < USB_ENDPOINT_OUT_COUNT; i++) { + usb_endpoint_out_init(&usb_endpoints_out[i]); + usb_endpoint_out_start(&usb_endpoints_out[i]); } /* @@ -777,23 +361,31 @@ void init_usb_driver(USBDriver *usbp) { wait_ms(50); usbStart(usbp, &usbcfg); usbConnectBus(usbp); - - chVTObjectInit(&keyboard_idle_timer); } __attribute__((weak)) void restart_usb_driver(USBDriver *usbp) { usbDisconnectBus(usbp); usbStop(usbp); -#if USB_SUSPEND_WAKEUP_DELAY > 0 - // Some hubs, kvm switches, and monitors do - // weird things, with USB device state bouncing - // around wildly on wakeup, yielding race - // conditions that can corrupt the keyboard state. - // - // Pause for a while to let things settle... - wait_ms(USB_SUSPEND_WAKEUP_DELAY); -#endif + for (int i = 0; i < USB_ENDPOINT_IN_COUNT; i++) { + usb_endpoint_in_stop(&usb_endpoints_in[i]); + } + + for (int i = 0; i < USB_ENDPOINT_OUT_COUNT; i++) { + usb_endpoint_out_stop(&usb_endpoints_out[i]); + } + + wait_ms(50); + + for (int i = 0; i < USB_ENDPOINT_IN_COUNT; i++) { + usb_endpoint_in_init(&usb_endpoints_in[i]); + usb_endpoint_in_start(&usb_endpoints_in[i]); + } + + for (int i = 0; i < USB_ENDPOINT_OUT_COUNT; i++) { + usb_endpoint_out_init(&usb_endpoints_out[i]); + usb_endpoint_out_start(&usb_endpoints_out[i]); + } usbStart(usbp, &usbcfg); usbConnectBus(usbp); @@ -804,81 +396,78 @@ __attribute__((weak)) void restart_usb_driver(USBDriver *usbp) { * --------------------------------------------------------- */ -/* Idle requests timer code - * callback (called from ISR, unlocked state) */ -static void keyboard_idle_timer_cb(struct ch_virtual_timer *timer, void *arg) { - (void)timer; - USBDriver *usbp = (USBDriver *)arg; - - osalSysLockFromISR(); - - /* check that the states of things are as they're supposed to */ - if (usbGetDriverStateI(usbp) != USB_ACTIVE) { - /* do not rearm the timer, should be enabled on IDLE request */ - osalSysUnlockFromISR(); - return; - } - -#ifdef NKRO_ENABLE - if (!keymap_config.nkro && keyboard_idle && keyboard_protocol) { -#else /* NKRO_ENABLE */ - if (keyboard_idle && keyboard_protocol) { -#endif /* NKRO_ENABLE */ - /* TODO: are we sure we want the KBD_ENDPOINT? */ - if (!usbGetTransmitStatusI(usbp, KEYBOARD_IN_EPNUM)) { - usbStartTransmitI(usbp, KEYBOARD_IN_EPNUM, (uint8_t *)&keyboard_report_sent, KEYBOARD_EPSIZE); - } - /* rearm the timer */ - chVTSetI(&keyboard_idle_timer, 4 * TIME_MS2I(keyboard_idle), keyboard_idle_timer_cb, (void *)usbp); - } - - /* do not rearm the timer if the condition above fails - * it should be enabled again on either IDLE or SET_PROTOCOL requests */ - osalSysUnlockFromISR(); -} - /* LED status */ uint8_t keyboard_leds(void) { return keyboard_led_state; } -void send_report(uint8_t endpoint, void *report, size_t size) { - osalSysLock(); - if (usbGetDriverStateI(&USB_DRIVER) != USB_ACTIVE) { - osalSysUnlock(); - return; - } - - if (usbGetTransmitStatusI(&USB_DRIVER, endpoint)) { - /* Need to either suspend, or loop and call unlock/lock during - * every iteration - otherwise the system will remain locked, - * no interrupts served, so USB not going through as well. - * Note: for suspend, need USB_USE_WAIT == TRUE in halconf.h */ - if (osalThreadSuspendTimeoutS(&(&USB_DRIVER)->epc[endpoint]->in_state->thread, TIME_MS2I(10)) == MSG_TIMEOUT) { - osalSysUnlock(); - return; - } - } - usbStartTransmitI(&USB_DRIVER, endpoint, report, size); - osalSysUnlock(); +/** + * @brief Send a report to the host, the report is enqueued into an output + * queue and send once the USB endpoint becomes empty. + * + * @param endpoint USB IN endpoint to send the report from + * @param report pointer to the report + * @param size size of the report + * @return true Success + * @return false Failure + */ +bool send_report(usb_endpoint_in_lut_t endpoint, void *report, size_t size) { + return usb_endpoint_in_send(&usb_endpoints_in[endpoint], (uint8_t *)report, size, TIME_MS2I(100), false); +} + +/** + * @brief Send a report to the host, but delay the sending until the size of + * endpoint report is reached or the incompletely filled buffer is flushed with + * a call to `flush_report_buffered`. This is useful if the report is being + * updated frequently. The complete report is then enqueued into an output + * queue and send once the USB endpoint becomes empty. + * + * @param endpoint USB IN endpoint to send the report from + * @param report pointer to the report + * @param size size of the report + * @return true Success + * @return false Failure + */ +static bool send_report_buffered(usb_endpoint_in_lut_t endpoint, void *report, size_t size) { + return usb_endpoint_in_send(&usb_endpoints_in[endpoint], (uint8_t *)report, size, TIME_MS2I(100), true); +} + +/** @brief Flush all buffered reports which were enqueued with a call to + * `send_report_buffered` that haven't been send. If necessary the buffered + * report can be padded with zeros up to the endpoints maximum size. + * + * @param endpoint USB IN endpoint to flush the reports from + * @param padded Pad the buffered report with zeros up to the endpoints maximum size + */ +static void flush_report_buffered(usb_endpoint_in_lut_t endpoint, bool padded) { + usb_endpoint_in_flush(&usb_endpoints_in[endpoint], padded); +} + +/** + * @brief Receive a report from the host. + * + * @param endpoint USB OUT endpoint to receive the report from + * @param report pointer to the report + * @param size size of the report + * @return true Success + * @return false Failure + */ +static bool receive_report(usb_endpoint_out_lut_t endpoint, void *report, size_t size) { + return usb_endpoint_out_receive(&usb_endpoints_out[endpoint], (uint8_t *)report, size, TIME_IMMEDIATE); } -/* prepare and start sending a report IN - * not callable from ISR or locked state */ void send_keyboard(report_keyboard_t *report) { /* If we're in Boot Protocol, don't send any report ID or other funky fields */ if (!keyboard_protocol) { - send_report(KEYBOARD_IN_EPNUM, &report->mods, 8); + send_report(USB_ENDPOINT_IN_KEYBOARD, &report->mods, 8); } else { - send_report(KEYBOARD_IN_EPNUM, report, KEYBOARD_REPORT_SIZE); + send_report(USB_ENDPOINT_IN_KEYBOARD, report, KEYBOARD_REPORT_SIZE); } - - keyboard_report_sent = *report; } void send_nkro(report_nkro_t *report) { #ifdef NKRO_ENABLE - send_report(SHARED_IN_EPNUM, report, sizeof(report_nkro_t)); + send_report(USB_ENDPOINT_IN_SHARED, report, sizeof(report_nkro_t)); #endif } @@ -889,8 +478,7 @@ void send_nkro(report_nkro_t *report) { void send_mouse(report_mouse_t *report) { #ifdef MOUSE_ENABLE - send_report(MOUSE_IN_EPNUM, report, sizeof(report_mouse_t)); - mouse_report_sent = *report; + send_report(USB_ENDPOINT_IN_MOUSE, report, sizeof(report_mouse_t)); #endif } @@ -901,25 +489,25 @@ void send_mouse(report_mouse_t *report) { void send_extra(report_extra_t *report) { #ifdef EXTRAKEY_ENABLE - send_report(SHARED_IN_EPNUM, report, sizeof(report_extra_t)); + send_report(USB_ENDPOINT_IN_SHARED, report, sizeof(report_extra_t)); #endif } void send_programmable_button(report_programmable_button_t *report) { #ifdef PROGRAMMABLE_BUTTON_ENABLE - send_report(SHARED_IN_EPNUM, report, sizeof(report_programmable_button_t)); + send_report(USB_ENDPOINT_IN_SHARED, report, sizeof(report_programmable_button_t)); #endif } void send_joystick(report_joystick_t *report) { #ifdef JOYSTICK_ENABLE - send_report(JOYSTICK_IN_EPNUM, report, sizeof(report_joystick_t)); + send_report(USB_ENDPOINT_IN_JOYSTICK, report, sizeof(report_joystick_t)); #endif } void send_digitizer(report_digitizer_t *report) { #ifdef DIGITIZER_ENABLE - send_report(DIGITIZER_IN_EPNUM, report, sizeof(report_digitizer_t)); + send_report(USB_ENDPOINT_IN_DIGITIZER, report, sizeof(report_digitizer_t)); #endif } @@ -931,46 +519,21 @@ void send_digitizer(report_digitizer_t *report) { #ifdef CONSOLE_ENABLE int8_t sendchar(uint8_t c) { - rbuf_enqueue(c); - return 0; + return (int8_t)send_report_buffered(USB_ENDPOINT_IN_CONSOLE, &c, sizeof(uint8_t)); } void console_task(void) { - if (!rbuf_has_data()) { - return; - } - - osalSysLock(); - if (usbGetDriverStateI(&USB_DRIVER) != USB_ACTIVE) { - osalSysUnlock(); - return; - } - - if (usbGetTransmitStatusI(&USB_DRIVER, CONSOLE_IN_EPNUM)) { - osalSysUnlock(); - return; - } - - // Send in chunks - padded with zeros to 32 - char send_buf[CONSOLE_EPSIZE] = {0}; - uint8_t send_buf_count = 0; - while (rbuf_has_data() && send_buf_count < CONSOLE_EPSIZE) { - send_buf[send_buf_count++] = rbuf_dequeue(); - } - - usbStartTransmitI(&USB_DRIVER, CONSOLE_IN_EPNUM, (const uint8_t *)send_buf, CONSOLE_EPSIZE); - osalSysUnlock(); + flush_report_buffered(USB_ENDPOINT_IN_CONSOLE, true); } #endif /* CONSOLE_ENABLE */ #ifdef RAW_ENABLE void raw_hid_send(uint8_t *data, uint8_t length) { - // TODO: implement variable size packet if (length != RAW_EPSIZE) { return; } - chnWrite(&drivers.raw_driver.driver, data, length); + send_report(USB_ENDPOINT_IN_RAW, data, length); } __attribute__((weak)) void raw_hid_receive(uint8_t *data, uint8_t length) { @@ -981,13 +544,9 @@ __attribute__((weak)) void raw_hid_receive(uint8_t *data, uint8_t length) { void raw_hid_task(void) { uint8_t buffer[RAW_EPSIZE]; - size_t size = 0; - do { - size = chnReadTimeout(&drivers.raw_driver.driver, buffer, sizeof(buffer), TIME_IMMEDIATE); - if (size > 0) { - raw_hid_receive(buffer, size); - } - } while (size > 0); + while (receive_report(USB_ENDPOINT_OUT_RAW, buffer, sizeof(buffer))) { + raw_hid_receive(buffer, sizeof(buffer)); + } } #endif @@ -995,32 +554,59 @@ void raw_hid_task(void) { #ifdef MIDI_ENABLE void send_midi_packet(MIDI_EventPacket_t *event) { - chnWrite(&drivers.midi_driver.driver, (uint8_t *)event, sizeof(MIDI_EventPacket_t)); + send_report(USB_ENDPOINT_IN_MIDI, (uint8_t *)event, sizeof(MIDI_EventPacket_t)); } bool recv_midi_packet(MIDI_EventPacket_t *const event) { - size_t size = chnReadTimeout(&drivers.midi_driver.driver, (uint8_t *)event, sizeof(MIDI_EventPacket_t), TIME_IMMEDIATE); - return size == sizeof(MIDI_EventPacket_t); + return receive_report(USB_ENDPOINT_OUT_MIDI, (uint8_t *)event, sizeof(MIDI_EventPacket_t)); } + void midi_ep_task(void) { uint8_t buffer[MIDI_STREAM_EPSIZE]; - size_t size = 0; - do { - size = chnReadTimeout(&drivers.midi_driver.driver, buffer, sizeof(buffer), TIME_IMMEDIATE); - if (size > 0) { - MIDI_EventPacket_t event; - recv_midi_packet(&event); - } - } while (size > 0); + while (receive_report(USB_ENDPOINT_OUT_MIDI, buffer, sizeof(buffer))) { + MIDI_EventPacket_t event; + // TODO: this seems totally wrong? The midi task will never see any + // packets if we consume them here + recv_midi_packet(&event); + } } #endif #ifdef VIRTSER_ENABLE +# include "hal_usb_cdc.h" +/** + * @brief CDC serial driver configuration structure. Set to 9600 baud, 1 stop bit, no parity, 8 data bits. + */ +static cdc_linecoding_t linecoding = {{0x00, 0x96, 0x00, 0x00}, LC_STOP_1, LC_PARITY_NONE, 8}; + +bool virtser_usb_request_cb(USBDriver *usbp) { + if ((usbp->setup[0] & USB_RTYPE_TYPE_MASK) == USB_RTYPE_TYPE_CLASS) { /* bmRequestType */ + if (usbp->setup[4] == CCI_INTERFACE) { /* wIndex (LSB) */ + switch (usbp->setup[1]) { /* bRequest */ + case CDC_GET_LINE_CODING: + usbSetupTransfer(usbp, (uint8_t *)&linecoding, sizeof(linecoding), NULL); + return true; + case CDC_SET_LINE_CODING: + usbSetupTransfer(usbp, (uint8_t *)&linecoding, sizeof(linecoding), NULL); + return true; + case CDC_SET_CONTROL_LINE_STATE: + /* Nothing to do, there are no control lines.*/ + usbSetupTransfer(usbp, NULL, 0, NULL); + return true; + default: + return false; + } + } + } + + return false; +} + void virtser_init(void) {} void virtser_send(const uint8_t byte) { - chnWrite(&drivers.serial_driver.driver, &byte, 1); + send_report_buffered(USB_ENDPOINT_IN_CDC_DATA, (void *)&byte, sizeof(byte)); } __attribute__((weak)) void virtser_recv(uint8_t c) { @@ -1028,14 +614,14 @@ __attribute__((weak)) void virtser_recv(uint8_t c) { } void virtser_task(void) { - uint8_t numBytesReceived = 0; - uint8_t buffer[16]; - do { - numBytesReceived = chnReadTimeout(&drivers.serial_driver.driver, buffer, sizeof(buffer), TIME_IMMEDIATE); - for (int i = 0; i < numBytesReceived; i++) { + uint8_t buffer[CDC_EPSIZE]; + while (receive_report(USB_ENDPOINT_OUT_CDC_DATA, buffer, sizeof(buffer))) { + for (int i = 0; i < sizeof(buffer); i++) { virtser_recv(buffer[i]); } - } while (numBytesReceived > 0); + } + + flush_report_buffered(USB_ENDPOINT_IN_CDC_DATA, false); } #endif diff --git a/tmk_core/protocol/chibios/usb_main.h b/tmk_core/protocol/chibios/usb_main.h index 3fd1e84fe84..5ba6fb1961e 100644 --- a/tmk_core/protocol/chibios/usb_main.h +++ b/tmk_core/protocol/chibios/usb_main.h @@ -1,25 +1,21 @@ -/* - * (c) 2015 flabberast - * - * Based on the following work: - * - Guillaume Duc's raw hid example (MIT License) - * https://github.com/guiduc/usb-hid-chibios-example - * - PJRC Teensy examples (MIT License) - * https://www.pjrc.com/teensy/usb_keyboard.html - * - hasu's TMK keyboard code (GPL v2 and some code Modified BSD) - * https://github.com/tmk/tmk_keyboard/ - * - ChibiOS demo code (Apache 2.0 License) - * http://www.chibios.org - * - * Since some GPL'd code is used, this work is licensed under - * GPL v2 or later. - */ +// Copyright 2023 Stefan Kerkmann (@KarlK90) +// Copyright 2020 Ryan (@fauxpark) +// Copyright 2020 Joel Challis (@zvecr) +// Copyright 2018 James Laird-Wah +// Copyright 2016 Fredizzimo +// Copyright 2016 Giovanni Di Sirio +// SPDX-License-Identifier: GPL-3.0-or-later OR Apache-2.0 #pragma once #include #include +#include "usb_device_state.h" +#include "usb_descriptor.h" +#include "usb_driver.h" +#include "usb_endpoints.h" + /* ------------------------- * General USB driver header * ------------------------- @@ -36,6 +32,8 @@ void init_usb_driver(USBDriver *usbp); /* Restart the USB driver and bus */ void restart_usb_driver(USBDriver *usbp); +bool send_report(usb_endpoint_in_lut_t endpoint, void *report, size_t size); + /* --------------- * USB Event queue * --------------- @@ -58,3 +56,14 @@ void usb_event_queue_task(void); int8_t sendchar(uint8_t c); #endif /* CONSOLE_ENABLE */ + +/* -------------- + * Virtser header + * -------------- + */ + +#if defined(VIRTSER_ENABLE) + +bool virtser_usb_request_cb(USBDriver *usbp); + +#endif diff --git a/tmk_core/protocol/chibios/usb_report_handling.c b/tmk_core/protocol/chibios/usb_report_handling.c new file mode 100644 index 00000000000..64074b21642 --- /dev/null +++ b/tmk_core/protocol/chibios/usb_report_handling.c @@ -0,0 +1,296 @@ +// Copyright 2023 Stefan Kerkmann (@KarlK90) +// SPDX-License-Identifier: GPL-3.0-or-later + +#include +#include +#include + +#include "usb_report_handling.h" +#include "usb_endpoints.h" +#include "usb_main.h" +#include "usb_types.h" +#include "usb_driver.h" +#include "report.h" + +extern usb_endpoint_in_t usb_endpoints_in[USB_ENDPOINT_IN_COUNT]; +extern usb_endpoint_in_lut_t usb_endpoint_interface_lut[TOTAL_INTERFACES]; + +void usb_set_report(usb_fs_report_t **reports, const uint8_t *data, size_t length) { + if (*reports == NULL) { + return; + } + + (*reports)->last_report = chVTGetSystemTimeX(); + (*reports)->length = length; + memcpy(&(*reports)->data, data, length); +} + +void usb_get_report(usb_fs_report_t **reports, uint8_t report_id, usb_fs_report_t *report) { + (void)report_id; + if (*reports == NULL) { + return; + } + + report->length = (*reports)->length; + memcpy(&report->data, &(*reports)->data, report->length); +} + +void usb_reset_report(usb_fs_report_t **reports) { + if (*reports == NULL) { + return; + } + + memset(&(*reports)->data, 0, (*reports)->length); + (*reports)->idle_rate = 0; + (*reports)->last_report = 0; +} + +void usb_shared_set_report(usb_fs_report_t **reports, const uint8_t *data, size_t length) { + uint8_t report_id = data[0]; + + if (report_id > REPORT_ID_COUNT || reports[report_id] == NULL) { + return; + } + + reports[report_id]->last_report = chVTGetSystemTimeX(); + reports[report_id]->length = length; + memcpy(&reports[report_id]->data, data, length); +} + +void usb_shared_get_report(usb_fs_report_t **reports, uint8_t report_id, usb_fs_report_t *report) { + if (report_id > REPORT_ID_COUNT || reports[report_id] == NULL) { + return; + } + + report->length = reports[report_id]->length; + memcpy(&report->data, &reports[report_id]->data, report->length); +} + +void usb_shared_reset_report(usb_fs_report_t **reports) { + for (int i = 0; i <= REPORT_ID_COUNT; i++) { + if (reports[i] == NULL) { + continue; + } + memset(&reports[i]->data, 0, reports[i]->length); + reports[i]->idle_rate = 0; + reports[i]->last_report = 0; + } +} + +bool usb_get_report_cb(USBDriver *driver) { + usb_control_request_t *setup = (usb_control_request_t *)driver->setup; + uint8_t interface = setup->wIndex; + uint8_t report_id = setup->wValue.lbyte; + + static usb_fs_report_t report; + + if (!IS_VALID_INTERFACE(interface) || !IS_VALID_REPORT_ID(report_id)) { + return false; + } + + usb_endpoint_in_lut_t ep = usb_endpoint_interface_lut[interface]; + + if (!IS_VALID_USB_ENDPOINT_IN_LUT(ep)) { + return false; + } + + usb_report_storage_t *report_storage = usb_endpoints_in[ep].report_storage; + + if (report_storage == NULL) { + return false; + } + + report_storage->get_report(report_storage->reports, report_id, &report); + + usbSetupTransfer(driver, (uint8_t *)report.data, report.length, NULL); + + return true; +} + +static bool run_idle_task = false; + +void usb_set_idle_rate(usb_fs_report_t **reports, uint8_t report_id, uint8_t idle_rate) { + (void)report_id; + + if (*reports == NULL) { + return; + } + + (*reports)->idle_rate = idle_rate * 4; + + run_idle_task |= idle_rate != 0; +} + +uint8_t usb_get_idle_rate(usb_fs_report_t **reports, uint8_t report_id) { + (void)report_id; + + if (*reports == NULL) { + return 0; + } + + return (*reports)->idle_rate / 4; +} + +bool usb_idle_timer_elapsed(usb_fs_report_t **reports, uint8_t report_id) { + (void)report_id; + + if (*reports == NULL) { + return false; + } + + osalSysLock(); + time_msecs_t idle_rate = (*reports)->idle_rate; + systime_t last_report = (*reports)->last_report; + osalSysUnlock(); + + if (idle_rate == 0) { + return false; + } + + return chTimeI2MS(chVTTimeElapsedSinceX(last_report)) >= idle_rate; +} + +void usb_shared_set_idle_rate(usb_fs_report_t **reports, uint8_t report_id, uint8_t idle_rate) { + // USB spec demands that a report_id of 0 would set the idle rate for all + // reports of that endpoint, but this can easily lead to resource + // exhaustion, therefore we deliberalty break the spec at this point. + if (report_id == 0 || report_id > REPORT_ID_COUNT || reports[report_id] == NULL) { + return; + } + + reports[report_id]->idle_rate = idle_rate * 4; + + run_idle_task |= idle_rate != 0; +} + +uint8_t usb_shared_get_idle_rate(usb_fs_report_t **reports, uint8_t report_id) { + if (report_id > REPORT_ID_COUNT || reports[report_id] == NULL) { + return 0; + } + + return reports[report_id]->idle_rate / 4; +} + +bool usb_shared_idle_timer_elapsed(usb_fs_report_t **reports, uint8_t report_id) { + if (report_id > REPORT_ID_COUNT || reports[report_id] == NULL) { + return false; + } + + osalSysLock(); + time_msecs_t idle_rate = reports[report_id]->idle_rate; + systime_t last_report = reports[report_id]->last_report; + osalSysUnlock(); + + if (idle_rate == 0) { + return false; + } + + return chTimeI2MS(chVTTimeElapsedSinceX(last_report)) >= idle_rate; +} + +void usb_idle_task(void) { + if (!run_idle_task) { + return; + } + + static usb_fs_report_t report; + bool non_zero_idle_rate_found = false; + + for (int ep = 0; ep < USB_ENDPOINT_IN_COUNT; ep++) { + usb_report_storage_t *report_storage = usb_endpoints_in[ep].report_storage; + + if (report_storage == NULL) { + continue; + } + +#if defined(SHARED_EP_ENABLE) + if (ep == USB_ENDPOINT_IN_SHARED) { + for (int report_id = 1; report_id <= REPORT_ID_COUNT; report_id++) { + osalSysLock(); + non_zero_idle_rate_found |= report_storage->get_idle(report_storage->reports, report_id) != 0; + osalSysUnlock(); + + if (usb_endpoint_in_is_inactive(&usb_endpoints_in[ep]) && report_storage->idle_timer_elasped(report_storage->reports, report_id)) { + osalSysLock(); + report_storage->get_report(report_storage->reports, report_id, &report); + osalSysUnlock(); + send_report(ep, &report.data, report.length); + } + } + continue; + } +#endif + + osalSysLock(); + non_zero_idle_rate_found |= report_storage->get_idle(report_storage->reports, 0) != 0; + osalSysUnlock(); + + if (usb_endpoint_in_is_inactive(&usb_endpoints_in[ep]) && report_storage->idle_timer_elasped(report_storage->reports, 0)) { + osalSysLock(); + report_storage->get_report(report_storage->reports, 0, &report); + osalSysUnlock(); + send_report(ep, &report.data, report.length); + } + } + + run_idle_task = non_zero_idle_rate_found; +} + +bool usb_get_idle_cb(USBDriver *driver) { + usb_control_request_t *setup = (usb_control_request_t *)driver->setup; + uint8_t interface = setup->wIndex; + uint8_t report_id = setup->wValue.lbyte; + + static uint8_t _Alignas(4) idle_rate; + + if (!IS_VALID_INTERFACE(interface) || !IS_VALID_REPORT_ID(report_id)) { + return false; + } + + usb_endpoint_in_lut_t ep = usb_endpoint_interface_lut[interface]; + + if (!IS_VALID_USB_ENDPOINT_IN_LUT(ep)) { + return false; + } + + usb_report_storage_t *report_storage = usb_endpoints_in[ep].report_storage; + + if (report_storage == NULL) { + return false; + } + + idle_rate = report_storage->get_idle(report_storage->reports, report_id); + + usbSetupTransfer(driver, &idle_rate, 1, NULL); + + return true; +} + +bool usb_set_idle_cb(USBDriver *driver) { + usb_control_request_t *setup = (usb_control_request_t *)driver->setup; + uint8_t interface = setup->wIndex; + uint8_t report_id = setup->wValue.lbyte; + uint8_t idle_rate = setup->wValue.hbyte; + + if (!IS_VALID_INTERFACE(interface) || !IS_VALID_REPORT_ID(report_id)) { + return false; + } + + usb_endpoint_in_lut_t ep = usb_endpoint_interface_lut[interface]; + + if (!IS_VALID_USB_ENDPOINT_IN_LUT(ep)) { + return false; + } + + usb_report_storage_t *report_storage = usb_endpoints_in[ep].report_storage; + + if (report_storage == NULL) { + return false; + } + + report_storage->set_idle(report_storage->reports, report_id, idle_rate); + + usbSetupTransfer(driver, NULL, 0, NULL); + + return true; +} diff --git a/tmk_core/protocol/chibios/usb_report_handling.h b/tmk_core/protocol/chibios/usb_report_handling.h new file mode 100644 index 00000000000..18b4ce5e263 --- /dev/null +++ b/tmk_core/protocol/chibios/usb_report_handling.h @@ -0,0 +1,77 @@ +// Copyright 2023 Stefan Kerkmann (@KarlK90) +// SPDX-License-Identifier: GPL-3.0-or-later + +#pragma once + +#include +#include +#include + +#include "timer.h" + +typedef struct { + time_msecs_t idle_rate; + systime_t last_report; + uint8_t data[64]; + size_t length; +} usb_fs_report_t; + +typedef struct { + usb_fs_report_t **reports; + const void (*get_report)(usb_fs_report_t **, uint8_t, usb_fs_report_t *); + const void (*set_report)(usb_fs_report_t **, const uint8_t *, size_t); + const void (*reset_report)(usb_fs_report_t **); + const void (*set_idle)(usb_fs_report_t **, uint8_t, uint8_t); + const uint8_t (*get_idle)(usb_fs_report_t **, uint8_t); + const bool (*idle_timer_elasped)(usb_fs_report_t **, uint8_t); +} usb_report_storage_t; + +#define QMK_USB_REPORT_STROAGE_ENTRY(_report_id, _report_size) [_report_id] = &((usb_fs_report_t){.data = {[0] = _report_id}, .length = _report_size}) + +#define QMK_USB_REPORT_STORAGE(_get_report, _set_report, _reset_report, _get_idle, _set_idle, _idle_timer_elasped, _report_count, _reports...) \ + &((usb_report_storage_t){ \ + .reports = (_Alignas(4) usb_fs_report_t *[_report_count]){_reports}, \ + .get_report = _get_report, \ + .set_report = _set_report, \ + .reset_report = _reset_report, \ + .get_idle = _get_idle, \ + .set_idle = _set_idle, \ + .idle_timer_elasped = _idle_timer_elasped, \ + }) + +#define QMK_USB_REPORT_STORAGE_DEFAULT(_report_length) \ + QMK_USB_REPORT_STORAGE(&usb_get_report, /* _get_report */ \ + &usb_set_report, /* _set_report */ \ + &usb_reset_report, /* _reset_report */ \ + &usb_get_idle_rate, /* _get_idle */ \ + &usb_set_idle_rate, /* _set_idle */ \ + &usb_idle_timer_elapsed, /* _idle_timer_elasped */ \ + 1, /* _report_count */ \ + QMK_USB_REPORT_STROAGE_ENTRY(0, _report_length)) + +// USB HID SET_REPORT and GET_REPORT handling functions +void usb_set_report(usb_fs_report_t **reports, const uint8_t *data, size_t length); +void usb_shared_set_report(usb_fs_report_t **reports, const uint8_t *data, size_t length); + +void usb_get_report(usb_fs_report_t **reports, uint8_t report_id, usb_fs_report_t *report); +void usb_shared_get_report(usb_fs_report_t **reports, uint8_t report_id, usb_fs_report_t *report); + +void usb_reset_report(usb_fs_report_t **reports); +void usb_shared_reset_report(usb_fs_report_t **reports); + +bool usb_get_report_cb(USBDriver *driver); + +// USB HID SET_IDLE and GET_IDLE handling functions +void usb_idle_task(void); + +void usb_set_idle_rate(usb_fs_report_t **timers, uint8_t report_id, uint8_t idle_rate); +void usb_shared_set_idle_rate(usb_fs_report_t **timers, uint8_t report_id, uint8_t idle_rate); + +uint8_t usb_get_idle_rate(usb_fs_report_t **timers, uint8_t report_id); +uint8_t usb_shared_get_idle_rate(usb_fs_report_t **timers, uint8_t report_id); + +bool usb_idle_timer_elapsed(usb_fs_report_t **timers, uint8_t report_id); +bool usb_shared_idle_timer_elapsed(usb_fs_report_t **timers, uint8_t report_id); + +bool usb_get_idle_cb(USBDriver *driver); +bool usb_set_idle_cb(USBDriver *driver); diff --git a/tmk_core/protocol/report.h b/tmk_core/protocol/report.h index 47bc4f2f2bb..0e4f6e9defa 100644 --- a/tmk_core/protocol/report.h +++ b/tmk_core/protocol/report.h @@ -29,7 +29,8 @@ along with this program. If not, see . // clang-format off /* HID report IDs */ -enum hid_report_ids { +enum hid_report_ids { + REPORT_ID_ALL = 0, REPORT_ID_KEYBOARD = 1, REPORT_ID_MOUSE, REPORT_ID_SYSTEM, @@ -37,9 +38,12 @@ enum hid_report_ids { REPORT_ID_PROGRAMMABLE_BUTTON, REPORT_ID_NKRO, REPORT_ID_JOYSTICK, - REPORT_ID_DIGITIZER + REPORT_ID_DIGITIZER, + REPORT_ID_COUNT = REPORT_ID_DIGITIZER }; +#define IS_VALID_REPORT_ID(id) ((id) >= REPORT_ID_ALL && (id) <= REPORT_ID_COUNT) + /* Mouse buttons */ #define MOUSE_BTN_MASK(n) (1 << (n)) enum mouse_buttons { diff --git a/tmk_core/protocol/usb_descriptor.h b/tmk_core/protocol/usb_descriptor.h index 2469990f4d8..ecfb022702b 100644 --- a/tmk_core/protocol/usb_descriptor.h +++ b/tmk_core/protocol/usb_descriptor.h @@ -196,6 +196,8 @@ enum usb_interfaces { TOTAL_INTERFACES }; +#define IS_VALID_INTERFACE(i) ((i) >= 0 && (i) < TOTAL_INTERFACES) + #define NEXT_EPNUM __COUNTER__ /*