diff options
Diffstat (limited to 'sys/dev/ath/if_ath.c')
-rw-r--r-- | sys/dev/ath/if_ath.c | 350 |
1 files changed, 323 insertions, 27 deletions
diff --git a/sys/dev/ath/if_ath.c b/sys/dev/ath/if_ath.c index c3485e4..5d4d0ba 100644 --- a/sys/dev/ath/if_ath.c +++ b/sys/dev/ath/if_ath.c @@ -1359,16 +1359,36 @@ ath_intr(void *arg) HAL_INT status = 0; uint32_t txqs; + /* + * If we're inside a reset path, just print a warning and + * clear the ISR. The reset routine will finish it for us. + */ + ATH_PCU_LOCK(sc); + if (sc->sc_inreset_cnt) { + HAL_INT status; + ath_hal_getisr(ah, &status); /* clear ISR */ + ath_hal_intrset(ah, 0); /* disable further intr's */ + DPRINTF(sc, ATH_DEBUG_ANY, + "%s: in reset, ignoring: status=0x%x\n", + __func__, status); + ATH_PCU_UNLOCK(sc); + return; + } + if (sc->sc_invalid) { /* * The hardware is not ready/present, don't touch anything. * Note this can happen early on if the IRQ is shared. */ DPRINTF(sc, ATH_DEBUG_ANY, "%s: invalid; ignored\n", __func__); + ATH_PCU_UNLOCK(sc); return; } - if (!ath_hal_intrpend(ah)) /* shared irq, not for us */ + if (!ath_hal_intrpend(ah)) { /* shared irq, not for us */ + ATH_PCU_UNLOCK(sc); return; + } + if ((ifp->if_flags & IFF_UP) == 0 || (ifp->if_drv_flags & IFF_DRV_RUNNING) == 0) { HAL_INT status; @@ -1377,8 +1397,10 @@ ath_intr(void *arg) __func__, ifp->if_flags); ath_hal_getisr(ah, &status); /* clear ISR */ ath_hal_intrset(ah, 0); /* disable further intr's */ + ATH_PCU_UNLOCK(sc); return; } + /* * Figure out the reason(s) for the interrupt. Note * that the hal returns a pseudo-ISR that may include @@ -1400,9 +1422,23 @@ ath_intr(void *arg) status &= sc->sc_imask; /* discard unasked for bits */ /* Short-circuit un-handled interrupts */ - if (status == 0x0) + if (status == 0x0) { + ATH_PCU_UNLOCK(sc); return; + } + /* + * Take a note that we're inside the interrupt handler, so + * the reset routines know to wait. + */ + sc->sc_intr_cnt++; + ATH_PCU_UNLOCK(sc); + + /* + * Handle the interrupt. We won't run concurrent with the reset + * or channel change routines as they'll wait for sc_intr_cnt + * to be 0 before continuing. + */ if (status & HAL_INT_FATAL) { sc->sc_stats.ast_hardware++; ath_hal_intrset(ah, 0); /* disable intr's until reset */ @@ -1443,6 +1479,7 @@ ath_intr(void *arg) if (status & HAL_INT_RXEOL) { int imask; CTR0(ATH_KTR_ERR, "ath_intr: RXEOL"); + ATH_PCU_LOCK(sc); /* * NB: the hardware should re-read the link when * RXE bit is written, but it doesn't work at @@ -1458,7 +1495,6 @@ ath_intr(void *arg) * by a call to ath_reset() somehow, the * interrupt mask will be correctly reprogrammed. */ - ATH_LOCK(sc); imask = sc->sc_imask; imask &= ~(HAL_INT_RXEOL | HAL_INT_RXORN); ath_hal_intrset(ah, imask); @@ -1476,13 +1512,13 @@ ath_intr(void *arg) if (! sc->sc_kickpcu) sc->sc_rxlink = NULL; sc->sc_kickpcu = 1; - ATH_UNLOCK(sc); /* * Enqueue an RX proc, to handled whatever * is in the RX queue. * This will then kick the PCU. */ taskqueue_enqueue(sc->sc_tq, &sc->sc_rxtask); + ATH_PCU_UNLOCK(sc); } if (status & HAL_INT_TXURN) { sc->sc_stats.ast_txurn++; @@ -1500,12 +1536,12 @@ ath_intr(void *arg) * and blank them. This is the only place we should be * doing this. */ - ATH_LOCK(sc); + ATH_PCU_LOCK(sc); txqs = 0xffffffff; ath_hal_gettxintrtxqs(sc->sc_ah, &txqs); sc->sc_txq_active |= txqs; - ATH_UNLOCK(sc); taskqueue_enqueue(sc->sc_tq, &sc->sc_txtask); + ATH_PCU_UNLOCK(sc); } if (status & HAL_INT_BMISS) { sc->sc_stats.ast_bmiss++; @@ -1517,6 +1553,7 @@ ath_intr(void *arg) sc->sc_stats.ast_tx_cst++; if (status & HAL_INT_MIB) { sc->sc_stats.ast_mib++; + ATH_PCU_LOCK(sc); /* * Disable interrupts until we service the MIB * interrupt; otherwise it will continue to fire. @@ -1533,10 +1570,9 @@ ath_intr(void *arg) * RXEOL before the rxproc has had a chance * to run. */ - ATH_LOCK(sc); if (sc->sc_kickpcu == 0) ath_hal_intrset(ah, sc->sc_imask); - ATH_UNLOCK(sc); + ATH_PCU_UNLOCK(sc); } if (status & HAL_INT_RXORN) { /* NB: hal marks HAL_INT_FATAL when RXORN is fatal */ @@ -1544,6 +1580,9 @@ ath_intr(void *arg) sc->sc_stats.ast_rxorn++; } } + ATH_PCU_LOCK(sc); + sc->sc_intr_cnt--; + ATH_PCU_UNLOCK(sc); } static void @@ -1818,6 +1857,48 @@ ath_stop_locked(struct ifnet *ifp) } } +#define MAX_TXRX_ITERATIONS 1000 +static void +ath_txrx_stop(struct ath_softc *sc) +{ + int i = MAX_TXRX_ITERATIONS; + + ATH_UNLOCK_ASSERT(sc); + + /* Stop any new TX/RX from occuring */ + taskqueue_block(sc->sc_tq); + + ATH_PCU_LOCK(sc); + + /* + * Sleep until all the pending operations have completed. + * + * The caller must ensure that reset has been incremented + * or the pending operations may continue being queued. + */ + while (sc->sc_rxproc_cnt || sc->sc_txproc_cnt || + sc->sc_txstart_cnt || sc->sc_intr_cnt) { + if (i <= 0) + break; + msleep(sc, &sc->sc_mtx, 0, "ath_txrx_stop", 1); + i--; + } + ATH_PCU_UNLOCK(sc); + + if (i <= 0) + device_printf(sc->sc_dev, + "%s: didn't finish after %d iterations\n", + __func__, MAX_TXRX_ITERATIONS); +} +#undef MAX_TXRX_ITERATIONS + +static void +ath_txrx_start(struct ath_softc *sc) +{ + + taskqueue_unblock(sc->sc_tq); +} + static void ath_stop(struct ifnet *ifp) { @@ -1842,17 +1923,51 @@ ath_reset(struct ifnet *ifp, ATH_RESET_TYPE reset_type) struct ieee80211com *ic = ifp->if_l2com; struct ath_hal *ah = sc->sc_ah; HAL_STATUS status; + int i; DPRINTF(sc, ATH_DEBUG_RESET, "%s: called\n", __func__); + /* XXX ensure ATH_LOCK isn't held; ath_rx_proc can't be locked */ + ATH_PCU_UNLOCK_ASSERT(sc); + ATH_UNLOCK_ASSERT(sc); + + ATH_PCU_LOCK(sc); + /* XXX if we're already inside a reset, print out a big warning */ + if (sc->sc_inreset_cnt > 0) { + device_printf(sc->sc_dev, + "%s: concurrent ath_reset()! Danger!\n", + __func__); + } + sc->sc_inreset_cnt++; ath_hal_intrset(ah, 0); /* disable interrupts */ + ATH_PCU_UNLOCK(sc); + + /* + * XXX should now wait for pending TX/RX to complete + * and block future ones from occuring. + */ + ath_txrx_stop(sc); + ath_draintxq(sc, reset_type); /* stop xmit side */ + /* - * XXX Don't flush if ATH_RESET_NOLOSS;but we have to first - * XXX need to ensure this doesn't race with an outstanding - * XXX taskqueue call. + * Regardless of whether we're doing a no-loss flush or + * not, stop the PCU and handle what's in the RX queue. + * That way frames aren't dropped which shouldn't be. */ - ath_stoprecv(sc); /* stop recv side */ + ath_hal_stoppcurecv(ah); + ath_hal_setrxfilter(ah, 0); + ath_rx_proc(sc, 0); + + /* + * If we're not doing a noloss reset, now call ath_stoprecv(). + * This fully stops all of the RX machinery and flushes whatever + * frames are in the RX ring buffer. Hopefully all completed + * frames have been handled at this point. + */ + if (reset_type != ATH_RESET_NOLOSS) + ath_stoprecv(sc); /* stop recv side */ + ath_settkipmic(sc); /* configure TKIP MIC handling */ /* NB: indicate channel change so we do a full reset */ if (!ath_hal_reset(ah, sc->sc_opmode, ic->ic_curchan, AH_TRUE, &status)) @@ -1879,8 +1994,59 @@ ath_reset(struct ifnet *ifp, ATH_RESET_TYPE reset_type) #endif ath_beacon_config(sc, NULL); } + + /* + * Release the reset lock and re-enable interrupts here. + * If an interrupt was being processed in ath_intr(), + * it would disable interrupts at this point. So we have + * to atomically enable interrupts and decrement the + * reset counter - this way ath_intr() doesn't end up + * disabling interrupts without a corresponding enable + * in the rest or channel change path. + */ + ATH_PCU_LOCK(sc); + sc->sc_inreset_cnt--; + /* XXX only do this if sc_inreset_cnt == 0? */ ath_hal_intrset(ah, sc->sc_imask); + ATH_PCU_UNLOCK(sc); + /* + * TX and RX can be started here. If it were started with + * sc_inreset_cnt > 0, the TX and RX path would abort. + * Thus if this is a nested call through the reset or + * channel change code, TX completion will occur but + * RX completion and ath_start / ath_tx_start will not + * run. + */ + + /* Restart TX/RX as needed */ + ath_txrx_start(sc); + + /* XXX Restart TX completion and pending TX */ + if (reset_type == ATH_RESET_NOLOSS) { + for (i = 0; i < HAL_NUM_TX_QUEUES; i++) { + if (ATH_TXQ_SETUP(sc, i)) { + ATH_TXQ_LOCK(&sc->sc_txq[i]); + ath_txq_restart_dma(sc, &sc->sc_txq[i]); + ath_txq_sched(sc, &sc->sc_txq[i]); + ATH_TXQ_UNLOCK(&sc->sc_txq[i]); + } + } + } + + /* + * This may have been set during an ath_start() call which + * set this once it detected a concurrent TX was going on. + * So, clear it. + */ + /* XXX do this inside of IF_LOCK? */ + ifp->if_drv_flags &= ~IFF_DRV_OACTIVE; + + /* Handle any frames in the TX queue */ + /* + * XXX should this be done by the caller, rather than + * ath_reset() ? + */ ath_start(ifp); /* restart xmit */ return 0; } @@ -2011,6 +2177,7 @@ ath_getbuf(struct ath_softc *sc) DPRINTF(sc, ATH_DEBUG_XMIT, "%s: stop queue\n", __func__); sc->sc_stats.ast_tx_qstop++; + /* XXX do this inside of IF_LOCK? */ ifp->if_drv_flags |= IFF_DRV_OACTIVE; } ATH_TXBUF_UNLOCK(sc); @@ -2028,6 +2195,20 @@ ath_start(struct ifnet *ifp) if ((ifp->if_drv_flags & IFF_DRV_RUNNING) == 0 || sc->sc_invalid) return; + + /* XXX is it ok to hold the ATH_LOCK here? */ + ATH_PCU_LOCK(sc); + if (sc->sc_inreset_cnt > 0) { + device_printf(sc->sc_dev, + "%s: sc_inreset_cnt > 0; bailing\n", __func__); + /* XXX do this inside of IF_LOCK? */ + ifp->if_drv_flags |= IFF_DRV_OACTIVE; + ATH_PCU_UNLOCK(sc); + return; + } + sc->sc_txstart_cnt++; + ATH_PCU_UNLOCK(sc); + for (;;) { /* * Grab a TX buffer and associated resources. @@ -2111,6 +2292,10 @@ ath_start(struct ifnet *ifp) sc->sc_wd_timer = 5; } + + ATH_PCU_LOCK(sc); + sc->sc_txstart_cnt--; + ATH_PCU_UNLOCK(sc); } static int @@ -3688,6 +3873,14 @@ ath_rx_tasklet(void *arg, int npending) CTR1(ATH_KTR_INTR, "ath_rx_proc: pending=%d", npending); DPRINTF(sc, ATH_DEBUG_RX_PROC, "%s: pending %u\n", __func__, npending); + ATH_PCU_LOCK(sc); + if (sc->sc_inreset_cnt > 0) { + device_printf(sc->sc_dev, + "%s: sc_inreset_cnt > 0; skipping\n", __func__); + ATH_PCU_UNLOCK(sc); + return; + } + ATH_PCU_UNLOCK(sc); ath_rx_proc(sc, 1); } @@ -3711,6 +3904,14 @@ ath_rx_proc(struct ath_softc *sc, int resched) u_int64_t tsf; int npkts = 0; + /* XXX we must not hold the ATH_LOCK here */ + ATH_UNLOCK_ASSERT(sc); + ATH_PCU_UNLOCK_ASSERT(sc); + + ATH_PCU_LOCK(sc); + sc->sc_rxproc_cnt++; + ATH_PCU_UNLOCK(sc); + DPRINTF(sc, ATH_DEBUG_RX_PROC, "%s: called\n", __func__); ngood = 0; nf = ath_hal_getchannoise(ah, sc->sc_curchan); @@ -4056,24 +4257,29 @@ rx_next: * need to be handled, kick the PCU if there's * been an RXEOL condition. */ + ATH_PCU_LOCK(sc); if (resched && sc->sc_kickpcu) { CTR0(ATH_KTR_ERR, "ath_rx_proc: kickpcu"); device_printf(sc->sc_dev, "%s: kickpcu; handled %d packets\n", __func__, npkts); /* XXX rxslink? */ + /* + * XXX can we hold the PCU lock here? + * Are there any net80211 buffer calls involved? + */ bf = TAILQ_FIRST(&sc->sc_rxbuf); ath_hal_putrxbuf(ah, bf->bf_daddr); ath_hal_rxena(ah); /* enable recv descriptors */ ath_mode_init(sc); /* set filters, etc. */ ath_hal_startpcurecv(ah); /* re-enable PCU/DMA engine */ - ATH_LOCK(sc); ath_hal_intrset(ah, sc->sc_imask); sc->sc_kickpcu = 0; - ATH_UNLOCK(sc); } + ATH_PCU_UNLOCK(sc); + /* XXX check this inside of IF_LOCK? */ if (resched && (ifp->if_drv_flags & IFF_DRV_OACTIVE) == 0) { #ifdef IEEE80211_SUPPORT_SUPERG ieee80211_ff_age_all(ic, 100); @@ -4082,6 +4288,10 @@ rx_next: ath_start(ifp); } #undef PA2DESC + + ATH_PCU_LOCK(sc); + sc->sc_rxproc_cnt--; + ATH_PCU_UNLOCK(sc); } static void @@ -4588,22 +4798,28 @@ ath_tx_proc_q0(void *arg, int npending) struct ifnet *ifp = sc->sc_ifp; uint32_t txqs; - ATH_LOCK(sc); + ATH_PCU_LOCK(sc); + sc->sc_txproc_cnt++; txqs = sc->sc_txq_active; sc->sc_txq_active &= ~txqs; - ATH_UNLOCK(sc); + ATH_PCU_UNLOCK(sc); if (TXQACTIVE(txqs, 0) && ath_tx_processq(sc, &sc->sc_txq[0], 1)) /* XXX why is lastrx updated in tx code? */ sc->sc_lastrx = ath_hal_gettsf64(sc->sc_ah); if (TXQACTIVE(txqs, sc->sc_cabq->axq_qnum)) ath_tx_processq(sc, sc->sc_cabq, 1); + /* XXX check this inside of IF_LOCK? */ ifp->if_drv_flags &= ~IFF_DRV_OACTIVE; sc->sc_wd_timer = 0; if (sc->sc_softled) ath_led_event(sc, sc->sc_txrix); + ATH_PCU_LOCK(sc); + sc->sc_txproc_cnt--; + ATH_PCU_UNLOCK(sc); + ath_start(ifp); } @@ -4619,10 +4835,11 @@ ath_tx_proc_q0123(void *arg, int npending) int nacked; uint32_t txqs; - ATH_LOCK(sc); + ATH_PCU_LOCK(sc); + sc->sc_txproc_cnt++; txqs = sc->sc_txq_active; sc->sc_txq_active &= ~txqs; - ATH_UNLOCK(sc); + ATH_PCU_UNLOCK(sc); /* * Process each active queue. @@ -4641,12 +4858,17 @@ ath_tx_proc_q0123(void *arg, int npending) if (nacked) sc->sc_lastrx = ath_hal_gettsf64(sc->sc_ah); + /* XXX check this inside of IF_LOCK? */ ifp->if_drv_flags &= ~IFF_DRV_OACTIVE; sc->sc_wd_timer = 0; if (sc->sc_softled) ath_led_event(sc, sc->sc_txrix); + ATH_PCU_LOCK(sc); + sc->sc_txproc_cnt--; + ATH_PCU_UNLOCK(sc); + ath_start(ifp); } @@ -4661,10 +4883,11 @@ ath_tx_proc(void *arg, int npending) int i, nacked; uint32_t txqs; - ATH_LOCK(sc); + ATH_PCU_LOCK(sc); + sc->sc_txproc_cnt++; txqs = sc->sc_txq_active; sc->sc_txq_active &= ~txqs; - ATH_UNLOCK(sc); + ATH_PCU_UNLOCK(sc); /* * Process each active queue. @@ -4676,12 +4899,17 @@ ath_tx_proc(void *arg, int npending) if (nacked) sc->sc_lastrx = ath_hal_gettsf64(sc->sc_ah); + /* XXX check this inside of IF_LOCK? */ ifp->if_drv_flags &= ~IFF_DRV_OACTIVE; sc->sc_wd_timer = 0; if (sc->sc_softled) ath_led_event(sc, sc->sc_txrix); + ATH_PCU_LOCK(sc); + sc->sc_txproc_cnt--; + ATH_PCU_UNLOCK(sc); + ath_start(ifp); } #undef TXQACTIVE @@ -4863,9 +5091,18 @@ ath_draintxq(struct ath_softc *sc, ATH_RESET_TYPE reset_type) (void) ath_stoptxdma(sc); - for (i = 0; i < HAL_NUM_TX_QUEUES; i++) - if (ATH_TXQ_SETUP(sc, i)) - ath_tx_draintxq(sc, &sc->sc_txq[i]); + for (i = 0; i < HAL_NUM_TX_QUEUES; i++) { + /* + * XXX TODO: should we just handle the completed TX frames + * here, whether or not the reset is a full one or not? + */ + if (ATH_TXQ_SETUP(sc, i)) { + if (reset_type == ATH_RESET_NOLOSS) + ath_tx_processq(sc, &sc->sc_txq[i], 0); + else + ath_tx_draintxq(sc, &sc->sc_txq[i]); + } + } #ifdef ATH_DEBUG if (sc->sc_debug & ATH_DEBUG_RESET) { struct ath_buf *bf = TAILQ_FIRST(&sc->sc_bbuf); @@ -4879,6 +5116,7 @@ ath_draintxq(struct ath_softc *sc, ATH_RESET_TYPE reset_type) } } #endif /* ATH_DEBUG */ + /* XXX check this inside of IF_LOCK? */ ifp->if_drv_flags &= ~IFF_DRV_OACTIVE; sc->sc_wd_timer = 0; } @@ -4984,6 +5222,22 @@ ath_chan_set(struct ath_softc *sc, struct ieee80211_channel *chan) struct ifnet *ifp = sc->sc_ifp; struct ieee80211com *ic = ifp->if_l2com; struct ath_hal *ah = sc->sc_ah; + int ret = 0; + int dointr = 0; + + /* Treat this as an interface reset */ + ATH_PCU_LOCK(sc); + if (sc->sc_inreset_cnt > 0) + device_printf(sc->sc_dev, "%s: danger! concurrent reset!\n", + __func__); + sc->sc_inreset_cnt++; + if (chan != sc->sc_curchan) { + dointr = 1; + /* XXX only do this if inreset_cnt is 1? */ + ath_hal_intrset(ah, 0); + } + ATH_PCU_UNLOCK(sc); + ath_txrx_stop(sc); DPRINTF(sc, ATH_DEBUG_RESET, "%s: %u (%u MHz, flags 0x%x)\n", __func__, ieee80211_chan2ieee(ic, chan), @@ -4996,7 +5250,9 @@ ath_chan_set(struct ath_softc *sc, struct ieee80211_channel *chan) * hardware at the new frequency, and then re-enable * the relevant bits of the h/w. */ +#if 0 ath_hal_intrset(ah, 0); /* disable interrupts */ +#endif ath_draintxq(sc, ATH_RESET_FULL); /* clear pending tx frames */ ath_stoprecv(sc); /* turn off frame recv */ if (!ath_hal_reset(ah, sc->sc_opmode, chan, AH_TRUE, &status)) { @@ -5004,7 +5260,8 @@ ath_chan_set(struct ath_softc *sc, struct ieee80211_channel *chan) "channel %u (%u MHz, flags 0x%x), hal status %u\n", __func__, ieee80211_chan2ieee(ic, chan), chan->ic_freq, chan->ic_flags, status); - return EIO; + ret = EIO; + goto finish; } sc->sc_diversity = ath_hal_getdiversity(ah); @@ -5017,7 +5274,8 @@ ath_chan_set(struct ath_softc *sc, struct ieee80211_channel *chan) if (ath_startrecv(sc) != 0) { if_printf(ifp, "%s: unable to restart recv logic\n", __func__); - return EIO; + ret = EIO; + goto finish; } /* @@ -5039,12 +5297,28 @@ ath_chan_set(struct ath_softc *sc, struct ieee80211_channel *chan) ath_beacon_config(sc, NULL); } +#if 0 /* * Re-enable interrupts. */ ath_hal_intrset(ah, sc->sc_imask); +#endif } - return 0; + +finish: + ATH_PCU_LOCK(sc); + sc->sc_inreset_cnt--; + /* XXX only do this if sc_inreset_cnt == 0? */ + if (dointr) + ath_hal_intrset(ah, sc->sc_imask); + ATH_PCU_UNLOCK(sc); + + /* XXX do this inside of IF_LOCK? */ + ifp->if_drv_flags &= ~IFF_DRV_OACTIVE; + ath_txrx_start(sc); + /* XXX ath_start? */ + + return ret; } /* @@ -5087,7 +5361,18 @@ ath_calibrate(void *arg) DPRINTF(sc, ATH_DEBUG_CALIBRATE, "%s: rfgain change\n", __func__); sc->sc_stats.ast_per_rfgain++; + /* + * Drop lock - we can't hold it across the + * ath_reset() call. Instead, we'll drop + * out here, do a reset, then reschedule + * the callout. + */ + callout_reset(&sc->sc_cal_ch, 1, ath_calibrate, sc); + sc->sc_resetcal = 0; + sc->sc_doresetcal = AH_TRUE; + ATH_UNLOCK(sc); ath_reset(ifp, ATH_RESET_NOLOSS); + return; } /* * If this long cal is after an idle period, then @@ -5742,6 +6027,7 @@ static void ath_watchdog(void *arg) { struct ath_softc *sc = arg; + int do_reset = 0; if (sc->sc_wd_timer != 0 && --sc->sc_wd_timer == 0) { struct ifnet *ifp = sc->sc_ifp; @@ -5753,10 +6039,20 @@ ath_watchdog(void *arg) hangs & 0xff ? "bb" : "mac", hangs); } else if_printf(ifp, "device timeout\n"); - ath_reset(ifp, ATH_RESET_NOLOSS); + do_reset = 1; ifp->if_oerrors++; sc->sc_stats.ast_watchdog++; } + + /* + * We can't hold the lock across the ath_reset() call. + */ + if (do_reset) { + ATH_UNLOCK(sc); + ath_reset(sc->sc_ifp, ATH_RESET_NOLOSS); + ATH_LOCK(sc); + } + callout_schedule(&sc->sc_wd_ch, hz); } |