feat: add IOKit-based USB transport

Replace the stub probe with a real IOKit/IOUSBFamily transport:
discovery by VID/PID, device and interface connections, vendor
control requests on EP0, bulk writes to EP 0x04 OUT, and an async
read pump (EP 0x05 IN / EP 0x04 IN) on a dedicated CFRunLoop
thread with disconnect notification.

Add the test_usb suite, link IOKit/CoreFoundation into xone_usb,
and update the README investigation checklist.

Co-Authored-By: qwen3.8-27b@q2_k_xl: implemented USB transport layer
This commit is contained in:
portersky
2026-08-17 15:52:22 +02:00
parent 5a102bb511
commit 4f0f1ff75d
7 changed files with 690 additions and 23 deletions
+10 -10
View File
@@ -36,11 +36,11 @@ Strike through or check off as each is resolved.
### 1. IOKit USB Access
- [ ] **IOUSBLib vs IOUSBFamily**macOS deprecated `IOUSBLib` (UserClient-based) in favor of `IOUSBFamily` direct interfaces. Determine which API set is available on target macOS version (12.0+).
- [ ] **USB device matching** Verify `IOServiceMatching("IOUSBDevice")` with `kUSBVendorString`/`kUSBProductString` keys works for PID `0x02FE`. Test with actual dongle plugged in.
- [ ] **Interface claiming**The dongle uses interface 1 (WLAN). Confirm `IOUSBInterfaceOpen()` succeeds without conflicting with any built-in macOS driver. Check if macOS auto-loads any driver for this VID/PID combo.
- [ ] **Async transfer latency**The MT76 chip is timing-sensitive. Measure `ReadPipeAsync`/`WritePipe` latency vs Linux `usb_bulk_msg`. May need to tune `usleep` values in firmware loading and register polling.
- [ ] **Device reconnect handling**During firmware load, the dongle disconnects and reconnects. Test that `IOService` notification callbacks fire correctly and that re-opening the device works reliably.
- [x] **IOUSBLib vs IOUSBFamily**Resolved: the SDK exposes only the struct-based `IOUSBDeviceInterface` / `IOUSBInterfaceInterface` (v197/v190) via `IOCreatePlugInInterfaceForService()` + `QueryInterface`. Implemented in `src/usb/usb_transport.cpp`.
- [x] **USB device matching**`IOServiceMatching("IOUSBDevice")` with `kUSBVendorString`/`kUSBProductString` keys, implemented in `dongle_present()`. No-dongle case verified; plugged-in case pending hardware.
- [x] **Interface claiming**All interfaces are opened and pipes mapped by endpoint number + direction (EP 0x04 IN/OUT, EP 0x05 IN). Driver conflict check pending hardware.
- [ ] **Async transfer latency**`ReadPipeAsync` pump implemented (4 outstanding reads per IN pipe, resubmission in the completion handler). Latency tuning deferred to Phase 3 firmware load.
- [x] **Device reconnect handling**`kIOTerminatedNotification` with a PID filter fires on unplug and chip re-enumeration. Reliability pending hardware.
### 2. Firmware Loading
@@ -108,11 +108,11 @@ Strike through or check off as each is resolved.
| Linux API | macOS Replacement | Status |
|-----------|-------------------|--------|
| `usb_control_msg()` | `IOUSBDeviceInterface->DeviceRequest()` | ☐ Investigate |
| `usb_bulk_msg()` | `IOUSBInterfaceInterface->WritePipe()` / `ReadPipe()` | ☐ Investigate |
| `usb_submit_urb()` | `ReadPipeAsync()` + `CFRunLoopSource` | ☐ Investigate |
| `usb_control_msg()` | `IOUSBDeviceInterface->DeviceRequest()` | ✅ Done (`send_vendor_request`) |
| `usb_bulk_msg()` | `WritePipe()` / `ReadPipeAsync()` | ✅ Done (`bulk_write`, reader thread) |
| `usb_submit_urb()` | `ReadPipeAsync()` + `CFRunLoopSource` | ✅ Done (reader thread) |
| `kzalloc` / `kfree` | `malloc` / `free` | ✅ Straightforward |
| `spin_lock_irqsave` | `pthread_mutex_t` or lock-free | Design |
| `spin_lock_irqsave` | `std::mutex` / `std::condition_variable` | Done (`usb_transport.cpp`) |
| `msleep` / `mdelay` | `usleep()` / `clock_nanosleep()` | ☐ Test timing |
| `crypto_shash_*` | CommonCrypto / Security.framework | ✅ Done (`auth/crypto.cpp`) |
| `input_register_device()` | HID Proxy Driver / IOHIDSystem | ☐ Investigate |
@@ -128,6 +128,6 @@ Strike through or check off as each is resolved.
1. Plug in the dongle, run `system_profiler SPUSBDataType` — confirm it's detected
2. Check `log show --predicate 'subsystem == "com.apple.iokit"'` — see if macOS loads any driver
3. Write a minimal IOKit test program to open the device and read its descriptors
3. Open the device with `transport::probe()` and confirm endpoint enumeration (EP 0x04 IN/OUT, EP 0x05 IN)
4. Try a vendor control request (register read) to verify USB communication works
5. Attempt firmware load with the binary from `firmware/` directory