diff options
Diffstat (limited to 'drivers/pci')
36 files changed, 2480 insertions, 206 deletions
diff --git a/drivers/pci/Kconfig b/drivers/pci/Kconfig index 4d762fc4878..c27e782e6df 100644 --- a/drivers/pci/Kconfig +++ b/drivers/pci/Kconfig @@ -17,6 +17,31 @@ config PCI_MSI If you don't know what to do here, say N. +config PCI_MULTITHREAD_PROBE + bool "PCI Multi-threaded probe (EXPERIMENTAL)" + depends on PCI && EXPERIMENTAL + help + Say Y here if you want the PCI core to spawn a new thread for + every PCI device that is probed. This can cause a huge + speedup in boot times on multiprocessor machines, and even a + smaller speedup on single processor machines. + + But it can also cause lots of bad things to happen. A number + of PCI drivers can not properly handle running in this way, + some will just not work properly at all, while others might + decide to blow up power supplies with a huge load all at once, + so use this option at your own risk. + + It is very unwise to use this option if you are not using a + boot process that can handle devices being created in any + order. A program that can create persistant block and network + device names (like udev) is a good idea if you wish to use + this option. + + Again, use this option at your own risk, you have been warned! + + When in doubt, say N. + config PCI_DEBUG bool "PCI Debugging" depends on PCI && DEBUG_KERNEL diff --git a/drivers/pci/bus.c b/drivers/pci/bus.c index 5f7db9d2436..aadaa3c8096 100644 --- a/drivers/pci/bus.c +++ b/drivers/pci/bus.c @@ -77,9 +77,12 @@ pci_bus_alloc_resource(struct pci_bus *bus, struct resource *res, * This adds a single pci device to the global * device list and adds sysfs and procfs entries */ -void __devinit pci_bus_add_device(struct pci_dev *dev) +int __devinit pci_bus_add_device(struct pci_dev *dev) { - device_add(&dev->dev); + int retval; + retval = device_add(&dev->dev); + if (retval) + return retval; down_write(&pci_bus_sem); list_add_tail(&dev->global_list, &pci_devices); @@ -87,6 +90,7 @@ void __devinit pci_bus_add_device(struct pci_dev *dev) pci_proc_attach_device(dev); pci_create_sysfs_dev_files(dev); + return 0; } /** @@ -104,6 +108,7 @@ void __devinit pci_bus_add_device(struct pci_dev *dev) void __devinit pci_bus_add_devices(struct pci_bus *bus) { struct pci_dev *dev; + int retval; list_for_each_entry(dev, &bus->devices, bus_list) { /* @@ -112,7 +117,9 @@ void __devinit pci_bus_add_devices(struct pci_bus *bus) */ if (!list_empty(&dev->global_list)) continue; - pci_bus_add_device(dev); + retval = pci_bus_add_device(dev); + if (retval) + dev_err(&dev->dev, "Error adding device, continuing\n"); } list_for_each_entry(dev, &bus->devices, bus_list) { @@ -129,10 +136,13 @@ void __devinit pci_bus_add_devices(struct pci_bus *bus) list_add_tail(&dev->subordinate->node, &dev->bus->children); up_write(&pci_bus_sem); - } + } pci_bus_add_devices(dev->subordinate); - - sysfs_create_link(&dev->subordinate->class_dev.kobj, &dev->dev.kobj, "bridge"); + retval = sysfs_create_link(&dev->subordinate->class_dev.kobj, + &dev->dev.kobj, "bridge"); + if (retval) + dev_err(&dev->dev, "Error creating sysfs " + "bridge symlink, continuing...\n"); } } } diff --git a/drivers/pci/hotplug/acpiphp.h b/drivers/pci/hotplug/acpiphp.h index be104eced34..7fff07e877c 100644 --- a/drivers/pci/hotplug/acpiphp.h +++ b/drivers/pci/hotplug/acpiphp.h @@ -150,6 +150,11 @@ struct acpiphp_attention_info struct module *owner; }; +struct acpiphp_ioapic { + struct pci_dev *dev; + u32 gsi_base; + struct list_head list; +}; /* PCI bus bridge HID */ #define ACPI_PCI_HOST_HID "PNP0A03" diff --git a/drivers/pci/hotplug/acpiphp_glue.c b/drivers/pci/hotplug/acpiphp_glue.c index ae67a8f55ba..83e8e4412de 100644 --- a/drivers/pci/hotplug/acpiphp_glue.c +++ b/drivers/pci/hotplug/acpiphp_glue.c @@ -53,6 +53,8 @@ #include "acpiphp.h" static LIST_HEAD(bridge_list); +static LIST_HEAD(ioapic_list); +static DEFINE_SPINLOCK(ioapic_list_lock); #define MY_NAME "acpiphp_glue" @@ -797,6 +799,7 @@ ioapic_add(acpi_handle handle, u32 lvl, void *context, void **rv) struct pci_dev *pdev; u32 gsi_base; u64 phys_addr; + struct acpiphp_ioapic *ioapic; /* Evaluate _STA if present */ status = acpi_evaluate_integer(handle, "_STA", NULL, &sta); @@ -811,41 +814,107 @@ ioapic_add(acpi_handle handle, u32 lvl, void *context, void **rv) if (get_gsi_base(handle, &gsi_base)) return AE_OK; + ioapic = kmalloc(sizeof(*ioapic), GFP_KERNEL); + if (!ioapic) + return AE_NO_MEMORY; + pdev = get_apic_pci_info(handle); if (!pdev) - return AE_OK; + goto exit_kfree; - if (pci_enable_device(pdev)) { - pci_dev_put(pdev); - return AE_OK; - } + if (pci_enable_device(pdev)) + goto exit_pci_dev_put; pci_set_master(pdev); - if (pci_request_region(pdev, 0, "I/O APIC(acpiphp)")) { - pci_disable_device(pdev); - pci_dev_put(pdev); - return AE_OK; - } + if (pci_request_region(pdev, 0, "I/O APIC(acpiphp)")) + goto exit_pci_disable_device; phys_addr = pci_resource_start(pdev, 0); - if (acpi_register_ioapic(handle, phys_addr, gsi_base)) { - pci_release_region(pdev, 0); - pci_disable_device(pdev); - pci_dev_put(pdev); + if (acpi_register_ioapic(handle, phys_addr, gsi_base)) + goto exit_pci_release_region; + + ioapic->gsi_base = gsi_base; + ioapic->dev = pdev; + spin_lock(&ioapic_list_lock); + list_add_tail(&ioapic->list, &ioapic_list); + spin_unlock(&ioapic_list_lock); + + return AE_OK; + + exit_pci_release_region: + pci_release_region(pdev, 0); + exit_pci_disable_device: + pci_disable_device(pdev); + exit_pci_dev_put: + pci_dev_put(pdev); + exit_kfree: + kfree(ioapic); + + return AE_OK; +} + +static acpi_status +ioapic_remove(acpi_handle handle, u32 lvl, void *context, void **rv) +{ + acpi_status status; + unsigned long sta; + acpi_handle tmp; + u32 gsi_base; + struct acpiphp_ioapic *pos, *n, *ioapic = NULL; + + /* Evaluate _STA if present */ + status = acpi_evaluate_integer(handle, "_STA", NULL, &sta); + if (ACPI_SUCCESS(status) && sta != ACPI_STA_ALL) + return AE_CTRL_DEPTH; + + /* Scan only PCI bus scope */ + status = acpi_get_handle(handle, "_HID", &tmp); + if (ACPI_SUCCESS(status)) + return AE_CTRL_DEPTH; + + if (get_gsi_base(handle, &gsi_base)) return AE_OK; + + acpi_unregister_ioapic(handle, gsi_base); + + spin_lock(&ioapic_list_lock); + list_for_each_entry_safe(pos, n, &ioapic_list, list) { + if (pos->gsi_base != gsi_base) + continue; + ioapic = pos; + list_del(&ioapic->list); + break; } + spin_unlock(&ioapic_list_lock); + + if (!ioapic) + return AE_OK; + + pci_release_region(ioapic->dev, 0); + pci_disable_device(ioapic->dev); + pci_dev_put(ioapic->dev); + kfree(ioapic); return AE_OK; } static int acpiphp_configure_ioapics(acpi_handle handle) { + ioapic_add(handle, 0, NULL, NULL); acpi_walk_namespace(ACPI_TYPE_DEVICE, handle, ACPI_UINT32_MAX, ioapic_add, NULL, NULL); return 0; } +static int acpiphp_unconfigure_ioapics(acpi_handle handle) +{ + ioapic_remove(handle, 0, NULL, NULL); + acpi_walk_namespace(ACPI_TYPE_DEVICE, handle, + ACPI_UINT32_MAX, ioapic_remove, NULL, NULL); + return 0; +} + static int power_on_slot(struct acpiphp_slot *slot) { acpi_status status; @@ -997,7 +1066,7 @@ acpiphp_bus_add_out: * @handle: handle to acpi namespace * */ -int acpiphp_bus_trim(acpi_handle handle) +static int acpiphp_bus_trim(acpi_handle handle) { struct acpi_device *device; int retval; @@ -1074,10 +1143,11 @@ static int enable_device(struct acpiphp_slot *slot) pci_bus_assign_resources(bus); acpiphp_sanitize_bus(bus); + acpiphp_set_hpp_values(slot->bridge->handle, bus); + list_for_each_entry(func, &slot->funcs, sibling) + acpiphp_configure_ioapics(func->handle); pci_enable_bridges(bus); pci_bus_add_devices(bus); - acpiphp_set_hpp_values(slot->bridge->handle, bus); - acpiphp_configure_ioapics(slot->bridge->handle); /* associate pci_dev to our representation */ list_for_each (l, &slot->funcs) { @@ -1103,6 +1173,16 @@ static int enable_device(struct acpiphp_slot *slot) return retval; } +static void disable_bridges(struct pci_bus *bus) +{ + struct pci_dev *dev; + list_for_each_entry(dev, &bus->devices, bus_list) { + if (dev->subordinate) { + disable_bridges(dev->subordinate); + pci_disable_device(dev); + } + } +} /** * disable_device - disable a slot @@ -1127,6 +1207,19 @@ static int disable_device(struct acpiphp_slot *slot) func->bridge = NULL; } + if (func->pci_dev) { + pci_stop_bus_device(func->pci_dev); + if (func->pci_dev->subordinate) { + disable_bridges(func->pci_dev->subordinate); + pci_disable_device(func->pci_dev); + } + } + } + + list_for_each (l, &slot->funcs) { + func = list_entry(l, struct acpiphp_func, sibling); + + acpiphp_unconfigure_ioapics(func->handle); acpiphp_bus_trim(func->handle); /* try to remove anyway. * acpiphp_bus_add might have been failed */ diff --git a/drivers/pci/hotplug/acpiphp_ibm.c b/drivers/pci/hotplug/acpiphp_ibm.c index 317457dd401..d0a07d9ab30 100644 --- a/drivers/pci/hotplug/acpiphp_ibm.c +++ b/drivers/pci/hotplug/acpiphp_ibm.c @@ -487,9 +487,7 @@ static void __exit ibm_acpiphp_exit(void) if (ACPI_FAILURE(status)) err("%s: Notification handler removal failed\n", __FUNCTION__); /* remove the /sys entries */ - if (sysfs_remove_bin_file(sysdir, &ibm_apci_table_attr)) - err("%s: removal of sysfs file apci_table failed\n", - __FUNCTION__); + sysfs_remove_bin_file(sysdir, &ibm_apci_table_attr); } module_init(ibm_acpiphp_init); diff --git a/drivers/pci/hotplug/cpqphp_sysfs.c b/drivers/pci/hotplug/cpqphp_sysfs.c index 8b3da007e85..5bab666cd67 100644 --- a/drivers/pci/hotplug/cpqphp_sysfs.c +++ b/drivers/pci/hotplug/cpqphp_sysfs.c @@ -140,7 +140,7 @@ struct ctrl_dbg { static int open(struct inode *inode, struct file *file) { - struct controller *ctrl = inode->u.generic_ip; + struct controller *ctrl = inode->i_private; struct ctrl_dbg *dbg; int retval = -ENOMEM; diff --git a/drivers/pci/hotplug/fakephp.c b/drivers/pci/hotplug/fakephp.c index dd2b762777c..05a4f0f9018 100644 --- a/drivers/pci/hotplug/fakephp.c +++ b/drivers/pci/hotplug/fakephp.c @@ -176,7 +176,9 @@ static void pci_rescan_slot(struct pci_dev *temp) struct pci_bus *bus = temp->bus; struct pci_dev *dev; int func; + int retval; u8 hdr_type; + if (!pci_read_config_byte(temp, PCI_HEADER_TYPE, &hdr_type)) { temp->hdr_type = hdr_type & 0x7f; if (!pci_find_slot(bus->number, temp->devfn)) { @@ -185,8 +187,12 @@ static void pci_rescan_slot(struct pci_dev *temp) dbg("New device on %s function %x:%x\n", bus->name, temp->devfn >> 3, temp->devfn & 7); - pci_bus_add_device(dev); - add_slot(dev); + retval = pci_bus_add_device(dev); + if (retval) + dev_err(&dev->dev, "error adding " + "device, continuing.\n"); + else + add_slot(dev); } } /* multifunction device? */ @@ -205,8 +211,12 @@ static void pci_rescan_slot(struct pci_dev *temp) dbg("New device on %s function %x:%x\n", bus->name, temp->devfn >> 3, temp->devfn & 7); - pci_bus_add_device(dev); - add_slot(dev); + retval = pci_bus_add_device(dev); + if (retval) + dev_err(&dev->dev, "error adding " + "device, continuing.\n"); + else + add_slot(dev); } } } diff --git a/drivers/pci/hotplug/pci_hotplug.h b/drivers/pci/hotplug/pci_hotplug.h index e929b7c1142..772523dc386 100644 --- a/drivers/pci/hotplug/pci_hotplug.h +++ b/drivers/pci/hotplug/pci_hotplug.h @@ -172,8 +172,8 @@ struct hotplug_slot { extern int pci_hp_register (struct hotplug_slot *slot); extern int pci_hp_deregister (struct hotplug_slot *slot); -extern int pci_hp_change_slot_info (struct hotplug_slot *slot, - struct hotplug_slot_info *info); +extern int __must_check pci_hp_change_slot_info (struct hotplug_slot *slot, + struct hotplug_slot_info *info); extern struct subsystem pci_hotplug_slots_subsys; /* PCI Setting Record (Type 0) */ diff --git a/drivers/pci/hotplug/pci_hotplug_core.c b/drivers/pci/hotplug/pci_hotplug_core.c index b7b378df89e..e2823ea9c4e 100644 --- a/drivers/pci/hotplug/pci_hotplug_core.c +++ b/drivers/pci/hotplug/pci_hotplug_core.c @@ -482,31 +482,95 @@ static int has_test_file (struct hotplug_slot *slot) static int fs_add_slot (struct hotplug_slot *slot) { - if (has_power_file(slot) == 0) - sysfs_create_file(&slot->kobj, &hotplug_slot_attr_power.attr); + int retval = 0; - if (has_attention_file(slot) == 0) - sysfs_create_file(&slot->kobj, &hotplug_slot_attr_attention.attr); + if (has_power_file(slot) == 0) { + retval = sysfs_create_file(&slot->kobj, &hotplug_slot_attr_power.attr); + if (retval) + goto exit_power; + } - if (has_latch_file(slot) == 0) - sysfs_create_file(&slot->kobj, &hotplug_slot_attr_latch.attr); + if (has_attention_file(slot) == 0) { + retval = sysfs_create_file(&slot->kobj, + &hotplug_slot_attr_attention.attr); + if (retval) + goto exit_attention; + } - if (has_adapter_file(slot) == 0) - sysfs_create_file(&slot->kobj, &hotplug_slot_attr_presence.attr); + if (has_latch_file(slot) == 0) { + retval = sysfs_create_file(&slot->kobj, + &hotplug_slot_attr_latch.attr); + if (retval) + goto exit_latch; + } - if (has_address_file(slot) == 0) - sysfs_create_file(&slot->kobj, &hotplug_slot_attr_address.attr); + if (has_adapter_file(slot) == 0) { + retval = sysfs_create_file(&slot->kobj, + &hotplug_slot_attr_presence.attr); + if (retval) + goto exit_adapter; + } - if (has_max_bus_speed_file(slot) == 0) - sysfs_create_file(&slot->kobj, &hotplug_slot_attr_max_bus_speed.attr); + if (has_address_file(slot) == 0) { + retval = sysfs_create_file(&slot->kobj, + &hotplug_slot_attr_address.attr); + if (retval) + goto exit_address; + } + if (has_max_bus_speed_file(slot) == 0) { + retval = sysfs_create_file(&slot->kobj, + &hotplug_slot_attr_max_bus_speed.attr); + if (retval) + goto exit_max_speed; + } + + if (has_cur_bus_speed_file(slot) == 0) { + retval = sysfs_create_file(&slot->kobj, + &hotplug_slot_attr_cur_bus_speed.attr); + if (retval) + goto exit_cur_speed; + } + + if (has_test_file(slot) == 0) { + retval = sysfs_create_file(&slot->kobj, + &hotplug_slot_attr_test.attr); + if (retval) + goto exit_test; + } + + goto exit; + +exit_test: if (has_cur_bus_speed_file(slot) == 0) - sysfs_create_file(&slot->kobj, &hotplug_slot_attr_cur_bus_speed.attr); + sysfs_remove_file(&slot->kobj, &hotplug_slot_attr_cur_bus_speed.attr); - if (has_test_file(slot) == 0) - sysfs_create_file(&slot->kobj, &hotplug_slot_attr_test.attr); +exit_cur_speed: + if (has_max_bus_speed_file(slot) == 0) + sysfs_remove_file(&slot->kobj, &hotplug_slot_attr_max_bus_speed.attr); - return 0; +exit_max_speed: + if (has_address_file(slot) == 0) + sysfs_remove_file(&slot->kobj, &hotplug_slot_attr_address.attr); + +exit_address: + if (has_adapter_file(slot) == 0) + sysfs_remove_file(&slot->kobj, &hotplug_slot_attr_presence.attr); + +exit_adapter: + if (has_latch_file(slot) == 0) + sysfs_remove_file(&slot->kobj, &hotplug_slot_attr_latch.attr); + +exit_latch: + if (has_attention_file(slot) == 0) + sysfs_remove_file(&slot->kobj, &hotplug_slot_attr_attention.attr); + +exit_attention: + if (has_power_file(slot) == 0) + sysfs_remove_file(&slot->kobj, &hotplug_slot_attr_power.attr); +exit_power: +exit: + return retval; } static void fs_remove_slot (struct hotplug_slot *slot) @@ -626,8 +690,11 @@ int pci_hp_deregister (struct hotplug_slot *slot) * * Returns 0 if successful, anything else for an error. */ -int pci_hp_change_slot_info (struct hotplug_slot *slot, struct hotplug_slot_info *info) +int __must_check pci_hp_change_slot_info(struct hotplug_slot *slot, + struct hotplug_slot_info *info) { + int retval; + if ((slot == NULL) || (info == NULL)) return -ENODEV; @@ -636,32 +703,60 @@ int pci_hp_change_slot_info (struct hotplug_slot *slot, struct hotplug_slot_info * for the files referring to the fields that have now changed. */ if ((has_power_file(slot) == 0) && - (slot->info->power_status != info->power_status)) - sysfs_update_file(&slot->kobj, &hotplug_slot_attr_power.attr); + (slot->info->power_status != info->power_status)) { + retval = sysfs_update_file(&slot->kobj, + &hotplug_slot_attr_power.attr); + if (retval) + return retval; + } if ((has_attention_file(slot) == 0) && - (slot->info->attention_status != info->attention_status)) - sysfs_update_file(&slot->kobj, &hotplug_slot_attr_attention.attr); + (slot->info->attention_status != info->attention_status)) { + retval = sysfs_update_file(&slot->kobj, + &hotplug_slot_attr_attention.attr); + if (retval) + return retval; + } if ((has_latch_file(slot) == 0) && - (slot->info->latch_status != info->latch_status)) - sysfs_update_file(&slot->kobj, &hotplug_slot_attr_latch.attr); + (slot->info->latch_status != info->latch_status)) { + retval = sysfs_update_file(&slot->kobj, + &hotplug_slot_attr_latch.attr); + if (retval) + return retval; + } if ((has_adapter_file(slot) == 0) && - (slot->info->adapter_status != info->adapter_status)) - sysfs_update_file(&slot->kobj, &hotplug_slot_attr_presence.attr); + (slot->info->adapter_status != info->adapter_status)) { + retval = sysfs_update_file(&slot->kobj, + &hotplug_slot_attr_presence.attr); + if (retval) + return retval; + } if ((has_address_file(slot) == 0) && - (slot->info->address != info->address)) - sysfs_update_file(&slot->kobj, &hotplug_slot_attr_address.attr); + (slot->info->address != info->address)) { + retval = sysfs_update_file(&slot->kobj, + &hotplug_slot_attr_address.attr); + if (retval) + return retval; + } if ((has_max_bus_speed_file(slot) == 0) && - (slot->info->max_bus_speed != info->max_bus_speed)) - sysfs_update_file(&slot->kobj, &hotplug_slot_attr_max_bus_speed.attr); + (slot->info->max_bus_speed != info->max_bus_speed)) { + retval = sysfs_update_file(&slot->kobj, + &hotplug_slot_attr_max_bus_speed.attr); + if (retval) + return retval; + } if ((has_cur_bus_speed_file(slot) == 0) && - (slot->info->cur_bus_speed != info->cur_bus_speed)) - sysfs_update_file(&slot->kobj, &hotplug_slot_attr_cur_bus_speed.attr); + (slot->info->cur_bus_speed != info->cur_bus_speed)) { + retval = sysfs_update_file(&slot->kobj, + &hotplug_slot_attr_cur_bus_speed.attr); + if (retval) + return retval; + } memcpy (slot->info, info, sizeof (struct hotplug_slot_info)); diff --git a/drivers/pci/hotplug/pciehp_ctrl.c b/drivers/pci/hotplug/pciehp_ctrl.c index 33d19876835..41290a106bd 100644 --- a/drivers/pci/hotplug/pciehp_ctrl.c +++ b/drivers/pci/hotplug/pciehp_ctrl.c @@ -762,14 +762,14 @@ int pciehp_enable_slot(struct slot *p_slot) if (rc || !getstatus) { info("%s: no adapter on slot(%x)\n", __FUNCTION__, p_slot->number); mutex_unlock(&p_slot->ctrl->crit_sect); - return 1; + return -ENODEV; } if (MRL_SENS(p_slot->ctrl->ctrlcap)) { rc = p_slot->hpc_ops->get_latch_status(p_slot, &getstatus); if (rc || getstatus) { info("%s: latch open on slot(%x)\n", __FUNCTION__, p_slot->number); mutex_unlock(&p_slot->ctrl->crit_sect); - return 1; + return -ENODEV; } } @@ -778,7 +778,7 @@ int pciehp_enable_slot(struct slot *p_slot) if (rc || getstatus) { info("%s: already enabled on slot(%x)\n", __FUNCTION__, p_slot->number); mutex_unlock(&p_slot->ctrl->crit_sect); - return 1; + return -EINVAL; } } mutex_unlock(&p_slot->ctrl->crit_sect); @@ -813,7 +813,7 @@ int pciehp_disable_slot(struct slot *p_slot) if (ret || !getstatus) { info("%s: no adapter on slot(%x)\n", __FUNCTION__, p_slot->number); mutex_unlock(&p_slot->ctrl->crit_sect); - return 1; + return -ENODEV; } } @@ -822,7 +822,7 @@ int pciehp_disable_slot(struct slot *p_slot) if (ret || getstatus) { info("%s: latch open on slot(%x)\n", __FUNCTION__, p_slot->number); mutex_unlock(&p_slot->ctrl->crit_sect); - return 1; + return -ENODEV; } } @@ -831,7 +831,7 @@ int pciehp_disable_slot(struct slot *p_slot) if (ret || !getstatus) { info("%s: already disabled slot(%x)\n", __FUNCTION__, p_slot->number); mutex_unlock(&p_slot->ctrl->crit_sect); - return 1; + return -EINVAL; } } diff --git a/drivers/pci/hotplug/pcihp_skeleton.c b/drivers/pci/hotplug/pcihp_skeleton.c index 8ad446605f7..2b9e10e3861 100644 --- a/drivers/pci/hotplug/pcihp_skeleton.c +++ b/drivers/pci/hotplug/pcihp_skeleton.c @@ -1,5 +1,5 @@ /* - * PCI Hot Plug Controller Skeleton Driver - 0.2 + * PCI Hot Plug Controller Skeleton Driver - 0.3 * * Copyright (C) 2001,2003 Greg Kroah-Hartman (greg@kroah.com) * Copyright (C) 2001,2003 IBM Corp. @@ -21,7 +21,7 @@ * along with this program; if not, write to the Free Software * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. * - * This driver is to be used as a skeleton driver to be show how to interface + * This driver is to be used as a skeleton driver to show how to interface * with the pci hotplug core easily. * * Send feedback to <greg@kroah.com> @@ -58,8 +58,6 @@ static LIST_HEAD(slot_list); #define info(format, arg...) printk(KERN_INFO "%s: " format "\n", MY_NAME , ## arg) #define warn(format, arg...) printk(KERN_WARNING "%s: " format "\n", MY_NAME , ## arg) - - /* local variables */ static int debug; static int num_slots; @@ -109,7 +107,6 @@ static int enable_slot(struct hotplug_slot *hotplug_slot) return retval; } - static int disable_slot(struct hotplug_slot *hotplug_slot) { struct slot *slot = hotplug_slot->private; @@ -342,7 +339,7 @@ static int __init pcihp_skel_init(void) info(DRIVER_DESC " version: " DRIVER_VERSION "\n"); /* * Do specific initialization stuff for your driver here - * Like initializing your controller hardware (if any) and + * like initializing your controller hardware (if any) and * determining the number of slots you have in the system * right now. */ diff --git a/drivers/pci/hotplug/shpchp.h b/drivers/pci/hotplug/shpchp.h index 7208b95c6ee..c7103ac5cd0 100644 --- a/drivers/pci/hotplug/shpchp.h +++ b/drivers/pci/hotplug/shpchp.h @@ -173,7 +173,7 @@ struct controller { #define msg_button_cancel "PCI slot #%s - action canceled due to button press.\n" /* sysfs functions for the hotplug controller info */ -extern void shpchp_create_ctrl_files (struct controller *ctrl); +extern int __must_check shpchp_create_ctrl_files(struct controller *ctrl); extern int shpchp_sysfs_enable_slot(struct slot *slot); extern int shpchp_sysfs_disable_slot(struct slot *slot); diff --git a/drivers/pci/hotplug/shpchp_core.c b/drivers/pci/hotplug/shpchp_core.c index a14e7de1984..235c18a2239 100644 --- a/drivers/pci/hotplug/shpchp_core.c +++ b/drivers/pci/hotplug/shpchp_core.c @@ -449,10 +449,14 @@ static int shpc_probe(struct pci_dev *pdev, const struct pci_device_id *ent) ctrl->speed = PCI_SPEED_33MHz; } - shpchp_create_ctrl_files(ctrl); + rc = shpchp_create_ctrl_files(ctrl); + if (rc) + goto err_cleanup_slots; return 0; +err_cleanup_slots: + cleanup_slots(ctrl); err_out_release_ctlr: ctrl->hpc_ops->release_ctlr(ctrl); err_out_free_ctrl: diff --git a/drivers/pci/hotplug/shpchp_sysfs.c b/drivers/pci/hotplug/shpchp_sysfs.c index 620e1139e60..29fa9d26ada 100644 --- a/drivers/pci/hotplug/shpchp_sysfs.c +++ b/drivers/pci/hotplug/shpchp_sysfs.c @@ -91,9 +91,9 @@ static ssize_t show_ctrl (struct device *dev, struct device_attribute *attr, cha } static DEVICE_ATTR (ctrl, S_IRUGO, show_ctrl, NULL); -void shpchp_create_ctrl_files (struct controller *ctrl) +int __must_check shpchp_create_ctrl_files (struct controller *ctrl) { - device_create_file (&ctrl->pci_dev->dev, &dev_attr_ctrl); + return device_create_file (&ctrl->pci_dev->dev, &dev_attr_ctrl); } void shpchp_remove_ctrl_files(struct controller *ctrl) diff --git a/drivers/pci/msi.c b/drivers/pci/msi.c index a83c1f5735d..27a057409ec 100644 --- a/drivers/pci/msi.c +++ b/drivers/pci/msi.c @@ -45,16 +45,10 @@ msi_register(struct msi_ops *ops) return 0; } -static void msi_cache_ctor(void *p, kmem_cache_t *cache, unsigned long flags) -{ - memset(p, 0, sizeof(struct msi_desc)); -} - static int msi_cache_init(void) { - msi_cachep = kmem_cache_create("msi_cache", - sizeof(struct msi_desc), - 0, SLAB_HWCACHE_ALIGN, msi_cache_ctor, NULL); + msi_cachep = kmem_cache_create("msi_cache", sizeof(struct msi_desc), + 0, SLAB_HWCACHE_ALIGN, NULL, NULL); if (!msi_cachep) return -ENOMEM; @@ -402,11 +396,10 @@ static struct msi_desc* alloc_msi_entry(void) { struct msi_desc *entry; - entry = kmem_cache_alloc(msi_cachep, SLAB_KERNEL); + entry = kmem_cache_zalloc(msi_cachep, GFP_KERNEL); if (!entry) return NULL; - memset(entry, 0, sizeof(struct msi_desc)); entry->link.tail = entry->link.head = 0; /* single message */ entry->dev = NULL; @@ -901,6 +894,33 @@ static int msix_capability_init(struct pci_dev *dev, } /** + * pci_msi_supported - check whether MSI may be enabled on device + * @dev: pointer to the pci_dev data structure of MSI device function + * + * MSI must be globally enabled and supported by the device and its root + * bus. But, the root bus is not easy to find since some architectures + * have virtual busses on top of the PCI hierarchy (for instance the + * hypertransport bus), while the actual bus where MSI must be supported + * is below. So we test the MSI flag on all parent busses and assume + * that no quirk will ever set the NO_MSI flag on a non-root bus. + **/ +static +int pci_msi_supported(struct pci_dev * dev) +{ + struct pci_bus *bus; + + if (!pci_msi_enable || !dev || dev->no_msi) + return -EINVAL; + + /* check MSI flags of all parent busses */ + for (bus = dev->bus; bus; bus = bus->parent) + if (bus->bus_flags & PCI_BUS_FLAGS_NO_MSI) + return -EINVAL; + + return 0; +} + +/** * pci_enable_msi - configure device's MSI capability structure * @dev: pointer to the pci_dev data structure of MSI device function * @@ -912,19 +932,11 @@ static int msix_capability_init(struct pci_dev *dev, **/ int pci_enable_msi(struct pci_dev* dev) { - struct pci_bus *bus; - int pos, temp, status = -EINVAL; + int pos, temp, status; u16 control; - if (!pci_msi_enable || !dev) - return status; - - if (dev->no_msi) - return status; - - for (bus = dev->bus; bus; bus = bus->parent) - if (bus->bus_flags & PCI_BUS_FLAGS_NO_MSI) - return -EINVAL; + if (pci_msi_supported(dev) < 0) + return -EINVAL; temp = dev->irq; @@ -1134,22 +1146,14 @@ static int reroute_msix_table(int head, struct msix_entry *entries, int *nvec) **/ int pci_enable_msix(struct pci_dev* dev, struct msix_entry *entries, int nvec) { - struct pci_bus *bus; int status, pos, nr_entries, free_vectors; int i, j, temp; u16 control; unsigned long flags; - if (!pci_msi_enable || !dev || !entries) + if (!entries || pci_msi_supported(dev) < 0) return -EINVAL; - if (dev->no_msi) - return -EINVAL; - - for (bus = dev->bus; bus; bus = bus->parent) - if (bus->bus_flags & PCI_BUS_FLAGS_NO_MSI) - return -EINVAL; - status = msi_init(); if (status < 0) return status; diff --git a/drivers/pci/pci-driver.c b/drivers/pci/pci-driver.c index 474e9cd0e9e..b1c0c707d96 100644 --- a/drivers/pci/pci-driver.c +++ b/drivers/pci/pci-driver.c @@ -17,6 +17,16 @@ * Registration of PCI drivers and handling of hot-pluggable devices. */ +/* multithreaded probe logic */ +static int pci_multithread_probe = +#ifdef CONFIG_PCI_MULTITHREAD_PROBE + 1; +#else + 0; +#endif +__module_param_call("", pci_multithread_probe, param_set_bool, param_get_bool, &pci_multithread_probe, 0644); + + /* * Dynamic device IDs are disabled for !CONFIG_HOTPLUG */ @@ -46,6 +56,7 @@ store_new_id(struct device_driver *driver, const char *buf, size_t count) subdevice=PCI_ANY_ID, class=0, class_mask=0; unsigned long driver_data=0; int fields=0; + int retval = 0; fields = sscanf(buf, "%x %x %x %x %x %x %lux", &vendor, &device, &subvendor, &subdevice, @@ -72,10 +83,12 @@ store_new_id(struct device_driver *driver, const char *buf, size_t count) spin_unlock(&pdrv->dynids.lock); if (get_driver(&pdrv->driver)) { - driver_attach(&pdrv->driver); + retval = driver_attach(&pdrv->driver); put_driver(&pdrv->driver); } + if (retval) + return retval; return count; } static DRIVER_ATTR(new_id, S_IWUSR, NULL, store_new_id); @@ -279,6 +292,18 @@ static int pci_device_suspend(struct device * dev, pm_message_t state) return i; } +static int pci_device_suspend_late(struct device * dev, pm_message_t state) +{ + struct pci_dev * pci_dev = to_pci_dev(dev); + struct pci_driver * drv = pci_dev->driver; + int i = 0; + + if (drv && drv->suspend_late) { + i = drv->suspend_late(pci_dev, state); + suspend_report_result(drv->suspend_late, i); + } + return i; +} /* * Default resume method for devices that have no driver provided resume, @@ -313,6 +338,17 @@ static int pci_device_resume(struct device * dev) return error; } +static int pci_device_resume_early(struct device * dev) +{ + int error = 0; + struct pci_dev * pci_dev = to_pci_dev(dev); + struct pci_driver * drv = pci_dev->driver; + + if (drv && drv->resume_early) + error = drv->resume_early(pci_dev); + return error; +} + static void pci_device_shutdown(struct device *dev) { struct pci_dev *pci_dev = to_pci_dev(dev); @@ -386,6 +422,11 @@ int __pci_register_driver(struct pci_driver *drv, struct module *owner) drv->driver.owner = owner; drv->driver.kobj.ktype = &pci_driver_kobj_type; + if (pci_multithread_probe) + drv->driver.multithread_probe = pci_multithread_probe; + else + drv->driver.multithread_probe = drv->multithread_probe; + spin_lock_init(&drv->dynids.lock); INIT_LIST_HEAD(&drv->dynids.list); @@ -509,8 +550,10 @@ struct bus_type pci_bus_type = { .probe = pci_device_probe, .remove = pci_device_remove, .suspend = pci_device_suspend, - .shutdown = pci_device_shutdown, + .suspend_late = pci_device_suspend_late, + .resume_early = pci_device_resume_early, .resume = pci_device_resume, + .shutdown = pci_device_shutdown, .dev_attrs = pci_dev_attrs, }; diff --git a/drivers/pci/pci-sysfs.c b/drivers/pci/pci-sysfs.c index fdefa7dcd15..a1d2e979b17 100644 --- a/drivers/pci/pci-sysfs.c +++ b/drivers/pci/pci-sysfs.c @@ -117,6 +117,7 @@ is_enabled_store(struct device *dev, struct device_attribute *attr, const char *buf, size_t count) { struct pci_dev *pdev = to_pci_dev(dev); + int retval = 0; /* this can crash the machine when done on the "wrong" device */ if (!capable(CAP_SYS_ADMIN)) @@ -126,11 +127,53 @@ is_enabled_store(struct device *dev, struct device_attribute *attr, pci_disable_device(pdev); if (*buf == '1') - pci_enable_device(pdev); + retval = pci_enable_device(pdev); + if (retval) + return retval; return count; } +static ssize_t +msi_bus_show(struct device *dev, struct device_attribute *attr, char *buf) +{ + struct pci_dev *pdev = to_pci_dev(dev); + + if (!pdev->subordinate) + return 0; + + return sprintf (buf, "%u\n", + !(pdev->subordinate->bus_flags & PCI_BUS_FLAGS_NO_MSI)); +} + +static ssize_t +msi_bus_store(struct device *dev, struct device_attribute *attr, + const char *buf, size_t count) +{ + struct pci_dev *pdev = to_pci_dev(dev); + + /* bad things may happen if the no_msi flag is changed + * while some drivers are loaded */ + if (!capable(CAP_SYS_ADMIN)) + return count; + + if (!pdev->subordinate) + return count; + + if (*buf == '0') { + pdev->subordinate->bus_flags |= PCI_BUS_FLAGS_NO_MSI; + dev_warn(&pdev->dev, "forced subordinate bus to not support MSI," + " bad things could happen.\n"); + } + + if (*buf == '1') { + pdev->subordinate->bus_flags &= ~PCI_BUS_FLAGS_NO_MSI; + dev_warn(&pdev->dev, "forced subordinate bus to support MSI," + " bad things could happen.\n"); + } + + return count; +} struct device_attribute pci_dev_attrs[] = { __ATTR_RO(resource), @@ -145,6 +188,7 @@ struct device_attribute pci_dev_attrs[] = { __ATTR(enable, 0600, is_enabled_show, is_enabled_store), __ATTR(broken_parity_status,(S_IRUGO|S_IWUSR), broken_parity_status_show,broken_parity_status_store), + __ATTR(msi_bus, 0644, msi_bus_show, msi_bus_store), __ATTR_NULL, }; @@ -385,15 +429,38 @@ pci_mmap_resource(struct kobject *kobj, struct bin_attribute *attr, } /** + * pci_remove_resource_files - cleanup resource files + * @dev: dev to cleanup + * + * If we created resource files for @dev, remove them from sysfs and + * free their resources. + */ +static void +pci_remove_resource_files(struct pci_dev *pdev) +{ + int i; + + for (i = 0; i < PCI_ROM_RESOURCE; i++) { + struct bin_attribute *res_attr; + + res_attr = pdev->res_attr[i]; + if (res_attr) { + sysfs_remove_bin_file(&pdev->dev.kobj, res_attr); + kfree(res_attr); + } + } +} + +/** * pci_create_resource_files - create resource files in sysfs for @dev * @dev: dev in question * * Walk the resources in @dev creating files for each resource available. */ -static void -pci_create_resource_files(struct pci_dev *pdev) +static int pci_create_resource_files(struct pci_dev *pdev) { int i; + int retval; /* Expose the PCI resources from this device as files */ for (i = 0; i < PCI_ROM_RESOURCE; i++) { @@ -416,35 +483,19 @@ pci_create_resource_files(struct pci_dev *pdev) res_attr->size = pci_resource_len(pdev, i); res_attr->mmap = pci_mmap_resource; res_attr->private = &pdev->resource[i]; - sysfs_create_bin_file(&pdev->dev.kobj, res_attr); - } - } -} - -/** - * pci_remove_resource_files - cleanup resource files - * @dev: dev to cleanup - * - * If we created resource files for @dev, remove them from sysfs and - * free their resources. - */ -static void -pci_remove_resource_files(struct pci_dev *pdev) -{ - int i; - - for (i = 0; i < PCI_ROM_RESOURCE; i++) { - struct bin_attribute *res_attr; - - res_attr = pdev->res_attr[i]; - if (res_attr) { - sysfs_remove_bin_file(&pdev->dev.kobj, res_attr); - kfree(res_attr); + retval = sysfs_create_bin_file(&pdev->dev.kobj, res_attr); + if (retval) { + pci_remove_resource_files(pdev); + return retval; + } + } else { + return -ENOMEM; } } + return 0; } #else /* !HAVE_PCI_MMAP */ -static inline void pci_create_resource_files(struct pci_dev *dev) { return; } +static inline int pci_create_resource_files(struct pci_dev *dev) { return 0; } static inline void pci_remove_resource_files(struct pci_dev *dev) { return; } #endif /* HAVE_PCI_MMAP */ @@ -529,22 +580,27 @@ static struct bin_attribute pcie_config_attr = { .write = pci_write_config, }; -int pci_create_sysfs_dev_files (struct pci_dev *pdev) +int __must_check pci_create_sysfs_dev_files (struct pci_dev *pdev) { + struct bin_attribute *rom_attr = NULL; + int retval; + if (!sysfs_initialized) return -EACCES; if (pdev->cfg_size < 4096) - sysfs_create_bin_file(&pdev->dev.kobj, &pci_config_attr); + retval = sysfs_create_bin_file(&pdev->dev.kobj, &pci_config_attr); else - sysfs_create_bin_file(&pdev->dev.kobj, &pcie_config_attr); + retval = sysfs_create_bin_file(&pdev->dev.kobj, &pcie_config_attr); + if (retval) + goto err; - pci_create_resource_files(pdev); + retval = pci_create_resource_files(pdev); + if (retval) + goto err_bin_file; /* If the device has a ROM, try to expose it in sysfs. */ if (pci_resource_len(pdev, PCI_ROM_RESOURCE)) { - struct bin_attribute *rom_attr; - rom_attr = kzalloc(sizeof(*rom_attr), GFP_ATOMIC); if (rom_attr) { pdev->rom_attr = rom_attr; @@ -554,13 +610,28 @@ int pci_create_sysfs_dev_files (struct pci_dev *pdev) rom_attr->attr.owner = THIS_MODULE; rom_attr->read = pci_read_rom; rom_attr->write = pci_write_rom; - sysfs_create_bin_file(&pdev->dev.kobj, rom_attr); + retval = sysfs_create_bin_file(&pdev->dev.kobj, rom_attr); + if (retval) + goto err_rom; + } else { + retval = -ENOMEM; + goto err_bin_file; } } /* add platform-specific attributes */ pcibios_add_platform_entries(pdev); - + return 0; + +err_rom: + kfree(rom_attr); +err_bin_file: + if (pdev->cfg_size < 4096) + sysfs_remove_bin_file(&pdev->dev.kobj, &pci_config_attr); + else + sysfs_remove_bin_file(&pdev->dev.kobj, &pcie_config_attr); +err: + return retval; } /** @@ -589,10 +660,14 @@ void pci_remove_sysfs_dev_files(struct pci_dev *pdev) static int __init pci_sysfs_init(void) { struct pci_dev *pdev = NULL; - + int retval; + sysfs_initialized = 1; - for_each_pci_dev(pdev) - pci_create_sysfs_dev_files(pdev); + for_each_pci_dev(pdev) { + retval = pci_create_sysfs_dev_files(pdev); + if (retval) + return retval; + } return 0; } diff --git a/drivers/pci/pci.c b/drivers/pci/pci.c index 9f79dd6d51a..a544997399b 100644 --- a/drivers/pci/pci.c +++ b/drivers/pci/pci.c @@ -432,10 +432,12 @@ pci_power_t pci_choose_state(struct pci_dev *dev, pm_message_t state) case PM_EVENT_ON: return PCI_D0; case PM_EVENT_FREEZE: + case PM_EVENT_PRETHAW: + /* REVISIT both freeze and pre-thaw "should" use D0 */ case PM_EVENT_SUSPEND: return PCI_D3hot; default: - printk("They asked me for state %d\n", state.event); + printk("Unrecognized suspend event %d\n", state.event); BUG(); } return PCI_D0; @@ -443,6 +445,51 @@ pci_power_t pci_choose_state(struct pci_dev *dev, pm_message_t state) EXPORT_SYMBOL(pci_choose_state); +static int pci_save_pcie_state(struct pci_dev *dev) +{ + int pos, i = 0; + struct pci_cap_saved_state *save_state; + u16 *cap; + + pos = pci_find_capability(dev, PCI_CAP_ID_EXP); + if (pos <= 0) + return 0; + + save_state = kzalloc(sizeof(*save_state) + sizeof(u16) * 4, GFP_KERNEL); + if (!save_state) { + dev_err(&dev->dev, "Out of memory in pci_save_pcie_state\n"); + return -ENOMEM; + } + cap = (u16 *)&save_state->data[0]; + + pci_read_config_word(dev, pos + PCI_EXP_DEVCTL, &cap[i++]); + pci_read_config_word(dev, pos + PCI_EXP_LNKCTL, &cap[i++]); + pci_read_config_word(dev, pos + PCI_EXP_SLTCTL, &cap[i++]); + pci_read_config_word(dev, pos + PCI_EXP_RTCTL, &cap[i++]); + pci_add_saved_cap(dev, save_state); + return 0; +} + +static void pci_restore_pcie_state(struct pci_dev *dev) +{ + int i = 0, pos; + struct pci_cap_saved_state *save_state; + u16 *cap; + + save_state = pci_find_saved_cap(dev, PCI_CAP_ID_EXP); + pos = pci_find_capability(dev, PCI_CAP_ID_EXP); + if (!save_state || pos <= 0) + return; + cap = (u16 *)&save_state->data[0]; + + pci_write_config_word(dev, pos + PCI_EXP_DEVCTL, cap[i++]); + pci_write_config_word(dev, pos + PCI_EXP_LNKCTL, cap[i++]); + pci_write_config_word(dev, pos + PCI_EXP_SLTCTL, cap[i++]); + pci_write_config_word(dev, pos + PCI_EXP_RTCTL, cap[i++]); + pci_remove_saved_cap(save_state); + kfree(save_state); +} + /** * pci_save_state - save the PCI configuration space of a device before suspending * @dev: - PCI device that we're dealing with @@ -458,6 +505,8 @@ pci_save_state(struct pci_dev *dev) return i; if ((i = pci_save_msix_state(dev)) != 0) return i; + if ((i = pci_save_pcie_state(dev)) != 0) + return i; return 0; } @@ -471,6 +520,9 @@ pci_restore_state(struct pci_dev *dev) int i; int val; + /* PCI Express register must be restored first */ + pci_restore_pcie_state(dev); + /* * The Base Address register should be programmed before the command * register(s) @@ -953,13 +1005,12 @@ static int __devinit pci_setup(char *str) } str = k; } - return 1; + return 0; } +early_param("pci", pci_setup); device_initcall(pci_init); -__setup("pci=", pci_setup); - #if defined(CONFIG_ISA) || defined(CONFIG_EISA) /* FIXME: Some boxes have multiple ISA bridges! */ struct pci_dev *isa_bridge; diff --git a/drivers/pci/pci.h b/drivers/pci/pci.h index 08d58fc78ee..6bf327db5c5 100644 --- a/drivers/pci/pci.h +++ b/drivers/pci/pci.h @@ -42,7 +42,7 @@ extern void pci_remove_legacy_files(struct pci_bus *bus); /* Lock for read/write access to pci device and bus lists */ extern struct rw_semaphore pci_bus_sem; -#ifdef CONFIG_X86_IO_APIC +#ifdef CONFIG_PCI_MSI extern int pci_msi_quirk; #else #define pci_msi_quirk 0 diff --git a/drivers/pci/pcie/Kconfig b/drivers/pci/pcie/Kconfig index 1012db8b8b2..0ad92a8ad8b 100644 --- a/drivers/pci/pcie/Kconfig +++ b/drivers/pci/pcie/Kconfig @@ -34,3 +34,4 @@ config HOTPLUG_PCI_PCIE_POLL_EVENT_MODE When in doubt, say N. +source "drivers/pci/pcie/aer/Kconfig" diff --git a/drivers/pci/pcie/Makefile b/drivers/pci/pcie/Makefile index 984fa87283e..e00fb99acf4 100644 --- a/drivers/pci/pcie/Makefile +++ b/drivers/pci/pcie/Makefile @@ -5,3 +5,6 @@ pcieportdrv-y := portdrv_core.o portdrv_pci.o portdrv_bus.o obj-$(CONFIG_PCIEPORTBUS) += pcieportdrv.o + +# Build PCI Express AER if needed +obj-$(CONFIG_PCIEAER) += aer/ diff --git a/drivers/pci/pcie/aer/Kconfig b/drivers/pci/pcie/aer/Kconfig new file mode 100644 index 00000000000..3f37a60a643 --- /dev/null +++ b/drivers/pci/pcie/aer/Kconfig @@ -0,0 +1,12 @@ +# +# PCI Express Root Port Device AER Configuration +# + +config PCIEAER + boolean "Root Port Advanced Error Reporting support" + depends on PCIEPORTBUS && ACPI + default y + help + This enables PCI Express Root Port Advanced Error Reporting + (AER) driver support. Error reporting messages sent to Root + Port will be handled by PCI Express AER driver. diff --git a/drivers/pci/pcie/aer/Makefile b/drivers/pci/pcie/aer/Makefile new file mode 100644 index 00000000000..15a4f40d520 --- /dev/null +++ b/drivers/pci/pcie/aer/Makefile @@ -0,0 +1,8 @@ +# +# Makefile for PCI-Express Root Port Advanced Error Reporting Driver +# + +obj-$(CONFIG_PCIEAER) += aerdriver.o + +aerdriver-objs := aerdrv_errprint.o aerdrv_core.o aerdrv.o aerdrv_acpi.o + diff --git a/drivers/pci/pcie/aer/aerdrv.c b/drivers/pci/pcie/aer/aerdrv.c new file mode 100644 index 00000000000..0d4ac027d53 --- /dev/null +++ b/drivers/pci/pcie/aer/aerdrv.c @@ -0,0 +1,346 @@ +/* + * drivers/pci/pcie/aer/aerdrv.c + * + * This file is subject to the terms and conditions of the GNU General Public + * License. See the file "COPYING" in the main directory of this archive + * for more details. + * + * This file implements the AER root port service driver. The driver will + * register an irq handler. When root port triggers an AER interrupt, the irq + * handler will collect root port status and schedule a work. + * + * Copyright (C) 2006 Intel Corp. + * Tom Long Nguyen (tom.l.nguyen@intel.com) + * Zhang Yanmin (yanmin.zhang@intel.com) + * + */ + +#include <linux/module.h> +#include <linux/pci.h> +#include <linux/kernel.h> +#include <linux/errno.h> +#include <linux/pm.h> +#include <linux/init.h> +#include <linux/interrupt.h> +#include <linux/delay.h> +#include <linux/pcieport_if.h> + +#include "aerdrv.h" + +/* + * Version Information + */ +#define DRIVER_VERSION "v1.0" +#define DRIVER_AUTHOR "tom.l.nguyen@intel.com" +#define DRIVER_DESC "Root Port Advanced Error Reporting Driver" +MODULE_AUTHOR(DRIVER_AUTHOR); +MODULE_DESCRIPTION(DRIVER_DESC); +MODULE_LICENSE("GPL"); + +static int __devinit aer_probe (struct pcie_device *dev, + const struct pcie_port_service_id *id ); +static void aer_remove(struct pcie_device *dev); +static int aer_suspend(struct pcie_device *dev, pm_message_t state) +{return 0;} +static int aer_resume(struct pcie_device *dev) {return 0;} +static pci_ers_result_t aer_error_detected(struct pci_dev *dev, + enum pci_channel_state error); +static void aer_error_resume(struct pci_dev *dev); +static pci_ers_result_t aer_root_reset(struct pci_dev *dev); + +/* + * PCI Express bus's AER Root service driver data structure + */ +static struct pcie_port_service_id aer_id[] = { + { + .vendor = PCI_ANY_ID, + .device = PCI_ANY_ID, + .port_type = PCIE_RC_PORT, + .service_type = PCIE_PORT_SERVICE_AER, + }, + { /* end: all zeroes */ } +}; + +static struct pci_error_handlers aer_error_handlers = { + .error_detected = aer_error_detected, + .resume = aer_error_resume, +}; + +static struct pcie_port_service_driver aerdrv = { + .name = "aer", + .id_table = &aer_id[0], + + .probe = aer_probe, + .remove = aer_remove, + + .suspend = aer_suspend, + .resume = aer_resume, + + .err_handler = &aer_error_handlers, + + .reset_link = aer_root_reset, +}; + +/** + * aer_irq - Root Port's ISR + * @irq: IRQ assigned to Root Port + * @context: pointer to Root Port data structure + * @r: pointer struct pt_regs + * + * Invoked when Root Port detects AER messages. + **/ +static irqreturn_t aer_irq(int irq, void *context, struct pt_regs * r) +{ + unsigned int status, id; + struct pcie_device *pdev = (struct pcie_device *)context; + struct aer_rpc *rpc = get_service_data(pdev); + int next_prod_idx; + unsigned long flags; + int pos; + + pos = pci_find_aer_capability(pdev->port); + /* + * Must lock access to Root Error Status Reg, Root Error ID Reg, + * and Root error producer/consumer index + */ + spin_lock_irqsave(&rpc->e_lock, flags); + + /* Read error status */ + pci_read_config_dword(pdev->port, pos + PCI_ERR_ROOT_STATUS, &status); + if (!(status & ROOT_ERR_STATUS_MASKS)) { + spin_unlock_irqrestore(&rpc->e_lock, flags); + return IRQ_NONE; + } + + /* Read error source and clear error status */ + pci_read_config_dword(pdev->port, pos + PCI_ERR_ROOT_COR_SRC, &id); + pci_write_config_dword(pdev->port, pos + PCI_ERR_ROOT_STATUS, status); + + /* Store error source for later DPC handler */ + next_prod_idx = rpc->prod_idx + 1; + if (next_prod_idx == AER_ERROR_SOURCES_MAX) + next_prod_idx = 0; + if (next_prod_idx == rpc->cons_idx) { + /* + * Error Storm Condition - possibly the same error occurred. + * Drop the error. + */ + spin_unlock_irqrestore(&rpc->e_lock, flags); + return IRQ_HANDLED; + } + rpc->e_sources[rpc->prod_idx].status = status; + rpc->e_sources[rpc->prod_idx].id = id; + rpc->prod_idx = next_prod_idx; + spin_unlock_irqrestore(&rpc->e_lock, flags); + + /* Invoke DPC handler */ + schedule_work(&rpc->dpc_handler); + + return IRQ_HANDLED; +} + +/** + * aer_alloc_rpc - allocate Root Port data structure + * @dev: pointer to the pcie_dev data structure + * + * Invoked when Root Port's AER service is loaded. + **/ +static struct aer_rpc* aer_alloc_rpc(struct pcie_device *dev) +{ + struct aer_rpc *rpc; + + if (!(rpc = (struct aer_rpc *)kmalloc(sizeof(struct aer_rpc), + GFP_KERNEL))) + return NULL; + + memset(rpc, 0, sizeof(struct aer_rpc)); + /* + * Initialize Root lock access, e_lock, to Root Error Status Reg, + * Root Error ID Reg, and Root error producer/consumer index. + */ + rpc->e_lock = SPIN_LOCK_UNLOCKED; + + rpc->rpd = dev; + INIT_WORK(&rpc->dpc_handler, aer_isr, (void *)dev); + rpc->prod_idx = rpc->cons_idx = 0; + mutex_init(&rpc->rpc_mutex); + init_waitqueue_head(&rpc->wait_release); + + /* Use PCIE bus function to store rpc into PCIE device */ + set_service_data(dev, rpc); + + return rpc; +} + +/** + * aer_remove - clean up resources + * @dev: pointer to the pcie_dev data structure + * + * Invoked when PCI Express bus unloads or AER probe fails. + **/ +static void aer_remove(struct pcie_device *dev) +{ + struct aer_rpc *rpc = get_service_data(dev); + + if (rpc) { + /* If register interrupt service, it must be free. */ + if (rpc->isr) + free_irq(dev->irq, dev); + + wait_event(rpc->wait_release, rpc->prod_idx == rpc->cons_idx); + + aer_delete_rootport(rpc); + set_service_data(dev, NULL); + } +} + +/** + * aer_probe - initialize resources + * @dev: pointer to the pcie_dev data structure + * @id: pointer to the service id data structure + * + * Invoked when PCI Express bus loads AER service driver. + **/ +static int __devinit aer_probe (struct pcie_device *dev, + const struct pcie_port_service_id *id ) +{ + int status; + struct aer_rpc *rpc; + struct device *device = &dev->device; + + /* Init */ + if ((status = aer_init(dev))) + return status; + + /* Alloc rpc data structure */ + if (!(rpc = aer_alloc_rpc(dev))) { + printk(KERN_DEBUG "%s: Alloc rpc fails on PCIE device[%s]\n", + __FUNCTION__, device->bus_id); + aer_remove(dev); + return -ENOMEM; + } + + /* Request IRQ ISR */ + if ((status = request_irq(dev->irq, aer_irq, SA_SHIRQ, "aerdrv", + dev))) { + printk(KERN_DEBUG "%s: Request ISR fails on PCIE device[%s]\n", + __FUNCTION__, device->bus_id); + aer_remove(dev); + return status; + } + + rpc->isr = 1; + + aer_enable_rootport(rpc); + + return status; +} + +/** + * aer_root_reset - reset link on Root Port + * @dev: pointer to Root Port's pci_dev data structure + * + * Invoked by Port Bus driver when performing link reset at Root Port. + **/ +static pci_ers_result_t aer_root_reset(struct pci_dev *dev) +{ + u16 p2p_ctrl; + u32 status; + int pos; + + pos = pci_find_aer_capability(dev); + + /* Disable Root's interrupt in response to error messages */ + pci_write_config_dword(dev, pos + PCI_ERR_ROOT_COMMAND, 0); + + /* Assert Secondary Bus Reset */ + pci_read_config_word(dev, PCI_BRIDGE_CONTROL, &p2p_ctrl); + p2p_ctrl |= PCI_CB_BRIDGE_CTL_CB_RESET; + pci_write_config_word(dev, PCI_BRIDGE_CONTROL, p2p_ctrl); + + /* De-assert Secondary Bus Reset */ + p2p_ctrl &= ~PCI_CB_BRIDGE_CTL_CB_RESET; + pci_write_config_word(dev, PCI_BRIDGE_CONTROL, p2p_ctrl); + + /* + * System software must wait for at least 100ms from the end + * of a reset of one or more device before it is permitted + * to issue Configuration Requests to those devices. + */ + msleep(200); + printk(KERN_DEBUG "Complete link reset at Root[%s]\n", dev->dev.bus_id); + + /* Enable Root Port's interrupt in response to error messages */ + pci_read_config_dword(dev, pos + PCI_ERR_ROOT_STATUS, &status); + pci_write_config_dword(dev, pos + PCI_ERR_ROOT_STATUS, status); + pci_write_config_dword(dev, + pos + PCI_ERR_ROOT_COMMAND, + ROOT_PORT_INTR_ON_MESG_MASK); + + return PCI_ERS_RESULT_RECOVERED; +} + +/** + * aer_error_detected - update severity status + * @dev: pointer to Root Port's pci_dev data structure + * @error: error severity being notified by port bus + * + * Invoked by Port Bus driver during error recovery. + **/ +static pci_ers_result_t aer_error_detected(struct pci_dev *dev, + enum pci_channel_state error) +{ + /* Root Port has no impact. Always recovers. */ + return PCI_ERS_RESULT_CAN_RECOVER; +} + +/** + * aer_error_resume - clean up corresponding error status bits + * @dev: pointer to Root Port's pci_dev data structure + * + * Invoked by Port Bus driver during nonfatal recovery. + **/ +static void aer_error_resume(struct pci_dev *dev) +{ + int pos; + u32 status, mask; + u16 reg16; + + /* Clean up Root device status */ + pos = pci_find_capability(dev, PCI_CAP_ID_EXP); + pci_read_config_word(dev, pos + PCI_EXP_DEVSTA, ®16); + pci_write_config_word(dev, pos + PCI_EXP_DEVSTA, reg16); + + /* Clean AER Root Error Status */ + pos = pci_find_aer_capability(dev); + pci_read_config_dword(dev, pos + PCI_ERR_UNCOR_STATUS, &status); + pci_read_config_dword(dev, pos + PCI_ERR_UNCOR_SEVER, &mask); + if (dev->error_state == pci_channel_io_normal) + status &= ~mask; /* Clear corresponding nonfatal bits */ + else + status &= mask; /* Clear corresponding fatal bits */ + pci_write_config_dword(dev, pos + PCI_ERR_UNCOR_STATUS, status); +} + +/** + * aer_service_init - register AER root service driver + * + * Invoked when AER root service driver is loaded. + **/ +static int __init aer_service_init(void) +{ + return pcie_port_service_register(&aerdrv); +} + +/** + * aer_service_exit - unregister AER root service driver + * + * Invoked when AER root service driver is unloaded. + **/ +static void __exit aer_service_exit(void) +{ + pcie_port_service_unregister(&aerdrv); +} + +module_init(aer_service_init); +module_exit(aer_service_exit); diff --git a/drivers/pci/pcie/aer/aerdrv.h b/drivers/pci/pcie/aer/aerdrv.h new file mode 100644 index 00000000000..daf0cad88fc --- /dev/null +++ b/drivers/pci/pcie/aer/aerdrv.h @@ -0,0 +1,125 @@ +/* + * Copyright (C) 2006 Intel Corp. + * Tom Long Nguyen (tom.l.nguyen@intel.com) + * Zhang Yanmin (yanmin.zhang@intel.com) + * + */ + +#ifndef _AERDRV_H_ +#define _AERDRV_H_ + +#include <linux/pcieport_if.h> +#include <linux/aer.h> + +#define AER_NONFATAL 0 +#define AER_FATAL 1 +#define AER_CORRECTABLE 2 +#define AER_UNCORRECTABLE 4 +#define AER_ERROR_MASK 0x001fffff +#define AER_ERROR(d) (d & AER_ERROR_MASK) + +#define OSC_METHOD_RUN_SUCCESS 0 +#define OSC_METHOD_NOT_SUPPORTED 1 +#define OSC_METHOD_RUN_FAILURE 2 + +/* Root Error Status Register Bits */ +#define ROOT_ERR_STATUS_MASKS 0x0f + +#define SYSTEM_ERROR_INTR_ON_MESG_MASK (PCI_EXP_RTCTL_SECEE| \ + PCI_EXP_RTCTL_SENFEE| \ + PCI_EXP_RTCTL_SEFEE) +#define ROOT_PORT_INTR_ON_MESG_MASK (PCI_ERR_ROOT_CMD_COR_EN| \ + PCI_ERR_ROOT_CMD_NONFATAL_EN| \ + PCI_ERR_ROOT_CMD_FATAL_EN) +#define ERR_COR_ID(d) (d & 0xffff) +#define ERR_UNCOR_ID(d) (d >> 16) + +#define AER_SUCCESS 0 +#define AER_UNSUCCESS 1 +#define AER_ERROR_SOURCES_MAX 100 + +#define AER_LOG_TLP_MASKS (PCI_ERR_UNC_POISON_TLP| \ + PCI_ERR_UNC_ECRC| \ + PCI_ERR_UNC_UNSUP| \ + PCI_ERR_UNC_COMP_ABORT| \ + PCI_ERR_UNC_UNX_COMP| \ + PCI_ERR_UNC_MALF_TLP) + +/* AER Error Info Flags */ +#define AER_TLP_HEADER_VALID_FLAG 0x00000001 +#define AER_MULTI_ERROR_VALID_FLAG 0x00000002 + +#define ERR_CORRECTABLE_ERROR_MASK 0x000031c1 +#define ERR_UNCORRECTABLE_ERROR_MASK 0x001ff010 + +struct header_log_regs { + unsigned int dw0; + unsigned int dw1; + unsigned int dw2; + unsigned int dw3; +}; + +struct aer_err_info { + int severity; /* 0:NONFATAL | 1:FATAL | 2:COR */ + int flags; + unsigned int status; /* COR/UNCOR Error Status */ + struct header_log_regs tlp; /* TLP Header */ +}; + +struct aer_err_source { + unsigned int status; + unsigned int id; +}; + +struct aer_rpc { + struct pcie_device *rpd; /* Root Port device */ + struct work_struct dpc_handler; + struct aer_err_source e_sources[AER_ERROR_SOURCES_MAX]; + unsigned short prod_idx; /* Error Producer Index */ + unsigned short cons_idx; /* Error Consumer Index */ + int isr; + spinlock_t e_lock; /* + * Lock access to Error Status/ID Regs + * and error producer/consumer index + */ + struct mutex rpc_mutex; /* + * only one thread could do + * recovery on the same + * root port hierachy + */ + wait_queue_head_t wait_release; +}; + +struct aer_broadcast_data { + enum pci_channel_state state; + enum pci_ers_result result; +}; + +static inline pci_ers_result_t merge_result(enum pci_ers_result orig, + enum pci_ers_result new) +{ + switch (orig) { + case PCI_ERS_RESULT_CAN_RECOVER: + case PCI_ERS_RESULT_RECOVERED: + orig = new; + break; + case PCI_ERS_RESULT_DISCONNECT: + if (new == PCI_ERS_RESULT_NEED_RESET) + orig = new; + break; + default: + break; + } + + return orig; +} + +extern struct bus_type pcie_port_bus_type; +extern void aer_enable_rootport(struct aer_rpc *rpc); +extern void aer_delete_rootport(struct aer_rpc *rpc); +extern int aer_init(struct pcie_device *dev); +extern void aer_isr(void *context); +extern void aer_print_error(struct pci_dev *dev, struct aer_err_info *info); +extern int aer_osc_setup(struct pci_dev *dev); + +#endif //_AERDRV_H_ diff --git a/drivers/pci/pcie/aer/aerdrv_acpi.c b/drivers/pci/pcie/aer/aerdrv_acpi.c new file mode 100644 index 00000000000..fa68e89ebec --- /dev/null +++ b/drivers/pci/pcie/aer/aerdrv_acpi.c @@ -0,0 +1,68 @@ +/* + * Access ACPI _OSC method + * + * Copyright (C) 2006 Intel Corp. + * Tom Long Nguyen (tom.l.nguyen@intel.com) + * Zhang Yanmin (yanmin.zhang@intel.com) + * + */ + +#include <linux/module.h> +#include <linux/pci.h> +#include <linux/kernel.h> +#include <linux/errno.h> +#include <linux/pm.h> +#include <linux/suspend.h> +#include <linux/acpi.h> +#include <linux/pci-acpi.h> +#include <linux/delay.h> +#include "aerdrv.h" + +/** + * aer_osc_setup - run ACPI _OSC method + * + * Return: + * Zero if success. Nonzero for otherwise. + * + * Invoked when PCIE bus loads AER service driver. To avoid conflict with + * BIOS AER support requires BIOS to yield AER control to OS native driver. + **/ +int aer_osc_setup(struct pci_dev *dev) +{ + int retval = OSC_METHOD_RUN_SUCCESS; + acpi_status status; + acpi_handle handle = DEVICE_ACPI_HANDLE(&dev->dev); + struct pci_dev *pdev = dev; + struct pci_bus *parent; + + while (!handle) { + if (!pdev || !pdev->bus->parent) + break; + parent = pdev->bus->parent; + if (!parent->self) + /* Parent must be a host bridge */ + handle = acpi_get_pci_rootbridge_handle( + pci_domain_nr(parent), + parent->number); + else + handle = DEVICE_ACPI_HANDLE( + &(parent->self->dev)); + pdev = parent->self; + } + + if (!handle) + return OSC_METHOD_NOT_SUPPORTED; + + pci_osc_support_set(OSC_EXT_PCI_CONFIG_SUPPORT); + status = pci_osc_control_set(handle, OSC_PCI_EXPRESS_AER_CONTROL | + OSC_PCI_EXPRESS_CAP_STRUCTURE_CONTROL); + if (ACPI_FAILURE(status)) { + if (status == AE_SUPPORT) + retval = OSC_METHOD_NOT_SUPPORTED; + else + retval = OSC_METHOD_RUN_FAILURE; + } + + return retval; +} + diff --git a/drivers/pci/pcie/aer/aerdrv_core.c b/drivers/pci/pcie/aer/aerdrv_core.c new file mode 100644 index 00000000000..1c7e660d653 --- /dev/null +++ b/drivers/pci/pcie/aer/aerdrv_core.c @@ -0,0 +1,758 @@ +/* + * drivers/pci/pcie/aer/aerdrv_core.c + * + * This file is subject to the terms and conditions of the GNU General Public + * License. See the file "COPYING" in the main directory of this archive + * for more details. + * + * This file implements the core part of PCI-Express AER. When an pci-express + * error is delivered, an error message will be collected and printed to + * console, then, an error recovery procedure will be executed by following + * the pci error recovery rules. + * + * Copyright (C) 2006 Intel Corp. + * Tom Long Nguyen (tom.l.nguyen@intel.com) + * Zhang Yanmin (yanmin.zhang@intel.com) + * + */ + +#include <linux/module.h> +#include <linux/pci.h> +#include <linux/kernel.h> +#include <linux/errno.h> +#include <linux/pm.h> +#include <linux/suspend.h> +#include <linux/acpi.h> +#include <linux/pci-acpi.h> +#include <linux/delay.h> +#include "aerdrv.h" + +static int forceload; +module_param(forceload, bool, 0); + +#define PCI_CFG_SPACE_SIZE (0x100) +int pci_find_aer_capability(struct pci_dev *dev) +{ + int pos; + u32 reg32 = 0; + + /* Check if it's a pci-express device */ + pos = pci_find_capability(dev, PCI_CAP_ID_EXP); + if (!pos) + return 0; + + /* Check if it supports pci-express AER */ + pos = PCI_CFG_SPACE_SIZE; + while (pos) { + if (pci_read_config_dword(dev, pos, ®32)) + return 0; + + /* some broken boards return ~0 */ + if (reg32 == 0xffffffff) + return 0; + + if (PCI_EXT_CAP_ID(reg32) == PCI_EXT_CAP_ID_ERR) + break; + + pos = reg32 >> 20; + } + + return pos; +} + +int pci_enable_pcie_error_reporting(struct pci_dev *dev) +{ + u16 reg16 = 0; + int pos; + + pos = pci_find_capability(dev, PCI_CAP_ID_EXP); + if (!pos) + return -EIO; + + pci_read_config_word(dev, pos+PCI_EXP_DEVCTL, ®16); + reg16 = reg16 | + PCI_EXP_DEVCTL_CERE | + PCI_EXP_DEVCTL_NFERE | + PCI_EXP_DEVCTL_FERE | + PCI_EXP_DEVCTL_URRE; + pci_write_config_word(dev, pos+PCI_EXP_DEVCTL, + reg16); + return 0; +} + +int pci_disable_pcie_error_reporting(struct pci_dev *dev) +{ + u16 reg16 = 0; + int pos; + + pos = pci_find_capability(dev, PCI_CAP_ID_EXP); + if (!pos) + return -EIO; + + pci_read_config_word(dev, pos+PCI_EXP_DEVCTL, ®16); + reg16 = reg16 & ~(PCI_EXP_DEVCTL_CERE | + PCI_EXP_DEVCTL_NFERE | + PCI_EXP_DEVCTL_FERE | + PCI_EXP_DEVCTL_URRE); + pci_write_config_word(dev, pos+PCI_EXP_DEVCTL, + reg16); + return 0; +} + +int pci_cleanup_aer_uncorrect_error_status(struct pci_dev *dev) +{ + int pos; + u32 status, mask; + + pos = pci_find_aer_capability(dev); + if (!pos) + return -EIO; + + pci_read_config_dword(dev, pos + PCI_ERR_UNCOR_STATUS, &status); + pci_read_config_dword(dev, pos + PCI_ERR_UNCOR_SEVER, &mask); + if (dev->error_state == pci_channel_io_normal) + status &= ~mask; /* Clear corresponding nonfatal bits */ + else + status &= mask; /* Clear corresponding fatal bits */ + pci_write_config_dword(dev, pos + PCI_ERR_UNCOR_STATUS, status); + + return 0; +} + +static int find_device_iter(struct device *device, void *data) +{ + struct pci_dev *dev; + u16 id = *(unsigned long *)data; + u8 secondary, subordinate, d_bus = id >> 8; + + if (device->bus == &pci_bus_type) { + dev = to_pci_dev(device); + if (id == ((dev->bus->number << 8) | dev->devfn)) { + /* + * Device ID match + */ + *(unsigned long*)data = (unsigned long)device; + return 1; + } + + /* + * If device is P2P, check if it is an upstream? + */ + if (dev->hdr_type & PCI_HEADER_TYPE_BRIDGE) { + pci_read_config_byte(dev, PCI_SECONDARY_BUS, + &secondary); + pci_read_config_byte(dev, PCI_SUBORDINATE_BUS, + &subordinate); + if (d_bus >= secondary && d_bus <= subordinate) { + *(unsigned long*)data = (unsigned long)device; + return 1; + } + } + } + + return 0; +} + +/** + * find_source_device - search through device hierarchy for source device + * @p_dev: pointer to Root Port pci_dev data structure + * @id: device ID of agent who sends an error message to this Root Port + * + * Invoked when error is detected at the Root Port. + **/ +static struct device* find_source_device(struct pci_dev *parent, u16 id) +{ + struct pci_dev *dev = parent; + struct device *device; + unsigned long device_addr; + int status; + + /* Is Root Port an agent that sends error message? */ + if (id == ((dev->bus->number << 8) | dev->devfn)) + return &dev->dev; + + do { + device_addr = id; + if ((status = device_for_each_child(&dev->dev, + &device_addr, find_device_iter))) { + device = (struct device*)device_addr; + dev = to_pci_dev(device); + if (id == ((dev->bus->number << 8) | dev->devfn)) + return device; + } + }while (status); + + return NULL; +} + +static void report_error_detected(struct pci_dev *dev, void *data) +{ + pci_ers_result_t vote; + struct pci_error_handlers *err_handler; + struct aer_broadcast_data *result_data; + result_data = (struct aer_broadcast_data *) data; + + dev->error_state = result_data->state; + + if (!dev->driver || + !dev->driver->err_handler || + !dev->driver->err_handler->error_detected) { + if (result_data->state == pci_channel_io_frozen && + !(dev->hdr_type & PCI_HEADER_TYPE_BRIDGE)) { + /* + * In case of fatal recovery, if one of down- + * stream device has no driver. We might be + * unable to recover because a later insmod + * of a driver for this device is unaware of + * its hw state. + */ + printk(KERN_DEBUG "Device ID[%s] has %s\n", + dev->dev.bus_id, (dev->driver) ? + "no AER-aware driver" : "no driver"); + } + return; + } + + err_handler = dev->driver->err_handler; + vote = err_handler->error_detected(dev, result_data->state); + result_data->result = merge_result(result_data->result, vote); + return; +} + +static void report_mmio_enabled(struct pci_dev *dev, void *data) +{ + pci_ers_result_t vote; + struct pci_error_handlers *err_handler; + struct aer_broadcast_data *result_data; + result_data = (struct aer_broadcast_data *) data; + + if (!dev->driver || + !dev->driver->err_handler || + !dev->driver->err_handler->mmio_enabled) + return; + + err_handler = dev->driver->err_handler; + vote = err_handler->mmio_enabled(dev); + result_data->result = merge_result(result_data->result, vote); + return; +} + +static void report_slot_reset(struct pci_dev *dev, void *data) +{ + pci_ers_result_t vote; + struct pci_error_handlers *err_handler; + struct aer_broadcast_data *result_data; + result_data = (struct aer_broadcast_data *) data; + + if (!dev->driver || + !dev->driver->err_handler || + !dev->driver->err_handler->slot_reset) + return; + + err_handler = dev->driver->err_handler; + vote = err_handler->slot_reset(dev); + result_data->result = merge_result(result_data->result, vote); + return; +} + +static void report_resume(struct pci_dev *dev, void *data) +{ + struct pci_error_handlers *err_handler; + + dev->error_state = pci_channel_io_normal; + + if (!dev->driver || + !dev->driver->err_handler || + !dev->driver->err_handler->slot_reset) + return; + + err_handler = dev->driver->err_handler; + err_handler->resume(dev); + return; +} + +/** + * broadcast_error_message - handle message broadcast to downstream drivers + * @device: pointer to from where in a hierarchy message is broadcasted down + * @api: callback to be broadcasted + * @state: error state + * + * Invoked during error recovery process. Once being invoked, the content + * of error severity will be broadcasted to all downstream drivers in a + * hierarchy in question. + **/ +static pci_ers_result_t broadcast_error_message(struct pci_dev *dev, + enum pci_channel_state state, + char *error_mesg, + void (*cb)(struct pci_dev *, void *)) +{ + struct aer_broadcast_data result_data; + + printk(KERN_DEBUG "Broadcast %s message\n", error_mesg); + result_data.state = state; + if (cb == report_error_detected) + result_data.result = PCI_ERS_RESULT_CAN_RECOVER; + else + result_data.result = PCI_ERS_RESULT_RECOVERED; + + if (dev->hdr_type & PCI_HEADER_TYPE_BRIDGE) { + /* + * If the error is reported by a bridge, we think this error + * is related to the downstream link of the bridge, so we + * do error recovery on all subordinates of the bridge instead + * of the bridge and clear the error status of the bridge. + */ + if (cb == report_error_detected) + dev->error_state = state; + pci_walk_bus(dev->subordinate, cb, &result_data); + if (cb == report_resume) { + pci_cleanup_aer_uncorrect_error_status(dev); + dev->error_state = pci_channel_io_normal; + } + } + else { + /* + * If the error is reported by an end point, we think this + * error is related to the upstream link of the end point. + */ + pci_walk_bus(dev->bus, cb, &result_data); + } + + return result_data.result; +} + +struct find_aer_service_data { + struct pcie_port_service_driver *aer_driver; + int is_downstream; +}; + +static int find_aer_service_iter(struct device *device, void *data) +{ + struct device_driver *driver; + struct pcie_port_service_driver *service_driver; + struct pcie_device *pcie_dev; + struct find_aer_service_data *result; + + result = (struct find_aer_service_data *) data; + + if (device->bus == &pcie_port_bus_type) { + pcie_dev = to_pcie_device(device); + if (pcie_dev->id.port_type == PCIE_SW_DOWNSTREAM_PORT) + result->is_downstream = 1; + + driver = device->driver; + if (driver) { + service_driver = to_service_driver(driver); + if (service_driver->id_table->service_type == + PCIE_PORT_SERVICE_AER) { + result->aer_driver = service_driver; + return 1; + } + } + } + + return 0; +} + +static void find_aer_service(struct pci_dev *dev, + struct find_aer_service_data *data) +{ + int retval; + retval = device_for_each_child(&dev->dev, data, find_aer_service_iter); +} + +static pci_ers_result_t reset_link(struct pcie_device *aerdev, + struct pci_dev *dev) +{ + struct pci_dev *udev; + pci_ers_result_t status; + struct find_aer_service_data data; + + if (dev->hdr_type & PCI_HEADER_TYPE_BRIDGE) + udev = dev; + else + udev= dev->bus->self; + + data.is_downstream = 0; + data.aer_driver = NULL; + find_aer_service(udev, &data); + + /* + * Use the aer driver of the error agent firstly. + * If it hasn't the aer driver, use the root port's + */ + if (!data.aer_driver || !data.aer_driver->reset_link) { + if (data.is_downstream && + aerdev->device.driver && + to_service_driver(aerdev->device.driver)->reset_link) { + data.aer_driver = + to_service_driver(aerdev->device.driver); + } else { + printk(KERN_DEBUG "No link-reset support to Device ID" + "[%s]\n", + dev->dev.bus_id); + return PCI_ERS_RESULT_DISCONNECT; + } + } + + status = data.aer_driver->reset_link(udev); + if (status != PCI_ERS_RESULT_RECOVERED) { + printk(KERN_DEBUG "Link reset at upstream Device ID" + "[%s] failed\n", + udev->dev.bus_id); + return PCI_ERS_RESULT_DISCONNECT; + } + + return status; +} + +/** + * do_recovery - handle nonfatal/fatal error recovery process + * @aerdev: pointer to a pcie_device data structure of root port + * @dev: pointer to a pci_dev data structure of agent detecting an error + * @severity: error severity type + * + * Invoked when an error is nonfatal/fatal. Once being invoked, broadcast + * error detected message to all downstream drivers within a hierarchy in + * question and return the returned code. + **/ +static pci_ers_result_t do_recovery(struct pcie_device *aerdev, + struct pci_dev *dev, + int severity) +{ + pci_ers_result_t status, result = PCI_ERS_RESULT_RECOVERED; + enum pci_channel_state state; + + if (severity == AER_FATAL) + state = pci_channel_io_frozen; + else + state = pci_channel_io_normal; + + status = broadcast_error_message(dev, + state, + "error_detected", + report_error_detected); + + if (severity == AER_FATAL) { + result = reset_link(aerdev, dev); + if (result != PCI_ERS_RESULT_RECOVERED) { + /* TODO: Should panic here? */ + return result; + } + } + + if (status == PCI_ERS_RESULT_CAN_RECOVER) + status = broadcast_error_message(dev, + state, + "mmio_enabled", + report_mmio_enabled); + + if (status == PCI_ERS_RESULT_NEED_RESET) { + /* + * TODO: Should call platform-specific + * functions to reset slot before calling + * drivers' slot_reset callbacks? + */ + status = broadcast_error_message(dev, + state, + "slot_reset", + report_slot_reset); + } + + if (status == PCI_ERS_RESULT_RECOVERED) + broadcast_error_message(dev, + state, + "resume", + report_resume); + + return status; +} + +/** + * handle_error_source - handle logging error into an event log + * @aerdev: pointer to pcie_device data structure of the root port + * @dev: pointer to pci_dev data structure of error source device + * @info: comprehensive error information + * + * Invoked when an error being detected by Root Port. + **/ +static void handle_error_source(struct pcie_device * aerdev, + struct pci_dev *dev, + struct aer_err_info info) +{ + pci_ers_result_t status = 0; + int pos; + + if (info.severity == AER_CORRECTABLE) { + /* + * Correctable error does not need software intevention. + * No need to go through error recovery process. + */ + pos = pci_find_aer_capability(dev); + if (pos) + pci_write_config_dword(dev, pos + PCI_ERR_COR_STATUS, + info.status); + } else { + status = do_recovery(aerdev, dev, info.severity); + if (status == PCI_ERS_RESULT_RECOVERED) { + printk(KERN_DEBUG "AER driver successfully recovered\n"); + } else { + /* TODO: Should kernel panic here? */ + printk(KERN_DEBUG "AER driver didn't recover\n"); + } + } +} + +/** + * aer_enable_rootport - enable Root Port's interrupts when receiving messages + * @rpc: pointer to a Root Port data structure + * + * Invoked when PCIE bus loads AER service driver. + **/ +void aer_enable_rootport(struct aer_rpc *rpc) +{ + struct pci_dev *pdev = rpc->rpd->port; + int pos, aer_pos; + u16 reg16; + u32 reg32; + + pos = pci_find_capability(pdev, PCI_CAP_ID_EXP); + /* Clear PCIE Capability's Device Status */ + pci_read_config_word(pdev, pos+PCI_EXP_DEVSTA, ®16); + pci_write_config_word(pdev, pos+PCI_EXP_DEVSTA, reg16); + + /* Disable system error generation in response to error messages */ + pci_read_config_word(pdev, pos + PCI_EXP_RTCTL, ®16); + reg16 &= ~(SYSTEM_ERROR_INTR_ON_MESG_MASK); + pci_write_config_word(pdev, pos + PCI_EXP_RTCTL, reg16); + + aer_pos = pci_find_aer_capability(pdev); + /* Clear error status */ + pci_read_config_dword(pdev, aer_pos + PCI_ERR_ROOT_STATUS, ®32); + pci_write_config_dword(pdev, aer_pos + PCI_ERR_ROOT_STATUS, reg32); + pci_read_config_dword(pdev, aer_pos + PCI_ERR_COR_STATUS, ®32); + pci_write_config_dword(pdev, aer_pos + PCI_ERR_COR_STATUS, reg32); + pci_read_config_dword(pdev, aer_pos + PCI_ERR_UNCOR_STATUS, ®32); + pci_write_config_dword(pdev, aer_pos + PCI_ERR_UNCOR_STATUS, reg32); + + /* Enable Root Port device reporting error itself */ + pci_read_config_word(pdev, pos+PCI_EXP_DEVCTL, ®16); + reg16 = reg16 | + PCI_EXP_DEVCTL_CERE | + PCI_EXP_DEVCTL_NFERE | + PCI_EXP_DEVCTL_FERE | + PCI_EXP_DEVCTL_URRE; + pci_write_config_word(pdev, pos+PCI_EXP_DEVCTL, + reg16); + + /* Enable Root Port's interrupt in response to error messages */ + pci_write_config_dword(pdev, + aer_pos + PCI_ERR_ROOT_COMMAND, + ROOT_PORT_INTR_ON_MESG_MASK); +} + +/** + * disable_root_aer - disable Root Port's interrupts when receiving messages + * @rpc: pointer to a Root Port data structure + * + * Invoked when PCIE bus unloads AER service driver. + **/ +static void disable_root_aer(struct aer_rpc *rpc) +{ + struct pci_dev *pdev = rpc->rpd->port; + u32 reg32; + int pos; + + pos = pci_find_aer_capability(pdev); + /* Disable Root's interrupt in response to error messages */ + pci_write_config_dword(pdev, pos + PCI_ERR_ROOT_COMMAND, 0); + + /* Clear Root's error status reg */ + pci_read_config_dword(pdev, pos + PCI_ERR_ROOT_STATUS, ®32); + pci_write_config_dword(pdev, pos + PCI_ERR_ROOT_STATUS, reg32); +} + +/** + * get_e_source - retrieve an error source + * @rpc: pointer to the root port which holds an error + * + * Invoked by DPC handler to consume an error. + **/ +static struct aer_err_source* get_e_source(struct aer_rpc *rpc) +{ + struct aer_err_source *e_source; + unsigned long flags; + + /* Lock access to Root error producer/consumer index */ + spin_lock_irqsave(&rpc->e_lock, flags); + if (rpc->prod_idx == rpc->cons_idx) { + spin_unlock_irqrestore(&rpc->e_lock, flags); + return NULL; + } + e_source = &rpc->e_sources[rpc->cons_idx]; + rpc->cons_idx++; + if (rpc->cons_idx == AER_ERROR_SOURCES_MAX) + rpc->cons_idx = 0; + spin_unlock_irqrestore(&rpc->e_lock, flags); + + return e_source; +} + +static int get_device_error_info(struct pci_dev *dev, struct aer_err_info *info) +{ + int pos; + + pos = pci_find_aer_capability(dev); + + /* The device might not support AER */ + if (!pos) + return AER_SUCCESS; + + if (info->severity == AER_CORRECTABLE) { + pci_read_config_dword(dev, pos + PCI_ERR_COR_STATUS, + &info->status); + if (!(info->status & ERR_CORRECTABLE_ERROR_MASK)) + return AER_UNSUCCESS; + } else if (dev->hdr_type & PCI_HEADER_TYPE_BRIDGE || + info->severity == AER_NONFATAL) { + + /* Link is still healthy for IO reads */ + pci_read_config_dword(dev, pos + PCI_ERR_UNCOR_STATUS, + &info->status); + if (!(info->status & ERR_UNCORRECTABLE_ERROR_MASK)) + return AER_UNSUCCESS; + + if (info->status & AER_LOG_TLP_MASKS) { + info->flags |= AER_TLP_HEADER_VALID_FLAG; + pci_read_config_dword(dev, + pos + PCI_ERR_HEADER_LOG, &info->tlp.dw0); + pci_read_config_dword(dev, + pos + PCI_ERR_HEADER_LOG + 4, &info->tlp.dw1); + pci_read_config_dword(dev, + pos + PCI_ERR_HEADER_LOG + 8, &info->tlp.dw2); + pci_read_config_dword(dev, + pos + PCI_ERR_HEADER_LOG + 12, &info->tlp.dw3); + } + } + + return AER_SUCCESS; +} + +/** + * aer_isr_one_error - consume an error detected by root port + * @p_device: pointer to error root port service device + * @e_src: pointer to an error source + **/ +static void aer_isr_one_error(struct pcie_device *p_device, + struct aer_err_source *e_src) +{ + struct device *s_device; + struct aer_err_info e_info = {0, 0, 0,}; + int i; + u16 id; + + /* + * There is a possibility that both correctable error and + * uncorrectable error being logged. Report correctable error first. + */ + for (i = 1; i & ROOT_ERR_STATUS_MASKS ; i <<= 2) { + if (i > 4) + break; + if (!(e_src->status & i)) + continue; + + /* Init comprehensive error information */ + if (i & PCI_ERR_ROOT_COR_RCV) { + id = ERR_COR_ID(e_src->id); + e_info.severity = AER_CORRECTABLE; + } else { + id = ERR_UNCOR_ID(e_src->id); + e_info.severity = ((e_src->status >> 6) & 1); + } + if (e_src->status & + (PCI_ERR_ROOT_MULTI_COR_RCV | + PCI_ERR_ROOT_MULTI_UNCOR_RCV)) + e_info.flags |= AER_MULTI_ERROR_VALID_FLAG; + if (!(s_device = find_source_device(p_device->port, id))) { + printk(KERN_DEBUG "%s->can't find device of ID%04x\n", + __FUNCTION__, id); + continue; + } + if (get_device_error_info(to_pci_dev(s_device), &e_info) == + AER_SUCCESS) { + aer_print_error(to_pci_dev(s_device), &e_info); + handle_error_source(p_device, + to_pci_dev(s_device), + e_info); + } + } +} + +/** + * aer_isr - consume errors detected by root port + * @context: pointer to a private data of pcie device + * + * Invoked, as DPC, when root port records new detected error + **/ +void aer_isr(void *context) +{ + struct pcie_device *p_device = (struct pcie_device *) context; + struct aer_rpc *rpc = get_service_data(p_device); + struct aer_err_source *e_src; + + mutex_lock(&rpc->rpc_mutex); + e_src = get_e_source(rpc); + while (e_src) { + aer_isr_one_error(p_device, e_src); + e_src = get_e_source(rpc); + } + mutex_unlock(&rpc->rpc_mutex); + + wake_up(&rpc->wait_release); +} + +/** + * aer_delete_rootport - disable root port aer and delete service data + * @rpc: pointer to a root port device being deleted + * + * Invoked when AER service unloaded on a specific Root Port + **/ +void aer_delete_rootport(struct aer_rpc *rpc) +{ + /* Disable root port AER itself */ + disable_root_aer(rpc); + + kfree(rpc); +} + +/** + * aer_init - provide AER initialization + * @dev: pointer to AER pcie device + * + * Invoked when AER service driver is loaded. + **/ +int aer_init(struct pcie_device *dev) +{ + int status; + + /* Run _OSC Method */ + status = aer_osc_setup(dev->port); + + if(status != OSC_METHOD_RUN_SUCCESS) { + printk(KERN_DEBUG "%s: AER service init fails - %s\n", + __FUNCTION__, + (status == OSC_METHOD_NOT_SUPPORTED) ? + "No ACPI _OSC support" : "Run ACPI _OSC fails"); + + if (!forceload) + return status; + } + + return AER_SUCCESS; +} + +EXPORT_SYMBOL_GPL(pci_find_aer_capability); +EXPORT_SYMBOL_GPL(pci_enable_pcie_error_reporting); +EXPORT_SYMBOL_GPL(pci_disable_pcie_error_reporting); +EXPORT_SYMBOL_GPL(pci_cleanup_aer_uncorrect_error_status); + diff --git a/drivers/pci/pcie/aer/aerdrv_errprint.c b/drivers/pci/pcie/aer/aerdrv_errprint.c new file mode 100644 index 00000000000..3933d4f30e8 --- /dev/null +++ b/drivers/pci/pcie/aer/aerdrv_errprint.c @@ -0,0 +1,248 @@ +/* + * drivers/pci/pcie/aer/aerdrv_errprint.c + * + * This file is subject to the terms and conditions of the GNU General Public + * License. See the file "COPYING" in the main directory of this archive + * for more details. + * + * Format error messages and print them to console. + * + * Copyright (C) 2006 Intel Corp. + * Tom Long Nguyen (tom.l.nguyen@intel.com) + * Zhang Yanmin (yanmin.zhang@intel.com) + * + */ + +#include <linux/module.h> +#include <linux/pci.h> +#include <linux/kernel.h> +#include <linux/errno.h> +#include <linux/pm.h> +#include <linux/suspend.h> + +#include "aerdrv.h" + +#define AER_AGENT_RECEIVER 0 +#define AER_AGENT_REQUESTER 1 +#define AER_AGENT_COMPLETER 2 +#define AER_AGENT_TRANSMITTER 3 + +#define AER_AGENT_REQUESTER_MASK (PCI_ERR_UNC_COMP_TIME| \ + PCI_ERR_UNC_UNSUP) + +#define AER_AGENT_COMPLETER_MASK PCI_ERR_UNC_COMP_ABORT + +#define AER_AGENT_TRANSMITTER_MASK(t, e) (e & (PCI_ERR_COR_REP_ROLL| \ + ((t == AER_CORRECTABLE) ? PCI_ERR_COR_REP_TIMER: 0))) + +#define AER_GET_AGENT(t, e) \ + ((e & AER_AGENT_COMPLETER_MASK) ? AER_AGENT_COMPLETER : \ + (e & AER_AGENT_REQUESTER_MASK) ? AER_AGENT_REQUESTER : \ + (AER_AGENT_TRANSMITTER_MASK(t, e)) ? AER_AGENT_TRANSMITTER : \ + AER_AGENT_RECEIVER) + +#define AER_PHYSICAL_LAYER_ERROR_MASK PCI_ERR_COR_RCVR +#define AER_DATA_LINK_LAYER_ERROR_MASK(t, e) \ + (PCI_ERR_UNC_DLP| \ + PCI_ERR_COR_BAD_TLP| \ + PCI_ERR_COR_BAD_DLLP| \ + PCI_ERR_COR_REP_ROLL| \ + ((t == AER_CORRECTABLE) ? \ + PCI_ERR_COR_REP_TIMER: 0)) + +#define AER_PHYSICAL_LAYER_ERROR 0 +#define AER_DATA_LINK_LAYER_ERROR 1 +#define AER_TRANSACTION_LAYER_ERROR 2 + +#define AER_GET_LAYER_ERROR(t, e) \ + ((e & AER_PHYSICAL_LAYER_ERROR_MASK) ? \ + AER_PHYSICAL_LAYER_ERROR : \ + (e & AER_DATA_LINK_LAYER_ERROR_MASK(t, e)) ? \ + AER_DATA_LINK_LAYER_ERROR : \ + AER_TRANSACTION_LAYER_ERROR) + +/* + * AER error strings + */ +static char* aer_error_severity_string[] = { + "Uncorrected (Non-Fatal)", + "Uncorrected (Fatal)", + "Corrected" +}; + +static char* aer_error_layer[] = { + "Physical Layer", + "Data Link Layer", + "Transaction Layer" +}; +static char* aer_correctable_error_string[] = { + "Receiver Error ", /* Bit Position 0 */ + NULL, + NULL, + NULL, + NULL, + NULL, + "Bad TLP ", /* Bit Position 6 */ + "Bad DLLP ", /* Bit Position 7 */ + "RELAY_NUM Rollover ", /* Bit Position 8 */ + NULL, + NULL, + NULL, + "Replay Timer Timeout ", /* Bit Position 12 */ + "Advisory Non-Fatal ", /* Bit Position 13 */ + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, +}; + +static char* aer_uncorrectable_error_string[] = { + NULL, + NULL, + NULL, + NULL, + "Data Link Protocol ", /* Bit Position 4 */ + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + "Poisoned TLP ", /* Bit Position 12 */ + "Flow Control Protocol ", /* Bit Position 13 */ + "Completion Timeout ", /* Bit Position 14 */ + "Completer Abort ", /* Bit Position 15 */ + "Unexpected Completion ", /* Bit Position 16 */ + "Receiver Overflow ", /* Bit Position 17 */ + "Malformed TLP ", /* Bit Position 18 */ + "ECRC ", /* Bit Position 19 */ + "Unsupported Request ", /* Bit Position 20 */ + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, +}; + +static char* aer_agent_string[] = { + "Receiver ID", + "Requester ID", + "Completer ID", + "Transmitter ID" +}; + +static char * aer_get_error_source_name(int severity, + unsigned int status, + char errmsg_buff[]) +{ + int i; + char * errmsg = NULL; + + for (i = 0; i < 32; i++) { + if (!(status & (1 << i))) + continue; + + if (severity == AER_CORRECTABLE) + errmsg = aer_correctable_error_string[i]; + else + errmsg = aer_uncorrectable_error_string[i]; + + if (!errmsg) { + sprintf(errmsg_buff, "Unknown Error Bit %2d ", i); + errmsg = errmsg_buff; + } + + break; + } + + return errmsg; +} + +static DEFINE_SPINLOCK(logbuf_lock); +static char errmsg_buff[100]; +void aer_print_error(struct pci_dev *dev, struct aer_err_info *info) +{ + char * errmsg; + int err_layer, agent; + char * loglevel; + + if (info->severity == AER_CORRECTABLE) + loglevel = KERN_WARNING; + else + loglevel = KERN_ERR; + + printk("%s+------ PCI-Express Device Error ------+\n", loglevel); + printk("%sError Severity\t\t: %s\n", loglevel, + aer_error_severity_string[info->severity]); + + if ( info->status == 0) { + printk("%sPCIE Bus Error type\t: (Unaccessible)\n", loglevel); + printk("%sUnaccessible Received\t: %s\n", loglevel, + info->flags & AER_MULTI_ERROR_VALID_FLAG ? + "Multiple" : "First"); + printk("%sUnregistered Agent ID\t: %04x\n", loglevel, + (dev->bus->number << 8) | dev->devfn); + } else { + err_layer = AER_GET_LAYER_ERROR(info->severity, info->status); + printk("%sPCIE Bus Error type\t: %s\n", loglevel, + aer_error_layer[err_layer]); + + spin_lock(&logbuf_lock); + errmsg = aer_get_error_source_name(info->severity, + info->status, + errmsg_buff); + printk("%s%s\t: %s\n", loglevel, errmsg, + info->flags & AER_MULTI_ERROR_VALID_FLAG ? + "Multiple" : "First"); + spin_unlock(&logbuf_lock); + + agent = AER_GET_AGENT(info->severity, info->status); + printk("%s%s\t\t: %04x\n", loglevel, + aer_agent_string[agent], + (dev->bus->number << 8) | dev->devfn); + + printk("%sVendorID=%04xh, DeviceID=%04xh," + " Bus=%02xh, Device=%02xh, Function=%02xh\n", + loglevel, + dev->vendor, + dev->device, + dev->bus->number, + PCI_SLOT(dev->devfn), + PCI_FUNC(dev->devfn)); + + if (info->flags & AER_TLP_HEADER_VALID_FLAG) { + unsigned char *tlp = (unsigned char *) &info->tlp; + printk("%sTLB Header:\n", loglevel); + printk("%s%02x%02x%02x%02x %02x%02x%02x%02x" + " %02x%02x%02x%02x %02x%02x%02x%02x\n", + loglevel, + *(tlp + 3), *(tlp + 2), *(tlp + 1), *tlp, + *(tlp + 7), *(tlp + 6), *(tlp + 5), *(tlp + 4), + *(tlp + 11), *(tlp + 10), *(tlp + 9), + *(tlp + 8), *(tlp + 15), *(tlp + 14), + *(tlp + 13), *(tlp + 12)); + } + } +} + diff --git a/drivers/pci/pcie/portdrv.h b/drivers/pci/pcie/portdrv.h index 1d317d22ee8..67fcd176bab 100644 --- a/drivers/pci/pcie/portdrv.h +++ b/drivers/pci/pcie/portdrv.h @@ -39,7 +39,7 @@ extern int pcie_port_device_suspend(struct pci_dev *dev, pm_message_t state); extern int pcie_port_device_resume(struct pci_dev *dev); #endif extern void pcie_port_device_remove(struct pci_dev *dev); -extern void pcie_port_bus_register(void); +extern int pcie_port_bus_register(void); extern void pcie_port_bus_unregister(void); #endif /* _PORTDRV_H_ */ diff --git a/drivers/pci/pcie/portdrv_bus.c b/drivers/pci/pcie/portdrv_bus.c index 3e84b501e6a..3f0976868ed 100644 --- a/drivers/pci/pcie/portdrv_bus.c +++ b/drivers/pci/pcie/portdrv_bus.c @@ -24,6 +24,7 @@ struct bus_type pcie_port_bus_type = { .suspend = pcie_port_bus_suspend, .resume = pcie_port_bus_resume, }; +EXPORT_SYMBOL_GPL(pcie_port_bus_type); static int pcie_port_bus_match(struct device *dev, struct device_driver *drv) { diff --git a/drivers/pci/pcie/portdrv_core.c b/drivers/pci/pcie/portdrv_core.c index 55c66226786..bd6615b4d40 100644 --- a/drivers/pci/pcie/portdrv_core.c +++ b/drivers/pci/pcie/portdrv_core.c @@ -6,6 +6,7 @@ * Copyright (C) Tom Long Nguyen (tom.l.nguyen@intel.com) */ +#include <linux/compiler.h> #include <linux/module.h> #include <linux/pci.h> #include <linux/kernel.h> @@ -339,8 +340,7 @@ static int suspend_iter(struct device *dev, void *data) int pcie_port_device_suspend(struct pci_dev *dev, pm_message_t state) { - device_for_each_child(&dev->dev, &state, suspend_iter); - return 0; + return device_for_each_child(&dev->dev, &state, suspend_iter); } static int resume_iter(struct device *dev, void *data) @@ -358,8 +358,7 @@ static int resume_iter(struct device *dev, void *data) int pcie_port_device_resume(struct pci_dev *dev) { - device_for_each_child(&dev->dev, NULL, resume_iter); - return 0; + return device_for_each_child(&dev->dev, NULL, resume_iter); } #endif @@ -402,9 +401,9 @@ void pcie_port_device_remove(struct pci_dev *dev) pci_disable_msi(dev); } -void pcie_port_bus_register(void) +int __must_check pcie_port_bus_register(void) { - bus_register(&pcie_port_bus_type); + return bus_register(&pcie_port_bus_type); } void pcie_port_bus_unregister(void) diff --git a/drivers/pci/pcie/portdrv_pci.c b/drivers/pci/pcie/portdrv_pci.c index 478d0d28f7a..037690e08f5 100644 --- a/drivers/pci/pcie/portdrv_pci.c +++ b/drivers/pci/pcie/portdrv_pci.c @@ -14,8 +14,10 @@ #include <linux/init.h> #include <linux/slab.h> #include <linux/pcieport_if.h> +#include <linux/aer.h> #include "portdrv.h" +#include "aer/aerdrv.h" /* * Version Information @@ -30,6 +32,43 @@ MODULE_LICENSE("GPL"); /* global data */ static const char device_name[] = "pcieport-driver"; +static int pcie_portdrv_save_config(struct pci_dev *dev) +{ + return pci_save_state(dev); +} + +#ifdef CONFIG_PM +static int pcie_portdrv_restore_config(struct pci_dev *dev) +{ + int retval; + + pci_restore_state(dev); + retval = pci_enable_device(dev); + if (retval) + return retval; + pci_set_master(dev); + return 0; +} + +static int pcie_portdrv_suspend(struct pci_dev *dev, pm_message_t state) +{ + int ret = pcie_port_device_suspend(dev, state); + + if (!ret) + ret = pcie_portdrv_save_config(dev); + return ret; +} + +static int pcie_portdrv_resume(struct pci_dev *dev) +{ + pcie_portdrv_restore_config(dev); + return pcie_port_device_resume(dev); +} +#else +#define pcie_portdrv_suspend NULL +#define pcie_portdrv_resume NULL +#endif + /* * pcie_portdrv_probe - Probe PCI-Express port devices * @dev: PCI-Express port device being probed @@ -61,6 +100,10 @@ static int __devinit pcie_portdrv_probe (struct pci_dev *dev, return -ENOMEM; } + pcie_portdrv_save_config(dev); + + pci_enable_pcie_error_reporting(dev); + return 0; } @@ -70,39 +113,151 @@ static void pcie_portdrv_remove (struct pci_dev *dev) kfree(pci_get_drvdata(dev)); } -#ifdef CONFIG_PM -static int pcie_portdrv_save_config(struct pci_dev *dev) +static int error_detected_iter(struct device *device, void *data) { - return pci_save_state(dev); + struct pcie_device *pcie_device; + struct pcie_port_service_driver *driver; + struct aer_broadcast_data *result_data; + pci_ers_result_t status; + + result_data = (struct aer_broadcast_data *) data; + + if (device->bus == &pcie_port_bus_type && device->driver) { + driver = to_service_driver(device->driver); + if (!driver || + !driver->err_handler || + !driver->err_handler->error_detected) + return 0; + + pcie_device = to_pcie_device(device); + + /* Forward error detected message to service drivers */ + status = driver->err_handler->error_detected( + pcie_device->port, + result_data->state); + result_data->result = + merge_result(result_data->result, status); + } + + return 0; } -static int pcie_portdrv_restore_config(struct pci_dev *dev) +static pci_ers_result_t pcie_portdrv_error_detected(struct pci_dev *dev, + enum pci_channel_state error) { + struct aer_broadcast_data result_data = + {error, PCI_ERS_RESULT_CAN_RECOVER}; int retval; - pci_restore_state(dev); - retval = pci_enable_device(dev); - if (retval) - return retval; - pci_set_master(dev); + /* can not fail */ + retval = device_for_each_child(&dev->dev, &result_data, error_detected_iter); + + return result_data.result; +} + +static int mmio_enabled_iter(struct device *device, void *data) +{ + struct pcie_device *pcie_device; + struct pcie_port_service_driver *driver; + pci_ers_result_t status, *result; + + result = (pci_ers_result_t *) data; + + if (device->bus == &pcie_port_bus_type && device->driver) { + driver = to_service_driver(device->driver); + if (driver && + driver->err_handler && + driver->err_handler->mmio_enabled) { + pcie_device = to_pcie_device(device); + + /* Forward error message to service drivers */ + status = driver->err_handler->mmio_enabled( + pcie_device->port); + *result = merge_result(*result, status); + } + } + return 0; } -static int pcie_portdrv_suspend (struct pci_dev *dev, pm_message_t state) +static pci_ers_result_t pcie_portdrv_mmio_enabled(struct pci_dev *dev) { - int ret = pcie_port_device_suspend(dev, state); + pci_ers_result_t status = PCI_ERS_RESULT_RECOVERED; + int retval; - if (!ret) - ret = pcie_portdrv_save_config(dev); - return ret; + /* get true return value from &status */ + retval = device_for_each_child(&dev->dev, &status, mmio_enabled_iter); + return status; } -static int pcie_portdrv_resume (struct pci_dev *dev) +static int slot_reset_iter(struct device *device, void *data) { - pcie_portdrv_restore_config(dev); - return pcie_port_device_resume(dev); + struct pcie_device *pcie_device; + struct pcie_port_service_driver *driver; + pci_ers_result_t status, *result; + + result = (pci_ers_result_t *) data; + + if (device->bus == &pcie_port_bus_type && device->driver) { + driver = to_service_driver(device->driver); + if (driver && + driver->err_handler && + driver->err_handler->slot_reset) { + pcie_device = to_pcie_device(device); + + /* Forward error message to service drivers */ + status = driver->err_handler->slot_reset( + pcie_device->port); + *result = merge_result(*result, status); + } + } + + return 0; +} + +static pci_ers_result_t pcie_portdrv_slot_reset(struct pci_dev *dev) +{ + pci_ers_result_t status; + int retval; + + /* If fatal, restore cfg space for possible link reset at upstream */ + if (dev->error_state == pci_channel_io_frozen) { + pcie_portdrv_restore_config(dev); + pci_enable_pcie_error_reporting(dev); + } + + /* get true return value from &status */ + retval = device_for_each_child(&dev->dev, &status, slot_reset_iter); + + return status; +} + +static int resume_iter(struct device *device, void *data) +{ + struct pcie_device *pcie_device; + struct pcie_port_service_driver *driver; + + if (device->bus == &pcie_port_bus_type && device->driver) { + driver = to_service_driver(device->driver); + if (driver && + driver->err_handler && + driver->err_handler->resume) { + pcie_device = to_pcie_device(device); + + /* Forward error message to service drivers */ + driver->err_handler->resume(pcie_device->port); + } + } + + return 0; +} + +static void pcie_portdrv_err_resume(struct pci_dev *dev) +{ + int retval; + /* nothing to do with error value, if it ever happens */ + retval = device_for_each_child(&dev->dev, NULL, resume_iter); } -#endif /* * LINUX Device Driver Model @@ -114,6 +269,13 @@ static const struct pci_device_id port_pci_ids[] = { { }; MODULE_DEVICE_TABLE(pci, port_pci_ids); +static struct pci_error_handlers pcie_portdrv_err_handler = { + .error_detected = pcie_portdrv_error_detected, + .mmio_enabled = pcie_portdrv_mmio_enabled, + .slot_reset = pcie_portdrv_slot_reset, + .resume = pcie_portdrv_err_resume, +}; + static struct pci_driver pcie_portdrv = { .name = (char *)device_name, .id_table = &port_pci_ids[0], @@ -121,20 +283,25 @@ static struct pci_driver pcie_portdrv = { .probe = pcie_portdrv_probe, .remove = pcie_portdrv_remove, -#ifdef CONFIG_PM .suspend = pcie_portdrv_suspend, .resume = pcie_portdrv_resume, -#endif /* PM */ + + .err_handler = &pcie_portdrv_err_handler, }; static int __init pcie_portdrv_init(void) { - int retval = 0; + int retval; - pcie_port_bus_register(); + retval = pcie_port_bus_register(); + if (retval) { + printk(KERN_WARNING "PCIE: bus_register error: %d\n", retval); + goto out; + } retval = pci_register_driver(&pcie_portdrv); if (retval) pcie_port_bus_unregister(); + out: return retval; } diff --git a/drivers/pci/probe.c b/drivers/pci/probe.c index c5a58d1c6c1..a3b0a5eb505 100644 --- a/drivers/pci/probe.c +++ b/drivers/pci/probe.c @@ -339,6 +339,7 @@ pci_alloc_child_bus(struct pci_bus *parent, struct pci_dev *bridge, int busnr) { struct pci_bus *child; int i; + int retval; /* * Allocate a new bus, and inherit stuff from the parent.. @@ -356,8 +357,13 @@ pci_alloc_child_bus(struct pci_bus *parent, struct pci_dev *bridge, int busnr) child->class_dev.class = &pcibus_class; sprintf(child->class_dev.class_id, "%04x:%02x", pci_domain_nr(child), busnr); - class_device_register(&child->class_dev); - class_device_create_file(&child->class_dev, &class_device_attr_cpuaffinity); + retval = class_device_register(&child->class_dev); + if (retval) + goto error_register; + retval = class_device_create_file(&child->class_dev, + &class_device_attr_cpuaffinity); + if (retval) + goto error_file_create; /* * Set up the primary, secondary and subordinate @@ -375,6 +381,12 @@ pci_alloc_child_bus(struct pci_bus *parent, struct pci_dev *bridge, int busnr) bridge->subordinate = child; return child; + +error_file_create: + class_device_unregister(&child->class_dev); +error_register: + kfree(child); + return NULL; } struct pci_bus * __devinit pci_add_new_bus(struct pci_bus *parent, struct pci_dev *dev, int busnr) diff --git a/drivers/pci/quirks.c b/drivers/pci/quirks.c index def78a2a7c1..08cd86a6dd6 100644 --- a/drivers/pci/quirks.c +++ b/drivers/pci/quirks.c @@ -577,8 +577,6 @@ static void __init quirk_ioapic_rmw(struct pci_dev *dev) } DECLARE_PCI_FIXUP_FINAL(PCI_VENDOR_ID_SI, PCI_ANY_ID, quirk_ioapic_rmw ); -int pci_msi_quirk; - #define AMD8131_revA0 0x01 #define AMD8131_revB0 0x11 #define AMD8131_MISC 0x40 @@ -587,12 +585,6 @@ static void __init quirk_amd_8131_ioapic(struct pci_dev *dev) { unsigned char revid, tmp; - if (dev->subordinate) { - printk(KERN_WARNING "PCI: MSI quirk detected. " - "PCI_BUS_FLAGS_NO_MSI set for subordinate bus.\n"); - dev->subordinate->bus_flags |= PCI_BUS_FLAGS_NO_MSI; - } - if (nr_ioapics == 0) return; @@ -605,13 +597,6 @@ static void __init quirk_amd_8131_ioapic(struct pci_dev *dev) } } DECLARE_PCI_FIXUP_FINAL(PCI_VENDOR_ID_AMD, PCI_DEVICE_ID_AMD_8131_BRIDGE, quirk_amd_8131_ioapic); - -static void __init quirk_svw_msi(struct pci_dev *dev) -{ - pci_msi_quirk = 1; - printk(KERN_WARNING "PCI: MSI quirk detected. pci_msi_quirk set.\n"); -} -DECLARE_PCI_FIXUP_FINAL(PCI_VENDOR_ID_SERVERWORKS, PCI_DEVICE_ID_SERVERWORKS_GCNB_LE, quirk_svw_msi ); #endif /* CONFIG_X86_IO_APIC */ @@ -1690,6 +1675,95 @@ static void __devinit quirk_nvidia_ck804_pcie_aer_ext_cap(struct pci_dev *dev) DECLARE_PCI_FIXUP_FINAL(PCI_VENDOR_ID_NVIDIA, PCI_DEVICE_ID_NVIDIA_CK804_PCIE, quirk_nvidia_ck804_pcie_aer_ext_cap); +#ifdef CONFIG_PCI_MSI +/* To disable MSI globally */ +int pci_msi_quirk; + +/* The Serverworks PCI-X chipset does not support MSI. We cannot easily rely + * on setting PCI_BUS_FLAGS_NO_MSI in its bus flags because there are actually + * some other busses controlled by the chipset even if Linux is not aware of it. + * Instead of setting the flag on all busses in the machine, simply disable MSI + * globally. + */ +static void __init quirk_svw_msi(struct pci_dev *dev) +{ + pci_msi_quirk = 1; + printk(KERN_WARNING "PCI: MSI quirk detected. pci_msi_quirk set.\n"); +} +DECLARE_PCI_FIXUP_FINAL(PCI_VENDOR_ID_SERVERWORKS, PCI_DEVICE_ID_SERVERWORKS_GCNB_LE, quirk_svw_msi); + +/* Disable MSI on chipsets that are known to not support it */ +static void __devinit quirk_disable_msi(struct pci_dev *dev) +{ + if (dev->subordinate) { + printk(KERN_WARNING "PCI: MSI quirk detected. " + "PCI_BUS_FLAGS_NO_MSI set for %s subordinate bus.\n", + pci_name(dev)); + dev->subordinate->bus_flags |= PCI_BUS_FLAGS_NO_MSI; + } +} +DECLARE_PCI_FIXUP_FINAL(PCI_VENDOR_ID_AMD, PCI_DEVICE_ID_AMD_8131_BRIDGE, quirk_disable_msi); + +/* Go through the list of Hypertransport capabilities and + * return 1 if a HT MSI capability is found and enabled */ +static int __devinit msi_ht_cap_enabled(struct pci_dev *dev) +{ + u8 pos; + int ttl; + for (pos = pci_find_capability(dev, PCI_CAP_ID_HT), ttl = 48; + pos && ttl; + pos = pci_find_next_capability(dev, pos, PCI_CAP_ID_HT), ttl--) { + u32 cap_hdr; + /* MSI mapping section according to Hypertransport spec */ + if (pci_read_config_dword(dev, pos, &cap_hdr) == 0 + && (cap_hdr & 0xf8000000) == 0xa8000000 /* MSI mapping */) { + printk(KERN_INFO "PCI: Found HT MSI mapping on %s with capability %s\n", + pci_name(dev), cap_hdr & 0x10000 ? "enabled" : "disabled"); + return (cap_hdr & 0x10000) != 0; /* MSI mapping cap enabled */ + } + } + return 0; +} + +/* Check the hypertransport MSI mapping to know whether MSI is enabled or not */ +static void __devinit quirk_msi_ht_cap(struct pci_dev *dev) +{ + if (dev->subordinate && !msi_ht_cap_enabled(dev)) { + printk(KERN_WARNING "PCI: MSI quirk detected. " + "MSI disabled on chipset %s.\n", + pci_name(dev)); + dev->subordinate->bus_flags |= PCI_BUS_FLAGS_NO_MSI; + } +} +DECLARE_PCI_FIXUP_FINAL(PCI_VENDOR_ID_SERVERWORKS, PCI_DEVICE_ID_SERVERWORKS_HT2000_PCIE, + quirk_msi_ht_cap); + +/* The nVidia CK804 chipset may have 2 HT MSI mappings. + * MSI are supported if the MSI capability set in any of these mappings. + */ +static void __devinit quirk_nvidia_ck804_msi_ht_cap(struct pci_dev *dev) +{ + struct pci_dev *pdev; + + if (!dev->subordinate) + return; + + /* check HT MSI cap on this chipset and the root one. + * a single one having MSI is enough to be sure that MSI are supported. + */ + pdev = pci_find_slot(dev->bus->number, 0); + if (dev->subordinate && !msi_ht_cap_enabled(dev) + && !msi_ht_cap_enabled(pdev)) { + printk(KERN_WARNING "PCI: MSI quirk detected. " + "MSI disabled on chipset %s.\n", + pci_name(dev)); + dev->subordinate->bus_flags |= PCI_BUS_FLAGS_NO_MSI; + } +} +DECLARE_PCI_FIXUP_FINAL(PCI_VENDOR_ID_NVIDIA, PCI_DEVICE_ID_NVIDIA_CK804_PCIE, + quirk_nvidia_ck804_msi_ht_cap); +#endif /* CONFIG_PCI_MSI */ + EXPORT_SYMBOL(pcie_mch_quirk); #ifdef CONFIG_HOTPLUG EXPORT_SYMBOL(pci_fixup_device); diff --git a/drivers/pci/remove.c b/drivers/pci/remove.c index 99ffbd478b2..430281b2e92 100644 --- a/drivers/pci/remove.c +++ b/drivers/pci/remove.c @@ -16,8 +16,11 @@ static void pci_free_resources(struct pci_dev *dev) } } -static void pci_destroy_dev(struct pci_dev *dev) +static void pci_stop_dev(struct pci_dev *dev) { + if (!dev->global_list.next) + return; + if (!list_empty(&dev->global_list)) { pci_proc_detach_device(dev); pci_remove_sysfs_dev_files(dev); @@ -27,6 +30,11 @@ static void pci_destroy_dev(struct pci_dev *dev) dev->global_list.next = dev->global_list.prev = NULL; up_write(&pci_bus_sem); } +} + +static void pci_destroy_dev(struct pci_dev *dev) +{ + pci_stop_dev(dev); /* Remove the device from the device lists, and prevent any further * list accesses from this device */ @@ -119,5 +127,32 @@ void pci_remove_behind_bridge(struct pci_dev *dev) } } +static void pci_stop_bus_devices(struct pci_bus *bus) +{ + struct list_head *l, *n; + + list_for_each_safe(l, n, &bus->devices) { + struct pci_dev *dev = pci_dev_b(l); + pci_stop_bus_device(dev); + } +} + +/** + * pci_stop_bus_device - stop a PCI device and any children + * @dev: the device to stop + * + * Stop a PCI device (detach the driver, remove from the global list + * and so on). This also stop any subordinate buses and children in a + * depth-first manner. + */ +void pci_stop_bus_device(struct pci_dev *dev) +{ + if (dev->subordinate) + pci_stop_bus_devices(dev->subordinate); + + pci_stop_dev(dev); +} + EXPORT_SYMBOL(pci_remove_bus_device); EXPORT_SYMBOL(pci_remove_behind_bridge); +EXPORT_SYMBOL_GPL(pci_stop_bus_device); diff --git a/drivers/pci/setup-bus.c b/drivers/pci/setup-bus.c index 47c1071ad84..54404917be9 100644 --- a/drivers/pci/setup-bus.c +++ b/drivers/pci/setup-bus.c @@ -55,12 +55,19 @@ pbus_assign_resources_sorted(struct pci_bus *bus) list_for_each_entry(dev, &bus->devices, bus_list) { u16 class = dev->class >> 8; - /* Don't touch classless devices or host bridges or ioapics. */ + /* Don't touch classless devices or host bridges. */ if (class == PCI_CLASS_NOT_DEFINED || - class == PCI_CLASS_BRIDGE_HOST || - class == PCI_CLASS_SYSTEM_PIC) + class == PCI_CLASS_BRIDGE_HOST) continue; + /* Don't touch ioapics if it has the assigned resources. */ + if (class == PCI_CLASS_SYSTEM_PIC) { + res = &dev->resource[0]; + if (res[0].start || res[1].start || res[2].start || + res[3].start || res[4].start || res[5].start) + continue; + } + pdev_sort_resources(dev, &head); } |