iommu/vt-d: Implement DMAR unit hotplug framework
[cascardo/linux.git] / drivers / iommu / dmar.c
index 78aa1b2..0bd536d 100644 (file)
@@ -75,7 +75,7 @@ static unsigned long dmar_seq_ids[BITS_TO_LONGS(DMAR_UNITS_SUPPORTED)];
 static int alloc_iommu(struct dmar_drhd_unit *drhd);
 static void free_iommu(struct intel_iommu *iommu);
 
-static void __init dmar_register_drhd_unit(struct dmar_drhd_unit *drhd)
+static void dmar_register_drhd_unit(struct dmar_drhd_unit *drhd)
 {
        /*
         * add INCLUDE_ALL at the tail, so scan the list will find it at
@@ -353,24 +353,45 @@ static struct notifier_block dmar_pci_bus_nb = {
        .priority = INT_MIN,
 };
 
+static struct dmar_drhd_unit *
+dmar_find_dmaru(struct acpi_dmar_hardware_unit *drhd)
+{
+       struct dmar_drhd_unit *dmaru;
+
+       list_for_each_entry_rcu(dmaru, &dmar_drhd_units, list)
+               if (dmaru->segment == drhd->segment &&
+                   dmaru->reg_base_addr == drhd->address)
+                       return dmaru;
+
+       return NULL;
+}
+
 /**
  * dmar_parse_one_drhd - parses exactly one DMA remapping hardware definition
  * structure which uniquely represent one DMA remapping hardware unit
  * present in the platform
  */
-static int __init
-dmar_parse_one_drhd(struct acpi_dmar_header *header, void *arg)
+static int dmar_parse_one_drhd(struct acpi_dmar_header *header, void *arg)
 {
        struct acpi_dmar_hardware_unit *drhd;
        struct dmar_drhd_unit *dmaru;
        int ret = 0;
 
        drhd = (struct acpi_dmar_hardware_unit *)header;
-       dmaru = kzalloc(sizeof(*dmaru), GFP_KERNEL);
+       dmaru = dmar_find_dmaru(drhd);
+       if (dmaru)
+               goto out;
+
+       dmaru = kzalloc(sizeof(*dmaru) + header->length, GFP_KERNEL);
        if (!dmaru)
                return -ENOMEM;
 
-       dmaru->hdr = header;
+       /*
+        * If header is allocated from slab by ACPI _DSM method, we need to
+        * copy the content because the memory buffer will be freed on return.
+        */
+       dmaru->hdr = (void *)(dmaru + 1);
+       memcpy(dmaru->hdr, header, header->length);
        dmaru->reg_base_addr = drhd->address;
        dmaru->segment = drhd->segment;
        dmaru->include_all = drhd->flags & 0x1; /* BIT0: INCLUDE_ALL */
@@ -391,6 +412,7 @@ dmar_parse_one_drhd(struct acpi_dmar_header *header, void *arg)
        }
        dmar_register_drhd_unit(dmaru);
 
+out:
        if (arg)
                (*(int *)arg)++;
 
@@ -428,8 +450,7 @@ static int __init dmar_parse_one_andd(struct acpi_dmar_header *header,
 }
 
 #ifdef CONFIG_ACPI_NUMA
-static int __init
-dmar_parse_one_rhsa(struct acpi_dmar_header *header, void *arg)
+static int dmar_parse_one_rhsa(struct acpi_dmar_header *header, void *arg)
 {
        struct acpi_dmar_rhsa *rhsa;
        struct dmar_drhd_unit *drhd;
@@ -821,14 +842,22 @@ dmar_validate_one_drhd(struct acpi_dmar_header *entry, void *arg)
                return -EINVAL;
        }
 
-       addr = early_ioremap(drhd->address, VTD_PAGE_SIZE);
+       if (arg)
+               addr = ioremap(drhd->address, VTD_PAGE_SIZE);
+       else
+               addr = early_ioremap(drhd->address, VTD_PAGE_SIZE);
        if (!addr) {
                pr_warn("IOMMU: can't validate: %llx\n", drhd->address);
                return -EINVAL;
        }
+
        cap = dmar_readq(addr + DMAR_CAP_REG);
        ecap = dmar_readq(addr + DMAR_ECAP_REG);
-       early_iounmap(addr, VTD_PAGE_SIZE);
+
+       if (arg)
+               iounmap(addr);
+       else
+               early_iounmap(addr, VTD_PAGE_SIZE);
 
        if (cap == (uint64_t)-1 && ecap == (uint64_t)-1) {
                warn_invalid_dmar(drhd->address, " returns all ones");
@@ -1702,12 +1731,17 @@ int __init dmar_ir_support(void)
        return dmar->flags & 0x1;
 }
 
+/* Check whether DMAR units are in use */
+static inline bool dmar_in_use(void)
+{
+       return irq_remapping_enabled || intel_iommu_enabled;
+}
+
 static int __init dmar_free_unused_resources(void)
 {
        struct dmar_drhd_unit *dmaru, *dmaru_n;
 
-       /* DMAR units are in use */
-       if (irq_remapping_enabled || intel_iommu_enabled)
+       if (dmar_in_use())
                return 0;
 
        if (dmar_dev_scope_status != 1 && !list_empty(&dmar_drhd_units))
@@ -1725,3 +1759,215 @@ static int __init dmar_free_unused_resources(void)
 
 late_initcall(dmar_free_unused_resources);
 IOMMU_INIT_POST(detect_intel_iommu);
+
+/*
+ * DMAR Hotplug Support
+ * For more details, please refer to Intel(R) Virtualization Technology
+ * for Directed-IO Architecture Specifiction, Rev 2.2, Section 8.8
+ * "Remapping Hardware Unit Hot Plug".
+ */
+static u8 dmar_hp_uuid[] = {
+       /* 0000 */    0xA6, 0xA3, 0xC1, 0xD8, 0x9B, 0xBE, 0x9B, 0x4C,
+       /* 0008 */    0x91, 0xBF, 0xC3, 0xCB, 0x81, 0xFC, 0x5D, 0xAF
+};
+
+/*
+ * Currently there's only one revision and BIOS will not check the revision id,
+ * so use 0 for safety.
+ */
+#define        DMAR_DSM_REV_ID                 0
+#define        DMAR_DSM_FUNC_DRHD              1
+#define        DMAR_DSM_FUNC_ATSR              2
+#define        DMAR_DSM_FUNC_RHSA              3
+
+static inline bool dmar_detect_dsm(acpi_handle handle, int func)
+{
+       return acpi_check_dsm(handle, dmar_hp_uuid, DMAR_DSM_REV_ID, 1 << func);
+}
+
+static int dmar_walk_dsm_resource(acpi_handle handle, int func,
+                                 dmar_res_handler_t handler, void *arg)
+{
+       int ret = -ENODEV;
+       union acpi_object *obj;
+       struct acpi_dmar_header *start;
+       struct dmar_res_callback callback;
+       static int res_type[] = {
+               [DMAR_DSM_FUNC_DRHD] = ACPI_DMAR_TYPE_HARDWARE_UNIT,
+               [DMAR_DSM_FUNC_ATSR] = ACPI_DMAR_TYPE_ROOT_ATS,
+               [DMAR_DSM_FUNC_RHSA] = ACPI_DMAR_TYPE_HARDWARE_AFFINITY,
+       };
+
+       if (!dmar_detect_dsm(handle, func))
+               return 0;
+
+       obj = acpi_evaluate_dsm_typed(handle, dmar_hp_uuid, DMAR_DSM_REV_ID,
+                                     func, NULL, ACPI_TYPE_BUFFER);
+       if (!obj)
+               return -ENODEV;
+
+       memset(&callback, 0, sizeof(callback));
+       callback.cb[res_type[func]] = handler;
+       callback.arg[res_type[func]] = arg;
+       start = (struct acpi_dmar_header *)obj->buffer.pointer;
+       ret = dmar_walk_remapping_entries(start, obj->buffer.length, &callback);
+
+       ACPI_FREE(obj);
+
+       return ret;
+}
+
+static int dmar_hp_add_drhd(struct acpi_dmar_header *header, void *arg)
+{
+       int ret;
+       struct dmar_drhd_unit *dmaru;
+
+       dmaru = dmar_find_dmaru((struct acpi_dmar_hardware_unit *)header);
+       if (!dmaru)
+               return -ENODEV;
+
+       ret = dmar_ir_hotplug(dmaru, true);
+       if (ret == 0)
+               ret = dmar_iommu_hotplug(dmaru, true);
+
+       return ret;
+}
+
+static int dmar_hp_remove_drhd(struct acpi_dmar_header *header, void *arg)
+{
+       int i, ret;
+       struct device *dev;
+       struct dmar_drhd_unit *dmaru;
+
+       dmaru = dmar_find_dmaru((struct acpi_dmar_hardware_unit *)header);
+       if (!dmaru)
+               return 0;
+
+       /*
+        * All PCI devices managed by this unit should have been destroyed.
+        */
+       if (!dmaru->include_all && dmaru->devices && dmaru->devices_cnt)
+               for_each_active_dev_scope(dmaru->devices,
+                                         dmaru->devices_cnt, i, dev)
+                       return -EBUSY;
+
+       ret = dmar_ir_hotplug(dmaru, false);
+       if (ret == 0)
+               ret = dmar_iommu_hotplug(dmaru, false);
+
+       return ret;
+}
+
+static int dmar_hp_release_drhd(struct acpi_dmar_header *header, void *arg)
+{
+       struct dmar_drhd_unit *dmaru;
+
+       dmaru = dmar_find_dmaru((struct acpi_dmar_hardware_unit *)header);
+       if (dmaru) {
+               list_del_rcu(&dmaru->list);
+               synchronize_rcu();
+               dmar_free_drhd(dmaru);
+       }
+
+       return 0;
+}
+
+static int dmar_hotplug_insert(acpi_handle handle)
+{
+       int ret;
+       int drhd_count = 0;
+
+       ret = dmar_walk_dsm_resource(handle, DMAR_DSM_FUNC_DRHD,
+                                    &dmar_validate_one_drhd, (void *)1);
+       if (ret)
+               goto out;
+
+       ret = dmar_walk_dsm_resource(handle, DMAR_DSM_FUNC_DRHD,
+                                    &dmar_parse_one_drhd, (void *)&drhd_count);
+       if (ret == 0 && drhd_count == 0) {
+               pr_warn(FW_BUG "No DRHD structures in buffer returned by _DSM method\n");
+               goto out;
+       } else if (ret) {
+               goto release_drhd;
+       }
+
+       ret = dmar_walk_dsm_resource(handle, DMAR_DSM_FUNC_RHSA,
+                                    &dmar_parse_one_rhsa, NULL);
+       if (ret)
+               goto release_drhd;
+
+       ret = dmar_walk_dsm_resource(handle, DMAR_DSM_FUNC_ATSR,
+                                    &dmar_parse_one_atsr, NULL);
+       if (ret)
+               goto release_atsr;
+
+       ret = dmar_walk_dsm_resource(handle, DMAR_DSM_FUNC_DRHD,
+                                    &dmar_hp_add_drhd, NULL);
+       if (!ret)
+               return 0;
+
+       dmar_walk_dsm_resource(handle, DMAR_DSM_FUNC_DRHD,
+                              &dmar_hp_remove_drhd, NULL);
+release_atsr:
+       dmar_walk_dsm_resource(handle, DMAR_DSM_FUNC_ATSR,
+                              &dmar_release_one_atsr, NULL);
+release_drhd:
+       dmar_walk_dsm_resource(handle, DMAR_DSM_FUNC_DRHD,
+                              &dmar_hp_release_drhd, NULL);
+out:
+       return ret;
+}
+
+static int dmar_hotplug_remove(acpi_handle handle)
+{
+       int ret;
+
+       ret = dmar_walk_dsm_resource(handle, DMAR_DSM_FUNC_ATSR,
+                                    &dmar_check_one_atsr, NULL);
+       if (ret)
+               return ret;
+
+       ret = dmar_walk_dsm_resource(handle, DMAR_DSM_FUNC_DRHD,
+                                    &dmar_hp_remove_drhd, NULL);
+       if (ret == 0) {
+               WARN_ON(dmar_walk_dsm_resource(handle, DMAR_DSM_FUNC_ATSR,
+                                              &dmar_release_one_atsr, NULL));
+               WARN_ON(dmar_walk_dsm_resource(handle, DMAR_DSM_FUNC_DRHD,
+                                              &dmar_hp_release_drhd, NULL));
+       } else {
+               dmar_walk_dsm_resource(handle, DMAR_DSM_FUNC_DRHD,
+                                      &dmar_hp_add_drhd, NULL);
+       }
+
+       return ret;
+}
+
+static int dmar_device_hotplug(acpi_handle handle, bool insert)
+{
+       int ret;
+
+       if (!dmar_in_use())
+               return 0;
+
+       if (!dmar_detect_dsm(handle, DMAR_DSM_FUNC_DRHD))
+               return 0;
+
+       down_write(&dmar_global_lock);
+       if (insert)
+               ret = dmar_hotplug_insert(handle);
+       else
+               ret = dmar_hotplug_remove(handle);
+       up_write(&dmar_global_lock);
+
+       return ret;
+}
+
+int dmar_device_add(acpi_handle handle)
+{
+       return dmar_device_hotplug(handle, true);
+}
+
+int dmar_device_remove(acpi_handle handle)
+{
+       return dmar_device_hotplug(handle, false);
+}