Currently, DMAR driver save target pci devices pointers for drhd/rmrr/atsr
in (pci_dev *) array, but never update it after initialization. 
This is not safe, because pci devices maybe hot added or removed during 
system running. They will have new pci_dev * pointer. So if there have 
two IOMMUs or more in system, these devices will find a wrong drhd during 
DMA mapping. And DMAR faults will occur. This patch save pci device id 
to fix this issue. Pci device id will be used to update pci_dev *pointer
info during device hotplug in intel iommu driver notifier, Other we use list 
to manage target devices for IOMMU, then we can easily use list helper.

Fault log: after remove and rescan a pci device
[  611.857095] dmar: DRHD: handling fault status reg 2
[  611.857109] dmar: DMAR:[DMA Read] Request device [86:00.3] fault addr 
ffff7000
[  611.857109] DMAR:[fault reason 02] Present bit in context entry is clear
[  611.857524] dmar: DRHD: handling fault status reg 102
[  611.857534] dmar: DMAR:[DMA Read] Request device [86:00.3] fault addr 
ffff6000
[  611.857534] DMAR:[fault reason 02] Present bit in context entry is clear
[  611.857936] dmar: DRHD: handling fault status reg 202
[  611.857947] dmar: DMAR:[DMA Read] Request device [86:00.3] fault addr 
ffff5000
[  611.857947] DMAR:[fault reason 02] Present bit in context entry is clear
[  611.858351] dmar: DRHD: handling fault status reg 302
[  611.858362] dmar: DMAR:[DMA Read] Request device [86:00.3] fault addr 
ffff4000
[  611.858362] DMAR:[fault reason 02] Present bit in context entry is clear
[  611.860819] IPv6: ADDRCONF(NETDEV_UP): eth3: link is not ready
[  611.860983] dmar: DRHD: handling fault status reg 402
[  611.860995] dmar: INTR-REMAP: Request device [[86:00.3] fault index a4
[  611.860995] INTR-REMAP:[fault reason 34] Present field in the IRTE entry is 
clear

Signed-off-by: Yijing Wang <[email protected]>
---
 drivers/iommu/dmar.c        |   82 +++++++++++-----------
 drivers/iommu/intel-iommu.c |  161 +++++++++++++++++++++++++++++-------------
 include/linux/dmar.h        |   24 ++++--
 3 files changed, 167 insertions(+), 100 deletions(-)

diff --git a/drivers/iommu/dmar.c b/drivers/iommu/dmar.c
index 8b452c9..012b431 100644
--- a/drivers/iommu/dmar.c
+++ b/drivers/iommu/dmar.c
@@ -65,11 +65,12 @@ static void __init dmar_register_drhd_unit(struct 
dmar_drhd_unit *drhd)
 }
 
 static int __init dmar_parse_one_dev_scope(struct acpi_dmar_device_scope 
*scope,
-                                          struct pci_dev **dev, u16 segment)
+                                          struct list_head *head, u16 segment)
 {
        struct pci_bus *bus;
        struct pci_dev *pdev = NULL;
        struct acpi_dmar_pci_path *path;
+       struct dmar_device *dmar_dev;
        int count;
 
        bus = pci_find_bus(segment, scope->bus);
@@ -99,8 +100,7 @@ static int __init dmar_parse_one_dev_scope(struct 
acpi_dmar_device_scope *scope,
        }
        if (!pdev) {
                pr_warn("Device scope device [%04x:%02x:%02x.%02x] not found\n",
-                       segment, scope->bus, path->device, path->function);
-               *dev = NULL;
+                       segment, scope->bus, path->dev, path->fn);
                return 0;
        }
        if ((scope->entry_type == ACPI_DMAR_SCOPE_TYPE_ENDPOINT && \
@@ -111,50 +111,36 @@ static int __init dmar_parse_one_dev_scope(struct 
acpi_dmar_device_scope *scope,
                        pci_name(pdev));
                return -EINVAL;
        }
-       *dev = pdev;
+
+       dmar_dev = kzalloc(sizeof(struct dmar_device), GFP_KERNEL);
+       if (!dmar_dev) {
+               pci_dev_put(pdev);
+               return -ENOMEM;
+       }
+       dmar_dev->pdev = pdev;
+       dmar_dev->bus = pdev->bus->number;
+       dmar_dev->devfn = pdev->devfn;
+       dmar_dev->segment = segment;
+
+       list_add_tail(&dmar_dev->list, head);
+
        return 0;
 }
 
