iommu/amd: Use the most comprehensive IVHD type that the driver can support
authorSuravee Suthikulpanit <Suravee.Suthikulpanit@amd.com>
Fri, 1 Apr 2016 13:05:59 +0000 (09:05 -0400)
committerJoerg Roedel <jroedel@suse.de>
Thu, 7 Apr 2016 11:29:41 +0000 (13:29 +0200)
The IVRS in more recent AMD system usually contains multiple
IVHD block types (e.g. 0x10, 0x11, and 0x40) for each IOMMU.
The newer IVHD types provide more information (e.g. new features
specified in the IOMMU spec), while maintain compatibility with
the older IVHD type.

Having multiple IVHD type allows older IOMMU drivers to still function
(e.g. using the older IVHD type 0x10) while the newer IOMMU driver can use
the newer IVHD types (e.g. 0x11 and 0x40). Therefore, the IOMMU driver
should only make use of the newest IVHD type that it can support.

This patch adds new logic to determine the highest level of IVHD type
it can support, and use it throughout the to initialize the driver.
This requires adding another pass to the IVRS parsing to determine
appropriate IVHD type (see function get_highest_supported_ivhd_type())
before parsing the contents.

[Vincent: fix the build error of IVHD_DEV_ACPI_HID flag not found]

Signed-off-by: Wan Zongshun <vincent.wan@amd.com>
Signed-off-by: Suravee Suthikulpanit <Suravee.Suthikulpanit@amd.com>
Signed-off-by: Joerg Roedel <jroedel@suse.de>
drivers/iommu/amd_iommu_init.c

index 22e078bfe35b141c0e37986abbd12bf485827668..8f496122572484c2dfb580e4ea9c8a48134b3b8b 100644 (file)
@@ -44,7 +44,7 @@
  */
 #define IVRS_HEADER_LENGTH 48
 
-#define ACPI_IVHD_TYPE                  0x10
+#define ACPI_IVHD_TYPE_MAX_SUPPORTED   0x40
 #define ACPI_IVMD_TYPE_ALL              0x20
 #define ACPI_IVMD_TYPE                  0x21
 #define ACPI_IVMD_TYPE_RANGE            0x22
@@ -58,6 +58,7 @@
 #define IVHD_DEV_EXT_SELECT             0x46
 #define IVHD_DEV_EXT_SELECT_RANGE       0x47
 #define IVHD_DEV_SPECIAL               0x48
+#define IVHD_DEV_ACPI_HID              0xf0
 
 #define IVHD_SPECIAL_IOAPIC            1
 #define IVHD_SPECIAL_HPET              2
@@ -137,6 +138,7 @@ bool amd_iommu_irq_remap __read_mostly;
 
 static bool amd_iommu_detected;
 static bool __initdata amd_iommu_disabled;
+static int amd_iommu_target_ivhd_type;
 
 u16 amd_iommu_last_bdf;                        /* largest PCI device id we have
                                           to handle */
