From c6cc1a421539eca78485614c0f53b288c1adcfb3 Mon Sep 17 00:00:00 2001 From: Luis R. Rodriguez Date: Tue, 10 Feb 2009 21:37:40 -0800 Subject: [PATCH] ath9k: implement IO serialization To: johannes@sipsolutions.net, linville@tuxdriver.com Cc: linux-wireless@vger.kernel.org All 802.11n PCI devices (Cardbus, PCI, mini-PCI) require serialization of IO when on non-uniprocessor systems. PCI express devices not not require this. Signed-off-by: Luis R. Rodriguez --- drivers/net/wireless/ath9k/ath9k.h | 1 + drivers/net/wireless/ath9k/hw.c | 49 ++++++++++++++++++++++++++++++++++++ drivers/net/wireless/ath9k/hw.h | 8 ++++- drivers/net/wireless/ath9k/main.c | 1 + 4 files changed, 57 insertions(+), 2 deletions(-) diff --git a/drivers/net/wireless/ath9k/ath9k.h b/drivers/net/wireless/ath9k/ath9k.h index 6481ea4..7245797 100644 --- a/drivers/net/wireless/ath9k/ath9k.h +++ b/drivers/net/wireless/ath9k/ath9k.h @@ -575,6 +575,7 @@ struct ath_softc { void __iomem *mem; int irq; spinlock_t sc_resetlock; + spinlock_t sc_serial_rw; struct mutex mutex; u8 curbssid[ETH_ALEN]; diff --git a/drivers/net/wireless/ath9k/hw.c b/drivers/net/wireless/ath9k/hw.c index 2acbb84..005dd51 100644 --- a/drivers/net/wireless/ath9k/hw.c +++ b/drivers/net/wireless/ath9k/hw.c @@ -37,6 +37,36 @@ static u32 ath9k_hw_ini_fixup(struct ath_hw *ah, static void ath9k_hw_9280_spur_mitigate(struct ath_hw *ah, struct ath9k_channel *chan); static void ath9k_hw_spur_mitigate(struct ath_hw *ah, struct ath9k_channel *chan); +/* + * Read and write, they both share the same lock. The exception is when + * in an the ISR -- in that case we don't need serialization as it should + * be "interrupting" everything and IRS are not reentrant. + */ + +void ath9k_iowrite32(struct ath_hw *ah, u32 reg_offset, u32 val) +{ + if (ah->config.serialize_regmode == SER_REG_MODE_ON && !in_irq()) { + unsigned long flags; + spin_lock_irqsave(&ah->ah_sc->sc_serial_rw, flags); + iowrite32(val, ah->ah_sc->mem + reg_offset); + spin_unlock_irqrestore(&ah->ah_sc->sc_serial_rw, flags); + } else + iowrite32(val, ah->ah_sc->mem + reg_offset); +} + +unsigned int ath9k_ioread32(struct ath_hw *ah, u32 reg_offset) +{ + u32 val; + if (ah->config.serialize_regmode == SER_REG_MODE_ON && !in_irq()) { + unsigned long flags; + spin_lock_irqsave(&ah->ah_sc->sc_serial_rw, flags); + val = ioread32(ah->ah_sc->mem + reg_offset); + spin_unlock_irqrestore(&ah->ah_sc->sc_serial_rw, flags); + } else + val = ioread32(ah->ah_sc->mem + reg_offset); + return val; +} + /********************/ /* Helper Functions */ /********************/ @@ -391,6 +421,25 @@ static void ath9k_hw_set_defaults(struct ath_hw *ah) } ah->config.intr_mitigation = 1; + + /* + * We need this for PCI devices only (Cardbus, PCI, miniPCI) + * _and_ if on non-uniprocessor systems (Multiprocessor/HT). + * This means we use it for all AR5416 devices, and the few + * minor PCI AR9280 devices out there. + * + * Serialization is required because these devices do not handle + * well the case of two concurrent reads/writes due to the latency + * involved. During one read/write another read/write can be issued + * on another CPU while the previous read/write may still be working + * on our hardware, if we hit this case the hardware poops in a loop. + * We prevent this by serializing reads and writes. + * + * This issue is not present on PCI-Express devices or pre-AR5416 + * devices (legacy, 802.11abg). + */ + if (num_possible_cpus() > 1) + ah->config.serialize_regmode = SER_REG_MODE_AUTO; } static struct ath_hw *ath9k_hw_newstate(u16 devid, struct ath_softc *sc, diff --git a/drivers/net/wireless/ath9k/hw.h b/drivers/net/wireless/ath9k/hw.h index 5ec416b..778137f 100644 --- a/drivers/net/wireless/ath9k/hw.h +++ b/drivers/net/wireless/ath9k/hw.h @@ -42,8 +42,8 @@ #define AR5416_MAGIC 0x19641014 /* Register read/write primitives */ -#define REG_WRITE(_ah, _reg, _val) iowrite32(_val, _ah->ah_sc->mem + _reg) -#define REG_READ(_ah, _reg) ioread32(_ah->ah_sc->mem + _reg) +#define REG_WRITE(_ah, _reg, _val) ath9k_iowrite32(_ah, _reg, _val) +#define REG_READ(_ah, _reg) ath9k_ioread32(_ah, _reg) #define SM(_v, _f) (((_v) << _f##_S) & _f) #define MS(_v, _f) (((_v) & _f) >> _f##_S) @@ -566,6 +566,10 @@ struct ath_hw { struct ar5416IniArray iniModesTxGain; }; +/* Read and write */ +void ath9k_iowrite32(struct ath_hw *ah, u32 reg_offset, u32 val); +unsigned int ath9k_ioread32(struct ath_hw *ah, u32 reg_offset); + /* Attach, Detach, Reset */ const char *ath9k_hw_probe(u16 vendorid, u16 devid); void ath9k_hw_detach(struct ath_hw *ah); diff --git a/drivers/net/wireless/ath9k/main.c b/drivers/net/wireless/ath9k/main.c index 1e38242..d25907d 100644 --- a/drivers/net/wireless/ath9k/main.c +++ b/drivers/net/wireless/ath9k/main.c @@ -1358,6 +1358,7 @@ static int ath_init(u16 devid, struct ath_softc *sc) printk(KERN_ERR "Unable to create debugfs files\n"); spin_lock_init(&sc->sc_resetlock); + spin_lock_init(&sc->sc_serial_rw); mutex_init(&sc->mutex); tasklet_init(&sc->intr_tq, ath9k_tasklet, (unsigned long)sc); tasklet_init(&sc->bcon_tasklet, ath9k_beacon_tasklet, -- 1.5.6.4 From 1f190c033aaa19d1cfa4589b4aa2010b4f8c9a9f Mon Sep 17 00:00:00 2001 From: Luis R. Rodriguez Date: Tue, 10 Feb 2009 21:57:18 -0800 Subject: [PATCH] ath9k: AR9280 PCI devices must serialize IO as well To: johannes@sipsolutions.net, linville@tuxdriver.com Cc: linux-wireless@vger.kernel.org Signed-off-by: Luis R. Rodriguez --- drivers/net/wireless/ath9k/hw.c | 9 ++++++++- 1 files changed, 8 insertions(+), 1 deletions(-) diff --git a/drivers/net/wireless/ath9k/hw.c b/drivers/net/wireless/ath9k/hw.c index 005dd51..f46aa3f 100644 --- a/drivers/net/wireless/ath9k/hw.c +++ b/drivers/net/wireless/ath9k/hw.c @@ -658,8 +658,15 @@ static struct ath_hw *ath9k_hw_do_attach(u16 devid, struct ath_softc *sc, goto bad; } + /* + * All PCI devices should be put here. + * XXX: remove ah->is_pciexpress and use pdev->is_pcie, then + * we can just check for !pdev->is_pcie here, but + * consideration must be taken for handling AHB as well. + */ if (ah->config.serialize_regmode == SER_REG_MODE_AUTO) { - if (ah->hw_version.macVersion == AR_SREV_VERSION_5416_PCI) { + if (ah->hw_version.macVersion == AR_SREV_VERSION_5416_PCI || + (AR_SREV_9280(ah) && !ah->is_pciexpress)) { ah->config.serialize_regmode = SER_REG_MODE_ON; } else { -- 1.5.6.4 From 0306d1dba123b53b436b360b4b2554dc19784438 Mon Sep 17 00:00:00 2001 From: Luis R. Rodriguez Date: Tue, 10 Feb 2009 23:33:57 -0800 Subject: [PATCH] ath9k: add cpu notifier to enhance device configuration To: johannes@sipsolutions.net, linville@tuxdriver.com Cc: linux-wireless@vger.kernel.org Upon CPU Hotplug the number of CPUs can change and we can tweak our driver configuration as such. Keep in mind CPU hotplug also occurs during suspend/resume. Since we have a notifier now we can rely on num_present_cpus() rather than num_possible_cpus() to enable/disable serialization. Signed-off-by: Luis R. Rodriguez --- drivers/net/wireless/ath9k/ath9k.h | 6 ++++ drivers/net/wireless/ath9k/hw.c | 49 ++++++++++++++++++++++------------- drivers/net/wireless/ath9k/hw.h | 3 ++ drivers/net/wireless/ath9k/main.c | 29 +++++++++++++++++++++ drivers/net/wireless/ath9k/pci.c | 4 +++ 5 files changed, 73 insertions(+), 18 deletions(-) diff --git a/drivers/net/wireless/ath9k/ath9k.h b/drivers/net/wireless/ath9k/ath9k.h index 7245797..5eaa3bc 100644 --- a/drivers/net/wireless/ath9k/ath9k.h +++ b/drivers/net/wireless/ath9k/ath9k.h @@ -22,6 +22,7 @@ #include #include #include +#include #include "hw.h" #include "rc.h" @@ -566,9 +567,14 @@ struct ath_bus_ops { bool (*eeprom_read)(struct ath_hw *ah, u32 off, u16 *data); }; +int ath9k_cpu_callback(struct notifier_block *nfb, + unsigned long action, void *hcpu); + struct ath_softc { struct ieee80211_hw *hw; struct device *dev; + /* This should or _needs_ to be __cpuinit (?) */ + struct notifier_block cpu_notifer; struct tasklet_struct intr_tq; struct tasklet_struct bcon_tasklet; struct ath_hw *sc_ah; diff --git a/drivers/net/wireless/ath9k/hw.c b/drivers/net/wireless/ath9k/hw.c index f46aa3f..abcbe23 100644 --- a/drivers/net/wireless/ath9k/hw.c +++ b/drivers/net/wireless/ath9k/hw.c @@ -16,6 +16,7 @@ #include #include +#include #include "ath9k.h" #include "initvals.h" @@ -383,6 +384,35 @@ static const char *ath9k_hw_devname(u16 devid) return NULL; } +/* + * We can slap on to here things we need to tune depending + * on the number of CPUs. This will happen on CPU hotplug + * which is also used for suspend/resume. + */ +void ath9k_hw_config_for_cpus(struct ath_hw *ah) +{ + /* + * We need this for PCI devices only (Cardbus, PCI, miniPCI) + * _and_ if on non-uniprocessor systems (Multiprocessor/HT). + * This means we use it for all AR5416 devices, and the few + * minor PCI AR9280 devices out there. + * + * Serialization is required because these devices do not handle + * well the case of two concurrent reads/writes due to the latency + * involved. During one read/write another read/write can be issued + * on another CPU while the previous read/write may still be working + * on our hardware, if we hit this case the hardware poops in a loop. + * We prevent this by serializing reads and writes. + * + * This issue is not present on PCI-Express devices or pre-AR5416 + * devices (legacy, 802.11abg). + */ + if (num_present_cpus() > 1) + ah->config.serialize_regmode = SER_REG_MODE_AUTO; + else + ah->config.serialize_regmode = SER_REG_MODE_OFF; +} + static void ath9k_hw_set_defaults(struct ath_hw *ah) { int i; @@ -422,24 +452,7 @@ static void ath9k_hw_set_defaults(struct ath_hw *ah) ah->config.intr_mitigation = 1; - /* - * We need this for PCI devices only (Cardbus, PCI, miniPCI) - * _and_ if on non-uniprocessor systems (Multiprocessor/HT). - * This means we use it for all AR5416 devices, and the few - * minor PCI AR9280 devices out there. - * - * Serialization is required because these devices do not handle - * well the case of two concurrent reads/writes due to the latency - * involved. During one read/write another read/write can be issued - * on another CPU while the previous read/write may still be working - * on our hardware, if we hit this case the hardware poops in a loop. - * We prevent this by serializing reads and writes. - * - * This issue is not present on PCI-Express devices or pre-AR5416 - * devices (legacy, 802.11abg). - */ - if (num_possible_cpus() > 1) - ah->config.serialize_regmode = SER_REG_MODE_AUTO; + ath9k_hw_config_for_cpus(ah); } static struct ath_hw *ath9k_hw_newstate(u16 devid, struct ath_softc *sc, diff --git a/drivers/net/wireless/ath9k/hw.h b/drivers/net/wireless/ath9k/hw.h index 778137f..778fb3c 100644 --- a/drivers/net/wireless/ath9k/hw.h +++ b/drivers/net/wireless/ath9k/hw.h @@ -570,6 +570,9 @@ struct ath_hw { void ath9k_iowrite32(struct ath_hw *ah, u32 reg_offset, u32 val); unsigned int ath9k_ioread32(struct ath_hw *ah, u32 reg_offset); +/* Lets us tweak the device per CPU changes */ +void ath9k_hw_config_for_cpus(struct ath_hw *ah); + /* Attach, Detach, Reset */ const char *ath9k_hw_probe(u16 vendorid, u16 devid); void ath9k_hw_detach(struct ath_hw *ah); diff --git a/drivers/net/wireless/ath9k/main.c b/drivers/net/wireless/ath9k/main.c index d25907d..7823d37 100644 --- a/drivers/net/wireless/ath9k/main.c +++ b/drivers/net/wireless/ath9k/main.c @@ -2626,6 +2626,35 @@ struct ieee80211_ops ath9k_ops = { .ampdu_action = ath9k_ampdu_action, }; +int ath9k_cpu_callback(struct notifier_block *nfb, + unsigned long action, void *hcpu) +{ + struct ath_softc *sc = container_of(nfb, struct ath_softc, cpu_notifer); + int old_serial_mode; + + old_serial_mode = sc->sc_ah->config.serialize_regmode; + + switch (action) { + case CPU_ONLINE: + case CPU_ONLINE_FROZEN: + ath9k_hw_config_for_cpus(sc->sc_ah); + break; + case CPU_DEAD: + case CPU_DEAD_FROZEN: + ath9k_hw_config_for_cpus(sc->sc_ah); + break; + } + + if (unlikely(old_serial_mode != sc->sc_ah->config.serialize_regmode)) { + DPRINTF(sc, ATH_DBG_RESET, + "serialize_regmode has changed due to CPU " + "count to %d\n", + sc->sc_ah->config.serialize_regmode); + } + + return NOTIFY_OK; +} + static struct { u32 version; const char * name; diff --git a/drivers/net/wireless/ath9k/pci.c b/drivers/net/wireless/ath9k/pci.c index eea9d3a..8711bc4 100644 --- a/drivers/net/wireless/ath9k/pci.c +++ b/drivers/net/wireless/ath9k/pci.c @@ -175,6 +175,9 @@ static int ath_pci_probe(struct pci_dev *pdev, const struct pci_device_id *id) goto bad3; } + sc->cpu_notifer.notifier_call = ath9k_cpu_callback; + register_hotcpu_notifier(&sc->cpu_notifer); + /* setup interrupt service routine */ if (request_irq(pdev->irq, ath_isr, IRQF_SHARED, "ath", sc)) { @@ -216,6 +219,7 @@ static void ath_pci_remove(struct pci_dev *pdev) struct ieee80211_hw *hw = pci_get_drvdata(pdev); struct ath_softc *sc = hw->priv; + unregister_hotcpu_notifier(&sc->cpu_notifer); ath_cleanup(sc); } -- 1.5.6.4 From 427d0cbbbaa574b8d66d00c32320e574046c4003 Mon Sep 17 00:00:00 2001 From: Luis R. Rodriguez Date: Mon, 23 Feb 2009 13:50:48 -0800 Subject: [PATCH] ath9k: disable D1 and D2 on PCI multicore systems To: johannes@sipsolutions.net, linville@tuxdriver.com Cc: linux-wireless@vger.kernel.org This fixes a hang seen on PCI devices present with AR5416. Signed-off-by: Luis R. Rodriguez --- drivers/net/wireless/ath9k/pci.c | 42 ++++++++++++++++++++++++++++++++++++++ 1 files changed, 42 insertions(+), 0 deletions(-) diff --git a/drivers/net/wireless/ath9k/pci.c b/drivers/net/wireless/ath9k/pci.c index 8711bc4..c8bf28a 100644 --- a/drivers/net/wireless/ath9k/pci.c +++ b/drivers/net/wireless/ath9k/pci.c @@ -80,6 +80,44 @@ static struct ath_bus_ops ath_pci_bus_ops = { .eeprom_read = ath_pci_eeprom_read, }; +/** + * ath9k_pci_disable_d1_d2 - disable D1 and D2 from PCI config space + * @pdev: PCI atheros device + * + * 0x41 is in vendor PCI config space, for Atheros 11n this consists + * of the "Power Management Capabilities and Next Item Pointer": + * + * 15:8 Next item pointer + * 19 PMECLK - if 0 indicates PCI clock is not required + * for PME, otherwise its set to 1. + * 24:22 Vaux current requirements (as per PCI spec) + * 25 D1 state support - 0 is not supported, 1 is supported + * 26 D2 state support - 0 is not supported, 1 is supported + * 30:27 PME power support. Indicates states from which PME + * can be generated. + * Bit 0 - PME state D0 + * Bit 1 - PME state D1 + * Bit 2 - PME state D2 + * Bit 3 - PME state D3 + * + * It seems on some systems D1 (light sleep) and D2 (deep sleep), + * which are optional as per the PCI spec, can create issues on SMP + * systems with PCI AR5416 so we disable it so later pci_pm_init() + * will detect them as not supported. Although this is only currently + * seen on AR5416 the AR9280 PCI devices are rare so we just enable + * this for any PCI and multiple CPU box. + */ +static void ath9k_pci_disable_d1_d2(struct pci_dev *pdev) +{ + u32 val; + if (pdev->is_pcie || num_possible_cpus() == 1) + return; + pci_read_config_dword(pdev, 0x41, &val); + val &= ~( 1 << 25 | 1 << 26); + pci_write_config_byte(pdev, 0x41, val); + printk(KERN_DEBUG "ath9k: Disabling D1 and D2\n"); +} + static int ath_pci_probe(struct pci_dev *pdev, const struct pci_device_id *id) { void __iomem *mem; @@ -141,6 +179,8 @@ static int ath_pci_probe(struct pci_dev *pdev, const struct pci_device_id *id) if ((val & 0x0000ff00) != 0) pci_write_config_dword(pdev, 0x40, val & 0xffff00ff); + ath9k_pci_disable_d1_d2(pdev); + ret = pci_request_region(pdev, 0, "ath9k"); if (ret) { dev_err(&pdev->dev, "PCI memory region reserve error\n"); @@ -264,6 +304,8 @@ static int ath_pci_resume(struct pci_dev *pdev) if ((val & 0x0000ff00) != 0) pci_write_config_dword(pdev, 0x40, val & 0xffff00ff); + ath9k_pci_disable_d1_d2(pdev); + /* Enable LED */ ath9k_hw_cfg_output(sc->sc_ah, ATH_LED_PIN, AR_GPIO_OUTPUT_MUX_AS_OUTPUT); -- 1.5.6.4