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
= {{0x00, 0x96, 0x00, 0x00}, /* 38400. */
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
) || (qmkusbp
->state
!= QMKUSB_READY
)) {
62 /* Checking if there is already a transaction ongoing on the endpoint.*/
63 if (usbGetReceiveStatusI(qmkusbp
->config
->usbp
, qmkusbp
->config
->bulk_in
)) {
67 /* Checking if there is a buffer ready for incoming data.*/
68 buf
= ibqGetEmptyBufferI(&qmkusbp
->ibqueue
);
73 /* Buffer found, starting a new transaction.*/
74 usbStartReceiveI(qmkusbp
->config
->usbp
, qmkusbp
->config
->bulk_out
, buf
, qmkusbp
->ibqueue
.bsize
- sizeof(size_t));
80 * Interface implementation.
83 static size_t _write(void *ip
, const uint8_t *bp
, size_t n
) { return obqWriteTimeout(&((QMKUSBDriver
*)ip
)->obqueue
, bp
, n
, TIME_INFINITE
); }
85 static size_t _read(void *ip
, uint8_t *bp
, size_t n
) { return ibqReadTimeout(&((QMKUSBDriver
*)ip
)->ibqueue
, bp
, n
, TIME_INFINITE
); }
87 static msg_t
_put(void *ip
, uint8_t b
) { return obqPutTimeout(&((QMKUSBDriver
*)ip
)->obqueue
, b
, TIME_INFINITE
); }
89 static msg_t
_get(void *ip
) { return ibqGetTimeout(&((QMKUSBDriver
*)ip
)->ibqueue
, TIME_INFINITE
); }
91 static msg_t
_putt(void *ip
, uint8_t b
, sysinterval_t timeout
) { return obqPutTimeout(&((QMKUSBDriver
*)ip
)->obqueue
, b
, timeout
); }
93 static msg_t
_gett(void *ip
, sysinterval_t timeout
) { return ibqGetTimeout(&((QMKUSBDriver
*)ip
)->ibqueue
, timeout
); }
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
); }
97 static size_t _readt(void *ip
, uint8_t *bp
, size_t n
, sysinterval_t timeout
) { return ibqReadTimeout(&((QMKUSBDriver
*)ip
)->ibqueue
, bp
, n
, timeout
); }
99 static const struct QMKUSBDriverVMT vmt
= {0, _write
, _read
, _put
, _get
, _putt
, _gett
, _writet
, _readt
};
102 * @brief Notification of empty buffer released into the input buffers queue.
104 * @param[in] bqp the buffers queue pointer.
106 static void ibnotify(io_buffers_queue_t
*bqp
) {
107 QMKUSBDriver
*qmkusbp
= bqGetLinkX(bqp
);
108 (void)qmkusb_start_receive(qmkusbp
);
112 * @brief Notification of filled buffer inserted into the output buffers queue.
114 * @param[in] bqp the buffers queue pointer.
116 static void obnotify(io_buffers_queue_t
*bqp
) {
118 QMKUSBDriver
*qmkusbp
= bqGetLinkX(bqp
);
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
)) {
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
);
131 /* Buffer found, starting a new transaction.*/
132 usbStartTransmitI(qmkusbp
->config
->usbp
, qmkusbp
->config
->bulk_in
, buf
, n
);
137 /*===========================================================================*/
138 /* Driver exported functions. */
139 /*===========================================================================*/
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.
148 void qmkusbInit(void) {}
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.
155 * @param[out] qmkusbp pointer to a @p QMKUSBDriver structure
159 void qmkusbObjectInit(QMKUSBDriver
*qmkusbp
, const QMKUSBConfig
*config
) {
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
);
169 * @brief Configures and starts the driver.
171 * @param[in] qmkusbp pointer to a @p QMKUSBDriver object
172 * @param[in] config the serial over USB driver configuration
176 void qmkusbStart(QMKUSBDriver
*qmkusbp
, const QMKUSBConfig
*config
) {
177 USBDriver
*usbp
= config
->usbp
;
179 osalDbgCheck(qmkusbp
!= NULL
);
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
;
188 qmkusbp
->config
= config
;
189 qmkusbp
->state
= QMKUSB_READY
;
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.
198 * @param[in] qmkusbp pointer to a @p QMKUSBDriver object
202 void qmkusbStop(QMKUSBDriver
*qmkusbp
) {
203 USBDriver
*usbp
= qmkusbp
->config
->usbp
;
205 osalDbgCheck(qmkusbp
!= NULL
);
209 osalDbgAssert((qmkusbp
->state
== QMKUSB_STOP
) || (qmkusbp
->state
== QMKUSB_READY
), "invalid state");
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
;
217 qmkusbp
->config
= NULL
;
218 qmkusbp
->state
= QMKUSB_STOP
;
220 /* Enforces a disconnection.*/
221 chnAddFlagsI(qmkusbp
, CHN_DISCONNECTED
);
222 ibqResetI(&qmkusbp
->ibqueue
);
223 obqResetI(&qmkusbp
->obqueue
);
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.
237 * @param[in] qmkusbp pointer to a @p QMKUSBDriver object
241 void qmkusbSuspendHookI(QMKUSBDriver
*qmkusbp
) {
242 chnAddFlagsI(qmkusbp
, CHN_DISCONNECTED
);
243 bqSuspendI(&qmkusbp
->ibqueue
);
244 bqSuspendI(&qmkusbp
->obqueue
);
248 * @brief USB device wakeup handler.
249 * @details Generates a @p CHN_CONNECT event and resumes normal queues
252 * @note If this function is not called from an ISR then an explicit call
253 * to @p osalOsRescheduleS() in necessary afterward.
255 * @param[in] qmkusbp pointer to a @p QMKUSBDriver object
259 void qmkusbWakeupHookI(QMKUSBDriver
*qmkusbp
) {
260 chnAddFlagsI(qmkusbp
, CHN_CONNECTED
);
261 bqResumeX(&qmkusbp
->ibqueue
);
262 bqResumeX(&qmkusbp
->obqueue
);
266 * @brief USB device configured handler.
268 * @param[in] qmkusbp pointer to a @p QMKUSBDriver object
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
);
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.
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.
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
);
302 case CDC_SET_LINE_CODING
:
303 usbSetupTransfer(usbp
, (uint8_t *)&linecoding
, sizeof(linecoding
), NULL
);
305 case CDC_SET_CONTROL_LINE_STATE
:
306 /* Nothing to do, there are no control lines.*/
307 usbSetupTransfer(usbp
, NULL
, 0, NULL
);
317 * @brief SOF handler.
318 * @details The SOF interrupt is used for automatic flushing of incomplete
319 * buffers pending in the output queue.
321 * @param[in] qmkusbp pointer to a @p QMKUSBDriver object
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
)) {
332 /* If there is already a transaction ongoing then another one cannot be
334 if (usbGetTransmitStatusI(qmkusbp
->config
->usbp
, qmkusbp
->config
->bulk_in
)) {
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
)) {
342 uint8_t *buf
= obqGetFullBufferI(&qmkusbp
->obqueue
, &n
);
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
;
350 osalDbgAssert(buf
!= NULL
, "queue is empty");
352 usbStartTransmitI(qmkusbp
->config
->usbp
, qmkusbp
->config
->bulk_in
, buf
, n
);
357 * @brief Default data transmitted callback.
358 * @details The application must use this function as callback for the IN
361 * @param[in] usbp pointer to the @p USBDriver object
362 * @param[in] ep IN endpoint number
364 void qmkusbDataTransmitted(USBDriver
*usbp
, usbep_t ep
) {
367 QMKUSBDriver
*qmkusbp
= usbp
->in_params
[ep
- 1U];
369 if (qmkusbp
== NULL
) {
373 osalSysLockFromISR();
375 /* Signaling that space is available in the output queue.*/
376 chnAddFlagsI(qmkusbp
, CHN_OUTPUT_EMPTY
);
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
);
383 /* Checking if there is a buffer ready for transmission.*/
384 buf
= obqGetFullBufferI(&qmkusbp
->obqueue
, &n
);
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);
400 /* Nothing to transmit.*/
403 osalSysUnlockFromISR();
407 * @brief Default data received callback.
408 * @details The application must use this function as callback for the OUT
411 * @param[in] usbp pointer to the @p USBDriver object
412 * @param[in] ep OUT endpoint number
414 void qmkusbDataReceived(USBDriver
*usbp
, usbep_t ep
) {
415 QMKUSBDriver
*qmkusbp
= usbp
->out_params
[ep
- 1U];
416 if (qmkusbp
== NULL
) {
420 osalSysLockFromISR();
422 /* Signaling that data is available in the input queue.*/
423 chnAddFlagsI(qmkusbp
, CHN_INPUT_AVAILABLE
);
425 /* Posting the filled buffer in the queue.*/
426 ibqPostFullBufferI(&qmkusbp
->ibqueue
, usbGetReceiveTransactionSizeX(qmkusbp
->config
->usbp
, qmkusbp
->config
->bulk_out
));
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
);
433 osalSysUnlockFromISR();
437 * @brief Default data received callback.
438 * @details The application must use this function as callback for the IN
439 * interrupt endpoint.
441 * @param[in] usbp pointer to the @p USBDriver object
442 * @param[in] ep endpoint number
444 void qmkusbInterruptTransmitted(USBDriver
*usbp
, usbep_t ep
) {