* [RFC PATCH v1 1/9] iommu/kho: Extend IOMMU KHO ABI for ARM SMMUv3
2026-09-29 7:19 [RFC PATCH v1 0/9] iommu/arm-smmu-v3: Implement Live Update support Pranjal Shrivastava
@ 2026-09-29 7:19 ` Pranjal Shrivastava
2026-09-29 7:19 ` [RFC PATCH v1 2/9] iommu/arm-smmu-v3: Implement KHO preservation for STEs Pranjal Shrivastava
` (7 subsequent siblings)
8 siblings, 0 replies; 10+ messages in thread
From: Pranjal Shrivastava @ 2026-09-29 7:19 UTC (permalink / raw)
To: iommu, Will Deacon, Jason Gunthorpe
Cc: Robin Murphy, Joerg Roedel, Nicolin Chen, Kevin Tian,
Samiullah Khawaja, David Matlack, Vipin Sharma, Mostafa Saleh,
Daniel Mentz, Pasha Tatashin, Pratyush Yadav, linux-arm-kernel,
kexec, linux-kernel, Pranjal Shrivastava
Extend the KHO IOMMU ABI to support state preservation for Arm SMMUv3.
Introduce struct iommu_smmuv3_hw_ser to capture SMMU hardware state,
including the register base, Stream Table config and Live Update state
tokens for the preserved Stream Tables.
Additionally, introduce struct iommu_master_smmuv3_ser to capture per
master state. For now it is used to preserve CD tables for Stage-1
translation.
Signed-off-by: Pranjal Shrivastava <praan@google.com>
---
include/linux/kho/abi/iommu.h | 36 +++++++++++++++++++++++++++++++++++
1 file changed, 36 insertions(+)
diff --git a/include/linux/kho/abi/iommu.h b/include/linux/kho/abi/iommu.h
index 308cd83fd3e3..397fdb0449a6 100644
--- a/include/linux/kho/abi/iommu.h
+++ b/include/linux/kho/abi/iommu.h
@@ -82,6 +82,7 @@
enum iommu_type_ser {
IOMMU_INVALID,
IOMMU_INTEL,
+ IOMMU_ARM_SMMUV3,
};
#define IOMMU_SER_FLAG_DELETED (1 << 0)
@@ -142,6 +143,23 @@ struct iommu_device_intel_ser {
u64 max_pasid;
} __packed;
+/**
+ * struct iommu_master_smmuv3_ser - SMMUv3 specific device state
+ * @l1_cdtab_lu_state: Live update state token for the linear/L1 CD table
+ * @num_l2_cdtables: Number of active L2 CD tables preserved
+ * @l2_cdtab_lu_states_phys: Physical pointer to an array of u64 LU state tokens
+ * for the active L2 CD tables (0 if linear)
+ *
+ * Note: Currently unused for Stage-2 Live Update. Reserved for future
+ * preservation of Stage-1 per-device Context Descriptor (CD) tables.
+ */
+struct iommu_master_smmuv3_ser {
+ u64 l1_cdtab_lu_state;
+ u32 num_l2_cdtables;
+ u32 padding;
+ u64 l2_cdtab_lu_states_phys;
+} __packed;
+
/**
* struct iommu_device_ser - Serialized state of a device
* @hdr: Common object header
@@ -159,6 +177,7 @@ struct iommu_device_ser {
struct iommu_dev_map_ser domain_iommu_ser;
union {
struct iommu_device_intel_ser intel;
+ struct iommu_master_smmuv3_ser smmuv3;
};
} __packed;
@@ -181,6 +200,22 @@ struct iommu_intel_ser {
u64 context_tables_bitmap[VTD_PRESERVED_BITMAP_LONGS];
};
+/**
+ * struct iommu_smmuv3_hw_ser - SMMUv3 specific hardware state
+ * @phys_addr: Physical address of the SMMU register base
+ * @strtab_base_cfg: STRTAB_BASE_CFG register value
+ * @l1_strtab_lu_state: Live update state token for the linear table OR the L1 table
+ * @l2_strtab_lu_states_phys: Physical pointer to an array of u64 LU state tokens
+ * indexed by L1 index, 0 for L2 tables that aren't
+ * preserved (0 if linear)
+ */
+struct iommu_smmuv3_hw_ser {
+ u64 phys_addr;
+ u64 strtab_base_cfg;
+ u64 l1_strtab_lu_state;
+ u64 l2_strtab_lu_states_phys;
+} __packed;
+
/**
* struct iommu_hw_ser - Serialized state of an IOMMU instance
* @hdr: Common object header
@@ -194,6 +229,7 @@ struct iommu_hw_ser {
u64 type;
union {
struct iommu_intel_ser intel;
+ struct iommu_smmuv3_hw_ser smmuv3;
};
} __packed;
--
2.56.0.rc1.315.gc6ed9934b7-goog
^ permalink raw reply [flat|nested] 10+ messages in thread* [RFC PATCH v1 2/9] iommu/arm-smmu-v3: Implement KHO preservation for STEs
2026-09-29 7:19 [RFC PATCH v1 0/9] iommu/arm-smmu-v3: Implement Live Update support Pranjal Shrivastava
2026-09-29 7:19 ` [RFC PATCH v1 1/9] iommu/kho: Extend IOMMU KHO ABI for ARM SMMUv3 Pranjal Shrivastava
@ 2026-09-29 7:19 ` Pranjal Shrivastava
2026-09-29 7:19 ` [RFC PATCH v1 3/9] iommu/arm-smmu-v3: Implement CD Table preservation Pranjal Shrivastava
` (6 subsequent siblings)
8 siblings, 0 replies; 10+ messages in thread
From: Pranjal Shrivastava @ 2026-09-29 7:19 UTC (permalink / raw)
To: iommu, Will Deacon, Jason Gunthorpe
Cc: Robin Murphy, Joerg Roedel, Nicolin Chen, Kevin Tian,
Samiullah Khawaja, David Matlack, Vipin Sharma, Mostafa Saleh,
Daniel Mentz, Pasha Tatashin, Pratyush Yadav, linux-arm-kernel,
kexec, linux-kernel, Pranjal Shrivastava
Introduce the foundation for SMMUv3 Live Update (LU) using the Kexec
Handover (KHO) framework. Implement the preserve and preserve_device
hooks to serialize SMMU & preserved masters' state for KHO, along with
the respective unpreserve hooks.
Since .preserve only runs for the first preserved device, preserve the
L2 Stream Tables in .preserve_device, recording their tokens by L1 index.
Signed-off-by: Pranjal Shrivastava <praan@google.com>
---
drivers/iommu/arm/arm-smmu-v3/Makefile | 1 +
.../arm/arm-smmu-v3/arm-smmu-v3-liveupdate.c | 277 ++++++++++++++++++
drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3.c | 7 +
drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3.h | 14 +
4 files changed, 299 insertions(+)
create mode 100644 drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3-liveupdate.c
diff --git a/drivers/iommu/arm/arm-smmu-v3/Makefile b/drivers/iommu/arm/arm-smmu-v3/Makefile
index 2bc52473d960..57db7621972c 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_IOMMU_LIVEUPDATE) += arm-smmu-v3-liveupdate.o
arm_smmu_v3-$(CONFIG_ARM_SMMU_V3_KEXEC) += arm-smmu-v3-kexec.o
arm_smmu_v3-$(CONFIG_CRASH_DUMP) += arm-smmu-v3-kdump.o
arm_smmu_v3-$(CONFIG_TEGRA241_CMDQV) += tegra241-cmdqv.o
diff --git a/drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3-liveupdate.c b/drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3-liveupdate.c
new file mode 100644
index 000000000000..515a266ac22e
--- /dev/null
+++ b/drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3-liveupdate.c
@@ -0,0 +1,277 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Copyright (C) 2026 Google LLC
+ * Author: Pranjal Shrivastava <praan@google.com>
+ */
+
+#include <linux/dma-mapping.h>
+#include <linux/iommu.h>
+#include <linux/iommu-liveupdate.h>
+#include <linux/kexec_handover.h>
+#include "arm-smmu-v3.h"
+
+#ifdef CONFIG_IOMMU_LIVEUPDATE
+static u64 *arm_smmu_l2_strtab_states(struct arm_smmu_device *smmu)
+{
+ struct iommu_hw_ser *iommu_ser = iommu_preserved_state(&smmu->iommu);
+
+ return phys_to_virt(iommu_ser->smmuv3.l2_strtab_lu_states_phys);
+}
+
+static bool arm_smmu_l2_strtab_in_use(struct arm_smmu_device *smmu, u32 idx)
+{
+ struct rb_node *node;
+
+ lockdep_assert_held(&smmu->streams_mutex);
+
+ for (node = rb_first(&smmu->streams); node; node = rb_next(node)) {
+ struct arm_smmu_stream *stream =
+ rb_entry(node, struct arm_smmu_stream, node);
+
+ if (stream->master->preserved &&
+ arm_smmu_strtab_l1_idx(stream->id) == idx)
+ return true;
+ }
+ return false;
+}
+
+/* Unpreserve the L2 tables of the first @num_streams, unless still in use */
+static void arm_smmu_unpreserve_l2_strtabs(struct arm_smmu_master *master,
+ unsigned int num_streams)
+{
+ struct arm_smmu_device *smmu = master->smmu;
+ struct arm_smmu_strtab_cfg *cfg = &smmu->strtab_cfg;
+ u64 *l2_states;
+ unsigned int i;
+
+ if (!(smmu->features & ARM_SMMU_FEAT_2_LVL_STRTAB))
+ return;
+
+ l2_states = arm_smmu_l2_strtab_states(smmu);
+
+ mutex_lock(&smmu->streams_mutex);
+ for (i = 0; i < num_streams; i++) {
+ u32 idx = arm_smmu_strtab_l1_idx(master->streams[i].id);
+ dma_addr_t l2_dma;
+
+ if (!l2_states[idx] || arm_smmu_l2_strtab_in_use(smmu, idx))
+ continue;
+
+ l2_dma = le64_to_cpu(cfg->l2.l1tab[idx].l2ptr) & STRTAB_L1_DESC_L2PTR_MASK;
+ dmam_unpreserve_coherent_allocation(smmu->dev, cfg->l2.l2ptrs[idx],
+ sizeof(struct arm_smmu_strtab_l2),
+ l2_dma, l2_states[idx]);
+ l2_states[idx] = 0;
+ }
+ mutex_unlock(&smmu->streams_mutex);
+}
+
+/* Preserve the L2 tables holding the STEs of @master */
+static int arm_smmu_preserve_l2_strtabs(struct arm_smmu_master *master)
+{
+ struct arm_smmu_device *smmu = master->smmu;
+ struct arm_smmu_strtab_cfg *cfg = &smmu->strtab_cfg;
+ u64 *l2_states;
+ unsigned int i;
+ int ret;
+
+ if (!(smmu->features & ARM_SMMU_FEAT_2_LVL_STRTAB))
+ return 0;
+
+ l2_states = arm_smmu_l2_strtab_states(smmu);
+
+ for (i = 0; i < master->num_streams; i++) {
+ u32 idx = arm_smmu_strtab_l1_idx(master->streams[i].id);
+ dma_addr_t l2_dma;
+
+ if (l2_states[idx])
+ continue;
+
+ l2_dma = le64_to_cpu(cfg->l2.l1tab[idx].l2ptr) & STRTAB_L1_DESC_L2PTR_MASK;
+ ret = dmam_preserve_coherent_allocation(smmu->dev,
+ cfg->l2.l2ptrs[idx],
+ sizeof(struct arm_smmu_strtab_l2),
+ l2_dma, &l2_states[idx]);
+ if (ret) {
+ arm_smmu_unpreserve_l2_strtabs(master, i);
+ return ret;
+ }
+ }
+ return 0;
+}
+
+int arm_smmu_preserve_device(struct device *dev,
+ struct iommu_device_ser *device_ser)
+{
+ struct arm_smmu_master *master = dev_iommu_priv_get(dev);
+ struct iommu_domain *domain = iommu_get_domain_for_dev(dev);
+ struct iommu_domain_ser *domain_ser;
+ int ret;
+
+ /*
+ * We'd anyway configure abort STEs for non-preserved masters.
+ * TODO: Re-visit for identity once IOMMUFD noIOMMU is merged
+ */
+ if (domain->type == IOMMU_DOMAIN_IDENTITY ||
+ domain->type == IOMMU_DOMAIN_BLOCKED)
+ return 0;
+
+ if (domain->preserved_state) {
+ domain_ser = domain->preserved_state;
+ } else {
+ /* Fallback for kernel-managed domains */
+ ret = iommu_preserve_domain(domain, &domain_ser);
+ if (ret)
+ return ret;
+ }
+
+ /* Link this master to the preserved IOMMU domain in the ABI */
+ device_ser->domain_iommu_ser.domain_phys = virt_to_phys(domain_ser);
+
+ ret = arm_smmu_preserve_l2_strtabs(master);
+ if (ret)
+ return ret;
+
+ /* Mark the master as preserved to track state across disable */
+ master->preserved = true;
+
+ /*
+ * TODO: Preserve CD tables for Stage-1 domains here using
+ * dmam_preserve_allocation_attrs() on master->cd_table.
+ */
+
+ return 0;
+}
+
+void arm_smmu_unpreserve_device(struct device *dev,
+ struct iommu_device_ser *device_ser)
+{
+ struct arm_smmu_master *master = dev_iommu_priv_get(dev);
+
+ if (!master->preserved)
+ return;
+
+ master->preserved = false;
+ arm_smmu_unpreserve_l2_strtabs(master, master->num_streams);
+}
+
+static int arm_smmu_preserve_strtab_2lvl(struct arm_smmu_device *smmu,
+ struct iommu_hw_ser *iommu_ser)
+{
+ struct arm_smmu_strtab_cfg *cfg = &smmu->strtab_cfg;
+ u32 l1size = cfg->l2.num_l1_ents * sizeof(struct arm_smmu_strtab_l1);
+ u64 *l2_states;
+ u64 state;
+ int ret;
+
+ /* Preserve the L1 Stream table */
+ ret = dmam_preserve_coherent_allocation(smmu->dev, cfg->l2.l1tab,
+ l1size, cfg->l2.l1_dma, &state);
+ if (ret) {
+ dev_err(smmu->dev, "L1 table preservation failed\n");
+ return ret;
+ }
+ iommu_ser->smmuv3.l1_strtab_lu_state = state;
+
+ /* The L2 tables are preserved along with the masters using them */
+ l2_states = kho_alloc_preserve(sizeof(*l2_states) * cfg->l2.num_l1_ents);
+ if (IS_ERR(l2_states)) {
+ dmam_unpreserve_coherent_allocation(smmu->dev, cfg->l2.l1tab,
+ l1size, cfg->l2.l1_dma, state);
+ return PTR_ERR(l2_states);
+ }
+
+ iommu_ser->smmuv3.l2_strtab_lu_states_phys = virt_to_phys(l2_states);
+ return 0;
+}
+
+static void arm_smmu_unpreserve_strtab_2lvl(struct arm_smmu_device *smmu,
+ struct iommu_hw_ser *iommu_ser)
+{
+ struct arm_smmu_strtab_cfg *cfg = &smmu->strtab_cfg;
+ u32 l1size = cfg->l2.num_l1_ents * sizeof(struct arm_smmu_strtab_l1);
+ u64 *l2_states = phys_to_virt(iommu_ser->smmuv3.l2_strtab_lu_states_phys);
+ u32 i;
+
+ for (i = 0; i < cfg->l2.num_l1_ents; i++) {
+ dma_addr_t l2_dma;
+
+ if (!l2_states[i])
+ continue;
+
+ l2_dma = le64_to_cpu(cfg->l2.l1tab[i].l2ptr) & STRTAB_L1_DESC_L2PTR_MASK;
+ dmam_unpreserve_coherent_allocation(smmu->dev, cfg->l2.l2ptrs[i],
+ sizeof(struct arm_smmu_strtab_l2),
+ l2_dma, l2_states[i]);
+ }
+ kho_unpreserve_free(l2_states);
+
+ dmam_unpreserve_coherent_allocation(smmu->dev, cfg->l2.l1tab, l1size,
+ cfg->l2.l1_dma,
+ iommu_ser->smmuv3.l1_strtab_lu_state);
+}
+
+static int arm_smmu_preserve_strtab_linear(struct arm_smmu_device *smmu,
+ struct iommu_hw_ser *iommu_ser)
+{
+ struct arm_smmu_strtab_cfg *cfg = &smmu->strtab_cfg;
+ u32 size = (1 << smmu->sid_bits) * sizeof(struct arm_smmu_ste);
+ u64 state;
+ int ret;
+
+ /* STRTAB BASE can't be changed hitlessly, preserve the whole table */
+ ret = dmam_preserve_coherent_allocation(smmu->dev, cfg->linear.table,
+ size, cfg->linear.ste_dma,
+ &state);
+ if (ret)
+ return ret;
+
+ iommu_ser->smmuv3.l1_strtab_lu_state = state;
+ iommu_ser->smmuv3.l2_strtab_lu_states_phys = 0;
+ return 0;
+}
+
+static void arm_smmu_unpreserve_strtab_linear(struct arm_smmu_device *smmu,
+ struct iommu_hw_ser *iommu_ser)
+{
+ struct arm_smmu_strtab_cfg *cfg = &smmu->strtab_cfg;
+ u32 size = (1 << smmu->sid_bits) * sizeof(struct arm_smmu_ste);
+
+ dmam_unpreserve_coherent_allocation(smmu->dev, cfg->linear.table, size,
+ cfg->linear.ste_dma,
+ iommu_ser->smmuv3.l1_strtab_lu_state);
+}
+
+int arm_smmu_preserve(struct iommu_device *iommu,
+ struct iommu_hw_ser *iommu_ser)
+{
+ struct arm_smmu_device *smmu =
+ container_of(iommu, struct arm_smmu_device, iommu);
+
+ /* Basic info */
+ iommu_ser->smmuv3.phys_addr = smmu->base_phys;
+ iommu_ser->token = smmu->base_phys;
+ iommu_ser->type = IOMMU_ARM_SMMUV3;
+ iommu_ser->smmuv3.strtab_base_cfg =
+ readl_relaxed(smmu->base + ARM_SMMU_STRTAB_BASE_CFG);
+
+ /* We always implements 2-level when supported by HW */
+ if (smmu->features & ARM_SMMU_FEAT_2_LVL_STRTAB)
+ return arm_smmu_preserve_strtab_2lvl(smmu, iommu_ser);
+ else
+ return arm_smmu_preserve_strtab_linear(smmu, iommu_ser);
+}
+
+void arm_smmu_unpreserve(struct iommu_device *iommu,
+ struct iommu_hw_ser *iommu_ser)
+{
+ struct arm_smmu_device *smmu =
+ container_of(iommu, struct arm_smmu_device, iommu);
+
+ if (smmu->features & ARM_SMMU_FEAT_2_LVL_STRTAB)
+ arm_smmu_unpreserve_strtab_2lvl(smmu, iommu_ser);
+ else
+ arm_smmu_unpreserve_strtab_linear(smmu, iommu_ser);
+}
+
+#endif
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 670487613459..ae4a1c98228c 100644
--- a/drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3.c
+++ b/drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3.c
@@ -4548,6 +4548,12 @@ static const struct iommu_ops arm_smmu_ops = {
.def_domain_type = arm_smmu_def_domain_type,
.get_viommu_size = arm_smmu_get_viommu_size,
.viommu_init = arm_vsmmu_init,
+#ifdef CONFIG_IOMMU_LIVEUPDATE
+ .preserve = arm_smmu_preserve,
+ .unpreserve = arm_smmu_unpreserve,
+ .preserve_device = arm_smmu_preserve_device,
+ .unpreserve_device = arm_smmu_unpreserve_device,
+#endif
.user_pasid_table = 1,
.owner = THIS_MODULE,
.default_domain_ops = &(const struct iommu_domain_ops) {
@@ -5807,6 +5813,7 @@ static int arm_smmu_device_probe(struct platform_device *pdev)
return -EINVAL;
}
ioaddr = res->start;
+ smmu->base_phys = ioaddr;
/*
* Don't map the IMPLEMENTATION DEFINED regions, since they may contain
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 69455eced889..7bce5bbe75e5 100644
--- a/drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3.h
+++ b/drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3.h
@@ -952,6 +952,7 @@ struct arm_smmu_device {
void __iomem *base;
void __iomem *page1;
+ phys_addr_t base_phys;
#define ARM_SMMU_FEAT_2_LVL_STRTAB (1 << 0)
#define ARM_SMMU_FEAT_2_LVL_CDTAB (1 << 1)
@@ -1078,6 +1079,8 @@ struct arm_smmu_master {
bool ste_ats_enabled : 1;
bool stall_enabled;
bool ats_always_on;
+ /* Preserved for a KHO Live Update */
+ bool preserved;
unsigned int ssid_bits;
unsigned int iopf_refcount;
};
@@ -1196,6 +1199,17 @@ extern struct mutex arm_smmu_asid_lock;
struct arm_smmu_domain *arm_smmu_domain_alloc(void);
+#ifdef CONFIG_IOMMU_LIVEUPDATE
+int arm_smmu_preserve_device(struct device *dev,
+ struct iommu_device_ser *device_ser);
+int arm_smmu_preserve(struct iommu_device *iommu,
+ struct iommu_hw_ser *iommu_ser);
+void arm_smmu_unpreserve_device(struct device *dev,
+ struct iommu_device_ser *device_ser);
+void arm_smmu_unpreserve(struct iommu_device *iommu,
+ struct iommu_hw_ser *iommu_ser);
+#endif
+
static inline void arm_smmu_domain_free(struct arm_smmu_domain *smmu_domain)
{
/* No concurrency with invalidation is possible at this point */
--
2.56.0.rc1.315.gc6ed9934b7-goog
^ permalink raw reply [flat|nested] 10+ messages in thread* [RFC PATCH v1 3/9] iommu/arm-smmu-v3: Implement CD Table preservation
2026-09-29 7:19 [RFC PATCH v1 0/9] iommu/arm-smmu-v3: Implement Live Update support Pranjal Shrivastava
2026-09-29 7:19 ` [RFC PATCH v1 1/9] iommu/kho: Extend IOMMU KHO ABI for ARM SMMUv3 Pranjal Shrivastava
2026-09-29 7:19 ` [RFC PATCH v1 2/9] iommu/arm-smmu-v3: Implement KHO preservation for STEs Pranjal Shrivastava
@ 2026-09-29 7:19 ` Pranjal Shrivastava
2026-09-29 7:19 ` [RFC PATCH v1 4/9] iommu/arm-smmu-v3: Implement Live Update Stream Table restoration Pranjal Shrivastava
` (5 subsequent siblings)
8 siblings, 0 replies; 10+ messages in thread
From: Pranjal Shrivastava @ 2026-09-29 7:19 UTC (permalink / raw)
To: iommu, Will Deacon, Jason Gunthorpe
Cc: Robin Murphy, Joerg Roedel, Nicolin Chen, Kevin Tian,
Samiullah Khawaja, David Matlack, Vipin Sharma, Mostafa Saleh,
Daniel Mentz, Pasha Tatashin, Pratyush Yadav, linux-arm-kernel,
kexec, linux-kernel, Pranjal Shrivastava
The core IOMMU Live Update framework preserves page tables pointed by the
respective TTBs but the CD tables are expected to be preserved by the
SMMUv3 driver.
Extend the arm-smmu-v3 driver to preserve CD tables across a KHO with
Live Update enabled. Currently, only support for preserving S1 CD tables
exists since nested CD tables are auto-preserved as they reside in the
guest memory. The current implementation doesn't support preservation at
a PASID granularity due to the similar limitation in IOMMU LU core.
Unpreserve the CD tables in .unpreserve_device.
Signed-off-by: Pranjal Shrivastava <praan@google.com>
---
.../arm/arm-smmu-v3/arm-smmu-v3-liveupdate.c | 173 +++++++++++++++++-
1 file changed, 164 insertions(+), 9 deletions(-)
diff --git a/drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3-liveupdate.c b/drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3-liveupdate.c
index 515a266ac22e..38b9d4e4ab4d 100644
--- a/drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3-liveupdate.c
+++ b/drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3-liveupdate.c
@@ -11,6 +11,114 @@
#include "arm-smmu-v3.h"
#ifdef CONFIG_IOMMU_LIVEUPDATE
+static int arm_smmu_preserve_cd_table_linear(struct arm_smmu_master *master,
+ struct iommu_device_ser *device_ser)
+{
+ struct device *dev = master->smmu->dev;
+ struct arm_smmu_ctx_desc_cfg *cd_table = &master->cd_table;
+ u32 size = cd_table->linear.num_ents * sizeof(struct arm_smmu_cd);
+ u64 state;
+ int ret;
+
+ ret = dma_preserve_coherent_allocation(dev, cd_table->linear.table,
+ size, cd_table->cdtab_dma, &state);
+ if (ret)
+ return ret;
+
+ device_ser->smmuv3.l1_cdtab_lu_state = state;
+ device_ser->smmuv3.num_l2_cdtables = 0;
+
+ return 0;
+}
+
+static int arm_smmu_preserve_cd_table_2lvl(struct arm_smmu_master *master,
+ struct iommu_device_ser *device_ser)
+{
+ struct device *dev = master->smmu->dev;
+ struct arm_smmu_ctx_desc_cfg *cd_table = &master->cd_table;
+ u32 l1size = cd_table->l2.num_l1_ents * sizeof(struct arm_smmu_cdtab_l1);
+ u32 num_l2 = 0;
+ u64 *l2_states;
+ u64 state;
+ int ret, i;
+
+ ret = dma_preserve_coherent_allocation(dev, cd_table->l2.l1tab,
+ l1size, cd_table->cdtab_dma, &state);
+ if (ret)
+ return ret;
+
+ device_ser->smmuv3.l1_cdtab_lu_state = state;
+
+ for (i = 0; i < cd_table->l2.num_l1_ents; i++) {
+ if (cd_table->l2.l2ptrs[i])
+ num_l2++;
+ }
+
+ device_ser->smmuv3.num_l2_cdtables = num_l2;
+ if (!num_l2)
+ return 0;
+
+ l2_states = kho_alloc_preserve(sizeof(*l2_states) * num_l2);
+ if (IS_ERR(l2_states)) {
+ ret = PTR_ERR(l2_states);
+ goto err_unpreserve_cd_l1;
+ }
+
+ device_ser->smmuv3.l2_cdtab_lu_states_phys = virt_to_phys(l2_states);
+ num_l2 = 0;
+
+ for (i = 0; i < cd_table->l2.num_l1_ents; i++) {
+ dma_addr_t l2_dma;
+
+ if (!cd_table->l2.l2ptrs[i])
+ continue;
+
+ l2_dma = le64_to_cpu(cd_table->l2.l1tab[i].l2ptr) & CTXDESC_L1_DESC_L2PTR_MASK;
+ ret = dma_preserve_coherent_allocation(dev, cd_table->l2.l2ptrs[i],
+ sizeof(struct arm_smmu_cdtab_l2),
+ l2_dma, &state);
+ if (ret)
+ goto err_free_cd_l2_states;
+
+ l2_states[num_l2++] = state;
+ }
+
+ return 0;
+
+err_free_cd_l2_states:
+ for (i = i - 1; i >= 0; i--) {
+ if (cd_table->l2.l2ptrs[i]) {
+ num_l2--;
+ dma_unpreserve_coherent_allocation(dev, l2_states[num_l2]);
+ }
+ }
+ kho_unpreserve_free(l2_states);
+err_unpreserve_cd_l1:
+ dma_unpreserve_coherent_allocation(dev, device_ser->smmuv3.l1_cdtab_lu_state);
+ return ret;
+}
+
+static void arm_smmu_unpreserve_cd_table(struct arm_smmu_master *master,
+ struct iommu_device_ser *device_ser)
+{
+ struct device *dev = master->smmu->dev;
+ u32 num_l2 = device_ser->smmuv3.num_l2_cdtables;
+ u64 *l2_states;
+ u32 i;
+
+ if (!device_ser->smmuv3.l1_cdtab_lu_state)
+ return;
+
+ if (num_l2) {
+ l2_states = phys_to_virt(device_ser->smmuv3.l2_cdtab_lu_states_phys);
+ for (i = 0; i < num_l2; i++)
+ dma_unpreserve_coherent_allocation(dev, l2_states[i]);
+ kho_unpreserve_free(l2_states);
+ }
+ dma_unpreserve_coherent_allocation(dev, device_ser->smmuv3.l1_cdtab_lu_state);
+ memset(&device_ser->smmuv3, 0, sizeof(device_ser->smmuv3));
+}
+
static u64 *arm_smmu_l2_strtab_states(struct arm_smmu_device *smmu)
{
struct iommu_hw_ser *iommu_ser = iommu_preserved_state(&smmu->iommu);
@@ -105,17 +213,53 @@ int arm_smmu_preserve_device(struct device *dev,
{
struct arm_smmu_master *master = dev_iommu_priv_get(dev);
struct iommu_domain *domain = iommu_get_domain_for_dev(dev);
+ struct arm_smmu_domain *smmu_domain;
+ struct arm_smmu_ctx_desc_cfg *cd_table = &master->cd_table;
struct iommu_domain_ser *domain_ser;
- int ret;
+ int ret = 0;
+
+ memset(&device_ser->smmuv3, 0, sizeof(device_ser->smmuv3));
/*
* We'd anyway configure abort STEs for non-preserved masters.
* TODO: Re-visit for identity once IOMMUFD noIOMMU is merged
+ * TODO: Re-visit for CXL + ATS + Identity CD Table thing
+ * Can use cd_table_allocated() helper here?
*/
if (domain->type == IOMMU_DOMAIN_IDENTITY ||
domain->type == IOMMU_DOMAIN_BLOCKED)
return 0;
+ /*
+ * For nested domains we only need to preserve STE.
+ * The S2 parent domain's page tables are preserved via its own
+ * iommu_preserve_domain() call during IOMMUFD's HWPT preservation.
+ * Since the CD Table in this case lives in the guest memory, it is
+ * naturally preserved by default across KHO when Live Update is enabled.
+ * Thus, since CD isn't allocated via DMA allocator by the host driver
+ * we must not attempt preserving CD here.
+ */
+ if (domain->type == IOMMU_DOMAIN_NESTED)
+ goto skip_cd_preservation;
+
+ smmu_domain = to_smmu_domain(domain);
+
+ /* SVA domains cannot be preserved across KHO */
+ if (smmu_domain->stage == ARM_SMMU_DOMAIN_SVA) {
+ dev_err(dev, "SVA domains are NOT preserved across KHO\n");
+ return -EOPNOTSUPP;
+ }
+
+ /*
+ * The IOMMU LU Core doesn't support preservation at a PASID
+ * granularity yet, reject preservation to prevent leaving active
+ * PASIDs pointing to unpreserved tables.
+ */
+ if (arm_smmu_ssids_in_use(&master->cd_table)) {
+ dev_err(dev, "Preserving devices with active PASIDS is NOT supported w/ Live Update\n");
+ return -EOPNOTSUPP;
+ }
+
if (domain->preserved_state) {
domain_ser = domain->preserved_state;
} else {
@@ -128,18 +272,28 @@ int arm_smmu_preserve_device(struct device *dev,
/* Link this master to the preserved IOMMU domain in the ABI */
device_ser->domain_iommu_ser.domain_phys = virt_to_phys(domain_ser);
- ret = arm_smmu_preserve_l2_strtabs(master);
+ /* If it's not Stage-1, or the CD table isn't allocated, we're done */
+ if (smmu_domain->stage != ARM_SMMU_DOMAIN_S1 ||
+ !arm_smmu_cdtab_allocated(&master->cd_table))
+ goto skip_cd_preservation;
+
+ if (cd_table->s1fmt == STRTAB_STE_0_S1FMT_LINEAR)
+ ret = arm_smmu_preserve_cd_table_linear(master, device_ser);
+ else if (cd_table->s1fmt == STRTAB_STE_0_S1FMT_64K_L2)
+ ret = arm_smmu_preserve_cd_table_2lvl(master, device_ser);
+
+skip_cd_preservation:
if (ret)
return ret;
- /* Mark the master as preserved to track state across disable */
- master->preserved = true;
-
- /*
- * TODO: Preserve CD tables for Stage-1 domains here using
- * dmam_preserve_allocation_attrs() on master->cd_table.
- */
+ ret = arm_smmu_preserve_l2_strtabs(master);
+ if (ret) {
+ arm_smmu_unpreserve_cd_table(master, device_ser);
+ return ret;
+ }
+ /* Mark the master as preserved to track state during disable */
+ master->preserved = true;
return 0;
}
@@ -153,6 +307,7 @@ void arm_smmu_unpreserve_device(struct device *dev,
master->preserved = false;
arm_smmu_unpreserve_l2_strtabs(master, master->num_streams);
+ arm_smmu_unpreserve_cd_table(master, device_ser);
}
static int arm_smmu_preserve_strtab_2lvl(struct arm_smmu_device *smmu,
--
2.56.0.rc1.315.gc6ed9934b7-goog
^ permalink raw reply [flat|nested] 10+ messages in thread* [RFC PATCH v1 4/9] iommu/arm-smmu-v3: Implement Live Update Stream Table restoration
2026-09-29 7:19 [RFC PATCH v1 0/9] iommu/arm-smmu-v3: Implement Live Update support Pranjal Shrivastava
` (2 preceding siblings ...)
2026-09-29 7:19 ` [RFC PATCH v1 3/9] iommu/arm-smmu-v3: Implement CD Table preservation Pranjal Shrivastava
@ 2026-09-29 7:19 ` Pranjal Shrivastava
2026-09-29 7:19 ` [RFC PATCH v1 5/9] iommu/arm-smmu-v3: Implement Live Update CD " Pranjal Shrivastava
` (4 subsequent siblings)
8 siblings, 0 replies; 10+ messages in thread
From: Pranjal Shrivastava @ 2026-09-29 7:19 UTC (permalink / raw)
To: iommu, Will Deacon, Jason Gunthorpe
Cc: Robin Murphy, Joerg Roedel, Nicolin Chen, Kevin Tian,
Samiullah Khawaja, David Matlack, Vipin Sharma, Mostafa Saleh,
Daniel Mentz, Pasha Tatashin, Pratyush Yadav, linux-arm-kernel,
kexec, linux-kernel, Pranjal Shrivastava
During the boot phase of a Kexec Handover (KHO), the incoming kernel
must adopt the preserved SMMU Stream Table to seamlessly inherit the
IOMMU state from the outgoing kernel.
Restore it in arm_smmu_init_strtab() using the kexec helpers to validate
the live STRTAB_BASE{,_CFG} and reserve the in-use ASIDs/VMIDs. Enable
ARM_SMMU_V3_KEXEC for IOMMU_LIVEUPDATE for this.
Signed-off-by: Pranjal Shrivastava <praan@google.com>
---
drivers/iommu/arm/Kconfig | 2 +-
.../arm/arm-smmu-v3/arm-smmu-v3-liveupdate.c | 142 ++++++++++++++++++
drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3.c | 4 +
drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3.h | 6 +
4 files changed, 153 insertions(+), 1 deletion(-)
diff --git a/drivers/iommu/arm/Kconfig b/drivers/iommu/arm/Kconfig
index ad1693e3b9c8..8f98d3ad984a 100644
--- a/drivers/iommu/arm/Kconfig
+++ b/drivers/iommu/arm/Kconfig
@@ -113,7 +113,7 @@ config ARM_SMMU_V3_IOMMUFD
# Common helpers for a kexec'd kernel, e.g. kdump adoption and live update
config ARM_SMMU_V3_KEXEC
- def_bool CRASH_DUMP
+ def_bool CRASH_DUMP || IOMMU_LIVEUPDATE
config ARM_SMMU_V3_KUNIT_TEST
tristate "KUnit tests for arm-smmu-v3 driver" if !KUNIT_ALL_TESTS
diff --git a/drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3-liveupdate.c b/drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3-liveupdate.c
index 38b9d4e4ab4d..4998234e1e7a 100644
--- a/drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3-liveupdate.c
+++ b/drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3-liveupdate.c
@@ -429,4 +429,146 @@ void arm_smmu_unpreserve(struct iommu_device *iommu,
arm_smmu_unpreserve_strtab_linear(smmu, iommu_ser);
}
+static int arm_smmu_liveupdate_restore_strtab_2lvl(struct arm_smmu_device *smmu,
+ struct iommu_hw_ser *iommu_ser,
+ u32 cfg_reg, phys_addr_t base)
+{
+ struct arm_smmu_strtab_cfg *cfg = &smmu->strtab_cfg;
+ u32 num_l1_ents, i;
+ u64 *l2_states;
+ int ret;
+
+ if (!iommu_ser->smmuv3.l2_strtab_lu_states_phys)
+ return -EINVAL;
+ l2_states = phys_to_virt(iommu_ser->smmuv3.l2_strtab_lu_states_phys);
+
+ ret = arm_smmu_kexec_parse_strtab_2lvl(smmu, cfg_reg, base,
+ &num_l1_ents);
+ if (ret)
+ goto out_free_states;
+ cfg->l2.num_l1_ents = num_l1_ents;
+
+ ret = -ENOMEM;
+ cfg->l2.l1tab = dmam_restore_coherent_allocation(smmu->dev,
+ num_l1_ents * sizeof(*cfg->l2.l1tab), &cfg->l2.l1_dma,
+ GFP_KERNEL, iommu_ser->smmuv3.l1_strtab_lu_state);
+ if (!cfg->l2.l1tab)
+ goto out_free_states;
+
+ cfg->l2.l2ptrs = devm_kcalloc(smmu->dev, num_l1_ents,
+ sizeof(*cfg->l2.l2ptrs), GFP_KERNEL);
+ if (!cfg->l2.l2ptrs)
+ goto out_free_states;
+
+ /* The outgoing .shutdown cleared the L1 STDs of unpreserved L2 tables */
+ for (i = 0; i < num_l1_ents; i++) {
+ u64 l1_desc = le64_to_cpu(cfg->l2.l1tab[i].l2ptr);
+ phys_addr_t l2_base;
+ dma_addr_t l2_dma;
+
+ ret = arm_smmu_kexec_check_strtab_l1_desc(smmu, l1_desc, i,
+ &l2_base);
+ if (ret == 1)
+ continue;
+ if (ret)
+ goto out_free_states;
+
+ if (!l2_states[i]) {
+ dev_err(smmu->dev, "L1[%u] has no preserved L2 table\n",
+ i);
+ ret = -EINVAL;
+ goto out_free_states;
+ }
+
+ l2_dma = l2_base;
+ cfg->l2.l2ptrs[i] = dmam_restore_coherent_allocation(smmu->dev,
+ sizeof(*cfg->l2.l2ptrs[i]), &l2_dma, GFP_KERNEL,
+ l2_states[i]);
+ if (!cfg->l2.l2ptrs[i]) {
+ ret = -ENOMEM;
+ goto out_free_states;
+ }
+ }
+ ret = 0;
+
+out_free_states:
+ kho_restore_free(l2_states);
+ return ret;
+}
+
+static int
+arm_smmu_liveupdate_restore_strtab_linear(struct arm_smmu_device *smmu,
+ struct iommu_hw_ser *iommu_ser,
+ u32 cfg_reg, phys_addr_t base)
+{
+ struct arm_smmu_strtab_cfg *cfg = &smmu->strtab_cfg;
+ u32 num_ents;
+ int ret;
+
+ ret = arm_smmu_kexec_parse_strtab_linear(smmu, cfg_reg, base,
+ &num_ents);
+ if (ret)
+ return ret;
+ cfg->linear.num_ents = num_ents;
+
+ cfg->linear.table = dmam_restore_coherent_allocation(smmu->dev,
+ num_ents * sizeof(*cfg->linear.table),
+ &cfg->linear.ste_dma, GFP_KERNEL,
+ iommu_ser->smmuv3.l1_strtab_lu_state);
+ if (!cfg->linear.table)
+ return -ENOMEM;
+ return 0;
+}
+
+/*
+ * Take over the stream table left programmed by the previous kernel and
+ * reserve the ASIDs/VMIDs used by the preserved STEs. Returns -ENOENT if
+ * nothing was preserved for this SMMU.
+ */
+int arm_smmu_liveupdate_restore_strtab(struct arm_smmu_device *smmu)
+{
+ u32 cfg_reg = readl_relaxed(smmu->base + ARM_SMMU_STRTAB_BASE_CFG);
+ u64 base_reg = readq_relaxed(smmu->base + ARM_SMMU_STRTAB_BASE);
+ bool is_2lvl = smmu->features & ARM_SMMU_FEAT_2_LVL_STRTAB;
+ phys_addr_t base = base_reg & STRTAB_BASE_ADDR_MASK;
+ u32 fmt = FIELD_GET(STRTAB_BASE_CFG_FMT, cfg_reg);
+ struct iommu_hw_ser *iommu_ser;
+ int ret;
+
+ iommu_ser = iommu_get_preserved_data(smmu->base_phys, IOMMU_ARM_SMMUV3);
+ if (!iommu_ser)
+ return -ENOENT;
+
+ if (cfg_reg != iommu_ser->smmuv3.strtab_base_cfg) {
+ dev_err(smmu->dev, "STRTAB_BASE_CFG changed across live update\n");
+ return -EINVAL;
+ }
+
+ /* The preserving kernel always uses a 2-level table when supported */
+ if (fmt == STRTAB_BASE_CFG_FMT_2LVL && is_2lvl)
+ ret = arm_smmu_liveupdate_restore_strtab_2lvl(smmu, iommu_ser,
+ cfg_reg, base);
+ else if (fmt == STRTAB_BASE_CFG_FMT_LINEAR && !is_2lvl)
+ ret = arm_smmu_liveupdate_restore_strtab_linear(smmu, iommu_ser,
+ cfg_reg, base);
+ else
+ ret = -EINVAL;
+ if (ret) {
+ dev_err(smmu->dev, "failed to restore stream table: %d\n", ret);
+ return ret;
+ }
+
+ /* Keep new domains off the ASIDs/VMIDs used by the preserved STEs */
+ ret = arm_smmu_kexec_scan_and_resv_ids(smmu);
+ if (ret) {
+ dev_err(smmu->dev, "failed to reserve in-use ASIDs/VMIDs\n");
+ arm_smmu_kexec_unresv_ids(smmu);
+ return ret;
+ }
+
+ dev_info(smmu->dev, "restored preserved %s stream table\n",
+ is_2lvl ? "2-level" : "linear");
+ return 0;
+}
+
#endif
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 ae4a1c98228c..c8bcf901f1c8 100644
--- a/drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3.c
+++ b/drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3.c
@@ -4772,6 +4772,10 @@ static int arm_smmu_init_strtab(struct arm_smmu_device *smmu)
!arm_smmu_kdump_adopt_strtab(smmu))
return 0;
+ ret = arm_smmu_liveupdate_restore_strtab(smmu);
+ if (ret != -ENOENT)
+ return ret;
+
if (smmu->features & ARM_SMMU_FEAT_2_LVL_STRTAB)
ret = arm_smmu_init_strtab_2lvl(smmu);
else
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 7bce5bbe75e5..6b75f48671f8 100644
--- a/drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3.h
+++ b/drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3.h
@@ -1208,6 +1208,12 @@ void arm_smmu_unpreserve_device(struct device *dev,
struct iommu_device_ser *device_ser);
void arm_smmu_unpreserve(struct iommu_device *iommu,
struct iommu_hw_ser *iommu_ser);
+int arm_smmu_liveupdate_restore_strtab(struct arm_smmu_device *smmu);
+#else
+static inline int arm_smmu_liveupdate_restore_strtab(struct arm_smmu_device *smmu)
+{
+ return -ENOENT;
+}
#endif
static inline void arm_smmu_domain_free(struct arm_smmu_domain *smmu_domain)
--
2.56.0.rc1.315.gc6ed9934b7-goog
^ permalink raw reply [flat|nested] 10+ messages in thread* [RFC PATCH v1 5/9] iommu/arm-smmu-v3: Implement Live Update CD Table restoration
2026-09-29 7:19 [RFC PATCH v1 0/9] iommu/arm-smmu-v3: Implement Live Update support Pranjal Shrivastava
` (3 preceding siblings ...)
2026-09-29 7:19 ` [RFC PATCH v1 4/9] iommu/arm-smmu-v3: Implement Live Update Stream Table restoration Pranjal Shrivastava
@ 2026-09-29 7:19 ` Pranjal Shrivastava
2026-09-29 7:19 ` [RFC PATCH v1 6/9] iommu/arm-smmu-v3: Implement Live Update shutdown Pranjal Shrivastava
` (3 subsequent siblings)
8 siblings, 0 replies; 10+ messages in thread
From: Pranjal Shrivastava @ 2026-09-29 7:19 UTC (permalink / raw)
To: iommu, Will Deacon, Jason Gunthorpe
Cc: Robin Murphy, Joerg Roedel, Nicolin Chen, Kevin Tian,
Samiullah Khawaja, David Matlack, Vipin Sharma, Mostafa Saleh,
Daniel Mentz, Pasha Tatashin, Pratyush Yadav, linux-arm-kernel,
kexec, linux-kernel, Pranjal Shrivastava
During the boot phase of a Kexec Handover (KHO), the incoming kernel
must restore Context Descriptor (CD) tables for preserved devices to
maintain Stage-1 translation contexts. Add a check within
arm_smmu_alloc_cd_tables() to implement the CD table restoration.
Signed-off-by: Pranjal Shrivastava <praan@google.com>
---
.../iommu/arm/arm-smmu-v3/arm-smmu-v3-kexec.c | 44 ++++++---
.../arm/arm-smmu-v3/arm-smmu-v3-liveupdate.c | 90 +++++++++++++++++++
drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3.c | 9 ++
drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3.h | 6 ++
4 files changed, 138 insertions(+), 11 deletions(-)
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
index 8c53cd757007..7dc499a7bef4 100644
--- a/drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3-kexec.c
+++ b/drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3-kexec.c
@@ -222,6 +222,33 @@ int arm_smmu_kexec_check_ste_cdtab(struct arm_smmu_device *smmu, u64 ste0,
return 0;
}
+/**
+ * arm_smmu_kexec_check_cdtab_l1_desc() - Check one CD table L1 descriptor
+ * @l1_desc: L1 descriptor value from the previous kernel's CD table
+ * @l2_base: pointer to return the L2 CD 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_cdtab_l1_desc(u64 l1_desc, phys_addr_t *l2_base)
+{
+ phys_addr_t base = l1_desc & CTXDESC_L1_DESC_L2PTR_MASK;
+
+ if (!(l1_desc & CTXDESC_L1_DESC_V))
+ return 1;
+
+ /*
+ * A valid descriptor never carries a null pointer. Also, an L2 table is
+ * always 64KB-aligned, so an unaligned pointer would make this kernel
+ * read a different table.
+ */
+ if (!base || !IS_ALIGNED(base, sizeof(struct arm_smmu_cdtab_l2)))
+ return -EINVAL;
+
+ *l2_base = base;
+ return 0;
+}
+
static int arm_smmu_kexec_resv_asid(struct arm_smmu_device *smmu, u32 asid)
{
/* A valid CD never has ASID 0; both kernels share the same HW limit */
@@ -310,21 +337,16 @@ static int arm_smmu_kexec_resv_s1_asids(struct arm_smmu_device *smmu, u64 ste0)
/* Aliased L2 tables cannot extend the walk; they only repeat a scan */
for (i = 0; i < num_l1_ents; i++) {
u64 l1_desc = le64_to_cpu(l1tab[i].l2ptr);
- phys_addr_t l2_base = l1_desc & CTXDESC_L1_DESC_L2PTR_MASK;
struct arm_smmu_cdtab_l2 *l2;
+ phys_addr_t l2_base;
- if (!(l1_desc & CTXDESC_L1_DESC_V))
+ ret = arm_smmu_kexec_check_cdtab_l1_desc(l1_desc, &l2_base);
+ if (ret == 1) {
+ ret = 0;
continue;
-
- /*
- * A valid descriptor never carries a null pointer. Also, an L2
- * table is always 64KB-aligned, so an unaligned pointer would
- * make this kernel read a different table.
- */
- if (!l2_base || !IS_ALIGNED(l2_base, sizeof(*l2))) {
- ret = -EINVAL;
- break;
}
+ if (ret)
+ break;
l2 = memremap(l2_base, num_cds * sizeof(*l2->cds), MEMREMAP_WB);
if (!l2) {
diff --git a/drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3-liveupdate.c b/drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3-liveupdate.c
index 4998234e1e7a..981b091a47a1 100644
--- a/drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3-liveupdate.c
+++ b/drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3-liveupdate.c
@@ -571,4 +571,94 @@ int arm_smmu_liveupdate_restore_strtab(struct arm_smmu_device *smmu)
return 0;
}
+int arm_smmu_liveupdate_restore_cd_tables(struct arm_smmu_master *master)
+{
+ struct arm_smmu_device *smmu = master->smmu;
+ struct arm_smmu_ctx_desc_cfg *cd_table = &master->cd_table;
+ struct iommu_device_ser *dev_ser = dev_iommu_restored_state(master->dev);
+ u32 max_contexts, s1fmt, num_l2, restored = 0, i;
+ struct arm_smmu_ste *ste;
+ u64 *l2_states = NULL;
+ phys_addr_t cdtab;
+ u64 ste0;
+ int ret;
+
+ if (!dev_ser)
+ return 0;
+
+ /* Only an S1 STE has a CD table behind it */
+ ste = arm_smmu_get_step_for_sid(smmu, master->streams[0].id);
+ ste0 = le64_to_cpu(ste->data[0]);
+ if (!(ste0 & STRTAB_STE_0_V) ||
+ FIELD_GET(STRTAB_STE_0_CFG, ste0) != STRTAB_STE_0_CFG_S1_TRANS)
+ return 0;
+
+ ret = arm_smmu_kexec_check_ste_cdtab(smmu, ste0, &cdtab, &s1fmt,
+ &max_contexts);
+ if (ret)
+ return ret;
+
+ cd_table->s1cdmax = ilog2(max_contexts);
+ cd_table->s1fmt = s1fmt;
+
+ if (s1fmt == STRTAB_STE_0_S1FMT_LINEAR) {
+ cd_table->linear.num_ents = max_contexts;
+ cd_table->linear.table = dma_restore_coherent_allocation(smmu->dev,
+ max_contexts * sizeof(*cd_table->linear.table),
+ &cd_table->cdtab_dma, GFP_KERNEL,
+ dev_ser->smmuv3.l1_cdtab_lu_state);
+ return cd_table->linear.table ? 0 : -ENOMEM;
+ }
+
+ cd_table->l2.num_l1_ents = DIV_ROUND_UP(max_contexts,
+ CTXDESC_L2_ENTRIES);
+ cd_table->l2.l1tab = dma_restore_coherent_allocation(smmu->dev,
+ cd_table->l2.num_l1_ents * sizeof(*cd_table->l2.l1tab),
+ &cd_table->cdtab_dma, GFP_KERNEL,
+ dev_ser->smmuv3.l1_cdtab_lu_state);
+ if (!cd_table->l2.l1tab)
+ return -ENOMEM;
+
+ cd_table->l2.l2ptrs = kcalloc(cd_table->l2.num_l1_ents,
+ sizeof(*cd_table->l2.l2ptrs), GFP_KERNEL);
+ if (!cd_table->l2.l2ptrs)
+ return -ENOMEM;
+
+ num_l2 = dev_ser->smmuv3.num_l2_cdtables;
+ if (num_l2)
+ l2_states = phys_to_virt(dev_ser->smmuv3.l2_cdtab_lu_states_phys);
+
+ for (i = 0; i < cd_table->l2.num_l1_ents; i++) {
+ u64 l1_desc = le64_to_cpu(cd_table->l2.l1tab[i].l2ptr);
+ phys_addr_t l2_base;
+ dma_addr_t l2_dma;
+
+ ret = arm_smmu_kexec_check_cdtab_l1_desc(l1_desc, &l2_base);
+ if (ret == 1)
+ continue;
+ if (ret)
+ goto out_free_states;
+
+ if (restored >= num_l2) {
+ ret = -EINVAL;
+ goto out_free_states;
+ }
+
+ l2_dma = l2_base;
+ cd_table->l2.l2ptrs[i] = dma_restore_coherent_allocation(smmu->dev,
+ sizeof(*cd_table->l2.l2ptrs[i]), &l2_dma,
+ GFP_KERNEL, l2_states[restored++]);
+ if (!cd_table->l2.l2ptrs[i]) {
+ ret = -ENOMEM;
+ goto out_free_states;
+ }
+ }
+ ret = 0;
+
+out_free_states:
+ if (l2_states)
+ kho_restore_free(l2_states);
+ return ret;
+}
+
#endif
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 c8bcf901f1c8..b13ed06a368f 100644
--- a/drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3.c
+++ b/drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3.c
@@ -17,6 +17,7 @@
#include <linux/err.h>
#include <linux/interrupt.h>
#include <linux/generic_pt/iommu.h>
+#include <linux/iommu-liveupdate.h>
#include <linux/iopoll.h>
#include <linux/jump_label.h>
@@ -1723,6 +1724,14 @@ static int arm_smmu_alloc_cd_tables(struct arm_smmu_master *master)
struct arm_smmu_device *smmu = master->smmu;
struct arm_smmu_ctx_desc_cfg *cd_table = &master->cd_table;
+ if (dev_iommu_restored_state(master->dev)) {
+ ret = arm_smmu_liveupdate_restore_cd_tables(master);
+ if (ret)
+ return ret;
+ if (arm_smmu_cdtab_allocated(cd_table))
+ return 0;
+ }
+
cd_table->s1cdmax = master->ssid_bits;
/*
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 6b75f48671f8..453ad34ab0fa 100644
--- a/drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3.h
+++ b/drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3.h
@@ -1209,11 +1209,16 @@ void arm_smmu_unpreserve_device(struct device *dev,
void arm_smmu_unpreserve(struct iommu_device *iommu,
struct iommu_hw_ser *iommu_ser);
int arm_smmu_liveupdate_restore_strtab(struct arm_smmu_device *smmu);
+int arm_smmu_liveupdate_restore_cd_tables(struct arm_smmu_master *master);
#else
static inline int arm_smmu_liveupdate_restore_strtab(struct arm_smmu_device *smmu)
{
return -ENOENT;
}
+static inline int arm_smmu_liveupdate_restore_cd_tables(struct arm_smmu_master *master)
+{
+ return 0;
+}
#endif
static inline void arm_smmu_domain_free(struct arm_smmu_domain *smmu_domain)
@@ -1369,6 +1374,7 @@ int arm_smmu_kexec_check_strtab_l1_desc(struct arm_smmu_device *smmu,
int arm_smmu_kexec_check_ste_cdtab(struct arm_smmu_device *smmu, u64 ste0,
phys_addr_t *cdtab, u32 *s1fmt,
u32 *max_contexts);
+int arm_smmu_kexec_check_cdtab_l1_desc(u64 l1_desc, phys_addr_t *l2_base);
int arm_smmu_kexec_scan_and_resv_ids(struct arm_smmu_device *smmu);
void arm_smmu_kexec_unresv_ids(struct arm_smmu_device *smmu);
#endif /* CONFIG_ARM_SMMU_V3_KEXEC */
--
2.56.0.rc1.315.gc6ed9934b7-goog
^ permalink raw reply [flat|nested] 10+ messages in thread* [RFC PATCH v1 6/9] iommu/arm-smmu-v3: Implement Live Update shutdown
2026-09-29 7:19 [RFC PATCH v1 0/9] iommu/arm-smmu-v3: Implement Live Update support Pranjal Shrivastava
` (4 preceding siblings ...)
2026-09-29 7:19 ` [RFC PATCH v1 5/9] iommu/arm-smmu-v3: Implement Live Update CD " Pranjal Shrivastava
@ 2026-09-29 7:19 ` Pranjal Shrivastava
2026-09-29 7:19 ` [RFC PATCH v1 7/9] iommu/arm-smmu-v3: Retain SMMUEN across a Live Update restore Pranjal Shrivastava
` (2 subsequent siblings)
8 siblings, 0 replies; 10+ messages in thread
From: Pranjal Shrivastava @ 2026-09-29 7:19 UTC (permalink / raw)
To: iommu, Will Deacon, Jason Gunthorpe
Cc: Robin Murphy, Joerg Roedel, Nicolin Chen, Kevin Tian,
Samiullah Khawaja, David Matlack, Vipin Sharma, Mostafa Saleh,
Daniel Mentz, Pasha Tatashin, Pratyush Yadav, linux-arm-kernel,
kexec, linux-kernel, Pranjal Shrivastava
During a Kexec Handover (KHO) with Live Update enabled, the SMMUv3
must not be disabled (via CR0.SMMUEN=0) during device shutdown since
doing so would instantly stop active DMA traffic preserved for masters.
Modify the .shutdown hook to install abort STEs for unpreserved masters,
invalidate the L1STDs of unpreserved L2 tables and flush the config and
TLB caches. Mask the interrupts, wait for the EVTQ handler and disable
the queues, leaving SMMUEN set. Fall back to a full disable on failure.
Signed-off-by: Pranjal Shrivastava <praan@google.com>
---
.../arm/arm-smmu-v3/arm-smmu-v3-liveupdate.c | 120 ++++++++++++++++++
drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3.c | 25 +++-
drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3.h | 12 ++
3 files changed, 152 insertions(+), 5 deletions(-)
diff --git a/drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3-liveupdate.c b/drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3-liveupdate.c
index 981b091a47a1..68652123d0e1 100644
--- a/drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3-liveupdate.c
+++ b/drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3-liveupdate.c
@@ -5,6 +5,7 @@
*/
#include <linux/dma-mapping.h>
+#include <linux/interrupt.h>
#include <linux/iommu.h>
#include <linux/iommu-liveupdate.h>
#include <linux/kexec_handover.h>
@@ -429,6 +430,125 @@ void arm_smmu_unpreserve(struct iommu_device *iommu,
arm_smmu_unpreserve_strtab_linear(smmu, iommu_ser);
}
+static void arm_smmu_liveupdate_clear_l1_std(struct arm_smmu_device *smmu,
+ unsigned long *l2_active)
+{
+ struct arm_smmu_strtab_cfg *cfg = &smmu->strtab_cfg;
+ int i;
+
+ for (i = 0; i < cfg->l2.num_l1_ents; i++) {
+ if (!cfg->l2.l2ptrs[i] || test_bit(i, l2_active))
+ continue;
+
+ /* Clear L1 STD for unpreserved streams */
+ WRITE_ONCE(cfg->l2.l1tab[i].l2ptr, 0);
+ }
+}
+
+int arm_smmu_liveupdate_shutdown(struct arm_smmu_device *smmu)
+{
+ struct arm_smmu_master *master;
+ struct arm_smmu_stream *stream;
+ struct arm_smmu_strtab_cfg *cfg = &smmu->strtab_cfg;
+ struct rb_node *node;
+ struct arm_smmu_ste abort_ste;
+ struct arm_smmu_cmd cmd_cfgi, cmd_el2, cmd_nsnh;
+ unsigned long *l2_active = NULL;
+ bool is_2lvl = smmu->features & ARM_SMMU_FEAT_2_LVL_STRTAB;
+ u32 cr0;
+ int ret;
+
+ /* Only the incoming kernel unmasks the SMMU interrupts again */
+ if (arm_smmu_disable_irqs(smmu))
+ dev_warn(smmu->dev, "failed to disable irqs\n");
+
+ /* Wait for a running EVTQ handler */
+ if (smmu->combined_irq || smmu->evtq.q.irq)
+ synchronize_irq(smmu->combined_irq ?: smmu->evtq.q.irq);
+
+ if (is_2lvl) {
+ l2_active = bitmap_zalloc(cfg->l2.num_l1_ents, GFP_KERNEL);
+ if (!l2_active) {
+ dev_err(smmu->dev, "OOM: Falling back to hard disable\n");
+ return -ENOMEM;
+ }
+ }
+
+ /* Prepare an abort STE for unpreserved masters */
+ arm_smmu_make_abort_ste(&abort_ste);
+
+ /*
+ * We do not scrub unpreserved Context Descriptors (CDs) here since:
+ *
+ * 1. Each master has its own independently allocated CD table page,
+ * i.e. multiple masters never share a CD table.
+ *
+ * 2. We explicitly reject preserving any device with active PASIDs in
+ * the .preserve_device op. Thus, any preserved master is guaranteed
+ * to only be using CD[0].
+ *
+ * Therefore, partial preservation within a CD table is not possible,
+ * and we only need to isolate unpreserved streams within shared
+ * Stream Tables.
+ */
+ mutex_lock(&smmu->streams_mutex);
+
+ /* Install the abort STEs for unpreserved masters */
+ for (node = rb_first(&smmu->streams); node; node = rb_next(node)) {
+ stream = rb_entry(node, struct arm_smmu_stream, node);
+ master = stream->master;
+
+ if (master->preserved) {
+ if (is_2lvl)
+ set_bit(arm_smmu_strtab_l1_idx(stream->id), l2_active);
+ } else {
+ arm_smmu_write_ste(master, stream->id,
+ arm_smmu_get_step_for_sid(smmu, stream->id),
+ &abort_ste);
+ }
+ }
+
+ /* Invalidate completely unpreserved streams */
+ if (is_2lvl) {
+ arm_smmu_liveupdate_clear_l1_std(smmu, l2_active);
+ bitmap_free(l2_active);
+ }
+
+ mutex_unlock(&smmu->streams_mutex);
+
+ /* Sync hardware caches to observe updated structures */
+ cmd_cfgi = arm_smmu_make_cmd_cfgi_all();
+ arm_smmu_cmdq_issue_cmdlist(smmu, &smmu->cmdq, &cmd_cfgi, 1, true);
+
+ /*
+ * Aggressively flush all TLBs to ensure no stale entries exist for
+ * unpreserved streams. The preserved streams will take a minor hit
+ * re-walking their page tables, but this guarantees safety.
+ */
+ if (smmu->features & ARM_SMMU_FEAT_HYP) {
+ cmd_el2 = arm_smmu_make_cmd_op(CMDQ_OP_TLBI_EL2_ALL);
+ arm_smmu_cmdq_issue_cmdlist(smmu, &smmu->cmdq, &cmd_el2, 1, true);
+ }
+
+ cmd_nsnh = arm_smmu_make_cmd_op(CMDQ_OP_TLBI_NSNH_ALL);
+ arm_smmu_cmdq_issue_cmdlist(smmu, &smmu->cmdq, &cmd_nsnh, 1, true);
+
+ /*
+ * No need to drain the CMDQ: the invalidations above are synced, no
+ * other submitters are left at shutdown and the incoming kernel
+ * invalidates everything again.
+ * TODO: Quiesce the CMDQV VCMDQs assigned to guests.
+ */
+
+ /* Disable the queues, leaving SMMUEN set for the preserved masters */
+ cr0 = readl_relaxed(smmu->base + ARM_SMMU_CR0);
+ cr0 &= ~(CR0_CMDQEN | CR0_EVTQEN | CR0_PRIQEN);
+ ret = arm_smmu_write_reg_sync(smmu, cr0, ARM_SMMU_CR0, ARM_SMMU_CR0ACK);
+ if (ret)
+ dev_err(smmu->dev, "failed to disable queues\n");
+ return ret;
+}
+
static int arm_smmu_liveupdate_restore_strtab_2lvl(struct arm_smmu_device *smmu,
struct iommu_hw_ser *iommu_ser,
u32 cfg_reg, phys_addr_t base)
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 b13ed06a368f..3cf97f451b64 100644
--- a/drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3.c
+++ b/drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3.c
@@ -1853,9 +1853,9 @@ static const struct arm_smmu_entry_writer_ops arm_smmu_ste_writer_ops = {
.get_update_safe = arm_smmu_get_ste_update_safe,
};
-static void arm_smmu_write_ste(struct arm_smmu_master *master, u32 sid,
- struct arm_smmu_ste *ste,
- const struct arm_smmu_ste *target)
+void arm_smmu_write_ste(struct arm_smmu_master *master, u32 sid,
+ struct arm_smmu_ste *ste,
+ const struct arm_smmu_ste *target)
{
struct arm_smmu_device *smmu = master->smmu;
struct arm_smmu_ste_writer ste_writer = {
@@ -4813,8 +4813,8 @@ static int arm_smmu_init_structures(struct arm_smmu_device *smmu)
return 0;
}
-static int arm_smmu_write_reg_sync(struct arm_smmu_device *smmu, u32 val,
- unsigned int reg_off, unsigned int ack_off)
+int arm_smmu_write_reg_sync(struct arm_smmu_device *smmu, u32 val,
+ unsigned int reg_off, unsigned int ack_off)
{
u32 reg;
@@ -4998,6 +4998,12 @@ static int arm_smmu_setup_irqs(struct arm_smmu_device *smmu)
return 0;
}
+int arm_smmu_disable_irqs(struct arm_smmu_device *smmu)
+{
+ return arm_smmu_write_reg_sync(smmu, 0, ARM_SMMU_IRQ_CTRL,
+ ARM_SMMU_IRQ_CTRLACK);
+}
+
static int arm_smmu_device_disable(struct arm_smmu_device *smmu)
{
int ret;
@@ -5919,6 +5925,15 @@ static void arm_smmu_device_shutdown(struct platform_device *pdev)
{
struct arm_smmu_device *smmu = platform_get_drvdata(pdev);
+ if (iommu_preserved_state(&smmu->iommu)) {
+ if (!arm_smmu_liveupdate_shutdown(smmu))
+ return;
+ }
+
+ /*
+ * Disable the SMMU on standard shutdown/reboot.
+ * Fallback to this path if the Live Update shutdown failed.
+ */
arm_smmu_device_disable(smmu);
}
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 453ad34ab0fa..0a88c0ec66ca 100644
--- a/drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3.h
+++ b/drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3.h
@@ -1198,6 +1198,9 @@ to_smmu_nested_domain(struct iommu_domain *dom)
extern struct mutex arm_smmu_asid_lock;
struct arm_smmu_domain *arm_smmu_domain_alloc(void);
+int arm_smmu_write_reg_sync(struct arm_smmu_device *smmu, u32 val,
+ unsigned int reg_off, unsigned int ack_off);
+int arm_smmu_disable_irqs(struct arm_smmu_device *smmu);
#ifdef CONFIG_IOMMU_LIVEUPDATE
int arm_smmu_preserve_device(struct device *dev,
@@ -1208,9 +1211,14 @@ void arm_smmu_unpreserve_device(struct device *dev,
struct iommu_device_ser *device_ser);
void arm_smmu_unpreserve(struct iommu_device *iommu,
struct iommu_hw_ser *iommu_ser);
+int arm_smmu_liveupdate_shutdown(struct arm_smmu_device *smmu);
int arm_smmu_liveupdate_restore_strtab(struct arm_smmu_device *smmu);
int arm_smmu_liveupdate_restore_cd_tables(struct arm_smmu_master *master);
#else
+static inline int arm_smmu_liveupdate_shutdown(struct arm_smmu_device *smmu)
+{
+ return -EOPNOTSUPP;
+}
static inline int arm_smmu_liveupdate_restore_strtab(struct arm_smmu_device *smmu)
{
return -ENOENT;
@@ -1242,6 +1250,10 @@ 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_write_ste(struct arm_smmu_master *master, u32 sid,
+ struct arm_smmu_ste *ste,
+ const struct arm_smmu_ste *target);
+
void arm_smmu_domain_tlbi(struct arm_smmu_tlbi *tlbi,
struct arm_smmu_domain *smmu_domain);
--
2.56.0.rc1.315.gc6ed9934b7-goog
^ permalink raw reply [flat|nested] 10+ messages in thread* [RFC PATCH v1 7/9] iommu/arm-smmu-v3: Retain SMMUEN across a Live Update restore
2026-09-29 7:19 [RFC PATCH v1 0/9] iommu/arm-smmu-v3: Implement Live Update support Pranjal Shrivastava
` (5 preceding siblings ...)
2026-09-29 7:19 ` [RFC PATCH v1 6/9] iommu/arm-smmu-v3: Implement Live Update shutdown Pranjal Shrivastava
@ 2026-09-29 7:19 ` Pranjal Shrivastava
2026-09-29 7:19 ` [RFC PATCH v1 8/9] iommu/arm-smmu-v3: Inherit the ASID/VMID of restored domains Pranjal Shrivastava
2026-09-29 7:19 ` [RFC PATCH v1 9/9] iommu/arm-smmu-v3: Adopt the Event queue across a Live Update Pranjal Shrivastava
8 siblings, 0 replies; 10+ messages in thread
From: Pranjal Shrivastava @ 2026-09-29 7:19 UTC (permalink / raw)
To: iommu, Will Deacon, Jason Gunthorpe
Cc: Robin Murphy, Joerg Roedel, Nicolin Chen, Kevin Tian,
Samiullah Khawaja, David Matlack, Vipin Sharma, Mostafa Saleh,
Daniel Mentz, Pasha Tatashin, Pratyush Yadav, linux-arm-kernel,
kexec, linux-kernel, Pranjal Shrivastava
arm_smmu_device_reset() clears SMMUEN and rewrites CR1, CR2 and
STRTAB_BASE, aborting the DMA of the preserved devices. The kdump adoption
path already avoids this for a live Stream Table.
Introduce arm_smmu_strtab_is_live() and route a Live Update restore
through the kdump reset path. Also ack a stale GERROR, since the outgoing
kernel masked the interrupts, and skip installing the RMR bypass STEs.
Signed-off-by: Pranjal Shrivastava <praan@google.com>
---
drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3.c | 29 ++++++++++++++-------
1 file changed, 20 insertions(+), 9 deletions(-)
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 3cf97f451b64..54c97ebc15b2 100644
--- a/drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3.c
+++ b/drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3.c
@@ -5048,6 +5048,13 @@ static void arm_smmu_write_strtab(struct arm_smmu_device *smmu)
writel_relaxed(reg, smmu->base + ARM_SMMU_STRTAB_BASE_CFG);
}
+/* Adopted by kdump or restored by LU (a failed restore fails the probe) */
+static bool arm_smmu_strtab_is_live(struct arm_smmu_device *smmu)
+{
+ return (smmu->options & ARM_SMMU_OPT_KDUMP_ADOPT) ||
+ iommu_get_preserved_data(smmu->base_phys, IOMMU_ARM_SMMUV3);
+}
+
static int arm_smmu_device_reset(struct arm_smmu_device *smmu)
{
int ret;
@@ -5064,10 +5071,11 @@ static int arm_smmu_device_reset(struct arm_smmu_device *smmu)
* According to spec, updating STRTAB_BASE/CR1/CR2 when CR0_SMMUEN=1 is
* CONSTRAINED UNPREDICTABLE. So, skip those register updates and rely
* on the adopted stream table from the crashed kernel.
+ * Same for a Live Update restore.
*/
- if (smmu->options & ARM_SMMU_OPT_KDUMP_ADOPT) {
- dev_info(smmu->dev,
- "kdump: retaining SMMUEN for in-flight DMA\n");
+ if (arm_smmu_strtab_is_live(smmu)) {
+ dev_info(smmu->dev, "%s: retaining SMMUEN for in-flight DMA\n",
+ is_kdump_kernel() ? "kdump" : "live update");
enables = reg & ~(CR0_CMDQEN | CR0_EVTQEN | CR0_PRIQEN);
goto reset_queues;
}
@@ -5103,7 +5111,7 @@ static int arm_smmu_device_reset(struct arm_smmu_device *smmu)
arm_smmu_write_strtab(smmu);
reset_queues:
- if (smmu->options & ARM_SMMU_OPT_KDUMP_ADOPT) {
+ if (arm_smmu_strtab_is_live(smmu)) {
/*
* Disable queues since arm_smmu_device_disable() was skipped.
* CR0 fields are independent per spec, so the queue enable bits
@@ -5122,8 +5130,9 @@ static int arm_smmu_device_reset(struct arm_smmu_device *smmu)
* errors would be visible. Ack everything prior to re-enabling the CMDQ
* as a stale CMDQ_ERR would halt the CMDQ and new command will timeout.
* Acking SFM_ERR is defined too, although it would not exit the SFM.
+ * Same for a Live Update, as the outgoing kernel masked the interrupts.
*/
- if (is_kdump_kernel()) {
+ if (is_kdump_kernel() || arm_smmu_strtab_is_live(smmu)) {
u32 gerror = readl_relaxed(smmu->base + ARM_SMMU_GERROR);
u32 gerrorn = readl_relaxed(smmu->base + ARM_SMMU_GERRORN);
@@ -5193,10 +5202,10 @@ static int arm_smmu_device_reset(struct arm_smmu_device *smmu)
}
/*
- * In a kdump adopt case, retain the crashed kernel's ATS-check policy
- * captured above rather than forcing it on.
+ * In a kdump adopt or a Live Update restore case, retain the previous
+ * kernel's ATS-check policy captured above rather than forcing it on.
*/
- if (!(smmu->options & ARM_SMMU_OPT_KDUMP_ADOPT) &&
+ if (!arm_smmu_strtab_is_live(smmu) &&
(smmu->features & ARM_SMMU_FEAT_ATS)) {
enables |= CR0_ATSCHK;
ret = arm_smmu_write_reg_sync(smmu, enables, ARM_SMMU_CR0,
@@ -5722,8 +5731,10 @@ static void arm_smmu_rmr_install_bypass_ste(struct arm_smmu_device *smmu)
* Kdump adoption keeps the crashed kernel's table live. Rewriting the
* adopted STE here could expose an in-flight fetch to a transient V=0
* entry, or change Cfg=translate to Cfg=bypass. Must skip here.
+ * Same for a Live Update restore.
+ * TODO: Re-install the bypass STEs of the unpreserved RMR SIDs.
*/
- if (smmu->options & ARM_SMMU_OPT_KDUMP_ADOPT)
+ if (arm_smmu_strtab_is_live(smmu))
return;
INIT_LIST_HEAD(&rmr_list);
--
2.56.0.rc1.315.gc6ed9934b7-goog
^ permalink raw reply [flat|nested] 10+ messages in thread* [RFC PATCH v1 8/9] iommu/arm-smmu-v3: Inherit the ASID/VMID of restored domains
2026-09-29 7:19 [RFC PATCH v1 0/9] iommu/arm-smmu-v3: Implement Live Update support Pranjal Shrivastava
` (6 preceding siblings ...)
2026-09-29 7:19 ` [RFC PATCH v1 7/9] iommu/arm-smmu-v3: Retain SMMUEN across a Live Update restore Pranjal Shrivastava
@ 2026-09-29 7:19 ` Pranjal Shrivastava
2026-09-29 7:19 ` [RFC PATCH v1 9/9] iommu/arm-smmu-v3: Adopt the Event queue across a Live Update Pranjal Shrivastava
8 siblings, 0 replies; 10+ messages in thread
From: Pranjal Shrivastava @ 2026-09-29 7:19 UTC (permalink / raw)
To: iommu, Will Deacon, Jason Gunthorpe
Cc: Robin Murphy, Joerg Roedel, Nicolin Chen, Kevin Tian,
Samiullah Khawaja, David Matlack, Vipin Sharma, Mostafa Saleh,
Daniel Mentz, Pasha Tatashin, Pratyush Yadav, linux-arm-kernel,
kexec, linux-kernel, Pranjal Shrivastava
The IOMMU core restores a preserved domain by allocating a new paging
domain around the preserved page table and attaching it to the restored
device. The new domain gets a new ASID/VMID though, while the device is
still doing DMA through the preserved CD/STE.
Similar to the Intel driver reclaiming the preserved Domain ID, record
the ASID/VMID in the attachment_id and let a restored domain inherit the
live ASID/VMID on its first attach. Check the stage, the page table root
and the ID against the live CD/STE, failing the attach on a mismatch.
Signed-off-by: Pranjal Shrivastava <praan@google.com>
---
.../arm/arm-smmu-v3/arm-smmu-v3-liveupdate.c | 115 ++++++++++++++++++
drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3.c | 6 +
drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3.h | 8 ++
3 files changed, 129 insertions(+)
diff --git a/drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3-liveupdate.c b/drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3-liveupdate.c
index 68652123d0e1..c0772bcb8d3a 100644
--- a/drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3-liveupdate.c
+++ b/drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3-liveupdate.c
@@ -273,6 +273,11 @@ int arm_smmu_preserve_device(struct device *dev,
/* Link this master to the preserved IOMMU domain in the ABI */
device_ser->domain_iommu_ser.domain_phys = virt_to_phys(domain_ser);
+ /* Record the ASID/VMID for the incoming kernel to cross-check */
+ device_ser->domain_iommu_ser.attachment_id =
+ smmu_domain->stage == ARM_SMMU_DOMAIN_S1 ?
+ smmu_domain->cd.asid : smmu_domain->s2_cfg.vmid;
+
/* If it's not Stage-1, or the CD table isn't allocated, we're done */
if (smmu_domain->stage != ARM_SMMU_DOMAIN_S1 ||
!arm_smmu_cdtab_allocated(&master->cd_table))
@@ -781,4 +786,114 @@ int arm_smmu_liveupdate_restore_cd_tables(struct arm_smmu_master *master)
return ret;
}
+/* Hand over the reserved ASID/VMID, releasing the never programmed one */
+static int arm_smmu_liveupdate_inherit_id(struct arm_smmu_domain *smmu_domain,
+ u32 id)
+{
+ struct arm_smmu_device *smmu = smmu_domain->smmu;
+ int ret;
+
+ if (smmu_domain->stage == ARM_SMMU_DOMAIN_S1) {
+ if (smmu_domain->cd.asid == id)
+ return 0;
+
+ /* Only a reserved entry, which loads as NULL, can be taken */
+ if (xa_load(&smmu->asid_map, id))
+ return -EBUSY;
+ ret = xa_err(xa_store(&smmu->asid_map, id, smmu_domain,
+ GFP_KERNEL));
+ if (ret)
+ return ret;
+ xa_erase(&smmu->asid_map, smmu_domain->cd.asid);
+ smmu_domain->cd.asid = id;
+ return 0;
+ }
+
+ if (smmu_domain->s2_cfg.vmid == id)
+ return 0;
+
+ /* The vmid_map already holds @id, simply transfer its ownership */
+ ida_free(&smmu->vmid_map, smmu_domain->s2_cfg.vmid);
+ smmu_domain->s2_cfg.vmid = id;
+ return 0;
+}
+
+/*
+ * Inherit the live ASID/VMID of a restored master, after checking that
+ * @smmu_domain matches its live translation. Called before
+ * arm_smmu_attach_prepare() builds the invalidation array.
+ */
+int arm_smmu_liveupdate_attach_restored(struct arm_smmu_master *master,
+ struct arm_smmu_domain *smmu_domain)
+{
+ struct iommu_device_ser *dev_ser = dev_iommu_restored_state(master->dev);
+ struct arm_smmu_device *smmu = master->smmu;
+ struct pt_iommu_armv8_hw_info info;
+ struct arm_smmu_ste *ste;
+ u64 ste0, ttb, live_ttb;
+ u32 id;
+
+ lockdep_assert_held(&arm_smmu_asid_lock);
+
+ if (!dev_ser || !iommu_domain_restored_state(&smmu_domain->domain))
+ return 0;
+
+ ste = arm_smmu_get_step_for_sid(smmu, master->streams[0].id);
+ ste0 = le64_to_cpu(ste->data[0]);
+ if (!(ste0 & STRTAB_STE_0_V))
+ return 0;
+
+ pt_iommu_armv8_hw_info(&smmu_domain->armv8pt, &info);
+
+ switch (FIELD_GET(STRTAB_STE_0_CFG, ste0)) {
+ case STRTAB_STE_0_CFG_S1_TRANS: {
+ struct arm_smmu_cd *cdptr;
+ u64 cd0;
+
+ if (smmu_domain->stage != ARM_SMMU_DOMAIN_S1)
+ goto err_mismatch;
+
+ cdptr = arm_smmu_get_cd_ptr(master, IOMMU_NO_PASID);
+ if (!cdptr)
+ goto err_mismatch;
+
+ cd0 = le64_to_cpu(cdptr->data[0]);
+ if (!(cd0 & CTXDESC_CD_0_V))
+ goto err_mismatch;
+
+ id = FIELD_GET(CTXDESC_CD_0_ASID, cd0);
+ live_ttb = le64_to_cpu(cdptr->data[1]) & CTXDESC_CD_1_TTB0_MASK;
+ ttb = info.ttb & CTXDESC_CD_1_TTB0_MASK;
+ break;
+ }
+ case STRTAB_STE_0_CFG_S2_TRANS:
+ if (smmu_domain->stage != ARM_SMMU_DOMAIN_S2)
+ goto err_mismatch;
+
+ id = FIELD_GET(STRTAB_STE_2_S2VMID, le64_to_cpu(ste->data[2]));
+ live_ttb = le64_to_cpu(ste->data[3]) & STRTAB_STE_3_S2TTB_MASK;
+ ttb = info.ttb & STRTAB_STE_3_S2TTB_MASK;
+ break;
+ default:
+ /* Nothing to inherit, nested STEs aren't restored (yet) */
+ return 0;
+ }
+
+ if (live_ttb != ttb || id != dev_ser->domain_iommu_ser.attachment_id)
+ goto err_mismatch;
+
+ /* A shared restored domain inherits its ID on the first attach */
+ if (!list_empty(&smmu_domain->devices) &&
+ id != (smmu_domain->stage == ARM_SMMU_DOMAIN_S1 ?
+ smmu_domain->cd.asid : smmu_domain->s2_cfg.vmid))
+ goto err_mismatch;
+
+ return arm_smmu_liveupdate_inherit_id(smmu_domain, id);
+
+err_mismatch:
+ dev_err(master->dev,
+ "restored domain doesn't match the live translation\n");
+ return -EINVAL;
+}
+
#endif
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 54c97ebc15b2..b371ea6b6009 100644
--- a/drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3.c
+++ b/drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3.c
@@ -3838,6 +3838,12 @@ static int arm_smmu_attach_dev(struct iommu_domain *domain, struct device *dev,
*/
mutex_lock(&arm_smmu_asid_lock);
+ ret = arm_smmu_liveupdate_attach_restored(master, smmu_domain);
+ if (ret) {
+ mutex_unlock(&arm_smmu_asid_lock);
+ return ret;
+ }
+
ret = arm_smmu_attach_prepare(&state, domain);
if (ret) {
mutex_unlock(&arm_smmu_asid_lock);
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 0a88c0ec66ca..1b33f713f3b7 100644
--- a/drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3.h
+++ b/drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3.h
@@ -1214,6 +1214,8 @@ void arm_smmu_unpreserve(struct iommu_device *iommu,
int arm_smmu_liveupdate_shutdown(struct arm_smmu_device *smmu);
int arm_smmu_liveupdate_restore_strtab(struct arm_smmu_device *smmu);
int arm_smmu_liveupdate_restore_cd_tables(struct arm_smmu_master *master);
+int arm_smmu_liveupdate_attach_restored(struct arm_smmu_master *master,
+ struct arm_smmu_domain *smmu_domain);
#else
static inline int arm_smmu_liveupdate_shutdown(struct arm_smmu_device *smmu)
{
@@ -1227,6 +1229,12 @@ static inline int arm_smmu_liveupdate_restore_cd_tables(struct arm_smmu_master *
{
return 0;
}
+static inline int
+arm_smmu_liveupdate_attach_restored(struct arm_smmu_master *master,
+ struct arm_smmu_domain *smmu_domain)
+{
+ return 0;
+}
#endif
static inline void arm_smmu_domain_free(struct arm_smmu_domain *smmu_domain)
--
2.56.0.rc1.315.gc6ed9934b7-goog
^ permalink raw reply [flat|nested] 10+ messages in thread* [RFC PATCH v1 9/9] iommu/arm-smmu-v3: Adopt the Event queue across a Live Update
2026-09-29 7:19 [RFC PATCH v1 0/9] iommu/arm-smmu-v3: Implement Live Update support Pranjal Shrivastava
` (7 preceding siblings ...)
2026-09-29 7:19 ` [RFC PATCH v1 8/9] iommu/arm-smmu-v3: Inherit the ASID/VMID of restored domains Pranjal Shrivastava
@ 2026-09-29 7:19 ` Pranjal Shrivastava
8 siblings, 0 replies; 10+ messages in thread
From: Pranjal Shrivastava @ 2026-09-29 7:19 UTC (permalink / raw)
To: iommu, Will Deacon, Jason Gunthorpe
Cc: Robin Murphy, Joerg Roedel, Nicolin Chen, Kevin Tian,
Samiullah Khawaja, David Matlack, Vipin Sharma, Mostafa Saleh,
Daniel Mentz, Pasha Tatashin, Pratyush Yadav, linux-arm-kernel,
kexec, linux-kernel, Pranjal Shrivastava
The SMMU may record events of the preserved devices across the kexec,
which the incoming kernel loses by resetting the EVTQ.
Preserve the EVTQ memory and leave the EVTQ enabled on shutdown. Only
store its preservation token in the ABI, as the incoming kernel reads the
base, size, PROD and CONS back from the EVTQ registers. Retain EVTQEN
across the reset and wake up the EVTQ thread to handle pending events.
Signed-off-by: Pranjal Shrivastava <praan@google.com>
---
.../arm/arm-smmu-v3/arm-smmu-v3-liveupdate.c | 108 +++++++++++++++++-
drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3.c | 39 +++++--
drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3.h | 10 ++
include/linux/kho/abi/iommu.h | 2 +
4 files changed, 143 insertions(+), 16 deletions(-)
diff --git a/drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3-liveupdate.c b/drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3-liveupdate.c
index c0772bcb8d3a..b7cacf48cf8b 100644
--- a/drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3-liveupdate.c
+++ b/drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3-liveupdate.c
@@ -403,11 +403,48 @@ static void arm_smmu_unpreserve_strtab_linear(struct arm_smmu_device *smmu,
iommu_ser->smmuv3.l1_strtab_lu_state);
}
+static size_t arm_smmu_evtq_size(struct arm_smmu_device *smmu)
+{
+ return ((1 << smmu->evtq.q.llq.max_n_shift) * EVTQ_ENT_DWORDS) << 3;
+}
+
+/* The EVTQ stays enabled across the kexec */
+static int arm_smmu_preserve_evtq(struct arm_smmu_device *smmu,
+ struct iommu_hw_ser *iommu_ser)
+{
+ struct arm_smmu_queue *q = &smmu->evtq.q;
+
+ iommu_ser->smmuv3.evtq_lu_state = 0;
+ if (!(smmu->features & ARM_SMMU_FEAT_EVTQ))
+ return 0;
+
+ return dmam_preserve_coherent_allocation(smmu->dev, q->base,
+ arm_smmu_evtq_size(smmu),
+ q->base_dma,
+ &iommu_ser->smmuv3.evtq_lu_state);
+}
+
+static void arm_smmu_unpreserve_evtq(struct arm_smmu_device *smmu,
+ struct iommu_hw_ser *iommu_ser)
+{
+ struct arm_smmu_queue *q = &smmu->evtq.q;
+
+ if (!iommu_ser->smmuv3.evtq_lu_state)
+ return;
+
+ dmam_unpreserve_coherent_allocation(smmu->dev, q->base,
+ arm_smmu_evtq_size(smmu),
+ q->base_dma,
+ iommu_ser->smmuv3.evtq_lu_state);
+ iommu_ser->smmuv3.evtq_lu_state = 0;
+}
+
int arm_smmu_preserve(struct iommu_device *iommu,
struct iommu_hw_ser *iommu_ser)
{
struct arm_smmu_device *smmu =
container_of(iommu, struct arm_smmu_device, iommu);
+ int ret;
/* Basic info */
iommu_ser->smmuv3.phys_addr = smmu->base_phys;
@@ -416,11 +453,20 @@ int arm_smmu_preserve(struct iommu_device *iommu,
iommu_ser->smmuv3.strtab_base_cfg =
readl_relaxed(smmu->base + ARM_SMMU_STRTAB_BASE_CFG);
+ ret = arm_smmu_preserve_evtq(smmu, iommu_ser);
+ if (ret) {
+ dev_err(smmu->dev, "EVTQ preservation failed\n");
+ return ret;
+ }
+
/* We always implements 2-level when supported by HW */
if (smmu->features & ARM_SMMU_FEAT_2_LVL_STRTAB)
- return arm_smmu_preserve_strtab_2lvl(smmu, iommu_ser);
+ ret = arm_smmu_preserve_strtab_2lvl(smmu, iommu_ser);
else
- return arm_smmu_preserve_strtab_linear(smmu, iommu_ser);
+ ret = arm_smmu_preserve_strtab_linear(smmu, iommu_ser);
+ if (ret)
+ arm_smmu_unpreserve_evtq(smmu, iommu_ser);
+ return ret;
}
void arm_smmu_unpreserve(struct iommu_device *iommu,
@@ -433,6 +479,7 @@ void arm_smmu_unpreserve(struct iommu_device *iommu,
arm_smmu_unpreserve_strtab_2lvl(smmu, iommu_ser);
else
arm_smmu_unpreserve_strtab_linear(smmu, iommu_ser);
+ arm_smmu_unpreserve_evtq(smmu, iommu_ser);
}
static void arm_smmu_liveupdate_clear_l1_std(struct arm_smmu_device *smmu,
@@ -545,12 +592,12 @@ int arm_smmu_liveupdate_shutdown(struct arm_smmu_device *smmu)
* TODO: Quiesce the CMDQV VCMDQs assigned to guests.
*/
- /* Disable the queues, leaving SMMUEN set for the preserved masters */
+ /* The incoming kernel resets the CMDQ and PRIQ and adopts the EVTQ */
cr0 = readl_relaxed(smmu->base + ARM_SMMU_CR0);
- cr0 &= ~(CR0_CMDQEN | CR0_EVTQEN | CR0_PRIQEN);
+ cr0 &= ~(CR0_CMDQEN | CR0_PRIQEN);
ret = arm_smmu_write_reg_sync(smmu, cr0, ARM_SMMU_CR0, ARM_SMMU_CR0ACK);
if (ret)
- dev_err(smmu->dev, "failed to disable queues\n");
+ dev_err(smmu->dev, "failed to disable CMDQ/PRIQ\n");
return ret;
}
@@ -696,6 +743,57 @@ int arm_smmu_liveupdate_restore_strtab(struct arm_smmu_device *smmu)
return 0;
}
+/* Adopt the live EVTQ. Returns -ENOENT if it wasn't preserved */
+int arm_smmu_liveupdate_restore_evtq(struct arm_smmu_device *smmu)
+{
+ u64 base = readq_relaxed(smmu->base + ARM_SMMU_EVTQ_BASE);
+ u32 log2size = FIELD_GET(Q_BASE_LOG2SIZE, base);
+ struct arm_smmu_queue *q = &smmu->evtq.q;
+ struct iommu_hw_ser *iommu_ser;
+
+ iommu_ser = iommu_get_preserved_data(smmu->base_phys, IOMMU_ARM_SMMUV3);
+ if (!iommu_ser || !iommu_ser->smmuv3.evtq_lu_state)
+ return -ENOENT;
+
+ if (log2size > q->llq.max_n_shift) {
+ dev_err(smmu->dev, "preserved EVTQ is larger than supported\n");
+ return -EINVAL;
+ }
+ q->llq.max_n_shift = log2size;
+
+ q->base = dmam_restore_coherent_allocation(smmu->dev,
+ arm_smmu_evtq_size(smmu), &q->base_dma, GFP_KERNEL,
+ iommu_ser->smmuv3.evtq_lu_state);
+ if (!q->base)
+ return -ENOMEM;
+
+ if (q->base_dma != (base & Q_BASE_ADDR_MASK)) {
+ dev_err(smmu->dev, "EVTQ_BASE doesn't match the preserved EVTQ\n");
+ return -EINVAL;
+ }
+
+ q->prod_reg = smmu->page1 + ARM_SMMU_EVTQ_PROD;
+ q->cons_reg = smmu->page1 + ARM_SMMU_EVTQ_CONS;
+ q->ent_dwords = EVTQ_ENT_DWORDS;
+ q->q_base = base;
+
+ q->llq.prod = readl_relaxed(q->prod_reg);
+ q->llq.cons = readl_relaxed(q->cons_reg);
+
+ dev_info(smmu->dev, "restored preserved evtq (%u entries)\n",
+ 1 << q->llq.max_n_shift);
+ return 0;
+}
+
+/* A failed EVTQ restore fails the probe, so a preserved EVTQ implies live */
+bool arm_smmu_liveupdate_evtq_is_live(struct arm_smmu_device *smmu)
+{
+ struct iommu_hw_ser *iommu_ser;
+
+ iommu_ser = iommu_get_preserved_data(smmu->base_phys, IOMMU_ARM_SMMUV3);
+ return iommu_ser && iommu_ser->smmuv3.evtq_lu_state;
+}
+
int arm_smmu_liveupdate_restore_cd_tables(struct arm_smmu_master *master)
{
struct arm_smmu_device *smmu = master->smmu;
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 b371ea6b6009..5f9f8bfac668 100644
--- a/drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3.c
+++ b/drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3.c
@@ -4683,10 +4683,13 @@ static int arm_smmu_init_queues(struct arm_smmu_device *smmu)
/* evtq */
if (smmu->features & ARM_SMMU_FEAT_EVTQ) {
- ret = arm_smmu_init_one_queue(smmu, &smmu->evtq.q, smmu->page1,
- ARM_SMMU_EVTQ_PROD,
- ARM_SMMU_EVTQ_CONS,
- EVTQ_ENT_DWORDS, "evtq");
+ ret = arm_smmu_liveupdate_restore_evtq(smmu);
+ if (ret == -ENOENT)
+ ret = arm_smmu_init_one_queue(smmu, &smmu->evtq.q,
+ smmu->page1,
+ ARM_SMMU_EVTQ_PROD,
+ ARM_SMMU_EVTQ_CONS,
+ EVTQ_ENT_DWORDS, "evtq");
if (ret)
return ret;
}
@@ -5080,9 +5083,15 @@ static int arm_smmu_device_reset(struct arm_smmu_device *smmu)
* Same for a Live Update restore.
*/
if (arm_smmu_strtab_is_live(smmu)) {
+ u32 qens = CR0_CMDQEN | CR0_EVTQEN | CR0_PRIQEN;
+
dev_info(smmu->dev, "%s: retaining SMMUEN for in-flight DMA\n",
is_kdump_kernel() ? "kdump" : "live update");
- enables = reg & ~(CR0_CMDQEN | CR0_EVTQEN | CR0_PRIQEN);
+
+ /* An adopted EVTQ keeps running */
+ if (arm_smmu_liveupdate_evtq_is_live(smmu))
+ qens &= ~CR0_EVTQEN;
+ enables = reg & ~qens;
goto reset_queues;
}
@@ -5173,12 +5182,15 @@ static int arm_smmu_device_reset(struct arm_smmu_device *smmu)
/* Event queue */
if (smmu->features & ARM_SMMU_FEAT_EVTQ) {
- writeq_relaxed(smmu->evtq.q.q_base,
- smmu->base + ARM_SMMU_EVTQ_BASE);
- writel_relaxed(smmu->evtq.q.llq.prod,
- smmu->page1 + ARM_SMMU_EVTQ_PROD);
- writel_relaxed(smmu->evtq.q.llq.cons,
- smmu->page1 + ARM_SMMU_EVTQ_CONS);
+ /* An adopted EVTQ resumes from its live BASE/PROD/CONS */
+ if (!arm_smmu_liveupdate_evtq_is_live(smmu)) {
+ writeq_relaxed(smmu->evtq.q.q_base,
+ smmu->base + ARM_SMMU_EVTQ_BASE);
+ writel_relaxed(smmu->evtq.q.llq.prod,
+ smmu->page1 + ARM_SMMU_EVTQ_PROD);
+ writel_relaxed(smmu->evtq.q.llq.cons,
+ smmu->page1 + ARM_SMMU_EVTQ_CONS);
+ }
enables |= CR0_EVTQEN;
ret = arm_smmu_write_reg_sync(smmu, enables, ARM_SMMU_CR0,
@@ -5228,6 +5240,11 @@ static int arm_smmu_device_reset(struct arm_smmu_device *smmu)
return ret;
}
+ /* Handle the events recorded across the Live Update */
+ if (arm_smmu_liveupdate_evtq_is_live(smmu) &&
+ (smmu->combined_irq || smmu->evtq.q.irq))
+ irq_wake_thread(smmu->combined_irq ?: smmu->evtq.q.irq, smmu);
+
/* Enable the SMMU interface */
enables |= CR0_SMMUEN;
ret = arm_smmu_write_reg_sync(smmu, enables, ARM_SMMU_CR0,
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 1b33f713f3b7..3a67ba685ef5 100644
--- a/drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3.h
+++ b/drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3.h
@@ -1214,6 +1214,8 @@ void arm_smmu_unpreserve(struct iommu_device *iommu,
int arm_smmu_liveupdate_shutdown(struct arm_smmu_device *smmu);
int arm_smmu_liveupdate_restore_strtab(struct arm_smmu_device *smmu);
int arm_smmu_liveupdate_restore_cd_tables(struct arm_smmu_master *master);
+int arm_smmu_liveupdate_restore_evtq(struct arm_smmu_device *smmu);
+bool arm_smmu_liveupdate_evtq_is_live(struct arm_smmu_device *smmu);
int arm_smmu_liveupdate_attach_restored(struct arm_smmu_master *master,
struct arm_smmu_domain *smmu_domain);
#else
@@ -1229,6 +1231,14 @@ static inline int arm_smmu_liveupdate_restore_cd_tables(struct arm_smmu_master *
{
return 0;
}
+static inline int arm_smmu_liveupdate_restore_evtq(struct arm_smmu_device *smmu)
+{
+ return -ENOENT;
+}
+static inline bool arm_smmu_liveupdate_evtq_is_live(struct arm_smmu_device *smmu)
+{
+ return false;
+}
static inline int
arm_smmu_liveupdate_attach_restored(struct arm_smmu_master *master,
struct arm_smmu_domain *smmu_domain)
diff --git a/include/linux/kho/abi/iommu.h b/include/linux/kho/abi/iommu.h
index 397fdb0449a6..4e8cc32c3136 100644
--- a/include/linux/kho/abi/iommu.h
+++ b/include/linux/kho/abi/iommu.h
@@ -208,12 +208,14 @@ struct iommu_intel_ser {
* @l2_strtab_lu_states_phys: Physical pointer to an array of u64 LU state tokens
* indexed by L1 index, 0 for L2 tables that aren't
* preserved (0 if linear)
+ * @evtq_lu_state: Live update state token for the Event queue (0 if not preserved)
*/
struct iommu_smmuv3_hw_ser {
u64 phys_addr;
u64 strtab_base_cfg;
u64 l1_strtab_lu_state;
u64 l2_strtab_lu_states_phys;
+ u64 evtq_lu_state;
} __packed;
/**
--
2.56.0.rc1.315.gc6ed9934b7-goog
^ permalink raw reply [flat|nested] 10+ messages in thread