mirror of
				https://github.com/mfulz/qmk_firmware.git
				synced 2025-10-31 05:12:33 +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 */ |             /* Remote wakeup */ | ||||||
|             if ((USB_DRIVER.status & USB_GETSTATUS_REMOTE_WAKEUP_ENABLED) && suspend_wakeup_condition()) { |             if ((USB_DRIVER.status & USB_GETSTATUS_REMOTE_WAKEUP_ENABLED) && suspend_wakeup_condition()) { | ||||||
|                 usbWakeupHost(&USB_DRIVER); |                 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 */ |         /* Woken up */ | ||||||
|         // variables has been already cleared by the wakeup hook
 |  | ||||||
|         send_keyboard_report(); |  | ||||||
| #    ifdef MOUSEKEY_ENABLE |  | ||||||
|         mousekey_send(); |  | ||||||
| #    endif /* MOUSEKEY_ENABLE */ |  | ||||||
|     } |     } | ||||||
| #endif | #endif | ||||||
| } | } | ||||||
| @ -218,4 +221,5 @@ void protocol_post_task(void) { | |||||||
| #ifdef RAW_ENABLE | #ifdef RAW_ENABLE | ||||||
|     raw_hid_task(); |     raw_hid_task(); | ||||||
| #endif | #endif | ||||||
|  |     usb_idle_task(); | ||||||
| } | } | ||||||
|  | |||||||
| @ -6,6 +6,8 @@ SRC += $(CHIBIOS_DIR)/usb_main.c | |||||||
| SRC += $(CHIBIOS_DIR)/chibios.c | SRC += $(CHIBIOS_DIR)/chibios.c | ||||||
| SRC += usb_descriptor.c | SRC += usb_descriptor.c | ||||||
| SRC += $(CHIBIOS_DIR)/usb_driver.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 += $(CHIBIOS_DIR)/usb_util.c | ||||||
| SRC += $(LIBSRC) | SRC += $(LIBSRC) | ||||||
| 
 | 
 | ||||||
|  | |||||||
| @ -1,127 +1,51 @@ | |||||||
| /*
 | // Copyright 2023 Stefan Kerkmann (@KarlK90)
 | ||||||
|     ChibiOS - Copyright (C) 2006..2016 Giovanni Di Sirio | // Copyright 2021 Purdea Andrei
 | ||||||
| 
 | // Copyright 2021 Michael Stapelberg
 | ||||||
|     Licensed under the Apache License, Version 2.0 (the "License"); | // Copyright 2020 Ryan (@fauxpark)
 | ||||||
|     you may not use this file except in compliance with the License. | // Copyright 2016 Fredizzimo
 | ||||||
|     You may obtain a copy of the License at | // Copyright 2016 Giovanni Di Sirio
 | ||||||
| 
 | // SPDX-License-Identifier: GPL-3.0-or-later OR Apache-2.0
 | ||||||
|         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 <hal.h> | ||||||
| #include "usb_driver.h" |  | ||||||
| #include <string.h> | #include <string.h> | ||||||
| 
 | 
 | ||||||
| /*===========================================================================*/ | #include "usb_driver.h" | ||||||
| /* Driver local definitions.                                                 */ | #include "util.h" | ||||||
| /*===========================================================================*/ |  | ||||||
| 
 |  | ||||||
| /*===========================================================================*/ |  | ||||||
| /* 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.                                                   */ | /* Driver local functions.                                                   */ | ||||||
| /*===========================================================================*/ | /*===========================================================================*/ | ||||||
| 
 | 
 | ||||||
