Index: if_ath_rx.c =================================================================== --- if_ath_rx.c (revision 249085) +++ if_ath_rx.c (working copy) @@ -231,6 +231,7 @@ struct ath_desc *ds; m = bf->bf_m; + if (m == NULL) { /* * NB: by assigning a page to the rx dma buffer we Index: if_athvar.h =================================================================== --- if_athvar.h (revision 248985) +++ if_athvar.h (working copy) @@ -182,6 +182,7 @@ uint32_t an_swq_depth; /* how many SWQ packets for this node */ int clrdmask; /* has clrdmask been set */ + uint32_t an_leak_count; /* How many frames to leak during pause */ /* variable-length rate control state follows */ }; #define ATH_NODE(ni) ((struct ath_node *)(ni)) @@ -463,6 +464,8 @@ void (*av_bmiss)(struct ieee80211vap *); void (*av_node_ps)(struct ieee80211_node *, int); int (*av_set_tim)(struct ieee80211_node *, int); + void (*av_recv_pspoll)(struct ieee80211_node *, + struct mbuf *); }; #define ATH_VAP(vap) ((struct ath_vap *)(vap)) Index: if_ath_sysctl.c =================================================================== --- if_ath_sysctl.c (revision 248985) +++ if_ath_sysctl.c (working copy) @@ -754,7 +754,7 @@ &sc->sc_txq_mcastq_maxdepth, 0, "Maximum buffer depth for multicast/broadcast frames"); -#if 0 +#if 1 SYSCTL_ADD_INT(ctx, SYSCTL_CHILDREN(tree), OID_AUTO, "cabq_enable", CTLFLAG_RW, &sc->sc_cabq_enable, 0, Index: if_ath_tx_ht.c =================================================================== --- if_ath_tx_ht.c (revision 248985) +++ if_ath_tx_ht.c (working copy) @@ -878,9 +878,14 @@ bf_prev = bf; /* - * XXX TODO: if any sub-frames have RTS/CTS enabled; - * enable it for the entire aggregate. + * If we're leaking frames, just return at this point; + * we've queued a single frame and we don't want to add + * any more. */ + if (tid->an->an_leak_count) { + status = ATH_AGGR_LEAK_CLOSED; + break; + } #if 0 /* Index: if_ath_tx_ht.h =================================================================== --- if_ath_tx_ht.h (revision 248985) +++ if_ath_tx_ht.h (working copy) @@ -46,6 +46,7 @@ ATH_AGGR_8K_LIMITED, ATH_AGGR_ERROR, ATH_AGGR_NONAGGR, + ATH_AGGR_LEAK_CLOSED, } ATH_AGGR_STATUS; extern int ath_max_4ms_framelen[4][32]; Index: if_ath_tx.c =================================================================== --- if_ath_tx.c (revision 249000) +++ if_ath_tx.c (working copy) @@ -1410,6 +1410,10 @@ * 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, @@ -1739,6 +1743,15 @@ bf->bf_state.bfs_rc[0].tries = try0; bf->bf_state.bfs_rc[0].ratecode = txrate; +#if 0 + device_printf(sc->sc_dev, "%s: %6D: rate0=0x%02x, txpower=%d dBm\n", + __func__, + ni->ni_macaddr, + ":", + txrate, + ni->ni_txpower); +#endif + /* Store the decided rate index values away */ bf->bf_state.bfs_pktlen = pktlen; bf->bf_state.bfs_hdrlen = hdrlen; @@ -1927,17 +1940,56 @@ * 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) { + } else 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__); bf->bf_state.bfs_txflags |= HAL_TXDESC_CLRDMASK; + /* + * XXX TODO: if the node is asleep, _do_ software + * queue this frame! + */ ath_tx_xmit_normal(sc, txq, bf); + } 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: bf=%p: swq: TX'ing\n", __func__, bf); + ath_tx_swq(sc, ni, txq, bf); + } 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); + 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, @@ -1950,6 +2002,12 @@ * 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: @@ -2100,6 +2158,15 @@ ds = bf->bf_desc; /* XXX check return value? */ +#if 0 + device_printf(sc->sc_dev, "%s: %6D: rate0=0x%02x, txpower=%d dBm\n", + __func__, + ni->ni_macaddr, + ":", + txrate, + params->ibp_power); +#endif + /* Store the decided rate index values away */ bf->bf_state.bfs_pktlen = pktlen; bf->bf_state.bfs_hdrlen = hdrlen; @@ -2165,6 +2232,12 @@ #if 1 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 { /* Queue to software queue */ @@ -2173,6 +2246,12 @@ #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; @@ -2588,7 +2667,8 @@ __func__, bf, SEQNO(bf->bf_state.bfs_seqno), tid->tx_buf[cindex], - SEQNO(tid->tx_buf[cindex]->bf_state.bfs_seqno)); + (tid->tx_buf[cindex] != NULL) ? + SEQNO(tid->tx_buf[cindex]->bf_state.bfs_seqno) : -1); } tid->tx_buf[cindex] = NULL; @@ -2603,6 +2683,68 @@ __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); + ATH_NODE_UNLOCK_ASSERT(tid->an); + + ATH_NODE_LOCK(tid->an); + + 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 --; + } + ATH_NODE_UNLOCK(tid->an); +} + +static int +ath_tx_tid_can_tx_or_sched(struct ath_softc *sc, struct ath_tid *tid) +{ + + ATH_TX_LOCK_ASSERT(sc); + ATH_NODE_UNLOCK_ASSERT(tid->an); + + ATH_NODE_LOCK(tid->an); + if (tid->an->an_leak_count > 0) { + ATH_NODE_UNLOCK(tid->an); + return (1); + } + ATH_NODE_UNLOCK(tid->an); + if (tid->paused) + return (0); + return (1); +} + /* * Mark the current node/TID as ready to TX. * @@ -2611,14 +2753,19 @@ * * 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) @@ -2626,6 +2773,30 @@ 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); } @@ -2722,7 +2893,7 @@ 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; @@ -2782,6 +2953,13 @@ /* 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); } @@ -2824,8 +3002,11 @@ * 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); @@ -2878,6 +3059,17 @@ 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 */ @@ -2974,7 +3166,19 @@ { ATH_TX_LOCK_ASSERT(sc); - tid->paused--; + /* + * There's some odd places where ath_tx_tid_resume() is called + * when it shouldn't be; this works around that particular issue + * until it's actually resolved. + */ + if (tid->paused == 0) { + device_printf(sc->sc_dev, "%s: %6D: paused=0?\n", + __func__, + tid->an->an_node.ni_macaddr, + ":"); + } else { + tid->paused--; + } DPRINTF(sc, ATH_DEBUG_SW_TX_CTRL, "%s: unpaused = %d\n", __func__, tid->paused); @@ -3239,13 +3443,19 @@ 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; @@ -3266,8 +3476,12 @@ 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); } @@ -3292,8 +3506,10 @@ 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); @@ -3303,8 +3519,10 @@ */ 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); @@ -3314,8 +3532,10 @@ /* 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; @@ -3337,8 +3557,10 @@ * 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); @@ -3354,8 +3576,12 @@ /* 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); } @@ -3442,10 +3668,12 @@ 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 } /* @@ -3486,7 +3714,7 @@ if (t == 0) { ath_tx_tid_drain_print(sc, an, "norm", tid, bf); - t = 1; +// t = 1; } ATH_TID_REMOVE(tid, bf, bf_list); @@ -3502,7 +3730,7 @@ 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); @@ -3544,6 +3772,20 @@ } /* + * 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) +{ + + tid->bar_wait = tid->bar_tx = tid->isfiltered = 0; + tid->paused = tid->sched = tid->addba_tx_pending = 0; + tid->incomp = tid->cleanup_inprogress = 0; +} + +/* * Flush all software queued packets for the given node. * * This occurs when a completion handler frees the last buffer @@ -3563,14 +3805,36 @@ &an->an_node); ATH_TX_LOCK(sc); + DPRINTF(sc, ATH_DEBUG_NODE, + "%s: %6D: flush; is_powersave=%d, stack_psq=%d, tim=%d, " + "swq_depth=%d, clrdmask=%d, leak_count=%d\n", + __func__, + an->an_node.ni_macaddr, + ":", + an->an_is_powersave, + an->an_stack_psq, + an->an_tim_set, + an->an_swq_depth, + an->clrdmask, + an->an_leak_count); + for (tid = 0; tid < IEEE80211_TID_SIZE; tid++) { struct ath_tid *atid = &an->an_tid[tid]; /* Free packets */ ath_tx_tid_drain(sc, an, atid, &bf_cq); + /* Remove this tid from the list of active tids */ ath_tx_tid_unsched(sc, atid); + + /* Reset the per-TID pause, BAR, etc state */ + ath_tx_tid_reset(sc, atid); } + + /* + * Clear global leak count + */ + an->an_leak_count = 0; ATH_TX_UNLOCK(sc); /* Handle completed frames */ @@ -4747,6 +5011,11 @@ DPRINTF(sc, ATH_DEBUG_SW_TX, "%s: tid=%d\n", __func__, tid->tid); ATH_TX_LOCK_ASSERT(sc); + /* + * XXX TODO: If we're called for a queue that we're leaking frames to, + * ensure we only leak one. + */ + tap = ath_tx_get_tx_tid(an, tid->tid); if (tid->tid == IEEE80211_NONQOS_TID) @@ -4764,7 +5033,7 @@ * of packet loss; but as its serialised with this code, * it won't "appear" half way through queuing packets. */ - if (tid->paused) + if (! ath_tx_tid_can_tx_or_sched(sc, tid)) break; bf = ATH_TID_FIRST(tid); @@ -4808,7 +5077,6 @@ ath_tx_rate_fill_rcflags(sc, bf); ath_tx_setds(sc, bf); ath_hal_clr11n_aggr(sc->sc_ah, bf->bf_desc); - sc->sc_aggr_stats.aggr_nonbaw_pkt++; /* Queue the packet; continue */ @@ -4907,7 +5175,6 @@ * already points to the rest in the chain. */ ath_tx_setds_11n(sc, bf); - } queuepkt: /* Set completion handler, multi-frame aggregate or not */ @@ -4916,6 +5183,14 @@ if (bf->bf_state.bfs_tid == IEEE80211_NONQOS_TID) device_printf(sc->sc_dev, "%s: TID=16?\n", __func__); + /* + * Update leak count and frame config if were leaking frames. + * + * XXX TODO: it should update all frames in an aggregate + * correctly! + */ + ath_tx_leak_count_update(sc, tid, bf); + /* Punt to txq */ ath_tx_handoff(sc, txq, bf); @@ -4931,7 +5206,8 @@ * XXX locking on txq here? */ if (txq->axq_aggr_depth >= sc->sc_hwq_limit || - status == ATH_AGGR_BAW_CLOSED) + (status == ATH_AGGR_BAW_CLOSED || + status == ATH_AGGR_LEAK_CLOSED)) break; } } @@ -4964,8 +5240,11 @@ /* * If the upper layers have paused the TID, don't * queue any further packets. + * + * XXX if we are leaking frames, make sure we decrement + * that counter _and_ we continue here. */ - if (tid->paused) + if (! ath_tx_tid_can_tx_or_sched(sc, tid)) break; bf = ATH_TID_FIRST(tid); @@ -5001,6 +5280,13 @@ ath_tx_rate_fill_rcflags(sc, bf); ath_tx_setds(sc, bf); + /* + * 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); + /* Track outstanding buffer count to hardware */ /* aggregates are "one" buffer */ tid->hwq_depth++; @@ -5048,7 +5334,11 @@ DPRINTF(sc, ATH_DEBUG_SW_TX, "%s: tid=%d, paused=%d\n", __func__, tid->tid, tid->paused); ath_tx_tid_unsched(sc, tid); - if (tid->paused) { + /* + * This node may be in power-save and we're leaking + * a frame; be careful. + */ + if (! ath_tx_tid_can_tx_or_sched(sc, tid)) { continue; } if (ath_tx_ampdu_running(sc, tid->an, tid->tid)) @@ -5069,6 +5359,11 @@ * If this was the last entry on the original list, stop. * Otherwise nodes that have been rescheduled onto the end * of the TID FIFO list will just keep being rescheduled. + * + * XXX What should we do about nodes that were paused + * but are pending a leaking frame in response to a ps-poll? + * They'll be put at the front of the list; so they'll + * prematurely trigger this condition! Ew. */ if (tid == last) break; @@ -5324,8 +5619,10 @@ int attempts = tap->txa_attempts; DPRINTF(sc, ATH_DEBUG_SW_TX_BAR, - "%s: called; tap=%p, atid=%p, txa_tid=%d, atid->tid=%d, status=%d, attempts=%d\n", + "%s: %6D: called; tap=%p, atid=%p, txa_tid=%d, atid->tid=%d, status=%d, attempts=%d\n", __func__, + ni->ni_macaddr, + ":", tap, atid, tap->txa_tid, @@ -5465,6 +5762,10 @@ /* In case of concurrency races from net80211.. */ if (an->an_is_powersave == 1) { + /* + * Clear any pending leaked frame requests + */ + an->an_leak_count = 0; ATH_NODE_UNLOCK(an); device_printf(sc->sc_dev, "%s: an=%p: node was already asleep\n", @@ -5511,6 +5812,10 @@ /* Mark node as awake */ an->an_is_powersave = 0; + /* + * Clear any pending leaked frame requests + */ + an->an_leak_count = 0; ATH_NODE_UNLOCK(an); Index: if_ath_tx.h =================================================================== --- if_ath_tx.h (revision 248985) +++ if_ath_tx.h (working copy) @@ -113,6 +113,7 @@ struct ath_tid *tid, struct ath_buf *bf); extern struct ieee80211_tx_ampdu * ath_tx_get_tx_tid(struct ath_node *an, int tid); +extern void ath_tx_tid_sched(struct ath_softc *sc, struct ath_tid *tid); /* TX addba handling */ extern int ath_addba_request(struct ieee80211_node *ni, Index: if_ath.c =================================================================== --- if_ath.c (revision 248999) +++ if_ath.c (working copy) @@ -125,7 +125,7 @@ /* * 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_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 @@ -1236,6 +1237,9 @@ 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 */ /* @@ -3367,7 +3371,7 @@ /* 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; } @@ -3377,6 +3381,9 @@ 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)); @@ -3389,7 +3396,8 @@ 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); } @@ -5203,6 +5211,26 @@ (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. + * + * XXX This here is a good time to have a node lock with sensible + * locking.. + */ + + ATH_TX_LOCK(sc); + if (! isnew && an->an_is_powersave) { + ATH_TX_UNLOCK(sc); + device_printf(sc->sc_dev, + "%s: %6D: reassoc + asleep, waking up..\n", + __func__, + ni->ni_macaddr, + ":"); + ath_tx_node_wakeup(sc, an); + } else + ATH_TX_UNLOCK(sc); } static int @@ -5718,8 +5746,11 @@ 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) @@ -5818,21 +5849,30 @@ */ 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); + "%s: %6D: enable=%d, tim_set=1, ignoring\n", + __func__, + ni->ni_macaddr, + ":", + enable); ATH_NODE_UNLOCK(an); } 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); 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); changed = avp->av_set_tim(ni, enable); @@ -5841,8 +5881,11 @@ * 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); changed = avp->av_set_tim(ni, enable); @@ -5854,8 +5897,11 @@ */ ATH_NODE_UNLOCK(an); 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; } @@ -5932,8 +5978,10 @@ 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); @@ -5953,9 +6001,11 @@ 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); @@ -5968,6 +6018,150 @@ #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)