usb-server/internal/usb/usbdevfs.go

307 lines
8.7 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) }
// 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{}))
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)
usbdevfsReset = io_('U', 20)
usbdevfsClearHalt = ior('U', 21, 4)
usbdevfsDisconnect = io_('U', 22)
usbdevfsConnect = io_('U', 23)
usbdevfsGetCapabilities = ior('U', 26, 4)
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
}
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
}
// 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(&params.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
}
// 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)
}