Skip to contentSkip to conversation

dma-mapping: Clean up arch_setup_dma_ops()

PATCH Superseded Robin Murphy 7 participants 32 messages Nov 29, 2023 – Dec 11, 2023

Subsystems (17)
Arm (32-bit) ACPI MIPS RISC-V +13 more subsystems

v1 of 4 Superseded Latest is v4 →

lore
Share & tools
lore

Robin Murphy

author maintainer
#0 lore raw

cover dma-mapping: Clean up arch_setup_dma_ops()

Hi all,

Prompted by Jason's proposal[1], here's a first step towards truly unpicking the dma_configure vs. IOMMU mess. As I commented before, we have an awful lot of accumulated cruft and technical debt here making things more complicated than they need to be, and we already have hacks on top of hacks trying to work around it, so polishing those hacks even further is really not a desirable direction of travel. And I do know they're hacks, because I wrote most of them and still remember enough of the context of the time ;)

I'm taking a methodical bottom-up approach here, so step 1 is cleaning *all* the out-of-date stuff from arch_setup_dma_ops() and simplifying that interface, which gets it right out of the way for the next step of pulling apart {of,acpi}_dma_configure(). This part is really a dma-mapping series, but I'm not sure yet if would need to target the IOMMU tree - nothing here should strictly depend on the pending IOMMU change, but the follow-up patches might. Still working on those, so hopefully I'll know soon...

Thanks,
Robin.

[1] https://lore.kernel.org/linux-iommu/[email protected]/

Robin Murphy (7):
OF: Retire dma-ranges mask workaround
OF: Simplify DMA range calculations ACPI/IORT: Handle memory address size limits as limits
dma-mapping: Add helpers for dma_range_map bounds iommu/dma: Make limit checks self-contained iommu/dma: Centralise iommu_setup_dma_ops()
dma-mapping: Simplify arch_setup_dma_ops()

 arch/arc/mm/dma.c               |  3 +--
 arch/arm/mm/dma-mapping-nommu.c |  3 +--
 arch/arm/mm/dma-mapping.c       | 12 ++++++----
 arch/arm64/mm/dma-mapping.c     |  5 +---
 arch/loongarch/kernel/dma.c     |  9 ++-----
 arch/mips/mm/dma-noncoherent.c  |  3 +--
 arch/riscv/mm/dma-noncoherent.c |  3 +--
 drivers/acpi/arm64/dma.c        | 17 ++++---------
 drivers/acpi/arm64/iort.c       | 18 +++++++-------
 drivers/acpi/scan.c             |  3 +--
 drivers/hv/hv_common.c          |  6 +----
 drivers/iommu/amd/iommu.c       |  8 -------
 drivers/iommu/dma-iommu.c       | 35 ++++++++++-----------------
 drivers/iommu/dma-iommu.h       |  6 +++++
 drivers/iommu/intel/iommu.c     |  7 ------
 drivers/iommu/iommu.c           |  2 ++
 drivers/iommu/s390-iommu.c      |  6 -----
 drivers/iommu/virtio-iommu.c    | 10 --------
 drivers/of/device.c             | 42 ++++++---------------------------
 include/linux/acpi_iort.h       |  4 ++--
 include/linux/dma-direct.h      | 18 ++++++++++++++
 include/linux/dma-map-ops.h     |  6 ++---
 include/linux/iommu.h           |  7 ------
 23 files changed, 78 insertions(+), 155 deletions(-)
Signature · 1 line
2.39.2.101.g768bb238c484.dirty
author maintainer
#1 lore raw

PATCH 1/7 OF: Retire dma-ranges mask workaround

From what I remember, the fixup adding 1 to the dma-ranges size was for the benefit of some early AMD Seattle DTs. Those are likely extinct by now, and anyone else who might have deserved to get the message has hopefully seen the warning in the 9 years we've had it there. The modern dma_range_map mechanism should happily handle odd-sized ranges with no ill effect, so there's little need to care anyway now. Clean it up.

---
 drivers/of/device.c | 16 ----------------
 1 file changed, 16 deletions(-)
drivers/of/device.c +0 -16
index 1ca42ad9dd15..526a42cdf66e 100644
@@ -129,22 +129,6 @@ int of_dma_configure_id(struct device *dev, struct device_node *np,
 				dma_end = r->dma_start + r->size;
 		}
 		size = dma_end - dma_start;
-
-		/*
-		 * Add a work around to treat the size as mask + 1 in case
-		 * it is defined in DT as a mask.
-		 */
-		if (size & 1) {
-			dev_warn(dev, "Invalid size 0x%llx for dma-range(s)\n",
-				 size);
-			size = size + 1;
-		}
-
-		if (!size) {
-			dev_err(dev, "Adjusted size 0x%llx invalid\n", size);
-			kfree(map);
-			return -EINVAL;
-		}
 	}
 
 	/*
Signature · 1 line
2.39.2.101.g768bb238c484.dirty
Acked-by on v1 1/7
#2 lore raw

Re: [PATCH 1/7] OF: Retire dma-ranges mask workaround

Quoted 7 lines · Robin Murphy

From what I remember, the fixup adding 1 to the dma-ranges size was for the benefit of some early AMD Seattle DTs. Those are likely extinct by now, and anyone else who might have deserved to get the message has hopefully seen the warning in the 9 years we've had it there. The modern dma_range_map mechanism should happily handle odd-sized ranges with no ill effect, so there's little need to care anyway now. Clean it up.

The commit has a tested by for Seattle, but the series adding this was for an issue on TI Keystone[1]. Looks like the patch adding this fixup and warning did 2 things. It added 1 to the default mask when 'dma-ranges' was not present (which keystone needed) and added 1 if the DT value was a mask along with the warning. It's not clear what Seattle needed, but there was a fix to dma-ranges about a year later[2].

I thought at some point we allowed 32-bit DTs to specify a ~0 size to avoid having to use 2 cells to express 4G size which wouldn't have been a warning, but I can't find any discussion on that. It would have been earlier than 2015 I think... Anyways, there is no upstream dts with that either, so I think we're good.

Quoted 4 lines
---
 drivers/of/device.c | 16 ----------------
 1 file changed, 16 deletions(-)

[1] https://lore.kernel.org/all/[email protected]/
[2] https://lore.kernel.org/all/[email protected]/

author maintainer
#3 lore raw

PATCH 2/7 OF: Simplify DMA range calculations

In reply to Robin Murphy

Juggling start, end, and size values for a range is somewhat redundant and a little hard to follow. Consolidate down to just using inclusive start and end, which saves us worrying about size overflows for full 64-bit ranges (note that passing a potentially-overflowed value through to arch_setup_dma_ops() is benign for all current implementations, and this is working towards removing that anyway).

---
 drivers/of/device.c | 19 ++++++++-----------
 1 file changed, 8 insertions(+), 11 deletions(-)
drivers/of/device.c +8 -11
index 526a42cdf66e..51062a831970 100644
@@ -97,7 +97,7 @@ int of_dma_configure_id(struct device *dev, struct device_node *np,
 	const struct bus_dma_region *map = NULL;
 	struct device_node *bus_np;
 	u64 dma_start = 0;
-	u64 mask, end, size = 0;
+	u64 mask, end = 0;
 	bool coherent;
 	int ret;
 
@@ -118,17 +118,15 @@ int of_dma_configure_id(struct device *dev, struct device_node *np,
 			return ret == -ENODEV ? 0 : ret;
 	} else {
 		const struct bus_dma_region *r = map;
-		u64 dma_end = 0;
 
 		/* Determine the overall bounds of all DMA regions */
 		for (dma_start = ~0; r->size; r++) {
 			/* Take lower and upper limits */
 			if (r->dma_start < dma_start)
 				dma_start = r->dma_start;
-			if (r->dma_start + r->size > dma_end)
-				dma_end = r->dma_start + r->size;
+			if (r->dma_start + r->size > end)
+				end = r->dma_start + r->size;
 		}
-		size = dma_end - dma_start;
 	}
 
 	/*
@@ -142,16 +140,15 @@ int of_dma_configure_id(struct device *dev, struct device_node *np,
 		dev->dma_mask = &dev->coherent_dma_mask;
 	}
 
-	if (!size && dev->coherent_dma_mask)
-		size = max(dev->coherent_dma_mask, dev->coherent_dma_mask + 1);
-	else if (!size)
-		size = 1ULL << 32;
+	if (!end && dev->coherent_dma_mask)
+		end = dev->coherent_dma_mask;
+	else if (!end)
+		end = (1ULL << 32) - 1;
 
 	/*
 	 * Limit coherent and dma mask based on size and default mask
 	 * set by the driver.
 	 */
-	end = dma_start + size - 1;
 	mask = DMA_BIT_MASK(ilog2(end) + 1);
 	dev->coherent_dma_mask &= mask;
 	*dev->dma_mask &= mask;
