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
This commit is contained in:
		| @@ -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) | ||||
|   | ||||
							
								
								
									
										502
									
								
								tmk_core/protocol/chibios/usb_driver.c
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										502
									
								
								tmk_core/protocol/chibios/usb_driver.c
									
									
									
									
									
										Normal file
									
								
							| @@ -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 <string.h> | ||||
|  | ||||
| /*===========================================================================*/ | ||||
| /* 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; | ||||
| } | ||||
|  | ||||
| /** @} */ | ||||
							
								
								
									
										184
									
								
								tmk_core/protocol/chibios/usb_driver.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										184
									
								
								tmk_core/protocol/chibios/usb_driver.h
									
									
									
									
									
										Normal file
									
								
							| @@ -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 */ | ||||
|  | ||||
| /** @} */ | ||||
| @@ -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;i<NUM_STREAM_DRIVERS;i++) { | ||||
|     for (int i=0;i<NUM_USB_DRIVERS;i++) { | ||||
|       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); | ||||
|       if (drivers.array[i].config.int_in) { | ||||
|         usbInitEndpointI(usbp, drivers.array[i].config.int_in, &drivers.array[i].int_ep_config); | ||||
|       } | ||||
|       sduConfigureHookI(&drivers.array[i].driver); | ||||
|       qmkusbConfigureHookI(&drivers.array[i].driver); | ||||
|     } | ||||
|     osalSysUnlockFromISR(); | ||||
|     return; | ||||
| @@ -333,20 +337,20 @@ static void usb_event_cb(USBDriver *usbp, usbevent_t event) { | ||||
|   case USB_EVENT_UNCONFIGURED: | ||||
|     /* Falls into.*/ | ||||
|   case USB_EVENT_RESET: | ||||
|       for (int i=0;i<NUM_STREAM_DRIVERS;i++) { | ||||
|       for (int i=0;i<NUM_USB_DRIVERS;i++) { | ||||
|         chSysLockFromISR(); | ||||
|         /* Disconnection event on suspend.*/ | ||||
|         sduSuspendHookI(&drivers.array[i].driver); | ||||
|         qmkusbSuspendHookI(&drivers.array[i].driver); | ||||
|         chSysUnlockFromISR(); | ||||
|       } | ||||
|     return; | ||||
|  | ||||
|   case USB_EVENT_WAKEUP: | ||||
|     //TODO: from ISR! print("[W]"); | ||||
|       for (int i=0;i<NUM_STREAM_DRIVERS;i++) { | ||||
|       for (int i=0;i<NUM_USB_DRIVERS;i++) { | ||||
|         chSysLockFromISR(); | ||||
|         /* Disconnection event on suspend.*/ | ||||
|         sduWakeupHookI(&drivers.array[i].driver); | ||||
|         qmkusbWakeupHookI(&drivers.array[i].driver); | ||||
|         chSysUnlockFromISR(); | ||||
|       } | ||||
|     suspend_wakeup_init(); | ||||
| @@ -527,10 +531,10 @@ static bool usb_request_hook_cb(USBDriver *usbp) { | ||||
|     return TRUE; | ||||
|   } | ||||
|  | ||||
|   for (int i=0;i<NUM_STREAM_DRIVERS;i++) { | ||||
|   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 sduRequestsHook(usbp); | ||||
|       return qmkusbRequestsHook(usbp); | ||||
|     } | ||||
|   } | ||||
|  | ||||
| @@ -541,8 +545,8 @@ static bool usb_request_hook_cb(USBDriver *usbp) { | ||||
| static void usb_sof_cb(USBDriver *usbp) { | ||||
|   kbd_sof_cb(usbp); | ||||
|   osalSysLockFromISR(); | ||||
|   for (int i=0; i<NUM_STREAM_DRIVERS;i++) { | ||||
|     sduSOFHookI(&drivers.array[i].driver); | ||||
|   for (int i=0; i<NUM_USB_DRIVERS;i++) { | ||||
|     qmkusbSOFHookI(&drivers.array[i].driver); | ||||
|   } | ||||
|   osalSysUnlockFromISR(); | ||||
| } | ||||
| @@ -560,17 +564,13 @@ static const USBConfig usbcfg = { | ||||
|  * Initialize the USB driver | ||||
|  */ | ||||
| void init_usb_driver(USBDriver *usbp) { | ||||
|   for (int i=0; i<NUM_STREAM_DRIVERS;i++) { | ||||
|     SerialUSBDriver* driver = &drivers.array[i].driver; | ||||
|   for (int i=0; i<NUM_USB_DRIVERS;i++) { | ||||
|     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; | ||||
|     sduObjectInit(driver); | ||||
|     bqnotify_t notify = driver->ibqueue.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); | ||||
|   } | ||||
|  | ||||
|   /* | ||||
|   | ||||
		Reference in New Issue
	
	Block a user