lists.openwall.net   lists  /  announce  owl-users  owl-dev  john-users  john-dev  passwdqc-users  yescrypt  popa3d-users  /  oss-security  kernel-hardening  musl  sabotage  tlsify  passwords  /  crypt-dev  xvendor  /  Bugtraq  Full-Disclosure  linux-kernel  linux-netdev  linux-ext4  linux-hardening  linux-cve-announce  PHC 
Open Source and information security mailing list archives
 
Hash Suite: Windows password security audit tool. GUI, reports in PDF.
[<prev] [next>] [<thread-prev] [day] [month] [year] [list]
Message-ID: <20251125080542.3721829-3-wangyushan12@huawei.com>
Date: Tue, 25 Nov 2025 16:05:41 +0800
From: Yushan Wang <wangyushan12@...wei.com>
To: <xuwei5@...ilicon.com>, <Jonathan.Cameron@...wei.com>,
	<wanghuiqiang@...wei.com>, <linux-kernel@...r.kernel.org>,
	<linux-arm-kernel@...ts.infradead.org>, <linuxarm@...wei.com>
CC: <prime.zeng@...ilicon.com>, <fanghao11@...wei.com>,
	<wangyushan12@...wei.com>
Subject: [RFC PATCH 2/3] soc cache: L3 cache lockdown support for HiSilicon SoC

This driver implements the interface exposed by framework, passes cache
lock/unlock requests to hardware.

The number of locked memory region is limited according to firmware,
and the total size of locked memory regions must be less than 70% of
cache size.

Signed-off-by: Yushan Wang <wangyushan12@...wei.com>
---
 drivers/soc/hisilicon/hisi_soc_l3c.c | 520 ++++++++++++++++++++++++++-
 1 file changed, 519 insertions(+), 1 deletion(-)

diff --git a/drivers/soc/hisilicon/hisi_soc_l3c.c b/drivers/soc/hisilicon/hisi_soc_l3c.c
index 2c196c4dfff1..1e3419436173 100644
--- a/drivers/soc/hisilicon/hisi_soc_l3c.c
+++ b/drivers/soc/hisilicon/hisi_soc_l3c.c
@@ -8,19 +8,49 @@
 
 #define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
 
+#include <linux/acpi.h>
 #include <linux/cleanup.h>
+#include <linux/cpuhotplug.h>
 #include <linux/init.h>
+#include <linux/io.h>
+#include <linux/iopoll.h>
 #include <linux/mm.h>
 #include <linux/miscdevice.h>
 #include <linux/module.h>
+#include <linux/platform_device.h>
 #include <linux/spinlock.h>
 #include <linux/spinlock_types.h>
 #include <linux/types.h>
+#include <linux/xarray.h>
 
 #include <asm/cputype.h>
 
 #include <uapi/misc/hisi_l3c.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_LOCK_CTRL_POLL_MAX_US	10000
+
+/* 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 to_hisi_l3c(p) container_of((p), struct hisi_l3c, comp)
 
 /**
@@ -85,8 +115,280 @@ struct hisi_soc_comp_list {
 	spinlock_t lock;
 };
 
+struct hisi_l3c {
+	struct hisi_soc_comp comp;
+	cpumask_t associated_cpus;
+
+	/* Stores the first address locked by each register sets. */
+	struct xarray lock_sets;
+	/* Locks lock_sets to forbid overlapping access. */
+	spinlock_t reg_lock;
+
+	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;
+};
+
+static int hisi_l3c_cpuhp_state;
+
 static struct hisi_soc_comp_list l3c_devs;
 
