Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit e041f65d authored by Seth Forshee's avatar Seth Forshee Committed by John W. Linville
Browse files

brcmsmac: Remove internal tx queue



The brcmsmac internal tx buffering is problematic. The amount of
buffering is excessive (228 packets in addition to the 256 slots in each
DMA ring), and frames may be dropped due to a lack of flow control.

This patch reworks the transmit code path to remove the internal
buffering. Frames are immediately handed off to the DMA support rather
than passing through an intermediate queue. Non-aggregate frames are
queued immediately into the tx rings, and aggregate frames are queued
temporarily in an AMPDU session until ready for transmit.

Transmit flow control is also added to avoid dropping packets when the
tx rings are full. Conceptually this is a separate change, but it's
included in this commit because removing the tx queue without adding
flow control could cause significant problems.

Signed-off-by: default avatarSeth Forshee <seth.forshee@canonical.com>
Reviewed-by: default avatarPieter-Paul Giesberts <pieterpg@broadcom.com>
Reviewed-by: default avatarArend van Spriel <arend@broadcom.com>
Tested-by: default avatarDaniel Wagner <wagi@monom.org>
Signed-off-by: default avatarJohn W. Linville <linville@tuxdriver.com>
parent 32d0f12a
Loading
Loading
Loading
Loading
+8 −159
Original line number Diff line number Diff line
@@ -813,133 +813,6 @@ void brcms_c_ampdu_finalize(struct brcms_ampdu_session *session)
		session->ampdu_len);
}

int
brcms_c_sendampdu(struct ampdu_info *ampdu, struct brcms_txq_info *qi,
	      struct sk_buff **pdu, int prec)
{
	struct brcms_c_info *wlc;
	struct sk_buff *p;
	struct brcms_ampdu_session session;
	int err = 0;
	u8 tid;

	uint count, fifo, seg_cnt = 0;
	struct scb *scb;
	struct scb_ampdu *scb_ampdu;
	struct scb_ampdu_tid_ini *ini;
	struct ieee80211_tx_info *tx_info;
	u16 qlen;
	struct wiphy *wiphy;

	wlc = ampdu->wlc;
	wiphy = wlc->wiphy;
	p = *pdu;

	tid = (u8) (p->priority);

	scb = &wlc->pri_scb;
	scb_ampdu = &scb->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 < min(scb_ampdu->max_pdu, ini->ba_wsize))
		/* Collect multiple MPDU's to be sent in the next AMPDU */
		return -EBUSY;

	/* at this point we intend to transmit an AMPDU */
	brcms_c_ampdu_reset_session(&session, wlc);

	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 = brcms_c_prep_pdu(wlc, p, &fifo);
		} else {
			wiphy_err(wiphy, "%s: AMPDU flag is off!\n", __func__);
			*pdu = NULL;
			err = 0;
			break;
		}

		if (err) {
			if (err == -EBUSY) {
				wiphy_err(wiphy, "wl%d: sendampdu: "
					  "prep_xdu retry\n", wlc->pub->unit);
				*pdu = p;
				break;
			}

			/* error in the packet; reject it */
			wiphy_err(wiphy, "wl%d: sendampdu: prep_xdu "
				  "rejected\n", wlc->pub->unit);
			*pdu = NULL;
			break;
		}

		err = brcms_c_ampdu_add_frame(&session, p);
		if (err == -ENOSPC) {
			/*
			 * No space for this packet in the AMPDU.
			 * Requeue packet and proceed;
			 */
			*pdu = p;
			break;
		} else if (err) {
			/* Unexpected error; reject packet */
			wiphy_err(wiphy, "wl%d: sendampdu: add_frame rejected",
				  wlc->pub->unit);
			*pdu = NULL;
			break;
		}

		seg_cnt += 1;

		/*
		 * check to see if the next pkt is
		 * a candidate for aggregation
		 */
		p = pktq_ppeek(&qi->q, prec);
		if (p) {
			tx_info = IEEE80211_SKB_CB(p);
			if (tx_info->flags & IEEE80211_TX_CTL_AMPDU) {
				/*
				 * check if there are enough
				 * descriptors available
				 */
				if (*wlc->core->txavail[fifo] <= seg_cnt + 1) {
					wiphy_err(wiphy, "%s: No fifo space "
						  "!!\n", __func__);
					p = NULL;
					continue;
				}
				/* next packet fit for aggregation so dequeue */
				p = brcmu_pktq_pdeq(&qi->q, prec);
			} else {
				p = NULL;
			}
		}
	}			/* end while(p) */

	count = skb_queue_len(&session.skb_list);
	ini->tx_in_transit += count;

	if (count) {
		/* patch up first and last txh's */
		brcms_c_ampdu_finalize(&session);

		while ((p = skb_dequeue(&session.skb_list)) != NULL)
			brcms_c_txfifo(wlc, fifo, p,
				       skb_queue_empty(&session.skb_list));
	}
	/* endif (count) */
	return err;
}

