source: mainline/uspace/drv/usbhid/usbhid.c@ 6c6a95d2

lfn serial ticket/834-toolchain-update topic/msim-upgrade topic/simplify-dev-export
Last change on this file since 6c6a95d2 was 054537b, checked in by Lubos Slovak <lubos.slovak@…>, 14 years ago

Small fix in USB HID driver.

  • Property mode set to 100644
File size: 16.7 KB
RevLine 
[966acede]1/*
2 * Copyright (c) 2011 Lubos Slovak
3 * All rights reserved.
4 *
5 * Redistribution and use in source and binary forms, with or without
6 * modification, are permitted provided that the following conditions
7 * are met:
8 *
9 * - Redistributions of source code must retain the above copyright
10 * notice, this list of conditions and the following disclaimer.
11 * - Redistributions in binary form must reproduce the above copyright
12 * notice, this list of conditions and the following disclaimer in the
13 * documentation and/or other materials provided with the distribution.
14 * - The name of the author may not be used to endorse or promote products
15 * derived from this software without specific prior written permission.
16 *
17 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
18 * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
19 * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
20 * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
21 * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
22 * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
23 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
24 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
25 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
26 * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
27 */
28
29/** @addtogroup drvusbhid
30 * @{
31 */
32/**
33 * @file
34 * USB HID driver API.
35 */
36
37#include <usb/debug.h>
38#include <usb/classes/classes.h>
[61257f4]39#include <usb/classes/hid.h>
40#include <usb/classes/hidparser.h>
41#include <usb/classes/hidreport.h>
[7f2e33a]42#include <usb/classes/hidreq.h>
[61257f4]43#include <errno.h>
[f76153ce]44#include <str_error.h>
[966acede]45
46#include "usbhid.h"
47
[61257f4]48#include "kbd/kbddev.h"
[dd10e07]49#include "generic/hiddev.h"
[e9f0348]50#include "mouse/mousedev.h"
[62bd8d3]51#include "subdrivers.h"
[61257f4]52
[966acede]53/*----------------------------------------------------------------------------*/
54
55/* Array of endpoints expected on the device, NULL terminated. */
[fec47d4]56usb_endpoint_description_t *usb_hid_endpoints[USB_HID_POLL_EP_COUNT + 1] = {
57 &usb_hid_kbd_poll_endpoint_description,
58 &usb_hid_mouse_poll_endpoint_description,
[61257f4]59 &usb_hid_generic_poll_endpoint_description,
[966acede]60 NULL
61};
62
[f76153ce]63static const int USB_HID_MAX_SUBDRIVERS = 10;
64
[966acede]65/*----------------------------------------------------------------------------*/
66
[60c0573]67static int usb_hid_set_boot_kbd_subdriver(usb_hid_dev_t *hid_dev)
[61257f4]68{
[e7df6cd]69 assert(hid_dev != NULL && hid_dev->subdriver_count == 0);
[61257f4]70
[60c0573]71 hid_dev->subdrivers = (usb_hid_subdriver_t *)malloc(
72 sizeof(usb_hid_subdriver_t));
73 if (hid_dev->subdrivers == NULL) {
74 return ENOMEM;
[61257f4]75 }
76
[60c0573]77 // set the init callback
78 hid_dev->subdrivers[0].init = usb_kbd_init;
79
80 // set the polling callback
81 hid_dev->subdrivers[0].poll = usb_kbd_polling_callback;
82
83 // set the polling ended callback
84 hid_dev->subdrivers[0].poll_end = NULL;
85
86 // set the deinit callback
87 hid_dev->subdrivers[0].deinit = usb_kbd_deinit;
88
89 // set subdriver count
90 hid_dev->subdriver_count = 1;
91
92 return EOK;
93}
94
95/*----------------------------------------------------------------------------*/
96
97static int usb_hid_set_boot_mouse_subdriver(usb_hid_dev_t *hid_dev)
98{
[e7df6cd]99 assert(hid_dev != NULL && hid_dev->subdriver_count == 0);
[60c0573]100
101 hid_dev->subdrivers = (usb_hid_subdriver_t *)malloc(
102 sizeof(usb_hid_subdriver_t));
103 if (hid_dev->subdrivers == NULL) {
104 return ENOMEM;
[61257f4]105 }
106
[60c0573]107 // set the init callback
108 hid_dev->subdrivers[0].init = usb_mouse_init;
109
110 // set the polling callback
111 hid_dev->subdrivers[0].poll = usb_mouse_polling_callback;
112
113 // set the polling ended callback
114 hid_dev->subdrivers[0].poll_end = NULL;
115
116 // set the deinit callback
117 hid_dev->subdrivers[0].deinit = usb_mouse_deinit;
118
119 // set subdriver count
120 hid_dev->subdriver_count = 1;
121
122 return EOK;
123}
124
125/*----------------------------------------------------------------------------*/
126
127static int usb_hid_set_generic_hid_subdriver(usb_hid_dev_t *hid_dev)
128{
[e7df6cd]129 assert(hid_dev != NULL && hid_dev->subdriver_count == 0);
[60c0573]130
131 hid_dev->subdrivers = (usb_hid_subdriver_t *)malloc(
132 sizeof(usb_hid_subdriver_t));
133 if (hid_dev->subdrivers == NULL) {
134 return ENOMEM;
135 }
136
137 // set the init callback
[3facf63a]138 hid_dev->subdrivers[0].init = usb_generic_hid_init;
[60c0573]139
140 // set the polling callback
141 hid_dev->subdrivers[0].poll = usb_generic_hid_polling_callback;
142
143 // set the polling ended callback
144 hid_dev->subdrivers[0].poll_end = NULL;
145
146 // set the deinit callback
147 hid_dev->subdrivers[0].deinit = NULL;
148
[f76153ce]149 // set subdriver count
150 hid_dev->subdriver_count = 1;
151
[60c0573]152 return EOK;
153}
154
155/*----------------------------------------------------------------------------*/
156
[f76153ce]157static bool usb_hid_ids_match(usb_hid_dev_t *hid_dev,
158 const usb_hid_subdriver_mapping_t *mapping)
159{
[36f737a]160 assert(hid_dev != NULL);
161 assert(hid_dev->usb_dev != NULL);
162
163 return (hid_dev->usb_dev->descriptors.device.vendor_id
164 == mapping->vendor_id
165 && hid_dev->usb_dev->descriptors.device.product_id
166 == mapping->product_id);
[f76153ce]167}
168
169/*----------------------------------------------------------------------------*/
170
171static bool usb_hid_path_matches(usb_hid_dev_t *hid_dev,
[e3b5129]172 const usb_hid_subdriver_mapping_t *mapping)
[f76153ce]173{
174 assert(hid_dev != NULL);
[e3b5129]175 assert(mapping != NULL);
[f76153ce]176
177 usb_hid_report_path_t *usage_path = usb_hid_report_path();
178 if (usage_path == NULL) {
[1cbb4b7]179 usb_log_debug("Failed to create usage path.\n");
[f76153ce]180 return false;
181 }
[777e336]182 int i = 0;
[e3b5129]183 while (mapping->usage_path[i].usage != 0
184 || mapping->usage_path[i].usage_page != 0) {
[f76153ce]185 if (usb_hid_report_path_append_item(usage_path,
[e3b5129]186 mapping->usage_path[i].usage_page,
187 mapping->usage_path[i].usage) != EOK) {
[1cbb4b7]188 usb_log_debug("Failed to append to usage path.\n");
[f76153ce]189 usb_hid_report_path_free(usage_path);
190 return false;
191 }
[777e336]192 ++i;
[f76153ce]193 }
194
[e3b5129]195 if (mapping->report_id >= 0) {
196 usb_hid_report_path_set_report_id(usage_path,
197 mapping->report_id);
[f76153ce]198 }
199
[e60436b]200 assert(hid_dev->report != NULL);
[1cbb4b7]201
[e3b5129]202 usb_log_debug("Compare flags: %d\n", mapping->compare);
[e60436b]203 size_t size = usb_hid_report_input_length(hid_dev->report, usage_path,
[e3b5129]204 mapping->compare);
[4125b7d]205 usb_log_debug("Size of the input report: %zuB\n", size);
[f76153ce]206
207 usb_hid_report_path_free(usage_path);
208
[1cbb4b7]209 return (size > 0);
[f76153ce]210}
211
212/*----------------------------------------------------------------------------*/
213
214static int usb_hid_save_subdrivers(usb_hid_dev_t *hid_dev,
215 const usb_hid_subdriver_t **subdrivers, int count)
[60c0573]216{
[f76153ce]217 int i;
218
219 if (count <= 0) {
220 hid_dev->subdriver_count = 0;
221 hid_dev->subdrivers = NULL;
222 return EOK;
223 }
224
225 hid_dev->subdrivers = (usb_hid_subdriver_t *)malloc(count *
226 sizeof(usb_hid_subdriver_t));
227 if (hid_dev->subdrivers == NULL) {
228 return ENOMEM;
229 }
230
231 for (i = 0; i < count; ++i) {
232 hid_dev->subdrivers[i].init = subdrivers[i]->init;
233 hid_dev->subdrivers[i].deinit = subdrivers[i]->deinit;
234 hid_dev->subdrivers[i].poll = subdrivers[i]->poll;
235 hid_dev->subdrivers[i].poll_end = subdrivers[i]->poll_end;
236 }
237
238 hid_dev->subdriver_count = count;
239
[60c0573]240 return EOK;
[61257f4]241}
[966acede]242
243/*----------------------------------------------------------------------------*/
244
[f76153ce]245static int usb_hid_find_subdrivers(usb_hid_dev_t *hid_dev)
246{
[e7df6cd]247 assert(hid_dev != NULL);
248
[f76153ce]249 const usb_hid_subdriver_t *subdrivers[USB_HID_MAX_SUBDRIVERS];
250
251 int i = 0, count = 0;
252 const usb_hid_subdriver_mapping_t *mapping = &usb_hid_subdrivers[i];
[4bb9fd2]253
254 bool ids_matched;
255 bool matched;
[f76153ce]256
257 while (count < USB_HID_MAX_SUBDRIVERS &&
258 (mapping->usage_path != NULL
[d0a6e54]259 || mapping->vendor_id >= 0 || mapping->product_id >= 0)) {
[f76153ce]260 // check the vendor & product ID
[d0a6e54]261 if (mapping->vendor_id >= 0 && mapping->product_id < 0) {
262 usb_log_warning("Missing Product ID for Vendor ID %d\n",
[f76153ce]263 mapping->vendor_id);
264 return EINVAL;
265 }
[d0a6e54]266 if (mapping->product_id >= 0 && mapping->vendor_id < 0) {
267 usb_log_warning("Missing Vendor ID for Product ID %d\n",
[f76153ce]268 mapping->product_id);
269 return EINVAL;
270 }
271
[4bb9fd2]272 ids_matched = false;
273 matched = false;
274
[d0a6e54]275 if (mapping->vendor_id >= 0) {
276 assert(mapping->product_id >= 0);
[777e336]277 usb_log_debug("Comparing device against vendor ID %u"
278 " and product ID %u.\n", mapping->vendor_id,
[f76153ce]279 mapping->product_id);
280 if (usb_hid_ids_match(hid_dev, mapping)) {
[4bb9fd2]281 usb_log_debug("IDs matched.\n");
282 ids_matched = true;
[f76153ce]283 }
284 }
285
286 if (mapping->usage_path != NULL) {
[1cbb4b7]287 usb_log_debug("Comparing device against usage path.\n");
[e3b5129]288 if (usb_hid_path_matches(hid_dev, mapping)) {
[4bb9fd2]289 // does not matter if IDs were matched
290 matched = true;
[f76153ce]291 }
[4bb9fd2]292 } else {
293 // matched only if IDs were matched and there is no path
294 matched = ids_matched;
[f76153ce]295 }
[4bb9fd2]296
297 if (matched) {
298 subdrivers[count++] = &mapping->subdriver;
[f76153ce]299 }
[4bb9fd2]300
[f76153ce]301 mapping = &usb_hid_subdrivers[++i];
302 }
303
304 // we have all subdrivers determined, save them into the hid device
305 return usb_hid_save_subdrivers(hid_dev, subdrivers, count);
306}
307
308/*----------------------------------------------------------------------------*/
309
[61257f4]310static int usb_hid_check_pipes(usb_hid_dev_t *hid_dev, usb_device_t *dev)
[966acede]311{
[e7df6cd]312 assert(hid_dev != NULL && dev != NULL);
313
[f76153ce]314 int rc = EOK;
[60c0573]315
[61257f4]316 if (dev->pipes[USB_HID_KBD_POLL_EP_NO].present) {
317 usb_log_debug("Found keyboard endpoint.\n");
[60c0573]318 // save the pipe index
[61257f4]319 hid_dev->poll_pipe_index = USB_HID_KBD_POLL_EP_NO;
320 } else if (dev->pipes[USB_HID_MOUSE_POLL_EP_NO].present) {
321 usb_log_debug("Found mouse endpoint.\n");
[60c0573]322 // save the pipe index
[61257f4]323 hid_dev->poll_pipe_index = USB_HID_MOUSE_POLL_EP_NO;
324 } else if (dev->pipes[USB_HID_GENERIC_POLL_EP_NO].present) {
325 usb_log_debug("Found generic HID endpoint.\n");
[60c0573]326 // save the pipe index
[61257f4]327 hid_dev->poll_pipe_index = USB_HID_GENERIC_POLL_EP_NO;
328 } else {
[60c0573]329 usb_log_error("None of supported endpoints found - probably"
[61257f4]330 " not a supported device.\n");
[60c0573]331 rc = ENOTSUP;
[61257f4]332 }
333
[60c0573]334 return rc;
[61257f4]335}
336
337/*----------------------------------------------------------------------------*/
338
[60c0573]339usb_hid_dev_t *usb_hid_new(void)
340{
341 usb_hid_dev_t *hid_dev = (usb_hid_dev_t *)calloc(1,
342 sizeof(usb_hid_dev_t));
343
344 if (hid_dev == NULL) {
345 usb_log_fatal("No memory!\n");
346 return NULL;
347 }
348
[e60436b]349 hid_dev->report = (usb_hid_report_t *)(malloc(sizeof(
[e50cd7f]350 usb_hid_report_t)));
[e60436b]351 if (hid_dev->report == NULL) {
[60c0573]352 usb_log_fatal("No memory!\n");
353 free(hid_dev);
354 return NULL;
355 }
356
[f76153ce]357 hid_dev->poll_pipe_index = -1;
358
[60c0573]359 return hid_dev;
360}
361
362/*----------------------------------------------------------------------------*/
363
[61257f4]364int usb_hid_init(usb_hid_dev_t *hid_dev, usb_device_t *dev)
365{
[60c0573]366 int rc, i;
[61257f4]367
368 usb_log_debug("Initializing HID structure...\n");
369
370 if (hid_dev == NULL) {
371 usb_log_error("Failed to init HID structure: no structure given"
372 ".\n");
373 return EINVAL;
374 }
375
376 if (dev == NULL) {
377 usb_log_error("Failed to init HID structure: no USB device"
378 " given.\n");
379 return EINVAL;
380 }
381
382 /* The USB device should already be initialized, save it in structure */
383 hid_dev->usb_dev = dev;
384
385 rc = usb_hid_check_pipes(hid_dev, dev);
386 if (rc != EOK) {
[aaf6155]387 //usb_hid_free(&hid_dev);
[61257f4]388 return rc;
389 }
[e50cd7f]390
[f76153ce]391 /* Get the report descriptor and parse it. */
392 rc = usb_hid_process_report_descriptor(hid_dev->usb_dev,
[e60436b]393 hid_dev->report);
[f76153ce]394
395 bool fallback = false;
396
397 if (rc == EOK) {
398 // try to find subdrivers that may want to handle this device
399 rc = usb_hid_find_subdrivers(hid_dev);
400 if (rc != EOK || hid_dev->subdriver_count == 0) {
401 // try to fall back to the boot protocol if available
402 usb_log_info("No subdrivers found to handle this"
403 " device.\n");
404 fallback = true;
[aaf6155]405 assert(hid_dev->subdrivers == NULL);
406 assert(hid_dev->subdriver_count == 0);
[f76153ce]407 }
408 } else {
409 usb_log_error("Failed to parse Report descriptor.\n");
410 // try to fall back to the boot protocol if available
411 fallback = true;
412 }
413
414 // TODO: remove the mouse hack
415 if (hid_dev->poll_pipe_index == USB_HID_MOUSE_POLL_EP_NO ||
416 fallback) {
417 // fall back to boot protocol
418 switch (hid_dev->poll_pipe_index) {
419 case USB_HID_KBD_POLL_EP_NO:
420 usb_log_info("Falling back to kbd boot protocol.\n");
421 rc = usb_kbd_set_boot_protocol(hid_dev);
422 if (rc == EOK) {
423 rc = usb_hid_set_boot_kbd_subdriver(hid_dev);
[60c0573]424 }
[f76153ce]425 break;
426 case USB_HID_MOUSE_POLL_EP_NO:
427 usb_log_info("Falling back to mouse boot protocol.\n");
428 rc = usb_mouse_set_boot_protocol(hid_dev);
429 if (rc == EOK) {
430 rc = usb_hid_set_boot_mouse_subdriver(hid_dev);
431 }
432 break;
433 default:
434 assert(hid_dev->poll_pipe_index
435 == USB_HID_GENERIC_POLL_EP_NO);
436
437 /* TODO: this has no meaning if the report descriptor
438 is not parsed */
439 usb_log_info("Falling back to generic HID driver.\n");
440 rc = usb_hid_set_generic_hid_subdriver(hid_dev);
[e9f0348]441 }
[61257f4]442 }
443
[f76153ce]444 if (rc != EOK) {
445 usb_log_error("No subdriver for handling this device could be"
446 " initialized: %s.\n", str_error(rc));
[aaf6155]447 usb_log_debug("Subdriver count: %d\n",
448 hid_dev->subdriver_count);
449 //usb_hid_free(&hid_dev);
[f76153ce]450 } else {
451 bool ok = false;
452
453 usb_log_debug("Subdriver count: %d\n",
454 hid_dev->subdriver_count);
455
456 for (i = 0; i < hid_dev->subdriver_count; ++i) {
457 if (hid_dev->subdrivers[i].init != NULL) {
458 usb_log_debug("Initializing subdriver %d.\n",i);
459 rc = hid_dev->subdrivers[i].init(hid_dev);
460 if (rc != EOK) {
461 usb_log_warning("Failed to initialize"
462 " HID subdriver structure.\n");
463 } else {
464 // at least one subdriver initialized
465 ok = true;
466 }
467 } else {
468 ok = true;
469 }
470 }
471
472 rc = (ok) ? EOK : -1; // what error to report
473 }
474
[61257f4]475 return rc;
476}
477
478/*----------------------------------------------------------------------------*/
479
[60c0573]480bool usb_hid_polling_callback(usb_device_t *dev, uint8_t *buffer,
481 size_t buffer_size, void *arg)
482{
483 int i;
484
485 if (dev == NULL || arg == NULL || buffer == NULL) {
486 usb_log_error("Missing arguments to polling callback.\n");
487 return false;
488 }
489
490 usb_hid_dev_t *hid_dev = (usb_hid_dev_t *)arg;
491
[054537b]492 int allocated = (hid_dev->input_report != NULL);
[31cfee16]493
494 if (!allocated
495 || hid_dev->input_report_size < buffer_size) {
496 uint8_t *input_old = hid_dev->input_report;
497 uint8_t *input_new = (uint8_t *)malloc(buffer_size);
498
499 if (input_new == NULL) {
500 usb_log_error("Failed to allocate space for input "
501 "buffer. This event may not be reported\n");
502 memset(hid_dev->input_report, 0,
503 hid_dev->input_report_size);
504 } else {
505 memcpy(input_new, input_old,
506 hid_dev->input_report_size);
507 hid_dev->input_report = input_new;
508 if (allocated) {
509 free(input_old);
510 }
511 }
512 }
513
514 /*! @todo This should probably be atomic. */
515 memcpy(hid_dev->input_report, buffer, buffer_size);
516 hid_dev->input_report_size = buffer_size;
517
[60c0573]518 bool cont = false;
519
520 // continue if at least one of the subdrivers want to continue
521 for (i = 0; i < hid_dev->subdriver_count; ++i) {
522 if (hid_dev->subdrivers[i].poll != NULL
523 && hid_dev->subdrivers[i].poll(hid_dev, buffer,
524 buffer_size)) {
525 cont = true;
526 }
527 }
528
529 return cont;
530}
531
532/*----------------------------------------------------------------------------*/
533
[61257f4]534void usb_hid_polling_ended_callback(usb_device_t *dev, bool reason,
535 void *arg)
536{
[60c0573]537 int i;
538
[61257f4]539 if (dev == NULL || arg == NULL) {
540 return;
541 }
542
543 usb_hid_dev_t *hid_dev = (usb_hid_dev_t *)arg;
544
[60c0573]545 for (i = 0; i < hid_dev->subdriver_count; ++i) {
546 if (hid_dev->subdrivers[i].poll_end != NULL) {
547 hid_dev->subdrivers[i].poll_end(hid_dev, reason);
548 }
549 }
550
[61257f4]551 usb_hid_free(&hid_dev);
552}
553
554/*----------------------------------------------------------------------------*/
555
[31cfee16]556//const char *usb_hid_get_function_name(const usb_hid_dev_t *hid_dev)
557//{
558// switch (hid_dev->poll_pipe_index) {
559// case USB_HID_KBD_POLL_EP_NO:
560// return HID_KBD_FUN_NAME;
561// break;
562// case USB_HID_MOUSE_POLL_EP_NO:
563// return HID_MOUSE_FUN_NAME;
564// break;
565// default:
566// return HID_GENERIC_FUN_NAME;
567// }
568//}
[61257f4]569
570/*----------------------------------------------------------------------------*/
571
[31cfee16]572//const char *usb_hid_get_class_name(const usb_hid_dev_t *hid_dev)
573//{
574// // this means that only boot protocol keyboards will be connected
575// // to the console; there is probably no better way to do this
576
577// switch (hid_dev->poll_pipe_index) {
578// case USB_HID_KBD_POLL_EP_NO:
579// return HID_KBD_CLASS_NAME;
580// break;
581// case USB_HID_MOUSE_POLL_EP_NO:
582// return HID_MOUSE_CLASS_NAME;
583// break;
584// default:
585// return HID_GENERIC_CLASS_NAME;
586// }
587//}
[61257f4]588
589/*----------------------------------------------------------------------------*/
590
591void usb_hid_free(usb_hid_dev_t **hid_dev)
592{
[60c0573]593 int i;
594
[61257f4]595 if (hid_dev == NULL || *hid_dev == NULL) {
596 return;
597 }
598
[aaf6155]599 usb_log_debug("Subdrivers: %p, subdriver count: %d\n",
600 (*hid_dev)->subdrivers, (*hid_dev)->subdriver_count);
601
[f76153ce]602 assert((*hid_dev)->subdrivers != NULL
603 || (*hid_dev)->subdriver_count == 0);
604
[60c0573]605 for (i = 0; i < (*hid_dev)->subdriver_count; ++i) {
606 if ((*hid_dev)->subdrivers[i].deinit != NULL) {
607 (*hid_dev)->subdrivers[i].deinit(*hid_dev);
608 }
[61257f4]609 }
[f76153ce]610
611 // free the subdrivers info
612 if ((*hid_dev)->subdrivers != NULL) {
[1cbb4b7]613 free((*hid_dev)->subdrivers);
[f76153ce]614 }
[61257f4]615
616 // destroy the parser
[e60436b]617 if ((*hid_dev)->report != NULL) {
618 usb_hid_free_report((*hid_dev)->report);
[61257f4]619 }
620
621 if ((*hid_dev)->report_desc != NULL) {
622 free((*hid_dev)->report_desc);
623 }
624
625 free(*hid_dev);
626 *hid_dev = NULL;
[966acede]627}
628
629/**
630 * @}
631 */
Note: See TracBrowser for help on using the repository browser.