Skip site navigation (1)Skip section navigation (2)
Date:      Wed, 30 Apr 2014 02:19:42 +0000 (UTC)
From:      Adrian Chadd <adrian@FreeBSD.org>
To:        src-committers@freebsd.org, svn-src-all@freebsd.org, svn-src-head@freebsd.org
Subject:   svn commit: r265115 - head/sys/dev/ath
Message-ID:  <201404300219.s3U2JgWn068449@svn.freebsd.org>

next in thread | raw e-mail | index | archive | help
Author: adrian
Date: Wed Apr 30 02:19:41 2014
New Revision: 265115
URL: http://svnweb.freebsd.org/changeset/base/265115

Log:
  Bring over some initial power save management support, reset path
  fixes and beacon programming / debugging into the ath(4) driver.
  
  The basic power save tracking:
  
  * Add some new code to track the current desired powersave state; and
  * Add some reference count tracking so we know when the NIC is awake; then
  * Add code in all the points where we're about to touch the hardware and
    push it to force-wake.
  
  Then, how things are moved into power save:
  
  * Only move into network-sleep during a RUN->SLEEP transition;
  * Force wake the hardware up everywhere that we're about to touch
    the hardware.
  
  The net80211 stack takes care of doing RUN<->SLEEP<->(other) state
  transitions so we don't have to do it in the driver.
  
  Next, when to wake things up:
  
  * In short - everywhere we touch the hardware.
  * The hardware will take care of staying awake if things are queued
    in the transmit queue(s); it'll then transit down to sleep if
    there's nothing left.  This way we don't have to track the
    software / hardware transmit queue(s) and keep the hardware
    awake for those.
  
  Then, some transmit path fixes that aren't related but useful:
  
  * Force EAPOL frames to go out at the lowest rate.  This improves
    reliability during the encryption handshake after 802.11
    negotiation.
  
  Next, some reset path fixes!
  
  * Fix the overlap between reset and transmit pause so we don't
    transmit frames during a reset.
  * Some noisy environments will end up taking a lot longer to reset
    than normal, so extend the reset period and drop the raise the
    reset interval to be more realistic and give the hardware some
    time to finish calibration.
  * Skip calibration during the reset path.  Tsk!
  
  Then, beacon fixes in station mode!
  
  * Add a _lot_ more debugging in the station beacon reset path.
    This is all quite fluid right now.
  * Modify the STA beacon programming code to try and take
    the TU gap between desired TSF and the target TU into
    account.  (Lifted from QCA.)
  
  Tested:
  
  * AR5210
  * AR5211
  * AR5212
  * AR5413
  * AR5416
  * AR9280
  * AR9285
  
  TODO:
  
  * More AP, IBSS, mesh, TDMA testing
  * Thorough AR9380 and later testing!
  * AR9160 and AR9287 testing
  
  Obtained from:	QCA

Modified:
  head/sys/dev/ath/if_ath.c
  head/sys/dev/ath/if_ath_beacon.c
  head/sys/dev/ath/if_ath_beacon.h
  head/sys/dev/ath/if_ath_debug.h
  head/sys/dev/ath/if_ath_keycache.c
  head/sys/dev/ath/if_ath_led.c
  head/sys/dev/ath/if_ath_misc.h
  head/sys/dev/ath/if_ath_rx.c
  head/sys/dev/ath/if_ath_rx_edma.c
  head/sys/dev/ath/if_ath_sysctl.c
  head/sys/dev/ath/if_ath_tx.c
  head/sys/dev/ath/if_athvar.h

Modified: head/sys/dev/ath/if_ath.c
==============================================================================
--- head/sys/dev/ath/if_ath.c	Wed Apr 30 02:08:27 2014	(r265114)
+++ head/sys/dev/ath/if_ath.c	Wed Apr 30 02:19:41 2014	(r265115)
@@ -279,6 +279,95 @@ ath_legacy_attach_comp_func(struct ath_s
 	}
 }
 
