# 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) │ │ │ └──────────────────────────────────────────┘ │ └──────────────────────────────────────────────────────┘ ``` ## Current Status - Phase 1 (GIP + auth): ported and unit-tested. - Phase 2 (USB transport): probe/open, async read pump, vendor requests, bulk write. Verified on hardware. - Phase 3 (MT76 chip): register/EFUSE access, firmware load (per-PID images), radio init (registers, crystal, MAC/BSSID, channel eval, beacon). Verified end-to-end: `radio-init` completes, beacon TX enabled, FCE shows firmware running. - C API + Swift app: async session (fast probe; firmware + radio on a worker thread), state display (idle/starting/ready/error). Remaining for Phase 3: controller association and the data path (design below). Then Phase 4 (HID) and Phase 5 (app polish). **Immediate goal:** pair a controller and show it in the GUI. Exposing it as a macOS HID device is deferred. ## 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) ## Controller Association Design Ported from `transport/dongle.c` and `transport/mt76.c`. Goal: pair a controller and show it in the GUI. Exposing it as a macOS HID device is deferred. ### RX Path Both EP IN endpoints (0x04 WLAN, 0x05 CMD) feed one handler: 1. `process_buffer`: take the raw IN buffer. 2. `process_message`: parse the u32 info header; read D_PORT. Ignore command responses (CMD_SEQ == 0x01). Strip header + 4-byte trailer. - D_PORT == WLAN: go to step 3. - D_PORT == CPU_RX: dispatch by EVT_TYPE: - BUTTON (0x04): enter pairing mode. - PACKET_RX (0x0c): go to step 3 (payload is a WLAN frame). - CLIENT_LOST (0x0e): payload[0] = wcid; remove that client. 3. `process_wlan`: parse rxwi (16 bytes); if RXINFO_L2PAD set, skip the 2-byte pad after the 802.11 header; trim to MPDU_LEN from rxwi.ctl. 4. `process_frame`: dispatch by frame_control: - DATA|QOS_DATA: feed the client's GIP adapter (`gip_process_buffer`). - MGMT|ASSOC_REQ: add a client (addr2 = controller MAC). - MGMT|DISASSOC: remove client (wcid from rxwi.ctl). - MGMT|0x70 (WLAN_RESERVED): client command; payload[1] is PAIR_REQ (0x01) or ENABLE_ENCRYPTION (0x10). ### Client Lifecycle - create_client: find a free WCID (1..16); create a GIP adapter for it. - associate_client(wcid, mac): `write_burst(WCID_ADDR, mac)`; ms_command( ADD_CLIENT, {wcid-1,0,0,0,0x40,0x1f,0,0}); send an ASSOC_RESP mgmt frame via `send_wlan` (fc = MGMT|ASSOC_RESP, da/sa/bssid, status_code = 0x0110, aid = 0x0f00). - pair_client(mac): send a PAIR_RESP mgmt frame via `send_wlan` (fc = MGMT|0x70, reserved + PAIR_RESP byte + 9-byte payload). - remove_client(wcid): ms_command(REMOVE_CLIENT, {wcid-1,0,0,0}); zero the WCID ADDR/IV/ATTR regions. - LED: on when a client is added outside pairing mode; off when the last client leaves. ### Architecture One GIP adapter per controller, keyed by the chip's WCID (matches upstream). The rxwi.wcid identifies which controller a frame belongs to; within each adapter the GIP client ID is 0. Our ported `get_client` auto-creates a client on first packet, so a fresh adapter yields its single client on demand. (Verify the GIP header client ID on hardware.) ### Constants To Add (mt76_defs.hpp) - WCID regions: ADDR base 0x1800 (+n*8), KEY base 0x8000 (+n*32, len 16), IV base 0xa000 (+n*8), ATTR base 0xa800 (+n*4); ATTR pairwise bit 0, pkey mode genmask(3,1) = AES_CCMP (4). - TXD info: DPORT genmask(29,27), QSEL genmask(26,25) EDCA=2, WIV bit 24, 80211 bit 19. - RX FCE info: CMD_SEQ genmask(19,16), EVT_TYPE genmask(23,20), D_PORT genmask(29,27). - rxwi: RXINFO_L2PAD bit 14; CTL_WCID genmask(7,0); CTL_MPDU_LEN genmask(29,16). - Events: BUTTON 0x04, PACKET_RX 0x0c, CLIENT_LOST 0x0e. - Client commands: WLAN_RESERVED fc 0x70; PAIR_REQ 0x01, PAIR_RESP 0x02, ENABLE_ENCRYPTION 0x10. - 802.11 FCTL: MGMT 0x00, DATA 0x08; ASSOC_REQ 0x00, ASSOC_RESP 0x10, DISASSOC 0xa0, QOS_DATA 0x70. ### chip Functions To Port (mt76.c) `send_wlan`, `associate_client`, `pair_client`, `send_client_command`, `set_client_key`, `remove_client`. ### GUI - C API: expose connected controllers (count + per-client info: MAC, product name from GIP identify, battery). - Swift app: list controllers in the Controllers section of the Debug view. ## 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.hpp` | Clean up kernel types | | `transport/mt76_defs.h` | `include/mt76/mt76_defs.hpp` | Mostly copy (register defs) | | `bus/protocol.c` | `src/gip/protocol.c` | Replace kernel alloc/lock/debug | | `bus/protocol.h` | `include/gip/protocol.hpp` | 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.hpp` | Copy | | `auth/crypto.c` | `src/auth/crypto.cpp` | AES-CCMP, mostly copy | | `auth/crypto.h` | `include/auth/crypto.hpp` | 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