From 342d1a74cc310c7010d8b14da4456abc7d4682bf 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 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 0b0f82c..559e695 100644 --- a/drivers/net/wireless/ath9k/ath9k.h +++ b/drivers/net/wireless/ath9k/ath9k.h @@ -577,6 +577,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 cad8e39..bdbdefd 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 (!in_irq() && ah->config.serialize_regmode == SER_REG_MODE_ON) { + 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 (!in_irq() && ah->config.serialize_regmode == SER_REG_MODE_ON) { + 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 */ /********************/ @@ -389,6 +419,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 8211163..0dfa18b 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) @@ -564,6 +564,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 fc3460f..b554712 100644 --- a/drivers/net/wireless/ath9k/main.c +++ b/drivers/net/wireless/ath9k/main.c @@ -1357,6 +1357,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.3 From c929e0aad310d71446ac7a75e6ca030c27aae03a 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 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 bdbdefd..2bf0bc2 100644 --- a/drivers/net/wireless/ath9k/hw.c +++ b/drivers/net/wireless/ath9k/hw.c @@ -656,8 +656,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.3 From 2fe8896dc2514e2c15e647de5b28f38b957c8300 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 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 | 3 ++ 5 files changed, 72 insertions(+), 18 deletions(-) diff --git a/drivers/net/wireless/ath9k/ath9k.h b/drivers/net/wireless/ath9k/ath9k.h index 559e695..b00b13f 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" @@ -568,9 +569,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 2bf0bc2..b7bee01 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" @@ -381,6 +382,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; @@ -420,24 +450,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 0dfa18b..b6c94bd 100644 --- a/drivers/net/wireless/ath9k/hw.h +++ b/drivers/net/wireless/ath9k/hw.h @@ -568,6 +568,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 b554712..6dfb695 100644 --- a/drivers/net/wireless/ath9k/main.c +++ b/drivers/net/wireless/ath9k/main.c @@ -2611,6 +2611,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 c28afe4..4fe7d9c 100644 --- a/drivers/net/wireless/ath9k/pci.c +++ b/drivers/net/wireless/ath9k/pci.c @@ -174,6 +174,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)) { -- 1.5.6.3