Linux-ARM-Kernel Archive on lore.kernel.org
 help / color / mirror / Atom feed
* [PATCH 0/6] Add support for Broadcom BCM2712 IOMMU driver (Raspberry Pi 5)
@ 2026-07-12 21:18 Daniel Drake
  2026-07-12 21:18 ` [PATCH 1/6] generic_pt: allow missing sw bit in DMA_INCOHERENT case Daniel Drake
                   ` (5 more replies)
  0 siblings, 6 replies; 15+ messages in thread
From: Daniel Drake @ 2026-07-12 21:18 UTC (permalink / raw)
  To: Joerg Roedel (AMD), Will Deacon, Robin Murphy, Rob Herring,
	Krzysztof Kozlowski, Conor Dooley, Florian Fainelli,
	Broadcom internal kernel review list, Daniel Drake
  Cc: iommu, linux-kernel, devicetree, linux-rpi-kernel,
	linux-arm-kernel, nick.hollinghurst, Jason Gunthorpe

Hi,

This series adds a driver for the Broadcom BCM2712 IOMMU found on
Raspberry Pi 5, and hooks up the display controller IOMMU for efficient
management of graphics memory. This is adapted from the downstream driver
from Raspberry Pi (original author Nick Hollinghurst), with main changes:
 - Implement the page table management using generic_pt
 - Implement brcm,iova-window and brcm,iommu-cache as standards-compliant
   DT property names, while maintaining compatibility with existing
   shipped RPi firmware
 - Drop the dma-iova-offset hack, used to work around some issue seen with
   dma-ranges. This will need to be investigated separately and solved
   properly. (It's not needed for display controller iommu support included
   here.)
 - Misc simplifications/standardisations/cleanups
 
The IOMMU works strictly with 4KB pages. This means that unfortunately
when the kernel is compiled with PAGE_SIZE=16KB (the Raspberry Pi 5
kernel default), 12KB is wasted in each page that is allocated for page
tables. I plan to address this in followup work.

It has been tested on Raspberry Pi 5 using a 3D-accelerated graphical
environment which causes plenty of IOMMU maps & unmaps.

Feedback and testing welcome!

---
Daniel Drake (6):
      generic_pt: allow missing sw bit in DMA_INCOHERENT case
      iommupt: allow full-table contiguous leaves in unit tests
      dt-bindings: iommu: Add Broadcom BCM2712 IOMMU
      iommu/generic_pt: Add Broadcom BCM2712 page table format
      iommu: Add Broadcom BCM2712 IOMMU driver
      arm64: dts: broadcom: bcm2712: Add GPU IOMMU and IOMMU cache nodes

 .../bindings/iommu/brcm,bcm2712-iommu.yaml         |  65 +++
 .../bindings/iommu/brcm,bcm2712-iommuc.yaml        |  35 ++
 arch/arm64/boot/dts/broadcom/bcm2712.dtsi          |  15 +
 drivers/iommu/Kconfig                              |  15 +
 drivers/iommu/Makefile                             |   1 +
 drivers/iommu/bcm2712-iommu-cache.c                |  73 +++
 drivers/iommu/bcm2712-iommu-cache.h                |   9 +
 drivers/iommu/bcm2712-iommu.c                      | 587 +++++++++++++++++++++
 drivers/iommu/generic_pt/.kunitconfig              |   1 +
 drivers/iommu/generic_pt/Kconfig                   |  10 +
 drivers/iommu/generic_pt/fmt/Makefile              |   2 +
 drivers/iommu/generic_pt/fmt/bcm2712.h             | 259 +++++++++
 drivers/iommu/generic_pt/fmt/defs_bcm2712.h        |  18 +
 drivers/iommu/generic_pt/fmt/iommu_bcm2712.c       |  10 +
 drivers/iommu/generic_pt/kunit_generic_pt.h        |  39 +-
 drivers/iommu/generic_pt/pt_fmt_defaults.h         |  13 +
 include/linux/generic_pt/common.h                  |   7 +
 include/linux/generic_pt/iommu.h                   |  13 +
 18 files changed, 1147 insertions(+), 25 deletions(-)
---
base-commit: f4fb100039e96211609dfc44fb24b9e4a8a0f2f9
change-id: 20260712-bcm2712-iommu-submit-2e09899e65c4

Best regards,
-- 
Daniel Drake <dan@reactivated.net>



^ permalink raw reply	[flat|nested] 15+ messages in thread

* [PATCH 1/6] generic_pt: allow missing sw bit in DMA_INCOHERENT case
  2026-07-12 21:18 [PATCH 0/6] Add support for Broadcom BCM2712 IOMMU driver (Raspberry Pi 5) Daniel Drake
@ 2026-07-12 21:18 ` Daniel Drake
  2026-07-12 22:00   ` Jason Gunthorpe
  2026-07-12 21:18 ` [PATCH 2/6] iommupt: allow full-table contiguous leaves in unit tests Daniel Drake
                   ` (4 subsequent siblings)
  5 siblings, 1 reply; 15+ messages in thread
From: Daniel Drake @ 2026-07-12 21:18 UTC (permalink / raw)
  To: Joerg Roedel (AMD), Will Deacon, Robin Murphy, Rob Herring,
	Krzysztof Kozlowski, Conor Dooley, Florian Fainelli,
	Broadcom internal kernel review list, Daniel Drake
  Cc: iommu, linux-kernel, devicetree, linux-rpi-kernel,
	linux-arm-kernel, nick.hollinghurst, Jason Gunthorpe

When working with a iommu with PT_FEAT_DMA_INCOHERENT set, generic_pt
will attempt to use a spare "SW" bit in the hardware page tables to
denote when a thread has flushed the CPU cache after modifying an entry.

This means that other threads know that they are not working with
cached/unflushed data, if they come across the same entry.

In the case where no SW bit is available, two things happen:

1. __map_range() defensively flushes every time it reads the PT.
   This ensures all data that may have just been manipulated by another
   thread gets flushed and made iommu-visible immediately.

2. An undefined reference to __pt_no_sw_bit() is created, causing a
   linker error in order to alert the developer that they are going
   to suffer a performance penalty in the previous point.

The BCM2712 IOMMU appears to be the first device supported by generic_pt
that hits this case. The prod to check for sw bits is appreciated,
but in this case there are no known bits that can be used, so we need
to acknowledge and accept the performance penalty without a linker error.

Adding an empty __pt_no_sw_bit() symbol to iommu_bcm2712 would be
problematic because if a second IOMMU were to have the same constraint,
this would result in a symbol clash in the global namespace. Also, the
kunit build creates and builds a 2nd variant of the same code and hits
this exact collision.

Add an opt-in PT_SW_BIT_NOT_PRESENT flag to avoid this. This prevents
the undefined reference from being created, allowing drivers like bcm2712
to acknowledge the situation and accept the performance penalty of the
defensive flushing.

Signed-off-by: Daniel Drake <dan@reactivated.net>
---
 drivers/iommu/generic_pt/pt_fmt_defaults.h | 13 +++++++++++++
 1 file changed, 13 insertions(+)

diff --git a/drivers/iommu/generic_pt/pt_fmt_defaults.h b/drivers/iommu/generic_pt/pt_fmt_defaults.h
index 69fb7c2314ca..cc57106c87d1 100644
--- a/drivers/iommu/generic_pt/pt_fmt_defaults.h
+++ b/drivers/iommu/generic_pt/pt_fmt_defaults.h
@@ -249,7 +249,20 @@ static inline unsigned int pt_max_sw_bit(struct pt_common *common)
 	return 0;
 }
 
+/*
+ * In the DMA_INCOHERENT case, if no SW bit has been defined, produce a linker
+ * error (undefined symbol __pt_no_sw_bit) to alert the developer that they
+ * will suffer a performance penalty due to defensive flushing. The driver
+ * can either implement pt_sw_bit (if supported by the hardware) for optimal
+ * performance, or otherwise set PT_SW_BIT_NOT_PRESENT to acknowledge the
+ * performance penalty.
+ */
+#ifdef PT_SW_BIT_NOT_PRESENT
+static inline void __pt_no_sw_bit(void) {}
+#else
 extern void __pt_no_sw_bit(void);
