diff --git a/CMakeLists.txt b/CMakeLists.txt index 9dab962..09f7a05 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -117,6 +117,8 @@ add_library(devourer hal/basic_types.h hal/hal_com_reg.h src/ieee80211_radiotap.h + src/HalmacCfgParam.cpp + src/HalmacCfgParam.h src/ParsedRadioPacket.cpp src/Radiotap.c src/RadiotapBuilder.cpp diff --git a/examples/common/env_config.cpp b/examples/common/env_config.cpp index 2791eb8..d22af58 100644 --- a/examples/common/env_config.cpp +++ b/examples/common/env_config.cpp @@ -120,6 +120,8 @@ devourer::DeviceConfig devourer_config_from_env() { cfg.tuning.fastretune_fw = static_cast(v); if (env_long("DEVOURER_KFR_OFLD", &v) && v >= 0) cfg.tuning.kestrel_fastretune_ofld = static_cast(v); + if (env_long("DEVOURER_FW_TABLE_OFLD", &v) && v >= 0) + cfg.tuning.fw_table_offload = static_cast(v); /* Jaguar3 per-packet power-bank step size (qdB per 0x1e70 offset-index * step; default 4 = 1 dB) — bench slope-calibration override. */ if (env_long("DEVOURER_TXPKT_STEP_QDB", &v) && v > 0) diff --git a/src/DeviceConfig.h b/src/DeviceConfig.h index 3b351c6..ac65301 100644 --- a/src/DeviceConfig.h +++ b/src/DeviceConfig.h @@ -236,6 +236,17 @@ struct DeviceConfig { * hop returns before the synth settles — the caller honours a ~1.5 ms * settle before TX (the demos' admission window). 8852B only. */ int kestrel_fastretune_ofld = 1; + /* env: DEVOURER_FW_TABLE_OFLD — Jaguar2 init-time firmware register + * IO-offload (HalMAC cfg_param): route the static BB/AGC/RF phy-table write + * stream through one FW_OFFLOAD/CFG_PARAM H2C per ~160-command batch that + * the firmware replays on-chip, collapsing the per-init USB register control + * transfers into a handful of bulk transfers. Bit flags: 1 = BB + AGC + * tables, 2 = also the RF radio tables (RF_W, routed through the firmware's + * RF write path). 0 = off (direct writes, byte-identical to before). Default + * 0 — init is higher blast-radius than a hop. Jaguar3 (8822C/E) is a + * follow-up: the fw replays cfg_param but lazily, and its rsvd-page download + * stalls per batch. */ + int fw_table_offload = 0; /* env: DEVOURER_DIS_CCA — Jaguar2/3 MAC carrier-sense disable at bring-up * (primary CCA 0x520[14] + EDCCA [15]): injected/beacon TX stops deferring to * a busy channel and punches through co-channel traffic. Runtime equivalent: diff --git a/src/HalmacCfgParam.cpp b/src/HalmacCfgParam.cpp new file mode 100644 index 0000000..8b3bcc3 --- /dev/null +++ b/src/HalmacCfgParam.cpp @@ -0,0 +1,130 @@ +#include "HalmacCfgParam.h" + +#include +#include +#include + +namespace devourer { + +namespace { +constexpr uint8_t CMD_LEN = 0x0C; /* 12-byte command */ +inline void put_le32(uint8_t *p, uint32_t v) { + p[0] = static_cast(v); + p[1] = static_cast(v >> 8); + p[2] = static_cast(v >> 16); + p[3] = static_cast(v >> 24); +} +} // namespace + +HalmacCfgParam::HalmacCfgParam(Transport t, Logger_t logger) + : _t(std::move(t)), _logger(std::move(logger)) { + _buf.reserve((static_cast(_t.max_cmds) + 1) * CMD_LEN); +} + +void HalmacCfgParam::push(uint8_t cmd, uint32_t addr_field, uint8_t /*path*/, + uint32_t value) { + /* addr_field is pre-shaped by the caller into word0[31:16] (MAC/BB addr) or + * word0[31:16] = RF_ADDR<<0 | RF_PATH<<8 already positioned. MSK_EN=0 for the + * full-register static writes this offload carries. */ + uint32_t w0 = CMD_LEN | (static_cast(cmd) << 8) | + (addr_field & 0xffff0000u); + const size_t off = _buf.size(); + _buf.resize(off + CMD_LEN); + put_le32(_buf.data() + off + 0, w0); + put_le32(_buf.data() + off + 4, value); + put_le32(_buf.data() + off + 8, 0); /* MASK unused (MSK_EN=0) */ + _num++; + if (_num >= _t.max_cmds) + flush(); +} + +void HalmacCfgParam::bb_write(uint16_t addr, uint32_t value) { + _last_was_bb = true; + _last_bb_addr = addr; + _last_bb_value = value; + push(BB_W32, static_cast(addr) << 16, 0, value); +} + +void HalmacCfgParam::mac_write(uint16_t addr, uint32_t value, uint8_t width) { + const uint8_t cmd = width == 1 ? MAC_W8 : width == 2 ? MAC_W16 : MAC_W32; + push(cmd, static_cast(addr) << 16, 0, value); +} + +void HalmacCfgParam::rf_write(uint8_t path, uint8_t addr, uint32_t value) { + /* word0[23:16] = RF_ADDR (8-bit), word0[31:24] = RF_PATH. */ + const uint32_t addr_field = (static_cast(addr) << 16) | + (static_cast(path) << 24); + push(RF_W, addr_field, path, value & 0x000fffffu); +} + +bool HalmacCfgParam::flush() { + if (_num == 0) + return _ok; + /* Terminate the buffer with an END command (belt-and-suspenders alongside the + * NUM field the trigger carries). */ + const size_t off = _buf.size(); + _buf.resize(off + CMD_LEN, 0); + put_le32(_buf.data() + off + 0, CMD_LEN | (static_cast(END) << 8)); + + const uint32_t num = _num; + bool ok = _t.dl_rsvd_page(_t.cfg_pg_addr, _buf.data(), + static_cast(_buf.size())); + if (ok) { + uint8_t trig[32] = {0}; + trig[0] = 0x01; + trig[1] = 0xff; /* CMD_ID_FW_OFFLOAD */ + trig[2] = 0x08; /* SUB_CMD_ID_CFG_PARAM */ + trig[4] = 8 + 4; /* header + 4-byte payload */ + trig[6] = _t.next_seq(); + /* byte 8..11: NUM[15:0] | INIT_CASE[16]=0 (drv mode) | LOC[31:24]. */ + put_le32(trig + 8, + (num & 0xffffu) | (static_cast(_t.cfg_loc) << 24)); + ok = _t.send_h2c_pkt(trig); + if (ok && _t.settle) + _t.settle(); /* trigger-consumed check (cheap; not the replay gate) */ + } + + /* Confirm the firmware actually replayed the batch by polling the last BB + * write back until it reads what was sent. This doubles as the completion + * gate (the extra-info page is safe to reuse once the value lands) and the + * correctness check (a dropped/mis-parsed H2C never lands → offload fails and + * the caller redoes the batch direct). No fixed settle — the poll returns the + * instant the on-chip replay reaches the final write. */ + if (ok && _last_was_bb && _t.verify_read) { + uint32_t got = 0; + bool matched = false; + for (int i = 0; i < 40; ++i) { /* up to ~8 ms */ + got = _t.verify_read(_last_bb_addr); + if (got == _last_bb_value) { + matched = true; + break; + } + std::this_thread::sleep_for(std::chrono::microseconds(200)); + } + if (!matched) { + _logger->error("HalMAC cfg_param: readback 0x{:04x}=0x{:08x} != sent " + "0x{:08x} — firmware did not replay the batch", + _last_bb_addr, got, _last_bb_value); + ok = false; + } + } else if (ok && _num > 0) { + /* Batch ended on a non-BB (RF) write, which the BB read-back can't gate; + * the shadow-window RF read-back isn't wired, so give the on-chip replay a + * bounded margin before the page is reused. */ + std::this_thread::sleep_for(std::chrono::milliseconds(2)); + } + + if (!ok) { + _ok = false; + _logger->error("HalMAC cfg_param: flush failed ({} cmds) — falling back", + num); + } else { + _total += num; + } + _buf.clear(); + _num = 0; + _last_was_bb = false; + return ok; +} + +} // namespace devourer diff --git a/src/HalmacCfgParam.h b/src/HalmacCfgParam.h new file mode 100644 index 0000000..a4f72db --- /dev/null +++ b/src/HalmacCfgParam.h @@ -0,0 +1,101 @@ +#ifndef DEVOURER_HALMAC_CFG_PARAM_H +#define DEVOURER_HALMAC_CFG_PARAM_H + +#include +#include +#include + +#include "logger.h" + +namespace devourer { + +/* HalMAC firmware register IO-offload (cfg_param). Accumulates a stream of + * static register writes into a 12-byte-per-command buffer, then flushes each + * batch as ONE FW_OFFLOAD/CFG_PARAM H2C the on-chip firmware replays locally — + * collapsing the thousands of per-init phy-table USB control transfers into a + * handful of bulk transfers. Generation-agnostic (8822B/8821C/8822C/8822E all + * speak the same cfg_param wire format); the per-generation transport is + * injected as callbacks. + * + * Command wire format (12 bytes LE, matches halmac add_param_buf_88xx): + * word0 = LEN(0x0C)[7:0] | IO_CMD[14:8] | MSK_EN[15] | + * { MAC/BB: ADDR[31:16] } | { RF: RF_ADDR[23:16] | RF_PATH[31:24] } + * word1 = DATA (value) + * word2 = MASK (only when MSK_EN=1) + * Static table entries are full-register writes, so MSK_EN=0 / MASK=0 — the + * firmware writes the value verbatim (no on-chip read-modify-write, so the + * masked-value shift convention is irrelevant here). + * + * Trigger H2C (32 bytes): {0x01, 0xff, 0x08(CFG_PARAM), 0, len, 0, seq, 0} then + * at byte 8: NUM[15:0] | INIT_CASE[16] | LOC[31:24]. Drv mode (INIT_CASE=0) + * places the buffer at the reserved H2C-extra-info page (LOC = its page offset + * from the rsvd boundary). */ +class HalmacCfgParam { +public: + /* IO_CMD ids (enum halmac_parameter_cmd). */ + enum Cmd : uint8_t { + MAC_W8 = 0x4, + MAC_W16 = 0x5, + MAC_W32 = 0x6, + RF_W = 0x7, + BB_W8 = 0x8, + BB_W16 = 0x9, + BB_W32 = 0xA, + END = 0xFF, + }; + + struct Transport { + /* DMA the packed command buffer to the reserved H2C-extra-info page + * (dl_rsvd_page / send_fw_page with a data descriptor). */ + std::function + dl_rsvd_page; + /* Send a 32-byte FW_OFFLOAD H2C packet (the CFG_PARAM trigger). */ + std::function send_h2c_pkt; + /* Next rolling H2C sequence number (halmac h2c_info.seq_num). */ + std::function next_seq; + /* Block until the firmware has consumed the trigger + replayed the buffer, + * so the extra-info page is safe to reuse for the next batch. */ + std::function settle; + /* Read back a BB register (full dword). After each flush the last BB write + * in the batch is read back and compared to what was sent — this both + * confirms the firmware actually replayed the buffer (vs silently dropping + * the H2C) and gates page reuse. A mismatch marks the offload failed so the + * caller falls back to direct writes. Optional; skipped if unset. */ + std::function verify_read; + uint16_t cfg_pg_addr = 0; /* absolute TX-FIFO page of the extra-info buffer */ + uint16_t cfg_loc = 0; /* LOC field: that page's offset from rsvd boundary */ + uint32_t max_cmds = 160; /* flush threshold (extra-info page holds ~170) */ + }; + + HalmacCfgParam(Transport t, Logger_t logger); + + void bb_write(uint16_t addr, uint32_t value); + void mac_write(uint16_t addr, uint32_t value, uint8_t width); + void rf_write(uint8_t path, uint8_t addr, uint32_t value); + + /* Flush the accumulated batch. No-op if empty. Returns false on a transport + * failure (the caller falls back to direct writes). Auto-invoked when the + * buffer reaches max_cmds. */ + bool flush(); + + bool ok() const { return _ok; } + uint64_t total() const { return _total; } /* commands offloaded this session */ + uint32_t pending() const { return _num; } + +private: + void push(uint8_t cmd, uint32_t addr_field, uint8_t path, uint32_t value); + + Transport _t; + Logger_t _logger; + std::vector _buf; /* packed 12-byte commands, current batch */ + uint32_t _num = 0; + uint64_t _total = 0; + bool _ok = true; + bool _last_was_bb = false; /* for the per-flush read-back verify */ + uint16_t _last_bb_addr = 0; + uint32_t _last_bb_value = 0; +}; + +} // namespace devourer + +#endif diff --git a/src/jaguar2/HalJaguar2.cpp b/src/jaguar2/HalJaguar2.cpp index 8bd9851..57f4e16 100644 --- a/src/jaguar2/HalJaguar2.cpp +++ b/src/jaguar2/HalJaguar2.cpp @@ -399,7 +399,9 @@ void HalJaguar2::rf_write(uint8_t path, uint32_t addr, uint32_t value) { std::this_thread::sleep_for(std::chrono::microseconds(1)); } -void HalJaguar2::apply_bb_rf_agc_tables(uint8_t rfe_type) { +void HalJaguar2::apply_bb_rf_agc_tables(uint8_t rfe_type, + devourer::HalmacCfgParam *cfg, + bool offload_rf) { JaguarPhyContext ctx{}; ctx.cut_version = _ver.cut; ctx.support_interface = 0x02; /* ODM_ITRF_USB */ @@ -418,25 +420,56 @@ void HalJaguar2::apply_bb_rf_agc_tables(uint8_t rfe_type) { * init_rf_reg (radioa, radiob) -> POST. */ phydm_pre_post_setting(/*post=*/false); - auto bb = [this](uint32_t a, uint32_t v) { bb_write(a, v); }; + /* BB/AGC sink: batch through the firmware register offload when armed; a + * delay pseudo-address (0xf9..0xfe) forces a flush then runs the host delay, + * and once the offload transport errors we fall back to a direct write. */ + auto bb = [this, cfg](uint32_t a, uint32_t v) { + if (cfg && cfg->ok()) { + if (a >= 0xf9 && a <= 0xfe) { + cfg->flush(); + bb_write(a, v); + return; + } + cfg->bb_write(static_cast(a), v); + return; + } + bb_write(a, v); + }; const auto phy_reg = _tables->phy_reg(); const auto agc_tab = _tables->agc_tab(); - _logger->info("Jaguar2: applying BB phy_reg ({} words) + agc_tab ({} words)", - phy_reg.len, agc_tab.len); + _logger->info("Jaguar2: applying BB phy_reg ({} words) + agc_tab ({} words){}", + phy_reg.len, agc_tab.len, cfg ? " [fw-offload]" : ""); PhyTableLoader::Load(phy_reg.data, phy_reg.len, ctx, bb); PhyTableLoader::Load(agc_tab.data, agc_tab.len, ctx, bb); - + if (cfg) + cfg->flush(); /* land all BB/AGC before the RF walk starts */ + + auto rf = [this, cfg, offload_rf](uint8_t path, uint32_t a, uint32_t v) { + if (offload_rf && cfg && cfg->ok()) { + if (a == 0xfe || a == 0xffe) { + cfg->flush(); + rf_write(path, a, v); + return; + } + cfg->rf_write(path, static_cast(a & 0xff), v); + return; + } + rf_write(path, a, v); + }; const auto radioa = _tables->radioa(); const auto radiob = _tables->radiob(); - _logger->info("Jaguar2: applying RF radioa ({} words) + radiob ({} words)", - radioa.len, radiob.len); + _logger->info("Jaguar2: applying RF radioa ({} words) + radiob ({} words){}", + radioa.len, radiob.len, + (cfg && offload_rf) ? " [fw-offload]" : ""); PhyTableLoader::Load(radioa.data, radioa.len, ctx, - [this](uint32_t a, uint32_t v) { rf_write(0, a, v); }); + [&rf](uint32_t a, uint32_t v) { rf(0, a, v); }); /* 1T1R chips (8821C) supply no radiob table (path A only) — skip the path-B * RF walk rather than feed the loader a null table. */ if (radiob.len) PhyTableLoader::Load(radiob.data, radiob.len, ctx, - [this](uint32_t a, uint32_t v) { rf_write(1, a, v); }); + [&rf](uint32_t a, uint32_t v) { rf(1, a, v); }); + if (cfg) + cfg->flush(); /* drain any RF batch (and the BB remainder on !offload_rf) */ phydm_pre_post_setting(/*post=*/true); diff --git a/src/jaguar2/HalJaguar2.h b/src/jaguar2/HalJaguar2.h index a7df9de..d078f6a 100644 --- a/src/jaguar2/HalJaguar2.h +++ b/src/jaguar2/HalJaguar2.h @@ -5,6 +5,7 @@ #include #include +#include "HalmacCfgParam.h" #include "logger.h" #include "RtlAdapter.h" #include "RxSense.h" @@ -114,7 +115,9 @@ class HalJaguar2 { * block disable/enable (config_phydm_parameter_init_8822b PRE/POST). Mirrors * rtl8822b_phy.c: PRE -> init_bb_reg -> init_rf_reg -> POST. rfe_type selects * the conditional table blocks. */ - void apply_bb_rf_agc_tables(uint8_t rfe_type); + void apply_bb_rf_agc_tables(uint8_t rfe_type, + devourer::HalmacCfgParam *cfg = nullptr, + bool offload_rf = false); /* Set RF channel + bandwidth (config_phydm_switch_channel_8822b + * config_phydm_switch_bandwidth_8822b): RF18 tune, band AGC/fc/CCK-filter, diff --git a/src/jaguar2/HalmacJaguar2Fw.h b/src/jaguar2/HalmacJaguar2Fw.h index c18dc68..edc2833 100644 --- a/src/jaguar2/HalmacJaguar2Fw.h +++ b/src/jaguar2/HalmacJaguar2Fw.h @@ -47,8 +47,9 @@ class HalmacJaguar2Fw { * dl_rsvd_page_88xx == send_fw_page). The FW requires its reserved-page set * before it enables the MAC TX scheduler; without it RX works but TX frames * never leave the MAC. */ - bool download_rsvd_page(uint16_t pg_addr, const uint8_t *buf, uint32_t size) { - return send_fw_page(pg_addr, buf, size, /*beacon_desc=*/true); + bool download_rsvd_page(uint16_t pg_addr, const uint8_t *buf, uint32_t size, + bool beacon_desc = true) { + return send_fw_page(pg_addr, buf, size, beacon_desc); } private: diff --git a/src/jaguar2/HalmacJaguar2MacInit.cpp b/src/jaguar2/HalmacJaguar2MacInit.cpp index 138e22e..148f834 100644 --- a/src/jaguar2/HalmacJaguar2MacInit.cpp +++ b/src/jaguar2/HalmacJaguar2MacInit.cpp @@ -440,6 +440,28 @@ bool HalmacJaguar2MacInit::send_h2c_pkt(const uint8_t pkt[32]) { return got == static_cast(sizeof(frame)); } +uint16_t HalmacJaguar2MacInit::cfg_param_page() const { + /* The rsvd pages stack up from the boundary: DRV(16) at the bottom, then the + * H2C-extra-info block — so the cfg_param buffer page is boundary + DRV_NUM. */ + return static_cast(_rsvd_boundary + RSVD_PG_DRV_NUM); +} + +uint16_t HalmacJaguar2MacInit::cfg_param_loc() const { return RSVD_PG_DRV_NUM; } + +void HalmacJaguar2MacInit::wait_h2c_drained(uint32_t timeout_ms) { + /* HEAD advances as packets enter the H2C queue, READ_ADDR as the FW consumes + * them (get_h2c_buf_free_space_88xx). Equal pointers = the trigger was read; + * a short settle then covers the on-chip register replay before the caller + * overwrites the extra-info page for the next batch. */ + for (uint32_t i = 0; i < timeout_ms; ++i) { + const uint32_t head = _device.rtw_read32(REG_H2C_HEAD) & 0x3ffff; + const uint32_t rd = _device.rtw_read32(REG_H2C_READ_ADDR) & 0x3ffff; + if (head == rd) + break; + std::this_thread::sleep_for(std::chrono::microseconds(200)); + } +} + bool HalmacJaguar2MacInit::send_fw_general_info(uint8_t rfe_type, bool r2t2r, uint8_t cut_ver, uint8_t package_type) { diff --git a/src/jaguar2/HalmacJaguar2MacInit.h b/src/jaguar2/HalmacJaguar2MacInit.h index 972677e..7693015 100644 --- a/src/jaguar2/HalmacJaguar2MacInit.h +++ b/src/jaguar2/HalmacJaguar2MacInit.h @@ -74,6 +74,21 @@ class HalmacJaguar2MacInit { bool send_fw_general_info(uint8_t rfe_type, bool r2t2r, uint8_t cut_ver, uint8_t package_type); + /* Runtime H2C-packet transport for the firmware register IO-offload + * (HalmacCfgParam cfg_param trigger): send a 32-byte FW_OFFLOAD packet on the + * H2C queue and hand out the next rolling sequence number. Shares the queue + + * seq counter with send_fw_general_info (same H2C stream). */ + bool h2c_pkt(const uint8_t pkt[32]) { return send_h2c_pkt(pkt); } + uint8_t next_h2c_seq() { return _h2c_seq++; } + /* Block until the firmware has drained the H2C queue (HEAD == READ_ADDR), + * i.e. the last trigger was consumed — so the extra-info page is reusable. */ + void wait_h2c_drained(uint32_t timeout_ms = 20); + /* Reserved H2C-extra-info page (absolute TX-FIFO page) that holds the + * cfg_param command buffer, and its LOC (page offset from the rsvd boundary) + * for the CFG_PARAM trigger. Valid after init_trx_cfg(). */ + uint16_t cfg_param_page() const; + uint16_t cfg_param_loc() const; + private: bool priority_queue_cfg(); void init_h2c(); diff --git a/src/jaguar2/RtlJaguar2Device.cpp b/src/jaguar2/RtlJaguar2Device.cpp index e2c5c38..0f8d9b5 100644 --- a/src/jaguar2/RtlJaguar2Device.cpp +++ b/src/jaguar2/RtlJaguar2Device.cpp @@ -129,8 +129,40 @@ void RtlJaguar2Device::bring_up(SelectedChannel channel) { _macinit.send_fw_general_info(rfe, r2t2r, _hal.chip_version().cut, pkg); } - _hal.apply_bb_rf_agc_tables(rfe); - _logger->info("RtlJaguar2Device: PHY tables applied"); + /* Optional firmware register IO-offload of the static BB/AGC/RF phy tables + * (DEVOURER_FW_TABLE_OFLD): batch the write stream through HalMAC cfg_param so + * the fw replays it on-chip — collapses the ~5700–9200 per-init USB register + * transfers into a handful of bulk transfers. Falls back to a direct re-apply + * (idempotent) if the transport errors. */ + const int ofld = _cfg.tuning.fw_table_offload; + if (ofld && _device.is_usb()) { + devourer::HalmacCfgParam::Transport tp; + tp.dl_rsvd_page = [this](uint16_t pg, const uint8_t *b, uint32_t l) { + return _fw.download_rsvd_page(pg, b, l, /*beacon_desc=*/false); + }; + tp.send_h2c_pkt = [this](const uint8_t pkt[32]) { + return _macinit.h2c_pkt(pkt); + }; + tp.next_seq = [this]() { return _macinit.next_h2c_seq(); }; + tp.settle = [this]() { _macinit.wait_h2c_drained(); }; + tp.verify_read = [this](uint16_t a) { return _device.rtw_read32(a); }; + tp.cfg_pg_addr = _macinit.cfg_param_page(); + tp.cfg_loc = _macinit.cfg_param_loc(); + devourer::HalmacCfgParam cfg(tp, _logger); + _hal.apply_bb_rf_agc_tables(rfe, &cfg, /*offload_rf=*/(ofld & 2) != 0); + if (!cfg.ok()) { + _logger->warn("RtlJaguar2Device: fw table offload failed — re-applying " + "tables direct"); + _hal.apply_bb_rf_agc_tables(rfe); + } else { + _logger->info("RtlJaguar2Device: PHY tables applied via fw offload " + "({} commands)", + cfg.total()); + } + } else { + _hal.apply_bb_rf_agc_tables(rfe); + _logger->info("RtlJaguar2Device: PHY tables applied"); + } /* halrf kfree init: read the PPG efuse trims and apply the PA-bias RF LUT * correction (write-only LUT state — the 0x3f sequence visible in the diff --git a/tests/fw_table_offload.sh b/tests/fw_table_offload.sh new file mode 100755 index 0000000..dab6b20 --- /dev/null +++ b/tests/fw_table_offload.sh @@ -0,0 +1,43 @@ +#!/usr/bin/env bash +# fw_table_offload.sh — A/B the Jaguar2/3 init-time firmware register IO-offload +# (DEVOURER_FW_TABLE_OFLD) against the direct-write path: cold-cycle the DUT, +# run init both ways, and report the init-time drop, the offload command count, +# the per-batch read-back verify verdict, and whether the RF LO locks. +# +# sudo tests/fw_table_offload.sh [ofld_flags] +# e.g. sudo tests/fw_table_offload.sh 2357:012d 10 2 1 +set -u + +VIDPID="${1:?VID:PID}"; HUB="${2:?uhubctl hub loc}"; PORT="${3:?hub port}" +OFLD="${4:-1}" +VID="0x${VIDPID%%:*}"; PID="0x${VIDPID##*:}" +REPO="$(cd "$(dirname "$0")/.." && pwd)" +TXDEMO="$REPO/build/txdemo" + +cleanup() { sudo pkill -9 -x txdemo 2>/dev/null; } +trap cleanup EXIT INT TERM + +cold_cycle() { + cleanup; sleep 1 + rm -f "/tmp/devourer-usb-${HUB}-${PORT}.lock" 2>/dev/null + echo ">> VBUS cold-cycle hub $HUB port $PORT" + uhubctl -l "$HUB" -p "$PORT" -a cycle -d 2 >/dev/null 2>&1 + sleep 4 +} + +run_init() { # $1=ofld flag, $2=tag + local out="/tmp/fwtof.$2" + env DEVOURER_VID="$VID" DEVOURER_PID="$PID" DEVOURER_FW_TABLE_OFLD="$1" \ + DEVOURER_EVENTS=stdout DEVOURER_LOG_LEVEL=info DEVOURER_CHANNEL=36 \ + DEVOURER_TX_GAP_US=100000 \ + timeout 16 "$TXDEMO" >"$out.out" 2>"$out.err" + echo "== $2 (FW_TABLE_OFLD=$1) ==" + grep -iE 'PHY tables applied|fw offload|readback|did not replay|LCK|channel set ch' "$out.err" | tail -6 + local ms + ms=$(grep -F '"stage":"txdemo.init_write"' "$out.out" | grep -oE '"ms":[0-9]+' | head -1 | cut -d: -f2) + echo " init_write = ${ms:-N/A} ms" + echo +} + +cold_cycle; run_init 0 direct +cold_cycle; run_init "$OFLD" offload