The IOVA allocator tracks address ranges by page frame number (PFN).
The highest node in its rbtree is a fixed anchor at PFN ~0UL, which is
embedded in the DMA cookie. dma-iommu converts reserved regions to PFNs
with iova_pfn(), which returns unsigned long. On x86-32 with PAE,
addresses are 64-bit but unsigned long is 32-bit, so the upper bits of
the PFN are lost.
A reserved region that ends at the top of the address space then gets
the anchor's PFN. reserve_iova() merges it into the anchor and lowers
the anchor's pfn_lo. At teardown, free_iova_mem() no longer recognises
the anchor and frees it to iova_cache, which corrupts the slab. PCI host
bridge windows above 16TiB and the reservation after the last PCI DMA
window hit the same problem.
Compute the PFNs in u64. Skip ranges that start above the PFN space and
clamp the end one PFN below the anchor. The allocator never hands out
those IOVAs, so nothing is lost. On 64-bit, PFNs never reach ~0UL, so
nothing changes there.

Fixes: fade1ec055dc ("iommu/dma: Avoid PCI host bridge windows")
Fixes: 7c1b058c8b5a ("iommu/dma: Handle IOMMU API reserved regions")
Fixes: aadad097cd46 ("iommu/dma: Reserve IOVA for PCIe inaccessible DMA 
address")
Reported-by: Sashiko <[email protected]>
Closes: https://lore.kernel.org/all/[email protected]/
Cc: [email protected]
Co-developed-by: Vishnu Reddy <[email protected]>
Signed-off-by: Vishnu Reddy <[email protected]>
Signed-off-by: Vikash Garodia <[email protected]>
---
 drivers/iommu/dma-iommu.c | 32 ++++++++++++++++++++------------
 1 file changed, 20 insertions(+), 12 deletions(-)

diff --git a/drivers/iommu/dma-iommu.c b/drivers/iommu/dma-iommu.c
index 
78742aeba932de1054fbf394c4fae86e9b1be20c..fbf788ac055b6d1e87482c3de043ef57bb12d9e6
 100644
--- a/drivers/iommu/dma-iommu.c
+++ b/drivers/iommu/dma-iommu.c
@@ -505,21 +505,34 @@ static int iommu_dma_ranges_sort(void *priv, const struct 
list_head *a,
        return res_a->res->start > res_b->res->start;
 }
 
+/*
+ * IOVA PFNs are unsigned long, and ~0UL is the rbtree anchor. On 32-bit
+ * with a 64-bit dma_addr_t, iova_pfn() would truncate the range. Compute
+ * the PFNs in u64 instead, skip a range that starts beyond the PFN space
+ * and clamp its end below the anchor. Such IOVAs cannot be allocated.
+ */
+static void iova_reserve_range(struct iova_domain *iovad, u64 start, u64 end)
+{
+       u64 lo = start >> iova_shift(iovad);
+       u64 hi = end >> iova_shift(iovad);
+
+       if (lo < ULONG_MAX)
+               reserve_iova(iovad, lo, min(hi, ULONG_MAX - 1));
+}
+
 static int iova_reserve_pci_windows(struct pci_dev *dev,
                struct iova_domain *iovad)
 {
        struct pci_host_bridge *bridge = pci_find_host_bridge(dev->bus);
        struct resource_entry *window;
-       unsigned long lo, hi;
        phys_addr_t start = 0, end;
 
        resource_list_for_each_entry(window, &bridge->windows) {
                if (resource_type(window->res) != IORESOURCE_MEM)
                        continue;
 
-               lo = iova_pfn(iovad, window->res->start - window->offset);
-               hi = iova_pfn(iovad, window->res->end - window->offset);
-               reserve_iova(iovad, lo, hi);
+               iova_reserve_range(iovad, window->res->start - window->offset,
+                                  window->res->end - window->offset);
        }
 
        /* Get reserved DMA windows from host bridge */
@@ -528,9 +541,7 @@ static int iova_reserve_pci_windows(struct pci_dev *dev,
                end = window->res->start - window->offset;
 resv_iova:
                if (end > start) {
-                       lo = iova_pfn(iovad, start);
-                       hi = iova_pfn(iovad, end);
-                       reserve_iova(iovad, lo, hi);
+                       iova_reserve_range(iovad, start, end);
                } else if (end < start) {
                        /* DMA ranges should be non-overlapping */
                        dev_err(&dev->dev,
@@ -568,15 +579,12 @@ static int iova_reserve_iommu_regions(struct device *dev,
 
        iommu_get_resv_regions(dev, &resv_regions);
        list_for_each_entry(region, &resv_regions, list) {
-               unsigned long lo, hi;
-
                /* We ARE the software that manages these! */
                if (region->type == IOMMU_RESV_SW_MSI)
                        continue;
 
-               lo = iova_pfn(iovad, region->start);
-               hi = iova_pfn(iovad, region->start + region->length - 1);
-               reserve_iova(iovad, lo, hi);
+               iova_reserve_range(iovad, region->start,
+                                  region->start + region->length - 1);
 
                if (region->type == IOMMU_RESV_MSI)
                        ret = cookie_init_hw_msi_region(cookie, region->start,

-- 
2.34.1


Reply via email to