added isochronous modus

This commit is contained in:
2026-02-18 22:42:52 +01:00
parent 170f5dabcc
commit 99d9242264
7 changed files with 428 additions and 60 deletions
+21 -5
View File
@@ -24,13 +24,29 @@ type Device struct {
// Interface represents a USB interface
type Interface struct {
Number uint8 `json:"number"`
Class uint8 `json:"class"`
SubClass uint8 `json:"sub_class"`
Protocol uint8 `json:"protocol"`
Driver string `json:"driver"`
Number uint8 `json:"number"`
Class uint8 `json:"class"`
SubClass uint8 `json:"sub_class"`
Protocol uint8 `json:"protocol"`
Driver string `json:"driver"`
Endpoints []Endpoint `json:"endpoints"`
}
// Endpoint represents a USB endpoint
type Endpoint struct {
Address uint8 `json:"address"` // bEndpointAddress (bit 7=direction, bits 3:0=number)
TransferType uint8 `json:"transfer_type"` // 0=control, 1=iso, 2=bulk, 3=interrupt
MaxPacketSize uint16 `json:"max_packet_size"`
}
// USB transfer types (from bmAttributes)
const (
TransferTypeControl = 0
TransferTypeIsochronous = 1
TransferTypeBulk = 2
TransferTypeInterrupt = 3
)
// DevID returns the USB/IP device ID (busnum << 16 | devnum)
func (d *Device) DevID() uint32 {
return (d.BusNum << 16) | d.DevNum
+43
View File
@@ -127,12 +127,55 @@ func readInterfaces(sysPath, busID string) []Interface {
iface.Driver = filepath.Base(driverLink)
}
// Read endpoints
iface.Endpoints = readEndpoints(ifacePath)
ifaces = append(ifaces, iface)
}
return ifaces
}
func readEndpoints(ifacePath string) []Endpoint {
entries, err := os.ReadDir(ifacePath)
if err != nil {
return nil
}
var eps []Endpoint
for _, entry := range entries {
name := entry.Name()
if !strings.HasPrefix(name, "ep_") || name == "ep_00" {
continue
}
epPath := filepath.Join(ifacePath, name)
addr, _ := strconv.ParseUint(readString(epPath, "bEndpointAddress"), 16, 8)
var transferType uint8
switch readString(epPath, "type") {
case "Control":
transferType = TransferTypeControl
case "Isoc":
transferType = TransferTypeIsochronous
case "Bulk":
transferType = TransferTypeBulk
case "Interrupt":
transferType = TransferTypeInterrupt
}
maxPkt := readUint32(epPath, "wMaxPacketSize")
eps = append(eps, Endpoint{
Address: uint8(addr),
TransferType: transferType,
MaxPacketSize: uint16(maxPkt),
})
}
return eps
}
func readString(dir, attr string) string {
data, err := os.ReadFile(filepath.Join(dir, attr))
if err != nil {
+103
View File
@@ -348,6 +348,109 @@ func (h *DeviceHandle) DiscardURB(urb *usbdevfsURB) error {
return nil
}
// SubmitISOURBParams holds parameters for async ISO URB submission
type SubmitISOURBParams struct {
Endpoint uint8
Flags uint32
Buffer []byte
NumberOfPackets int32
PacketLengths []uint32 // length of each ISO packet
UserContext uintptr
}
// SubmitISOURB submits an asynchronous isochronous URB.
// Returns the URB pointer and the backing memory slice (must be kept alive until reap).
func (h *DeviceHandle) SubmitISOURB(params *SubmitISOURBParams) (urb *usbdevfsURB, mem []byte, err error) {
urbSize := unsafe.Sizeof(usbdevfsURB{})
isoDescSize := unsafe.Sizeof(usbdevfsISOPacketDesc{})
totalSize := urbSize + uintptr(params.NumberOfPackets)*isoDescSize
mem = make([]byte, totalSize)
urb = (*usbdevfsURB)(unsafe.Pointer(&mem[0]))
var bufPtr uintptr
if len(params.Buffer) > 0 {
bufPtr = uintptr(unsafe.Pointer(&params.Buffer[0]))
}
urb.Type = urbTypeISO
urb.Endpoint = params.Endpoint
urb.Flags = params.Flags
urb.Buffer = bufPtr
urb.BufferLength = int32(len(params.Buffer))
urb.NumberOfPackets = params.NumberOfPackets
urb.UserContext = params.UserContext
// Fill ISO packet descriptors (immediately following the URB in memory)
for i := int32(0); i < params.NumberOfPackets; i++ {
descOffset := urbSize + uintptr(i)*isoDescSize
desc := (*usbdevfsISOPacketDesc)(unsafe.Pointer(&mem[descOffset]))
if i < int32(len(params.PacketLengths)) {
desc.Length = params.PacketLengths[i]
}
}
_, _, errno := unix.Syscall(unix.SYS_IOCTL, uintptr(h.fd), usbdevfsSubmitURB, uintptr(unsafe.Pointer(urb)))
if errno != 0 {
return nil, nil, fmt.Errorf("USBDEVFS_SUBMITURB (ISO): %w", errno)
}
return urb, mem, nil
}
// ISOPacketResult holds the result of one ISO packet after reaping
type ISOPacketResult struct {
Length uint32
ActualLength uint32
Status uint32
}
// ReadISOResults reads the ISO packet results from a reaped ISO URB's backing memory.
func ReadISOResults(mem []byte, numPackets int32) []ISOPacketResult {
urbSize := unsafe.Sizeof(usbdevfsURB{})
isoDescSize := unsafe.Sizeof(usbdevfsISOPacketDesc{})
results := make([]ISOPacketResult, numPackets)
for i := int32(0); i < numPackets; i++ {
offset := urbSize + uintptr(i)*isoDescSize
if int(offset+isoDescSize) > len(mem) {
break
}
desc := (*usbdevfsISOPacketDesc)(unsafe.Pointer(&mem[offset]))
results[i] = ISOPacketResult{
Length: desc.Length,
ActualLength: desc.ActualLength,
Status: desc.Status,
}
}
return results
}
// ReapedURBInfo holds exported fields from a reaped URB needed for response building
type ReapedURBInfo struct {
UserContext uintptr
Status int32
ActualLength int32
StartFrame int32
ErrorCount int32
}
// ReapURBInfo blocks until a URB completes and returns exported info
func (h *DeviceHandle) ReapURBInfo() (*ReapedURBInfo, error) {
var urbPtr uintptr
_, _, errno := unix.Syscall(unix.SYS_IOCTL, uintptr(h.fd), usbdevfsReapURB, uintptr(unsafe.Pointer(&urbPtr)))
if errno != 0 {
return nil, fmt.Errorf("USBDEVFS_REAPURB: %w", errno)
}
urb := (*usbdevfsURB)(unsafe.Pointer(urbPtr))
return &ReapedURBInfo{
UserContext: urb.UserContext,
Status: urb.Status,
ActualLength: urb.ActualLength,
StartFrame: urb.StartFrame,
ErrorCount: urb.ErrorCount,
}, nil
}
// GetFile returns an os.File wrapping the device fd (useful for epoll/select)
func (h *DeviceHandle) GetFile() *os.File {
return os.NewFile(uintptr(h.fd), h.devPath)