diff --git a/service/usbip/client.go b/service/usbip/client.go index 13b7f5c38..de51f98dc 100644 --- a/service/usbip/client.go +++ b/service/usbip/client.go @@ -139,7 +139,16 @@ func (c *ClientService) runBusIDLoop(ctx context.Context, busid, description str if ctx.Err() != nil { return } - if !c.shouldRetryBusID(ctx, busid) { + retry := true + if !c.assignment.Matched() { + err = c.syncRemoteStateContext(ctx) + if err != nil { + c.logger.Warn("refresh remote exports after releasing ", busid, ": ", err) + } else { + retry = c.assignment.IsRetryDesired(busid) + } + } + if !retry { c.logger.Info("remote export ", busid, " disappeared; stopping import worker") return } diff --git a/service/usbip/client_control.go b/service/usbip/client_control.go index b75c530d5..944596237 100644 --- a/service/usbip/client_control.go +++ b/service/usbip/client_control.go @@ -72,7 +72,9 @@ func (s *clientControlSession) requestLease(ctx context.Context, busid string) ( Version: controlProtocolVersion, }, request) if err != nil { - s.removeLeaseWaiter(nonce) + s.access.Lock() + delete(s.pending, nonce) + s.access.Unlock() return controlLeaseResponse{}, err } @@ -80,7 +82,9 @@ func (s *clientControlSession) requestLease(ctx context.Context, busid string) ( case result := <-waiter: return result.response, result.err case <-ctx.Done(): - s.removeLeaseWaiter(nonce) + s.access.Lock() + delete(s.pending, nonce) + s.access.Unlock() return controlLeaseResponse{}, ctx.Err() } } @@ -100,12 +104,6 @@ func (s *clientControlSession) deliverLeaseResponse(response controlLeaseRespons return true } -func (s *clientControlSession) removeLeaseWaiter(nonce uint64) { - s.access.Lock() - delete(s.pending, nonce) - s.access.Unlock() -} - func (s *clientControlSession) closeWithError(err error) { s.access.Lock() if s.closed { @@ -144,15 +142,6 @@ func (c *ClientService) requestImportLease(ctx context.Context, busid string) (c }, nil } -func (c *ClientService) applyControlSnapshot(snapshot controlDeviceSnapshot) { - devices := deviceInfoV2Map(snapshot.Devices) - values := sortedDeviceInfoV2Values(devices) - c.remoteAccess.Lock() - c.remoteDevicesV2 = devices - c.remoteAccess.Unlock() - c.applyRemoteDeviceState(values) -} - func (c *ClientService) applyControlDelta(delta controlDeviceDelta) { c.remoteAccess.Lock() if c.remoteDevicesV2 == nil { @@ -194,6 +183,10 @@ func (c *ClientService) syncRemoteStateAndResetControlState(ctx context.Context) c.remoteAccess.Lock() c.remoteDevicesV2 = devices c.remoteAccess.Unlock() - c.applyRemoteEntries(entries) + if !c.assignment.Matched() { + c.applyRemoteExports(entries) + return nil + } + c.applyMatchedExportsWithRetained(entries, nil) return nil } diff --git a/service/usbip/client_darwin.go b/service/usbip/client_darwin.go index 1026b2a87..70e721646 100644 --- a/service/usbip/client_darwin.go +++ b/service/usbip/client_darwin.go @@ -34,7 +34,7 @@ type darwinPendingSubmit struct { reply chan SubmitResponse } -var _ DataSession = (*darwinVirtualController)(nil) +var _ AttachedSession = (*darwinVirtualController)(nil) type darwinVirtualController struct { ctx context.Context @@ -124,6 +124,10 @@ func (c *darwinVirtualController) Err() error { return c.runErr } +func (c *darwinVirtualController) Description() string { + return "IOUSBHostControllerInterface" +} + func (c *darwinVirtualController) enqueueEvent(event darwinControllerEvent) { select { case c.events <- event: diff --git a/service/usbip/client_shared.go b/service/usbip/client_shared.go index 68291c695..c3ecba0dc 100644 --- a/service/usbip/client_shared.go +++ b/service/usbip/client_shared.go @@ -99,11 +99,11 @@ func (c *ClientService) runControlSession() error { if err != nil { return E.Cause(errControlUnsupported, "write control preface: ", err) } - err = writeControlFrame(conn, controlFrame{ + err = writeControlMessage(conn, controlFrame{ Type: controlFrameHello, Version: controlProtocolVersion, Capabilities: controlCapabilities, - }) + }, nil) if err != nil { return E.Cause(errControlUnsupported, "write control hello: ", err) } @@ -190,7 +190,12 @@ func (c *ClientService) runControlSession() error { return E.Cause(errImmediateReconnect, "read device snapshot: ", err) } lastSeq = frame.Sequence - c.applyControlSnapshot(snapshot) + devices := deviceInfoV2Map(snapshot.Devices) + values := sortedDeviceInfoV2Values(devices) + c.remoteAccess.Lock() + c.remoteDevicesV2 = devices + c.remoteAccess.Unlock() + c.applyRemoteDeviceState(values) case controlFrameDeviceDelta: if !extended { return E.Cause(errImmediateReconnect, "unexpected control frame ", frame.Type) @@ -254,16 +259,12 @@ func (c *ClientService) syncRemoteStateContext(ctx context.Context) error { if err != nil { return err } - c.applyRemoteEntries(entries) - return nil -} - -func (c *ClientService) applyRemoteEntries(entries []DeviceEntry) { if !c.assignment.Matched() { c.applyRemoteExports(entries) - return + return nil } c.applyMatchedExportsWithRetained(entries, nil) + return nil } func (c *ClientService) applyRemoteDeviceState(devices []DeviceInfoV2) { @@ -448,15 +449,3 @@ func (c *ClientService) fetchDevList(ctx context.Context) ([]DeviceEntry, error) } return ReadOpRepDevListBody(conn) } - -func (c *ClientService) shouldRetryBusID(ctx context.Context, busid string) bool { - if c.assignment.Matched() { - return true - } - err := c.syncRemoteStateContext(ctx) - if err != nil { - c.logger.Warn("refresh remote exports after releasing ", busid, ": ", err) - return true - } - return c.assignment.IsRetryDesired(busid) -} diff --git a/service/usbip/control_protocol.go b/service/usbip/control_protocol.go index 1d0a0922b..22ffff2d9 100644 --- a/service/usbip/control_protocol.go +++ b/service/usbip/control_protocol.go @@ -161,10 +161,6 @@ func (cr *controlReader) read(r io.Reader) (controlMessage, error) { return controlMessage{Frame: frame, Payload: payload}, nil } -func writeControlFrame(w io.Writer, frame controlFrame) error { - return writeControlMessage(w, frame, nil) -} - func writeControlMessage(w io.Writer, frame controlFrame, payload any) error { rawPayload, err := marshalControlPayload(payload) if err != nil { @@ -224,12 +220,16 @@ func deviceInfoV2FromEntry(entry DeviceEntry, backend string, stableID string, s if state == "" { state = deviceStateAvailable } + serial := entry.Serial + if serial == "" { + serial = entry.Info.SerialString() + } return DeviceInfoV2{ BusID: entry.Info.BusIDString(), StableID: stableID, Backend: backend, Path: cstring(entry.Info.Path[:]), - Serial: entrySerial(entry), + Serial: serial, VendorID: entry.Info.IDVendor, ProductID: entry.Info.IDProduct, BCDDevice: entry.Info.BCDDevice, diff --git a/service/usbip/darwin_integration_test.go b/service/usbip/darwin_integration_test.go index 39fa77946..016b72e5b 100644 --- a/service/usbip/darwin_integration_test.go +++ b/service/usbip/darwin_integration_test.go @@ -144,7 +144,7 @@ func TestDarwinClientSessionClosesOnContextCancel(t *testing.T) { defer serverConn.Close() controller := newDarwinVirtualController(context.Background(), newTestLogger(t), clientConn, DeviceInfoTruncated{}) go controller.readLoop() - session := &darwinClientSession{controller: controller} + session := controller ctx, cancel := context.WithCancel(context.Background()) cancel() @@ -304,11 +304,11 @@ func (s *darwinFakeUSBIPServer) handleControlConn(conn net.Conn) { return } capabilities := hello.Capabilities & controlCapabilities - err = writeControlFrame(conn, controlFrame{ + err = writeControlMessage(conn, controlFrame{ Type: controlFrameAck, Version: controlProtocolVersion, Capabilities: capabilities, - }) + }, nil) if err != nil { return } @@ -345,7 +345,7 @@ func (s *darwinFakeUSBIPServer) handleControlConn(conn net.Conn) { } return } - err = writeControlFrame(conn, controlFrame{Type: controlFramePong, Version: controlProtocolVersion}) + err = writeControlMessage(conn, controlFrame{Type: controlFramePong, Version: controlProtocolVersion}, nil) if err != nil { return } diff --git a/service/usbip/data_session.go b/service/usbip/data_session.go deleted file mode 100644 index 1893f79f1..000000000 --- a/service/usbip/data_session.go +++ /dev/null @@ -1,11 +0,0 @@ -package usbip - -// DataSession implementations MUST close the channel returned by Done -// when the session terminates for any reason. Err is only valid after -// Done is closed; it returns nil for a clean detach. Close is idempotent -// and safe to call from any goroutine. -type DataSession interface { - Done() <-chan struct{} - Err() error - Close() error -} diff --git a/service/usbip/host.go b/service/usbip/host.go index 63d5ec7b1..3c7a72272 100644 --- a/service/usbip/host.go +++ b/service/usbip/host.go @@ -48,6 +48,16 @@ type ExportSnapshot struct { RawStatus int } +// DataSession implementations MUST close the channel returned by Done +// when the session terminates for any reason. Err is only valid after +// Done is closed; it returns nil for a clean detach. Close is idempotent +// and safe to call from any goroutine. +type DataSession interface { + Done() <-chan struct{} + Err() error + Close() error +} + type AttachedSession interface { DataSession Description() string diff --git a/service/usbip/host_darwin.go b/service/usbip/host_darwin.go index 5572b646d..2f17c92a8 100644 --- a/service/usbip/host_darwin.go +++ b/service/usbip/host_darwin.go @@ -24,7 +24,7 @@ func newPlatformExportHost(logger log.ContextLogger, matches []option.USBIPDevic } func newPlatformImportHost(logger log.ContextLogger) (ImportHost, error) { - return newDarwinImportHost(logger), nil + return &darwinImportHost{logger: logger}, nil } // darwinExportHost retains stale captures: devices that reconcile @@ -285,10 +285,6 @@ type darwinImportHost struct { logger log.ContextLogger } -func newDarwinImportHost(logger log.ContextLogger) *darwinImportHost { - return &darwinImportHost{logger: logger} -} - func (h *darwinImportHost) Start(ctx context.Context) error { return nil } @@ -304,27 +300,7 @@ func (h *darwinImportHost) Attach(ctx context.Context, info DeviceInfoTruncated, _ = controller.Close() return nil, err } - return &darwinClientSession{controller: controller}, nil -} - -type darwinClientSession struct { - controller *darwinVirtualController -} - -func (s *darwinClientSession) Done() <-chan struct{} { - return s.controller.Done() -} - -func (s *darwinClientSession) Err() error { - return s.controller.Err() -} - -func (s *darwinClientSession) Close() error { - return s.controller.Close() -} - -func (s *darwinClientSession) Description() string { - return "IOUSBHostControllerInterface" + return controller, nil } var _ DataSession = (*darwinServerDataSession)(nil) diff --git a/service/usbip/host_linux.go b/service/usbip/host_linux.go index 706dd4dda..3e188f739 100644 --- a/service/usbip/host_linux.go +++ b/service/usbip/host_linux.go @@ -26,7 +26,10 @@ func newPlatformExportHost(logger log.ContextLogger, matches []option.USBIPDevic } func newPlatformImportHost(logger log.ContextLogger) (ImportHost, error) { - return newLinuxImportHost(logger), nil + return &linuxImportHost{ + logger: logger, + ports: make(map[int]struct{}), + }, nil } func isMissingUSBDeviceError(err error) bool { @@ -463,13 +466,6 @@ type linuxImportHost struct { ports map[int]struct{} } -func newLinuxImportHost(logger log.ContextLogger) *linuxImportHost { - return &linuxImportHost{ - logger: logger, - ports: make(map[int]struct{}), - } -} - func (h *linuxImportHost) Start(ctx context.Context) error { return ensureKernelPath(sysVHCIControllerV0, "vhci-hcd", "vhci_hcd.0") } @@ -512,7 +508,8 @@ func (h *linuxImportHost) attachOnce(ctx context.Context, info DeviceInfoTruncat triedPorts[port] = struct{}{} continue } - err = vhciAttach(port, handoff.file.Fd(), info.DevID(), info.Speed) + attachLine := fmt.Sprintf("%d %d %d %d", port, int(handoff.file.Fd()), info.DevID(), info.Speed) + err = writeSysfs(filepath.Join(sysVHCIControllerV0, "attach"), attachLine) if err != nil { h.releasePort(port) if errors.Is(err, unix.EBUSY) { diff --git a/service/usbip/protocol.go b/service/usbip/protocol.go index f5441a83d..3284a8f6c 100644 --- a/service/usbip/protocol.go +++ b/service/usbip/protocol.go @@ -287,18 +287,15 @@ func trailingCString(b []byte) string { return "" } -func entrySerial(entry DeviceEntry) string { - if entry.Serial != "" { - return entry.Serial - } - return entry.Info.SerialString() -} - func entryDeviceKey(entry DeviceEntry) DeviceKey { + serial := entry.Serial + if serial == "" { + serial = entry.Info.SerialString() + } return DeviceKey{ BusID: entry.Info.BusIDString(), VendorID: entry.Info.IDVendor, ProductID: entry.Info.IDProduct, - Serial: entrySerial(entry), + Serial: serial, } } diff --git a/service/usbip/server.go b/service/usbip/server.go index ac01f2b8d..88309aebf 100644 --- a/service/usbip/server.go +++ b/service/usbip/server.go @@ -198,12 +198,12 @@ func (s *ServerService) handleControlConn(conn net.Conn) { capabilities := hello.Capabilities & controlCapabilities sub, seq := s.ledger.Subscribe(s.ctx, conn, capabilities) defer s.ledger.Unsubscribe(sub) - err = writeControlFrame(conn, controlFrame{ + err = writeControlMessage(conn, controlFrame{ Type: controlFrameAck, Version: controlProtocolVersion, Capabilities: capabilities, Sequence: seq, - }) + }, nil) if err != nil { s.logger.Debug("write control ack: ", err) return diff --git a/service/usbip/sysfs_linux.go b/service/usbip/sysfs_linux.go index 8f6b34cc9..bea277583 100644 --- a/service/usbip/sysfs_linux.go +++ b/service/usbip/sysfs_linux.go @@ -177,7 +177,11 @@ func vhciPickFreePort(speed uint32, skip map[int]struct{}) (int, error) { if err != nil { return -1, err } - targetHub := vhciHubForSpeed(speed) + targetHub := "hs" + switch speed { + case SpeedSuper, SpeedSuperPlus: + targetHub = "ss" + } for _, record := range records { if record.hub != targetHub || record.state != 4 { continue @@ -190,11 +194,6 @@ func vhciPickFreePort(speed uint32, skip map[int]struct{}) (int, error) { return -1, E.New("no free ", targetHub, " vhci port") } -func vhciAttach(port int, fd uintptr, devid uint32, speed uint32) error { - line := fmt.Sprintf("%d %d %d %d", port, int(fd), devid, speed) - return writeSysfs(filepath.Join(sysVHCIControllerV0, "attach"), line) -} - func readVHCIStatus() ([]vhciStatusRecord, error) { raw, err := os.ReadFile(filepath.Join(sysVHCIControllerV0, "status")) if err != nil { @@ -237,15 +236,6 @@ func parseVHCIStatus(raw string) []vhciStatusRecord { return records } -func vhciHubForSpeed(speed uint32) string { - switch speed { - case SpeedSuper, SpeedSuperPlus: - return "ss" - default: - return "hs" - } -} - func ensureKernelPath(path string, module string, description string) error { _, err := os.Stat(path) if err == nil {