Commit c40d9710 authored by Ma Wupeng's avatar Ma Wupeng Committed by Wupeng Ma
Browse files

hisi: l3t: Add L3 cache driver for hisi

hulk inclusion
category: feature
bugzilla: https://gitee.com/openeuler/kernel/issues/I9UDJX



--------------------------------

Last Level Cache driver for platforms such Kunpeng 920. This provides
interfaces to enable LLC cache lockdown.

Signed-off-by: default avatarMa Wupeng <mawupeng1@huawei.com>
parent 3a9180b0
Loading
Loading
Loading
Loading
+9 −0
Original line number Diff line number Diff line
@@ -54,4 +54,13 @@ config HISI_HBMDEV_ACLS

	  If not sure say no.

config HISI_L3T
	tristate "Add support for l3t"
	depends on ARM64 && ACPI
	help
	  Last Level Cache driver for platforms such Kunpeng 920. This provides
	  interfaces to enable LLC cache lockdown.

	  If not sure say no.

endmenu
+2 −0
Original line number Diff line number Diff line
@@ -4,3 +4,5 @@ obj-$(CONFIG_KUNPENG_HCCS) += kunpeng_hccs.o
obj-$(CONFIG_HISI_HBMDEV)	+= hisi_hbmdev.o
obj-$(CONFIG_HISI_HBMCACHE)	+= hisi_hbmcache.o
obj-$(CONFIG_ARM64_PBHA)		+= pbha.o
hisi_l3t-objs			:= hisi_lockdown.o l3t.o
obj-$(CONFIG_HISI_L3T)		+= hisi_l3t.o
+45 −0
Original line number Diff line number Diff line
// SPDX-License-Identifier: GPL-2.0
/*
 * Copyright (C) Huawei Technologies Co., Ltd. 2024. All rights reserved.
 */

#ifndef _HISI_L3T_H
#define _HISI_L3T_H

/* L3T register definition */
#define L3T_VERSION		0x1cf0
#define L3T_LOCK_CTRL		0x0440
#define L3T_LOCK_AREA		0x0444
#define L3T_LOCK_START_L	0x0448
#define L3T_LOCK_START_H	0x044C
#define L3T_LOCK_STEP		0x10

#define L3T_REG_NUM		4

extern struct mutex l3t_mutex;

struct hisi_l3t {
	struct device *dev;
	void __iomem *base;
	int sccl_id;
	int ccl_id;
	int nid;
};

struct hisi_sccl {
	int nid;			/* numa node id */
	int ccl_cnt;			/* ccl count for this sccl */
	struct hisi_l3t **l3t;
};

struct hisi_sccl *hisi_l3t_get_sccl(int nid);
void hisi_l3t_read(struct hisi_l3t *l3t, int slot_idx, unsigned long *s_addr,
		     int *size);
void hisi_l3t_lock(struct hisi_l3t *l3t, int slot_idx, unsigned long s_addr,
		   int size);
void hisi_l3t_unlock(struct hisi_l3t *l3t, int slot_idx);

int l3t_shared_lock(int nid, unsigned long pfn, unsigned long size);
int l3t_shared_unlock(int nid, unsigned long pfn, unsigned long size);

#endif
+140 −0
Original line number Diff line number Diff line
// SPDX-License-Identifier: GPL-2.0
/*
 * Copyright (C) Huawei Technologies Co., Ltd. 2024. All rights reserved.
 */

#define pr_fmt(fmt) "hisi_l3t: " fmt

#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/init.h>
#include <linux/kallsyms.h>
#include <linux/mm.h>

#include "hisi_l3t.h"

struct hisi_l3t_ops {
	bool (*l3t_slot_test)(struct hisi_l3t *l3t, int slot_idx,
			      unsigned long addr, int size);
	void (*l3t_slot_action)(struct hisi_l3t *l3t, int slot_idx,
				unsigned long addr, int size);
};

