Skip to content

Commit 27f45ca

Browse files
committed
Merge google#32 (Hotplug).
2 parents 15d2fa2 + 26ac067 commit 27f45ca

File tree

5 files changed

+286
-0
lines changed

5 files changed

+286
-0
lines changed

constants.go

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -265,3 +265,15 @@ const (
265265

266266
// Milliamperes is a unit of electric current consumption.
267267
type Milliamperes uint
268+
269+
// HotplugEventType identifies the type of the hotplug event.
270+
type HotplugEventType uint
271+
272+
// Hotplug events.
273+
const (
274+
HotplugEventDeviceArrived HotplugEventType = C.LIBUSB_HOTPLUG_EVENT_DEVICE_ARRIVED
275+
HotplugEventDeviceLeft HotplugEventType = C.LIBUSB_HOTPLUG_EVENT_DEVICE_LEFT
276+
HotplugEventAny HotplugEventType = HotplugEventDeviceArrived | HotplugEventDeviceLeft
277+
)
278+
279+
const hotplugMatchAny = C.LIBUSB_HOTPLUG_MATCH_ANY

fakelibusb_test.go

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -209,6 +209,12 @@ func (f *fakeLibusb) empty() bool {
209209
return len(f.submitted) == 0
210210
}
211211

212+
func (f *fakeLibusb) registerHotplugCallback(ctx *libusbContext, events HotplugEventType, enumerate bool, vendorId int32, productId int32, devClass int32, fn libusbHotplugCallback) (func(), error) {
213+
// TODO: implement
214+
return func() {
215+
}, nil
216+
}
217+
212218
func newFakeLibusb() *fakeLibusb {
213219
fl := &fakeLibusb{
214220
fakeDevices: make(map[*libusbDevice]*fakeDevice),

hotplug.c

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
// Copyright 2013 Google Inc. All rights reserved.
2+
// Copyright 2016 the gousb Authors. All rights reserved.
3+
//
4+
// Licensed under the Apache License, Version 2.0 (the "License");
5+
// you may not use this file except in compliance with the License.
6+
// You may obtain a copy of the License at
7+
//
8+
// http://www.apache.org/licenses/LICENSE-2.0
9+
//
10+
// Unless required by applicable law or agreed to in writing, software
11+
// distributed under the License is distributed on an "AS IS" BASIS,
12+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
// See the License for the specific language governing permissions and
14+
// limitations under the License.
15+
16+
#include <libusb.h>
17+
#include "_cgo_export.h"
18+
19+
int gousb_hotplug_register_callback(
20+
libusb_context* ctx,
21+
libusb_hotplug_event events,
22+
libusb_hotplug_flag flags,
23+
int vid,
24+
int pid,
25+
int dev_class,
26+
void *user_data,
27+
libusb_hotplug_callback_handle *handle
28+
) {
29+
return libusb_hotplug_register_callback(
30+
ctx, events, flags, vid, pid, dev_class, (libusb_hotplug_callback_fn)(goHotplugCallback), user_data, handle
31+
);
32+
}

hotplug.go

Lines changed: 121 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,121 @@
1+
// Copyright 2013 Google Inc. All rights reserved.
2+
// Copyright 2016 the gousb Authors. All rights reserved.
3+
//
4+
// Licensed under the Apache License, Version 2.0 (the "License");
5+
// you may not use this file except in compliance with the License.
6+
// You may obtain a copy of the License at
7+
//
8+
// http://www.apache.org/licenses/LICENSE-2.0
9+
//
10+
// Unless required by applicable law or agreed to in writing, software
11+
// distributed under the License is distributed on an "AS IS" BASIS,
12+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
// See the License for the specific language governing permissions and
14+
// limitations under the License.
15+
16+
package gousb
17+
18+
type HotplugEvent interface {
19+
// Type returns the event's type (HotplugEventDeviceArrived or HotplugEventDeviceLeft).
20+
Type() HotplugEventType
21+
// IsEnumerated returns true if the device was already plugged in when the callback was registered.
22+
IsEnumerated() bool
23+
// DeviceDesc returns the device's descriptor.
24+
DeviceDesc() (*DeviceDesc, error)
25+
// Open opens the device.
26+
Open() (*Device, error)
27+
// Deregister deregisters the callback registration after the callback function returns.
28+
Deregister()
29+
}
30+
31+
// RegisterHotplug registers a hotplug callback function.
32+
// The callback will receive arrive events for all currently plugged in devices.
33+
// These events will return true from IsEnumerated().
34+
// Note that events are delivered concurrently. You may receive arrive and leave events
35+
// concurrently with enumerated arrive events. You may also receive arrive events twice
36+
// for the same device, and you may receive a leave event for a device for which
37+
// you never received an arrive event.
38+
func (c *Context) RegisterHotplug(fn func(HotplugEvent)) (func(), error) {
39+
dereg, err := c.libusb.registerHotplugCallback(c.ctx, HotplugEventAny, false, hotplugMatchAny, hotplugMatchAny, hotplugMatchAny, func(ctx *libusbContext, dev *libusbDevice, eventType HotplugEventType) bool {
40+
desc, err := c.libusb.getDeviceDesc(dev)
41+
e := &hotplugEvent{
42+
eventType: eventType,
43+
ctx: c,
44+
dev: dev,
45+
desc: desc,
46+
err: err,
47+
enumerated: false,
48+
}
49+
fn(e)
50+
return e.deregister
51+
})
52+
if err != nil {
53+
return nil, err
54+
}
55+
// enumerate devices
56+
// this is done in gousb to properly support cancellation and to distinguish enumerated devices
57+
list, err := c.libusb.getDevices(c.ctx)
58+
if err != nil {
59+
dereg()
60+
return nil, err
61+
}
62+
for _, dev := range list {
63+
desc, err := c.libusb.getDeviceDesc(dev)
64+
e := &hotplugEvent{
65+
eventType: HotplugEventDeviceArrived,
66+
ctx: c,
67+
dev: dev,
68+
desc: desc,
69+
err: err,
70+
enumerated: true,
71+
}
72+
fn(e)
73+
if e.deregister {
74+
dereg()
75+
break
76+
}
77+
}
78+
return dereg, nil
79+
}
80+
81+
type hotplugEvent struct {
82+
eventType HotplugEventType
83+
ctx *Context
84+
dev *libusbDevice
85+
desc *DeviceDesc
86+
err error
87+
enumerated bool
88+
deregister bool
89+
}
90+
91+
// Type returns the event's type (HotplugEventDeviceArrived or HotplugEventDeviceLeft).
92+
func (e *hotplugEvent) Type() HotplugEventType {
93+
return e.eventType
94+
}
95+
96+
// DeviceDesc returns the device's descriptor.
97+
func (e *hotplugEvent) DeviceDesc() (*DeviceDesc, error) {
98+
return e.desc, e.err
99+
}
100+
101+
// IsEnumerated returns true if the device was already plugged in when the callback was registered.
102+
func (e *hotplugEvent) IsEnumerated() bool {
103+
return e.enumerated
104+
}
105+
106+
// Open opens the device.
107+
func (e *hotplugEvent) Open() (*Device, error) {
108+
if e.err != nil {
109+
return nil, e.err
110+
}
111+
handle, err := e.ctx.libusb.open(e.dev)
112+
if err != nil {
113+
return nil, err
114+
}
115+
return &Device{handle: handle, ctx: e.ctx, Desc: e.desc}, nil
116+
}
117+
118+
// Deregister deregisters the callback registration after the callback function returns.
119+
func (e *hotplugEvent) Deregister() {
120+
e.deregister = true
121+
}

libusb.go

Lines changed: 115 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
package gousb
1616

1717
import (
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
3032
int gousb_compact_iso_data(struct libusb_transfer *xfer, unsigned char *status);
3133
struct libusb_transfer *gousb_alloc_transfer_and_buffer(int bufLen, int numIsoPackets);
3234
void gousb_free_transfer_and_buffer(struct libusb_transfer *xfer);
3335
int submit(struct libusb_transfer *xfer);
3436
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+
);
3547
*/
3648
import "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

217234
func (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 {
510538
func 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

Comments
 (0)