[PATCH] PCI: Block config access during BIST
authorBrian King <brking@us.ibm.com>
Tue, 27 Sep 2005 08:21:55 +0000 (01:21 -0700)
committerGreg Kroah-Hartman <gregkh@suse.de>
Fri, 28 Oct 2005 22:36:58 +0000 (15:36 -0700)
Some PCI adapters (eg.  ipr scsi adapters) have an exposure today in that they
issue BIST to the adapter to reset the card.  If, during the time it takes to
complete BIST, userspace attempts to access PCI config space, the host bus
bridge will master abort the access since the ipr adapter does not respond on
the PCI bus for a brief period of time when running BIST.  On PPC64 hardware,
this master abort results in the host PCI bridge isolating that PCI device
from the rest of the system, making the device unusable until Linux is
rebooted.  This patch is an attempt to close that exposure by introducing some
blocking code in the PCI code.  When blocked, writes will be humored and reads
will return the cached value.  Ben Herrenschmidt has also mentioned that he
plans to use this in PPC power management.

Signed-off-by: Brian King <brking@us.ibm.com>
Cc: Benjamin Herrenschmidt <benh@kernel.crashing.org>
Signed-off-by: Andrew Morton <akpm@osdl.org>
Signed-off-by: Greg Kroah-Hartman <gregkh@suse.de>
 drivers/pci/access.c    |   89 ++++++++++++++++++++++++++++++++++++++++++++++++
 drivers/pci/pci-sysfs.c |   20 +++++-----
 drivers/pci/pci.h       |    7 +++
 drivers/pci/proc.c      |   28 +++++++--------
 drivers/pci/syscall.c   |   14 +++----
 include/linux/pci.h     |    7 +++
 6 files changed, 134 insertions(+), 31 deletions(-)

drivers/pci/access.c
drivers/pci/pci-sysfs.c
drivers/pci/pci.h
drivers/pci/proc.c
drivers/pci/syscall.c
include/linux/pci.h

index 24a76de49f410b91315ea8291f39ee55077dd5a0..2a42add7f56356d87f0ca4d74f1f8521975603b4 100644 (file)
@@ -60,3 +60,92 @@ EXPORT_SYMBOL(pci_bus_read_config_dword);
 EXPORT_SYMBOL(pci_bus_write_config_byte);
 EXPORT_SYMBOL(pci_bus_write_config_word);
 EXPORT_SYMBOL(pci_bus_write_config_dword);
