diff --git a/usb/config.go b/usb/config.go index fd45f2a..6c265dc 100644 --- a/usb/config.go +++ b/usb/config.go @@ -15,47 +15,11 @@ package usb -import "fmt" - -// InterfaceInfo contains information about a USB interface, extracted from -// the descriptor. -type InterfaceInfo struct { - // Number is the number of this interface, a zero-based index in the array - // of interfaces supported by the device configuration. - Number int - // AltSettings is a list of alternate settings supported by the interface. - AltSettings []InterfaceSetting -} - -// String returns a human-readable descripton of the interface and it's -// alternate settings. -func (i InterfaceInfo) String() string { - return fmt.Sprintf("Interface %d (%d alternate settings)", i.Number, len(i.AltSettings)) -} - -// InterfaceSetting contains information about a USB interface with a particular -// alternate setting, extracted from the descriptor. -type InterfaceSetting struct { - // Number is the number of this interface, the same as in InterfaceInfo. - Number int - // Alternate is the number of this alternate setting. - Alternate int - // Class is the USB-IF class code, as defined by the USB spec. - Class Class - // SubClass is the USB-IF subclass code, as defined by the USB spec. - SubClass Class - // Protocol is USB protocol code, as defined by the USB spe.c - Protocol Protocol - // Endpoints has the list of endpoints available on this interface with - // this alternate setting. - Endpoints []EndpointInfo -} - -// String returns a human-readable descripton of the particular -// alternate setting of an interface. -func (a InterfaceSetting) String() string { - return fmt.Sprintf("Interface %d alternate setting %d", a.Number, a.Alternate) -} +import ( + "fmt" + "sync" + "time" +) // ConfigInfo contains the information about a USB device configuration. type ConfigInfo struct { @@ -75,5 +39,73 @@ type ConfigInfo struct { // String returns the human-readable description of the configuration. func (c ConfigInfo) String() string { - return fmt.Sprintf("Config %d", c.Config) + return fmt.Sprintf("config=%d", c.Config) +} + +// Config represents a USB device set to use a particular configuration. +// Only one Config of a particular device can be used at any one time. +type Config struct { + Info ConfigInfo + ControlTimeout time.Duration + + dev *Device + + // Claimed interfaces + mu sync.Mutex + claimed map[int]bool +} + +// Close releases the underlying device, allowing the caller to switch the device to a different configuration. +func (c *Config) Close() error { + c.mu.Lock() + defer c.mu.Unlock() + if len(c.claimed) > 0 { + var ifs []int + for k := range c.claimed { + ifs = append(ifs, k) + } + return fmt.Errorf("failed to release %s, interfaces %v are still open", c, ifs) + } + c.dev.mu.Lock() + defer c.dev.mu.Unlock() + c.dev.claimed = nil + return nil +} + +func (c *Config) String() string { + return fmt.Sprintf("%s,%s", c.dev.String(), c.Info.String()) +} + +// Control sends a control request to the device. +func (c *Config) Control(rType, request uint8, val, idx uint16, data []byte) (int, error) { + return libusb.control(c.dev.handle, c.ControlTimeout, rType, request, val, idx, data) +} + +// Interface claims and returns an interface on a USB device. +func (c *Config) Interface(intf, alt int) (*Interface, error) { + if intf < 0 || intf >= len(c.Info.Interfaces) { + return nil, fmt.Errorf("interface %d not found in %s. Interface number needs to be a 0-based index into the interface table, which has %d elements.", intf, c, len(c.Info.Interfaces)) + } + ifInfo := c.Info.Interfaces[intf] + if alt < 0 || alt >= len(ifInfo.AltSettings) { + return nil, fmt.Errorf("Inteface %d does not have alternate setting %d. Alt setting needs to be a 0-based index into the settings table, which has %d elements.", ifInfo, alt, len(ifInfo.AltSettings)) + } + + // Claim the interface + if err := libusb.claim(c.dev.handle, uint8(intf)); err != nil { + return nil, fmt.Errorf("failed to claim interface %d on %s: %v", intf, c, err) + } + + if err := libusb.setAlt(c.dev.handle, uint8(intf), uint8(alt)); err != nil { + libusb.release(c.dev.handle, uint8(intf)) + return nil, fmt.Errorf("failed to set alternate config %d on interface %d of %s: %v", alt, intf, c, err) + } + + c.mu.Lock() + defer c.mu.Unlock() + c.claimed[intf] = true + return &Interface{ + Setting: ifInfo.AltSettings[alt], + config: c, + }, nil } diff --git a/usb/debug.go b/usb/debug.go index 8818bda..d3d21ee 100644 --- a/usb/debug.go +++ b/usb/debug.go @@ -33,5 +33,5 @@ func init() { if os.Getenv(debugEnvVarName) != "" { out = os.Stderr } - debug = log.New(out, "usb", log.LstdFlags|log.Lshortfile) + debug = log.New(out, "gousb: ", log.LstdFlags|log.Lshortfile) } diff --git a/usb/descriptor.go b/usb/descriptor.go index c87e1bc..37fe471 100644 --- a/usb/descriptor.go +++ b/usb/descriptor.go @@ -15,6 +15,8 @@ package usb +import "fmt" + // Descriptor is a representation of a USB device descriptor. type Descriptor struct { // Bus information @@ -37,3 +39,8 @@ type Descriptor struct { // Configuration information Configs []ConfigInfo } + +// String represents a human readable representation of the device in the descriptor. +func (d *Descriptor) String() string { + return fmt.Sprintf("vid=%s,pid=%s,bus=%d,addr=%d", d.Vendor, d.Product, d.Bus, d.Address) +} diff --git a/usb/device.go b/usb/device.go index 6f74cc1..91c0b00 100644 --- a/usb/device.go +++ b/usb/device.go @@ -18,16 +18,6 @@ package usb import ( "fmt" "sync" - "time" -) - -var ( - // DefaultReadTimeout controls the default timeout for IN endpoint transfers. - DefaultReadTimeout = 1 * time.Second - // DefaultWriteTimeout controls the default timeout for OUT endpoint transfers. - DefaultWriteTimeout = 1 * time.Second - // DefaultControlTimeout controls the default timeout for control transfers. - DefaultControlTimeout = 250 * time.Millisecond ) // Device represents an opened USB device. @@ -37,41 +27,21 @@ type Device struct { // 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[int]int -} - -func newDevice(handle *libusbDevHandle, desc *Descriptor) *Device { - ifaces := 0 - d := &Device{ - handle: handle, - Descriptor: desc, - ReadTimeout: DefaultReadTimeout, - WriteTimeout: DefaultWriteTimeout, - ControlTimeout: DefaultControlTimeout, - lock: new(sync.Mutex), - claimed: make(map[int]int, ifaces), - } - - return d + // Claimed config + mu sync.Mutex + claimed *Config } // Reset performs a USB port reset to reinitialize a device. func (d *Device) Reset() error { + d.mu.Lock() + defer d.mu.Unlock() + if d.claimed != nil { + return fmt.Errorf("can't reset a device with an open configuration") + } return libusb.reset(d.handle) } -// Control sends a control request to the device. -func (d *Device) Control(rType, request uint8, val, idx uint16, data []byte) (int, error) { - 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() (int, error) { @@ -79,136 +49,51 @@ func (d *Device) ActiveConfig() (int, error) { return int(ret), err } -// SetConfig attempts to change the active configuration. +// Config returns a USB device set to use a particular config. // 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 int) error { - return libusb.setConfig(d.handle, uint8(cfg)) +// USB supports only one active config per device at a time. Config claims the +// device before setting the desired config and keeps it locked until Close is called. +func (d *Device) Config(cfgNum int) (*Config, error) { + cfg := &Config{ + dev: d, + claimed: make(map[int]bool), + } + var found bool + for _, info := range d.Descriptor.Configs { + if info.Config == cfgNum { + found = true + cfg.Info = info + break + } + } + if !found { + return nil, fmt.Errorf("configuration id %d not found in the descriptor of the device %s", cfg, d) + } + if err := libusb.setConfig(d.handle, uint8(cfgNum)); err != nil { + return nil, fmt.Errorf("failed to set active config %d for the device %s: %v", cfgNum, d, err) + } + d.mu.Lock() + defer d.mu.Unlock() + d.claimed = cfg + return cfg, nil } // Close closes the device. func (d *Device) Close() error { if d.handle == nil { - return fmt.Errorf("usb: double close on device") + return fmt.Errorf("double close on device %s", d) } - d.lock.Lock() - defer d.lock.Unlock() - for iface := range d.claimed { - libusb.release(d.handle, uint8(iface)) + d.mu.Lock() + defer d.mu.Unlock() + if d.claimed != nil { + return fmt.Errorf("can't release the device %s, it has an open config %s", d, d.claimed.Info.Config) } libusb.close(d.handle) d.handle = nil return nil } -func (d *Device) openEndpoint(cfgNum, ifNum, setNum, epAddr int) (*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 0x%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 0x%02x", ifNum) - } - - var setAlternate bool - var ifs *InterfaceSetting - for i, s := range intf.AltSettings { - if s.Alternate == setNum { - setAlternate = i != 0 - debug.Printf("found setup: %+v [default: %v]\n", s, !setAlternate) - ifs = &s - break - } - } - if ifs == nil { - return nil, fmt.Errorf("usb: unknown setup 0x%02x", setNum) - } - - var ep *EndpointInfo - for _, e := range ifs.Endpoints { - if endpointAddr(e.Number, e.Direction) == epAddr { - debug.Printf("found ep #%d %s in %+v\n", e.Number, e.Direction, *ifs) - ep = &e - break - } - } - if ep == nil { - return nil, fmt.Errorf("usb: didn't find endpoint address 0x%02x", epAddr) - } - - end := newEndpoint(d.handle, *ifs, *ep) - - // Set the configuration - activeConf, err := libusb.getConfig(d.handle) - if err != nil { - return nil, fmt.Errorf("usb: getcfg: %s", err) - } - if activeConf != uint8(cfgNum) { - if err := libusb.setConfig(d.handle, uint8(cfgNum)); err != nil { - return nil, fmt.Errorf("usb: setcfg: %s", err) - } - } - - // Claim the interface - if err := libusb.claim(d.handle, uint8(ifNum)); err != nil { - return nil, fmt.Errorf("usb: claim: %s", err) - } - - // Increment the claim count - d.lock.Lock() - 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, uint8(ifNum), uint8(setNum)); err != nil { - return nil, fmt.Errorf("usb: setalt: %s", err) - } - } - - return end, nil -} - -// InEndpoint prepares an IN endpoint for transfer. -func (d *Device) InEndpoint(cfgNum, ifNum, setNum, epNum int) (*InEndpoint, error) { - ep, err := d.openEndpoint(cfgNum, ifNum, setNum, endpointAddr(epNum, EndpointDirectionIn)) - if err != nil { - return nil, err - } - ep.SetTimeout(d.ReadTimeout) - return &InEndpoint{ - endpoint: ep, - }, nil -} - -// OutEndpoint prepares an OUT endpoint for transfer. -func (d *Device) OutEndpoint(cfgNum, ifNum, setNum, epNum int) (*OutEndpoint, error) { - ep, err := d.openEndpoint(cfgNum, ifNum, setNum, endpointAddr(epNum, EndpointDirectionOut)) - if err != nil { - return nil, err - } - ep.SetTimeout(d.WriteTimeout) - return &OutEndpoint{ - endpoint: ep, - }, nil -} - // GetStringDescriptor returns a device string descriptor with the given index // number. The first supported language is always used and the returned // descriptor string is converted to ASCII (non-ASCII characters are replaced diff --git a/usb/device_test.go b/usb/device_test.go index d1613a2..c9367ec 100644 --- a/usb/device_test.go +++ b/usb/device_test.go @@ -31,9 +31,19 @@ func TestOpenEndpoint(t *testing.T) { } defer dev.Close() if err != nil { - t.Fatalf("OpenDeviceWithVidPid(0x8888, 0x0002): got error %v, want nil", err) + t.Fatalf("OpenDeviceWithVidPid(0x8888, 0x0002): %v", err) } - got, err := dev.InEndpoint(1, 1, 1, 6) + cfg, err := dev.Config(1) + if err != nil { + t.Fatalf("%s Config(1): %v", dev, err) + } + defer cfg.Close() + intf, err := cfg.Interface(1, 1) + if err != nil { + t.Fatalf("%s Interface(1, 1): %v", cfg, err) + } + defer intf.Close() + got, err := intf.InEndpoint(6) if err != nil { t.Fatalf("InEndpoint(cfg=1, if=1, alt=1, ep=6IN): got error %v, want nil", err) } diff --git a/usb/endpoint.go b/usb/endpoint.go index 8a21135..05aa5f3 100644 --- a/usb/endpoint.go +++ b/usb/endpoint.go @@ -57,7 +57,7 @@ func endpointAddr(n int, d EndpointDirection) int { // String returns the human-readable description of the endpoint. func (e EndpointInfo) String() string { ret := make([]string, 0, 3) - ret = append(ret, fmt.Sprintf("Endpoint #%d %s (address 0x%02x) %s", e.Number, e.Direction, endpointAddr(e.Number, e.Direction), e.TransferType)) + ret = append(ret, fmt.Sprintf("ep #%d %s (address 0x%02x) %s", e.Number, e.Direction, endpointAddr(e.Number, e.Direction), e.TransferType)) switch e.TransferType { case TransferTypeIsochronous: ret = append(ret, fmt.Sprintf("- %s %s", e.IsoSyncType, e.UsageType)) @@ -74,7 +74,7 @@ type endpoint struct { InterfaceSetting Info EndpointInfo - timeout time.Duration + Timeout time.Duration } // String returns a human-readable description of the endpoint. @@ -82,18 +82,12 @@ func (e *endpoint) String() string { return e.Info.String() } -// SetTimeout sets a timeout duration for all new USB transfers involving -// this endpoint. -func (e *endpoint) SetTimeout(t time.Duration) { - e.timeout = t -} - func (e *endpoint) transfer(buf []byte) (int, error) { if len(buf) == 0 { return 0, nil } - t, err := newUSBTransfer(e.h, &e.Info, buf, e.timeout) + t, err := newUSBTransfer(e.h, &e.Info, buf, e.Timeout) if err != nil { return 0, err } @@ -110,14 +104,6 @@ func (e *endpoint) transfer(buf []byte) (int, error) { return n, nil } -func newEndpoint(h *libusbDevHandle, s InterfaceSetting, e EndpointInfo) *endpoint { - return &endpoint{ - InterfaceSetting: s, - Info: e, - h: h, - } -} - // InEndpoint represents an IN endpoint open for transfer. type InEndpoint struct { *endpoint diff --git a/usb/endpoint_stream.go b/usb/endpoint_stream.go index 6279cba..93be2a5 100644 --- a/usb/endpoint_stream.go +++ b/usb/endpoint_stream.go @@ -17,7 +17,7 @@ package usb func (e *endpoint) newStream(size, count int, submit bool) (*stream, error) { var ts []transferIntf for i := 0; i < count; i++ { - t, err := newUSBTransfer(e.h, &e.Info, make([]byte, size), e.timeout) + t, err := newUSBTransfer(e.h, &e.Info, make([]byte, size), e.Timeout) if err != nil { for _, t := range ts { t.free() diff --git a/usb/endpoint_test.go b/usb/endpoint_test.go index eddea21..62ead9a 100644 --- a/usb/endpoint_test.go +++ b/usb/endpoint_test.go @@ -85,7 +85,7 @@ func TestEndpoint(t *testing.T) { wantErr: true, }, } { - ep := newEndpoint(nil, epData.intf, epData.ei) + ep := &endpoint{h: nil, InterfaceSetting: epData.intf, Info: epData.ei} go func() { fakeT := lib.waitForSubmitted() fakeT.length = tc.ret @@ -116,7 +116,7 @@ func TestEndpointInfo(t *testing.T) { TransferType: TransferTypeBulk, MaxPacketSize: 512, }, - want: "Endpoint #6 IN (address 0x86) bulk [512 bytes]", + want: "ep #6 IN (address 0x86) bulk [512 bytes]", }, { ep: EndpointInfo{ @@ -127,7 +127,7 @@ func TestEndpointInfo(t *testing.T) { IsoSyncType: IsoSyncTypeAsync, UsageType: IsoUsageTypeData, }, - want: "Endpoint #2 OUT (address 0x02) isochronous - asynchronous data [512 bytes]", + want: "ep #2 OUT (address 0x02) isochronous - asynchronous data [512 bytes]", }, { ep: EndpointInfo{ @@ -137,7 +137,7 @@ func TestEndpointInfo(t *testing.T) { MaxPacketSize: 16, UsageType: InterruptUsageTypePeriodic, }, - want: "Endpoint #3 IN (address 0x83) interrupt - periodic [16 bytes]", + want: "ep #3 IN (address 0x83) interrupt - periodic [16 bytes]", }, } { if got := tc.ep.String(); got != tc.want { @@ -146,7 +146,7 @@ func TestEndpointInfo(t *testing.T) { } } -func TestEndpointIn(t *testing.T) { +func TestEndpointInOut(t *testing.T) { defer func(i libusbIntf) { libusb = i }(libusb) lib, done := newFakeLibusb() @@ -158,9 +158,30 @@ func TestEndpointIn(t *testing.T) { if err != nil { t.Fatalf("OpenDeviceWithVidPid(0x9999, 0x0001): got error %v, want nil", err) } - ep, err := d.InEndpoint(1, 0, 0, 2) + defer func() { + if err := d.Close(); err != nil { + t.Errorf("%s.Close(): %v", d, err) + } + }() + cfg, err := d.Config(1) if err != nil { - t.Fatalf("InEndpoint(1, 0, 0, 2): got error %v, want nil", err) + t.Fatalf("%s.Config(1): %v", d, err) + } + defer func() { + if err := cfg.Close(); err != nil { + t.Errorf("%s.Close(): %v", cfg, err) + } + }() + intf, err := cfg.Interface(0, 0) + if err != nil { + t.Fatalf("%s.Interface(0, 0): %v", cfg, err) + } + defer intf.Close() + + // IN endpoint 2 + iep, err := intf.InEndpoint(2) + if err != nil { + t.Fatalf("%s.InEndpoint(2): got error %v, want nil", intf, err) } dataTransferred := 100 go func() { @@ -170,52 +191,38 @@ func TestEndpointIn(t *testing.T) { close(fakeT.done) }() buf := make([]byte, 512) - got, err := ep.Read(buf) + got, err := iep.Read(buf) if err != nil { - t.Errorf("ep.Read: got error %v, want nil", err) + t.Errorf("%s.Read: got error %v, want nil", iep, err) } else if got != dataTransferred { - t.Errorf("ep.Read: got %d, want %d", got, dataTransferred) + t.Errorf("%s.Read: got %d, want %d", iep, got, dataTransferred) } - _, err = d.InEndpoint(1, 0, 0, 1) + _, err = intf.InEndpoint(1) if err == nil { - t.Error("InEndpoint(1, 0, 0, 1): got nil, want error") + t.Errorf("%s.InEndpoint(1): got nil, want error", intf) } -} -func TestEndpointOut(t *testing.T) { - defer func(i libusbIntf) { libusb = i }(libusb) - - lib, done := newFakeLibusb() - defer done() - - ctx := NewContext() - defer ctx.Close() - d, err := ctx.OpenDeviceWithVidPid(0x9999, 0x0001) + // OUT endpoint 1 + oep, err := intf.OutEndpoint(1) if err != nil { - t.Fatalf("OpenDeviceWithVidPid(0x9999, 0x0001): got error %v, want nil", err) + t.Fatalf("%s.OutEndpoint(1): got error %v, want nil", intf, err) } - ep, err := d.OutEndpoint(1, 0, 0, 1) - if err != nil { - t.Fatalf("OutEndpoint(1, 0, 0, 1): got error %v, want nil", err) - } - dataTransferred := 100 go func() { fakeT := lib.waitForSubmitted() fakeT.length = dataTransferred fakeT.status = TransferCompleted close(fakeT.done) }() - buf := make([]byte, 512) - got, err := ep.Write(buf) + got, err = oep.Write(buf) if err != nil { - t.Errorf("ep.Write: got error %v, want nil", err) + t.Errorf("%s.Write: got error %v, want nil", oep, err) } else if got != dataTransferred { - t.Errorf("ep.Write: got %d, want %d", got, dataTransferred) + t.Errorf("%s.Write: got %d, want %d", oep, got, dataTransferred) } - _, err = d.OutEndpoint(1, 0, 0, 2) + _, err = intf.OutEndpoint(2) if err == nil { - t.Error("OutEndpoint(1, 0, 0, 2): got nil, want error") + t.Errorf("%s.OutEndpoint(2): got nil, want error", intf) } } diff --git a/usb/fakelibusb_test.go b/usb/fakelibusb_test.go index c195c48..0a7cce3 100644 --- a/usb/fakelibusb_test.go +++ b/usb/fakelibusb_test.go @@ -214,6 +214,7 @@ func (f *fakeLibusb) control(*libusbDevHandle, time.Duration, uint8, uint8, uint } func (f *fakeLibusb) getConfig(*libusbDevHandle) (uint8, error) { return 1, nil } func (f *fakeLibusb) setConfig(d *libusbDevHandle, cfg uint8) error { + debug.Printf("setConfig(%p, %d)\n", d, cfg) f.mu.Lock() defer f.mu.Unlock() if len(f.claims[f.handles[d]]) != 0 { @@ -230,6 +231,7 @@ func (f *fakeLibusb) getStringDesc(*libusbDevHandle, int) (string, error) { func (f *fakeLibusb) setAutoDetach(*libusbDevHandle, int) error { return nil } func (f *fakeLibusb) claim(d *libusbDevHandle, intf uint8) error { + debug.Printf("claim(%p, %d)\n", d, intf) f.mu.Lock() defer f.mu.Unlock() c := f.claims[f.handles[d]] @@ -241,6 +243,7 @@ func (f *fakeLibusb) claim(d *libusbDevHandle, intf uint8) error { return nil } func (f *fakeLibusb) release(d *libusbDevHandle, intf uint8) { + debug.Printf("release(%p, %d)\n", d, intf) f.mu.Lock() defer f.mu.Unlock() c := f.claims[f.handles[d]] @@ -250,6 +253,7 @@ func (f *fakeLibusb) release(d *libusbDevHandle, intf uint8) { c[intf] = false } func (f *fakeLibusb) setAlt(d *libusbDevHandle, intf, alt uint8) error { + debug.Printf("setAlt(%p, %d, %d)\n", d, intf, alt) f.mu.Lock() defer f.mu.Unlock() if !f.claims[f.handles[d]][intf] { diff --git a/usb/interface.go b/usb/interface.go new file mode 100644 index 0000000..5d986ae --- /dev/null +++ b/usb/interface.go @@ -0,0 +1,127 @@ +// Copyright 2013 Google Inc. All rights reserved. +// Copyright 2016 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" +) + +// InterfaceInfo contains information about a USB interface, extracted from +// the descriptor. +type InterfaceInfo struct { + // Number is the number of this interface, a zero-based index in the array + // of interfaces supported by the device configuration. + Number int + // AltSettings is a list of alternate settings supported by the interface. + AltSettings []InterfaceSetting +} + +// String returns a human-readable descripton of the interface and it's +// alternate settings. +func (i InterfaceInfo) String() string { + return fmt.Sprintf("if=%d", i.Number) +} + +// InterfaceSetting contains information about a USB interface with a particular +// alternate setting, extracted from the descriptor. +type InterfaceSetting struct { + // Number is the number of this interface, the same as in InterfaceInfo. + Number int + // Alternate is the number of this alternate setting. + Alternate int + // Class is the USB-IF class code, as defined by the USB spec. + Class Class + // SubClass is the USB-IF subclass code, as defined by the USB spec. + SubClass Class + // Protocol is USB protocol code, as defined by the USB spe.c + Protocol Protocol + // Endpoints has the list of endpoints available on this interface with + // this alternate setting. + Endpoints []EndpointInfo +} + +// String returns a human-readable descripton of the particular +// alternate setting of an interface. +func (a InterfaceSetting) String() string { + return fmt.Sprintf("Interface %d alternate setting %d", a.Number, a.Alternate) +} + +type Interface struct { + Setting InterfaceSetting + + config *Config +} + +func (i *Interface) String() string { + return fmt.Sprintf("%s,if=%d,alt=%d", i.config, i.Setting.Number, i.Setting.Alternate) +} + +// Close releases the interface. +func (i *Interface) Close() { + libusb.release(i.config.dev.handle, uint8(i.Setting.Number)) + i.config.mu.Lock() + defer i.config.mu.Unlock() + delete(i.config.claimed, i.Setting.Number) +} + +func (i *Interface) openEndpoint(epNum int) (*endpoint, error) { + var ep EndpointInfo + var found bool + for _, e := range i.Setting.Endpoints { + if e.Number == epNum { + debug.Printf("found ep %s in %s\n", e, i) + ep = e + found = true + break + } + } + if !found { + return nil, fmt.Errorf("%s does not have endpoint number %d. Available endpoints: %v", epNum, i.Setting.Endpoints) + } + return &endpoint{ + InterfaceSetting: i.Setting, + Info: ep, + h: i.config.dev.handle, + }, nil +} + +// InEndpoint prepares an IN endpoint for transfer. +func (i *Interface) InEndpoint(epNum int) (*InEndpoint, error) { + ep, err := i.openEndpoint(epNum) + if err != nil { + return nil, err + } + if ep.Info.Direction != EndpointDirectionIn { + return nil, fmt.Errorf("%s is not an IN endpoint", ep) + } + return &InEndpoint{ + endpoint: ep, + }, nil +} + +// OutEndpoint prepares an OUT endpoint for transfer. +func (i *Interface) OutEndpoint(epNum int) (*OutEndpoint, error) { + ep, err := i.openEndpoint(epNum) + if err != nil { + return nil, err + } + if ep.Info.Direction != EndpointDirectionOut { + return nil, fmt.Errorf("%s is not an OUT endpoint", ep) + } + return &OutEndpoint{ + endpoint: ep, + }, nil +} diff --git a/usb/transfer_stream_test.go b/usb/transfer_stream_test.go index 86537dc..cabad9f 100644 --- a/usb/transfer_stream_test.go +++ b/usb/transfer_stream_test.go @@ -226,7 +226,7 @@ func TestReadStream(t *testing.T) { got := make([]readRes, len(tc.want)) for i := range tc.want { if i == tc.closeBefore-1 { - t.Logf("Close()", tcNum) + t.Log("Close()") s.Close() } n, err := s.Read(buf) diff --git a/usb/usb.go b/usb/usb.go index 0df0696..d33f258 100644 --- a/usb/usb.go +++ b/usb/usb.go @@ -68,7 +68,7 @@ func (c *Context) ListDevices(each func(desc *Descriptor) bool) ([]*Device, erro reterr = err continue } - ret = append(ret, newDevice(handle, desc)) + ret = append(ret, &Device{handle: handle, Descriptor: desc}) } else { libusb.dereference(dev) }