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:
@@ -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
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user