+/*
+ * Set the target power mode.
+ *
+ * If this is called during a point in time where
+ * the hardware is being programmed elsewhere, it will
+ * simply store it away and update it when all current
+ * uses of the hardware are completed.
+ */
+void
+_ath_power_setpower(struct ath_softc *sc, int power_state, const char *file, int line)
+{
+	ATH_LOCK_ASSERT(sc);
+
+	sc->sc_target_powerstate = power_state;
+
+	DPRINTF(sc, ATH_DEBUG_PWRSAVE, "%s: (%s:%d) state=%d, refcnt=%d\n",
+	    __func__,
+	    file,
+	    line,
+	    power_state,
+	    sc->sc_powersave_refcnt);
+
+	if (sc->sc_powersave_refcnt == 0 &&
+	    power_state != sc->sc_cur_powerstate) {
+		sc->sc_cur_powerstate = power_state;
+		ath_hal_setpower(sc->sc_ah, power_state);
+	}
+}
+
+/*
+ * Set the hardware power mode and take a reference.
+ *
+ * This doesn't update the target power mode in the driver;
+ * it just updates the hardware power state.
+ *
+ * XXX it should only ever force the hardware awake; it should
+ * never be called to set it asleep.
+ */
+void
+_ath_power_set_power_state(struct ath_softc *sc, int power_state, const char *file, int line)
+{
+	ATH_LOCK_ASSERT(sc);
+
+	DPRINTF(sc, ATH_DEBUG_PWRSAVE, "%s: (%s:%d) state=%d, refcnt=%d\n",
+	    __func__,
+	    file,
+	    line,
+	    power_state,
+	    sc->sc_powersave_refcnt);
+
+	sc->sc_powersave_refcnt++;
+
+	if (power_state != sc->sc_cur_powerstate) {
+		ath_hal_setpower(sc->sc_ah, power_state);
+		sc->sc_cur_powerstate = power_state;
+	}
+}
+
+/*
+ * Restore the power save mode to what it once was.
+ *
+ * This will decrement the reference counter and once it hits
+ * zero, it'll restore the powersave state.
+ */
+void
+_ath_power_restore_power_state(struct ath_softc *sc, const char *file, int line)
+{
+
+	ATH_LOCK_ASSERT(sc);
+
+	DPRINTF(sc, ATH_DEBUG_PWRSAVE, "%s: (%s:%d) refcnt=%d, target state=%d\n",
+	    __func__,
+	    file,
+	    line,
+	    sc->sc_powersave_refcnt,
+	    sc->sc_target_powerstate);
+
+	if (sc->sc_powersave_refcnt == 0)
+		device_printf(sc->sc_dev, "%s: refcnt=0?\n", __func__);
+	else
+		sc->sc_powersave_refcnt--;
+
+	if (sc->sc_powersave_refcnt == 0 &&
+	    sc->sc_target_powerstate != sc->sc_cur_powerstate) {
+		sc->sc_cur_powerstate = sc->sc_target_powerstate;
+		ath_hal_setpower(sc->sc_ah, sc->sc_target_powerstate);
+	}
+}
+
 #define	HAL_MODE_HT20 (HAL_MODE_11NG_HT20 | HAL_MODE_11NA_HT20)
 #define	HAL_MODE_HT40 \
 	(HAL_MODE_11NG_HT40PLUS | HAL_MODE_11NG_HT40MINUS | \
@@ -341,6 +430,10 @@ ath_attach(u_int16_t devid, struct ath_s
 		ath_xmit_setup_legacy(sc);
 	}
 
+	if (ath_hal_hasmybeacon(sc->sc_ah)) {
+		sc->sc_do_mybeacon = 1;
+	}
+
 	/*
 	 * Check if the MAC has multi-rate retry support.
 	 * We do this by trying to setup a fake extended
@@ -605,6 +698,8 @@ ath_attach(u_int16_t devid, struct ath_s
 #ifdef	ATH_ENABLE_DFS
 		| IEEE80211_C_DFS		/* Enable radar detection */
 #endif
+		| IEEE80211_C_PMGT		/* Station side power mgmt */
+		| IEEE80211_C_SWSLEEP
 		;
 	/*
 	 * Query the hal to figure out h/w crypto support.
@@ -994,6 +1089,14 @@ ath_attach(u_int16_t devid, struct ath_s
 	if (bootverbose)
 		ieee80211_announce(ic);
 	ath_announce(sc);
+
+	/*
+	 * Put it to sleep for now.
+	 */
+	ATH_LOCK(sc);
+	ath_power_setpower(sc, HAL_PM_FULL_SLEEP);
+	ATH_UNLOCK(sc);
+
 	return 0;
 bad2:
 	ath_tx_cleanup(sc);
@@ -1039,7 +1142,22 @@ ath_detach(struct ath_softc *sc)
 	 *   it last
 	 * Other than that, it's straightforward...
 	 */
+
+	/*
+	 * XXX Wake the hardware up first.  ath_stop() will still
+	 * wake it up first, but I'd rather do it here just to
+	 * ensure it's awake.
+	 */
+	ATH_LOCK(sc);
+	ath_power_set_power_state(sc, HAL_PM_AWAKE);
+	ath_power_setpower(sc, HAL_PM_AWAKE);
+	ATH_UNLOCK(sc);
+
+	/*
+	 * Stop things cleanly.
+	 */
 	ath_stop(ifp);
+
 	ieee80211_ifdetach(ifp->if_l2com);
 	taskqueue_free(sc->sc_tq);
 #ifdef ATH_TX99_DIAG
@@ -1402,6 +1520,10 @@ ath_vap_delete(struct ieee80211vap *vap)
 	struct ath_hal *ah = sc->sc_ah;
 	struct ath_vap *avp = ATH_VAP(vap);
 
+	ATH_LOCK(sc);
+	ath_power_set_power_state(sc, HAL_PM_AWAKE);
+	ATH_UNLOCK(sc);
+
 	DPRINTF(sc, ATH_DEBUG_RESET, "%s: called\n", __func__);
 	if (ifp->if_drv_flags & IFF_DRV_RUNNING) {
 		/*
@@ -1415,6 +1537,8 @@ ath_vap_delete(struct ieee80211vap *vap)
 		ath_stoprecv(sc, 1);		/* stop recv side */
 	}
 
+	/* .. leave the hardware awake for now. */
+
 	ieee80211_vap_detach(vap);
 
 	/*
@@ -1502,6 +1626,9 @@ ath_vap_delete(struct ieee80211vap *vap)
 		}
 		ath_hal_intrset(ah, sc->sc_imask);
 	}
+
+	/* Ok, let the hardware asleep. */
+	ath_power_restore_power_state(sc);
 	ATH_UNLOCK(sc);
 }
 
@@ -1547,8 +1674,12 @@ ath_reset_keycache(struct ath_softc *sc)
 	struct ath_hal *ah = sc->sc_ah;
 	int i;
 
+	ATH_LOCK(sc);
+	ath_power_set_power_state(sc, HAL_PM_AWAKE);
 	for (i = 0; i < sc->sc_keymax; i++)
 		ath_hal_keyreset(ah, i);
+	ath_power_restore_power_state(sc);
+	ATH_UNLOCK(sc);
 	ieee80211_crypto_reload_keys(ic);
 }
 
@@ -1600,6 +1731,13 @@ ath_resume(struct ath_softc *sc)
 	    sc->sc_curchan != NULL ? sc->sc_curchan : ic->ic_curchan);
 	ath_hal_setchainmasks(sc->sc_ah, sc->sc_cur_txchainmask,
 	    sc->sc_cur_rxchainmask);
