diff mbox series

[v13,5/9] PCI: Allow userspace to query and set device reset mechanism

Message ID 20210801142518.1224-6-ameynarkhede03@gmail.com (mailing list archive)
State Superseded
Delegated to: Bjorn Helgaas
Headers show
Series PCI: Expose and manage PCI device reset | expand

Commit Message

ameynarkhede03 Aug. 1, 2021, 2:25 p.m. UTC
Add reset_method sysfs attribute to enable user to query and set user
preferred device reset methods and their ordering.

Co-developed-by: Alex Williamson <alex.williamson@redhat.com>
Signed-off-by: Alex Williamson <alex.williamson@redhat.com>
Signed-off-by: Amey Narkhede <ameynarkhede03@gmail.com>
---
 Documentation/ABI/testing/sysfs-bus-pci |  19 +++++
 drivers/pci/pci-sysfs.c                 |   1 +
 drivers/pci/pci.c                       | 105 ++++++++++++++++++++++++
 drivers/pci/pci.h                       |   2 +
 4 files changed, 127 insertions(+)

Comments

Bjorn Helgaas Aug. 2, 2021, 10:55 p.m. UTC | #1
On Sun, Aug 01, 2021 at 07:55:14PM +0530, Amey Narkhede wrote:
> Add reset_method sysfs attribute to enable user to query and set user
> preferred device reset methods and their ordering.
> 
> Co-developed-by: Alex Williamson <alex.williamson@redhat.com>
> Signed-off-by: Alex Williamson <alex.williamson@redhat.com>
> Signed-off-by: Amey Narkhede <ameynarkhede03@gmail.com>
> ---
>  Documentation/ABI/testing/sysfs-bus-pci |  19 +++++
>  drivers/pci/pci-sysfs.c                 |   1 +
>  drivers/pci/pci.c                       | 105 ++++++++++++++++++++++++
>  drivers/pci/pci.h                       |   2 +
>  4 files changed, 127 insertions(+)
> 
> diff --git a/Documentation/ABI/testing/sysfs-bus-pci b/Documentation/ABI/testing/sysfs-bus-pci
> index ef00fada2efb..ef66b62bf025 100644
> --- a/Documentation/ABI/testing/sysfs-bus-pci
> +++ b/Documentation/ABI/testing/sysfs-bus-pci
> @@ -121,6 +121,25 @@ Description:
>  		child buses, and re-discover devices removed earlier
>  		from this part of the device tree.
>  
> +What:		/sys/bus/pci/devices/.../reset_method
> +Date:		March 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 will be present in sysfs. Initially reading
> +		this file will give names of the device supported reset
> +		methods and their ordering. After write, this file will
> +		give names and ordering of currently enabled reset methods.
> +		Writing the name or space separated list of names of any of
> +		the device supported reset methods to this file will set
> +		the reset methods and their ordering to be used when
> +		resetting the device. Writing empty string to this file
> +		will disable ability to reset the device and writing
> +		"default" will return to the original value.
> +
>  What:		/sys/bus/pci/devices/.../reset
>  Date:		July 2009
>  Contact:	Michael S. Tsirkin <mst@redhat.com>
> diff --git a/drivers/pci/pci-sysfs.c b/drivers/pci/pci-sysfs.c
> index 316f70c3e3b4..54ee7193b463 100644
> --- a/drivers/pci/pci-sysfs.c
> +++ b/drivers/pci/pci-sysfs.c
> @@ -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,
> diff --git a/drivers/pci/pci.c b/drivers/pci/pci.c
> index 932dd21e759b..c496cd164aca 100644
> --- a/drivers/pci/pci.c
> +++ b/drivers/pci/pci.c
> @@ -5132,6 +5132,111 @@ 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)

Looks like "buf" would fit on the previous line.

> +{
> +	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 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);
> +	int i = 0;
> +	char *name, *options = NULL;
> +
> +	if (count >= (PAGE_SIZE - 1))
> +		return -EINVAL;
> +
> +	if (sysfs_streq(buf, "")) {
> +		pdev->reset_methods[0] = 0;
> +		pci_warn(pdev, "All device reset methods disabled by user");
> +		return count;
> +	}

I think it's possible for the user to disable all reset methods by
supplying only junk.  Maybe this check could be moved to the end of
the function to catch both the "empty input" and the "input contains
only junk" cases?

> +	if (sysfs_streq(buf, "default")) {
> +		pci_init_reset_methods(pdev);
> +		return count;
> +	}
> +
> +	options = kstrndup(buf, count, GFP_KERNEL);
> +	if (!options)
> +		return -ENOMEM;
> +

  i = 0;

here so it's nearer the loop it controls.

