Implement BAR TX.

A BAR frame must be transmitted when an frame in an A-MPDU session fails
to transmit - it's retried too often, or it can't be cloned for
re-transmission.  The BAR frame tells the remote side to advance the
left edge of the block-ack window (BAW) to a new value.

In order to do this:

* TX for that particular node/TID must be paused;
* The existing frames in the hardware queue needs to be completed, whether
  they're TXed successfully or otherwise;
* The new left edge of the BAW is then communicated to the remote side
  via a BAR frame;
* Once the BAR frame has been sucessfully TXed, aggregation can resume;
* If the BAR frame can't be successfully TXed, the aggregation session
  is torn down.

This is a first pass that implements the above.  What needs to be done/
tested:

* What happens during say, a channel reset / stuck beacon _and_ BAR
  TX.  It _should_ be correctly buffered and retried once the
  reset has completed.  But if a bgscan occurs (and they shouldn't,
  grr) the BAR frame will be forcibly failed and the aggregation session
  will be torn down.

  Yes, another reason to disable bgscan until I've figured this out.

* There's way too much locking going on here.  I'm going to do a couple
  of further passes of sanitising and refactoring so the (re) locking
  isn't so heavy.  Right now I'm going for correctness, not speed.

* The BAR TX can fail if the hardware TX queue is full.  Since there's
  no "free" space kept for management frames, a full TX queue (from eg
  an iperf test) can race with your ability to allocate ath_buf/mbufs
  and cause issues.  I'll knock this on the head with a subsequent
  commit.

* I need to do some _much_ more thorough testing in hostap mode to ensure
  that many concurrent traffic streams to different end nodes are correctly
  handled.  I'll find and squish whichever bugs show up here.

But, this is an important step to being able to flip on 802.11n by default.
The last issue (besides bug fixes, of course) is HT frame protection and
I'll address that in a subsequent commit.
This commit is contained in:
Adrian Chadd 2012-04-04 23:45:15 +00:00
parent ae2e7abf3c
commit 88b3d48316
Notes: svn2git 2020-12-20 02:59:44 +00:00
svn path=/head/; revision=233908
2 changed files with 205 additions and 27 deletions

View File

