[PATCH v9 03/12] iommu/arm-smmu-v3: Add strtab parse helpers to a new arm-smmu-v3-kexec.c
From: Nicolin Chen <hidden>
Date: 2026-07-21 19:49:25
Also in:
linux-iommu, lkml
Subsystem:
arm smmu drivers, iommu subsystem, the rest · Maintainers:
Will Deacon, Joerg Roedel, Linus Torvalds
When booting after a kexec, both the kdump adoption and also the in-flight SMMUv3 live-update restoration built on the Kexec Handover (KHO) framework need to parse the previous kernel's STRTAB_BASE/STRTAB_BASE_CFG registers, in order to retain the stream table translating any in-flight DMAs. Add a new arm-smmu-v3-kexec.c holding the common read-only helpers: - arm_smmu_kexec_parse_strtab_2lvl() - arm_smmu_kexec_parse_strtab_linear() - arm_smmu_kexec_check_strtab_l1_desc() These helpers only validate the previous kernel's values against this very kernel's own limits, taking no ownership of the tables: a kdump kernel will adopt the tables via memremaps, while a live-update kernel will claim them using the KHO restore API. Build this new file under a hidden ARM_SMMU_V3_KEXEC Kconfig symbol, which is, for now, merely a CRASH_DUMP. Then, the coming live-update series would need to append its own user to this symbol, e.g. "|| IOMMU_LIVEUPDATE". Suggested-by: Pranjal Shrivastava <praan@google.com> Assisted-by: Claude:claude-fable-5 Signed-off-by: Nicolin Chen <redacted> --- drivers/iommu/arm/Kconfig | 4 + drivers/iommu/arm/arm-smmu-v3/Makefile | 1 + drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3.h | 11 ++ .../iommu/arm/arm-smmu-v3/arm-smmu-v3-kexec.c | 159 ++++++++++++++++++ 4 files changed, 175 insertions(+) create mode 100644 drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3-kexec.c
diff --git a/drivers/iommu/arm/Kconfig b/drivers/iommu/arm/Kconfig
index 5fac08b89deea..04b05d49abf76 100644
--- a/drivers/iommu/arm/Kconfig
+++ b/drivers/iommu/arm/Kconfig@@ -109,6 +109,10 @@ config ARM_SMMU_V3_IOMMUFD Say Y here if you are doing development and testing on this feature. +# Common helpers for a kexec'd kernel, e.g. kdump adoption and live update +config ARM_SMMU_V3_KEXEC + def_bool CRASH_DUMP + config ARM_SMMU_V3_KUNIT_TEST tristate "KUnit tests for arm-smmu-v3 driver" if !KUNIT_ALL_TESTS depends on KUNIT
diff --git a/drivers/iommu/arm/arm-smmu-v3/Makefile b/drivers/iommu/arm/arm-smmu-v3/Makefile
index 493a659cc66bb..89a27de8dd2f0 100644
--- a/drivers/iommu/arm/arm-smmu-v3/Makefile
+++ b/drivers/iommu/arm/arm-smmu-v3/Makefile@@ -3,6 +3,7 @@ obj-$(CONFIG_ARM_SMMU_V3) += arm_smmu_v3.o arm_smmu_v3-y := arm-smmu-v3.o arm_smmu_v3-$(CONFIG_ARM_SMMU_V3_IOMMUFD) += arm-smmu-v3-iommufd.o arm_smmu_v3-$(CONFIG_ARM_SMMU_V3_SVA) += arm-smmu-v3-sva.o +arm_smmu_v3-$(CONFIG_ARM_SMMU_V3_KEXEC) += arm-smmu-v3-kexec.o arm_smmu_v3-$(CONFIG_TEGRA241_CMDQV) += tegra241-cmdqv.o obj-$(CONFIG_ARM_SMMU_V3_KUNIT_TEST) += arm-smmu-v3-test.o
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 c909c9a88538b..d37492ff08fb3 100644
--- a/drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3.h
+++ b/drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3.h@@ -1239,6 +1239,17 @@ tegra241_cmdqv_probe(struct arm_smmu_device *smmu) } #endif /* CONFIG_TEGRA241_CMDQV */ +#ifdef CONFIG_ARM_SMMU_V3_KEXEC +int arm_smmu_kexec_parse_strtab_2lvl(struct arm_smmu_device *smmu, u32 cfg_reg, + phys_addr_t base, u32 *num_l1_ents); +int arm_smmu_kexec_parse_strtab_linear(struct arm_smmu_device *smmu, + u32 cfg_reg, phys_addr_t base, + u32 *num_ents); +int arm_smmu_kexec_check_strtab_l1_desc(struct arm_smmu_device *smmu, + u64 l1_desc, u32 idx, + phys_addr_t *l2_base); +#endif /* CONFIG_ARM_SMMU_V3_KEXEC */ + struct arm_vsmmu { struct iommufd_viommu core; struct arm_smmu_device *smmu;
diff --git a/drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3-kexec.c b/drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3-kexec.c
new file mode 100644
index 0000000000000..5346132529427
--- /dev/null
+++ b/drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3-kexec.c@@ -0,0 +1,159 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Common helpers for a kexec'd kernel to parse, validate, and walk through the + * previous kernel's SMMU table structures, shared by the kdump adoption and a + * future live-update restoration. + * + * All of the helpers are read-only against the previous kernel's structures: a + * table that is not yet mapped by this kernel gets a transient memremap during + * a walk, followed by an immediate memunmap. They never allocate memory or take + * ownership of the previous kernel's tables; the callers make those decisions. + */ + +#include <linux/io.h> + +#include "arm-smmu-v3.h" + +/** + * arm_smmu_kexec_parse_strtab_2lvl() - Validate a 2-level stream table + * @smmu: SMMU device of this kernel + * @cfg_reg: STRTAB_BASE_CFG register value set by the previous kernel + * @base: stream table base address extracted from the STRTAB_BASE register + * @num_l1_ents: pointer to return the number of L1 entries + * + * Validate the 2-level stream table geometry in @cfg_reg and @base's alignment + * against this kernel's hardware limits. + * + * Return: 0 on success with @num_l1_ents set, or -EINVAL on a bad geometry + */ +int arm_smmu_kexec_parse_strtab_2lvl(struct arm_smmu_device *smmu, u32 cfg_reg, + phys_addr_t base, u32 *num_l1_ents) +{ + u32 log2size = FIELD_GET(STRTAB_BASE_CFG_LOG2SIZE, cfg_reg); + u32 split = FIELD_GET(STRTAB_BASE_CFG_SPLIT, cfg_reg); + u32 num_ents; + size_t size; + + if (log2size < split || log2size > smmu->sid_bits) { + dev_err(smmu->dev, "log2size %u out of range [%u, %u]\n", + log2size, split, smmu->sid_bits); + return -EINVAL; + } + if (split != STRTAB_SPLIT) { + dev_err(smmu->dev, + "unsupported STRTAB_SPLIT %u (expected %u)\n", split, + STRTAB_SPLIT); + return -EINVAL; + } + + num_ents = 1U << (log2size - split); + if (num_ents > STRTAB_MAX_L1_ENTRIES) { + dev_err(smmu->dev, "l1 entries %u exceeds max %u\n", num_ents, + STRTAB_MAX_L1_ENTRIES); + return -EINVAL; + } + + size = num_ents * sizeof(struct arm_smmu_strtab_l1); + /* + * HW aligns the base down to the L1 table size (min 64 bytes), so any + * unaligned base would make this kernel read a different table. + */ + if (!IS_ALIGNED(base, size)) { + dev_err(smmu->dev, "unaligned l1 stream table base %pa\n", + &base); + return -EINVAL; + } + + *num_l1_ents = num_ents; + return 0; +} + +/** + * arm_smmu_kexec_parse_strtab_linear() - Validate a linear stream table + * @smmu: SMMU device of this kernel + * @cfg_reg: STRTAB_BASE_CFG register value set by the previous kernel + * @base: stream table base address extracted from the STRTAB_BASE register + * @num_ents: pointer to return the number of STEs + * + * Validate the linear stream table geometry in @cfg_reg and @base's alignment + * against this kernel's own limits. + * + * Return: 0 on success with @num_ents set, or -EINVAL on a bad geometry + */ +int arm_smmu_kexec_parse_strtab_linear(struct arm_smmu_device *smmu, + u32 cfg_reg, phys_addr_t base, + u32 *num_ents) +{ + u32 log2size = FIELD_GET(STRTAB_BASE_CFG_LOG2SIZE, cfg_reg); + unsigned int max_log2size = smmu->sid_bits; + size_t size; + + /* Cap the size at what this kernel itself would have allocated */ + if (smmu->features & ARM_SMMU_FEAT_2_LVL_STRTAB) + max_log2size = min_t( + unsigned int, max_log2size, + ilog2(STRTAB_MAX_L1_ENTRIES * STRTAB_NUM_L2_STES)); + + /* num_ents is limited to a u32, so cap log2size at 31 */ + max_log2size = min(max_log2size, 31U); + if (log2size > max_log2size) { + dev_err(smmu->dev, "unsupported log2size %u (> %u)\n", log2size, + max_log2size); + return -EINVAL; + } + + size = (1U << log2size) * sizeof(struct arm_smmu_ste); + /* + * HW aligns the base down to the table size, ignoring the low bits, so + * an unaligned base would make this kernel read a different table. + */ + if (!IS_ALIGNED(base, size)) { + dev_err(smmu->dev, "unaligned stream table base %pa\n", &base); + return -EINVAL; + } + + *num_ents = 1U << log2size; + return 0; +} + +/** + * arm_smmu_kexec_check_strtab_l1_desc() - Check one stream table L1 descriptor + * @smmu: SMMU device of this kernel + * @l1_desc: L1 descriptor value from the previous kernel's stream table + * @idx: index of the L1 descriptor, for diagnostics + * @l2_base: pointer to return the L2 table's physical address + * + * Return: 1 if the descriptor is unused, 0 if it is valid with @l2_base set, or + * -EINVAL if it is malformed + */ +int arm_smmu_kexec_check_strtab_l1_desc(struct arm_smmu_device *smmu, + u64 l1_desc, u32 idx, + phys_addr_t *l2_base) +{ + phys_addr_t base = l1_desc & STRTAB_L1_DESC_L2PTR_MASK; + u32 span = FIELD_GET(STRTAB_L1_DESC_SPAN, l1_desc); + + /* L1STD.L2Ptr is invalid */ + if (!span) + return 1; + + if (span != STRTAB_SPLIT + 1) { + dev_err(smmu->dev, "L1[%u] unsupported span %u (vs %u)\n", idx, + span, STRTAB_SPLIT + 1); + return -EINVAL; + } + + /* + * A valid descriptor never carries a null pointer. Also, HW aligns the + * pointer down to the L2 table size, so an unaligned pointer would make + * this kernel read a different table. + */ + if (!base || !IS_ALIGNED(base, sizeof(struct arm_smmu_strtab_l2))) { + dev_err(smmu->dev, "L1[%u] bad l2 table base %pa\n", idx, + &base); + return -EINVAL; + } + + *l2_base = base; + return 0; +}
--
2.43.0