| static bool qmkusb_start_receive(QMKUSBDriver *qmkusbp) { | static void usb_start_receive(usb_endpoint_out_t *endpoint) { | ||||||
|     uint8_t *buf; |  | ||||||
| 
 |  | ||||||
|     /* If the USB driver is not in the appropriate state then transactions
 |     /* If the USB driver is not in the appropriate state then transactions
 | ||||||
|        must not be started.*/ |        must not be started.*/ | ||||||
|     if ((usbGetDriverStateI(qmkusbp->config->usbp) != USB_ACTIVE) || (qmkusbp->state != QMKUSB_READY)) { |     if ((usbGetDriverStateI(endpoint->config.usbp) != USB_ACTIVE)) { | ||||||
|         return true; |         return; | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     /* Checking if there is already a transaction ongoing on the endpoint.*/ |     /* Checking if there is already a transaction ongoing on the endpoint.*/ | ||||||
|     if (usbGetReceiveStatusI(qmkusbp->config->usbp, qmkusbp->config->bulk_out)) { |     if (usbGetReceiveStatusI(endpoint->config.usbp, endpoint->config.ep)) { | ||||||
|         return true; |         return; | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     /* Checking if there is a buffer ready for incoming data.*/ |     /* Checking if there is a buffer ready for incoming data.*/ | ||||||
|     buf = ibqGetEmptyBufferI(&qmkusbp->ibqueue); |     uint8_t *buffer = ibqGetEmptyBufferI(&endpoint->ibqueue); | ||||||
|     if (buf == NULL) { |     if (buffer == NULL) { | ||||||
|         return true; |         return; | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     /* Buffer found, starting a new transaction.*/ |     /* Buffer found, starting a new transaction.*/ | ||||||
|     usbStartReceiveI(qmkusbp->config->usbp, qmkusbp->config->bulk_out, buf, qmkusbp->ibqueue.bsize - sizeof(size_t)); |     usbStartReceiveI(endpoint->config.usbp, endpoint->config.ep, buffer, endpoint->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, 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. |  * @brief   Notification of empty buffer released into the input buffers queue. | ||||||
|  * |  * | ||||||
|  * @param[in] bqp       the buffers queue pointer. |  * @param[in] bqp       the buffers queue pointer. | ||||||
|  */ |  */ | ||||||
| static void ibnotify(io_buffers_queue_t *bqp) { | static void ibnotify(io_buffers_queue_t *bqp) { | ||||||
|     QMKUSBDriver *qmkusbp = bqGetLinkX(bqp); |     usb_endpoint_out_t *endpoint = bqGetLinkX(bqp); | ||||||
|     (void)qmkusb_start_receive(qmkusbp); |     usb_start_receive(endpoint); | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| /**
 | /**
 | ||||||
| @ -130,22 +54,22 @@ static void ibnotify(io_buffers_queue_t *bqp) { | |||||||
|  * @param[in] bqp       the buffers queue pointer. |  * @param[in] bqp       the buffers queue pointer. | ||||||
|  */ |  */ | ||||||
| static void obnotify(io_buffers_queue_t *bqp) { | static void obnotify(io_buffers_queue_t *bqp) { | ||||||
|     size_t        n; |     usb_endpoint_in_t *endpoint = bqGetLinkX(bqp); | ||||||
|     QMKUSBDriver *qmkusbp = 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.*/ |        must not be started.*/ | ||||||
|     if ((usbGetDriverStateI(qmkusbp->config->usbp) != USB_ACTIVE) || (qmkusbp->state != QMKUSB_READY)) { |     if ((usbGetDriverStateI(endpoint->config.usbp) != USB_ACTIVE)) { | ||||||
|         return; |         return; | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     /* Checking if there is already a transaction ongoing on the endpoint.*/ |     /* 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.*/ |         /* Trying to get a full buffer.*/ | ||||||
|         uint8_t *buf = obqGetFullBufferI(&qmkusbp->obqueue, &n); |         size_t   n; | ||||||
|         if (buf != NULL) { |         uint8_t *buffer = obqGetFullBufferI(&endpoint->obqueue, &n); | ||||||
|  |         if (buffer != NULL) { | ||||||
|             /* Buffer found, starting a new transaction.*/ |             /* 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.                                                */ | /* Driver exported functions.                                                */ | ||||||
| /*===========================================================================*/ | /*===========================================================================*/ | ||||||
| 
 | 
 | ||||||
| /**
 | void usb_endpoint_in_init(usb_endpoint_in_t *endpoint) { | ||||||
|  * @brief   Serial Driver initialization. |     usb_endpoint_config_t *config = &endpoint->config; | ||||||
|  * @note    This function is implicitly invoked by @p halInit(), there is |     endpoint->ep_config.in_state  = &endpoint->ep_in_state; | ||||||
|  *          no need to explicitly initialize the driver. |  | ||||||
|  * |  | ||||||
|  * @init |  | ||||||
|  */ |  | ||||||
| void qmkusbInit(void) {} |  | ||||||
| 
 | 
 | ||||||
| /**
 | #if defined(USB_ENDPOINTS_ARE_REORDERABLE) | ||||||
|  * @brief   Initializes a generic full duplex driver object. |     if (endpoint->is_shared) { | ||||||
|  * @details The HW dependent part of the initialization has to be performed |         endpoint->ep_config.out_state = &endpoint->ep_out_state; | ||||||
|  *          outside, usually in the hardware initialization code. |     } | ||||||
|  * | #endif | ||||||
|  * @param[out] qmkusbp     pointer to a @p QMKUSBDriver structure |     obqObjectInit(&endpoint->obqueue, true, config->buffer, config->buffer_size, config->buffer_capacity, obnotify, endpoint); | ||||||
|  * |  | ||||||
|  * @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); |  | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| /**
 | void usb_endpoint_out_init(usb_endpoint_out_t *endpoint) { | ||||||
|  * @brief   Configures and starts the driver. |     usb_endpoint_config_t *config = &endpoint->config; | ||||||
|  * |     endpoint->ep_config.out_state = &endpoint->ep_out_state; | ||||||
|  * @param[in] qmkusbp      pointer to a @p QMKUSBDriver object |     ibqObjectInit(&endpoint->ibqueue, true, config->buffer, config->buffer_size, config->buffer_capacity, ibnotify, endpoint); | ||||||
|  * @param[in] config    the serial over USB driver configuration | } | ||||||
|  * |  | ||||||
|  * @api |  | ||||||
|  */ |  | ||||||
| void qmkusbStart(QMKUSBDriver *qmkusbp, const QMKUSBConfig *config) { |  | ||||||
|     USBDriver *usbp = config->usbp; |  | ||||||
| 
 | 
 | ||||||
|     osalDbgCheck(qmkusbp != NULL); | void usb_endpoint_in_start(usb_endpoint_in_t *endpoint) { | ||||||
|  |     osalDbgCheck(endpoint != NULL); | ||||||
| 
 | 
 | ||||||
|     osalSysLock(); |     osalSysLock(); | ||||||
|     osalDbgAssert((qmkusbp->state == QMKUSB_STOP) || (qmkusbp->state == QMKUSB_READY), "invalid state"); |     osalDbgAssert((usbGetDriverStateI(endpoint->config.usbp) == USB_STOP) || (usbGetDriverStateI(endpoint->config.usbp) == USB_READY), "invalid state"); | ||||||
|     usbp->in_params[config->bulk_in - 1U]   = qmkusbp; |     endpoint->config.usbp->in_params[endpoint->config.ep - 1U] = endpoint; | ||||||
|     usbp->out_params[config->bulk_out - 1U] = qmkusbp; |     endpoint->timed_out                                        = false; | ||||||
|     if (config->int_in > 0U) { |  | ||||||
|         usbp->in_params[config->int_in - 1U] = qmkusbp; |  | ||||||
|     } |  | ||||||
|     qmkusbp->config = config; |  | ||||||
|     qmkusbp->state  = QMKUSB_READY; |  | ||||||
|     osalSysUnlock(); |     osalSysUnlock(); | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| /**
 | void usb_endpoint_out_start(usb_endpoint_out_t *endpoint) { | ||||||
|  * @brief   Stops the driver. |     osalDbgCheck(endpoint != NULL); | ||||||
|  * @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(); |     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.*/ |     osalSysLock(); | ||||||
|     usbp->in_params[qmkusbp->config->bulk_in - 1U]   = NULL; |     endpoint->config.usbp->in_params[endpoint->config.ep - 1U] = NULL; | ||||||
|     usbp->out_params[qmkusbp->config->bulk_out - 1U] = NULL; | 
 | ||||||
|     if (qmkusbp->config->int_in > 0U) { |     bqSuspendI(&endpoint->obqueue); | ||||||
|         usbp->in_params[qmkusbp->config->int_in - 1U] = NULL; |     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(); |     osalOsRescheduleS(); | ||||||
| 
 |  | ||||||
|     osalSysUnlock(); |     osalSysUnlock(); | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| /**
 | void usb_endpoint_out_stop(usb_endpoint_out_t *endpoint) { | ||||||
|  * @brief   USB device suspend handler. |     osalDbgCheck(endpoint != NULL); | ||||||
|  * @details Generates a @p CHN_DISCONNECT event and puts queues in | 
 | ||||||
|  *          non-blocking mode, this way the application cannot get stuck |     osalSysLock(); | ||||||
|  *          in the middle of an I/O operations. |     osalDbgAssert((usbGetDriverStateI(endpoint->config.usbp) == USB_STOP) || (usbGetDriverStateI(endpoint->config.usbp) == USB_READY), "invalid state"); | ||||||
|  * @note    If this function is not called from an ISR then an explicit call | 
 | ||||||
|  *          to @p osalOsRescheduleS() in necessary afterward. |     bqSuspendI(&endpoint->ibqueue); | ||||||
|  * |     ibqResetI(&endpoint->ibqueue); | ||||||
|  * @param[in] qmkusbp      pointer to a @p QMKUSBDriver object |     osalOsRescheduleS(); | ||||||
|  * |     osalSysUnlock(); | ||||||
|  * @iclass |  | ||||||
|  */ |  | ||||||
| void qmkusbSuspendHookI(QMKUSBDriver *qmkusbp) { |  | ||||||
|     chnAddFlagsI(qmkusbp, CHN_DISCONNECTED); |  | ||||||
|     bqSuspendI(&qmkusbp->ibqueue); |  | ||||||
|     bqSuspendI(&qmkusbp->obqueue); |  | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| /**
 | void usb_endpoint_in_suspend_cb(usb_endpoint_in_t *endpoint) { | ||||||
|  * @brief   USB device wakeup handler. |     bqSuspendI(&endpoint->obqueue); | ||||||
|  * @details Generates a @p CHN_CONNECT event and resumes normal queues |     obqResetI(&endpoint->obqueue); | ||||||
|  *          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); |  | ||||||
| } |  | ||||||
| 
 | 
 | ||||||
| /**
 |     if (endpoint->report_storage != NULL) { | ||||||
|  * @brief   USB device configured handler. |         endpoint->report_storage->reset_report(endpoint->report_storage->reports); | ||||||
|  * |  | ||||||
|  * @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); |  | ||||||
|     } |     } | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| /**
 | void usb_endpoint_out_suspend_cb(usb_endpoint_out_t *endpoint) { | ||||||
|  * @brief   Default data transmitted callback. |     bqSuspendI(&endpoint->ibqueue); | ||||||
|  * @details The application must use this function as callback for the IN |     ibqResetI(&endpoint->ibqueue); | ||||||
|  *          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) { | void usb_endpoint_in_wakeup_cb(usb_endpoint_in_t *endpoint) { | ||||||
|  |     bqResumeX(&endpoint->obqueue); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | void usb_endpoint_out_wakeup_cb(usb_endpoint_out_t *endpoint) { | ||||||
|  |     bqResumeX(&endpoint->ibqueue); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | void usb_endpoint_in_configure_cb(usb_endpoint_in_t *endpoint) { | ||||||
|  |     usbInitEndpointI(endpoint->config.usbp, endpoint->config.ep, &endpoint->ep_config); | ||||||
|  |     obqResetI(&endpoint->obqueue); | ||||||
|  |     bqResumeX(&endpoint->obqueue); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | void usb_endpoint_out_configure_cb(usb_endpoint_out_t *endpoint) { | ||||||
|  |     /* The current assumption is that there are no standalone OUT endpoints,
 | ||||||
|  |      * therefore if we share an endpoint with an IN endpoint, it is already | ||||||
|  |      * initialized. */ | ||||||
|  | #if !defined(USB_ENDPOINTS_ARE_REORDERABLE) | ||||||
|  |     usbInitEndpointI(endpoint->config.usbp, endpoint->config.ep, &endpoint->ep_config); | ||||||
|  | #endif | ||||||
|  |     ibqResetI(&endpoint->ibqueue); | ||||||
|  |     bqResumeX(&endpoint->ibqueue); | ||||||
|  |     (void)usb_start_receive(endpoint); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | void usb_endpoint_in_tx_complete_cb(USBDriver *usbp, usbep_t ep) { | ||||||
|  |     usb_endpoint_in_t *endpoint = usbp->in_params[ep - 1U]; | ||||||
|  |     size_t             n; | ||||||
|  |     uint8_t *          buffer; | ||||||
|  | 
 | ||||||
|  |     if (endpoint == NULL) { | ||||||
|         return; |         return; | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     osalSysLockFromISR(); |     osalSysLockFromISR(); | ||||||
| 
 | 
 | ||||||
|     /* Signaling that space is available in the output queue.*/ |     /* Sending succeded, so we can reset the timed out state. */ | ||||||
|     chnAddFlagsI(qmkusbp, CHN_OUTPUT_EMPTY); |     endpoint->timed_out = false; | ||||||
| 
 | 
 | ||||||
|     /* Freeing the buffer just transmitted, if it was not a zero size packet.*/ |     /* Freeing the buffer just transmitted, if it was not a zero size packet.*/ | ||||||
|     if (usbp->epc[ep]->in_state->txsize > 0U) { |     if (!obqIsEmptyI(&endpoint->obqueue) && usbp->epc[ep]->in_state->txsize > 0U) { | ||||||
|         obqReleaseEmptyBufferI(&qmkusbp->obqueue); |         /* 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.*/ |     /* 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,
 |         /* The endpoint cannot be busy, we are in the context of the callback,
 | ||||||
|            so it is safe to transmit without a check.*/ |            so it is safe to transmit without a check.*/ | ||||||
|         usbStartTransmitI(usbp, ep, buf, n); |         usbStartTransmitI(usbp, ep, buffer, 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)) { |     } 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
 |         /* Transmit zero sized packet in case the last one has maximum allowed
 | ||||||
|            size. Otherwise the recipient may expect more data coming soon and |          * size. Otherwise the recipient may expect more data coming soon and | ||||||
|            not return buffered data to app. See section 5.8.3 Bulk Transfer |          * not return buffered data to app. See section 5.8.3 Bulk Transfer | ||||||
|            Packet Size Constraints of the USB Specification document.*/ |          * Packet Size Constraints of the USB Specification document. */ | ||||||
|         if (!qmkusbp->config->fixed_size) { |         usbStartTransmitI(usbp, ep, usbp->setup, 0); | ||||||
|             usbStartTransmitI(usbp, ep, usbp->setup, 0); |  | ||||||
|         } |  | ||||||
| 
 |  | ||||||
|     } else { |     } else { | ||||||
|         /* Nothing to transmit.*/ |         /* Nothing to transmit.*/ | ||||||
|     } |     } | ||||||
| @ -419,47 +228,114 @@ void qmkusbDataTransmitted(USBDriver *usbp, usbep_t ep) { | |||||||
|     osalSysUnlockFromISR(); |     osalSysUnlockFromISR(); | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| /**
 | void usb_endpoint_out_rx_complete_cb(USBDriver *usbp, usbep_t ep) { | ||||||
|  * @brief   Default data received callback. |     usb_endpoint_out_t *endpoint = usbp->out_params[ep - 1U]; | ||||||
|  * @details The application must use this function as callback for the OUT |     if (endpoint == NULL) { | ||||||
|  *          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; |         return; | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     osalSysLockFromISR(); |     osalSysLockFromISR(); | ||||||
| 
 | 
 | ||||||
|     /* Signaling that data is available in the input queue.*/ |     size_t size = usbGetReceiveTransactionSizeX(usbp, ep); | ||||||
|     chnAddFlagsI(qmkusbp, CHN_INPUT_AVAILABLE); |     if (size > 0) { | ||||||
|  |         /* Posting the filled buffer in the queue.*/ | ||||||
|  |         ibqPostFullBufferI(&endpoint->ibqueue, usbGetReceiveTransactionSizeX(endpoint->config.usbp, endpoint->config.ep)); | ||||||
|  |     } | ||||||
| 
 | 
 | ||||||
|     /* Posting the filled buffer in the queue.*/ |     /* The endpoint cannot be busy, we are in the context of the callback, so a
 | ||||||
|     ibqPostFullBufferI(&qmkusbp->ibqueue, usbGetReceiveTransactionSizeX(qmkusbp->config->usbp, qmkusbp->config->bulk_out)); |      * packet is in the buffer for sure. Trying to get a free buffer for the | ||||||
| 
 |      * next transaction.*/ | ||||||
|     /* The endpoint cannot be busy, we are in the context of the callback,
 |     usb_start_receive(endpoint); | ||||||
|        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(); |     osalSysUnlockFromISR(); | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| /**
 | bool usb_endpoint_in_send(usb_endpoint_in_t *endpoint, const uint8_t *data, size_t size, sysinterval_t timeout, bool buffered) { | ||||||
|  * @brief   Default data received callback. |     osalDbgCheck((endpoint != NULL) && (data != NULL) && (size > 0U) && (size <= endpoint->config.buffer_size)); | ||||||
|  * @details The application must use this function as callback for the IN | 
 | ||||||
|  *          interrupt endpoint. |     osalSysLock(); | ||||||
|  * |     if (usbGetDriverStateI(endpoint->config.usbp) != USB_ACTIVE) { | ||||||
|  * @param[in] usbp      pointer to the @p USBDriver object |         osalSysUnlock(); | ||||||
|  * @param[in] ep        endpoint number |         return false; | ||||||
|  */ |     } | ||||||
| void qmkusbInterruptTransmitted(USBDriver *usbp, usbep_t ep) { | 
 | ||||||
|     (void)usbp; |     /* Short circuit the waiting if this endpoint timed out before, e.g. if
 | ||||||
|     (void)ep; |      * 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 @@ | |||||||
| /*
 | // Copyright 2023 Stefan Kerkmann (@KarlK90)
 | ||||||
|     ChibiOS - Copyright (C) 2006..2016 Giovanni Di Sirio | // Copyright 2020 Ryan (@fauxpark)
 | ||||||
| 
 | // Copyright 2016 Fredizzimo
 | ||||||
|     Licensed under the Apache License, Version 2.0 (the "License"); | // Copyright 2016 Giovanni Di Sirio
 | ||||||
|     you may not use this file except in compliance with the License. | // SPDX-License-Identifier: GPL-3.0-or-later OR Apache-2.0
 | ||||||
|     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 |  | ||||||
|  * @{ |  | ||||||
|  */ |  | ||||||
| 
 | 
 | ||||||
| #pragma once | #pragma once | ||||||
| 
 | 
 | ||||||
| #include <hal_usb_cdc.h> | #include <hal_buffers.h> | ||||||
| 
 | #include "usb_descriptor.h" | ||||||
| /*===========================================================================*/ | #include "chibios_config.h" | ||||||
| /* Driver constants.                                                         */ | #include "usb_report_handling.h" | ||||||
| /*===========================================================================*/ | #include "string.h" | ||||||
| 
 | #include "timer.h" | ||||||
| /*===========================================================================*/ |  | ||||||
| /* Derived constants and error checks.                                       */ |  | ||||||
| /*===========================================================================*/ |  | ||||||
| 
 | 
 | ||||||
| #if HAL_USE_USB == FALSE | #if HAL_USE_USB == FALSE | ||||||
| #    error "The USB Driver requires HAL_USE_USB" | #    error "The USB Driver requires HAL_USE_USB" | ||||||
| #endif | #endif | ||||||
| 
 | 
 | ||||||
| /*===========================================================================*/ | /* USB Low Level driver specific endpoint fields */ | ||||||
| /* Driver data structures and types.                                         */ | #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 { | #define QMK_USB_ENDPOINT_IN(mode, ep_size, ep_num, _buffer_capacity, _usb_requests_cb, _report_storage) \ | ||||||
|     QMKUSB_UNINIT = 0, /**< Not initialized.                   */ |     {                                                                                                   \ | ||||||
|     QMKUSB_STOP   = 1, /**< Stopped.                           */ |         .usb_requests_cb = _usb_requests_cb, .report_storage = _report_storage,                         \ | ||||||
|     QMKUSB_READY  = 2  /**< Ready.                             */ |         .ep_config =                                                                                    \ | ||||||
| } qmkusbstate_t; |             {                                                                                           \ | ||||||
|  |                 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},     \ | ||||||
|  |         }                                                                                               \ | ||||||
|  |     } | ||||||
| 
 | 
 | ||||||
| /**
 | #if !defined(USB_ENDPOINTS_ARE_REORDERABLE) | ||||||
|  * @brief   Structure representing a serial over USB driver. | 
 | ||||||
|  */ | #    define QMK_USB_ENDPOINT_OUT(mode, ep_size, ep_num, _buffer_capacity)                              \ | ||||||
| typedef struct QMKUSBDriver QMKUSBDriver; |         {                                                                                              \ | ||||||
|  |             .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 { | typedef struct { | ||||||
|     /**
 |     /**
 | ||||||
|      * @brief   USB driver to use. |      * @brief   USB driver to use. | ||||||
|      */ |      */ | ||||||
|     USBDriver *usbp; |     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; |     usbep_t ep; | ||||||
|     size_t out_buffers; |  | ||||||
| 
 | 
 | ||||||
|     /**
 |     /**
 | ||||||
|      * @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 buffer_capacity; | ||||||
|     size_t out_size; |  | ||||||
| 
 | 
 | ||||||
|     /**
 |     /**
 | ||||||
|      * @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; |     uint8_t *buffer; | ||||||
|     /* Output buffer
 | } usb_endpoint_config_t; | ||||||
|      * @note needs to be initialized with a memory buffer of the right size |  | ||||||
|      */ |  | ||||||
|     uint8_t *ob; |  | ||||||
| } QMKUSBConfig; |  | ||||||
| 
 | 
 | ||||||
| /**
 | typedef struct { | ||||||
|  * @brief   @p SerialDriver specific data. |     output_buffers_queue_t obqueue; | ||||||
|  */ |     USBEndpointConfig      ep_config; | ||||||
| #define _qmk_usb_driver_data                           \ |     USBInEndpointState     ep_in_state; | ||||||
|     _base_asynchronous_channel_data /* Driver state.*/ \ | #if defined(USB_ENDPOINTS_ARE_REORDERABLE) | ||||||
|         qmkusbstate_t state;                           \ |     USBOutEndpointState ep_out_state; | ||||||
|     /* Input buffers queue.*/                          \ |     bool                is_shared; | ||||||
|     input_buffers_queue_t ibqueue;                     \ | #endif | ||||||
|     /* Output queue.*/                                 \ |     usb_endpoint_config_t config; | ||||||
|     output_buffers_queue_t obqueue;                    \ |     usbreqhandler_t       usb_requests_cb; | ||||||
|     /* End of the mandatory fields.*/                  \ |     bool                  timed_out; | ||||||
|     /* Current configuration data.*/                   \ |     usb_report_storage_t *report_storage; | ||||||
|     const QMKUSBConfig *config; | } usb_endpoint_in_t; | ||||||
| 
 | 
 | ||||||
| /**
 | typedef struct { | ||||||
|  * @brief   @p SerialUSBDriver specific methods. |     input_buffers_queue_t ibqueue; | ||||||
|  */ |     USBEndpointConfig     ep_config; | ||||||
| #define _qmk_usb_driver_methods _base_asynchronous_channel_methods |     USBOutEndpointState   ep_out_state; | ||||||
| 
 |     usb_endpoint_config_t config; | ||||||
| /**
 |     bool                  timed_out; | ||||||
|  * @extends BaseAsynchronousChannelVMT | } usb_endpoint_out_t; | ||||||
|  * |  | ||||||
|  * @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 | #ifdef __cplusplus | ||||||
| extern "C" { | extern "C" { | ||||||
| #endif | #endif | ||||||
| void qmkusbInit(void); | 
 | ||||||
| void qmkusbObjectInit(QMKUSBDriver *qmkusbp, const QMKUSBConfig *config); | void usb_endpoint_in_init(usb_endpoint_in_t *endpoint); | ||||||
| void qmkusbStart(QMKUSBDriver *qmkusbp, const QMKUSBConfig *config); | void usb_endpoint_in_start(usb_endpoint_in_t *endpoint); | ||||||
| void qmkusbStop(QMKUSBDriver *qmkusbp); | void usb_endpoint_in_stop(usb_endpoint_in_t *endpoint); | ||||||
| void qmkusbSuspendHookI(QMKUSBDriver *qmkusbp); | 
 | ||||||
| void qmkusbWakeupHookI(QMKUSBDriver *qmkusbp); | bool usb_endpoint_in_send(usb_endpoint_in_t *endpoint, const uint8_t *data, size_t size, sysinterval_t timeout, bool buffered); | ||||||
| void qmkusbConfigureHookI(QMKUSBDriver *qmkusbp); | void usb_endpoint_in_flush(usb_endpoint_in_t *endpoint, bool padded); | ||||||
| bool qmkusbRequestsHook(USBDriver *usbp); | bool usb_endpoint_in_is_inactive(usb_endpoint_in_t *endpoint); | ||||||
| void qmkusbSOFHookI(QMKUSBDriver *qmkusbp); | 
 | ||||||
| void qmkusbDataTransmitted(USBDriver *usbp, usbep_t ep); | void usb_endpoint_in_suspend_cb(usb_endpoint_in_t *endpoint); | ||||||
| void qmkusbDataReceived(USBDriver *usbp, usbep_t ep); | void usb_endpoint_in_wakeup_cb(usb_endpoint_in_t *endpoint); | ||||||
| void qmkusbInterruptTransmitted(USBDriver *usbp, usbep_t ep); | 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 | #ifdef __cplusplus | ||||||
| } | } | ||||||
| #endif | #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 @@ | |||||||
| /*
 | // Copyright 2023 Stefan Kerkmann (@KarlK90)
 | ||||||
|  * (c) 2015 flabberast <s3+flabbergast@sdfeu.org> | // Copyright 2020 Ryan (@fauxpark)
 | ||||||
|  * | // Copyright 2020 Joel Challis (@zvecr)
 | ||||||
|  * Based on the following work: | // Copyright 2018 James Laird-Wah
 | ||||||
|  *  - Guillaume Duc's raw hid example (MIT License) | // Copyright 2016 Fredizzimo
 | ||||||
|  *    https://github.com/guiduc/usb-hid-chibios-example
 | // Copyright 2016 Giovanni Di Sirio
 | ||||||
|  *  - PJRC Teensy examples (MIT License) | // SPDX-License-Identifier: GPL-3.0-or-later OR Apache-2.0
 | ||||||
|  *    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. |  | ||||||
|  */ |  | ||||||
| 
 | 
 | ||||||
| #pragma once | #pragma once | ||||||
| 
 | 
 | ||||||
| #include <ch.h> | #include <ch.h> | ||||||
| #include <hal.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 |  * General USB driver header | ||||||
|  * ------------------------- |  * ------------------------- | ||||||
| @ -36,6 +32,8 @@ void init_usb_driver(USBDriver *usbp); | |||||||
| /* Restart the USB driver and bus */ | /* Restart the USB driver and bus */ | ||||||
| void restart_usb_driver(USBDriver *usbp); | void restart_usb_driver(USBDriver *usbp); | ||||||
| 
 | 
 | ||||||
|  | bool send_report(usb_endpoint_in_lut_t endpoint, void *report, size_t size); | ||||||
|  | 
 | ||||||
| /* ---------------
 | /* ---------------
 | ||||||
|  * USB Event queue |  * USB Event queue | ||||||
|  * --------------- |  * --------------- | ||||||
| @ -58,3 +56,14 @@ void usb_event_queue_task(void); | |||||||
| int8_t sendchar(uint8_t c); | int8_t sendchar(uint8_t c); | ||||||
| 
 | 
 | ||||||
| #endif /* CONSOLE_ENABLE */ | #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 */ | /* HID report IDs */ | ||||||
| enum hid_report_ids {  | enum hid_report_ids {  | ||||||
|  |     REPORT_ID_ALL = 0, | ||||||
|     REPORT_ID_KEYBOARD = 1, |     REPORT_ID_KEYBOARD = 1, | ||||||
|     REPORT_ID_MOUSE, |     REPORT_ID_MOUSE, | ||||||
|     REPORT_ID_SYSTEM, |     REPORT_ID_SYSTEM, | ||||||
| @ -37,9 +38,12 @@ enum hid_report_ids { | |||||||
|     REPORT_ID_PROGRAMMABLE_BUTTON, |     REPORT_ID_PROGRAMMABLE_BUTTON, | ||||||
|     REPORT_ID_NKRO, |     REPORT_ID_NKRO, | ||||||
|     REPORT_ID_JOYSTICK, |     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 */ | /* Mouse buttons */ | ||||||
| #define MOUSE_BTN_MASK(n) (1 << (n)) | #define MOUSE_BTN_MASK(n) (1 << (n)) | ||||||
| enum mouse_buttons { | enum mouse_buttons { | ||||||
|  | |||||||
| @ -196,6 +196,8 @@ enum usb_interfaces { | |||||||
|     TOTAL_INTERFACES |     TOTAL_INTERFACES | ||||||
| }; | }; | ||||||
| 
 | 
 | ||||||
|  | #define IS_VALID_INTERFACE(i) ((i) >= 0 && (i) < TOTAL_INTERFACES) | ||||||
|  | 
 | ||||||
| #define NEXT_EPNUM __COUNTER__ | #define NEXT_EPNUM __COUNTER__ | ||||||
| 
 | 
 | ||||||
| /*
 | /*
 | ||||||
|  | |||||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user
	 Stefan Kerkmann
						Stefan Kerkmann