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 }