+
+static u32 pci_user_cached_config(struct pci_dev *dev, int pos)
+{
+       u32 data;
+
+       data = dev->saved_config_space[pos/sizeof(dev->saved_config_space[0])];
+       data >>= (pos % sizeof(dev->saved_config_space[0])) * 8;
+       return data;
+}
+
+#define PCI_USER_READ_CONFIG(size,type)                                        \
+int pci_user_read_config_##size                                                \
+       (struct pci_dev *dev, int pos, type *val)                       \
+{                                                                      \
+       unsigned long flags;                                            \
+       int ret = 0;                                                    \
+       u32 data = -1;                                                  \
+       if (PCI_##size##_BAD) return PCIBIOS_BAD_REGISTER_NUMBER;       \
+       spin_lock_irqsave(&pci_lock, flags);                            \
+       if (likely(!dev->block_ucfg_access))                            \
+               ret = dev->bus->ops->read(dev->bus, dev->devfn,         \
+                                       pos, sizeof(type), &data);      \
+       else if (pos < sizeof(dev->saved_config_space))                 \
+               data = pci_user_cached_config(dev, pos);                \
+       spin_unlock_irqrestore(&pci_lock, flags);                       \
+       *val = (type)data;                                              \
+       return ret;                                                     \
+}
+
+#define PCI_USER_WRITE_CONFIG(size,type)                               \
+int pci_user_write_config_##size                                       \
+       (struct pci_dev *dev, int pos, type val)                        \
+{                                                                      \
+       unsigned long flags;                                            \
+       int ret = -EIO;                                                 \
+       if (PCI_##size##_BAD) return PCIBIOS_BAD_REGISTER_NUMBER;       \
+       spin_lock_irqsave(&pci_lock, flags);                            \
+       if (likely(!dev->block_ucfg_access))                            \
+               ret = dev->bus->ops->write(dev->bus, dev->devfn,        \
+                                       pos, sizeof(type), val);        \
+       spin_unlock_irqrestore(&pci_lock, flags);                       \
+       return ret;                                                     \
+}
+
+PCI_USER_READ_CONFIG(byte, u8)
+PCI_USER_READ_CONFIG(word, u16)
+PCI_USER_READ_CONFIG(dword, u32)
+PCI_USER_WRITE_CONFIG(byte, u8)
+PCI_USER_WRITE_CONFIG(word, u16)
+PCI_USER_WRITE_CONFIG(dword, u32)
+
+/**
+ * pci_block_user_cfg_access - Block userspace PCI config reads/writes
+ * @dev:       pci device struct
+ *
+ * This function blocks any userspace PCI config accesses from occurring.
+ * When blocked, any writes will be bit bucketed and reads will return the
+ * data saved using pci_save_state for the first 64 bytes of config
+ * space and return 0xff for all other config reads.
+ **/
+void pci_block_user_cfg_access(struct pci_dev *dev)
+{
+       unsigned long flags;
+
+       pci_save_state(dev);
+
+       /* spinlock to synchronize with anyone reading config space now */
+       spin_lock_irqsave(&pci_lock, flags);
+       dev->block_ucfg_access = 1;
+       spin_unlock_irqrestore(&pci_lock, flags);
+}
+EXPORT_SYMBOL_GPL(pci_block_user_cfg_access);
+
+/**
+ * pci_unblock_user_cfg_access - Unblock userspace PCI config reads/writes
+ * @dev:       pci device struct
+ *
+ * This function allows userspace PCI config accesses to resume.
+ **/
+void pci_unblock_user_cfg_access(struct pci_dev *dev)
+{
+       unsigned long flags;
+
+       /* spinlock to synchronize with anyone reading saved config space */
+       spin_lock_irqsave(&pci_lock, flags);
+       dev->block_ucfg_access = 0;
+       spin_unlock_irqrestore(&pci_lock, flags);
+}
+EXPORT_SYMBOL_GPL(pci_unblock_user_cfg_access);
index 2898830c496fca15ae10e77ea09d2b4839a20d0c..965a5934623a30b19740308c7d31a5bd954bd322 100644 (file)
@@ -130,7 +130,7 @@ pci_read_config(struct kobject *kobj, char *buf, loff_t off, size_t count)
 
        if ((off & 1) && size) {
                u8 val;
-               pci_read_config_byte(dev, off, &val);
+               pci_user_read_config_byte(dev, off, &val);
                data[off - init_off] = val;
                off++;
                size--;
@@ -138,7 +138,7 @@ pci_read_config(struct kobject *kobj, char *buf, loff_t off, size_t count)
 
        if ((off & 3) && size > 2) {
                u16 val;
-               pci_read_config_word(dev, off, &val);
+               pci_user_read_config_word(dev, off, &val);
                data[off - init_off] = val & 0xff;
                data[off - init_off + 1] = (val >> 8) & 0xff;
                off += 2;
@@ -147,7 +147,7 @@ pci_read_config(struct kobject *kobj, char *buf, loff_t off, size_t count)
 
        while (size > 3) {
                u32 val;
-               pci_read_config_dword(dev, off, &val);
+               pci_user_read_config_dword(dev, off, &val);
                data[off - init_off] = val & 0xff;
                data[off - init_off + 1] = (val >> 8) & 0xff;
                data[off - init_off + 2] = (val >> 16) & 0xff;
@@ -158,7 +158,7 @@ pci_read_config(struct kobject *kobj, char *buf, loff_t off, size_t count)
 
        if (size >= 2) {
                u16 val;
-               pci_read_config_word(dev, off, &val);
+               pci_user_read_config_word(dev, off, &val);
                data[off - init_off] = val & 0xff;
                data[off - init_off + 1] = (val >> 8) & 0xff;
                off += 2;
@@ -167,7 +167,7 @@ pci_read_config(struct kobject *kobj, char *buf, loff_t off, size_t count)
 
        if (size > 0) {
                u8 val;
-               pci_read_config_byte(dev, off, &val);
+               pci_user_read_config_byte(dev, off, &val);
                data[off - init_off] = val;
                off++;
                --size;
@@ -192,7 +192,7 @@ pci_write_config(struct kobject *kobj, char *buf, loff_t off, size_t count)
        }
        
        if ((off & 1) && size) {
-               pci_write_config_byte(dev, off, data[off - init_off]);
+               pci_user_write_config_byte(dev, off, data[off - init_off]);
                off++;
                size--;
        }
@@ -200,7 +200,7 @@ pci_write_config(struct kobject *kobj, char *buf, loff_t off, size_t count)
        if ((off & 3) && size > 2) {
                u16 val = data[off - init_off];
                val |= (u16) data[off - init_off + 1] << 8;
-                pci_write_config_word(dev, off, val);
+                pci_user_write_config_word(dev, off, val);
                 off += 2;
                 size -= 2;
         }
@@ -210,7 +210,7 @@ pci_write_config(struct kobject *kobj, char *buf, loff_t off, size_t count)
                val |= (u32) data[off - init_off + 1] << 8;
                val |= (u32) data[off - init_off + 2] << 16;
                val |= (u32) data[off - init_off + 3] << 24;
-               pci_write_config_dword(dev, off, val);
+               pci_user_write_config_dword(dev, off, val);
                off += 4;
                size -= 4;
        }
@@ -218,13 +218,13 @@ pci_write_config(struct kobject *kobj, char *buf, loff_t off, size_t count)
        if (size >= 2) {
                u16 val = data[off - init_off];
                val |= (u16) data[off - init_off + 1] << 8;
-               pci_write_config_word(dev, off, val);
+               pci_user_write_config_word(dev, off, val);
                off += 2;
                size -= 2;
        }
 
        if (size) {
-               pci_write_config_byte(dev, off, data[off - init_off]);
+               pci_user_write_config_byte(dev, off, data[off - init_off]);
                off++;
                --size;
        }
index d3f3dd42240d79b52c0fdecf5e95333f17131246..6527b36c9a61d2254b5915adfc344205369de2c7 100644 (file)
@@ -15,6 +15,13 @@ extern int pci_bus_alloc_resource(struct pci_bus *bus, struct resource *res,
 extern int (*platform_pci_choose_state)(struct pci_dev *dev, pm_message_t state);
 extern int (*platform_pci_set_power_state)(struct pci_dev *dev, pci_power_t state);
 
+extern int pci_user_read_config_byte(struct pci_dev *dev, int where, u8 *val);
+extern int pci_user_read_config_word(struct pci_dev *dev, int where, u16 *val);
+extern int pci_user_read_config_dword(struct pci_dev *dev, int where, u32 *val);
+extern int pci_user_write_config_byte(struct pci_dev *dev, int where, u8 val);
+extern int pci_user_write_config_word(struct pci_dev *dev, int where, u16 val);
+extern int pci_user_write_config_dword(struct pci_dev *dev, int where, u32 val);
+
 /* PCI /proc functions */
 #ifdef CONFIG_PROC_FS
 extern int pci_proc_attach_device(struct pci_dev *dev);
index 9613f666c110b9561705afb38e5508229eee519d..9eb465727fce41431a29083f8ee6d4b1f2b97442 100644 (file)
@@ -80,7 +80,7 @@ proc_bus_pci_read(struct file *file, char __user *buf, size_t nbytes, loff_t *pp
 
        if ((pos & 1) && cnt) {
                unsigned char val;
-               pci_read_config_byte(dev, pos, &val);
+               pci_user_read_config_byte(dev, pos, &val);
                __put_user(val, buf);
                buf++;
                pos++;
@@ -89,7 +89,7 @@ proc_bus_pci_read(struct file *file, char __user *buf, size_t nbytes, loff_t *pp
 
        if ((pos & 3) && cnt > 2) {
                unsigned short val;
-               pci_read_config_word(dev, pos, &val);
+               pci_user_read_config_word(dev, pos, &val);
                __put_user(cpu_to_le16(val), (unsigned short __user *) buf);
                buf += 2;
                pos += 2;
@@ -98,7 +98,7 @@ proc_bus_pci_read(struct file *file, char __user *buf, size_t nbytes, loff_t *pp
 
        while (cnt >= 4) {
                unsigned int val;
-               pci_read_config_dword(dev, pos, &val);
+               pci_user_read_config_dword(dev, pos, &val);
                __put_user(cpu_to_le32(val), (unsigned int __user *) buf);
                buf += 4;
                pos += 4;
@@ -107,7 +107,7 @@ proc_bus_pci_read(struct file *file, char __user *buf, size_t nbytes, loff_t *pp
 
        if (cnt >= 2) {
                unsigned short val;
-               pci_read_config_word(dev, pos, &val);
+               pci_user_read_config_word(dev, pos, &val);
                __put_user(cpu_to_le16(val), (unsigned short __user *) buf);
                buf += 2;
                pos += 2;
@@ -116,7 +116,7 @@ proc_bus_pci_read(struct file *file, char __user *buf, size_t nbytes, loff_t *pp
 
        if (cnt) {
                unsigned char val;
-               pci_read_config_byte(dev, pos, &val);
+               pci_user_read_config_byte(dev, pos, &val);
                __put_user(val, buf);
                buf++;
                pos++;
@@ -151,7 +151,7 @@ proc_bus_pci_write(struct file *file, const char __user *buf, size_t nbytes, lof
        if ((pos & 1) && cnt) {
                unsigned char val;
                __get_user(val, buf);
-               pci_write_config_byte(dev, pos, val);
+               pci_user_write_config_byte(dev, pos, val);
                buf++;
                pos++;
                cnt--;
@@ -160,7 +160,7 @@ proc_bus_pci_write(struct file *file, const char __user *buf, size_t nbytes, lof
        if ((pos & 3) && cnt > 2) {
                unsigned short val;
                __get_user(val, (unsigned short __user *) buf);
-               pci_write_config_word(dev, pos, le16_to_cpu(val));
+               pci_user_write_config_word(dev, pos, le16_to_cpu(val));
                buf += 2;
                pos += 2;
                cnt -= 2;
@@ -169,7 +169,7 @@ proc_bus_pci_write(struct file *file, const char __user *buf, size_t nbytes, lof
        while (cnt >= 4) {
                unsigned int val;
                __get_user(val, (unsigned int __user *) buf);
-               pci_write_config_dword(dev, pos, le32_to_cpu(val));
+               pci_user_write_config_dword(dev, pos, le32_to_cpu(val));
                buf += 4;
                pos += 4;
                cnt -= 4;
@@ -178,7 +178,7 @@ proc_bus_pci_write(struct file *file, const char __user *buf, size_t nbytes, lof
        if (cnt >= 2) {
                unsigned short val;
                __get_user(val, (unsigned short __user *) buf);
-               pci_write_config_word(dev, pos, le16_to_cpu(val));
+               pci_user_write_config_word(dev, pos, le16_to_cpu(val));
                buf += 2;
                pos += 2;
                cnt -= 2;
@@ -187,7 +187,7 @@ proc_bus_pci_write(struct file *file, const char __user *buf, size_t nbytes, lof
        if (cnt) {
                unsigned char val;
                __get_user(val, buf);
-               pci_write_config_byte(dev, pos, val);
+               pci_user_write_config_byte(dev, pos, val);
                buf++;
                pos++;
                cnt--;
@@ -484,10 +484,10 @@ static int show_dev_config(struct seq_file *m, void *v)
 
        drv = pci_dev_driver(dev);
 
-       pci_read_config_dword(dev, PCI_CLASS_REVISION, &class_rev);
-       pci_read_config_byte (dev, PCI_LATENCY_TIMER, &latency);
-       pci_read_config_byte (dev, PCI_MIN_GNT, &min_gnt);
-       pci_read_config_byte (dev, PCI_MAX_LAT, &max_lat);
+       pci_user_read_config_dword(dev, PCI_CLASS_REVISION, &class_rev);
+       pci_user_read_config_byte (dev, PCI_LATENCY_TIMER, &latency);
+       pci_user_read_config_byte (dev, PCI_MIN_GNT, &min_gnt);
+       pci_user_read_config_byte (dev, PCI_MAX_LAT, &max_lat);
        seq_printf(m, "  Bus %2d, device %3d, function %2d:\n",
               dev->bus->number, PCI_SLOT(dev->devfn), PCI_FUNC(dev->devfn));
        seq_printf(m, "    Class %04x", class_rev >> 16);
index c071790cc9830da6e53a56d022010b05cd18c3be..87fafc08cb9d6a3f31f861c29d260d842ae141d0 100644 (file)
@@ -13,7 +13,7 @@
 #include <linux/smp_lock.h>
 #include <linux/syscalls.h>
 #include <asm/uaccess.h>
-
+#include "pci.h"
 
 asmlinkage long
 sys_pciconfig_read(unsigned long bus, unsigned long dfn,
@@ -38,13 +38,13 @@ sys_pciconfig_read(unsigned long bus, unsigned long dfn,
        lock_kernel();
        switch (len) {
        case 1:
-               cfg_ret = pci_read_config_byte(dev, off, &byte);
+               cfg_ret = pci_user_read_config_byte(dev, off, &byte);
                break;
        case 2:
-               cfg_ret = pci_read_config_word(dev, off, &word);
+               cfg_ret = pci_user_read_config_word(dev, off, &word);
                break;
        case 4:
-               cfg_ret = pci_read_config_dword(dev, off, &dword);
+               cfg_ret = pci_user_read_config_dword(dev, off, &dword);
                break;
        default:
                err = -EINVAL;
@@ -112,7 +112,7 @@ sys_pciconfig_write(unsigned long bus, unsigned long dfn,
                err = get_user(byte, (u8 __user *)buf);
                if (err)
                        break;
-               err = pci_write_config_byte(dev, off, byte);
+               err = pci_user_write_config_byte(dev, off, byte);
                if (err != PCIBIOS_SUCCESSFUL)
                        err = -EIO;
                break;
@@ -121,7 +121,7 @@ sys_pciconfig_write(unsigned long bus, unsigned long dfn,
                err = get_user(word, (u16 __user *)buf);
                if (err)
                        break;
-               err = pci_write_config_word(dev, off, word);
+               err = pci_user_write_config_word(dev, off, word);
                if (err != PCIBIOS_SUCCESSFUL)
                        err = -EIO;
                break;
@@ -130,7 +130,7 @@ sys_pciconfig_write(unsigned long bus, unsigned long dfn,
                err = get_user(dword, (u32 __user *)buf);
                if (err)
                        break;
-               err = pci_write_config_dword(dev, off, dword);
+               err = pci_user_write_config_dword(dev, off, dword);
                if (err != PCIBIOS_SUCCESSFUL)
                        err = -EIO;
                break;
index 7349058ed7789b6417cd198203371b3463267880..3596ac94ecff74916f88499fb0e4bfa02a1fe1ff 100644 (file)
@@ -132,6 +132,7 @@ struct pci_dev {
        unsigned int    is_enabled:1;   /* pci_enable_device has been called */
        unsigned int    is_busmaster:1; /* device is busmaster */
        unsigned int    no_msi:1;       /* device may not use msi */
+       unsigned int    block_ucfg_access:1;    /* userspace config space access is blocked */
 
        u32             saved_config_space[16]; /* config space saved at suspend time */
        struct bin_attribute *rom_attr; /* attribute descriptor for sysfs ROM entry */
@@ -490,6 +491,9 @@ extern void pci_disable_msix(struct pci_dev *dev);
 extern void msi_remove_pci_irq_vectors(struct pci_dev *dev);
 #endif
 
+extern void pci_block_user_cfg_access(struct pci_dev *dev);
+extern void pci_unblock_user_cfg_access(struct pci_dev *dev);
+
 /*
  * PCI domain support.  Sometimes called PCI segment (eg by ACPI),
  * a PCI domain is defined to be a set of PCI busses which share
@@ -560,6 +564,9 @@ static inline int pci_enable_wake(struct pci_dev *dev, pci_power_t state, int en
 
 #define pci_dma_burst_advice(pdev, strat, strategy_parameter) do { } while (0)
 
+static inline void pci_block_user_cfg_access(struct pci_dev *dev) { }
+static inline void pci_unblock_user_cfg_access(struct pci_dev *dev) { }
+
 #endif /* CONFIG_PCI */
 
 /* Include architecture-dependent settings and functions */