+
+	/* Ensure we set the current power state to on */
+	ATH_LOCK(sc);
+	ath_power_set_power_state(sc, HAL_PM_AWAKE);
+	ath_power_setpower(sc, HAL_PM_AWAKE);
+	ATH_UNLOCK(sc);
+
 	ath_hal_reset(ah, sc->sc_opmode,
 	    sc->sc_curchan != NULL ? sc->sc_curchan : ic->ic_curchan,
 	    AH_FALSE, &status);
@@ -1632,6 +1770,10 @@ ath_resume(struct ath_softc *sc)
 	if (sc->sc_resume_up)
 		ieee80211_resume_all(ic);
 
+	ATH_LOCK(sc);
+	ath_power_restore_power_state(sc);
+	ATH_UNLOCK(sc);
+
 	/* XXX beacons ? */
 }
 
@@ -1689,6 +1831,10 @@ ath_intr(void *arg)
 		return;
 	}
 
+	ATH_LOCK(sc);
+	ath_power_set_power_state(sc, HAL_PM_AWAKE);
+	ATH_UNLOCK(sc);
+
 	if ((ifp->if_flags & IFF_UP) == 0 ||
 	    (ifp->if_drv_flags & IFF_DRV_RUNNING) == 0) {
 		HAL_INT status;
@@ -1698,6 +1844,10 @@ ath_intr(void *arg)
 		ath_hal_getisr(ah, &status);	/* clear ISR */
 		ath_hal_intrset(ah, 0);		/* disable further intr's */
 		ATH_PCU_UNLOCK(sc);
+
+		ATH_LOCK(sc);
+		ath_power_restore_power_state(sc);
+		ATH_UNLOCK(sc);
 		return;
 	}
 
@@ -1737,6 +1887,11 @@ ath_intr(void *arg)
 	/* Short-circuit un-handled interrupts */
 	if (status == 0x0) {
 		ATH_PCU_UNLOCK(sc);
+
+		ATH_LOCK(sc);
+		ath_power_restore_power_state(sc);
+		ATH_UNLOCK(sc);
+
 		return;
 	}
 
@@ -1903,10 +2058,18 @@ ath_intr(void *arg)
 			ATH_KTR(sc, ATH_KTR_ERROR, 0, "ath_intr: RXORN");
 			sc->sc_stats.ast_rxorn++;
 		}
+		if (status & HAL_INT_TSFOOR) {
+			device_printf(sc->sc_dev, "%s: TSFOOR\n", __func__);
+			sc->sc_syncbeacon = 1;
+		}
 	}
 	ATH_PCU_LOCK(sc);
 	sc->sc_intr_cnt--;
 	ATH_PCU_UNLOCK(sc);
+
+	ATH_LOCK(sc);
+	ath_power_restore_power_state(sc);
+	ATH_UNLOCK(sc);
 }
 
 static void
@@ -1937,6 +2100,8 @@ ath_fatal_proc(void *arg, int pending)
 static void
 ath_bmiss_vap(struct ieee80211vap *vap)
 {
+	struct ath_softc *sc = vap->iv_ic->ic_ifp->if_softc;
+
 	/*
 	 * Workaround phantom bmiss interrupts by sanity-checking
 	 * the time of our last rx'd frame.  If it is within the
@@ -1945,6 +2110,16 @@ ath_bmiss_vap(struct ieee80211vap *vap)
 	 * be dispatched up for processing.  Note this applies only
 	 * for h/w beacon miss events.
 	 */
+
+	/*
+	 * XXX TODO: Just read the TSF during the interrupt path;
+	 * that way we don't have to wake up again just to read it
+	 * again.
+	 */
+	ATH_LOCK(sc);
+	ath_power_set_power_state(sc, HAL_PM_AWAKE);
+	ATH_UNLOCK(sc);
+
 	if ((vap->iv_flags_ext & IEEE80211_FEXT_SWBMISS) == 0) {
 		struct ifnet *ifp = vap->iv_ic->ic_ifp;
 		struct ath_softc *sc = ifp->if_softc;
@@ -1962,12 +2137,32 @@ ath_bmiss_vap(struct ieee80211vap *vap)
 
 		if (tsf - lastrx <= bmisstimeout) {
 			sc->sc_stats.ast_bmiss_phantom++;
+
+			ATH_LOCK(sc);
+			ath_power_restore_power_state(sc);
+			ATH_UNLOCK(sc);
+
 			return;
 		}
 	}
+
+	/*
+	 * There's no need to keep the hardware awake during the call
+	 * to av_bmiss().
+	 */
+	ATH_LOCK(sc);
+	ath_power_restore_power_state(sc);
+	ATH_UNLOCK(sc);
+
+	/*
+	 * Attempt to force a beacon resync.
+	 */
+	sc->sc_syncbeacon = 1;
+
 	ATH_VAP(vap)->av_bmiss(vap);
 }
 
+/* XXX this needs a force wakeup! */
 int
 ath_hal_gethangstate(struct ath_hal *ah, uint32_t mask, uint32_t *hangs)
 {
@@ -1990,6 +2185,12 @@ ath_bmiss_proc(void *arg, int pending)
 
 	DPRINTF(sc, ATH_DEBUG_ANY, "%s: pending %u\n", __func__, pending);
 
+	ATH_LOCK(sc);
+	ath_power_set_power_state(sc, HAL_PM_AWAKE);
+	ATH_UNLOCK(sc);
+
+	ath_beacon_miss(sc);
+
 	/*
 	 * Do a reset upon any becaon miss event.
 	 *
@@ -2003,6 +2204,13 @@ ath_bmiss_proc(void *arg, int pending)
 		ath_reset(ifp, ATH_RESET_NOLOSS);
 		ieee80211_beacon_miss(ifp->if_l2com);
 	}
+
+	/* Force a beacon resync, in case they've drifted */
+	sc->sc_syncbeacon = 1;
+
+	ATH_LOCK(sc);
+	ath_power_restore_power_state(sc);
+	ATH_UNLOCK(sc);
 }
 
 /*
@@ -2042,6 +2250,12 @@ ath_init(void *arg)
 
 	ATH_LOCK(sc);
 	/*
+	 * Force the sleep state awake.
+	 */
+	ath_power_set_power_state(sc, HAL_PM_AWAKE);
+	ath_power_setpower(sc, HAL_PM_AWAKE);
+
+	/*
 	 * Stop anything previously setup.  This is safe
 	 * whether this is the first time through or not.
 	 */
