* [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; 19+ 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] 19+ 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; 19+ 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] 19+ 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; 19+ 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] 19+ 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-12 21:23 ` sashiko-bot
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, 2 replies; 19+ 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] 19+ 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 21:36 ` sashiko-bot
` (2 more replies)
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, 3 replies; 19+ 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] 19+ 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 21:34 ` sashiko-bot
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, 2 replies; 19+ 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] 19+ 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-12 21:27 ` sashiko-bot
2026-07-13 9:17 ` Krzysztof Kozlowski
5 siblings, 2 replies; 19+ 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] 19+ 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-12 21:23 ` sashiko-bot
2026-07-13 9:16 ` Krzysztof Kozlowski
1 sibling, 0 replies; 19+ messages in thread
From: sashiko-bot @ 2026-07-12 21:23 UTC (permalink / raw)
To: Daniel Drake; +Cc: conor+dt, robh, devicetree
Thank you for your contribution! Sashiko AI review found 1 potential issue(s) to consider:
- [Medium] The `brcm,bcm2712-iommu` binding unconditionally requires the `brcm,iommu-cache` property, breaking validation compatibility with existing RPi5 firmware.
--
--- Patch [3]: [PATCH 3/6] dt-bindings: iommu: Add Broadcom BCM2712 IOMMU ---
commit abf846fdde20af5f8370795e68ffc7099b1c33db
Author: Daniel Drake <dan@reactivated.net>
dt-bindings: iommu: Add Broadcom BCM2712 IOMMU
This commit adds device tree bindings for the Broadcom BCM2712 IOMMU
and its shared TLB cache. It aims to support hardware address translation
while maintaining compatibility with existing Raspberry Pi 5 firmware.
> diff --git a/Documentation/devicetree/bindings/iommu/brcm,bcm2712-iommu.yaml b/Documentation/devicetree/bindings/iommu/brcm,bcm2712-iommu.yaml
> new file mode 100644
> --- /dev/null
> +++ b/Documentation/devicetree/bindings/iommu/brcm,bcm2712-iommu.yaml
[ ... ]
> +required:
> + - compatible
> + - reg
> + - '#iommu-cells'
> + - brcm,iommu-cache
[Severity: Medium]
Will this cause schema validation to fail for older device trees?
The commit message mentions including compatibility with the cache phandle
used in existing firmware shipped on Raspberry Pi 5. If those existing
device trees use the deprecated "cache" property instead of
"brcm,iommu-cache", unconditionally requiring "brcm,iommu-cache" here
seems like it would break dtbs_check validation for them.
Could this be handled using a oneOf rule to require either
"brcm,iommu-cache" or "cache" to ensure older firmware setups still pass
validation?
--
Sashiko AI review · https://sashiko.dev/#/patchset/20260712-bcm2712-iommu-submit-v1-0-80e10cdde2ea@reactivated.net?part=3
^ permalink raw reply [flat|nested] 19+ 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-12 21:27 ` sashiko-bot
2026-07-13 9:17 ` Krzysztof Kozlowski
1 sibling, 0 replies; 19+ messages in thread
From: sashiko-bot @ 2026-07-12 21:27 UTC (permalink / raw)
To: Daniel Drake; +Cc: devicetree, robh, conor+dt
Thank you for your contribution! Sashiko AI review found 1 potential issue(s) to consider:
- [Low] Device tree node name unit-addresses do not match their `reg` property base addresses for `iommu4` and `iommuc`.
--
--- Patch [6]: [PATCH 6/6] arm64: dts: broadcom: bcm2712: Add GPU IOMMU and IOMMU cache nodes ---
commit e5ff2e7025bd202974b9df36bf104c8bc98df79f
Author: Daniel Drake <dan@reactivated.net>
arm64: dts: broadcom: bcm2712: Add GPU IOMMU and IOMMU cache nodes
This commit adds devicetree nodes for the IOMMU and IOMMU cache on the
Broadcom BCM2712 SoC. It also configures the vc4 GPU to utilize the
newly defined IOMMU.
> diff --git a/arch/arm64/boot/dts/broadcom/bcm2712.dtsi b/arch/arm64/boot/dts/broadcom/bcm2712.dtsi
> index 761c59d90ffc9..ed3178db8acbd 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 {
[Severity: Low]
This isn't a bug, but does this unit-address truncate the upper 32 bits
of the address defined in the reg property below?
The reg property specifies a base address of 0x10 0x5200, which evaluates
to 1000005200. Naming the node iommu@5200 instead of iommu@1000005200
typically triggers dtc simple_bus_reg warnings for mismatched unit
addresses.
> + /* 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 {
[Severity: Low]
Similarly, does this node name truncate the upper 32 bits?
The reg property base address is 0x10 0x5b00 (1000005b00), but the unit
address is written as 5b00.
> + compatible = "brcm,bcm2712-iommuc";
> + reg = <0x10 0x5b00 0x0 0x80>;
> + };
--
Sashiko AI review · https://sashiko.dev/#/patchset/20260712-bcm2712-iommu-submit-v1-0-80e10cdde2ea@reactivated.net?part=6
^ permalink raw reply [flat|nested] 19+ 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 21:34 ` sashiko-bot
2026-07-12 22:11 ` Jason Gunthorpe
1 sibling, 0 replies; 19+ messages in thread
From: sashiko-bot @ 2026-07-12 21:34 UTC (permalink / raw)
To: Daniel Drake; +Cc: robh, conor+dt, devicetree
Thank you for your contribution! Sashiko AI review found 8 potential issue(s) to consider:
- [Critical] IOMMU Grouping Violation leading to Privilege Escalation
- [High] Reference Leak and NULL Pointer Dereference in `bcm2712_iommu_of_xlate()`
- [High] Unhandled Hardware Timeout in TLB Flush Operations
- [High] Premature Publication of `bcm2712_iommu_cache` via drvdata
- [High] Hardware State Modification While MMU is Active
- [High] Detached Domain Sync Re-Enables MMU Unintentionally
- [High] Unaligned Aperture Size Causes Hardware Rejection
- [Medium] `DEFAULT_APERTURE_BASE` Integer Overflow on 32-bit Builds
--
--- Patch [5]: [PATCH 5/6] iommu: Add Broadcom BCM2712 IOMMU driver ---
commit 9729453735ad30531b63496af1c27a9da3cc01e2
Author: Daniel Drake <dan@reactivated.net>
iommu: Add Broadcom BCM2712 IOMMU driver
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.
> diff --git a/drivers/iommu/bcm2712-iommu-cache.c b/drivers/iommu/bcm2712-iommu-cache.c
> --- /dev/null
> +++ b/drivers/iommu/bcm2712-iommu-cache.c
[ ... ]
> +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);
[Severity: High]
Does this code need to handle the -ETIMEDOUT return value?
If the hardware times out, the function silently returns success. During
page unmap operations, the IOMMU core assumes the TLB is clear and
proceeds to free the physical pages, which could lead to memory
corruption if the hardware stall allows continued DMA.
> +
> + 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);
[Severity: High]
Could this lead to a race condition where the drvdata is used before
initialization?
The drvdata is published via platform_set_drvdata() before
cache->reg_base is initialized. If the main IOMMU driver discovers this
cache device concurrently via of_find_device_by_node(), it might
retrieve the drvdata and call bcm2712_iommu_cache_flush(), resulting in
a dereference of the uninitialized cache->reg_base.
> diff --git a/drivers/iommu/bcm2712-iommu.c b/drivers/iommu/bcm2712-iommu.c
> --- /dev/null
> +++ b/drivers/iommu/bcm2712-iommu.c
[ ... ]
> +#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)
[Severity: Medium]
Could this shift operation overflow on 32-bit builds?
On 32-bit builds (which can be compiled via COMPILE_TEST), the unsigned
long type is 32 bits. The bitwise shift (40ul << 30) overflows this type
and evaluates to 0, which would incorrectly set the aperture base
address.
[ ... ]
> +static int bcm2712_iommu_attach_dev(struct iommu_domain *domain,
> + struct device *dev,
> + struct iommu_domain *old)
> +{
[ ... ]
> + /*
> + * 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);
[Severity: High]
Could this calculation truncate if the aperture size is unaligned?
If mmu->aperture_size is not a multiple of 256MB, the bitwise shift
by ADDR_CAP_SHIFT will truncate the remainder. This can lead to an end
capacity that is mathematically lower than the start base, causing the
hardware to reject DMA accesses.
[Severity: High]
Is it safe to modify these hardware registers while the MMU is active?
The page table capacity registers (and page table base below) are being
updated without first clearing the enable bit (MMMU_CTRL_ENABLE). If the
MMU is actively processing DMA transactions during a domain transition,
in-flight requests might be evaluated against a mixed or corrupted state.
> + 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));
[ ... ]
> +static int bcm2712_iommu_sync_range(struct iommu_domain *domain,
> + unsigned long iova, size_t size)
> +{
[ ... ]
> + /* 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);
[Severity: High]
Can this unintentionally re-enable the MMU for a detached domain?
The MMU_WR call unconditionally applies MMMU_CTRL_OPERATING_FLAGS to the
hardware control register. If iommu_unmap() is called on a detached
domain, writing these operating flags would blindly re-enable the MMU.
This could break identity/bypass mode and disrupt active DMA bypass
transactions.
> + readl_poll_timeout_atomic(mmu->reg_base + MMMU_CTRL_OFFSET, val,
> + !(val & MMMU_CTRL_TLB_CLEARING), 0,
> + 1000);
[Severity: High]
Does this code need to handle the -ETIMEDOUT return value?
If the hardware times out, the function silently returns success, masking
hardware failures.
> + } else {
> + bcm2712_iommu_shootdown_range(mmu, iova, size);
> + }
> +
> + spin_unlock_irqrestore(&mmu->hw_lock, flags);
> + return 0;
> +}
[ ... ]
> +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);
[Severity: High]
Is there a missing NULL check and a reference leak here?
of_find_device_by_node() acquires a reference to the device node and can
return NULL if the platform device is not found. Unconditionally passing
this pointer to platform_get_drvdata() would cause a NULL pointer
dereference. Also, the reference acquired by of_find_device_by_node() is
never released.
> +
> + dev_iommu_priv_set(dev, mmu);
> + return 0;
> +}
[ ... ]
> +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,
[Severity: Critical]
Does assigning generic_single_device_group allow isolating devices that
share identical hardware translation state?
The hardware does not distinguish between different devices sharing the
IOMMU. Placing each device in a separate IOMMU group tells the IOMMU
core they are safely isolated.
If one of these devices is passed to a userspace driver (like VFIO), a
new domain attachment will blindly overwrite the shared hardware page
table registers for all connected devices, potentially allowing
unauthorized DMA access to kernel memory.
--
Sashiko AI review · https://sashiko.dev/#/patchset/20260712-bcm2712-iommu-submit-v1-0-80e10cdde2ea@reactivated.net?part=5
^ permalink raw reply [flat|nested] 19+ 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 21:36 ` sashiko-bot
2026-07-12 22:05 ` Jason Gunthorpe
2026-07-13 10:02 ` Nick Hollinghurst
2 siblings, 0 replies; 19+ messages in thread
From: sashiko-bot @ 2026-07-12 21:36 UTC (permalink / raw)
To: Daniel Drake; +Cc: devicetree, robh, conor+dt
Thank you for your contribution! Sashiko AI review found 4 potential issue(s) to consider:
- [High] Physical addresses for page tables are silently truncated if they exceed 40 bits.
- [High] Installing a contiguous block for an unsupported page size results in mismatched PAGESIZE hints and partial unmapping.
- [Low] Unnecessary Kconfig dependency on !GENERIC_ATOMIC64 restricts compilation on 32-bit architectures.
- [Low] Missing module test dependency in IOMMU_PT_KUNIT_TEST.
--
--- Patch [4]: [PATCH 4/6] iommu/generic_pt: Add Broadcom BCM2712 page table format ---
commit de6027129434bac44f0964115b67d7adb9dca294
Author: Daniel Drake <dan@reactivated.net>
iommu/generic_pt: Add Broadcom BCM2712 page table format
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.
> diff --git a/drivers/iommu/generic_pt/Kconfig b/drivers/iommu/generic_pt/Kconfig
> index f4ed1add58b74..5c01cebb365fa 100644
> --- a/drivers/iommu/generic_pt/Kconfig
> +++ b/drivers/iommu/generic_pt/Kconfig
[ ... ]
> +config IOMMU_PT_BCM2712
> + tristate "IOMMU page table for Broadcom BCM2712"
> + depends on !GENERIC_ATOMIC64 # for cmpxchg64
[Severity: Low]
Is this dependency necessary? The bcm2712 format appears to use 32-bit entries
(pt_bcm2712_entry_t) and pt_table_install32(), which only requires standard
32-bit cmpxchg.
It doesn't seem to use cmpxchg64, and this dependency artificially restricts
compilation on 32-bit architectures during COMPILE_TEST.
> + 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
[Severity: Low]
Does this test need a conditional dependency for the new BCM2712 format?
If IOMMU_PT_BCM2712 is compiled as a module, but this test is built-in (=y),
it might cause a linker failure. Other formats typically have a dependency
list like "depends on IOMMU_PT_X86_64 || !IOMMU_PT_X86_64" to ensure the test
degrades to a module if any supported format is a module.
> diff --git a/drivers/iommu/generic_pt/fmt/bcm2712.h b/drivers/iommu/generic_pt/fmt/bcm2712.h
> new file mode 100644
> index 0000000000000..e62d59181bfe5
> --- /dev/null
> +++ b/drivers/iommu/generic_pt/fmt/bcm2712.h
[ ... ]
> +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)
> +{
[ ... ]
> + 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);
> + }
> +}
[Severity: High]
If a driver requests a contiguous leaf entry for an unsupported size, does
this logic leave the PAGESIZE hint as 0 while still writing the full block
of PTEs?
If the layout is initialized with an incoming cfg->pgsize_bitmap containing
sizes this format cannot encode, the hint is never set here. Later, when
unmapping, bcm2712pt_entry_num_contig_lg2() will read the 0 hint, assume it
is a single 4KB page, and fail to clear the remainder of the block.
Could this result in stale mappings remaining active in the hardware and
causing memory corruption or use-after-free via DMA?
[ ... ]
> +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);
> +}
[Severity: High]
Can this silently truncate physical addresses larger than 40 bits?
The framework allocates page tables using standard GFP_KERNEL memory, which
can exceed 1TB on generic ARM64 kernels. Since the BCM2712 format encodes
a 28-bit PFN, and FIELD_PREP silently masks out upper bits, an address
above 1TB could be truncated. This could result in programming an incorrect
physical address into the hardware table.
[ ... ]
> +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;
> +}
[Severity: High]
Should this pgsize_bitmap be sanitized or masked against the specific sizes
the bcm2712 format can actually encode (4K, bigpage, superpage, 4M)?
Implicitly trusting the incoming configuration is what allows the unsupported
page size issue in bcm2712pt_install_leaf_entry() to occur.
--
Sashiko AI review · https://sashiko.dev/#/patchset/20260712-bcm2712-iommu-submit-v1-0-80e10cdde2ea@reactivated.net?part=4
^ permalink raw reply [flat|nested] 19+ 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; 19+ 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] 19+ 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; 19+ 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] 19+ 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 21:36 ` sashiko-bot
@ 2026-07-12 22:05 ` Jason Gunthorpe
2026-07-13 10:02 ` Nick Hollinghurst
2 siblings, 0 replies; 19+ 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] 19+ 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 21:34 ` sashiko-bot
@ 2026-07-12 22:11 ` Jason Gunthorpe
1 sibling, 0 replies; 19+ 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] 19+ 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-12 21:23 ` sashiko-bot
@ 2026-07-13 9:16 ` Krzysztof Kozlowski
1 sibling, 0 replies; 19+ 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] 19+ 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-12 21:27 ` sashiko-bot
@ 2026-07-13 9:17 ` Krzysztof Kozlowski
1 sibling, 0 replies; 19+ 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] 19+ 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 21:36 ` sashiko-bot
2026-07-12 22:05 ` Jason Gunthorpe
@ 2026-07-13 10:02 ` Nick Hollinghurst
2026-07-13 12:00 ` Jason Gunthorpe
2 siblings, 1 reply; 19+ 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] 19+ 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; 19+ 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] 19+ messages in thread
end of thread, other threads:[~2026-07-13 12:00 UTC | newest]
Thread overview: 19+ 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-12 21:23 ` sashiko-bot
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 21:36 ` sashiko-bot
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 21:34 ` sashiko-bot
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-12 21:27 ` sashiko-bot
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