iommu/amd: Add new map for storing IVHD dev entry type HID
[cascardo/linux.git] / drivers / iommu / amd_iommu_init.c
index bf4959f..e7ebfa2 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
 #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 UID_NOT_PRESENT                 0
+#define UID_IS_INTEGER                  1
+#define UID_IS_CHARACTER                2
 
 #define IVHD_SPECIAL_IOAPIC            1
 #define IVHD_SPECIAL_HPET              2
@@ -99,7 +104,11 @@ struct ivhd_header {
        u64 mmio_phys;
        u16 pci_seg;
        u16 info;
-       u32 efr;
+       u32 efr_attr;
+
+       /* Following only valid on IVHD type 11h and 40h */
+       u64 efr_reg; /* Exact copy of MMIO_EXT_FEATURES */
+       u64 res;
 } __attribute__((packed));
 
 /*
@@ -111,6 +120,11 @@ struct ivhd_entry {
        u16 devid;
        u8 flags;
        u32 ext;
+       u32 hidh;
+       u64 cid;
+       u8 uidf;
+       u8 uidl;
+       u8 uid;
 } __attribute__((packed));
 
 /*
@@ -133,6 +147,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 */
@@ -218,8 +233,12 @@ enum iommu_init_state {
 #define EARLY_MAP_SIZE         4
 static struct devid_map __initdata early_ioapic_map[EARLY_MAP_SIZE];
 static struct devid_map __initdata early_hpet_map[EARLY_MAP_SIZE];
+static struct acpihid_map_entry __initdata early_acpihid_map[EARLY_MAP_SIZE];
+
 static int __initdata early_ioapic_map_size;
 static int __initdata early_hpet_map_size;
+static int __initdata early_acpihid_map_size;
+
 static bool __initdata cmdline_maps;
 
 static enum iommu_init_state init_state = IOMMU_START_STATE;
@@ -394,6 +413,22 @@ static void __init iommu_unmap_mmio_space(struct amd_iommu *iommu)
        release_mem_region(iommu->mmio_phys, iommu->mmio_phys_end);
 }
 
+static inline u32 get_ivhd_header_size(struct ivhd_header *h)
+{
+       u32 size = 0;
+
+       switch (h->type) {
+       case 0x10:
+               size = 24;
+               break;
+       case 0x11:
+       case 0x40:
+               size = 40;
+               break;
+       }
+       return size;
+}
+
 /****************************************************************************
  *
  * The functions below belong to the first pass of AMD IOMMU ACPI table
@@ -408,7 +443,15 @@ static void __init iommu_unmap_mmio_space(struct amd_iommu *iommu)
  */
 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;
 }
 
 /*
@@ -420,7 +463,14 @@ static int __init find_last_devid_from_ivhd(struct ivhd_header *h)
        u8 *p = (void *)h, *end = (void *)h;
        struct ivhd_entry *dev;
 
-       p += sizeof(*h);
+       u32 ivhd_size = get_ivhd_header_size(h);
+
+       if (!ivhd_size) {
+               pr_err("AMD-Vi: Unsupported IVHD type %#x\n", h->type);
+               return -EINVAL;
+       }
+
+       p += ivhd_size;
        end += h->length;
 
        while (p < end) {
@@ -448,6 +498,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
@@ -455,31 +521,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;
        }
@@ -724,6 +778,42 @@ static int __init add_special_device(u8 type, u8 id, u16 *devid, bool cmd_line)
        return 0;
 }
 
+static int __init add_acpi_hid_device(u8 *hid, u8 *uid, u16 *devid,
+                                     bool cmd_line)
+{
+       struct acpihid_map_entry *entry;
+       struct list_head *list = &acpihid_map;
+
+       list_for_each_entry(entry, list, list) {
+               if (strcmp(entry->hid, hid) ||
+                   (*uid && *entry->uid && strcmp(entry->uid, uid)) ||
+                   !entry->cmd_line)
+                       continue;
+
+               pr_info("AMD-Vi: Command-line override for hid:%s uid:%s\n",
+                       hid, uid);
+               *devid = entry->devid;
+               return 0;
+       }
+
+       entry = kzalloc(sizeof(*entry), GFP_KERNEL);
+       if (!entry)
+               return -ENOMEM;
+
+       memcpy(entry->uid, uid, strlen(uid));
+       memcpy(entry->hid, hid, strlen(hid));
+       entry->devid = *devid;
+       entry->cmd_line = cmd_line;
+       entry->root_devid = (entry->devid & (~0x7));
+
+       pr_info("AMD-Vi:%s, add hid:%s, uid:%s, rdevid:%d\n",
+               entry->cmd_line ? "cmd" : "ivrs",
+               entry->hid, entry->uid, entry->root_devid);
+
+       list_add_tail(&entry->list, list);
+       return 0;
+}
+
 static int __init add_early_maps(void)
 {
        int i, ret;
@@ -746,6 +836,15 @@ static int __init add_early_maps(void)
                        return ret;
        }
 
+       for (i = 0; i < early_acpihid_map_size; ++i) {
+               ret = add_acpi_hid_device(early_acpihid_map[i].hid,
+                                         early_acpihid_map[i].uid,
+                                         &early_acpihid_map[i].devid,
+                                         early_acpihid_map[i].cmd_line);
+               if (ret)
+                       return ret;
+       }
+
        return 0;
 }
 
@@ -785,6 +884,7 @@ static int __init init_iommu_from_acpi(struct amd_iommu *iommu,
        u32 dev_i, ext_flags = 0;
        bool alias = false;
        struct ivhd_entry *e;
+       u32 ivhd_size;
        int ret;
 
 
@@ -800,7 +900,14 @@ static int __init init_iommu_from_acpi(struct amd_iommu *iommu,
        /*
         * Done. Now parse the device entries
         */
