diff --git a/bin/usb-client b/bin/usb-client index eecd7d3..59653e1 100755 Binary files a/bin/usb-client and b/bin/usb-client differ diff --git a/bin/usb-relay b/bin/usb-relay index 45c7722..051bc20 100755 Binary files a/bin/usb-relay and b/bin/usb-relay differ diff --git a/internal/client/share.go b/internal/client/share.go index b38819d..16ea06b 100644 --- a/internal/client/share.go +++ b/internal/client/share.go @@ -276,17 +276,28 @@ func (sm *ShareManager) handleReleaseDevice(busID, fromClient string) { return } - // Clean up tunnel + // Close the tunnel pipe to signal HandleConnection to stop reading + var tunnelDone <-chan struct{} if tunnel, ok := sm.tunnels[share.tunnelID]; ok { tunnel.inPipe.Close() + tunnelDone = tunnel.done delete(sm.tunnels, share.tunnelID) } - // Detach device (release interfaces, reconnect kernel driver) - share.server.Detach() + server := share.server delete(sm.active, busID) sm.mu.Unlock() + // Wait for HandleConnection goroutine to finish before detaching. + // This ensures no more URBs are being submitted when we detach. + if tunnelDone != nil { + <-tunnelDone + log.Printf("[share] HandleConnection goroutine finished for %s", busID) + } + + // Now safe to detach - no more USB/IP protocol processing + server.Detach() + // Notify client sm.client.SendJSON(&protocol.DeviceReleased{ Type: protocol.MsgDeviceReleased, diff --git a/internal/usbip/server.go b/internal/usbip/server.go index 489907c..281449f 100644 --- a/internal/usbip/server.go +++ b/internal/usbip/server.go @@ -8,7 +8,10 @@ import ( "fmt" "io" "log" + "os" + "path/filepath" "sync" + "time" "unsafe" "github.com/duffy/usb-server/internal/usb" @@ -95,7 +98,7 @@ func (s *Server) Attach() error { return nil } -// Detach releases all interfaces, reconnects kernel driver, and closes the device +// Detach releases all interfaces, closes the device, and rebinds kernel drivers. func (s *Server) Detach() { s.mu.Lock() s.closed = true @@ -124,25 +127,60 @@ func (s *Server) Detach() { } } - // 3. Reset device to force driver re-probing (most reliable method) - // A USB reset forces the kernel to re-enumerate and rebind drivers. - // This is important for devices like webcams that may be left in a - // streaming state after URB transfers. - if err := s.handle.ResetDevice(); err != nil { - log.Printf("[usbip-server] device reset failed: %v, trying ConnectDriver", err) - // Fallback: try to reconnect kernel driver without reset - if err := s.handle.ConnectDriver(); err != nil { - log.Printf("[usbip-server] ConnectDriver also failed: %v", err) - } else { - log.Printf("[usbip-server] kernel driver reconnected via ConnectDriver") - } - } else { - log.Printf("[usbip-server] device reset OK, kernel drivers re-bound") - } - - // 4. Close the device file descriptor + // 3. Close the device file descriptor. + // The kernel auto-cancels remaining URBs on close. s.handle.Close() s.handle = nil + + // 4. Force kernel driver re-binding via sysfs authorized toggle. + // After USBDEVFS_DISCONNECT_CLAIM, the kernel sets privileges_dropped=true. + // This means closing the fd does NOT auto-rebind drivers. + // Also USBDEVFS_RESET after ReleaseInterface doesn't rebind because + // the kernel sets needs_binding=false on release. + // The reliable solution: toggle authorized 0->1 which forces complete + // re-enumeration and driver binding. + s.rebindDrivers() +} + +// rebindDrivers forces the kernel to re-bind drivers to the device +// by toggling the sysfs authorized attribute. +func (s *Server) rebindDrivers() { + authPath := filepath.Join(s.device.SysPath, "authorized") + + // Deauthorize: kernel disconnects device, unbinds all drivers + if err := os.WriteFile(authPath, []byte("0"), 0644); err != nil { + log.Printf("[usbip-server] sysfs deauthorize failed: %v, trying fallback", err) + s.rebindDriversFallback() + return + } + + // Brief delay for the kernel to process the deauthorization + time.Sleep(100 * time.Millisecond) + + // Re-authorize: kernel re-enumerates device, binds drivers + if err := os.WriteFile(authPath, []byte("1"), 0644); err != nil { + log.Printf("[usbip-server] sysfs re-authorize failed: %v", err) + return + } + + log.Printf("[usbip-server] device re-authorized, kernel drivers re-bound") +} + +// rebindDriversFallback tries alternative methods to rebind drivers +func (s *Server) rebindDriversFallback() { + // Try writing bus_id to each original driver's bind file + for _, iface := range s.device.Interfaces { + if iface.Driver == "" { + continue + } + ifaceName := fmt.Sprintf("%s:%d.%d", s.device.BusID, s.device.ConfigValue, iface.Number) + bindPath := filepath.Join("/sys/bus/usb/drivers", iface.Driver, "bind") + if err := os.WriteFile(bindPath, []byte(ifaceName), 0644); err != nil { + log.Printf("[usbip-server] bind %s to %s failed: %v", ifaceName, iface.Driver, err) + } else { + log.Printf("[usbip-server] re-bound %s to driver %s", ifaceName, iface.Driver) + } + } } // buildEndpointTypeMap builds the endpoint number -> URB type map from device descriptors @@ -504,9 +542,11 @@ func (s *Server) reapLoop(retChan chan<- []byte, done <-chan struct{}) { s.mu.Unlock() return } + // Save handle reference under lock to prevent nil deref race + handle := s.handle s.mu.Unlock() - urbInfo, err := s.handle.ReapURBInfo() + urbInfo, err := handle.ReapURBInfo() if err != nil { select { case <-done: