Fix usbip host bind recovery

This commit is contained in:
世界
2026-04-24 22:48:14 +08:00
parent 7b73e80df3
commit 0e34a2d088
4 changed files with 108 additions and 3 deletions
+68 -1
View File
@@ -201,6 +201,10 @@ func newTestUSBIPOps(t *testing.T) usbipOps {
t.Fatalf("unexpected hostUnbind")
return nil
},
reloadHostDriver: func() error {
t.Fatalf("unexpected reloadHostDriver")
return nil
},
readUsbipStatus: func(string) (int, error) {
t.Fatalf("unexpected readUsbipStatus")
return 0, nil
@@ -837,6 +841,70 @@ func TestServerReconcileExportsBindsMatchesAndSkipsHub(t *testing.T) {
}, server.snapshotExports())
}
func TestServerBindOneRetriesAfterStaleHostMatch(t *testing.T) {
t.Parallel()
device := newTestDevice("1-1", 0x1d6b, 0x0104, "regular", SpeedHigh)
ops := newTestUSBIPOps(t)
var actions []string
bindCalls := 0
ops.currentDriver = func(busid string) (string, error) {
return "usb", nil
}
ops.unbindFromDriver = func(busid, driver string) error {
actions = append(actions, "unbind "+busid+" "+driver)
return nil
}
ops.hostMatchBusID = func(busid string, add bool) error {
actions = append(actions, "match "+busid+" "+map[bool]string{true: "add", false: "del"}[add])
return nil
}
ops.hostBind = func(busid string) error {
bindCalls++
actions = append(actions, "hostbind "+busid)
if bindCalls == 1 {
return &os.PathError{Op: "write", Path: filepath.Join(sysUsbipHostDriver, "bind"), Err: unix.ENODEV}
}
return nil
}
ops.bindToDriver = func(busid, driver string) error {
actions = append(actions, "bind "+busid+" "+driver)
return nil
}
ops.reloadHostDriver = func() error {
actions = append(actions, "reload")
return nil
}
server := &ServerService{
ctx: context.Background(),
logger: newTestLogger(),
exports: make(map[string]serverExport),
controlSubs: make(map[uint64]*serverControlConn),
ops: ops,
}
require.NoError(t, server.bindOne(&device))
require.Equal(t, []string{
"unbind 1-1 usb",
"match 1-1 add",
"hostbind 1-1",
"match 1-1 del",
"bind 1-1 usb",
"reload",
"unbind 1-1 usb",
"match 1-1 add",
"hostbind 1-1",
}, actions)
require.Equal(t, map[string]serverExport{
"1-1": {
busid: "1-1",
managed: true,
originalDriver: "usb",
},
}, server.snapshotExports())
}
func TestServerReconcileExportsSkipsVHCIDevices(t *testing.T) {
t.Parallel()
@@ -1047,7 +1115,6 @@ func TestServerCloseSerializesRollbackWithActiveReconcile(t *testing.T) {
record("hostunbind " + busid)
return nil
}
server := &ServerService{
ctx: ctx,
cancel: cancel,
+2
View File
@@ -19,6 +19,7 @@ type usbipOps struct {
hostMatchBusID func(busid string, add bool) error
hostBind func(busid string) error
hostUnbind func(busid string) error
reloadHostDriver func() error
readUsbipStatus func(busid string) (int, error)
writeUsbipSockfd func(busid string, fd int) error
newUEventListener func() (usbEventListener, error)
@@ -40,6 +41,7 @@ var systemUSBIPOps = usbipOps{
hostMatchBusID: hostMatchBusID,
hostBind: hostBind,
hostUnbind: hostUnbind,
reloadHostDriver: reloadHostDriver,
readUsbipStatus: readUsbipStatus,
writeUsbipSockfd: writeUsbipSockfd,
newUEventListener: func() (usbEventListener, error) {
+26 -2
View File
@@ -173,8 +173,7 @@ func (s *ServerService) reconcileExports() (bool, error) {
continue
}
if err := s.bindOne(&device); err != nil {
s.logger.Warn("bind ", busid, ": ", err)
continue
return changed, E.Cause(err, "bind ", busid)
}
changed = true
}
@@ -192,6 +191,24 @@ func (s *ServerService) reconcileExports() (bool, error) {
}
func (s *ServerService) bindOne(d *sysfsDevice) error {
var err error
for attempt := 0; attempt < 2; attempt++ {
err = s.bindOneOnce(d)
if err == nil {
return nil
}
if attempt > 0 || !errors.Is(err, unix.ENODEV) {
break
}
s.logger.Warn("reset usbip-host after bind failure on ", d.BusID, ": ", err)
if resetErr := s.resetHostDriverForBindRetry(); resetErr != nil {
return E.Cause(resetErr, "reset usbip-host after bind failure")
}
}
return err
}
func (s *ServerService) bindOneOnce(d *sysfsDevice) error {
driver, err := s.ops.currentDriver(d.BusID)
if err != nil {
return err
@@ -228,6 +245,13 @@ func (s *ServerService) bindOne(d *sysfsDevice) error {
return nil
}
func (s *ServerService) resetHostDriverForBindRetry() error {
if len(s.snapshotExports()) > 0 {
return E.New("active usbip-host exports are present")
}
return s.ops.reloadHostDriver()
}
func (s *ServerService) releaseExport(export serverExport, restore bool) error {
if !export.managed {
s.deleteExport(export.busid)
+12
View File
@@ -215,6 +215,18 @@ func hostUnbind(busid string) error {
return writeSysfs(filepath.Join(sysUsbipHostDriver, "unbind"), busid)
}
func reloadHostDriver() error {
modprobePath, err := findModprobePath()
if err != nil {
return err
}
output, err := shell.Exec(modprobePath, "-r", "usbip-host").Read()
if err != nil {
return E.Extend(E.Cause(err, "unload kernel module usbip-host"), strings.TrimSpace(output))
}
return ensureHostDriver()
}
// readUsbipStatus returns the usbip_status attribute value for busid.
func readUsbipStatus(busid string) (int, error) {
raw, err := os.ReadFile(filepath.Join(sysBusUSBDevices, busid, "usbip_status"))