PCI: Allow userspace to query and set device reset mechanism

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>
This commit is contained in:
Amey Narkhede 2021-08-17 23:34:56 +05:30 committed by Bjorn Helgaas
parent 4ec36dfeb1
commit d88f521da3
4 changed files with 142 additions and 0 deletions

View 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>

View 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,

View 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.

View 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 */