b3e747b900
Split the dongle session into a fast probe (xone_open) and a background start (xone_start) that loads the firmware and initializes the radio on a worker thread, so the app UI never blocks. Add xone_state/xone_error for progress display, and select the firmware image per product ID when no path is given. The Swift app polls the state each second and shows idle/starting/ready/error plus the firmware build or error string. The session destructor joins the worker so quitting the app does not terminate on a joinable thread. Note in the CLI and transport that the recover/re-enumerate path should not be used yet: a crashed SIE drops off the bus, while the MCU watchdog recovers it after ~90s of quiet. Co-Authored-By: qwen3.8-27b@q2_k_xl: async C API, Swift state display, and exit-time worker join
135 lines
5.6 KiB
C++
135 lines
5.6 KiB
C++
#pragma once
|
|
|
|
// ==============================================================================
|
|
// USB transport (IOKit / IOUSBFamily)
|
|
// ==============================================================================
|
|
// Owns the physical Xbox Wireless Dongle:
|
|
// VID 0x045E, PIDs 0x02E6 (old), 0x02FE (new), 0x02F9 (ASUS/Lenovo built-in),
|
|
// 0x091E (Surface Book 2).
|
|
//
|
|
// Port target: medusalix/xone transport/dongle.c + the USB calls in mt76.c.
|
|
// Linux usb_control_msg()/usb_bulk_msg() map to IOUSBDeviceInterface
|
|
// DeviceRequest and IOUSBInterfaceInterface WritePipe/ReadPipeAsync.
|
|
// ==============================================================================
|
|
|
|
#include <cstddef>
|
|
#include <cstdint>
|
|
#include <functional>
|
|
#include <memory>
|
|
|
|
namespace xone::usb {
|
|
|
|
// Vendor ID of the Xbox Wireless Dongle.
|
|
constexpr std::uint16_t vid = 0x045E;
|
|
|
|
// Supported dongle PIDs (port of xone_dongle_id_table in transport/dongle.c).
|
|
constexpr std::uint16_t pid_old_dongle = 0x02E6;
|
|
constexpr std::uint16_t pid_new_dongle = 0x02FE;
|
|
constexpr std::uint16_t pid_builtin_asus_lenovo = 0x02F9;
|
|
constexpr std::uint16_t pid_surface_book_2 = 0x091E;
|
|
|
|
// Vendor control request codes (port of MT_VEND_* in transport/mt76_defs.h).
|
|
enum class vendor_request : std::uint8_t {
|
|
dev_mode = 0x01, // enter firmware load mode
|
|
write = 0x02, // register write
|
|
power_on = 0x04,
|
|
multi_write = 0x06, // register write (multi)
|
|
multi_read = 0x07, // register read (multi)
|
|
read_eeprom = 0x09,
|
|
write_fce = 0x42,
|
|
write_cfg = 0x46, // register write (config space)
|
|
read_cfg = 0x47, // register read (config space)
|
|
read_ext = 0x63,
|
|
write_ext = 0x66,
|
|
feature_set = 0x91,
|
|
};
|
|
|
|
// Bulk endpoint numbers (port of XONE_MT_EP_* in transport/mt76.h).
|
|
constexpr std::uint8_t ep_in_cmd = 0x05; // MCU command responses
|
|
constexpr std::uint8_t ep_in_wlan = 0x04; // WLAN data frames
|
|
constexpr std::uint8_t ep_out = 0x04; // commands to the chip
|
|
|
|
// Receive buffer sizes (port of XONE_DONGLE_LEN_* in transport/dongle.c).
|
|
constexpr std::size_t len_cmd_pkt = 0x0654;
|
|
constexpr std::size_t len_wlan_pkt = 0x8400;
|
|
|
|
// Delivered from the transport's reader thread for every message received
|
|
// on a bulk IN endpoint. `ep` is ep_in_cmd or ep_in_wlan. Do not block and
|
|
// do not destroy the transport from within the callback.
|
|
using frame_callback = std::function<void(std::uint8_t ep, void const *data, std::size_t len)>;
|
|
|
|
// Delivered once from the transport's reader thread when the dongle is
|
|
// disconnected (including the chip reconnect during firmware load). Do not
|
|
// block and do not destroy the transport from within the callback.
|
|
using disconnect_callback = std::function<void()>;
|
|
|
|
// Owns an open Xbox Wireless Dongle: its device and interface connections,
|
|
// endpoint pipes, and the reader thread that pumps async bulk IN reads.
|
|
class transport {
|
|
public:
|
|
// Probe for a connected dongle (any supported PID), open it, and start
|
|
// the async read pump on EP 0x05 IN and EP 0x04 IN. `frames` receives
|
|
// each received message and `disconnected` fires when the dongle goes
|
|
// away; either may be empty. Returns nullptr if no dongle is present or
|
|
// opening fails.
|
|
static auto probe(frame_callback frames, disconnect_callback disconnected) -> std::unique_ptr<transport>;
|
|
|
|
// Force a hub port reset and re-enumeration (port of the Linux remove+
|
|
// rescan). Host-side operation that works even when EP0 is wedged. Any
|
|
// existing transport handles are invalidated; probe() again for a fresh
|
|
// session. Returns 0 on success, -errno on failure.
|
|
//
|
|
// Do not use yet: when the chip's SIE is crashed it cannot finish the
|
|
// host-side port reset handshake and the device drops off the bus
|
|
// entirely. A crashed chip recovers on its own via the MCU watchdog after
|
|
// ~90s of a quiet bus, so prefer waiting over re-enumeration.
|
|
auto re_enumerate() -> int;
|
|
|
|
~transport();
|
|
|
|
transport(transport const&) = delete;
|
|
transport& operator=(transport const&) = delete;
|
|
|
|
// Product ID of the connected dongle.
|
|
auto pid() const -> std::uint16_t;
|
|
|
|
// Vendor control request on EP0 (register R/W, firmware mode). `is_read`
|
|
// selects IN (`data` holds len bytes to receive) or OUT (`data` holds
|
|
// len bytes to send). Returns bytes transferred, or -EIO.
|
|
auto send_vendor_request(vendor_request req, bool is_read, std::uint16_t w_value,
|
|
std::uint16_t w_index, void *data, std::size_t len) -> int;
|
|
|
|
// Bulk write to EP 0x04 OUT. Returns bytes written, or -EIO.
|
|
auto bulk_write(void const *data, std::size_t len) -> int;
|
|
|
|
private:
|
|
struct read_slot;
|
|
struct state;
|
|
|
|
transport() = default;
|
|
|
|
auto open(frame_callback frames, disconnect_callback disconnected) -> bool;
|
|
// IOKit services are uint32_t registry handles (io_service_t).
|
|
auto open_interface(std::uint32_t child) -> bool;
|
|
auto submit_read(read_slot *slot) -> bool;
|
|
|
|
// Abort the in-flight async reads, stop the reader thread, and join it.
|
|
// Idempotent; safe to call from the destructor and re_enumerate().
|
|
void stop_pump();
|
|
|
|
void worker_loop();
|
|
// IOKit callbacks use IOReturn (int32_t) and io_iterator_t (uint32_t).
|
|
void handle_read(read_slot *slot, std::int32_t result, std::size_t len);
|
|
void handle_disconnected();
|
|
|
|
static void on_read_completion(void *refcon, std::int32_t result, void *arg0);
|
|
static void on_dongle_terminated(void *refcon, std::uint32_t iter);
|
|
|
|
std::unique_ptr<state> state_;
|
|
};
|
|
|
|
// TRUE if an Xbox Wireless Dongle is connected (any supported PID).
|
|
auto dongle_present() -> bool;
|
|
|
|
} // namespace xone::usb
|