178 lines
4.1 KiB
Go
178 lines
4.1 KiB
Go
//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
|
|
}
|
|
|
|
// VHCIUnavailableError returns an error describing why VHCI is not available,
|
|
// or nil if VHCI is ready to use.
|
|
func VHCIUnavailableError() error {
|
|
if IsVHCIAvailable() {
|
|
return nil
|
|
}
|
|
return fmt.Errorf("vhci-hcd Kernel-Modul nicht geladen (ausfuehren: sudo modprobe vhci-hcd)")
|
|
}
|