ieee80211_ht.c revision 173865
1170530Ssam/*- 2170530Ssam * Copyright (c) 2007 Sam Leffler, Errno Consulting 3170530Ssam * All rights reserved. 4170530Ssam * 5170530Ssam * Redistribution and use in source and binary forms, with or without 6170530Ssam * modification, are permitted provided that the following conditions 7170530Ssam * are met: 8170530Ssam * 1. Redistributions of source code must retain the above copyright 9170530Ssam * notice, this list of conditions and the following disclaimer. 10170530Ssam * 2. Redistributions in binary form must reproduce the above copyright 11170530Ssam * notice, this list of conditions and the following disclaimer in the 12170530Ssam * documentation and/or other materials provided with the distribution. 13170530Ssam * 14170530Ssam * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR 15170530Ssam * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES 16170530Ssam * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. 17170530Ssam * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, 18170530Ssam * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT 19170530Ssam * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 20170530Ssam * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 21170530Ssam * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 22170530Ssam * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF 23170530Ssam * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 24170530Ssam */ 25170530Ssam 26170530Ssam#include <sys/cdefs.h> 27170530Ssam#ifdef __FreeBSD__ 28170530Ssam__FBSDID("$FreeBSD: head/sys/net80211/ieee80211_ht.c 173865 2007-11-23 06:14:32Z sam $"); 29170530Ssam#endif 30170530Ssam 31170530Ssam/* 32170530Ssam * IEEE 802.11n protocol support. 33170530Ssam */ 34170530Ssam 35170530Ssam#include "opt_inet.h" 36170530Ssam 37170530Ssam#include <sys/param.h> 38170530Ssam#include <sys/kernel.h> 39170530Ssam#include <sys/systm.h> 40170530Ssam#include <sys/endian.h> 41170530Ssam 42170530Ssam#include <sys/socket.h> 43170530Ssam 44170530Ssam#include <net/if.h> 45170530Ssam#include <net/if_media.h> 46170530Ssam#include <net/ethernet.h> 47170530Ssam 48170530Ssam#include <net80211/ieee80211_var.h> 49170530Ssam 50170530Ssam/* define here, used throughout file */ 51170530Ssam#define MS(_v, _f) (((_v) & _f) >> _f##_S) 52170530Ssam#define SM(_v, _f) (((_v) << _f##_S) & _f) 53170530Ssam 54170530Ssam/* XXX need max array size */ 55170530Ssamconst int ieee80211_htrates[16] = { 56170530Ssam 13, /* IFM_IEEE80211_MCS0 */ 57170530Ssam 26, /* IFM_IEEE80211_MCS1 */ 58170530Ssam 39, /* IFM_IEEE80211_MCS2 */ 59170530Ssam 52, /* IFM_IEEE80211_MCS3 */ 60170530Ssam 78, /* IFM_IEEE80211_MCS4 */ 61170530Ssam 104, /* IFM_IEEE80211_MCS5 */ 62170530Ssam 117, /* IFM_IEEE80211_MCS6 */ 63170530Ssam 130, /* IFM_IEEE80211_MCS7 */ 64170530Ssam 26, /* IFM_IEEE80211_MCS8 */ 65170530Ssam 52, /* IFM_IEEE80211_MCS9 */ 66170530Ssam 78, /* IFM_IEEE80211_MCS10 */ 67170530Ssam 104, /* IFM_IEEE80211_MCS11 */ 68170530Ssam 156, /* IFM_IEEE80211_MCS12 */ 69170530Ssam 208, /* IFM_IEEE80211_MCS13 */ 70170530Ssam 234, /* IFM_IEEE80211_MCS14 */ 71170530Ssam 260, /* IFM_IEEE80211_MCS15 */ 72170530Ssam}; 73170530Ssam 74170530Ssamstatic const struct ieee80211_htrateset ieee80211_rateset_11n = 75170530Ssam { 16, { 76170530Ssam /* MCS: 6.5 13 19.5 26 39 52 58.5 65 13 26 */ 77170530Ssam 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 78170530Ssam /* 39 52 78 104 117, 130 */ 79170530Ssam 10, 11, 12, 13, 14, 15 } 80170530Ssam }; 81170530Ssam 82173273Ssam#ifdef IEEE80211_AMPDU_AGE 83173273Ssam/* XXX public for sysctl hookup */ 84173273Ssamint ieee80211_ampdu_age = -1; /* threshold for ampdu reorder q (ms) */ 85173273Ssam#endif 86173273Ssamint ieee80211_recv_bar_ena = 1; 87173273Ssam 88170530Ssam#define IEEE80211_AGGR_TIMEOUT msecs_to_ticks(250) 89170530Ssam#define IEEE80211_AGGR_MINRETRY msecs_to_ticks(10*1000) 90170530Ssam#define IEEE80211_AGGR_MAXTRIES 3 91170530Ssam 92170530Ssamstatic int ieee80211_addba_request(struct ieee80211_node *ni, 93170530Ssam struct ieee80211_tx_ampdu *tap, 94170530Ssam int dialogtoken, int baparamset, int batimeout); 95170530Ssamstatic int ieee80211_addba_response(struct ieee80211_node *ni, 96170530Ssam struct ieee80211_tx_ampdu *tap, 97170530Ssam int code, int baparamset, int batimeout); 98170530Ssamstatic void ieee80211_addba_stop(struct ieee80211_node *ni, 99170530Ssam struct ieee80211_tx_ampdu *tap); 100170530Ssamstatic void ieee80211_aggr_recv_action(struct ieee80211_node *ni, 101170530Ssam const uint8_t *frm, const uint8_t *efrm); 102170530Ssam 103170530Ssamvoid 104170530Ssamieee80211_ht_attach(struct ieee80211com *ic) 105170530Ssam{ 106173273Ssam#ifdef IEEE80211_AMPDU_AGE 107173273Ssam if (ieee80211_ampdu_age == -1) 108173273Ssam ieee80211_ampdu_age = msecs_to_ticks(500); 109173273Ssam#endif 110170530Ssam 111170530Ssam /* setup default aggregation policy */ 112170530Ssam ic->ic_recv_action = ieee80211_aggr_recv_action; 113170530Ssam ic->ic_send_action = ieee80211_send_action; 114170530Ssam ic->ic_addba_request = ieee80211_addba_request; 115170530Ssam ic->ic_addba_response = ieee80211_addba_response; 116170530Ssam ic->ic_addba_stop = ieee80211_addba_stop; 117170530Ssam 118173273Ssam ic->ic_htprotmode = IEEE80211_PROT_RTSCTS; 119173273Ssam ic->ic_curhtprotmode = IEEE80211_HTINFO_OPMODE_PURE; 120173273Ssam 121173273Ssam /* XXX get from driver */ 122173273Ssam ic->ic_ampdu_rxmax = IEEE80211_HTCAP_MAXRXAMPDU_8K; 123173273Ssam ic->ic_ampdu_density = IEEE80211_HTCAP_MPDUDENSITY_NA; 124173273Ssam ic->ic_ampdu_limit = ic->ic_ampdu_rxmax; 125173273Ssam ic->ic_amsdu_limit = IEEE80211_HTCAP_MAXAMSDU_3839; 126173273Ssam 127173273Ssam if (ic->ic_htcaps & IEEE80211_HTC_HT) { 128170530Ssam /* 129173273Ssam * Device is HT capable; enable all HT-related 130173273Ssam * facilities by default. 131170530Ssam * XXX these choices may be too aggressive. 132170530Ssam */ 133170530Ssam ic->ic_flags_ext |= IEEE80211_FEXT_HT 134170530Ssam | IEEE80211_FEXT_HTCOMPAT 135170530Ssam ; 136170530Ssam if (ic->ic_htcaps & IEEE80211_HTCAP_SHORTGI20) 137170530Ssam ic->ic_flags_ext |= IEEE80211_FEXT_SHORTGI20; 138173273Ssam /* XXX infer from channel list? */ 139170530Ssam if (ic->ic_htcaps & IEEE80211_HTCAP_CHWIDTH40) { 140170530Ssam ic->ic_flags_ext |= IEEE80211_FEXT_USEHT40; 141170530Ssam if (ic->ic_htcaps & IEEE80211_HTCAP_SHORTGI40) 142170530Ssam ic->ic_flags_ext |= IEEE80211_FEXT_SHORTGI40; 143170530Ssam } 144170530Ssam /* NB: A-MPDU and A-MSDU rx are mandated, these are tx only */ 145170530Ssam ic->ic_flags_ext |= IEEE80211_FEXT_AMPDU_RX; 146170530Ssam if (ic->ic_htcaps & IEEE80211_HTC_AMPDU) 147170530Ssam ic->ic_flags_ext |= IEEE80211_FEXT_AMPDU_TX; 148170530Ssam ic->ic_flags_ext |= IEEE80211_FEXT_AMSDU_RX; 149170530Ssam if (ic->ic_htcaps & IEEE80211_HTC_AMSDU) 150170530Ssam ic->ic_flags_ext |= IEEE80211_FEXT_AMSDU_TX; 151170530Ssam } 152170530Ssam} 153170530Ssam 154170530Ssamvoid 155170530Ssamieee80211_ht_detach(struct ieee80211com *ic) 156170530Ssam{ 157170530Ssam} 158170530Ssam 159170530Ssamstatic void 160170530Ssamht_announce(struct ieee80211com *ic, int mode, 161170530Ssam const struct ieee80211_htrateset *rs) 162170530Ssam{ 163170530Ssam struct ifnet *ifp = ic->ic_ifp; 164170530Ssam int i, rate, mword; 165170530Ssam 166170530Ssam if_printf(ifp, "%s MCS: ", ieee80211_phymode_name[mode]); 167170530Ssam for (i = 0; i < rs->rs_nrates; i++) { 168172226Ssam mword = ieee80211_rate2media(ic, 169172226Ssam rs->rs_rates[i] | IEEE80211_RATE_MCS, mode); 170170530Ssam if (IFM_SUBTYPE(mword) != IFM_IEEE80211_MCS) 171170530Ssam continue; 172170530Ssam rate = ieee80211_htrates[rs->rs_rates[i]]; 173170530Ssam printf("%s%d%sMbps", (i != 0 ? " " : ""), 174170530Ssam rate / 2, ((rate & 0x1) != 0 ? ".5" : "")); 175170530Ssam } 176170530Ssam printf("\n"); 177170530Ssam} 178170530Ssam 179170530Ssamvoid 180170530Ssamieee80211_ht_announce(struct ieee80211com *ic) 181170530Ssam{ 182170530Ssam if (isset(ic->ic_modecaps, IEEE80211_MODE_11NA)) 183170530Ssam ht_announce(ic, IEEE80211_MODE_11NA, &ieee80211_rateset_11n); 184170530Ssam if (isset(ic->ic_modecaps, IEEE80211_MODE_11NG)) 185170530Ssam ht_announce(ic, IEEE80211_MODE_11NG, &ieee80211_rateset_11n); 186170530Ssam} 187170530Ssam 188170530Ssamconst struct ieee80211_htrateset * 189170530Ssamieee80211_get_suphtrates(struct ieee80211com *ic, 190170530Ssam const struct ieee80211_channel *c) 191170530Ssam{ 192173273Ssam return &ieee80211_rateset_11n; 193170530Ssam} 194170530Ssam 195170530Ssam/* 196170530Ssam * Receive processing. 197170530Ssam */ 198170530Ssam 199170530Ssam/* 200170530Ssam * Decap the encapsulated A-MSDU frames and dispatch all but 201170530Ssam * the last for delivery. The last frame is returned for 202170530Ssam * delivery via the normal path. 203170530Ssam */ 204170530Ssamstruct mbuf * 205170530Ssamieee80211_decap_amsdu(struct ieee80211_node *ni, struct mbuf *m) 206170530Ssam{ 207170530Ssam struct ieee80211com *ic = ni->ni_ic; 208173462Ssam int framelen; 209170530Ssam struct mbuf *n; 210170530Ssam 211170530Ssam /* discard 802.3 header inserted by ieee80211_decap */ 212170530Ssam m_adj(m, sizeof(struct ether_header)); 213170530Ssam 214170530Ssam ic->ic_stats.is_amsdu_decap++; 215170530Ssam 216170530Ssam for (;;) { 217170530Ssam /* 218170530Ssam * Decap the first frame, bust it apart from the 219170530Ssam * remainder and deliver. We leave the last frame 220170530Ssam * delivery to the caller (for consistency with other 221170530Ssam * code paths, could also do it here). 222170530Ssam */ 223170530Ssam m = ieee80211_decap1(m, &framelen); 224170530Ssam if (m == NULL) { 225170530Ssam IEEE80211_DISCARD_MAC(ic, IEEE80211_MSG_ANY, 226173462Ssam ni->ni_macaddr, "a-msdu", "%s", "decap failed"); 227170530Ssam ic->ic_stats.is_amsdu_tooshort++; 228170530Ssam return NULL; 229170530Ssam } 230173462Ssam if (m->m_pkthdr.len == framelen) 231170530Ssam break; 232170530Ssam n = m_split(m, framelen, M_NOWAIT); 233170530Ssam if (n == NULL) { 234170530Ssam IEEE80211_DISCARD_MAC(ic, IEEE80211_MSG_ANY, 235170530Ssam ni->ni_macaddr, "a-msdu", 236170530Ssam "%s", "unable to split encapsulated frames"); 237170530Ssam ic->ic_stats.is_amsdu_split++; 238170530Ssam m_freem(m); /* NB: must reclaim */ 239170530Ssam return NULL; 240170530Ssam } 241170530Ssam ieee80211_deliver_data(ic, ni, m); 242170530Ssam 243170530Ssam /* 244170530Ssam * Remove frame contents; each intermediate frame 245170530Ssam * is required to be aligned to a 4-byte boundary. 246170530Ssam */ 247170530Ssam m = n; 248170530Ssam m_adj(m, roundup2(framelen, 4) - framelen); /* padding */ 249170530Ssam } 250170530Ssam return m; /* last delivered by caller */ 251170530Ssam} 252170530Ssam 253170530Ssam/* 254170530Ssam * Start A-MPDU rx/re-order processing for the specified TID. 255170530Ssam */ 256170530Ssamstatic void 257170530Ssamampdu_rx_start(struct ieee80211_rx_ampdu *rap, int bufsiz, int start) 258170530Ssam{ 259170530Ssam memset(rap, 0, sizeof(*rap)); 260170530Ssam rap->rxa_wnd = (bufsiz == 0) ? 261170530Ssam IEEE80211_AGGR_BAWMAX : min(bufsiz, IEEE80211_AGGR_BAWMAX); 262170530Ssam rap->rxa_start = start; 263170530Ssam rap->rxa_flags |= IEEE80211_AGGR_XCHGPEND; 264170530Ssam} 265170530Ssam 266170530Ssam/* 267170530Ssam * Purge all frames in the A-MPDU re-order queue. 268170530Ssam */ 269170530Ssamstatic void 270170530Ssamampdu_rx_purge(struct ieee80211_rx_ampdu *rap) 271170530Ssam{ 272170530Ssam struct mbuf *m; 273170530Ssam int i; 274170530Ssam 275170530Ssam for (i = 0; i < rap->rxa_wnd; i++) { 276170530Ssam m = rap->rxa_m[i]; 277170530Ssam if (m != NULL) { 278170530Ssam rap->rxa_m[i] = NULL; 279170530Ssam rap->rxa_qbytes -= m->m_pkthdr.len; 280170530Ssam m_freem(m); 281170530Ssam if (--rap->rxa_qframes == 0) 282170530Ssam break; 283170530Ssam } 284170530Ssam } 285170530Ssam KASSERT(rap->rxa_qbytes == 0 && rap->rxa_qframes == 0, 286170530Ssam ("lost %u data, %u frames on ampdu rx q", 287170530Ssam rap->rxa_qbytes, rap->rxa_qframes)); 288170530Ssam} 289170530Ssam 290170530Ssam/* 291170530Ssam * Stop A-MPDU rx processing for the specified TID. 292170530Ssam */ 293170530Ssamstatic void 294170530Ssamampdu_rx_stop(struct ieee80211_rx_ampdu *rap) 295170530Ssam{ 296170530Ssam rap->rxa_flags &= ~IEEE80211_AGGR_XCHGPEND; 297170530Ssam ampdu_rx_purge(rap); 298170530Ssam} 299170530Ssam 300170530Ssam/* 301170530Ssam * Dispatch a frame from the A-MPDU reorder queue. The 302170530Ssam * frame is fed back into ieee80211_input marked with an 303170530Ssam * M_AMPDU flag so it doesn't come back to us (it also 304170530Ssam * permits ieee80211_input to optimize re-processing). 305170530Ssam */ 306170530Ssamstatic __inline void 307170530Ssamampdu_dispatch(struct ieee80211_node *ni, struct mbuf *m) 308170530Ssam{ 309170530Ssam m->m_flags |= M_AMPDU; /* bypass normal processing */ 310170530Ssam /* NB: rssi, noise, and rstamp are ignored w/ M_AMPDU set */ 311170530Ssam (void) ieee80211_input(ni->ni_ic, m, ni, 0, 0, 0); 312170530Ssam} 313170530Ssam 314170530Ssam/* 315170530Ssam * Dispatch as many frames as possible from the re-order queue. 316170530Ssam * Frames will always be "at the front"; we process all frames 317170530Ssam * up to the first empty slot in the window. On completion we 318170530Ssam * cleanup state if there are still pending frames in the current 319170530Ssam * BA window. We assume the frame at slot 0 is already handled 320170530Ssam * by the caller; we always start at slot 1. 321170530Ssam */ 322170530Ssamstatic void 323170530Ssamampdu_rx_dispatch(struct ieee80211_rx_ampdu *rap, struct ieee80211_node *ni) 324170530Ssam{ 325170530Ssam struct ieee80211com *ic = ni->ni_ic; 326170530Ssam struct mbuf *m; 327170530Ssam int i; 328170530Ssam 329170530Ssam /* flush run of frames */ 330170530Ssam for (i = 1; i < rap->rxa_wnd; i++) { 331170530Ssam m = rap->rxa_m[i]; 332170530Ssam if (m == NULL) 333170530Ssam break; 334170530Ssam rap->rxa_m[i] = NULL; 335170530Ssam rap->rxa_qbytes -= m->m_pkthdr.len; 336170530Ssam rap->rxa_qframes--; 337170530Ssam 338170530Ssam ampdu_dispatch(ni, m); 339170530Ssam } 340170530Ssam /* 341170530Ssam * If frames remain, copy the mbuf pointers down so 342170530Ssam * they correspond to the offsets in the new window. 343170530Ssam */ 344170530Ssam if (rap->rxa_qframes != 0) { 345170530Ssam int n = rap->rxa_qframes, j; 346170530Ssam for (j = i+1; j < rap->rxa_wnd; j++) { 347170530Ssam if (rap->rxa_m[j] != NULL) { 348170530Ssam rap->rxa_m[j-i] = rap->rxa_m[j]; 349170530Ssam rap->rxa_m[j] = NULL; 350170530Ssam if (--n == 0) 351170530Ssam break; 352170530Ssam } 353170530Ssam } 354170530Ssam KASSERT(n == 0, ("lost %d frames", n)); 355170530Ssam ic->ic_stats.is_ampdu_rx_copy += rap->rxa_qframes; 356170530Ssam } 357173273Ssam /* 358173273Ssam * Adjust the start of the BA window to 359173273Ssam * reflect the frames just dispatched. 360173273Ssam */ 361173273Ssam rap->rxa_start = IEEE80211_SEQ_ADD(rap->rxa_start, i); 362173273Ssam ic->ic_stats.is_ampdu_rx_oor += i; 363170530Ssam} 364170530Ssam 365173273Ssam#ifdef IEEE80211_AMPDU_AGE 366170530Ssam/* 367173273Ssam * Dispatch all frames in the A-MPDU re-order queue. 368170530Ssam */ 369170530Ssamstatic void 370173273Ssamampdu_rx_flush(struct ieee80211_node *ni, struct ieee80211_rx_ampdu *rap) 371170530Ssam{ 372173273Ssam struct ieee80211com *ic = ni->ni_ic; 373170530Ssam struct mbuf *m; 374170530Ssam int i; 375170530Ssam 376173273Ssam for (i = 0; i < rap->rxa_wnd; i++) { 377170530Ssam m = rap->rxa_m[i]; 378170530Ssam if (m == NULL) 379170530Ssam continue; 380170530Ssam rap->rxa_m[i] = NULL; 381170530Ssam rap->rxa_qbytes -= m->m_pkthdr.len; 382173273Ssam rap->rxa_qframes--; 383173273Ssam ic->ic_stats.is_ampdu_rx_oor++; 384173273Ssam 385170530Ssam ampdu_dispatch(ni, m); 386173273Ssam if (rap->rxa_qframes == 0) 387170530Ssam break; 388170530Ssam } 389170530Ssam} 390173273Ssam#endif /* IEEE80211_AMPDU_AGE */ 391170530Ssam 392170530Ssam/* 393173273Ssam * Dispatch all frames in the A-MPDU re-order queue 394173273Ssam * preceding the specified sequence number. This logic 395173273Ssam * handles window moves due to a received MSDU or BAR. 396173273Ssam */ 397173273Ssamstatic void 398173273Ssamampdu_rx_flush_upto(struct ieee80211_node *ni, 399173273Ssam struct ieee80211_rx_ampdu *rap, ieee80211_seq winstart) 400173273Ssam{ 401173273Ssam struct ieee80211com *ic = ni->ni_ic; 402173273Ssam struct mbuf *m; 403173273Ssam ieee80211_seq seqno; 404173273Ssam int i; 405173273Ssam 406173273Ssam /* 407173273Ssam * Flush any complete MSDU's with a sequence number lower 408173273Ssam * than winstart. Gaps may exist. Note that we may actually 409173273Ssam * dispatch frames past winstart if a run continues; this is 410173273Ssam * an optimization that avoids having to do a separate pass 411173273Ssam * to dispatch frames after moving the BA window start. 412173273Ssam */ 413173273Ssam seqno = rap->rxa_start; 414173273Ssam for (i = 0; i < rap->rxa_wnd; i++) { 415173273Ssam m = rap->rxa_m[i]; 416173273Ssam if (m != NULL) { 417173273Ssam rap->rxa_m[i] = NULL; 418173273Ssam rap->rxa_qbytes -= m->m_pkthdr.len; 419173273Ssam rap->rxa_qframes--; 420173273Ssam ic->ic_stats.is_ampdu_rx_oor++; 421173273Ssam 422173273Ssam ampdu_dispatch(ni, m); 423173273Ssam } else { 424173273Ssam if (!IEEE80211_SEQ_BA_BEFORE(seqno, winstart)) 425173273Ssam break; 426173273Ssam } 427173273Ssam seqno = IEEE80211_SEQ_INC(seqno); 428173273Ssam } 429173273Ssam /* 430173273Ssam * If frames remain, copy the mbuf pointers down so 431173273Ssam * they correspond to the offsets in the new window. 432173273Ssam */ 433173273Ssam if (rap->rxa_qframes != 0) { 434173273Ssam int n = rap->rxa_qframes, j; 435173273Ssam for (j = i+1; j < rap->rxa_wnd; j++) { 436173273Ssam if (rap->rxa_m[j] != NULL) { 437173273Ssam rap->rxa_m[j-i] = rap->rxa_m[j]; 438173273Ssam rap->rxa_m[j] = NULL; 439173273Ssam if (--n == 0) 440173273Ssam break; 441173273Ssam } 442173273Ssam } 443173273Ssam KASSERT(n == 0, ("%s: lost %d frames, qframes %d off %d " 444173273Ssam "BA win <%d:%d> winstart %d", 445173273Ssam __func__, n, rap->rxa_qframes, i, rap->rxa_start, 446173273Ssam IEEE80211_SEQ_ADD(rap->rxa_start, rap->rxa_wnd-1), 447173273Ssam winstart)); 448173273Ssam ic->ic_stats.is_ampdu_rx_copy += rap->rxa_qframes; 449173273Ssam } 450173273Ssam /* 451173273Ssam * Move the start of the BA window; we use the 452173273Ssam * sequence number of the last MSDU that was 453173273Ssam * passed up the stack+1 or winstart if stopped on 454173273Ssam * a gap in the reorder buffer. 455173273Ssam */ 456173273Ssam rap->rxa_start = seqno; 457173273Ssam} 458173273Ssam 459173273Ssam/* 460170530Ssam * Process a received QoS data frame for an HT station. Handle 461170530Ssam * A-MPDU reordering: if this frame is received out of order 462170530Ssam * and falls within the BA window hold onto it. Otherwise if 463173273Ssam * this frame completes a run, flush any pending frames. We 464170530Ssam * return 1 if the frame is consumed. A 0 is returned if 465170530Ssam * the frame should be processed normally by the caller. 466170530Ssam */ 467170530Ssamint 468170530Ssamieee80211_ampdu_reorder(struct ieee80211_node *ni, struct mbuf *m) 469170530Ssam{ 470170530Ssam#define IEEE80211_FC0_QOSDATA \ 471170530Ssam (IEEE80211_FC0_TYPE_DATA|IEEE80211_FC0_SUBTYPE_QOS|IEEE80211_FC0_VERSION_0) 472173273Ssam#define PROCESS 0 /* caller should process frame */ 473173273Ssam#define CONSUMED 1 /* frame consumed, caller does nothing */ 474170530Ssam struct ieee80211com *ic = ni->ni_ic; 475170530Ssam struct ieee80211_qosframe *wh; 476170530Ssam struct ieee80211_rx_ampdu *rap; 477170530Ssam ieee80211_seq rxseq; 478170530Ssam uint8_t tid; 479170530Ssam int off; 480170530Ssam 481170530Ssam KASSERT(ni->ni_flags & IEEE80211_NODE_HT, ("not an HT sta")); 482170530Ssam 483170530Ssam /* NB: m_len known to be sufficient */ 484170530Ssam wh = mtod(m, struct ieee80211_qosframe *); 485170530Ssam KASSERT(wh->i_fc[0] == IEEE80211_FC0_QOSDATA, ("not QoS data")); 486170530Ssam 487173273Ssam if ((wh->i_fc[1] & IEEE80211_FC1_DIR_MASK) == IEEE80211_FC1_DIR_DSTODS) 488173273Ssam tid = ((struct ieee80211_qosframe_addr4 *)wh)->i_qos[0]; 489173273Ssam else 490173273Ssam tid = wh->i_qos[0]; 491173273Ssam tid &= IEEE80211_QOS_TID; 492170530Ssam rap = &ni->ni_rx_ampdu[tid]; 493170530Ssam if ((rap->rxa_flags & IEEE80211_AGGR_XCHGPEND) == 0) { 494170530Ssam /* 495170530Ssam * No ADDBA request yet, don't touch. 496170530Ssam */ 497173273Ssam return PROCESS; 498170530Ssam } 499170530Ssam rxseq = le16toh(*(uint16_t *)wh->i_seq) >> IEEE80211_SEQ_SEQ_SHIFT; 500173273Ssam rap->rxa_nframes++; 501173273Ssamagain: 502170530Ssam if (rxseq == rap->rxa_start) { 503170530Ssam /* 504170530Ssam * First frame in window. 505170530Ssam */ 506170530Ssam if (rap->rxa_qframes != 0) { 507170530Ssam /* 508170530Ssam * Dispatch as many packets as we can. 509170530Ssam */ 510170530Ssam KASSERT(rap->rxa_m[0] == NULL, ("unexpected dup")); 511170530Ssam ampdu_dispatch(ni, m); 512170530Ssam ampdu_rx_dispatch(rap, ni); 513173273Ssam return CONSUMED; 514170530Ssam } else { 515170530Ssam /* 516170530Ssam * In order; advance window and notify 517170530Ssam * caller to dispatch directly. 518170530Ssam */ 519170530Ssam rap->rxa_start = IEEE80211_SEQ_INC(rxseq); 520173273Ssam return PROCESS; 521170530Ssam } 522170530Ssam } 523170530Ssam /* 524173273Ssam * Frame is out of order; store if in the BA window. 525170530Ssam */ 526170530Ssam /* calculate offset in BA window */ 527170530Ssam off = IEEE80211_SEQ_SUB(rxseq, rap->rxa_start); 528173273Ssam if (off < rap->rxa_wnd) { 529170530Ssam /* 530173273Ssam * Common case (hopefully): in the BA window. 531173273Ssam * Sec 9.10.7.6 a) (D2.04 p.118 line 47) 532173273Ssam */ 533173273Ssam#ifdef IEEE80211_AMPDU_AGE 534173273Ssam /* 535173273Ssam * Check for frames sitting too long in the reorder queue. 536173273Ssam * This should only ever happen if frames are not delivered 537173273Ssam * without the sender otherwise notifying us (e.g. with a 538173273Ssam * BAR to move the window). Typically this happens because 539173273Ssam * of vendor bugs that cause the sequence number to jump. 540173273Ssam * When this happens we get a gap in the reorder queue that 541173273Ssam * leaves frame sitting on the queue until they get pushed 542173273Ssam * out due to window moves. When the vendor does not send 543173273Ssam * BAR this move only happens due to explicit packet sends 544170530Ssam * 545173273Ssam * NB: we only track the time of the oldest frame in the 546173273Ssam * reorder q; this means that if we flush we might push 547173273Ssam * frames that still "new"; if this happens then subsequent 548173273Ssam * frames will result in BA window moves which cost something 549173273Ssam * but is still better than a big throughput dip. 550170530Ssam */ 551173273Ssam if (rap->rxa_qframes != 0) { 552173273Ssam /* XXX honor batimeout? */ 553173273Ssam if (ticks - rap->rxa_age > ieee80211_ampdu_age) { 554173273Ssam /* 555173273Ssam * Too long since we received the first 556173273Ssam * frame; flush the reorder buffer. 557173273Ssam */ 558173273Ssam if (rap->rxa_qframes != 0) { 559173273Ssam ic->ic_stats.is_ampdu_rx_age += 560173273Ssam rap->rxa_qframes; 561173273Ssam ampdu_rx_flush(ni, rap); 562173273Ssam } 563173273Ssam rap->rxa_start = IEEE80211_SEQ_INC(rxseq); 564173273Ssam return PROCESS; 565173273Ssam } 566173273Ssam } else { 567173273Ssam /* 568173273Ssam * First frame, start aging timer. 569173273Ssam */ 570173273Ssam rap->rxa_age = ticks; 571173273Ssam } 572173273Ssam#endif /* IEEE80211_AMPDU_AGE */ 573173273Ssam /* save packet */ 574173273Ssam if (rap->rxa_m[off] == NULL) { 575173273Ssam rap->rxa_m[off] = m; 576173273Ssam rap->rxa_qframes++; 577173273Ssam rap->rxa_qbytes += m->m_pkthdr.len; 578173273Ssam ic->ic_stats.is_ampdu_rx_reorder++; 579173273Ssam } else { 580173273Ssam IEEE80211_DISCARD_MAC(ic, 581173273Ssam IEEE80211_MSG_INPUT | IEEE80211_MSG_11N, 582173273Ssam ni->ni_macaddr, "a-mpdu duplicate", 583173273Ssam "seqno %u tid %u BA win <%u:%u>", 584173273Ssam rxseq, tid, rap->rxa_start, 585173273Ssam IEEE80211_SEQ_ADD(rap->rxa_start, rap->rxa_wnd-1)); 586173273Ssam ic->ic_stats.is_rx_dup++; 587173273Ssam IEEE80211_NODE_STAT(ni, rx_dup); 588173273Ssam m_freem(m); 589173273Ssam } 590173273Ssam return CONSUMED; 591173273Ssam } 592173273Ssam if (off < IEEE80211_SEQ_BA_RANGE) { 593173273Ssam /* 594173273Ssam * Outside the BA window, but within range; 595173273Ssam * flush the reorder q and move the window. 596173273Ssam * Sec 9.10.7.6 b) (D2.04 p.118 line 60) 597173273Ssam */ 598170530Ssam IEEE80211_NOTE(ic, IEEE80211_MSG_11N, ni, 599173273Ssam "move BA win <%u:%u> (%u frames) rxseq %u tid %u", 600170530Ssam rap->rxa_start, 601173273Ssam IEEE80211_SEQ_ADD(rap->rxa_start, rap->rxa_wnd-1), 602170530Ssam rap->rxa_qframes, rxseq, tid); 603173273Ssam ic->ic_stats.is_ampdu_rx_move++; 604170530Ssam 605173273Ssam /* 606173273Ssam * The spec says to flush frames up to but not including: 607173273Ssam * WinStart_B = rxseq - rap->rxa_wnd + 1 608173273Ssam * Then insert the frame or notify the caller to process 609173273Ssam * it immediately. We can safely do this by just starting 610173273Ssam * over again because we know the frame will now be within 611173273Ssam * the BA window. 612173273Ssam */ 613173273Ssam /* NB: rxa_wnd known to be >0 */ 614173273Ssam ampdu_rx_flush_upto(ni, rap, 615173273Ssam IEEE80211_SEQ_SUB(rxseq, rap->rxa_wnd-1)); 616173273Ssam goto again; 617170530Ssam } else { 618170530Ssam /* 619173273Ssam * Outside the BA window and out of range; toss. 620173273Ssam * Sec 9.10.7.6 c) (D2.04 p.119 line 16) 621170530Ssam */ 622170530Ssam IEEE80211_DISCARD_MAC(ic, 623173273Ssam IEEE80211_MSG_INPUT | IEEE80211_MSG_11N, ni->ni_macaddr, 624173273Ssam "MSDU", "BA win <%u:%u> (%u frames) rxseq %u tid %u%s", 625173273Ssam rap->rxa_start, 626173273Ssam IEEE80211_SEQ_ADD(rap->rxa_start, rap->rxa_wnd-1), 627173273Ssam rap->rxa_qframes, rxseq, tid, 628173273Ssam wh->i_fc[1] & IEEE80211_FC1_RETRY ? " (retransmit)" : ""); 629173273Ssam ic->ic_stats.is_ampdu_rx_drop++; 630173273Ssam IEEE80211_NODE_STAT(ni, rx_drop); 631170530Ssam m_freem(m); 632173273Ssam return CONSUMED; 633170530Ssam } 634173273Ssam#undef CONSUMED 635173273Ssam#undef PROCESS 636170530Ssam#undef IEEE80211_FC0_QOSDATA 637170530Ssam} 638170530Ssam 639170530Ssam/* 640170530Ssam * Process a BAR ctl frame. Dispatch all frames up to 641170530Ssam * the sequence number of the frame. If this frame is 642173273Ssam * out of range it's discarded. 643170530Ssam */ 644170530Ssamvoid 645170530Ssamieee80211_recv_bar(struct ieee80211_node *ni, struct mbuf *m0) 646170530Ssam{ 647170530Ssam struct ieee80211com *ic = ni->ni_ic; 648170530Ssam struct ieee80211_frame_bar *wh; 649170530Ssam struct ieee80211_rx_ampdu *rap; 650170530Ssam ieee80211_seq rxseq; 651170530Ssam int tid, off; 652170530Ssam 653173273Ssam if (!ieee80211_recv_bar_ena) { 654173273Ssam#if 0 655173273Ssam IEEE80211_DISCARD_MAC(ic, IEEE80211_MSG_11N, 656173273Ssam ni->ni_macaddr, "BAR", "%s", "processing disabled"); 657173273Ssam#endif 658173273Ssam ic->ic_stats.is_ampdu_bar_bad++; 659173273Ssam return; 660173273Ssam } 661170530Ssam wh = mtod(m0, struct ieee80211_frame_bar *); 662170530Ssam /* XXX check basic BAR */ 663170530Ssam tid = MS(le16toh(wh->i_ctl), IEEE80211_BAR_TID); 664170530Ssam rap = &ni->ni_rx_ampdu[tid]; 665170530Ssam if ((rap->rxa_flags & IEEE80211_AGGR_XCHGPEND) == 0) { 666170530Ssam /* 667170530Ssam * No ADDBA request yet, don't touch. 668170530Ssam */ 669170530Ssam IEEE80211_DISCARD_MAC(ic, 670170530Ssam IEEE80211_MSG_INPUT | IEEE80211_MSG_11N, 671170530Ssam ni->ni_macaddr, "BAR", "no BA stream, tid %u", tid); 672170530Ssam ic->ic_stats.is_ampdu_bar_bad++; 673170530Ssam return; 674170530Ssam } 675170530Ssam ic->ic_stats.is_ampdu_bar_rx++; 676170530Ssam rxseq = le16toh(wh->i_seq) >> IEEE80211_SEQ_SEQ_SHIFT; 677173273Ssam if (rxseq == rap->rxa_start) 678173273Ssam return; 679170530Ssam /* calculate offset in BA window */ 680170530Ssam off = IEEE80211_SEQ_SUB(rxseq, rap->rxa_start); 681173273Ssam if (off < IEEE80211_SEQ_BA_RANGE) { 682170530Ssam /* 683173273Ssam * Flush the reorder q up to rxseq and move the window. 684173273Ssam * Sec 9.10.7.6 a) (D2.04 p.119 line 22) 685170530Ssam */ 686173273Ssam IEEE80211_NOTE(ic, IEEE80211_MSG_11N, ni, 687173273Ssam "BAR moves BA win <%u:%u> (%u frames) rxseq %u tid %u", 688170530Ssam rap->rxa_start, 689173273Ssam IEEE80211_SEQ_ADD(rap->rxa_start, rap->rxa_wnd-1), 690173273Ssam rap->rxa_qframes, rxseq, tid); 691173273Ssam ic->ic_stats.is_ampdu_bar_move++; 692173273Ssam 693173273Ssam ampdu_rx_flush_upto(ni, rap, rxseq); 694173273Ssam if (off >= rap->rxa_wnd) { 695173273Ssam /* 696173273Ssam * BAR specifies a window start to the right of BA 697173273Ssam * window; we must move it explicitly since 698173273Ssam * ampdu_rx_flush_upto will not. 699173273Ssam */ 700173273Ssam rap->rxa_start = rxseq; 701170530Ssam } 702173273Ssam } else { 703170530Ssam /* 704173273Ssam * Out of range; toss. 705173273Ssam * Sec 9.10.7.6 b) (D2.04 p.119 line 41) 706170530Ssam */ 707173273Ssam IEEE80211_DISCARD_MAC(ic, 708173273Ssam IEEE80211_MSG_INPUT | IEEE80211_MSG_11N, ni->ni_macaddr, 709173273Ssam "BAR", "BA win <%u:%u> (%u frames) rxseq %u tid %u%s", 710173273Ssam rap->rxa_start, 711173273Ssam IEEE80211_SEQ_ADD(rap->rxa_start, rap->rxa_wnd-1), 712173273Ssam rap->rxa_qframes, rxseq, tid, 713173273Ssam wh->i_fc[1] & IEEE80211_FC1_RETRY ? " (retransmit)" : ""); 714173273Ssam ic->ic_stats.is_ampdu_bar_oow++; 715173273Ssam IEEE80211_NODE_STAT(ni, rx_drop); 716170530Ssam } 717170530Ssam} 718170530Ssam 719170530Ssam/* 720170530Ssam * Setup HT-specific state in a node. Called only 721170530Ssam * when HT use is negotiated so we don't do extra 722170530Ssam * work for temporary and/or legacy sta's. 723170530Ssam */ 724170530Ssamvoid 725170530Ssamieee80211_ht_node_init(struct ieee80211_node *ni, const uint8_t *htcap) 726170530Ssam{ 727170530Ssam struct ieee80211_tx_ampdu *tap; 728170530Ssam int ac; 729170530Ssam 730173273Ssam if (ni->ni_flags & IEEE80211_NODE_HT) { 731173273Ssam /* 732173273Ssam * Clean AMPDU state on re-associate. This handles the case 733173273Ssam * where a station leaves w/o notifying us and then returns 734173273Ssam * before node is reaped for inactivity. 735173273Ssam */ 736173273Ssam ieee80211_ht_node_cleanup(ni); 737173273Ssam } 738170530Ssam ieee80211_parse_htcap(ni, htcap); 739170530Ssam for (ac = 0; ac < WME_NUM_AC; ac++) { 740170530Ssam tap = &ni->ni_tx_ampdu[ac]; 741170530Ssam tap->txa_ac = ac; 742173273Ssam /* NB: further initialization deferred */ 743170530Ssam } 744173273Ssam ni->ni_flags |= IEEE80211_NODE_HT | IEEE80211_NODE_AMPDU; 745170530Ssam} 746170530Ssam 747170530Ssam/* 748170530Ssam * Cleanup HT-specific state in a node. Called only 749170530Ssam * when HT use has been marked. 750170530Ssam */ 751170530Ssamvoid 752170530Ssamieee80211_ht_node_cleanup(struct ieee80211_node *ni) 753170530Ssam{ 754170530Ssam struct ieee80211com *ic = ni->ni_ic; 755170530Ssam int i; 756170530Ssam 757170530Ssam KASSERT(ni->ni_flags & IEEE80211_NODE_HT, ("not an HT node")); 758170530Ssam 759170530Ssam /* XXX optimize this */ 760170530Ssam for (i = 0; i < WME_NUM_AC; i++) { 761170530Ssam struct ieee80211_tx_ampdu *tap = &ni->ni_tx_ampdu[i]; 762173273Ssam if (tap->txa_flags & IEEE80211_AGGR_SETUP) { 763173273Ssam /* 764173273Ssam * Stop BA stream if setup so driver has a chance 765173273Ssam * to reclaim any resources it might have allocated. 766173273Ssam */ 767170530Ssam ic->ic_addba_stop(ni, &ni->ni_tx_ampdu[i]); 768173273Ssam IEEE80211_TAPQ_DESTROY(tap); 769173273Ssam /* NB: clearing NAK means we may re-send ADDBA */ 770173273Ssam tap->txa_flags &= 771173273Ssam ~(IEEE80211_AGGR_SETUP | IEEE80211_AGGR_NAK); 772173273Ssam } 773170530Ssam } 774170530Ssam for (i = 0; i < WME_NUM_TID; i++) 775170530Ssam ampdu_rx_stop(&ni->ni_rx_ampdu[i]); 776170530Ssam 777170530Ssam ni->ni_htcap = 0; 778173273Ssam ni->ni_flags &= ~(IEEE80211_NODE_HT | IEEE80211_NODE_HTCOMPAT | 779173273Ssam IEEE80211_NODE_AMPDU); 780170530Ssam} 781170530Ssam 782173273Ssamstatic struct ieee80211_channel * 783173273Ssamfindhtchan(struct ieee80211com *ic, struct ieee80211_channel *c, int htflags) 784173273Ssam{ 785173273Ssam return ieee80211_find_channel(ic, c->ic_freq, 786173273Ssam (c->ic_flags &~ IEEE80211_CHAN_HT) | htflags); 787173273Ssam} 788173273Ssam 789173273Ssam/* 790173273Ssam * Adjust a channel to be HT/non-HT according to the vap's configuration. 791173273Ssam */ 792173273Ssamstruct ieee80211_channel * 793173273Ssamieee80211_ht_adjust_channel(struct ieee80211com *ic, 794173273Ssam struct ieee80211_channel *chan, int flags) 795173273Ssam{ 796173273Ssam struct ieee80211_channel *c; 797173273Ssam 798173273Ssam if (flags & IEEE80211_FEXT_HT) { 799173273Ssam /* promote to HT if possible */ 800173273Ssam if (flags & IEEE80211_FEXT_USEHT40) { 801173273Ssam if (!IEEE80211_IS_CHAN_HT40(chan)) { 802173273Ssam /* NB: arbitrarily pick ht40+ over ht40- */ 803173273Ssam c = findhtchan(ic, chan, IEEE80211_CHAN_HT40U); 804173273Ssam if (c == NULL) 805173273Ssam c = findhtchan(ic, chan, 806173273Ssam IEEE80211_CHAN_HT40D); 807173273Ssam if (c == NULL) 808173273Ssam c = findhtchan(ic, chan, 809173273Ssam IEEE80211_CHAN_HT20); 810173273Ssam if (c != NULL) 811173273Ssam chan = c; 812173273Ssam } 813173273Ssam } else if (!IEEE80211_IS_CHAN_HT20(chan)) { 814173273Ssam c = findhtchan(ic, chan, IEEE80211_CHAN_HT20); 815173273Ssam if (c != NULL) 816173273Ssam chan = c; 817173273Ssam } 818173273Ssam } else if (IEEE80211_IS_CHAN_HT(chan)) { 819173273Ssam /* demote to legacy, HT use is disabled */ 820173273Ssam c = ieee80211_find_channel(ic, chan->ic_freq, 821173273Ssam chan->ic_flags &~ IEEE80211_CHAN_HT); 822173273Ssam if (c != NULL) 823173273Ssam chan = c; 824173273Ssam } 825173273Ssam return chan; 826173273Ssam} 827173273Ssam 828173273Ssam/* 829173273Ssam * Setup HT-specific state for a legacy WDS peer. 830173273Ssam */ 831173273Ssamvoid 832173273Ssamieee80211_ht_wds_init(struct ieee80211_node *ni) 833173273Ssam{ 834173273Ssam struct ieee80211com *ic = ni->ni_ic; 835173273Ssam struct ieee80211_tx_ampdu *tap; 836173273Ssam int ac; 837173273Ssam 838173273Ssam KASSERT(ic->ic_flags_ext & IEEE80211_FEXT_HT, ("no HT requested")); 839173273Ssam 840173273Ssam /* XXX check scan cache in case peer has an ap and we have info */ 841173273Ssam /* 842173273Ssam * If setup with a legacy channel; locate an HT channel. 843173273Ssam * Otherwise if the inherited channel (from a companion 844173273Ssam * AP) is suitable use it so we use the same location 845173273Ssam * for the extension channel). 846173273Ssam */ 847173273Ssam ni->ni_chan = ieee80211_ht_adjust_channel(ic, ni->ni_chan, 848173273Ssam ic->ic_flags_ext); 849173273Ssam 850173273Ssam ni->ni_htcap = 0; 851173273Ssam if (ic->ic_flags_ext & IEEE80211_FEXT_SHORTGI20) 852173273Ssam ni->ni_htcap |= IEEE80211_HTCAP_SHORTGI20; 853173273Ssam if (IEEE80211_IS_CHAN_HT40(ni->ni_chan)) { 854173273Ssam ni->ni_htcap |= IEEE80211_HTCAP_CHWIDTH40; 855173273Ssam ni->ni_chw = 40; 856173273Ssam if (IEEE80211_IS_CHAN_HT40U(ni->ni_chan)) 857173273Ssam ni->ni_ht2ndchan = IEEE80211_HTINFO_2NDCHAN_ABOVE; 858173273Ssam else if (IEEE80211_IS_CHAN_HT40D(ni->ni_chan)) 859173273Ssam ni->ni_ht2ndchan = IEEE80211_HTINFO_2NDCHAN_BELOW; 860173273Ssam if (ic->ic_flags_ext & IEEE80211_FEXT_SHORTGI40) 861173273Ssam ni->ni_htcap |= IEEE80211_HTCAP_SHORTGI40; 862173273Ssam } else { 863173273Ssam ni->ni_chw = 20; 864173273Ssam ni->ni_ht2ndchan = IEEE80211_HTINFO_2NDCHAN_NONE; 865173273Ssam } 866173273Ssam ni->ni_htctlchan = ni->ni_chan->ic_ieee; 867173273Ssam 868173273Ssam ni->ni_htopmode = 0; /* XXX need protection state */ 869173273Ssam ni->ni_htstbc = 0; /* XXX need info */ 870173273Ssam 871173273Ssam for (ac = 0; ac < WME_NUM_AC; ac++) { 872173273Ssam tap = &ni->ni_tx_ampdu[ac]; 873173273Ssam tap->txa_ac = ac; 874173273Ssam } 875173273Ssam /* NB: AMPDU tx/rx governed by IEEE80211_FEXT_AMPDU_{TX,RX} */ 876173273Ssam ni->ni_flags |= IEEE80211_NODE_HT | IEEE80211_NODE_AMPDU; 877173273Ssam} 878173273Ssam 879173273Ssam/* 880173273Ssam * Notify hostap vaps of a change in the HTINFO ie. 881173273Ssam */ 882173273Ssamstatic void 883173273Ssamhtinfo_notify(struct ieee80211com *ic) 884173273Ssam{ 885173273Ssam if (ic->ic_opmode != IEEE80211_M_HOSTAP) 886173273Ssam return; 887173273Ssam IEEE80211_NOTE(ic, 888173273Ssam IEEE80211_MSG_ASSOC | IEEE80211_MSG_11N, 889173273Ssam ic->ic_bss, 890173273Ssam "HT bss occupancy change: %d sta, %d ht, " 891173273Ssam "%d ht40%s, HT protmode now 0x%x" 892173273Ssam , ic->ic_sta_assoc 893173273Ssam , ic->ic_ht_sta_assoc 894173273Ssam , ic->ic_ht40_sta_assoc 895173273Ssam , (ic->ic_flags_ext & IEEE80211_FEXT_NONHT_PR) ? 896173273Ssam ", non-HT sta present" : "" 897173273Ssam , ic->ic_curhtprotmode); 898173273Ssam ieee80211_beacon_notify(ic, IEEE80211_BEACON_HTINFO); 899173273Ssam} 900173273Ssam 901173273Ssam/* 902173273Ssam * Calculate HT protection mode from current 903173273Ssam * state and handle updates. 904173273Ssam */ 905173273Ssamstatic void 906173273Ssamhtinfo_update(struct ieee80211com *ic) 907173273Ssam{ 908173273Ssam uint8_t protmode; 909173273Ssam 910173273Ssam if (ic->ic_flags_ext & IEEE80211_FEXT_NONHT_PR) { 911173273Ssam protmode = IEEE80211_HTINFO_OPMODE_PROTOPT 912173273Ssam | IEEE80211_HTINFO_NONHT_PRESENT; 913173273Ssam } else if (ic->ic_sta_assoc != ic->ic_ht_sta_assoc) { 914173273Ssam protmode = IEEE80211_HTINFO_OPMODE_MIXED 915173273Ssam | IEEE80211_HTINFO_NONHT_PRESENT; 916173273Ssam } else if (IEEE80211_IS_CHAN_HT40(ic->ic_bsschan) && 917173273Ssam ic->ic_sta_assoc != ic->ic_ht40_sta_assoc) { 918173273Ssam protmode = IEEE80211_HTINFO_OPMODE_HT20PR; 919173273Ssam } else { 920173273Ssam protmode = IEEE80211_HTINFO_OPMODE_PURE; 921173273Ssam } 922173273Ssam if (protmode != ic->ic_curhtprotmode) { 923173273Ssam ic->ic_curhtprotmode = protmode; 924173273Ssam htinfo_notify(ic); 925173273Ssam } 926173273Ssam} 927173273Ssam 928173273Ssam/* 929173273Ssam * Handle an HT station joining a BSS. 930173273Ssam */ 931173273Ssamvoid 932173273Ssamieee80211_ht_node_join(struct ieee80211_node *ni) 933173273Ssam{ 934173273Ssam struct ieee80211com *ic = ni->ni_ic; 935173273Ssam 936173273Ssam IEEE80211_LOCK_ASSERT(ic); 937173273Ssam 938173273Ssam if (ni->ni_flags & IEEE80211_NODE_HT) { 939173273Ssam ic->ic_ht_sta_assoc++; 940173273Ssam if (ni->ni_chw == 40) 941173273Ssam ic->ic_ht40_sta_assoc++; 942173273Ssam } 943173273Ssam htinfo_update(ic); 944173273Ssam} 945173273Ssam 946173273Ssam/* 947173273Ssam * Handle an HT station leaving a BSS. 948173273Ssam */ 949173273Ssamvoid 950173273Ssamieee80211_ht_node_leave(struct ieee80211_node *ni) 951173273Ssam{ 952173273Ssam struct ieee80211com *ic = ni->ni_ic; 953173273Ssam 954173273Ssam IEEE80211_LOCK_ASSERT(ic); 955173273Ssam 956173273Ssam if (ni->ni_flags & IEEE80211_NODE_HT) { 957173273Ssam ic->ic_ht_sta_assoc--; 958173273Ssam if (ni->ni_chw == 40) 959173273Ssam ic->ic_ht40_sta_assoc--; 960173273Ssam } 961173273Ssam htinfo_update(ic); 962173273Ssam} 963173273Ssam 964173273Ssam/* 965173273Ssam * Public version of htinfo_update; used for processing 966173273Ssam * beacon frames from overlapping bss in hostap_recv_mgmt. 967173273Ssam */ 968173273Ssamvoid 969173273Ssamieee80211_htinfo_update(struct ieee80211com *ic, int protmode) 970173273Ssam{ 971173273Ssam if (protmode != ic->ic_curhtprotmode) { 972173273Ssam ic->ic_curhtprotmode = protmode; 973173273Ssam htinfo_notify(ic); 974173273Ssam } 975173273Ssam} 976173273Ssam 977173273Ssam/* 978173273Ssam * Time out presence of an overlapping bss with non-HT 979173273Ssam * stations. When operating in hostap mode we listen for 980173273Ssam * beacons from other stations and if we identify a non-HT 981173273Ssam * station is present we update the opmode field of the 982173273Ssam * HTINFO ie. To identify when all non-HT stations are 983173273Ssam * gone we time out this condition. 984173273Ssam */ 985173273Ssamvoid 986173273Ssamieee80211_ht_timeout(struct ieee80211com *ic) 987173273Ssam{ 988173273Ssam IEEE80211_LOCK_ASSERT(ic); 989173273Ssam 990173273Ssam if ((ic->ic_flags_ext & IEEE80211_FEXT_NONHT_PR) && 991173273Ssam time_after(ticks, ic->ic_lastnonht + IEEE80211_NONHT_PRESENT_AGE)) { 992173273Ssam#if 0 993173273Ssam IEEE80211_NOTE(ic, IEEE80211_MSG_11N, ni, 994173273Ssam "%s", "time out non-HT STA present on channel"); 995173273Ssam#endif 996173273Ssam ic->ic_flags_ext &= ~IEEE80211_FEXT_NONHT_PR; 997173273Ssam htinfo_update(ic); 998173273Ssam } 999173273Ssam} 1000173273Ssam 1001170530Ssam/* unalligned little endian access */ 1002170530Ssam#define LE_READ_2(p) \ 1003170530Ssam ((uint16_t) \ 1004170530Ssam ((((const uint8_t *)(p))[0] ) | \ 1005170530Ssam (((const uint8_t *)(p))[1] << 8))) 1006170530Ssam 1007170530Ssam/* 1008170530Ssam * Process an 802.11n HT capabilities ie. 1009170530Ssam */ 1010170530Ssamvoid 1011170530Ssamieee80211_parse_htcap(struct ieee80211_node *ni, const uint8_t *ie) 1012170530Ssam{ 1013170530Ssam struct ieee80211com *ic = ni->ni_ic; 1014170530Ssam 1015170530Ssam if (ie[0] == IEEE80211_ELEMID_VENDOR) { 1016170530Ssam /* 1017170530Ssam * Station used Vendor OUI ie to associate; 1018170530Ssam * mark the node so when we respond we'll use 1019170530Ssam * the Vendor OUI's and not the standard ie's. 1020170530Ssam */ 1021170530Ssam ni->ni_flags |= IEEE80211_NODE_HTCOMPAT; 1022170530Ssam ie += 4; 1023170530Ssam } else 1024170530Ssam ni->ni_flags &= ~IEEE80211_NODE_HTCOMPAT; 1025170530Ssam 1026170530Ssam ni->ni_htcap = LE_READ_2(ie + 1027170530Ssam __offsetof(struct ieee80211_ie_htcap, hc_cap)); 1028170530Ssam ni->ni_htparam = ie[__offsetof(struct ieee80211_ie_htcap, hc_param)]; 1029173273Ssam /* XXX needed or will ieee80211_parse_htinfo always be called? */ 1030173273Ssam ni->ni_chw = (ni->ni_htcap & IEEE80211_HTCAP_CHWIDTH40) && 1031173273Ssam (ic->ic_flags_ext & IEEE80211_FEXT_USEHT40) ? 40 : 20; 1032170530Ssam} 1033170530Ssam 1034170530Ssam/* 1035173273Ssam * Process an 802.11n HT info ie and update the node state. 1036173273Ssam * Note that we handle use this information to identify the 1037173273Ssam * correct channel (HT20, HT40+, HT40-, legacy). The caller 1038173273Ssam * is responsible for insuring any required channel change is 1039173273Ssam * done (e.g. in sta mode when parsing the contents of a 1040173273Ssam * beacon frame). 1041170530Ssam */ 1042170530Ssamvoid 1043170530Ssamieee80211_parse_htinfo(struct ieee80211_node *ni, const uint8_t *ie) 1044170530Ssam{ 1045173273Ssam struct ieee80211com *ic = ni->ni_ic; 1046170530Ssam const struct ieee80211_ie_htinfo *htinfo; 1047173273Ssam struct ieee80211_channel *c; 1048170530Ssam uint16_t w; 1049173273Ssam int htflags, chanflags; 1050170530Ssam 1051170530Ssam if (ie[0] == IEEE80211_ELEMID_VENDOR) 1052170530Ssam ie += 4; 1053170530Ssam htinfo = (const struct ieee80211_ie_htinfo *) ie; 1054170530Ssam ni->ni_htctlchan = htinfo->hi_ctrlchannel; 1055170530Ssam ni->ni_ht2ndchan = SM(htinfo->hi_byte1, IEEE80211_HTINFO_2NDCHAN); 1056172055Ssam w = LE_READ_2(&htinfo->hi_byte2); 1057170530Ssam ni->ni_htopmode = SM(w, IEEE80211_HTINFO_OPMODE); 1058170530Ssam w = LE_READ_2(&htinfo->hi_byte45); 1059170530Ssam ni->ni_htstbc = SM(w, IEEE80211_HTINFO_BASIC_STBCMCS); 1060173273Ssam /* 1061173273Ssam * Handle 11n channel switch. Use the received HT ie's to 1062173273Ssam * identify the right channel to use. If we cannot locate it 1063173273Ssam * in the channel table then fallback to legacy operation. 1064173273Ssam */ 1065173273Ssam htflags = (ic->ic_flags_ext & IEEE80211_FEXT_HT) ? 1066173273Ssam IEEE80211_CHAN_HT20 : 0; 1067173273Ssam /* NB: honor operating mode constraint */ 1068173273Ssam if ((htinfo->hi_byte1 & IEEE80211_HTINFO_TXWIDTH_2040) && 1069173273Ssam (ic->ic_flags_ext & IEEE80211_FEXT_USEHT40)) { 1070173273Ssam if (ni->ni_ht2ndchan == IEEE80211_HTINFO_2NDCHAN_ABOVE) 1071173273Ssam htflags = IEEE80211_CHAN_HT40U; 1072173273Ssam else if (ni->ni_ht2ndchan == IEEE80211_HTINFO_2NDCHAN_BELOW) 1073173273Ssam htflags = IEEE80211_CHAN_HT40D; 1074170530Ssam } 1075173273Ssam chanflags = (ni->ni_chan->ic_flags &~ IEEE80211_CHAN_HT) | htflags; 1076173273Ssam if (chanflags != ni->ni_chan->ic_flags) { 1077173273Ssam c = ieee80211_find_channel(ic, ni->ni_chan->ic_freq, chanflags); 1078173273Ssam if (c == NULL && htflags != IEEE80211_CHAN_HT20) { 1079173273Ssam /* 1080173273Ssam * No HT40 channel entry in our table; fall back 1081173273Ssam * to HT20 operation. This should not happen. 1082173273Ssam */ 1083173273Ssam c = findhtchan(ic, ni->ni_chan, IEEE80211_CHAN_HT20); 1084173273Ssam IEEE80211_NOTE(ni->ni_ic, 1085173273Ssam IEEE80211_MSG_ASSOC | IEEE80211_MSG_11N, ni, 1086173273Ssam "no HT40 channel (freq %u), falling back to HT20", 1087173273Ssam ni->ni_chan->ic_freq); 1088173273Ssam /* XXX stat */ 1089173273Ssam } 1090173273Ssam if (c != NULL && c != ni->ni_chan) { 1091173273Ssam IEEE80211_NOTE(ni->ni_ic, 1092173273Ssam IEEE80211_MSG_ASSOC | IEEE80211_MSG_11N, ni, 1093173273Ssam "switch station to HT%d channel %u/0x%x", 1094173273Ssam IEEE80211_IS_CHAN_HT40(c) ? 40 : 20, 1095173273Ssam c->ic_freq, c->ic_flags); 1096173273Ssam ni->ni_chan = c; 1097173273Ssam } 1098173273Ssam /* NB: caller responsible for forcing any channel change */ 1099173273Ssam } 1100173273Ssam /* update node's tx channel width */ 1101173273Ssam ni->ni_chw = IEEE80211_IS_CHAN_HT40(ni->ni_chan)? 40 : 20; 1102170530Ssam} 1103170530Ssam 1104170530Ssam/* 1105170530Ssam * Install received HT rate set by parsing the HT cap ie. 1106170530Ssam */ 1107170530Ssamint 1108170530Ssamieee80211_setup_htrates(struct ieee80211_node *ni, const uint8_t *ie, int flags) 1109170530Ssam{ 1110170530Ssam struct ieee80211com *ic = ni->ni_ic; 1111170530Ssam const struct ieee80211_ie_htcap *htcap; 1112170530Ssam struct ieee80211_htrateset *rs; 1113170530Ssam int i; 1114170530Ssam 1115170530Ssam rs = &ni->ni_htrates; 1116170530Ssam memset(rs, 0, sizeof(*rs)); 1117170530Ssam if (ie != NULL) { 1118170530Ssam if (ie[0] == IEEE80211_ELEMID_VENDOR) 1119170530Ssam ie += 4; 1120170530Ssam htcap = (const struct ieee80211_ie_htcap *) ie; 1121170530Ssam for (i = 0; i < IEEE80211_HTRATE_MAXSIZE; i++) { 1122170530Ssam if (isclr(htcap->hc_mcsset, i)) 1123170530Ssam continue; 1124170530Ssam if (rs->rs_nrates == IEEE80211_HTRATE_MAXSIZE) { 1125170530Ssam IEEE80211_NOTE(ic, 1126170530Ssam IEEE80211_MSG_XRATE | IEEE80211_MSG_11N, ni, 1127170530Ssam "WARNING, HT rate set too large; only " 1128170530Ssam "using %u rates", IEEE80211_HTRATE_MAXSIZE); 1129170530Ssam ic->ic_stats.is_rx_rstoobig++; 1130170530Ssam break; 1131170530Ssam } 1132170530Ssam rs->rs_rates[rs->rs_nrates++] = i; 1133170530Ssam } 1134170530Ssam } 1135170530Ssam return ieee80211_fix_rate(ni, (struct ieee80211_rateset *) rs, flags); 1136170530Ssam} 1137170530Ssam 1138170530Ssam/* 1139170530Ssam * Mark rates in a node's HT rate set as basic according 1140170530Ssam * to the information in the supplied HT info ie. 1141170530Ssam */ 1142170530Ssamvoid 1143170530Ssamieee80211_setup_basic_htrates(struct ieee80211_node *ni, const uint8_t *ie) 1144170530Ssam{ 1145170530Ssam const struct ieee80211_ie_htinfo *htinfo; 1146170530Ssam struct ieee80211_htrateset *rs; 1147170530Ssam int i, j; 1148170530Ssam 1149170530Ssam if (ie[0] == IEEE80211_ELEMID_VENDOR) 1150170530Ssam ie += 4; 1151170530Ssam htinfo = (const struct ieee80211_ie_htinfo *) ie; 1152170530Ssam rs = &ni->ni_htrates; 1153170530Ssam if (rs->rs_nrates == 0) { 1154170530Ssam IEEE80211_NOTE(ni->ni_ic, 1155170530Ssam IEEE80211_MSG_XRATE | IEEE80211_MSG_11N, ni, 1156170530Ssam "%s", "WARNING, empty HT rate set"); 1157170530Ssam return; 1158170530Ssam } 1159170530Ssam for (i = 0; i < IEEE80211_HTRATE_MAXSIZE; i++) { 1160170530Ssam if (isclr(htinfo->hi_basicmcsset, i)) 1161170530Ssam continue; 1162170530Ssam for (j = 0; j < rs->rs_nrates; j++) 1163170530Ssam if ((rs->rs_rates[j] & IEEE80211_RATE_VAL) == i) 1164170530Ssam rs->rs_rates[j] |= IEEE80211_RATE_BASIC; 1165170530Ssam } 1166170530Ssam} 1167170530Ssam 1168170530Ssamstatic void 1169170530Ssamaddba_timeout(void *arg) 1170170530Ssam{ 1171170530Ssam struct ieee80211_tx_ampdu *tap = arg; 1172170530Ssam 1173170530Ssam /* XXX ? */ 1174170530Ssam tap->txa_flags &= ~IEEE80211_AGGR_XCHGPEND; 1175170530Ssam tap->txa_attempts++; 1176170530Ssam} 1177170530Ssam 1178170530Ssamstatic void 1179170530Ssamaddba_start_timeout(struct ieee80211_tx_ampdu *tap) 1180170530Ssam{ 1181170530Ssam /* XXX use CALLOUT_PENDING instead? */ 1182170530Ssam callout_reset(&tap->txa_timer, IEEE80211_AGGR_TIMEOUT, 1183170530Ssam addba_timeout, tap); 1184170530Ssam tap->txa_flags |= IEEE80211_AGGR_XCHGPEND; 1185170530Ssam tap->txa_lastrequest = ticks; 1186170530Ssam} 1187170530Ssam 1188170530Ssamstatic void 1189170530Ssamaddba_stop_timeout(struct ieee80211_tx_ampdu *tap) 1190170530Ssam{ 1191170530Ssam /* XXX use CALLOUT_PENDING instead? */ 1192170530Ssam if (tap->txa_flags & IEEE80211_AGGR_XCHGPEND) { 1193170530Ssam callout_stop(&tap->txa_timer); 1194170530Ssam tap->txa_flags &= ~IEEE80211_AGGR_XCHGPEND; 1195170530Ssam } 1196170530Ssam} 1197170530Ssam 1198170530Ssam/* 1199170530Ssam * Default method for requesting A-MPDU tx aggregation. 1200170530Ssam * We setup the specified state block and start a timer 1201170530Ssam * to wait for an ADDBA response frame. 1202170530Ssam */ 1203170530Ssamstatic int 1204170530Ssamieee80211_addba_request(struct ieee80211_node *ni, 1205170530Ssam struct ieee80211_tx_ampdu *tap, 1206170530Ssam int dialogtoken, int baparamset, int batimeout) 1207170530Ssam{ 1208170530Ssam int bufsiz; 1209170530Ssam 1210170530Ssam /* XXX locking */ 1211170530Ssam tap->txa_token = dialogtoken; 1212170530Ssam tap->txa_flags |= IEEE80211_AGGR_IMMEDIATE; 1213170530Ssam tap->txa_start = tap->txa_seqstart = 0; 1214170530Ssam bufsiz = MS(baparamset, IEEE80211_BAPS_BUFSIZ); 1215170530Ssam tap->txa_wnd = (bufsiz == 0) ? 1216170530Ssam IEEE80211_AGGR_BAWMAX : min(bufsiz, IEEE80211_AGGR_BAWMAX); 1217170530Ssam addba_start_timeout(tap); 1218170530Ssam return 1; 1219170530Ssam} 1220170530Ssam 1221170530Ssam/* 1222170530Ssam * Default method for processing an A-MPDU tx aggregation 1223170530Ssam * response. We shutdown any pending timer and update the 1224170530Ssam * state block according to the reply. 1225170530Ssam */ 1226170530Ssamstatic int 1227170530Ssamieee80211_addba_response(struct ieee80211_node *ni, 1228170530Ssam struct ieee80211_tx_ampdu *tap, 1229170530Ssam int status, int baparamset, int batimeout) 1230170530Ssam{ 1231170530Ssam int bufsiz; 1232170530Ssam 1233170530Ssam /* XXX locking */ 1234170530Ssam addba_stop_timeout(tap); 1235170530Ssam if (status == IEEE80211_STATUS_SUCCESS) { 1236170530Ssam bufsiz = MS(baparamset, IEEE80211_BAPS_BUFSIZ); 1237170530Ssam /* XXX override our request? */ 1238170530Ssam tap->txa_wnd = (bufsiz == 0) ? 1239170530Ssam IEEE80211_AGGR_BAWMAX : min(bufsiz, IEEE80211_AGGR_BAWMAX); 1240170530Ssam tap->txa_flags |= IEEE80211_AGGR_RUNNING; 1241173273Ssam } else { 1242173273Ssam /* mark tid so we don't try again */ 1243173273Ssam tap->txa_flags |= IEEE80211_AGGR_NAK; 1244170530Ssam } 1245170530Ssam return 1; 1246170530Ssam} 1247170530Ssam 1248170530Ssam/* 1249170530Ssam * Default method for stopping A-MPDU tx aggregation. 1250170530Ssam * Any timer is cleared and we drain any pending frames. 1251170530Ssam */ 1252170530Ssamstatic void 1253170530Ssamieee80211_addba_stop(struct ieee80211_node *ni, struct ieee80211_tx_ampdu *tap) 1254170530Ssam{ 1255170530Ssam /* XXX locking */ 1256170530Ssam addba_stop_timeout(tap); 1257170530Ssam if (tap->txa_flags & IEEE80211_AGGR_RUNNING) { 1258170530Ssam /* clear aggregation queue */ 1259170530Ssam ieee80211_drain_ifq(&tap->txa_q); 1260170530Ssam tap->txa_flags &= ~IEEE80211_AGGR_RUNNING; 1261170530Ssam } 1262170530Ssam tap->txa_attempts = 0; 1263170530Ssam} 1264170530Ssam 1265170530Ssam/* 1266170530Ssam * Process a received action frame using the default aggregation 1267170530Ssam * policy. We intercept ADDBA-related frames and use them to 1268170530Ssam * update our aggregation state. All other frames are passed up 1269170530Ssam * for processing by ieee80211_recv_action. 1270170530Ssam */ 1271170530Ssamstatic void 1272170530Ssamieee80211_aggr_recv_action(struct ieee80211_node *ni, 1273170530Ssam const uint8_t *frm, const uint8_t *efrm) 1274170530Ssam{ 1275170530Ssam struct ieee80211com *ic = ni->ni_ic; 1276170530Ssam const struct ieee80211_action *ia; 1277170530Ssam struct ieee80211_rx_ampdu *rap; 1278170530Ssam struct ieee80211_tx_ampdu *tap; 1279170530Ssam uint8_t dialogtoken; 1280170530Ssam uint16_t baparamset, batimeout, baseqctl, code; 1281170530Ssam uint16_t args[4]; 1282170530Ssam int tid, ac, bufsiz; 1283170530Ssam 1284170530Ssam ia = (const struct ieee80211_action *) frm; 1285170530Ssam switch (ia->ia_category) { 1286170530Ssam case IEEE80211_ACTION_CAT_BA: 1287170530Ssam switch (ia->ia_action) { 1288170530Ssam case IEEE80211_ACTION_BA_ADDBA_REQUEST: 1289170530Ssam dialogtoken = frm[2]; 1290170530Ssam baparamset = LE_READ_2(frm+3); 1291170530Ssam batimeout = LE_READ_2(frm+5); 1292170530Ssam baseqctl = LE_READ_2(frm+7); 1293170530Ssam 1294170530Ssam tid = MS(baparamset, IEEE80211_BAPS_TID); 1295170530Ssam bufsiz = MS(baparamset, IEEE80211_BAPS_BUFSIZ); 1296170530Ssam 1297170530Ssam IEEE80211_NOTE(ic, 1298170530Ssam IEEE80211_MSG_ACTION | IEEE80211_MSG_11N, ni, 1299170530Ssam "recv ADDBA request: dialogtoken %u " 1300170530Ssam "baparamset 0x%x (tid %d bufsiz %d) batimeout %d " 1301173273Ssam "baseqctl %d:%d", 1302173273Ssam dialogtoken, baparamset, tid, bufsiz, batimeout, 1303173273Ssam MS(baseqctl, IEEE80211_BASEQ_START), 1304173273Ssam MS(baseqctl, IEEE80211_BASEQ_FRAG)); 1305170530Ssam 1306170530Ssam rap = &ni->ni_rx_ampdu[tid]; 1307170530Ssam 1308170530Ssam /* Send ADDBA response */ 1309170530Ssam args[0] = dialogtoken; 1310173273Ssam /* 1311173273Ssam * NB: We ack only if the sta associated with HT and 1312173273Ssam * the ap is configured to do AMPDU rx (the latter 1313173273Ssam * violates the 11n spec and is mostly for testing). 1314173273Ssam */ 1315173273Ssam if ((ni->ni_flags & IEEE80211_NODE_AMPDU_RX) && 1316173273Ssam (ic->ic_flags_ext & IEEE80211_FEXT_AMPDU_RX)) { 1317170530Ssam ampdu_rx_start(rap, bufsiz, 1318170530Ssam MS(baseqctl, IEEE80211_BASEQ_START)); 1319170530Ssam 1320170530Ssam args[1] = IEEE80211_STATUS_SUCCESS; 1321173273Ssam } else { 1322173273Ssam IEEE80211_NOTE(ic, 1323173273Ssam IEEE80211_MSG_ACTION | IEEE80211_MSG_11N, 1324173273Ssam ni, "reject ADDBA request: %s", 1325173273Ssam ni->ni_flags & IEEE80211_NODE_AMPDU_RX ? 1326173273Ssam "administratively disabled" : 1327173273Ssam "not negotiated for station"); 1328173273Ssam ic->ic_stats.is_addba_reject++; 1329170530Ssam args[1] = IEEE80211_STATUS_UNSPECIFIED; 1330173273Ssam } 1331170530Ssam /* XXX honor rap flags? */ 1332170530Ssam args[2] = IEEE80211_BAPS_POLICY_IMMEDIATE 1333170530Ssam | SM(tid, IEEE80211_BAPS_TID) 1334170530Ssam | SM(rap->rxa_wnd, IEEE80211_BAPS_BUFSIZ) 1335170530Ssam ; 1336170530Ssam args[3] = 0; 1337170530Ssam ic->ic_send_action(ni, IEEE80211_ACTION_CAT_BA, 1338170530Ssam IEEE80211_ACTION_BA_ADDBA_RESPONSE, args); 1339170530Ssam return; 1340170530Ssam 1341170530Ssam case IEEE80211_ACTION_BA_ADDBA_RESPONSE: 1342170530Ssam dialogtoken = frm[2]; 1343170530Ssam code = LE_READ_2(frm+3); 1344170530Ssam baparamset = LE_READ_2(frm+5); 1345170530Ssam tid = MS(baparamset, IEEE80211_BAPS_TID); 1346170530Ssam bufsiz = MS(baparamset, IEEE80211_BAPS_BUFSIZ); 1347170530Ssam batimeout = LE_READ_2(frm+7); 1348170530Ssam 1349173273Ssam ac = TID_TO_WME_AC(tid); 1350173273Ssam tap = &ni->ni_tx_ampdu[ac]; 1351173273Ssam if ((tap->txa_flags & ~IEEE80211_AGGR_XCHGPEND) == 0) { 1352173273Ssam IEEE80211_DISCARD_MAC(ic, 1353173273Ssam IEEE80211_MSG_ACTION | IEEE80211_MSG_11N, 1354173273Ssam ni->ni_macaddr, "ADDBA response", 1355173273Ssam "no pending ADDBA, tid %d dialogtoken %u " 1356173273Ssam "code %d", tid, dialogtoken, code); 1357173273Ssam ic->ic_stats.is_addba_norequest++; 1358173273Ssam return; 1359173273Ssam } 1360173273Ssam if (dialogtoken != tap->txa_token) { 1361173273Ssam IEEE80211_DISCARD_MAC(ic, 1362173273Ssam IEEE80211_MSG_ACTION | IEEE80211_MSG_11N, 1363173273Ssam ni->ni_macaddr, "ADDBA response", 1364173273Ssam "dialogtoken mismatch: waiting for %d, " 1365173273Ssam "received %d, tid %d code %d", 1366173273Ssam tap->txa_token, dialogtoken, tid, code); 1367173273Ssam ic->ic_stats.is_addba_badtoken++; 1368173273Ssam return; 1369173273Ssam } 1370173273Ssam 1371170530Ssam IEEE80211_NOTE(ic, 1372170530Ssam IEEE80211_MSG_ACTION | IEEE80211_MSG_11N, ni, 1373170530Ssam "recv ADDBA response: dialogtoken %u code %d " 1374170530Ssam "baparamset 0x%x (tid %d bufsiz %d) batimeout %d", 1375170530Ssam dialogtoken, code, baparamset, tid, bufsiz, 1376170530Ssam batimeout); 1377170530Ssam ic->ic_addba_response(ni, tap, 1378170530Ssam code, baparamset, batimeout); 1379170530Ssam return; 1380170530Ssam 1381170530Ssam case IEEE80211_ACTION_BA_DELBA: 1382170530Ssam baparamset = LE_READ_2(frm+2); 1383170530Ssam code = LE_READ_2(frm+4); 1384170530Ssam 1385170530Ssam tid = MS(baparamset, IEEE80211_DELBAPS_TID); 1386170530Ssam 1387170530Ssam IEEE80211_NOTE(ic, 1388170530Ssam IEEE80211_MSG_ACTION | IEEE80211_MSG_11N, ni, 1389170530Ssam "recv DELBA: baparamset 0x%x (tid %d initiator %d) " 1390170530Ssam "code %d", baparamset, tid, 1391170530Ssam MS(baparamset, IEEE80211_DELBAPS_INIT), code); 1392170530Ssam 1393170530Ssam if ((baparamset & IEEE80211_DELBAPS_INIT) == 0) { 1394170530Ssam ac = TID_TO_WME_AC(tid); 1395170530Ssam tap = &ni->ni_tx_ampdu[ac]; 1396170530Ssam ic->ic_addba_stop(ni, tap); 1397170530Ssam } else { 1398170530Ssam rap = &ni->ni_rx_ampdu[tid]; 1399170530Ssam ampdu_rx_stop(rap); 1400170530Ssam } 1401170530Ssam return; 1402170530Ssam } 1403170530Ssam break; 1404170530Ssam } 1405173273Ssam ieee80211_recv_action(ni, frm, efrm); 1406170530Ssam} 1407170530Ssam 1408170530Ssam/* 1409170530Ssam * Process a received 802.11n action frame. 1410170530Ssam * Aggregation-related frames are assumed to be handled 1411170530Ssam * already; we handle any other frames we can, otherwise 1412170530Ssam * complain about being unsupported (with debugging). 1413170530Ssam */ 1414170530Ssamvoid 1415170530Ssamieee80211_recv_action(struct ieee80211_node *ni, 1416170530Ssam const uint8_t *frm, const uint8_t *efrm) 1417170530Ssam{ 1418170530Ssam struct ieee80211com *ic = ni->ni_ic; 1419170530Ssam const struct ieee80211_action *ia; 1420170530Ssam int chw; 1421170530Ssam 1422170530Ssam ia = (const struct ieee80211_action *) frm; 1423170530Ssam switch (ia->ia_category) { 1424170530Ssam case IEEE80211_ACTION_CAT_BA: 1425170530Ssam IEEE80211_NOTE(ic, 1426170530Ssam IEEE80211_MSG_ACTION | IEEE80211_MSG_11N, ni, 1427170530Ssam "%s: BA action %d not implemented", __func__, 1428170530Ssam ia->ia_action); 1429170530Ssam ic->ic_stats.is_rx_mgtdiscard++; 1430170530Ssam break; 1431170530Ssam case IEEE80211_ACTION_CAT_HT: 1432170530Ssam switch (ia->ia_action) { 1433170530Ssam case IEEE80211_ACTION_HT_TXCHWIDTH: 1434170530Ssam chw = frm[2] == IEEE80211_A_HT_TXCHWIDTH_2040 ? 40 : 20; 1435170530Ssam if (chw != ni->ni_chw) { 1436170530Ssam ni->ni_chw = chw; 1437170530Ssam ni->ni_flags |= IEEE80211_NODE_CHWUPDATE; 1438170530Ssam } 1439170530Ssam IEEE80211_NOTE(ic, 1440170530Ssam IEEE80211_MSG_ACTION | IEEE80211_MSG_11N, ni, 1441173273Ssam "%s: HT txchwidth, width %d (%s)", 1442170530Ssam __func__, chw, 1443170530Ssam ni->ni_flags & IEEE80211_NODE_CHWUPDATE ? 1444170530Ssam "new" : "no change"); 1445170530Ssam break; 1446173273Ssam case IEEE80211_ACTION_HT_MIMOPWRSAVE: 1447173273Ssam IEEE80211_NOTE(ic, 1448173273Ssam IEEE80211_MSG_ACTION | IEEE80211_MSG_11N, ni, 1449173273Ssam "%s: HT MIMO PS", __func__); 1450173273Ssam break; 1451170530Ssam default: 1452170530Ssam IEEE80211_NOTE(ic, 1453170530Ssam IEEE80211_MSG_ACTION | IEEE80211_MSG_11N, ni, 1454170530Ssam "%s: HT action %d not implemented", __func__, 1455170530Ssam ia->ia_action); 1456170530Ssam ic->ic_stats.is_rx_mgtdiscard++; 1457170530Ssam break; 1458170530Ssam } 1459170530Ssam break; 1460170530Ssam default: 1461170530Ssam IEEE80211_NOTE(ic, 1462170530Ssam IEEE80211_MSG_ACTION | IEEE80211_MSG_11N, ni, 1463170530Ssam "%s: category %d not implemented", __func__, 1464170530Ssam ia->ia_category); 1465170530Ssam ic->ic_stats.is_rx_mgtdiscard++; 1466170530Ssam break; 1467170530Ssam } 1468170530Ssam} 1469170530Ssam 1470170530Ssam/* 1471170530Ssam * Transmit processing. 1472170530Ssam */ 1473170530Ssam 1474170530Ssam/* 1475170530Ssam * Request A-MPDU tx aggregation. Setup local state and 1476170530Ssam * issue an ADDBA request. BA use will only happen after 1477170530Ssam * the other end replies with ADDBA response. 1478170530Ssam */ 1479170530Ssamint 1480170530Ssamieee80211_ampdu_request(struct ieee80211_node *ni, 1481170530Ssam struct ieee80211_tx_ampdu *tap) 1482170530Ssam{ 1483170530Ssam struct ieee80211com *ic = ni->ni_ic; 1484170530Ssam uint16_t args[4]; 1485170530Ssam int tid, dialogtoken; 1486170530Ssam static int tokens = 0; /* XXX */ 1487170530Ssam 1488170530Ssam /* XXX locking */ 1489170530Ssam if ((tap->txa_flags & IEEE80211_AGGR_SETUP) == 0) { 1490170530Ssam /* do deferred setup of state */ 1491173273Ssam IEEE80211_TAPQ_INIT(tap); 1492170530Ssam callout_init(&tap->txa_timer, CALLOUT_MPSAFE); 1493170530Ssam tap->txa_flags |= IEEE80211_AGGR_SETUP; 1494170530Ssam } 1495170530Ssam if (tap->txa_attempts >= IEEE80211_AGGR_MAXTRIES && 1496170530Ssam (ticks - tap->txa_lastrequest) < IEEE80211_AGGR_MINRETRY) { 1497170530Ssam /* 1498170530Ssam * Don't retry too often; IEEE80211_AGGR_MINRETRY 1499170530Ssam * defines the minimum interval we'll retry after 1500170530Ssam * IEEE80211_AGGR_MAXTRIES failed attempts to 1501170530Ssam * negotiate use. 1502170530Ssam */ 1503170530Ssam return 0; 1504170530Ssam } 1505173273Ssam /* XXX hack for not doing proper locking */ 1506173273Ssam tap->txa_flags &= ~IEEE80211_AGGR_NAK; 1507173273Ssam 1508170530Ssam dialogtoken = (tokens+1) % 63; /* XXX */ 1509170530Ssam 1510170530Ssam tid = WME_AC_TO_TID(tap->txa_ac); 1511170530Ssam args[0] = dialogtoken; 1512170530Ssam args[1] = IEEE80211_BAPS_POLICY_IMMEDIATE 1513170530Ssam | SM(tid, IEEE80211_BAPS_TID) 1514170530Ssam | SM(IEEE80211_AGGR_BAWMAX, IEEE80211_BAPS_BUFSIZ) 1515170530Ssam ; 1516170530Ssam args[2] = 0; /* batimeout */ 1517170530Ssam args[3] = SM(0, IEEE80211_BASEQ_START) 1518170530Ssam | SM(0, IEEE80211_BASEQ_FRAG) 1519170530Ssam ; 1520170530Ssam /* NB: do first so there's no race against reply */ 1521170530Ssam if (!ic->ic_addba_request(ni, tap, dialogtoken, args[1], args[2])) { 1522170530Ssam /* unable to setup state, don't make request */ 1523173273Ssam IEEE80211_NOTE(ni->ni_ic, IEEE80211_MSG_11N, 1524173273Ssam ni, "%s: could not setup BA stream for AC %d", 1525173273Ssam __func__, tap->txa_ac); 1526173273Ssam /* defer next try so we don't slam the driver with requests */ 1527173273Ssam tap->txa_attempts = IEEE80211_AGGR_MAXTRIES; 1528173273Ssam tap->txa_lastrequest = ticks; 1529170530Ssam return 0; 1530170530Ssam } 1531170530Ssam tokens = dialogtoken; /* allocate token */ 1532170530Ssam return ic->ic_send_action(ni, IEEE80211_ACTION_CAT_BA, 1533170530Ssam IEEE80211_ACTION_BA_ADDBA_REQUEST, args); 1534170530Ssam} 1535170530Ssam 1536170530Ssam/* 1537173273Ssam * Terminate an AMPDU tx stream. State is reclaimed 1538173273Ssam * and the peer notified with a DelBA Action frame. 1539173273Ssam */ 1540173273Ssamvoid 1541173273Ssamieee80211_ampdu_stop(struct ieee80211_node *ni, struct ieee80211_tx_ampdu *tap) 1542173273Ssam{ 1543173273Ssam struct ieee80211com *ic = ni->ni_ic; 1544173273Ssam uint16_t args[4]; 1545173273Ssam 1546173273Ssam /* XXX locking */ 1547173273Ssam if (IEEE80211_AMPDU_RUNNING(tap)) { 1548173273Ssam IEEE80211_NOTE(ic, IEEE80211_MSG_ACTION | IEEE80211_MSG_11N, 1549173273Ssam ni, "%s: stop BA stream for AC %d", __func__, tap->txa_ac); 1550173273Ssam ic->ic_stats.is_ampdu_stop++; 1551173273Ssam 1552173273Ssam ic->ic_addba_stop(ni, tap); 1553173273Ssam args[0] = WME_AC_TO_TID(tap->txa_ac); 1554173273Ssam args[1] = IEEE80211_DELBAPS_INIT; 1555173273Ssam args[2] = 1; /* XXX reason code */ 1556173273Ssam ieee80211_send_action(ni, IEEE80211_ACTION_CAT_BA, 1557173273Ssam IEEE80211_ACTION_BA_DELBA, args); 1558173273Ssam } else { 1559173273Ssam IEEE80211_NOTE(ic, IEEE80211_MSG_ACTION | IEEE80211_MSG_11N, 1560173273Ssam ni, "%s: BA stream for AC %d not running", 1561173273Ssam __func__, tap->txa_ac); 1562173273Ssam ic->ic_stats.is_ampdu_stop_failed++; 1563173273Ssam } 1564173273Ssam} 1565173273Ssam 1566173273Ssam/* 1567170530Ssam * Transmit a BAR frame to the specified node. The 1568170530Ssam * BAR contents are drawn from the supplied aggregation 1569170530Ssam * state associated with the node. 1570170530Ssam */ 1571170530Ssamint 1572170530Ssamieee80211_send_bar(struct ieee80211_node *ni, 1573170530Ssam const struct ieee80211_tx_ampdu *tap) 1574170530Ssam{ 1575170530Ssam#define senderr(_x, _v) do { ic->ic_stats._v++; ret = _x; goto bad; } while (0) 1576170530Ssam#define ADDSHORT(frm, v) do { \ 1577170530Ssam frm[0] = (v) & 0xff; \ 1578170530Ssam frm[1] = (v) >> 8; \ 1579170530Ssam frm += 2; \ 1580170530Ssam} while (0) 1581170530Ssam struct ieee80211com *ic = ni->ni_ic; 1582170530Ssam struct ifnet *ifp = ic->ic_ifp; 1583170530Ssam struct ieee80211_frame_min *wh; 1584170530Ssam struct mbuf *m; 1585170530Ssam uint8_t *frm; 1586170530Ssam uint16_t barctl, barseqctl; 1587170530Ssam int tid, ret; 1588170530Ssam 1589170530Ssam ieee80211_ref_node(ni); 1590170530Ssam 1591170530Ssam m = ieee80211_getmgtframe(&frm, 1592170530Ssam ic->ic_headroom + sizeof(struct ieee80211_frame_min), 1593170530Ssam sizeof(struct ieee80211_ba_request) 1594170530Ssam ); 1595170530Ssam if (m == NULL) 1596170530Ssam senderr(ENOMEM, is_tx_nobuf); 1597170530Ssam 1598170530Ssam wh = mtod(m, struct ieee80211_frame_min *); 1599170530Ssam wh->i_fc[0] = IEEE80211_FC0_VERSION_0 | 1600170530Ssam IEEE80211_FC0_TYPE_CTL | IEEE80211_FC0_SUBTYPE_BAR; 1601170530Ssam wh->i_fc[1] = 0; 1602170530Ssam IEEE80211_ADDR_COPY(wh->i_addr1, ni->ni_macaddr); 1603170530Ssam IEEE80211_ADDR_COPY(wh->i_addr2, ic->ic_myaddr); 1604170530Ssam 1605170530Ssam tid = WME_AC_TO_TID(tap->txa_ac); 1606170530Ssam barctl = (tap->txa_flags & IEEE80211_AGGR_IMMEDIATE ? 1607170530Ssam IEEE80211_BAPS_POLICY_IMMEDIATE : 1608170530Ssam IEEE80211_BAPS_POLICY_DELAYED) 1609170530Ssam | SM(tid, IEEE80211_BAPS_TID) 1610170530Ssam | SM(tap->txa_wnd, IEEE80211_BAPS_BUFSIZ) 1611170530Ssam ; 1612170530Ssam barseqctl = SM(tap->txa_start, IEEE80211_BASEQ_START) 1613170530Ssam | SM(0, IEEE80211_BASEQ_FRAG) 1614170530Ssam ; 1615170530Ssam ADDSHORT(frm, barctl); 1616170530Ssam ADDSHORT(frm, barseqctl); 1617170530Ssam m->m_pkthdr.len = m->m_len = frm - mtod(m, uint8_t *); 1618170530Ssam 1619170530Ssam IEEE80211_NODE_STAT(ni, tx_mgmt); /* XXX tx_ctl? */ 1620170530Ssam 1621173273Ssam IEEE80211_NOTE(ic, IEEE80211_MSG_DEBUG | IEEE80211_MSG_DUMPPKTS, 1622173273Ssam ni, "send bar frame (tid %u start %u) on channel %u", 1623173273Ssam tid, tap->txa_start, ieee80211_chan2ieee(ic, ic->ic_curchan)); 1624170530Ssam 1625170530Ssam m->m_pkthdr.rcvif = (void *)ni; 1626170530Ssam IF_ENQUEUE(&ic->ic_mgtq, m); /* cheat */ 1627173273Ssam if_start(ifp); 1628170530Ssam 1629170530Ssam return 0; 1630170530Ssambad: 1631170530Ssam ieee80211_free_node(ni); 1632170530Ssam return ret; 1633170530Ssam#undef ADDSHORT 1634170530Ssam#undef senderr 1635170530Ssam} 1636170530Ssam 1637170530Ssam/* 1638170530Ssam * Send an action management frame. The arguments are stuff 1639170530Ssam * into a frame without inspection; the caller is assumed to 1640170530Ssam * prepare them carefully (e.g. based on the aggregation state). 1641170530Ssam */ 1642170530Ssamint 1643170530Ssamieee80211_send_action(struct ieee80211_node *ni, 1644170530Ssam int category, int action, uint16_t args[4]) 1645170530Ssam{ 1646170530Ssam#define senderr(_x, _v) do { ic->ic_stats._v++; ret = _x; goto bad; } while (0) 1647170530Ssam#define ADDSHORT(frm, v) do { \ 1648170530Ssam frm[0] = (v) & 0xff; \ 1649170530Ssam frm[1] = (v) >> 8; \ 1650170530Ssam frm += 2; \ 1651170530Ssam} while (0) 1652170530Ssam struct ieee80211com *ic = ni->ni_ic; 1653170530Ssam struct mbuf *m; 1654170530Ssam uint8_t *frm; 1655170530Ssam uint16_t baparamset; 1656170530Ssam int ret; 1657170530Ssam 1658170530Ssam KASSERT(ni != NULL, ("null node")); 1659170530Ssam 1660170530Ssam /* 1661170530Ssam * Hold a reference on the node so it doesn't go away until after 1662170530Ssam * the xmit is complete all the way in the driver. On error we 1663170530Ssam * will remove our reference. 1664170530Ssam */ 1665170530Ssam IEEE80211_DPRINTF(ic, IEEE80211_MSG_NODE, 1666170530Ssam "ieee80211_ref_node (%s:%u) %p<%s> refcnt %d\n", 1667170530Ssam __func__, __LINE__, 1668170530Ssam ni, ether_sprintf(ni->ni_macaddr), 1669170530Ssam ieee80211_node_refcnt(ni)+1); 1670170530Ssam ieee80211_ref_node(ni); 1671170530Ssam 1672170530Ssam m = ieee80211_getmgtframe(&frm, 1673170530Ssam ic->ic_headroom + sizeof(struct ieee80211_frame), 1674170530Ssam sizeof(uint16_t) /* action+category */ 1675170530Ssam /* XXX may action payload */ 1676170530Ssam + sizeof(struct ieee80211_action_ba_addbaresponse) 1677170530Ssam ); 1678170530Ssam if (m == NULL) 1679170530Ssam senderr(ENOMEM, is_tx_nobuf); 1680170530Ssam 1681170530Ssam *frm++ = category; 1682170530Ssam *frm++ = action; 1683170530Ssam switch (category) { 1684170530Ssam case IEEE80211_ACTION_CAT_BA: 1685170530Ssam switch (action) { 1686170530Ssam case IEEE80211_ACTION_BA_ADDBA_REQUEST: 1687170530Ssam IEEE80211_NOTE(ic, 1688170530Ssam IEEE80211_MSG_ACTION | IEEE80211_MSG_11N, ni, 1689173273Ssam "send ADDBA request: dialogtoken %d " 1690173273Ssam "baparamset 0x%x (tid %d) batimeout 0x%x baseqctl 0x%x", 1691173273Ssam args[0], args[1], MS(args[1], IEEE80211_BAPS_TID), 1692173273Ssam args[2], args[3]); 1693170530Ssam 1694170530Ssam *frm++ = args[0]; /* dialog token */ 1695170530Ssam ADDSHORT(frm, args[1]); /* baparamset */ 1696170530Ssam ADDSHORT(frm, args[2]); /* batimeout */ 1697170530Ssam ADDSHORT(frm, args[3]); /* baseqctl */ 1698170530Ssam break; 1699170530Ssam case IEEE80211_ACTION_BA_ADDBA_RESPONSE: 1700170530Ssam IEEE80211_NOTE(ic, 1701170530Ssam IEEE80211_MSG_ACTION | IEEE80211_MSG_11N, ni, 1702170530Ssam "send ADDBA response: dialogtoken %d status %d " 1703170530Ssam "baparamset 0x%x (tid %d) batimeout %d", 1704170530Ssam args[0], args[1], args[2], 1705170530Ssam MS(args[2], IEEE80211_BAPS_TID), args[3]); 1706170530Ssam 1707170530Ssam *frm++ = args[0]; /* dialog token */ 1708170530Ssam ADDSHORT(frm, args[1]); /* statuscode */ 1709170530Ssam ADDSHORT(frm, args[2]); /* baparamset */ 1710170530Ssam ADDSHORT(frm, args[3]); /* batimeout */ 1711170530Ssam break; 1712170530Ssam case IEEE80211_ACTION_BA_DELBA: 1713170530Ssam /* XXX */ 1714170530Ssam baparamset = SM(args[0], IEEE80211_DELBAPS_TID) 1715170530Ssam | SM(args[1], IEEE80211_DELBAPS_INIT) 1716170530Ssam ; 1717170530Ssam ADDSHORT(frm, baparamset); 1718170530Ssam ADDSHORT(frm, args[2]); /* reason code */ 1719170530Ssam 1720170530Ssam IEEE80211_NOTE(ic, 1721170530Ssam IEEE80211_MSG_ACTION | IEEE80211_MSG_11N, ni, 1722170530Ssam "send DELBA action: tid %d, initiator %d reason %d", 1723170530Ssam args[0], args[1], args[2]); 1724170530Ssam break; 1725170530Ssam default: 1726170530Ssam goto badaction; 1727170530Ssam } 1728170530Ssam break; 1729170530Ssam case IEEE80211_ACTION_CAT_HT: 1730170530Ssam switch (action) { 1731170530Ssam case IEEE80211_ACTION_HT_TXCHWIDTH: 1732170530Ssam IEEE80211_NOTE(ic, 1733170530Ssam IEEE80211_MSG_ACTION | IEEE80211_MSG_11N, 1734170530Ssam ni, "send HT txchwidth: width %d", 1735173273Ssam IEEE80211_IS_CHAN_HT40(ic->ic_bsschan) ? 40 : 20 1736170530Ssam ); 1737170530Ssam *frm++ = IEEE80211_IS_CHAN_HT40(ic->ic_bsschan) ? 1738170530Ssam IEEE80211_A_HT_TXCHWIDTH_2040 : 1739170530Ssam IEEE80211_A_HT_TXCHWIDTH_20; 1740170530Ssam break; 1741170530Ssam default: 1742170530Ssam goto badaction; 1743170530Ssam } 1744170530Ssam break; 1745170530Ssam default: 1746170530Ssam badaction: 1747170530Ssam IEEE80211_NOTE(ic, 1748170530Ssam IEEE80211_MSG_ACTION | IEEE80211_MSG_11N, ni, 1749170530Ssam "%s: unsupported category %d action %d", __func__, 1750170530Ssam category, action); 1751170530Ssam senderr(EINVAL, is_tx_unknownmgt); 1752170530Ssam /* NOTREACHED */ 1753170530Ssam } 1754170530Ssam m->m_pkthdr.len = m->m_len = frm - mtod(m, uint8_t *); 1755170530Ssam 1756170530Ssam ret = ieee80211_mgmt_output(ic, ni, m, IEEE80211_FC0_SUBTYPE_ACTION); 1757170530Ssam if (ret != 0) 1758170530Ssam goto bad; 1759170530Ssam return 0; 1760170530Ssambad: 1761170530Ssam ieee80211_free_node(ni); 1762170530Ssam return ret; 1763170530Ssam#undef ADDSHORT 1764170530Ssam#undef senderr 1765170530Ssam} 1766170530Ssam 1767170530Ssam/* 1768170530Ssam * Construct the MCS bit mask for inclusion 1769170530Ssam * in an HT information element. 1770170530Ssam */ 1771170530Ssamstatic void 1772170530Ssamieee80211_set_htrates(uint8_t *frm, const struct ieee80211_htrateset *rs) 1773170530Ssam{ 1774170530Ssam int i; 1775170530Ssam 1776170530Ssam for (i = 0; i < rs->rs_nrates; i++) { 1777170530Ssam int r = rs->rs_rates[i] & IEEE80211_RATE_VAL; 1778170530Ssam if (r < IEEE80211_HTRATE_MAXSIZE) { /* XXX? */ 1779170530Ssam /* NB: this assumes a particular implementation */ 1780170530Ssam setbit(frm, r); 1781170530Ssam } 1782170530Ssam } 1783170530Ssam} 1784170530Ssam 1785170530Ssam/* 1786170530Ssam * Add body of an HTCAP information element. 1787170530Ssam */ 1788170530Ssamstatic uint8_t * 1789170530Ssamieee80211_add_htcap_body(uint8_t *frm, struct ieee80211_node *ni) 1790170530Ssam{ 1791170530Ssam#define ADDSHORT(frm, v) do { \ 1792170530Ssam frm[0] = (v) & 0xff; \ 1793170530Ssam frm[1] = (v) >> 8; \ 1794170530Ssam frm += 2; \ 1795170530Ssam} while (0) 1796170530Ssam struct ieee80211com *ic = ni->ni_ic; 1797170530Ssam uint16_t caps; 1798173865Ssam int rxmax, density; 1799170530Ssam 1800170530Ssam /* HT capabilities */ 1801170530Ssam caps = ic->ic_htcaps & 0xffff; 1802173273Ssam /* 1803173273Ssam * Note channel width depends on whether we are operating as 1804173273Ssam * a sta or not. When operating as a sta we are generating 1805173273Ssam * a request based on our desired configuration. Otherwise 1806173273Ssam * we are operational and the channel attributes identify 1807173273Ssam * how we've been setup (which might be different if a fixed 1808173273Ssam * channel is specified). 1809173273Ssam */ 1810173273Ssam if (ic->ic_opmode == IEEE80211_M_STA) { 1811173273Ssam /* override 20/40 use based on config */ 1812173273Ssam if (ic->ic_flags_ext & IEEE80211_FEXT_USEHT40) 1813173273Ssam caps |= IEEE80211_HTCAP_CHWIDTH40; 1814173273Ssam else 1815173273Ssam caps &= ~IEEE80211_HTCAP_CHWIDTH40; 1816173865Ssam /* use advertised setting (XXX locally constraint) */ 1817173865Ssam rxmax = MS(ni->ni_htparam, IEEE80211_HTCAP_MAXRXAMPDU); 1818173865Ssam density = MS(ni->ni_htparam, IEEE80211_HTCAP_MPDUDENSITY); 1819173273Ssam } else { 1820173273Ssam /* override 20/40 use based on current channel */ 1821173273Ssam if (IEEE80211_IS_CHAN_HT40(ic->ic_bsschan)) 1822173273Ssam caps |= IEEE80211_HTCAP_CHWIDTH40; 1823173273Ssam else 1824173273Ssam caps &= ~IEEE80211_HTCAP_CHWIDTH40; 1825173865Ssam rxmax = ic->ic_ampdu_rxmax; 1826173865Ssam density = ic->ic_ampdu_density; 1827173273Ssam } 1828170530Ssam /* adjust short GI based on channel and config */ 1829170530Ssam if ((ic->ic_flags_ext & IEEE80211_FEXT_SHORTGI20) == 0) 1830170530Ssam caps &= ~IEEE80211_HTCAP_SHORTGI20; 1831170530Ssam if ((ic->ic_flags_ext & IEEE80211_FEXT_SHORTGI40) == 0 || 1832170530Ssam (caps & IEEE80211_HTCAP_CHWIDTH40) == 0) 1833170530Ssam caps &= ~IEEE80211_HTCAP_SHORTGI40; 1834170530Ssam ADDSHORT(frm, caps); 1835170530Ssam 1836170530Ssam /* HT parameters */ 1837173865Ssam *frm = SM(rxmax, IEEE80211_HTCAP_MAXRXAMPDU) 1838173865Ssam | SM(density, IEEE80211_HTCAP_MPDUDENSITY) 1839173273Ssam ; 1840170530Ssam frm++; 1841170530Ssam 1842170530Ssam /* pre-zero remainder of ie */ 1843170530Ssam memset(frm, 0, sizeof(struct ieee80211_ie_htcap) - 1844170530Ssam __offsetof(struct ieee80211_ie_htcap, hc_mcsset)); 1845170530Ssam 1846170530Ssam /* supported MCS set */ 1847173273Ssam /* 1848173273Ssam * XXX it would better to get the rate set from ni_htrates 1849173273Ssam * so we can restrict it but for sta mode ni_htrates isn't 1850173273Ssam * setup when we're called to form an AssocReq frame so for 1851173273Ssam * now we're restricted to the default HT rate set. 1852173273Ssam */ 1853173273Ssam ieee80211_set_htrates(frm, &ieee80211_rateset_11n); 1854170530Ssam 1855170530Ssam frm += sizeof(struct ieee80211_ie_htcap) - 1856170530Ssam __offsetof(struct ieee80211_ie_htcap, hc_mcsset); 1857170530Ssam return frm; 1858170530Ssam#undef ADDSHORT 1859170530Ssam} 1860170530Ssam 1861170530Ssam/* 1862170530Ssam * Add 802.11n HT capabilities information element 1863170530Ssam */ 1864170530Ssamuint8_t * 1865170530Ssamieee80211_add_htcap(uint8_t *frm, struct ieee80211_node *ni) 1866170530Ssam{ 1867170530Ssam frm[0] = IEEE80211_ELEMID_HTCAP; 1868170530Ssam frm[1] = sizeof(struct ieee80211_ie_htcap) - 2; 1869170530Ssam return ieee80211_add_htcap_body(frm + 2, ni); 1870170530Ssam} 1871170530Ssam 1872170530Ssam/* 1873170530Ssam * Add Broadcom OUI wrapped standard HTCAP ie; this is 1874170530Ssam * used for compatibility w/ pre-draft implementations. 1875170530Ssam */ 1876170530Ssamuint8_t * 1877170530Ssamieee80211_add_htcap_vendor(uint8_t *frm, struct ieee80211_node *ni) 1878170530Ssam{ 1879170530Ssam frm[0] = IEEE80211_ELEMID_VENDOR; 1880170530Ssam frm[1] = 4 + sizeof(struct ieee80211_ie_htcap) - 2; 1881170530Ssam frm[2] = (BCM_OUI >> 0) & 0xff; 1882170530Ssam frm[3] = (BCM_OUI >> 8) & 0xff; 1883170530Ssam frm[4] = (BCM_OUI >> 16) & 0xff; 1884170530Ssam frm[5] = BCM_OUI_HTCAP; 1885170530Ssam return ieee80211_add_htcap_body(frm + 6, ni); 1886170530Ssam} 1887170530Ssam 1888170530Ssam/* 1889170530Ssam * Construct the MCS bit mask of basic rates 1890170530Ssam * for inclusion in an HT information element. 1891170530Ssam */ 1892170530Ssamstatic void 1893170530Ssamieee80211_set_basic_htrates(uint8_t *frm, const struct ieee80211_htrateset *rs) 1894170530Ssam{ 1895170530Ssam int i; 1896170530Ssam 1897170530Ssam for (i = 0; i < rs->rs_nrates; i++) { 1898170530Ssam int r = rs->rs_rates[i] & IEEE80211_RATE_VAL; 1899170530Ssam if ((rs->rs_rates[i] & IEEE80211_RATE_BASIC) && 1900170530Ssam r < IEEE80211_HTRATE_MAXSIZE) { 1901170530Ssam /* NB: this assumes a particular implementation */ 1902170530Ssam setbit(frm, r); 1903170530Ssam } 1904170530Ssam } 1905170530Ssam} 1906170530Ssam 1907170530Ssam/* 1908172211Ssam * Update the HTINFO ie for a beacon frame. 1909172211Ssam */ 1910172211Ssamvoid 1911172211Ssamieee80211_ht_update_beacon(struct ieee80211com *ic, 1912172211Ssam struct ieee80211_beacon_offsets *bo) 1913172211Ssam{ 1914172211Ssam#define PROTMODE (IEEE80211_HTINFO_OPMODE|IEEE80211_HTINFO_NONHT_PRESENT) 1915172211Ssam struct ieee80211_ie_htinfo *ht = 1916172211Ssam (struct ieee80211_ie_htinfo *) bo->bo_htinfo; 1917172211Ssam 1918172211Ssam /* XXX only update on channel change */ 1919172211Ssam ht->hi_ctrlchannel = ieee80211_chan2ieee(ic, ic->ic_bsschan); 1920172211Ssam ht->hi_byte1 = IEEE80211_HTINFO_RIFSMODE_PROH; 1921172211Ssam if (IEEE80211_IS_CHAN_HT40U(ic->ic_bsschan)) 1922172211Ssam ht->hi_byte1 |= IEEE80211_HTINFO_2NDCHAN_ABOVE; 1923172211Ssam else if (IEEE80211_IS_CHAN_HT40D(ic->ic_bsschan)) 1924172211Ssam ht->hi_byte1 |= IEEE80211_HTINFO_2NDCHAN_BELOW; 1925172211Ssam else 1926172211Ssam ht->hi_byte1 |= IEEE80211_HTINFO_2NDCHAN_NONE; 1927172211Ssam if (IEEE80211_IS_CHAN_HT40(ic->ic_bsschan)) 1928172211Ssam ht->hi_byte1 |= IEEE80211_HTINFO_TXWIDTH_2040; 1929172211Ssam 1930172211Ssam /* protection mode */ 1931172211Ssam ht->hi_byte2 = (ht->hi_byte2 &~ PROTMODE) | ic->ic_curhtprotmode; 1932172211Ssam 1933172211Ssam /* XXX propagate to vendor ie's */ 1934172211Ssam#undef PROTMODE 1935172211Ssam} 1936172211Ssam 1937172211Ssam/* 1938170530Ssam * Add body of an HTINFO information element. 1939173273Ssam * 1940173273Ssam * NB: We don't use struct ieee80211_ie_htinfo because we can 1941173273Ssam * be called to fillin both a standard ie and a compat ie that 1942173273Ssam * has a vendor OUI at the front. 1943170530Ssam */ 1944170530Ssamstatic uint8_t * 1945170530Ssamieee80211_add_htinfo_body(uint8_t *frm, struct ieee80211_node *ni) 1946170530Ssam{ 1947170530Ssam struct ieee80211com *ic = ni->ni_ic; 1948170530Ssam 1949170530Ssam /* pre-zero remainder of ie */ 1950170530Ssam memset(frm, 0, sizeof(struct ieee80211_ie_htinfo) - 2); 1951170530Ssam 1952170530Ssam /* primary/control channel center */ 1953170530Ssam *frm++ = ieee80211_chan2ieee(ic, ic->ic_bsschan); 1954170530Ssam 1955170530Ssam frm[0] = IEEE80211_HTINFO_RIFSMODE_PROH; 1956170530Ssam if (IEEE80211_IS_CHAN_HT40U(ic->ic_bsschan)) 1957170530Ssam frm[0] |= IEEE80211_HTINFO_2NDCHAN_ABOVE; 1958170530Ssam else if (IEEE80211_IS_CHAN_HT40D(ic->ic_bsschan)) 1959170530Ssam frm[0] |= IEEE80211_HTINFO_2NDCHAN_BELOW; 1960170530Ssam else 1961170530Ssam frm[0] |= IEEE80211_HTINFO_2NDCHAN_NONE; 1962170530Ssam if (IEEE80211_IS_CHAN_HT40(ic->ic_bsschan)) 1963170530Ssam frm[0] |= IEEE80211_HTINFO_TXWIDTH_2040; 1964170530Ssam 1965172211Ssam frm[1] = ic->ic_curhtprotmode; 1966170530Ssam 1967170530Ssam frm += 5; 1968170530Ssam 1969170530Ssam /* basic MCS set */ 1970170530Ssam ieee80211_set_basic_htrates(frm, &ni->ni_htrates); 1971170530Ssam frm += sizeof(struct ieee80211_ie_htinfo) - 1972170530Ssam __offsetof(struct ieee80211_ie_htinfo, hi_basicmcsset); 1973170530Ssam return frm; 1974170530Ssam} 1975170530Ssam 1976170530Ssam/* 1977170530Ssam * Add 802.11n HT information information element. 1978170530Ssam */ 1979170530Ssamuint8_t * 1980170530Ssamieee80211_add_htinfo(uint8_t *frm, struct ieee80211_node *ni) 1981170530Ssam{ 1982170530Ssam frm[0] = IEEE80211_ELEMID_HTINFO; 1983170530Ssam frm[1] = sizeof(struct ieee80211_ie_htinfo) - 2; 1984170530Ssam return ieee80211_add_htinfo_body(frm + 2, ni); 1985170530Ssam} 1986170530Ssam 1987170530Ssam/* 1988170530Ssam * Add Broadcom OUI wrapped standard HTINFO ie; this is 1989170530Ssam * used for compatibility w/ pre-draft implementations. 1990170530Ssam */ 1991170530Ssamuint8_t * 1992170530Ssamieee80211_add_htinfo_vendor(uint8_t *frm, struct ieee80211_node *ni) 1993170530Ssam{ 1994170530Ssam frm[0] = IEEE80211_ELEMID_VENDOR; 1995170530Ssam frm[1] = 4 + sizeof(struct ieee80211_ie_htinfo) - 2; 1996170530Ssam frm[2] = (BCM_OUI >> 0) & 0xff; 1997170530Ssam frm[3] = (BCM_OUI >> 8) & 0xff; 1998170530Ssam frm[4] = (BCM_OUI >> 16) & 0xff; 1999170530Ssam frm[5] = BCM_OUI_HTINFO; 2000170530Ssam return ieee80211_add_htinfo_body(frm + 6, ni); 2001170530Ssam} 2002