plugeth/vendor/github.com/karalabe/usb/raw_enabled.go

254 lines
8.9 KiB
Go
Raw Normal View History

// usb - Self contained USB and HID library for Go
// Copyright 2019 The library Authors
//
// This library is free software: you can redistribute it and/or modify it under
// the terms of the GNU Lesser General Public License as published by the Free
// Software Foundation, either version 3 of the License, or (at your option) any
// later version.
//
// The library is distributed in the hope that it will be useful, but WITHOUT ANY
// WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
// A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public License along
// with the library. If not, see <http://www.gnu.org/licenses/>.
// +build freebsd,cgo linux,cgo darwin,!ios,cgo windows,cgo
package usb
/*
#include "./libusb/libusb/libusb.h"
// ctx is a global libusb context to interact with devices through.
libusb_context* ctx;
*/
import "C"
import (
"fmt"
"reflect"
"sync"
"unsafe"
)
// enumerateRaw returns a list of all the USB devices attached to the system which
// match the vendor and product id:
// - If the vendor id is set to 0 then any vendor matches.
// - If the product id is set to 0 then any product matches.
// - If the vendor and product id are both 0, all USB devices are returned.
func enumerateRaw(vendorID uint16, productID uint16) ([]DeviceInfo, error) {
// Enumerate the devices, and free all the matching refcounts (we'll reopen any
// explicitly requested).
infos, err := enumerateRawWithRef(vendorID, productID)
for _, info := range infos {
C.libusb_unref_device(info.rawDevice.(*C.libusb_device))
}
// If enumeration failed, don't return anything, otherwise everything
if err != nil {
return nil, err
}
return infos, nil
}
// enumerateRawWithRef is the internal device enumerator that retains 1 reference
// to every matched device so they may selectively be opened on request.
func enumerateRawWithRef(vendorID uint16, productID uint16) ([]DeviceInfo, error) {
// Ensure we have a libusb context to interact through. The enumerate call is
// protexted by a mutex outside, so it's fine to do the below check and init.
if C.ctx == nil {
if err := fromRawErrno(C.libusb_init((**C.libusb_context)(&C.ctx))); err != nil {
return nil, fmt.Errorf("failed to initialize libusb: %v", err)
}
}
// Retrieve all the available USB devices and wrap them in Go
var deviceList **C.libusb_device
count := C.libusb_get_device_list(C.ctx, &deviceList)
if count < 0 {
return nil, rawError(count)
}
defer C.libusb_free_device_list(deviceList, 1)
var devices []*C.libusb_device
*(*reflect.SliceHeader)(unsafe.Pointer(&devices)) = reflect.SliceHeader{
Data: uintptr(unsafe.Pointer(deviceList)),
Len: int(count),
Cap: int(count),
}
//
var infos []DeviceInfo
for devnum, dev := range devices {
// Retrieve the libusb device descriptor and skip non-queried ones
var desc C.struct_libusb_device_descriptor
if err := fromRawErrno(C.libusb_get_device_descriptor(dev, &desc)); err != nil {
return infos, fmt.Errorf("failed to get device %d descriptor: %v", devnum, err)
}
if (vendorID > 0 && uint16(desc.idVendor) != vendorID) || (productID > 0 && uint16(desc.idProduct) != productID) {
continue
}
// Skip HID devices, they are handled directly by OS libraries
if desc.bDeviceClass == C.LIBUSB_CLASS_HID {
continue
}
// Iterate over all the configurations and find raw interfaces
for cfgnum := 0; cfgnum < int(desc.bNumConfigurations); cfgnum++ {
// Retrieve the all the possible USB configurations of the device
var cfg *C.struct_libusb_config_descriptor
if err := fromRawErrno(C.libusb_get_config_descriptor(dev, C.uint8_t(cfgnum), &cfg)); err != nil {
return infos, fmt.Errorf("failed to get device %d config %d: %v", devnum, cfgnum, err)
}
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),
}
// Drill down into each advertised interface
for ifacenum, 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),
}
for _, alt := range alts {
// Skip HID interfaces, they are handled directly by OS libraries
if alt.bInterfaceClass == C.LIBUSB_CLASS_HID {
continue
}
// Find the endpoints that can speak libusb interrupts
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),
}
var reader, writer *uint8
for _, end := range ends {
// Skip any non-interrupt endpoints
if end.bmAttributes != C.LIBUSB_TRANSFER_TYPE_INTERRUPT {
continue
}
if end.bEndpointAddress&C.LIBUSB_ENDPOINT_IN == C.LIBUSB_ENDPOINT_IN {
reader = new(uint8)
*reader = uint8(end.bEndpointAddress)
} else {
writer = new(uint8)
*writer = uint8(end.bEndpointAddress)
}
}
// If both in and out interrupts are available, match the device
if reader != nil && writer != nil {
// Enumeration matched, bump the device refcount to avoid cleaning it up
C.libusb_ref_device(dev)
port := uint8(C.libusb_get_port_number(dev))
infos = append(infos, DeviceInfo{
Path: fmt.Sprintf("%04x:%04x:%02d", vendorID, uint16(desc.idProduct), port),
VendorID: uint16(desc.idVendor),
ProductID: uint16(desc.idProduct),
Interface: ifacenum,
rawDevice: dev,
rawPort: &port,
rawReader: reader,
rawWriter: writer,
})
}
}
}
}
}
return infos, nil
}
// openRaw connects to a low level libusb device by its path name.
func openRaw(info DeviceInfo) (*rawDevice, error) {
// Enumerate all the devices matching this particular info
matches, err := enumerateRawWithRef(info.VendorID, info.ProductID)
if err != nil {
// Enumeration failed, make sure any subresults are released
for _, match := range matches {
C.libusb_unref_device(match.rawDevice.(*C.libusb_device))
}
return nil, err
}
// Find the specific endpoint we're interested in
var device *C.libusb_device
for _, match := range matches {
// Keep the matching device reference, release anything else
if device == nil && *match.rawPort == *info.rawPort && match.Interface == info.Interface {
device = match.rawDevice.(*C.libusb_device)
} else {
C.libusb_unref_device(match.rawDevice.(*C.libusb_device))
}
}
if device == nil {
return nil, fmt.Errorf("failed to open device: not found")
}
// Open the mathcing device
info.rawDevice = device
var handle *C.struct_libusb_device_handle
if err := fromRawErrno(C.libusb_open(info.rawDevice.(*C.libusb_device), (**C.struct_libusb_device_handle)(&handle))); err != nil {
return nil, fmt.Errorf("failed to open device: %v", err)
}
if err := fromRawErrno(C.libusb_claim_interface(handle, (C.int)(info.Interface))); err != nil {
C.libusb_close(handle)
return nil, fmt.Errorf("failed to claim interface: %v", err)
}
return &rawDevice{
DeviceInfo: info,
handle: handle,
}, nil
}
// rawDevice is a live low level USB connected device handle.
type rawDevice struct {
DeviceInfo // Embed the infos for easier access
handle *C.struct_libusb_device_handle // Low level USB device to communicate through
lock sync.Mutex
}
// Close releases the raw USB device handle.
func (dev *rawDevice) Close() error {
dev.lock.Lock()
defer dev.lock.Unlock()
if dev.handle != nil {
C.libusb_release_interface(dev.handle, (C.int)(dev.Interface))
C.libusb_close(dev.handle)
dev.handle = nil
}
C.libusb_unref_device(dev.rawDevice.(*C.libusb_device))
return nil
}
// Write sends a binary blob to a low level USB device.
func (dev *rawDevice) Write(b []byte) (int, error) {
dev.lock.Lock()
defer dev.lock.Unlock()
var transferred C.int
if err := fromRawErrno(C.libusb_interrupt_transfer(dev.handle, (C.uchar)(*dev.rawWriter), (*C.uchar)(&b[0]), (C.int)(len(b)), &transferred, (C.uint)(0))); err != nil {
return 0, fmt.Errorf("failed to write to device: %v", err)
}
return int(transferred), nil
}
// Read retrieves a binary blob from a low level USB device.
func (dev *rawDevice) Read(b []byte) (int, error) {
dev.lock.Lock()
defer dev.lock.Unlock()
var transferred C.int
if err := fromRawErrno(C.libusb_interrupt_transfer(dev.handle, (C.uchar)(*dev.rawReader), (*C.uchar)(&b[0]), (C.int)(len(b)), &transferred, (C.uint)(0))); err != nil {
return 0, fmt.Errorf("failed to read from device: %v", err)
}
return int(transferred), nil
}