Pick upstream PR #21656

This commit is contained in:
lokher 2024-05-21 12:08:19 +08:00
parent dda14805a1
commit b6d42d84d2
13 changed files with 1292 additions and 1158 deletions

View file

@ -192,14 +192,18 @@ void protocol_pre_task(void) {
/* Remote wakeup */ /* Remote wakeup */
if ((USB_DRIVER.status & USB_GETSTATUS_REMOTE_WAKEUP_ENABLED) && suspend_wakeup_condition()) { if ((USB_DRIVER.status & USB_GETSTATUS_REMOTE_WAKEUP_ENABLED) && suspend_wakeup_condition()) {
usbWakeupHost(&USB_DRIVER); usbWakeupHost(&USB_DRIVER);
# if USB_SUSPEND_WAKEUP_DELAY > 0
// Some hubs, kvm switches, and monitors do
// weird things, with USB device state bouncing
// around wildly on wakeup, yielding race
// conditions that can corrupt the keyboard state.
//
// Pause for a while to let things settle...
wait_ms(USB_SUSPEND_WAKEUP_DELAY);
# endif
} }
} }
/* Woken up */ /* Woken up */
// variables has been already cleared by the wakeup hook
send_keyboard_report();
# ifdef MOUSEKEY_ENABLE
mousekey_send();
# endif /* MOUSEKEY_ENABLE */
} }
#endif #endif
} }
@ -217,4 +221,5 @@ void protocol_post_task(void) {
#ifdef RAW_ENABLE #ifdef RAW_ENABLE
raw_hid_task(); raw_hid_task();
#endif #endif
usb_idle_task();
} }

View file

@ -6,6 +6,8 @@ SRC += $(CHIBIOS_DIR)/usb_main.c
SRC += $(CHIBIOS_DIR)/chibios.c SRC += $(CHIBIOS_DIR)/chibios.c
SRC += usb_descriptor.c SRC += usb_descriptor.c
SRC += $(CHIBIOS_DIR)/usb_driver.c SRC += $(CHIBIOS_DIR)/usb_driver.c
SRC += $(CHIBIOS_DIR)/usb_endpoints.c
SRC += $(CHIBIOS_DIR)/usb_report_handling.c
SRC += $(CHIBIOS_DIR)/usb_util.c SRC += $(CHIBIOS_DIR)/usb_util.c
SRC += $(LIBSRC) SRC += $(LIBSRC)

View file

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

View file

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

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;

View file