static void
brcms_c_ampdu_rate_status(struct brcms_c_info *wlc,
			  struct ieee80211_tx_info *tx_info,
@@ -1113,9 +986,16 @@ brcms_c_ampdu_dotxstatus_complete(struct ampdu_info *ampdu, struct scb *scb,
		/* either retransmit or send bar if ack not recd */
		if (!ack_recd) {
			if (retry && (ini->txretry[index] < (int)retry_limit)) {
				int ret;
				ini->txretry[index]++;
				ini->tx_in_transit--;
				brcms_c_txq_enq(wlc, scb, p);
				ret = brcms_c_txfifo(wlc, queue, p);
				/*
				 * We shouldn't be out of space in the DMA
				 * ring here since we're reinserting a frame
				 * that was just pulled out.
				 */
				WARN_ONCE(ret, "queue %d out of txds\n", queue);
			} else {
				/* Retry timeout */
				ini->tx_in_transit--;
@@ -1142,12 +1022,9 @@ brcms_c_ampdu_dotxstatus_complete(struct ampdu_info *ampdu, struct scb *scb,

		p = dma_getnexttxp(wlc->hw->di[queue], DMA_RANGE_TRANSMITTED);
	}
	brcms_c_send_q(wlc);

	/* update rate state */
	antselid = brcms_c_antsel_antsel2id(wlc->asi, mimoantsel);

	brcms_c_txfifo_complete(wlc, queue);
}

void
@@ -1204,7 +1081,6 @@ brcms_c_ampdu_dotxstatus(struct ampdu_info *ampdu, struct scb *scb,
			p = dma_getnexttxp(wlc->hw->di[queue],
					   DMA_RANGE_TRANSMITTED);
		}
		brcms_c_txfifo_complete(wlc, queue);
	}
}

@@ -1243,23 +1119,6 @@ void brcms_c_ampdu_shm_upd(struct ampdu_info *ampdu)
	}
}

/*
 * callback function that helps flushing ampdu packets from a priority queue
 */
static bool cb_del_ampdu_pkt(struct sk_buff *mpdu, void *arg_a)
{
	struct ieee80211_tx_info *tx_info = IEEE80211_SKB_CB(mpdu);
	struct cb_del_ampdu_pars *ampdu_pars =
				 (struct cb_del_ampdu_pars *)arg_a;
	bool rc;

	rc = tx_info->flags & IEEE80211_TX_CTL_AMPDU ? true : false;
	rc = rc && (tx_info->rate_driver_data[0] == NULL || ampdu_pars->sta == NULL ||
		    tx_info->rate_driver_data[0] == ampdu_pars->sta);
	rc = rc && ((u8)(mpdu->priority) == ampdu_pars->tid);
	return rc;
}

/*
 * callback function that helps invalidating ampdu packets in a DMA queue
 */
@@ -1280,15 +1139,5 @@ static void dma_cb_fn_ampdu(void *txi, void *arg_a)
void brcms_c_ampdu_flush(struct brcms_c_info *wlc,
		     struct ieee80211_sta *sta, u16 tid)
{
	struct brcms_txq_info *qi = wlc->pkt_queue;
	struct pktq *pq = &qi->q;
	int prec;
	struct cb_del_ampdu_pars ampdu_pars;