-       p += sizeof(struct ivhd_header);
+       ivhd_size = get_ivhd_header_size(h);
+       if (!ivhd_size) {
+               pr_err("AMD-Vi: Unsupported IVHD type %#x\n", h->type);
+               return -EINVAL;
+       }
+
+       p += ivhd_size;
+
        end += h->length;
 
 
@@ -958,6 +1065,70 @@ static int __init init_iommu_from_acpi(struct amd_iommu *iommu,
 
                        break;
                }
+               case IVHD_DEV_ACPI_HID: {
+                       u16 devid;
+                       u8 hid[ACPIHID_HID_LEN] = {0};
+                       u8 uid[ACPIHID_UID_LEN] = {0};
+                       int ret;
+
+                       if (h->type != 0x40) {
+                               pr_err(FW_BUG "Invalid IVHD device type %#x\n",
+                                      e->type);
+                               break;
+                       }
+
+                       memcpy(hid, (u8 *)(&e->ext), ACPIHID_HID_LEN - 1);
+                       hid[ACPIHID_HID_LEN - 1] = '\0';
+
+                       if (!(*hid)) {
+                               pr_err(FW_BUG "Invalid HID.\n");
+                               break;
+                       }
+
+                       switch (e->uidf) {
+                       case UID_NOT_PRESENT:
+
+                               if (e->uidl != 0)
+                                       pr_warn(FW_BUG "Invalid UID length.\n");
+
+                               break;
+                       case UID_IS_INTEGER:
+
+                               sprintf(uid, "%d", e->uid);
+
+                               break;
+                       case UID_IS_CHARACTER:
+
+                               memcpy(uid, (u8 *)(&e->uid), ACPIHID_UID_LEN - 1);
+                               uid[ACPIHID_UID_LEN - 1] = '\0';
+
+                               break;
+                       default:
+                               break;
+                       }
+
+                       DUMP_printk("  DEV_ACPI_HID(%s[%s])\t\tdevid: %02x:%02x.%x\n",
+                                   hid, uid,
+                                   PCI_BUS_NUM(devid),
+                                   PCI_SLOT(devid),
+                                   PCI_FUNC(devid));
+
+                       devid  = e->devid;
+                       flags = e->flags;
+
+                       ret = add_acpi_hid_device(hid, uid, &devid, false);
+                       if (ret)
+                               return ret;
+
+                       /*
+                        * add_special_device might update the devid in case a
+                        * command-line override is present. So call
+                        * set_dev_entry_from_acpi after add_special_device.
+                        */
+                       set_dev_entry_from_acpi(iommu, devid, e->flags, 0);
+
+                       break;
+               }
                default:
                        break;
                }
@@ -1078,13 +1249,25 @@ static int __init init_iommu_one(struct amd_iommu *iommu, struct ivhd_header *h)
        iommu->pci_seg = h->pci_seg;
        iommu->mmio_phys = h->mmio_phys;
 
-       /* Check if IVHD EFR contains proper max banks/counters */
-       if ((h->efr != 0) &&
-           ((h->efr & (0xF << 13)) != 0) &&
-           ((h->efr & (0x3F << 17)) != 0)) {
-               iommu->mmio_phys_end = MMIO_REG_END_OFFSET;
-       } else {
-               iommu->mmio_phys_end = MMIO_CNTR_CONF_OFFSET;
+       switch (h->type) {
+       case 0x10:
+               /* Check if IVHD EFR contains proper max banks/counters */
+               if ((h->efr_attr != 0) &&
+                   ((h->efr_attr & (0xF << 13)) != 0) &&
+                   ((h->efr_attr & (0x3F << 17)) != 0))
+                       iommu->mmio_phys_end = MMIO_REG_END_OFFSET;
+               else
+                       iommu->mmio_phys_end = MMIO_CNTR_CONF_OFFSET;
+               break;
+       case 0x11:
+       case 0x40:
+               if (h->efr_reg & (1 << 9))
+                       iommu->mmio_phys_end = MMIO_REG_END_OFFSET;
+               else
+                       iommu->mmio_phys_end = MMIO_CNTR_CONF_OFFSET;
+               break;
+       default:
+               return -EINVAL;
        }
 
        iommu->mmio_base = iommu_map_mmio_space(iommu->mmio_phys,
@@ -1117,6 +1300,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()
@@ -1133,8 +1342,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",
@@ -1151,9 +1359,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;
 
@@ -1818,18 +2023,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) Discover the most comprehensive IVHD type to use.
  *
- *     1 pass) Find the highest PCI device id the driver has to handle.
+ *     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.
@@ -1856,6 +2063,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