AMD IOMMU: add MSI interrupt support
authorJoerg Roedel <joerg.roedel@amd.com>
Thu, 11 Sep 2008 14:51:41 +0000 (16:51 +0200)
committerIngo Molnar <mingo@elte.hu>
Fri, 19 Sep 2008 10:59:15 +0000 (12:59 +0200)
The AMD IOMMU can generate interrupts for various reasons. This patch
adds the basic interrupt enabling infrastructure to the driver.

Signed-off-by: Joerg Roedel <joerg.roedel@amd.com>
Signed-off-by: Ingo Molnar <mingo@elte.hu>
arch/x86/Kconfig
arch/x86/kernel/amd_iommu.c
arch/x86/kernel/amd_iommu_init.c
include/asm-x86/amd_iommu.h
include/asm-x86/amd_iommu_types.h

index ed92864d1325c09092f3e8ed4918f26341f23161..39fd3f42696d861727572fc29bf0c9a608281573 100644 (file)
@@ -553,6 +553,7 @@ config CALGARY_IOMMU_ENABLED_BY_DEFAULT
 config AMD_IOMMU
        bool "AMD IOMMU support"
        select SWIOTLB
+       select PCI_MSI
        depends on X86_64 && PCI && ACPI
        help
          With this option you can enable support for AMD IOMMU hardware in
index 15792ed082e00910d2204e37377e3a4f594f3ec9..0e494b9d5f2005a6fbab5f479c4a1cee7e4fe2f9 100644 (file)
@@ -49,6 +49,17 @@ static int iommu_has_npcache(struct amd_iommu *iommu)
        return iommu->cap & IOMMU_CAP_NPCACHE;
 }
 
