diff options
author | Alex Williamson <alex.williamson@redhat.com> | 2010-07-19 09:45:34 -0600 |
---|---|---|
committer | Jesse Barnes <jbarnes@virtuousgeek.org> | 2010-07-30 09:32:08 -0700 |
commit | 8633328be242677fdedc42052838dd0608e7f342 (patch) | |
tree | 20b16af605298a1d05973d7ec2d0a479412ed3e6 /drivers/pci | |
parent | 2491762cfb475dbdfa3db11ebea6de49f58b7fac (diff) | |
download | linux-8633328be242677fdedc42052838dd0608e7f342.tar.bz2 |
PCI: Allow read/write access to sysfs I/O port resources
PCI sysfs resource files currently only allow mmap'ing. On x86 this
works fine for memory backed BARs, but doesn't work at all for I/O
port backed BARs. Add read/write to I/O port PCI sysfs resource
files to allow userspace access to these device regions.
Acked-by: Chris Wright <chrisw@redhat.com>
Signed-off-by: Alex Williamson <alex.williamson@redhat.com>
Signed-off-by: Jesse Barnes <jbarnes@virtuousgeek.org>
Diffstat (limited to 'drivers/pci')
-rw-r--r-- | drivers/pci/pci-sysfs.c | 68 |
1 files changed, 68 insertions, 0 deletions
diff --git a/drivers/pci/pci-sysfs.c b/drivers/pci/pci-sysfs.c index 5935b854917f..f7692dc531e4 100644 --- a/drivers/pci/pci-sysfs.c +++ b/drivers/pci/pci-sysfs.c @@ -778,6 +778,70 @@ pci_mmap_resource_wc(struct file *filp, struct kobject *kobj, return pci_mmap_resource(kobj, attr, vma, 1); } +static ssize_t +pci_resource_io(struct file *filp, struct kobject *kobj, + struct bin_attribute *attr, char *buf, + loff_t off, size_t count, bool write) +{ + struct pci_dev *pdev = to_pci_dev(container_of(kobj, + struct device, kobj)); + struct resource *res = attr->private; + unsigned long port = off; + int i; + + for (i = 0; i < PCI_ROM_RESOURCE; i++) + if (res == &pdev->resource[i]) + break; + if (i >= PCI_ROM_RESOURCE) + return -ENODEV; + + port += pci_resource_start(pdev, i); + + if (port > pci_resource_end(pdev, i)) + return 0; + + if (port + count - 1 > pci_resource_end(pdev, i)) + return -EINVAL; + + switch (count) { + case 1: + if (write) + outb(*(u8 *)buf, port); + else + *(u8 *)buf = inb(port); + return 1; + case 2: + if (write) + outw(*(u16 *)buf, port); + else + *(u16 *)buf = inw(port); + return 2; + case 4: + if (write) + outl(*(u32 *)buf, port); + else + *(u32 *)buf = inl(port); + return 4; + } + return -EINVAL; +} + +static ssize_t +pci_read_resource_io(struct file *filp, struct kobject *kobj, + struct bin_attribute *attr, char *buf, + loff_t off, size_t count) +{ + return pci_resource_io(filp, kobj, attr, buf, off, count, false); +} + +static ssize_t +pci_write_resource_io(struct file *filp, struct kobject *kobj, + struct bin_attribute *attr, char *buf, + loff_t off, size_t count) +{ + return pci_resource_io(filp, kobj, attr, buf, off, count, true); +} + /** * pci_remove_resource_files - cleanup resource files * @pdev: dev to cleanup @@ -828,6 +892,10 @@ static int pci_create_attr(struct pci_dev *pdev, int num, int write_combine) sprintf(res_attr_name, "resource%d", num); res_attr->mmap = pci_mmap_resource_uc; } + if (pci_resource_flags(pdev, num) & IORESOURCE_IO) { + res_attr->read = pci_read_resource_io; + res_attr->write = pci_write_resource_io; + } res_attr->attr.name = res_attr_name; res_attr->attr.mode = S_IRUSR | S_IWUSR; res_attr->size = pci_resource_len(pdev, num); |