static int hisi_l3t_sccl_action(struct hisi_sccl *sccl, unsigned long addr,
				int size, struct hisi_l3t_ops *ops)
{
	struct hisi_l3t *l3t;
	int i, slot_idx;

	/*
	 * in shared mode, all l3t belongs to one sccl is the same.
	 * use the first l3t to test.
	 */
	l3t = sccl->l3t[0];

	mutex_lock(&l3t_mutex);
	for (slot_idx = 0; slot_idx < L3T_REG_NUM; slot_idx++) {
		if (ops->l3t_slot_test(l3t, slot_idx, addr, size))
			break;
	}

	if (slot_idx >= L3T_REG_NUM) {
		mutex_unlock(&l3t_mutex);
		return -EINVAL;
	}

	for (i = 0; i < sccl->ccl_cnt; i++) {
		l3t = sccl->l3t[i];
		if (l3t)
			ops->l3t_slot_action(l3t, slot_idx, addr, size);
	}
	mutex_unlock(&l3t_mutex);

	return 0;
}

struct hisi_sccl *get_valid_sccl(int nid)
{
	struct hisi_sccl *sccl;

	sccl = hisi_l3t_get_sccl(nid);
	if (!sccl || !sccl->ccl_cnt)
		return NULL;

	if (!sccl->l3t || !sccl->l3t[0])
		return NULL;

	return sccl;
}

static bool hisi_l3t_test_empty(struct hisi_l3t *l3t, int slot_idx,
				     unsigned long __always_unused addr,
				     int __always_unused size)
{
	unsigned long _addr;
	int _size;

	hisi_l3t_read(l3t, slot_idx, &_addr, &_size);

	return _addr == 0;
}

int l3t_shared_lock(int nid, unsigned long pfn, unsigned long size)
{
	struct hisi_l3t_ops ops = {
		.l3t_slot_test = hisi_l3t_test_empty,
		.l3t_slot_action = hisi_l3t_lock,
	};
	struct hisi_sccl *sccl;
	int ret;

	sccl = get_valid_sccl(nid);
	if (!sccl)
		return -ENODEV;

	ret = hisi_l3t_sccl_action(sccl, pfn << PAGE_SHIFT, size, &ops);
	if (ret)
		return -ENOMEM;

	return 0;
}
EXPORT_SYMBOL_GPL(l3t_shared_lock);

static bool hisi_l3t_test_equal(struct hisi_l3t *l3t, int slot_idx,
				unsigned long addr, int size)
{
	unsigned long _addr;
	int _size;

	hisi_l3t_read(l3t, slot_idx, &_addr, &_size);

	return (_addr == addr && _size == size);
}

static void hisi_l3t_do_unlock(struct hisi_l3t *l3t, int slot_idx,
			       unsigned long __always_unused addr,
			       int __always_unused size)
{
	hisi_l3t_unlock(l3t, slot_idx);
}

int l3t_shared_unlock(int nid, unsigned long pfn, unsigned long size)
{
	struct hisi_l3t_ops ops = {
		.l3t_slot_test = hisi_l3t_test_equal,
		.l3t_slot_action = hisi_l3t_do_unlock,
	};
	struct hisi_sccl *sccl;
	int ret;

	sccl = get_valid_sccl(nid);
	if (!sccl)
		return -ENODEV;

	ret = hisi_l3t_sccl_action(sccl, pfn << PAGE_SHIFT, size, &ops);
	if (ret)
		return -EINVAL;

	return 0;
}
EXPORT_SYMBOL_GPL(l3t_shared_unlock);
+263 −0
Original line number Diff line number Diff line
// SPDX-License-Identifier: GPL-2.0
/*
 * Copyright (C) Huawei Technologies Co., Ltd. 2024. All rights reserved.
 */

#define pr_fmt(fmt) "hisi_l3t: " fmt

#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/init.h>
#include <linux/kallsyms.h>
#include <linux/mm.h>
#include <linux/slab.h>
#include <linux/platform_device.h>
#include <linux/smp.h>
#include <linux/xarray.h>
#include <linux/irqchip.h>

#include "hisi_l3t.h"

#define LOCK_EN		BIT(0)
#define LOCK_DONE	BIT(1)
#define UNLOCK_EN	BIT(2)
#define UNLOCK_DONE	BIT(3)

DEFINE_MUTEX(l3t_mutex);
static DEFINE_XARRAY(l3t_mapping);

static int sccl_to_node_id(int id)
{
	int sccl_id, cpu;
	u64 mpidr;

	for_each_possible_cpu(cpu) {
		mpidr = cpu_logical_map(cpu);
		sccl_id = MPIDR_AFFINITY_LEVEL(mpidr, 3);
		if (sccl_id == id)
			return cpu_to_node(cpu);
	}

	pr_err("invalid sccl id: %d\n", id);
	return -EINVAL;
}

static int hisi_l3t_init_data(struct platform_device *pdev,
			      struct hisi_l3t *l3t)
{
	if (device_property_read_u32(&pdev->dev, "hisilicon,scl-id",
				     &l3t->sccl_id))
		return -EINVAL;

	if (device_property_read_u32(&pdev->dev, "hisilicon,ccl-id",
				     &l3t->ccl_id))
		return -EINVAL;

	l3t->base = devm_platform_ioremap_resource(pdev, 0);
	if (IS_ERR(l3t->base))
		return PTR_ERR(l3t->base);

	l3t->nid = sccl_to_node_id(l3t->sccl_id);
	if (l3t->nid < 0)
		return -EINVAL;

	return 0;
}

static int hisi_l3t_insert_to_sccl(struct hisi_sccl *sccl, struct hisi_l3t *l3t)
{
	void *tmp_l3t;

	if (sccl->ccl_cnt > l3t->ccl_id)
		goto set;

	tmp_l3t = krealloc(sccl->l3t,
			   (l3t->ccl_id + 1) * sizeof(struct hisi_l3t *),
			   GFP_KERNEL);
	if (!tmp_l3t)
		return -ENOMEM;

	sccl->ccl_cnt = l3t->ccl_id + 1;
	sccl->l3t = tmp_l3t;

set:
	sccl->l3t[l3t->ccl_id] = l3t;
	return 0;
}

/*
 * Use xarray to store the mapping b/t nid to sccl
 * all ccls belong be one sccl is store with vla in sccl->l3t
 */
static int hisi_l3t_init_mapping(struct device *dev, struct hisi_l3t *l3t)
{
	struct hisi_sccl *sccl;
	int ret = -ENOMEM;

	mutex_lock(&l3t_mutex);
	sccl = xa_load(&l3t_mapping, l3t->nid);
	if (!sccl) {
		sccl = devm_kzalloc(dev, sizeof(*sccl), GFP_KERNEL);
		if (!sccl)
			goto unlock;
		sccl->nid = l3t->nid;

		xa_store(&l3t_mapping, l3t->nid, sccl, GFP_KERNEL);
	}

	ret = hisi_l3t_insert_to_sccl(sccl, l3t);
unlock:
	mutex_unlock(&l3t_mutex);

	return ret;
}

/* write bit b_update and wait bit b_wait to be zero */
static void __l3t_update_and_wait(void __iomem *addr, u32 b_update, u32 b_wait)
{
	u32 val;

	writel(b_update, addr);

	do {
		val = readl(addr);
	} while ((val & b_wait) == 0);
}

static void __l3t_maintain(void __iomem *addr, int slot_idx,
			   unsigned long s_addr, int size, bool lock)
{
	if (slot_idx < 0 || slot_idx >= L3T_REG_NUM) {
		pr_err("slot index is invalid: %d\n", slot_idx);
		return;
	}

	if (!addr) {
		pr_err("invalid unlock addr\n");
		return;
	}

	addr += slot_idx * L3T_LOCK_STEP;

	writeq(s_addr, addr + L3T_LOCK_START_L);
	writel(size, addr + L3T_LOCK_AREA);

	if (lock)
		__l3t_update_and_wait(addr + L3T_LOCK_CTRL, LOCK_EN, LOCK_DONE);
	else
		__l3t_update_and_wait(addr + L3T_LOCK_CTRL, UNLOCK_EN,
				      UNLOCK_DONE);
}

