Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 9adcf440 authored by Felix Fietkau's avatar Felix Fietkau Committed by John W. Linville
Browse files

ath9k: merge reset related functions



reduces unnecessary code duplication. Also takes the sc_pcu_lock within
ath_reset instead of callsites, which makes it possible to always cancel
all queued work items before the reset, possibly fixing a few race
conditions (work items vs reset) along with it.

Signed-off-by: default avatarFelix Fietkau <nbd@openwrt.org>
Signed-off-by: default avatarJohn W. Linville <linville@tuxdriver.com>
parent 236de514
Loading
Loading
Loading
Loading
+127 −176
Original line number Diff line number Diff line
@@ -212,84 +212,58 @@ static int ath_update_survey_stats(struct ath_softc *sc)
	return ret;
}

/*
 * Set/change channels.  If the channel is really being changed, it's done
 * by reseting the chip.  To accomplish this we must first cleanup any pending
 * DMA, then restart stuff.
*/
static int ath_set_channel(struct ath_softc *sc, struct ieee80211_hw *hw,
		    struct ath9k_channel *hchan)
static void __ath_cancel_work(struct ath_softc *sc)
{
	struct ath_hw *ah = sc->sc_ah;
	struct ath_common *common = ath9k_hw_common(ah);
	struct ieee80211_conf *conf = &common->hw->conf;
	bool fastcc = true, stopped;
	struct ieee80211_channel *channel = hw->conf.channel;
	struct ath9k_hw_cal_data *caldata = NULL;
	int r;

	if (sc->sc_flags & SC_OP_INVALID)
		return -EIO;

	sc->hw_busy_count = 0;

	del_timer_sync(&common->ani.timer);
	cancel_work_sync(&sc->paprd_work);
	cancel_work_sync(&sc->hw_check_work);
	cancel_work_sync(&sc->hw_reset_work);
	cancel_delayed_work_sync(&sc->tx_complete_work);
	cancel_delayed_work_sync(&sc->hw_pll_work);
}

	ath9k_ps_wakeup(sc);

	spin_lock_bh(&sc->sc_pcu_lock);
static void ath_cancel_work(struct ath_softc *sc)
{
	__ath_cancel_work(sc);
	cancel_work_sync(&sc->hw_reset_work);
}

	/*
	 * This is only performed if the channel settings have
	 * actually changed.
	 *
	 * To switch channels clear any pending DMA operations;
	 * wait long enough for the RX fifo to drain, reset the
	 * hardware at the new frequency, and then re-enable
	 * the relevant bits of the h/w.
	 */
	ath9k_hw_disable_interrupts(ah);
	stopped = ath_drain_all_txq(sc, false);
