acpiphp: add support for ioapic hot-remove

This patch adds support for ioapics hot-remove.

Signed-off-by: Kenji Kaneshige <kaneshige.kenji@jp.fujitsu.com>
Signed-off-by: MUNEDA Takahiro <muneda.takahiro@jp.fujitsu.com>
Signed-off-by: Satoru Takeuchi <takeuchi_satoru@jp.fujitsu.com>
Signed-off-by: Kristen Carlson Accardi <kristen.c.accardi@intel.com>
Signed-off-by: Greg Kroah-Hartman <gregkh@suse.de>


diff --git a/drivers/pci/hotplug/acpiphp_glue.c b/drivers/pci/hotplug/acpiphp_glue.c
index 712f02f..83e8e44 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 @@
 	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,30 +814,87 @@
 	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;
 }
@@ -847,6 +907,14 @@
 	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;
@@ -1146,7 +1214,12 @@
 				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 */