@@ -177,7 +174,7 @@ int of_dma_configure_id(struct device *dev, struct device_node *np,
 	dev_dbg(dev, "device is%sbehind an iommu\n",
 		iommu ? " " : " not ");
 
-	arch_setup_dma_ops(dev, dma_start, size, iommu, coherent);
+	arch_setup_dma_ops(dev, dma_start, end - dma_start + 1, iommu, coherent);
 
 	if (!iommu)
 		of_dma_set_restricted_buffer(dev, np);
Signature · 1 line
2.39.2.101.g768bb238c484.dirty
Reviewed-by on v1 2/7
#4 lore raw

Re: [PATCH 2/7] OF: Simplify DMA range calculations

Quoted 6 lines · Robin Murphy

Juggling start, end, and size values for a range is somewhat redundant and a little hard to follow. Consolidate down to just using inclusive start and end, which saves us worrying about size overflows for full 64-bit ranges (note that passing a potentially-overflowed value through to arch_setup_dma_ops() is benign for all current implementations, and this is working towards removing that anyway).

In iommu code I've been trying to use consistent language with other parts of the kernel like interval tree and maple tree:

* In this file the term 'last' indicates an inclusive and closed interval, eg
* [0,0] refers to a single PFN. 'end' means an open range, eg [0,0) refers to
* no PFNs.

Here I think you've swapped end to mean last?

Regardless the change looks correct

Jason

Acked-by on v1 2/7
#5 lore raw

Re: [PATCH 2/7] OF: Simplify DMA range calculations

In reply to Robin Murphy
Quoted 8 lines · Robin Murphy

Juggling start, end, and size values for a range is somewhat redundant and a little hard to follow. Consolidate down to just using inclusive start and end, which saves us worrying about size overflows for full 64-bit ranges (note that passing a potentially-overflowed value through to arch_setup_dma_ops() is benign for all current implementations, and this is working towards removing that anyway).

Quoted 4 lines
---
 drivers/of/device.c | 19 ++++++++-----------
 1 file changed, 8 insertions(+), 11 deletions(-)
author maintainer
#6 lore raw

PATCH 3/7 ACPI/IORT: Handle memory address size limits as limits

In reply to Robin Murphy

Return the Root Complex/Named Component memory address size limit as an inclusive limit value, rather than an exclusive size. This saves us having to special-case 64-bit overflow, and simplifies our caller too.

---
 drivers/acpi/arm64/dma.c  |  9 +++------
 drivers/acpi/arm64/iort.c | 18 ++++++++----------
 include/linux/acpi_iort.h |  4 ++--
 3 files changed, 13 insertions(+), 18 deletions(-)
drivers/acpi/arm64/dma.c +3 -6
index 93d796531af3..b98a149f8d50 100644
@@ -8,7 +8,6 @@ void acpi_arch_dma_setup(struct device *dev)
 {
 	int ret;
 	u64 end, mask;
-	u64 size = 0;
 	const struct bus_dma_region *map = NULL;
 
 	/*
@@ -23,9 +22,9 @@ void acpi_arch_dma_setup(struct device *dev)
 	}
 
 	if (dev->coherent_dma_mask)
-		size = max(dev->coherent_dma_mask, dev->coherent_dma_mask + 1);
+		end = dev->coherent_dma_mask;
 	else
-		size = 1ULL << 32;
+		end = (1ULL << 32) - 1;
 
 	ret = acpi_dma_get_range(dev, &map);
 	if (!ret && map) {
@@ -36,18 +35,16 @@ void acpi_arch_dma_setup(struct device *dev)
 				end = r->dma_start + r->size - 1;
 		}
 
-		size = end + 1;
 		dev->dma_range_map = map;
 	}
 
 	if (ret == -ENODEV)
-		ret = iort_dma_get_ranges(dev, &size);
+		ret = iort_dma_get_ranges(dev, &end);
 	if (!ret) {
 		/*
 		 * Limit coherent and dma mask based on size retrieved from
 		 * firmware.
 		 */
-		end = size - 1;
 		mask = DMA_BIT_MASK(ilog2(end) + 1);
 		dev->bus_dma_limit = end;
 		dev->coherent_dma_mask = min(dev->coherent_dma_mask, mask);
drivers/acpi/arm64/iort.c +8 -10
index 6496ff5a6ba2..eb64d8e17dd1 100644
@@ -1367,7 +1367,7 @@ int iort_iommu_configure_id(struct device *dev, const u32 *input_id)
 { return -ENODEV; }
 #endif
 
-static int nc_dma_get_range(struct device *dev, u64 *size)
+static int nc_dma_get_range(struct device *dev, u64 *limit)
 {
 	struct acpi_iort_node *node;
 	struct acpi_iort_named_component *ncomp;
@@ -1384,13 +1384,12 @@ static int nc_dma_get_range(struct device *dev, u64 *size)
 		return -EINVAL;
 	}
 
-	*size = ncomp->memory_address_limit >= 64 ? U64_MAX :
-			1ULL<<ncomp->memory_address_limit;
+	*limit = (1ULL << ncomp->memory_address_limit) - 1;
 
 	return 0;
 }
 
-static int rc_dma_get_range(struct device *dev, u64 *size)
+static int rc_dma_get_range(struct device *dev, u64 *limit)
 {
 	struct acpi_iort_node *node;
 	struct acpi_iort_root_complex *rc;
@@ -1408,8 +1407,7 @@ static int rc_dma_get_range(struct device *dev, u64 *size)
 		return -EINVAL;
 	}
 
-	*size = rc->memory_address_limit >= 64 ? U64_MAX :
-			1ULL<<rc->memory_address_limit;
+	*limit = (1ULL << rc->memory_address_limit) - 1;
 
 	return 0;
 }
@@ -1417,16 +1415,16 @@ static int rc_dma_get_range(struct device *dev, u64 *size)
 /**
  * iort_dma_get_ranges() - Look up DMA addressing limit for the device
  * @dev: device to lookup
- * @size: DMA range size result pointer
+ * @limit: DMA limit result pointer
  *
  * Return: 0 on success, an error otherwise.
  */
