intel-iommu: Handle PCI domains appropriately.
David Woodhouse [Sat, 4 Apr 2009 00:45:37 +0000 (01:45 +0100)]
We were comparing {bus,devfn} and assuming that a match meant it was the
same device. It doesn't -- the same {bus,devfn} can exist in
multiple PCI domains. Include domain number in device identification
(and call it 'segment' in most places, because there's already a lot of
references to 'domain' which means something else, and this code is
infected with ACPI thinking already).

Signed-off-by: David Woodhouse <David.Woodhouse@intel.com>

drivers/pci/dmar.c
drivers/pci/intel-iommu.c
include/linux/dmar.h

index 3fbe6af..25a00ce 100644 (file)
@@ -180,6 +180,7 @@ dmar_parse_one_drhd(struct acpi_dmar_header *header)
        dmaru->hdr = header;
        drhd = (struct acpi_dmar_hardware_unit *)header;
        dmaru->reg_base_addr = drhd->address;
+       dmaru->segment = drhd->segment;
        dmaru->include_all = drhd->flags & 0x1; /* BIT0: INCLUDE_ALL */
 
        ret = alloc_iommu(dmaru);
index 6262c19..fd7472f 100644 (file)
@@ -248,7 +248,8 @@ struct dmar_domain {
 struct device_domain_info {
        struct list_head link;  /* link to domain siblings */
        struct list_head global; /* link to global list */
-       u8 bus;                 /* PCI bus numer */
+       int segment;            /* PCI domain */
+       u8 bus;                 /* PCI bus number */
        u8 devfn;               /* PCI devfn number */
        struct pci_dev *dev; /* it's NULL for PCIE-to-PCI bridge */
        struct dmar_domain *domain; /* pointer to domain */
@@ -468,7 +469,7 @@ static void domain_update_iommu_cap(struct dmar_domain *domain)
        domain_update_iommu_snooping(domain);
 }
 
-static struct intel_iommu *device_to_iommu(u8 bus, u8 devfn)
+static struct intel_iommu *device_to_iommu(int segment, u8 bus, u8 devfn)
 {
        struct dmar_drhd_unit *drhd = NULL;
        int i;
@@ -476,6 +477,8 @@ static struct intel_iommu *device_to_iommu(u8 bus, u8 devfn)
        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] &&
@@ -1318,7 +1321,7 @@ static void domain_exit(struct dmar_domain *domain)
 }
 
 static int domain_context_mapping_one(struct dmar_domain *domain,
-               u8 bus, u8 devfn)
+                                     int segment, u8 bus, u8 devfn)
 {
        struct context_entry *context;
        unsigned long flags;
@@ -1333,7 +1336,7 @@ static int domain_context_mapping_one(struct dmar_domain *domain,
                bus, PCI_SLOT(devfn), PCI_FUNC(devfn));
        BUG_ON(!domain->pgd);
 
-       iommu = device_to_iommu(bus, devfn);
+       iommu = device_to_iommu(segment, bus, devfn);
        if (!iommu)
                return -ENODEV;
 
@@ -1423,8 +1426,8 @@ domain_context_mapping(struct dmar_domain *domain, struct pci_dev *pdev)
        int ret;
        struct pci_dev *tmp, *parent;
 
-       ret = domain_context_mapping_one(domain, pdev->bus->number,
-               pdev->devfn);
+       ret = domain_context_mapping_one(domain, pci_domain_nr(pdev->bus),
+                                        pdev->bus->number, pdev->devfn);
        if (ret)
                return ret;
 
@@ -1435,18 +1438,23 @@ domain_context_mapping(struct dmar_domain *domain, struct pci_dev *pdev)
        /* Secondary interface's bus number and devfn 0 */
        parent = pdev->bus->self;
        while (parent != tmp) {
-               ret = domain_context_mapping_one(domain, parent->bus->number,
-                       parent->devfn);
+               ret = domain_context_mapping_one(domain,
+                                                pci_domain_nr(parent->bus),
+                                                parent->bus->number,
+                                                parent->devfn);
                if (ret)
                        return ret;
                parent = parent->bus->self;
        }
        if (tmp->is_pcie) /* this is a PCIE-to-PCI bridge */
                return domain_context_mapping_one(domain,
-                       tmp->subordinate->number, 0);
+                                       pci_domain_nr(tmp->subordinate),
+                                       tmp->subordinate->number, 0);
        else /* this is a legacy PCI bridge */
                return domain_context_mapping_one(domain,
-                       tmp->bus->number, tmp->devfn);
+                                                 pci_domain_nr(tmp->bus),
+                                                 tmp->bus->number,
+                                                 tmp->devfn);
 }
 
 static int domain_context_mapped(struct pci_dev *pdev)
@@ -1455,12 +1463,12 @@ static int domain_context_mapped(struct pci_dev *pdev)
        struct pci_dev *tmp, *parent;
        struct intel_iommu *iommu;
 
-       iommu = device_to_iommu(pdev->bus->number, pdev->devfn);
+       iommu = device_to_iommu(pci_domain_nr(pdev->bus), pdev->bus->number,
+                               pdev->devfn);
        if (!iommu)
                return -ENODEV;
 
-       ret = device_context_mapped(iommu,
-               pdev->bus->number, pdev->devfn);
+       ret = device_context_mapped(iommu, pdev->bus->number, pdev->devfn);
        if (!ret)
                return ret;
        /* dependent device mapping */
@@ -1471,17 +1479,17 @@ static int domain_context_mapped(struct pci_dev *pdev)
        parent = pdev->bus->self;
        while (parent != tmp) {
                ret = device_context_mapped(iommu, parent->bus->number,
-                       parent->devfn);
+                                           parent->devfn);
                if (!ret)
                        return ret;
                parent = parent->bus->self;
        }
        if (tmp->is_pcie)
-               return device_context_mapped(iommu,
-                       tmp->subordinate->number, 0);
+               return device_context_mapped(iommu, tmp->subordinate->number,
+                                            0);
        else
-               return device_context_mapped(iommu,
-                       tmp->bus->number, tmp->devfn);
+               return device_context_mapped(iommu, tmp->bus->number,
+                                            tmp->devfn);
 }
 
 static int
@@ -1548,7 +1556,7 @@ static void domain_remove_dev_info(struct dmar_domain *domain)
                        info->dev->dev.archdata.iommu = NULL;
                spin_unlock_irqrestore(&device_domain_lock, flags);
 
-               iommu = device_to_iommu(info->bus, info->devfn);
+               iommu = device_to_iommu(info->segment, info->bus, info->devfn);
                iommu_detach_dev(iommu, info->bus, info->devfn);
                free_devinfo_mem(info);
 
@@ -1583,11 +1591,14 @@ static struct dmar_domain *get_domain_for_dev(struct pci_dev *pdev, int gaw)
        struct pci_dev *dev_tmp;
        unsigned long flags;
        int bus = 0, devfn = 0;
+       int segment;
 
        domain = find_domain(pdev);
        if (domain)
                return domain;
 
