e04b0ea2e0
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(-)
146 lines
2.7 KiB
C
146 lines
2.7 KiB
C
/*
|
|
* pci_syscall.c
|
|
*
|
|
* For architectures where we want to allow direct access
|
|
* to the PCI config stuff - it would probably be preferable
|
|
* on PCs too, but there people just do it by hand with the
|
|
* magic northbridge registers..
|
|
*/
|
|
|
|
#include <linux/sched.h>
|
|
#include <linux/errno.h>
|
|
#include <linux/pci.h>
|
|
#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,
|
|
unsigned long off, unsigned long len,
|
|
void __user *buf)
|
|
{
|
|
struct pci_dev *dev;
|
|
u8 byte;
|
|
u16 word;
|
|
u32 dword;
|
|
long err, cfg_ret;
|
|
|
|
err = -EPERM;
|
|
if (!capable(CAP_SYS_ADMIN))
|
|
goto error;
|
|
|
|
err = -ENODEV;
|
|
dev = pci_find_slot(bus, dfn);
|
|
if (!dev)
|
|
goto error;
|
|
|
|
lock_kernel();
|
|
switch (len) {
|
|
case 1:
|
|
cfg_ret = pci_user_read_config_byte(dev, off, &byte);
|
|
break;
|
|
case 2:
|
|
cfg_ret = pci_user_read_config_word(dev, off, &word);
|
|
break;
|
|
case 4:
|
|
cfg_ret = pci_user_read_config_dword(dev, off, &dword);
|
|
break;
|
|
default:
|
|
err = -EINVAL;
|
|
unlock_kernel();
|
|
goto error;
|
|
};
|
|
unlock_kernel();
|
|
|
|
err = -EIO;
|
|
if (cfg_ret != PCIBIOS_SUCCESSFUL)
|
|
goto error;
|
|
|
|
switch (len) {
|
|
case 1:
|
|
err = put_user(byte, (unsigned char __user *)buf);
|
|
break;
|
|
case 2:
|
|
err = put_user(word, (unsigned short __user *)buf);
|
|
break;
|
|
case 4:
|
|
err = put_user(dword, (unsigned int __user *)buf);
|
|
break;
|
|
};
|
|
return err;
|
|
|
|
error:
|
|
/* ??? XFree86 doesn't even check the return value. They
|
|
just look for 0xffffffff in the output, since that's what
|
|
they get instead of a machine check on x86. */
|
|
switch (len) {
|
|
case 1:
|
|
put_user(-1, (unsigned char __user *)buf);
|
|
break;
|
|
case 2:
|
|
put_user(-1, (unsigned short __user *)buf);
|
|
break;
|
|
case 4:
|
|
put_user(-1, (unsigned int __user *)buf);
|
|
break;
|
|
};
|
|
return err;
|
|
}
|
|
|
|
asmlinkage long
|
|
sys_pciconfig_write(unsigned long bus, unsigned long dfn,
|
|
unsigned long off, unsigned long len,
|
|
void __user *buf)
|
|
{
|
|
struct pci_dev *dev;
|
|
u8 byte;
|
|
u16 word;
|
|
u32 dword;
|
|
int err = 0;
|
|
|
|
if (!capable(CAP_SYS_ADMIN))
|
|
return -EPERM;
|
|
|
|
dev = pci_find_slot(bus, dfn);
|
|
if (!dev)
|
|
return -ENODEV;
|
|
|
|
lock_kernel();
|
|
switch(len) {
|
|
case 1:
|
|
err = get_user(byte, (u8 __user *)buf);
|
|
if (err)
|
|
break;
|
|
err = pci_user_write_config_byte(dev, off, byte);
|
|
if (err != PCIBIOS_SUCCESSFUL)
|
|
err = -EIO;
|
|
break;
|
|
|
|
case 2:
|
|
err = get_user(word, (u16 __user *)buf);
|
|
if (err)
|
|
break;
|
|
err = pci_user_write_config_word(dev, off, word);
|
|
if (err != PCIBIOS_SUCCESSFUL)
|
|
err = -EIO;
|
|
break;
|
|
|
|
case 4:
|
|
err = get_user(dword, (u32 __user *)buf);
|
|
if (err)
|
|
break;
|
|
err = pci_user_write_config_dword(dev, off, dword);
|
|
if (err != PCIBIOS_SUCCESSFUL)
|
|
err = -EIO;
|
|
break;
|
|
|
|
default:
|
|
err = -EINVAL;
|
|
break;
|
|
};
|
|
unlock_kernel();
|
|
|
|
return err;
|
|
}
|