//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(¶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 } // 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) }