Date: Sun, 21 Apr 2013 19:33:55 +0000 (UTC) From: Adrian Chadd <adrian@FreeBSD.org> To: src-committers@freebsd.org, svn-src-user@freebsd.org Subject: svn commit: r249738 - user/adrian/net80211_tx/sys/dev/ath Message-ID: <201304211933.r3LJXtqr020369@svn.freebsd.org>
next in thread | raw e-mail | index | archive | help
Author: adrian Date: Sun Apr 21 19:33:55 2013 New Revision: 249738 URL: http://svnweb.freebsd.org/changeset/base/249738 Log: Bring over my initial power save queue work. Since this is a little more complicated than I thought, I'll need some subversion history for this. So far: * Do node pause/unpause when the stack enters/leaves powersave for the given station; * Remove the use of ath_node lock outside of the rate control code - now the rest of the ath_node stat is protected by the tx lock; * When a node reassociates, a bunch of frames are in the software queue and hardware queue. We need to tidy that up - so for now, just go through the normal cleanup path. But that may not work in the long run. * .. and whilst doing this, reset some of the state (eg BAR TX state, filtered frames, etc.) * .. this still doesn't entirely close the queue stalls; but they may be BAR TX related rather than this particular work. * Add support for leaking frames out whilst the node is in sleep state. That way I can do interesting things for PS-POLL. * Tie into the PS-POLL hook from net80211; leak out a software queued frame if we have any; else just mark the node as ready to leak a frame and leave it up to the stack. I'm still looking into whether there's any possible races with ps-poll handling - notably, if this happens: * STA sends PS-POLL * Stack generates a null frame response; * .. but in another thread, the stack gets handed a frame; * .. and that thread wins the transmit race and it gets queued out first. Now hopefully the TX path locking in net80211 stops this from occuring. But still.. Modified: user/adrian/net80211_tx/sys/dev/ath/if_ath.c user/adrian/net80211_tx/sys/dev/ath/if_ath_tx.c user/adrian/net80211_tx/sys/dev/ath/if_ath_tx.h user/adrian/net80211_tx/sys/dev/ath/if_ath_tx_ht.c user/adrian/net80211_tx/sys/dev/ath/if_ath_tx_ht.h user/adrian/net80211_tx/sys/dev/ath/if_athvar.h Modified: user/adrian/net80211_tx/sys/dev/ath/if_ath.c ============================================================================== --- user/adrian/net80211_tx/sys/dev/ath/if_ath.c Sun Apr 21 19:24:37 2013 (r249737) +++ user/adrian/net80211_tx/sys/dev/ath/if_ath.c Sun Apr 21 19:33:55 2013 (r249738) @@ -125,7 +125,7 @@ __FBSDID("$FreeBSD$"); /* * Only enable this if you're working on PS-POLL support. */ -#undef ATH_SW_PSQ +#define ATH_SW_PSQ /* * ATH_BCBUF determines the number of vap's that can transmit @@ -212,6 +212,7 @@ static void ath_announce(struct ath_soft static void ath_dfs_tasklet(void *, int); static void ath_node_powersave(struct ieee80211_node *, int); static int ath_node_set_tim(struct ieee80211_node *, int); +static void ath_node_recv_pspoll(struct ieee80211_node *, struct mbuf *); #ifdef IEEE80211_SUPPORT_TDMA #include <dev/ath/if_ath_tdma.h> @@ -1241,6 +1242,9 @@ ath_vap_create(struct ieee80211com *ic, avp->av_set_tim = vap->iv_set_tim; vap->iv_set_tim = ath_node_set_tim; + avp->av_recv_pspoll = vap->iv_recv_pspoll; + vap->iv_recv_pspoll = ath_node_recv_pspoll; + /* Set default parameters */ /* @@ -3378,7 +3382,7 @@ ath_node_alloc(struct ieee80211vap *vap, /* XXX setup ath_tid */ ath_tx_tid_init(sc, an); - DPRINTF(sc, ATH_DEBUG_NODE, "%s: an %p\n", __func__, an); + DPRINTF(sc, ATH_DEBUG_NODE, "%s: %6D: an %p\n", __func__, mac, ":", an); return &an->an_node; } @@ -3388,6 +3392,9 @@ ath_node_cleanup(struct ieee80211_node * struct ieee80211com *ic = ni->ni_ic; struct ath_softc *sc = ic->ic_ifp->if_softc; + DPRINTF(sc, ATH_DEBUG_NODE, "%s: %6D: an %p\n", __func__, + ni->ni_macaddr, ":", ATH_NODE(ni)); + /* Cleanup ath_tid, free unused bufs, unlink bufs in TXQ */ ath_tx_node_flush(sc, ATH_NODE(ni)); ath_rate_node_cleanup(sc, ATH_NODE(ni)); @@ -3400,7 +3407,8 @@ ath_node_free(struct ieee80211_node *ni) struct ieee80211com *ic = ni->ni_ic; struct ath_softc *sc = ic->ic_ifp->if_softc; - DPRINTF(sc, ATH_DEBUG_NODE, "%s: ni %p\n", __func__, ni); + DPRINTF(sc, ATH_DEBUG_NODE, "%s: %6D: an %p\n", __func__, + ni->ni_macaddr, ":", ATH_NODE(ni)); mtx_destroy(&ATH_NODE(ni)->an_mtx); sc->sc_node_free(ni); } @@ -3773,8 +3781,11 @@ ath_tx_default_comp(struct ath_softc *sc * XXX TODO: during drain, ensure that the callback is * being called so we get a chance to update the TIM. */ - if (bf->bf_node) + if (bf->bf_node) { + ATH_TX_LOCK(sc); ath_tx_update_tim(sc, bf->bf_node, 0); + ATH_TX_UNLOCK(sc); + } /* * Do any tx complete callback. Note this must @@ -5220,6 +5231,31 @@ ath_newassoc(struct ieee80211_node *ni, (vap->iv_flags & IEEE80211_F_PRIVACY) == 0 && sc->sc_hasclrkey && ni->ni_ucastkey.wk_keyix == IEEE80211_KEYIX_NONE) ath_setup_stationkey(ni); + + /* + * If we're reassociating, make sure that any paused queues + * get unpaused. + * + * Now, we may hvae frames in the hardware queue for this node. + * So if we are reassociating and there are frames in the queue, + * we need to go through the cleanup path to ensure that they're + * marked as non-aggregate. + */ + if (! isnew) { + device_printf(sc->sc_dev, + "%s: %6D: reassoc; is_powersave=%d\n", + __func__, + ni->ni_macaddr, + ":", + an->an_is_powersave); + + /* XXX for now, we can't hold the lock across assoc */ + ath_tx_node_reassoc(sc, an); + + /* XXX for now, we can't hold the lock across wakeup */ + if (an->an_is_powersave) + ath_tx_node_wakeup(sc, an); + } } static int @@ -5732,11 +5768,13 @@ ath_node_powersave(struct ieee80211_node struct ath_softc *sc = ic->ic_ifp->if_softc; struct ath_vap *avp = ATH_VAP(ni->ni_vap); - ATH_NODE_UNLOCK_ASSERT(an); /* XXX and no TXQ locks should be held here */ - DPRINTF(sc, ATH_DEBUG_NODE_PWRSAVE, "%s: ni=%p, enable=%d\n", - __func__, ni, enable); + DPRINTF(sc, ATH_DEBUG_NODE_PWRSAVE, "%s: %6D: enable=%d\n", + __func__, + ni->ni_macaddr, + ":", + !! enable); /* Suspend or resume software queue handling */ if (enable) @@ -5799,12 +5837,7 @@ ath_node_set_tim(struct ieee80211_node * struct ath_vap *avp = ATH_VAP(ni->ni_vap); int changed = 0; - ATH_NODE_UNLOCK_ASSERT(an); - - /* - * For now, just track and then update the TIM. - */ - ATH_NODE_LOCK(an); + ATH_TX_LOCK(sc); an->an_stack_psq = enable; /* @@ -5815,7 +5848,7 @@ ath_node_set_tim(struct ieee80211_node * * and AP/IBSS node power save. */ if (avp->av_set_tim == NULL) { - ATH_NODE_UNLOCK(an); + ATH_TX_UNLOCK(sc); return (0); } @@ -5835,33 +5868,45 @@ ath_node_set_tim(struct ieee80211_node * */ if (enable && an->an_tim_set == 1) { DPRINTF(sc, ATH_DEBUG_NODE_PWRSAVE, - "%s: an=%p, enable=%d, tim_set=1, ignoring\n", - __func__, an, enable); - ATH_NODE_UNLOCK(an); + "%s: %6D: enable=%d, tim_set=1, ignoring\n", + __func__, + ni->ni_macaddr, + ":", + enable); + ATH_TX_UNLOCK(sc); } else if (enable) { DPRINTF(sc, ATH_DEBUG_NODE_PWRSAVE, - "%s: an=%p, enable=%d, enabling TIM\n", - __func__, an, enable); + "%s: %6D: enable=%d, enabling TIM\n", + __func__, + ni->ni_macaddr, + ":", + enable); an->an_tim_set = 1; - ATH_NODE_UNLOCK(an); + ATH_TX_UNLOCK(sc); changed = avp->av_set_tim(ni, enable); } else if (atomic_load_acq_int(&an->an_swq_depth) == 0) { /* disable */ DPRINTF(sc, ATH_DEBUG_NODE_PWRSAVE, - "%s: an=%p, enable=%d, an_swq_depth == 0, disabling\n", - __func__, an, enable); + "%s: %6D: enable=%d, an_swq_depth == 0, disabling\n", + __func__, + ni->ni_macaddr, + ":", + enable); an->an_tim_set = 0; - ATH_NODE_UNLOCK(an); + ATH_TX_UNLOCK(sc); changed = avp->av_set_tim(ni, enable); } else if (! an->an_is_powersave) { /* * disable regardless; the node isn't in powersave now */ DPRINTF(sc, ATH_DEBUG_NODE_PWRSAVE, - "%s: an=%p, enable=%d, an_pwrsave=0, disabling\n", - __func__, an, enable); + "%s: %6D: enable=%d, an_pwrsave=0, disabling\n", + __func__, + ni->ni_macaddr, + ":", + enable); an->an_tim_set = 0; - ATH_NODE_UNLOCK(an); + ATH_TX_UNLOCK(sc); changed = avp->av_set_tim(ni, enable); } else { /* @@ -5869,10 +5914,13 @@ ath_node_set_tim(struct ieee80211_node * * software queue isn't empty, so don't clear the TIM bit * for now. */ - ATH_NODE_UNLOCK(an); + ATH_TX_UNLOCK(sc); DPRINTF(sc, ATH_DEBUG_NODE_PWRSAVE, - "%s: enable=%d, an_swq_depth > 0, ignoring\n", - __func__, enable); + "%s: %6D: enable=%d, an_swq_depth > 0, ignoring\n", + __func__, + ni->ni_macaddr, + ":", + enable); changed = 0; } @@ -5934,7 +5982,7 @@ ath_tx_update_tim(struct ath_softc *sc, if (avp->av_set_tim == NULL) return; - ATH_NODE_UNLOCK_ASSERT(an); + ATH_TX_LOCK_ASSERT(sc); if (enable) { /* @@ -5944,18 +5992,16 @@ ath_tx_update_tim(struct ath_softc *sc, if (atomic_load_acq_int(&an->an_swq_depth) == 0) return; - ATH_NODE_LOCK(an); if (an->an_is_powersave && an->an_tim_set == 0 && atomic_load_acq_int(&an->an_swq_depth) != 0) { DPRINTF(sc, ATH_DEBUG_NODE_PWRSAVE, - "%s: an=%p, swq_depth>0, tim_set=0, set!\n", - __func__, an); + "%s: %6D: swq_depth>0, tim_set=0, set!\n", + __func__, + ni->ni_macaddr, + ":"); an->an_tim_set = 1; - ATH_NODE_UNLOCK(an); (void) avp->av_set_tim(ni, 1); - } else { - ATH_NODE_UNLOCK(an); } } else { /* @@ -5964,20 +6010,18 @@ ath_tx_update_tim(struct ath_softc *sc, if (atomic_load_acq_int(&an->an_swq_depth) != 0) return; - ATH_NODE_LOCK(an); if (an->an_is_powersave && an->an_stack_psq == 0 && an->an_tim_set == 1 && atomic_load_acq_int(&an->an_swq_depth) == 0) { DPRINTF(sc, ATH_DEBUG_NODE_PWRSAVE, - "%s: an=%p, swq_depth=0, tim_set=1, psq_set=0," + "%s: %6D: swq_depth=0, tim_set=1, psq_set=0," " clear!\n", - __func__, an); + __func__, + ni->ni_macaddr, + ":"); an->an_tim_set = 0; - ATH_NODE_UNLOCK(an); (void) avp->av_set_tim(ni, 0); - } else { - ATH_NODE_UNLOCK(an); } } #else @@ -5985,6 +6029,150 @@ ath_tx_update_tim(struct ath_softc *sc, #endif /* ATH_SW_PSQ */ } +/* + * Received a ps-poll frame from net80211. + * + * Here we get a chance to serve out a software-queued frame ourselves + * before we punt it to net80211 to transmit us one itself - either + * because there's traffic in the net80211 psq, or a NULL frame to + * indicate there's nothing else. + */ +static void +ath_node_recv_pspoll(struct ieee80211_node *ni, struct mbuf *m) +{ + struct ath_node *an; + struct ath_vap *avp; + struct ieee80211com *ic = ni->ni_ic; + struct ath_softc *sc = ic->ic_ifp->if_softc; + int tid; + + /* Just paranoia */ + if (ni == NULL) + return; + + /* + * Unassociated (temporary node) station. + */ + if (ni->ni_associd == 0) + return; + + /* + * We do have an active node, so let's begin looking into it. + */ + an = ATH_NODE(ni); + avp = ATH_VAP(ni->ni_vap); + + /* + * For now, we just call the original ps-poll method. + * Once we're ready to flip this on: + * + * + Set leak to 1, as no matter what we're going to have + * to send a frame; + * + Check the software queue and if there's something in it, + * schedule the highest TID thas has traffic from this node. + * Then make sure we schedule the software scheduler to + * run so it picks up said frame. + * + * That way whatever happens, we'll at least send _a_ frame + * to the given node. + * + * Again, yes, it's crappy QoS if the node has multiple + * TIDs worth of traffic - but let's get it working first + * before we optimise it. + * + * Also yes, there's definitely latency here - we're not + * direct dispatching to the hardware in this path (and + * we're likely being called from the packet receive path, + * so going back into TX may be a little hairy!) but again + * I'd like to get this working first before optimising + * turn-around time. + */ + + ATH_NODE_LOCK(an); + + /* + * Legacy - we're called and the node isn't asleep. + * Immediately punt. + */ + if (! an->an_is_powersave) { + device_printf(sc->sc_dev, + "%s: %6D: not in powersave?\n", + __func__, + ni->ni_macaddr, + ":"); + ATH_NODE_UNLOCK(an); + avp->av_recv_pspoll(ni, m); + return; + } + + /* + * We're in powersave. + * + * Leak a frame. + */ + an->an_leak_count = 1; + + /* + * Now, if there's no frames in the node, just punt to + * recv_pspoll. + * + * Don't bother checking if the TIM bit is set, we really + * only care if there are any frames here! + */ + if (atomic_load_acq_int(&an->an_swq_depth) == 0) { + ATH_NODE_UNLOCK(an); + DPRINTF(sc, ATH_DEBUG_NODE_PWRSAVE, + "%s: %6D: SWQ empty; punting to net80211\n", + __func__, + ni->ni_macaddr, + ":"); + avp->av_recv_pspoll(ni, m); + return; + } + + ATH_NODE_UNLOCK(an); + + /* + * Ok, let's schedule the highest TID that has traffic + * and then schedule something. + */ + ATH_TX_LOCK(sc); + for (tid = IEEE80211_TID_SIZE - 1; tid >= 0; tid--) { + struct ath_tid *atid = &an->an_tid[tid]; + /* + * No frames? Skip. + */ + if (atid->axq_depth == 0) + continue; + ath_tx_tid_sched(sc, atid); + /* + * XXX we could do a direct call to the TXQ + * scheduler code here to optimise latency + * at the expense of a REALLY deep callstack. + */ + taskqueue_enqueue(sc->sc_tq, &sc->sc_txqtask); + DPRINTF(sc, ATH_DEBUG_NODE_PWRSAVE, + "%s: %6D: leaking frame to TID %d\n", + __func__, + ni->ni_macaddr, + ":", + tid); + ATH_TX_UNLOCK(sc); + return; + } + + ATH_TX_UNLOCK(sc); + + /* + * XXX nothing in the TIDs at this point? Eek. + */ + device_printf(sc->sc_dev, "%s: %6D: TIDs empty, but ath_node showed traffic?!\n", + __func__, + ni->ni_macaddr, + ":"); + avp->av_recv_pspoll(ni, m); +} + MODULE_VERSION(if_ath, 1); MODULE_DEPEND(if_ath, wlan, 1, 1, 1); /* 802.11 media layer */ #if defined(IEEE80211_ALQ) || defined(AH_DEBUG_ALQ) Modified: user/adrian/net80211_tx/sys/dev/ath/if_ath_tx.c ============================================================================== --- user/adrian/net80211_tx/sys/dev/ath/if_ath_tx.c Sun Apr 21 19:24:37 2013 (r249737) +++ user/adrian/net80211_tx/sys/dev/ath/if_ath_tx.c Sun Apr 21 19:33:55 2013 (r249738) @@ -1401,6 +1401,69 @@ ath_tx_update_clrdmask(struct ath_softc } /* + * Return whether this frame should be software queued or + * direct dispatched. + * + * When doing powersave, BAR frames should be queued but other management + * frames should be directly sent. + * + * When not doing powersave, stick BAR frames into the hardware queue + * so it goes out even though the queue is paused. + * + * For now, management frames are also software queued by default. + */ +static int +ath_tx_should_swq_frame(struct ath_softc *sc, struct ath_node *an, + struct mbuf *m0, int *queue_to_head) +{ + struct ieee80211_node *ni = &an->an_node; + struct ieee80211_frame *wh; + uint8_t type, subtype; + + wh = mtod(m0, struct ieee80211_frame *); + type = wh->i_fc[0] & IEEE80211_FC0_TYPE_MASK; + subtype = wh->i_fc[0] & IEEE80211_FC0_SUBTYPE_MASK; + + (*queue_to_head) = 0; + + /* If it's not in powersave - direct-dispatch BAR */ + if ((ATH_NODE(ni)->an_is_powersave == 0) + && type == IEEE80211_FC0_TYPE_CTL && + subtype == IEEE80211_FC0_SUBTYPE_BAR) { + DPRINTF(sc, ATH_DEBUG_SW_TX, + "%s: BAR: TX'ing direct\n", __func__); + return (0); + } else if ((ATH_NODE(ni)->an_is_powersave == 1) + && type == IEEE80211_FC0_TYPE_CTL && + subtype == IEEE80211_FC0_SUBTYPE_BAR) { + /* BAR TX whilst asleep; queue */ + DPRINTF(sc, ATH_DEBUG_SW_TX, + "%s: swq: TX'ing\n", __func__); + (*queue_to_head) = 1; + return (1); + } else if ((ATH_NODE(ni)->an_is_powersave == 1) + && (type == IEEE80211_FC0_TYPE_MGT || + type == IEEE80211_FC0_TYPE_CTL)) { + /* + * Other control/mgmt frame; bypass software queuing + * for now! + */ + device_printf(sc->sc_dev, + "%s: %6D: Node is asleep; sending mgmt " + "(type=%d, subtype=%d)\n", + __func__, + ni->ni_macaddr, + ":", + type, + subtype); + return (0); + } else { + return (1); + } +} + + +/* * Transmit the given frame to the hardware. * * The frame must already be setup; rate control must already have @@ -1410,6 +1473,10 @@ ath_tx_update_clrdmask(struct ath_softc * it for this long when not doing software aggregation), later on * break this function into "setup_normal" and "xmit_normal". The * lock only needs to be held for the ath_tx_handoff call. + * + * XXX we don't update the leak count here - if we're doing + * direct frame dispatch, we need to be able to do it without + * decrementing the leak count (eg multicast queue frames.) */ static void ath_tx_xmit_normal(struct ath_softc *sc, struct ath_txq *txq, @@ -1786,6 +1853,7 @@ ath_tx_start(struct ath_softc *sc, struc int is_ampdu, is_ampdu_tx, is_ampdu_pending; ieee80211_seq seqno; uint8_t type, subtype; + int queue_to_head; ATH_TX_LOCK_ASSERT(sc); @@ -1927,22 +1995,26 @@ ath_tx_start(struct ath_softc *sc, struc * either been TXed successfully or max retries has been * reached.) */ + /* + * Until things are better debugged - if this node is asleep + * and we're sending it a non-BAR frame, direct dispatch it. + * Why? Because we need to figure out what's actually being + * sent - eg, during reassociation/reauthentication after + * the node (last) disappeared whilst asleep, the driver should + * have unpaused/unsleep'ed the node. So until that is + * sorted out, use this workaround. + */ if (txq == &avp->av_mcastq) { DPRINTF(sc, ATH_DEBUG_SW_TX, "%s: bf=%p: mcastq: TX'ing\n", __func__, bf); bf->bf_state.bfs_txflags |= HAL_TXDESC_CLRDMASK; ath_tx_xmit_normal(sc, txq, bf); - } else if (type == IEEE80211_FC0_TYPE_CTL && - subtype == IEEE80211_FC0_SUBTYPE_BAR) { - DPRINTF(sc, ATH_DEBUG_SW_TX, - "%s: BAR: TX'ing direct\n", __func__); + } else if (ath_tx_should_swq_frame(sc, ATH_NODE(ni), m0, + &queue_to_head)) { + ath_tx_swq(sc, ni, txq, queue_to_head, bf); + } else { bf->bf_state.bfs_txflags |= HAL_TXDESC_CLRDMASK; ath_tx_xmit_normal(sc, txq, bf); - } else { - /* add to software queue */ - DPRINTF(sc, ATH_DEBUG_SW_TX, - "%s: bf=%p: swq: TX'ing\n", __func__, bf); - ath_tx_swq(sc, ni, txq, bf); } #else /* @@ -1950,6 +2022,12 @@ ath_tx_start(struct ath_softc *sc, struc * direct-dispatch to the hardware. */ bf->bf_state.bfs_txflags |= HAL_TXDESC_CLRDMASK; + /* + * Update the current leak count if + * we're leaking frames; and set the + * MORE flag as appropriate. + */ + ath_tx_leak_count_update(sc, tid, bf); ath_tx_xmit_normal(sc, txq, bf); #endif done: @@ -1976,6 +2054,8 @@ ath_tx_raw_start(struct ath_softc *sc, s u_int pri; int o_tid = -1; int do_override; + uint8_t type, subtype; + int queue_to_head; ATH_TX_LOCK_ASSERT(sc); @@ -1989,6 +2069,9 @@ ath_tx_raw_start(struct ath_softc *sc, s /* XXX honor IEEE80211_BPF_DATAPAD */ pktlen = m0->m_pkthdr.len - (hdrlen & 3) + IEEE80211_CRC_LEN; + type = wh->i_fc[0] & IEEE80211_FC0_TYPE_MASK; + subtype = wh->i_fc[0] & IEEE80211_FC0_SUBTYPE_MASK; + ATH_KTR(sc, ATH_KTR_TX, 2, "ath_tx_raw_start: ni=%p, bf=%p, raw", ni, bf); @@ -2165,16 +2248,35 @@ ath_tx_raw_start(struct ath_softc *sc, s __func__, do_override); #if 1 + /* + * Put addba frames in the right place in the right TID/HWQ. + */ if (do_override) { bf->bf_state.bfs_txflags |= HAL_TXDESC_CLRDMASK; + /* + * XXX if it's addba frames, should we be leaking + * them out via the frame leak method? + * XXX for now let's not risk it; but we may wish + * to investigate this later. + */ ath_tx_xmit_normal(sc, sc->sc_ac2q[pri], bf); - } else { + } else if (ath_tx_should_swq_frame(sc, ATH_NODE(ni), m0, + &queue_to_head)) { /* Queue to software queue */ - ath_tx_swq(sc, ni, sc->sc_ac2q[pri], bf); + ath_tx_swq(sc, ni, sc->sc_ac2q[pri], queue_to_head, bf); + } else { + bf->bf_state.bfs_txflags |= HAL_TXDESC_CLRDMASK; + ath_tx_xmit_normal(sc, sc->sc_ac2q[pri], bf); } #else /* Direct-dispatch to the hardware */ bf->bf_state.bfs_txflags |= HAL_TXDESC_CLRDMASK; + /* + * Update the current leak count if + * we're leaking frames; and set the + * MORE flag as appropriate. + */ + ath_tx_leak_count_update(sc, tid, bf); ath_tx_xmit_normal(sc, sc->sc_ac2q[pri], bf); #endif return 0; @@ -2606,6 +2708,60 @@ ath_tx_update_baw(struct ath_softc *sc, __func__, tap->txa_start, tap->txa_wnd, tid->baw_head); } +static void +ath_tx_leak_count_update(struct ath_softc *sc, struct ath_tid *tid, + struct ath_buf *bf) +{ + struct ieee80211_frame *wh; + + ATH_TX_LOCK_ASSERT(sc); + + if (tid->an->an_leak_count > 0) { + wh = mtod(bf->bf_m, struct ieee80211_frame *); + + /* + * Update MORE based on the software/net80211 queue states. + */ + if ((tid->an->an_stack_psq > 0) + || (atomic_load_acq_int(&tid->an->an_swq_depth) > 0)) + wh->i_fc[1] |= IEEE80211_FC1_MORE_DATA; + else + wh->i_fc[1] &= ~IEEE80211_FC1_MORE_DATA; + + DPRINTF(sc, ATH_DEBUG_NODE_PWRSAVE, + "%s: %6D: leak count = %d, psq=%d, swq=%d, MORE=%d\n", + __func__, + tid->an->an_node.ni_macaddr, + ":", + tid->an->an_leak_count, + tid->an->an_stack_psq, + atomic_load_acq_int(&tid->an->an_swq_depth), + !! (wh->i_fc[1] & IEEE80211_FC1_MORE_DATA)); + + /* + * Re-sync the underlying buffer. + */ + bus_dmamap_sync(sc->sc_dmat, bf->bf_dmamap, + BUS_DMASYNC_PREWRITE); + + tid->an->an_leak_count --; + } +} + +static int +ath_tx_tid_can_tx_or_sched(struct ath_softc *sc, struct ath_tid *tid) +{ + + ATH_TX_LOCK_ASSERT(sc); + + if (tid->an->an_leak_count > 0) { + return (1); + } + if (tid->paused) + return (0); + return (1); +} + /* * Mark the current node/TID as ready to TX. * @@ -2614,14 +2770,19 @@ ath_tx_update_baw(struct ath_softc *sc, * * The TXQ lock must be held. */ -static void +void ath_tx_tid_sched(struct ath_softc *sc, struct ath_tid *tid) { struct ath_txq *txq = sc->sc_ac2q[tid->ac]; ATH_TX_LOCK_ASSERT(sc); - if (tid->paused) + /* + * If we are leaking out a frame to this destination + * for PS-POLL, ensure that we allow scheduling to + * occur. + */ + if (! ath_tx_tid_can_tx_or_sched(sc, tid)) return; /* paused, can't schedule yet */ if (tid->sched) @@ -2629,6 +2790,30 @@ ath_tx_tid_sched(struct ath_softc *sc, s tid->sched = 1; +#if 0 + /* + * If this is a sleeping node we're leaking to, given + * it a higher priority. This is so bad for QoS it hurts. + */ + if (tid->an->an_leak_count) { + TAILQ_INSERT_HEAD(&txq->axq_tidq, tid, axq_qelem); + } else { + TAILQ_INSERT_TAIL(&txq->axq_tidq, tid, axq_qelem); + } +#endif + + /* + * We can't do the above - it'll confuse the TXQ software + * scheduler which will keep checking the _head_ TID + * in the list to see if it has traffic. If we queue + * a TID to the head of the list and it doesn't transmit, + * we'll check it again. + * + * So, get the rest of this leaking frames support working + * and reliable first and _then_ optimise it so they're + * pushed out in front of any other pending software + * queued nodes. + */ TAILQ_INSERT_TAIL(&txq->axq_tidq, tid, axq_qelem); } @@ -2725,7 +2910,7 @@ ath_tx_xmit_aggr(struct ath_softc *sc, s tap = ath_tx_get_tx_tid(an, tid->tid); /* paused? queue */ - if (tid->paused) { + if (! ath_tx_tid_can_tx_or_sched(sc, tid)) { ATH_TID_INSERT_HEAD(tid, bf, bf_list); /* XXX don't sched - we're paused! */ return; @@ -2785,6 +2970,13 @@ ath_tx_xmit_aggr(struct ath_softc *sc, s /* Set completion handler, multi-frame aggregate or not */ bf->bf_comp = ath_tx_aggr_comp; + /* + * Update the current leak count if + * we're leaking frames; and set the + * MORE flag as appropriate. + */ + ath_tx_leak_count_update(sc, tid, bf); + /* Hand off to hardware */ ath_tx_handoff(sc, txq, bf); } @@ -2796,8 +2988,8 @@ ath_tx_xmit_aggr(struct ath_softc *sc, s * relevant software queue. */ void -ath_tx_swq(struct ath_softc *sc, struct ieee80211_node *ni, struct ath_txq *txq, - struct ath_buf *bf) +ath_tx_swq(struct ath_softc *sc, struct ieee80211_node *ni, + struct ath_txq *txq, int queue_to_head, struct ath_buf *bf) { struct ath_node *an = ATH_NODE(ni); struct ieee80211_frame *wh; @@ -2827,11 +3019,21 @@ ath_tx_swq(struct ath_softc *sc, struct * If the hardware queue is busy, queue it. * If the TID is paused or the traffic it outside BAW, software * queue it. + * + * If the node is in power-save and we're leaking a frame, + * leak a single frame. */ - if (atid->paused) { + if (! ath_tx_tid_can_tx_or_sched(sc, atid)) { /* TID is paused, queue */ DPRINTF(sc, ATH_DEBUG_SW_TX, "%s: paused\n", __func__); - ATH_TID_INSERT_TAIL(atid, bf, bf_list); + /* + * If the caller requested that it be sent at a high + * priority, queue it at the head of the list. + */ + if (queue_to_head) + ATH_TID_INSERT_HEAD(atid, bf, bf_list); + else + ATH_TID_INSERT_TAIL(atid, bf, bf_list); } else if (ath_tx_ampdu_pending(sc, an, tid)) { /* AMPDU pending; queue */ DPRINTF(sc, ATH_DEBUG_SW_TX, "%s: pending\n", __func__); @@ -2881,6 +3083,17 @@ ath_tx_swq(struct ath_softc *sc, struct DPRINTF(sc, ATH_DEBUG_SW_TX, "%s: xmit_normal\n", __func__); /* See if clrdmask needs to be set */ ath_tx_update_clrdmask(sc, atid, bf); + + /* + * Update the current leak count if + * we're leaking frames; and set the + * MORE flag as appropriate. + */ + ath_tx_leak_count_update(sc, atid, bf); + + /* + * Dispatch the frame. + */ ath_tx_xmit_normal(sc, txq, bf); } else { /* Busy; queue */ @@ -3254,13 +3467,19 @@ ath_tx_tid_bar_unsuspend(struct ath_soft ATH_TX_LOCK_ASSERT(sc); DPRINTF(sc, ATH_DEBUG_SW_TX_BAR, - "%s: tid=%p, called\n", + "%s: %6D: tid=%p, called\n", __func__, + tid->an->an_node.ni_macaddr, + ":", 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); + device_printf(sc->sc_dev, + "%s: %6D: bar_tx=%d, bar_wait=%d: ?\n", + __func__, + tid->an->an_node.ni_macaddr, + ":", + tid->bar_tx, tid->bar_wait); } tid->bar_tx = tid->bar_wait = 0; @@ -3281,8 +3500,12 @@ ath_tx_tid_bar_tx_ready(struct ath_softc if (tid->bar_wait == 0 || tid->hwq_depth > 0) return (0); - DPRINTF(sc, ATH_DEBUG_SW_TX_BAR, "%s: tid=%p (%d), bar ready\n", - __func__, tid, tid->tid); + DPRINTF(sc, ATH_DEBUG_SW_TX_BAR, + "%s: %6D: tid=%p (%d), bar ready\n", + __func__, + tid->an->an_node.ni_macaddr, + ":", + tid, tid->tid); return (1); } @@ -3307,8 +3530,10 @@ ath_tx_tid_bar_tx(struct ath_softc *sc, ATH_TX_LOCK_ASSERT(sc); DPRINTF(sc, ATH_DEBUG_SW_TX_BAR, - "%s: tid=%p, called\n", + "%s: %6D: tid=%p, called\n", __func__, + tid->an->an_node.ni_macaddr, + ":", tid); tap = ath_tx_get_tx_tid(tid->an, tid->tid); @@ -3318,8 +3543,10 @@ ath_tx_tid_bar_tx(struct ath_softc *sc, */ if (tid->bar_wait == 0 || tid->bar_tx == 1) { device_printf(sc->sc_dev, - "%s: tid=%p, bar_tx=%d, bar_wait=%d: ?\n", + "%s: %6D: tid=%p, bar_tx=%d, bar_wait=%d: ?\n", __func__, + tid->an->an_node.ni_macaddr, + ":", tid, tid->bar_tx, tid->bar_wait); @@ -3329,8 +3556,10 @@ ath_tx_tid_bar_tx(struct ath_softc *sc, /* Don't do anything if we still have pending frames */ if (tid->hwq_depth > 0) { DPRINTF(sc, ATH_DEBUG_SW_TX_BAR, - "%s: tid=%p, hwq_depth=%d, waiting\n", + "%s: %6D: tid=%p, hwq_depth=%d, waiting\n", __func__, + tid->an->an_node.ni_macaddr, + ":", tid, tid->hwq_depth); return; @@ -3352,8 +3581,10 @@ ath_tx_tid_bar_tx(struct ath_softc *sc, * XXX verify this is _actually_ the valid value to begin at! */ DPRINTF(sc, ATH_DEBUG_SW_TX_BAR, - "%s: tid=%p, new BAW left edge=%d\n", + "%s: %6D: tid=%p, new BAW left edge=%d\n", __func__, + tid->an->an_node.ni_macaddr, + ":", tid, tap->txa_start); @@ -3369,8 +3600,12 @@ ath_tx_tid_bar_tx(struct ath_softc *sc, /* Failure? For now, warn loudly and continue */ ATH_TX_LOCK(sc); - device_printf(sc->sc_dev, "%s: tid=%p, failed to TX BAR, continue!\n", - __func__, tid); + device_printf(sc->sc_dev, + "%s: %6D: tid=%p, failed to TX BAR, continue!\n", + __func__, + tid->an->an_node.ni_macaddr, + ":", + tid); ath_tx_tid_bar_unsuspend(sc, tid); } @@ -3457,10 +3692,12 @@ ath_tx_tid_drain_print(struct ath_softc tid->baw_tail, tap == NULL ? -1 : tap->txa_start, ni->ni_txseqs[tid->tid]); +#if 0 /* XXX Dump the frame, see what it is? */ ieee80211_dump_pkt(ni->ni_ic, mtod(bf->bf_m, const uint8_t *), bf->bf_m->m_len, 0, -1); +#endif } /* @@ -3501,7 +3738,7 @@ ath_tx_tid_drain(struct ath_softc *sc, s if (t == 0) { ath_tx_tid_drain_print(sc, an, "norm", tid, bf); - t = 1; +// t = 1; } ATH_TID_REMOVE(tid, bf, bf_list); @@ -3517,7 +3754,7 @@ ath_tx_tid_drain(struct ath_softc *sc, s if (t == 0) { ath_tx_tid_drain_print(sc, an, "filt", tid, bf); - t = 1; +// t = 1; } ATH_TID_FILT_REMOVE(tid, bf, bf_list); @@ -3548,10 +3785,15 @@ ath_tx_tid_drain(struct ath_softc *sc, s /* But don't do it for non-QoS TIDs */ if (tap) { -#if 0 +#if 1 DPRINTF(sc, ATH_DEBUG_SW_TX_CTRL, - "%s: node %p: TID %d: sliding BAW left edge to %d\n", - __func__, an, tid->tid, tap->txa_start); + "%s: %6D: node %p: TID %d: sliding BAW left edge to %d\n", + __func__, + ni->ni_macaddr, + ":", + an, + tid->tid, + tap->txa_start); #endif ni->ni_txseqs[tid->tid] = tap->txa_start; tid->baw_tail = tid->baw_head; @@ -3559,6 +3801,43 @@ ath_tx_tid_drain(struct ath_softc *sc, s } /* + * Reset the TID state. This must be only called once the node has + * had its frames flushed from this TID, to ensure that no other + * pause / unpause logic can kick in. + */ +static void +ath_tx_tid_reset(struct ath_softc *sc, struct ath_tid *tid) +{ + +#if 0 + tid->bar_wait = tid->bar_tx = tid->isfiltered = 0; + tid->paused = tid->sched = tid->addba_tx_pending = 0; + tid->incomp = tid->cleanup_inprogress = 0; +#endif + + /* + * Clear BAR, filtered frames, scheduled and ADDBA pending. + * The TID may be going through cleanup from the last association + * where things in the BAW are still in the hardware queue. + */ + tid->bar_wait = 0; + tid->bar_tx = 0; + tid->isfiltered = 0; + tid->sched = 0; + tid->addba_tx_pending = 0; + + /* + * XXX TODO: it may just be enough to walk the HWQs and mark + * frames for that node as non-aggregate; or mark the ath_node + * with something that indicates that aggregation is no longer + * occuring. Then we can just toss the BAW complaints and + * do a complete hard reset of state here - no pause, no + * complete counter, etc. *** DIFF OUTPUT TRUNCATED AT 1000 LINES ***
Want to link to this message? Use this URL: <https://mail-archive.FreeBSD.org/cgi/mid.cgi?201304211933.r3LJXtqr020369>