2 ChibiOS - Copyright (C) 2006..2016 Giovanni Di Sirio
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
8 http://www.apache.org/licenses/LICENSE-2.0
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.
18 * @file hal_serial_usb.c
19 * @brief Serial over USB Driver code.
21 * @addtogroup SERIAL_USB
26 #include "usb_driver.h"
29 /*===========================================================================*/
30 /* Driver local definitions. */
31 /*===========================================================================*/
33 /*===========================================================================*/
34 /* Driver exported variables. */
35 /*===========================================================================*/
37 /*===========================================================================*/
38 /* Driver local variables and types. */
39 /*===========================================================================*/
42 * Current Line Coding.
44 static cdc_linecoding_t linecoding = {
45 {0x00, 0x96, 0x00, 0x00}, /* 38400. */
46 LC_STOP_1, LC_PARITY_NONE, 8
49 /*===========================================================================*/
50 /* Driver local functions. */
51 /*===========================================================================*/
53 static bool qmkusb_start_receive(QMKUSBDriver *qmkusbp) {
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)) {
63 /* Checking if there is already a transaction ongoing on the endpoint.*/
64 if (usbGetReceiveStatusI(qmkusbp->config->usbp, qmkusbp->config->bulk_in)) {
68 /* Checking if there is a buffer ready for incoming data.*/
69 buf = ibqGetEmptyBufferI(&qmkusbp->ibqueue);
74 /* Buffer found, starting a new transaction.*/
75 usbStartReceiveI(qmkusbp->config->usbp, qmkusbp->config->bulk_out,
76 buf, qmkusbp->ibqueue.bsize - sizeof(size_t));
82 * Interface implementation.
85 static size_t _write(void *ip, const uint8_t *bp, size_t n) {
87 return obqWriteTimeout(&((QMKUSBDriver *)ip)->obqueue, bp,
91 static size_t _read(void *ip, uint8_t *bp, size_t n) {
93 return ibqReadTimeout(&((QMKUSBDriver *)ip)->ibqueue, bp,
97 static msg_t _put(void *ip, uint8_t b) {
99 return obqPutTimeout(&((QMKUSBDriver *)ip)->obqueue, b, TIME_INFINITE);
102 static msg_t _get(void *ip) {
104 return ibqGetTimeout(&((QMKUSBDriver *)ip)->ibqueue, TIME_INFINITE);
107 static msg_t _putt(void *ip, uint8_t b, systime_t timeout) {
109 return obqPutTimeout(&((QMKUSBDriver *)ip)->obqueue, b, timeout);
112 static msg_t _gett(void *ip, systime_t timeout) {
114 return ibqGetTimeout(&((QMKUSBDriver *)ip)->ibqueue, timeout);
117 static size_t _writet(void *ip, const uint8_t *bp, size_t n, systime_t timeout) {
119 return obqWriteTimeout(&((QMKUSBDriver *)ip)->obqueue, bp, n, timeout);
122 static size_t _readt(void *ip, uint8_t *bp, size_t n, systime_t timeout) {
124 return ibqReadTimeout(&((QMKUSBDriver *)ip)->ibqueue, bp, n, timeout);
127 static const struct QMKUSBDriverVMT vmt = {
128 _write, _read, _put, _get,
129 _putt, _gett, _writet, _readt
133 * @brief Notification of empty buffer released into the input buffers queue.
135 * @param[in] bqp the buffers queue pointer.
137 static void ibnotify(io_buffers_queue_t *bqp) {
138 QMKUSBDriver *qmkusbp = bqGetLinkX(bqp);
139 (void) qmkusb_start_receive(qmkusbp);
143 * @brief Notification of filled buffer inserted into the output buffers queue.
145 * @param[in] bqp the buffers queue pointer.
147 static void obnotify(io_buffers_queue_t *bqp) {
149 QMKUSBDriver *qmkusbp = bqGetLinkX(bqp);
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)) {
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);
163 /* Buffer found, starting a new transaction.*/
164 usbStartTransmitI(qmkusbp->config->usbp, qmkusbp->config->bulk_in, buf, n);
169 /*===========================================================================*/
170 /* Driver exported functions. */
171 /*===========================================================================*/
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.
180 void qmkusbInit(void) {
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.
188 * @param[out] qmkusbp pointer to a @p QMKUSBDriver structure
192 void qmkusbObjectInit(QMKUSBDriver *qmkusbp, const QMKUSBConfig *config) {
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,
201 obqObjectInit(&qmkusbp->obqueue, true, config->ib,
202 config->in_size, config->in_buffers,
207 * @brief Configures and starts the driver.
209 * @param[in] qmkusbp pointer to a @p QMKUSBDriver object
210 * @param[in] config the serial over USB driver configuration
214 void qmkusbStart(QMKUSBDriver *qmkusbp, const QMKUSBConfig *config) {
215 USBDriver *usbp = config->usbp;
217 osalDbgCheck(qmkusbp != NULL);
220 osalDbgAssert((qmkusbp->state == QMKUSB_STOP) || (qmkusbp->state == QMKUSB_READY),
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;
227 qmkusbp->config = config;
228 qmkusbp->state = QMKUSB_READY;
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.
237 * @param[in] qmkusbp pointer to a @p QMKUSBDriver object
241 void qmkusbStop(QMKUSBDriver *qmkusbp) {
242 USBDriver *usbp = qmkusbp->config->usbp;
244 osalDbgCheck(qmkusbp != NULL);
248 osalDbgAssert((qmkusbp->state == QMKUSB_STOP) || (qmkusbp->state == QMKUSB_READY),
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;
257 qmkusbp->config = NULL;
258 qmkusbp->state = QMKUSB_STOP;
260 /* Enforces a disconnection.*/
261 chnAddFlagsI(qmkusbp, CHN_DISCONNECTED);
262 ibqResetI(&qmkusbp->ibqueue);
263 obqResetI(&qmkusbp->obqueue);
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.
277 * @param[in] qmkusbp pointer to a @p QMKUSBDriver object
281 void qmkusbSuspendHookI(QMKUSBDriver *qmkusbp) {
283 chnAddFlagsI(qmkusbp, CHN_DISCONNECTED);
284 bqSuspendI(&qmkusbp->ibqueue);
285 bqSuspendI(&qmkusbp->obqueue);
289 * @brief USB device wakeup handler.
290 * @details Generates a @p CHN_CONNECT event and resumes normal queues
293 * @note If this function is not called from an ISR then an explicit call
294 * to @p osalOsRescheduleS() in necessary afterward.
296 * @param[in] qmkusbp pointer to a @p QMKUSBDriver object
300 void qmkusbWakeupHookI(QMKUSBDriver *qmkusbp) {
302 chnAddFlagsI(qmkusbp, CHN_CONNECTED);
303 bqResumeX(&qmkusbp->ibqueue);
304 bqResumeX(&qmkusbp->obqueue);
308 * @brief USB device configured handler.
310 * @param[in] qmkusbp pointer to a @p QMKUSBDriver object
314 void qmkusbConfigureHookI(QMKUSBDriver *qmkusbp) {
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);
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.
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.
339 bool qmkusbRequestsHook(USBDriver *usbp) {
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);
346 case CDC_SET_LINE_CODING:
347 usbSetupTransfer(usbp, (uint8_t *)&linecoding, sizeof(linecoding), NULL);
349 case CDC_SET_CONTROL_LINE_STATE:
350 /* Nothing to do, there are no control lines.*/
351 usbSetupTransfer(usbp, NULL, 0, NULL);
361 * @brief SOF handler.
362 * @details The SOF interrupt is used for automatic flushing of incomplete
363 * buffers pending in the output queue.
365 * @param[in] qmkusbp pointer to a @p QMKUSBDriver object
369 void qmkusbSOFHookI(QMKUSBDriver *qmkusbp) {
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)) {
378 /* If there is already a transaction ongoing then another one cannot be
380 if (usbGetTransmitStatusI(qmkusbp->config->usbp, qmkusbp->config->bulk_in)) {
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)) {
388 uint8_t *buf = obqGetFullBufferI(&qmkusbp->obqueue, &n);
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;
396 osalDbgAssert(buf != NULL, "queue is empty");
398 usbStartTransmitI(qmkusbp->config->usbp, qmkusbp->config->bulk_in, buf, n);
403 * @brief Default data transmitted callback.
404 * @details The application must use this function as callback for the IN
407 * @param[in] usbp pointer to the @p USBDriver object
408 * @param[in] ep IN endpoint number
410 void qmkusbDataTransmitted(USBDriver *usbp, usbep_t ep) {
413 QMKUSBDriver *qmkusbp = usbp->in_params[ep - 1U];
415 if (qmkusbp == NULL) {
419 osalSysLockFromISR();
421 /* Signaling that space is available in the output queue.*/
422 chnAddFlagsI(qmkusbp, CHN_OUTPUT_EMPTY);
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);
429 /* Checking if there is a buffer ready for transmission.*/
430 buf = obqGetFullBufferI(&qmkusbp->obqueue, &n);
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);
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);
450 /* Nothing to transmit.*/
453 osalSysUnlockFromISR();
457 * @brief Default data received callback.
458 * @details The application must use this function as callback for the OUT
461 * @param[in] usbp pointer to the @p USBDriver object
462 * @param[in] ep OUT endpoint number
464 void qmkusbDataReceived(USBDriver *usbp, usbep_t ep) {
465 QMKUSBDriver *qmkusbp = usbp->out_params[ep - 1U];
466 if (qmkusbp == NULL) {
470 osalSysLockFromISR();
472 /* Signaling that data is available in the input queue.*/
473 chnAddFlagsI(qmkusbp, CHN_INPUT_AVAILABLE);
475 /* Posting the filled buffer in the queue.*/
476 ibqPostFullBufferI(&qmkusbp->ibqueue,
477 usbGetReceiveTransactionSizeX(qmkusbp->config->usbp,
478 qmkusbp->config->bulk_out));
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);
485 osalSysUnlockFromISR();
489 * @brief Default data received callback.
490 * @details The application must use this function as callback for the IN
491 * interrupt endpoint.
493 * @param[in] usbp pointer to the @p USBDriver object
494 * @param[in] ep endpoint number
496 void qmkusbInterruptTransmitted(USBDriver *usbp, usbep_t ep) {