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