Add a custom USB driver for ARM (#2750) 0.6.10
authorfredizzimo <fsundvik@gmail.com>
Mon, 16 Apr 2018 00:42:53 +0000 (03:42 +0300)
committerJack Humbert <jack.humb@gmail.com>
Mon, 16 Apr 2018 00:42:53 +0000 (20:42 -0400)
* Copy Chibios serial_usb_driver into the chibios/protocol

It's renamed to usb_driver to avoid name conflicts

* Make the usb driver compile

* Disable ChibiOS serial usb driver for all keyboards

* Change usb_main to use QMKUSBDriver

* Initialize the usb driver buffers

* Add support for fixed size queues

* Fix USB driver initialization

* Don't transfer an empty packet for fixed size streams

13 files changed:
keyboards/chibios_test/stm32_f072_onekey/halconf.h
keyboards/chibios_test/stm32_f103_onekey/halconf.h
keyboards/chibios_test/teensy_lc_onekey/halconf.h
keyboards/clueboard/60/halconf.h
keyboards/ergodox_infinity/halconf.h
keyboards/infinity60/halconf.h
keyboards/jm60/halconf.h
keyboards/k_type/halconf.h
keyboards/whitefox/halconf.h
tmk_core/protocol/chibios.mk
tmk_core/protocol/chibios/usb_driver.c [new file with mode: 0644]
tmk_core/protocol/chibios/usb_driver.h [new file with mode: 0644]
tmk_core/protocol/chibios/usb_main.c

index 7625725..8b9724b 100644 (file)
  * @brief   Enables the SERIAL over USB subsystem.
  */
 #if !defined(HAL_USE_SERIAL_USB) || defined(__DOXYGEN__)
-#define HAL_USE_SERIAL_USB          TRUE
+#define HAL_USE_SERIAL_USB          FALSE
 #endif
 
 /**
index 7625725..8b9724b 100644 (file)
  * @brief   Enables the SERIAL over USB subsystem.
  */
 #if !defined(HAL_USE_SERIAL_USB) || defined(__DOXYGEN__)
-#define HAL_USE_SERIAL_USB          TRUE
+#define HAL_USE_SERIAL_USB          FALSE
 #endif
 
 /**
index 5e1f6a8..1b6f2ad 100644 (file)
  * @brief   Enables the SERIAL over USB subsystem.
  */
 #if !defined(HAL_USE_SERIAL_USB) || defined(__DOXYGEN__)
-#define HAL_USE_SERIAL_USB          TRUE
+#define HAL_USE_SERIAL_USB          FALSE
 #endif
 
 /**
index 8fe8e0c..e617fdf 100644 (file)
  * @brief   Enables the SERIAL over USB subsystem.
  */
 #if !defined(HAL_USE_SERIAL_USB) || defined(__DOXYGEN__)
-#define HAL_USE_SERIAL_USB          TRUE
+#define HAL_USE_SERIAL_USB          FALSE
 #endif
 
 /**
index 3f4ffb7..ade55ae 100644 (file)
  * @brief   Enables the SERIAL over USB subsystem.
  */
 #if !defined(HAL_USE_SERIAL_USB) || defined(__DOXYGEN__)
-#define HAL_USE_SERIAL_USB          TRUE
+#define HAL_USE_SERIAL_USB          FALSE
 #endif
 
 /**
index f48413c..b87b063 100644 (file)
  * @brief   Enables the SERIAL over USB subsystem.
  */
 #if !defined(HAL_USE_SERIAL_USB) || defined(__DOXYGEN__)
-#define HAL_USE_SERIAL_USB          TRUE
+#define HAL_USE_SERIAL_USB          FALSE
 #endif
 
 /**
index 7625725..8b9724b 100644 (file)
  * @brief   Enables the SERIAL over USB subsystem.
  */
 #if !defined(HAL_USE_SERIAL_USB) || defined(__DOXYGEN__)
-#define HAL_USE_SERIAL_USB          TRUE
+#define HAL_USE_SERIAL_USB          FALSE
 #endif
 
 /**
index f48413c..b87b063 100644 (file)
  * @brief   Enables the SERIAL over USB subsystem.
  */
 #if !defined(HAL_USE_SERIAL_USB) || defined(__DOXYGEN__)
-#define HAL_USE_SERIAL_USB          TRUE
+#define HAL_USE_SERIAL_USB          FALSE
 #endif
 
 /**
index f48413c..b87b063 100644 (file)
  * @brief   Enables the SERIAL over USB subsystem.
  */
 #if !defined(HAL_USE_SERIAL_USB) || defined(__DOXYGEN__)
-#define HAL_USE_SERIAL_USB          TRUE
+#define HAL_USE_SERIAL_USB          FALSE
 #endif
 
 /**
index 6e7cfbd..222fb4d 100644 (file)
@@ -5,6 +5,7 @@ CHIBIOS_DIR = $(PROTOCOL_DIR)/chibios
 SRC += $(CHIBIOS_DIR)/usb_main.c
 SRC += $(CHIBIOS_DIR)/main.c
 SRC += usb_descriptor.c
+SRC += $(CHIBIOS_DIR)/usb_driver.c
 
 VPATH += $(TMK_PATH)/$(PROTOCOL_DIR)
 VPATH += $(TMK_PATH)/$(CHIBIOS_DIR)
diff --git a/tmk_core/protocol/chibios/usb_driver.c b/tmk_core/protocol/chibios/usb_driver.c
new file mode 100644 (file)
index 0000000..fe535ee
--- /dev/null
@@ -0,0 +1,502 @@
+/*
+    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
+ * @{
+ */
+
+#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
+};
+
+/*===========================================================================*/
+/* Driver local functions.                                                   */
+/*===========================================================================*/
+
+static bool qmkusb_start_receive(QMKUSBDriver *qmkusbp) {
+  uint8_t *buf;
+
+  /* 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;
+  }
+
+  /* Checking if there is already a transaction ongoing on the endpoint.*/
+  if (usbGetReceiveStatusI(qmkusbp->config->usbp, qmkusbp->config->bulk_in)) {
+    return true;
+  }
+
+  /* Checking if there is a buffer ready for incoming data.*/
+  buf = ibqGetEmptyBufferI(&qmkusbp->ibqueue);
+  if (buf == NULL) {
+    return true;
+  }
+
+  /* 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, systime_t timeout) {
+
+  return obqPutTimeout(&((QMKUSBDriver *)ip)->obqueue, b, timeout);
+}
+
+static msg_t _gett(void *ip, systime_t timeout) {
+
+  return ibqGetTimeout(&((QMKUSBDriver *)ip)->ibqueue, timeout);
+}
+
+static size_t _writet(void *ip, const uint8_t *bp, size_t n, systime_t timeout) {
+
+  return obqWriteTimeout(&((QMKUSBDriver *)ip)->obqueue, bp, n, timeout);
+}
+
+static size_t _readt(void *ip, uint8_t *bp, size_t n, systime_t timeout) {
+
+  return ibqReadTimeout(&((QMKUSBDriver *)ip)->ibqueue, bp, n, timeout);
+}
+
+static const struct QMKUSBDriverVMT vmt = {
+  _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);
+}
+
+/**
+ * @brief   Notification of filled buffer inserted into the output buffers queue.
+ *
+ * @param[in] bqp       the buffers queue pointer.
+ */
+static void obnotify(io_buffers_queue_t *bqp) {
+  size_t n;
+  QMKUSBDriver *qmkusbp = bqGetLinkX(bqp);
+
+  /* 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;
+  }
+
+  /* Checking if there is already a transaction ongoing on the endpoint.*/
+  if (!usbGetTransmitStatusI(qmkusbp->config->usbp, qmkusbp->config->bulk_in)) {
+    /* Trying to get a full buffer.*/
+    uint8_t *buf = obqGetFullBufferI(&qmkusbp->obqueue, &n);
+    if (buf != NULL) {
+      /* Buffer found, starting a new transaction.*/
+      usbStartTransmitI(qmkusbp->config->usbp, qmkusbp->config->bulk_in, buf, n);
+    }
+  }
+}
+
+/*===========================================================================*/
+/* 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) {
+}
+
+/**
+ * @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);
+}
+
+/**
+ * @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;
+
+  osalDbgCheck(qmkusbp != 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;
+  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);
+
+  osalSysLock();
+
+  osalDbgAssert((qmkusbp->state == QMKUSB_STOP) || (qmkusbp->state == QMKUSB_READY),
+                "invalid state");
+
+  /* Driver in stopped state.*/
+  usbp->in_params[qmkusbp->config->bulk_in - 1U]   = NULL;
+  usbp->out_params[qmkusbp->config->bulk_out - 1U] = NULL;
+  if (qmkusbp->config->int_in > 0U) {
+    usbp->in_params[qmkusbp->config->int_in - 1U]  = NULL;
+  }
+  qmkusbp->config = NULL;
+  qmkusbp->state  = QMKUSB_STOP;
+
+  /* Enforces a disconnection.*/
+  chnAddFlagsI(qmkusbp, CHN_DISCONNECTED);
+  ibqResetI(&qmkusbp->ibqueue);
+  obqResetI(&qmkusbp->obqueue);
+  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);
+}
+
+/**
+ * @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);
+}
+
+/**
+ * @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);
+}
+
+/**
+ * @brief   Default requests hook.
+ * @details Applications wanting to use the Serial over USB driver can use
+ *          this function as requests hook in the USB configuration.
+ *          The following requests are emulated:
+ *          - CDC_GET_LINE_CODING.
+ *          - CDC_SET_LINE_CODING.
+ *          - CDC_SET_CONTROL_LINE_STATE.
+ *          .
+ *
+ * @param[in] usbp      pointer to the @p USBDriver object
+ * @return              The hook status.
+ * @retval true         Message handled internally.
+ * @retval false        Message not handled.
+ */
+bool qmkusbRequestsHook(USBDriver *usbp) {
+
+  if ((usbp->setup[0] & USB_RTYPE_TYPE_MASK) == USB_RTYPE_TYPE_CLASS) {
+    switch (usbp->setup[1]) {
+    case CDC_GET_LINE_CODING:
+      usbSetupTransfer(usbp, (uint8_t *)&linecoding, sizeof(linecoding), NULL);
+      return true;
+    case CDC_SET_LINE_CODING:
+      usbSetupTransfer(usbp, (uint8_t *)&linecoding, sizeof(linecoding), NULL);
+      return true;
+    case CDC_SET_CONTROL_LINE_STATE:
+      /* Nothing to do, there are no control lines.*/
+      usbSetupTransfer(usbp, NULL, 0, NULL);
+      return true;
+    default:
+      return false;
+    }
+  }
+  return false;
+}
+
+/**
+ * @brief   SOF handler.
+ * @details The SOF interrupt is used for automatic flushing of incomplete
+ *          buffers pending in the output queue.
+ *
+ * @param[in] qmkusbp      pointer to a @p QMKUSBDriver object
+ *
+ * @iclass
+ */
+void qmkusbSOFHookI(QMKUSBDriver *qmkusbp) {
+
+  /* If the USB driver is not in the appropriate state then transactions
+     must not be started.*/
+  if ((usbGetDriverStateI(qmkusbp->config->usbp) != USB_ACTIVE) ||
+      (qmkusbp->state != QMKUSB_READY)) {
+    return;
+  }
+
+  /* If there is already a transaction ongoing then another one cannot be
+     started.*/
+  if (usbGetTransmitStatusI(qmkusbp->config->usbp, qmkusbp->config->bulk_in)) {
+    return;
+  }
+
+  /* Checking if there only a buffer partially filled, if so then it is
+     enforced in the queue and transmitted.*/
+  if (obqTryFlushI(&qmkusbp->obqueue)) {
+    size_t n;
+    uint8_t *buf = obqGetFullBufferI(&qmkusbp->obqueue, &n);
+
+    /* For fixed size drivers, fill the end with zeros */
+    if (qmkusbp->config->fixed_size) {
+      memset(buf + n, 0, qmkusbp->config->in_size - n);
+      n = qmkusbp->config->in_size;
+    }
+
+    osalDbgAssert(buf != NULL, "queue is empty");
+
+    usbStartTransmitI(qmkusbp->config->usbp, qmkusbp->config->bulk_in, buf, n);
+  }
+}
+
+/**
+ * @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;
+  }
+
+  osalSysLockFromISR();
+
+  /* Signaling that space is available in the output queue.*/
+  chnAddFlagsI(qmkusbp, CHN_OUTPUT_EMPTY);
+
+  /* Freeing the buffer just transmitted, if it was not a zero size packet.*/
+  if (usbp->epc[ep]->in_state->txsize > 0U) {
+    obqReleaseEmptyBufferI(&qmkusbp->obqueue);
+  }
+
+  /* Checking if there is a buffer ready for transmission.*/
+  buf = obqGetFullBufferI(&qmkusbp->obqueue, &n);
+
+  if (buf != 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)) {
+    /* 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);
+    }
+
+  }
+  else {
+    /* Nothing to transmit.*/
+  }
+
+  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) {
+    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));
+
+  /* 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);
+
+  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;
+}
+
+/** @} */
diff --git a/tmk_core/protocol/chibios/usb_driver.h b/tmk_core/protocol/chibios/usb_driver.h
new file mode 100644 (file)
index 0000000..558479e
--- /dev/null
@@ -0,0 +1,184 @@
+/*
+    ChibiOS - Copyright (C) 2006..2016 Giovanni Di Sirio
+
+    Licensed under the Apache License, Version 2.0 (the "License");
+    you may not use this file except in compliance with the License.
+    You may obtain a copy of the License at
+
+        http://www.apache.org/licenses/LICENSE-2.0
+
+    Unless required by applicable law or agreed to in writing, software
+    distributed under the License is distributed on an "AS IS" BASIS,
+    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+    See the License for the specific language governing permissions and
+    limitations under the License.
+*/
+
+/**
+ * @file    usb_driver.h
+ * @brief   Usb driver suitable for both packet and serial formats
+ *
+ * @addtogroup SERIAL_USB
+ * @{
+ */
+
+#ifndef USB_DRIVER_H
+#define USB_DRIVER_H
+
+#include "hal_usb_cdc.h"
+
+/*===========================================================================*/
+/* Driver constants.                                                         */
+/*===========================================================================*/
+
+/*===========================================================================*/
+/* Derived constants and error checks.                                       */
+/*===========================================================================*/
+
+#if HAL_USE_USB == FALSE
+#error "The USB Driver requires HAL_USE_USB"
+#endif
+
+/*===========================================================================*/
+/* Driver data structures and types.                                         */
+/*===========================================================================*/
+
+/**
+ * @brief Driver state machine possible states.
+ */
+typedef enum {
+  QMKUSB_UNINIT = 0,                   /**< Not initialized.                   */
+  QMKUSB_STOP = 1,                     /**< Stopped.                           */
+  QMKUSB_READY = 2                     /**< Ready.                             */
+} qmkusbstate_t;
+
+/**
+ * @brief   Structure representing a serial over USB driver.
+ */
+typedef struct QMKUSBDriver QMKUSBDriver;
+
+/**
+ * @brief   Serial over USB Driver configuration structure.
+ * @details An instance of this structure must be passed to @p sduStart()
+ *          in order to configure and start the driver operations.
+ */
+typedef struct {
+  /**
+   * @brief   USB driver to use.
+   */
+  USBDriver                 *usbp;
+  /**
+   * @brief   Bulk IN endpoint used for outgoing data transfer.
+   */
+  usbep_t                   bulk_in;
+  /**
+   * @brief   Bulk OUT endpoint used for incoming data transfer.
+   */
+  usbep_t                   bulk_out;
+  /**
+   * @brief   Interrupt IN endpoint used for notifications.
+   * @note    If set to zero then the INT endpoint is assumed to be not
+   *          present, USB descriptors must be changed accordingly.
+   */
+  usbep_t                   int_in;
+
+  /**
+   * @brief The number of buffers in the queues
+   */
+  size_t                    in_buffers;
+  size_t                    out_buffers;
+
+  /**
+   * @brief The size of each buffer in the queue, typically the same as the endpoint size
+   */
+  size_t                    in_size;
+  size_t                    out_size;
+
+  /**
+   * @brief Always send full buffers in_size (the rest is filled with zeroes)
+   */
+  bool                      fixed_size;
+
+  /* Input buffer
+   * @note needs to be initialized with a memory buffer of the right size
+   */
+  uint8_t*                  ib;
+  /* Output buffer
+   * @note needs to be initialized with a memory buffer of the right size
+   */
+  uint8_t*                  ob;
+} QMKUSBConfig;
+
+/**
+ * @brief   @p SerialDriver specific data.
+ */
+#define _qmk_usb_driver_data                                                \
+  _base_asynchronous_channel_data                                           \
+  /* Driver state.*/                                                        \
+  qmkusbstate_t             state;                                          \
+  /* Input buffers queue.*/                                                 \
+  input_buffers_queue_t     ibqueue;                                        \
+  /* Output queue.*/                                                        \
+  output_buffers_queue_t    obqueue;                                        \
+  /* End of the mandatory fields.*/                                         \
+  /* Current configuration data.*/                                          \
+  const QMKUSBConfig     *config;
+
+/**
+ * @brief   @p SerialUSBDriver specific methods.
+ */
+#define _qmk_usb_driver_methods                                             \
+  _base_asynchronous_channel_methods
+
+/**
+ * @extends BaseAsynchronousChannelVMT
+ *
+ * @brief   @p SerialDriver virtual methods table.
+ */
+struct QMKUSBDriverVMT {
+  _qmk_usb_driver_methods
+};
+
+/**
+ * @extends BaseAsynchronousChannel
+ *
+ * @brief   Full duplex serial driver class.
+ * @details This class extends @p BaseAsynchronousChannel by adding physical
+ *          I/O queues.
+ */
+struct QMKUSBDriver {
+  /** @brief Virtual Methods Table.*/
+  const struct QMKUSBDriverVMT *vmt;
+  _qmk_usb_driver_data
+};
+
+/*===========================================================================*/
+/* Driver macros.                                                            */
+/*===========================================================================*/
+
+/*===========================================================================*/
+/* External declarations.                                                    */
+/*===========================================================================*/
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+  void qmkusbInit(void);
+  void qmkusbObjectInit(QMKUSBDriver *qmkusbp, const QMKUSBConfig * config);
+  void qmkusbStart(QMKUSBDriver *qmkusbp, const QMKUSBConfig *config);
+  void qmkusbStop(QMKUSBDriver *qmkusbp);
+  void qmkusbSuspendHookI(QMKUSBDriver *qmkusbp);
+  void qmkusbWakeupHookI(QMKUSBDriver *qmkusbp);
+  void qmkusbConfigureHookI(QMKUSBDriver *qmkusbp);
+  bool qmkusbRequestsHook(USBDriver *usbp);
+  void qmkusbSOFHookI(QMKUSBDriver *qmkusbp);
+  void qmkusbDataTransmitted(USBDriver *usbp, usbep_t ep);
+  void qmkusbDataReceived(USBDriver *usbp, usbep_t ep);
+  void qmkusbInterruptTransmitted(USBDriver *usbp, usbep_t ep);
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* USB_DRIVER_H */
+
+/** @} */
index f980024..cbe2571 100644 (file)
@@ -29,6 +29,7 @@
 #endif
 #include "wait.h"
 #include "usb_descriptor.h"
