-
Notifications
You must be signed in to change notification settings - Fork 129
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Android support via fileDescriptor #130
Changes from 8 commits
e3b240a
3900892
25dfbc2
beb6261
b6f20af
f7bafd7
d7d6cb5
3b9530c
c0bdd87
40a2d32
c1f9177
c4e0d59
be696ef
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -87,6 +87,8 @@ type fakeLibusb struct { | |
mu sync.Mutex | ||
// fakeDevices has a map of devices and their descriptors. | ||
fakeDevices map[*libusbDevice]*fakeDevice | ||
// fakeSysDevices keeps the order of devices to be accessd by wrapSysDevice | ||
fakeSysDevices map[uintptr]*libusbDevice | ||
// ts has a map of all allocated transfers, indexed by the pointer of | ||
// underlying libusbTransfer. | ||
ts map[*libusbTransfer]*fakeTransfer | ||
|
@@ -108,6 +110,21 @@ func (f *fakeLibusb) getDevices(*libusbContext) ([]*libusbDevice, error) { | |
return ret, nil | ||
} | ||
|
||
func (f *fakeLibusb) wrapSysDevice(ctx *libusbContext, systemDeviceHandle uintptr) (*libusbDevHandle, error) { | ||
if _, ok := f.fakeSysDevices[systemDeviceHandle]; !ok { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I'd suggest: dev, ok := f.fakeSysDevices[...] |
||
return nil, fmt.Errorf("The passed file descriptor %d does not point to a valid device", systemDeviceHandle) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. lowercase "the" |
||
} | ||
h := newDevHandlePointer() | ||
f.mu.Lock() | ||
defer f.mu.Unlock() | ||
f.handles[h] = f.fakeSysDevices[systemDeviceHandle] | ||
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 { | ||
|
@@ -288,11 +305,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), | ||
fakeDevices: make(map[*libusbDevice]*fakeDevice), | ||
fakeSysDevices: 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 +319,11 @@ func newFakeLibusb() *fakeLibusb { | |
// The contents of these pointers is never accessed. | ||
fd := new(fakeDevice) | ||
*fd = d | ||
fl.fakeDevices[newDevicePointer()] = fd | ||
devPointer := newDevicePointer() | ||
fl.fakeDevices[devPointer] = fd | ||
if fd.sysDevPtr != 0 { // the sysDevPtr not being set in the fakeDevices list | ||
fl.fakeSysDevices[fd.sysDevPtr] = devPointer | ||
} | ||
} | ||
return fl | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -40,6 +40,7 @@ type libusbDevice C.libusb_device | |
type libusbDevHandle C.libusb_device_handle | ||
type libusbTransfer C.struct_libusb_transfer | ||
type libusbEndpoint C.struct_libusb_endpoint_descriptor | ||
type libusbLogLevel C.enum_libusb_log_level | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. drop (for later PR) |
||
|
||
func (ep libusbEndpoint) endpointDesc(dev *DeviceDesc) EndpointDesc { | ||
ei := EndpointDesc{ | ||
|
@@ -143,6 +144,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 +154,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 +172,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 libusb_options [4]C.struct_libusb_init_option // fixed to 4 - there are maximum 4 options | ||
zagrodzki marked this conversation as resolved.
Show resolved
Hide resolved
|
||
n_options := 0 | ||
zagrodzki marked this conversation as resolved.
Show resolved
Hide resolved
|
||
if impl.discovery == DisableDeviceDiscovery { | ||
libusb_options[n_options].option = C.LIBUSB_OPTION_NO_DEVICE_DISCOVERY | ||
n_options += 1 | ||
zagrodzki marked this conversation as resolved.
Show resolved
Hide resolved
|
||
} | ||
|
||
if err := fromErrNo(C.libusb_init_context(&ctx, &(libusb_options[0]), C.int(n_options))); err != nil { | ||
return nil, err | ||
} | ||
|
||
return (*libusbContext)(ctx), nil | ||
} | ||
|
||
|
@@ -217,6 +231,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 | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -168,6 +168,23 @@ func NewContext() *Context { | |
return newContextWithImpl(libusbImpl{}) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. would it make sense to replace this with "return ContextOptions{}.New()"? It's now becoming essentially a "context with defaults" anyway. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I agree - it also matches better the 'new' libusb initializer, since the int |
||
} | ||
|
||
type DeviceDiscovery int | ||
|
||
const ( | ||
EnableDeviceDiscovery = iota | ||
DisableDeviceDiscovery | ||
) | ||
|
||
type ContextOptions struct { | ||
DeviceDiscovery DeviceDiscovery | ||
zagrodzki marked this conversation as resolved.
Show resolved
Hide resolved
|
||
} | ||
|
||
func (o ContextOptions) New() *Context { | ||
return newContextWithImpl(libusbImpl{ | ||
discovery: o.DeviceDiscovery, | ||
zagrodzki marked this conversation as resolved.
Show resolved
Hide resolved
|
||
}) | ||
} | ||
|
||
// OpenDevices calls opener with each enumerated device. | ||
// If the opener returns true, the device is opened and a Device is returned if the operation succeeds. | ||
// Every Device returned (whether an error is also returned or not) must be closed. | ||
|
@@ -210,6 +227,27 @@ func (c *Context) OpenDevices(opener func(desc *DeviceDesc) bool) ([]*Device, er | |
return ret, reterr | ||
} | ||
|
||
func (c *Context) OpenDeviceWithFileDescriptor(fd uintptr) (*Device, error) { | ||
handle, err := c.libusb.wrapSysDevice(c.ctx, fd) | ||
if err != nil { | ||
return nil, fmt.Errorf("Error opening device from file descriptor: %d, %s", fd, err.Error()) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. remove the word "Error" from the message, it's superfluous in a context of an error message. And "opening device from the file descriptor" seems unnecessary too, because the caller know what they just called. Better just return the error from libusb wrapper as is. |
||
} | ||
|
||
dev := c.libusb.getDevice(handle) | ||
|
||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. drop this empty line There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. still open There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think it's already in place There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I meant remove the empty line (i.e. remove line 262 between the dev assignment and desc assignment) |
||
desc, err := c.libusb.getDeviceDesc(dev) | ||
if err != nil { | ||
return nil, err | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. if you want to annotate an error somewhere, here it would be appropriate perhaps: |
||
} | ||
|
||
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. | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -94,3 +94,45 @@ 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 dev == nil { | ||
t.Errorf("OpenDeviceWithFileDescriptor(%d): device == nil for a valid device", d.sysDevPtr) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Fatalf. Also add a check to see if the obtained device is the one you asked for (does VID/PID match). There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. these two above (line 112 and 115) still need to be Fatalf. With Fatal(f), the test is immediately interrupted. With Error(f), the test continues, but will report a failure at the end with all recorded errors. The principle is "return as much useful test results from a single run as possible". Hence:
For future reference: if there's enough distinct check flows that each check forms a chain of dependencies, it may make sense to split individual checks into a subtest (t.Run). |
||
} | ||
if err != nil { | ||
t.Errorf("OpenDeviceWithFileDescriptor(%d): err != nil for a valid device: %v", d.sysDevPtr, err) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Fatalf, and move before the |
||
} | ||
} | ||
|
||
} | ||
|
||
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 | ||
} { | ||
dev, err := ctx.OpenDeviceWithFileDescriptor(sysDevPtr) | ||
if err == nil { | ||
t.Errorf("OpenDeviceWithFileDescriptor(%d): got nil error for invalid device", sysDevPtr) | ||
} | ||
if dev != nil { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. drop this check - it's enough to check that error was non-nil. The return value is undefined, it may be non-nil, but it shouldn't be used either way. |
||
t.Errorf("OpenDeviceWithFileDescriptor(%d): got non-nil device for invalid device", sysDevPtr) | ||
} | ||
} | ||
|
||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
actually, perhaps just "sysDevices" - this is already inside the fakeLibusb, so "fake" stutters a bit. "fakeDevices" is ok because the type is called "fakeDevice", although perhaps it should also be renamed "devices"...
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You're the boss, but I'd argue that either both should be changed or neither - it's much more readable to see these two named in the same way. You immediately go "ah, right, these are just some mappings of the fakes"
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'd go with "both" then :)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I understand this as "change both" :)
Will update