	ampdu_pars.sta = sta;
	ampdu_pars.tid = tid;
	for (prec = 0; prec < pq->num_prec; prec++)
		brcmu_pktq_pflush(pq, prec, true, cb_del_ampdu_pkt,
			    (void *)&ampdu_pars);
	brcms_c_inval_dma_pkts(wlc->hw, sta, dma_cb_fn_ampdu);
}
+0 −3
Original line number Diff line number Diff line
@@ -45,9 +45,6 @@ extern void brcms_c_ampdu_finalize(struct brcms_ampdu_session *session);

extern struct ampdu_info *brcms_c_ampdu_attach(struct brcms_c_info *wlc);
extern void brcms_c_ampdu_detach(struct ampdu_info *ampdu);
extern int brcms_c_sendampdu(struct ampdu_info *ampdu,
			     struct brcms_txq_info *qi,
			     struct sk_buff **aggp, int prec);
extern void brcms_c_ampdu_dotxstatus(struct ampdu_info *ampdu, struct scb *scb,
				 struct sk_buff *p, struct tx_status *txs);
extern void brcms_c_ampdu_macaddr_upd(struct brcms_c_info *wlc);
+153 −36
Original line number Diff line number Diff line
@@ -19,12 +19,17 @@
#include <linux/slab.h>
#include <linux/delay.h>
#include <linux/pci.h>
#include <net/cfg80211.h>
#include <net/mac80211.h>

#include <brcmu_utils.h>
#include <aiutils.h>
#include "types.h"
#include "main.h"
#include "dma.h"
#include "soc.h"
#include "scb.h"
#include "ampdu.h"

/*
 * dma register field offset calculation
@@ -230,6 +235,9 @@ struct dma_info {
	struct bcma_device *core;
	struct device *dmadev;

	/* session information for AMPDU */
	struct brcms_ampdu_session ampdu_session;

	bool dma64;	/* this dma engine is operating in 64-bit mode */
	bool addrext;	/* this dma engine supports DmaExtendedAddrChanges */

@@ -564,12 +572,13 @@ static bool _dma_alloc(struct dma_info *di, uint direction)
	return dma64_alloc(di, direction);
}

struct dma_pub *dma_attach(char *name, struct si_pub *sih,
			   struct bcma_device *core,
struct dma_pub *dma_attach(char *name, struct brcms_c_info *wlc,
			   uint txregbase, uint rxregbase, uint ntxd, uint nrxd,
			   uint rxbufsize, int rxextheadroom,
			   uint nrxpost, uint rxoffset, uint *msg_level)
{
	struct si_pub *sih = wlc->hw->sih;
	struct bcma_device *core = wlc->hw->d11core;
	struct dma_info *di;
	u8 rev = core->id.rev;
	uint size;
@@ -714,6 +723,9 @@ struct dma_pub *dma_attach(char *name, struct si_pub *sih,
		}
	}

	/* Initialize AMPDU session */
	brcms_c_ampdu_reset_session(&di->ampdu_session, wlc);

	DMA_TRACE("ddoffsetlow 0x%x ddoffsethigh 0x%x dataoffsetlow 0x%x dataoffsethigh 0x%x addrext %d\n",
		  di->ddoffsetlow, di->ddoffsethigh,
		  di->dataoffsetlow, di->dataoffsethigh,
@@ -1016,6 +1028,17 @@ static bool dma64_rxidle(struct dma_info *di)
		 D64_RS0_CD_MASK));
}

static bool dma64_txidle(struct dma_info *di)
{
	if (di->ntxd == 0)
		return true;

	return ((bcma_read32(di->core,
			     DMA64TXREGOFFS(di, status0)) & D64_XS0_CD_MASK) ==
		(bcma_read32(di->core, DMA64TXREGOFFS(di, ptr)) &
		 D64_XS0_CD_MASK));
}

