2020 February 29 Breaking Changes Update (#8064)
[jackhill/qmk/firmware.git] / tmk_core / protocol / chibios / usb_driver.c
1 /*
2 ChibiOS - Copyright (C) 2006..2016 Giovanni Di Sirio
3
4 Licensed under the Apache License, Version 2.0 (the "License");
5 you may not use this file except in compliance with the License.
6 You may obtain a copy of the License at
7
8 http://www.apache.org/licenses/LICENSE-2.0
9
10 Unless required by applicable law or agreed to in writing, software
11 distributed under the License is distributed on an "AS IS" BASIS,
12 WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 See the License for the specific language governing permissions and
14 limitations under the License.
15 */
16
17 /**
18 * @file hal_serial_usb.c
19 * @brief Serial over USB Driver code.
20 *
21 * @addtogroup SERIAL_USB
22 * @{
23 */
24
25 #include "hal.h"
26 #include "usb_driver.h"
27 #include <string.h>
28
29 /*===========================================================================*/
30 /* Driver local definitions. */
31 /*===========================================================================*/
32
33 /*===========================================================================*/
34 /* Driver exported variables. */
35 /*===========================================================================*/
36
37 /*===========================================================================*/
38 /* Driver local variables and types. */
39 /*===========================================================================*/
40
41 /*
42 * Current Line Coding.
43 */
44 static cdc_linecoding_t linecoding = {{0x00, 0x96, 0x00, 0x00}, /* 38400. */
45 LC_STOP_1,
46 LC_PARITY_NONE,
47 8};
48
49 /*===========================================================================*/
50 /* Driver local functions. */
51 /*===========================================================================*/
52
53 static bool qmkusb_start_receive(QMKUSBDriver *qmkusbp) {
54 uint8_t *buf;
55
56 /* If the USB driver is not in the appropriate state then transactions
57 must not be started.*/
58 if ((usbGetDriverStateI(qmkusbp->config->usbp) != USB_ACTIVE) || (qmkusbp->state != QMKUSB_READY)) {
59 return true;
60 }
61
62 /* Checking if there is already a transaction ongoing on the endpoint.*/
63 if (usbGetReceiveStatusI(qmkusbp->config->usbp, qmkusbp->config->bulk_in)) {
64 return true;
65 }
66
67 /* Checking if there is a buffer ready for incoming data.*/
68 buf = ibqGetEmptyBufferI(&qmkusbp->ibqueue);
69 if (buf == NULL) {
70 return true;
71 }
72
73 /* Buffer found, starting a new transaction.*/
74 usbStartReceiveI(qmkusbp->config->usbp, qmkusbp->config->bulk_out, buf, qmkusbp->ibqueue.bsize - sizeof(size_t));
75
76 return false;
77 }
78
79 /*
80 * Interface implementation.
81 */
82
83 static size_t _write(void *ip, const uint8_t *bp, size_t n) { return obqWriteTimeout(&((QMKUSBDriver *)ip)->obqueue, bp, n, TIME_INFINITE); }
84
85 static size_t _read(void *ip, uint8_t *bp, size_t n) { return ibqReadTimeout(&((QMKUSBDriver *)ip)->ibqueue, bp, n, TIME_INFINITE); }
86
87 static msg_t _put(void *ip, uint8_t b) { return obqPutTimeout(&((QMKUSBDriver *)ip)->obqueue, b, TIME_INFINITE); }
88
89 static msg_t _get(void *ip) { return ibqGetTimeout(&((QMKUSBDriver *)ip)->ibqueue, TIME_INFINITE); }
90
91 static msg_t _putt(void *ip, uint8_t b, sysinterval_t timeout) { return obqPutTimeout(&((QMKUSBDriver *)ip)->obqueue, b, timeout); }
92
93 static msg_t _gett(void *ip, sysinterval_t timeout) { return ibqGetTimeout(&((QMKUSBDriver *)ip)->ibqueue, timeout); }
94
95 static size_t _writet(void *ip, const uint8_t *bp, size_t n, sysinterval_t timeout) { return obqWriteTimeout(&((QMKUSBDriver *)ip)->obqueue, bp, n, timeout); }
96
97 static size_t _readt(void *ip, uint8_t *bp, size_t n, sysinterval_t timeout) { return ibqReadTimeout(&((QMKUSBDriver *)ip)->ibqueue, bp, n, timeout); }
98
99 static const struct QMKUSBDriverVMT vmt = {0, _write, _read, _put, _get, _putt, _gett, _writet, _readt};
100
101 /**
102 * @brief Notification of empty buffer released into the input buffers queue.
103 *
104 * @param[in] bqp the buffers queue pointer.
105 */
106 static void ibnotify(io_buffers_queue_t *bqp) {
107 QMKUSBDriver *qmkusbp = bqGetLinkX(bqp);
108 (void)qmkusb_start_receive(qmkusbp);
109 }
110
111 /**
112 * @brief Notification of filled buffer inserted into the output buffers queue.
113 *
114 * @param[in] bqp the buffers queue pointer.
115 */
116 static void obnotify(io_buffers_queue_t *bqp) {
117 size_t n;
118 QMKUSBDriver *qmkusbp = bqGetLinkX(bqp);
119
120 /* If the USB driver is not in the appropriate state then transactions
121 must not be started.*/
122 if ((usbGetDriverStateI(qmkusbp->config->usbp) != USB_ACTIVE) || (qmkusbp->state != QMKUSB_READY)) {
123 return;
124 }
125
126 /* Checking if there is already a transaction ongoing on the endpoint.*/
127 if (!usbGetTransmitStatusI(qmkusbp->config->usbp, qmkusbp->config->bulk_in)) {
128 /* Trying to get a full buffer.*/
129 uint8_t *buf = obqGetFullBufferI(&qmkusbp->obqueue, &n);
130 if (buf != NULL) {
131 /* Buffer found, starting a new transaction.*/
132 usbStartTransmitI(qmkusbp->config->usbp, qmkusbp->config->bulk_in, buf, n);
133 }
134 }
135 }
136
137 /*===========================================================================*/
138 /* Driver exported functions. */
139 /*===========================================================================*/
140
141 /**
142 * @brief Serial Driver initialization.
143 * @note This function is implicitly invoked by @p halInit(), there is
144 * no need to explicitly initialize the driver.
145 *
146 * @init
147 */
148 void qmkusbInit(void) {}
149
150 /**
151 * @brief Initializes a generic full duplex driver object.
152 * @details The HW dependent part of the initialization has to be performed
153 * outside, usually in the hardware initialization code.
154 *
155 * @param[out] qmkusbp pointer to a @p QMKUSBDriver structure
156 *
157 * @init
158 */
159 void qmkusbObjectInit(QMKUSBDriver *qmkusbp, const QMKUSBConfig *config) {
160 qmkusbp->vmt = &vmt;
161 osalEventObjectInit(&qmkusbp->event);
162 qmkusbp->state = QMKUSB_STOP;
163 // Note that the config uses the USB direction naming
164 ibqObjectInit(&qmkusbp->ibqueue, true, config->ob, config->out_size, config->out_buffers, ibnotify, qmkusbp);
165 obqObjectInit(&qmkusbp->obqueue, true, config->ib, config->in_size, config->in_buffers, obnotify, qmkusbp);
166 }
167
168 /**
169 * @brief Configures and starts the driver.
170 *
171 * @param[in] qmkusbp pointer to a @p QMKUSBDriver object
172 * @param[in] config the serial over USB driver configuration
173 *
174 * @api
175 */
176 void qmkusbStart(QMKUSBDriver *qmkusbp, const QMKUSBConfig *config) {
177 USBDriver *usbp = config->usbp;
178
179 osalDbgCheck(qmkusbp != NULL);
180
181 osalSysLock();
182 osalDbgAssert((qmkusbp->state == QMKUSB_STOP) || (qmkusbp->state == QMKUSB_READY), "invalid state");
183 usbp->in_params[config->bulk_in - 1U] = qmkusbp;
184 usbp->out_params[config->bulk_out - 1U] = qmkusbp;
185 if (config->int_in > 0U) {
186 usbp->in_params[config->int_in - 1U] = qmkusbp;
187 }
188 qmkusbp->config = config;
189 qmkusbp->state = QMKUSB_READY;
190 osalSysUnlock();
191 }
192
193 /**
194 * @brief Stops the driver.
195 * @details Any thread waiting on the driver's queues will be awakened with
196 * the message @p MSG_RESET.
197 *
198 * @param[in] qmkusbp pointer to a @p QMKUSBDriver object
199 *
200 * @api
201 */
202 void qmkusbStop(QMKUSBDriver *qmkusbp) {
203 USBDriver *usbp = qmkusbp->config->usbp;
204
205 osalDbgCheck(qmkusbp != NULL);
206
207 osalSysLock();
208
209 osalDbgAssert((qmkusbp->state == QMKUSB_STOP) || (qmkusbp->state == QMKUSB_READY), "invalid state");
210
211 /* Driver in stopped state.*/
212 usbp->in_params[qmkusbp->config->bulk_in - 1U] = NULL;
213 usbp->out_params[qmkusbp->config->bulk_out - 1U] = NULL;
214 if (qmkusbp->config->int_in > 0U) {
215 usbp->in_params[qmkusbp->config->int_in - 1U] = NULL;
216 }
217 qmkusbp->config = NULL;
218 qmkusbp->state = QMKUSB_STOP;
219
220 /* Enforces a disconnection.*/
221 chnAddFlagsI(qmkusbp, CHN_DISCONNECTED);
222 ibqResetI(&qmkusbp->ibqueue);
223 obqResetI(&qmkusbp->obqueue);
224 osalOsRescheduleS();
225
226 osalSysUnlock();
227 }
228
229 /**
230 * @brief USB device suspend handler.
231 * @details Generates a @p CHN_DISCONNECT event and puts queues in
232 * non-blocking mode, this way the application cannot get stuck
233 * in the middle of an I/O operations.
234 * @note If this function is not called from an ISR then an explicit call
235 * to @p osalOsRescheduleS() in necessary afterward.
236 *
237 * @param[in] qmkusbp pointer to a @p QMKUSBDriver object
238 *
239 * @iclass
240 */
241 void qmkusbSuspendHookI(QMKUSBDriver *qmkusbp) {
242 chnAddFlagsI(qmkusbp, CHN_DISCONNECTED);
243 bqSuspendI(&qmkusbp->ibqueue);
244 bqSuspendI(&qmkusbp->obqueue);
245 }
246
247 /**
248 * @brief USB device wakeup handler.
249 * @details Generates a @p CHN_CONNECT event and resumes normal queues
250 * operations.
251 *
252 * @note If this function is not called from an ISR then an explicit call
253 * to @p osalOsRescheduleS() in necessary afterward.
254 *
255 * @param[in] qmkusbp pointer to a @p QMKUSBDriver object
256 *
257 * @iclass
258 */
259 void qmkusbWakeupHookI(QMKUSBDriver *qmkusbp) {
260 chnAddFlagsI(qmkusbp, CHN_CONNECTED);
261 bqResumeX(&qmkusbp->ibqueue);
262 bqResumeX(&qmkusbp->obqueue);
263 }
264
265 /**
266 * @brief USB device configured handler.
267 *
268 * @param[in] qmkusbp pointer to a @p QMKUSBDriver object
269 *
270 * @iclass
271 */
272 void qmkusbConfigureHookI(QMKUSBDriver *qmkusbp) {
273 ibqResetI(&qmkusbp->ibqueue);
274 bqResumeX(&qmkusbp->ibqueue);
275 obqResetI(&qmkusbp->obqueue);
276 bqResumeX(&qmkusbp->obqueue);
277 chnAddFlagsI(qmkusbp, CHN_CONNECTED);
278 (void)qmkusb_start_receive(qmkusbp);
279 }
280
281 /**
282 * @brief Default requests hook.
283 * @details Applications wanting to use the Serial over USB driver can use
284 * this function as requests hook in the USB configuration.
285 * The following requests are emulated:
286 * - CDC_GET_LINE_CODING.
287 * - CDC_SET_LINE_CODING.
288 * - CDC_SET_CONTROL_LINE_STATE.
289 * .
290 *
291 * @param[in] usbp pointer to the @p USBDriver object
292 * @return The hook status.
293 * @retval true Message handled internally.
294 * @retval false Message not handled.
295 */
296 bool qmkusbRequestsHook(USBDriver *usbp) {
297 if ((usbp->setup[0] & USB_RTYPE_TYPE_MASK) == USB_RTYPE_TYPE_CLASS) {
298 switch (usbp->setup[1]) {
299 case CDC_GET_LINE_CODING:
300 usbSetupTransfer(usbp, (uint8_t *)&linecoding, sizeof(linecoding), NULL);
301 return true;
302 case CDC_SET_LINE_CODING:
303 usbSetupTransfer(usbp, (uint8_t *)&linecoding, sizeof(linecoding), NULL);
304 return true;
305 case CDC_SET_CONTROL_LINE_STATE:
306 /* Nothing to do, there are no control lines.*/
307 usbSetupTransfer(usbp, NULL, 0, NULL);
308 return true;
309 default:
310 return false;
311 }
312 }
313 return false;
314 }
315
316 /**
317 * @brief SOF handler.
318 * @details The SOF interrupt is used for automatic flushing of incomplete
319 * buffers pending in the output queue.
320 *
321 * @param[in] qmkusbp pointer to a @p QMKUSBDriver object
322 *
323 * @iclass
324 */
325 void qmkusbSOFHookI(QMKUSBDriver *qmkusbp) {
326 /* If the USB driver is not in the appropriate state then transactions
327 must not be started.*/
328 if ((usbGetDriverStateI(qmkusbp->config->usbp) != USB_ACTIVE) || (qmkusbp->state != QMKUSB_READY)) {
329 return;
330 }
331
332 /* If there is already a transaction ongoing then another one cannot be
333 started.*/
334 if (usbGetTransmitStatusI(qmkusbp->config->usbp, qmkusbp->config->bulk_in)) {
335 return;
336 }
337
338 /* Checking if there only a buffer partially filled, if so then it is
339 enforced in the queue and transmitted.*/
340 if (obqTryFlushI(&qmkusbp->obqueue)) {
341 size_t n;
342 uint8_t *buf = obqGetFullBufferI(&qmkusbp->obqueue, &n);
343
344 /* For fixed size drivers, fill the end with zeros */
345 if (qmkusbp->config->fixed_size) {
346 memset(buf + n, 0, qmkusbp->config->in_size - n);
347 n = qmkusbp->config->in_size;
348 }
349
350 osalDbgAssert(buf != NULL, "queue is empty");
351
352 usbStartTransmitI(qmkusbp->config->usbp, qmkusbp->config->bulk_in, buf, n);
353 }
354 }
355
356 /**
357 * @brief Default data transmitted callback.
358 * @details The application must use this function as callback for the IN
359 * data endpoint.
360 *
361 * @param[in] usbp pointer to the @p USBDriver object
362 * @param[in] ep IN endpoint number
363 */
364 void qmkusbDataTransmitted(USBDriver *usbp, usbep_t ep) {
365 uint8_t * buf;
366 size_t n;
367 QMKUSBDriver *qmkusbp = usbp->in_params[ep - 1U];
368
369 if (qmkusbp == NULL) {
370 return;
371 }
372
373 osalSysLockFromISR();
374
375 /* Signaling that space is available in the output queue.*/
376 chnAddFlagsI(qmkusbp, CHN_OUTPUT_EMPTY);
377
378 /* Freeing the buffer just transmitted, if it was not a zero size packet.*/
379 if (usbp->epc[ep]->in_state->txsize > 0U) {
380 obqReleaseEmptyBufferI(&qmkusbp->obqueue);
381 }
382
383 /* Checking if there is a buffer ready for transmission.*/
384 buf = obqGetFullBufferI(&qmkusbp->obqueue, &n);
385
386 if (buf != NULL) {
387 /* The endpoint cannot be busy, we are in the context of the callback,
388 so it is safe to transmit without a check.*/
389 usbStartTransmitI(usbp, ep, buf, n);
390 } else if ((usbp->epc[ep]->in_state->txsize > 0U) && ((usbp->epc[ep]->in_state->txsize & ((size_t)usbp->epc[ep]->in_maxsize - 1U)) == 0U)) {
391 /* Transmit zero sized packet in case the last one has maximum allowed
392 size. Otherwise the recipient may expect more data coming soon and
393 not return buffered data to app. See section 5.8.3 Bulk Transfer
394 Packet Size Constraints of the USB Specification document.*/
395 if (!qmkusbp->config->fixed_size) {
396 usbStartTransmitI(usbp, ep, usbp->setup, 0);
397 }
398
399 } else {
400 /* Nothing to transmit.*/
401 }
402
403 osalSysUnlockFromISR();
404 }
405
406 /**
407 * @brief Default data received callback.
408 * @details The application must use this function as callback for the OUT
409 * data endpoint.
410 *
411 * @param[in] usbp pointer to the @p USBDriver object
412 * @param[in] ep OUT endpoint number
413 */
414 void qmkusbDataReceived(USBDriver *usbp, usbep_t ep) {
415 QMKUSBDriver *qmkusbp = usbp->out_params[ep - 1U];
416 if (qmkusbp == NULL) {
417 return;
418 }
419
420 osalSysLockFromISR();
421
422 /* Signaling that data is available in the input queue.*/
423 chnAddFlagsI(qmkusbp, CHN_INPUT_AVAILABLE);
424
425 /* Posting the filled buffer in the queue.*/
426 ibqPostFullBufferI(&qmkusbp->ibqueue, usbGetReceiveTransactionSizeX(qmkusbp->config->usbp, qmkusbp->config->bulk_out));
427
428 /* The endpoint cannot be busy, we are in the context of the callback,
429 so a packet is in the buffer for sure. Trying to get a free buffer
430 for the next transaction.*/
431 (void)qmkusb_start_receive(qmkusbp);
432
433 osalSysUnlockFromISR();
434 }
435
436 /**
437 * @brief Default data received callback.
438 * @details The application must use this function as callback for the IN
439 * interrupt endpoint.
440 *
441 * @param[in] usbp pointer to the @p USBDriver object
442 * @param[in] ep endpoint number
443 */
444 void qmkusbInterruptTransmitted(USBDriver *usbp, usbep_t ep) {
445 (void)usbp;
446 (void)ep;
447 }
448
449 /** @} */