Skip to content

Commit abb0f6b

Browse files
committed
Merge google#32 into master.
2 parents 0eba1b1 + 26ac067 commit abb0f6b

5 files changed

Lines changed: 286 additions & 0 deletions

File tree

constants.go

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

275275
// Milliamperes is a unit of electric current consumption.
276276
type Milliamperes uint
277+
278+
// HotplugEventType identifies the type of the hotplug event.
279+
type HotplugEventType uint
280+
281+
// Hotplug events.
282+
const (
283+
HotplugEventDeviceArrived HotplugEventType = C.LIBUSB_HOTPLUG_EVENT_DEVICE_ARRIVED
284+
HotplugEventDeviceLeft HotplugEventType = C.LIBUSB_HOTPLUG_EVENT_DEVICE_LEFT
285+
HotplugEventAny HotplugEventType = HotplugEventDeviceArrived | HotplugEventDeviceLeft
286+
)
287+
288+
const hotplugMatchAny = C.LIBUSB_HOTPLUG_MATCH_ANY

fakelibusb_test.go

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

289+
func (f *fakeLibusb) registerHotplugCallback(ctx *libusbContext, events HotplugEventType, enumerate bool, vendorId int32, productId int32, devClass int32, fn libusbHotplugCallback) (func(), error) {
290+
// TODO: implement
291+
return func() {
292+
}, nil
293+
}
294+
289295
func newFakeLibusb() *fakeLibusb {
290296
fl := &fakeLibusb{
291297
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

@@ -524,3 +552,90 @@ func newContextPointer() *libusbContext {
524552
func newDevHandlePointer() *libusbDevHandle {
525553
return (*libusbDevHandle)(unsafe.Pointer(C.malloc(1)))
526554
}
555+
556+
// hotplugCallbackMap keeps a map of go callback functions for libusb hotplug callbacks
557+
// for each context.
558+
// When a context is closed with libusb_exit, its callbacks are automatically deregistered
559+
// by libusb, and they are removed from this map too.
560+
var hotplugCallbackMap = struct {
561+
m map[*libusbContext]map[unsafe.Pointer]libusbHotplugCallback
562+
sync.RWMutex
563+
}{
564+
m: make(map[*libusbContext]map[unsafe.Pointer]libusbHotplugCallback),
565+
}
566+
567+
//export goHotplugCallback
568+
func goHotplugCallback(ctx *C.libusb_context, device *C.libusb_device, event C.libusb_hotplug_event, userData unsafe.Pointer) C.int {
569+
var fn libusbHotplugCallback
570+
hotplugCallbackMap.RLock()
571+
m, ok := hotplugCallbackMap.m[(*libusbContext)(ctx)]
572+
if ok {
573+
fn, ok = m[userData]
574+
}
575+
hotplugCallbackMap.RUnlock()
576+
if !ok {
577+
// This shouldn't happen. Deregister the callback.
578+
return 1
579+
}
580+
dereg := fn((*libusbContext)(ctx), (*libusbDevice)(device), HotplugEventType(event))
581+
582+
if dereg {
583+
hotplugCallbackMap.Lock()
584+
m, ok := hotplugCallbackMap.m[(*libusbContext)(ctx)]
585+
if ok {
586+
delete(m, userData)
587+
C.free(userData)
588+
}
589+
hotplugCallbackMap.Unlock()
590+
return 1
591+
}
592+
return 0
593+
}
594+
595+
func (libusbImpl) registerHotplugCallback(ctx *libusbContext, events HotplugEventType, enumerate bool, vendorID int32, productID int32, devClass int32, fn libusbHotplugCallback) (func(), error) {
596+
// We must allocate memory to pass to C, since we can't pass a go pointer.
597+
// We can use the resulting pointer as a map key instead of
598+
// storing the map key inside the memory allocated.
599+
id := C.malloc(1)
600+
if id == nil {
601+
panic(errors.New("Failed to allocate memory during callback registration"))
602+
}
603+
hotplugCallbackMap.Lock()
604+
m, ok := hotplugCallbackMap.m[ctx]
605+
if !ok {
606+
hotplugCallbackMap.m[ctx] = make(map[unsafe.Pointer]libusbHotplugCallback)
607+
m = hotplugCallbackMap.m[ctx]
608+
}
609+
m[id] = fn
610+
hotplugCallbackMap.Unlock()
611+
612+
var flags C.libusb_hotplug_flag
613+
if enumerate {
614+
flags = C.LIBUSB_HOTPLUG_ENUMERATE
615+
}
616+
var handle C.libusb_hotplug_callback_handle
617+
618+
// TODO: figure out how to run deregister in callback.
619+
// There's a race condition here, because the callback may be called before
620+
// gousb_hotplug_register_callback returns, depending on libusb's implementation.
621+
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)
622+
err := fromErrNo(res)
623+
if err != nil {
624+
hotplugCallbackMap.Lock()
625+
delete(hotplugCallbackMap.m[ctx], id)
626+
hotplugCallbackMap.Unlock()
627+
C.free(id)
628+
return nil, err
629+
}
630+
631+
return func() {
632+
C.libusb_hotplug_deregister_callback((*C.libusb_context)(ctx), handle)
633+
hotplugCallbackMap.Lock()
634+
m, ok := hotplugCallbackMap.m[ctx]
635+
if ok {
636+
delete(m, id)
637+
C.free(id)
638+
}
639+
hotplugCallbackMap.Unlock()
640+
}, nil
641+
}

0 commit comments

Comments
 (0)