summary refs log tree commit diff
path: root/drivers/pci/ioapic.c
diff options
context:
space:
mode:
Diffstat (limited to 'drivers/pci/ioapic.c')
-rw-r--r--drivers/pci/ioapic.c127
1 files changed, 127 insertions, 0 deletions
diff --git a/drivers/pci/ioapic.c b/drivers/pci/ioapic.c
new file mode 100644
index 000000000000..3e0d7b5dd1b9
--- /dev/null
+++ b/drivers/pci/ioapic.c
@@ -0,0 +1,127 @@
+/*
+ * IOAPIC/IOxAPIC/IOSAPIC driver
+ *
+ * Copyright (C) 2009 Fujitsu Limited.
+ * (c) Copyright 2009 Hewlett-Packard Development Company, L.P.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ */
+
+/*
+ * This driver manages PCI I/O APICs added by hotplug after boot.  We try to
+ * claim all I/O APIC PCI devices, but those present at boot were registered
+ * when we parsed the ACPI MADT, so we'll fail when we try to re-register
+ * them.
+ */
+
+#include <linux/pci.h>
+#include <linux/acpi.h>
+#include <acpi/acpi_bus.h>
+
+struct ioapic {
+	acpi_handle	handle;
+	u32		gsi_base;
+};
+
+static int ioapic_probe(struct pci_dev *dev, const struct pci_device_id *ent)
+{
+	acpi_handle handle;
+	acpi_status status;
+	unsigned long long gsb;
+	struct ioapic *ioapic;
+	u64 addr;
+	int ret;
+	char *type;
+
+	handle = DEVICE_ACPI_HANDLE(&dev->dev);
+	if (!handle)
+		return -EINVAL;
+
+	status = acpi_evaluate_integer(handle, "_GSB", NULL, &gsb);
+	if (ACPI_FAILURE(status))
+		return -EINVAL;
+
+	/*
+	 * 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.
+	 */
+
+	ioapic = kzalloc(sizeof(*ioapic), GFP_KERNEL);
+	if (!ioapic)
+		return -ENOMEM;
+
+	ioapic->handle = handle;
+	ioapic->gsi_base = (u32) gsb;
+
+	if (dev->class == PCI_CLASS_SYSTEM_PIC_IOAPIC)
+		type = "IOAPIC";
+	else
+		type = "IOxAPIC";
+
+	ret = pci_enable_device(dev);
+	if (ret < 0)
+		goto exit_free;
+
+	pci_set_master(dev);
+
+	if (pci_request_region(dev, 0, type))
+		goto exit_disable;
+
+	addr = pci_resource_start(dev, 0);
+	if (acpi_register_ioapic(ioapic->handle, addr, ioapic->gsi_base))
+		goto exit_release;
+
+	pci_set_drvdata(dev, ioapic);
+	dev_info(&dev->dev, "%s at %#llx, GSI %u\n", type, addr,
+		 ioapic->gsi_base);
+	return 0;
+
+exit_release:
+	pci_release_region(dev, 0);
+exit_disable:
+	pci_disable_device(dev);
+exit_free:
+	kfree(ioapic);
+	return -ENODEV;
+}
+
+static void ioapic_remove(struct pci_dev *dev)
+{
+	struct ioapic *ioapic = pci_get_drvdata(dev);
+
+	acpi_unregister_ioapic(ioapic->handle, ioapic->gsi_base);
+	pci_release_region(dev, 0);
+	pci_disable_device(dev);
+	kfree(ioapic);
+}
+
+
+static struct pci_device_id ioapic_devices[] = {
+	{ PCI_ANY_ID, PCI_ANY_ID, PCI_ANY_ID, PCI_ANY_ID,
+	  PCI_CLASS_SYSTEM_PIC_IOAPIC << 8, 0xffff00, },
+	{ PCI_ANY_ID, PCI_ANY_ID, PCI_ANY_ID, PCI_ANY_ID,
+	  PCI_CLASS_SYSTEM_PIC_IOXAPIC << 8, 0xffff00, },
+	{ }
+};
+
+static struct pci_driver ioapic_driver = {
+	.name		= "ioapic",
+	.id_table	= ioapic_devices,
+	.probe		= ioapic_probe,
+	.remove		= __devexit_p(ioapic_remove),
+};
+
+static int __init ioapic_init(void)
+{
+	return pci_register_driver(&ioapic_driver);
+}
+
+static void __exit ioapic_exit(void)
+{
+	pci_unregister_driver(&ioapic_driver);
+}
+
+module_init(ioapic_init);
+module_exit(ioapic_exit);