From 1755936932ccfbc03ab045b29b386eafcab1599e Mon Sep 17 00:00:00 2001 From: Sebastian Zagrodzki Date: Wed, 8 Feb 2017 22:30:03 +0100 Subject: [PATCH 1/6] Change how device is addressed, add alternative --bus/--addr flags. Print all diagnostics to the log (stderr). Capture the data from the specified endpoint and print it to stdout. --- rawread/main.go | 112 ++++++++++++++++++++++++++++++------------------ 1 file changed, 70 insertions(+), 42 deletions(-) diff --git a/rawread/main.go b/rawread/main.go index 8a2efac..55ab813 100644 --- a/rawread/main.go +++ b/rawread/main.go @@ -20,18 +20,23 @@ import ( "flag" "fmt" "log" + "os" "github.com/kylelemons/gousb/usb" "github.com/kylelemons/gousb/usbid" ) var ( - device = flag.String("device", "vend:prod", "Device to which to connect") - config = flag.Int("config", 1, "Endpoint to which to connect") - iface = flag.Int("interface", 0, "Endpoint to which to connect") - setup = flag.Int("setup", 0, "Endpoint to which to connect") - endpoint = flag.Int("endpoint", 1, "Endpoint to which to connect") + vid = flag.Uint("vid", 0, "VID of the device to which to connect. Exclusive with bus/addr flags.") + pid = flag.Uint("pid", 0, "PID of the device to which to connect. Exclusive with bus/addr flags.") + bus = flag.Uint("bus", 0, "Bus number for the device to which to connect. Exclusive with vid/pid flags.") + addr = flag.Uint("addr", 0, "Address of the device to which to connect. Exclusive with vid/pid flags.") + config = flag.Uint("config", 1, "Endpoint to which to connect") + iface = flag.Uint("interface", 0, "Endpoint to which to connect") + setup = flag.Uint("setup", 0, "Endpoint to which to connect") + endpoint = flag.Uint("endpoint", 1, "Endpoint to which to connect") debug = flag.Int("debug", 3, "Debug level for libusb") + size = flag.Uint("read_size", 1024, "Maximum number of bytes of data to read. Collected will be printed to STDOUT.") ) func main() { @@ -43,40 +48,29 @@ func main() { ctx.Debug(*debug) - log.Printf("Scanning for device %q...", *device) + var devName string + switch { + case *vid == 0 && *pid == 0 && *bus == 0 && *addr == 0: + log.Fatal("You need to specify the device, either through --vid/--pid flags or through --bus/--addr flags.") + case (*vid > 0 || *pid > 0) && (*bus > 0 || *addr > 0): + log.Fatal("You can't use --vid/--pid flags at the same time as --bus/--addr.") + case *vid > 0 || *pid > 0: + devName = fmt.Sprintf("VID:PID %04x:%04x", *vid, *pid) + default: + devName = fmt.Sprintf("bus:addr %d:%d", *bus, *addr) + } + log.Printf("Scanning for device %q...", devName) // ListDevices is used to find the devices to open. devs, err := ctx.ListDevices(func(desc *usb.Descriptor) bool { - if fmt.Sprintf("%s:%s", desc.Vendor, desc.Product) != *device { - return false + switch { + case usb.ID(*vid) == desc.Vendor && usb.ID(*pid) == desc.Product: + return true + case uint8(*bus) == desc.Bus && uint8(*addr) == desc.Address: + return true } - - // The usbid package can be used to print out human readable information. - fmt.Printf(" Protocol: %s\n", usbid.Classify(desc)) - - // The configurations can be examined from the Descriptor, though they can only - // be set once the device is opened. All configuration references must be closed, - // to free up the memory in libusb. - for _, cfg := range desc.Configs { - // This loop just uses more of the built-in and usbid pretty printing to list - // the USB devices. - fmt.Printf(" %s:\n", cfg) - for _, alt := range cfg.Interfaces { - fmt.Printf(" --------------\n") - for _, iface := range alt.Setups { - fmt.Printf(" %s\n", iface) - fmt.Printf(" %s\n", usbid.Classify(iface)) - for _, end := range iface.Endpoints { - fmt.Printf(" %s\n", end) - } - } - } - fmt.Printf(" --------------\n") - } - - return true + return false }) - // All Devices returned from ListDevices must be closed. defer func() { for _, d := range devs { @@ -84,22 +78,56 @@ func main() { } }() - // ListDevices can occaionally fail, so be sure to check its return value. + // ListDevices can occasionally fail, so be sure to check its return value. if err != nil { - log.Fatalf("list: %s", err) + log.Printf("Warning: ListDevices: %s.", err) } - - if len(devs) == 0 { - log.Fatalf("no devices found") + switch { + case len(devs) == 0: + log.Fatal("No matching devices found.") + case len(devs) > 1: + log.Printf("Warning: multiple devices found. Using bus %d, addr %d.", devs[0].Bus, devs[0].Address) + for _, d := range devs[1:] { + d.Close() + } + devs = devs[:1] } - dev := devs[0] + // The usbid package can be used to print out human readable information. + log.Printf(" Protocol: %s\n", usbid.Classify(dev.Descriptor)) + + // The configurations can be examined from the Descriptor, though they can only + // be set once the device is opened. All configuration references must be closed, + // to free up the memory in libusb. + for _, cfg := range dev.Configs { + // This loop just uses more of the built-in and usbid pretty printing to list + // the USB devices. + log.Printf(" %s:\n", cfg) + for _, alt := range cfg.Interfaces { + log.Printf(" --------------\n") + for _, iface := range alt.Setups { + log.Printf(" %s\n", iface) + log.Printf(" %s\n", usbid.Classify(iface)) + for _, end := range iface.Endpoints { + log.Printf(" %s\n", end) + } + } + } + log.Printf(" --------------\n") + } + log.Printf("Connecting to endpoint...") - log.Printf("- %#v", dev.Descriptor) ep, err := dev.OpenEndpoint(uint8(*config), uint8(*iface), uint8(*setup), uint8(*endpoint)|uint8(usb.ENDPOINT_DIR_IN)) if err != nil { log.Fatalf("open: %s", err) } - _ = ep + + buf := make([]byte, *size) + num, err := ep.Read(buf) + if err != nil { + log.Fatalf("Reading from device failed: %v", err) + } + log.Printf("Read %d bytes of data", num) + os.Stdout.Write(buf[:num]) } From a19ac0f65490cec94ecb9b15ed9a3eea020a9dcc Mon Sep 17 00:00:00 2001 From: Sebastian Zagrodzki Date: Wed, 8 Feb 2017 22:35:45 +0100 Subject: [PATCH 2/6] Per http://www.beyondlogic.org/usbnutshell/usb5.shtml#EndpointDescriptors: bits 0-3 are endpoint number, 4-6 reserved, set to zero, bit 7 is direction. Bits 0-3 is 0x0f. --- usb/constants.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/usb/constants.go b/usb/constants.go index b4dc590..88a28b7 100644 --- a/usb/constants.go +++ b/usb/constants.go @@ -87,7 +87,7 @@ func (dt DescriptorType) String() string { type EndpointDirection uint8 const ( - ENDPOINT_NUM_MASK = 0x03 + ENDPOINT_NUM_MASK = 0x0f ENDPOINT_DIR_IN EndpointDirection = C.LIBUSB_ENDPOINT_IN ENDPOINT_DIR_OUT EndpointDirection = C.LIBUSB_ENDPOINT_OUT ENDPOINT_DIR_MASK EndpointDirection = 0x80 From a35725f4fb2f9ed3dd3be290770f0e649937f7ed Mon Sep 17 00:00:00 2001 From: Sebastian Zagrodzki Date: Wed, 8 Feb 2017 22:37:12 +0100 Subject: [PATCH 3/6] When extracting transfer data, pay attention to the size of the buffer provided by the user, which may be smaller than the length of data received in the transfer. --- usb/iso.c | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/usb/iso.c b/usb/iso.c index 1204eb7..7cd4dd0 100644 --- a/usb/iso.c +++ b/usb/iso.c @@ -74,8 +74,8 @@ int extract_data(struct libusb_transfer *xfer, void *raw, int max, unsigned char // Copy the data int len = pkt.actual_length; - if (len > max) { - len = max; + if (copied + len > max) { + len = max - copied; } memcpy(out, in, len); copied += len; @@ -84,10 +84,14 @@ int extract_data(struct libusb_transfer *xfer, void *raw, int max, unsigned char in += pkt.length; out += len; + if (copied == max) { + break; + } + // Extract first error if (pkt.status == 0 || *status != 0) { continue; - } + } *status = pkt.status; } return copied; From c27a77b547eac14996ea51e7ee3ca3a8a57308ed Mon Sep 17 00:00:00 2001 From: Sebastian Zagrodzki Date: Wed, 8 Feb 2017 23:02:08 +0100 Subject: [PATCH 4/6] Pass the max buffer size down to the alloc_transfer. Use the number of iso packets matching the buffer size. This guarantees that the transfer size is smaller or equal to the buffer. Device will transfer less data if iso transfer response does not utilize the maximum available number of iso packets per microframe or doesn't fill the packets entirely. --- usb/iso.go | 25 ++++++++++++++++--------- 1 file changed, 16 insertions(+), 9 deletions(-) diff --git a/usb/iso.go b/usb/iso.go index 517a126..a62df3a 100644 --- a/usb/iso.go +++ b/usb/iso.go @@ -37,18 +37,25 @@ func iso_callback(cptr unsafe.Pointer) { close(ch) } -func (end *endpoint) allocTransfer() *Transfer { - const ( - iso_packets = 8 // 128 // 242 - ) - - xfer := C.libusb_alloc_transfer(C.int(iso_packets)) +func (end *endpoint) allocTransfer(maxLen int) *Transfer { + isoPacketSize := end.EndpointInfo.MaxIsoPacket + // the larget the input buffer, the more packets we use in a single + // transfer. + numIsoPackets := maxLen / int(isoPacketSize) + if numIsoPackets*int(isoPacketSize) < maxLen { + numIsoPackets++ + } + // arbitrary limit + if numIsoPackets > 200 { + numIsoPackets = 200 + } + xfer := C.libusb_alloc_transfer(C.int(numIsoPackets)) if xfer == nil { log.Printf("usb: transfer allocation failed?!") return nil } - buf := make([]byte, iso_packets*end.EndpointInfo.MaxIsoPacket) + buf := make([]byte, numIsoPackets*int(end.EndpointInfo.MaxIsoPacket)) done := make(chan struct{}, 1) xfer.dev_handle = end.Device.handle @@ -57,7 +64,7 @@ func (end *endpoint) allocTransfer() *Transfer { xfer.buffer = (*C.uchar)((unsafe.Pointer)(&buf[0])) xfer.length = C.int(len(buf)) - xfer.num_iso_packets = iso_packets + xfer.num_iso_packets = C.int(numIsoPackets) C.libusb_set_iso_packet_lengths(xfer, C.uint(end.EndpointInfo.MaxIsoPacket)) /* @@ -130,7 +137,7 @@ func (t *Transfer) Close() error { } func isochronous_xfer(e *endpoint, buf []byte, timeout time.Duration) (int, error) { - t := e.allocTransfer() + t := e.allocTransfer(len(buf)) defer t.Close() if err := t.Submit(timeout); err != nil { From 91119ca790e4691ef07c180ada441ae969495596 Mon Sep 17 00:00:00 2001 From: Sebastian Zagrodzki Date: Wed, 8 Feb 2017 23:28:52 +0100 Subject: [PATCH 5/6] Add a benchmark option. --- rawread/main.go | 43 +++++++++++++++++++++++++++++++++++-------- 1 file changed, 35 insertions(+), 8 deletions(-) diff --git a/rawread/main.go b/rawread/main.go index 55ab813..4e56e1e 100644 --- a/rawread/main.go +++ b/rawread/main.go @@ -21,6 +21,8 @@ import ( "fmt" "log" "os" + "sync/atomic" + "time" "github.com/kylelemons/gousb/usb" "github.com/kylelemons/gousb/usbid" @@ -36,7 +38,8 @@ var ( setup = flag.Uint("setup", 0, "Endpoint to which to connect") endpoint = flag.Uint("endpoint", 1, "Endpoint to which to connect") debug = flag.Int("debug", 3, "Debug level for libusb") - size = flag.Uint("read_size", 1024, "Maximum number of bytes of data to read. Collected will be printed to STDOUT.") + size = flag.Uint("read_size", 1024, "Number of bytes of data to read in a single transaction.") + bench = flag.Bool("benchmark", false, "When true, keep reading from the endpoint and periodically report the measured throughput. If false, only one read operation is performed and obtained data is printed to STDOUT.") ) func main() { @@ -117,17 +120,41 @@ func main() { log.Printf(" --------------\n") } - log.Printf("Connecting to endpoint...") + log.Print("Connecting to endpoint...") ep, err := dev.OpenEndpoint(uint8(*config), uint8(*iface), uint8(*setup), uint8(*endpoint)|uint8(usb.ENDPOINT_DIR_IN)) if err != nil { log.Fatalf("open: %s", err) } + log.Print("Reading...") - buf := make([]byte, *size) - num, err := ep.Read(buf) - if err != nil { - log.Fatalf("Reading from device failed: %v", err) + var total uint64 + if *bench { + go func() { + var last uint64 + var before = time.Now() + for { + time.Sleep(4 * time.Second) + cur := atomic.LoadUint64(&total) + now := time.Now() + dur := now.Sub(before) + log.Printf("%.2f B/s\n", float64(cur-last)/(float64(dur)/float64(time.Second))) + before = now + last = cur + } + }() + } + + for { + buf := make([]byte, *size) + num, err := ep.Read(buf) + if err != nil { + log.Fatalf("Reading from device failed: %v", err) + } + if !*bench { + log.Printf("Read %d bytes of data", num) + os.Stdout.Write(buf[:num]) + return + } + atomic.AddUint64(&total, uint64(num)) } - log.Printf("Read %d bytes of data", num) - os.Stdout.Write(buf[:num]) } From 66a1f45cd9151eef375c9af6d40f20eafbd8ec8d Mon Sep 17 00:00:00 2001 From: Sebastian Zagrodzki Date: Wed, 8 Feb 2017 23:29:17 +0100 Subject: [PATCH 6/6] Remove the artificial limit. The worst case is a longer timeout for the transfer is required. --- usb/iso.go | 4 ---- 1 file changed, 4 deletions(-) diff --git a/usb/iso.go b/usb/iso.go index a62df3a..20a6da6 100644 --- a/usb/iso.go +++ b/usb/iso.go @@ -45,10 +45,6 @@ func (end *endpoint) allocTransfer(maxLen int) *Transfer { if numIsoPackets*int(isoPacketSize) < maxLen { numIsoPackets++ } - // arbitrary limit - if numIsoPackets > 200 { - numIsoPackets = 200 - } xfer := C.libusb_alloc_transfer(C.int(numIsoPackets)) if xfer == nil { log.Printf("usb: transfer allocation failed?!")