-int iort_dma_get_ranges(struct device *dev, u64 *size)
+int iort_dma_get_ranges(struct device *dev, u64 *limit)
 {
 	if (dev_is_pci(dev))
-		return rc_dma_get_range(dev, size);
+		return rc_dma_get_range(dev, limit);
 	else
-		return nc_dma_get_range(dev, size);
+		return nc_dma_get_range(dev, limit);
 }
 
 static void __init acpi_iort_register_irq(int hwirq, const char *name,
include/linux/acpi_iort.h +2 -2
index 1cb65592c95d..d4ed5622cf2b 100644
@@ -39,7 +39,7 @@ void iort_get_rmr_sids(struct fwnode_handle *iommu_fwnode,
 void iort_put_rmr_sids(struct fwnode_handle *iommu_fwnode,
 		       struct list_head *head);
 /* IOMMU interface */
-int iort_dma_get_ranges(struct device *dev, u64 *size);
+int iort_dma_get_ranges(struct device *dev, u64 *limit);
 int iort_iommu_configure_id(struct device *dev, const u32 *id_in);
 void iort_iommu_get_resv_regions(struct device *dev, struct list_head *head);
 phys_addr_t acpi_iort_dma_get_max_cpu_address(void);
@@ -55,7 +55,7 @@ void iort_get_rmr_sids(struct fwnode_handle *iommu_fwnode, struct list_head *hea
 static inline
 void iort_put_rmr_sids(struct fwnode_handle *iommu_fwnode, struct list_head *head) { }
 /* IOMMU interface */
-static inline int iort_dma_get_ranges(struct device *dev, u64 *size)
+static inline int iort_dma_get_ranges(struct device *dev, u64 *limit)
 { return -ENODEV; }
 static inline int iort_iommu_configure_id(struct device *dev, const u32 *id_in)
 { return -ENODEV; }
Signature · 1 line
2.39.2.101.g768bb238c484.dirty
Reviewed-by on v1 3/7
#7 lore raw

Re: [PATCH 3/7] ACPI/IORT: Handle memory address size limits as limits

Quoted 10 lines · Robin Murphy

Return the Root Complex/Named Component memory address size limit as an inclusive limit value, rather than an exclusive size. This saves us having to special-case 64-bit overflow, and simplifies our caller too.

---
 drivers/acpi/arm64/dma.c  |  9 +++------
 drivers/acpi/arm64/iort.c | 18 ++++++++----------
 include/linux/acpi_iort.h |  4 ++--
 3 files changed, 13 insertions(+), 18 deletions(-)

Jason

maintainer
#8 lore raw

Re: [PATCH 3/7] ACPI/IORT: Handle memory address size limits as limits

In reply to Robin Murphy
Quoted 10 lines · Robin Murphy

Return the Root Complex/Named Component memory address size limit as an inclusive limit value, rather than an exclusive size. This saves us having to special-case 64-bit overflow, and simplifies our caller too.

---
 drivers/acpi/arm64/dma.c  |  9 +++------
 drivers/acpi/arm64/iort.c | 18 ++++++++----------
 include/linux/acpi_iort.h |  4 ++--
 3 files changed, 13 insertions(+), 18 deletions(-)

[...]

Quoted 14 lines
drivers/acpi/arm64/iort.c +1 -1
index 6496ff5a6ba2..eb64d8e17dd1 100644
@@ -1367,7 +1367,7 @@ int iort_iommu_configure_id(struct device *dev, const u32 *input_id)
 { return -ENODEV; }
 #endif
 
-static int nc_dma_get_range(struct device *dev, u64 *size)
+static int nc_dma_get_range(struct device *dev, u64 *limit)
 {
 	struct acpi_iort_node *node;
 	struct acpi_iort_named_component *ncomp;

drivers/acpi/arm64/iort.c static int nc_dma_get_range(struct device *dev, u64 *size) · line 1387

Quoted 6 lines
drivers/acpi/arm64/iort.c +1 -2
@@ -1384,13 +1384,12 @@ static int nc_dma_get_range(struct device *dev, u64 *size)
 		return -EINVAL;
 	}
 
-	*size = ncomp->memory_address_limit >= 64 ? U64_MAX :
-			1ULL<<ncomp->memory_address_limit;
+	*limit = (1ULL << ncomp->memory_address_limit) - 1;

The old code handled 'ncomp->memory_address_limit >= 64' -- why is it safe to drop that? You mention it in the cover letter, so clearly I'm missing something!

Quoted 10 lines
drivers/acpi/arm64/iort.c +1 -1
 	return 0;
 }
 
-static int rc_dma_get_range(struct device *dev, u64 *size)
+static int rc_dma_get_range(struct device *dev, u64 *limit)
 {
 	struct acpi_iort_node *node;
 	struct acpi_iort_root_complex *rc;

drivers/acpi/arm64/iort.c static int rc_dma_get_range(struct device *dev, u64 *size) · line 1410

Quoted 6 lines
drivers/acpi/arm64/iort.c +1 -2
@@ -1408,8 +1407,7 @@ static int rc_dma_get_range(struct device *dev, u64 *size)
 		return -EINVAL;
 	}
 
-	*size = rc->memory_address_limit >= 64 ? U64_MAX :
-			1ULL<<rc->memory_address_limit;
+	*limit = (1ULL << rc->memory_address_limit) - 1;

Same thing here.

Will

author maintainer
#9 lore raw

Re: [PATCH 3/7] ACPI/IORT: Handle memory address size limits as limits

Quoted 31 lines · Will Deacon
Quoted 10 lines · Robin Murphy

Return the Root Complex/Named Component memory address size limit as an inclusive limit value, rather than an exclusive size. This saves us having to special-case 64-bit overflow, and simplifies our caller too.

---
  drivers/acpi/arm64/dma.c  |  9 +++------
  drivers/acpi/arm64/iort.c | 18 ++++++++----------
  include/linux/acpi_iort.h |  4 ++--
  3 files changed, 13 insertions(+), 18 deletions(-)

[...]

Quoted 17 lines
drivers/acpi/arm64/iort.c +1 -1
index 6496ff5a6ba2..eb64d8e17dd1 100644
@@ -1367,7 +1367,7 @@ int iort_iommu_configure_id(struct device *dev, const u32 *input_id)
  { return -ENODEV; }
  #endif
 
-static int nc_dma_get_range(struct device *dev, u64 *size)
+static int nc_dma_get_range(struct device *dev, u64 *limit)
  {
  	struct acpi_iort_node *node;
  	struct acpi_iort_named_component *ncomp;
@@ -1384,13 +1384,12 @@ static int nc_dma_get_range(struct device *dev, u64 *size)
  		return -EINVAL;
  	}
 
Quoted 7 lines

drivers/acpi/arm64/iort.c static int nc_dma_get_range(struct device *dev, u64 *size) · line 1387

Quoted 3 lines
drivers/acpi/arm64/iort.c +1 -2
-	*size = ncomp->memory_address_limit >= 64 ? U64_MAX :
-			1ULL<<ncomp->memory_address_limit;
+	*limit = (1ULL << ncomp->memory_address_limit) - 1;

The old code handled 'ncomp->memory_address_limit >= 64' -- why is it safe to drop that? You mention it in the cover letter, so clearly I'm missing something!

Because an unsigned shift by 64 or more generates 0 (modulo 2^64), thus subtracting 1 results in the correct all-bits-set value for an inclusive 64-bit limit.

Thanks,
Robin.

Quoted 20 lines
Quoted 10 lines
drivers/acpi/arm64/iort.c +1 -1
  	return 0;
  }
 
-static int rc_dma_get_range(struct device *dev, u64 *size)
+static int rc_dma_get_range(struct device *dev, u64 *limit)
  {
  	struct acpi_iort_node *node;
  	struct acpi_iort_root_complex *rc;

drivers/acpi/arm64/iort.c static int rc_dma_get_range(struct device *dev, u64 *size) · line 1410

Quoted 6 lines
drivers/acpi/arm64/iort.c +1 -2
@@ -1408,8 +1407,7 @@ static int rc_dma_get_range(struct device *dev, u64 *size)
  		return -EINVAL;
  	}
 
-	*size = rc->memory_address_limit >= 64 ? U64_MAX :
-			1ULL<<rc->memory_address_limit;
+	*limit = (1ULL << rc->memory_address_limit) - 1;

Same thing here.

Will

maintainer
#10 lore raw

Re: [PATCH 3/7] ACPI/IORT: Handle memory address size limits as limits

Quoted 38 lines · Robin Murphy
Quoted 28 lines · Will Deacon
Quoted 10 lines · Robin Murphy

Return the Root Complex/Named Component memory address size limit as an inclusive limit value, rather than an exclusive size. This saves us having to special-case 64-bit overflow, and simplifies our caller too.

---
  drivers/acpi/arm64/dma.c  |  9 +++------
  drivers/acpi/arm64/iort.c | 18 ++++++++----------
  include/linux/acpi_iort.h |  4 ++--
  3 files changed, 13 insertions(+), 18 deletions(-)

[...]

Quoted 14 lines
drivers/acpi/arm64/iort.c +1 -1
index 6496ff5a6ba2..eb64d8e17dd1 100644
@@ -1367,7 +1367,7 @@ int iort_iommu_configure_id(struct device *dev, const u32 *input_id)
  { return -ENODEV; }
  #endif
-static int nc_dma_get_range(struct device *dev, u64 *size)
+static int nc_dma_get_range(struct device *dev, u64 *limit)
  {
  	struct acpi_iort_node *node;
  	struct acpi_iort_named_component *ncomp;
@@ -1384,13 +1384,12 @@ static int nc_dma_get_range(struct device *dev, u64 *size)
  		return -EINVAL;
Quoted 8 lines

drivers/acpi/arm64/iort.c static int nc_dma_get_range(struct device *dev, u64 *size) · line 1387

Quoted 4 lines
drivers/acpi/arm64/iort.c +1 -2
  	}
-	*size = ncomp->memory_address_limit >= 64 ? U64_MAX :
-			1ULL<<ncomp->memory_address_limit;
+	*limit = (1ULL << ncomp->memory_address_limit) - 1;

The old code handled 'ncomp->memory_address_limit >= 64' -- why is it safe to drop that? You mention it in the cover letter, so clearly I'm missing something!

Quoted 3 lines

Because an unsigned shift by 64 or more generates 0 (modulo 2^64), thus subtracting 1 results in the correct all-bits-set value for an inclusive 64-bit limit.

Oh, I'd have thought you'd have gotten one of those "left shift count >= width of type" warnings if you did that.

Will

#11 lore raw

Re: [PATCH 3/7] ACPI/IORT: Handle memory address size limits as limits

Quoted 39 lines · Will Deacon
Quoted 38 lines · Robin Murphy
Quoted 36 lines · Will Deacon
Quoted 10 lines · Robin Murphy

Return the Root Complex/Named Component memory address size limit as an inclusive limit value, rather than an exclusive size. This saves us having to special-case 64-bit overflow, and simplifies our caller too.

---
  drivers/acpi/arm64/dma.c  |  9 +++------
  drivers/acpi/arm64/iort.c | 18 ++++++++----------
  include/linux/acpi_iort.h |  4 ++--
  3 files changed, 13 insertions(+), 18 deletions(-)

[...]

Quoted 13 lines
drivers/acpi/arm64/iort.c +1 -1
index 6496ff5a6ba2..eb64d8e17dd1 100644
@@ -1367,7 +1367,7 @@ int iort_iommu_configure_id(struct device *dev, const u32 *input_id)
  { return -ENODEV; }
  #endif
-static int nc_dma_get_range(struct device *dev, u64 *size)
+static int nc_dma_get_range(struct device *dev, u64 *limit)
  {
  	struct acpi_iort_node *node;
  	struct acpi_iort_named_component *ncomp;

drivers/acpi/arm64/iort.c static int nc_dma_get_range(struct device *dev, u64 *size) · line 1386

Quoted 5 lines
drivers/acpi/arm64/iort.c +1 -2
@@ -1384,13 +1384,12 @@ static int nc_dma_get_range(struct device *dev, u64 *size)
  		return -EINVAL;
  	}
-	*size = ncomp->memory_address_limit >= 64 ? U64_MAX :
-			1ULL<<ncomp->memory_address_limit;
+	*limit = (1ULL << ncomp->memory_address_limit) - 1;

The old code handled 'ncomp->memory_address_limit >= 64' -- why is it safe to drop that? You mention it in the cover letter, so clearly I'm missing something!

Quoted 6 lines
Quoted 3 lines

Because an unsigned shift by 64 or more generates 0 (modulo 2^64), thus subtracting 1 results in the correct all-bits-set value for an inclusive 64-bit limit.

Oh, I'd have thought you'd have gotten one of those "left shift count >= width of type" warnings if you did that.

Yes, UBSAN generates warnings for these cases. I'm not sure if it is actually undefined C behavior or just "suspicious", but such is what it is..

Jason

author maintainer
#12 lore raw

Re: [PATCH 3/7] ACPI/IORT: Handle memory address size limits as limits

In reply to Will Deacon
Quoted 39 lines · Will Deacon
Quoted 38 lines · Robin Murphy
Quoted 36 lines · Will Deacon
Quoted 10 lines · Robin Murphy

Return the Root Complex/Named Component memory address size limit as an inclusive limit value, rather than an exclusive size. This saves us having to special-case 64-bit overflow, and simplifies our caller too.

---
   drivers/acpi/arm64/dma.c  |  9 +++------
   drivers/acpi/arm64/iort.c | 18 ++++++++----------
   include/linux/acpi_iort.h |  4 ++--
   3 files changed, 13 insertions(+), 18 deletions(-)

[...]

Quoted 13 lines
drivers/acpi/arm64/iort.c +1 -1
index 6496ff5a6ba2..eb64d8e17dd1 100644
@@ -1367,7 +1367,7 @@ int iort_iommu_configure_id(struct device *dev, const u32 *input_id)
   { return -ENODEV; }
   #endif
-static int nc_dma_get_range(struct device *dev, u64 *size)
+static int nc_dma_get_range(struct device *dev, u64 *limit)
   {
   	struct acpi_iort_node *node;
   	struct acpi_iort_named_component *ncomp;

drivers/acpi/arm64/iort.c static int nc_dma_get_range(struct device *dev, u64 *size) · line 1386

Quoted 5 lines
drivers/acpi/arm64/iort.c +1 -2
@@ -1384,13 +1384,12 @@ static int nc_dma_get_range(struct device *dev, u64 *size)
   		return -EINVAL;
   	}
-	*size = ncomp->memory_address_limit >= 64 ? U64_MAX :
-			1ULL<<ncomp->memory_address_limit;
+	*limit = (1ULL << ncomp->memory_address_limit) - 1;

The old code handled 'ncomp->memory_address_limit >= 64' -- why is it safe to drop that? You mention it in the cover letter, so clearly I'm missing something!

Quoted 6 lines
Quoted 3 lines

Because an unsigned shift by 64 or more generates 0 (modulo 2^64), thus subtracting 1 results in the correct all-bits-set value for an inclusive 64-bit limit.

Oh, I'd have thought you'd have gotten one of those "left shift count >= width of type" warnings if you did that.

Compilers might give such a warning if it was a constant shift whose size was visible at compile time, but even then only because compilers seem to have a vendetta against us relying on the well-defined behaviours of unsigned integer overflow (it's only *signed* shifts which are UB if the result is unrepresentable).

Cheers,
Robin.

reviewer
#13 lore raw

Re: [PATCH 3/7] ACPI/IORT: Handle memory address size limits as limits

In reply to Will Deacon
Quoted 39 lines · Will Deacon
Quoted 38 lines · Robin Murphy
Quoted 36 lines · Will Deacon
Quoted 10 lines · Robin Murphy

Return the Root Complex/Named Component memory address size limit as an inclusive limit value, rather than an exclusive size. This saves us having to special-case 64-bit overflow, and simplifies our caller too.

---
  drivers/acpi/arm64/dma.c  |  9 +++------
  drivers/acpi/arm64/iort.c | 18 ++++++++----------
  include/linux/acpi_iort.h |  4 ++--
  3 files changed, 13 insertions(+), 18 deletions(-)

[...]

Quoted 13 lines
drivers/acpi/arm64/iort.c +1 -1
index 6496ff5a6ba2..eb64d8e17dd1 100644
@@ -1367,7 +1367,7 @@ int iort_iommu_configure_id(struct device *dev, const u32 *input_id)
  { return -ENODEV; }
  #endif
-static int nc_dma_get_range(struct device *dev, u64 *size)
+static int nc_dma_get_range(struct device *dev, u64 *limit)
  {
  	struct acpi_iort_node *node;
  	struct acpi_iort_named_component *ncomp;

drivers/acpi/arm64/iort.c static int nc_dma_get_range(struct device *dev, u64 *size) · line 1386

Quoted 5 lines
drivers/acpi/arm64/iort.c +1 -2
@@ -1384,13 +1384,12 @@ static int nc_dma_get_range(struct device *dev, u64 *size)
  		return -EINVAL;
  	}
-	*size = ncomp->memory_address_limit >= 64 ? U64_MAX :
-			1ULL<<ncomp->memory_address_limit;
+	*limit = (1ULL << ncomp->memory_address_limit) - 1;

The old code handled 'ncomp->memory_address_limit >= 64' -- why is it safe to drop that? You mention it in the cover letter, so clearly I'm missing something!

Quoted 6 lines
Quoted 3 lines

Because an unsigned shift by 64 or more generates 0 (modulo 2^64), thus subtracting 1 results in the correct all-bits-set value for an inclusive 64-bit limit.

Oh, I'd have thought you'd have gotten one of those "left shift count >= width of type" warnings if you did that.

I think you'll get a UBSAN splat, but here the compiler doesn't know what 'ncomp->memory_address_limit' will be and so doesn't produce a compile-time warning.

Regardless, it's undefined behaviour.

Mark.

author maintainer
#14 lore raw

Re: [PATCH 3/7] ACPI/IORT: Handle memory address size limits as limits

Quoted 44 lines · Mark Rutland
Quoted 43 lines · Will Deacon
Quoted 41 lines · Robin Murphy
Quoted 28 lines · Will Deacon
Quoted 10 lines · Robin Murphy

Return the Root Complex/Named Component memory address size limit as an inclusive limit value, rather than an exclusive size. This saves us having to special-case 64-bit overflow, and simplifies our caller too.

---
   drivers/acpi/arm64/dma.c  |  9 +++------
   drivers/acpi/arm64/iort.c | 18 ++++++++----------
   include/linux/acpi_iort.h |  4 ++--
   3 files changed, 13 insertions(+), 18 deletions(-)

[...]

Quoted 14 lines
drivers/acpi/arm64/iort.c +1 -1
index 6496ff5a6ba2..eb64d8e17dd1 100644
@@ -1367,7 +1367,7 @@ int iort_iommu_configure_id(struct device *dev, const u32 *input_id)
   { return -ENODEV; }
   #endif
-static int nc_dma_get_range(struct device *dev, u64 *size)
+static int nc_dma_get_range(struct device *dev, u64 *limit)
   {
   	struct acpi_iort_node *node;
   	struct acpi_iort_named_component *ncomp;
@@ -1384,13 +1384,12 @@ static int nc_dma_get_range(struct device *dev, u64 *size)
   		return -EINVAL;
Quoted 8 lines

drivers/acpi/arm64/iort.c static int nc_dma_get_range(struct device *dev, u64 *size) · line 1387

Quoted 4 lines
drivers/acpi/arm64/iort.c +1 -2
   	}
-	*size = ncomp->memory_address_limit >= 64 ? U64_MAX :
-			1ULL<<ncomp->memory_address_limit;
+	*limit = (1ULL << ncomp->memory_address_limit) - 1;

The old code handled 'ncomp->memory_address_limit >= 64' -- why is it safe to drop that? You mention it in the cover letter, so clearly I'm missing something!

Because an unsigned shift by 64 or more generates 0 (modulo 2^64), thus subtracting 1 results in the correct all-bits-set value for an inclusive 64-bit limit.

Quoted 8 lines
Quoted 2 lines

Oh, I'd have thought you'd have gotten one of those "left shift count >= width of type" warnings if you did that.

I think you'll get a UBSAN splat, but here the compiler doesn't know what 'ncomp->memory_address_limit' will be and so doesn't produce a compile-time warning.

Regardless, it's undefined behaviour.

Urgh, you're right... I double-checked 6.5.7.4 in the standard but managed to miss 6.5.7.3. So yeah, even though "4 << 62" or "2 << 63" are well-defined here, "1 << 64" isn't, dang. Thanks, funky old ISAs which did weird things for crazy large shifts and have no relevance to this code :(

Cheers,
Robin.

reviewer
#15 lore raw

Re: [PATCH 3/7] ACPI/IORT: Handle memory address size limits as limits

In reply to Robin Murphy
Quoted 38 lines · Robin Murphy
Quoted 28 lines · Will Deacon
Quoted 10 lines · Robin Murphy

Return the Root Complex/Named Component memory address size limit as an inclusive limit value, rather than an exclusive size. This saves us having to special-case 64-bit overflow, and simplifies our caller too.

---
  drivers/acpi/arm64/dma.c  |  9 +++------
  drivers/acpi/arm64/iort.c | 18 ++++++++----------
  include/linux/acpi_iort.h |  4 ++--
  3 files changed, 13 insertions(+), 18 deletions(-)

[...]

Quoted 14 lines
drivers/acpi/arm64/iort.c +1 -1
index 6496ff5a6ba2..eb64d8e17dd1 100644
@@ -1367,7 +1367,7 @@ int iort_iommu_configure_id(struct device *dev, const u32 *input_id)
  { return -ENODEV; }
  #endif
-static int nc_dma_get_range(struct device *dev, u64 *size)
+static int nc_dma_get_range(struct device *dev, u64 *limit)
  {
  	struct acpi_iort_node *node;
  	struct acpi_iort_named_component *ncomp;
@@ -1384,13 +1384,12 @@ static int nc_dma_get_range(struct device *dev, u64 *size)
  		return -EINVAL;
Quoted 8 lines

drivers/acpi/arm64/iort.c static int nc_dma_get_range(struct device *dev, u64 *size) · line 1387

Quoted 4 lines
drivers/acpi/arm64/iort.c +1 -2
  	}
-	*size = ncomp->memory_address_limit >= 64 ? U64_MAX :
-			1ULL<<ncomp->memory_address_limit;
+	*limit = (1ULL << ncomp->memory_address_limit) - 1;

The old code handled 'ncomp->memory_address_limit >= 64' -- why is it safe to drop that? You mention it in the cover letter, so clearly I'm missing something!

Quoted 1 line

Because an unsigned shift by 64 or more generates 0 (modulo 2^64),

I'm pretty sure that regardless of whether a type is signed, shifting more than the type's width is undefined behaviour. That causes GCC to scream at compile time:

|   CC      arch/arm64/kernel/setup.o
| arch/arm64/kernel/setup.c: In function 'shift_test':
| arch/arm64/kernel/setup.c:295:20: warning: left shift count >= width of type [-Wshift-count-overflow]
|   295 |         return 1UL << 64;
|       |                    ^~

... and a UBSAN splat:

| ================================================================================
| UBSAN: shift-out-of-bounds in arch/arm64/kernel/setup.c:295:13
| shift exponent 64 is too large for 64-bit type 'long unsigned int'
| CPU: 0 PID: 0 Comm: swapper Not tainted 6.7.0-rc1-00005-g06034455cb74-dirty #3
| Call trace:
|  dump_backtrace+0x90/0xe8
|  show_stack+0x18/0x24
|  dump_stack_lvl+0x48/0x60
|  dump_stack+0x18/0x24
|  __ubsan_handle_shift_out_of_bounds+0x114/0x244
|  shift_test+0x24/0x34
|  setup_arch+0x238/0x68c
|  start_kernel+0x70/0x610
|  __primary_switched+0xbc/0xc4
| ================================================================================

Mark.

Quoted 24 lines

thus
subtracting 1 results in the correct all-bits-set value for an inclusive 64-bit limit.

Thanks,
Robin.

Quoted 17 lines

drivers/acpi/arm64/iort.c static int rc_dma_get_range(struct device *dev, u64 *size) · line 1409

Quoted 13 lines
drivers/acpi/arm64/iort.c +2 -3
  	return 0;
  }
-static int rc_dma_get_range(struct device *dev, u64 *size)
+static int rc_dma_get_range(struct device *dev, u64 *limit)
  {
  	struct acpi_iort_node *node;
  	struct acpi_iort_root_complex *rc;
@@ -1408,8 +1407,7 @@ static int rc_dma_get_range(struct device *dev, u64 *size)
  		return -EINVAL;
  	}
-	*size = rc->memory_address_limit >= 64 ? U64_MAX :
-			1ULL<<rc->memory_address_limit;
+	*limit = (1ULL << rc->memory_address_limit) - 1;

Same thing here.

Will

author maintainer
#16 lore raw

PATCH 4/7 dma-mapping: Add helpers for dma_range_map bounds

In reply to Robin Murphy

Several places want to compute the lower and/or upper bounds of a dma_range_map, so let's factor that out into reusable helpers.

---
 arch/loongarch/kernel/dma.c |  9 ++-------
 drivers/acpi/arm64/dma.c    |  8 +-------
 drivers/of/device.c         | 11 ++---------
 include/linux/dma-direct.h  | 18 ++++++++++++++++++
 4 files changed, 23 insertions(+), 23 deletions(-)
arch/loongarch/kernel/dma.c +2 -7
index 7a9c6a9dd2d0..429555fb4e13 100644
@@ -8,17 +8,12 @@
 void acpi_arch_dma_setup(struct device *dev)
 {
 	int ret;
-	u64 mask, end = 0;
+	u64 mask, end;
 	const struct bus_dma_region *map = NULL;
 
 	ret = acpi_dma_get_range(dev, &map);
 	if (!ret && map) {
-		const struct bus_dma_region *r = map;
-
-		for (end = 0; r->size; r++) {
-			if (r->dma_start + r->size - 1 > end)
-				end = r->dma_start + r->size - 1;
-		}
+		end = dma_range_map_max(map);
 
 		mask = DMA_BIT_MASK(ilog2(end) + 1);
 		dev->bus_dma_limit = end;
drivers/acpi/arm64/dma.c +1 -7
index b98a149f8d50..52b2abf88689 100644
@@ -28,13 +28,7 @@ void acpi_arch_dma_setup(struct device *dev)
 
 	ret = acpi_dma_get_range(dev, &map);
 	if (!ret && map) {
-		const struct bus_dma_region *r = map;
-
-		for (end = 0; r->size; r++) {
-			if (r->dma_start + r->size - 1 > end)
-				end = r->dma_start + r->size - 1;
-		}
-
+		end = dma_range_map_max(map);
 		dev->dma_range_map = map;
 	}
 
drivers/of/device.c +2 -9
index 51062a831970..66879edb4a61 100644
@@ -117,16 +117,9 @@ int of_dma_configure_id(struct device *dev, struct device_node *np,
 		if (!force_dma)
 			return ret == -ENODEV ? 0 : ret;
 	} else {
-		const struct bus_dma_region *r = map;
-
 		/* Determine the overall bounds of all DMA regions */
-		for (dma_start = ~0; r->size; r++) {
-			/* Take lower and upper limits */
-			if (r->dma_start < dma_start)
-				dma_start = r->dma_start;
-			if (r->dma_start + r->size > end)
-				end = r->dma_start + r->size;
-		}
+		dma_start = dma_range_map_min(map);
+		end = dma_range_map_max(map);
 	}
 
 	/*
include/linux/dma-direct.h +18 -0
index 3eb3589ff43e..b77e3863daab 100644
@@ -54,6 +54,24 @@ static inline phys_addr_t translate_dma_to_phys(struct device *dev,
 	return (phys_addr_t)-1;
 }
 
+static inline dma_addr_t dma_range_map_min(const struct bus_dma_region *map)
+{
+	dma_addr_t ret = U64_MAX;
+
+	for (; map->size; map++)
+		ret = min(ret, map->dma_start);
+	return ret;
+}
+
+static inline dma_addr_t dma_range_map_max(const struct bus_dma_region *map)
+{
+	dma_addr_t ret = 0;
+
+	for (; map->size; map++)
+		ret = max(ret, map->dma_start + map->size - 1);
+	return ret;
+}
+
 #ifdef CONFIG_ARCH_HAS_PHYS_TO_DMA
 #include <asm/dma-direct.h>
 #ifndef phys_to_dma_unencrypted
Signature · 1 line
2.39.2.101.g768bb238c484.dirty
Reviewed-by on v1 4/7
#17 lore raw

Re: [PATCH 4/7] dma-mapping: Add helpers for dma_range_map bounds

Quoted 10 lines · Robin Murphy

Several places want to compute the lower and/or upper bounds of a dma_range_map, so let's factor that out into reusable helpers.

---
 arch/loongarch/kernel/dma.c |  9 ++-------
 drivers/acpi/arm64/dma.c    |  8 +-------
 drivers/of/device.c         | 11 ++---------
 include/linux/dma-direct.h  | 18 ++++++++++++++++++
 4 files changed, 23 insertions(+), 23 deletions(-)

Jason

#18 lore raw

Re: [PATCH 4/7] dma-mapping: Add helpers for dma_range_map bounds

In reply to Robin Murphy
kernel test robot noticed the following build warnings:

Hi Robin,

kernel test robot noticed the following build warnings:

[auto build test WARNING on rafael-pm/linux-next]
[also build test WARNING on rafael-pm/acpi-bus linus/master rafael-pm/devprop v6.7-rc3 next-20231129] [cannot apply to joro-iommu/next]
[If your patch is applied to the wrong git tree, kindly drop us a note.
And when submitting patch, we suggest to use '--base' as documented in
https://git-scm.com/docs/git-format-patch#_base_tree_information]

url:    https://github.com/intel-lab-lkp/linux/commits/Robin-Murphy/OF-Simplify-DMA-range-calculations/20231130-024624
base:   https://git.kernel.org/pub/scm/linux/kernel/git/rafael/linux-pm.git linux-next
patch link:    https://lore.kernel.org/r/b6626985d97ddc33a23b4b9fafa881b35001547e.1701268753.git.robin.murphy%40arm.com
patch subject: [PATCH 4/7] dma-mapping: Add helpers for dma_range_map bounds
config: arm-allnoconfig (https://download.01.org/0day-ci/archive/20231130/[email protected]/config)
compiler: arm-linux-gnueabi-gcc (GCC) 13.2.0
reproduce (this is a W=1 build): (https://download.01.org/0day-ci/archive/20231130/[email protected]/reproduce)

If you fix the issue in a separate patch/commit (i.e. not just a new version of the same patch/commit), kindly add following tags
| Reported-by: kernel test robot <[email protected]>
| Closes: https://lore.kernel.org/oe-kbuild-all/[email protected]/

All warnings (new ones prefixed by >>):

In file included from include/linux/thread_info.h:12,
from include/asm-generic/preempt.h:5, from ./arch/arm/include/generated/asm/preempt.h:1, from include/linux/preempt.h:79,
from include/linux/spinlock.h:56,
from include/linux/mmzone.h:8,
from include/linux/gfp.h:7,
from include/linux/mm.h:7,
from include/linux/memblock.h:12,
from kernel/dma/mapping.c:8:
include/linux/dma-direct.h: In function 'dma_range_map_min':

>> include/linux/limits.h:25:25: warning: conversion from 'long long unsigned int' to 'dma_addr_t' {aka 'unsigned int'} changes value from '18446744073709551615' to '4294967295' [-Woverflow]
      25 | #define U64_MAX         ((u64)~0ULL)
         |                         ^
   include/linux/dma-direct.h:54:26: note: in expansion of macro 'U64_MAX'
      54 |         dma_addr_t ret = U64_MAX;
         |                          ^~~~~~~

vim +25 include/linux/limits.h

3c9d017cc283df Andy Shevchenko 2023-08-04  14  
54d50897d544c8 Masahiro Yamada 2019-03-07  15  #define U8_MAX		((u8)~0U)
54d50897d544c8 Masahiro Yamada 2019-03-07  16  #define S8_MAX		((s8)(U8_MAX >> 1))
54d50897d544c8 Masahiro Yamada 2019-03-07  17  #define S8_MIN		((s8)(-S8_MAX - 1))
54d50897d544c8 Masahiro Yamada 2019-03-07  18  #define U16_MAX		((u16)~0U)
54d50897d544c8 Masahiro Yamada 2019-03-07  19  #define S16_MAX		((s16)(U16_MAX >> 1))
54d50897d544c8 Masahiro Yamada 2019-03-07  20  #define S16_MIN		((s16)(-S16_MAX - 1))
54d50897d544c8 Masahiro Yamada 2019-03-07  21  #define U32_MAX		((u32)~0U)
3f50f132d8400e John Fastabend  2020-03-30  22  #define U32_MIN		((u32)0)
54d50897d544c8 Masahiro Yamada 2019-03-07  23  #define S32_MAX		((s32)(U32_MAX >> 1))
54d50897d544c8 Masahiro Yamada 2019-03-07  24  #define S32_MIN		((s32)(-S32_MAX - 1))
54d50897d544c8 Masahiro Yamada 2019-03-07 @25  #define U64_MAX		((u64)~0ULL)
54d50897d544c8 Masahiro Yamada 2019-03-07  26  #define S64_MAX		((s64)(U64_MAX >> 1))
54d50897d544c8 Masahiro Yamada 2019-03-07  27  #define S64_MIN		((s64)(-S64_MAX - 1))

54d50897d544c8 Masahiro Yamada 2019-03-07 28

Signature · 2 lines
0-DAY CI Kernel Test Service
https://github.com/intel/lkp-tests/wiki
#19 lore raw

Re: [PATCH 4/7] dma-mapping: Add helpers for dma_range_map bounds

In reply to Robin Murphy
kernel test robot noticed the following build warnings:

Hi Robin,

kernel test robot noticed the following build warnings:

[auto build test WARNING on rafael-pm/linux-next]
[also build test WARNING on rafael-pm/acpi-bus linus/master rafael-pm/devprop v6.7-rc3 next-20231129] [cannot apply to joro-iommu/next]
[If your patch is applied to the wrong git tree, kindly drop us a note.
And when submitting patch, we suggest to use '--base' as documented in
https://git-scm.com/docs/git-format-patch#_base_tree_information]

url:    https://github.com/intel-lab-lkp/linux/commits/Robin-Murphy/OF-Simplify-DMA-range-calculations/20231130-024624
base:   https://git.kernel.org/pub/scm/linux/kernel/git/rafael/linux-pm.git linux-next
patch link:    https://lore.kernel.org/r/b6626985d97ddc33a23b4b9fafa881b35001547e.1701268753.git.robin.murphy%40arm.com
patch subject: [PATCH 4/7] dma-mapping: Add helpers for dma_range_map bounds
config: arm-lpc32xx_defconfig (https://download.01.org/0day-ci/archive/20231130/[email protected]/config)
compiler: clang version 17.0.0 (https://github.com/llvm/llvm-project.git 4a5ac14ee968ff0ad5d2cc1ffa0299048db4c88a)
reproduce (this is a W=1 build): (https://download.01.org/0day-ci/archive/20231130/[email protected]/reproduce)

If you fix the issue in a separate patch/commit (i.e. not just a new version of the same patch/commit), kindly add following tags
| Reported-by: kernel test robot <[email protected]>
| Closes: https://lore.kernel.org/oe-kbuild-all/[email protected]/

All warnings (new ones prefixed by >>):

In file included from arch/arm/mm/dma-mapping.c:17:

>> include/linux/dma-direct.h:54:19: warning: implicit conversion from 'u64' (aka 'unsigned long long') to 'dma_addr_t' (aka 'unsigned int') changes value from 18446744073709551615 to 4294967295 [-Wconstant-conversion]
      54 |         dma_addr_t ret = U64_MAX;
         |                    ~~~   ^~~~~~~
   include/linux/limits.h:25:19: note: expanded from macro 'U64_MAX'
      25 | #define U64_MAX         ((u64)~0ULL)
         |                          ^~~~~~~~~~

1 warning generated.

vim +54 include/linux/dma-direct.h

    51	
    52	static inline dma_addr_t dma_range_map_min(const struct bus_dma_region *map)
    53	{
  > 54		dma_addr_t ret = U64_MAX;
    55	
    56		for (; map->size; map++)
    57			ret = min(ret, map->dma_start);
    58		return ret;
    59	}
    60	
Signature · 2 lines
0-DAY CI Kernel Test Service
https://github.com/intel/lkp-tests/wiki
Reviewed-by on v1 4/7
#20 lore raw

Re: [PATCH 4/7] dma-mapping: Add helpers for dma_range_map bounds

In reply to Robin Murphy
Quoted 2 lines · Robin Murphy

Several places want to compute the lower and/or upper bounds of a dma_range_map, so let's factor that out into reusable helpers.

As the build bot pointed out this will need a fix for the initialization all-Fs for a 32-bit dma_addr_t, e.g. by using (dma_addr_t)-1, but otherwise looks good:

author maintainer
#21 lore raw

PATCH 5/7 iommu/dma: Make limit checks self-contained

In reply to Robin Murphy

It's now easy to retrieve the device's DMA limits if we want to check them against the domain aperture, so do that ourselves instead of relying on them being passed through the callchain.

---
 drivers/iommu/dma-iommu.c | 18 ++++++++----------
 1 file changed, 8 insertions(+), 10 deletions(-)
drivers/iommu/dma-iommu.c +8 -10
index 5dc012220ca9..7745e7e17010 100644
@@ -659,8 +659,6 @@ static void iommu_dma_init_options(struct iommu_dma_options *options,
 /**
  * iommu_dma_init_domain - Initialise a DMA mapping domain
  * @domain: IOMMU domain previously prepared by iommu_get_dma_cookie()
- * @base: IOVA at which the mappable address space starts
- * @limit: Last address of the IOVA space
  * @dev: Device the domain is being initialised for
  *
  * @base and @limit + 1 should be exact multiples of IOMMU page granularity to
@@ -668,10 +666,10 @@ static void iommu_dma_init_options(struct iommu_dma_options *options,
  * to ensure it is an invalid IOVA. It is safe to reinitialise a domain, but
  * any change which could make prior IOVAs invalid will fail.
  */
-static int iommu_dma_init_domain(struct iommu_domain *domain, dma_addr_t base,
-				 dma_addr_t limit, struct device *dev)
+static int iommu_dma_init_domain(struct iommu_domain *domain, struct device *dev)
 {
 	struct iommu_dma_cookie *cookie = domain->iova_cookie;
+	const struct bus_dma_region *map = dev->dma_range_map;
 	unsigned long order, base_pfn;
 	struct iova_domain *iovad;
 	int ret;
@@ -683,18 +681,18 @@ static int iommu_dma_init_domain(struct iommu_domain *domain, dma_addr_t base,
 
 	/* Use the smallest supported page size for IOVA granularity */
 	order = __ffs(domain->pgsize_bitmap);
-	base_pfn = max_t(unsigned long, 1, base >> order);
+	base_pfn = 1;
 
 	/* Check the domain allows at least some access to the device... */
-	if (domain->geometry.force_aperture) {
+	if (map) {
+		dma_addr_t base = dma_range_map_min(map);
 		if (base > domain->geometry.aperture_end ||
-		    limit < domain->geometry.aperture_start) {
+		    dma_range_map_max(map) < domain->geometry.aperture_start) {
 			pr_warn("specified DMA range outside IOMMU capability\n");
 			return -EFAULT;
 		}
 		/* ...then finally give it a kicking to make sure it fits */
-		base_pfn = max_t(unsigned long, base_pfn,
-				domain->geometry.aperture_start >> order);
+		base_pfn = max(base, domain->geometry.aperture_start) >> order;
 	}
 
 	/* start_pfn is always nonzero for an already-initialised domain */
@@ -1743,7 +1741,7 @@ void iommu_setup_dma_ops(struct device *dev, u64 dma_base, u64 dma_limit)
 	 * underlying IOMMU driver needs to support via the dma-iommu layer.
 	 */
 	if (iommu_is_dma_domain(domain)) {
-		if (iommu_dma_init_domain(domain, dma_base, dma_limit, dev))
+		if (iommu_dma_init_domain(domain, dev))
 			goto out_err;
 		dev->dma_ops = &iommu_dma_ops;
 	}
Signature · 1 line
2.39.2.101.g768bb238c484.dirty
Reviewed-by on v1 5/7
#22 lore raw

Re: [PATCH 5/7] iommu/dma: Make limit checks self-contained

Quoted 8 lines · Robin Murphy

It's now easy to retrieve the device's DMA limits if we want to check them against the domain aperture, so do that ourselves instead of relying on them being passed through the callchain.

---
 drivers/iommu/dma-iommu.c | 18 ++++++++----------
 1 file changed, 8 insertions(+), 10 deletions(-)

When I spent some time noodling on this a few weeks ago I was looking at putting the dma_range_map_min() effectively as a new reserved region in the common reserved region code so it naturally flows out to all the right places.

But this is no worse in that regard than what we have right now:

drivers/iommu/dma-iommu.c static int iommu_dma_init_domain(struct iommu_domain *domain, dma_addr_t base, · line 687

Quoted 3 lines
drivers/iommu/dma-iommu.c +1 -1
 	/* Check the domain allows at least some access to the device... */
-	if (domain->geometry.force_aperture) {
+	if (map) {

Oh, I've been sitting on a patch to delete force_aperture now too..

Jason

author maintainer
#23 lore raw

PATCH 6/7 iommu/dma: Centralise iommu_setup_dma_ops()

In reply to Robin Murphy

It's somewhat hard to see, but arm64's arch_setup_dma_ops() should only ever call iommu_setup_dma_ops() after a successful iommu_probe_device(), which means there should be no harm in instead running it off the back of iommu_probe_device() itself, as is currently done for x86 and s390 with .probe_finalize bodges. Pull it all into the main flow properly.

---
 arch/arm64/mm/dma-mapping.c  |  2 --
 drivers/iommu/amd/iommu.c    |  8 --------
 drivers/iommu/dma-iommu.c    | 17 ++++-------------
 drivers/iommu/dma-iommu.h    |  6 ++++++
 drivers/iommu/intel/iommu.c  |  7 -------
 drivers/iommu/iommu.c        |  2 ++
 drivers/iommu/s390-iommu.c   |  6 ------
 drivers/iommu/virtio-iommu.c | 10 ----------
 include/linux/iommu.h        |  7 -------
 9 files changed, 12 insertions(+), 53 deletions(-)
arch/arm64/mm/dma-mapping.c +0 -2
index 3cb101e8cb29..96ff791199e8 100644
@@ -58,8 +58,6 @@ void arch_setup_dma_ops(struct device *dev, u64 dma_base, u64 size,
 		   ARCH_DMA_MINALIGN, cls);
 
 	dev->dma_coherent = coherent;
-	if (iommu)
-		iommu_setup_dma_ops(dev, dma_base, dma_base + size - 1);
 
 	xen_setup_dma_ops(dev);
 }
drivers/iommu/amd/iommu.c +0 -8
index fcc987f5d4ed..9418808009ba 100644
@@ -1985,13 +1985,6 @@ static struct iommu_device *amd_iommu_probe_device(struct device *dev)
 	return iommu_dev;
 }
 
-static void amd_iommu_probe_finalize(struct device *dev)
-{
-	/* Domains are initialized for this device - have a look what we ended up with */
-	set_dma_ops(dev, NULL);
-	iommu_setup_dma_ops(dev, 0, U64_MAX);
-}
-
 static void amd_iommu_release_device(struct device *dev)
 {
 	struct amd_iommu *iommu;
@@ -2646,7 +2639,6 @@ const struct iommu_ops amd_iommu_ops = {
 	.domain_alloc_user = amd_iommu_domain_alloc_user,
 	.probe_device = amd_iommu_probe_device,
 	.release_device = amd_iommu_release_device,
-	.probe_finalize = amd_iommu_probe_finalize,
 	.device_group = amd_iommu_device_group,
 	.get_resv_regions = amd_iommu_get_resv_regions,
 	.is_attach_deferred = amd_iommu_is_attach_deferred,
drivers/iommu/dma-iommu.c +4 -13
index 7745e7e17010..e0ba8714fdbd 100644
@@ -1725,25 +1725,17 @@ static const struct dma_map_ops iommu_dma_ops = {
 	.opt_mapping_size	= iommu_dma_opt_mapping_size,
 };
 
-/*
- * The IOMMU core code allocates the default DMA domain, which the underlying
- * IOMMU driver needs to support via the dma-iommu layer.
- */
-void iommu_setup_dma_ops(struct device *dev, u64 dma_base, u64 dma_limit)
+void iommu_setup_dma_ops(struct device *dev)
 {
 	struct iommu_domain *domain = iommu_get_domain_for_dev(dev);
 
-	if (!domain)
-		goto out_err;
-
-	/*
-	 * The IOMMU core code allocates the default DMA domain, which the
-	 * underlying IOMMU driver needs to support via the dma-iommu layer.
-	 */
 	if (iommu_is_dma_domain(domain)) {
 		if (iommu_dma_init_domain(domain, dev))
 			goto out_err;
 		dev->dma_ops = &iommu_dma_ops;
+	} else if (dev->dma_ops == &iommu_dma_ops) {
+		/* Clean up if we've switched *from* a DMA domain */
+		dev->dma_ops = NULL;
 	}
 
 	return;
@@ -1751,7 +1743,6 @@ void iommu_setup_dma_ops(struct device *dev, u64 dma_base, u64 dma_limit)
 	 pr_warn("Failed to set up IOMMU for device %s; retaining platform DMA ops\n",
 		 dev_name(dev));
 }
-EXPORT_SYMBOL_GPL(iommu_setup_dma_ops);
 
 static struct iommu_dma_msi_page *iommu_dma_get_msi_page(struct device *dev,
 		phys_addr_t msi_addr, struct iommu_domain *domain)
drivers/iommu/dma-iommu.h +6 -0
index c829f1f82a99..cf04560531ed 100644
@@ -9,6 +9,8 @@
 
 #ifdef CONFIG_IOMMU_DMA
 
+void iommu_setup_dma_ops(struct device *dev);
+
 int iommu_get_dma_cookie(struct iommu_domain *domain);
 void iommu_put_dma_cookie(struct iommu_domain *domain);
 
@@ -24,6 +26,10 @@ static inline void iommu_dma_set_pci_32bit_workaround(struct device *dev)
 
 #else /* CONFIG_IOMMU_DMA */
 
+static inline void iommu_setup_dma_ops(struct device *dev)
+{
+}
+
 static inline int iommu_dma_init_fq(struct iommu_domain *domain)
 {
 	return -EINVAL;
drivers/iommu/intel/iommu.c +0 -7
index 3531b956556c..f7347ed41e89 100644
@@ -4480,12 +4480,6 @@ static void intel_iommu_release_device(struct device *dev)
 	set_dma_ops(dev, NULL);
 }
 
-static void intel_iommu_probe_finalize(struct device *dev)
-{
-	set_dma_ops(dev, NULL);
-	iommu_setup_dma_ops(dev, 0, U64_MAX);
-}
-
 static void intel_iommu_get_resv_regions(struct device *device,
 					 struct list_head *head)
 {
@@ -4937,7 +4931,6 @@ const struct iommu_ops intel_iommu_ops = {
 	.domain_alloc		= intel_iommu_domain_alloc,
 	.domain_alloc_user	= intel_iommu_domain_alloc_user,
 	.probe_device		= intel_iommu_probe_device,
-	.probe_finalize		= intel_iommu_probe_finalize,
 	.release_device		= intel_iommu_release_device,
 	.get_resv_regions	= intel_iommu_get_resv_regions,
 	.device_group		= intel_iommu_device_group,
drivers/iommu/iommu.c +2 -0
index 824989874dee..3a0901165b69 100644
@@ -589,6 +589,8 @@ int iommu_probe_device(struct device *dev)
 	if (ret)
 		return ret;
 
+	iommu_setup_dma_ops(dev);
+
 	ops = dev_iommu_ops(dev);
 	if (ops->probe_finalize)
 		ops->probe_finalize(dev);
drivers/iommu/s390-iommu.c +0 -6
index 9a5196f523de..d8eaa7ea380b 100644
@@ -695,11 +695,6 @@ static size_t s390_iommu_unmap_pages(struct iommu_domain *domain,
 	return size;
 }
 
-static void s390_iommu_probe_finalize(struct device *dev)
-{
-	iommu_setup_dma_ops(dev, 0, U64_MAX);
-}
-
 struct zpci_iommu_ctrs *zpci_get_iommu_ctrs(struct zpci_dev *zdev)
 {
 	if (!zdev || !zdev->s390_domain)
@@ -785,7 +780,6 @@ static const struct iommu_ops s390_iommu_ops = {
 	.capable = s390_iommu_capable,
 	.domain_alloc_paging = s390_domain_alloc_paging,
 	.probe_device = s390_iommu_probe_device,
-	.probe_finalize = s390_iommu_probe_finalize,
 	.release_device = s390_iommu_release_device,
 	.device_group = generic_device_group,
 	.pgsize_bitmap = SZ_4K,
drivers/iommu/virtio-iommu.c +0 -10
index 9bcffdde6175..5a558456946b 100644
@@ -998,15 +998,6 @@ static struct iommu_device *viommu_probe_device(struct device *dev)
 	return ERR_PTR(ret);
 }
 
-static void viommu_probe_finalize(struct device *dev)
-{
-#ifndef CONFIG_ARCH_HAS_SETUP_DMA_OPS
-	/* First clear the DMA ops in case we're switching from a DMA domain */
-	set_dma_ops(dev, NULL);
-	iommu_setup_dma_ops(dev, 0, U64_MAX);
-#endif
-}
-
 static void viommu_release_device(struct device *dev)
 {
 	struct viommu_endpoint *vdev = dev_iommu_priv_get(dev);
@@ -1043,7 +1034,6 @@ static struct iommu_ops viommu_ops = {
 	.capable		= viommu_capable,
 	.domain_alloc		= viommu_domain_alloc,
 	.probe_device		= viommu_probe_device,
-	.probe_finalize		= viommu_probe_finalize,
 	.release_device		= viommu_release_device,
 	.device_group		= viommu_device_group,
 	.get_resv_regions	= viommu_get_resv_regions,
include/linux/iommu.h +0 -7
index 20ed3a4fc5e0..3a3bf8afb8ca 100644
@@ -1283,9 +1283,6 @@ static inline void iommu_debugfs_setup(void) {}
 #ifdef CONFIG_IOMMU_DMA
 #include <linux/msi.h>
 
-/* Setup call for arch DMA mapping code */
-void iommu_setup_dma_ops(struct device *dev, u64 dma_base, u64 dma_limit);
-
 int iommu_get_msi_cookie(struct iommu_domain *domain, dma_addr_t base);
 
 int iommu_dma_prepare_msi(struct msi_desc *desc, phys_addr_t msi_addr);
@@ -1296,10 +1293,6 @@ void iommu_dma_compose_msi_msg(struct msi_desc *desc, struct msi_msg *msg);
 struct msi_desc;
 struct msi_msg;
 
-static inline void iommu_setup_dma_ops(struct device *dev, u64 dma_base, u64 dma_limit)
-{
-}
-
 static inline int iommu_get_msi_cookie(struct iommu_domain *domain, dma_addr_t base)
 {
 	return -ENODEV;
Signature · 1 line
2.39.2.101.g768bb238c484.dirty
#24 lore raw

Re: [PATCH 6/7] iommu/dma: Centralise iommu_setup_dma_ops()

Quoted 7 lines · Robin Murphy

It's somewhat hard to see, but arm64's arch_setup_dma_ops() should only ever call iommu_setup_dma_ops() after a successful iommu_probe_device(), which means there should be no harm in instead running it off the back of iommu_probe_device() itself, as is currently done for x86 and s390 with .probe_finalize bodges. Pull it all into the main flow properly.

Quoted 11 lines
---
 arch/arm64/mm/dma-mapping.c  |  2 --
 drivers/iommu/amd/iommu.c    |  8 --------
 drivers/iommu/dma-iommu.c    | 17 ++++-------------
 drivers/iommu/dma-iommu.h    |  6 ++++++
 drivers/iommu/intel/iommu.c  |  7 -------
 drivers/iommu/iommu.c        |  2 ++
 drivers/iommu/s390-iommu.c   |  6 ------
 drivers/iommu/virtio-iommu.c | 10 ----------
 include/linux/iommu.h        |  7 -------
 9 files changed, 12 insertions(+), 53 deletions(-)

Yes! That probe_finalize() stuff is not nice

drivers/iommu/iommu.c int iommu_probe_device(struct device *dev) · line 593

Quoted 10 lines
drivers/iommu/iommu.c +2 -0
index 824989874dee..3a0901165b69 100644
@@ -589,6 +589,8 @@ int iommu_probe_device(struct device *dev)
 	if (ret)
 		return ret;
 
+	iommu_setup_dma_ops(dev);
+

I'm pretty sure this should be inside the group mutex lock.

The setting of dev->dma_ops should exactly follow the setting of group->domain, and all transitions, including from the sysfs override file should update it, right?

Thus to avoid races agsinst concurrent sysfs this should be locked.

I think you can just put this in iommu_setup_default_domain() and it will take care of all the cases?

Once in iommu_setup_default_domain() it is easy to call it with the domain argument and it can know the domain type without using iommu_is_dma_domain() which is one of the very last few places checking for the DMA domain type.

Jason

author maintainer
#25 lore raw

PATCH 7/7 dma-mapping: Simplify arch_setup_dma_ops()

In reply to Robin Murphy

The dma_base, size and iommu arguments are only used by ARM, and can now easily be deduced from the device itself, so there's no need to pass them through the callchain as well.

---
 arch/arc/mm/dma.c               |  3 +--
 arch/arm/mm/dma-mapping-nommu.c |  3 +--
 arch/arm/mm/dma-mapping.c       | 12 ++++++++----
 arch/arm64/mm/dma-mapping.c     |  3 +--
 arch/mips/mm/dma-noncoherent.c  |  3 +--
 arch/riscv/mm/dma-noncoherent.c |  3 +--
 drivers/acpi/scan.c             |  3 +--
 drivers/hv/hv_common.c          |  6 +-----
 drivers/of/device.c             |  4 +---
 include/linux/dma-map-ops.h     |  6 ++----
 10 files changed, 18 insertions(+), 28 deletions(-)
arch/arc/mm/dma.c +1 -2
index 2a7fbbb83b70..6b85e94f3275 100644
@@ -90,8 +90,7 @@ void arch_sync_dma_for_cpu(phys_addr_t paddr, size_t size,
 /*
  * Plug in direct dma map ops.
  */
-void arch_setup_dma_ops(struct device *dev, u64 dma_base, u64 size,
-			const struct iommu_ops *iommu, bool coherent)
+void arch_setup_dma_ops(struct device *dev, bool coherent)
 {
 	/*
 	 * IOC hardware snoops all DMA traffic keeping the caches consistent
arch/arm/mm/dma-mapping-nommu.c +1 -2
index cfd9c933d2f0..97db5397c320 100644
@@ -33,8 +33,7 @@ void arch_sync_dma_for_cpu(phys_addr_t paddr, size_t size,
 	}
 }
 
-void arch_setup_dma_ops(struct device *dev, u64 dma_base, u64 size,
-			const struct iommu_ops *iommu, bool coherent)
+void arch_setup_dma_ops(struct device *dev, bool coherent)
 {
 	if (IS_ENABLED(CONFIG_CPU_V7M)) {
 		/*
arch/arm/mm/dma-mapping.c +8 -4
index 5409225b4abc..70a2131f9b09 100644
@@ -1716,7 +1716,12 @@ static void arm_setup_iommu_dma_ops(struct device *dev, u64 dma_base, u64 size,
 				    const struct iommu_ops *iommu, bool coherent)
 {
 	struct dma_iommu_mapping *mapping;
+	u64 dma_base = 0, size = SZ_4GB;
 
+	if (dev->dma_range_map) {
+		dma_base = dma_range_map_min(dev->dma_range_map);
+		size = dma_range_map_max(dev->dma_range_map) - dma_base;
+	}
 	mapping = arm_iommu_create_mapping(dev->bus, dma_base, size);
 	if (IS_ERR(mapping)) {
 		pr_warn("Failed to create %llu-byte IOMMU mapping for device %s\n",
@@ -1756,8 +1761,7 @@ static void arm_teardown_iommu_dma_ops(struct device *dev) { }
 
 #endif	/* CONFIG_ARM_DMA_USE_IOMMU */
 
-void arch_setup_dma_ops(struct device *dev, u64 dma_base, u64 size,
-			const struct iommu_ops *iommu, bool coherent)
+void arch_setup_dma_ops(struct device *dev, bool coherent)
 {
 	/*
 	 * Due to legacy code that sets the ->dma_coherent flag from a bus
@@ -1776,8 +1780,8 @@ void arch_setup_dma_ops(struct device *dev, u64 dma_base, u64 size,
 	if (dev->dma_ops)
 		return;
 
-	if (iommu)
-		arm_setup_iommu_dma_ops(dev, dma_base, size, iommu, coherent);
+	if (device_iommu_mapped(dev))
+		arm_setup_iommu_dma_ops(dev);
 
 	xen_setup_dma_ops(dev);
 	dev->archdata.dma_ops_setup = true;
arch/arm64/mm/dma-mapping.c +1 -2
index 96ff791199e8..0b320a25a471 100644
@@ -46,8 +46,7 @@ void arch_teardown_dma_ops(struct device *dev)
 }
 #endif
 
-void arch_setup_dma_ops(struct device *dev, u64 dma_base, u64 size,
-			const struct iommu_ops *iommu, bool coherent)
+void arch_setup_dma_ops(struct device *dev, bool coherent)
 {
 	int cls = cache_line_size_of_cpu();
 
arch/mips/mm/dma-noncoherent.c +1 -2
index 3c4fc97b9f39..ab4f2a75a7d0 100644
@@ -137,8 +137,7 @@ void arch_sync_dma_for_cpu(phys_addr_t paddr, size_t size,
 #endif
 
 #ifdef CONFIG_ARCH_HAS_SETUP_DMA_OPS
-void arch_setup_dma_ops(struct device *dev, u64 dma_base, u64 size,
-		const struct iommu_ops *iommu, bool coherent)
+void arch_setup_dma_ops(struct device *dev, bool coherent)
 {
 	dev->dma_coherent = coherent;
 }
arch/riscv/mm/dma-noncoherent.c +1 -2
index 4e4e469b8dd6..cb89d7e0ba88 100644
@@ -128,8 +128,7 @@ void arch_dma_prep_coherent(struct page *page, size_t size)
 	ALT_CMO_OP(FLUSH, flush_addr, size, riscv_cbom_block_size);
 }
 
-void arch_setup_dma_ops(struct device *dev, u64 dma_base, u64 size,
-		const struct iommu_ops *iommu, bool coherent)
+void arch_setup_dma_ops(struct device *dev, bool coherent)
 {
 	WARN_TAINT(!coherent && riscv_cbom_block_size > ARCH_DMA_MINALIGN,
 		   TAINT_CPU_OUT_OF_SPEC,
drivers/acpi/scan.c +1 -2
index ee88a727f200..cad171fc31e8 100644
@@ -1640,8 +1640,7 @@ int acpi_dma_configure_id(struct device *dev, enum dev_dma_attr attr,
 	if (PTR_ERR(iommu) == -EPROBE_DEFER)
 		return -EPROBE_DEFER;
 
-	arch_setup_dma_ops(dev, 0, U64_MAX,
-				iommu, attr == DEV_DMA_COHERENT);
+	arch_setup_dma_ops(dev, attr == DEV_DMA_COHERENT);
 
 	return 0;
 }
drivers/hv/hv_common.c +1 -5
index 4372f5d146ab..0e2decd1167a 100644
@@ -484,11 +484,7 @@ EXPORT_SYMBOL_GPL(hv_query_ext_cap);
 
 void hv_setup_dma_ops(struct device *dev, bool coherent)
 {
-	/*
-	 * Hyper-V does not offer a vIOMMU in the guest
-	 * VM, so pass 0/NULL for the IOMMU settings
-	 */
-	arch_setup_dma_ops(dev, 0, 0, NULL, coherent);
+	arch_setup_dma_ops(dev, coherent);
 }
 EXPORT_SYMBOL_GPL(hv_setup_dma_ops);
 
drivers/of/device.c +1 -3
index 66879edb4a61..3394751015d3 100644
@@ -96,7 +96,6 @@ int of_dma_configure_id(struct device *dev, struct device_node *np,
 	const struct iommu_ops *iommu;
 	const struct bus_dma_region *map = NULL;
 	struct device_node *bus_np;
-	u64 dma_start = 0;
 	u64 mask, end = 0;
 	bool coherent;
 	int ret;
@@ -118,7 +117,6 @@ int of_dma_configure_id(struct device *dev, struct device_node *np,
 			return ret == -ENODEV ? 0 : ret;
 	} else {
 		/* Determine the overall bounds of all DMA regions */
-		dma_start = dma_range_map_min(map);
 		end = dma_range_map_max(map);
 	}
 
@@ -167,7 +165,7 @@ int of_dma_configure_id(struct device *dev, struct device_node *np,
 	dev_dbg(dev, "device is%sbehind an iommu\n",
 		iommu ? " " : " not ");
 
-	arch_setup_dma_ops(dev, dma_start, end - dma_start + 1, iommu, coherent);
+	arch_setup_dma_ops(dev, coherent);
 
 	if (!iommu)
 		of_dma_set_restricted_buffer(dev, np);
include/linux/dma-map-ops.h +2 -4
index a52e508d1869..023f265eae2e 100644
@@ -426,11 +426,9 @@ bool arch_dma_unmap_sg_direct(struct device *dev, struct scatterlist *sg,
 #endif
 
 #ifdef CONFIG_ARCH_HAS_SETUP_DMA_OPS
-void arch_setup_dma_ops(struct device *dev, u64 dma_base, u64 size,
-		const struct iommu_ops *iommu, bool coherent);
+void arch_setup_dma_ops(struct device *dev, bool coherent);
 #else
-static inline void arch_setup_dma_ops(struct device *dev, u64 dma_base,
-		u64 size, const struct iommu_ops *iommu, bool coherent)
+static inline void arch_setup_dma_ops(struct device *dev, bool coherent)
 {
 }
 #endif /* CONFIG_ARCH_HAS_SETUP_DMA_OPS */
Signature · 1 line
2.39.2.101.g768bb238c484.dirty
#26 lore raw

Re: [PATCH 7/7] dma-mapping: Simplify arch_setup_dma_ops()

kernel test robot noticed the following build errors:

Hi Robin,

kernel test robot noticed the following build errors:

[auto build test ERROR on rafael-pm/linux-next]
[also build test ERROR on linus/master v6.7-rc3 next-20231129] [cannot apply to joro-iommu/next rafael-pm/acpi-bus rafael-pm/devprop] [If your patch is applied to the wrong git tree, kindly drop us a note. And when submitting patch, we suggest to use '--base' as documented in
https://git-scm.com/docs/git-format-patch#_base_tree_information]

url:    https://github.com/intel-lab-lkp/linux/commits/Robin-Murphy/OF-Simplify-DMA-range-calculations/20231130-024624
base:   https://git.kernel.org/pub/scm/linux/kernel/git/rafael/linux-pm.git linux-next
patch link:    https://lore.kernel.org/r/590a4a1b7d10fb9bb1c42ca6cd438e98e6cc94a7.1701268753.git.robin.murphy%40arm.com
patch subject: [PATCH 7/7] dma-mapping: Simplify arch_setup_dma_ops()
config: arm-aspeed_g5_defconfig (https://download.01.org/0day-ci/archive/20231130/[email protected]/config)
compiler: arm-linux-gnueabi-gcc (GCC) 13.2.0
reproduce (this is a W=1 build): (https://download.01.org/0day-ci/archive/20231130/[email protected]/reproduce)

If you fix the issue in a separate patch/commit (i.e. not just a new version of the same patch/commit), kindly add following tags
| Reported-by: kernel test robot <[email protected]>
| Closes: https://lore.kernel.org/oe-kbuild-all/[email protected]/

All errors (new ones prefixed by >>):

In file included from include/linux/thread_info.h:12,
from include/asm-generic/preempt.h:5,
from ./arch/arm/include/generated/asm/preempt.h:1,
from include/linux/preempt.h:79,
from include/linux/spinlock.h:56,
from include/linux/mmzone.h:8,
from include/linux/gfp.h:7,
from include/linux/umh.h:4,
from include/linux/kmod.h:9,
from include/linux/module.h:17,
from arch/arm/mm/dma-mapping.c:9:
include/linux/dma-direct.h: In function 'dma_range_map_min':
include/linux/limits.h:25:25: warning: conversion from 'long long unsigned int' to 'dma_addr_t' {aka 'unsigned int'} changes value from '18446744073709551615' to '4294967295' [-Woverflow]

      25 | #define U64_MAX         ((u64)~0ULL)
         |                         ^
   include/linux/dma-direct.h:54:26: note: in expansion of macro 'U64_MAX'
      54 |         dma_addr_t ret = U64_MAX;
         |                          ^~~~~~~
   arch/arm/mm/dma-mapping.c: In function 'arch_setup_dma_ops':
>> arch/arm/mm/dma-mapping.c:1784:17: error: too few arguments to function 'arm_setup_iommu_dma_ops'
    1784 |                 arm_setup_iommu_dma_ops(dev);
         |                 ^~~~~~~~~~~~~~~~~~~~~~~

arch/arm/mm/dma-mapping.c:1755:13: note: declared here
1755 | static void arm_setup_iommu_dma_ops(struct device *dev, u64 dma_base, u64 size,
| ^~~~~~~~~~~~~~~~~~~~~~~

vim +/arm_setup_iommu_dma_ops +1784 arch/arm/mm/dma-mapping.c

1763
1764 void arch_setup_dma_ops(struct device *dev, bool coherent)
1765 {
1766 /*
1767 * Due to legacy code that sets the ->dma_coherent flag from a bus
1768 * notifier we can't just assign coherent to the ->dma_coherent flag
1769 * here, but instead have to make sure we only set but never clear it
1770 * for now.
1771 */
1772 if (coherent)
1773 dev->dma_coherent = true;
1774
1775 /*
1776 * Don't override the dma_ops if they have already been set. Ideally
1777 * this should be the only location where dma_ops are set, remove this
1778 * check when all other callers of set_dma_ops will have disappeared.
1779 */
1780 if (dev->dma_ops)
1781 return;
1782
1783 if (device_iommu_mapped(dev))

> 1784			arm_setup_iommu_dma_ops(dev);

1785
1786 xen_setup_dma_ops(dev);
1787 dev->archdata.dma_ops_setup = true;
1788 }
1789

Signature · 2 lines
0-DAY CI Kernel Test Service
https://github.com/intel/lkp-tests/wiki
Reviewed-by on v1 7/7
#27 lore raw

Re: [PATCH 7/7] dma-mapping: Simplify arch_setup_dma_ops()

In reply to Robin Murphy
Quoted 3 lines · Robin Murphy

The dma_base, size and iommu arguments are only used by ARM, and can now easily be deduced from the device itself, so there's no need to pass them through the callchain as well.

This looks even better than the patch form Jason that only removed the iommu argument:

I wonder if it makes sense to also remove the coherent argument by setting up dev->dma_coherent in the caller. That would require a pretty careful audit as we're doing a few weird things in that area, though.

author maintainer
#28 lore raw

Re: [PATCH 7/7] dma-mapping: Simplify arch_setup_dma_ops()

Quoted 8 lines · Christoph Hellwig
Quoted 3 lines · Robin Murphy

The dma_base, size and iommu arguments are only used by ARM, and can now easily be deduced from the device itself, so there's no need to pass them through the callchain as well.

This looks even better than the patch form Jason that only removed the iommu argument:

Quoted 6 lines

I wonder if it makes sense to also remove the coherent argument by setting up dev->dma_coherent in the caller. That would require a pretty careful audit as we're doing a few weird things in that area, though.

Yeah, it crossed my mind too, but then I remembered we have at least the ARM stuff which may have already set a platform-specific value for dev->dma_coherent to take precedence over the firmware value, thus still needs to differentiate between the two at this point. Leaving the established argument in place seems neater IMO than having to go back to arch-specific mechanisms for that and any other similar tricks.

Thanks,
Robin.

#29 lore raw
In reply to Robin Murphy
Quoted 10 lines · Robin Murphy

Hi all,

Prompted by Jason's proposal[1], here's a first step towards truly unpicking the dma_configure vs. IOMMU mess. As I commented before, we have an awful lot of accumulated cruft and technical debt here making things more complicated than they need to be, and we already have hacks on top of hacks trying to work around it, so polishing those hacks even further is really not a desirable direction of travel. And I do know they're hacks, because I wrote most of them and still remember enough of the context of the time ;)

I quite like this, I was also looking at getting rid of those other parameters.

I wanted to take smaller steps because it is all pretty hairy.

One thing that still concerns me is if the FW data restricts the valid IOVA window that really should be reflected into the reserved ranges and not just dumped into the struct device for use by the DMA API.

Or, perhaps, viof/iommufd should be using the struct device data to generate some additional reserved ranges?

Either way, I would like to see the dma_iommu and the rest of the subsystem agree on what the valid IOVA ranges actually are.

Jason

author maintainer
#30 lore raw
Quoted 21 lines · Jason Gunthorpe
Quoted 10 lines · Robin Murphy

Hi all,

Prompted by Jason's proposal[1], here's a first step towards truly unpicking the dma_configure vs. IOMMU mess. As I commented before, we have an awful lot of accumulated cruft and technical debt here making things more complicated than they need to be, and we already have hacks on top of hacks trying to work around it, so polishing those hacks even further is really not a desirable direction of travel. And I do know they're hacks, because I wrote most of them and still remember enough of the context of the time ;)

I quite like this, I was also looking at getting rid of those other parameters.

I wanted to take smaller steps because it is all pretty hairy.

One thing that still concerns me is if the FW data restricts the valid IOVA window that really should be reflected into the reserved ranges and not just dumped into the struct device for use by the DMA API.

Quoted 5 lines

Or, perhaps, viof/iommufd should be using the struct device data to generate some additional reserved ranges?

Either way, I would like to see the dma_iommu and the rest of the subsystem agree on what the valid IOVA ranges actually are.

Note that there is some intentional divergence where iommu-dma reserves IOVAs matching PCI outbound windows because it knows it wants to avoid clashing with potential peer-to-peer addresses and doesn't want to have to get into the details of ACS redirect etc., but we don't expose those as generic reserved regions because they're firmly a property of the PCI host bridge, not of the IOMMU group (and more practically, because we did do so briefly and it made QEMU unhappy). I think there may also have been some degree of conclusion that it's not the IOMMU API's place to get in the way of other domain users trying to do weird P2P stuff if they really want to.

Another issue is that the generic dma_range_map strictly represents device-specific constraints which may not always be desirable or appropriate to apply to a whole group. There wasn't really a conscious decision as such, but it kind of works out as why we still only consider PCI's bridge->dma_ranges (which comes from the same underlying data), since we can at least assume every device behind a bridge accesses memory through that bridge and so inherits its restrictions. However I don't recall any conscious decision for inbound windows to only be considered for DMA domain reservations rather than for proper reserved regions - pretty sure that's just a case of that code being added in the place where it seemed to fit best at the time (because hey it's more host bridge windows and we already have a thing for host bridge windows...)

Thanks,
Robin.

#31 lore raw
Quoted 28 lines · Robin Murphy
Quoted 21 lines · Jason Gunthorpe
Quoted 10 lines · Robin Murphy

Hi all,

Prompted by Jason's proposal[1], here's a first step towards truly unpicking the dma_configure vs. IOMMU mess. As I commented before, we have an awful lot of accumulated cruft and technical debt here making things more complicated than they need to be, and we already have hacks on top of hacks trying to work around it, so polishing those hacks even further is really not a desirable direction of travel. And I do know they're hacks, because I wrote most of them and still remember enough of the context of the time ;)

I quite like this, I was also looking at getting rid of those other parameters.

I wanted to take smaller steps because it is all pretty hairy.

One thing that still concerns me is if the FW data restricts the valid IOVA window that really should be reflected into the reserved ranges and not just dumped into the struct device for use by the DMA API.

Quoted 5 lines

Or, perhaps, viof/iommufd should be using the struct device data to generate some additional reserved ranges?

Either way, I would like to see the dma_iommu and the rest of the subsystem agree on what the valid IOVA ranges actually are.

Quoted 9 lines

Note that there is some intentional divergence where iommu-dma reserves IOVAs matching PCI outbound windows because it knows it wants to avoid clashing with potential peer-to-peer addresses and doesn't want to have to get into the details of ACS redirect etc., but we don't expose those as generic reserved regions because they're firmly a property of the PCI host bridge, not of the IOMMU group (and more practically, because we did do so briefly and it made QEMU unhappy). I think there may also have been some degree of conclusion that it's not the IOMMU API's place to get in the way of other domain users trying to do weird P2P stuff if they really want to.

I'm not sure this is the fully correct conclusion - eg if today we take a NIC device on a non-ACS topology and run DPDK through VFIO it has a chance of failure because some IOVA simply cannot be used by DPDK for DMA at all.

qemu and kvm are a different situation that want different things. Eg it would want to identity map the PCI BAR spaces to the IOVA they are claiming.

It should still somehow carve out any other IOVA that is unusable due to guest-invisible ACS and reflect it through FW tables into the VM.

I'm starting to see people build non-ACS systems and want it to work with VFIO and I'm a little worried we have been too loose here.

Quoted 6 lines

bridge and so inherits its restrictions. However I don't recall any conscious decision for inbound windows to only be considered for DMA domain reservations rather than for proper reserved regions - pretty sure that's just a case of that code being added in the place where it seemed to fit best at the time (because hey it's more host bridge windows and we already have a thing for host bridge windows...)

Yeah, and I don't think anyone actually cared much..

At least as a step it would be nice if the DMA API only restrictions can come out as a special type of reserved region. Then the caller could decide if they want to follow them or not. iommufd could provide an opt-in API to DPDK that matches DMA API's safe IOVA allocator.

Jason