]> git.donarmstrong.com Git - qmk_firmware.git/blob - tmk_core/protocol/chibios/usb_driver.c
Fix Command feature: use get_mods() instead of keyboard_report->mods (#4955)
[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 = {
45   {0x00, 0x96, 0x00, 0x00},             /* 38400.                           */
46   LC_STOP_1, LC_PARITY_NONE, 8
47 };
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) ||
59       (qmkusbp->state != QMKUSB_READY)) {
60     return true;
61   }
62
63   /* Checking if there is already a transaction ongoing on the endpoint.*/
64   if (usbGetReceiveStatusI(qmkusbp->config->usbp, qmkusbp->config->bulk_in)) {
65     return true;
66   }
67
68   /* Checking if there is a buffer ready for incoming data.*/
69   buf = ibqGetEmptyBufferI(&qmkusbp->ibqueue);
70   if (buf == NULL) {
71     return true;
72   }
73
74   /* Buffer found, starting a new transaction.*/
75   usbStartReceiveI(qmkusbp->config->usbp, qmkusbp->config->bulk_out,
76                    buf, qmkusbp->ibqueue.bsize - sizeof(size_t));
77
78   return false;
79 }
80
81 /*
82  * Interface implementation.
83  */
84
85 static size_t _write(void *ip, const uint8_t *bp, size_t n) {
86
87   return obqWriteTimeout(&((QMKUSBDriver *)ip)->obqueue, bp,
88                          n, TIME_INFINITE);
89 }
90
91 static size_t _read(void *ip, uint8_t *bp, size_t n) {
92
93   return ibqReadTimeout(&((QMKUSBDriver *)ip)->ibqueue, bp,
94                         n, TIME_INFINITE);
95 }
96
97 static msg_t _put(void *ip, uint8_t b) {
98
99   return obqPutTimeout(&((QMKUSBDriver *)ip)->obqueue, b, TIME_INFINITE);
100 }
101
102 static msg_t _get(void *ip) {
103
104   return ibqGetTimeout(&((QMKUSBDriver *)ip)->ibqueue, TIME_INFINITE);
105 }
106
107 static msg_t _putt(void *ip, uint8_t b, systime_t timeout) {
108
109   return obqPutTimeout(&((QMKUSBDriver *)ip)->obqueue, b, timeout);
110 }
111
112 static msg_t _gett(void *ip, systime_t timeout) {
113
114   return ibqGetTimeout(&((QMKUSBDriver *)ip)->ibqueue, timeout);
115 }
116
117 static size_t _writet(void *ip, const uint8_t *bp, size_t n, systime_t timeout) {
118
119   return obqWriteTimeout(&((QMKUSBDriver *)ip)->obqueue, bp, n, timeout);
120 }
121
122 static size_t _readt(void *ip, uint8_t *bp, size_t n, systime_t timeout) {
123
124   return ibqReadTimeout(&((QMKUSBDriver *)ip)->ibqueue, bp, n, timeout);
125 }
126
127 static const struct QMKUSBDriverVMT vmt = {
128   _write, _read, _put, _get,
129   _putt, _gett, _writet, _readt
130 };
131
132 /**
133  * @brief   Notification of empty buffer released into the input buffers queue.
134  *
135  * @param[in] bqp       the buffers queue pointer.
136  */
137 static void ibnotify(io_buffers_queue_t *bqp) {
138   QMKUSBDriver *qmkusbp = bqGetLinkX(bqp);
139   (void) qmkusb_start_receive(qmkusbp);
140 }
141
142 /**
143  * @brief   Notification of filled buffer inserted into the output buffers queue.
144  *
145  * @param[in] bqp       the buffers queue pointer.
146  */
147 static void obnotify(io_buffers_queue_t *bqp) {
148   size_t n;
149   QMKUSBDriver *qmkusbp = bqGetLinkX(bqp);
150
151   /* If the USB driver is not in the appropriate state then transactions
152      must not be started.*/
153   if ((usbGetDriverStateI(qmkusbp->config->usbp) != USB_ACTIVE) ||
154       (qmkusbp->state != QMKUSB_READY)) {
155     return;
156   }
157
158   /* Checking if there is already a transaction ongoing on the endpoint.*/
159   if (!usbGetTransmitStatusI(qmkusbp->config->usbp, qmkusbp->config->bulk_in)) {
160     /* Trying to get a full buffer.*/
161     uint8_t *buf = obqGetFullBufferI(&qmkusbp->obqueue, &n);
162     if (buf != NULL) {
163       /* Buffer found, starting a new transaction.*/
164       usbStartTransmitI(qmkusbp->config->usbp, qmkusbp->config->bulk_in, buf, n);
165     }
166   }
167 }
168
169 /*===========================================================================*/
170 /* Driver exported functions.                                                */
171 /*===========================================================================*/
172
173 /**
174  * @brief   Serial Driver initialization.
175  * @note    This function is implicitly invoked by @p halInit(), there is
176  *          no need to explicitly initialize the driver.
177  *
178  * @init
179  */
180 void qmkusbInit(void) {
181 }
182
183 /**
184  * @brief   Initializes a generic full duplex driver object.
185  * @details The HW dependent part of the initialization has to be performed
186  *          outside, usually in the hardware initialization code.
187  *
188  * @param[out] qmkusbp     pointer to a @p QMKUSBDriver structure
189  *
190  * @init
191  */
192 void qmkusbObjectInit(QMKUSBDriver *qmkusbp, const QMKUSBConfig *config) {
193
194   qmkusbp->vmt = &vmt;
195   osalEventObjectInit(&qmkusbp->event);
196   qmkusbp->state = QMKUSB_STOP;
197   // Note that the config uses the USB direction naming
198   ibqObjectInit(&qmkusbp->ibqueue, true, config->ob,
199                 config->out_size, config->out_buffers,
200                 ibnotify, qmkusbp);
201   obqObjectInit(&qmkusbp->obqueue, true, config->ib,
202                 config->in_size, config->in_buffers,
203                 obnotify, qmkusbp);
204 }
205
206 /**
207  * @brief   Configures and starts the driver.
208  *
209  * @param[in] qmkusbp      pointer to a @p QMKUSBDriver object
210  * @param[in] config    the serial over USB driver configuration
211  *
212  * @api
213  */
214 void qmkusbStart(QMKUSBDriver *qmkusbp, const QMKUSBConfig *config) {
215   USBDriver *usbp = config->usbp;
216
217   osalDbgCheck(qmkusbp != NULL);
218
219   osalSysLock();
220   osalDbgAssert((qmkusbp->state == QMKUSB_STOP) || (qmkusbp->state == QMKUSB_READY),
221                 "invalid state");
222   usbp->in_params[config->bulk_in - 1U]   = qmkusbp;
223   usbp->out_params[config->bulk_out - 1U] = qmkusbp;
224   if (config->int_in > 0U) {
225     usbp->in_params[config->int_in - 1U]  = qmkusbp;
226   }
227   qmkusbp->config = config;
228   qmkusbp->state = QMKUSB_READY;
229   osalSysUnlock();
230 }
231
232 /**
233  * @brief   Stops the driver.
234  * @details Any thread waiting on the driver's queues will be awakened with
235  *          the message @p MSG_RESET.
236  *
237  * @param[in] qmkusbp      pointer to a @p QMKUSBDriver object
238  *
239  * @api
240  */
241 void qmkusbStop(QMKUSBDriver *qmkusbp) {
242   USBDriver *usbp = qmkusbp->config->usbp;
243
244   osalDbgCheck(qmkusbp != NULL);
245
246   osalSysLock();
247
248   osalDbgAssert((qmkusbp->state == QMKUSB_STOP) || (qmkusbp->state == QMKUSB_READY),
249                 "invalid state");
250
251   /* Driver in stopped state.*/
252   usbp->in_params[qmkusbp->config->bulk_in - 1U]   = NULL;
253   usbp->out_params[qmkusbp->config->bulk_out - 1U] = NULL;
254   if (qmkusbp->config->int_in > 0U) {
255     usbp->in_params[qmkusbp->config->int_in - 1U]  = NULL;
256   }
257   qmkusbp->config = NULL;
258   qmkusbp->state  = QMKUSB_STOP;
259
260   /* Enforces a disconnection.*/
261   chnAddFlagsI(qmkusbp, CHN_DISCONNECTED);
262   ibqResetI(&qmkusbp->ibqueue);
263   obqResetI(&qmkusbp->obqueue);
264   osalOsRescheduleS();
265
266   osalSysUnlock();
267 }
268
269 /**
270  * @brief   USB device suspend handler.
271  * @details Generates a @p CHN_DISCONNECT event and puts queues in
272  *          non-blocking mode, this way the application cannot get stuck
273  *          in the middle of an I/O operations.
274  * @note    If this function is not called from an ISR then an explicit call
275  *          to @p osalOsRescheduleS() in necessary afterward.
276  *
277  * @param[in] qmkusbp      pointer to a @p QMKUSBDriver object
278  *
279  * @iclass
280  */
281 void qmkusbSuspendHookI(QMKUSBDriver *qmkusbp) {
282
283   chnAddFlagsI(qmkusbp, CHN_DISCONNECTED);
284   bqSuspendI(&qmkusbp->ibqueue);
285   bqSuspendI(&qmkusbp->obqueue);
286 }
287
288 /**
289  * @brief   USB device wakeup handler.
290  * @details Generates a @p CHN_CONNECT event and resumes normal queues
291  *          operations.
292  *
293  * @note    If this function is not called from an ISR then an explicit call
294  *          to @p osalOsRescheduleS() in necessary afterward.
295  *
296  * @param[in] qmkusbp      pointer to a @p QMKUSBDriver object
297  *
298  * @iclass
299  */
300 void qmkusbWakeupHookI(QMKUSBDriver *qmkusbp) {
301
302   chnAddFlagsI(qmkusbp, CHN_CONNECTED);
303   bqResumeX(&qmkusbp->ibqueue);
304   bqResumeX(&qmkusbp->obqueue);
305 }
306
307 /**
308  * @brief   USB device configured handler.
309  *
310  * @param[in] qmkusbp      pointer to a @p QMKUSBDriver object
311  *
312  * @iclass
313  */
314 void qmkusbConfigureHookI(QMKUSBDriver *qmkusbp) {
315
316   ibqResetI(&qmkusbp->ibqueue);
317   bqResumeX(&qmkusbp->ibqueue);
318   obqResetI(&qmkusbp->obqueue);
319   bqResumeX(&qmkusbp->obqueue);
320   chnAddFlagsI(qmkusbp, CHN_CONNECTED);
321   (void) qmkusb_start_receive(qmkusbp);
322 }
323
324 /**
325  * @brief   Default requests hook.
326  * @details Applications wanting to use the Serial over USB driver can use
327  *          this function as requests hook in the USB configuration.
328  *          The following requests are emulated:
329  *          - CDC_GET_LINE_CODING.
330  *          - CDC_SET_LINE_CODING.
331  *          - CDC_SET_CONTROL_LINE_STATE.
332  *          .
333  *
334  * @param[in] usbp      pointer to the @p USBDriver object
335  * @return              The hook status.
336  * @retval true         Message handled internally.
337  * @retval false        Message not handled.
338  */
339 bool qmkusbRequestsHook(USBDriver *usbp) {
340
341   if ((usbp->setup[0] & USB_RTYPE_TYPE_MASK) == USB_RTYPE_TYPE_CLASS) {
342     switch (usbp->setup[1]) {
343     case CDC_GET_LINE_CODING:
344       usbSetupTransfer(usbp, (uint8_t *)&linecoding, sizeof(linecoding), NULL);
345       return true;
346     case CDC_SET_LINE_CODING:
347       usbSetupTransfer(usbp, (uint8_t *)&linecoding, sizeof(linecoding), NULL);
348       return true;
349     case CDC_SET_CONTROL_LINE_STATE:
350       /* Nothing to do, there are no control lines.*/
351       usbSetupTransfer(usbp, NULL, 0, NULL);
352       return true;
353     default:
354       return false;
355     }
356   }
357   return false;
358 }
359
360 /**
361  * @brief   SOF handler.
362  * @details The SOF interrupt is used for automatic flushing of incomplete
363  *          buffers pending in the output queue.
364  *
365  * @param[in] qmkusbp      pointer to a @p QMKUSBDriver object
366  *
367  * @iclass
368  */
369 void qmkusbSOFHookI(QMKUSBDriver *qmkusbp) {
370
371   /* If the USB driver is not in the appropriate state then transactions
372      must not be started.*/
373   if ((usbGetDriverStateI(qmkusbp->config->usbp) != USB_ACTIVE) ||
374       (qmkusbp->state != QMKUSB_READY)) {
375     return;
376   }
377
378   /* If there is already a transaction ongoing then another one cannot be
379      started.*/
380   if (usbGetTransmitStatusI(qmkusbp->config->usbp, qmkusbp->config->bulk_in)) {
381     return;
382   }
383
384   /* Checking if there only a buffer partially filled, if so then it is
385      enforced in the queue and transmitted.*/
386   if (obqTryFlushI(&qmkusbp->obqueue)) {
387     size_t n;
388     uint8_t *buf = obqGetFullBufferI(&qmkusbp->obqueue, &n);
389
390     /* For fixed size drivers, fill the end with zeros */
391     if (qmkusbp->config->fixed_size) {
392       memset(buf + n, 0, qmkusbp->config->in_size - n);
393       n = qmkusbp->config->in_size;
394     }
395
396     osalDbgAssert(buf != NULL, "queue is empty");
397
398     usbStartTransmitI(qmkusbp->config->usbp, qmkusbp->config->bulk_in, buf, n);
399   }
400 }
401
402 /**
403  * @brief   Default data transmitted callback.
404  * @details The application must use this function as callback for the IN
405  *          data endpoint.
406  *
407  * @param[in] usbp      pointer to the @p USBDriver object
408  * @param[in] ep        IN endpoint number
409  */
410 void qmkusbDataTransmitted(USBDriver *usbp, usbep_t ep) {
411   uint8_t *buf;
412   size_t n;
413   QMKUSBDriver *qmkusbp = usbp->in_params[ep - 1U];
414
415   if (qmkusbp == NULL) {
416     return;
417   }
418
419   osalSysLockFromISR();
420
421   /* Signaling that space is available in the output queue.*/
422   chnAddFlagsI(qmkusbp, CHN_OUTPUT_EMPTY);
423
424   /* Freeing the buffer just transmitted, if it was not a zero size packet.*/
425   if (usbp->epc[ep]->in_state->txsize > 0U) {
426     obqReleaseEmptyBufferI(&qmkusbp->obqueue);
427   }
428
429   /* Checking if there is a buffer ready for transmission.*/
430   buf = obqGetFullBufferI(&qmkusbp->obqueue, &n);
431
432   if (buf != NULL) {
433     /* The endpoint cannot be busy, we are in the context of the callback,
434        so it is safe to transmit without a check.*/
435     usbStartTransmitI(usbp, ep, buf, n);
436   }
437   else if ((usbp->epc[ep]->in_state->txsize > 0U) &&
438            ((usbp->epc[ep]->in_state->txsize &
439             ((size_t)usbp->epc[ep]->in_maxsize - 1U)) == 0U)) {
440     /* Transmit zero sized packet in case the last one has maximum allowed
441        size. Otherwise the recipient may expect more data coming soon and
442        not return buffered data to app. See section 5.8.3 Bulk Transfer
443        Packet Size Constraints of the USB Specification document.*/
444     if (!qmkusbp->config->fixed_size) {
445       usbStartTransmitI(usbp, ep, usbp->setup, 0);
446     }
447
448   }
449   else {
450     /* Nothing to transmit.*/
451   }
452
453   osalSysUnlockFromISR();
454 }
455
456 /**
457  * @brief   Default data received callback.
458  * @details The application must use this function as callback for the OUT
459  *          data endpoint.
460  *
461  * @param[in] usbp      pointer to the @p USBDriver object
462  * @param[in] ep        OUT endpoint number
463  */
464 void qmkusbDataReceived(USBDriver *usbp, usbep_t ep) {
465   QMKUSBDriver *qmkusbp = usbp->out_params[ep - 1U];
466   if (qmkusbp == NULL) {
467     return;
468   }
469
470   osalSysLockFromISR();
471
472   /* Signaling that data is available in the input queue.*/
473   chnAddFlagsI(qmkusbp, CHN_INPUT_AVAILABLE);
474
475   /* Posting the filled buffer in the queue.*/
476   ibqPostFullBufferI(&qmkusbp->ibqueue,
477                      usbGetReceiveTransactionSizeX(qmkusbp->config->usbp,
478                                                    qmkusbp->config->bulk_out));
479
480   /* The endpoint cannot be busy, we are in the context of the callback,
481      so a packet is in the buffer for sure. Trying to get a free buffer
482      for the next transaction.*/
483   (void) qmkusb_start_receive(qmkusbp);
484
485   osalSysUnlockFromISR();
486 }
487
488 /**
489  * @brief   Default data received callback.
490  * @details The application must use this function as callback for the IN
491  *          interrupt endpoint.
492  *
493  * @param[in] usbp      pointer to the @p USBDriver object
494  * @param[in] ep        endpoint number
495  */
496 void qmkusbInterruptTransmitted(USBDriver *usbp, usbep_t ep) {
497
498   (void)usbp;
499   (void)ep;
500 }
501
502 /** @} */