@@ -2058,6 +2272,7 @@ ath_init(void *arg)
 	ath_update_chainmasks(sc, ic->ic_curchan);
 	ath_hal_setchainmasks(sc->sc_ah, sc->sc_cur_txchainmask,
 	    sc->sc_cur_rxchainmask);
+
 	if (!ath_hal_reset(ah, sc->sc_opmode, ic->ic_curchan, AH_FALSE, &status)) {
 		if_printf(ifp, "unable to reset hardware; hal status %u\n",
 			status);
@@ -2113,6 +2328,7 @@ ath_init(void *arg)
 	 */
 	if (ath_startrecv(sc) != 0) {
 		if_printf(ifp, "unable to start recv logic\n");
+		ath_power_restore_power_state(sc);
 		ATH_UNLOCK(sc);
 		return;
 	}
@@ -2139,6 +2355,15 @@ ath_init(void *arg)
 	if (sc->sc_needmib && ic->ic_opmode == IEEE80211_M_STA)
 		sc->sc_imask |= HAL_INT_MIB;
 
+	/*
+	 * XXX add capability for this.
+	 *
+	 * If we're in STA mode (and maybe IBSS?) then register for
+	 * TSFOOR interrupts.
+	 */
+	if (ic->ic_opmode == IEEE80211_M_STA)
+		sc->sc_imask |= HAL_INT_TSFOOR;
+
 	/* Enable global TX timeout and carrier sense timeout if available */
 	if (ath_hal_gtxto_supported(ah))
 		sc->sc_imask |= HAL_INT_GTT;
@@ -2150,6 +2375,7 @@ ath_init(void *arg)
 	callout_reset(&sc->sc_wd_ch, hz, ath_watchdog, sc);
 	ath_hal_intrset(ah, sc->sc_imask);
 
+	ath_power_restore_power_state(sc);
 	ATH_UNLOCK(sc);
 
 #ifdef ATH_TX99_DIAG
@@ -2170,6 +2396,12 @@ ath_stop_locked(struct ifnet *ifp)
 		__func__, sc->sc_invalid, ifp->if_flags);
 
 	ATH_LOCK_ASSERT(sc);
+
+	/*
+	 * Wake the hardware up before fiddling with it.
+	 */
+	ath_power_set_power_state(sc, HAL_PM_AWAKE);
+
 	if (ifp->if_drv_flags & IFF_DRV_RUNNING) {
 		/*
 		 * Shutdown the hardware and driver:
@@ -2210,9 +2442,20 @@ ath_stop_locked(struct ifnet *ifp)
 			sc->sc_rxlink = NULL;
 		ath_beacon_free(sc);	/* XXX not needed */
 	}
+
+	/* And now, restore the current power state */
+	ath_power_restore_power_state(sc);
 }
 