/*
 * post receive buffers
 *  return false is refill failed completely and ring is empty this will stall
@@ -1264,50 +1287,25 @@ bool dma_rxreset(struct dma_pub *pub)
	return status == D64_RS0_RS_DISABLED;
}

/* Update count of available tx descriptors based on current DMA state */
static void dma_update_txavail(struct dma_info *di)
static void dma_txenq(struct dma_info *di, struct sk_buff *p)
{
	/*
	 * Available space is number of descriptors less the number of
	 * active descriptors and the number of queued AMPDU frames.
	 */
	di->dma.txavail = di->ntxd - ntxdactive(di, di->txin, di->txout) - 1;
}


/*
 * !! tx entry routine
 * WARNING: call must check the return value for error.
 *   the error(toss frames) could be fatal and cause many subsequent hard
 *   to debug problems
 */
int dma_txfast(struct dma_pub *pub, struct sk_buff *p, bool commit)
{
	struct dma_info *di = (struct dma_info *)pub;
	unsigned char *data;
	uint len;
	u16 txout;
	u32 flags = 0;
	dma_addr_t pa;

	DMA_TRACE("%s:\n", di->name);

	txout = di->txout;

	if (WARN_ON(nexttxd(di, txout) == di->txin))
		return;

	/*
	 * obtain and initialize transmit descriptor entry.
	 */
	data = p->data;
	len = p->len;

	/* no use to transmit a zero length packet */
	if (len == 0)
		return 0;

	/* return nonzero if out of tx descriptors */
	if (nexttxd(di, txout) == di->txin)
		goto outoftxd;

	/* get physical address of buffer start */
	pa = dma_map_single(di->dmadev, data, len, DMA_TO_DEVICE);

@@ -1329,15 +1327,106 @@ int dma_txfast(struct dma_pub *pub, struct sk_buff *p, bool commit)

	/* bump the tx descriptor index */
	di->txout = txout;
}

static void ampdu_finalize(struct dma_info *di)
{
	struct brcms_ampdu_session *session = &di->ampdu_session;
	struct sk_buff *p;

	if (WARN_ON(skb_queue_empty(&session->skb_list)))
		return;

	brcms_c_ampdu_finalize(session);

	while (!skb_queue_empty(&session->skb_list)) {
		p = skb_dequeue(&session->skb_list);
		dma_txenq(di, p);
	}

	/* kick the chip */
	if (commit)
	bcma_write32(di->core, DMA64TXREGOFFS(di, ptr),
		      di->xmtptrbase + I2B(txout, struct dma64desc));
		     di->xmtptrbase + I2B(di->txout, struct dma64desc));
	brcms_c_ampdu_reset_session(session, session->wlc);
}

static void prep_ampdu_frame(struct dma_info *di, struct sk_buff *p)
{
	struct brcms_ampdu_session *session = &di->ampdu_session;
	int ret;

	ret = brcms_c_ampdu_add_frame(session, p);
	if (ret == -ENOSPC) {
		/*
		 * AMPDU cannot accomodate this frame. Close out the in-
		 * progress AMPDU session and start a new one.
		 */
		ampdu_finalize(di);
		ret = brcms_c_ampdu_add_frame(session, p);
	}

	WARN_ON(ret);
}

/* Update count of available tx descriptors based on current DMA state */
static void dma_update_txavail(struct dma_info *di)
{
	/*
	 * Available space is number of descriptors less the number of
	 * active descriptors and the number of queued AMPDU frames.
	 */
	di->dma.txavail = di->ntxd - ntxdactive(di, di->txin, di->txout) -
			  skb_queue_len(&di->ampdu_session.skb_list) - 1;
}

/*
 * !! tx entry routine
 * WARNING: call must check the return value for error.
 *   the error(toss frames) could be fatal and cause many subsequent hard
 *   to debug problems
 */
