diff --git a/anolis/configs/L1-RECOMMEND/arm64/CONFIG_HISI_SOC_CACHE b/anolis/configs/L1-RECOMMEND/arm64/CONFIG_HISI_SOC_CACHE new file mode 100644 index 0000000000000000000000000000000000000000..0088cbcc8e8683b9dc0c03eaa9a91b6f0c065382 --- /dev/null +++ b/anolis/configs/L1-RECOMMEND/arm64/CONFIG_HISI_SOC_CACHE @@ -0,0 +1 @@ +CONFIG_HISI_SOC_CACHE=y \ No newline at end of file diff --git a/anolis/configs/L1-RECOMMEND/arm64/CONFIG_HISI_SOC_HHA b/anolis/configs/L1-RECOMMEND/arm64/CONFIG_HISI_SOC_HHA new file mode 100644 index 0000000000000000000000000000000000000000..489346ed5871ab0855d41413f525c86a05a73447 --- /dev/null +++ b/anolis/configs/L1-RECOMMEND/arm64/CONFIG_HISI_SOC_HHA @@ -0,0 +1 @@ +CONFIG_HISI_SOC_HHA=m \ No newline at end of file diff --git a/anolis/configs/L1-RECOMMEND/arm64/CONFIG_HISI_SOC_L3C b/anolis/configs/L1-RECOMMEND/arm64/CONFIG_HISI_SOC_L3C new file mode 100644 index 0000000000000000000000000000000000000000..c0e33a62aeeb89d4a53df909b9c8324e2dc573a4 --- /dev/null +++ b/anolis/configs/L1-RECOMMEND/arm64/CONFIG_HISI_SOC_L3C @@ -0,0 +1 @@ +CONFIG_HISI_SOC_L3C=m \ No newline at end of file diff --git a/drivers/soc/hisilicon/Kconfig b/drivers/soc/hisilicon/Kconfig index 0ab688af308fed625ec8c04723a87911b8da7373..419aede252fadeff53cdd36363fe8f2da0042964 100644 --- a/drivers/soc/hisilicon/Kconfig +++ b/drivers/soc/hisilicon/Kconfig @@ -18,4 +18,37 @@ config KUNPENG_HCCS Say M here if you want to include support for querying the health status and port information of HCCS on Kunpeng SoC. +config HISI_SOC_CACHE + bool "HiSilicon Cache driver for Kunpeng SoC" + depends on ARCH_HISI + help + This driver provides the basic utilities for drivers of + different part of Kunpeng SoC cache, including L3 cache and + Hydra Home Agent etc. + + If either HiSilicon L3 cache driver or HiSilicon Hydra Home + Agent driver is needed, say yes. + +config HISI_SOC_L3C + tristate "HiSilicon L3 Cache device driver" + depends on ARM64 && ACPI || COMPILE_TEST + depends on HISI_SOC_CACHE + help + This driver provides the functions to lock L3 cache entries from + being evicted for better performance. + + This driver can be built as a module. If so, the module will be + called hisi_soc_l3c. + +config HISI_SOC_HHA + tristate "HiSilicon Hydra Home Agent (HHA) device driver" + depends on ARM64 && ACPI || COMPILE_TEST + depends on HISI_SOC_CACHE + help + The Hydra Home Agent (HHA) is responsible of cache coherency + on SoC. This drivers provides cache maintenance functions of HHA. + + This driver can be built as a module. If so, the module will be + called hisi_soc_hha. + endmenu diff --git a/drivers/soc/hisilicon/Makefile b/drivers/soc/hisilicon/Makefile index 226e747e70d67511e1954010e143d39df6b73f55..497c66ab8b8565ac18c04ae1efd1e43742d800d3 100644 --- a/drivers/soc/hisilicon/Makefile +++ b/drivers/soc/hisilicon/Makefile @@ -1,2 +1,6 @@ # SPDX-License-Identifier: GPL-2.0-only obj-$(CONFIG_KUNPENG_HCCS) += kunpeng_hccs.o + +obj-$(CONFIG_HISI_SOC_CACHE) += hisi_soc_cache_framework.o +obj-$(CONFIG_HISI_SOC_HHA) += hisi_soc_hha.o +obj-$(CONFIG_HISI_SOC_L3C) += hisi_soc_l3c.o diff --git a/drivers/soc/hisilicon/hisi_soc_cache_framework.c b/drivers/soc/hisilicon/hisi_soc_cache_framework.c new file mode 100644 index 0000000000000000000000000000000000000000..40d5a5e942da270cfad38dc177c084ec78d33764 --- /dev/null +++ b/drivers/soc/hisilicon/hisi_soc_cache_framework.c @@ -0,0 +1,641 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Framework for HiSilicon SoC cache, manages HiSilicon SoC cache drivers. + * + * Copyright (c) 2024 HiSilicon Technologies Co., Ltd. + * Author: Jie Wang + * Author: Yicong Yang + * Author: Yushan Wang + */ + +#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#include "hisi_soc_cache_framework.h" + +struct hisi_soc_cache_lock_region { + /* physical address of the arena allocated for aligned address */ + unsigned long arena_start; + /* VMA region of locked memory for future release */ + unsigned long vm_start; + unsigned long vm_end; + phys_addr_t addr; + size_t size; + /* Return value of cache lock call */ + int status; + int cpu; +}; + +struct hisi_soc_comp_inst { + struct list_head node; + struct hisi_soc_comp *comp; +}; + +struct hisi_soc_comp_list { + struct list_head node; + /* protects list of HiSilicon SoC cache components */ + spinlock_t lock; + u32 inst_num; +}; + +static struct hisi_soc_comp_list soc_cache_devs[SOC_COMP_TYPE_MAX]; + +static int hisi_soc_cache_lock(int cpu, phys_addr_t addr, size_t size) +{ + struct hisi_soc_comp_inst *inst; + struct list_head *head; + int ret = -ENOMEM; + + guard(spinlock)(&soc_cache_devs[HISI_SOC_L3C].lock); + + /* Avoid null pointer when there is no instance onboard. */ + if (soc_cache_devs[HISI_SOC_L3C].inst_num <= 0) + return ret; + + /* Iterate L3C instances to perform operation, break loop once found. */ + head = &soc_cache_devs[HISI_SOC_L3C].node; + list_for_each_entry(inst, head, node) { + if (!cpumask_test_cpu(cpu, &inst->comp->affinity_mask)) + continue; + ret = inst->comp->ops->do_lock(inst->comp, addr, size); + if (ret) + return ret; + } + + list_for_each_entry(inst, head, node) { + if (!cpumask_test_cpu(cpu, &inst->comp->affinity_mask)) + continue; + ret = inst->comp->ops->poll_lock_done(inst->comp, addr, size); + if (ret) + return ret; + } + + return ret; +} + +static int hisi_soc_cache_unlock(int cpu, phys_addr_t addr) +{ + struct hisi_soc_comp_inst *inst; + struct list_head *head; + int ret = 0; + + guard(spinlock)(&soc_cache_devs[HISI_SOC_L3C].lock); + + /* Avoid null pointer when there is no instance onboard. */ + if (soc_cache_devs[HISI_SOC_L3C].inst_num <= 0) + return ret; + + /* Iterate L3C instances to perform operation, break loop once found. */ + head = &soc_cache_devs[HISI_SOC_L3C].node; + list_for_each_entry(inst, head, node) { + if (!cpumask_test_cpu(cpu, &inst->comp->affinity_mask)) + continue; + ret = inst->comp->ops->do_unlock(inst->comp, addr); + if (ret) + return ret; + } + + list_for_each_entry(inst, head, node) { + if (!cpumask_test_cpu(cpu, &inst->comp->affinity_mask)) + continue; + ret = inst->comp->ops->poll_unlock_done(inst->comp, addr); + if (ret) + return ret; + } + + return ret; +} + +int hisi_soc_cache_maintain(phys_addr_t addr, size_t size, + enum hisi_soc_cache_maint_type mnt_type) +{ + struct hisi_soc_comp_inst *inst; + struct list_head *head; + int ret = -EOPNOTSUPP; + + if (mnt_type >= HISI_CACHE_MAINT_MAX) + return -EINVAL; + + guard(spinlock)(&soc_cache_devs[HISI_SOC_HHA].lock); + + head = &soc_cache_devs[HISI_SOC_HHA].node; + list_for_each_entry(inst, head, node) { + ret = inst->comp->ops->do_maintain(inst->comp, addr, size, + mnt_type); + if (ret) + return ret; + } + + list_for_each_entry(inst, head, node) { + ret = inst->comp->ops->poll_maintain_done(inst->comp, addr, + size, mnt_type); + if (ret) + return ret; + } + + return ret; +} +EXPORT_SYMBOL_GPL(hisi_soc_cache_maintain); + +static int hisi_soc_cache_maint_pte_entry(pte_t *pte, unsigned long addr, + unsigned long next, struct mm_walk *walk) +{ +#ifdef HISI_SOC_CACHE_LLT + struct hisi_soc_cache_ioctl_param *param = walk->priv; +#else + struct hisi_soc_cache_ioctl_param *param = walk->private; +#endif + size_t size = min(next - addr, param->size + param->addr - addr); + unsigned long offset = offset_in_page(max(addr, param->addr)); + phys_addr_t paddr = PFN_PHYS(pte_pfn(*pte)) + offset; + + if (!pte_present(ptep_get(pte))) + return -EINVAL; + + return hisi_soc_cache_maintain(paddr, size, param->op_type); +} + +static const struct mm_walk_ops hisi_soc_cache_maint_walk = { + .pte_entry = hisi_soc_cache_maint_pte_entry, + .walk_lock = PGWALK_RDLOCK, +}; + +static int hisi_soc_cache_inst_check(const struct hisi_soc_comp *comp, + enum hisi_soc_comp_type comp_type) +{ + struct hisi_soc_comp_ops *ops = comp->ops; + + /* Different types of component could have different ops. */ + switch (comp_type) { + case HISI_SOC_L3C: + if (!ops->do_lock || !ops->poll_lock_done + || !ops->do_unlock || !ops->poll_unlock_done) + return -EINVAL; + break; + case HISI_SOC_HHA: + if (!comp->ops->do_maintain || !comp->ops->poll_maintain_done) + return -EINVAL; + break; + default: + return -EINVAL; + } + + return 0; +} + +static int hisi_soc_cache_inst_add(struct hisi_soc_comp *comp, + enum hisi_soc_comp_type comp_type) +{ + struct hisi_soc_comp_inst *comp_inst; + int ret; + + ret = hisi_soc_cache_inst_check(comp, comp_type); + if (ret) + return ret; + + comp_inst = kzalloc(sizeof(*comp_inst), GFP_KERNEL); + if (!comp_inst) + return -ENOMEM; + + comp_inst->comp = comp; + + scoped_guard(spinlock, &soc_cache_devs[comp_type].lock) { + list_add_tail(&comp_inst->node, + &soc_cache_devs[comp_type].node); + soc_cache_devs[comp_type].inst_num++; + } + + return 0; +} + +/* + * When @comp is NULL, it means to delete all instances of @comp_type. + */ +static void hisi_soc_cache_inst_del(struct hisi_soc_comp *comp, + enum hisi_soc_comp_type comp_type) +{ + struct hisi_soc_comp_inst *inst, *tmp; + + guard(spinlock)(&soc_cache_devs[comp_type].lock); + list_for_each_entry_safe(inst, tmp, &soc_cache_devs[comp_type].node, + node) { + if (comp && comp != inst->comp) + continue; + + if (soc_cache_devs[comp_type].inst_num > 0) + soc_cache_devs[comp_type].inst_num--; + + list_del(&inst->node); + kfree(inst); + + /* Stop the loop if we have already deleted @comp. */ + if (comp) + break; + } +} + +int hisi_soc_comp_inst_add(struct hisi_soc_comp *comp) +{ + int ret, i = HISI_SOC_L3C; + + if (!comp || !comp->ops || comp->comp_type == 0) + return -EINVAL; + + for_each_set_bit_from(i, &comp->comp_type, SOC_COMP_TYPE_MAX) { + ret = hisi_soc_cache_inst_add(comp, i); + if (ret) + return ret; + } + + return 0; +} +EXPORT_SYMBOL_GPL(hisi_soc_comp_inst_add); + +int hisi_soc_comp_inst_del(struct hisi_soc_comp *comp) +{ + int i; + + if (!comp) + return -EINVAL; + + for_each_set_bit(i, &comp->comp_type, SOC_COMP_TYPE_MAX) + hisi_soc_cache_inst_del(comp, i); + + return 0; +} +EXPORT_SYMBOL_GPL(hisi_soc_comp_inst_del); + +/** + * hisi_soc_cache_aligned_alloc - Allocate memory region to be locked and + * returns address that aligned to the requested + * size. + * @clr: The locked memory region to be allocated for. + * @size: Requested memory size. + * @addr: Pointer of the start physical address of the requested + * memory region. + * + * @return: + * - -ENOMEM: If allocation fails. + * - 0: If allocations succeeds. + * + * Physical address of allocated memory region is requested to be aligned to + * its size. In order to achieve that, add the order of requested memory size + * by 1 to double the size of allocated memory to ensure the existence of size- + * aligned address. After locating the aligned region, release the unused + * pages from both sides to avoid waste. + */ +static int hisi_soc_cache_aligned_alloc(struct hisi_soc_cache_lock_region *clr, + unsigned long size, + unsigned long *addr) +{ + int order = get_order(size) + 1; + unsigned long arena_start; + struct page *pg; + + pg = alloc_contig_pages(1 << order, GFP_KERNEL | __GFP_NOWARN | __GFP_ZERO, + cpu_to_node(smp_processor_id()), NULL); + if (!pg) + return -ENOMEM; + + arena_start = page_to_phys(pg); + + /* + * Align up the address by the requested size if the address is not + * naturally aligned to the size. + */ + *addr = arena_start % size == 0 + ? arena_start + : arena_start / size * size + size; + + clr->arena_start = arena_start; + + return 0; +} + +/** + * hisi_soc_cache_aligned_free - Free the aligned memory region allcated by + * hisi_soc_cache_aligned_alloc(). + * @clr: The allocated locked memory region. + * + * Since unused memory pages are release in hisi_soc_cache_aligned_alloc(), the + * memory region to be freed here may not be power of 2 numbers of pages. + * Thus split the memory by page order and release them accordingly. + */ +static void hisi_soc_cache_aligned_free(struct hisi_soc_cache_lock_region *clr) +{ + int order = get_order(clr->size) + 1; + + free_contig_range(PHYS_PFN(clr->arena_start), 1 << order); +} + +static void hisi_soc_cache_vm_open(struct vm_area_struct *vma) +{ + struct hisi_soc_cache_lock_region *clr = vma->vm_private_data; + + /* + * Only perform cache lock when the vma passed in is created + * in hisi_soc_cache_mmap. + */ + if (clr->vm_start != vma->vm_start || clr->vm_end != vma->vm_end) + return; + + clr->status = hisi_soc_cache_lock(clr->cpu, clr->addr, clr->size); +} + +static void hisi_soc_cache_vm_close(struct vm_area_struct *vma) +{ + struct hisi_soc_cache_lock_region *clr = vma->vm_private_data; + + /* + * Only perform cache unlock when the vma passed in is created + * in hisi_soc_cache_mmap. + */ + if (clr->vm_start != vma->vm_start || clr->vm_end != vma->vm_end) + return; + + hisi_soc_cache_unlock(clr->cpu, clr->addr); + + hisi_soc_cache_aligned_free(clr); + kfree(clr); + vma->vm_private_data = NULL; +} + +/* + * mremap operation is not supported for HiSilicon SoC cache. + */ +static int hisi_soc_cache_vm_mremap(struct vm_area_struct *vma) +{ + struct hisi_soc_cache_lock_region *clr = vma->vm_private_data; + + /* + * vma region size will be changed as requested by mremap despite the + * callback failure in this function. Thus, change the vma region + * stored in clr according to the parameters to verify if the pages + * should be freed when unmapping. + */ + clr->vm_end = clr->vm_start + (vma->vm_end - vma->vm_start); + pr_err("mremap for HiSilicon SoC locked cache is not supported\n"); + + return -EOPNOTSUPP; +} + +static int hisi_soc_cache_may_split(struct vm_area_struct *area, unsigned long addr) +{ + pr_err("HiSilicon SoC locked cache may not be split.\n"); + return -EINVAL; +} + +static const struct vm_operations_struct hisi_soc_cache_vm_ops = { + .open = hisi_soc_cache_vm_open, + .close = hisi_soc_cache_vm_close, + .may_split = hisi_soc_cache_may_split, + .mremap = hisi_soc_cache_vm_mremap, +}; + +static int hisi_soc_cache_mmap(struct file *file, struct vm_area_struct *vma) +{ + unsigned long size = vma->vm_end - vma->vm_start; + struct hisi_soc_cache_lock_region *clr; + unsigned long addr; + int ret; + + clr = kzalloc(sizeof(*clr), GFP_KERNEL); + if (!clr) + return -ENOMEM; + + ret = hisi_soc_cache_aligned_alloc(clr, size, &addr); + if (ret) + goto out_clr; + + ret = remap_pfn_range(vma, vma->vm_start, addr >> PAGE_SHIFT, size, + vma->vm_page_prot); + if (ret) + goto out_page; + + clr->addr = addr; + clr->size = size; + clr->cpu = smp_processor_id(); + vm_flags_set(vma, VM_DONTCOPY | VM_DONTEXPAND); + + /* + * The vma should not be moved throughout its lifetime, store the + * region for verification. + */ + clr->vm_start = vma->vm_start; + clr->vm_end = vma->vm_end; + + vma->vm_private_data = clr; + vma->vm_ops = &hisi_soc_cache_vm_ops; + hisi_soc_cache_vm_ops.open(vma); + + if (clr->status) { + ret = clr->status; + goto out_page; + } + + return 0; + +out_page: + hisi_soc_cache_aligned_free(clr); +out_clr: + kfree(clr); + return ret; +} + +static int __hisi_soc_cache_maintain(struct hisi_soc_cache_ioctl_param *param) +{ + unsigned long start = untagged_addr(param->addr); + struct vm_area_struct *vma; + int ret = 0; + + /* MakeInvalid is not allowed for calls from userspace. */ + if (param->op_type >= HISI_CACHE_MAINT_MAKEINVALID) + return -EINVAL; + + /* Prevent overflow of vaddr + size. */ + if (!param->size || start + param->size < start) + return -EINVAL; + + ret = mmap_read_lock_killable(current->mm); + if (ret) + return ret; + + vma = vma_lookup(current->mm, param->addr); + if (!range_in_vma(vma, start, start + param->size)) { + ret = -EINVAL; + goto out; + } + + /* User should have the write permission of target memory */ + if (!(vma->vm_flags & VM_WRITE)) { + ret = -EINVAL; + goto out; + } + + ret = walk_page_range(current->mm, PAGE_ALIGN_DOWN(start), + PAGE_ALIGN(start + param->size), + &hisi_soc_cache_maint_walk, param); +out: + mmap_read_unlock(current->mm); + return ret; +} + +static long hisi_soc_cache_mgmt_ioctl(struct file *file, u32 cmd, unsigned long arg) +{ + struct hisi_soc_cache_ioctl_param param; + long ret; + + if (copy_from_user(¶m, (void __user *)arg, sizeof(param))) + return -EFAULT; + + switch (cmd) { + case HISI_CACHE_MAINTAIN: + ret = __hisi_soc_cache_maintain(¶m); + break; + default: + ret = -EINVAL; + break; + } + + return ret; +} + +static const struct file_operations soc_cache_dev_fops = { + .owner = THIS_MODULE, + .unlocked_ioctl = hisi_soc_cache_mgmt_ioctl, + .mmap = hisi_soc_cache_mmap, +}; + +static struct miscdevice soc_cache_miscdev = { + .minor = MISC_DYNAMIC_MINOR, + .name = "hisi_soc_cache_mgmt", + .fops = &soc_cache_dev_fops, + .mode = 0600, +}; + +static void hisi_soc_cache_inst_uninit(void) +{ + int i; + + for (i = 0; i < ARRAY_SIZE(soc_cache_devs); ++i) + hisi_soc_cache_inst_del(NULL, i); +} + +static void hisi_soc_cache_framework_data_init(void) +{ + int i; + + for (i = 0; i < ARRAY_SIZE(soc_cache_devs); ++i) { + spin_lock_init(&soc_cache_devs[i].lock); + INIT_LIST_HEAD(&soc_cache_devs[i].node); + } +} + +static const char *const hisi_soc_cache_item_str[SOC_COMP_TYPE_MAX] = { + "cache", + "hha" +}; + +/* + * Print cache instance number debug information for debug FS. + */ +static ssize_t hisi_soc_cache_dbg_get_inst_num(struct file *file, + char __user *buff, + size_t cnt, + loff_t *ppos) +{ +#define HISI_SOC_CACHE_DBGFS_REG_LEN 100 + char *read_buff; + int len, i, pos = 0; + int ret = 0; + + if (!access_ok(buff, cnt)) + return -EFAULT; + if (*ppos < 0) + return -EINVAL; + if (cnt == 0) + return 0; + + read_buff = kzalloc(HISI_SOC_CACHE_DBGFS_REG_LEN, GFP_KERNEL); + if (!read_buff) + return -ENOMEM; + + len = HISI_SOC_CACHE_DBGFS_REG_LEN; + + for (i = 0; i < ARRAY_SIZE(soc_cache_devs); i++) { + guard(spinlock)(&soc_cache_devs[i].lock); + pos += scnprintf(read_buff + pos, len - pos, + "%s inst num: %u\n", + hisi_soc_cache_item_str[i], + soc_cache_devs[i].inst_num); + } + + ret = simple_read_from_buffer(buff, cnt, ppos, read_buff, + strlen(read_buff)); + kfree(read_buff); + return ret; +} + +static struct dentry *hisi_cache_dbgfs_root; +static const struct file_operations hisi_cache_dbgfs_ops = { + .owner = THIS_MODULE, + .open = simple_open, + .read = hisi_soc_cache_dbg_get_inst_num, +}; + +static void hisi_soc_cache_dbgfs_init(void) +{ + hisi_cache_dbgfs_root = debugfs_create_dir("hisi_soc_cache_frm", NULL); + debugfs_create_file("instance", 0400, hisi_cache_dbgfs_root, NULL, + &hisi_cache_dbgfs_ops); +} + +static void hisi_soc_cache_dbgfs_uninit(void) +{ + debugfs_remove_recursive(hisi_cache_dbgfs_root); + hisi_cache_dbgfs_root = NULL; +} + +static int __init hisi_soc_cache_framework_init(void) +{ + int ret; + + hisi_soc_cache_framework_data_init(); + + ret = misc_register(&soc_cache_miscdev); + if (ret) { + hisi_soc_cache_inst_uninit(); + return ret; + } + + hisi_soc_cache_dbgfs_init(); + + return 0; +} +module_init(hisi_soc_cache_framework_init); + +static void __exit hisi_soc_cache_framework_exit(void) +{ + hisi_soc_cache_dbgfs_uninit(); + misc_deregister(&soc_cache_miscdev); + hisi_soc_cache_inst_uninit(); +} +module_exit(hisi_soc_cache_framework_exit); + +MODULE_DESCRIPTION("HiSilicon SoC Cache Framework Driver"); +MODULE_AUTHOR("Jie Wang "); +MODULE_AUTHOR("Yushan Wang "); +MODULE_LICENSE("GPL"); diff --git a/drivers/soc/hisilicon/hisi_soc_cache_framework.h b/drivers/soc/hisilicon/hisi_soc_cache_framework.h new file mode 100644 index 0000000000000000000000000000000000000000..67ee9a33f382f448a38d90d0bf892702d76c4c01 --- /dev/null +++ b/drivers/soc/hisilicon/hisi_soc_cache_framework.h @@ -0,0 +1,92 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* + * Header file of framework for HiSilicon SoC cache. + * + * Copyright (c) 2024 HiSilicon Technologies Co., Ltd. + * Author: Jie Wang + * Author: Yicong Yang + * Author: Yushan Wang + */ + +#ifndef HISI_CACHE_FRAMEWORK_H +#define HISI_CACHE_FRAMEWORK_H + +#include +#include + +#include + +enum hisi_soc_comp_type { + HISI_SOC_L3C, + HISI_SOC_HHA, + SOC_COMP_TYPE_MAX +}; + +struct hisi_soc_comp; + +/** + * struct hisi_soc_comp_ops - Callbacks for SoC cache drivers to handle + * operation requests. + * + * @lock_enable: lock certain region of L3 cache from being evicted. + * @poll_lock_done: check if the lock operation has succeeded. + * @unlock_enable: unlock the locked region of L3 cache back to normal. + * @poll_unlock_done: check if the unlock operation has succeeded. + operation requests. + * @maintain_enable: perform certain cache maintain operation on HHA. + * @poll_maintain_done: check if the HHA maintain operation has succeeded. + * + * Operations are decoupled into two phases so that framework does not have + * to wait for one operation to finish before calling the next when multiple + * hardwares onboard. + * + * Implementers must implement the functions in pairs. Implementation should + * return -EBUSY when: + * - insufficient resources are available to perform the operation. + * - previously raised operation is not finished. + * - new operations (do_lock(), do_unlock() etc.) to the same address + * before corresponding done functions being called. + */ +struct hisi_soc_comp_ops { + int (*do_lock)(struct hisi_soc_comp *comp, + phys_addr_t addr, size_t size); + int (*poll_lock_done)(struct hisi_soc_comp *comp, + phys_addr_t addr, size_t size); + int (*do_unlock)(struct hisi_soc_comp *comp, + phys_addr_t addr); + int (*poll_unlock_done)(struct hisi_soc_comp *comp, + phys_addr_t addr); + int (*do_maintain)(struct hisi_soc_comp *comp, + phys_addr_t addr, size_t size, + enum hisi_soc_cache_maint_type mnt_type); + int (*poll_maintain_done)(struct hisi_soc_comp *comp, + phys_addr_t addr, size_t size, + enum hisi_soc_cache_maint_type mnt_type); +}; + +/** + * struct hisi_soc_comp - Struct of HiSilicon SoC cache components. + * @ops: possible operations a component may perform. + * @affinity_mask: cpus that associate with this component. + * @comp_type: bitmap declaring the type of the component. + * + * A component may have multiple types (e.g. a piece of multi-function device). + * If so, set the bit of @comp_type according to its supporting type in struct + * hisi_soc_comp_type. + */ +struct hisi_soc_comp { + struct hisi_soc_comp_ops *ops; + cpumask_t affinity_mask; + /* + * Setting bit x to 1 means this instance supports feature of x-th + * entry in enum hisi_soc_comp_type. + */ + unsigned long comp_type; +}; + +int hisi_soc_comp_inst_add(struct hisi_soc_comp *comp); +int hisi_soc_comp_inst_del(struct hisi_soc_comp *comp); +int hisi_soc_cache_maintain(phys_addr_t addr, size_t size, + enum hisi_soc_cache_maint_type mnt_type); + +#endif diff --git a/drivers/soc/hisilicon/hisi_soc_hha.c b/drivers/soc/hisilicon/hisi_soc_hha.c new file mode 100644 index 0000000000000000000000000000000000000000..22a1ec8b8fc941c84a506a4a9311a520d15f38ea --- /dev/null +++ b/drivers/soc/hisilicon/hisi_soc_hha.c @@ -0,0 +1,194 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Driver for HiSilicon Hydra Home Agent (HHA). + * + * Copyright (c) 2024 HiSilicon Technologies Co., Ltd. + * Author: Yicong Yang + * Yushan Wang + */ + +#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "hisi_soc_cache_framework.h" + +#define HISI_HHA_CTRL 0x5004 +#define HISI_HHA_CTRL_EN BIT(0) +#define HISI_HHA_CTRL_RANGE BIT(1) +#define HISI_HHA_CTRL_TYPE GENMASK(3, 2) +#define HISI_HHA_START_L 0x5008 +#define HISI_HHA_START_H 0x500c +#define HISI_HHA_LEN_L 0x5010 +#define HISI_HHA_LEN_H 0x5014 + +/* The maintain operation performs in a 128 Byte granularity */ +#define HISI_HHA_MAINT_ALIGN 128 + +#define HISI_HHA_POLL_GAP_US 10 + +struct hisi_soc_hha { + struct hisi_soc_comp comp; + /* Locks HHA instance to forbid overlapping access. */ + spinlock_t lock; + struct device *dev; + void __iomem *base; +}; + +static bool hisi_hha_cache_maintain_wait_finished(struct hisi_soc_hha *soc_hha) +{ + u32 val; + + return !readl_poll_timeout_atomic(soc_hha->base + HISI_HHA_CTRL, val, + !(val & HISI_HHA_CTRL_EN), + HISI_HHA_POLL_GAP_US, + jiffies_to_usecs(HZ)); +} + +static int hisi_hha_cache_do_maintain(struct hisi_soc_comp *comp, + phys_addr_t addr, size_t size, + enum hisi_soc_cache_maint_type mnt_type) +{ + struct hisi_soc_hha *soc_hha = container_of(comp, struct hisi_soc_hha, + comp); + phys_addr_t top; + int ret = 0; + u32 reg; + + if (!size) + return -EINVAL; + + addr = ALIGN_DOWN(addr, HISI_HHA_MAINT_ALIGN); + top = ALIGN(addr + size, HISI_HHA_MAINT_ALIGN); + size = top - addr; + + if (mnt_type < 0 || mnt_type >= HISI_CACHE_MAINT_MAX) + return -EOPNOTSUPP; + + /* + * Hardware will search for addresses ranging [addr, addr + size -1], + * last byte included, and perform maintain in 128 byte granule + * on those which contain the addresses. + */ + size -= 1; + + guard(spinlock)(&soc_hha->lock); + + if (!hisi_hha_cache_maintain_wait_finished(soc_hha)) + return -EBUSY; + + writel(lower_32_bits(addr), soc_hha->base + HISI_HHA_START_L); + writel(upper_32_bits(addr), soc_hha->base + HISI_HHA_START_H); + writel(lower_32_bits(size), soc_hha->base + HISI_HHA_LEN_L); + writel(upper_32_bits(size), soc_hha->base + HISI_HHA_LEN_H); + + reg = FIELD_PREP(HISI_HHA_CTRL_TYPE, mnt_type); + reg |= HISI_HHA_CTRL_RANGE | HISI_HHA_CTRL_EN; + writel(reg, soc_hha->base + HISI_HHA_CTRL); + + return ret; +} + +static int hisi_hha_cache_poll_maintain_done(struct hisi_soc_comp *comp, + phys_addr_t addr, size_t size, + enum hisi_soc_cache_maint_type mnt_type) +{ + struct hisi_soc_hha *soc_hha = container_of(comp, struct hisi_soc_hha, + comp); + + guard(spinlock)(&soc_hha->lock); + + if (!hisi_hha_cache_maintain_wait_finished(soc_hha)) + return -ETIMEDOUT; + + return 0; +} + +static struct hisi_soc_comp_ops hisi_soc_hha_comp_ops = { + .do_maintain = hisi_hha_cache_do_maintain, + .poll_maintain_done = hisi_hha_cache_poll_maintain_done, +}; + +static void hisi_hha_comp_inst_del(void *priv) +{ + struct hisi_soc_hha *soc_hha = priv; + + hisi_soc_comp_inst_del(&soc_hha->comp); +} + +static int hisi_soc_hha_probe(struct platform_device *pdev) +{ + struct hisi_soc_hha *soc_hha; + struct resource *mem; + int ret; + + soc_hha = devm_kzalloc(&pdev->dev, sizeof(*soc_hha), GFP_KERNEL); + if (!soc_hha) + return -ENOMEM; + + platform_set_drvdata(pdev, soc_hha); + soc_hha->dev = &pdev->dev; + + spin_lock_init(&soc_hha->lock); + + mem = platform_get_resource(pdev, IORESOURCE_MEM, 0); + if (!mem) + return -ENODEV; + + /* + * HHA cache driver share the same register region with HHA uncore PMU + * driver in hardware's perspective, none of them should reserve the + * resource to itself only. Here exclusive access verification is + * avoided by calling devm_ioremap instead of devm_ioremap_resource to + * allow both drivers to exist at the same time. + */ + soc_hha->base = devm_ioremap(&pdev->dev, mem->start, + resource_size(mem)); + if (IS_ERR_OR_NULL(soc_hha->base)) { + return dev_err_probe(&pdev->dev, PTR_ERR(soc_hha->base), + "failed to remap io memory"); + } + + soc_hha->comp.ops = &hisi_soc_hha_comp_ops; + soc_hha->comp.comp_type = BIT(HISI_SOC_HHA); + cpumask_copy(&soc_hha->comp.affinity_mask, cpu_possible_mask); + + ret = hisi_soc_comp_inst_add(&soc_hha->comp); + if (ret) + return dev_err_probe(&pdev->dev, ret, + "failed to register maintain inst"); + + return devm_add_action_or_reset(&pdev->dev, hisi_hha_comp_inst_del, + soc_hha); +} + +static const struct acpi_device_id hisi_soc_hha_ids[] = { + { "HISI0511", }, + { } +}; +MODULE_DEVICE_TABLE(acpi, hisi_soc_hha_ids); + +static struct platform_driver hisi_soc_hha_driver = { + .driver = { + .name = "hisi_soc_hha", + .acpi_match_table = hisi_soc_hha_ids, + }, + .probe = hisi_soc_hha_probe, +}; + +module_platform_driver(hisi_soc_hha_driver); + +MODULE_DESCRIPTION("Hisilicon Hydra Home Agent driver supporting cache maintenance"); +MODULE_AUTHOR("Yicong Yang "); +MODULE_AUTHOR("Yushan Wang "); +MODULE_LICENSE("GPL"); diff --git a/drivers/soc/hisilicon/hisi_soc_l3c.c b/drivers/soc/hisilicon/hisi_soc_l3c.c new file mode 100644 index 0000000000000000000000000000000000000000..0aec6766b34b82c13af70ac1fd6937779e6e5eb7 --- /dev/null +++ b/drivers/soc/hisilicon/hisi_soc_l3c.c @@ -0,0 +1,607 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Driver for HiSilicon L3 cache. + * + * Copyright (c) 2024 HiSilicon Technologies Co., Ltd. + * Author: Yushan Wang + */ + +#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#include "hisi_soc_cache_framework.h" + +#define HISI_L3C_LOCK_CTRL 0x0530 +#define HISI_L3C_LOCK_AREA 0x0534 +#define HISI_L3C_LOCK_START_L 0x0538 +#define HISI_L3C_LOCK_START_H 0x053C + +#define HISI_L3C_DYNAMIC_AUCTRL 0x0404 + +#define HISI_L3C_LOCK_CTRL_POLL_GAP_US 10 + +#define HISI_L3C_MAX_LOCKREGION_SIZE \ + "hisilicon,l3c-max-single-lockregion-size" +#define HISI_L3C_MAX_LOCKREGION_NUM \ + "hisilicon,l3c-lockregion-num" + +/* L3C control register bit definition */ +#define HISI_L3C_LOCK_CTRL_LOCK_EN BIT(0) +#define HISI_L3C_LOCK_CTRL_LOCK_DONE BIT(1) +#define HISI_L3C_LOCK_CTRL_UNLOCK_EN BIT(2) +#define HISI_L3C_LOCK_CTRL_UNLOCK_DONE BIT(3) + +#define HISI_L3C_LOCK_MIN_SIZE (1 * 1024 * 1024) +#define HISI_L3_CACHE_LINE_SIZE 64 + +/* Allow maximum 70% of cache locked. */ +#define HISI_L3C_MAX_LOCK_SIZE(size) ((size) / 10 * 7) + +#define l3c_lock_reg_offset(reg, set) ((reg) + 16 * (set)) + +#define l3c_lock_ctrl_mask(lock_ctrl, mask) ((lock_ctrl) & (mask)) + +#define to_hisi_l3c(p) container_of((p), struct hisi_soc_l3c, comp) + +static int hisi_l3c_cpuhp_state; + +struct hisi_soc_l3c { + struct hisi_soc_comp comp; + cpumask_t associated_cpus; + + /* Stores the first address locked by each register sets. */ + struct xarray lock_sets; + /* Stores if a set of lock control register has been used. */ + u32 reg_used_map; + /* Locks reg_used_map and lock_sets to forbid overlapping access. */ + spinlock_t reg_lock; + + /* Locked memory size range. */ + size_t max_lock_size; + size_t min_lock_size; + size_t locked_size; + + /* Maximum number of locked memory size. */ + int max_lock_num; + + struct hlist_node node; + void __iomem *base; + + /* ID of Super CPU cluster on where the L3 cache locates. */ + int sccl_id; + /* ID of CPU cluster where L3 cache is located. */ + int ccl_id; +}; + +struct hisi_soc_l3c_lock_region { + phys_addr_t addr; + size_t size; +}; + +/** + * hisi_soc_l3c_alloc_lock_reg_set - Allocate an available control register set + * of L3 cache for lock & unlock operations. + * @soc_l3c: The L3C instance on which the register set will be allocated. + * @addr: The address to be locked. + * @size: The size to be locked. + * + * @return: + * - -EBUSY: If there is no available register sets. + * - -ENOMEM: If there is no available memory for lock region struct. + * - -EINVAL: If there is no available cache size for lock. + * - 0: If allocation succeeds. + * + * Maintains the resource of control registers of L3 cache. On allocation, + * the index of a spare set of registers is returned, then the address is + * stored inside for future match of unlock operation. + */ +static int hisi_soc_l3c_alloc_lock_reg_set(struct hisi_soc_l3c *soc_l3c, + phys_addr_t addr, size_t size) +{ + struct hisi_soc_l3c_lock_region *lr; + unsigned long idx; + void *entry; + + if (size > soc_l3c->max_lock_size - soc_l3c->locked_size) + return -EINVAL; + + for (idx = 0; idx < soc_l3c->max_lock_num; ++idx) { + entry = xa_load(&soc_l3c->lock_sets, idx); + if (!entry) + break; + } + + if (idx >= soc_l3c->max_lock_num) + return -EBUSY; + + lr = kzalloc(sizeof(struct hisi_soc_l3c_lock_region), GFP_KERNEL); + if (!lr) + return -ENOMEM; + + lr->addr = addr; + lr->size = size; + + entry = xa_store(&soc_l3c->lock_sets, idx, lr, GFP_KERNEL); + if (xa_is_err(entry)) { + kfree(lr); + return xa_err(entry); + } + + soc_l3c->locked_size += size; + + return idx; +} + +/** + * hisi_soc_l3c_get_locked_reg_set - Get the index of an allocated register set + * by locked address. + * @soc_l3c: The L3C instance on which the register set is allocated. + * @addr: The locked address. + * + * @return: + * - >= 0: index of register set which controls locked memory region of @addr. + * - -EINVAL: If @addr is not locked in this cache. + */ +static int hisi_soc_l3c_get_locked_reg_set(struct hisi_soc_l3c *soc_l3c, + phys_addr_t addr) +{ + struct hisi_soc_l3c_lock_region *entry; + unsigned long idx; + + xa_for_each_range(&soc_l3c->lock_sets, idx, entry, 0, + soc_l3c->max_lock_num) { + if (entry->addr == addr) + return idx; + } + return -EINVAL; +} + +/** + * hisi_soc_l3c_free_lock_reg_set - Free an allocated register set by locked + * address. + * + * @soc_l3c: The L3C instance on which the register set is allocated. + * @regset: ID of Register set to be freed. + */ +static void hisi_soc_l3c_free_lock_reg_set(struct hisi_soc_l3c *soc_l3c, + int regset) +{ + struct hisi_soc_l3c_lock_region *entry; + + if (regset < 0) + return; + + entry = xa_erase(&soc_l3c->lock_sets, regset); + if (!entry) + return; + + soc_l3c->locked_size -= entry->size; + kfree(entry); +} + +static int hisi_l3c_lock_ctrl_wait_finished(struct hisi_soc_l3c *soc_l3c, + int regset, u32 mask) +{ + u32 reg_used_map = soc_l3c->reg_used_map; + void *base = soc_l3c->base; + u32 val; + + /* + * Each HiSilicon L3 cache instance will have lock/unlock done bit set + * to 0 when first put to use even if the device is available. + * A reg_used_map is proposed to record if an instance has been called + * to lock down, then we can determine if it is available by + * reading lock/unlock done bit. + */ + if (!(reg_used_map & BIT(regset))) { + reg_used_map |= BIT(regset); + return 1; + } + + return !readl_poll_timeout_atomic( + base + l3c_lock_reg_offset(HISI_L3C_LOCK_CTRL, regset), + val, l3c_lock_ctrl_mask(val, mask), + HISI_L3C_LOCK_CTRL_POLL_GAP_US, + jiffies_to_usecs(HZ)); +} + +static int hisi_soc_l3c_do_lock(struct hisi_soc_comp *l3c_comp, + phys_addr_t addr, size_t size) +{ + struct hisi_soc_l3c *soc_l3c = to_hisi_l3c(l3c_comp); + void *base = soc_l3c->base; + int regset; + u32 ctrl; + + if (soc_l3c->max_lock_num == 1 && addr % size != 0) + return -EINVAL; + + if (size < soc_l3c->min_lock_size) + return -EINVAL; + + guard(spinlock)(&soc_l3c->reg_lock); + + regset = hisi_soc_l3c_alloc_lock_reg_set(soc_l3c, addr, size); + if (regset < 0) + return regset; + + if (!hisi_l3c_lock_ctrl_wait_finished(soc_l3c, regset, + HISI_L3C_LOCK_CTRL_LOCK_DONE)) { + hisi_soc_l3c_free_lock_reg_set(soc_l3c, regset); + return -EBUSY; + } + + writel(lower_32_bits(addr), + base + l3c_lock_reg_offset(HISI_L3C_LOCK_START_L, regset)); + writel(upper_32_bits(addr), + base + l3c_lock_reg_offset(HISI_L3C_LOCK_START_H, regset)); + writel(size, base + l3c_lock_reg_offset(HISI_L3C_LOCK_AREA, regset)); + + ctrl = readl(base + HISI_L3C_DYNAMIC_AUCTRL); + ctrl |= BIT(regset); + writel(ctrl, base + HISI_L3C_DYNAMIC_AUCTRL); + + ctrl = readl(base + l3c_lock_reg_offset(HISI_L3C_LOCK_CTRL, regset)); + ctrl = (ctrl | HISI_L3C_LOCK_CTRL_LOCK_EN) & + ~HISI_L3C_LOCK_CTRL_UNLOCK_EN; + writel(ctrl, base + l3c_lock_reg_offset(HISI_L3C_LOCK_CTRL, regset)); + + return 0; +} + +static int hisi_soc_l3c_poll_lock_done(struct hisi_soc_comp *l3c_comp, + phys_addr_t addr, size_t size) +{ + struct hisi_soc_l3c *soc_l3c = to_hisi_l3c(l3c_comp); + int regset; + + guard(spinlock)(&soc_l3c->reg_lock); + + regset = hisi_soc_l3c_get_locked_reg_set(soc_l3c, addr); + if (regset < 0) + return -EINVAL; + + if (!hisi_l3c_lock_ctrl_wait_finished(soc_l3c, regset, + HISI_L3C_LOCK_CTRL_LOCK_DONE)) + return -ETIMEDOUT; + + return 0; +} + +static int hisi_soc_l3c_do_unlock(struct hisi_soc_comp *l3c_comp, + phys_addr_t addr) +{ + struct hisi_soc_l3c *soc_l3c = to_hisi_l3c(l3c_comp); + void *base = soc_l3c->base; + int regset; + u32 ctrl; + + guard(spinlock)(&soc_l3c->reg_lock); + + regset = hisi_soc_l3c_get_locked_reg_set(soc_l3c, addr); + if (regset < 0) + return -EINVAL; + + if (!hisi_l3c_lock_ctrl_wait_finished(soc_l3c, regset, + HISI_L3C_LOCK_CTRL_UNLOCK_DONE)) + return -EBUSY; + + ctrl = readl(base + HISI_L3C_DYNAMIC_AUCTRL); + ctrl &= ~BIT(regset); + writel(ctrl, base + HISI_L3C_DYNAMIC_AUCTRL); + + ctrl = readl(base + l3c_lock_reg_offset(HISI_L3C_LOCK_CTRL, regset)); + ctrl = (ctrl | HISI_L3C_LOCK_CTRL_UNLOCK_EN) & + ~HISI_L3C_LOCK_CTRL_LOCK_EN; + writel(ctrl, base + l3c_lock_reg_offset(HISI_L3C_LOCK_CTRL, regset)); + + return 0; +} + +static int hisi_soc_l3c_poll_unlock_done(struct hisi_soc_comp *l3c_comp, + phys_addr_t addr) +{ + struct hisi_soc_l3c *soc_l3c = to_hisi_l3c(l3c_comp); + int regset; + + guard(spinlock)(&soc_l3c->reg_lock); + + regset = hisi_soc_l3c_get_locked_reg_set(soc_l3c, addr); + if (regset < 0) + return -EINVAL; + + if (!hisi_l3c_lock_ctrl_wait_finished(soc_l3c, regset, + HISI_L3C_LOCK_CTRL_UNLOCK_DONE)) + return -ETIMEDOUT; + + hisi_soc_l3c_free_lock_reg_set(soc_l3c, regset); + + return 0; +} + +/** + * hisi_soc_l3c_remove_locks - Remove all cache locks when the driver exits. + * + * @soc_l3c: The L3C instance on which the cache locks should be removed. + */ +static void hisi_soc_l3c_remove_locks(struct hisi_soc_l3c *soc_l3c) +{ + + void *base = soc_l3c->base; + unsigned long regset; + int timeout; + void *entry; + u32 ctrl; + + guard(spinlock)(&soc_l3c->reg_lock); + + xa_for_each(&soc_l3c->lock_sets, regset, entry) { + ctrl = readl(base + l3c_lock_reg_offset(HISI_L3C_LOCK_CTRL, + regset)); + ctrl = (ctrl | HISI_L3C_LOCK_CTRL_UNLOCK_EN) & + ~HISI_L3C_LOCK_CTRL_LOCK_EN; + writel(ctrl, base + l3c_lock_reg_offset(HISI_L3C_LOCK_CTRL, + regset)); + + timeout = hisi_l3c_lock_ctrl_wait_finished(soc_l3c, regset, + HISI_L3C_LOCK_CTRL_UNLOCK_DONE); + + /* + * If cache lock remove fails, inform user since the removal of + * driver cannot fail. + */ + if (timeout) + pr_err("failed to remove %lu-th cache lock.\n", regset); + } +} + +static int hisi_soc_l3c_init_lock_capacity(struct hisi_soc_l3c *soc_l3c, + struct device *dev) +{ + int ret; + u32 val; + + ret = device_property_read_u32(dev, HISI_L3C_MAX_LOCKREGION_NUM, &val); + if (ret || val <= 0) + return -EINVAL; + + soc_l3c->max_lock_num = val; + + ret = device_property_read_u32(dev, HISI_L3C_MAX_LOCKREGION_SIZE, &val); + if (ret || val <= 0) + return -EINVAL; + + soc_l3c->max_lock_size = HISI_L3C_MAX_LOCK_SIZE(val); + + soc_l3c->min_lock_size = soc_l3c->max_lock_num == 1 + ? HISI_L3C_LOCK_MIN_SIZE + : HISI_L3_CACHE_LINE_SIZE; + + return 0; +} + +static int hisi_soc_l3c_init_topology(struct hisi_soc_l3c *soc_l3c, + struct device *dev) +{ + soc_l3c->sccl_id = -1; + soc_l3c->ccl_id = -1; + + if (device_property_read_u32(dev, "hisilicon,scl-id", &soc_l3c->sccl_id) + || soc_l3c->sccl_id < 0) + return -EINVAL; + + if (device_property_read_u32(dev, "hisilicon,ccl-id", &soc_l3c->ccl_id) + || soc_l3c->ccl_id < 0) + return -EINVAL; + + return 0; +} + +static void hisi_init_associated_cpus(struct hisi_soc_l3c *soc_l3c) +{ + if (!cpumask_empty(&soc_l3c->associated_cpus)) + return; + cpumask_clear(&soc_l3c->associated_cpus); + cpumask_copy(&soc_l3c->comp.affinity_mask, &soc_l3c->associated_cpus); +} + +static struct hisi_soc_comp_ops hisi_soc_l3c_comp_ops = { + .do_lock = hisi_soc_l3c_do_lock, + .poll_lock_done = hisi_soc_l3c_poll_lock_done, + .do_unlock = hisi_soc_l3c_do_unlock, + .poll_unlock_done = hisi_soc_l3c_poll_unlock_done, +}; + +static struct hisi_soc_comp hisi_soc_l3c_comp = { + .ops = &hisi_soc_l3c_comp_ops, + .comp_type = BIT(HISI_SOC_L3C), +}; + +static int hisi_soc_l3c_probe(struct platform_device *pdev) +{ + struct hisi_soc_l3c *soc_l3c; + struct resource *mem; + int ret = 0; + + soc_l3c = devm_kzalloc(&pdev->dev, sizeof(*soc_l3c), GFP_KERNEL); + if (!soc_l3c) + return -ENOMEM; + + platform_set_drvdata(pdev, soc_l3c); + + mem = platform_get_resource(pdev, IORESOURCE_MEM, 0); + if (!mem) + return -ENODEV; + + /* + * L3C cache driver share the same register region with L3C uncore PMU + * driver in hardware's perspective, none of them should reserve the + * resource to itself only. Here exclusive access verification is + * avoided by calling devm_ioremap instead of devm_ioremap_resource to + * allow both drivers to exist at the same time. + */ + soc_l3c->base = devm_ioremap(&pdev->dev, mem->start, + resource_size(mem)); + if (IS_ERR_OR_NULL(soc_l3c->base)) + return PTR_ERR(soc_l3c->base); + + soc_l3c->comp = hisi_soc_l3c_comp; + soc_l3c->locked_size = 0; + spin_lock_init(&soc_l3c->reg_lock); + xa_init(&soc_l3c->lock_sets); + + ret = hisi_soc_l3c_init_lock_capacity(soc_l3c, &pdev->dev); + if (ret) + goto err_xa; + + hisi_init_associated_cpus(soc_l3c); + + ret = hisi_soc_l3c_init_topology(soc_l3c, &pdev->dev); + if (ret) + goto err_xa; + + ret = cpuhp_state_add_instance(hisi_l3c_cpuhp_state, &soc_l3c->node); + if (ret) + goto err_xa; + + ret = hisi_soc_comp_inst_add(&soc_l3c->comp); + if (ret) + goto err_hotplug; + + return ret; + +err_hotplug: + cpuhp_state_remove_instance_nocalls(hisi_l3c_cpuhp_state, + &soc_l3c->node); + +err_xa: + xa_destroy(&soc_l3c->lock_sets); + return ret; +} + +static int hisi_soc_l3c_remove(struct platform_device *pdev) +{ + struct hisi_soc_l3c *soc_l3c = platform_get_drvdata(pdev); + unsigned long idx; + struct hisi_soc_l3c_lock_region *entry; + + hisi_soc_comp_inst_del(&soc_l3c->comp); + + cpuhp_state_remove_instance_nocalls(hisi_l3c_cpuhp_state, + &soc_l3c->node); + + hisi_soc_l3c_remove_locks(soc_l3c); + + xa_for_each(&soc_l3c->lock_sets, idx, entry) { + entry = xa_erase(&soc_l3c->lock_sets, idx); + kfree(entry); + } + + xa_destroy(&soc_l3c->lock_sets); + + return 0; +} + +static void hisi_read_sccl_and_ccl_id(int *scclp, int *cclp) +{ + u64 mpidr = read_cpuid_mpidr(); + int aff3 = MPIDR_AFFINITY_LEVEL(mpidr, 3); + int aff2 = MPIDR_AFFINITY_LEVEL(mpidr, 2); + int aff1 = MPIDR_AFFINITY_LEVEL(mpidr, 1); + int sccl, ccl; + + if (mpidr & MPIDR_MT_BITMASK) { + sccl = aff3; + ccl = aff2; + } else { + sccl = aff2; + ccl = aff1; + } + + *scclp = sccl; + *cclp = ccl; +} + +static bool hisi_soc_l3c_is_associated(struct hisi_soc_l3c *soc_l3c) +{ + int sccl_id, ccl_id; + + hisi_read_sccl_and_ccl_id(&sccl_id, &ccl_id); + return sccl_id == soc_l3c->sccl_id && ccl_id == soc_l3c->ccl_id; +} + +static int hisi_soc_l3c_online_cpu(unsigned int cpu, struct hlist_node *node) +{ + struct hisi_soc_l3c *soc_l3c = + hlist_entry_safe(node, struct hisi_soc_l3c, node); + + if (!cpumask_test_cpu(cpu, &soc_l3c->associated_cpus)) { + if (!(hisi_soc_l3c_is_associated(soc_l3c))) + return 0; + + cpumask_set_cpu(cpu, &soc_l3c->associated_cpus); + cpumask_copy(&soc_l3c->comp.affinity_mask, + &soc_l3c->associated_cpus); + } + return 0; +} + +static const struct acpi_device_id hisi_l3c_acpi_match[] = { + { "HISI0501", }, + { } +}; +MODULE_DEVICE_TABLE(acpi, hisi_l3c_acpi_match); + +static struct platform_driver hisi_soc_l3c_driver = { + .driver = { + .name = "hisi_soc_l3c", + .acpi_match_table = hisi_l3c_acpi_match, + }, + .probe = hisi_soc_l3c_probe, + .remove = hisi_soc_l3c_remove, +}; + +static int __init hisi_soc_l3c_init(void) +{ + int ret; + + ret = cpuhp_setup_state_multi(CPUHP_AP_ONLINE_DYN, "hisi_soc_l3c", + hisi_soc_l3c_online_cpu, NULL); + if (ret < 0) + return ret; + hisi_l3c_cpuhp_state = ret; + + ret = platform_driver_register(&hisi_soc_l3c_driver); + if (ret) + cpuhp_remove_multi_state(hisi_l3c_cpuhp_state); + + return ret; +} +module_init(hisi_soc_l3c_init); + +static void __exit hisi_soc_l3c_exit(void) +{ + platform_driver_unregister(&hisi_soc_l3c_driver); + cpuhp_remove_multi_state(hisi_l3c_cpuhp_state); +} +module_exit(hisi_soc_l3c_exit); + +MODULE_DESCRIPTION("Driver supporting cache lockdown for Hisilicon L3 cache"); +MODULE_AUTHOR("Yushan Wang "); +MODULE_LICENSE("GPL"); diff --git a/include/uapi/misc/hisi_soc_cache/hisi_soc_cache.h b/include/uapi/misc/hisi_soc_cache/hisi_soc_cache.h new file mode 100644 index 0000000000000000000000000000000000000000..8b190941c8050e0928c3fe04b28d0a73926ac6fb --- /dev/null +++ b/include/uapi/misc/hisi_soc_cache/hisi_soc_cache.h @@ -0,0 +1,37 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later WITH Linux-syscall-note */ +/* Copyright (c) 2024-2024 HiSilicon Limited. */ +#ifndef _UAPI_HISI_SOC_CACHE_H +#define _UAPI_HISI_SOC_CACHE_H + +#include + +/* HISI_CACHE_MAINTAIN: cache maintain operation for HiSilicon SoC */ +#define HISI_CACHE_MAINTAIN _IOW('C', 1, unsigned long) + +/* + * Further information of these operations can be found at: + * https://developer.arm.com/documentation/ihi0050/latest/ + */ +enum hisi_soc_cache_maint_type { + HISI_CACHE_MAINT_CLEANSHARED, + HISI_CACHE_MAINT_CLEANINVALID, +#ifdef __KERNEL__ + HISI_CACHE_MAINT_MAKEINVALID, +#endif + + HISI_CACHE_MAINT_MAX +}; + +/** + * struct hisi_soc_cache_ioctl_param - User data for hisi cache operates. + * @op_type: cache maintain type + * @addr: cache maintain address + * @size: cache maintain size + */ +struct hisi_soc_cache_ioctl_param { + enum hisi_soc_cache_maint_type op_type; + unsigned long addr; + unsigned long size; +}; + +#endif