1515package gousb
1616
1717import (
18+ "errors"
1819 "fmt"
1920 "log"
2021 "reflect"
@@ -26,12 +27,23 @@ import (
2627/*
2728#cgo pkg-config: libusb-1.0
2829#include <libusb.h>
30+ #include <stdlib.h>
2931
3032int gousb_compact_iso_data(struct libusb_transfer *xfer, unsigned char *status);
3133struct libusb_transfer *gousb_alloc_transfer_and_buffer(int bufLen, int numIsoPackets);
3234void gousb_free_transfer_and_buffer(struct libusb_transfer *xfer);
3335int submit(struct libusb_transfer *xfer);
3436void 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+ );
3547*/
3648import "C"
3749
@@ -125,6 +137,8 @@ func (ep libusbEndpoint) endpointDesc(dev *DeviceDesc) EndpointDesc {
125137 return ei
126138}
127139
140+ type libusbHotplugCallback func (* libusbContext , * libusbDevice , HotplugEventType ) bool
141+
128142// libusbIntf is a set of trivial idiomatic Go wrappers around libusb C functions.
129143// The underlying code is generally not testable or difficult to test,
130144// since libusb interacts directly with the host USB stack.
@@ -166,6 +180,9 @@ type libusbIntf interface {
166180 data (* libusbTransfer ) (int , TransferStatus )
167181 free (* libusbTransfer )
168182 setIsoPacketLengths (* libusbTransfer , uint32 )
183+
184+ // hotplug
185+ registerHotplugCallback (ctx * libusbContext , events HotplugEventType , enumerate bool , vendorID int32 , productID int32 , devClass int32 , fn libusbHotplugCallback ) (func (), error )
169186}
170187
171188// libusbImpl is an implementation of libusbIntf using real CGo-wrapped libusb.
@@ -216,6 +233,17 @@ func (libusbImpl) getDevices(ctx *libusbContext) ([]*libusbDevice, error) {
216233
217234func (libusbImpl ) exit (c * libusbContext ) error {
218235 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 ()
219247 return nil
220248}
221249
@@ -510,3 +538,90 @@ func newDevicePointer() *libusbDevice {
510538func newFakeTransferPointer () * libusbTransfer {
511539 return (* libusbTransfer )(unsafe .Pointer (C .malloc (1 )))
512540}
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