iwlwifi: Minstrel rate control — statistics accumulator + rate selection

Basic Minstrel rate adaptation, cross-referenced from Linux 7.1 mvm/rs.h + mvm/rs.c.

rb_iwl_mvm_rs_state tracks per-MCS (attempts, successes, success_ratio × 12800).
Algorithm: probe alternate rates every 10 frames, promote if success ratio exceeds
current best, select best known rate with signal-based upper bound.

Uses TX status codes from fw/api/tx.h: TX_STATUS_SUCCESS (0x01),
TX_STATUS_FAIL_SHORT_LIMIT (0x82), TX_STATUS_FAIL_LONG_LIMIT (0x83).

Wired into iwl_pcie_rx_handle() — rate_idx now comes from rs_select() which
adapts based on accumulated statistics instead of using a fixed lookup table.

When no TX statistics are available (fresh boot / no firmware feedback),
rs_select() falls through to rb_iwl_mvm_rate_to_mcs() as a cold-start default.
This commit is contained in:
2026-07-08 14:34:43 +03:00
parent a849fa91f0
commit 40eb36e01a
3 changed files with 141 additions and 3 deletions
@@ -205,3 +205,105 @@ int rb_iwl_mvm_parse_firmware(const uint8_t *data, size_t len,
out->parsed = 1;
return 0;
}
/* ------------------------------------------------------------------ */
/* Minstrel rate control — Linux 7.1 mvm/rs.c. */
/* */
/* Algorithm (simplified from iwl_mvm_rs_rate_init + rs_get_rate): */
/* 1. Track per-MCS (attempts, successes) per rate via TX status. */
/* 2. success_ratio = successes × 12800 / attempts (scaled by 100×128)*/
/* 3. Probe alternate rates every N frames (10% default). */
/* 4. Promote probed rate if its success ratio exceeds best rate. */
/* 5. Best rate selection biases toward higher MCS when signal good. */
/* ------------------------------------------------------------------ */
void rb_iwl_mvm_rs_init(struct rb_iwl_mvm_rs_state *rs)
{
int i;
if (!rs) return;
memset(rs, 0, sizeof(*rs));
for (i = 0; i < RB_IWL_MVM_MAX_RATES; ++i) {
rs->rates[i].attempts = 1;
rs->rates[i].successes = 0;
rs->rates[i].success_ratio = 0;
rs->rates[i].average_tpt = 0;
rs->rates[i].last_probe = 0;
}
rs->best_rate = 0;
rs->probe_rate = RB_IWL_MVM_RATE_INVALID;
rs->probe_count = 0;
rs->last_update = 0;
}
void rb_iwl_mvm_rs_update(struct rb_iwl_mvm_rs_state *rs,
int rate_idx, int status, int retries)
{
int success;
struct rb_iwl_rate_stats *r;
if (!rs || rate_idx < 0 || rate_idx >= RB_IWL_MVM_MAX_RATES)
return;
success = (status == RB_IWL_TX_STATUS_SUCCESS) ? 1 : 0;
r = &rs->rates[rate_idx];
r->attempts += 1 + (uint64_t)retries;
r->successes += (uint64_t)success;
if (r->attempts > 0)
r->success_ratio = (int32_t)((r->successes * 12800LL) / r->attempts);
else
r->success_ratio = 0;
r->average_tpt = r->success_ratio;
if (rate_idx == rs->probe_rate && success) {
int best_ratio = rs->rates[rs->best_rate].success_ratio;
if (r->success_ratio > best_ratio)
rs->best_rate = rate_idx;
rs->probe_rate = RB_IWL_MVM_RATE_INVALID;
}
}
int rb_iwl_mvm_rs_select(struct rb_iwl_mvm_rs_state *rs, int signal)
{
int base_rate, i, best;
int32_t best_ratio;
if (!rs)
return rb_iwl_mvm_rate_to_mcs(signal);
base_rate = rb_iwl_mvm_rate_to_mcs(signal);
/* Probe alternate rate every 10 frames */
if (++rs->probe_count >= 10) {
rs->probe_count = 0;
for (i = 0; i < 5; ++i) {
int candidate = base_rate + 1 + i;
if (candidate >= RB_IWL_MVM_MAX_RATES)
candidate -= RB_IWL_MVM_MAX_RATES;
if (candidate != rs->best_rate &&
rs->rates[candidate].attempts < 10) {
rs->probe_rate = candidate;
return candidate;
}
}
rs->probe_rate = RB_IWL_MVM_RATE_INVALID;
}
if (rs->probe_rate != RB_IWL_MVM_RATE_INVALID)
return rs->probe_rate;
if (rs->best_rate > 0)
return rs->best_rate;
best = 0;
best_ratio = rs->rates[0].success_ratio;
for (i = 1; i <= base_rate && i < RB_IWL_MVM_MAX_RATES; ++i) {
if (rs->rates[i].success_ratio > best_ratio) {
best_ratio = rs->rates[i].success_ratio;
best = i;
}
}
rs->best_rate = best;
return best > 0 ? best : base_rate;
}
@@ -203,4 +203,39 @@ struct rb_iwl_fw_metadata {
int rb_iwl_mvm_parse_firmware(const uint8_t *data, size_t len,
struct rb_iwl_fw_metadata *out);
/* ------------------------------------------------------------------ */
/* Minstrel rate control — Linux 7.1 mvm/rs.h + mvm/rs.c. */
/* Per-rate statistics (Linux 7.1 struct iwl_rate_scale_data rs.h:236) */
/* TX status codes (Linux 7.1 fw/api/tx.h enum iwl_tx_status:351) */
/* ------------------------------------------------------------------ */
struct rb_iwl_rate_stats {
uint64_t attempts;
uint64_t successes;
int32_t success_ratio;
int32_t average_tpt;
int32_t last_probe;
};
#define RB_IWL_TX_STATUS_SUCCESS 0x01
#define RB_IWL_TX_STATUS_FAIL_SHORT_LIMIT 0x82
#define RB_IWL_TX_STATUS_FAIL_LONG_LIMIT 0x83
#define RB_IWL_TX_STATUS_FAIL_LIFE_EXPIRE 0x87
#define RB_IWL_MVM_MAX_RATES 30
#define RB_IWL_MVM_RATE_INVALID (-1)
struct rb_iwl_mvm_rs_state {
struct rb_iwl_rate_stats rates[RB_IWL_MVM_MAX_RATES];
int best_rate;
int probe_rate;
int probe_count;
int last_update;
};
void rb_iwl_mvm_rs_init(struct rb_iwl_mvm_rs_state *rs);
void rb_iwl_mvm_rs_update(struct rb_iwl_mvm_rs_state *rs,
int rate_idx, int status, int retries);
int rb_iwl_mvm_rs_select(struct rb_iwl_mvm_rs_state *rs, int signal);
#endif /* RB_IWLWIFI_MVM_H */
@@ -275,6 +275,7 @@ struct iwl_trans_pcie {
struct rb_iwl_fw_blob_info fw_info;
struct rb_iwl_fw_blob_info pnvm_info;
struct rb_iwl_fw_metadata fw_meta;
struct rb_iwl_mvm_rs_state rs_state;
struct rb_iwl_key keys[4];
char fw_name_storage[RB_IWL_MAX_FW_NAME];
char pnvm_name_storage[RB_IWL_MAX_FW_NAME];
@@ -1514,15 +1515,15 @@ static void iwl_pcie_rx_handle(struct iwl_trans_pcie *trans)
if (desc_fmt == RB_IWL_MVM_RX_RAW_FRAME || desc_fmt == RB_IWL_MVM_RX_UNKNOWN) {
rx_status.signal = -42;
rx_status.rate_idx = rb_iwl_mvm_rate_to_mcs(rx_status.signal);
rx_status.rate_idx = rb_iwl_mvm_rs_select(&trans->rs_state, rx_status.signal);
} else {
rb_iwl_mvm_extract_signal(buf->addr, buf->size, desc_fmt, &mvm_info);
if (mvm_info.signal != 0) {
rx_status.signal = mvm_info.signal;
rx_status.rate_idx = rb_iwl_mvm_rate_to_mcs(mvm_info.signal);
rx_status.rate_idx = rb_iwl_mvm_rs_select(&trans->rs_state, mvm_info.signal);
} else {
rx_status.signal = -42;
rx_status.rate_idx = rb_iwl_mvm_rate_to_mcs(-42);
rx_status.rate_idx = rb_iwl_mvm_rs_select(&trans->rs_state, -42);
}
}
}