summaryrefslogtreecommitdiffstats
path: root/drivers/staging/brcm80211/sys/wlc_ampdu.c
diff options
context:
space:
mode:
Diffstat (limited to 'drivers/staging/brcm80211/sys/wlc_ampdu.c')
-rw-r--r--drivers/staging/brcm80211/sys/wlc_ampdu.c1411
1 files changed, 1411 insertions, 0 deletions
diff --git a/drivers/staging/brcm80211/sys/wlc_ampdu.c b/drivers/staging/brcm80211/sys/wlc_ampdu.c
new file mode 100644
index 0000000..a4e49f3
--- /dev/null
+++ b/drivers/staging/brcm80211/sys/wlc_ampdu.c
@@ -0,0 +1,1411 @@
+/*
+ * Copyright (c) 2010 Broadcom Corporation
+ *
+ * Permission to use, copy, modify, and/or distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY
+ * SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION
+ * OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN
+ * CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+#include <linux/kernel.h>
+#include <wlc_cfg.h>
+#include <bcmdefs.h>
+#include <linuxver.h>
+#include <bcmdefs.h>
+#include <osl.h>
+#include <bcmutils.h>
+#include <siutils.h>
+#include <bcmendian.h>
+#include <wlioctl.h>
+#include <sbhnddma.h>
+#include <hnddma.h>
+#include <d11.h>
+#include <wlc_rate.h>
+#include <wlc_pub.h>
+#include <wlc_key.h>
+#include <wlc_mac80211.h>
+#include <wlc_phy_hal.h>
+#include <wlc_antsel.h>
+#include <wlc_scb.h>
+#include <net/mac80211.h>
+#include <wlc_ampdu.h>
+#include <wl_export.h>
+
+#ifdef WLC_HIGH_ONLY
+#include <bcm_rpc_tp.h>
+#include <wlc_rpctx.h>
+#endif
+
+#define AMPDU_MAX_MPDU 32 /* max number of mpdus in an ampdu */
+#define AMPDU_NUM_MPDU_LEGACY 16 /* max number of mpdus in an ampdu to a legacy */
+#define AMPDU_TX_BA_MAX_WSIZE 64 /* max Tx ba window size (in pdu) */
+#define AMPDU_TX_BA_DEF_WSIZE 64 /* default Tx ba window size (in pdu) */
+#define AMPDU_RX_BA_DEF_WSIZE 64 /* max Rx ba window size (in pdu) */
+#define AMPDU_RX_BA_MAX_WSIZE 64 /* default Rx ba window size (in pdu) */
+#define AMPDU_MAX_DUR 5 /* max dur of tx ampdu (in msec) */
+#define AMPDU_DEF_RETRY_LIMIT 5 /* default tx retry limit */
+#define AMPDU_DEF_RR_RETRY_LIMIT 2 /* default tx retry limit at reg rate */
+#define AMPDU_DEF_TXPKT_WEIGHT 2 /* default weight of ampdu in txfifo */
+#define AMPDU_DEF_FFPLD_RSVD 2048 /* default ffpld reserved bytes */
+#define AMPDU_INI_FREE 10 /* # of inis to be freed on detach */
+#define AMPDU_SCB_MAX_RELEASE 20 /* max # of mpdus released at a time */
+
+#define NUM_FFPLD_FIFO 4 /* number of fifo concerned by pre-loading */
+#define FFPLD_TX_MAX_UNFL 200 /* default value of the average number of ampdu
+ * without underflows
+ */
+#define FFPLD_MPDU_SIZE 1800 /* estimate of maximum mpdu size */
+#define FFPLD_MAX_MCS 23 /* we don't deal with mcs 32 */
+#define FFPLD_PLD_INCR 1000 /* increments in bytes */
+#define FFPLD_MAX_AMPDU_CNT 5000 /* maximum number of ampdu we
+ * accumulate between resets.
+ */
+
+#define TX_SEQ_TO_INDEX(seq) ((seq) % AMPDU_TX_BA_MAX_WSIZE)
+
+/* max possible overhead per mpdu in the ampdu; 3 is for roundup if needed */
+#define AMPDU_MAX_MPDU_OVERHEAD (DOT11_FCS_LEN + DOT11_ICV_AES_LEN + AMPDU_DELIMITER_LEN + 3 \
+ + DOT11_A4_HDR_LEN + DOT11_QOS_LEN + DOT11_IV_MAX_LEN)
+
+#ifdef BCMDBG
+u32 wl_ampdu_dbg =
+ WL_AMPDU_UPDN_VAL |
+ WL_AMPDU_ERR_VAL |
+ WL_AMPDU_TX_VAL |
+ WL_AMPDU_RX_VAL |
+ WL_AMPDU_CTL_VAL |
+ WL_AMPDU_HW_VAL | WL_AMPDU_HWTXS_VAL | WL_AMPDU_HWDBG_VAL;
+#endif
+
+/* structure to hold tx fifo information and pre-loading state
+ * counters specific to tx underflows of ampdus
+ * some counters might be redundant with the ones in wlc or ampdu structures.
+ * This allows to maintain a specific state independantly of
+ * how often and/or when the wlc counters are updated.
+ */
+typedef struct wlc_fifo_info {
+ u16 ampdu_pld_size; /* number of bytes to be pre-loaded */
+ u8 mcs2ampdu_table[FFPLD_MAX_MCS + 1]; /* per-mcs max # of mpdus in an ampdu */
+ u16 prev_txfunfl; /* num of underflows last read from the HW macstats counter */
+ u32 accum_txfunfl; /* num of underflows since we modified pld params */
+ u32 accum_txampdu; /* num of tx ampdu since we modified pld params */
+ u32 prev_txampdu; /* previous reading of tx ampdu */
+ u32 dmaxferrate; /* estimated dma avg xfer rate in kbits/sec */
+} wlc_fifo_info_t;
+
+/* AMPDU module specific state */
+struct ampdu_info {
+ wlc_info_t *wlc; /* pointer to main wlc structure */
+ int scb_handle; /* scb cubby handle to retrieve data from scb */
+ u8 ini_enable[AMPDU_MAX_SCB_TID]; /* per-tid initiator enable/disable of ampdu */
+ u8 ba_tx_wsize; /* Tx ba window size (in pdu) */
+ u8 ba_rx_wsize; /* Rx ba window size (in pdu) */
+ u8 retry_limit; /* mpdu transmit retry limit */
+ u8 rr_retry_limit; /* mpdu transmit retry limit at regular rate */
+ u8 retry_limit_tid[AMPDU_MAX_SCB_TID]; /* per-tid mpdu transmit retry limit */
+ /* per-tid mpdu transmit retry limit at regular rate */
+ u8 rr_retry_limit_tid[AMPDU_MAX_SCB_TID];
+ u8 mpdu_density; /* min mpdu spacing (0-7) ==> 2^(x-1)/8 usec */
+ s8 max_pdu; /* max pdus allowed in ampdu */
+ u8 dur; /* max duration of an ampdu (in msec) */
+ u8 txpkt_weight; /* weight of ampdu in txfifo; reduces rate lag */
+ u8 rx_factor; /* maximum rx ampdu factor (0-3) ==> 2^(13+x) bytes */
+ u32 ffpld_rsvd; /* number of bytes to reserve for preload */
+ u32 max_txlen[MCS_TABLE_SIZE][2][2]; /* max size of ampdu per mcs, bw and sgi */
+ void *ini_free[AMPDU_INI_FREE]; /* array of ini's to be freed on detach */
+ bool mfbr; /* enable multiple fallback rate */
+ u32 tx_max_funl; /* underflows should be kept such that
+ * (tx_max_funfl*underflows) < tx frames
+ */
+ wlc_fifo_info_t fifo_tb[NUM_FFPLD_FIFO]; /* table of fifo infos */
+
+#ifdef WLC_HIGH_ONLY
+ void *p;
+ tx_status_t txs;
+ bool waiting_status; /* To help sanity checks */
+#endif
+};
+
+#define AMPDU_CLEANUPFLAG_RX (0x1)
+#define AMPDU_CLEANUPFLAG_TX (0x2)
+
+#define SCB_AMPDU_CUBBY(ampdu, scb) (&(scb->scb_ampdu))
+#define SCB_AMPDU_INI(scb_ampdu, tid) (&(scb_ampdu->ini[tid]))
+
+static void wlc_ffpld_init(ampdu_info_t *ampdu);
+static int wlc_ffpld_check_txfunfl(wlc_info_t *wlc, int f);
+static void wlc_ffpld_calc_mcs2ampdu_table(ampdu_info_t *ampdu, int f);
+
+static scb_ampdu_tid_ini_t *wlc_ampdu_init_tid_ini(ampdu_info_t *ampdu,
+ scb_ampdu_t *scb_ampdu,
+ u8 tid, bool override);
+static void ampdu_cleanup_tid_ini(ampdu_info_t *ampdu, scb_ampdu_t *scb_ampdu,
+ u8 tid, bool force);
+static void ampdu_update_max_txlen(ampdu_info_t *ampdu, u8 dur);
+static void scb_ampdu_update_config(ampdu_info_t *ampdu, struct scb *scb);
+static void scb_ampdu_update_config_all(ampdu_info_t *ampdu);
+
+#define wlc_ampdu_txflowcontrol(a, b, c) do {} while (0)
+
+static void wlc_ampdu_dotxstatus_complete(ampdu_info_t *ampdu, struct scb *scb,
+ void *p, tx_status_t *txs,
+ u32 frmtxstatus,
+ u32 frmtxstatus2);
+
+static inline u16 pkt_txh_seqnum(wlc_info_t *wlc, void *p)
+{
+ d11txh_t *txh;
+ struct dot11_header *h;
+ txh = (d11txh_t *) PKTDATA(p);
+ h = (struct dot11_header *)((u8 *) (txh + 1) + D11_PHY_HDR_LEN);
+ return ltoh16(h->seq) >> SEQNUM_SHIFT;
+}
+
+ampdu_info_t *wlc_ampdu_attach(wlc_info_t *wlc)
+{
+ ampdu_info_t *ampdu;
+ int i;
+
+ /* some code depends on packed structures */
+ ASSERT(DOT11_MAXNUMFRAGS == NBITS(u16));
+ ASSERT(ISPOWEROF2(AMPDU_TX_BA_MAX_WSIZE));
+ ASSERT(ISPOWEROF2(AMPDU_RX_BA_MAX_WSIZE));
+ ASSERT(wlc->pub->tunables->ampdunummpdu <= AMPDU_MAX_MPDU);
+ ASSERT(wlc->pub->tunables->ampdunummpdu > 0);
+
+ ampdu = kzalloc(sizeof(ampdu_info_t), GFP_ATOMIC);
+ if (!ampdu) {
+ WL_ERROR(("wl%d: wlc_ampdu_attach: out of mem\n", wlc->pub->unit));
+ return NULL;
+ }
+ ampdu->wlc = wlc;
+
+ for (i = 0; i < AMPDU_MAX_SCB_TID; i++)
+ ampdu->ini_enable[i] = true;
+ /* Disable ampdu for VO by default */
+ ampdu->ini_enable[PRIO_8021D_VO] = false;
+ ampdu->ini_enable[PRIO_8021D_NC] = false;
+
+ /* Disable ampdu for BK by default since not enough fifo space */
+ ampdu->ini_enable[PRIO_8021D_NONE] = false;
+ ampdu->ini_enable[PRIO_8021D_BK] = false;
+
+ ampdu->ba_tx_wsize = AMPDU_TX_BA_DEF_WSIZE;
+ ampdu->ba_rx_wsize = AMPDU_RX_BA_DEF_WSIZE;
+ ampdu->mpdu_density = AMPDU_DEF_MPDU_DENSITY;
+ ampdu->max_pdu = AUTO;
+ ampdu->dur = AMPDU_MAX_DUR;
+ ampdu->txpkt_weight = AMPDU_DEF_TXPKT_WEIGHT;
+
+ ampdu->ffpld_rsvd = AMPDU_DEF_FFPLD_RSVD;
+ /* bump max ampdu rcv size to 64k for all 11n devices except 4321A0 and 4321A1 */
+ if (WLCISNPHY(wlc->band) && NREV_LT(wlc->band->phyrev, 2))
+ ampdu->rx_factor = AMPDU_RX_FACTOR_32K;
+ else
+ ampdu->rx_factor = AMPDU_RX_FACTOR_64K;
+#ifdef WLC_HIGH_ONLY
+ /* Restrict to smaller rcv size for BMAC dongle */
+ ampdu->rx_factor = AMPDU_RX_FACTOR_32K;
+#endif
+ ampdu->retry_limit = AMPDU_DEF_RETRY_LIMIT;
+ ampdu->rr_retry_limit = AMPDU_DEF_RR_RETRY_LIMIT;
+
+ for (i = 0; i < AMPDU_MAX_SCB_TID; i++) {
+ ampdu->retry_limit_tid[i] = ampdu->retry_limit;
+ ampdu->rr_retry_limit_tid[i] = ampdu->rr_retry_limit;
+ }
+
+ ampdu_update_max_txlen(ampdu, ampdu->dur);
+ ampdu->mfbr = false;
+ /* try to set ampdu to the default value */
+ wlc_ampdu_set(ampdu, wlc->pub->_ampdu);
+
+ ampdu->tx_max_funl = FFPLD_TX_MAX_UNFL;
+ wlc_ffpld_init(ampdu);
+
+ return ampdu;
+}
+
+void wlc_ampdu_detach(ampdu_info_t *ampdu)
+{
+ int i;
+
+ if (!ampdu)
+ return;
+
+ /* free all ini's which were to be freed on callbacks which were never called */
+ for (i = 0; i < AMPDU_INI_FREE; i++) {
+ if (ampdu->ini_free[i]) {
+ kfree(ampdu->ini_free[i]);
+ }
+ }
+
+ wlc_module_unregister(ampdu->wlc->pub, "ampdu", ampdu);
+ kfree(ampdu);
+}
+
+void scb_ampdu_cleanup(ampdu_info_t *ampdu, struct scb *scb)
+{
+ scb_ampdu_t *scb_ampdu = SCB_AMPDU_CUBBY(ampdu, scb);
+ u8 tid;
+
+ WL_AMPDU_UPDN(("scb_ampdu_cleanup: enter\n"));
+ ASSERT(scb_ampdu);
+
+ for (tid = 0; tid < AMPDU_MAX_SCB_TID; tid++) {
+ ampdu_cleanup_tid_ini(ampdu, scb_ampdu, tid, false);
+ }
+}
+
+/* reset the ampdu state machine so that it can gracefully handle packets that were
+ * freed from the dma and tx queues during reinit
+ */
+void wlc_ampdu_reset(ampdu_info_t *ampdu)
+{
+ WL_NONE(("%s: Entering\n", __func__));
+}
+
+static void scb_ampdu_update_config(ampdu_info_t *ampdu, struct scb *scb)
+{
+ scb_ampdu_t *scb_ampdu = SCB_AMPDU_CUBBY(ampdu, scb);
+ int i;
+
+ scb_ampdu->max_pdu = (u8) ampdu->wlc->pub->tunables->ampdunummpdu;
+
+ /* go back to legacy size if some preloading is occuring */
+ for (i = 0; i < NUM_FFPLD_FIFO; i++) {
+ if (ampdu->fifo_tb[i].ampdu_pld_size > FFPLD_PLD_INCR)
+ scb_ampdu->max_pdu = AMPDU_NUM_MPDU_LEGACY;
+ }
+
+ /* apply user override */
+ if (ampdu->max_pdu != AUTO)
+ scb_ampdu->max_pdu = (u8) ampdu->max_pdu;
+
+ scb_ampdu->release = min_t(u8, scb_ampdu->max_pdu, AMPDU_SCB_MAX_RELEASE);
+
+ if (scb_ampdu->max_rxlen)
+ scb_ampdu->release =
+ min_t(u8, scb_ampdu->release, scb_ampdu->max_rxlen / 1600);
+
+ scb_ampdu->release = min(scb_ampdu->release,
+ ampdu->fifo_tb[TX_AC_BE_FIFO].
+ mcs2ampdu_table[FFPLD_MAX_MCS]);
+
+ ASSERT(scb_ampdu->release);
+}
+
+void scb_ampdu_update_config_all(ampdu_info_t *ampdu)
+{
+ scb_ampdu_update_config(ampdu, ampdu->wlc->pub->global_scb);
+}
+
+static void wlc_ffpld_init(ampdu_info_t *ampdu)
+{
+ int i, j;
+ wlc_fifo_info_t *fifo;
+
+ for (j = 0; j < NUM_FFPLD_FIFO; j++) {
+ fifo = (ampdu->fifo_tb + j);
+ fifo->ampdu_pld_size = 0;
+ for (i = 0; i <= FFPLD_MAX_MCS; i++)
+ fifo->mcs2ampdu_table[i] = 255;
+ fifo->dmaxferrate = 0;
+ fifo->accum_txampdu = 0;
+ fifo->prev_txfunfl = 0;
+ fifo->accum_txfunfl = 0;
+
+ }
+}
+
+/* evaluate the dma transfer rate using the tx underflows as feedback.
+ * If necessary, increase tx fifo preloading. If not enough,
+ * decrease maximum ampdu size for each mcs till underflows stop
+ * Return 1 if pre-loading not active, -1 if not an underflow event,
+ * 0 if pre-loading module took care of the event.
+ */
+static int wlc_ffpld_check_txfunfl(wlc_info_t *wlc, int fid)
+{
+ ampdu_info_t *ampdu = wlc->ampdu;
+ u32 phy_rate = MCS_RATE(FFPLD_MAX_MCS, true, false);
+ u32 txunfl_ratio;
+ u8 max_mpdu;
+ u32 current_ampdu_cnt = 0;
+ u16 max_pld_size;
+ u32 new_txunfl;
+ wlc_fifo_info_t *fifo = (ampdu->fifo_tb + fid);
+ uint xmtfifo_sz;
+ u16 cur_txunfl;
+
+ /* return if we got here for a different reason than underflows */
+ cur_txunfl =
+ wlc_read_shm(wlc,
+ M_UCODE_MACSTAT + offsetof(macstat_t, txfunfl[fid]));
+ new_txunfl = (u16) (cur_txunfl - fifo->prev_txfunfl);
+ if (new_txunfl == 0) {
+ WL_FFPLD(("check_txunfl : TX status FRAG set but no tx underflows\n"));
+ return -1;
+ }
+ fifo->prev_txfunfl = cur_txunfl;
+
+ if (!ampdu->tx_max_funl)
+ return 1;
+
+ /* check if fifo is big enough */
+ if (wlc_xmtfifo_sz_get(wlc, fid, &xmtfifo_sz)) {
+ WL_FFPLD(("check_txunfl : get xmtfifo_sz failed.\n"));
+ return -1;
+ }
+
+ if ((TXFIFO_SIZE_UNIT * (u32) xmtfifo_sz) <= ampdu->ffpld_rsvd)
+ return 1;
+
+ max_pld_size = TXFIFO_SIZE_UNIT * xmtfifo_sz - ampdu->ffpld_rsvd;
+ fifo->accum_txfunfl += new_txunfl;
+
+ /* we need to wait for at least 10 underflows */
+ if (fifo->accum_txfunfl < 10)
+ return 0;
+
+ WL_FFPLD(("ampdu_count %d tx_underflows %d\n",
+ current_ampdu_cnt, fifo->accum_txfunfl));
+
+ /*
+ compute the current ratio of tx unfl per ampdu.
+ When the current ampdu count becomes too
+ big while the ratio remains small, we reset
+ the current count in order to not
+ introduce too big of a latency in detecting a
+ large amount of tx underflows later.
+ */
+
+ txunfl_ratio = current_ampdu_cnt / fifo->accum_txfunfl;
+
+ if (txunfl_ratio > ampdu->tx_max_funl) {
+ if (current_ampdu_cnt >= FFPLD_MAX_AMPDU_CNT) {
+ fifo->accum_txfunfl = 0;
+ }
+ return 0;
+ }
+ max_mpdu =
+ min_t(u8, fifo->mcs2ampdu_table[FFPLD_MAX_MCS], AMPDU_NUM_MPDU_LEGACY);
+
+ /* In case max value max_pdu is already lower than
+ the fifo depth, there is nothing more we can do.
+ */
+
+ if (fifo->ampdu_pld_size >= max_mpdu * FFPLD_MPDU_SIZE) {
+ WL_FFPLD(("tx fifo pld : max ampdu fits in fifo\n)"));
+ fifo->accum_txfunfl = 0;
+ return 0;
+ }
+
+ if (fifo->ampdu_pld_size < max_pld_size) {
+
+ /* increment by TX_FIFO_PLD_INC bytes */
+ fifo->ampdu_pld_size += FFPLD_PLD_INCR;
+ if (fifo->ampdu_pld_size > max_pld_size)
+ fifo->ampdu_pld_size = max_pld_size;
+
+ /* update scb release size */
+ scb_ampdu_update_config_all(ampdu);
+
+ /*
+ compute a new dma xfer rate for max_mpdu @ max mcs.
+ This is the minimum dma rate that
+ can acheive no unferflow condition for the current mpdu size.
+ */
+ /* note : we divide/multiply by 100 to avoid integer overflows */
+ fifo->dmaxferrate =
+ (((phy_rate / 100) *
+ (max_mpdu * FFPLD_MPDU_SIZE - fifo->ampdu_pld_size))
+ / (max_mpdu * FFPLD_MPDU_SIZE)) * 100;
+
+ WL_FFPLD(("DMA estimated transfer rate %d; pre-load size %d\n",
+ fifo->dmaxferrate, fifo->ampdu_pld_size));
+ } else {
+
+ /* decrease ampdu size */
+ if (fifo->mcs2ampdu_table[FFPLD_MAX_MCS] > 1) {
+ if (fifo->mcs2ampdu_table[FFPLD_MAX_MCS] == 255)
+ fifo->mcs2ampdu_table[FFPLD_MAX_MCS] =
+ AMPDU_NUM_MPDU_LEGACY - 1;
+ else
+ fifo->mcs2ampdu_table[FFPLD_MAX_MCS] -= 1;
+
+ /* recompute the table */
+ wlc_ffpld_calc_mcs2ampdu_table(ampdu, fid);
+
+ /* update scb release size */
+ scb_ampdu_update_config_all(ampdu);
+ }
+ }
+ fifo->accum_txfunfl = 0;
+ return 0;
+}
+
+static void wlc_ffpld_calc_mcs2ampdu_table(ampdu_info_t *ampdu, int f)
+{
+ int i;
+ u32 phy_rate, dma_rate, tmp;
+ u8 max_mpdu;
+ wlc_fifo_info_t *fifo = (ampdu->fifo_tb + f);
+
+ /* recompute the dma rate */
+ /* note : we divide/multiply by 100 to avoid integer overflows */
+ max_mpdu =
+ min_t(u8, fifo->mcs2ampdu_table[FFPLD_MAX_MCS], AMPDU_NUM_MPDU_LEGACY);
+ phy_rate = MCS_RATE(FFPLD_MAX_MCS, true, false);
+ dma_rate =
+ (((phy_rate / 100) *
+ (max_mpdu * FFPLD_MPDU_SIZE - fifo->ampdu_pld_size))
+ / (max_mpdu * FFPLD_MPDU_SIZE)) * 100;
+ fifo->dmaxferrate = dma_rate;
+
+ /* fill up the mcs2ampdu table; do not recalc the last mcs */
+ dma_rate = dma_rate >> 7;
+ for (i = 0; i < FFPLD_MAX_MCS; i++) {
+ /* shifting to keep it within integer range */
+ phy_rate = MCS_RATE(i, true, false) >> 7;
+ if (phy_rate > dma_rate) {
+ tmp = ((fifo->ampdu_pld_size * phy_rate) /
+ ((phy_rate - dma_rate) * FFPLD_MPDU_SIZE)) + 1;
+ tmp = min_t(u32, tmp, 255);
+ fifo->mcs2ampdu_table[i] = (u8) tmp;
+ }
+ }
+}
+
+static void BCMFASTPATH
+wlc_ampdu_agg(ampdu_info_t *ampdu, struct scb *scb, void *p, uint prec)
+{
+ scb_ampdu_t *scb_ampdu;
+ scb_ampdu_tid_ini_t *ini;
+ u8 tid = (u8) PKTPRIO(p);
+
+ scb_ampdu = SCB_AMPDU_CUBBY(ampdu, scb);
+
+ /* initialize initiator on first packet; sends addba req */
+ ini = SCB_AMPDU_INI(scb_ampdu, tid);
+ if (ini->magic != INI_MAGIC) {
+ ini = wlc_ampdu_init_tid_ini(ampdu, scb_ampdu, tid, false);
+ }
+ return;
+}
+
+int BCMFASTPATH
+wlc_sendampdu(ampdu_info_t *ampdu, wlc_txq_info_t *qi, void **pdu, int prec)
+{
+ wlc_info_t *wlc;
+ osl_t *osh;
+ void *p, *pkt[AMPDU_MAX_MPDU];
+ u8 tid, ndelim;
+ int err = 0;
+ u8 preamble_type = WLC_GF_PREAMBLE;
+ u8 fbr_preamble_type = WLC_GF_PREAMBLE;
+ u8 rts_preamble_type = WLC_LONG_PREAMBLE;
+ u8 rts_fbr_preamble_type = WLC_LONG_PREAMBLE;
+
+ bool rr = true, fbr = false;
+ uint i, count = 0, fifo, seg_cnt = 0;
+ u16 plen, len, seq = 0, mcl, mch, index, frameid, dma_len = 0;
+ u32 ampdu_len, maxlen = 0;
+ d11txh_t *txh = NULL;
+ u8 *plcp;
+ struct dot11_header *h;
+ struct scb *scb;
+ scb_ampdu_t *scb_ampdu;
+ scb_ampdu_tid_ini_t *ini;
+ u8 mcs = 0;
+ bool use_rts = false, use_cts = false;
+ ratespec_t rspec = 0, rspec_fallback = 0;
+ ratespec_t rts_rspec = 0, rts_rspec_fallback = 0;
+ u16 mimo_ctlchbw = PHY_TXC1_BW_20MHZ;
+ struct dot11_rts_frame *rts;
+ u8 rr_retry_limit;
+ wlc_fifo_info_t *f;
+ bool fbr_iscck;
+ struct ieee80211_tx_info *tx_info;
+ u16 qlen;
+
+ wlc = ampdu->wlc;
+ osh = wlc->osh;
+ p = *pdu;
+
+ ASSERT(p);
+
+ tid = (u8) PKTPRIO(p);
+ ASSERT(tid < AMPDU_MAX_SCB_TID);
+
+ f = ampdu->fifo_tb + prio2fifo[tid];
+
+ scb = wlc->pub->global_scb;
+ ASSERT(scb->magic == SCB_MAGIC);
+
+ scb_ampdu = SCB_AMPDU_CUBBY(ampdu, scb);
+ ASSERT(scb_ampdu);
+ ini = &scb_ampdu->ini[tid];
+
+ /* Let pressure continue to build ... */
+ qlen = pktq_plen(&qi->q, prec);
+ if (ini->tx_in_transit > 0 && qlen < scb_ampdu->max_pdu) {
+ return BCME_BUSY;
+ }
+
+ wlc_ampdu_agg(ampdu, scb, p, tid);
+
+ if (wlc->block_datafifo) {
+ WL_ERROR(("%s: Fifo blocked\n", __func__));
+ return BCME_BUSY;
+ }
+ rr_retry_limit = ampdu->rr_retry_limit_tid[tid];
+ ampdu_len = 0;
+ dma_len = 0;
+ while (p) {
+ struct ieee80211_tx_rate *txrate;
+
+ tx_info = IEEE80211_SKB_CB(p);
+ txrate = tx_info->status.rates;
+
+ if (tx_info->flags & IEEE80211_TX_CTL_AMPDU) {
+ err = wlc_prep_pdu(wlc, p, &fifo);
+ } else {
+ WL_ERROR(("%s: AMPDU flag is off!\n", __func__));
+ *pdu = NULL;
+ err = 0;
+ break;
+ }
+
+ if (err) {
+ if (err == BCME_BUSY) {
+ WL_ERROR(("wl%d: wlc_sendampdu: prep_xdu retry; seq 0x%x\n", wlc->pub->unit, seq));
+ WLCNTINCR(ampdu->cnt->sduretry);
+ *pdu = p;
+ break;
+ }
+
+ /* error in the packet; reject it */
+ WL_AMPDU_ERR(("wl%d: wlc_sendampdu: prep_xdu rejected; seq 0x%x\n", wlc->pub->unit, seq));
+ WLCNTINCR(ampdu->cnt->sdurejected);
+
+ *pdu = NULL;
+ break;
+ }
+
+ /* pkt is good to be aggregated */
+ ASSERT(tx_info->flags & IEEE80211_TX_CTL_AMPDU);
+ txh = (d11txh_t *) PKTDATA(p);
+ plcp = (u8 *) (txh + 1);
+ h = (struct dot11_header *)(plcp + D11_PHY_HDR_LEN);
+ seq = ltoh16(h->seq) >> SEQNUM_SHIFT;
+ index = TX_SEQ_TO_INDEX(seq);
+
+ /* check mcl fields and test whether it can be agg'd */
+ mcl = ltoh16(txh->MacTxControlLow);
+ mcl &= ~TXC_AMPDU_MASK;
+ fbr_iscck = !(ltoh16(txh->XtraFrameTypes) & 0x3);
+ ASSERT(!fbr_iscck);
+ txh->PreloadSize = 0; /* always default to 0 */
+
+ /* Handle retry limits */
+ if (txrate[0].count <= rr_retry_limit) {
+ txrate[0].count++;
+ rr = true;
+ fbr = false;
+ ASSERT(!fbr);
+ } else {
+ fbr = true;
+ rr = false;
+ txrate[1].count++;
+ }
+
+ /* extract the length info */
+ len = fbr_iscck ? WLC_GET_CCK_PLCP_LEN(txh->FragPLCPFallback)
+ : WLC_GET_MIMO_PLCP_LEN(txh->FragPLCPFallback);
+
+ /* retrieve null delimiter count */
+ ndelim = txh->RTSPLCPFallback[AMPDU_FBR_NULL_DELIM];
+ seg_cnt += 1;
+
+ WL_AMPDU_TX(("wl%d: wlc_sendampdu: mpdu %d plcp_len %d\n",
+ wlc->pub->unit, count, len));
+
+ /*
+ * aggregateable mpdu. For ucode/hw agg,
+ * test whether need to break or change the epoch
+ */
+ if (count == 0) {
+ u16 fc;
+ mcl |= (TXC_AMPDU_FIRST << TXC_AMPDU_SHIFT);
+ /* refill the bits since might be a retx mpdu */
+ mcl |= TXC_STARTMSDU;
+ rts = (struct dot11_rts_frame *)&txh->rts_frame;
+ fc = ltoh16(rts->fc);
+ if ((fc & FC_KIND_MASK) == FC_RTS) {
+ mcl |= TXC_SENDRTS;
+ use_rts = true;
+ }
+ if ((fc & FC_KIND_MASK) == FC_CTS) {
+ mcl |= TXC_SENDCTS;
+ use_cts = true;
+ }
+ } else {
+ mcl |= (TXC_AMPDU_MIDDLE << TXC_AMPDU_SHIFT);
+ mcl &= ~(TXC_STARTMSDU | TXC_SENDRTS | TXC_SENDCTS);
+ }
+
+ len = roundup(len, 4);
+ ampdu_len += (len + (ndelim + 1) * AMPDU_DELIMITER_LEN);
+
+ dma_len += (u16) pkttotlen(osh, p);
+
+ WL_AMPDU_TX(("wl%d: wlc_sendampdu: ampdu_len %d seg_cnt %d null delim %d\n", wlc->pub->unit, ampdu_len, seg_cnt, ndelim));
+
+ txh->MacTxControlLow = htol16(mcl);
+
+ /* this packet is added */
+ pkt[count++] = p;
+
+ /* patch the first MPDU */
+ if (count == 1) {
+ u8 plcp0, plcp3, is40, sgi;
+ struct ieee80211_sta *sta;
+
+ sta = tx_info->control.sta;
+
+ if (rr) {
+ plcp0 = plcp[0];
+ plcp3 = plcp[3];
+ } else {
+ plcp0 = txh->FragPLCPFallback[0];
+ plcp3 = txh->FragPLCPFallback[3];
+
+ }
+ is40 = (plcp0 & MIMO_PLCP_40MHZ) ? 1 : 0;
+ sgi = PLCP3_ISSGI(plcp3) ? 1 : 0;
+ mcs = plcp0 & ~MIMO_PLCP_40MHZ;
+ ASSERT(mcs < MCS_TABLE_SIZE);
+ maxlen =
+ min(scb_ampdu->max_rxlen,
+ ampdu->max_txlen[mcs][is40][sgi]);
+
+ WL_NONE(("sendampdu: sgi %d, is40 %d, mcs %d\n", sgi,
+ is40, mcs));
+
+ maxlen = 64 * 1024; /* XXX Fix me to honor real max_rxlen */
+
+ if (is40)
+ mimo_ctlchbw =
+ CHSPEC_SB_UPPER(WLC_BAND_PI_RADIO_CHANSPEC)
+ ? PHY_TXC1_BW_20MHZ_UP : PHY_TXC1_BW_20MHZ;
+
+ /* rebuild the rspec and rspec_fallback */
+ rspec = RSPEC_MIMORATE;
+ rspec |= plcp[0] & ~MIMO_PLCP_40MHZ;
+ if (plcp[0] & MIMO_PLCP_40MHZ)
+ rspec |= (PHY_TXC1_BW_40MHZ << RSPEC_BW_SHIFT);
+
+ if (fbr_iscck) /* CCK */
+ rspec_fallback =
+ CCK_RSPEC(CCK_PHY2MAC_RATE
+ (txh->FragPLCPFallback[0]));
+ else { /* MIMO */
+ rspec_fallback = RSPEC_MIMORATE;
+ rspec_fallback |=
+ txh->FragPLCPFallback[0] & ~MIMO_PLCP_40MHZ;
+ if (txh->FragPLCPFallback[0] & MIMO_PLCP_40MHZ)
+ rspec_fallback |=
+ (PHY_TXC1_BW_40MHZ <<
+ RSPEC_BW_SHIFT);
+ }
+
+ if (use_rts || use_cts) {
+ rts_rspec =
+ wlc_rspec_to_rts_rspec(wlc, rspec, false,
+ mimo_ctlchbw);
+ rts_rspec_fallback =
+ wlc_rspec_to_rts_rspec(wlc, rspec_fallback,
+ false, mimo_ctlchbw);
+ }
+ }
+
+ /* if (first mpdu for host agg) */
+ /* test whether to add more */
+ if ((MCS_RATE(mcs, true, false) >= f->dmaxferrate) &&
+ (count == f->mcs2ampdu_table[mcs])) {
+ WL_AMPDU_ERR(("wl%d: PR 37644: stopping ampdu at %d for mcs %d", wlc->pub->unit, count, mcs));
+ break;
+ }
+
+ if (count == scb_ampdu->max_pdu) {
+ WL_NONE(("Stop taking from q, reached %d deep\n",
+ scb_ampdu->max_pdu));
+ break;
+ }
+
+ /* check to see if the next pkt is a candidate for aggregation */
+ p = pktq_ppeek(&qi->q, prec);
+ tx_info = IEEE80211_SKB_CB(p); /* tx_info must be checked with current p */
+
+ if (p) {
+ if ((tx_info->flags & IEEE80211_TX_CTL_AMPDU) &&
+ ((u8) PKTPRIO(p) == tid)) {
+
+ plen =
+ pkttotlen(osh, p) + AMPDU_MAX_MPDU_OVERHEAD;
+ plen = max(scb_ampdu->min_len, plen);
+
+ if ((plen + ampdu_len) > maxlen) {
+ p = NULL;
+ WL_ERROR(("%s: Bogus plen #1\n",
+ __func__));
+ ASSERT(3 == 4);
+ continue;
+ }
+
+ /* check if there are enough descriptors available */
+ if (TXAVAIL(wlc, fifo) <= (seg_cnt + 1)) {
+ WL_ERROR(("%s: No fifo space !!!!!!\n", __func__));
+ p = NULL;
+ continue;
+ }
+ p = pktq_pdeq(&qi->q, prec);
+ ASSERT(p);
+ } else {
+ p = NULL;
+ }
+ }
+ } /* end while(p) */
+
+ ini->tx_in_transit += count;
+
+ if (count) {
+ WLCNTADD(ampdu->cnt->txmpdu, count);
+
+ /* patch up the last txh */
+ txh = (d11txh_t *) PKTDATA(pkt[count - 1]);
+ mcl = ltoh16(txh->MacTxControlLow);
+ mcl &= ~TXC_AMPDU_MASK;
+ mcl |= (TXC_AMPDU_LAST << TXC_AMPDU_SHIFT);
+ txh->MacTxControlLow = htol16(mcl);
+
+ /* remove the null delimiter after last mpdu */
+ ndelim = txh->RTSPLCPFallback[AMPDU_FBR_NULL_DELIM];
+ txh->RTSPLCPFallback[AMPDU_FBR_NULL_DELIM] = 0;
+ ampdu_len -= ndelim * AMPDU_DELIMITER_LEN;
+
+ /* remove the pad len from last mpdu */
+ fbr_iscck = ((ltoh16(txh->XtraFrameTypes) & 0x3) == 0);
+ len = fbr_iscck ? WLC_GET_CCK_PLCP_LEN(txh->FragPLCPFallback)
+ : WLC_GET_MIMO_PLCP_LEN(txh->FragPLCPFallback);
+ ampdu_len -= roundup(len, 4) - len;
+
+ /* patch up the first txh & plcp */
+ txh = (d11txh_t *) PKTDATA(pkt[0]);
+ plcp = (u8 *) (txh + 1);
+
+ WLC_SET_MIMO_PLCP_LEN(plcp, ampdu_len);
+ /* mark plcp to indicate ampdu */
+ WLC_SET_MIMO_PLCP_AMPDU(plcp);
+
+ /* reset the mixed mode header durations */
+ if (txh->MModeLen) {
+ u16 mmodelen =
+ wlc_calc_lsig_len(wlc, rspec, ampdu_len);
+ txh->MModeLen = htol16(mmodelen);
+ preamble_type = WLC_MM_PREAMBLE;
+ }
+ if (txh->MModeFbrLen) {
+ u16 mmfbrlen =
+ wlc_calc_lsig_len(wlc, rspec_fallback, ampdu_len);
+ txh->MModeFbrLen = htol16(mmfbrlen);
+ fbr_preamble_type = WLC_MM_PREAMBLE;
+ }
+
+ /* set the preload length */
+ if (MCS_RATE(mcs, true, false) >= f->dmaxferrate) {
+ dma_len = min(dma_len, f->ampdu_pld_size);
+ txh->PreloadSize = htol16(dma_len);
+ } else
+ txh->PreloadSize = 0;
+
+ mch = ltoh16(txh->MacTxControlHigh);
+
+ /* update RTS dur fields */
+ if (use_rts || use_cts) {
+ u16 durid;
+ rts = (struct dot11_rts_frame *)&txh->rts_frame;
+ if ((mch & TXC_PREAMBLE_RTS_MAIN_SHORT) ==
+ TXC_PREAMBLE_RTS_MAIN_SHORT)
+ rts_preamble_type = WLC_SHORT_PREAMBLE;
+
+ if ((mch & TXC_PREAMBLE_RTS_FB_SHORT) ==
+ TXC_PREAMBLE_RTS_FB_SHORT)
+ rts_fbr_preamble_type = WLC_SHORT_PREAMBLE;
+
+ durid =
+ wlc_compute_rtscts_dur(wlc, use_cts, rts_rspec,
+ rspec, rts_preamble_type,
+ preamble_type, ampdu_len,
+ true);
+ rts->durid = htol16(durid);
+ durid = wlc_compute_rtscts_dur(wlc, use_cts,
+ rts_rspec_fallback,
+ rspec_fallback,
+ rts_fbr_preamble_type,
+ fbr_preamble_type,
+ ampdu_len, true);
+ txh->RTSDurFallback = htol16(durid);
+ /* set TxFesTimeNormal */
+ txh->TxFesTimeNormal = rts->durid;
+ /* set fallback rate version of TxFesTimeNormal */
+ txh->TxFesTimeFallback = txh->RTSDurFallback;
+ }
+
+ /* set flag and plcp for fallback rate */
+ if (fbr) {
+ WLCNTADD(ampdu->cnt->txfbr_mpdu, count);
+ WLCNTINCR(ampdu->cnt->txfbr_ampdu);
+ mch |= TXC_AMPDU_FBR;
+ txh->MacTxControlHigh = htol16(mch);
+ WLC_SET_MIMO_PLCP_AMPDU(plcp);
+ WLC_SET_MIMO_PLCP_AMPDU(txh->FragPLCPFallback);
+ }
+
+ WL_AMPDU_TX(("wl%d: wlc_sendampdu: count %d ampdu_len %d\n",
+ wlc->pub->unit, count, ampdu_len));
+
+ /* inform rate_sel if it this is a rate probe pkt */
+ frameid = ltoh16(txh->TxFrameID);
+ if (frameid & TXFID_RATE_PROBE_MASK) {
+ WL_ERROR(("%s: XXX what to do with TXFID_RATE_PROBE_MASK!?\n", __func__));
+ }
+#ifdef WLC_HIGH_ONLY
+ if (wlc->rpc_agg & BCM_RPC_TP_HOST_AGG_AMPDU)
+ bcm_rpc_tp_agg_set(bcm_rpc_tp_get(wlc->rpc),
+ BCM_RPC_TP_HOST_AGG_AMPDU, true);
+#endif
+ for (i = 0; i < count; i++)
+ wlc_txfifo(wlc, fifo, pkt[i], i == (count - 1),
+ ampdu->txpkt_weight);
+#ifdef WLC_HIGH_ONLY
+ if (wlc->rpc_agg & BCM_RPC_TP_HOST_AGG_AMPDU)
+ bcm_rpc_tp_agg_set(bcm_rpc_tp_get(wlc->rpc),
+ BCM_RPC_TP_HOST_AGG_AMPDU, false);
+#endif
+
+ }
+ /* endif (count) */
+ return err;
+}
+
+void BCMFASTPATH
+wlc_ampdu_dotxstatus(ampdu_info_t *ampdu, struct scb *scb, void *p,
+ tx_status_t *txs)
+{
+ scb_ampdu_t *scb_ampdu;
+ wlc_info_t *wlc = ampdu->wlc;
+ scb_ampdu_tid_ini_t *ini;
+ u32 s1 = 0, s2 = 0;
+ struct ieee80211_tx_info *tx_info;
+
+ tx_info = IEEE80211_SKB_CB(p);
+ ASSERT(tx_info->flags & IEEE80211_TX_CTL_AMPDU);
+ ASSERT(scb);
+ ASSERT(scb->magic == SCB_MAGIC);
+ ASSERT(txs->status & TX_STATUS_AMPDU);
+ scb_ampdu = SCB_AMPDU_CUBBY(ampdu, scb);
+ ASSERT(scb_ampdu);
+ ini = SCB_AMPDU_INI(scb_ampdu, PKTPRIO(p));
+ ASSERT(ini->scb == scb);
+
+ /* BMAC_NOTE: For the split driver, second level txstatus comes later
+ * So if the ACK was received then wait for the second level else just
+ * call the first one
+ */
+ if (txs->status & TX_STATUS_ACK_RCV) {
+#ifdef WLC_LOW
+ u8 status_delay = 0;
+
+ /* wait till the next 8 bytes of txstatus is available */
+ while (((s1 =
+ R_REG(wlc->osh,
+ &wlc->regs->frmtxstatus)) & TXS_V) == 0) {
+ udelay(1);
+ status_delay++;
+ if (status_delay > 10) {
+ ASSERT(status_delay <= 10);
+ return;
+ }
+ }
+
+ ASSERT(!(s1 & TX_STATUS_INTERMEDIATE));
+ ASSERT(s1 & TX_STATUS_AMPDU);
+ s2 = R_REG(wlc->osh, &wlc->regs->frmtxstatus2);
+#else /* WLC_LOW */
+
+ /* Store the relevant information in ampdu structure */
+ WL_AMPDU_TX(("wl%d: wlc_ampdu_dotxstatus: High Recvd\n",
+ wlc->pub->unit));
+
+ ASSERT(!ampdu->p);
+ ampdu->p = p;
+ bcopy(txs, &ampdu->txs, sizeof(tx_status_t));
+ ampdu->waiting_status = true;
+ return;
+#endif /* WLC_LOW */
+ }
+
+ wlc_ampdu_dotxstatus_complete(ampdu, scb, p, txs, s1, s2);
+ wlc_ampdu_txflowcontrol(wlc, scb_ampdu, ini);
+}
+
+#ifdef WLC_HIGH_ONLY
+void wlc_ampdu_txstatus_complete(ampdu_info_t *ampdu, u32 s1, u32 s2)
+{
+ WL_AMPDU_TX(("wl%d: wlc_ampdu_txstatus_complete: High Recvd 0x%x 0x%x p:%p\n", ampdu->wlc->pub->unit, s1, s2, ampdu->p));
+
+ ASSERT(ampdu->waiting_status);
+
+ /* The packet may have been freed if the SCB went away, if so, then still free the
+ * DMA chain
+ */
+ if (ampdu->p) {
+ struct ieee80211_tx_info *tx_info;
+ struct scb *scb;
+
+ tx_info = IEEE80211_SKB_CB(ampdu->p);
+ scb = (struct scb *)tx_info->control.sta->drv_priv;
+
+ wlc_ampdu_dotxstatus_complete(ampdu, scb, ampdu->p, &ampdu->txs,
+ s1, s2);
+ ampdu->p = NULL;
+ }
+
+ ampdu->waiting_status = false;
+}
+#endif /* WLC_HIGH_ONLY */
+void rate_status(wlc_info_t *wlc, struct ieee80211_tx_info *tx_info,
+ tx_status_t *txs, u8 mcs);
+
+void
+rate_status(wlc_info_t *wlc, struct ieee80211_tx_info *tx_info,
+ tx_status_t *txs, u8 mcs)
+{
+ struct ieee80211_tx_rate *txrate = tx_info->status.rates;
+ int i;
+
+ /* clear the rest of the rates */
+ for (i = 2; i < IEEE80211_TX_MAX_RATES; i++) {
+ txrate[i].idx = -1;
+ txrate[i].count = 0;
+ }
+}
+
+extern void wlc_txq_enq(wlc_info_t *wlc, struct scb *scb, void *sdu,
+ uint prec);
+
+#define SHORTNAME "AMPDU status"
+
+static void BCMFASTPATH
+wlc_ampdu_dotxstatus_complete(ampdu_info_t *ampdu, struct scb *scb, void *p,
+ tx_status_t *txs, u32 s1, u32 s2)
+{
+ scb_ampdu_t *scb_ampdu;
+ wlc_info_t *wlc = ampdu->wlc;
+ scb_ampdu_tid_ini_t *ini;
+ u8 bitmap[8], queue, tid;
+ d11txh_t *txh;
+ u8 *plcp;
+ struct dot11_header *h;
+ u16 seq, start_seq = 0, bindex, index, mcl;
+ u8 mcs = 0;
+ bool ba_recd = false, ack_recd = false;
+ u8 suc_mpdu = 0, tot_mpdu = 0;
+ uint supr_status;
+ bool update_rate = true, retry = true, tx_error = false;
+ u16 mimoantsel = 0;
+ u8 antselid = 0;
+ u8 retry_limit, rr_retry_limit;
+ struct ieee80211_tx_info *tx_info = IEEE80211_SKB_CB(p);
+
+#ifdef BCMDBG
+ u8 hole[AMPDU_MAX_MPDU];
+ bzero(hole, sizeof(hole));
+#endif
+
+ ASSERT(tx_info->flags & IEEE80211_TX_CTL_AMPDU);
+ ASSERT(txs->status & TX_STATUS_AMPDU);
+
+ scb_ampdu = SCB_AMPDU_CUBBY(ampdu, scb);
+ ASSERT(scb_ampdu);
+
+ tid = (u8) PKTPRIO(p);
+
+ ini = SCB_AMPDU_INI(scb_ampdu, tid);
+ retry_limit = ampdu->retry_limit_tid[tid];
+ rr_retry_limit = ampdu->rr_retry_limit_tid[tid];
+
+ ASSERT(ini->scb == scb);
+
+ bzero(bitmap, sizeof(bitmap));
+ queue = txs->frameid & TXFID_QUEUE_MASK;
+ ASSERT(queue < AC_COUNT);
+
+ supr_status = txs->status & TX_STATUS_SUPR_MASK;
+
+ if (txs->status & TX_STATUS_ACK_RCV) {
+ if (TX_STATUS_SUPR_UF == supr_status) {
+ update_rate = false;
+ }
+
+ ASSERT(txs->status & TX_STATUS_INTERMEDIATE);
+ start_seq = txs->sequence >> SEQNUM_SHIFT;
+ bitmap[0] = (txs->status & TX_STATUS_BA_BMAP03_MASK) >>
+ TX_STATUS_BA_BMAP03_SHIFT;
+
+ ASSERT(!(s1 & TX_STATUS_INTERMEDIATE));
+ ASSERT(s1 & TX_STATUS_AMPDU);
+
+ bitmap[0] |=
+ (s1 & TX_STATUS_BA_BMAP47_MASK) <<
+ TX_STATUS_BA_BMAP47_SHIFT;
+ bitmap[1] = (s1 >> 8) & 0xff;
+ bitmap[2] = (s1 >> 16) & 0xff;
+ bitmap[3] = (s1 >> 24) & 0xff;
+
+ bitmap[4] = s2 & 0xff;
+ bitmap[5] = (s2 >> 8) & 0xff;
+ bitmap[6] = (s2 >> 16) & 0xff;
+ bitmap[7] = (s2 >> 24) & 0xff;
+
+ ba_recd = true;
+ } else {
+ WLCNTINCR(ampdu->cnt->noba);
+ if (supr_status) {
+ update_rate = false;
+ if (supr_status == TX_STATUS_SUPR_BADCH) {
+ WL_ERROR(("%s: Pkt tx suppressed, illegal channel possibly %d\n", __func__, CHSPEC_CHANNEL(wlc->default_bss->chanspec)));
+ } else {
+ if (supr_status == TX_STATUS_SUPR_FRAG)
+ WL_NONE(("%s: AMPDU frag err\n",
+ __func__));
+ else
+ WL_ERROR(("%s: wlc_ampdu_dotxstatus: supr_status 0x%x\n", __func__, supr_status));
+ }
+ /* no need to retry for badch; will fail again */
+ if (supr_status == TX_STATUS_SUPR_BADCH ||
+ supr_status == TX_STATUS_SUPR_EXPTIME) {
+ retry = false;
+ WLCNTINCR(wlc->pub->_cnt->txchanrej);
+ } else if (supr_status == TX_STATUS_SUPR_EXPTIME) {
+
+ WLCNTINCR(wlc->pub->_cnt->txexptime);
+
+ /* TX underflow : try tuning pre-loading or ampdu size */
+ } else if (supr_status == TX_STATUS_SUPR_FRAG) {
+ /* if there were underflows, but pre-loading is not active,
+ notify rate adaptation.
+ */
+ if (wlc_ffpld_check_txfunfl(wlc, prio2fifo[tid])
+ > 0) {
+ tx_error = true;
+#ifdef WLC_HIGH_ONLY
+ /* With BMAC, TX Underflows should not happen */
+ WL_ERROR(("wl%d: BMAC TX Underflow?",
+ wlc->pub->unit));
+#endif
+ }
+ }
+ } else if (txs->phyerr) {
+ update_rate = false;
+ WLCNTINCR(wlc->pub->_cnt->txphyerr);
+ WL_ERROR(("wl%d: wlc_ampdu_dotxstatus: tx phy error (0x%x)\n", wlc->pub->unit, txs->phyerr));
+
+#ifdef BCMDBG
+ if (WL_ERROR_ON()) {
+ prpkt("txpkt (AMPDU)", wlc->osh, p);
+ wlc_print_txdesc((d11txh_t *) PKTDATA(p));
+ wlc_print_txstatus(txs);
+ }
+#endif /* BCMDBG */
+ }
+ }
+
+ /* loop through all pkts and retry if not acked */
+ while (p) {
+ tx_info = IEEE80211_SKB_CB(p);
+ ASSERT(tx_info->flags & IEEE80211_TX_CTL_AMPDU);
+ txh = (d11txh_t *) PKTDATA(p);
+ mcl = ltoh16(txh->MacTxControlLow);
+ plcp = (u8 *) (txh + 1);
+ h = (struct dot11_header *)(plcp + D11_PHY_HDR_LEN);
+ seq = ltoh16(h->seq) >> SEQNUM_SHIFT;
+
+ if (tot_mpdu == 0) {
+ mcs = plcp[0] & MIMO_PLCP_MCS_MASK;
+ mimoantsel = ltoh16(txh->ABI_MimoAntSel);
+ }
+
+ index = TX_SEQ_TO_INDEX(seq);
+ ack_recd = false;
+ if (ba_recd) {
+ bindex = MODSUB_POW2(seq, start_seq, SEQNUM_MAX);
+
+ WL_AMPDU_TX(("%s: tid %d seq is %d, start_seq is %d, "
+ "bindex is %d set %d, index %d\n",
+ __func__, tid, seq, start_seq, bindex,
+ isset(bitmap, bindex), index));
+
+ /* if acked then clear bit and free packet */
+ if ((bindex < AMPDU_TX_BA_MAX_WSIZE)
+ && isset(bitmap, bindex)) {
+ ini->tx_in_transit--;
+ ini->txretry[index] = 0;
+
+ /* ampdu_ack_len: number of acked aggregated frames */
+ /* ampdu_ack_map: block ack bit map for the aggregation */
+ /* ampdu_len: number of aggregated frames */
+ rate_status(wlc, tx_info, txs, mcs);
+ tx_info->flags |= IEEE80211_TX_STAT_ACK;
+ tx_info->flags |= IEEE80211_TX_STAT_AMPDU;
+
+ /* XXX TODO: Make these accurate. */
+ tx_info->status.ampdu_ack_len =
+ (txs->
+ status & TX_STATUS_FRM_RTX_MASK) >>
+ TX_STATUS_FRM_RTX_SHIFT;
+ tx_info->status.ampdu_len =
+ (txs->
+ status & TX_STATUS_FRM_RTX_MASK) >>
+ TX_STATUS_FRM_RTX_SHIFT;
+
+ PKTPULL(p, D11_PHY_HDR_LEN);
+ PKTPULL(p, D11_TXH_LEN);
+
+ ieee80211_tx_status_irqsafe(wlc->pub->ieee_hw,
+ p);
+ ack_recd = true;
+ suc_mpdu++;
+ }
+ }
+ /* either retransmit or send bar if ack not recd */
+ if (!ack_recd) {
+ struct ieee80211_tx_rate *txrate =
+ tx_info->status.rates;
+ if (retry && (txrate[0].count < (int)retry_limit)) {
+ ini->txretry[index]++;
+ ini->tx_in_transit--;
+ /* Use high prededence for retransmit to give some punch */
+ /* wlc_txq_enq(wlc, scb, p, WLC_PRIO_TO_PREC(tid)); */
+ wlc_txq_enq(wlc, scb, p,
+ WLC_PRIO_TO_HI_PREC(tid));
+ } else {
+ /* Retry timeout */
+ ini->tx_in_transit--;
+ ieee80211_tx_info_clear_status(tx_info);
+ tx_info->flags |=
+ IEEE80211_TX_STAT_AMPDU_NO_BACK;
+ PKTPULL(p, D11_PHY_HDR_LEN);
+ PKTPULL(p, D11_TXH_LEN);
+ WL_ERROR(("%s: BA Timeout, seq %d, in_transit %d\n", SHORTNAME, seq, ini->tx_in_transit));
+ ieee80211_tx_status_irqsafe(wlc->pub->ieee_hw,
+ p);
+ }
+ }
+ tot_mpdu++;
+
+ /* break out if last packet of ampdu */
+ if (((mcl & TXC_AMPDU_MASK) >> TXC_AMPDU_SHIFT) ==
+ TXC_AMPDU_LAST)
+ break;
+
+ p = GETNEXTTXP(wlc, queue);
+ if (p == NULL) {
+ ASSERT(p);
+ break;
+ }
+ }
+ wlc_send_q(wlc, wlc->active_queue);
+
+ /* update rate state */
+ if (WLANTSEL_ENAB(wlc))
+ antselid = wlc_antsel_antsel2id(wlc->asi, mimoantsel);
+
+ wlc_txfifo_complete(wlc, queue, ampdu->txpkt_weight);
+}
+
+static void
+ampdu_cleanup_tid_ini(ampdu_info_t *ampdu, scb_ampdu_t *scb_ampdu, u8 tid,
+ bool force)
+{
+ scb_ampdu_tid_ini_t *ini;
+ ini = SCB_AMPDU_INI(scb_ampdu, tid);
+ if (!ini)
+ return;
+
+ WL_AMPDU_CTL(("wl%d: ampdu_cleanup_tid_ini: tid %d\n",
+ ampdu->wlc->pub->unit, tid));
+
+ if (ini->tx_in_transit && !force)
+ return;
+
+ scb_ampdu = SCB_AMPDU_CUBBY(ampdu, ini->scb);
+ ASSERT(ini == &scb_ampdu->ini[ini->tid]);
+
+ /* free all buffered tx packets */
+ pktq_pflush(ampdu->wlc->osh, &scb_ampdu->txq, ini->tid, true, NULL, 0);
+}
+
+/* initialize the initiator code for tid */
+static scb_ampdu_tid_ini_t *wlc_ampdu_init_tid_ini(ampdu_info_t *ampdu,
+ scb_ampdu_t *scb_ampdu,
+ u8 tid, bool override)
+{
+ scb_ampdu_tid_ini_t *ini;
+
+ ASSERT(scb_ampdu);
+ ASSERT(scb_ampdu->scb);
+ ASSERT(SCB_AMPDU(scb_ampdu->scb));
+ ASSERT(tid < AMPDU_MAX_SCB_TID);
+
+ /* check for per-tid control of ampdu */
+ if (!ampdu->ini_enable[tid]) {
+ WL_ERROR(("%s: Rejecting tid %d\n", __func__, tid));
+ return NULL;
+ }
+
+ ini = SCB_AMPDU_INI(scb_ampdu, tid);
+ ini->tid = tid;
+ ini->scb = scb_ampdu->scb;
+ ini->magic = INI_MAGIC;
+ WLCNTINCR(ampdu->cnt->txaddbareq);
+
+ return ini;
+}
+
+int wlc_ampdu_set(ampdu_info_t *ampdu, bool on)
+{
+ wlc_info_t *wlc = ampdu->wlc;
+
+ wlc->pub->_ampdu = false;
+
+ if (on) {
+ if (!N_ENAB(wlc->pub)) {
+ WL_AMPDU_ERR(("wl%d: driver not nmode enabled\n",
+ wlc->pub->unit));
+ return BCME_UNSUPPORTED;
+ }
+ if (!wlc_ampdu_cap(ampdu)) {
+ WL_AMPDU_ERR(("wl%d: device not ampdu capable\n",
+ wlc->pub->unit));
+ return BCME_UNSUPPORTED;
+ }
+ wlc->pub->_ampdu = on;
+ }
+
+ return 0;
+}
+
+bool wlc_ampdu_cap(ampdu_info_t *ampdu)
+{
+ if (WLC_PHY_11N_CAP(ampdu->wlc->band))
+ return true;
+ else
+ return false;
+}
+
+static void ampdu_update_max_txlen(ampdu_info_t *ampdu, u8 dur)
+{
+ u32 rate, mcs;
+
+ for (mcs = 0; mcs < MCS_TABLE_SIZE; mcs++) {
+ /* rate is in Kbps; dur is in msec ==> len = (rate * dur) / 8 */
+ /* 20MHz, No SGI */
+ rate = MCS_RATE(mcs, false, false);
+ ampdu->max_txlen[mcs][0][0] = (rate * dur) >> 3;
+ /* 40 MHz, No SGI */
+ rate = MCS_RATE(mcs, true, false);
+ ampdu->max_txlen[mcs][1][0] = (rate * dur) >> 3;
+ /* 20MHz, SGI */
+ rate = MCS_RATE(mcs, false, true);
+ ampdu->max_txlen[mcs][0][1] = (rate * dur) >> 3;
+ /* 40 MHz, SGI */
+ rate = MCS_RATE(mcs, true, true);
+ ampdu->max_txlen[mcs][1][1] = (rate * dur) >> 3;
+ }
+}
+
+u8 BCMFASTPATH
+wlc_ampdu_null_delim_cnt(ampdu_info_t *ampdu, struct scb *scb,
+ ratespec_t rspec, int phylen)
+{
+ scb_ampdu_t *scb_ampdu;
+ int bytes, cnt, tmp;
+ u8 tx_density;
+
+ ASSERT(scb);
+ ASSERT(SCB_AMPDU(scb));
+
+ scb_ampdu = SCB_AMPDU_CUBBY(ampdu, scb);
+ ASSERT(scb_ampdu);
+
+ if (scb_ampdu->mpdu_density == 0)
+ return 0;
+
+ /* RSPEC2RATE is in kbps units ==> ~RSPEC2RATE/2^13 is in bytes/usec
+ density x is in 2^(x-4) usec
+ ==> # of bytes needed for req density = rate/2^(17-x)
+ ==> # of null delimiters = ceil(ceil(rate/2^(17-x)) - phylen)/4)
+ */
+
+ tx_density = scb_ampdu->mpdu_density;
+
+ ASSERT(tx_density <= AMPDU_MAX_MPDU_DENSITY);
+ tmp = 1 << (17 - tx_density);
+ bytes = CEIL(RSPEC2RATE(rspec), tmp);
+
+ if (bytes > phylen) {
+ cnt = CEIL(bytes - phylen, AMPDU_DELIMITER_LEN);
+ ASSERT(cnt <= 255);
+ return (u8) cnt;
+ } else
+ return 0;
+}
+
+void wlc_ampdu_macaddr_upd(wlc_info_t *wlc)
+{
+ char template[T_RAM_ACCESS_SZ * 2];
+
+ /* driver needs to write the ta in the template; ta is at offset 16 */
+ bzero(template, sizeof(template));
+ bcopy((char *)wlc->pub->cur_etheraddr.octet, template, ETHER_ADDR_LEN);
+ wlc_write_template_ram(wlc, (T_BA_TPL_BASE + 16), (T_RAM_ACCESS_SZ * 2),
+ template);
+}
+
+bool wlc_aggregatable(wlc_info_t *wlc, u8 tid)
+{
+ return wlc->ampdu->ini_enable[tid];
+}
+
+void wlc_ampdu_shm_upd(ampdu_info_t *ampdu)
+{
+ wlc_info_t *wlc = ampdu->wlc;
+
+ /* Extend ucode internal watchdog timer to match larger received frames */
+ if ((ampdu->rx_factor & HT_PARAMS_RX_FACTOR_MASK) ==
+ AMPDU_RX_FACTOR_64K) {
+ wlc_write_shm(wlc, M_MIMO_MAXSYM, MIMO_MAXSYM_MAX);
+ wlc_write_shm(wlc, M_WATCHDOG_8TU, WATCHDOG_8TU_MAX);
+ } else {
+ wlc_write_shm(wlc, M_MIMO_MAXSYM, MIMO_MAXSYM_DEF);
+ wlc_write_shm(wlc, M_WATCHDOG_8TU, WATCHDOG_8TU_DEF);
+ }
+}
OpenPOWER on IntegriCloud