-int __init dmar_parse_dev_scope(void *start, void *end, int *cnt,
-                               struct pci_dev ***devices, u16 segment)
+int __init dmar_parse_dev_scope(void *start, void *end,
+               struct list_head *head, u16 segment)
 {
        struct acpi_dmar_device_scope *scope;
-       void * tmp = start;
-       int index;
        int ret;
 
-       *cnt = 0;
-       while (start < end) {
-               scope = start;
-               if (scope->entry_type == ACPI_DMAR_SCOPE_TYPE_ENDPOINT ||
-                   scope->entry_type == ACPI_DMAR_SCOPE_TYPE_BRIDGE)
-                       (*cnt)++;
-               else if (scope->entry_type != ACPI_DMAR_SCOPE_TYPE_IOAPIC &&
-                       scope->entry_type != ACPI_DMAR_SCOPE_TYPE_HPET) {
-                       pr_warn("Unsupported device scope\n");
-               }
-               start += scope->length;
-       }
-       if (*cnt == 0)
-               return 0;
-
-       *devices = kcalloc(*cnt, sizeof(struct pci_dev *), GFP_KERNEL);
-       if (!*devices)
-               return -ENOMEM;
-
-       start = tmp;
-       index = 0;
        while (start < end) {
                scope = start;
                if (scope->entry_type == ACPI_DMAR_SCOPE_TYPE_ENDPOINT ||
                    scope->entry_type == ACPI_DMAR_SCOPE_TYPE_BRIDGE) {
-                       ret = dmar_parse_one_dev_scope(scope,
-                               &(*devices)[index], segment);
+                       ret = dmar_parse_one_dev_scope(scope, head, segment);
                        if (ret) {
-                               kfree(*devices);
                                return ret;
                        }
-                       index ++;
                }
                start += scope->length;
        }
@@ -183,6 +169,7 @@ dmar_parse_one_drhd(struct acpi_dmar_header *header)
        dmaru->reg_base_addr = drhd->address;
        dmaru->segment = drhd->segment;
        dmaru->include_all = drhd->flags & 0x1; /* BIT0: INCLUDE_ALL */
+       INIT_LIST_HEAD(&dmaru->devices);
 
        ret = alloc_iommu(dmaru);
        if (ret) {
@@ -193,6 +180,19 @@ dmar_parse_one_drhd(struct acpi_dmar_header *header)
        return 0;
 }
 
+static void dmar_free(struct dmar_drhd_unit *dmaru)
+{
+       struct dmar_device *dmar_dev, *tmp;
+
+       list_for_each_entry_safe(dmar_dev, tmp,
+                       &dmaru->devices, list) {
+               if (dmar_dev->pdev)
+                       pci_dev_put(dmar_dev->pdev);
+               list_del(&dmar_dev->list);
+               kfree(dmar_dev);
+       }
+}
+
 static int __init dmar_parse_dev(struct dmar_drhd_unit *dmaru)
 {
        struct acpi_dmar_hardware_unit *drhd;
@@ -205,11 +205,10 @@ static int __init dmar_parse_dev(struct dmar_drhd_unit 
*dmaru)
 
        ret = dmar_parse_dev_scope((void *)(drhd + 1),
                                ((void *)drhd) + drhd->header.length,
-                               &dmaru->devices_cnt, &dmaru->devices,
-                               drhd->segment);
+                               &dmaru->devices, drhd->segment);
        if (ret) {
                list_del(&dmaru->list);
-               kfree(dmaru);
+               dmar_free(dmaru);
        }
        return ret;
 }
@@ -378,14 +377,14 @@ parse_dmar_table(void)
        return ret;
 }
 
