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
20 changes: 19 additions & 1 deletion docs/frequency-hopping.md
Original file line number Diff line number Diff line change
Expand Up @@ -336,7 +336,7 @@ every generation overrides it with a lean path built from the tricks above:
| RTL8821CU (Jaguar2) | ~30 ms | **~0.55 ms** | code-covered, no HW | ~0.5 ms | 1 |
| RTL8822CU (Jaguar3) | ~12 ms | **~1.9 ms** | **~0.8 ms** | ~0.21 ms | 9 |
| RTL8812EU (Jaguar3) | ~12 ms | **~2.4 ms** | **~0.8 ms** (8822E) | ~0.27 ms | 9 |
| RTL8852BU (Kestrel/AX) | ~90 ms | **~9 ms** | | ~1–2 ms | ~4 |
| RTL8852BU (Kestrel/AX) | ~90 ms | **~9 ms** | **~0.15 ms** (1 H2C) | ~1–2 ms | ~4 |

The Kestrel (rtw89/AX) hop needed the most work to reach 11ac-ish territory,
but the same techniques applied. The vendored halrf channel setting does an
Expand All @@ -361,6 +361,24 @@ architecture (`SCAN_OFFLOAD` and MCC are the only rtw89 channel primitives). A
Kestrel dwell-1 slot is ~30 ms (approaching the 8822B's ~20 ms) — the
N-channel data plane runs on AX at a usable hop rate.

There is, however, a firmware *register* IO-offload (`DEVOURER_KFR_OFLD`,
default on, 8852B). rtw89 has no channel-switch primitive, but its mac_ax firmware
does expose a generic register-write offload (`FW_OFLD`/`CMD_OFLD_REG`): a
buffer of masked write commands the on-chip CPU replays locally. The whole
same-sub-band hop — the RF18/0xcf channel-set writes, the BB reset, and the
fixed-dBm power target — packs into ONE H2C, so ~20 per-hop USB register
round-trips collapse into a single bulk-OUT. The host-side hop cost drops from
~9 ms to **~0.15 ms**. The catch is honest and instructive: the offload frees
the *host*, not the *radio*. The direct path's ~5 ms of BB-reset round-trips
were incidentally covering the RF synth's settle; removing them exposes a
**~1.5 ms VCO settle floor** below which a frame airs mid-retune. So the
caller must honour a ~1.5 ms settle before it TXes on the new channel — the
time-to-usable-channel is ~1.7 ms (still ~5×), but the host is free to prepare
the next frame or service RX during the settle instead of blocking on EP0. This
changes the `FastRetune` timing contract (it returns before the channel is
usable); the hop demos' admission window already honours the settle, and
`DEVOURER_KFR_OFLD=0` restores the self-pacing direct path.

(Median `hop.dwell` switch_us over a 1/6/11 hop set; per-stage numbers from
`DEVOURER_HOP_PROF=1`. Every hop microsecond is USB round-trips: one register
read or write is one synchronous control transfer, whose latency is a property
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 @@ -118,6 +118,8 @@ devourer::DeviceConfig devourer_config_from_env() {
cfg.tuning.disable_cca = env_flag("DEVOURER_DIS_CCA");
if (env_long("DEVOURER_FASTRETUNE_FW", &v) && v >= 0)
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);
/* 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
6 changes: 5 additions & 1 deletion examples/dwelltx/main.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -155,7 +155,11 @@ int main() {
}
const long slot_ms = env_long("DEVOURER_DWELL_SLOT_MS", 20);
const long total_slots = env_long("DEVOURER_DWELL_SLOTS", 0);
const long settle_us = env_long("DEVOURER_DWELL_SETTLE_US", 500);
// Post-switch settle before admitting a frame. On the Kestrel 8852B the
// default IO-offload hop returns before the ~1.5 ms RF synth settle (the host
// no longer self-paces it via slow register writes), so the window must
// cover that floor or a frame airs mid-retune. 2 ms is safe on every chip.
const long settle_us = env_long("DEVOURER_DWELL_SETTLE_US", 2000);
const long guard_us = env_long("DEVOURER_DWELL_GUARD_US", 1000);
const long airtime_us = env_long("DEVOURER_DWELL_AIRTIME_US", 300);
const long late_us = env_long("DEVOURER_DWELL_LATE_US", 0);
Expand Down
11 changes: 11 additions & 0 deletions src/DeviceConfig.h
Original file line number Diff line number Diff line change
Expand Up @@ -225,6 +225,17 @@ struct DeviceConfig {
* bring-up band's — active power knobs re-fold). Bench + protocol:
* docs/experiments/kernel-channel-switch-offload.md. */
int fastretune_fw = 0;
/* env: DEVOURER_KFR_OFLD — Kestrel FastRetune firmware IO-offload (mac_ax
* fwofld.c): batch the whole same-sub-band hop (RF18/0xcf channel set + BB
* reset + fixed TX power) into ONE FW_OFLD/CMD_OFLD_REG H2C the on-chip fw
* replays, collapsing ~20 USB register round-trips into one bulk-OUT
* (~9 ms -> ~0.15 ms host-side). Default 1 (on); 0 forces the direct
* write-only path (self-paces the ~1.5 ms RF synth settle instead of
* returning before it). No rtw89 firmware channel-switch H2C exists, so
* this offloads the raw register writes, not a fw switch primitive. The
* 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_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
26 changes: 26 additions & 0 deletions src/kestrel/CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -112,6 +112,32 @@ here (H2C 0x1D is 11ac HalMAC; rtw89 has only `SCAN_OFFLOAD` + MCC). The
dwell-1 / N-channel data plane (`examples/dwelltx`) runs on Kestrel at ~30 ms
slots (soak: 2000 hops, zero wrong-channel, ~97 % delivery).

### Firmware IO-offload hop (`DEVOURER_KFR_OFLD`, default on)

`fast_retune_ofld_8852b` ships the *entire* same-sub-band hop — the six RF18/
0xcf channel-set writes, `halbb_bb_reset_all_8852b`, and the fixed-dBm TX-power
target — as ONE `FW_OFLD`/`CMD_OFLD_REG` H2C (mac_ax `fwofld.c`) the on-chip
firmware replays locally. `KestrelFw::reg_write_ofld` packs the 16-byte
`fwcmd_cmd_ofld` command buffer (src RF a-die / BB / MAC, `WRITE`/`DELAY`
types, LC on the last, `cmd_num` running); the firmware applies each masked
write as `(reg & ~mask) | ((val << ctz(mask)) & mask)` — so BB/MAC entries
carry the **raw** field value (pre-shifting double-shifts and mis-tunes),
while RF writes use the full `MASKRF` (shift 0). This collapses ~20 per-hop USB
register round-trips into a single bulk-OUT: host-side hop cost **~9.3 ms →
~0.15 ms (~45×)**, zero wrong-channel over a 6000-hop soak.

The offload does not speed up the RF synth — it only frees the host. The
direct path's ~5 ms of `bb_reset` USB round-trips incidentally covered the VCO
settle; collapsing them to ~0.15 ms exposes the true **~1.5 ms synth-settle
floor** (below it, a frame airs mid-retune → wrong-channel; ≥1.5 ms is clean,
`DEVOURER_DWELL_SETTLE_US` on the demo). Net time-to-usable-channel ~1.7 ms
(~5.5×), with the host free to prep the next frame / service RX during the
settle. The hop returns before the synth settles, so this changes the
`FastRetune` timing contract — the caller honours the settle (the demos'
admission window). Default on; `DEVOURER_KFR_OFLD=0` forces the self-pacing
direct path (for A/B or a suspect chip). A bucket crossing (verified synth
relock) and the 8852C fall through to the direct steps.

## TX power

A fixed BB dBm (`halbb_set_txpwr_dbm`, default 20 dBm, `DEVOURER_TX_PWR`
Expand Down
114 changes: 101 additions & 13 deletions src/kestrel/HalKestrel.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1744,9 +1744,12 @@ bool HalKestrel::fast_rf_channel_8852b(uint8_t channel, bool relock) {
rf_wrf(0, 0xd3, 1u << 8, 0x0);
rf_wrf(0, 0xb1, r::MASKRF, b1);
fast_lck_check_8852b();
} else {
rf_wrf(0, 0x18, r::MASKRF, rf18_dav);
cf_toggle(0, _kfr_cf_a);
rf_wrf(1, 0x18, r::MASKRF, rf18_dav);
cf_toggle(1, _kfr_cf_b);
return true;
}
rf_wrf(0, 0x18, r::MASKRF, rf18_dav);
cf_toggle(0, _kfr_cf_a);
/* path-B DAV. The DDV (d-die, 0x10018) window is not populated on the
* single-die 8852B — skipping it costs no channel accuracy (soak-confirmed)
Expand All @@ -1756,6 +1759,84 @@ bool HalKestrel::fast_rf_channel_8852b(uint8_t channel, bool relock) {
return true;
}

namespace {
/* count-trailing-zeros for the RMW field shift. */
inline uint8_t ctz32(uint32_t m) {
uint8_t s = 0;
while (m && !(m & 1)) {
m >>= 1;
++s;
}
return s;
}
} // namespace

bool HalKestrel::fast_retune_ofld_8852b(uint8_t channel) {
if (_variant != ChipVariant::C8852B || !_halbb_ctx)
return false;
/* Prime the a-die RF18/0xcf dwords once per epoch — the only reads on this
* path; everything after is composed write-only and shipped as one H2C. */
if (!_kfr_primed) {
_kfr_rf18_dav = rf_rrf(0, 0x18, r::MASKRF);
_kfr_cf_a = rf_rrf(0, 0xcf, r::MASKRF);
_kfr_cf_b = rf_rrf(1, 0xcf, r::MASKRF);
_kfr_primed = true;
}
uint32_t rf18_dav = (_kfr_rf18_dav & ~0x3e3ffu) | channel;
if (channel > 14)
rf18_dav |= (1u << 16) | (1u << 8);
rf18_dav = (rf18_dav & 0xf0fffu) | (1u << 12);
_kfr_rf18_dav = rf18_dav;

using O = KestrelFw::OfldWrite;
const uint8_t RF = 1, BB = 0, MAC = 2, DLY = 2;
/* The fw applies a masked write as (reg & ~mask) | ((val << ctz(mask)) &
* mask) — it does the field shift itself (matching halbb_fw_set_reg, which
* forwards the RAW field value). So pass the unshifted field value, NOT
* pre-positioned. (RF writes use the full MASKRF, shift 0, so they are
* identical either way.) */
auto bb = [](uint16_t off, uint32_t msk, uint32_t val) {
return O{BB, 0, off, val, msk, 0};
};
const int16_t dbm_q2 = static_cast<int16_t>(_txpwr_dbm_q2 + _txpwr_offset_qdb);
/* One batch = the whole same-sub-band hop the fw replays locally:
* - RF18 (path A/B, a-die) + 0xcf clear/set latch toggle (channel set),
* - halbb_bb_reset_all_8852b (BB RMWs the fw applies under mask on-chip; the
* 1/2 us settles become DELAY ops; 0xce40 phy-sts stop/start is a MAC RMW),
* - halbb_set_txpwr_dbm_8852b (fixed-dBm arm + s(9,2) target).
* DDV (d-die 0x10018) is unpopulated on the single-die 8852B, so path-B is
* a-die too. This is the 8852B-only non-relock path; a sub-band crossing
* keeps the direct synth-relock (verified lock) path. */
const O cmds[] = {
{RF, 0, 0x18, rf18_dav, r::MASKRF, 0},
{RF, 0, 0xcf, _kfr_cf_a & ~1u, r::MASKRF, 0},
{RF, 0, 0xcf, _kfr_cf_a | 1u, r::MASKRF, 0},
{RF, 1, 0x18, rf18_dav, r::MASKRF, 0},
{RF, 1, 0xcf, _kfr_cf_b & ~1u, r::MASKRF, 0},
{RF, 1, 0xcf, _kfr_cf_b | 1u, r::MASKRF, 0},
/* bb_reset_all (8852B) */
bb(0x2344, 1u << 31, 1),
bb(0xc3c, 1u << 9, 1),
bb(0x1200, 0x7u << 28, 0x7),
bb(0x3200, 0x7u << 28, 0x7),
{0, 0, 0, 1, 0, DLY}, /* delay 1us */
{MAC, 0, 0xce40, 0u, 0x1u, 0}, /* stop phy-sts (clear bit0) */
{0, 0, 0, 2, 0, DLY}, /* delay 2us */
bb(0x704, 1u << 1, 1),
bb(0x704, 1u << 1, 0),
bb(0x1200, 0x7u << 28, 0x0),
bb(0x3200, 0x7u << 28, 0x0),
bb(0x704, 1u << 1, 1),
{MAC, 0, 0xce40, 0x1u, 0x1u, 0}, /* start phy-sts (set bit0) */
bb(0x2344, 1u << 31, 0),
bb(0xc3c, 1u << 9, 0),
/* set_txpwr_dbm */
bb(0x09a4, 1u << 16, 1),
bb(0x4594, 0x7fc00000u, static_cast<uint32_t>(dbm_q2) & 0x1ffu),
};
return _fw.reg_write_ofld(cmds, sizeof(cmds) / sizeof(cmds[0]));
}

void HalKestrel::vnd_bb_set_gain(uint8_t channel, uint8_t band_type) {
if (_halbb_ctx)
kestrel_halbb_set_gain(_halbb_ctx, channel, band_type);
Expand Down Expand Up @@ -1799,15 +1880,6 @@ HalKestrel::~HalKestrel() {
}

namespace {
/* count-trailing-zeros for the RMW field shift. */
inline uint8_t ctz32(uint32_t m) {
uint8_t s = 0;
while (m && !(m & 1)) {
m >>= 1;
++s;
}
return s;
}
constexpr uint32_t BB_WIN = 0x10000; /* bb0_cr_offset (wIndex=1) */
} /* namespace */

Expand Down Expand Up @@ -1976,6 +2048,19 @@ void HalKestrel::fast_retune(uint8_t channel) {
* ctl_ch's per-hop RF18/0xcf reads and, within a sub-band, the ~13 ms synth
* LCK poll. Falls back to the vendored tune on the 8852C or a cold cache;
* the synth-lock diagnostic reads are skipped either way (diag=false). */
using kfr_clock = std::chrono::steady_clock;
auto t0 = kfr_clock::now();
/* Fully-offloaded same-sub-band hop: the whole RF+bb_reset+txpwr write burst
* ships as ONE FW_OFLD H2C the fw replays on-chip, collapsing ~20 USB
* register round-trips into a single bulk-OUT. Only the 8852B non-relock
* path; a bucket crossing (or the 8852C) falls through to the direct steps. */
if (_kfr_ofld && !bucket_changed && fast_retune_ofld_8852b(channel)) {
_logger->debug("Kestrel FastRetune: ch{} (offloaded) {}us", channel,
std::chrono::duration_cast<std::chrono::microseconds>(
kfr_clock::now() - t0)
.count());
return;
}
if (!fast_rf_channel_8852b(channel, /*relock=*/bucket_changed))
vnd_rf_tune(band_type, channel, CHANNEL_WIDTH_20, /*diag=*/false);
if (bucket_changed) {
Expand All @@ -1984,8 +2069,11 @@ void HalKestrel::fast_retune(uint8_t channel) {
}
bb_reset_all();
set_txpwr_dbm(static_cast<int16_t>(_txpwr_dbm_q2 + _txpwr_offset_qdb));
_logger->debug("Kestrel FastRetune: ch{} ({})", channel,
band_type == 0 ? "2.4G" : band_type == 2 ? "6G" : "5G");
_logger->debug("Kestrel FastRetune: ch{} ({}) {}us", channel,
band_type == 0 ? "2.4G" : band_type == 2 ? "6G" : "5G",
std::chrono::duration_cast<std::chrono::microseconds>(
kfr_clock::now() - t0)
.count());
}

void HalKestrel::fast_set_bw(ChannelWidth_t bw) {
Expand Down
6 changes: 6 additions & 0 deletions src/kestrel/HalKestrel.h
Original file line number Diff line number Diff line change
Expand Up @@ -564,16 +564,22 @@ class HalKestrel {
* Returns false (caller falls back to vnd_rf_tune) on the 8852C or a cold
* cache. Invalidated by every full set_channel. */
bool fast_rf_channel_8852b(uint8_t channel, bool relock);
/* Whole same-sub-band hop (RF18/0xcf + bb_reset + fixed TX power) as ONE
* FW_OFLD H2C the fw replays on-chip — collapses the ~20 per-hop USB
* register round-trips into a single bulk-OUT. 8852B, non-relock only. */
bool fast_retune_ofld_8852b(uint8_t channel);
void fast_lck_check_8852b(); /* halrf_lck_check_8852b (synth-lock verify) */
bool _kfr_primed = false;
uint32_t _kfr_rf18_dav = 0; /* a-die RF18 compose base */
uint32_t _kfr_cf_a = 0, _kfr_cf_b = 0; /* RF 0xcf full value/path */
bool _kfr_ofld = false; /* batch the same-band hop writes via fw IO-offload */
struct kestrel_halrf_ctx *_halrf_ctx = nullptr;
bool _halrf_rfk_inited = false; /* NCTL engine loaded (one-time, lazy) */

public:
~HalKestrel();
void set_cca_on(bool on) { _cca_on = on; }
void set_kfr_ofld(bool on) { _kfr_ofld = on; }
};

} /* namespace kestrel */
Expand Down
33 changes: 33 additions & 0 deletions src/kestrel/KestrelFw.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -568,6 +568,39 @@ bool KestrelFw::fw_send_beacon(const uint8_t *body, uint32_t len, uint8_t macid,
return ok;
}

bool KestrelFw::reg_write_ofld(const OfldWrite *cmds, size_t n) {
/* Build the fwofld.c command buffer verbatim: N contiguous 16-byte
* fwcmd_cmd_ofld entries, cmd_num running 0..N-1, LC on the last, then send
* it as ONE FW_OFLD/CMD_OFLD_REG H2C. The fw replays the write-list on-chip.
* v0 layout: dword0 = src|type|lc|path|cmd_num|offset, dword1 = id|base_offset
* (0 a-die / 1 d-die for RF, else offset[31:16]), dword2 = value,
* dword3 = mask. type is fixed WRITE (masked RMW on-chip). */
if (n == 0 || n > 0x7f)
return false;
std::vector<uint8_t> buf(n * 16, 0);
for (size_t i = 0; i < n; i++) {
const OfldWrite &c = cmds[i];
const uint32_t base = (c.src == 3) ? 1u /* RF d-die */
: (c.src == 1) ? 0u /* RF a-die */
: ((uint32_t)c.offset >> 16) & 0xffff;
const uint32_t d0 =
((uint32_t)(c.src & 0x3)) |
((uint32_t)(c.type & 0x3) << 2) /* WRITE=0 / DELAY=2 */ |
((i + 1 == n) ? (1u << 4) : 0u) /* LC on last */ |
((uint32_t)(c.path & 0x3) << 5) |
((uint32_t)(i & 0x7f) << 8) /* cmd_num */ |
((uint32_t)(c.offset & 0xffff) << 16);
const uint32_t d1 = (0u & 0xffff) /* id */ | (base << 16);
put_le32(buf.data() + i * 16 + 0, d0);
put_le32(buf.data() + i * 16 + 4, d1);
put_le32(buf.data() + i * 16 + 8, c.value);
put_le32(buf.data() + i * 16 + 12, c.mask);
}
return send_h2c_cmd(r::FWCMD_H2C_CAT_MAC, r::FWCMD_H2C_CL_FW_OFLD,
r::FWCMD_H2C_FUNC_CMD_OFLD_REG, buf.data(),
static_cast<uint32_t>(buf.size()));
}

bool KestrelFw::send_h2c_cmd(uint8_t cat, uint8_t h2c_class, uint8_t func,
const uint8_t *content, uint32_t len) {
/* Serialize: this mutates the shared _txbuf scratch + the _h2c_seq counter and
Expand Down
17 changes: 17 additions & 0 deletions src/kestrel/KestrelFw.h
Original file line number Diff line number Diff line change
Expand Up @@ -143,6 +143,23 @@ class KestrelFw {
bool fw_upd_cctl_bf(uint8_t macid, uint8_t addr_cam_idx,
const devourer::StaBfCaps &bf);

/* Firmware register IO-offload (mac_ax fwofld.c): batch a write-list into one
* FW_OFLD H2C (func CMD_OFLD_REG) that the on-chip fw replays locally, so N
* register writes cost ONE USB round-trip instead of N. Each entry is a
* masked write; src selects the target space (RF a-die/d-die, BB, MAC), path
* the RF chain. The last command carries the LC (list-complete) bit. Used by
* the FastRetune hot path to collapse the per-hop RF18/0xcf write burst.
* Fire-and-forget: the C2H IO_OFLD_RESULT is not awaited (write-only batch).*/
struct OfldWrite {
uint8_t src; /* RTW_MAC_{RF=1, RF_DDIE=3, BB=0, MAC=2}_CMD_OFLD */
uint8_t path; /* RF path (0=A, 1=B); 0 for BB/MAC */
uint16_t offset; /* register / RF address */
uint32_t value; /* pre-shifted into the mask field (DELAY: microseconds) */
uint32_t mask;
uint8_t type = 0; /* RTW_MAC_{WRITE=0, DELAY=2}_OFLD */
};
bool reg_write_ofld(const OfldWrite *cmds, size_t n);

private:
/* Generic H2C over CH12: [WD 24B][fwcmd_hdr 8B][content]. */
bool send_h2c_cmd(uint8_t cat, uint8_t h2c_class, uint8_t func,
Expand Down
3 changes: 3 additions & 0 deletions src/kestrel/RtlKestrelDevice.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,9 @@ RtlKestrelDevice::RtlKestrelDevice(RtlAdapter device, Logger_t logger,
else
_logger->info("Kestrel: die-id 0x{:02x} ({}), cut {}", info.die_id,
die_name(info.die_id), info.cut);
/* FastRetune firmware IO-offload (8852B same-sub-band hop). Armed here so it
* covers every bring-up path; a no-op on the 8852C / relock path. */
_hal.set_kfr_ofld(_cfg.tuning.kestrel_fastretune_ofld != 0);
/* DEVOURER_TX_PWR on Kestrel = the fixed BB TX power in whole dBm (distinct
* from the Jaguar2 TXAGC-index meaning). Applied at every set_channel. */
if (_cfg.tx.power_index.has_value())
Expand Down
4 changes: 4 additions & 0 deletions tests/dwell1_ab.py
Original file line number Diff line number Diff line change
Expand Up @@ -251,6 +251,8 @@ def spawn_dwelltx(dut, chans, a, out_dir):
"DEVOURER_DWELL_SLOTS": str(a.slots + 20), # +warmup
"DEVOURER_HOP_FAST": str(a.hop_fast),
})
if a.kfr_ofld:
env["DEVOURER_KFR_OFLD"] = str(a.kfr_ofld)
if a.seed:
env["DEVOURER_HOP_SEED"] = a.seed
if a.tx_pwr:
Expand Down Expand Up @@ -312,6 +314,8 @@ def main():
ap.add_argument("--slots", type=int, default=5000)
ap.add_argument("--fw", type=int, default=1)
ap.add_argument("--hop-fast", type=int, default=1)
ap.add_argument("--kfr-ofld", type=int, default=0,
help="Kestrel FastRetune fw IO-offload (DEVOURER_KFR_OFLD)")
ap.add_argument("--late-us", type=int, default=0)
ap.add_argument("--settle-us", type=int, default=0,
help="post-switch admission delay; fw switch needs "
Expand Down
Loading
Loading