-#define	MAX_TXRX_ITERATIONS	1000
+/*
+ * Wait until all pending TX/RX has completed.
+ *
+ * This waits until all existing transmit, receive and interrupts
+ * have completed.  It's assumed that the caller has first
+ * grabbed the reset lock so it doesn't try to do overlapping
+ * chip resets.
+ */
+#define	MAX_TXRX_ITERATIONS	100
 static void
 ath_txrx_stop_locked(struct ath_softc *sc)
 {
@@ -2231,7 +2474,8 @@ ath_txrx_stop_locked(struct ath_softc *s
 	    sc->sc_txstart_cnt || sc->sc_intr_cnt) {
 		if (i <= 0)
 			break;
-		msleep(sc, &sc->sc_pcu_mtx, 0, "ath_txrx_stop", 1);
+		msleep(sc, &sc->sc_pcu_mtx, 0, "ath_txrx_stop",
+		    msecs_to_ticks(10));
 		i--;
 	}
 
@@ -2278,7 +2522,7 @@ ath_txrx_start(struct ath_softc *sc)
  * Another, cleaner way should be found to serialise all of
  * these operations.
  */
-#define	MAX_RESET_ITERATIONS	10
+#define	MAX_RESET_ITERATIONS	25
 static int
 ath_reset_grablock(struct ath_softc *sc, int dowait)
 {
@@ -2296,7 +2540,11 @@ ath_reset_grablock(struct ath_softc *sc,
 			break;
 		}
 		ATH_PCU_UNLOCK(sc);
-		pause("ath_reset_grablock", 1);
+		/*
+		 * 1 tick is likely not enough time for long calibrations
+		 * to complete.  So we should wait quite a while.
+		 */
+		pause("ath_reset_grablock", msecs_to_ticks(100));
 		i--;
 		ATH_PCU_LOCK(sc);
 	} while (i > 0);
@@ -2361,6 +2609,13 @@ ath_reset(struct ifnet *ifp, ATH_RESET_T
 	/* Try to (stop any further TX/RX from occuring */
 	taskqueue_block(sc->sc_tq);
 
+	/*
+	 * Wake the hardware up.
+	 */
+	ATH_LOCK(sc);
+	ath_power_set_power_state(sc, HAL_PM_AWAKE);
+	ATH_UNLOCK(sc);
+
 	ATH_PCU_LOCK(sc);
 
 	/*
@@ -2455,9 +2710,13 @@ ath_reset(struct ifnet *ifp, ATH_RESET_T
 	 * reset counter - this way ath_intr() doesn't end up
 	 * disabling interrupts without a corresponding enable
 	 * in the rest or channel change path.
+	 *
+	 * Grab the TX reference in case we need to transmit.
+	 * That way a parallel transmit doesn't.
 	 */
 	ATH_PCU_LOCK(sc);
 	sc->sc_inreset_cnt--;
+	sc->sc_txstart_cnt++;
 	/* XXX only do this if sc_inreset_cnt == 0? */
 	ath_hal_intrset(ah, sc->sc_imask);
 	ATH_PCU_UNLOCK(sc);
@@ -2474,6 +2733,8 @@ ath_reset(struct ifnet *ifp, ATH_RESET_T
 	/* Restart TX/RX as needed */
 	ath_txrx_start(sc);
 
+	/* XXX TODO: we need to hold the tx refcount here! */
+
 	/* Restart TX completion and pending TX */
 	if (reset_type == ATH_RESET_NOLOSS) {
 		for (i = 0; i < HAL_NUM_TX_QUEUES; i++) {
@@ -2498,6 +2759,14 @@ ath_reset(struct ifnet *ifp, ATH_RESET_T
 	ifp->if_drv_flags &= ~IFF_DRV_OACTIVE;
 	IF_UNLOCK(&ifp->if_snd);
 
+	ATH_LOCK(sc);
+	ath_power_restore_power_state(sc);
+	ATH_UNLOCK(sc);
+
+	ATH_PCU_LOCK(sc);
+	sc->sc_txstart_cnt--;
+	ATH_PCU_UNLOCK(sc);
+
 	/* Handle any frames in the TX queue */
 	/*
 	 * XXX should this be done by the caller, rather than
@@ -2638,6 +2907,7 @@ ath_buf_clone(struct ath_softc *sc, stru
 	tbf->bf_status = bf->bf_status;
 	tbf->bf_m = bf->bf_m;
 	tbf->bf_node = bf->bf_node;
+	KASSERT((bf->bf_node != NULL), ("%s: bf_node=NULL!", __func__));
 	/* will be setup by the chain/setup function */
 	tbf->bf_lastds = NULL;
 	/* for now, last == self */
@@ -2739,6 +3009,11 @@ ath_transmit(struct ifnet *ifp, struct m
 	sc->sc_txstart_cnt++;
 	ATH_PCU_UNLOCK(sc);
 
+	/* Wake the hardware up already */
+	ATH_LOCK(sc);
+	ath_power_set_power_state(sc, HAL_PM_AWAKE);
+	ATH_UNLOCK(sc);
+
 	ATH_KTR(sc, ATH_KTR_TX, 0, "ath_transmit: start");
 	/*
 	 * Grab the TX lock - it's ok to do this here; we haven't
@@ -2972,6 +3247,11 @@ finish:
 	sc->sc_txstart_cnt--;
 	ATH_PCU_UNLOCK(sc);
 
+	/* Sleep the hardware if required */
+	ATH_LOCK(sc);
+	ath_power_restore_power_state(sc);
+	ATH_UNLOCK(sc);
+
 	ATH_KTR(sc, ATH_KTR_TX, 0, "ath_transmit: finished");
 	
 	return (retval);
@@ -2999,7 +3279,6 @@ ath_key_update_begin(struct ieee80211vap
 
 	DPRINTF(sc, ATH_DEBUG_KEYCACHE, "%s:\n", __func__);
 	taskqueue_block(sc->sc_tq);
-	IF_LOCK(&ifp->if_snd);		/* NB: doesn't block mgmt frames */
 }
 
 static void
@@ -3009,7 +3288,6 @@ ath_key_update_end(struct ieee80211vap *
 	struct ath_softc *sc = ifp->if_softc;
 
 	DPRINTF(sc, ATH_DEBUG_KEYCACHE, "%s:\n", __func__);
-	IF_UNLOCK(&ifp->if_snd);
 	taskqueue_unblock(sc->sc_tq);
 }
 
@@ -3020,8 +3298,12 @@ ath_update_promisc(struct ifnet *ifp)
 	u_int32_t rfilt;
 
 	/* configure rx filter */
+	ATH_LOCK(sc);
+	ath_power_set_power_state(sc, HAL_PM_AWAKE);
 	rfilt = ath_calcrxfilter(sc);
 	ath_hal_setrxfilter(sc->sc_ah, rfilt);
+	ath_power_restore_power_state(sc);
+	ATH_UNLOCK(sc);
 
 	DPRINTF(sc, ATH_DEBUG_MODE, "%s: RX filter 0x%x\n", __func__, rfilt);
 }
@@ -3057,7 +3339,11 @@ ath_update_mcast(struct ifnet *ifp)
 		if_maddr_runlock(ifp);
 	} else
 		mfilt[0] = mfilt[1] = ~0;
+	ATH_LOCK(sc);
+	ath_power_set_power_state(sc, HAL_PM_AWAKE);
 	ath_hal_setmcastfilter(sc->sc_ah, mfilt[0], mfilt[1]);
+	ath_power_restore_power_state(sc);
+	ATH_UNLOCK(sc);
 	DPRINTF(sc, ATH_DEBUG_MODE, "%s: MC filter %08x:%08x\n",
 		__func__, mfilt[0], mfilt[1]);
 }
@@ -3119,8 +3405,13 @@ ath_setslottime(struct ath_softc *sc)
 	    __func__, ic->ic_curchan->ic_freq, ic->ic_curchan->ic_flags,
 	    ic->ic_flags & IEEE80211_F_SHSLOT ? "short" : "long", usec);
 
+	/* Wake up the hardware first before updating the slot time */
+	ATH_LOCK(sc);
+	ath_power_set_power_state(sc, HAL_PM_AWAKE);
 	ath_hal_setslottime(ah, usec);
+	ath_power_restore_power_state(sc);
 	sc->sc_updateslot = OK;
+	ATH_UNLOCK(sc);
 }
 
 /*
@@ -3137,6 +3428,8 @@ ath_updateslot(struct ifnet *ifp)
 	 * When not coordinating the BSS, change the hardware
 	 * immediately.  For other operation we defer the change
 	 * until beacon updates have propagated to the stations.
+	 *
+	 * XXX sc_updateslot isn't changed behind a lock?
 	 */
 	if (ic->ic_opmode == IEEE80211_M_HOSTAP ||
 	    ic->ic_opmode == IEEE80211_M_MBSS)
@@ -4258,6 +4551,10 @@ ath_tx_proc_q0(void *arg, int npending)
 	sc->sc_txq_active &= ~txqs;
 	ATH_PCU_UNLOCK(sc);
 
+	ATH_LOCK(sc);
+	ath_power_set_power_state(sc, HAL_PM_AWAKE);
+	ATH_UNLOCK(sc);
+
 	ATH_KTR(sc, ATH_KTR_TXCOMP, 1,
 	    "ath_tx_proc_q0: txqs=0x%08x", txqs);
 
@@ -4278,6 +4575,10 @@ ath_tx_proc_q0(void *arg, int npending)
 	sc->sc_txproc_cnt--;
 	ATH_PCU_UNLOCK(sc);
 
+	ATH_LOCK(sc);
+	ath_power_restore_power_state(sc);
+	ATH_UNLOCK(sc);
+
 	ath_tx_kick(sc);
 }
 
@@ -4299,6 +4600,10 @@ ath_tx_proc_q0123(void *arg, int npendin
 	sc->sc_txq_active &= ~txqs;
 	ATH_PCU_UNLOCK(sc);
 
+	ATH_LOCK(sc);
+	ath_power_set_power_state(sc, HAL_PM_AWAKE);
+	ATH_UNLOCK(sc);
+
 	ATH_KTR(sc, ATH_KTR_TXCOMP, 1,
 	    "ath_tx_proc_q0123: txqs=0x%08x", txqs);
 
@@ -4331,6 +4636,10 @@ ath_tx_proc_q0123(void *arg, int npendin
 	sc->sc_txproc_cnt--;
 	ATH_PCU_UNLOCK(sc);
 
+	ATH_LOCK(sc);
+	ath_power_restore_power_state(sc);
+	ATH_UNLOCK(sc);
+
 	ath_tx_kick(sc);
 }
 
@@ -4351,6 +4660,10 @@ ath_tx_proc(void *arg, int npending)
 	sc->sc_txq_active &= ~txqs;
 	ATH_PCU_UNLOCK(sc);
 
+	ATH_LOCK(sc);
+	ath_power_set_power_state(sc, HAL_PM_AWAKE);
+	ATH_UNLOCK(sc);
+
 	ATH_KTR(sc, ATH_KTR_TXCOMP, 1, "ath_tx_proc: txqs=0x%08x", txqs);
 
 	/*
@@ -4376,6 +4689,10 @@ ath_tx_proc(void *arg, int npending)
 	sc->sc_txproc_cnt--;
 	ATH_PCU_UNLOCK(sc);
 
+	ATH_LOCK(sc);
+	ath_power_restore_power_state(sc);
+	ATH_UNLOCK(sc);
+
 	ath_tx_kick(sc);
 }
 #undef	TXQACTIVE
@@ -4402,6 +4719,10 @@ ath_txq_sched_tasklet(void *arg, int npe
 	sc->sc_txproc_cnt++;
 	ATH_PCU_UNLOCK(sc);
 
+	ATH_LOCK(sc);
+	ath_power_set_power_state(sc, HAL_PM_AWAKE);
+	ATH_UNLOCK(sc);
+
 	ATH_TX_LOCK(sc);
 	for (i = 0; i < HAL_NUM_TX_QUEUES; i++) {
 		if (ATH_TXQ_SETUP(sc, i)) {
@@ -4410,6 +4731,10 @@ ath_txq_sched_tasklet(void *arg, int npe
 	}
 	ATH_TX_UNLOCK(sc);
 
+	ATH_LOCK(sc);
+	ath_power_restore_power_state(sc);
+	ATH_UNLOCK(sc);
+
 	ATH_PCU_LOCK(sc);
 	sc->sc_txproc_cnt--;
 	ATH_PCU_UNLOCK(sc);
@@ -5057,6 +5382,15 @@ ath_calibrate(void *arg)
 	HAL_BOOL aniCal, shortCal = AH_FALSE;
 	int nextcal;
 
+	/*
+	 * Force the hardware awake for ANI work.
+	 */
+	ath_power_set_power_state(sc, HAL_PM_AWAKE);
+
+	/* Skip trying to do this if we're in reset */
+	if (sc->sc_inreset_cnt)
+		goto restart;
+
 	if (ic->ic_flags & IEEE80211_F_SCAN)	/* defer, off channel */
 		goto restart;
 	longCal = (ticks - sc->sc_lastlongcal >= ath_longcalinterval*hz);
@@ -5086,6 +5420,7 @@ ath_calibrate(void *arg)
 			sc->sc_doresetcal = AH_TRUE;
 			taskqueue_enqueue(sc->sc_tq, &sc->sc_resettask);
 			callout_reset(&sc->sc_cal_ch, 1, ath_calibrate, sc);
+			ath_power_restore_power_state(sc);
 			return;
 		}
 		/*
@@ -5157,6 +5492,10 @@ restart:
 		    __func__);
 		/* NB: don't rearm timer */
 	}
