diff options
Diffstat (limited to 'drivers/pci/pci-sysfs.c')
-rw-r--r-- | drivers/pci/pci-sysfs.c | 238 |
1 files changed, 196 insertions, 42 deletions
diff --git a/drivers/pci/pci-sysfs.c b/drivers/pci/pci-sysfs.c index 40cfa716392f..268c69daa4d5 100644 --- a/drivers/pci/pci-sysfs.c +++ b/drivers/pci/pci-sysfs.c @@ -13,6 +13,7 @@ */ #include <linux/bitfield.h> +#include <linux/cleanup.h> #include <linux/kernel.h> #include <linux/sched.h> #include <linux/pci.h> @@ -31,6 +32,10 @@ #include <linux/aperture.h> #include "pci.h" +#ifndef ARCH_PCI_DEV_GROUPS +#define ARCH_PCI_DEV_GROUPS +#endif + static int sysfs_initialized; /* = 0 */ /* show configuration fields */ @@ -517,6 +522,31 @@ static ssize_t bus_rescan_store(struct device *dev, static struct device_attribute dev_attr_bus_rescan = __ATTR(rescan, 0200, NULL, bus_rescan_store); +static ssize_t reset_subordinate_store(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t count) +{ + struct pci_dev *pdev = to_pci_dev(dev); + struct pci_bus *bus = pdev->subordinate; + unsigned long val; + + if (!capable(CAP_SYS_ADMIN)) + return -EPERM; + + if (kstrtoul(buf, 0, &val) < 0) + return -EINVAL; + + if (val) { + int ret = __pci_reset_bus(bus); + + if (ret) + return ret; + } + + return count; +} +static DEVICE_ATTR_WO(reset_subordinate); + #if defined(CONFIG_PM) && defined(CONFIG_ACPI) static ssize_t d3cold_allowed_store(struct device *dev, struct device_attribute *attr, @@ -621,6 +651,7 @@ static struct attribute *pci_dev_attrs[] = { static struct attribute *pci_bridge_attrs[] = { &dev_attr_subordinate_bus_number.attr, &dev_attr_secondary_bus_number.attr, + &dev_attr_reset_subordinate.attr, NULL, }; @@ -664,7 +695,7 @@ static ssize_t boot_vga_show(struct device *dev, struct device_attribute *attr, static DEVICE_ATTR_RO(boot_vga); static ssize_t pci_read_config(struct file *filp, struct kobject *kobj, - struct bin_attribute *bin_attr, char *buf, + const struct bin_attribute *bin_attr, char *buf, loff_t off, size_t count) { struct pci_dev *dev = to_pci_dev(kobj_to_dev(kobj)); @@ -739,7 +770,7 @@ static ssize_t pci_read_config(struct file *filp, struct kobject *kobj, } static ssize_t pci_write_config(struct file *filp, struct kobject *kobj, - struct bin_attribute *bin_attr, char *buf, + const struct bin_attribute *bin_attr, char *buf, loff_t off, size_t count) { struct pci_dev *dev = to_pci_dev(kobj_to_dev(kobj)); @@ -807,28 +838,27 @@ static ssize_t pci_write_config(struct file *filp, struct kobject *kobj, return count; } -static BIN_ATTR(config, 0644, pci_read_config, pci_write_config, 0); +static const BIN_ATTR(config, 0644, pci_read_config, pci_write_config, 0); -static struct bin_attribute *pci_dev_config_attrs[] = { +static const struct bin_attribute *const pci_dev_config_attrs[] = { &bin_attr_config, NULL, }; -static umode_t pci_dev_config_attr_is_visible(struct kobject *kobj, - struct bin_attribute *a, int n) +static size_t pci_dev_config_attr_bin_size(struct kobject *kobj, + const struct bin_attribute *a, + int n) { struct pci_dev *pdev = to_pci_dev(kobj_to_dev(kobj)); - a->size = PCI_CFG_SPACE_SIZE; if (pdev->cfg_size > PCI_CFG_SPACE_SIZE) - a->size = PCI_CFG_SPACE_EXP_SIZE; - - return a->attr.mode; + return PCI_CFG_SPACE_EXP_SIZE; + return PCI_CFG_SPACE_SIZE; } static const struct attribute_group pci_dev_config_attr_group = { - .bin_attrs = pci_dev_config_attrs, - .is_bin_visible = pci_dev_config_attr_is_visible, + .bin_attrs_new = pci_dev_config_attrs, + .bin_size = pci_dev_config_attr_bin_size, }; /* @@ -838,7 +868,7 @@ static const struct attribute_group pci_dev_config_attr_group = { static __maybe_unused loff_t pci_llseek_resource(struct file *filep, struct kobject *kobj __always_unused, - struct bin_attribute *attr, + const struct bin_attribute *attr, loff_t offset, int whence) { return fixed_size_llseek(filep, offset, whence, attr->size); @@ -858,8 +888,8 @@ pci_llseek_resource(struct file *filep, * callback routine (pci_legacy_read). */ static ssize_t pci_read_legacy_io(struct file *filp, struct kobject *kobj, - struct bin_attribute *bin_attr, char *buf, - loff_t off, size_t count) + const struct bin_attribute *bin_attr, + char *buf, loff_t off, size_t count) { struct pci_bus *bus = to_pci_bus(kobj_to_dev(kobj)); @@ -883,8 +913,8 @@ static ssize_t pci_read_legacy_io(struct file *filp, struct kobject *kobj, * callback routine (pci_legacy_write). */ static ssize_t pci_write_legacy_io(struct file *filp, struct kobject *kobj, - struct bin_attribute *bin_attr, char *buf, - loff_t off, size_t count) + const struct bin_attribute *bin_attr, + char *buf, loff_t off, size_t count) { struct pci_bus *bus = to_pci_bus(kobj_to_dev(kobj)); @@ -907,7 +937,7 @@ static ssize_t pci_write_legacy_io(struct file *filp, struct kobject *kobj, * memory space. */ static int pci_mmap_legacy_mem(struct file *filp, struct kobject *kobj, - struct bin_attribute *attr, + const struct bin_attribute *attr, struct vm_area_struct *vma) { struct pci_bus *bus = to_pci_bus(kobj_to_dev(kobj)); @@ -927,7 +957,7 @@ static int pci_mmap_legacy_mem(struct file *filp, struct kobject *kobj, * memory space. Returns -ENOSYS if the operation isn't supported */ static int pci_mmap_legacy_io(struct file *filp, struct kobject *kobj, - struct bin_attribute *attr, + const struct bin_attribute *attr, struct vm_area_struct *vma) { struct pci_bus *bus = to_pci_bus(kobj_to_dev(kobj)); @@ -974,8 +1004,8 @@ void pci_create_legacy_files(struct pci_bus *b) b->legacy_io->attr.name = "legacy_io"; b->legacy_io->size = 0xffff; b->legacy_io->attr.mode = 0600; - b->legacy_io->read = pci_read_legacy_io; - b->legacy_io->write = pci_write_legacy_io; + b->legacy_io->read_new = pci_read_legacy_io; + b->legacy_io->write_new = pci_write_legacy_io; /* See pci_create_attr() for motivation */ b->legacy_io->llseek = pci_llseek_resource; b->legacy_io->mmap = pci_mmap_legacy_io; @@ -1031,7 +1061,7 @@ void pci_remove_legacy_files(struct pci_bus *b) * * Use the regular PCI mapping routines to map a PCI resource into userspace. */ -static int pci_mmap_resource(struct kobject *kobj, struct bin_attribute *attr, +static int pci_mmap_resource(struct kobject *kobj, const struct bin_attribute *attr, struct vm_area_struct *vma, int write_combine) { struct pci_dev *pdev = to_pci_dev(kobj_to_dev(kobj)); @@ -1056,21 +1086,21 @@ static int pci_mmap_resource(struct kobject *kobj, struct bin_attribute *attr, } static int pci_mmap_resource_uc(struct file *filp, struct kobject *kobj, - struct bin_attribute *attr, + const struct bin_attribute *attr, struct vm_area_struct *vma) { return pci_mmap_resource(kobj, attr, vma, 0); } static int pci_mmap_resource_wc(struct file *filp, struct kobject *kobj, - struct bin_attribute *attr, + const struct bin_attribute *attr, struct vm_area_struct *vma) { return pci_mmap_resource(kobj, attr, vma, 1); } static ssize_t pci_resource_io(struct file *filp, struct kobject *kobj, - struct bin_attribute *attr, char *buf, + const struct bin_attribute *attr, char *buf, loff_t off, size_t count, bool write) { #ifdef CONFIG_HAS_IOPORT @@ -1113,14 +1143,14 @@ static ssize_t pci_resource_io(struct file *filp, struct kobject *kobj, } static ssize_t pci_read_resource_io(struct file *filp, struct kobject *kobj, - struct bin_attribute *attr, char *buf, + const struct bin_attribute *attr, char *buf, loff_t off, size_t count) { return pci_resource_io(filp, kobj, attr, buf, off, count, false); } static ssize_t pci_write_resource_io(struct file *filp, struct kobject *kobj, - struct bin_attribute *attr, char *buf, + const struct bin_attribute *attr, char *buf, loff_t off, size_t count) { int ret; @@ -1181,8 +1211,8 @@ static int pci_create_attr(struct pci_dev *pdev, int num, int write_combine) } else { sprintf(res_attr_name, "resource%d", num); if (pci_resource_flags(pdev, num) & IORESOURCE_IO) { - res_attr->read = pci_read_resource_io; - res_attr->write = pci_write_resource_io; + res_attr->read_new = pci_read_resource_io; + res_attr->write_new = pci_write_resource_io; if (arch_can_pci_mmap_io()) res_attr->mmap = pci_mmap_resource_uc; } else { @@ -1227,6 +1257,10 @@ static int pci_create_resource_files(struct pci_dev *pdev) int i; int retval; + /* Skip devices with non-mappable BARs */ + if (pdev->non_mappable_bars) + return 0; + /* Expose the PCI resources from this device as files */ for (i = 0; i < PCI_STD_NUM_BARS; i++) { @@ -1263,7 +1297,7 @@ void __weak pci_remove_resource_files(struct pci_dev *dev) { return; } * writing anything except 0 enables it */ static ssize_t pci_write_rom(struct file *filp, struct kobject *kobj, - struct bin_attribute *bin_attr, char *buf, + const struct bin_attribute *bin_attr, char *buf, loff_t off, size_t count) { struct pci_dev *pdev = to_pci_dev(kobj_to_dev(kobj)); @@ -1289,7 +1323,7 @@ static ssize_t pci_write_rom(struct file *filp, struct kobject *kobj, * device corresponding to @kobj. */ static ssize_t pci_read_rom(struct file *filp, struct kobject *kobj, - struct bin_attribute *bin_attr, char *buf, + const struct bin_attribute *bin_attr, char *buf, loff_t off, size_t count) { struct pci_dev *pdev = to_pci_dev(kobj_to_dev(kobj)); @@ -1315,32 +1349,37 @@ static ssize_t pci_read_rom(struct file *filp, struct kobject *kobj, return count; } -static BIN_ATTR(rom, 0600, pci_read_rom, pci_write_rom, 0); +static const BIN_ATTR(rom, 0600, pci_read_rom, pci_write_rom, 0); -static struct bin_attribute *pci_dev_rom_attrs[] = { +static const struct bin_attribute *const pci_dev_rom_attrs[] = { &bin_attr_rom, NULL, }; static umode_t pci_dev_rom_attr_is_visible(struct kobject *kobj, - struct bin_attribute *a, int n) + const struct bin_attribute *a, int n) { struct pci_dev *pdev = to_pci_dev(kobj_to_dev(kobj)); - size_t rom_size; /* If the device has a ROM, try to expose it in sysfs. */ - rom_size = pci_resource_len(pdev, PCI_ROM_RESOURCE); - if (!rom_size) + if (!pci_resource_end(pdev, PCI_ROM_RESOURCE)) return 0; - a->size = rom_size; - return a->attr.mode; } +static size_t pci_dev_rom_attr_bin_size(struct kobject *kobj, + const struct bin_attribute *a, int n) +{ + struct pci_dev *pdev = to_pci_dev(kobj_to_dev(kobj)); + + return pci_resource_len(pdev, PCI_ROM_RESOURCE); +} + static const struct attribute_group pci_dev_rom_attr_group = { - .bin_attrs = pci_dev_rom_attrs, + .bin_attrs_new = pci_dev_rom_attrs, .is_bin_visible = pci_dev_rom_attr_is_visible, + .bin_size = pci_dev_rom_attr_bin_size, }; static ssize_t reset_store(struct device *dev, struct device_attribute *attr, @@ -1387,6 +1426,116 @@ static const struct attribute_group pci_dev_reset_attr_group = { .is_visible = pci_dev_reset_attr_is_visible, }; +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 *tmp_options, *name; + int m, n; + u8 reset_methods[PCI_NUM_RESET_METHODS] = {}; + + if (sysfs_streq(buf, "")) { + pdev->reset_methods[0] = 0; + pci_warn(pdev, "All device reset methods disabled by user"); + return count; + } + + pm_runtime_get_sync(dev); + struct device *pmdev __free(pm_runtime_put) = dev; + + if (sysfs_streq(buf, "default")) { + pci_init_reset_methods(pdev); + return count; + } + + char *options __free(kfree) = kstrndup(buf, count, GFP_KERNEL); + if (!options) + return -ENOMEM; + + n = 0; + tmp_options = options; + while ((name = strsep(&tmp_options, " ")) != NULL) { + if (sysfs_streq(name, "")) + continue; + + name = strim(name); + + /* Leave previous methods unchanged if input is invalid */ + m = reset_method_lookup(name); + if (!m) { + pci_err(pdev, "Invalid reset method '%s'", name); + return -EINVAL; + } + + if (pci_reset_fn_methods[m].reset_fn(pdev, PCI_RESET_PROBE)) { + pci_err(pdev, "Unsupported reset method '%s'", name); + return -EINVAL; + } + + if (n == PCI_NUM_RESET_METHODS - 1) { + pci_err(pdev, "Too many reset methods\n"); + return -EINVAL; + } + + 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, PCI_RESET_PROBE) == 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)); + return count; +} +static DEVICE_ATTR_RW(reset_method); + +static struct attribute *pci_dev_reset_method_attrs[] = { + &dev_attr_reset_method.attr, + NULL, +}; + +static const struct attribute_group pci_dev_reset_method_attr_group = { + .attrs = pci_dev_reset_method_attrs, + .is_visible = pci_dev_reset_attr_is_visible, +}; + static ssize_t __resource_resize_show(struct device *dev, int n, char *buf) { struct pci_dev *pdev = to_pci_dev(dev); @@ -1414,7 +1563,7 @@ static ssize_t __resource_resize_store(struct device *dev, int n, return -EINVAL; device_lock(dev); - if (dev->driver) { + if (dev->driver || pci_num_vf(pdev)) { ret = -EBUSY; goto unlock; } @@ -1436,7 +1585,7 @@ static ssize_t __resource_resize_store(struct device *dev, int n, pci_remove_resource_files(pdev); - for (i = 0; i < PCI_STD_NUM_BARS; i++) { + for (i = 0; i < PCI_BRIDGE_RESOURCES; i++) { if (pci_resource_len(pdev, i) && pci_resource_flags(pdev, i) == flags) pci_release_resource(pdev, i); @@ -1624,6 +1773,7 @@ const struct attribute_group *pci_dev_groups[] = { &pci_dev_acpi_attr_group, #endif &pci_dev_resource_resize_group, + ARCH_PCI_DEV_GROUPS NULL, }; @@ -1658,9 +1808,13 @@ const struct attribute_group *pci_dev_attr_groups[] = { &pcie_dev_attr_group, #ifdef CONFIG_PCIEAER &aer_stats_attr_group, + &aer_attr_group, #endif #ifdef CONFIG_PCIEASPM &aspm_ctrl_attr_group, #endif +#ifdef CONFIG_PCI_DOE + &pci_doe_sysfs_group, +#endif NULL, }; |