docs: update README with current project status

Check off investigation items resolved on hardware (firmware load,
EFUSE, radio init, beacon, pairing, reconnect), align build commands
with AGENTS.md, add xone_cli and scripts to the layout, replace the
completed first-steps list with a current status section, and drop
em dashes per the documentation conventions.

Co-Authored-By: qwen3.8-27b@q3_k_xl: updated README with current status
This commit is contained in:
portersky
2026-08-29 14:21:18 +02:00
parent 418e177b5c
commit 51192023aa
+92 -80
View File
@@ -1,4 +1,4 @@
# Xbox Wireless Dongle macOS Port # Xbox Wireless Dongle: macOS Port
User-space macOS app for the Xbox Wireless Dongle (MediaTek MT76xx). User-space macOS app for the Xbox Wireless Dongle (MediaTek MT76xx).
Ports the Linux kernel driver [`xone/`](../xone/) to macOS. Ports the Linux kernel driver [`xone/`](../xone/) to macOS.
@@ -15,17 +15,21 @@ so configure with one of them explicitly:
```sh ```sh
cmake -S . -B build -GNinja -DBUILD_TESTING=ON cmake -S . -B build -GNinja -DBUILD_TESTING=ON
cmake --build build ninja -C build
./build/xone_app ./build/xone_app
cmake --build build --target check # run the test suite ninja -C build check # build and run the test suite
``` ```
Layout (mirrors `../refix/`): Layout (mirrors `../refix/`):
- `deps/` CMake modules: `Platform.cmake`, `Flags.cmake`, `Sanitizers.cmake`, `FindUnity.cmake` - `deps/`: CMake modules: `Platform.cmake`, `Flags.cmake`, `Sanitizers.cmake`,
- `src/{usb,mt76,gip,auth,hid}` — C++ protocol stack (ported from `medusalix/xone`) `FindUnity.cmake`
- `src/app/` — Swift app entry point + C ABI bridge (`include/app/xone_api.h`) - `src/{usb,mt76,gip,auth,hid}`: C++ protocol stack (ported from
- `tests/` — Unity test suite (`BUILD_TESTING=ON`) `medusalix/xone`)
- `src/app/`: Swift app entry point + C ABI bridge (`include/app/xone_api.h`)
- `src/cli/`: C++ CLI (`xone_cli`) for manual protocol sequences
- `scripts/`: `download-firmware.sh` (port of the upstream firmware fetcher)
- `tests/`: Unity test suite (`BUILD_TESTING=ON`)
--- ---
@@ -36,123 +40,124 @@ Strike through or check off as each is resolved.
### 1. IOKit USB Access ### 1. IOKit USB Access
- [x] **IOUSBLib vs IOUSBFamily** Resolved: the SDK exposes only the - [x] **IOUSBLib vs IOUSBFamily**: Resolved: the SDK exposes only the
struct-based `IOUSBDeviceInterface` / `IOUSBInterfaceInterface` (v197/v190) struct-based `IOUSBDeviceInterface` / `IOUSBInterfaceInterface` (v197/v190)
via `IOCreatePlugInInterfaceForService()` + `QueryInterface`. Implemented in via `IOCreatePlugInInterfaceForService()` + `QueryInterface`. Implemented in
`src/usb/usb_transport.cpp`. `src/usb/usb_transport.cpp`.
- [x] **USB device matching** Class match on `IOUSBDevice` plus a user-space - [x] **USB device matching**: Class match on `IOUSBDevice` plus a user-space
VID/PID filter on the `idVendor`/`idProduct` properties (numeric registry VID/PID filter on the `idVendor`/`idProduct` properties (numeric registry
matching proved unreliable). Verified with a physical dongle (PID 0x02E6). matching proved unreliable). Verified with a physical dongle (PID 0x02E6).
- [x] **Interface claiming** All interfaces are opened and pipes mapped by - [x] **Interface claiming**: All interfaces are opened and pipes mapped by
endpoint number + direction (EP 0x04 IN/OUT, EP 0x05 IN). Verified: opens endpoint number + direction (EP 0x04 IN/OUT, EP 0x05 IN). Verified: opens
cleanly with no driver conflict. cleanly with no driver conflict.
- [ ] **Async transfer latency** `ReadPipeAsync` pump implemented (4 - [x] **Async transfer latency**: Resolved: the `ReadPipeAsync` pump (4
outstanding reads per IN pipe, resubmission in the completion handler). outstanding reads per IN pipe, resubmission in the completion handler)
Latency tuning deferred to Phase 3 firmware load. streams live controller input with no drops at game-report rates.
- [x] **Device reconnect handling** `kIOTerminatedNotification` with a PID - [x] **Device reconnect handling**: Resolved: `kIOTerminatedNotification`
filter fires on unplug and chip re-enumeration. Reliability pending hardware. with a PID filter fires on unplug and chip re-enumeration. Verified
across firmware-load resets and repeated unplug/replug cycles.
### 2. Firmware Loading ### 2. Firmware Loading
- [ ] **Firmware binary availability** — Run `../xone/install/firmware.sh` (or - [x] **Firmware binary availability**: Resolved: `scripts/download-firmware.sh`
ported `scripts/download-firmware.sh`) to confirm the Windows Update CAB URLs (port of `../xone/install/firmware.sh`) fetches the Windows Update CAB
still work and firmware hashes match. images and the hashes match the Linux driver. Images land in `firmware/`.
- [ ] **Firmware load sequence** — Trace the Linux `xone_mt76_load_firmware()` - [x] **Firmware load sequence**: Resolved: ported to `src/mt76/mt76.cpp`
flow: control request to enter firmware mode bulk transfer in 0x3800-byte (control request to firmware mode, bulk transfer in 0x3800-byte chunks,
chunks → MCU completion poll chip reset. Map each step to IOKit equivalents. MCU completion poll, chip reset). Verified on hardware: FCE shows the
- [ ] **Post-firmware reconnect** — After firmware loads, the chip resets and firmware running after load.
re-enumerates. Verify the USB device reappears with the same VID/PID and can - [x] **Post-firmware reconnect**: Resolved: the chip re-enumerates with the
be re-opened. same VID/PID; the app and CLI close, wait, and reopen the device. The
full sequence runs end-to-end.
### 3. MT76 Register Access ### 3. MT76 Register Access
- [ ] **Vendor request format** — The Linux driver uses `usb_control_msg()` with - [x] **Vendor request format**: Resolved: the Linux values work unchanged via
vendor requests (bRequest 0x84/0x86 for register R/W). Confirm the exact `IOUSBDeviceInterface->DeviceRequest()` (bRequest 0x84/0x86 for register
`bmRequestType`, `bRequest`, `wValue`, `wIndex`, `wLength` values work via R/W). All radio init traffic uses this path.
`IOUSBDeviceInterface->DeviceRequest()`. - [x] **Register timing**: Resolved: `usleep()` / `clock_nanosleep()` in
- [ ] **Register timing** — Some register writes require delays between them. user-space are sufficient; radio init and channel evaluation complete
Test if `usleep()` in user-space provides sufficient precision, or if reliably.
`clock_nanosleep()` is needed. - [x] **EFUSE read**: Resolved: the EFUSE sequence returns valid MAC address,
- [ ] **EFUSE read** — Verify the EFUSE read sequence returns valid MAC address, chip ID, and TX power calibration data on macOS (`xone_cli info`).
chip ID, and TX power calibration data on macOS.
### 4. 802.11 Frame Handling ### 4. 802.11 Frame Handling
- [ ] **Beacon construction** — The Linux driver builds raw 802.11 beacon frames - [x] **Beacon construction**: Resolved: beacons with the Microsoft OUI
with a Microsoft OUI (00:50:f2) information element. Verify the frame format (00:50:f2) information element transmit correctly, including the chip
matches what the MT76 chip expects (may include chip-specific headers before header before the 802.11 frame. Controllers hear them and associate.
the 802.11 frame). - [x] **Frame encapsulation**: Resolved: TX/RX chip headers handled per the
- [ ] **Frame encapsulation** — The MT76 chip wraps 802.11 frames in a Linux driver (`send_wlan` / `process_wlan`). Association, pairing, and
proprietary header. Reverse-engineer or confirm the header format from Linux encryption-enable management frames verified against hardware.
driver source (`xone_mt76_tx()` / `xone_mt76_rx()`). - [ ] **QoS data frames**: RX path verified: controller input QoS frames are
- [ ] **QoS data frames** — Controller input/output uses 802.11 QoS data frames. decrypted, fed to the GIP layer, and shown live in the app. Host to
Verify the frame construction and AES-CCMP encryption/decryption flow. controller TX (rumble, LED over the data path) is not implemented yet.
### 5. AES-CCMP Encryption ### 5. AES-CCMP Encryption
- [x] **Crypto backend choice** CommonCrypto (SHA-256/HMAC), Security.framework - [x] **Crypto backend choice**: CommonCrypto (SHA-256/HMAC), Security.framework
(RSA PKCS#1), and a self-contained P-256 ECDH (no public macOS C API for EC (RSA PKCS#1), and a self-contained P-256 ECDH (no public macOS C API for EC
key agreement). Implemented in `src/auth/crypto.cpp`. key agreement). Implemented in `src/auth/crypto.cpp`.
- [x] **CCMP mode implementation** Not needed on the host: AES-CCMP runs on the - [x] **CCMP mode implementation**: Not needed on the host: AES-CCMP runs on the
MT76 chip; the host only installs keys via WCID registers MT76 chip; the host only installs keys via WCID registers
(`xone_mt76_set_client_key`). (`xone_mt76_set_client_key`).
- [x] **ECDH key exchange** P-256 implemented and validated against - [x] **ECDH key exchange**: P-256 implemented and validated against
OpenSSL-derived test vectors (`tests/test_crypto.cpp`). OpenSSL-derived test vectors (`tests/test_crypto.cpp`).
### 6. Virtual HID Gamepad ### 6. Virtual HID Gamepad
- [ ] **HID Proxy Driver feasibility** Research Apple's [HID Proxy - [ ] **HID Proxy Driver feasibility**: Research Apple's [HID Proxy
Driver](https://developer.apple.com/documentation/coreaudio/hid_proxy_driver) Driver](https://developer.apple.com/documentation/coreaudio/hid_proxy_driver)
(DriverKit). Can we create a virtual Xbox controller that games recognize (DriverKit). Can we create a virtual Xbox controller that games recognize
natively? natively?
- [ ] **IOHIDDevice user-space alternative** Can we create a virtual HID - [ ] **IOHIDDevice user-space alternative**: Can we create a virtual HID
device entirely in user-space? Test with `IOHIDManager` and see if games device entirely in user-space? Test with `IOHIDManager` and see if games
(Steam, Game Center) recognize it. (Steam, Game Center) recognize it.
- [ ] **HID report descriptor** Write an Xbox 360/One-compatible HID report - [ ] **HID report descriptor**: Write an Xbox 360/One-compatible HID report
descriptor. Test with existing Xbox controller (via Bluetooth) to capture the descriptor. Test with existing Xbox controller (via Bluetooth) to capture the
exact report format macOS expects. exact report format macOS expects.
- [ ] **Force feedback** Can the virtual HID device receive rumble commands - [ ] **Force feedback**: Can the virtual HID device receive rumble commands
from games and relay them to the controller via GIP? from games and relay them to the controller via GIP?
### 7. Core Audio (Headset Support) ### 7. Core Audio (Headset Support)
- [ ] **Audio Unit setup** Test creating an `AURenderCallback` / - [ ] **Audio Unit setup**: Test creating an `AURenderCallback` /
`AUOutputUnit` for headset playback and `AURecordingCallback` / `AUInputUnit` `AUOutputUnit` for headset playback and `AURecordingCallback` / `AUInputUnit`
for mic input. for mic input.
- [ ] **Latency requirements** The GIP protocol sends audio in 8ms intervals. - [ ] **Latency requirements**: The GIP protocol sends audio in 8ms intervals.
Can Core Audio maintain this latency without glitches? Can Core Audio maintain this latency without glitches?
- [ ] **Format negotiation** The headset negotiates audio format (sample rate, - [ ] **Format negotiation**: The headset negotiates audio format (sample rate,
channels) via GIP. Map GIP audio formats to Core Audio channels) via GIP. Map GIP audio formats to Core Audio
`AudioStreamBasicDescription`. `AudioStreamBasicDescription`.
### 8. Build System ### 8. Build System
- [ ] **CMake vs Xcode** — CMake is simpler for the C library portions, but - [x] **CMake vs Xcode**: Resolved: CMake with the Ninja generator compiles
Xcode is needed for the macOS app bundle and any DriverKit extension. Decide both the C++ stack and the Swift app (see Building). An Xcode project is
on primary build system. only needed later for a DriverKit extension, if Phase 4 goes that route.
- [ ] **Minimum macOS version** Target 12.0 (Monterey) for modern - [ ] **Minimum macOS version**: Target 12.0 (Monterey) for modern
IOKit/DriverKit. Verify all APIs are available. IOKit/DriverKit. Verify all APIs are available.
- [ ] **Code signing** — IOKit USB access may require specific entitlements - [ ] **Code signing**: Development builds run unsigned on the local machine
(`com.apple.kpi.iokit`, `com.apple.security.device.usb`). DriverKit requires with no entitlements needed for IOKit USB access. DriverKit
notarization. Plan for development vs distribution signing. notarization and distribution signing deferred to the HID phase.
### 9. Regulatory / Legal ### 9. Regulatory / Legal
- [ ] **5GHz channel restrictions** — macOS enforces regulatory domain for 5GHz. - [ ] **5GHz channel restrictions**: Channel evaluation picks a working
The dongle may try to use channels blocked in the current region. May need to channel and pairing is verified on hardware; no regulatory failures seen
limit to 2.4GHz only or find a way to override. so far. Regional edge cases (blocked channels) remain untested.
- [ ] **Firmware license** The firmware binaries are from Microsoft Windows - [ ] **Firmware license**: The firmware binaries are from Microsoft Windows
Update. Confirm they can be redistributed with the macOS port (the Linux Update. Confirm they can be redistributed with the macOS port (the Linux
driver includes them with a disclaimer). driver includes them with a disclaimer).
### 10. Testing Hardware ### 10. Testing Hardware
- [ ] **Dongle** Xbox Wireless Dongle (PID 0x02FE preferred, 0x02E6 also - [x] **Dongle**: Xbox Wireless Dongle (PID 0x02FE preferred, 0x02E6 also
works) works). In hand: PID 0x02E6 verified end-to-end.
- [ ] **Controller** Xbox One or Series X|S controller (for pairing and input - [x] **Controller**: Xbox One or Series X|S controller (for pairing and input
testing) testing). In hand: pairing and live input verified.
- [ ] **Headset** Xbox Wireless Headset (optional, for audio testing) - [ ] **Headset**: Xbox Wireless Headset (optional, for audio testing)
- [ ] **macOS machine** Intel or Apple Silicon (test both if possible, IOKit - [ ] **macOS machine**: Intel or Apple Silicon (test both if possible, IOKit
may differ) may differ)
--- ---
@@ -166,7 +171,7 @@ Strike through or check off as each is resolved.
| `usb_submit_urb()` | `ReadPipeAsync()` + `CFRunLoopSource` | ✅ Done (reader thread) | | `usb_submit_urb()` | `ReadPipeAsync()` + `CFRunLoopSource` | ✅ Done (reader thread) |
| `kzalloc` / `kfree` | `malloc` / `free` | ✅ Straightforward | | `kzalloc` / `kfree` | `malloc` / `free` | ✅ Straightforward |
| `spin_lock_irqsave` | `std::mutex` / `std::condition_variable` | ✅ Done (`usb_transport.cpp`) | | `spin_lock_irqsave` | `std::mutex` / `std::condition_variable` | ✅ Done (`usb_transport.cpp`) |
| `msleep` / `mdelay` | `usleep()` / `clock_nanosleep()` | ☐ Test timing | | `msleep` / `mdelay` | `usleep()` / `clock_nanosleep()` | ✅ Done (radio init, channel eval) |
| `crypto_shash_*` | CommonCrypto / Security.framework | ✅ Done (`auth/crypto.cpp`) | | `crypto_shash_*` | CommonCrypto / Security.framework | ✅ Done (`auth/crypto.cpp`) |
| `input_register_device()` | HID Proxy Driver / IOHIDSystem | ☐ Investigate | | `input_register_device()` | HID Proxy Driver / IOHIDSystem | ☐ Investigate |
| `snd_pcm_*` | Core Audio (Audio Units) | ☐ Investigate | | `snd_pcm_*` | Core Audio (Audio Units) | ☐ Investigate |
@@ -177,14 +182,21 @@ Strike through or check off as each is resolved.
--- ---
## First Steps on macOS ## Current Status
1. Plug in the dongle, run `system_profiler SPUSBDataType` — confirm it's Working end-to-end on hardware:
detected
2. Check `log show --predicate 'subsystem == "com.apple.iokit"'` — see if macOS - Dongle probe/open, async read pump, vendor register R/W
loads any driver - Firmware download (`scripts/download-firmware.sh`) and load
3. Open the device with `transport::probe()` and confirm endpoint enumeration - EFUSE read, radio init, channel evaluation, beacon TX
(EP 0x04 IN/OUT, EP 0x05 IN) - Pairing mode, controller association, GIP handshake + auth
4. Try a vendor control request (register read) to verify USB communication - Live controller input (buttons, sticks, triggers) in the Swift app
works
5. Attempt firmware load with the binary from `firmware/` directory Not done yet: host to controller TX over the data path (rumble, LED),
virtual HID gamepad (Phase 4), headset audio (Phase 5).
`xone_cli` exercises the stack manually in a single dongle session:
```sh
./build/xone_cli info firmware firmware/xow_dongle.bin radio-init pair
```