Commit 5faaff74 authored by Bob Copeland's avatar Bob Copeland Committed by John W. Linville

ath5k: move reset to mac80211 workqueue

We currently trigger a reset via a tasklet when certain error
conditions are detected so that the card will (eventually)
restart.  Unfortunately this makes locking complicated since
reset can also be called in process context (e.g. for channel
change).  Currently nothing protects against concurrent resets,
which can be the source of corruption bugs.

Reset takes too long to spinlock the whole thing, so this
patch moves deferred resets into the mac80211 workqueue to
enable use of sc->lock mutex.
Signed-off-by: default avatarBob Copeland <me@bobcopeland.com>
Acked-by: default avatarBruno Randolf <br1@einfach.org>
Signed-off-by: default avatarJohn W. Linville <linville@tuxdriver.com>
parent da5747eb
......@@ -388,7 +388,7 @@ static int ath5k_init(struct ath5k_softc *sc);
static int ath5k_stop_locked(struct ath5k_softc *sc);
static int ath5k_stop_hw(struct ath5k_softc *sc);
static irqreturn_t ath5k_intr(int irq, void *dev_id);
static void ath5k_tasklet_reset(unsigned long data);
static void ath5k_reset_work(struct work_struct *work);
static void ath5k_tasklet_calibrate(unsigned long data);
......@@ -831,11 +831,12 @@ ath5k_attach(struct pci_dev *pdev, struct ieee80211_hw *hw)
tasklet_init(&sc->rxtq, ath5k_tasklet_rx, (unsigned long)sc);
tasklet_init(&sc->txtq, ath5k_tasklet_tx, (unsigned long)sc);
tasklet_init(&sc->restq, ath5k_tasklet_reset, (unsigned long)sc);
tasklet_init(&sc->calib, ath5k_tasklet_calibrate, (unsigned long)sc);
tasklet_init(&sc->beacontq, ath5k_tasklet_beacon, (unsigned long)sc);
tasklet_init(&sc->ani_tasklet, ath5k_tasklet_ani, (unsigned long)sc);
INIT_WORK(&sc->reset_work, ath5k_reset_work);
ret = ath5k_eeprom_read_mac(ah, mac);
if (ret) {
ATH5K_ERR(sc, "unable to read address from EEPROM: 0x%04x\n",
......@@ -2294,8 +2295,8 @@ ath5k_beacon_setup(struct ath5k_softc *sc, struct ath5k_buf *bf)
* frame contents are done as needed and the slot time is
* also adjusted based on current state.
*
* This is called from software irq context (beacontq or restq
* tasklets) or user context from ath5k_beacon_config.
* This is called from software irq context (beacontq tasklets)
* or user context from ath5k_beacon_config.
*/
static void
ath5k_beacon_send(struct ath5k_softc *sc)
......@@ -2328,7 +2329,7 @@ ath5k_beacon_send(struct ath5k_softc *sc)
sc->bmisscount);
ATH5K_DBG(sc, ATH5K_DEBUG_RESET,
"stuck beacon, resetting\n");
tasklet_schedule(&sc->restq);
ieee80211_queue_work(sc->hw, &sc->reset_work);
}
return;
}
......@@ -2684,7 +2685,6 @@ ath5k_stop_hw(struct ath5k_softc *sc)
tasklet_kill(&sc->rxtq);
tasklet_kill(&sc->txtq);
tasklet_kill(&sc->restq);
tasklet_kill(&sc->calib);
tasklet_kill(&sc->beacontq);
tasklet_kill(&sc->ani_tasklet);
......@@ -2737,7 +2737,7 @@ ath5k_intr(int irq, void *dev_id)
*/
ATH5K_DBG(sc, ATH5K_DEBUG_RESET,
"fatal int, resetting\n");
tasklet_schedule(&sc->restq);
ieee80211_queue_work(sc->hw, &sc->reset_work);
} else if (unlikely(status & AR5K_INT_RXORN)) {
/*
* Receive buffers are full. Either the bus is busy or
......@@ -2752,7 +2752,7 @@ ath5k_intr(int irq, void *dev_id)
if (ah->ah_mac_srev < AR5K_SREV_AR5212) {
ATH5K_DBG(sc, ATH5K_DEBUG_RESET,
"rx overrun, resetting\n");
tasklet_schedule(&sc->restq);
ieee80211_queue_work(sc->hw, &sc->reset_work);
}
else
tasklet_schedule(&sc->rxtq);
......@@ -2799,14 +2799,6 @@ ath5k_intr(int irq, void *dev_id)
return IRQ_HANDLED;
}
static void
ath5k_tasklet_reset(unsigned long data)
{
struct ath5k_softc *sc = (void *)data;
ath5k_reset(sc, sc->curchan);
}
/*
* Periodically recalibrate the PHY to account
* for temperature/environment changes.
......@@ -2830,7 +2822,7 @@ ath5k_tasklet_calibrate(unsigned long data)
* to load new gain values.
*/
ATH5K_DBG(sc, ATH5K_DEBUG_RESET, "calibration, resetting\n");
ath5k_reset(sc, sc->curchan);
ieee80211_queue_work(sc->hw, &sc->reset_work);
}
if (ath5k_hw_phy_calibrate(ah, sc->curchan))
ATH5K_ERR(sc, "calibration of channel %u failed\n",
......@@ -2934,6 +2926,8 @@ static int ath5k_tx_queue(struct ieee80211_hw *hw, struct sk_buff *skb,
/*
* Reset the hardware. If chan is not NULL, then also pause rx/tx
* and change to the given channel.
*
* This should be called with sc->lock.
*/
static int
ath5k_reset(struct ath5k_softc *sc, struct ieee80211_channel *chan)
......@@ -2990,6 +2984,16 @@ ath5k_reset(struct ath5k_softc *sc, struct ieee80211_channel *chan)
return ret;
}
static void ath5k_reset_work(struct work_struct *work)
{
struct ath5k_softc *sc = container_of(work, struct ath5k_softc,
reset_work);
mutex_lock(&sc->lock);
ath5k_reset(sc, sc->curchan);
mutex_unlock(&sc->lock);
}
static int ath5k_start(struct ieee80211_hw *hw)
{
return ath5k_init(hw->priv);
......
......@@ -47,6 +47,7 @@
#include <linux/if_ether.h>
#include <linux/leds.h>
#include <linux/rfkill.h>
#include <linux/workqueue.h>
#include "ath5k.h"
#include "debug.h"
......@@ -189,7 +190,7 @@ struct ath5k_softc {
unsigned int led_pin, /* GPIO pin for driving LED */
led_on; /* pin setting for LED on */
struct tasklet_struct restq; /* reset tasklet */
struct work_struct reset_work; /* deferred chip reset */
unsigned int rxbufsize; /* rx size based on mtu */
struct list_head rxbuf; /* receive buffer */
......
......@@ -279,7 +279,7 @@ static ssize_t write_file_reset(struct file *file,
{
struct ath5k_softc *sc = file->private_data;
ATH5K_DBG(sc, ATH5K_DEBUG_RESET, "debug file triggered reset\n");
tasklet_schedule(&sc->restq);
ieee80211_queue_work(sc->hw, &sc->reset_work);
return count;
}
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment