Commit d88f521d authored by Amey Narkhede's avatar Amey Narkhede Committed by Bjorn Helgaas
Browse files

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: default avatarAlex Williamson <alex.williamson@redhat.com>
Link: https://lore.kernel.org/r/20210817180500.1253-6-ameynarkhede03@gmail.com


Signed-off-by: default avatarAlex Williamson <alex.williamson@redhat.com>
Signed-off-by: default avatarAmey Narkhede <ameynarkhede03@gmail.com>
Signed-off-by: default avatarBjorn Helgaas <bhelgaas@google.com>
Reviewed-by: default avatarRaphael Norwitz <raphael.norwitz@nutanix.com>
parent 4ec36dfe
Loading
Loading
Loading
Loading
+17 −0
Original line number Diff line number Diff line
@@ -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>
+1 −0
Original line number Diff line number Diff line
@@ -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,
+122 −0
Original line number Diff line number Diff line
@@ -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.
+2 −0
Original line number Diff line number Diff line
@@ -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 */