aboutsummaryrefslogtreecommitdiff
path: root/lib/chibios-contrib/os/hal/src/hal_usb_hid.c
diff options
context:
space:
mode:
Diffstat (limited to 'lib/chibios-contrib/os/hal/src/hal_usb_hid.c')
-rw-r--r--lib/chibios-contrib/os/hal/src/hal_usb_hid.c589
1 files changed, 589 insertions, 0 deletions
diff --git a/lib/chibios-contrib/os/hal/src/hal_usb_hid.c b/lib/chibios-contrib/os/hal/src/hal_usb_hid.c
new file mode 100644
index 000000000..8b0ac3292
--- /dev/null
+++ b/lib/chibios-contrib/os/hal/src/hal_usb_hid.c
@@ -0,0 +1,589 @@
1/*
2 ChibiOS - Copyright (C) 2016 Jonathan Struebel
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_usb_hid.c
19 * @brief USB HID Driver code.
20 *
21 * @addtogroup USB_HID
22 * @{
23 */
24
25#include "hal.h"
26
27#if (HAL_USE_USB_HID == TRUE) || defined(__DOXYGEN__)
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/* Driver local functions. */
43/*===========================================================================*/
44
45static uint16_t get_hword(uint8_t *p) {
46 uint16_t hw;
47
48 hw = (uint16_t)*p++;
49 hw |= (uint16_t)*p << 8U;
50 return hw;
51}
52
53/*
54 * Interface implementation.
55 */
56
57static size_t write(void *ip, const uint8_t *bp, size_t n) {
58
59 if (usbGetDriverStateI(((USBHIDDriver *)ip)->config->usbp) != USB_ACTIVE) {
60 return 0;
61 }
62
63 return obqWriteTimeout(&((USBHIDDriver *)ip)->obqueue, bp,
64 n, TIME_INFINITE);
65}
66
67static size_t read(void *ip, uint8_t *bp, size_t n) {
68
69 if (usbGetDriverStateI(((USBHIDDriver *)ip)->config->usbp) != USB_ACTIVE) {
70 return 0;
71 }
72
73 return ibqReadTimeout(&((USBHIDDriver *)ip)->ibqueue, bp,
74 n, TIME_INFINITE);
75}
76
77static msg_t put(void *ip, uint8_t b) {
78
79 if (usbGetDriverStateI(((USBHIDDriver *)ip)->config->usbp) != USB_ACTIVE) {
80 return MSG_RESET;
81 }
82
83 return obqPutTimeout(&((USBHIDDriver *)ip)->obqueue, b, TIME_INFINITE);
84}
85
86static msg_t get(void *ip) {
87
88 if (usbGetDriverStateI(((USBHIDDriver *)ip)->config->usbp) != USB_ACTIVE) {
89 return MSG_RESET;
90 }
91
92 return ibqGetTimeout(&((USBHIDDriver *)ip)->ibqueue, TIME_INFINITE);
93}
94
95static msg_t putt(void *ip, uint8_t b, systime_t timeout) {
96
97 if (usbGetDriverStateI(((USBHIDDriver *)ip)->config->usbp) != USB_ACTIVE) {
98 return MSG_RESET;
99 }
100
101 return obqPutTimeout(&((USBHIDDriver *)ip)->obqueue, b, timeout);
102}
103
104static msg_t gett(void *ip, systime_t timeout) {
105
106 if (usbGetDriverStateI(((USBHIDDriver *)ip)->config->usbp) != USB_ACTIVE) {
107 return MSG_RESET;
108 }
109
110 return ibqGetTimeout(&((USBHIDDriver *)ip)->ibqueue, timeout);
111}
112
113static size_t writet(void *ip, const uint8_t *bp, size_t n, systime_t timeout) {
114
115 if (usbGetDriverStateI(((USBHIDDriver *)ip)->config->usbp) != USB_ACTIVE) {
116 return 0;
117 }
118
119 return obqWriteTimeout(&((USBHIDDriver *)ip)->obqueue, bp, n, timeout);
120}
121
122static size_t readt(void *ip, uint8_t *bp, size_t n, systime_t timeout) {
123
124 if (usbGetDriverStateI(((USBHIDDriver *)ip)->config->usbp) != USB_ACTIVE) {
125 return 0;
126 }
127
128 return ibqReadTimeout(&((USBHIDDriver *)ip)->ibqueue, bp, n, timeout);
129}
130
131static msg_t ctl(void *ip, unsigned int operation, void *arg) {
132 (void)ip;
133 (void)operation;
134 (void)arg;
135 return MSG_OK;
136}
137
138static void flush(void *ip) {
139
140 obqFlush(&((USBHIDDriver *)ip)->obqueue);
141}
142
143static const struct USBHIDDriverVMT vmt = {
144 (size_t)0,
145 write, read, put, get,
146 putt, gett, writet, readt,
147 ctl, flush
148};
149
150/**
151 * @brief Notification of empty buffer released into the input buffers queue.
152 *
153 * @param[in] bqp the buffers queue pointer.
154 */
155static void ibnotify(io_buffers_queue_t *bqp) {
156 USBHIDDriver *uhdp = bqGetLinkX(bqp);
157
158 /* If the USB driver is not in the appropriate state then transactions
159 must not be started.*/
160 if ((usbGetDriverStateI(uhdp->config->usbp) != USB_ACTIVE) ||
161 (uhdp->state != HID_READY)) {
162 return;
163 }
164
165 /* Checking if there is already a transaction ongoing on the endpoint.*/
166 if (!usbGetReceiveStatusI(uhdp->config->usbp, uhdp->config->int_out)) {
167 /* Trying to get a free buffer.*/
168 uint8_t *buf = ibqGetEmptyBufferI(&uhdp->ibqueue);
169 if (buf != NULL) {
170 /* Buffer found, starting a new transaction.*/
171 usbStartReceiveI(uhdp->config->usbp, uhdp->config->int_out,
172 buf, SERIAL_USB_BUFFERS_SIZE);
173 }
174 }
175}
176
177/**
178 * @brief Notification of filled buffer inserted into the output buffers queue.
179 *
180 * @param[in] bqp the buffers queue pointer.
181 */
182static void obnotify(io_buffers_queue_t *bqp) {
183 size_t n;
184 USBHIDDriver *uhdp = bqGetLinkX(bqp);
185
186 /* If the USB driver is not in the appropriate state then transactions
187 must not be started.*/
188 if ((usbGetDriverStateI(uhdp->config->usbp) != USB_ACTIVE) ||
189 (uhdp->state != HID_READY)) {
190 return;
191 }
192
193 /* Checking if there is already a transaction ongoing on the endpoint.*/
194 if (!usbGetTransmitStatusI(uhdp->config->usbp, uhdp->config->int_in)) {
195 /* Trying to get a full buffer.*/
196 uint8_t *buf = obqGetFullBufferI(&uhdp->obqueue, &n);
197 if (buf != NULL) {
198 /* Buffer found, starting a new transaction.*/
199 usbStartTransmitI(uhdp->config->usbp, uhdp->config->int_in, buf, n);
200 }
201 }
202}
203
204/*===========================================================================*/
205/* Driver exported functions. */
206/*===========================================================================*/
207
208/**
209 * @brief USB HID Driver initialization.
210 * @note This function is implicitly invoked by @p halInit(), there is
211 * no need to explicitly initialize the driver.
212 *
213 * @init
214 */
215void hidInit(void) {
216}
217
218/**
219 * @brief Initializes a generic full duplex USB HID driver object.
220 * @details The HW dependent part of the initialization has to be performed
221 * outside, usually in the hardware initialization code.
222 *
223 * @param[out] uhdp pointer to a @p USBHIDDriver structure
224 *
225 * @init
226 */
227void hidObjectInit(USBHIDDriver *uhdp) {
228
229 uhdp->vmt = &vmt;
230 osalEventObjectInit(&uhdp->event);
231 uhdp->state = HID_STOP;
232 ibqObjectInit(&uhdp->ibqueue, true, uhdp->ib,
233 USB_HID_BUFFERS_SIZE, USB_HID_BUFFERS_NUMBER,
234 ibnotify, uhdp);
235 obqObjectInit(&uhdp->obqueue, true, uhdp->ob,
236 USB_HID_BUFFERS_SIZE, USB_HID_BUFFERS_NUMBER,
237 obnotify, uhdp);
238}
239
240/**
241 * @brief Configures and starts the driver.
242 *
243 * @param[in] uhdp pointer to a @p USBHIDDriver object
244 * @param[in] config the USB HID driver configuration
245 *
246 * @api
247 */
248void hidStart(USBHIDDriver *uhdp, const USBHIDConfig *config) {
249 USBDriver *usbp = config->usbp;
250
251 osalDbgCheck(uhdp != NULL);
252
253 osalSysLock();
254 osalDbgAssert((uhdp->state == HID_STOP) || (uhdp->state == HID_READY),
255 "invalid state");
256 usbp->in_params[config->int_in - 1U] = uhdp;
257 usbp->out_params[config->int_out - 1U] = uhdp;
258 uhdp->config = config;
259 uhdp->state = HID_READY;
260 osalSysUnlock();
261}
262
263/**
264 * @brief Stops the driver.
265 * @details Any thread waiting on the driver's queues will be awakened with
266 * the message @p MSG_RESET.
267 *
268 * @param[in] uhdp pointer to a @p USBHIDDriver object
269 *
270 * @api
271 */
272void hidStop(USBHIDDriver *uhdp) {
273 USBDriver *usbp = uhdp->config->usbp;
274
275 osalDbgCheck(uhdp != NULL);
276
277 osalSysLock();
278 osalDbgAssert((uhdp->state == HID_STOP) || (uhdp->state == HID_READY),
279 "invalid state");
280
281 /* Driver in stopped state.*/
282 usbp->in_params[uhdp->config->int_in - 1U] = NULL;
283 usbp->out_params[uhdp->config->int_out - 1U] = NULL;
284 uhdp->state = HID_STOP;
285
286 /* Enforces a disconnection.*/
287 hidDisconnectI(uhdp);
288 osalOsRescheduleS();
289 osalSysUnlock();
290}
291
292/**
293 * @brief USB device disconnection handler.
294 * @note If this function is not called from an ISR then an explicit call
295 * to @p osalOsRescheduleS() in necessary afterward.
296 *
297 * @param[in] uhdp pointer to a @p USBHIDDriver object
298 *
299 * @iclass
300 */
301void hidDisconnectI(USBHIDDriver *uhdp) {
302
303 /* Queues reset in order to signal the driver stop to the application.*/
304 chnAddFlagsI(uhdp, CHN_DISCONNECTED);
305 ibqResetI(&uhdp->ibqueue);
306 obqResetI(&uhdp->obqueue);
307}
308
309/**
310 * @brief USB device configured handler.
311 *
312 * @param[in] uhdp pointer to a @p USBHIDDriver object
313 *
314 * @iclass
315 */
316void hidConfigureHookI(USBHIDDriver *uhdp) {
317 uint8_t *buf;
318
319 ibqResetI(&uhdp->ibqueue);
320 obqResetI(&uhdp->obqueue);
321 chnAddFlagsI(uhdp, CHN_CONNECTED);
322
323 /* Starts the first OUT transaction immediately.*/
324 buf = ibqGetEmptyBufferI(&uhdp->ibqueue);
325
326 osalDbgAssert(buf != NULL, "no free buffer");
327
328 usbStartReceiveI(uhdp->config->usbp, uhdp->config->int_out,
329 buf, USB_HID_BUFFERS_SIZE);
330}
331
332/**
333 * @brief Default requests hook.
334 * @details Applications wanting to use the USB HID driver can use
335 * this function at the end of the application specific
336 * requests hook. The HID_* requests handled here do not
337 * transfer any data to the application.
338 * The following requests are handled:
339 * - HID_GET_IDLE.
340 * - HID_GET_PROTOCOL.
341 * - HID_SET_REPORT.
342 * - HID_SET_IDLE.
343 * - HID_SET_PROTOCOL.
344 * - USB_REQ_GET_DESCRIPTOR.
345 * .
346 *
347 * @param[in] usbp pointer to the @p USBDriver object
348 * @return The hook status.
349 * @retval true Message handled internally.
350 * @retval false Message not handled.
351 */
352bool hidRequestsHook(USBDriver *usbp) {
353 const USBDescriptor *dp;
354
355 if ((usbp->setup[0] & USB_RTYPE_TYPE_MASK) == USB_RTYPE_TYPE_CLASS) {
356 switch (usbp->setup[1]) {
357 case HID_GET_IDLE:
358 usbSetupTransfer(usbp, NULL, 0, NULL);
359 return true;
360 case HID_GET_PROTOCOL:
361 return true;
362 case HID_SET_REPORT:
363 usbSetupTransfer(usbp, NULL, 0, NULL);
364 return true;
365 case HID_SET_IDLE:
366 usbSetupTransfer(usbp, NULL, 0, NULL);
367 return true;
368 case HID_SET_PROTOCOL:
369 return true;
370 default:
371 return false;
372 }
373 }
374
375 /* GET_DESCRIPTOR from interface not handled by default so handle it here */
376 if (((usbp->setup[0] & USB_RTYPE_DIR_MASK) == USB_RTYPE_DIR_DEV2HOST) &&
377 ((usbp->setup[0] & USB_RTYPE_RECIPIENT_MASK) == USB_RTYPE_RECIPIENT_INTERFACE)) {
378 switch (usbp->setup[1]) {
379 case USB_REQ_GET_DESCRIPTOR:
380 dp = usbp->config->get_descriptor_cb(usbp, usbp->setup[3], usbp->setup[2],
381 get_hword(&usbp->setup[4]));
382 if (dp == NULL)
383 return false;
384
385 usbSetupTransfer(usbp, (uint8_t *)dp->ud_string, dp->ud_size, NULL);
386 return true;
387 default:
388 return false;
389 }
390 }
391 return false;
392}
393
394/**
395 * @brief Default data transmitted callback.
396 * @details The application must use this function as callback for the IN
397 * data endpoint.
398 *
399 * @param[in] usbp pointer to the @p USBDriver object
400 * @param[in] ep IN endpoint number
401 */
402void hidDataTransmitted(USBDriver *usbp, usbep_t ep) {
403 uint8_t *buf;
404 size_t n;
405 USBHIDDriver *uhdp = usbp->in_params[ep - 1U];
406
407 if (uhdp == NULL) {
408 return;
409 }
410
411 osalSysLockFromISR();
412
413 /* Signaling that space is available in the output queue.*/
414 chnAddFlagsI(uhdp, CHN_OUTPUT_EMPTY);
415
416 /* Freeing the buffer just transmitted, if it was not a zero size packet.*/
417 if (usbp->epc[ep]->in_state->txsize > 0U) {
418 obqReleaseEmptyBufferI(&uhdp->obqueue);
419 }
420
421 /* Checking if there is a buffer ready for transmission.*/
422 buf = obqGetFullBufferI(&uhdp->obqueue, &n);
423
424 if (buf != NULL) {
425 /* The endpoint cannot be busy, we are in the context of the callback,
426 so it is safe to transmit without a check.*/
427 usbStartTransmitI(usbp, ep, buf, n);
428 }
429 else if ((usbp->epc[ep]->in_state->txsize > 0U) &&
430 ((usbp->epc[ep]->in_state->txsize &
431 ((size_t)usbp->epc[ep]->in_maxsize - 1U)) == 0U)) {
432 /* Transmit zero sized packet in case the last one has maximum allowed
433 size. Otherwise the recipient may expect more data coming soon and
434 not return buffered data to app. See section 5.8.3 Bulk Transfer
435 Packet Size Constraints of the USB Specification document.*/
436 usbStartTransmitI(usbp, ep, usbp->setup, 0);
437
438 }
439 else {
440 /* Nothing to transmit.*/
441 }
442
443 osalSysUnlockFromISR();
444}
445
446/**
447 * @brief Default data received callback.
448 * @details The application must use this function as callback for the OUT
449 * data endpoint.
450 *
451 * @param[in] usbp pointer to the @p USBDriver object
452 * @param[in] ep OUT endpoint number
453 */
454void hidDataReceived(USBDriver *usbp, usbep_t ep) {
455 uint8_t *buf;
456 USBHIDDriver *uhdp = usbp->out_params[ep - 1U];
457
458 if (uhdp == NULL) {
459 return;
460 }
461
462 osalSysLockFromISR();
463
464 /* Signaling that data is available in the input queue.*/
465 chnAddFlagsI(uhdp, CHN_INPUT_AVAILABLE);
466
467 /* Posting the filled buffer in the queue.*/
468 ibqPostFullBufferI(&uhdp->ibqueue,
469 usbGetReceiveTransactionSizeX(uhdp->config->usbp, ep));
470
471 /* The endpoint cannot be busy, we are in the context of the callback,
472 so a packet is in the buffer for sure. Trying to get a free buffer
473 for the next transaction.*/
474 buf = ibqGetEmptyBufferI(&uhdp->ibqueue);
475 if (buf != NULL) {
476 /* Buffer found, starting a new transaction.*/
477 usbStartReceiveI(uhdp->config->usbp, ep, buf, USB_HID_BUFFERS_SIZE);
478 }
479
480 osalSysUnlockFromISR();
481}
482
483/**
484 * @brief Write HID Report
485 * @details The function writes data from a buffer to an output queue. The
486 * operation completes when the specified amount of data has been
487 * transferred or if the queue has been reset.
488 *
489 * @param[in] uhdp pointer to the @p USBHIDDriver object
490 * @param[in] bp pointer to the report data buffer
491 * @param[in] n the maximum amount of data to be transferred, the
492 * value 0 is reserved
493 * @return The number of bytes effectively transferred.
494 * @retval 0 if a timeout occurred.
495 *
496 * @api
497 */
498size_t hidWriteReport(USBHIDDriver *uhdp, uint8_t *bp, size_t n) {
499 size_t val;
500
501 val = uhdp->vmt->write(uhdp, bp, n);
502
503 if (val > 0)
504 uhdp->vmt->flush(uhdp);
505
506 return val;
507}
508
509/**
510 * @brief Write HID report with timeout
511 * @details The function writes data from a buffer to an output queue. The
512 * operation completes when the specified amount of data has been
513 * transferred or after the specified timeout or if the queue has
514 * been reset.
515 *
516 * @param[in] uhdp pointer to the @p USBHIDDriver object
517 * @param[in] bp pointer to the report data buffer
518 * @param[in] n the maximum amount of data to be transferred, the
519 * value 0 is reserved
520 * @param[in] timeout the number of ticks before the operation timeouts,
521 * the following special values are allowed:
522 * - @a TIME_IMMEDIATE immediate timeout.
523 * - @a TIME_INFINITE no timeout.
524 * .
525 * @return The number of bytes effectively transferred.
526 * @retval 0 if a timeout occurred.
527 *
528 * @api
529 */
530size_t hidWriteReportt(USBHIDDriver *uhdp, uint8_t *bp, size_t n, systime_t timeout) {
531 size_t val;
532
533 val = uhdp->vmt->writet(uhdp, bp, n, timeout);
534
535 if (val > 0)
536 uhdp->vmt->flush(uhdp);
537
538 return val;
539}
540
541/**
542 * @brief Read HID report
543 * @details The function reads data from an input queue into a buffer.
544 * The operation completes when the specified amount of data has been
545 * transferred or if the queue has been reset.
546 *
547 * @param[in] uhdp pointer to the @p input_buffers_queue_t object
548 * @param[out] bp pointer to the data buffer
549 * @param[in] n the maximum amount of data to be transferred, the
550 * value 0 is reserved
551 * @return The number of bytes effectively transferred.
552 * @retval 0 if a timeout occurred.
553 *
554 * @api
555 */
556size_t hidReadReport(USBHIDDriver *uhdp, uint8_t *bp, size_t n) {
557
558 return uhdp->vmt->read(uhdp, bp, n);
559}
560
561/**
562 * @brief Read HID report with timeout
563 * @details The function reads data from an input queue into a buffer.
564 * The operation completes when the specified amount of data has been
565 * transferred or after the specified timeout or if the queue has
566 * been reset.
567 *
568 * @param[in] uhdp pointer to the @p input_buffers_queue_t object
569 * @param[out] bp pointer to the data buffer
570 * @param[in] n the maximum amount of data to be transferred, the
571 * value 0 is reserved
572 * @param[in] timeout the number of ticks before the operation timeouts,
573 * the following special values are allowed:
574 * - @a TIME_IMMEDIATE immediate timeout.
575 * - @a TIME_INFINITE no timeout.
576 * .
577 * @return The number of bytes effectively transferred.
578 * @retval 0 if a timeout occurred.
579 *
580 * @api
581 */
582size_t hidReadReportt(USBHIDDriver *uhdp, uint8_t *bp, size_t n, systime_t timeout) {
583
584 return uhdp->vmt->readt(uhdp, bp, n, timeout);
585}
586
587#endif /* HAL_USE_USB_HID == TRUE */
588
589/** @} */