@ -1,45 +1,32 @@
/* // Copyright 2023 Stefan Kerkmann
* (c) 2015 flabberast <s3+flabbergast@sdfeu.org> // Copyright 2020-2021 Ryan (@fauxpark)
* // Copyright 2020 Nick Brassel (@tzarc)
* Based on the following work: // Copyright 2020 a-chol
* - Guillaume Duc's raw hid example (MIT License) // Copyright 2020 xyzz
* https://github.com/guiduc/usb-hid-chibios-example // Copyright 2020 Joel Challis (@zvecr)
* - PJRC Teensy examples (MIT License) // Copyright 2020 George (@goshdarnharris)
* https://www.pjrc.com/teensy/usb_keyboard.html // Copyright 2018 James Laird-Wah
* - hasu's TMK keyboard code (GPL v2 and some code Modified BSD) // Copyright 2018 Drashna Jaelre (@drashna)
* https://github.com/tmk/tmk_keyboard/ // Copyright 2016 Fredizzimo
* - ChibiOS demo code (Apache 2.0 License) // Copyright 2016 Giovanni Di Sirio
* http://www.chibios.org // SPDX-License-Identifier: GPL-3.0-or-later OR Apache-2.0
*
* 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.
*/
#include <ch.h> #include <ch.h>
#include <hal.h> #include <hal.h>
#include <string.h> #include <string.h>
#include "usb_main.h" #include "usb_main.h"
#include "usb_report_handling.h"
#include "host.h" #include "host.h"
#include "chibios_config.h"
#include "debug.h"
#include "suspend.h" #include "suspend.h"
#include "timer.h"
#ifdef SLEEP_LED_ENABLE #ifdef SLEEP_LED_ENABLE
# include "sleep_led.h" # include "sleep_led.h"
# include "led.h" # include "led.h"
#endif #endif
#include "wait.h" #include "wait.h"
#include "usb_endpoints.h"
#include "usb_device_state.h" #include "usb_device_state.h"
#include "usb_descriptor.h" #include "usb_descriptor.h"
#include "usb_driver.h" #include "usb_driver.h"
@ -64,33 +51,16 @@ extern keymap_config_t keymap_config;
# define usb_lld_disconnect_bus(usbp) # define usb_lld_disconnect_bus(usbp)
#endif #endif
uint8_t keyboard_idle __attribute__((aligned(2))) = 0; extern usb_endpoint_in_t usb_endpoints_in[USB_ENDPOINT_IN_COUNT];
uint8_t keyboard_protocol __attribute__((aligned(2))) = 1; extern usb_endpoint_out_t usb_endpoints_out[USB_ENDPOINT_OUT_COUNT];
uint8_t _Alignas(2) keyboard_idle = 0;
uint8_t _Alignas(2) keyboard_protocol = 1;
uint8_t keyboard_led_state = 0; 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); 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);
report_keyboard_t keyboard_report_sent = {0}; static bool __attribute__((__unused__)) receive_report(usb_endpoint_out_lut_t endpoint, void *report, size_t size);
report_mouse_t mouse_report_sent = {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};
/* --------------------------------------------------------- /* ---------------------------------------------------------
* Descriptors and USB driver objects * Descriptors and USB driver objects
@ -104,6 +74,11 @@ union {
NULL, /* SETUP buffer (not a SETUP endpoint) */ NULL, /* SETUP buffer (not a SETUP endpoint) */
#endif #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) { 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; usb_control_request_t *setup = (usb_control_request_t *)usbp->setup;
@ -118,291 +93,6 @@ static const USBDescriptor *usb_get_descriptor_cb(USBDriver *usbp, uint8_t dtype
return &descriptor; 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 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 CONSOLE_ENABLE
usb_driver_config_t console_driver;
#endif
#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 CONSOLE_ENABLE
# define CONSOLE_IN_CAPACITY 4
# define CONSOLE_OUT_CAPACITY 4
# define CONSOLE_IN_MODE USB_EP_MODE_TYPE_INTR
# define CONSOLE_OUT_MODE USB_EP_MODE_TYPE_INTR
.console_driver = QMK_USB_DRIVER_CONFIG(CONSOLE, 0, true),
#endif
#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 * USB driver functions
* --------------------------------------------------------- * ---------------------------------------------------------
@ -494,33 +184,11 @@ static void usb_event_cb(USBDriver *usbp, usbevent_t event) {
case USB_EVENT_CONFIGURED: case USB_EVENT_CONFIGURED:
osalSysLockFromISR(); osalSysLockFromISR();
/* Enable the endpoints specified into the configuration. */ for (int i = 0; i < USB_ENDPOINT_IN_COUNT; i++) {
#ifndef KEYBOARD_SHARED_EP usb_endpoint_in_configure_cb(&usb_endpoints_in[i]);
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
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_OUT_COUNT; i++) {
usb_endpoint_out_configure_cb(&usb_endpoints_out[i]);
} }
osalSysUnlockFromISR(); osalSysUnlockFromISR();
if (last_suspend_state) { if (last_suspend_state) {
@ -534,22 +202,25 @@ static void usb_event_cb(USBDriver *usbp, usbevent_t event) {
/* Falls into.*/ /* Falls into.*/
case USB_EVENT_RESET: case USB_EVENT_RESET:
usb_event_queue_enqueue(event); usb_event_queue_enqueue(event);
for (int i = 0; i < NUM_USB_DRIVERS; i++) {
chSysLockFromISR(); chSysLockFromISR();
/* Disconnection event on suspend.*/ for (int i = 0; i < USB_ENDPOINT_IN_COUNT; i++) {
qmkusbSuspendHookI(&drivers.array[i].driver); usb_endpoint_in_suspend_cb(&usb_endpoints_in[i]);
chSysUnlockFromISR();
} }
for (int i = 0; i < USB_ENDPOINT_OUT_COUNT; i++) {
usb_endpoint_out_suspend_cb(&usb_endpoints_out[i]);
}
chSysUnlockFromISR();
return; return;
case USB_EVENT_WAKEUP: case USB_EVENT_WAKEUP:
// TODO: from ISR! print("[W]");
for (int i = 0; i < NUM_USB_DRIVERS; i++) {
chSysLockFromISR(); chSysLockFromISR();
/* Disconnection event on suspend.*/ for (int i = 0; i < USB_ENDPOINT_IN_COUNT; i++) {
qmkusbWakeupHookI(&drivers.array[i].driver); usb_endpoint_in_wakeup_cb(&usb_endpoints_in[i]);
chSysUnlockFromISR();
} }
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); usb_event_queue_enqueue(USB_EVENT_WAKEUP);
return; return;
@ -571,7 +242,7 @@ static void usb_event_cb(USBDriver *usbp, usbevent_t event) {
* Other Device Required Optional Optional Optional Optional Optional * 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) { static void set_led_transfer_cb(USBDriver *usbp) {
usb_control_request_t *setup = (usb_control_request_t *)usbp->setup; usb_control_request_t *setup = (usb_control_request_t *)usbp->setup;
@ -595,41 +266,7 @@ static bool usb_requests_hook_cb(USBDriver *usbp) {
case USB_RTYPE_DIR_DEV2HOST: case USB_RTYPE_DIR_DEV2HOST:
switch (setup->bRequest) { switch (setup->bRequest) {
case HID_REQ_GetReport: case HID_REQ_GetReport:
switch (setup->wIndex) { return usb_get_report_cb(usbp);
#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;
case HID_REQ_GetProtocol: case HID_REQ_GetProtocol:
if (setup->wIndex == KEYBOARD_INTERFACE) { if (setup->wIndex == KEYBOARD_INTERFACE) {
usbSetupTransfer(usbp, &keyboard_protocol, sizeof(uint8_t), NULL); usbSetupTransfer(usbp, &keyboard_protocol, sizeof(uint8_t), NULL);
@ -638,10 +275,8 @@ static bool usb_requests_hook_cb(USBDriver *usbp) {
break; break;
case HID_REQ_GetIdle: case HID_REQ_GetIdle:
usbSetupTransfer(usbp, &keyboard_idle, sizeof(uint8_t), NULL); return usb_get_idle_cb(usbp);
return true;
} }
break;
case USB_RTYPE_DIR_HOST2DEV: case USB_RTYPE_DIR_HOST2DEV:
switch (setup->bRequest) { switch (setup->bRequest) {
@ -655,38 +290,15 @@ static bool usb_requests_hook_cb(USBDriver *usbp) {
return true; return true;
} }
break; break;
case HID_REQ_SetProtocol: case HID_REQ_SetProtocol:
if (setup->wIndex == KEYBOARD_INTERFACE) { if (setup->wIndex == KEYBOARD_INTERFACE) {
keyboard_protocol = setup->wValue.word; 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); usbSetupTransfer(usbp, NULL, 0, NULL);
return true; return true;
case HID_REQ_SetIdle: case HID_REQ_SetIdle:
keyboard_idle = setup->wValue.hbyte; keyboard_idle = setup->wValue.hbyte;
/* arm the timer */ return usb_set_idle_cb(usbp);
#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;
} }
break; break;
} }
@ -703,52 +315,40 @@ static bool usb_requests_hook_cb(USBDriver *usbp) {
return true; return true;
} }
for (int i = 0; i < NUM_USB_DRIVERS; i++) { for (int i = 0; i < USB_ENDPOINT_IN_COUNT; i++) {
if (drivers.array[i].config.int_in) { if (usb_endpoints_in[i].usb_requests_cb != NULL) {
// NOTE: Assumes that we only have one serial driver if (usb_endpoints_in[i].usb_requests_cb(usbp)) {
return qmkusbRequestsHook(usbp); return true;
}
} }
} }
return false; return false;
} }
static void usb_sof_cb(USBDriver *usbp) { static __attribute__((unused)) void dummy_cb(USBDriver *usbp) {
osalSysLockFromISR(); (void)usbp;
for (int i = 0; i < NUM_USB_DRIVERS; i++) {
qmkusbSOFHookI(&drivers.array[i].driver);
}
osalSysUnlockFromISR();
} }
/* USB driver configuration */
static const USBConfig usbcfg = { static const USBConfig usbcfg = {
usb_event_cb, /* USB events callback */ usb_event_cb, /* USB events callback */
usb_get_descriptor_cb, /* Device GET_DESCRIPTOR request callback */ usb_get_descriptor_cb, /* Device GET_DESCRIPTOR request callback */
usb_requests_hook_cb, /* Requests hook 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) { void init_usb_driver(USBDriver *usbp) {
for (int i = 0; i < NUM_USB_DRIVERS; i++) { for (int i = 0; i < USB_ENDPOINT_IN_COUNT; i++) {
#ifdef USB_ENDPOINTS_ARE_REORDERABLE usb_endpoint_in_init(&usb_endpoints_in[i]);
QMKUSBDriver *driver = &drivers.array[i].driver; usb_endpoint_in_start(&usb_endpoints_in[i]);
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; for (int i = 0; i < USB_ENDPOINT_OUT_COUNT; i++) {
qmkusbObjectInit(driver, &drivers.array[i].config); usb_endpoint_out_init(&usb_endpoints_out[i]);
qmkusbStart(driver, &drivers.array[i].config); usb_endpoint_out_start(&usb_endpoints_out[i]);
#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
} }
/* /*
@ -761,8 +361,6 @@ void init_usb_driver(USBDriver *usbp) {
wait_ms(50); wait_ms(50);
usbStart(usbp, &usbcfg); usbStart(usbp, &usbcfg);
usbConnectBus(usbp); usbConnectBus(usbp);
chVTObjectInit(&keyboard_idle_timer);
} }
__attribute__((weak)) void usb_start(USBDriver *usbp) { __attribute__((weak)) void usb_start(USBDriver *usbp) {
@ -775,81 +373,78 @@ __attribute__((weak)) void usb_start(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 */ /* LED status */
uint8_t keyboard_leds(void) { uint8_t keyboard_leds(void) {
return keyboard_led_state; return keyboard_led_state;
} }
void send_report(uint8_t endpoint, void *report, size_t size) { /**
osalSysLock(); * @brief Send a report to the host, the report is enqueued into an output
if (usbGetDriverStateI(&USB_DRIVER) != USB_ACTIVE) { * queue and send once the USB endpoint becomes empty.
osalSysUnlock(); *
return; * @param endpoint USB IN endpoint to send the report from
} * @param report pointer to the report
* @param size size of the report
if (usbGetTransmitStatusI(&USB_DRIVER, endpoint)) { * @return true Success
/* Need to either suspend, or loop and call unlock/lock during * @return false Failure
* every iteration - otherwise the system will remain locked, */
* no interrupts served, so USB not going through as well. bool send_report(usb_endpoint_in_lut_t endpoint, void *report, size_t size) {
* Note: for suspend, need USB_USE_WAIT == TRUE in halconf.h */ return usb_endpoint_in_send(&usb_endpoints_in[endpoint], (uint8_t *)report, size, TIME_MS2I(100), false);
if (osalThreadSuspendTimeoutS(&(&USB_DRIVER)->epc[endpoint]->in_state->thread, TIME_MS2I(10)) == MSG_TIMEOUT) { }
osalSysUnlock();
return; /**
} * @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
usbStartTransmitI(&USB_DRIVER, endpoint, report, size); * a call to `flush_report_buffered`. This is useful if the report is being
osalSysUnlock(); * 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) { void send_keyboard(report_keyboard_t *report) {
/* If we're in Boot Protocol, don't send any report ID or other funky fields */ /* If we're in Boot Protocol, don't send any report ID or other funky fields */
if (!keyboard_protocol) { if (!keyboard_protocol) {
send_report(KEYBOARD_IN_EPNUM, &report->mods, 8); send_report(USB_ENDPOINT_IN_KEYBOARD, &report->mods, 8);
} else { } 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) { void send_nkro(report_nkro_t *report) {
#ifdef NKRO_ENABLE #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 #endif
} }
@ -860,8 +455,7 @@ void send_nkro(report_nkro_t *report) {
void send_mouse(report_mouse_t *report) { void send_mouse(report_mouse_t *report) {
#ifdef MOUSE_ENABLE #ifdef MOUSE_ENABLE
send_report(MOUSE_IN_EPNUM, report, sizeof(report_mouse_t)); send_report(USB_ENDPOINT_IN_MOUSE, report, sizeof(report_mouse_t));
mouse_report_sent = *report;
#endif #endif
} }
@ -872,25 +466,25 @@ void send_mouse(report_mouse_t *report) {
void send_extra(report_extra_t *report) { void send_extra(report_extra_t *report) {
#ifdef EXTRAKEY_ENABLE #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 #endif
} }
void send_programmable_button(report_programmable_button_t *report) { void send_programmable_button(report_programmable_button_t *report) {
#ifdef PROGRAMMABLE_BUTTON_ENABLE #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 #endif
} }
void send_joystick(report_joystick_t *report) { void send_joystick(report_joystick_t *report) {
#ifdef JOYSTICK_ENABLE #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 #endif
} }
void send_digitizer(report_digitizer_t *report) { void send_digitizer(report_digitizer_t *report) {
#ifdef DIGITIZER_ENABLE #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 #endif
} }
@ -902,61 +496,21 @@ void send_digitizer(report_digitizer_t *report) {
#ifdef CONSOLE_ENABLE #ifdef CONSOLE_ENABLE
int8_t sendchar(uint8_t c) { int8_t sendchar(uint8_t c) {
static bool timed_out = false; return (int8_t)send_report_buffered(USB_ENDPOINT_IN_CONSOLE, &c, sizeof(uint8_t));
/* The `timed_out` state is an approximation of the ideal `is_listener_disconnected?` state.
*
* When a 5ms timeout write has timed out, hid_listen is most likely not running, or not
* listening to this keyboard, so we go into the timed_out state. In this state we assume
* that hid_listen is most likely not gonna be connected to us any time soon, so it would
* be wasteful to write follow-up characters with a 5ms timeout, it would all add up and
* unncecessarily slow down the firmware. However instead of just dropping the characters,
* we write them with a TIME_IMMEDIATE timeout, which is a zero timeout,
* and this will succeed only if hid_listen gets connected again. When a write with
* TIME_IMMEDIATE timeout succeeds, we know that hid_listen is listening to us again, and
* we can go back to the timed_out = false state, and following writes will be executed
* with a 5ms timeout. The reason we don't just send all characters with the TIME_IMMEDIATE
* timeout is that this could cause bytes to be lost even if hid_listen is running, if there
* is a lot of data being sent over the console.
*
* This logic will work correctly as long as hid_listen is able to receive at least 200
* bytes per second. On a heavily overloaded machine that's so overloaded that it's
* unusable, and constantly swapping, hid_listen might have trouble receiving 200 bytes per
* second, so some bytes might be lost on the console.
*/
const sysinterval_t timeout = timed_out ? TIME_IMMEDIATE : TIME_MS2I(5);
const size_t result = chnWriteTimeout(&drivers.console_driver.driver, &c, 1, timeout);
timed_out = (result == 0);
return result;
}
// Just a dummy function for now, this could be exposed as a weak function
// Or connected to the actual QMK console
static void console_receive(uint8_t *data, uint8_t length) {
(void)data;
(void)length;
} }
void console_task(void) { void console_task(void) {
uint8_t buffer[CONSOLE_EPSIZE]; flush_report_buffered(USB_ENDPOINT_IN_CONSOLE, true);
size_t size = 0;
do {
size = chnReadTimeout(&drivers.console_driver.driver, buffer, sizeof(buffer), TIME_IMMEDIATE);
if (size > 0) {
console_receive(buffer, size);
}
} while (size > 0);
} }
#endif /* CONSOLE_ENABLE */ #endif /* CONSOLE_ENABLE */
#ifdef RAW_ENABLE #ifdef RAW_ENABLE
void raw_hid_send(uint8_t *data, uint8_t length) { void raw_hid_send(uint8_t *data, uint8_t length) {
// TODO: implement variable size packet
if (length != RAW_EPSIZE) { if (length != RAW_EPSIZE) {
return; 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) { __attribute__((weak)) void raw_hid_receive(uint8_t *data, uint8_t length) {
@ -967,13 +521,9 @@ __attribute__((weak)) void raw_hid_receive(uint8_t *data, uint8_t length) {
void raw_hid_task(void) { void raw_hid_task(void) {
uint8_t buffer[RAW_EPSIZE]; uint8_t buffer[RAW_EPSIZE];
size_t size = 0; while (receive_report(USB_ENDPOINT_OUT_RAW, buffer, sizeof(buffer))) {
do { raw_hid_receive(buffer, sizeof(buffer));
size = chnReadTimeout(&drivers.raw_driver.driver, buffer, sizeof(buffer), TIME_IMMEDIATE);
if (size > 0) {
raw_hid_receive(buffer, size);
} }
} while (size > 0);
} }
#endif #endif
@ -981,32 +531,59 @@ void raw_hid_task(void) {
#ifdef MIDI_ENABLE #ifdef MIDI_ENABLE
void send_midi_packet(MIDI_EventPacket_t *event) { 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) { 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 receive_report(USB_ENDPOINT_OUT_MIDI, (uint8_t *)event, sizeof(MIDI_EventPacket_t));
return size == sizeof(MIDI_EventPacket_t);
} }
void midi_ep_task(void) { void midi_ep_task(void) {
uint8_t buffer[MIDI_STREAM_EPSIZE]; uint8_t buffer[MIDI_STREAM_EPSIZE];
size_t size = 0; while (receive_report(USB_ENDPOINT_OUT_MIDI, buffer, sizeof(buffer))) {
do {
size = chnReadTimeout(&drivers.midi_driver.driver, buffer, sizeof(buffer), TIME_IMMEDIATE);
if (size > 0) {
MIDI_EventPacket_t event; 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); recv_midi_packet(&event);
} }
} while (size > 0);
} }
#endif #endif
#ifdef VIRTSER_ENABLE #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_init(void) {}
void virtser_send(const uint8_t byte) { 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) { __attribute__((weak)) void virtser_recv(uint8_t c) {
@ -1014,14 +591,14 @@ __attribute__((weak)) void virtser_recv(uint8_t c) {
} }
void virtser_task(void) { void virtser_task(void) {
uint8_t numBytesReceived = 0; uint8_t buffer[CDC_EPSIZE];
uint8_t buffer[16]; while (receive_report(USB_ENDPOINT_OUT_CDC_DATA, buffer, sizeof(buffer))) {
do { for (int i = 0; i < sizeof(buffer); i++) {
numBytesReceived = chnReadTimeout(&drivers.serial_driver.driver, buffer, sizeof(buffer), TIME_IMMEDIATE);
for (int i = 0; i < numBytesReceived; i++) {
virtser_recv(buffer[i]); virtser_recv(buffer[i]);
} }
} while (numBytesReceived > 0); }
flush_report_buffered(USB_ENDPOINT_IN_CDC_DATA, false);
} }
#endif #endif

View file

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

@ -26,6 +26,7 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
/* HID report IDs */ /* HID report IDs */
enum hid_report_ids { enum hid_report_ids {
REPORT_ID_ALL = 0,
REPORT_ID_KEYBOARD = 1, REPORT_ID_KEYBOARD = 1,
REPORT_ID_MOUSE, REPORT_ID_MOUSE,
REPORT_ID_SYSTEM, REPORT_ID_SYSTEM,
@ -33,9 +34,12 @@ enum hid_report_ids {
REPORT_ID_PROGRAMMABLE_BUTTON, REPORT_ID_PROGRAMMABLE_BUTTON,
REPORT_ID_NKRO, REPORT_ID_NKRO,
REPORT_ID_JOYSTICK, REPORT_ID_JOYSTICK,
REPORT_ID_DIGITIZER REPORT_ID_DIGITIZER,
REPORT_ID_COUNT = REPORT_ID_DIGITIZER
}; };
#define IS_VALID_REPORT_ID(id) ((id) >= REPORT_ID_ALL && (id) <= REPORT_ID_COUNT)
#ifdef APDAPTIVE_NKRO_ENABLE #ifdef APDAPTIVE_NKRO_ENABLE
/* Keyboard report type */ /* Keyboard report type */
#define KB_RPT_MASK(n) (1 << (n)) #define KB_RPT_MASK(n) (1 << (n))

View file

@ -420,14 +420,6 @@ const USB_Descriptor_HIDReport_Datatype_t PROGMEM ConsoleReport[] = {
HID_RI_REPORT_COUNT(8, CONSOLE_EPSIZE), HID_RI_REPORT_COUNT(8, CONSOLE_EPSIZE),
HID_RI_REPORT_SIZE(8, 0x08), HID_RI_REPORT_SIZE(8, 0x08),
HID_RI_INPUT(8, HID_IOF_DATA | HID_IOF_VARIABLE | HID_IOF_ABSOLUTE), HID_RI_INPUT(8, HID_IOF_DATA | HID_IOF_VARIABLE | HID_IOF_ABSOLUTE),
// Data from host
HID_RI_USAGE(8, 0x76), // Vendor Defined
HID_RI_LOGICAL_MINIMUM(8, 0x00),
HID_RI_LOGICAL_MAXIMUM(16, 0x00FF),
HID_RI_REPORT_COUNT(8, CONSOLE_EPSIZE),
HID_RI_REPORT_SIZE(8, 0x08),
HID_RI_OUTPUT(8, HID_IOF_DATA | HID_IOF_VARIABLE | HID_IOF_ABSOLUTE | HID_IOF_NON_VOLATILE),
HID_RI_END_COLLECTION(0), HID_RI_END_COLLECTION(0),
}; };
#endif #endif
@ -677,7 +669,7 @@ const USB_Descriptor_Configuration_t PROGMEM ConfigurationDescriptor = {
}, },
.InterfaceNumber = CONSOLE_INTERFACE, .InterfaceNumber = CONSOLE_INTERFACE,
.AlternateSetting = 0x00, .AlternateSetting = 0x00,
.TotalEndpoints = 2, .TotalEndpoints = 1,
.Class = HID_CSCP_HIDClass, .Class = HID_CSCP_HIDClass,
.SubClass = HID_CSCP_NonBootSubclass, .SubClass = HID_CSCP_NonBootSubclass,
.Protocol = HID_CSCP_NonBootProtocol, .Protocol = HID_CSCP_NonBootProtocol,
@ -704,16 +696,6 @@ const USB_Descriptor_Configuration_t PROGMEM ConfigurationDescriptor = {
.EndpointSize = CONSOLE_EPSIZE, .EndpointSize = CONSOLE_EPSIZE,
.PollingIntervalMS = 0x01 .PollingIntervalMS = 0x01
}, },
.Console_OUTEndpoint = {
.Header = {
.Size = sizeof(USB_Descriptor_Endpoint_t),
.Type = DTYPE_Endpoint
},
.EndpointAddress = (ENDPOINT_DIR_OUT | CONSOLE_OUT_EPNUM),
.Attributes = (EP_TYPE_INTERRUPT | ENDPOINT_ATTR_NO_SYNC | ENDPOINT_USAGE_DATA),
.EndpointSize = CONSOLE_EPSIZE,
.PollingIntervalMS = 0x01
},
#endif #endif
#ifdef MIDI_ENABLE #ifdef MIDI_ENABLE

View file

@ -97,7 +97,6 @@ typedef struct {
USB_Descriptor_Interface_t Console_Interface; USB_Descriptor_Interface_t Console_Interface;
USB_HID_Descriptor_HID_t Console_HID; USB_HID_Descriptor_HID_t Console_HID;
USB_Descriptor_Endpoint_t Console_INEndpoint; USB_Descriptor_Endpoint_t Console_INEndpoint;
USB_Descriptor_Endpoint_t Console_OUTEndpoint;
#endif #endif
#ifdef MIDI_ENABLE #ifdef MIDI_ENABLE
@ -197,6 +196,8 @@ enum usb_interfaces {
TOTAL_INTERFACES TOTAL_INTERFACES
}; };
#define IS_VALID_INTERFACE(i) ((i) >= 0 && (i) < TOTAL_INTERFACES)
#define NEXT_EPNUM __COUNTER__ #define NEXT_EPNUM __COUNTER__
/* /*
@ -232,19 +233,6 @@ enum usb_endpoints {
#ifdef CONSOLE_ENABLE #ifdef CONSOLE_ENABLE
CONSOLE_IN_EPNUM = NEXT_EPNUM, CONSOLE_IN_EPNUM = NEXT_EPNUM,
# ifdef PROTOCOL_CHIBIOS
// ChibiOS has enough memory and descriptor to actually enable the endpoint
// It could use the same endpoint numbers, as that's supported by ChibiOS
// But the QMK code currently assumes that the endpoint numbers are different
# ifdef USB_ENDPOINTS_ARE_REORDERABLE
# define CONSOLE_OUT_EPNUM CONSOLE_IN_EPNUM
# else
CONSOLE_OUT_EPNUM = NEXT_EPNUM,
# endif
# else
# define CONSOLE_OUT_EPNUM CONSOLE_IN_EPNUM
# endif
#endif #endif
#ifdef MIDI_ENABLE #ifdef MIDI_ENABLE