added isochronous modus
This commit is contained in:
+21
-5
@@ -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
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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(¶ms.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)
|
||||
|
||||
Reference in New Issue
Block a user