> +	while ((name = strsep(&options, " ")) != NULL) {
> +		int m;
> +
> +		if (sysfs_streq(name, ""))
> +			continue;
> +
> +		name = strim(name);
> +
> +		for (m = 1; m < PCI_NUM_RESET_METHODS && i < PCI_NUM_RESET_METHODS; m++) {
> +			if (sysfs_streq(name, pci_reset_fn_methods[m].name) &&
> +			    !pci_reset_fn_methods[m].reset_fn(pdev, 1)) {
> +				pdev->reset_methods[i++] = m;
> +				break;
> +			}
> +		}
> +
> +		if (m == PCI_NUM_RESET_METHODS) {
> +			kfree(options);
> +			return -EINVAL;

In this case, I think we have actually updated pdev->reset_methods[],
but we still return -EINVAL, right?  If we decide to silently ignore
unrecognized methods, we probably should return success here.

> +
> +		}
> +	}
> +
> +	if (i < PCI_NUM_RESET_METHODS)
> +		pdev->reset_methods[i] = 0;
> +
> +	if (!pci_reset_fn_methods[1].reset_fn(pdev, 1) && pdev->reset_methods[0] != 1)

Looks longer than 80 columns?  "Fixed" by rewrapping in patch 9/9, but
would be better to fix here.

> +		pci_warn(pdev, "Device specific reset disabled/de-prioritized by user");
> +
> +	kfree(options);
> +
> +	return count;
> +}
> +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.
> diff --git a/drivers/pci/pci.h b/drivers/pci/pci.h
> index 7438953745e0..31458d48eda7 100644
> --- a/drivers/pci/pci.h
> +++ b/drivers/pci/pci.h
> @@ -714,4 +714,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 */
> -- 
> 2.32.0
>
ameynarkhede03 Aug. 3, 2021, 6:34 a.m. UTC | #2
On 21/08/02 05:55PM, Bjorn Helgaas wrote:
> On Sun, Aug 01, 2021 at 07:55:14PM +0530, Amey Narkhede wrote:
> > Add reset_method sysfs attribute to enable user to query and set user
> > preferred device reset methods and their ordering.
> >
> > Co-developed-by: Alex Williamson <alex.williamson@redhat.com>
> > Signed-off-by: Alex Williamson <alex.williamson@redhat.com>
> > Signed-off-by: Amey Narkhede <ameynarkhede03@gmail.com>
> > ---
> > +
[...]
> > +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);
> > +	int i = 0;
> > +	char *name, *options = NULL;
> > +
> > +	if (count >= (PAGE_SIZE - 1))
> > +		return -EINVAL;
> > +
> > +	if (sysfs_streq(buf, "")) {
> > +		pdev->reset_methods[0] = 0;
> > +		pci_warn(pdev, "All device reset methods disabled by user");
> > +		return count;
> > +	}
>
> I think it's possible for the user to disable all reset methods by
> supplying only junk.  Maybe this check could be moved to the end of
> the function to catch both the "empty input" and the "input contains
> only junk" cases?
>
Supplying only junk doesn't disable the reset. It returns -EINVAL as it
will go in following while loop. The check m == PCI_NUM_RESET_METHODS
returns -EINVAL

> > +	if (sysfs_streq(buf, "default")) {
> > +		pci_init_reset_methods(pdev);
> > +		return count;
> > +	}
> > +
> > +	options = kstrndup(buf, count, GFP_KERNEL);
> > +	if (!options)
> > +		return -ENOMEM;
> > +
>
>   i = 0;
>
> here so it's nearer the loop it controls.
>
> > +	while ((name = strsep(&options, " ")) != NULL) {
> > +		int m;
> > +
> > +		if (sysfs_streq(name, ""))
> > +			continue;
> > +
> > +		name = strim(name);
> > +
> > +		for (m = 1; m < PCI_NUM_RESET_METHODS && i < PCI_NUM_RESET_METHODS; m++) {
> > +			if (sysfs_streq(name, pci_reset_fn_methods[m].name) &&
> > +			    !pci_reset_fn_methods[m].reset_fn(pdev, 1)) {
> > +				pdev->reset_methods[i++] = m;
> > +				break;
> > +			}
> > +		}
> > +
> > +		if (m == PCI_NUM_RESET_METHODS) {
> > +			kfree(options);
> > +			return -EINVAL;
>
> In this case, I think we have actually updated pdev->reset_methods[],
> but we still return -EINVAL, right?  If we decide to silently ignore
> unrecognized methods, we probably should return success here.
>
Is it okay to do that? I hope it won't cause any trouble for user
scripts

Thanks,
Amey

[...]
Shanker Donthineni Aug. 3, 2021, 1:45 p.m. UTC | #3
On 8/1/21 9:25 AM, Amey Narkhede wrote:
> External email: Use caution opening links or attachments
>
>
> Add reset_method sysfs attribute to enable user to query and set user
> preferred device reset methods and their ordering.
>
> Co-developed-by: Alex Williamson <alex.williamson@redhat.com>
> Signed-off-by: Alex Williamson <alex.williamson@redhat.com>
> Signed-off-by: Amey Narkhede <ameynarkhede03@gmail.com>
> ---
>  Documentation/ABI/testing/sysfs-bus-pci |  19 +++++
>  drivers/pci/pci-sysfs.c                 |   1 +
>  drivers/pci/pci.c                       | 105 ++++++++++++++++++++++++
>  drivers/pci/pci.h                       |   2 +
>  4 files changed, 127 insertions(+)
>
> diff --git a/Documentation/ABI/testing/sysfs-bus-pci b/Documentation/ABI/testing/sysfs-bus-pci
> index ef00fada2efb..ef66b62bf025 100644
> --- a/Documentation/ABI/testing/sysfs-bus-pci
> +++ b/Documentation/ABI/testing/sysfs-bus-pci
> @@ -121,6 +121,25 @@ Description:
>                 child buses, and re-discover devices removed earlier
>                 from this part of the device tree.
>
> +What:          /sys/bus/pci/devices/.../reset_method
> +Date:          March 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 will be present in sysfs. Initially reading
> +               this file will give names of the device supported reset
> +               methods and their ordering. After write, this file will
> +               give names and ordering of currently enabled reset methods.
> +               Writing the name or space separated list of names of any of
> +               the device supported reset methods to this file will set
> +               the reset methods and their ordering to be used when
> +               resetting the device. Writing empty string to this file
> +               will disable ability to reset the device and writing
> +               "default" will return to the original value.
> +
>  What:          /sys/bus/pci/devices/.../reset
>  Date:          July 2009
>  Contact:       Michael S. Tsirkin <mst@redhat.com>
> diff --git a/drivers/pci/pci-sysfs.c b/drivers/pci/pci-sysfs.c
> index 316f70c3e3b4..54ee7193b463 100644
> --- a/drivers/pci/pci-sysfs.c
> +++ b/drivers/pci/pci-sysfs.c
> @@ -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,
> diff --git a/drivers/pci/pci.c b/drivers/pci/pci.c
> index 932dd21e759b..c496cd164aca 100644
> --- a/drivers/pci/pci.c
> +++ b/drivers/pci/pci.c
> @@ -5132,6 +5132,111 @@ 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 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);
> +       int i = 0;
> +       char *name, *options = NULL;
> +
> +       if (count >= (PAGE_SIZE - 1))
> +               return -EINVAL;
> +
> +       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;
> +
> +       while ((name = strsep(&options, " ")) != NULL) {
> +               int m;
> +
> +               if (sysfs_streq(name, ""))
> +                       continue;
> +
> +               name = strim(name);
> +
> +               for (m = 1; m < PCI_NUM_RESET_METHODS && i < PCI_NUM_RESET_METHODS; m++) {
> +                       if (sysfs_streq(name, pci_reset_fn_methods[m].name) &&
> +                           !pci_reset_fn_methods[m].reset_fn(pdev, 1)) {
> +                               pdev->reset_methods[i++] = m;
> +                               break;
> +                       }
> +               }
> +
Checking reset method logic isn't optimized, iterating through all entries if the
device doesn't support a requested method.

Something like this:
        for (m = 1; m < PCI_NUM_RESET_METHODS && i < PCI_NUM_RESET_METHODS; m++) {
                if (!sysfs_streq(name, pci_reset_fn_methods[m].name))
                        continue;
                if(!pci_reset_fn_methods[m].reset_fn(pdev, 1))
                        pdev->reset_methods[i++] = m;
                break;
        }

I think we should avoid duplicate entries in pdev->reset_methods.
Example:
   root# cat reset_method
   acpi flr bus

   root# echo "acpi flr bus flr" > reset_method
   root# cat reset_method
   acpi flr bus flr




> +               if (m == PCI_NUM_RESET_METHODS) {
> +                       kfree(options);
> +                       return -EINVAL;
Set the last entry to zero in pdev->reset_methods otherwise the inconsistent
methods are enabled.
Example:
   root# cat reset_method
   acpi flr bus

   root# echo "flr a" > reset_method
   root# cat reset_method
   flr flr bus

> +
> +               }
> +       }
> +
> +       if (i < PCI_NUM_RESET_METHODS)
> +               pdev->reset_methods[i] = 0;
> +
Last entry can be set unconditionally after removing the duplicate entries.
Refactored code to filter duplicate entries and warn the user about the invalid
& unsupported reset methods.

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 *name, *options = NULL;
        int i, m, n = 0;

        if (count >= (PAGE_SIZE - 1))
                return -EINVAL;

        if (sysfs_streq(buf, ""))
                goto done;

        if (sysfs_streq(buf, "default")) {
                pci_init_reset_methods(pdev);
                return count;
        }

        options = kstrndup(buf, count, GFP_KERNEL);
        if (!options)
                return -ENOMEM;

        while ((name = strsep(&options, " ")) != NULL) {
                if (sysfs_streq(name, ""))
                        continue;
                name = strim(name);

                /* Validate reset method */
                for (m = 1; m < PCI_NUM_RESET_METHODS; m++) {
                        if (sysfs_streq(name, pci_reset_fn_methods[m].name))
                                break;
                }
                if (m == PCI_NUM_RESET_METHODS) {
                        pci_warn(pdev, "Skip invalid reset method '%s'", name);
                        continue;
                }

                /* Check if the reset method is already enabled */
                for (i = 0; i < n; i++) {
                        if (pdev->reset_methods[i] == m)
                                break;
                }
                if (i < n)
                        continue;

                /* Probe the requested reset method */
                if (pci_reset_fn_methods[m].reset_fn(pdev, 1))
                        pci_warn(pdev, "Unsupported reset method '%s'", name);

                pdev->reset_methods[n++] = m;
                BUG_ON(n == PCI_NUM_RESET_METHODS)
       }
        kfree(options);

done:
        pdev->reset_methods[n] = 0;
        if (pdev->reset_methods[0] == 0) {
                pci_warn(pdev, "All device reset methods are disabled");
        } else if ((pdev->reset_methods[0] != 1) &&
                   !pci_reset_fn_methods[1].reset_fn(pdev, 1)) {
                pci_warn(pdev, "Device specific reset disabled/de-prioritized by user");
        }
        return count;
}
diff mbox series

Patch

diff --git a/Documentation/ABI/testing/sysfs-bus-pci b/Documentation/ABI/testing/sysfs-bus-pci
index ef00fada2efb..ef66b62bf025 100644
--- a/Documentation/ABI/testing/sysfs-bus-pci
+++ b/Documentation/ABI/testing/sysfs-bus-pci
@@ -121,6 +121,25 @@  Description:
 		child buses, and re-discover devices removed earlier
 		from this part of the device tree.
 
+What:		/sys/bus/pci/devices/.../reset_method
+Date:		March 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 will be present in sysfs. Initially reading
+		this file will give names of the device supported reset
+		methods and their ordering. After write, this file will
+		give names and ordering of currently enabled reset methods.
+		Writing the name or space separated list of names of any of
+		the device supported reset methods to this file will set
+		the reset methods and their ordering to be used when
+		resetting the device. Writing empty string to this file
+		will disable ability to reset the device and writing
+		"default" will return to the original value.
+
 What:		/sys/bus/pci/devices/.../reset
 Date:		July 2009
 Contact:	Michael S. Tsirkin <mst@redhat.com>
diff --git a/drivers/pci/pci-sysfs.c b/drivers/pci/pci-sysfs.c
index 316f70c3e3b4..54ee7193b463 100644
--- a/drivers/pci/pci-sysfs.c
+++ b/drivers/pci/pci-sysfs.c
@@ -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,
diff --git a/drivers/pci/pci.c b/drivers/pci/pci.c
index 932dd21e759b..c496cd164aca 100644
--- a/drivers/pci/pci.c
+++ b/drivers/pci/pci.c
@@ -5132,6 +5132,111 @@  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 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);
+	int i = 0;
+	char *name, *options = NULL;
+
+	if (count >= (PAGE_SIZE - 1))
+		return -EINVAL;
+
+	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;
+
+	while ((name = strsep(&options, " ")) != NULL) {
+		int m;
+
+		if (sysfs_streq(name, ""))
+			continue;
+
+		name = strim(name);
+
+		for (m = 1; m < PCI_NUM_RESET_METHODS && i < PCI_NUM_RESET_METHODS; m++) {
+			if (sysfs_streq(name, pci_reset_fn_methods[m].name) &&
+			    !pci_reset_fn_methods[m].reset_fn(pdev, 1)) {
+				pdev->reset_methods[i++] = m;
+				break;
+			}
+		}
+
+		if (m == PCI_NUM_RESET_METHODS) {
+			kfree(options);
+			return -EINVAL;
+
+		}
+	}
+
+	if (i < PCI_NUM_RESET_METHODS)
+		pdev->reset_methods[i] = 0;
+
+	if (!pci_reset_fn_methods[1].reset_fn(pdev, 1) && pdev->reset_methods[0] != 1)
+		pci_warn(pdev, "Device specific reset disabled/de-prioritized by user");
+
+	kfree(options);
+
+	return count;
+}
+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.
diff --git a/drivers/pci/pci.h b/drivers/pci/pci.h
index 7438953745e0..31458d48eda7 100644
--- a/drivers/pci/pci.h
+++ b/drivers/pci/pci.h
@@ -714,4 +714,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 */