480 lines
14 KiB
Go
480 lines
14 KiB
Go
//go:build linux
|
|
|
|
package usb
|
|
|
|
import (
|
|
"fmt"
|
|
"os"
|
|
"unsafe"
|
|
|
|
"golang.org/x/sys/unix"
|
|
)
|
|
|
|
// ioctl direction constants
|
|
const (
|
|
iocNone = 0
|
|
iocWrite = 1
|
|
iocRead = 2
|
|
)
|
|
|
|
// ioctl encoding helpers
|
|
func ioc(dir, typ, nr, size uintptr) uintptr {
|
|
return (dir << 30) | (size << 16) | (typ << 8) | nr
|
|
}
|
|
|
|
func ior(typ, nr, size uintptr) uintptr { return ioc(iocRead, typ, nr, size) }
|
|
func iow(typ, nr, size uintptr) uintptr { return ioc(iocWrite, typ, nr, size) }
|
|
func iowr(typ, nr, size uintptr) uintptr { return ioc(iocRead|iocWrite, typ, nr, size) }
|
|
func io_(typ, nr uintptr) uintptr { return ioc(iocNone, typ, nr, 0) }
|
|
|
|
// USBDEVFS_DISCONNECT_CLAIM flags
|
|
const disconnectClaimIfDriver = 0x01
|
|
|
|
// USB device file system ioctl numbers
|
|
var (
|
|
usbdevfsControl = iowr('U', 0, unsafe.Sizeof(usbdevfsCtrlTransfer{}))
|
|
usbdevfsBulk = iowr('U', 2, unsafe.Sizeof(usbdevfsBulkTransfer{}))
|
|
usbdevfsSetInterface = ior('U', 4, unsafe.Sizeof(usbdevfsSetIntf{}))
|
|
usbdevfsSetConfig = ior('U', 5, 4)
|
|
usbdevfsSubmitURB = ior('U', 10, unsafe.Sizeof(usbdevfsURB{}))
|
|
usbdevfsResetEP = ior('U', 3, 4)
|
|
usbdevfsDiscardURB = io_('U', 11)
|
|
usbdevfsReapURB = iow('U', 12, unsafe.Sizeof(uintptr(0)))
|
|
usbdevfsReapURBNDelay = iow('U', 13, unsafe.Sizeof(uintptr(0)))
|
|
usbdevfsClaimInterface = ior('U', 15, 4)
|
|
usbdevfsReleaseInterface = ior('U', 16, 4)
|
|
usbdevfsIoctl = iowr('U', 18, unsafe.Sizeof(usbdevfsIoctlArg{}))
|
|
usbdevfsReset = io_('U', 20)
|
|
usbdevfsClearHalt = ior('U', 21, 4)
|
|
usbdevfsDisconnect = io_('U', 22)
|
|
usbdevfsConnect = io_('U', 23)
|
|
usbdevfsGetCapabilities = ior('U', 26, 4)
|
|
usbdevfsDisconnectClaim = ior('U', 27, unsafe.Sizeof(usbdevfsDisconnectClaimArg{}))
|
|
usbdevfsGetSpeed = io_('U', 31)
|
|
)
|
|
|
|
// URB type constants
|
|
const (
|
|
urbTypeISO = 0
|
|
urbTypeInterrupt = 1
|
|
urbTypeControl = 2
|
|
urbTypeBulk = 3
|
|
)
|
|
|
|
// usbdevfs structures for ioctls
|
|
|
|
type usbdevfsCtrlTransfer struct {
|
|
RequestType uint8
|
|
Request uint8
|
|
Value uint16
|
|
Index uint16
|
|
Length uint16
|
|
Timeout uint32
|
|
Data uintptr
|
|
}
|
|
|
|
type usbdevfsBulkTransfer struct {
|
|
Endpoint uint32
|
|
Length uint32
|
|
Timeout uint32
|
|
Data uintptr
|
|
}
|
|
|
|
type usbdevfsSetIntf struct {
|
|
Interface uint32
|
|
AltSetting uint32
|
|
}
|
|
|
|
type usbdevfsISOPacketDesc struct {
|
|
Length uint32
|
|
ActualLength uint32
|
|
Status uint32
|
|
}
|
|
|
|
// usbdevfsIoctlArg is the argument for USBDEVFS_IOCTL (per-interface sub-ioctl)
|
|
type usbdevfsIoctlArg struct {
|
|
Ifno int32
|
|
IoctlCode int32
|
|
Data uintptr
|
|
}
|
|
|
|
// usbdevfsDisconnectClaimArg is the argument for USBDEVFS_DISCONNECT_CLAIM
|
|
type usbdevfsDisconnectClaimArg struct {
|
|
Interface uint32
|
|
Flags uint32
|
|
Driver [256]byte
|
|
}
|
|
|
|
type usbdevfsURB struct {
|
|
Type uint8
|
|
Endpoint uint8
|
|
Status int32
|
|
Flags uint32
|
|
Buffer uintptr
|
|
BufferLength int32
|
|
ActualLength int32
|
|
StartFrame int32
|
|
NumberOfPackets int32 // or StreamID
|
|
ErrorCount int32
|
|
Signr uint32
|
|
UserContext uintptr
|
|
// ISO packet descriptors follow in memory if Type == urbTypeISO
|
|
}
|
|
|
|
// DeviceHandle provides low-level USB device access via usbdevfs
|
|
type DeviceHandle struct {
|
|
fd int
|
|
busID string
|
|
devPath string
|
|
}
|
|
|
|
// OpenDevice opens a USB device file for direct access
|
|
func OpenDevice(devPath string, busID string) (*DeviceHandle, error) {
|
|
fd, err := unix.Open(devPath, unix.O_RDWR, 0)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("opening %s: %w", devPath, err)
|
|
}
|
|
|
|
return &DeviceHandle{
|
|
fd: fd,
|
|
busID: busID,
|
|
devPath: devPath,
|
|
}, nil
|
|
}
|
|
|
|
// Close closes the device handle
|
|
func (h *DeviceHandle) Close() error {
|
|
return unix.Close(h.fd)
|
|
}
|
|
|
|
// Fd returns the file descriptor
|
|
func (h *DeviceHandle) Fd() int {
|
|
return h.fd
|
|
}
|
|
|
|
// DisconnectDriver disconnects the kernel driver from the device
|
|
func (h *DeviceHandle) DisconnectDriver() error {
|
|
_, _, errno := unix.Syscall(unix.SYS_IOCTL, uintptr(h.fd), usbdevfsDisconnect, 0)
|
|
if errno != 0 {
|
|
return fmt.Errorf("USBDEVFS_DISCONNECT: %w", errno)
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// ConnectDriver reconnects the kernel driver
|
|
func (h *DeviceHandle) ConnectDriver() error {
|
|
_, _, errno := unix.Syscall(unix.SYS_IOCTL, uintptr(h.fd), usbdevfsConnect, 0)
|
|
if errno != 0 {
|
|
return fmt.Errorf("USBDEVFS_CONNECT: %w", errno)
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// DisconnectDriverForInterface disconnects the kernel driver from a specific interface
|
|
// Uses USBDEVFS_IOCTL with USBDEVFS_DISCONNECT sub-ioctl
|
|
func (h *DeviceHandle) DisconnectDriverForInterface(ifnum uint32) error {
|
|
arg := usbdevfsIoctlArg{
|
|
Ifno: int32(ifnum),
|
|
IoctlCode: int32(usbdevfsDisconnect),
|
|
Data: 0,
|
|
}
|
|
_, _, errno := unix.Syscall(unix.SYS_IOCTL, uintptr(h.fd), usbdevfsIoctl, uintptr(unsafe.Pointer(&arg)))
|
|
if errno != 0 && errno != unix.ENODATA { // ENODATA = no driver bound, that's OK
|
|
return fmt.Errorf("USBDEVFS_IOCTL(DISCONNECT, iface %d): %w", ifnum, errno)
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// DisconnectClaimInterface atomically disconnects kernel driver and claims an interface
|
|
// Uses USBDEVFS_DISCONNECT_CLAIM (available since Linux 3.7)
|
|
func (h *DeviceHandle) DisconnectClaimInterface(ifnum uint32) error {
|
|
arg := usbdevfsDisconnectClaimArg{
|
|
Interface: ifnum,
|
|
Flags: disconnectClaimIfDriver,
|
|
}
|
|
_, _, errno := unix.Syscall(unix.SYS_IOCTL, uintptr(h.fd), usbdevfsDisconnectClaim, uintptr(unsafe.Pointer(&arg)))
|
|
if errno != 0 {
|
|
return fmt.Errorf("USBDEVFS_DISCONNECT_CLAIM(%d): %w", ifnum, errno)
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// ClaimInterface claims exclusive access to a USB interface
|
|
func (h *DeviceHandle) ClaimInterface(ifnum uint32) error {
|
|
_, _, errno := unix.Syscall(unix.SYS_IOCTL, uintptr(h.fd), usbdevfsClaimInterface, uintptr(unsafe.Pointer(&ifnum)))
|
|
if errno != 0 {
|
|
return fmt.Errorf("USBDEVFS_CLAIMINTERFACE(%d): %w", ifnum, errno)
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// ReleaseInterface releases a claimed interface
|
|
func (h *DeviceHandle) ReleaseInterface(ifnum uint32) error {
|
|
_, _, errno := unix.Syscall(unix.SYS_IOCTL, uintptr(h.fd), usbdevfsReleaseInterface, uintptr(unsafe.Pointer(&ifnum)))
|
|
if errno != 0 {
|
|
return fmt.Errorf("USBDEVFS_RELEASEINTERFACE(%d): %w", ifnum, errno)
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// SetConfiguration sets the device configuration
|
|
func (h *DeviceHandle) SetConfiguration(config uint32) error {
|
|
_, _, errno := unix.Syscall(unix.SYS_IOCTL, uintptr(h.fd), usbdevfsSetConfig, uintptr(unsafe.Pointer(&config)))
|
|
if errno != 0 {
|
|
return fmt.Errorf("USBDEVFS_SETCONFIGURATION(%d): %w", config, errno)
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// SetInterface sets alternate setting for an interface
|
|
func (h *DeviceHandle) SetInterface(iface, altSetting uint32) error {
|
|
si := usbdevfsSetIntf{Interface: iface, AltSetting: altSetting}
|
|
_, _, errno := unix.Syscall(unix.SYS_IOCTL, uintptr(h.fd), usbdevfsSetInterface, uintptr(unsafe.Pointer(&si)))
|
|
if errno != 0 {
|
|
return fmt.Errorf("USBDEVFS_SETINTERFACE(%d, %d): %w", iface, altSetting, errno)
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// ClearHalt clears endpoint halt/stall condition
|
|
func (h *DeviceHandle) ClearHalt(endpoint uint32) error {
|
|
_, _, errno := unix.Syscall(unix.SYS_IOCTL, uintptr(h.fd), usbdevfsClearHalt, uintptr(unsafe.Pointer(&endpoint)))
|
|
if errno != 0 {
|
|
return fmt.Errorf("USBDEVFS_CLEAR_HALT(%d): %w", endpoint, errno)
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// ResetDevice resets the USB device
|
|
func (h *DeviceHandle) ResetDevice() error {
|
|
_, _, errno := unix.Syscall(unix.SYS_IOCTL, uintptr(h.fd), usbdevfsReset, 0)
|
|
if errno != 0 {
|
|
return fmt.Errorf("USBDEVFS_RESET: %w", errno)
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// GetSpeed returns the device speed
|
|
func (h *DeviceHandle) GetSpeed() (uint32, error) {
|
|
r, _, errno := unix.Syscall(unix.SYS_IOCTL, uintptr(h.fd), usbdevfsGetSpeed, 0)
|
|
if errno != 0 {
|
|
return 0, fmt.Errorf("USBDEVFS_GET_SPEED: %w", errno)
|
|
}
|
|
return uint32(r), nil
|
|
}
|
|
|
|
// ControlTransfer performs a synchronous control transfer
|
|
func (h *DeviceHandle) ControlTransfer(requestType, request uint8, value, index, length uint16, timeout uint32, data []byte) (int, error) {
|
|
var dataPtr uintptr
|
|
if len(data) > 0 {
|
|
dataPtr = uintptr(unsafe.Pointer(&data[0]))
|
|
}
|
|
|
|
ct := usbdevfsCtrlTransfer{
|
|
RequestType: requestType,
|
|
Request: request,
|
|
Value: value,
|
|
Index: index,
|
|
Length: length,
|
|
Timeout: timeout,
|
|
Data: dataPtr,
|
|
}
|
|
|
|
r, _, errno := unix.Syscall(unix.SYS_IOCTL, uintptr(h.fd), usbdevfsControl, uintptr(unsafe.Pointer(&ct)))
|
|
if errno != 0 {
|
|
return 0, fmt.Errorf("USBDEVFS_CONTROL: %w", errno)
|
|
}
|
|
return int(r), nil
|
|
}
|
|
|
|
// SubmitURBParams holds parameters for async URB submission
|
|
type SubmitURBParams struct {
|
|
Type uint8
|
|
Endpoint uint8
|
|
Flags uint32
|
|
Buffer []byte
|
|
UserContext uintptr
|
|
}
|
|
|
|
// SubmitURB submits an asynchronous URB
|
|
func (h *DeviceHandle) SubmitURB(params *SubmitURBParams) (*usbdevfsURB, error) {
|
|
var bufPtr uintptr
|
|
if len(params.Buffer) > 0 {
|
|
bufPtr = uintptr(unsafe.Pointer(¶ms.Buffer[0]))
|
|
}
|
|
|
|
urb := &usbdevfsURB{
|
|
Type: params.Type,
|
|
Endpoint: params.Endpoint,
|
|
Flags: params.Flags,
|
|
Buffer: bufPtr,
|
|
BufferLength: int32(len(params.Buffer)),
|
|
NumberOfPackets: -1, // 0xFFFFFFFF for non-ISO
|
|
UserContext: params.UserContext,
|
|
}
|
|
|
|
_, _, errno := unix.Syscall(unix.SYS_IOCTL, uintptr(h.fd), usbdevfsSubmitURB, uintptr(unsafe.Pointer(urb)))
|
|
if errno != 0 {
|
|
return nil, fmt.Errorf("USBDEVFS_SUBMITURB: %w", errno)
|
|
}
|
|
return urb, nil
|
|
}
|
|
|
|
// ReapURB blocks until a URB completes, then returns it
|
|
func (h *DeviceHandle) ReapURB() (*usbdevfsURB, error) {
|
|
var urbPtr uintptr
|
|
_, _, errno := unix.Syscall(unix.SYS_IOCTL, uintptr(h.fd), usbdevfsReapURB, uintptr(unsafe.Pointer(&urbPtr)))
|
|
if errno != 0 {
|
|
return nil, fmt.Errorf("USBDEVFS_REAPURB: %w", errno)
|
|
}
|
|
return (*usbdevfsURB)(unsafe.Pointer(urbPtr)), nil
|
|
}
|
|
|
|
// ReapURBNonBlock tries to reap a URB without blocking
|
|
func (h *DeviceHandle) ReapURBNonBlock() (*usbdevfsURB, error) {
|
|
var urbPtr uintptr
|
|
_, _, errno := unix.Syscall(unix.SYS_IOCTL, uintptr(h.fd), usbdevfsReapURBNDelay, uintptr(unsafe.Pointer(&urbPtr)))
|
|
if errno != 0 {
|
|
return nil, fmt.Errorf("USBDEVFS_REAPURBNDELAY: %w", errno)
|
|
}
|
|
return (*usbdevfsURB)(unsafe.Pointer(urbPtr)), nil
|
|
}
|
|
|
|
// DiscardURB cancels a submitted URB
|
|
func (h *DeviceHandle) DiscardURB(urb *usbdevfsURB) error {
|
|
_, _, errno := unix.Syscall(unix.SYS_IOCTL, uintptr(h.fd), usbdevfsDiscardURB, uintptr(unsafe.Pointer(urb)))
|
|
if errno != 0 {
|
|
return fmt.Errorf("USBDEVFS_DISCARDURB: %w", errno)
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// DiscardURBByPtr cancels a submitted URB given its raw pointer.
|
|
// Use this when the URB type is not accessible (e.g. from another package).
|
|
func (h *DeviceHandle) DiscardURBByPtr(ptr unsafe.Pointer) error {
|
|
_, _, errno := unix.Syscall(unix.SYS_IOCTL, uintptr(h.fd), usbdevfsDiscardURB, uintptr(ptr))
|
|
if errno != 0 {
|
|
return fmt.Errorf("USBDEVFS_DISCARDURB: %w", errno)
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// ResetEndpoint resets the host-side data toggle for an endpoint without
|
|
// sending any USB traffic to the device. Use after SET_CONFIGURATION to
|
|
// sync host controller toggle state with the device.
|
|
func (h *DeviceHandle) ResetEndpoint(endpoint uint32) error {
|
|
_, _, errno := unix.Syscall(unix.SYS_IOCTL, uintptr(h.fd), usbdevfsResetEP, uintptr(unsafe.Pointer(&endpoint)))
|
|
if errno != 0 {
|
|
return fmt.Errorf("USBDEVFS_RESETEP(%d): %w", endpoint, errno)
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// SubmitISOURBParams holds parameters for async ISO URB submission
|
|
type SubmitISOURBParams struct {
|
|
Endpoint uint8
|
|
Flags uint32
|
|
Buffer []byte
|
|
NumberOfPackets int32
|
|
PacketLengths []uint32 // length of each ISO packet
|
|
UserContext uintptr
|
|
}
|
|
|
|
// SubmitISOURB submits an asynchronous isochronous URB.
|
|
// Returns the URB pointer and the backing memory slice (must be kept alive until reap).
|
|
func (h *DeviceHandle) SubmitISOURB(params *SubmitISOURBParams) (urb *usbdevfsURB, mem []byte, err error) {
|
|
urbSize := unsafe.Sizeof(usbdevfsURB{})
|
|
isoDescSize := unsafe.Sizeof(usbdevfsISOPacketDesc{})
|
|
totalSize := urbSize + uintptr(params.NumberOfPackets)*isoDescSize
|
|
|
|
mem = make([]byte, totalSize)
|
|
urb = (*usbdevfsURB)(unsafe.Pointer(&mem[0]))
|
|
|
|
var bufPtr uintptr
|
|
if len(params.Buffer) > 0 {
|
|
bufPtr = uintptr(unsafe.Pointer(¶ms.Buffer[0]))
|
|
}
|
|
|
|
urb.Type = urbTypeISO
|
|
urb.Endpoint = params.Endpoint
|
|
urb.Flags = params.Flags
|
|
urb.Buffer = bufPtr
|
|
urb.BufferLength = int32(len(params.Buffer))
|
|
urb.NumberOfPackets = params.NumberOfPackets
|
|
urb.UserContext = params.UserContext
|
|
|
|
// Fill ISO packet descriptors (immediately following the URB in memory)
|
|
for i := int32(0); i < params.NumberOfPackets; i++ {
|
|
descOffset := urbSize + uintptr(i)*isoDescSize
|
|
desc := (*usbdevfsISOPacketDesc)(unsafe.Pointer(&mem[descOffset]))
|
|
if i < int32(len(params.PacketLengths)) {
|
|
desc.Length = params.PacketLengths[i]
|
|
}
|
|
}
|
|
|
|
_, _, errno := unix.Syscall(unix.SYS_IOCTL, uintptr(h.fd), usbdevfsSubmitURB, uintptr(unsafe.Pointer(urb)))
|
|
if errno != 0 {
|
|
return nil, nil, fmt.Errorf("USBDEVFS_SUBMITURB (ISO): %w", errno)
|
|
}
|
|
return urb, mem, nil
|
|
}
|
|
|
|
// ISOPacketResult holds the result of one ISO packet after reaping
|
|
type ISOPacketResult struct {
|
|
Length uint32
|
|
ActualLength uint32
|
|
Status uint32
|
|
}
|
|
|
|
// ReadISOResults reads the ISO packet results from a reaped ISO URB's backing memory.
|
|
func ReadISOResults(mem []byte, numPackets int32) []ISOPacketResult {
|
|
urbSize := unsafe.Sizeof(usbdevfsURB{})
|
|
isoDescSize := unsafe.Sizeof(usbdevfsISOPacketDesc{})
|
|
|
|
results := make([]ISOPacketResult, numPackets)
|
|
for i := int32(0); i < numPackets; i++ {
|
|
offset := urbSize + uintptr(i)*isoDescSize
|
|
if int(offset+isoDescSize) > len(mem) {
|
|
break
|
|
}
|
|
desc := (*usbdevfsISOPacketDesc)(unsafe.Pointer(&mem[offset]))
|
|
results[i] = ISOPacketResult{
|
|
Length: desc.Length,
|
|
ActualLength: desc.ActualLength,
|
|
Status: desc.Status,
|
|
}
|
|
}
|
|
return results
|
|
}
|
|
|
|
// ReapedURBInfo holds exported fields from a reaped URB needed for response building
|
|
type ReapedURBInfo struct {
|
|
UserContext uintptr
|
|
Status int32
|
|
ActualLength int32
|
|
StartFrame int32
|
|
ErrorCount int32
|
|
}
|
|
|
|
// ReapURBInfo blocks until a URB completes and returns exported info
|
|
func (h *DeviceHandle) ReapURBInfo() (*ReapedURBInfo, error) {
|
|
var urbPtr uintptr
|
|
_, _, errno := unix.Syscall(unix.SYS_IOCTL, uintptr(h.fd), usbdevfsReapURB, uintptr(unsafe.Pointer(&urbPtr)))
|
|
if errno != 0 {
|
|
return nil, fmt.Errorf("USBDEVFS_REAPURB: %w", errno)
|
|
}
|
|
urb := (*usbdevfsURB)(unsafe.Pointer(urbPtr))
|
|
return &ReapedURBInfo{
|
|
UserContext: urb.UserContext,
|
|
Status: urb.Status,
|
|
ActualLength: urb.ActualLength,
|
|
StartFrame: urb.StartFrame,
|
|
ErrorCount: urb.ErrorCount,
|
|
}, nil
|
|
}
|
|
|
|
// GetFile returns an os.File wrapping the device fd (useful for epoll/select)
|
|
func (h *DeviceHandle) GetFile() *os.File {
|
|
return os.NewFile(uintptr(h.fd), h.devPath)
|
|
}
|