Skip site navigation (1)Skip section navigation (2)
Date:      Fri, 13 Jan 2017 07:02:04 +0000 (UTC)
From:      Adrian Chadd <adrian@FreeBSD.org>
To:        src-committers@freebsd.org, svn-src-all@freebsd.org, svn-src-head@freebsd.org
Subject:   svn commit: r312015 - head/sys/net80211
Message-ID:  <201701130702.v0D724S1009010@repo.freebsd.org>

next in thread | raw e-mail | index | archive | help
Author: adrian
Date: Fri Jan 13 07:02:04 2017
New Revision: 312015
URL: https://svnweb.freebsd.org/changeset/base/312015

Log:
  [net80211] Initial VHT node upgrade/downgrade support and initial IE parsing.
  
  This is the bulk of the magic to start enabling VHT channel negotiation.
  It is absolutely, positively not yet even a complete VHT wave-1 implementation.
  
  * parse IEs in scan, assoc req/resp, probe req/resp;
  * break apart the channel upgrade from the HT IE parsing - do it after the
    VHT IEs are parsed;
  * (dirty! sigh) add channel width decision making in ieee80211_ht.c htinfo_update_chw().
    This is the main bit where negotiated channel promotion through IEs occur.
  * Shoehorn in VHT node init ,teardown, rate control, etc calls like the HT
    versions;
  * Do VHT channel adjustment where appropriate
  
  Tested:
  
  * monitor mode, ath10k port
  * STA mode, ath10k port - VHT20, VHT40, VHT80 modes
  
  TODO:
  
  * IBSS;
  * hostap;
  * (ignore mesh, wds for now);
  * finish 11n state engine - channel width change, opmode notifications, SMPS, etc;
  * VHT basic rate negotiation and acceptance criteria when scanning, associating, etc;
  * VHT control/management frame handling (group managment and operating mode being
    the two big ones);
  * Verify TX/RX VHT rate negotiation is actually working correctly.
  
  Whilst here, add some comments about seqno allocation and locking.  To achieve
  the full VHT rates I need to push seqno allocation into the drivers and
  finally remove the IEEE80211_TX_LOCK() I added years ago to fix issues. :/

Modified:
  head/sys/net80211/ieee80211_adhoc.c
  head/sys/net80211/ieee80211_hostap.c
  head/sys/net80211/ieee80211_ht.c
  head/sys/net80211/ieee80211_ht.h
  head/sys/net80211/ieee80211_input.c
  head/sys/net80211/ieee80211_node.c
  head/sys/net80211/ieee80211_output.c
  head/sys/net80211/ieee80211_scan_sta.c
  head/sys/net80211/ieee80211_sta.c