-static int dmar_pci_device_match(struct pci_dev *devices[], int cnt,
+static int dmar_pci_device_match(struct list_head *head,
                          struct pci_dev *dev)
 {
-       int index;
+       struct dmar_device *dmar_dev;
 
        while (dev) {
-               for (index = 0; index < cnt; index++)
-                       if (dev == devices[index])
+               list_for_each_entry(dmar_dev, head, list)
+                       if (dev == dmar_dev->pdev)
                                return 1;
 
                /* Check our parent */
@@ -412,8 +411,7 @@ dmar_find_matched_drhd_unit(struct pci_dev *dev)
                    drhd->segment == pci_domain_nr(dev->bus))
                        return dmaru;
 
-               if (dmar_pci_device_match(dmaru->devices,
-                                         dmaru->devices_cnt, dev))
+               if (dmar_pci_device_match(&dmaru->devices, dev))
                        return dmaru;
        }
 
diff --git a/drivers/iommu/intel-iommu.c b/drivers/iommu/intel-iommu.c
index 43b9bfe..8dd7dbe 100644
--- a/drivers/iommu/intel-iommu.c
+++ b/drivers/iommu/intel-iommu.c
@@ -650,23 +650,23 @@ static void domain_update_iommu_cap(struct dmar_domain 
*domain)
 static struct intel_iommu *device_to_iommu(int segment, u8 bus, u8 devfn)
 {
        struct dmar_drhd_unit *drhd = NULL;
-       int i;
+       struct dmar_device *dmar_dev;
 
        for_each_drhd_unit(drhd) {
                if (drhd->ignored)
                        continue;
                if (segment != drhd->segment)
                        continue;
-
-               for (i = 0; i < drhd->devices_cnt; i++) {
-                       if (drhd->devices[i] &&
-                           drhd->devices[i]->bus->number == bus &&
-                           drhd->devices[i]->devfn == devfn)
+
+               list_for_each_entry(dmar_dev, &drhd->devices, list) {
+                       if (dmar_dev->pdev &&
+                           dmar_dev->pdev->bus->number == bus &&
+                           dmar_dev->pdev->devfn == devfn)
                                return drhd->iommu;
-                       if (drhd->devices[i] &&
-                           drhd->devices[i]->subordinate &&
-                           drhd->devices[i]->subordinate->number <= bus &&
-                           drhd->devices[i]->subordinate->busn_res.end >= bus)
+                       if (dmar_dev->pdev &&
+                           dmar_dev->pdev->subordinate &&
+                               dmar_dev->pdev->subordinate->number <= bus &&
+                           dmar_dev->pdev->subordinate->busn_res.end >= bus)
                                return drhd->iommu;
                }
 
@@ -2335,15 +2335,16 @@ static int domain_add_dev_info(struct dmar_domain 
*domain,
 static bool device_has_rmrr(struct pci_dev *dev)
 {
        struct dmar_rmrr_unit *rmrr;
-       int i;
+       struct dmar_device *dmar_dev;
 
        for_each_rmrr_units(rmrr) {
-               for (i = 0; i < rmrr->devices_cnt; i++) {
+               list_for_each_entry(dmar_dev, &rmrr->devices,
+                               list) {
                        /*
                         * Return TRUE if this RMRR contains the device that
                         * is passed in.
                         */
-                       if (rmrr->devices[i] == dev)
+                       if (dmar_dev->pdev == dev)
                                return true;
                }
        }
@@ -2455,7 +2456,7 @@ static int __init init_dmars(void)
        struct dmar_rmrr_unit *rmrr;
        struct pci_dev *pdev;
        struct intel_iommu *iommu;
-       int i, ret;
+       int ret;
 
        /*
         * for each drhd
@@ -2609,8 +2610,10 @@ static int __init init_dmars(void)
         */
        printk(KERN_INFO "IOMMU: Setting RMRR:\n");
        for_each_rmrr_units(rmrr) {
-               for (i = 0; i < rmrr->devices_cnt; i++) {
-                       pdev = rmrr->devices[i];
+               struct dmar_device *dmar_dev;
+               list_for_each_entry(dmar_dev, &rmrr->devices,
+                        list) {
+                       pdev = dmar_dev->pdev;
                        /*
                         * some BIOS lists non-exist devices in DMAR
                         * table.
@@ -3305,30 +3308,39 @@ DECLARE_PCI_FIXUP_ENABLE(PCI_VENDOR_ID_INTEL, 
PCI_DEVICE_ID_INTEL_IOAT_SNB, quir
 static void __init init_no_remapping_devices(void)
 {
        struct dmar_drhd_unit *drhd;
+       int ignore;
 
        for_each_drhd_unit(drhd) {
                if (!drhd->include_all) {
-                       int i;
-                       for (i = 0; i < drhd->devices_cnt; i++)
-                               if (drhd->devices[i] != NULL)
+                       struct dmar_device *dmar_dev;
+                       ignore = 1;
+                       list_for_each_entry(dmar_dev, &drhd->devices,
+                                       list)
+                               if (dmar_dev->pdev != NULL) {
+                                       ignore = 0;
                                        break;
+                               }
                        /* ignore DMAR unit if no pci devices exist */
-                       if (i == drhd->devices_cnt)
+                       if (ignore)
                                drhd->ignored = 1;
                }
        }
 
        for_each_drhd_unit(drhd) {
-               int i;
+               struct dmar_device *dmar_dev;
+               int flag = 0;
+
                if (drhd->ignored || drhd->include_all)
                        continue;
 
-               for (i = 0; i < drhd->devices_cnt; i++)
-                       if (drhd->devices[i] &&
-                           !IS_GFX_DEVICE(drhd->devices[i]))
+               list_for_each_entry(dmar_dev, &drhd->devices, list)
+                       if (dmar_dev->pdev &&
+                           !IS_GFX_DEVICE(dmar_dev->pdev)) {
+                               flag = 1;
                                break;
+                       }
 
-               if (i < drhd->devices_cnt)
+               if (flag)
                        continue;
 
                /* This IOMMU has *only* gfx devices. Either bypass it or
@@ -3337,10 +3349,10 @@ static void __init init_no_remapping_devices(void)
                        intel_iommu_gfx_mapped = 1;
                } else {
                        drhd->ignored = 1;
-                       for (i = 0; i < drhd->devices_cnt; i++) {
-                               if (!drhd->devices[i])
+                       list_for_each_entry(dmar_dev, &drhd->devices, list) {
+                               if (!dmar_dev->pdev)
                                        continue;
-                               drhd->devices[i]->dev.archdata.iommu = 
DUMMY_DEVICE_DOMAIN_INFO;
+                               dmar_dev->pdev->dev.archdata.iommu = 
DUMMY_DEVICE_DOMAIN_INFO;
                        }
                }
        }
@@ -3505,11 +3517,25 @@ int __init dmar_parse_one_rmrr(struct acpi_dmar_header 
*header)
        rmrr = (struct acpi_dmar_reserved_memory *)header;
        rmrru->base_address = rmrr->base_address;
        rmrru->end_address = rmrr->end_address;
+       INIT_LIST_HEAD(&rmrru->devices);
 
        dmar_register_rmrr_unit(rmrru);
        return 0;
 }
 
+static void rmrr_free(struct dmar_rmrr_unit *rmrru)
+{
+       struct dmar_device *dmar_dev, *tmp;
+
+       list_for_each_entry_safe(dmar_dev, tmp,
+                       &rmrru->devices, list) {
+               if (dmar_dev->pdev)
+                       pci_dev_put(dmar_dev->pdev);
+               list_del(&dmar_dev->list);
+               kfree(dmar_dev);
+       }
+}
+
 static int __init
 rmrr_parse_dev(struct dmar_rmrr_unit *rmrru)
 {
@@ -3519,11 +3545,11 @@ rmrr_parse_dev(struct dmar_rmrr_unit *rmrru)
        rmrr = (struct acpi_dmar_reserved_memory *) rmrru->hdr;
        ret = dmar_parse_dev_scope((void *)(rmrr + 1),
                ((void *)rmrr) + rmrr->header.length,
-               &rmrru->devices_cnt, &rmrru->devices, rmrr->segment);
+               &rmrru->devices, rmrr->segment);
 
-       if (ret || (rmrru->devices_cnt == 0)) {
+       if (ret || list_empty(&rmrru->devices)) {
                list_del(&rmrru->list);
-               kfree(rmrru);
+               rmrr_free(rmrru);
        }
        return ret;
 }
@@ -3542,12 +3568,26 @@ int __init dmar_parse_one_atsr(struct acpi_dmar_header 
*hdr)
 
        atsru->hdr = hdr;
        atsru->include_all = atsr->flags & 0x1;
+       INIT_LIST_HEAD(&atsru->devices);
 
        list_add(&atsru->list, &dmar_atsr_units);
 
        return 0;
 }
 
+static void atsr_free(struct dmar_atsr_unit *atsru)
+{
+       struct dmar_device *dmar_dev, *tmp;
+
+       list_for_each_entry_safe(dmar_dev, tmp,
+                       &atsru->devices, list) {
+               if (dmar_dev->pdev)
+                       pci_dev_put(dmar_dev->pdev);
+               list_del(&dmar_dev->list);
+               kfree(dmar_dev);
+       }
+}
+
 static int __init atsr_parse_dev(struct dmar_atsr_unit *atsru)
 {
        int rc;
@@ -3559,11 +3599,10 @@ static int __init atsr_parse_dev(struct dmar_atsr_unit 
*atsru)
        atsr = container_of(atsru->hdr, struct acpi_dmar_atsr, header);
        rc = dmar_parse_dev_scope((void *)(atsr + 1),
                                (void *)atsr + atsr->header.length,
-                               &atsru->devices_cnt, &atsru->devices,
-                               atsr->segment);
-       if (rc || !atsru->devices_cnt) {
+                               &atsru->devices, atsr->segment);
+       if (rc || list_empty(&atsru->devices)) {
                list_del(&atsru->list);
-               kfree(atsru);
+               atsr_free(atsru);
        }
 
        return rc;
@@ -3571,7 +3610,6 @@ static int __init atsr_parse_dev(struct dmar_atsr_unit 
*atsru)
 
 int dmar_find_matched_atsr_unit(struct pci_dev *dev)
 {
-       int i;
        struct pci_bus *bus;
        struct acpi_dmar_atsr *atsr;
        struct dmar_atsr_unit *atsru;
@@ -3589,14 +3627,16 @@ int dmar_find_matched_atsr_unit(struct pci_dev *dev)
 found:
        for (bus = dev->bus; bus; bus = bus->parent) {
                struct pci_dev *bridge = bus->self;
+               struct dmar_device *dmar_dev;
 
                if (!bridge || !pci_is_pcie(bridge) ||
                    pci_pcie_type(bridge) == PCI_EXP_TYPE_PCI_BRIDGE)
                        return 0;
 
                if (pci_pcie_type(bridge) == PCI_EXP_TYPE_ROOT_PORT) {
-                       for (i = 0; i < atsru->devices_cnt; i++)
-                               if (atsru->devices[i] == bridge)
+                       list_for_each_entry(dmar_dev, &atsru->devices,
+                                       list)
+                               if (dmar_dev->pdev == bridge)
                                        return 1;
                        break;
                }
@@ -3641,21 +3681,42 @@ static int device_notifier(struct notifier_block *nb,
        struct device *dev = data;
        struct pci_dev *pdev = to_pci_dev(dev);
        struct dmar_domain *domain;
+       struct dmar_device *dmar_dev;
+       struct dmar_drhd_unit *drhd;
 
-       if (iommu_no_mapping(dev))
-               return 0;
-
-       domain = find_domain(pdev);
-       if (!domain)
-               return 0;
+       switch (action) {
+       case BUS_NOTIFY_ADD_DEVICE:
+               for_each_drhd_unit(drhd)
+                       list_for_each_entry(dmar_dev, &drhd->devices, list)
+                               if (dmar_dev->segment == 
pci_domain_nr(pdev->bus)
+                                       && dmar_dev->bus == pdev->bus->number
+                                       && dmar_dev->devfn == pdev->devfn)
+                                       dmar_dev->pdev = pci_dev_get(pdev);
+               break;
+       case BUS_NOTIFY_DEL_DEVICE:
+               for_each_drhd_unit(drhd)
+                       list_for_each_entry(dmar_dev, &drhd->devices, list)
+                               if (dmar_dev->pdev == pdev) {
+                                       pci_dev_put(pdev);
+                                       dmar_dev->pdev = NULL;
+                               }
+               break;
+       case BUS_NOTIFY_UNBOUND_DRIVER:
+               if (iommu_no_mapping(dev))
+                       return 0;
 
-       if (action == BUS_NOTIFY_UNBOUND_DRIVER && !iommu_pass_through) {
-               domain_remove_one_dev_info(domain, pdev);
+               domain = find_domain(pdev);
+               if (!domain)
+                       return 0;
 
-               if (!(domain->flags & DOMAIN_FLAG_VIRTUAL_MACHINE) &&
-                   !(domain->flags & DOMAIN_FLAG_STATIC_IDENTITY) &&
-                   list_empty(&domain->devices))
-                       domain_exit(domain);
+               if (!iommu_pass_through) {
+                       domain_remove_one_dev_info(domain, pdev);
+                       if (!(domain->flags & DOMAIN_FLAG_VIRTUAL_MACHINE)
+                               && !(domain->flags & 
DOMAIN_FLAG_STATIC_IDENTITY)
+                               && list_empty(&domain->devices))
+                               domain_exit(domain);
+               }
+               break;
        }
 
        return 0;
diff --git a/include/linux/dmar.h b/include/linux/dmar.h
index b029d1a..022cb80 100644
--- a/include/linux/dmar.h
+++ b/include/linux/dmar.h
@@ -32,6 +32,17 @@ struct acpi_dmar_header;
 #define DMAR_INTR_REMAP                0x1
 #define DMAR_X2APIC_OPT_OUT    0x2
 
+/* Save pci device id here to help update
+ * (pci_dev *) pointer during device hotplug
+ */
+struct dmar_device {
+       struct list_head list;
+       u8 bus;
+       u8 devfn;
+       u16 segment;
+       struct pci_dev *pdev;
+};
+
 struct intel_iommu;
 #ifdef CONFIG_DMAR_TABLE
 extern struct acpi_table_header *dmar_tbl;
@@ -39,8 +50,7 @@ struct dmar_drhd_unit {
        struct list_head list;          /* list of drhd units   */
        struct  acpi_dmar_header *hdr;  /* ACPI header          */
        u64     reg_base_addr;          /* register base address*/
-       struct  pci_dev **devices;      /* target device array  */
-       int     devices_cnt;            /* target device count  */
+       struct list_head devices; /* target device list */
        u16     segment;                /* PCI domain           */
        u8      ignored:1;              /* ignore drhd          */
        u8      include_all:1;
@@ -139,8 +149,7 @@ struct dmar_rmrr_unit {
        struct acpi_dmar_header *hdr;   /* ACPI header          */
        u64     base_address;           /* reserved base address*/
        u64     end_address;            /* reserved end address */
-       struct pci_dev **devices;       /* target devices */
-       int     devices_cnt;            /* target device count */
+       struct list_head devices; /* target device list */
 };
 
 #define for_each_rmrr_units(rmrr) \
@@ -149,16 +158,15 @@ struct dmar_rmrr_unit {
 struct dmar_atsr_unit {
        struct list_head list;          /* list of ATSR units */
        struct acpi_dmar_header *hdr;   /* ACPI header */
-       struct pci_dev **devices;       /* target devices */
-       int devices_cnt;                /* target device count */
+       struct list_head devices;       /* target device list */
        u8 include_all:1;               /* include all ports */
 };
 
 int dmar_parse_rmrr_atsr_dev(void);
 extern int dmar_parse_one_rmrr(struct acpi_dmar_header *header);
 extern int dmar_parse_one_atsr(struct acpi_dmar_header *header);
-extern int dmar_parse_dev_scope(void *start, void *end, int *cnt,
-                               struct pci_dev ***devices, u16 segment);
+extern int dmar_parse_dev_scope(void *start, void *end,
+                               struct list_head *head, u16 segment);
 extern int intel_iommu_init(void);
 #else /* !CONFIG_INTEL_IOMMU: */
 static inline int intel_iommu_init(void) { return -ENODEV; }
-- 
1.7.1


_______________________________________________
iommu mailing list
[email protected]
https://lists.linuxfoundation.org/mailman/listinfo/iommu

Reply via email to