summaryrefslogtreecommitdiffstats
path: root/sys/dev/ath/if_ath.c
diff options
context:
space:
mode:
Diffstat (limited to 'sys/dev/ath/if_ath.c')
-rw-r--r--sys/dev/ath/if_ath.c588
1 files changed, 567 insertions, 21 deletions
diff --git a/sys/dev/ath/if_ath.c b/sys/dev/ath/if_ath.c
index 6c98e60..a3ab2f6 100644
--- a/sys/dev/ath/if_ath.c
+++ b/sys/dev/ath/if_ath.c
@@ -165,6 +165,7 @@ static void ath_bmiss_vap(struct ieee80211vap *);
static void ath_bmiss_proc(void *, int);
static void ath_key_update_begin(struct ieee80211vap *);
static void ath_key_update_end(struct ieee80211vap *);
+static void ath_update_mcast_hw(struct ath_softc *);
static void ath_update_mcast(struct ifnet *);
static void ath_update_promisc(struct ifnet *);
static void ath_updateslot(struct ifnet *);
@@ -279,6 +280,164 @@ ath_legacy_attach_comp_func(struct ath_softc *sc)
}
}
+/*
+ * 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);
+
+ /*
+ * If the NIC is force-awake, then set the
+ * self-gen frame state appropriately.
+ *
+ * If the nic is in network sleep or full-sleep,
+ * we let the above call leave the self-gen
+ * state as "sleep".
+ */
+ if (sc->sc_cur_powerstate == HAL_PM_AWAKE &&
+ sc->sc_target_selfgen_state != HAL_PM_AWAKE) {
+ ath_hal_setselfgenpower(sc->sc_ah,
+ sc->sc_target_selfgen_state);
+ }
+ }
+}
+
+/*
+ * Set the current self-generated frames state.
+ *
+ * This is separate from the target power mode. The chip may be
+ * awake but the desired state is "sleep", so frames sent to the
+ * destination has PWRMGT=1 in the 802.11 header. The NIC also
+ * needs to know to set PWRMGT=1 in self-generated frames.
+ */
+void
+_ath_power_set_selfgen(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_target_selfgen_state);
+
+ sc->sc_target_selfgen_state = power_state;
+
+ /*
+ * If the NIC is force-awake, then set the power state.
+ * Network-state and full-sleep will already transition it to
+ * mark self-gen frames as sleeping - and we can't
+ * guarantee the NIC is awake to program the self-gen frame
+ * setting anyway.
+ */
+ if (sc->sc_cur_powerstate == HAL_PM_AWAKE) {
+ ath_hal_setselfgenpower(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;
+
+ /*
+ * Adjust the self-gen powerstate if appropriate.
+ */
+ if (sc->sc_cur_powerstate == HAL_PM_AWAKE &&
+ sc->sc_target_selfgen_state != HAL_PM_AWAKE) {
+ ath_hal_setselfgenpower(sc->sc_ah,
+ sc->sc_target_selfgen_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);
+ }
+
+ /*
+ * Adjust the self-gen powerstate if appropriate.
+ */
+ if (sc->sc_cur_powerstate == HAL_PM_AWAKE &&
+ sc->sc_target_selfgen_state != HAL_PM_AWAKE) {
+ ath_hal_setselfgenpower(sc->sc_ah,
+ sc->sc_target_selfgen_state);
+ }
+
+}
+
#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 +500,10 @@ ath_attach(u_int16_t devid, struct ath_softc *sc)
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 +768,8 @@ ath_attach(u_int16_t devid, struct ath_softc *sc)
#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 +1159,14 @@ ath_attach(u_int16_t devid, struct ath_softc *sc)
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 +1212,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 +1590,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 +1607,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 +1696,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 +1744,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 +1801,14 @@ 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_setselfgen(sc, HAL_PM_AWAKE);
+ 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 +1841,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 +1902,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 +1915,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 +1958,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 +2129,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 +2171,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 +2181,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 +2208,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 +2256,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 +2275,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 +2321,13 @@ ath_init(void *arg)
ATH_LOCK(sc);
/*
+ * Force the sleep state awake.
+ */
+ ath_power_setselfgen(sc, HAL_PM_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 +2344,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 +2400,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 +2427,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 +2447,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 +2468,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 +2514,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 +2546,8 @@ ath_txrx_stop_locked(struct ath_softc *sc)
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 +2594,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 +2612,11 @@ ath_reset_grablock(struct ath_softc *sc, int dowait)
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 +2681,13 @@ ath_reset(struct ifnet *ifp, ATH_RESET_TYPE reset_type)
/* 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 +2782,13 @@ ath_reset(struct ifnet *ifp, ATH_RESET_TYPE reset_type)
* 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 +2805,8 @@ ath_reset(struct ifnet *ifp, ATH_RESET_TYPE reset_type)
/* 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 +2831,14 @@ ath_reset(struct ifnet *ifp, ATH_RESET_TYPE reset_type)
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 +2979,7 @@ ath_buf_clone(struct ath_softc *sc, struct ath_buf *bf)
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 +3081,11 @@ ath_transmit(struct ifnet *ifp, struct mbuf *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 +3319,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 +3351,6 @@ ath_key_update_begin(struct ieee80211vap *vap)
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 +3360,6 @@ ath_key_update_end(struct ieee80211vap *vap)
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,16 +3370,25 @@ 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);
}
+/*
+ * Driver-internal mcast update call.
+ *
+ * Assumes the hardware is already awake.
+ */
static void
-ath_update_mcast(struct ifnet *ifp)
+ath_update_mcast_hw(struct ath_softc *sc)
{
- struct ath_softc *sc = ifp->if_softc;
+ struct ifnet *ifp = sc->sc_ifp;
u_int32_t mfilt[2];
/* calculate and install multicast filter */
@@ -3057,11 +3416,33 @@ ath_update_mcast(struct ifnet *ifp)
if_maddr_runlock(ifp);
} else
mfilt[0] = mfilt[1] = ~0;
+
ath_hal_setmcastfilter(sc->sc_ah, mfilt[0], mfilt[1]);
+
DPRINTF(sc, ATH_DEBUG_MODE, "%s: MC filter %08x:%08x\n",
__func__, mfilt[0], mfilt[1]);
}
+/*
+ * Called from the net80211 layer - force the hardware
+ * awake before operating.
+ */
+static void
+ath_update_mcast(struct ifnet *ifp)
+{
+ struct ath_softc *sc = ifp->if_softc;
+
+ ATH_LOCK(sc);
+ ath_power_set_power_state(sc, HAL_PM_AWAKE);
+ ATH_UNLOCK(sc);
+
+ ath_update_mcast_hw(sc);
+
+ ATH_LOCK(sc);
+ ath_power_restore_power_state(sc);
+ ATH_UNLOCK(sc);
+}
+
void
ath_mode_init(struct ath_softc *sc)
{
@@ -3087,7 +3468,7 @@ ath_mode_init(struct ath_softc *sc)
ath_hal_setmac(ah, IF_LLADDR(ifp));
/* calculate and install multicast filter */
- ath_update_mcast(ifp);
+ ath_update_mcast_hw(sc);
}
/*
@@ -3119,8 +3500,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 +3523,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 +4646,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 +4670,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 +4695,10 @@ ath_tx_proc_q0123(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_q0123: txqs=0x%08x", txqs);
@@ -4331,6 +4731,10 @@ ath_tx_proc_q0123(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);
}
@@ -4351,6 +4755,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 +4784,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 +4814,10 @@ ath_txq_sched_tasklet(void *arg, int npending)
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 +4826,10 @@ ath_txq_sched_tasklet(void *arg, int npending)
}
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 +5477,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 +5515,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 +5587,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 +5676,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 +5690,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 +5723,7 @@ ath_newstate(struct ieee80211vap *vap, enum ieee80211_state nstate, int arg)
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 +5737,7 @@ ath_newstate(struct ieee80211vap *vap, enum ieee80211_state nstate, int arg)
};
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 +5749,26 @@ ath_newstate(struct ieee80211vap *vap, enum ieee80211_state nstate, int arg)
*/
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);
+ /*
+ * If the NIC is in anything other than SLEEP state,
+ * we need to ensure that self-generated frames are
+ * set for PWRMGT=0. Otherwise we may end up with
+ * strange situations.
+ *
+ * XXX TODO: is this actually the case? :-)
+ */
+ if (nstate != IEEE80211_S_SLEEP)
+ ath_power_setselfgen(sc, HAL_PM_AWAKE);
+
+ /*
+ * Now, wake the thing up.
+ */
+ 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 +5781,13 @@ ath_newstate(struct ieee80211vap *vap, enum ieee80211_state nstate, int arg)
* [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_setselfgen(sc, HAL_PM_AWAKE);
+ 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 +5800,11 @@ ath_newstate(struct ieee80211vap *vap, enum ieee80211_state nstate, int arg)
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 +5907,14 @@ ath_newstate(struct ieee80211vap *vap, enum ieee80211_state nstate, int arg)
* 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 +5928,8 @@ ath_newstate(struct ieee80211vap *vap, enum ieee80211_state nstate, int arg)
* timer fires (too often), leading to a STA
* disassociation.
*/
- sc->sc_beacons = 1;
+ sc->sc_beacons = 1;
+ }
break;
case IEEE80211_M_MONITOR:
/*
@@ -5480,6 +5955,15 @@ ath_newstate(struct ieee80211vap *vap, enum ieee80211_state nstate, int arg)
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_setselfgen(sc, HAL_PM_AWAKE);
+ 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 +5975,7 @@ ath_newstate(struct ieee80211vap *vap, enum ieee80211_state nstate, int arg)
DPRINTF(sc, ATH_DEBUG_CALIBRATE,
"%s: calibration disabled\n", __func__);
}
+
taskqueue_unblock(sc->sc_tq);
} else if (nstate == IEEE80211_S_INIT) {
/*
@@ -5510,9 +5995,43 @@ ath_newstate(struct ieee80211vap *vap, enum ieee80211_state nstate, int arg)
#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);
+ /*
+ * Always at least set the self-generated
+ * frame config to set PWRMGT=1.
+ */
+ ath_power_setselfgen(sc, HAL_PM_NETWORK_SLEEP);
+
+ /*
+ * If we're not syncing beacons, transition
+ * to NETWORK_SLEEP.
+ *
+ * We stay awake if syncbeacon > 0 in case
+ * we need to listen for some beacons otherwise
+ * our beacon timer config may be wrong.
+ */
+ if (sc->sc_syncbeacon == 0) {
+ ath_power_setpower(sc, 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 +6086,13 @@ ath_newassoc(struct ieee80211_node *ni, int isnew)
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 +6339,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 +6352,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 +6453,13 @@ ath_ioctl_diag(struct ath_softc *sc, struct ath_diag *ad)
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 +6469,12 @@ ath_ioctl_diag(struct ath_softc *sc, struct ath_diag *ad)
} 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);
@@ -5957,7 +6504,9 @@ ath_ioctl(struct ifnet *ifp, u_long cmd, caddr_t data)
* only reflect promisc mode settings.
*/
ATH_LOCK(sc);
+ ath_power_set_power_state(sc, HAL_PM_AWAKE);
ath_mode_init(sc);
+ ath_power_restore_power_state(sc);
ATH_UNLOCK(sc);
} else if (ifp->if_flags & IFF_UP) {
/*
@@ -5974,11 +6523,8 @@ ath_ioctl(struct ifnet *ifp, u_long cmd, caddr_t data)
} 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;
OpenPOWER on IntegriCloud