Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 12 additions & 0 deletions constants.go
Original file line number Diff line number Diff line change
Expand Up @@ -256,3 +256,15 @@ const (

// Milliamperes is a unit of electric current consumption.
type Milliamperes uint

// HotplugEventType identifies the type of the hotplug event.
type HotplugEventType uint

// Hotplug events.
const (
HotplugEventDeviceArrived HotplugEventType = C.LIBUSB_HOTPLUG_EVENT_DEVICE_ARRIVED
HotplugEventDeviceLeft HotplugEventType = C.LIBUSB_HOTPLUG_EVENT_DEVICE_LEFT
HotplugEventAny HotplugEventType = HotplugEventDeviceArrived | HotplugEventDeviceLeft
)

const hotplugMatchAny = C.LIBUSB_HOTPLUG_MATCH_ANY
6 changes: 6 additions & 0 deletions fakelibusb_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -209,6 +209,12 @@ func (f *fakeLibusb) empty() bool {
return len(f.submitted) == 0
}

func (f *fakeLibusb) registerHotplugCallback(ctx *libusbContext, events HotplugEventType, enumerate bool, vendorId int32, productId int32, devClass int32, fn libusbHotplugCallback) (func(), error) {
// TODO: implement
return func() {
}, nil
}

func newFakeLibusb() *fakeLibusb {
fl := &fakeLibusb{
fakeDevices: make(map[*libusbDevice]*fakeDevice),
Expand Down
32 changes: 32 additions & 0 deletions hotplug.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
// Copyright 2013 Google Inc. All rights reserved.
// Copyright 2016 the gousb Authors. All rights reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

#include <libusb.h>
#include "_cgo_export.h"

int gousb_hotplug_register_callback(
libusb_context* ctx,
libusb_hotplug_event events,
libusb_hotplug_flag flags,
int vid,
int pid,
int dev_class,
void *user_data,
libusb_hotplug_callback_handle *handle
) {
return libusb_hotplug_register_callback(
ctx, events, flags, vid, pid, dev_class, (libusb_hotplug_callback_fn)(goHotplugCallback), user_data, handle
);
}
121 changes: 121 additions & 0 deletions hotplug.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,121 @@
// Copyright 2013 Google Inc. All rights reserved.
// Copyright 2016 the gousb Authors. All rights reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package gousb

type HotplugEvent interface {
// Type returns the event's type (HotplugEventDeviceArrived or HotplugEventDeviceLeft).
Type() HotplugEventType
// IsEnumerated returns true if the device was already plugged in when the callback was registered.
IsEnumerated() bool
// DeviceDesc returns the device's descriptor.
DeviceDesc() (*DeviceDesc, error)
// Open opens the device.
Open() (*Device, error)
// Deregister deregisters the callback registration after the callback function returns.
Deregister()
}

// RegisterHotplug registers a hotplug callback function.
// The callback will receive arrive events for all currently plugged in devices.
// These events will return true from IsEnumerated().
// Note that events are delivered concurrently. You may receive arrive and leave events
// concurrently with enumerated arrive events. You may also receive arrive events twice
// for the same device, and you may receive a leave event for a device for which
// you never received an arrive event.
func (c *Context) RegisterHotplug(fn func(HotplugEvent)) (func(), error) {
dereg, err := c.libusb.registerHotplugCallback(c.ctx, HotplugEventAny, false, hotplugMatchAny, hotplugMatchAny, hotplugMatchAny, func(ctx *libusbContext, dev *libusbDevice, eventType HotplugEventType) bool {
desc, err := c.libusb.getDeviceDesc(dev)
e := &hotplugEvent{
eventType: eventType,
ctx: c,
dev: dev,
desc: desc,
err: err,
enumerated: false,
}
fn(e)
return e.deregister
})
if err != nil {
return nil, err
}
// enumerate devices
// this is done in gousb to properly support cancellation and to distinguish enumerated devices
list, err := c.libusb.getDevices(c.ctx)
if err != nil {
dereg()
return nil, err
}
for _, dev := range list {
desc, err := c.libusb.getDeviceDesc(dev)
e := &hotplugEvent{
eventType: HotplugEventDeviceArrived,
ctx: c,
dev: dev,
desc: desc,
err: err,
enumerated: true,
}
fn(e)
if e.deregister {
dereg()
break
}
}
return dereg, nil
}

type hotplugEvent struct {
eventType HotplugEventType
ctx *Context
dev *libusbDevice
desc *DeviceDesc
err error
enumerated bool
deregister bool
}

// Type returns the event's type (HotplugEventDeviceArrived or HotplugEventDeviceLeft).
func (e *hotplugEvent) Type() HotplugEventType {
return e.eventType
}

// DeviceDesc returns the device's descriptor.
func (e *hotplugEvent) DeviceDesc() (*DeviceDesc, error) {
return e.desc, e.err
}

// IsEnumerated returns true if the device was already plugged in when the callback was registered.
func (e *hotplugEvent) IsEnumerated() bool {
return e.enumerated
}

// Open opens the device.
func (e *hotplugEvent) Open() (*Device, error) {
if e.err != nil {
return nil, e.err
}
handle, err := e.ctx.libusb.open(e.dev)
if err != nil {
return nil, err
}
return &Device{handle: handle, ctx: e.ctx, Desc: e.desc}, nil
}

// Deregister deregisters the callback registration after the callback function returns.
func (e *hotplugEvent) Deregister() {
e.deregister = true
}
115 changes: 115 additions & 0 deletions libusb.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
package gousb

import (
"errors"
"fmt"
"log"
"reflect"
Expand All @@ -26,11 +27,22 @@ import (
/*
#cgo pkg-config: libusb-1.0
#include <libusb.h>
#include <stdlib.h>

int gousb_compact_iso_data(struct libusb_transfer *xfer, unsigned char *status);
struct libusb_transfer *gousb_alloc_transfer_and_buffer(int bufLen, int numIsoPackets);
void gousb_free_transfer_and_buffer(struct libusb_transfer *xfer);
int submit(struct libusb_transfer *xfer);
int gousb_hotplug_register_callback(
libusb_context* ctx,
libusb_hotplug_event events,
libusb_hotplug_flag flags,
int vid,
int pid,
int dev_class,
void *user_data,
libusb_hotplug_callback_handle *handle
);
*/
import "C"

Expand Down Expand Up @@ -124,6 +136,8 @@ func (ep libusbEndpoint) endpointDesc(dev *DeviceDesc) EndpointDesc {
return ei
}

type libusbHotplugCallback func(*libusbContext, *libusbDevice, HotplugEventType) bool

// libusbIntf is a set of trivial idiomatic Go wrappers around libusb C functions.
// The underlying code is generally not testable or difficult to test,
// since libusb interacts directly with the host USB stack.
Expand Down Expand Up @@ -165,6 +179,9 @@ type libusbIntf interface {
data(*libusbTransfer) (int, TransferStatus)
free(*libusbTransfer)
setIsoPacketLengths(*libusbTransfer, uint32)

// hotplug
registerHotplugCallback(ctx *libusbContext, events HotplugEventType, enumerate bool, vendorID int32, productID int32, devClass int32, fn libusbHotplugCallback) (func(), error)
}

// libusbImpl is an implementation of libusbIntf using real CGo-wrapped libusb.
Expand Down Expand Up @@ -215,6 +232,17 @@ func (libusbImpl) getDevices(ctx *libusbContext) ([]*libusbDevice, error) {

func (libusbImpl) exit(c *libusbContext) error {
C.libusb_exit((*C.libusb_context)(c))
// libusb_exit automatically deregisters hotplug callbacks,
// but we need to free the callback map.
hotplugCallbackMap.Lock()
if m, ok := hotplugCallbackMap.m[c]; ok {
for id := range m {
delete(m, id)
C.free(id)
}
}
delete(hotplugCallbackMap.m, c)
hotplugCallbackMap.Unlock()
return nil
}

Expand Down Expand Up @@ -509,3 +537,90 @@ func newDevicePointer() *libusbDevice {
func newFakeTransferPointer() *libusbTransfer {
return (*libusbTransfer)(unsafe.Pointer(C.malloc(1)))
}

// hotplugCallbackMap keeps a map of go callback functions for libusb hotplug callbacks
// for each context.
// When a context is closed with libusb_exit, its callbacks are automatically deregistered
// by libusb, and they are removed from this map too.
var hotplugCallbackMap = struct {
m map[*libusbContext]map[unsafe.Pointer]libusbHotplugCallback
sync.RWMutex
}{
m: make(map[*libusbContext]map[unsafe.Pointer]libusbHotplugCallback),
}

//export goHotplugCallback
func goHotplugCallback(ctx *C.libusb_context, device *C.libusb_device, event C.libusb_hotplug_event, userData unsafe.Pointer) C.int {
var fn libusbHotplugCallback
hotplugCallbackMap.RLock()
m, ok := hotplugCallbackMap.m[(*libusbContext)(ctx)]
if ok {
fn, ok = m[userData]
}
hotplugCallbackMap.RUnlock()
if !ok {
// This shouldn't happen. Deregister the callback.
return 1
}
dereg := fn((*libusbContext)(ctx), (*libusbDevice)(device), HotplugEventType(event))

if dereg {
hotplugCallbackMap.Lock()
m, ok := hotplugCallbackMap.m[(*libusbContext)(ctx)]
if ok {
delete(m, userData)
C.free(userData)
}
hotplugCallbackMap.Unlock()
return 1
}
return 0
}

func (libusbImpl) registerHotplugCallback(ctx *libusbContext, events HotplugEventType, enumerate bool, vendorID int32, productID int32, devClass int32, fn libusbHotplugCallback) (func(), error) {
// We must allocate memory to pass to C, since we can't pass a go pointer.
// We can use the resulting pointer as a map key instead of
// storing the map key inside the memory allocated.
id := C.malloc(1)
if id == nil {
panic(errors.New("Failed to allocate memory during callback registration"))
}
hotplugCallbackMap.Lock()
m, ok := hotplugCallbackMap.m[ctx]
if !ok {
hotplugCallbackMap.m[ctx] = make(map[unsafe.Pointer]libusbHotplugCallback)
m = hotplugCallbackMap.m[ctx]
}
m[id] = fn
hotplugCallbackMap.Unlock()

var flags C.libusb_hotplug_flag
if enumerate {
flags = C.LIBUSB_HOTPLUG_ENUMERATE
}
var handle C.libusb_hotplug_callback_handle

// TODO: figure out how to run deregister in callback.
// There's a race condition here, because the callback may be called before
// gousb_hotplug_register_callback returns, depending on libusb's implementation.
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)
err := fromErrNo(res)
if err != nil {
hotplugCallbackMap.Lock()
delete(hotplugCallbackMap.m[ctx], id)
hotplugCallbackMap.Unlock()
C.free(id)
return nil, err
}

return func() {
C.libusb_hotplug_deregister_callback((*C.libusb_context)(ctx), handle)
hotplugCallbackMap.Lock()
m, ok := hotplugCallbackMap.m[ctx]
if ok {
delete(m, id)
C.free(id)
}
hotplugCallbackMap.Unlock()
}, nil
}