+#endif
+
 static inline bool pt_test_sw_bit_acquire(struct pt_state *pts,
 					  unsigned int bitnr)
 {

-- 
2.55.0



^ permalink raw reply related	[flat|nested] 15+ messages in thread

* [PATCH 2/6] iommupt: allow full-table contiguous leaves in unit tests
  2026-07-12 21:18 [PATCH 0/6] Add support for Broadcom BCM2712 IOMMU driver (Raspberry Pi 5) Daniel Drake
  2026-07-12 21:18 ` [PATCH 1/6] generic_pt: allow missing sw bit in DMA_INCOHERENT case Daniel Drake
@ 2026-07-12 21:18 ` Daniel Drake
  2026-07-12 22:02   ` Jason Gunthorpe
  2026-07-12 21:18 ` [PATCH 3/6] dt-bindings: iommu: Add Broadcom BCM2712 IOMMU Daniel Drake
                   ` (3 subsequent siblings)
  5 siblings, 1 reply; 15+ messages in thread
From: Daniel Drake @ 2026-07-12 21:18 UTC (permalink / raw)
  To: Joerg Roedel (AMD), Will Deacon, Robin Murphy, Rob Herring,
	Krzysztof Kozlowski, Conor Dooley, Florian Fainelli,
	Broadcom internal kernel review list, Daniel Drake
  Cc: iommu, linux-kernel, devicetree, linux-rpi-kernel,
	linux-arm-kernel, nick.hollinghurst, Jason Gunthorpe

The generic_pt tests currently assume that a format should never support
a contiguous page size that spans the entire mapping range of the table
it resides in.

This assumption breaks for BCM2712, which has a 2-level page table.
Level 1 entries cover 4MB of memory via their corresponding Level 0
table pages. There is a concept of a 4MB largepage, but that is
represented as a Level 1 entry pointing at a Level 0 table with the 1024
usual PTEs with an additional largepage hint applied (for TLB
optimization purposes). Unlike typical IOMMUs, it is not possible to have
the Level 1 entry point directly at a 4MB chunk without the need for
Level 0 mapping.

This BCM2712 format is well supported by generic_pt: SZ_4M can be
advertised as a possible size at Level 0 (so that the iommu core feeds
it 4MB-aligned mapping requests where possible), and drivers can also
advertise that *all* leaf entries are installed at Level 0. This indicates
that the tests are overly strict.

Transform the test to become a validation of the driver's routing intent:
for every advertised page size, check that pt_pgsz_lg2_to_level() maps that
size back to the table level under test. This is roughly what the original
tests were doing, but also allows the BCM2712 case to pass.

Signed-off-by: Daniel Drake <dan@reactivated.net>
---
 drivers/iommu/generic_pt/kunit_generic_pt.h | 39 +++++++++++------------------
 1 file changed, 14 insertions(+), 25 deletions(-)

diff --git a/drivers/iommu/generic_pt/kunit_generic_pt.h b/drivers/iommu/generic_pt/kunit_generic_pt.h
index ef2c90b6d6af..7f4e172ef08b 100644
--- a/drivers/iommu/generic_pt/kunit_generic_pt.h
+++ b/drivers/iommu/generic_pt/kunit_generic_pt.h
@@ -419,40 +419,35 @@ static void test_table_radix(struct kunit *test)
 	}
 }
 
-static unsigned int safe_pt_num_items_lg2(const struct pt_state *pts)
-{
-	struct pt_range top_range = pt_top_range(pts->range->common);
-	struct pt_state top_pts = pt_init_top(&top_range);
-
-	/*
-	 * Avoid calling pt_num_items_lg2() on the top, instead we can derive
-	 * the size of the top table from the top range.
-	 */
-	if (pts->level == top_range.top_level)
-		return ilog2(pt_range_to_end_index(&top_pts));
-	return pt_num_items_lg2(pts);
-}
-
 static void test_lvl_possible_sizes(struct kunit *test, struct pt_state *pts,
 				    void *arg)
 {
-	unsigned int num_items_lg2 = safe_pt_num_items_lg2(pts);
 	pt_vaddr_t pgsize_bitmap = pt_possible_sizes(pts);
 	/* Matches get_info() */
 	pt_vaddr_t limited_pgsize_bitmap =
 		log2_mod(pgsize_bitmap, pts->range->common->max_vasz_lg2 - 1);
 	unsigned int isz_lg2 = pt_table_item_lg2sz(pts);
+	unsigned int sz_lg2;
 
 	if (!pt_can_have_leaf(pts)) {
 		KUNIT_ASSERT_EQ(test, pgsize_bitmap, 0);
 		return;
 	}
 
-	/* No bits for sizes that would be outside this table */
+	/* A page size cannot be smaller than the span of a single entry. */
 	KUNIT_ASSERT_EQ(test, log2_mod(pgsize_bitmap, isz_lg2), 0);
-	KUNIT_ASSERT_EQ(
-		test,
-		fvalog2_div(limited_pgsize_bitmap, num_items_lg2 + isz_lg2), 0);
+
+	/*
+	 * Ensure every page size claimed to be supported at this level
+	 * actually routes back to this level
+	 */
+	for (sz_lg2 = 0; sz_lg2 < PT_VADDR_MAX_LG2; sz_lg2++) {
+		if (limited_pgsize_bitmap & log2_to_int(sz_lg2)) {
+			KUNIT_ASSERT_EQ(test,
+				pt_pgsz_lg2_to_level(pts->range->common, sz_lg2),
+				pts->level);
+		}
+	}
 
 	/*
 	 * Non contiguous must be supported. AMDv1 has a HW bug where it does
@@ -463,12 +458,6 @@ static void test_lvl_possible_sizes(struct kunit *test, struct pt_state *pts,
 		KUNIT_ASSERT_TRUE(test, pgsize_bitmap & log2_to_int(isz_lg2));
 	else
 		KUNIT_ASSERT_NE(test, pgsize_bitmap, 0);
-
-	/* A contiguous entry should not span the whole table */
-	if (num_items_lg2 + isz_lg2 != PT_VADDR_MAX_LG2)
-		KUNIT_ASSERT_FALSE(
-			test, limited_pgsize_bitmap &
-					log2_to_int(num_items_lg2 + isz_lg2));
 }
 
 static void test_entry_possible_sizes(struct kunit *test)

-- 
2.55.0



^ permalink raw reply related	[flat|nested] 15+ messages in thread

* [PATCH 3/6] dt-bindings: iommu: Add Broadcom BCM2712 IOMMU
  2026-07-12 21:18 [PATCH 0/6] Add support for Broadcom BCM2712 IOMMU driver (Raspberry Pi 5) Daniel Drake
  2026-07-12 21:18 ` [PATCH 1/6] generic_pt: allow missing sw bit in DMA_INCOHERENT case Daniel Drake
  2026-07-12 21:18 ` [PATCH 2/6] iommupt: allow full-table contiguous leaves in unit tests Daniel Drake
@ 2026-07-12 21:18 ` Daniel Drake
  2026-07-13  9:16   ` Krzysztof Kozlowski
  2026-07-12 21:18 ` [PATCH 4/6] iommu/generic_pt: Add Broadcom BCM2712 page table format Daniel Drake
                   ` (2 subsequent siblings)
  5 siblings, 1 reply; 15+ messages in thread
From: Daniel Drake @ 2026-07-12 21:18 UTC (permalink / raw)
  To: Joerg Roedel (AMD), Will Deacon, Robin Murphy, Rob Herring,
	Krzysztof Kozlowski, Conor Dooley, Florian Fainelli,
	Broadcom internal kernel review list, Daniel Drake
  Cc: iommu, linux-kernel, devicetree, linux-rpi-kernel,
	linux-arm-kernel, nick.hollinghurst, Jason Gunthorpe

Add bindings for the Broadcom BCM2712 IOMMU and its shared TLB cache.

Include compatibility with the cache phandle used in existing firmware
shipped on Raspberry Pi 5, and also allow the iova-window to be omitted,
which similarly will maintain compatibility with existing RPi5 firmware.

Signed-off-by: Daniel Drake <dan@reactivated.net>
---
 .../bindings/iommu/brcm,bcm2712-iommu.yaml         | 65 ++++++++++++++++++++++
 .../bindings/iommu/brcm,bcm2712-iommuc.yaml        | 35 ++++++++++++
 2 files changed, 100 insertions(+)

diff --git a/Documentation/devicetree/bindings/iommu/brcm,bcm2712-iommu.yaml b/Documentation/devicetree/bindings/iommu/brcm,bcm2712-iommu.yaml
new file mode 100644
index 000000000000..0d91c513afc4
--- /dev/null
+++ b/Documentation/devicetree/bindings/iommu/brcm,bcm2712-iommu.yaml
@@ -0,0 +1,65 @@
+# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause)
+%YAML 1.2
+---
+$id: http://devicetree.org/schemas/iommu/brcm,bcm2712-iommu.yaml#
+$schema: http://devicetree.org/meta-schemas/core.yaml#
+
+title: Broadcom BCM2712 IOMMU
+
+maintainers:
+  - Daniel Drake <dan@reactivated.net>
+
+description: |
+  The BCM2712 IOMMU provides address translation for various hardware blocks
+  on the BCM2712 SoC, such as the VC6 display pipeline.
+
+properties:
+  compatible:
+    const: brcm,bcm2712-iommu
+
+  reg:
+    maxItems: 1
+
+  '#iommu-cells':
+    const: 0
+
+  brcm,iommu-cache:
+    $ref: /schemas/types.yaml#/definitions/phandle
+    description: Phandle to the shared IOMMU cache (IOMMUC).
+
+  cache:
+    $ref: /schemas/types.yaml#/definitions/phandle
+    description: Deprecated. Use brcm,iommu-cache instead.
+    deprecated: true
+
+  brcm,iova-window:
+    $ref: /schemas/types.yaml#/definitions/uint64-array
+    description: |
+      An array of two 64-bit integers specifying the IOVA aperture start
+      address and the aperture size. IOMMU mappings will be created inside
+      this aperture. All preceding address space is in identity/bypass mode.
+      The aperture start address must be 4GB-aligned.
+      Should be considered a required property, but it is technically optional
+      in order to maintain compatibility with historical firmware versions.
+      If absent, defaults to a 4GB window at 40GiB (0xa00000000).
+    items:
+      - description: IOVA window start address
+      - description: IOVA window size
+
+required:
+  - compatible
+  - reg
+  - '#iommu-cells'
+  - brcm,iommu-cache
+
+additionalProperties: false
+
+examples:
+  - |
+    iommu@5200 {
+        compatible = "brcm,bcm2712-iommu";
+        reg = <0x5200 0x80>;
+        brcm,iommu-cache = <&iommuc>;
+        #iommu-cells = <0>;
+        brcm,iova-window = /bits/ 64 <0xa00000000 0x100000000>;
+    };
diff --git a/Documentation/devicetree/bindings/iommu/brcm,bcm2712-iommuc.yaml b/Documentation/devicetree/bindings/iommu/brcm,bcm2712-iommuc.yaml
new file mode 100644
index 000000000000..f22e9be2c473
--- /dev/null
+++ b/Documentation/devicetree/bindings/iommu/brcm,bcm2712-iommuc.yaml
@@ -0,0 +1,35 @@
+# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause)
+%YAML 1.2
+---
+$id: http://devicetree.org/schemas/iommu/brcm,bcm2712-iommuc.yaml#
+$schema: http://devicetree.org/meta-schemas/core.yaml#
+
+title: Broadcom BCM2712 Shared IOMMU Cache (IOMMUC)
+
+maintainers:
+  - Daniel Drake <dan@reactivated.net>
+
+description: |
+  The BCM2712 IOMMU Cache (IOMMUC) provides a shared Translation Lookaside
+  Buffer (TLB) cache used by the primary BCM2712 IOMMUs to speed up address
+  translations.
+
+properties:
+  compatible:
+    const: brcm,bcm2712-iommuc
+
+  reg:
+    maxItems: 1
+
+required:
+  - compatible
+  - reg
+
+additionalProperties: false
+
+examples:
+  - |
+    iommuc: iommu-cache@1001000 {
+        compatible = "brcm,bcm2712-iommuc";
+        reg = <0x1001000 0x100>;
+    };

-- 
2.55.0



^ permalink raw reply related	[flat|nested] 15+ messages in thread

* [PATCH 4/6] iommu/generic_pt: Add Broadcom BCM2712 page table format
  2026-07-12 21:18 [PATCH 0/6] Add support for Broadcom BCM2712 IOMMU driver (Raspberry Pi 5) Daniel Drake
                   ` (2 preceding siblings ...)
  2026-07-12 21:18 ` [PATCH 3/6] dt-bindings: iommu: Add Broadcom BCM2712 IOMMU Daniel Drake
@ 2026-07-12 21:18 ` Daniel Drake
  2026-07-12 22:05   ` Jason Gunthorpe
  2026-07-13 10:02   ` Nick Hollinghurst
  2026-07-12 21:18 ` [PATCH 5/6] iommu: Add Broadcom BCM2712 IOMMU driver Daniel Drake
  2026-07-12 21:18 ` [PATCH 6/6] arm64: dts: broadcom: bcm2712: Add GPU IOMMU and IOMMU cache nodes Daniel Drake
  5 siblings, 2 replies; 15+ messages in thread
From: Daniel Drake @ 2026-07-12 21:18 UTC (permalink / raw)
  To: Joerg Roedel (AMD), Will Deacon, Robin Murphy, Rob Herring,
	Krzysztof Kozlowski, Conor Dooley, Florian Fainelli,
	Broadcom internal kernel review list, Daniel Drake
  Cc: iommu, linux-kernel, devicetree, linux-rpi-kernel,
	linux-arm-kernel, nick.hollinghurst, Jason Gunthorpe

The BCM2712 IOMMU implements a 2-level page table format. It is relatively
simple, with one unusual aspect: leaf entries can only be installed at
Level 0.

Adapted from Raspberry Pi's downstream bcm2712-iommu driver (original
author Nick Hollinghurst).

Signed-off-by: Daniel Drake <dan@reactivated.net>
---
 drivers/iommu/generic_pt/.kunitconfig        |   1 +
 drivers/iommu/generic_pt/Kconfig             |  10 ++
 drivers/iommu/generic_pt/fmt/Makefile        |   2 +
 drivers/iommu/generic_pt/fmt/bcm2712.h       | 259 +++++++++++++++++++++++++++
 drivers/iommu/generic_pt/fmt/defs_bcm2712.h  |  18 ++
 drivers/iommu/generic_pt/fmt/iommu_bcm2712.c |  10 ++
 include/linux/generic_pt/common.h            |   7 +
 include/linux/generic_pt/iommu.h             |  13 ++
 8 files changed, 320 insertions(+)

diff --git a/drivers/iommu/generic_pt/.kunitconfig b/drivers/iommu/generic_pt/.kunitconfig
index 0bb98fe581fe..a3da74328f12 100644
--- a/drivers/iommu/generic_pt/.kunitconfig
+++ b/drivers/iommu/generic_pt/.kunitconfig
@@ -7,6 +7,7 @@ CONFIG_IOMMU_PT_AMDV1=y
 CONFIG_IOMMU_PT_VTDSS=y
 CONFIG_IOMMU_PT_RISCV64=y
 CONFIG_IOMMU_PT_X86_64=y
+CONFIG_IOMMU_PT_BCM2712=y
 CONFIG_IOMMU_PT_KUNIT_TEST=y
 
 CONFIG_IOMMUFD=y
diff --git a/drivers/iommu/generic_pt/Kconfig b/drivers/iommu/generic_pt/Kconfig
index f4ed1add58b7..5c01cebb365f 100644
--- a/drivers/iommu/generic_pt/Kconfig
+++ b/drivers/iommu/generic_pt/Kconfig
@@ -72,6 +72,16 @@ config IOMMU_PT_X86_64
 
 	  Selected automatically by an IOMMU driver that uses this format.
 
+config IOMMU_PT_BCM2712
+	tristate "IOMMU page table for Broadcom BCM2712"
+	depends on !GENERIC_ATOMIC64 # for cmpxchg64
+	help
+	  iommu_domain implementation for the Broadcom BCM2712 IOMMU found on
+	  Raspberry Pi 5. It supports 4K page sizes over a 2-level page table
+	  format.
+
+	  Selected automatically by an IOMMU driver that uses this format.
+
 config IOMMU_PT_KUNIT_TEST
 	tristate "IOMMU Page Table KUnit Test" if !KUNIT_ALL_TESTS
 	depends on KUNIT
diff --git a/drivers/iommu/generic_pt/fmt/Makefile b/drivers/iommu/generic_pt/fmt/Makefile
index ea024d582594..82ca3c36823d 100644
--- a/drivers/iommu/generic_pt/fmt/Makefile
+++ b/drivers/iommu/generic_pt/fmt/Makefile
@@ -9,6 +9,8 @@ iommu_pt_fmt-$(CONFIG_IOMMU_PT_RISCV64) += riscv64
 
 iommu_pt_fmt-$(CONFIG_IOMMU_PT_X86_64) += x86_64
 
