Index: sys/net80211/ieee80211.c =================================================================== --- sys/net80211/ieee80211.c (revision 311663) +++ sys/net80211/ieee80211.c (working copy) @@ -54,6 +54,7 @@ #include #endif #include +#include #include @@ -70,6 +71,8 @@ [IEEE80211_MODE_QUARTER] = "quarter", [IEEE80211_MODE_11NA] = "11na", [IEEE80211_MODE_11NG] = "11ng", + [IEEE80211_MODE_VHT_2GHZ] = "11acg", + [IEEE80211_MODE_VHT_5GHZ] = "11ac", }; /* map ieee80211_opmode to the corresponding capability bit */ const int ieee80211_opcap[IEEE80211_OPMODE_MAX] = { @@ -117,6 +120,8 @@ { 12, { B(2), B(4), B(11), B(22), 12, 18, 24, 36, 48, 72, 96, 108 } }; #undef B +static int set_vht_extchan(struct ieee80211_channel *c); + /* * Fill in 802.11 available channel set, mark * all available channels as active, and pick @@ -148,10 +153,23 @@ */ if (c->ic_ieee == 0) c->ic_ieee = ieee80211_mhz2ieee(c->ic_freq,c->ic_flags); + + /* + * Setup the HT40/VHT40 upper/lower bits. + * The VHT80 math is done elsewhere. + */ if (IEEE80211_IS_CHAN_HT40(c) && c->ic_extieee == 0) c->ic_extieee = ieee80211_mhz2ieee(c->ic_freq + (IEEE80211_IS_CHAN_HT40U(c) ? 20 : -20), c->ic_flags); + + /* Update VHT math */ + /* + * XXX VHT again, note that this assumes VHT80 channels + * are legit already + */ + set_vht_extchan(c); + /* default max tx power to max regulatory */ if (c->ic_maxpower == 0) c->ic_maxpower = 2*c->ic_maxregpower; @@ -181,6 +199,10 @@ setbit(ic->ic_modecaps, IEEE80211_MODE_11NA); if (IEEE80211_IS_CHAN_HTG(c)) setbit(ic->ic_modecaps, IEEE80211_MODE_11NG); + if (IEEE80211_IS_CHAN_VHTA(c)) + setbit(ic->ic_modecaps, IEEE80211_MODE_VHT_5GHZ); + if (IEEE80211_IS_CHAN_VHTG(c)) + setbit(ic->ic_modecaps, IEEE80211_MODE_VHT_2GHZ); } /* initialize candidate channels to all available */ memcpy(ic->ic_chan_active, ic->ic_chan_avail, @@ -208,6 +230,8 @@ DEFAULTRATES(IEEE80211_MODE_QUARTER, ieee80211_rateset_quarter); DEFAULTRATES(IEEE80211_MODE_11NA, ieee80211_rateset_11a); DEFAULTRATES(IEEE80211_MODE_11NG, ieee80211_rateset_11g); + DEFAULTRATES(IEEE80211_MODE_VHT_5GHZ, ieee80211_rateset_11a); + DEFAULTRATES(IEEE80211_MODE_VHT_2GHZ, ieee80211_rateset_11g); /* * Setup required information to fill the mcsset field, if driver did @@ -335,6 +359,7 @@ ieee80211_superg_attach(ic); #endif ieee80211_ht_attach(ic); + ieee80211_vht_attach(ic); ieee80211_scan_attach(ic); ieee80211_regdomain_attach(ic); ieee80211_dfs_attach(ic); @@ -378,6 +403,7 @@ #ifdef IEEE80211_SUPPORT_SUPERG ieee80211_superg_detach(ic); #endif + ieee80211_vht_detach(ic); ieee80211_ht_detach(ic); /* NB: must be called before ieee80211_node_detach */ ieee80211_proto_detach(ic); @@ -507,8 +533,15 @@ vap->iv_flags_ext = ic->ic_flags_ext; vap->iv_flags_ven = ic->ic_flags_ven; vap->iv_caps = ic->ic_caps &~ IEEE80211_C_OPMODE; + + /* 11n capabilities - XXX methodize */ vap->iv_htcaps = ic->ic_htcaps; vap->iv_htextcaps = ic->ic_htextcaps; + + /* 11ac capabilities - XXX methodize */ + vap->iv_vhtcaps = ic->ic_vhtcaps; + vap->iv_vhtextcaps = ic->ic_vhtextcaps; + vap->iv_opmode = opmode; vap->iv_caps |= ieee80211_opcap[opmode]; IEEE80211_ADDR_COPY(vap->iv_myaddr, ic->ic_macaddr); @@ -593,6 +626,7 @@ ieee80211_superg_vattach(vap); #endif ieee80211_ht_vattach(vap); + ieee80211_vht_vattach(vap); ieee80211_scan_vattach(vap); ieee80211_regdomain_vattach(vap); ieee80211_radiotap_vattach(vap); @@ -729,6 +763,7 @@ #ifdef IEEE80211_SUPPORT_SUPERG ieee80211_superg_vdetach(vap); #endif + ieee80211_vht_vdetach(vap); ieee80211_ht_vdetach(vap); /* NB: must be before ieee80211_node_vdetach */ ieee80211_proto_vdetach(vap); @@ -1073,7 +1108,109 @@ c->ic_extieee = 0; } +/* + * Populate the freq1/freq2 fields as appropriate for VHT channels. + * + * This for now uses a hard-coded list of 80MHz wide channels. + * + * For HT20/HT40, freq1 just is the centre frequency of the 40MHz + * wide channel we've already decided upon. + * + * For VHT80 and VHT160, there are only a small number of fixed + * 80/160MHz wide channels, so we just use those. + * + * This is all likely very very wrong - both the regulatory code + * and this code needs to ensure that all four channels are + * available and valid before the VHT80 (and eight for VHT160) channel + * is created. + */ + +struct vht_chan_range { + uint16_t freq_start; + uint16_t freq_end; +}; + +struct vht_chan_range vht80_chan_ranges[] = { + { 5170, 5250 }, + { 5250, 5330 }, + { 5490, 5570 }, + { 5570, 5650 }, + { 5650, 5730 }, + { 5735, 5815 }, + { 0, 0, } +}; + static int +set_vht_extchan(struct ieee80211_channel *c) +{ + int i; + + if (! IEEE80211_IS_CHAN_VHT(c)) { + return (0); + } + + if (IEEE80211_IS_CHAN_VHT20(c)) { + c->ic_vht_ch_freq1 = c->ic_ieee; + return (1); + } + + if (IEEE80211_IS_CHAN_VHT40(c)) { + if (IEEE80211_IS_CHAN_HT40U(c)) + c->ic_vht_ch_freq1 = c->ic_ieee + 2; + else if (IEEE80211_IS_CHAN_HT40D(c)) + c->ic_vht_ch_freq1 = c->ic_ieee - 2; + else + return (0); + return (1); + } + + if (IEEE80211_IS_CHAN_VHT80(c)) { + for (i = 0; vht80_chan_ranges[i].freq_start != 0; i++) { + if (c->ic_freq >= vht80_chan_ranges[i].freq_start && + c->ic_freq < vht80_chan_ranges[i].freq_end) { + int midpoint; + + midpoint = vht80_chan_ranges[i].freq_start + 40; + c->ic_vht_ch_freq1 = + ieee80211_mhz2ieee(midpoint, c->ic_flags); + c->ic_vht_ch_freq2 = 0; + printf("%s: %d, freq=%d, midpoint=%d, freq1=%d, freq2=%d\n", + __func__, c->ic_ieee, c->ic_freq, midpoint, + c->ic_vht_ch_freq1, c->ic_vht_ch_freq2); + return (1); + } + } + return (0); + } + + printf("%s: unknown VHT channel type (ieee=%d, flags=0x%08x)\n", + __func__, + c->ic_ieee, + c->ic_flags); + + return (0); +} + +/* + * Return whether the current channel could possibly be a part of + * a VHT80 channel. + * + * This doesn't check that the whole range is in the allowed list + * according to regulatory. + */ +static int +is_vht80_valid_freq(uint16_t freq) +{ + int i; + for (i = 0; vht80_chan_ranges[i].freq_start != 0; i++) { + if (freq >= vht80_chan_ranges[i].freq_start && + freq < vht80_chan_ranges[i].freq_end) + return (1); + } + return (0); +} + +static int addchan(struct ieee80211_channel chans[], int maxchans, int *nchans, uint8_t ieee, uint16_t freq, int8_t maxregpower, uint32_t flags) { @@ -1082,6 +1219,15 @@ if (*nchans >= maxchans) return (ENOBUFS); +#if 0 + printf("%s: %d: ieee=%d, freq=%d, flags=0x%08x\n", + __func__, + *nchans, + ieee, + freq, + flags); +#endif + c = &chans[(*nchans)++]; c->ic_ieee = ieee; c->ic_freq = freq != 0 ? freq : ieee80211_ieee2mhz(ieee, flags); @@ -1088,7 +1234,10 @@ c->ic_maxregpower = maxregpower; c->ic_maxpower = 2 * maxregpower; c->ic_flags = flags; + c->ic_vht_ch_freq1 = 0; + c->ic_vht_ch_freq2 = 0; set_extchan(c); + set_vht_extchan(c); return (0); } @@ -1104,14 +1253,27 @@ if (*nchans >= maxchans) return (ENOBUFS); +#if 0 + printf("%s: %d: flags=0x%08x\n", + __func__, + *nchans, + flags); +#endif + c = &chans[(*nchans)++]; c[0] = c[-1]; c->ic_flags = flags; + c->ic_vht_ch_freq1 = 0; + c->ic_vht_ch_freq2 = 0; set_extchan(c); + set_vht_extchan(c); return (0); } +/* + * XXX VHT-2GHz + */ static void getflags_2ghz(const uint8_t bands[], uint32_t flags[], int ht40) { @@ -1131,36 +1293,77 @@ flags[nmodes] = 0; } +/* + * XXX VHT-5GHz + */ static void -getflags_5ghz(const uint8_t bands[], uint32_t flags[], int ht40) +getflags_5ghz(const uint8_t bands[], uint32_t flags[], int ht40, int vht80) { int nmodes; + /* + * the addchan_list function seems to expect the flags array to + * be in channel width order, so the VHT bits are interspersed + * as appropriate to maintain said order. + * + * It also assumes HT40U is before HT40D. + */ nmodes = 0; + + /* 20MHz */ if (isset(bands, IEEE80211_MODE_11A)) flags[nmodes++] = IEEE80211_CHAN_A; if (isset(bands, IEEE80211_MODE_11NA)) flags[nmodes++] = IEEE80211_CHAN_A | IEEE80211_CHAN_HT20; + if (isset(bands, IEEE80211_MODE_VHT_5GHZ)) { + flags[nmodes++] = IEEE80211_CHAN_A | IEEE80211_CHAN_HT20 | + IEEE80211_CHAN_VHT20; + + /* 40MHz */ if (ht40) { flags[nmodes++] = IEEE80211_CHAN_A | IEEE80211_CHAN_HT40U; + } + if (ht40 && isset(bands, IEEE80211_MODE_VHT_5GHZ)) { + flags[nmodes++] = IEEE80211_CHAN_A | IEEE80211_CHAN_HT40U + | IEEE80211_CHAN_VHT40U; + } + if (ht40) { flags[nmodes++] = IEEE80211_CHAN_A | IEEE80211_CHAN_HT40D; } + if (ht40 && isset(bands, IEEE80211_MODE_VHT_5GHZ)) { + flags[nmodes++] = IEEE80211_CHAN_A | IEEE80211_CHAN_HT40D + | IEEE80211_CHAN_VHT40D; + } + + /* 80MHz */ + if (vht80 && isset(bands, IEEE80211_MODE_VHT_5GHZ)) { + flags[nmodes++] = IEEE80211_CHAN_A | + IEEE80211_CHAN_HT40U | IEEE80211_CHAN_VHT80; + flags[nmodes++] = IEEE80211_CHAN_A | + IEEE80211_CHAN_HT40D | IEEE80211_CHAN_VHT80; + } + } + + /* XXX VHT80+80 */ + /* XXX VHT160 */ flags[nmodes] = 0; } static void -getflags(const uint8_t bands[], uint32_t flags[], int ht40) +getflags(const uint8_t bands[], uint32_t flags[], int ht40, int vht80) { flags[0] = 0; if (isset(bands, IEEE80211_MODE_11A) || - isset(bands, IEEE80211_MODE_11NA)) { + isset(bands, IEEE80211_MODE_11NA) || + isset(bands, IEEE80211_MODE_VHT_5GHZ)) { if (isset(bands, IEEE80211_MODE_11B) || isset(bands, IEEE80211_MODE_11G) || - isset(bands, IEEE80211_MODE_11NG)) + isset(bands, IEEE80211_MODE_11NG) || + isset(bands, IEEE80211_MODE_VHT_2GHZ)) return; - getflags_5ghz(bands, flags, ht40); + getflags_5ghz(bands, flags, ht40, vht80); } else getflags_2ghz(bands, flags, ht40); } @@ -1176,7 +1379,7 @@ uint32_t flags[IEEE80211_MODE_MAX]; int i, error; - getflags(bands, flags, 0); + getflags(bands, flags, 0, 0); KASSERT(flags[0] != 0, ("%s: no correct mode provided\n", __func__)); error = addchan(chans, maxchans, nchans, ieee, freq, maxregpower, @@ -1210,6 +1413,7 @@ /* * Add 40 MHz channel pair into specified channel list. */ +/* XXX VHT */ int ieee80211_add_channel_ht40(struct ieee80211_channel chans[], int maxchans, int *nchans, uint8_t ieee, int8_t maxregpower, uint32_t flags) @@ -1272,6 +1476,13 @@ ieee80211_get_channel_center_freq1(const struct ieee80211_channel *c) { + /* + * VHT - use the pre-calculated centre frequency + * of the given channel. + */ + if (IEEE80211_IS_CHAN_VHT(c)) + return (ieee80211_ieee2mhz(c->ic_vht_ch_freq1, c->ic_flags)); + if (IEEE80211_IS_CHAN_HT40U(c)) { return (c->ic_freq + 10); } @@ -1283,12 +1494,15 @@ } /* - * For now, no 80+80 support; this is zero. + * For now, no 80+80 support; it will likely always return 0. */ uint32_t ieee80211_get_channel_center_freq2(const struct ieee80211_channel *c) { + if (IEEE80211_IS_CHAN_VHT(c) && (c->ic_vht_ch_freq2 != 0)) + return (ieee80211_ieee2mhz(c->ic_vht_ch_freq2, c->ic_flags)); + return (0); } @@ -1296,6 +1510,7 @@ * Adds channels into specified channel list (ieee[] array must be sorted). * Channels are already sorted. */ +/* XXX VHT? */ static int add_chanlist(struct ieee80211_channel chans[], int maxchans, int *nchans, const uint8_t ieee[], int nieee, uint32_t flags[]) @@ -1302,16 +1517,70 @@ { uint16_t freq; int i, j, error; + int is_vht; for (i = 0; i < nieee; i++) { freq = ieee80211_ieee2mhz(ieee[i], flags[0]); for (j = 0; flags[j] != 0; j++) { + /* + * Notes: + * + HT40 and VHT40 channels occur together, so + * we need to be careful that we actually allow that. + * + VHT80, VHT160 will coexist with HT40/VHT40, so + * make sure it's not skipped because of the overlap + * check used for (V)HT40. + */ + is_vht = !! (flags[j] & IEEE80211_CHAN_VHT); + + /* + * Test for VHT80. + * XXX This is all very broken right now. + * What we /should/ do is: + * + * + check that the frequency is in the list of + * allowed VHT80 ranges; and + * + the other 3 channels in the list are actually + * also available. + */ + if (is_vht && flags[j] & IEEE80211_CHAN_VHT80) + if (! is_vht80_valid_freq(freq)) + continue; + + /* + * Test for (V)HT40. + * + * This is also a fall through from VHT80; as we only + * allow a VHT80 channel if the VHT40 combination is + * also valid. If the VHT40 form is not valid then + * we certainly can't do VHT80.. + */ if (flags[j] & IEEE80211_CHAN_HT40D) + /* + * Can't have a "lower" channel if we are the + * first channel. + * + * Can't have a "lower" channel if it's below/ + * within 20MHz of the first channel. + * + * Can't have a "lower" channel if the channel + * below it is not 20MHz away. + */ if (i == 0 || ieee[i] < ieee[0] + 4 || freq - 20 != ieee80211_ieee2mhz(ieee[i] - 4, flags[j])) continue; if (flags[j] & IEEE80211_CHAN_HT40U) + /* + * Can't have an "upper" channel if we are + * the last channel. + * + * Can't have an "upper" channel be above the + * last channel in the list. + * + * Can't have an "upper" channel if the next + * channel according to the math isn't 20MHz + * away. (Likely for channel 13/14.) + */ if (i == nieee - 1 || ieee[i] + 4 > ieee[nieee - 1] || freq + 20 != @@ -1340,6 +1609,7 @@ { uint32_t flags[IEEE80211_MODE_MAX]; + /* XXX no VHT for now */ getflags_2ghz(bands, flags, ht40); KASSERT(flags[0] != 0, ("%s: no correct mode provided\n", __func__)); @@ -1352,8 +1622,15 @@ int ht40) { uint32_t flags[IEEE80211_MODE_MAX]; + int vht80 = 0; - getflags_5ghz(bands, flags, ht40); + /* + * For now, assume VHT == VHT80 support as a minimum. + */ + if (isset(bands, IEEE80211_MODE_VHT_5GHZ)) + vht80 = 1; + + getflags_5ghz(bands, flags, ht40, vht80); KASSERT(flags[0] != 0, ("%s: no correct mode provided\n", __func__)); return (add_chanlist(chans, maxchans, nchans, ieee, nieee, flags)); @@ -1492,6 +1769,8 @@ [IEEE80211_MODE_QUARTER] = IFM_IEEE80211_11A, /* XXX */ [IEEE80211_MODE_11NA] = IFM_IEEE80211_11NA, [IEEE80211_MODE_11NG] = IFM_IEEE80211_11NG, + [IEEE80211_MODE_VHT_2GHZ] = IFM_IEEE80211_VHT2G, + [IEEE80211_MODE_VHT_5GHZ] = IFM_IEEE80211_VHT5G, }; u_int mopt; @@ -1604,6 +1883,19 @@ if (rate > maxrate) maxrate = rate; } + + /* + * Add VHT media. + */ + for (; mode <= IEEE80211_MODE_VHT_5GHZ; mode++) { + if (isclr(ic->ic_modecaps, mode)) + continue; + addmedia(media, caps, addsta, mode, IFM_AUTO); + addmedia(media, caps, addsta, mode, IFM_IEEE80211_VHT); + + /* XXX TODO: VHT maxrate */ + } + return maxrate; } @@ -1639,6 +1931,7 @@ printf("\n"); } ieee80211_ht_announce(ic); + ieee80211_vht_announce(ic); } void Index: sys/net80211/ieee80211_adhoc.c =================================================================== --- sys/net80211/ieee80211_adhoc.c (revision 311663) +++ sys/net80211/ieee80211_adhoc.c (working copy) @@ -822,10 +822,14 @@ #if 0 if (scan.htcap != NULL && scan.htinfo != NULL && (vap->iv_flags_ht & IEEE80211_FHT_HT)) { - if (ieee80211_ht_updateparams(ni, + ieee80211_ht_updateparams(ni, scan.htcap, scan.htinfo)) + if (ieee80211_ht_updateparams_final(ni, + scan.htcap, scan.htinfo)) ht_state_change = 1; } + + /* XXX same for VHT? */ #endif if (ni != NULL) { IEEE80211_RSSI_LPF(ni->ni_avgrssi, rssi); Index: sys/net80211/ieee80211_hostap.c =================================================================== --- sys/net80211/ieee80211_hostap.c (revision 311663) +++ sys/net80211/ieee80211_hostap.c (working copy) @@ -62,6 +62,7 @@ #include #endif #include +#include #define IEEE80211_RATE2MBS(r) (((r) & IEEE80211_RATE_VAL) / 2) @@ -1745,6 +1746,7 @@ struct ieee80211_frame *wh; uint8_t *frm, *efrm, *sfrm; uint8_t *ssid, *rates, *xrates, *wpa, *rsn, *wme, *ath, *htcap; + uint8_t *vhtcap, *vhtinfo; int reassoc, resp; uint8_t rate; @@ -2042,6 +2044,7 @@ if (reassoc) frm += 6; /* ignore current AP info */ ssid = rates = xrates = wpa = rsn = wme = ath = htcap = NULL; + vhtcap = vhtinfo = NULL; sfrm = frm; while (efrm - frm > 1) { IEEE80211_VERIFY_LENGTH(efrm - frm, frm[1] + 2, return); @@ -2061,6 +2064,12 @@ case IEEE80211_ELEMID_HTCAP: htcap = frm; break; + case IEEE80211_ELEMID_VHT_CAP: + vhtcap = frm; + break; + case IEEE80211_ELEMID_VHT_OPMODE: + vhtinfo = frm; + break; case IEEE80211_ELEMID_VENDOR: if (iswpaoui(frm)) wpa = frm; @@ -2135,10 +2144,22 @@ vap->iv_stats.is_rx_assoc_norate++; return; } + /* * Do HT rate set handling and setup HT node state. */ ni->ni_chan = vap->iv_bss->ni_chan; + + /* VHT */ + if (IEEE80211_IS_CHAN_VHT(ni->ni_chan)) { + /* XXX TODO; see below */ + printf("%s: VHT TODO!\n", __func__); + ieee80211_vht_node_init(ni); + ieee80211_vht_update_cap(ni, vhtcap, vhtinfo); + } else if (ni->ni_flags & IEEE80211_NODE_VHT) + ieee80211_vht_node_cleanup(ni); + + /* HT */ if (IEEE80211_IS_CHAN_HT(ni->ni_chan) && htcap != NULL) { rate = ieee80211_setup_htrates(ni, htcap, IEEE80211_F_DOFMCS | IEEE80211_F_DONEGO | @@ -2153,6 +2174,12 @@ ieee80211_ht_updatehtcap(ni, htcap); } else if (ni->ni_flags & IEEE80211_NODE_HT) ieee80211_ht_node_cleanup(ni); + + /* Finally - this will use HT/VHT info to change node channel */ + if (IEEE80211_IS_CHAN_HT(ni->ni_chan) && htcap != NULL) { + ieee80211_ht_updatehtcap_final(ni, htcap); + } + #ifdef IEEE80211_SUPPORT_SUPERG /* Always do ff node cleanup; for A-MSDU */ ieee80211_ff_node_cleanup(ni); Index: sys/net80211/ieee80211_ht.c =================================================================== --- sys/net80211/ieee80211_ht.c (revision 311663) +++ sys/net80211/ieee80211_ht.c (working copy) @@ -1490,9 +1490,12 @@ } /* - * Handle 11n channel switch. Use the received HT ie's to - * identify the right channel to use. If we cannot locate it - * in the channel table then fallback to legacy operation. + * Handle 11n/11ac channel switch. + * + * Use the received HT/VHT ie's to identify the right channel to use. + * If we cannot locate it in the channel table then fallback to + * legacy operation. + * * Note that we use this information to identify the node's * channel only; the caller is responsible for insuring any * required channel change is done (e.g. in sta mode when @@ -1499,7 +1502,7 @@ * parsing the contents of a beacon frame). */ static int -htinfo_update_chw(struct ieee80211_node *ni, int htflags) +htinfo_update_chw(struct ieee80211_node *ni, int htflags, int vhtflags) { struct ieee80211com *ic = ni->ni_ic; struct ieee80211_channel *c; @@ -1506,36 +1509,99 @@ int chanflags; int ret = 0; - chanflags = (ni->ni_chan->ic_flags &~ IEEE80211_CHAN_HT) | htflags; - if (chanflags != ni->ni_chan->ic_flags) { - /* XXX not right for ht40- */ - c = ieee80211_find_channel(ic, ni->ni_chan->ic_freq, chanflags); - if (c == NULL && (htflags & IEEE80211_CHAN_HT40)) { - /* - * No HT40 channel entry in our table; fall back - * to HT20 operation. This should not happen. - */ - c = findhtchan(ic, ni->ni_chan, IEEE80211_CHAN_HT20); + /* + * First step - do HT/VHT only channel lookup based on operating mode + * flags. This involves masking out the VHT flags as well. + * Otherwise we end up doing the full channel walk each time + * we trigger this, which is expensive. + */ + chanflags = (ni->ni_chan->ic_flags &~ + (IEEE80211_CHAN_HT | IEEE80211_CHAN_VHT)) | htflags | vhtflags; + + if (chanflags == ni->ni_chan->ic_flags) + goto done; + + /* + * If HT /or/ VHT flags have changed then check both. + * We need to start by picking a HT channel anyway. + */ + + c = NULL; + chanflags = (ni->ni_chan->ic_flags &~ + (IEEE80211_CHAN_HT | IEEE80211_CHAN_VHT)) | htflags; + /* XXX not right for ht40- */ + c = ieee80211_find_channel(ic, ni->ni_chan->ic_freq, chanflags); + if (c == NULL && (htflags & IEEE80211_CHAN_HT40)) { + /* + * No HT40 channel entry in our table; fall back + * to HT20 operation. This should not happen. + */ + c = findhtchan(ic, ni->ni_chan, IEEE80211_CHAN_HT20); #if 0 - IEEE80211_NOTE(ni->ni_vap, - IEEE80211_MSG_ASSOC | IEEE80211_MSG_11N, ni, - "no HT40 channel (freq %u), falling back to HT20", - ni->ni_chan->ic_freq); + IEEE80211_NOTE(ni->ni_vap, + IEEE80211_MSG_ASSOC | IEEE80211_MSG_11N, ni, + "no HT40 channel (freq %u), falling back to HT20", + ni->ni_chan->ic_freq); #endif - /* XXX stat */ - } - if (c != NULL && c != ni->ni_chan) { - IEEE80211_NOTE(ni->ni_vap, - IEEE80211_MSG_ASSOC | IEEE80211_MSG_11N, ni, - "switch station to HT%d channel %u/0x%x", - IEEE80211_IS_CHAN_HT40(c) ? 40 : 20, - c->ic_freq, c->ic_flags); - ni->ni_chan = c; - ret = 1; - } - /* NB: caller responsible for forcing any channel change */ + /* XXX stat */ } - /* update node's tx channel width */ + + /* Nothing found - leave it alone; move onto VHT */ + if (c == NULL) + c = ni->ni_chan; + + /* + * If it's non-HT, then bail out now. + */ + if (! IEEE80211_IS_CHAN_HT(c)) { + IEEE80211_NOTE(ni->ni_vap, + IEEE80211_MSG_ASSOC | IEEE80211_MSG_11N, ni, + "not HT; skipping VHT check (%u/0x%x)", + c->ic_freq, c->ic_flags); + goto done; + } + + /* + * Next step - look at the current VHT flags and determine + * if we need to upgrade. Mask out the VHT and HT flags since + * the vhtflags field will already have the correct HT + * flags to use. + */ + if (IEEE80211_CONF_VHT(ic) && ni->ni_vhtcap != 0 && vhtflags != 0) { + chanflags = (c->ic_flags + &~ (IEEE80211_CHAN_HT | IEEE80211_CHAN_VHT)) + | vhtflags; + IEEE80211_NOTE(ni->ni_vap, + IEEE80211_MSG_ASSOC | IEEE80211_MSG_11N, + ni, + "%s: VHT; chanwidth=0x%02x; vhtflags=0x%08x", + __func__, ni->ni_vht_chanwidth, vhtflags); + + IEEE80211_NOTE(ni->ni_vap, + IEEE80211_MSG_ASSOC | IEEE80211_MSG_11N, + ni, + "%s: VHT; trying lookup for %d/0x%08x", + __func__, c->ic_freq, chanflags); + c = ieee80211_find_channel(ic, c->ic_freq, chanflags); + } + + /* Finally, if it's changed */ + if (c != NULL && c != ni->ni_chan) { + IEEE80211_NOTE(ni->ni_vap, + IEEE80211_MSG_ASSOC | IEEE80211_MSG_11N, ni, + "switch station to %s%d channel %u/0x%x", + IEEE80211_IS_CHAN_VHT(c) ? "VHT" : "HT", + IEEE80211_IS_CHAN_VHT80(c) ? 80 : + (IEEE80211_IS_CHAN_HT40(c) ? 40 : 20), + c->ic_freq, c->ic_flags); + ni->ni_chan = c; + ret = 1; + } + /* NB: caller responsible for forcing any channel change */ + +done: + /* update node's (11n) tx channel width */ + /* TODO: I think this is just for debug, and can become 80/160 too */ ni->ni_chw = IEEE80211_IS_CHAN_HT40(ni->ni_chan)? 40 : 20; return (ret); } @@ -1587,15 +1653,18 @@ /* * Parse and update HT-related state extracted from * the HT cap and info ie's. + * + * This is called from the STA management path and + * the ieee80211_node_join() path. It will take into + * account the IEs discovered during scanning and + * adjust things accordingly. */ -int +void ieee80211_ht_updateparams(struct ieee80211_node *ni, const uint8_t *htcapie, const uint8_t *htinfoie) { struct ieee80211vap *vap = ni->ni_vap; const struct ieee80211_ie_htinfo *htinfo; - int htflags; - int ret = 0; ieee80211_parse_htcap(ni, htcapie); if (vap->iv_htcaps & IEEE80211_HTCAP_SMPS) @@ -1607,8 +1676,49 @@ htinfo = (const struct ieee80211_ie_htinfo *) htinfoie; htinfo_parse(ni, htinfo); + /* + * Defer the node channel change; we need to now + * update VHT parameters before we do it. + */ + + if ((htinfo->hi_byte1 & IEEE80211_HTINFO_RIFSMODE_PERM) && + (vap->iv_flags_ht & IEEE80211_FHT_RIFS)) + ni->ni_flags |= IEEE80211_NODE_RIFS; + else + ni->ni_flags &= ~IEEE80211_NODE_RIFS; +} + +/* + * Final part of updating the HT parameters. + * + * This is called from the STA management path and + * the ieee80211_node_join() path. It will take into + * account the IEs discovered during scanning and + * adjust things accordingly. + * + * This is done after a call to ieee80211_ht_updateparams() + * because it (and the upcoming VHT version of updateparams) + * needs to ensure everything is parsed before htinfo_update_chw() + * is called - which will change the channel config for the + * node for us. + * + * XXX TODO: rely on the node htcap/htinfo (and VHT versions) + * being setup and remove the passed in versions. + */ +int +ieee80211_ht_updateparams_final(struct ieee80211_node *ni, + const uint8_t *htcapie, const uint8_t *htinfoie) +{ + struct ieee80211vap *vap = ni->ni_vap; + const struct ieee80211_ie_htinfo *htinfo; + int htflags, vhtflags; + int ret = 0; + + htinfo = (const struct ieee80211_ie_htinfo *) htinfoie; + htflags = (vap->iv_flags_ht & IEEE80211_FHT_HT) ? IEEE80211_CHAN_HT20 : 0; + /* NB: honor operating mode constraint */ if ((htinfo->hi_byte1 & IEEE80211_HTINFO_TXWIDTH_2040) && (vap->iv_flags_ht & IEEE80211_FHT_USEHT40)) { @@ -1617,15 +1727,84 @@ else if (ni->ni_ht2ndchan == IEEE80211_HTINFO_2NDCHAN_BELOW) htflags = IEEE80211_CHAN_HT40D; } - if (htinfo_update_chw(ni, htflags)) + + /* + * VHT flags - do much the same; check whether VHT is available + * and if so, what our ideal channel use would be based on our + * capabilities and the (pre-parsed) VHT info IE. + * + * XXX TODO: this is a copy of the hostap version. + * I am worried it shouldn't be. + * + * The STA version uses the htinfo field for whether to + * use HT20/HT40. The AP version uses the htcap field. + * I'll have to look at the specification to see exactly + * what and why. For 11ac, VHTCAP doesn't have what + * we want, only VHT op mode (VHTINFO here) does. + * + * Chances are I'll have to pull this extra information + * out of an extended operating mode IE later on and + * add it to the mix. + */ + vhtflags = 0; + if (vap->iv_flags_vht & IEEE80211_FVHT_VHT) { + if ((ni->ni_vht_chanwidth == IEEE80211_VHT_CHANWIDTH_160MHZ) && + (vap->iv_vhtcaps & IEEE80211_VHTCAP_SUPP_CHAN_WIDTH_160MHZ) && + (vap->iv_flags_vht & IEEE80211_FVHT_USEVHT160)) { + vhtflags = IEEE80211_CHAN_VHT160; + /* Mirror the HT40 flags */ + if (htflags == IEEE80211_CHAN_HT40U) { + vhtflags |= IEEE80211_CHAN_HT40U; + } else if (htflags == IEEE80211_CHAN_HT40D) { + vhtflags |= IEEE80211_CHAN_HT40D; + } + } else if ((ni->ni_vht_chanwidth == IEEE80211_VHT_CHANWIDTH_80P80MHZ) && + (vap->iv_vhtcaps & IEEE80211_VHTCAP_SUPP_CHAN_WIDTH_160_80PLUS80MHZ) && + (vap->iv_flags_vht & IEEE80211_FVHT_USEVHT80P80)) { + vhtflags = IEEE80211_CHAN_VHT80_80; + /* Mirror the HT40 flags */ + if (htflags == IEEE80211_CHAN_HT40U) { + vhtflags |= IEEE80211_CHAN_HT40U; + } else if (htflags == IEEE80211_CHAN_HT40D) { + vhtflags |= IEEE80211_CHAN_HT40D; + } + } else if ((ni->ni_vht_chanwidth == IEEE80211_VHT_CHANWIDTH_80MHZ) && + (vap->iv_flags_vht & IEEE80211_FVHT_USEVHT80)) { + vhtflags = IEEE80211_CHAN_VHT80; + /* Mirror the HT40 flags */ + if (htflags == IEEE80211_CHAN_HT40U) { + vhtflags |= IEEE80211_CHAN_HT40U; + } else if (htflags == IEEE80211_CHAN_HT40D) { + vhtflags |= IEEE80211_CHAN_HT40D; + } + } else if (ni->ni_vht_chanwidth == IEEE80211_VHT_CHANWIDTH_USE_HT) { + /* Mirror the HT40 flags */ + /* + * XXX TODO: if ht40 is disabled, but vht40 isn't + * disabled then this logic will get very, very sad. + * It's quite possible the only sane thing to do is + * to not have vht40 as an option, and just obey + * 'ht40' as that flag. + */ + if ((htflags == IEEE80211_CHAN_HT40U) && + (vap->iv_flags_vht & IEEE80211_FVHT_USEVHT40)) { + vhtflags = IEEE80211_CHAN_VHT40U; + } else if (htflags == IEEE80211_CHAN_HT40D && + (vap->iv_flags_vht & IEEE80211_FVHT_USEVHT40)) { + vhtflags = IEEE80211_CHAN_VHT40D; + } else if (htflags == IEEE80211_CHAN_HT20) { + vhtflags = IEEE80211_CHAN_VHT20; + } + } else { + vhtflags = IEEE80211_CHAN_VHT20; + } + } + + + /* XXX should do the same thing for vhtflags */ + if (htinfo_update_chw(ni, htflags, vhtflags)) ret = 1; - if ((htinfo->hi_byte1 & IEEE80211_HTINFO_RIFSMODE_PERM) && - (vap->iv_flags_ht & IEEE80211_FHT_RIFS)) - ni->ni_flags |= IEEE80211_NODE_RIFS; - else - ni->ni_flags &= ~IEEE80211_NODE_RIFS; - return (ret); } @@ -1632,18 +1811,33 @@ /* * Parse and update HT-related state extracted from the HT cap ie * for a station joining an HT BSS. + * + * This is called from the hostap path for each station. */ void ieee80211_ht_updatehtcap(struct ieee80211_node *ni, const uint8_t *htcapie) { struct ieee80211vap *vap = ni->ni_vap; - int htflags; ieee80211_parse_htcap(ni, htcapie); if (vap->iv_htcaps & IEEE80211_HTCAP_SMPS) htcap_update_mimo_ps(ni); htcap_update_shortgi(ni); +} +/* + * Called once HT and VHT capabilities are parsed in hostap mode - + * this will adjust the channel configuration of the given node + * based on the configuration and capabilities. + */ +void +ieee80211_ht_updatehtcap_final(struct ieee80211_node *ni, + const uint8_t *htcapie) +{ + struct ieee80211vap *vap = ni->ni_vap; + int htflags; + int vhtflags; + /* NB: honor operating mode constraint */ /* XXX 40 MHz intolerant */ htflags = (vap->iv_flags_ht & IEEE80211_FHT_HT) ? @@ -1655,7 +1849,72 @@ else if (IEEE80211_IS_CHAN_HT40D(vap->iv_bss->ni_chan)) htflags = IEEE80211_CHAN_HT40D; } - (void) htinfo_update_chw(ni, htflags); + /* + * VHT flags - do much the same; check whether VHT is available + * and if so, what our ideal channel use would be based on our + * capabilities and the (pre-parsed) VHT info IE. + * + * Note: VHT mandates HT20, HT40, VHT20, VHT40 and VHT80. + * We currently don't have a bitmap that says what the driver + * supports, only the vhtcap bits that allow for 80+80MHz and 160MHz. + * + * If this is truely the right way then refactor this code out. + */ + vhtflags = 0; + if (vap->iv_flags_vht & IEEE80211_FVHT_VHT) { + if ((ni->ni_vht_chanwidth == IEEE80211_VHT_CHANWIDTH_160MHZ) && + (vap->iv_vhtcaps & IEEE80211_VHTCAP_SUPP_CHAN_WIDTH_160MHZ) && + (vap->iv_flags_vht & IEEE80211_FVHT_USEVHT160)) { + vhtflags = IEEE80211_CHAN_VHT160; + /* Mirror the HT40 flags */ + if (htflags == IEEE80211_CHAN_HT40U) { + vhtflags |= IEEE80211_CHAN_HT40U; + } else if (htflags == IEEE80211_CHAN_HT40D) { + vhtflags |= IEEE80211_CHAN_HT40D; + } + } else if ((ni->ni_vht_chanwidth == IEEE80211_VHT_CHANWIDTH_80P80MHZ) && + (vap->iv_vhtcaps & IEEE80211_VHTCAP_SUPP_CHAN_WIDTH_160_80PLUS80MHZ) && + (vap->iv_flags_vht & IEEE80211_FVHT_USEVHT80P80)) { + vhtflags = IEEE80211_CHAN_VHT80_80; + /* Mirror the HT40 flags */ + if (htflags == IEEE80211_CHAN_HT40U) { + vhtflags |= IEEE80211_CHAN_HT40U; + } else if (htflags == IEEE80211_CHAN_HT40D) { + vhtflags |= IEEE80211_CHAN_HT40D; + } + } else if ((ni->ni_vht_chanwidth == IEEE80211_VHT_CHANWIDTH_80MHZ) && + (vap->iv_flags_vht & IEEE80211_FVHT_USEVHT80)) { + vhtflags = IEEE80211_CHAN_VHT80; + /* Mirror the HT40 flags */ + if (htflags == IEEE80211_CHAN_HT40U) { + vhtflags |= IEEE80211_CHAN_HT40U; + } else if (htflags == IEEE80211_CHAN_HT40D) { + vhtflags |= IEEE80211_CHAN_HT40D; + } + } else if (ni->ni_vht_chanwidth == IEEE80211_VHT_CHANWIDTH_USE_HT) { + /* Mirror the HT40 flags */ + /* + * XXX TODO: if ht40 is disabled, but vht40 isn't + * disabled then this logic will get very, very sad. + * It's quite possible the only sane thing to do is + * to not have vht40 as an option, and just obey + * 'ht40' as that flag. + */ + if ((htflags == IEEE80211_CHAN_HT40U) && + (vap->iv_flags_vht & IEEE80211_FVHT_USEVHT40)) { + vhtflags = IEEE80211_CHAN_VHT40U; + } else if (htflags == IEEE80211_CHAN_HT40D && + (vap->iv_flags_vht & IEEE80211_FVHT_USEVHT40)) { + vhtflags = IEEE80211_CHAN_VHT40D; + } else if (htflags == IEEE80211_CHAN_HT20) { + vhtflags = IEEE80211_CHAN_VHT20; + } + } else { + vhtflags = IEEE80211_CHAN_VHT20; + } + } + + (void) htinfo_update_chw(ni, htflags, vhtflags); } /* @@ -2229,6 +2488,10 @@ dialogtoken = (tokens+1) % 63; /* XXX */ tid = tap->txa_tid; + + /* + * XXX TODO: This is racy with any other parallel TX going on. :( + */ tap->txa_start = ni->ni_txseqs[tid]; args[0] = dialogtoken; Index: sys/net80211/ieee80211_ht.h =================================================================== --- sys/net80211/ieee80211_ht.h (revision 311663) +++ sys/net80211/ieee80211_ht.h (working copy) @@ -201,9 +201,12 @@ void ieee80211_ht_timeout(struct ieee80211com *); void ieee80211_parse_htcap(struct ieee80211_node *, const uint8_t *); void ieee80211_parse_htinfo(struct ieee80211_node *, const uint8_t *); -int ieee80211_ht_updateparams(struct ieee80211_node *, const uint8_t *, +void ieee80211_ht_updateparams(struct ieee80211_node *, const uint8_t *, const uint8_t *); +int ieee80211_ht_updateparams_final(struct ieee80211_node *, const uint8_t *, + const uint8_t *); void ieee80211_ht_updatehtcap(struct ieee80211_node *, const uint8_t *); +void ieee80211_ht_updatehtcap_final(struct ieee80211_node *, const uint8_t *); int ieee80211_ampdu_request(struct ieee80211_node *, struct ieee80211_tx_ampdu *); void ieee80211_ampdu_stop(struct ieee80211_node *, Index: sys/net80211/ieee80211_input.c =================================================================== --- sys/net80211/ieee80211_input.c (revision 311663) +++ sys/net80211/ieee80211_input.c (working copy) @@ -494,6 +494,8 @@ scan->status = 0; /* * beacon/probe response frame format + * + * XXX Update from 802.11-2012 - eg where HT is * [8] time stamp * [2] beacon interval * [2] capability information @@ -508,6 +510,8 @@ * [tlv] WPA or RSN * [tlv] HT capabilities * [tlv] HT information + * [tlv] VHT capabilities + * [tlv] VHT information * [tlv] Atheros capabilities * [tlv] Mesh ID * [tlv] Mesh Configuration @@ -585,6 +589,12 @@ case IEEE80211_ELEMID_HTCAP: scan->htcap = frm; break; + case IEEE80211_ELEMID_VHT_CAP: + scan->vhtcap = frm; + break; + case IEEE80211_ELEMID_VHT_OPMODE: + scan->vhtopmode = frm; + break; case IEEE80211_ELEMID_RSN: scan->rsn = frm; break; @@ -718,6 +728,19 @@ sizeof(struct ieee80211_ie_htinfo)-2, scan->htinfo = NULL); } + + /* Process VHT IEs */ + if (scan->vhtcap != NULL) { + IEEE80211_VERIFY_LENGTH(scan->vhtcap[1], + sizeof(struct ieee80211_ie_vhtcap) - 2, + scan->vhtcap = NULL); + } + if (scan->vhtopmode != NULL) { + IEEE80211_VERIFY_LENGTH(scan->vhtopmode[1], + sizeof(struct ieee80211_ie_vht_operation) - 2, + scan->vhtopmode = NULL); + } + return scan->status; } @@ -838,6 +861,9 @@ } break; #endif + case IEEE80211_ACTION_CAT_VHT: + printf("%s: TODO: VHT handling!\n", __func__); + break; } return 0; } Index: sys/net80211/ieee80211_node.c =================================================================== --- sys/net80211/ieee80211_node.c (revision 311663) +++ sys/net80211/ieee80211_node.c (working copy) @@ -53,6 +53,7 @@ #include #include #include +#include #include @@ -412,9 +413,13 @@ /* XXX TODO: other bits and pieces - eg fast-frames? */ /* If we're an 11n channel then initialise the 11n bits */ - if (IEEE80211_IS_CHAN_HT(ni->ni_chan)) { + if (IEEE80211_IS_CHAN_VHT(ni->ni_chan)) { /* XXX what else? */ ieee80211_ht_node_init(ni); + ieee80211_vht_node_init(ni); + } else if (IEEE80211_IS_CHAN_HT(ni->ni_chan)) { + /* XXX what else? */ + ieee80211_ht_node_init(ni); } (void) ieee80211_sta_join1(ieee80211_ref_node(ni)); @@ -708,9 +713,42 @@ } /* + * Calculate VHT channel promotion flags for all vaps. + * This assumes ni_chan have been setup for each vap. + */ +static int +getvhtadjustflags(struct ieee80211com *ic) +{ + struct ieee80211vap *vap; + int flags; + + flags = 0; + /* XXX locking */ + TAILQ_FOREACH(vap, &ic->ic_vaps, iv_next) { + if (vap->iv_state < IEEE80211_S_RUN) + continue; + switch (vap->iv_opmode) { + case IEEE80211_M_WDS: + case IEEE80211_M_STA: + case IEEE80211_M_AHDEMO: + case IEEE80211_M_HOSTAP: + case IEEE80211_M_IBSS: + case IEEE80211_M_MBSS: + flags |= ieee80211_vhtchanflags(vap->iv_bss->ni_chan); + break; + default: + break; + } + } + return flags; +} + +/* * Check if the current channel needs to change based on whether * any vap's are using HT20/HT40. This is used to sync the state * of ic_curchan after a channel width change on a running vap. + * + * Same applies for VHT. */ void ieee80211_sync_curchan(struct ieee80211com *ic) @@ -718,6 +756,8 @@ struct ieee80211_channel *c; c = ieee80211_ht_adjust_channel(ic, ic->ic_curchan, gethtadjustflags(ic)); + c = ieee80211_vht_adjust_channel(ic, c, getvhtadjustflags(ic)); + if (c != ic->ic_curchan) { ic->ic_curchan = c; ic->ic_curmode = ieee80211_chan2mode(ic->ic_curchan); @@ -743,10 +783,23 @@ * set of running vap's. This assumes we are called * after ni_chan is setup for each vap. */ + /* XXX VHT? */ /* NB: this assumes IEEE80211_FHT_USEHT40 > IEEE80211_FHT_HT */ if (flags > ieee80211_htchanflags(c)) c = ieee80211_ht_adjust_channel(ic, c, flags); } + + /* + * VHT promotion - this will at least promote to VHT20/40 + * based on what HT has done; it may further promote the + * channel to VHT80 or above. + */ + if (ic->ic_vhtcaps != 0) { + int flags = getvhtadjustflags(ic); + if (flags > ieee80211_vhtchanflags(c)) + c = ieee80211_vht_adjust_channel(ic, c, flags); + } + ic->ic_bsschan = ic->ic_curchan = c; ic->ic_curmode = ieee80211_chan2mode(ic->ic_curchan); ic->ic_rt = ieee80211_get_ratetable(ic->ic_curchan); @@ -849,6 +902,7 @@ { struct ieee80211com *ic = vap->iv_ic; struct ieee80211_node *ni; + int do_ht = 0; ni = ieee80211_alloc_node(&ic->ic_sta, vap, se->se_macaddr); if (ni == NULL) { @@ -896,9 +950,13 @@ if (ni->ni_ies.tdma_ie != NULL) ieee80211_parse_tdma(ni, ni->ni_ies.tdma_ie); #endif + if (ni->ni_ies.vhtcap_ie != NULL) + ieee80211_parse_vhtcap(ni, ni->ni_ies.vhtcap_ie); + if (ni->ni_ies.vhtopmode_ie != NULL) + ieee80211_parse_vhtopmode(ni, ni->ni_ies.vhtopmode_ie); - /* XXX parse VHT IEs */ /* XXX parse BSSLOAD IE */ + /* XXX parse TXPWRENV IE */ /* XXX parse APCHANREP IE */ } @@ -929,7 +987,31 @@ ieee80211_setup_htrates(ni, ni->ni_ies.htcap_ie, IEEE80211_F_JOIN | IEEE80211_F_DOBRS); ieee80211_setup_basic_htrates(ni, ni->ni_ies.htinfo_ie); + do_ht = 1; } + + /* + * Setup VHT state for this node if it's available. + * Same as the above. + */ + if (ni->ni_ies.vhtopmode_ie != NULL && + ni->ni_ies.vhtcap_ie != NULL && + vap->iv_flags_vht & IEEE80211_FVHT_VHT) { + ieee80211_vht_node_init(ni); + ieee80211_vht_updateparams(ni, + ni->ni_ies.vhtcap_ie, + ni->ni_ies.vhtopmode_ie); + ieee80211_setup_vht_rates(ni, ni->ni_ies.vhtcap_ie, + ni->ni_ies.vhtopmode_ie); + do_ht = 1; + } + + /* Finally do the node channel change */ + if (do_ht) + ieee80211_ht_updateparams_final(ni, + ni->ni_ies.htcap_ie, + ni->ni_ies.htinfo_ie); + /* XXX else check for ath FF? */ /* XXX QoS? Difficult given that WME config is specific to a master */ @@ -1102,8 +1184,10 @@ "power save mode off, %u sta's in ps mode", vap->iv_ps_sta); } /* - * Cleanup any HT-related state. + * Cleanup any VHT and HT-related state. */ + if (ni->ni_flags & IEEE80211_NODE_VHT) + ieee80211_vht_node_cleanup(ni); if (ni->ni_flags & IEEE80211_NODE_HT) ieee80211_ht_node_cleanup(ni); #ifdef IEEE80211_SUPPORT_SUPERG @@ -1423,6 +1507,7 @@ if (vap->iv_flags & IEEE80211_F_FF) ni->ni_flags |= IEEE80211_NODE_FF; #endif + /* XXX VHT */ if ((ic->ic_htcaps & IEEE80211_HTC_HT) && (vap->iv_flags_ht & IEEE80211_FHT_HT)) { /* @@ -1431,6 +1516,9 @@ * ni_chan will be adjusted to an HT channel. */ ieee80211_ht_wds_init(ni); + if (vap->iv_flags_vht & IEEE80211_FVHT_VHT) { + printf("%s: TODO: vht_wds_init\n", __func__); + } } else { struct ieee80211_channel *c = ni->ni_chan; /* @@ -1638,7 +1726,7 @@ const struct ieee80211_frame *wh, const struct ieee80211_scanparams *sp) { - int do_ht_setup = 0; + int do_ht_setup = 0, do_vht_setup = 0; ni->ni_esslen = sp->ssid[1]; memcpy(ni->ni_essid, sp->ssid + 2, sp->ssid[1]); @@ -1670,11 +1758,23 @@ if (ni->ni_ies.htinfo_ie != NULL) ieee80211_parse_htinfo(ni, ni->ni_ies.htinfo_ie); + if (ni->ni_ies.vhtcap_ie != NULL) + ieee80211_parse_vhtcap(ni, ni->ni_ies.vhtcap_ie); + if (ni->ni_ies.vhtopmode_ie != NULL) + ieee80211_parse_vhtopmode(ni, ni->ni_ies.vhtopmode_ie); + if ((ni->ni_ies.htcap_ie != NULL) && (ni->ni_ies.htinfo_ie != NULL) && (ni->ni_vap->iv_flags_ht & IEEE80211_FHT_HT)) { do_ht_setup = 1; } + + if ((ni->ni_ies.vhtcap_ie != NULL) && + (ni->ni_ies.vhtopmode_ie != NULL) && + (ni->ni_vap->iv_flags_vht & IEEE80211_FVHT_VHT)) { + do_vht_setup = 1; + } + } /* NB: must be after ni_chan is setup */ @@ -1697,10 +1797,29 @@ IEEE80211_F_JOIN | IEEE80211_F_DOBRS); ieee80211_setup_basic_htrates(ni, ni->ni_ies.htinfo_ie); + + if (do_vht_setup) { + ieee80211_vht_node_init(ni); + ieee80211_vht_updateparams(ni, + ni->ni_ies.vhtcap_ie, + ni->ni_ies.vhtopmode_ie); + ieee80211_setup_vht_rates(ni, + ni->ni_ies.vhtcap_ie, + ni->ni_ies.vhtopmode_ie); + } + + /* + * Finally do the channel upgrade/change based + * on the HT/VHT configuration. + */ + ieee80211_ht_updateparams_final(ni, + ni->ni_ies.vhtcap_ie, + ni->ni_ies.vhtopmode_ie); + ieee80211_node_setuptxparms(ni); ieee80211_ratectl_node_init(ni); - /* Reassociate; we're now 11n */ + /* Reassociate; we're now 11n/11ac */ /* * XXX TODO: this is the wrong thing to do - * we're calling it with isnew=1 so the ath(4) @@ -2365,6 +2484,7 @@ IEEE80211_LOCK(ic); ieee80211_erp_timeout(ic); ieee80211_ht_timeout(ic); + ieee80211_vht_timeout(ic); IEEE80211_UNLOCK(ic); } callout_reset(&ic->ic_inact, IEEE80211_INACT_WAIT*hz, @@ -2462,8 +2582,9 @@ printf("\thtcap %x htparam %x htctlchan %u ht2ndchan %u\n", ni->ni_htcap, ni->ni_htparam, ni->ni_htctlchan, ni->ni_ht2ndchan); - printf("\thtopmode %x htstbc %x chw %u\n", + printf("\thtopmode %x htstbc %x htchw %u\n", ni->ni_htopmode, ni->ni_htstbc, ni->ni_chw); + /* XXX VHT state */ } void @@ -2594,6 +2715,8 @@ if (IEEE80211_IS_CHAN_HT(ic->ic_bsschan)) ieee80211_ht_node_join(ni); + if (IEEE80211_IS_CHAN_VHT(ic->ic_bsschan)) + ieee80211_vht_node_join(ni); if (IEEE80211_IS_CHAN_ANYG(ic->ic_bsschan) && IEEE80211_IS_CHAN_FULL(ic->ic_bsschan)) ieee80211_node_join_11g(ni); @@ -2603,6 +2726,9 @@ } else newassoc = 0; + /* + * XXX VHT - should log VHT channel width, etc + */ IEEE80211_NOTE(vap, IEEE80211_MSG_ASSOC | IEEE80211_MSG_DEBUG, ni, "station associated at aid %d: %s preamble, %s slot time%s%s%s%s%s%s%s%s", IEEE80211_NODE_AID(ni), @@ -2774,6 +2900,8 @@ vap->iv_sta_assoc--; ic->ic_sta_assoc--; + if (IEEE80211_IS_CHAN_VHT(ic->ic_bsschan)) + ieee80211_vht_node_leave(ni); if (IEEE80211_IS_CHAN_HT(ic->ic_bsschan)) ieee80211_ht_node_leave(ni); if (IEEE80211_IS_CHAN_ANYG(ic->ic_bsschan) && Index: sys/net80211/ieee80211_output.c =================================================================== --- sys/net80211/ieee80211_output.c (revision 311663) +++ sys/net80211/ieee80211_output.c (working copy) @@ -58,6 +58,7 @@ #endif #include #include +#include #if defined(INET) || defined(INET6) #include @@ -764,6 +765,16 @@ } *(uint16_t *)&wh->i_dur[0] = 0; + /* + * XXX TODO: this is what the TX lock is for. + * Here we're incrementing sequence numbers, and they + * need to be in lock-step with what the driver is doing + * both in TX ordering and crypto encap (IV increment.) + * + * If the driver does seqno itself, then we can skip + * assigning sequence numbers here, and we can avoid + * requiring the TX lock. + */ tap = &ni->ni_tx_ampdu[tid]; if (tid != IEEE80211_NONQOS_TID && IEEE80211_AMPDU_RUNNING(tap)) m->m_flags |= M_AMPDU_MPDU; @@ -1542,6 +1553,11 @@ if (is_amsdu) qos[0] |= IEEE80211_QOS_AMSDU; + /* + * XXX TODO TX lock is needed for atomic updates of sequence + * numbers. If the driver does it, then don't do it here; + * and we don't need the TX lock held. + */ if ((m->m_flags & M_AMPDU_MPDU) == 0) { /* * NB: don't assign a sequence # to potential @@ -1561,6 +1577,11 @@ M_SEQNO_SET(m, seqno); } } else { + /* + * XXX TODO TX lock is needed for atomic updates of sequence + * numbers. If the driver does it, then don't do it here; + * and we don't need the TX lock held. + */ seqno = ni->ni_txseqs[IEEE80211_NONQOS_TID]++; *(uint16_t *)wh->i_seq = htole16(seqno << IEEE80211_SEQ_SEQ_SHIFT); @@ -2065,6 +2086,7 @@ * Send a probe request frame with the specified ssid * and any optional information element data. */ +/* XXX VHT? */ int ieee80211_send_probereq(struct ieee80211_node *ni, const uint8_t sa[IEEE80211_ADDR_LEN], @@ -2111,6 +2133,7 @@ * [tlv] RSN (optional) * [tlv] extended supported rates * [tlv] HT cap (optional) + * [tlv] VHT cap (optional) * [tlv] WPA (optional) * [tlv] user-specified ie's */ @@ -2119,7 +2142,8 @@ 2 + IEEE80211_NWID_LEN + 2 + IEEE80211_RATE_SIZE + sizeof(struct ieee80211_ie_htcap) - + sizeof(struct ieee80211_ie_htinfo) + + sizeof(struct ieee80211_ie_vhtcap) + + sizeof(struct ieee80211_ie_htinfo) /* XXX not needed? */ + sizeof(struct ieee80211_ie_wpa) + 2 + (IEEE80211_RATE_MAXSIZE - IEEE80211_RATE_SIZE) + sizeof(struct ieee80211_ie_wpa) @@ -2159,6 +2183,21 @@ frm = ieee80211_add_htcap_ch(frm, vap, c); } + /* + * XXX TODO: need to figure out what/how to update the + * VHT channel. + */ +#if 0 + (vap->iv_flags_vht & IEEE80211_FVHT_VHT) { + struct ieee80211_channel *c; + + c = ieee80211_ht_adjust_channel(ic, ic->ic_curchan, + vap->iv_flags_ht); + c = ieee80211_vht_adjust_channel(ic, c, vap->iv_flags_vht); + frm = ieee80211_add_vhtcap_ch(frm, vap, c); + } +#endif + frm = ieee80211_add_wpa(frm, vap); if (vap->iv_appie_probereq != NULL) frm = add_appie(frm, vap->iv_appie_probereq); @@ -2357,6 +2396,7 @@ case IEEE80211_FC0_SUBTYPE_ASSOC_REQ: case IEEE80211_FC0_SUBTYPE_REASSOC_REQ: + /* XXX VHT? */ /* * asreq frame format * [2] capability information @@ -2368,6 +2408,7 @@ * [4] power capability (optional) * [28] supported channels (optional) * [tlv] HT capabilities + * [tlv] VHT capabilities * [tlv] WME (optional) * [tlv] Vendor OUI HT capabilities (optional) * [tlv] Atheros capabilities (if negotiated) @@ -2385,6 +2426,7 @@ + 2 + 26 + sizeof(struct ieee80211_wme_info) + sizeof(struct ieee80211_ie_htcap) + + sizeof(struct ieee80211_ie_vhtcap) + 4 + sizeof(struct ieee80211_ie_htcap) #ifdef IEEE80211_SUPPORT_SUPERG + sizeof(struct ieee80211_ath_ie) @@ -2449,6 +2491,14 @@ ni->ni_ies.htcap_ie[0] == IEEE80211_ELEMID_HTCAP) { frm = ieee80211_add_htcap(frm, ni); } + + if ((vap->iv_flags_vht & IEEE80211_FVHT_VHT) && + IEEE80211_IS_CHAN_VHT(ni->ni_chan) && + ni->ni_ies.vhtcap_ie != NULL && + ni->ni_ies.vhtcap_ie[0] == IEEE80211_ELEMID_VHT_CAP) { + frm = ieee80211_add_vhtcap(frm, ni); + } + frm = ieee80211_add_wpa(frm, vap); if ((ic->ic_flags & IEEE80211_F_WME) && ni->ni_ies.wme_ie != NULL) @@ -2492,6 +2542,8 @@ * [tlv] extended supported rates * [tlv] HT capabilities (standard, if STA enabled) * [tlv] HT information (standard, if STA enabled) + * [tlv] VHT capabilities (standard, if STA enabled) + * [tlv] VHT information (standard, if STA enabled) * [tlv] WME (if configured and STA enabled) * [tlv] HT capabilities (vendor OUI, if STA enabled) * [tlv] HT information (vendor OUI, if STA enabled) @@ -2507,6 +2559,8 @@ + 2 + (IEEE80211_RATE_MAXSIZE - IEEE80211_RATE_SIZE) + sizeof(struct ieee80211_ie_htcap) + 4 + sizeof(struct ieee80211_ie_htinfo) + 4 + + sizeof(struct ieee80211_ie_vhtcap) + + sizeof(struct ieee80211_ie_vht_operation) + sizeof(struct ieee80211_wme_param) #ifdef IEEE80211_SUPPORT_SUPERG + sizeof(struct ieee80211_ath_ie) @@ -2545,6 +2599,10 @@ frm = ieee80211_add_htcap_vendor(frm, ni); frm = ieee80211_add_htinfo_vendor(frm, ni); } + if (ni->ni_flags & IEEE80211_NODE_VHT) { + frm = ieee80211_add_vhtcap(frm, ni); + frm = ieee80211_add_vhtinfo(frm, ni); + } #ifdef IEEE80211_SUPPORT_SUPERG if (IEEE80211_ATH_CAP(vap, ni, IEEE80211_F_ATHEROS)) frm = ieee80211_add_ath(frm, @@ -2600,6 +2658,7 @@ * Space is left to prepend and 802.11 header at the * front but it's left to the caller to fill in. */ +/* XXX VHT? */ struct mbuf * ieee80211_alloc_proberesp(struct ieee80211_node *bss, int legacy) { @@ -2934,6 +2993,7 @@ } } +/* XXX VHT? */ static void ieee80211_beacon_construct(struct mbuf *m, uint8_t *frm, struct ieee80211_node *ni) @@ -2946,6 +3006,10 @@ /* * beacon frame format + * + * TODO: update to 802.11-2012; a lot of stuff has changed; + * vendor extensions should be at the end, etc. + * * [8] time stamp * [2] beacon interval * [2] cabability information @@ -2957,11 +3021,34 @@ * [tlv] country (optional) * [3] power control (optional) * [5] channel switch announcement (CSA) (optional) + * XXX TODO: Quiet + * XXX TODO: IBSS DFS + * XXX TODO: TPC report * [tlv] extended rate phy (ERP) * [tlv] extended supported rates * [tlv] RSN parameters + * XXX TODO: BSSLOAD + * (XXX EDCA parameter set, QoS capability?) + * XXX TODO: AP channel report + * * [tlv] HT capabilities * [tlv] HT information + * XXX TODO: 20/40 BSS coexistence + * Mesh: + * XXX TODO: Meshid + * XXX TODO: mesh config + * XXX TODO: mesh awake window + * XXX TODO: beacon timing (mesh, etc) + * XXX TODO: MCCAOP Advertisement Overview + * XXX TODO: MCCAOP Advertisement + * XXX TODO: Mesh channel switch parameters + * VHT: + * XXX TODO: VHT capabilities + * XXX TODO: VHT operation + * XXX TODO: VHT transmit power envelope + * XXX TODO: channel switch wrapper element + * XXX TODO: extended BSS load element + * * XXX Vendor-specific OIDs (e.g. Atheros) * [tlv] WPA parameters * [tlv] WME parameters @@ -3055,6 +3142,16 @@ bo->bo_htinfo = frm; frm = ieee80211_add_htinfo(frm, ni); } + + if (IEEE80211_IS_CHAN_VHT(ni->ni_chan)) { + frm = ieee80211_add_vhtcap(frm, ni); + bo->bo_vhtinfo = frm; + frm = ieee80211_add_vhtinfo(frm, ni); + /* Transmit power envelope */ + /* Channel switch wrapper element */ + /* Extended bss load element */ + } + frm = ieee80211_add_wpa(frm, vap); if (vap->iv_flags & IEEE80211_F_WME) { bo->bo_wme = frm; @@ -3065,6 +3162,7 @@ frm = ieee80211_add_htcap_vendor(frm, ni); frm = ieee80211_add_htinfo_vendor(frm, ni); } + #ifdef IEEE80211_SUPPORT_SUPERG if (vap->iv_flags & IEEE80211_F_ATHEROS) { bo->bo_ath = frm; @@ -3082,6 +3180,8 @@ bo->bo_appie_len = vap->iv_appie_beacon->ie_len; frm = add_appie(frm, vap->iv_appie_beacon); } + + /* XXX TODO: move meshid/meshconf up to before vendor extensions? */ #ifdef IEEE80211_SUPPORT_MESH if (vap->iv_opmode == IEEE80211_M_MBSS) { frm = ieee80211_add_meshid(frm, vap); @@ -3097,6 +3197,7 @@ /* * Allocate a beacon frame and fillin the appropriate bits. */ +/* XXX VHT? */ struct mbuf * ieee80211_beacon_alloc(struct ieee80211_node *ni) { @@ -3110,6 +3211,9 @@ /* * beacon frame format + * + * Note: This needs updating for 802.11-2012. + * * [8] time stamp * [2] beacon interval * [2] cabability information @@ -3126,6 +3230,8 @@ * [tlv] RSN parameters * [tlv] HT capabilities * [tlv] HT information + * [tlv] VHT capabilities + * [tlv] VHT operation * [tlv] Vendor OUI HT capabilities (optional) * [tlv] Vendor OUI HT information (optional) * XXX Vendor-specific OIDs (e.g. Atheros) @@ -3138,6 +3244,7 @@ * NB: we allocate the max space required for the TIM bitmap. * XXX how big is this? */ + /* XXX VHT? */ pktlen = 8 /* time stamp */ + sizeof(uint16_t) /* beacon interval */ + sizeof(uint16_t) /* capabilities */ @@ -3157,6 +3264,8 @@ /* XXX conditional? */ + 4+2*sizeof(struct ieee80211_ie_htcap)/* HT caps */ + 4+2*sizeof(struct ieee80211_ie_htinfo)/* HT info */ + + sizeof(struct ieee80211_ie_vhtcap)/* VHT caps */ + + sizeof(struct ieee80211_ie_vht_operation)/* VHT info */ + (vap->iv_caps & IEEE80211_C_WME ? /* WME */ sizeof(struct ieee80211_wme_param) : 0) #ifdef IEEE80211_SUPPORT_SUPERG @@ -3242,6 +3351,15 @@ } wh = mtod(m, struct ieee80211_frame *); + + /* + * XXX TODO Strictly speaking this should be incremented with the TX + * lock held so as to serialise access to the non-qos TID sequence + * number space. + * + * If the driver identifies it does its own TX seqno management then + * we can skip this (and still not do the TX seqno.) + */ seqno = ni->ni_txseqs[IEEE80211_NONQOS_TID]++; *(uint16_t *)&wh->i_seq[0] = htole16(seqno << IEEE80211_SEQ_SEQ_SHIFT); @@ -3347,6 +3465,10 @@ timoff = 0; timlen = 1; } + + /* + * TODO: validate this! + */ if (timlen != bo->bo_tim_len) { /* copy up/down trailer */ int adjust = tie->tim_bitmap+timlen @@ -3357,6 +3479,7 @@ bo->bo_tim_trailer += adjust; bo->bo_erp += adjust; bo->bo_htinfo += adjust; + bo->bo_vhtinfo += adjust; #ifdef IEEE80211_SUPPORT_SUPERG bo->bo_ath += adjust; #endif @@ -3411,6 +3534,7 @@ memmove(&csa[1], csa, bo->bo_csa_trailer_len); bo->bo_erp += sizeof(*csa); bo->bo_htinfo += sizeof(*csa); + bo->bo_vhtinfo += sizeof(*csa); bo->bo_wme += sizeof(*csa); #ifdef IEEE80211_SUPPORT_SUPERG bo->bo_ath += sizeof(*csa); Index: sys/net80211/ieee80211_scan_sta.c =================================================================== --- sys/net80211/ieee80211_scan_sta.c (revision 311663) +++ sys/net80211/ieee80211_scan_sta.c (working copy) @@ -55,6 +55,7 @@ #include #endif #include +#include #include @@ -325,8 +326,26 @@ } } else ise->se_chan = curchan; + + /* VHT demotion */ + if (IEEE80211_IS_CHAN_VHT(ise->se_chan) && sp->vhtcap == NULL) { + IEEE80211_DPRINTF(vap, IEEE80211_MSG_11N, + "%s: demoting VHT->HT %d/0x%08x\n", + __func__, ise->se_chan->ic_freq, ise->se_chan->ic_flags); + /* Demote legacy networks to a non-VHT channel. */ + c = ieee80211_find_channel(ic, ise->se_chan->ic_freq, + ise->se_chan->ic_flags & ~IEEE80211_CHAN_VHT); + KASSERT(c != NULL, + ("no non-VHT channel %u", ise->se_chan->ic_ieee)); + ise->se_chan = c; + } + + /* HT demotion */ if (IEEE80211_IS_CHAN_HT(ise->se_chan) && sp->htcap == NULL) { /* Demote legacy networks to a non-HT channel. */ + IEEE80211_DPRINTF(vap, IEEE80211_MSG_11N, + "%s: demoting HT->legacy %d/0x%08x\n", + __func__, ise->se_chan->ic_freq, ise->se_chan->ic_flags); c = ieee80211_find_channel(ic, ise->se_chan->ic_freq, ise->se_chan->ic_flags & ~IEEE80211_CHAN_HT); KASSERT(c != NULL, @@ -333,6 +352,7 @@ ("no legacy channel %u", ise->se_chan->ic_ieee)); ise->se_chan = c; } + ise->se_fhdwell = sp->fhdwell; ise->se_fhindex = sp->fhindex; ise->se_erp = sp->erp; @@ -531,10 +551,11 @@ /* * Ignore dynamic turbo channels; we scan them * in normal mode (i.e. not boosted). Likewise - * for HT channels, they get scanned using + * for HT/VHT channels, they get scanned using * legacy rates. */ - if (IEEE80211_IS_CHAN_DTURBO(c) || IEEE80211_IS_CHAN_HT(c)) + if (IEEE80211_IS_CHAN_DTURBO(c) || IEEE80211_IS_CHAN_HT(c) || + IEEE80211_IS_CHAN_VHT(c)) continue; /* @@ -819,6 +840,9 @@ * that we assume compatibility/usability has already been checked * so we don't need to (e.g. validate whether privacy is supported). * Used to select the best scan candidate for association in a BSS. + * + * TODO: should we take 11n, 11ac into account when selecting the + * best? Right now it just compares frequency band and RSSI. */ static int sta_compare(const struct sta_entry *a, const struct sta_entry *b) @@ -1626,6 +1650,8 @@ */ chan = ieee80211_ht_adjust_channel(ic, chan, vap->iv_flags_ht); + chan = ieee80211_vht_adjust_channel(ic, + chan, vap->iv_flags_vht); ieee80211_create_ibss(vap, chan); return 1; } @@ -1657,6 +1683,8 @@ */ chan = ieee80211_ht_adjust_channel(ic, chan, vap->iv_flags_ht); + chan = ieee80211_vht_adjust_channel(ic, + chan, vap->iv_flags_vht); if (!ieee80211_sta_join(vap, chan, &selbs->base)) goto notfound; return 1; /* terminate scan */ @@ -1776,7 +1804,7 @@ ap_end(struct ieee80211_scan_state *ss, struct ieee80211vap *vap) { struct ieee80211com *ic = vap->iv_ic; - struct ieee80211_channel *bestchan; + struct ieee80211_channel *bestchan, *chan; KASSERT(vap->iv_opmode == IEEE80211_M_HOSTAP, ("wrong opmode %u", vap->iv_opmode)); @@ -1808,8 +1836,10 @@ ss->ss_flags &= ~IEEE80211_SCAN_NOPICK; return 1; } - ieee80211_create_ibss(vap, - ieee80211_ht_adjust_channel(ic, bestchan, vap->iv_flags_ht)); + chan = ieee80211_ht_adjust_channel(ic, bestchan, vap->iv_flags_ht); + chan = ieee80211_vht_adjust_channel(ic, chan, vap->iv_flags_vht); + ieee80211_create_ibss(vap, chan); + return 1; } @@ -1881,10 +1911,14 @@ IEEE80211_IS_CHAN_RADAR(vap->iv_des_chan)) { struct ieee80211com *ic = vap->iv_ic; + /* XXX VHT */ chan = adhoc_pick_channel(ss, 0); - if (chan != NULL) + if (chan != NULL) { chan = ieee80211_ht_adjust_channel(ic, chan, vap->iv_flags_ht); + chan = ieee80211_vht_adjust_channel(ic, + chan, vap->iv_flags_vht); + } } else chan = vap->iv_des_chan; if (chan != NULL) { Index: sys/net80211/ieee80211_sta.c =================================================================== --- sys/net80211/ieee80211_sta.c (revision 311663) +++ sys/net80211/ieee80211_sta.c (working copy) @@ -64,6 +64,7 @@ #endif #include #include +#include #define IEEE80211_RATE2MBS(r) (((r) & IEEE80211_RATE_VAL) / 2) @@ -1330,8 +1331,9 @@ struct ieee80211_frame *wh; uint8_t *frm, *efrm; uint8_t *rates, *xrates, *wme, *htcap, *htinfo; + uint8_t *vhtcap, *vhtopmode; uint8_t rate; - int ht_state_change = 0; + int ht_state_change = 0, do_ht = 0; wh = mtod(m0, struct ieee80211_frame *); frm = (uint8_t *)&wh[1]; @@ -1430,10 +1432,23 @@ if (scan.htcap != NULL && scan.htinfo != NULL && (vap->iv_flags_ht & IEEE80211_FHT_HT)) { /* XXX state changes? */ - if (ieee80211_ht_updateparams(ni, + ieee80211_ht_updateparams(ni, + scan.htcap, scan.htinfo); + do_ht = 1; + } + if (scan.vhtcap != NULL && scan.vhtopmode != NULL && + (vap->iv_flags_vht & IEEE80211_FVHT_VHT)) { + /* XXX state changes? */ + ieee80211_vht_updateparams(ni, + scan.vhtcap, scan.vhtopmode); + do_ht = 1; + } + if (do_ht) { + if (ieee80211_ht_updateparams_final(ni, scan.htcap, scan.htinfo)) ht_state_change = 1; } + if (scan.quiet) ic->ic_set_quiet(ni, scan.quiet); @@ -1660,6 +1675,7 @@ frm += 2; rates = xrates = wme = htcap = htinfo = NULL; + vhtcap = vhtopmode = NULL; while (efrm - frm > 1) { IEEE80211_VERIFY_LENGTH(efrm - frm, frm[1] + 2, return); switch (*frm) { @@ -1693,6 +1709,12 @@ } /* XXX Atheros OUI support */ break; + case IEEE80211_ELEMID_VHT_CAP: + vhtcap = frm; + break; + case IEEE80211_ELEMID_VHT_OPMODE: + vhtopmode = frm; + break; } frm += frm[1] + 2; } @@ -1740,6 +1762,16 @@ ieee80211_setup_htrates(ni, htcap, IEEE80211_F_JOIN | IEEE80211_F_DOBRS); ieee80211_setup_basic_htrates(ni, htinfo); + + if ((vhtcap != NULL) && (vhtopmode != NULL) & + (vap->iv_flags_vht & IEEE80211_FVHT_VHT)) { + ieee80211_vht_node_init(ni); + ieee80211_vht_updateparams(ni, vhtcap, vhtopmode); + ieee80211_setup_vht_rates(ni, vhtcap, vhtopmode); + } + + ieee80211_ht_updateparams_final(ni, htcap, htinfo); + ieee80211_node_setuptxparms(ni); ieee80211_ratectl_node_init(ni); }