[PATCH v3 4/5] iommu: Add Broadcom BCM2712 IOMMU driver
Florian Fainelli
florian.fainelli at broadcom.com
Wed Aug 26 11:33:49 PDT 2026
On 8/25/26 13:56, Daniel Drake wrote:
> 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.
>
> The driver uses the generic_pt framework to manage the 2-level page
> tables. It also provides support for the centralized L2 TLB (IOMMUC),
> which is shared across the SoC's IOMMU instances and must be flushed
> when modifying or unmapping page table entries.
>
> Adapted from Raspberry Pi's downstream bcm2712-iommu driver
> (original author Nick Hollinghurst).
>
> Signed-off-by: Daniel Drake <dan at reactivated.net>
> ---
> drivers/iommu/Kconfig | 15 +
> drivers/iommu/Makefile | 1 +
> drivers/iommu/bcm2712-iommu-cache.c | 83 ++++++
> drivers/iommu/bcm2712-iommu-cache.h | 9 +
> drivers/iommu/bcm2712-iommu.c | 556 ++++++++++++++++++++++++++++++++++++
> 5 files changed, 664 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..040cfc4ccf9f
> --- /dev/null
> +++ b/drivers/iommu/bcm2712-iommu-cache.c
> @@ -0,0 +1,83 @@
> +// SPDX-License-Identifier: GPL-2.0-only
> +/*
> + * IOMMU driver for BCM2712 TLB cache
> + *
> + * Copyright (c) 2023 Raspberry Pi Ltd.
> + * Copyright (c) 2026 Daniel Drake
> + *
> + * The BCM2712 IOMMUC is a centralized TLB which accelerates address translation
> + * across the SoC's IOMMU devices. If an address mapping is not found in the
> + * IOMMU's local TLB cache, then this IOMMUC is consulted. The IOMMUC must be
> + * explicitly invalidated when modifying or unmapping IOMMU page tables.
> + */
> +
> +#include <linux/cleanup.h>
> +#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 {
> + struct device *dev;
> + spinlock_t hw_lock;
> + void __iomem *reg_base;
> +};
> +
> +#define MMUC_CONTROL_ENABLE 1
> +#define MMUC_CONTROL_FLUSH 2
> +#define MMUC_CONTROL_FLUSHING 4
Those should be BIT(0), BIT(1) and BIT(2) respectively.
> +
> +void bcm2712_iommu_cache_flush(struct bcm2712_iommu_cache *cache)
> +{
> + u32 val;
> + int ret;
> +
> + scoped_guard(spinlock_irqsave, &cache->hw_lock) {
> + writel(MMUC_CONTROL_ENABLE | MMUC_CONTROL_FLUSH,
> + cache->reg_base);
> +
> + ret = readl_poll_timeout_atomic(cache->reg_base, val,
> + !(val & MMUC_CONTROL_FLUSHING),
> + 0, 50);
> + }
> +
> + if (ret)
> + dev_err_ratelimited(cache->dev, "cache flush timed out\n");
> +}
> +
> +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;
> +
> + cache->dev = &pdev->dev;
> + 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);
> +
> + platform_set_drvdata(pdev, cache);
> + 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..1bb456a83df5
> --- /dev/null
> +++ b/drivers/iommu/bcm2712-iommu.c
> @@ -0,0 +1,556 @@
> +// SPDX-License-Identifier: GPL-2.0-only
> +/*
> + * IOMMU driver for Broadcom BCM2712
> + *
> + * Copyright (c) 2023-2025 Raspberry Pi Ltd.
> + * Copyright (c) 2026 Daniel Drake
> + *
> + * Each BCM2712 IOMMU has multiple devices hardwired into it, whose
> + * DMA transactions all route through the IOMMU. There is no stream ID tagging
> + * or any other kind of segmentation to differentiate between requests from
> + * different devices. It is also not possible to toggle a specific device
> + * between iommu-mapped and bypass modes.
> + *
> + * The hardware supports the simultaneous configuration of a bypass window
> + * (where memory accesses operate in identity mode) and a translation aperture
> + * (supporting page mapping). While combining these two would allow for
> + * GART-like operation, this driver's paging domain implementation configures
> + * only the translation aperture (starting at IOVA 0) offering a degree of
> + * memory protection.
> + *
> + * The page table format is a two-level format handled by generic_pt/bcm2712.
> + */
> +
> +#include <linux/bitfield.h>
> +#include <linux/cleanup.h>
> +#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 "bcm2712-iommu-cache.h"
> +#include "dma-iommu.h"
> +#include "iommu-pages.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)
> +
> +#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)
> +
> +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;
> + 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))
I would prefer using static inline wrappers here because it gets super
easy to just instrument those with debug prints by adding a couple of
lines, it also gives you type validation at the same time.
[snip]
> +static struct iommu_domain bcm2712_blocking_domain = {
> + .type = IOMMU_DOMAIN_BLOCKED,
> + .ops = &(const struct iommu_domain_ops) {
> + .attach_dev = bcm2712_iommu_blocking_attach,
> + },
> +};
> +
> +static int bcm2712_iommu_enable_and_clear_tlb(struct bcm2712_iommu *mmu)
Maybe add __must_hold() here to indicate when this is called?
The rest looks good to me, but I defer to Jim for the functional review.
--
Florian
More information about the linux-arm-kernel
mailing list