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