diff --git a/lsusb/main.go b/lsusb/main.go index dc30939..bafb862 100644 --- a/lsusb/main.go +++ b/lsusb/main.go @@ -26,7 +26,7 @@ func main() { // ListDevices is used to find the devices to open. devs, err := ctx.ListDevices(func(desc *usb.Descriptor) bool { // The usbid package can be used to print out human readable information. - fmt.Printf("%03d.%03d %s\n", desc.Bus, desc.Address, usbid.Describe(desc)) + fmt.Printf("%03d.%03d %s:%s %s\n", desc.Bus, desc.Address, desc.Vendor, desc.Product, usbid.Describe(desc)) fmt.Printf(" Protocol: %s\n", usbid.Classify(desc)) // The configurations can be examined from the Descriptor, though they can only diff --git a/rawread/main.go b/rawread/main.go new file mode 100644 index 0000000..9fb041a --- /dev/null +++ b/rawread/main.go @@ -0,0 +1,89 @@ +package main + +import ( + "flag" + "fmt" + "log" + + "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") + debug = flag.Int("debug", 3, "Debug level for libusb") +) + +func main() { + flag.Parse() + + // Only one context should be needed for an application. It should always be closed. + ctx := usb.NewContext() + defer ctx.Close() + + ctx.Debug(*debug) + + log.Printf("Scanning for device %q...", *device) + + // 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 + } + + // 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 + }) + + // All Devices returned from ListDevices must be closed. + defer func() { + for _, d := range devs { + d.Close() + } + }() + + // ListDevices can occaionally fail, so be sure to check its return value. + if err != nil { + log.Fatalf("list: %s", err) + } + + if len(devs) == 0 { + log.Fatalf("no devices found") + } + + dev := devs[0] + + 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 +} diff --git a/rawread/rawread b/rawread/rawread new file mode 100755 index 0000000..4639c44 Binary files /dev/null and b/rawread/rawread differ diff --git a/usb/config.go b/usb/config.go index e0e37d4..c519e38 100644 --- a/usb/config.go +++ b/usb/config.go @@ -1,6 +1,5 @@ package usb -// #cgo LDFLAGS: -lusb-1.0 // #include import "C" diff --git a/usb/constants.go b/usb/constants.go index e64e00c..b83c2d3 100644 --- a/usb/constants.go +++ b/usb/constants.go @@ -1,6 +1,5 @@ package usb -// #cgo LDFLAGS: -lusb-1.0 // #include import "C" diff --git a/usb/descriptor.go b/usb/descriptor.go index 4a196b7..dbab6f1 100644 --- a/usb/descriptor.go +++ b/usb/descriptor.go @@ -1,6 +1,5 @@ package usb -// #cgo LDFLAGS: -lusb-1.0 // #include import "C" diff --git a/usb/device.go b/usb/device.go index c28c0d1..366ab7e 100644 --- a/usb/device.go +++ b/usb/device.go @@ -1,177 +1,9 @@ package usb -// #cgo LDFLAGS: -lusb-1.0 // #include import "C" import ( - "fmt" - "reflect" - "runtime" - "sync" - "time" - "unsafe" ) -var DefaultReadTimeout = 1 * time.Second -var DefaultWriteTimeout = 1 * time.Second -var DefaultControlTimeout = 5 * time.Second -type Device struct { - handle *C.libusb_device_handle - - // Embed the device information for easy access - *Descriptor - - // Timeouts - ReadTimeout time.Duration - WriteTimeout time.Duration - ControlTimeout time.Duration - - // Claimed interfaces - lock *sync.Mutex - claimed map[uint8]int -} - -func newDevice(handle *C.libusb_device_handle, desc *Descriptor) *Device { - ifaces := 0 - d := &Device{ - handle: handle, - Descriptor: desc, - ReadTimeout: DefaultReadTimeout, - WriteTimeout: DefaultWriteTimeout, - ControlTimeout: DefaultControlTimeout, - lock: new(sync.Mutex), - claimed: make(map[uint8]int, ifaces), - } - - // This doesn't seem to actually get called - runtime.SetFinalizer(d, (*Device).Close) - - return d -} - -func (d *Device) Reset() error { - if errno := C.libusb_reset_device(d.handle); errno != 0 { - return usbError(errno) - } - return nil -} - -func (d *Device) Control(rType, request uint8, val, idx uint16, data []byte) (int, error) { - 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 -} - -// 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 -} - -// 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 -} - -// Close the device. -func (d *Device) Close() error { - if d.handle == nil { - return fmt.Errorf("usb: double close on device") - } - d.lock.Lock() - defer d.lock.Unlock() - for iface := range d.claimed { - C.libusb_release_interface(d.handle, C.int(iface)) - } - C.libusb_close(d.handle) - d.handle = nil - return nil -} - -func (d *Device) OpenEndpoint(conf, iface, setup, epoint uint8) (Endpoint, error) { - end := &endpoint{ - Device: d, - } - - for _, c := range d.Configs { - if c.Config != conf { - continue - } - for _, i := range c.Interfaces { - if i.Number != iface { - continue - } - for _, s := range i.Setups { - if s.Alternate != setup { - continue - } - for _, e := range s.Endpoints { - if e.Address != epoint { - continue - } - end.InterfaceSetup = s - end.EndpointInfo = e - switch tt := TransferType(e.Attributes) & TRANSFER_TYPE_MASK; tt { - case TRANSFER_TYPE_BULK: - end.xfer = bulk_xfer - case TRANSFER_TYPE_INTERRUPT: - end.xfer = interrupt_xfer - default: - return nil, fmt.Errorf("usb: %s transfer is unsupported", tt) - } - 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) - - found: - - // 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)) - } - - // Increment the claim count - d.lock.Lock() - d.claimed[iface]++ - d.lock.Unlock() // unlock immediately because the next calls may block - - // Set the configuration - if errno := C.libusb_set_configuration(d.handle, C.int(conf)); errno < 0 { - return nil, fmt.Errorf("usb: setcfg: %s", usbError(errno)) - } - - // Choose the alternate - if errno := C.libusb_set_interface_alt_setting(d.handle, C.int(iface), C.int(setup)); errno < 0 { - return nil, fmt.Errorf("usb: setalt: %s", usbError(errno)) - } - - return end, nil -} diff --git a/usb/endpoint.go b/usb/endpoint.go new file mode 100644 index 0000000..b8a15d0 --- /dev/null +++ b/usb/endpoint.go @@ -0,0 +1,86 @@ +package usb + +// #include +import "C" + +import ( + "fmt" + "reflect" + "unsafe" + "time" +) + +type Endpoint interface { + Read(b []byte) (int, error) + Write(b []byte) (int, error) + Interface() InterfaceSetup + Info() EndpointInfo +} + +type endpoint struct { + *Device + InterfaceSetup + EndpointInfo + xfer func(*endpoint, []byte, time.Duration) (int, error) +} + +func (e *endpoint) Read(buf []byte) (int, error) { + if EndpointDirection(e.Address) & ENDPOINT_DIR_MASK != ENDPOINT_DIR_OUT { + return 0, fmt.Errorf("usb: read: not an IN endpoint") + } + + return e.xfer(e, buf, e.ReadTimeout) +} + +func (e *endpoint) Write(buf []byte) (int, error) { + if EndpointDirection(e.Address) & ENDPOINT_DIR_MASK != ENDPOINT_DIR_OUT { + return 0, fmt.Errorf("usb: write: not an OUT endpoint") + } + + return e.xfer(e, buf, e.WriteTimeout) +} + +func (e *endpoint) Interface() InterfaceSetup { return InterfaceSetup{} } +func (e *endpoint) Info() EndpointInfo { return EndpointInfo{} } + +// TODO(kevlar): (*Endpoint).Close + +func bulk_xfer(e *endpoint, buf []byte, timeout time.Duration) (int, error) { + if len(buf) == 0 { + return 0, nil + } + + data := (*reflect.SliceHeader)(unsafe.Pointer(&buf)).Data + + var cnt C.int + if errno := C.libusb_bulk_transfer( + e.handle, + C.uchar(e.Address), + (*C.uchar)(unsafe.Pointer(data)), + C.int(len(buf)), + &cnt, + C.uint(timeout/time.Millisecond)); errno < 0 { + return 0, usbError(errno) + } + return int(cnt), nil +} + +func interrupt_xfer(e *endpoint, buf []byte, timeout time.Duration) (int, error) { + if len(buf) == 0 { + return 0, nil + } + + data := (*reflect.SliceHeader)(unsafe.Pointer(&buf)).Data + + var cnt C.int + if errno := C.libusb_interrupt_transfer( + e.handle, + C.uchar(e.Address), + (*C.uchar)(unsafe.Pointer(data)), + C.int(len(buf)), + &cnt, + C.uint(timeout/time.Millisecond)); errno < 0 { + return 0, usbError(errno) + } + return int(cnt), nil +} diff --git a/usb/error.go b/usb/error.go index 37c81b7..6418dd0 100644 --- a/usb/error.go +++ b/usb/error.go @@ -1,6 +1,5 @@ package usb -// #cgo LDFLAGS: -lusb-1.0 // #include import "C" diff --git a/usb/iso.go b/usb/iso.go new file mode 100644 index 0000000..c309d72 --- /dev/null +++ b/usb/iso.go @@ -0,0 +1,182 @@ +package usb + +// #include +import "C" + +import ( + "fmt" + "reflect" + "runtime" + "sync" + "time" + "unsafe" +) + +var DefaultReadTimeout = 1 * time.Second +var DefaultWriteTimeout = 1 * time.Second +var DefaultControlTimeout = 5 * time.Second + +type Device struct { + handle *C.libusb_device_handle + + // Embed the device information for easy access + *Descriptor + + // Timeouts + ReadTimeout time.Duration + WriteTimeout time.Duration + ControlTimeout time.Duration + + // Claimed interfaces + lock *sync.Mutex + claimed map[uint8]int +} + +func newDevice(handle *C.libusb_device_handle, desc *Descriptor) *Device { + ifaces := 0 + d := &Device{ + handle: handle, + Descriptor: desc, + ReadTimeout: DefaultReadTimeout, + WriteTimeout: DefaultWriteTimeout, + ControlTimeout: DefaultControlTimeout, + lock: new(sync.Mutex), + claimed: make(map[uint8]int, ifaces), + } + + // This doesn't seem to actually get called + runtime.SetFinalizer(d, (*Device).Close) + + return d +} + +func (d *Device) Reset() error { + if errno := C.libusb_reset_device(d.handle); errno != 0 { + return usbError(errno) + } + return nil +} + +func (d *Device) Control(rType, request uint8, val, idx uint16, data []byte) (int, error) { + 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 +} + +// 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 +} + +// 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 +} + +// Close the device. +func (d *Device) Close() error { + if d.handle == nil { + return fmt.Errorf("usb: double close on device") + } + d.lock.Lock() + defer d.lock.Unlock() + for iface := range d.claimed { + C.libusb_release_interface(d.handle, C.int(iface)) + } + C.libusb_close(d.handle) + d.handle = nil + return nil +} + +func (d *Device) OpenEndpoint(conf, iface, setup, epoint uint8) (Endpoint, error) { + end := &endpoint{ + Device: d, + } + + for _, c := range d.Configs { + if c.Config != conf { + continue + } + fmt.Printf("found conf: %#v\n", c) + for _, i := range c.Interfaces { + if i.Number != iface { + continue + } + fmt.Printf("found iface: %#v\n", i) + for _, s := range i.Setups { + if s.Alternate != setup { + continue + } + fmt.Printf("found setup: %#v\n", s) + for _, e := range s.Endpoints { + fmt.Printf("ep %02x search: %#v\n", epoint, s) + if e.Address != epoint { + continue + } + end.InterfaceSetup = s + end.EndpointInfo = e + switch tt := TransferType(e.Attributes) & TRANSFER_TYPE_MASK; tt { + case TRANSFER_TYPE_BULK: + end.xfer = bulk_xfer + case TRANSFER_TYPE_INTERRUPT: + end.xfer = interrupt_xfer + default: + return nil, fmt.Errorf("usb: %s transfer is unsupported", tt) + } + 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) + + found: + + // 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)) + } + + // Increment the claim count + d.lock.Lock() + d.claimed[iface]++ + d.lock.Unlock() // unlock immediately because the next calls may block + + // Set the configuration + if errno := C.libusb_set_configuration(d.handle, C.int(conf)); errno < 0 { + return nil, fmt.Errorf("usb: setcfg: %s", usbError(errno)) + } + + // Choose the alternate + /* This doesn't seem to work... + if errno := C.libusb_set_interface_alt_setting(d.handle, C.int(iface), C.int(setup)); errno < 0 { + return nil, fmt.Errorf("usb: setalt: %s", usbError(errno)) + } + */ + + return end, nil +} diff --git a/usbid/describe.go b/usbid/describe.go index 1bb20e6..28e6490 100644 --- a/usbid/describe.go +++ b/usbid/describe.go @@ -39,13 +39,13 @@ func Describe(val interface{}) string { // // The given val must be one of the following: // - *usb.Descriptor "Class (SubClass) Protocol" -// - *usb.InterfaceSetup "IfClass (IfSubClass) IfProtocol" +// - usb.InterfaceSetup "IfClass (IfSubClass) IfProtocol" func Classify(val interface{}) string { var class, sub, proto uint8 switch val := val.(type) { case *usb.Descriptor: class, sub, proto = val.Class, val.SubClass, val.Protocol - case *usb.InterfaceSetup: + case usb.InterfaceSetup: class, sub, proto = val.IfClass, val.IfSubClass, val.IfProtocol default: return fmt.Sprintf("Unknown (%T)", val)