+iommu_pt_fmt-$(CONFIG_IOMMU_PT_BCM2712) += bcm2712
+
 IOMMU_PT_KUNIT_TEST :=
 define create_format
 obj-$(2) += iommu_$(1).o
diff --git a/drivers/iommu/generic_pt/fmt/bcm2712.h b/drivers/iommu/generic_pt/fmt/bcm2712.h
new file mode 100644
index 000000000000..e62d59181bfe
--- /dev/null
+++ b/drivers/iommu/generic_pt/fmt/bcm2712.h
@@ -0,0 +1,259 @@
+/* SPDX-License-Identifier: GPL-2.0-only */
+/*
+ * Copyright (c) 2026 Daniel Drake
+ *
+ * BCM2712 IOMMU simple two level page table
+ */
+#ifndef __GENERIC_PT_FMT_BCM2712_H
+#define __GENERIC_PT_FMT_BCM2712_H
+
+#include "defs_bcm2712.h"
+#include "../pt_defs.h"
+
+#include <linux/bitfield.h>
+#include <linux/container_of.h>
+#include <linux/log2.h>
+#include <linux/sizes.h>
+
+enum {
+	/* Hardware provides a two-level page table */
+	PT_MAX_TOP_LEVEL = 1,
+
+	/* Hardware page size is strictly 4kb */
+	PT_GRANULE_LG2SZ = ilog2(SZ_4K),
+	PT_TABLEMEM_LG2SZ = PT_GRANULE_LG2SZ,
+
+	/* Table entries and leaf entries are 32 bits */
+	PT_ITEM_WORD_SIZE = sizeof(pt_bcm2712_entry_t),
+
+	/* Leaf entries encode a 28-bit PFN */
+	PT_MAX_OUTPUT_ADDRESS_LG2 = 28 + PT_GRANULE_LG2SZ,
+
+	/*
+	 * For simplicity, only manage mappings within an address space of
+	 * exactly 4GB. This is because our Level 1 directory page will be 4kb
+	 * in the smallest case, permitting 1024 entries pointing at Level 0
+	 * pages, each permitting 4mb of mapped memory.
+	 */
+	PT_MAX_VA_ADDRESS_LG2 = ilog2(SZ_4G),
+
+	/* Level 1 base address is programmed as a 32-bit PFN */
+	PT_TOP_PHYS_MASK = GENMASK_ULL(31 + PT_GRANULE_LG2SZ, PT_GRANULE_LG2SZ),
+};
+
+/* PTE bits */
+enum {
+	BCM2712PT_VALID = BIT(28),
+	BCM2712PT_WRITE = BIT(29),
+	BCM2712PT_PAGESIZE = GENMASK(31, 30),
+	BCM2712PT_PFN = GENMASK(27, 0),
+};
+
+#define common_to_bcm2712pt(common_ptr) \
+	container_of_const(common_ptr, struct pt_bcm2712, common)
+#define to_bcm2712pt(pts) common_to_bcm2712pt((pts)->range->common)
+
+static inline pt_oaddr_t bcm2712pt_table_pa(const struct pt_state *pts)
+{
+	return oalog2_mul(FIELD_GET(BCM2712PT_PFN, pts->entry),
+			  PT_GRANULE_LG2SZ);
+}
+#define pt_table_pa bcm2712pt_table_pa
+#define pt_item_oa bcm2712pt_table_pa
+
+/* All leaf entries are installed at level 0 */
+static inline bool bcm2712pt_can_have_leaf(const struct pt_state *pts)
+{
+	return pts->level == 0;
+}
+#define pt_can_have_leaf bcm2712pt_can_have_leaf
+
+/* All leaf entries are installed at level 0 */
+static inline unsigned int bcm2712pt_pgsz_lg2_to_level(struct pt_common *common,
+						       unsigned int pgsize_lg2)
+{
+	return 0;
+}
+#define pt_pgsz_lg2_to_level bcm2712pt_pgsz_lg2_to_level
+
+static inline pt_vaddr_t bcm2712pt_possible_sizes(const struct pt_state *pts)
+{
+	struct pt_bcm2712 *table = common_to_bcm2712pt(pts->range->common);
+
+	if (!bcm2712pt_can_have_leaf(pts))
+		return 0;
+
+	return table->pgsize_bitmap;
+}
+#define pt_possible_sizes bcm2712pt_possible_sizes
+
+static inline unsigned int
+bcm2712pt_entry_num_contig_lg2(const struct pt_state *pts)
+{
+	struct pt_bcm2712 *table = common_to_bcm2712pt(pts->range->common);
+	u32 pgsz = FIELD_GET(BCM2712PT_PAGESIZE, pts->entry);
+
+	if (pts->level != 0 || !(pts->entry & BCM2712PT_VALID))
+		return 0;
+
+	/*
+	 * Hardware only supports contiguous mapping (really just hinting) via
+	 * PAGESIZE hints on each leaf entry.
+	 */
+	if (pgsz == 3)
+		return ilog2(SZ_4M) - PT_GRANULE_LG2SZ;
+	else if (pgsz == 2 && table->superpage_lg2)
+		return table->superpage_lg2 - PT_GRANULE_LG2SZ;
+	else if (pgsz == 1 && table->bigpage_lg2)
+		return table->bigpage_lg2 - PT_GRANULE_LG2SZ;
+	return 0;
+}
+#define pt_entry_num_contig_lg2 bcm2712pt_entry_num_contig_lg2
+
+static inline unsigned int bcm2712pt_num_items_lg2(const struct pt_state *pts)
+{
+	return PT_TABLEMEM_LG2SZ - ilog2(PT_ITEM_WORD_SIZE);
+}
+#define pt_num_items_lg2 bcm2712pt_num_items_lg2
+
+static inline enum pt_entry_type bcm2712pt_load_entry_raw(struct pt_state *pts)
+{
+	const pt_bcm2712_entry_t *tablep =
+		pt_cur_table(pts, pt_bcm2712_entry_t);
+
+	pts->entry = READ_ONCE(tablep[pts->index]);
+	if (!(pts->entry & BCM2712PT_VALID))
+		return PT_ENTRY_EMPTY;
+	return pts->level == 0 ? PT_ENTRY_OA : PT_ENTRY_TABLE;
+}
+#define pt_load_entry_raw bcm2712pt_load_entry_raw
+
+static inline void
+bcm2712pt_install_leaf_entry(struct pt_state *pts, pt_oaddr_t oa,
+			     unsigned int oasz_lg2,
+			     const struct pt_write_attrs *attrs)
+{
+	pt_bcm2712_entry_t *tablep = pt_cur_table(pts, pt_bcm2712_entry_t);
+	pt_bcm2712_entry_t entry;
+
+	if (!pt_check_install_leaf_args(pts, oa, oasz_lg2))
+		return;
+
+	entry = BCM2712PT_VALID | attrs->descriptor_bits |
+		FIELD_PREP(BCM2712PT_PFN, oalog2_div(oa, PT_GRANULE_LG2SZ));
+
+	if (oasz_lg2 == PT_GRANULE_LG2SZ) {
+		WRITE_ONCE(tablep[pts->index], entry);
+		pts->entry = entry;
+	} else {
+		struct pt_bcm2712 *table =
+			common_to_bcm2712pt(pts->range->common);
+		u32 *end;
+
+		tablep += pts->index;
+		end = tablep + log2_to_int(oasz_lg2 - PT_GRANULE_LG2SZ);
+
+		/*
+		 * Leaf entries can contain hints indicating multi-page
+		 * contiguous mappings, presumably to permit TLB optimization
+		 */
+		if (oasz_lg2 == ilog2(SZ_4M))
+			entry |= FIELD_PREP(BCM2712PT_PAGESIZE, 3);
+		else if (oasz_lg2 == table->superpage_lg2)
+			entry |= FIELD_PREP(BCM2712PT_PAGESIZE, 2);
+		else if (oasz_lg2 == table->bigpage_lg2)
+			entry |= FIELD_PREP(BCM2712PT_PAGESIZE, 1);
+
+		pts->entry = entry;
+		for (; tablep != end; tablep++, entry++)
+			WRITE_ONCE(*tablep, entry);
+	}
+}
+#define pt_install_leaf_entry bcm2712pt_install_leaf_entry
+
+static inline bool bcm2712pt_install_table(struct pt_state *pts,
+					   pt_oaddr_t table_pa,
+					   const struct pt_write_attrs *attrs)
+{
+	pt_bcm2712_entry_t entry =
+		BCM2712PT_VALID |
+		FIELD_PREP(BCM2712PT_PFN,
+			   oalog2_div(table_pa, PT_GRANULE_LG2SZ));
+
+	return pt_table_install32(pts, entry);
+}
+#define pt_install_table bcm2712pt_install_table
+
+static inline void bcm2712pt_attr_from_entry(const struct pt_state *pts,
+					     struct pt_write_attrs *attrs)
+{
+	attrs->descriptor_bits = pts->entry & BCM2712PT_WRITE;
+}
+#define pt_attr_from_entry bcm2712pt_attr_from_entry
+
+/* --- iommu */
+#include <linux/generic_pt/iommu.h>
+#include <linux/iommu.h>
+
+#define pt_iommu_table pt_iommu_bcm2712
+
+/* The common struct is in the per-format common struct */
+static inline struct pt_common *common_from_iommu(struct pt_iommu *iommu_table)
+{
+	return &container_of(iommu_table, struct pt_iommu_table, iommu)
+			->bcm2712pt.common;
+}
+
+static inline struct pt_iommu *iommu_from_common(struct pt_common *common)
+{
+	return &container_of(common, struct pt_iommu_table, bcm2712pt.common)
+			->iommu;
+}
+
+static inline int bcm2712_pt_iommu_set_prot(struct pt_common *common,
+					    struct pt_write_attrs *attrs,
+					    unsigned int iommu_prot)
+{
+	attrs->descriptor_bits = 0;
+	if (iommu_prot & IOMMU_WRITE)
+		attrs->descriptor_bits |= BCM2712PT_WRITE;
+	return 0;
+}
+#define pt_iommu_set_prot bcm2712_pt_iommu_set_prot
+
+static inline int bcm2712_pt_fmt_init(struct pt_iommu_table *fmt_table,
+				      const struct pt_iommu_bcm2712_cfg *cfg)
+{
+	fmt_table->bcm2712pt.pgsize_bitmap = cfg->pgsize_bitmap;
+	fmt_table->bcm2712pt.bigpage_lg2 = cfg->bigpage_lg2;
+	fmt_table->bcm2712pt.superpage_lg2 = cfg->superpage_lg2;
+	pt_top_set_level(&fmt_table->bcm2712pt.common, PT_MAX_TOP_LEVEL);
+	return 0;
+}
+#define pt_iommu_fmt_init bcm2712_pt_fmt_init
+
+static inline void
+bcm2712pt_iommu_fmt_hw_info(struct pt_iommu_bcm2712 *table,
+			    const struct pt_range *top_range,
+			    struct pt_iommu_bcm2712_hw_info *info)
+{
+	info->pt_base = virt_to_phys(top_range->top_table);
+	PT_WARN_ON(info->pt_base & ~PT_TOP_PHYS_MASK);
+}
+#define pt_iommu_fmt_hw_info bcm2712pt_iommu_fmt_hw_info
+
+#if defined(GENERIC_PT_KUNIT)
+static const struct pt_iommu_bcm2712_cfg bcm2712_kunit_fmt_cfgs[] = {
+	[0] = {
+		.common.hw_max_vasz_lg2 = 32,
+		.common.hw_max_oasz_lg2 = 40,
+		.pgsize_bitmap = SZ_4K | SZ_64K | SZ_1M | SZ_4M,
+		.bigpage_lg2 = 16,
+		.superpage_lg2 = 20,
+	},
+};
+#define kunit_fmt_cfgs bcm2712_kunit_fmt_cfgs
+enum { KUNIT_FMT_FEATURES = 0 };
+#endif
+
+#endif
diff --git a/drivers/iommu/generic_pt/fmt/defs_bcm2712.h b/drivers/iommu/generic_pt/fmt/defs_bcm2712.h
new file mode 100644
index 000000000000..8d87f7ea25f5
--- /dev/null
+++ b/drivers/iommu/generic_pt/fmt/defs_bcm2712.h
@@ -0,0 +1,18 @@
+/* SPDX-License-Identifier: GPL-2.0-only */
+#ifndef __GENERIC_PT_FMT_DEFS_BCM2712_H
+#define __GENERIC_PT_FMT_DEFS_BCM2712_H
+
+#include <linux/generic_pt/common.h>
+#include <linux/types.h>
+
+typedef u32 pt_bcm2712_entry_t;
+typedef pt_bcm2712_entry_t pt_vaddr_t;
+typedef u64 pt_oaddr_t;
+
+struct bcm2712pt_write_attrs {
+	pt_bcm2712_entry_t descriptor_bits;
+	gfp_t gfp;
+};
+#define pt_write_attrs bcm2712pt_write_attrs
+
+#endif
diff --git a/drivers/iommu/generic_pt/fmt/iommu_bcm2712.c b/drivers/iommu/generic_pt/fmt/iommu_bcm2712.c
new file mode 100644
index 000000000000..059c34360785
--- /dev/null
+++ b/drivers/iommu/generic_pt/fmt/iommu_bcm2712.c
@@ -0,0 +1,10 @@
+// SPDX-License-Identifier: GPL-2.0-only
+#define PT_FMT bcm2712
+#define PT_SUPPORTED_FEATURES BIT(PT_FEAT_DMA_INCOHERENT)
+#define PT_FORCE_ENABLED_FEATURES 0
+
+/* BCM2712 has no spare software bits for tracking cache flushes */
+#define PT_SW_BIT_NOT_PRESENT
+
+#include <linux/generic_pt/iommu.h>
+#include "iommu_template.h"
diff --git a/include/linux/generic_pt/common.h b/include/linux/generic_pt/common.h
index 07ef1c8341a4..8418e7fc621e 100644
--- a/include/linux/generic_pt/common.h
+++ b/include/linux/generic_pt/common.h
@@ -204,6 +204,13 @@ struct pt_x86_64 {
 	struct pt_common common;
 };
 
