first commit
This commit is contained in:
@@ -0,0 +1,168 @@
|
||||
//go:build linux
|
||||
|
||||
package usbip
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strconv"
|
||||
"strings"
|
||||
)
|
||||
|
||||
const vhciBasePath = "/sys/devices/platform/vhci_hcd.0"
|
||||
|
||||
// VHCIPort represents a virtual USB port on the VHCI controller
|
||||
type VHCIPort struct {
|
||||
Hub string // "hs" or "ss"
|
||||
Port int
|
||||
Status int
|
||||
Speed int
|
||||
DevID uint32
|
||||
SocketFD int
|
||||
LocalBusID string
|
||||
}
|
||||
|
||||
// VHCI status constants
|
||||
const (
|
||||
VDevStNull = 0x04
|
||||
VDevStNotAssigned = 0x05
|
||||
VDevStUsed = 0x06
|
||||
VDevStError = 0x07
|
||||
)
|
||||
|
||||
// ReadVHCIStatus reads the current VHCI port status
|
||||
func ReadVHCIStatus() ([]VHCIPort, error) {
|
||||
// Try status file directly, then status.0, status.1, etc.
|
||||
var allPorts []VHCIPort
|
||||
|
||||
paths := []string{
|
||||
filepath.Join(vhciBasePath, "status"),
|
||||
}
|
||||
|
||||
// Check for multi-controller status files
|
||||
for i := 0; i < 16; i++ {
|
||||
p := filepath.Join(vhciBasePath, fmt.Sprintf("status.%d", i))
|
||||
if _, err := os.Stat(p); err == nil {
|
||||
paths = append(paths, p)
|
||||
} else {
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
for _, path := range paths {
|
||||
ports, err := parseStatusFile(path)
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
allPorts = append(allPorts, ports...)
|
||||
}
|
||||
|
||||
if len(allPorts) == 0 {
|
||||
return nil, fmt.Errorf("vhci-hcd module not loaded or no ports found (check: modprobe vhci-hcd)")
|
||||
}
|
||||
|
||||
return allPorts, nil
|
||||
}
|
||||
|
||||
func parseStatusFile(path string) ([]VHCIPort, error) {
|
||||
data, err := os.ReadFile(path)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
lines := strings.Split(strings.TrimSpace(string(data)), "\n")
|
||||
var ports []VHCIPort
|
||||
|
||||
for _, line := range lines {
|
||||
line = strings.TrimSpace(line)
|
||||
// Skip header lines
|
||||
if strings.HasPrefix(line, "hub") || strings.HasPrefix(line, "prt") || line == "" {
|
||||
continue
|
||||
}
|
||||
|
||||
fields := strings.Fields(line)
|
||||
if len(fields) < 7 {
|
||||
continue
|
||||
}
|
||||
|
||||
port := VHCIPort{Hub: fields[0]}
|
||||
|
||||
if v, err := strconv.Atoi(fields[1]); err == nil {
|
||||
port.Port = v
|
||||
}
|
||||
if v, err := strconv.Atoi(fields[2]); err == nil {
|
||||
port.Status = v
|
||||
}
|
||||
if v, err := strconv.Atoi(fields[3]); err == nil {
|
||||
port.Speed = v
|
||||
}
|
||||
if v, err := strconv.ParseUint(fields[4], 16, 32); err == nil {
|
||||
port.DevID = uint32(v)
|
||||
}
|
||||
if v, err := strconv.Atoi(fields[5]); err == nil {
|
||||
port.SocketFD = v
|
||||
}
|
||||
port.LocalBusID = fields[6]
|
||||
|
||||
ports = append(ports, port)
|
||||
}
|
||||
|
||||
return ports, nil
|
||||
}
|
||||
|
||||
// FindFreePort finds an available VHCI port for the given speed
|
||||
func FindFreePort(speed uint32) (int, error) {
|
||||
ports, err := ReadVHCIStatus()
|
||||
if err != nil {
|
||||
return -1, err
|
||||
}
|
||||
|
||||
// Determine desired hub type based on speed
|
||||
wantHub := "hs" // high-speed and below
|
||||
if speed >= SpeedSuper {
|
||||
wantHub = "ss" // super-speed
|
||||
}
|
||||
|
||||
for _, port := range ports {
|
||||
if port.Status == VDevStNull && port.Hub == wantHub {
|
||||
return port.Port, nil
|
||||
}
|
||||
}
|
||||
|
||||
return -1, fmt.Errorf("no free VHCI port available for hub type %s", wantHub)
|
||||
}
|
||||
|
||||
// AttachDevice writes to the VHCI attach file to create a virtual USB device.
|
||||
// sockfd must be a valid TCP socket file descriptor connected to the USB/IP server.
|
||||
func AttachDevice(port int, sockfd int, devID uint32, speed uint32) error {
|
||||
attachPath := filepath.Join(vhciBasePath, "attach")
|
||||
|
||||
// Format: "<port> <sockfd> <devid> <speed>"
|
||||
data := fmt.Sprintf("%d %d %d %d", port, sockfd, devID, speed)
|
||||
|
||||
if err := os.WriteFile(attachPath, []byte(data), 0); err != nil {
|
||||
return fmt.Errorf("writing to VHCI attach: %w", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// DetachDevice writes to the VHCI detach file to remove a virtual USB device
|
||||
func DetachDevice(port int) error {
|
||||
detachPath := filepath.Join(vhciBasePath, "detach")
|
||||
|
||||
data := fmt.Sprintf("%d", port)
|
||||
|
||||
if err := os.WriteFile(detachPath, []byte(data), 0); err != nil {
|
||||
return fmt.Errorf("writing to VHCI detach: %w", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// IsVHCIAvailable checks if the vhci-hcd kernel module is loaded
|
||||
func IsVHCIAvailable() bool {
|
||||
_, err := os.Stat(vhciBasePath)
|
||||
return err == nil
|
||||
}
|
||||
Reference in New Issue
Block a user