PCI: expose function reset capability in sysfs
authorMichael S. Tsirkin <mst@redhat.com>
Mon, 27 Jul 2009 20:37:48 +0000 (23:37 +0300)
committerJesse Barnes <jbarnes@virtuousgeek.org>
Wed, 9 Sep 2009 20:29:24 +0000 (13:29 -0700)
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 <gregkh@suse.de>
Signed-off-by: Michael S. Tsirkin <mst@redhat.com>
Signed-off-by: Jesse Barnes <jbarnes@virtuousgeek.org>
Documentation/ABI/testing/sysfs-bus-pci
drivers/pci/pci-sysfs.c
drivers/pci/pci.c
drivers/pci/pci.h
include/linux/pci.h

index 6bf68053e4b817491f615c0d36e787e74120bf38..25be3250f7d66f17c0a4d88974441ced8f4ca633 100644 (file)
@@ -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 <mst@redhat.com>
+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 <bhutchings@solarflare.com>
index 85ebd02a64a7903bece3330703f72801466cde28..0f6382f090ee575f04add353066be31abbb05336 100644 (file)
@@ -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;
+       }
 }
 
 /**
index 7b70312181d78a28cdfba11639c673626ee8eed6..7d55039ffa050b35cb785d49df667f5d0b20f2f2 100644 (file)
@@ -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
index 5ff4d25bf0e938f28904f22b46b5c6505239af8e..73d9d92715a07c7284b83431240d7c691b43c989 100644 (file)
@@ -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
index 115fb7ba508907ce85704f1f2a9eb7bac5b9be4c..a90f940207982618e424ec19d017dd163a32be78 100644 (file)
@@ -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 */