@@ -428,7 +430,15 @@ static inline u32 get_ivhd_header_size(struct ivhd_header *h)
  */
 static inline int ivhd_entry_length(u8 *ivhd)
 {
-       return 0x04 << (*ivhd >> 6);
+       u32 type = ((struct ivhd_entry *)ivhd)->type;
+
+       if (type < 0x80) {
+               return 0x04 << (*ivhd >> 6);
+       } else if (type == IVHD_DEV_ACPI_HID) {
+               /* For ACPI_HID, offset 21 is uid len */
+               return *((u8 *)ivhd + 21) + 22;
+       }
+       return 0;
 }
 
 /*
@@ -475,6 +485,22 @@ static int __init find_last_devid_from_ivhd(struct ivhd_header *h)
        return 0;
 }
 
+static int __init check_ivrs_checksum(struct acpi_table_header *table)
+{
+       int i;
+       u8 checksum = 0, *p = (u8 *)table;
+
+       for (i = 0; i < table->length; ++i)
+               checksum += p[i];
+       if (checksum != 0) {
+               /* ACPI table corrupt */
+               pr_err(FW_BUG "AMD-Vi: IVRS invalid checksum\n");
+               return -ENODEV;
+       }
+
+       return 0;
+}
+
 /*
  * Iterate over all IVHD entries in the ACPI table and find the highest device
  * id which we need to handle. This is the first of three functions which parse
@@ -482,31 +508,19 @@ static int __init find_last_devid_from_ivhd(struct ivhd_header *h)
  */
 static int __init find_last_devid_acpi(struct acpi_table_header *table)
 {
-       int i;
-       u8 checksum = 0, *p = (u8 *)table, *end = (u8 *)table;
+       u8 *p = (u8 *)table, *end = (u8 *)table;
        struct ivhd_header *h;
 
-       /*
-        * Validate checksum here so we don't need to do it when
-        * we actually parse the table
-        */
-       for (i = 0; i < table->length; ++i)
-               checksum += p[i];
-       if (checksum != 0)
-               /* ACPI table corrupt */
-               return -ENODEV;
-
        p += IVRS_HEADER_LENGTH;
 
        end += table->length;
        while (p < end) {
                h = (struct ivhd_header *)p;
-               switch (h->type) {
-               case ACPI_IVHD_TYPE:
-                       find_last_devid_from_ivhd(h);
-                       break;
-               default:
-                       break;
+               if (h->type == amd_iommu_target_ivhd_type) {
+                       int ret = find_last_devid_from_ivhd(h);
+
+                       if (ret)
+                               return ret;
                }
                p += h->length;
        }
@@ -1164,6 +1178,32 @@ static int __init init_iommu_one(struct amd_iommu *iommu, struct ivhd_header *h)
        return 0;
 }
 
+/**
+ * get_highest_supported_ivhd_type - Look up the appropriate IVHD type
+ * @ivrs          Pointer to the IVRS header
+ *
+ * This function search through all IVDB of the maximum supported IVHD
+ */
+static u8 get_highest_supported_ivhd_type(struct acpi_table_header *ivrs)
+{
+       u8 *base = (u8 *)ivrs;
+       struct ivhd_header *ivhd = (struct ivhd_header *)
+                                       (base + IVRS_HEADER_LENGTH);
+       u8 last_type = ivhd->type;
+       u16 devid = ivhd->devid;
+
+       while (((u8 *)ivhd - base < ivrs->length) &&
+              (ivhd->type <= ACPI_IVHD_TYPE_MAX_SUPPORTED)) {
+               u8 *p = (u8 *) ivhd;
+
+               if (ivhd->devid == devid)
+                       last_type = ivhd->type;
+               ivhd = (struct ivhd_header *)(p + ivhd->length);
+       }
+
+       return last_type;
+}
+
 /*
  * Iterates over all IOMMU entries in the ACPI table, allocates the
  * IOMMU structure and initializes it with init_iommu_one()
@@ -1180,8 +1220,7 @@ static int __init init_iommu_all(struct acpi_table_header *table)
 
        while (p < end) {
                h = (struct ivhd_header *)p;
-               switch (*p) {
-               case ACPI_IVHD_TYPE:
+               if (*p == amd_iommu_target_ivhd_type) {
 
                        DUMP_printk("device: %02x:%02x.%01x cap: %04x "
                                    "seg: %d flags: %01x info %04x\n",
@@ -1198,9 +1237,6 @@ static int __init init_iommu_all(struct acpi_table_header *table)
                        ret = init_iommu_one(iommu, h);
                        if (ret)
                                return ret;
-                       break;
-               default:
-                       break;
                }
                p += h->length;
 
@@ -1865,18 +1901,20 @@ static void __init free_dma_resources(void)
  * remapping setup code.
  *
  * This function basically parses the ACPI table for AMD IOMMU (IVRS)
- * three times:
+ * four times:
  *
- *     1 pass) Find the highest PCI device id the driver has to handle.
+ *     1 pass) Discover the most comprehensive IVHD type to use.
+ *
+ *     2 pass) Find the highest PCI device id the driver has to handle.
  *             Upon this information the size of the data structures is
  *             determined that needs to be allocated.
  *
- *     2 pass) Initialize the data structures just allocated with the
+ *     3 pass) Initialize the data structures just allocated with the
  *             information in the ACPI table about available AMD IOMMUs
  *             in the system. It also maps the PCI devices in the
  *             system to specific IOMMUs
  *
- *     3 pass) After the basic data structures are allocated and
+ *     4 pass) After the basic data structures are allocated and
  *             initialized we update them with information about memory
  *             remapping requirements parsed out of the ACPI table in
  *             this last pass.
@@ -1903,6 +1941,17 @@ static int __init early_amd_iommu_init(void)
                return -EINVAL;
        }
 
+       /*
+        * Validate checksum here so we don't need to do it when
+        * we actually parse the table
+        */
+       ret = check_ivrs_checksum(ivrs_base);
+       if (ret)
+               return ret;
+
+       amd_iommu_target_ivhd_type = get_highest_supported_ivhd_type(ivrs_base);
+       DUMP_printk("Using IVHD type %#x\n", amd_iommu_target_ivhd_type);
+
        /*
         * First parse ACPI tables to find the largest Bus/Dev/Func
         * we need to handle. Upon this information the shared data