feat: port MT76 radio init and CLI tool

Complete the radio bring-up sequence: init_registers now writes the
upstream register values (PBF out of reset, beacon TX off), crystal
calibration, MAC/BSSID programming, channel evaluation, and beacon
programming through an MCU burst into PBF shared memory. The beacon
txwi is the full 20-byte struct and the RF patch is applied on the
cold firmware path.

Download firmware per product (xone_dongle_02e6.bin / 02fe.bin) and
add the xone_cli debug tool (info, firmware, radio-init/deinit, burst,
reg-read/write, led, recover). Recover uses USBDeviceReEnumerate for a
host-side port reset.

Co-Authored-By: grok4.6: internet search (firmware split, beacon SRAM, upstream issues)
Co-Authored-By: qwen3.8-27b@q2_k_xl: initial radio init and CLI implementation
Co-Authored-By: deepseek/deepseek-v4-pro-0813: final radio init fixes and verification
This commit is contained in:
portersky
2026-08-17 19:29:51 +02:00
parent 4f5f16e43f
commit c9ea1a6919
11 changed files with 1142 additions and 39 deletions
+11 -1
View File
@@ -6,7 +6,7 @@ if(NOT CMAKE_GENERATOR MATCHES "^(Ninja|Xcode)$")
endif() endif()
cmake_minimum_required(VERSION 3.21) 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) 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" -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 # Tests
# ============================================================================== # ==============================================================================
+1
View File
@@ -43,6 +43,7 @@ inline auto log_msg(log_level level, char const* fmt, ...) -> void
std::vfprintf(stderr, fmt, ap); std::vfprintf(stderr, fmt, ap);
va_end(ap); va_end(ap);
std::fprintf(stderr, "\n"); std::fprintf(stderr, "\n");
std::fflush(stderr); // The app is often killed; do not lose the tail.
} }
} // namespace xone } // namespace xone
+40
View File
@@ -13,6 +13,8 @@
#include <cstddef> #include <cstddef>
#include <cstdint> #include <cstdint>
#include "mt76/mt76_defs.hpp"
namespace xone::usb { namespace xone::usb {
class transport; class transport;
} }
@@ -48,6 +50,25 @@ public:
// Build string of the loaded firmware image (empty until load_firmware). // Build string of the loaded firmware image (empty until load_firmware).
auto firmware_build() const -> char const *; 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: private:
// Send an MCU command (port of xone_mt76_send_command). `cmd` is the // 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. // 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 send_firmware(void const *fw_data, std::size_t fw_size) -> int;
auto reset_firmware() -> 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_; usb::transport &transport_;
char build_time_[17] = {}; char build_time_[17] = {};
std::array<channel, num_channels> channels_{};
channel current_channel_{};
}; };
} // namespace xone::mt76 } // namespace xone::mt76
+271 -2
View File
@@ -3,8 +3,8 @@
// ============================================================================== // ==============================================================================
// MT76 chip register definitions (port of transport/mt76_defs.h) // MT76 chip register definitions (port of transport/mt76_defs.h)
// ============================================================================== // ==============================================================================
// Only the registers needed so far are ported; the rest land with the radio // Only the registers needed so far are ported; the rest land with the
// init and firmware load increments. // pairing and data path increments.
// ============================================================================== // ==============================================================================
#include <cstddef> #include <cstddef>
@@ -113,4 +113,273 @@ struct fw_header {
char build_time[16]; char build_time[16];
} XONE_PACKED; } 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 } // namespace xone::mt76
+10
View File
@@ -74,6 +74,12 @@ public:
// opening fails. // opening fails.
static auto probe(frame_callback frames, disconnect_callback disconnected) -> std::unique_ptr<transport>; 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.
auto re_enumerate() -> int;
~transport(); ~transport();
transport(transport const&) = delete; transport(transport const&) = delete;
@@ -102,6 +108,10 @@ private:
auto open_interface(std::uint32_t child) -> bool; auto open_interface(std::uint32_t child) -> bool;
auto submit_read(read_slot *slot) -> 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(); void worker_loop();
// IOKit callbacks use IOReturn (int32_t) and io_iterator_t (uint32_t). // 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_read(read_slot *slot, std::int32_t result, std::size_t len);
+21 -8
View File
@@ -1,6 +1,7 @@
#!/bin/sh #!/bin/sh
# Download and extract the wireless dongle firmware from Windows Update. # 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: # The firmware is subject to Microsoft's Terms of Use:
# https://www.microsoft.com/en-us/legal/terms-of-use # https://www.microsoft.com/en-us/legal/terms-of-use
@@ -9,8 +10,10 @@ set -eu
cd "$(dirname "$0")/.." cd "$(dirname "$0")/.."
firmware_url='http://download.windowsupdate.com/c/msdownload/update/driver/drvs/2017/07/1cd6a87c-623f-4407-a52d-c31be49e925c_e19f60808bdcbfbd3c3df6be3e71ffc52e43261e.cab' 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='48084d9fa53b9bb04358f3bb127b7495dc8f7bb0b3ca1437bd24ef2b6eabdf66' 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 "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' echo 'https://www.microsoft.com/en-us/legal/terms-of-use'
@@ -31,18 +34,28 @@ sha256() {
tmpdir=$(mktemp -d) tmpdir=$(mktemp -d)
trap 'rm -rf "$tmpdir"' EXIT INT HUP TERM trap 'rm -rf "$tmpdir"' EXIT INT HUP TERM
echo "Downloading driver package..." fetch_firmware() {
curl -L --fail -o "$tmpdir/driver.cab" "$firmware_url" # fetch_firmware <url> <sha256> <output name>
url=$1
hash=$2
outname=$3
echo "Downloading $outname..."
curl -L --fail -o "$tmpdir/driver.cab" "$url"
# Extract the firmware image (requires a libarchive-based tar, e.g. macOS). # Extract the firmware image (requires a libarchive-based tar, e.g. macOS).
tar -xf "$tmpdir/driver.cab" -C "$tmpdir" tar -xf "$tmpdir/driver.cab" -C "$tmpdir"
actual=$(sha256 "$tmpdir/FW_ACC_00U.bin") actual=$(sha256 "$tmpdir/FW_ACC_00U.bin")
if [ "$actual" != "$firmware_hash" ]; then if [ "$actual" != "$hash" ]; then
echo "Firmware hash mismatch: $actual" >&2 echo "Firmware hash mismatch: $actual" >&2
exit 1 exit 1
fi fi
mkdir -p firmware mkdir -p firmware
mv "$tmpdir/FW_ACC_00U.bin" firmware/xow_dongle.bin mv "$tmpdir/FW_ACC_00U.bin" "firmware/$outname"
echo "Firmware written to firmware/xow_dongle.bin" 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
+3
View File
@@ -60,6 +60,9 @@ extern "C" xone_dongle *xone_open(const char *firmware_path)
int ret = s->chip->load_firmware(firmware_path); int ret = s->chip->load_firmware(firmware_path);
if (ret != 0) if (ret != 0)
xone::log_msg(xone::log_level::warn, "api: firmware load failed (%d)", ret); 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); current_session = std::move(s);
+178
View File
@@ -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 <chrono>
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <thread>
#include <string>
#include <vector>
#include "common/log.hpp"
#include "mt76/mt76.hpp"
#include "usb/usb_transport.hpp"
namespace {
auto usage() -> int
{
std::fprintf(stderr,
"Usage: xone_cli <command>...\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 <path> Load the firmware image\n"
" radio-init Initialize the radio\n"
" led <mode> Set LED mode (0 blink, 1 on, 2 off)\n"
" func <func> <val> MCU function select (debug)\n"
" radio-deinit Suspend the radio\n"
" burst <idx> <hex> MCU burst write to idx + memmap_wlan\n"
" reg-read <addr> Read a 32-bit register\n"
" reg-write <addr> <value> Write a 32-bit register\n"
" sleep <seconds> Wait (debug)\n");
return 1;
}
auto parse_hex(char const *text, std::vector<std::uint8_t> &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::uint8_t>(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::uint32_t>(std::strtoul(argv[i++], nullptr, 16));
auto val = static_cast<std::uint32_t>(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::uint32_t>(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::uint32_t>(std::strtoul(argv[i++], nullptr, 16));
std::vector<std::uint8_t> 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::uint32_t>(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::size_t>(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::uint32_t>(std::strtoul(argv[i++], nullptr, 16));
auto val = static_cast<std::uint32_t>(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<std::uint8_t const *>(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);
}
+466 -1
View File
@@ -166,7 +166,8 @@ auto chip::send_command(std::uint32_t cmd, void const *payload,
| field_prep(mt_mcu_msg_cmd_type, cmd); | field_prep(mt_mcu_msg_cmd_type, cmd);
auto buf = build_message(info, payload, payload_len); 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 auto chip::load_ivb() -> int
@@ -286,6 +287,11 @@ auto chip::load_firmware(char const *path) -> int
return ret; return ret;
write_register(mt_fce_dma_addr | mt_vend_type_cfg, 0); 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) if (auto err = load_ivb(); err != 0)
return err; return err;
@@ -296,4 +302,463 @@ auto chip::load_firmware(char const *path) -> int
return 0; 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<std::uint32_t>(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<std::uint8_t>(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<std::uint8_t> 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<std::uint8_t> buf(4 + len);
xone::store_le32(buf.data(), static_cast<std::uint32_t>(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<std::uint32_t>(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<std::uint16_t>((trim[3] << 8) | trim[2]);
auto offset = static_cast<int>(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<std::uint16_t>((trim[3] << 8) | trim[2]);
val &= 0xFF;
if (!val || val == 0xFF)
val = 0x14; // Default value
}
val = static_cast<std::uint16_t>(static_cast<int>(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<std::uint32_t>(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<std::uint16_t>(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<std::uint8_t>(target + (offset & genmask(5, 0))) :
static_cast<std::uint8_t>(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(&current_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(&current_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<std::uint16_t>(sizeof(mgmt) + sizeof(data)));
std::vector<std::uint8_t> 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<std::uint8_t> 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<std::uint32_t>(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<std::uint32_t>(wow_feature::wow_traffic_op));
payload[4] = static_cast<std::uint8_t>(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(&current_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 } // namespace xone::mt76
+42 -6
View File
@@ -5,6 +5,7 @@
#include <cerrno> #include <cerrno>
#include <condition_variable> #include <condition_variable>
#include <cstdint>
#include <cstring> #include <cstring>
#include <mutex> #include <mutex>
#include <optional> #include <optional>
@@ -15,6 +16,7 @@
#include <IOKit/IOCFPlugIn.h> #include <IOKit/IOCFPlugIn.h>
#include <IOKit/IOKitLib.h> #include <IOKit/IOKitLib.h>
#include <IOKit/usb/IOUSBLib.h> #include <IOKit/usb/IOUSBLib.h>
#include <IOKit/usb/USB.h>
#include "common/log.hpp" #include "common/log.hpp"
@@ -96,6 +98,7 @@ struct transport::state {
IOUSBInterfaceInterface190 **iface_ref = nullptr; IOUSBInterfaceInterface190 **iface_ref = nullptr;
}; };
std::vector<iface_conn> ifaces; std::vector<iface_conn> ifaces;
bool re_enumerated = false;
struct pipe_ref { struct pipe_ref {
IOUSBInterfaceInterface190 **iface_ref = nullptr; IOUSBInterfaceInterface190 **iface_ref = nullptr;
@@ -138,20 +141,21 @@ auto transport::probe(frame_callback frames, disconnect_callback disconnected) -
return t; return t;
} }
transport::~transport() void transport::stop_pump()
{ {
if (!state_)
return;
// Stop generating completions, then wait for the in-flight ones to drain.
{ {
std::lock_guard<std::mutex> lock(state_->lock); std::lock_guard<std::mutex> lock(state_->lock);
if (state_->stopping)
return;
state_->stopping = true; state_->stopping = true;
} }
for (auto &slot : state_->slots) for (auto &slot : state_->slots)
(*slot.iface_ref)->AbortPipe(slot.iface_ref, slot.pipe_ref); (*slot.iface_ref)->AbortPipe(slot.iface_ref, slot.pipe_ref);
if (state_->thread_started) { if (!state_->thread_started)
return;
CFRunLoopRef runloop = nullptr; CFRunLoopRef runloop = nullptr;
{ {
std::unique_lock<std::mutex> lock(state_->lock); std::unique_lock<std::mutex> lock(state_->lock);
@@ -164,11 +168,43 @@ transport::~transport()
state_->thread.join(); 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_pump();
// Release the termination watch and async event sources. // Release the termination watch and async event sources.
if (state_->termination_iter) if (state_->termination_iter)
IOObjectRelease(state_->termination_iter); IOObjectRelease(state_->termination_iter);
if (state_->notify_port) if (state_->notify_port)
IONotificationPortDestroy(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) for (auto *source : state_->sources)
CFRelease(source); CFRelease(source);
+78
View File
@@ -43,11 +43,89 @@ void test_firmware_layout(void)
TEST_ASSERT_EQUAL_UINT32(xone::mt76::mt_fce_dma_len, 0x0234); 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<std::uint32_t>(xone::mt76::mcu_cmd::cmd_fun_set_op), 1);
TEST_ASSERT_EQUAL_UINT32(
static_cast<std::uint32_t>(xone::mt76::mcu_cmd::cmd_burst_write), 8);
TEST_ASSERT_EQUAL_UINT32(
static_cast<std::uint32_t>(xone::mt76::mcu_cmd::cmd_power_saving_op), 20);
TEST_ASSERT_EQUAL_UINT32(
static_cast<std::uint32_t>(xone::mt76::mcu_cmd::cmd_switch_channel_op), 30);
TEST_ASSERT_EQUAL_UINT32(
static_cast<std::uint32_t>(xone::mt76::power_mode::radio_off), 0x30);
TEST_ASSERT_EQUAL_UINT32(
static_cast<std::uint32_t>(xone::mt76::power_mode::radio_on), 0x31);
TEST_ASSERT_EQUAL_UINT32(
static_cast<std::uint32_t>(xone::mt76::ms_command::ms_set_mac_address),
0x00);
TEST_ASSERT_EQUAL_UINT32(
static_cast<std::uint32_t>(xone::mt76::ms_command::ms_set_idle_time), 0x05);
TEST_ASSERT_EQUAL_UINT32(
static_cast<std::uint32_t>(
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<std::uint32_t>(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<std::uint8_t>(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<std::uint8_t>(cal_channel_group::ch_5g_unii_1));
TEST_ASSERT_EQUAL_UINT8(channels[7].index, 0x95);
TEST_ASSERT_EQUAL_UINT8(channels[7].bandwidth,
static_cast<std::uint8_t>(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<std::uint32_t>(
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) int main(void)
{ {
UNITY_BEGIN(); UNITY_BEGIN();
RUN_TEST(test_bitfield_helpers); RUN_TEST(test_bitfield_helpers);
RUN_TEST(test_efuse_registers); RUN_TEST(test_efuse_registers);
RUN_TEST(test_firmware_layout); 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(); return UNITY_END();
} }