diff options
author | Stefan Sperling <stsp@cvs.openbsd.org> | 2022-03-19 10:25:10 +0000 |
---|---|---|
committer | Stefan Sperling <stsp@cvs.openbsd.org> | 2022-03-19 10:25:10 +0000 |
commit | c59ec59ce25930e1f8e6488ac0ab8a7f25870ee9 (patch) | |
tree | 19102aaf04180bf2a9d4b926ad59645d8cee8e4d /sys/net80211 | |
parent | 08c7eee9fed83963ae843c38623faf89cf804d82 (diff) |
Add 11ac/VHT Tx rate adaptation support to net80211.
Based on our existing RA module for 11n.
The main difference is in dealing with 11ac-specific ratesets.
Tx rate selection heuristics remain identical.
Only supports 80MHz channels, for now. 160MHz is left for future work.
ok sthen@
Diffstat (limited to 'sys/net80211')
-rw-r--r-- | sys/net80211/ieee80211.c | 38 | ||||
-rw-r--r-- | sys/net80211/ieee80211_node.h | 29 | ||||
-rw-r--r-- | sys/net80211/ieee80211_ra_vht.c | 756 | ||||
-rw-r--r-- | sys/net80211/ieee80211_ra_vht.h | 91 |
4 files changed, 899 insertions, 15 deletions
diff --git a/sys/net80211/ieee80211.c b/sys/net80211/ieee80211.c index fcd76377267..d2e876c03a1 100644 --- a/sys/net80211/ieee80211.c +++ b/sys/net80211/ieee80211.c @@ -1,4 +1,4 @@ -/* $OpenBSD: ieee80211.c,v 1.87 2022/03/14 15:07:24 stsp Exp $ */ +/* $OpenBSD: ieee80211.c,v 1.88 2022/03/19 10:25:09 stsp Exp $ */ /* $NetBSD: ieee80211.c,v 1.19 2004/06/06 05:45:29 dyoung Exp $ */ /*- @@ -892,40 +892,52 @@ const struct ieee80211_ht_rateset ieee80211_std_ratesets_11n[] = { const struct ieee80211_vht_rateset ieee80211_std_ratesets_11ac[] = { /* MCS 0-8 (MCS 9 N/A), 1 SS, 20MHz channel, no SGI */ - { 9, { 13, 26, 39, 52, 78, 104, 117, 130, 156 }, 1, 0 }, + { 0, 9, { 13, 26, 39, 52, 78, 104, 117, 130, 156 }, + 1, 0, 0, 0 }, /* MCS 0-8 (MCS 9 N/A), 1 SS, 20MHz channel, SGI */ - { 9, { 14, 29, 43, 58, 87, 116, 130, 144, 174 }, 1, 1 }, + { 1, 9, { 14, 29, 43, 58, 87, 116, 130, 144, 174 }, + 1, 0, 0, 1 }, /* MCS 0-8 (MCS 9 N/A), 2 SS, 20MHz channel, no SGI */ - { 9, { 26, 52, 78, 104, 156, 208, 234, 260, 312 }, 2, 0 }, + { 2, 9, { 26, 52, 78, 104, 156, 208, 234, 260, 312 }, + 2, 0, 0, 0 }, /* MCS 0-8 (MCS 9 N/A), 2 SS, 20MHz channel, SGI */ - { 9, { 29, 58, 87, 116, 173, 231, 261, 289, 347 }, 2, 1 }, + { 3, 9, { 29, 58, 87, 116, 173, 231, 261, 289, 347 }, + 2, 0, 0, 1 }, /* MCS 0-9, 1 SS, 40MHz channel, no SGI */ - { 10, { 27, 54, 81, 108, 162, 216, 243, 270, 324, 360 }, 1, 0 }, + { 4, 10, { 27, 54, 81, 108, 162, 216, 243, 270, 324, 360 }, + 1, 1, 0, 0 }, /* MCS 0-9, 1 SS, 40MHz channel, SGI */ - { 10, { 30, 60, 90, 120, 180, 240, 270, 300, 360, 400 }, 1, 1 }, + { 5, 10, { 30, 60, 90, 120, 180, 240, 270, 300, 360, 400 }, + 1, 1, 0, 1 }, /* MCS 0-9, 2 SS, 40MHz channel, no SGI */ - { 10, { 54, 108, 162, 216, 324, 432, 486, 540, 648, 720 }, 2, 0 }, + { 6, 10, { 54, 108, 162, 216, 324, 432, 486, 540, 648, 720 }, + 2, 1, 0, 0 }, /* MCS 0-9, 2 SS, 40MHz channel, SGI */ - { 10, { 60, 120, 180, 240, 360, 480, 540, 600, 720, 800 }, 2, 1 }, + { 7, 10, { 60, 120, 180, 240, 360, 480, 540, 600, 720, 800 }, + 2, 1, 0, 1 }, /* MCS 0-9, 1 SS, 80MHz channel, no SGI */ - { 10, { 59, 117, 176, 234, 351, 468, 527, 585, 702, 780 }, 1, 0 }, + { 8, 10, { 59, 117, 176, 234, 351, 468, 527, 585, 702, 780 }, + 1, 0, 1, 0 }, /* MCS 0-9, 1 SS, 80MHz channel, SGI */ - { 10, { 65, 130, 195, 260, 390, 520, 585, 650, 780, 867 }, 1, 1 }, + { 9, 10, { 65, 130, 195, 260, 390, 520, 585, 650, 780, 867 }, + 1, 0, 1, 1 }, /* MCS 0-9, 2 SS, 80MHz channel, no SGI */ - { 10, { 117, 234, 351, 468, 702, 936, 1053, 1404, 1560 }, 2, 0 }, + { 10, 10, { 117, 234, 351, 468, 702, 936, 1053, 1404, 1560 }, + 2, 0, 1, 0 }, /* MCS 0-9, 2 SS, 80MHz channel, SGI */ - { 10, { 130, 260, 390, 520, 780, 1040, 1170, 1300, 1560, 1734 }, 2, 1 }, + { 11, 10, { 130, 260, 390, 520, 780, 1040, 1170, 1300, 1560, 1734 }, + 2, 0, 1, 1 }, }; /* diff --git a/sys/net80211/ieee80211_node.h b/sys/net80211/ieee80211_node.h index d1535a318cc..d03d25669b6 100644 --- a/sys/net80211/ieee80211_node.h +++ b/sys/net80211/ieee80211_node.h @@ -1,4 +1,4 @@ -/* $OpenBSD: ieee80211_node.h,v 1.92 2022/03/14 15:07:24 stsp Exp $ */ +/* $OpenBSD: ieee80211_node.h,v 1.93 2022/03/19 10:25:09 stsp Exp $ */ /* $NetBSD: ieee80211_node.h,v 1.9 2004/04/30 22:57:32 dyoung Exp $ */ /*- @@ -113,16 +113,20 @@ extern const struct ieee80211_ht_rateset ieee80211_std_ratesets_11n[]; #define IEEE80211_VHT_RATESET_MIMO2_80_SGI 11 #define IEEE80211_VHT_NUM_RATESETS 12 -/* Maximum number of rates in a HT rateset. */ +/* Maximum number of rates in a VHT rateset. */ #define IEEE80211_VHT_RATESET_MAX_NRATES 10 struct ieee80211_vht_rateset { + int idx; /* This rateset's index in ieee80211_std_rateset_11ac[]. */ + uint32_t nrates; uint32_t rates[IEEE80211_VHT_RATESET_MAX_NRATES]; /* 500 kbit/s units */ /* Number of spatial streams used by rates in this rateset. */ int num_ss; + int chan40; + int chan80; int sgi; }; @@ -574,6 +578,27 @@ ieee80211_node_supports_vht_chan80(struct ieee80211_node *ni) op_chan_width == IEEE80211_VHTOP0_CHAN_WIDTH_8080); } +/* Check if the peer can receive frames sent on a 160 MHz channel. */ +static inline int +ieee80211_node_supports_vht_chan160(struct ieee80211_node *ni) +{ + uint8_t cap_chan_width, op_chan_width; + + if (!ieee80211_node_supports_vht(ni)) + return 0; + + cap_chan_width = (ni->ni_vhtcaps & IEEE80211_VHTCAP_CHAN_WIDTH_MASK) >> + IEEE80211_VHTCAP_CHAN_WIDTH_SHIFT; + if (cap_chan_width != IEEE80211_VHTCAP_CHAN_WIDTH_160) + return 0; + + op_chan_width = (ni->ni_vht_chan_width & + IEEE80211_VHTOP0_CHAN_WIDTH_MASK) >> + IEEE80211_VHTOP0_CHAN_WIDTH_SHIFT; + + return (op_chan_width == IEEE80211_VHTOP0_CHAN_WIDTH_160); +} + struct ieee80211com; typedef void ieee80211_iter_func(void *, struct ieee80211_node *); diff --git a/sys/net80211/ieee80211_ra_vht.c b/sys/net80211/ieee80211_ra_vht.c new file mode 100644 index 00000000000..0d176ea7ade --- /dev/null +++ b/sys/net80211/ieee80211_ra_vht.c @@ -0,0 +1,756 @@ +/* $OpenBSD: ieee80211_ra_vht.c,v 1.1 2022/03/19 10:25:09 stsp Exp $ */ + +/* + * Copyright (c) 2021 Christian Ehrhardt <ehrhardt@genua.de> + * Copyright (c) 2016, 2021, 2022 Stefan Sperling <stsp@openbsd.org> + * Copyright (c) 2016 Theo Buehler <tb@openbsd.org> + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#include <sys/param.h> +#include <sys/systm.h> +#include <sys/socket.h> + +#include <net/if.h> +#include <net/if_media.h> + +#include <netinet/in.h> +#include <netinet/if_ether.h> + +#include <net80211/ieee80211_var.h> +#include <net80211/ieee80211_ra_vht.h> + +int ieee80211_ra_vht_next_intra_rate(struct ieee80211_ra_vht_node *, + struct ieee80211_node *); +const struct ieee80211_vht_rateset * ieee80211_ra_vht_next_rateset( + struct ieee80211_ra_vht_node *, struct ieee80211_node *); +int ieee80211_ra_vht_best_mcs_in_rateset(struct ieee80211_ra_vht_node *, + const struct ieee80211_vht_rateset *); +void ieee80211_ra_vht_probe_next_rateset(struct ieee80211_ra_vht_node *, + struct ieee80211_node *, const struct ieee80211_vht_rateset *); +int ieee80211_ra_vht_next_mcs(struct ieee80211_ra_vht_node *, + struct ieee80211_node *); +void ieee80211_ra_vht_probe_done(struct ieee80211_ra_vht_node *, int); +int ieee80211_ra_vht_intra_mode_ra_finished( + struct ieee80211_ra_vht_node *, struct ieee80211_node *); +void ieee80211_ra_vht_trigger_next_rateset(struct ieee80211_ra_vht_node *, + struct ieee80211_node *); +int ieee80211_ra_vht_inter_mode_ra_finished( + struct ieee80211_ra_vht_node *, struct ieee80211_node *); +int ieee80211_ra_vht_best_rate(struct ieee80211_ra_vht_node *, + struct ieee80211_node *); +void ieee80211_ra_vht_probe_next_rate(struct ieee80211_ra_vht_node *, + struct ieee80211_node *); +void ieee80211_ra_vht_init_valid_rates(struct ieee80211com *, + struct ieee80211_node *, struct ieee80211_ra_vht_node *); +int ieee80211_ra_vht_probe_valid(struct ieee80211_ra_vht_goodput_stats *); + +/* We use fixed point arithmetic with 64 bit integers. */ +#define RA_FP_SHIFT 21 +#define RA_FP_INT(x) (x ## ULL << RA_FP_SHIFT) /* the integer x */ +#define RA_FP_1 RA_FP_INT(1) + +/* Multiply two fixed point numbers. */ +#define RA_FP_MUL(a, b) \ + (((a) * (b)) >> RA_FP_SHIFT) + +/* Divide two fixed point numbers. */ +#define RA_FP_DIV(a, b) \ + (b == 0 ? (uint64_t)-1 : (((a) << RA_FP_SHIFT) / (b))) + +#define RA_DEBUG +#ifdef RA_DEBUG +#define DPRINTF(x) do { if (ra_vht_debug > 0) printf x; } while (0) +#define DPRINTFN(n, x) do { if (ra_vht_debug >= (n)) printf x; } while (0) +int ra_vht_debug = 0; +#else +#define DPRINTF(x) do { ; } while (0) +#define DPRINTFN(n, x) do { ; } while (0) +#endif + +#ifdef RA_DEBUG +void +ra_vht_fixedp_split(uint32_t *i, uint32_t *f, uint64_t fp) +{ + uint64_t tmp; + + /* integer part */ + *i = (fp >> RA_FP_SHIFT); + + /* fractional part */ + tmp = (fp & ((uint64_t)-1 >> (64 - RA_FP_SHIFT))); + tmp *= 100; + *f = (uint32_t)(tmp >> RA_FP_SHIFT); +} + +char * +ra_vht_fp_sprintf(uint64_t fp) +{ + uint32_t i, f; + static char buf[64]; + int ret; + + ra_vht_fixedp_split(&i, &f, fp); + ret = snprintf(buf, sizeof(buf), "%u.%02u", i, f); + if (ret == -1 || ret >= sizeof(buf)) + return "ERR"; + + return buf; +} +#endif /* RA_DEBUG */ + +const struct ieee80211_vht_rateset * +ieee80211_ra_vht_get_rateset(int mcs, int nss, int chan40, int chan80, int sgi) +{ + const struct ieee80211_vht_rateset *rs; + int i; + + for (i = 0; i < IEEE80211_VHT_NUM_RATESETS; i++) { + rs = &ieee80211_std_ratesets_11ac[i]; + if (mcs < rs->nrates && rs->num_ss == nss && + chan40 == rs->chan40 && chan80 == rs->chan80 && + sgi == rs->sgi) + return rs; + } + + panic("MCS %d NSS %d is not part of any rateset", mcs, nss); +} + +int +ieee80211_ra_vht_use_sgi(struct ieee80211_node *ni) +{ + if ((ni->ni_chan->ic_xflags & IEEE80211_CHANX_160MHZ) && + ieee80211_node_supports_vht_chan160(ni)) { + if (ni->ni_flags & IEEE80211_NODE_VHT_SGI160) + return 1; + } + + if ((ni->ni_chan->ic_xflags & IEEE80211_CHANX_80MHZ) && + ieee80211_node_supports_vht_chan80(ni)) { + if (ni->ni_flags & IEEE80211_NODE_VHT_SGI80) + return 1; + } + + return 0; +} + +/* + * Update goodput statistics. + */ + +uint64_t +ieee80211_ra_vht_get_txrate(int mcs, int nss, int chan40, int chan80, int sgi) +{ + const struct ieee80211_vht_rateset *rs; + uint64_t txrate; + + rs = ieee80211_ra_vht_get_rateset(mcs, nss, chan40, chan80, sgi); + txrate = rs->rates[mcs]; + txrate <<= RA_FP_SHIFT; /* convert to fixed-point */ + txrate *= 500; /* convert to kbit/s */ + txrate /= 1000; /* convert to mbit/s */ + + return txrate; +} + +/* + * Rate selection. + */ + +/* A rate's goodput has to be at least this much larger to be "better". */ +#define IEEE80211_RA_RATE_THRESHOLD (RA_FP_1 / 64) /* ~ 0.015 */ + +int +ieee80211_ra_vht_next_lower_intra_rate(struct ieee80211_ra_vht_node *rn, + struct ieee80211_node *ni) +{ + if (ni->ni_txmcs <= 0) + return 0; + + return ni->ni_txmcs - 1; +} + +int +ieee80211_ra_vht_get_max_mcs(int vht_mcs, int nss, int chan40) +{ + int supp_mcs = (vht_mcs & IEEE80211_VHT_MCS_FOR_SS_MASK(nss)) >> + IEEE80211_VHT_MCS_FOR_SS_SHIFT(nss); + int max_mcs = -1; + + switch (supp_mcs) { + case IEEE80211_VHT_MCS_SS_NOT_SUPP: + break; + case IEEE80211_VHT_MCS_0_7: + max_mcs = 7; + break; + case IEEE80211_VHT_MCS_0_8: + max_mcs = 8; + break; + case IEEE80211_VHT_MCS_0_9: + /* Disable VHT MCS 9 for 20MHz-only stations. */ + if (!chan40) + max_mcs = 8; + else + max_mcs = 9; + break; + default: + /* Should not happen; Values above cover the possible range. */ + panic("invalid VHT Rx MCS value %u", supp_mcs); + } + + return max_mcs; +} + +int +ieee80211_ra_vht_next_intra_rate(struct ieee80211_ra_vht_node *rn, + struct ieee80211_node *ni) +{ + int max_mcs; + + max_mcs = ieee80211_ra_vht_get_max_mcs(ni->ni_vht_rxmcs, + ni->ni_vht_ss, ieee80211_node_supports_ht_chan40(ni)); + if (max_mcs != 7 && max_mcs != 8 && max_mcs != 9) + panic("ni->ni_vht_ss invalid: %u", ni->ni_vht_ss); + + if (ni->ni_txmcs >= max_mcs) + return max_mcs; + + return ni->ni_txmcs + 1; +} + +const struct ieee80211_vht_rateset * +ieee80211_ra_vht_next_rateset(struct ieee80211_ra_vht_node *rn, + struct ieee80211_node *ni) +{ + const struct ieee80211_vht_rateset *rs, *rsnext; + int next; + int sgi = ieee80211_ra_vht_use_sgi(ni); + int mcs = ni->ni_txmcs; + int nss = ni->ni_vht_ss; + + /* + * We only probe 80MHz ratesets. + * Drivers handle retries on slower rates if needed. + */ + rs = ieee80211_ra_vht_get_rateset(mcs, nss, 0, 1, sgi); + if (rn->probing & IEEE80211_RA_PROBING_UP) { + switch (rs->idx) { + case IEEE80211_VHT_RATESET_SISO_80: + next = IEEE80211_VHT_RATESET_MIMO2_80; + break; + case IEEE80211_VHT_RATESET_SISO_80_SGI: + next = IEEE80211_VHT_RATESET_MIMO2_80_SGI; + break; + default: + return NULL; + } + } else if (rn->probing & IEEE80211_RA_PROBING_DOWN) { + switch (rs->idx) { + case IEEE80211_VHT_RATESET_MIMO2_80: + next = IEEE80211_VHT_RATESET_SISO_80; + break; + case IEEE80211_VHT_RATESET_MIMO2_80_SGI: + next = IEEE80211_VHT_RATESET_SISO_80_SGI; + break; + default: + return NULL; + } + } else + panic("%s: invalid probing mode %d", __func__, rn->probing); + + rsnext = &ieee80211_std_ratesets_11ac[next]; + if (rn->valid_rates[rsnext->num_ss - 1] == 0) + return NULL; + + return rsnext; +} + +int +ieee80211_ra_vht_best_mcs_in_rateset(struct ieee80211_ra_vht_node *rn, + const struct ieee80211_vht_rateset *rs) +{ + uint64_t gmax = 0; + int mcs, best_mcs = 0; + + for (mcs = 0; mcs < rs->nrates; mcs++) { + struct ieee80211_ra_vht_goodput_stats *g = &rn->g[rs->idx][mcs]; + if (((1 << mcs) & rn->valid_rates[rs->num_ss - 1]) == 0) + continue; + if (g->measured > gmax + IEEE80211_RA_RATE_THRESHOLD) { + gmax = g->measured; + best_mcs = mcs; + } + } + + return best_mcs; +} + +void +ieee80211_ra_vht_probe_next_rateset(struct ieee80211_ra_vht_node *rn, + struct ieee80211_node *ni, const struct ieee80211_vht_rateset *rsnext) +{ + const struct ieee80211_vht_rateset *rs; + struct ieee80211_ra_vht_goodput_stats *g; + int best_mcs, mcs; + + /* Find most recently measured best MCS from the current rateset. */ + rs = ieee80211_ra_vht_get_rateset(ni->ni_txmcs, ni->ni_vht_ss, 0, 1, + ieee80211_ra_vht_use_sgi(ni)); + best_mcs = ieee80211_ra_vht_best_mcs_in_rateset(rn, rs); + + /* Switch to the next rateset. */ + ni->ni_txmcs = 0; + ni->ni_vht_ss = rsnext->num_ss; + + /* Select the lowest rate from the next rateset with loss-free + * goodput close to the current best measurement. */ + g = &rn->g[rs->idx][best_mcs]; + for (mcs = 0; mcs < rsnext->nrates; mcs++) { + uint64_t txrate = rsnext->rates[mcs]; + + if ((rn->valid_rates[rsnext->num_ss - 1] & (1 << mcs)) == 0) + continue; + + txrate = txrate * 500; /* convert to kbit/s */ + txrate <<= RA_FP_SHIFT; /* convert to fixed-point */ + txrate /= 1000; /* convert to mbit/s */ + + if (txrate > g->measured + IEEE80211_RA_RATE_THRESHOLD) { + ni->ni_txmcs = mcs; + break; + } + } + /* If all rates are lower the maximum rate is the closest match. */ + if (mcs == rsnext->nrates) + ni->ni_txmcs = rn->max_mcs[rsnext->num_ss - 1]; + + /* Add rates from the next rateset as candidates. */ + rn->candidate_rates[rsnext->num_ss - 1] |= (1 << ni->ni_txmcs); + if (rn->probing & IEEE80211_RA_PROBING_UP) { + rn->candidate_rates[rsnext->num_ss - 1] |= + (1 << ieee80211_ra_vht_next_intra_rate(rn, ni)); + } else if (rn->probing & IEEE80211_RA_PROBING_DOWN) { + rn->candidate_rates[rsnext->num_ss - 1] |= + (1 << ieee80211_ra_vht_next_lower_intra_rate(rn, ni)); + } else + panic("%s: invalid probing mode %d", __func__, rn->probing); +} + +int +ieee80211_ra_vht_next_mcs(struct ieee80211_ra_vht_node *rn, + struct ieee80211_node *ni) +{ + int next; + + if (rn->probing & IEEE80211_RA_PROBING_DOWN) + next = ieee80211_ra_vht_next_lower_intra_rate(rn, ni); + else if (rn->probing & IEEE80211_RA_PROBING_UP) + next = ieee80211_ra_vht_next_intra_rate(rn, ni); + else + panic("%s: invalid probing mode %d", __func__, rn->probing); + + return next; +} + +void +ieee80211_ra_vht_probe_clear(struct ieee80211_ra_vht_goodput_stats *g) +{ + g->nprobe_pkts = 0; + g->nprobe_fail = 0; +} + +void +ieee80211_ra_vht_probe_done(struct ieee80211_ra_vht_node *rn, int nss) +{ + rn->probing = IEEE80211_RA_NOT_PROBING; + rn->probed_rates[nss - 1] = 0; + rn->valid_probes[nss - 1] = 0; + rn->candidate_rates[nss - 1] = 0; +} + +int +ieee80211_ra_vht_intra_mode_ra_finished(struct ieee80211_ra_vht_node *rn, + struct ieee80211_node *ni) +{ + const struct ieee80211_vht_rateset *rs; + struct ieee80211_ra_vht_goodput_stats *g; + int next_mcs, best_mcs; + uint64_t next_rate; + int nss = ni->ni_vht_ss; + int sgi = ieee80211_ra_vht_use_sgi(ni); + + rn->probed_rates[nss - 1] = (rn->probed_rates[nss - 1] | + (1 << ni->ni_txmcs)); + + /* Check if the min/max MCS in this rateset has been probed. */ + rs = ieee80211_ra_vht_get_rateset(ni->ni_txmcs, nss, 0, 1, sgi); + if (rn->probing & IEEE80211_RA_PROBING_DOWN) { + if (ni->ni_txmcs == 0 || + rn->probed_rates[nss - 1] & (1 << 0)) { + ieee80211_ra_vht_trigger_next_rateset(rn, ni); + return 1; + } + } else if (rn->probing & IEEE80211_RA_PROBING_UP) { + if (ni->ni_txmcs == rn->max_mcs[nss - 1] || + rn->probed_rates[nss - 1] & (1 << rn->max_mcs[nss - 1])) { + ieee80211_ra_vht_trigger_next_rateset(rn, ni); + return 1; + } + } + + /* + * Check if the measured goodput is loss-free and better than the + * loss-free goodput of the candidate rate. + */ + next_mcs = ieee80211_ra_vht_next_mcs(rn, ni); + if (next_mcs == ni->ni_txmcs) { + ieee80211_ra_vht_trigger_next_rateset(rn, ni); + return 1; + } + next_rate = ieee80211_ra_vht_get_txrate(next_mcs, nss, 0, 1, sgi); + g = &rn->g[rs->idx][ni->ni_txmcs]; + if (g->loss == 0 && + g->measured >= next_rate + IEEE80211_RA_RATE_THRESHOLD) { + ieee80211_ra_vht_trigger_next_rateset(rn, ni); + return 1; + } + + /* Check if we had a better measurement at a previously probed MCS. */ + best_mcs = ieee80211_ra_vht_best_mcs_in_rateset(rn, rs); + if (best_mcs != ni->ni_txmcs && + (rn->probed_rates[nss - 1] & (1 << best_mcs))) { + if ((rn->probing & IEEE80211_RA_PROBING_UP) && + best_mcs < ni->ni_txmcs) { + ieee80211_ra_vht_trigger_next_rateset(rn, ni); + return 1; + } + if ((rn->probing & IEEE80211_RA_PROBING_DOWN) && + best_mcs > ni->ni_txmcs) { + ieee80211_ra_vht_trigger_next_rateset(rn, ni); + return 1; + } + } + + /* Check if all rates in the set of candidate rates have been probed. */ + if ((rn->candidate_rates[nss - 1] & rn->probed_rates[nss - 1]) == + rn->candidate_rates[nss - 1]) { + /* Remain in the current rateset until above checks trigger. */ + rn->probing &= ~IEEE80211_RA_PROBING_INTER; + return 1; + } + + return 0; +} + +void +ieee80211_ra_vht_trigger_next_rateset(struct ieee80211_ra_vht_node *rn, + struct ieee80211_node *ni) +{ + const struct ieee80211_vht_rateset *rsnext; + + rsnext = ieee80211_ra_vht_next_rateset(rn, ni); + if (rsnext) { + ieee80211_ra_vht_probe_next_rateset(rn, ni, rsnext); + rn->probing |= IEEE80211_RA_PROBING_INTER; + } else + rn->probing &= ~IEEE80211_RA_PROBING_INTER; +} + +int +ieee80211_ra_vht_inter_mode_ra_finished(struct ieee80211_ra_vht_node *rn, + struct ieee80211_node *ni) +{ + return ((rn->probing & IEEE80211_RA_PROBING_INTER) == 0); +} + +int +ieee80211_ra_vht_best_rate(struct ieee80211_ra_vht_node *rn, + struct ieee80211_node *ni) +{ + const struct ieee80211_vht_rateset *rs; + int i, j, best_mcs = rn->best_mcs, best_nss = rn->best_nss; + uint64_t gmax; + + rs = ieee80211_ra_vht_get_rateset(best_mcs, best_nss, 0, 1, + ieee80211_ra_vht_use_sgi(ni)); + gmax = rn->g[rs->idx][best_mcs].measured; + + for (i = 0; i < IEEE80211_VHT_NUM_RATESETS; i++) { + rs = &ieee80211_std_ratesets_11ac[i]; + for (j = 0; j < IEEE80211_VHT_RATESET_MAX_NRATES; j++) { + struct ieee80211_ra_vht_goodput_stats *g = &rn->g[i][j]; + if (((1 << i) & rn->valid_rates[rs->num_ss - 1]) == 0) + continue; + if (g->measured > gmax + IEEE80211_RA_RATE_THRESHOLD) { + gmax = g->measured; + best_mcs = j; + best_nss = rs->num_ss; + } + } + } + +#ifdef RA_DEBUG + if (rn->best_mcs != best_mcs || rn->best_nss != best_nss) { + DPRINTF(("MCS,NSS %d,%d is best; MCS,NSS{cur|avg|loss}:", + best_mcs, best_nss)); + for (i = 0; i < IEEE80211_VHT_NUM_RATESETS; i++) { + rs = &ieee80211_std_ratesets_11ac[i]; + for (j = 0; j < IEEE80211_VHT_RATESET_MAX_NRATES; j++) { + struct ieee80211_ra_vht_goodput_stats *g; + g = &rn->g[i][j]; + if ((rn->valid_rates[rs->num_ss - 1] & + (1 << i)) == 0) + continue; + DPRINTF((" %d,%d{%s|", i, j, + ra_vht_fp_sprintf(g->measured))); + DPRINTF(("%s|", ra_vht_fp_sprintf(g->average))); + DPRINTF(("%s%%}", ra_vht_fp_sprintf(g->loss))); + } + } + DPRINTF(("\n")); + } +#endif + return best_mcs; +} + +void +ieee80211_ra_vht_probe_next_rate(struct ieee80211_ra_vht_node *rn, + struct ieee80211_node *ni) +{ + /* Select the next rate to probe. */ + rn->probed_rates[ni->ni_vht_ss - 1] |= (1 << ni->ni_txmcs); + ni->ni_txmcs = ieee80211_ra_vht_next_mcs(rn, ni); +} + +void +ieee80211_ra_vht_init_valid_rates(struct ieee80211com *ic, + struct ieee80211_node *ni, struct ieee80211_ra_vht_node *rn) +{ + int nss, ic_max_mcs, ni_max_mcs, max_mcs; + + memset(rn->max_mcs, 0, sizeof(rn->max_mcs)); + memset(rn->valid_rates, 0, sizeof(rn->valid_rates)); + + for (nss = 1; nss <= IEEE80211_VHT_NUM_SS; nss++) { + ic_max_mcs = ieee80211_ra_vht_get_max_mcs(ic->ic_vht_txmcs, + nss, IEEE80211_CHAN_40MHZ_ALLOWED(ic->ic_bss->ni_chan)); + ni_max_mcs = ieee80211_ra_vht_get_max_mcs(ni->ni_vht_rxmcs, + nss, ieee80211_node_supports_ht_chan40(ni)); + if ((ic_max_mcs != 7 && ic_max_mcs != 8 && ic_max_mcs != 9) || + (ni_max_mcs != 7 && ni_max_mcs != 8 && ni_max_mcs != 9)) + continue; + + max_mcs = MIN(ic_max_mcs, ni_max_mcs); + rn->max_mcs[nss - 1] = max_mcs; + rn->valid_rates[nss - 1] = ((1 << (max_mcs + 1)) - 1); + } +} + +int +ieee80211_ra_vht_probe_valid(struct ieee80211_ra_vht_goodput_stats *g) +{ + /* 128 packets make up a valid probe in any case. */ + if (g->nprobe_pkts >= 128) + return 1; + + /* 8 packets with > 75% loss make a valid probe, too. */ + if (g->nprobe_pkts >= 8 && + g->nprobe_pkts - g->nprobe_fail < g->nprobe_pkts / 4) + return 1; + + return 0; +} + +void +ieee80211_ra_vht_add_stats(struct ieee80211_ra_vht_node *rn, + struct ieee80211com *ic, struct ieee80211_node *ni, + int mcs, int nss, uint32_t total, uint32_t fail) +{ + static const uint64_t alpha = RA_FP_1 / 8; /* 1/8 = 0.125 */ + static const uint64_t beta = RA_FP_1 / 4; /* 1/4 = 0.25 */ + int s; + const struct ieee80211_vht_rateset *rs; + struct ieee80211_ra_vht_goodput_stats *g; + uint64_t sfer, rate, delta; + + /* + * Ignore invalid values. These values may come from hardware + * so asserting valid values via panic is not appropriate. + */ + if (mcs < 0 || mcs >= IEEE80211_VHT_RATESET_MAX_NRATES) + return; + if (nss <= 0 || nss > IEEE80211_VHT_NUM_SS) + return; + if (total == 0) + return; + + s = splnet(); + + rs = ieee80211_ra_vht_get_rateset(mcs, nss, 0, 1, + ieee80211_ra_vht_use_sgi(ni)); + g = &rn->g[rs->idx][mcs]; + g->nprobe_pkts += total; + g->nprobe_fail += fail; + + if (!ieee80211_ra_vht_probe_valid(g)) { + splx(s); + return; + } + rn->valid_probes[nss - 1] |= 1U << mcs; + + if (g->nprobe_fail > g->nprobe_pkts) { + DPRINTF(("%s fail %u > pkts %u\n", + ether_sprintf(ni->ni_macaddr), + g->nprobe_fail, g->nprobe_pkts)); + g->nprobe_fail = g->nprobe_pkts; + } + + sfer = g->nprobe_fail << RA_FP_SHIFT; + sfer /= g->nprobe_pkts; + g->nprobe_fail = 0; + g->nprobe_pkts = 0; + + rate = ieee80211_ra_vht_get_txrate(mcs, nss, 0, 1, + ieee80211_ra_vht_use_sgi(ni)); + + g->loss = sfer * 100; + g->measured = RA_FP_MUL(RA_FP_1 - sfer, rate); + g->average = RA_FP_MUL(RA_FP_1 - alpha, g->average); + g->average += RA_FP_MUL(alpha, g->measured); + + g->stddeviation = RA_FP_MUL(RA_FP_1 - beta, g->stddeviation); + if (g->average > g->measured) + delta = g->average - g->measured; + else + delta = g->measured - g->average; + g->stddeviation += RA_FP_MUL(beta, delta); + + splx(s); +} + +void +ieee80211_ra_vht_choose(struct ieee80211_ra_vht_node *rn, + struct ieee80211com *ic, struct ieee80211_node *ni) +{ + struct ieee80211_ra_vht_goodput_stats *g; + int s; + int sgi = ieee80211_ra_vht_use_sgi(ni); + const struct ieee80211_vht_rateset *rs, *rsnext; + int nss = ni->ni_vht_ss; + + s = splnet(); + + if (rn->valid_rates[0] == 0) { + ieee80211_ra_vht_init_valid_rates(ic, ni, rn); + if (rn->valid_rates[0] == 0) + panic("VHT not supported"); + } + + rs = ieee80211_ra_vht_get_rateset(ni->ni_txmcs, nss, 0, 1, sgi); + g = &rn->g[rs->idx][ni->ni_txmcs]; + + if (rn->probing) { + /* Probe another rate or settle at the best rate. */ + if (!(rn->valid_probes[nss - 1] & (1UL << ni->ni_txmcs))) { + splx(s); + return; + } + ieee80211_ra_vht_probe_clear(g); + if (!ieee80211_ra_vht_intra_mode_ra_finished(rn, ni)) { + ieee80211_ra_vht_probe_next_rate(rn, ni); + DPRINTFN(3, ("probing MCS,NSS %d,%d\n", + ni->ni_txmcs, ni->ni_vht_ss)); + } else if (ieee80211_ra_vht_inter_mode_ra_finished(rn, ni)) { + rn->best_mcs = ieee80211_ra_vht_best_rate(rn, ni); + ni->ni_txmcs = rn->best_mcs; + ni->ni_vht_ss = rn->best_nss; + ieee80211_ra_vht_probe_done(rn, nss); + } + + splx(s); + return; + } else { + rn->valid_probes[nss - 1] = 0; + } + + + rs = ieee80211_ra_vht_get_rateset(ni->ni_txmcs, nss, 0, 1, sgi); + if ((g->measured >> RA_FP_SHIFT) == 0LL || + (g->average >= 3 * g->stddeviation && + g->measured < g->average - 3 * g->stddeviation)) { + /* Channel becomes bad. Probe downwards. */ + rn->probing = IEEE80211_RA_PROBING_DOWN; + rn->probed_rates[nss - 1] = 0; + if (ni->ni_txmcs == 0) { + rsnext = ieee80211_ra_vht_next_rateset(rn, ni); + if (rsnext) { + ieee80211_ra_vht_probe_next_rateset(rn, ni, + rsnext); + } else { + /* Cannot probe further down. */ + rn->probing = IEEE80211_RA_NOT_PROBING; + } + } else { + ni->ni_txmcs = ieee80211_ra_vht_next_mcs(rn, ni); + rn->candidate_rates[nss - 1] = (1 << ni->ni_txmcs); + } + } else if (g->loss < 2 * RA_FP_1 || + g->measured > g->average + 3 * g->stddeviation) { + /* Channel becomes good. */ + rn->probing = IEEE80211_RA_PROBING_UP; + rn->probed_rates[nss - 1] = 0; + if (ni->ni_txmcs == rn->max_mcs[nss - 1]) { + rsnext = ieee80211_ra_vht_next_rateset(rn, ni); + if (rsnext) { + ieee80211_ra_vht_probe_next_rateset(rn, ni, + rsnext); + } else { + /* Cannot probe further up. */ + rn->probing = IEEE80211_RA_NOT_PROBING; + } + } else { + ni->ni_txmcs = ieee80211_ra_vht_next_mcs(rn, ni); + rn->candidate_rates[nss - 1] = (1 << ni->ni_txmcs); + } + } else { + /* Remain at current rate. */ + rn->probing = IEEE80211_RA_NOT_PROBING; + rn->probed_rates[nss - 1] = 0; + rn->candidate_rates[nss - 1] = 0; + } + + splx(s); + + if (rn->probing) { + if (rn->probing & IEEE80211_RA_PROBING_UP) + DPRINTFN(2, ("channel becomes good; probe up\n")); + else + DPRINTFN(2, ("channel becomes bad; probe down\n")); + + DPRINTFN(3, ("measured: %s Mbit/s\n", + ra_vht_fp_sprintf(g->measured))); + DPRINTFN(3, ("average: %s Mbit/s\n", + ra_vht_fp_sprintf(g->average))); + DPRINTFN(3, ("stddeviation: %s\n", + ra_vht_fp_sprintf(g->stddeviation))); + DPRINTFN(3, ("loss: %s%%\n", ra_vht_fp_sprintf(g->loss))); + } +} + +void +ieee80211_ra_vht_node_init(struct ieee80211_ra_vht_node *rn) +{ + memset(rn, 0, sizeof(*rn)); + rn->best_nss = 1; +} diff --git a/sys/net80211/ieee80211_ra_vht.h b/sys/net80211/ieee80211_ra_vht.h new file mode 100644 index 00000000000..6d463fcf961 --- /dev/null +++ b/sys/net80211/ieee80211_ra_vht.h @@ -0,0 +1,91 @@ +/* $OpenBSD: ieee80211_ra_vht.h,v 1.1 2022/03/19 10:25:09 stsp Exp $ */ + +/* + * Copyright (c) 2021 Christian Ehrhardt <ehrhardt@genua.de> + * Copyright (c) 2021, 2022 Stefan Sperling <stsp@openbsd.org> + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +/* + * Goodput statistics struct. Measures the effective data rate of a rate. + * All uint64_t numbers in this struct use fixed-point arithmetic. + */ +struct ieee80211_ra_vht_goodput_stats { + uint64_t measured; /* Most recently measured goodput. */ + uint64_t average; /* Average measured goodput. */ + uint64_t stddeviation; /* Goodput standard deviation. */ + uint64_t loss; /* This rate's loss percentage SFER. */ + uint32_t nprobe_pkts; /* Number of packets in current probe. */ + uint32_t nprobe_fail; /* Number of failed packets. */ +}; + +/* + * Rate adaptation state. + * + * Drivers should not modify any fields of this structure directly. + * Use ieee80211_ra_vht_init() and ieee80211_ra_vht_add_stats() only. + */ +struct ieee80211_ra_vht_node { + /* Bitmaps for MCS 0-9 for a given number of spatial streams. */ + uint16_t valid_probes[IEEE80211_VHT_NUM_SS]; + uint16_t valid_rates[IEEE80211_VHT_NUM_SS]; + uint16_t candidate_rates[IEEE80211_VHT_NUM_SS]; + uint16_t probed_rates[IEEE80211_VHT_NUM_SS]; + + /* Maximum usable MCS per given number of spatial streams. */ + int max_mcs[IEEE80211_VHT_NUM_SS]; + + /* Probing state. */ + int probing; +#define IEEE80211_RA_NOT_PROBING 0x0 +#define IEEE80211_RA_PROBING_DOWN 0x1 +#define IEEE80211_RA_PROBING_UP 0x2 +#define IEEE80211_RA_PROBING_INTER 0x4 /* combined with UP or DOWN */ + + /* The current best MCS,NSS found by probing. */ + int best_mcs; + int best_nss; + + /* Goodput statistics for each rate. */ + struct ieee80211_ra_vht_goodput_stats + g[IEEE80211_VHT_NUM_RATESETS][IEEE80211_VHT_RATESET_MAX_NRATES]; +}; + +/* Initialize rate adaptation state. */ +void ieee80211_ra_vht_node_init(struct ieee80211_ra_vht_node *); + +/* + * Drivers report information about 802.11ac/VHT Tx attempts here. + * mcs: The VHT MCS used during this Tx attempt. + * nss: The number of spatial streams used during this Tx attempt. + * total: How many Tx attempts (initial attempt + any retries) were made? + * fail: How many of these Tx attempts failed? + */ +void ieee80211_ra_vht_add_stats(struct ieee80211_ra_vht_node *, + struct ieee80211com *, struct ieee80211_node *, + int mcs, int nss, unsigned int total, unsigned int fail); + +/* Drivers call this function to update ni->ni_txmcs and ni->ni_vht_ss. */ +void ieee80211_ra_vht_choose(struct ieee80211_ra_vht_node *, + struct ieee80211com *, struct ieee80211_node *); + +/* + * Get the VHT rateset for a particular VHT MCS, NSS, with 40MHz, 80MHz, + * and/or SGI on/off. + */ +const struct ieee80211_vht_rateset * ieee80211_ra_vht_get_rateset(int mcs, + int nss, int chan40, int chan80, int sgi); + +/* Check whether SGI should be used. */ +int ieee80211_ra_vht_use_sgi(struct ieee80211_node *); |