@ -2598,11 +2598,11 @@ ath_tx_tid_init(struct ath_softc *sc, struct ath_node *an)
static void
ath_tx_tid_pause(struct ath_softc *sc, struct ath_tid *tid)
{
ATH_TXQ_LOCK(sc->sc_ac2q[tid->ac]);
ATH_TXQ_LOCK_ASSERT(sc->sc_ac2q[tid->ac]);
tid->paused++;
DPRINTF(sc, ATH_DEBUG_SW_TX_CTRL, "%s: paused = %d\n",
__func__, tid->paused);
ATH_TXQ_UNLOCK(sc->sc_ac2q[tid->ac]);
}
/*
@ -2628,6 +2628,158 @@ ath_tx_tid_resume(struct ath_softc *sc, struct ath_tid *tid)
taskqueue_enqueue(sc->sc_tq, &sc->sc_txqtask);
}
/*
* Suspend the queue because we need to TX a BAR.
*/
static void
ath_tx_tid_bar_suspend(struct ath_softc *sc, struct ath_tid *tid)
{
ATH_TXQ_LOCK_ASSERT(sc->sc_ac2q[tid->ac]);
DPRINTF(sc, ATH_DEBUG_SW_TX_BAW,
"%s: tid=%p, called\n",
__func__,
tid);
/* We shouldn't be called when bar_tx is 1 */
if (tid->bar_tx) {
device_printf(sc->sc_dev, "%s: bar_tx is 1?!\n",
__func__);
}
/* If we've already been called, just be patient. */
if (tid->bar_wait)
return;
/* Wait! */
tid->bar_wait = 1;
/* Only one pause, no matter how many frames fail */
ath_tx_tid_pause(sc, tid);
}
/*
* We've finished with BAR handling - either we succeeded or
* failed. Either way, unsuspend TX.
*/
static void
ath_tx_tid_bar_unsuspend(struct ath_softc *sc, struct ath_tid *tid)
{
ATH_TXQ_LOCK_ASSERT(sc->sc_ac2q[tid->ac]);
DPRINTF(sc, ATH_DEBUG_SW_TX_BAW,
"%s: tid=%p, called\n",
__func__,
tid);
if (tid->bar_tx == 0 || tid->bar_wait == 0) {
device_printf(sc->sc_dev, "%s: bar_tx=%d, bar_wait=%d: ?\n",
__func__, tid->bar_tx, tid->bar_wait);
}
tid->bar_tx = tid->bar_wait = 0;
ath_tx_tid_resume(sc, tid);
}
/*
* Return whether we're ready to TX a BAR frame.
*
* Requires the TID lock be held.
*/
static int
ath_tx_tid_bar_tx_ready(struct ath_softc *sc, struct ath_tid *tid)
{
ATH_TXQ_LOCK_ASSERT(sc->sc_ac2q[tid->ac]);
if (tid->bar_wait == 0 || tid->hwq_depth > 0)
return (0);
return (1);
}
/*
* Check whether the current TID is ready to have a BAR
* TXed and if so, do the TX.
*
* Since the TID/TXQ lock can't be held during a call to
* ieee80211_send_bar(), we have to do the dirty thing of unlocking it,
* sending the BAR and locking it again.
*
* Eventually, the code to send the BAR should be broken out
* from this routine so the lock doesn't have to be reacquired
* just to be immediately dropped by the caller.
*/
static void
ath_tx_tid_bar_tx(struct ath_softc *sc, struct ath_tid *tid)
{
struct ieee80211_tx_ampdu *tap;
ATH_TXQ_LOCK_ASSERT(sc->sc_ac2q[tid->ac]);
DPRINTF(sc, ATH_DEBUG_SW_TX_BAW,
"%s: tid=%p, called\n",
__func__,
tid);
tap = ath_tx_get_tx_tid(tid->an, tid->tid);
/*
* This is an error condition!
*/
if (tid->bar_wait == 0 || tid->bar_tx == 1) {
device_printf(sc->sc_dev,
"%s: tid=%p, bar_tx=%d, bar_wait=%d: ?\n",
__func__,
tid,
tid->bar_tx,
tid->bar_wait);
return;
}
/* Don't do anything if we still have pending frames */
if (tid->hwq_depth > 0) {
DPRINTF(sc, ATH_DEBUG_SW_TX_BAW,
"%s: tid=%p, hwq_depth=%d, waiting\n",
__func__,
tid,
tid->hwq_depth);
return;
}
/* We're now about to TX */
tid->bar_tx = 1;
/*
* Calculate new BAW left edge, now that all frames have either
* succeeded or failed.
*
* XXX verify this is _actually_ the valid value to begin at!
*/
DPRINTF(sc, ATH_DEBUG_SW_TX_BAW,
"%s: tid=%p, new BAW left edge=%d\n",
__func__,
tid,
tap->txa_start);
/* Try sending the BAR frame */
/* We can't hold the lock here! */
ATH_TXQ_UNLOCK(sc->sc_ac2q[tid->ac]);
if (ieee80211_send_bar(&tid->an->an_node, tap, tap->txa_start) == 0) {
/* Success? Now we wait for notification that it's done */
ATH_TXQ_LOCK(sc->sc_ac2q[tid->ac]);
return;
}
/* Failure? For now, warn loudly and continue */
ATH_TXQ_LOCK(sc->sc_ac2q[tid->ac]);
device_printf(sc->sc_dev, "%s: tid=%p, failed to TX BAR, continue!\n",
__func__, tid);
ath_tx_tid_bar_unsuspend(sc, tid);
}
/*
* Free any packets currently pending in the software TX queue.
*
@ -3077,7 +3229,6 @@ ath_tx_aggr_retry_unaggr(struct ath_softc *sc, struct ath_buf *bf)
int tid = bf->bf_state.bfs_tid;
struct ath_tid *atid = &an->an_tid[tid];
struct ieee80211_tx_ampdu *tap;
int txseq;
ATH_TXQ_LOCK(sc->sc_ac2q[atid->ac]);
@ -3118,19 +3269,15 @@ ath_tx_aggr_retry_unaggr(struct ath_softc *sc, struct ath_buf *bf)
}
bf->bf_state.bfs_dobaw = 0;
/* Send BAR frame */
/*
* This'll end up going into net80211 and back out
* again, via ic->ic_raw_xmit().
*/
txseq = tap->txa_start;
/* Suspend the TX queue and get ready to send the BAR */
ath_tx_tid_bar_suspend(sc, atid);
/* Send the BAR if there are no other frames waiting */
if (ath_tx_tid_bar_tx_ready(sc, atid))
ath_tx_tid_bar_tx(sc, atid);
ATH_TXQ_UNLOCK(sc->sc_ac2q[atid->ac]);
device_printf(sc->sc_dev,
"%s: TID %d: send BAR; seq %d\n", __func__, tid, txseq);
/* XXX TODO: send BAR */
/* Free buffer, bf is free after this call */
ath_tx_default_comp(sc, bf, 0);
return;
@ -3149,6 +3296,9 @@ ath_tx_aggr_retry_unaggr(struct ath_softc *sc, struct ath_buf *bf)
*/
ATH_TXQ_INSERT_HEAD(atid, bf, bf_list);
ath_tx_tid_sched(sc, atid);
/* Send the BAR if there are no other frames waiting */
if (ath_tx_tid_bar_tx_ready(sc, atid))
ath_tx_tid_bar_tx(sc, atid);
ATH_TXQ_UNLOCK(sc->sc_ac2q[atid->ac]);
}
@ -3278,17 +3428,20 @@ ath_tx_comp_aggr_error(struct ath_softc *sc, struct ath_buf *bf_first,
* in the ifnet TX context or raw TX context.)
*/
if (drops) {
int txseq = tap->txa_start;
ATH_TXQ_UNLOCK(sc->sc_ac2q[tid->ac]);
device_printf(sc->sc_dev,
"%s: TID %d: send BAR; seq %d\n",
__func__, tid->tid, txseq);
/* XXX TODO: send BAR */
} else {
ATH_TXQ_UNLOCK(sc->sc_ac2q[tid->ac]);
/* Suspend the TX queue and get ready to send the BAR */
ath_tx_tid_bar_suspend(sc, tid);
}
ATH_TXQ_UNLOCK(sc->sc_ac2q[tid->ac]);
/*
* Send BAR if required
*/
ATH_TXQ_LOCK(sc->sc_ac2q[tid->ac]);
if (ath_tx_tid_bar_tx_ready(sc, tid))
ath_tx_tid_bar_tx(sc, tid);
ATH_TXQ_UNLOCK(sc->sc_ac2q[tid->ac]);
/* Complete frames which errored out */
while ((bf = TAILQ_FIRST(&bf_cq)) != NULL) {
TAILQ_REMOVE(&bf_cq, bf, bf_list);
@ -3328,6 +3481,10 @@ ath_tx_comp_cleanup_aggr(struct ath_softc *sc, struct ath_buf *bf_first)
atid->cleanup_inprogress = 0;
ath_tx_tid_resume(sc, atid);
}
/* Send BAR if required */
if (ath_tx_tid_bar_tx_ready(sc, atid))
ath_tx_tid_bar_tx(sc, atid);
ATH_TXQ_UNLOCK(sc->sc_ac2q[atid->ac]);
/* Handle frame completion */
@ -3542,9 +3699,10 @@ ath_tx_aggr_comp_aggr(struct ath_softc *sc, struct ath_buf *bf_first,
* send bar if we dropped any frames
*/
if (drops) {
device_printf(sc->sc_dev,
"%s: TID %d: send BAR; seq %d\n", __func__, tid, txseq);
/* XXX TODO: send BAR */
/* Suspend the TX queue and get ready to send the BAR */
ATH_TXQ_LOCK(sc->sc_ac2q[atid->ac]);
ath_tx_tid_bar_suspend(sc, atid);
ATH_TXQ_UNLOCK(sc->sc_ac2q[atid->ac]);
}
/* Prepend all frames to the beginning of the queue */
@ -3559,6 +3717,14 @@ ath_tx_aggr_comp_aggr(struct ath_softc *sc, struct ath_buf *bf_first,
DPRINTF(sc, ATH_DEBUG_SW_TX_AGGR,
"%s: txa_start now %d\n", __func__, tap->txa_start);
/*
* Send BAR if required
*/
ATH_TXQ_LOCK(sc->sc_ac2q[atid->ac]);
if (ath_tx_tid_bar_tx_ready(sc, atid))
ath_tx_tid_bar_tx(sc, atid);
ATH_TXQ_UNLOCK(sc->sc_ac2q[atid->ac]);
/* Do deferred completion */
while ((bf = TAILQ_FIRST(&bf_cq)) != NULL) {
TAILQ_REMOVE(&bf_cq, bf, bf_list);
@ -3652,6 +3818,12 @@ ath_tx_aggr_comp_unaggr(struct ath_softc *sc, struct ath_buf *bf, int fail)
__func__, SEQNO(bf->bf_state.bfs_seqno));
}
/*
* Send BAR if required
*/
if (ath_tx_tid_bar_tx_ready(sc, atid))
ath_tx_tid_bar_tx(sc, atid);
ATH_TXQ_UNLOCK(sc->sc_ac2q[atid->ac]);
ath_tx_default_comp(sc, bf, fail);
@ -4080,7 +4252,9 @@ ath_addba_request(struct ieee80211_node *ni, struct ieee80211_tx_ampdu *tap,
* it'll be "after" the left edge of the BAW and thus it'll
* fall within it.
*/
ATH_TXQ_LOCK(sc->sc_ac2q[atid->tid]);
ath_tx_tid_pause(sc, atid);
ATH_TXQ_UNLOCK(sc->sc_ac2q[atid->tid]);
DPRINTF(sc, ATH_DEBUG_SW_TX_CTRL,
"%s: called; dialogtoken=%d, baparamset=%d, batimeout=%d\n",
@ -4166,7 +4340,9 @@ ath_addba_stop(struct ieee80211_node *ni, struct ieee80211_tx_ampdu *tap)
DPRINTF(sc, ATH_DEBUG_SW_TX_CTRL, "%s: called\n", __func__);
/* Pause TID traffic early, so there aren't any races */
ATH_TXQ_LOCK(sc->sc_ac2q[atid->tid]);
ath_tx_tid_pause(sc, atid);
ATH_TXQ_UNLOCK(sc->sc_ac2q[atid->tid]);
/* There's no need to hold the TXQ lock here */
sc->sc_addba_stop(ni, tap);
@ -4213,7 +4389,7 @@ ath_bar_response(struct ieee80211_node *ni, struct ieee80211_tx_ampdu *tap,
*/
if (status == 0 || attempts == 50) {
ATH_TXQ_LOCK(sc->sc_ac2q[atid->ac]);
ath_tx_tid_resume(sc, atid);
ath_tx_tid_bar_unsuspend(sc, atid);
ATH_TXQ_UNLOCK(sc->sc_ac2q[atid->ac]);
}
}

View File

@ -106,6 +106,8 @@ struct ath_tid {
TAILQ_ENTRY(ath_tid) axq_qelem;
int sched;
int paused; /* >0 if the TID has been paused */
int bar_wait; /* waiting for BAR */
int bar_tx; /* BAR TXed */
/*
* Is the TID being cleaned up after a transition