| // 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 operations instance |
| * responsible for a given memory region has not yet registered. |
| */ |
| #include <linux/cache_coherency.h> |
| #include <linux/cleanup.h> |
| #include <linux/container_of.h> |
| #include <linux/export.h> |
| #include <linux/kref.h> |
| #include <linux/list.h> |
| #include <linux/memregion.h> |
| #include <linux/module.h> |
| #include <linux/rwsem.h> |
| #include <linux/slab.h> |
| |
| static LIST_HEAD(cache_ops_instance_list); |
| static DECLARE_RWSEM(cache_ops_instance_list_lock); |
| |
| static void __cache_coherency_ops_instance_free(struct kref *kref) |
| { |
| struct cache_coherency_ops_inst *cci = |
| container_of(kref, struct cache_coherency_ops_inst, kref); |
| kfree(cci); |
| } |
| |
| void cache_coherency_ops_instance_put(struct cache_coherency_ops_inst *cci) |
| { |
| kref_put(&cci->kref, __cache_coherency_ops_instance_free); |
| } |
| EXPORT_SYMBOL_GPL(cache_coherency_ops_instance_put); |
| |
| static int cache_inval_one(struct cache_coherency_ops_inst *cci, void *data) |
| { |
| if (!cci->ops) |
| return -EINVAL; |
| |
| return cci->ops->wbinv(cci, data); |
| } |
| |
| static int cache_inval_done_one(struct cache_coherency_ops_inst *cci) |
| { |
| if (!cci->ops) |
| return -EINVAL; |
| |
| if (!cci->ops->done) |
| return 0; |
| |
| return cci->ops->done(cci); |
| } |
| |
| static int cache_invalidate_memregion(phys_addr_t addr, size_t size) |
| { |
| int ret; |
| struct cache_coherency_ops_inst *cci; |
| struct cc_inval_params params = { |
| .addr = addr, |
| .size = size, |
| }; |
| |
| guard(rwsem_read)(&cache_ops_instance_list_lock); |
| list_for_each_entry(cci, &cache_ops_instance_list, node) { |
| ret = cache_inval_one(cci, ¶ms); |
| if (ret) |
| return ret; |
| } |
| list_for_each_entry(cci, &cache_ops_instance_list, node) { |
| ret = cache_inval_done_one(cci); |
| if (ret) |
| return ret; |
| } |
| |
| return 0; |
| } |
| |
| struct cache_coherency_ops_inst * |
| _cache_coherency_ops_instance_alloc(const struct cache_coherency_ops *ops, |
| size_t size) |
| { |
| struct cache_coherency_ops_inst *cci; |
| |
| if (!ops || !ops->wbinv) |
| return NULL; |
| |
| cci = kzalloc(size, GFP_KERNEL); |
| if (!cci) |
| return NULL; |
| |
| cci->ops = ops; |
| INIT_LIST_HEAD(&cci->node); |
| kref_init(&cci->kref); |
| |
| return cci; |
| } |
| EXPORT_SYMBOL_NS_GPL(_cache_coherency_ops_instance_alloc, "CACHE_COHERENCY"); |
| |
| int cache_coherency_ops_instance_register(struct cache_coherency_ops_inst *cci) |
| { |
| guard(rwsem_write)(&cache_ops_instance_list_lock); |
| list_add(&cci->node, &cache_ops_instance_list); |
| |
| return 0; |
| } |
| EXPORT_SYMBOL_NS_GPL(cache_coherency_ops_instance_register, "CACHE_COHERENCY"); |
| |
| void cache_coherency_ops_instance_unregister(struct cache_coherency_ops_inst *cci) |
| { |
| guard(rwsem_write)(&cache_ops_instance_list_lock); |
| list_del(&cci->node); |
| } |
| EXPORT_SYMBOL_NS_GPL(cache_coherency_ops_instance_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 |
| * operations instance to register and so this will always return false. |
| */ |
| bool cpu_cache_has_invalidate_memregion(void) |
| { |
| guard(rwsem_read)(&cache_ops_instance_list_lock); |
| return !list_empty(&cache_ops_instance_list); |
| } |
| EXPORT_SYMBOL_NS_GPL(cpu_cache_has_invalidate_memregion, "DEVMEM"); |