fix USBDEVFS_DISCONNECT_CLAIM

This commit is contained in:
duffyduck 2026-02-18 22:24:54 +01:00
parent 88564d9837
commit 170f5dabcc
4 changed files with 90 additions and 25 deletions

Binary file not shown.

Binary file not shown.

View File

@ -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)))

View File

@ -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