aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorJouni Malinen <jouni.malinen@atheros.com>2009-03-03 12:23:37 -0500
committerJohn W. Linville <linville@tuxdriver.com>2009-03-05 14:39:47 -0500
commit7ec3e514d9361596cbd8aa71ce41d6e5b0220103 (patch)
tree55c4fbc57230fe5d237a31c17dc01ed5ee65cead
parentee166a0e71947e0ebeb044fd2277435f665270ac (diff)
ath9k: Add workaround to recover from failed channel changes
It looks like channel change may fail in some cases and end up leaving the hardware in state where it cannot transmit any frames. Add a workaround to recover from this state if we detect that wiphy selection is failing due to wiphys not leaving PAUSING state. Signed-off-by: Jouni Malinen <jouni.malinen@atheros.com> Signed-off-by: John W. Linville <linville@tuxdriver.com>
-rw-r--r--drivers/net/wireless/ath9k/ath9k.h4
-rw-r--r--drivers/net/wireless/ath9k/main.c4
-rw-r--r--drivers/net/wireless/ath9k/virtual.c37
3 files changed, 43 insertions, 2 deletions
diff --git a/drivers/net/wireless/ath9k/ath9k.h b/drivers/net/wireless/ath9k/ath9k.h
index 24373d395e49..4fc054e4354f 100644
--- a/drivers/net/wireless/ath9k/ath9k.h
+++ b/drivers/net/wireless/ath9k/ath9k.h
@@ -567,6 +567,8 @@ struct ath_softc {
567 int chan_is_ht; 567 int chan_is_ht;
568 struct ath_wiphy *next_wiphy; 568 struct ath_wiphy *next_wiphy;
569 struct work_struct chan_work; 569 struct work_struct chan_work;
570 int wiphy_select_failures;
571 unsigned long wiphy_select_first_fail;
570 572
571 struct tasklet_struct intr_tq; 573 struct tasklet_struct intr_tq;
572 struct tasklet_struct bcon_tasklet; 574 struct tasklet_struct bcon_tasklet;
@@ -665,6 +667,8 @@ void ath9k_update_ichannel(struct ath_softc *sc, struct ieee80211_hw *hw,
665void ath_update_chainmask(struct ath_softc *sc, int is_ht); 667void ath_update_chainmask(struct ath_softc *sc, int is_ht);
666int ath_set_channel(struct ath_softc *sc, struct ieee80211_hw *hw, 668int ath_set_channel(struct ath_softc *sc, struct ieee80211_hw *hw,
667 struct ath9k_channel *hchan); 669 struct ath9k_channel *hchan);
670void ath_radio_enable(struct ath_softc *sc);
671void ath_radio_disable(struct ath_softc *sc);
668 672
669#ifdef CONFIG_PCI 673#ifdef CONFIG_PCI
670int ath_pci_init(void); 674int ath_pci_init(void);
diff --git a/drivers/net/wireless/ath9k/main.c b/drivers/net/wireless/ath9k/main.c
index 6d19a31934d5..bb6e1ddb4a57 100644
--- a/drivers/net/wireless/ath9k/main.c
+++ b/drivers/net/wireless/ath9k/main.c
@@ -1090,7 +1090,7 @@ fail:
1090/* Rfkill */ 1090/* Rfkill */
1091/*******************/ 1091/*******************/
1092 1092
1093static void ath_radio_enable(struct ath_softc *sc) 1093void ath_radio_enable(struct ath_softc *sc)
1094{ 1094{
1095 struct ath_hw *ah = sc->sc_ah; 1095 struct ath_hw *ah = sc->sc_ah;
1096 struct ieee80211_channel *channel = sc->hw->conf.channel; 1096 struct ieee80211_channel *channel = sc->hw->conf.channel;
@@ -1131,7 +1131,7 @@ static void ath_radio_enable(struct ath_softc *sc)
1131 ath9k_ps_restore(sc); 1131 ath9k_ps_restore(sc);
1132} 1132}
1133 1133
1134static void ath_radio_disable(struct ath_softc *sc) 1134void ath_radio_disable(struct ath_softc *sc)
1135{ 1135{
1136 struct ath_hw *ah = sc->sc_ah; 1136 struct ath_hw *ah = sc->sc_ah;
1137 struct ieee80211_channel *channel = sc->hw->conf.channel; 1137 struct ieee80211_channel *channel = sc->hw->conf.channel;
diff --git a/drivers/net/wireless/ath9k/virtual.c b/drivers/net/wireless/ath9k/virtual.c
index 6122f48f25fb..913d2043d23e 100644
--- a/drivers/net/wireless/ath9k/virtual.c
+++ b/drivers/net/wireless/ath9k/virtual.c
@@ -432,6 +432,18 @@ int ath9k_wiphy_unpause(struct ath_wiphy *aphy)
432 return ret; 432 return ret;
433} 433}
434 434
435static void __ath9k_wiphy_mark_all_paused(struct ath_softc *sc)
436{
437 int i;
438 if (sc->pri_wiphy->state != ATH_WIPHY_INACTIVE)
439 sc->pri_wiphy->state = ATH_WIPHY_PAUSED;
440 for (i = 0; i < sc->num_sec_wiphy; i++) {
441 if (sc->sec_wiphy[i] &&
442 sc->sec_wiphy[i]->state != ATH_WIPHY_INACTIVE)
443 sc->sec_wiphy[i]->state = ATH_WIPHY_PAUSED;
444 }
445}
446
435/* caller must hold wiphy_lock */ 447/* caller must hold wiphy_lock */
436static void __ath9k_wiphy_pause_all(struct ath_softc *sc) 448static void __ath9k_wiphy_pause_all(struct ath_softc *sc)
437{ 449{
@@ -452,9 +464,34 @@ int ath9k_wiphy_select(struct ath_wiphy *aphy)
452 464
453 spin_lock_bh(&sc->wiphy_lock); 465 spin_lock_bh(&sc->wiphy_lock);
454 if (__ath9k_wiphy_pausing(sc)) { 466 if (__ath9k_wiphy_pausing(sc)) {
467 if (sc->wiphy_select_failures == 0)
468 sc->wiphy_select_first_fail = jiffies;
469 sc->wiphy_select_failures++;
470 if (time_after(jiffies, sc->wiphy_select_first_fail + HZ / 2))
471 {
472 printk(KERN_DEBUG "ath9k: Previous wiphy select timed "
473 "out; disable/enable hw to recover\n");
474 __ath9k_wiphy_mark_all_paused(sc);
475 /*
476 * TODO: this workaround to fix hardware is unlikely to
477 * be specific to virtual wiphy changes. It can happen
478 * on normal channel change, too, and as such, this
479 * should really be made more generic. For example,
480 * tricker radio disable/enable on GTT interrupt burst
481 * (say, 10 GTT interrupts received without any TX
482 * frame being completed)
483 */
484 spin_unlock_bh(&sc->wiphy_lock);
485 ath_radio_disable(sc);
486 ath_radio_enable(sc);
487 queue_work(aphy->sc->hw->workqueue,
488 &aphy->sc->chan_work);
489 return -EBUSY; /* previous select still in progress */
490 }
455 spin_unlock_bh(&sc->wiphy_lock); 491 spin_unlock_bh(&sc->wiphy_lock);
456 return -EBUSY; /* previous select still in progress */ 492 return -EBUSY; /* previous select still in progress */
457 } 493 }
494 sc->wiphy_select_failures = 0;
458 495
459 /* Store the new channel */ 496 /* Store the new channel */
460 sc->chan_idx = aphy->chan_idx; 497 sc->chan_idx = aphy->chan_idx;