Android support via fileDescriptor (#130)
Adds a new way to initialize the context, `ContextOptions`, and a new way to open devices, `OpenDeviceWithFileDescriptor`. Combined they can be used to support Android devices, where device enumeration is not supported (can be now disabled through `ContextOptions`) and where USB devices can be opened in the Android SDK and passed over to gousb through a file descriptor. Co-authored-by: Juan <994594+juaoose@users.noreply.github.com> Co-authored-by: Kuba Raczkowski <kuba.raczkowski@spectricity.com>
This commit is contained in:
@@ -16,9 +16,10 @@ package gousb
|
|||||||
|
|
||||||
// fake devices connected through the fakeLibusb stack.
|
// fake devices connected through the fakeLibusb stack.
|
||||||
type fakeDevice struct {
|
type fakeDevice struct {
|
||||||
devDesc *DeviceDesc
|
devDesc *DeviceDesc
|
||||||
strDesc map[int]string
|
strDesc map[int]string
|
||||||
alt uint8
|
alt uint8
|
||||||
|
sysDevPtr uintptr
|
||||||
}
|
}
|
||||||
|
|
||||||
var fakeDevices = []fakeDevice{
|
var fakeDevices = []fakeDevice{
|
||||||
@@ -64,6 +65,7 @@ var fakeDevices = []fakeDevice{
|
|||||||
}},
|
}},
|
||||||
}},
|
}},
|
||||||
},
|
},
|
||||||
|
sysDevPtr: 78,
|
||||||
},
|
},
|
||||||
// Bus 001 Device 002: ID 8888:0002
|
// Bus 001 Device 002: ID 8888:0002
|
||||||
// One config, two interfaces. interface #0 with no endpoints,
|
// One config, two interfaces. interface #0 with no endpoints,
|
||||||
@@ -186,6 +188,7 @@ var fakeDevices = []fakeDevice{
|
|||||||
8: "Slower streaming",
|
8: "Slower streaming",
|
||||||
9: "Interface for https://github.com/google/gousb/issues/65",
|
9: "Interface for https://github.com/google/gousb/issues/65",
|
||||||
},
|
},
|
||||||
|
sysDevPtr: 94,
|
||||||
},
|
},
|
||||||
// Bus 001 Device 003: ID 9999:0002
|
// Bus 001 Device 003: ID 9999:0002
|
||||||
// One config, one interface, one setup,
|
// One config, one interface, one setup,
|
||||||
|
@@ -85,8 +85,10 @@ func (t *fakeTransfer) setStatus(st TransferStatus) {
|
|||||||
// that allows the test to explicitly control individual transfer behavior.
|
// that allows the test to explicitly control individual transfer behavior.
|
||||||
type fakeLibusb struct {
|
type fakeLibusb struct {
|
||||||
mu sync.Mutex
|
mu sync.Mutex
|
||||||
// fakeDevices has a map of devices and their descriptors.
|
// devices has a map of devices and their descriptors.
|
||||||
fakeDevices map[*libusbDevice]*fakeDevice
|
devices map[*libusbDevice]*fakeDevice
|
||||||
|
// sysDevices keeps the order of devices to be accessd by wrapSysDevice
|
||||||
|
sysDevices map[uintptr]*libusbDevice
|
||||||
// ts has a map of all allocated transfers, indexed by the pointer of
|
// ts has a map of all allocated transfers, indexed by the pointer of
|
||||||
// underlying libusbTransfer.
|
// underlying libusbTransfer.
|
||||||
ts map[*libusbTransfer]*fakeTransfer
|
ts map[*libusbTransfer]*fakeTransfer
|
||||||
@@ -102,12 +104,28 @@ func (f *fakeLibusb) init() (*libusbContext, error) { retu
|
|||||||
func (f *fakeLibusb) handleEvents(c *libusbContext, done <-chan struct{}) { <-done }
|
func (f *fakeLibusb) handleEvents(c *libusbContext, done <-chan struct{}) { <-done }
|
||||||
func (f *fakeLibusb) getDevices(*libusbContext) ([]*libusbDevice, error) {
|
func (f *fakeLibusb) getDevices(*libusbContext) ([]*libusbDevice, error) {
|
||||||
ret := make([]*libusbDevice, 0, len(fakeDevices))
|
ret := make([]*libusbDevice, 0, len(fakeDevices))
|
||||||
for d := range f.fakeDevices {
|
for d := range f.devices {
|
||||||
ret = append(ret, d)
|
ret = append(ret, d)
|
||||||
}
|
}
|
||||||
return ret, nil
|
return ret, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (f *fakeLibusb) wrapSysDevice(ctx *libusbContext, systemDeviceHandle uintptr) (*libusbDevHandle, error) {
|
||||||
|
dev, ok := f.sysDevices[systemDeviceHandle]
|
||||||
|
if !ok {
|
||||||
|
return nil, fmt.Errorf("the passed file descriptor %d does not point to a valid device", systemDeviceHandle)
|
||||||
|
}
|
||||||
|
h := newDevHandlePointer()
|
||||||
|
f.mu.Lock()
|
||||||
|
defer f.mu.Unlock()
|
||||||
|
f.handles[h] = dev
|
||||||
|
return h, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f *fakeLibusb) getDevice(handle *libusbDevHandle) *libusbDevice {
|
||||||
|
return f.handles[handle]
|
||||||
|
}
|
||||||
|
|
||||||
func (f *fakeLibusb) exit(*libusbContext) error {
|
func (f *fakeLibusb) exit(*libusbContext) error {
|
||||||
close(f.submitted)
|
close(f.submitted)
|
||||||
if got := len(f.ts); got > 0 {
|
if got := len(f.ts); got > 0 {
|
||||||
@@ -122,7 +140,7 @@ func (f *fakeLibusb) exit(*libusbContext) error {
|
|||||||
func (f *fakeLibusb) setDebug(*libusbContext, int) {}
|
func (f *fakeLibusb) setDebug(*libusbContext, int) {}
|
||||||
func (f *fakeLibusb) dereference(d *libusbDevice) {}
|
func (f *fakeLibusb) dereference(d *libusbDevice) {}
|
||||||
func (f *fakeLibusb) getDeviceDesc(d *libusbDevice) (*DeviceDesc, error) {
|
func (f *fakeLibusb) getDeviceDesc(d *libusbDevice) (*DeviceDesc, error) {
|
||||||
if dev, ok := f.fakeDevices[d]; ok {
|
if dev, ok := f.devices[d]; ok {
|
||||||
return dev.devDesc, nil
|
return dev.devDesc, nil
|
||||||
}
|
}
|
||||||
return nil, fmt.Errorf("invalid USB device %p", d)
|
return nil, fmt.Errorf("invalid USB device %p", d)
|
||||||
@@ -158,7 +176,7 @@ func (f *fakeLibusb) setConfig(d *libusbDevHandle, cfg uint8) error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
func (f *fakeLibusb) getStringDesc(d *libusbDevHandle, index int) (string, error) {
|
func (f *fakeLibusb) getStringDesc(d *libusbDevHandle, index int) (string, error) {
|
||||||
dev, ok := f.fakeDevices[f.handles[d]]
|
dev, ok := f.devices[f.handles[d]]
|
||||||
if !ok {
|
if !ok {
|
||||||
return "", fmt.Errorf("invalid USB device %p", d)
|
return "", fmt.Errorf("invalid USB device %p", d)
|
||||||
}
|
}
|
||||||
@@ -201,7 +219,7 @@ func (f *fakeLibusb) setAlt(d *libusbDevHandle, intf, alt uint8) error {
|
|||||||
if !f.claims[f.handles[d]][intf] {
|
if !f.claims[f.handles[d]][intf] {
|
||||||
return fmt.Errorf("interface %d must be claimed before alt setup can be set", intf)
|
return fmt.Errorf("interface %d must be claimed before alt setup can be set", intf)
|
||||||
}
|
}
|
||||||
f.fakeDevices[f.handles[d]].alt = alt
|
f.devices[f.handles[d]].alt = alt
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -288,11 +306,12 @@ func (f *fakeLibusb) empty() bool {
|
|||||||
|
|
||||||
func newFakeLibusb() *fakeLibusb {
|
func newFakeLibusb() *fakeLibusb {
|
||||||
fl := &fakeLibusb{
|
fl := &fakeLibusb{
|
||||||
fakeDevices: make(map[*libusbDevice]*fakeDevice),
|
devices: make(map[*libusbDevice]*fakeDevice),
|
||||||
ts: make(map[*libusbTransfer]*fakeTransfer),
|
sysDevices: make(map[uintptr]*libusbDevice),
|
||||||
submitted: make(chan *fakeTransfer, 10),
|
ts: make(map[*libusbTransfer]*fakeTransfer),
|
||||||
handles: make(map[*libusbDevHandle]*libusbDevice),
|
submitted: make(chan *fakeTransfer, 10),
|
||||||
claims: make(map[*libusbDevice]map[uint8]bool),
|
handles: make(map[*libusbDevHandle]*libusbDevice),
|
||||||
|
claims: make(map[*libusbDevice]map[uint8]bool),
|
||||||
}
|
}
|
||||||
for _, d := range fakeDevices {
|
for _, d := range fakeDevices {
|
||||||
// libusb does not export a way to allocate a new libusb_device struct
|
// libusb does not export a way to allocate a new libusb_device struct
|
||||||
@@ -301,7 +320,11 @@ func newFakeLibusb() *fakeLibusb {
|
|||||||
// The contents of these pointers is never accessed.
|
// The contents of these pointers is never accessed.
|
||||||
fd := new(fakeDevice)
|
fd := new(fakeDevice)
|
||||||
*fd = d
|
*fd = d
|
||||||
fl.fakeDevices[newDevicePointer()] = fd
|
devPointer := newDevicePointer()
|
||||||
|
fl.devices[devPointer] = fd
|
||||||
|
if fd.sysDevPtr != 0 { // the sysDevPtr not being set in the fakeDevices list
|
||||||
|
fl.sysDevices[fd.sysDevPtr] = devPointer
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return fl
|
return fl
|
||||||
}
|
}
|
||||||
|
33
libusb.go
33
libusb.go
@@ -143,6 +143,7 @@ type libusbIntf interface {
|
|||||||
dereference(*libusbDevice)
|
dereference(*libusbDevice)
|
||||||
getDeviceDesc(*libusbDevice) (*DeviceDesc, error)
|
getDeviceDesc(*libusbDevice) (*DeviceDesc, error)
|
||||||
open(*libusbDevice) (*libusbDevHandle, error)
|
open(*libusbDevice) (*libusbDevHandle, error)
|
||||||
|
wrapSysDevice(*libusbContext, uintptr) (*libusbDevHandle, error)
|
||||||
|
|
||||||
close(*libusbDevHandle)
|
close(*libusbDevHandle)
|
||||||
reset(*libusbDevHandle) error
|
reset(*libusbDevHandle) error
|
||||||
@@ -152,6 +153,7 @@ type libusbIntf interface {
|
|||||||
getStringDesc(*libusbDevHandle, int) (string, error)
|
getStringDesc(*libusbDevHandle, int) (string, error)
|
||||||
setAutoDetach(*libusbDevHandle, int) error
|
setAutoDetach(*libusbDevHandle, int) error
|
||||||
detachKernelDriver(*libusbDevHandle, uint8) error
|
detachKernelDriver(*libusbDevHandle, uint8) error
|
||||||
|
getDevice(*libusbDevHandle) *libusbDevice
|
||||||
|
|
||||||
// interface
|
// interface
|
||||||
claim(*libusbDevHandle, uint8) error
|
claim(*libusbDevHandle, uint8) error
|
||||||
@@ -169,13 +171,24 @@ type libusbIntf interface {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// libusbImpl is an implementation of libusbIntf using real CGo-wrapped libusb.
|
// libusbImpl is an implementation of libusbIntf using real CGo-wrapped libusb.
|
||||||
type libusbImpl struct{}
|
type libusbImpl struct {
|
||||||
|
discovery DeviceDiscovery
|
||||||
|
}
|
||||||
|
|
||||||
func (libusbImpl) init() (*libusbContext, error) {
|
func (impl libusbImpl) init() (*libusbContext, error) {
|
||||||
var ctx *C.libusb_context
|
var ctx *C.libusb_context
|
||||||
if err := fromErrNo(C.libusb_init(&ctx)); err != nil {
|
|
||||||
|
var libusbOpts [4]C.struct_libusb_init_option // fixed to 4 - there are maximum 4 options
|
||||||
|
nOpts := 0
|
||||||
|
if impl.discovery == DisableDeviceDiscovery {
|
||||||
|
libusbOpts[nOpts].option = C.LIBUSB_OPTION_NO_DEVICE_DISCOVERY
|
||||||
|
nOpts++
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := fromErrNo(C.libusb_init_context(&ctx, &(libusbOpts[0]), C.int(nOpts))); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
return (*libusbContext)(ctx), nil
|
return (*libusbContext)(ctx), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -217,6 +230,20 @@ func (libusbImpl) getDevices(ctx *libusbContext) ([]*libusbDevice, error) {
|
|||||||
return ret, nil
|
return ret, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (libusbImpl) wrapSysDevice(ctx *libusbContext, fd uintptr) (*libusbDevHandle, error) {
|
||||||
|
var handle *C.libusb_device_handle
|
||||||
|
if ret := C.libusb_wrap_sys_device((*C.libusb_context)(ctx), C.intptr_t(fd), &handle); ret < 0 {
|
||||||
|
return nil, fromErrNo(C.int(ret))
|
||||||
|
}
|
||||||
|
|
||||||
|
return (*libusbDevHandle)(handle), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (libusbImpl) getDevice(d *libusbDevHandle) *libusbDevice {
|
||||||
|
device := C.libusb_get_device((*C.libusb_device_handle)(d))
|
||||||
|
return (*libusbDevice)(device)
|
||||||
|
}
|
||||||
|
|
||||||
func (libusbImpl) exit(c *libusbContext) error {
|
func (libusbImpl) exit(c *libusbContext) error {
|
||||||
C.libusb_exit((*C.libusb_context)(c))
|
C.libusb_exit((*C.libusb_context)(c))
|
||||||
return nil
|
return nil
|
||||||
|
66
usb.go
66
usb.go
@@ -163,9 +163,36 @@ func newContextWithImpl(impl libusbIntf) *Context {
|
|||||||
return ctx
|
return ctx
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewContext returns a new Context instance.
|
// NewContext returns a new Context instance with default ContextOptions.
|
||||||
func NewContext() *Context {
|
func NewContext() *Context {
|
||||||
return newContextWithImpl(libusbImpl{})
|
return ContextOptions{}.New()
|
||||||
|
}
|
||||||
|
|
||||||
|
// DeviceDiscovery controls USB device discovery.
|
||||||
|
type DeviceDiscovery int
|
||||||
|
|
||||||
|
const (
|
||||||
|
// EnableDeviceDiscovery means the connected USB devices will be enumerated
|
||||||
|
// on Context initialization. This enables the use of OpenDevices and
|
||||||
|
// OpenWithVIDPID. This is the default.
|
||||||
|
EnableDeviceDiscovery = iota
|
||||||
|
// DisableDeviceDiscovery means the USB devices are not enumerated and
|
||||||
|
// OpenDevices will not return any devices.
|
||||||
|
// Without device discovery, OpenDeviceWithFileDescriptor can be used
|
||||||
|
// to open devices.
|
||||||
|
DisableDeviceDiscovery
|
||||||
|
)
|
||||||
|
|
||||||
|
// ContextOptions holds parameters for Context initialization.
|
||||||
|
type ContextOptions struct {
|
||||||
|
DeviceDiscovery DeviceDiscovery
|
||||||
|
}
|
||||||
|
|
||||||
|
// New creates a Context, taking into account the optional flags contained in ContextOptions
|
||||||
|
func (o ContextOptions) New() *Context {
|
||||||
|
return newContextWithImpl(libusbImpl{
|
||||||
|
discovery: o.DeviceDiscovery,
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
// OpenDevices calls opener with each enumerated device.
|
// OpenDevices calls opener with each enumerated device.
|
||||||
@@ -210,6 +237,41 @@ func (c *Context) OpenDevices(opener func(desc *DeviceDesc) bool) ([]*Device, er
|
|||||||
return ret, reterr
|
return ret, reterr
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// OpenDeviceWithFileDescriptor takes a (Unix) file descriptor of an opened USB
|
||||||
|
// device and wraps the library around it.
|
||||||
|
// This is particularly useful when working on Android, where the USB device can be
|
||||||
|
// opened by the SDK (Java), giving access to the device through the file descriptor
|
||||||
|
// (https://developer.android.com/reference/android/hardware/usb/UsbDeviceConnection#getFileDescriptor()).
|
||||||
|
//
|
||||||
|
// Do note that for this to work the automatic device discovery must be disabled
|
||||||
|
// at the time when the new Context is created, through the use of
|
||||||
|
// ContextOptions.DeviceDiscovery.
|
||||||
|
//
|
||||||
|
// Example:
|
||||||
|
//
|
||||||
|
// ctx := ContextOptions{DeviceDiscovery: DisableDeviceDiscovery}.New()
|
||||||
|
// device, err := ctx.OpenDeviceWithFileDescriptor(fd)
|
||||||
|
//
|
||||||
|
// An error is returned in case the file descriptor is not valid.
|
||||||
|
func (c *Context) OpenDeviceWithFileDescriptor(fd uintptr) (*Device, error) {
|
||||||
|
handle, err := c.libusb.wrapSysDevice(c.ctx, fd)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
dev := c.libusb.getDevice(handle)
|
||||||
|
desc, err := c.libusb.getDeviceDesc(dev)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("device was opened, but getting device descriptor failed: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
o := &Device{handle: handle, ctx: c, Desc: desc}
|
||||||
|
c.mu.Lock()
|
||||||
|
c.devices[o] = true
|
||||||
|
c.mu.Unlock()
|
||||||
|
|
||||||
|
return o, nil
|
||||||
|
}
|
||||||
|
|
||||||
// OpenDeviceWithVIDPID opens Device from specific VendorId and ProductId.
|
// OpenDeviceWithVIDPID opens Device from specific VendorId and ProductId.
|
||||||
// If none is found, it returns nil and nil error. If there are multiple devices
|
// If none is found, it returns nil and nil error. If there are multiple devices
|
||||||
// with the same VID/PID, it will return one of them, picked arbitrarily.
|
// with the same VID/PID, it will return one of them, picked arbitrarily.
|
||||||
|
41
usb_test.go
41
usb_test.go
@@ -94,3 +94,44 @@ func TestOpenDeviceWithVIDPID(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestOpenDeviceWithFileDescriptor(t *testing.T) {
|
||||||
|
ctx := newContextWithImpl(newFakeLibusb())
|
||||||
|
defer ctx.Close()
|
||||||
|
|
||||||
|
// file descriptor is an index to the FakeDevices array
|
||||||
|
for _, d := range []struct {
|
||||||
|
vid, pid ID
|
||||||
|
sysDevPtr uintptr
|
||||||
|
}{
|
||||||
|
{0x9999, 0x0001, 78},
|
||||||
|
{0x8888, 0x0002, 94},
|
||||||
|
} {
|
||||||
|
dev, err := ctx.OpenDeviceWithFileDescriptor(d.sysDevPtr)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("OpenDeviceWithFileDescriptor(%d): err != nil for a valid device: %v", d.sysDevPtr, err)
|
||||||
|
}
|
||||||
|
if dev == nil {
|
||||||
|
t.Fatalf("OpenDeviceWithFileDescriptor(%d): device == nil for a valid device", d.sysDevPtr)
|
||||||
|
}
|
||||||
|
if dev != nil && (dev.Desc.Vendor != ID(d.vid) || dev.Desc.Product != ID(d.pid)) {
|
||||||
|
t.Errorf("OpenDeviceWithFileDescriptor(%d): device's VID/PID %s/%s don't match expected: %s/%s", d.sysDevPtr, dev.Desc.Vendor, dev.Desc.Product, ID(d.vid), ID(d.pid))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestOpenDeviceWithFileDescriptorOnMissingDevice(t *testing.T) {
|
||||||
|
ctx := newContextWithImpl(newFakeLibusb())
|
||||||
|
defer ctx.Close()
|
||||||
|
|
||||||
|
for _, sysDevPtr := range []uintptr{
|
||||||
|
7, // set, but does not exist in the fakeDevices array
|
||||||
|
0, // unset
|
||||||
|
} {
|
||||||
|
if _, err := ctx.OpenDeviceWithFileDescriptor(sysDevPtr); err == nil {
|
||||||
|
t.Errorf("OpenDeviceWithFileDescriptor(%d): got nil error for invalid device", sysDevPtr)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
Reference in New Issue
Block a user