+struct pt_bcm2712 {
+	struct pt_common common;
+	u64 pgsize_bitmap;
+	u8 bigpage_lg2;
+	u8 superpage_lg2;
+};
+
 enum {
 	/*
 	 * The memory backing the tables is encrypted. Use __sme_set() to adjust
diff --git a/include/linux/generic_pt/iommu.h b/include/linux/generic_pt/iommu.h
index dd0edd02a48a..c4ee54561373 100644
--- a/include/linux/generic_pt/iommu.h
+++ b/include/linux/generic_pt/iommu.h
@@ -346,6 +346,19 @@ struct pt_iommu_x86_64_hw_info {
 
 IOMMU_FORMAT(x86_64, x86_64_pt);
 
+struct pt_iommu_bcm2712_cfg {
+	struct pt_iommu_cfg common;
+	u64 pgsize_bitmap;
+	u8 bigpage_lg2;
+	u8 superpage_lg2;
+};
+
+struct pt_iommu_bcm2712_hw_info {
+	phys_addr_t pt_base;
+};
+
+IOMMU_FORMAT(bcm2712, bcm2712pt);
+
 #undef IOMMU_PROTOTYPES
 #undef IOMMU_FORMAT
 #endif

-- 
2.55.0



^ permalink raw reply related	[flat|nested] 15+ messages in thread

* [PATCH 5/6] iommu: Add Broadcom BCM2712 IOMMU driver
  2026-07-12 21:18 [PATCH 0/6] Add support for Broadcom BCM2712 IOMMU driver (Raspberry Pi 5) Daniel Drake
                   ` (3 preceding siblings ...)
  2026-07-12 21:18 ` [PATCH 4/6] iommu/generic_pt: Add Broadcom BCM2712 page table format Daniel Drake
@ 2026-07-12 21:18 ` Daniel Drake
  2026-07-12 22:11   ` Jason Gunthorpe
  2026-07-12 21:18 ` [PATCH 6/6] arm64: dts: broadcom: bcm2712: Add GPU IOMMU and IOMMU cache nodes Daniel Drake
  5 siblings, 1 reply; 15+ messages in thread
From: Daniel Drake @ 2026-07-12 21:18 UTC (permalink / raw)
  To: Joerg Roedel (AMD), Will Deacon, Robin Murphy, Rob Herring,
	Krzysztof Kozlowski, Conor Dooley, Florian Fainelli,
	Broadcom internal kernel review list, Daniel Drake
  Cc: iommu, linux-kernel, devicetree, linux-rpi-kernel,
	linux-arm-kernel, nick.hollinghurst, Jason Gunthorpe

This IOMMU translates memory access requests for the VC6 display
pipeline and various multimedia devices in the Broadcom BCM2712 SoC used
on Raspberry Pi 5.

This driver places a 4GB aperture at a high memory address in which the
IOMMU mappings are operated. All addresses outside this window bypass the
translation logic and give direct access to physical memory.

Also includes support for the IOMMU cache, which is shared between
all the IOMMU devices found on the SoC.

Adapted from Raspberry Pi's downstream bcm2712-iommu driver
(original author Nick Hollinghurst).

Signed-off-by: Daniel Drake <dan@reactivated.net>
---
 drivers/iommu/Kconfig               |  15 +
 drivers/iommu/Makefile              |   1 +
 drivers/iommu/bcm2712-iommu-cache.c |  73 +++++
 drivers/iommu/bcm2712-iommu-cache.h |   9 +
 drivers/iommu/bcm2712-iommu.c       | 587 ++++++++++++++++++++++++++++++++++++
 5 files changed, 685 insertions(+)

diff --git a/drivers/iommu/Kconfig b/drivers/iommu/Kconfig
index 6e07bd69467a..f2c8788158b0 100644
--- a/drivers/iommu/Kconfig
+++ b/drivers/iommu/Kconfig
@@ -386,6 +386,21 @@ config VSI_IOMMU
 	  Say Y here if you want to use this IOMMU in front of these
 	  hardware blocks.
 
+config BCM2712_IOMMU
+	bool "BCM2712 IOMMU driver"
+	depends on (ARCH_BCM && ARM64) || COMPILE_TEST
+	select IOMMU_API
+	select GENERIC_PT
+	select IOMMU_PT
+	select IOMMU_PT_BCM2712
+	help
+	  Support for IOMMU on BCM2712 SoC. This IOMMU can be used by the
+	  display controller and various multimedia devices to perform
+	  efficient memory management.
+
+	  Say Y here if you want to use this IOMMU in front of these
+	  hardware blocks.
+
 config IOMMU_DEBUG_PAGEALLOC
 	bool "Debug IOMMU mappings against page allocations"
 	depends on DEBUG_PAGEALLOC && IOMMU_API && PAGE_EXTENSION
diff --git a/drivers/iommu/Makefile b/drivers/iommu/Makefile
index 2f05725eaab1..29a26d2a3af1 100644
--- a/drivers/iommu/Makefile
+++ b/drivers/iommu/Makefile
@@ -37,4 +37,5 @@ obj-$(CONFIG_IOMMU_IOPF) += io-pgfault.o
 obj-$(CONFIG_SPRD_IOMMU) += sprd-iommu.o
 obj-$(CONFIG_APPLE_DART) += apple-dart.o
 obj-$(CONFIG_VSI_IOMMU) += vsi-iommu.o
+obj-$(CONFIG_BCM2712_IOMMU) += bcm2712-iommu.o bcm2712-iommu-cache.o
 obj-$(CONFIG_IOMMU_DEBUG_PAGEALLOC) += iommu-debug-pagealloc.o
diff --git a/drivers/iommu/bcm2712-iommu-cache.c b/drivers/iommu/bcm2712-iommu-cache.c
new file mode 100644
index 000000000000..4a248e462e30
--- /dev/null
+++ b/drivers/iommu/bcm2712-iommu-cache.c
@@ -0,0 +1,73 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * IOMMU driver for BCM2712 TLB cache
+ *
+ * Copyright (c) 2023 Raspberry Pi Ltd.
+ */
+
+#include <linux/err.h>
+#include <linux/of_platform.h>
+#include <linux/platform_device.h>
+#include <linux/spinlock.h>
+#include <linux/iopoll.h>
+
+#include "bcm2712-iommu-cache.h"
+
+struct bcm2712_iommu_cache {
+	spinlock_t hw_lock;
+	void __iomem *reg_base;
+};
+
+#define MMUC_CONTROL_ENABLE   1
+#define MMUC_CONTROL_FLUSH    2
+#define MMUC_CONTROL_FLUSHING 4
+
+void bcm2712_iommu_cache_flush(struct bcm2712_iommu_cache *cache)
+{
+	unsigned long flags;
+	u32 val;
+
+	spin_lock_irqsave(&cache->hw_lock, flags);
+
+	/* Enable and flush the TLB cache */
+	writel(MMUC_CONTROL_ENABLE | MMUC_CONTROL_FLUSH, cache->reg_base);
+
+	/* Wait for flush to complete: it should be very quick */
+	readl_poll_timeout_atomic(cache->reg_base, val,
+				  !(val & MMUC_CONTROL_FLUSHING), 0, 1000);
+
+	spin_unlock_irqrestore(&cache->hw_lock, flags);
+}
+
+static int bcm2712_iommu_cache_probe(struct platform_device *pdev)
+{
+	struct bcm2712_iommu_cache *cache;
+
+	cache = devm_kzalloc(&pdev->dev, sizeof(*cache), GFP_KERNEL);
+	if (!cache)
+		return -ENOMEM;
+
+	platform_set_drvdata(pdev, cache);
+	spin_lock_init(&cache->hw_lock);
+
+	cache->reg_base = devm_platform_ioremap_resource(pdev, 0);
+	if (IS_ERR(cache->reg_base))
+		return PTR_ERR(cache->reg_base);
+
+	return 0;
+}
+
+static const struct of_device_id bcm2712_iommu_cache_of_match[] = {
+	{ .compatible = "brcm,bcm2712-iommuc" },
+	{ /* sentinel */ },
+};
+
+static struct platform_driver bcm2712_iommu_cache_driver = {
+	.probe = bcm2712_iommu_cache_probe,
+	.driver = {
+		.name = "bcm2712-iommu-cache",
+		.of_match_table = bcm2712_iommu_cache_of_match,
+		.suppress_bind_attrs = true,
+	},
+};
+builtin_platform_driver(bcm2712_iommu_cache_driver);
diff --git a/drivers/iommu/bcm2712-iommu-cache.h b/drivers/iommu/bcm2712-iommu-cache.h
new file mode 100644
index 000000000000..d2f7851831cc
--- /dev/null
+++ b/drivers/iommu/bcm2712-iommu-cache.h
@@ -0,0 +1,9 @@
+/* SPDX-License-Identifier: GPL-2.0-only */
+#ifndef _BCM2712_IOMMU_CACHE_H
+#define _BCM2712_IOMMU_CACHE_H
+
+struct bcm2712_iommu_cache;
+
+void bcm2712_iommu_cache_flush(struct bcm2712_iommu_cache *cache);
+
+#endif
diff --git a/drivers/iommu/bcm2712-iommu.c b/drivers/iommu/bcm2712-iommu.c
new file mode 100644
index 000000000000..f04dfbee7df5
--- /dev/null
+++ b/drivers/iommu/bcm2712-iommu.c
@@ -0,0 +1,587 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * IOMMU driver for Broadcom BCM2712
+ *
+ * Copyright (c) 2023-2025 Raspberry Pi Ltd.
+ * Copyright (c) 2026 Daniel Drake
+ *
+ * Driver operation:
+ * - An aperture (max 4GB) is defined at a high address, inside which IOMMU
+ *   mappings can be created.
+ * - All preceding address space is in identity/bypass mode, allowing
+ *   for multiple devices to be connected to one IOMMU, some using the IOMMU
+ *   (via aperture) and others with normal/direct access to RAM.
+ * - There is no tagging/separation of devices; all physically connected devices
+ *   go through the IOMMU block without any way of distinguishing requests from
+ *   different devices.
+ * - 2-level page table is handled by generic_pt/bcm2712
+ */
+
+#include <linux/dma-mapping.h>
+#include <linux/err.h>
+#include <linux/iommu.h>
+#include <linux/iopoll.h>
+#include <linux/of_platform.h>
+#include <linux/platform_device.h>
+#include <linux/spinlock.h>
+#include <linux/sizes.h>
+#include <linux/generic_pt/iommu.h>
+
+#include "iommu-pages.h"
+#include "bcm2712-iommu-cache.h"
+
+/* BCM2712 IOMMU is organized around 4Kbyte pages */
+#define IOMMU_PAGE_SHIFT       12
+#define IOMMU_PAGE_SIZE        (1ul << IOMMU_PAGE_SHIFT)
+/* A PTE is 4 bytes */
+#define PTE_SIZE_SHIFT         2
+/* L1/L2 table sizing (IOMMU hardware pages): 1024 entries per page */
+#define PTES_PER_IOPG_SHIFT    (IOMMU_PAGE_SHIFT - PTE_SIZE_SHIFT)
+/* An iommu hugepage covers 4MB  */
+#define IOMMU_HUGEPAGE_SHIFT   (IOMMU_PAGE_SHIFT + PTES_PER_IOPG_SHIFT)
+
+#define MMMU_CTRL_OFFSET                       0x00
+#define MMMU_CTRL_CAP_EXCEEDED                 BIT(27)
+#define MMMU_CTRL_CAP_EXCEEDED_ABORT_EN        BIT(26)
+#define MMMU_CTRL_CAP_EXCEEDED_INT_EN          BIT(25)
+#define MMMU_CTRL_CAP_EXCEEDED_EXCEPTION_EN    BIT(24)
+#define MMMU_CTRL_PT_INVALID                   BIT(20)
+#define MMMU_CTRL_PT_INVALID_ABORT_EN          BIT(19)
+#define MMMU_CTRL_PT_INVALID_INT_EN            BIT(18)
+#define MMMU_CTRL_PT_INVALID_EXCEPTION_EN      BIT(17)
+#define MMMU_CTRL_PT_INVALID_EN                BIT(16)
+#define MMMU_CTRL_WRITE_VIOLATION              BIT(12)
+#define MMMU_CTRL_WRITE_VIOLATION_ABORT_EN     BIT(11)
+#define MMMU_CTRL_WRITE_VIOLATION_INT_EN       BIT(10)
+#define MMMU_CTRL_WRITE_VIOLATION_EXCEPTION_EN BIT(9)
+#define MMMU_CTRL_BYPASS                       BIT(8)
+#define MMMU_CTRL_TLB_CLEARING                 BIT(7)
+#define MMMU_CTRL_STATS_CLEAR                  BIT(3)
+#define MMMU_CTRL_TLB_CLEAR                    BIT(2)
+#define MMMU_CTRL_STATS_ENABLE                 BIT(1)
+#define MMMU_CTRL_ENABLE                       BIT(0)
+
+#define MMMU_CTRL_OPERATING_FLAGS (\
+	MMMU_CTRL_CAP_EXCEEDED_ABORT_EN    | \
+	MMMU_CTRL_PT_INVALID_ABORT_EN      | \
+	MMMU_CTRL_PT_INVALID_EN            | \
+	MMMU_CTRL_WRITE_VIOLATION_ABORT_EN | \
+	MMMU_CTRL_STATS_ENABLE             | \
+	MMMU_CTRL_ENABLE)
+
+#define MMMU_PT_PA_BASE_OFFSET                 0x04
+
+#define MMMU_ADDR_CAP_OFFSET                   0x14
+#define MMMU_ADDR_CAP_ENABLE                   BIT(31)
+#define ADDR_CAP_SHIFT                         ilog2(SZ_256M)
+
+#define MMMU_SHOOT_DOWN_OFFSET                 0x18
+#define MMMU_SHOOT_DOWN_SHOOTING               BIT(31)
+#define MMMU_SHOOT_DOWN_SHOOT                  BIT(30)
+
+#define MMMU_BYPASS_START_OFFSET               0x1c
+#define MMMU_BYPASS_START_ENABLE               BIT(31)
+
+#define MMMU_BYPASS_END_OFFSET                 0x20
+#define MMMU_BYPASS_END_ENABLE                 BIT(31)
+
+#define MMMU_MISC_OFFSET                       0x24
+#define MMMU_MISC_SINGLE_TABLE                 BIT(31)
+
+#define MMMU_ILLEGAL_ADR_OFFSET                0x30
+#define MMMU_ILLEGAL_ADR_ENABLE                BIT(31)
+
+#define MMMU_DEBUG_INFO_OFFSET                 0x38
+#define MMMU_DEBUG_INFO_VERSION_MASK           0x0000000Fu
+#define MMMU_DEBUG_INFO_VA_WIDTH_MASK          0x000000F0u
+#define MMMU_DEBUG_INFO_PA_WIDTH_MASK          0x00000F00u
+#define MMMU_DEBUG_INFO_BIGPAGE_WIDTH_MASK     0x000FF000u
+#define MMMU_DEBUG_INFO_SUPERPAGE_WIDTH_MASK   0x0FF00000u
+#define MMMU_DEBUG_INFO_BYPASS_4M              BIT(28)
+#define MMMU_DEBUG_INFO_BYPASS                 BIT(29)
+
+#define DEFAULT_APERTURE_BASE (40ul << 30)
+
+struct bcm2712_iommu {
+	struct device *dev;
+	struct iommu_device iommu;
+	struct bcm2712_iommu_domain *domain;
+	struct bcm2712_iommu_cache *cache;
+	void __iomem *reg_base;
+	spinlock_t hw_lock;
+	u64 aperture_start;
+	u64 aperture_size;
+	size_t bigpage_size;
+	size_t superpage_size;
+};
+
+struct bcm2712_iommu_domain {
+	union {
+		struct iommu_domain base;
+		struct pt_iommu_bcm2712 pt;
+	};
+	struct bcm2712_iommu *mmu;
+	void *default_page;
+};
+
+#define MMU_WR(off, val)  writel(val, mmu->reg_base + (off))
+#define MMU_RD(off)       readl(mmu->reg_base + (off))
+
+#define domain_to_mmu(d) \
+	(container_of(d, struct bcm2712_iommu_domain, base)->mmu)
+
+static struct bcm2712_iommu_domain *
+to_bcm2712_domain(struct iommu_domain *domain)
+{
+	return container_of(domain, struct bcm2712_iommu_domain, base);
+}
+
+static inline unsigned long
+bcm2712_iova_to_offset(struct bcm2712_iommu_domain *domain, unsigned long iova)
+{
+	return iova - domain->mmu->aperture_start;
+}
+
+static void bcm2712_iommu_init(struct bcm2712_iommu *mmu)
+{
+	unsigned int bigpage_width, superpage_width;
+	u32 u = MMU_RD(MMMU_DEBUG_INFO_OFFSET);
+	u32 pa_width = FIELD_GET(MMMU_DEBUG_INFO_PA_WIDTH_MASK, u);
+
+	dev_dbg(mmu->dev, "DEBUG_INFO = 0x%08x\n", u);
+	WARN_ON(FIELD_GET(MMMU_DEBUG_INFO_VERSION_MASK, u) < 4 ||
+		FIELD_GET(MMMU_DEBUG_INFO_VA_WIDTH_MASK, u) < 6 ||
+		pa_width < 6 || !(u & MMMU_DEBUG_INFO_BYPASS));
+
+	dma_set_mask_and_coherent(mmu->dev, DMA_BIT_MASK(pa_width + 30u));
+
+	bigpage_width = FIELD_GET(MMMU_DEBUG_INFO_BIGPAGE_WIDTH_MASK, u);
+	if (bigpage_width)
+		mmu->bigpage_size = IOMMU_PAGE_SIZE << bigpage_width;
+
+	superpage_width = FIELD_GET(MMMU_DEBUG_INFO_SUPERPAGE_WIDTH_MASK, u);
+	if (superpage_width)
+		mmu->superpage_size = IOMMU_PAGE_SIZE << superpage_width;
+
+	/* Disable MMU and clear sticky flags; meanwhile flush the TLB */
+	MMU_WR(MMMU_CTRL_OFFSET, MMMU_CTRL_CAP_EXCEEDED | MMMU_CTRL_PT_INVALID |
+					 MMMU_CTRL_WRITE_VIOLATION |
+					 MMMU_CTRL_STATS_CLEAR |
+					 MMMU_CTRL_TLB_CLEAR);
+
+	/* Put MMU into 2-level mode */
+	MMU_WR(MMMU_MISC_OFFSET,
+	       MMU_RD(MMMU_MISC_OFFSET) & ~MMMU_MISC_SINGLE_TABLE);
+}
+
+/*
+ * Since the BCM2712 IOMMU is address-based (not device based), we don't
+ * need to change any hardware state to support identity mapping.
+ * The IOMMU is natively bypassed for addresses outside the aperture.
+ */
+static int bcm2712_iommu_identity_attach(struct iommu_domain *identity_domain,
+					 struct device *dev,
+					 struct iommu_domain *old)
+{
+	struct bcm2712_iommu *mmu = dev_iommu_priv_get(dev);
+	unsigned long flags;
+
+	spin_lock_irqsave(&mmu->hw_lock, flags);
+	MMU_WR(MMMU_CTRL_OFFSET, 0);
+	mmu->domain = NULL;
+	spin_unlock_irqrestore(&mmu->hw_lock, flags);
+
+	return 0;
+}
+
+static struct iommu_domain bcm2712_identity_domain = {
+	.type = IOMMU_DOMAIN_IDENTITY,
+	.ops = &(const struct iommu_domain_ops) {
+		.attach_dev = bcm2712_iommu_identity_attach,
+	},
+};
+
+static int bcm2712_iommu_attach_dev(struct iommu_domain *domain,
+				    struct device *dev,
+				    struct iommu_domain *old)
+{
+	struct bcm2712_iommu *mmu = dev_iommu_priv_get(dev);
+	struct bcm2712_iommu_domain *mydomain = to_bcm2712_domain(domain);
+	struct pt_iommu_bcm2712_hw_info info;
+	u32 default_page_pfn, u;
+	unsigned int byp_shift;
+	unsigned long flags;
+
+	spin_lock_irqsave(&mmu->hw_lock, flags);
+
+	if (mmu->domain == mydomain)
+		goto unlock;
+
+	mmu->domain = mydomain;
+
+	/*
+	 * This driver is for VC IOMMU version >= 4 and assumes at least 36
+	 * bits of virtual and physical address space.
+	 */
+	u = MMU_RD(MMMU_DEBUG_INFO_OFFSET);
+	byp_shift = (u & MMMU_DEBUG_INFO_BYPASS_4M) ? IOMMU_HUGEPAGE_SHIFT :
+						      ADDR_CAP_SHIFT;
+
+	/*
+	 * Set address cap and bypass range (note unintuitive off-by-ones).
+	 * Requests to the bypass window pass straight through unchanged: this
+	 * is useful for blocks which share an IOMMU with other blocks whose
+	 * drivers are not IOMMU-aware.
+	 */
+	MMU_WR(MMMU_ADDR_CAP_OFFSET,
+	       MMMU_ADDR_CAP_ENABLE +
+	       ((mmu->aperture_start + mmu->aperture_size) >> ADDR_CAP_SHIFT)
+	       - 1);
+	MMU_WR(MMMU_BYPASS_START_OFFSET, 0);
+	MMU_WR(MMMU_BYPASS_END_OFFSET,
+	       MMMU_BYPASS_END_ENABLE + (mmu->aperture_start >> byp_shift));
+
+	/*
+	 * When the IOMMU handles a request, it adds the PT_PA_BASE_OFFSET to
+	 * (IOVA>>32) to calculate the PFN of the corresponding L1 directory page.
+	 * IOVA bits [31:22] are then used to fetch the L1 descriptor within
+	 * (which in turn points to the L2 table).
+	 * This clever logic would allow for a L1 table larger than 4kb (and hence
+	 * a larger aperture.
+	 */
+	pt_iommu_bcm2712_hw_info(&mydomain->pt, &info);
+	MMU_WR(MMMU_PT_PA_BASE_OFFSET, (info.pt_base >> IOMMU_PAGE_SHIFT) -
+				       (mmu->aperture_start >> 32));
+
+	/* Set up a default (error) page used to catch illegal reads/writes */
+	default_page_pfn = virt_to_phys(mydomain->default_page) >> IOMMU_PAGE_SHIFT;
+	MMU_WR(MMMU_ILLEGAL_ADR_OFFSET,
+	       MMMU_ILLEGAL_ADR_ENABLE + default_page_pfn);
+
+	/* Flush (and enable) the shared TLB cache; enable this MMU. */
+	bcm2712_iommu_cache_flush(mmu->cache);
+	MMU_WR(MMMU_CTRL_OFFSET, MMMU_CTRL_OPERATING_FLAGS);
+
+unlock:
+	spin_unlock_irqrestore(&mmu->hw_lock, flags);
+	return 0;
+}
+
+static void bcm2712_iommu_shootdown_range(struct bcm2712_iommu *mmu,
+					  unsigned long iova, size_t size)
+{
+	unsigned long iova_end = iova + size - 1;
+	unsigned int page_group;
+	u32 val;
+
+	/* Shootdown register deals with 4 pages at a time */
+	for (page_group = iova >> (IOMMU_PAGE_SHIFT + 2);
+	     page_group <= iova_end >> (IOMMU_PAGE_SHIFT + 2); page_group++) {
+		MMU_WR(MMMU_SHOOT_DOWN_OFFSET,
+		       MMMU_SHOOT_DOWN_SHOOT + (page_group << 2));
+		readl_poll_timeout_atomic(
+			mmu->reg_base + MMMU_SHOOT_DOWN_OFFSET, val,
+			!(val & MMMU_SHOOT_DOWN_SHOOTING), 0, 1000);
+	}
+}
+
+static int bcm2712_iommu_sync_range(struct iommu_domain *domain,
+				    unsigned long iova, size_t size)
+{
+	struct bcm2712_iommu *mmu = domain_to_mmu(domain);
+	unsigned long flags;
+	u32 val;
+
+	spin_lock_irqsave(&mmu->hw_lock, flags);
+	bcm2712_iommu_cache_flush(mmu->cache);
+
+	/* If invalidating more than 16MB, just do a full TLB clear */
+	if (size >= SZ_16M) {
+		MMU_WR(MMMU_CTRL_OFFSET,
+		       MMMU_CTRL_OPERATING_FLAGS | MMMU_CTRL_TLB_CLEAR);
+		readl_poll_timeout_atomic(mmu->reg_base + MMMU_CTRL_OFFSET, val,
+					  !(val & MMMU_CTRL_TLB_CLEARING), 0,
+					  1000);
+	} else {
+		bcm2712_iommu_shootdown_range(mmu, iova, size);
+	}
+
+	spin_unlock_irqrestore(&mmu->hw_lock, flags);
+	return 0;
+}
+
+static void bcm2712_iommu_sync(struct iommu_domain *domain,
+			       struct iommu_iotlb_gather *gather)
+{
+	struct bcm2712_iommu *mmu = domain_to_mmu(domain);
+
+	bcm2712_iommu_sync_range(domain, gather->start + mmu->aperture_start,
+				 gather->end - gather->start + 1);
+}
+
+static int bcm2712_iommu_sync_map(struct iommu_domain *domain,
+				  unsigned long iova, size_t size)
+{
+	return bcm2712_iommu_sync_range(domain, iova, size);
+}
+
+static void bcm2712_iommu_sync_all(struct iommu_domain *domain)
+{
+	size_t aperture_size = domain->geometry.aperture_end -
+			       domain->geometry.aperture_start + 1;
+
+	bcm2712_iommu_sync_range(domain, domain->geometry.aperture_start,
+				 aperture_size);
+}
+
+static void bcm2712_iommu_domain_free(struct iommu_domain *domain)
+{
+	struct bcm2712_iommu_domain *mydomain = to_bcm2712_domain(domain);
+	struct bcm2712_iommu *mmu = mydomain->mmu;
+
+	if (mmu && mmu->domain == mydomain) {
+		unsigned long flags;
+
+		spin_lock_irqsave(&mmu->hw_lock, flags);
+		MMU_WR(MMMU_CTRL_OFFSET, 0);
+		mmu->domain = NULL;
+		spin_unlock_irqrestore(&mmu->hw_lock, flags);
+	}
+
+	pt_iommu_deinit(&mydomain->pt.iommu);
+	if (mydomain->default_page)
+		iommu_free_pages(mydomain->default_page);
+	kfree(mydomain);
+}
+
+static struct iommu_domain *bcm2712_iommu_domain_alloc(struct device *dev)
+{
+	struct bcm2712_iommu *mmu = dev_iommu_priv_get(dev);
+	struct bcm2712_iommu_domain *domain;
+	struct pt_iommu_bcm2712_cfg cfg;
+	int ret;
+
+	domain = kzalloc_obj(*domain);
+	if (!domain)
+		return NULL;
+
+	domain->mmu = mmu;
+	domain->pt.iommu.iommu_device = mmu->dev;
+	memset(&cfg, 0, sizeof(cfg));
+	cfg.common.features = BIT(PT_FEAT_DMA_INCOHERENT);
+
+	/*
+	 * Bigpage and superpage sizes are typically 64K and 1M, but may vary
+	 * (hugepage size is fixed at 4M, the range covered by an L2 page).
+	 */
+	cfg.pgsize_bitmap = SZ_4K | SZ_4M;
+	if (mmu->bigpage_size) {
+		cfg.pgsize_bitmap |= mmu->bigpage_size;
+		cfg.bigpage_lg2 = ilog2(mmu->bigpage_size);
+	}
+	if (mmu->superpage_size) {
+		cfg.pgsize_bitmap |= mmu->superpage_size;
+		cfg.superpage_lg2 = ilog2(mmu->superpage_size);
+	}
+
+	/* 2-level format: 10-bit L1 + 10-bit L2 + 12-bit page offset */
+	cfg.common.hw_max_vasz_lg2 =
+		(2 * PTES_PER_IOPG_SHIFT) + IOMMU_PAGE_SHIFT;
+
+	/* PTEs encode a 28-bit output address PFN */
+	cfg.common.hw_max_oasz_lg2 = 28 + IOMMU_PAGE_SHIFT;
+
+	ret = pt_iommu_bcm2712_init(&domain->pt, &cfg, GFP_KERNEL);
+	if (ret)
+		goto err;
+
+	/* Set up a default (error) page used to catch illegal reads/writes */
+	domain->default_page = iommu_alloc_pages_sz(GFP_KERNEL, PAGE_SIZE);
+	if (!domain->default_page)
+		goto err;
+
+	domain->base.geometry.aperture_start = mmu->aperture_start;
+	domain->base.geometry.aperture_end =
+		mmu->aperture_start + mmu->aperture_size - 1ul;
+	domain->base.geometry.force_aperture = true;
+	domain->base.is_iommupt = false;
+	return &domain->base;
+
+err:
+	bcm2712_iommu_domain_free(&domain->base);
+	return NULL;
+}
+
+static struct iommu_device *bcm2712_iommu_probe_device(struct device *dev)
+{
+	struct bcm2712_iommu *mmu = dev_iommu_priv_get(dev);
+
+	return &mmu->iommu;
+}
+
+static int bcm2712_iommu_of_xlate(struct device *dev,
+				  const struct of_phandle_args *args)
+{
+	struct platform_device *iommu_dev = of_find_device_by_node(args->np);
+	struct bcm2712_iommu *mmu = platform_get_drvdata(iommu_dev);
+
+	dev_iommu_priv_set(dev, mmu);
+	return 0;
+}
+
+static int bcm2712_iommu_map_pages(struct iommu_domain *domain,
+				   unsigned long iova, phys_addr_t paddr,
+				   size_t pgsize, size_t pgcount, int prot,
+				   gfp_t gfp, size_t *mapped)
+{
+	struct bcm2712_iommu_domain *mydomain = to_bcm2712_domain(domain);
+	struct pt_iommu *pt = &mydomain->pt.iommu;
+
+	return pt->ops->map_range(pt, bcm2712_iova_to_offset(mydomain, iova),
+				  paddr, pgsize * pgcount, prot, gfp, mapped);
+}
+
+static size_t bcm2712_iommu_unmap_pages(struct iommu_domain *domain,
+					unsigned long iova, size_t pgsize,
+					size_t pgcount,
+					struct iommu_iotlb_gather *gather)
+{
+	struct bcm2712_iommu_domain *mydomain = to_bcm2712_domain(domain);
+	struct pt_iommu *pt = &mydomain->pt.iommu;
+
+	return pt->ops->unmap_range(pt, bcm2712_iova_to_offset(mydomain, iova),
+				    pgsize * pgcount, gather);
+}
+
+static phys_addr_t bcm2712_iova_to_phys(struct iommu_domain *domain,
+					dma_addr_t iova)
+{
+	struct bcm2712_iommu_domain *mydomain = to_bcm2712_domain(domain);
+	unsigned long offset_iova = bcm2712_iova_to_offset(mydomain, iova);
+
+	return pt_iommu_bcm2712_iova_to_phys(domain, offset_iova);
+}
+
+static const struct iommu_ops bcm2712_iommu_ops = {
+	.identity_domain = &bcm2712_identity_domain,
+	.domain_alloc_paging = bcm2712_iommu_domain_alloc,
+	.probe_device = bcm2712_iommu_probe_device,
+	.device_group = generic_single_device_group,
+	.of_xlate = bcm2712_iommu_of_xlate,
+	.default_domain_ops = &(const struct iommu_domain_ops) {
+		.attach_dev	 = bcm2712_iommu_attach_dev,
+		.iotlb_sync      = bcm2712_iommu_sync,
+		.iotlb_sync_map  = bcm2712_iommu_sync_map,
+		.flush_iotlb_all = bcm2712_iommu_sync_all,
+		.free		 = bcm2712_iommu_domain_free,
+		.map_pages       = bcm2712_iommu_map_pages,
+		.unmap_pages     = bcm2712_iommu_unmap_pages,
+		.iova_to_phys    = bcm2712_iova_to_phys,
+	},
+};
+
+static const struct of_device_id bcm2712_iommu_of_match[] = {
+	{ .compatible = "brcm,bcm2712-iommu" },
+	{ /* sentinel */ }
+};
+
+static int bcm2712_iommu_init_cache(struct bcm2712_iommu *mmu, struct platform_device *pdev)
+{
+	struct device *dev = &pdev->dev;
+	struct platform_device *cache_pdev;
+	struct device_node *cache_np;
+
+	cache_np = of_parse_phandle(dev->of_node, "brcm,iommu-cache", 0);
+
+	/* Fall back on 'cache' property used in old/downstream firmware */
+	if (!cache_np)
+		cache_np = of_parse_phandle(dev->of_node, "cache", 0);
+
+	if (!cache_np)
+		return dev_err_probe(dev, -ENOENT,
+				     "missing brcm,iommu-cache property\n");
+
+	cache_pdev = of_find_device_by_node(cache_np);
+	of_node_put(cache_np);
+	if (!cache_pdev)
+		return -EPROBE_DEFER;
+
+	mmu->cache = platform_get_drvdata(cache_pdev);
+	if (!mmu->cache) {
+		put_device(&cache_pdev->dev);
+		return -EPROBE_DEFER;
+	}
+
+	put_device(&cache_pdev->dev);
+	return 0;
+}
+
+static int bcm2712_iommu_probe(struct platform_device *pdev)
+{
+	struct device *dev = &pdev->dev;
+	u64 window[2];
+	struct bcm2712_iommu *mmu;
+	int ret;
+
+	mmu = devm_kzalloc(dev, sizeof(*mmu), GFP_KERNEL);
+	if (!mmu)
+		return -ENOMEM;
+
+	mmu->dev = dev;
+	spin_lock_init(&mmu->hw_lock);
+
+	mmu->reg_base = devm_platform_ioremap_resource(pdev, 0);
+	if (IS_ERR(mmu->reg_base))
+		return PTR_ERR(mmu->reg_base);
+
+	ret = bcm2712_iommu_init_cache(mmu, pdev);
+	if (ret)
+		return ret;
+
+	if (!of_property_read_u64_array(dev->of_node, "brcm,iova-window",
+					window, 2)) {
+		mmu->aperture_start = window[0];
+		mmu->aperture_size = window[1];
+		if (mmu->aperture_size > SZ_4G) {
+			dev_warn(dev, "Aperture size exceeds 4GB, capping to 4GB\n");
+			mmu->aperture_size = SZ_4G;
+		}
+	} else {
+		mmu->aperture_start = DEFAULT_APERTURE_BASE;
+		mmu->aperture_size = SZ_4G;
+	}
+
+	if (!IS_ALIGNED(mmu->aperture_start, SZ_4G)) {
+		dev_err(dev, "Aperture start address is not 4GB aligned\n");
+		return -EINVAL;
+	}
+
+	platform_set_drvdata(pdev, mmu);
+	bcm2712_iommu_init(mmu);
+
+	ret = iommu_device_sysfs_add(&mmu->iommu, dev, NULL, dev_name(dev));
+	if (ret)
+		return ret;
+
+	ret = iommu_device_register(&mmu->iommu, &bcm2712_iommu_ops, dev);
+	if (ret) {
+		iommu_device_sysfs_remove(&mmu->iommu);
+		return ret;
+	}
+
+	return 0;
+}
+
+static struct platform_driver bcm2712_iommu_driver = {
+	.driver	= {
+		.name		= "bcm2712-iommu",
+		.of_match_table	= bcm2712_iommu_of_match,
+		.suppress_bind_attrs = true,
+	},
+	.probe	= bcm2712_iommu_probe,
+};
+builtin_platform_driver(bcm2712_iommu_driver);
+
+MODULE_LICENSE("GPL");
+MODULE_AUTHOR("Daniel Drake <dan@reactivated.net>");
+MODULE_DESCRIPTION("Broadcom BCM2712 IOMMU driver");

