From cc135fe616b62d70c34f0ce55649add16a676b59 Mon Sep 17 00:00:00 2001 From: Sebastian Zagrodzki Date: Thu, 23 Feb 2017 10:25:02 +0100 Subject: [PATCH 01/20] move all libusb wrappers to libusb.go. --- usb/config.go | 75 ---------------------- usb/descriptor.go | 34 ---------- usb/device.go | 107 ++++++++------------------------ usb/endpoint.go | 4 +- usb/error.go | 8 +++ usb/transfer.go | 65 +++++-------------- usb/transfer_fakelibusb_test.go | 12 ++-- usb/transfer_test.go | 12 ++-- usb/usb.go | 91 ++++++++------------------- 9 files changed, 88 insertions(+), 320 deletions(-) diff --git a/usb/config.go b/usb/config.go index b6ab0e1..3205ee4 100644 --- a/usb/config.go +++ b/usb/config.go @@ -15,13 +15,8 @@ package usb -// #include -import "C" - import ( "fmt" - "reflect" - "unsafe" ) type EndpointInfo struct { @@ -87,73 +82,3 @@ type ConfigInfo struct { func (c ConfigInfo) String() string { return fmt.Sprintf("Config %02x", c.Config) } - -func newConfig(dev *C.libusb_device, cfg *C.struct_libusb_config_descriptor) ConfigInfo { - c := ConfigInfo{ - Config: uint8(cfg.bConfigurationValue), - Attributes: uint8(cfg.bmAttributes), - MaxPower: uint8(cfg.MaxPower), - } - - var ifaces []C.struct_libusb_interface - *(*reflect.SliceHeader)(unsafe.Pointer(&ifaces)) = reflect.SliceHeader{ - Data: uintptr(unsafe.Pointer(cfg._interface)), - Len: int(cfg.bNumInterfaces), - Cap: int(cfg.bNumInterfaces), - } - c.Interfaces = make([]InterfaceInfo, 0, len(ifaces)) - for _, iface := range ifaces { - if iface.num_altsetting == 0 { - continue - } - - var alts []C.struct_libusb_interface_descriptor - *(*reflect.SliceHeader)(unsafe.Pointer(&alts)) = reflect.SliceHeader{ - Data: uintptr(unsafe.Pointer(iface.altsetting)), - Len: int(iface.num_altsetting), - Cap: int(iface.num_altsetting), - } - descs := make([]InterfaceSetup, 0, len(alts)) - for _, alt := range alts { - i := InterfaceSetup{ - Number: uint8(alt.bInterfaceNumber), - Alternate: uint8(alt.bAlternateSetting), - IfClass: uint8(alt.bInterfaceClass), - IfSubClass: uint8(alt.bInterfaceSubClass), - IfProtocol: uint8(alt.bInterfaceProtocol), - } - var ends []C.struct_libusb_endpoint_descriptor - *(*reflect.SliceHeader)(unsafe.Pointer(&ends)) = reflect.SliceHeader{ - Data: uintptr(unsafe.Pointer(alt.endpoint)), - Len: int(alt.bNumEndpoints), - Cap: int(alt.bNumEndpoints), - } - i.Endpoints = make([]EndpointInfo, 0, len(ends)) - for _, end := range ends { - ei := EndpointInfo{ - Address: uint8(end.bEndpointAddress), - Attributes: uint8(end.bmAttributes), - MaxPacketSize: uint16(end.wMaxPacketSize), - PollInterval: uint8(end.bInterval), - RefreshRate: uint8(end.bRefresh), - SynchAddress: uint8(end.bSynchAddress), - } - if ei.TransferType() == TRANSFER_TYPE_ISOCHRONOUS { - // bits 0-10 identify the packet size, bits 11-12 are the number of additional transactions per microframe. - // Don't use libusb_get_max_iso_packet_size, as it has a bug where it returns the same value - // regardless of alternative setting used, where different alternative settings might define different - // max packet sizes. - // See http://libusb.org/ticket/77 for more background. - ei.MaxIsoPacket = uint32(end.wMaxPacketSize) & 0x07ff * (uint32(end.wMaxPacketSize)>>11&3 + 1) - } - i.Endpoints = append(i.Endpoints, ei) - } - descs = append(descs, i) - } - c.Interfaces = append(c.Interfaces, InterfaceInfo{ - Number: descs[0].Number, - Setups: descs, - }) - } - return c -} diff --git a/usb/descriptor.go b/usb/descriptor.go index 79e5036..76a9b77 100644 --- a/usb/descriptor.go +++ b/usb/descriptor.go @@ -15,9 +15,6 @@ package usb -// #include -import "C" - type Descriptor struct { // Bus information Bus uint8 // The bus on which the device was detected @@ -39,34 +36,3 @@ type Descriptor struct { // Configuration information Configs []ConfigInfo } - -func newDescriptor(dev *C.libusb_device) (*Descriptor, error) { - var desc C.struct_libusb_device_descriptor - if errno := C.libusb_get_device_descriptor(dev, &desc); errno < 0 { - return nil, usbError(errno) - } - - // Enumerate configurations - var cfgs []ConfigInfo - for i := 0; i < int(desc.bNumConfigurations); i++ { - var cfg *C.struct_libusb_config_descriptor - if errno := C.libusb_get_config_descriptor(dev, C.uint8_t(i), &cfg); errno < 0 { - return nil, usbError(errno) - } - cfgs = append(cfgs, newConfig(dev, cfg)) - C.libusb_free_config_descriptor(cfg) - } - - return &Descriptor{ - Bus: uint8(C.libusb_get_bus_number(dev)), - Address: uint8(C.libusb_get_device_address(dev)), - Spec: BCD(desc.bcdUSB), - Device: BCD(desc.bcdDevice), - Vendor: ID(desc.idVendor), - Product: ID(desc.idProduct), - Class: uint8(desc.bDeviceClass), - SubClass: uint8(desc.bDeviceSubClass), - Protocol: uint8(desc.bDeviceProtocol), - Configs: cfgs, - }, nil -} diff --git a/usb/device.go b/usb/device.go index 86a0a31..72e9699 100644 --- a/usb/device.go +++ b/usb/device.go @@ -15,15 +15,10 @@ package usb -// #include -import "C" - import ( "fmt" - "reflect" "sync" "time" - "unsafe" ) var DefaultReadTimeout = 1 * time.Second @@ -31,7 +26,7 @@ var DefaultWriteTimeout = 1 * time.Second var DefaultControlTimeout = 250 * time.Millisecond //5 * time.Second type Device struct { - handle *C.libusb_device_handle + handle *libusbDevHandle // Embed the device information for easy access *Descriptor @@ -46,7 +41,7 @@ type Device struct { claimed map[uint8]int } -func newDevice(handle *C.libusb_device_handle, desc *Descriptor) *Device { +func newDevice(handle *libusbDevHandle, desc *Descriptor) *Device { ifaces := 0 d := &Device{ handle: handle, @@ -62,48 +57,24 @@ func newDevice(handle *C.libusb_device_handle, desc *Descriptor) *Device { } func (d *Device) Reset() error { - if errno := C.libusb_reset_device(d.handle); errno != 0 { - return usbError(errno) - } - return nil + return libusb.reset(d.handle) } func (d *Device) Control(rType, request uint8, val, idx uint16, data []byte) (int, error) { - //log.Printf("control xfer: %d:%d/%d:%d %x", idx, rType, request, val, string(data)) - dataSlice := (*reflect.SliceHeader)(unsafe.Pointer(&data)) - n := C.libusb_control_transfer( - d.handle, - C.uint8_t(rType), - C.uint8_t(request), - C.uint16_t(val), - C.uint16_t(idx), - (*C.uchar)(unsafe.Pointer(dataSlice.Data)), - C.uint16_t(len(data)), - C.uint(d.ControlTimeout/time.Millisecond)) - if n < 0 { - return int(n), usbError(n) - } - return int(n), nil + return libusb.control(d.handle, d.ControlTimeout, rType, request, val, idx, data) } // ActiveConfig returns the config id (not the index) of the active configuration. // This corresponds to the ConfigInfo.Config field. func (d *Device) ActiveConfig() (uint8, error) { - var cfg C.int - if errno := C.libusb_get_configuration(d.handle, &cfg); errno < 0 { - return 0, usbError(errno) - } - return uint8(cfg), nil + return libusb.getConfig(d.handle) } // SetConfig attempts to change the active configuration. // The cfg provided is the config id (not the index) of the configuration to set, // which corresponds to the ConfigInfo.Config field. func (d *Device) SetConfig(cfg uint8) error { - if errno := C.libusb_set_configuration(d.handle, C.int(cfg)); errno < 0 { - return usbError(errno) - } - return nil + return libusb.setConfig(d.handle, cfg) } // Close the device. @@ -114,9 +85,9 @@ func (d *Device) Close() error { d.lock.Lock() defer d.lock.Unlock() for iface := range d.claimed { - C.libusb_release_interface(d.handle, C.int(iface)) + libusb.release(d.handle, iface) } - C.libusb_close(d.handle) + libusb.close(d.handle) d.handle = nil return nil } @@ -162,19 +133,19 @@ func (d *Device) OpenEndpoint(conf, iface, setup, epoint uint8) (Endpoint, error found: // Set the configuration - var activeConf C.int - if errno := C.libusb_get_configuration(d.handle, &activeConf); errno < 0 { - return nil, fmt.Errorf("usb: getcfg: %s", usbError(errno)) + activeConf, err := libusb.getConfig(d.handle) + if err != nil { + return nil, fmt.Errorf("usb: getcfg: %s", err) } - if int(activeConf) != int(conf) { - if errno := C.libusb_set_configuration(d.handle, C.int(conf)); errno < 0 { - return nil, fmt.Errorf("usb: setcfg: %s", usbError(errno)) + if activeConf != conf { + if err := libusb.setConfig(d.handle, conf); err != nil { + return nil, fmt.Errorf("usb: setcfg: %s", err) } } // Claim the interface - if errno := C.libusb_claim_interface(d.handle, C.int(iface)); errno < 0 { - return nil, fmt.Errorf("usb: claim: %s", usbError(errno)) + if err := libusb.claim(d.handle, iface); err != nil { + return nil, fmt.Errorf("usb: claim: %s", err) } // Increment the claim count @@ -184,9 +155,8 @@ found: // Choose the alternate if setAlternate { - if errno := C.libusb_set_interface_alt_setting(d.handle, C.int(iface), C.int(setup)); errno < 0 { - debug.Printf("altsetting error: %s", usbError(errno)) - return nil, fmt.Errorf("usb: setalt: %s", usbError(errno)) + if err := libusb.setAlt(d.handle, iface, setup); err != nil { + return nil, fmt.Errorf("usb: setalt: %s", err) } } @@ -194,26 +164,7 @@ found: } func (d *Device) GetStringDescriptor(desc_index int) (string, error) { - - // allocate 200-byte array limited the length of string descriptor - goBuffer := make([]byte, 200) - - // get string descriptor from libusb. if errno < 0 then there are any errors. - // if errno >= 0; it is a length of result string descriptor - errno := C.libusb_get_string_descriptor_ascii( - d.handle, - C.uint8_t(desc_index), - (*C.uchar)(unsafe.Pointer(&goBuffer[0])), - 200) - - // if any errors occur - if errno < 0 { - return "", fmt.Errorf("usb: getstr: %s", usbError(errno)) - } - // convert slice of byte to string with limited length from errno - stringDescriptor := string(goBuffer[:errno]) - - return stringDescriptor, nil + return libusb.getStringDesc(d.handle, desc_index) } // SetAutoDetach enables/disables libusb's automatic kernel driver detachment. @@ -221,20 +172,12 @@ func (d *Device) GetStringDescriptor(desc_index int) (string, error) { // on the interface and reattach it when releasing the interface. // Automatic kernel driver detachment is disabled on newly opened device handles by default. func (d *Device) SetAutoDetach(autodetach bool) error { - autodetachInt := 0 - if autodetach { + var autodetachInt int + switch autodetach { + case true: autodetachInt = 1 + case false: + autodetachInt = 0 } - - err := C.libusb_set_auto_detach_kernel_driver( - d.handle, - C.int(autodetachInt), - ) - - // TODO LIBUSB_ERROR_NOT_SUPPORTED (-12) handling - // if any errors occur - if err != C.int(SUCCESS) { - return fmt.Errorf("usb: setautodetach: %s", usbError(err)) - } - return nil + return libusb.setAutoDetach(d.handle, autodetachInt) } diff --git a/usb/endpoint.go b/usb/endpoint.go index a33dd15..f942709 100644 --- a/usb/endpoint.go +++ b/usb/endpoint.go @@ -34,7 +34,7 @@ type transferIntf interface { } type endpoint struct { - h *deviceHandle + h *libusbDevHandle InterfaceSetup EndpointInfo @@ -92,7 +92,7 @@ func (e *endpoint) transfer(buf []byte, timeout time.Duration) (int, error) { func newEndpoint(d *Device) *endpoint { ep := &endpoint{ - h: (*deviceHandle)(d.handle), + h: d.handle, readTimeout: d.ReadTimeout, writeTimeout: d.WriteTimeout, } diff --git a/usb/error.go b/usb/error.go index 73115ee..892c54d 100644 --- a/usb/error.go +++ b/usb/error.go @@ -28,6 +28,14 @@ func (e usbError) Error() string { return fmt.Sprintf("libusb: %s [code %d]", usbErrorString[e], int(e)) } +func fromUSBError(errno C.int) error { + err := usbError(errno) + if err == SUCCESS { + return nil + } + return err +} + const ( SUCCESS usbError = C.LIBUSB_SUCCESS ERROR_IO usbError = C.LIBUSB_ERROR_IO diff --git a/usb/transfer.go b/usb/transfer.go index 9784d20..b82902d 100644 --- a/usb/transfer.go +++ b/usb/transfer.go @@ -14,14 +14,6 @@ package usb -/* -#include - -int compact_iso_data(struct libusb_transfer *xfer, unsigned char *status); -int submit(struct libusb_transfer *xfer); -*/ -import "C" - import ( "errors" "fmt" @@ -31,32 +23,8 @@ import ( "unsafe" ) -// libusb hooks used as injection points for tests. -var ( - cCancel = func(t *libusbTransfer) usbError { - return usbError(C.libusb_cancel_transfer((*C.struct_libusb_transfer)(t))) - } - cSubmit = func(t *libusbTransfer) usbError { - return usbError(C.submit((*C.struct_libusb_transfer)(t))) - } -) - -// because of a limitation of cgo, tests cannot import C. -type deviceHandle C.libusb_device_handle -type libusbTransfer C.struct_libusb_transfer -type libusbIso C.struct_libusb_iso_packet_descriptor - -// also for tests -var ( - libusbIsoSize = C.sizeof_struct_libusb_iso_packet_descriptor - libusbIsoOffset = unsafe.Offsetof(C.struct_libusb_transfer{}.iso_packet_desc) -) - -//export xfer_callback -func xfer_callback(cptr unsafe.Pointer) { - ch := *(*chan struct{})(cptr) - close(ch) -} +// #include +import "C" type usbTransfer struct { // mu protects the transfer state. @@ -84,7 +52,7 @@ func (t *usbTransfer) submit() error { } t.done = make(chan struct{}) t.xfer.user_data = (unsafe.Pointer)(&t.done) - if err := cSubmit(t.xfer); err != SUCCESS { + if err := libusb.submit(t.xfer); err != nil { return err } t.submitted = true @@ -111,10 +79,9 @@ func (t *usbTransfer) wait() (n int, err error) { var status TransferStatus switch TransferType(t.xfer._type) { case TRANSFER_TYPE_ISOCHRONOUS: - n = int(C.compact_iso_data((*C.struct_libusb_transfer)(t.xfer), (*C.uchar)(unsafe.Pointer(&status)))) + n, status = libusb.compactIsoData(t.xfer) default: - n = int(t.xfer.actual_length) - status = TransferStatus(t.xfer.status) + n, status = int(t.xfer.actual_length), TransferStatus(t.xfer.status) } if status != LIBUSB_TRANSFER_COMPLETED { return n, status @@ -130,15 +97,12 @@ func (t *usbTransfer) cancel() error { if !t.submitted { return nil } - err := usbError(cCancel(t.xfer)) + err := libusb.cancel(t.xfer) if err == ERROR_NOT_FOUND { // transfer already completed - err = SUCCESS + return nil } - if err != SUCCESS { - return err - } - return nil + return err } // free releases the memory allocated for the transfer. @@ -150,7 +114,7 @@ func (t *usbTransfer) free() error { if t.submitted { return errors.New("free() cannot be called on a submitted transfer until wait() returns") } - C.libusb_free_transfer((*C.struct_libusb_transfer)(t.xfer)) + libusb.free(t.xfer) t.xfer = nil t.buf = nil t.done = nil @@ -159,7 +123,7 @@ func (t *usbTransfer) free() error { // newUSBTransfer allocates a new transfer structure for communication with a // given device/endpoint, with buf as the underlying transfer buffer. -func newUSBTransfer(dev *deviceHandle, ei EndpointInfo, buf []byte, timeout time.Duration) (*usbTransfer, error) { +func newUSBTransfer(dev *libusbDevHandle, ei EndpointInfo, buf []byte, timeout time.Duration) (*usbTransfer, error) { var isoPackets int tt := ei.TransferType() if tt == TRANSFER_TYPE_ISOCHRONOUS { @@ -169,9 +133,9 @@ func newUSBTransfer(dev *deviceHandle, ei EndpointInfo, buf []byte, timeout time } } - xfer := C.libusb_alloc_transfer(C.int(isoPackets)) - if xfer == nil { - return nil, fmt.Errorf("libusb_alloc_transfer(%d) failed", isoPackets) + xfer, err := libusb.alloc(isoPackets) + if err != nil { + return nil, err } xfer.dev_handle = (*C.struct_libusb_device_handle)(dev) @@ -183,8 +147,7 @@ func newUSBTransfer(dev *deviceHandle, ei EndpointInfo, buf []byte, timeout time xfer.length = C.int(len(buf)) if tt == TRANSFER_TYPE_ISOCHRONOUS { - xfer.num_iso_packets = C.int(isoPackets) - C.libusb_set_iso_packet_lengths(xfer, C.uint(ei.MaxIsoPacket)) + libusb.setIsoPacketLengths(xfer, ei.MaxIsoPacket) } t := &usbTransfer{ diff --git a/usb/transfer_fakelibusb_test.go b/usb/transfer_fakelibusb_test.go index 9b59d1f..319853f 100644 --- a/usb/transfer_fakelibusb_test.go +++ b/usb/transfer_fakelibusb_test.go @@ -19,19 +19,20 @@ import "sync" type fakeLibusb struct { sync.Mutex ts map[*libusbTransfer]chan struct{} + libusbIntf } -func (f *fakeLibusb) submit(t *libusbTransfer) usbError { +func (f *fakeLibusb) submit(t *libusbTransfer) error { f.Lock() defer f.Unlock() if f.ts[t] == nil { f.ts[t] = make(chan struct{}) } close(f.ts[t]) - return SUCCESS + return nil } -func (f *fakeLibusb) cancel(t *libusbTransfer) usbError { return SUCCESS } +func (f *fakeLibusb) cancel(t *libusbTransfer) error { return nil } func (f *fakeLibusb) waitForSubmit(t *usbTransfer) { f.Lock() @@ -52,5 +53,8 @@ func (f *fakeLibusb) runCallback(t *usbTransfer, cb func(*usbTransfer)) { } func newFakeLibusb() *fakeLibusb { - return &fakeLibusb{ts: make(map[*libusbTransfer]chan struct{})} + return &fakeLibusb{ + ts: make(map[*libusbTransfer]chan struct{}), + libusbIntf: libusbImpl{}, + } } diff --git a/usb/transfer_test.go b/usb/transfer_test.go index 21a99b3..1185583 100644 --- a/usb/transfer_test.go +++ b/usb/transfer_test.go @@ -87,12 +87,10 @@ func TestNewTransfer(t *testing.T) { } func TestTransferProtocol(t *testing.T) { - origSubmit, origCancel := cSubmit, cCancel - defer func() { cSubmit, cCancel = origSubmit, origCancel }() + defer func(i libusbIntf) { libusb = i }(libusb) f := newFakeLibusb() - cSubmit = f.submit - cCancel = f.cancel + libusb = f xfers := make([]*usbTransfer, 2) var err error @@ -167,12 +165,10 @@ func TestTransferProtocol(t *testing.T) { } func TestIsoPackets(t *testing.T) { - origSubmit, origCancel := cSubmit, cCancel - defer func() { cSubmit, cCancel = origSubmit, origCancel }() + defer func(i libusbIntf) { libusb = i }(libusb) f := newFakeLibusb() - cSubmit = f.submit - cCancel = f.cancel + libusb = f xfer, err := newUSBTransfer(nil, EndpointInfo{ Address: 0x82, diff --git a/usb/usb.go b/usb/usb.go index 47784fb..d22ad59 100644 --- a/usb/usb.go +++ b/usb/usb.go @@ -20,50 +20,26 @@ package usb // #include import "C" -import ( - "log" - "reflect" - "unsafe" -) - type Context struct { - ctx *C.libusb_context + ctx *libusbContext done chan struct{} } func (c *Context) Debug(level int) { - C.libusb_set_debug(c.ctx, C.int(level)) + libusb.setDebug(c.ctx, level) } func NewContext() *Context { - c := &Context{ + c, err := libusb.init() + if err != nil { + panic(err) + } + ctx := &Context{ + ctx: c, done: make(chan struct{}), } - - if errno := C.libusb_init(&c.ctx); errno != 0 { - panic(usbError(errno)) - } - - go func() { - tv := C.struct_timeval{ - tv_sec: 0, - tv_usec: 100000, - } - for { - select { - case <-c.done: - return - default: - } - if errno := C.libusb_handle_events_timeout_completed(c.ctx, &tv, nil); errno < 0 { - log.Printf("handle_events: error: %s", usbError(errno)) - continue - } - //log.Printf("handle_events returned") - } - }() - - return c + go libusb.handleEvents(ctx.ctx, ctx.done) + return ctx } // ListDevices calls each with each enumerated device. @@ -72,36 +48,30 @@ func NewContext() *Context { // If there are any errors enumerating the devices, // the final one is returned along with any successfully opened devices. func (c *Context) ListDevices(each func(desc *Descriptor) bool) ([]*Device, error) { - var list **C.libusb_device - cnt := C.libusb_get_device_list(c.ctx, &list) - if cnt < 0 { - return nil, usbError(cnt) - } - defer C.libusb_free_device_list(list, 1) - - var slice []*C.libusb_device - *(*reflect.SliceHeader)(unsafe.Pointer(&slice)) = reflect.SliceHeader{ - Data: uintptr(unsafe.Pointer(list)), - Len: int(cnt), - Cap: int(cnt), + list, err := libusb.getDevices(c.ctx) + if err != nil { + return nil, err } var reterr error var ret []*Device - for _, dev := range slice { - desc, err := newDescriptor(dev) + for _, dev := range list { + desc, err := libusb.getDeviceDesc(dev) if err != nil { + libusb.dereference(dev) reterr = err continue } if each(desc) { - var handle *C.libusb_device_handle - if errno := C.libusb_open(dev, &handle); errno != 0 { - reterr = usbError(errno) + handle, err := libusb.open(dev) + if err != nil { + reterr = err continue } ret = append(ret, newDevice(handle, desc)) + } else { + libusb.dereference(dev) } } return ret, reterr @@ -110,21 +80,14 @@ func (c *Context) ListDevices(each func(desc *Descriptor) bool) ([]*Device, erro // OpenDeviceWithVidPid opens Device from specific VendorId and ProductId. // If there are any errors, it'll returns at second value. func (c *Context) OpenDeviceWithVidPid(vid, pid int) (*Device, error) { - - handle := C.libusb_open_device_with_vid_pid(c.ctx, (C.uint16_t)(vid), (C.uint16_t)(pid)) - if handle == nil { - return nil, ERROR_NOT_FOUND + dev, handle, err := libusb.openVIDPID(c.ctx, vid, pid) + if err != nil { + return nil, err } - - dev := C.libusb_get_device(handle) - if dev == nil { - return nil, ERROR_NO_DEVICE - } - - desc, err := newDescriptor(dev) - + desc, err := libusb.getDeviceDesc(dev) // return an error from nil-handle and nil-device if err != nil { + libusb.dereference(dev) return nil, err } @@ -135,7 +98,7 @@ func (c *Context) OpenDeviceWithVidPid(vid, pid int) (*Device, error) { func (c *Context) Close() error { close(c.done) if c.ctx != nil { - C.libusb_exit(c.ctx) + libusb.exit(c.ctx) } c.ctx = nil return nil From 82184ea552e42bcadad7456cc6dc84f0c9d5b234 Mon Sep 17 00:00:00 2001 From: Sebastian Zagrodzki Date: Thu, 23 Feb 2017 10:29:24 +0100 Subject: [PATCH 02/20] make libusbDevHandle part of alloc() --- usb/transfer.go | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/usb/transfer.go b/usb/transfer.go index b82902d..81f3da7 100644 --- a/usb/transfer.go +++ b/usb/transfer.go @@ -133,12 +133,11 @@ func newUSBTransfer(dev *libusbDevHandle, ei EndpointInfo, buf []byte, timeout t } } - xfer, err := libusb.alloc(isoPackets) + xfer, err := libusb.alloc(dev, isoPackets) if err != nil { return nil, err } - xfer.dev_handle = (*C.struct_libusb_device_handle)(dev) xfer.timeout = C.uint(timeout / time.Millisecond) xfer.endpoint = C.uchar(ei.Address) xfer._type = C.uchar(tt) From 74b932b02236405b6ff4f3ca8bf7b231bd32e684 Mon Sep 17 00:00:00 2001 From: Sebastian Zagrodzki Date: Thu, 23 Feb 2017 10:38:25 +0100 Subject: [PATCH 03/20] Duh, missing libusb.go... --- usb/libusb.go | 373 ++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 373 insertions(+) create mode 100644 usb/libusb.go diff --git a/usb/libusb.go b/usb/libusb.go new file mode 100644 index 0000000..ec08620 --- /dev/null +++ b/usb/libusb.go @@ -0,0 +1,373 @@ +// Copyright 2017 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 usb + +import ( + "fmt" + "log" + "reflect" + "time" + "unsafe" +) + +/* +#include + +int compact_iso_data(struct libusb_transfer *xfer, unsigned char *status); +int submit(struct libusb_transfer *xfer); +*/ +import "C" + +type libusbContext C.libusb_context +type libusbDevice C.libusb_device +type libusbDevHandle C.libusb_device_handle +type libusbTransfer C.struct_libusb_transfer +type libusbIso C.struct_libusb_iso_packet_descriptor + +// 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. +// +// All functions here should operate on types defined on C.libusb* data types, +// and occasionally on convenience data types (like TransferType or Descriptor). +type libusbIntf interface { + // context + init() (*libusbContext, error) + handleEvents(*libusbContext, <-chan struct{}) + getDevices(*libusbContext) ([]*libusbDevice, error) + exit(*libusbContext) + setDebug(*libusbContext, int) + openVIDPID(*libusbContext, int, int) (*libusbDevice, *libusbDevHandle, error) + + // device + dereference(*libusbDevice) + getDeviceDesc(*libusbDevice) (*Descriptor, error) + open(*libusbDevice) (*libusbDevHandle, error) + + close(*libusbDevHandle) + reset(*libusbDevHandle) error + control(*libusbDevHandle, time.Duration, uint8, uint8, uint16, uint16, []byte) (int, error) + getConfig(*libusbDevHandle) (uint8, error) + setConfig(*libusbDevHandle, uint8) error + getStringDesc(*libusbDevHandle, int) (string, error) + setAutoDetach(*libusbDevHandle, int) error + + // interface + claim(*libusbDevHandle, uint8) error + release(*libusbDevHandle, uint8) + setAlt(*libusbDevHandle, uint8, uint8) error + + // endpoint + alloc(*libusbDevHandle, int) (*libusbTransfer, error) + cancel(*libusbTransfer) error + submit(*libusbTransfer) error + free(*libusbTransfer) + setIsoPacketLengths(*libusbTransfer, uint32) + compactIsoData(*libusbTransfer) (int, TransferStatus) +} + +// libusbImpl is an implementation of libusbIntf using real CGo-wrapped libusb. +type libusbImpl struct{} + +func (libusbImpl) init() (*libusbContext, error) { + var ctx *C.libusb_context + if err := fromUSBError(C.libusb_init(&ctx)); err != nil { + return nil, err + } + return (*libusbContext)(ctx), nil +} + +func (libusbImpl) handleEvents(c *libusbContext, done <-chan struct{}) { + tv := C.struct_timeval{tv_sec: 10} + for { + select { + case <-done: + return + default: + } + if errno := C.libusb_handle_events_timeout_completed((*C.libusb_context)(c), &tv, nil); errno < 0 { + log.Printf("handle_events: error: %s", usbError(errno)) + } + } +} + +func (libusbImpl) getDevices(ctx *libusbContext) ([]*libusbDevice, error) { + var list **C.libusb_device + cnt := C.libusb_get_device_list((*C.libusb_context)(ctx), &list) + if cnt < 0 { + return nil, fromUSBError(C.int(cnt)) + } + var devs []*C.libusb_device + *(*reflect.SliceHeader)(unsafe.Pointer(&devs)) = reflect.SliceHeader{ + Data: uintptr(unsafe.Pointer(list)), + Len: int(cnt), + Cap: int(cnt), + } + var ret []*libusbDevice + for _, d := range devs { + ret = append(ret, (*libusbDevice)(d)) + } + // devices will be dereferenced later, during close. + C.libusb_free_device_list(list, 0) + return ret, nil +} + +func (libusbImpl) exit(c *libusbContext) { + C.libusb_exit((*C.libusb_context)(c)) +} + +func (libusbImpl) setDebug(c *libusbContext, lvl int) { + C.libusb_set_debug((*C.libusb_context)(c), C.int(lvl)) +} + +func (libusbImpl) openVIDPID(ctx *libusbContext, vid, pid int) (*libusbDevice, *libusbDevHandle, error) { + h := C.libusb_open_device_with_vid_pid((*C.libusb_context)(ctx), (C.uint16_t)(vid), (C.uint16_t)(pid)) + if h == nil { + return nil, nil, ERROR_NOT_FOUND + } + dev := C.libusb_get_device(h) + if dev == nil { + return nil, nil, ERROR_NO_DEVICE + } + C.libusb_ref_device(dev) + return (*libusbDevice)(dev), (*libusbDevHandle)(h), nil +} + +func (libusbImpl) getDeviceDesc(d *libusbDevice) (*Descriptor, error) { + var desc C.struct_libusb_device_descriptor + if err := fromUSBError(C.libusb_get_device_descriptor((*C.libusb_device)(d), &desc)); err != nil { + return nil, err + } + // Enumerate configurations + var cfgs []ConfigInfo + for i := 0; i < int(desc.bNumConfigurations); i++ { + var cfg *C.struct_libusb_config_descriptor + if err := fromUSBError(C.libusb_get_config_descriptor((*C.libusb_device)(d), C.uint8_t(i), &cfg)); err != nil { + return nil, err + } + c := ConfigInfo{ + Config: uint8(cfg.bConfigurationValue), + Attributes: uint8(cfg.bmAttributes), + MaxPower: uint8(cfg.MaxPower), + } + + var ifaces []C.struct_libusb_interface + *(*reflect.SliceHeader)(unsafe.Pointer(&ifaces)) = reflect.SliceHeader{ + Data: uintptr(unsafe.Pointer(cfg._interface)), + Len: int(cfg.bNumInterfaces), + Cap: int(cfg.bNumInterfaces), + } + c.Interfaces = make([]InterfaceInfo, 0, len(ifaces)) + for _, iface := range ifaces { + if iface.num_altsetting == 0 { + continue + } + + var alts []C.struct_libusb_interface_descriptor + *(*reflect.SliceHeader)(unsafe.Pointer(&alts)) = reflect.SliceHeader{ + Data: uintptr(unsafe.Pointer(iface.altsetting)), + Len: int(iface.num_altsetting), + Cap: int(iface.num_altsetting), + } + descs := make([]InterfaceSetup, 0, len(alts)) + for _, alt := range alts { + i := InterfaceSetup{ + Number: uint8(alt.bInterfaceNumber), + Alternate: uint8(alt.bAlternateSetting), + IfClass: uint8(alt.bInterfaceClass), + IfSubClass: uint8(alt.bInterfaceSubClass), + IfProtocol: uint8(alt.bInterfaceProtocol), + } + var ends []C.struct_libusb_endpoint_descriptor + *(*reflect.SliceHeader)(unsafe.Pointer(&ends)) = reflect.SliceHeader{ + Data: uintptr(unsafe.Pointer(alt.endpoint)), + Len: int(alt.bNumEndpoints), + Cap: int(alt.bNumEndpoints), + } + i.Endpoints = make([]EndpointInfo, 0, len(ends)) + for _, end := range ends { + ei := EndpointInfo{ + Address: uint8(end.bEndpointAddress), + Attributes: uint8(end.bmAttributes), + MaxPacketSize: uint16(end.wMaxPacketSize), + PollInterval: uint8(end.bInterval), + RefreshRate: uint8(end.bRefresh), + SynchAddress: uint8(end.bSynchAddress), + } + if ei.TransferType() == TRANSFER_TYPE_ISOCHRONOUS { + // bits 0-10 identify the packet size, bits 11-12 are the number of additional transactions per microframe. + // Don't use libusb_get_max_iso_packet_size, as it has a bug where it returns the same value + // regardless of alternative setting used, where different alternative settings might define different + // max packet sizes. + // See http://libusb.org/ticket/77 for more background. + ei.MaxIsoPacket = uint32(end.wMaxPacketSize) & 0x07ff * (uint32(end.wMaxPacketSize)>>11&3 + 1) + } + i.Endpoints = append(i.Endpoints, ei) + } + descs = append(descs, i) + } + c.Interfaces = append(c.Interfaces, InterfaceInfo{ + Number: descs[0].Number, + Setups: descs, + }) + } + C.libusb_free_config_descriptor(cfg) + cfgs = append(cfgs, c) + } + + return &Descriptor{ + Bus: uint8(C.libusb_get_bus_number((*C.libusb_device)(d))), + Address: uint8(C.libusb_get_device_address((*C.libusb_device)(d))), + Spec: BCD(desc.bcdUSB), + Device: BCD(desc.bcdDevice), + Vendor: ID(desc.idVendor), + Product: ID(desc.idProduct), + Class: uint8(desc.bDeviceClass), + SubClass: uint8(desc.bDeviceSubClass), + Protocol: uint8(desc.bDeviceProtocol), + Configs: cfgs, + }, nil +} + +func (libusbImpl) dereference(d *libusbDevice) { + C.libusb_unref_device((*C.libusb_device)(d)) +} + +func (libusbImpl) open(d *libusbDevice) (*libusbDevHandle, error) { + var handle *C.libusb_device_handle + if err := fromUSBError(C.libusb_open((*C.libusb_device)(d), &handle)); err != nil { + return nil, err + } + return (*libusbDevHandle)(handle), nil +} + +func (libusbImpl) close(d *libusbDevHandle) { + C.libusb_close((*C.libusb_device_handle)(d)) +} + +func (libusbImpl) reset(d *libusbDevHandle) error { + return fromUSBError(C.libusb_reset_device((*C.libusb_device_handle)(d))) +} + +func (libusbImpl) control(d *libusbDevHandle, timeout time.Duration, rType, request uint8, val, idx uint16, data []byte) (int, error) { + dataSlice := (*reflect.SliceHeader)(unsafe.Pointer(&data)) + n := C.libusb_control_transfer( + (*C.libusb_device_handle)(d), + C.uint8_t(rType), + C.uint8_t(request), + C.uint16_t(val), + C.uint16_t(idx), + (*C.uchar)(unsafe.Pointer(dataSlice.Data)), + C.uint16_t(len(data)), + C.uint(timeout/time.Millisecond)) + if n < 0 { + return int(n), fromUSBError(n) + } + return int(n), nil +} + +func (libusbImpl) getConfig(d *libusbDevHandle) (uint8, error) { + var cfg C.int + if errno := C.libusb_get_configuration((*C.libusb_device_handle)(d), &cfg); errno < 0 { + return 0, fromUSBError(errno) + } + return uint8(cfg), nil +} + +func (libusbImpl) setConfig(d *libusbDevHandle, cfg uint8) error { + return fromUSBError(C.libusb_set_configuration((*C.libusb_device_handle)(d), C.int(cfg))) +} + +func (libusbImpl) getStringDesc(d *libusbDevHandle, index int) (string, error) { + // allocate 200-byte array limited the length of string descriptor + buf := make([]byte, 200) + // get string descriptor from libusb. if errno < 0 then there are any errors. + // if errno >= 0; it is a length of result string descriptor + errno := C.libusb_get_string_descriptor_ascii( + (*C.libusb_device_handle)(d), + C.uint8_t(index), + (*C.uchar)(unsafe.Pointer(&buf[0])), + 200) + if errno < 0 { + return "", fmt.Errorf("usb: getstr: %s", fromUSBError(errno)) + } + return string(buf[:errno]), nil +} + +func (libusbImpl) setAutoDetach(d *libusbDevHandle, val int) error { + err := fromUSBError(C.libusb_set_auto_detach_kernel_driver((*C.libusb_device_handle)(d), C.int(val))) + if err != nil && err != ERROR_NOT_SUPPORTED { + return err + } + return nil +} + +func (libusbImpl) claim(d *libusbDevHandle, iface uint8) error { + return fromUSBError(C.libusb_claim_interface((*C.libusb_device_handle)(d), C.int(iface))) +} + +func (libusbImpl) release(d *libusbDevHandle, iface uint8) { + C.libusb_release_interface((*C.libusb_device_handle)(d), C.int(iface)) +} + +func (libusbImpl) setAlt(d *libusbDevHandle, iface, setup uint8) error { + return fromUSBError(C.libusb_set_interface_alt_setting((*C.libusb_device_handle)(d), C.int(iface), C.int(setup))) +} + +func (libusbImpl) alloc(d *libusbDevHandle, isoPackets int) (*libusbTransfer, error) { + xfer := C.libusb_alloc_transfer(C.int(isoPackets)) + if xfer == nil { + return nil, fmt.Errorf("libusb_alloc_transfer(%d) failed", isoPackets) + } + xfer.dev_handle = (*C.libusb_device_handle)(d) + xfer.num_iso_packets = C.int(isoPackets) + return (*libusbTransfer)(xfer), nil +} + +func (libusbImpl) cancel(t *libusbTransfer) error { + return fromUSBError(C.libusb_cancel_transfer((*C.struct_libusb_transfer)(t))) +} + +func (libusbImpl) submit(t *libusbTransfer) error { + return fromUSBError(C.submit((*C.struct_libusb_transfer)(t))) +} + +func (libusbImpl) free(t *libusbTransfer) { + C.libusb_free_transfer((*C.struct_libusb_transfer)(t)) +} + +func (libusbImpl) setIsoPacketLengths(t *libusbTransfer, length uint32) { + C.libusb_set_iso_packet_lengths((*C.struct_libusb_transfer)(t), C.uint(length)) +} + +func (libusbImpl) compactIsoData(t *libusbTransfer) (int, TransferStatus) { + var status TransferStatus + n := int(C.compact_iso_data((*C.struct_libusb_transfer)(t), (*C.uchar)(unsafe.Pointer(&status)))) + return n, status +} + +// libusb is an injection point for tests +var libusb libusbIntf = libusbImpl{} + +var ( + libusbIsoSize = C.sizeof_struct_libusb_iso_packet_descriptor + libusbIsoOffset = unsafe.Offsetof(C.struct_libusb_transfer{}.iso_packet_desc) +) + +//export xfer_callback +func xfer_callback(cptr unsafe.Pointer) { + ch := *(*chan struct{})(cptr) + close(ch) +} From 21d53c918095a80fe6942ffd8be5d4c34842ed46 Mon Sep 17 00:00:00 2001 From: Sebastian Zagrodzki Date: Thu, 23 Feb 2017 10:39:50 +0100 Subject: [PATCH 04/20] usb.go no longer needs "C" --- usb/libusb.go | 1 + usb/usb.go | 4 ---- 2 files changed, 1 insertion(+), 4 deletions(-) diff --git a/usb/libusb.go b/usb/libusb.go index ec08620..00951d4 100644 --- a/usb/libusb.go +++ b/usb/libusb.go @@ -23,6 +23,7 @@ import ( ) /* +#cgo pkg-config: libusb-1.0 #include int compact_iso_data(struct libusb_transfer *xfer, unsigned char *status); diff --git a/usb/usb.go b/usb/usb.go index d22ad59..6e6e813 100644 --- a/usb/usb.go +++ b/usb/usb.go @@ -16,10 +16,6 @@ // Package usb provides a wrapper around libusb-1.0. package usb -// #cgo pkg-config: libusb-1.0 -// #include -import "C" - type Context struct { ctx *libusbContext done chan struct{} From bf3b23ccd2986ffafe48bcc57d65b79b8a96ce4b Mon Sep 17 00:00:00 2001 From: Sebastian Zagrodzki Date: Thu, 23 Feb 2017 10:45:35 +0100 Subject: [PATCH 05/20] move all transfer C parts to libusb.go to alloc() --- usb/libusb.go | 9 +++++++-- usb/transfer.go | 11 ++--------- 2 files changed, 9 insertions(+), 11 deletions(-) diff --git a/usb/libusb.go b/usb/libusb.go index 00951d4..d5412a9 100644 --- a/usb/libusb.go +++ b/usb/libusb.go @@ -71,7 +71,7 @@ type libusbIntf interface { setAlt(*libusbDevHandle, uint8, uint8) error // endpoint - alloc(*libusbDevHandle, int) (*libusbTransfer, error) + alloc(*libusbDevHandle, uint8, TransferType, time.Duration, int, []byte) (*libusbTransfer, error) cancel(*libusbTransfer) error submit(*libusbTransfer) error free(*libusbTransfer) @@ -327,13 +327,18 @@ func (libusbImpl) setAlt(d *libusbDevHandle, iface, setup uint8) error { return fromUSBError(C.libusb_set_interface_alt_setting((*C.libusb_device_handle)(d), C.int(iface), C.int(setup))) } -func (libusbImpl) alloc(d *libusbDevHandle, isoPackets int) (*libusbTransfer, error) { +func (libusbImpl) alloc(d *libusbDevHandle, addr uint8, tt TransferType, timeout time.Duration, isoPackets int, buf []byte) (*libusbTransfer, error) { xfer := C.libusb_alloc_transfer(C.int(isoPackets)) if xfer == nil { return nil, fmt.Errorf("libusb_alloc_transfer(%d) failed", isoPackets) } xfer.dev_handle = (*C.libusb_device_handle)(d) + xfer.endpoint = C.uchar(addr) + xfer.timeout = C.uint(timeout / time.Millisecond) + xfer._type = C.uchar(tt) xfer.num_iso_packets = C.int(isoPackets) + xfer.buffer = (*C.uchar)((unsafe.Pointer)(&buf[0])) + xfer.length = C.int(len(buf)) return (*libusbTransfer)(xfer), nil } diff --git a/usb/transfer.go b/usb/transfer.go index 81f3da7..fcc0afb 100644 --- a/usb/transfer.go +++ b/usb/transfer.go @@ -133,24 +133,17 @@ func newUSBTransfer(dev *libusbDevHandle, ei EndpointInfo, buf []byte, timeout t } } - xfer, err := libusb.alloc(dev, isoPackets) + xfer, err := libusb.alloc(dev, ei.Address, tt, timeout, isoPackets, buf) if err != nil { return nil, err } - xfer.timeout = C.uint(timeout / time.Millisecond) - xfer.endpoint = C.uchar(ei.Address) - xfer._type = C.uchar(tt) - - xfer.buffer = (*C.uchar)((unsafe.Pointer)(&buf[0])) - xfer.length = C.int(len(buf)) - if tt == TRANSFER_TYPE_ISOCHRONOUS { libusb.setIsoPacketLengths(xfer, ei.MaxIsoPacket) } t := &usbTransfer{ - xfer: (*libusbTransfer)(xfer), + xfer: xfer, buf: buf, } runtime.SetFinalizer(t, func(t *usbTransfer) { From 81e9253f4971e908ce427fe33cb92a4e4bee9f17 Mon Sep 17 00:00:00 2001 From: Sebastian Zagrodzki Date: Thu, 23 Feb 2017 10:46:04 +0100 Subject: [PATCH 06/20] no more C in transfer.go --- usb/transfer.go | 3 --- 1 file changed, 3 deletions(-) diff --git a/usb/transfer.go b/usb/transfer.go index fcc0afb..90b5a0a 100644 --- a/usb/transfer.go +++ b/usb/transfer.go @@ -23,9 +23,6 @@ import ( "unsafe" ) -// #include -import "C" - type usbTransfer struct { // mu protects the transfer state. mu sync.Mutex From 00dbfd23dd3734336965303420122e76a4377d39 Mon Sep 17 00:00:00 2001 From: Sebastian Zagrodzki Date: Sun, 26 Feb 2017 21:02:29 +0100 Subject: [PATCH 07/20] done channel is now initialized on submit. add a data() method, to avoid reaching into any xfer fields. --- usb/libusb.go | 26 +++++++++++++++----------- 1 file changed, 15 insertions(+), 11 deletions(-) diff --git a/usb/libusb.go b/usb/libusb.go index d5412a9..b708fb6 100644 --- a/usb/libusb.go +++ b/usb/libusb.go @@ -70,13 +70,13 @@ type libusbIntf interface { release(*libusbDevHandle, uint8) setAlt(*libusbDevHandle, uint8, uint8) error - // endpoint + // transfer alloc(*libusbDevHandle, uint8, TransferType, time.Duration, int, []byte) (*libusbTransfer, error) cancel(*libusbTransfer) error - submit(*libusbTransfer) error + submit(*libusbTransfer, chan struct{}) error + data(*libusbTransfer) (int, TransferStatus) free(*libusbTransfer) setIsoPacketLengths(*libusbTransfer, uint32) - compactIsoData(*libusbTransfer) (int, TransferStatus) } // libusbImpl is an implementation of libusbIntf using real CGo-wrapped libusb. @@ -91,7 +91,7 @@ func (libusbImpl) init() (*libusbContext, error) { } func (libusbImpl) handleEvents(c *libusbContext, done <-chan struct{}) { - tv := C.struct_timeval{tv_sec: 10} + tv := C.struct_timeval{tv_usec: 100e3} for { select { case <-done: @@ -346,10 +346,20 @@ func (libusbImpl) cancel(t *libusbTransfer) error { return fromUSBError(C.libusb_cancel_transfer((*C.struct_libusb_transfer)(t))) } -func (libusbImpl) submit(t *libusbTransfer) error { +func (libusbImpl) submit(t *libusbTransfer, done chan struct{}) error { + t.user_data = (unsafe.Pointer)(&done) return fromUSBError(C.submit((*C.struct_libusb_transfer)(t))) } +func (libusbImpl) data(t *libusbTransfer) (int, TransferStatus) { + if TransferType(t._type) == TRANSFER_TYPE_ISOCHRONOUS { + var status TransferStatus + n := int(C.compact_iso_data((*C.struct_libusb_transfer)(t), (*C.uchar)(unsafe.Pointer(&status)))) + return n, status + } + return int(t.actual_length), TransferStatus(t.status) +} + func (libusbImpl) free(t *libusbTransfer) { C.libusb_free_transfer((*C.struct_libusb_transfer)(t)) } @@ -358,12 +368,6 @@ func (libusbImpl) setIsoPacketLengths(t *libusbTransfer, length uint32) { C.libusb_set_iso_packet_lengths((*C.struct_libusb_transfer)(t), C.uint(length)) } -func (libusbImpl) compactIsoData(t *libusbTransfer) (int, TransferStatus) { - var status TransferStatus - n := int(C.compact_iso_data((*C.struct_libusb_transfer)(t), (*C.uchar)(unsafe.Pointer(&status)))) - return n, status -} - // libusb is an injection point for tests var libusb libusbIntf = libusbImpl{} From c2167157bed2218d335685a19ee6812a583c1a59 Mon Sep 17 00:00:00 2001 From: Sebastian Zagrodzki Date: Sun, 26 Feb 2017 21:08:08 +0100 Subject: [PATCH 08/20] Move transfer_fakelibusb to fakelibusb, used in other tests too. Change the interface a bit. --- usb/fakelibusb_test.go | 85 +++++++++++++++++++++++++++++++++ usb/transfer_fakelibusb_test.go | 60 ----------------------- 2 files changed, 85 insertions(+), 60 deletions(-) create mode 100644 usb/fakelibusb_test.go delete mode 100644 usb/transfer_fakelibusb_test.go diff --git a/usb/fakelibusb_test.go b/usb/fakelibusb_test.go new file mode 100644 index 0000000..4a73782 --- /dev/null +++ b/usb/fakelibusb_test.go @@ -0,0 +1,85 @@ +// Copyright 2017 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 usb + +import ( + "sync" + "time" +) + +type fakeTransfer struct { + // done is the channel that needs to be closed when the transfer has finished. + done chan struct{} + // buf is the slice for reading/writing data between the submit() and wait() returning. + buf []byte + // status will be returned by wait() on this transfer + status TransferStatus + // length is the number of bytes used from the buffer (write) or available + // in the buffer (read). + length int +} + +type fakeLibusb struct { + libusbIntf + + mu sync.Mutex + // ts has a map of all allocated transfers, indexed by the pointer of + // underlying libusbTransfer. + ts map[*libusbTransfer]*fakeTransfer + // submitted receives a fakeTransfers when submit() is called. + submitted chan *fakeTransfer +} + +func (f *fakeLibusb) alloc(_ *libusbDevHandle, _ uint8, _ TransferType, _ time.Duration, _ int, buf []byte) (*libusbTransfer, error) { + f.mu.Lock() + defer f.mu.Unlock() + t := new(libusbTransfer) + f.ts[t] = &fakeTransfer{buf: buf} + return t, nil +} + +func (f *fakeLibusb) submit(t *libusbTransfer, done chan struct{}) error { + f.mu.Lock() + ft := f.ts[t] + f.mu.Unlock() + ft.done = done + f.submitted <- ft + return nil +} + +func (f *fakeLibusb) cancel(t *libusbTransfer) error { return nil } +func (f *fakeLibusb) free(t *libusbTransfer) { + f.mu.Lock() + defer f.mu.Unlock() + delete(f.ts, t) +} + +func (f *fakeLibusb) data(t *libusbTransfer) (int, TransferStatus) { + f.mu.Lock() + defer f.mu.Unlock() + return f.ts[t].length, f.ts[t].status +} + +func (f *fakeLibusb) waitForSubmitted() *fakeTransfer { + return <-f.submitted +} + +func newFakeLibusb() *fakeLibusb { + return &fakeLibusb{ + ts: make(map[*libusbTransfer]*fakeTransfer), + submitted: make(chan *fakeTransfer, 10), + libusbIntf: libusbImpl{}, + } +} diff --git a/usb/transfer_fakelibusb_test.go b/usb/transfer_fakelibusb_test.go deleted file mode 100644 index 319853f..0000000 --- a/usb/transfer_fakelibusb_test.go +++ /dev/null @@ -1,60 +0,0 @@ -// Copyright 2017 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 usb - -import "sync" - -type fakeLibusb struct { - sync.Mutex - ts map[*libusbTransfer]chan struct{} - libusbIntf -} - -func (f *fakeLibusb) submit(t *libusbTransfer) error { - f.Lock() - defer f.Unlock() - if f.ts[t] == nil { - f.ts[t] = make(chan struct{}) - } - close(f.ts[t]) - return nil -} - -func (f *fakeLibusb) cancel(t *libusbTransfer) error { return nil } - -func (f *fakeLibusb) waitForSubmit(t *usbTransfer) { - f.Lock() - if f.ts[t.xfer] == nil { - f.ts[t.xfer] = make(chan struct{}) - } - ch := f.ts[t.xfer] - f.Unlock() - <-ch -} - -func (f *fakeLibusb) runCallback(t *usbTransfer, cb func(*usbTransfer)) { - f.Lock() - defer f.Unlock() - delete(f.ts, t.xfer) - cb(t) - close(t.done) -} - -func newFakeLibusb() *fakeLibusb { - return &fakeLibusb{ - ts: make(map[*libusbTransfer]chan struct{}), - libusbIntf: libusbImpl{}, - } -} From 6caeb9d701bd17beeb10f88865d9b993c4ca9a93 Mon Sep 17 00:00:00 2001 From: Sebastian Zagrodzki Date: Sun, 26 Feb 2017 21:12:18 +0100 Subject: [PATCH 09/20] remove transferIntf, instead rely on transfer.go "usbTransfer", but with a fake libusb xfer underneath. --- usb/endpoint.go | 18 ++---------------- 1 file changed, 2 insertions(+), 16 deletions(-) diff --git a/usb/endpoint.go b/usb/endpoint.go index f942709..ff0a970 100644 --- a/usb/endpoint.go +++ b/usb/endpoint.go @@ -27,12 +27,6 @@ type Endpoint interface { Info() EndpointInfo } -type transferIntf interface { - submit() error - wait() (int, error) - free() error -} - type endpoint struct { h *libusbDevHandle @@ -41,8 +35,6 @@ type endpoint struct { readTimeout time.Duration writeTimeout time.Duration - - newUSBTransfer func([]byte, time.Duration) (transferIntf, error) } func (e *endpoint) Read(buf []byte) (int, error) { @@ -64,16 +56,12 @@ func (e *endpoint) Write(buf []byte) (int, error) { func (e *endpoint) Interface() InterfaceSetup { return e.InterfaceSetup } func (e *endpoint) Info() EndpointInfo { return e.EndpointInfo } -func (e *endpoint) newLibUSBTransfer(buf []byte, timeout time.Duration) (transferIntf, error) { - return newUSBTransfer(e.h, e.EndpointInfo, buf, timeout) -} - func (e *endpoint) transfer(buf []byte, timeout time.Duration) (int, error) { if len(buf) == 0 { return 0, nil } - t, err := e.newUSBTransfer(buf, timeout) + t, err := newUSBTransfer(e.h, e.EndpointInfo, buf, timeout) if err != nil { return 0, err } @@ -91,11 +79,9 @@ func (e *endpoint) transfer(buf []byte, timeout time.Duration) (int, error) { } func newEndpoint(d *Device) *endpoint { - ep := &endpoint{ + return &endpoint{ h: d.handle, readTimeout: d.ReadTimeout, writeTimeout: d.WriteTimeout, } - ep.newUSBTransfer = ep.newLibUSBTransfer - return ep } From 9eebb871cb38026da1e2808ce9b56418329aa080 Mon Sep 17 00:00:00 2001 From: Sebastian Zagrodzki Date: Sun, 26 Feb 2017 21:13:09 +0100 Subject: [PATCH 10/20] Use fake libusb. Add testcase descriptions. --- usb/transfer_test.go | 101 ++++++++----------------------------------- 1 file changed, 17 insertions(+), 84 deletions(-) diff --git a/usb/transfer_test.go b/usb/transfer_test.go index 1185583..eb93261 100644 --- a/usb/transfer_test.go +++ b/usb/transfer_test.go @@ -17,7 +17,6 @@ package usb import ( "testing" "time" - "unsafe" ) func TestNewTransfer(t *testing.T) { @@ -107,24 +106,26 @@ func TestTransferProtocol(t *testing.T) { } go func() { - f.waitForSubmit(xfers[0]) - f.runCallback(xfers[0], func(t *usbTransfer) { - t.xfer.actual_length = 5 - t.xfer.status = uint32(SUCCESS) - copy(t.buf, []byte{1, 2, 3, 4, 5}) - }) - }() - go func() { - f.waitForSubmit(xfers[1]) - f.runCallback(xfers[1], func(t *usbTransfer) { - t.xfer.actual_length = 99 - t.xfer.status = uint32(SUCCESS) - copy(t.buf, []byte{12, 12, 12, 12, 12}) - }) + ft := f.waitForSubmitted() + ft.length = 5 + ft.status = LIBUSB_TRANSFER_COMPLETED + copy(ft.buf, []byte{1, 2, 3, 4, 5}) + close(ft.done) + + ft = f.waitForSubmitted() + ft.length = 99 + ft.status = LIBUSB_TRANSFER_COMPLETED + copy(ft.buf, []byte{12, 12, 12, 12, 12}) + close(ft.done) + + ft = f.waitForSubmitted() + ft.length = 123 + ft.status = LIBUSB_TRANSFER_CANCELLED + close(ft.done) }() - xfers[1].submit() xfers[0].submit() + xfers[1].submit() got, err := xfers[0].wait() if err != nil { t.Errorf("xfer#0.wait returned error %v, want nil", err) @@ -140,13 +141,6 @@ func TestTransferProtocol(t *testing.T) { t.Errorf("xfer#0.wait returned %d bytes, want %d", got, want) } - go func() { - f.waitForSubmit(xfers[1]) - f.runCallback(xfers[1], func(t *usbTransfer) { - t.xfer.actual_length = 123 - t.xfer.status = uint32(LIBUSB_TRANSFER_CANCELLED) - }) - }() xfers[1].submit() xfers[1].cancel() got, err = xfers[1].wait() @@ -163,64 +157,3 @@ func TestTransferProtocol(t *testing.T) { x.free() } } - -func TestIsoPackets(t *testing.T) { - defer func(i libusbIntf) { libusb = i }(libusb) - - f := newFakeLibusb() - libusb = f - - xfer, err := newUSBTransfer(nil, EndpointInfo{ - Address: 0x82, - Attributes: uint8(TRANSFER_TYPE_ISOCHRONOUS), - MaxPacketSize: 3<<11 + 1024, - MaxIsoPacket: 3 * 1024, - PollInterval: 1, - }, make([]byte, 15000), time.Second) - if err != nil { - t.Fatalf("newUSBTransfer: %v", err) - } - - // 15000 / (3*1024) = 4.something, rounded up to 5 - if got, want := int(xfer.xfer.num_iso_packets), 5; got != want { - t.Fatalf("newUSBTransfer: got %d iso packets, want %d", got, want) - } - - go func() { - f.waitForSubmit(xfer) - f.runCallback(xfer, func(x *usbTransfer) { - x.xfer.actual_length = 1234 // meaningless for iso transfers - x.xfer.status = uint32(LIBUSB_TRANSFER_TIMED_OUT) - for i := 0; i < int(xfer.xfer.num_iso_packets); i++ { - // this is a horrible calculation. - // libusb_transfer uses a flexible array for the iso packet - // descriptors at the end of the transfer struct. - // The only way to get access to the elements of that array - // is to use pointer arithmetic. - // Calculate the offset of the first descriptor in the struct, - // then move by sizeof(iso descriptor) for num_iso_packets. - desc := (*libusbIso)(unsafe.Pointer(uintptr(unsafe.Pointer(x.xfer)) + libusbIsoOffset + uintptr(i*libusbIsoSize))) - // max iso packet = 3 * 1024 - if desc.length != 3*1024 { - t.Errorf("iso pkt length: got %d, want %d", desc.length, 3*1024) - } - desc.actual_length = 100 - // packets 0..2 are successful, packet 3 is timed out - if i != 4 { - desc.status = uint32(LIBUSB_TRANSFER_COMPLETED) - } else { - desc.status = uint32(LIBUSB_TRANSFER_TIMED_OUT) - } - } - }) - }() - - xfer.submit() - got, err := xfer.wait() - if err == nil { - t.Error("Iso transfer: got nil error, want non-nil") - } - if want := 4 * 100; got != want { - t.Errorf("Iso transfer: got %d bytes, want %d", got, want) - } -} From 8addfb562f19a131cd585c4603e196b8f58caf31 Mon Sep 17 00:00:00 2001 From: Sebastian Zagrodzki Date: Sun, 26 Feb 2017 21:13:31 +0100 Subject: [PATCH 11/20] updated submit interface and added data() method. --- usb/transfer.go | 12 ++---------- 1 file changed, 2 insertions(+), 10 deletions(-) diff --git a/usb/transfer.go b/usb/transfer.go index 90b5a0a..4e783ae 100644 --- a/usb/transfer.go +++ b/usb/transfer.go @@ -20,7 +20,6 @@ import ( "runtime" "sync" "time" - "unsafe" ) type usbTransfer struct { @@ -48,8 +47,7 @@ func (t *usbTransfer) submit() error { return errors.New("transfer was already submitted and is not finished yet.") } t.done = make(chan struct{}) - t.xfer.user_data = (unsafe.Pointer)(&t.done) - if err := libusb.submit(t.xfer); err != nil { + if err := libusb.submit(t.xfer, t.done); err != nil { return err } t.submitted = true @@ -73,13 +71,7 @@ func (t *usbTransfer) wait() (n int, err error) { case <-t.done: } t.submitted = false - var status TransferStatus - switch TransferType(t.xfer._type) { - case TRANSFER_TYPE_ISOCHRONOUS: - n, status = libusb.compactIsoData(t.xfer) - default: - n, status = int(t.xfer.actual_length), TransferStatus(t.xfer.status) - } + n, status := libusb.data(t.xfer) if status != LIBUSB_TRANSFER_COMPLETED { return n, status } From c0d81e08a81f449eb71bb41946e50ab8e82af15c Mon Sep 17 00:00:00 2001 From: Sebastian Zagrodzki Date: Sun, 26 Feb 2017 21:14:04 +0100 Subject: [PATCH 12/20] use a shared fakelibusb for mocking --- usb/endpoint_test.go | 54 +++++++++++++++++++++++++++----------------- 1 file changed, 33 insertions(+), 21 deletions(-) diff --git a/usb/endpoint_test.go b/usb/endpoint_test.go index eb05960..a8b7a04 100644 --- a/usb/endpoint_test.go +++ b/usb/endpoint_test.go @@ -15,23 +15,12 @@ package usb import ( - "errors" "reflect" "testing" - "time" ) -type fakeTransfer struct { - buf []byte - ret int - err error -} - -func (t *fakeTransfer) submit() error { return nil } -func (t *fakeTransfer) wait() (int, error) { return t.ret, t.err } -func (t *fakeTransfer) free() error { return nil } - func TestEndpoint(t *testing.T) { + defer func(i libusbIntf) { libusb = i }(libusb) for _, epCfg := range []struct { method string InterfaceSetup @@ -42,35 +31,58 @@ func TestEndpoint(t *testing.T) { } { t.Run(epCfg.method, func(t *testing.T) { for _, tc := range []struct { + desc string buf []byte ret int - err error + status TransferStatus want int wantErr bool }{ - {buf: nil, ret: 10, want: 0}, - {buf: make([]byte, 128), ret: 60, want: 60}, - {buf: make([]byte, 128), ret: 10, err: errors.New("some error"), want: 10, wantErr: true}, + { + desc: "empty buffer", + buf: nil, + ret: 10, + want: 0, + }, + { + desc: "128B buffer, 60 transferred", + buf: make([]byte, 128), + ret: 60, + want: 60, + }, + { + desc: "128B buffer, 10 transferred and then error", + buf: make([]byte, 128), + ret: 10, + status: LIBUSB_TRANSFER_ERROR, + want: 10, + wantErr: true, + }, } { + lib := newFakeLibusb() + libusb = lib ep := &endpoint{ InterfaceSetup: epCfg.InterfaceSetup, EndpointInfo: epCfg.EndpointInfo, - newUSBTransfer: func(buf []byte, timeout time.Duration) (transferIntf, error) { - return &fakeTransfer{buf: buf, ret: tc.ret, err: tc.err}, nil - }, } op, ok := reflect.TypeOf(ep).MethodByName(epCfg.method) if !ok { t.Fatalf("method %s not found in endpoint struct", epCfg.method) } + go func() { + fakeT := lib.waitForSubmitted() + fakeT.length = tc.ret + fakeT.status = tc.status + close(fakeT.done) + }() opv := op.Func.Interface().(func(*endpoint, []byte) (int, error)) got, err := opv(ep, tc.buf) if (err != nil) != tc.wantErr { - t.Errorf("bulkInEP.Read(): got err: %v, err != nil is %v, want %v", err, err != nil, tc.wantErr) + t.Errorf("%s: bulkInEP.Read(): got err: %v, err != nil is %v, want %v", tc.desc, err, err != nil, tc.wantErr) continue } if got != tc.want { - t.Errorf("bulkInEP.Read(): got %d bytes, want %d", got, tc.want) + t.Errorf("%s: bulkInEP.Read(): got %d bytes, want %d", tc.desc, got, tc.want) } } }) From 676cb78caef96c39de5c70c4af5226b50130ef1b Mon Sep 17 00:00:00 2001 From: Sebastian Zagrodzki Date: Sun, 26 Feb 2017 21:43:56 +0100 Subject: [PATCH 13/20] unwrap the nested ifs --- usb/device.go | 89 +++++++++++++++++++++++++++++---------------------- 1 file changed, 50 insertions(+), 39 deletions(-) diff --git a/usb/device.go b/usb/device.go index 72e9699..5a256c3 100644 --- a/usb/device.go +++ b/usb/device.go @@ -92,70 +92,81 @@ func (d *Device) Close() error { return nil } -func (d *Device) OpenEndpoint(conf, iface, setup, epoint uint8) (Endpoint, error) { - end := newEndpoint(d) +func (d *Device) OpenEndpoint(cfgNum, ifNum, setNum, epNum uint8) (Endpoint, error) { + var cfg *ConfigInfo + for _, c := range d.Configs { + if c.Config == cfgNum { + debug.Printf("found conf: %#v\n", c) + cfg = &c + break + } + } + if cfg == nil { + return nil, fmt.Errorf("usb: unknown configuration %02x", cfgNum) + } + + var intf *InterfaceInfo + for _, i := range cfg.Interfaces { + if i.Number == ifNum { + debug.Printf("found iface: %#v\n", i) + intf = &i + break + } + } + if intf == nil { + return nil, fmt.Errorf("usb: unknown interface %02x", ifNum) + } var setAlternate bool - for _, c := range d.Configs { - if c.Config != conf { - continue + var ifs *InterfaceSetup + for i, s := range intf.Setups { + if s.Alternate == setNum { + setAlternate = i != 0 + debug.Printf("found setup: %#v [default: %v]\n", s, !setAlternate) + ifs = &s } - debug.Printf("found conf: %#v\n", c) - for _, i := range c.Interfaces { - if i.Number != iface { - continue - } - debug.Printf("found iface: %#v\n", i) - for i, s := range i.Setups { - if s.Alternate != setup { - continue - } - setAlternate = i != 0 - - debug.Printf("found setup: %#v [default: %v]\n", s, !setAlternate) - for _, e := range s.Endpoints { - debug.Printf("ep %02x search: %#v\n", epoint, s) - if e.Address != epoint { - continue - } - end.InterfaceSetup = s - end.EndpointInfo = e - goto found - } - return nil, fmt.Errorf("usb: unknown endpoint %02x", epoint) - } - return nil, fmt.Errorf("usb: unknown setup %02x", setup) - } - return nil, fmt.Errorf("usb: unknown interface %02x", iface) } - return nil, fmt.Errorf("usb: unknown configuration %02x", conf) + if ifs == nil { + return nil, fmt.Errorf("usb: unknown setup %02x", setNum) + } -found: + var ep *EndpointInfo + for _, e := range ifs.Endpoints { + if e.Address == epNum { + debug.Printf("found ep %02x in %#v\n", epNum, *ifs) + ep = &e + } + } + if ep == nil { + return nil, fmt.Errorf("usb: unknown endpoint %02x", epNum) + } + + end := newEndpoint(d, *ifs, *ep) // Set the configuration activeConf, err := libusb.getConfig(d.handle) if err != nil { return nil, fmt.Errorf("usb: getcfg: %s", err) } - if activeConf != conf { - if err := libusb.setConfig(d.handle, conf); err != nil { + if activeConf != cfgNum { + if err := libusb.setConfig(d.handle, cfgNum); err != nil { return nil, fmt.Errorf("usb: setcfg: %s", err) } } // Claim the interface - if err := libusb.claim(d.handle, iface); err != nil { + if err := libusb.claim(d.handle, ifNum); err != nil { return nil, fmt.Errorf("usb: claim: %s", err) } // Increment the claim count d.lock.Lock() - d.claimed[iface]++ + d.claimed[ifNum]++ d.lock.Unlock() // unlock immediately because the next calls may block // Choose the alternate if setAlternate { - if err := libusb.setAlt(d.handle, iface, setup); err != nil { + if err := libusb.setAlt(d.handle, ifNum, setNum); err != nil { return nil, fmt.Errorf("usb: setalt: %s", err) } } From d0859b8c475640f9ae5977a6754d709057593eb5 Mon Sep 17 00:00:00 2001 From: Sebastian Zagrodzki Date: Sun, 26 Feb 2017 21:44:15 +0100 Subject: [PATCH 14/20] Pass all required info to newEndpoint. --- usb/endpoint.go | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/usb/endpoint.go b/usb/endpoint.go index ff0a970..cd9b427 100644 --- a/usb/endpoint.go +++ b/usb/endpoint.go @@ -78,10 +78,12 @@ func (e *endpoint) transfer(buf []byte, timeout time.Duration) (int, error) { return n, nil } -func newEndpoint(d *Device) *endpoint { +func newEndpoint(d *Device, s InterfaceSetup, e EndpointInfo) *endpoint { return &endpoint{ - h: d.handle, - readTimeout: d.ReadTimeout, - writeTimeout: d.WriteTimeout, + InterfaceSetup: s, + EndpointInfo: e, + h: d.handle, + readTimeout: d.ReadTimeout, + writeTimeout: d.WriteTimeout, } } From 2d51a51ec5d79e29014111fbf57ac2acc1279eee Mon Sep 17 00:00:00 2001 From: Sebastian Zagrodzki Date: Sun, 26 Feb 2017 21:47:47 +0100 Subject: [PATCH 15/20] newEndpoint no longer references *Device, but receives individual values extracted from the device. Makes testing easier. --- usb/device.go | 2 +- usb/endpoint.go | 8 ++++---- usb/endpoint_test.go | 6 ++---- 3 files changed, 7 insertions(+), 9 deletions(-) diff --git a/usb/device.go b/usb/device.go index 5a256c3..9129990 100644 --- a/usb/device.go +++ b/usb/device.go @@ -141,7 +141,7 @@ func (d *Device) OpenEndpoint(cfgNum, ifNum, setNum, epNum uint8) (Endpoint, err return nil, fmt.Errorf("usb: unknown endpoint %02x", epNum) } - end := newEndpoint(d, *ifs, *ep) + end := newEndpoint(d.handle, *ifs, *ep, d.ReadTimeout, d.WriteTimeout) // Set the configuration activeConf, err := libusb.getConfig(d.handle) diff --git a/usb/endpoint.go b/usb/endpoint.go index cd9b427..067fa63 100644 --- a/usb/endpoint.go +++ b/usb/endpoint.go @@ -78,12 +78,12 @@ func (e *endpoint) transfer(buf []byte, timeout time.Duration) (int, error) { return n, nil } -func newEndpoint(d *Device, s InterfaceSetup, e EndpointInfo) *endpoint { +func newEndpoint(h *libusbDevHandle, s InterfaceSetup, e EndpointInfo, rt, wt time.Duration) *endpoint { return &endpoint{ InterfaceSetup: s, EndpointInfo: e, - h: d.handle, - readTimeout: d.ReadTimeout, - writeTimeout: d.WriteTimeout, + h: h, + readTimeout: rt, + writeTimeout: wt, } } diff --git a/usb/endpoint_test.go b/usb/endpoint_test.go index a8b7a04..3e89868 100644 --- a/usb/endpoint_test.go +++ b/usb/endpoint_test.go @@ -17,6 +17,7 @@ package usb import ( "reflect" "testing" + "time" ) func TestEndpoint(t *testing.T) { @@ -61,10 +62,7 @@ func TestEndpoint(t *testing.T) { } { lib := newFakeLibusb() libusb = lib - ep := &endpoint{ - InterfaceSetup: epCfg.InterfaceSetup, - EndpointInfo: epCfg.EndpointInfo, - } + ep := newEndpoint(nil, epCfg.InterfaceSetup, epCfg.EndpointInfo, time.Second, time.Second) op, ok := reflect.TypeOf(ep).MethodByName(epCfg.method) if !ok { t.Fatalf("method %s not found in endpoint struct", epCfg.method) From bed02e983898b3401ee3338d1cedd5cf0a3f0dcb Mon Sep 17 00:00:00 2001 From: Sebastian Zagrodzki Date: Wed, 1 Mar 2017 10:51:17 +0100 Subject: [PATCH 16/20] A benchmark on the trivial cgo call, to assess overhead of different ways of calling out to cgo. --- usb/libusb.go | 5 ++++ usb/libusb_cgo_benchmark_test.go | 46 ++++++++++++++++++++++++++++++++ 2 files changed, 51 insertions(+) create mode 100644 usb/libusb_cgo_benchmark_test.go diff --git a/usb/libusb.go b/usb/libusb.go index b708fb6..2c2f231 100644 --- a/usb/libusb.go +++ b/usb/libusb.go @@ -381,3 +381,8 @@ func xfer_callback(cptr unsafe.Pointer) { ch := *(*chan struct{})(cptr) close(ch) } + +// for benchmarking +func libusbSetDebug(c *libusbContext, lvl int) { + C.libusb_set_debug((*C.libusb_context)(c), C.int(lvl)) +} diff --git a/usb/libusb_cgo_benchmark_test.go b/usb/libusb_cgo_benchmark_test.go new file mode 100644 index 0000000..da8acc9 --- /dev/null +++ b/usb/libusb_cgo_benchmark_test.go @@ -0,0 +1,46 @@ +package usb + +import "testing" + +func BenchmarkCGo(b *testing.B) { + for _, bc := range []struct { + name string + bfunc func(*libusbContext, int) + }{ + { + name: "simple function", + bfunc: func(ctx *libusbContext, N int) { + for i := 0; i < N; i++ { + libusbSetDebug(ctx, i&1) + } + }, + }, + { + name: "method", + bfunc: func(ctx *libusbContext, N int) { + impl := libusbImpl{} + for i := 0; i < N; i++ { + impl.setDebug(ctx, i&1) + } + }, + }, + { + name: "interface", + bfunc: func(ctx *libusbContext, N int) { + var intf libusbIntf = libusbImpl{} + for i := 0; i < N; i++ { + intf.setDebug(ctx, i&1) + } + }, + }, + } { + b.Run(bc.name, func(b *testing.B) { + ctx, err := libusbImpl{}.init() + if err != nil { + b.Fatalf("libusb_init() failed: %v", err) + } + b.ResetTimer() + bc.bfunc(ctx, b.N) + }) + } +} From 7c55d36758ffd8d1deef471b06c11f22c027cdbc Mon Sep 17 00:00:00 2001 From: Sebastian Zagrodzki Date: Wed, 1 Mar 2017 10:58:34 +0100 Subject: [PATCH 17/20] Run benchmarks for tests as well. --- .travis.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index 816a977..cd5453e 100644 --- a/.travis.yml +++ b/.travis.yml @@ -6,7 +6,7 @@ go: - 1.8 - tip -script: go test -v -test.run='BCD|Parse' ./... +script: go test -v -test.run='BCD|Parse' -bench.run=. ./... addons: apt: From 42e4e1a37df15789dbee439e266ec2daa1bfc06c Mon Sep 17 00:00:00 2001 From: Sebastian Zagrodzki Date: Wed, 1 Mar 2017 11:04:41 +0100 Subject: [PATCH 18/20] Gah. s/bench.run/test.bench/ --- .travis.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index cd5453e..6ba87ba 100644 --- a/.travis.yml +++ b/.travis.yml @@ -6,7 +6,7 @@ go: - 1.8 - tip -script: go test -v -test.run='BCD|Parse' -bench.run=. ./... +script: go test -v -test.run='BCD|Parse' -test.bench=. ./... addons: apt: From 2c956c55d81c4cdda573e225929aa6358964007f Mon Sep 17 00:00:00 2001 From: Sebastian Zagrodzki Date: Wed, 1 Mar 2017 11:13:16 +0100 Subject: [PATCH 19/20] Revert "Gah. s/bench.run/test.bench/" This reverts commit 42e4e1a37df15789dbee439e266ec2daa1bfc06c. --- .travis.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index 6ba87ba..cd5453e 100644 --- a/.travis.yml +++ b/.travis.yml @@ -6,7 +6,7 @@ go: - 1.8 - tip -script: go test -v -test.run='BCD|Parse' -test.bench=. ./... +script: go test -v -test.run='BCD|Parse' -bench.run=. ./... addons: apt: From 810a98e9fae6cc96f91e443c8b7293948eb679ac Mon Sep 17 00:00:00 2001 From: Sebastian Zagrodzki Date: Wed, 1 Mar 2017 11:13:22 +0100 Subject: [PATCH 20/20] Revert "Run benchmarks for tests as well." This reverts commit 7c55d36758ffd8d1deef471b06c11f22c027cdbc. --- .travis.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index cd5453e..816a977 100644 --- a/.travis.yml +++ b/.travis.yml @@ -6,7 +6,7 @@ go: - 1.8 - tip -script: go test -v -test.run='BCD|Parse' -bench.run=. ./... +script: go test -v -test.run='BCD|Parse' ./... addons: apt: