PCI: Allow userspace to query and set device reset mechanism
authorAmey Narkhede <ameynarkhede03@gmail.com>
Tue, 17 Aug 2021 18:04:56 +0000 (23:34 +0530)
committerBjorn Helgaas <bhelgaas@google.com>
Wed, 18 Aug 2021 22:03:44 +0000 (17:03 -0500)
Add "reset_method" sysfs attribute to enable user to query and set
preferred device reset methods and their ordering.

[bhelgaas: on invalid sysfs input, return error and preserve previous
config, as in earlier patch versions]
Co-developed-by: Alex Williamson <alex.williamson@redhat.com>
Link: https://lore.kernel.org/r/20210817180500.1253-6-ameynarkhede03@gmail.com
Signed-off-by: Alex Williamson <alex.williamson@redhat.com>
Signed-off-by: Amey Narkhede <ameynarkhede03@gmail.com>
Signed-off-by: Bjorn Helgaas <bhelgaas@google.com>
Reviewed-by: Raphael Norwitz <raphael.norwitz@nutanix.com>
Documentation/ABI/testing/sysfs-bus-pci
drivers/pci/pci-sysfs.c
drivers/pci/pci.c
drivers/pci/pci.h

index 793cbb76cd2505d4365fddff9f485c609fc4a16d..d4ae03296861e4710e7befe7527937c5e165050b 100644 (file)
@@ -121,6 +121,23 @@ Description:
                child buses, and re-discover devices removed earlier
                from this part of the device tree.
 
+What:          /sys/bus/pci/devices/.../reset_method
+Date:          August 2021
+Contact:       Amey Narkhede <ameynarkhede03@gmail.com>
+Description:
+               Some devices allow an individual function to be reset
+               without affecting other functions in the same slot.
+
+               For devices that have this support, a file named
+               reset_method is present in sysfs.  Reading this file
+               gives names of the supported and enabled reset methods and
+               their ordering.  Writing a space-separated list of names of
+               reset methods sets the reset methods and ordering to be
+               used when resetting the device.  Writing an empty string
+               disables the ability to reset the device.  Writing
+               "default" enables all supported reset methods in the
+               default ordering.
+
 What:          /sys/bus/pci/devices/.../reset
 Date:          July 2009
 Contact:       Michael S. Tsirkin <mst@redhat.com>
index a1d9b0e83615a644a3530e6c7ffbaf2cb44ec241..2fd44c97b7ec09402f0685975adf9c1f28f955fc 100644 (file)
@@ -1491,6 +1491,7 @@ const struct attribute_group *pci_dev_groups[] = {
        &pci_dev_config_attr_group,
        &pci_dev_rom_attr_group,
        &pci_dev_reset_attr_group,
+       &pci_dev_reset_method_attr_group,
        &pci_dev_vpd_attr_group,
 #ifdef CONFIG_DMI
        &pci_dev_smbios_attr_group,
index 5ead8826c702c675f19ce66f8e1441d7208ce636..6da5f6d87f6a33d1dc7a4f0b92d305180f852021 100644 (file)
@@ -5132,6 +5132,128 @@ static const struct pci_reset_fn_method pci_reset_fn_methods[] = {
        { pci_reset_bus_function, .name = "bus" },
 };
 
+static ssize_t reset_method_show(struct device *dev,
+                                struct device_attribute *attr, char *buf)
+{
+       struct pci_dev *pdev = to_pci_dev(dev);
+       ssize_t len = 0;
+       int i, m;
+
+       for (i = 0; i < PCI_NUM_RESET_METHODS; i++) {
+               m = pdev->reset_methods[i];
+               if (!m)
+                       break;
+
+               len += sysfs_emit_at(buf, len, "%s%s", len ? " " : "",
+                                    pci_reset_fn_methods[m].name);
+       }
+
+       if (len)
+               len += sysfs_emit_at(buf, len, "\n");
+
+       return len;
+}
+
+static int reset_method_lookup(const char *name)
+{
+       int m;
+
+       for (m = 1; m < PCI_NUM_RESET_METHODS; m++) {
+               if (sysfs_streq(name, pci_reset_fn_methods[m].name))
+                       return m;
+       }
+
+       return 0;       /* not found */
+}
+
+static ssize_t reset_method_store(struct device *dev,
+                                 struct device_attribute *attr,
+                                 const char *buf, size_t count)
+{
+       struct pci_dev *pdev = to_pci_dev(dev);
+       char *options, *name;
+       int m, n;
+       u8 reset_methods[PCI_NUM_RESET_METHODS] = { 0 };
+
+       if (sysfs_streq(buf, "")) {
+               pdev->reset_methods[0] = 0;
+               pci_warn(pdev, "All device reset methods disabled by user");
+               return count;
+       }
+
+       if (sysfs_streq(buf, "default")) {
+               pci_init_reset_methods(pdev);
+               return count;
+       }
+
+       options = kstrndup(buf, count, GFP_KERNEL);
+       if (!options)
+               return -ENOMEM;
+
+       n = 0;
+       while ((name = strsep(&options, " ")) != NULL) {
+               if (sysfs_streq(name, ""))
+                       continue;
+
+               name = strim(name);
+
+               m = reset_method_lookup(name);
+               if (!m) {
+                       pci_err(pdev, "Invalid reset method '%s'", name);
+                       goto error;
+               }
+
+               if (pci_reset_fn_methods[m].reset_fn(pdev, 1)) {
+                       pci_err(pdev, "Unsupported reset method '%s'", name);
+                       goto error;
+               }
+
+               if (n == PCI_NUM_RESET_METHODS - 1) {
+                       pci_err(pdev, "Too many reset methods\n");
+                       goto error;
+               }
+
+               reset_methods[n++] = m;
+       }
+
+       reset_methods[n] = 0;
+
+       /* Warn if dev-specific supported but not highest priority */
+       if (pci_reset_fn_methods[1].reset_fn(pdev, 1) == 0 &&
+           reset_methods[0] != 1)
+               pci_warn(pdev, "Device-specific reset disabled/de-prioritized by user");
+       memcpy(pdev->reset_methods, reset_methods, sizeof(pdev->reset_methods));
+       kfree(options);
+       return count;
+
+error:
+       /* Leave previous methods unchanged */
+       kfree(options);
+       return -EINVAL;
+}
+static DEVICE_ATTR_RW(reset_method);
+
+static struct attribute *pci_dev_reset_method_attrs[] = {
+       &dev_attr_reset_method.attr,
+       NULL,
+};
+
+static umode_t pci_dev_reset_method_attr_is_visible(struct kobject *kobj,
+                                                   struct attribute *a, int n)
+{
+       struct pci_dev *pdev = to_pci_dev(kobj_to_dev(kobj));
+
+       if (!pci_reset_supported(pdev))
+               return 0;
+
+       return a->mode;
+}
+
+const struct attribute_group pci_dev_reset_method_attr_group = {
+       .attrs = pci_dev_reset_method_attrs,
+       .is_visible = pci_dev_reset_method_attr_is_visible,
+};
+
 /**
  * __pci_reset_function_locked - reset a PCI device function while holding
  * the @dev mutex lock.
index ebeacb3dbe1ee9f3ea0e0473cca5bf86938af052..b31afd4669cc05fb577cf5df07dbd968446e0d57 100644 (file)
@@ -718,4 +718,6 @@ static inline int pci_acpi_program_hp_params(struct pci_dev *dev)
 extern const struct attribute_group aspm_ctrl_attr_group;
 #endif
 
+extern const struct attribute_group pci_dev_reset_method_attr_group;
+
 #endif /* DRIVERS_PCI_H */