summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorstsp <stsp@openbsd.org>2019-07-29 10:50:08 +0000
committerstsp <stsp@openbsd.org>2019-07-29 10:50:08 +0000
commitaefc44da88be244ed78bb957542781aeda020367 (patch)
tree8b2a5ccfe2d1a8a1d00c1ba73a351a6c5efc3353
parentreduce the diff to linux (diff)
downloadwireguard-openbsd-aefc44da88be244ed78bb957542781aeda020367.tar.xz
wireguard-openbsd-aefc44da88be244ed78bb957542781aeda020367.zip
Add support for 802.11n Tx aggregation to net80211 and the iwn(4) driver.
In particular, add Tx block ack session management to net80211, with enough funcionality to support Tx aggregation on devices which perform A-MPDU subframe scheduling in firmware. Make use of the iwn(4) firmware Tx scheduler to build A-MPDUs. net80211's QoS support code is now enabled and used by Tx aggregation. A-MSDU frames inside A-MPDUs have been tested and work in principle. For now, this feature is disabled because unfair TCP connection sharing was observed during testing, where bursts of TCP Ack frames for a single tcpbench(1) connection arriving in A-MSDUs made other TCP connections stall. Switch off support for A-MSDUs inside A-MPDUs on the Rx side as well. Tested on iwn chipsets 1000, 4965, 5100, 5300, 2200, 6200, 6205, 6300 (committed version of tested diff has all debug printfs removed) tests/ok benno kmos mlarkin kevlo
-rw-r--r--sys/dev/pci/if_iwn.c466
-rw-r--r--sys/dev/pci/if_iwnreg.h97
-rw-r--r--sys/dev/pci/if_iwnvar.h12
-rw-r--r--sys/net80211/ieee80211.c34
-rw-r--r--sys/net80211/ieee80211_input.c80
-rw-r--r--sys/net80211/ieee80211_mira.c4
-rw-r--r--sys/net80211/ieee80211_node.c96
-rw-r--r--sys/net80211/ieee80211_node.h12
-rw-r--r--sys/net80211/ieee80211_output.c185
-rw-r--r--sys/net80211/ieee80211_proto.c67
-rw-r--r--sys/net80211/ieee80211_proto.h19
-rw-r--r--sys/net80211/ieee80211_var.h3
12 files changed, 935 insertions, 140 deletions
diff --git a/sys/dev/pci/if_iwn.c b/sys/dev/pci/if_iwn.c
index 520d6ce8425..be1f18695ea 100644
--- a/sys/dev/pci/if_iwn.c
+++ b/sys/dev/pci/if_iwn.c
@@ -1,4 +1,4 @@
-/* $OpenBSD: if_iwn.c,v 1.211 2019/07/25 01:46:15 cheloha Exp $ */
+/* $OpenBSD: if_iwn.c,v 1.212 2019/07/29 10:50:08 stsp Exp $ */
/*-
* Copyright (c) 2007-2010 Damien Bergamini <damien.bergamini@free.fr>
@@ -57,6 +57,8 @@
#include <net80211/ieee80211_amrr.h>
#include <net80211/ieee80211_mira.h>
#include <net80211/ieee80211_radiotap.h>
+#include <net80211/ieee80211_priv.h> /* for SEQ_LT */
+#undef DPRINTF /* defined in ieee80211_priv.h */
#include <dev/pci/if_iwnreg.h>
#include <dev/pci/if_iwnvar.h>
@@ -161,12 +163,20 @@ void iwn5000_rx_calib_results(struct iwn_softc *,
struct iwn_rx_desc *, struct iwn_rx_data *);
void iwn_rx_statistics(struct iwn_softc *, struct iwn_rx_desc *,
struct iwn_rx_data *);
+void iwn_ampdu_txq_advance(struct iwn_softc *, struct iwn_tx_ring *,
+ int, int);
+void iwn_ampdu_tx_done(struct iwn_softc *, struct iwn_tx_ring *,
+ struct iwn_rx_desc *, uint16_t, struct iwn_txagg_status *,
+ int, uint32_t);
void iwn4965_tx_done(struct iwn_softc *, struct iwn_rx_desc *,
struct iwn_rx_data *);
void iwn5000_tx_done(struct iwn_softc *, struct iwn_rx_desc *,
struct iwn_rx_data *);
+void iwn_tx_done_free_txdata(struct iwn_softc *,
+ struct iwn_tx_data *);
+void iwn_clear_oactive(struct iwn_softc *, struct iwn_tx_ring *);
void iwn_tx_done(struct iwn_softc *, struct iwn_rx_desc *,
- uint8_t, uint8_t, uint8_t, uint16_t);
+ uint8_t, int, int, uint16_t);
void iwn_cmd_done(struct iwn_softc *, struct iwn_rx_desc *);
void iwn_notif_intr(struct iwn_softc *);
void iwn_wakeup_intr(struct iwn_softc *);
@@ -174,6 +184,7 @@ void iwn_fatal_intr(struct iwn_softc *);
int iwn_intr(void *);
void iwn4965_update_sched(struct iwn_softc *, int, int, uint8_t,
uint16_t);
+void iwn4965_reset_sched(struct iwn_softc *, int, int);
void iwn5000_update_sched(struct iwn_softc *, int, int, uint8_t,
uint16_t);
void iwn5000_reset_sched(struct iwn_softc *, int, int);
@@ -469,6 +480,7 @@ iwn_attach(struct device *parent, struct device *self, void *aux)
ic->ic_aselcaps = 0;
ic->ic_ampdu_params = (IEEE80211_AMPDU_PARAM_SS_4 | 0x3 /* 64k */);
if (sc->sc_flags & IWN_FLAG_HAS_11N) {
+ ic->ic_caps |= (IEEE80211_C_QOS | IEEE80211_C_TX_AMPDU);
/* Set HT capabilities. */
ic->ic_htcaps = IEEE80211_HTCAP_SGI20;
#ifdef notyet
@@ -526,10 +538,8 @@ iwn_attach(struct device *parent, struct device *self, void *aux)
ic->ic_update_htprot = iwn_update_htprot;
ic->ic_ampdu_rx_start = iwn_ampdu_rx_start;
ic->ic_ampdu_rx_stop = iwn_ampdu_rx_stop;
-#ifdef notyet
ic->ic_ampdu_tx_start = iwn_ampdu_tx_start;
ic->ic_ampdu_tx_stop = iwn_ampdu_tx_stop;
-#endif
/* Override 802.11 state transition machine. */
sc->sc_newstate = ic->ic_newstate;
@@ -566,6 +576,7 @@ iwn4965_attach(struct iwn_softc *sc, pci_product_id_t pid)
ops->read_eeprom = iwn4965_read_eeprom;
ops->post_alive = iwn4965_post_alive;
ops->nic_config = iwn4965_nic_config;
+ ops->reset_sched = iwn4965_reset_sched;
ops->update_sched = iwn4965_update_sched;
ops->get_temperature = iwn4965_get_temperature;
ops->get_rssi = iwn4965_get_rssi;
@@ -577,6 +588,7 @@ iwn4965_attach(struct iwn_softc *sc, pci_product_id_t pid)
ops->ampdu_tx_start = iwn4965_ampdu_tx_start;
ops->ampdu_tx_stop = iwn4965_ampdu_tx_stop;
sc->ntxqs = IWN4965_NTXQUEUES;
+ sc->first_agg_txq = IWN4965_FIRST_AGG_TXQUEUE;
sc->ndmachnls = IWN4965_NDMACHNLS;
sc->broadcast_id = IWN4965_ID_BROADCAST;
sc->rxonsz = IWN4965_RXONSZ;
@@ -603,6 +615,7 @@ iwn5000_attach(struct iwn_softc *sc, pci_product_id_t pid)
ops->read_eeprom = iwn5000_read_eeprom;
ops->post_alive = iwn5000_post_alive;
ops->nic_config = iwn5000_nic_config;
+ ops->reset_sched = iwn5000_reset_sched;
ops->update_sched = iwn5000_update_sched;
ops->get_temperature = iwn5000_get_temperature;
ops->get_rssi = iwn5000_get_rssi;
@@ -614,6 +627,7 @@ iwn5000_attach(struct iwn_softc *sc, pci_product_id_t pid)
ops->ampdu_tx_start = iwn5000_ampdu_tx_start;
ops->ampdu_tx_stop = iwn5000_ampdu_tx_stop;
sc->ntxqs = IWN5000_NTXQUEUES;
+ sc->first_agg_txq = IWN5000_FIRST_AGG_TXQUEUE;
sc->ndmachnls = IWN5000_NDMACHNLS;
sc->broadcast_id = IWN5000_ID_BROADCAST;
sc->rxonsz = IWN5000_RXONSZ;
@@ -1275,13 +1289,6 @@ iwn_alloc_tx_ring(struct iwn_softc *sc, struct iwn_tx_ring *ring, int qid)
sc->sc_dev.dv_xname);
goto fail;
}
- /*
- * We only use rings 0 through 4 (4 EDCA + cmd) so there is no need
- * to allocate commands space for other rings.
- * XXX Do we really need to allocate descriptors for other rings?
- */
- if (qid > 4)
- return 0;
size = IWN_TX_RING_COUNT * sizeof (struct iwn_tx_cmd);
error = iwn_dma_contig_alloc(sc->sc_dmat, &ring->cmd_dma,
@@ -1765,6 +1772,14 @@ iwn_newstate(struct ieee80211com *ic, enum ieee80211_state nstate, int arg)
int error;
if (ic->ic_state == IEEE80211_S_RUN) {
+ if (nstate == IEEE80211_S_SCAN) {
+ /*
+ * During RUN->SCAN we don't call sc_newstate() so
+ * we must stop A-MPDU Tx ourselves in this case.
+ */
+ ieee80211_stop_ampdu_tx(ic, ni, -1);
+ ieee80211_ba_del(ni);
+ }
ieee80211_mira_cancel_timeouts(&wn->mn);
timeout_del(&sc->calib_to);
sc->calib.state = IWN_CALIB_STATE_INIT;
@@ -2240,14 +2255,70 @@ void
iwn_rx_compressed_ba(struct iwn_softc *sc, struct iwn_rx_desc *desc,
struct iwn_rx_data *data)
{
- struct iwn_compressed_ba *ba = (struct iwn_compressed_ba *)(desc + 1);
+ struct iwn_compressed_ba *cba = (struct iwn_compressed_ba *)(desc + 1);
+ struct ieee80211com *ic = &sc->sc_ic;
+ struct ieee80211_node *ni;
+ struct ieee80211_tx_ba *ba;
+ struct iwn_node *wn;
struct iwn_tx_ring *txq;
+ uint16_t ssn, idx;
+ int qid;
- bus_dmamap_sync(sc->sc_dmat, data->map, sizeof (*desc), sizeof (*ba),
+ bus_dmamap_sync(sc->sc_dmat, data->map, sizeof (*desc), sizeof (*cba),
BUS_DMASYNC_POSTREAD);
- txq = &sc->txq[letoh16(ba->qid)];
- /* XXX TBD */
+ if (!IEEE80211_ADDR_EQ(ic->ic_bss->ni_macaddr, cba->macaddr))
+ return;
+
+ ni = ic->ic_bss;
+ wn = (void *)ni;
+
+ qid = le16toh(cba->qid);
+ if (qid < sc->first_agg_txq || qid >= sc->ntxqs)
+ return;
+
+ txq = &sc->txq[qid];
+ ssn = le16toh(cba->ssn); /* BA window starting sequence number */
+ idx = IWN_AGG_SSN_TO_TXQ_IDX(ssn);
+
+ /* Protect against a firmware bug where the queue/TID are off. */
+ if (qid != sc->first_agg_txq + cba->tid)
+ return;
+ /*
+ * Update Tx rate statistics.
+ */
+ if (ic->ic_state == IEEE80211_S_RUN && cba->nframes_sent > 0) {
+ uint8_t nframes = cba->nframes_sent;
+ int read = txq->read;
+ wn->mn.agglen = 0;
+ wn->mn.ampdu_size = 0;
+ /* Add up the lengths of all frames before the window. */
+ while (nframes && read != idx) {
+ struct iwn_tx_data *txdata = &txq->data[read];
+ wn->mn.agglen++;
+ wn->mn.ampdu_size += txdata->totlen + IEEE80211_CRC_LEN;
+ read = (read + 1) % IWN_TX_RING_COUNT;
+ nframes--;
+ }
+ wn->mn.frames += cba->nframes_sent;
+ /* If firmware reports a bogus ACK counter, fix it up. */
+ if (cba->nframes_acked > cba->nframes_sent)
+ cba->nframes_acked = cba->nframes_sent;
+ wn->mn.retries += cba->nframes_sent - cba->nframes_acked;
+ if (wn->mn.txfail > wn->mn.frames)
+ wn->mn.txfail = wn->mn.frames;
+ if (wn->mn.ampdu_size > 0)
+ ieee80211_mira_choose(&wn->mn, ic, ni);
+ }
+
+ ba = &ni->ni_tx_ba[cba->tid];
+
+ if (!SEQ_LT(ssn, ba->ba_winstart)) {
+ ieee80211_output_ba_move_window(ic, ni, cba->tid, ssn);
+ iwn_ampdu_txq_advance(sc, txq, qid,
+ IWN_AGG_SSN_TO_TXQ_IDX(ssn));
+ iwn_clear_oactive(sc, txq);
+ }
}
/*
@@ -2265,7 +2336,7 @@ iwn5000_rx_calib_results(struct iwn_softc *sc, struct iwn_rx_desc *desc,
if (sc->sc_flags & IWN_FLAG_CALIB_DONE)
return;
- len = (letoh32(desc->len) & 0x3fff) - 4;
+ len = (letoh32(desc->len) & IWN_RX_DESC_LEN_MASK) - 4;
bus_dmamap_sync(sc->sc_dmat, data->map, sizeof (*desc), len,
BUS_DMASYNC_POSTREAD);
@@ -2372,6 +2443,96 @@ iwn_rx_statistics(struct iwn_softc *sc, struct iwn_rx_desc *desc,
iwn_tune_sensitivity(sc, &stats->rx);
}
+void
+iwn_ampdu_txq_advance(struct iwn_softc *sc, struct iwn_tx_ring *txq, int qid,
+ int idx)
+{
+ struct iwn_ops *ops = &sc->ops;
+
+ DPRINTFN(3, ("%s: txq->cur=%d txq->read=%d txq->queued=%d qid=%d "
+ "idx=%d\n", __func__, txq->cur, txq->read, txq->queued, qid, idx));
+
+ while (txq->read != idx) {
+ struct iwn_tx_data *txdata = &txq->data[txq->read];
+ if (txdata->m != NULL) {
+ ops->reset_sched(sc, qid, txq->read);
+ iwn_tx_done_free_txdata(sc, txdata);
+ txq->queued--;
+ }
+ txq->read = (txq->read + 1) % IWN_TX_RING_COUNT;
+ }
+}
+
+/*
+ * Handle A-MPDU Tx queue status report.
+ * Tx failures come as single frames (perhaps out of order), and before failing
+ * an A-MPDU subframe the firmware transmits it as a single frame at least once
+ * and reports Tx success/failure here. Frames successfully transmitted in an
+ * A-MPDU are completed when a compressed block ack notification is received.
+ */
+void
+iwn_ampdu_tx_done(struct iwn_softc *sc, struct iwn_tx_ring *txq,
+ struct iwn_rx_desc *desc, uint16_t status,
+ struct iwn_txagg_status *agg_status, int nframes, uint32_t ssn)
+{
+ struct ieee80211com *ic = &sc->sc_ic;
+ int tid = desc->qid - sc->first_agg_txq;
+ struct iwn_tx_data *txdata = &txq->data[desc->idx];
+ struct ieee80211_node *ni = txdata->ni;
+ struct iwn_node *wn = (void *)ni;
+ int txfail = (status != IWN_TX_STATUS_SUCCESS &&
+ status != IWN_TX_STATUS_DIRECT_DONE);
+ struct ieee80211_tx_ba *ba;
+
+ sc->sc_tx_timer = 0;
+
+ if (ic->ic_state != IEEE80211_S_RUN || nframes > 1 || ni == NULL)
+ return;
+
+ ba = &ni->ni_tx_ba[tid];
+
+ /* This is a final single-frame Tx attempt. */
+ DPRINTFN(3, ("%s: final tx status=0x%x qid=%d queued=%d idx=%d ssn=%u "
+ "bitmap=0x%llx\n", __func__, status, desc->qid, txq->queued,
+ desc->idx, ssn, ba->ba_bitmap));
+
+ wn->mn.frames++;
+ wn->mn.ampdu_size = txdata->totlen + IEEE80211_CRC_LEN;
+ wn->mn.agglen = 1;
+ if (txfail)
+ wn->mn.txfail++;
+ if (wn->mn.txfail > wn->mn.frames)
+ wn->mn.txfail = wn->mn.frames;
+ ieee80211_mira_choose(&wn->mn, ic, ni);
+
+ if (txfail)
+ ieee80211_tx_compressed_bar(ic, ni, tid, ssn);
+ else if (!SEQ_LT(ssn, ba->ba_winstart)) {
+ /*
+ * Move window forward if SSN lies beyond end of window,
+ * otherwise we can't record the ACK for this frame.
+ * Non-acked frames which left holes in the bitmap near
+ * the beginning of the window must be discarded.
+ */
+ uint16_t s = ssn;
+ while (SEQ_LT(ba->ba_winend, s)) {
+ ieee80211_output_ba_move_window(ic, ni, tid, s);
+ iwn_ampdu_txq_advance(sc, txq, desc->qid,
+ IWN_AGG_SSN_TO_TXQ_IDX(s));
+ s = (s + 1) % 0xfff;
+ }
+ /* SSN should now be within the window; set corresponding bit. */
+ ieee80211_output_ba_record_ack(ic, ni, tid, ssn);
+ }
+
+ /* Move window forward up to the first hole in the bitmap. */
+ ieee80211_output_ba_move_window_to_first_unacked(ic, ni, tid, ssn);
+ iwn_ampdu_txq_advance(sc, txq, desc->qid,
+ IWN_AGG_SSN_TO_TXQ_IDX(ba->ba_winstart));
+
+ iwn_clear_oactive(sc, txq);
+}
+
/*
* Process a TX_DONE firmware notification. Unfortunately, the 4965AGN
* and 5000 adapters have different incompatible TX status formats.
@@ -2381,15 +2542,45 @@ iwn4965_tx_done(struct iwn_softc *sc, struct iwn_rx_desc *desc,
struct iwn_rx_data *data)
{
struct iwn4965_tx_stat *stat = (struct iwn4965_tx_stat *)(desc + 1);
- struct iwn_tx_ring *ring = &sc->txq[desc->qid & 0xf];
- struct iwn_tx_data *txdata = &ring->data[desc->idx];
- /* XXX 4965 does not report byte count */
- uint16_t len = txdata->totlen + IEEE80211_CRC_LEN;
+ struct iwn_tx_ring *ring;
+ size_t len = (letoh32(desc->len) & IWN_RX_DESC_LEN_MASK);
+ uint16_t status = letoh32(stat->stat.status) & 0xff;
+ uint32_t ssn;
+
+ if (desc->qid > IWN4965_NTXQUEUES)
+ return;
+
+ ring = &sc->txq[desc->qid];
bus_dmamap_sync(sc->sc_dmat, data->map, sizeof (*desc),
- sizeof (*stat), BUS_DMASYNC_POSTREAD);
- iwn_tx_done(sc, desc, stat->nframes, stat->ackfailcnt,
- letoh32(stat->status) & 0xff, len);
+ len, BUS_DMASYNC_POSTREAD);
+
+ /* Sanity checks. */
+ if (sizeof(*stat) > len)
+ return;
+ if (stat->nframes < 1 || stat->nframes > IWN_AMPDU_MAX)
+ return;
+ if (desc->qid < sc->first_agg_txq && stat->nframes > 1)
+ return;
+ if (desc->qid >= sc->first_agg_txq && sizeof(*stat) + sizeof(ssn) +
+ stat->nframes * sizeof(stat->stat) > len)
+ return;
+
+ if (desc->qid < sc->first_agg_txq) {
+ /* XXX 4965 does not report byte count */
+ struct iwn_tx_data *txdata = &ring->data[desc->idx];
+ uint16_t framelen = txdata->totlen + IEEE80211_CRC_LEN;
+ int txfail = (status != IWN_TX_STATUS_SUCCESS &&
+ status != IWN_TX_STATUS_DIRECT_DONE);
+
+ iwn_tx_done(sc, desc, stat->ackfailcnt, txfail, desc->qid,
+ framelen);
+ } else {
+ memcpy(&ssn, &stat->stat.status + stat->nframes, sizeof(ssn));
+ ssn = le32toh(ssn) & 0xfff;
+ iwn_ampdu_tx_done(sc, ring, desc, status, stat->stat.agg_status,
+ stat->nframes, ssn);
+ }
}
void
@@ -2397,72 +2588,121 @@ iwn5000_tx_done(struct iwn_softc *sc, struct iwn_rx_desc *desc,
struct iwn_rx_data *data)
{
struct iwn5000_tx_stat *stat = (struct iwn5000_tx_stat *)(desc + 1);
+ struct iwn_tx_ring *ring;
+ size_t len = (letoh32(desc->len) & IWN_RX_DESC_LEN_MASK);
+ uint16_t status = letoh32(stat->stat.status) & 0xff;
+ uint32_t ssn;
-#ifdef notyet
- /* Reset TX scheduler slot. */
- iwn5000_reset_sched(sc, desc->qid & 0xf, desc->idx);
-#endif
+ if (desc->qid > IWN5000_NTXQUEUES)
+ return;
+
+ ring = &sc->txq[desc->qid];
bus_dmamap_sync(sc->sc_dmat, data->map, sizeof (*desc),
sizeof (*stat), BUS_DMASYNC_POSTREAD);
- iwn_tx_done(sc, desc, stat->nframes, stat->ackfailcnt,
- letoh16(stat->status) & 0xff, letoh16(stat->len));
+
+ /* Sanity checks. */
+ if (sizeof(*stat) > len)
+ return;
+ if (stat->nframes < 1 || stat->nframes > IWN_AMPDU_MAX)
+ return;
+ if (desc->qid < sc->first_agg_txq && stat->nframes > 1)
+ return;
+ if (desc->qid >= sc->first_agg_txq && sizeof(*stat) + sizeof(ssn) +
+ stat->nframes * sizeof(stat->stat) > len)
+ return;
+
+ /* If this was not an aggregated frame, complete it now. */
+ if (desc->qid < sc->first_agg_txq) {
+ int txfail = (status != IWN_TX_STATUS_SUCCESS &&
+ status != IWN_TX_STATUS_DIRECT_DONE);
+
+ /* Reset TX scheduler slot. */
+ iwn5000_reset_sched(sc, desc->qid, desc->idx);
+
+ iwn_tx_done(sc, desc, stat->ackfailcnt, txfail, desc->qid,
+ letoh16(stat->len));
+ } else {
+ memcpy(&ssn, &stat->stat.status + stat->nframes, sizeof(ssn));
+ ssn = le32toh(ssn) & 0xfff;
+ iwn_ampdu_tx_done(sc, ring, desc, status, stat->stat.agg_status,
+ stat->nframes, ssn);
+ }
+}
+
+void
+iwn_tx_done_free_txdata(struct iwn_softc *sc, struct iwn_tx_data *data)
+{
+ struct ieee80211com *ic = &sc->sc_ic;
+
+ bus_dmamap_sync(sc->sc_dmat, data->map, 0, data->map->dm_mapsize,
+ BUS_DMASYNC_POSTWRITE);
+ bus_dmamap_unload(sc->sc_dmat, data->map);
+ m_freem(data->m);
+ data->m = NULL;
+ ieee80211_release_node(ic, data->ni);
+ data->ni = NULL;
+ data->totlen = 0;
+}
+
+void
+iwn_clear_oactive(struct iwn_softc *sc, struct iwn_tx_ring *ring)
+{
+ struct ieee80211com *ic = &sc->sc_ic;
+ struct ifnet *ifp = &ic->ic_if;
+
+ if (ring->queued < IWN_TX_RING_LOMARK) {
+ sc->qfullmsk &= ~(1 << ring->qid);
+ if (sc->qfullmsk == 0 && ifq_is_oactive(&ifp->if_snd)) {
+ ifq_clr_oactive(&ifp->if_snd);
+ (*ifp->if_start)(ifp);
+ }
+ }
}
/*
* Adapter-independent backend for TX_DONE firmware notifications.
+ * This handles Tx status for non-aggregation queues.
*/
void
-iwn_tx_done(struct iwn_softc *sc, struct iwn_rx_desc *desc, uint8_t nframes,
- uint8_t ackfailcnt, uint8_t status, uint16_t len)
+iwn_tx_done(struct iwn_softc *sc, struct iwn_rx_desc *desc,
+ uint8_t ackfailcnt, int txfail, int qid, uint16_t len)
{
struct ieee80211com *ic = &sc->sc_ic;
struct ifnet *ifp = &ic->ic_if;
- struct iwn_tx_ring *ring = &sc->txq[desc->qid & 0xf];
+ struct iwn_tx_ring *ring = &sc->txq[qid];
struct iwn_tx_data *data = &ring->data[desc->idx];
struct iwn_node *wn = (void *)data->ni;
- int txfail = (status != 1 && status != 2);
-
- KASSERT(nframes == 1); /* We don't support aggregation yet. */
/* Update rate control statistics. */
if (data->ni->ni_flags & IEEE80211_NODE_HT) {
- wn->mn.frames += nframes;
+ wn->mn.frames++;
wn->mn.ampdu_size = len;
- wn->mn.agglen = nframes;
+ wn->mn.agglen = 1;
if (ackfailcnt > 0)
wn->mn.retries += ackfailcnt;
if (txfail)
- wn->mn.txfail += nframes;
- if (ic->ic_state == IEEE80211_S_RUN)
+ wn->mn.txfail++;
+ if (ic->ic_state == IEEE80211_S_RUN) {
+ if (wn->mn.retries > wn->mn.frames)
+ wn->mn.retries = wn->mn.frames;
+ if (wn->mn.txfail > wn->mn.frames)
+ wn->mn.txfail = wn->mn.frames;
ieee80211_mira_choose(&wn->mn, ic, data->ni);
+ }
} else {
wn->amn.amn_txcnt++;
if (ackfailcnt > 0)
wn->amn.amn_retrycnt++;
}
- if (txfail) {
- DPRINTF(("%s: status=0x%x\n", __func__, status));
+ if (txfail)
ifp->if_oerrors++;
- }
- /* Unmap and free mbuf. */
- bus_dmamap_sync(sc->sc_dmat, data->map, 0, data->map->dm_mapsize,
- BUS_DMASYNC_POSTWRITE);
- bus_dmamap_unload(sc->sc_dmat, data->map);
- m_freem(data->m);
- data->m = NULL;
- ieee80211_release_node(ic, data->ni);
- data->ni = NULL;
+ iwn_tx_done_free_txdata(sc, data);
sc->sc_tx_timer = 0;
- if (--ring->queued < IWN_TX_RING_LOMARK) {
- sc->qfullmsk &= ~(1 << ring->qid);
- if (sc->qfullmsk == 0 && ifq_is_oactive(&ifp->if_snd)) {
- ifq_clr_oactive(&ifp->if_snd);
- (*ifp->if_start)(ifp);
- }
- }
+ ring->queued--;
+ iwn_clear_oactive(sc, ring);
}
/*
@@ -2885,6 +3125,12 @@ iwn4965_update_sched(struct iwn_softc *sc, int qid, int idx, uint8_t id,
}
void
+iwn4965_reset_sched(struct iwn_softc *sc, int qid, int idx)
+{
+ /* TBD */
+}
+
+void
iwn5000_update_sched(struct iwn_softc *sc, int qid, int idx, uint8_t id,
uint16_t len)
{
@@ -2935,6 +3181,7 @@ iwn_rval2ridx(int rval)
int
iwn_tx(struct iwn_softc *sc, struct mbuf *m, struct ieee80211_node *ni)
{
+ struct iwn_ops *ops = &sc->ops;
struct ieee80211com *ic = &sc->sc_ic;
struct iwn_node *wn = (void *)ni;
struct iwn_tx_ring *ring;
@@ -2946,29 +3193,50 @@ iwn_tx(struct iwn_softc *sc, struct mbuf *m, struct ieee80211_node *ni)
struct ieee80211_frame *wh;
struct ieee80211_key *k = NULL;
enum ieee80211_edca_ac ac;
+ int qid;
uint32_t flags;
uint16_t qos;
u_int hdrlen;
bus_dma_segment_t *seg;
- uint8_t *ivp, tid, ridx, txant, type;
+ uint8_t *ivp, tid, ridx, txant, type, subtype;
int i, totlen, hasqos, error, pad;
wh = mtod(m, struct ieee80211_frame *);
- hdrlen = ieee80211_get_hdrlen(wh);
type = wh->i_fc[0] & IEEE80211_FC0_TYPE_MASK;
+ subtype = wh->i_fc[0] & IEEE80211_FC0_SUBTYPE_MASK;
+ if (type == IEEE80211_FC0_TYPE_CTL)
+ hdrlen = sizeof(struct ieee80211_frame_min);
+ else
+ hdrlen = ieee80211_get_hdrlen(wh);
- /* Select EDCA Access Category and TX ring for this frame. */
if ((hasqos = ieee80211_has_qos(wh))) {
+ /* Select EDCA Access Category and TX ring for this frame. */
+ struct ieee80211_tx_ba *ba;
qos = ieee80211_get_qos(wh);
tid = qos & IEEE80211_QOS_TID;
ac = ieee80211_up_to_ac(ic, tid);
+ qid = ac;
+
+ /* If possible, put this frame on an aggregation queue. */
+ if (sc->sc_tx_ba[tid].wn == wn) {
+ ba = &ni->ni_tx_ba[tid];
+ if (!IEEE80211_IS_MULTICAST(wh->i_addr1) &&
+ ba->ba_state == IEEE80211_BA_AGREED) {
+ qid = sc->first_agg_txq + tid;
+ if (sc->qfullmsk & (1 << qid)) {
+ m_freem(m);
+ return ENOBUFS;
+ }
+ }
+ }
} else {
qos = 0;
- tid = 0;
+ tid = IWN_NONQOS_TID;
ac = EDCA_AC_BE;
+ qid = ac;
}
- ring = &sc->txq[ac];
+ ring = &sc->txq[qid];
desc = &ring->desc[ring->cur];
data = &ring->data[ring->cur];
@@ -3058,10 +3326,9 @@ iwn_tx(struct iwn_softc *sc, struct mbuf *m, struct ieee80211_node *ni)
IEEE80211_QOS_ACK_POLICY_NOACK)
flags |= IWN_TX_NEED_ACK;
}
- if ((wh->i_fc[0] &
- (IEEE80211_FC0_TYPE_MASK | IEEE80211_FC0_SUBTYPE_MASK)) ==
- (IEEE80211_FC0_TYPE_CTL | IEEE80211_FC0_SUBTYPE_BAR))
- flags |= IWN_TX_IMM_BA; /* Cannot happen yet. */
+ if (type == IEEE80211_FC0_TYPE_CTL &&
+ subtype == IEEE80211_FC0_SUBTYPE_BAR)
+ flags |= (IWN_TX_NEED_ACK | IWN_TX_IMM_BA);
if (wh->i_fc[1] & IEEE80211_FC1_MORE_FRAG)
flags |= IWN_TX_MORE_FRAG; /* Cannot happen yet. */
@@ -3101,8 +3368,6 @@ iwn_tx(struct iwn_softc *sc, struct mbuf *m, struct ieee80211_node *ni)
tx->id = wn->id;
if (type == IEEE80211_FC0_TYPE_MGT) {
- uint8_t subtype = wh->i_fc[0] & IEEE80211_FC0_SUBTYPE_MASK;
-
#ifndef IEEE80211_STA_ONLY
/* Tell HW to set timestamp in probe responses. */
if (subtype == IEEE80211_FC0_SUBTYPE_PROBE_RESP)
@@ -3178,7 +3443,8 @@ iwn_tx(struct iwn_softc *sc, struct mbuf *m, struct ieee80211_node *ni)
ivp[7] = k->k_tsc >> 40;
tx->security = IWN_CIPHER_CCMP;
- /* XXX flags |= IWN_TX_AMPDU_CCMP; */
+ if (qid >= sc->first_agg_txq)
+ flags |= IWN_TX_AMPDU_CCMP;
memcpy(tx->key, k->k_key, k->k_len);
/* TX scheduler includes CCMP MIC len w/5000 Series. */
@@ -3245,10 +3511,8 @@ iwn_tx(struct iwn_softc *sc, struct mbuf *m, struct ieee80211_node *ni)
(caddr_t)desc - ring->desc_dma.vaddr, sizeof (*desc),
BUS_DMASYNC_PREWRITE);
-#ifdef notyet
/* Update TX scheduler. */
ops->update_sched(sc, ring->qid, ring->cur, tx->id, totlen);
-#endif
/* Kick TX ring. */
ring->cur = (ring->cur + 1) % IWN_TX_RING_COUNT;
@@ -3277,6 +3541,7 @@ iwn_start(struct ifnet *ifp)
ifq_set_oactive(&ifp->if_snd);
break;
}
+
/* Send pending management frames first. */
m = mq_dequeue(&ic->ic_mgtq);
if (m != NULL) {
@@ -3399,6 +3664,7 @@ iwn_ioctl(struct ifnet *ifp, u_long cmd, caddr_t data)
int
iwn_cmd(struct iwn_softc *sc, int code, const void *buf, int size, int async)
{
+ struct iwn_ops *ops = &sc->ops;
struct iwn_tx_ring *ring = &sc->txq[4];
struct iwn_tx_desc *desc;
struct iwn_tx_data *data;
@@ -3461,10 +3727,8 @@ iwn_cmd(struct iwn_softc *sc, int code, const void *buf, int size, int async)
(caddr_t)desc - ring->desc_dma.vaddr, sizeof (*desc),
BUS_DMASYNC_PREWRITE);
-#ifdef notyet
/* Update TX scheduler. */
ops->update_sched(sc, ring->qid, ring->cur, 0, 0);
-#endif
/* Kick command ring. */
ring->cur = (ring->cur + 1) % IWN_TX_RING_COUNT;
@@ -5366,8 +5630,14 @@ iwn_ampdu_tx_start(struct ieee80211com *ic, struct ieee80211_node *ni,
struct iwn_ops *ops = &sc->ops;
struct iwn_node *wn = (void *)ni;
struct iwn_node_info node;
+ int qid = sc->first_agg_txq + tid;
int error;
+ /* Ensure we can map this TID to an aggregation queue. */
+ if (tid >= IWN_NUM_AMPDU_TID || ba->ba_winsize > IWN_SCHED_WINSZ ||
+ qid > sc->ntxqs || (sc->agg_queue_mask & (1 << qid)))
+ return ENOSPC;
+
/* Enable TX for the specified RA/TID. */
wn->disable_tid &= ~(1 << tid);
memset(&node, 0, sizeof node);
@@ -5383,6 +5653,11 @@ iwn_ampdu_tx_start(struct ieee80211com *ic, struct ieee80211_node *ni,
return error;
ops->ampdu_tx_start(sc, ni, tid, ba->ba_winstart);
iwn_nic_unlock(sc);
+
+ sc->agg_queue_mask |= (1 << qid);
+ sc->sc_tx_ba[tid].wn = wn;
+ ba->ba_bitmap = 0;
+
return 0;
}
@@ -5393,11 +5668,27 @@ iwn_ampdu_tx_stop(struct ieee80211com *ic, struct ieee80211_node *ni,
struct ieee80211_tx_ba *ba = &ni->ni_tx_ba[tid];
struct iwn_softc *sc = ic->ic_softc;
struct iwn_ops *ops = &sc->ops;
+ int qid = sc->first_agg_txq + tid;
+ struct iwn_node *wn = (void *)ni;
+ struct iwn_node_info node;
if (iwn_nic_lock(sc) != 0)
return;
ops->ampdu_tx_stop(sc, tid, ba->ba_winstart);
iwn_nic_unlock(sc);
+
+ sc->agg_queue_mask &= ~(1 << qid);
+ sc->sc_tx_ba[tid].wn = NULL;
+ ba->ba_bitmap = 0;
+
+ /* Disable TX for the specified RA/TID. */
+ wn->disable_tid |= (1 << tid);
+ memset(&node, 0, sizeof node);
+ node.id = wn->id;
+ node.control = IWN_NODE_UPDATE;
+ node.flags = IWN_FLAG_SET_DISABLE_TID;
+ node.disable_tid = htole16(wn->disable_tid);
+ ops->add_node(sc, &node, 1);
}
void
@@ -5405,7 +5696,8 @@ iwn4965_ampdu_tx_start(struct iwn_softc *sc, struct ieee80211_node *ni,
uint8_t tid, uint16_t ssn)
{
struct iwn_node *wn = (void *)ni;
- int qid = 7 + tid;
+ int qid = IWN4965_FIRST_AGG_TXQUEUE + tid;
+ uint16_t idx = IWN_AGG_SSN_TO_TXQ_IDX(ssn);
/* Stop TX scheduler while we're changing its configuration. */
iwn_prph_write(sc, IWN4965_SCHED_QUEUE_STATUS(qid),
@@ -5419,7 +5711,8 @@ iwn4965_ampdu_tx_start(struct iwn_softc *sc, struct ieee80211_node *ni,
iwn_prph_setbits(sc, IWN4965_SCHED_QCHAIN_SEL, 1 << qid);
/* Set starting sequence number from the ADDBA request. */
- IWN_WRITE(sc, IWN_HBUS_TARG_WRPTR, qid << 8 | (ssn & 0xff));
+ sc->txq[qid].cur = sc->txq[qid].read = idx;
+ IWN_WRITE(sc, IWN_HBUS_TARG_WRPTR, qid << 8 | idx);
iwn_prph_write(sc, IWN4965_SCHED_QUEUE_RDPTR(qid), ssn);
/* Set scheduler window size. */
@@ -5441,14 +5734,16 @@ iwn4965_ampdu_tx_start(struct iwn_softc *sc, struct ieee80211_node *ni,
void
iwn4965_ampdu_tx_stop(struct iwn_softc *sc, uint8_t tid, uint16_t ssn)
{
- int qid = 7 + tid;
+ int qid = IWN4965_FIRST_AGG_TXQUEUE + tid;
+ uint16_t idx = IWN_AGG_SSN_TO_TXQ_IDX(ssn);
/* Stop TX scheduler while we're changing its configuration. */
iwn_prph_write(sc, IWN4965_SCHED_QUEUE_STATUS(qid),
IWN4965_TXQ_STATUS_CHGACT);
/* Set starting sequence number from the ADDBA request. */
- IWN_WRITE(sc, IWN_HBUS_TARG_WRPTR, qid << 8 | (ssn & 0xff));
+ sc->txq[qid].cur = sc->txq[qid].read = idx;
+ IWN_WRITE(sc, IWN_HBUS_TARG_WRPTR, qid << 8 | idx);
iwn_prph_write(sc, IWN4965_SCHED_QUEUE_RDPTR(qid), ssn);
/* Disable interrupts for the queue. */
@@ -5463,8 +5758,9 @@ void
iwn5000_ampdu_tx_start(struct iwn_softc *sc, struct ieee80211_node *ni,
uint8_t tid, uint16_t ssn)
{
+ int qid = IWN5000_FIRST_AGG_TXQUEUE + tid;
+ int idx = IWN_AGG_SSN_TO_TXQ_IDX(ssn);
struct iwn_node *wn = (void *)ni;
- int qid = 10 + tid;
/* Stop TX scheduler while we're changing its configuration. */
iwn_prph_write(sc, IWN5000_SCHED_QUEUE_STATUS(qid),
@@ -5481,7 +5777,8 @@ iwn5000_ampdu_tx_start(struct iwn_softc *sc, struct ieee80211_node *ni,
iwn_prph_setbits(sc, IWN5000_SCHED_AGGR_SEL, 1 << qid);
/* Set starting sequence number from the ADDBA request. */
- IWN_WRITE(sc, IWN_HBUS_TARG_WRPTR, qid << 8 | (ssn & 0xff));
+ sc->txq[qid].cur = sc->txq[qid].read = idx;
+ IWN_WRITE(sc, IWN_HBUS_TARG_WRPTR, qid << 8 | idx);
iwn_prph_write(sc, IWN5000_SCHED_QUEUE_RDPTR(qid), ssn);
/* Set scheduler window size and frame limit. */
@@ -5499,7 +5796,8 @@ iwn5000_ampdu_tx_start(struct iwn_softc *sc, struct ieee80211_node *ni,
void
iwn5000_ampdu_tx_stop(struct iwn_softc *sc, uint8_t tid, uint16_t ssn)
{
- int qid = 10 + tid;
+ int qid = IWN5000_FIRST_AGG_TXQUEUE + tid;
+ int idx = IWN_AGG_SSN_TO_TXQ_IDX(ssn);
/* Stop TX scheduler while we're changing its configuration. */
iwn_prph_write(sc, IWN5000_SCHED_QUEUE_STATUS(qid),
@@ -5509,7 +5807,8 @@ iwn5000_ampdu_tx_stop(struct iwn_softc *sc, uint8_t tid, uint16_t ssn)
iwn_prph_clrbits(sc, IWN5000_SCHED_AGGR_SEL, 1 << qid);
/* Set starting sequence number from the ADDBA request. */
- IWN_WRITE(sc, IWN_HBUS_TARG_WRPTR, qid << 8 | (ssn & 0xff));
+ sc->txq[qid].cur = sc->txq[qid].read = idx;
+ IWN_WRITE(sc, IWN_HBUS_TARG_WRPTR, qid << 8 | idx);
iwn_prph_write(sc, IWN5000_SCHED_QUEUE_RDPTR(qid), ssn);
/* Disable interrupts for the queue. */
@@ -5729,6 +6028,9 @@ iwn5000_post_alive(struct iwn_softc *sc)
/* Set physical address of TX scheduler rings (1KB aligned). */
iwn_prph_write(sc, IWN5000_SCHED_DRAM_ADDR, sc->sched_dma.paddr >> 10);
+ /* Disable scheduler chain extension (enabled by default in HW). */
+ iwn_prph_write(sc, IWN5000_SCHED_CHAINEXT_EN, 0);
+
IWN_SETBITS(sc, IWN_FH_TX_CHICKEN, IWN_FH_TX_CHICKEN_SCHED_RETRY);
/* Enable chain mode for all queues, except command queue. */
@@ -6575,6 +6877,8 @@ iwn_init(struct ifnet *ifp)
int error;
memset(sc->bss_node_addr, 0, sizeof(sc->bss_node_addr));
+ sc->agg_queue_mask = 0;
+ memset(sc->sc_tx_ba, 0, sizeof(sc->sc_tx_ba));
if ((error = iwn_hw_prepare(sc)) != 0) {
printf("%s: hardware not ready\n", sc->sc_dev.dv_xname);
diff --git a/sys/dev/pci/if_iwnreg.h b/sys/dev/pci/if_iwnreg.h
index ded19f3c037..a8fc8504b7f 100644
--- a/sys/dev/pci/if_iwnreg.h
+++ b/sys/dev/pci/if_iwnreg.h
@@ -1,4 +1,4 @@
-/* $OpenBSD: if_iwnreg.h,v 1.55 2017/12/14 14:21:11 stsp Exp $ */
+/* $OpenBSD: if_iwnreg.h,v 1.56 2019/07/29 10:50:08 stsp Exp $ */
/*-
* Copyright (c) 2007, 2008
@@ -23,8 +23,10 @@
#define IWN_RX_RING_COUNT_LOG 6
#define IWN_RX_RING_COUNT (1 << IWN_RX_RING_COUNT_LOG)
-#define IWN4965_NTXQUEUES 16
-#define IWN5000_NTXQUEUES 20
+#define IWN4965_NTXQUEUES 16
+#define IWN4965_FIRST_AGG_TXQUEUE 7
+#define IWN5000_NTXQUEUES 20
+#define IWN5000_FIRST_AGG_TXQUEUE 10
#define IWN4965_NDMACHNLS 7
#define IWN5000_NDMACHNLS 8
@@ -123,6 +125,7 @@
#define IWN4965_SCHED_QUEUE_STATUS(qid) (IWN_SCHED_BASE + 0x104 + (qid) * 4)
#define IWN5000_SCHED_INTR_MASK (IWN_SCHED_BASE + 0x108)
#define IWN5000_SCHED_QUEUE_STATUS(qid) (IWN_SCHED_BASE + 0x10c + (qid) * 4)
+#define IWN5000_SCHED_CHAINEXT_EN (IWN_SCHED_BASE + 0x244)
#define IWN5000_SCHED_AGGR_SEL (IWN_SCHED_BASE + 0x248)
/*
@@ -685,6 +688,7 @@ struct iwn_cmd_data {
#define IWN_TX_MORE_FRAG (1 << 14)
#define IWN_TX_INSERT_TSTAMP (1 << 16)
#define IWN_TX_NEED_PADDING (1 << 20)
+#define IWN_TX_AMPDU_CCMP (1 << 22)
uint32_t scratch;
uint8_t plcp;
@@ -1188,6 +1192,62 @@ struct iwn_ucode_info {
} __packed;
/* Structures for IWN_TX_DONE notification. */
+
+/* Tx status for aggregated frames (A-MPDU). */
+struct iwn_txagg_status {
+ uint16_t status;
+#define IWN_AGG_TX_STATE_TRANSMITTED 0x0000
+#define IWN_AGG_TX_STATE_UNDERRUN 0x0001
+#define IWN_AGG_TX_STATE_BT_PRIO 0x0002
+#define IWN_AGG_TX_STATE_FEW_BYTES 0x0004
+#define IWN_AGG_TX_STATE_ABORT 0x0008
+#define IWN_AGG_TX_STATE_LAST_SENT_TTL 0x0010
+#define IWN_AGG_TX_STATE_LAST_SENT_TRY_CNT 0x0020
+#define IWN_AGG_TX_STATE_LAST_SENT_BT_KILL 0x0040
+#define IWN_AGG_TX_STATE_SCD_QUERY 0x0080
+#define IWN_AGG_TX_STATE_TEST_BAD_CRC32 0x0100
+#define IWN_AGG_TX_STATE_RESPONSE_MASK 0x01ff
+#define IWN_AGG_TX_STATE_DUMP_TX 0x0200
+#define IWN_AGG_TX_STATE_DELAY_TX 0x0400
+#define IWN_AGG_TX_STATUS_MASK 0x0fff
+/* Number of TX attempts for first frame in aggregation: */
+#define IWN_AGG_TX_TRY 0xf000
+#define IWN_AGG_TX_TRY_SHIFT 12
+
+ /* Copied from Tx command we have sent to the firmware. */
+ uint8_t idx;
+ uint8_t qid;
+} __packed;
+
+/* For aggregation queues, index must be aligned to frame sequence number. */
+#define IWN_AGG_SSN_TO_TXQ_IDX(x) ((x) & (IWN_TX_RING_COUNT - 1))
+
+/* Tx status codes for non-aggregated frames. */
+#define IWN_TX_STATUS_SUCCESS 0x01
+#define IWN_TX_STATUS_DIRECT_DONE 0x02
+#define IWN_TX_STATUS_POSTPONE_DELAY 0x40
+#define IWN_TX_STATUS_POSTPONE_FEW_BYTES 0x41
+#define IWN_TX_STATUS_POSTPONE_BT_PRIO 0x42
+#define IWN_TX_STATUS_POSTPONE_QUIET_PERIOD 0x43
+#define IWN_TX_STATUS_POSTPONE_CALC_TTAK 0x44
+#define IWN_TX_STATUS_FAIL_CROSSED_RETRY 0x81
+#define IWN_TX_STATUS_FAIL_SHORT_LIMIT 0x82
+#define IWN_TX_STATUS_FAIL_LONG_LIMIT 0x83
+#define IWN_TX_STATUS_FAIL_FIFO_UNDERRUN 0x84
+#define IWN_TX_STATUS_FAIL_DRAIN_FLOW 0x85
+#define IWN_TX_STATUS_FAIL_RFKILL_FLUSH 0x86
+#define IWN_TX_STATUS_FAIL_LIFE_EXPIRE 0x87
+#define IWN_TX_STATUS_FAIL_DEST_PS 0x88
+#define IWN_TX_STATUS_FAIL_HOST_ABORTED 0x89
+#define IWN_TX_STATUS_FAIL_BT_RETRY 0x8a
+#define IWN_TX_STATUS_FAIL_STA_INVALID 0x8b
+#define IWN_TX_STATUS_FAIL_FRAG_DROPPED 0x8c
+#define IWN_TX_STATUS_FAIL_TID_DISABLE 0x8d
+#define IWN_TX_STATUS_FAIL_FIFO_FLUSHED 0x8e
+#define IWN_TX_STATUS_FAIL_INSUFFICIENT_CF_POLL 0x8f
+#define IWN_TX_STATUS_FAIL_PASSIVE_NO_RX 0x90
+#define IWN_TX_STATUS_FAIL_NO_BEACON_ON_RADAR 0x91
+
struct iwn4965_tx_stat {
uint8_t nframes;
uint8_t btkillcnt;
@@ -1199,7 +1259,11 @@ struct iwn4965_tx_stat {
uint16_t duration;
uint16_t reserved;
uint32_t power[2];
- uint32_t status;
+ union {
+ uint32_t status; /* if nframes == 1 */
+ struct iwn_txagg_status agg_status[0]; /* nframes elements */
+ } stat;
+ /* Followed by current scheduler SSN (uint32_t). */
} __packed;
struct iwn5000_tx_stat {
@@ -1218,9 +1282,16 @@ struct iwn5000_tx_stat {
uint16_t len;
uint8_t tlc;
uint8_t ratid;
+#define IWN_TX_RES_TID_SHIFT 0
+#define IWN_TX_RES_TID 0x0f
+#define IWN_TX_RES_RA_SHIFT 4
+#define IWN_TX_RES_RA 0xf0
uint8_t fc[2];
- uint16_t status;
- uint16_t sequence;
+ union {
+ uint32_t status; /* if nframes == 1 */
+ struct iwn_txagg_status agg_status[0]; /* nframes elements */
+ } stat;
+ /* Followed by current scheduler SSN (uint32_t). */
} __packed;
/* Structure for IWN_BEACON_MISSED notification. */
@@ -1288,6 +1359,9 @@ struct iwn_compressed_ba {
uint64_t bitmap;
uint16_t qid;
uint16_t ssn;
+ uint8_t nframes_sent;
+ uint8_t nframes_acked;
+ uint16_t reserved2;
} __packed;
/* Structure for IWN_START_SCAN notification. */
@@ -1926,8 +2000,15 @@ static const struct iwn_sensitivity_limits iwn2000_sensitivity_limits = {
};
/* Map TID to TX scheduler's FIFO. */
-static const uint8_t iwn_tid2fifo[] = {
- 1, 0, 0, 1, 2, 2, 3, 3, 7, 7, 7, 7, 7, 7, 7, 7, 3
+#define IWN_NUM_AMPDU_TID 8
+#define IWN_NONQOS_TID IWN_NUM_AMPDU_TID
+#define IWN_TX_FIFO_BK 0
+#define IWN_TX_FIFO_BE 1
+#define IWN_TX_FIFO_VI 2
+#define IWN_TX_FIFO_VO 3
+static const uint8_t iwn_tid2fifo[IWN_NUM_AMPDU_TID] = {
+ IWN_TX_FIFO_VO, IWN_TX_FIFO_VI, IWN_TX_FIFO_BE, IWN_TX_FIFO_BK,
+ IWN_TX_FIFO_VO, IWN_TX_FIFO_VI, IWN_TX_FIFO_BE, IWN_TX_FIFO_BK
};
/* WiFi/WiMAX coexist event priority table for 6050. */
diff --git a/sys/dev/pci/if_iwnvar.h b/sys/dev/pci/if_iwnvar.h
index fd9616f0258..206d15fb356 100644
--- a/sys/dev/pci/if_iwnvar.h
+++ b/sys/dev/pci/if_iwnvar.h
@@ -1,4 +1,4 @@
-/* $OpenBSD: if_iwnvar.h,v 1.34 2018/02/25 12:40:06 stsp Exp $ */
+/* $OpenBSD: if_iwnvar.h,v 1.35 2019/07/29 10:50:08 stsp Exp $ */
/*-
* Copyright (c) 2007, 2008
@@ -78,6 +78,7 @@ struct iwn_tx_ring {
int qid;
int queued;
int cur;
+ int read;
};
struct iwn_softc;
@@ -163,6 +164,7 @@ struct iwn_ops {
void (*read_eeprom)(struct iwn_softc *);
int (*post_alive)(struct iwn_softc *);
int (*nic_config)(struct iwn_softc *);
+ void (*reset_sched)(struct iwn_softc *, int, int);
void (*update_sched)(struct iwn_softc *, int, int, uint8_t,
uint16_t);
int (*get_temperature)(struct iwn_softc *);
@@ -180,6 +182,10 @@ struct iwn_ops {
uint16_t);
};
+struct iwn_tx_ba {
+ struct iwn_node * wn;
+};
+
struct iwn_softc {
struct device sc_dev;
@@ -212,6 +218,8 @@ struct iwn_softc {
const struct iwn_sensitivity_limits
*limits;
int ntxqs;
+ int first_agg_txq;
+ int agg_queue_mask;
int ndmachnls;
uint8_t broadcast_id;
int rxonsz;
@@ -302,6 +310,8 @@ struct iwn_softc {
int sc_tx_timer;
+ struct iwn_tx_ba sc_tx_ba[IEEE80211_NUM_TID];
+
#if NBPFILTER > 0
caddr_t sc_drvbpf;
diff --git a/sys/net80211/ieee80211.c b/sys/net80211/ieee80211.c
index a46b07c1306..9424dd2a492 100644
--- a/sys/net80211/ieee80211.c
+++ b/sys/net80211/ieee80211.c
@@ -1,4 +1,4 @@
-/* $OpenBSD: ieee80211.c,v 1.76 2019/06/10 16:33:02 stsp Exp $ */
+/* $OpenBSD: ieee80211.c,v 1.77 2019/07/29 10:50:08 stsp Exp $ */
/* $NetBSD: ieee80211.c,v 1.19 2004/06/06 05:45:29 dyoung Exp $ */
/*-
@@ -67,6 +67,7 @@ int ieee80211_cache_size = IEEE80211_CACHE_SIZE;
void ieee80211_setbasicrates(struct ieee80211com *);
int ieee80211_findrate(struct ieee80211com *, enum ieee80211_phymode, int);
+void ieee80211_configure_ampdu_tx(struct ieee80211com *, int);
void
ieee80211_begin_bgscan(struct ifnet *ifp)
@@ -74,7 +75,7 @@ ieee80211_begin_bgscan(struct ifnet *ifp)
struct ieee80211com *ic = (void *)ifp;
if ((ic->ic_flags & IEEE80211_F_BGSCAN) ||
- ic->ic_state != IEEE80211_S_RUN)
+ ic->ic_state != IEEE80211_S_RUN || ic->ic_mgt_timer != 0)
return;
if (ic->ic_bgscan_start != NULL && ic->ic_bgscan_start(ic) == 0) {
@@ -274,6 +275,22 @@ ieee80211_ieee2mhz(u_int chan, u_int flags)
}
}
+void
+ieee80211_configure_ampdu_tx(struct ieee80211com *ic, int enable)
+{
+ if ((ic->ic_caps & IEEE80211_C_TX_AMPDU) == 0)
+ return;
+
+ /* Sending AMPDUs requires QoS support. */
+ if ((ic->ic_caps & IEEE80211_C_QOS) == 0)
+ return;
+
+ if (enable)
+ ic->ic_flags |= IEEE80211_F_QOS;
+ else
+ ic->ic_flags &= ~IEEE80211_F_QOS;
+}
+
/*
* Setup the media data structures according to the channel and
* rate tables. This must be called by the driver after
@@ -409,6 +426,7 @@ ieee80211_media_init(struct ifnet *ifp,
mopt | IFM_IEEE80211_MONITOR);
}
ic->ic_flags |= IEEE80211_F_HTON; /* enable 11n by default */
+ ieee80211_configure_ampdu_tx(ic, 1);
}
if (ic->ic_modecaps & (1 << IEEE80211_MODE_11AC)) {
@@ -443,6 +461,8 @@ ieee80211_media_init(struct ifnet *ifp,
}
#if 0
ic->ic_flags |= IEEE80211_F_VHTON; /* enable 11ac by default */
+ if (ic->ic_caps & IEEE80211_C_QOS)
+ ic->ic_flags |= IEEE80211_F_QOS;
#endif
}
@@ -625,14 +645,18 @@ ieee80211_media_change(struct ifnet *ifp)
* Committed to changes, install the MCS/rate setting.
*/
ic->ic_flags &= ~(IEEE80211_F_HTON | IEEE80211_F_VHTON);
+ ieee80211_configure_ampdu_tx(ic, 0);
if ((ic->ic_modecaps & (1 << IEEE80211_MODE_11AC)) &&
(newphymode == IEEE80211_MODE_AUTO ||
- newphymode == IEEE80211_MODE_11AC))
+ newphymode == IEEE80211_MODE_11AC)) {
ic->ic_flags |= IEEE80211_F_VHTON;
- else if ((ic->ic_modecaps & (1 << IEEE80211_MODE_11N)) &&
+ ieee80211_configure_ampdu_tx(ic, 1);
+ } else if ((ic->ic_modecaps & (1 << IEEE80211_MODE_11N)) &&
(newphymode == IEEE80211_MODE_AUTO ||
- newphymode == IEEE80211_MODE_11N))
+ newphymode == IEEE80211_MODE_11N)) {
ic->ic_flags |= IEEE80211_F_HTON;
+ ieee80211_configure_ampdu_tx(ic, 1);
+ }
if ((ic->ic_flags & (IEEE80211_F_HTON | IEEE80211_F_VHTON)) == 0) {
ic->ic_fixed_mcs = -1;
if (ic->ic_fixed_rate != i) {
diff --git a/sys/net80211/ieee80211_input.c b/sys/net80211/ieee80211_input.c
index c118ba764e4..31842d7d3ff 100644
--- a/sys/net80211/ieee80211_input.c
+++ b/sys/net80211/ieee80211_input.c
@@ -1,4 +1,4 @@
-/* $OpenBSD: ieee80211_input.c,v 1.206 2019/05/12 18:12:38 stsp Exp $ */
+/* $OpenBSD: ieee80211_input.c,v 1.207 2019/07/29 10:50:08 stsp Exp $ */
/*-
* Copyright (c) 2001 Atsushi Onoe
@@ -1915,7 +1915,7 @@ ieee80211_recv_assoc_req(struct ieee80211com *ic, struct mbuf *m,
{
const struct ieee80211_frame *wh;
const u_int8_t *frm, *efrm;
- const u_int8_t *ssid, *rates, *xrates, *rsnie, *wpaie, *htcaps;
+ const u_int8_t *ssid, *rates, *xrates, *rsnie, *wpaie, *wmeie, *htcaps;
u_int16_t capinfo, bintval;
int resp, status = 0;
struct ieee80211_rsnparams rsn;
@@ -1949,7 +1949,7 @@ ieee80211_recv_assoc_req(struct ieee80211com *ic, struct mbuf *m,
} else
resp = IEEE80211_FC0_SUBTYPE_ASSOC_RESP;
- ssid = rates = xrates = rsnie = wpaie = htcaps = NULL;
+ ssid = rates = xrates = rsnie = wpaie = wmeie = htcaps = NULL;
while (frm + 2 <= efrm) {
if (frm + 2 + frm[1] > efrm) {
ic->ic_stats.is_rx_elem_toosmall++;
@@ -1981,6 +1981,9 @@ ieee80211_recv_assoc_req(struct ieee80211com *ic, struct mbuf *m,
if (memcmp(frm + 2, MICROSOFT_OUI, 3) == 0) {
if (frm[5] == 1)
wpaie = frm;
+ /* WME info IE: len=7 type=2 subtype=0 */
+ if (frm[1] == 7 && frm[5] == 2 && frm[6] == 0)
+ wmeie = frm;
}
break;
}
@@ -2095,6 +2098,13 @@ ieee80211_recv_assoc_req(struct ieee80211com *ic, struct mbuf *m,
}
}
+ if (ic->ic_flags & IEEE80211_F_QOS) {
+ if (wmeie != NULL)
+ ni->ni_flags |= IEEE80211_NODE_QOS;
+ else /* for Reassociation */
+ ni->ni_flags &= ~IEEE80211_NODE_QOS;
+ }
+
if (ic->ic_flags & IEEE80211_F_RSNON) {
if (ni->ni_rsnprotos == IEEE80211_PROTO_NONE) {
/*
@@ -2588,7 +2598,16 @@ ieee80211_recv_addba_req(struct ieee80211com *ic, struct mbuf *m,
ba->ba_winsize = IEEE80211_BA_MAX_WINSZ;
ba->ba_params = (params & IEEE80211_ADDBA_BA_POLICY);
ba->ba_params |= ((ba->ba_winsize << IEEE80211_ADDBA_BUFSZ_SHIFT) |
- (tid << IEEE80211_ADDBA_TID_SHIFT) | IEEE80211_ADDBA_AMSDU);
+ (tid << IEEE80211_ADDBA_TID_SHIFT));
+#if 0
+ /*
+ * XXX A-MSDUs inside A-MPDUs expose a problem with bad TCP connection
+ * sharing behaviour. One connection eats all available bandwidth
+ * while others stall. Leave this disabled for now to give packets
+ * from disparate connections better chances of interleaving.
+ */
+ ba->ba_params |= IEEE80211_ADDBA_AMSDU;
+#endif
ba->ba_winstart = ssn;
ba->ba_winend = (ba->ba_winstart + ba->ba_winsize - 1) & 0xfff;
/* allocate and setup our reordering buffer */
@@ -2671,6 +2690,7 @@ ieee80211_recv_addba_resp(struct ieee80211com *ic, struct mbuf *m,
struct ieee80211_tx_ba *ba;
u_int16_t status, params, bufsz, timeout;
u_int8_t token, tid;
+ int err = 0;
if (m->m_len < sizeof(*wh) + 9) {
DPRINTF(("frame too short\n"));
@@ -2707,23 +2727,65 @@ ieee80211_recv_addba_resp(struct ieee80211com *ic, struct mbuf *m,
timeout_del(&ba->ba_to);
if (status != IEEE80211_STATUS_SUCCESS) {
- /* MLME-ADDBA.confirm(Failure) */
- ba->ba_state = IEEE80211_BA_INIT;
+ if (ni->ni_addba_req_intval[tid] <
+ IEEE80211_ADDBA_REQ_INTVAL_MAX)
+ ni->ni_addba_req_intval[tid]++;
+
+ ieee80211_addba_resp_refuse(ic, ni, tid, status);
+
+ /*
+ * In case the peer believes there is an existing
+ * block ack agreement with us, try to delete it.
+ */
+ IEEE80211_SEND_ACTION(ic, ni, IEEE80211_CATEG_BA,
+ IEEE80211_ACTION_DELBA,
+ IEEE80211_REASON_SETUP_REQUIRED << 16 | 1 << 8 | tid);
return;
}
+
+ /* notify drivers of this new Block Ack agreement */
+ if (ic->ic_ampdu_tx_start != NULL)
+ err = ic->ic_ampdu_tx_start(ic, ni, tid);
+
+ if (err == EBUSY) {
+ /* driver will accept or refuse agreement when done */
+ return;
+ } else if (err) {
+ /* driver failed to setup, rollback */
+ ieee80211_addba_resp_refuse(ic, ni, tid,
+ IEEE80211_STATUS_UNSPECIFIED);
+ } else
+ ieee80211_addba_resp_accept(ic, ni, tid);
+}
+
+void
+ieee80211_addba_resp_accept(struct ieee80211com *ic,
+ struct ieee80211_node *ni, uint8_t tid)
+{
+ struct ieee80211_tx_ba *ba = &ni->ni_tx_ba[tid];
+
/* MLME-ADDBA.confirm(Success) */
ba->ba_state = IEEE80211_BA_AGREED;
ic->ic_stats.is_ht_tx_ba_agreements++;
- /* notify drivers of this new Block Ack agreement */
- if (ic->ic_ampdu_tx_start != NULL)
- (void)ic->ic_ampdu_tx_start(ic, ni, tid);
+ /* Reset ADDBA request interval. */
+ ni->ni_addba_req_intval[tid] = 1;
/* start Block Ack inactivity timeout */
if (ba->ba_timeout_val != 0)
timeout_add_usec(&ba->ba_to, ba->ba_timeout_val);
}
+void
+ieee80211_addba_resp_refuse(struct ieee80211com *ic,
+ struct ieee80211_node *ni, uint8_t tid, uint16_t status)
+{
+ struct ieee80211_tx_ba *ba = &ni->ni_tx_ba[tid];
+
+ /* MLME-ADDBA.confirm(Failure) */
+ ba->ba_state = IEEE80211_BA_INIT;
+}
+
/*-
* DELBA frame format:
* [1] Category
diff --git a/sys/net80211/ieee80211_mira.c b/sys/net80211/ieee80211_mira.c
index 8a0a697dc09..4438af5f63e 100644
--- a/sys/net80211/ieee80211_mira.c
+++ b/sys/net80211/ieee80211_mira.c
@@ -1,4 +1,4 @@
-/* $OpenBSD: ieee80211_mira.c,v 1.15 2019/06/18 21:04:52 stsp Exp $ */
+/* $OpenBSD: ieee80211_mira.c,v 1.16 2019/07/29 10:50:09 stsp Exp $ */
/*
* Copyright (c) 2016 Stefan Sperling <stsp@openbsd.org>
@@ -508,7 +508,7 @@ ieee80211_mira_reset_driver_stats(struct ieee80211_mira_node *mn)
#define IEEE80211_MIRA_MIN_PROBE_FRAMES 4
/* Number of bytes which, alternatively, render a probe valid. */
-#define IEEE80211_MIRA_MIN_PROBE_BYTES IEEE80211_MAX_LEN
+#define IEEE80211_MIRA_MIN_PROBE_BYTES (2 * IEEE80211_MAX_LEN)
/* Number of Tx failures which, alternatively, render a probe valid. */
#define IEEE80211_MIRA_MAX_PROBE_TXFAIL 1
diff --git a/sys/net80211/ieee80211_node.c b/sys/net80211/ieee80211_node.c
index 925c7347070..ec3e0f61e96 100644
--- a/sys/net80211/ieee80211_node.c
+++ b/sys/net80211/ieee80211_node.c
@@ -1,4 +1,4 @@
-/* $OpenBSD: ieee80211_node.c,v 1.169 2019/07/23 18:56:23 stsp Exp $ */
+/* $OpenBSD: ieee80211_node.c,v 1.170 2019/07/29 10:50:09 stsp Exp $ */
/* $NetBSD: ieee80211_node.c,v 1.14 2004/05/09 09:18:47 dyoung Exp $ */
/*-
@@ -69,13 +69,18 @@ int ieee80211_node_checkrssi(struct ieee80211com *,
const struct ieee80211_node *);
int ieee80211_ess_is_better(struct ieee80211com *ic, struct ieee80211_node *,
struct ieee80211_node *);
+void ieee80211_node_set_timeouts(struct ieee80211_node *);
void ieee80211_setup_node(struct ieee80211com *, struct ieee80211_node *,
const u_int8_t *);
void ieee80211_free_node(struct ieee80211com *, struct ieee80211_node *);
-void ieee80211_ba_del(struct ieee80211_node *);
struct ieee80211_node *ieee80211_alloc_node_helper(struct ieee80211com *);
void ieee80211_node_cleanup(struct ieee80211com *, struct ieee80211_node *);
void ieee80211_node_switch_bss(struct ieee80211com *, struct ieee80211_node *);
+void ieee80211_node_addba_request(struct ieee80211_node *, int);
+void ieee80211_node_addba_request_ac_be_to(void *);
+void ieee80211_node_addba_request_ac_bk_to(void *);
+void ieee80211_node_addba_request_ac_vi_to(void *);
+void ieee80211_node_addba_request_ac_vo_to(void *);
void ieee80211_needs_auth(struct ieee80211com *, struct ieee80211_node *);
#ifndef IEEE80211_STA_ONLY
void ieee80211_node_join_ht(struct ieee80211com *, struct ieee80211_node *);
@@ -1386,9 +1391,11 @@ ieee80211_end_scan(struct ifnet *ifp)
ic->ic_bgscan_fail = 0;
/*
- * We are going to switch APs.
- * Queue a de-auth frame addressed to our current AP.
+ * We are going to switch APs. Stop A-MPDU Tx and
+ * queue a de-auth frame addressed to our current AP.
*/
+ ieee80211_stop_ampdu_tx(ic, ic->ic_bss,
+ IEEE80211_FC0_SUBTYPE_DEAUTH);
if (IEEE80211_SEND_MGMT(ic, ic->ic_bss,
IEEE80211_FC0_SUBTYPE_DEAUTH,
IEEE80211_REASON_AUTH_LEAVE) != 0) {
@@ -1528,6 +1535,10 @@ ieee80211_node_copy(struct ieee80211com *ic,
dst->ni_rsnie = NULL;
if (src->ni_rsnie != NULL)
ieee80211_save_ie(src->ni_rsnie, &dst->ni_rsnie);
+ ieee80211_node_set_timeouts(dst);
+#ifndef IEEE80211_STA_ONLY
+ mq_init(&dst->ni_savedq, IEEE80211_PS_MAX_QUEUE, IPL_NET);
+#endif
}
u_int8_t
@@ -1560,6 +1571,27 @@ ieee80211_node_checkrssi(struct ieee80211com *ic,
}
void
+ieee80211_node_set_timeouts(struct ieee80211_node *ni)
+{
+ int i;
+
+#ifndef IEEE80211_STA_ONLY
+ timeout_set(&ni->ni_eapol_to, ieee80211_eapol_timeout, ni);
+ timeout_set(&ni->ni_sa_query_to, ieee80211_sa_query_timeout, ni);
+#endif
+ timeout_set(&ni->ni_addba_req_to[EDCA_AC_BE],
+ ieee80211_node_addba_request_ac_be_to, ni);
+ timeout_set(&ni->ni_addba_req_to[EDCA_AC_BK],
+ ieee80211_node_addba_request_ac_bk_to, ni);
+ timeout_set(&ni->ni_addba_req_to[EDCA_AC_VI],
+ ieee80211_node_addba_request_ac_vi_to, ni);
+ timeout_set(&ni->ni_addba_req_to[EDCA_AC_VO],
+ ieee80211_node_addba_request_ac_vo_to, ni);
+ for (i = 0; i < nitems(ni->ni_addba_req_intval); i++)
+ ni->ni_addba_req_intval[i] = 1;
+}
+
+void
ieee80211_setup_node(struct ieee80211com *ic,
struct ieee80211_node *ni, const u_int8_t *macaddr)
{
@@ -1576,9 +1608,9 @@ ieee80211_setup_node(struct ieee80211com *ic,
ni->ni_qos_rxseqs[i] = 0xffffU;
#ifndef IEEE80211_STA_ONLY
mq_init(&ni->ni_savedq, IEEE80211_PS_MAX_QUEUE, IPL_NET);
- timeout_set(&ni->ni_eapol_to, ieee80211_eapol_timeout, ni);
- timeout_set(&ni->ni_sa_query_to, ieee80211_sa_query_timeout, ni);
#endif
+ ieee80211_node_set_timeouts(ni);
+
s = splnet();
RBT_INSERT(ieee80211_tree, &ic->ic_tree, ni);
ic->ic_nnodes++;
@@ -1868,6 +1900,11 @@ ieee80211_ba_del(struct ieee80211_node *ni)
ba->ba_state = IEEE80211_BA_INIT;
}
}
+
+ timeout_del(&ni->ni_addba_req_to[EDCA_AC_BE]);
+ timeout_del(&ni->ni_addba_req_to[EDCA_AC_BK]);
+ timeout_del(&ni->ni_addba_req_to[EDCA_AC_VI]);
+ timeout_del(&ni->ni_addba_req_to[EDCA_AC_VO]);
}
void
@@ -2225,6 +2262,53 @@ ieee80211_setup_rates(struct ieee80211com *ic, struct ieee80211_node *ni,
return ieee80211_fix_rate(ic, ni, flags);
}
+void
+ieee80211_node_trigger_addba_req(struct ieee80211_node *ni, int tid)
+{
+ if (ni->ni_tx_ba[tid].ba_state == IEEE80211_BA_INIT &&
+ !timeout_pending(&ni->ni_addba_req_to[tid])) {
+ timeout_add_sec(&ni->ni_addba_req_to[tid],
+ ni->ni_addba_req_intval[tid]);
+ }
+}
+
+void
+ieee80211_node_addba_request(struct ieee80211_node *ni, int tid)
+{
+ struct ieee80211com *ic = ni->ni_ic;
+ uint16_t ssn = ni->ni_qos_txseqs[tid];
+
+ ieee80211_addba_request(ic, ni, ssn, tid);
+}
+
+void
+ieee80211_node_addba_request_ac_be_to(void *arg)
+{
+ struct ieee80211_node *ni = arg;
+ ieee80211_node_addba_request(ni, EDCA_AC_BE);
+}
+
+void
+ieee80211_node_addba_request_ac_bk_to(void *arg)
+{
+ struct ieee80211_node *ni = arg;
+ ieee80211_node_addba_request(ni, EDCA_AC_BK);
+}
+
+void
+ieee80211_node_addba_request_ac_vi_to(void *arg)
+{
+ struct ieee80211_node *ni = arg;
+ ieee80211_node_addba_request(ni, EDCA_AC_VI);
+}
+
+void
+ieee80211_node_addba_request_ac_vo_to(void *arg)
+{
+ struct ieee80211_node *ni = arg;
+ ieee80211_node_addba_request(ni, EDCA_AC_VO);
+}
+
#ifndef IEEE80211_STA_ONLY
/*
* Check if the specified node supports ERP.
diff --git a/sys/net80211/ieee80211_node.h b/sys/net80211/ieee80211_node.h
index 0b891938db0..af6ad346c60 100644
--- a/sys/net80211/ieee80211_node.h
+++ b/sys/net80211/ieee80211_node.h
@@ -1,4 +1,4 @@
-/* $OpenBSD: ieee80211_node.h,v 1.80 2019/03/01 08:13:11 stsp Exp $ */
+/* $OpenBSD: ieee80211_node.h,v 1.81 2019/07/29 10:50:09 stsp Exp $ */
/* $NetBSD: ieee80211_node.h,v 1.9 2004/04/30 22:57:32 dyoung Exp $ */
/*-
@@ -204,6 +204,9 @@ struct ieee80211_tx_ba {
#define IEEE80211_BA_MAX_WINSZ 64 /* corresponds to maximum ADDBA BUFSZ */
u_int8_t ba_token;
+
+ /* Bitmap for ACK'd frames in the current BA window. */
+ uint64_t ba_bitmap;
};
struct ieee80211_rx_ba {
@@ -330,6 +333,11 @@ struct ieee80211_node {
uint16_t ni_htop2;
uint8_t ni_basic_mcs[howmany(128,NBBY)];
+ /* Timeout handlers which trigger Tx Block Ack negotiation. */
+ struct timeout ni_addba_req_to[IEEE80211_NUM_TID];
+ int ni_addba_req_intval[IEEE80211_NUM_TID];
+#define IEEE80211_ADDBA_REQ_INTVAL_MAX 30 /* in seconds */
+
/* Block Ack records */
struct ieee80211_tx_ba ni_tx_ba[IEEE80211_NUM_TID];
struct ieee80211_rx_ba ni_rx_ba[IEEE80211_NUM_TID];
@@ -472,6 +480,7 @@ struct ieee80211_node *ieee80211_dup_bss(struct ieee80211com *,
const u_int8_t *);
struct ieee80211_node *ieee80211_find_node(struct ieee80211com *,
const u_int8_t *);
+void ieee80211_ba_del(struct ieee80211_node *);
struct ieee80211_node *ieee80211_find_rxnode(struct ieee80211com *,
const struct ieee80211_frame *);
struct ieee80211_node *ieee80211_find_txnode(struct ieee80211com *,
@@ -494,6 +503,7 @@ int ieee80211_setup_htop(struct ieee80211_node *, const uint8_t *,
uint8_t, int);
int ieee80211_setup_rates(struct ieee80211com *,
struct ieee80211_node *, const u_int8_t *, const u_int8_t *, int);
+void ieee80211_node_trigger_addba_req(struct ieee80211_node *, int);
int ieee80211_iserp_sta(const struct ieee80211_node *);
void ieee80211_count_longslotsta(void *, struct ieee80211_node *);
void ieee80211_count_nonerpsta(void *, struct ieee80211_node *);
diff --git a/sys/net80211/ieee80211_output.c b/sys/net80211/ieee80211_output.c
index d90af79e3f7..37cf81c8f2e 100644
--- a/sys/net80211/ieee80211_output.c
+++ b/sys/net80211/ieee80211_output.c
@@ -1,4 +1,4 @@
-/* $OpenBSD: ieee80211_output.c,v 1.125 2019/05/12 18:12:38 stsp Exp $ */
+/* $OpenBSD: ieee80211_output.c,v 1.126 2019/07/29 10:50:09 stsp Exp $ */
/* $NetBSD: ieee80211_output.c,v 1.13 2004/05/31 11:02:55 dyoung Exp $ */
/*-
@@ -65,6 +65,8 @@
int ieee80211_mgmt_output(struct ifnet *, struct ieee80211_node *,
struct mbuf *, int);
+int ieee80211_can_use_ampdu(struct ieee80211com *,
+ struct ieee80211_node *);
u_int8_t *ieee80211_add_rsn_body(u_int8_t *, struct ieee80211com *,
const struct ieee80211_node *, int);
struct mbuf *ieee80211_getmgmt(int, int, u_int);
@@ -426,21 +428,56 @@ ieee80211_classify(struct ieee80211com *ic, struct mbuf *m)
*/
switch (ds_field & 0xfc) {
case IPTOS_PREC_PRIORITY:
- return 2;
+ return EDCA_AC_VI;
case IPTOS_PREC_IMMEDIATE:
- return 1;
+ return EDCA_AC_BK;
case IPTOS_PREC_FLASH:
- return 3;
case IPTOS_PREC_FLASHOVERRIDE:
- return 4;
case IPTOS_PREC_CRITIC_ECP:
- return 5;
case IPTOS_PREC_INTERNETCONTROL:
- return 6;
case IPTOS_PREC_NETCONTROL:
- return 7;
+ return EDCA_AC_VO;
+ default:
+ return EDCA_AC_BE;
}
- return 0; /* default to Best-Effort */
+}
+
+int
+ieee80211_can_use_ampdu(struct ieee80211com *ic, struct ieee80211_node *ni)
+{
+ return (ni->ni_flags & IEEE80211_NODE_HT) &&
+ (ic->ic_caps & IEEE80211_C_TX_AMPDU) &&
+ !(ic->ic_opmode == IEEE80211_M_STA && ni != ic->ic_bss) &&
+ /*
+ * Don't use A-MPDU on non-encrypted networks. There are devices
+ * with buggy firmware which allow an attacker to inject 802.11
+ * frames into a wifi network by embedding rouge A-MPDU subframes
+ * in an arbitrary data payload (e.g. PNG images) which may end
+ * up appearing as actual frames after de-aggregation by a buggy
+ * device; see https://github.com/rpp0/aggr-inject for details.
+ * WPA2 prevents this injection attack since the attacker would
+ * need to inject frames which get decrypted correctly.
+ */
+ ((ic->ic_flags & IEEE80211_F_RSNON) &&
+ (ni->ni_rsnprotos & IEEE80211_PROTO_RSN));
+}
+
+void
+ieee80211_tx_compressed_bar(struct ieee80211com *ic, struct ieee80211_node *ni,
+ int tid, uint16_t ssn)
+{
+ struct ifnet *ifp = &ic->ic_if;
+ struct mbuf *m;
+
+ m = ieee80211_get_compressed_bar(ic, ni, tid, ssn);
+ if (m == NULL)
+ return;
+
+ ieee80211_ref_node(ni);
+ if (mq_enqueue(&ic->ic_mgtq, m) == 0)
+ if_start(ifp);
+ else
+ ieee80211_release_node(ic, ni);
}
/*
@@ -539,9 +576,20 @@ ieee80211_encap(struct ifnet *ifp, struct mbuf *m, struct ieee80211_node **pni)
(ni->ni_flags & IEEE80211_NODE_QOS) &&
/* do not QoS-encapsulate EAPOL frames */
eh.ether_type != htons(ETHERTYPE_PAE)) {
+ struct ieee80211_tx_ba *ba;
tid = ieee80211_classify(ic, m);
- hdrlen = sizeof(struct ieee80211_qosframe);
- addqos = 1;
+ ba = &ni->ni_tx_ba[tid];
+ /*
+ * Don't use TID's sequence number space while an ADDBA
+ * request is in progress.
+ */
+ if (ba->ba_state == IEEE80211_BA_REQUESTED) {
+ hdrlen = sizeof(struct ieee80211_frame);
+ addqos = 0;
+ } else {
+ hdrlen = sizeof(struct ieee80211_qosframe);
+ addqos = 1;
+ }
} else {
hdrlen = sizeof(struct ieee80211_frame);
addqos = 0;
@@ -566,11 +614,15 @@ ieee80211_encap(struct ifnet *ifp, struct mbuf *m, struct ieee80211_node **pni)
struct ieee80211_qosframe *qwh =
(struct ieee80211_qosframe *)wh;
u_int16_t qos = tid;
+ struct ieee80211_tx_ba *ba = &ni->ni_tx_ba[tid];
if (ic->ic_tid_noack & (1 << tid))
qos |= IEEE80211_QOS_ACK_POLICY_NOACK;
- else if (ni->ni_tx_ba[tid].ba_state == IEEE80211_BA_AGREED)
- qos |= IEEE80211_QOS_ACK_POLICY_BA;
+ else if (ba->ba_state == IEEE80211_BA_AGREED) {
+ /* Use HT immediate block-ack. */
+ qos |= IEEE80211_QOS_ACK_POLICY_NORMAL;
+ } else if (ieee80211_can_use_ampdu(ic, ni))
+ ieee80211_node_trigger_addba_req(ni, tid);
qwh->i_fc[0] |= IEEE80211_FC0_SUBTYPE_QOS;
*(u_int16_t *)qwh->i_qos = htole16(qos);
*(u_int16_t *)qwh->i_seq =
@@ -1482,13 +1534,77 @@ ieee80211_get_addba_req(struct ieee80211com *ic, struct ieee80211_node *ni,
*frm++ = ba->ba_token;
LE_WRITE_2(frm, ba->ba_params); frm += 2;
LE_WRITE_2(frm, ba->ba_timeout_val / IEEE80211_DUR_TU); frm += 2;
- LE_WRITE_2(frm, ba->ba_winstart); frm += 2;
+ LE_WRITE_2(frm, ba->ba_winstart << IEEE80211_SEQ_SEQ_SHIFT); frm += 2;
m->m_pkthdr.len = m->m_len = frm - mtod(m, u_int8_t *);
return m;
}
+/* Move Tx BA window forward to the specified SSN. */
+void
+ieee80211_output_ba_move_window(struct ieee80211com *ic,
+ struct ieee80211_node *ni, uint8_t tid, uint16_t ssn)
+{
+ struct ieee80211_tx_ba *ba = &ni->ni_tx_ba[tid];
+ uint16_t s = ba->ba_winstart;
+
+ while (SEQ_LT(s, ssn) && ba->ba_bitmap) {
+ s = (s + 1) % 0xfff;
+ ba->ba_bitmap >>= 1;
+ }
+
+ ba->ba_winstart = (ssn & 0xfff);
+ ba->ba_winend = (ba->ba_winstart + ba->ba_winsize - 1) & 0xfff;
+}
+
+/*
+ * Move Tx BA window forward up to the first hole in the bitmap
+ * or up to the specified SSN, whichever comes first.
+ * After calling this function, frames before the start of the
+ * potentially changed BA window should be discarded.
+ */
+void
+ieee80211_output_ba_move_window_to_first_unacked(struct ieee80211com *ic,
+ struct ieee80211_node *ni, uint8_t tid, uint16_t ssn)
+{
+ struct ieee80211_tx_ba *ba = &ni->ni_tx_ba[tid];
+ uint16_t s = ba->ba_winstart;
+ uint64_t bitmap = ba->ba_bitmap;
+ int can_move_window = 0;
+
+ while (bitmap && SEQ_LT(s, ssn)) {
+ if ((bitmap & 1) == 0)
+ break;
+ s = (s + 1) % 0xfff;
+ bitmap >>= 1;
+ can_move_window = 1;
+ }
+
+ if (can_move_window)
+ ieee80211_output_ba_move_window(ic, ni, tid, s);
+}
+
+/* Record an ACK for a frame with a given SSN within the Tx BA window. */
+void
+ieee80211_output_ba_record_ack(struct ieee80211com *ic,
+ struct ieee80211_node *ni, uint8_t tid, uint16_t ssn)
+{
+ struct ieee80211_tx_ba *ba = &ni->ni_tx_ba[tid];
+ int i = 0;
+ uint16_t s = ba->ba_winstart;
+
+ KASSERT(!SEQ_LT(ssn, ba->ba_winstart));
+ KASSERT(!SEQ_LT(ba->ba_winend, ssn));
+
+ while (SEQ_LT(s, ssn)) {
+ s = (s + 1) % 0xfff;
+ i++;
+ }
+ if (i < ba->ba_winsize)
+ ba->ba_bitmap |= (1 << i);
+}
+
/*-
* ADDBA Response frame format:
* [1] Category
@@ -1791,6 +1907,47 @@ ieee80211_get_cts_to_self(struct ieee80211com *ic, u_int16_t dur)
return m;
}
+/*
+ * Build a compressed Block Ack Request control frame.
+ */
+struct mbuf *
+ieee80211_get_compressed_bar(struct ieee80211com *ic,
+ struct ieee80211_node *ni, int tid, uint16_t ssn)
+{
+ struct ieee80211_frame_min *wh;
+ uint8_t *frm;
+ uint16_t ctl;
+ struct mbuf *m;
+
+ MGETHDR(m, M_DONTWAIT, MT_DATA);
+ if (m == NULL)
+ return NULL;
+
+ m->m_pkthdr.len = m->m_len = sizeof(struct ieee80211_frame_min) +
+ sizeof(ctl) + sizeof(ssn);
+
+ wh = mtod(m, struct ieee80211_frame_min *);
+ wh->i_fc[0] = IEEE80211_FC0_VERSION_0 | IEEE80211_FC0_TYPE_CTL |
+ IEEE80211_FC0_SUBTYPE_BAR;
+ wh->i_fc[1] = IEEE80211_FC1_DIR_NODS;
+ *(u_int16_t *)wh->i_dur = 0;
+ IEEE80211_ADDR_COPY(wh->i_addr1, ni->ni_macaddr);
+ IEEE80211_ADDR_COPY(wh->i_addr2, ic->ic_myaddr);
+ frm = (uint8_t *)&wh[1];
+
+ ctl = IEEE80211_BA_COMPRESSED | (tid << IEEE80211_BA_TID_INFO_SHIFT);
+ LE_WRITE_2(frm, ctl);
+ frm += 2;
+
+ LE_WRITE_2(frm, ssn << IEEE80211_SEQ_SEQ_SHIFT);
+ frm += 2;
+
+ m->m_pkthdr.len = m->m_len = frm - mtod(m, u_int8_t *);
+ m->m_pkthdr.ph_cookie = ni;
+
+ return m;
+}
+
#ifndef IEEE80211_STA_ONLY
/*-
* Beacon frame format:
diff --git a/sys/net80211/ieee80211_proto.c b/sys/net80211/ieee80211_proto.c
index 42c0ebb9d3c..30c784c7b2f 100644
--- a/sys/net80211/ieee80211_proto.c
+++ b/sys/net80211/ieee80211_proto.c
@@ -1,4 +1,4 @@
-/* $OpenBSD: ieee80211_proto.c,v 1.93 2019/02/27 06:00:29 stsp Exp $ */
+/* $OpenBSD: ieee80211_proto.c,v 1.94 2019/07/29 10:50:09 stsp Exp $ */
/* $NetBSD: ieee80211_proto.c,v 1.8 2004/04/30 23:58:20 dyoung Exp $ */
/*-
@@ -604,16 +604,24 @@ ieee80211_tx_ba_timeout(void *arg)
u_int8_t tid;
int s;
- ic->ic_stats.is_ht_tx_ba_timeout++;
-
s = splnet();
+ tid = ((caddr_t)ba - (caddr_t)ni->ni_tx_ba) / sizeof(*ba);
if (ba->ba_state == IEEE80211_BA_REQUESTED) {
/* MLME-ADDBA.confirm(TIMEOUT) */
ba->ba_state = IEEE80211_BA_INIT;
-
+ if (ni->ni_addba_req_intval[tid] <
+ IEEE80211_ADDBA_REQ_INTVAL_MAX)
+ ni->ni_addba_req_intval[tid]++;
+ /*
+ * In case the peer believes there is an existing
+ * block ack agreement with us, try to delete it.
+ */
+ IEEE80211_SEND_ACTION(ic, ni, IEEE80211_CATEG_BA,
+ IEEE80211_ACTION_DELBA,
+ IEEE80211_REASON_SETUP_REQUIRED << 16 | 1 << 8 | tid);
} else if (ba->ba_state == IEEE80211_BA_AGREED) {
/* Block Ack inactivity timeout */
- tid = ((caddr_t)ba - (caddr_t)ni->ni_tx_ba) / sizeof(*ba);
+ ic->ic_stats.is_ht_tx_ba_timeout++;
ieee80211_delba_request(ic, ni, IEEE80211_REASON_TIMEOUT,
1, tid);
}
@@ -649,9 +657,13 @@ ieee80211_addba_request(struct ieee80211com *ic, struct ieee80211_node *ni,
{
struct ieee80211_tx_ba *ba = &ni->ni_tx_ba[tid];
+ if (ba->ba_state != IEEE80211_BA_INIT)
+ return EBUSY;
+
/* MLME-ADDBA.request */
/* setup Block Ack */
+ ba->ba_ni = ni;
ba->ba_state = IEEE80211_BA_REQUESTED;
ba->ba_token = ic->ic_dialog_token++;
ba->ba_timeout_val = 0;
@@ -661,7 +673,16 @@ ieee80211_addba_request(struct ieee80211com *ic, struct ieee80211_node *ni,
ba->ba_winend = (ba->ba_winstart + ba->ba_winsize - 1) & 0xfff;
ba->ba_params =
(ba->ba_winsize << IEEE80211_ADDBA_BUFSZ_SHIFT) |
- (tid << IEEE80211_ADDBA_TID_SHIFT) | IEEE80211_ADDBA_AMSDU;
+ (tid << IEEE80211_ADDBA_TID_SHIFT);
+#if 0
+ /*
+ * XXX A-MSDUs inside A-MPDUs expose a problem with bad TCP connection
+ * sharing behaviour. One connection eats all available bandwidth
+ * while others stall. Leave this disabled for now to give packets
+ * from disparate connections better chances of interleaving.
+ */
+ ba->ba_params |= IEEE80211_ADDBA_AMSDU;
+#endif
if ((ic->ic_htcaps & IEEE80211_HTCAP_DELAYEDBA) == 0)
/* immediate BA */
ba->ba_params |= IEEE80211_ADDBA_BA_POLICY;
@@ -673,7 +694,7 @@ ieee80211_addba_request(struct ieee80211com *ic, struct ieee80211_node *ni,
}
/*
- * Request the deletion of Block Ack with a peer.
+ * Request the deletion of Block Ack with a peer and notify driver.
*/
void
ieee80211_delba_request(struct ieee80211com *ic, struct ieee80211_node *ni,
@@ -681,9 +702,11 @@ ieee80211_delba_request(struct ieee80211com *ic, struct ieee80211_node *ni,
{
/* MLME-DELBA.request */
- /* transmit a DELBA frame */
- IEEE80211_SEND_ACTION(ic, ni, IEEE80211_CATEG_BA,
- IEEE80211_ACTION_DELBA, reason << 16 | dir << 8 | tid);
+ if (reason) {
+ /* transmit a DELBA frame */
+ IEEE80211_SEND_ACTION(ic, ni, IEEE80211_CATEG_BA,
+ IEEE80211_ACTION_DELBA, reason << 16 | dir << 8 | tid);
+ }
if (dir) {
/* MLME-DELBA.confirm(Originator) */
struct ieee80211_tx_ba *ba = &ni->ni_tx_ba[tid];
@@ -900,6 +923,22 @@ ieee80211_set_beacon_miss_threshold(struct ieee80211com *ic)
ic->ic_bmissthres, ic->ic_bss->ni_intval);
}
+/* Tell our peer, and the driver, to stop A-MPDU Tx for all TIDs. */
+void
+ieee80211_stop_ampdu_tx(struct ieee80211com *ic, struct ieee80211_node *ni,
+ int mgt)
+{
+ int tid;
+
+ 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)
+ continue;
+ ieee80211_delba_request(ic, ni,
+ mgt == -1 ? 0 : IEEE80211_REASON_AUTH_LEAVE, 1, tid);
+ }
+}
+
int
ieee80211_newstate(struct ieee80211com *ic, enum ieee80211_state nstate,
int mgt)
@@ -932,6 +971,8 @@ ieee80211_newstate(struct ieee80211com *ic, enum ieee80211_state nstate,
case IEEE80211_S_RUN:
if (mgt == -1)
goto justcleanup;
+ ieee80211_stop_ampdu_tx(ic, ni, mgt);
+ ieee80211_ba_del(ni);
switch (ic->ic_opmode) {
case IEEE80211_M_STA:
IEEE80211_SEND_MGMT(ic, ni,
@@ -986,6 +1027,7 @@ justcleanup:
if (ic->ic_opmode == IEEE80211_M_HOSTAP)
timeout_del(&ic->ic_rsn_timeout);
#endif
+ ieee80211_ba_del(ni);
timeout_del(&ic->ic_bgscan_timeout);
ic->ic_bgscan_fail = 0;
ic->ic_mgt_timer = 0;
@@ -1041,6 +1083,7 @@ justcleanup:
}
timeout_del(&ic->ic_bgscan_timeout);
ic->ic_bgscan_fail = 0;
+ ieee80211_stop_ampdu_tx(ic, ni, mgt);
ieee80211_free_allnodes(ic, 1);
/* FALLTHROUGH */
case IEEE80211_S_AUTH:
@@ -1086,6 +1129,8 @@ justcleanup:
case IEEE80211_S_RUN:
timeout_del(&ic->ic_bgscan_timeout);
ic->ic_bgscan_fail = 0;
+ ieee80211_stop_ampdu_tx(ic, ni, mgt);
+ ieee80211_ba_del(ni);
switch (mgt) {
case IEEE80211_FC0_SUBTYPE_AUTH:
IEEE80211_SEND_MGMT(ic, ni,
@@ -1116,6 +1161,8 @@ justcleanup:
IEEE80211_FC0_SUBTYPE_ASSOC_REQ, 0);
break;
case IEEE80211_S_RUN:
+ ieee80211_stop_ampdu_tx(ic, ni, mgt);
+ ieee80211_ba_del(ni);
IEEE80211_SEND_MGMT(ic, ni,
IEEE80211_FC0_SUBTYPE_ASSOC_REQ, 1);
break;
diff --git a/sys/net80211/ieee80211_proto.h b/sys/net80211/ieee80211_proto.h
index 379563161f1..e622d35f4dc 100644
--- a/sys/net80211/ieee80211_proto.h
+++ b/sys/net80211/ieee80211_proto.h
@@ -1,4 +1,4 @@
-/* $OpenBSD: ieee80211_proto.h,v 1.44 2018/02/05 08:44:13 stsp Exp $ */
+/* $OpenBSD: ieee80211_proto.h,v 1.45 2019/07/29 10:50:09 stsp Exp $ */
/* $NetBSD: ieee80211_proto.h,v 1.3 2003/10/13 04:23:56 dyoung Exp $ */
/*-
@@ -76,12 +76,16 @@ extern int ieee80211_send_mgmt(struct ieee80211com *, struct ieee80211_node *,
int, int, int);
extern void ieee80211_eapol_key_input(struct ieee80211com *, struct mbuf *,
struct ieee80211_node *);
+extern void ieee80211_tx_compressed_bar(struct ieee80211com *,
+ struct ieee80211_node *, int, uint16_t);
extern struct mbuf *ieee80211_encap(struct ifnet *, struct mbuf *,
struct ieee80211_node **);
extern struct mbuf *ieee80211_get_rts(struct ieee80211com *,
const struct ieee80211_frame *, u_int16_t);
extern struct mbuf *ieee80211_get_cts_to_self(struct ieee80211com *,
u_int16_t);
+extern struct mbuf *ieee80211_get_compressed_bar(struct ieee80211com *,
+ struct ieee80211_node *, int, uint16_t);
extern struct mbuf *ieee80211_beacon_alloc(struct ieee80211com *,
struct ieee80211_node *);
extern int ieee80211_save_ie(const u_int8_t *, u_int8_t **);
@@ -149,6 +153,8 @@ extern void ieee80211_auth_open_confirm(struct ieee80211com *,
extern void ieee80211_auth_open(struct ieee80211com *,
const struct ieee80211_frame *, struct ieee80211_node *,
struct ieee80211_rxinfo *rs, u_int16_t, u_int16_t);
+extern void ieee80211_stop_ampdu_tx(struct ieee80211com *,
+ struct ieee80211_node *, int);
extern void ieee80211_gtk_rekey_timeout(void *);
extern int ieee80211_keyrun(struct ieee80211com *, u_int8_t *);
extern void ieee80211_setkeys(struct ieee80211com *);
@@ -168,6 +174,15 @@ extern void ieee80211_addba_req_accept(struct ieee80211com *,
struct ieee80211_node *, uint8_t);
extern void ieee80211_addba_req_refuse(struct ieee80211com *,
struct ieee80211_node *, uint8_t);
-
+extern void ieee80211_addba_resp_accept(struct ieee80211com *,
+ struct ieee80211_node *, uint8_t);
+extern void ieee80211_addba_resp_refuse(struct ieee80211com *,
+ struct ieee80211_node *, uint8_t, uint16_t);
+extern void ieee80211_output_ba_move_window(struct ieee80211com *,
+ struct ieee80211_node *, uint8_t, uint16_t);
+extern void ieee80211_output_ba_move_window_to_first_unacked(
+ struct ieee80211com *, struct ieee80211_node *, uint8_t, uint16_t);
+extern void ieee80211_output_ba_record_ack(struct ieee80211com *,
+ struct ieee80211_node *, uint8_t, uint16_t);
#endif /* _NET80211_IEEE80211_PROTO_H_ */
diff --git a/sys/net80211/ieee80211_var.h b/sys/net80211/ieee80211_var.h
index e866e28b1f9..2a567a01422 100644
--- a/sys/net80211/ieee80211_var.h
+++ b/sys/net80211/ieee80211_var.h
@@ -1,4 +1,4 @@
-/* $OpenBSD: ieee80211_var.h,v 1.96 2019/05/12 18:12:38 stsp Exp $ */
+/* $OpenBSD: ieee80211_var.h,v 1.97 2019/07/29 10:50:09 stsp Exp $ */
/* $NetBSD: ieee80211_var.h,v 1.7 2004/05/06 03:07:10 dyoung Exp $ */
/*-
@@ -417,6 +417,7 @@ struct ieee80211_ess {
#define IEEE80211_C_MFP 0x00002000 /* CAPABILITY: MFP avail */
#define IEEE80211_C_RAWCTL 0x00004000 /* CAPABILITY: raw ctl */
#define IEEE80211_C_SCANALLBAND 0x00008000 /* CAPABILITY: scan all bands */
+#define IEEE80211_C_TX_AMPDU 0x00010000 /* CAPABILITY: send A-MPDU */
/* flags for ieee80211_fix_rate() */
#define IEEE80211_F_DOSORT 0x00000001 /* sort rate list */