usbip: remove dead code, ceremonial guards, and WHAT comments

This commit is contained in:
世界
2026-05-25 16:16:56 +08:00
parent 3800783232
commit b3fb32abc0
25 changed files with 101 additions and 469 deletions
+8 -18
View File
@@ -177,13 +177,6 @@ type IsoPacket struct {
Status URBError
}
// URBResult mirrors the fields VBoxUSB writes back into the URB struct.
type URBResult struct {
Error URBError
Length uint64
IsoPackets []IsoPacket
}
// urbStructSize is the on-the-wire size of USBSUP_URB with Pack=4 on
// 64-bit systems (both amd64 and arm64; nint is 8 bytes either way).
// Layout (offsets in bytes):
@@ -206,9 +199,6 @@ const urbStructSize = 104
// Caller must keep urb.Buffer alive across the call (SendURB does so
// internally for the duration of the syscall).
func (d *Device) SendURB(urb *URB) error {
if len(urb.IsoPackets) > MaxIsoPacketsPerURB {
return E.New("vboxusb: too many iso packets: ", len(urb.IsoPackets), " > ", MaxIsoPacketsPerURB)
}
var raw [urbStructSize]byte
binary.LittleEndian.PutUint32(raw[0:4], uint32(urb.Type))
binary.LittleEndian.PutUint32(raw[4:8], urb.Endpoint)
@@ -277,14 +267,14 @@ func (e *URBStatusError) Error() string {
}
}
// ioctl is the single synchronous overlapped DeviceIoControl primitive.
// Ported from common/windivert/handle_windows.go:263-290. The event is
// the per-Device event (reused across calls) so we avoid CreateEvent on
// every URB.
func (d *Device) ioctl(code uint32, in []byte, out []byte) (uint32, error) {
return overlappedIoctl(d.handle, code, in, out, d.event)
}
func overlappedIoctl(handle windows.Handle, code uint32, in []byte, out []byte, event windows.Handle) (uint32, error) {
var overlapped windows.Overlapped
overlapped.HEvent = d.event
_ = windows.ResetEvent(d.event)
overlapped.HEvent = event
_ = windows.ResetEvent(event)
var inPtr *byte
var inLen uint32
if len(in) > 0 {
@@ -298,11 +288,11 @@ func (d *Device) ioctl(code uint32, in []byte, out []byte) (uint32, error) {
outLen = uint32(len(out))
}
var returned uint32
err := windows.DeviceIoControl(d.handle, code, inPtr, inLen, outPtr, outLen, &returned, &overlapped)
err := windows.DeviceIoControl(handle, code, inPtr, inLen, outPtr, outLen, &returned, &overlapped)
if err != nil && !errors.Is(err, windows.ERROR_IO_PENDING) {
return 0, err
}
err = windows.GetOverlappedResult(d.handle, &overlapped, &returned, true)
err = windows.GetOverlappedResult(handle, &overlapped, &returned, true)
if err != nil {
return 0, err
}
-4
View File
@@ -42,10 +42,6 @@ var (
)
func installDrivers() error {
if runtime.GOARCH != "amd64" && runtime.GOARCH != "arm64" {
return E.New("vboxusb: unsupported GOARCH ", runtime.GOARCH)
}
dir, err := ensureExtracted()
if err != nil {
return err
+1 -25
View File
@@ -126,31 +126,7 @@ func (m *Monitor) RemoveFilter(id uint64) error {
}
func (m *Monitor) ioctl(code uint32, in []byte, out []byte) (uint32, error) {
var overlapped windows.Overlapped
overlapped.HEvent = m.event
_ = windows.ResetEvent(m.event)
var inPtr *byte
var inLen uint32
if len(in) > 0 {
inPtr = &in[0]
inLen = uint32(len(in))
}
var outPtr *byte
var outLen uint32
if len(out) > 0 {
outPtr = &out[0]
outLen = uint32(len(out))
}
var returned uint32
err := windows.DeviceIoControl(m.handle, code, inPtr, inLen, outPtr, outLen, &returned, &overlapped)
if err != nil && !errors.Is(err, windows.ERROR_IO_PENDING) {
return 0, err
}
err = windows.GetOverlappedResult(m.handle, &overlapped, &returned, true)
if err != nil {
return 0, err
}
return returned, nil
return overlappedIoctl(m.handle, code, in, out, m.event)
}
// encodeFilter builds a 312-byte USBFILTER packed struct matching the
+17 -111
View File
@@ -3,19 +3,30 @@
package vboxusb
import (
"encoding/binary"
"errors"
"strconv"
"strings"
"sync"
"time"
"unsafe"
E "github.com/sagernet/sing/common/exceptions"
"golang.org/x/sys/windows"
)
var MonitorAccessGUID = windows.GUID{
Data1: 0x00873fdf,
Data2: 0xCAFE,
Data3: 0x80EE,
Data4: [8]byte{0xaa, 0x5e, 0x00, 0xc0, 0x4f, 0xb1, 0x72, 0x0b},
}
var USBDeviceInterfaceGUID = windows.GUID{
Data1: 0xa5dcbf10,
Data2: 0x6530,
Data3: 0x11d2,
Data4: [8]byte{0x90, 0x1f, 0x00, 0xc0, 0x4f, 0xb9, 0x51, 0xed},
}
// USBDeviceInfo describes one USB device enumerated by Windows
// regardless of which function driver currently owns it. Bus/Address
// values are normalized into a stable bus-id string ("<bus>-<address>")
@@ -36,13 +47,9 @@ type USBDeviceInfo struct {
// one record per attached USB device. Hubs are not filtered out here;
// the caller (export host) skips DeviceClass == 0x09.
func EnumerateUSBDevices() ([]USBDeviceInfo, error) {
guid := USBDeviceInterfaceGUID
devInfo, err := windows.SetupDiGetClassDevsEx(
&windows.GUID{
Data1: USBDeviceInterfaceGUID.Data1,
Data2: USBDeviceInterfaceGUID.Data2,
Data3: USBDeviceInterfaceGUID.Data3,
Data4: USBDeviceInterfaceGUID.Data4,
},
&guid,
"",
0,
windows.DIGCF_PRESENT|windows.DIGCF_DEVICEINTERFACE,
@@ -87,62 +94,8 @@ func EnumerateUSBDevices() ([]USBDeviceInfo, error) {
return out, nil
}
// CycleHubPort issues IOCTL_USB_HUB_CYCLE_PORT on the parent hub for
// the given 1-based port number. This is the supported equivalent of
// physically unplugging and replugging the device; combined with a
// VBoxUSBMon filter it triggers PnP to bind VBoxUSB to the device.
//
// hubInterfacePath is the setupapi-resolved interface path of the
// parent hub (obtained via CM_Get_Device_Interface_List with
// USBHubInterfaceGUID).
func CycleHubPort(hubInterfacePath string, port uint32) error {
pathW, err := windows.UTF16PtrFromString(hubInterfacePath)
if err != nil {
return E.Cause(err, "vboxusb: utf16 hub path")
}
handle, err := windows.CreateFile(
pathW,
windows.GENERIC_READ|windows.GENERIC_WRITE,
windows.FILE_SHARE_READ|windows.FILE_SHARE_WRITE,
nil,
windows.OPEN_EXISTING,
windows.FILE_ATTRIBUTE_NORMAL,
0,
)
if err != nil {
return E.Cause(err, "vboxusb: open hub ", hubInterfacePath)
}
defer windows.CloseHandle(handle)
// USB_CYCLE_PORT_PARAMS: ULONG ConnectionIndex; ULONG StatusReturned;
var params [8]byte
binary.LittleEndian.PutUint32(params[0:4], port)
var returned uint32
err = windows.DeviceIoControl(
handle,
IOCTLHubCyclePort,
&params[0], uint32(len(params)),
&params[0], uint32(len(params)),
&returned, nil,
)
if err != nil {
return E.Cause(err, "vboxusb: IOCTL_USB_HUB_CYCLE_PORT")
}
return nil
}
// WaitForVBoxUSBInterface polls CM_Get_Device_Interface_List for the
// VBoxUSB class GUID until an interface path matching instanceID
// appears, or timeout elapses. After RestartDevice + filter trigger,
// PnP needs a moment to load VBoxUSB.sys on the device; matching
// usbipd-win's 10-second window.
func WaitForVBoxUSBInterface(instanceID string, timeout time.Duration) (string, error) {
guid := windows.GUID{
Data1: MonitorAccessGUID.Data1,
Data2: MonitorAccessGUID.Data2,
Data3: MonitorAccessGUID.Data3,
Data4: MonitorAccessGUID.Data4,
}
guid := MonitorAccessGUID
deadline := time.Now().Add(timeout)
for {
paths, err := windows.CM_Get_Device_Interface_List(instanceID, &guid, windows.CM_GET_DEVICE_INTERFACE_LIST_PRESENT)
@@ -160,39 +113,6 @@ func WaitForVBoxUSBInterface(instanceID string, timeout time.Duration) (string,
}
}
// RestartDevice triggers the PnP unplug/replug cycle that lets a
// pending VBoxUSBMon CAPTURE filter activate. usbipd-win uses
// CM_Query_And_Remove_SubTree -> IOCTL_USB_HUB_CYCLE_PORT ->
// CM_Setup_DevNode.
//
// TODO(phase B follow-up): CM_Query_And_Remove_SubTree and
// CM_Setup_DevNode are not exposed by golang.org/x/sys/windows and
// need direct LazyDLL wrappers. Until that is in place, callers
// should arrange for the device to be physically replugged.
func RestartDevice(_ string) error {
return E.New("vboxusb: RestartDevice not yet implemented (use CycleHubPort for now)")
}
// WatchDeviceArrival registers a callback for device arrival/removal
// on GUID_DEVINTERFACE_USB_DEVICE. Returns a handle that must be
// closed when the watcher is no longer needed.
//
// TODO(phase B follow-up): CM_Register_Notification is not exposed by
// golang.org/x/sys/windows. Until wired up, the export host will need
// to poll via EnumerateUSBDevices on a reconcile timer.
func WatchDeviceArrival(_ func()) (DeviceWatcher, error) {
return nil, E.New("vboxusb: WatchDeviceArrival not yet implemented (poll EnumerateUSBDevices instead)")
}
// DeviceWatcher is the handle returned by WatchDeviceArrival. Close
// stops delivery and releases the underlying CM_Notify_HNOTIFICATION.
type DeviceWatcher interface {
Close() error
}
// firstString returns the first NUL-separated string in a REG_MULTI_SZ
// value (returned by SetupDiGetDeviceRegistryProperty for HardwareID).
// Returns the value unchanged for REG_SZ.
func firstString(value any) string {
switch v := value.(type) {
case string:
@@ -215,9 +135,6 @@ func toUint32(value any) uint32 {
return 0
}
// parseHardwareID extracts VID/PID/REV from a USB hardware ID such as
// "USB\VID_046D&PID_C31C&REV_6400". Returns zeros on parse failure;
// the caller decides whether that disqualifies the device.
func parseHardwareID(hwid string) (vid, pid, rev uint16) {
upper := strings.ToUpper(hwid)
vid = extractHex16(upper, "VID_")
@@ -252,14 +169,3 @@ func extractHex16(s, prefix string) uint16 {
func isHex(r rune) bool {
return (r >= '0' && r <= '9') || (r >= 'A' && r <= 'F') || (r >= 'a' && r <= 'f')
}
// pnpProcOnce + LazyProc handles for direct syscall to functions that
// golang.org/x/sys/windows does not yet wrap. Kept in one place so the
// RestartDevice / WatchDeviceArrival follow-up has the resolver
// boilerplate already laid out.
var (
pnpProcOnce sync.Once
modCfgMgr32 = windows.NewLazyDLL("cfgmgr32.dll")
_ = modCfgMgr32 // referenced by upcoming RestartDevice impl
_ unsafe.Pointer
)
-45
View File
@@ -43,46 +43,6 @@ const (
MonitorDevicePath = `\\.\VBoxUSBMon`
)
// MonitorAccessGUID is GUID_CLASS_VBOXUSB from VirtualBox usblib-win.h.
// Used with SetupDiEnumDeviceInterfaces to find the per-device file
// path after VBoxUSB binds.
var MonitorAccessGUID = GUID{
Data1: 0x00873fdf,
Data2: 0xCAFE,
Data3: 0x80EE,
Data4: [8]byte{0xaa, 0x5e, 0x00, 0xc0, 0x4f, 0xb1, 0x72, 0x0b},
}
// USBDeviceInterfaceGUID is GUID_DEVINTERFACE_USB_DEVICE
// ({a5dcbf10-6530-11d2-901f-00c04fb951ed}). Used to enumerate plugged
// USB devices regardless of which function driver currently owns them.
var USBDeviceInterfaceGUID = GUID{
Data1: 0xa5dcbf10,
Data2: 0x6530,
Data3: 0x11d2,
Data4: [8]byte{0x90, 0x1f, 0x00, 0xc0, 0x4f, 0xb9, 0x51, 0xed},
}
// USBHubInterfaceGUID is GUID_DEVINTERFACE_USB_HUB
// ({f18a0e88-c30c-11d0-8815-00a0c906bed8}). The parent hub of a target
// device is opened with this GUID to issue IOCTL_USB_HUB_CYCLE_PORT.
var USBHubInterfaceGUID = GUID{
Data1: 0xf18a0e88,
Data2: 0xc30c,
Data3: 0x11d0,
Data4: [8]byte{0x88, 0x15, 0x00, 0xa0, 0xc9, 0x06, 0xbe, 0xd8},
}
// GUID matches the Windows GUID layout exactly. We carry our own copy
// so the (cross-platform) package-level vars above can be declared
// without depending on golang.org/x/sys/windows.
type GUID struct {
Data1 uint32
Data2 uint16
Data3 uint16
Data4 [8]byte
}
// IOCTL codes from VirtualBox usblib-win.h, identical to those used by
// usbipd-win (Usbipd/Interop/VBoxUsb.cs:26-39 and VBoxUsbMon.cs:122-129).
// Encoding is the standard CTL_CODE shape:
@@ -108,11 +68,6 @@ const (
IOCTLMonitorRemoveFilter uint32 = 0x0022_1848
)
// IOCTLHubCyclePort = IOCTL_USB_HUB_CYCLE_PORT from usbioctl.h
// (FILE_DEVICE_USB=0x22, FILE_ANY_ACCESS=0, function=0x111,
// METHOD_BUFFERED=0).
const IOCTLHubCyclePort uint32 = 0x0022_0444
// USB/IP-style transfer type enum, matching VirtualBox USBSUP_TRANSFER_TYPE.
type TransferType uint32
+2 -2
View File
@@ -32,8 +32,8 @@ type ClientService struct {
assignedWorkers []*clientAssignedWorker
allWorkers map[string]context.CancelFunc
remoteAccess sync.Mutex
remoteDevicesV2 map[string]DeviceInfoV2
remoteAccess sync.Mutex
remoteDevices map[string]ControlDeviceInfo
}
func NewClientService(ctx context.Context, logger log.ContextLogger, tag string, options option.USBIPClientServiceOptions) (adapter.Service, error) {
-6
View File
@@ -9,9 +9,6 @@ import (
"github.com/sagernet/sing-box/option"
)
// clientAssignment runs in two modes that share active-busid tracking:
// matched (len(targets) > 0) binds each target to at most one busid;
// import-all (len(targets) == 0) marks every advertised busid as desired.
type clientAssignment struct {
access sync.Mutex
@@ -82,9 +79,6 @@ func (a *clientAssignment) ApplyMatched(entries []DeviceEntry, knownKeys map[str
return nextAssigned, prev
}
// ApplyAll keeps no-longer-desired-but-active busids registered so the
// runBusIDLoop exits naturally after the active session ends, via
// IsRetryDesired returning false.
func (a *clientAssignment) ApplyAll(entries []DeviceEntry) (start []string, stop []string) {
desired := make(map[string]struct{}, len(entries))
for i := range entries {
+1 -19
View File
@@ -42,7 +42,6 @@ type darwinVirtualController struct {
runErr error
eventStarted atomic.Bool
stateAccess sync.Mutex
powered bool
connected bool
nextAddress uint8
@@ -136,7 +135,6 @@ func (c *darwinVirtualController) enqueueEvent(event darwinControllerEvent) {
}
func (c *darwinVirtualController) eventLoop() {
c.eventStarted.Store(true)
defer close(c.eventDone)
defer c.teardownIOUSBHostState()
for {
@@ -200,27 +198,21 @@ func (c *darwinVirtualController) handleDeviceCreate(message darwinCIMessage) er
if err != nil {
return err
}
c.stateAccess.Lock()
address := c.nextAddress
c.nextAddress++
c.devices[address] = device
c.stateAccess.Unlock()
return device.respondCreate(message, ciStatusSuccess, address)
}
func (c *darwinVirtualController) handleDeviceCommand(message darwinCIMessage) error {
address := message.deviceAddress()
c.stateAccess.Lock()
device := c.devices[address]
c.stateAccess.Unlock()
if device == nil {
return nil
}
err := device.respond(message, ciStatusSuccess)
if message.messageType() == ciMsgDeviceDestroy {
c.stateAccess.Lock()
delete(c.devices, address)
c.stateAccess.Unlock()
device.Close()
}
return err
@@ -233,21 +225,17 @@ func (c *darwinVirtualController) handleEndpointCreate(message darwinCIMessage)
}
key := darwinEndpointKey{device: message.deviceAddress(), endpoint: message.endpointAddress()}
endpoint := newDarwinEndpoint(c.ctx, c.logger, sm, c.peer, c.CurrentFrame, c.info.DevID(), key)
c.stateAccess.Lock()
c.endpoints[key] = endpoint
c.stateAccess.Unlock()
return sm.respond(message, ciStatusSuccess)
}
func (c *darwinVirtualController) handleEndpointCommand(message darwinCIMessage) error {
key := darwinEndpointKey{device: message.deviceAddress(), endpoint: message.endpointAddress()}
destroy := message.messageType() == ciMsgEndpointDestroy
c.stateAccess.Lock()
endpoint := c.endpoints[key]
if destroy {
delete(c.endpoints, key)
}
c.stateAccess.Unlock()
if endpoint == nil {
return nil
}
@@ -263,9 +251,7 @@ func (c *darwinVirtualController) handleDoorbell(doorbell uint32) {
device: uint8(doorbell & 0xff),
endpoint: uint8((doorbell >> 8) & 0xff),
}
c.stateAccess.Lock()
endpoint := c.endpoints[key]
c.stateAccess.Unlock()
if endpoint == nil {
return
}
@@ -273,7 +259,6 @@ func (c *darwinVirtualController) handleDoorbell(doorbell uint32) {
}
func (c *darwinVirtualController) teardownIOUSBHostState() {
c.stateAccess.Lock()
endpoints := make([]*darwinEndpoint, 0, len(c.endpoints))
for _, endpoint := range c.endpoints {
endpoints = append(endpoints, endpoint)
@@ -286,7 +271,6 @@ func (c *darwinVirtualController) teardownIOUSBHostState() {
c.devices = make(map[uint8]*darwinUSBHostDeviceSM)
controller := c.controller
c.controller = nil
c.stateAccess.Unlock()
for _, endpoint := range endpoints {
endpoint.Close()
@@ -294,7 +278,5 @@ func (c *darwinVirtualController) teardownIOUSBHostState() {
for _, device := range devices {
device.Close()
}
if controller != nil {
controller.Close()
}
controller.Close()
}
+5 -9
View File
@@ -115,10 +115,6 @@ func (c *ClientService) run() {
}
}
// Dynamic export discovery and hotplug updates are provided by the sing-box
// USB/IP control extensions. Standard USB/IP implementations expose only a
// static DEVLIST snapshot, so we seed assignments once and do not keep polling
// for newly exported devices.
func (c *ClientService) runStandardStaticMode() error {
err := c.syncRemoteStateContext(c.ctx)
if err != nil {
@@ -199,10 +195,10 @@ func (c *ClientService) runControlSession() error {
if err != nil {
return E.Cause(errImmediateReconnect, "read device snapshot: ", err)
}
devices := deviceInfoV2Map(snapshot.Devices)
values := sortedDeviceInfoV2Values(devices)
devices := controlDeviceInfoMap(snapshot.Devices)
values := sortedControlDeviceInfoValues(devices)
c.remoteAccess.Lock()
c.remoteDevicesV2 = devices
c.remoteDevices = devices
c.remoteAccess.Unlock()
c.applyRemoteDeviceState(values)
case controlFramePong:
@@ -249,8 +245,8 @@ func (c *ClientService) syncRemoteStateContext(ctx context.Context) error {
return nil
}
func (c *ClientService) applyRemoteDeviceState(devices []DeviceInfoV2) {
availableEntries := deviceInfoV2ToEntries(devices, true)
func (c *ClientService) applyRemoteDeviceState(devices []ControlDeviceInfo) {
availableEntries := controlDeviceInfoToEntries(devices, true)
if !c.assignment.Matched() {
c.applyRemoteExports(availableEntries)
return
+37 -46
View File
@@ -16,9 +16,9 @@ const (
controlFrameHello uint8 = 1
controlFrameAck uint8 = 2
controlFramePing uint8 = 4
controlFramePong uint8 = 5
controlFrameDeviceSnapshot uint8 = 6
controlFramePing uint8 = 3
controlFramePong uint8 = 4
controlFrameDeviceSnapshot uint8 = 5
controlPrefaceSize = 8
controlFrameSize = 4
@@ -46,40 +46,39 @@ type controlMessage struct {
Payload []byte
}
type DeviceInterfaceV2 struct {
type ControlDeviceInterface struct {
Class uint8 `json:"class"`
SubClass uint8 `json:"subclass"`
Protocol uint8 `json:"protocol"`
}
type DeviceInfoV2 struct {
BusID string `json:"busid"`
StableID string `json:"stable_id,omitempty"`
Backend string `json:"backend,omitempty"`
Path string `json:"path,omitempty"`
Serial string `json:"serial,omitempty"`
VendorID uint16 `json:"vendor_id"`
ProductID uint16 `json:"product_id"`
BCDDevice uint16 `json:"bcd_device,omitempty"`
Speed uint32 `json:"speed"`
DeviceClass uint8 `json:"device_class"`
DeviceSubClass uint8 `json:"device_subclass"`
DeviceProtocol uint8 `json:"device_protocol"`
ConfigurationValue uint8 `json:"configuration_value"`
NumConfigurations uint8 `json:"num_configurations"`
NumInterfaces uint8 `json:"num_interfaces"`
Interfaces []DeviceInterfaceV2 `json:"interfaces,omitempty"`
State string `json:"state"`
StatusCode int `json:"status_code,omitempty"`
StatusReason string `json:"status_reason,omitempty"`
type ControlDeviceInfo struct {
BusID string `json:"busid"`
StableID string `json:"stable_id,omitempty"`
Backend string `json:"backend,omitempty"`
Path string `json:"path,omitempty"`
Serial string `json:"serial,omitempty"`
VendorID uint16 `json:"vendor_id"`
ProductID uint16 `json:"product_id"`
BCDDevice uint16 `json:"bcd_device,omitempty"`
Speed uint32 `json:"speed"`
DeviceClass uint8 `json:"device_class"`
DeviceSubClass uint8 `json:"device_subclass"`
DeviceProtocol uint8 `json:"device_protocol"`
ConfigurationValue uint8 `json:"configuration_value"`
NumConfigurations uint8 `json:"num_configurations"`
NumInterfaces uint8 `json:"num_interfaces"`
Interfaces []ControlDeviceInterface `json:"interfaces,omitempty"`
State string `json:"state"`
StatusCode int `json:"status_code,omitempty"`
StatusReason string `json:"status_reason,omitempty"`
}
type controlDeviceSnapshot struct {
Devices []DeviceInfoV2 `json:"devices"`
Devices []ControlDeviceInfo `json:"devices"`
}
// controlReader reuses its payload scratch across successive reads on a
// single connection. The returned payload is only valid until the next call.
// The returned payload is only valid until the next call.
type controlReader struct {
scratch []byte
}
@@ -151,10 +150,10 @@ func unmarshalControlPayload(payload []byte, value any) error {
return json.Unmarshal(payload, value)
}
func deviceInfoV2FromEntry(entry DeviceEntry, backend string, stableID string, state string, statusCode int, statusReason string) DeviceInfoV2 {
interfaces := make([]DeviceInterfaceV2, len(entry.Interfaces))
func controlDeviceInfoFromEntry(entry DeviceEntry, backend string, stableID string, state string, statusCode int, statusReason string) ControlDeviceInfo {
interfaces := make([]ControlDeviceInterface, len(entry.Interfaces))
for i := range entry.Interfaces {
interfaces[i] = DeviceInterfaceV2{
interfaces[i] = ControlDeviceInterface{
Class: entry.Interfaces[i].BInterfaceClass,
SubClass: entry.Interfaces[i].BInterfaceSubClass,
Protocol: entry.Interfaces[i].BInterfaceProtocol,
@@ -167,7 +166,7 @@ func deviceInfoV2FromEntry(entry DeviceEntry, backend string, stableID string, s
if serial == "" {
serial = entry.Info.SerialString()
}
return DeviceInfoV2{
return ControlDeviceInfo{
BusID: entry.Info.BusIDString(),
StableID: stableID,
Backend: backend,
@@ -190,8 +189,8 @@ func deviceInfoV2FromEntry(entry DeviceEntry, backend string, stableID string, s
}
}
func deviceInfoV2Map(devices []DeviceInfoV2) map[string]DeviceInfoV2 {
out := make(map[string]DeviceInfoV2, len(devices))
func controlDeviceInfoMap(devices []ControlDeviceInfo) map[string]ControlDeviceInfo {
out := make(map[string]ControlDeviceInfo, len(devices))
for _, device := range devices {
if device.BusID == "" {
continue
@@ -201,20 +200,20 @@ func deviceInfoV2Map(devices []DeviceInfoV2) map[string]DeviceInfoV2 {
return out
}
func sortedDeviceInfoV2Values(devices map[string]DeviceInfoV2) []DeviceInfoV2 {
func sortedControlDeviceInfoValues(devices map[string]ControlDeviceInfo) []ControlDeviceInfo {
busids := make([]string, 0, len(devices))
for busid := range devices {
busids = append(busids, busid)
}
slices.Sort(busids)
out := make([]DeviceInfoV2, 0, len(busids))
out := make([]ControlDeviceInfo, 0, len(busids))
for _, busid := range busids {
out = append(out, devices[busid])
}
return out
}
func deviceInfoV2ToEntries(devices []DeviceInfoV2, availableOnly bool) []DeviceEntry {
func controlDeviceInfoToEntries(devices []ControlDeviceInfo, availableOnly bool) []DeviceEntry {
entries := make([]DeviceEntry, 0, len(devices))
for _, device := range devices {
if availableOnly && device.State != "" && device.State != deviceStateAvailable {
@@ -246,7 +245,7 @@ func deviceInfoV2ToEntries(devices []DeviceInfoV2, availableOnly bool) []DeviceE
return entries
}
func deviceInfoV2Equal(a, b DeviceInfoV2) bool {
func controlDeviceInfoEqual(a, b ControlDeviceInfo) bool {
if a.BusID != b.BusID ||
a.StableID != b.StableID ||
a.Backend != b.Backend ||
@@ -267,13 +266,5 @@ func deviceInfoV2Equal(a, b DeviceInfoV2) bool {
a.StatusReason != b.StatusReason {
return false
}
if len(a.Interfaces) != len(b.Interfaces) {
return false
}
for i := range a.Interfaces {
if a.Interfaces[i] != b.Interfaces[i] {
return false
}
}
return true
return slices.Equal(a.Interfaces, b.Interfaces)
}
+1 -1
View File
@@ -279,7 +279,7 @@ func (s *darwinFakeUSBIPServer) handleControlConn(conn net.Conn) {
Type: controlFrameDeviceSnapshot,
Version: controlProtocolVersion,
}, controlDeviceSnapshot{
Devices: []DeviceInfoV2{deviceInfoV2FromEntry(s.entry, "darwin-fake", "darwin-fake:"+s.entry.Info.BusIDString(), deviceStateAvailable, 0, "available")},
Devices: []ControlDeviceInfo{controlDeviceInfoFromEntry(s.entry, "darwin-fake", "darwin-fake:"+s.entry.Info.BusIDString(), deviceStateAvailable, 0, "available")},
})
for {
message, err := cr.read(conn)
+1 -6
View File
@@ -202,8 +202,6 @@ func (e *darwinEndpoint) finalizePending(pending *pendingTransfer) {
}
}
// validateResponse reconciles a RET_SUBMIT against the original request shape.
// All wire-shape rules live here so accept can assume well-formed input.
func (p *pendingTransfer) validateResponse(response SubmitResponse) (int32, error) {
if response.ActualLength < 0 {
return -int32(unix.EPROTO), E.New("RET_SUBMIT actual_length is negative: ", response.ActualLength)
@@ -226,10 +224,7 @@ func (p *pendingTransfer) validateResponse(response SubmitResponse) (int32, erro
return 0, nil
}
// accept validates a RET_SUBMIT against the original request and, for IN
// transfers, scatters the payload into the Apple-owned buffer. A non-nil err
// signals a protocol violation; the caller is expected to cancel the endpoint
// so no further wire-corrupt completions are delivered to IOUSBHost.
// A non-nil err signals a protocol violation; the caller must cancel the endpoint.
func (p *pendingTransfer) accept(response SubmitResponse) (int32, int, error) {
errStatus, err := p.validateResponse(response)
if err != nil {
+1 -4
View File
@@ -2,10 +2,7 @@
package usbip
// darwinIOUSBHostEngine drives one captured IOUSBHost device for the
// duration of one USBIP attachment. The device handle itself is owned
// by darwinExportHost (kept across attachments for re-capture), so
// Close here is intentionally a no-op.
// Close is a no-op: darwinExportHost owns the device handle across attachments.
type darwinIOUSBHostEngine struct {
device *darwinUSBHostDevice
}
+1 -19
View File
@@ -3,15 +3,10 @@
package usbip
import (
"errors"
"github.com/sagernet/sing-box/common/vboxusb"
E "github.com/sagernet/sing/common/exceptions"
)
// vboxusbEngine adapts a vboxusb.Device into the URBEngine interface.
// It owns the device handle for the lifetime of one userspaceURBSession
// (closed via Close).
type vboxusbEngine struct {
device *vboxusb.Device
}
@@ -52,9 +47,6 @@ func (e *vboxusbEngine) Close() error {
return e.device.Close()
}
// trapStandardControl returns (response, true) when the EP0 transfer
// is one VBoxUSB requires us to translate. (zero, false) means the
// caller should proceed with a normal control SEND_URB.
func (e *vboxusbEngine) trapStandardControl(command SubmitCommand) (URBResponse, bool) {
bmRequestType := command.Setup[0]
bRequest := command.Setup[1]
@@ -147,10 +139,6 @@ func (e *vboxusbEngine) bulkSubmit(req URBRequest, transferType vboxusb.Transfer
return resp
}
// isoSubmit currently rejects iso transfers exceeding VBoxUSB's 8-
// packet-per-URB limit. usbipd-win splits these into multiple
// parallel SEND_URB calls sharing one pinned buffer; that splitter is
// a Phase C follow-up. Single-shot iso under 8 packets does work.
func (e *vboxusbEngine) isoSubmit(req URBRequest) URBResponse {
command := req.Command
if len(command.IsoPackets) > vboxusb.MaxIsoPacketsPerURB {
@@ -215,22 +203,16 @@ func flagsFromCommand(transferFlags int32, usbipDir uint32) vboxusb.TransferFlag
return vboxusb.TransferFlagShortOK
}
// classifyURBError returns (status, ok). ok=true means the URB completed
// (with possibly a non-zero device-level status); ok=false means a
// transport failure that the caller surfaces as URBResponse.Error.
func classifyURBError(err error) (int32, bool) {
if err == nil {
return 0, true
}
var statusErr *vboxusb.URBStatusError
if errors.As(err, &statusErr) {
if statusErr, isStatus := E.Cast[*vboxusb.URBStatusError](err); isStatus {
return vboxusbStatusToUSBIP(statusErr.Code), true
}
return 0, false
}
// vboxusbStatusToUSBIP maps USBSUP_ERROR onto the USBIP (Linux errno)
// wire status convention.
func vboxusbStatusToUSBIP(code vboxusb.URBError) int32 {
switch code {
case vboxusb.URBOK:
+12 -18
View File
@@ -24,7 +24,7 @@ type exportLedger struct {
broadcastAccess sync.Mutex
nextSubID uint64
subs map[uint64]*exportSubscriber
state map[string]DeviceInfoV2
state map[string]ControlDeviceInfo
inventoryAccess sync.Mutex
exports map[string]Export
@@ -47,7 +47,7 @@ func newExportLedger(logger log.ContextLogger, now func() time.Time) *exportLedg
logger: logger,
now: now,
subs: make(map[uint64]*exportSubscriber),
state: make(map[string]DeviceInfoV2),
state: make(map[string]ControlDeviceInfo),
exports: make(map[string]Export),
busy: make(map[string]bool),
}
@@ -70,8 +70,6 @@ func (l *exportLedger) withInventoryRead(body func()) {
body()
}
// withInventoryWriteQuiet is for mutations whose broadcast is the
// caller's responsibility (paired with BroadcastIfChanged or shutdown).
func (l *exportLedger) withInventoryWriteQuiet(body func()) {
l.inventoryAccess.Lock()
defer l.inventoryAccess.Unlock()
@@ -115,22 +113,22 @@ func (l *exportLedger) ApplyHostSnapshot(snapshot map[string]Export, released []
}
func (l *exportLedger) SeedBroadcastState() {
nextState := deviceInfoV2Map(l.snapshotDeviceState())
nextState := controlDeviceInfoMap(l.snapshotDeviceState())
l.broadcastAccess.Lock()
l.state = nextState
l.broadcastAccess.Unlock()
}
func (l *exportLedger) BroadcastIfChanged() bool {
nextState := deviceInfoV2Map(l.snapshotDeviceState())
nextState := controlDeviceInfoMap(l.snapshotDeviceState())
l.broadcastAccess.Lock()
if maps.EqualFunc(l.state, nextState, deviceInfoV2Equal) {
if maps.EqualFunc(l.state, nextState, controlDeviceInfoEqual) {
l.broadcastAccess.Unlock()
return false
}
l.state = nextState
devices := sortedDeviceInfoV2Values(nextState)
devices := sortedControlDeviceInfoValues(nextState)
targets := make([]*exportSubscriber, 0, len(l.subs))
for _, sub := range l.subs {
targets = append(targets, sub)
@@ -145,8 +143,7 @@ func (l *exportLedger) BroadcastIfChanged() bool {
return true
}
// TryReserveForImport atomically reserves an exported busid. The single
// critical section under inventoryAccess closes the TOCTOU window between
// The single critical section closes the TOCTOU window between
// availability check and busy mark. Caller must pair success with
// ReleaseImport and broadcast once the session is wired up.
func (l *exportLedger) TryReserveForImport(busid string) (Export, bool, string) {
@@ -201,21 +198,18 @@ func (l *exportLedger) Subscribe(conn net.Conn) *exportSubscriber {
l.enqueuePayload(sub, controlFrame{
Type: controlFrameDeviceSnapshot,
Version: controlProtocolVersion,
}, controlDeviceSnapshot{Devices: sortedDeviceInfoV2Values(l.state)})
}, controlDeviceSnapshot{Devices: sortedControlDeviceInfoValues(l.state)})
l.subs[sub.id] = sub
return sub
}
// Unsubscribe leaves the subscriber's send channel for the GC to
// reclaim; the transport read loop has already exited.
func (l *exportLedger) Unsubscribe(sub *exportSubscriber) {
l.broadcastAccess.Lock()
delete(l.subs, sub.id)
l.broadcastAccess.Unlock()
}
// CloseAllSubscribers returns the underlying connections so the caller
// can close them outside any lock.
// Returns connections to close outside the lock.
func (l *exportLedger) CloseAllSubscribers() []net.Conn {
l.broadcastAccess.Lock()
conns := make([]net.Conn, 0, len(l.subs))
@@ -234,7 +228,7 @@ func (l *exportLedger) ResetForClose() {
})
}
func (l *exportLedger) snapshotDeviceState() []DeviceInfoV2 {
func (l *exportLedger) snapshotDeviceState() []ControlDeviceInfo {
type entry struct {
export Export
busy bool
@@ -252,13 +246,13 @@ func (l *exportLedger) snapshotDeviceState() []DeviceInfoV2 {
slices.SortFunc(entries, func(a, b entry) int {
return strings.Compare(a.export.BusID(), b.export.BusID())
})
out := make([]DeviceInfoV2, 0, len(entries))
out := make([]ControlDeviceInfo, 0, len(entries))
for _, e := range entries {
snapshot := e.export.Snapshot(e.busy)
if snapshot.State == deviceStateUnavailable && snapshot.Entry.Info.BusIDString() == "" {
continue
}
out = append(out, deviceInfoV2FromEntry(snapshot.Entry, snapshot.Backend, snapshot.StableID, snapshot.State, snapshot.RawStatus, snapshot.StatusReason))
out = append(out, controlDeviceInfoFromEntry(snapshot.Entry, snapshot.Backend, snapshot.StableID, snapshot.State, snapshot.RawStatus, snapshot.StatusReason))
}
return out
}
-4
View File
@@ -165,10 +165,6 @@ func (h *kernelHandoffSession) Start() error {
}
func (h *kernelHandoffSession) runDirect(ctx context.Context, logger log.ContextLogger, side string, busid string, file *os.File) {
if file == nil {
h.markDone(nil)
return
}
closeFile := sync.OnceFunc(func() {
_ = file.Close()
})
-7
View File
@@ -254,10 +254,6 @@ func (h *darwinExportHost) snapshotSelf() map[string]Export {
return snapshotDarwinExports(h.exports)
}
// snapshotDarwinExports returns every tracked export, including stale
// ones, matching the ExportSnapshot contract: stale exports surface
// to the ledger so they broadcast as State: unavailable updates
// instead of disappearing.
func snapshotDarwinExports(exports map[string]*darwinExport) map[string]Export {
out := make(map[string]Export, len(exports))
for busid, exp := range exports {
@@ -329,9 +325,6 @@ func (e *darwinExport) DeviceInfo() (DeviceInfoTruncated, error) {
}
func (e *darwinExport) NewServerDataSession(ctx context.Context, conn net.Conn) (DataSession, error) {
if e.device == nil {
return nil, E.New("darwin export ", e.busid, " has no device handle")
}
return newUserspaceURBSession(ctx, e.logger, conn, newDarwinIOUSBHostEngine(e.device)), nil
}
+3 -21
View File
@@ -100,16 +100,10 @@ func (i linuxExportIdentity) Equal(other linuxExportIdentity) bool {
i.ConfigValue != other.ConfigValue ||
i.NumConfigs != other.NumConfigs ||
i.NumInterfaces != other.NumInterfaces ||
i.Serial != other.Serial ||
len(i.Interfaces) != len(other.Interfaces) {
i.Serial != other.Serial {
return false
}
for index := range i.Interfaces {
if i.Interfaces[index] != other.Interfaces[index] {
return false
}
}
return true
return slices.Equal(i.Interfaces, other.Interfaces)
}
type linuxExportHost struct {
@@ -311,11 +305,7 @@ func (h *linuxExportHost) Reconcile(isReserved func(busid string) bool) (map[str
var reconcileErrors []error
for _, busid := range plan.toStale {
exp, found := committed[busid]
if !found {
continue
}
cloned := cloneLinuxExport(exp)
cloned := cloneLinuxExport(committed[busid])
cloned.stale = true
committed[busid] = cloned
}
@@ -413,11 +403,6 @@ func (h *linuxExportHost) snapshotSelf() map[string]Export {
return snapshotLinuxExports(h.exports)
}
// snapshotLinuxExports returns every tracked export, including stale
// ones. The ledger treats stale entries as broadcastable State:
// unavailable updates via Export.Snapshot, which is what the
// ExportSnapshot contract requires; filtering here would surface a
// removed device instead of an updated one.
func snapshotLinuxExports(exports map[string]*linuxExport) map[string]Export {
out := make(map[string]Export, len(exports))
for busid, exp := range exports {
@@ -427,9 +412,6 @@ func snapshotLinuxExports(exports map[string]*linuxExport) map[string]Export {
}
func cloneLinuxExport(exp *linuxExport) *linuxExport {
if exp == nil {
return nil
}
clone := *exp
clone.descriptor.Interfaces = slices.Clone(exp.descriptor.Interfaces)
clone.identity.Interfaces = slices.Clone(exp.identity.Interfaces)
-34
View File
@@ -19,18 +19,10 @@ func newPlatformExportHost(ctx context.Context, logger log.ContextLogger, matche
return newWindowsExportHost(ctx, logger, matches), nil
}
// newPlatformImportHost: usbip-client is not yet implemented on
// Windows. A virtual host controller driver is required to drive
// imported devices into the local USB stack — that is a separate
// effort tracked outside this PR.
func newPlatformImportHost(_ log.ContextLogger) (ImportHost, error) {
return nil, E.New("usbip-client service is not yet implemented on Windows")
}
// windowsExportHost manages one VBoxUSBMon handle and one filter id
// per actively-matched device. Reconcile is called by ServerService
// on a timer (Events delivers ticks because CM_Register_Notification
// is not yet wired up — see vboxusb/pnp_windows.go).
type windowsExportHost struct {
logger log.ContextLogger
matches []option.USBIPDeviceMatch
@@ -79,9 +71,7 @@ func (h *windowsExportHost) Start() error {
return E.New("windows usbip: VBoxUSBMon major version ", major, " (need ", vboxusb.DriverMajorVersion, ")")
}
h.logger.Info("VBoxUSBMon ", major, ".", minor, " ready")
h.access.Lock()
h.monitor = monitor
h.access.Unlock()
return nil
}
@@ -112,10 +102,6 @@ func (h *windowsExportHost) Close() error {
return nil
}
// Events returns a channel that the server polls for topology changes.
// On Windows, CM_Register_Notification wiring is a Phase B follow-up;
// for now we tick on a fixed interval so Reconcile runs periodically.
// Once the notification hook lands, this becomes edge-triggered.
func (h *windowsExportHost) Events() (<-chan struct{}, error) {
ch := make(chan struct{}, 1)
go func() {
@@ -209,10 +195,6 @@ func (h *windowsExportHost) FinishImport(busid string) (bool, error) {
_ = exp.device.Close()
exp.device = nil
}
// TODO(phase B follow-up): once vboxusb.RestartDevice is implemented,
// call it here (or CycleHubPort) so Windows re-binds the device's
// natural function driver. Until then, the device stays in VBoxUSB's
// captured state until physical unplug.
return false, nil
}
@@ -226,10 +208,6 @@ func (h *windowsExportHost) snapshotSelf() map[string]Export {
return out
}
// installFilterLocked is called from Reconcile under the assumption
// the caller already holds (or does not need) the lock for this
// h.filters mutation — but we re-lock briefly to keep the mutation
// safe.
func (h *windowsExportHost) installFilterLocked(monitor *vboxusb.Monitor, busid string, info vboxusb.USBDeviceInfo) {
if monitor == nil {
return
@@ -266,8 +244,6 @@ func (h *windowsExportHost) removeFilterLocked(monitor *vboxusb.Monitor, busid s
}
}
// windowsExport carries the per-device state needed to satisfy the
// Export interface and to drive the claim sequence in NewServerDataSession.
type windowsExport struct {
info vboxusb.USBDeviceInfo
entry DeviceEntry
@@ -313,16 +289,6 @@ func (e *windowsExport) DeviceInfo() (DeviceInfoTruncated, error) {
return e.entry.Info, nil
}
// NewServerDataSession executes the VBoxUSB claim sequence:
//
// 1. Wait for the VBoxUSB-bound device interface to appear under
// GUID_CLASS_VBOXUSB. This only resolves if VBoxUSBMon's filter has
// already triggered (next PnP arrival after AddFilter); for devices
// already plugged in when sing-box started, the user must physically
// unplug/replug, or RestartDevice must complete (Phase B follow-up).
// 2. Open the device handle and verify driver version.
// 3. USB_CLAIM_DEVICE to take exclusive ownership.
// 4. Wrap as vboxusbEngine and hand to userspaceURBSession.
func (e *windowsExport) NewServerDataSession(ctx context.Context, conn net.Conn) (DataSession, error) {
path, err := vboxusb.WaitForVBoxUSBInterface(e.info.InstanceID, 10*time.Second)
if err != nil {
-5
View File
@@ -6,11 +6,6 @@ import (
E "github.com/sagernet/sing/common/exceptions"
)
// EncodeIsoSubmit fills the isochronous SUBMIT fields on base. When asap is
// true, the wire-level ASAP flag is set and StartFrame is zeroed. Otherwise
// RebaseFrame recovers the absolute frame number from the controller's
// monotonic counter and ciFrame's 8 bits, then StartFrame carries the low 32
// bits across the wire.
func EncodeIsoSubmit(currentFrame uint64, base SubmitCommand, ciFrame uint8, asap bool) SubmitCommand {
if asap {
base.TransferFlags |= usbipTransferFlagIsoASAP
-3
View File
@@ -34,9 +34,6 @@ func matches(m option.USBIPDeviceMatch, d DeviceKey) bool {
return true
}
// SelectMatches returns the indexes of keys that match at least one
// non-zero pattern. Indexes are deduplicated and returned in ascending
// order so callers iterate devices in a stable sequence.
func SelectMatches(patterns []option.USBIPDeviceMatch, keys []DeviceKey) []int {
if len(patterns) == 0 || len(keys) == 0 {
return nil
-5
View File
@@ -18,11 +18,6 @@ import (
var _ DataSession = (*userspaceURBSession)(nil)
// userspaceURBSession drives one USBIP attachment when the per-URB I/O
// happens in user space (Darwin IOUSBHost, Windows VBoxUSB). It reads
// CMD_SUBMIT/CMD_UNLINK from conn, serializes per endpoint, dispatches
// to a URBEngine, frames RET_SUBMIT/RET_UNLINK back over conn. Linux
// kernel-handoff sessions use kernelHandoffSession instead.
type userspaceURBSession struct {
ctx context.Context
logger log.ContextLogger
+1 -11
View File
@@ -66,9 +66,6 @@ func (d *sysfsDevice) toProtocol() DeviceInfoTruncated {
return info
}
// vhciStatusRecord is one row of /sys/devices/platform/vhci_hcd.0/status
// or status.N. The kernel emits globally unique port numbers across every
// status* file.
type vhciStatusRecord struct {
hub string
port int
@@ -180,10 +177,7 @@ func readUsbipStatus(busid string) (int, error) {
return v, nil
}
// finishImportStatusTimeout is the upper bound for waitForUsbipStatusCleared.
// It is a var (not const) so interop tests can shrink it without changing the
// polling cadence.
var finishImportStatusTimeout = 2 * time.Second
const finishImportStatusTimeout = 2 * time.Second
const finishImportStatusPollInterval = 25 * time.Millisecond
@@ -209,10 +203,6 @@ func waitForUsbipStatusCleared(ctx context.Context, busid string) {
}
}
// readPrimaryVHCIStatus reads every status* file under
// /sys/devices/platform/vhci_hcd.0 and concatenates the rows in lexical
// order. Port numbers are already globally unique across controllers — no
// remapping is needed.
func readPrimaryVHCIStatus() ([]vhciStatusRecord, error) {
matches, err := filepath.Glob(filepath.Join(sysVHCIControllerV0, "status*"))
if err != nil {
+3 -16
View File
@@ -2,25 +2,12 @@
package usbip
// URBEngine executes USB Request Blocks against an already-claimed
// device. The session layer (session_userspace.go) handles framing,
// per-endpoint ordering, and unlink bookkeeping; the engine performs
// the per-URB I/O and per-endpoint aborts only.
//
// Submit is called from per-endpoint goroutines; the session never
// issues two Submits concurrently for the same endpoint, so the engine
// does not need its own cross-endpoint serialization.
// Submit is called from per-endpoint goroutines; the session serializes
// per-endpoint so the engine needs no cross-endpoint lock.
type URBEngine interface {
Submit(request URBRequest) URBResponse
// AbortEndpoint cancels all in-flight submits on the given raw
// endpoint address (direction bit included). It is invoked once per
// pending sequence at CMD_UNLINK time and once per active endpoint
// at session shutdown.
AbortEndpoint(endpoint uint8) error
// Close releases engine-owned resources. For engines that own the
// underlying device handle (e.g. Windows VBoxUSB), this releases it.
// For engines where the host manages the device handle separately
// (e.g. Darwin IOUSBHost capture), Close may be a no-op. Idempotent.
// Idempotent.
Close() error
}
+7 -30
View File
@@ -21,15 +21,7 @@ import (
)
const (
ciStatusSuccess = 1
ciStatusOffline = 2
ciStatusNotPermitted = 3
ciStatusBadArgument = 4
ciStatusTimeout = 5
ciStatusNoResources = 6
ciStatusEndpointStopped = 7
ciStatusStallError = 11
ciStatusError = 13
ciStatusSuccess = 1
ciMsgControllerPowerOn = 0x10
ciMsgControllerPowerOff = 0x11
@@ -89,7 +81,6 @@ func (c cgoCallbackHandle) token() C.uintptr_t {
return C.uintptr_t(c.handle)
}
// deleteRaw rolls back a failed C-side create without a drain.
func (c cgoCallbackHandle) deleteRaw() {
c.handle.Delete()
}
@@ -171,7 +162,7 @@ func darwinWatchUSBHostDevices(callback func()) (*darwinUSBHostDeviceWatcher, er
}
func (w *darwinUSBHostDeviceWatcher) Close() {
if w == nil || w.handle == nil {
if w.handle == nil {
return
}
w.callback.closeAfter(func() {
@@ -192,7 +183,7 @@ func darwinCreateUSBHostController(controller *darwinVirtualController, portCoun
}
func (c *darwinUSBHostController) Close() {
if c == nil || c.handle == nil {
if c.handle == nil {
return
}
c.callback.closeAfter(func() {
@@ -239,7 +230,7 @@ func (c *darwinUSBHostController) createDeviceSM(message darwinCIMessage) (*darw
}
func (s *darwinUSBHostDeviceSM) Close() {
if s == nil || s.handle == nil {
if s.handle == nil {
return
}
C.box_usbhost_device_sm_destroy(s.handle)
@@ -275,7 +266,7 @@ func (c *darwinUSBHostController) createEndpointSM(message darwinCIMessage) (*da
}
func (s *darwinUSBHostEndpointSM) Close() {
if s == nil || s.handle == nil {
if s.handle == nil {
return
}
C.box_usbhost_endpoint_sm_destroy(s.handle)
@@ -403,7 +394,7 @@ func box_usbip_darwin_usb_event(ref C.uintptr_t) {
}
func (d *darwinUSBHostDevice) Close() {
if d == nil || d.handle == nil {
if d.handle == nil {
return
}
C.box_usbhost_device_close(d.handle)
@@ -411,9 +402,6 @@ func (d *darwinUSBHostDevice) Close() {
}
func (d *darwinUSBHostDevice) control(setup [8]byte, buffer []byte) (int32, int32, []byte, error) {
if d == nil || d.handle == nil {
return -int32(unix.ENODEV), 0, nil, E.New("IOUSBHostDevice control: closed")
}
var actual C.size_t
var status C.int32_t
var errorPtr *C.char
@@ -429,9 +417,6 @@ func (d *darwinUSBHostDevice) control(setup [8]byte, buffer []byte) (int32, int3
}
func (d *darwinUSBHostDevice) io(endpoint uint8, buffer []byte) (int32, int32, []byte, error) {
if d == nil || d.handle == nil {
return -int32(unix.ENODEV), 0, nil, E.New("IOUSBHostPipe IO: closed")
}
var actual C.size_t
var status C.int32_t
var errorPtr *C.char
@@ -446,9 +431,6 @@ func (d *darwinUSBHostDevice) io(endpoint uint8, buffer []byte) (int32, int32, [
}
func (d *darwinUSBHostDevice) iso(endpoint uint8, buffer []byte, startFrame int32, asap bool, packets []IsoPacketDescriptor) (int32, int32, []byte, []IsoPacketDescriptor, error) {
if d == nil || d.handle == nil {
return -int32(unix.ENODEV), 0, nil, nil, E.New("IOUSBHostPipe isochronous IO: closed")
}
var actual C.size_t
var status C.int32_t
var errorPtr *C.char
@@ -478,9 +460,6 @@ func (d *darwinUSBHostDevice) iso(endpoint uint8, buffer []byte, startFrame int3
}
func (d *darwinUSBHostDevice) abortEndpoint(endpoint uint8) error {
if d == nil || d.handle == nil {
return nil
}
var errorPtr *C.char
if !bool(C.box_usbhost_device_abort_endpoint(d.handle, C.uint8_t(endpoint), &errorPtr)) {
return darwinCError(errorPtr)
@@ -574,9 +553,7 @@ func darwinIOReturnToUSBIPStatus(status int32) int32 {
}
}
// darwinUSBIPStatusToCIStatus maps USB/IP transfer completion status to the
// corresponding IOUSBHostCI completion status. This is only used for real
// transfer completion, not for EndpointPause-driven state machine events.
// Only for real transfer completion, not for EndpointPause-driven state machine events.
func darwinUSBIPStatusToCIStatus(status int32) int {
if status == 0 {
return int(C.IOUSBHostCIMessageStatusSuccess)