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:
@@ -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);
|
||||
|
||||
@@ -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
@@ -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<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(¤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<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(¤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
|
||||
|
||||
+56
-20
@@ -5,6 +5,7 @@
|
||||
|
||||
#include <cerrno>
|
||||
#include <condition_variable>
|
||||
#include <cstdint>
|
||||
#include <cstring>
|
||||
#include <mutex>
|
||||
#include <optional>
|
||||
@@ -15,6 +16,7 @@
|
||||
#include <IOKit/IOCFPlugIn.h>
|
||||
#include <IOKit/IOKitLib.h>
|
||||
#include <IOKit/usb/IOUSBLib.h>
|
||||
#include <IOKit/usb/USB.h>
|
||||
|
||||
#include "common/log.hpp"
|
||||
|
||||
@@ -96,6 +98,7 @@ struct transport::state {
|
||||
IOUSBInterfaceInterface190 **iface_ref = nullptr;
|
||||
};
|
||||
std::vector<iface_conn> 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<std::mutex> 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<std::mutex> 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<std::mutex> 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<std::mutex> 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);
|
||||
|
||||
|
||||
Reference in New Issue
Block a user