qmk_firmware

QMK firmware for my keyboards (Corne, Sweep Ferris) and trackball (Ploopy Adept)
Log | Files | Refs | Submodules | LICENSE

usb_driver.c (12126B)


      1 // Copyright 2023 Stefan Kerkmann (@KarlK90)
      2 // Copyright 2021 Purdea Andrei
      3 // Copyright 2021 Michael Stapelberg
      4 // Copyright 2020 Ryan (@fauxpark)
      5 // Copyright 2016 Fredizzimo
      6 // Copyright 2016 Giovanni Di Sirio
      7 // SPDX-License-Identifier: GPL-3.0-or-later OR Apache-2.0
      8 
      9 #include <hal.h>
     10 #include <string.h>
     11 
     12 #include "usb_driver.h"
     13 #include "util.h"
     14 
     15 /*===========================================================================*/
     16 /* Driver local functions.                                                   */
     17 /*===========================================================================*/
     18 
     19 static void usb_start_receive(usb_endpoint_out_t *endpoint) {
     20     /* If the USB driver is not in the appropriate state then transactions
     21        must not be started.*/
     22     if ((usbGetDriverStateI(endpoint->config.usbp) != USB_ACTIVE)) {
     23         return;
     24     }
     25 
     26     /* Checking if there is already a transaction ongoing on the endpoint.*/
     27     if (usbGetReceiveStatusI(endpoint->config.usbp, endpoint->config.ep)) {
     28         return;
     29     }
     30 
     31     /* Checking if there is a buffer ready for incoming data.*/
     32     uint8_t *buffer = ibqGetEmptyBufferI(&endpoint->ibqueue);
     33     if (buffer == NULL) {
     34         return;
     35     }
     36 
     37     /* Buffer found, starting a new transaction.*/
     38     usbStartReceiveI(endpoint->config.usbp, endpoint->config.ep, buffer, endpoint->ibqueue.bsize - sizeof(size_t));
     39 }
     40 
     41 /**
     42  * @brief   Notification of empty buffer released into the input buffers queue.
     43  *
     44  * @param[in] bqp       the buffers queue pointer.
     45  */
     46 static void ibnotify(io_buffers_queue_t *bqp) {
     47     usb_endpoint_out_t *endpoint = bqGetLinkX(bqp);
     48     usb_start_receive(endpoint);
     49 }
     50 
     51 /**
     52  * @brief   Notification of filled buffer inserted into the output buffers queue.
     53  *
     54  * @param[in] bqp       the buffers queue pointer.
     55  */
     56 static void obnotify(io_buffers_queue_t *bqp) {
     57     usb_endpoint_in_t *endpoint = bqGetLinkX(bqp);
     58 
     59     /* If the USB endpoint is not in the appropriate state then transactions
     60        must not be started.*/
     61     if ((usbGetDriverStateI(endpoint->config.usbp) != USB_ACTIVE)) {
     62         return;
     63     }
     64 
     65     /* Checking if there is already a transaction ongoing on the endpoint.*/
     66     if (!usbGetTransmitStatusI(endpoint->config.usbp, endpoint->config.ep)) {
     67         /* Trying to get a full buffer.*/
     68         size_t   n;
     69         uint8_t *buffer = obqGetFullBufferI(&endpoint->obqueue, &n);
     70         if (buffer != NULL) {
     71             /* Buffer found, starting a new transaction.*/
     72             usbStartTransmitI(endpoint->config.usbp, endpoint->config.ep, buffer, n);
     73         }
     74     }
     75 }
     76 
     77 /*===========================================================================*/
     78 /* Driver exported functions.                                                */
     79 /*===========================================================================*/
     80 
     81 void usb_endpoint_in_init(usb_endpoint_in_t *endpoint) {
     82     usb_endpoint_config_t *config = &endpoint->config;
     83     endpoint->ep_config.in_state  = &endpoint->ep_in_state;
     84 
     85 #if defined(USB_ENDPOINTS_ARE_REORDERABLE)
     86     if (endpoint->is_shared) {
     87         endpoint->ep_config.out_state = &endpoint->ep_out_state;
     88     }
     89 #endif
     90     obqObjectInit(&endpoint->obqueue, true, config->buffer, config->buffer_size, config->buffer_capacity, obnotify, endpoint);
     91 }
     92 
     93 void usb_endpoint_out_init(usb_endpoint_out_t *endpoint) {
     94     usb_endpoint_config_t *config = &endpoint->config;
     95     endpoint->ep_config.out_state = &endpoint->ep_out_state;
     96     ibqObjectInit(&endpoint->ibqueue, true, config->buffer, config->buffer_size, config->buffer_capacity, ibnotify, endpoint);
     97 }
     98 
     99 void usb_endpoint_in_start(usb_endpoint_in_t *endpoint) {
    100     osalDbgCheck(endpoint != NULL);
    101 
    102     osalSysLock();
    103     osalDbgAssert((usbGetDriverStateI(endpoint->config.usbp) == USB_STOP) || (usbGetDriverStateI(endpoint->config.usbp) == USB_READY), "invalid state");
    104     endpoint->config.usbp->in_params[endpoint->config.ep - 1U] = endpoint;
    105     endpoint->timed_out                                        = false;
    106     osalSysUnlock();
    107 }
    108 
    109 void usb_endpoint_out_start(usb_endpoint_out_t *endpoint) {
    110     osalDbgCheck(endpoint != NULL);
    111 
    112     osalSysLock();
    113     osalDbgAssert((usbGetDriverStateI(endpoint->config.usbp) == USB_STOP) || (usbGetDriverStateI(endpoint->config.usbp) == USB_READY), "invalid state");
    114     endpoint->config.usbp->out_params[endpoint->config.ep - 1U] = endpoint;
    115     endpoint->timed_out                                         = false;
    116     osalSysUnlock();
    117 }
    118 
    119 void usb_endpoint_in_stop(usb_endpoint_in_t *endpoint) {
    120     osalDbgCheck(endpoint != NULL);
    121 
    122     osalSysLock();
    123     endpoint->config.usbp->in_params[endpoint->config.ep - 1U] = NULL;
    124 
    125     bqSuspendI(&endpoint->obqueue);
    126     obqResetI(&endpoint->obqueue);
    127     if (endpoint->report_storage != NULL) {
    128         endpoint->report_storage->reset_report(endpoint->report_storage->reports);
    129     }
    130     osalOsRescheduleS();
    131     osalSysUnlock();
    132 }
    133 
    134 void usb_endpoint_out_stop(usb_endpoint_out_t *endpoint) {
    135     osalDbgCheck(endpoint != NULL);
    136 
    137     osalSysLock();
    138     osalDbgAssert((usbGetDriverStateI(endpoint->config.usbp) == USB_STOP) || (usbGetDriverStateI(endpoint->config.usbp) == USB_READY), "invalid state");
    139 
    140     bqSuspendI(&endpoint->ibqueue);
    141     ibqResetI(&endpoint->ibqueue);
    142     osalOsRescheduleS();
    143     osalSysUnlock();
    144 }
    145 
    146 void usb_endpoint_in_suspend_cb(usb_endpoint_in_t *endpoint) {
    147     bqSuspendI(&endpoint->obqueue);
    148     obqResetI(&endpoint->obqueue);
    149 
    150     if (endpoint->report_storage != NULL) {
    151         endpoint->report_storage->reset_report(endpoint->report_storage->reports);
    152     }
    153 }
    154 
    155 void usb_endpoint_out_suspend_cb(usb_endpoint_out_t *endpoint) {
    156     bqSuspendI(&endpoint->ibqueue);
    157     ibqResetI(&endpoint->ibqueue);
    158 }
    159 
    160 void usb_endpoint_in_wakeup_cb(usb_endpoint_in_t *endpoint) {
    161     bqResumeX(&endpoint->obqueue);
    162 }
    163 
    164 void usb_endpoint_out_wakeup_cb(usb_endpoint_out_t *endpoint) {
    165     bqResumeX(&endpoint->ibqueue);
    166 }
    167 
    168 void usb_endpoint_in_configure_cb(usb_endpoint_in_t *endpoint) {
    169     usbInitEndpointI(endpoint->config.usbp, endpoint->config.ep, &endpoint->ep_config);
    170     obqResetI(&endpoint->obqueue);
    171     bqResumeX(&endpoint->obqueue);
    172 }
    173 
    174 void usb_endpoint_out_configure_cb(usb_endpoint_out_t *endpoint) {
    175     /* The current assumption is that there are no standalone OUT endpoints,
    176      * therefore if we share an endpoint with an IN endpoint, it is already
    177      * initialized. */
    178 #if !defined(USB_ENDPOINTS_ARE_REORDERABLE)
    179     usbInitEndpointI(endpoint->config.usbp, endpoint->config.ep, &endpoint->ep_config);
    180 #endif
    181     ibqResetI(&endpoint->ibqueue);
    182     bqResumeX(&endpoint->ibqueue);
    183     (void)usb_start_receive(endpoint);
    184 }
    185 
    186 void usb_endpoint_in_tx_complete_cb(USBDriver *usbp, usbep_t ep) {
    187     usb_endpoint_in_t *endpoint = usbp->in_params[ep - 1U];
    188     size_t             n;
    189     uint8_t           *buffer;
    190 
    191     if (endpoint == NULL) {
    192         return;
    193     }
    194 
    195     osalSysLockFromISR();
    196 
    197     /* Sending succeded, so we can reset the timed out state. */
    198     endpoint->timed_out = false;
    199 
    200     /* Freeing the buffer just transmitted, if it was not a zero size packet.*/
    201     if (!obqIsEmptyI(&endpoint->obqueue) && usbp->epc[ep]->in_state->txsize > 0U) {
    202         /* Store the last send report in the endpoint to be retrieved by a
    203          * GET_REPORT request or IDLE report handling. */
    204         if (endpoint->report_storage != NULL) {
    205             buffer = obqGetFullBufferI(&endpoint->obqueue, &n);
    206             endpoint->report_storage->set_report(endpoint->report_storage->reports, buffer, n);
    207         }
    208         obqReleaseEmptyBufferI(&endpoint->obqueue);
    209     }
    210 
    211     /* Checking if there is a buffer ready for transmission.*/
    212     buffer = obqGetFullBufferI(&endpoint->obqueue, &n);
    213 
    214     if (buffer != NULL) {
    215         /* The endpoint cannot be busy, we are in the context of the callback,
    216            so it is safe to transmit without a check.*/
    217         usbStartTransmitI(usbp, ep, buffer, n);
    218     } 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)) {
    219         /* Transmit zero sized packet in case the last one has maximum allowed
    220          * size. Otherwise the recipient may expect more data coming soon and
    221          * not return buffered data to app. See section 5.8.3 Bulk Transfer
    222          * Packet Size Constraints of the USB Specification document. */
    223         usbStartTransmitI(usbp, ep, usbp->setup, 0);
    224     } else {
    225         /* Nothing to transmit.*/
    226     }
    227 
    228     osalSysUnlockFromISR();
    229 }
    230 
    231 void usb_endpoint_out_rx_complete_cb(USBDriver *usbp, usbep_t ep) {
    232     usb_endpoint_out_t *endpoint = usbp->out_params[ep - 1U];
    233     if (endpoint == NULL) {
    234         return;
    235     }
    236 
    237     osalSysLockFromISR();
    238 
    239     size_t size = usbGetReceiveTransactionSizeX(usbp, ep);
    240     if (size > 0) {
    241         /* Posting the filled buffer in the queue.*/
    242         ibqPostFullBufferI(&endpoint->ibqueue, usbGetReceiveTransactionSizeX(endpoint->config.usbp, endpoint->config.ep));
    243     }
    244 
    245     /* The endpoint cannot be busy, we are in the context of the callback, so a
    246      * packet is in the buffer for sure. Trying to get a free buffer for the
    247      * next transaction.*/
    248     usb_start_receive(endpoint);
    249 
    250     osalSysUnlockFromISR();
    251 }
    252 
    253 bool usb_endpoint_in_send(usb_endpoint_in_t *endpoint, const uint8_t *data, size_t size, sysinterval_t timeout, bool buffered) {
    254     osalDbgCheck((endpoint != NULL) && (data != NULL) && (size > 0U) && (size <= endpoint->config.buffer_size));
    255 
    256     osalSysLock();
    257     if (usbGetDriverStateI(endpoint->config.usbp) != USB_ACTIVE) {
    258         osalSysUnlock();
    259         return false;
    260     }
    261 
    262     /* Short circuit the waiting if this endpoint timed out before, e.g. if
    263      * nobody is listening on this endpoint (is disconnected) such as
    264      * `hid_listen`/`qmk console` or we are in an environment with a very
    265      * restricted USB stack. The reason is to not introduce micro lock-ups if
    266      * the report is send periodically. */
    267     if (endpoint->timed_out && timeout != TIME_INFINITE) {
    268         timeout = TIME_IMMEDIATE;
    269     }
    270     osalSysUnlock();
    271 
    272     while (true) {
    273         size_t sent = obqWriteTimeout(&endpoint->obqueue, data, size, timeout);
    274 
    275         if (sent < size) {
    276             osalSysLock();
    277             endpoint->timed_out |= sent == 0;
    278             bqSuspendI(&endpoint->obqueue);
    279             obqResetI(&endpoint->obqueue);
    280             bqResumeX(&endpoint->obqueue);
    281             osalOsRescheduleS();
    282             osalSysUnlock();
    283             continue;
    284         }
    285 
    286         if (!buffered) {
    287             obqFlush(&endpoint->obqueue);
    288         }
    289 
    290         return true;
    291     }
    292 }
    293 
    294 void usb_endpoint_in_flush(usb_endpoint_in_t *endpoint, bool padded) {
    295     osalDbgCheck(endpoint != NULL);
    296 
    297     output_buffers_queue_t *obqp = &endpoint->obqueue;
    298 
    299     if (padded && obqp->ptr != NULL) {
    300         ptrdiff_t bytes_left = (size_t)obqp->top - (size_t)obqp->ptr;
    301         while (bytes_left > 0) {
    302             // Putting bytes into a buffer that has space left should never
    303             // fail and be instant, therefore we don't check the return value
    304             // for errors here.
    305             obqPutTimeout(obqp, 0, TIME_IMMEDIATE);
    306             bytes_left--;
    307         }
    308     }
    309 
    310     obqFlush(obqp);
    311 }
    312 
    313 bool usb_endpoint_in_is_inactive(usb_endpoint_in_t *endpoint) {
    314     osalDbgCheck(endpoint != NULL);
    315 
    316     osalSysLock();
    317     bool inactive = obqIsEmptyI(&endpoint->obqueue) && !usbGetTransmitStatusI(endpoint->config.usbp, endpoint->config.ep);
    318     osalSysUnlock();
    319 
    320     return inactive;
    321 }
    322 
    323 bool usb_endpoint_out_receive(usb_endpoint_out_t *endpoint, uint8_t *data, size_t size, sysinterval_t timeout) {
    324     osalDbgCheck((endpoint != NULL) && (data != NULL) && (size > 0U));
    325 
    326     osalSysLock();
    327     if (usbGetDriverStateI(endpoint->config.usbp) != USB_ACTIVE) {
    328         osalSysUnlock();
    329         return false;
    330     }
    331 
    332     if (endpoint->timed_out && timeout != TIME_INFINITE) {
    333         timeout = TIME_IMMEDIATE;
    334     }
    335     osalSysUnlock();
    336 
    337     const size_t received = ibqReadTimeout(&endpoint->ibqueue, data, size, timeout);
    338     endpoint->timed_out   = received == 0;
    339 
    340     return received == size;
    341 }