+/**
+ * hisi_l3c_alloc_lock_reg_set - Allocate an available control register set
+ *				     of L3 cache for lock & unlock operations.
+ * @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_l3c_alloc_lock_reg_set(struct hisi_l3c *l3c, phys_addr_t addr, size_t size)
+{
+	struct hisi_l3c_lock_info *info = l3c->comp.private;
+	struct hisi_l3c_lock_region *lr;
+	void *entry;
+	int idx, ret;
+
+	if (size > info->lock_size)
+		return -EINVAL;
+
+	for (idx = 0; idx < info->lock_region_num; ++idx) {
+		entry = xa_load(&l3c->lock_sets, idx);
+		if (!entry)
+			break;
+	}
+
+	if (idx > info->lock_region_num)
+		return -EBUSY;
+
+	lr = kzalloc(sizeof(*lr), GFP_KERNEL);
+	if (!lr)
+		return -ENOMEM;
+
+	lr->addr = addr;
+	lr->size = size;
+
+	ret = xa_alloc(&l3c->lock_sets, &idx, lr, xa_limit_31b, GFP_KERNEL);
+	if (ret) {
+		kfree(lr);
+		return ret;
+	}
+
+	info->lock_size -= size;
+	info->lock_region_num -= 1;
+
+	return idx;
+}
+
+/**
+ * hisi_l3c_get_locked_reg_set - Get the index of an allocated register set
+ *				     by locked address.
+ * @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_l3c_get_locked_reg_set(struct hisi_l3c *l3c, phys_addr_t addr)
+{
+	struct hisi_l3c_lock_region *entry;
+	unsigned long idx;
+
+	xa_for_each(&l3c->lock_sets, idx, entry) {
+		if (entry->addr == addr)
+			return idx;
+	}
+	return -EINVAL;
+}
+
+/**
+ * hisi_l3c_free_lock_reg_set - Free an allocated register set by locked
+ *				    address.
+ *
+ * @l3c:	The L3C instance on which the register set is allocated.
+ * @regset:	ID of Register set to be freed.
+ */
+static void hisi_l3c_free_lock_reg_set(struct hisi_l3c *l3c, int regset)
+{
+	struct hisi_l3c_lock_info *info = l3c->comp.private;
+	struct hisi_l3c_lock_region *entry;
+
+	if (regset < 0)
+		return;
+
+	entry = xa_erase(&l3c->lock_sets, regset);
+	if (!entry)
+		return;
+
+	info->lock_size += entry->size;
+	info->lock_region_num += 1;
+	kfree(entry);
+}
+
+static bool hisi_l3c_lock_wait_finished(struct hisi_l3c *l3c, int regset)
+{
+	void *reg = l3c->base + l3c_lock_reg_offset(HISI_L3C_LOCK_CTRL, regset);
+	/* Wait until neither lock or unlock operation is going on. */
+	u32 mask = HISI_L3C_LOCK_CTRL_LOCK_DONE | HISI_L3C_LOCK_CTRL_UNLOCK_DONE;
+	u32 val;
+
+	/*
+	 * Lock/unlock done bits are initially 0 if no lock operation was ever
+	 * issued, and will be set until next operation comes.
+	 * Check if this is the first lock operation after boot by checking if
+	 * the register is 0. If so, proceed with the operation.
+	 */
+	val = readl(reg);
+	if (!val)
+		return true;
+
+	return !readl_poll_timeout_atomic(reg, val, val & mask,
+			HISI_L3C_LOCK_CTRL_POLL_GAP_US,
+			HISI_L3C_LOCK_CTRL_POLL_MAX_US);
+}
+
+static int hisi_l3c_do_lock(struct hisi_soc_comp *comp, phys_addr_t addr, size_t size)
+{
+	struct hisi_l3c *l3c = to_hisi_l3c(comp);
+	struct hisi_l3c_lock_info *info = l3c->comp.private;
+	void *base = l3c->base;
+	int regset;
+	u32 ctrl;
+
+	if (info->address_alignment && addr % size != 0)
+		return -EINVAL;
+
+	if (size < info->min_lock_size || size > info->max_lock_size)
+		return -EINVAL;
+
+	guard(spinlock)(&l3c->reg_lock);
+
+	regset = hisi_l3c_alloc_lock_reg_set(l3c, addr, size);
+	if (regset < 0)
+		return -EBUSY;
+
+	if (!hisi_l3c_lock_wait_finished(l3c, regset)) {
+		hisi_l3c_free_lock_reg_set(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_l3c_poll_lock_done(struct hisi_soc_comp *comp, phys_addr_t addr, size_t size)
+{
+	struct hisi_l3c *l3c = to_hisi_l3c(comp);
+	int regset;
+
+	guard(spinlock)(&l3c->reg_lock);
+
+	regset = hisi_l3c_get_locked_reg_set(l3c, addr);
+	if (regset < 0)
+		return -EINVAL;
+
+	if (!hisi_l3c_lock_wait_finished(l3c, regset))
+		return -ETIMEDOUT;
+
+	return 0;
+}
+
+static int hisi_l3c_do_unlock(struct hisi_soc_comp *comp, phys_addr_t addr)
+{
+	struct hisi_l3c *l3c = to_hisi_l3c(comp);
+	void *base = l3c->base;
+	int regset;
+	u32 ctrl;
+
+	guard(spinlock)(&l3c->reg_lock);
+
+	regset = hisi_l3c_get_locked_reg_set(l3c, addr);
+	if (regset < 0)
+		return -EINVAL;
+
+	if (!hisi_l3c_lock_wait_finished(l3c, regset))
+		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_l3c_poll_unlock_done(struct hisi_soc_comp *comp, phys_addr_t addr)
+{
+	struct hisi_l3c *l3c = to_hisi_l3c(comp);
+	int regset;
+
+	guard(spinlock)(&l3c->reg_lock);
+
+	regset = hisi_l3c_get_locked_reg_set(l3c, addr);
+	if (regset < 0)
+		return -EINVAL;
+
+	if (!hisi_l3c_lock_wait_finished(l3c, regset))
+		return -ETIMEDOUT;
+
+	hisi_l3c_free_lock_reg_set(l3c, regset);
+
+	return 0;
+}
+
+static void hisi_l3c_remove_locks(struct hisi_l3c *l3c)
+{
+	void *base = l3c->base;
+	unsigned long regset;
+	void *entry;
+
+	guard(spinlock)(&l3c->reg_lock);
+
+	xa_for_each(&l3c->lock_sets, regset, entry) {
+		int timeout;
+		u32 ctrl;
+
+		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_wait_finished(l3c, regset);
+		if (timeout)
+			pr_err("failed to remove %lu-th cache lock.\n", regset);
+	}
+}
+
 static int hisi_l3c_lock(int cpu, phys_addr_t addr, size_t size)
 {
 	struct hisi_soc_comp *comp;
@@ -315,6 +617,196 @@ static int hisi_l3c_lock_restriction(unsigned long arg)
 	return -ENODEV;
 }
 
+static int hisi_l3c_init_lock_capacity(struct hisi_l3c *l3c, struct device *dev)
+{
+	int ret;
+	u32 val;
+
+	struct hisi_l3c_lock_info *info __free(kfree) = kmalloc(sizeof(*info), GFP_KERNEL);
+	if (!info)
+		return -ENOMEM;
+
+	ret = device_property_read_u32(dev, "hisilicon,l3c-lockregion-num", &val);
+	if (ret || val <= 0)
+		return -EINVAL;
+
+	info->lock_region_num = val;
+
+	ret = device_property_read_u32(dev, "hisilicon,l3c-max-single-lockregion-size", &val);
+	if (ret || val <= 0)
+		return -EINVAL;
+
+	info->lock_size = HISI_L3C_MAX_LOCK_SIZE(val);
+	info->address_alignment = info->lock_region_num == 1;
+	info->max_lock_size = HISI_L3C_MAX_LOCK_SIZE(val);
+	info->min_lock_size = info->lock_region_num == 1
+					? HISI_L3C_LOCK_MIN_SIZE
+					: HISI_L3_CACHE_LINE_SIZE;
+
+	l3c->comp.private = no_free_ptr(info);
+
+	return 0;
+}
+
+static int hisi_l3c_init_topology(struct hisi_l3c *l3c, struct device *dev)
+{
+	l3c->sccl_id = -1;
+	l3c->ccl_id = -1;
+
+	if (device_property_read_u32(dev, "hisilicon,scl-id", &l3c->sccl_id) ||
+	    l3c->sccl_id < 0)
+		return -EINVAL;
+
+	if (device_property_read_u32(dev, "hisilicon,ccl-id", &l3c->ccl_id) ||
+	    l3c->ccl_id < 0)
+		return -EINVAL;
+
+	return 0;
+}
+
+static void hisi_init_associated_cpus(struct hisi_l3c *l3c)
+{
+	if (!cpumask_empty(&l3c->associated_cpus))
+		return;
+	cpumask_clear(&l3c->associated_cpus);
+	cpumask_copy(&l3c->comp.affinity_mask, &l3c->associated_cpus);
+}
+
+static struct hisi_soc_comp_ops hisi_comp_ops = {
+	.do_lock = hisi_l3c_do_lock,
+	.poll_lock_done = hisi_l3c_poll_lock_done,
+	.do_unlock = hisi_l3c_do_unlock,
+	.poll_unlock_done = hisi_l3c_poll_unlock_done,
+};
+
+static struct hisi_soc_comp hisi_comp = {
+	.ops = &hisi_comp_ops,
+};
+
+static int hisi_l3c_probe(struct platform_device *pdev)
+{
+	struct hisi_l3c *l3c;
+	struct resource *mem;
+	int ret;
+
+	l3c = devm_kzalloc(&pdev->dev, sizeof(*l3c), GFP_KERNEL);
+	if (!l3c)
+		return -ENOMEM;
+
+	platform_set_drvdata(pdev, l3c);
+
+	mem = platform_get_resource(pdev, IORESOURCE_MEM, 0);
+	if (!mem)
+		return -ENODEV;
+
+	l3c->base = devm_ioremap(&pdev->dev, mem->start, resource_size(mem));
+	if (IS_ERR_OR_NULL(l3c->base))
+		return PTR_ERR(l3c->base);
+
+	l3c->comp = hisi_comp;
+	spin_lock_init(&l3c->reg_lock);
+	xa_init_flags(&l3c->lock_sets, XA_FLAGS_ALLOC);
+
+	ret = hisi_l3c_init_lock_capacity(l3c, &pdev->dev);
+	if (ret)
+		goto err_xa;
+
+	hisi_init_associated_cpus(l3c);
+
+	ret = hisi_l3c_init_topology(l3c, &pdev->dev);
+	if (ret)
+		goto err_xa;
+
+	ret = cpuhp_state_add_instance(hisi_l3c_cpuhp_state, &l3c->node);
+	if (ret)
+		goto err_xa;
+
+	hisi_soc_comp_add(&l3c->comp);
+
+	return 0;
+
+err_xa:
+	xa_destroy(&l3c->lock_sets);
+	return ret;
+}
+
+static void hisi_l3c_remove(struct platform_device *pdev)
+{
+	struct hisi_l3c *l3c = platform_get_drvdata(pdev);
+	unsigned long idx;
+	struct hisi_l3c_lock_region *entry;
+
+	hisi_l3c_remove_locks(l3c);
+
+	hisi_soc_comp_del(&l3c->comp);
+
+	cpuhp_state_remove_instance_nocalls(hisi_l3c_cpuhp_state, &l3c->node);
+
+	xa_for_each(&l3c->lock_sets, idx, entry)
+		entry = xa_erase(&l3c->lock_sets, idx);
+
+	xa_destroy(&l3c->lock_sets);
+}
+
+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_l3c_is_associated(struct hisi_l3c *l3c)
+{
+	int sccl_id, ccl_id;
+
+	hisi_read_sccl_and_ccl_id(&sccl_id, &ccl_id);
+
+	return sccl_id == l3c->sccl_id && ccl_id == l3c->ccl_id;
+}
+
+static int hisi_l3c_online_cpu(unsigned int cpu, struct hlist_node *node)
+{
+	struct hisi_l3c *l3c = hlist_entry_safe(node, struct hisi_l3c, node);
+
+	if (!cpumask_test_cpu(cpu, &l3c->associated_cpus)) {
+		if (!(hisi_l3c_is_associated(l3c)))
+			return 0;
+
+		cpumask_set_cpu(cpu, &l3c->associated_cpus);
+		cpumask_copy(&l3c->comp.affinity_mask,
+			     &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_l3c_driver = {
+	.driver = {
+		.name = "hisi_l3c",
+		.acpi_match_table = hisi_l3c_acpi_match,
+	},
+	.probe = hisi_l3c_probe,
+	.remove = hisi_l3c_remove,
+};
+
 static long hisi_l3c_ioctl(struct file *file, u32 cmd, unsigned long arg)
 {
 	switch (cmd) {
@@ -340,15 +832,41 @@ static struct miscdevice l3c_miscdev = {
 
 static int __init hisi_l3c_init(void)
 {
+	int ret;
+
 	spin_lock_init(&l3c_devs.lock);
 	INIT_LIST_HEAD(&l3c_devs.node);
 
-	return misc_register(&l3c_miscdev);
+	ret = misc_register(&l3c_miscdev);
+	if (ret)
+		return ret;
+
+	ret = cpuhp_setup_state_multi(CPUHP_AP_ONLINE_DYN, "hisi_l3c",
+				      hisi_l3c_online_cpu, NULL);
+	if (ret < 0)
+		goto err_hp;
+
+	hisi_l3c_cpuhp_state = ret;
+
+	ret = platform_driver_register(&hisi_l3c_driver);
+	if (ret)
+		goto err_plat;
+
+	return 0;
+
+err_plat:
+	cpuhp_remove_multi_state(CPUHP_AP_ONLINE_DYN);
+err_hp:
+	misc_deregister(&l3c_miscdev);
+
+	return ret;
 }
 module_init(hisi_l3c_init);
 
 static void __exit hisi_l3c_exit(void)
 {
+	platform_driver_unregister(&hisi_l3c_driver);
+	cpuhp_remove_multi_state(CPUHP_AP_ONLINE_DYN);
 	misc_deregister(&l3c_miscdev);
 	hisi_soc_comp_del(NULL);
 }
-- 
2.33.0


Powered by blists - more mailing lists

Powered by Openwall GNU/*/Linux Powered by OpenVZ