Modified: head/sys/net80211/ieee80211_adhoc.c
==============================================================================
--- head/sys/net80211/ieee80211_adhoc.c	Fri Jan 13 06:53:56 2017	(r312014)
+++ head/sys/net80211/ieee80211_adhoc.c	Fri Jan 13 07:02:04 2017	(r312015)
@@ -822,10 +822,14 @@ adhoc_recv_mgmt(struct ieee80211_node *n
 #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);

Modified: head/sys/net80211/ieee80211_hostap.c
==============================================================================
--- head/sys/net80211/ieee80211_hostap.c	Fri Jan 13 06:53:56 2017	(r312014)
+++ head/sys/net80211/ieee80211_hostap.c	Fri Jan 13 07:02:04 2017	(r312015)
@@ -62,6 +62,7 @@ __FBSDID("$FreeBSD$");
 #include <net80211/ieee80211_superg.h>
 #endif
 #include <net80211/ieee80211_wds.h>
+#include <net80211/ieee80211_vht.h>
 
 #define	IEEE80211_RATE2MBS(r)	(((r) & IEEE80211_RATE_VAL) / 2)
 
@@ -1745,6 +1746,7 @@ hostap_recv_mgmt(struct ieee80211_node *
 	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 @@ hostap_recv_mgmt(struct ieee80211_node *
 		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 @@ hostap_recv_mgmt(struct ieee80211_node *
 			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 @@ hostap_recv_mgmt(struct ieee80211_node *
 			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 @@ hostap_recv_mgmt(struct ieee80211_node *
 			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);
+		}
+
 #ifdef IEEE80211_SUPPORT_SUPERG
 		/* Always do ff node cleanup; for A-MSDU */
 		ieee80211_ff_node_cleanup(ni);

Modified: head/sys/net80211/ieee80211_ht.c
==============================================================================
--- head/sys/net80211/ieee80211_ht.c	Fri Jan 13 06:53:56 2017	(r312014)
+++ head/sys/net80211/ieee80211_ht.c	Fri Jan 13 07:02:04 2017	(r312015)
@@ -1490,52 +1490,117 @@ ieee80211_parse_htinfo(struct ieee80211_
 }
 
 /*
- * 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
  * 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;
 	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 */
 	ni->ni_chw = IEEE80211_IS_CHAN_HT40(ni->ni_chan)? 40 : 20;
 	return (ret);
 }
@@ -1587,15 +1652,18 @@ htcap_update_shortgi(struct ieee80211_no
 /*
  * 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 +1675,115 @@ ieee80211_ht_updateparams(struct ieee802
 	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;
+}
+
+static uint32_t
+ieee80211_vht_get_vhtflags(struct ieee80211_node *ni, uint32_t htflags)
+{
+	struct ieee80211vap *vap = ni->ni_vap;
+	uint32_t vhtflags = 0;
+
+	vhtflags = 0;
+	if (ni->ni_flags & IEEE80211_NODE_VHT && vap->iv_flags_vht & IEEE80211_FVHT_VHT) {
+		if ((ni->ni_vht_chanwidth == IEEE80211_VHT_CHANWIDTH_160MHZ) &&
+		    /* XXX 2 means "160MHz and 80+80MHz", 1 means "160MHz" */
+		    (MS(vap->iv_vhtcaps,
+		     IEEE80211_VHTCAP_SUPP_CHAN_WIDTH_MASK) >= 1) &&
+		    (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) &&
+		    /* XXX 2 means "160MHz and 80+80MHz" */
+		    (MS(vap->iv_vhtcaps,
+		     IEEE80211_VHTCAP_SUPP_CHAN_WIDTH_MASK) == 2) &&
+		    (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
+				    | IEEE80211_CHAN_HT40U;
+			} else if (htflags == IEEE80211_CHAN_HT40D &&
+			    (vap->iv_flags_vht & IEEE80211_FVHT_USEVHT40)) {
+				vhtflags = IEEE80211_CHAN_VHT40D
+				    | IEEE80211_CHAN_HT40D;
+			} else if (htflags == IEEE80211_CHAN_HT20) {
+				vhtflags = IEEE80211_CHAN_VHT20
+				    | IEEE80211_CHAN_HT20;
+			}
+		} else {
+			vhtflags = IEEE80211_CHAN_VHT20;
+		}
+	}
+	return (vhtflags);
+}
+
+/*
+ * 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.
+ */
+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,14 +1792,16 @@ ieee80211_ht_updateparams(struct ieee802
 		else if (ni->ni_ht2ndchan == IEEE80211_HTINFO_2NDCHAN_BELOW)
 			htflags = IEEE80211_CHAN_HT40D;
 	}
-	if (htinfo_update_chw(ni, htflags))
-		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;
+	/*
+	 * 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.
+	 */
+	vhtflags = ieee80211_vht_get_vhtflags(ni, htflags);
+
+	if (htinfo_update_chw(ni, htflags, vhtflags))
+		ret = 1;
 
 	return (ret);
 }
@@ -1632,17 +1809,31 @@ ieee80211_ht_updateparams(struct ieee802
 /*
  * 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)
+{
+	struct ieee80211vap *vap = ni->ni_vap;
+	int htflags;
+	int vhtflags;
 
 	/* NB: honor operating mode constraint */
 	/* XXX 40 MHz intolerant */
@@ -1655,7 +1846,14 @@ ieee80211_ht_updatehtcap(struct ieee8021
 		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.
+	 */
+	vhtflags = ieee80211_vht_get_vhtflags(ni, htflags);
+
+	(void) htinfo_update_chw(ni, htflags, vhtflags);
 }
 
 /*
@@ -2135,6 +2333,7 @@ ht_recv_action_ht_txchwidth(struct ieee8
 	    "%s: HT txchwidth, width %d%s",
 	    __func__, chw, ni->ni_chw != chw ? "*" : "");
 	if (chw != ni->ni_chw) {
+		/* XXX does this need to change the ht40 station count? */
 		ni->ni_chw = chw;
 		/* XXX notify on change */
 	}