int dma_txfast(struct brcms_c_info *wlc, struct dma_pub *pub,
	       struct sk_buff *p)
{
	struct dma_info *di = (struct dma_info *)pub;
	struct brcms_ampdu_session *session = &di->ampdu_session;
	struct ieee80211_tx_info *tx_info;
	bool is_ampdu;

	DMA_TRACE("%s:\n", di->name);

	/* no use to transmit a zero length packet */
	if (p->len == 0)
		return 0;

	/* return nonzero if out of tx descriptors */
	if (di->dma.txavail == 0 || nexttxd(di, di->txout) == di->txin)
		goto outoftxd;

	tx_info = IEEE80211_SKB_CB(p);
	is_ampdu = tx_info->flags & IEEE80211_TX_CTL_AMPDU;
	if (is_ampdu)
		prep_ampdu_frame(di, p);
	else
		dma_txenq(di, p);

	/* tx flow control */
	dma_update_txavail(di);

	/* kick the chip */
	if (is_ampdu) {
		/*
		 * Start sending data if we've got a full AMPDU, there's
		 * no more space in the DMA ring, or the ring isn't
		 * currently transmitting.
		 */
		if (skb_queue_len(&session->skb_list) == session->max_ampdu_frames ||
		    di->dma.txavail == 0 || dma64_txidle(di))
			ampdu_finalize(di);
	} else {
		bcma_write32(di->core, DMA64TXREGOFFS(di, ptr),
			     di->xmtptrbase + I2B(di->txout, struct dma64desc));
	}

	return 0;

 outoftxd:
@@ -1345,7 +1434,35 @@ int dma_txfast(struct dma_pub *pub, struct sk_buff *p, bool commit)
	brcmu_pkt_buf_free_skb(p);
	di->dma.txavail = 0;
	di->dma.txnobuf++;
	return -1;
	return -ENOSPC;
}

void dma_txflush(struct dma_pub *pub)
{
	struct dma_info *di = (struct dma_info *)pub;
	struct brcms_ampdu_session *session = &di->ampdu_session;

	if (!skb_queue_empty(&session->skb_list))
		ampdu_finalize(di);
}

int dma_txpending(struct dma_pub *pub)
{
	struct dma_info *di = (struct dma_info *)pub;
	return ntxdactive(di, di->txin, di->txout);
}

/*
 * If we have an active AMPDU session and are not transmitting,
 * this function will force tx to start.
 */
void dma_kick_tx(struct dma_pub *pub)
{
	struct dma_info *di = (struct dma_info *)pub;
	struct brcms_ampdu_session *session = &di->ampdu_session;

	if (!skb_queue_empty(&session->skb_list) && dma64_txidle(di))
		ampdu_finalize(di);
}

/*
+6 −3
Original line number Diff line number Diff line
@@ -74,8 +74,7 @@ struct dma_pub {
	uint txnobuf;		/* tx out of dma descriptors */
};

extern struct dma_pub *dma_attach(char *name, struct si_pub *sih,
				  struct bcma_device *d11core,
extern struct dma_pub *dma_attach(char *name, struct brcms_c_info *wlc,
				  uint txregbase, uint rxregbase,
				  uint ntxd, uint nrxd,
				  uint rxbufsize, int rxextheadroom,
@@ -87,7 +86,11 @@ bool dma_rxfill(struct dma_pub *pub);
bool dma_rxreset(struct dma_pub *pub);
bool dma_txreset(struct dma_pub *pub);
void dma_txinit(struct dma_pub *pub);
int dma_txfast(struct dma_pub *pub, struct sk_buff *p0, bool commit);
int dma_txfast(struct brcms_c_info *wlc, struct dma_pub *pub,
	       struct sk_buff *p0);
void dma_txflush(struct dma_pub *pub);
int dma_txpending(struct dma_pub *pub);
void dma_kick_tx(struct dma_pub *pub);
void dma_txsuspend(struct dma_pub *pub);
bool dma_txsuspended(struct dma_pub *pub);
void dma_txresume(struct dma_pub *pub);
+147 −312

File changed.

Preview size limit exceeded, changes collapsed.

Loading