From 1755936932ccfbc03ab045b29b386eafcab1599e Mon Sep 17 00:00:00 2001 From: Sebastian Zagrodzki Date: Wed, 8 Feb 2017 22:30:03 +0100 Subject: [PATCH 1/8] 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/8] 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/8] 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/8] 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/8] 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/8] 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?!") From 66db4a686ba47e71cf1ecba3e9c5ce0f22adeb9a Mon Sep 17 00:00:00 2001 From: Sebastian Zagrodzki Date: Fri, 10 Feb 2017 12:48:44 +0100 Subject: [PATCH 7/8] Merge flags vid/pid and bus/addr into single flags, vidpid and busaddr. --- rawread/main.go | 68 +++++++++++++++++++++++++++++++++++++++---------- 1 file changed, 55 insertions(+), 13 deletions(-) diff --git a/rawread/main.go b/rawread/main.go index 4e56e1e..b885294 100644 --- a/rawread/main.go +++ b/rawread/main.go @@ -21,6 +21,8 @@ import ( "fmt" "log" "os" + "strconv" + "strings" "sync/atomic" "time" @@ -29,10 +31,8 @@ import ( ) var ( - 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.") + vidPID = flag.String("vidpid", "", "VID:PID of the device to which to connect. Exclusive with busaddr flag.") + busAddr = flag.String("busaddr", "", "Bus:address of the device to which to connect. Exclusive with vidpid flag.") 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") @@ -42,6 +42,38 @@ var ( 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 parseVIDPID(vidPid string) (usb.ID, usb.ID, error) { + s := strings.Split(vidPid, ":") + if len(s) != 2 { + return 0, 0, fmt.Errorf("want VID:PID, two 32-bit hex numbers separated by colon, e.g. 1d6b:0002") + } + vid, err := strconv.ParseUint(s[0], 16, 32) + if err != nil { + return 0, 0, fmt.Errorf("VID must be a hexadecimal 32-bit number, e.g. 1d6b") + } + pid, err := strconv.ParseUint(s[1], 16, 32) + if err != nil { + return 0, 0, fmt.Errorf("PID must be a hexadecimal 32-bit number, e.g. 1d6b") + } + return usb.ID(vid), usb.ID(pid), nil +} + +func parseBusAddr(busAddr string) (uint8, uint8, error) { + s := strings.Split(busAddr, ":") + if len(s) != 2 { + return 0, 0, fmt.Errorf("want bus:addr, two 8-bit decimal unsigned integers separated by colon, e.g. 1:1") + } + bus, err := strconv.ParseUint(s[0], 10, 8) + if err != nil { + return 0, 0, fmt.Errorf("bus number must be an 8-bit decimal unsigned integer") + } + addr, err := strconv.ParseUint(s[1], 10, 8) + if err != nil { + return 0, 0, fmt.Errorf("device address must be an 8-bit decimal unsigned integer") + } + return uint8(bus), uint8(addr), nil +} + func main() { flag.Parse() @@ -52,24 +84,34 @@ func main() { ctx.Debug(*debug) var devName string + var vid, pid usb.ID + var bus, addr uint8 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) + case *vidPID == "" && *busAddr == "": + log.Fatal("You need to specify the device through a --vidpid flag or through a --busaddr flag.") + case *vidPID != "" && *busAddr != "": + log.Fatal("You can't use --vidpid flag together with --busaddr. Pick one.") + case *vidPID != "": + vid, pid, err := parseVIDPID(*vidPID) + if err != nil { + log.Fatalf("Invalid value for --vidpid (%q): %v", *vidPID, err) + } + devName = fmt.Sprintf("VID:PID %s:%s", vid, pid) default: - devName = fmt.Sprintf("bus:addr %d:%d", *bus, *addr) + bus, addr, err := parseBusAddr(*busAddr) + if err != nil { + log.Fatalf("Invalid value for --busaddr (%q): %v", *busAddr, err) + } + 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 { switch { - case usb.ID(*vid) == desc.Vendor && usb.ID(*pid) == desc.Product: + case vid == desc.Vendor && pid == desc.Product: return true - case uint8(*bus) == desc.Bus && uint8(*addr) == desc.Address: + case bus == desc.Bus && addr == desc.Address: return true } return false From 0588c4e512141ffa7fc46976e4781928db4cdcbd Mon Sep 17 00:00:00 2001 From: Sebastian Zagrodzki Date: Fri, 10 Feb 2017 13:02:48 +0100 Subject: [PATCH 8/8] Remove the bench option, add num_reads instead for controlling how many reads to send. Default is read forever. --- rawread/main.go | 40 +++++++++------------------------------- 1 file changed, 9 insertions(+), 31 deletions(-) diff --git a/rawread/main.go b/rawread/main.go index b885294..c389c4f 100644 --- a/rawread/main.go +++ b/rawread/main.go @@ -23,8 +23,6 @@ import ( "os" "strconv" "strings" - "sync/atomic" - "time" "github.com/kylelemons/gousb/usb" "github.com/kylelemons/gousb/usbid" @@ -39,7 +37,7 @@ var ( 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, "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.") + num = flag.Uint("read_num", 0, "Number of read transactions to perform. 0 means infinite.") ) func parseVIDPID(vidPid string) (usb.ID, usb.ID, error) { @@ -92,13 +90,15 @@ func main() { case *vidPID != "" && *busAddr != "": log.Fatal("You can't use --vidpid flag together with --busaddr. Pick one.") case *vidPID != "": - vid, pid, err := parseVIDPID(*vidPID) + var err error + vid, pid, err = parseVIDPID(*vidPID) if err != nil { log.Fatalf("Invalid value for --vidpid (%q): %v", *vidPID, err) } devName = fmt.Sprintf("VID:PID %s:%s", vid, pid) default: - bus, addr, err := parseBusAddr(*busAddr) + var err error + bus, addr, err = parseBusAddr(*busAddr) if err != nil { log.Fatalf("Invalid value for --busaddr (%q): %v", *busAddr, err) } @@ -162,41 +162,19 @@ func main() { log.Printf(" --------------\n") } - log.Print("Connecting to endpoint...") + log.Printf("Connecting to endpoint %d...", *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...") - 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) + buf := make([]byte, *size) + for i := uint(0); *num == 0 || i < *num; i++ { 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)) + os.Stdout.Write(buf[:num]) } }