static bool ath_prepare_reset(struct ath_softc *sc, bool retry_tx, bool flush)
{
	struct ath_hw *ah = sc->sc_ah;
	struct ath_common *common = ath9k_hw_common(ah);
	bool ret;

	if (!ath_stoprecv(sc))
		stopped = false;
	ieee80211_stop_queues(sc->hw);

	if (!ath9k_hw_check_alive(ah))
		stopped = false;
	sc->hw_busy_count = 0;
	del_timer_sync(&common->ani.timer);

	/* XXX: do not flush receive queue here. We don't want
	 * to flush data frames already in queue because of
	 * changing channel. */
	ath9k_debug_samp_bb_mac(sc);
	ath9k_hw_disable_interrupts(ah);

	if (!stopped || !(sc->sc_flags & SC_OP_OFFCHANNEL))
		fastcc = false;
	ret = ath_drain_all_txq(sc, retry_tx);

	if (!(sc->sc_flags & SC_OP_OFFCHANNEL))
		caldata = &sc->caldata;
	if (!ath_stoprecv(sc))
		ret = false;

	ath_dbg(common, ATH_DBG_CONFIG,
		"(%u MHz) -> (%u MHz), conf_is_ht40: %d fastcc: %d\n",
		sc->sc_ah->curchan->channel,
		channel->center_freq, conf_is_ht40(conf),
		fastcc);
	if (!flush) {
		if (ah->caps.hw_caps & ATH9K_HW_CAP_EDMA)
			ath_rx_tasklet(sc, 0, true);
		ath_rx_tasklet(sc, 0, false);
	} else {
		ath_flushrecv(sc);
	}

	r = ath9k_hw_reset(ah, hchan, caldata, fastcc);
	if (r) {
		ath_err(common,
			"Unable to reset channel (%u MHz), reset status %d\n",
			channel->center_freq, r);
		goto ps_restore;
	return ret;
}

static bool ath_complete_reset(struct ath_softc *sc, bool start)
{
	struct ath_hw *ah = sc->sc_ah;
	struct ath_common *common = ath9k_hw_common(ah);

	if (ath_startrecv(sc) != 0) {
		ath_err(common, "Unable to restart recv logic\n");
		r = -EIO;
		goto ps_restore;
		return false;
	}

	ath9k_cmn_update_txpow(ah, sc->curtxpow,
@@ -297,21 +271,93 @@ static int ath_set_channel(struct ath_softc *sc, struct ieee80211_hw *hw,
	ath9k_hw_set_interrupts(ah, ah->imask);
	ath9k_hw_enable_interrupts(ah);

	if (!(sc->sc_flags & (SC_OP_OFFCHANNEL))) {
	if (!(sc->sc_flags & (SC_OP_OFFCHANNEL)) && start) {
		if (sc->sc_flags & SC_OP_BEACONS)
			ath_set_beacon(sc);

		ieee80211_queue_delayed_work(sc->hw, &sc->tx_complete_work, 0);
		ieee80211_queue_delayed_work(sc->hw, &sc->hw_pll_work, HZ/2);
		if (!common->disable_ani)
			ath_start_ani(common);
	}

 ps_restore:
	ieee80211_wake_queues(hw);
	ieee80211_wake_queues(sc->hw);

	return true;
}

static int ath_reset_internal(struct ath_softc *sc, struct ath9k_channel *hchan,
			      bool retry_tx)
{
	struct ath_hw *ah = sc->sc_ah;
	struct ath_common *common = ath9k_hw_common(ah);
	struct ath9k_hw_cal_data *caldata = NULL;
	bool fastcc = true;
	bool flush = false;
	int r;

	__ath_cancel_work(sc);

	spin_lock_bh(&sc->sc_pcu_lock);

	if (!(sc->sc_flags & SC_OP_OFFCHANNEL)) {
		fastcc = false;
		caldata = &sc->caldata;
	}

	if (!hchan) {
		fastcc = false;
		flush = true;
		hchan = ah->curchan;
	}

	if (fastcc && !ath9k_hw_check_alive(ah))
		fastcc = false;

	if (!ath_prepare_reset(sc, retry_tx, flush))
		fastcc = false;

	ath_dbg(common, ATH_DBG_CONFIG,
		"Reset to %u MHz, HT40: %d fastcc: %d\n",
		hchan->channel, !!(hchan->channelFlags & (CHANNEL_HT40MINUS |
							  CHANNEL_HT40PLUS)),
		fastcc);

	r = ath9k_hw_reset(ah, hchan, caldata, fastcc);
	if (r) {
		ath_err(common,
			"Unable to reset channel, reset status %d\n", r);
		goto out;
	}

	if (!ath_complete_reset(sc, true))
		r = -EIO;

out:
	spin_unlock_bh(&sc->sc_pcu_lock);
	return r;
}


/*
 * Set/change channels.  If the channel is really being changed, it's done
 * by reseting the chip.  To accomplish this we must first cleanup any pending
 * DMA, then restart stuff.
*/
static int ath_set_channel(struct ath_softc *sc, struct ieee80211_hw *hw,
		    struct ath9k_channel *hchan)
{
	int r;

	if (sc->sc_flags & SC_OP_INVALID)
		return -EIO;

	ath9k_ps_wakeup(sc);

	r = ath_reset_internal(sc, hchan, false);

	ath9k_ps_restore(sc);

	return r;
}

@@ -825,28 +871,13 @@ static void ath_radio_enable(struct ath_softc *sc, struct ieee80211_hw *hw)
			channel->center_freq, r);
	}

	ath9k_cmn_update_txpow(ah, sc->curtxpow,
			       sc->config.txpowlimit, &sc->curtxpow);
	if (ath_startrecv(sc) != 0) {
		ath_err(common, "Unable to restart recv logic\n");
		goto out;
	}
	if (sc->sc_flags & SC_OP_BEACONS)
		ath_set_beacon(sc);	/* restart beacons */

	/* Re-Enable  interrupts */
	ath9k_hw_set_interrupts(ah, ah->imask);
	ath9k_hw_enable_interrupts(ah);
	ath_complete_reset(sc, true);

	/* Enable LED */
	ath9k_hw_cfg_output(ah, ah->led_pin,
			    AR_GPIO_OUTPUT_MUX_AS_OUTPUT);
	ath9k_hw_set_gpio(ah, ah->led_pin, 0);

	ieee80211_wake_queues(hw);
	ieee80211_queue_delayed_work(hw, &sc->hw_pll_work, HZ/2);

out:
	spin_unlock_bh(&sc->sc_pcu_lock);

	ath9k_ps_restore(sc);
@@ -859,11 +890,10 @@ void ath_radio_disable(struct ath_softc *sc, struct ieee80211_hw *hw)
	int r;

	ath9k_ps_wakeup(sc);
	cancel_delayed_work_sync(&sc->hw_pll_work);

	spin_lock_bh(&sc->sc_pcu_lock);
	ath_cancel_work(sc);

	ieee80211_stop_queues(hw);
	spin_lock_bh(&sc->sc_pcu_lock);

	/*
	 * Keep the LED on when the radio is disabled
@@ -874,13 +904,7 @@ void ath_radio_disable(struct ath_softc *sc, struct ieee80211_hw *hw)
		ath9k_hw_cfg_gpio_input(ah, ah->led_pin);
	}

	/* Disable interrupts */
	ath9k_hw_disable_interrupts(ah);

	ath_drain_all_txq(sc, false);	/* clear pending tx frames */

	ath_stoprecv(sc);		/* turn off frame recv */
	ath_flushrecv(sc);		/* flush recv queue */
	ath_prepare_reset(sc, false, true);

	if (!ah->curchan)
		ah->curchan = ath9k_cmn_get_curchannel(hw, ah);
@@ -902,49 +926,11 @@ void ath_radio_disable(struct ath_softc *sc, struct ieee80211_hw *hw)

static int ath_reset(struct ath_softc *sc, bool retry_tx)
{
	struct ath_hw *ah = sc->sc_ah;
	struct ath_common *common = ath9k_hw_common(ah);
	struct ieee80211_hw *hw = sc->hw;
	int r;

	sc->hw_busy_count = 0;

	ath9k_debug_samp_bb_mac(sc);
	/* Stop ANI */

	del_timer_sync(&common->ani.timer);

	ath9k_ps_wakeup(sc);

	ieee80211_stop_queues(hw);

	ath9k_hw_disable_interrupts(ah);
	ath_drain_all_txq(sc, retry_tx);

	ath_stoprecv(sc);
	ath_flushrecv(sc);

	r = ath9k_hw_reset(ah, sc->sc_ah->curchan, ah->caldata, false);
	if (r)
		ath_err(common,
			"Unable to reset hardware; reset status %d\n", r);

	if (ath_startrecv(sc) != 0)
		ath_err(common, "Unable to start recv logic\n");

	/*
	 * We may be doing a reset in response to a request
	 * that changes the channel so update any state that
	 * might change as a result.
	 */
	ath9k_cmn_update_txpow(ah, sc->curtxpow,
			       sc->config.txpowlimit, &sc->curtxpow);

	if ((sc->sc_flags & SC_OP_BEACONS) || !(sc->sc_flags & (SC_OP_OFFCHANNEL)))
		ath_set_beacon(sc);	/* restart beacons */

	ath9k_hw_set_interrupts(ah, ah->imask);
	ath9k_hw_enable_interrupts(ah);
	r = ath_reset_internal(sc, NULL, retry_tx);

	if (retry_tx) {
		int i;
@@ -957,12 +943,6 @@ static int ath_reset(struct ath_softc *sc, bool retry_tx)
		}
	}

	ieee80211_wake_queues(hw);

	/* Start ANI */
	if (!common->disable_ani)
		ath_start_ani(common);

	ath9k_ps_restore(sc);

	return r;
@@ -972,9 +952,7 @@ void ath_reset_work(struct work_struct *work)
{
	struct ath_softc *sc = container_of(work, struct ath_softc, hw_reset_work);

	spin_lock_bh(&sc->sc_pcu_lock);
	ath_reset(sc, true);
	spin_unlock_bh(&sc->sc_pcu_lock);
}

void ath_hw_check(struct work_struct *work)
@@ -995,11 +973,8 @@ void ath_hw_check(struct work_struct *work)
	ath_dbg(common, ATH_DBG_RESET, "Possible baseband hang, "
		"busy=%d (try %d)\n", busy, sc->hw_busy_count + 1);
	if (busy >= 99) {
		if (++sc->hw_busy_count >= 3) {
			spin_lock_bh(&sc->sc_pcu_lock);
			ath_reset(sc, true);
			spin_unlock_bh(&sc->sc_pcu_lock);
		}
		if (++sc->hw_busy_count >= 3)
			ieee80211_queue_work(sc->hw, &sc->hw_reset_work);

	} else if (busy >= 0)
		sc->hw_busy_count = 0;
@@ -1019,9 +994,7 @@ static void ath_hw_pll_rx_hang_check(struct ath_softc *sc, u32 pll_sqsum)
			/* Rx is hung for more than 500ms. Reset it */
			ath_dbg(common, ATH_DBG_RESET,
				"Possible RX hang, resetting");
			spin_lock_bh(&sc->sc_pcu_lock);
			ath_reset(sc, true);
			spin_unlock_bh(&sc->sc_pcu_lock);
			ieee80211_queue_work(sc->hw, &sc->hw_reset_work);
			count = 0;
		}
	} else
