commit 78f042ab84bb368289875d87b5cbca65e8d94807 Author: Pratchaya Khansomboon Date: Tue Aug 4 17:12:54 2026 +0200 Initial commit diff --git a/PLAN.md b/PLAN.md new file mode 100644 index 0000000..80aeac4 --- /dev/null +++ b/PLAN.md @@ -0,0 +1,241 @@ +# Xbox Wireless Dongle — macOS Port + +## Goal + +User-space macOS app that speaks to the Xbox Wireless Dongle (MT76xx chip) and exposes connected controllers as HID gamepads. + +## Architecture + +``` +┌──────────────────────────────────────────────────────┐ +│ macOS App (Swift/C++) │ +│ ┌──────────┐ ┌──────────┐ ┌──────────────────┐ │ +│ │ App/UI │ │ HID Proxy│ │ Core Audio (opt) │ │ +│ └────┬─────┘ └────▲─────┘ └──────────────────┘ │ +│ │ │ │ +│ ┌────▼──────────────▼──────────────────────┐ │ +│ │ GIP Protocol Layer │ │ +│ │ (bus/protocol.c — ported, pure C) │ │ +│ └──────────────────┬───────────────────────┘ │ +│ │ │ +│ ┌──────────────────▼───────────────────────┐ │ +│ │ MT76 Chip Protocol Layer │ │ +│ │ (transport/mt76.c — ported, USB calls) │ │ +│ └──────────────────┬───────────────────────┘ │ +│ │ │ +│ ┌──────────────────▼───────────────────────┐ │ +│ │ USB Transport Layer (new) │ │ +│ │ (IOKit / IOUSBInterfaceInterface) │ │ +│ └──────────────────────────────────────────┘ │ +│ │ │ +│ ┌──────────────────▼───────────────────────┐ │ +│ │ Xbox Wireless Dongle │ │ +│ │ VID:045E PID:02FE (02E6, 02F9, 091E) │ │ +│ └──────────────────────────────────────────┘ │ +└──────────────────────────────────────────────────────┘ +``` + +## Directory Layout + +``` +macos/ +├── PLAN.md ← this file +├── src/ +│ ├── usb/ ← IOKit USB transport (new) +│ ├── mt76/ ← MT76 chip protocol (port from transport/mt76.c) +│ ├── gip/ ← GIP protocol (port from bus/protocol.c) +│ ├── auth/ ← Auth + crypto (port from auth/) +│ ├── hid/ ← Virtual HID gamepad (new) +│ └── app/ ← macOS app entry point + UI (new) +├── include/ +│ ├── common/ ← Shared types, platform abstraction +│ ├── usb/ +│ ├── mt76/ +│ ├── gip/ +│ ├── auth/ +│ └── hid/ +├── scripts/ +│ └── download-firmware.sh ← Port of install/firmware.sh +├── firmware/ ← Downloaded firmware binaries (gitignored) +└── build/ ← Build output (gitignored) +``` + +## Implementation Phases + +### Phase 1 — Foundation (Linux → C library) + +Extract the protocol logic from Linux kernel code into standalone C. + +**src/gip/** + +- Port `bus/protocol.c` → pure C, no kernel deps +- Redefine `__le16`, `__packed`, `guid_t` for user-space +- Replace `kzalloc`/`kfree` → `malloc`/`free` +- Replace `spin_lock_irqsave` → pthread mutex or lock-free ring buffer +- Replace `dev_dbg`/`gip_err` → NSLog or custom logger +- Replace `EXPORT_SYMBOL_GPL` → nothing (static library) + +**src/auth/** + +- Port `auth/auth.c` + `auth/crypto.c` → AES-CCMP, authentication handshake +- Should be mostly copy-paste, these are pure crypto + +**include/common/** + +- Platform types (`stdint.h` based) +- Endianness helpers +- Memory allocator abstraction (so we can swap malloc if needed) + +### Phase 2 — USB Transport (new) + +**src/usb/** + +- IOKit device matching on `USB\VID_045E&PID_02FE` (and other PIDs) +- Open device, claim interface (bInterfaceNumber = 1) +- `IOUSBDevInterface` for control transfers (vendor requests) +- `IOUSBInterfaceInterface` for bulk endpoints: + - EP 0x04 IN — WLAN data (802.11 frames from controllers) + - EP 0x04 OUT — Bulk out (commands to chip) + - EP 0x05 IN — MCU commands (firmware load responses) +- Async completion callbacks → callback/dispatch queue +- Device disconnect handling (chip reconnects during firmware load) + +**Key IOKit APIs:** + +- `IOServiceMatching("IOUSBDevice")` — find dongle +- `IOUSBDeviceOpen` / `IOUSBInterfaceOpen` — claim +- `DeviceRequest` — control transfers (register R/W) +- `WritePipe` / `ReadPipe` — bulk transfers +- `CreateInterruptEndpoint` — for EP 0x05 (MCU) + +### Phase 3 — MT76 Chip Protocol (port) + +**src/mt76/** + +- Port `transport/mt76.c` → replace USB calls with Phase 2 transport +- **Firmware loading:** send binary in 0x3800-byte chunks, poll for completion +- **EFUSE read:** MAC address, chip ID, crystal trim, TX power calibration +- **Radio init:** ~80 hardcoded register writes (AGC, EDCA, TX power, protection) +- **Channel evaluation:** cycle through 12 channels, pick highest power +- **Beacon transmission:** 802.11 beacons with Microsoft OUI IE +- **Pairing mode:** rotate channels every 2s when pairing enabled +- **Controller association:** 802.11 assoc, WCID assignment, AES-CCMP encryption +- **Data path:** QoS data frames to/from controllers + +**Key replacement map:** + +- `usb_control_msg()` → `IOUSBDevInterface->DeviceRequest()` +- `usb_bulk_msg()` → `IOUSBInterfaceInterface->WritePipe()` / `ReadPipe()` +- `kzalloc`/`skb_put` → `malloc`/buffer management +- `msleep`/`mdelay` → `usleep`/`nanosleep` +- `ieee80211_*` → custom 802.11 frame builders +- `cfg80211_*` → nothing (no regulatory domain reporting needed) + +### Phase 4 — Virtual HID Gamepad (new) + +**src/hid/** + +- Expose controller as macOS HID device so games work natively +- Options: + - **HID Proxy Driver (DriverKit)** — Maps USB device to virtual HID. Minimal kernel code. Preferred approach. + - **IOHIDSystem user-space** — Create virtual HID device entirely in user-space. May not work for all games. + - **Gamepad wrapper** — Lower-level, translate input events to HID reports. +- Map Xbox controller buttons/sticks/triggers to standard Xbox 360/One HID report descriptor +- Handle force feedback (rumble) — send back to dongle via GIP +- Battery status reporting + +### Phase 5 — macOS App (new) + +**src/app/** + +- Swift or C++ main app +- Device discovery (IOKit notification when dongle plugged in) +- Pairing UI (LED blink, controller button press) +- Status display (connected controllers, battery) +- Preferences (channel selection, LED mode) +- Menubar icon for status +- Firmware download helper (script or in-app) + +## Dongle Initialization Sequence + +``` +1. Detect dongle via IOKit +2. Download/verify firmware (firmware.sh) +3. Load firmware into chip (bulk transfer, 0x3800 byte chunks) +4. Chip disconnects + reconnects (handle this!) +5. Read EFUSE (MAC, chip ID, TX power cal) +6. Initialize radio (~80 register writes) +7. Evaluate channels (find best channel) +8. Start beacon transmission +9. Enter pairing mode (channel rotation) +10. When controller associates: + a. Parse 802.11 assoc request + b. Assign WCID (client ID) + c. Set up AES-CCMP encryption + d. Send assoc response +11. GIP handshake: + a. Controller ANNOUNCE (vendor/product/fw version) + b. Host sends IDENTIFY request + c. Controller sends IDENTIFY (capabilities, interfaces, HID descriptor) + d. Auth handshake (AES-CCMP key exchange) + e. Status reports begin (battery, connected) +12. Game loop: + a. Controller → HID reports (input) + b. Host → rumble, LED, audio control +``` + +## Firmware + +Firmware binaries are downloaded from Microsoft Windows Update driver catalog: + +| PID | Dongle Type | Firmware File | +| ---- | ---------------------- | -------------------- | +| 02E6 | Old dongle | xone_dongle_02e6.bin | +| 02FE | New dongle | xone_dongle_02fe.bin | +| 02F9 | Built-in (ASUS/Lenovo) | xone_dongle_02f9.bin | +| 091E | Surface Book 2 | xone_dongle_091e.bin | + +Downloaded via `scripts/download-firmware.sh` (port of `install/firmware.sh`). + +## Known Challenges + +1. **USB timing** — MT76 is timing-sensitive. User-space USB on macOS may have different latency than Linux kernel URBs. May need careful tuning of `usleep` values. + +2. **Chip reconnect** — During firmware load, the dongle disconnects and reconnects. IOKit needs to handle this gracefully (close, wait, reopen, re-claim). + +3. **Virtual HID** — Games expect a real HID device. HID Proxy Driver (DriverKit) is the cleanest path but requires a minimal kernel extension. + +4. **5GHz regulatory** — The dongle uses 5GHz channels. macOS may have regulatory restrictions. May need to limit to 2.4GHz only. + +5. **Audio** — Headset audio is complex (real-time PCM streaming). Lower priority, tackle after gamepad works. + +## Source Files to Port + +| Linux source | Target | Notes | +| ----------------------- | -------------------------- | ---------------------------------- | +| `transport/mt76.c` | `src/mt76/mt76.c` | Replace USB calls, remove cfg80211 | +| `transport/mt76.h` | `include/mt76/mt76.h` | Clean up kernel types | +| `transport/mt76_defs.h` | `include/mt76/mt76_defs.h` | Mostly copy (register defs) | +| `bus/protocol.c` | `src/gip/protocol.c` | Replace kernel alloc/lock/debug | +| `bus/protocol.h` | `include/gip/protocol.h` | Clean up kernel types | +| `bus/bus.c` | `src/gip/bus.c` | Client lifecycle management | +| `auth/auth.c` | `src/auth/auth.c` | Pure crypto, mostly copy | +| `auth/auth.h` | `include/auth/auth.h` | Copy | +| `auth/crypto.c` | `src/auth/crypto.c` | AES-CCMP, mostly copy | +| `auth/crypto.h` | `include/auth/crypto.h` | Copy | +| `driver/gamepad.c` | N/A | Replaced by HID layer | + +## Build System + +- **CMake** or **Xcode project** — either works +- Static library for protocol/auth layers +- macOS app bundle for the final product +- Minimum macOS: 12.0 (Monterey) — for modern IOKit/DriverKit support + +## Dependencies + +- IOKit (system) +- CoreFoundation (system) +- HID Proxy Driver framework (optional, for virtual gamepad) +- Core Audio (optional, for headset support) +- No external dependencies for protocol/auth layers diff --git a/README.md b/README.md new file mode 100644 index 0000000..06a79ed --- /dev/null +++ b/README.md @@ -0,0 +1,109 @@ +# Xbox Wireless Dongle — macOS Port + +User-space macOS app for the Xbox Wireless Dongle (MediaTek MT76xx). +Ports the Linux kernel driver [`xone/`](../xone/) to macOS. + +See [PLAN.md](PLAN.md) for full architecture and implementation phases. + +--- + +## Investigation Checklist + +These items need research on macOS before implementation can begin. +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. + +### 2. Firmware Loading + +- [ ] **Firmware binary availability** — Run `../xone/install/firmware.sh` (or ported `scripts/download-firmware.sh`) to confirm the Windows Update CAB URLs still work and firmware hashes match. +- [ ] **Firmware load sequence** — Trace the Linux `xone_mt76_load_firmware()` flow: control request to enter firmware mode → bulk transfer in 0x3800-byte chunks → MCU completion poll → chip reset. Map each step to IOKit equivalents. +- [ ] **Post-firmware reconnect** — After firmware loads, the chip resets and re-enumerates. Verify the USB device reappears with the same VID/PID and can be re-opened. + +### 3. MT76 Register Access + +- [ ] **Vendor request format** — The Linux driver uses `usb_control_msg()` with vendor requests (bRequest 0x84/0x86 for register R/W). Confirm the exact `bmRequestType`, `bRequest`, `wValue`, `wIndex`, `wLength` values work via `IOUSBDeviceInterface->DeviceRequest()`. +- [ ] **Register timing** — Some register writes require delays between them. Test if `usleep()` in user-space provides sufficient precision, or if `clock_nanosleep()` is needed. +- [ ] **EFUSE read** — Verify the EFUSE read sequence returns valid MAC address, chip ID, and TX power calibration data on macOS. + +### 4. 802.11 Frame Handling + +- [ ] **Beacon construction** — The Linux driver builds raw 802.11 beacon frames with a Microsoft OUI (00:50:f2) information element. Verify the frame format matches what the MT76 chip expects (may include chip-specific headers before the 802.11 frame). +- [ ] **Frame encapsulation** — The MT76 chip wraps 802.11 frames in a proprietary header. Reverse-engineer or confirm the header format from Linux driver source (`xone_mt76_tx()` / `xone_mt76_rx()`). +- [ ] **QoS data frames** — Controller input/output uses 802.11 QoS data frames. Verify the frame construction and AES-CCMP encryption/decryption flow. + +### 5. AES-CCMP Encryption + +- [ ] **Crypto backend choice** — Decide between: + - **CommonCrypto** (system, zero deps) — `CCryptorCreate()` for AES-CTR/CBC + - **Security.framework** (system) — `SecKeyRef` for ECDH key exchange + - **libcrypto/OpenSSL** (Homebrew) — `EVP_*` APIs, more familiar but external dep +- [ ] **CCMP mode implementation** — AES-CCMP = AES-CTR encryption + AES-CBC-MAC authentication. Neither CommonCrypto nor OpenSSL has a direct CCMP API. Need to implement the mode manually (encrypt then MIC, or verify MIC then decrypt). +- [ ] **ECDH key exchange** — The authentication handshake uses ECDH (P-256 curve). Test `SecKeyCreateWithData()` + `SecKeyCopyKeyExchangeResult()` on macOS for key agreement. + +### 6. Virtual HID Gamepad + +- [ ] **HID Proxy Driver feasibility** — Research Apple's [HID Proxy Driver](https://developer.apple.com/documentation/coreaudio/hid_proxy_driver) (DriverKit). Can we create a virtual Xbox controller that games recognize natively? +- [ ] **IOHIDDevice user-space alternative** — Can we create a virtual HID device entirely in user-space? Test with `IOHIDManager` and see if games (Steam, Game Center) recognize it. +- [ ] **HID report descriptor** — Write an Xbox 360/One-compatible HID report descriptor. Test with existing Xbox controller (via Bluetooth) to capture the exact report format macOS expects. +- [ ] **Force feedback** — Can the virtual HID device receive rumble commands from games and relay them to the controller via GIP? + +### 7. Core Audio (Headset Support) + +- [ ] **Audio Unit setup** — Test creating an `AURenderCallback` / `AUOutputUnit` for headset playback and `AURecordingCallback` / `AUInputUnit` for mic input. +- [ ] **Latency requirements** — The GIP protocol sends audio in 8ms intervals. Can Core Audio maintain this latency without glitches? +- [ ] **Format negotiation** — The headset negotiates audio format (sample rate, channels) via GIP. Map GIP audio formats to Core Audio `AudioStreamBasicDescription`. + +### 8. Build System + +- [ ] **CMake vs Xcode** — CMake is simpler for the C library portions, but Xcode is needed for the macOS app bundle and any DriverKit extension. Decide on primary build system. +- [ ] **Minimum macOS version** — Target 12.0 (Monterey) for modern IOKit/DriverKit. Verify all APIs are available. +- [ ] **Code signing** — IOKit USB access may require specific entitlements (`com.apple.kpi.iokit`, `com.apple.security.device.usb`). DriverKit requires notarization. Plan for development vs distribution signing. + +### 9. Regulatory / Legal + +- [ ] **5GHz channel restrictions** — macOS enforces regulatory domain for 5GHz. The dongle may try to use channels blocked in the current region. May need to limit to 2.4GHz only or find a way to override. +- [ ] **Firmware license** — The firmware binaries are from Microsoft Windows Update. Confirm they can be redistributed with the macOS port (the Linux driver includes them with a disclaimer). + +### 10. Testing Hardware + +- [ ] **Dongle** — Xbox Wireless Dongle (PID 0x02FE preferred, 0x02E6 also works) +- [ ] **Controller** — Xbox One or Series X|S controller (for pairing and input testing) +- [ ] **Headset** — Xbox Wireless Headset (optional, for audio testing) +- [ ] **macOS machine** — Intel or Apple Silicon (test both if possible, IOKit may differ) + +--- + +## Quick Reference + +| Linux API | macOS Replacement | Status | +|-----------|-------------------|--------| +| `usb_control_msg()` | `IOUSBDeviceInterface->DeviceRequest()` | ☐ Investigate | +| `usb_bulk_msg()` | `IOUSBInterfaceInterface->WritePipe()` / `ReadPipe()` | ☐ Investigate | +| `usb_submit_urb()` | `ReadPipeAsync()` + `CFRunLoopSource` | ☐ Investigate | +| `kzalloc` / `kfree` | `malloc` / `free` | ✅ Straightforward | +| `spin_lock_irqsave` | `pthread_mutex_t` or lock-free | ☐ Design | +| `msleep` / `mdelay` | `usleep()` / `clock_nanosleep()` | ☐ Test timing | +| `crypto_shash_*` | CommonCrypto / Security.framework | ☐ Choose backend | +| `input_register_device()` | HID Proxy Driver / IOHIDSystem | ☐ Investigate | +| `snd_pcm_*` | Core Audio (Audio Units) | ☐ Investigate | +| `request_firmware()` | File I/O (`fopen`/`fread`) | ✅ Straightforward | +| `cfg80211_*` | Nothing (no regulatory reporting) | ✅ Remove | +| `bus_register()` | Custom client management | ✅ Redesign | +| `device_create()` / sysfs | Nothing (no sysfs) | ✅ Remove | + +--- + +## First Steps on macOS + +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 +4. Try a vendor control request (register read) to verify USB communication works +5. Attempt firmware load with the binary from `firmware/` directory