diff --git a/bin/usb-client b/bin/usb-client index 95e53a3..799c9ee 100755 Binary files a/bin/usb-client and b/bin/usb-client differ diff --git a/bin/usb-relay b/bin/usb-relay index 98cd263..94a7ab8 100755 Binary files a/bin/usb-relay and b/bin/usb-relay differ diff --git a/internal/usb/usbdevfs.go b/internal/usb/usbdevfs.go index 7234fca..7b9c103 100644 --- a/internal/usb/usbdevfs.go +++ b/internal/usb/usbdevfs.go @@ -27,24 +27,29 @@ 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{})) - 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) + 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) + 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 @@ -85,6 +90,20 @@ type usbdevfsISOPacketDesc struct { 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 @@ -150,6 +169,35 @@ func (h *DeviceHandle) ConnectDriver() error { 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))) diff --git a/internal/usbip/server.go b/internal/usbip/server.go index a30664c..48f096d 100644 --- a/internal/usbip/server.go +++ b/internal/usbip/server.go @@ -51,19 +51,36 @@ func (s *Server) Attach() error { } s.handle = handle - // Disconnect kernel drivers from all interfaces + // For each interface: disconnect kernel driver and claim it for _, iface := range s.device.Interfaces { - if iface.Driver != "" && iface.Driver != "(none)" { - // Try to disconnect - ignore errors for already-disconnected interfaces - handle.DisconnectDriver() - } - } + ifnum := uint32(iface.Number) - // Claim all interfaces - for _, iface := range s.device.Interfaces { - if err := handle.ClaimInterface(uint32(iface.Number)); err != nil { - log.Printf("[usbip-server] warning: could not claim interface %d: %v", iface.Number, err) + // Try atomic disconnect+claim first (USBDEVFS_DISCONNECT_CLAIM, Linux 3.7+) + err := handle.DisconnectClaimInterface(ifnum) + if err == nil { + log.Printf("[usbip-server] interface %d: disconnect+claim OK", ifnum) + continue } + + // Fallback: disconnect driver per interface, then claim + if disconnErr := handle.DisconnectDriverForInterface(ifnum); disconnErr != nil { + log.Printf("[usbip-server] interface %d: disconnect warning: %v", ifnum, disconnErr) + } + + if claimErr := handle.ClaimInterface(ifnum); claimErr != nil { + log.Printf("[usbip-server] error: could not claim interface %d: %v", ifnum, claimErr) + // This is a critical error - clean up and fail + for _, prev := range s.device.Interfaces { + if uint32(prev.Number) < ifnum { + handle.ReleaseInterface(uint32(prev.Number)) + } + } + handle.ConnectDriver() + handle.Close() + s.handle = nil + return fmt.Errorf("claiming interface %d: %w", ifnum, claimErr) + } + log.Printf("[usbip-server] interface %d: fallback disconnect+claim OK", ifnum) } return nil