@@ -1092,28 +1065,6 @@ static int ath9k_start(struct ieee80211_hw *hw)
		goto mutex_unlock;
	}

	/*
	 * This is needed only to setup initial state
	 * but it's best done after a reset.
	 */
	ath9k_cmn_update_txpow(ah, sc->curtxpow,
			sc->config.txpowlimit, &sc->curtxpow);

	/*
	 * Setup the hardware after reset:
	 * The receive engine is set going.
	 * Frame transmit is handled entirely
	 * in the frame output path; there's nothing to do
	 * here except setup the interrupt mask.
	 */
	if (ath_startrecv(sc) != 0) {
		ath_err(common, "Unable to start recv logic\n");
		r = -EIO;
		spin_unlock_bh(&sc->sc_pcu_lock);
		goto mutex_unlock;
	}
	spin_unlock_bh(&sc->sc_pcu_lock);

	/* Setup our intr mask. */
	ah->imask = ATH9K_INT_TX | ATH9K_INT_RXEOL |
		    ATH9K_INT_RXORN | ATH9K_INT_FATAL |
@@ -1136,12 +1087,14 @@ static int ath9k_start(struct ieee80211_hw *hw)

	/* Disable BMISS interrupt when we're not associated */
	ah->imask &= ~(ATH9K_INT_SWBA | ATH9K_INT_BMISS);
	ath9k_hw_set_interrupts(ah, ah->imask);
	ath9k_hw_enable_interrupts(ah);

	ieee80211_wake_queues(hw);
	if (!ath_complete_reset(sc, false)) {
		r = -EIO;
		spin_unlock_bh(&sc->sc_pcu_lock);
		goto mutex_unlock;
	}

	ieee80211_queue_delayed_work(sc->hw, &sc->tx_complete_work, 0);
	spin_unlock_bh(&sc->sc_pcu_lock);

	if ((ah->btcoex_hw.scheme != ATH_BTCOEX_CFG_NONE) &&
	    !ah->btcoex_hw.enabled) {
@@ -1234,11 +1187,7 @@ static void ath9k_stop(struct ieee80211_hw *hw)

	mutex_lock(&sc->mutex);

	cancel_delayed_work_sync(&sc->tx_complete_work);
	cancel_delayed_work_sync(&sc->hw_pll_work);
	cancel_work_sync(&sc->paprd_work);
	cancel_work_sync(&sc->hw_check_work);
	cancel_work_sync(&sc->hw_reset_work);
	ath_cancel_work(sc);

	if (sc->sc_flags & SC_OP_INVALID) {
		ath_dbg(common, ATH_DBG_ANY, "Device not present\n");
@@ -2349,9 +2298,11 @@ static void ath9k_flush(struct ieee80211_hw *hw, bool drop)
	ath9k_ps_wakeup(sc);
	spin_lock_bh(&sc->sc_pcu_lock);
	drain_txq = ath_drain_all_txq(sc, false);
	spin_unlock_bh(&sc->sc_pcu_lock);

	if (!drain_txq)
		ath_reset(sc, false);
	spin_unlock_bh(&sc->sc_pcu_lock);

	ath9k_ps_restore(sc);
	ieee80211_wake_queues(hw);