15
15
package gousb
16
16
17
17
import (
18
+ "errors"
18
19
"fmt"
19
20
"log"
20
21
"reflect"
@@ -26,12 +27,23 @@ import (
26
27
/*
27
28
#cgo pkg-config: libusb-1.0
28
29
#include <libusb.h>
30
+ #include <stdlib.h>
29
31
30
32
int gousb_compact_iso_data(struct libusb_transfer *xfer, unsigned char *status);
31
33
struct libusb_transfer *gousb_alloc_transfer_and_buffer(int bufLen, int numIsoPackets);
32
34
void gousb_free_transfer_and_buffer(struct libusb_transfer *xfer);
33
35
int submit(struct libusb_transfer *xfer);
34
36
void gousb_set_debug(libusb_context *ctx, int lvl);
37
+ int gousb_hotplug_register_callback(
38
+ libusb_context* ctx,
39
+ libusb_hotplug_event events,
40
+ libusb_hotplug_flag flags,
41
+ int vid,
42
+ int pid,
43
+ int dev_class,
44
+ void *user_data,
45
+ libusb_hotplug_callback_handle *handle
46
+ );
35
47
*/
36
48
import "C"
37
49
@@ -125,6 +137,8 @@ func (ep libusbEndpoint) endpointDesc(dev *DeviceDesc) EndpointDesc {
125
137
return ei
126
138
}
127
139
140
+ type libusbHotplugCallback func (* libusbContext , * libusbDevice , HotplugEventType ) bool
141
+
128
142
// libusbIntf is a set of trivial idiomatic Go wrappers around libusb C functions.
129
143
// The underlying code is generally not testable or difficult to test,
130
144
// since libusb interacts directly with the host USB stack.
@@ -166,6 +180,9 @@ type libusbIntf interface {
166
180
data (* libusbTransfer ) (int , TransferStatus )
167
181
free (* libusbTransfer )
168
182
setIsoPacketLengths (* libusbTransfer , uint32 )
183
+
184
+ // hotplug
185
+ registerHotplugCallback (ctx * libusbContext , events HotplugEventType , enumerate bool , vendorID int32 , productID int32 , devClass int32 , fn libusbHotplugCallback ) (func (), error )
169
186
}
170
187
171
188
// libusbImpl is an implementation of libusbIntf using real CGo-wrapped libusb.
@@ -216,6 +233,17 @@ func (libusbImpl) getDevices(ctx *libusbContext) ([]*libusbDevice, error) {
216
233
217
234
func (libusbImpl ) exit (c * libusbContext ) error {
218
235
C .libusb_exit ((* C .libusb_context )(c ))
236
+ // libusb_exit automatically deregisters hotplug callbacks,
237
+ // but we need to free the callback map.
238
+ hotplugCallbackMap .Lock ()
239
+ if m , ok := hotplugCallbackMap .m [c ]; ok {
240
+ for id := range m {
241
+ delete (m , id )
242
+ C .free (id )
243
+ }
244
+ }
245
+ delete (hotplugCallbackMap .m , c )
246
+ hotplugCallbackMap .Unlock ()
219
247
return nil
220
248
}
221
249
@@ -510,3 +538,90 @@ func newDevicePointer() *libusbDevice {
510
538
func newFakeTransferPointer () * libusbTransfer {
511
539
return (* libusbTransfer )(unsafe .Pointer (C .malloc (1 )))
512
540
}
541
+
542
+ // hotplugCallbackMap keeps a map of go callback functions for libusb hotplug callbacks
543
+ // for each context.
544
+ // When a context is closed with libusb_exit, its callbacks are automatically deregistered
545
+ // by libusb, and they are removed from this map too.
546
+ var hotplugCallbackMap = struct {
547
+ m map [* libusbContext ]map [unsafe.Pointer ]libusbHotplugCallback
548
+ sync.RWMutex
549
+ }{
550
+ m : make (map [* libusbContext ]map [unsafe.Pointer ]libusbHotplugCallback ),
551
+ }
552
+
553
+ //export goHotplugCallback
554
+ func goHotplugCallback (ctx * C.libusb_context , device * C.libusb_device , event C.libusb_hotplug_event , userData unsafe.Pointer ) C.int {
555
+ var fn libusbHotplugCallback
556
+ hotplugCallbackMap .RLock ()
557
+ m , ok := hotplugCallbackMap .m [(* libusbContext )(ctx )]
558
+ if ok {
559
+ fn , ok = m [userData ]
560
+ }
561
+ hotplugCallbackMap .RUnlock ()
562
+ if ! ok {
563
+ // This shouldn't happen. Deregister the callback.
564
+ return 1
565
+ }
566
+ dereg := fn ((* libusbContext )(ctx ), (* libusbDevice )(device ), HotplugEventType (event ))
567
+
568
+ if dereg {
569
+ hotplugCallbackMap .Lock ()
570
+ m , ok := hotplugCallbackMap .m [(* libusbContext )(ctx )]
571
+ if ok {
572
+ delete (m , userData )
573
+ C .free (userData )
574
+ }
575
+ hotplugCallbackMap .Unlock ()
576
+ return 1
577
+ }
578
+ return 0
579
+ }
580
+
581
+ func (libusbImpl ) registerHotplugCallback (ctx * libusbContext , events HotplugEventType , enumerate bool , vendorID int32 , productID int32 , devClass int32 , fn libusbHotplugCallback ) (func (), error ) {
582
+ // We must allocate memory to pass to C, since we can't pass a go pointer.
583
+ // We can use the resulting pointer as a map key instead of
584
+ // storing the map key inside the memory allocated.
585
+ id := C .malloc (1 )
586
+ if id == nil {
587
+ panic (errors .New ("Failed to allocate memory during callback registration" ))
588
+ }
589
+ hotplugCallbackMap .Lock ()
590
+ m , ok := hotplugCallbackMap .m [ctx ]
591
+ if ! ok {
592
+ hotplugCallbackMap .m [ctx ] = make (map [unsafe.Pointer ]libusbHotplugCallback )
593
+ m = hotplugCallbackMap .m [ctx ]
594
+ }
595
+ m [id ] = fn
596
+ hotplugCallbackMap .Unlock ()
597
+
598
+ var flags C.libusb_hotplug_flag
599
+ if enumerate {
600
+ flags = C .LIBUSB_HOTPLUG_ENUMERATE
601
+ }
602
+ var handle C.libusb_hotplug_callback_handle
603
+
604
+ // TODO: figure out how to run deregister in callback.
605
+ // There's a race condition here, because the callback may be called before
606
+ // gousb_hotplug_register_callback returns, depending on libusb's implementation.
607
+ res := C .gousb_hotplug_register_callback ((* C .libusb_context )(ctx ), C .libusb_hotplug_event (events ), flags , C .int (vendorID ), C .int (productID ), C .int (devClass ), id , & handle )
608
+ err := fromErrNo (res )
609
+ if err != nil {
610
+ hotplugCallbackMap .Lock ()
611
+ delete (hotplugCallbackMap .m [ctx ], id )
612
+ hotplugCallbackMap .Unlock ()
613
+ C .free (id )
614
+ return nil , err
615
+ }
616
+
617
+ return func () {
618
+ C .libusb_hotplug_deregister_callback ((* C .libusb_context )(ctx ), handle )
619
+ hotplugCallbackMap .Lock ()
620
+ m , ok := hotplugCallbackMap .m [ctx ]
621
+ if ok {
622
+ delete (m , id )
623
+ C .free (id )
624
+ }
625
+ hotplugCallbackMap .Unlock ()
626
+ }, nil
627
+ }
0 commit comments