[PATCH v3 2/2] cache: add SMCCC-backed cache invalidate provider
From: Srirangan Madhavan <hidden>
Date: 2026-09-10 08:22:12
Also in:
linux-tegra, lkml
Subsystem:
the rest · Maintainer:
Linus Torvalds
Add a cache maintenance provider for the Arm SMCCC cache clean+invalidate interface. The provider discovers SMCCC support and attributes at init time, serializes firmware calls, handles transient BUSY and RATE_LIMITED responses with bounded retries, and registers with the generic cache coherency framework used by memregion callers. Signed-off-by: Srirangan Madhavan <redacted> Reviewed-by: Jonathan Cameron <jic23@kernel.org> --- drivers/cache/Kconfig | 11 +++ drivers/cache/Makefile | 2 + drivers/cache/arm_smccc_cache.c | 149 ++++++++++++++++++++++++++++++++ 3 files changed, 162 insertions(+) create mode 100644 drivers/cache/arm_smccc_cache.c
diff --git a/drivers/cache/Kconfig b/drivers/cache/Kconfig
index 1518449d47b5..57fd1823dec5 100644
--- a/drivers/cache/Kconfig
+++ b/drivers/cache/Kconfig@@ -42,6 +42,17 @@ menuconfig CACHEMAINT_FOR_HOTPLUG if CACHEMAINT_FOR_HOTPLUG +config ARM_SMCCC_CACHE + bool "Arm SMCCC cache maintenance provider" + depends on ARM64 && HAVE_ARM_SMCCC_DISCOVERY + help + Enable support for the Arm SMCCC cache clean+invalidate + interface as a provider for memory hotplug-like cache + maintenance operations. + The provider registers only when firmware advertises the + SMCCC calls and attributes, so systems without firmware support + continue without this registered provider. + config HISI_SOC_HHA tristate "HiSilicon Hydra Home Agent (HHA) device driver" depends on (ARM64 && ACPI) || COMPILE_TEST
diff --git a/drivers/cache/Makefile b/drivers/cache/Makefile
index b3362b15d6c1..55736a032d6f 100644
--- a/drivers/cache/Makefile
+++ b/drivers/cache/Makefile@@ -4,4 +4,6 @@ obj-$(CONFIG_AX45MP_L2_CACHE) += ax45mp_cache.o obj-$(CONFIG_SIFIVE_CCACHE) += sifive_ccache.o obj-$(CONFIG_STARFIVE_STARLINK_CACHE) += starfive_starlink_cache.o +# Providers below depend on CACHEMAINT_FOR_HOTPLUG. +obj-$(CONFIG_ARM_SMCCC_CACHE) += arm_smccc_cache.o obj-$(CONFIG_HISI_SOC_HHA) += hisi_soc_hha.o
diff --git a/drivers/cache/arm_smccc_cache.c b/drivers/cache/arm_smccc_cache.c
new file mode 100644
index 000000000000..1cfa8f8809d0
--- /dev/null
+++ b/drivers/cache/arm_smccc_cache.c@@ -0,0 +1,149 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Copyright (C) 2026 NVIDIA Corporation + * + * Arm SMCCC cache maintenance provider using cache clean+invalidate calls. + */ + +#include <linux/arm-smccc.h> +#include <linux/cache_coherency.h> +#include <linux/cleanup.h> +#include <linux/delay.h> +#include <linux/errno.h> +#include <linux/init.h> +#include <linux/kernel.h> +#include <linux/mutex.h> +#include <linux/nmi.h> + +#define SMCCC_CACHE_MAX_RETRIES 5 +#define SMCCC_CACHE_DEFAULT_DELAY_US 1000UL +#define SMCCC_CACHE_MAX_DELAY_US 20000UL + +struct smccc_cache { + /* Must be first member */ + struct cache_coherency_ops_inst cci; + struct mutex lock; /* Serializes SMCCC cache maintenance calls. */ + u64 rate_limit; + u32 latency_us; +}; + +static int smccc_cache_status_to_errno(s32 status) +{ + switch (status) { + case SMCCC_RET_SUCCESS: + return 0; + case SMCCC_RET_NOT_SUPPORTED: + return -EOPNOTSUPP; + case SMCCC_RET_INVALID_PARAMETER: + return -EINVAL; + case SMCCC_RET_RATE_LIMITED: + return -EAGAIN; + case SMCCC_RET_BUSY: + return -EBUSY; + default: + return -EIO; + } +} + +static unsigned long smccc_cache_delay_us(const struct smccc_cache *cache) +{ + unsigned long delay_us = 0; + + if (cache->rate_limit) + delay_us = DIV_ROUND_UP_ULL(USEC_PER_SEC, cache->rate_limit); + + if (cache->latency_us) + delay_us = max(delay_us, (unsigned long)cache->latency_us); + + /* + * Firmware may advertise neither a rate limit nor a latency hint; use + * a small bounded backoff instead of retrying in a tight loop. + */ + if (!delay_us) + delay_us = SMCCC_CACHE_DEFAULT_DELAY_US; + + return min(delay_us, SMCCC_CACHE_MAX_DELAY_US); +} + +static int smccc_cache_wbinv(struct cache_coherency_ops_inst *cci, + struct cc_inval_params *invp) +{ + struct smccc_cache *cache = container_of(cci, struct smccc_cache, cci); + unsigned long delay_us = smccc_cache_delay_us(cache); + struct arm_smccc_res res = {}; + int ret; + + if (!invp->size) + return -EINVAL; + + /* + * Serialize the full retry sequence. With the default bounds, a caller + * may hold the mutex across up to five 20ms backoff sleeps. + */ + guard(mutex)(&cache->lock); + + for (unsigned int i = 0; i < SMCCC_CACHE_MAX_RETRIES; i++) { + /* Long firmware operations can trigger watchdog checks. */ + touch_nmi_watchdog(); + + arm_smccc_1_1_invoke(ARM_SMCCC_ARCH_CLEAN_INV_MEMREGION, + invp->addr, invp->size, 0UL, &res); + + ret = smccc_cache_status_to_errno((s32)res.a0); + if (!ret) + return 0; + + if (ret != -EBUSY && ret != -EAGAIN) + return ret; + + fsleep(delay_us); + } + + return ret; +} + +static const struct cache_coherency_ops smccc_cache_ops = { + .wbinv = smccc_cache_wbinv, +}; + +static int __init smccc_cache_init(void) +{ + struct arm_smccc_res res = {}; + struct smccc_cache *cache; + int ret; + + if (arm_smccc_get_version() < ARM_SMCCC_VERSION_1_1) + return -ENODEV; + + if (arm_smccc_1_1_get_conduit() == SMCCC_CONDUIT_NONE) + return -ENODEV; + + arm_smccc_1_1_invoke(ARM_SMCCC_ARCH_FEATURES_FUNC_ID, + ARM_SMCCC_ARCH_CLEAN_INV_MEMREGION, &res); + if ((s32)res.a0 < 0) + return -ENODEV; + + arm_smccc_1_1_invoke(ARM_SMCCC_ARCH_CLEAN_INV_MEMREGION_ATTRIBUTES, + &res); + if ((s32)res.a0) + return -ENODEV; + + cache = cache_coherency_ops_instance_alloc(&smccc_cache_ops, + struct smccc_cache, cci); + if (!cache) + return -ENOMEM; + + mutex_init(&cache->lock); + cache->latency_us = lower_32_bits(res.a2); + cache->rate_limit = res.a3; + + ret = cache_coherency_ops_instance_register(&cache->cci); + if (ret) { + mutex_destroy(&cache->lock); + cache_coherency_ops_instance_put(&cache->cci); + return ret; + } + + return 0; +} +arch_initcall(smccc_cache_init);
--
2.43.0