void hisi_l3t_lock(struct hisi_l3t *l3t, int slot_idx, unsigned long s_addr,
		   int size)
{
	__l3t_maintain(l3t->base, slot_idx, s_addr, size, true);

	pr_debug("lock success. addr: %#lx, slot: %d, s_addr: %#lx, size: %#x\n",
		(unsigned long)l3t->base, slot_idx, s_addr, size);
}

void hisi_l3t_unlock(struct hisi_l3t *l3t, int slot_idx)
{
	__l3t_maintain(l3t->base, slot_idx, 0, 0, false);

	pr_debug("unlock success. addr: %#lx, slot: %d\n",
		 (unsigned long)l3t->base, slot_idx);
}

static void hisi_l3t_read_inner(void __iomem *addr, int locksel,
				unsigned long *s_addr, int *size)
{
	if (!addr) {
		*s_addr = 0;
		*size = 0;
		pr_err("invalid unlock addr\n");
		return;
	}

	*s_addr = readq(addr + L3T_LOCK_START_L + locksel * L3T_LOCK_STEP);
	*size = readl(addr + L3T_LOCK_AREA + locksel * L3T_LOCK_STEP);
}

void hisi_l3t_read(struct hisi_l3t *l3t, int slot_idx, unsigned long *s_addr,
		   int *size)
{
	if (slot_idx < 0 || slot_idx >= L3T_REG_NUM) {
		pr_err("slot index is invalid: %d\n", slot_idx);
		return;
	}

	return hisi_l3t_read_inner(l3t->base, slot_idx, s_addr, size);
}

struct hisi_sccl *hisi_l3t_get_sccl(int nid)
{
	return xa_load(&l3t_mapping, nid);
}

static const struct acpi_device_id hisi_l3t_acpi_match[] = {
	{ "HISI0501", },
	{}
};
MODULE_DEVICE_TABLE(acpi, hisi_l3t_acpi_match);

static int hisi_l3t_probe(struct platform_device *pdev)
{
	struct hisi_l3t *l3t;
	int ret;

	l3t = devm_kzalloc(&pdev->dev, sizeof(*l3t), GFP_KERNEL);
	if (!l3t)
		return -ENOMEM;

	platform_set_drvdata(pdev, l3t);

	ret = hisi_l3t_init_data(pdev, l3t);
	if (!ret) {
		l3t->dev = &pdev->dev;
		ret = hisi_l3t_init_mapping(&pdev->dev, l3t);
	}

	return ret;
}

static struct platform_driver hisi_l3t_driver = {
	.driver = {
		.name = "hisi_l3t",
		.acpi_match_table = ACPI_PTR(hisi_l3t_acpi_match),
		.suppress_bind_attrs = true,
	},
	.probe = hisi_l3t_probe,
};

static int __init hisi_l3t_init(void)
{
	mutex_init(&l3t_mutex);
	xa_init(&l3t_mapping);

	return platform_driver_register(&hisi_l3t_driver);
}
module_init(hisi_l3t_init);

static void hisi_l3t_destroy_sccl(void)
{
	struct hisi_sccl *sccl;
	unsigned long nid;

	xa_for_each(&l3t_mapping, nid, sccl)
		kfree(sccl->l3t);
}

static void __exit hisi_l3t_exit(void)
{
	hisi_l3t_destroy_sccl();
	xa_destroy(&l3t_mapping);

	platform_driver_unregister(&hisi_l3t_driver);
}
module_exit(hisi_l3t_exit);

MODULE_DESCRIPTION("HiSilicon SoC L3T driver");
MODULE_LICENSE("GPL v2");
MODULE_AUTHOR("Ma Wupeng <mawupeng1@huawei.com>");