+	/*
+	 * Restore power state now that we're done.
+	 */
+	ath_power_restore_power_state(sc);
 }
 
 static void
@@ -5242,6 +5581,10 @@ ath_set_channel(struct ieee80211com *ic)
 	struct ifnet *ifp = ic->ic_ifp;
 	struct ath_softc *sc = ifp->if_softc;
 
+	ATH_LOCK(sc);
+	ath_power_set_power_state(sc, HAL_PM_AWAKE);
+	ATH_UNLOCK(sc);
+
 	(void) ath_chan_set(sc, ic->ic_curchan);
 	/*
 	 * If we are returning to our bss channel then mark state
@@ -5252,6 +5595,7 @@ ath_set_channel(struct ieee80211com *ic)
 	ATH_LOCK(sc);
 	if (!sc->sc_scanning && ic->ic_curchan == ic->ic_bsschan)
 		sc->sc_syncbeacon = 1;
+	ath_power_restore_power_state(sc);
 	ATH_UNLOCK(sc);
 }
 
@@ -5284,6 +5628,7 @@ ath_newstate(struct ieee80211vap *vap, e
 	int i, error, stamode;
 	u_int32_t rfilt;
 	int csa_run_transition = 0;
+	enum ieee80211_state ostate = vap->iv_state;
 
 	static const HAL_LED_STATE leds[] = {
 	    HAL_LED_INIT,	/* IEEE80211_S_INIT */