-- 
2.55.0



^ permalink raw reply related	[flat|nested] 15+ messages in thread

* [PATCH 6/6] arm64: dts: broadcom: bcm2712: Add GPU IOMMU and IOMMU cache nodes
  2026-07-12 21:18 [PATCH 0/6] Add support for Broadcom BCM2712 IOMMU driver (Raspberry Pi 5) Daniel Drake
                   ` (4 preceding siblings ...)
  2026-07-12 21:18 ` [PATCH 5/6] iommu: Add Broadcom BCM2712 IOMMU driver Daniel Drake
@ 2026-07-12 21:18 ` Daniel Drake
  2026-07-13  9:17   ` Krzysztof Kozlowski
  5 siblings, 1 reply; 15+ messages in thread
From: Daniel Drake @ 2026-07-12 21:18 UTC (permalink / raw)
  To: Joerg Roedel (AMD), Will Deacon, Robin Murphy, Rob Herring,
	Krzysztof Kozlowski, Conor Dooley, Florian Fainelli,
	Broadcom internal kernel review list, Daniel Drake
  Cc: iommu, linux-kernel, devicetree, linux-rpi-kernel,
	linux-arm-kernel, nick.hollinghurst, Jason Gunthorpe

Define the IOMMU and IOMMUC nodes for the Broadcom BCM2712 SoC found in
the Raspberry Pi 5. Enable use of the IOMMU for the graphics/display
block.

