[Core] Refactor ChibiOS USB endpoints to be fully async (#21656)

master
Stefan Kerkmann 2024-02-28 12:00:27 +01:00 committed by GitHub
parent b43f6cb7ef
commit 0e02b0c41e
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
12 changed files with 1311 additions and 1134 deletions

View File

@ -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();
}

View File

@ -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)

View File

@ -1,127 +1,51 @@
/*
ChibiOS - Copyright (C) 2006..2016 Giovanni Di Sirio
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
/**
* @file hal_serial_usb.c
* @brief Serial over USB Driver code.
*
* @addtogroup SERIAL_USB
* @{
*/
// Copyright 2023 Stefan Kerkmann (@KarlK90)
// Copyright 2021 Purdea Andrei
// Copyright 2021 Michael Stapelberg
// Copyright 2020 Ryan (@fauxpark)
// Copyright 2016 Fredizzimo
// Copyright 2016 Giovanni Di Sirio
// SPDX-License-Identifier: GPL-3.0-or-later OR Apache-2.0
#include <hal.h>
#include "usb_driver.h"
#include <string.h>
/*===========================================================================*/
/* Driver local definitions. */
/*===========================================================================*/
/*===========================================================================*/
/* Driver exported variables. */
/*===========================================================================*/
/*===========================================================================*/
/* Driver local variables and types. */
/*===========================================================================*/
/*
* Current Line Coding.
*/
static cdc_linecoding_t linecoding = {{0x00, 0x96, 0x00, 0x00}, /* 38400. */
LC_STOP_1,
LC_PARITY_NONE,
8};
#include "usb_driver.h"
#include "util.h"
/*===========================================================================*/
/* Driver local functions. */
/*===========================================================================*/
static bool qmkusb_start_receive(QMKUSBDriver *qmkusbp) {
uint8_t *buf;
static void usb_start_receive(usb_endpoint_out_t *endpoint) {
/* If the USB driver is not in the appropriate state then transactions
must not be started.*/
if ((usbGetDriverStateI(qmkusbp->config->usbp) != USB_ACTIVE) || (qmkusbp->state != QMKUSB_READY)) {
return true;
if ((usbGetDriverStateI(endpoint->config.usbp) != USB_ACTIVE)) {
return;
}
/* Checking if there is already a transaction ongoing on the endpoint.*/
if (usbGetReceiveStatusI(qmkusbp->config->usbp, qmkusbp->config->bulk_out)) {
return true;
if (usbGetReceiveStatusI(endpoint->config.usbp, endpoint->config.ep)) {
return;
}
/* Checking if there is a buffer ready for incoming data.*/
buf = ibqGetEmptyBufferI(&qmkusbp->ibqueue);
if (buf == NULL) {
return true;
uint8_t *buffer = ibqGetEmptyBufferI(&endpoint->ibqueue);
if (buffer == NULL) {
return;
}
/* Buffer found, starting a new transaction.*/
usbStartReceiveI(qmkusbp->config->usbp, qmkusbp->config->bulk_out, buf, qmkusbp->ibqueue.bsize - sizeof(size_t));
return false;
usbStartReceiveI(endpoint->config.usbp, endpoint->config.ep, buffer, endpoint->ibqueue.bsize - sizeof(size_t));
}
/*
* Interface implementation.
*/
static size_t _write(void *ip, const uint8_t *bp, size_t n) {
return obqWriteTimeout(&((QMKUSBDriver *)ip)->obqueue, bp, n, TIME_INFINITE);
}
static size_t _read(void *ip, uint8_t *bp, size_t n) {
return ibqReadTimeout(&((QMKUSBDriver *)ip)->ibqueue, bp, n, TIME_INFINITE);
}
static msg_t _put(void *ip, uint8_t b) {
return obqPutTimeout(&((QMKUSBDriver *)ip)->obqueue, b, TIME_INFINITE);
}
static msg_t _get(void *ip) {
return ibqGetTimeout(&((QMKUSBDriver *)ip)->ibqueue, TIME_INFINITE);
}
static msg_t _putt(void *ip, uint8_t b, sysinterval_t timeout) {
return obqPutTimeout(&((QMKUSBDriver *)ip)->obqueue, b, timeout);
}
static msg_t _gett(void *ip, sysinterval_t timeout) {
return ibqGetTimeout(&((QMKUSBDriver *)ip)->ibqueue, timeout);
}
static size_t _writet(void *ip, const uint8_t *bp, size_t n, sysinterval_t timeout) {
return obqWriteTimeout(&((QMKUSBDriver *)ip)->obqueue, bp, n, timeout);
}
static size_t _readt(void *ip, uint8_t *bp, size_t n, sysinterval_t timeout) {
return ibqReadTimeout(&((QMKUSBDriver *)ip)->ibqueue, bp, n, timeout);
}
static const struct QMKUSBDriverVMT vmt = {0, _write, _read, _put, _get, _putt, _gett, _writet, _readt};
/**
* @brief Notification of empty buffer released into the input buffers queue.
*
* @param[in] bqp the buffers queue pointer.
*/
static void ibnotify(io_buffers_queue_t *bqp) {
QMKUSBDriver *qmkusbp = bqGetLinkX(bqp);
(void)qmkusb_start_receive(qmkusbp);
usb_endpoint_out_t *endpoint = bqGetLinkX(bqp);
usb_start_receive(endpoint);
}
/**
@ -130,22 +54,22 @@ static void ibnotify(io_buffers_queue_t *bqp) {
* @param[in] bqp the buffers queue pointer.
*/
static void obnotify(io_buffers_queue_t *bqp) {
size_t n;
QMKUSBDriver *qmkusbp = bqGetLinkX(bqp);
usb_endpoint_in_t *endpoint = bqGetLinkX(bqp);
/* If the USB driver is not in the appropriate state then transactions
/* If the USB endpoint is not in the appropriate state then transactions
must not be started.*/
if ((usbGetDriverStateI(qmkusbp->config->usbp) != USB_ACTIVE) || (qmkusbp->state != QMKUSB_READY)) {
if ((usbGetDriverStateI(endpoint->config.usbp) != USB_ACTIVE)) {
return;
}
/* Checking if there is already a transaction ongoing on the endpoint.*/
if (!usbGetTransmitStatusI(qmkusbp->config->usbp, qmkusbp->config->bulk_in)) {
if (!usbGetTransmitStatusI(endpoint->config.usbp, endpoint->config.ep)) {
/* Trying to get a full buffer.*/
uint8_t *buf = obqGetFullBufferI(&qmkusbp->obqueue, &n);
if (buf != NULL) {
size_t n;
uint8_t *buffer = obqGetFullBufferI(&endpoint->obqueue, &n);
if (buffer != NULL) {
/* Buffer found, starting a new transaction.*/
usbStartTransmitI(qmkusbp->config->usbp, qmkusbp->config->bulk_in, buf, n);
usbStartTransmitI(endpoint->config.usbp, endpoint->config.ep, buffer, n);
}
}
}
@ -154,264 +78,149 @@ static void obnotify(io_buffers_queue_t *bqp) {
/* Driver exported functions. */
/*===========================================================================*/
/**
* @brief Serial Driver initialization.
* @note This function is implicitly invoked by @p halInit(), there is
* no need to explicitly initialize the driver.
*
* @init
*/
void qmkusbInit(void) {}
void usb_endpoint_in_init(usb_endpoint_in_t *endpoint) {
usb_endpoint_config_t *config = &endpoint->config;
endpoint->ep_config.in_state = &endpoint->ep_in_state;
/**
* @brief Initializes a generic full duplex driver object.
* @details The HW dependent part of the initialization has to be performed
* outside, usually in the hardware initialization code.
*
* @param[out] qmkusbp pointer to a @p QMKUSBDriver structure
*
* @init
*/
void qmkusbObjectInit(QMKUSBDriver *qmkusbp, const QMKUSBConfig *config) {
qmkusbp->vmt = &vmt;
osalEventObjectInit(&qmkusbp->event);
qmkusbp->state = QMKUSB_STOP;
// Note that the config uses the USB direction naming
ibqObjectInit(&qmkusbp->ibqueue, true, config->ob, config->out_size, config->out_buffers, ibnotify, qmkusbp);
obqObjectInit(&qmkusbp->obqueue, true, config->ib, config->in_size, config->in_buffers, obnotify, qmkusbp);
#if defined(USB_ENDPOINTS_ARE_REORDERABLE)
if (endpoint->is_shared) {
endpoint->ep_config.out_state = &endpoint->ep_out_state;
}
#endif
obqObjectInit(&endpoint->obqueue, true, config->buffer, config->buffer_size, config->buffer_capacity, obnotify, endpoint);
}
/**
* @brief Configures and starts the driver.
*
* @param[in] qmkusbp pointer to a @p QMKUSBDriver object
* @param[in] config the serial over USB driver configuration
*
* @api
*/
void qmkusbStart(QMKUSBDriver *qmkusbp, const QMKUSBConfig *config) {
USBDriver *usbp = config->usbp;
void usb_endpoint_out_init(usb_endpoint_out_t *endpoint) {
usb_endpoint_config_t *config = &endpoint->config;
endpoint->ep_config.out_state = &endpoint->ep_out_state;
ibqObjectInit(&endpoint->ibqueue, true, config->buffer, config->buffer_size, config->buffer_capacity, ibnotify, endpoint);
}
osalDbgCheck(qmkusbp != NULL);
void usb_endpoint_in_start(usb_endpoint_in_t *endpoint) {
osalDbgCheck(endpoint != NULL);
osalSysLock();
osalDbgAssert((qmkusbp->state == QMKUSB_STOP) || (qmkusbp->state == QMKUSB_READY), "invalid state");
usbp->in_params[config->bulk_in - 1U] = qmkusbp;
usbp->out_params[config->bulk_out - 1U] = qmkusbp;
if (config->int_in > 0U) {
usbp->in_params[config->int_in - 1U] = qmkusbp;
}
qmkusbp->config = config;
qmkusbp->state = QMKUSB_READY;
osalDbgAssert((usbGetDriverStateI(endpoint->config.usbp) == USB_STOP) || (usbGetDriverStateI(endpoint->config.usbp) == USB_READY), "invalid state");
endpoint->config.usbp->in_params[endpoint->config.ep - 1U] = endpoint;
endpoint->timed_out = false;
osalSysUnlock();
}
/**
* @brief Stops the driver.
* @details Any thread waiting on the driver's queues will be awakened with
* the message @p MSG_RESET.
*
* @param[in] qmkusbp pointer to a @p QMKUSBDriver object
*
* @api
*/
void qmkusbStop(QMKUSBDriver *qmkusbp) {
USBDriver *usbp = qmkusbp->config->usbp;
osalDbgCheck(qmkusbp != NULL);
void usb_endpoint_out_start(usb_endpoint_out_t *endpoint) {
osalDbgCheck(endpoint != NULL);
osalSysLock();
osalDbgAssert((qmkusbp->state == QMKUSB_STOP) || (qmkusbp->state == QMKUSB_READY), "invalid state");
/* Driver in stopped state.*/
usbp->in_params[qmkusbp->config->bulk_in - 1U] = NULL;
usbp->out_params[qmkusbp->config->bulk_out - 1U] = NULL;
if (qmkusbp->config->int_in > 0U) {
usbp->in_params[qmkusbp->config->int_in - 1U] = NULL;
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();
}
qmkusbp->config = NULL;
qmkusbp->state = QMKUSB_STOP;
/* Enforces a disconnection.*/
chnAddFlagsI(qmkusbp, CHN_DISCONNECTED);
ibqResetI(&qmkusbp->ibqueue);
obqResetI(&qmkusbp->obqueue);
void usb_endpoint_in_stop(usb_endpoint_in_t *endpoint) {
osalDbgCheck(endpoint != NULL);
osalSysLock();
endpoint->config.usbp->in_params[endpoint->config.ep - 1U] = NULL;
bqSuspendI(&endpoint->obqueue);
obqResetI(&endpoint->obqueue);
if (endpoint->report_storage != NULL) {
endpoint->report_storage->reset_report(endpoint->report_storage->reports);
}
osalOsRescheduleS();
osalSysUnlock();
}
/**
* @brief USB device suspend handler.
* @details Generates a @p CHN_DISCONNECT event and puts queues in
* non-blocking mode, this way the application cannot get stuck
* in the middle of an I/O operations.
* @note If this function is not called from an ISR then an explicit call
* to @p osalOsRescheduleS() in necessary afterward.
*
* @param[in] qmkusbp pointer to a @p QMKUSBDriver object
*
* @iclass
*/
void qmkusbSuspendHookI(QMKUSBDriver *qmkusbp) {
chnAddFlagsI(qmkusbp, CHN_DISCONNECTED);
bqSuspendI(&qmkusbp->ibqueue);
bqSuspendI(&qmkusbp->obqueue);
void usb_endpoint_out_stop(usb_endpoint_out_t *endpoint) {
osalDbgCheck(endpoint != NULL);
osalSysLock();
osalDbgAssert((usbGetDriverStateI(endpoint->config.usbp) == USB_STOP) || (usbGetDriverStateI(endpoint->config.usbp) == USB_READY), "invalid state");
bqSuspendI(&endpoint->ibqueue);
ibqResetI(&endpoint->ibqueue);
osalOsRescheduleS();
osalSysUnlock();
}
/**
* @brief USB device wakeup handler.
* @details Generates a @p CHN_CONNECT event and resumes normal queues
* operations.
*
* @note If this function is not called from an ISR then an explicit call
* to @p osalOsRescheduleS() in necessary afterward.
*
* @param[in] qmkusbp pointer to a @p QMKUSBDriver object
*
* @iclass
*/
void qmkusbWakeupHookI(QMKUSBDriver *qmkusbp) {
chnAddFlagsI(qmkusbp, CHN_CONNECTED);
bqResumeX(&qmkusbp->ibqueue);
bqResumeX(&qmkusbp->obqueue);
void usb_endpoint_in_suspend_cb(usb_endpoint_in_t *endpoint) {
bqSuspendI(&endpoint->obqueue);
obqResetI(&endpoint->obqueue);
if (endpoint->report_storage != NULL) {
endpoint->report_storage->reset_report(endpoint->report_storage->reports);
}
}
/**
* @brief USB device configured handler.
*
* @param[in] qmkusbp pointer to a @p QMKUSBDriver object
*
* @iclass
*/
void qmkusbConfigureHookI(QMKUSBDriver *qmkusbp) {
ibqResetI(&qmkusbp->ibqueue);
bqResumeX(&qmkusbp->ibqueue);
obqResetI(&qmkusbp->obqueue);
bqResumeX(&qmkusbp->obqueue);
chnAddFlagsI(qmkusbp, CHN_CONNECTED);
(void)qmkusb_start_receive(qmkusbp);
void usb_endpoint_out_suspend_cb(usb_endpoint_out_t *endpoint) {
bqSuspendI(&endpoint->ibqueue);
ibqResetI(&endpoint->ibqueue);
}
/**
* @brief Default requests hook.
* @details Applications wanting to use the Serial over USB driver can use
* this function as requests hook in the USB configuration.
* The following requests are emulated:
* - CDC_GET_LINE_CODING.
* - CDC_SET_LINE_CODING.
* - CDC_SET_CONTROL_LINE_STATE.
* .
*
* @param[in] usbp pointer to the @p USBDriver object
* @return The hook status.
* @retval true Message handled internally.
* @retval false Message not handled.
*/
bool qmkusbRequestsHook(USBDriver *usbp) {
if ((usbp->setup[0] & USB_RTYPE_TYPE_MASK) == USB_RTYPE_TYPE_CLASS) {
switch (usbp->setup[1]) {
case CDC_GET_LINE_CODING:
usbSetupTransfer(usbp, (uint8_t *)&linecoding, sizeof(linecoding), NULL);
return true;
case CDC_SET_LINE_CODING:
usbSetupTransfer(usbp, (uint8_t *)&linecoding, sizeof(linecoding), NULL);
return true;
case CDC_SET_CONTROL_LINE_STATE:
/* Nothing to do, there are no control lines.*/
usbSetupTransfer(usbp, NULL, 0, NULL);
return true;
default:
return false;
}
}
return false;
void usb_endpoint_in_wakeup_cb(usb_endpoint_in_t *endpoint) {
bqResumeX(&endpoint->obqueue);
}
/**
* @brief SOF handler.
* @details The SOF interrupt is used for automatic flushing of incomplete
* buffers pending in the output queue.
*
* @param[in] qmkusbp pointer to a @p QMKUSBDriver object
*
* @iclass
*/
void qmkusbSOFHookI(QMKUSBDriver *qmkusbp) {
/* If the USB driver is not in the appropriate state then transactions
must not be started.*/
if ((usbGetDriverStateI(qmkusbp->config->usbp) != USB_ACTIVE) || (qmkusbp->state != QMKUSB_READY)) {
return;
void usb_endpoint_out_wakeup_cb(usb_endpoint_out_t *endpoint) {
bqResumeX(&endpoint->ibqueue);
}
/* If there is already a transaction ongoing then another one cannot be
started.*/
if (usbGetTransmitStatusI(qmkusbp->config->usbp, qmkusbp->config->bulk_in)) {
return;
void usb_endpoint_in_configure_cb(usb_endpoint_in_t *endpoint) {
usbInitEndpointI(endpoint->config.usbp, endpoint->config.ep, &endpoint->ep_config);
obqResetI(&endpoint->obqueue);
bqResumeX(&endpoint->obqueue);
}
/* Checking if there only a buffer partially filled, if so then it is
enforced in the queue and transmitted.*/
if (obqTryFlushI(&qmkusbp->obqueue)) {
void usb_endpoint_out_configure_cb(usb_endpoint_out_t *endpoint) {
/* The current assumption is that there are no standalone OUT endpoints,
* therefore if we share an endpoint with an IN endpoint, it is already
* initialized. */
#if !defined(USB_ENDPOINTS_ARE_REORDERABLE)
usbInitEndpointI(endpoint->config.usbp, endpoint->config.ep, &endpoint->ep_config);
#endif
ibqResetI(&endpoint->ibqueue);
bqResumeX(&endpoint->ibqueue);
(void)usb_start_receive(endpoint);
}
void usb_endpoint_in_tx_complete_cb(USBDriver *usbp, usbep_t ep) {
usb_endpoint_in_t *endpoint = usbp->in_params[ep - 1U];
size_t n;
uint8_t *buf = obqGetFullBufferI(&qmkusbp->obqueue, &n);
uint8_t * buffer;
/* For fixed size drivers, fill the end with zeros */
if (qmkusbp->config->fixed_size) {
memset(buf + n, 0, qmkusbp->config->in_size - n);
n = qmkusbp->config->in_size;
}
osalDbgAssert(buf != NULL, "queue is empty");
usbStartTransmitI(qmkusbp->config->usbp, qmkusbp->config->bulk_in, buf, n);
}
}
/**
* @brief Default data transmitted callback.
* @details The application must use this function as callback for the IN
* data endpoint.
*
* @param[in] usbp pointer to the @p USBDriver object
* @param[in] ep IN endpoint number
*/
void qmkusbDataTransmitted(USBDriver *usbp, usbep_t ep) {
uint8_t * buf;
size_t n;
QMKUSBDriver *qmkusbp = usbp->in_params[ep - 1U];
if (qmkusbp == NULL) {
if (endpoint == NULL) {
return;
}
osalSysLockFromISR();
/* Signaling that space is available in the output queue.*/
chnAddFlagsI(qmkusbp, CHN_OUTPUT_EMPTY);
/* Sending succeded, so we can reset the timed out state. */
endpoint->timed_out = false;
/* Freeing the buffer just transmitted, if it was not a zero size packet.*/
if (usbp->epc[ep]->in_state->txsize > 0U) {
obqReleaseEmptyBufferI(&qmkusbp->obqueue);
if (!obqIsEmptyI(&endpoint->obqueue) && usbp->epc[ep]->in_state->txsize > 0U) {
/* Store the last send report in the endpoint to be retrieved by a
* GET_REPORT request or IDLE report handling. */
if (endpoint->report_storage != NULL) {
buffer = obqGetFullBufferI(&endpoint->obqueue, &n);
endpoint->report_storage->set_report(endpoint->report_storage->reports, buffer, n);
}
obqReleaseEmptyBufferI(&endpoint->obqueue);
}
/* Checking if there is a buffer ready for transmission.*/
buf = obqGetFullBufferI(&qmkusbp->obqueue, &n);
buffer = obqGetFullBufferI(&endpoint->obqueue, &n);
if (buf != NULL) {
if (buffer != NULL) {
/* The endpoint cannot be busy, we are in the context of the callback,
so it is safe to transmit without a check.*/
usbStartTransmitI(usbp, ep, buf, n);
} else if ((usbp->epc[ep]->in_state->txsize > 0U) && ((usbp->epc[ep]->in_state->txsize & ((size_t)usbp->epc[ep]->in_maxsize - 1U)) == 0U)) {
usbStartTransmitI(usbp, ep, buffer, n);
} else if ((usbp->epc[ep]->ep_mode == USB_EP_MODE_TYPE_BULK) && (usbp->epc[ep]->in_state->txsize > 0U) && ((usbp->epc[ep]->in_state->txsize & ((size_t)usbp->epc[ep]->in_maxsize - 1U)) == 0U)) {
/* Transmit zero sized packet in case the last one has maximum allowed
size. Otherwise the recipient may expect more data coming soon and
not return buffered data to app. See section 5.8.3 Bulk Transfer
Packet Size Constraints of the USB Specification document.*/
if (!qmkusbp->config->fixed_size) {
* size. Otherwise the recipient may expect more data coming soon and
* not return buffered data to app. See section 5.8.3 Bulk Transfer
* Packet Size Constraints of the USB Specification document. */
usbStartTransmitI(usbp, ep, usbp->setup, 0);
}
} else {
/* Nothing to transmit.*/
}
@ -419,47 +228,114 @@ void qmkusbDataTransmitted(USBDriver *usbp, usbep_t ep) {
osalSysUnlockFromISR();
}
/**
* @brief Default data received callback.
* @details The application must use this function as callback for the OUT
* data endpoint.
*
* @param[in] usbp pointer to the @p USBDriver object
* @param[in] ep OUT endpoint number
*/
void qmkusbDataReceived(USBDriver *usbp, usbep_t ep) {
QMKUSBDriver *qmkusbp = usbp->out_params[ep - 1U];
if (qmkusbp == NULL) {
void usb_endpoint_out_rx_complete_cb(USBDriver *usbp, usbep_t ep) {
usb_endpoint_out_t *endpoint = usbp->out_params[ep - 1U];
if (endpoint == NULL) {
return;
}
osalSysLockFromISR();
/* Signaling that data is available in the input queue.*/
chnAddFlagsI(qmkusbp, CHN_INPUT_AVAILABLE);
size_t size = usbGetReceiveTransactionSizeX(usbp, ep);
if (size > 0) {
/* Posting the filled buffer in the queue.*/
ibqPostFullBufferI(&qmkusbp->ibqueue, usbGetReceiveTransactionSizeX(qmkusbp->config->usbp, qmkusbp->config->bulk_out));
ibqPostFullBufferI(&endpoint->ibqueue, usbGetReceiveTransactionSizeX(endpoint->config.usbp, endpoint->config.ep));
}
/* The endpoint cannot be busy, we are in the context of the callback,
so a packet is in the buffer for sure. Trying to get a free buffer
for the next transaction.*/
(void)qmkusb_start_receive(qmkusbp);
/* The endpoint cannot be busy, we are in the context of the callback, so a
* packet is in the buffer for sure. Trying to get a free buffer for the
* next transaction.*/
usb_start_receive(endpoint);
osalSysUnlockFromISR();
}
/**
* @brief Default data received callback.
* @details The application must use this function as callback for the IN
* interrupt endpoint.
*
* @param[in] usbp pointer to the @p USBDriver object
* @param[in] ep endpoint number
*/
void qmkusbInterruptTransmitted(USBDriver *usbp, usbep_t ep) {
(void)usbp;
(void)ep;
bool usb_endpoint_in_send(usb_endpoint_in_t *endpoint, const uint8_t *data, size_t size, sysinterval_t timeout, bool buffered) {
osalDbgCheck((endpoint != NULL) && (data != NULL) && (size > 0U) && (size <= endpoint->config.buffer_size));
osalSysLock();
if (usbGetDriverStateI(endpoint->config.usbp) != USB_ACTIVE) {
osalSysUnlock();
return false;
}
/** @} */
/* Short circuit the waiting if this endpoint timed out before, e.g. if
* nobody is listening on this endpoint (is disconnected) such as
* `hid_listen`/`qmk console` or we are in an environment with a very
* restricted USB stack. The reason is to not introduce micro lock-ups if
* the report is send periodically. */
if (endpoint->timed_out && timeout != TIME_INFINITE) {
timeout = TIME_IMMEDIATE;
}
osalSysUnlock();
while (true) {
size_t sent = obqWriteTimeout(&endpoint->obqueue, data, size, timeout);
if (sent < size) {
osalSysLock();
endpoint->timed_out |= sent == 0;
bqSuspendI(&endpoint->obqueue);
obqResetI(&endpoint->obqueue);
bqResumeX(&endpoint->obqueue);
osalOsRescheduleS();
osalSysUnlock();
continue;
}
if (!buffered) {
obqFlush(&endpoint->obqueue);
}
return true;
}
}
void usb_endpoint_in_flush(usb_endpoint_in_t *endpoint, bool padded) {
osalDbgCheck(endpoint != NULL);
output_buffers_queue_t *obqp = &endpoint->obqueue;
if (padded && obqp->ptr != NULL) {
ptrdiff_t bytes_left = (size_t)obqp->top - (size_t)obqp->ptr;
while (bytes_left > 0) {
// Putting bytes into a buffer that has space left should never
// fail and be instant, therefore we don't check the return value
// for errors here.
obqPutTimeout(obqp, 0, TIME_IMMEDIATE);
bytes_left--;
}
}
obqFlush(obqp);
}
bool usb_endpoint_in_is_inactive(usb_endpoint_in_t *endpoint) {
osalDbgCheck(endpoint != NULL);
osalSysLock();
bool inactive = obqIsEmptyI(&endpoint->obqueue) && !usbGetTransmitStatusI(endpoint->config.usbp, endpoint->config.ep);
osalSysUnlock();
return inactive;
}
bool usb_endpoint_out_receive(usb_endpoint_out_t *endpoint, uint8_t *data, size_t size, sysinterval_t timeout) {
osalDbgCheck((endpoint != NULL) && (data != NULL) && (size > 0U));
osalSysLock();
if (usbGetDriverStateI(endpoint->config.usbp) != USB_ACTIVE) {
osalSysUnlock();
return false;
}
if (endpoint->timed_out && timeout != TIME_INFINITE) {
timeout = TIME_IMMEDIATE;
}
osalSysUnlock();
const size_t received = ibqReadTimeout(&endpoint->ibqueue, data, size, timeout);
endpoint->timed_out = received == 0;
return received == size;
}

View File

@ -1,177 +1,209 @@
/*
ChibiOS - Copyright (C) 2006..2016 Giovanni Di Sirio
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
/**
* @file usb_driver.h
* @brief Usb driver suitable for both packet and serial formats
*
* @addtogroup SERIAL_USB
* @{
*/
// Copyright 2023 Stefan Kerkmann (@KarlK90)
// Copyright 2020 Ryan (@fauxpark)
// Copyright 2016 Fredizzimo
// Copyright 2016 Giovanni Di Sirio
// SPDX-License-Identifier: GPL-3.0-or-later OR Apache-2.0
#pragma once
#include <hal_usb_cdc.h>
/*===========================================================================*/
/* Driver constants. */
/*===========================================================================*/
/*===========================================================================*/
/* Derived constants and error checks. */
/*===========================================================================*/
#include <hal_buffers.h>
#include "usb_descriptor.h"
#include "chibios_config.h"
#include "usb_report_handling.h"
#include "string.h"
#include "timer.h"
#if HAL_USE_USB == FALSE
# error "The USB Driver requires HAL_USE_USB"
#endif
/*===========================================================================*/
/* Driver data structures and types. */
/*===========================================================================*/
/* USB Low Level driver specific endpoint fields */
#if !defined(usb_lld_endpoint_fields)
# define usb_lld_endpoint_fields \
2, /* IN multiplier */ \
NULL, /* SETUP buffer (not a SETUP endpoint) */
#endif
/**
* @brief Driver state machine possible states.
/*
* Implementation notes:
*
* USBEndpointConfig - Configured using explicit order instead of struct member name.
* This is due to ChibiOS hal LLD differences, which is dependent on hardware,
* "USBv1" devices have `ep_buffers` and "OTGv1" have `in_multiplier`.
* Given `USBv1/hal_usb_lld.h` marks the field as "not currently used" this code file
* makes the assumption this is safe to avoid littering with preprocessor directives.
*/
typedef enum {
QMKUSB_UNINIT = 0, /**< Not initialized. */
QMKUSB_STOP = 1, /**< Stopped. */
QMKUSB_READY = 2 /**< Ready. */
} qmkusbstate_t;
#define QMK_USB_ENDPOINT_IN(mode, ep_size, ep_num, _buffer_capacity, _usb_requests_cb, _report_storage) \
{ \
.usb_requests_cb = _usb_requests_cb, .report_storage = _report_storage, \
.ep_config = \
{ \
mode, /* EP Mode */ \
NULL, /* SETUP packet notification callback */ \
usb_endpoint_in_tx_complete_cb, /* IN notification callback */ \
NULL, /* OUT notification callback */ \
ep_size, /* IN maximum packet size */ \
0, /* OUT maximum packet size */ \
NULL, /* IN Endpoint state */ \
NULL, /* OUT endpoint state */ \
usb_lld_endpoint_fields /* USB driver specific endpoint fields */ \
}, \
.config = { \
.usbp = &USB_DRIVER, \
.ep = ep_num, \
.buffer_capacity = _buffer_capacity, \
.buffer_size = ep_size, \
.buffer = (_Alignas(4) uint8_t[BQ_BUFFER_SIZE(_buffer_capacity, ep_size)]){0}, \
} \
}
/**
* @brief Structure representing a serial over USB driver.
*/
typedef struct QMKUSBDriver QMKUSBDriver;
#if !defined(USB_ENDPOINTS_ARE_REORDERABLE)
# define QMK_USB_ENDPOINT_OUT(mode, ep_size, ep_num, _buffer_capacity) \
{ \
.ep_config = \
{ \
mode, /* EP Mode */ \
NULL, /* SETUP packet notification callback */ \
NULL, /* IN notification callback */ \
usb_endpoint_out_rx_complete_cb, /* OUT notification callback */ \
0, /* IN maximum packet size */ \
ep_size, /* OUT maximum packet size */ \
NULL, /* IN Endpoint state */ \
NULL, /* OUT endpoint state */ \
usb_lld_endpoint_fields /* USB driver specific endpoint fields */ \
}, \
.config = { \
.usbp = &USB_DRIVER, \
.ep = ep_num, \
.buffer_capacity = _buffer_capacity, \
.buffer_size = ep_size, \
.buffer = (_Alignas(4) uint8_t[BQ_BUFFER_SIZE(_buffer_capacity, ep_size)]){0} \
} \
}
#else
# define QMK_USB_ENDPOINT_IN_SHARED(mode, ep_size, ep_num, _buffer_capacity, _usb_requests_cb, _report_storage) \
{ \
.usb_requests_cb = _usb_requests_cb, .is_shared = true, .report_storage = _report_storage, \
.ep_config = \
{ \
mode, /* EP Mode */ \
NULL, /* SETUP packet notification callback */ \
usb_endpoint_in_tx_complete_cb, /* IN notification callback */ \
usb_endpoint_out_rx_complete_cb, /* OUT notification callback */ \
ep_size, /* IN maximum packet size */ \
ep_size, /* OUT maximum packet size */ \
NULL, /* IN Endpoint state */ \
NULL, /* OUT endpoint state */ \
usb_lld_endpoint_fields /* USB driver specific endpoint fields */ \
}, \
.config = { \
.usbp = &USB_DRIVER, \
.ep = ep_num, \
.buffer_capacity = _buffer_capacity, \
.buffer_size = ep_size, \
.buffer = (_Alignas(4) uint8_t[BQ_BUFFER_SIZE(_buffer_capacity, ep_size)]){0}, \
} \
}
/* The current assumption is that there are no standalone OUT endpoints, so the
* OUT endpoint is always initialized by the IN endpoint. */
# define QMK_USB_ENDPOINT_OUT(mode, ep_size, ep_num, _buffer_capacity) \
{ \
.ep_config = \
{ \
0 /* Already defined in the IN endpoint */ \
}, \
.config = { \
.usbp = &USB_DRIVER, \
.ep = ep_num, \
.buffer_capacity = _buffer_capacity, \
.buffer_size = ep_size, \
.buffer = (_Alignas(4) uint8_t[BQ_BUFFER_SIZE(_buffer_capacity, ep_size)]){0} \
} \
}
#endif
/**
* @brief Serial over USB Driver configuration structure.
* @details An instance of this structure must be passed to @p sduStart()
* in order to configure and start the driver operations.
*/
typedef struct {
/**
* @brief USB driver to use.
*/
USBDriver *usbp;
/**
* @brief Bulk IN endpoint used for outgoing data transfer.
*/
usbep_t bulk_in;
/**
* @brief Bulk OUT endpoint used for incoming data transfer.
*/
usbep_t bulk_out;
/**
* @brief Interrupt IN endpoint used for notifications.
* @note If set to zero then the INT endpoint is assumed to be not
* present, USB descriptors must be changed accordingly.
*/
usbep_t int_in;
/**
* @brief The number of buffers in the queues
* @brief Endpoint used for data transfer
*/
size_t in_buffers;
size_t out_buffers;
usbep_t ep;
/**
* @brief The size of each buffer in the queue, typically the same as the endpoint size
* @brief The number of buffers in the queue
*/
size_t in_size;
size_t out_size;
size_t buffer_capacity;
/**
* @brief Always send full buffers in_size (the rest is filled with zeroes)
* @brief The size of each buffer in the queue, same as the endpoint size
*/
bool fixed_size;
/* 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;
size_t buffer_size;
/**
* @brief @p SerialDriver specific data.
* @brief Buffer backing storage
*/
#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;
uint8_t *buffer;
} usb_endpoint_config_t;
/**
* @brief @p SerialUSBDriver specific methods.
*/
#define _qmk_usb_driver_methods _base_asynchronous_channel_methods
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;
/**
* @extends BaseAsynchronousChannelVMT
*
* @brief @p SerialDriver virtual methods table.
*/
struct QMKUSBDriverVMT {
_qmk_usb_driver_methods
};
/**
* @extends BaseAsynchronousChannel
*
* @brief Full duplex serial driver class.
* @details This class extends @p BaseAsynchronousChannel by adding physical
* I/O queues.
*/
struct QMKUSBDriver {
/** @brief Virtual Methods Table.*/
const struct QMKUSBDriverVMT *vmt;
_qmk_usb_driver_data
};
/*===========================================================================*/
/* Driver macros. */
/*===========================================================================*/
/*===========================================================================*/
/* External declarations. */
/*===========================================================================*/
typedef struct {
input_buffers_queue_t ibqueue;
USBEndpointConfig ep_config;
USBOutEndpointState ep_out_state;
usb_endpoint_config_t config;
bool timed_out;
} usb_endpoint_out_t;
#ifdef __cplusplus
extern "C" {
#endif
void qmkusbInit(void);
void qmkusbObjectInit(QMKUSBDriver *qmkusbp, const QMKUSBConfig *config);
void qmkusbStart(QMKUSBDriver *qmkusbp, const QMKUSBConfig *config);
void qmkusbStop(QMKUSBDriver *qmkusbp);
void qmkusbSuspendHookI(QMKUSBDriver *qmkusbp);
void qmkusbWakeupHookI(QMKUSBDriver *qmkusbp);
void qmkusbConfigureHookI(QMKUSBDriver *qmkusbp);
bool qmkusbRequestsHook(USBDriver *usbp);
void qmkusbSOFHookI(QMKUSBDriver *qmkusbp);
void qmkusbDataTransmitted(USBDriver *usbp, usbep_t ep);
void qmkusbDataReceived(USBDriver *usbp, usbep_t ep);
void qmkusbInterruptTransmitted(USBDriver *usbp, usbep_t ep);
void usb_endpoint_in_init(usb_endpoint_in_t *endpoint);
void usb_endpoint_in_start(usb_endpoint_in_t *endpoint);
void usb_endpoint_in_stop(usb_endpoint_in_t *endpoint);
bool usb_endpoint_in_send(usb_endpoint_in_t *endpoint, const uint8_t *data, size_t size, sysinterval_t timeout, bool buffered);
void usb_endpoint_in_flush(usb_endpoint_in_t *endpoint, bool padded);
bool usb_endpoint_in_is_inactive(usb_endpoint_in_t *endpoint);
void usb_endpoint_in_suspend_cb(usb_endpoint_in_t *endpoint);
void usb_endpoint_in_wakeup_cb(usb_endpoint_in_t *endpoint);
void usb_endpoint_in_configure_cb(usb_endpoint_in_t *endpoint);
void usb_endpoint_in_tx_complete_cb(USBDriver *usbp, usbep_t ep);
void usb_endpoint_out_init(usb_endpoint_out_t *endpoint);
void usb_endpoint_out_start(usb_endpoint_out_t *endpoint);
void usb_endpoint_out_stop(usb_endpoint_out_t *endpoint);
bool usb_endpoint_out_receive(usb_endpoint_out_t *endpoint, uint8_t *data, size_t size, sysinterval_t timeout);
void usb_endpoint_out_suspend_cb(usb_endpoint_out_t *endpoint);
void usb_endpoint_out_wakeup_cb(usb_endpoint_out_t *endpoint);
void usb_endpoint_out_configure_cb(usb_endpoint_out_t *endpoint);
void usb_endpoint_out_rx_complete_cb(USBDriver *usbp, usbep_t ep);
#ifdef __cplusplus
}
#endif

View File

@ -0,0 +1,152 @@
// Copyright 2023 Stefan Kerkmann (@KarlK90)
// SPDX-License-Identifier: GPL-3.0-or-later
#include <ch.h>
#include <hal.h>
#include "usb_main.h"
#include "usb_driver.h"
#include "usb_endpoints.h"
#include "report.h"
usb_endpoint_in_t usb_endpoints_in[USB_ENDPOINT_IN_COUNT] = {
// clang-format off
#if defined(SHARED_EP_ENABLE)
[USB_ENDPOINT_IN_SHARED] = QMK_USB_ENDPOINT_IN(USB_EP_MODE_TYPE_INTR, SHARED_EPSIZE, SHARED_IN_EPNUM, SHARED_IN_CAPACITY, NULL,
QMK_USB_REPORT_STORAGE(
&usb_shared_get_report,
&usb_shared_set_report,
&usb_shared_reset_report,
&usb_shared_get_idle_rate,
&usb_shared_set_idle_rate,
&usb_shared_idle_timer_elapsed,
(REPORT_ID_COUNT + 1),
#if defined(KEYBOARD_SHARED_EP)
QMK_USB_REPORT_STROAGE_ENTRY(REPORT_ID_KEYBOARD, sizeof(report_keyboard_t)),
#endif
#if defined(MOUSE_SHARED_EP)
QMK_USB_REPORT_STROAGE_ENTRY(REPORT_ID_MOUSE, sizeof(report_mouse_t)),
#endif
#if defined(EXTRAKEY_ENABLE)
QMK_USB_REPORT_STROAGE_ENTRY(REPORT_ID_SYSTEM, sizeof(report_extra_t)),
QMK_USB_REPORT_STROAGE_ENTRY(REPORT_ID_CONSUMER, sizeof(report_extra_t)),
#endif
#if defined(PROGRAMMABLE_BUTTON_ENABLE)
QMK_USB_REPORT_STROAGE_ENTRY(REPORT_ID_PROGRAMMABLE_BUTTON, sizeof(report_programmable_button_t)),
#endif
#if defined(NKRO_ENABLE)
QMK_USB_REPORT_STROAGE_ENTRY(REPORT_ID_NKRO, sizeof(report_nkro_t)),
#endif
#if defined(JOYSTICK_SHARED_EP)
QMK_USB_REPORT_STROAGE_ENTRY(REPORT_ID_JOYSTICK, sizeof(report_joystick_t)),
#endif
#if defined(DIGITIZER_SHARED_EP)
QMK_USB_REPORT_STROAGE_ENTRY(REPORT_ID_DIGITIZER, sizeof(report_digitizer_t)),
#endif
)
),
#endif
// clang-format on
#if !defined(KEYBOARD_SHARED_EP)
[USB_ENDPOINT_IN_KEYBOARD] = QMK_USB_ENDPOINT_IN(USB_EP_MODE_TYPE_INTR, KEYBOARD_EPSIZE, KEYBOARD_IN_EPNUM, KEYBOARD_IN_CAPACITY, NULL, QMK_USB_REPORT_STORAGE_DEFAULT(sizeof(report_keyboard_t))),
#endif
#if defined(MOUSE_ENABLE) && !defined(MOUSE_SHARED_EP)
[USB_ENDPOINT_IN_MOUSE] = QMK_USB_ENDPOINT_IN(USB_EP_MODE_TYPE_INTR, MOUSE_EPSIZE, MOUSE_IN_EPNUM, MOUSE_IN_CAPACITY, NULL, QMK_USB_REPORT_STORAGE_DEFAULT(sizeof(report_mouse_t))),
#endif
#if defined(JOYSTICK_ENABLE) && !defined(JOYSTICK_SHARED_EP)
[USB_ENDPOINT_IN_JOYSTICK] = QMK_USB_ENDPOINT_IN(USB_EP_MODE_TYPE_INTR, JOYSTICK_EPSIZE, JOYSTICK_IN_EPNUM, JOYSTICK_IN_CAPACITY, QMK_USB_REPORT_STORAGE_DEFAULT(sizeof(report_joystick_t))),
#endif
#if defined(DIGITIZER_ENABLE) && !defined(DIGITIZER_SHARED_EP)
[USB_ENDPOINT_IN_JOYSTICK] = QMK_USB_ENDPOINT_IN(USB_EP_MODE_TYPE_INTR, DIGITIZER_EPSIZE, DIGITIZER_IN_EPNUM, DIGITIZER_IN_CAPACITY, QMK_USB_REPORT_STORAGE_DEFAULT(sizeof(report_digitizer_t))),
#endif
#if defined(CONSOLE_ENABLE)
# if defined(USB_ENDPOINTS_ARE_REORDERABLE)
[USB_ENDPOINT_IN_CONSOLE] = QMK_USB_ENDPOINT_IN_SHARED(USB_EP_MODE_TYPE_INTR, CONSOLE_EPSIZE, CONSOLE_IN_EPNUM, CONSOLE_IN_CAPACITY, NULL, QMK_USB_REPORT_STORAGE_DEFAULT(CONSOLE_EPSIZE)),
# else
[USB_ENDPOINT_IN_CONSOLE] = QMK_USB_ENDPOINT_IN(USB_EP_MODE_TYPE_INTR, CONSOLE_EPSIZE, CONSOLE_IN_EPNUM, CONSOLE_IN_CAPACITY, NULL, QMK_USB_REPORT_STORAGE_DEFAULT(CONSOLE_EPSIZE)),
# endif
#endif
#if defined(RAW_ENABLE)
# if defined(USB_ENDPOINTS_ARE_REORDERABLE)
[USB_ENDPOINT_IN_RAW] = QMK_USB_ENDPOINT_IN_SHARED(USB_EP_MODE_TYPE_INTR, RAW_EPSIZE, RAW_IN_EPNUM, RAW_IN_CAPACITY, NULL, QMK_USB_REPORT_STORAGE_DEFAULT(RAW_EPSIZE)),
# else
[USB_ENDPOINT_IN_RAW] = QMK_USB_ENDPOINT_IN(USB_EP_MODE_TYPE_INTR, RAW_EPSIZE, RAW_IN_EPNUM, RAW_IN_CAPACITY, NULL, QMK_USB_REPORT_STORAGE_DEFAULT(RAW_EPSIZE)),
# endif
#endif
#if defined(MIDI_ENABLE)
# if defined(USB_ENDPOINTS_ARE_REORDERABLE)
[USB_ENDPOINT_IN_MIDI] = QMK_USB_ENDPOINT_IN_SHARED(USB_EP_MODE_TYPE_BULK, MIDI_STREAM_EPSIZE, MIDI_STREAM_IN_EPNUM, MIDI_STREAM_IN_CAPACITY, NULL, NULL),
# else
[USB_ENDPOINT_IN_MIDI] = QMK_USB_ENDPOINT_IN(USB_EP_MODE_TYPE_BULK, MIDI_STREAM_EPSIZE, MIDI_STREAM_IN_EPNUM, MIDI_STREAM_IN_CAPACITY, NULL, NULL),
# endif
#endif
#if defined(VIRTSER_ENABLE)
# if defined(USB_ENDPOINTS_ARE_REORDERABLE)
[USB_ENDPOINT_IN_CDC_DATA] = QMK_USB_ENDPOINT_IN_SHARED(USB_EP_MODE_TYPE_BULK, CDC_EPSIZE, CDC_IN_EPNUM, CDC_IN_CAPACITY, virtser_usb_request_cb, NULL),
# else
[USB_ENDPOINT_IN_CDC_DATA] = QMK_USB_ENDPOINT_IN(USB_EP_MODE_TYPE_BULK, CDC_EPSIZE, CDC_IN_EPNUM, CDC_IN_CAPACITY, virtser_usb_request_cb, NULL),
# endif
[USB_ENDPOINT_IN_CDC_SIGNALING] = QMK_USB_ENDPOINT_IN(USB_EP_MODE_TYPE_INTR, CDC_NOTIFICATION_EPSIZE, CDC_NOTIFICATION_EPNUM, CDC_SIGNALING_DUMMY_CAPACITY, NULL, NULL),
#endif
};
usb_endpoint_in_lut_t usb_endpoint_interface_lut[TOTAL_INTERFACES] = {
#if !defined(KEYBOARD_SHARED_EP)
[KEYBOARD_INTERFACE] = USB_ENDPOINT_IN_KEYBOARD,
#endif
#if defined(RAW_ENABLE)
[RAW_INTERFACE] = USB_ENDPOINT_IN_RAW,
#endif
#if defined(MOUSE_ENABLE) && !defined(MOUSE_SHARED_EP)
[MOUSE_INTERFACE] = USB_ENDPOINT_IN_MOUSE,
#endif
#if defined(SHARED_EP_ENABLE)
[SHARED_INTERFACE] = USB_ENDPOINT_IN_SHARED,
#endif
#if defined(CONSOLE_ENABLE)
[CONSOLE_INTERFACE] = USB_ENDPOINT_IN_CONSOLE,
#endif
#if defined(MIDI_ENABLE)
[AS_INTERFACE] = USB_ENDPOINT_IN_MIDI,
#endif
#if defined(VIRTSER_ENABLE)
[CCI_INTERFACE] = USB_ENDPOINT_IN_CDC_SIGNALING,
[CDI_INTERFACE] = USB_ENDPOINT_IN_CDC_DATA,
#endif
#if defined(JOYSTICK_ENABLE) && !defined(JOYSTICK_SHARED_EP)
[JOYSTICK_INTERFACE] = USB_ENDPOINT_IN_JOYSTICK,
#endif
#if defined(DIGITIZER_ENABLE) && !defined(DIGITIZER_SHARED_EP)
[DIGITIZER_INTERFACE] = USB_ENDPOINT_IN_DIGITIZER,
#endif
};
usb_endpoint_out_t usb_endpoints_out[USB_ENDPOINT_OUT_COUNT] = {
#if defined(RAW_ENABLE)
[USB_ENDPOINT_OUT_RAW] = QMK_USB_ENDPOINT_OUT(USB_EP_MODE_TYPE_INTR, RAW_EPSIZE, RAW_OUT_EPNUM, RAW_OUT_CAPACITY),
#endif
#if defined(MIDI_ENABLE)
[USB_ENDPOINT_OUT_MIDI] = QMK_USB_ENDPOINT_OUT(USB_EP_MODE_TYPE_BULK, MIDI_STREAM_EPSIZE, MIDI_STREAM_OUT_EPNUM, MIDI_STREAM_OUT_CAPACITY),
#endif
#if defined(VIRTSER_ENABLE)
[USB_ENDPOINT_OUT_CDC_DATA] = QMK_USB_ENDPOINT_OUT(USB_EP_MODE_TYPE_BULK, CDC_EPSIZE, CDC_OUT_EPNUM, CDC_OUT_CAPACITY),
#endif
};

View File

@ -0,0 +1,137 @@
// Copyright 2023 Stefan Kerkmann (@KarlK90)
// SPDX-License-Identifier: GPL-3.0-or-later
#pragma once
#include "usb_descriptor.h"
#if !defined(USB_DEFAULT_BUFFER_CAPACITY)
# define USB_DEFAULT_BUFFER_CAPACITY 4
#endif
#if !defined(KEYBOARD_IN_CAPACITY)
# define KEYBOARD_IN_CAPACITY USB_DEFAULT_BUFFER_CAPACITY
#endif
#if !defined(SHARED_IN_CAPACITY)
# define SHARED_IN_CAPACITY USB_DEFAULT_BUFFER_CAPACITY
#endif
#if !defined(MOUSE_IN_CAPACITY)
# define MOUSE_IN_CAPACITY USB_DEFAULT_BUFFER_CAPACITY
#endif
#if !defined(JOYSTICK_IN_CAPACITY)
# define JOYSTICK_IN_CAPACITY USB_DEFAULT_BUFFER_CAPACITY
#endif
#if !defined(DIGITIZER_IN_CAPACITY)
# define DIGITIZER_IN_CAPACITY USB_DEFAULT_BUFFER_CAPACITY
#endif
#if !defined(CONSOLE_IN_CAPACITY)
# define CONSOLE_IN_CAPACITY USB_DEFAULT_BUFFER_CAPACITY
#endif
#if !defined(CONSOLE_OUT_CAPACITY)
# define CONSOLE_OUT_CAPACITY USB_DEFAULT_BUFFER_CAPACITY
#endif
#if !defined(RAW_IN_CAPACITY)
# define RAW_IN_CAPACITY USB_DEFAULT_BUFFER_CAPACITY
#endif
#if !defined(RAW_OUT_CAPACITY)
# define RAW_OUT_CAPACITY USB_DEFAULT_BUFFER_CAPACITY
#endif
#if !defined(MIDI_STREAM_IN_CAPACITY)
# define MIDI_STREAM_IN_CAPACITY USB_DEFAULT_BUFFER_CAPACITY
#endif
#if !defined(MIDI_STREAM_OUT_CAPACITY)
# define MIDI_STREAM_OUT_CAPACITY USB_DEFAULT_BUFFER_CAPACITY
#endif
#if !defined(CDC_IN_CAPACITY)
# define CDC_IN_CAPACITY USB_DEFAULT_BUFFER_CAPACITY
#endif
#if !defined(CDC_OUT_CAPACITY)
# define CDC_OUT_CAPACITY USB_DEFAULT_BUFFER_CAPACITY
#endif
#define CDC_SIGNALING_DUMMY_CAPACITY 1
typedef enum {
#if defined(SHARED_EP_ENABLE)
USB_ENDPOINT_IN_SHARED,
#endif
#if !defined(KEYBOARD_SHARED_EP)
USB_ENDPOINT_IN_KEYBOARD,
#endif
#if defined(MOUSE_ENABLE) && !defined(MOUSE_SHARED_EP)
USB_ENDPOINT_IN_MOUSE,
#endif
#if defined(JOYSTICK_ENABLE) && !defined(JOYSTICK_SHARED_EP)
USB_ENDPOINT_IN_JOYSTICK,
#endif
#if defined(DIGITIZER_ENABLE) && !defined(DIGITIZER_SHARED_EP)
USB_ENDPOINT_IN_DIGITIZER,
#endif
#if defined(CONSOLE_ENABLE)
USB_ENDPOINT_IN_CONSOLE,
#endif
#if defined(RAW_ENABLE)
USB_ENDPOINT_IN_RAW,
#endif
#if defined(MIDI_ENABLE)
USB_ENDPOINT_IN_MIDI,
#endif
#if defined(VIRTSER_ENABLE)
USB_ENDPOINT_IN_CDC_DATA,
USB_ENDPOINT_IN_CDC_SIGNALING,
#endif
USB_ENDPOINT_IN_COUNT,
/* All non shared endpoints have to be consequtive numbers starting from 0, so
* that they can be used as array indices. The shared endpoints all point to
* the same endpoint so they have to be defined last to not reset the enum
* counter. */
#if defined(SHARED_EP_ENABLE)
# if defined(KEYBOARD_SHARED_EP)
USB_ENDPOINT_IN_KEYBOARD = USB_ENDPOINT_IN_SHARED,
# endif
# if defined(MOUSE_SHARED_EP)
USB_ENDPOINT_IN_MOUSE = USB_ENDPOINT_IN_SHARED,
# endif
# if defined(JOYSTICK_SHARED_EP)
USB_ENDPOINT_IN_JOYSTICK = USB_ENDPOINT_IN_SHARED,
# endif
# if defined(DIGITIZER_SHARED_EP)
USB_ENDPOINT_IN_DIGITIZER = USB_ENDPOINT_IN_SHARED,
# endif
#endif
} usb_endpoint_in_lut_t;
#define IS_VALID_USB_ENDPOINT_IN_LUT(i) ((i) >= 0 && (i) < USB_ENDPOINT_IN_COUNT)
usb_endpoint_in_lut_t usb_endpoint_interface_lut[TOTAL_INTERFACES];
typedef enum {
#if defined(RAW_ENABLE)
USB_ENDPOINT_OUT_RAW,
#endif
#if defined(MIDI_ENABLE)
USB_ENDPOINT_OUT_MIDI,
#endif
#if defined(VIRTSER_ENABLE)
USB_ENDPOINT_OUT_CDC_DATA,
#endif
USB_ENDPOINT_OUT_COUNT,
} usb_endpoint_out_lut_t;

File diff suppressed because it is too large Load Diff

View File

@ -1,25 +1,21 @@
/*
* (c) 2015 flabberast <s3+flabbergast@sdfeu.org>
*
* Based on the following work:
* - Guillaume Duc's raw hid example (MIT License)
* https://github.com/guiduc/usb-hid-chibios-example
* - PJRC Teensy examples (MIT License)
* https://www.pjrc.com/teensy/usb_keyboard.html
* - hasu's TMK keyboard code (GPL v2 and some code Modified BSD)
* https://github.com/tmk/tmk_keyboard/
* - ChibiOS demo code (Apache 2.0 License)
* http://www.chibios.org
*
* Since some GPL'd code is used, this work is licensed under
* GPL v2 or later.
*/
// Copyright 2023 Stefan Kerkmann (@KarlK90)
// Copyright 2020 Ryan (@fauxpark)
// Copyright 2020 Joel Challis (@zvecr)
// Copyright 2018 James Laird-Wah
// Copyright 2016 Fredizzimo
// Copyright 2016 Giovanni Di Sirio
// SPDX-License-Identifier: GPL-3.0-or-later OR Apache-2.0
#pragma once
#include <ch.h>
#include <hal.h>
#include "usb_device_state.h"
#include "usb_descriptor.h"
#include "usb_driver.h"
#include "usb_endpoints.h"
/* -------------------------
* General USB driver header
* -------------------------
@ -36,6 +32,8 @@ void init_usb_driver(USBDriver *usbp);
/* Restart the USB driver and bus */
void restart_usb_driver(USBDriver *usbp);
bool send_report(usb_endpoint_in_lut_t endpoint, void *report, size_t size);
/* ---------------
* USB Event queue
* ---------------
@ -58,3 +56,14 @@ void usb_event_queue_task(void);
int8_t sendchar(uint8_t c);
#endif /* CONSOLE_ENABLE */
/* --------------
* Virtser header
* --------------
*/
#if defined(VIRTSER_ENABLE)
bool virtser_usb_request_cb(USBDriver *usbp);
#endif

View File

@ -0,0 +1,296 @@
// Copyright 2023 Stefan Kerkmann (@KarlK90)
// SPDX-License-Identifier: GPL-3.0-or-later
#include <string.h>
#include <stdint.h>
#include <stdbool.h>
#include "usb_report_handling.h"
#include "usb_endpoints.h"
#include "usb_main.h"
#include "usb_types.h"
#include "usb_driver.h"
#include "report.h"
extern usb_endpoint_in_t usb_endpoints_in[USB_ENDPOINT_IN_COUNT];
extern usb_endpoint_in_lut_t usb_endpoint_interface_lut[TOTAL_INTERFACES];
void usb_set_report(usb_fs_report_t **reports, const uint8_t *data, size_t length) {
if (*reports == NULL) {
return;
}
(*reports)->last_report = chVTGetSystemTimeX();
(*reports)->length = length;
memcpy(&(*reports)->data, data, length);
}
void usb_get_report(usb_fs_report_t **reports, uint8_t report_id, usb_fs_report_t *report) {
(void)report_id;
if (*reports == NULL) {
return;
}
report->length = (*reports)->length;
memcpy(&report->data, &(*reports)->data, report->length);
}
void usb_reset_report(usb_fs_report_t **reports) {
if (*reports == NULL) {
return;
}
memset(&(*reports)->data, 0, (*reports)->length);
(*reports)->idle_rate = 0;
(*reports)->last_report = 0;
}
void usb_shared_set_report(usb_fs_report_t **reports, const uint8_t *data, size_t length) {
uint8_t report_id = data[0];
if (report_id > REPORT_ID_COUNT || reports[report_id] == NULL) {
return;
}
reports[report_id]->last_report = chVTGetSystemTimeX();
reports[report_id]->length = length;
memcpy(&reports[report_id]->data, data, length);
}
void usb_shared_get_report(usb_fs_report_t **reports, uint8_t report_id, usb_fs_report_t *report) {
if (report_id > REPORT_ID_COUNT || reports[report_id] == NULL) {
return;
}
report->length = reports[report_id]->length;
memcpy(&report->data, &reports[report_id]->data, report->length);
}
void usb_shared_reset_report(usb_fs_report_t **reports) {
for (int i = 0; i <= REPORT_ID_COUNT; i++) {
if (reports[i] == NULL) {
continue;
}
memset(&reports[i]->data, 0, reports[i]->length);
reports[i]->idle_rate = 0;
reports[i]->last_report = 0;
}
}
bool usb_get_report_cb(USBDriver *driver) {
usb_control_request_t *setup = (usb_control_request_t *)driver->setup;
uint8_t interface = setup->wIndex;
uint8_t report_id = setup->wValue.lbyte;
static usb_fs_report_t report;
if (!IS_VALID_INTERFACE(interface) || !IS_VALID_REPORT_ID(report_id)) {
return false;
}
usb_endpoint_in_lut_t ep = usb_endpoint_interface_lut[interface];
if (!IS_VALID_USB_ENDPOINT_IN_LUT(ep)) {
return false;
}
usb_report_storage_t *report_storage = usb_endpoints_in[ep].report_storage;
if (report_storage == NULL) {
return false;
}
report_storage->get_report(report_storage->reports, report_id, &report);
usbSetupTransfer(driver, (uint8_t *)report.data, report.length, NULL);
return true;
}
static bool run_idle_task = false;
void usb_set_idle_rate(usb_fs_report_t **reports, uint8_t report_id, uint8_t idle_rate) {
(void)report_id;
if (*reports == NULL) {
return;
}
(*reports)->idle_rate = idle_rate * 4;
run_idle_task |= idle_rate != 0;
}
uint8_t usb_get_idle_rate(usb_fs_report_t **reports, uint8_t report_id) {
(void)report_id;
if (*reports == NULL) {
return 0;
}
return (*reports)->idle_rate / 4;
}
bool usb_idle_timer_elapsed(usb_fs_report_t **reports, uint8_t report_id) {
(void)report_id;
if (*reports == NULL) {
return false;
}
osalSysLock();
time_msecs_t idle_rate = (*reports)->idle_rate;
systime_t last_report = (*reports)->last_report;
osalSysUnlock();
if (idle_rate == 0) {
return false;
}
return chTimeI2MS(chVTTimeElapsedSinceX(last_report)) >= idle_rate;
}
void usb_shared_set_idle_rate(usb_fs_report_t **reports, uint8_t report_id, uint8_t idle_rate) {
// USB spec demands that a report_id of 0 would set the idle rate for all
// reports of that endpoint, but this can easily lead to resource
// exhaustion, therefore we deliberalty break the spec at this point.
if (report_id == 0 || report_id > REPORT_ID_COUNT || reports[report_id] == NULL) {
return;
}
reports[report_id]->idle_rate = idle_rate * 4;
run_idle_task |= idle_rate != 0;
}
uint8_t usb_shared_get_idle_rate(usb_fs_report_t **reports, uint8_t report_id) {
if (report_id > REPORT_ID_COUNT || reports[report_id] == NULL) {
return 0;
}
return reports[report_id]->idle_rate / 4;
}
bool usb_shared_idle_timer_elapsed(usb_fs_report_t **reports, uint8_t report_id) {
if (report_id > REPORT_ID_COUNT || reports[report_id] == NULL) {
return false;
}
osalSysLock();
time_msecs_t idle_rate = reports[report_id]->idle_rate;
systime_t last_report = reports[report_id]->last_report;
osalSysUnlock();
if (idle_rate == 0) {
return false;
}
return chTimeI2MS(chVTTimeElapsedSinceX(last_report)) >= idle_rate;
}
void usb_idle_task(void) {
if (!run_idle_task) {
return;
}
static usb_fs_report_t report;
bool non_zero_idle_rate_found = false;
for (int ep = 0; ep < USB_ENDPOINT_IN_COUNT; ep++) {
usb_report_storage_t *report_storage = usb_endpoints_in[ep].report_storage;
if (report_storage == NULL) {
continue;
}
#if defined(SHARED_EP_ENABLE)
if (ep == USB_ENDPOINT_IN_SHARED) {
for (int report_id = 1; report_id <= REPORT_ID_COUNT; report_id++) {
osalSysLock();
non_zero_idle_rate_found |= report_storage->get_idle(report_storage->reports, report_id) != 0;
osalSysUnlock();
if (usb_endpoint_in_is_inactive(&usb_endpoints_in[ep]) && report_storage->idle_timer_elasped(report_storage->reports, report_id)) {
osalSysLock();
report_storage->get_report(report_storage->reports, report_id, &report);
osalSysUnlock();
send_report(ep, &report.data, report.length);
}
}
continue;
}
#endif
osalSysLock();
non_zero_idle_rate_found |= report_storage->get_idle(report_storage->reports, 0) != 0;
osalSysUnlock();
if (usb_endpoint_in_is_inactive(&usb_endpoints_in[ep]) && report_storage->idle_timer_elasped(report_storage->reports, 0)) {
osalSysLock();
report_storage->get_report(report_storage->reports, 0, &report);
osalSysUnlock();
send_report(ep, &report.data, report.length);
}
}
run_idle_task = non_zero_idle_rate_found;
}
bool usb_get_idle_cb(USBDriver *driver) {
usb_control_request_t *setup = (usb_control_request_t *)driver->setup;
uint8_t interface = setup->wIndex;
uint8_t report_id = setup->wValue.lbyte;
static uint8_t _Alignas(4) idle_rate;
if (!IS_VALID_INTERFACE(interface) || !IS_VALID_REPORT_ID(report_id)) {
return false;
}
usb_endpoint_in_lut_t ep = usb_endpoint_interface_lut[interface];
if (!IS_VALID_USB_ENDPOINT_IN_LUT(ep)) {
return false;
}
usb_report_storage_t *report_storage = usb_endpoints_in[ep].report_storage;
if (report_storage == NULL) {
return false;
}
idle_rate = report_storage->get_idle(report_storage->reports, report_id);
usbSetupTransfer(driver, &idle_rate, 1, NULL);
return true;
}
bool usb_set_idle_cb(USBDriver *driver) {
usb_control_request_t *setup = (usb_control_request_t *)driver->setup;
uint8_t interface = setup->wIndex;
uint8_t report_id = setup->wValue.lbyte;
uint8_t idle_rate = setup->wValue.hbyte;
if (!IS_VALID_INTERFACE(interface) || !IS_VALID_REPORT_ID(report_id)) {
return false;
}
usb_endpoint_in_lut_t ep = usb_endpoint_interface_lut[interface];
if (!IS_VALID_USB_ENDPOINT_IN_LUT(ep)) {
return false;
}
usb_report_storage_t *report_storage = usb_endpoints_in[ep].report_storage;
if (report_storage == NULL) {
return false;
}
report_storage->set_idle(report_storage->reports, report_id, idle_rate);
usbSetupTransfer(driver, NULL, 0, NULL);
return true;
}

View File

@ -0,0 +1,77 @@
// Copyright 2023 Stefan Kerkmann (@KarlK90)
// SPDX-License-Identifier: GPL-3.0-or-later
#pragma once
#include <ch.h>
#include <hal.h>
#include <stdint.h>
#include "timer.h"
typedef struct {
time_msecs_t idle_rate;
systime_t last_report;
uint8_t data[64];
size_t length;
} usb_fs_report_t;
typedef struct {
usb_fs_report_t **reports;
const void (*get_report)(usb_fs_report_t **, uint8_t, usb_fs_report_t *);
const void (*set_report)(usb_fs_report_t **, const uint8_t *, size_t);
const void (*reset_report)(usb_fs_report_t **);
const void (*set_idle)(usb_fs_report_t **, uint8_t, uint8_t);
const uint8_t (*get_idle)(usb_fs_report_t **, uint8_t);
const bool (*idle_timer_elasped)(usb_fs_report_t **, uint8_t);
} usb_report_storage_t;
#define QMK_USB_REPORT_STROAGE_ENTRY(_report_id, _report_size) [_report_id] = &((usb_fs_report_t){.data = {[0] = _report_id}, .length = _report_size})
#define QMK_USB_REPORT_STORAGE(_get_report, _set_report, _reset_report, _get_idle, _set_idle, _idle_timer_elasped, _report_count, _reports...) \
&((usb_report_storage_t){ \
.reports = (_Alignas(4) usb_fs_report_t *[_report_count]){_reports}, \
.get_report = _get_report, \
.set_report = _set_report, \
.reset_report = _reset_report, \
.get_idle = _get_idle, \
.set_idle = _set_idle, \
.idle_timer_elasped = _idle_timer_elasped, \
})
#define QMK_USB_REPORT_STORAGE_DEFAULT(_report_length) \
QMK_USB_REPORT_STORAGE(&usb_get_report, /* _get_report */ \
&usb_set_report, /* _set_report */ \
&usb_reset_report, /* _reset_report */ \
&usb_get_idle_rate, /* _get_idle */ \
&usb_set_idle_rate, /* _set_idle */ \
&usb_idle_timer_elapsed, /* _idle_timer_elasped */ \
1, /* _report_count */ \
QMK_USB_REPORT_STROAGE_ENTRY(0, _report_length))
// USB HID SET_REPORT and GET_REPORT handling functions
void usb_set_report(usb_fs_report_t **reports, const uint8_t *data, size_t length);
void usb_shared_set_report(usb_fs_report_t **reports, const uint8_t *data, size_t length);
void usb_get_report(usb_fs_report_t **reports, uint8_t report_id, usb_fs_report_t *report);
void usb_shared_get_report(usb_fs_report_t **reports, uint8_t report_id, usb_fs_report_t *report);
void usb_reset_report(usb_fs_report_t **reports);
void usb_shared_reset_report(usb_fs_report_t **reports);
bool usb_get_report_cb(USBDriver *driver);
// USB HID SET_IDLE and GET_IDLE handling functions
void usb_idle_task(void);
void usb_set_idle_rate(usb_fs_report_t **timers, uint8_t report_id, uint8_t idle_rate);
void usb_shared_set_idle_rate(usb_fs_report_t **timers, uint8_t report_id, uint8_t idle_rate);
uint8_t usb_get_idle_rate(usb_fs_report_t **timers, uint8_t report_id);
uint8_t usb_shared_get_idle_rate(usb_fs_report_t **timers, uint8_t report_id);
bool usb_idle_timer_elapsed(usb_fs_report_t **timers, uint8_t report_id);
bool usb_shared_idle_timer_elapsed(usb_fs_report_t **timers, uint8_t report_id);
bool usb_get_idle_cb(USBDriver *driver);
bool usb_set_idle_cb(USBDriver *driver);

View File

@ -30,6 +30,7 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
/* HID report IDs */
enum hid_report_ids {
REPORT_ID_ALL = 0,
REPORT_ID_KEYBOARD = 1,
REPORT_ID_MOUSE,
REPORT_ID_SYSTEM,
@ -37,9 +38,12 @@ enum hid_report_ids {
REPORT_ID_PROGRAMMABLE_BUTTON,
REPORT_ID_NKRO,
REPORT_ID_JOYSTICK,
REPORT_ID_DIGITIZER
REPORT_ID_DIGITIZER,
REPORT_ID_COUNT = REPORT_ID_DIGITIZER
};
#define IS_VALID_REPORT_ID(id) ((id) >= REPORT_ID_ALL && (id) <= REPORT_ID_COUNT)
/* Mouse buttons */
#define MOUSE_BTN_MASK(n) (1 << (n))
enum mouse_buttons {

View File

@ -196,6 +196,8 @@ enum usb_interfaces {
TOTAL_INTERFACES
};
#define IS_VALID_INTERFACE(i) ((i) >= 0 && (i) < TOTAL_INTERFACES)
#define NEXT_EPNUM __COUNTER__
/*