@@ -7,6 +7,7 @@ package libusb
77
88// #cgo pkg-config: libusb-1.0
99// #include <libusb.h>
10+ // #include <sys/time.h>
1011// int libusbHotplugCallback (libusb_context *ctx, libusb_device *device, libusb_hotplug_event event, void *user_data);
1112// typedef struct libusb_device_descriptor libusb_device_descriptor_struct;
1213// static int libusb_hotplug_register_callback_wrapper (
@@ -18,6 +19,12 @@ package libusb
1819// {
1920// return libusb_hotplug_register_callback(ctx, events, flags, vendor_id, product_id, dev_class, cb_fn, user_data, callback_handle);
2021// }
22+ // static int libusb_handle_events_timeout_ms(libusb_context *ctx, int timeout_ms) {
23+ // struct timeval tv;
24+ // tv.tv_sec = timeout_ms / 1000;
25+ // tv.tv_usec = (timeout_ms % 1000) * 1000;
26+ // return libusb_handle_events_timeout_completed(ctx, &tv, NULL);
27+ // }
2128import "C"
2229import (
2330 "fmt"
@@ -26,10 +33,10 @@ import (
2633 "unsafe"
2734)
2835
29- // HotPlugEventType .. .
36+ // HotPlugEventType represents the type of hotplug event .
3037type HotPlugEventType uint8
3138
32- // HotPlugCbFunc callback
39+ // HotPlugCbFunc is the callback function signature for hotplug events.
3340type HotPlugCbFunc func (vID , pID uint16 , eventType HotPlugEventType )
3441
3542// HotPlug Event Types
@@ -51,34 +58,68 @@ type hotplugCallback struct {
5158 fn HotPlugCbFunc
5259}
5360
54- // HotplugCallbackStorage .. .
55- type HotplugCallbackStorage struct {
61+ // hotplugStorage holds the callback map and done channel for a single context .
62+ type hotplugStorage struct {
5663 callbackMap map [uint32 ]hotplugCallback
5764 done chan struct {}
58- mu sync.RWMutex // Protects the callbackMap from concurrent access
65+ mu sync.RWMutex
5966}
6067
61- var hotplugCallbackStorage HotplugCallbackStorage
68+ // hotplugEventTimeoutMs is the timeout in milliseconds for
69+ // libusb_handle_events_timeout_completed in the event loop. This prevents
70+ // busy-spinning while still being responsive to the done signal.
71+ const hotplugEventTimeoutMs = 200
6272
63- func (ctx * Context ) newHotPlugHandler () {
64- hotplugCallbackStorage .callbackMap = make (map [uint32 ]hotplugCallback )
65- hotplugCallbackStorage .done = make (chan struct {})
73+ // hotplugRegistry maps context pointers to their hotplug storage, allowing
74+ // multiple contexts to register hotplug callbacks independently.
75+ var (
76+ hotplugRegistry = make (map [* C.libusb_context ]* hotplugStorage )
77+ hotplugRegistryMu sync.RWMutex
78+ )
6679
67- go hotplugCallbackStorage .handleEvents (ctx .libusbContext )
80+ func getHotplugStorage (
81+ libCtx * C.libusb_context ,
82+ ) * hotplugStorage {
83+ hotplugRegistryMu .RLock ()
84+ defer hotplugRegistryMu .RUnlock ()
85+ return hotplugRegistry [libCtx ]
6886}
6987
70- func (s * HotplugCallbackStorage ) isEmpty () bool {
71- s .mu .RLock ()
72- defer s .mu .RUnlock ()
73- return s .callbackMap == nil
88+ func (ctx * Context ) newHotPlugHandler () * hotplugStorage {
89+ storage := & hotplugStorage {
90+ callbackMap : make (map [uint32 ]hotplugCallback ),
91+ done : make (chan struct {}),
92+ }
93+
94+ hotplugRegistryMu .Lock ()
95+ hotplugRegistry [ctx .libusbContext ] = storage
96+ hotplugRegistryMu .Unlock ()
97+
98+ go storage .handleEvents (ctx .libusbContext )
99+ return storage
74100}
75101
76- // HotplugRegisterCallbackEvent ...
77- func (ctx * Context ) HotplugRegisterCallbackEvent (vendorID , productID uint16 ,
78- eventType HotPlugEventType , cb HotPlugCbFunc ) error {
79- if hotplugCallbackStorage .isEmpty () {
80- ctx .newHotPlugHandler ()
102+ func (ctx * Context ) getOrCreateHotplugStorage () * hotplugStorage {
103+ storage := getHotplugStorage (ctx .libusbContext )
104+ if storage == nil {
105+ storage = ctx .newHotPlugHandler ()
81106 }
107+ return storage
108+ }
109+
110+ func removeHotplugStorage (libCtx * C.libusb_context ) {
111+ hotplugRegistryMu .Lock ()
112+ delete (hotplugRegistry , libCtx )
113+ hotplugRegistryMu .Unlock ()
114+ }
115+
116+ // HotplugRegisterCallbackEvent registers a hotplug callback for the given
117+ // vendor/product ID pair and event type.
118+ func (ctx * Context ) HotplugRegisterCallbackEvent (
119+ vendorID , productID uint16 ,
120+ eventType HotPlugEventType , cb HotPlugCbFunc ,
121+ ) error {
122+ storage := ctx .getOrCreateHotplugStorage ()
82123
83124 var event C.int
84125 switch eventType {
@@ -87,7 +128,8 @@ func (ctx *Context) HotplugRegisterCallbackEvent(vendorID, productID uint16,
87128 case HotplugLeft :
88129 event = C .LIBUSB_HOTPLUG_EVENT_DEVICE_LEFT
89130 default :
90- event = C .LIBUSB_HOTPLUG_EVENT_DEVICE_ARRIVED | C .LIBUSB_HOTPLUG_EVENT_DEVICE_LEFT
131+ event = C .LIBUSB_HOTPLUG_EVENT_DEVICE_ARRIVED |
132+ C .LIBUSB_HOTPLUG_EVENT_DEVICE_LEFT
91133 }
92134
93135 var vID C.int = C .LIBUSB_HOTPLUG_MATCH_ANY
@@ -109,77 +151,83 @@ func (ctx *Context) HotplugRegisterCallbackEvent(vendorID, productID uint16,
109151 vID ,
110152 pID ,
111153 C .LIBUSB_HOTPLUG_MATCH_ANY ,
112- C .libusb_hotplug_callback_fn (unsafe .Pointer (C .libusbHotplugCallback )),
154+ C .libusb_hotplug_callback_fn (
155+ unsafe .Pointer (C .libusbHotplugCallback ),
156+ ),
113157 nil ,
114158 & cbHandle ,
115159 )
116160 if rc != C .LIBUSB_SUCCESS {
117- return fmt .Errorf ("libusb_hotplug_register_callback error: %s" , ErrorCode (rc ))
161+ return fmt .Errorf (
162+ "libusb_hotplug_register_callback error: %s" , ErrorCode (rc ),
163+ )
118164 }
119165
120- hotplugCallbackStorage .mu .Lock ()
121- hotplugCallbackStorage .callbackMap [vidPidToUint32 (vendorID , productID )] = hotplugCallback {
166+ key := vidPidToUint32 (vendorID , productID )
167+ storage .mu .Lock ()
168+ storage .callbackMap [key ] = hotplugCallback {
122169 handler : & cbHandle ,
123170 fn : cb ,
124171 }
125- hotplugCallbackStorage .mu .Unlock ()
172+ storage .mu .Unlock ()
126173
127174 return nil
128175}
129176
130- // HotplugDeregisterCallback ...
131- func (ctx * Context ) HotplugDeregisterCallback (vendorID , productID uint16 ) error {
132- if hotplugCallbackStorage .isEmpty () {
177+ // HotplugDeregisterCallback deregisters a hotplug callback for the given
178+ // vendor/product ID pair.
179+ func (ctx * Context ) HotplugDeregisterCallback (
180+ vendorID , productID uint16 ,
181+ ) error {
182+ storage := getHotplugStorage (ctx .libusbContext )
183+ if storage == nil {
133184 return nil
134185 }
135186
136187 key := vidPidToUint32 (vendorID , productID )
137188
138- hotplugCallbackStorage .mu .RLock ()
139- cb , ok := hotplugCallbackStorage .callbackMap [key ]
140- hotplugCallbackStorage .mu .RUnlock ()
189+ storage .mu .RLock ()
190+ cb , ok := storage .callbackMap [key ]
191+ storage .mu .RUnlock ()
141192
142193 if ! ok {
143194 return nil
144195 }
145196
146197 C .libusb_hotplug_deregister_callback (ctx .libusbContext , * cb .handler )
147198
148- hotplugCallbackStorage .mu .Lock ()
149- delete (hotplugCallbackStorage .callbackMap , key )
150- mapEmpty := len (hotplugCallbackStorage .callbackMap ) == 0
151- hotplugCallbackStorage .mu .Unlock ()
199+ storage .mu .Lock ()
200+ delete (storage .callbackMap , key )
201+ mapEmpty := len (storage .callbackMap ) == 0
202+ storage .mu .Unlock ()
152203
153204 if mapEmpty {
154205 ctx .hotplugHandleEventsCompleteAll ()
155206 }
156207 return nil
157208}
158209
159- // HotplugDeregisterAllCallbacks ...
210+ // HotplugDeregisterAllCallbacks deregisters all hotplug callbacks for this
211+ // context and stops the event handler goroutine.
160212func (ctx * Context ) HotplugDeregisterAllCallbacks () error {
161- hotplugCallbackStorage .mu .RLock ()
162- mapExists := hotplugCallbackStorage .callbackMap != nil
163-
164- if mapExists {
165- // Make a copy of the handlers to avoid holding the lock during C function
166- // calls
167- handlers := make (
168- []* C.libusb_hotplug_callback_handle ,
169- 0 ,
170- len (hotplugCallbackStorage .callbackMap ),
171- )
172- for _ , cb := range hotplugCallbackStorage .callbackMap {
173- handlers = append (handlers , cb .handler )
174- }
175- hotplugCallbackStorage .mu .RUnlock ()
213+ storage := getHotplugStorage (ctx .libusbContext )
214+ if storage == nil {
215+ return nil
216+ }
176217
177- // Deregister callbacks without holding the lock
178- for _ , handler := range handlers {
179- C .libusb_hotplug_deregister_callback (ctx .libusbContext , * handler )
180- }
181- } else {
182- hotplugCallbackStorage .mu .RUnlock ()
218+ storage .mu .RLock ()
219+ handlers := make (
220+ []* C.libusb_hotplug_callback_handle ,
221+ 0 ,
222+ len (storage .callbackMap ),
223+ )
224+ for _ , cb := range storage .callbackMap {
225+ handlers = append (handlers , cb .handler )
226+ }
227+ storage .mu .RUnlock ()
228+
229+ for _ , handler := range handlers {
230+ C .libusb_hotplug_deregister_callback (ctx .libusbContext , * handler )
183231 }
184232
185233 ctx .hotplugHandleEventsCompleteAll ()
@@ -188,48 +236,57 @@ func (ctx *Context) HotplugDeregisterAllCallbacks() error {
188236}
189237
190238func (ctx * Context ) hotplugHandleEventsCompleteAll () {
191- if hotplugCallbackStorage .isEmpty () {
239+ storage := getHotplugStorage (ctx .libusbContext )
240+ if storage == nil {
192241 return
193242 }
194243
195- // Signal the event handler to stop
196- hotplugCallbackStorage .done <- struct {}{}
244+ // Signal the event handler goroutine to stop. Closing the channel
245+ // unblocks all receivers immediately without needing a separate send.
246+ close (storage .done )
197247
198- // Clear the callbackMap and close the channel
199- hotplugCallbackStorage .mu .Lock ()
200- hotplugCallbackStorage .callbackMap = nil
201- hotplugCallbackStorage .mu .Unlock ()
248+ // Clean up the storage for this context.
249+ storage .mu .Lock ()
250+ storage .callbackMap = nil
251+ storage .mu .Unlock ()
202252
203- close ( hotplugCallbackStorage . done )
253+ removeHotplugStorage ( ctx . libusbContext )
204254}
205255
206- func (storage * HotplugCallbackStorage ) handleEvents (libCtx * C.libusb_context ) {
256+ func (storage * hotplugStorage ) handleEvents (
257+ libCtx * C.libusb_context ,
258+ ) {
207259 for {
208260 select {
209261 case <- storage .done :
210262 return
211263 default :
212264 }
213- if errno := C .libusb_handle_events_completed (libCtx , nil ); errno < 0 {
265+ errno := C .libusb_handle_events_timeout_ms (
266+ libCtx , C .int (hotplugEventTimeoutMs ),
267+ )
268+ if errno < 0 {
214269 if ErrorCode (errno ) == errorInterrupted {
215- continue // ignore harmless EINTR
270+ continue
216271 }
217272 log .Printf ("handle_events error: %s" , ErrorCode (errno ))
218273 }
219274 }
220275}
221276
222277//export libusbHotplugCallback
223- func libusbHotplugCallback (ctx * C.libusb_context , dev * C.libusb_device ,
224- event C.libusb_hotplug_event , p unsafe.Pointer ) C.int {
278+ func libusbHotplugCallback (
279+ ctx * C.libusb_context , dev * C.libusb_device ,
280+ event C.libusb_hotplug_event , p unsafe.Pointer ,
281+ ) C.int {
225282 var desc C.libusb_device_descriptor_struct
226283 rc := C .libusb_get_device_descriptor (dev , & desc )
227284 if rc != C .LIBUSB_SUCCESS {
228285 return rc
229286 }
230287
231- var vendorID = uint16 (desc .idVendor )
232- var productID = uint16 (desc .idProduct )
288+ vendorID : = uint16 (desc .idVendor )
289+ productID : = uint16 (desc .idProduct )
233290
234291 var e HotPlugEventType
235292 switch event {
@@ -241,28 +298,27 @@ func libusbHotplugCallback(ctx *C.libusb_context, dev *C.libusb_device,
241298 e = HotplugUndefined
242299 }
243300
244- // Read callback map with a read lock
245- hotplugCallbackStorage .mu .RLock ()
246- // Get device-specific callback
247- cb , ok := hotplugCallbackStorage .callbackMap [vidPidToUint32 (vendorID , productID )]
301+ storage := getHotplugStorage (ctx )
302+ if storage == nil {
303+ return C .LIBUSB_SUCCESS
304+ }
305+
306+ storage .mu .RLock ()
307+ cb , ok := storage .callbackMap [vidPidToUint32 (vendorID , productID )]
248308 var deviceCallback HotPlugCbFunc
249309 if ok {
250310 deviceCallback = cb .fn
251311 }
252-
253- // Get the callback for all devices
254- cb , ok = hotplugCallbackStorage .callbackMap [0 ]
312+ cb , ok = storage .callbackMap [0 ]
255313 var allCallback HotPlugCbFunc
256314 if ok {
257315 allCallback = cb .fn
258316 }
259- hotplugCallbackStorage .mu .RUnlock ()
317+ storage .mu .RUnlock ()
260318
261- // Call callbacks outside the lock
262319 if deviceCallback != nil {
263320 deviceCallback (vendorID , productID , e )
264321 }
265-
266322 if allCallback != nil {
267323 allCallback (vendorID , productID , e )
268324 }
0 commit comments