iommu/amd: Check if IOAPIC information is correct
[platform/adaptation/renesas_rcar/renesas_kernel.git] / drivers / iommu / amd_iommu_init.c
index 18a89b7..f8a222b 100644 (file)
 #include <asm/gart.h>
 #include <asm/x86_init.h>
 #include <asm/iommu_table.h>
+#include <asm/io_apic.h>
 
 #include "amd_iommu_proto.h"
 #include "amd_iommu_types.h"
+#include "irq_remapping.h"
 
 /*
  * definitions for the ACPI scanning code
 #define IVHD_DEV_ALIAS_RANGE            0x43
 #define IVHD_DEV_EXT_SELECT             0x46
 #define IVHD_DEV_EXT_SELECT_RANGE       0x47
+#define IVHD_DEV_SPECIAL               0x48
+
+#define IVHD_SPECIAL_IOAPIC            1
+#define IVHD_SPECIAL_HPET              2
 
 #define IVHD_FLAG_HT_TUN_EN_MASK        0x01
 #define IVHD_FLAG_PASSPW_EN_MASK        0x02
@@ -123,6 +129,7 @@ struct ivmd_header {
 } __attribute__((packed));
 
 bool amd_iommu_dump;
+bool amd_iommu_irq_remap __read_mostly;
 
 static bool amd_iommu_detected;
 static bool __initdata amd_iommu_disabled;
@@ -178,6 +185,12 @@ u16 *amd_iommu_alias_table;
 struct amd_iommu **amd_iommu_rlookup_table;
 
 /*
+ * This table is used to find the irq remapping table for a given device id
+ * quickly.
+ */
+struct irq_remap_table **irq_lookup_table;
+
+/*
  * AMD IOMMU allows up to 2^16 differend protection domains. This is a bitmap
  * to know which ones are already in use.
  */
@@ -690,6 +703,31 @@ static void __init set_dev_entry_from_acpi(struct amd_iommu *iommu,
        set_iommu_for_device(iommu, devid);
 }
 
+static int add_special_device(u8 type, u8 id, u16 devid)
+{
+       struct devid_map *entry;
+       struct list_head *list;
+
+       if (type != IVHD_SPECIAL_IOAPIC && type != IVHD_SPECIAL_HPET)
+               return -EINVAL;
+
+       entry = kzalloc(sizeof(*entry), GFP_KERNEL);
+       if (!entry)
+               return -ENOMEM;
+
+       entry->id    = id;
+       entry->devid = devid;
+
+       if (type == IVHD_SPECIAL_IOAPIC)
+               list = &ioapic_map;
+       else
+               list = &hpet_map;
+
+       list_add_tail(&entry->list, list);
+
+       return 0;
+}
+
 /*
  * Reads the device exclusion range from ACPI and initialize IOMMU with
  * it
@@ -717,7 +755,7 @@ static void __init set_device_exclusion_range(u16 devid, struct ivmd_header *m)
  * Takes a pointer to an AMD IOMMU entry in the ACPI table and
  * initializes the hardware and our data structures with it.
  */
-static void __init init_iommu_from_acpi(struct amd_iommu *iommu,
+static int __init init_iommu_from_acpi(struct amd_iommu *iommu,
                                        struct ivhd_header *h)
 {
        u8 *p = (u8 *)h;
@@ -867,12 +905,43 @@ static void __init init_iommu_from_acpi(struct amd_iommu *iommu,
                                                        flags, ext_flags);
                        }
                        break;
+               case IVHD_DEV_SPECIAL: {
+                       u8 handle, type;
+                       const char *var;
+                       u16 devid;
+                       int ret;
+
+                       handle = e->ext & 0xff;
+                       devid  = (e->ext >>  8) & 0xffff;
+                       type   = (e->ext >> 24) & 0xff;
+
+                       if (type == IVHD_SPECIAL_IOAPIC)
+                               var = "IOAPIC";
+                       else if (type == IVHD_SPECIAL_HPET)
+                               var = "HPET";
+                       else
+                               var = "UNKNOWN";
+
+                       DUMP_printk("  DEV_SPECIAL(%s[%d])\t\tdevid: %02x:%02x.%x\n",
+                                   var, (int)handle,
+                                   PCI_BUS(devid),
+                                   PCI_SLOT(devid),
+                                   PCI_FUNC(devid));
+
+                       set_dev_entry_from_acpi(iommu, devid, e->flags, 0);
+                       ret = add_special_device(type, handle, devid);
+                       if (ret)
+                               return ret;
+                       break;
+               }
                default:
                        break;
                }
 
                p += ivhd_entry_length(p);
        }
