mirror of
				https://github.com/torvalds/linux.git
				synced 2025-11-04 10:40:15 +02:00 
			
		
		
		
	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:
		
							parent
							
								
									4ec36dfeb1
								
							
						
					
					
						commit
						d88f521da3
					
				
					 4 changed files with 142 additions and 0 deletions
				
			
		| 
						 | 
				
			
			@ -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>
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -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,
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -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.
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -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 */
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
		Loading…
	
		Reference in a new issue