19
19
#include <wchar.h>
20
20
#include <string.h>
21
21
#include <stdlib.h>
22
+ #include <ctype.h> // for "tolower()"
22
23
23
24
#include <hidapi.h>
24
25
25
- // Headers needed for sleeping.
26
+ // Headers needed for sleeping and console management (wait for a keypress)
26
27
#ifdef _WIN32
27
28
#include <windows.h>
29
+ #include <conio.h>
28
30
#else
31
+ #include <fcntl.h>
32
+ #include <termios.h>
29
33
#include <unistd.h>
30
34
#endif
31
35
50
54
#if defined(USING_HIDAPI_LIBUSB ) && HID_API_VERSION >= HID_API_MAKE_VERSION (0 , 12 , 0 )
51
55
#include <hidapi_libusb.h>
52
56
#endif
57
+
53
58
//
59
+ // A function that waits for a key to be pressed and reports it's code
60
+ // Used for immediate response in interactive subroutines
61
+ // Taken from:
62
+ // https://cboard.cprogramming.com/c-programming/63166-kbhit-linux.html
63
+ int waitkey (void )
64
+ {
65
+ #ifdef _WIN32
66
+ return _getch ();
67
+ #else
68
+ struct termios oldt , newt ;
69
+ int ch ;
70
+ int oldf ;
71
+
72
+ tcgetattr (STDIN_FILENO , & oldt );
73
+ newt = oldt ;
74
+ newt .c_lflag &= ~(ICANON | ECHO );
75
+ tcsetattr (STDIN_FILENO , TCSANOW , & newt );
76
+ oldf = fcntl (STDIN_FILENO , F_GETFL , 0 );
77
+ fcntl (STDIN_FILENO , F_SETFL , oldf | O_NONBLOCK );
78
+
79
+ do
80
+ {
81
+ usleep (1 );
82
+ ch = getchar ();
83
+ }
84
+ while (EOF == ch );
85
+
86
+ tcsetattr (STDIN_FILENO , TCSANOW , & oldt );
87
+ fcntl (STDIN_FILENO , F_SETFL , oldf );
54
88
89
+ return ch ;
90
+ #endif
91
+ }
92
+
93
+ //
94
+
95
+ //
96
+ // Report Device info
55
97
const char * hid_bus_name (hid_bus_type bus_type ) {
56
98
static const char * const HidBusTypeName [] = {
57
99
"Unknown" ,
@@ -126,80 +168,28 @@ void print_devices_with_descriptor(struct hid_device_info *cur_dev) {
126
168
}
127
169
}
128
170
129
- int device_callback (
130
- hid_hotplug_callback_handle callback_handle ,
131
- struct hid_device_info * device ,
132
- hid_hotplug_event event ,
133
- void * user_data )
171
+ //
172
+ // Default static testing
173
+ void test_static (void )
134
174
{
135
- (void )user_data ;
136
-
137
- if (event & HID_API_HOTPLUG_EVENT_DEVICE_ARRIVED )
138
- printf ("Handle %d: New device is connected: %s.\n" , callback_handle , device -> path );
139
- else
140
- printf ("Handle %d: Device was disconnected: %s.\n" , callback_handle , device -> path );
141
-
142
- printf ("type: %04hx %04hx\n serial_number: %ls" , device -> vendor_id , device -> product_id , device -> serial_number );
143
- printf ("\n" );
144
- printf (" Manufacturer: %ls\n" , device -> manufacturer_string );
145
- printf (" Product: %ls\n" , device -> product_string );
146
- printf (" Release: %hx\n" , device -> release_number );
147
- printf (" Interface: %d\n" , device -> interface_number );
148
- printf (" Usage (page): 0x%hx (0x%hx)\n" , device -> usage , device -> usage_page );
149
- printf ("\n" );
150
-
151
- /* Printed data might not show on the screen - force it out */
152
- fflush (stdout );
175
+ struct hid_device_info * devs ;
153
176
154
- return 0 ;
177
+ devs = hid_enumerate (0x0 , 0x0 );
178
+ print_devices_with_descriptor (devs );
179
+ hid_free_enumeration (devs );
155
180
}
156
181
157
- int main (int argc , char * argv [])
158
- {
159
- (void )argc ;
160
- (void )argv ;
161
182
183
+ //
184
+ // Fixed device testing
185
+ void test_device (void )
186
+ {
162
187
int res ;
163
188
unsigned char buf [256 ];
164
- #define MAX_STR 255
189
+ #define MAX_STR 255
165
190
wchar_t wstr [MAX_STR ];
166
191
hid_device * handle ;
167
192
int i ;
168
- hid_hotplug_callback_handle token1 , token2 ;
169
-
170
- struct hid_device_info * devs ;
171
-
172
- printf ("hidapi test/example tool. Compiled with hidapi version %s, runtime version %s.\n" , HID_API_VERSION_STR , hid_version_str ());
173
- if (HID_API_VERSION == HID_API_MAKE_VERSION (hid_version ()-> major , hid_version ()-> minor , hid_version ()-> patch )) {
174
- printf ("Compile-time version matches runtime version of hidapi.\n\n" );
175
- }
176
- else {
177
- printf ("Compile-time version is different than runtime version of hidapi.\n]n" );
178
- }
179
-
180
- if (hid_init ())
181
- return -1 ;
182
-
183
- #if defined(__APPLE__ ) && HID_API_VERSION >= HID_API_MAKE_VERSION (0 , 12 , 0 )
184
- // To work properly needs to be called before hid_open/hid_open_path after hid_init.
185
- // Best/recommended option - call it right after hid_init.
186
- hid_darwin_set_open_exclusive (0 );
187
- #endif
188
-
189
- devs = hid_enumerate (0x0 , 0x0 );
190
- print_devices_with_descriptor (devs );
191
- hid_free_enumeration (devs );
192
-
193
- hid_hotplug_register_callback (0 , 0 , HID_API_HOTPLUG_EVENT_DEVICE_ARRIVED | HID_API_HOTPLUG_EVENT_DEVICE_LEFT , HID_API_HOTPLUG_ENUMERATE , device_callback , NULL , & token1 );
194
- hid_hotplug_register_callback (0x054c , 0x0ce6 , HID_API_HOTPLUG_EVENT_DEVICE_ARRIVED | HID_API_HOTPLUG_EVENT_DEVICE_LEFT , HID_API_HOTPLUG_ENUMERATE , device_callback , NULL , & token2 );
195
-
196
- while (1 )
197
- {
198
-
199
- }
200
-
201
- hid_hotplug_deregister_callback (token2 );
202
- hid_hotplug_deregister_callback (token1 );
203
193
204
194
// Set up the command buffer.
205
195
memset (buf ,0x00 ,sizeof (buf ));
@@ -213,8 +203,7 @@ int main(int argc, char* argv[])
213
203
handle = hid_open (0x4d8 , 0x3f , NULL );
214
204
if (!handle ) {
215
205
printf ("unable to open device\n" );
216
- hid_exit ();
217
- return 1 ;
206
+ return ;
218
207
}
219
208
220
209
// Read the Manufacturer String
@@ -344,13 +333,221 @@ int main(int argc, char* argv[])
344
333
}
345
334
346
335
hid_close (handle );
336
+ }
347
337
348
- /* Free static HIDAPI objects. */
349
- hid_exit ();
338
+ //
339
+ // Normal hotplug testing
340
+ int device_callback (
341
+ hid_hotplug_callback_handle callback_handle ,
342
+ struct hid_device_info * device ,
343
+ hid_hotplug_event event ,
344
+ void * user_data )
345
+ {
346
+ (void )user_data ;
350
347
351
- #ifdef _WIN32
352
- system ("pause" );
348
+ if (event & HID_API_HOTPLUG_EVENT_DEVICE_ARRIVED )
349
+ printf ("Handle %d: New device is connected: %s.\n" , callback_handle , device -> path );
350
+ else
351
+ printf ("Handle %d: Device was disconnected: %s.\n" , callback_handle , device -> path );
352
+
353
+ printf ("type: %04hx %04hx\n serial_number: %ls" , device -> vendor_id , device -> product_id , device -> serial_number );
354
+ printf ("\n" );
355
+ printf (" Manufacturer: %ls\n" , device -> manufacturer_string );
356
+ printf (" Product: %ls\n" , device -> product_string );
357
+ printf (" Release: %hx\n" , device -> release_number );
358
+ printf (" Interface: %d\n" , device -> interface_number );
359
+ printf (" Usage (page): 0x%hx (0x%hx)\n" , device -> usage , device -> usage_page );
360
+ printf ("(Press Q to exit the test)\n" );
361
+ printf ("\n" );
362
+
363
+ return 0 ;
364
+ }
365
+
366
+
367
+ void test_hotplug (void )
368
+ {
369
+ printf ("Starting the Hotplug test\n" );
370
+ printf ("(Press Q to exit the test)\n" );
371
+
372
+ hid_hotplug_callback_handle token1 , token2 ;
373
+
374
+ hid_hotplug_register_callback (0 , 0 , HID_API_HOTPLUG_EVENT_DEVICE_ARRIVED | HID_API_HOTPLUG_EVENT_DEVICE_LEFT , HID_API_HOTPLUG_ENUMERATE , device_callback , NULL , & token1 );
375
+ hid_hotplug_register_callback (0x054c , 0x0ce6 , HID_API_HOTPLUG_EVENT_DEVICE_ARRIVED | HID_API_HOTPLUG_EVENT_DEVICE_LEFT , HID_API_HOTPLUG_ENUMERATE , device_callback , NULL , & token2 );
376
+
377
+ while (1 )
378
+ {
379
+ int command = tolower (waitkey ());
380
+ if ('q' == command )
381
+ {
382
+ break ;
383
+ }
384
+ }
385
+
386
+ hid_hotplug_deregister_callback (token2 );
387
+ hid_hotplug_deregister_callback (token1 );
388
+
389
+ printf ("\n\nHotplug test stopped\n" );
390
+ }
391
+
392
+ //
393
+ // Stress-testing weird edge cases in hotplugs
394
+ int cb1_handle ;
395
+ int cb2_handle ;
396
+ int cb_test1_triggered ;
397
+
398
+ int cb2_func (hid_hotplug_callback_handle callback_handle ,
399
+ struct hid_device_info * device ,
400
+ hid_hotplug_event event ,
401
+ void * user_data )
402
+ {
403
+ (void ) callback_handle ;
404
+ (void ) device ;
405
+ (void ) event ;
406
+ (void ) user_data ;
407
+ // TIP: only perform the test once
408
+ if (cb_test1_triggered )
409
+ {
410
+ return 1 ;
411
+ }
412
+
413
+ printf ("Callback 2 fired\n" );
414
+
415
+ // Deregister the first callback
416
+ // It should be placed in the list at an index prior to the current one, which will make the pointer to the current one invalid on some implementations
417
+ hid_hotplug_deregister_callback (cb1_handle );
418
+
419
+ cb_test1_triggered = 1 ;
420
+
421
+ // As long as we are inside this callback, nothing goes wrong; however, returning from here will cause a use-after-free error on flawed implementations
422
+ // as to retrieve the next element (or to check for it's presence) it will look those dereference a pointer located in an already freed area
423
+ // Undefined behavior
424
+ return 1 ;
425
+ }
426
+
427
+ int cb1_func (hid_hotplug_callback_handle callback_handle ,
428
+ struct hid_device_info * device ,
429
+ hid_hotplug_event event ,
430
+ void * user_data )
431
+ {
432
+ (void ) callback_handle ;
433
+ (void ) device ;
434
+ (void ) event ;
435
+ (void ) user_data ;
436
+
437
+ // TIP: only perform the test once
438
+ if (cb_test1_triggered )
439
+ {
440
+ return 1 ;
441
+ }
442
+
443
+ printf ("Callback 1 fired\n" );
444
+
445
+ // Register the second callback and make it be called immediately by enumeration attempt
446
+ // Will cause a deadlock on Linux immediately
447
+ hid_hotplug_register_callback (0 , 0 , HID_API_HOTPLUG_EVENT_DEVICE_ARRIVED | HID_API_HOTPLUG_EVENT_DEVICE_LEFT , HID_API_HOTPLUG_ENUMERATE , cb2_func , NULL , & cb2_handle );
448
+ return 1 ;
449
+ }
450
+
451
+ void test_hotplug_deadlocks (void )
452
+ {
453
+ cb_test1_triggered = 0 ;
454
+ printf ("Starting the Hotplug callbacks deadlocks test\n" );
455
+ printf ("TIP: if you don't see a message that it succeeded, it means the test failed and the system is now deadlocked\n" );
456
+ // Register the first callback and make it be called immediately by enumeration attempt (if at least 1 device is present)
457
+ hid_hotplug_register_callback (0 , 0 , HID_API_HOTPLUG_EVENT_DEVICE_ARRIVED | HID_API_HOTPLUG_EVENT_DEVICE_LEFT , HID_API_HOTPLUG_ENUMERATE , cb1_func , NULL , & cb1_handle );
458
+
459
+ printf ("Test finished successfully (at least no deadlocks were found)\n" );
460
+
461
+ // Intentionally leave a callback registered to test how hid_exit handles it
462
+ //hid_hotplug_deregister_callback(cb2_handle);
463
+ }
464
+
465
+
466
+ //
467
+ // CLI
468
+
469
+ void print_version_check (void )
470
+ {
471
+ printf ("hidapi test/example tool. Compiled with hidapi version %s, runtime version %s.\n" , HID_API_VERSION_STR , hid_version_str ());
472
+ if (HID_API_VERSION == HID_API_MAKE_VERSION (hid_version ()-> major , hid_version ()-> minor , hid_version ()-> patch )) {
473
+ printf ("Compile-time version matches runtime version of hidapi.\n\n" );
474
+ }
475
+ else {
476
+ printf ("Compile-time version is different than runtime version of hidapi.\n]n" );
477
+ }
478
+ }
479
+
480
+ void interactive_loop (void )
481
+ {
482
+ int command = 0 ;
483
+
484
+ print_version_check ();
485
+
486
+ do {
487
+ printf ("Interactive HIDAPI testing utility\n" );
488
+ printf (" 1: List connected devices\n" );
489
+ printf (" 2: Dynamic hotplug test\n" );
490
+ printf (" 3: Test specific device [04d8:003f]\n" );
491
+ printf (" 4: Test hotplug callback management deadlocking scenario\n" );
492
+ printf (" Q: Quit\n" );
493
+ printf ("Please enter command:" );
494
+
495
+ /* Printed data might not show on the screen when the command is done - force it out */
496
+ fflush (stdout );
497
+
498
+ command = toupper (waitkey ());
499
+
500
+ printf ("%c\n\n========================================\n\n" , command );
501
+
502
+ // GET COMMAND
503
+ switch (command ) {
504
+ case '1' :
505
+ test_static ();
506
+ break ;
507
+ case '2' :
508
+ test_hotplug ();
509
+ break ;
510
+ case '3' :
511
+ test_device ();
512
+ break ;
513
+ case '4' :
514
+ test_hotplug_deadlocks ();
515
+ break ;
516
+ case 'Q' :
517
+ printf ("Quitting.\n" );
518
+ return ;
519
+ default :
520
+ printf ("Command not recognized\n" );
521
+ break ;
522
+ }
523
+
524
+ /* Printed data might not show on the screen when the command is done - force it out */
525
+ fflush (stdout );
526
+
527
+ printf ("\n\n========================================\n\n" );
528
+ } while (command != 'Q' );
529
+ }
530
+
531
+ //
532
+ // Main
533
+ int main (int argc , char * argv [])
534
+ {
535
+ (void )argc ;
536
+ (void )argv ;
537
+
538
+ if (hid_init ())
539
+ return -1 ;
540
+
541
+ #if defined(__APPLE__ ) && HID_API_VERSION >= HID_API_MAKE_VERSION (0 , 12 , 0 )
542
+ // To work properly needs to be called before hid_open/hid_open_path after hid_init.
543
+ // Best/recommended option - call it right after hid_init.
544
+ hid_darwin_set_open_exclusive (0 );
353
545
#endif
354
546
547
+ interactive_loop ();
548
+
549
+ /* Free static HIDAPI objects. */
550
+ hid_exit ();
551
+
355
552
return 0 ;
356
553
}
0 commit comments