diff --git a/usb/config.go b/usb/config.go index de3e12b..cdb1b18 100644 --- a/usb/config.go +++ b/usb/config.go @@ -5,6 +5,7 @@ package usb import "C" import ( + "fmt" "log" "reflect" "runtime" @@ -13,38 +14,64 @@ import ( type Endpoint struct { Type DescriptorType - Address ID + Address uint8 + Attributes uint8 MaxPacketSize uint16 PollInterval uint8 RefreshRate uint8 - SynchAddress ID + SynchAddress uint8 +} + +func (e Endpoint) Number() int { + return int(e.Address) & ENDPOINT_NUM_MASK +} + +func (e Endpoint) Direction() EndpointDirection { + return EndpointDirection(e.Address) & ENDPOINT_DIR_MASK +} + +func (e Endpoint) String() string { + return fmt.Sprintf("Endpoint %d %-3s %s - %s %s", + e.Number(), e.Direction(), + TransferType(e.Attributes) & TRANSFER_TYPE_MASK, + IsoSyncType(e.Attributes) & ISO_SYNC_TYPE_MASK, + IsoUsageType(e.Attributes) & ISO_USAGE_TYPE_MASK, + ) } type Interface struct { Type DescriptorType - Number ID - Alternate ID - IfClass Class + Number uint8 + Alternate uint8 + IfClass uint8 IfSubClass uint8 IfProtocol uint8 Endpoints []*Endpoint } +func (i Interface) String() string { + return fmt.Sprintf("Interface %02x (config %02x)", i.Number, i.Alternate) +} + type Config struct { cfg *C.struct_libusb_config_descriptor Type DescriptorType - Config ID + Config uint8 Attributes uint8 MaxPower uint8 Interfaces [][]*Interface } +func (c Config) String() string { + return fmt.Sprintf("Config %02x", c.Config) +} + func newConfig(cfg *C.struct_libusb_config_descriptor) *Config { c := &Config{ cfg: cfg, Type: DescriptorType(cfg.bDescriptorType), - Config: ID(cfg.bConfigurationValue), + Config: uint8(cfg.bConfigurationValue), Attributes: uint8(cfg.bmAttributes), MaxPower: uint8(cfg.MaxPower), } @@ -67,9 +94,9 @@ func newConfig(cfg *C.struct_libusb_config_descriptor) *Config { for _, alt := range alts { i := &Interface{ Type: DescriptorType(alt.bDescriptorType), - Number: ID(alt.bInterfaceNumber), - Alternate: ID(alt.bAlternateSetting), - IfClass: Class(alt.bInterfaceClass), + Number: uint8(alt.bInterfaceNumber), + Alternate: uint8(alt.bAlternateSetting), + IfClass: uint8(alt.bInterfaceClass), IfSubClass: uint8(alt.bInterfaceSubClass), IfProtocol: uint8(alt.bInterfaceProtocol), } @@ -83,11 +110,12 @@ func newConfig(cfg *C.struct_libusb_config_descriptor) *Config { for _, end := range ends { i.Endpoints = append(i.Endpoints, &Endpoint{ Type: DescriptorType(end.bDescriptorType), - Address: ID(end.bEndpointAddress), + Address: uint8(end.bEndpointAddress), + Attributes: uint8(end.bmAttributes), MaxPacketSize: uint16(end.wMaxPacketSize), PollInterval: uint8(end.bInterval), RefreshRate: uint8(end.bRefresh), - SynchAddress: ID(end.bSynchAddress), + SynchAddress: uint8(end.bSynchAddress), }) } descs = append(descs, i) diff --git a/usb/descriptor.go b/usb/descriptor.go index d5abf02..657d246 100644 --- a/usb/descriptor.go +++ b/usb/descriptor.go @@ -9,9 +9,9 @@ type Descriptor struct { Type DescriptorType // The type of this descriptor Spec BCD // USB Specification Release Number - Class Class // The class of this device - SubClass uint8 // The sub-class (within the class) of this device - Protocol uint8 // The protocol (within the sub-class) of this device + Class uint8 // The class of this device + SubClass uint8 // The sub-class (within the class) of this device + Protocol uint8 // The protocol (within the sub-class) of this device Vendor ID // The 8-bit Vendor identifer Product ID // The 8-bit Product identifier Device BCD // The device version @@ -26,7 +26,7 @@ func newDescriptor(dev *C.libusb_device) (*Descriptor, error) { desc: &desc, Type: DescriptorType(desc.bDescriptorType), Spec: BCD(desc.bcdUSB), - Class: Class(desc.bDeviceClass), + Class: uint8(desc.bDeviceClass), SubClass: uint8(desc.bDeviceSubClass), Protocol: uint8(desc.bDeviceProtocol), Vendor: ID(desc.idVendor), @@ -147,13 +147,15 @@ func (dt DescriptorType) String() string { type EndpointDirection int const ( - ENDPOINT_IN EndpointDirection = C.LIBUSB_ENDPOINT_IN - ENDPOINT_OUT EndpointDirection = C.LIBUSB_ENDPOINT_OUT + ENDPOINT_NUM_MASK = 0x03 + ENDPOINT_DIR_IN EndpointDirection = C.LIBUSB_ENDPOINT_IN + ENDPOINT_DIR_OUT EndpointDirection = C.LIBUSB_ENDPOINT_OUT + ENDPOINT_DIR_MASK EndpointDirection = 0x80 ) var endpointDirectionDescription = map[EndpointDirection]string{ - ENDPOINT_IN: "device-to-host", - ENDPOINT_OUT: "host-to-device", + ENDPOINT_DIR_IN: "IN", + ENDPOINT_DIR_OUT: "OUT", } func (ed EndpointDirection) String() string { @@ -167,6 +169,7 @@ const ( TRANSFER_TYPE_ISOCHRONOUS TransferType = C.LIBUSB_TRANSFER_TYPE_ISOCHRONOUS TRANSFER_TYPE_BULK TransferType = C.LIBUSB_TRANSFER_TYPE_BULK TRANSFER_TYPE_INTERRUPT TransferType = C.LIBUSB_TRANSFER_TYPE_INTERRUPT + TRANSFER_TYPE_MASK TransferType = 0x03 ) var transferTypeDescription = map[TransferType]string{ @@ -183,14 +186,15 @@ func (tt TransferType) String() string { type IsoSyncType int const ( - ISO_SYNC_TYPE_NONE IsoSyncType = C.LIBUSB_ISO_SYNC_TYPE_NONE - ISO_SYNC_TYPE_ASYNC IsoSyncType = C.LIBUSB_ISO_SYNC_TYPE_ASYNC - ISO_SYNC_TYPE_ADAPTIVE IsoSyncType = C.LIBUSB_ISO_SYNC_TYPE_ADAPTIVE - ISO_SYNC_TYPE_SYNC IsoSyncType = C.LIBUSB_ISO_SYNC_TYPE_SYNC + ISO_SYNC_TYPE_NONE IsoSyncType = C.LIBUSB_ISO_SYNC_TYPE_NONE << 2 + ISO_SYNC_TYPE_ASYNC IsoSyncType = C.LIBUSB_ISO_SYNC_TYPE_ASYNC << 2 + ISO_SYNC_TYPE_ADAPTIVE IsoSyncType = C.LIBUSB_ISO_SYNC_TYPE_ADAPTIVE << 2 + ISO_SYNC_TYPE_SYNC IsoSyncType = C.LIBUSB_ISO_SYNC_TYPE_SYNC << 2 + ISO_SYNC_TYPE_MASK IsoSyncType = 0x0C ) var isoSyncTypeDescription = map[IsoSyncType]string{ - ISO_SYNC_TYPE_NONE: "no synchronization", + ISO_SYNC_TYPE_NONE: "unsynchronized", ISO_SYNC_TYPE_ASYNC: "asynchronous", ISO_SYNC_TYPE_ADAPTIVE: "adaptive", ISO_SYNC_TYPE_SYNC: "synchronous", @@ -203,9 +207,10 @@ func (ist IsoSyncType) String() string { type IsoUsageType int const ( - ISO_USAGE_TYPE_DATA IsoUsageType = C.LIBUSB_ISO_USAGE_TYPE_DATA - ISO_USAGE_TYPE_FEEDBACK IsoUsageType = C.LIBUSB_ISO_USAGE_TYPE_FEEDBACK - ISO_USAGE_TYPE_IMPLICIT IsoUsageType = C.LIBUSB_ISO_USAGE_TYPE_IMPLICIT + ISO_USAGE_TYPE_DATA IsoUsageType = C.LIBUSB_ISO_USAGE_TYPE_DATA << 4 + ISO_USAGE_TYPE_FEEDBACK IsoUsageType = C.LIBUSB_ISO_USAGE_TYPE_FEEDBACK << 4 + ISO_USAGE_TYPE_IMPLICIT IsoUsageType = C.LIBUSB_ISO_USAGE_TYPE_IMPLICIT << 4 + ISO_USAGE_TYPE_MASK IsoUsageType = 0x30 ) var isoUsageTypeDescription = map[IsoUsageType]string{ diff --git a/usb/device.go b/usb/device.go index 5d87a5c..89e4833 100644 --- a/usb/device.go +++ b/usb/device.go @@ -8,6 +8,7 @@ import ( "log" "reflect" "runtime" + "time" "unsafe" ) @@ -89,6 +90,7 @@ func (d *Device) Reset() error { return nil } +var ControlTimeout = 5*time.Second 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( @@ -99,13 +101,28 @@ func (d *Device) Control(rType, request uint8, val, idx uint16, data []byte) (in C.uint16_t(idx), (*C.uchar)(unsafe.Pointer(dataSlice.Data)), C.uint16_t(len(data)), - 0) + C.uint(ControlTimeout/time.Millisecond)) if n < 0 { return int(n), usbError(n) } return int(n), nil } +func (d *Device) ActiveConfig() (int, error) { + var cfg C.int + if errno := C.libusb_get_configuration(d.handle, &cfg); errno < 0 { + return 0, usbError(errno) + } + return int(cfg), nil +} + +func (d *Device) SetConfig(cfg int) error { + if errno := C.libusb_set_configuration(d.handle, C.int(cfg)); errno < 0 { + return usbError(errno) + } + return nil +} + func (d *Device) Close() error { if d.handle != nil { log.Printf("device %p closed", d.handle) diff --git a/usb/usb_test.go b/usb/usb_test.go index 10c8fd0..e36863b 100644 --- a/usb/usb_test.go +++ b/usb/usb_test.go @@ -45,14 +45,8 @@ func TestEnum(t *testing.T) { bus := dev.BusNumber() addr := dev.Address() - t.Logf("%03d:%03d %+v", bus, addr, desc) - if v, ok := usbid.Vendors[desc.Vendor]; ok { - if p, ok := v.Devices[desc.Product]; ok { - t.Logf(" - %s (%s) %s (%s)", v, desc.Vendor, p, desc.Product) - } else { - t.Logf(" - %s (%s) Unknown", v, desc.Vendor) - } - } + t.Logf("%03d:%03d %s", bus, addr, usbid.Describe(desc)) + t.Logf("- Protocol: %s", usbid.Classify(desc)) cfgs, err := dev.Configurations() defer func() { @@ -61,21 +55,23 @@ func TestEnum(t *testing.T) { } }() if err != nil { - t.Errorf(" - configs: %s", err) + t.Errorf(" - configs: %s", err) continue } for _, cfg := range cfgs { - t.Logf(" - %#v", cfg) + t.Logf("- %s:", cfg) for _, alt := range cfg.Interfaces { + t.Logf(" --------------") for _, iface := range alt { - t.Logf(" - %#v", iface) + t.Logf(" - %s", iface) + t.Logf(" - %s", usbid.Classify(iface)) for _, end := range iface.Endpoints { - t.Logf(" - %#v", end) + t.Logf(" - %s", end) } } - t.Logf(" -----") } + t.Logf(" --------------") } } } diff --git a/usbid/describe.go b/usbid/describe.go new file mode 100644 index 0000000..26f0b22 --- /dev/null +++ b/usbid/describe.go @@ -0,0 +1,48 @@ +package usbid + +import ( + "fmt" + + "github.com/kylelemons/gousb/usb" +) + +// Describe one of the following: +// - *usb.Descriptor Product (Vendor) +func Describe(val interface{}) string { + switch val := val.(type) { + case *usb.Descriptor: + if v, ok := Vendors[val.Vendor]; ok { + if d, ok := v.Product[val.Product]; ok { + return fmt.Sprintf("%s (%s)", d, v) + } + return fmt.Sprintf("Unknown (%s)", v) + } + return fmt.Sprintf("Unknown %s:%s", val.Vendor, val.Product) + } + return fmt.Sprintf("Unknown (%T)", val) +} + +// Classify one of the following: +// - *usb.Descriptor Class SubClass Protocol +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.Interface: + class, sub, proto = val.IfClass, val.IfSubClass, val.IfProtocol + default: + return fmt.Sprintf("Unknown (%T)", val) + } + + if c, ok := Classes[class]; ok { + if s, ok := c.SubClass[sub]; ok { + if p, ok := s.Protocol[proto]; ok { + return fmt.Sprintf("%s (%s) %s", c, s, p) + } + return fmt.Sprintf("%s (%s)", c, s) + } + return fmt.Sprintf("%s", c) + } + return fmt.Sprintf("Unknown %s.%s.%s", class, sub, proto) +} diff --git a/usbid/load.go b/usbid/load.go index 719462a..f7c59d9 100644 --- a/usbid/load.go +++ b/usbid/load.go @@ -13,6 +13,7 @@ const ( ) var Vendors map[usb.ID]*Vendor +var Classes map[uint8]*Class func LoadFromURL(url string) error { resp, err := http.Get(url) @@ -21,21 +22,23 @@ func LoadFromURL(url string) error { } defer resp.Body.Close() - ids, err := ParseIDs(resp.Body) + ids, cls, err := ParseIDs(resp.Body) if err != nil { return err } Vendors = ids + Classes = cls return nil } func init() { - ids, err := ParseIDs(strings.NewReader(usbIdListData)) + ids, cls, err := ParseIDs(strings.NewReader(usbIdListData)) if err != nil { log.Printf("usbid: failed to parse: %s", err) return } Vendors = ids + Classes = cls } diff --git a/usbid/parse.go b/usbid/parse.go index a8c1ab2..83b1403 100644 --- a/usbid/parse.go +++ b/usbid/parse.go @@ -12,39 +12,45 @@ import ( type Vendor struct { Name string - Devices map[usb.ID]*Device + Product map[usb.ID]*Product } func (v Vendor) String() string { return v.Name } -// TODO(kevlar) s/device/ -type Device struct { +type Product struct { Name string - Interfaces map[usb.ID]string + Interface map[usb.ID]string } -func (d Device) String() string { - return d.Name +func (p Product) String() string { + return p.Name } -/* type Class struct { Name string - SubClass map[usb.ID]*SubClass + SubClass map[uint8]*SubClass +} + +func (c Class) String() string { + return c.Name } type SubClass struct { Name string - Protocol map[usb.ID]string + Protocol map[uint8]string } -*/ -func ParseIDs(r io.Reader) (map[usb.ID]*Vendor, error) { +func (s SubClass) String() string { + return s.Name +} + +func ParseIDs(r io.Reader) (map[usb.ID]*Vendor, map[uint8]*Class, error) { vendors := make(map[usb.ID]*Vendor, 2800) + classes := make(map[uint8]*Class) // TODO(kevlar): count - split := func(s string) (kind string, level int, id usb.ID, name string, err error) { + split := func(s string) (kind string, level int, id uint64, name string, err error) { pieces := strings.SplitN(s, " ", 2) if len(pieces) != 2 { err = fmt.Errorf("malformatted line %q", s) @@ -71,16 +77,18 @@ func ParseIDs(r io.Reader) (map[usb.ID]*Vendor, error) { err = fmt.Errorf("malformatted id %q: %s", pieces[0], err) return } - id = usb.ID(i) + id = i return } // Hold the interim values var vendor *Vendor - var device *Device + var device *Product + + parseVendor := func(level int, raw uint64, name string) error { + id := usb.ID(raw) - parseVendor := func(level int, id usb.ID, name string) error { switch level { case 0: vendor = &Vendor{ @@ -93,23 +101,23 @@ func ParseIDs(r io.Reader) (map[usb.ID]*Vendor, error) { return fmt.Errorf("product line without vendor line") } - device = &Device{ + device = &Product{ Name: name, } - if vendor.Devices == nil { - vendor.Devices = make(map[usb.ID]*Device) + if vendor.Product == nil { + vendor.Product = make(map[usb.ID]*Product) } - vendor.Devices[id] = device + vendor.Product[id] = device case 2: if device == nil { return fmt.Errorf("interface line without device line") } - if device.Interfaces == nil { - device.Interfaces = make(map[usb.ID]string) + if device.Interface == nil { + device.Interface = make(map[usb.ID]string) } - device.Interfaces[id] = name + device.Interface[id] = name default: return fmt.Errorf("too many levels of nesting for vendor block") @@ -118,6 +126,50 @@ func ParseIDs(r io.Reader) (map[usb.ID]*Vendor, error) { return nil } + // Hold the interim values + var class *Class + var subclass *SubClass + + parseClass := func(level int, raw uint64, name string) error { + id := uint8(raw) + + switch level { + case 0: + class = &Class{ + Name: name, + } + classes[id] = class + + case 1: + if class == nil { + return fmt.Errorf("subclass line without class line") + } + + subclass = &SubClass{ + Name: name, + } + if class.SubClass == nil { + class.SubClass = make(map[uint8]*SubClass) + } + class.SubClass[id] = subclass + + case 2: + if subclass == nil { + return fmt.Errorf("protocol line without subclass line") + } + + if subclass.Protocol == nil { + subclass.Protocol = make(map[uint8]string) + } + subclass.Protocol[id] = name + + default: + return fmt.Errorf("too many levels of nesting for class") + } + + return nil + } + // TODO(kevlar): Parse class information, etc //var class *Class //var subclass *SubClass @@ -132,9 +184,9 @@ parseLines: case err == io.EOF: break parseLines case err != nil: - return nil, err + return nil, nil, err case isPrefix: - return nil, fmt.Errorf("line %d: line too long", lineno) + return nil, nil, fmt.Errorf("line %d: line too long", lineno) } line := string(b) @@ -144,7 +196,7 @@ parseLines: k, level, id, name, err := split(line) if err != nil { - return nil, fmt.Errorf("line %d: %s", lineno, err) + return nil, nil, fmt.Errorf("line %d: %s", lineno, err) } if k != "" { kind = k @@ -153,11 +205,13 @@ parseLines: switch kind { case "": err = parseVendor(level, id, name) + case "C": + err = parseClass(level, id, name) } if err != nil { - return nil, fmt.Errorf("line %d: %s", lineno, err) + return nil, nil, fmt.Errorf("line %d: %s", lineno, err) } } - return vendors, nil + return vendors, classes, nil }