@@ -5297,7 +5642,7 @@ ath_newstate(struct ieee80211vap *vap, e
 	};
 
 	DPRINTF(sc, ATH_DEBUG_STATE, "%s: %s -> %s\n", __func__,
-		ieee80211_state_name[vap->iv_state],
+		ieee80211_state_name[ostate],
 		ieee80211_state_name[nstate]);
 
 	/*
@@ -5309,7 +5654,12 @@ ath_newstate(struct ieee80211vap *vap, e
 	 */
 	IEEE80211_LOCK_ASSERT(ic);
 
-	if (vap->iv_state == IEEE80211_S_CSA && nstate == IEEE80211_S_RUN)
+	/* Before we touch the hardware - wake it up */
+	ATH_LOCK(sc);
+	ath_power_set_power_state(sc, HAL_PM_AWAKE);
+	ATH_UNLOCK(sc);
+
+	if (ostate == IEEE80211_S_CSA && nstate == IEEE80211_S_RUN)
 		csa_run_transition = 1;
 
 	callout_drain(&sc->sc_cal_ch);
@@ -5322,6 +5672,12 @@ ath_newstate(struct ieee80211vap *vap, e
 		 * [re]setup beacons.  Unblock the task q thread so
 		 * deferred interrupt processing is done.
 		 */
+
+		/* Ensure we stay awake during scan */
+		ATH_LOCK(sc);
+		ath_power_setpower(sc, HAL_PM_AWAKE);
+		ATH_UNLOCK(sc);
+
 		ath_hal_intrset(ah,
 		    sc->sc_imask &~ (HAL_INT_SWBA | HAL_INT_BMISS));
 		sc->sc_imask &= ~(HAL_INT_SWBA | HAL_INT_BMISS);
@@ -5334,6 +5690,11 @@ ath_newstate(struct ieee80211vap *vap, e
 	stamode = (vap->iv_opmode == IEEE80211_M_STA ||
 		   vap->iv_opmode == IEEE80211_M_AHDEMO ||
 		   vap->iv_opmode == IEEE80211_M_IBSS);
+
+	/*
+	 * XXX Dont need to do this (and others) if we've transitioned
+	 * from SLEEP->RUN.
+	 */
 	if (stamode && nstate == IEEE80211_S_RUN) {
 		sc->sc_curaid = ni->ni_associd;
 		IEEE80211_ADDR_COPY(sc->sc_curbssid, ni->ni_bssid);
@@ -5436,11 +5797,14 @@ ath_newstate(struct ieee80211vap *vap, e
 			 * beacon to update the beacon timer and thus we
 			 * won't get notified of the missing beacons.
 			 */
-			sc->sc_syncbeacon = 1;
-#if 0
-			if (csa_run_transition)
-#endif
-				ath_beacon_config(sc, vap);
+			if (ostate != IEEE80211_S_RUN &&
+			    ostate != IEEE80211_S_SLEEP) {
+				DPRINTF(sc, ATH_DEBUG_BEACON,
+				    "%s: STA; syncbeacon=1\n", __func__);
+				sc->sc_syncbeacon = 1;
+
+				if (csa_run_transition)
+					ath_beacon_config(sc, vap);
 
 			/*
 			 * PR: kern/175227
@@ -5454,7 +5818,8 @@ ath_newstate(struct ieee80211vap *vap, e
 			 * timer fires (too often), leading to a STA
 			 * disassociation.
 			 */
-			sc->sc_beacons = 1;
+				sc->sc_beacons = 1;
+			}
 			break;
 		case IEEE80211_M_MONITOR:
 			/*
@@ -5480,6 +5845,14 @@ ath_newstate(struct ieee80211vap *vap, e
 		sc->sc_halstats.ns_avgbrssi = ATH_RSSI_DUMMY_MARKER;
 		sc->sc_halstats.ns_avgrssi = ATH_RSSI_DUMMY_MARKER;
 		sc->sc_halstats.ns_avgtxrssi = ATH_RSSI_DUMMY_MARKER;
+
+		/*
+		 * Force awake for RUN mode.
+		 */
+		ATH_LOCK(sc);
+		ath_power_setpower(sc, HAL_PM_AWAKE);
+		ATH_UNLOCK(sc);
+
 		/*
 		 * Finally, start any timers and the task q thread
 		 * (in case we didn't go through SCAN state).
@@ -5491,6 +5864,7 @@ ath_newstate(struct ieee80211vap *vap, e
 			DPRINTF(sc, ATH_DEBUG_CALIBRATE,
 			    "%s: calibration disabled\n", __func__);
 		}
+
 		taskqueue_unblock(sc->sc_tq);
 	} else if (nstate == IEEE80211_S_INIT) {
 		/*
@@ -5510,9 +5884,40 @@ ath_newstate(struct ieee80211vap *vap, e
 #ifdef IEEE80211_SUPPORT_TDMA
 		ath_hal_setcca(ah, AH_TRUE);
 #endif
+	} else if (nstate == IEEE80211_S_SLEEP) {
+		/* We're going to sleep, so transition appropriately */
+		/* For now, only do this if we're a single STA vap */
+		if (sc->sc_nvaps == 1 &&
+		    vap->iv_opmode == IEEE80211_M_STA) {
+			DPRINTF(sc, ATH_DEBUG_BEACON, "%s: syncbeacon=%d\n", __func__, sc->sc_syncbeacon);
+			ATH_LOCK(sc);
+			if (sc->sc_syncbeacon == 0) {
+				ath_power_setpower(sc, HAL_PM_NETWORK_SLEEP);
+			}
+			/*
+			 * Always at least set the self-generated
+			 * power bits appropriately.
+			 *
+			 * XXX TODO: this should be an ath_power_*() call
+			 * which also tracks whether we're doing self-gen
+			 * frames or not, and allows the hardware to be
+			 * awake _but_ self-gen frames to have PWRMGT=1.
+			 */
+			ath_hal_setselfgenpower(sc->sc_ah,
+			    HAL_PM_NETWORK_SLEEP);
+			ATH_UNLOCK(sc);
+		}
 	}
 bad:
 	ieee80211_free_node(ni);
+
+	/*
+	 * Restore the power state - either to what it was, or
+	 * to network_sleep if it's alright.
+	 */
+	ATH_LOCK(sc);
+	ath_power_restore_power_state(sc);
+	ATH_UNLOCK(sc);
 	return error;
 }
 
@@ -5567,6 +5972,13 @@ ath_newassoc(struct ieee80211_node *ni, 
 	an->an_mcastrix = ath_tx_findrix(sc, tp->mcastrate);
 	an->an_mgmtrix = ath_tx_findrix(sc, tp->mgmtrate);
 
+	DPRINTF(sc, ATH_DEBUG_NODE, "%s: %6D: reassoc; isnew=%d, is_powersave=%d\n",
+	    __func__,
+	    ni->ni_macaddr,
+	    ":",
+	    isnew,
+	    an->an_is_powersave);
+
 	ATH_NODE_LOCK(an);
 	ath_rate_newassoc(sc, an, isnew);
 	ATH_NODE_UNLOCK(an);
@@ -5813,6 +6225,10 @@ ath_watchdog(void *arg)
 		struct ifnet *ifp = sc->sc_ifp;
 		uint32_t hangs;
 
+		ATH_LOCK(sc);
+		ath_power_set_power_state(sc, HAL_PM_AWAKE);
+		ATH_UNLOCK(sc);
+
 		if (ath_hal_gethangstate(sc->sc_ah, 0xffff, &hangs) &&
 		    hangs != 0) {
 			if_printf(ifp, "%s hang detected (0x%x)\n",
@@ -5822,6 +6238,10 @@ ath_watchdog(void *arg)
 		do_reset = 1;
 		ifp->if_oerrors++;
 		sc->sc_stats.ast_watchdog++;
+
+		ATH_LOCK(sc);
+		ath_power_restore_power_state(sc);
+		ATH_UNLOCK(sc);
 	}
 
 	/*
@@ -5919,6 +6339,13 @@ ath_ioctl_diag(struct ath_softc *sc, str
 			goto bad;
 		}
 	}
+
+
+	ATH_LOCK(sc);
+	if (id != HAL_DIAG_REGS)
+		ath_power_set_power_state(sc, HAL_PM_AWAKE);
+	ATH_UNLOCK(sc);
+
 	if (ath_hal_getdiagstate(ah, id, indata, insize, &outdata, &outsize)) {
 		if (outsize < ad->ad_out_size)
 			ad->ad_out_size = outsize;
@@ -5928,6 +6355,12 @@ ath_ioctl_diag(struct ath_softc *sc, str
 	} else {
 		error = EINVAL;
 	}
+
+	ATH_LOCK(sc);
+	if (id != HAL_DIAG_REGS)
+		ath_power_restore_power_state(sc);
+	ATH_UNLOCK(sc);
+
 bad:
 	if ((ad->ad_id & ATH_DIAG_IN) && indata != NULL)
 		free(indata, M_TEMP);
@@ -5974,11 +6407,8 @@ ath_ioctl(struct ifnet *ifp, u_long cmd,
 		} else {
 			ATH_LOCK(sc);
 			ath_stop_locked(ifp);
-#ifdef notyet
-			/* XXX must wakeup in places like ath_vap_delete */
 			if (!sc->sc_invalid)
-				ath_hal_setpower(sc->sc_ah, HAL_PM_FULL_SLEEP);
-#endif
+				ath_power_setpower(sc, HAL_PM_FULL_SLEEP);
 			ATH_UNLOCK(sc);
 		}
 		break;

Modified: head/sys/dev/ath/if_ath_beacon.c
==============================================================================
--- head/sys/dev/ath/if_ath_beacon.c	Wed Apr 30 02:08:27 2014	(r265114)

*** DIFF OUTPUT TRUNCATED AT 1000 LINES ***



Want to link to this message? Use this URL: <https://mail-archive.FreeBSD.org/cgi/mid.cgi?201404300219.s3U2JgWn068449>