Signed-off-by: Daniel Drake <dan@reactivated.net>
---
 arch/arm64/boot/dts/broadcom/bcm2712.dtsi | 15 +++++++++++++++
 1 file changed, 15 insertions(+)

diff --git a/arch/arm64/boot/dts/broadcom/bcm2712.dtsi b/arch/arm64/boot/dts/broadcom/bcm2712.dtsi
index 761c59d90ffc..ed3178db8acb 100644
--- a/arch/arm64/boot/dts/broadcom/bcm2712.dtsi
+++ b/arch/arm64/boot/dts/broadcom/bcm2712.dtsi
@@ -626,6 +626,20 @@ pcie2: pcie@1000120000 {
 			status = "disabled";
 		};
 
+		iommu4: iommu@5200 {
+			/* IOMMU for VC4 (HVS & MPL/TXP), Unicam, PISP-FE, MiniBVN */
+			compatible = "brcm,bcm2712-iommu";
+			reg = <0x10 0x5200 0x0 0x80>;
+			#iommu-cells = <0>;
+			brcm,iommu-cache = <&iommuc>;
+			brcm,iova-window = /bits/ 64 <0xa00000000 0x100000000>;
+		};
+
+		iommuc: iommuc@5b00 {
+			compatible = "brcm,bcm2712-iommuc";
+			reg = <0x10 0x5b00 0x0 0x80>;
+		};
+
 		mip0: msi-controller@1000130000 {
 			compatible = "brcm,bcm2712-mip";
 			reg = <0x10 0x00130000 0x00 0xc0>,
@@ -667,6 +681,7 @@ v3d: gpu@1002000000 {
 
 	vc4: gpu {
 		compatible = "brcm,bcm2712-vc6";
+		iommus = <&iommu4>;
 	};
 
 	timer {

-- 
2.55.0



^ permalink raw reply related	[flat|nested] 15+ messages in thread

* Re: [PATCH 1/6] generic_pt: allow missing sw bit in DMA_INCOHERENT case
  2026-07-12 21:18 ` [PATCH 1/6] generic_pt: allow missing sw bit in DMA_INCOHERENT case Daniel Drake
@ 2026-07-12 22:00   ` Jason Gunthorpe
  0 siblings, 0 replies; 15+ messages in thread
From: Jason Gunthorpe @ 2026-07-12 22:00 UTC (permalink / raw)
  To: Daniel Drake
  Cc: Joerg Roedel (AMD), Will Deacon, Robin Murphy, Rob Herring,
	Krzysztof Kozlowski, Conor Dooley, Florian Fainelli,
	Broadcom internal kernel review list, iommu, linux-kernel,
	devicetree, linux-rpi-kernel, linux-arm-kernel, nick.hollinghurst

On Sun, Jul 12, 2026 at 10:18:51PM +0100, Daniel Drake wrote:
> When working with a iommu with PT_FEAT_DMA_INCOHERENT set, generic_pt
> will attempt to use a spare "SW" bit in the hardware page tables to
> denote when a thread has flushed the CPU cache after modifying an entry.
> 
> This means that other threads know that they are not working with
> cached/unflushed data, if they come across the same entry.
> 
> In the case where no SW bit is available, two things happen:
> 
> 1. __map_range() defensively flushes every time it reads the PT.
>    This ensures all data that may have just been manipulated by another
>    thread gets flushed and made iommu-visible immediately.
> 
> 2. An undefined reference to __pt_no_sw_bit() is created, causing a
>    linker error in order to alert the developer that they are going
>    to suffer a performance penalty in the previous point.

Ah, actually this is all setup like this because it doesn't have an
implementation for supporting no-SW bit versions right now. That a
mandatory flush happens is not something I thought about, my original
plan was to put the SW bit into the struct page memory instead and
have some extra barriers..

It would be better to not call the SW bit code at all if
PT_SW_BIT_NOT_PRESENT so we have a clear algorithm that explains how
it is intended to work (ie mandatory flush) in this case instead of
disabling the linker safety check of using SW bit without support

Jason


^ permalink raw reply	[flat|nested] 15+ messages in thread

* Re: [PATCH 2/6] iommupt: allow full-table contiguous leaves in unit tests
  2026-07-12 21:18 ` [PATCH 2/6] iommupt: allow full-table contiguous leaves in unit tests Daniel Drake
@ 2026-07-12 22:02   ` Jason Gunthorpe
  0 siblings, 0 replies; 15+ messages in thread
From: Jason Gunthorpe @ 2026-07-12 22:02 UTC (permalink / raw)
  To: Daniel Drake
  Cc: Joerg Roedel (AMD), Will Deacon, Robin Murphy, Rob Herring,
	Krzysztof Kozlowski, Conor Dooley, Florian Fainelli,
	Broadcom internal kernel review list, iommu, linux-kernel,
	devicetree, linux-rpi-kernel, linux-arm-kernel, nick.hollinghurst

On Sun, Jul 12, 2026 at 10:18:52PM +0100, Daniel Drake wrote:
> The generic_pt tests currently assume that a format should never support
> a contiguous page size that spans the entire mapping range of the table
> it resides in.
> 
> This assumption breaks for BCM2712, which has a 2-level page table.
> Level 1 entries cover 4MB of memory via their corresponding Level 0
> table pages. There is a concept of a 4MB largepage, but that is
> represented as a Level 1 entry pointing at a Level 0 table with the 1024
> usual PTEs with an additional largepage hint applied (for TLB
> optimization purposes). Unlike typical IOMMUs, it is not possible to have
> the Level 1 entry point directly at a 4MB chunk without the need for
> Level 0 mapping.
> 
> This BCM2712 format is well supported by generic_pt: SZ_4M can be
> advertised as a possible size at Level 0 (so that the iommu core feeds
> it 4MB-aligned mapping requests where possible), and drivers can also
> advertise that *all* leaf entries are installed at Level 0. This indicates
> that the tests are overly strict.
> 
> Transform the test to become a validation of the driver's routing intent:
> for every advertised page size, check that pt_pgsz_lg2_to_level() maps that
> size back to the table level under test. This is roughly what the original
> tests were doing, but also allows the BCM2712 case to pass.

Seems fine, but I wonder if it should be conditional on the format
somehow, I put it in to detect mistakes on what was the universal case
that nothing would do this.

Either way

Reviewed-by: Jason Gunthorpe <jgg@nvidia.com>

Jason


^ permalink raw reply	[flat|nested] 15+ messages in thread

* Re: [PATCH 4/6] iommu/generic_pt: Add Broadcom BCM2712 page table format
  2026-07-12 21:18 ` [PATCH 4/6] iommu/generic_pt: Add Broadcom BCM2712 page table format Daniel Drake
@ 2026-07-12 22:05   ` Jason Gunthorpe
  2026-07-13 10:02   ` Nick Hollinghurst
  1 sibling, 0 replies; 15+ messages in thread
From: Jason Gunthorpe @ 2026-07-12 22:05 UTC (permalink / raw)
  To: Daniel Drake
  Cc: Joerg Roedel (AMD), Will Deacon, Robin Murphy, Rob Herring,
	Krzysztof Kozlowski, Conor Dooley, Florian Fainelli,
	Broadcom internal kernel review list, iommu, linux-kernel,
	devicetree, linux-rpi-kernel, linux-arm-kernel, nick.hollinghurst

On Sun, Jul 12, 2026 at 10:18:54PM +0100, Daniel Drake wrote:
> The BCM2712 IOMMU implements a 2-level page table format. It is relatively
> simple, with one unusual aspect: leaf entries can only be installed at
> Level 0.
> 
> Adapted from Raspberry Pi's downstream bcm2712-iommu driver (original
> author Nick Hollinghurst).
> 
> Signed-off-by: Daniel Drake <dan@reactivated.net>
> ---
>  drivers/iommu/generic_pt/.kunitconfig        |   1 +
>  drivers/iommu/generic_pt/Kconfig             |  10 ++
>  drivers/iommu/generic_pt/fmt/Makefile        |   2 +
>  drivers/iommu/generic_pt/fmt/bcm2712.h       | 259 +++++++++++++++++++++++++++
>  drivers/iommu/generic_pt/fmt/defs_bcm2712.h  |  18 ++
>  drivers/iommu/generic_pt/fmt/iommu_bcm2712.c |  10 ++
>  include/linux/generic_pt/common.h            |   7 +
>  include/linux/generic_pt/iommu.h             |  13 ++
>  8 files changed, 320 insertions(+)

Looks straightforward to me!

Reviewed-by: Jason Gunthorpe <jgg@nvidia.com>

Jason


^ permalink raw reply	[flat|nested] 15+ messages in thread

* Re: [PATCH 5/6] iommu: Add Broadcom BCM2712 IOMMU driver
  2026-07-12 21:18 ` [PATCH 5/6] iommu: Add Broadcom BCM2712 IOMMU driver Daniel Drake
@ 2026-07-12 22:11   ` Jason Gunthorpe
  0 siblings, 0 replies; 15+ messages in thread
From: Jason Gunthorpe @ 2026-07-12 22:11 UTC (permalink / raw)
  To: Daniel Drake
  Cc: Joerg Roedel (AMD), Will Deacon, Robin Murphy, Rob Herring,
	Krzysztof Kozlowski, Conor Dooley, Florian Fainelli,
	Broadcom internal kernel review list, iommu, linux-kernel,
	devicetree, linux-rpi-kernel, linux-arm-kernel, nick.hollinghurst

On Sun, Jul 12, 2026 at 10:18:55PM +0100, Daniel Drake wrote:
> +static int bcm2712_iommu_of_xlate(struct device *dev,
> +				  const struct of_phandle_args *args)
> +{
> +	struct platform_device *iommu_dev = of_find_device_by_node(args->np);
> +	struct bcm2712_iommu *mmu = platform_get_drvdata(iommu_dev);
> +
> +	dev_iommu_priv_set(dev, mmu);
> +	return 0;
> +}

Any chance this could work the way that smmuv3 does? I view it as the
more modern example..

> +static int bcm2712_iommu_map_pages(struct iommu_domain *domain,
> +				   unsigned long iova, phys_addr_t paddr,
> +				   size_t pgsize, size_t pgcount, int prot,
> +				   gfp_t gfp, size_t *mapped)
> +{
> +	struct bcm2712_iommu_domain *mydomain = to_bcm2712_domain(domain);
> +	struct pt_iommu *pt = &mydomain->pt.iommu;
> +
> +	return pt->ops->map_range(pt, bcm2712_iova_to_offset(mydomain, iova),
> +				  paddr, pgsize * pgcount, prot, gfp, mapped);
> +}

These ops should not be present, the iommpt provides the ops directly
through a macro;

> +static const struct iommu_ops bcm2712_iommu_ops = {
> +	.identity_domain = &bcm2712_identity_domain,
> +	.domain_alloc_paging = bcm2712_iommu_domain_alloc,
> +	.probe_device = bcm2712_iommu_probe_device,
> +	.device_group = generic_single_device_group,
> +	.of_xlate = bcm2712_iommu_of_xlate,
> +	.default_domain_ops = &(const struct iommu_domain_ops) {
> +		.attach_dev	 = bcm2712_iommu_attach_dev,
> +		.iotlb_sync      = bcm2712_iommu_sync,
> +		.iotlb_sync_map  = bcm2712_iommu_sync_map,
> +		.flush_iotlb_all = bcm2712_iommu_sync_all,
> +		.free		 = bcm2712_iommu_domain_free,
> +		.map_pages       = bcm2712_iommu_map_pages,
> +		.unmap_pages     = bcm2712_iommu_unmap_pages,
> +		.iova_to_phys    = bcm2712_iova_to_phys,
> +	},

"default_domain_ops" should ideally be split out to a "paging domain
ops" static and set directly during alloc_paging. They are not really
"default" anymore if the driver has unique ops for every domain type.

Then use something like:

		IOMMU_PT_DOMAIN_OPS(bcm2712),

To define all the page table related ops automatically.

Any chace the HW can do a blocking_domain, or is the only way to do
that with an empty paging domain?

Jason


^ permalink raw reply	[flat|nested] 15+ messages in thread

* Re: [PATCH 3/6] dt-bindings: iommu: Add Broadcom BCM2712 IOMMU
  2026-07-12 21:18 ` [PATCH 3/6] dt-bindings: iommu: Add Broadcom BCM2712 IOMMU Daniel Drake
@ 2026-07-13  9:16   ` Krzysztof Kozlowski
  0 siblings, 0 replies; 15+ messages in thread
From: Krzysztof Kozlowski @ 2026-07-13  9:16 UTC (permalink / raw)
  To: Daniel Drake
  Cc: Joerg Roedel (AMD), Will Deacon, Robin Murphy, Rob Herring,
	Krzysztof Kozlowski, Conor Dooley, Florian Fainelli,
	Broadcom internal kernel review list, iommu, linux-kernel,
	devicetree, linux-rpi-kernel, linux-arm-kernel, nick.hollinghurst,
	Jason Gunthorpe

On Sun, Jul 12, 2026 at 10:18:53PM +0100, Daniel Drake wrote:
> Add bindings for the Broadcom BCM2712 IOMMU and its shared TLB cache.
> 
> Include compatibility with the cache phandle used in existing firmware
> shipped on Raspberry Pi 5, and also allow the iova-window to be omitted,
> which similarly will maintain compatibility with existing RPi5 firmware.
> 
> Signed-off-by: Daniel Drake <dan@reactivated.net>
> ---
>  .../bindings/iommu/brcm,bcm2712-iommu.yaml         | 65 ++++++++++++++++++++++
>  .../bindings/iommu/brcm,bcm2712-iommuc.yaml        | 35 ++++++++++++
>  2 files changed, 100 insertions(+)
> 
> diff --git a/Documentation/devicetree/bindings/iommu/brcm,bcm2712-iommu.yaml b/Documentation/devicetree/bindings/iommu/brcm,bcm2712-iommu.yaml
> new file mode 100644
> index 000000000000..0d91c513afc4
> --- /dev/null
> +++ b/Documentation/devicetree/bindings/iommu/brcm,bcm2712-iommu.yaml
> @@ -0,0 +1,65 @@
> +# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause)
> +%YAML 1.2
> +---
> +$id: http://devicetree.org/schemas/iommu/brcm,bcm2712-iommu.yaml#
> +$schema: http://devicetree.org/meta-schemas/core.yaml#
> +
> +title: Broadcom BCM2712 IOMMU
> +
> +maintainers:
> +  - Daniel Drake <dan@reactivated.net>
> +
> +description: |

Do not need '|' unless you need to preserve formatting.

> +  The BCM2712 IOMMU provides address translation for various hardware blocks
> +  on the BCM2712 SoC, such as the VC6 display pipeline.
> +
> +properties:
> +  compatible:
> +    const: brcm,bcm2712-iommu
> +
> +  reg:
> +    maxItems: 1
> +
> +  '#iommu-cells':
> +    const: 0
> +
> +  brcm,iommu-cache:
> +    $ref: /schemas/types.yaml#/definitions/phandle
> +    description: Phandle to the shared IOMMU cache (IOMMUC).

You need to explain what for, the purpose in hardware.

> +
> +  cache:
> +    $ref: /schemas/types.yaml#/definitions/phandle
> +    description: Deprecated. Use brcm,iommu-cache instead.
> +    deprecated: true

Drop property, we do not take new bindings with deprecated properties.
Existing FW is not an excuse, Raspberry 5 developers could reach out and
ask if this is okay. They did not.

> +
> +  brcm,iova-window:
> +    $ref: /schemas/types.yaml#/definitions/uint64-array
> +    description: |
> +      An array of two 64-bit integers specifying the IOVA aperture start
> +      address and the aperture size. IOMMU mappings will be created inside
> +      this aperture. All preceding address space is in identity/bypass mode.
> +      The aperture start address must be 4GB-aligned.
> +      Should be considered a required property, but it is technically optional
> +      in order to maintain compatibility with historical firmware versions.
> +      If absent, defaults to a 4GB window at 40GiB (0xa00000000).
> +    items:
> +      - description: IOVA window start address
> +      - description: IOVA window size

This looks a lot like reserved region, so why do you need a new
property? If this is not a reserved memory, then why compatible does not
imply the value?


> +
> +required:
> +  - compatible
> +  - reg
> +  - '#iommu-cells'
> +  - brcm,iommu-cache
> +
> +additionalProperties: false
> +
> +examples:
> +  - |
> +    iommu@5200 {
> +        compatible = "brcm,bcm2712-iommu";
> +        reg = <0x5200 0x80>;
> +        brcm,iommu-cache = <&iommuc>;
> +        #iommu-cells = <0>;
> +        brcm,iova-window = /bits/ 64 <0xa00000000 0x100000000>;

Best regards,
Krzysztof



^ permalink raw reply	[flat|nested] 15+ messages in thread

* Re: [PATCH 6/6] arm64: dts: broadcom: bcm2712: Add GPU IOMMU and IOMMU cache nodes
  2026-07-12 21:18 ` [PATCH 6/6] arm64: dts: broadcom: bcm2712: Add GPU IOMMU and IOMMU cache nodes Daniel Drake
@ 2026-07-13  9:17   ` Krzysztof Kozlowski
  0 siblings, 0 replies; 15+ messages in thread
From: Krzysztof Kozlowski @ 2026-07-13  9:17 UTC (permalink / raw)
  To: Daniel Drake
  Cc: Joerg Roedel (AMD), Will Deacon, Robin Murphy, Rob Herring,
	Krzysztof Kozlowski, Conor Dooley, Florian Fainelli,
	Broadcom internal kernel review list, iommu, linux-kernel,
	devicetree, linux-rpi-kernel, linux-arm-kernel, nick.hollinghurst,
	Jason Gunthorpe

On Sun, Jul 12, 2026 at 10:18:56PM +0100, Daniel Drake wrote:
> Define the IOMMU and IOMMUC nodes for the Broadcom BCM2712 SoC found in
> the Raspberry Pi 5. Enable use of the IOMMU for the graphics/display
> block.
> 
> Signed-off-by: Daniel Drake <dan@reactivated.net>
> ---
>  arch/arm64/boot/dts/broadcom/bcm2712.dtsi | 15 +++++++++++++++
>  1 file changed, 15 insertions(+)
> 

Does not like built with W=1.

Best regards,
Krzysztof



^ permalink raw reply	[flat|nested] 15+ messages in thread

* Re: [PATCH 4/6] iommu/generic_pt: Add Broadcom BCM2712 page table format
  2026-07-12 21:18 ` [PATCH 4/6] iommu/generic_pt: Add Broadcom BCM2712 page table format Daniel Drake
  2026-07-12 22:05   ` Jason Gunthorpe
@ 2026-07-13 10:02   ` Nick Hollinghurst
  2026-07-13 12:00     ` Jason Gunthorpe
  1 sibling, 1 reply; 15+ messages in thread
From: Nick Hollinghurst @ 2026-07-13 10:02 UTC (permalink / raw)
  To: Daniel Drake
  Cc: Joerg Roedel (AMD), Will Deacon, Robin Murphy, Rob Herring,
	Krzysztof Kozlowski, Conor Dooley, Florian Fainelli,
	Broadcom internal kernel review list, iommu, linux-kernel,
	devicetree, linux-rpi-kernel, linux-arm-kernel, Jason Gunthorpe

Hi Daniel,

On Sun, 12 Jul 2026 at 22:19, Daniel Drake <dan@reactivated.net> wrote:
>
> The BCM2712 IOMMU implements a 2-level page table format. It is relatively
> simple, with one unusual aspect: leaf entries can only be installed at
> Level 0.

I should admit that a single-level huge page mapping is possible, but
I was too lazy to implement it (and haven't much tested it).

It can be done by setting bits 31, 30 and 28 of the top-level entry
(all other combinations of bits 31, 30 will point to a  next-level
table). The page number is in 4K units but must be 4MB aligned.

Regards,

 Nick


^ permalink raw reply	[flat|nested] 15+ messages in thread

* Re: [PATCH 4/6] iommu/generic_pt: Add Broadcom BCM2712 page table format
  2026-07-13 10:02   ` Nick Hollinghurst
@ 2026-07-13 12:00     ` Jason Gunthorpe
  0 siblings, 0 replies; 15+ messages in thread
From: Jason Gunthorpe @ 2026-07-13 12:00 UTC (permalink / raw)
  To: Nick Hollinghurst
  Cc: Daniel Drake, Joerg Roedel (AMD), Will Deacon, Robin Murphy,
	Rob Herring, Krzysztof Kozlowski, Conor Dooley, Florian Fainelli,
	Broadcom internal kernel review list, iommu, linux-kernel,
	devicetree, linux-rpi-kernel, linux-arm-kernel

On Mon, Jul 13, 2026 at 11:02:15AM +0100, Nick Hollinghurst wrote:
> Hi Daniel,
> 
> On Sun, 12 Jul 2026 at 22:19, Daniel Drake <dan@reactivated.net> wrote:
> >
> > The BCM2712 IOMMU implements a 2-level page table format. It is relatively
> > simple, with one unusual aspect: leaf entries can only be installed at
> > Level 0.
> 
> I should admit that a single-level huge page mapping is possible, but
> I was too lazy to implement it (and haven't much tested it).
> 
> It can be done by setting bits 31, 30 and 28 of the top-level entry
> (all other combinations of bits 31, 30 will point to a  next-level
> table). The page number is in 4K units but must be 4MB aligned.

Let's do that instead of the weird full level contiguation page thing
please

Jason


^ permalink raw reply	[flat|nested] 15+ messages in thread

end of thread, other threads:[~2026-07-13 13:35 UTC | newest]

Thread overview: 15+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-07-12 21:18 [PATCH 0/6] Add support for Broadcom BCM2712 IOMMU driver (Raspberry Pi 5) Daniel Drake
2026-07-12 21:18 ` [PATCH 1/6] generic_pt: allow missing sw bit in DMA_INCOHERENT case Daniel Drake
2026-07-12 22:00   ` Jason Gunthorpe
2026-07-12 21:18 ` [PATCH 2/6] iommupt: allow full-table contiguous leaves in unit tests Daniel Drake
2026-07-12 22:02   ` Jason Gunthorpe
2026-07-12 21:18 ` [PATCH 3/6] dt-bindings: iommu: Add Broadcom BCM2712 IOMMU Daniel Drake
2026-07-13  9:16   ` Krzysztof Kozlowski
2026-07-12 21:18 ` [PATCH 4/6] iommu/generic_pt: Add Broadcom BCM2712 page table format Daniel Drake
2026-07-12 22:05   ` Jason Gunthorpe
2026-07-13 10:02   ` Nick Hollinghurst
2026-07-13 12:00     ` Jason Gunthorpe
2026-07-12 21:18 ` [PATCH 5/6] iommu: Add Broadcom BCM2712 IOMMU driver Daniel Drake
2026-07-12 22:11   ` Jason Gunthorpe
2026-07-12 21:18 ` [PATCH 6/6] arm64: dts: broadcom: bcm2712: Add GPU IOMMU and IOMMU cache nodes Daniel Drake
2026-07-13  9:17   ` Krzysztof Kozlowski

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox