Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
2 changes: 2 additions & 0 deletions examples/common/env_config.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -120,6 +120,8 @@ devourer::DeviceConfig devourer_config_from_env() {
cfg.tuning.fastretune_fw = static_cast<int>(v);
if (env_long("DEVOURER_KFR_OFLD", &v) && v >= 0)
cfg.tuning.kestrel_fastretune_ofld = static_cast<int>(v);
if (env_long("DEVOURER_FW_TABLE_OFLD", &v) && v >= 0)
cfg.tuning.fw_table_offload = static_cast<int>(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)
Expand Down
11 changes: 11 additions & 0 deletions src/DeviceConfig.h
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand Down
130 changes: 130 additions & 0 deletions src/HalmacCfgParam.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,130 @@
#include "HalmacCfgParam.h"

#include <chrono>
#include <cstring>
#include <thread>

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<uint8_t>(v);
p[1] = static_cast<uint8_t>(v >> 8);
p[2] = static_cast<uint8_t>(v >> 16);
p[3] = static_cast<uint8_t>(v >> 24);
}
} // namespace

HalmacCfgParam::HalmacCfgParam(Transport t, Logger_t logger)
: _t(std::move(t)), _logger(std::move(logger)) {
_buf.reserve((static_cast<size_t>(_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<uint32_t>(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<uint32_t>(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<uint32_t>(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<uint32_t>(addr) << 16) |
(static_cast<uint32_t>(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<uint32_t>(END) << 8));

const uint32_t num = _num;
bool ok = _t.dl_rsvd_page(_t.cfg_pg_addr, _buf.data(),
static_cast<uint32_t>(_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<uint32_t>(_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
101 changes: 101 additions & 0 deletions src/HalmacCfgParam.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,101 @@
#ifndef DEVOURER_HALMAC_CFG_PARAM_H
#define DEVOURER_HALMAC_CFG_PARAM_H

#include <cstdint>
#include <functional>
#include <vector>

#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<bool(uint16_t pg_addr, const uint8_t *buf, uint32_t len)>
dl_rsvd_page;
/* Send a 32-byte FW_OFFLOAD H2C packet (the CFG_PARAM trigger). */
std::function<bool(const uint8_t pkt[32])> send_h2c_pkt;
/* Next rolling H2C sequence number (halmac h2c_info.seq_num). */
std::function<uint8_t()> 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<void()> 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<uint32_t(uint16_t bb_addr)> 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<uint8_t> _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
51 changes: 42 additions & 9 deletions src/jaguar2/HalJaguar2.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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 */
Expand All @@ -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<uint16_t>(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<uint8_t>(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);

Expand Down
5 changes: 4 additions & 1 deletion src/jaguar2/HalJaguar2.h
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
#include <cstdint>
#include <memory>

#include "HalmacCfgParam.h"
#include "logger.h"
#include "RtlAdapter.h"
#include "RxSense.h"
Expand Down Expand Up @@ -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,
Expand Down
5 changes: 3 additions & 2 deletions src/jaguar2/HalmacJaguar2Fw.h
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand Down
Loading
Loading