
- I don't expect them to get much use, and reuse of BCD for USB spec version and device revision makes it somewhat confusing.
437 lines
15 KiB
Go
437 lines
15 KiB
Go
// Copyright 2017 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"
|
|
"log"
|
|
"reflect"
|
|
"time"
|
|
"unsafe"
|
|
)
|
|
|
|
/*
|
|
#cgo pkg-config: libusb-1.0
|
|
#include <libusb.h>
|
|
|
|
int compact_iso_data(struct libusb_transfer *xfer, unsigned char *status);
|
|
int submit(struct libusb_transfer *xfer);
|
|
*/
|
|
import "C"
|
|
|
|
type libusbContext C.libusb_context
|
|
type libusbDevice C.libusb_device
|
|
type libusbDevHandle C.libusb_device_handle
|
|
type libusbTransfer C.struct_libusb_transfer
|
|
type libusbIso C.struct_libusb_iso_packet_descriptor
|
|
type libusbEndpoint C.struct_libusb_endpoint_descriptor
|
|
|
|
func (ep libusbEndpoint) endpointInfo(dev *Descriptor) EndpointInfo {
|
|
ei := EndpointInfo{
|
|
Number: uint8(ep.bEndpointAddress & endpointNumMask),
|
|
Direction: EndpointDirection((ep.bEndpointAddress & endpointDirectionMask) != 0),
|
|
TransferType: TransferType(ep.bmAttributes & transferTypeMask),
|
|
MaxPacketSize: uint32(ep.wMaxPacketSize),
|
|
}
|
|
if ei.TransferType == TransferTypeIsochronous {
|
|
// bits 0-10 identify the packet size, bits 11-12 are the number of additional transactions per microframe.
|
|
// Don't use libusb_get_max_iso_packet_size, as it has a bug where it returns the same value
|
|
// regardless of alternative setting used, where different alternative settings might define different
|
|
// max packet sizes.
|
|
// See http://libusb.org/ticket/77 for more background.
|
|
ei.MaxPacketSize = uint32(ep.wMaxPacketSize) & 0x07ff * (uint32(ep.wMaxPacketSize)>>11&3 + 1)
|
|
ei.IsoSyncType = IsoSyncType(ep.bmAttributes & isoSyncTypeMask)
|
|
switch ep.bmAttributes & usageTypeMask {
|
|
case C.LIBUSB_ISO_USAGE_TYPE_DATA:
|
|
ei.UsageType = IsoUsageTypeData
|
|
case C.LIBUSB_ISO_USAGE_TYPE_FEEDBACK:
|
|
ei.UsageType = IsoUsageTypeFeedback
|
|
case C.LIBUSB_ISO_USAGE_TYPE_IMPLICIT:
|
|
ei.UsageType = IsoUsageTypeImplicit
|
|
}
|
|
}
|
|
// TODO(sebek): PollInterval does not work yet. The meaning of bInterval
|
|
// in the descriptor varies depending on the device and USB version:
|
|
// - if the device conforms to USB1.x:
|
|
// Interval for polling endpoint for data transfers. Expressed in
|
|
// milliseconds.
|
|
// This field is ignored for bulk and control endpoints. For
|
|
// isochronous endpoints this field must be set to 1. For interrupt
|
|
// endpoints, this field may range from 1 to 255.
|
|
// - if the device conforms to USB[23].x and the device is in low speed
|
|
// of full speed mode:
|
|
// Interval for polling endpoint for data transfers. Expressed in
|
|
// frames or microframes depending on the device operating speed
|
|
// (i.e., either 1 millisecond or 125 µs units).
|
|
// For full-/high-speed isochronous endpoints, this value must be in
|
|
// the range from 1 to 16. The bInterval value is used as the exponent
|
|
// for a 2bInterval-1 value; e.g., a bInterval of 4 means a period
|
|
// of 8 (24-1).
|
|
// For full-/low-speed interrupt endpoints, the value of this field may
|
|
// be from 1 to 255.
|
|
// For high-speed interrupt endpoints, the bInterval value is used as
|
|
// the exponent for a 2bInterval-1 value; e.g., a bInterval of 4 means
|
|
// a period of 8 (24-1). This value must be from 1 to 16.
|
|
// For high-speed bulk/control OUT endpoints, the bInterval must
|
|
// specify the maximum NAK rate of the endpoint. A value of 0 indicates
|
|
// the endpoint never NAKs. Other values indicate at most 1 NAK each
|
|
// bInterval number of microframes. This value must be in the range
|
|
// from 0 to 255.
|
|
// - if the device conforms to USB3.x and the device is in SuperSpeed mode:
|
|
// Interval for servicing the endpoint for data transfers. Expressed in
|
|
// 125-µs units.
|
|
// For Enhanced SuperSpeed isochronous and interrupt endpoints, this
|
|
// value shall be in the range from 1 to 16. However, the valid ranges
|
|
// are 8 to 16 for Notification type Interrupt endpoints. The bInterval
|
|
// value is used as the exponent for a 2(bInterval-1) value; e.g., a
|
|
// bInterval of 4 means a period of 8 (2(4-1) → 23 → 8).
|
|
// This field is reserved and shall not be used for Enhanced SuperSpeed
|
|
// bulk or control endpoints.
|
|
//
|
|
// Note: in low-speed mode, isochronous transfers are not supported.
|
|
ei.PollInterval = 0
|
|
return ei
|
|
}
|
|
|
|
// libusbIntf is a set of trivial idiomatic Go wrappers around libusb C functions.
|
|
// The underlying code is generally not testable or difficult to test,
|
|
// since libusb interacts directly with the host USB stack.
|
|
//
|
|
// All functions here should operate on types defined on C.libusb* data types,
|
|
// and occasionally on convenience data types (like TransferType or Descriptor).
|
|
type libusbIntf interface {
|
|
// context
|
|
init() (*libusbContext, error)
|
|
handleEvents(*libusbContext, <-chan struct{})
|
|
getDevices(*libusbContext) ([]*libusbDevice, error)
|
|
exit(*libusbContext)
|
|
setDebug(*libusbContext, int)
|
|
|
|
// device
|
|
dereference(*libusbDevice)
|
|
getDeviceDesc(*libusbDevice) (*Descriptor, error)
|
|
open(*libusbDevice) (*libusbDevHandle, error)
|
|
|
|
close(*libusbDevHandle)
|
|
reset(*libusbDevHandle) error
|
|
control(*libusbDevHandle, time.Duration, uint8, uint8, uint16, uint16, []byte) (int, error)
|
|
getConfig(*libusbDevHandle) (uint8, error)
|
|
setConfig(*libusbDevHandle, uint8) error
|
|
getStringDesc(*libusbDevHandle, int) (string, error)
|
|
setAutoDetach(*libusbDevHandle, int) error
|
|
|
|
// interface
|
|
claim(*libusbDevHandle, uint8) error
|
|
release(*libusbDevHandle, uint8)
|
|
setAlt(*libusbDevHandle, uint8, uint8) error
|
|
|
|
// transfer
|
|
alloc(*libusbDevHandle, *EndpointInfo, time.Duration, int, []byte) (*libusbTransfer, error)
|
|
cancel(*libusbTransfer) error
|
|
submit(*libusbTransfer, chan struct{}) error
|
|
data(*libusbTransfer) (int, TransferStatus)
|
|
free(*libusbTransfer)
|
|
setIsoPacketLengths(*libusbTransfer, uint32)
|
|
}
|
|
|
|
// libusbImpl is an implementation of libusbIntf using real CGo-wrapped libusb.
|
|
type libusbImpl struct{}
|
|
|
|
func (libusbImpl) init() (*libusbContext, error) {
|
|
var ctx *C.libusb_context
|
|
if err := fromErrNo(C.libusb_init(&ctx)); err != nil {
|
|
return nil, err
|
|
}
|
|
return (*libusbContext)(ctx), nil
|
|
}
|
|
|
|
func (libusbImpl) handleEvents(c *libusbContext, done <-chan struct{}) {
|
|
tv := C.struct_timeval{tv_usec: 100e3}
|
|
for {
|
|
select {
|
|
case <-done:
|
|
return
|
|
default:
|
|
}
|
|
if errno := C.libusb_handle_events_timeout_completed((*C.libusb_context)(c), &tv, nil); errno < 0 {
|
|
log.Printf("handle_events: error: %s", Error(errno))
|
|
}
|
|
}
|
|
}
|
|
|
|
func (libusbImpl) getDevices(ctx *libusbContext) ([]*libusbDevice, error) {
|
|
var list **C.libusb_device
|
|
cnt := C.libusb_get_device_list((*C.libusb_context)(ctx), &list)
|
|
if cnt < 0 {
|
|
return nil, fromErrNo(C.int(cnt))
|
|
}
|
|
var devs []*C.libusb_device
|
|
*(*reflect.SliceHeader)(unsafe.Pointer(&devs)) = reflect.SliceHeader{
|
|
Data: uintptr(unsafe.Pointer(list)),
|
|
Len: int(cnt),
|
|
Cap: int(cnt),
|
|
}
|
|
var ret []*libusbDevice
|
|
for _, d := range devs {
|
|
ret = append(ret, (*libusbDevice)(d))
|
|
}
|
|
// devices will be dereferenced later, during close.
|
|
C.libusb_free_device_list(list, 0)
|
|
return ret, nil
|
|
}
|
|
|
|
func (libusbImpl) exit(c *libusbContext) {
|
|
C.libusb_exit((*C.libusb_context)(c))
|
|
}
|
|
|
|
func (libusbImpl) setDebug(c *libusbContext, lvl int) {
|
|
C.libusb_set_debug((*C.libusb_context)(c), C.int(lvl))
|
|
}
|
|
|
|
func (libusbImpl) getDeviceDesc(d *libusbDevice) (*Descriptor, error) {
|
|
var desc C.struct_libusb_device_descriptor
|
|
if err := fromErrNo(C.libusb_get_device_descriptor((*C.libusb_device)(d), &desc)); err != nil {
|
|
return nil, err
|
|
}
|
|
// Enumerate configurations
|
|
var cfgs []ConfigInfo
|
|
for i := 0; i < int(desc.bNumConfigurations); i++ {
|
|
var cfg *C.struct_libusb_config_descriptor
|
|
if err := fromErrNo(C.libusb_get_config_descriptor((*C.libusb_device)(d), C.uint8_t(i), &cfg)); err != nil {
|
|
return nil, err
|
|
}
|
|
c := ConfigInfo{
|
|
Config: uint8(cfg.bConfigurationValue),
|
|
SelfPowered: (cfg.bmAttributes & selfPoweredMask) != 0,
|
|
RemoteWakeup: (cfg.bmAttributes & remoteWakeupMask) != 0,
|
|
// TODO(sebek): at GenX speeds MaxPower is expressed in units of 8mA, not 2mA.
|
|
MaxPower: 2 * Milliamperes(cfg.MaxPower),
|
|
}
|
|
|
|
var ifaces []C.struct_libusb_interface
|
|
*(*reflect.SliceHeader)(unsafe.Pointer(&ifaces)) = reflect.SliceHeader{
|
|
Data: uintptr(unsafe.Pointer(cfg._interface)),
|
|
Len: int(cfg.bNumInterfaces),
|
|
Cap: int(cfg.bNumInterfaces),
|
|
}
|
|
c.Interfaces = make([]InterfaceInfo, 0, len(ifaces))
|
|
for _, iface := range ifaces {
|
|
if iface.num_altsetting == 0 {
|
|
continue
|
|
}
|
|
|
|
var alts []C.struct_libusb_interface_descriptor
|
|
*(*reflect.SliceHeader)(unsafe.Pointer(&alts)) = reflect.SliceHeader{
|
|
Data: uintptr(unsafe.Pointer(iface.altsetting)),
|
|
Len: int(iface.num_altsetting),
|
|
Cap: int(iface.num_altsetting),
|
|
}
|
|
descs := make([]InterfaceSetup, 0, len(alts))
|
|
for _, alt := range alts {
|
|
i := InterfaceSetup{
|
|
Number: uint8(alt.bInterfaceNumber),
|
|
Alternate: uint8(alt.bAlternateSetting),
|
|
Class: Class(alt.bInterfaceClass),
|
|
SubClass: Class(alt.bInterfaceSubClass),
|
|
Protocol: Protocol(alt.bInterfaceProtocol),
|
|
}
|
|
var ends []C.struct_libusb_endpoint_descriptor
|
|
*(*reflect.SliceHeader)(unsafe.Pointer(&ends)) = reflect.SliceHeader{
|
|
Data: uintptr(unsafe.Pointer(alt.endpoint)),
|
|
Len: int(alt.bNumEndpoints),
|
|
Cap: int(alt.bNumEndpoints),
|
|
}
|
|
i.Endpoints = make([]EndpointInfo, 0, len(ends))
|
|
for n, end := range ends {
|
|
// TODO(sebek): pass the device descriptor too.
|
|
i.Endpoints[n] = libusbEndpoint(end).endpointInfo(nil)
|
|
}
|
|
descs = append(descs, i)
|
|
}
|
|
c.Interfaces = append(c.Interfaces, InterfaceInfo{
|
|
Number: descs[0].Number,
|
|
Setups: descs,
|
|
})
|
|
}
|
|
C.libusb_free_config_descriptor(cfg)
|
|
cfgs = append(cfgs, c)
|
|
}
|
|
|
|
return &Descriptor{
|
|
Bus: uint8(C.libusb_get_bus_number((*C.libusb_device)(d))),
|
|
Address: uint8(C.libusb_get_device_address((*C.libusb_device)(d))),
|
|
Spec: BCD(desc.bcdUSB),
|
|
Device: BCD(desc.bcdDevice),
|
|
Vendor: ID(desc.idVendor),
|
|
Product: ID(desc.idProduct),
|
|
Class: Class(desc.bDeviceClass),
|
|
SubClass: Class(desc.bDeviceSubClass),
|
|
Protocol: Protocol(desc.bDeviceProtocol),
|
|
Configs: cfgs,
|
|
}, nil
|
|
}
|
|
|
|
func (libusbImpl) dereference(d *libusbDevice) {
|
|
C.libusb_unref_device((*C.libusb_device)(d))
|
|
}
|
|
|
|
func (libusbImpl) open(d *libusbDevice) (*libusbDevHandle, error) {
|
|
var handle *C.libusb_device_handle
|
|
if err := fromErrNo(C.libusb_open((*C.libusb_device)(d), &handle)); err != nil {
|
|
return nil, err
|
|
}
|
|
return (*libusbDevHandle)(handle), nil
|
|
}
|
|
|
|
func (libusbImpl) close(d *libusbDevHandle) {
|
|
C.libusb_close((*C.libusb_device_handle)(d))
|
|
}
|
|
|
|
func (libusbImpl) reset(d *libusbDevHandle) error {
|
|
return fromErrNo(C.libusb_reset_device((*C.libusb_device_handle)(d)))
|
|
}
|
|
|
|
func (libusbImpl) control(d *libusbDevHandle, timeout time.Duration, rType, request uint8, val, idx uint16, data []byte) (int, error) {
|
|
dataSlice := (*reflect.SliceHeader)(unsafe.Pointer(&data))
|
|
n := C.libusb_control_transfer(
|
|
(*C.libusb_device_handle)(d),
|
|
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(timeout/time.Millisecond))
|
|
if n < 0 {
|
|
return int(n), fromErrNo(n)
|
|
}
|
|
return int(n), nil
|
|
}
|
|
|
|
func (libusbImpl) getConfig(d *libusbDevHandle) (uint8, error) {
|
|
var cfg C.int
|
|
if errno := C.libusb_get_configuration((*C.libusb_device_handle)(d), &cfg); errno < 0 {
|
|
return 0, fromErrNo(errno)
|
|
}
|
|
return uint8(cfg), nil
|
|
}
|
|
|
|
func (libusbImpl) setConfig(d *libusbDevHandle, cfg uint8) error {
|
|
return fromErrNo(C.libusb_set_configuration((*C.libusb_device_handle)(d), C.int(cfg)))
|
|
}
|
|
|
|
// TODO(sebek): device string descriptors are natively in UTF16 and support
|
|
// multiple languages. get_string_descriptor_ascii uses always the first
|
|
// language and discards non-ascii bytes. We could do better if needed.
|
|
func (libusbImpl) getStringDesc(d *libusbDevHandle, index int) (string, error) {
|
|
// allocate 200-byte array limited the length of string descriptor
|
|
buf := make([]byte, 200)
|
|
// get string descriptor from libusb. if errno < 0 then there are any errors.
|
|
// if errno >= 0; it is a length of result string descriptor
|
|
errno := C.libusb_get_string_descriptor_ascii(
|
|
(*C.libusb_device_handle)(d),
|
|
C.uint8_t(index),
|
|
(*C.uchar)(unsafe.Pointer(&buf[0])),
|
|
200)
|
|
if errno < 0 {
|
|
return "", fmt.Errorf("usb: getstr: %s", fromErrNo(errno))
|
|
}
|
|
return string(buf[:errno]), nil
|
|
}
|
|
|
|
func (libusbImpl) setAutoDetach(d *libusbDevHandle, val int) error {
|
|
err := fromErrNo(C.libusb_set_auto_detach_kernel_driver((*C.libusb_device_handle)(d), C.int(val)))
|
|
if err != nil && err != ErrorNotSupported {
|
|
return err
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func (libusbImpl) claim(d *libusbDevHandle, iface uint8) error {
|
|
return fromErrNo(C.libusb_claim_interface((*C.libusb_device_handle)(d), C.int(iface)))
|
|
}
|
|
|
|
func (libusbImpl) release(d *libusbDevHandle, iface uint8) {
|
|
C.libusb_release_interface((*C.libusb_device_handle)(d), C.int(iface))
|
|
}
|
|
|
|
func (libusbImpl) setAlt(d *libusbDevHandle, iface, setup uint8) error {
|
|
return fromErrNo(C.libusb_set_interface_alt_setting((*C.libusb_device_handle)(d), C.int(iface), C.int(setup)))
|
|
}
|
|
|
|
func (libusbImpl) alloc(d *libusbDevHandle, ep *EndpointInfo, timeout time.Duration, isoPackets int, buf []byte) (*libusbTransfer, error) {
|
|
xfer := C.libusb_alloc_transfer(C.int(isoPackets))
|
|
if xfer == nil {
|
|
return nil, fmt.Errorf("libusb_alloc_transfer(%d) failed", isoPackets)
|
|
}
|
|
xfer.dev_handle = (*C.libusb_device_handle)(d)
|
|
xfer.endpoint = C.uchar(endpointAddr(ep.Number, ep.Direction))
|
|
xfer.timeout = C.uint(timeout / time.Millisecond)
|
|
xfer._type = C.uchar(ep.TransferType)
|
|
xfer.num_iso_packets = C.int(isoPackets)
|
|
xfer.buffer = (*C.uchar)((unsafe.Pointer)(&buf[0]))
|
|
xfer.length = C.int(len(buf))
|
|
return (*libusbTransfer)(xfer), nil
|
|
}
|
|
|
|
func (libusbImpl) cancel(t *libusbTransfer) error {
|
|
return fromErrNo(C.libusb_cancel_transfer((*C.struct_libusb_transfer)(t)))
|
|
}
|
|
|
|
func (libusbImpl) submit(t *libusbTransfer, done chan struct{}) error {
|
|
t.user_data = (unsafe.Pointer)(&done)
|
|
return fromErrNo(C.submit((*C.struct_libusb_transfer)(t)))
|
|
}
|
|
|
|
func (libusbImpl) data(t *libusbTransfer) (int, TransferStatus) {
|
|
if TransferType(t._type) == TransferTypeIsochronous {
|
|
var status TransferStatus
|
|
n := int(C.compact_iso_data((*C.struct_libusb_transfer)(t), (*C.uchar)(unsafe.Pointer(&status))))
|
|
return n, status
|
|
}
|
|
return int(t.actual_length), TransferStatus(t.status)
|
|
}
|
|
|
|
func (libusbImpl) free(t *libusbTransfer) {
|
|
C.libusb_free_transfer((*C.struct_libusb_transfer)(t))
|
|
}
|
|
|
|
func (libusbImpl) setIsoPacketLengths(t *libusbTransfer, length uint32) {
|
|
C.libusb_set_iso_packet_lengths((*C.struct_libusb_transfer)(t), C.uint(length))
|
|
}
|
|
|
|
// libusb is an injection point for tests
|
|
var libusb libusbIntf = libusbImpl{}
|
|
|
|
var (
|
|
libusbIsoSize = C.sizeof_struct_libusb_iso_packet_descriptor
|
|
libusbIsoOffset = unsafe.Offsetof(C.struct_libusb_transfer{}.iso_packet_desc)
|
|
)
|
|
|
|
//export xferCallback
|
|
func xferCallback(cptr unsafe.Pointer) {
|
|
ch := *(*chan struct{})(cptr)
|
|
close(ch)
|
|
}
|
|
|
|
// for benchmarking and testing
|
|
func libusbSetDebug(c *libusbContext, lvl int) {
|
|
C.libusb_set_debug((*C.libusb_context)(c), C.int(lvl))
|
|
}
|
|
|
|
func newDevicePointer() *libusbDevice {
|
|
return (*libusbDevice)(unsafe.Pointer(C.malloc(1)))
|
|
}
|