From 7bf1b915649fdf8e499847836f0aedd082a7fc00 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=B8=96=E7=95=8C?= Date: Wed, 10 Jun 2026 09:23:29 +0800 Subject: [PATCH] usbip: re-probe driverless devices on linux export release MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit When the exported device was replaced underneath usbip-host (devnum changed after a flap) or the recorded original driver was empty, releaseExport skipped the re-bind and left the device driverless — and the USB core never re-probes such devices, so it stayed dead until a physical replug. Worse, the next Reconcile recorded originalDriver="" for it, making every later release repeat the damage. Release now falls back to usbip-host's rebind attribute (device_attach, the official usbip unbind path) with drivers_probe as a second fallback. --- service/usbip/host_linux.go | 61 ++++++++++++++++++++++--------------- 1 file changed, 37 insertions(+), 24 deletions(-) diff --git a/service/usbip/host_linux.go b/service/usbip/host_linux.go index 4fcff99ff..7c5584777 100644 --- a/service/usbip/host_linux.go +++ b/service/usbip/host_linux.go @@ -559,35 +559,48 @@ func (h *linuxExportHost) releaseExport(exp *linuxExport) error { if err != nil { return err } - restoreCurrentDevice, err := h.shouldRestoreCurrentDevice(exp) - if err != nil { - return err - } - if !restoreCurrentDevice { - h.logger.Info("removed export state for ", exp.busid) - return nil - } - if exp.originalDriver == "" { - h.logger.Info("released ", exp.busid, " from usbip-host") - return nil - } - err = writeSysfs(filepath.Join("/sys/bus/usb/drivers", exp.originalDriver, "bind"), exp.busid) - if err != nil { - return err - } - h.logger.Info("restored ", exp.busid, " to ", exp.originalDriver) - return nil -} - -func (h *linuxExportHost) shouldRestoreCurrentDevice(exp *linuxExport) (bool, error) { descriptor, err := readSysfsDevice(exp.busid, filepath.Join(sysBusUSBDevices, exp.busid)) if err != nil { if os.IsNotExist(err) || isMissingUSBDeviceError(err) { - return false, nil + h.logger.Info("removed export state for ", exp.busid) + return nil } - return false, E.Cause(err, "read current device ", exp.busid) + return E.Cause(err, "read current device ", exp.busid) } - return exp.identity.Equal(newLinuxExportIdentity(descriptor)), nil + if exp.originalDriver != "" && exp.identity.Equal(newLinuxExportIdentity(descriptor)) { + err = writeSysfs(filepath.Join("/sys/bus/usb/drivers", exp.originalDriver, "bind"), exp.busid) + if err == nil { + h.logger.Info("restored ", exp.busid, " to ", exp.originalDriver) + return nil + } + h.logger.Warn("bind ", exp.busid, " back to ", exp.originalDriver, ": ", err) + } + // Device replaced while exported, original driver unknown, or the + // precise re-bind failed. The USB core never re-probes a driverless + // device on its own — without an explicit re-probe the device stays + // dead until physically replugged. + err = h.reprobeDevice(exp.busid) + if err != nil { + return E.Cause(err, "re-probe ", exp.busid) + } + h.logger.Info("released ", exp.busid, " for driver re-probe") + return nil +} + +// reprobeDevice asks the kernel to attach a driver to a currently +// driverless device. usbip-host's rebind attribute calls +// device_attach — the path the official usbip unbind tool uses; +// drivers_probe is the bus-generic fallback. +func (h *linuxExportHost) reprobeDevice(busid string) error { + err := writeSysfs(filepath.Join(sysUsbipHostDriver, "rebind"), busid) + if err == nil { + return nil + } + probeErr := writeSysfs("/sys/bus/usb/drivers_probe", busid) + if probeErr == nil { + return nil + } + return E.Errors(err, probeErr) } func (h *linuxExportHost) newExport(descriptor sysfsDevice, managed bool, originalDriver string) *linuxExport {