Fix USB/IP unlink and runtime test gating

This commit is contained in:
世界
2026-04-24 05:30:58 +08:00
parent 9ea04956fa
commit 7ab2269998
6 changed files with 120 additions and 34 deletions
+1
View File
@@ -22,6 +22,7 @@ const (
maxUSBIPTransferBufferLength = 16 << 20
maxUSBIPIsoPackets = 4096
nonIsoPacketCount = -1
usbipStatusECONNRESET = -104
)
type DataHeader struct {
+2 -2
View File
@@ -267,7 +267,7 @@ func TestUSBIPUnlinkDelayedFakeTransfer(t *testing.T) {
Direction: unlink.Header.Direction,
Endpoint: unlink.Header.Endpoint,
},
Status: 0,
Status: usbipStatusECONNRESET,
})
}()
@@ -297,7 +297,7 @@ func TestUSBIPUnlinkDelayedFakeTransfer(t *testing.T) {
require.Equal(t, RetUnlink, header.Command)
response, err := ReadUnlinkResponseBody(client, header)
require.NoError(t, err)
require.Equal(t, int32(0), response.Status)
require.Equal(t, int32(usbipStatusECONNRESET), response.Status)
require.NoError(t, <-serverDone)
}
+25 -13
View File
@@ -139,7 +139,9 @@ func ensureTestUDCs(t *testing.T, minCount int) []string {
}
modprobePath, err := findModprobePath()
require.NoError(t, err)
if err != nil {
t.Skipf("dummy_hcd unavailable: %v", err)
}
command := exec.Command(modprobePath, "-r", "dummy_hcd")
command.Env = os.Environ()
@@ -148,13 +150,20 @@ func ensureTestUDCs(t *testing.T, minCount int) []string {
command = exec.Command(modprobePath, "dummy_hcd", "num="+strconv.Itoa(minCount))
command.Env = os.Environ()
output, err := command.CombinedOutput()
require.NoErrorf(t, err, "modprobe dummy_hcd num=%d\n%s", minCount, string(output))
if err != nil {
t.Skipf("dummy_hcd with %d UDCs unavailable: %v\n%s", minCount, err, string(output))
}
require.Eventually(t, func() bool {
return len(currentUDCNames()) >= minCount
}, 5*time.Second, 100*time.Millisecond)
deadline := time.Now().Add(5 * time.Second)
for time.Now().Before(deadline) {
if udcs = currentUDCNames(); len(udcs) >= minCount {
return udcs
}
time.Sleep(100 * time.Millisecond)
}
return currentUDCNames()
t.Skipf("dummy_hcd provided %d UDCs, need %d", len(currentUDCNames()), minCount)
return nil
}
func reserveTestUDC(t *testing.T) string {
@@ -276,6 +285,7 @@ func pickFreeTCPPort(t *testing.T) uint16 {
func startRealUSBIPServer(t *testing.T, devices []option.USBIPDeviceMatch) (*ServerService, M.Socksaddr) {
t.Helper()
requireUSBIPHost(t)
serviceInstance, err := NewServerService(context.Background(), newTestLogger(), "usbip-server-test", option.USBIPServerServiceOptions{
ListenOptions: option.ListenOptions{
@@ -297,6 +307,7 @@ func startRealUSBIPServer(t *testing.T, devices []option.USBIPDeviceMatch) (*Ser
func startRealUSBIPClient(t *testing.T, destination M.Socksaddr, devices []option.USBIPDeviceMatch) *ClientService {
t.Helper()
requireVHCI(t)
serviceInstance, err := NewClientService(context.Background(), newTestLogger(), "usbip-client-test", option.USBIPClientServiceOptions{
ServerOptions: option.ServerOptions{
@@ -853,6 +864,7 @@ func (g *testHIDGadget) exerciseImportedIO(t *testing.T, importedHID string) {
func bindWithOfficialUSBIP(t *testing.T, tools testUSBIPTools, busid string) {
t.Helper()
requireUSBIPHost(t)
if driver, err := currentDriver(busid); err == nil && driver == "usbip-host" {
return
@@ -877,7 +889,7 @@ func TestUSBIPInteropOurServerWithOfficialClientACM(t *testing.T) {
requireRoot(t)
resetUSBIPInteropState(t)
tools := requireUSBIPTools(t)
require.NoError(t, ensureVHCI())
requireVHCI(t)
gadget := newTestACMGadget(t)
server, address := startRealUSBIPServer(t, []option.USBIPDeviceMatch{{Serial: gadget.serial}})
@@ -905,7 +917,7 @@ func TestUSBIPInteropOurServerWithOfficialClientHID(t *testing.T) {
requireRoot(t)
resetUSBIPInteropState(t)
tools := requireUSBIPTools(t)
require.NoError(t, ensureVHCI())
requireVHCI(t)
gadget := newTestHIDGadget(t)
_, address := startRealUSBIPServer(t, []option.USBIPDeviceMatch{{Serial: gadget.serial}})
@@ -931,7 +943,7 @@ func TestUSBIPInteropOurClientWithOfficialServerACM(t *testing.T) {
requireRoot(t)
resetUSBIPInteropState(t)
tools := requireUSBIPTools(t)
require.NoError(t, ensureVHCI())
requireVHCI(t)
gadget := newTestACMGadget(t)
bindWithOfficialUSBIP(t, tools, gadget.busid)
@@ -960,7 +972,7 @@ func TestUSBIPInteropOurClientWithOfficialServerHID(t *testing.T) {
requireRoot(t)
resetUSBIPInteropState(t)
tools := requireUSBIPTools(t)
require.NoError(t, ensureVHCI())
requireVHCI(t)
gadget := newTestHIDGadget(t)
bindWithOfficialUSBIP(t, tools, gadget.busid)
@@ -989,7 +1001,7 @@ func TestUSBIPOfficialServerHasStaticDiscoveryOnly(t *testing.T) {
requireRoot(t)
resetUSBIPInteropState(t)
tools := requireUSBIPTools(t)
require.NoError(t, ensureVHCI())
requireVHCI(t)
first := newTestACMGadget(t)
bindWithOfficialUSBIP(t, tools, first.busid)
@@ -1023,7 +1035,7 @@ func TestUSBIPOfficialServerHasStaticDiscoveryOnly(t *testing.T) {
func TestUSBIPControlHotplugACMReattach(t *testing.T) {
requireRoot(t)
resetUSBIPInteropState(t)
require.NoError(t, ensureVHCI())
requireVHCI(t)
ensureTestUDCs(t, testUDCCount)
server, address := startRealUSBIPServer(t, []option.USBIPDeviceMatch{{
@@ -1056,7 +1068,7 @@ func TestUSBIPControlHotplugACMReattach(t *testing.T) {
func TestUSBIPControlImportAllACMAndHID(t *testing.T) {
requireRoot(t)
resetUSBIPInteropState(t)
require.NoError(t, ensureVHCI())
requireVHCI(t)
ensureTestUDCs(t, testUDCCount)
_, address := startRealUSBIPServer(t, []option.USBIPDeviceMatch{
+29 -7
View File
@@ -380,7 +380,9 @@ func requireKernelModule(t *testing.T, module string) {
}
modprobePath, err := findModprobePath()
require.NoError(t, err)
if err != nil {
t.Skipf("kernel module %s unavailable: %v", module, err)
}
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
defer cancel()
@@ -389,9 +391,25 @@ func requireKernelModule(t *testing.T, module string) {
command.Env = os.Environ()
output, err := command.CombinedOutput()
if ctx.Err() != nil {
t.Fatalf("modprobe %s timed out: %s", module, string(output))
t.Skipf("modprobe %s timed out: %s", module, string(output))
}
if err != nil {
t.Skipf("kernel module %s unavailable: %v: %s", module, err, string(output))
}
}
func requireUSBIPHost(t *testing.T) {
t.Helper()
if err := ensureHostDriver(); err != nil {
t.Skipf("usbip-host unavailable: %v", err)
}
}
func requireVHCI(t *testing.T) {
t.Helper()
if err := ensureVHCI(); err != nil {
t.Skipf("vhci_hcd unavailable: %v", err)
}
require.NoErrorf(t, err, "modprobe %s: %s", module, string(output))
}
func writeSysfsLine(path string, content string) error {
@@ -407,8 +425,12 @@ func newTestUSBGadget(t *testing.T) *testUSBGadget {
requireKernelModule(t, "dummy_hcd")
udcs, err := os.ReadDir("/sys/class/udc")
require.NoError(t, err)
require.NotEmpty(t, udcs)
if err != nil {
t.Skipf("USB device controllers unavailable: %v", err)
}
if len(udcs) == 0 {
t.Skip("USB device controllers unavailable")
}
gadget := &testUSBGadget{
path: filepath.Join("/sys/kernel/config/usb_gadget", fmt.Sprintf("codex_usbip_%d", time.Now().UnixNano())),
@@ -1777,8 +1799,8 @@ func TestClientRunControlSessionSyncsAssignmentsOnChanged(t *testing.T) {
func TestUSBIPLinuxSmoke(t *testing.T) {
requireRoot(t)
require.NoError(t, ensureHostDriver())
require.NoError(t, ensureVHCI())
requireUSBIPHost(t)
requireVHCI(t)
gadget := newTestUSBGadget(t)
device, err := readSysfsDevice(gadget.busid, sysBusDevicePath(gadget.busid))
+32 -12
View File
@@ -483,17 +483,22 @@ type darwinServerDataSession struct {
device *darwinUSBHostDevice
writeMu sync.Mutex
mu sync.Mutex
pending map[uint32]uint8
pending map[uint32]darwinServerPendingSubmit
wg sync.WaitGroup
}
type darwinServerPendingSubmit struct {
endpoint uint8
unlinked bool
}
func newDarwinServerDataSession(ctx context.Context, logger log.ContextLogger, conn net.Conn, device *darwinUSBHostDevice) *darwinServerDataSession {
return &darwinServerDataSession{
ctx: ctx,
logger: logger,
conn: conn,
device: device,
pending: make(map[uint32]uint8),
pending: make(map[uint32]darwinServerPendingSubmit),
}
}
@@ -519,8 +524,10 @@ func (s *darwinServerDataSession) serve() error {
s.wg.Add(1)
go func() {
defer s.wg.Done()
defer s.untrackSubmit(command.Header.SeqNum)
response := s.handleSubmit(command)
if !s.finishSubmit(command.Header.SeqNum) {
return
}
s.writeMu.Lock()
err := WriteSubmitResponse(s.conn, response)
s.writeMu.Unlock()
@@ -533,12 +540,12 @@ func (s *darwinServerDataSession) serve() error {
if err != nil {
return err
}
status := -int32(unix.ECONNRESET)
if endpoint, ok := s.untrackSubmit(command.SeqNum); ok {
status := int32(0)
if endpoint, ok := s.markSubmitUnlinked(command.SeqNum); ok {
if err := s.device.abortEndpoint(endpoint); err != nil {
s.logger.Debug("abort endpoint 0x", hex8(endpoint), ": ", err)
}
status = 0
status = usbipStatusECONNRESET
}
s.writeMu.Lock()
err = WriteUnlinkResponse(s.conn, UnlinkResponse{
@@ -604,17 +611,30 @@ func (s *darwinServerDataSession) handleSubmit(command SubmitCommand) SubmitResp
func (s *darwinServerDataSession) trackSubmit(seq uint32, endpoint uint8) {
s.mu.Lock()
defer s.mu.Unlock()
s.pending[seq] = endpoint
s.pending[seq] = darwinServerPendingSubmit{endpoint: endpoint}
}
func (s *darwinServerDataSession) untrackSubmit(seq uint32) (uint8, bool) {
func (s *darwinServerDataSession) markSubmitUnlinked(seq uint32) (uint8, bool) {
s.mu.Lock()
defer s.mu.Unlock()
endpoint, ok := s.pending[seq]
if ok {
delete(s.pending, seq)
pending, ok := s.pending[seq]
if !ok {
return 0, false
}
return endpoint, ok
pending.unlinked = true
s.pending[seq] = pending
return pending.endpoint, true
}
func (s *darwinServerDataSession) finishSubmit(seq uint32) bool {
s.mu.Lock()
defer s.mu.Unlock()
pending, ok := s.pending[seq]
if !ok {
return true
}
delete(s.pending, seq)
return !pending.unlinked
}
func commandEndpoint(command SubmitCommand) uint8 {
+31
View File
@@ -0,0 +1,31 @@
//go:build darwin && cgo
package usbip
import (
"testing"
"github.com/stretchr/testify/require"
)
func TestDarwinServerPendingSubmitUnlinkState(t *testing.T) {
t.Parallel()
session := &darwinServerDataSession{
pending: make(map[uint32]darwinServerPendingSubmit),
}
const endpoint uint8 = 0x81
session.trackSubmit(7, endpoint)
unlinkedEndpoint, active := session.markSubmitUnlinked(7)
require.True(t, active)
require.Equal(t, endpoint, unlinkedEndpoint)
require.False(t, session.finishSubmit(7))
session.trackSubmit(8, endpoint)
require.True(t, session.finishSubmit(8))
_, active = session.markSubmitUnlinked(8)
require.False(t, active)
}