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

Commit 124b979b authored by Rajkumar Manoharan's avatar Rajkumar Manoharan Committed by John W. Linville
Browse files

ath9k: Fix race in reset-work usage



Using work_pending() to defer certain operations when
a HW-reset work has been queued is racy since the check
would return false when the work item is actually in
execution. Use SC_OP_HW_RESET instead to fix this race.
Also, unify the reset debug statistics maintenance.

Signed-off-by: default avatarRajkumar Manoharan <rmanohar@qca.qualcomm.com>
Signed-off-by: default avatarSujith Manoharan <c_manoha@qca.qualcomm.com>
Signed-off-by: default avatarJohn W. Linville <linville@tuxdriver.com>
parent 6dcc3444
Loading
Loading
Loading
Loading
+1 −0
Original line number Diff line number Diff line
@@ -452,6 +452,7 @@ void ath_stop_ani(struct ath_softc *sc);
void ath_check_ani(struct ath_softc *sc);
int ath_update_survey_stats(struct ath_softc *sc);
void ath_update_survey_nf(struct ath_softc *sc, int channel);
void ath9k_queue_reset(struct ath_softc *sc, enum ath_reset_type type);

/**********/
/* BTCOEX */
+3 −2
Original line number Diff line number Diff line
@@ -317,11 +317,12 @@ void ath9k_beacon_tasklet(unsigned long data)
	bool edma = !!(ah->caps.hw_caps & ATH9K_HW_CAP_EDMA);
	int slot;

	if (work_pending(&sc->hw_reset_work)) {
	if (test_bit(SC_OP_HW_RESET, &sc->sc_flags)) {
		ath_dbg(common, RESET,
			"reset work is pending, skip beaconing now\n");
		return;
	}

	/*
	 * Check if the previous beacon has gone out.  If
	 * not don't try to post another, skip this period
@@ -345,7 +346,7 @@ void ath9k_beacon_tasklet(unsigned long data)
		} else if (sc->beacon.bmisscnt >= BSTUCK_THRESH) {
			ath_dbg(common, BSTUCK, "beacon is officially stuck\n");
			sc->beacon.bmisscnt = 0;
			ieee80211_queue_work(sc->hw, &sc->hw_reset_work);
			ath9k_queue_reset(sc, RESET_TYPE_BEACON_STUCK);
		}

		return;
+13 −11
Original line number Diff line number Diff line
@@ -32,6 +32,19 @@ struct ath_buf;
#define RESET_STAT_INC(sc, type) do { } while (0)
#endif

enum ath_reset_type {
	RESET_TYPE_BB_HANG,
	RESET_TYPE_BB_WATCHDOG,
	RESET_TYPE_FATAL_INT,
	RESET_TYPE_TX_ERROR,
	RESET_TYPE_TX_HANG,
	RESET_TYPE_PLL_HANG,
	RESET_TYPE_MAC_HANG,
	RESET_TYPE_BEACON_STUCK,
	RESET_TYPE_MCI,
	__RESET_TYPE_MAX
};

#ifdef CONFIG_ATH9K_DEBUGFS

/**
@@ -209,17 +222,6 @@ struct ath_rx_stats {
	u32 rx_frags;
};

enum ath_reset_type {
	RESET_TYPE_BB_HANG,
	RESET_TYPE_BB_WATCHDOG,
	RESET_TYPE_FATAL_INT,
	RESET_TYPE_TX_ERROR,
	RESET_TYPE_TX_HANG,
	RESET_TYPE_PLL_HANG,
	RESET_TYPE_MAC_HANG,
	__RESET_TYPE_MAX
};

struct ath_stats {
	struct ath_interrupt_stats istats;
	struct ath_tx_stats txstats[ATH9K_NUM_TX_QUEUES];
+6 −7
Original line number Diff line number Diff line
@@ -50,8 +50,7 @@ void ath_tx_complete_poll_work(struct work_struct *work)
	if (needreset) {
		ath_dbg(ath9k_hw_common(sc->sc_ah), RESET,
			"tx hung, resetting the chip\n");
		RESET_STAT_INC(sc, RESET_TYPE_TX_HANG);
		ieee80211_queue_work(sc->hw, &sc->hw_reset_work);
		ath9k_queue_reset(sc, RESET_TYPE_TX_HANG);
		return;
	}

@@ -69,6 +68,7 @@ void ath_hw_check(struct work_struct *work)
	unsigned long flags;
	int busy;
	u8 is_alive, nbeacon = 1;
	enum ath_reset_type type;

	ath9k_ps_wakeup(sc);
	is_alive = ath9k_hw_check_alive(sc->sc_ah);
@@ -78,7 +78,7 @@ void ath_hw_check(struct work_struct *work)
	else if (!is_alive && AR_SREV_9300(sc->sc_ah)) {
		ath_dbg(common, RESET,
			"DCU stuck is detected. Schedule chip reset\n");
		RESET_STAT_INC(sc, RESET_TYPE_MAC_HANG);
		type = RESET_TYPE_MAC_HANG;
		goto sched_reset;
	}

@@ -90,7 +90,7 @@ void ath_hw_check(struct work_struct *work)
		busy, sc->hw_busy_count + 1);
	if (busy >= 99) {
		if (++sc->hw_busy_count >= 3) {
			RESET_STAT_INC(sc, RESET_TYPE_BB_HANG);
			type = RESET_TYPE_BB_HANG;
			goto sched_reset;
		}
	} else if (busy >= 0) {
@@ -102,7 +102,7 @@ void ath_hw_check(struct work_struct *work)
	goto out;

sched_reset:
	ieee80211_queue_work(sc->hw, &sc->hw_reset_work);
	ath9k_queue_reset(sc, type);
out:
	ath9k_ps_restore(sc);
}
@@ -119,8 +119,7 @@ static bool ath_hw_pll_rx_hang_check(struct ath_softc *sc, u32 pll_sqsum)
		count++;
		if (count == 3) {
			ath_dbg(common, RESET, "PLL WAR, resetting the chip\n");
			RESET_STAT_INC(sc, RESET_TYPE_PLL_HANG);
			ieee80211_queue_work(sc->hw, &sc->hw_reset_work);
			ath9k_queue_reset(sc, RESET_TYPE_PLL_HANG);
			count = 0;
			return true;
		}
+11 −6
Original line number Diff line number Diff line
@@ -363,6 +363,7 @@ void ath9k_tasklet(unsigned long data)
	struct ath_softc *sc = (struct ath_softc *)data;
	struct ath_hw *ah = sc->sc_ah;
	struct ath_common *common = ath9k_hw_common(ah);
	enum ath_reset_type type;
	unsigned long flags;
	u32 status = sc->intrstatus;
	u32 rxmask;
@@ -372,18 +373,13 @@ void ath9k_tasklet(unsigned long data)

	if ((status & ATH9K_INT_FATAL) ||
	    (status & ATH9K_INT_BB_WATCHDOG)) {
#ifdef CONFIG_ATH9K_DEBUGFS
		enum ath_reset_type type;

		if (status & ATH9K_INT_FATAL)
			type = RESET_TYPE_FATAL_INT;
		else
			type = RESET_TYPE_BB_WATCHDOG;

		RESET_STAT_INC(sc, type);
#endif
		set_bit(SC_OP_HW_RESET, &sc->sc_flags);
		ieee80211_queue_work(sc->hw, &sc->hw_reset_work);
		ath9k_queue_reset(sc, type);
		goto out;
	}

@@ -584,6 +580,15 @@ static int ath_reset(struct ath_softc *sc, bool retry_tx)
	return r;
}

void ath9k_queue_reset(struct ath_softc *sc, enum ath_reset_type type)
{
#ifdef CONFIG_ATH9K_DEBUGFS
	RESET_STAT_INC(sc, type);
#endif
	set_bit(SC_OP_HW_RESET, &sc->sc_flags);
	ieee80211_queue_work(sc->hw, &sc->hw_reset_work);
}

void ath_reset_work(struct work_struct *work)
{
	struct ath_softc *sc = container_of(work, struct ath_softc, hw_reset_work);
Loading