aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorStefan Kerkmann <[email protected]>2024-02-28 12:00:27 +0100
committerGitHub <[email protected]>2024-02-28 22:00:27 +1100
commit0e02b0c41e47d5f5ad799a9860869b9d30ab881a (patch)
tree4b9cf95e22162db622ea049ea96bc0d15358cf9f
parentb43f6cb7ef33f38ee494f639e2704a25c1e68332 (diff)
downloadqmk_firmware-0e02b0c41e47d5f5ad799a9860869b9d30ab881a.tar.gz
qmk_firmware-0e02b0c41e47d5f5ad799a9860869b9d30ab881a.zip
[Core] Refactor ChibiOS USB endpoints to be fully async (#21656)
-rw-r--r--tmk_core/protocol/chibios/chibios.c16
-rw-r--r--tmk_core/protocol/chibios/chibios.mk2
-rw-r--r--tmk_core/protocol/chibios/usb_driver.c572
-rw-r--r--tmk_core/protocol/chibios/usb_driver.h312
-rw-r--r--tmk_core/protocol/chibios/usb_endpoints.c152
-rw-r--r--tmk_core/protocol/chibios/usb_endpoints.h137
-rw-r--r--tmk_core/protocol/chibios/usb_main.c814
-rw-r--r--tmk_core/protocol/chibios/usb_main.h41
-rw-r--r--tmk_core/protocol/chibios/usb_report_handling.c296
-rw-r--r--tmk_core/protocol/chibios/usb_report_handling.h77
-rw-r--r--tmk_core/protocol/report.h8
-rw-r--r--tmk_core/protocol/usb_descriptor.h2
12 files changed, 1303 insertions, 1126 deletions
diff --git a/tmk_core/protocol/chibios/chibios.c b/tmk_core/protocol/chibios/chibios.c
index 91bb252c7c..76a37ae538 100644
--- a/tmk_core/protocol/chibios/chibios.c
+++ b/tmk_core/protocol/chibios/chibios.c
@@ -192,15 +192,18 @@ void protocol_pre_task(void) {
/* Remote wakeup */
if ((USB_DRIVER.status & USB_GETSTATUS_REMOTE_WAKEUP_ENABLED) && suspend_wakeup_condition()) {
usbWakeupHost(&USB_DRIVER);
- restart_usb_driver(&USB_DRIVER);
+# if USB_SUSPEND_WAKEUP_DELAY > 0
+ // Some hubs, kvm switches, and monitors do
+ // weird things, with USB device state bouncing
+ // around wildly on wakeup, yielding race
+ // conditions that can corrupt the keyboard state.
+ //
+ // Pause for a while to let things settle...
+ wait_ms(USB_SUSPEND_WAKEUP_DELAY);
+# endif
}
}
/* Woken up */
- // variables has been already cleared by the wakeup hook
- send_keyboard_report();
-# ifdef MOUSEKEY_ENABLE
- mousekey_send();
-# endif /* MOUSEKEY_ENABLE */
}
#endif
}
@@ -218,4 +221,5 @@ void protocol_post_task(void) {
#ifdef RAW_ENABLE
raw_hid_task();
#endif
+ usb_idle_task();
}
diff --git a/tmk_core/protocol/chibios/chibios.mk b/tmk_core/protocol/chibios/chibios.mk
index 8eaf5b10d2..aee3f5f056 100644
--- a/tmk_core/protocol/chibios/chibios.mk
+++ b/tmk_core/protocol/chibios/chibios.mk
@@ -6,6 +6,8 @@ SRC += $(CHIBIOS_DIR)/usb_main.c
SRC += $(CHIBIOS_DIR)/chibios.c
SRC += usb_descriptor.c
SRC += $(CHIBIOS_DIR)/usb_driver.c
+SRC += $(CHIBIOS_DIR)/usb_endpoints.c
+SRC += $(CHIBIOS_DIR)/usb_report_handling.c
SRC += $(CHIBIOS_DIR)/usb_util.c
SRC += $(LIBSRC)
diff --git a/tmk_core/protocol/chibios/usb_driver.c b/tmk_core/protocol/chibios/usb_driver.c
index ad45f9b1da..7c3ce44687 100644
--- a/tmk_core/protocol/chibios/usb_driver.c
+++ b/tmk_core/protocol/chibios/usb_driver.c
@@ -1,127 +1,51 @@
-/*
- ChibiOS - Copyright (C) 2006..2016 Giovanni Di Sirio
-
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
-*/
-
-/**
- * @file hal_serial_usb.c
- * @brief Serial over USB Driver code.
- *
- * @addtogroup SERIAL_USB
- * @{
- */
+// Copyright 2023 Stefan Kerkmann (@KarlK90)
+// Copyright 2021 Purdea Andrei
+// Copyright 2021 Michael Stapelberg
+// Copyright 2020 Ryan (@fauxpark)
+// Copyright 2016 Fredizzimo
+// Copyright 2016 Giovanni Di Sirio
+// SPDX-License-Identifier: GPL-3.0-or-later OR Apache-2.0
#include <hal.h>
-#include "usb_driver.h"
#include <string.h>
-/*===========================================================================*/
-/* Driver local definitions. */
-/*===========================================================================*/
-
-/*===========================================================================*/
-/* Driver exported variables. */
-/*===========================================================================*/
-
-/*===========================================================================*/
-/* Driver local variables and types. */
-/*===========================================================================*/
-
-/*
- * Current Line Coding.
- */
-static cdc_linecoding_t linecoding = {{0x00, 0x96, 0x00, 0x00}, /* 38400. */
- LC_STOP_1,
- LC_PARITY_NONE,
- 8};
+#include "usb_driver.h"
+#include "util.h"
/*===========================================================================*/
/* Driver local functions. */
/*===========================================================================*/
-static bool qmkusb_start_receive(QMKUSBDriver *qmkusbp) {
- uint8_t *buf;
-
+static void usb_start_receive(usb_endpoint_out_t *endpoint) {
/* If the USB driver is not in the appropriate state then transactions
must not be started.*/
- if ((usbGetDriverStateI(qmkusbp->config->usbp) != USB_ACTIVE) || (qmkusbp->state != QMKUSB_READY)) {
- return true;
+ if ((usbGetDriverStateI(endpoint->config.usbp) != USB_ACTIVE)) {
+ return;
}
/* Checking if there is already a transaction ongoing on the endpoint.*/
- if (usbGetReceiveStatusI(qmkusbp->config->usbp, qmkusbp->config->bulk_out)) {
- return true;
+ if (usbGetReceiveStatusI(endpoint->config.usbp, endpoint->config.ep)) {
+ return;
}
/* Checking if there is a buffer ready for incoming data.*/
- buf = ibqGetEmptyBufferI(&qmkusbp->ibqueue);
- if (buf == NULL) {
- return true;
+ uint8_t *buffer = ibqGetEmptyBufferI(&endpoint->ibqueue);
+ if (buffer == NULL) {
+ return;
}
/* Buffer found, starting a new transaction.*/
- usbStartReceiveI(qmkusbp->config->usbp, qmkusbp->config->bulk_out, buf, qmkusbp->ibqueue.bsize - sizeof(size_t));
-
- return false;
-}
-
-/*
- * 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);
+ usbStartReceiveI(endpoint->config.usbp, endpoint->config.ep, buffer, endpoint->ibqueue.bsize - sizeof(size_t));
}
-static size_t _writet(void *ip, const uint8_t *bp, size_t n, sysinterval_t timeout) {
- return obqWriteTimeout(&((QMKUSBDriver *)ip)->obqueue, bp, n, timeout);
-}
-
-static size_t _readt(void *ip, uint8_t *bp, size_t n, sysinterval_t timeout) {
- return ibqReadTimeout(&((QMKUSBDriver *)ip)->ibqueue, bp, n, timeout);
-}
-
-static const struct QMKUSBDriverVMT vmt = {0, _write, _read, _put, _get, _putt, _gett, _writet, _readt};
-
/**
* @brief Notification of empty buffer released into the input buffers queue.
*
* @param[in] bqp the buffers queue pointer.
*/
static void ibnotify(io_buffers_queue_t *bqp) {
- QMKUSBDriver *qmkusbp = bqGetLinkX(bqp);
- (void)qmkusb_start_receive(qmkusbp);
+ usb_endpoint_out_t *endpoint = bqGetLinkX(bqp);
+ usb_start_receive(endpoint);
}
/**
@@ -130,22 +54,22 @@ static void ibnotify(io_buffers_queue_t *bqp) {
* @param[in] bqp the buffers queue pointer.
*/
static void obnotify(io_buffers_queue_t *bqp) {
- size_t n;
- QMKUSBDriver *qmkusbp = bqGetLinkX(bqp);
+ usb_endpoint_in_t *endpoint = bqGetLinkX(bqp);
- /* If the USB driver is not in the appropriate state then transactions
+ /* If the USB endpoint is not in the appropriate state then transactions
must not be started.*/
- if ((usbGetDriverStateI(qmkusbp->config->usbp) != USB_ACTIVE) || (qmkusbp->state != QMKUSB_READY)) {
+ if ((usbGetDriverStateI(endpoint->config.usbp) != USB_ACTIVE)) {
return;
}
/* Checking if there is already a transaction ongoing on the endpoint.*/
- if (!usbGetTransmitStatusI(qmkusbp->config->usbp, qmkusbp->config->bulk_in)) {
+ if (!usbGetTransmitStatusI(endpoint->config.usbp, endpoint->config.ep)) {
/* Trying to get a full buffer.*/
- uint8_t *buf = obqGetFullBufferI(&qmkusbp->obqueue, &n);
- if (buf != NULL) {
+ size_t n;
+ uint8_t *buffer = obqGetFullBufferI(&endpoint->obqueue, &n);
+ if (buffer != NULL) {
/* Buffer found, starting a new transaction.*/
- usbStartTransmitI(qmkusbp->config->usbp, qmkusbp->config->bulk_in, buf, n);
+ usbStartTransmitI(endpoint->config.usbp, endpoint->config.ep, buffer, n);
}
}
}
@@ -154,264 +78,149 @@ static void obnotify(io_buffers_queue_t *bqp) {
/* Driver exported functions. */
/*===========================================================================*/
-/**
- * @brief Serial Driver initialization.
- * @note This function is implicitly invoked by @p halInit(), there is
- * no need to explicitly initialize the driver.
- *
- * @init
- */
-void qmkusbInit(void) {}
+void usb_endpoint_in_init(usb_endpoint_in_t *endpoint) {
+ usb_endpoint_config_t *config = &endpoint->config;
+ endpoint->ep_config.in_state = &endpoint->ep_in_state;
-/**
- * @brief Initializes a generic full duplex driver object.
- * @details The HW dependent part of the initialization has to be performed
- * outside, usually in the hardware initialization code.
- *
- * @param[out] qmkusbp pointer to a @p QMKUSBDriver structure
- *
- * @init
- */
-void qmkusbObjectInit(QMKUSBDriver *qmkusbp, const QMKUSBConfig *config) {
- qmkusbp->vmt = &vmt;
- osalEventObjectInit(&qmkusbp->event);
- qmkusbp->state = QMKUSB_STOP;
- // Note that the config uses the USB direction naming
- ibqObjectInit(&qmkusbp->ibqueue, true, config->ob, config->out_size, config->out_buffers, ibnotify, qmkusbp);
- obqObjectInit(&qmkusbp->obqueue, true, config->ib, config->in_size, config->in_buffers, obnotify, qmkusbp);
+#if defined(USB_ENDPOINTS_ARE_REORDERABLE)
+ if (endpoint->is_shared) {
+ endpoint->ep_config.out_state = &endpoint->ep_out_state;
+ }
+#endif
+ obqObjectInit(&endpoint->obqueue, true, config->buffer, config->buffer_size, config->buffer_capacity, obnotify, endpoint);
}
-/**
- * @brief Configures and starts the driver.
- *
- * @param[in] qmkusbp pointer to a @p QMKUSBDriver object
- * @param[in] config the serial over USB driver configuration
- *
- * @api
- */
-void qmkusbStart(QMKUSBDriver *qmkusbp, const QMKUSBConfig *config) {
- USBDriver *usbp = config->usbp;
+void usb_endpoint_out_init(usb_endpoint_out_t *endpoint) {
+ usb_endpoint_config_t *config = &endpoint->config;
+ endpoint->ep_config.out_state = &endpoint->ep_out_state;
+ ibqObjectInit(&endpoint->ibqueue, true, config->buffer, config->buffer_size, config->buffer_capacity, ibnotify, endpoint);
+}
- osalDbgCheck(qmkusbp != NULL);
+void usb_endpoint_in_start(usb_endpoint_in_t *endpoint) {
+ osalDbgCheck(endpoint != NULL);
osalSysLock();
- osalDbgAssert((qmkusbp->state == QMKUSB_STOP) || (qmkusbp->state == QMKUSB_READY), "invalid state");
- usbp->in_params[config->bulk_in - 1U] = qmkusbp;
- usbp->out_params[config->bulk_out - 1U] = qmkusbp;
- if (config->int_in > 0U) {
- usbp->in_params[config->int_in - 1U] = qmkusbp;
- }
- qmkusbp->config = config;
- qmkusbp->state = QMKUSB_READY;
+ osalDbgAssert((usbGetDriverStateI(endpoint->config.usbp) == USB_STOP) || (usbGetDriverStateI(endpoint->config.usbp) == USB_READY), "invalid state");
+ endpoint->config.usbp->in_params[endpoint->config.ep - 1U] = endpoint;
+ endpoint->timed_out = false;
osalSysUnlock();
}
-/**
- * @brief Stops the driver.
- * @details Any thread waiting on the driver's queues will be awakened with
- * the message @p MSG_RESET.
- *
- * @param[in] qmkusbp pointer to a @p QMKUSBDriver object
- *
- * @api
- */
-void qmkusbStop(QMKUSBDriver *qmkusbp) {
- USBDriver *usbp = qmkusbp->config->usbp;
-
- osalDbgCheck(qmkusbp != NULL);
+void usb_endpoint_out_start(usb_endpoint_out_t *endpoint) {
+ osalDbgCheck(endpoint != NULL);
osalSysLock();
+ osalDbgAssert((usbGetDriverStateI(endpoint->config.usbp) == USB_STOP) || (usbGetDriverStateI(endpoint->config.usbp) == USB_READY), "invalid state");
+ endpoint->config.usbp->out_params[endpoint->config.ep - 1U] = endpoint;
+ endpoint->timed_out = false;
+ osalSysUnlock();
+}
- osalDbgAssert((qmkusbp->state == QMKUSB_STOP) || (qmkusbp->state == QMKUSB_READY), "invalid state");
+void usb_endpoint_in_stop(usb_endpoint_in_t *endpoint) {
+ osalDbgCheck(endpoint != NULL);
- /* Driver in stopped state.*/
- usbp->in_params[qmkusbp->config->bulk_in - 1U] = NULL;
- usbp->out_params[qmkusbp->config->bulk_out - 1U] = NULL;
- if (qmkusbp->config->int_in > 0U) {
- usbp->in_params[qmkusbp->config->int_in - 1U] = NULL;
- }
- qmkusbp->config = NULL;
- qmkusbp->state = QMKUSB_STOP;
+ osalSysLock();
+ endpoint->config.usbp->in_params[endpoint->config.ep - 1U] = NULL;
- /* Enforces a disconnection.*/
- chnAddFlagsI(qmkusbp, CHN_DISCONNECTED);
- ibqResetI(&qmkusbp->ibqueue);
- obqResetI(&qmkusbp->obqueue);
+ bqSuspendI(&endpoint->obqueue);
+ obqResetI(&endpoint->obqueue);
+ if (endpoint->report_storage != NULL) {
+ endpoint->report_storage->reset_report(endpoint->report_storage->reports);
+ }
osalOsRescheduleS();
-
osalSysUnlock();
}
-/**
- * @brief USB device suspend handler.
- * @details Generates a @p CHN_DISCONNECT event and puts queues in
- * non-blocking mode, this way the application cannot get stuck
- * in the middle of an I/O operations.
- * @note If this function is not called from an ISR then an explicit call
- * to @p osalOsRescheduleS() in necessary afterward.
- *
- * @param[in] qmkusbp pointer to a @p QMKUSBDriver object
- *
- * @iclass
- */
-void qmkusbSuspendHookI(QMKUSBDriver *qmkusbp) {
- chnAddFlagsI(qmkusbp, CHN_DISCONNECTED);
- bqSuspendI(&qmkusbp->ibqueue);
- bqSuspendI(&qmkusbp->obqueue);
-}
+void usb_endpoint_out_stop(usb_endpoint_out_t *endpoint) {
+ osalDbgCheck(endpoint != NULL);
-/**
- * @brief USB device wakeup handler.
- * @details Generates a @p CHN_CONNECT event and resumes normal queues
- * operations.
- *
- * @note If this function is not called from an ISR then an explicit call
- * to @p osalOsRescheduleS() in necessary afterward.
- *
- * @param[in] qmkusbp pointer to a @p QMKUSBDriver object
- *
- * @iclass
- */
-void qmkusbWakeupHookI(QMKUSBDriver *qmkusbp) {
- chnAddFlagsI(qmkusbp, CHN_CONNECTED);
- bqResumeX(&qmkusbp->ibqueue);
- bqResumeX(&qmkusbp->obqueue);
-}
+ osalSysLock();
+ osalDbgAssert((usbGetDriverStateI(endpoint->config.usbp) == USB_STOP) || (usbGetDriverStateI(endpoint->config.usbp) == USB_READY), "invalid state");
-/**
- * @brief USB device configured handler.
- *
- * @param[in] qmkusbp pointer to a @p QMKUSBDriver object
- *
- * @iclass
- */
-void qmkusbConfigureHookI(QMKUSBDriver *qmkusbp) {
- ibqResetI(&qmkusbp->ibqueue);
- bqResumeX(&qmkusbp->ibqueue);
- obqResetI(&qmkusbp->obqueue);
- bqResumeX(&qmkusbp->obqueue);
- chnAddFlagsI(qmkusbp, CHN_CONNECTED);
- (void)qmkusb_start_receive(qmkusbp);
+ bqSuspendI(&endpoint->ibqueue);
+ ibqResetI(&endpoint->ibqueue);
+ osalOsRescheduleS();
+ osalSysUnlock();
}
-/**
- * @brief Default requests hook.
- * @details Applications wanting to use the Serial over USB driver can use
- * this function as requests hook in the USB configuration.
- * The following requests are emulated:
- * - CDC_GET_LINE_CODING.
- * - CDC_SET_LINE_CODING.
- * - CDC_SET_CONTROL_LINE_STATE.
- * .
- *
- * @param[in] usbp pointer to the @p USBDriver object
- * @return The hook status.
- * @retval true Message handled internally.
- * @retval false Message not handled.
- */
-bool qmkusbRequestsHook(USBDriver *usbp) {
- if ((usbp->setup[0] & USB_RTYPE_TYPE_MASK) == USB_RTYPE_TYPE_CLASS) {
- switch (usbp->setup[1]) {
- case CDC_GET_LINE_CODING:
- usbSetupTransfer(usbp, (uint8_t *)&linecoding, sizeof(linecoding), NULL);
- return true;
- case CDC_SET_LINE_CODING:
- usbSetupTransfer(usbp, (uint8_t *)&linecoding, sizeof(linecoding), NULL);
- return true;
- case CDC_SET_CONTROL_LINE_STATE:
- /* Nothing to do, there are no control lines.*/
- usbSetupTransfer(usbp, NULL, 0, NULL);
- return true;
- default:
- return false;
- }
- }
- return false;
-}
+void usb_endpoint_in_suspend_cb(usb_endpoint_in_t *endpoint) {
+ bqSuspendI(&endpoint->obqueue);
+ obqResetI(&endpoint->obqueue);
-/**
- * @brief SOF handler.
- * @details The SOF interrupt is used for automatic flushing of incomplete
- * buffers pending in the output queue.
- *
- * @param[in] qmkusbp pointer to a @p QMKUSBDriver object
- *
- * @iclass
- */
-void qmkusbSOFHookI(QMKUSBDriver *qmkusbp) {
- /* If the USB driver is not in the appropriate state then transactions
- must not be started.*/
- if ((usbGetDriverStateI(qmkusbp->config->usbp) != USB_ACTIVE) || (qmkusbp->state != QMKUSB_READY)) {
- return;
+ if (endpoint->report_storage != NULL) {
+ endpoint->report_storage->reset_report(endpoint->report_storage->reports);
}
+}
- /* If there is already a transaction ongoing then another one cannot be
- started.*/
- if (usbGetTransmitStatusI(qmkusbp->config->usbp, qmkusbp->config->bulk_in)) {
- return;
- }
+void usb_endpoint_out_suspend_cb(usb_endpoint_out_t *endpoint) {
+ bqSuspendI(&endpoint->ibqueue);
+ ibqResetI(&endpoint->ibqueue);
+}
- /* 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);
+void usb_endpoint_in_wakeup_cb(usb_endpoint_in_t *endpoint) {
+ bqResumeX(&endpoint->obqueue);
+}
- /* 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;
- }
+void usb_endpoint_out_wakeup_cb(usb_endpoint_out_t *endpoint) {
+ bqResumeX(&endpoint->ibqueue);
+}
- osalDbgAssert(buf != NULL, "queue is empty");
+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);
+}
- usbStartTransmitI(qmkusbp->config->usbp, qmkusbp->config->bulk_in, buf, n);
- }
+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);
}
-/**
- * @brief Default data transmitted callback.
- * @details The application must use this function as callback for the IN
- * data endpoint.
- *
- * @param[in] usbp pointer to the @p USBDriver object
- * @param[in] ep IN endpoint number
- */
-void qmkusbDataTransmitted(USBDriver *usbp, usbep_t ep) {
- uint8_t * buf;
- size_t n;
- QMKUSBDriver *qmkusbp = usbp->in_params[ep - 1U];
+void usb_endpoint_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 (qmkusbp == NULL) {
+ if (endpoint == NULL) {
return;
}
osalSysLockFromISR();
- /* Signaling that space is available in the output queue.*/
- chnAddFlagsI(qmkusbp, CHN_OUTPUT_EMPTY);
+ /* Sending succeded, so we can reset the timed out state. */
+ endpoint->timed_out = false;
/* Freeing the buffer just transmitted, if it was not a zero size packet.*/
- if (usbp->epc[ep]->in_state->txsize > 0U) {
- obqReleaseEmptyBufferI(&qmkusbp->obqueue);
+ if (!obqIsEmptyI(&endpoint->obqueue) && usbp->epc[ep]->in_state->txsize > 0U) {
+ /* Store the last send report in the endpoint to be retrieved by a
+ * GET_REPORT request or IDLE report handling. */
+ if (endpoint->report_storage != NULL) {
+ buffer = obqGetFullBufferI(&endpoint->obqueue, &n);
+ endpoint->report_storage->set_report(endpoint->report_storage->reports, buffer, n);
+ }
+ obqReleaseEmptyBufferI(&endpoint->obqueue);
}
/* Checking if there is a buffer ready for transmission.*/
- buf = obqGetFullBufferI(&qmkusbp->obqueue, &n);
+ buffer = obqGetFullBufferI(&endpoint->obqueue, &n);
- if (buf != NULL) {
+ if (buffer != NULL) {
/* The endpoint cannot be busy, we are in the context of the callback,
so it is safe to transmit without a check.*/
- usbStartTransmitI(usbp, ep, buf, n);
- } else if ((usbp->epc[ep]->in_state->txsize > 0U) && ((usbp->epc[ep]->in_state->txsize & ((size_t)usbp->epc[ep]->in_maxsize - 1U)) == 0U)) {
+ usbStartTransmitI(usbp, ep, buffer, n);
+ } else if ((usbp->epc[ep]->ep_mode == USB_EP_MODE_TYPE_BULK) && (usbp->epc[ep]->in_state->txsize > 0U) && ((usbp->epc[ep]->in_state->txsize & ((size_t)usbp->epc[ep]->in_maxsize - 1U)) == 0U)) {
/* Transmit zero sized packet in case the last one has maximum allowed
- size. Otherwise the recipient may expect more data coming soon and
- not return buffered data to app. See section 5.8.3 Bulk Transfer
- Packet Size Constraints of the USB Specification document.*/
- if (!qmkusbp->config->fixed_size) {
- usbStartTransmitI(usbp, ep, usbp->setup, 0);
- }
-
+ * size. Otherwise the recipient may expect more data coming soon and
+ * not return buffered data to app. See section 5.8.3 Bulk Transfer
+ * Packet Size Constraints of the USB Specification document. */
+ usbStartTransmitI(usbp, ep, usbp->setup, 0);
} else {
/* Nothing to transmit.*/
}
@@ -419,47 +228,114 @@ void qmkusbDataTransmitted(USBDriver *usbp, usbep_t ep) {
osalSysUnlockFromISR();
}
-/**
- * @brief Default data received callback.
- * @details The application must use this function as callback for the OUT
- * data endpoint.
- *
- * @param[in] usbp pointer to the @p USBDriver object
- * @param[in] ep OUT endpoint number
- */
-void qmkusbDataReceived(USBDriver *usbp, usbep_t ep) {
- QMKUSBDriver *qmkusbp = usbp->out_params[ep - 1U];
- if (qmkusbp == NULL) {
+void usb_endpoint_out_rx_complete_cb(USBDriver *usbp, usbep_t ep) {
+ usb_endpoint_out_t *endpoint = usbp->out_params[ep - 1U];
+ if (endpoint == NULL) {
return;
}
osalSysLockFromISR();
- /* Signaling that data is available in the input queue.*/
- chnAddFlagsI(qmkusbp, CHN_INPUT_AVAILABLE);
-
- /* Posting the filled buffer in the queue.*/
- ibqPostFullBufferI(&qmkusbp->ibqueue, usbGetReceiveTransactionSizeX(qmkusbp->config->usbp, qmkusbp->config->bulk_out));
+ size_t size = usbGetReceiveTransactionSizeX(usbp, ep);
+ if (size > 0) {
+ /* Posting the filled buffer in the queue.*/
+ ibqPostFullBufferI(&endpoint->ibqueue, usbGetReceiveTransactionSizeX(endpoint->config.usbp, endpoint->config.ep));
+ }
- /* The endpoint cannot be busy, we are in the context of the callback,
- so a packet is in the buffer for sure. Trying to get a free buffer
- for the next transaction.*/
- (void)qmkusb_start_receive(qmkusbp);
+ /* The endpoint cannot be busy, we are in the context of the callback, so a
+ * packet is in the buffer for sure. Trying to get a free buffer for the
+ * next transaction.*/
+ usb_start_receive(endpoint);
osalSysUnlockFromISR();
}
-/**
- * @brief Default data received callback.
- * @details The application must use this function as callback for the IN
- * interrupt endpoint.
- *
- * @param[in] usbp pointer to the @p USBDriver object
- * @param[in] ep endpoint number
- */
-void qmkusbInterruptTransmitted(USBDriver *usbp, usbep_t ep) {
- (void)usbp;
- (void)ep;
+bool usb_endpoint_in_send(usb_endpoint_in_t *endpoint, const uint8_t *data, size_t size, sysinterval_t timeout, bool buffered) {
+ osalDbgCheck((endpoint != NULL) && (data != NULL) && (size > 0U) && (size <= endpoint->config.buffer_size));
+
+ osalSysLock();
+ if (usbGetDriverStateI(endpoint->config.usbp) != USB_ACTIVE) {
+ osalSysUnlock();
+ return false;
+ }
+
+ /* Short circuit the waiting if this endpoint timed out before, e.g. if
+ * nobody is listening on this endpoint (is disconnected) such as
+ * `hid_listen`/`qmk console` or we are in an environment with a very
+ * restricted USB stack. The reason is to not introduce micro lock-ups if
+ * the report is send periodically. */
+ if (endpoint->timed_out && timeout != TIME_INFINITE) {
+ timeout = TIME_IMMEDIATE;
+ }
+ osalSysUnlock();
+
+ while (true) {
+ size_t sent = obqWriteTimeout(&endpoint->obqueue, data, size, timeout);
+
+ if (sent < size) {
+ osalSysLock();
+ endpoint->timed_out |= sent == 0;
+ bqSuspendI(&endpoint->obqueue);
+ obqResetI(&endpoint->obqueue);
+ bqResumeX(&endpoint->obqueue);
+ osalOsRescheduleS();
+ osalSysUnlock();
+ continue;
+ }
+
+ if (!buffered) {
+ obqFlush(&endpoint->obqueue);
+ }
+
+ return true;
+ }
+}
+
+void usb_endpoint_in_flush(usb_endpoint_in_t *endpoint, bool padded) {
+ osalDbgCheck(endpoint != NULL);
+
+ output_buffers_queue_t *obqp = &endpoint->obqueue;
+
+ if (padded && obqp->ptr != NULL) {
+ ptrdiff_t bytes_left = (size_t)obqp->top - (size_t)obqp->ptr;
+ while (bytes_left > 0) {
+ // Putting bytes into a buffer that has space left should never
+ // fail and be instant, therefore we don't check the return value
+ // for errors here.
+ obqPutTimeout(obqp, 0, TIME_IMMEDIATE);
+ bytes_left--;
+ }
+ }
+
+ obqFlush(obqp);
+}
+
+bool usb_endpoint_in_is_inactive(usb_endpoint_in_t *endpoint) {
+ osalDbgCheck(endpoint != NULL);
+
+ osalSysLock();
+ bool inactive = obqIsEmptyI(&endpoint->obqueue) && !usbGetTransmitStatusI(endpoint->config.usbp, endpoint->config.ep);
+ osalSysUnlock();
+
+ return inactive;
}
-/** @} */
+bool usb_endpoint_out_receive(usb_endpoint_out_t *endpoint, uint8_t *data, size_t size, sysinterval_t timeout) {
+ osalDbgCheck((endpoint != NULL) && (data != NULL) && (size > 0U));
+
+ osalSysLock();
+ if (usbGetDriverStateI(endpoint->config.usbp) != USB_ACTIVE) {
+ osalSysUnlock();
+ return false;
+ }
+
+ if (endpoint->timed_out && timeout != TIME_INFINITE) {
+ timeout = TIME_IMMEDIATE;
+ }
+ osalSysUnlock();
+
+ const size_t received = ibqReadTimeout(&endpoint->ibqueue, data, size, timeout);
+ endpoint->timed_out = received == 0;
+
+ return received == size;
+}
diff --git a/tmk_core/protocol/chibios/usb_driver.h b/tmk_core/protocol/chibios/usb_driver.h
index f94387debd..26787e1eba 100644
--- a/tmk_core/protocol/chibios/usb_driver.h
+++ b/tmk_core/protocol/chibios/usb_driver.h
@@ -1,177 +1,209 @@
-/*
- ChibiOS - Copyright (C) 2006..2016 Giovanni Di Sirio
-
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
-*/
-
-/**
- * @file usb_driver.h
- * @brief Usb driver suitable for both packet and serial formats
- *
- * @addtogroup SERIAL_USB
- * @{
- */
+// Copyright 2023 Stefan Kerkmann (@KarlK90)
+// Copyright 2020 Ryan (@fauxpark)
+// Copyright 2016 Fredizzimo
+// Copyright 2016 Giovanni Di Sirio
+// SPDX-License-Identifier: GPL-3.0-or-later OR Apache-2.0
#pragma once
-#include <hal_usb_cdc.h>
-
-/*===========================================================================*/
-/* Driver constants. */
-/*===========================================================================*/
-
-/*===========================================================================*/
-/* Derived constants and error checks. */
-/*===========================================================================*/
+#include <hal_buffers.h>
+#include "usb_descriptor.h"
+#include "chibios_config.h"
+#include "usb_report_handling.h"
+#include "string.h"
+#include "timer.h"
#if HAL_USE_USB == FALSE
# error "The USB Driver requires HAL_USE_USB"
#endif
-/*===========================================================================*/
-/* Driver data structures and types. */
-/*===========================================================================*/
+/* USB Low Level driver specific endpoint fields */
+#if !defined(usb_lld_endpoint_fields)
+# define usb_lld_endpoint_fields \
+ 2, /* IN multiplier */ \
+ NULL, /* SETUP buffer (not a SETUP endpoint) */
+#endif
-/**
- * @brief Driver state machine possible states.
- */
-typedef enum {
- QMKUSB_UNINIT = 0, /**< Not initialized. */
- QMKUSB_STOP = 1, /**< Stopped. */
- QMKUSB_READY = 2 /**< Ready. */
-} qmkusbstate_t;
-
-/**
- * @brief Structure representing a serial over USB driver.
+/*
+ * 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 struct QMKUSBDriver QMKUSBDriver;
+#define QMK_USB_ENDPOINT_IN(mode, ep_size, ep_num, _buffer_capacity, _usb_requests_cb, _report_storage) \
+ { \
+ .usb_requests_cb = _usb_requests_cb, .report_storage = _report_storage, \
+ .ep_config = \
+ { \
+ mode, /* EP Mode */ \
+ NULL, /* SETUP packet notification callback */ \
+ usb_endpoint_in_tx_complete_cb, /* IN notification callback */ \
+ NULL, /* OUT notification callback */ \
+ ep_size, /* IN maximum packet size */ \
+ 0, /* OUT maximum packet size */ \
+ NULL, /* IN Endpoint state */ \
+ NULL, /* OUT endpoint state */ \
+ usb_lld_endpoint_fields /* USB driver specific endpoint fields */ \
+ }, \
+ .config = { \
+ .usbp = &USB_DRIVER, \
+ .ep = ep_num, \
+ .buffer_capacity = _buffer_capacity, \
+ .buffer_size = ep_size, \
+ .buffer = (_Alignas(4) uint8_t[BQ_BUFFER_SIZE(_buffer_capacity, ep_size)]){0}, \
+ } \
+ }
+
+#if !defined(USB_ENDPOINTS_ARE_REORDERABLE)
+
+# define QMK_USB_ENDPOINT_OUT(mode, ep_size, ep_num, _buffer_capacity) \
+ { \
+ .ep_config = \
+ { \
+ mode, /* EP Mode */ \
+ NULL, /* SETUP packet notification callback */ \
+ NULL, /* IN notification callback */ \
+ usb_endpoint_out_rx_complete_cb, /* OUT notification callback */ \
+ 0, /* IN maximum packet size */ \
+ ep_size, /* OUT maximum packet size */ \
+ NULL, /* IN Endpoint state */ \
+ NULL, /* OUT endpoint state */ \
+ usb_lld_endpoint_fields /* USB driver specific endpoint fields */ \
+ }, \
+ .config = { \
+ .usbp = &USB_DRIVER, \
+ .ep = ep_num, \
+ .buffer_capacity = _buffer_capacity, \
+ .buffer_size = ep_size, \
+ .buffer = (_Alignas(4) uint8_t[BQ_BUFFER_SIZE(_buffer_capacity, ep_size)]){0} \
+ } \
+ }
+
+#else
+
+# define QMK_USB_ENDPOINT_IN_SHARED(mode, ep_size, ep_num, _buffer_capacity, _usb_requests_cb, _report_storage) \
+ { \
+ .usb_requests_cb = _usb_requests_cb, .is_shared = true, .report_storage = _report_storage, \
+ .ep_config = \
+ { \
+ mode, /* EP Mode */ \
+ NULL, /* SETUP packet notification callback */ \
+ usb_endpoint_in_tx_complete_cb, /* IN notification callback */ \
+ usb_endpoint_out_rx_complete_cb, /* OUT notification callback */ \
+ ep_size, /* IN maximum packet size */ \
+ ep_size, /* OUT maximum packet size */ \
+ NULL, /* IN Endpoint state */ \
+ NULL, /* OUT endpoint state */ \
+ usb_lld_endpoint_fields /* USB driver specific endpoint fields */ \
+ }, \
+ .config = { \
+ .usbp = &USB_DRIVER, \
+ .ep = ep_num, \
+ .buffer_capacity = _buffer_capacity, \
+ .buffer_size = ep_size, \
+ .buffer = (_Alignas(4) uint8_t[BQ_BUFFER_SIZE(_buffer_capacity, ep_size)]){0}, \
+ } \
+ }
+
+/* The current assumption is that there are no standalone OUT endpoints, so the
+ * OUT endpoint is always initialized by the IN endpoint. */
+# define QMK_USB_ENDPOINT_OUT(mode, ep_size, ep_num, _buffer_capacity) \
+ { \
+ .ep_config = \
+ { \
+ 0 /* Already defined in the IN endpoint */ \
+ }, \
+ .config = { \
+ .usbp = &USB_DRIVER, \
+ .ep = ep_num, \
+ .buffer_capacity = _buffer_capacity, \
+ .buffer_size = ep_size, \
+ .buffer = (_Alignas(4) uint8_t[BQ_BUFFER_SIZE(_buffer_capacity, ep_size)]){0} \
+ } \
+ }
+
+#endif
-/**
- * @brief Serial over USB Driver configuration structure.
- * @details An instance of this structure must be passed to @p sduStart()
- * in order to configure and start the driver operations.
- */
typedef struct {
/**
* @brief USB driver to use.
*/
USBDriver *usbp;
+
/**
- * @brief Bulk IN endpoint used for outgoing data transfer.
- */
- usbep_t bulk_in;
- /**
- * @brief Bulk OUT endpoint used for incoming data transfer.
- */
- usbep_t bulk_out;
- /**
- * @brief Interrupt IN endpoint used for notifications.
- * @note If set to zero then the INT endpoint is assumed to be not
- * present, USB descriptors must be changed accordingly.
+ * @brief Endpoint used for data transfer
*/
- usbep_t int_in;
+ usbep_t ep;
/**
- * @brief The number of buffers in the queues
+ * @brief The number of buffers in the queue
*/
- size_t in_buffers;
- size_t out_buffers;
+ size_t buffer_capacity;
/**
- * @brief The size of each buffer in the queue, typically the same as the endpoint size
+ * @brief The size of each buffer in the queue, same as the endpoint size
*/
- size_t in_size;
- size_t out_size;
+ size_t buffer_size;
/**
- * @brief Always send full buffers in_size (the rest is filled with zeroes)
+ * @brief Buffer backing storage
*/
- bool fixed_size;
+ uint8_t *buffer;
+} usb_endpoint_config_t;
- /* Input buffer
- * @note needs to be initialized with a memory buffer of the right size
- */
- uint8_t *ib;
- /* Output buffer
- * @note needs to be initialized with a memory buffer of the right size
- */
- uint8_t *ob;
-} QMKUSBConfig;
+typedef struct {
+ output_buffers_queue_t obqueue;
+ USBEndpointConfig ep_config;
+ USBInEndpointState ep_in_state;
+#if defined(USB_ENDPOINTS_ARE_REORDERABLE)
+ USBOutEndpointState ep_out_state;
+ bool is_shared;
+#endif
+ usb_endpoint_config_t config;
+ usbreqhandler_t usb_requests_cb;
+ bool timed_out;
+ usb_report_storage_t *report_storage;
+} usb_endpoint_in_t;
-/**
- * @brief @p SerialDriver specific data.
- */
-#define _qmk_usb_driver_data \
- _base_asynchronous_channel_data /* Driver state.*/ \
- qmkusbstate_t state; \
- /* Input buffers queue.*/ \
- input_buffers_queue_t ibqueue; \
- /* Output queue.*/ \
- output_buffers_queue_t obqueue; \
- /* End of the mandatory fields.*/ \
- /* Current configuration data.*/ \
- const QMKUSBConfig *config;
-
-/**
- * @brief @p SerialUSBDriver specific methods.
- */
-#define _qmk_usb_driver_methods _base_asynchronous_channel_methods
+typedef struct {
+ input_buffers_queue_t ibqueue;
+ USBEndpointConfig ep_config;
+ USBOutEndpointState ep_out_state;
+ usb_endpoint_config_t config;
+ bool timed_out;
+} usb_endpoint_out_t;
-/**
- * @extends BaseAsynchronousChannelVMT
- *
- * @brief @p SerialDriver virtual methods table.
- */
-struct QMKUSBDriverVMT {
- _qmk_usb_driver_methods
-};
+#ifdef __cplusplus
+extern "C" {
+#endif
-/**
- * @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
-};
+void usb_endpoint_in_init(usb_endpoint_in_t *endpoint);
+void usb_endpoint_in_start(usb_endpoint_in_t *endpoint);
+void usb_endpoint_in_stop(usb_endpoint_in_t *endpoint);
-/*===========================================================================*/
-/* Driver macros. */
-/*===========================================================================*/
+bool usb_endpoint_in_send(usb_endpoint_in_t *endpoint, const uint8_t *data, size_t size, sysinterval_t timeout, bool buffered);
+void usb_endpoint_in_flush(usb_endpoint_in_t *endpoint, bool padded);
+bool usb_endpoint_in_is_inactive(usb_endpoint_in_t *endpoint);
-/*===========================================================================*/
-/* External declarations. */
-/*===========================================================================*/
+void usb_endpoint_in_suspend_cb(usb_endpoint_in_t *endpoint);
+void usb_endpoint_in_wakeup_cb(usb_endpoint_in_t *endpoint);
+void usb_endpoint_in_configure_cb(usb_endpoint_in_t *endpoint);
+void usb_endpoint_in_tx_complete_cb(USBDriver *usbp, usbep_t ep);
+
+void usb_endpoint_out_init(usb_endpoint_out_t *endpoint);
+void usb_endpoint_out_start(usb_endpoint_out_t *endpoint);
+void usb_endpoint_out_stop(usb_endpoint_out_t *endpoint);
+
+bool usb_endpoint_out_receive(usb_endpoint_out_t *endpoint, uint8_t *data, size_t size, sysinterval_t timeout);
+
+void usb_endpoint_out_suspend_cb(usb_endpoint_out_t *endpoint);
+void usb_endpoint_out_wakeup_cb(usb_endpoint_out_t *endpoint);
+void usb_endpoint_out_configure_cb(usb_endpoint_out_t *endpoint);
+void usb_endpoint_out_rx_complete_cb(USBDriver *usbp, usbep_t ep);
-#ifdef __cplusplus
-extern "C" {
-#endif
-void qmkusbInit(void);
-void qmkusbObjectInit(QMKUSBDriver *qmkusbp, const QMKUSBConfig *config);
-void qmkusbStart(QMKUSBDriver *qmkusbp, const QMKUSBConfig *config);
-void qmkusbStop(QMKUSBDriver *qmkusbp);
-void qmkusbSuspendHookI(QMKUSBDriver *qmkusbp);
-void qmkusbWakeupHookI(QMKUSBDriver *qmkusbp);
-void qmkusbConfigureHookI(QMKUSBDriver *qmkusbp);
-bool qmkusbRequestsHook(USBDriver *usbp);
-void qmkusbSOFHookI(QMKUSBDriver *qmkusbp);
-void qmkusbDataTransmitted(USBDriver *usbp, usbep_t ep);
-void qmkusbDataReceived(USBDriver *usbp, usbep_t ep);
-void qmkusbInterruptTransmitted(USBDriver *usbp, usbep_t ep);
#ifdef __cplusplus
}
#endif
diff --git a/tmk_core/protocol/chibios/usb_endpoints.c b/tmk_core/protocol/chibios/usb_endpoints.c
new file mode 100644
index 0000000000..37ec3c722f
--- /dev/null
+++ b/tmk_core/protocol/chibios/usb_endpoints.c
@@ -0,0 +1,152 @@
+// Copyright 2023 Stefan Kerkmann (@KarlK90)
+// SPDX-License-Identifier: GPL-3.0-or-later
+
+#include <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
+};
diff --git a/tmk_core/protocol/chibios/usb_endpoints.h b/tmk_core/protocol/chibios/usb_endpoints.h
new file mode 100644
index 0000000000..a4e5ed88fc
--- /dev/null
+++ b/tmk_core/protocol/chibios/usb_endpoints.h
@@ -0,0 +1,137 @@
+// Copyright 2023 Stefan Kerkmann (@KarlK90)
+// SPDX-License-Identifier: GPL-3.0-or-later
+
+#pragma once
+
+#include "usb_descriptor.h"
+
+#if !defined(USB_DEFAULT_BUFFER_CAPACITY)
+# define USB_DEFAULT_BUFFER_CAPACITY 4
+#endif
+
+#if !defined(KEYBOARD_IN_CAPACITY)
+# define KEYBOARD_IN_CAPACITY USB_DEFAULT_BUFFER_CAPACITY
+#endif
+#if !defined(SHARED_IN_CAPACITY)
+# define SHARED_IN_CAPACITY USB_DEFAULT_BUFFER_CAPACITY
+#endif
+#if !defined(MOUSE_IN_CAPACITY)
+# define MOUSE_IN_CAPACITY USB_DEFAULT_BUFFER_CAPACITY
+#endif
+
+#if !defined(JOYSTICK_IN_CAPACITY)
+# define JOYSTICK_IN_CAPACITY USB_DEFAULT_BUFFER_CAPACITY
+#endif
+
+#if !defined(DIGITIZER_IN_CAPACITY)
+# define DIGITIZER_IN_CAPACITY USB_DEFAULT_BUFFER_CAPACITY
+#endif
+
+#if !defined(CONSOLE_IN_CAPACITY)
+# define CONSOLE_IN_CAPACITY USB_DEFAULT_BUFFER_CAPACITY
+#endif
+
+#if !defined(CONSOLE_OUT_CAPACITY)
+# define CONSOLE_OUT_CAPACITY USB_DEFAULT_BUFFER_CAPACITY
+#endif
+
+#if !defined(RAW_IN_CAPACITY)
+# define RAW_IN_CAPACITY USB_DEFAULT_BUFFER_CAPACITY
+#endif
+
+#if !defined(RAW_OUT_CAPACITY)
+# define RAW_OUT_CAPACITY USB_DEFAULT_BUFFER_CAPACITY
+#endif
+
+#if !defined(MIDI_STREAM_IN_CAPACITY)
+# define MIDI_STREAM_IN_CAPACITY USB_DEFAULT_BUFFER_CAPACITY
+#endif
+
+#if !defined(MIDI_STREAM_OUT_CAPACITY)
+# define MIDI_STREAM_OUT_CAPACITY USB_DEFAULT_BUFFER_CAPACITY
+#endif
+
+#if !defined(CDC_IN_CAPACITY)
+# define CDC_IN_CAPACITY USB_DEFAULT_BUFFER_CAPACITY
+#endif
+
+#if !defined(CDC_OUT_CAPACITY)
+# define CDC_OUT_CAPACITY USB_DEFAULT_BUFFER_CAPACITY
+#endif
+
+#define CDC_SIGNALING_DUMMY_CAPACITY 1
+
+typedef enum {
+#if defined(SHARED_EP_ENABLE)
+ USB_ENDPOINT_IN_SHARED,
+#endif
+
+#if !defined(KEYBOARD_SHARED_EP)
+ USB_ENDPOINT_IN_KEYBOARD,
+#endif
+
+#if defined(MOUSE_ENABLE) && !defined(MOUSE_SHARED_EP)
+ USB_ENDPOINT_IN_MOUSE,
+#endif
+
+#if defined(JOYSTICK_ENABLE) && !defined(JOYSTICK_SHARED_EP)
+ USB_ENDPOINT_IN_JOYSTICK,
+#endif
+
+#if defined(DIGITIZER_ENABLE) && !defined(DIGITIZER_SHARED_EP)
+ USB_ENDPOINT_IN_DIGITIZER,
+#endif
+
+#if defined(CONSOLE_ENABLE)
+ USB_ENDPOINT_IN_CONSOLE,
+#endif
+
+#if defined(RAW_ENABLE)
+ USB_ENDPOINT_IN_RAW,
+#endif
+
+#if defined(MIDI_ENABLE)
+ USB_ENDPOINT_IN_MIDI,
+#endif
+
+#if defined(VIRTSER_ENABLE)
+ USB_ENDPOINT_IN_CDC_DATA,
+ USB_ENDPOINT_IN_CDC_SIGNALING,
+#endif
+ USB_ENDPOINT_IN_COUNT,
+/* All non shared endpoints have to be consequtive numbers starting from 0, so
+ * that they can be used as array indices. The shared endpoints all point to
+ * the same endpoint so they have to be defined last to not reset the enum
+ * counter. */
+#if defined(SHARED_EP_ENABLE)
+# if defined(KEYBOARD_SHARED_EP)
+ USB_ENDPOINT_IN_KEYBOARD = USB_ENDPOINT_IN_SHARED,
+# endif
+# if defined(MOUSE_SHARED_EP)
+ USB_ENDPOINT_IN_MOUSE = USB_ENDPOINT_IN_SHARED,
+# endif
+# if defined(JOYSTICK_SHARED_EP)
+ USB_ENDPOINT_IN_JOYSTICK = USB_ENDPOINT_IN_SHARED,
+# endif
+# if defined(DIGITIZER_SHARED_EP)
+ USB_ENDPOINT_IN_DIGITIZER = USB_ENDPOINT_IN_SHARED,
+# endif
+#endif
+} usb_endpoint_in_lut_t;
+
+#define IS_VALID_USB_ENDPOINT_IN_LUT(i) ((i) >= 0 && (i) < USB_ENDPOINT_IN_COUNT)
+
+usb_endpoint_in_lut_t usb_endpoint_interface_lut[TOTAL_INTERFACES];
+
+typedef enum {
+#if defined(RAW_ENABLE)
+ USB_ENDPOINT_OUT_RAW,
+#endif
+#if defined(MIDI_ENABLE)
+ USB_ENDPOINT_OUT_MIDI,
+#endif
+#if defined(VIRTSER_ENABLE)
+ USB_ENDPOINT_OUT_CDC_DATA,
+#endif
+ USB_ENDPOINT_OUT_COUNT,
+} usb_endpoint_out_lut_t;
diff --git a/tmk_core/protocol/chibios/usb_main.c b/tmk_core/protocol/chibios/usb_main.c
index 7b1e641213..ced5fd4fc2 100644
--- a/tmk_core/protocol/chibios/usb_main.c
+++ b/tmk_core/protocol/chibios/usb_main.c
@@ -1,45 +1,32 @@
-/*
- * (c) 2015 flabberast <[email protected]>
- *
- * Based on the following work:
- * - Guillaume Duc's raw hid example (MIT License)
- * https://github.com/guiduc/usb-hid-chibios-example
- * - PJRC Teensy examples (MIT License)
- * https://www.pjrc.com/teensy/usb_keyboard.html
- * - hasu's TMK keyboard code (GPL v2 and some code Modified BSD)
- * https://github.com/tmk/tmk_keyboard/
- * - ChibiOS demo code (Apache 2.0 License)
- * http://www.chibios.org
- *
- * Since some GPL'd code is used, this work is licensed under
- * GPL v2 or later.
- */
-
-/*
- * Implementation notes:
- *
- * USBEndpointConfig - Configured using explicit order instead of struct member name.
- * This is due to ChibiOS hal LLD differences, which is dependent on hardware,
- * "USBv1" devices have `ep_buffers` and "OTGv1" have `in_multiplier`.
- * Given `USBv1/hal_usb_lld.h` marks the field as "not currently used" this code file
- * makes the assumption this is safe to avoid littering with preprocessor directives.
- */
+// Copyright 2023 Stefan Kerkmann
+// Copyright 2020-2021 Ryan (@fauxpark)
+// Copyright 2020 Nick Brassel (@tzarc)
+// Copyright 2020 a-chol
+// Copyright 2020 xyzz
+// Copyright 2020 Joel Challis (@zvecr)
+// Copyright 2020 George (@goshdarnharris)
+// Copyright 2018 James Laird-Wah
+// Copyright 2018 Drashna Jaelre (@drashna)
+// Copyright 2016 Fredizzimo
+// Copyright 2016 Giovanni Di Sirio
+// SPDX-License-Identifier: GPL-3.0-or-later OR Apache-2.0
#include <ch.h>
#include <hal.h>
#include <string.h>
#include "usb_main.h"
+#include "usb_report_handling.h"
#include "host.h"
-#include "chibios_config.h"
-#include "debug.h"
#include "suspend.h"
+#include "timer.h"
#ifdef SLEEP_LED_ENABLE
# include "sleep_led.h"
# include "led.h"
#endif
#include "wait.h"
+#include "usb_endpoints.h"
#include "usb_device_state.h"
#include "usb_descriptor.h"
#include "usb_driver.h"
@@ -51,11 +38,6 @@
extern keymap_config_t keymap_config;
#endif
-#if defined(CONSOLE_ENABLE)
-# define RBUF_SIZE 256
-# include "ring_buffer.h"
-#endif
-
/* ---------------------------------------------------------
* Global interface variables and declarations
* ---------------------------------------------------------
@@ -69,33 +51,16 @@ extern keymap_config_t keymap_config;
# define usb_lld_disconnect_bus(usbp)
#endif
-uint8_t keyboard_idle __attribute__((aligned(2))) = 0;
-uint8_t keyboard_protocol __attribute__((aligned(2))) = 1;
-uint8_t keyboard_led_state = 0;
-volatile uint16_t keyboard_idle_count = 0;
-static virtual_timer_t keyboard_idle_timer;
-
-static void keyboard_idle_timer_cb(struct ch_virtual_timer *, void *arg);
+extern usb_endpoint_in_t usb_endpoints_in[USB_ENDPOINT_IN_COUNT];
+extern usb_endpoint_out_t usb_endpoints_out[USB_ENDPOINT_OUT_COUNT];
-report_keyboard_t keyboard_report_sent = {0};
-report_mouse_t mouse_report_sent = {0};
+uint8_t _Alignas(2) keyboard_idle = 0;
+uint8_t _Alignas(2) keyboard_protocol = 1;
+uint8_t keyboard_led_state = 0;
-union {
- uint8_t report_id;
- report_keyboard_t keyboard;
-#ifdef EXTRAKEY_ENABLE
- report_extra_t extra;
-#endif
-#ifdef MOUSE_ENABLE
- report_mouse_t mouse;
-#endif
-#ifdef DIGITIZER_ENABLE
- report_digitizer_t digitizer;
-#endif
-#ifdef JOYSTICK_ENABLE
- report_joystick_t joystick;
-#endif
-} universal_report_blank = {0};
+static bool __attribute__((__unused__)) send_report_buffered(usb_endpoint_in_lut_t endpoint, void *report, size_t size);
+static void __attribute__((__unused__)) flush_report_buffered(usb_endpoint_in_lut_t endpoint, bool padded);
+static bool __attribute__((__unused__)) receive_report(usb_endpoint_out_lut_t endpoint, void *report, size_t size);
/* ---------------------------------------------------------
* Descriptors and USB driver objects
@@ -109,6 +74,11 @@ union {
NULL, /* SETUP buffer (not a SETUP endpoint) */
#endif
+/*
+ * Handles the GET_DESCRIPTOR callback
+ *
+ * Returns the proper descriptor
+ */
static const USBDescriptor *usb_get_descriptor_cb(USBDriver *usbp, uint8_t dtype, uint8_t dindex, uint16_t wIndex) {
usb_control_request_t *setup = (usb_control_request_t *)usbp->setup;
@@ -123,299 +93,6 @@ static const USBDescriptor *usb_get_descriptor_cb(USBDriver *usbp, uint8_t dtype
return &descriptor;
}
-/*
- * USB notification callback that does nothing. Needed to work around bugs in
- * some USB LLDs that fail to resume the waiting thread when the notification
- * callback pointer is NULL.
- */
-static void dummy_usb_cb(USBDriver *usbp, usbep_t ep) {
- (void)usbp;
- (void)ep;
-}
-
-#ifndef KEYBOARD_SHARED_EP
-/* keyboard endpoint state structure */
-static USBInEndpointState kbd_ep_state;
-/* keyboard endpoint initialization structure (IN) - see USBEndpointConfig comment at top of file */
-static const USBEndpointConfig kbd_ep_config = {
- USB_EP_MODE_TYPE_INTR, /* Interrupt EP */
- NULL, /* SETUP packet notification callback */
- dummy_usb_cb, /* IN notification callback */
- NULL, /* OUT notification callback */
- KEYBOARD_EPSIZE, /* IN maximum packet size */
- 0, /* OUT maximum packet size */
- &kbd_ep_state, /* IN Endpoint state */
- NULL, /* OUT endpoint state */
- usb_lld_endpoint_fields /* USB driver specific endpoint fields */
-};
-#endif
-
-#if defined(MOUSE_ENABLE) && !defined(MOUSE_SHARED_EP)
-/* mouse endpoint state structure */
-static USBInEndpointState mouse_ep_state;
-
-/* mouse endpoint initialization structure (IN) - see USBEndpointConfig comment at top of file */
-static const USBEndpointConfig mouse_ep_config = {
- USB_EP_MODE_TYPE_INTR, /* Interrupt EP */
- NULL, /* SETUP packet notification callback */
- dummy_usb_cb, /* IN notification callback */
- NULL, /* OUT notification callback */
- MOUSE_EPSIZE, /* IN maximum packet size */
- 0, /* OUT maximum packet size */
- &mouse_ep_state, /* IN Endpoint state */
- NULL, /* OUT endpoint state */
- usb_lld_endpoint_fields /* USB driver specific endpoint fields */
-};
-#endif
-
-#ifdef SHARED_EP_ENABLE
-/* shared endpoint state structure */
-static USBInEndpointState shared_ep_state;
-
-/* shared endpoint initialization structure (IN) - see USBEndpointConfig comment at top of file */
-static const USBEndpointConfig shared_ep_config = {
- USB_EP_MODE_TYPE_INTR, /* Interrupt EP */
- NULL, /* SETUP packet notification callback */
- dummy_usb_cb, /* IN notification callback */
- NULL, /* OUT notification callback */
- SHARED_EPSIZE, /* IN maximum packet size */
- 0, /* OUT maximum packet size */
- &shared_ep_state, /* IN Endpoint state */
- NULL, /* OUT endpoint state */
- usb_lld_endpoint_fields /* USB driver specific endpoint fields */
-};
-#endif
-
-#if defined(JOYSTICK_ENABLE) && !defined(JOYSTICK_SHARED_EP)
-/* joystick endpoint state structure */
-static USBInEndpointState joystick_ep_state;
-
-/* joystick endpoint initialization structure (IN) - see USBEndpointConfig comment at top of file */
-static const USBEndpointConfig joystick_ep_config = {
- USB_EP_MODE_TYPE_INTR, /* Interrupt EP */
- NULL, /* SETUP packet notification callback */
- dummy_usb_cb, /* IN notification callback */
- NULL, /* OUT notification callback */
- JOYSTICK_EPSIZE, /* IN maximum packet size */
- 0, /* OUT maximum packet size */
- &joystick_ep_state, /* IN Endpoint state */
- NULL, /* OUT endpoint state */
- usb_lld_endpoint_fields /* USB driver specific endpoint fields */
-};
-#endif
-
-#if defined(DIGITIZER_ENABLE) && !defined(DIGITIZER_SHARED_EP)
-/* digitizer endpoint state structure */
-static USBInEndpointState digitizer_ep_state;
-
-/* digitizer endpoint initialization structure (IN) - see USBEndpointConfig comment at top of file */
-static const USBEndpointConfig digitizer_ep_config = {
- USB_EP_MODE_TYPE_INTR, /* Interrupt EP */
- NULL, /* SETUP packet notification callback */
- dummy_usb_cb, /* IN notification callback */
- NULL, /* OUT notification callback */
- DIGITIZER_EPSIZE, /* IN maximum packet size */
- 0, /* OUT maximum packet size */
- &digitizer_ep_state, /* IN Endpoint state */
- NULL, /* OUT endpoint state */
- usb_lld_endpoint_fields /* USB driver specific endpoint fields */
-};
-#endif
-
-#ifdef CONSOLE_ENABLE
-/* Console endpoint state structure */
-static USBInEndpointState console_ep_state;
-
-/* Console endpoint initialization structure (IN) - see USBEndpointConfig comment at top of file */
-static const USBEndpointConfig console_ep_config = {
- USB_EP_MODE_TYPE_INTR, /* Interrupt EP */
- NULL, /* SETUP packet notification callback */
- dummy_usb_cb, /* IN notification callback */
- NULL, /* OUT notification callback */
- CONSOLE_EPSIZE, /* IN maximum packet size */
- 0, /* OUT maximum packet size */
- &console_ep_state, /* IN Endpoint state */
- NULL, /* OUT endpoint state */
- usb_lld_endpoint_fields /* USB driver specific endpoint fields */
-};
-#endif
-
-#ifdef USB_ENDPOINTS_ARE_REORDERABLE
-typedef struct {
- size_t queue_capacity_in;
- size_t queue_capacity_out;
- USBInEndpointState in_ep_state;
- USBOutEndpointState out_ep_state;
- USBInEndpointState int_ep_state;
- USBEndpointConfig inout_ep_config;
- USBEndpointConfig int_ep_config;
- const QMKUSBConfig config;
- QMKUSBDriver driver;
-} usb_driver_config_t;
-#else
-typedef struct {
- size_t queue_capacity_in;
- size_t queue_capacity_out;
- USBInEndpointState in_ep_state;
- USBOutEndpointState out_ep_state;
- USBInEndpointState int_ep_state;
- USBEndpointConfig in_ep_config;
- USBEndpointConfig out_ep_config;
- USBEndpointConfig int_ep_config;
- const QMKUSBConfig config;
- QMKUSBDriver driver;
-} usb_driver_config_t;
-#endif
-
-#ifdef USB_ENDPOINTS_ARE_REORDERABLE
-/* Reusable initialization structure - see USBEndpointConfig comment at top of file */
-# define QMK_USB_DRIVER_CONFIG(stream, notification, fixedsize) \
- { \
- .queue_capacity_in = stream##_IN_CAPACITY, .queue_capacity_out = stream##_OUT_CAPACITY, \
- .inout_ep_config = \
- { \
- stream##_IN_MODE, /* Interrupt EP */ \
- NULL, /* SETUP packet notification callback */ \
- qmkusbDataTransmitted, /* IN notification callback */ \
- qmkusbDataReceived, /* OUT notification callback */ \
- stream##_EPSIZE, /* IN maximum packet size */ \
- stream##_EPSIZE, /* OUT maximum packet size */ \
- NULL, /* IN Endpoint state */ \
- NULL, /* OUT endpoint state */ \
- usb_lld_endpoint_fields /* USB driver specific endpoint fields */ \
- }, \
- .int_ep_config = \
- { \
- USB_EP_MODE_TYPE_INTR, /* Interrupt EP */ \
- NULL, /* SETUP packet notification callback */ \
- qmkusbInterruptTransmitted, /* IN notification callback */ \
- NULL, /* OUT notification callback */ \
- CDC_NOTIFICATION_EPSIZE, /* IN maximum packet size */ \
- 0, /* OUT maximum packet size */ \
- NULL, /* IN Endpoint state */ \
- NULL, /* OUT endpoint state */ \
- usb_lld_endpoint_fields /* USB driver specific endpoint fields */ \
- }, \
- .config = { \
- .usbp = &USB_DRIVER, \
- .bulk_in = stream##_IN_EPNUM, \
- .bulk_out = stream##_OUT_EPNUM, \
- .int_in = notification, \
- .in_buffers = stream##_IN_CAPACITY, \
- .out_buffers = stream##_OUT_CAPACITY, \
- .in_size = stream##_EPSIZE, \
- .out_size = stream##_EPSIZE, \
- .fixed_size = fixedsize, \
- .ib = (__attribute__((aligned(4))) uint8_t[BQ_BUFFER_SIZE(stream##_IN_CAPACITY, stream##_EPSIZE)]){}, \
- .ob = (__attribute__((aligned(4))) uint8_t[BQ_BUFFER_SIZE(stream##_OUT_CAPACITY, stream##_EPSIZE)]){}, \
- } \
- }
-#else
-/* Reusable initialization structure - see USBEndpointConfig comment at top of file */
-# define QMK_USB_DRIVER_CONFIG(stream, notification, fixedsize) \
- { \
- .queue_capacity_in = stream##_IN_CAPACITY, .queue_capacity_out = stream##_OUT_CAPACITY, \
- .in_ep_config = \
- { \
- stream##_IN_MODE, /* Interrupt EP */ \
- NULL, /* SETUP packet notification callback */ \
- qmkusbDataTransmitted, /* IN notification callback */ \
- NULL, /* OUT notification callback */ \
- stream##_EPSIZE, /* IN maximum packet size */ \
- 0, /* OUT maximum packet size */ \
- NULL, /* IN Endpoint state */ \
- NULL, /* OUT endpoint state */ \
- usb_lld_endpoint_fields /* USB driver specific endpoint fields */ \
- }, \
- .out_ep_config = \
- { \
- stream##_OUT_MODE, /* Interrupt EP */ \
- NULL, /* SETUP packet notification callback */ \
- NULL, /* IN notification callback */ \
- qmkusbDataReceived, /* OUT notification callback */ \
- 0, /* IN maximum packet size */ \
- stream##_EPSIZE, /* OUT maximum packet size */ \
- NULL, /* IN Endpoint state */ \
- NULL, /* OUT endpoint state */ \
- usb_lld_endpoint_fields /* USB driver specific endpoint fields */ \
- }, \
- .int_ep_config = \
- { \
- USB_EP_MODE_TYPE_INTR, /* Interrupt EP */ \
- NULL, /* SETUP packet notification callback */ \
- qmkusbInterruptTransmitted, /* IN notification callback */ \
- NULL, /* OUT notification callback */ \
- CDC_NOTIFICATION_EPSIZE, /* IN maximum packet size */ \
- 0, /* OUT maximum packet size */ \
- NULL, /* IN Endpoint state */ \
- NULL, /* OUT endpoint state */ \
- usb_lld_endpoint_fields /* USB driver specific endpoint fields */ \
- }, \
- .config = { \
- .usbp = &USB_DRIVER, \
- .bulk_in = stream##_IN_EPNUM, \
- .bulk_out = stream##_OUT_EPNUM, \
- .int_in = notification, \
- .in_buffers = stream##_IN_CAPACITY, \
- .out_buffers = stream##_OUT_CAPACITY, \
- .in_size = stream##_EPSIZE, \
- .out_size = stream##_EPSIZE, \
- .fixed_size = fixedsize, \
- .ib = (__attribute__((aligned(4))) uint8_t[BQ_BUFFER_SIZE(stream##_IN_CAPACITY, stream##_EPSIZE)]){}, \
- .ob = (__attribute__((aligned(4))) uint8_t[BQ_BUFFER_SIZE(stream##_OUT_CAPACITY, stream##_EPSIZE)]){}, \
- } \
- }
-#endif
-
-typedef struct {
- union {
- struct {
-#ifdef RAW_ENABLE
- usb_driver_config_t raw_driver;
-#endif
-#ifdef MIDI_ENABLE
- usb_driver_config_t midi_driver;
-#endif
-#ifdef VIRTSER_ENABLE
- usb_driver_config_t serial_driver;
-#endif
- };
- usb_driver_config_t array[0];
- };
-} usb_driver_configs_t;
-
-static usb_driver_configs_t drivers = {
-#ifdef RAW_ENABLE
-# ifndef RAW_IN_CAPACITY
-# define RAW_IN_CAPACITY 4
-# endif
-# ifndef RAW_OUT_CAPACITY
-# define RAW_OUT_CAPACITY 4
-# endif
-# define RAW_IN_MODE USB_EP_MODE_TYPE_INTR
-# define RAW_OUT_MODE USB_EP_MODE_TYPE_INTR
- .raw_driver = QMK_USB_DRIVER_CONFIG(RAW, 0, false),
-#endif
-
-#ifdef MIDI_ENABLE
-# define MIDI_STREAM_IN_CAPACITY 4
-# define MIDI_STREAM_OUT_CAPACITY 4
-# define MIDI_STREAM_IN_MODE USB_EP_MODE_TYPE_BULK
-# define MIDI_STREAM_OUT_MODE USB_EP_MODE_TYPE_BULK
- .midi_driver = QMK_USB_DRIVER_CONFIG(MIDI_STREAM, 0, false),
-#endif
-
-#ifdef VIRTSER_ENABLE
-# define CDC_IN_CAPACITY 4
-# define CDC_OUT_CAPACITY 4
-# define CDC_IN_MODE USB_EP_MODE_TYPE_BULK
-# define CDC_OUT_MODE USB_EP_MODE_TYPE_BULK
- .serial_driver = QMK_USB_DRIVER_CONFIG(CDC, CDC_NOTIFICATION_EPNUM, false),
-#endif
-};
-
-#define NUM_USB_DRIVERS (sizeof(drivers) / sizeof(usb_driver_config_t))
-
/* ---------------------------------------------------------
* USB driver functions
* ---------------------------------------------------------
@@ -507,36 +184,11 @@ static void usb_event_cb(USBDriver *usbp, usbevent_t event) {
case USB_EVENT_CONFIGURED:
osalSysLockFromISR();
- /* Enable the endpoints specified into the configuration. */
-#ifndef KEYBOARD_SHARED_EP
- usbInitEndpointI(usbp, KEYBOARD_IN_EPNUM, &kbd_ep_config);
-#endif
-#if defined(MOUSE_ENABLE) && !defined(MOUSE_SHARED_EP)
- usbInitEndpointI(usbp, MOUSE_IN_EPNUM, &mouse_ep_config);
-#endif
-#ifdef SHARED_EP_ENABLE
- usbInitEndpointI(usbp, SHARED_IN_EPNUM, &shared_ep_config);
-#endif
-#if defined(JOYSTICK_ENABLE) && !defined(JOYSTICK_SHARED_EP)
- usbInitEndpointI(usbp, JOYSTICK_IN_EPNUM, &joystick_ep_config);
-#endif
-#if defined(DIGITIZER_ENABLE) && !defined(DIGITIZER_SHARED_EP)
- usbInitEndpointI(usbp, DIGITIZER_IN_EPNUM, &digitizer_ep_config);
-#endif
-#ifdef CONSOLE_ENABLE
- usbInitEndpointI(usbp, CONSOLE_IN_EPNUM, &console_ep_config);
-#endif
- for (int i = 0; i < NUM_USB_DRIVERS; i++) {
-#ifdef USB_ENDPOINTS_ARE_REORDERABLE
- usbInitEndpointI(usbp, drivers.array[i].config.bulk_in, &drivers.array[i].inout_ep_config);
-#else
- usbInitEndpointI(usbp, drivers.array[i].config.bulk_in, &drivers.array[i].in_ep_config);
- usbInitEndpointI(usbp, drivers.array[i].config.bulk_out, &drivers.array[i].out_ep_config);
-#endif
- if (drivers.array[i].config.int_in) {
- usbInitEndpointI(usbp, drivers.array[i].config.int_in, &drivers.array[i].int_ep_config);
- }
- qmkusbConfigureHookI(&drivers.array[i].driver);
+ for (int i = 0; i < USB_ENDPOINT_IN_COUNT; i++) {
+ usb_endpoint_in_configure_cb(&usb_endpoints_in[i]);
+ }
+ for (int i = 0; i < USB_ENDPOINT_OUT_COUNT; i++) {
+ usb_endpoint_out_configure_cb(&usb_endpoints_out[i]);
}
osalSysUnlockFromISR();
if (last_suspend_state) {
@@ -550,22 +202,25 @@ static void usb_event_cb(USBDriver *usbp, usbevent_t event) {
/* Falls into.*/
case USB_EVENT_RESET:
usb_event_queue_enqueue(event);
- for (int i = 0; i < NUM_USB_DRIVERS; i++) {
- chSysLockFromISR();
- /* Disconnection event on suspend.*/
- qmkusbSuspendHookI(&drivers.array[i].driver);
- chSysUnlockFromISR();
+ chSysLockFromISR();
+ for (int i = 0; i < USB_ENDPOINT_IN_COUNT; i++) {
+ usb_endpoint_in_suspend_cb(&usb_endpoints_in[i]);
}
+ for (int i = 0; i < USB_ENDPOINT_OUT_COUNT; i++) {
+ usb_endpoint_out_suspend_cb(&usb_endpoints_out[i]);
+ }
+ chSysUnlockFromISR();
return;
case USB_EVENT_WAKEUP:
- // TODO: from ISR! print("[W]");
- for (int i = 0; i < NUM_USB_DRIVERS; i++) {
- chSysLockFromISR();
- /* Disconnection event on suspend.*/
- qmkusbWakeupHookI(&drivers.array[i].driver);
- chSysUnlockFromISR();
+ chSysLockFromISR();
+ for (int i = 0; i < USB_ENDPOINT_IN_COUNT; i++) {
+ usb_endpoint_in_wakeup_cb(&usb_endpoints_in[i]);
+ }
+ for (int i = 0; i < USB_ENDPOINT_OUT_COUNT; i++) {
+ usb_endpoint_out_wakeup_cb(&usb_endpoints_out[i]);
}
+ chSysUnlockFromISR();
usb_event_queue_enqueue(USB_EVENT_WAKEUP);
return;
@@ -587,7 +242,7 @@ static void usb_event_cb(USBDriver *usbp, usbevent_t event) {
* Other Device Required Optional Optional Optional Optional Optional
*/
-static uint8_t set_report_buf[2] __attribute__((aligned(4)));
+static uint8_t _Alignas(4) set_report_buf[2];
static void set_led_transfer_cb(USBDriver *usbp) {
usb_control_request_t *setup = (usb_control_request_t *)usbp->setup;
@@ -611,41 +266,7 @@ static bool usb_requests_hook_cb(USBDriver *usbp) {
case USB_RTYPE_DIR_DEV2HOST:
switch (setup->bRequest) {
case HID_REQ_GetReport:
- switch (setup->wIndex) {
-#ifndef KEYBOARD_SHARED_EP
- case KEYBOARD_INTERFACE:
- usbSetupTransfer(usbp, (uint8_t *)&keyboard_report_sent, KEYBOARD_REPORT_SIZE, NULL);
- return TRUE;
- break;
-#endif
-#if defined(MOUSE_ENABLE) && !defined(MOUSE_SHARED_EP)
- case MOUSE_INTERFACE:
- usbSetupTransfer(usbp, (uint8_t *)&mouse_report_sent, sizeof(mouse_report_sent), NULL);
- return TRUE;
- break;
-#endif
-#ifdef SHARED_EP_ENABLE
- case SHARED_INTERFACE:
-# ifdef KEYBOARD_SHARED_EP
- if (setup->wValue.lbyte == REPORT_ID_KEYBOARD) {
- usbSetupTransfer(usbp, (uint8_t *)&keyboard_report_sent, KEYBOARD_REPORT_SIZE, NULL);
- return true;
- }
-# endif
-# ifdef MOUSE_SHARED_EP
- if (setup->wValue.lbyte == REPORT_ID_MOUSE) {
- usbSetupTransfer(usbp, (uint8_t *)&mouse_report_sent, sizeof(mouse_report_sent), NULL);
- return true;
- }
-# endif
-#endif /* SHARED_EP_ENABLE */
- default:
- universal_report_blank.report_id = setup->wValue.lbyte;
- usbSetupTransfer(usbp, (uint8_t *)&universal_report_blank, setup->wLength, NULL);
- return true;
- }
- break;
-
+ return usb_get_report_cb(usbp);
case HID_REQ_GetProtocol:
if (setup->wIndex == KEYBOARD_INTERFACE) {
usbSetupTransfer(usbp, &keyboard_protocol, sizeof(uint8_t), NULL);
@@ -654,10 +275,8 @@ static bool usb_requests_hook_cb(USBDriver *usbp) {
break;
case HID_REQ_GetIdle:
- usbSetupTransfer(usbp, &keyboard_idle, sizeof(uint8_t), NULL);
- return true;
+ return usb_get_idle_cb(usbp);
}
- break;
case USB_RTYPE_DIR_HOST2DEV:
switch (setup->bRequest) {
@@ -671,38 +290,15 @@ static bool usb_requests_hook_cb(USBDriver *usbp) {
return true;
}
break;
-
case HID_REQ_SetProtocol:
if (setup->wIndex == KEYBOARD_INTERFACE) {
keyboard_protocol = setup->wValue.word;
-#ifdef NKRO_ENABLE
- if (!keyboard_protocol && keyboard_idle) {
-#else /* NKRO_ENABLE */
- if (keyboard_idle) {
-#endif /* NKRO_ENABLE */
- /* arm the idle timer if boot protocol & idle */
- osalSysLockFromISR();
- chVTSetI(&keyboard_idle_timer, 4 * TIME_MS2I(keyboard_idle), keyboard_idle_timer_cb, (void *)usbp);
- osalSysUnlockFromISR();
- }
}
usbSetupTransfer(usbp, NULL, 0, NULL);
return true;
-
case HID_REQ_SetIdle:
keyboard_idle = setup->wValue.hbyte;
- /* arm the timer */
-#ifdef NKRO_ENABLE
- if (!keymap_config.nkro && keyboard_idle) {
-#else /* NKRO_ENABLE */
- if (keyboard_idle) {
-#endif /* NKRO_ENABLE */
- osalSysLockFromISR();
- chVTSetI(&keyboard_idle_timer, 4 * TIME_MS2I(keyboard_idle), keyboard_idle_timer_cb, (void *)usbp);
- osalSysUnlockFromISR();
- }
- usbSetupTransfer(usbp, NULL, 0, NULL);
- return true;
+ return usb_set_idle_cb(usbp);
}
break;
}
@@ -719,52 +315,40 @@ static bool usb_requests_hook_cb(USBDriver *usbp) {
return true;
}
- for (int i = 0; i < NUM_USB_DRIVERS; i++) {
- if (drivers.array[i].config.int_in) {
- // NOTE: Assumes that we only have one serial driver
- return qmkusbRequestsHook(usbp);
+ for (int i = 0; i < USB_ENDPOINT_IN_COUNT; i++) {
+ if (usb_endpoints_in[i].usb_requests_cb != NULL) {
+ if (usb_endpoints_in[i].usb_requests_cb(usbp)) {
+ return true;
+ }
}
}
return false;
}
-static void usb_sof_cb(USBDriver *usbp) {
- osalSysLockFromISR();
- for (int i = 0; i < NUM_USB_DRIVERS; i++) {
- qmkusbSOFHookI(&drivers.array[i].driver);
- }
- osalSysUnlockFromISR();
+static __attribute__((unused)) void dummy_cb(USBDriver *usbp) {
+ (void)usbp;
}
-/* USB driver configuration */
static const USBConfig usbcfg = {
usb_event_cb, /* USB events callback */
usb_get_descriptor_cb, /* Device GET_DESCRIPTOR request callback */
usb_requests_hook_cb, /* Requests hook callback */
- usb_sof_cb /* Start Of Frame callback */
+#if STM32_USB_USE_OTG1 == TRUE || STM32_USB_USE_OTG2 == TRUE
+ dummy_cb, /* Workaround for OTG Peripherals not servicing new interrupts
+ after resuming from suspend. */
+#endif
};
-/*
- * Initialize the USB driver
- */
void init_usb_driver(USBDriver *usbp) {
- for (int i = 0; i < NUM_USB_DRIVERS; i++) {
-#ifdef USB_ENDPOINTS_ARE_REORDERABLE
- QMKUSBDriver *driver = &drivers.array[i].driver;
- drivers.array[i].inout_ep_config.in_state = &drivers.array[i].in_ep_state;
- drivers.array[i].inout_ep_config.out_state = &drivers.array[i].out_ep_state;
- drivers.array[i].int_ep_config.in_state = &drivers.array[i].int_ep_state;
- qmkusbObjectInit(driver, &drivers.array[i].config);
- qmkusbStart(driver, &drivers.array[i].config);
-#else
- QMKUSBDriver *driver = &drivers.array[i].driver;
- drivers.array[i].in_ep_config.in_state = &drivers.array[i].in_ep_state;
- drivers.array[i].out_ep_config.out_state = &drivers.array[i].out_ep_state;
- drivers.array[i].int_ep_config.in_state = &drivers.array[i].int_ep_state;
- qmkusbObjectInit(driver, &drivers.array[i].config);
- qmkusbStart(driver, &drivers.array[i].config);
-#endif
+ for (int i = 0; i < USB_ENDPOINT_IN_COUNT; i++) {
+ usb_endpoint_in_init(&usb_endpoints_in[i]);
+ usb_endpoint_in_start(&usb_endpoints_in[i]);
+ }
+
+ for (int i = 0; i < USB_ENDPOINT_OUT_COUNT; i++) {
+ usb_endpoint_out_init(&usb_endpoints_out[i]);
+ usb_endpoint_out_start(&usb_endpoints_out[i]);
}
/*
@@ -777,23 +361,31 @@ void init_usb_driver(USBDriver *usbp) {
wait_ms(50);
usbStart(usbp, &usbcfg);
usbConnectBus(usbp);
-
- chVTObjectInit(&keyboard_idle_timer);
}
__attribute__((weak)) void restart_usb_driver(USBDriver *usbp) {
usbDisconnectBus(usbp);
usbStop(usbp);
-#if USB_SUSPEND_WAKEUP_DELAY > 0
- // Some hubs, kvm switches, and monitors do
- // weird things, with USB device state bouncing
- // around wildly on wakeup, yielding race
- // conditions that can corrupt the keyboard state.
- //
- // Pause for a while to let things settle...
- wait_ms(USB_SUSPEND_WAKEUP_DELAY);
-#endif
+ for (int i = 0; i < USB_ENDPOINT_IN_COUNT; i++) {
+ usb_endpoint_in_stop(&usb_endpoints_in[i]);
+ }
+
+ for (int i = 0; i < USB_ENDPOINT_OUT_COUNT; i++) {
+ usb_endpoint_out_stop(&usb_endpoints_out[i]);
+ }
+
+ wait_ms(50);
+
+ for (int i = 0; i < USB_ENDPOINT_IN_COUNT; i++) {
+ usb_endpoint_in_init(&usb_endpoints_in[i]);
+ usb_endpoint_in_start(&usb_endpoints_in[i]);
+ }
+
+ for (int i = 0; i < USB_ENDPOINT_OUT_COUNT; i++) {
+ usb_endpoint_out_init(&usb_endpoints_out[i]);
+ usb_endpoint_out_start(&usb_endpoints_out[i]);
+ }
usbStart(usbp, &usbcfg);
usbConnectBus(usbp);
@@ -804,81 +396,78 @@ __attribute__((weak)) void restart_usb_driver(USBDriver *usbp) {
* ---------------------------------------------------------
*/
-/* Idle requests timer code
- * callback (called from ISR, unlocked state) */
-static void keyboard_idle_timer_cb(struct ch_virtual_timer *timer, void *arg) {
- (void)timer;
- USBDriver *usbp = (USBDriver *)arg;
-
- osalSysLockFromISR();
-
- /* check that the states of things are as they're supposed to */
- if (usbGetDriverStateI(usbp) != USB_ACTIVE) {
- /* do not rearm the timer, should be enabled on IDLE request */
- osalSysUnlockFromISR();
- return;
- }
-
-#ifdef NKRO_ENABLE
- if (!keymap_config.nkro && keyboard_idle && keyboard_protocol) {
-#else /* NKRO_ENABLE */
- if (keyboard_idle && keyboard_protocol) {
-#endif /* NKRO_ENABLE */
- /* TODO: are we sure we want the KBD_ENDPOINT? */
- if (!usbGetTransmitStatusI(usbp, KEYBOARD_IN_EPNUM)) {
- usbStartTransmitI(usbp, KEYBOARD_IN_EPNUM, (uint8_t *)&keyboard_report_sent, KEYBOARD_EPSIZE);
- }
- /* rearm the timer */
- chVTSetI(&keyboard_idle_timer, 4 * TIME_MS2I(keyboard_idle), keyboard_idle_timer_cb, (void *)usbp);
- }
-
- /* do not rearm the timer if the condition above fails
- * it should be enabled again on either IDLE or SET_PROTOCOL requests */
- osalSysUnlockFromISR();
-}
-
/* LED status */
uint8_t keyboard_leds(void) {
return keyboard_led_state;
}
-void send_report(uint8_t endpoint, void *report, size_t size) {
- osalSysLock();
- if (usbGetDriverStateI(&USB_DRIVER) != USB_ACTIVE) {
- osalSysUnlock();
- return;
- }
+/**
+ * @brief Send a report to the host, the report is enqueued into an output
+ * queue and send once the USB endpoint becomes empty.
+ *
+ * @param endpoint USB IN endpoint to send the report from
+ * @param report pointer to the report
+ * @param size size of the report
+ * @return true Success
+ * @return false Failure
+ */
+bool send_report(usb_endpoint_in_lut_t endpoint, void *report, size_t size) {
+ return usb_endpoint_in_send(&usb_endpoints_in[endpoint], (uint8_t *)report, size, TIME_MS2I(100), false);
+}
- if (usbGetTransmitStatusI(&USB_DRIVER, endpoint)) {
- /* Need to either suspend, or loop and call unlock/lock during
- * every iteration - otherwise the system will remain locked,
- * no interrupts served, so USB not going through as well.
- * Note: for suspend, need USB_USE_WAIT == TRUE in halconf.h */
- if (osalThreadSuspendTimeoutS(&(&USB_DRIVER)->epc[endpoint]->in_state->thread, TIME_MS2I(10)) == MSG_TIMEOUT) {
- osalSysUnlock();
- return;
- }
- }
- usbStartTransmitI(&USB_DRIVER, endpoint, report, size);
- osalSysUnlock();
+/**
+ * @brief Send a report to the host, but delay the sending until the size of
+ * endpoint report is reached or the incompletely filled buffer is flushed with
+ * a call to `flush_report_buffered`. This is useful if the report is being
+ * updated frequently. The complete report is then enqueued into an output
+ * queue and send once the USB endpoint becomes empty.
+ *
+ * @param endpoint USB IN endpoint to send the report from
+ * @param report pointer to the report
+ * @param size size of the report
+ * @return true Success
+ * @return false Failure
+ */
+static bool send_report_buffered(usb_endpoint_in_lut_t endpoint, void *report, size_t size) {
+ return usb_endpoint_in_send(&usb_endpoints_in[endpoint], (uint8_t *)report, size, TIME_MS2I(100), true);
+}
+
+/** @brief Flush all buffered reports which were enqueued with a call to
+ * `send_report_buffered` that haven't been send. If necessary the buffered
+ * report can be padded with zeros up to the endpoints maximum size.
+ *
+ * @param endpoint USB IN endpoint to flush the reports from
+ * @param padded Pad the buffered report with zeros up to the endpoints maximum size
+ */
+static void flush_report_buffered(usb_endpoint_in_lut_t endpoint, bool padded) {
+ usb_endpoint_in_flush(&usb_endpoints_in[endpoint], padded);
+}
+
+/**
+ * @brief Receive a report from the host.
+ *
+ * @param endpoint USB OUT endpoint to receive the report from
+ * @param report pointer to the report
+ * @param size size of the report
+ * @return true Success
+ * @return false Failure
+ */
+static bool receive_report(usb_endpoint_out_lut_t endpoint, void *report, size_t size) {
+ return usb_endpoint_out_receive(&usb_endpoints_out[endpoint], (uint8_t *)report, size, TIME_IMMEDIATE);
}
-/* prepare and start sending a report IN
- * not callable from ISR or locked state */
void send_keyboard(report_keyboard_t *report) {
/* If we're in Boot Protocol, don't send any report ID or other funky fields */
if (!keyboard_protocol) {
- send_report(KEYBOARD_IN_EPNUM, &report->mods, 8);
+ send_report(USB_ENDPOINT_IN_KEYBOARD, &report->mods, 8);
} else {
- send_report(KEYBOARD_IN_EPNUM, report, KEYBOARD_REPORT_SIZE);
+ send_report(USB_ENDPOINT_IN_KEYBOARD, report, KEYBOARD_REPORT_SIZE);
}
-
- keyboard_report_sent = *report;
}
void send_nkro(report_nkro_t *report) {
#ifdef NKRO_ENABLE
- send_report(SHARED_IN_EPNUM, report, sizeof(report_nkro_t));
+ send_report(USB_ENDPOINT_IN_SHARED, report, sizeof(report_nkro_t));
#endif
}
@@ -889,8 +478,7 @@ void send_nkro(report_nkro_t *report) {
void send_mouse(report_mouse_t *report) {
#ifdef MOUSE_ENABLE
- send_report(MOUSE_IN_EPNUM, report, sizeof(report_mouse_t));
- mouse_report_sent = *report;
+ send_report(USB_ENDPOINT_IN_MOUSE, report, sizeof(report_mouse_t));
#endif
}
@@ -901,25 +489,25 @@ void send_mouse(report_mouse_t *report) {
void send_extra(report_extra_t *report) {
#ifdef EXTRAKEY_ENABLE
- send_report(SHARED_IN_EPNUM, report, sizeof(report_extra_t));
+ send_report(USB_ENDPOINT_IN_SHARED, report, sizeof(report_extra_t));
#endif
}
void send_programmable_button(report_programmable_button_t *report) {
#ifdef PROGRAMMABLE_BUTTON_ENABLE
- send_report(SHARED_IN_EPNUM, report, sizeof(report_programmable_button_t));
+ send_report(USB_ENDPOINT_IN_SHARED, report, sizeof(report_programmable_button_t));
#endif
}
void send_joystick(report_joystick_t *report) {
#ifdef JOYSTICK_ENABLE
- send_report(JOYSTICK_IN_EPNUM, report, sizeof(report_joystick_t));
+ send_report(USB_ENDPOINT_IN_JOYSTICK, report, sizeof(report_joystick_t));
#endif
}
void send_digitizer(report_digitizer_t *report) {
#ifdef DIGITIZER_ENABLE
- send_report(DIGITIZER_IN_EPNUM, report, sizeof(report_digitizer_t));
+ send_report(USB_ENDPOINT_IN_DIGITIZER, report, sizeof(report_digitizer_t));
#endif
}
@@ -931,46 +519,21 @@ void send_digitizer(report_digitizer_t *report) {
#ifdef CONSOLE_ENABLE
int8_t sendchar(uint8_t c) {
- rbuf_enqueue(c);
- return 0;
+ return (int8_t)send_report_buffered(USB_ENDPOINT_IN_CONSOLE, &c, sizeof(uint8_t));
}
void console_task(void) {
- if (!rbuf_has_data()) {
- return;
- }
-
- osalSysLock();
- if (usbGetDriverStateI(&USB_DRIVER) != USB_ACTIVE) {
- osalSysUnlock();
- return;
- }
-
- if (usbGetTransmitStatusI(&USB_DRIVER, CONSOLE_IN_EPNUM)) {
- osalSysUnlock();
- return;
- }
-
- // Send in chunks - padded with zeros to 32
- char send_buf[CONSOLE_EPSIZE] = {0};
- uint8_t send_buf_count = 0;
- while (rbuf_has_data() && send_buf_count < CONSOLE_EPSIZE) {
- send_buf[send_buf_count++] = rbuf_dequeue();
- }
-
- usbStartTransmitI(&USB_DRIVER, CONSOLE_IN_EPNUM, (const uint8_t *)send_buf, CONSOLE_EPSIZE);
- osalSysUnlock();
+ flush_report_buffered(USB_ENDPOINT_IN_CONSOLE, true);
}
#endif /* CONSOLE_ENABLE */
#ifdef RAW_ENABLE
void raw_hid_send(uint8_t *data, uint8_t length) {
- // TODO: implement variable size packet
if (length != RAW_EPSIZE) {
return;
}
- chnWrite(&drivers.raw_driver.driver, data, length);
+ send_report(USB_ENDPOINT_IN_RAW, data, length);
}
__attribute__((weak)) void raw_hid_receive(uint8_t *data, uint8_t length) {
@@ -981,13 +544,9 @@ __attribute__((weak)) void raw_hid_receive(uint8_t *data, uint8_t length) {
void raw_hid_task(void) {
uint8_t buffer[RAW_EPSIZE];
- size_t size = 0;
- do {
- size = chnReadTimeout(&drivers.raw_driver.driver, buffer, sizeof(buffer), TIME_IMMEDIATE);
- if (size > 0) {
- raw_hid_receive(buffer, size);
- }
- } while (size > 0);
+ while (receive_report(USB_ENDPOINT_OUT_RAW, buffer, sizeof(buffer))) {
+ raw_hid_receive(buffer, sizeof(buffer));
+ }
}
#endif
@@ -995,32 +554,59 @@ void raw_hid_task(void) {
#ifdef MIDI_ENABLE
void send_midi_packet(MIDI_EventPacket_t *event) {
- chnWrite(&drivers.midi_driver.driver, (uint8_t *)event, sizeof(MIDI_EventPacket_t));
+ send_report(USB_ENDPOINT_IN_MIDI, (uint8_t *)event, sizeof(MIDI_EventPacket_t));
}
bool recv_midi_packet(MIDI_EventPacket_t *const event) {
- size_t size = chnReadTimeout(&drivers.midi_driver.driver, (uint8_t *)event, sizeof(MIDI_EventPacket_t), TIME_IMMEDIATE);
- return size == sizeof(MIDI_EventPacket_t);
+ return receive_report(USB_ENDPOINT_OUT_MIDI, (uint8_t *)event, sizeof(MIDI_EventPacket_t));
}
+
void midi_ep_task(void) {
uint8_t buffer[MIDI_STREAM_EPSIZE];
- size_t size = 0;
- do {
- size = chnReadTimeout(&drivers.midi_driver.driver, buffer, sizeof(buffer), TIME_IMMEDIATE);
- if (size > 0) {
- MIDI_EventPacket_t event;
- recv_midi_packet(&event);
- }
- } while (size > 0);
+ while (receive_report(USB_ENDPOINT_OUT_MIDI, buffer, sizeof(buffer))) {
+ MIDI_EventPacket_t event;
+ // TODO: this seems totally wrong? The midi task will never see any
+ // packets if we consume them here
+ recv_midi_packet(&event);
+ }
}
#endif
#ifdef VIRTSER_ENABLE
+# include "hal_usb_cdc.h"
+/**
+ * @brief CDC serial driver configuration structure. Set to 9600 baud, 1 stop bit, no parity, 8 data bits.
+ */
+static cdc_linecoding_t linecoding = {{0x00, 0x96, 0x00, 0x00}, LC_STOP_1, LC_PARITY_NONE, 8};
+
+bool virtser_usb_request_cb(USBDriver *usbp) {
+ if ((usbp->setup[0] & USB_RTYPE_TYPE_MASK) == USB_RTYPE_TYPE_CLASS) { /* bmRequestType */
+ if (usbp->setup[4] == CCI_INTERFACE) { /* wIndex (LSB) */
+ switch (usbp->setup[1]) { /* bRequest */
+ case CDC_GET_LINE_CODING:
+ usbSetupTransfer(usbp, (uint8_t *)&linecoding, sizeof(linecoding), NULL);
+ return true;
+ case CDC_SET_LINE_CODING:
+ usbSetupTransfer(usbp, (uint8_t *)&linecoding, sizeof(linecoding), NULL);
+ return true;
+ case CDC_SET_CONTROL_LINE_STATE:
+ /* Nothing to do, there are no control lines.*/
+ usbSetupTransfer(usbp, NULL, 0, NULL);
+ return true;
+ default:
+ return false;
+ }
+ }
+ }
+
+ return false;
+}
+
void virtser_init(void) {}
void virtser_send(const uint8_t byte) {
- chnWrite(&drivers.serial_driver.driver, &byte, 1);
+ send_report_buffered(USB_ENDPOINT_IN_CDC_DATA, (void *)&byte, sizeof(byte));
}
__attribute__((weak)) void virtser_recv(uint8_t c) {
@@ -1028,14 +614,14 @@ __attribute__((weak)) void virtser_recv(uint8_t c) {
}
void virtser_task(void) {
- uint8_t numBytesReceived = 0;
- uint8_t buffer[16];
- do {
- numBytesReceived = chnReadTimeout(&drivers.serial_driver.driver, buffer, sizeof(buffer), TIME_IMMEDIATE);
- for (int i = 0; i < numBytesReceived; i++) {
+ uint8_t buffer[CDC_EPSIZE];
+ while (receive_report(USB_ENDPOINT_OUT_CDC_DATA, buffer, sizeof(buffer))) {
+ for (int i = 0; i < sizeof(buffer); i++) {
virtser_recv(buffer[i]);
}
- } while (numBytesReceived > 0);
+ }
+
+ flush_report_buffered(USB_ENDPOINT_IN_CDC_DATA, false);
}
#endif
diff --git a/tmk_core/protocol/chibios/usb_main.h b/tmk_core/protocol/chibios/usb_main.h
index 3fd1e84fe8..5ba6fb1961 100644
--- a/tmk_core/protocol/chibios/usb_main.h
+++ b/tmk_core/protocol/chibios/usb_main.h
@@ -1,25 +1,21 @@
-/*
- * (c) 2015 flabberast <[email protected]>
- *
- * Based on the following work:
- * - Guillaume Duc's raw hid example (MIT License)
- * https://github.com/guiduc/usb-hid-chibios-example
- * - PJRC Teensy examples (MIT License)
- * https://www.pjrc.com/teensy/usb_keyboard.html
- * - hasu's TMK keyboard code (GPL v2 and some code Modified BSD)
- * https://github.com/tmk/tmk_keyboard/
- * - ChibiOS demo code (Apache 2.0 License)
- * http://www.chibios.org
- *
- * Since some GPL'd code is used, this work is licensed under
- * GPL v2 or later.
- */
+// Copyright 2023 Stefan Kerkmann (@KarlK90)
+// Copyright 2020 Ryan (@fauxpark)
+// Copyright 2020 Joel Challis (@zvecr)
+// Copyright 2018 James Laird-Wah
+// Copyright 2016 Fredizzimo
+// Copyright 2016 Giovanni Di Sirio
+// SPDX-License-Identifier: GPL-3.0-or-later OR Apache-2.0
#pragma once
#include <ch.h>
#include <hal.h>
+#include "usb_device_state.h"
+#include "usb_descriptor.h"
+#include "usb_driver.h"
+#include "usb_endpoints.h"
+
/* -------------------------
* General USB driver header
* -------------------------
@@ -36,6 +32,8 @@ void init_usb_driver(USBDriver *usbp);
/* Restart the USB driver and bus */
void restart_usb_driver(USBDriver *usbp);
+bool send_report(usb_endpoint_in_lut_t endpoint, void *report, size_t size);
+
/* ---------------
* USB Event queue
* ---------------
@@ -58,3 +56,14 @@ void usb_event_queue_task(void);
int8_t sendchar(uint8_t c);
#endif /* CONSOLE_ENABLE */
+
+/* --------------
+ * Virtser header
+ * --------------
+ */
+
+#if defined(VIRTSER_ENABLE)
+
+bool virtser_usb_request_cb(USBDriver *usbp);
+
+#endif
diff --git a/tmk_core/protocol/chibios/usb_report_handling.c b/tmk_core/protocol/chibios/usb_report_handling.c
new file mode 100644
index 0000000000..64074b2164
--- /dev/null
+++ b/tmk_core/protocol/chibios/usb_report_handling.c
@@ -0,0 +1,296 @@
+// Copyright 2023 Stefan Kerkmann (@KarlK90)
+// SPDX-License-Identifier: GPL-3.0-or-later
+
+#include <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;
+}
diff --git a/tmk_core/protocol/chibios/usb_report_handling.h b/tmk_core/protocol/chibios/usb_report_handling.h
new file mode 100644
index 0000000000..18b4ce5e26
--- /dev/null
+++ b/tmk_core/protocol/chibios/usb_report_handling.h
@@ -0,0 +1,77 @@
+// Copyright 2023 Stefan Kerkmann (@KarlK90)
+// SPDX-License-Identifier: GPL-3.0-or-later
+
+#pragma once
+
+#include <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);
diff --git a/tmk_core/protocol/report.h b/tmk_core/protocol/report.h
index 47bc4f2f2b..0e4f6e9def 100644
--- a/tmk_core/protocol/report.h
+++ b/tmk_core/protocol/report.h
@@ -29,7 +29,8 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
// clang-format off
/* HID report IDs */
-enum hid_report_ids {
+enum hid_report_ids {
+ REPORT_ID_ALL = 0,
REPORT_ID_KEYBOARD = 1,
REPORT_ID_MOUSE,
REPORT_ID_SYSTEM,
@@ -37,9 +38,12 @@ enum hid_report_ids {
REPORT_ID_PROGRAMMABLE_BUTTON,
REPORT_ID_NKRO,
REPORT_ID_JOYSTICK,
- REPORT_ID_DIGITIZER
+ REPORT_ID_DIGITIZER,
+ REPORT_ID_COUNT = REPORT_ID_DIGITIZER
};
+#define IS_VALID_REPORT_ID(id) ((id) >= REPORT_ID_ALL && (id) <= REPORT_ID_COUNT)
+
/* Mouse buttons */
#define MOUSE_BTN_MASK(n) (1 << (n))
enum mouse_buttons {
diff --git a/tmk_core/protocol/usb_descriptor.h b/tmk_core/protocol/usb_descriptor.h
index 2469990f4d..ecfb022702 100644
--- a/tmk_core/protocol/usb_descriptor.h
+++ b/tmk_core/protocol/usb_descriptor.h
@@ -196,6 +196,8 @@ enum usb_interfaces {
TOTAL_INTERFACES
};
+#define IS_VALID_INTERFACE(i) ((i) >= 0 && (i) < TOTAL_INTERFACES)
+
#define NEXT_EPNUM __COUNTER__
/*