From 711d57796f5ce2d02d6e62c9034afbb16aedda31 Mon Sep 17 00:00:00 2001 From: "Michael S. Tsirkin" Date: Mon, 27 Jul 2009 23:37:48 +0300 Subject: [PATCH] PCI: expose function reset capability in sysfs Some devices allow an individual function to be reset without affecting other functions in the same device: that's what pci_reset_function does. For devices that have this support, expose reset attribite in sysfs. This is useful e.g. for virtualization, where a qemu userspace process wants to reset the device when the guest is reset, to emulate machine reboot as closely as possible. Acked-by: Greg Kroah-Hartman Signed-off-by: Michael S. Tsirkin Signed-off-by: Jesse Barnes --- Documentation/ABI/testing/sysfs-bus-pci | 10 +++++++ drivers/pci/pci-sysfs.c | 37 +++++++++++++++++++++++++ drivers/pci/pci.c | 16 +++++++++++ drivers/pci/pci.h | 1 + include/linux/pci.h | 1 + 5 files changed, 65 insertions(+) diff --git a/Documentation/ABI/testing/sysfs-bus-pci b/Documentation/ABI/testing/sysfs-bus-pci index 6bf68053e4b8..25be3250f7d6 100644 --- a/Documentation/ABI/testing/sysfs-bus-pci +++ b/Documentation/ABI/testing/sysfs-bus-pci @@ -84,6 +84,16 @@ Description: from this part of the device tree. Depends on CONFIG_HOTPLUG. +What: /sys/bus/pci/devices/.../reset +Date: July 2009 +Contact: Michael S. Tsirkin +Description: + Some devices allow an individual function to be reset + without affecting other functions in the same device. + For devices that have this support, a file named reset + will be present in sysfs. Writing 1 to this file + will perform reset. + What: /sys/bus/pci/devices/.../vpd Date: February 2008 Contact: Ben Hutchings diff --git a/drivers/pci/pci-sysfs.c b/drivers/pci/pci-sysfs.c index 85ebd02a64a7..0f6382f090ee 100644 --- a/drivers/pci/pci-sysfs.c +++ b/drivers/pci/pci-sysfs.c @@ -916,6 +916,24 @@ int __attribute__ ((weak)) pcibios_add_platform_entries(struct pci_dev *dev) return 0; } +static ssize_t reset_store(struct device *dev, + struct device_attribute *attr, const char *buf, + size_t count) +{ + struct pci_dev *pdev = to_pci_dev(dev); + unsigned long val; + ssize_t result = strict_strtoul(buf, 0, &val); + + if (result < 0) + return result; + + if (val != 1) + return -EINVAL; + return pci_reset_function(pdev); +} + +static struct device_attribute reset_attr = __ATTR(reset, 0200, NULL, reset_store); + static int pci_create_capabilities_sysfs(struct pci_dev *dev) { int retval; @@ -943,7 +961,22 @@ static int pci_create_capabilities_sysfs(struct pci_dev *dev) /* Active State Power Management */ pcie_aspm_create_sysfs_dev_files(dev); + if (!pci_probe_reset_function(dev)) { + retval = device_create_file(&dev->dev, &reset_attr); + if (retval) + goto error; + dev->reset_fn = 1; + } return 0; + +error: + pcie_aspm_remove_sysfs_dev_files(dev); + if (dev->vpd && dev->vpd->attr) { + sysfs_remove_bin_file(&dev->dev.kobj, dev->vpd->attr); + kfree(dev->vpd->attr); + } + + return retval; } int __must_check pci_create_sysfs_dev_files (struct pci_dev *pdev) @@ -1037,6 +1070,10 @@ static void pci_remove_capabilities_sysfs(struct pci_dev *dev) } pcie_aspm_remove_sysfs_dev_files(dev); + if (dev->reset_fn) { + device_remove_file(&dev->dev, &reset_attr); + dev->reset_fn = 0; + } } /** diff --git a/drivers/pci/pci.c b/drivers/pci/pci.c index 7b70312181d7..7d55039ffa05 100644 --- a/drivers/pci/pci.c +++ b/drivers/pci/pci.c @@ -2261,6 +2261,22 @@ int __pci_reset_function(struct pci_dev *dev) } EXPORT_SYMBOL_GPL(__pci_reset_function); +/** + * pci_probe_reset_function - check whether the device can be safely reset + * @dev: PCI device to reset + * + * Some devices allow an individual function to be reset without affecting + * other functions in the same device. The PCI device must be responsive + * to PCI config space in order to use this function. + * + * Returns 0 if the device function can be reset or negative if the + * device doesn't support resetting a single function. + */ +int pci_probe_reset_function(struct pci_dev *dev) +{ + return pci_dev_reset(dev, 1); +} + /** * pci_reset_function - quiesce and reset a PCI device function * @dev: PCI device to reset diff --git a/drivers/pci/pci.h b/drivers/pci/pci.h index 5ff4d25bf0e9..73d9d92715a0 100644 --- a/drivers/pci/pci.h +++ b/drivers/pci/pci.h @@ -16,6 +16,7 @@ extern void pci_cleanup_rom(struct pci_dev *dev); extern int pci_mmap_fits(struct pci_dev *pdev, int resno, struct vm_area_struct *vma); #endif +int pci_probe_reset_function(struct pci_dev *dev); /** * struct pci_platform_pm_ops - Firmware PM callbacks diff --git a/include/linux/pci.h b/include/linux/pci.h index 115fb7ba5089..a90f94020798 100644 --- a/include/linux/pci.h +++ b/include/linux/pci.h @@ -276,6 +276,7 @@ struct pci_dev { unsigned int state_saved:1; unsigned int is_physfn:1; unsigned int is_virtfn:1; + unsigned int reset_fn:1; pci_dev_flags_t dev_flags; atomic_t enable_cnt; /* pci_enable_device has been called */