diff --git a/bin/usb-client b/bin/usb-client index e096a5a..41d28ae 100755 Binary files a/bin/usb-client and b/bin/usb-client differ diff --git a/bin/usb-client.exe b/bin/usb-client.exe index 1badc81..cfa0759 100755 Binary files a/bin/usb-client.exe and b/bin/usb-client.exe differ diff --git a/bin/usb-relay b/bin/usb-relay index fbe145f..7bbbd7e 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 7d4c45d..fe001aa 100644 --- a/internal/usb/usbdevfs.go +++ b/internal/usb/usbdevfs.go @@ -37,6 +37,7 @@ var ( 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))) @@ -348,6 +349,27 @@ func (h *DeviceHandle) DiscardURB(urb *usbdevfsURB) error { 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 diff --git a/internal/usbip/server.go b/internal/usbip/server.go index e06d593..cf9cdcb 100644 --- a/internal/usbip/server.go +++ b/internal/usbip/server.go @@ -15,7 +15,6 @@ import ( "unsafe" "github.com/duffy/usb-server/internal/usb" - "golang.org/x/sys/unix" ) // Server handles USB/IP protocol on the share side. @@ -112,9 +111,7 @@ func (s *Server) Detach() { s.mu.Lock() for seqNum, pending := range s.pendingURBs { if pending.urbPtr != nil { - unix.Syscall(unix.SYS_IOCTL, uintptr(s.handle.Fd()), - 0x8000550B, // USBDEVFS_DISCARDURB - uintptr(pending.urbPtr)) + s.handle.DiscardURBByPtr(pending.urbPtr) } delete(s.pendingURBs, seqNum) } @@ -410,6 +407,33 @@ func (s *Server) handleCmdSubmit(r io.Reader, hdr *URBHeader, retChan chan<- []b status = -32 } + case bmRequestType == 0x00 && bRequest == 0x09: + // SET_CONFIGURATION — the device resets all endpoint data toggles + // to DATA0 on SET_CONFIGURATION, but USBDEVFS_CONTROL doesn't update + // the host controller's toggle state. We must manually reset host-side + // toggles via USBDEVFS_RESETEP, otherwise data toggle mismatch causes + // all non-control transfers (bulk, interrupt) to fail silently. + buf := transferBuf + if buf == nil { + buf = make([]byte, 0) + } + _, err := s.handle.ControlTransfer(bmRequestType, bRequest, wValue, wIndex, 0, 5000, buf) + if err != nil { + log.Printf("[usbip-server] SET_CONFIGURATION(%d) failed: %v", wValue, err) + status = -32 + } else { + log.Printf("[usbip-server] SET_CONFIGURATION(%d) OK, resetting endpoint toggles", wValue) + for epNum := range s.epTypes { + if epNum == 0 { + continue + } + // Reset both OUT and IN directions; ignore errors for + // non-existent directions (e.g. endpoint only has IN) + s.handle.ResetEndpoint(uint32(epNum)) + s.handle.ResetEndpoint(uint32(epNum) | 0x80) + } + } + default: // Generic OUT control transfer buf := transferBuf @@ -577,11 +601,7 @@ func (s *Server) handleCmdUnlink(r io.Reader, hdr *URBHeader, retChan chan<- []b var status int32 if exists && pending.urbPtr != nil { - // Try to discard the URB via proper ioctl - _, _, errno := unix.Syscall(unix.SYS_IOCTL, uintptr(s.handle.Fd()), - uintptr(0x8000550B), // USBDEVFS_DISCARDURB - uintptr(pending.urbPtr)) - if errno == 0 { + if err := s.handle.DiscardURBByPtr(pending.urbPtr); err == nil { status = -104 // -ECONNRESET } }