On Wed, Dec 11, 2019 at 08:03:59PM +0100, Stefan Sperling wrote:
> Back in February we disabled iwm firmware Tx retries at lower rates.
> This improved MiRA's Tx rate selection because bad rates do actually
> look bad, rather than being compensated for by firmware retries.
> The result was increased Tx throughput. The original discussion is here:
> https://marc.info/?l=openbsd-tech&m=155067514103877&w=2
>
> This approach has a drawback: If a given Tx rate is bad we will lose all
> frames sent at that rate. This results in user-visiable packet loss.
> The diff below attempts to reduce such user-visible packet loss by
> allowing the firmware to retry at lower rates again, but only if MiRA
> is not currently probing for a new rate.
>
> I hope this will reduce observable packet loss while still selecting
> the best Tx rate. Lightly tested on 8265. More tests welcome.
The previous version of this diff had a bug where it could potentially
put a rate which is currently being probed into the firmware's rate table.
Fixed in this version which always uses the best rate determined by the
most recent probe.
diff refs/heads/master refs/heads/probing-fixed-rate
blob - dff9f8047f5c0300a4ddde4d9b090f58c28dd8f9 (mode 644)
blob + 516c45458a8a22650715c95a7ba75f8c495c3e86 (mode 600)
--- sys/dev/pci/if_iwm.c
+++ sys/dev/pci/if_iwm.c
@@ -453,6 +453,8 @@ int iwm_run(struct iwm_softc *);
int iwm_run_stop(struct iwm_softc *);
struct ieee80211_node *iwm_node_alloc(struct ieee80211com *);
void iwm_calib_timeout(void *);
+void iwm_setrates_task(void *);
+void iwm_setrates(struct iwm_node *);
int iwm_media_change(struct ifnet *);
void iwm_newstate_task(void *);
int iwm_newstate(struct ieee80211com *, enum ieee80211_state, int);
@@ -4161,13 +4163,24 @@ iwm_rx_tx_cmd_single(struct iwm_softc *sc, struct iwm_
if (txfail)
in->in_mn.txfail += tx_resp->frame_count;
if (ic->ic_state == IEEE80211_S_RUN && !in->ht_force_cck) {
- int otxmcs = ni->ni_txmcs;
+ int best_mcs;
ieee80211_mira_choose(&in->in_mn, ic, &in->in_ni);
+ /*
+ * If MiRA has chosen a new TX rate we must update
+ * the firwmare's LQ rate table from process context.
+ * ni_txmcs may change again before the task runs so
+ * cache the chosen rate in the iwm_node structure.
+ */
+ best_mcs = ieee80211_mira_get_best_mcs(&in->in_mn);
+ if (best_mcs != in->chosen_txmcs) {
+ in->chosen_txmcs = best_mcs;
+ iwm_add_task(sc, systq, &sc->setrates_task);
+ }
/* Fall back to CCK rates if MCS 0 is failing. */
if (txfail && IEEE80211_IS_CHAN_2GHZ(ni->ni_chan) &&
- otxmcs == 0 && ni->ni_txmcs == 0)
+ in->chosen_txmcs == 0 && best_mcs == 0)
iwm_enable_ht_cck_fallback(sc, in);
}
}
@@ -4693,14 +4706,35 @@ iwm_tx_fill_cmd(struct iwm_softc *sc, struct iwm_node
ridx = sc->sc_fixed_ridx;
} else if (ic->ic_fixed_rate != -1) {
ridx = sc->sc_fixed_ridx;
- } else if ((ni->ni_flags & IEEE80211_NODE_HT) && !in->ht_force_cck) {
+ } else if ((ni->ni_flags & IEEE80211_NODE_HT) && !in->ht_force_cck &&
+ ieee80211_mira_is_probing(&in->in_mn)) {
+ /* Keep Tx rate constant while mira is probing. */
ridx = iwm_mcs2ridx[ni->ni_txmcs];
- } else {
+ } else if ((ni->ni_flags & IEEE80211_NODE_HT) && in->ht_force_cck) {
uint8_t rval;
rval = (rs->rs_rates[ni->ni_txrate] & IEEE80211_RATE_VAL);
ridx = iwm_rval2ridx(rval);
if (ridx < min_ridx)
ridx = min_ridx;
+ } else {
+ int i;
+ /* Use firmware rateset retry table. */
+ tx->initial_rate_index = 0;
+ tx->tx_flags |= htole32(IWM_TX_CMD_FLG_STA_RATE);
+ if (ni->ni_flags & IEEE80211_NODE_HT) {
+ ridx = iwm_mcs2ridx[ni->ni_txmcs];
+ return &iwm_rates[ridx];
+ }
+ ridx = (IEEE80211_IS_CHAN_5GHZ(ni->ni_chan)) ?
+ IWM_RIDX_OFDM : IWM_RIDX_CCK;
+ for (i = 0; i < ni->ni_rates.rs_nrates; i++) {
+ if (iwm_rates[i].rate == (ni->ni_txrate &
+ IEEE80211_RATE_VAL)) {
+ ridx = i;
+ break;
+ }
+ }
+ return &iwm_rates[ridx];
}
rinfo = &iwm_rates[ridx];
@@ -6631,6 +6665,9 @@ iwm_run(struct iwm_softc *sc)
/* Start at lowest available bit-rate, AMRR will raise. */
in->in_ni.ni_txrate = 0;
in->in_ni.ni_txmcs = 0;
+ in->chosen_txrate = 0;
+ in->chosen_txmcs = 0;
+ iwm_setrates(in);
timeout_add_msec(&sc->sc_calib_to, 500);
iwm_led_enable(sc);
@@ -6703,6 +6740,16 @@ iwm_calib_timeout(void *arg)
((ni->ni_flags & IEEE80211_NODE_HT) == 0 || in->ht_force_cck) &&
ic->ic_opmode == IEEE80211_M_STA && ic->ic_bss) {
ieee80211_amrr_choose(&sc->sc_amrr, &in->in_ni, &in->in_amn);
+ /*
+ * If AMRR has chosen a new TX rate we must update
+ * the firwmare's LQ rate table from process context.
+ * ni_txrate may change again before the task runs so
+ * cache the chosen rate in the iwm_node structure.
+ */
+ if (ni->ni_txrate != in->chosen_txrate) {
+ in->chosen_txrate = ni->ni_txrate;
+ iwm_add_task(sc, systq, &sc->setrates_task);
+ }
if (in->ht_force_cck) {
struct ieee80211_rateset *rs = &ni->ni_rates;
uint8_t rv;
@@ -6717,6 +6764,139 @@ iwm_calib_timeout(void *arg)
timeout_add_msec(&sc->sc_calib_to, 500);
}
+void
+iwm_setrates_task(void *arg)
+{
+ struct iwm_softc *sc = arg;
+ struct ieee80211com *ic = &sc->sc_ic;
+ struct iwm_node *in = (struct iwm_node *)ic->ic_bss;
+ int s = splnet();
+
+ if (sc->sc_flags & IWM_FLAG_SHUTDOWN) {
+ refcnt_rele_wake(&sc->task_refs);
+ splx(s);
+ return;
+ }
+
+ /* Update rates table based on new TX rate determined by AMRR. */
+ iwm_setrates(in);
+ refcnt_rele_wake(&sc->task_refs);
+ splx(s);
+}
+
+void
+iwm_setrates(struct iwm_node *in)
+{
+ struct ieee80211_node *ni = &in->in_ni;
+ struct ieee80211com *ic = ni->ni_ic;
+ struct iwm_softc *sc = IC2IFP(ic)->if_softc;
+ struct iwm_lq_cmd lqcmd;
+ struct ieee80211_rateset *rs = &ni->ni_rates;
+ int i, ridx, ridx_min, ridx_max, j, sgi_ok = 0, mimo, tab = 0;
+ struct iwm_host_cmd cmd = {
+ .id = IWM_LQ_CMD,
+ .len = { sizeof(lqcmd), },
+ };
+
+ memset(&lqcmd, 0, sizeof(lqcmd));
+ lqcmd.sta_id = IWM_STATION_ID;
+
+ if (ic->ic_flags & IEEE80211_F_USEPROT)
+ lqcmd.flags |= IWM_LQ_FLAG_USE_RTS_MSK;
+
+ if ((ni->ni_flags & IEEE80211_NODE_HT) &&
+ ieee80211_node_supports_ht_sgi20(ni)) {
+ ni->ni_flags |= IEEE80211_NODE_HT_SGI20;
+ sgi_ok = 1;
+ }
+
+ /*
+ * Fill the LQ rate selection table with legacy and/or HT rates
+ * in descending order, i.e. with the node's current TX rate first.
+ * In cases where throughput of an HT rate corresponds to a legacy
+ * rate it makes no sense to add both. We rely on the fact that
+ * iwm_rates is laid out such that equivalent HT/legacy rates share
+ * the same IWM_RATE_*_INDEX value. Also, rates not applicable to
+ * legacy/HT are assumed to be marked with an 'invalid' PLCP value.
+ */
+ j = 0;
+ ridx_min = iwm_rval2ridx(ieee80211_min_basic_rate(ic));
+ mimo = iwm_is_mimo_mcs(in->chosen_txmcs);
+ ridx_max = (mimo ? IWM_RIDX_MAX : IWM_LAST_HT_SISO_RATE);
+ for (ridx = ridx_max; ridx >= ridx_min; ridx--) {
+ uint8_t plcp = iwm_rates[ridx].plcp;
+ uint8_t ht_plcp = iwm_rates[ridx].ht_plcp;
+
+ if (j >= nitems(lqcmd.rs_table))
+ break;
+ tab = 0;
+ if (ni->ni_flags & IEEE80211_NODE_HT) {
+ if (ht_plcp == IWM_RATE_HT_SISO_MCS_INV_PLCP)
+ continue;
+ /* Do not mix SISO and MIMO HT rates. */
+ if ((mimo && !iwm_is_mimo_ht_plcp(ht_plcp)) ||
+ (!mimo && iwm_is_mimo_ht_plcp(ht_plcp)))
+ continue;
+ for (i = in->chosen_txmcs; i >= 0; i--) {
+ if (isclr(ni->ni_rxmcs, i))
+ continue;
+ if (ridx == iwm_mcs2ridx[i]) {
+ tab = ht_plcp;
+ tab |= IWM_RATE_MCS_HT_MSK;
+ if (sgi_ok)
+ tab |= IWM_RATE_MCS_SGI_MSK;
+ break;
+ }
+ }
+ } else if (plcp != IWM_RATE_INVM_PLCP) {
+ for (i = in->chosen_txrate; i >= 0; i--) {
+ if (iwm_rates[ridx].rate == (rs->rs_rates[i] &
+ IEEE80211_RATE_VAL)) {
+ tab = plcp;
+ break;
+ }
+ }
+ }
+
+ if (tab == 0)
+ continue;
+
+ if (iwm_is_mimo_ht_plcp(ht_plcp))
+ tab |= IWM_RATE_MCS_ANT_AB_MSK;
+ else
+ tab |= IWM_RATE_MCS_ANT_A_MSK;
+
+ if (IWM_RIDX_IS_CCK(ridx))
+ tab |= IWM_RATE_MCS_CCK_MSK;
+ lqcmd.rs_table[j++] = htole32(tab);
+ }
+
+ lqcmd.mimo_delim = (mimo ? j : 0);
+
+ /* Fill the rest with the lowest possible rate */
+ while (j < nitems(lqcmd.rs_table)) {
+ tab = iwm_rates[ridx_min].plcp;
+ if (IWM_RIDX_IS_CCK(ridx_min))
+ tab |= IWM_RATE_MCS_CCK_MSK;
+ tab |= IWM_RATE_MCS_ANT_A_MSK;
+ lqcmd.rs_table[j++] = htole32(tab);
+ }
+
+ lqcmd.single_stream_ant_msk = IWM_ANT_A;
+ lqcmd.dual_stream_ant_msk = IWM_ANT_AB;
+
+ lqcmd.agg_time_limit = htole16(4000); /* 4ms */
+ lqcmd.agg_disable_start_th = 3;
+#ifdef notyet
+ lqcmd.agg_frame_cnt_limit = 0x3f;
+#else
+ lqcmd.agg_frame_cnt_limit = 1; /* tx agg disabled */
+#endif
+
+ cmd.data[0] = &lqcmd;
+ iwm_send_cmd(sc, &cmd);
+}
+
int
iwm_media_change(struct ifnet *ifp)
{
@@ -6862,6 +7042,7 @@ iwm_newstate(struct ieee80211com *ic, enum ieee80211_s
ieee80211_mira_cancel_timeouts(&in->in_mn);
iwm_del_task(sc, systq, &sc->ba_task);
iwm_del_task(sc, systq, &sc->htprot_task);
+ iwm_del_task(sc, systq, &sc->setrates_task);
}
sc->ns_nstate = nstate;
@@ -7621,6 +7802,7 @@ iwm_stop(struct ifnet *ifp)
/* Cancel scheduled tasks and let any stale tasks finish up. */
task_del(systq, &sc->init_task);
iwm_del_task(sc, sc->sc_nswq, &sc->newstate_task);
+ iwm_del_task(sc, systq, &sc->setrates_task);
iwm_del_task(sc, systq, &sc->ba_task);
iwm_del_task(sc, systq, &sc->htprot_task);
KASSERT(sc->task_refs.refs >= 1);
@@ -9030,6 +9212,7 @@ iwm_attach(struct device *parent, struct device *self,
timeout_set(&sc->sc_led_blink_to, iwm_led_blink_timeout, sc);
task_set(&sc->init_task, iwm_init_task, sc);
task_set(&sc->newstate_task, iwm_newstate_task, sc);
+ task_set(&sc->setrates_task, iwm_setrates_task, sc);
task_set(&sc->ba_task, iwm_ba_task, sc);
task_set(&sc->htprot_task, iwm_htprot_task, sc);
blob - dd4c70febfbca68cd4208159c5212052e5a811fa
blob + 650e9a562c6b794551c5ef42872465548743d473
--- sys/dev/pci/if_iwmvar.h
+++ sys/dev/pci/if_iwmvar.h
@@ -374,6 +374,7 @@ struct iwm_softc {
struct task init_task; /* NB: not reference-counted */
struct refcnt task_refs;
struct task newstate_task;
+ struct task setrates_task;
enum ieee80211_state ns_nstate;
int ns_arg;
@@ -546,7 +547,9 @@ struct iwm_node {
uint16_t in_color;
struct ieee80211_amrr_node in_amn;
+ int chosen_txrate;
struct ieee80211_mira_node in_mn;
+ int chosen_txmcs;
/* Set in 11n mode if we don't receive ACKs for OFDM frames. */
int ht_force_cck;
blob - 4438af5f63e4868903a2a8b7cc71f89d1868eab0
blob + 81ca8350d8e7619236e1c7c23b7cae8d7d76a8d1
--- sys/net80211/ieee80211_mira.c
+++ sys/net80211/ieee80211_mira.c
@@ -1254,3 +1254,15 @@ ieee80211_mira_cancel_timeouts(struct ieee80211_mira_n
for (t = 0; t < nitems(mn->probe_to); t++)
timeout_del(&mn->probe_to[t]);
}
+
+int
+ieee80211_mira_is_probing(struct ieee80211_mira_node *mn)
+{
+ return mn->probing != IEEE80211_MIRA_NOT_PROBING;
+}
+
+int
+ieee80211_mira_get_best_mcs(struct ieee80211_mira_node *mn)
+{
+ return mn->best_mcs;
+}
blob - 9e6d5e7c3854bf22f877792fe87348b06dbe331d
blob + 2150fb511ae24789ebfcdc9916325635d9f40304
--- sys/net80211/ieee80211_mira.h
+++ sys/net80211/ieee80211_mira.h
@@ -111,4 +111,10 @@ void ieee80211_mira_cancel_timeouts(struct
ieee80211_m
int ieee80211_mira_get_rts_threshold(struct ieee80211_mira_node *,
struct ieee80211com *, struct ieee80211_node *, size_t);
+/* Indicate whether Tx rates are currently being probed. */
+int ieee80211_mira_is_probing(struct ieee80211_mira_node *);
+
+/* Return the best MCS determined by the most recent probe. */
+int ieee80211_mira_get_best_mcs(struct ieee80211_mira_node *);
+
#endif /* _NET80211_IEEE80211_MIRA_H_ */