From e9d32b60b7f103cda42a19c5216e65b7b64ce9eb Mon Sep 17 00:00:00 2001 From: fredizzimo Date: Mon, 16 Apr 2018 03:42:53 +0300 Subject: [PATCH] Add a custom USB driver for ARM (#2750) * Copy Chibios serial_usb_driver into the chibios/protocol It's renamed to usb_driver to avoid name conflicts * Make the usb driver compile * Disable ChibiOS serial usb driver for all keyboards * Change usb_main to use QMKUSBDriver * Initialize the usb driver buffers * Add support for fixed size queues * Fix USB driver initialization * Don't transfer an empty packet for fixed size streams --- .../chibios_test/stm32_f072_onekey/halconf.h | 2 +- .../chibios_test/stm32_f103_onekey/halconf.h | 2 +- .../chibios_test/teensy_lc_onekey/halconf.h | 2 +- keyboards/clueboard/60/halconf.h | 2 +- keyboards/ergodox_infinity/halconf.h | 2 +- keyboards/infinity60/halconf.h | 2 +- keyboards/jm60/halconf.h | 2 +- keyboards/k_type/halconf.h | 2 +- keyboards/whitefox/halconf.h | 2 +- tmk_core/protocol/chibios.mk | 1 + tmk_core/protocol/chibios/usb_driver.c | 502 ++++++++++++++++++ tmk_core/protocol/chibios/usb_driver.h | 184 +++++++ tmk_core/protocol/chibios/usb_main.c | 84 +-- 13 files changed, 738 insertions(+), 51 deletions(-) create mode 100644 tmk_core/protocol/chibios/usb_driver.c create mode 100644 tmk_core/protocol/chibios/usb_driver.h diff --git a/keyboards/chibios_test/stm32_f072_onekey/halconf.h b/keyboards/chibios_test/stm32_f072_onekey/halconf.h index 76257255..8b9724b1 100644 --- a/keyboards/chibios_test/stm32_f072_onekey/halconf.h +++ b/keyboards/chibios_test/stm32_f072_onekey/halconf.h @@ -139,7 +139,7 @@ * @brief Enables the SERIAL over USB subsystem. */ #if !defined(HAL_USE_SERIAL_USB) || defined(__DOXYGEN__) -#define HAL_USE_SERIAL_USB TRUE +#define HAL_USE_SERIAL_USB FALSE #endif /** diff --git a/keyboards/chibios_test/stm32_f103_onekey/halconf.h b/keyboards/chibios_test/stm32_f103_onekey/halconf.h index 76257255..8b9724b1 100644 --- a/keyboards/chibios_test/stm32_f103_onekey/halconf.h +++ b/keyboards/chibios_test/stm32_f103_onekey/halconf.h @@ -139,7 +139,7 @@ * @brief Enables the SERIAL over USB subsystem. */ #if !defined(HAL_USE_SERIAL_USB) || defined(__DOXYGEN__) -#define HAL_USE_SERIAL_USB TRUE +#define HAL_USE_SERIAL_USB FALSE #endif /** diff --git a/keyboards/chibios_test/teensy_lc_onekey/halconf.h b/keyboards/chibios_test/teensy_lc_onekey/halconf.h index 5e1f6a8a..1b6f2adc 100644 --- a/keyboards/chibios_test/teensy_lc_onekey/halconf.h +++ b/keyboards/chibios_test/teensy_lc_onekey/halconf.h @@ -139,7 +139,7 @@ * @brief Enables the SERIAL over USB subsystem. */ #if !defined(HAL_USE_SERIAL_USB) || defined(__DOXYGEN__) -#define HAL_USE_SERIAL_USB TRUE +#define HAL_USE_SERIAL_USB FALSE #endif /** diff --git a/keyboards/clueboard/60/halconf.h b/keyboards/clueboard/60/halconf.h index 8fe8e0c6..e617fdff 100644 --- a/keyboards/clueboard/60/halconf.h +++ b/keyboards/clueboard/60/halconf.h @@ -146,7 +146,7 @@ * @brief Enables the SERIAL over USB subsystem. */ #if !defined(HAL_USE_SERIAL_USB) || defined(__DOXYGEN__) -#define HAL_USE_SERIAL_USB TRUE +#define HAL_USE_SERIAL_USB FALSE #endif /** diff --git a/keyboards/ergodox_infinity/halconf.h b/keyboards/ergodox_infinity/halconf.h index 3f4ffb7b..ade55ae7 100644 --- a/keyboards/ergodox_infinity/halconf.h +++ b/keyboards/ergodox_infinity/halconf.h @@ -139,7 +139,7 @@ * @brief Enables the SERIAL over USB subsystem. */ #if !defined(HAL_USE_SERIAL_USB) || defined(__DOXYGEN__) -#define HAL_USE_SERIAL_USB TRUE +#define HAL_USE_SERIAL_USB FALSE #endif /** diff --git a/keyboards/infinity60/halconf.h b/keyboards/infinity60/halconf.h index f48413c6..b87b0635 100644 --- a/keyboards/infinity60/halconf.h +++ b/keyboards/infinity60/halconf.h @@ -139,7 +139,7 @@ * @brief Enables the SERIAL over USB subsystem. */ #if !defined(HAL_USE_SERIAL_USB) || defined(__DOXYGEN__) -#define HAL_USE_SERIAL_USB TRUE +#define HAL_USE_SERIAL_USB FALSE #endif /** diff --git a/keyboards/jm60/halconf.h b/keyboards/jm60/halconf.h index 76257255..8b9724b1 100644 --- a/keyboards/jm60/halconf.h +++ b/keyboards/jm60/halconf.h @@ -139,7 +139,7 @@ * @brief Enables the SERIAL over USB subsystem. */ #if !defined(HAL_USE_SERIAL_USB) || defined(__DOXYGEN__) -#define HAL_USE_SERIAL_USB TRUE +#define HAL_USE_SERIAL_USB FALSE #endif /** diff --git a/keyboards/k_type/halconf.h b/keyboards/k_type/halconf.h index f48413c6..b87b0635 100644 --- a/keyboards/k_type/halconf.h +++ b/keyboards/k_type/halconf.h @@ -139,7 +139,7 @@ * @brief Enables the SERIAL over USB subsystem. */ #if !defined(HAL_USE_SERIAL_USB) || defined(__DOXYGEN__) -#define HAL_USE_SERIAL_USB TRUE +#define HAL_USE_SERIAL_USB FALSE #endif /** diff --git a/keyboards/whitefox/halconf.h b/keyboards/whitefox/halconf.h index f48413c6..b87b0635 100644 --- a/keyboards/whitefox/halconf.h +++ b/keyboards/whitefox/halconf.h @@ -139,7 +139,7 @@ * @brief Enables the SERIAL over USB subsystem. */ #if !defined(HAL_USE_SERIAL_USB) || defined(__DOXYGEN__) -#define HAL_USE_SERIAL_USB TRUE +#define HAL_USE_SERIAL_USB FALSE #endif /** diff --git a/tmk_core/protocol/chibios.mk b/tmk_core/protocol/chibios.mk index 6e7cfbd8..222fb4da 100644 --- a/tmk_core/protocol/chibios.mk +++ b/tmk_core/protocol/chibios.mk @@ -5,6 +5,7 @@ CHIBIOS_DIR = $(PROTOCOL_DIR)/chibios SRC += $(CHIBIOS_DIR)/usb_main.c SRC += $(CHIBIOS_DIR)/main.c SRC += usb_descriptor.c +SRC += $(CHIBIOS_DIR)/usb_driver.c VPATH += $(TMK_PATH)/$(PROTOCOL_DIR) VPATH += $(TMK_PATH)/$(CHIBIOS_DIR) diff --git a/tmk_core/protocol/chibios/usb_driver.c b/tmk_core/protocol/chibios/usb_driver.c new file mode 100644 index 00000000..fe535eeb --- /dev/null +++ b/tmk_core/protocol/chibios/usb_driver.c @@ -0,0 +1,502 @@ +/* + 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 + * @{ + */ + +#include "hal.h" +#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 +}; + +/*===========================================================================*/ +/* Driver local functions. */ +/*===========================================================================*/ + +static bool qmkusb_start_receive(QMKUSBDriver *qmkusbp) { + uint8_t *buf; + + /* 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; + } + + /* Checking if there is already a transaction ongoing on the endpoint.*/ + if (usbGetReceiveStatusI(qmkusbp->config->usbp, qmkusbp->config->bulk_in)) { + return true; + } + + /* Checking if there is a buffer ready for incoming data.*/ + buf = ibqGetEmptyBufferI(&qmkusbp->ibqueue); + if (buf == NULL) { + return true; + } + + /* Buffer found, starting a new transaction.*/ + usbStartReceiveI(qmkusbp->config->usbp, qmkusbp->config->bulk_out, + buf, qmkusbp->ibqueue.bsize - sizeof(size_t)); + + return false; +} + +/* + * 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, systime_t timeout) { + + return obqPutTimeout(&((QMKUSBDriver *)ip)->obqueue, b, timeout); +} + +static msg_t _gett(void *ip, systime_t timeout) { + + return ibqGetTimeout(&((QMKUSBDriver *)ip)->ibqueue, timeout); +} + +static size_t _writet(void *ip, const uint8_t *bp, size_t n, systime_t timeout) { + + return obqWriteTimeout(&((QMKUSBDriver *)ip)->obqueue, bp, n, timeout); +} + +static size_t _readt(void *ip, uint8_t *bp, size_t n, systime_t timeout) { + + return ibqReadTimeout(&((QMKUSBDriver *)ip)->ibqueue, bp, n, timeout); +} + +static const struct QMKUSBDriverVMT vmt = { + _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); +} + +/** + * @brief Notification of filled buffer inserted into the output buffers queue. + * + * @param[in] bqp the buffers queue pointer. + */ +static void obnotify(io_buffers_queue_t *bqp) { + size_t n; + QMKUSBDriver *qmkusbp = bqGetLinkX(bqp); + + /* 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; + } + + /* Checking if there is already a transaction ongoing on the endpoint.*/ + if (!usbGetTransmitStatusI(qmkusbp->config->usbp, qmkusbp->config->bulk_in)) { + /* Trying to get a full buffer.*/ + uint8_t *buf = obqGetFullBufferI(&qmkusbp->obqueue, &n); + if (buf != NULL) { + /* Buffer found, starting a new transaction.*/ + usbStartTransmitI(qmkusbp->config->usbp, qmkusbp->config->bulk_in, buf, n); + } + } +} + +/*===========================================================================*/ +/* 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) { +} + +/** + * @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); +} + +/** + * @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; + + osalDbgCheck(qmkusbp != 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; + 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); + + osalSysLock(); + + osalDbgAssert((qmkusbp->state == QMKUSB_STOP) || (qmkusbp->state == QMKUSB_READY), + "invalid state"); + + /* 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; + } + 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); +} + +/** + * @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); +} + +/** + * @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); + } +} + +/** + * @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]; + + if (qmkusbp == NULL) { + return; + } + + osalSysLockFromISR(); + + /* Signaling that space is available in the output queue.*/ + chnAddFlagsI(qmkusbp, CHN_OUTPUT_EMPTY); + + /* Freeing the buffer just transmitted, if it was not a zero size packet.*/ + if (usbp->epc[ep]->in_state->txsize > 0U) { + obqReleaseEmptyBufferI(&qmkusbp->obqueue); + } + + /* Checking if there is a buffer ready for transmission.*/ + buf = obqGetFullBufferI(&qmkusbp->obqueue, &n); + + if (buf != 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)) { + /* 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); + } + + } + else { + /* Nothing to transmit.*/ + } + + 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) { + return; + } + + osalSysLockFromISR(); + + /* Signaling that data is available in the input queue.*/ + chnAddFlagsI(qmkusbp, CHN_INPUT_AVAILABLE); + + /* 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); + + 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; +} + +/** @} */ diff --git a/tmk_core/protocol/chibios/usb_driver.h b/tmk_core/protocol/chibios/usb_driver.h new file mode 100644 index 00000000..558479e1 --- /dev/null +++ b/tmk_core/protocol/chibios/usb_driver.h @@ -0,0 +1,184 @@ +/* + 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 + * @{ + */ + +#ifndef USB_DRIVER_H +#define USB_DRIVER_H + +#include "hal_usb_cdc.h" + +/*===========================================================================*/ +/* Driver constants. */ +/*===========================================================================*/ + +/*===========================================================================*/ +/* Derived constants and error checks. */ +/*===========================================================================*/ + +#if HAL_USE_USB == FALSE +#error "The USB Driver requires HAL_USE_USB" +#endif + +/*===========================================================================*/ +/* Driver data structures and types. */ +/*===========================================================================*/ + +/** + * @brief Driver state machine possible states. + */ +typedef enum { + QMKUSB_UNINIT = 0, /**< Not initialized. */ + QMKUSB_STOP = 1, /**< Stopped. */ + QMKUSB_READY = 2 /**< Ready. */ +} qmkusbstate_t; + +/** + * @brief Structure representing a serial over USB driver. + */ +typedef struct QMKUSBDriver QMKUSBDriver; + +/** + * @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 + */ + size_t in_buffers; + size_t out_buffers; + + /** + * @brief The size of each buffer in the queue, typically the same as the endpoint size + */ + size_t in_size; + size_t out_size; + + /** + * @brief Always send full buffers in_size (the rest is filled with zeroes) + */ + bool fixed_size; + + /* Input buffer + * @note needs to be initialized with a memory buffer of the right size + */ + uint8_t* ib; + /* Output buffer + * @note needs to be initialized with a memory buffer of the right size + */ + uint8_t* ob; +} QMKUSBConfig; + +/** + * @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; + +/** + * @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. */ +/*===========================================================================*/ + +#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); +#ifdef __cplusplus +} +#endif + +#endif /* USB_DRIVER_H */ + +/** @} */ diff --git a/tmk_core/protocol/chibios/usb_main.c b/tmk_core/protocol/chibios/usb_main.c index f980024a..cbe25719 100644 --- a/tmk_core/protocol/chibios/usb_main.c +++ b/tmk_core/protocol/chibios/usb_main.c @@ -29,6 +29,7 @@ #endif #include "wait.h" #include "usb_descriptor.h" +#include "usb_driver.h" #ifdef NKRO_ENABLE #include "keycode_config.h" @@ -170,27 +171,23 @@ static const USBEndpointConfig nkro_ep_config = { typedef struct { size_t queue_capacity_in; size_t queue_capacity_out; - uint8_t* queue_buffer_in; - uint8_t* queue_buffer_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 SerialUSBConfig config; - SerialUSBDriver driver; -} stream_driver_t; + const QMKUSBConfig config; + QMKUSBDriver driver; +} usb_driver_config_t; -#define STREAM_DRIVER(stream, notification) { \ +#define QMK_USB_DRIVER_CONFIG(stream, notification, fixedsize) { \ .queue_capacity_in = stream##_IN_CAPACITY, \ .queue_capacity_out = stream##_OUT_CAPACITY, \ - .queue_buffer_in = (uint8_t[BQ_BUFFER_SIZE(stream##_IN_CAPACITY, stream##_EPSIZE)]) {}, \ - .queue_buffer_out = (uint8_t[BQ_BUFFER_SIZE(stream##_OUT_CAPACITY,stream##_EPSIZE)]) {}, \ .in_ep_config = { \ .ep_mode = stream##_IN_MODE, \ .setup_cb = NULL, \ - .in_cb = sduDataTransmitted, \ + .in_cb = qmkusbDataTransmitted, \ .out_cb = NULL, \ .in_maxsize = stream##_EPSIZE, \ .out_maxsize = 0, \ @@ -204,7 +201,7 @@ typedef struct { .ep_mode = stream##_OUT_MODE, \ .setup_cb = NULL, \ .in_cb = NULL, \ - .out_cb = sduDataReceived, \ + .out_cb = qmkusbDataReceived, \ .in_maxsize = 0, \ .out_maxsize = stream##_EPSIZE, \ /* The pointer to the states will be filled during initialization */ \ @@ -216,7 +213,7 @@ typedef struct { .int_ep_config = { \ .ep_mode = USB_EP_MODE_TYPE_INTR, \ .setup_cb = NULL, \ - .in_cb = sduInterruptTransmitted, \ + .in_cb = qmkusbInterruptTransmitted, \ .out_cb = NULL, \ .in_maxsize = CDC_NOTIFICATION_EPSIZE, \ .out_maxsize = 0, \ @@ -230,7 +227,14 @@ typedef struct { .usbp = &USB_DRIVER, \ .bulk_in = stream##_IN_EPNUM, \ .bulk_out = stream##_OUT_EPNUM, \ - .int_in = notification \ + .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 = (uint8_t[BQ_BUFFER_SIZE(stream##_IN_CAPACITY, stream##_EPSIZE)]) {}, \ + .ob = (uint8_t[BQ_BUFFER_SIZE(stream##_OUT_CAPACITY,stream##_EPSIZE)]) {}, \ } \ } @@ -238,36 +242,36 @@ typedef struct { union { struct { #ifdef CONSOLE_ENABLE - stream_driver_t console_driver; + usb_driver_config_t console_driver; #endif #ifdef RAW_ENABLE - stream_driver_t raw_driver; + usb_driver_config_t raw_driver; #endif #ifdef MIDI_ENABLE - stream_driver_t midi_driver; + usb_driver_config_t midi_driver; #endif #ifdef VIRTSER_ENABLE - stream_driver_t serial_driver; + usb_driver_config_t serial_driver; #endif }; - stream_driver_t array[0]; + usb_driver_config_t array[0]; }; -} stream_drivers_t; +} usb_driver_configs_t; -static stream_drivers_t drivers = { +static usb_driver_configs_t drivers = { #ifdef CONSOLE_ENABLE #define CONSOLE_IN_CAPACITY 4 #define CONSOLE_OUT_CAPACITY 4 #define CONSOLE_IN_MODE USB_EP_MODE_TYPE_INTR #define CONSOLE_OUT_MODE USB_EP_MODE_TYPE_INTR - .console_driver = STREAM_DRIVER(CONSOLE, 0), + .console_driver = QMK_USB_DRIVER_CONFIG(CONSOLE, 0, true), #endif #ifdef RAW_ENABLE #define RAW_IN_CAPACITY 4 #define RAW_OUT_CAPACITY 4 #define RAW_IN_MODE USB_EP_MODE_TYPE_INTR #define RAW_OUT_MODE USB_EP_MODE_TYPE_INTR - .raw_driver = STREAM_DRIVER(RAW, 0), + .raw_driver = QMK_USB_DRIVER_CONFIG(RAW, 0, false), #endif #ifdef MIDI_ENABLE @@ -275,7 +279,7 @@ static stream_drivers_t drivers = { #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 = STREAM_DRIVER(MIDI_STREAM, 0), + .midi_driver = QMK_USB_DRIVER_CONFIG(MIDI_STREAM, 0, false), #endif #ifdef VIRTSER_ENABLE @@ -283,11 +287,11 @@ static stream_drivers_t drivers = { #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 = STREAM_DRIVER(CDC, CDC_NOTIFICATION_EPNUM), + .serial_driver = QMK_USB_DRIVER_CONFIG(CDC, CDC_NOTIFICATION_EPNUM, false), #endif }; -#define NUM_STREAM_DRIVERS (sizeof(drivers) / sizeof(stream_driver_t)) +#define NUM_USB_DRIVERS (sizeof(drivers) / sizeof(usb_driver_config_t)) /* --------------------------------------------------------- @@ -315,13 +319,13 @@ static void usb_event_cb(USBDriver *usbp, usbevent_t event) { #ifdef NKRO_ENABLE usbInitEndpointI(usbp, NKRO_IN_EPNUM, &nkro_ep_config); #endif /* NKRO_ENABLE */ - for (int i=0;iibqueue.notify; - ibqObjectInit(&driver->ibqueue, false, drivers.array[i].queue_buffer_in, drivers.array[i].in_ep_config.in_maxsize, drivers.array[i].queue_capacity_in, notify, driver); - notify = driver->obqueue.notify; - ibqObjectInit(&driver->ibqueue, false, drivers.array[i].queue_buffer_out, drivers.array[i].out_ep_config.out_maxsize, drivers.array[i].queue_capacity_out, notify, driver); - sduStart(driver, &drivers.array[i].config); + qmkusbObjectInit(driver, &drivers.array[i].config); + qmkusbStart(driver, &drivers.array[i].config); } /*