summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorLinus Torvalds <torvalds@linux-foundation.org>2026-10-23 14:48:26 -1000
committerLinus Torvalds <torvalds@linux-foundation.org>2026-10-23 14:48:26 -1000
commit4da9dedf3fc59137fd86d664693776aa38cb97f5 (patch)
tree9470f80ec290e88346f39407a8669f187306e340
parent906b6e1d8c782a8442b30bdf8b8992f5031ffc59 (diff)
parent20bfd25757659ff8229eeed6f048c76f9a11a4a3 (diff)
Merge tag 'iommu-updates-v7.4' of git://git.kernel.org/pub/scm/linux/kernel/git/iommu/linux
Pull IOMMU updates from Joerg Roedel: "Core: - Detect scatterlist length overflows and finalize deferred domain attachments before mapping MSI pages - Consolidate x86 MSI reserved-region handling and clean up internal DMA and SVA helpers AMD IOMMU: - Add IOMMU PerfOpt support, enabled by amdgpu for supported integrated GPUs in identity domains - Restrict forced identity mapping to GPUs that require it - Fix kdump device-table allocation and reuse, ATS configuration error handling, and an interrupt-remapping allocation leak - Clean up device probing and initialization - Add Vasant Hegde as an AMD IOMMU maintainer ARM SMMUv2: - Device-tree binding updates for Qualcomm Maili and Nord SoCs ARM SMMUv3: - Work around hardware integration issue on Google Tensor G5 SoCs where all DMA transactions appear as instruction fetches - Allow command queue size to be configured on the kernel command-line - Reduce queue sizes when running as a kdump kernel - Fix some pre-existing issues in the driver and kunit tests reported by Sashiko - Fix handling of Arm hardware errata relating to the contiguous hint when using SVA - Rework the TLB invalidation logic in preparation for enabling iommu-pt in future - Minor cleanups in preparation for improving support for kdump Apple DART: - Support device-tree DMA aperture specifications Broadcom: - Add BCM2712 IOMMU support for Raspberry Pi 5 display and multimedia devices, including the generic page-table format and shared TLB cache management Hyper-V: - Add a para-virtualized guest IOMMU driver with guest-managed stage-1 page tables and page-selective IOTLB invalidation Intel VT-d: - Enhanced IQE fault handling for batched multiple descriptors, - Optimization of page-table level calculation in compute_vasz_lg2_ss(), - Fixes for several pre-existing issues reported by Sashiko, - General cleanup and refinement RISC-V: - Report cache-coherency capability for coherent devices, enabling VFIO/iommufd use Samsung Exynos: - Support SysMMUs without BLOCK mode, fixing their enable sequence and TLB invalidation - Improve v7 fault diagnostics with transaction information" * tag 'iommu-updates-v7.4' of git://git.kernel.org/pub/scm/linux/kernel/git/iommu/linux: (69 commits) MAINTAINERS: Make Vasant Hegde AMD IOMMU maintainer iommu/hyperv: Make hv_iommu_device static iommu/hyperv: Add missing PCI dependency for the guest IOMMU driver iommu/amd: Don't allocate kdump device table from DMA32 iommu/amd: Program device table when kdump reuse fails iommu/dma: Clean up redundant MSI cookie check iommu/dma: Catch scatterlist length overflows iommu/hyperv: Add page-selective IOTLB flush support iommu/hyperv: Add para-virtualized IOMMU support for Hyper-V guest iommu/x86: Add architectural MSI reserved region helper Drivers: hv: Add logical device ID registry for vPCI devices hyperv: Introduce new hypercall interfaces used by Hyper-V guest IOMMU iommu: Make iommu-dma helpers private iommu/arm-smmu-v3: Don't rb_erase() a never-inserted stream node iommu/arm-smmu-v3: Add Q_POS() macro iommu/arm-smmu-v3: Add arm_smmu_attach_release() iommu/arm-smmu-v3: Disable the impl before disabling the SMMU on shutdown iommu/arm-smmu-v3: Populate the tlbi at the top of the call chain iommu/arm-smmu-v3: Precompute the invalidation commands iommu/arm-smmu-v3: Keep track in arm_smmu_invs if range invalidation is used ...
-rw-r--r--Documentation/arch/arm64/silicon-errata.rst3
-rw-r--r--Documentation/devicetree/bindings/display/brcm,bcm2835-hvs.yaml3
-rw-r--r--Documentation/devicetree/bindings/iommu/apple,dart.yaml12
-rw-r--r--Documentation/devicetree/bindings/iommu/arm,smmu-v3.yaml10
-rw-r--r--Documentation/devicetree/bindings/iommu/arm,smmu.yaml4
-rw-r--r--Documentation/devicetree/bindings/iommu/brcm,bcm2712-iommu.yaml54
-rw-r--r--Documentation/devicetree/bindings/iommu/brcm,bcm2712-iommuc.yaml40
-rw-r--r--MAINTAINERS14
-rw-r--r--arch/x86/hyperv/hv_init.c4
-rw-r--r--arch/x86/include/asm/iommu.h20
-rw-r--r--arch/x86/include/asm/mshyperv.h4
-rw-r--r--drivers/gpu/drm/amd/amdgpu/amdgpu.h1
-rw-r--r--drivers/gpu/drm/amd/amdgpu/amdgpu_device.c50
-rw-r--r--drivers/gpu/drm/amd/amdgpu/amdgpu_drv.c12
-rw-r--r--drivers/hv/hv_common.c115
-rw-r--r--drivers/iommu/Kconfig18
-rw-r--r--drivers/iommu/Makefile1
-rw-r--r--drivers/iommu/amd/amd_iommu.h3
-rw-r--r--drivers/iommu/amd/amd_iommu_types.h7
-rw-r--r--drivers/iommu/amd/init.c192
-rw-r--r--drivers/iommu/amd/iommu.c438
-rw-r--r--drivers/iommu/apple-dart.c74
-rw-r--r--drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3-iommufd.c1
-rw-r--r--drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3-sva.c29
-rw-r--r--drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3-test.c57
-rw-r--r--drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3.c604
-rw-r--r--drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3.h77
-rw-r--r--drivers/iommu/arm/arm-smmu-v3/tegra241-cmdqv.c2
-rw-r--r--drivers/iommu/bcm2712-iommu-cache.c84
-rw-r--r--drivers/iommu/bcm2712-iommu-cache.h9
-rw-r--r--drivers/iommu/bcm2712-iommu.c550
-rw-r--r--drivers/iommu/dma-iommu.c32
-rw-r--r--drivers/iommu/exynos-iommu.c37
-rw-r--r--drivers/iommu/generic_pt/.kunitconfig1
-rw-r--r--drivers/iommu/generic_pt/Kconfig10
-rw-r--r--drivers/iommu/generic_pt/fmt/Makefile2
-rw-r--r--drivers/iommu/generic_pt/fmt/bcm2712.h288
-rw-r--r--drivers/iommu/generic_pt/fmt/defs_bcm2712.h18
-rw-r--r--drivers/iommu/generic_pt/fmt/iommu_bcm2712.c6
-rw-r--r--drivers/iommu/generic_pt/kunit_iommu_pt.h2
-rw-r--r--drivers/iommu/hyperv/Kconfig15
-rw-r--r--drivers/iommu/hyperv/Makefile1
-rw-r--r--drivers/iommu/hyperv/hv-iommu-guest.c682
-rw-r--r--drivers/iommu/hyperv/iommu.h44
-rw-r--r--drivers/iommu/intel/dmar.c72
-rw-r--r--drivers/iommu/intel/iommu.c167
-rw-r--r--drivers/iommu/intel/iommu.h17
-rw-r--r--drivers/iommu/intel/nested.c2
-rw-r--r--drivers/iommu/iommu-priv.h4
-rw-r--r--drivers/iommu/iommu-sva.c14
-rw-r--r--drivers/iommu/iommu.c4
-rw-r--r--drivers/iommu/riscv/iommu.c12
-rw-r--r--drivers/pci/controller/pci-hyperv.c21
-rw-r--r--include/asm-generic/mshyperv.h4
-rw-r--r--include/hyperv/hvgdk_mini.h10
-rw-r--r--include/hyperv/hvhdk_mini.h155
-rw-r--r--include/linux/amd-iommu.h13
-rw-r--r--include/linux/generic_pt/common.h6
-rw-r--r--include/linux/generic_pt/iommu.h12
-rw-r--r--include/linux/hyperv.h8
-rw-r--r--include/linux/iommu.h2
61 files changed, 3690 insertions, 463 deletions
diff --git a/Documentation/arch/arm64/silicon-errata.rst b/Documentation/arch/arm64/silicon-errata.rst
index d78225079dbe..afe109afe692 100644
--- a/Documentation/arch/arm64/silicon-errata.rst
+++ b/Documentation/arch/arm64/silicon-errata.rst
@@ -276,7 +276,8 @@ stable kernels.
+----------------+-----------------+-----------------+-----------------------------+
| ARM | MMU L1 | #3878312 | N/A |
+----------------+-----------------+-----------------+-----------------------------+
-| ARM | MMU S3 | #3995052 | N/A |
+| ARM | MMU S3 | #3995052, | N/A |
+| | | #3673557 | |
+----------------+-----------------+-----------------+-----------------------------+
| ARM | GIC-700 | #2941627 | ARM64_ERRATUM_2941627 |
+----------------+-----------------+-----------------+-----------------------------+
diff --git a/Documentation/devicetree/bindings/display/brcm,bcm2835-hvs.yaml b/Documentation/devicetree/bindings/display/brcm,bcm2835-hvs.yaml
index 9aca38a58a16..8f8aae9f3788 100644
--- a/Documentation/devicetree/bindings/display/brcm,bcm2835-hvs.yaml
+++ b/Documentation/devicetree/bindings/display/brcm,bcm2835-hvs.yaml
@@ -35,6 +35,9 @@ properties:
minItems: 1
maxItems: 2
+ iommus:
+ maxItems: 1
+
required:
- compatible
- reg
diff --git a/Documentation/devicetree/bindings/iommu/apple,dart.yaml b/Documentation/devicetree/bindings/iommu/apple,dart.yaml
index 47ec7fa52c3a..6efbe732b4ce 100644
--- a/Documentation/devicetree/bindings/iommu/apple,dart.yaml
+++ b/Documentation/devicetree/bindings/iommu/apple,dart.yaml
@@ -44,10 +44,12 @@ properties:
Optional since not all IOMMUs are attached to a clock gate.
'#iommu-cells':
- const: 1
+ enum: [ 1, 5 ]
description:
- Has to be one. The single cell describes the stream id emitted by
- a master to the IOMMU.
+ Has to be one or five. The first cell describes the stream id emitted by
+ a master to the IOMMU. The optional second and third cell describe the
+ aperture start of a master's DMA window and fourth and fifth cell describe
+ the size of DMA window.
power-domains:
maxItems: 1
@@ -66,11 +68,11 @@ examples:
compatible = "apple,t8103-dart";
reg = <0x82f80000 0x4000>;
interrupts = <1 781 4>;
- #iommu-cells = <1>;
+ #iommu-cells = <5>;
};
master1 {
- iommus = <&dart1 0>;
+ iommus = <&dart1 0 0x100 0x0 0x10 0x0>;
};
- |+
diff --git a/Documentation/devicetree/bindings/iommu/arm,smmu-v3.yaml b/Documentation/devicetree/bindings/iommu/arm,smmu-v3.yaml
index 82957334bea2..3c044e4f03d4 100644
--- a/Documentation/devicetree/bindings/iommu/arm,smmu-v3.yaml
+++ b/Documentation/devicetree/bindings/iommu/arm,smmu-v3.yaml
@@ -83,6 +83,16 @@ properties:
register access with page 0 offsets. Set for Cavium ThunderX2 silicon that
doesn't support SMMU page1 register space.
+ arm,instdata-override:
+ type: boolean
+ description:
+ Some SoCs like Google Tensor G5 (Laguna) have an SMMU integration
+ silicon erratum where AxPROT[2] signal is always 1, making all
+ transactions appear as instructions.
+
+ This property enables a software workaround to override the
+ instruction/data attribute of incoming traffic to data.
+
allOf:
- if:
not:
diff --git a/Documentation/devicetree/bindings/iommu/arm,smmu.yaml b/Documentation/devicetree/bindings/iommu/arm,smmu.yaml
index ffa33b7f4a96..6748351c7812 100644
--- a/Documentation/devicetree/bindings/iommu/arm,smmu.yaml
+++ b/Documentation/devicetree/bindings/iommu/arm,smmu.yaml
@@ -101,7 +101,9 @@ properties:
- qcom,glymur-smmu-500
- qcom,hawi-smmu-500
- qcom,kaanapali-smmu-500
+ - qcom,maili-smmu-500
- qcom,milos-smmu-500
+ - qcom,nord-smmu-500
- qcom,qcm2290-smmu-500
- qcom,qcs615-smmu-500
- qcom,qcs8300-smmu-500
@@ -579,6 +581,8 @@ allOf:
- qcom,glymur-smmu-500
- qcom,hawi-smmu-500
- qcom,kaanapali-smmu-500
+ - qcom,maili-smmu-500
+ - qcom,nord-smmu-500
- qcom,sm8750-smmu-500
- const: qcom,adreno-smmu
- const: qcom,smmu-500
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..5ca8ce469ec3
--- /dev/null
+++ b/Documentation/devicetree/bindings/iommu/brcm,bcm2712-iommu.yaml
@@ -0,0 +1,54 @@
+# 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>
+ - Florian Fainelli <florian.fainelli@broadcom.com>
+ - Jim Quinlan <james.quinlan@broadcom.com>
+
+description:
+ The BCM2712 SoC features multiple independent IOMMU instances providing
+ address translation for multimedia hardware blocks, such as the VC6 display
+ pipeline, camera receivers, and ISP. Each IOMMU has its own page tables,
+ control registers, and local TLB, and is additionally linked to a centralized
+ L2 TLB (IOMMUC).
+
+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). Each IOMMU instance caches
+ translated descriptors in this centralized L2 TLB cache block and must
+ trigger cache invalidation commands on it when updating or unmapping page
+ table entries.
+
+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>;
+ };
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..47c584ad667a
--- /dev/null
+++ b/Documentation/devicetree/bindings/iommu/brcm,bcm2712-iommuc.yaml
@@ -0,0 +1,40 @@
+# 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>
+ - Florian Fainelli <florian.fainelli@broadcom.com>
+ - Jim Quinlan <james.quinlan@broadcom.com>
+
+description:
+ The BCM2712 IOMMUC is a centralized Translation Lookaside Buffer (TLB) cache
+ 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 as a L2 TLB. The SoC includes a single IOMMUC which is
+ shared between all IOMMUs, and it must be explicitly invalidated when
+ modifying or unmapping IOMMU page tables.
+
+properties:
+ compatible:
+ const: brcm,bcm2712-iommuc
+
+ reg:
+ maxItems: 1
+
+required:
+ - compatible
+ - reg
+
+additionalProperties: false
+
+examples:
+ - |
+ iommuc@5b00 {
+ compatible = "brcm,bcm2712-iommuc";
+ reg = <0x5b00 0x80>;
+ };
diff --git a/MAINTAINERS b/MAINTAINERS
index c06f28684c4c..5121b3242d1c 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -1191,8 +1191,8 @@ F: drivers/platform/x86/amd/hfi/
AMD IOMMU (AMD-VI)
M: Joerg Roedel (AMD) <joro@8bytes.org>
+M: Vasant Hegde <vasant.hegde@amd.com>
R: Suravee Suthikulpanit <suravee.suthikulpanit@amd.com>
-R: Vasant Hegde <vasant.hegde@amd.com>
L: iommu@lists.linux.dev
S: Maintained
T: git git://git.kernel.org/pub/scm/linux/kernel/git/iommu/linux.git
@@ -5188,6 +5188,18 @@ N: bcm2712
N: bcm283*
N: raspberrypi
+BROADCOM BCM2712 IOMMU DRIVER
+M: Daniel Drake <dan@reactivated.net>
+M: Florian Fainelli <florian.fainelli@broadcom.com>
+M: Jim Quinlan <james.quinlan@broadcom.com>
+R: Broadcom internal kernel review list <bcm-kernel-feedback-list@broadcom.com>
+L: iommu@lists.linux.dev
+L: linux-rpi-kernel@lists.infradead.org (moderated for non-subscribers)
+S: Maintained
+F: Documentation/devicetree/bindings/iommu/brcm,bcm2712-iommu*
+F: drivers/iommu/bcm2712-iommu*
+F: drivers/iommu/generic_pt/fmt/*bcm2712*
+
BROADCOM BCM281XX/BCM11XXX/BCM216XX ARM ARCHITECTURE
M: Florian Fainelli <florian.fainelli@broadcom.com>
M: Ray Jui <rjui@broadcom.com>
diff --git a/arch/x86/hyperv/hv_init.c b/arch/x86/hyperv/hv_init.c
index 9900a5c59fd3..b90e6f487533 100644
--- a/arch/x86/hyperv/hv_init.c
+++ b/arch/x86/hyperv/hv_init.c
@@ -579,6 +579,10 @@ skip_hypercall_pg_init:
old_setup_percpu_clockev = x86_init.timers.setup_percpu_clockev;
x86_init.timers.setup_percpu_clockev = hv_stimer_setup_percpu_clockev;
+#ifdef CONFIG_HYPERV_PVIOMMU
+ x86_init.iommu.iommu_init = hv_iommu_init;
+#endif
+
hv_apic_init();
x86_init.pci.arch_init = hv_pci_init;
diff --git a/arch/x86/include/asm/iommu.h b/arch/x86/include/asm/iommu.h
index 3be2451e7bc8..4e1e55c3c09a 100644
--- a/arch/x86/include/asm/iommu.h
+++ b/arch/x86/include/asm/iommu.h
@@ -4,6 +4,11 @@
#include <linux/acpi.h>
+#ifdef CONFIG_IOMMU_API
+#include <linux/iommu.h>
+#include <linux/sizes.h>
+#endif
+
#include <asm/e820/api.h>
extern int force_iommu, no_iommu;
@@ -21,6 +26,21 @@ extern bool x86_swiotlb_enable;
/* 10 seconds */
#define DMAR_OPERATION_TIMEOUT ((cycles_t) tsc_khz*10*1000)
+#ifdef CONFIG_IOMMU_API
+static inline int iommu_add_resv_x86_msi_region(struct list_head *head)
+{
+ struct iommu_resv_region *region;
+
+ region = iommu_alloc_resv_region(0xfee00000, SZ_1M, 0,
+ IOMMU_RESV_MSI, GFP_KERNEL);
+ if (!region)
+ return -ENOMEM;
+
+ list_add_tail(&region->list, head);
+ return 0;
+}
+#endif
+
static inline int __init
arch_rmrr_sanity_check(struct acpi_dmar_reserved_memory *rmrr)
{
diff --git a/arch/x86/include/asm/mshyperv.h b/arch/x86/include/asm/mshyperv.h
index f64393e853ee..20d947c2c758 100644
--- a/arch/x86/include/asm/mshyperv.h
+++ b/arch/x86/include/asm/mshyperv.h
@@ -313,6 +313,10 @@ static inline void mshv_vtl_return_hypercall(void) {}
static inline void __mshv_vtl_return_call(struct mshv_vtl_cpu_context *vtl0) {}
#endif
+#ifdef CONFIG_HYPERV_PVIOMMU
+int __init hv_iommu_init(void);
+#endif
+
#include <asm-generic/mshyperv.h>
#endif
diff --git a/drivers/gpu/drm/amd/amdgpu/amdgpu.h b/drivers/gpu/drm/amd/amdgpu/amdgpu.h
index a9c6f5d4a639..0e53a02ad1ba 100644
--- a/drivers/gpu/drm/amd/amdgpu/amdgpu.h
+++ b/drivers/gpu/drm/amd/amdgpu/amdgpu.h
@@ -156,6 +156,7 @@ struct amdgpu_watchdog_timer {
* Modules parameters.
*/
extern int amdgpu_modeset;
+extern int amdgpu_iommu_perfopt;
extern unsigned int amdgpu_vram_limit;
extern int amdgpu_vis_vram_limit;
extern int amdgpu_gart_size;
diff --git a/drivers/gpu/drm/amd/amdgpu/amdgpu_device.c b/drivers/gpu/drm/amd/amdgpu/amdgpu_device.c
index ef9ff30c0a7b..1f67ad0233d5 100644
--- a/drivers/gpu/drm/amd/amdgpu/amdgpu_device.c
+++ b/drivers/gpu/drm/amd/amdgpu/amdgpu_device.c
@@ -33,6 +33,7 @@
#include <linux/console.h>
#include <linux/slab.h>
#include <linux/iommu.h>
+#include <linux/amd-iommu.h>
#include <linux/pci.h>
#include <linux/pci-p2pdma.h>
#include <linux/apple-gmux.h>
@@ -3751,6 +3752,28 @@ amdgpu_device_should_register_switcheroo(struct amdgpu_device *adev, bool px)
apple_gmux_detect(NULL, NULL)));
}
+static inline bool amdgpu_device_identity(struct amdgpu_device *adev)
+{
+ struct pci_dev *pdev = adev->pdev;
+ struct iommu_domain *domain = iommu_get_domain_for_dev(&pdev->dev);
+
+ if (!domain)
+ return false;
+
+ return domain->type == IOMMU_DOMAIN_IDENTITY;
+}
+
+static bool amdgpu_device_use_perfopt(struct amdgpu_device *adev)
+{
+ if (amdgpu_iommu_perfopt == 0)
+ return false;
+
+ if (!(adev->flags & AMD_IS_APU))
+ return false;
+
+ return amdgpu_device_identity(adev);
+}
+
/**
* amdgpu_device_init - initialize the driver
*
@@ -3958,6 +3981,16 @@ int amdgpu_device_init(struct amdgpu_device *adev,
if (r)
return r;
+ if (amdgpu_device_use_perfopt(adev)) {
+ int perfopt_ret = amd_iommu_enable_perfopt(pdev);
+
+ /* Optional optimization; a failure to arm it must not abort probe. */
+ if (perfopt_ret)
+ dev_warn(adev->dev,
+ "Failed to enable IOMMU PerfOpt (%d); continuing without it\n",
+ perfopt_ret);
+ }
+
/*
* No need to remove conflicting FBs for non-display class devices.
* This prevents the sysfb from being freed accidently.
@@ -4327,6 +4360,9 @@ void amdgpu_device_fini_hw(struct amdgpu_device *adev)
amdgpu_gart_dummy_page_fini(adev);
+ if (amdgpu_device_use_perfopt(adev))
+ amd_iommu_disable_perfopt(adev->pdev);
+
if (pci_dev_is_disconnected(adev->pdev))
amdgpu_device_unmap_mmio(adev);
@@ -4690,6 +4726,20 @@ int amdgpu_device_resume(struct drm_device *dev, bool notify_clients)
if (dev->switch_power_state == DRM_SWITCH_POWER_OFF)
return 0;
+ if (amdgpu_device_use_perfopt(adev)) {
+ int perfopt_ret = amd_iommu_enable_perfopt(adev->pdev);
+
+ /*
+ * Must not return on failure: a bare return would leak the
+ * SR-IOV VF exclusive-mode acquisition taken above (released
+ * via the exit: path).
+ */
+ if (perfopt_ret)
+ dev_warn(adev->dev,
+ "Failed to enable IOMMU PerfOpt (%d); continuing without it\n",
+ perfopt_ret);
+ }
+
if (adev->in_s0ix)
amdgpu_dpm_gfx_state_change(adev, sGpuChangeState_D0Entry);
diff --git a/drivers/gpu/drm/amd/amdgpu/amdgpu_drv.c b/drivers/gpu/drm/amd/amdgpu/amdgpu_drv.c
index d9d794a7db96..d27f7677a520 100644
--- a/drivers/gpu/drm/amd/amdgpu/amdgpu_drv.c
+++ b/drivers/gpu/drm/amd/amdgpu/amdgpu_drv.c
@@ -185,6 +185,7 @@ char *amdgpu_disable_cu;
char *amdgpu_virtual_display;
int amdgpu_enforce_isolation = -1;
int amdgpu_modeset = -1;
+int amdgpu_iommu_perfopt = -1;
/* Specifies the default granularity for SVM, used in buffer
* migration and restoration of backing memory when handling
@@ -393,6 +394,17 @@ MODULE_PARM_DESC(aspm, "ASPM support (1 = enable, 0 = disable, -1 = auto)");
module_param_named(aspm, amdgpu_aspm, int, 0444);
/**
+ * DOC: iommu_perfopt (int)
+ * Control the AMD IOMMU PerfOpt DMA-latency optimization
+ * (0 = disable; -1 = enable on supported devices).
+ * This arms the IOMMU PerfOpt control (IOMMU spec, MMIO Offset 016Ch, EFR PerfOptSup / PerfOptEn)
+ * Arming it disables ATS, PRI, PASID and SVA for the GPU and removes IOMMU DMA containment for it,
+ * trading isolation for lower DMA latency.
+ */
+MODULE_PARM_DESC(iommu_perfopt, "Control IOMMU PerfOpt DMA-latency optimization (-1 = enable on supported devices, 0 = disable)");
+module_param_named(iommu_perfopt, amdgpu_iommu_perfopt, int, 0444);
+
+/**
* DOC: runpm (int)
* Override for runtime power management control for dGPUs. The amdgpu driver can dynamically power down
* the dGPUs when they are idle if supported. The default is -1 (auto enable).
diff --git a/drivers/hv/hv_common.c b/drivers/hv/hv_common.c
index 31256cb22b39..4f3bb9b4e87c 100644
--- a/drivers/hv/hv_common.c
+++ b/drivers/hv/hv_common.c
@@ -21,6 +21,7 @@
#include <linux/panic_notifier.h>
#include <linux/ptrace.h>
#include <linux/random.h>
+#include <linux/rhashtable.h>
#include <linux/efi.h>
#include <linux/kdebug.h>
#include <linux/kmsg_dump.h>
@@ -78,6 +79,22 @@ static struct ctl_table_header *hv_ctl_table_hdr;
u8 * __percpu *hv_synic_eventring_tail;
EXPORT_SYMBOL_GPL(hv_synic_eventring_tail);
+struct hv_pci_busdata {
+ int pci_domain_nr;
+ u32 logical_dev_id_prefix;
+ struct rhash_head node;
+ struct rcu_head rcu;
+};
+
+static struct rhashtable hv_pci_bus_ht;
+static bool hv_pci_bus_ht_initialized;
+
+static const struct rhashtable_params hv_pci_bus_ht_params = {
+ .key_len = sizeof_field(struct hv_pci_busdata, pci_domain_nr),
+ .key_offset = offsetof(struct hv_pci_busdata, pci_domain_nr),
+ .head_offset = offsetof(struct hv_pci_busdata, node),
+};
+
/*
* Hyper-V specific initialization and shutdown code that is
* common across all architectures. Called from architecture
@@ -86,6 +103,11 @@ EXPORT_SYMBOL_GPL(hv_synic_eventring_tail);
void __init hv_common_free(void)
{
+ if (hv_pci_bus_ht_initialized) {
+ rhashtable_destroy(&hv_pci_bus_ht);
+ hv_pci_bus_ht_initialized = false;
+ }
+
unregister_sysctl_table(hv_ctl_table_hdr);
hv_ctl_table_hdr = NULL;
@@ -315,6 +337,7 @@ u8 __init get_vtl(void)
int __init hv_common_init(void)
{
int i;
+ int ret;
union hv_hypervisor_version_info version;
/* Get information about the Microsoft Hypervisor version */
@@ -394,6 +417,13 @@ int __init hv_common_init(void)
for (i = 0; i < nr_cpu_ids; i++)
hv_vp_index[i] = VP_INVAL;
+ ret = rhashtable_init(&hv_pci_bus_ht, &hv_pci_bus_ht_params);
+ if (ret) {
+ hv_common_free();
+ return ret;
+ }
+ hv_pci_bus_ht_initialized = true;
+
return 0;
}
@@ -808,6 +838,7 @@ static const struct hv_status_info hv_status_infos[] = {
_STATUS_INFO(HV_STATUS_PROCESSOR_FEATURE_NOT_SUPPORTED, -EOPNOTSUPP),
_STATUS_INFO(HV_STATUS_INVALID_LP_INDEX, -EINVAL),
_STATUS_INFO(HV_STATUS_INVALID_REGISTER_VALUE, -EINVAL),
+ _STATUS_INFO(HV_STATUS_DEVICE_ALREADY_IN_DOMAIN, -EEXIST),
_STATUS_INFO(HV_STATUS_OPERATION_FAILED, -EIO),
_STATUS_INFO(HV_STATUS_TIME_OUT, -EIO),
_STATUS_INFO(HV_STATUS_CALL_PENDING, -EIO),
@@ -861,3 +892,87 @@ const char *hv_result_to_string(u64 status)
return "Unknown";
}
EXPORT_SYMBOL_GPL(hv_result_to_string);
+
+/*
+ * Logical device ID registry for Hyper-V PCI buses. The pci-hyperv
+ * driver registers each bus's logical device ID prefix before scanning
+ * its devices. Consumers look up the prefix by PCI domain number when
+ * building logical device IDs for Hyper-V interfaces.
+ */
+int hv_pci_register_dev_id(int pci_domain_nr, u32 logical_dev_id_prefix)
+{
+ struct hv_pci_busdata *bus, *new;
+ int ret;
+
+ new = kzalloc_obj(*new, GFP_KERNEL);
+ if (!new)
+ return -ENOMEM;
+
+ new->pci_domain_nr = pci_domain_nr;
+ new->logical_dev_id_prefix = logical_dev_id_prefix;
+
+ bus = rhashtable_lookup_get_insert_fast(&hv_pci_bus_ht, &new->node,
+ hv_pci_bus_ht_params);
+ if (IS_ERR(bus)) {
+ ret = PTR_ERR(bus);
+ goto free_new;
+ }
+
+ if (WARN_ONCE(bus != NULL,
+ "Hyper-V PCI domain %04x is already registered\n",
+ pci_domain_nr)) {
+ ret = -EEXIST;
+ goto free_new;
+ }
+
+ return 0;
+
+free_new:
+ kfree(new);
+ return ret;
+}
+EXPORT_SYMBOL_FOR_MODULES(hv_pci_register_dev_id, "pci-hyperv");
+
+void hv_pci_unregister_dev_id(int pci_domain_nr)
+{
+ struct hv_pci_busdata *bus;
+ int ret = -ENOENT;
+
+ rcu_read_lock();
+ bus = rhashtable_lookup(&hv_pci_bus_ht, &pci_domain_nr,
+ hv_pci_bus_ht_params);
+ if (bus)
+ ret = rhashtable_remove_fast(&hv_pci_bus_ht, &bus->node,
+ hv_pci_bus_ht_params);
+ rcu_read_unlock();
+
+ if (WARN_ONCE(ret,
+ "Failed to unregister Hyper-V PCI domain %04x\n",
+ pci_domain_nr))
+ return;
+
+ kfree_rcu(bus, rcu);
+}
+EXPORT_SYMBOL_FOR_MODULES(hv_pci_unregister_dev_id, "pci-hyperv");
+
+/*
+ * Look up the logical device ID prefix registered for @pci_domain_nr.
+ * Returns 0 on success with *prefix filled in; -ENODEV if no entry is
+ * registered for that PCI domain.
+ */
+int hv_pci_lookup_dev_id(int pci_domain_nr, u32 *prefix)
+{
+ struct hv_pci_busdata *bus;
+ int ret = -ENODEV;
+
+ rcu_read_lock();
+ bus = rhashtable_lookup(&hv_pci_bus_ht, &pci_domain_nr,
+ hv_pci_bus_ht_params);
+ if (bus) {
+ *prefix = bus->logical_dev_id_prefix;
+ ret = 0;
+ }
+ rcu_read_unlock();
+
+ return ret;
+}
diff --git a/drivers/iommu/Kconfig b/drivers/iommu/Kconfig
index 6e07bd69467a..85a8d7fdfa30 100644
--- a/drivers/iommu/Kconfig
+++ b/drivers/iommu/Kconfig
@@ -195,6 +195,7 @@ config MSM_IOMMU
source "drivers/iommu/amd/Kconfig"
source "drivers/iommu/arm/Kconfig"
source "drivers/iommu/intel/Kconfig"
+source "drivers/iommu/hyperv/Kconfig"
source "drivers/iommu/iommufd/Kconfig"
source "drivers/iommu/riscv/Kconfig"
@@ -303,7 +304,7 @@ config SPAPR_TCE_IOMMU
config APPLE_DART
tristate "Apple DART IOMMU Support"
- depends on ARCH_APPLE || COMPILE_TEST
+ depends on ARCH_APPLE || (COMPILE_TEST && 64BIT)
depends on !GENERIC_ATOMIC64 # for IOMMU_IO_PGTABLE_DART
select IOMMU_API
select IOMMU_IO_PGTABLE_DART
@@ -386,6 +387,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_BCM2835 && 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/amd/amd_iommu.h b/drivers/iommu/amd/amd_iommu.h
index 71113e860859..93845b3c3fbf 100644
--- a/drivers/iommu/amd/amd_iommu.h
+++ b/drivers/iommu/amd/amd_iommu.h
@@ -48,6 +48,9 @@ extern u8 amd_iommu_hpt_vasize;
extern unsigned long amd_iommu_pgsize_bitmap;
extern bool amd_iommu_hatdis;
+int amd_iommu_perfopt_clear(struct amd_iommu *iommu);
+int amd_iommu_perfopt_restore(struct amd_iommu *iommu);
+
/* Protection domain ops */
void amd_iommu_init_identity_domain(void);
struct protection_domain *protection_domain_alloc(void);
diff --git a/drivers/iommu/amd/amd_iommu_types.h b/drivers/iommu/amd/amd_iommu_types.h
index 4a15b16bd2ca..0e79c43be001 100644
--- a/drivers/iommu/amd/amd_iommu_types.h
+++ b/drivers/iommu/amd/amd_iommu_types.h
@@ -53,6 +53,7 @@
#define MMIO_MSI_ADDR_LO_OFFSET 0x015C
#define MMIO_MSI_ADDR_HI_OFFSET 0x0160
#define MMIO_MSI_DATA_OFFSET 0x0164
+#define MMIO_PERF_OPT_OFFSET 0x016C
#define MMIO_INTCAPXT_EVT_OFFSET 0x0170
#define MMIO_INTCAPXT_PPR_OFFSET 0x0178
#define MMIO_INTCAPXT_GALOG_OFFSET 0x0180
@@ -87,6 +88,8 @@
#define FEATURE_GLX GENMASK_ULL(15, 14)
#define FEATURE_GAM_VAPIC BIT_ULL(21)
#define FEATURE_PASMAX GENMASK_ULL(36, 32)
+#define FEATURE_PERF_OPT BIT_ULL(45)
+#define PERF_OPT_EN BIT(13)
#define FEATURE_GIOSUP BIT_ULL(48)
#define FEATURE_HASUP BIT_ULL(49)
#define FEATURE_EPHSUP BIT_ULL(50)
@@ -657,6 +660,9 @@ struct amd_iommu {
/* Extended features 2 */
u64 features2;
+ /* Devices requesting PerfOpt; the shared PERF_OPT_EN bit is on while >0. Protected by @lock. */
+ int perfopt_refcount;
+
/* PCI device id of the IOMMU device */
u16 devid;
@@ -818,6 +824,7 @@ struct iommu_dev_data {
u8 ppr :1; /* Enable device PPR support */
bool use_vapic; /* Enable device to use vapic mode */
bool defer_attach;
+ bool perfopt;
struct ratelimit_state rs; /* Ratelimit IOPF messages */
};
diff --git a/drivers/iommu/amd/init.c b/drivers/iommu/amd/init.c
index edcc187b8f14..de6db5621194 100644
--- a/drivers/iommu/amd/init.c
+++ b/drivers/iommu/amd/init.c
@@ -163,7 +163,6 @@ int amd_iommu_gpt_level = PAGE_MODE_4_LEVEL;
int amd_iommu_guest_ir = AMD_IOMMU_GUEST_IR_VAPIC;
static int amd_iommu_xt_mode = IRQ_REMAP_XAPIC_MODE;
-static bool amd_iommu_detected;
static bool amd_iommu_disabled __initdata;
static bool amd_iommu_force_enable __initdata;
static bool amd_iommu_irtcachedis;
@@ -390,7 +389,13 @@ static void iommu_set_device_table(struct amd_iommu *iommu)
BUG_ON(iommu->mmio_base == NULL);
- if (is_kdump_kernel())
+ /*
+ * A kdump kernel that reuses the previous kernel's device table must
+ * leave the base register alone. It already points at that table, and
+ * with SNP enabled the register is locked. If reuse failed, program
+ * the freshly allocated table like a normal boot.
+ */
+ if (iommu->pci_seg->old_dev_tbl_cpy)
return;
entry = iommu_virt_to_phys(dev_table);
@@ -623,8 +628,19 @@ static int __init find_last_devid_acpi(struct acpi_table_header *table, u16 pci_
/* Allocate per PCI segment device table */
static inline int __init alloc_dev_table(struct amd_iommu_pci_seg *pci_seg)
{
- pci_seg->dev_table = iommu_alloc_pages_sz(GFP_KERNEL | GFP_DMA32,
- pci_seg->dev_table_size);
+ gfp_t gfp = GFP_KERNEL;
+
+ /*
+ * Keep normal kernel device tables below 4G because a later kdump
+ * kernel only trusts and reuses old tables below that limit. A kdump
+ * kernel's new table is either discarded when reuse succeeds or
+ * programmed after the IOMMU is disabled, so the table does not
+ * need DMA32 memory (which the crashkernel may not have).
+ */
+ if (!is_kdump_kernel())
+ gfp |= GFP_DMA32;
+
+ pci_seg->dev_table = iommu_alloc_pages_sz(gfp, pci_seg->dev_table_size);
if (!pci_seg->dev_table)
return -ENOMEM;
@@ -916,13 +932,13 @@ static void free_ga_log(struct amd_iommu *iommu)
}
#ifdef CONFIG_IRQ_REMAP
-static int iommu_ga_log_enable(struct amd_iommu *iommu)
+static bool iommu_ga_log_enable(struct amd_iommu *iommu)
{
u32 status, i;
u64 entry;
if (!iommu->ga_log)
- return -EINVAL;
+ return false;
entry = iommu_virt_to_phys(iommu->ga_log) | GA_LOG_SIZE_512;
memcpy_toio(iommu->mmio_base + MMIO_GA_LOG_BASE_OFFSET,
@@ -946,35 +962,31 @@ static int iommu_ga_log_enable(struct amd_iommu *iommu)
}
if (WARN_ON(i >= MMIO_STATUS_TIMEOUT))
- return -EINVAL;
+ return false;
- return 0;
+ return true;
}
+#endif /* CONFIG_IRQ_REMAP */
-static int iommu_init_ga_log(struct amd_iommu *iommu)
+static int alloc_ga_log(struct amd_iommu *iommu)
{
+#ifdef CONFIG_IRQ_REMAP
int nid = iommu->dev ? dev_to_node(&iommu->dev->dev) : NUMA_NO_NODE;
- if (WARN_ON_ONCE(!AMD_IOMMU_GUEST_IR_VAPIC(amd_iommu_guest_ir)))
- return -EINVAL;
-
- if (iommu->ga_log && iommu->ga_log_tail)
- return 0;
-
iommu->ga_log = iommu_alloc_pages_node_sz(nid, GFP_KERNEL, GA_LOG_SIZE);
if (!iommu->ga_log)
- goto err_out;
+ return -ENOMEM;
iommu->ga_log_tail = iommu_alloc_pages_node_sz(nid, GFP_KERNEL, 8);
- if (!iommu->ga_log_tail)
- goto err_out;
+ if (!iommu->ga_log_tail) {
+ iommu_free_pages(iommu->ga_log);
+ iommu->ga_log = NULL;
+ return -ENOMEM;
+ }
+#endif
return 0;
-err_out:
- free_ga_log(iommu);
- return -EINVAL;
}
-#endif /* CONFIG_IRQ_REMAP */
static int __init alloc_cwwb_sem(struct amd_iommu *iommu)
{
@@ -1948,6 +1960,9 @@ static int __init init_iommu_one(struct amd_iommu *iommu, struct ivhd_header *h,
if (!iommu->mmio_base)
return -ENOMEM;
+ if (amd_iommu_perfopt_clear(iommu))
+ pr_err("IOMMU%d: failed to clear PerfOpt\n", iommu->index);
+
return init_iommu_from_acpi(iommu, h);
}
@@ -2152,6 +2167,27 @@ static void __init late_iommu_features_init(struct amd_iommu *iommu)
}
}
+/* Must be called after SNP support check is complete (iommu_snp_enable()) */
+static bool check_vapic_support(void)
+{
+ if (!AMD_IOMMU_GUEST_IR_VAPIC(amd_iommu_guest_ir))
+ return false;
+
+ if (!check_feature(FEATURE_GAM_VAPIC)) {
+ amd_iommu_guest_ir = AMD_IOMMU_GUEST_IR_LEGACY_GA;
+ return false;
+ }
+
+ if (amd_iommu_snp_en &&
+ !FEATURE_SNPAVICSUP_GAM(amd_iommu_efr2)) {
+ pr_warn("Force to disable Virtual APIC due to SNP\n");
+ amd_iommu_guest_ir = AMD_IOMMU_GUEST_IR_LEGACY_GA;
+ return false;
+ }
+
+ return true;
+}
+
static int __init iommu_init_pci(struct amd_iommu *iommu)
{
int cap_ptr = iommu->cap_ptr;
@@ -2196,6 +2232,12 @@ static int __init iommu_init_pci(struct amd_iommu *iommu)
if (check_feature(FEATURE_PPR) && amd_iommu_alloc_ppr_log(iommu))
return -ENOMEM;
+ if (check_vapic_support()) {
+ ret = alloc_ga_log(iommu);
+ if (ret)
+ return ret;
+ }
+
if (iommu->cap & (1UL << IOMMU_CAP_NPCACHE)) {
pr_info("Using strict mode due to virtualization\n");
iommu_set_dma_strict();
@@ -3010,22 +3052,9 @@ static void enable_iommus_vapic(void)
if (!AMD_IOMMU_GUEST_IR_VAPIC(amd_iommu_guest_ir))
return;
- if (!check_feature(FEATURE_GAM_VAPIC)) {
- amd_iommu_guest_ir = AMD_IOMMU_GUEST_IR_LEGACY_GA;
- return;
- }
-
- if (amd_iommu_snp_en &&
- !FEATURE_SNPAVICSUP_GAM(amd_iommu_efr2)) {
- pr_warn("Force to disable Virtual APIC due to SNP\n");
- amd_iommu_guest_ir = AMD_IOMMU_GUEST_IR_LEGACY_GA;
- return;
- }
-
/* Enabling GAM and SNPAVIC support */
for_each_iommu(iommu) {
- if (iommu_init_ga_log(iommu) ||
- iommu_ga_log_enable(iommu))
+ if (!iommu_ga_log_enable(iommu))
return;
iommu_feature_enable(iommu, CONTROL_GAM_EN);
@@ -3038,10 +3067,46 @@ static void enable_iommus_vapic(void)
#endif
}
+static int clear_perfopt_all(void)
+{
+ struct amd_iommu *iommu;
+ int err, ret = 0;
+
+ for_each_iommu(iommu) {
+ err = amd_iommu_perfopt_clear(iommu);
+ if (err)
+ ret = err;
+ }
+
+ return ret;
+}
+
+static int restore_perfopt_all(void)
+{
+ struct amd_iommu *iommu;
+ int err, ret = 0;
+
+ for_each_iommu(iommu) {
+ err = amd_iommu_perfopt_restore(iommu);
+ if (err)
+ ret = err;
+ }
+
+ return ret;
+}
+
static void disable_iommus(void)
{
struct amd_iommu *iommu;
+ /*
+ * PerfOpt is an optional performance bit, so a failure to clear it must
+ * not skip the mandatory disable below. This also runs from the void
+ * amd_iommu_disable() shutdown/kexec path, which cannot report an error.
+ */
+ if (clear_perfopt_all())
+ pr_err("Failed to clear PerfOpt while disabling IOMMUs\n");
+
for_each_iommu(iommu)
iommu_disable(iommu);
@@ -3067,6 +3132,10 @@ static void amd_iommu_resume(void *data)
for_each_iommu(iommu)
early_enable_iommu(iommu);
+ /* early_enable_iommu() cleared PERF_OPT_EN; re-assert it from the refcount. */
+ if (restore_perfopt_all())
+ pr_err("Failed to restore PerfOpt after IOMMU resume\n");
+
iommu_enable_event_buffer();
amd_iommu_enable_interrupts();
}
@@ -3228,9 +3297,6 @@ static int __init early_amd_iommu_init(void)
acpi_status status;
u8 efr_hats, max_vasize;
- if (!amd_iommu_detected)
- return -ENODEV;
-
status = acpi_get_table("IVRS", 0, &ivrs_base);
if (status == AE_NOT_FOUND)
return -ENODEV;
@@ -3309,7 +3375,7 @@ static int __init early_amd_iommu_init(void)
}
/* Disable any previously enabled IOMMUs */
- if (!is_kdump_kernel() || amd_iommu_disabled)
+ if (!is_kdump_kernel())
disable_iommus();
if (amd_iommu_irq_remap)
@@ -3404,12 +3470,6 @@ static __init void iommu_snp_enable(void)
if (!cc_platform_has(CC_ATTR_HOST_SEV_SNP))
return;
- /* SNP support required IOMMU to be ON */
- if (no_iommu) {
- pr_warn("SNP: IOMMU disabled, SNP cannot be supported.\n");
- goto disable_snp;
- }
-
amd_iommu_snp_mode0_sup = check_feature2(FEATURE_SNP_PAGE_MODE0_SUP);
/*
* If SNP page mode 0 is not enabled, then SNP support requires that IOMMU
@@ -3476,6 +3536,8 @@ static void amd_iommu_apply_erratum_snp(void)
#endif
}
+static bool amd_iommu_sme_check(void);
+
/****************************************************************************
*
* AMD IOMMU Initialization State Machine
@@ -3488,7 +3550,13 @@ static int __init state_next(void)
switch (init_state) {
case IOMMU_START_STATE:
- if (!detect_ivrs()) {
+ if (no_iommu || amd_iommu_disabled) {
+ init_state = IOMMU_CMDLINE_DISABLED;
+ ret = -EINVAL;
+ } else if (!amd_iommu_sme_check()) {
+ init_state = IOMMU_INIT_ERROR;
+ ret = -EINVAL;
+ } else if (!detect_ivrs()) {
init_state = IOMMU_NOT_FOUND;
ret = -ENODEV;
} else {
@@ -3496,13 +3564,8 @@ static int __init state_next(void)
}
break;
case IOMMU_IVRS_DETECTED:
- if (amd_iommu_disabled) {
- init_state = IOMMU_CMDLINE_DISABLED;
- ret = -EINVAL;
- } else {
- ret = early_amd_iommu_init();
- init_state = ret ? IOMMU_INIT_ERROR : IOMMU_ACPI_FINISHED;
- }
+ ret = early_amd_iommu_init();
+ init_state = ret ? IOMMU_INIT_ERROR : IOMMU_ACPI_FINISHED;
break;
case IOMMU_ACPI_FINISHED:
early_enable_iommus();
@@ -3574,6 +3637,19 @@ static int __init iommu_go_to_state(enum iommu_init_state state)
{
int ret = -EINVAL;
+ /*
+ * Some essential housekeeping work is done by amd_iommu_detect().
+ * Skipping calling it implies that the platform (e.g., Xen hypervisor)
+ * has taken over the hardware. Progressing the state machine in this
+ * case is worthless and fragile.
+ *
+ * There are several paths requesting later states, so disallow implicit
+ * START_STATE => IVRS_DETECTED transition to prevent these paths from
+ * accidentally progressing the state machine.
+ */
+ if (init_state == IOMMU_START_STATE && state != IOMMU_IVRS_DETECTED)
+ goto out;
+
while (init_state != state) {
if (init_state == IOMMU_NOT_FOUND ||
init_state == IOMMU_INIT_ERROR ||
@@ -3582,6 +3658,7 @@ static int __init iommu_go_to_state(enum iommu_init_state state)
ret = state_next();
}
+out:
/*
* SNP platform initilazation requires IOMMUs to be fully configured.
* If the SNP support on IOMMUs has NOT been checked, simply mark SNP
@@ -3701,17 +3778,10 @@ void __init amd_iommu_detect(void)
{
int ret;
- if (no_iommu || (iommu_detected && !gart_iommu_aperture))
- goto disable_snp;
-
- if (!amd_iommu_sme_check())
- goto disable_snp;
-
ret = iommu_go_to_state(IOMMU_IVRS_DETECTED);
if (ret)
goto disable_snp;
- amd_iommu_detected = true;
iommu_detected = 1;
x86_init.iommu.iommu_init = amd_iommu_init;
return;
diff --git a/drivers/iommu/amd/iommu.c b/drivers/iommu/amd/iommu.c
index 893cd2be11a2..960e1dc05ec6 100644
--- a/drivers/iommu/amd/iommu.c
+++ b/drivers/iommu/amd/iommu.c
@@ -50,8 +50,6 @@
#define CMD_SET_TYPE(cmd, t) ((cmd)->data[1] |= ((t) << 28))
/* Reserved IOVA ranges */
-#define MSI_RANGE_START (0xfee00000)
-#define MSI_RANGE_END (0xfeefffff)
#define HT_RANGE_START (0xfd00000000ULL)
#define HT_RANGE_END (0xffffffffffULL)
@@ -573,10 +571,17 @@ static inline int pdev_enable_cap_ats(struct pci_dev *pdev)
if (amd_iommu_iotlb_sup &&
(dev_data->flags & AMD_IOMMU_DEVICE_FLAG_ATS_SUP)) {
ret = pci_enable_ats(pdev, PAGE_SHIFT);
- if (!ret) {
- dev_data->ats_enabled = 1;
- dev_data->ats_qdep = pci_ats_queue_depth(pdev);
- }
+
+ /*
+ * pci_enable_ats() should not fail here because earlier
+ * checks have already verified support & config.
+ */
+ if (WARN_ON(ret))
+ return ret;
+
+ dev_data->ats_enabled = 1;
+ dev_data->ats_qdep = pci_ats_queue_depth(pdev);
+ ret = 0;
}
return ret;
@@ -675,7 +680,8 @@ static void pdev_disable_caps(struct pci_dev *pdev)
* This function checks if the driver got a valid device from the caller to
* avoid dereferencing invalid pointers.
*/
-static bool check_device(struct device *dev)
+static bool lookup_device(struct device *dev,
+ struct amd_iommu **iommu_out, u16 *devid_out)
{
struct amd_iommu_pci_seg *pci_seg;
struct amd_iommu *iommu;
@@ -690,7 +696,7 @@ static bool check_device(struct device *dev)
devid = PCI_SBDF_TO_DEVID(sbdf);
iommu = rlookup_amd_iommu(dev);
- if (!iommu)
+ if (!iommu || !iommu->iommu.ops)
return false;
/* Out of our scope? */
@@ -698,65 +704,35 @@ static bool check_device(struct device *dev)
if (devid > pci_seg->last_bdf)
return false;
+ *iommu_out = iommu;
+ *devid_out = devid;
return true;
}
-static int iommu_init_device(struct amd_iommu *iommu, struct device *dev)
+static struct iommu_dev_data *iommu_init_device(struct amd_iommu *iommu,
+ struct device *dev, u16 devid)
{
struct iommu_dev_data *dev_data;
- int devid, sbdf;
- if (dev_iommu_priv_get(dev))
- return 0;
-
- sbdf = get_device_sbdf_id(dev);
- if (sbdf < 0)
- return sbdf;
-
- devid = PCI_SBDF_TO_DEVID(sbdf);
dev_data = find_dev_data(iommu, devid);
if (!dev_data)
- return -ENOMEM;
+ return ERR_PTR(-ENOMEM);
dev_data->dev = dev;
/*
- * The dev_iommu_priv_set() needes to be called before setup_aliases.
+ * The dev_iommu_priv_set() needs to be called before setup_aliases.
* Otherwise, subsequent call to dev_iommu_priv_get() will fail.
*/
dev_iommu_priv_set(dev, dev_data);
setup_aliases(iommu, dev);
- /*
- * By default we use passthrough mode for IOMMUv2 capable device.
- * But if amd_iommu=force_isolation is set (e.g. to debug DMA to
- * invalid address), we ignore the capability for the device so
- * it'll be forced to go into translation mode.
- */
- if ((iommu_default_passthrough() || !amd_iommu_force_isolation) &&
- dev_is_pci(dev) && amd_iommu_gt_ppr_supported()) {
- dev_data->flags = pdev_get_caps(to_pci_dev(dev));
- }
+ /* Wait for DTE updates to go through */
+ iommu_completion_wait(iommu);
- return 0;
+ return dev_data;
}
-static void iommu_ignore_device(struct amd_iommu *iommu, struct device *dev)
-{
- struct amd_iommu_pci_seg *pci_seg = iommu->pci_seg;
- struct dev_table_entry *dev_table = get_dev_table(iommu);
- int devid, sbdf;
-
- sbdf = get_device_sbdf_id(dev);
- if (sbdf < 0)
- return;
-
- devid = PCI_SBDF_TO_DEVID(sbdf);
- pci_seg->rlookup_table[devid] = NULL;
- memset(&dev_table[devid], 0, sizeof(struct dev_table_entry));
-
- setup_aliases(iommu, dev);
-}
/****************************************************************************
@@ -2392,6 +2368,9 @@ static int attach_device(struct device *dev,
if (ret)
goto out;
+ if (dev_data->perfopt)
+ goto skip_caps;
+
/* Setup GCR3 table */
if (pdom_is_sva_capable(domain)) {
ret = init_gcr3_table(dev_data, domain);
@@ -2416,6 +2395,7 @@ static int attach_device(struct device *dev,
pdev_enable_cap_ats(pdev);
}
+skip_caps:
/* Update data structures */
dev_data->domain = domain;
spin_lock_irqsave(&domain->lock, flags);
@@ -2484,73 +2464,289 @@ out:
mutex_unlock(&dev_data->mutex);
}
+static int iommu_init_device_caps(struct iommu_dev_data *dev_data,
+ struct device *dev,
+ struct amd_iommu *iommu)
+{
+ int ret;
+
+ if (FEATURE_NUM_INT_REMAP_SUP_2K(amd_iommu_efr2))
+ dev_data->max_irqs = MAX_IRQS_PER_TABLE_2K;
+ else
+ dev_data->max_irqs = MAX_IRQS_PER_TABLE_512;
+
+ amd_iommu_set_pci_msi_domain(dev, iommu);
+
+ if (!dev_is_pci(dev))
+ return 0;
+
+ /*
+ * By default we use passthrough mode for IOMMUv2 capable device.
+ * But if amd_iommu=force_isolation is set (e.g. to debug DMA to
+ * invalid address), we ignore the capability for the device so
+ * it'll be forced to go into translation mode.
+ */
+ if ((iommu_default_passthrough() || !amd_iommu_force_isolation) &&
+ amd_iommu_gt_ppr_supported()) {
+ dev_data->flags = pdev_get_caps(to_pci_dev(dev));
+ }
+
+ /*
+ * If IOMMU and device supports PASID then it will contain max
+ * supported PASIDs, else it will be zero.
+ */
+ if (amd_iommu_pasid_supported() &&
+ pdev_pasid_supported(dev_data)) {
+ dev_data->max_pasids = min_t(u32, iommu->iommu.max_pasids,
+ pci_max_pasids(to_pci_dev(dev)));
+ }
+
+ if (pci_ats_supported(to_pci_dev(dev))) {
+ ret = pci_prepare_ats(to_pci_dev(dev), PAGE_SHIFT);
+ if (ret)
+ return ret;
+ }
+
+ return 0;
+}
+
+/* Program the per-IOMMU PerfOpt enable bit. Caller must hold iommu->lock. */
+static int __perfopt_write(struct amd_iommu *iommu, bool enable)
+{
+ u32 old, val, readback;
+
+ if (!(readq(iommu->mmio_base + MMIO_EXT_FEATURES) & FEATURE_PERF_OPT))
+ return enable ? -ENODEV : 0;
+
+ old = readl(iommu->mmio_base + MMIO_PERF_OPT_OFFSET);
+ if (old == U32_MAX)
+ return -EIO;
+
+ val = enable ? old | PERF_OPT_EN : old & ~PERF_OPT_EN;
+ if (val != old)
+ writel(val, iommu->mmio_base + MMIO_PERF_OPT_OFFSET);
+ readback = readl(iommu->mmio_base + MMIO_PERF_OPT_OFFSET);
+ if (readback == U32_MAX ||
+ (readback & PERF_OPT_EN) != (val & PERF_OPT_EN))
+ return -EIO;
+ return 0;
+}
+
+/*
+ * PERF_OPT_EN is a single bit shared by every device behind @iommu, so it is
+ * reference counted: armed on the first requesting device, cleared on the last.
+ */
+static int perfopt_get(struct amd_iommu *iommu)
+{
+ unsigned long flags;
+ int ret = 0;
+
+ if (!iommu->mmio_base)
+ return 0;
+
+ raw_spin_lock_irqsave(&iommu->lock, flags);
+ if (iommu->perfopt_refcount == 0) {
+ ret = __perfopt_write(iommu, true);
+ if (ret)
+ goto out;
+ }
+ iommu->perfopt_refcount++;
+out:
+ raw_spin_unlock_irqrestore(&iommu->lock, flags);
+ return ret;
+}
+
+static int perfopt_put(struct amd_iommu *iommu)
+{
+ unsigned long flags;
+ int ret = 0;
+
+ if (!iommu->mmio_base)
+ return 0;
+
+ raw_spin_lock_irqsave(&iommu->lock, flags);
+ if (iommu->perfopt_refcount > 0 && --iommu->perfopt_refcount == 0)
+ ret = __perfopt_write(iommu, false);
+ raw_spin_unlock_irqrestore(&iommu->lock, flags);
+ return ret;
+}
+
+/*
+ * Force PERF_OPT_EN off without touching the refcount (used on init, shutdown,
+ * and suspend). The count is preserved so amd_iommu_perfopt_restore() can
+ * re-arm on resume.
+ */
+int amd_iommu_perfopt_clear(struct amd_iommu *iommu)
+{
+ unsigned long flags;
+ int ret;
+
+ if (!iommu->mmio_base)
+ return 0;
+
+ raw_spin_lock_irqsave(&iommu->lock, flags);
+ ret = __perfopt_write(iommu, false);
+ raw_spin_unlock_irqrestore(&iommu->lock, flags);
+ return ret;
+}
+
+/*
+ * Re-assert PERF_OPT_EN from the refcount after the hardware was reprogrammed on
+ * resume, so devices armed before suspend keep the optimization without each
+ * consumer driver re-arming.
+ */
+int amd_iommu_perfopt_restore(struct amd_iommu *iommu)
+{
+ unsigned long flags;
+ int ret;
+
+ if (!iommu->mmio_base)
+ return 0;
+
+ raw_spin_lock_irqsave(&iommu->lock, flags);
+ ret = __perfopt_write(iommu, iommu->perfopt_refcount > 0);
+ raw_spin_unlock_irqrestore(&iommu->lock, flags);
+ return ret;
+}
+
+int amd_iommu_enable_perfopt(struct pci_dev *pdev)
+{
+ struct iommu_dev_data *dev_data = dev_iommu_priv_get(&pdev->dev);
+ struct amd_iommu *iommu = rlookup_amd_iommu(&pdev->dev);
+ struct protection_domain *domain;
+ int ret;
+
+ if (!iommu || !dev_data)
+ return -ENODEV;
+
+ if (!(iommu->features & FEATURE_PERF_OPT))
+ return -ENODEV;
+
+ domain = dev_data->domain;
+ if (!domain)
+ return -ENODEV;
+
+ /* Already armed for this device (e.g. re-entry on resume). */
+ if (dev_data->perfopt)
+ return 0;
+
+ /*
+ * The bit is only architecturally valid while the device is untranslated:
+ * identity domain with ATS/PRI/PASID off. The identity domain is
+ * SVA-capable so attach_device() enabled ATS/PRI/PASID and built a GCR3
+ * table. Re-home the device onto the same identity domain with
+ * perfopt set, so the attach_device() skip_caps path leaves
+ * ATS/PRI/PASID off and no GCR3 table. This follows the detach/attach
+ * pattern used by amd_iommu_attach_device().
+ *
+ * Locking: this and amd_iommu_disable_perfopt() run only from the
+ * consumer driver's bind/unbind path. group->mutex is not exposed to
+ * drivers, but a device bound to its native driver cannot have its domain
+ * changed concurrently by the core (VFIO ownership is mutually exclusive;
+ * sysfs domain changes require an unused group), so the detach/attach pair
+ * is serialized without it.
+ */
+ dev_data->perfopt = true;
+ detach_device(&pdev->dev);
+ ret = attach_device(&pdev->dev, domain);
+ if (ret)
+ goto err_restore;
+
+ ret = perfopt_get(iommu);
+ if (ret)
+ goto err_rearm;
+
+ dev_info_once(&pdev->dev, "PerfOpt armed on IOMMU%d\n", iommu->index);
+ return 0;
+
+err_rearm:
+ detach_device(&pdev->dev);
+err_restore:
+ dev_data->perfopt = false;
+ if (attach_device(&pdev->dev, domain))
+ pci_err(pdev, "failed to restore state after PerfOpt setup; device left detached\n");
+ dev_err_once(&pdev->dev, "PerfOpt failed to arm on IOMMU%d (%d)\n",
+ iommu->index, ret);
+ return ret;
+}
+EXPORT_SYMBOL_GPL(amd_iommu_enable_perfopt);
+
+void amd_iommu_disable_perfopt(struct pci_dev *pdev)
+{
+ struct iommu_dev_data *dev_data = dev_iommu_priv_get(&pdev->dev);
+ struct amd_iommu *iommu = rlookup_amd_iommu(&pdev->dev);
+ struct protection_domain *domain;
+
+ if (!iommu || !dev_data || !dev_data->perfopt || !dev_data->domain)
+ return;
+
+ if (WARN_ON(perfopt_put(iommu)))
+ pci_err(pdev, "failed to clear PerfOpt\n");
+
+ /*
+ * Restore ATS/PRI/PASID (and thus SVA) by re-homing the device onto its
+ * identity domain with the flag cleared, so a later bind without PerfOpt
+ * sees a normally-capable device. See the locking note in
+ * amd_iommu_enable_perfopt().
+ */
+ domain = dev_data->domain;
+ dev_data->perfopt = false;
+ detach_device(&pdev->dev);
+ if (attach_device(&pdev->dev, domain))
+ pci_err(pdev, "failed to restore caps after PerfOpt disable\n");
+}
+EXPORT_SYMBOL_GPL(amd_iommu_disable_perfopt);
static struct iommu_device *amd_iommu_probe_device(struct device *dev)
{
struct iommu_device *iommu_dev;
struct amd_iommu *iommu;
struct iommu_dev_data *dev_data;
+ u16 devid;
int ret;
- if (!check_device(dev))
- return ERR_PTR(-ENODEV);
-
- iommu = rlookup_amd_iommu(dev);
- if (!iommu)
- return ERR_PTR(-ENODEV);
-
- /* Not registered yet? */
- if (!iommu->iommu.ops)
+ if (!lookup_device(dev, &iommu, &devid))
return ERR_PTR(-ENODEV);
if (dev_iommu_priv_get(dev))
return &iommu->iommu;
- ret = iommu_init_device(iommu, dev);
- if (ret) {
+ dev_data = iommu_init_device(iommu, dev, devid);
+ if (IS_ERR(dev_data)) {
dev_err(dev, "Failed to initialize - trying to proceed anyway\n");
- iommu_dev = ERR_PTR(ret);
- iommu_ignore_device(iommu, dev);
- goto out_err;
+ return ERR_CAST(dev_data);
}
- amd_iommu_set_pci_msi_domain(dev, iommu);
+ ret = iommu_init_device_caps(dev_data, dev, iommu);
+ if (ret)
+ return ERR_PTR(ret);
+
iommu_dev = &iommu->iommu;
/*
- * If IOMMU and device supports PASID then it will contain max
- * supported PASIDs, else it will be zero.
+ * When DMA translation is unavailable return error so the iommu core
+ * won't attempt domain attach for this device, while preserving its
+ * rlookup entry for interrupt remapping.
*/
- dev_data = dev_iommu_priv_get(dev);
- if (amd_iommu_pasid_supported() && dev_is_pci(dev) &&
- pdev_pasid_supported(dev_data)) {
- dev_data->max_pasids = min_t(u32, iommu->iommu.max_pasids,
- pci_max_pasids(to_pci_dev(dev)));
- }
-
if (amd_iommu_pgtable == PD_MODE_NONE) {
pr_warn_once("%s: DMA translation not supported by iommu.\n",
__func__);
- iommu_dev = ERR_PTR(-ENODEV);
- goto out_err;
+ return ERR_PTR(-ENODEV);
}
- iommu_completion_wait(iommu);
-
- if (FEATURE_NUM_INT_REMAP_SUP_2K(amd_iommu_efr2))
- dev_data->max_irqs = MAX_IRQS_PER_TABLE_2K;
- else
- dev_data->max_irqs = MAX_IRQS_PER_TABLE_512;
-
- if (dev_is_pci(dev))
- pci_prepare_ats(to_pci_dev(dev), PAGE_SHIFT);
-
-out_err:
return iommu_dev;
}
static void amd_iommu_release_device(struct device *dev)
{
struct iommu_dev_data *dev_data = dev_iommu_priv_get(dev);
+ struct amd_iommu *iommu = get_amd_iommu_from_dev_data(dev_data);
+
+ if (dev_data->perfopt) {
+ if (WARN_ON(perfopt_put(iommu)))
+ dev_err(dev, "IOMMU%d: failed to clear PerfOpt on release\n",
+ iommu->index);
+ dev_data->perfopt = false;
+ }
WARN_ON(dev_data->domain);
@@ -2925,6 +3121,19 @@ static int blocked_domain_attach_device(struct iommu_domain *domain,
struct iommu_domain *old)
{
struct iommu_dev_data *dev_data = dev_iommu_priv_get(dev);
+ struct amd_iommu *iommu = get_amd_iommu_from_dev_data(dev_data);
+
+ /*
+ * blocked_domain is also the .release_domain, so this is the normal
+ * teardown path: drop the reference and clear the flag here too, and
+ * don't fail teardown if the WARN-guarded write doesn't stick.
+ */
+ if (dev_data->perfopt) {
+ if (WARN_ON(perfopt_put(iommu)))
+ dev_err(dev, "IOMMU%d: failed to clear PerfOpt for blocked domain\n",
+ iommu->index);
+ dev_data->perfopt = false;
+ }
if (dev_data->domain)
detach_device(dev);
@@ -2993,6 +3202,9 @@ static int amd_iommu_attach_device(struct iommu_domain *dom, struct device *dev,
struct amd_iommu *iommu = get_amd_iommu_from_dev(dev);
int ret;
+ if (dev_data->perfopt && !pdom_is_in_pt_mode(domain))
+ return -EBUSY;
+
/*
* Skip attach device to domain if new domain is same as
* devices current domain
@@ -3137,12 +3349,8 @@ static void amd_iommu_get_resv_regions(struct device *dev,
list_add_tail(&region->list, head);
}
- region = iommu_alloc_resv_region(MSI_RANGE_START,
- MSI_RANGE_END - MSI_RANGE_START + 1,
- 0, IOMMU_RESV_MSI, GFP_KERNEL);
- if (!region)
+ if (iommu_add_resv_x86_msi_region(head))
return;
- list_add_tail(&region->list, head);
if (amd_iommu_ht_range_ignore())
return;
@@ -3162,6 +3370,26 @@ static bool amd_iommu_is_attach_deferred(struct device *dev)
return dev_data->defer_attach;
}
+static bool quirks_force_identity_mapping(struct pci_dev *pdev)
+{
+ int class = pdev->class >> 8;
+
+ /* AMD GPU vendor ID */
+ if (pdev->vendor != PCI_VENDOR_ID_ATI)
+ return false;
+
+ /* GPU class */
+ if (class != PCI_CLASS_DISPLAY_VGA &&
+ class != PCI_CLASS_DISPLAY_OTHER)
+ return false;
+
+ if (pci_upstream_bridge(pdev)->vendor == PCI_VENDOR_ID_ATI)
+ return false;
+
+ /* It is the GPU in an APU, force identity domain */
+ return true;
+}
+
static int amd_iommu_def_domain_type(struct device *dev)
{
struct iommu_dev_data *dev_data;
@@ -3170,20 +3398,23 @@ static int amd_iommu_def_domain_type(struct device *dev)
if (!dev_data)
return 0;
+ if (!dev_is_pci(dev))
+ return 0;
+
/* Always use DMA domain for untrusted device */
- if (dev_is_pci(dev) && to_pci_dev(dev)->untrusted)
+ if (to_pci_dev(dev)->untrusted)
return IOMMU_DOMAIN_DMA;
- /*
- * Do not identity map IOMMUv2 capable devices when:
- * - memory encryption is active, because some of those devices
- * (AMD GPUs) don't have the encryption bit in their DMA-mask
- * and require remapping.
- * - SNP is enabled, because it prohibits DTE[Mode]=0.
- */
- if (pdev_pasid_supported(dev_data) &&
- !cc_platform_has(CC_ATTR_MEM_ENCRYPT) &&
- !amd_iommu_snp_en) {
+ /* Apply device specific quirks */
+ if (quirks_force_identity_mapping(to_pci_dev(dev))) {
+ /*
+ * When memory encryption is active, some of these devices
+ * don't have the encryption bit in their DMA-mask and
+ * require remapping.
+ */
+ if (cc_platform_has(CC_ATTR_MEM_ENCRYPT))
+ return 0;
+
return IOMMU_DOMAIN_IDENTITY;
}
@@ -3902,8 +4133,11 @@ static int irq_remapping_alloc(struct irq_domain *domain, unsigned int virq,
out_free_data:
for (i--; i >= 0; i--) {
irq_data = irq_domain_get_irq_data(domain, virq + i);
- if (irq_data)
- kfree(irq_data->chip_data);
+ if (irq_data && irq_data->chip_data) {
+ data = irq_data->chip_data;
+ kfree(data->entry);
+ kfree(data);
+ }
}
for (i = 0; i < nr_irqs; i++)
free_irte(iommu, devid, index + i);
diff --git a/drivers/iommu/apple-dart.c b/drivers/iommu/apple-dart.c
index b160fb464c5f..ebf4547d32b4 100644
--- a/drivers/iommu/apple-dart.c
+++ b/drivers/iommu/apple-dart.c
@@ -21,6 +21,7 @@
#include <linux/io-pgtable.h>
#include <linux/iommu.h>
#include <linux/iopoll.h>
+#include <linux/minmax.h>
#include <linux/module.h>
#include <linux/of.h>
#include <linux/of_address.h>
@@ -267,6 +268,7 @@ struct apple_dart_domain {
struct io_pgtable_ops *pgtbl_ops;
bool finalized;
+ u64 mask;
struct mutex init_lock;
struct apple_dart_atomic_stream_map stream_maps[MAX_DARTS_PER_DEVICE];
@@ -285,6 +287,13 @@ struct apple_dart_master_cfg {
/* Intersection of DART capabilitles */
u32 supports_bypass : 1;
+ /*
+ * DMA aperture start and end to be overridden by "iommus"' phandle
+ * args. By default determined by DART's ias but may be outside of it.
+ */
+ dma_addr_t dma_min;
+ dma_addr_t dma_max;
+
struct apple_dart_stream_map stream_maps[MAX_DARTS_PER_DEVICE];
};
@@ -537,7 +546,7 @@ static phys_addr_t apple_dart_iova_to_phys(struct iommu_domain *domain,
if (!ops)
return 0;
- return ops->iova_to_phys(ops, iova);
+ return ops->iova_to_phys(ops, iova & dart_domain->mask);
}
static int apple_dart_map_pages(struct iommu_domain *domain, unsigned long iova,
@@ -551,8 +560,8 @@ static int apple_dart_map_pages(struct iommu_domain *domain, unsigned long iova,
if (!ops)
return -ENODEV;
- return ops->map_pages(ops, iova, paddr, pgsize, pgcount, prot, gfp,
- mapped);
+ return ops->map_pages(ops, iova & dart_domain->mask, paddr, pgsize,
+ pgcount, prot, gfp, mapped);
}
static size_t apple_dart_unmap_pages(struct iommu_domain *domain,
@@ -563,7 +572,8 @@ static size_t apple_dart_unmap_pages(struct iommu_domain *domain,
struct apple_dart_domain *dart_domain = to_dart_domain(domain);
struct io_pgtable_ops *ops = dart_domain->pgtbl_ops;
- return ops->unmap_pages(ops, iova, pgsize, pgcount, gather);
+ return ops->unmap_pages(ops, iova & dart_domain->mask, pgsize, pgcount,
+ gather);
}
static void
@@ -590,6 +600,7 @@ static int apple_dart_finalize_domain(struct apple_dart_domain *dart_domain,
{
struct apple_dart *dart = cfg->stream_maps[0].dart;
struct io_pgtable_cfg pgtbl_cfg;
+ u32 ias = min_t(u32, dart->ias, fls64(cfg->dma_max));
int ret = 0;
int i, j;
@@ -610,7 +621,7 @@ static int apple_dart_finalize_domain(struct apple_dart_domain *dart_domain,
pgtbl_cfg = (struct io_pgtable_cfg){
.pgsize_bitmap = dart->pgsize,
- .ias = dart->ias,
+ .ias = ias,
.oas = dart->oas,
.coherent_walk = 1,
.iommu_dev = dart->dev,
@@ -623,10 +634,10 @@ static int apple_dart_finalize_domain(struct apple_dart_domain *dart_domain,
goto done;
}
+ dart_domain->mask = DMA_BIT_MASK(pgtbl_cfg.ias);
dart_domain->domain.pgsize_bitmap = pgtbl_cfg.pgsize_bitmap;
- dart_domain->domain.geometry.aperture_start = 0;
- dart_domain->domain.geometry.aperture_end =
- (dma_addr_t)DMA_BIT_MASK(pgtbl_cfg.ias);
+ dart_domain->domain.geometry.aperture_start = cfg->dma_min;
+ dart_domain->domain.geometry.aperture_end = cfg->dma_max;
dart_domain->domain.geometry.force_aperture = true;
dart_domain->finalized = true;
@@ -803,20 +814,65 @@ static int apple_dart_of_xlate(struct device *dev,
struct platform_device *iommu_pdev = of_find_device_by_node(args->np);
struct apple_dart *dart = platform_get_drvdata(iommu_pdev);
struct apple_dart *cfg_dart;
+ dma_addr_t dma_max = DMA_BIT_MASK(dart->ias);
+ dma_addr_t dma_min = 0;
int i, sid;
put_device(&iommu_pdev->dev);
- if (args->args_count != 1)
+ if (args->args_count != 1 && args->args_count != 5)
return -EINVAL;
+
sid = args->args[0];
+ if (args->args_count == 5) {
+ dma_addr_t length = ((dma_addr_t)args->args[3] << 32) | args->args[4];
+
+ if (!length)
+ return -EINVAL;
+
+ dma_min = ((dma_addr_t)args->args[1] << 32) | args->args[2];
+
+ if (!IS_ALIGNED(dma_min, dart->pgsize) ||
+ !IS_ALIGNED(length, dart->pgsize)) {
+ dev_err(dev, "Unaligned DMA window %pad, %pad (0x%x)\n",
+ &dma_min, &length, dart->pgsize);
+ return -EINVAL;
+ }
+ if (check_add_overflow(dma_min, length - 1, &dma_max)) {
+ dev_err(dev, "DMA window length (%pad) overflows range for start %pad\n",
+ &length, &dma_min);
+ return -EINVAL;
+ }
+
+ /*
+ * Ensure that the DMA window does not exceed the DART's ias.
+ */
+ if ((dma_min ^ dma_max) & ~DMA_BIT_MASK(dart->ias)) {
+ dev_err(dev, "Invalid DMA window for ias=%d\n",
+ dart->ias);
+ return -EINVAL;
+ }
+ }
+
if (!cfg) {
cfg = kzalloc_obj(*cfg);
if (!cfg)
return -ENOMEM;
/* Will be ANDed with DART capabilities */
cfg->supports_bypass = true;
+ /* Will be merged with other DARTs to the common range. */
+ cfg->dma_min = dma_min;
+ cfg->dma_max = dma_max;
+ } else {
+ if (dma_min >= cfg->dma_max || cfg->dma_min >= dma_max) {
+ dev_err(dev, "non-overlapping DMA windows: %pad..%pad, %pad..%pad\n",
+ &dma_min, &dma_max,
+ &cfg->dma_min, &cfg->dma_max);
+ return -EINVAL;
+ }
+ cfg->dma_min = max(dma_min, cfg->dma_min);
+ cfg->dma_max = min(dma_max, cfg->dma_max);
}
dev_iommu_priv_set(dev, cfg);
diff --git a/drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3-iommufd.c b/drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3-iommufd.c
index ab1078a97d80..e68b46485095 100644
--- a/drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3-iommufd.c
+++ b/drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3-iommufd.c
@@ -194,6 +194,7 @@ static int arm_smmu_attach_dev_nested(struct iommu_domain *domain,
arm_smmu_install_ste_for_dev(master, &ste);
arm_smmu_attach_commit(&state);
mutex_unlock(&arm_smmu_asid_lock);
+ arm_smmu_attach_release(&state);
return 0;
}
diff --git a/drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3-sva.c b/drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3-sva.c
index 0a429c64fbf3..faeec656da9e 100644
--- a/drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3-sva.c
+++ b/drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3-sva.c
@@ -140,16 +140,19 @@ static void arm_smmu_mm_arch_invalidate_secondary_tlbs(struct mmu_notifier *mn,
{
struct arm_smmu_domain *smmu_domain =
container_of(mn, struct arm_smmu_domain, mmu_notifier);
- size_t size;
-
- /*
- * The mm_types defines vm_end as the first byte after the end address,
- * different from IOMMU subsystem using the last address of an address
- * range. So do a simple translation here by calculating size correctly.
- */
- size = end - start;
+ struct arm_smmu_tlbi tlbi = {
+ .tgsz_lg2 = smmu_domain->tgsz_lg2,
+ .iova = start,
+ /*
+ * The mm_types defines vm_end as the first byte after the end
+ * address, different from IOMMU subsystem using the last
+ * address of an address range.
+ */
+ .size = end - start,
+ .iopte_size = PAGE_SIZE,
+ };
- arm_smmu_domain_inv_range(smmu_domain, start, size, PAGE_SIZE, false);
+ arm_smmu_domain_tlbi(&tlbi, smmu_domain);
}
static void arm_smmu_mm_release(struct mmu_notifier *mn, struct mm_struct *mm)
@@ -215,6 +218,13 @@ bool arm_smmu_sva_supported(struct arm_smmu_device *smmu)
if (system_supports_haft())
feat_mask |= ARM_SMMU_FEAT_HAFT;
+ /*
+ * The workaround for ARM_SMMU_OPT_FULL_CONT_RANGE_INV requires range
+ * invalidation support.
+ */
+ if (smmu->options & ARM_SMMU_OPT_FULL_CONT_RANGE_INV)
+ feat_mask |= ARM_SMMU_FEAT_RANGE_INV;
+
if ((smmu->features & feat_mask) != feat_mask)
return false;
@@ -338,6 +348,7 @@ struct iommu_domain *arm_smmu_sva_domain_alloc(struct device *dev,
* ARM_SMMU_FEAT_RANGE_INV is present
*/
smmu_domain->domain.pgsize_bitmap = PAGE_SIZE;
+ smmu_domain->tgsz_lg2 = PAGE_SHIFT;
smmu_domain->stage = ARM_SMMU_DOMAIN_SVA;
smmu_domain->smmu = smmu;
diff --git a/drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3-test.c b/drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3-test.c
index add671363c82..5a4e45e15125 100644
--- a/drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3-test.c
+++ b/drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3-test.c
@@ -24,7 +24,9 @@ struct arm_smmu_test_writer {
static struct arm_smmu_ste bypass_ste;
static struct arm_smmu_ste abort_ste;
static struct arm_smmu_device smmu = {
- .features = ARM_SMMU_FEAT_STALLS | ARM_SMMU_FEAT_ATTR_TYPES_OVR
+ .features = ARM_SMMU_FEAT_STALLS | ARM_SMMU_FEAT_ATTR_TYPES_OVR |
+ ARM_SMMU_FEAT_RANGE_INV,
+ .options = ARM_SMMU_OPT_FULL_CONT_RANGE_INV,
};
static struct mm_struct sva_mm = {
.pgd = (void *)0xdaedbeefdeadbeefULL,
@@ -125,8 +127,8 @@ arm_smmu_v3_test_debug_print_used_bits(struct arm_smmu_entry_writer *writer,
{
__le64 used_bits[NUM_ENTRY_QWORDS] = {};
- arm_smmu_get_ste_used(ste, used_bits);
- pr_debug("STE used bits: ");
+ writer->ops->get_used(ste, used_bits);
+ pr_debug("Entry used bits: ");
print_hex_dump_debug(" ", DUMP_PREFIX_NONE, 16, 8, used_bits,
sizeof(used_bits), false);
}
@@ -643,8 +645,10 @@ static void arm_smmu_v3_invs_test_verify(struct kunit *test,
const int *ids, const int *users,
const int *ssids)
{
- KUNIT_EXPECT_EQ(test, invs->num_invs, num_invs);
+ KUNIT_ASSERT_EQ(test, invs->num_invs, num_invs);
KUNIT_EXPECT_EQ(test, invs->num_trashes, num_trashes);
+ KUNIT_EXPECT_TRUE(test, invs->has_range_inv);
+ KUNIT_EXPECT_TRUE(test, invs->has_full_cont_range_inv);
while (num_invs--) {
KUNIT_EXPECT_EQ(test, invs->inv[num_invs].id, ids[num_invs]);
KUNIT_EXPECT_EQ(test, READ_ONCE(invs->inv[num_invs].users),
@@ -654,38 +658,43 @@ static void arm_smmu_v3_invs_test_verify(struct kunit *test,
}
static struct arm_smmu_invs invs1 = {
+ .max_invs = 3,
.num_invs = 3,
- .inv = { { .type = INV_TYPE_S2_VMID, .id = 1, },
- { .type = INV_TYPE_S2_VMID_S1_CLEAR, .id = 1, },
- { .type = INV_TYPE_ATS, .id = 3, }, },
+ .inv = { { .smmu = &smmu, .type = INV_TYPE_S2_VMID, .id = 1, },
+ { .smmu = &smmu, .type = INV_TYPE_S2_VMID_S1_CLEAR, .id = 1, },
+ { .smmu = &smmu, .type = INV_TYPE_ATS, .id = 3, }, },
};
static struct arm_smmu_invs invs2 = {
+ .max_invs = 3,
.num_invs = 3,
- .inv = { { .type = INV_TYPE_S2_VMID, .id = 1, }, /* duplicated */
- { .type = INV_TYPE_ATS, .id = 4, },
- { .type = INV_TYPE_ATS, .id = 5, }, },
+ .inv = { { .smmu = &smmu, .type = INV_TYPE_S2_VMID, .id = 1, }, /* duplicated */
+ { .smmu = &smmu, .type = INV_TYPE_ATS, .id = 4, },
+ { .smmu = &smmu, .type = INV_TYPE_ATS, .id = 5, }, },
};
static struct arm_smmu_invs invs3 = {
+ .max_invs = 3,
.num_invs = 3,
- .inv = { { .type = INV_TYPE_S2_VMID, .id = 1, }, /* duplicated */
- { .type = INV_TYPE_ATS, .id = 5, }, /* recover a trash */
- { .type = INV_TYPE_ATS, .id = 6, }, },
+ .inv = { { .smmu = &smmu, .type = INV_TYPE_S2_VMID, .id = 1, }, /* duplicated */
+ { .smmu = &smmu, .type = INV_TYPE_ATS, .id = 5, }, /* recover a trash */
+ { .smmu = &smmu, .type = INV_TYPE_ATS, .id = 6, }, },
};
static struct arm_smmu_invs invs4 = {
+ .max_invs = 3,
.num_invs = 3,
- .inv = { { .type = INV_TYPE_ATS, .id = 10, .ssid = 1 },
- { .type = INV_TYPE_ATS, .id = 10, .ssid = 3 },
- { .type = INV_TYPE_ATS, .id = 12, .ssid = 1 }, },
+ .inv = { { .smmu = &smmu, .type = INV_TYPE_ATS, .id = 10, .ssid = 1 },
+ { .smmu = &smmu, .type = INV_TYPE_ATS, .id = 10, .ssid = 3 },
+ { .smmu = &smmu, .type = INV_TYPE_ATS, .id = 12, .ssid = 1 }, },
};
static struct arm_smmu_invs invs5 = {
+ .max_invs = 3,
.num_invs = 3,
- .inv = { { .type = INV_TYPE_ATS, .id = 10, .ssid = 2 },
- { .type = INV_TYPE_ATS, .id = 10, .ssid = 3 }, /* duplicate */
- { .type = INV_TYPE_ATS, .id = 12, .ssid = 2 }, },
+ .inv = { { .smmu = &smmu, .type = INV_TYPE_ATS, .id = 10, .ssid = 2 },
+ { .smmu = &smmu, .type = INV_TYPE_ATS, .id = 10, .ssid = 3 }, /* duplicate */
+ { .smmu = &smmu, .type = INV_TYPE_ATS, .id = 12, .ssid = 2 }, },
};
static void arm_smmu_v3_invs_test(struct kunit *test)
@@ -704,17 +713,22 @@ static void arm_smmu_v3_invs_test(struct kunit *test)
/* New array */
test_a = arm_smmu_invs_alloc(0);
+ KUNIT_ASSERT_NOT_ERR_OR_NULL(test, test_a);
KUNIT_EXPECT_EQ(test, test_a->num_invs, 0);
+ KUNIT_EXPECT_FALSE(test, test_a->has_range_inv);
+ KUNIT_EXPECT_FALSE(test, test_a->has_full_cont_range_inv);
/* Test1: merge invs1 (new array) */
test_b = arm_smmu_invs_merge(test_a, &invs1);
kfree(test_a);
+ KUNIT_ASSERT_NOT_ERR_OR_NULL(test, test_b);
arm_smmu_v3_invs_test_verify(test, test_b, ARRAY_SIZE(results1[0]), 0,
results1[0], results1[1], results1[2]);
/* Test2: merge invs2 (new array) */
test_a = arm_smmu_invs_merge(test_b, &invs2);
kfree(test_b);
+ KUNIT_ASSERT_NOT_ERR_OR_NULL(test, test_a);
arm_smmu_v3_invs_test_verify(test, test_a, ARRAY_SIZE(results2[0]), 0,
results2[0], results2[1], results2[2]);
@@ -726,6 +740,7 @@ static void arm_smmu_v3_invs_test(struct kunit *test)
/* Test4: merge invs3 (new array) */
test_b = arm_smmu_invs_merge(test_a, &invs3);
kfree(test_a);
+ KUNIT_ASSERT_NOT_ERR_OR_NULL(test, test_b);
arm_smmu_v3_invs_test_verify(test, test_b, ARRAY_SIZE(results4[0]), 0,
results4[0], results4[1], results4[2]);
@@ -737,6 +752,7 @@ static void arm_smmu_v3_invs_test(struct kunit *test)
/* Test6: purge test_b (new array) */
test_a = arm_smmu_invs_purge(test_b);
kfree(test_b);
+ KUNIT_ASSERT_NOT_ERR_OR_NULL(test, test_a);
arm_smmu_v3_invs_test_verify(test, test_a, ARRAY_SIZE(results6[0]), 0,
results6[0], results6[1], results6[2]);
@@ -748,12 +764,14 @@ static void arm_smmu_v3_invs_test(struct kunit *test)
/* Test8: merge invs4 (new array) */
test_b = arm_smmu_invs_merge(test_a, &invs4);
kfree(test_a);
+ KUNIT_ASSERT_NOT_ERR_OR_NULL(test, test_b);
arm_smmu_v3_invs_test_verify(test, test_b, ARRAY_SIZE(results7[0]), 0,
results7[0], results7[1], results7[2]);
/* Test9: merge invs5 (new array) */
test_a = arm_smmu_invs_merge(test_b, &invs5);
kfree(test_b);
+ KUNIT_ASSERT_NOT_ERR_OR_NULL(test, test_a);
arm_smmu_v3_invs_test_verify(test, test_a, ARRAY_SIZE(results8[0]), 0,
results8[0], results8[1], results8[2]);
@@ -765,6 +783,7 @@ static void arm_smmu_v3_invs_test(struct kunit *test)
/* Test11: purge test_a (new array) */
test_b = arm_smmu_invs_purge(test_a);
kfree(test_a);
+ KUNIT_ASSERT_NOT_ERR_OR_NULL(test, test_b);
arm_smmu_v3_invs_test_verify(test, test_b, ARRAY_SIZE(results10[0]), 0,
results10[0], results10[1], results10[2]);
diff --git a/drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3.c b/drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3.c
index 98f105798b89..6d4520c47308 100644
--- a/drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3.c
+++ b/drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3.c
@@ -40,6 +40,11 @@ module_param(disable_msipolling, bool, 0444);
MODULE_PARM_DESC(disable_msipolling,
"Disable MSI-based polling for CMD_SYNC completion.");
+static unsigned int cmdq_max_n_shift = CMDQ_MAX_SZ_SHIFT;
+module_param(cmdq_max_n_shift, uint, 0444);
+MODULE_PARM_DESC(cmdq_max_n_shift,
+ "Cap on the command queue depth, as log2 of the number of entries. Defaults to the hardware maximum; the queue never shrinks below one page. A kdump kernel always uses one page.");
+
static const struct iommu_ops arm_smmu_ops;
static struct iommu_dirty_ops arm_smmu_dirty_ops;
@@ -91,6 +96,7 @@ DEFINE_MUTEX(arm_smmu_asid_lock);
static struct arm_smmu_option_prop arm_smmu_options[] = {
{ ARM_SMMU_OPT_SKIP_PREFETCH, "hisilicon,broken-prefetch-cmd" },
{ ARM_SMMU_OPT_PAGE0_REGS_ONLY, "cavium,cn9900-broken-page1-regspace"},
+ { ARM_SMMU_OPT_OVR_INSTCFG_DATA, "arm,instdata-override"},
{ 0, NULL},
};
@@ -181,8 +187,8 @@ static void queue_sync_cons_out(struct arm_smmu_queue *q)
static void queue_inc_cons(struct arm_smmu_ll_queue *q)
{
- u32 cons = (Q_WRP(q, q->cons) | Q_IDX(q, q->cons)) + 1;
- q->cons = Q_OVF(q->cons) | Q_WRP(q, cons) | Q_IDX(q, cons);
+ u32 cons = Q_POS(q, q->cons) + 1;
+ q->cons = Q_OVF(q->cons) | Q_POS(q, cons);
}
static void queue_sync_cons_ovf(struct arm_smmu_queue *q)
@@ -192,8 +198,7 @@ static void queue_sync_cons_ovf(struct arm_smmu_queue *q)
if (likely(Q_OVF(llq->prod) == Q_OVF(llq->cons)))
return;
- llq->cons = Q_OVF(llq->prod) | Q_WRP(llq, llq->cons) |
- Q_IDX(llq, llq->cons);
+ llq->cons = Q_OVF(llq->prod) | Q_POS(llq, llq->cons);
queue_sync_cons_out(q);
}
@@ -218,8 +223,8 @@ static int queue_sync_prod_in(struct arm_smmu_queue *q)
static u32 queue_inc_prod_n(struct arm_smmu_ll_queue *q, int n)
{
- u32 prod = (Q_WRP(q, q->prod) | Q_IDX(q, q->prod)) + n;
- return Q_OVF(q->prod) | Q_WRP(q, prod) | Q_IDX(q, prod);
+ u32 prod = Q_POS(q, q->prod) + n;
+ return Q_OVF(q->prod) | Q_POS(q, prod);
}
static void queue_poll_init(struct arm_smmu_device *smmu,
@@ -1053,6 +1058,19 @@ static inline int arm_smmu_invs_iter_next_cmp(struct arm_smmu_invs *invs_l,
return arm_smmu_inv_cmp(cur_l, &invs_r->inv[next_r]);
}
+static void arm_smmu_invs_update_caps(struct arm_smmu_invs *invs,
+ const struct arm_smmu_inv *inv)
+{
+ if (arm_smmu_inv_is_ats(inv))
+ invs->has_ats = true;
+
+ if (inv->smmu->features & ARM_SMMU_FEAT_RANGE_INV) {
+ invs->has_range_inv = true;
+ if (inv->smmu->options & ARM_SMMU_OPT_FULL_CONT_RANGE_INV)
+ invs->has_full_cont_range_inv = true;
+ }
+}
+
/**
* arm_smmu_invs_for_each_cmp - Iterate over two sorted arrays computing for
* arm_smmu_invs_merge() or arm_smmu_invs_unref()
@@ -1123,8 +1141,7 @@ struct arm_smmu_invs *arm_smmu_invs_merge(struct arm_smmu_invs *invs,
*/
if (new != new_invs->inv)
WARN_ON_ONCE(arm_smmu_inv_cmp(new - 1, new) == 1);
- if (arm_smmu_inv_is_ats(new))
- new_invs->has_ats = true;
+ arm_smmu_invs_update_caps(new_invs, new);
new++;
}
@@ -1234,8 +1251,7 @@ struct arm_smmu_invs *arm_smmu_invs_purge(struct arm_smmu_invs *invs)
arm_smmu_invs_for_each_entry(invs, i, inv) {
new_invs->inv[num_invs] = *inv;
- if (arm_smmu_inv_is_ats(inv))
- new_invs->has_ats = true;
+ arm_smmu_invs_update_caps(new_invs, inv);
num_invs++;
}
@@ -1271,7 +1287,8 @@ void arm_smmu_get_ste_used(const __le64 *ent, __le64 *used_bits)
cpu_to_le64(STRTAB_STE_1_S1DSS | STRTAB_STE_1_S1CIR |
STRTAB_STE_1_S1COR | STRTAB_STE_1_S1CSH |
STRTAB_STE_1_S1STALLD | STRTAB_STE_1_STRW |
- STRTAB_STE_1_EATS | STRTAB_STE_1_MEV);
+ STRTAB_STE_1_EATS | STRTAB_STE_1_MEV |
+ STRTAB_STE_1_INSTCFG);
used_bits[2] |= cpu_to_le64(STRTAB_STE_2_S2VMID);
/*
@@ -1287,7 +1304,8 @@ void arm_smmu_get_ste_used(const __le64 *ent, __le64 *used_bits)
if (cfg & BIT(1)) {
used_bits[1] |=
cpu_to_le64(STRTAB_STE_1_S2FWB | STRTAB_STE_1_EATS |
- STRTAB_STE_1_SHCFG | STRTAB_STE_1_MEV);
+ STRTAB_STE_1_SHCFG | STRTAB_STE_1_MEV |
+ STRTAB_STE_1_INSTCFG);
used_bits[2] |=
cpu_to_le64(STRTAB_STE_2_S2VMID | STRTAB_STE_2_VTCR |
STRTAB_STE_2_S2AA64 | STRTAB_STE_2_S2ENDI |
@@ -1515,6 +1533,9 @@ static void arm_smmu_write_cd_l1_desc(struct arm_smmu_cdtab_l1 *dst,
{
u64 val = (l2ptr_dma & CTXDESC_L1_DESC_L2PTR_MASK) | CTXDESC_L1_DESC_V;
+ /* Ensure the zero-cleared L2 table is fully visible. */
+ dma_wmb();
+
/* The HW has 64 bit atomicity with stores to the L2 CD table */
WRITE_ONCE(dst->l2ptr, cpu_to_le64(val));
}
@@ -1804,6 +1825,9 @@ static void arm_smmu_write_strtab_l1_desc(struct arm_smmu_strtab_l1 *dst,
val |= FIELD_PREP(STRTAB_L1_DESC_SPAN, STRTAB_SPLIT + 1);
val |= l2ptr_dma & STRTAB_L1_DESC_L2PTR_MASK;
+ /* Ensure the new L2 table is fully visible. */
+ dma_wmb();
+
/* The HW has 64 bit atomicity with stores to the L2 STE table */
WRITE_ONCE(dst->l2ptr, cpu_to_le64(val));
}
@@ -1900,7 +1924,11 @@ void arm_smmu_make_cdtable_ste(struct arm_smmu_ste *target,
STRTAB_STE_1_S1STALLD :
0) |
FIELD_PREP(STRTAB_STE_1_EATS,
- ats_enabled ? STRTAB_STE_1_EATS_TRANS : 0));
+ ats_enabled ? STRTAB_STE_1_EATS_TRANS : 0) |
+ FIELD_PREP(STRTAB_STE_1_INSTCFG,
+ smmu->options & ARM_SMMU_OPT_OVR_INSTCFG_DATA ?
+ STRTAB_STE_1_INSTCFG_DATA :
+ STRTAB_STE_1_INSTCFG_INCOMING));
if ((smmu->features & ARM_SMMU_FEAT_ATTR_TYPES_OVR) &&
s1dss == STRTAB_STE_1_S1DSS_BYPASS)
@@ -1952,7 +1980,11 @@ void arm_smmu_make_s2_domain_ste(struct arm_smmu_ste *target,
target->data[1] = cpu_to_le64(
FIELD_PREP(STRTAB_STE_1_EATS,
- ats_enabled ? STRTAB_STE_1_EATS_TRANS : 0));
+ ats_enabled ? STRTAB_STE_1_EATS_TRANS : 0) |
+ FIELD_PREP(STRTAB_STE_1_INSTCFG,
+ smmu->options & ARM_SMMU_OPT_OVR_INSTCFG_DATA ?
+ STRTAB_STE_1_INSTCFG_DATA :
+ STRTAB_STE_1_INSTCFG_INCOMING));
if (pgtbl_cfg->quirks & IO_PGTABLE_QUIRK_ARM_S2FWB)
target->data[1] |= cpu_to_le64(STRTAB_STE_1_S2FWB);
@@ -2363,8 +2395,8 @@ static irqreturn_t arm_smmu_combined_irq_handler(int irq, void *dev)
return IRQ_WAKE_THREAD;
}
-static struct arm_smmu_cmd
-arm_smmu_atc_inv_to_cmd(u32 sid, int ssid, unsigned long iova, size_t size)
+static struct arm_smmu_cmd arm_smmu_atc_inv_to_cmd(u32 sid, int ssid,
+ struct arm_smmu_tlbi *tlbi)
{
size_t log2_span;
size_t span_mask;
@@ -2386,8 +2418,8 @@ arm_smmu_atc_inv_to_cmd(u32 sid, int ssid, unsigned long iova, size_t size)
* This has the unpleasant side-effect of invalidating all PASID-tagged
* ATC entries within the address range.
*/
- page_start = iova >> inval_grain_shift;
- page_end = (iova + size - 1) >> inval_grain_shift;
+ page_start = tlbi->iova >> inval_grain_shift;
+ page_end = (tlbi->iova + tlbi->size - 1) >> inval_grain_shift;
/*
* In an ATS Invalidate Request, the address must be aligned on the
@@ -2460,126 +2492,231 @@ static void arm_smmu_tlb_inv_context(void *cookie)
arm_smmu_domain_inv(smmu_domain);
}
-static void arm_smmu_cmdq_batch_add_range(struct arm_smmu_device *smmu,
- struct arm_smmu_cmdq_batch *cmds,
- struct arm_smmu_cmd *cmd, bool leaf,
- unsigned long iova, size_t size,
- size_t granule, size_t pgsize)
+/*
+ * Check address alignment for TTL hint per SMMUv3 H.a Section 4.4.1. Address
+ * bits below the alignment must be zero, otherwise UNPREDICTABLE.
+ */
+static bool arm_smmu_ttl_addr_aligned(u64 address, unsigned int tg,
+ unsigned int ttl)
{
- unsigned long end = iova + size, num_pages = 0, tg = pgsize;
- u64 orig_data0 = cmd->data[0];
- size_t inv_range = granule;
- u8 ttl = 0, tg_enc = 0;
+ unsigned int pgsz_lg2 = arm_smmu_pt_level_to_lg2sz(tg, 3 - ttl);
- if (WARN_ON_ONCE(!size))
- return;
+ return !(address & GENMASK_U64(pgsz_lg2 - 1, 0));
+}
- if (smmu->features & ARM_SMMU_FEAT_RANGE_INV) {
- num_pages = size >> tg;
+struct arm_smmu_range_inv {
+ u64 start_tg;
+ /* Normal integer, not encoded. 0 means 0.*/
+ u64 num;
+ unsigned int scale;
+};
- /* Convert page size of 12,14,16 (log2) to 1,2,3 */
- tg_enc = (tg - 10) / 2;
+static unsigned int arm_smmu_range_inv_calc_scale(u64 num_tg)
+{
+ return fls64((num_tg - 1) / (CMDQ_TLBI_RANGE_NUM_MAX + 1));
+}
- /*
- * Determine what level the granule is at. For non-leaf, both
- * io-pgtable and SVA pass a nominal last-level granule because
- * they don't know what level(s) actually apply, so ignore that
- * and leave TTL=0. However for various errata reasons we still
- * want to use a range command, so avoid the SVA corner case
- * where both scale and num could be 0 as well.
- */
- if (leaf)
- ttl = 4 - ((ilog2(granule) - 3) / (tg - 3));
- else if ((num_pages & CMDQ_TLBI_RANGE_NUM_MAX) == 1)
- num_pages++;
- }
+static u64 arm_smmu_range_inv_calc_num(u64 num_tg, unsigned int scale)
+{
+ return DIV_ROUND_UP_ULL(num_tg, 1ULL << scale);
+}
- while (iova < end) {
- if (smmu->features & ARM_SMMU_FEAT_RANGE_INV) {
- /*
- * On each iteration of the loop, the range is 5 bits
- * worth of the aligned size remaining.
- * The range in pages is:
- *
- * range = (num_pages & (0x1f << __ffs(num_pages)))
- */
- unsigned long scale, num;
+/*
+ * Initialize the smallest range invalidation covering num_tg and ending at
+ * last_tg.
+ */
+static struct arm_smmu_range_inv arm_smmu_range_inv_init_end(u64 last_tg,
+ u64 num_tg)
+{
+ struct arm_smmu_range_inv range_inv = {};
+
+ if (!num_tg)
+ return range_inv;
+
+ range_inv.scale = arm_smmu_range_inv_calc_scale(num_tg);
+ range_inv.num = arm_smmu_range_inv_calc_num(num_tg, range_inv.scale);
+ range_inv.start_tg = last_tg - ((range_inv.num << range_inv.scale) - 1);
+ return range_inv;
+}
- /* Determine the power of 2 multiple number of pages */
- scale = __ffs(num_pages);
+static void
+arm_smmu_tlbi_add_range_cmd(struct arm_smmu_tlbi *tlbi,
+ const struct arm_smmu_range_inv *range_inv, u8 ttl,
+ u8 tg_enc)
+{
+ struct arm_smmu_cmd *cmd = &tlbi->range.cmds[tlbi->range.num_cmds++];
+ unsigned int tgsz_lg2 = tg_enc * 2 + 10;
+ u64 iova = range_inv->start_tg << tgsz_lg2;
+ unsigned int num = range_inv->num - 1;
- /* Determine how many chunks of 2^scale size we have */
- num = (num_pages >> scale) & CMDQ_TLBI_RANGE_NUM_MAX;
+ /* 16K granule TTL=1 is reserved (Section 4.4.1) */
+ if (WARN_ON(tgsz_lg2 == 14 && ttl == 1))
+ ttl = 0;
- /* Keep the pre-DS 5-bit truncation when scale > 31 */
- cmd->data[0] = orig_data0 |
- FIELD_PREP(CMDQ_TLBI_0_NUM, num - 1) |
- FIELD_PREP(CMDQ_TLBI_0_SCALE, scale & 0x1f);
+ /* Verify address alignment for the TTL hint */
+ if (ttl && !arm_smmu_ttl_addr_aligned(iova, tgsz_lg2, ttl))
+ ttl = 0;
- /* range is num * 2^scale * pgsize */
- inv_range = num << (scale + tg);
+ /*
+ * SMMUv3 H.a Section 4.4.1: TG!=0, NUM==0, SCALE==0, TTL==0 is Reserved
+ * and causes CERROR_ILL. Single tg uses NUM=0, SCALE=0 with a TTL hint
+ * to target only the exact leaf entry.
+ *
+ * For a single tg invalidation a 0 TTL can come from places like the
+ * SVA path that don't have enough information to get a TTL, or as a
+ * side of effect of the splitting.
+ *
+ * A single-TG invalidation cannot reach this point if it is part of a
+ * CONT group, so it is safe to transform it into a single invalidation.
+ * The ARM_SMMU_OPT_FULL_CONT_RANGE_INV errata does not apply.
+ */
+ if (!num && !range_inv->scale && !ttl)
+ tg_enc = 0;
- /* Clear out the lower order bits for the next iteration */
- num_pages -= num << scale;
- }
+ cmd->data[0] = FIELD_PREP(CMDQ_TLBI_0_NUM, num) |
+ FIELD_PREP(CMDQ_TLBI_0_SCALE, range_inv->scale);
+ cmd->data[1] = FIELD_PREP(CMDQ_TLBI_1_LEAF, tlbi->leaf_only) |
+ FIELD_PREP(CMDQ_TLBI_1_TTL, ttl) |
+ FIELD_PREP(CMDQ_TLBI_1_TG, tg_enc) | iova;
+}
+/*
+ * Generate up to two range TLBI command payloads covering [iova, iova+size).
+ * Sets use_full_inv if the range is too large to represent.
+ *
+ * Normally the first range invalidation is the largest representable span which
+ * does not exceed the requested range. If necessary, the second range
+ * invalidation is the smallest representable range covering the remainder and
+ * is anchored at the end. Any excess coverage from the second range
+ * invalidation overlaps the first instead of exceeding the requested range.
+ *
+ * For SVA on an invs containing an SMMU with ARM_SMMU_OPT_FULL_CONT_RANGE_INV,
+ * produce only a single range invalidation and overinvalidate so any potential CONT is
+ * covered by one command.
+ */
+static void arm_smmu_tlbi_calc_range(struct arm_smmu_tlbi *tlbi,
+ bool single_range_inv)
+{
+ u8 tgsz_lg2 = tlbi->tgsz_lg2;
+ struct arm_smmu_range_inv first = { .start_tg = tlbi->iova >>
+ tgsz_lg2 };
+ u64 last_tg = (tlbi->iova + tlbi->size - 1) >> tgsz_lg2;
+ u64 num_tg = last_tg - first.start_tg + 1;
+ u8 tg_enc = (tgsz_lg2 - 10) / 2;
+ struct arm_smmu_range_inv trail;
+ u8 ttl = 0;
+
+ /*
+ * Determine what level the granule is at. For non-leaf, both io-pgtable
+ * and SVA pass a nominal last-level granule because they don't know
+ * what level(s) actually apply, so leave TTL=0.
+ */
+ if (tlbi->leaf_only)
+ ttl = 4 - ((ilog2(tlbi->iopte_size) - 3) / (tgsz_lg2 - 3));
+
+ /*
+ * The spec defines the invalidated range as:
+ * Range = ((NUM+1) * 2^SCALE) * Translation_Granule_Size
+ * NUM is 5 bits, so (NUM+1) covers 1..32 granules. Find the smallest
+ * SCALE at which a single command could cover num_tg.
+ *
+ * Unlike other IOMMUs the spec has no alignment requirement on the
+ * address beyond alignment to tg (so long as TTL=0).
+ */
+ first.scale = arm_smmu_range_inv_calc_scale(num_tg);
+ if (first.scale > 31) {
+ /* Range too large for a single command do full invalidation */
+ tlbi->range.use_full_inv = true;
+ return;
+ }
+
+ if (single_range_inv) {
/*
- * IPA has fewer bits than VA, but they are reserved in the
- * command and something would be very broken if iova had them
- * set.
+ * Produce a single invalidation by rounding up and disabling
+ * the trailer.
*/
- cmd->data[1] = FIELD_PREP(CMDQ_TLBI_1_LEAF, leaf) |
- FIELD_PREP(CMDQ_TLBI_1_TTL, ttl) |
- FIELD_PREP(CMDQ_TLBI_1_TG, tg_enc) |
- (iova & ~GENMASK_U64(11, 0));
-
- arm_smmu_cmdq_batch_add_cmd_p(smmu, cmds, cmd);
- iova += inv_range;
+ first.num = arm_smmu_range_inv_calc_num(num_tg, first.scale);
+ trail.num = 0;
+ } else {
+ /*
+ * Produce two invalidations by rounding down and adding a
+ * second trailing range invalidation anchored at the end.
+ */
+ first.num = num_tg >> first.scale;
+ trail = arm_smmu_range_inv_init_end(
+ last_tg, num_tg - ((u64)first.num << first.scale));
}
+ arm_smmu_tlbi_add_range_cmd(tlbi, &first, ttl, tg_enc);
+
+ if (trail.num)
+ arm_smmu_tlbi_add_range_cmd(tlbi, &trail, ttl, tg_enc);
}
-static bool arm_smmu_inv_size_too_big(struct arm_smmu_device *smmu, size_t size,
- size_t granule)
+/*
+ * One TLBI command per IOTLB entry, assuming the entries are all at least
+ * iopte_granule sized. Sets use_full_inv if too many commands would be needed
+ * which indicates too high a latency. The threshold is similar to MAX_DVM_OPS
+ * in arch/arm64/include/asm/tlbflush.h for the 4k PAGE_SIZE.
+ */
+static void arm_smmu_tlbi_calc_single(struct arm_smmu_tlbi *tlbi)
{
- size_t max_tlbi_ops;
-
- /* 0 size means invalidate all */
- if (!size || size == SIZE_MAX)
- return true;
+ unsigned long num_ops = tlbi->size / tlbi->iopte_size;
- if (smmu->features & ARM_SMMU_FEAT_RANGE_INV)
- return false;
+ if (!num_ops || num_ops > 512) {
+ tlbi->single.use_full_inv = true;
+ return;
+ }
+ tlbi->single.num = num_ops;
+}
- /*
- * Borrowed from the MAX_TLBI_OPS in arch/arm64/include/asm/tlbflush.h,
- * this is used as a threshold to replace "size_opcode" commands with a
- * single "nsize_opcode" command, when SMMU doesn't implement the range
- * invalidation feature, where there can be too many per-granule TLBIs,
- * resulting in a soft lockup.
- */
- max_tlbi_ops = 1 << (ilog2(granule) - 3);
- return size >= max_tlbi_ops * granule;
+static void arm_smmu_inv_all_cmd(struct arm_smmu_inv *inv,
+ struct arm_smmu_cmdq_batch *cmds,
+ struct arm_smmu_cmd *cmd)
+{
+ u64p_replace_bits(&cmd->data[0], inv->nsize_opcode, CMDQ_0_OP);
+ arm_smmu_cmdq_batch_add_cmd_p(inv->smmu, cmds, cmd);
}
-/* Used by non INV_TYPE_ATS* invalidations */
-static void arm_smmu_inv_to_cmdq_batch(struct arm_smmu_inv *inv,
+/*
+ * Used by non INV_TYPE_ATS* invalidations. Returns true if it fell back to
+ * full invalidation using nsize_opcode.
+ */
+static bool arm_smmu_inv_to_cmdq_batch(struct arm_smmu_inv *inv,
struct arm_smmu_cmdq_batch *cmds,
struct arm_smmu_cmd *cmd,
- bool leaf,
- unsigned long iova, size_t size,
- unsigned int granule)
+ struct arm_smmu_tlbi *tlbi)
{
- if (arm_smmu_inv_size_too_big(inv->smmu, size, granule)) {
- struct arm_smmu_cmd nsize_cmd = *cmd;
+ u64 iova = tlbi->iova;
+ unsigned int i;
- u64p_replace_bits(&nsize_cmd.data[0], inv->nsize_opcode,
- CMDQ_0_OP);
- arm_smmu_cmdq_batch_add_cmd_p(inv->smmu, cmds, &nsize_cmd);
- return;
+ if (inv->smmu->features & ARM_SMMU_FEAT_RANGE_INV) {
+ if (tlbi->range.use_full_inv) {
+ arm_smmu_inv_all_cmd(inv, cmds, cmd);
+ return true;
+ }
+ for (i = 0; i < tlbi->range.num_cmds; i++) {
+ struct arm_smmu_cmd range_cmd = tlbi->range.cmds[i];
+
+ range_cmd.data[0] |= cmd->data[0];
+ range_cmd.data[1] |= cmd->data[1];
+ arm_smmu_cmdq_batch_add_cmd_p(inv->smmu, cmds,
+ &range_cmd);
+ }
+ return false;
}
- arm_smmu_cmdq_batch_add_range(inv->smmu, cmds, cmd, leaf,
- iova, size, granule, inv->pgsize);
+ if (tlbi->single.use_full_inv) {
+ arm_smmu_inv_all_cmd(inv, cmds, cmd);
+ return true;
+ }
+
+ for (i = 0; i < tlbi->single.num; i++) {
+ cmd->data[1] = FIELD_PREP(CMDQ_TLBI_1_LEAF, tlbi->leaf_only) |
+ (iova & ~GENMASK_U64(11, 0));
+ iova += tlbi->iopte_size;
+ arm_smmu_cmdq_batch_add_cmd_p(inv->smmu, cmds, cmd);
+ }
+ return false;
}
static inline bool arm_smmu_invs_end_batch(struct arm_smmu_inv *cur,
@@ -2598,10 +2735,10 @@ static inline bool arm_smmu_invs_end_batch(struct arm_smmu_inv *cur,
return false;
}
-static void __arm_smmu_domain_inv_range(struct arm_smmu_invs *invs,
- unsigned long iova, size_t size,
- unsigned int granule, bool leaf)
+static void arm_smmu_domain_tlbi_inv(struct arm_smmu_tlbi *tlbi,
+ struct arm_smmu_invs *invs)
{
+ struct arm_smmu_inv *used_s12_vmall = NULL;
struct arm_smmu_cmdq_batch cmds = {};
struct arm_smmu_inv *cur;
struct arm_smmu_inv *end;
@@ -2630,18 +2767,24 @@ static void __arm_smmu_domain_inv_range(struct arm_smmu_invs *invs,
case INV_TYPE_S1_ASID:
cmd = arm_smmu_make_cmd_tlbi(cur->size_opcode,
cur->id, 0);
- arm_smmu_inv_to_cmdq_batch(cur, &cmds, &cmd, leaf,
- iova, size, granule);
+ arm_smmu_inv_to_cmdq_batch(cur, &cmds, &cmd, tlbi);
break;
case INV_TYPE_S2_VMID:
- cmd = arm_smmu_make_cmd_tlbi(cur->size_opcode,
- 0, cur->id);
- arm_smmu_inv_to_cmdq_batch(cur, &cmds, &cmd, leaf,
- iova, size, granule);
+ cmd = arm_smmu_make_cmd_tlbi(cur->size_opcode, 0,
+ cur->id);
+ if (arm_smmu_inv_to_cmdq_batch(cur, &cmds, &cmd, tlbi))
+ used_s12_vmall = cur + 1;
break;
case INV_TYPE_S2_VMID_S1_CLEAR:
- /* CMDQ_OP_TLBI_S12_VMALL already flushed S1 entries */
- if (arm_smmu_inv_size_too_big(cur->smmu, size, granule))
+ /*
+ * S2_VMID used CMDQ_OP_TLBI_S12_VMALL which already
+ * flushed S1 entries. These two types always come in
+ * pairs and arm_smmu_inv_cmp() ensures that they are
+ * consecutive in the list for the same SMMU. There may
+ * be several pairings so check this is paired with the
+ * one that did the full invalidation.
+ */
+ if (used_s12_vmall == cur)
break;
arm_smmu_cmdq_batch_add_cmd(
smmu, &cmds,
@@ -2652,7 +2795,7 @@ static void __arm_smmu_domain_inv_range(struct arm_smmu_invs *invs,
arm_smmu_cmdq_batch_add_cmd(
smmu, &cmds,
arm_smmu_atc_inv_to_cmd(cur->id, cur->ssid,
- iova, size));
+ tlbi));
break;
case INV_TYPE_ATS_FULL:
arm_smmu_cmdq_batch_add_cmd(
@@ -2679,12 +2822,20 @@ static void __arm_smmu_domain_inv_range(struct arm_smmu_invs *invs,
}
}
-void arm_smmu_domain_inv_range(struct arm_smmu_domain *smmu_domain,
- unsigned long iova, size_t size,
- unsigned int granule, bool leaf)
+/*
+ * Perform the invalidation with the parameters described by tlbi on everything
+ * caching for smmu_domain. tlbi is an on-stack structure that contains both the
+ * input description and working memory for this function it must be discarded
+ * after this returns.
+ */
+void arm_smmu_domain_tlbi(struct arm_smmu_tlbi *tlbi,
+ struct arm_smmu_domain *smmu_domain)
{
struct arm_smmu_invs *invs;
+ if (!tlbi->single.use_full_inv)
+ arm_smmu_tlbi_calc_single(tlbi);
+
/*
* An invalidation request must follow some IOPTE change and then load
* an invalidation array. In the meantime, a domain attachment mutates
@@ -2702,7 +2853,7 @@ void arm_smmu_domain_inv_range(struct arm_smmu_domain *smmu_domain,
*
* [CPU0] | [CPU1]
* change IOPTE on new domain: |
- * arm_smmu_domain_inv_range() { | arm_smmu_install_new_domain_invs()
+ * arm_smmu_domain_tlbi() { | arm_smmu_install_new_domain_invs()
* smp_mb(); // ensures IOPTE | arm_smmu_install_ste_for_dev {
* // seen by SMMU | dma_wmb(); // ensures invs update
* // load the updated invs | // before updating STE
@@ -2716,6 +2867,20 @@ void arm_smmu_domain_inv_range(struct arm_smmu_domain *smmu_domain,
invs = rcu_dereference(smmu_domain->invs);
/*
+ * Only precompute range invalidation commands when they will be used.
+ * The invs generation ensures this matches the instances being
+ * invalidated.
+ */
+ if (invs->has_range_inv) {
+ if (!tlbi->range.use_full_inv) {
+ arm_smmu_tlbi_calc_range(
+ tlbi,
+ smmu_domain->stage == ARM_SMMU_DOMAIN_SVA &&
+ invs->has_full_cont_range_inv);
+ }
+ }
+
+ /*
* Avoid locking unless ATS is being used. No ATC invalidation can be
* going on after a domain is detached.
*/
@@ -2723,10 +2888,10 @@ void arm_smmu_domain_inv_range(struct arm_smmu_domain *smmu_domain,
unsigned long flags;
read_lock_irqsave(&invs->rwlock, flags);
- __arm_smmu_domain_inv_range(invs, iova, size, granule, leaf);
+ arm_smmu_domain_tlbi_inv(tlbi, invs);
read_unlock_irqrestore(&invs->rwlock, flags);
} else {
- __arm_smmu_domain_inv_range(invs, iova, size, granule, leaf);
+ arm_smmu_domain_tlbi_inv(tlbi, invs);
}
rcu_read_unlock();
@@ -2742,12 +2907,23 @@ static void arm_smmu_tlb_inv_page_nosync(struct iommu_iotlb_gather *gather,
iommu_iotlb_gather_add_page(domain, gather, iova, granule);
}
+/*
+ * Called by io-pgtable-arm.c for each single table level it wants to remove.
+ * size is the size of the table level and granule is the tg in bytes. This must
+ * clear the walk cache and any leaves within the range.
+ */
static void arm_smmu_tlb_inv_walk(unsigned long iova, size_t size,
size_t granule, void *cookie)
{
struct arm_smmu_domain *smmu_domain = cookie;
+ struct arm_smmu_tlbi tlbi = {
+ .tgsz_lg2 = smmu_domain->tgsz_lg2,
+ .iova = iova,
+ .size = size,
+ .iopte_size = 1 << smmu_domain->tgsz_lg2,
+ };
- arm_smmu_domain_inv_range(smmu_domain, iova, size, granule, false);
+ arm_smmu_domain_tlbi(&tlbi, smmu_domain);
}
static const struct iommu_flush_ops arm_smmu_flush_ops = {
@@ -2933,6 +3109,7 @@ static int arm_smmu_domain_finalise(struct arm_smmu_domain *smmu_domain,
return -ENOMEM;
smmu_domain->domain.pgsize_bitmap = pgtbl_cfg.pgsize_bitmap;
+ smmu_domain->tgsz_lg2 = __ffs(pgtbl_cfg.pgsize_bitmap);
smmu_domain->domain.geometry.aperture_end = (1UL << pgtbl_cfg.ias) - 1;
smmu_domain->domain.geometry.force_aperture = true;
if (enable_dirty && smmu_domain->stage == ARM_SMMU_DOMAIN_S1)
@@ -3172,15 +3349,13 @@ static void arm_smmu_disable_iopf(struct arm_smmu_master *master,
static struct arm_smmu_inv *
arm_smmu_master_build_inv(struct arm_smmu_master *master,
- enum arm_smmu_inv_type type, u32 id, ioasid_t ssid,
- size_t pgsize)
+ enum arm_smmu_inv_type type, u32 id, ioasid_t ssid)
{
struct arm_smmu_invs *build_invs = master->build_invs;
struct arm_smmu_inv *cur, inv = {
.smmu = master->smmu,
.type = type,
.id = id,
- .pgsize = pgsize,
};
if (WARN_ON(build_invs->num_invs >= build_invs->max_invs))
@@ -3232,28 +3407,24 @@ arm_smmu_master_build_invs(struct arm_smmu_master *master, bool ats_enabled,
ioasid_t ssid, struct arm_smmu_domain *smmu_domain)
{
const bool nesting = smmu_domain->nest_parent;
- size_t pgsize = 0, i;
+ size_t i;
iommu_group_mutex_assert(master->dev);
master->build_invs->num_invs = 0;
- /* Range-based invalidation requires the leaf pgsize for calculation */
- if (master->smmu->features & ARM_SMMU_FEAT_RANGE_INV)
- pgsize = __ffs(smmu_domain->domain.pgsize_bitmap);
-
switch (smmu_domain->stage) {
case ARM_SMMU_DOMAIN_SVA:
case ARM_SMMU_DOMAIN_S1:
if (!arm_smmu_master_build_inv(master, INV_TYPE_S1_ASID,
smmu_domain->cd.asid,
- IOMMU_NO_PASID, pgsize))
+ IOMMU_NO_PASID))
return NULL;
break;
case ARM_SMMU_DOMAIN_S2:
if (!arm_smmu_master_build_inv(master, INV_TYPE_S2_VMID,
smmu_domain->s2_cfg.vmid,
- IOMMU_NO_PASID, pgsize))
+ IOMMU_NO_PASID))
return NULL;
break;
default:
@@ -3265,7 +3436,7 @@ arm_smmu_master_build_invs(struct arm_smmu_master *master, bool ats_enabled,
if (nesting) {
if (!arm_smmu_master_build_inv(
master, INV_TYPE_S2_VMID_S1_CLEAR,
- smmu_domain->s2_cfg.vmid, IOMMU_NO_PASID, 0))
+ smmu_domain->s2_cfg.vmid, IOMMU_NO_PASID))
return NULL;
}
@@ -3276,7 +3447,7 @@ arm_smmu_master_build_invs(struct arm_smmu_master *master, bool ats_enabled,
*/
if (!arm_smmu_master_build_inv(
master, nesting ? INV_TYPE_ATS_FULL : INV_TYPE_ATS,
- master->streams[i].id, ssid, 0))
+ master->streams[i].id, ssid))
return NULL;
}
@@ -3285,9 +3456,9 @@ arm_smmu_master_build_invs(struct arm_smmu_master *master, bool ats_enabled,
return master->build_invs;
}
-static void arm_smmu_remove_master_domain(struct arm_smmu_master *master,
- struct iommu_domain *domain,
- ioasid_t ssid)
+static struct arm_smmu_master_domain *
+arm_smmu_remove_master_domain(struct arm_smmu_master *master,
+ struct iommu_domain *domain, ioasid_t ssid)
{
struct arm_smmu_domain *smmu_domain = to_smmu_domain_devices(domain);
struct arm_smmu_master_domain *master_domain;
@@ -3295,7 +3466,7 @@ static void arm_smmu_remove_master_domain(struct arm_smmu_master *master,
unsigned long flags;
if (!smmu_domain)
- return;
+ return NULL;
if (domain->type == IOMMU_DOMAIN_NESTED)
nested_ats_flush = to_smmu_nested_domain(domain)->enable_ats;
@@ -3310,8 +3481,24 @@ static void arm_smmu_remove_master_domain(struct arm_smmu_master *master,
}
spin_unlock_irqrestore(&smmu_domain->devices_lock, flags);
+ /* arm_smmu_attach_release() will free it */
+ return master_domain;
+}
+
+/* Release the old master_domain detached by arm_smmu_remove_master_domain() */
+void arm_smmu_attach_release(struct arm_smmu_attach_state *state)
+{
+ struct arm_smmu_master_domain *master_domain = state->old_master_domain;
+ struct arm_smmu_master *master = state->master;
+
+ iommu_group_mutex_assert(master->dev);
+
+ if (!master_domain)
+ return;
+
arm_smmu_disable_iopf(master, master_domain);
kfree(master_domain);
+ state->old_master_domain = NULL;
}
/*
@@ -3609,7 +3796,8 @@ void arm_smmu_attach_commit(struct arm_smmu_attach_state *state)
arm_smmu_atc_inv_master(master, IOMMU_NO_PASID);
}
- arm_smmu_remove_master_domain(master, state->old_domain, state->ssid);
+ state->old_master_domain = arm_smmu_remove_master_domain(
+ master, state->old_domain, state->ssid);
arm_smmu_install_old_domain_invs(state);
master->ats_enabled = state->ats_enabled;
}
@@ -3684,6 +3872,7 @@ static int arm_smmu_attach_dev(struct iommu_domain *domain, struct device *dev,
arm_smmu_attach_commit(&state);
mutex_unlock(&arm_smmu_asid_lock);
+ arm_smmu_attach_release(&state);
return 0;
}
@@ -3766,8 +3955,10 @@ int arm_smmu_set_pasid(struct arm_smmu_master *master,
mutex_lock(&arm_smmu_asid_lock);
ret = arm_smmu_attach_prepare(&state, &smmu_domain->domain);
- if (ret)
- goto out_unlock;
+ if (ret) {
+ mutex_unlock(&arm_smmu_asid_lock);
+ return ret;
+ }
/*
* We don't want to obtain to the asid_lock too early, so fix up the
@@ -3781,10 +3972,9 @@ int arm_smmu_set_pasid(struct arm_smmu_master *master,
arm_smmu_update_ste(master, sid_domain, state.ats_enabled);
arm_smmu_attach_commit(&state);
-
-out_unlock:
mutex_unlock(&arm_smmu_asid_lock);
- return ret;
+ arm_smmu_attach_release(&state);
+ return 0;
}
static int arm_smmu_blocking_set_dev_pasid(struct iommu_domain *new_domain,
@@ -3804,9 +3994,11 @@ static int arm_smmu_blocking_set_dev_pasid(struct iommu_domain *new_domain,
arm_smmu_clear_cd(master, pasid);
if (master->ats_enabled)
arm_smmu_atc_inv_master(master, pasid);
- arm_smmu_remove_master_domain(master, &smmu_domain->domain, pasid);
+ state.old_master_domain = arm_smmu_remove_master_domain(
+ master, &smmu_domain->domain, pasid);
arm_smmu_install_old_domain_invs(&state);
mutex_unlock(&arm_smmu_asid_lock);
+ arm_smmu_attach_release(&state);
/*
* When the last user of the CD table goes away downgrade the STE back
@@ -3869,6 +4061,7 @@ static void arm_smmu_attach_dev_ste(struct iommu_domain *domain,
arm_smmu_install_ste_for_dev(master, ste);
arm_smmu_attach_commit(&state);
mutex_unlock(&arm_smmu_asid_lock);
+ arm_smmu_attach_release(&state);
/*
* This has to be done after removing the master from the
@@ -4025,13 +4218,18 @@ static void arm_smmu_iotlb_sync(struct iommu_domain *domain,
struct iommu_iotlb_gather *gather)
{
struct arm_smmu_domain *smmu_domain = to_smmu_domain(domain);
+ struct arm_smmu_tlbi tlbi = {
+ .tgsz_lg2 = smmu_domain->tgsz_lg2,
+ .iova = gather->start,
+ .size = gather->end - gather->start + 1,
+ .iopte_size = gather->pgsize,
+ .leaf_only = true,
+ };
if (!gather->pgsize)
return;
- arm_smmu_domain_inv_range(smmu_domain, gather->start,
- gather->end - gather->start + 1,
- gather->pgsize, true);
+ arm_smmu_domain_tlbi(&tlbi, smmu_domain);
}
static phys_addr_t
@@ -4122,6 +4320,13 @@ static int arm_smmu_insert_master(struct arm_smmu_device *smmu,
sizeof(master->streams[0]), arm_smmu_stream_id_cmp,
NULL);
+ /*
+ * Clear after sorting: RB_CLEAR_NODE() records the node's own address,
+ * which sort_nonatomic() invalidates by relocating the entries.
+ */
+ for (i = 0; i < fwspec->num_ids; i++)
+ RB_CLEAR_NODE(&master->streams[i].node);
+
mutex_lock(&smmu->streams_mutex);
for (i = 0; i < fwspec->num_ids; i++) {
struct arm_smmu_stream *new_stream = &master->streams[i];
@@ -4154,7 +4359,9 @@ static int arm_smmu_insert_master(struct arm_smmu_device *smmu,
if (ret) {
for (i--; i >= 0; i--)
- rb_erase(&master->streams[i].node, &smmu->streams);
+ if (!RB_EMPTY_NODE(&master->streams[i].node))
+ rb_erase(&master->streams[i].node,
+ &smmu->streams);
kfree(master->streams);
kfree(master->build_invs);
}
@@ -4174,7 +4381,8 @@ static void arm_smmu_remove_master(struct arm_smmu_master *master)
mutex_lock(&smmu->streams_mutex);
for (i = 0; i < fwspec->num_ids; i++)
- rb_erase(&master->streams[i].node, &smmu->streams);
+ if (!RB_EMPTY_NODE(&master->streams[i].node))
+ rb_erase(&master->streams[i].node, &smmu->streams);
mutex_unlock(&smmu->streams_mutex);
kfree(master->streams);
@@ -4412,6 +4620,56 @@ static struct iommu_dirty_ops arm_smmu_dirty_ops = {
};
/* Probing and initialisation functions */
+
+/**
+ * arm_smmu_queue_max_n_shift() - pick the log2 depth of a queue
+ * @hw_max_n_shift: log2 depth the hardware advertises in IDR1
+ * @ent_sz_shift: log2 of the queue entry size in bytes
+ * @limit_n_shift: log2 depth to cap the queue at
+ *
+ * A kdump capture kernel gets one page worth of entries whatever the limit.
+ *
+ * @limit_n_shift is floored at one page, because coherent DMA is page
+ * granular: a shallower queue occupies the same memory as one that fills the
+ * page, and arm_smmu_init_one_queue() stops shrinking at a page too.
+ */
+static u32 arm_smmu_queue_max_n_shift(u32 hw_max_n_shift, u32 ent_sz_shift,
+ u32 limit_n_shift)
+{
+ u32 floor_n_shift = PAGE_SHIFT - ent_sz_shift;
+
+ if (is_kdump_kernel())
+ return min(hw_max_n_shift, floor_n_shift);
+
+ limit_n_shift = max(limit_n_shift, floor_n_shift);
+ return min(hw_max_n_shift, limit_n_shift);
+}
+
+static inline u32 arm_smmu_evtq_max_n_shift(u32 hw_max_n_shift)
+{
+ /* Capped to ensure natural alignment */
+ return arm_smmu_queue_max_n_shift(hw_max_n_shift, EVTQ_ENT_SZ_SHIFT,
+ EVTQ_MAX_SZ_SHIFT);
+}
+
+static inline u32 arm_smmu_priq_max_n_shift(u32 hw_max_n_shift)
+{
+ /* Capped to ensure natural alignment */
+ return arm_smmu_queue_max_n_shift(hw_max_n_shift, PRIQ_ENT_SZ_SHIFT,
+ PRIQ_MAX_SZ_SHIFT);
+}
+
+/*
+ * Command queues are also allocated by the Tegra241 CMDQV for its VCMDQs, which
+ * need the same depth decision.
+ */
+u32 arm_smmu_cmdq_max_n_shift(u32 hw_max_n_shift)
+{
+ /* Capped to ensure natural alignment */
+ return arm_smmu_queue_max_n_shift(hw_max_n_shift, CMDQ_ENT_SZ_SHIFT,
+ min(CMDQ_MAX_SZ_SHIFT, cmdq_max_n_shift));
+}
+
int arm_smmu_init_one_queue(struct arm_smmu_device *smmu,
struct arm_smmu_queue *q, void __iomem *page,
unsigned long prod_off, unsigned long cons_off,
@@ -5009,11 +5267,20 @@ static void arm_smmu_device_iidr_probe(struct arm_smmu_device *smmu)
/* Arm errata 2268618, 2812531 */
smmu->features &= ~ARM_SMMU_FEAT_NESTING;
}
+ /* Arm errata 3777127 */
+ smmu->options |= ARM_SMMU_OPT_FULL_CONT_RANGE_INV;
break;
case IIDR_PRODUCTID_ARM_MMU_L1:
+ /* Arm errata 3878312 */
+ smmu->features &= ~ARM_SMMU_FEAT_BTM;
+ break;
case IIDR_PRODUCTID_ARM_MMU_S3:
- /* Arm errata 3878312/3995052 */
+ /* Arm errata 3995052 */
smmu->features &= ~ARM_SMMU_FEAT_BTM;
+ /* Arm errata 3673557 */
+ if (variant < 1 || (variant == 1 && revision < 1))
+ smmu->options |=
+ ARM_SMMU_OPT_FULL_CONT_RANGE_INV;
break;
}
break;
@@ -5155,9 +5422,14 @@ static int arm_smmu_device_hw_probe(struct arm_smmu_device *smmu)
if (reg & IDR1_ATTR_TYPES_OVR)
smmu->features |= ARM_SMMU_FEAT_ATTR_TYPES_OVR;
- /* Queue sizes, capped to ensure natural alignment */
- smmu->cmdq.q.llq.max_n_shift = min_t(u32, CMDQ_MAX_SZ_SHIFT,
- FIELD_GET(IDR1_CMDQS, reg));
+ if ((smmu->options & ARM_SMMU_OPT_OVR_INSTCFG_DATA) &&
+ !(reg & IDR1_ATTR_PERMS_OVR)) {
+ dev_err(smmu->dev, "Inst/Data attribute override not supported\n");
+ return -ENXIO;
+ }
+
+ smmu->cmdq.q.llq.max_n_shift =
+ arm_smmu_cmdq_max_n_shift(FIELD_GET(IDR1_CMDQS, reg));
if (smmu->cmdq.q.llq.max_n_shift <= ilog2(CMDQ_BATCH_ENTRIES)) {
/*
* We don't support splitting up batches, so one batch of
@@ -5170,10 +5442,10 @@ static int arm_smmu_device_hw_probe(struct arm_smmu_device *smmu)
return -ENXIO;
}
- smmu->evtq.q.llq.max_n_shift = min_t(u32, EVTQ_MAX_SZ_SHIFT,
- FIELD_GET(IDR1_EVTQS, reg));
- smmu->priq.q.llq.max_n_shift = min_t(u32, PRIQ_MAX_SZ_SHIFT,
- FIELD_GET(IDR1_PRIQS, reg));
+ smmu->evtq.q.llq.max_n_shift =
+ arm_smmu_evtq_max_n_shift(FIELD_GET(IDR1_EVTQS, reg));
+ smmu->priq.q.llq.max_n_shift =
+ arm_smmu_priq_max_n_shift(FIELD_GET(IDR1_PRIQS, reg));
/* SID/SSID sizes */
smmu->ssid_bits = FIELD_GET(IDR1_SSIDSIZE, reg);
@@ -5633,7 +5905,7 @@ static void arm_smmu_device_shutdown(struct platform_device *pdev)
{
struct arm_smmu_device *smmu = platform_get_drvdata(pdev);
- arm_smmu_device_disable(smmu);
+ arm_smmu_disable_action(smmu);
}
static const struct of_device_id arm_smmu_of_match[] = {
diff --git a/drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3.h b/drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3.h
index dd2fee2f560e..bb048e08d613 100644
--- a/drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3.h
+++ b/drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3.h
@@ -52,6 +52,7 @@ struct arm_vsmmu;
#define IDR1_QUEUES_PRESET (1 << 29)
#define IDR1_REL (1 << 28)
#define IDR1_ATTR_TYPES_OVR (1 << 27)
+#define IDR1_ATTR_PERMS_OVR (1 << 26)
#define IDR1_CMDQS GENMASK(25, 21)
#define IDR1_EVTQS GENMASK(20, 16)
#define IDR1_PRIQS GENMASK(15, 11)
@@ -187,6 +188,8 @@ struct arm_vsmmu;
#define Q_IDX(llq, p) ((p) & ((1 << (llq)->max_n_shift) - 1))
#define Q_WRP(llq, p) ((p) & (1 << (llq)->max_n_shift))
+/* A position is Q_WRP | Q_IDX, wrapping at twice the queue capacity */
+#define Q_POS(llq, p) (Q_WRP(llq, p) | Q_IDX(llq, p))
#define Q_OVERFLOW_FLAG (1U << 31)
#define Q_OVF(p) ((p) & Q_OVERFLOW_FLAG)
#define Q_ENT(q, p) ((q)->base + \
@@ -285,6 +288,10 @@ static inline u32 arm_smmu_strtab_l2_idx(u32 sid)
#define STRTAB_STE_1_SHCFG GENMASK_ULL(45, 44)
#define STRTAB_STE_1_SHCFG_INCOMING 1UL
+#define STRTAB_STE_1_INSTCFG GENMASK_ULL(51, 50)
+#define STRTAB_STE_1_INSTCFG_INCOMING 0UL
+#define STRTAB_STE_1_INSTCFG_DATA 2UL
+
#define STRTAB_STE_2_S2VMID GENMASK_ULL(15, 0)
#define STRTAB_STE_2_VTCR GENMASK_ULL(50, 32)
#define STRTAB_STE_2_VTCR_S2T0SZ GENMASK_ULL(5, 0)
@@ -736,10 +743,9 @@ struct arm_smmu_inv {
u8 size_opcode;
u8 nsize_opcode;
u32 id; /* ASID or VMID or SID */
- union {
- size_t pgsize; /* ARM_SMMU_FEAT_RANGE_INV */
- u32 ssid; /* INV_TYPE_ATS */
- };
+
+ /* Only used by INV_TYPE_ATS */
+ u32 ssid;
int users; /* users=0 to mark as a trash to be purged */
};
@@ -759,6 +765,9 @@ static inline bool arm_smmu_inv_is_ats(const struct arm_smmu_inv *inv)
* Must not be greater than @num_invs
* @rwlock: optional rwlock to fence ATS operations
* @has_ats: flag if the array contains an INV_TYPE_ATS or INV_TYPE_ATS_FULL
+ * @has_range_inv: flag if any entry's SMMU supports range invalidation
+ * @has_full_cont_range_inv: flag if any entry's SMMU requires the CONT range
+ * invalidation workaround
* @rcu: rcu head for kfree_rcu()
* @inv: flexible invalidation array
*
@@ -788,6 +797,8 @@ struct arm_smmu_invs {
size_t num_trashes;
rwlock_t rwlock;
bool has_ats;
+ bool has_range_inv;
+ bool has_full_cont_range_inv;
struct rcu_head rcu;
struct arm_smmu_inv inv[] __counted_by(max_invs);
};
@@ -805,6 +816,40 @@ static inline struct arm_smmu_invs *arm_smmu_invs_alloc(size_t num_invs)
return new_invs;
}
+/* Generic page-table level 0 is the leaf-only level. */
+static inline unsigned int arm_smmu_pt_level_to_lg2sz(unsigned int tgsz_lg2,
+ unsigned int level)
+{
+ return tgsz_lg2 + (tgsz_lg2 - ilog2(sizeof(u64))) * level;
+}
+
+static inline unsigned int arm_smmu_pt_lg2sz_to_level(unsigned int tgsz_lg2,
+ unsigned int lg2sz)
+{
+ return (lg2sz - tgsz_lg2) / (tgsz_lg2 - ilog2(sizeof(u64)));
+}
+
+struct arm_smmu_tlbi {
+ unsigned long iova;
+ size_t size;
+ /* page or block size of the leaf iopte */
+ unsigned int iopte_size;
+ /* Base Translation Granule of the page table */
+ u8 tgsz_lg2;
+ bool leaf_only;
+
+ struct {
+ bool use_full_inv;
+ u16 num;
+ } single;
+
+ struct {
+ bool use_full_inv;
+ u8 num_cmds;
+ struct arm_smmu_cmd cmds[2];
+ } range;
+};
+
struct arm_smmu_evtq {
struct arm_smmu_queue q;
struct iopf_queue *iopf;
@@ -934,6 +979,12 @@ struct arm_smmu_device {
#define ARM_SMMU_OPT_MSIPOLL (1 << 2)
#define ARM_SMMU_OPT_CMDQ_FORCE_SYNC (1 << 3)
#define ARM_SMMU_OPT_TEGRA241_CMDQV (1 << 4)
+#define ARM_SMMU_OPT_OVR_INSTCFG_DATA (1 << 5)
+/*
+ * Range invalidation is mandatory and one range invalidation must fully span an
+ * invalidated CONT
+ */
+#define ARM_SMMU_OPT_FULL_CONT_RANGE_INV (1 << 6)
u32 options;
struct arm_smmu_cmdq cmdq;
@@ -1049,6 +1100,7 @@ struct arm_smmu_domain {
spinlock_t devices_lock;
bool enforce_cache_coherency : 1;
bool nest_parent : 1;
+ u8 tgsz_lg2;
struct mmu_notifier mmu_notifier;
};
@@ -1154,17 +1206,24 @@ int arm_smmu_set_pasid(struct arm_smmu_master *master,
struct arm_smmu_domain *smmu_domain, ioasid_t pasid,
struct arm_smmu_cd *cd, struct iommu_domain *old);
-void arm_smmu_domain_inv_range(struct arm_smmu_domain *smmu_domain,
- unsigned long iova, size_t size,
- unsigned int granule, bool leaf);
+void arm_smmu_domain_tlbi(struct arm_smmu_tlbi *tlbi,
+ struct arm_smmu_domain *smmu_domain);
static inline void arm_smmu_domain_inv(struct arm_smmu_domain *smmu_domain)
{
- arm_smmu_domain_inv_range(smmu_domain, 0, 0, 0, false);
+ /* Prefilled for invalidate all */
+ struct arm_smmu_tlbi tlbi = {
+ .tgsz_lg2 = smmu_domain->tgsz_lg2,
+ .single.use_full_inv = true,
+ .range.use_full_inv = true,
+ };
+
+ arm_smmu_domain_tlbi(&tlbi, smmu_domain);
}
void __arm_smmu_cmdq_skip_err(struct arm_smmu_device *smmu,
struct arm_smmu_cmdq *cmdq);
+u32 arm_smmu_cmdq_max_n_shift(u32 hw_max_n_shift);
int arm_smmu_init_one_queue(struct arm_smmu_device *smmu,
struct arm_smmu_queue *q, void __iomem *page,
unsigned long prod_off, unsigned long cons_off,
@@ -1204,12 +1263,14 @@ struct arm_smmu_attach_state {
struct arm_smmu_vmaster *vmaster;
struct arm_smmu_inv_state old_domain_invst;
struct arm_smmu_inv_state new_domain_invst;
+ struct arm_smmu_master_domain *old_master_domain;
bool ats_enabled;
};
int arm_smmu_attach_prepare(struct arm_smmu_attach_state *state,
struct iommu_domain *new_domain);
void arm_smmu_attach_commit(struct arm_smmu_attach_state *state);
+void arm_smmu_attach_release(struct arm_smmu_attach_state *state);
void arm_smmu_install_ste_for_dev(struct arm_smmu_master *master,
const struct arm_smmu_ste *target);
diff --git a/drivers/iommu/arm/arm-smmu-v3/tegra241-cmdqv.c b/drivers/iommu/arm/arm-smmu-v3/tegra241-cmdqv.c
index 6644075c1431..710a4c694b94 100644
--- a/drivers/iommu/arm/arm-smmu-v3/tegra241-cmdqv.c
+++ b/drivers/iommu/arm/arm-smmu-v3/tegra241-cmdqv.c
@@ -663,7 +663,7 @@ static int tegra241_vcmdq_alloc_smmu_cmdq(struct tegra241_vcmdq *vcmdq)
/* Cap queue size to SMMU's IDR1.CMDQS and ensure natural alignment */
regval = readl_relaxed(smmu->base + ARM_SMMU_IDR1);
q->llq.max_n_shift =
- min_t(u32, CMDQ_MAX_SZ_SHIFT, FIELD_GET(IDR1_CMDQS, regval));
+ arm_smmu_cmdq_max_n_shift(FIELD_GET(IDR1_CMDQS, regval));
/* Use the common helper to init the VCMDQ, and then... */
ret = arm_smmu_init_one_queue(smmu, q, vcmdq->page0,
diff --git a/drivers/iommu/bcm2712-iommu-cache.c b/drivers/iommu/bcm2712-iommu-cache.c
new file mode 100644
index 000000000000..8ed436943b83
--- /dev/null
+++ b/drivers/iommu/bcm2712-iommu-cache.c
@@ -0,0 +1,84 @@
+// 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/bits.h>
+#include <linux/cleanup.h>
+#include <linux/err.h>
+#include <linux/iopoll.h>
+#include <linux/of_platform.h>
+#include <linux/platform_device.h>
+#include <linux/spinlock.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 BIT(0)
+#define MMUC_CONTROL_FLUSH BIT(1)
+#define MMUC_CONTROL_FLUSHING BIT(2)
+
+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..301855475c88
--- /dev/null
+++ b/drivers/iommu/bcm2712-iommu.c
@@ -0,0 +1,550 @@
+// 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;
+};
+
+static inline void bcm2712_iommu_writel(struct bcm2712_iommu *mmu,
+ unsigned int offset, u32 val)
+{
+ writel(val, mmu->reg_base + offset);
+}
+
+static inline u32 bcm2712_iommu_readl(struct bcm2712_iommu *mmu,
+ unsigned int offset)
+{
+ return readl(mmu->reg_base + offset);
+}
+
+static struct bcm2712_iommu_domain *
+to_bcm2712_domain(struct iommu_domain *domain)
+{
+ return container_of(domain, struct bcm2712_iommu_domain, base);
+}
+
+static void bcm2712_iommu_init(struct bcm2712_iommu *mmu)
+{
+ unsigned int bigpage_width, superpage_width;
+ u32 u = bcm2712_iommu_readl(mmu, 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 */
+ bcm2712_iommu_writel(mmu, MMMU_CTRL_OFFSET,
+ MMMU_CTRL_CAP_EXCEEDED | MMMU_CTRL_PT_INVALID |
+ MMMU_CTRL_WRITE_VIOLATION |
+ MMMU_CTRL_STATS_CLEAR);
+
+ /* Put MMU into 2-level mode */
+ bcm2712_iommu_writel(mmu, MMMU_MISC_OFFSET,
+ bcm2712_iommu_readl(mmu, MMMU_MISC_OFFSET) &
+ ~MMMU_MISC_SINGLE_TABLE);
+}
+
+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);
+
+ guard(spinlock_irqsave)(&mmu->hw_lock);
+ bcm2712_iommu_writel(mmu, MMMU_CTRL_OFFSET, 0);
+ mmu->domain = NULL;
+
+ 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_clear_and_enable(struct bcm2712_iommu *mmu)
+ __must_hold(&mmu->hw_lock)
+{
+ u32 val;
+
+ bcm2712_iommu_cache_flush(mmu->cache);
+ bcm2712_iommu_writel(mmu, MMMU_CTRL_OFFSET,
+ MMMU_CTRL_OPERATING_FLAGS | MMMU_CTRL_TLB_CLEAR);
+ return readl_poll_timeout_atomic(mmu->reg_base + MMMU_CTRL_OFFSET, val,
+ !(val & MMMU_CTRL_TLB_CLEARING), 0,
+ 50);
+}
+
+
+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;
+ int ret = 0;
+
+ /* all attached devices must belong to the same IOMMU instance */
+ if (mydomain->mmu != mmu)
+ return -EINVAL;
+
+ scoped_guard(spinlock_irqsave, &mmu->hw_lock) {
+ if (mmu->domain == mydomain)
+ break;
+
+ /* Configure translation aperture */
+ bcm2712_iommu_writel(mmu, MMMU_ADDR_CAP_OFFSET,
+ MMMU_ADDR_CAP_ENABLE |
+ (domain->geometry.aperture_end >>
+ ADDR_CAP_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);
+ bcm2712_iommu_writel(mmu, MMMU_PT_PA_BASE_OFFSET,
+ info.pt_base >> IOMMU_PAGE_SHIFT);
+
+ /* Disable bypass window */
+ bcm2712_iommu_writel(mmu, MMMU_BYPASS_START_OFFSET, 0);
+ bcm2712_iommu_writel(mmu, MMMU_BYPASS_END_OFFSET, 0);
+
+ /*
+ * A default (error) page is used to catch illegal reads/writes.
+ */
+ default_page_pfn = virt_to_phys(mydomain->default_page) >>
+ IOMMU_PAGE_SHIFT;
+ bcm2712_iommu_writel(mmu, MMMU_ILLEGAL_ADR_OFFSET,
+ MMMU_ILLEGAL_ADR_ENABLE |
+ default_page_pfn);
+
+ ret = bcm2712_iommu_clear_and_enable(mmu);
+ if (ret) {
+ bcm2712_iommu_writel(mmu, MMMU_CTRL_OFFSET, 0);
+ mmu->domain = NULL;
+ break;
+ }
+
+ mmu->domain = mydomain;
+ }
+
+ if (ret)
+ dev_err_ratelimited(mmu->dev,
+ "TLB clear timed out during attach\n");
+
+ return ret;
+}
+
+static int bcm2712_iommu_shootdown_range(struct bcm2712_iommu *mmu,
+ unsigned long iova, size_t size)
+ __must_hold(&mmu->hw_lock)
+{
+ unsigned long start_pfn = iova >> IOMMU_PAGE_SHIFT;
+ unsigned long end_pfn = (iova + size - 1) >> IOMMU_PAGE_SHIFT;
+ unsigned long pfn;
+ u32 val;
+ int ret;
+
+ /* Shootdown register deals with 4 pages at a time */
+ for (pfn = round_down(start_pfn, 4); pfn <= end_pfn; pfn += 4) {
+ bcm2712_iommu_writel(mmu, MMMU_SHOOT_DOWN_OFFSET,
+ MMMU_SHOOT_DOWN_SHOOT | pfn);
+ ret = readl_poll_timeout_atomic(
+ mmu->reg_base + MMMU_SHOOT_DOWN_OFFSET, val,
+ !(val & MMMU_SHOOT_DOWN_SHOOTING), 0, 50);
+ if (ret)
+ return ret;
+ }
+
+ return 0;
+}
+
+static int bcm2712_iommu_sync_range(struct iommu_domain *domain,
+ unsigned long iova, size_t size)
+{
+ struct bcm2712_iommu_domain *mydomain = to_bcm2712_domain(domain);
+ struct bcm2712_iommu *mmu = mydomain->mmu;
+ int ret = 0;
+
+ if (!size)
+ return 0;
+
+ scoped_guard(spinlock_irqsave, &mmu->hw_lock) {
+ if (mmu->domain != mydomain)
+ return 0;
+
+ /* If invalidating more than 1MB, just do a full TLB clear */
+ if (size >= SZ_1M) {
+ ret = bcm2712_iommu_clear_and_enable(mmu);
+ } else {
+ bcm2712_iommu_cache_flush(mmu->cache);
+ ret = bcm2712_iommu_shootdown_range(mmu, iova, size);
+ }
+ }
+
+ if (ret)
+ dev_err_ratelimited(mmu->dev,
+ "TLB sync timed out (size=%#zx)\n", size);
+
+ return ret;
+}
+
+static void bcm2712_iommu_sync(struct iommu_domain *domain,
+ struct iommu_iotlb_gather *gather)
+{
+ bcm2712_iommu_sync_range(domain, gather->start,
+ gather->end - gather->start + 1);
+ iommu_put_pages_list(&gather->freelist);
+}
+
+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;
+
+ scoped_guard(spinlock_irqsave, &mmu->hw_lock) {
+ if (mmu->domain == mydomain) {
+ bcm2712_iommu_writel(mmu, MMMU_CTRL_OFFSET, 0);
+ mmu->domain = NULL;
+ }
+ }
+
+ pt_iommu_deinit(&mydomain->pt.iommu);
+ if (mydomain->default_page)
+ iommu_free_pages(mydomain->default_page);
+ kfree(mydomain);
+}
+
+static const struct iommu_domain_ops bcm2712_paging_domain_ops = {
+ IOMMU_PT_DOMAIN_OPS(bcm2712),
+ .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,
+};
+
+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;
+ cfg.common.features = BIT(PT_FEAT_DMA_INCOHERENT);
+
+ /* Bigpage and superpage sizes are typically 64K and 1M, but may vary */
+ if (mmu->bigpage_size)
+ cfg.bigpage_lg2 = ilog2(mmu->bigpage_size);
+ if (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 25-bit output address PFN */
+ cfg.common.hw_max_oasz_lg2 = 25 + 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.ops = &bcm2712_paging_domain_ops;
+ return &domain->base;
+
+err:
+ bcm2712_iommu_domain_free(&domain->base);
+ return NULL;
+}
+
+static struct bcm2712_iommu *
+bcm2712_iommu_get_by_fwnode(struct fwnode_handle *fwnode)
+{
+ struct device *dev __free(put_device) =
+ bus_find_device_by_fwnode(&platform_bus_type, fwnode);
+
+ return dev ? dev_get_drvdata(dev) : NULL;
+}
+
+static struct iommu_device *bcm2712_iommu_probe_device(struct device *dev)
+{
+ struct iommu_fwspec *fwspec = dev_iommu_fwspec_get(dev);
+ struct bcm2712_iommu *mmu;
+
+ if (!fwspec || !fwspec->iommu_fwnode)
+ return ERR_PTR(-ENODEV);
+
+ mmu = bcm2712_iommu_get_by_fwnode(fwspec->iommu_fwnode);
+ if (!mmu)
+ return ERR_PTR(-ENODEV);
+
+ dev_iommu_priv_set(dev, mmu);
+
+ return &mmu->iommu;
+}
+
+static int bcm2712_iommu_of_xlate(struct device *dev,
+ const struct of_phandle_args *args)
+{
+ return iommu_fwspec_add_ids(dev, args->args, 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,
+ .get_resv_regions = iommu_dma_get_resv_regions,
+ .of_xlate = bcm2712_iommu_of_xlate,
+};
+
+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 device_node *cache_np __free(device_node) =
+ of_parse_phandle(dev->of_node, "brcm,iommu-cache", 0);
+ if (!cache_np)
+ return dev_err_probe(dev, -ENOENT,
+ "missing brcm,iommu-cache property\n");
+
+ struct platform_device *cache_pdev __free(platform_device_put) =
+ of_find_device_by_node(cache_np);
+ if (!cache_pdev)
+ return dev_err_probe(dev, -EPROBE_DEFER,
+ "waiting for cache device\n");
+
+ mmu->cache = platform_get_drvdata(cache_pdev);
+ if (!mmu->cache)
+ return dev_err_probe(dev, -EPROBE_DEFER,
+ "waiting for cache driver probe\n");
+
+ return 0;
+}
+
+static int bcm2712_iommu_probe(struct platform_device *pdev)
+{
+ struct device *dev = &pdev->dev;
+ 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;
+
+ platform_set_drvdata(pdev, mmu);
+ bcm2712_iommu_init(mmu);
+
+ ret = iommu_device_sysfs_add(&mmu->iommu, dev, NULL, "%s",
+ 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");
diff --git a/drivers/iommu/dma-iommu.c b/drivers/iommu/dma-iommu.c
index 26b9c2d71cd3..78742aeba932 100644
--- a/drivers/iommu/dma-iommu.c
+++ b/drivers/iommu/dma-iommu.c
@@ -37,6 +37,7 @@
#include "dma-iommu.h"
#include "iommu-pages.h"
+#include "iommu-priv.h"
struct iommu_dma_msi_page {
struct list_head list;
@@ -1477,6 +1478,11 @@ int iommu_dma_map_sg(struct device *dev, struct scatterlist *sg, int nents,
sg_dma_len(s) = s_length;
s->offset -= s_iova_off;
s_length = iova_align(iovad, s_length + s_iova_off);
+ if (overflows_type(s_length, s->length)) {
+ ret = -EOVERFLOW;
+
+ goto out_restore_sg;
+ }
s->length = s_length;
/*
@@ -1493,7 +1499,20 @@ int iommu_dma_map_sg(struct device *dev, struct scatterlist *sg, int nents,
* time through here (i.e. before it has a meaningful value).
*/
if (pad_len && pad_len < s_length - 1) {
- prev->length += pad_len;
+ unsigned int new_prev_len;
+
+ /*
+ * For large mappings spanning multiple GBs we
+ * may not be able to fit all needed padding into
+ * sg->length.
+ */
+ if (check_add_overflow(prev->length, pad_len, &new_prev_len)) {
+ ret = -EOVERFLOW;
+
+ goto out_restore_sg;
+ }
+
+ prev->length = new_prev_len;
iova_len += pad_len;
}
@@ -2163,12 +2182,6 @@ out_err:
dev_clear_dma_iommu(dev);
}
-static bool has_msi_cookie(const struct iommu_domain *domain)
-{
- return domain && (domain->cookie_type == IOMMU_COOKIE_DMA_IOVA ||
- domain->cookie_type == IOMMU_COOKIE_DMA_MSI);
-}
-
static size_t cookie_msi_granule(const struct iommu_domain *domain)
{
switch (domain->cookie_type) {
@@ -2250,11 +2263,6 @@ int iommu_dma_sw_msi(struct iommu_domain *domain, struct msi_desc *desc,
struct device *dev = msi_desc_to_dev(desc);
const struct iommu_dma_msi_page *msi_page;
- if (!has_msi_cookie(domain)) {
- msi_desc_set_iommu_msi_iova(desc, 0, 0);
- return 0;
- }
-
iommu_group_mutex_assert(dev);
msi_page = iommu_dma_get_msi_page(dev, msi_addr, domain);
if (!msi_page)
diff --git a/drivers/iommu/exynos-iommu.c b/drivers/iommu/exynos-iommu.c
index 874d05f4b396..0bca3662fdc2 100644
--- a/drivers/iommu/exynos-iommu.c
+++ b/drivers/iommu/exynos-iommu.c
@@ -8,6 +8,7 @@
#define DEBUG
#endif
+#include <linux/bitfield.h>
#include <linux/clk.h>
#include <linux/dma-mapping.h>
#include <linux/err.h>
@@ -143,6 +144,10 @@ static u32 lv2ent_offset(sysmmu_iova_t iova)
#define CTRL_VM_FAULT_MODE_STALL BIT(3)
#define CAPA0_CAPA1_EXIST BIT(11)
#define CAPA1_VCR_ENABLED BIT(14)
+#define CAPA1_NO_BLOCK_MODE BIT(15)
+#define FAULT_INFO_AXID GENMASK(15, 0)
+#define FAULT_INFO_AXLEN GENMASK(19, 16)
+#define FAULT_INFO_WRITE BIT(20)
/* common registers */
#define REG_MMU_CTRL 0x000
@@ -193,6 +198,7 @@ struct sysmmu_fault {
sysmmu_iova_t addr; /* IOVA address that caused fault */
const char *name; /* human readable fault name */
unsigned int type; /* fault type for report_iommu_fault() */
+ u32 info; /* raw transaction info (v7+ only) */
};
struct sysmmu_v1_fault_info {
@@ -306,6 +312,7 @@ struct sysmmu_drvdata {
/* v7 fields */
bool has_vcr; /* virtual machine control register */
+ bool no_block; /* BLOCK mode not implemented */
};
#define SYSMMU_REG(data, reg) ((data)->sfrbase + (data)->variant->reg)
@@ -358,7 +365,8 @@ static int exynos_sysmmu_v7_get_fault_info(struct sysmmu_drvdata *data,
fault->addr = readl(SYSMMU_REG(data, fault_va));
fault->name = sysmmu_v7_fault_names[itype % 4];
- fault->type = (info & BIT(20)) ? IOMMU_FAULT_WRITE : IOMMU_FAULT_READ;
+ fault->type = (info & FAULT_INFO_WRITE) ? IOMMU_FAULT_WRITE : IOMMU_FAULT_READ;
+ fault->info = info;
return 0;
}
@@ -511,6 +519,7 @@ static void __sysmmu_get_vcr(struct sysmmu_drvdata *data)
u32 capa1 = readl(data->sfrbase + REG_V7_CAPA1);
data->has_vcr = capa1 & CAPA1_VCR_ENABLED;
+ data->no_block = capa1 & CAPA1_NO_BLOCK_MODE;
}
static void __sysmmu_get_version(struct sysmmu_drvdata *data)
@@ -555,6 +564,12 @@ static void show_fault_information(struct sysmmu_drvdata *data,
dev_name(data->master),
fault->type == IOMMU_FAULT_READ ? "READ" : "WRITE",
fault->name, fault->addr);
+ /* AxID identifies the issuing port inside the master */
+ if (data->variant->fault_info)
+ dev_err(data->sysmmu, "transaction info %#010x: AxID %#lx, AxLEN %lu\n",
+ fault->info,
+ FIELD_GET(FAULT_INFO_AXID, fault->info),
+ FIELD_GET(FAULT_INFO_AXLEN, fault->info));
dev_dbg(data->sysmmu, "Page table base: %pa\n", &data->pgtable);
ent = section_entry(phys_to_virt(data->pgtable), fault->addr);
dev_dbg(data->sysmmu, "\tLv1 entry: %#x\n", *ent);
@@ -652,7 +667,12 @@ static void __sysmmu_enable(struct sysmmu_drvdata *data)
__sysmmu_enable_clocks(data);
spin_lock_irqsave(&data->lock, flags);
- writel(CTRL_BLOCK, data->sfrbase + REG_MMU_CTRL);
+ /*
+ * On no-block hardware CTRL_BLOCK acts as a plain enable; keep the
+ * MMU disabled until it is fully programmed.
+ */
+ if (!data->no_block)
+ writel(CTRL_BLOCK, data->sfrbase + REG_MMU_CTRL);
__sysmmu_init_config(data);
__sysmmu_set_ptbase(data, data->pgtable);
__sysmmu_enable_vid(data);
@@ -677,7 +697,14 @@ static void sysmmu_tlb_invalidate_flpdcache(struct sysmmu_drvdata *data,
spin_lock_irqsave(&data->lock, flags);
if (data->active && data->version >= MAKE_MMU_VER(3, 3)) {
clk_enable(data->clk_master);
- if (sysmmu_block(data)) {
+ /*
+ * No-block hardware accepts invalidation writes while the
+ * MMU is running; it is also v7+, so flush-all covers the
+ * FLPD cache.
+ */
+ if (data->no_block) {
+ __sysmmu_tlb_invalidate(data);
+ } else if (sysmmu_block(data)) {
if (data->version >= MAKE_MMU_VER(5, 0))
__sysmmu_tlb_invalidate(data);
else
@@ -713,7 +740,9 @@ static void sysmmu_tlb_invalidate_entry(struct sysmmu_drvdata *data,
if (MMU_MAJ_VER(data->version) == 2)
num_inv = min_t(unsigned int, size / SPAGE_SIZE, 64);
- if (sysmmu_block(data)) {
+ if (data->no_block) {
+ __sysmmu_tlb_invalidate_entry(data, iova, num_inv);
+ } else if (sysmmu_block(data)) {
__sysmmu_tlb_invalidate_entry(data, iova, num_inv);
sysmmu_unblock(data);
}
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..2a81dac960ac 100644
--- a/drivers/iommu/generic_pt/Kconfig
+++ b/drivers/iommu/generic_pt/Kconfig
@@ -72,6 +72,15 @@ 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"
+ 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
@@ -79,6 +88,7 @@ config IOMMU_PT_KUNIT_TEST
depends on IOMMU_PT_RISCV64 || !IOMMU_PT_RISCV64
depends on IOMMU_PT_X86_64 || !IOMMU_PT_X86_64
depends on IOMMU_PT_VTDSS || !IOMMU_PT_VTDSS
+ depends on IOMMU_PT_BCM2712 || !IOMMU_PT_BCM2712
default KUNIT_ALL_TESTS
help
Enable kunit tests for GENERIC_PT and IOMMU_PT that covers all the
diff --git a/drivers/iommu/generic_pt/fmt/Makefile b/drivers/iommu/generic_pt/fmt/Makefile
index e308760d02b7..a43f3b81d2db 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..2c5b034441c1
--- /dev/null
+++ b/drivers/iommu/generic_pt/fmt/bcm2712.h
@@ -0,0 +1,288 @@
+/* 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 25-bit PFN */
+ PT_MAX_OUTPUT_ADDRESS_LG2 = 25 + 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(24, 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
+
+static inline bool bcm2712pt_can_have_leaf(const struct pt_state *pts)
+{
+ return true;
+}
+#define pt_can_have_leaf bcm2712pt_can_have_leaf
+
+/* 4MB pages are installed at level 1, everything else at level 0 */
+static inline unsigned int bcm2712pt_pgsz_lg2_to_level(struct pt_common *common,
+ unsigned int pgsize_lg2)
+{
+ return pgsize_lg2 == ilog2(SZ_4M) ? 1 : 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 (pts->level == 1)
+ return SZ_4M;
+
+ return SZ_4K |
+ (table->bigpage_lg2 ? BIT_ULL(table->bigpage_lg2) : 0) |
+ (table->superpage_lg2 ? BIT_ULL(table->superpage_lg2) : 0);
+}
+#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;
+
+ /*
+ * Superpage/bigpage contiguous mapping (really just hinting) is handled
+ * via PAGESIZE bits on each leaf entry.
+ */
+ 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;
+
+ if (pts->level == 1) {
+ if (FIELD_GET(BCM2712PT_PAGESIZE, pts->entry) == 3)
+ return PT_ENTRY_OA;
+ return PT_ENTRY_TABLE;
+ }
+
+ return PT_ENTRY_OA;
+}
+#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 (pts->level == 1) {
+ /* Level 1 hugepage (4MB) */
+ entry |= FIELD_PREP(BCM2712PT_PAGESIZE, 3);
+ WRITE_ONCE(tablep[pts->index], entry);
+ pts->entry = entry;
+ return;
+ }
+
+ 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 bigpage/superpage
+ * contiguous mappings to permit TLB optimization
+ */
+ 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
+
+static inline unsigned int bcm2712pt_max_sw_bit(struct pt_common *common)
+{
+ return 2;
+}
+#define pt_max_sw_bit bcm2712pt_max_sw_bit
+
+static inline u32 bcm2712pt_sw_bit(unsigned int bitnr)
+{
+ if (__builtin_constant_p(bitnr) && bitnr > 2)
+ BUILD_BUG();
+ if (PT_WARN_ON(bitnr > 2))
+ return 0;
+
+ /* Bits 27, 26 and 25 are believed spare */
+ return BIT(27 - bitnr);
+}
+#define pt_sw_bit bcm2712pt_sw_bit
+
+/* --- 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.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 = 37,
+ .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..f1d983be1716
--- /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 u64 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..43fbc3cf3adc
--- /dev/null
+++ b/drivers/iommu/generic_pt/fmt/iommu_bcm2712.c
@@ -0,0 +1,6 @@
+// SPDX-License-Identifier: GPL-2.0-only
+#define PT_FMT bcm2712
+#define PT_SUPPORTED_FEATURES BIT(PT_FEAT_DMA_INCOHERENT)
+
+#include <linux/generic_pt/iommu.h>
+#include "iommu_template.h"
diff --git a/drivers/iommu/generic_pt/kunit_iommu_pt.h b/drivers/iommu/generic_pt/kunit_iommu_pt.h
index ece1c9b8c55d..9fc71db74234 100644
--- a/drivers/iommu/generic_pt/kunit_iommu_pt.h
+++ b/drivers/iommu/generic_pt/kunit_iommu_pt.h
@@ -403,7 +403,7 @@ static void test_pgsize_boundary(struct kunit *test)
priv->smallest_pgsz != SZ_4K)
kunit_skip(test, "Format does not have the required range");
- do_map(test, 0xfef80000, 0x208b95d000, 0xfef9ffff - 0xfef80000 + 1);
+ do_map(test, 0xfef80000, 0x8b95d000, 0xfef9ffff - 0xfef80000 + 1);
}
/* See https://lore.kernel.org/r/20250826143816.38686-1-eugkoira@amazon.com */
diff --git a/drivers/iommu/hyperv/Kconfig b/drivers/iommu/hyperv/Kconfig
new file mode 100644
index 000000000000..f2f993f647c3
--- /dev/null
+++ b/drivers/iommu/hyperv/Kconfig
@@ -0,0 +1,15 @@
+# SPDX-License-Identifier: GPL-2.0-only
+# Hyper-V para-virtualized IOMMU driver for Linux guests
+config HYPERV_PVIOMMU
+ bool "Microsoft Hyper-V para-virtualized IOMMU driver for Linux guests"
+ depends on X86_64 && HYPERV && PCI
+ select IOMMU_DMA
+ select GENERIC_PT
+ select IOMMU_PT
+ select IOMMU_PT_X86_64
+ default HYPERV
+ help
+ Para-virtualized IOMMU driver for Linux guests running on
+ Microsoft Hyper-V. Provides DMA remapping and IOTLB
+ flush support to enable DMA isolation for PCI devices
+ assigned to the guest.
diff --git a/drivers/iommu/hyperv/Makefile b/drivers/iommu/hyperv/Makefile
index 6ef0ef97f3dd..d3b2a259439c 100644
--- a/drivers/iommu/hyperv/Makefile
+++ b/drivers/iommu/hyperv/Makefile
@@ -1,2 +1,3 @@
# SPDX-License-Identifier: GPL-2.0
obj-$(CONFIG_IRQ_REMAP) += hv-irq-remap-x86.o
+obj-$(CONFIG_HYPERV_PVIOMMU) += hv-iommu-guest.o
diff --git a/drivers/iommu/hyperv/hv-iommu-guest.c b/drivers/iommu/hyperv/hv-iommu-guest.c
new file mode 100644
index 000000000000..876a22f21f1e
--- /dev/null
+++ b/drivers/iommu/hyperv/hv-iommu-guest.c
@@ -0,0 +1,682 @@
+// SPDX-License-Identifier: GPL-2.0
+
+/*
+ * Hyper-V para-virtualized IOMMU driver for Linux guests.
+ *
+ * Copyright (C) 2024-2026 Microsoft, Inc.
+ */
+
+#define pr_fmt(fmt) "Hyper-V pvIOMMU: " fmt
+#define dev_fmt(fmt) pr_fmt(fmt)
+
+#include <linux/iommu.h>
+#include <linux/pci.h>
+#include <linux/dma-map-ops.h>
+#include <linux/generic_pt/iommu.h>
+#include <linux/pci-ats.h>
+
+#include <asm/iommu.h>
+#include <asm/hypervisor.h>
+#include <asm/mshyperv.h>
+
+#include "iommu.h"
+#include "../iommu-pages.h"
+
+static struct hv_iommu_dev *hv_iommu_device;
+
+/*
+ * Identity and blocking domains are static singletons: identity is a 1:1
+ * passthrough with no page table, blocking rejects all DMA. Neither holds
+ * per-IOMMU state, so one instance suffices even with multiple vIOMMUs.
+ */
+static const struct iommu_domain_ops hv_iommu_identity_domain_ops;
+static const struct iommu_domain_ops hv_iommu_blocking_domain_ops;
+static struct iommu_ops hv_iommu_ops;
+
+static struct hv_iommu_domain hv_identity_domain = {
+ .domain = {
+ .type = IOMMU_DOMAIN_IDENTITY,
+ .ops = &hv_iommu_identity_domain_ops,
+ .owner = &hv_iommu_ops,
+ },
+};
+
+static struct hv_iommu_domain hv_blocking_domain = {
+ .domain = {
+ .type = IOMMU_DOMAIN_BLOCKED,
+ .ops = &hv_iommu_blocking_domain_ops,
+ .owner = &hv_iommu_ops,
+ },
+};
+
+static inline bool hv_iommu_present(u64 cap)
+{
+ return cap & HV_IOMMU_CAP_PRESENT;
+}
+
+static inline bool hv_iommu_s1_domain_supported(u64 cap)
+{
+ return cap & HV_IOMMU_CAP_S1;
+}
+
+static inline bool hv_iommu_5lvl_supported(u64 cap)
+{
+ return cap & HV_IOMMU_CAP_S1_5LVL;
+}
+
+static inline bool hv_iommu_ats_supported(u64 cap)
+{
+ return cap & HV_IOMMU_CAP_ATS;
+}
+
+static int hv_create_device_domain(struct hv_iommu_domain *hv_domain)
+{
+ int ret;
+ u64 status;
+ unsigned long flags;
+ struct hv_input_create_device_domain *input;
+
+ ret = ida_alloc_range(&hv_iommu_device->domain_ids,
+ hv_iommu_device->first_domain,
+ hv_iommu_device->last_domain, GFP_KERNEL);
+ if (ret < 0)
+ return ret;
+
+ hv_domain->device_domain.partition_id = HV_PARTITION_ID_SELF;
+ hv_domain->device_domain.domain_id.type = HV_DEVICE_DOMAIN_TYPE_S1;
+ hv_domain->device_domain.domain_id.id = ret;
+ hv_domain->hv_iommu = hv_iommu_device;
+
+ local_irq_save(flags);
+
+ input = *this_cpu_ptr(hyperv_pcpu_input_arg);
+ memset(input, 0, sizeof(*input));
+ input->device_domain = hv_domain->device_domain;
+ input->create_device_domain_flags.forward_progress_required = 1;
+ input->create_device_domain_flags.inherit_owning_vtl = 0;
+ status = hv_do_hypercall(HVCALL_CREATE_DEVICE_DOMAIN, input, NULL);
+
+ local_irq_restore(flags);
+
+ if (!hv_result_success(status)) {
+ hv_status_err(status, "HVCALL_CREATE_DEVICE_DOMAIN failed\n");
+ ida_free(&hv_iommu_device->domain_ids,
+ hv_domain->device_domain.domain_id.id);
+ }
+
+ return hv_result_to_errno(status);
+}
+
+static void hv_delete_device_domain(struct hv_iommu_domain *hv_domain)
+{
+ u64 status;
+ unsigned long flags;
+ struct hv_input_delete_device_domain *input;
+
+ local_irq_save(flags);
+
+ input = *this_cpu_ptr(hyperv_pcpu_input_arg);
+ memset(input, 0, sizeof(*input));
+ input->device_domain = hv_domain->device_domain;
+ status = hv_do_hypercall(HVCALL_DELETE_DEVICE_DOMAIN, input, NULL);
+
+ local_irq_restore(flags);
+
+ if (!hv_result_success(status))
+ hv_status_err(status, "HVCALL_DELETE_DEVICE_DOMAIN failed\n");
+
+ ida_free(&hv_domain->hv_iommu->domain_ids,
+ hv_domain->device_domain.domain_id.id);
+}
+
+static int
+hv_configure_device_domain(struct hv_iommu_domain *hv_domain,
+ const struct hv_device_domain_settings *settings)
+{
+ u64 status;
+ unsigned long flags;
+ struct hv_input_configure_device_domain *input;
+
+ local_irq_save(flags);
+
+ input = *this_cpu_ptr(hyperv_pcpu_input_arg);
+ memset(input, 0, sizeof(*input));
+ input->device_domain = hv_domain->device_domain;
+ input->settings = *settings;
+ status = hv_do_hypercall(HVCALL_CONFIGURE_DEVICE_DOMAIN, input, NULL);
+
+ local_irq_restore(flags);
+
+ if (!hv_result_success(status))
+ hv_status_err(status,
+ "HVCALL_CONFIGURE_DEVICE_DOMAIN failed\n");
+
+ return hv_result_to_errno(status);
+}
+
+static int
+hv_create_configure_device_domain(struct hv_iommu_domain *hv_domain,
+ const struct hv_device_domain_settings *settings)
+{
+ int ret;
+
+ ret = hv_create_device_domain(hv_domain);
+ if (ret)
+ return ret;
+
+ ret = hv_configure_device_domain(hv_domain, settings);
+ if (ret)
+ hv_delete_device_domain(hv_domain);
+
+ return ret;
+}
+
+static bool hv_iommu_capable(struct device *dev, enum iommu_cap cap)
+{
+ return cap == IOMMU_CAP_CACHE_COHERENCY;
+}
+
+static void hv_flush_device_domain(struct hv_iommu_domain *hv_domain)
+{
+ u64 status;
+ unsigned long flags;
+ struct hv_input_flush_device_domain *input;
+
+ local_irq_save(flags);
+
+ input = *this_cpu_ptr(hyperv_pcpu_input_arg);
+ memset(input, 0, sizeof(*input));
+ input->device_domain = hv_domain->device_domain;
+ status = hv_do_hypercall(HVCALL_FLUSH_DEVICE_DOMAIN, input, NULL);
+
+ local_irq_restore(flags);
+
+ WARN_ONCE(!hv_result_success(status),
+ "HVCALL_FLUSH_DEVICE_DOMAIN failed: %#llx (%s)\n",
+ status, hv_result_to_string(status));
+}
+
+static int hv_iommu_attach_dev(struct iommu_domain *domain, struct device *dev,
+ struct iommu_domain *old)
+{
+ u64 status;
+ u32 prefix;
+ unsigned long flags;
+ struct pci_dev *pdev;
+ struct hv_input_attach_device_domain *input;
+ struct hv_iommu_domain *hv_domain = to_hv_iommu_domain(domain);
+ int pci_domain;
+ int ret;
+
+ pdev = to_pci_dev(dev);
+ dev_dbg(dev, "attaching to domain %d\n",
+ hv_domain->device_domain.domain_id.id);
+
+ pci_domain = pci_domain_nr(pdev->bus);
+ ret = hv_pci_lookup_dev_id(pci_domain, &prefix);
+ if (ret) {
+ dev_err(dev,
+ "no logical device ID registered for PCI domain %04x\n",
+ pci_domain);
+ return ret;
+ }
+
+ local_irq_save(flags);
+
+ input = *this_cpu_ptr(hyperv_pcpu_input_arg);
+ memset(input, 0, sizeof(*input));
+ input->device_domain = hv_domain->device_domain;
+ input->device_id.as_uint64 = (u64)prefix | PCI_FUNC(pdev->devfn);
+ status = hv_do_hypercall(HVCALL_ATTACH_DEVICE_DOMAIN, input, NULL);
+
+ local_irq_restore(flags);
+
+ if (!hv_result_success(status) &&
+ hv_result(status) != HV_STATUS_DEVICE_ALREADY_IN_DOMAIN) {
+ hv_status_err(status, "HVCALL_ATTACH_DEVICE_DOMAIN failed\n");
+ return hv_result_to_errno(status);
+ }
+
+ if (domain != &hv_blocking_domain.domain &&
+ !pdev->ats_enabled &&
+ hv_iommu_ats_supported(hv_domain->hv_iommu->cap) &&
+ pci_ats_supported(pdev))
+ pci_enable_ats(pdev, PAGE_SHIFT);
+
+ return 0;
+}
+
+static int hv_iommu_blocking_attach_dev(struct iommu_domain *domain,
+ struct device *dev,
+ struct iommu_domain *old)
+{
+ struct pci_dev *pdev = to_pci_dev(dev);
+ int ret;
+
+ if (pdev->ats_enabled)
+ pci_disable_ats(pdev);
+
+ ret = hv_iommu_attach_dev(domain, dev, old);
+
+ /*
+ * Attaching to the blocking domain only asks the hypervisor to
+ * disable translation and IOPF for the device, so it cannot fail
+ * unless there is a driver or hypervisor bug. Return the hypercall
+ * status rather than 0 so that a failure on the DMA ownership claim
+ * path (VFIO/iommufd) fails the claim instead of leaving the device
+ * unblocked. WARN since such a failure indicates a bug.
+ */
+ WARN_ON(ret);
+ return ret;
+}
+
+static int
+hv_iommu_get_dev_property(struct device *dev, u32 code,
+ struct hv_output_get_logical_device_property *prop)
+{
+ u64 status;
+ u32 prefix;
+ unsigned long flags;
+ int ret;
+ struct pci_dev *pdev = to_pci_dev(dev);
+ struct hv_input_get_logical_device_property *input;
+ struct hv_output_get_logical_device_property *output;
+
+ ret = hv_pci_lookup_dev_id(pci_domain_nr(pdev->bus), &prefix);
+ if (ret)
+ return ret;
+
+ local_irq_save(flags);
+
+ /*
+ * The combined input and output fit in the per-CPU input page,
+ * so no separate output page is needed.
+ */
+ input = *this_cpu_ptr(hyperv_pcpu_input_arg);
+ output = (struct hv_output_get_logical_device_property *)(input + 1);
+ memset(input, 0, sizeof(*input));
+ input->partition_id = HV_PARTITION_ID_SELF;
+ input->logical_device_id = (u64)prefix | PCI_FUNC(pdev->devfn);
+ input->code = code;
+ status = hv_do_hypercall(HVCALL_GET_LOGICAL_DEVICE_PROPERTY,
+ input, output);
+ *prop = *output;
+
+ local_irq_restore(flags);
+
+ if (!hv_result_success(status))
+ hv_status_err(status,
+ "HVCALL_GET_LOGICAL_DEVICE_PROPERTY failed\n");
+
+ return hv_result_to_errno(status);
+}
+
+static struct iommu_device *hv_iommu_probe_device(struct device *dev)
+{
+ struct hv_output_get_logical_device_property property = {0};
+ int ret;
+
+ if (!dev_is_pci(dev))
+ return ERR_PTR(-ENODEV);
+
+ ret = hv_iommu_get_dev_property(dev, HV_LOGICAL_DEVICE_PROPERTY_PVIOMMU,
+ &property);
+ if (ret || !(property.device_iommu & HV_DEVICE_IOMMU_ENABLED))
+ return ERR_PTR(-ENODEV);
+
+ return &hv_iommu_device->iommu;
+}
+
+static struct iommu_group *hv_iommu_device_group(struct device *dev)
+{
+ if (!dev_is_pci(dev))
+ return ERR_PTR(-ENODEV);
+
+ return pci_device_group(dev);
+}
+
+static int __init hv_initialize_static_domains(void)
+{
+ /*
+ * Clearing translation_enabled bypasses stage-1 translation, so DMA
+ * addresses are used directly as GPAs. Hyper-V requires paging and
+ * blocked domains to keep translation_enabled set.
+ */
+ const struct hv_device_domain_settings identity_settings = {
+ .flags.translation_enabled = 0,
+ };
+ const struct hv_device_domain_settings blocked_settings = {
+ .flags = {
+ .translation_enabled = 1,
+ .blocked = 1,
+ },
+ };
+ int ret;
+
+ /* Default stage-1 identity domain */
+ ret = hv_create_configure_device_domain(&hv_identity_domain,
+ &identity_settings);
+ if (ret)
+ return ret;
+
+ /* Default stage-1 blocked domain */
+ ret = hv_create_configure_device_domain(&hv_blocking_domain,
+ &blocked_settings);
+ if (ret)
+ goto delete_identity_domain;
+
+ return 0;
+
+delete_identity_domain:
+ hv_delete_device_domain(&hv_identity_domain);
+ return ret;
+}
+
+static void hv_iommu_get_resv_regions(struct device *dev,
+ struct list_head *head)
+{
+ iommu_add_resv_x86_msi_region(head);
+}
+
+static void hv_iommu_flush_iotlb_all(struct iommu_domain *domain)
+{
+ hv_flush_device_domain(to_hv_iommu_domain(domain));
+}
+
+/*
+ * Calculate the minimal power-of-two aligned range that covers [start, end]
+ * (end is inclusive). Returns a single (page_number, page_mask_shift)
+ * descriptor that may over-flush when the range is not naturally aligned.
+ */
+static void
+hv_iommu_calc_flush_range(unsigned long start, unsigned long end,
+ union hv_iommu_flush_va *va)
+{
+ unsigned long aligned_start;
+ unsigned int sz_lg2;
+
+ sz_lg2 = max(fls_long(start ^ end), HV_HYP_PAGE_SHIFT);
+
+ /*
+ * A valid IOVA range shall not span bit 63. Use the maximum mask
+ * so the host can safely perform a full flush.
+ */
+ if (WARN_ON_ONCE(sz_lg2 == BITS_PER_LONG))
+ aligned_start = 0;
+ else
+ aligned_start =
+ start & GENMASK(BITS_PER_LONG - 1, sz_lg2);
+
+ va->page_number = aligned_start >> HV_HYP_PAGE_SHIFT;
+ va->page_mask_shift = sz_lg2 - HV_HYP_PAGE_SHIFT;
+}
+
+static void hv_flush_device_domain_list(struct hv_iommu_domain *hv_domain,
+ struct iommu_iotlb_gather *iotlb_gather)
+{
+ u64 status;
+ unsigned long flags;
+ struct hv_input_flush_device_domain_list *input;
+
+ local_irq_save(flags);
+
+ input = *this_cpu_ptr(hyperv_pcpu_input_arg);
+ /* Clear the fixed header and the single range entry. */
+ memset(input, 0, struct_size(input, iova_list, 1));
+
+ input->device_domain = hv_domain->device_domain;
+ input->flags |= HV_FLUSH_DEVICE_DOMAIN_LIST_IOMMU_FORMAT;
+ hv_iommu_calc_flush_range(iotlb_gather->start, iotlb_gather->end,
+ &input->iova_list[0]);
+
+ status = hv_do_rep_hypercall(HVCALL_FLUSH_DEVICE_DOMAIN_LIST,
+ 1, 0, input, NULL);
+
+ if (WARN_ONCE(!hv_result_success(status),
+ "HVCALL_FLUSH_DEVICE_DOMAIN_LIST failed: %#llx (%s)\n",
+ status, hv_result_to_string(status))) {
+ /* Page-selective flush failed, fall back to full flush. */
+ struct hv_input_flush_device_domain *flush_all = (void *)input;
+
+ memset(flush_all, 0, sizeof(*flush_all));
+ flush_all->device_domain = hv_domain->device_domain;
+ status = hv_do_hypercall(HVCALL_FLUSH_DEVICE_DOMAIN,
+ flush_all, NULL);
+ WARN(!hv_result_success(status),
+ "HVCALL_FLUSH_DEVICE_DOMAIN fallback also failed: %#llx (%s)\n",
+ status, hv_result_to_string(status));
+ }
+
+ local_irq_restore(flags);
+}
+
+static void hv_iommu_iotlb_sync(struct iommu_domain *domain,
+ struct iommu_iotlb_gather *iotlb_gather)
+{
+ hv_flush_device_domain_list(to_hv_iommu_domain(domain), iotlb_gather);
+
+ /*
+ * The hypercall also invalidates non-leaf page-walk caches, so
+ * detached page-table pages can now be freed.
+ */
+ iommu_put_pages_list(&iotlb_gather->freelist);
+}
+
+static void hv_iommu_paging_domain_free(struct iommu_domain *domain)
+{
+ struct hv_iommu_domain *hv_domain = to_hv_iommu_domain(domain);
+
+ /* Free all remaining mappings */
+ pt_iommu_deinit(&hv_domain->pt_iommu);
+
+ hv_delete_device_domain(hv_domain);
+
+ kfree(hv_domain);
+}
+
+static const struct iommu_domain_ops hv_iommu_identity_domain_ops = {
+ .attach_dev = hv_iommu_attach_dev,
+};
+
+static const struct iommu_domain_ops hv_iommu_blocking_domain_ops = {
+ .attach_dev = hv_iommu_blocking_attach_dev,
+};
+
+static const struct iommu_domain_ops hv_iommu_paging_domain_ops = {
+ .attach_dev = hv_iommu_attach_dev,
+ IOMMU_PT_DOMAIN_OPS(x86_64),
+ .flush_iotlb_all = hv_iommu_flush_iotlb_all,
+ .iotlb_sync = hv_iommu_iotlb_sync,
+ .free = hv_iommu_paging_domain_free,
+};
+
+static struct iommu_domain *hv_iommu_domain_alloc_paging(struct device *dev)
+{
+ int ret;
+ struct hv_iommu_domain *hv_domain;
+ struct pt_iommu_x86_64_hw_info pt_info;
+ struct pt_iommu_x86_64_cfg cfg = {};
+ struct hv_device_domain_settings settings = {
+ .flags = {
+ .translation_enabled = 1,
+ },
+ };
+
+ hv_domain = kzalloc_obj(*hv_domain, GFP_KERNEL);
+ if (!hv_domain)
+ return ERR_PTR(-ENOMEM);
+
+ hv_domain->pt_iommu.nid = dev_to_node(dev);
+
+ cfg.common.hw_max_vasz_lg2 = hv_iommu_device->max_iova_width;
+ cfg.common.hw_max_oasz_lg2 = 52;
+ cfg.common.features |= BIT(PT_FEAT_FLUSH_RANGE);
+ /*
+ * Hyper-V S1 domains use a 4-level root for IOVA widths up to
+ * 48 bits. A 5-level root is used only for wider apertures when
+ * HV_IOMMU_CAP_S1_5LVL is advertised.
+ */
+ cfg.top_level = (hv_iommu_device->max_iova_width > 48) ? 4 : 3;
+
+ ret = pt_iommu_x86_64_init(&hv_domain->pt_iommu_x86_64, &cfg, GFP_KERNEL);
+ if (ret)
+ goto err_free;
+
+ /* Constrain to page sizes the hypervisor supports */
+ hv_domain->domain.pgsize_bitmap &= hv_iommu_device->pgsize_bitmap;
+
+ hv_domain->domain.ops = &hv_iommu_paging_domain_ops;
+
+ pt_iommu_x86_64_hw_info(&hv_domain->pt_iommu_x86_64, &pt_info);
+ settings.page_table_root = pt_info.gcr3_pt;
+ settings.flags.first_stage_paging_mode = pt_info.levels == 5;
+
+ ret = hv_create_configure_device_domain(hv_domain, &settings);
+ if (ret)
+ goto err_pt_deinit;
+
+ return &hv_domain->domain;
+
+err_pt_deinit:
+ pt_iommu_deinit(&hv_domain->pt_iommu);
+err_free:
+ kfree(hv_domain);
+ return ERR_PTR(ret);
+}
+
+static struct iommu_ops hv_iommu_ops = {
+ .capable = hv_iommu_capable,
+ .domain_alloc_paging = hv_iommu_domain_alloc_paging,
+ .probe_device = hv_iommu_probe_device,
+ .device_group = hv_iommu_device_group,
+ .get_resv_regions = hv_iommu_get_resv_regions,
+ .owner = THIS_MODULE,
+ .identity_domain = &hv_identity_domain.domain,
+ .blocked_domain = &hv_blocking_domain.domain,
+ .release_domain = &hv_blocking_domain.domain,
+};
+
+static u64 hv_iommu_detect(struct hv_output_get_iommu_capabilities *cap)
+{
+ u64 status;
+ unsigned long flags;
+ struct hv_input_get_iommu_capabilities *input;
+ struct hv_output_get_iommu_capabilities *output;
+
+ local_irq_save(flags);
+
+ /*
+ * The combined input and output fit in the per-CPU input page,
+ * so no separate output page is needed.
+ */
+ input = *this_cpu_ptr(hyperv_pcpu_input_arg);
+ output = (struct hv_output_get_iommu_capabilities *)(input + 1);
+ memset(input, 0, sizeof(*input));
+ input->partition_id = HV_PARTITION_ID_SELF;
+ status = hv_do_hypercall(HVCALL_GET_IOMMU_CAPABILITIES, input, output);
+ *cap = *output;
+
+ local_irq_restore(flags);
+
+ return status;
+}
+
+static void __init
+hv_init_iommu_device(struct hv_iommu_dev *hv_iommu,
+ struct hv_output_get_iommu_capabilities *hv_iommu_cap)
+{
+ ida_init(&hv_iommu->domain_ids);
+
+ hv_iommu->cap = hv_iommu_cap->iommu_cap;
+ hv_iommu->max_iova_width = hv_iommu_cap->max_iova_width;
+ if (!hv_iommu_5lvl_supported(hv_iommu->cap) &&
+ hv_iommu->max_iova_width > 48) {
+ pr_info("5-level paging not supported, limiting iova width to 48.\n");
+ hv_iommu->max_iova_width = 48;
+ }
+
+ hv_iommu->first_domain = HV_DEVICE_DOMAIN_ID_DEFAULT + 1;
+ hv_iommu->last_domain = HV_DEVICE_DOMAIN_ID_NULL - 1;
+ hv_iommu->pgsize_bitmap = hv_iommu_cap->pgsize_bitmap;
+ hv_iommu_device = hv_iommu;
+}
+
+int __init hv_iommu_init(void)
+{
+ u64 status;
+ int ret = 0;
+ struct hv_iommu_dev *hv_iommu = NULL;
+ struct hv_output_get_iommu_capabilities hv_iommu_cap = {0};
+
+ if (no_iommu || iommu_detected)
+ return -ENODEV;
+
+ if (!hv_is_hyperv_initialized())
+ return -ENODEV;
+
+ status = hv_iommu_detect(&hv_iommu_cap);
+ if (!hv_result_success(status)) {
+ if (hv_result(status) == HV_STATUS_INVALID_HYPERCALL_CODE)
+ return -ENODEV;
+
+ hv_status_err(status, "HVCALL_GET_IOMMU_CAPABILITIES failed\n");
+ return hv_result_to_errno(status);
+ }
+
+ if (!hv_iommu_present(hv_iommu_cap.iommu_cap))
+ return -ENODEV;
+
+ if (!hv_iommu_s1_domain_supported(hv_iommu_cap.iommu_cap)) {
+ pr_err("stage-1 translation not supported: cap=%#llx\n",
+ hv_iommu_cap.iommu_cap);
+ return -ENODEV;
+ }
+
+ /*
+ * Require the base page size. The domain page-size bitmap is later
+ * restricted to the sizes supported by both iommupt and Hyper-V.
+ */
+ if (!(hv_iommu_cap.pgsize_bitmap & PAGE_SIZE)) {
+ pr_err("unsupported page sizes: pgsize_bitmap=0x%llx\n",
+ hv_iommu_cap.pgsize_bitmap);
+ return -ENODEV;
+ }
+
+ iommu_detected = 1;
+ pci_request_acs();
+
+ hv_iommu = kzalloc_obj(*hv_iommu, GFP_KERNEL);
+ if (!hv_iommu)
+ return -ENOMEM;
+
+ hv_init_iommu_device(hv_iommu, &hv_iommu_cap);
+
+ ret = hv_initialize_static_domains();
+ if (ret)
+ goto err_free;
+
+ ret = iommu_device_sysfs_add(&hv_iommu->iommu, NULL, NULL, "%s", "hv-iommu");
+ if (ret) {
+ pr_err("iommu_device_sysfs_add failed: %d\n", ret);
+ goto err_delete_static_domains;
+ }
+
+ ret = iommu_device_register(&hv_iommu->iommu, &hv_iommu_ops, NULL);
+ if (ret) {
+ pr_err("iommu_device_register failed: %d\n", ret);
+ goto err_sysfs_remove;
+ }
+
+ pr_info("initialized: %u-bit IOVA aperture, page-size bitmap %#llx\n",
+ hv_iommu->max_iova_width, hv_iommu->pgsize_bitmap);
+ return 0;
+
+err_sysfs_remove:
+ iommu_device_sysfs_remove(&hv_iommu->iommu);
+err_delete_static_domains:
+ hv_delete_device_domain(&hv_blocking_domain);
+ hv_delete_device_domain(&hv_identity_domain);
+err_free:
+ kfree(hv_iommu);
+ return ret;
+}
diff --git a/drivers/iommu/hyperv/iommu.h b/drivers/iommu/hyperv/iommu.h
new file mode 100644
index 000000000000..c412dc1fd037
--- /dev/null
+++ b/drivers/iommu/hyperv/iommu.h
@@ -0,0 +1,44 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+
+/*
+ * Hyper-V IOMMU driver.
+ *
+ * Copyright (C) 2024-2026, Microsoft, Inc.
+ *
+ */
+
+#ifndef _HYPERV_IOMMU_H
+#define _HYPERV_IOMMU_H
+
+struct hv_iommu_dev {
+ struct iommu_device iommu;
+ struct ida domain_ids;
+
+ /* Device configuration */
+ u8 max_iova_width;
+ u8 max_pasid_width;
+ u64 cap;
+ u64 pgsize_bitmap;
+
+ u64 first_domain;
+ u64 last_domain;
+};
+
+struct hv_iommu_domain {
+ union {
+ struct iommu_domain domain;
+ struct pt_iommu pt_iommu;
+ struct pt_iommu_x86_64 pt_iommu_x86_64;
+ };
+ struct hv_iommu_dev *hv_iommu;
+ struct hv_input_device_domain device_domain;
+ u64 pgsize_bitmap;
+};
+
+PT_IOMMU_CHECK_DOMAIN(struct hv_iommu_domain, pt_iommu, domain);
+PT_IOMMU_CHECK_DOMAIN(struct hv_iommu_domain, pt_iommu_x86_64.iommu, domain);
+
+#define to_hv_iommu_domain(d) \
+ container_of(d, struct hv_iommu_domain, domain)
+
+#endif /* _HYPERV_IOMMU_H */
diff --git a/drivers/iommu/intel/dmar.c b/drivers/iommu/intel/dmar.c
index ba675b08cd20..6377ba97a9ea 100644
--- a/drivers/iommu/intel/dmar.c
+++ b/drivers/iommu/intel/dmar.c
@@ -1344,6 +1344,49 @@ static void qi_dump_fault(struct intel_iommu *iommu, u32 fault)
(unsigned long long)desc->qw1);
}
+static int qi_drain_remaining_descs(struct intel_iommu *iommu, int head,
+ int wait_index)
+{
+ struct q_inval *qi = iommu->qi;
+ int shift = qi_shift(iommu);
+ struct qi_desc wait_desc = {};
+ struct qi_desc nop_desc = {};
+ cycles_t start;
+
+ nop_desc.qw0 = QI_IWD_FENCE | QI_IWD_TYPE;
+
+ wait_desc.qw0 = QI_IWD_STATUS_DATA(QI_DONE) | QI_IWD_PRQ_DRAIN |
+ QI_IWD_STATUS_WRITE | QI_IWD_FENCE | QI_IWD_TYPE;
+ wait_desc.qw1 = virt_to_phys(&qi->desc_status[wait_index]);
+
+ while (head != wait_index) {
+ memcpy(qi->desc + (head << shift), &nop_desc, 1 << shift);
+ head = (head + 1) % QI_LENGTH;
+ }
+
+ WRITE_ONCE(qi->desc_status[wait_index], QI_IN_USE);
+ memcpy(qi->desc + (wait_index << shift), &wait_desc, 1 << shift);
+
+ /*
+ * Order the descriptor rewrites before the writel() that restarts
+ * the fetch engine.
+ */
+ wmb();
+
+ writel(DMA_FSTS_IQE, iommu->reg + DMAR_FSTS_REG);
+
+ start = get_cycles();
+ while (READ_ONCE(qi->desc_status[wait_index]) != QI_DONE) {
+ if (DMAR_OPERATION_TIMEOUT < (get_cycles() - start)) {
+ pr_err("Timeout draining invalidation queue after IQE\n");
+ return -ETIMEDOUT;
+ }
+ cpu_relax();
+ }
+
+ return 0;
+}
+
static int qi_check_fault(struct intel_iommu *iommu, int index, int wait_index)
{
u32 fault;
@@ -1366,18 +1409,25 @@ static int qi_check_fault(struct intel_iommu *iommu, int index, int wait_index)
* is cleared.
*/
if (fault & DMA_FSTS_IQE) {
+ int head_idx, ret;
+
head = readl(iommu->reg + DMAR_IQH_REG);
- if ((head >> shift) == index) {
- struct qi_desc *desc = qi->desc + head;
-
- /*
- * desc->qw2 and desc->qw3 are either reserved or
- * used by software as private data. We won't print
- * out these two qw's for security consideration.
- */
- memcpy(desc, qi->desc + (wait_index << shift),
- 1 << shift);
- writel(DMA_FSTS_IQE, iommu->reg + DMAR_FSTS_REG);
+ head_idx = (head >> shift) % QI_LENGTH;
+
+ /*
+ * The faulting descriptor can be anywhere within the current
+ * submission's range [index, wait_index]. Since the queue is
+ * circular, this submission may wrap around QI_LENGTH
+ * (index > wait_index in that case), so check both the
+ * non-wrapped and wrapped cases of the range.
+ */
+ if (index <= wait_index ?
+ (head_idx >= index && head_idx <= wait_index) :
+ (head_idx >= index || head_idx <= wait_index)) {
+ ret = qi_drain_remaining_descs(iommu, head_idx, wait_index);
+ if (ret)
+ return ret;
+
pr_info("Invalidation Queue Error (IQE) cleared\n");
return -EINVAL;
}
diff --git a/drivers/iommu/intel/iommu.c b/drivers/iommu/intel/iommu.c
index 2e3b3ab216f8..5a2e705a2f2e 100644
--- a/drivers/iommu/intel/iommu.c
+++ b/drivers/iommu/intel/iommu.c
@@ -39,8 +39,6 @@
#define IS_ISA_DEVICE(pdev) ((pdev->class >> 8) == PCI_CLASS_BRIDGE_ISA)
#define IS_AZALIA(pdev) ((pdev)->vendor == 0x8086 && (pdev)->device == 0x3a3e)
-#define IOAPIC_RANGE_START (0xfee00000)
-#define IOAPIC_RANGE_END (0xfeefffff)
#define IOVA_START_ADDR (0x1000)
#define DEFAULT_DOMAIN_ADDRESS_WIDTH 57
@@ -1452,12 +1450,103 @@ static void intel_iommu_init_qi(struct intel_iommu *iommu)
}
}
+/*
+ * Reserve a domain ID inherited from the previous kernel so that it is not
+ * handed out again while the copied translation structures are still live.
+ *
+ * Returns 0 when the ID is reserved, was already reserved, or cannot be
+ * re-assigned, and a negative errno for a genuine allocation failure.
+ */
+static int reserve_domain_id(struct intel_iommu *iommu, int did)
+{
+ int ret;
+
+ if (did < 0 || did >= iommu->max_domain_id)
+ return 0;
+
+ ret = ida_alloc_range(&iommu->domain_ida, did, did, GFP_KERNEL);
+ /*
+ * Devices sharing a domain share its ID, so the same ID is seen in
+ * more than one context entry; -ENOSPC merely reports that it is
+ * already reserved. On success the allocated ID is returned, which
+ * is not an error either.
+ */
+ if (ret == -ENOSPC || ret >= 0)
+ return 0;
+
+ return ret;
+}
+
+/*
+ * Reserve the domain IDs used by a scalable mode context entry copied from
+ * the previous kernel.
+ */
+static int copy_pasid_table_dids(struct intel_iommu *iommu, struct context_entry *ce)
+{
+ struct pasid_dir_entry *dir;
+ unsigned long dir_size;
+ phys_addr_t dir_phys;
+ int ret = 0;
+ int i, j;
+
+ dir_phys = ce->lo & VTD_PAGE_MASK;
+ if (!dir_phys)
+ return 0;
+
+ dir_size = get_pasid_dir_size(ce);
+ dir = memremap(dir_phys, dir_size * sizeof(*dir), MEMREMAP_WB);
+ if (!dir)
+ return -ENOMEM;
+
+ for (i = 0; i < dir_size; i++) {
+ struct pasid_entry *table;
+ phys_addr_t table_phys;
+
+ if (!pasid_pde_is_present(&dir[i]))
+ continue;
+
+ /*
+ * Do not use get_pasid_table_from_pde(); that returns a
+ * phys_to_virt() pointer, which is not valid for memory
+ * owned by the previous kernel.
+ */
+ table_phys = READ_ONCE(dir[i].val) & PDE_PFN_MASK;
+ if (!table_phys)
+ continue;
+
+ /* A PASID table is one page: PASID_TBL_ENTRIES * 64 bytes. */
+ table = memremap(table_phys, PAGE_SIZE, MEMREMAP_WB);
+ if (!table) {
+ ret = -ENOMEM;
+ goto out;
+ }
+
+ for (j = 0; j < PASID_TBL_ENTRIES; j++) {
+ if (!pasid_pte_is_present(&table[j]))
+ continue;
+
+ ret = reserve_domain_id(iommu, pasid_get_domain_id(&table[j]));
+ if (ret) {
+ memunmap(table);
+ goto out;
+ }
+ }
+
+ memunmap(table);
+ }
+
+out:
+ memunmap(dir);
+
+ return ret;
+}
+
static int copy_context_table(struct intel_iommu *iommu,
struct root_entry *old_re,
struct context_entry **tbl,
int bus, bool ext)
{
- int tbl_idx, tbl_slot = 0, idx, devfn, ret = 0, did;
+ int tbl_idx, tbl_slot = 0, idx, devfn, ret = 0;
struct context_entry *new_ce = NULL, ce;
struct context_entry *old_ce = NULL;
struct root_entry re;
@@ -1519,10 +1608,20 @@ static int copy_context_table(struct intel_iommu *iommu,
if (!context_present(&ce))
continue;
-
- did = context_domain_id(&ce);
- if (did >= 0 && did < iommu->max_domain_id)
- ida_alloc_range(&iommu->domain_ida, did, did, GFP_KERNEL);
+ /*
+ * The context entry only holds a domain ID in legacy mode.
+ * In scalable mode the IDs are in the PASID table entries.
+ */
+ if (ext)
+ ret = copy_pasid_table_dids(iommu, &ce);
+ else
+ ret = reserve_domain_id(iommu, context_domain_id(&ce));
+ if (ret) {
+ /* Not yet published through @tbl, so free it here. */
+ iommu_free_pages(new_ce);
+ new_ce = NULL;
+ goto out_unmap;
+ }
set_context_copied(iommu, bus, devfn);
new_ce[idx] = ce;
@@ -1591,7 +1690,7 @@ static int copy_translation_tables(struct intel_iommu *iommu)
if (ret) {
pr_err("%s: Failed to copy context table for bus %d\n",
iommu->name, bus);
- continue;
+ goto err_free_ctxt_tbls;
}
}
@@ -1623,11 +1722,27 @@ static int copy_translation_tables(struct intel_iommu *iommu)
memunmap(old_rt);
return 0;
+err_free_ctxt_tbls:
+ /*
+ * None of these tables have been linked into iommu->root_entry yet,
+ * so they are unreachable and must be freed here.
+ */
+ for (bus = 0; bus < ctxt_table_entries; bus++)
+ iommu_free_pages(ctxt_tbls[bus]);
+ kfree(ctxt_tbls);
out_unmap:
memunmap(old_rt);
err_free_bitmap:
bitmap_free(iommu->copied_tables);
iommu->copied_tables = NULL;
+
+ /*
+ * Only reservations taken from the old context entries can be in the
+ * ida at this point; no domain has been allocated on this IOMMU yet.
+ * ida_destroy() empties it and leaves it ready for reuse.
+ */
+ ida_destroy(&iommu->domain_ida);
+
return ret;
}
@@ -2782,9 +2897,7 @@ static int blocking_domain_attach_dev(struct iommu_domain *domain,
struct device *dev,
struct iommu_domain *old)
{
- struct device_domain_info *info = dev_iommu_priv_get(dev);
-
- iopf_for_domain_remove(info->domain ? &info->domain->domain : NULL, dev);
+ iopf_for_domain_remove(old, dev);
device_block_translation(dev);
return 0;
}
@@ -2911,10 +3024,10 @@ static unsigned int compute_vasz_lg2_ss(struct intel_iommu *iommu,
*top_level = 4;
return min(57, mgaw);
} else if (mgaw > 39 && sagaw >= BIT(2)) {
- *top_level = 3 + ffs(sagaw >> 3);
+ *top_level = 2 + ffs(sagaw >> 2);
return min(48, mgaw);
} else if (mgaw > 30 && sagaw >= BIT(1)) {
- *top_level = 2 + ffs(sagaw >> 2);
+ *top_level = 1 + ffs(sagaw >> 1);
return min(39, mgaw);
}
return 0;
@@ -3412,7 +3525,6 @@ static void intel_iommu_get_resv_regions(struct device *device,
struct list_head *head)
{
int prot = DMA_PTE_READ | DMA_PTE_WRITE;
- struct iommu_resv_region *reg;
struct dmar_rmrr_unit *rmrr;
struct device *i_dev;
int i;
@@ -3448,6 +3560,7 @@ static void intel_iommu_get_resv_regions(struct device *device,
#ifdef CONFIG_INTEL_IOMMU_FLOPPY_WA
if (dev_is_pci(device)) {
struct pci_dev *pdev = to_pci_dev(device);
+ struct iommu_resv_region *reg;
if ((pdev->class >> 8) == PCI_CLASS_BRIDGE_ISA) {
reg = iommu_alloc_resv_region(0, 1UL << 24, prot,
@@ -3459,12 +3572,7 @@ static void intel_iommu_get_resv_regions(struct device *device,
}
#endif /* CONFIG_INTEL_IOMMU_FLOPPY_WA */
- reg = iommu_alloc_resv_region(IOAPIC_RANGE_START,
- IOAPIC_RANGE_END - IOAPIC_RANGE_START + 1,
- 0, IOMMU_RESV_MSI, GFP_KERNEL);
- if (!reg)
- return;
- list_add_tail(&reg->list, head);
+ iommu_add_resv_x86_msi_region(head);
}
static struct iommu_group *intel_iommu_device_group(struct device *dev)
@@ -3870,6 +3978,14 @@ static int identity_domain_attach_dev(struct iommu_domain *domain,
if (dev_is_real_dma_subdevice(dev))
return 0;
+ if (sm_supported(iommu))
+ ret = intel_pasid_setup_pass_through(iommu, dev, IOMMU_NO_PASID);
+ else
+ ret = device_setup_pass_through(dev);
+
+ if (ret)
+ return ret;
+
/*
* The identity domain has no iopf_handler, so no IOPF reference is
* taken for it. The reference held by the old domain must still be
@@ -3877,16 +3993,9 @@ static int identity_domain_attach_dev(struct iommu_domain *domain,
* not affect the IOPF reference count.
*/
iopf_for_domain_remove(old, dev);
+ info->domain_attached = true;
- if (sm_supported(iommu))
- ret = intel_pasid_setup_pass_through(iommu, dev, IOMMU_NO_PASID);
- else
- ret = device_setup_pass_through(dev);
-
- if (!ret)
- info->domain_attached = true;
-
- return ret;
+ return 0;
}
static int identity_domain_set_dev_pasid(struct iommu_domain *domain,
diff --git a/drivers/iommu/intel/iommu.h b/drivers/iommu/intel/iommu.h
index 23dbe6c24439..5b3d234ae265 100644
--- a/drivers/iommu/intel/iommu.h
+++ b/drivers/iommu/intel/iommu.h
@@ -1105,13 +1105,21 @@ static inline void qi_desc_dev_iotlb_pasid(u16 sid, u16 pfsid, u32 pasid,
unsigned int size_order,
struct qi_desc *desc)
{
- unsigned long mask = 1UL << (VTD_PAGE_SHIFT + size_order - 1);
-
desc->qw0 = QI_DEV_EIOTLB_PASID(pasid) | QI_DEV_EIOTLB_SID(sid) |
QI_DEV_EIOTLB_QDEP(qdep) | QI_DEIOTLB_TYPE |
QI_DEV_IOTLB_PFSID(pfsid);
/*
+ * The widest range the descriptor can express is a full flush, encoded
+ * by making bit 63 the least significant zero bit of ADDR, that is
+ * @size_order == 64 - VTD_PAGE_SHIFT. Bound @size_order there so that
+ * a caller passing something larger cannot produce an out-of-range
+ * shift below.
+ */
+ if (size_order > 64 - VTD_PAGE_SHIFT)
+ size_order = 64 - VTD_PAGE_SHIFT;
+
+ /*
* If S bit is 0, we only flush a single page. If S bit is set,
* The least significant zero bit indicates the invalidation address
* range. VT-d spec 6.5.2.6.
@@ -1120,7 +1128,8 @@ static inline void qi_desc_dev_iotlb_pasid(u16 sid, u16 pfsid, u32 pasid,
* Max Invs Pending (MIP) is set to 0 for now until we have DIT in
* ECAP.
*/
- if (!IS_ALIGNED(addr, VTD_PAGE_SIZE << size_order))
+ if (size_order < 64 - VTD_PAGE_SHIFT &&
+ !IS_ALIGNED(addr, BIT_ULL(VTD_PAGE_SHIFT + size_order)))
pr_warn_ratelimited("Invalidate non-aligned address %llx, order %d\n",
addr, size_order);
@@ -1136,7 +1145,7 @@ static inline void qi_desc_dev_iotlb_pasid(u16 sid, u16 pfsid, u32 pasid,
desc->qw1 |= GENMASK_ULL(size_order + VTD_PAGE_SHIFT - 1,
VTD_PAGE_SHIFT);
/* Clear size_order bit to indicate size */
- desc->qw1 &= ~mask;
+ desc->qw1 &= ~BIT_ULL(VTD_PAGE_SHIFT + size_order - 1);
/* Set the S bit to indicate flushing more than 1 page */
desc->qw1 |= QI_DEV_EIOTLB_SIZE;
}
diff --git a/drivers/iommu/intel/nested.c b/drivers/iommu/intel/nested.c
index 36e0e8e85c06..df72724242bf 100644
--- a/drivers/iommu/intel/nested.c
+++ b/drivers/iommu/intel/nested.c
@@ -59,6 +59,8 @@ static int intel_nested_attach_dev(struct iommu_domain *domain,
if (ret)
goto disable_iopf;
+ iopf_for_domain_remove(old, dev);
+
info->domain = dmar_domain;
info->domain_attached = true;
spin_lock_irqsave(&dmar_domain->lock, flags);
diff --git a/drivers/iommu/iommu-priv.h b/drivers/iommu/iommu-priv.h
index aaffad5854fc..1f9f886b2c10 100644
--- a/drivers/iommu/iommu-priv.h
+++ b/drivers/iommu/iommu-priv.h
@@ -30,6 +30,10 @@ static inline const struct iommu_ops *iommu_fwspec_ops(struct iommu_fwspec *fwsp
void iommu_fwspec_free(struct device *dev);
+int iommu_deferred_attach(struct device *dev, struct iommu_domain *domain);
+
+struct iommu_domain *iommu_get_dma_domain(struct device *dev);
+
int iommu_device_register_bus(struct iommu_device *iommu,
const struct iommu_ops *ops,
const struct bus_type *bus,
diff --git a/drivers/iommu/iommu-sva.c b/drivers/iommu/iommu-sva.c
index 9742cb593577..9827cb804363 100644
--- a/drivers/iommu/iommu-sva.c
+++ b/drivers/iommu/iommu-sva.c
@@ -11,7 +11,6 @@
#include "iommu-priv.h"
static DEFINE_MUTEX(iommu_sva_lock);
-static bool iommu_sva_present;
static LIST_HEAD(iommu_sva_mms);
static struct iommu_domain *iommu_sva_domain_alloc(struct device *dev,
struct mm_struct *mm);
@@ -137,11 +136,9 @@ struct iommu_sva *iommu_sva_bind_device(struct device *dev, struct mm_struct *mm
goto out_free_domain;
domain->users = 1;
- if (list_empty(&iommu_mm->sva_domains)) {
- if (list_empty(&iommu_sva_mms))
- iommu_sva_present = true;
+ if (list_empty(&iommu_mm->sva_domains))
list_add(&iommu_mm->mm_list_elm, &iommu_sva_mms);
- }
+
list_add(&domain->next, &iommu_mm->sva_domains);
out:
refcount_set(&handle->users, 1);
@@ -182,11 +179,8 @@ void iommu_sva_unbind_device(struct iommu_sva *handle)
iommu_detach_device_pasid(domain, dev, iommu_mm->pasid);
if (--domain->users == 0) {
list_del(&domain->next);
- if (list_empty(&iommu_mm->sva_domains)) {
+ if (list_empty(&iommu_mm->sva_domains))
list_del(&iommu_mm->mm_list_elm);
- if (list_empty(&iommu_sva_mms))
- iommu_sva_present = false;
- }
iommu_domain_free(domain);
}
@@ -334,8 +328,6 @@ void iommu_sva_invalidate_kva_range(unsigned long start, unsigned long end)
struct iommu_mm_data *iommu_mm;
guard(mutex)(&iommu_sva_lock);
- if (!iommu_sva_present)
- return;
list_for_each_entry(iommu_mm, &iommu_sva_mms, mm_list_elm)
mmu_notifier_arch_invalidate_secondary_tlbs(iommu_mm->mm, start, end);
diff --git a/drivers/iommu/iommu.c b/drivers/iommu/iommu.c
index cd1bca7ede9a..b486b8bbd1fc 100644
--- a/drivers/iommu/iommu.c
+++ b/drivers/iommu/iommu.c
@@ -4243,6 +4243,10 @@ int iommu_dma_prepare_msi(struct msi_desc *desc, phys_addr_t msi_addr)
if (!group)
return 0;
+ ret = iommu_deferred_attach(dev, iommu_get_domain_for_dev(dev));
+ if (ret)
+ return ret;
+
mutex_lock(&group->mutex);
/* An IDENTITY domain must pass through */
if (group->domain && group->domain->type != IOMMU_DOMAIN_IDENTITY) {
diff --git a/drivers/iommu/riscv/iommu.c b/drivers/iommu/riscv/iommu.c
index fe8e6d0f8a23..fbdca69c436e 100644
--- a/drivers/iommu/riscv/iommu.c
+++ b/drivers/iommu/riscv/iommu.c
@@ -16,6 +16,7 @@
#include <linux/acpi_rimt.h>
#include <linux/compiler.h>
#include <linux/crash_dump.h>
+#include <linux/dma-map-ops.h>
#include <linux/init.h>
#include <linux/iommu.h>
#include <linux/iopoll.h>
@@ -1444,6 +1445,16 @@ static struct iommu_group *riscv_iommu_device_group(struct device *dev)
return generic_device_group(dev);
}
+static bool riscv_iommu_capable(struct device *dev, enum iommu_cap cap)
+{
+ switch (cap) {
+ case IOMMU_CAP_CACHE_COHERENCY:
+ return dev_is_dma_coherent(dev);
+ default:
+ return false;
+ }
+}
+
static int riscv_iommu_of_xlate(struct device *dev, const struct of_phandle_args *args)
{
return iommu_fwspec_add_ids(dev, args->args, 1);
@@ -1505,6 +1516,7 @@ static void riscv_iommu_release_device(struct device *dev)
static const struct iommu_ops riscv_iommu_ops = {
.of_xlate = riscv_iommu_of_xlate,
+ .capable = riscv_iommu_capable,
.identity_domain = &riscv_iommu_identity_domain,
.blocked_domain = &riscv_iommu_blocking_domain,
.release_domain = &riscv_iommu_blocking_domain,
diff --git a/drivers/pci/controller/pci-hyperv.c b/drivers/pci/controller/pci-hyperv.c
index 85d826f8f68d..824cf27e85e6 100644
--- a/drivers/pci/controller/pci-hyperv.c
+++ b/drivers/pci/controller/pci-hyperv.c
@@ -641,10 +641,7 @@ static void hv_irq_retarget_interrupt(struct irq_data *data)
params->int_entry.source = HV_INTERRUPT_SOURCE_MSI;
params->int_entry.msi_entry.address.as_uint32 = int_desc->address & 0xffffffff;
params->int_entry.msi_entry.data.as_uint32 = int_desc->data;
- params->device_id = (hbus->hdev->dev_instance.b[5] << 24) |
- (hbus->hdev->dev_instance.b[4] << 16) |
- (hbus->hdev->dev_instance.b[7] << 8) |
- (hbus->hdev->dev_instance.b[6] & 0xf8) |
+ params->device_id = hv_build_logical_dev_id_prefix(hbus->hdev) |
PCI_FUNC(pdev->devfn);
params->int_target.vector = hv_msi_get_int_vector(data);
@@ -3742,6 +3739,7 @@ static int hv_pci_probe(struct hv_device *hdev,
struct hv_pcibus_device *hbus;
int ret, dom;
u16 dom_req;
+ u32 prefix;
char *name;
bridge = devm_pci_alloc_host_bridge(&hdev->device, 0);
@@ -3884,13 +3882,22 @@ static int hv_pci_probe(struct hv_device *hdev,
hbus->state = hv_pcibus_probed;
- ret = create_root_hv_pci_bus(hbus);
+ /* Register the bus before scanning any devices on it. */
+ prefix = hv_build_logical_dev_id_prefix(hdev);
+
+ ret = hv_pci_register_dev_id(dom, prefix);
if (ret)
goto free_windows;
+ ret = create_root_hv_pci_bus(hbus);
+ if (ret)
+ goto unregister_pci_bus;
+
mutex_unlock(&hbus->state_lock);
return 0;
+unregister_pci_bus:
+ hv_pci_unregister_dev_id(dom);
free_windows:
hv_pci_free_bridge_windows(hbus);
exit_d0:
@@ -4004,6 +4011,8 @@ static void hv_pci_remove(struct hv_device *hdev)
hbus = hv_get_drvdata(hdev);
if (hbus->state == hv_pcibus_installed) {
+ int dom = hbus->bridge->domain_nr;
+
tasklet_disable(&hdev->channel->callback_event);
hbus->state = hv_pcibus_removing;
tasklet_enable(&hdev->channel->callback_event);
@@ -4021,6 +4030,8 @@ static void hv_pci_remove(struct hv_device *hdev)
hv_pci_remove_slots(hbus);
pci_remove_root_bus(hbus->bridge->bus);
pci_unlock_rescan_remove();
+
+ hv_pci_unregister_dev_id(dom);
}
hv_pci_bus_exit(hdev, false);
diff --git a/include/asm-generic/mshyperv.h b/include/asm-generic/mshyperv.h
index bf601d67cecb..34d4c332449c 100644
--- a/include/asm-generic/mshyperv.h
+++ b/include/asm-generic/mshyperv.h
@@ -73,6 +73,10 @@ extern enum hv_partition_type hv_curr_partition_type;
extern void * __percpu *hyperv_pcpu_input_arg;
extern void * __percpu *hyperv_pcpu_output_arg;
+int hv_pci_register_dev_id(int pci_domain_nr, u32 logical_dev_id_prefix);
+void hv_pci_unregister_dev_id(int pci_domain_nr);
+int hv_pci_lookup_dev_id(int pci_domain_nr, u32 *prefix);
+
u64 hv_do_hypercall(u64 control, void *inputaddr, void *outputaddr);
u64 hv_do_fast_hypercall8(u16 control, u64 input8);
u64 hv_do_fast_hypercall16(u16 control, u64 input1, u64 input2);
diff --git a/include/hyperv/hvgdk_mini.h b/include/hyperv/hvgdk_mini.h
index 6a4e8b9d570f..966945f0504f 100644
--- a/include/hyperv/hvgdk_mini.h
+++ b/include/hyperv/hvgdk_mini.h
@@ -37,6 +37,7 @@ struct hv_u128 {
#define HV_STATUS_PROCESSOR_FEATURE_NOT_SUPPORTED 0x20
#define HV_STATUS_INVALID_LP_INDEX 0x41
#define HV_STATUS_INVALID_REGISTER_VALUE 0x50
+#define HV_STATUS_DEVICE_ALREADY_IN_DOMAIN 0x66
#define HV_STATUS_OPERATION_FAILED 0x71
#define HV_STATUS_INSUFFICIENT_ROOT_MEMORY 0x73
#define HV_STATUS_INSUFFICIENT_CONTIGUOUS_MEMORY 0x75
@@ -486,10 +487,17 @@ union hv_vp_assist_msr_contents { /* HV_REGISTER_VP_ASSIST_PAGE */
#define HVCALL_GET_VP_INDEX_FROM_APIC_ID 0x009a
#define HVCALL_FLUSH_GUEST_PHYSICAL_ADDRESS_SPACE 0x00af
#define HVCALL_FLUSH_GUEST_PHYSICAL_ADDRESS_LIST 0x00b0
+#define HVCALL_CREATE_DEVICE_DOMAIN 0x00b1
+#define HVCALL_ATTACH_DEVICE_DOMAIN 0x00b2
#define HVCALL_SIGNAL_EVENT_DIRECT 0x00c0
#define HVCALL_POST_MESSAGE_DIRECT 0x00c1
#define HVCALL_DISPATCH_VP 0x00c2
+#define HVCALL_DETACH_DEVICE_DOMAIN 0x00c4
+#define HVCALL_DELETE_DEVICE_DOMAIN 0x00c5
#define HVCALL_GET_GPA_PAGES_ACCESS_STATES 0x00c9
+#define HVCALL_CONFIGURE_DEVICE_DOMAIN 0x00ce
+#define HVCALL_FLUSH_DEVICE_DOMAIN 0x00d0
+#define HVCALL_FLUSH_DEVICE_DOMAIN_LIST 0x00d1
#define HVCALL_ACQUIRE_SPARSE_SPA_PAGE_HOST_ACCESS 0x00d7
#define HVCALL_RELEASE_SPARSE_SPA_PAGE_HOST_ACCESS 0x00d8
#define HVCALL_MODIFY_SPARSE_GPA_PAGE_HOST_VISIBILITY 0x00db
@@ -502,6 +510,8 @@ union hv_vp_assist_msr_contents { /* HV_REGISTER_VP_ASSIST_PAGE */
#define HVCALL_MMIO_READ 0x0106
#define HVCALL_MMIO_WRITE 0x0107
#define HVCALL_DISABLE_HYP_EX 0x010f
+#define HVCALL_GET_IOMMU_CAPABILITIES 0x0125
+#define HVCALL_GET_LOGICAL_DEVICE_PROPERTY 0x0127
#define HVCALL_MAP_STATS_PAGE2 0x0131
/* HV_HYPERCALL_INPUT */
diff --git a/include/hyperv/hvhdk_mini.h b/include/hyperv/hvhdk_mini.h
index 035ba20870f7..57b70efe60d4 100644
--- a/include/hyperv/hvhdk_mini.h
+++ b/include/hyperv/hvhdk_mini.h
@@ -548,4 +548,159 @@ union hv_device_id { /* HV_DEVICE_ID */
} acpi;
} __packed;
+/* Device domain types */
+#define HV_DEVICE_DOMAIN_TYPE_S1 1 /* HV_DEVICE_DOMAIN_ID_TYPE_S1 */
+
+/* ID for default domain and NULL domain */
+#define HV_DEVICE_DOMAIN_ID_DEFAULT 0
+#define HV_DEVICE_DOMAIN_ID_NULL 0xFFFFFFFFULL
+
+union hv_device_domain_id { /* HV_DEVICE_DOMAIN_ID */
+ u64 as_uint64;
+ struct {
+ u32 type: 4;
+ u32 reserved: 28;
+ u32 id;
+ } __packed;
+};
+
+struct hv_input_device_domain { /* HV_INPUT_DEVICE_DOMAIN */
+ u64 partition_id;
+ union hv_input_vtl owner_vtl;
+ u8 padding[7];
+ union hv_device_domain_id domain_id;
+} __packed;
+
+/* HV_CREATE_DEVICE_DOMAIN_FLAGS */
+union hv_create_device_domain_flags {
+ u32 as_uint32;
+ struct {
+ u32 forward_progress_required: 1;
+ u32 inherit_owning_vtl: 1;
+ u32 reserved: 30;
+ } __packed;
+};
+
+/* HV_INPUT_CREATE_DEVICE_DOMAIN */
+struct hv_input_create_device_domain {
+ struct hv_input_device_domain device_domain;
+ union hv_create_device_domain_flags create_device_domain_flags;
+ u32 padding;
+} __packed;
+
+/* HV_INPUT_DELETE_DEVICE_DOMAIN */
+struct hv_input_delete_device_domain {
+ struct hv_input_device_domain device_domain;
+} __packed;
+
+/* HV_INPUT_ATTACH_DEVICE_DOMAIN */
+struct hv_input_attach_device_domain {
+ struct hv_input_device_domain device_domain;
+ union hv_device_id device_id;
+} __packed;
+
+/* HV_INPUT_DETACH_DEVICE_DOMAIN */
+struct hv_input_detach_device_domain {
+ u64 partition_id;
+ union hv_device_id device_id;
+} __packed;
+
+struct hv_device_domain_settings { /* HV_DEVICE_DOMAIN_SETTINGS */
+ struct {
+ /*
+ * Enable translations. If not enabled, all transaction bypass
+ * S1 translations.
+ */
+ u64 translation_enabled: 1;
+ u64 blocked: 1;
+ /*
+ * First stage address translation paging mode:
+ * 0: 4-level paging (default)
+ * 1: 5-level paging
+ */
+ u64 first_stage_paging_mode: 1;
+ u64 reserved: 61;
+ } flags;
+
+ /* Address of translation table */
+ u64 page_table_root;
+} __packed;
+
+/* HV_INPUT_CONFIGURE_DEVICE_DOMAIN */
+struct hv_input_configure_device_domain {
+ struct hv_input_device_domain device_domain;
+ struct hv_device_domain_settings settings;
+} __packed;
+
+/* HV_INPUT_GET_IOMMU_CAPABILITIES */
+struct hv_input_get_iommu_capabilities {
+ u64 partition_id;
+ u64 reserved;
+} __packed;
+
+/* HV_OUTPUT_GET_IOMMU_CAPABILITIES */
+struct hv_output_get_iommu_capabilities {
+ u32 size;
+ u16 reserved;
+ u8 max_iova_width;
+ u8 max_pasid_width;
+
+#define HV_IOMMU_CAP_PRESENT BIT_ULL(0)
+#define HV_IOMMU_CAP_S2 BIT_ULL(1)
+#define HV_IOMMU_CAP_S1 BIT_ULL(2)
+#define HV_IOMMU_CAP_S1_5LVL BIT_ULL(3)
+#define HV_IOMMU_CAP_PASID BIT_ULL(4)
+#define HV_IOMMU_CAP_ATS BIT_ULL(5)
+#define HV_IOMMU_CAP_PRI BIT_ULL(6)
+
+ u64 iommu_cap;
+ u64 pgsize_bitmap;
+} __packed;
+
+/* HV_LOGICAL_DEVICE_PROPERTY_CODE */
+enum hv_logical_device_property_code {
+ HV_LOGICAL_DEVICE_PROPERTY_PVIOMMU = 10,
+};
+
+/* HV_INPUT_GET_LOGICAL_DEVICE_PROPERTY */
+struct hv_input_get_logical_device_property {
+ u64 partition_id;
+ u64 logical_device_id;
+ /* Takes values from enum hv_logical_device_property_code. */
+ u32 code;
+ u32 reserved;
+} __packed;
+
+/* HV_OUTPUT_GET_LOGICAL_DEVICE_PROPERTY */
+struct hv_output_get_logical_device_property {
+#define HV_DEVICE_IOMMU_ENABLED BIT_ULL(0)
+ u64 device_iommu;
+ u64 reserved;
+} __packed;
+
+/* HV_INPUT_FLUSH_DEVICE_DOMAIN */
+struct hv_input_flush_device_domain {
+ struct hv_input_device_domain device_domain;
+ u32 flags;
+ u32 reserved;
+} __packed;
+
+union hv_iommu_flush_va { /* HV_IOMMU_GVA_RANGE */
+ u64 as_uint64;
+ struct {
+ u64 page_mask_shift : 6;
+ u64 reserved : 6;
+ u64 page_number : 52;
+ };
+} __packed;
+
+/* HV_INPUT_FLUSH_DEVICE_DOMAIN_LIST */
+struct hv_input_flush_device_domain_list {
+ struct hv_input_device_domain device_domain;
+#define HV_FLUSH_DEVICE_DOMAIN_LIST_IOMMU_FORMAT BIT(0)
+ u32 flags;
+ u32 reserved;
+ union hv_iommu_flush_va iova_list[];
+} __packed;
+
#endif /* _HV_HVHDK_MINI_H */
diff --git a/include/linux/amd-iommu.h b/include/linux/amd-iommu.h
index edcee9f5335a..104817e8d897 100644
--- a/include/linux/amd-iommu.h
+++ b/include/linux/amd-iommu.h
@@ -11,11 +11,11 @@
#include <linux/types.h>
struct amd_iommu;
+struct pci_dev;
#ifdef CONFIG_AMD_IOMMU
struct task_struct;
-struct pci_dev;
extern void amd_iommu_detect(void);
@@ -76,4 +76,15 @@ static inline int amd_iommu_snp_disable(void) { return 0; }
static inline bool amd_iommu_sev_tio_supported(void) { return false; }
#endif
+#ifdef CONFIG_AMD_IOMMU
+int amd_iommu_enable_perfopt(struct pci_dev *pdev);
+void amd_iommu_disable_perfopt(struct pci_dev *pdev);
+#else
+static inline int amd_iommu_enable_perfopt(struct pci_dev *pdev)
+{
+ return 0;
+}
+static inline void amd_iommu_disable_perfopt(struct pci_dev *pdev) { }
+#endif
+
#endif /* _ASM_X86_AMD_IOMMU_H */
diff --git a/include/linux/generic_pt/common.h b/include/linux/generic_pt/common.h
index 07ef1c8341a4..1b39b27f0cfa 100644
--- a/include/linux/generic_pt/common.h
+++ b/include/linux/generic_pt/common.h
@@ -213,4 +213,10 @@ enum {
PT_FEAT_X86_64_AMD_ENCRYPT_TABLES = PT_FEAT_FMT_START,
};
+struct pt_bcm2712 {
+ struct pt_common common;
+ u8 bigpage_lg2;
+ u8 superpage_lg2;
+};
+
#endif
diff --git a/include/linux/generic_pt/iommu.h b/include/linux/generic_pt/iommu.h
index dd0edd02a48a..13ebb72a67de 100644
--- a/include/linux/generic_pt/iommu.h
+++ b/include/linux/generic_pt/iommu.h
@@ -346,6 +346,18 @@ struct pt_iommu_x86_64_hw_info {
IOMMU_FORMAT(x86_64, x86_64_pt);
+struct pt_iommu_bcm2712_cfg {
+ struct pt_iommu_cfg common;
+ 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
diff --git a/include/linux/hyperv.h b/include/linux/hyperv.h
index 9e109d91aa14..e61f9a4cb7c3 100644
--- a/include/linux/hyperv.h
+++ b/include/linux/hyperv.h
@@ -1299,6 +1299,14 @@ struct hv_device {
#define device_to_hv_device(d) container_of_const(d, struct hv_device, device)
#define drv_to_hv_drv(d) container_of_const(d, struct hv_driver, driver)
+static inline u32 hv_build_logical_dev_id_prefix(struct hv_device *hdev)
+{
+ return ((u32)hdev->dev_instance.b[5] << 24) |
+ ((u32)hdev->dev_instance.b[4] << 16) |
+ ((u32)hdev->dev_instance.b[7] << 8) |
+ (hdev->dev_instance.b[6] & 0xf8u);
+}
+
static inline void hv_set_drvdata(struct hv_device *dev, void *data)
{
dev_set_drvdata(&dev->device, data);
diff --git a/include/linux/iommu.h b/include/linux/iommu.h
index 05f7cee1375e..ef5043752a36 100644
--- a/include/linux/iommu.h
+++ b/include/linux/iommu.h
@@ -900,7 +900,6 @@ int iommu_device_sysfs_add(struct iommu_device *iommu,
void iommu_device_sysfs_remove(struct iommu_device *iommu);
int iommu_device_link(struct iommu_device *iommu, struct device *link);
void iommu_device_unlink(struct iommu_device *iommu, struct device *link);
-int iommu_deferred_attach(struct device *dev, struct iommu_domain *domain);
static inline struct iommu_device *dev_to_iommu_device(struct device *dev)
{
@@ -945,7 +944,6 @@ extern void iommu_detach_device(struct iommu_domain *domain,
struct device *dev);
extern struct iommu_domain *iommu_get_domain_for_dev(struct device *dev);
struct iommu_domain *iommu_driver_get_domain_for_dev(struct device *dev);
-extern struct iommu_domain *iommu_get_dma_domain(struct device *dev);
extern int iommu_map(struct iommu_domain *domain, unsigned long iova,
phys_addr_t paddr, size_t size, int prot, gfp_t gfp);
int iommu_map_nosync(struct iommu_domain *domain, unsigned long iova,