From 9849a972c613236a93c27ddb84d5a1f8589a792b Mon Sep 17 00:00:00 2001 From: Kyle Lemons Date: Thu, 12 Apr 2012 20:15:30 -0700 Subject: [PATCH] Added isochronous transfer --- usb/config.go | 7 +- usb/descriptor.go | 2 +- usb/device.go | 176 +++++++++++++++++++++++++++++++++++ usb/endpoint.go | 8 +- usb/iso.c | 9 ++ usb/iso.go | 227 ++++++++++++++++------------------------------ 6 files changed, 271 insertions(+), 158 deletions(-) create mode 100644 usb/iso.c diff --git a/usb/config.go b/usb/config.go index c519e38..1870c3e 100644 --- a/usb/config.go +++ b/usb/config.go @@ -13,6 +13,7 @@ type EndpointInfo struct { Address uint8 Attributes uint8 MaxPacketSize uint16 + MaxIsoPacket uint32 PollInterval uint8 RefreshRate uint8 SynchAddress uint8 @@ -27,11 +28,12 @@ func (e EndpointInfo) Direction() EndpointDirection { } func (e EndpointInfo) String() string { - return fmt.Sprintf("Endpoint %d %-3s %s - %s %s", + return fmt.Sprintf("Endpoint %d %-3s %s - %s %s [%d %d]", e.Number(), e.Direction(), TransferType(e.Attributes)&TRANSFER_TYPE_MASK, IsoSyncType(e.Attributes)&ISO_SYNC_TYPE_MASK, IsoUsageType(e.Attributes)&ISO_USAGE_TYPE_MASK, + e.MaxPacketSize, e.MaxIsoPacket, ) } @@ -68,7 +70,7 @@ func (c ConfigInfo) String() string { return fmt.Sprintf("Config %02x", c.Config) } -func newConfig(cfg *C.struct_libusb_config_descriptor) ConfigInfo { +func newConfig(dev *C.libusb_device, cfg *C.struct_libusb_config_descriptor) ConfigInfo { c := ConfigInfo{ Config: uint8(cfg.bConfigurationValue), Attributes: uint8(cfg.bmAttributes), @@ -114,6 +116,7 @@ func newConfig(cfg *C.struct_libusb_config_descriptor) ConfigInfo { Address: uint8(end.bEndpointAddress), Attributes: uint8(end.bmAttributes), MaxPacketSize: uint16(end.wMaxPacketSize), + //MaxIsoPacket: uint32(C.libusb_get_max_iso_packet_size(dev, C.uchar(end.bEndpointAddress))), PollInterval: uint8(end.bInterval), RefreshRate: uint8(end.bRefresh), SynchAddress: uint8(end.bSynchAddress), diff --git a/usb/descriptor.go b/usb/descriptor.go index dbab6f1..0c4d87a 100644 --- a/usb/descriptor.go +++ b/usb/descriptor.go @@ -38,7 +38,7 @@ func newDescriptor(dev *C.libusb_device) (*Descriptor, error) { if errno := C.libusb_get_config_descriptor(dev, C.uint8_t(i), &cfg); errno < 0 { return nil, usbError(errno) } - cfgs = append(cfgs, newConfig(cfg)) + cfgs = append(cfgs, newConfig(dev, cfg)) C.libusb_free_config_descriptor(cfg) } diff --git a/usb/device.go b/usb/device.go index 366ab7e..f0d2f4f 100644 --- a/usb/device.go +++ b/usb/device.go @@ -4,6 +4,182 @@ package usb import "C" import ( + "fmt" + "log" + "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 + case TRANSFER_TYPE_ISOCHRONOUS: + end.xfer = isochronous_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 { + log.Printf("ignoring altsetting error: %s", usbError(errno)) + //return nil, fmt.Errorf("usb: setalt: %s", usbError(errno)) + } + + return end, nil +} diff --git a/usb/endpoint.go b/usb/endpoint.go index b8a15d0..4791597 100644 --- a/usb/endpoint.go +++ b/usb/endpoint.go @@ -6,8 +6,8 @@ import "C" import ( "fmt" "reflect" - "unsafe" "time" + "unsafe" ) type Endpoint interface { @@ -25,7 +25,7 @@ type endpoint struct { } func (e *endpoint) Read(buf []byte) (int, error) { - if EndpointDirection(e.Address) & ENDPOINT_DIR_MASK != ENDPOINT_DIR_OUT { + if EndpointDirection(e.Address)&ENDPOINT_DIR_MASK != ENDPOINT_DIR_OUT { return 0, fmt.Errorf("usb: read: not an IN endpoint") } @@ -33,7 +33,7 @@ func (e *endpoint) Read(buf []byte) (int, error) { } func (e *endpoint) Write(buf []byte) (int, error) { - if EndpointDirection(e.Address) & ENDPOINT_DIR_MASK != ENDPOINT_DIR_OUT { + if EndpointDirection(e.Address)&ENDPOINT_DIR_MASK != ENDPOINT_DIR_OUT { return 0, fmt.Errorf("usb: write: not an OUT endpoint") } @@ -41,7 +41,7 @@ func (e *endpoint) Write(buf []byte) (int, error) { } func (e *endpoint) Interface() InterfaceSetup { return InterfaceSetup{} } -func (e *endpoint) Info() EndpointInfo { return EndpointInfo{} } +func (e *endpoint) Info() EndpointInfo { return EndpointInfo{} } // TODO(kevlar): (*Endpoint).Close diff --git a/usb/iso.c b/usb/iso.c new file mode 100644 index 0000000..1041641 --- /dev/null +++ b/usb/iso.c @@ -0,0 +1,9 @@ +#include + +void _callback(struct libusb_transfer *xfer) { + iso_callback(xfer->user_data); +} + +libusb_transfer_cb_fn callback() { + return &_callback; +} diff --git a/usb/iso.go b/usb/iso.go index c309d72..0e7de4a 100644 --- a/usb/iso.go +++ b/usb/iso.go @@ -1,182 +1,107 @@ package usb -// #include +/* +#include + +libusb_transfer_cb_fn callback(); +*/ import "C" import ( - "fmt" + "log" "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 +//export iso_callback +func iso_callback(cptr unsafe.Pointer) { + ch := *(*chan struct{})(cptr) + close(ch) } -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), +func (end *endpoint) allocTransfer() *Transfer { + // Use libusb_get_max_iso_packet_size ? + const ( + iso_packets = 242 + packet_size = 1760 + ) + + xfer := C.libusb_alloc_transfer(C.int(iso_packets)) + if xfer == nil { + log.Printf("usb: transfer allocation failed?!") + return nil } - // This doesn't seem to actually get called - runtime.SetFinalizer(d, (*Device).Close) + xfer.dev_handle = end.Device.handle + xfer.endpoint = C.uchar(end.Address) + xfer._type = C.LIBUSB_TRANSFER_TYPE_ISOCHRONOUS - return d + xfer.callback = C.callback() + xfer.timeout = C.uint(5 * time.Second / time.Millisecond) + + buf := make([]byte, iso_packets*packet_size) + xfer.num_iso_packets = iso_packets + xfer.buffer = (*C.uchar)((unsafe.Pointer)(&buf[0])) + xfer.length = C.int(len(buf)) + C.libusb_set_iso_packet_lengths(xfer, C.uint(len(buf))) + pkts := *(*[]C.struct_libusb_packet_descriptor)(unsafe.Pointer(&reflect.SliceHeader{ + Data: uintptr(unsafe.Pointer(&xfer.iso_packet_desc)), + Len: iso_packets, + Cap: iso_packets, + })) + + done := make(chan struct{}) + xfer.user_data = (unsafe.Pointer)(&done) + + t := &Transfer{ + xfer: xfer, + pkts: pkts, + done: done, + buf: buf, + } + + runtime.SetFinalizer(t, (*Transfer).Close) + + return t } -func (d *Device) Reset() error { - if errno := C.libusb_reset_device(d.handle); errno != 0 { +type Transfer struct { + xfer *C.struct_libusb_transfer + pkts []C.struct_libusb_packet_descriptor + done chan struct{} + buf []byte +} + +func (xfer *Transfer) Submit() error { + if errno := C.libusb_submit_transfer(xfer.xfer); 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) +func (xfer *Transfer) Wait() (n int, err error) { + <-xfer.done + n = int(xfer.xfer.actual_length) + for _, pkt := range xfer.pkts { + log.Printf("PACKET[%4d] - %#v", pkt) } - return int(n), nil + return n, err } -// 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) - } +func (xfer *Transfer) Close() error { + C.libusb_free_transfer(xfer.xfer) return nil } -// Close the device. -func (d *Device) Close() error { - if d.handle == nil { - return fmt.Errorf("usb: double close on device") +func isochronous_xfer(e *endpoint, buf []byte, timeout time.Duration) (int, error) { + xfer := e.allocTransfer() + defer xfer.Close() + + if err := xfer.Submit(); err != nil { + return 0, err } - 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 + + return xfer.Wait() }