Close race in handling mcast traffic when operating as an ap with

stations in power save: add a new q where mcast frames are stashed
and on beacon update (at DTIM) move frames from the mcast q to the
cabq and start it.  This ensures the cabq is only manipulated in
one place.

Sponsored by:	Hobnob
MFC after:	2 weeks
This commit is contained in:
Sam Leffler 2006-06-26 03:10:45 +00:00
parent d5046da865
commit 622b3fd21c
2 changed files with 105 additions and 40 deletions

View File

@ -1,5 +1,5 @@
/*-
* Copyright (c) 2002-2005 Sam Leffler, Errno Consulting
* Copyright (c) 2002-2006 Sam Leffler, Errno Consulting
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
@ -150,6 +150,7 @@ static void ath_recv_mgmt(struct ieee80211com *ic, struct mbuf *m,
int subtype, int rssi, u_int32_t rstamp);
static void ath_setdefantenna(struct ath_softc *, u_int);
static void ath_rx_proc(void *, int);
static void ath_txq_init(struct ath_softc *sc, struct ath_txq *, int);
static struct ath_txq *ath_txq_setup(struct ath_softc*, int qtype, int subtype);
static int ath_tx_setup(struct ath_softc *, int, int);
static int ath_wme_update(struct ieee80211com *);
@ -422,6 +423,7 @@ ath_attach(u_int16_t devid, struct ath_softc *sc)
error = EIO;
goto bad2;
}
ath_txq_init(sc, &sc->sc_mcastq, -1); /* NB: s/w q, qnum not used */
/* NB: insure BK queue is the lowest priority h/w queue */
if (!ath_tx_setup(sc, WME_AC_BK, HAL_WME_AC_BK)) {
if_printf(ifp, "unable to setup xmit queue for %s traffic!\n",
@ -1987,6 +1989,20 @@ ath_beacon_setup(struct ath_softc *sc, struct ath_buf *bf)
#undef USE_SHPREAMBLE
}
/*
* Append the contents of src to dst; both queues
* are assumed to be locked.
*/
static void
ath_txqmove(struct ath_txq *dst, struct ath_txq *src)
{
STAILQ_CONCAT(&dst->axq_q, &src->axq_q);
dst->axq_link = src->axq_link;
src->axq_link = NULL;
dst->axq_depth += src->axq_depth;
src->axq_depth = 0;
}
/*
* Transmit a beacon frame at SWBA. Dynamic updates to the
* frame contents are done as needed and the slot time is
@ -2000,8 +2016,9 @@ ath_beacon_proc(void *arg, int pending)
struct ieee80211_node *ni = bf->bf_node;
struct ieee80211com *ic = ni->ni_ic;
struct ath_hal *ah = sc->sc_ah;
struct ath_txq *cabq = sc->sc_cabq;
struct mbuf *m;
int ncabq, error, otherant;
int ncabq, nmcastq, error, otherant;
DPRINTF(sc, ATH_DEBUG_BEACON_PROC, "%s: pending %u\n",
__func__, pending);
@ -2043,8 +2060,9 @@ ath_beacon_proc(void *arg, int pending)
* of the TIM bitmap).
*/
m = bf->bf_m;
ncabq = sc->sc_cabq->axq_depth;
if (ieee80211_beacon_update(ic, bf->bf_node, &sc->sc_boff, m, ncabq)) {
nmcastq = sc->sc_mcastq.axq_depth;
ncabq = ath_hal_numtxpending(ah, cabq->axq_qnum);
if (ieee80211_beacon_update(ic, bf->bf_node, &sc->sc_boff, m, ncabq+nmcastq)) {
/* XXX too conservative? */
bus_dmamap_unload(sc->sc_dmat, bf->bf_dmamap);
error = bus_dmamap_load_mbuf_sg(sc->sc_dmat, bf->bf_dmamap, m,
@ -2057,6 +2075,18 @@ ath_beacon_proc(void *arg, int pending)
return;
}
}
if (ncabq && (sc->sc_boff.bo_tim[4] & 1)) {
/*
* CABQ traffic from the previous DTIM is still pending.
* This is ok for now but when there are multiple vap's
* and we are using staggered beacons we'll want to drain
* the cabq before loading frames for the different vap.
*/
DPRINTF(sc, ATH_DEBUG_BEACON,
"%s: cabq did not drain, mcastq %u cabq %u/%u\n",
__func__, nmcastq, ncabq, cabq->axq_depth);
sc->sc_stats.ast_cabq_busy++;
}
/*
* Handle slot time change when a non-ERP station joins/leaves
@ -2103,8 +2133,30 @@ ath_beacon_proc(void *arg, int pending)
* Enable the CAB queue before the beacon queue to
* insure cab frames are triggered by this beacon.
*/
if (sc->sc_boff.bo_tim[4] & 1) /* NB: only at DTIM */
ath_hal_txstart(ah, sc->sc_cabq->axq_qnum);
if (sc->sc_boff.bo_tim[4] & 1) { /* NB: only at DTIM */
ATH_TXQ_LOCK(cabq);
ATH_TXQ_LOCK(&sc->sc_mcastq);
if (nmcastq) {
struct ath_buf *bfm;
/*
* Move frames from the s/w mcast q to the h/w cab q.
*/
bfm = STAILQ_FIRST(&sc->sc_mcastq.axq_q);
if (cabq->axq_link != NULL) {
*cabq->axq_link = bfm->bf_daddr;
} else
ath_hal_puttxbuf(ah, cabq->axq_qnum,
bfm->bf_daddr);
ath_txqmove(cabq, &sc->sc_mcastq);
sc->sc_stats.ast_cabq_xmit += nmcastq;
}
/* NB: gated by beacon so safe to start here */
ath_hal_txstart(ah, cabq->axq_qnum);
ATH_TXQ_UNLOCK(cabq);
ATH_TXQ_UNLOCK(&sc->sc_mcastq);
}
ath_hal_puttxbuf(ah, sc->sc_bhalq, bf->bf_daddr);
ath_hal_txstart(ah, sc->sc_bhalq);
DPRINTF(sc, ATH_DEBUG_BEACON_PROC,
@ -3031,6 +3083,17 @@ ath_rx_proc(void *arg, int npending)
#undef PA2DESC
}
static void
ath_txq_init(struct ath_softc *sc, struct ath_txq *txq, int qnum)
{
txq->axq_qnum = qnum;
txq->axq_depth = 0;
txq->axq_intrcnt = 0;
txq->axq_link = NULL;
STAILQ_INIT(&txq->axq_q);
ATH_TXQ_LOCK_INIT(sc, txq);
}
/*
* Setup a h/w transmit queue.
*/
@ -3076,14 +3139,7 @@ ath_txq_setup(struct ath_softc *sc, int qtype, int subtype)
return NULL;
}
if (!ATH_TXQ_SETUP(sc, qnum)) {
struct ath_txq *txq = &sc->sc_txq[qnum];
txq->axq_qnum = qnum;
txq->axq_depth = 0;
txq->axq_intrcnt = 0;
txq->axq_link = NULL;
STAILQ_INIT(&txq->axq_q);
ATH_TXQ_LOCK_INIT(sc, txq);
ath_txq_init(sc, &sc->sc_txq[qnum], qnum);
sc->sc_txqsetup |= 1<<qnum;
}
return &sc->sc_txq[qnum];
@ -3190,6 +3246,7 @@ ath_tx_cleanup(struct ath_softc *sc)
for (i = 0; i < HAL_NUM_TX_QUEUES; i++)
if (ATH_TXQ_SETUP(sc, i))
ath_tx_cleanupq(sc, &sc->sc_txq[i]);
ATH_TXQ_LOCK_DESTROY(&sc->sc_mcastq);
}
/*
@ -3529,11 +3586,12 @@ ath_tx_start(struct ath_softc *sc, struct ieee80211_node *ni, struct ath_buf *bf
/*
* When servicing one or more stations in power-save mode
* multicast frames must be buffered until after the beacon.
* We use the CAB queue for that.
* (or) if there is some mcast data waiting on the mcast
* queue (to prevent out of order delivery) multicast
* frames must be buffered until after the beacon.
*/
if (ismcast && ic->ic_ps_sta) {
txq = sc->sc_cabq;
if (ismcast && (ic->ic_ps_sta || sc->sc_mcastq.axq_depth)) {
txq = &sc->sc_mcastq;
/* XXX? more bit in 802.11 frame header */
}
@ -3722,31 +3780,36 @@ ath_tx_start(struct ath_softc *sc, struct ieee80211_node *ni, struct ath_buf *bf
ds->ds_ctl0, ds->ds_ctl1, ds->ds_hw[0], ds->ds_hw[1]);
}
/*
* Insert the frame on the outbound list and
* pass it on to the hardware.
* Insert the frame on the outbound list and pass it on
* to the hardware. Multicast frames buffered for power
* save stations and transmit from the CAB queue are stored
* on a s/w only queue and loaded on to the CAB queue in
* the SWBA handler since frames only go out on DTIM and
* to avoid possible races.
*/
ATH_TXQ_LOCK(txq);
ATH_TXQ_INSERT_TAIL(txq, bf, bf_list);
if (txq->axq_link == NULL) {
ath_hal_puttxbuf(ah, txq->axq_qnum, bf->bf_daddr);
DPRINTF(sc, ATH_DEBUG_XMIT,
"%s: TXDP[%u] = %p (%p) depth %d\n", __func__,
txq->axq_qnum, (caddr_t)bf->bf_daddr, bf->bf_desc,
txq->axq_depth);
} else {
*txq->axq_link = bf->bf_daddr;
DPRINTF(sc, ATH_DEBUG_XMIT,
"%s: link[%u](%p)=%p (%p) depth %d\n", __func__,
txq->axq_qnum, txq->axq_link,
(caddr_t)bf->bf_daddr, bf->bf_desc, txq->axq_depth);
}
txq->axq_link = &bf->bf_desc[bf->bf_nseg - 1].ds_link;
/*
* The CAB queue is started from the SWBA handler since
* frames only go out on DTIM and to avoid possible races.
*/
if (txq != sc->sc_cabq)
if (txq != &sc->sc_mcastq) {
if (txq->axq_link == NULL) {
ath_hal_puttxbuf(ah, txq->axq_qnum, bf->bf_daddr);
DPRINTF(sc, ATH_DEBUG_XMIT,
"%s: TXDP[%u] = %p (%p) depth %d\n", __func__,
txq->axq_qnum, (caddr_t)bf->bf_daddr, bf->bf_desc,
txq->axq_depth);
} else {
*txq->axq_link = bf->bf_daddr;
DPRINTF(sc, ATH_DEBUG_XMIT,
"%s: link[%u](%p)=%p (%p) depth %d\n", __func__,
txq->axq_qnum, txq->axq_link,
(caddr_t)bf->bf_daddr, bf->bf_desc, txq->axq_depth);
}
txq->axq_link = &bf->bf_desc[bf->bf_nseg - 1].ds_link;
ath_hal_txstart(ah, txq->axq_qnum);
} else {
if (txq->axq_link != NULL)
*txq->axq_link = bf->bf_daddr;
txq->axq_link = &bf->bf_desc[bf->bf_nseg - 1].ds_link;
}
ATH_TXQ_UNLOCK(txq);
return 0;
@ -4044,6 +4107,7 @@ ath_draintxq(struct ath_softc *sc)
for (i = 0; i < HAL_NUM_TX_QUEUES; i++)
if (ATH_TXQ_SETUP(sc, i))
ath_tx_draintxq(sc, &sc->sc_txq[i]);
ath_tx_draintxq(sc, &sc->sc_mcastq);
#ifdef ATH_DEBUG
if (sc->sc_debug & ATH_DEBUG_RESET) {
struct ath_buf *bf = STAILQ_FIRST(&sc->sc_bbuf);

View File

@ -1,5 +1,5 @@
/*-
* Copyright (c) 2002-2005 Sam Leffler, Errno Consulting
* Copyright (c) 2002-2006 Sam Leffler, Errno Consulting
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
@ -286,6 +286,7 @@ struct ath_softc {
UPDATE, /* update pending */
COMMIT /* beacon sent, commit change */
} sc_updateslot; /* slot time update fsm */
struct ath_txq sc_mcastq; /* mcast xmits w/ ps sta's */
struct callout sc_cal_ch; /* callout handle for cals */
int sc_calinterval; /* current polling interval */