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