From: Yicong Yang ARCH_HAS_CPU_CACHE_INVALIDATE_MEMREGION provides the mechanism for invalidating certain memory regions in a cache-incoherent manner. Currently this is used by NVDIMM and CXL memory drivers in cases where it is necessary to flush all data from caches by physical address range. In some architectures these operations are supported by system components that may become available only later in boot as they are either present on a discoverable bus, or via a firmware description of an MMIO interface (e.g. ACPI DSDT). Provide a framework to handle this case. Architectures can opt in for this support via CONFIG_GENERIC_CPU_CACHE_MAINTENANCE Add a registration framework. Each driver provides an ops structure and the first op is Write Back and Invalidate by PA Range. The driver may over invalidate. An optional completion check operation is also provided. If present that should be called to ensure that the action has finished. When multiple agents are present in the system each should register with this framework and the core code will issue the invalidate to all of them before checking for completion on each. This is done to avoid need for filtering in the core code which can become complex when interleave, potentially across different cache coherency hardware is going on, so it is easier to tell everyone and let those who don't care do nothing. Signed-off-by: Yicong Yang Co-developed-by: Jonathan Cameron Signed-off-by: Jonathan Cameron --- v3: Squash all the layering from v2 so that the infrastucture is always present. Suggestions on naming welcome. Note that the hardware I have available supports a much richer set of maintenance operations than Write Back and Invalidate, so I'd like a name that covers all resonable maintenance operations. Use an allocation wrapper macro, based on the fwctl one to ensure that the first element of the allocated driver structure is a struct cache_coherency_device. Thanks to all who provided feedback. --- include/linux/cache_coherency.h | 57 ++++++++++++++ lib/Kconfig | 3 + lib/Makefile | 2 + lib/cache_maint.c | 128 ++++++++++++++++++++++++++++++++ 4 files changed, 190 insertions(+) diff --git a/include/linux/cache_coherency.h b/include/linux/cache_coherency.h new file mode 100644 index 000000000000..cb195b17b6e6 --- /dev/null +++ b/include/linux/cache_coherency.h @@ -0,0 +1,57 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* + * Cache coherency maintenace operation device drivers + * + * Copyright Huawei 2025 + */ +#ifndef _LINUX_CACHE_COHERENCY_H_ +#define _LINUX_CACHE_COHERENCY_H_ + +#include +#include + +struct cc_inval_params { + phys_addr_t addr; + size_t size; +}; + +struct cache_coherency_device; + +struct coherency_ops { + int (*wbinv)(struct cache_coherency_device *ccd, struct cc_inval_params *invp); + int (*done)(struct cache_coherency_device *ccd); +}; + +struct cache_coherency_device { + struct list_head node; + const struct coherency_ops *ops; +}; + +int cache_coherency_device_register(struct cache_coherency_device *ccd); +void cache_coherency_device_unregister(struct cache_coherency_device *ccd); + +struct cache_coherency_device * +_cache_coherency_device_alloc(const struct coherency_ops *ops, size_t size); +/** + * cache_coherency_device_alloc - Allocate a cache coherency device + * @ops: Cache maintenance operations + * @drv_struct: structure that contains the struct cache_coherency_device + * @member: Name of the struct cache_coherency_device member in @drv_struct. + * + * This allocates and initializes the cache_coherency_device embedded in the + * drv_struct. Upon success the pointer must be freed via + * cache_coherency_device_free(). + * + * Returns a 'drv_struct \*' on success, NULL on error. + */ +#define cache_coherency_device_alloc(ops, drv_struct, member) \ + ({ \ + static_assert(__same_type(struct cache_coherency_device, \ + ((drv_struct *)NULL)->member)); \ + static_assert(offsetof(drv_struct, member) == 0); \ + (drv_struct *)_cache_coherency_device_alloc(ops, \ + sizeof(drv_struct)); \ + }) +void cache_coherency_device_free(struct cache_coherency_device *ccd); + +#endif diff --git a/lib/Kconfig b/lib/Kconfig index c483951b624f..cd8e5844f9bb 100644 --- a/lib/Kconfig +++ b/lib/Kconfig @@ -543,6 +543,9 @@ config MEMREGION config ARCH_HAS_CPU_CACHE_INVALIDATE_MEMREGION bool +config GENERIC_CPU_CACHE_MAINTENANCE + bool + config ARCH_HAS_MEMREMAP_COMPAT_ALIGN bool diff --git a/lib/Makefile b/lib/Makefile index 392ff808c9b9..eed20c50f358 100644 --- a/lib/Makefile +++ b/lib/Makefile @@ -130,6 +130,8 @@ obj-$(CONFIG_HAS_IOMEM) += iomap_copy.o devres.o obj-$(CONFIG_CHECK_SIGNATURE) += check_signature.o obj-$(CONFIG_DEBUG_LOCKING_API_SELFTESTS) += locking-selftest.o +obj-$(CONFIG_GENERIC_CPU_CACHE_MAINTENANCE) += cache_maint.o + lib-y += logic_pio.o lib-$(CONFIG_INDIRECT_IOMEM) += logic_iomem.o diff --git a/lib/cache_maint.c b/lib/cache_maint.c new file mode 100644 index 000000000000..05d9c5e99941 --- /dev/null +++ b/lib/cache_maint.c @@ -0,0 +1,128 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Generic support for Memory System Cache Maintenance operations. + * + * Coherency maintenance drivers register with this simple framework that will + * iterate over each registered instance to first kick off invalidation and + * then to wait until it is complete. + * + * If no implementations are registered yet cpu_cache_has_invalidate_memregion() + * will return false. If this runs concurrently with unregistration then a + * race exists but this is no worse than the case where the device responsible + * for a given memory region has not yet registered. + */ +#include +#include +#include +#include +#include +#include +#include +#include +#include + +static LIST_HEAD(cache_device_list); +static DECLARE_RWSEM(cache_device_list_lock); + +void cache_coherency_device_free(struct cache_coherency_device *ccd) +{ + kfree(ccd); +} +EXPORT_SYMBOL_GPL(cache_coherency_device_free); + +static int cache_inval_one(struct cache_coherency_device *ccd, void *data) +{ + if (!ccd->ops) + return -EINVAL; + + return ccd->ops->wbinv(ccd, data); +} + +static int cache_inval_done_one(struct cache_coherency_device *ccd) +{ + if (!ccd->ops) + return -EINVAL; + + if (!ccd->ops->done) + return 0; + + return ccd->ops->done(ccd); +} + +static int cache_invalidate_memregion(phys_addr_t addr, size_t size) +{ + int ret; + struct cache_coherency_device *ccd; + struct cc_inval_params params = { + .addr = addr, + .size = size, + }; + + guard(rwsem_read)(&cache_device_list_lock); + list_for_each_entry(ccd, &cache_device_list, node) { + ret = cache_inval_one(ccd, ¶ms); + if (ret) + return ret; + } + list_for_each_entry(ccd, &cache_device_list, node) { + ret = cache_inval_done_one(ccd); + if (ret) + return ret; + } + + return 0; +} + +struct cache_coherency_device * +_cache_coherency_device_alloc(const struct coherency_ops *ops, size_t size) +{ + struct cache_coherency_device *ccd; + + if (!ops || !ops->wbinv) + return NULL; + + ccd = kzalloc(size, GFP_KERNEL); + if (!ccd) + return NULL; + + ccd->ops = ops; + INIT_LIST_HEAD(&ccd->node); + + return ccd; +} +EXPORT_SYMBOL_NS_GPL(_cache_coherency_device_alloc, "CACHE_COHERENCY"); + +int cache_coherency_device_register(struct cache_coherency_device *ccd) +{ + guard(rwsem_write)(&cache_device_list_lock); + list_add(&ccd->node, &cache_device_list); + + return 0; +} +EXPORT_SYMBOL_NS_GPL(cache_coherency_device_register, "CACHE_COHERENCY"); + +void cache_coherency_device_unregister(struct cache_coherency_device *ccd) +{ + guard(rwsem_write)(&cache_device_list_lock); + list_del(&ccd->node); +} +EXPORT_SYMBOL_NS_GPL(cache_coherency_device_unregister, "CACHE_COHERENCY"); + +int cpu_cache_invalidate_memregion(phys_addr_t start, size_t len) +{ + return cache_invalidate_memregion(start, len); +} +EXPORT_SYMBOL_NS_GPL(cpu_cache_invalidate_memregion, "DEVMEM"); + +/* + * Used for optimization / debug purposes only as removal can race + * + * Machines that do not support invalidation, e.g. VMs, will not + * have any devices to register and so this will always return false. + */ +bool cpu_cache_has_invalidate_memregion(void) +{ + guard(rwsem_read)(&cache_device_list_lock); + return !list_empty(&cache_device_list); +} +EXPORT_SYMBOL_NS_GPL(cpu_cache_has_invalidate_memregion, "DEVMEM"); -- 2.48.1