@@ -2229,6 +2428,10 @@ ieee80211_ampdu_request(struct ieee80211
 
 	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;

Modified: head/sys/net80211/ieee80211_ht.h
==============================================================================
--- head/sys/net80211/ieee80211_ht.h	Fri Jan 13 06:53:56 2017	(r312014)
+++ head/sys/net80211/ieee80211_ht.h	Fri Jan 13 07:02:04 2017	(r312015)
@@ -201,9 +201,12 @@ void	ieee80211_htprot_update(struct ieee
 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 *);
 int	ieee80211_ampdu_request(struct ieee80211_node *,
 		struct ieee80211_tx_ampdu *);
 void	ieee80211_ampdu_stop(struct ieee80211_node *,

Modified: head/sys/net80211/ieee80211_input.c
==============================================================================
--- head/sys/net80211/ieee80211_input.c	Fri Jan 13 06:53:56 2017	(r312014)
+++ head/sys/net80211/ieee80211_input.c	Fri Jan 13 07:02:04 2017	(r312015)
@@ -494,6 +494,8 @@ ieee80211_parse_beacon(struct ieee80211_
 	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 @@ ieee80211_parse_beacon(struct ieee80211_
 	 *	[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 @@ ieee80211_parse_beacon(struct ieee80211_
 		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 @@ ieee80211_parse_beacon(struct ieee80211_
 			 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 @@ ieee80211_parse_action(struct ieee80211_
 		}
 		break;
 #endif
+	case IEEE80211_ACTION_CAT_VHT:
+		printf("%s: TODO: VHT handling!\n", __func__);
+		break;
 	}
 	return 0;
 }

Modified: head/sys/net80211/ieee80211_node.c
==============================================================================
--- head/sys/net80211/ieee80211_node.c	Fri Jan 13 06:53:56 2017	(r312014)
+++ head/sys/net80211/ieee80211_node.c	Fri Jan 13 07:02:04 2017	(r312015)
@@ -53,6 +53,7 @@ __FBSDID("$FreeBSD$");
 #include <net80211/ieee80211_wds.h>
 #include <net80211/ieee80211_mesh.h>
 #include <net80211/ieee80211_ratectl.h>
+#include <net80211/ieee80211_vht.h>
 
 #include <net/bpf.h>
 
@@ -412,7 +413,11 @@ ieee80211_create_ibss(struct ieee80211va
 	/* 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);
 	}
@@ -708,9 +713,42 @@ gethtadjustflags(struct ieee80211com *ic
 }
 
 /*
+ * 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 @@ ieee80211_sync_curchan(struct ieee80211c
 	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 @@ ieee80211_setupcurchan(struct ieee80211c
 		 * 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 @@ ieee80211_sta_join(struct ieee80211vap *
 {
 	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 @@ ieee80211_sta_join(struct ieee80211vap *
 		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 */
 	}
 
@@ -926,10 +984,43 @@ ieee80211_sta_join(struct ieee80211vap *
 		ieee80211_ht_updateparams(ni,
 		    ni->ni_ies.htcap_ie,
 		    ni->ni_ies.htinfo_ie);
+		do_ht = 1;
+	}
+
+	/*
+	 * Setup VHT state for this node if it's available.
+	 * Same as the above.
+	 *
+	 * For now, don't allow 2GHz VHT operation.
+	 */
+	if (ni->ni_ies.vhtopmode_ie != NULL &&
+	    ni->ni_ies.vhtcap_ie != NULL &&
+	    vap->iv_flags_vht & IEEE80211_FVHT_VHT) {
+		if (IEEE80211_IS_CHAN_2GHZ(ni->ni_chan)) {
+			printf("%s: BSS %6D: 2GHz channel, VHT info; ignoring\n",
+			    __func__,
+			    ni->ni_macaddr,
+			    ":");
+		} else {
+			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);
 		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);
 	}
+
 	/* XXX else check for ath FF? */
 	/* XXX QoS? Difficult given that WME config is specific to a master */
 
@@ -1102,8 +1193,10 @@ node_cleanup(struct ieee80211_node *ni)
 		    "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 +1516,7 @@ ieee80211_node_create_wds(struct ieee802
 		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 +1525,9 @@ ieee80211_node_create_wds(struct ieee802
 			 * 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 +1735,7 @@ ieee80211_init_neighbor(struct ieee80211
 	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 +1767,23 @@ ieee80211_init_neighbor(struct ieee80211
 		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 */
@@ -1692,15 +1801,40 @@ ieee80211_init_neighbor(struct ieee80211
 		ieee80211_ht_updateparams(ni,
 		    ni->ni_ies.htcap_ie,
 		    ni->ni_ies.htinfo_ie);
+
+		if (do_vht_setup) {
+			if (IEEE80211_IS_CHAN_2GHZ(ni->ni_chan)) {
+				printf("%s: BSS %6D: 2GHz channel, VHT info; ignoring\n",
+				    __func__,
+				    ni->ni_macaddr,
+				    ":");
+			} else {
+				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.htcap_ie,
+		    ni->ni_ies.htinfo_ie);
 		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);
+
 		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 +2499,7 @@ ieee80211_node_timeout(void *arg)
 		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 +2597,12 @@ ieee80211_dump_node(struct ieee80211_nod
 	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);
+	printf("\tvhtcap %x freq1 %d freq2 %d vhtbasicmcs %x\n",
+		ni->ni_vhtcap, (int) ni->ni_vht_chan1, (int) ni->ni_vht_chan2,
+		(int) ni->ni_vht_basicmcs);
+	/* XXX VHT state */
 }
 
 void
@@ -2594,6 +2733,8 @@ ieee80211_node_join(struct ieee80211_nod
 
 		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 +2744,9 @@ ieee80211_node_join(struct ieee80211_nod
 	} 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),
@@ -2610,6 +2754,7 @@ ieee80211_node_join(struct ieee80211_nod
 	    ic->ic_flags & IEEE80211_F_SHSLOT ? "short" : "long",
 	    ic->ic_flags & IEEE80211_F_USEPROT ? ", protection" : "",
 	    ni->ni_flags & IEEE80211_NODE_QOS ? ", QoS" : "",
+	    /* XXX update for VHT string */
 	    ni->ni_flags & IEEE80211_NODE_HT ?
 		(ni->ni_chw == 40 ? ", HT40" : ", HT20") : "",
 	    ni->ni_flags & IEEE80211_NODE_AMPDU ? " (+AMPDU)" : "",
@@ -2774,6 +2919,8 @@ ieee80211_node_leave(struct ieee80211_no
 	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) &&

Modified: head/sys/net80211/ieee80211_output.c
==============================================================================
--- head/sys/net80211/ieee80211_output.c	Fri Jan 13 06:53:56 2017	(r312014)
+++ head/sys/net80211/ieee80211_output.c	Fri Jan 13 07:02:04 2017	(r312015)
@@ -58,6 +58,7 @@ __FBSDID("$FreeBSD$");
 #endif
 #include <net80211/ieee80211_wds.h>
 #include <net80211/ieee80211_mesh.h>
+#include <net80211/ieee80211_vht.h>
 
 #if defined(INET) || defined(INET6)
 #include <netinet/in.h> 
@@ -764,6 +765,16 @@ ieee80211_send_setup(
 	}
 	*(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 @@ ieee80211_encap(struct ieee80211vap *vap
 		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 @@ ieee80211_encap(struct ieee80211vap *vap
 			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 @@ ieee80211_add_qos(uint8_t *frm, const st
  * 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 @@ ieee80211_send_probereq(struct ieee80211
 	 *	[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 @@ ieee80211_send_probereq(struct ieee80211
 	       	 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 @@ ieee80211_send_probereq(struct ieee80211
 		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 @@ ieee80211_send_mgmt(struct ieee80211_nod
 
 	case IEEE80211_FC0_SUBTYPE_ASSOC_REQ:
 	case IEEE80211_FC0_SUBTYPE_REASSOC_REQ:
+		/* XXX VHT? */
 		/*
 		 * asreq frame format
 		 *	[2] capability information
@@ -2368,6 +2408,7 @@ ieee80211_send_mgmt(struct ieee80211_nod
 		 *	[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 @@ ieee80211_send_mgmt(struct ieee80211_nod
 		       + 2 + 26
 		       + sizeof(struct ieee80211_wme_info)

*** DIFF OUTPUT TRUNCATED AT 1000 LINES ***



Want to link to this message? Use this URL: <https://mail-archive.FreeBSD.org/cgi/mid.cgi?201701130702.v0D724S1009010>