diff --git a/fakelibusb_devices.go b/fakelibusb_devices.go index ff1cf1a..7b56eb3 100644 --- a/fakelibusb_devices.go +++ b/fakelibusb_devices.go @@ -16,9 +16,10 @@ package gousb // fake devices connected through the fakeLibusb stack. type fakeDevice struct { - devDesc *DeviceDesc - strDesc map[int]string - alt uint8 + devDesc *DeviceDesc + strDesc map[int]string + alt uint8 + sysDevPtr uintptr } var fakeDevices = []fakeDevice{ @@ -64,6 +65,7 @@ var fakeDevices = []fakeDevice{ }}, }}, }, + sysDevPtr: 78, }, // Bus 001 Device 002: ID 8888:0002 // One config, two interfaces. interface #0 with no endpoints, @@ -186,6 +188,7 @@ var fakeDevices = []fakeDevice{ 8: "Slower streaming", 9: "Interface for https://github.com/google/gousb/issues/65", }, + sysDevPtr: 94, }, // Bus 001 Device 003: ID 9999:0002 // One config, one interface, one setup, diff --git a/fakelibusb_test.go b/fakelibusb_test.go index 868db65..b9ca735 100644 --- a/fakelibusb_test.go +++ b/fakelibusb_test.go @@ -85,8 +85,10 @@ func (t *fakeTransfer) setStatus(st TransferStatus) { // that allows the test to explicitly control individual transfer behavior. type fakeLibusb struct { mu sync.Mutex - // fakeDevices has a map of devices and their descriptors. - fakeDevices map[*libusbDevice]*fakeDevice + // devices has a map of devices and their descriptors. + 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 // underlying libusbTransfer. 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) getDevices(*libusbContext) ([]*libusbDevice, error) { ret := make([]*libusbDevice, 0, len(fakeDevices)) - for d := range f.fakeDevices { + for d := range f.devices { ret = append(ret, d) } 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 { close(f.submitted) 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) dereference(d *libusbDevice) {} 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 nil, fmt.Errorf("invalid USB device %p", d) @@ -158,7 +176,7 @@ func (f *fakeLibusb) setConfig(d *libusbDevHandle, cfg uint8) error { return nil } 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 { 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] { 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 } @@ -288,11 +306,12 @@ func (f *fakeLibusb) empty() bool { func newFakeLibusb() *fakeLibusb { fl := &fakeLibusb{ - fakeDevices: make(map[*libusbDevice]*fakeDevice), - ts: make(map[*libusbTransfer]*fakeTransfer), - submitted: make(chan *fakeTransfer, 10), - handles: make(map[*libusbDevHandle]*libusbDevice), - claims: make(map[*libusbDevice]map[uint8]bool), + devices: make(map[*libusbDevice]*fakeDevice), + sysDevices: make(map[uintptr]*libusbDevice), + ts: make(map[*libusbTransfer]*fakeTransfer), + submitted: make(chan *fakeTransfer, 10), + handles: make(map[*libusbDevHandle]*libusbDevice), + claims: make(map[*libusbDevice]map[uint8]bool), } for _, d := range fakeDevices { // 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. fd := new(fakeDevice) *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 } diff --git a/libusb.go b/libusb.go index ddc0b2a..0997dc8 100644 --- a/libusb.go +++ b/libusb.go @@ -143,6 +143,7 @@ type libusbIntf interface { dereference(*libusbDevice) getDeviceDesc(*libusbDevice) (*DeviceDesc, error) open(*libusbDevice) (*libusbDevHandle, error) + wrapSysDevice(*libusbContext, uintptr) (*libusbDevHandle, error) close(*libusbDevHandle) reset(*libusbDevHandle) error @@ -152,6 +153,7 @@ type libusbIntf interface { getStringDesc(*libusbDevHandle, int) (string, error) setAutoDetach(*libusbDevHandle, int) error detachKernelDriver(*libusbDevHandle, uint8) error + getDevice(*libusbDevHandle) *libusbDevice // interface claim(*libusbDevHandle, uint8) error @@ -169,13 +171,24 @@ type libusbIntf interface { } // 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 - 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 (*libusbContext)(ctx), nil } @@ -217,6 +230,20 @@ func (libusbImpl) getDevices(ctx *libusbContext) ([]*libusbDevice, error) { 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 { C.libusb_exit((*C.libusb_context)(c)) return nil diff --git a/usb.go b/usb.go index 25fbb2f..b77edb4 100644 --- a/usb.go +++ b/usb.go @@ -163,9 +163,36 @@ func newContextWithImpl(impl libusbIntf) *Context { return ctx } -// NewContext returns a new Context instance. +// NewContext returns a new Context instance with default ContextOptions. 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. @@ -210,6 +237,41 @@ func (c *Context) OpenDevices(opener func(desc *DeviceDesc) bool) ([]*Device, er 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. // 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. diff --git a/usb_test.go b/usb_test.go index d98f8bc..1c59436 100644 --- a/usb_test.go +++ b/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) + } + } + +}