On Thu, Nov 12, 2015 at 08:22:07PM +0100, Stefan Sperling wrote:
> This diff adds an initial implementation of 802.11n.
Some of this has now been committed.
The diff below shows the outstanding parts relative to -current.
- receiving A-MPDU and A-MSDU frames
- 11n support for the iwm(4) driver (this won't go in before
the above part is proven stable)
The show stopper is crashes observed with APs that send large
A-MSDUs (perhaps a use after free). I could not reproduce
the issue myself so far due to lack of APs that send such frames.
If anyone else can reproduce such crashes, please talk to me.
Index: dev/pci/if_iwm.c
===================================================================
RCS file: /cvs/src/sys/dev/pci/if_iwm.c,v
retrieving revision 1.63
diff -u -p -r1.63 if_iwm.c
--- dev/pci/if_iwm.c 4 Nov 2015 12:11:59 -0000 1.63
+++ dev/pci/if_iwm.c 15 Nov 2015 13:06:25 -0000
@@ -175,19 +175,22 @@ const uint8_t iwm_nvm_channels[] = {
const struct iwm_rate {
uint8_t rate;
uint8_t plcp;
+ uint8_t ht_plcp;
} iwm_rates[] = {
- { 2, IWM_RATE_1M_PLCP },
- { 4, IWM_RATE_2M_PLCP },
- { 11, IWM_RATE_5M_PLCP },
- { 22, IWM_RATE_11M_PLCP },
- { 12, IWM_RATE_6M_PLCP },
- { 18, IWM_RATE_9M_PLCP },
- { 24, IWM_RATE_12M_PLCP },
- { 36, IWM_RATE_18M_PLCP },
- { 48, IWM_RATE_24M_PLCP },
- { 72, IWM_RATE_36M_PLCP },
- { 96, IWM_RATE_48M_PLCP },
- { 108, IWM_RATE_54M_PLCP },
+ /* Legacy */ /* HT */
+ { 2, IWM_RATE_1M_PLCP, IWM_RATE_HT_SISO_MCS_INV_PLCP },
+ { 4, IWM_RATE_2M_PLCP, IWM_RATE_HT_SISO_MCS_INV_PLCP },
+ { 11, IWM_RATE_5M_PLCP, IWM_RATE_HT_SISO_MCS_INV_PLCP },
+ { 22, IWM_RATE_11M_PLCP, IWM_RATE_HT_SISO_MCS_INV_PLCP },
+ { 12, IWM_RATE_6M_PLCP, IWM_RATE_HT_SISO_MCS_0_PLCP },
+ { 18, IWM_RATE_9M_PLCP, IWM_RATE_HT_SISO_MCS_INV_PLCP },
+ { 24, IWM_RATE_12M_PLCP, IWM_RATE_HT_SISO_MCS_1_PLCP },
+ { 36, IWM_RATE_18M_PLCP, IWM_RATE_HT_SISO_MCS_2_PLCP },
+ { 48, IWM_RATE_24M_PLCP, IWM_RATE_HT_SISO_MCS_3_PLCP },
+ { 72, IWM_RATE_36M_PLCP, IWM_RATE_HT_SISO_MCS_4_PLCP },
+ { 96, IWM_RATE_48M_PLCP, IWM_RATE_HT_SISO_MCS_5_PLCP },
+ { 108, IWM_RATE_54M_PLCP, IWM_RATE_HT_SISO_MCS_6_PLCP },
+ { 128, IWM_RATE_INVM_PLCP, IWM_RATE_HT_SISO_MCS_7_PLCP },
};
#define IWM_RIDX_CCK 0
#define IWM_RIDX_OFDM 4
@@ -195,6 +198,18 @@ const struct iwm_rate {
#define IWM_RIDX_IS_CCK(_i_) ((_i_) < IWM_RIDX_OFDM)
#define IWM_RIDX_IS_OFDM(_i_) ((_i_) >= IWM_RIDX_OFDM)
+/* Convert an MCS index into an iwm_rates[] index. */
+const int iwm_mcs2ridx[] = {
+ IWM_RATE_MCS_0_INDEX,
+ IWM_RATE_MCS_1_INDEX,
+ IWM_RATE_MCS_2_INDEX,
+ IWM_RATE_MCS_3_INDEX,
+ IWM_RATE_MCS_4_INDEX,
+ IWM_RATE_MCS_5_INDEX,
+ IWM_RATE_MCS_6_INDEX,
+ IWM_RATE_MCS_7_INDEX,
+};
+
int iwm_store_cscheme(struct iwm_softc *, uint8_t *, size_t);
int iwm_firmware_store_section(struct iwm_softc *, enum iwm_ucode_type,
uint8_t *, size_t);
@@ -283,6 +298,24 @@ int iwm_nvm_read_chunk(struct iwm_softc
int iwm_nvm_read_section(struct iwm_softc *, uint16_t, uint8_t *,
uint16_t *);
void iwm_init_channel_map(struct iwm_softc *, const uint16_t * const);
+#ifndef IEEE80211_NO_HT
+void iwm_setup_ht_rates(struct iwm_softc *);
+int iwm_ampdu_rx_start(struct ieee80211com *,
+ struct ieee80211_node *, uint8_t);
+void iwm_ampdu_rx_stop(struct ieee80211com *,
+ struct ieee80211_node *, uint8_t);
+void iwm_mvm_sta_rx_agg(struct iwm_softc *, struct ieee80211_node *,
+ uint8_t, uint16_t, int);
+#ifdef notyet
+int iwm_ampdu_tx_start(struct ieee80211com *,
+ struct ieee80211_node *, uint8_t);
+void iwm_ampdu_tx_stop(struct ieee80211com *,
+ struct ieee80211_node *, uint8_t);
+#endif
+void iwm_ba_task(void *);
+
+#endif /* IEEE80211_NO_HT */
+
int iwm_parse_nvm_data(struct iwm_softc *, const uint16_t *,
const uint16_t *, const uint16_t *, uint8_t,
uint8_t);
@@ -2375,6 +2408,13 @@ iwm_nvm_read_section(struct iwm_softc *s
* BEGIN IWM_NVM_PARSE
*/
+#define IWM_FW_VALID_TX_ANT(sc) \
+ ((sc->sc_fw_phy_config & IWM_FW_PHY_CFG_TX_CHAIN) \
+ >> IWM_FW_PHY_CFG_TX_CHAIN_POS)
+#define IWM_FW_VALID_RX_ANT(sc) \
+ ((sc->sc_fw_phy_config & IWM_FW_PHY_CFG_RX_CHAIN) \
+ >> IWM_FW_PHY_CFG_RX_CHAIN_POS)
+
/* NVM offsets (in words) definitions */
enum wkp_nvm_offsets {
/* NVM HW-Section offset (in words) definitions */
@@ -2399,6 +2439,7 @@ enum nvm_sku_bits {
IWM_NVM_SKU_CAP_BAND_52GHZ = (1 << 1),
IWM_NVM_SKU_CAP_11N_ENABLE = (1 << 2),
IWM_NVM_SKU_CAP_11AC_ENABLE = (1 << 3),
+ IWM_NVM_SKU_CAP_MIMO_DISABLE = (1 << 5),
};
/* radio config bits (actual values from NVM definition) */
@@ -2482,9 +2523,146 @@ iwm_init_channel_map(struct iwm_softc *s
if (!(ch_flags & IWM_NVM_CHANNEL_ACTIVE))
channel->ic_flags |= IEEE80211_CHAN_PASSIVE;
+
+#ifndef IEEE80211_NO_HT
+ if (data->sku_cap_11n_enable)
+ channel->ic_flags |= IEEE80211_CHAN_HT;
+#endif
+ }
+}
+
+#ifndef IEEE80211_NO_HT
+void
+iwm_setup_ht_rates(struct iwm_softc *sc)
+{
+ struct ieee80211com *ic = &sc->sc_ic;
+
+ /* TX is supported with the same MCS as RX. */
+ ic->ic_tx_mcs_set = IEEE80211_TX_MCS_SET_DEFINED;
+
+ ic->ic_sup_mcs[0] = 0xff; /* MCS 0-7 */
+
+#ifdef notyet
+ if (sc->sc_nvm.sku_cap_mimo_disable)
+ return;
+
+ if (IWM_FW_VALID_RX_ANT(sc) > 1)
+ ic->ic_sup_mcs[1] = 0xff; /* MCS 7-15 */
+ if (IWM_FW_VALID_RX_ANT(sc) > 2)
+ ic->ic_sup_mcs[2] = 0xff; /* MCS 16-23 */
+#endif
+}
+
+#define IWM_MAX_RX_BA_SESSIONS 16
+
+void
+iwm_mvm_sta_rx_agg(struct iwm_softc *sc, struct ieee80211_node *ni,
+ uint8_t tid, uint16_t ssn, int start)
+{
+ struct iwm_mvm_add_sta_cmd_v6 cmd;
+ struct iwm_node *in = (void *)ni;
+ int ret, s;
+ uint32_t status;
+
+ if (start && sc->sc_rx_ba_sessions >= IWM_MAX_RX_BA_SESSIONS)
+ return;
+
+ memset(&cmd, 0, sizeof(cmd));
+
+ cmd.sta_id = IWM_STATION_ID;
+ cmd.mac_id_n_color
+ = htole32(IWM_FW_CMD_ID_AND_COLOR(in->in_id, in->in_color));
+ cmd.add_modify = IWM_STA_MODE_MODIFY;
+
+ if (start) {
+ cmd.add_immediate_ba_tid = (uint8_t)tid;
+ cmd.add_immediate_ba_ssn = ssn;
+ } else {
+ cmd.remove_immediate_ba_tid = (uint8_t)tid;
+ }
+ cmd.modify_mask = start ? IWM_STA_MODIFY_ADD_BA_TID :
+ IWM_STA_MODIFY_REMOVE_BA_TID;
+
+ status = IWM_ADD_STA_SUCCESS;
+ ret = iwm_mvm_send_add_sta_cmd_status(sc, &cmd, &status);
+ if (ret)
+ return;
+
+ switch (status) {
+ case IWM_ADD_STA_SUCCESS:
+ DPRINTF(("RX BA Session %sed in fw\n",
+ start ? "start" : "stopp"));
+ s = splnet();
+ if (start)
+ sc->sc_rx_ba_sessions++;
+ else if (sc->sc_rx_ba_sessions > 0)
+ /* check that restart flow didn't zero counter */
+ sc->sc_rx_ba_sessions--;
+ splx(s);
+ break;
+ case IWM_ADD_STA_IMMEDIATE_BA_FAILURE:
+ ret = EIO;
+ DPRINTF(("RX BA Session refused by fw\n"));
+ break;
+ default:
+ ret = EIO;
+ DPRINTF(("RX BA Session failed %sing, status 0x%x\n",
+ start ? "start" : "stopp", status));
+ break;
}
}
+void
+iwm_ba_task(void *arg)
+{
+ struct iwm_softc *sc = arg;
+ struct ieee80211com *ic = &sc->sc_ic;
+ struct ieee80211_node *ni = ic->ic_bss;
+
+ if (sc->ba_start)
+ iwm_mvm_sta_rx_agg(sc, ni, sc->ba_tid, sc->ba_ssn, 1);
+ else
+ iwm_mvm_sta_rx_agg(sc, ni, sc->ba_tid, 0, 0);
+}
+
+/*
+ * This function is called by upper layer when an ADDBA request is received
+ * from another STA and before the ADDBA response is sent.
+ */
+int
+iwm_ampdu_rx_start(struct ieee80211com *ic, struct ieee80211_node *ni,
+ uint8_t tid)
+{
+ struct ieee80211_rx_ba *ba = &ni->ni_rx_ba[tid];
+ struct iwm_softc *sc = IC2IFP(ic)->if_softc;
+
+ if (sc->sc_rx_ba_sessions >= IWM_MAX_RX_BA_SESSIONS)
+ return ENOSPC;
+
+ sc->ba_start = 1;
+ sc->ba_tid = tid;
+ sc->ba_ssn = htole16(ba->ba_winstart);
+ task_add(systq, &sc->ba_task);
+
+ return 0; /* XXX firmware may still fail to add BA agreement... */
+}
+
+/*
+ * This function is called by upper layer on teardown of an HT-immediate
+ * Block Ack agreement (eg. uppon receipt of a DELBA frame).
+ */
+void
+iwm_ampdu_rx_stop(struct ieee80211com *ic, struct ieee80211_node *ni,
+ uint8_t tid)
+{
+ struct iwm_softc *sc = IC2IFP(ic)->if_softc;
+
+ sc->ba_start = 0;
+ sc->ba_tid = tid;
+ task_add(systq, &sc->ba_task);
+}
+#endif /* IEEE80211_NO_HT */
+
int
iwm_parse_nvm_data(struct iwm_softc *sc,
const uint16_t *nvm_hw, const uint16_t *nvm_sw,
@@ -2507,7 +2685,13 @@ iwm_parse_nvm_data(struct iwm_softc *sc,
sku = le16_to_cpup(nvm_sw + IWM_SKU);
data->sku_cap_band_24GHz_enable = sku & IWM_NVM_SKU_CAP_BAND_24GHZ;
data->sku_cap_band_52GHz_enable = sku & IWM_NVM_SKU_CAP_BAND_52GHZ;
+#ifndef IEEE80211_NO_HT
+ data->sku_cap_11n_enable = sku & IWM_NVM_SKU_CAP_11N_ENABLE;
+ data->sku_cap_mimo_disable = sku & IWM_NVM_SKU_CAP_MIMO_DISABLE;
+#else
data->sku_cap_11n_enable = 0;
+ data->sku_cap_mimo_disable = 1;
+#endif
if (!data->valid_tx_ant || !data->valid_rx_ant) {
DPRINTF(("%s: invalid antennas (0x%x, 0x%x)\n",
@@ -2549,13 +2733,6 @@ struct iwm_nvm_section {
const uint8_t *data;
};
-#define IWM_FW_VALID_TX_ANT(sc) \
- ((sc->sc_fw_phy_config & IWM_FW_PHY_CFG_TX_CHAIN) \
- >> IWM_FW_PHY_CFG_TX_CHAIN_POS)
-#define IWM_FW_VALID_RX_ANT(sc) \
- ((sc->sc_fw_phy_config & IWM_FW_PHY_CFG_RX_CHAIN) \
- >> IWM_FW_PHY_CFG_RX_CHAIN_POS)
-
int
iwm_parse_nvm_sections(struct iwm_softc *sc, struct iwm_nvm_section *sections)
{
@@ -3080,23 +3257,35 @@ iwm_mvm_rx_rx_mpdu(struct iwm_softc *sc,
tap->wr_dbm_antsignal = (int8_t)rssi;
tap->wr_dbm_antnoise = (int8_t)sc->sc_noise;
tap->wr_tsft = phy_info->system_timestamp;
- switch (phy_info->rate) {
- /* CCK rates. */
- case 10: tap->wr_rate = 2; break;
- case 20: tap->wr_rate = 4; break;
- case 55: tap->wr_rate = 11; break;
- case 110: tap->wr_rate = 22; break;
- /* OFDM rates. */
- case 0xd: tap->wr_rate = 12; break;
- case 0xf: tap->wr_rate = 18; break;
- case 0x5: tap->wr_rate = 24; break;
- case 0x7: tap->wr_rate = 36; break;
- case 0x9: tap->wr_rate = 48; break;
- case 0xb: tap->wr_rate = 72; break;
- case 0x1: tap->wr_rate = 96; break;
- case 0x3: tap->wr_rate = 108; break;
- /* Unknown rate: should not happen. */
- default: tap->wr_rate = 0;
+ if (phy_info->phy_flags &
+ htole16(IWM_RX_RES_PHY_FLAGS_OFDM_HT)) {
+#ifdef notyet
+ uint8_t mcs = (phy_info->rate_n_flags &
+ htole32(IWM_RATE_HT_MCS_RATE_CODE_MSK));
+#endif
+ /* XXX need a way to pass current MCS in 11n mode */
+ tap->wr_rate = 0;
+ } else {
+ uint8_t rate = (phy_info->rate_n_flags &
+ htole32(IWM_RATE_LEGACY_RATE_MSK));
+ switch (rate) {
+ /* CCK rates. */
+ case 10: tap->wr_rate = 2; break;
+ case 20: tap->wr_rate = 4; break;
+ case 55: tap->wr_rate = 11; break;
+ case 110: tap->wr_rate = 22; break;
+ /* OFDM rates. */
+ case 0xd: tap->wr_rate = 12; break;
+ case 0xf: tap->wr_rate = 18; break;
+ case 0x5: tap->wr_rate = 24; break;
+ case 0x7: tap->wr_rate = 36; break;
+ case 0x9: tap->wr_rate = 48; break;
+ case 0xb: tap->wr_rate = 72; break;
+ case 0x1: tap->wr_rate = 96; break;
+ case 0x3: tap->wr_rate = 108; break;
+ /* Unknown rate: should not happen. */
+ default: tap->wr_rate = 0;
+ }
}
mb.m_data = (caddr_t)tap;
@@ -3665,7 +3854,7 @@ iwm_tx_fill_cmd(struct iwm_softc *sc, st
struct ieee80211_node *ni = &in->in_ni;
const struct iwm_rate *rinfo;
int type = wh->i_fc[0] & IEEE80211_FC0_TYPE_MASK;
- int ridx, rate_flags;
+ int ridx, rate_flags, i;
int nrates = ni->ni_rates.rs_nrates;
tx->rts_retry_limit = IWM_RTS_DFAULT_RETRY_LIMIT;
@@ -3673,16 +3862,40 @@ iwm_tx_fill_cmd(struct iwm_softc *sc, st
if (type != IEEE80211_FC0_TYPE_DATA) {
/* for non-data, use the lowest supported rate */
- ridx = (ic->ic_curmode == IEEE80211_MODE_11A) ?
+ ridx = (ic->ic_curmode == IEEE80211_MODE_11A ||
+ ic->ic_curmode == IEEE80211_MODE_11N) ?
IWM_RIDX_OFDM : IWM_RIDX_CCK;
+#ifndef IEEE80211_NO_HT
+ } else if (ic->ic_fixed_mcs != -1) {
+ ridx = sc->sc_fixed_ridx;
+#endif
} else if (ic->ic_fixed_rate != -1) {
ridx = sc->sc_fixed_ridx;
} else {
/* for data frames, use RS table */
- tx->initial_rate_index = (nrates - 1) - ni->ni_txrate;
+#ifndef IEEE80211_NO_HT
+ if (ni->ni_flags & IEEE80211_NODE_HT) {
+ tx->initial_rate_index =
+ (nitems(iwm_mcs2ridx) - 1) - ni->ni_txmcs;
+ } else
+#endif
+ tx->initial_rate_index = (nrates - 1) - ni->ni_txrate;
tx->tx_flags |= htole32(IWM_TX_CMD_FLG_STA_RATE);
DPRINTFN(12, ("start with txrate %d\n",
tx->initial_rate_index));
- ridx = in->in_ridx[ni->ni_txrate];
+#ifndef IEEE80211_NO_HT
+ if (ni->ni_flags & IEEE80211_NODE_HT) {
+ ridx = iwm_mcs2ridx[ni->ni_txmcs];
+ return &iwm_rates[ridx];
+ }
+#endif
+ ridx = 0;
+ for (i = 0; i < nrates; i++) {
+ if (iwm_rates[i].rate == (ni->ni_txrate &
+ IEEE80211_RATE_VAL)) {
+ ridx = i;
+ break;
+ }
+ }
return &iwm_rates[ridx];
}
@@ -3690,7 +3903,14 @@ iwm_tx_fill_cmd(struct iwm_softc *sc, st
rate_flags = 1 << IWM_RATE_MCS_ANT_POS;
if (IWM_RIDX_IS_CCK(ridx))
rate_flags |= IWM_RATE_MCS_CCK_MSK;
- tx->rate_n_flags = htole32(rate_flags | rinfo->plcp);
+#ifndef IEEE80211_NO_HT
+ if ((ni->ni_flags & IEEE80211_NODE_HT) &&
+ rinfo->ht_plcp != IWM_RATE_HT_SISO_MCS_INV_PLCP) {
+ rate_flags |= IWM_RATE_MCS_HT_MSK;
+ tx->rate_n_flags = htole32(rate_flags | rinfo->ht_plcp);
+ } else
+#endif
+ tx->rate_n_flags = htole32(rate_flags | rinfo->plcp);
return rinfo;
}
@@ -3755,7 +3975,11 @@ iwm_tx(struct iwm_softc *sc, struct mbuf
tap->wt_flags = 0;
tap->wt_chan_freq = htole16(ni->ni_chan->ic_freq);
tap->wt_chan_flags = htole16(ni->ni_chan->ic_flags);
- tap->wt_rate = rinfo->rate;
+ if (rinfo->plcp == IWM_RATE_INVM_PLCP) {
+ /* XXX need a way to pass current MCS in 11n mode */
+ tap->wt_rate = 0;
+ } else
+ tap->wt_rate = rinfo->rate;
tap->wt_hwqueue = ac;
if ((ic->ic_flags & IEEE80211_F_WEPON) &&
(wh->i_fc[1] & IEEE80211_FC1_PROTECTED))
@@ -4751,6 +4975,30 @@ iwm_mvm_mac_ctxt_cmd_common(struct iwm_s
cmd->ac[txf].edca_txop = 0;
}
+#ifndef IEEE80211_NO_HT
+ if (ni->ni_flags & IEEE80211_NODE_HT) {
+ enum ieee80211_htprot htprot =
+ (ni->ni_htop1 & IEEE80211_HTOP1_PROT_MASK);
+ switch (htprot) {
+ case IEEE80211_HTPROT_NONE:
+ break;
+ case IEEE80211_HTPROT_NONMEMBER:
+ case IEEE80211_HTPROT_NONHT_MIXED:
+ cmd->protection_flags |=
+ htole32(IWM_MAC_PROT_FLG_HT_PROT);
+ case IEEE80211_HTPROT_20MHZ:
+ cmd->protection_flags |=
+ htole32(IWM_MAC_PROT_FLG_HT_PROT |
+ IWM_MAC_PROT_FLG_FAT_PROT);
+ break;
+ default:
+ DPRINTF(("Unknown protection mode %d\n", htprot));
+ break;
+ }
+
+ cmd->qos_flags |= htole32(IWM_MAC_QOS_FLG_TGN);
+ }
+#endif
if (ic->ic_flags & IEEE80211_F_USEPROT)
cmd->protection_flags |= htole32(IWM_MAC_PROT_FLG_TGG_PROTECT);
@@ -5142,8 +5390,11 @@ iwm_calib_timeout(void *arg)
int s;
s = splnet();
- if (ic->ic_fixed_rate == -1
- && ic->ic_opmode == IEEE80211_M_STA
+ if ((ic->ic_fixed_rate == -1
+#ifndef IEEE80211_NO_HT
+ || ic->ic_fixed_mcs == -1
+#endif
+ ) && ic->ic_opmode == IEEE80211_M_STA
&& ic->ic_bss) {
struct iwm_node *in = (void *)ic->ic_bss;
ieee80211_amrr_choose(&sc->sc_amrr, &in->in_ni, &in->in_amn);
@@ -5160,77 +5411,92 @@ iwm_setrates(struct iwm_node *in)
struct ieee80211com *ic = ni->ni_ic;
struct iwm_softc *sc = IC2IFP(ic)->if_softc;
struct iwm_lq_cmd *lq = &in->in_lq;
- int nrates = ni->ni_rates.rs_nrates;
- int i, ridx, tab = 0;
- int txant = 0;
-
- if (nrates > nitems(lq->rs_table)) {
- DPRINTF(("%s: node supports %d rates, driver handles "
- "only %zu\n", DEVNAME(sc), nrates, nitems(lq->rs_table)));
- return;
- }
-
- /* first figure out which rates we should support */
- memset(&in->in_ridx, -1, sizeof(in->in_ridx));
- for (i = 0; i < nrates; i++) {
- int rate = ni->ni_rates.rs_rates[i] & IEEE80211_RATE_VAL;
-
- /* Map 802.11 rate to HW rate index. */
- for (ridx = 0; ridx <= IWM_RIDX_MAX; ridx++)
- if (iwm_rates[ridx].rate == rate)
+ struct ieee80211_rateset *rs = &ni->ni_rates;
+ int i, ridx, j, tab = 0;
+#ifndef IEEE80211_NO_HT
+ int maxmcs = 0;
+
+ /* Figure out which HT rates we should support. */
+ if (ni->ni_flags & IEEE80211_NODE_HT) {
+ for (i = 0; i < IEEE80211_HT_NUM_MCS; i++) {
+ if (isclr(ic->ic_sup_mcs, i))
break;
- if (ridx > IWM_RIDX_MAX)
- DPRINTF(("%s: WARNING: device rate for %d not found!\n",
- DEVNAME(sc), rate));
- else
- in->in_ridx[i] = ridx;
- }
-
- /* then construct a lq_cmd based on those */
+ if (isset(ni->ni_rxmcs, i))
+ maxmcs = i;
+ }
+ }
+#endif
memset(lq, 0, sizeof(*lq));
lq->sta_id = IWM_STATION_ID;
/*
- * are these used? (we don't do SISO or MIMO)
- * need to set them to non-zero, though, or we get an error.
+ * Fill the LQ rate selection table with legacy and/or HT rates
+ * in descending order, i.e. with the highest rate first.
+ * In cases where throughput of an HT rate corresponds to a legacy
+ * rate it makes no sense to add both. We rely on the fact that
+ * iwm_rates is laid out such that equivalent HT/legacy rates share
+ * the same IWM_RATE_*_INDEX value. Also, rates not applicable to
+ * legacy/HT are assumed to be marked with an 'invalid' PLCP value.
*/
- lq->single_stream_ant_msk = 1;
- lq->dual_stream_ant_msk = 1;
+ j = 0;
+ for (ridx = IWM_RIDX_MAX; ridx >= 0; ridx--) {
+ if (j >= nitems(lq->rs_table))
+ break;
+ tab = 0;
+#ifndef IEEE80211_NO_HT
+ if ((ni->ni_flags & IEEE80211_NODE_HT) &&
+ iwm_rates[ridx].ht_plcp != IWM_RATE_HT_SISO_MCS_INV_PLCP) {
+ for (i = 0; i <= maxmcs; i++) {
+ if (isclr(ni->ni_rxmcs, i))
+ continue;
+ if (ridx == iwm_mcs2ridx[i]) {
+ tab = iwm_rates[ridx].ht_plcp;
+ tab |= IWM_RATE_MCS_HT_MSK;
+ break;
+ }
+ }
+ }
+#endif
+ if (tab == 0 && iwm_rates[ridx].plcp != IWM_RATE_INVM_PLCP) {
+ for (i = 0; i < rs->rs_nrates; i++) {
+ if (iwm_rates[ridx].rate == (rs->rs_rates[i] &
+ IEEE80211_RATE_VAL)) {
+ tab = iwm_rates[ridx].plcp;
+ break;
+ }
+ }
+ }
- /*
- * Build the actual rate selection table.
- * The lowest bits are the rates. Additionally,
- * CCK needs bit 9 to be set. The rest of the bits
- * we add to the table select the tx antenna
- * Note that we add the rates in the highest rate first
- * (opposite of ni_rates).
- */
- for (i = 0; i < nrates; i++) {
- int nextant;
+ if (tab == 0)
+ continue;
- if (txant == 0)
- txant = IWM_FW_VALID_TX_ANT(sc);
- nextant = 1<<(ffs(txant)-1);
- txant &= ~nextant;
-
- ridx = in->in_ridx[(nrates-1)-i];
- tab = iwm_rates[ridx].plcp;
- tab |= nextant << IWM_RATE_MCS_ANT_POS;
+ tab |= 1 << IWM_RATE_MCS_ANT_POS;
if (IWM_RIDX_IS_CCK(ridx))
tab |= IWM_RATE_MCS_CCK_MSK;
- DPRINTFN(2, ("station rate %d %x\n", i, tab));
- lq->rs_table[i] = htole32(tab);
- }
- /* then fill the rest with the lowest possible rate */
- for (i = nrates; i < nitems(lq->rs_table); i++) {
- KASSERT(tab != 0);
- lq->rs_table[i] = htole32(tab);
+ DPRINTFN(2, ("station rate %d %x\n", j, tab));
+ lq->rs_table[j++] = htole32(tab);
}
+ /* Fill the rest with the lowest possible rate */
+ i = j > 0 ? j - 1 : 0;
+ while (j < nitems(lq->rs_table))
+ lq->rs_table[j++] = lq->rs_table[i];
+
+ lq->single_stream_ant_msk = IWM_ANT_A;
+ lq->dual_stream_ant_msk = IWM_ANT_AB;
+
/* init amrr */
ieee80211_amrr_node_init(&sc->sc_amrr, &in->in_amn);
/* Start at lowest available bit-rate, AMRR will raise. */
ni->ni_txrate = 0;
+
+#ifndef IEEE80211_NO_HT
+ /*
+ * XXX AMRR doesn't seem to raise properly in HT mode.
+ * For now, start with the highest rate and let AMRR drop down.
+ */
+ ni->ni_txmcs = 7;
+#endif
}
int
@@ -5245,6 +5511,11 @@ iwm_media_change(struct ifnet *ifp)
if (error != ENETRESET)
return error;
+#ifndef IEEE80211_NO_HT
+ if (ic->ic_fixed_mcs != -1)
+ sc->sc_fixed_ridx = iwm_mcs2ridx[ic->ic_fixed_mcs];
+ else
+#endif
if (ic->ic_fixed_rate != -1) {
rate = ic->ic_sup_rates[ic->ic_curmode].
rs_rates[ic->ic_fixed_rate] & IEEE80211_RATE_VAL;
@@ -5654,6 +5925,9 @@ iwm_stop(struct ifnet *ifp, int disable)
task_del(systq, &sc->init_task);
task_del(sc->sc_nswq, &sc->newstate_task);
task_del(sc->sc_eswq, &sc->sc_eswk);
+#ifndef IEEE80211_NO_HT
+ task_del(systq, &sc->ba_task);
+#endif
sc->sc_newstate(ic, IEEE80211_S_INIT, -1);
timeout_del(&sc->sc_calib_to);
@@ -6347,6 +6621,10 @@ iwm_preinit(struct iwm_softc *sc)
IWM_UCODE_API(sc->sc_fwver),
ether_sprintf(sc->sc_nvm.hw_addr));
+#ifndef IEEE80211_NO_HT
+ if (sc->sc_nvm.sku_cap_11n_enable)
+ iwm_setup_ht_rates(sc);
+#endif
/* not all hardware can do 5GHz band */
if (!sc->sc_nvm.sku_cap_band_52GHz_enable)
memset(&ic->ic_sup_rates[IEEE80211_MODE_11A], 0,
@@ -6366,6 +6644,14 @@ iwm_preinit(struct iwm_softc *sc)
/* Override 802.11 state transition machine. */
sc->sc_newstate = ic->ic_newstate;
ic->ic_newstate = iwm_newstate;
+#ifndef IEEE80211_NO_HT
+ ic->ic_ampdu_rx_start = iwm_ampdu_rx_start;
+ ic->ic_ampdu_rx_stop = iwm_ampdu_rx_stop;
+#ifdef notyet
+ ic->ic_ampdu_tx_start = iwm_ampdu_tx_start;
+ ic->ic_ampdu_tx_stop = iwm_ampdu_tx_stop;
+#endif
+#endif
ieee80211_media_init(ifp, iwm_media_change, ieee80211_media_status);
return 0;
@@ -6551,6 +6837,14 @@ iwm_attach(struct device *parent, struct
IEEE80211_C_SHSLOT | /* short slot time supported */
IEEE80211_C_SHPREAMBLE; /* short preamble supported */
+#ifndef IEEE80211_NO_HT
+ /* No optional HT features supported for now, */
+ ic->ic_htcaps = 0;
+ ic->ic_htxcaps = 0;
+ ic->ic_txbfcaps = 0;
+ ic->ic_aselcaps = 0;
+#endif
+
ic->ic_sup_rates[IEEE80211_MODE_11A] = ieee80211_std_rateset_11a;
ic->ic_sup_rates[IEEE80211_MODE_11B] = ieee80211_std_rateset_11b;
ic->ic_sup_rates[IEEE80211_MODE_11G] = ieee80211_std_rateset_11g;
@@ -6587,6 +6881,9 @@ iwm_attach(struct device *parent, struct
timeout_set(&sc->sc_led_blink_to, iwm_led_blink_timeout, sc);
task_set(&sc->init_task, iwm_init_task, sc);
task_set(&sc->newstate_task, iwm_newstate_task, sc);
+#ifndef IEEE80211_NO_HT
+ task_set(&sc->ba_task, iwm_ba_task, sc);
+#endif
/*
* We cannot read the MAC address without loading the
Index: dev/pci/if_iwmreg.h
===================================================================
RCS file: /cvs/src/sys/dev/pci/if_iwmreg.h,v
retrieving revision 1.6
diff -u -p -r1.6 if_iwmreg.h
--- dev/pci/if_iwmreg.h 15 Nov 2015 09:39:09 -0000 1.6
+++ dev/pci/if_iwmreg.h 15 Nov 2015 13:06:25 -0000
@@ -2361,9 +2361,7 @@ struct iwm_rx_phy_info {
#define IWM_PHY_INFO_FLAG_SHPREAMBLE (1 << 2)
uint16_t channel;
uint32_t non_cfg_phy[IWM_RX_INFO_PHY_CNT];
- uint8_t rate;
- uint8_t rflags;
- uint16_t xrflags;
+ uint32_t rate_n_flags;
uint32_t byte_count;
uint16_t mac_active_msk;
uint16_t frame_time;
@@ -3424,10 +3422,56 @@ struct iwm_beacon_filter_cmd {
.bf_escape_timer = htole32(IWM_BF_ESCAPE_TIMER_DEFAULT), \
.ba_escape_timer = htole32(IWM_BA_ESCAPE_TIMER_DEFAULT)
+/* uCode API values for HT/VHT bit rates */
+enum {
+ IWM_RATE_HT_SISO_MCS_0_PLCP = 0,
+ IWM_RATE_HT_SISO_MCS_1_PLCP = 1,
+ IWM_RATE_HT_SISO_MCS_2_PLCP = 2,
+ IWM_RATE_HT_SISO_MCS_3_PLCP = 3,
+ IWM_RATE_HT_SISO_MCS_4_PLCP = 4,
+ IWM_RATE_HT_SISO_MCS_5_PLCP = 5,
+ IWM_RATE_HT_SISO_MCS_6_PLCP = 6,
+ IWM_RATE_HT_SISO_MCS_7_PLCP = 7,
+ IWM_RATE_HT_MIMO2_MCS_0_PLCP = 0x8,
+ IWM_RATE_HT_MIMO2_MCS_1_PLCP = 0x9,
+ IWM_RATE_HT_MIMO2_MCS_2_PLCP = 0xA,
+ IWM_RATE_HT_MIMO2_MCS_3_PLCP = 0xB,
+ IWM_RATE_HT_MIMO2_MCS_4_PLCP = 0xC,
+ IWM_RATE_HT_MIMO2_MCS_5_PLCP = 0xD,
+ IWM_RATE_HT_MIMO2_MCS_6_PLCP = 0xE,
+ IWM_RATE_HT_MIMO2_MCS_7_PLCP = 0xF,
+ IWM_RATE_VHT_SISO_MCS_0_PLCP = 0,
+ IWM_RATE_VHT_SISO_MCS_1_PLCP = 1,
+ IWM_RATE_VHT_SISO_MCS_2_PLCP = 2,
+ IWM_RATE_VHT_SISO_MCS_3_PLCP = 3,
+ IWM_RATE_VHT_SISO_MCS_4_PLCP = 4,
+ IWM_RATE_VHT_SISO_MCS_5_PLCP = 5,
+ IWM_RATE_VHT_SISO_MCS_6_PLCP = 6,
+ IWM_RATE_VHT_SISO_MCS_7_PLCP = 7,
+ IWM_RATE_VHT_SISO_MCS_8_PLCP = 8,
+ IWM_RATE_VHT_SISO_MCS_9_PLCP = 9,
+ IWM_RATE_VHT_MIMO2_MCS_0_PLCP = 0x10,
+ IWM_RATE_VHT_MIMO2_MCS_1_PLCP = 0x11,
+ IWM_RATE_VHT_MIMO2_MCS_2_PLCP = 0x12,
+ IWM_RATE_VHT_MIMO2_MCS_3_PLCP = 0x13,
+ IWM_RATE_VHT_MIMO2_MCS_4_PLCP = 0x14,
+ IWM_RATE_VHT_MIMO2_MCS_5_PLCP = 0x15,
+ IWM_RATE_VHT_MIMO2_MCS_6_PLCP = 0x16,
+ IWM_RATE_VHT_MIMO2_MCS_7_PLCP = 0x17,
+ IWM_RATE_VHT_MIMO2_MCS_8_PLCP = 0x18,
+ IWM_RATE_VHT_MIMO2_MCS_9_PLCP = 0x19,
+ IWM_RATE_HT_SISO_MCS_INV_PLCP,
+ IWM_RATE_HT_MIMO2_MCS_INV_PLCP = IWM_RATE_HT_SISO_MCS_INV_PLCP,
+ IWM_RATE_VHT_SISO_MCS_INV_PLCP = IWM_RATE_HT_SISO_MCS_INV_PLCP,
+ IWM_RATE_VHT_MIMO2_MCS_INV_PLCP = IWM_RATE_HT_SISO_MCS_INV_PLCP,
+ IWM_RATE_HT_SISO_MCS_8_PLCP = IWM_RATE_HT_SISO_MCS_INV_PLCP,
+ IWM_RATE_HT_SISO_MCS_9_PLCP = IWM_RATE_HT_SISO_MCS_INV_PLCP,
+ IWM_RATE_HT_MIMO2_MCS_8_PLCP = IWM_RATE_HT_SISO_MCS_INV_PLCP,
+ IWM_RATE_HT_MIMO2_MCS_9_PLCP = IWM_RATE_HT_SISO_MCS_INV_PLCP,
+};
+
/*
- * These serve as indexes into
- * struct iwm_rate_info fw_rate_idx_to_plcp[IWM_RATE_COUNT];
- * TODO: avoid overlap between legacy and HT rates
+ * These serve as indexes into struct iwm_rate iwm_rates[IWM_RIDX_MAX].
*/
enum {
IWM_RATE_1M_INDEX = 0,
@@ -3481,7 +3525,7 @@ enum {
IWM_RATE_2M_PLCP = 20,
IWM_RATE_5M_PLCP = 55,
IWM_RATE_11M_PLCP = 110,
- IWM_RATE_INVM_PLCP = -1,
+ IWM_RATE_INVM_PLCP = 0xff,
};
/*
Index: dev/pci/if_iwmvar.h
===================================================================
RCS file: /cvs/src/sys/dev/pci/if_iwmvar.h,v
retrieving revision 1.12
diff -u -p -r1.12 if_iwmvar.h
--- dev/pci/if_iwmvar.h 22 Oct 2015 11:51:28 -0000 1.12
+++ dev/pci/if_iwmvar.h 15 Nov 2015 13:06:25 -0000
@@ -191,6 +191,7 @@ struct iwm_nvm_data {
int sku_cap_11n_enable;
int sku_cap_amt_enable;
int sku_cap_ipan_enable;
+ int sku_cap_mimo_disable;
uint8_t radio_cfg_type;
uint8_t radio_cfg_step;
@@ -368,6 +369,14 @@ struct iwm_softc {
enum ieee80211_state ns_nstate;
int ns_arg;
+#ifndef IEEE80211_NO_HT
+ /* Task for firmware BlockAck setup/teardown and its arguments. */
+ struct task ba_task;
+ int ba_start;
+ int ba_tid;
+ uint16_t ba_ssn;
+#endif
+
bus_space_tag_t sc_st;
bus_space_handle_t sc_sh;
bus_size_t sc_sz;
@@ -435,6 +444,9 @@ struct iwm_softc {
struct iwm_bf_data sc_bf;
int sc_tx_timer;
+#ifndef IEEE80211_NO_HT
+ int sc_rx_ba_sessions;
+#endif
struct iwm_scan_cmd *sc_scan_cmd;
size_t sc_scan_cmd_len;
@@ -499,8 +511,6 @@ struct iwm_node {
struct iwm_lq_cmd in_lq;
struct ieee80211_amrr_node in_amn;
-
- uint8_t in_ridx[IEEE80211_RATE_MAXSIZE];
};
#define IWM_STATION_ID 0
Index: net80211/ieee80211_input.c
===================================================================
RCS file: /cvs/src/sys/net80211/ieee80211_input.c,v
retrieving revision 1.142
diff -u -p -r1.142 ieee80211_input.c
--- net80211/ieee80211_input.c 15 Nov 2015 11:14:17 -0000 1.142
+++ net80211/ieee80211_input.c 15 Nov 2015 13:06:28 -0000
@@ -280,6 +280,43 @@ ieee80211_input(struct ifnet *ifp, struc
tid = 0;
}
+#ifndef IEEE80211_NO_HT
+ if (type == IEEE80211_FC0_TYPE_DATA && hasqos &&
+ !(rxi->rxi_flags & IEEE80211_RXI_AMPDU_DONE)) {
+ int ba_state = ni->ni_rx_ba[tid].ba_state;
+
+ /*
+ * If Block Ack was explicitly requested, check
+ * if we have a BA agreement for this RA/TID.
+ */
+ if ((qos & IEEE80211_QOS_ACK_POLICY_MASK) ==
+ IEEE80211_QOS_ACK_POLICY_BA &&
+ ba_state != IEEE80211_BA_AGREED) {
+ DPRINTF(("no BA agreement for %s, TID %d\n",
+ ether_sprintf(ni->ni_macaddr), tid));
+ /* send a DELBA with reason code UNKNOWN-BA */
+ IEEE80211_SEND_ACTION(ic, ni,
+ IEEE80211_CATEG_BA, IEEE80211_ACTION_DELBA,
+ IEEE80211_REASON_SETUP_REQUIRED << 16 | tid);
+ goto err;
+ }
+
+ /*
+ * Check if we have an explicit or implicit
+ * Block Ack Request for a valid BA agreement.
+ */
+ if (ba_state == IEEE80211_BA_AGREED &&
+ ((qos & IEEE80211_QOS_ACK_POLICY_MASK) ==
+ IEEE80211_QOS_ACK_POLICY_BA ||
+ (qos & IEEE80211_QOS_ACK_POLICY_MASK) ==
+ IEEE80211_QOS_ACK_POLICY_NORMAL)) {
+ /* go through A-MPDU reordering */
+ ieee80211_input_ba(ifp, m, ni, tid, rxi);
+ return; /* don't free m! */
+ }
+ }
+#endif
+
/* duplicate detection (see 9.2.9) */
if (ieee80211_has_seq(wh) &&
ic->ic_state != IEEE80211_S_SCAN) {
@@ -430,27 +467,6 @@ ieee80211_input(struct ifnet *ifp, struc
goto out;
}
-#ifndef IEEE80211_NO_HT
- if (!(rxi->rxi_flags & IEEE80211_RXI_AMPDU_DONE) &&
- hasqos && (qos & IEEE80211_QOS_ACK_POLICY_MASK) ==
- IEEE80211_QOS_ACK_POLICY_BA) {
- /* check if we have a BA agreement for this RA/TID */
- if (ni->ni_rx_ba[tid].ba_state !=
- IEEE80211_BA_AGREED) {
- DPRINTF(("no BA agreement for %s, TID %d\n",
- ether_sprintf(ni->ni_macaddr), tid));
- /* send a DELBA with reason code UNKNOWN-BA */
- IEEE80211_SEND_ACTION(ic, ni,
- IEEE80211_CATEG_BA, IEEE80211_ACTION_DELBA,
- IEEE80211_REASON_SETUP_REQUIRED << 16 |
- tid);
- goto err;
- }
- /* go through A-MPDU reordering */
- ieee80211_input_ba(ifp, m, ni, tid, rxi);
- return; /* don't free m! */
- }
-#endif
if ((ic->ic_flags & IEEE80211_F_WEPON) ||
((ic->ic_flags & IEEE80211_F_RSNON) &&
(ni->ni_flags & IEEE80211_NODE_RXPROT))) {
@@ -1046,6 +1062,11 @@ ieee80211_amsdu_decap(struct ieee80211co
}
len += ETHER_HDR_LEN;
+ if (m->m_pkthdr.len == len) {
+ ieee80211_deliver_data(ic, m, ni);
+ break;
+ }
+
/* "detach" our A-MSDU subframe from the others */
n = m_split(m, len, M_NOWAIT);
if (n == NULL) {
@@ -2449,6 +2470,7 @@ ieee80211_recv_addba_req(struct ieee8021
ba->ba_timeout_val = IEEE80211_BA_MIN_TIMEOUT;
else if (ba->ba_timeout_val > IEEE80211_BA_MAX_TIMEOUT)
ba->ba_timeout_val = IEEE80211_BA_MAX_TIMEOUT;
+ ba->ba_ni = ni;
timeout_set(&ba->ba_to, ieee80211_rx_ba_timeout, ba);
ba->ba_winsize = bufsz;
if (ba->ba_winsize == 0 || ba->ba_winsize > IEEE80211_BA_MAX_WINSZ)
@@ -2465,7 +2487,7 @@ ieee80211_recv_addba_req(struct ieee8021
ba->ba_head = 0;
/* notify drivers of this new Block Ack agreement */
- if (ic->ic_ampdu_rx_start != NULL &&
+ if (ic->ic_ampdu_rx_start == NULL ||
ic->ic_ampdu_rx_start(ic, ni, tid) != 0) {
/* driver failed to setup, rollback */
free(ba->ba_buf, M_DEVBUF, 0);
Index: net80211/ieee80211_node.c
===================================================================
RCS file: /cvs/src/sys/net80211/ieee80211_node.c,v
retrieving revision 1.91
diff -u -p -r1.91 ieee80211_node.c
--- net80211/ieee80211_node.c 15 Nov 2015 11:14:17 -0000 1.91
+++ net80211/ieee80211_node.c 15 Nov 2015 13:06:28 -0000
@@ -69,6 +69,9 @@ u_int8_t ieee80211_node_getrssi(struct i
void ieee80211_setup_node(struct ieee80211com *, struct ieee80211_node *,
const u_int8_t *);
void ieee80211_free_node(struct ieee80211com *, struct ieee80211_node *);
+#ifndef IEEE80211_NO_HT
+void ieee80211_ba_del(struct ieee80211_node *);
+#endif
struct ieee80211_node *ieee80211_alloc_node_helper(struct ieee80211com *);
void ieee80211_node_cleanup(struct ieee80211com *, struct ieee80211_node *);
void ieee80211_needs_auth(struct ieee80211com *, struct ieee80211_node *);
@@ -758,6 +761,9 @@ ieee80211_node_cleanup(struct ieee80211c
free(ni->ni_rsnie, M_DEVBUF, 0);
ni->ni_rsnie = NULL;
}
+#ifndef IEEE80211_NOT_HT
+ ieee80211_ba_del(ni);
+#endif
}
void
@@ -1066,6 +1072,32 @@ ieee80211_find_node_for_beacon(struct ie
return (keep);
}
+#ifndef IEEE80211_NO_HT
+void
+ieee80211_ba_del(struct ieee80211_node *ni)
+{
+ int tid;
+
+ for (tid = 0; tid < nitems(ni->ni_rx_ba); tid++) {
+ struct ieee80211_rx_ba *ba = &ni->ni_rx_ba[tid];
+ if (ba->ba_state == IEEE80211_BA_AGREED) {
+ if (timeout_pending(&ba->ba_to))
+ timeout_del(&ba->ba_to);
+ ba->ba_state = IEEE80211_BA_INIT;
+ }
+ }
+
+ for (tid = 0; tid < nitems(ni->ni_tx_ba); tid++) {
+ struct ieee80211_tx_ba *ba = &ni->ni_tx_ba[tid];
+ if (ba->ba_state == IEEE80211_BA_AGREED) {
+ if (timeout_pending(&ba->ba_to))
+ timeout_del(&ba->ba_to);
+ ba->ba_state = IEEE80211_BA_INIT;
+ }
+ }
+}
+#endif
+
void
ieee80211_free_node(struct ieee80211com *ic, struct ieee80211_node *ni)
{
@@ -1079,6 +1111,9 @@ ieee80211_free_node(struct ieee80211com
timeout_del(&ni->ni_eapol_to);
timeout_del(&ni->ni_sa_query_to);
IEEE80211_AID_CLR(ni->ni_associd, ic->ic_aid_bitmap);
+#endif
+#ifndef IEEE80211_NOT_HT
+ ieee80211_ba_del(ni);
#endif
RB_REMOVE(ieee80211_tree, &ic->ic_tree, ni);
ic->ic_nnodes--;
Index: net80211/ieee80211_node.h
===================================================================
RCS file: /cvs/src/sys/net80211/ieee80211_node.h,v
retrieving revision 1.49
diff -u -p -r1.49 ieee80211_node.h
--- net80211/ieee80211_node.h 15 Nov 2015 12:34:07 -0000 1.49
+++ net80211/ieee80211_node.h 15 Nov 2015 13:06:28 -0000
@@ -112,8 +112,8 @@ struct ieee80211_tx_ba {
struct ieee80211_node *ba_ni; /* backpointer for callbacks */
struct timeout ba_to;
int ba_timeout_val;
-#define IEEE80211_BA_MIN_TIMEOUT (10 * 1000) /* 10msec */
-#define IEEE80211_BA_MAX_TIMEOUT (10 * 1000 * 1000) /* 10sec */
+#define IEEE80211_BA_MIN_TIMEOUT (10 * 1000 * 1000) /* 10 sec */
+#define IEEE80211_BA_MAX_TIMEOUT (60 * 1000 * 1000) /* 60 sec */
int ba_state;
#define IEEE80211_BA_INIT 0