diff --git a/CMakeLists.txt b/CMakeLists.txt index f56705d..63d55f9 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -6,7 +6,7 @@ if(NOT CMAKE_GENERATOR MATCHES "^(Ninja|Xcode)$") endif() cmake_minimum_required(VERSION 3.21) -project(xone_macos VERSION 0.1.7 LANGUAGES CXX Swift) +project(xone_macos VERSION 0.1.8 LANGUAGES CXX Swift) set(CMAKE_EXPORT_COMPILE_COMMANDS ON) @@ -103,6 +103,16 @@ target_compile_options(xone_app PRIVATE -import-objc-header "${CMAKE_SOURCE_DIR}/include/app/xone_api.h" ) +# ============================================================================== +# Command line tool (protocol stack debugging against hardware) +# ============================================================================== +add_executable(xone_cli + "src/cli/main.cpp" +) +target_include_directories(xone_cli PRIVATE "${CMAKE_SOURCE_DIR}/include") +target_compile_definitions(xone_cli PRIVATE ${BASE_DEFINITIONS}) +target_link_libraries(xone_cli PRIVATE xone_mt76) + # ============================================================================== # Tests # ============================================================================== diff --git a/include/common/log.hpp b/include/common/log.hpp index d459c68..2745703 100644 --- a/include/common/log.hpp +++ b/include/common/log.hpp @@ -43,6 +43,7 @@ inline auto log_msg(log_level level, char const* fmt, ...) -> void std::vfprintf(stderr, fmt, ap); va_end(ap); std::fprintf(stderr, "\n"); + std::fflush(stderr); // The app is often killed; do not lose the tail. } } // namespace xone diff --git a/include/mt76/mt76.hpp b/include/mt76/mt76.hpp index d620ad2..dd666b6 100644 --- a/include/mt76/mt76.hpp +++ b/include/mt76/mt76.hpp @@ -13,6 +13,8 @@ #include #include +#include "mt76/mt76_defs.hpp" + namespace xone::usb { class transport; } @@ -48,6 +50,25 @@ public: // Build string of the loaded firmware image (empty until load_firmware). auto firmware_build() const -> char const *; + // MCU commands (port of the xone_mt76_* wrappers around send_command). + auto set_led_mode(std::uint32_t mode) -> int; + auto select_function(std::uint32_t func, std::uint32_t val) -> int; + auto set_power_mode(power_mode mode) -> int; + auto load_cr(cr_mode mode) -> int; + auto write_burst(std::uint32_t idx, void *data, std::size_t len) -> int; + auto send_ms_command(ms_command cmd, void *data, std::size_t len) -> int; + auto calibrate(calibration calib, std::uint32_t val) -> int; + + // Initialize the radio (port of xone_mt76_init_radio). Returns 0 or -errno. + auto init_radio() -> int; + + // Suspend or resume the radio (ports of xone_mt76_suspend/resume_radio). + auto suspend_radio() -> int; + auto resume_radio() -> int; + + // Enter or leave pairing mode (port of xone_mt76_set_pairing). + auto set_pairing(bool enable) -> int; + private: // Send an MCU command (port of xone_mt76_send_command). `cmd` is the // MT_MCU_MSG_CMD_TYPE field. Returns bytes written, or a negative errno. @@ -61,8 +82,27 @@ private: auto send_firmware(void const *fw_data, std::size_t fw_size) -> int; auto reset_firmware() -> int; + // Radio suspend/resume steps (ports of the set_wow_* wrappers). + auto set_wow_enable(bool enable) -> int; + auto set_wow_traffic(wow_traffic traffic) -> int; + + // Radio init steps (port of the xone_mt76_init_radio sub-functions). + auto init_registers() -> void; + auto calibrate_crystal() -> int; + auto init_address() -> int; + auto set_idle_time() -> int; + auto calibrate_radio() -> int; + auto get_channel_power(channel *chan) -> int; + auto switch_channel(channel const *chan) -> int; + auto evaluate_channels() -> int; + auto init_channels() -> int; + auto write_beacon(bool pair) -> int; + auto set_channel_candidates() -> int; + usb::transport &transport_; char build_time_[17] = {}; + std::array channels_{}; + channel current_channel_{}; }; } // namespace xone::mt76 diff --git a/include/mt76/mt76_defs.hpp b/include/mt76/mt76_defs.hpp index 944619d..36dbbfe 100644 --- a/include/mt76/mt76_defs.hpp +++ b/include/mt76/mt76_defs.hpp @@ -3,8 +3,8 @@ // ============================================================================== // MT76 chip register definitions (port of transport/mt76_defs.h) // ============================================================================== -// Only the registers needed so far are ported; the rest land with the radio -// init and firmware load increments. +// Only the registers needed so far are ported; the rest land with the +// pairing and data path increments. // ============================================================================== #include @@ -113,4 +113,273 @@ struct fw_header { char build_time[16]; } XONE_PACKED; +// MCU command types (port of enum mt76_mcu_cmd). +enum mcu_cmd : std::uint32_t { + cmd_fun_set_op = 1, + cmd_load_cr = 2, + cmd_init_gain_op = 3, + cmd_dync_vga_op = 6, + cmd_tdls_ch_sw = 7, + cmd_burst_write = 8, + cmd_read_modify_write = 9, + cmd_random_read = 10, + cmd_burst_read = 11, + cmd_random_write = 12, + cmd_led_mode_op = 16, + cmd_power_saving_op = 20, + cmd_wow_config = 21, + cmd_wow_query = 22, + cmd_wow_feature = 24, + cmd_carrier_detect_op = 28, + cmd_rador_detect_op = 29, + cmd_switch_channel_op = 30, + cmd_calibration_op = 31, + cmd_beacon_op = 32, + cmd_antenna_op = 33, +}; + +// MCU function select (port of enum mt76_mcu_function). +enum mcu_function : std::uint32_t { + fun_q_select = 1, + fun_bw_setting = 2, + fun_usb2_sw_disconnect = 2, // duplicate value upstream + fun_usb3_sw_disconnect = 3, + fun_log_fw_debug_msg = 4, + fun_get_fw_version = 5, +}; + +// CR load modes (port of enum mt76_mcu_cr_mode). +enum cr_mode : std::uint32_t { + rf_cr = 0, + bbp_cr, + rf_bbp_cr, + hl_temp_cr_update, +}; + +// Radio power modes (port of enum mt76_mcu_power_mode). +enum power_mode : std::uint32_t { + radio_off = 0x30, + radio_on = 0x31, + radio_off_auto_wakeup = 0x32, + radio_off_advance = 0x33, + radio_on_advance = 0x34, +}; + +// Calibration types (port of enum mt76_mcu_calibration). +enum calibration : std::uint32_t { + cal_r = 1, + cal_temp_sensor, + cal_rxdcoc, + cal_rc, + cal_sx_logen, + cal_lc, + cal_tx_loft, + cal_txiq, + cal_tssi, + cal_tssi_comp, + cal_dpd, + cal_rxiqc_fi, +}; + +// Commands to Microsoft's proprietary firmware (port of enum xone_mt76_ms_command). +enum ms_command : std::uint32_t { + ms_set_mac_address = 0x00, + ms_add_client = 0x01, + ms_remove_client = 0x02, + ms_set_idle_time = 0x05, + ms_set_chan_candidates = 0x07, +}; + +// LED modes (port of enum xone_mt76_led_mode). +enum led_mode : std::uint32_t { + led_blink = 0x00, + led_on = 0x01, + led_off = 0x02, +}; + +// Wake-on-wireless features (port of enum xone_mt76_wow_feature). +enum wow_feature : std::uint32_t { + wow_enable = 0x01, + wow_traffic_op = 0x03, +}; + +// WOW traffic direction (port of enum xone_mt76_wow_traffic). +enum wow_traffic : std::uint32_t { + wow_to_firmware = 0x00, + wow_to_host = 0x01, +}; + +// PHY types (port of enum mt76_phy_type, used in MT_RXWI_RATE_PHY). +enum phy_type : std::uint32_t { + phy_cck = 0, + phy_ofdm, + phy_ht, + phy_ht_gf, + phy_vht, + phy_he_su = 8, +}; + +// PHY bandwidths (port of enum mt76_phy_bandwidth). +enum phy_bandwidth : std::uint32_t { + bw_20 = 0, + bw_40, + bw_80, +}; + +// Calibration channel groups (port of enum mt76_cal_channel_group). +enum cal_channel_group : std::uint32_t { + ch_5g_japan = 0, + ch_5g_unii_1, + ch_5g_unii_2, + ch_5g_unii_2e_1, + ch_5g_unii_2e_2, + ch_5g_unii_3, +}; + +// Channel bands (port of XONE_MT_CH_*). +constexpr std::uint8_t ch_2g_low = 0x01; +constexpr std::uint8_t ch_2g_mid = 0x02; +constexpr std::uint8_t ch_2g_high = 0x03; +constexpr std::uint8_t ch_5g_low = 0x01; +constexpr std::uint8_t ch_5g_high = 0x02; + +// Channel table (port of xone_mt76_channels). +struct channel { + std::uint8_t index; + std::uint8_t band; // ch_* value + std::uint8_t bandwidth; // phy_bandwidth + std::uint8_t group; // cal_channel_group (5G only) + bool scan; + std::uint8_t power; +}; +constexpr std::size_t num_channels = 12; +inline constexpr channel channels[num_channels] = { + { 0x01, ch_2g_low, bw_20, 0, true, 0 }, + { 0x06, ch_2g_mid, bw_20, 0, true, 0 }, + { 0x0b, ch_2g_high, bw_20, 0, true, 0 }, + { 0x24, ch_5g_low, bw_40, ch_5g_unii_1, true, 0 }, + { 0x28, ch_5g_low, bw_40, ch_5g_unii_1, false, 0 }, + { 0x2c, ch_5g_high, bw_40, ch_5g_unii_1, true, 0 }, + { 0x30, ch_5g_high, bw_40, ch_5g_unii_1, false, 0 }, + { 0x95, ch_5g_low, bw_80, ch_5g_unii_3, true, 0 }, + { 0x99, ch_5g_low, bw_80, ch_5g_unii_3, false, 0 }, + { 0x9d, ch_5g_high, bw_80, ch_5g_unii_3, true, 0 }, + { 0xa1, ch_5g_high, bw_80, ch_5g_unii_3, false, 0 }, + { 0xa5, ch_5g_high, bw_80, ch_5g_unii_3, false, 0 }, +}; + +// EFUSE TX power tables and crystal trim. +constexpr std::uint16_t mt_ee_xtal_trim_1 = 0x003a; +constexpr std::uint16_t mt_ee_xtal_trim_2 = 0x009e; +constexpr std::uint16_t mt_ee_tx_power_0_start_2g = 0x0056; +constexpr std::size_t tx_power_group_size_5g = 5; +constexpr std::uint16_t mt_ee_tx_power_0_start_5g = 0x0062; + +// Crystal oscillator control registers. +constexpr std::uint32_t mt_cmb_ctrl = 0x0020; +constexpr std::uint32_t mt_xo_ctrl5 = 0x0114; +constexpr std::uint32_t mt_xo_ctrl5_c2_val = genmask(14, 8); +constexpr std::uint32_t mt_xo_ctrl6 = 0x0118; +constexpr std::uint32_t mt_xo_ctrl6_c2_ctrl = genmask(14, 8); + +// MAC address registers and MCU memory map (port of MT_MCU_MEMMAP_WLAN). +constexpr std::uint32_t mt_mcu_memmap_wlan = 0x410000; +constexpr std::uint32_t mt_mac_addr_dw0 = 0x1008; +constexpr std::uint32_t mt_mac_bssid_dw0 = 0x1010; + +// MAC system control register. +constexpr std::uint32_t mt_mac_sys_ctrl = 0x1004; +constexpr std::uint32_t mt_mac_sys_ctrl_reset_csr = bit(0); +constexpr std::uint32_t mt_mac_sys_ctrl_reset_bbp = bit(1); +constexpr std::uint32_t mt_mac_sys_ctrl_enable_tx = bit(2); +constexpr std::uint32_t mt_mac_sys_ctrl_enable_rx = bit(3); + +// Radio init registers (port of the xone_mt76_init_registers writes). +constexpr std::uint32_t mt_usb_dma_cfg = 0x0238; +constexpr std::uint32_t mt_pwr_pin_cfg = 0x1204; +constexpr std::uint32_t mt_ldo_ctrl_1 = 0x0070; +constexpr std::uint32_t mt_wpdma_glo_cfg = 0x0208; +constexpr std::uint32_t mt_wmm_aifsn = 0x0214; +constexpr std::uint32_t mt_wmm_cwmin = 0x0218; +constexpr std::uint32_t mt_wmm_cwmax = 0x021c; +constexpr std::uint32_t mt_tso_ctrl = 0x0250; +constexpr std::uint32_t mt_pbf_sys_ctrl = 0x0400; +constexpr std::uint32_t mt_pbf_tx_max_pcnt = 0x0408; +constexpr std::uint32_t mt_auto_rsp_cfg = 0x1404; +constexpr std::uint32_t mt_max_len_cfg = 0x1018; +constexpr std::uint32_t mt_ampdu_max_len_20m1s = 0x1030; +constexpr std::uint32_t mt_ampdu_max_len_20m2s = 0x1034; +constexpr std::uint32_t mt_bkoff_slot_cfg = 0x1104; +constexpr std::uint32_t mt_edca_cfg_base = 0x1300; +inline constexpr auto mt_edca_cfg_ac(std::size_t n) -> std::uint32_t +{ + return mt_edca_cfg_base + (n << 2); +} +constexpr std::uint32_t mt_tx_pin_cfg = 0x1328; +constexpr std::uint32_t mt_tx_sw_cfg0 = 0x1330; +constexpr std::uint32_t mt_tx_sw_cfg1 = 0x1334; +constexpr std::uint32_t mt_txop_ctrl_cfg = 0x1340; +constexpr std::uint32_t mt_tx_rts_cfg = 0x1344; +constexpr std::uint32_t mt_tx_timeout_cfg = 0x1348; +constexpr std::uint32_t mt_tx_retry_cfg = 0x134c; +constexpr std::uint32_t mt_cck_prot_cfg = 0x1364; +constexpr std::uint32_t mt_ofdm_prot_cfg = 0x1368; +constexpr std::uint32_t mt_mm20_prot_cfg = 0x136c; +constexpr std::uint32_t mt_gf20_prot_cfg = 0x1374; +constexpr std::uint32_t mt_gf40_prot_cfg = 0x1378; +constexpr std::uint32_t mt_exp_ack_time = 0x1380; +constexpr std::uint32_t mt_tx_alc_cfg_0 = 0x13b0; +constexpr std::uint32_t mt_tx_alc_cfg_2 = 0x13a8; +constexpr std::uint32_t mt_tx_alc_cfg_3 = 0x13ac; +constexpr std::uint32_t mt_tx_alc_cfg_4 = 0x13c0; +constexpr std::uint32_t mt_pifs_tx_cfg = 0x13ec; +constexpr std::uint32_t mt_rx_filtr_cfg = 0x1400; +constexpr std::uint32_t mt_legacy_basic_rate = 0x1408; +constexpr std::uint32_t mt_ht_basic_rate = 0x140c; +constexpr std::uint32_t mt_pn_pad_mode = 0x150c; +constexpr std::uint32_t mt_txop_hldr_et = 0x1608; +constexpr std::uint32_t mt_tx_prot_cfg6 = 0x13e0; +constexpr std::uint32_t mt_tx_prot_cfg7 = 0x13e4; +constexpr std::uint32_t mt_tx_prot_cfg8 = 0x13e8; +constexpr std::uint32_t mt_dacclk_en_dly_cfg = 0x1264; +constexpr std::uint32_t mt_rf_pa_mode_adj0 = 0x1228; +constexpr std::uint32_t mt_rf_pa_mode_adj1 = 0x122c; +constexpr std::uint32_t mt_tx0_rf_gain_corr = 0x13a0; +constexpr std::uint32_t mt_tx1_rf_gain_corr = 0x13a4; +constexpr std::uint32_t mt_pbf_cfg = 0x0404; +constexpr std::uint32_t mt_pause_enable_control1 = 0x0a38; +constexpr std::uint32_t mt_rf_bypass_0 = 0x0504; +constexpr std::uint32_t mt_rf_setting_0 = 0x050c; +constexpr std::uint32_t mt_xifs_time_cfg = 0x1100; +constexpr std::uint32_t mt_fce_l2_stuff = 0x080c; +constexpr std::uint32_t mt_ext_cca_cfg = 0x141c; +constexpr std::uint32_t mt_ch_time_cfg = 0x110c; + +// BBP registers (port of MT_BBP(type, n)). +constexpr std::uint32_t mt_bbp_agc_base = 0x2300; +inline constexpr auto mt_bbp_agc(std::size_t n) -> std::uint32_t +{ + return mt_bbp_agc_base + (n << 2); +} + +// Beacon timer register. +constexpr std::uint32_t mt_beacon_time_cfg = 0x1114; +constexpr std::uint32_t mt_beacon_time_cfg_intval = genmask(15, 0); +constexpr std::uint32_t mt_beacon_time_cfg_timer_en = bit(16); +constexpr std::uint32_t mt_beacon_time_cfg_sync_mode = genmask(18, 17); +constexpr std::uint32_t mt_beacon_time_cfg_tbtt_en = bit(19); +constexpr std::uint32_t mt_beacon_time_cfg_beacon_tx = bit(20); + +// Broadcast MAC address (port of eth_broadcast_addr). +inline constexpr std::uint8_t broadcast_address[6] = { + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, +}; + +// Beacon frame storage and TXWI fields. +constexpr std::uint32_t mt_beacon_base = 0xc000; +constexpr std::uint32_t mt_rxwi_rate_phy = genmask(15, 13); +constexpr std::uint8_t mt_txwi_ack_ctl_nseq = bit(1); +// 802.11 frame control for a beacon (MGMT type, BEACON subtype). +constexpr std::uint16_t ieee80211_fc_beacon = 0x0080; + } // namespace xone::mt76 diff --git a/include/usb/usb_transport.hpp b/include/usb/usb_transport.hpp index a5c9fce..73e6c00 100644 --- a/include/usb/usb_transport.hpp +++ b/include/usb/usb_transport.hpp @@ -74,6 +74,12 @@ public: // opening fails. static auto probe(frame_callback frames, disconnect_callback disconnected) -> std::unique_ptr; + // 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. + auto re_enumerate() -> int; + ~transport(); transport(transport const&) = delete; @@ -102,6 +108,10 @@ private: 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); diff --git a/scripts/download-firmware.sh b/scripts/download-firmware.sh index 6f40e4a..030b8f6 100755 --- a/scripts/download-firmware.sh +++ b/scripts/download-firmware.sh @@ -1,6 +1,7 @@ #!/bin/sh # Download and extract the wireless dongle firmware from Windows Update. -# Port of install/firmware.sh from medusalix/xone. +# Port of install/firmware.sh from medusalix/xone, extended with the +# per-product split from dlundqvist/xone. # # The firmware is subject to Microsoft's Terms of Use: # https://www.microsoft.com/en-us/legal/terms-of-use @@ -9,8 +10,10 @@ set -eu cd "$(dirname "$0")/.." -firmware_url='http://download.windowsupdate.com/c/msdownload/update/driver/drvs/2017/07/1cd6a87c-623f-4407-a52d-c31be49e925c_e19f60808bdcbfbd3c3df6be3e71ffc52e43261e.cab' -firmware_hash='48084d9fa53b9bb04358f3bb127b7495dc8f7bb0b3ca1437bd24ef2b6eabdf66' +firmware_url_02e6='http://catalog.s.download.windowsupdate.com/d/msdownload/update/driver/drvs/2017/03/2ea9591b-f751-442c-80ce-8f4692cdc67b_6b555a3a288153cf04aec6e03cba360afe2fce34.cab' +firmware_hash_02e6='080ce4091e53a4ef3e5fe29939f51fd91f46d6a88be6d67eb6e99a5723b3a223' +firmware_url_02fe='http://download.windowsupdate.com/c/msdownload/update/driver/drvs/2017/07/1cd6a87c-623f-4407-a52d-c31be49e925c_e19f60808bdcbfbd3c3df6be3e71ffc52e43261e.cab' +firmware_hash_02fe='48084d9fa53b9bb04358f3bb127b7495dc8f7bb0b3ca1437bd24ef2b6eabdf66' echo "The firmware for the wireless dongle is subject to Microsoft's Terms of Use:" echo 'https://www.microsoft.com/en-us/legal/terms-of-use' @@ -31,18 +34,28 @@ sha256() { tmpdir=$(mktemp -d) trap 'rm -rf "$tmpdir"' EXIT INT HUP TERM -echo "Downloading driver package..." -curl -L --fail -o "$tmpdir/driver.cab" "$firmware_url" +fetch_firmware() { + # fetch_firmware + url=$1 + hash=$2 + outname=$3 -# Extract the firmware image (requires a libarchive-based tar, e.g. macOS). -tar -xf "$tmpdir/driver.cab" -C "$tmpdir" + echo "Downloading $outname..." + curl -L --fail -o "$tmpdir/driver.cab" "$url" -actual=$(sha256 "$tmpdir/FW_ACC_00U.bin") -if [ "$actual" != "$firmware_hash" ]; then - echo "Firmware hash mismatch: $actual" >&2 - exit 1 -fi + # Extract the firmware image (requires a libarchive-based tar, e.g. macOS). + tar -xf "$tmpdir/driver.cab" -C "$tmpdir" -mkdir -p firmware -mv "$tmpdir/FW_ACC_00U.bin" firmware/xow_dongle.bin -echo "Firmware written to firmware/xow_dongle.bin" + actual=$(sha256 "$tmpdir/FW_ACC_00U.bin") + if [ "$actual" != "$hash" ]; then + echo "Firmware hash mismatch: $actual" >&2 + exit 1 + fi + + mkdir -p firmware + mv "$tmpdir/FW_ACC_00U.bin" "firmware/$outname" + echo "Firmware written to firmware/$outname" +} + +fetch_firmware "$firmware_url_02e6" "$firmware_hash_02e6" xone_dongle_02e6.bin +fetch_firmware "$firmware_url_02fe" "$firmware_hash_02fe" xone_dongle_02fe.bin diff --git a/src/app/api.cpp b/src/app/api.cpp index ac0ba94..50954ee 100644 --- a/src/app/api.cpp +++ b/src/app/api.cpp @@ -60,6 +60,9 @@ extern "C" xone_dongle *xone_open(const char *firmware_path) int ret = s->chip->load_firmware(firmware_path); if (ret != 0) xone::log_msg(xone::log_level::warn, "api: firmware load failed (%d)", ret); + else if (auto err = s->chip->init_radio(); err != 0) + xone::log_msg(xone::log_level::warn, + "api: radio init failed (%d)", err); } current_session = std::move(s); diff --git a/src/cli/main.cpp b/src/cli/main.cpp new file mode 100644 index 0000000..0256844 --- /dev/null +++ b/src/cli/main.cpp @@ -0,0 +1,178 @@ +// Command line tool for exercising the protocol stack against hardware. +// All commands run in a single dongle session (probe ... close). + +#include +#include +#include +#include +#include +#include +#include + +#include "common/log.hpp" +#include "mt76/mt76.hpp" +#include "usb/usb_transport.hpp" + +namespace { + +auto usage() -> int +{ + std::fprintf(stderr, + "Usage: xone_cli ...\n" + "\n" + "Commands (run in a single dongle session):\n" + " recover Port reset + re-enumerate (first command)\n" + " info Show dongle and chip state\n" + " firmware Load the firmware image\n" + " radio-init Initialize the radio\n" + " led Set LED mode (0 blink, 1 on, 2 off)\n" + " func MCU function select (debug)\n" + " radio-deinit Suspend the radio\n" + " burst MCU burst write to idx + memmap_wlan\n" + " reg-read Read a 32-bit register\n" + " reg-write Write a 32-bit register\n" + " sleep Wait (debug)\n"); + return 1; +} + +auto parse_hex(char const *text, std::vector &out) -> bool +{ + if (std::strlen(text) % 2 != 0) + return false; + for (std::size_t i = 0; text[i]; i += 2) { + auto byte = static_cast(std::strtoul(&text[i], nullptr, 16)); + out.push_back(byte); + } + return true; +} + +auto run(int argc, char **argv, xone::usb::transport &transport, + xone::mt76::chip &chip) -> int +{ + for (int i = 0; i < argc; ) { + auto const *cmd = argv[i++]; + + if (!std::strcmp(cmd, "info")) { + auto mac = chip.mac_address(); + std::printf("pid=0x%04x chip_id=0x%04x mac=%02x:%02x:%02x:%02x:%02x:%02x\n", + transport.pid(), chip.chip_id(), + mac[0], mac[1], mac[2], mac[3], mac[4], mac[5]); + std::printf("firmware_build=%s\n", chip.firmware_build()); + } else if (!std::strcmp(cmd, "firmware")) { + if (i >= argc) + return usage(); + int ret = chip.load_firmware(argv[i++]); + if (ret != 0) + std::fprintf(stderr, "xone_cli: firmware load failed (%d)\n", ret); + } else if (!std::strcmp(cmd, "radio-init")) { + int ret = chip.init_radio(); + if (ret != 0) + std::fprintf(stderr, "xone_cli: radio init failed (%d)\n", ret); + } else if (!std::strcmp(cmd, "func")) { + if (i + 2 > argc) + return usage(); + auto func = static_cast(std::strtoul(argv[i++], nullptr, 16)); + auto val = static_cast(std::strtoul(argv[i++], nullptr, 16)); + int ret = chip.select_function(func, val); + if (ret != 0) + std::fprintf(stderr, "xone_cli: func failed (%d)\n", ret); + } else if (!std::strcmp(cmd, "led")) { + if (i >= argc) + return usage(); + auto mode = static_cast(std::strtoul(argv[i++], nullptr, 16)); + int ret = chip.set_led_mode(mode); + if (ret != 0) + std::fprintf(stderr, "xone_cli: led failed (%d)\n", ret); + } else if (!std::strcmp(cmd, "radio-deinit")) { + int ret = chip.suspend_radio(); + if (ret != 0) + std::fprintf(stderr, "xone_cli: radio deinit failed (%d)\n", ret); + } else if (!std::strcmp(cmd, "burst")) { + if (i + 2 > argc) + return usage(); + auto idx = static_cast(std::strtoul(argv[i++], nullptr, 16)); + std::vector data; + if (!parse_hex(argv[i++], data)) + return usage(); + int ret = chip.write_burst(idx, data.data(), data.size()); + if (ret != 0) + std::fprintf(stderr, "xone_cli: burst failed (%d)\n", ret); + } else if (!std::strcmp(cmd, "reg-read")) { + if (i >= argc) + return usage(); + auto addr = static_cast(std::strtoul(argv[i++], nullptr, 16)); + std::printf("0x%04x = 0x%08x\n", addr, chip.read_register(addr)); + } else if (!std::strcmp(cmd, "sleep")) { + if (i >= argc) + return usage(); + auto secs = std::chrono::seconds(static_cast(std::strtoul(argv[i++], nullptr, 10))); + std::this_thread::sleep_for(secs); + } else if (!std::strcmp(cmd, "reg-write")) { + if (i + 2 > argc) + return usage(); + auto addr = static_cast(std::strtoul(argv[i++], nullptr, 16)); + auto val = static_cast(std::strtoul(argv[i++], nullptr, 16)); + chip.write_register(addr, val); + } else { + return usage(); + } + } + + return 0; +} + +} // namespace + +auto main(int argc, char **argv) -> int +{ + if (argc < 2) + return usage(); + + // Log all IN endpoint traffic (the Linux driver sees this via its read URBs). + auto on_frame = [](std::uint8_t ep, void const *data, std::size_t len) { + auto const *p = static_cast(data); + std::fprintf(stderr, "[in] ep=0x%02x len=%zu: ", ep, len); + for (std::size_t i = 0; i < len && i < 32; i++) + std::fprintf(stderr, "%02x", p[i]); + if (len > 32) + std::fprintf(stderr, "..."); + std::fprintf(stderr, "\n"); + }; + + // `recover` forces a hub port reset and re-enumeration. The transport is + // dead afterwards, so probe again for the fresh device. It must be the + // first command. + int arg = 1; + if (!std::strcmp(argv[1], "recover")) { + auto current = xone::usb::transport::probe(on_frame, nullptr); + if (!current) { + std::fprintf(stderr, "xone_cli: no dongle found\n"); + return 1; + } + if (current->re_enumerate() != 0) { + std::fprintf(stderr, "xone_cli: recover failed\n"); + return 1; + } + arg = 2; + } + + // After a re-enumeration the chip may take a while to come back (its + // watchdog only fires on a quiet bus). Poll the registry only: probing + // would generate USB traffic and delay the recovery. + for (int attempt = 0; arg > 1 && !xone::usb::dongle_present(); attempt++) { + if (attempt >= 180) { + std::fprintf(stderr, "xone_cli: dongle did not re-enumerate\n"); + return 1; + } + std::this_thread::sleep_for(std::chrono::milliseconds(500)); + } + + auto transport = xone::usb::transport::probe(on_frame, nullptr); + if (!transport) { + std::fprintf(stderr, "xone_cli: no dongle found\n"); + return 1; + } + + xone::mt76::chip chip(*transport); + return run(argc - arg, argv + arg, *transport, chip); +} diff --git a/src/mt76/mt76.cpp b/src/mt76/mt76.cpp index 6ccee37..b2357e1 100644 --- a/src/mt76/mt76.cpp +++ b/src/mt76/mt76.cpp @@ -166,7 +166,8 @@ auto chip::send_command(std::uint32_t cmd, void const *payload, | field_prep(mt_mcu_msg_cmd_type, cmd); auto buf = build_message(info, payload, payload_len); - return transport_.bulk_write(buf.data(), buf.size()); + auto ret = transport_.bulk_write(buf.data(), buf.size()); + return ret < 0 ? ret : 0; } auto chip::load_ivb() -> int @@ -286,6 +287,11 @@ auto chip::load_firmware(char const *path) -> int return ret; write_register(mt_fce_dma_addr | mt_vend_type_cfg, 0); + + // Apply power-on RF patch. + auto val = read_register(xone_mt_rf_patch | mt_vend_type_cfg); + write_register(xone_mt_rf_patch | mt_vend_type_cfg, val & ~bit(19)); + if (auto err = load_ivb(); err != 0) return err; @@ -296,4 +302,463 @@ auto chip::load_firmware(char const *path) -> int return 0; } +auto chip::set_led_mode(std::uint32_t mode) -> int +{ + std::uint8_t payload[4]; + xone::store_le32(payload, mode); + + return send_command(mcu_cmd::cmd_led_mode_op, payload, sizeof(payload)); +} + +auto chip::select_function(std::uint32_t func, std::uint32_t val) -> int +{ + std::uint8_t payload[8]; + xone::store_le32(payload + 0, func); + xone::store_le32(payload + 4, val); + + return send_command(mcu_cmd::cmd_fun_set_op, payload, sizeof(payload)); +} + +auto chip::set_power_mode(power_mode mode) -> int +{ + std::uint8_t payload[4]; + xone::store_le32(payload, static_cast(mode)); + + return send_command(mcu_cmd::cmd_power_saving_op, payload, + sizeof(payload)); +} + +auto chip::load_cr(cr_mode mode) -> int +{ + // struct xone_mt76_msg_load_cr {mode, temperature, channel, padding}. + std::uint8_t payload[4] = {}; + payload[0] = static_cast(mode); + + return send_command(mcu_cmd::cmd_load_cr, payload, sizeof(payload)); +} + +auto chip::write_burst(std::uint32_t idx, void *data, std::size_t len) -> int +{ + std::vector buf(4 + len); + xone::store_le32(buf.data(), idx + mt_mcu_memmap_wlan); // Register offset in memory. + std::memcpy(buf.data() + 4, data, len); + + return send_command(mcu_cmd::cmd_burst_write, buf.data(), buf.size()); +} + +auto chip::send_ms_command(ms_command cmd, void *data, std::size_t len) -> int +{ + std::vector buf(4 + len); + xone::store_le32(buf.data(), static_cast(cmd)); + std::memcpy(buf.data() + 4, data, len); + + return send_command(mcu_cmd::cmd_init_gain_op, buf.data(), buf.size()); +} + +auto chip::calibrate(calibration calib, std::uint32_t val) -> int +{ + std::uint8_t payload[8]; + xone::store_le32(payload + 0, static_cast(calib)); + xone::store_le32(payload + 4, val); + + return send_command(mcu_cmd::cmd_calibration_op, payload, sizeof(payload)); +} + +auto chip::init_registers() -> void +{ + // Port of xone_mt76_init_registers (transport/mt76.c). + write_register(mt_mac_sys_ctrl, + mt_mac_sys_ctrl_reset_bbp | mt_mac_sys_ctrl_reset_csr); + write_register(mt_usb_dma_cfg, 0); + write_register(mt_mac_sys_ctrl, 0); + write_register(mt_pwr_pin_cfg, 0); + write_register(mt_ldo_ctrl_1, 0x6b006464); + write_register(mt_wpdma_glo_cfg, 0x70); + write_register(mt_wmm_aifsn, 0x2273); + write_register(mt_wmm_cwmin, 0x2344); + write_register(mt_wmm_cwmax, 0x34aa); + write_register(mt_fce_dma_addr, 0x041200); + write_register(mt_tso_ctrl, 0); + write_register(mt_pbf_sys_ctrl, 0x080c00); + write_register(mt_pbf_tx_max_pcnt, 0x1fbf1f1f); + write_register(mt_fce_pse_ctrl, 0x01); + write_register(mt_mac_sys_ctrl, + mt_mac_sys_ctrl_enable_rx | mt_mac_sys_ctrl_enable_tx); + write_register(mt_auto_rsp_cfg, 0x13); + write_register(mt_max_len_cfg, 0x3e3fff); + write_register(mt_ampdu_max_len_20m1s, 0xfffc9855); + write_register(mt_ampdu_max_len_20m2s, 0xff); + write_register(mt_bkoff_slot_cfg, 0x0109); + write_register(mt_pwr_pin_cfg, 0); + write_register(mt_edca_cfg_ac(0), 0x064320); + write_register(mt_edca_cfg_ac(1), 0x0a4700); + write_register(mt_edca_cfg_ac(2), 0x043238); + write_register(mt_edca_cfg_ac(3), 0x03212f); + write_register(mt_tx_pin_cfg, 0x150f0f); + write_register(mt_tx_sw_cfg0, 0x101001); + write_register(mt_tx_sw_cfg1, 0x010000); + write_register(mt_txop_ctrl_cfg, 0x10583f); + write_register(mt_tx_timeout_cfg, 0x0a0f90); + write_register(mt_tx_retry_cfg, 0x47d01f0f); + write_register(mt_cck_prot_cfg, 0x03f40003); + write_register(mt_ofdm_prot_cfg, 0x03f40003); + write_register(mt_mm20_prot_cfg, 0x01742004); + write_register(mt_gf20_prot_cfg, 0x01742004); + write_register(mt_gf40_prot_cfg, 0x03f42084); + write_register(mt_exp_ack_time, 0x2c00dc); + write_register(mt_tx_alc_cfg_2, 0x22160a00); + write_register(mt_tx_alc_cfg_3, 0x22160a76); + write_register(mt_tx_alc_cfg_0, 0x3f3f1818); + write_register(mt_tx_alc_cfg_4, 0x0606); + write_register(mt_pifs_tx_cfg, 0x060fff); + write_register(mt_rx_filtr_cfg, 0x017f17); + write_register(mt_legacy_basic_rate, 0x017f); + write_register(mt_ht_basic_rate, 0x8003); + write_register(mt_pn_pad_mode, 0x02); + write_register(mt_txop_hldr_et, 0x02); + write_register(mt_tx_prot_cfg6, 0xe3f42004); + write_register(mt_tx_prot_cfg7, 0xe3f42084); + write_register(mt_tx_prot_cfg8, 0xe3f42104); + write_register(mt_dacclk_en_dly_cfg, 0); + write_register(mt_rf_pa_mode_adj0, 0xee000000); + write_register(mt_rf_pa_mode_adj1, 0xee000000); + write_register(mt_tx0_rf_gain_corr, 0x0f3c3c3c); + write_register(mt_tx1_rf_gain_corr, 0x0f3c3c3c); + write_register(mt_pbf_cfg, 0x1efebcf5); + write_register(mt_pause_enable_control1, 0x0a); + write_register(mt_rf_bypass_0, 0x7f000000); + write_register(mt_rf_setting_0, 0x1a800000); + write_register(mt_xifs_time_cfg, 0x33a40e0a); + write_register(mt_fce_l2_stuff, 0x03ff0223); + write_register(mt_tx_rts_cfg, 0); + write_register(mt_beacon_time_cfg, 0x0640); + write_register(mt_ext_cca_cfg, 0xf0e4); + write_register(mt_ch_time_cfg, 0x015f); +} + +auto chip::calibrate_crystal() -> int +{ + std::uint8_t trim[4] = {}; + if (read_efuse(mt_ee_xtal_trim_2, trim, sizeof(trim)) != 0) + return -EIO; + + auto val = static_cast((trim[3] << 8) | trim[2]); + auto offset = static_cast(val & genmask(6, 0)); + if ((val & 0xFF) == 0xFF) + offset = 0; + else if (val & bit(7)) + offset = -offset; + + val >>= 8; + if (!val || val == 0xFF) { + if (read_efuse(mt_ee_xtal_trim_1, trim, sizeof(trim)) != 0) + return -EIO; + val = static_cast((trim[3] << 8) | trim[2]); + val &= 0xFF; + if (!val || val == 0xFF) + val = 0x14; // Default value + } + + val = static_cast(static_cast(val & genmask(6, 0)) + offset); + auto ctrl = read_register(mt_xo_ctrl5 | mt_vend_type_cfg); + write_register(mt_xo_ctrl5 | mt_vend_type_cfg, + (ctrl & ~mt_xo_ctrl5_c2_val) | (static_cast(val) << 8)); + write_register(mt_xo_ctrl6 | mt_vend_type_cfg, mt_xo_ctrl6_c2_ctrl); + write_register(mt_cmb_ctrl, 0x0091a7ff); + + return 0; +} + +auto chip::init_address() -> int +{ + auto address = mac_address(); + + if (auto err = write_burst(mt_mac_addr_dw0, address.data(), address.size()); + err != 0) + return err; + if (auto err = write_burst(mt_mac_bssid_dw0, address.data(), address.size()); + err != 0) + return err; + + return send_ms_command(ms_command::ms_set_mac_address, address.data(), + address.size()); +} + +auto chip::set_idle_time() -> int +{ + // Prevent wireless clients from disconnecting when idle. + std::uint8_t payload[4]; + xone::store_le32(payload, 64); + + return send_ms_command(ms_command::ms_set_idle_time, payload, + sizeof(payload)); +} + +auto chip::calibrate_radio() -> int +{ + // Enable AGC for all antennas. + write_register(mt_bbp_agc(0), 0x0000001f); + write_register(mt_bbp_agc(1), 0x0000001f); + write_register(mt_bbp_agc(2), 0x0000001f); + + return calibrate(calibration::cal_rc, 0); +} + +auto chip::get_channel_power(channel *chan) -> int +{ + std::uint32_t addr; + std::size_t idx; + if (chan->bandwidth == phy_bandwidth::bw_20) { + addr = mt_ee_tx_power_0_start_2g; + idx = 4; + } else { + // Each group has its own power table. + addr = mt_ee_tx_power_0_start_5g + + chan->group * tx_power_group_size_5g; + idx = 5; + } + + std::uint8_t entry[8] = {}; + if (read_efuse(static_cast(addr), entry, sizeof(entry)) != 0) + return -EIO; + + auto target = entry[idx]; + auto offset = entry[idx + chan->band]; + + // Increase or decrease power by the offset (in 0.5 dB steps). + if (offset & bit(7)) + chan->power = (offset & bit(6)) ? + static_cast(target + (offset & genmask(5, 0))) : + static_cast(target - (offset & genmask(5, 0))); + else + chan->power = target; + + return 0; +} + +auto chip::switch_channel(channel const *chan) -> int +{ + // struct xone_mt76_msg_switch_channel. + std::uint8_t msg[20] = {}; + msg[0] = chan->index; + xone::store_le16(msg + 4, 0x0101); // Select TX and RX stream 1. + msg[16] = chan->bandwidth; + msg[17] = chan->power; + msg[18] = chan->scan ? 1 : 0; + + return send_command(mcu_cmd::cmd_switch_channel_op, msg, sizeof(msg)); +} + +auto chip::evaluate_channels() -> int +{ + for (std::size_t i = 0; i < num_channels; i++) + channels_[i] = channels[i]; + + for (std::size_t i = 0; i < num_channels; i++) { + if (auto err = get_channel_power(&channels_[i]); err != 0) + return err; + if (auto err = switch_channel(&channels_[i]); err != 0) + return err; + } + + // The last channel may not be the best one. + current_channel_ = channels_.back(); + + return 0; +} + +auto chip::init_channels() -> int +{ + // Enable promiscuous mode. + write_register(mt_rx_filtr_cfg, 0x014f13); + + if (auto err = evaluate_channels(); err != 0) + return err; + + // Disable promiscuous mode. + write_register(mt_rx_filtr_cfg, 0x017f17); + + current_channel_.scan = true; + if (auto err = switch_channel(¤t_channel_); err != 0) + return err; + + if (auto err = set_power_mode(power_mode::radio_off); err != 0) + return err; + + usleep(50 * 1000); + + if (auto err = set_power_mode(power_mode::radio_on); err != 0) + return err; + + current_channel_.scan = false; + if (auto err = switch_channel(¤t_channel_); err != 0) + return err; + + return set_channel_candidates(); +} + +auto chip::write_beacon(bool pair) -> int +{ + auto address = mac_address(); + + // Beacon management frame (port of struct ieee80211_mgmt, zero + // initialized). Layout: [frame_control][duration][da][sa][bssid] + // [seq_ctrl][timestamp][beacon_int][capab_info]. + std::uint8_t mgmt[36] = {}; + xone::store_le16(mgmt + 0, ieee80211_fc_beacon); // frame_control: MGMT | BEACON + std::memcpy(mgmt + 4, broadcast_address, + sizeof(broadcast_address)); // da + std::memcpy(mgmt + 10, address.data(), address.size()); // sa + std::memcpy(mgmt + 16, address.data(), address.size()); // bssid + // seq_ctrl and timestamp stay zero; the MAC splices in the TSF. + xone::store_le16(mgmt + 32, 100); // beacon_int: default (100 ms) + xone::store_le16(mgmt + 34, 0xc631); // capab_info: original + + // Information element with Microsoft's OUI (00:50:f2). + std::uint8_t data[20] = { + 0x00, 0x00, 0xdd, 0x10, 0x00, 0x50, 0xf2, 0x11, + 0x01, 0x10, 0, 0xa5, 0x30, 0x99, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, + }; + data[10] = pair ? 1 : 0; + + // struct mt76_txwi (20 bytes, packed). + std::uint8_t txwi[20] = {}; + xone::store_le16(txwi + 0, bit(3)); // flags: TS + xone::store_le16(txwi + 2, + field_prep(mt_rxwi_rate_phy, phy_type::phy_ofdm)); // rate + txwi[4] = mt_txwi_ack_ctl_nseq; // ack_ctl + // wcid, iv, eiv, aid, txstream, ctl2, pktid stay zero. + xone::store_le16(txwi + 6, + static_cast(sizeof(mgmt) + sizeof(data))); + + std::vector frame(sizeof(txwi) + sizeof(mgmt) + sizeof(data)); + std::memcpy(frame.data(), txwi, sizeof(txwi)); + std::memcpy(frame.data() + sizeof(txwi), mgmt, sizeof(mgmt)); + std::memcpy(frame.data() + sizeof(txwi) + sizeof(mgmt), data, + sizeof(data)); + + // Program the beacon buffer via an MCU burst write into PBF shared + // memory. The vendor register path does not map this region on this + // hardware. + return write_burst(mt_beacon_base, frame.data(), frame.size()); +} + +auto chip::set_channel_candidates() -> int +{ + // [le32 1][le32 best][le32 count-1][le32 chan]... (skip the best). + std::vector buf; + auto append = [&](std::uint32_t val) { + buf.resize(buf.size() + 4); + xone::store_le32(buf.data() + buf.size() - 4, val); + }; + + append(1); + append(current_channel_.index); + append(num_channels - 1); + for (auto const &c : channels_) + if (c.index != current_channel_.index) + append(c.index); + + return send_ms_command(ms_command::ms_set_chan_candidates, buf.data(), + buf.size()); +} + +auto chip::set_pairing(bool enable) -> int +{ + if (auto err = write_beacon(enable); err != 0) + return err; + + // Enable TSF/TBTT timers, AP mode and beacon transmission. + write_register(mt_beacon_time_cfg, + mt_beacon_time_cfg_beacon_tx | + mt_beacon_time_cfg_tbtt_en | + mt_beacon_time_cfg_sync_mode | + mt_beacon_time_cfg_timer_en | + field_prep(mt_beacon_time_cfg_intval, 0x0640)); + + return 0; +} + +auto chip::init_radio() -> int +{ + xone::log_msg(log_level::info, "mt76: init radio (id=0x%04x)", chip_id()); + + if (auto err = select_function(mcu_function::fun_q_select, 1); err != 0) + return err; + if (auto err = set_power_mode(power_mode::radio_on); err != 0) + return err; + if (auto err = load_cr(cr_mode::rf_bbp_cr); err != 0) + return err; + + init_registers(); + + if (auto err = calibrate_crystal(); err != 0) + return err; + if (auto err = init_address(); err != 0) + return err; + if (auto err = set_idle_time(); err != 0) + return err; + if (auto err = calibrate_radio(); err != 0) + return err; + if (auto err = init_channels(); err != 0) + return err; + + // Mandatory delay after channel change. + usleep(1000 * 1000); + + return set_pairing(false); +} + +auto chip::set_wow_enable(bool enable) -> int +{ + std::uint8_t payload[6]; + xone::store_le32(payload + 0, static_cast(wow_feature::wow_enable)); + payload[4] = enable ? 1 : 0; + payload[5] = current_channel_.index; + + return send_command(mcu_cmd::cmd_wow_feature, payload, sizeof(payload)); +} + +auto chip::set_wow_traffic(wow_traffic traffic) -> int +{ + std::uint8_t payload[5]; + xone::store_le32(payload + 0, static_cast(wow_feature::wow_traffic_op)); + payload[4] = static_cast(traffic); + + return send_command(mcu_cmd::cmd_wow_feature, payload, sizeof(payload)); +} + +auto chip::suspend_radio() -> int +{ + write_register(mt_mac_sys_ctrl, 0); + + // Enable wake-on-wireless. + if (auto err = set_wow_enable(true); err != 0) + return err; + + return set_wow_traffic(wow_traffic::wow_to_host); +} + +auto chip::resume_radio() -> int +{ + if (auto err = set_wow_traffic(wow_traffic::wow_to_firmware); err != 0) + return err; + + // Disable wake-on-wireless. + if (auto err = set_wow_enable(false); err != 0) + return err; + + if (auto err = switch_channel(¤t_channel_); err != 0) + return err; + + if (auto err = set_pairing(false); err != 0) + return err; + + write_register(mt_mac_sys_ctrl, + mt_mac_sys_ctrl_enable_rx | mt_mac_sys_ctrl_enable_tx); + + return 0; +} + } // namespace xone::mt76 diff --git a/src/usb/usb_transport.cpp b/src/usb/usb_transport.cpp index b7b77bb..d478b68 100644 --- a/src/usb/usb_transport.cpp +++ b/src/usb/usb_transport.cpp @@ -5,6 +5,7 @@ #include #include +#include #include #include #include @@ -15,6 +16,7 @@ #include #include #include +#include #include "common/log.hpp" @@ -96,6 +98,7 @@ struct transport::state { IOUSBInterfaceInterface190 **iface_ref = nullptr; }; std::vector ifaces; + bool re_enumerated = false; struct pipe_ref { IOUSBInterfaceInterface190 **iface_ref = nullptr; @@ -138,37 +141,70 @@ auto transport::probe(frame_callback frames, disconnect_callback disconnected) - return t; } +void transport::stop_pump() +{ + { + std::lock_guard lock(state_->lock); + if (state_->stopping) + return; + state_->stopping = true; + } + + for (auto &slot : state_->slots) + (*slot.iface_ref)->AbortPipe(slot.iface_ref, slot.pipe_ref); + + if (!state_->thread_started) + return; + + CFRunLoopRef runloop = nullptr; + { + std::unique_lock lock(state_->lock); + state_->cv.wait(lock, [this] { return state_->loop_ready && state_->in_flight == 0; }); + runloop = state_->runloop; + } + + // The reader thread is joined below; no callback can run after this. + CFRunLoopStop(runloop); + state_->thread.join(); +} + +auto transport::re_enumerate() -> int +{ + // Stop the pump first so no completion callback touches the interface + // references while the kernel tears them down. + stop_pump(); + + auto kr = (*state_->dev_ref)->USBDeviceReEnumerate(state_->dev_ref, kUSBAddExtraResetTimeMask); + if (kr != kIOReturnSuccess) { + xone::log_msg(log_level::error, "usb: re-enumerate failed (%d)", kr); + return -EIO; + } + + // The kernel terminated all of our clients; the USB references are dead. + state_->re_enumerated = true; + xone::log_msg(log_level::info, "usb: re-enumerate ok"); + return 0; +} transport::~transport() { if (!state_) return; - // Stop generating completions, then wait for the in-flight ones to drain. - { - std::lock_guard lock(state_->lock); - state_->stopping = true; - } - for (auto &slot : state_->slots) - (*slot.iface_ref)->AbortPipe(slot.iface_ref, slot.pipe_ref); - - if (state_->thread_started) { - CFRunLoopRef runloop = nullptr; - { - std::unique_lock lock(state_->lock); - state_->cv.wait(lock, [this] { return state_->loop_ready && state_->in_flight == 0; }); - runloop = state_->runloop; - } - - // The reader thread is joined below; no callback can run after this. - CFRunLoopStop(runloop); - state_->thread.join(); - } + stop_pump(); // Release the termination watch and async event sources. if (state_->termination_iter) IOObjectRelease(state_->termination_iter); if (state_->notify_port) IONotificationPortDestroy(state_->notify_port); + + if (state_->re_enumerated) { + // The kernel already tore down the device and interfaces. + if (state_->service) + IOObjectRelease(state_->service); + return; + } + for (auto *source : state_->sources) CFRelease(source); diff --git a/tests/test_mt76.cpp b/tests/test_mt76.cpp index 9229719..34499d5 100644 --- a/tests/test_mt76.cpp +++ b/tests/test_mt76.cpp @@ -43,11 +43,89 @@ void test_firmware_layout(void) TEST_ASSERT_EQUAL_UINT32(xone::mt76::mt_fce_dma_len, 0x0234); } +// Pin the MCU command layer to transport/mt76_defs.h values. +void test_mcu_commands(void) +{ + TEST_ASSERT_EQUAL_UINT32( + static_cast(xone::mt76::mcu_cmd::cmd_fun_set_op), 1); + TEST_ASSERT_EQUAL_UINT32( + static_cast(xone::mt76::mcu_cmd::cmd_burst_write), 8); + TEST_ASSERT_EQUAL_UINT32( + static_cast(xone::mt76::mcu_cmd::cmd_power_saving_op), 20); + TEST_ASSERT_EQUAL_UINT32( + static_cast(xone::mt76::mcu_cmd::cmd_switch_channel_op), 30); + TEST_ASSERT_EQUAL_UINT32( + static_cast(xone::mt76::power_mode::radio_off), 0x30); + TEST_ASSERT_EQUAL_UINT32( + static_cast(xone::mt76::power_mode::radio_on), 0x31); + TEST_ASSERT_EQUAL_UINT32( + static_cast(xone::mt76::ms_command::ms_set_mac_address), + 0x00); + TEST_ASSERT_EQUAL_UINT32( + static_cast(xone::mt76::ms_command::ms_set_idle_time), 0x05); + TEST_ASSERT_EQUAL_UINT32( + static_cast( + xone::mt76::ms_command::ms_set_chan_candidates), 0x07); +} + +// Pin the radio init register addresses to transport/mt76_defs.h values. +void test_radio_registers(void) +{ + TEST_ASSERT_EQUAL_UINT32(xone::mt76::mt_mac_sys_ctrl, 0x1004); + TEST_ASSERT_EQUAL_UINT32(xone::mt76::mt_usb_dma_cfg, 0x0238); + TEST_ASSERT_EQUAL_UINT32(xone::mt76::mt_edca_cfg_ac(2), 0x1308); + TEST_ASSERT_EQUAL_UINT32(xone::mt76::mt_bbp_agc(1), 0x2304); + TEST_ASSERT_EQUAL_UINT32(xone::mt76::mt_beacon_time_cfg, 0x1114); + TEST_ASSERT_EQUAL_UINT32( + xone::mt76::mt_beacon_time_cfg_beacon_tx, + static_cast(xone::mt76::bit(20))); + TEST_ASSERT_EQUAL_UINT32(xone::mt76::mt_mcu_memmap_wlan, 0x410000); + TEST_ASSERT_EQUAL_UINT32(xone::mt76::mt_mac_addr_dw0, 0x1008); + TEST_ASSERT_EQUAL_UINT32(xone::mt76::mt_mac_bssid_dw0, 0x1010); +} + +// Pin the channel table to transport/mt76.c values. +void test_channel_table(void) +{ + using namespace xone::mt76; + TEST_ASSERT_EQUAL_UINT32(num_channels, 12); + TEST_ASSERT_EQUAL_UINT8(channels[0].index, 0x01); + TEST_ASSERT_EQUAL_UINT8(channels[0].bandwidth, + static_cast(phy_bandwidth::bw_20)); + TEST_ASSERT_TRUE(channels[0].scan); + TEST_ASSERT_EQUAL_UINT8(channels[3].index, 0x24); + TEST_ASSERT_EQUAL_UINT8(channels[3].group, + static_cast(cal_channel_group::ch_5g_unii_1)); + TEST_ASSERT_EQUAL_UINT8(channels[7].index, 0x95); + TEST_ASSERT_EQUAL_UINT8(channels[7].bandwidth, + static_cast(phy_bandwidth::bw_80)); + TEST_ASSERT_EQUAL_UINT8(channels[11].index, 0xa5); + TEST_ASSERT_FALSE(channels[11].scan); +} + +// Pin the beacon frame layout to transport/mt76.c values. +void test_beacon_layout(void) +{ + TEST_ASSERT_EQUAL_UINT32(xone::mt76::mt_beacon_base, 0xc000); + TEST_ASSERT_EQUAL_UINT16(xone::mt76::ieee80211_fc_beacon, 0x0080); + TEST_ASSERT_EQUAL_UINT32( + xone::mt76::field_prep(xone::mt76::mt_rxwi_rate_phy, + static_cast( + xone::mt76::phy_type::phy_ofdm)), + 0x2000u); + TEST_ASSERT_EQUAL_UINT8(xone::mt76::mt_txwi_ack_ctl_nseq, 0x02); + TEST_ASSERT_EQUAL_UINT32(sizeof(xone::mt76::broadcast_address), 6); +} + int main(void) { UNITY_BEGIN(); RUN_TEST(test_bitfield_helpers); RUN_TEST(test_efuse_registers); RUN_TEST(test_firmware_layout); + RUN_TEST(test_mcu_commands); + RUN_TEST(test_radio_registers); + RUN_TEST(test_channel_table); + RUN_TEST(test_beacon_layout); return UNITY_END(); }