+       segment = pci_domain_nr(pdev->bus);
+
        dev_tmp = pci_find_upstream_pcie_bridge(pdev);
        if (dev_tmp) {
                if (dev_tmp->is_pcie) {
@@ -1599,7 +1610,8 @@ static struct dmar_domain *get_domain_for_dev(struct pci_dev *pdev, int gaw)
                }
                spin_lock_irqsave(&device_domain_lock, flags);
                list_for_each_entry(info, &device_domain_list, global) {
-                       if (info->bus == bus && info->devfn == devfn) {
+                       if (info->segment == segment &&
+                           info->bus == bus && info->devfn == devfn) {
                                found = info->domain;
                                break;
                        }
@@ -1637,6 +1649,7 @@ static struct dmar_domain *get_domain_for_dev(struct pci_dev *pdev, int gaw)
                        domain_exit(domain);
                        goto error;
                }
+               info->segment = segment;
                info->bus = bus;
                info->devfn = devfn;
                info->dev = NULL;
@@ -1648,7 +1661,8 @@ static struct dmar_domain *get_domain_for_dev(struct pci_dev *pdev, int gaw)
                found = NULL;
                spin_lock_irqsave(&device_domain_lock, flags);
                list_for_each_entry(tmp, &device_domain_list, global) {
-                       if (tmp->bus == bus && tmp->devfn == devfn) {
+                       if (tmp->segment == segment &&
+                           tmp->bus == bus && tmp->devfn == devfn) {
                                found = tmp->domain;
                                break;
                        }
@@ -1668,6 +1682,7 @@ found_domain:
        info = alloc_devinfo_mem();
        if (!info)
                goto error;
+       info->segment = segment;
        info->bus = pdev->bus->number;
        info->devfn = pdev->devfn;
        info->dev = pdev;
@@ -2808,6 +2823,7 @@ static int vm_domain_add_dev_info(struct dmar_domain *domain,
        if (!info)
                return -ENOMEM;
 
+       info->segment = pci_domain_nr(pdev->bus);
        info->bus = pdev->bus->number;
        info->devfn = pdev->devfn;
        info->dev = pdev;
@@ -2837,15 +2853,15 @@ static void iommu_detach_dependent_devices(struct intel_iommu *iommu,
                parent = pdev->bus->self;
                while (parent != tmp) {
                        iommu_detach_dev(iommu, parent->bus->number,
-                               parent->devfn);
+                                        parent->devfn);
                        parent = parent->bus->self;
                }
                if (tmp->is_pcie) /* this is a PCIE-to-PCI bridge */
                        iommu_detach_dev(iommu,
                                tmp->subordinate->number, 0);
                else /* this is a legacy PCI bridge */
-                       iommu_detach_dev(iommu,
-                               tmp->bus->number, tmp->devfn);
+                       iommu_detach_dev(iommu, tmp->bus->number,
+                                        tmp->devfn);
        }
 }
 
@@ -2858,13 +2874,15 @@ static void vm_domain_remove_one_dev_info(struct dmar_domain *domain,
        int found = 0;
        struct list_head *entry, *tmp;
 
-       iommu = device_to_iommu(pdev->bus->number, pdev->devfn);
+       iommu = device_to_iommu(pci_domain_nr(pdev->bus), pdev->bus->number,
+                               pdev->devfn);
        if (!iommu)
                return;
 
        spin_lock_irqsave(&device_domain_lock, flags);
        list_for_each_safe(entry, tmp, &domain->devices) {
                info = list_entry(entry, struct device_domain_info, link);
+               /* No need to compare PCI domain; it has to be the same */
                if (info->bus == pdev->bus->number &&
                    info->devfn == pdev->devfn) {
                        list_del(&info->link);
@@ -2889,7 +2907,8 @@ static void vm_domain_remove_one_dev_info(struct dmar_domain *domain,
                 * owned by this domain, clear this iommu in iommu_bmp
                 * update iommu count and coherency
                 */
-               if (device_to_iommu(info->bus, info->devfn) == iommu)
+               if (iommu == device_to_iommu(info->segment, info->bus,
+                                           info->devfn))
                        found = 1;
        }
 
@@ -2922,7 +2941,7 @@ static void vm_domain_remove_all_dev_info(struct dmar_domain *domain)
 
                spin_unlock_irqrestore(&device_domain_lock, flags1);
 
-               iommu = device_to_iommu(info->bus, info->devfn);
+               iommu = device_to_iommu(info->segment, info->bus, info->devfn);
                iommu_detach_dev(iommu, info->bus, info->devfn);
                iommu_detach_dependent_devices(iommu, info->dev);
 
@@ -3110,7 +3129,8 @@ static int intel_iommu_attach_device(struct iommu_domain *domain,
                }
        }
 
-       iommu = device_to_iommu(pdev->bus->number, pdev->devfn);
+       iommu = device_to_iommu(pci_domain_nr(pdev->bus), pdev->bus->number,
+                               pdev->devfn);
        if (!iommu)
                return -ENODEV;
 
index 4a0ce6f..e397dc3 100644 (file)
@@ -34,6 +34,7 @@ struct dmar_drhd_unit {
        u64     reg_base_addr;          /* register base address*/
        struct  pci_dev **devices;      /* target device array  */
        int     devices_cnt;            /* target device count  */
+       u16     segment;                /* PCI domain           */
        u8      ignored:1;              /* ignore drhd          */
        u8      include_all:1;
        struct intel_iommu *iommu;