diff options
author | Jouni Malinen <jouni.malinen@atheros.com> | 2009-03-03 12:23:37 -0500 |
---|---|---|
committer | John W. Linville <linville@tuxdriver.com> | 2009-03-05 14:39:47 -0500 |
commit | 7ec3e514d9361596cbd8aa71ce41d6e5b0220103 (patch) | |
tree | 55c4fbc57230fe5d237a31c17dc01ed5ee65cead | |
parent | ee166a0e71947e0ebeb044fd2277435f665270ac (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.h | 4 | ||||
-rw-r--r-- | drivers/net/wireless/ath9k/main.c | 4 | ||||
-rw-r--r-- | drivers/net/wireless/ath9k/virtual.c | 37 |
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, | |||
665 | void ath_update_chainmask(struct ath_softc *sc, int is_ht); | 667 | void ath_update_chainmask(struct ath_softc *sc, int is_ht); |
666 | int ath_set_channel(struct ath_softc *sc, struct ieee80211_hw *hw, | 668 | int ath_set_channel(struct ath_softc *sc, struct ieee80211_hw *hw, |
667 | struct ath9k_channel *hchan); | 669 | struct ath9k_channel *hchan); |
670 | void ath_radio_enable(struct ath_softc *sc); | ||
671 | void ath_radio_disable(struct ath_softc *sc); | ||
668 | 672 | ||
669 | #ifdef CONFIG_PCI | 673 | #ifdef CONFIG_PCI |
670 | int ath_pci_init(void); | 674 | int 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 | ||
1093 | static void ath_radio_enable(struct ath_softc *sc) | 1093 | void 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 | ||
1134 | static void ath_radio_disable(struct ath_softc *sc) | 1134 | void 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 | ||
435 | static 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 */ |
436 | static void __ath9k_wiphy_pause_all(struct ath_softc *sc) | 448 | static 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; |