+/****************************************************************************
+ *
+ * Interrupt handling functions
+ *
+ ****************************************************************************/
+
+irqreturn_t amd_iommu_int_handler(int irq, void *data)
+{
+       return IRQ_NONE;
+}
+
 /****************************************************************************
  *
  * IOMMU command queuing functions
index a7eb89d8923dc6fe666d8021cfc7fd179ef3b1e7..14a06464a6947499cdd29fe4fbb017e7aaf85334 100644 (file)
@@ -22,6 +22,8 @@
 #include <linux/gfp.h>
 #include <linux/list.h>
 #include <linux/sysdev.h>
+#include <linux/interrupt.h>
+#include <linux/msi.h>
 #include <asm/pci-direct.h>
 #include <asm/amd_iommu_types.h>
 #include <asm/amd_iommu.h>
@@ -515,17 +517,20 @@ static void __init set_device_exclusion_range(u16 devid, struct ivmd_header *m)
 static void __init init_iommu_from_pci(struct amd_iommu *iommu)
 {
        int cap_ptr = iommu->cap_ptr;
-       u32 range;
+       u32 range, misc;
 
        pci_read_config_dword(iommu->dev, cap_ptr + MMIO_CAP_HDR_OFFSET,
                              &iommu->cap);
        pci_read_config_dword(iommu->dev, cap_ptr + MMIO_RANGE_OFFSET,
                              &range);
+       pci_read_config_dword(iommu->dev, cap_ptr + MMIO_MISC_OFFSET,
+                             &misc);
 
        iommu->first_device = calc_devid(MMIO_GET_BUS(range),
                                         MMIO_GET_FD(range));
        iommu->last_device = calc_devid(MMIO_GET_BUS(range),
                                        MMIO_GET_LD(range));
+       iommu->evt_msi_num = MMIO_MSI_NUM(misc);
 }
 
 /*
@@ -696,6 +701,8 @@ static int __init init_iommu_one(struct amd_iommu *iommu, struct ivhd_header *h)
        if (!iommu->evt_buf)
                return -ENOMEM;
 
+       iommu->int_enabled = false;
+
        init_iommu_from_pci(iommu);
        init_iommu_from_acpi(iommu, h);
        init_iommu_devices(iommu);
@@ -741,6 +748,95 @@ static int __init init_iommu_all(struct acpi_table_header *table)
        return 0;
 }
 
+/****************************************************************************
+ *
+ * The following functions initialize the MSI interrupts for all IOMMUs
+ * in the system. Its a bit challenging because there could be multiple
+ * IOMMUs per PCI BDF but we can call pci_enable_msi(x) only once per
+ * pci_dev.
+ *
+ ****************************************************************************/
+
+static int __init iommu_setup_msix(struct amd_iommu *iommu)
+{
+       struct amd_iommu *curr;
+       struct msix_entry entries[32]; /* only 32 supported by AMD IOMMU */
+       int nvec = 0, i;
+
+       list_for_each_entry(curr, &amd_iommu_list, list) {
+               if (curr->dev == iommu->dev) {
+                       entries[nvec].entry = curr->evt_msi_num;
+                       entries[nvec].vector = 0;
+                       curr->int_enabled = true;
+                       nvec++;
+               }
+       }
+
+       if (pci_enable_msix(iommu->dev, entries, nvec)) {
+               pci_disable_msix(iommu->dev);
+               return 1;
+       }
+
+       for (i = 0; i < nvec; ++i) {
+               int r = request_irq(entries->vector, amd_iommu_int_handler,
+                                   IRQF_SAMPLE_RANDOM,
+                                   "AMD IOMMU",
+                                   NULL);
+               if (r)
+                       goto out_free;
+       }
+
+       return 0;
+
+out_free:
+       for (i -= 1; i >= 0; --i)
+               free_irq(entries->vector, NULL);
+
+       pci_disable_msix(iommu->dev);
+
+       return 1;
+}
+
+static int __init iommu_setup_msi(struct amd_iommu *iommu)
+{
+       int r;
+       struct amd_iommu *curr;
+
+       list_for_each_entry(curr, &amd_iommu_list, list) {
+               if (curr->dev == iommu->dev)
+                       curr->int_enabled = true;
+       }
+
+
+       if (pci_enable_msi(iommu->dev))
+               return 1;
+
+       r = request_irq(iommu->dev->irq, amd_iommu_int_handler,
+                       IRQF_SAMPLE_RANDOM,
+                       "AMD IOMMU",
+                       NULL);
+
+       if (r) {
+               pci_disable_msi(iommu->dev);
+               return 1;
+       }
+
+       return 0;
+}
+
+static int __init iommu_init_msi(struct amd_iommu *iommu)
+{
+       if (iommu->int_enabled)
+               return 0;
+
+       if (pci_find_capability(iommu->dev, PCI_CAP_ID_MSIX))
+               return iommu_setup_msix(iommu);
+       else if (pci_find_capability(iommu->dev, PCI_CAP_ID_MSI))
+               return iommu_setup_msi(iommu);
+
+       return 1;
+}
+
 /****************************************************************************
  *
  * The next functions belong to the third pass of parsing the ACPI
@@ -862,6 +958,7 @@ static void __init enable_iommus(void)
 
        list_for_each_entry(iommu, &amd_iommu_list, list) {
                iommu_set_exclusion_range(iommu);
+               iommu_init_msi(iommu);
                iommu_enable(iommu);
        }
 }
index 30a12049353b1bc6c17d9811046f71096b9d6759..2fd97cb250c7540f1606bef3cddbc2607fe176cd 100644 (file)
 #ifndef _ASM_X86_AMD_IOMMU_H
 #define _ASM_X86_AMD_IOMMU_H
 
+#include <linux/irqreturn.h>
+
 #ifdef CONFIG_AMD_IOMMU
 extern int amd_iommu_init(void);
 extern int amd_iommu_init_dma_ops(void);
 extern void amd_iommu_detect(void);
+extern irqreturn_t amd_iommu_int_handler(int irq, void *data);
 #else
 static inline int amd_iommu_init(void) { return -ENODEV; }
 static inline void amd_iommu_detect(void) { }
index a5629a21557cdcd212bd27347bc31b8a8aa9fede..8533f09b34b7712adee76b2d8c9d56ac48456de6 100644 (file)
@@ -37,6 +37,7 @@
 /* Capability offsets used by the driver */
 #define MMIO_CAP_HDR_OFFSET    0x00
 #define MMIO_RANGE_OFFSET      0x0c
+#define MMIO_MISC_OFFSET       0x10
 
 /* Masks, shifts and macros to parse the device range capability */
 #define MMIO_RANGE_LD_MASK     0xff000000
@@ -48,6 +49,7 @@
 #define MMIO_GET_LD(x)  (((x) & MMIO_RANGE_LD_MASK) >> MMIO_RANGE_LD_SHIFT)
 #define MMIO_GET_FD(x)  (((x) & MMIO_RANGE_FD_MASK) >> MMIO_RANGE_FD_SHIFT)
 #define MMIO_GET_BUS(x) (((x) & MMIO_RANGE_BUS_MASK) >> MMIO_RANGE_BUS_SHIFT)
+#define MMIO_MSI_NUM(x)        ((x) & 0x1f)
 
 /* Flag masks for the AMD IOMMU exclusion range */
 #define MMIO_EXCL_ENABLE_MASK 0x01ULL
@@ -255,10 +257,15 @@ struct amd_iommu {
        u8 *evt_buf;
        /* size of event buffer */
        u32 evt_buf_size;
+       /* MSI number for event interrupt */
+       u16 evt_msi_num;
 
        /* if one, we need to send a completion wait command */
        int need_sync;
 
+       /* true if interrupts for this IOMMU are already enabled */
+       bool int_enabled;
+
        /* default dma_ops domain for that IOMMU */
        struct dma_ops_domain *default_dom;
 };