+#include "usb_driver.h"
 
 #ifdef NKRO_ENABLE
   #include "keycode_config.h"
@@ -170,27 +171,23 @@ static const USBEndpointConfig nkro_ep_config = {
 typedef struct {
   size_t queue_capacity_in;
   size_t queue_capacity_out;
-  uint8_t* queue_buffer_in;
-  uint8_t* queue_buffer_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 SerialUSBConfig config;
-  SerialUSBDriver driver;
-} stream_driver_t;
+  const QMKUSBConfig config;
+  QMKUSBDriver driver;
+} usb_driver_config_t;
 
-#define STREAM_DRIVER(stream, notification) { \
+#define QMK_USB_DRIVER_CONFIG(stream, notification, fixedsize) { \
   .queue_capacity_in = stream##_IN_CAPACITY, \
   .queue_capacity_out = stream##_OUT_CAPACITY, \
-  .queue_buffer_in = (uint8_t[BQ_BUFFER_SIZE(stream##_IN_CAPACITY, stream##_EPSIZE)]) {}, \
-  .queue_buffer_out = (uint8_t[BQ_BUFFER_SIZE(stream##_OUT_CAPACITY,stream##_EPSIZE)]) {}, \
   .in_ep_config = { \
     .ep_mode = stream##_IN_MODE, \
     .setup_cb = NULL, \
-    .in_cb = sduDataTransmitted, \
+    .in_cb = qmkusbDataTransmitted, \
     .out_cb = NULL, \
     .in_maxsize = stream##_EPSIZE, \
     .out_maxsize = 0, \
@@ -204,7 +201,7 @@ typedef struct {
     .ep_mode = stream##_OUT_MODE, \
     .setup_cb = NULL, \
     .in_cb = NULL, \
-    .out_cb = sduDataReceived, \
+    .out_cb = qmkusbDataReceived, \
     .in_maxsize = 0, \
     .out_maxsize = stream##_EPSIZE, \
     /* The pointer to the states will be filled during initialization */ \
@@ -216,7 +213,7 @@ typedef struct {
   .int_ep_config = { \
     .ep_mode = USB_EP_MODE_TYPE_INTR, \
     .setup_cb = NULL, \
-    .in_cb = sduInterruptTransmitted, \
+    .in_cb = qmkusbInterruptTransmitted, \
     .out_cb = NULL, \
     .in_maxsize = CDC_NOTIFICATION_EPSIZE, \
     .out_maxsize = 0, \
@@ -230,7 +227,14 @@ typedef struct {
     .usbp = &USB_DRIVER, \
     .bulk_in = stream##_IN_EPNUM, \
     .bulk_out = stream##_OUT_EPNUM, \
-    .int_in = notification \
+    .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 = (uint8_t[BQ_BUFFER_SIZE(stream##_IN_CAPACITY, stream##_EPSIZE)]) {}, \
+    .ob = (uint8_t[BQ_BUFFER_SIZE(stream##_OUT_CAPACITY,stream##_EPSIZE)]) {}, \
   } \
 }
 
@@ -238,36 +242,36 @@ typedef struct {
   union {
     struct {
 #ifdef CONSOLE_ENABLE
-      stream_driver_t console_driver;
+      usb_driver_config_t console_driver;
 #endif
 #ifdef RAW_ENABLE
-      stream_driver_t raw_driver;
+      usb_driver_config_t raw_driver;
 #endif
 #ifdef MIDI_ENABLE
-      stream_driver_t midi_driver;
+      usb_driver_config_t midi_driver;
 #endif
 #ifdef VIRTSER_ENABLE
-      stream_driver_t serial_driver;
+      usb_driver_config_t serial_driver;
 #endif
     };
-    stream_driver_t array[0];
+    usb_driver_config_t array[0];
   };
-} stream_drivers_t;
+} usb_driver_configs_t;
 
-static stream_drivers_t drivers = {
+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 = STREAM_DRIVER(CONSOLE, 0),
+  .console_driver = QMK_USB_DRIVER_CONFIG(CONSOLE, 0, true),
 #endif
 #ifdef RAW_ENABLE
   #define RAW_IN_CAPACITY 4
   #define RAW_OUT_CAPACITY 4
   #define RAW_IN_MODE USB_EP_MODE_TYPE_INTR
   #define RAW_OUT_MODE USB_EP_MODE_TYPE_INTR
-  .raw_driver = STREAM_DRIVER(RAW, 0),
+  .raw_driver = QMK_USB_DRIVER_CONFIG(RAW, 0, false),
 #endif
 
 #ifdef MIDI_ENABLE
@@ -275,7 +279,7 @@ static stream_drivers_t drivers = {
   #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 = STREAM_DRIVER(MIDI_STREAM, 0),
+  .midi_driver = QMK_USB_DRIVER_CONFIG(MIDI_STREAM, 0, false),
 #endif
 
 #ifdef VIRTSER_ENABLE
@@ -283,11 +287,11 @@ static stream_drivers_t drivers = {
   #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 = STREAM_DRIVER(CDC, CDC_NOTIFICATION_EPNUM),
+  .serial_driver = QMK_USB_DRIVER_CONFIG(CDC, CDC_NOTIFICATION_EPNUM, false),
 #endif
 };
 
-#define NUM_STREAM_DRIVERS (sizeof(drivers) / sizeof(stream_driver_t))
+#define NUM_USB_DRIVERS (sizeof(drivers) / sizeof(usb_driver_config_t))
 
 
 /* ---------------------------------------------------------
@@ -315,13 +319,13 @@ static void usb_event_cb(USBDriver *usbp, usbevent_t event) {
 #ifdef NKRO_ENABLE
     usbInitEndpointI(usbp, NKRO_IN_EPNUM, &nkro_ep_config);
 #endif /* NKRO_ENABLE */
-    for (int i=0;i<NUM_STREAM_DRIVERS;i++) {
+    for (int i=0;i<NUM_USB_DRIVERS;i++) {
       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);
       if (drivers.array[i].config.int_in) {
         usbInitEndpointI(usbp, drivers.array[i].config.int_in, &drivers.array[i].int_ep_config);
       }
-      sduConfigureHookI(&drivers.array[i].driver);
+      qmkusbConfigureHookI(&drivers.array[i].driver);
     }
     osalSysUnlockFromISR();
     return;
@@ -333,20 +337,20 @@ static void usb_event_cb(USBDriver *usbp, usbevent_t event) {
   case USB_EVENT_UNCONFIGURED:
     /* Falls into.*/
   case USB_EVENT_RESET:
-      for (int i=0;i<NUM_STREAM_DRIVERS;i++) {
+      for (int i=0;i<NUM_USB_DRIVERS;i++) {
         chSysLockFromISR();
         /* Disconnection event on suspend.*/
-        sduSuspendHookI(&drivers.array[i].driver);
+        qmkusbSuspendHookI(&drivers.array[i].driver);
         chSysUnlockFromISR();
       }
     return;
 
   case USB_EVENT_WAKEUP:
     //TODO: from ISR! print("[W]");
-      for (int i=0;i<NUM_STREAM_DRIVERS;i++) {
+      for (int i=0;i<NUM_USB_DRIVERS;i++) {
         chSysLockFromISR();
         /* Disconnection event on suspend.*/
-        sduWakeupHookI(&drivers.array[i].driver);
+        qmkusbWakeupHookI(&drivers.array[i].driver);
         chSysUnlockFromISR();
       }
     suspend_wakeup_init();
@@ -527,10 +531,10 @@ static bool usb_request_hook_cb(USBDriver *usbp) {
     return TRUE;
   }
 
-  for (int i=0;i<NUM_STREAM_DRIVERS;i++) {
+  for (int i=0;i<NUM_USB_DRIVERS;i++) {
     if (drivers.array[i].config.int_in) {
       // NOTE: Assumes that we only have one serial driver
-      return sduRequestsHook(usbp);
+      return qmkusbRequestsHook(usbp);
     }
   }
 
@@ -541,8 +545,8 @@ static bool usb_request_hook_cb(USBDriver *usbp) {
 static void usb_sof_cb(USBDriver *usbp) {
   kbd_sof_cb(usbp);
   osalSysLockFromISR();
-  for (int i=0; i<NUM_STREAM_DRIVERS;i++) {
-    sduSOFHookI(&drivers.array[i].driver);
+  for (int i=0; i<NUM_USB_DRIVERS;i++) {
+    qmkusbSOFHookI(&drivers.array[i].driver);
   }
   osalSysUnlockFromISR();
 }
@@ -560,17 +564,13 @@ static const USBConfig usbcfg = {
  * Initialize the USB driver
  */
 void init_usb_driver(USBDriver *usbp) {
-  for (int i=0; i<NUM_STREAM_DRIVERS;i++) {
-    SerialUSBDriver* driver = &drivers.array[i].driver;
+  for (int i=0; i<NUM_USB_DRIVERS;i++) {
+    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;
-    sduObjectInit(driver);
-    bqnotify_t notify = driver->ibqueue.notify;
-    ibqObjectInit(&driver->ibqueue, false, drivers.array[i].queue_buffer_in, drivers.array[i].in_ep_config.in_maxsize, drivers.array[i].queue_capacity_in, notify, driver);
-    notify = driver->obqueue.notify;
-    ibqObjectInit(&driver->ibqueue, false, drivers.array[i].queue_buffer_out, drivers.array[i].out_ep_config.out_maxsize, drivers.array[i].queue_capacity_out, notify, driver);
-    sduStart(driver, &drivers.array[i].config);
+    qmkusbObjectInit(driver, &drivers.array[i].config);
+    qmkusbStart(driver, &drivers.array[i].config);
   }
 
   /*