+
+       return 0;
 }
 
 /* Initializes the device->iommu mapping for the driver */
@@ -912,6 +981,8 @@ static void __init free_iommu_all(void)
  */
 static int __init init_iommu_one(struct amd_iommu *iommu, struct ivhd_header *h)
 {
+       int ret;
+
        spin_lock_init(&iommu->lock);
 
        /* Add IOMMU to internal data structures */
@@ -947,7 +1018,9 @@ static int __init init_iommu_one(struct amd_iommu *iommu, struct ivhd_header *h)
 
        iommu->int_enabled = false;
 
-       init_iommu_from_acpi(iommu, h);
+       ret = init_iommu_from_acpi(iommu, h);
+       if (ret)
+               return ret;
        init_iommu_devices(iommu);
 
        return 0;
@@ -1466,6 +1539,15 @@ static struct syscore_ops amd_iommu_syscore_ops = {
 
 static void __init free_on_init_error(void)
 {
+       free_pages((unsigned long)irq_lookup_table,
+                  get_order(rlookup_table_size));
+
+       if (amd_iommu_irq_cache) {
+               kmem_cache_destroy(amd_iommu_irq_cache);
+               amd_iommu_irq_cache = NULL;
+
+       }
+
        amd_iommu_uninit_devices();
 
        free_pages((unsigned long)amd_iommu_pd_alloc_bitmap,
@@ -1494,6 +1576,23 @@ static void __init free_on_init_error(void)
 #endif
 }
 
+static bool __init check_ioapic_information(void)
+{
+       int idx;
+
+       for (idx = 0; idx < nr_ioapics; idx++) {
+               int id = mpc_ioapic_id(idx);
+
+               if (get_ioapic_devid(id) < 0) {
+                       pr_err(FW_BUG "AMD-Vi: IO-APIC[%d] not in IVRS table\n", id);
+                       pr_err("AMD-Vi: Disabling interrupt remapping due to BIOS Bug\n");
+                       return false;
+               }
+       }
+
+       return true;
+}
+
 /*
  * This is the hardware init function for AMD IOMMU in the system.
  * This function is called either from amd_iommu_init or from the interrupt
@@ -1580,9 +1679,6 @@ static int __init early_amd_iommu_init(void)
        if (amd_iommu_pd_alloc_bitmap == NULL)
                goto out;
 
-       /* init the device table */
-       init_device_table();
-
        /*
         * let all alias entries point to itself
         */
@@ -1605,10 +1701,35 @@ static int __init early_amd_iommu_init(void)
        if (ret)
                goto out;
 
+       if (amd_iommu_irq_remap)
+               amd_iommu_irq_remap = check_ioapic_information();
+
+       if (amd_iommu_irq_remap) {
+               /*
+                * Interrupt remapping enabled, create kmem_cache for the
+                * remapping tables.
+                */
+               amd_iommu_irq_cache = kmem_cache_create("irq_remap_cache",
+                               MAX_IRQS_PER_TABLE * sizeof(u32),
+                               IRQ_TABLE_ALIGNMENT,
+                               0, NULL);
+               if (!amd_iommu_irq_cache)
+                       goto out;
+
+               irq_lookup_table = (void *)__get_free_pages(
+                               GFP_KERNEL | __GFP_ZERO,
+                               get_order(rlookup_table_size));
+               if (!irq_lookup_table)
+                       goto out;
+       }
+
        ret = init_memory_definitions(ivrs_base);
        if (ret)
                goto out;
 
+       /* init the device table */
+       init_device_table();
+
 out:
        /* Don't leak any ACPI memory */
        early_acpi_os_unmap_memory((char __iomem *)ivrs_base, ivrs_size);
@@ -1652,6 +1773,9 @@ static bool detect_ivrs(void)
        /* Make sure ACS will be enabled during PCI probe */
        pci_request_acs();
 
+       if (!disable_irq_remap)
+               amd_iommu_irq_remap = true;
+
        return true;
 }