We need to have ioapic setup before normal pci drivers. otherwise other pci driver can not setup irq. Make ioapic built-in, so can call add/remove during host-bridge add/remove the same as the booting path. Also need to make it depends on X86_IO_APIC. Signed-off-by: <yinghai@xxxxxxxxxx> --- arch/x86/kernel/acpi/boot.c | 10 +- drivers/acpi/pci_root.c | 4 + drivers/pci/Kconfig | 3 +- drivers/pci/ioapic.c | 216 ++++++++++++++++++++++++++++++------------- include/linux/pci-acpi.h | 8 ++ 5 files changed, 172 insertions(+), 69 deletions(-) diff --git a/arch/x86/kernel/acpi/boot.c b/arch/x86/kernel/acpi/boot.c index 350879f..338163b 100644 --- a/arch/x86/kernel/acpi/boot.c +++ b/arch/x86/kernel/acpi/boot.c @@ -697,16 +697,18 @@ EXPORT_SYMBOL(acpi_unmap_lsapic); int acpi_register_ioapic(acpi_handle handle, u64 phys_addr, u32 gsi_base) { - /* TBD */ - return -EINVAL; + unsigned long long id = 0; + + acpi_evaluate_integer(handle, "_UID", NULL, &id); + + return __mp_register_ioapic(id, phys_addr, gsi_base, true); } EXPORT_SYMBOL(acpi_register_ioapic); int acpi_unregister_ioapic(acpi_handle handle, u32 gsi_base) { - /* TBD */ - return -EINVAL; + return mp_unregister_ioapic(gsi_base); } EXPORT_SYMBOL(acpi_unregister_ioapic); diff --git a/drivers/acpi/pci_root.c b/drivers/acpi/pci_root.c index 417487a..54d61ce 100644 --- a/drivers/acpi/pci_root.c +++ b/drivers/acpi/pci_root.c @@ -587,6 +587,8 @@ static int acpi_pci_root_add(struct acpi_device *device) pci_assign_unassigned_bus_resources(root->bus); } + acpi_pci_ioapic_add(root); + mutex_lock(&acpi_pci_root_lock); list_for_each_entry(driver, &acpi_pci_drivers, node) if (driver->add) @@ -626,6 +628,8 @@ static int acpi_pci_root_remove(struct acpi_device *device, int type) driver->remove(root); mutex_unlock(&acpi_pci_root_lock); + acpi_pci_ioapic_remove(root); + device_set_run_wake(root->bus->bridge, false); pci_acpi_remove_bus_pm_notifier(device); diff --git a/drivers/pci/Kconfig b/drivers/pci/Kconfig index 6d51aa6..720989f 100644 --- a/drivers/pci/Kconfig +++ b/drivers/pci/Kconfig @@ -110,10 +110,11 @@ config PCI_PASID If unsure, say N. config PCI_IOAPIC - tristate "PCI IO-APIC hotplug support" if X86 + bool "PCI IO-APIC hotplug support" if X86 depends on PCI depends on ACPI depends on HOTPLUG + depends on X86_IO_APIC default !X86 config PCI_LABEL diff --git a/drivers/pci/ioapic.c b/drivers/pci/ioapic.c index 8dacfd0..12e56c7 100644 --- a/drivers/pci/ioapic.c +++ b/drivers/pci/ioapic.c @@ -22,70 +22,128 @@ #include <linux/slab.h> #include <acpi/acpi_bus.h> -struct ioapic { - acpi_handle handle; +struct acpi_pci_ioapic { + acpi_handle root_handle; u32 gsi_base; + struct pci_dev *pdev; + struct list_head list; }; -static int ioapic_probe(struct pci_dev *dev, const struct pci_device_id *ent) +static LIST_HEAD(ioapic_list); +static DEFINE_MUTEX(ioapic_list_lock); + +static acpi_status setup_res(struct acpi_resource *acpi_res, void *data) +{ + struct resource *res; + struct acpi_resource_address64 addr; + acpi_status status; + unsigned long flags; + u64 start, end; + + status = acpi_resource_to_address64(acpi_res, &addr); + if (!ACPI_SUCCESS(status)) + return AE_OK; + + if (addr.resource_type == ACPI_MEMORY_RANGE) { + if (addr.info.mem.caching == ACPI_PREFETCHABLE_MEMORY) + return AE_OK; + flags = IORESOURCE_MEM; + } else + return AE_OK; + + start = addr.minimum + addr.translation_offset; + end = addr.maximum + addr.translation_offset; + + res = data; + res->flags = flags; + res->start = start; + res->end = end; + + return AE_OK; +} + +static void handle_ioapic_add(acpi_handle handle, struct pci_dev **pdev, + u32 *pgsi_base) { - acpi_handle handle; acpi_status status; unsigned long long gsb; - struct ioapic *ioapic; + struct pci_dev *dev; + u32 gsi_base; int ret; char *type; - struct resource *res; + struct resource r; + struct resource *res = &r; + char objname[64]; + struct acpi_buffer buffer = {sizeof(objname), objname}; - handle = DEVICE_ACPI_HANDLE(&dev->dev); - if (!handle) - return -EINVAL; + *pdev = NULL; + *pgsi_base = 0; status = acpi_evaluate_integer(handle, "_GSB", NULL, &gsb); - if (ACPI_FAILURE(status)) - return -EINVAL; + if (ACPI_FAILURE(status) || !gsb) + return; + + dev = acpi_get_pci_dev(handle); + if (!dev) { + struct acpi_device_info *info; + char *hid = NULL; + + status = acpi_get_object_info(handle, &info); + if (ACPI_FAILURE(status)) + return; + if (info->valid & ACPI_VALID_HID) + hid = info->hardware_id.string; + if (!hid || strcmp(hid, "ACPI0009")) { + kfree(info); + return; + } + kfree(info); + memset(res, 0, sizeof(*res)); + acpi_walk_resources(handle, METHOD_NAME__CRS, setup_res, res); + if (!res->flags) + return; + } - /* - * The previous code in acpiphp evaluated _MAT if _GSB failed, but - * ACPI spec 4.0 sec 6.2.2 requires _GSB for hot-pluggable I/O APICs. - */ + acpi_get_name(handle, ACPI_FULL_PATHNAME, &buffer); - ioapic = kzalloc(sizeof(*ioapic), GFP_KERNEL); - if (!ioapic) - return -ENOMEM; + gsi_base = gsb; + type = "IOxAPIC"; + if (dev) { + ret = pci_enable_device(dev); + if (ret < 0) + goto exit_put; - ioapic->handle = handle; - ioapic->gsi_base = (u32) gsb; + pci_set_master(dev); - if (dev->class == PCI_CLASS_SYSTEM_PIC_IOAPIC) - type = "IOAPIC"; - else - type = "IOxAPIC"; + if (dev->class == PCI_CLASS_SYSTEM_PIC_IOAPIC) + type = "IOAPIC"; - ret = pci_enable_device(dev); - if (ret < 0) - goto exit_free; + if (pci_request_region(dev, 0, type)) + goto exit_disable; - pci_set_master(dev); + res = &dev->resource[0]; + } - if (pci_request_region(dev, 0, type)) - goto exit_disable; + if (acpi_register_ioapic(handle, res->start, gsi_base)) { + if (dev) + goto exit_release; + return; + } - res = &dev->resource[0]; - if (acpi_register_ioapic(ioapic->handle, res->start, ioapic->gsi_base)) - goto exit_release; + pr_info("%s %s %s at %pR, GSI %u\n", + dev ? dev_name(&dev->dev) : "", objname, type, + res, gsi_base); - pci_set_drvdata(dev, ioapic); - dev_info(&dev->dev, "%s at %pR, GSI %u\n", type, res, ioapic->gsi_base); - return 0; + *pdev = dev; + *pgsi_base = gsi_base; + return; exit_release: pci_release_region(dev, 0); exit_disable: pci_disable_device(dev); -exit_free: - kfree(ioapic); - return -ENODEV; +exit_put: + pci_dev_put(dev); } static void pci_disable_device_mem(struct pci_dev *dev) @@ -99,44 +157,74 @@ static void pci_disable_device_mem(struct pci_dev *dev) } } -static void ioapic_remove(struct pci_dev *dev) +static void handle_ioapic_remove(acpi_handle handle, struct pci_dev *dev, + u32 gsi_base) { - struct ioapic *ioapic = pci_get_drvdata(dev); + acpi_unregister_ioapic(handle, gsi_base); + + if (!dev) + return; - acpi_unregister_ioapic(ioapic->handle, ioapic->gsi_base); pci_release_region(dev, 0); pci_disable_device(dev); /* need to disable it, otherwise remove/rescan will not work */ pci_disable_device_mem(dev); - kfree(ioapic); + pci_dev_put(dev); } +static acpi_status register_ioapic(acpi_handle handle, u32 lvl, + void *context, void **rv) +{ + acpi_handle root_handle = context; + struct pci_dev *pdev; + u32 gsi_base; + struct acpi_pci_ioapic *ioapic; -static DEFINE_PCI_DEVICE_TABLE(ioapic_devices) = { - { PCI_DEVICE_CLASS(PCI_CLASS_SYSTEM_PIC_IOAPIC, ~0) }, - { PCI_DEVICE_CLASS(PCI_CLASS_SYSTEM_PIC_IOXAPIC, ~0) }, - { } -}; -MODULE_DEVICE_TABLE(pci, ioapic_devices); + handle_ioapic_add(handle, &pdev, &gsi_base); + if (!gsi_base) + return AE_OK; -static struct pci_driver ioapic_driver = { - .name = "ioapic", - .id_table = ioapic_devices, - .probe = ioapic_probe, - .remove = ioapic_remove, -}; + ioapic = kzalloc(sizeof(*ioapic), GFP_KERNEL); + if (!ioapic) { + pr_err("%s: cannot allocate memory\n", __func__); + handle_ioapic_remove(root_handle, pdev, gsi_base); + return AE_OK; + } + ioapic->root_handle = root_handle; + ioapic->pdev = pdev; + ioapic->gsi_base = gsi_base; -static int __init ioapic_init(void) -{ - return pci_register_driver(&ioapic_driver); + mutex_lock(&ioapic_list_lock); + list_add(&ioapic->list, &ioapic_list); + mutex_unlock(&ioapic_list_lock); + + return AE_OK; } -static void __exit ioapic_exit(void) +void acpi_pci_ioapic_add(struct acpi_pci_root *root) { - pci_unregister_driver(&ioapic_driver); -} + acpi_status status; -module_init(ioapic_init); -module_exit(ioapic_exit); + status = acpi_walk_namespace(ACPI_TYPE_DEVICE, root->device->handle, + (u32)1, register_ioapic, NULL, + root->device->handle, + NULL); + if (ACPI_FAILURE(status)) + pr_err("%s: register_ioapic failure - %d", __func__, status); +} -MODULE_LICENSE("GPL"); +void acpi_pci_ioapic_remove(struct acpi_pci_root *root) +{ + struct acpi_pci_ioapic *ioapic, *tmp; + + mutex_lock(&ioapic_list_lock); + list_for_each_entry_safe(ioapic, tmp, &ioapic_list, list) { + if (root->device->handle != ioapic->root_handle) + continue; + list_del(&ioapic->list); + handle_ioapic_remove(ioapic->root_handle, ioapic->pdev, + ioapic->gsi_base); + kfree(ioapic); + } + mutex_unlock(&ioapic_list_lock); +} diff --git a/include/linux/pci-acpi.h b/include/linux/pci-acpi.h index 9a22b5e..6f83039 100644 --- a/include/linux/pci-acpi.h +++ b/include/linux/pci-acpi.h @@ -49,4 +49,12 @@ extern bool aer_acpi_firmware_first(void); static inline bool aer_acpi_firmware_first(void) { return false; } #endif +#ifdef CONFIG_PCI_IOAPIC +void acpi_pci_ioapic_add(struct acpi_pci_root *root); +void acpi_pci_ioapic_remove(struct acpi_pci_root *root); +#else +static inline void acpi_pci_ioapic_add(struct acpi_pci_root *root) { } +static inline void acpi_pci_ioapic_remove(struct acpi_pci_root *root) { } +#endif + #endif /* _PCI_ACPI_H_ */ -- 1.7.10.4 -- To unsubscribe from this list: send the line "unsubscribe linux-pci" in the body of a message to majordomo@xxxxxxxxxxxxxxx More majordomo info at http://vger.kernel.org/majordomo-info.html