usbip: serialize darwin device state, pack iso-IN per descriptor

Darwin URBs handled in concurrent goroutines mutated BoxUSBHostDevice's
pipes/interfaces dictionaries without serialization, so SetConfiguration
racing with endpoint IO could corrupt them or hand back stale pipes.
Guard the collections with os_unfair_lock; keep the slow IOKit calls
outside the lock so unrelated endpoints don't stall.

Iso IN responses were flat-copied buffer[:actual], losing offsets for
multi-packet transfers. Pack the wire payload per IsoPackets[i].Offset/
ActualLength to match Linux's stub_tx.c, and mirror the scatter on the
Darwin client side so a future multi-packet emitter is correct.
This commit is contained in:
世界
2026-05-15 13:35:50 +08:00
parent 1a17ea0ba9
commit d741a6a7e9
3 changed files with 138 additions and 23 deletions
+33 -1
View File
@@ -547,11 +547,43 @@ func (c *darwinVirtualController) completeSubmitInTransfer(ptr unsafe.Pointer, r
copyLength = len(response.Buffer)
}
if copyLength > 0 && ptr != nil {
copy(unsafe.Slice((*byte)(ptr), copyLength), response.Buffer[:copyLength])
if len(response.IsoPackets) > 0 {
dst := unsafe.Slice((*byte)(ptr), requestLength)
scatterIsoInResponseBuffer(dst, response.Buffer[:copyLength], response.IsoPackets)
} else {
copy(unsafe.Slice((*byte)(ptr), copyLength), response.Buffer[:copyLength])
}
}
return response.Status, actualLength
}
func scatterIsoInResponseBuffer(dst []byte, payload []byte, packets []IsoPacketDescriptor) {
cursor := 0
for i := range packets {
length := int(packets[i].ActualLength)
if length <= 0 {
continue
}
if cursor+length > len(payload) {
length = len(payload) - cursor
if length <= 0 {
return
}
}
offset := int(packets[i].Offset)
if offset < 0 || offset >= len(dst) {
cursor += length
continue
}
end := offset + length
if end > len(dst) {
end = len(dst)
}
copy(dst[offset:end], payload[cursor:cursor+(end-offset)])
cursor += length
}
}
func (c *darwinVirtualController) sendSubmit(command SubmitCommand) (SubmitResponse, error) {
seq := c.seq.Add(1)
command.Header.SeqNum = seq
+40 -1
View File
@@ -525,11 +525,50 @@ func (s *darwinServerDataSession) handleSubmit(command SubmitCommand) SubmitResp
}
response.ActualLength = actual
if command.Header.Direction == USBIPDirIn && actual > 0 {
response.Buffer = buffer[:min(int(actual), len(buffer))]
if command.NumberOfPackets > 0 {
response.Buffer = packIsoInResponseBuffer(buffer, response.IsoPackets)
response.ActualLength = int32(len(response.Buffer))
} else {
response.Buffer = buffer[:min(int(actual), len(buffer))]
}
}
return response
}
func packIsoInResponseBuffer(buffer []byte, packets []IsoPacketDescriptor) []byte {
var total int
for i := range packets {
length := int(packets[i].ActualLength)
if length <= 0 {
packets[i].ActualLength = 0
continue
}
offset := int(packets[i].Offset)
if offset < 0 || offset >= len(buffer) {
packets[i].ActualLength = 0
continue
}
if offset+length > len(buffer) {
length = len(buffer) - offset
packets[i].ActualLength = int32(length)
}
total += length
}
if total == 0 {
return nil
}
packed := make([]byte, 0, total)
for i := range packets {
length := int(packets[i].ActualLength)
if length <= 0 {
continue
}
offset := int(packets[i].Offset)
packed = append(packed, buffer[offset:offset+length]...)
}
return packed
}
func (s *darwinServerDataSession) trackSubmit(seq uint32, endpoint uint8) {
s.access.Lock()
defer s.access.Unlock()
+65 -21
View File
@@ -5,16 +5,32 @@
#import <IOUSBHost/AppleUSBDescriptorParsing.h>
#import <dispatch/dispatch.h>
#import <mach/mach_time.h>
#import <os/lock.h>
#import <stdlib.h>
#import <string.h>
@interface BoxUSBHostDevice : NSObject
@interface BoxUSBHostDevice : NSObject {
os_unfair_lock _stateLock;
}
@property(nonatomic, strong) IOUSBHostDevice *device;
@property(nonatomic, strong) NSMutableArray<IOUSBHostInterface *> *interfaces;
@property(nonatomic, strong) NSMutableDictionary<NSNumber *, IOUSBHostPipe *> *pipes;
- (void)withStateLock:(NS_NOESCAPE void (^)(void))block;
@end
@implementation BoxUSBHostDevice
- (instancetype)init {
self = [super init];
if (self != nil) {
_stateLock = OS_UNFAIR_LOCK_INIT;
}
return self;
}
- (void)withStateLock:(NS_NOESCAPE void (^)(void))block {
os_unfair_lock_lock(&_stateLock);
block();
os_unfair_lock_unlock(&_stateLock);
}
@end
@interface BoxUSBHostController : NSObject
@@ -283,20 +299,25 @@ static IOUSBHostPipe *box_find_pipe_for_endpoint(BoxUSBHostDevice *box, uint8_t
}
static IOUSBHostPipe *box_pipe_for_endpoint(BoxUSBHostDevice *box, uint8_t endpoint) {
NSNumber *key = @(endpoint);
IOUSBHostPipe *cached = box.pipes[key];
if (cached != nil) {
return cached;
}
IOUSBHostPipe *pipe = box_find_pipe_for_endpoint(box, endpoint);
if (pipe == nil) {
box_load_interfaces(box);
pipe = box_find_pipe_for_endpoint(box, endpoint);
}
if (pipe != nil) {
box.pipes[key] = pipe;
}
return pipe;
__block IOUSBHostPipe *result = nil;
[box withStateLock:^{
NSNumber *key = @(endpoint);
IOUSBHostPipe *cached = box.pipes[key];
if (cached != nil) {
result = cached;
return;
}
IOUSBHostPipe *pipe = box_find_pipe_for_endpoint(box, endpoint);
if (pipe == nil) {
box_load_interfaces(box);
pipe = box_find_pipe_for_endpoint(box, endpoint);
}
if (pipe != nil) {
box.pipes[key] = pipe;
}
result = pipe;
}];
return result;
}
static IOUSBHostInterface *box_interface_for_number(BoxUSBHostDevice *box, uint8_t interface_number) {
@@ -493,18 +514,25 @@ bool box_usbhost_device_control(box_usbhost_device_t *device, const uint8_t setu
if (request.bmRequestType == 0 && request.bRequest == kIOUSBDeviceRequestSetConfiguration && request.wIndex == 0 && request.wLength == 0) {
ok = [box.device configureWithValue:request.wValue matchInterfaces:YES error:&error];
if (ok) {
box.pipes = [NSMutableDictionary dictionary];
box_load_interfaces(box);
[box withStateLock:^{
box.pipes = [NSMutableDictionary dictionary];
box_load_interfaces(box);
}];
}
} else if (request.bmRequestType == kIOUSBDeviceRequestRecipientInterface && request.bRequest == kIOUSBDeviceRequestSetInterface && request.wLength == 0) {
IOUSBHostInterface *interface = box_interface_for_number(box, request.wIndex & 0xff);
__block IOUSBHostInterface *interface = nil;
[box withStateLock:^{
interface = box_interface_for_number(box, request.wIndex & 0xff);
}];
if (interface == nil) {
error = [NSError errorWithDomain:NSMachErrorDomain code:kIOReturnNotFound userInfo:nil];
} else {
ok = [interface selectAlternateSetting:request.wValue error:&error];
if (ok) {
box.pipes = [NSMutableDictionary dictionary];
box_load_interfaces(box);
[box withStateLock:^{
box.pipes = [NSMutableDictionary dictionary];
box_load_interfaces(box);
}];
}
}
} else {
@@ -601,7 +629,23 @@ bool box_usbhost_device_iso(box_usbhost_device_t *device, uint8_t endpoint, uint
*status_out = ok ? 0 : (int32_t)(error != nil ? error.code : kIOReturnError);
}
if (ok && (endpoint & kIOUSBEndpointDescriptorDirection) == kIOUSBEndpointDescriptorDirectionIn && data_len > 0 && data != NULL) {
memcpy(data, payload.bytes, actual <= data_len ? actual : data_len);
const uint8_t *src = payload.bytes;
for (size_t i = 0; i < packet_count; i++) {
int32_t signed_offset = packets[i].offset;
int32_t signed_length = packets[i].actual_length;
if (signed_length <= 0 || signed_offset < 0) {
continue;
}
size_t offset = (size_t)signed_offset;
size_t length = (size_t)signed_length;
if (offset >= data_len) {
continue;
}
if (offset + length > data_len) {
length = data_len - offset;
}
memcpy(data + offset, src + offset, length);
}
}
return true;
}