Files
opensbi/lib/utils/cache/fdt_andes_llcache.c
T
Ben Zong-You Xie 4c5d9d9f93 lib: utils/andes: add the SMU and LLC registers to the root domain
When a hart implements Smepmp, S-mode and U-mode cannot reach either
device unless its region is shared explicitly. Suspend and resume then
take an access violation as soon as the supervisor touches the SMU or
the LLC.

Share both regions, and take their sizes from the DT nodes rather than
ignoring them.

Signed-off-by: Ben Zong-You Xie <ben717@andestech.com>
Reviewed-by: Anup Patel <anup@brainfault.org>
Link: https://lore.kernel.org/r/20260728081041.2724668-3-ben717@andestech.com
Signed-off-by: Anup Patel <anup@brainfault.org>
2026-09-01 10:46:23 +05:30

182 lines
5.0 KiB
C

/*
* SPDX-License-Identifier: BSD-2-Clause
*
* Copyright (c) 2025 Andes Technology Corporation
*/
#include <sbi/riscv_io.h>
#include <sbi/sbi_domain.h>
#include <sbi/sbi_error.h>
#include <sbi/sbi_hart.h>
#include <sbi/sbi_heap.h>
#include <sbi/sbi_scratch.h>
#include <sbi_utils/cache/fdt_cache.h>
#include <sbi_utils/fdt/fdt_driver.h>
#include <sbi_utils/hsm/fdt_hsm_andes_atcsmu.h>
#define LLCACHE_REG_CFG_OFFSET 0x0
#define LLCACHE_REG_CTRL_OFFSET 0x8
#define LLCACHE_REG_CCTL_CMD_OFFSET 0x40
#define LLCACHE_REG_CCTL_STATUS_OFFSET 0x80
#define LLCACHE_REG_CFG_MAP_MASK BIT(20)
#define LLCACHE_REG_CTRL_EN_MASK BIT(0)
#define LLCACHE_REG_CTRL_INIT_MASK BIT(14)
#define LLCACHE_REG_CCTL_STATUS_MASK GENMASK(3, 0)
#define LLCACHE_WBINVAL_ALL 0x12
struct andes_llcache {
struct cache_device dev;
void *base;
uint32_t cmd_stride;
uint32_t status_stride;
uint32_t status_core_stride;
};
#define to_llcache(_dev) container_of(_dev, struct andes_llcache, dev)
static bool andes_llcache_init_done(struct andes_llcache *llcache)
{
uint32_t llcache_ctrl;
void *ctrl_addr = (char *)llcache->base + LLCACHE_REG_CTRL_OFFSET;
llcache_ctrl = readl_relaxed(ctrl_addr);
return !EXTRACT_FIELD(llcache_ctrl, LLCACHE_REG_CTRL_INIT_MASK);
}
static bool andes_llcache_cctl_done(struct andes_llcache *llcache, uint32_t hartid)
{
uint32_t llcache_cctl_status;
void *cctl_status_addr = (char *)llcache->base + LLCACHE_REG_CCTL_STATUS_OFFSET +
hartid * llcache->status_stride;
llcache_cctl_status = readl_relaxed(cctl_status_addr);
return !EXTRACT_FIELD(llcache_cctl_status,
LLCACHE_REG_CCTL_STATUS_MASK <<
hartid * llcache->status_core_stride);
}
static int andes_llcache_flush_all(struct cache_device *dev)
{
uint32_t hartid = current_hartid();
struct andes_llcache *llcache = to_llcache(dev);
void *cctl_cmd_addr = (char *)llcache->base + LLCACHE_REG_CCTL_CMD_OFFSET +
hartid * llcache->cmd_stride;
/*
* Each command register corresponds to one CPU core, so each CPU core
* should only use its command registers to do the cache operation.
*/
writel(LLCACHE_WBINVAL_ALL, cctl_cmd_addr);
/* Wait for the command completion */
while (!andes_llcache_cctl_done(llcache, hartid))
;
return 0;
}
static int andes_llcache_enable(struct cache_device *dev, bool enable)
{
struct andes_llcache *llcache = to_llcache(dev);
u32 llcache_ctrl;
void *ctrl_addr = (char *)llcache->base + LLCACHE_REG_CTRL_OFFSET;
/*
* To properly enable the last level cache to cache both instructions
* and data, apply the following sequence:
*
* - Write the control register with the desired value, except the
* CEN field should be set to zero. Thus, store the control register
* value with the CEN field being 0 when disabling the last level
* cache.
* - Write the control register again using the same value of step 1
* with the CEN field being 1.
*/
if (enable) {
llcache_ctrl = atcsmu_read_scratch();
writel(llcache_ctrl, ctrl_addr);
writel(llcache_ctrl | LLCACHE_REG_CTRL_EN_MASK, ctrl_addr);
} else {
llcache_ctrl = readl(ctrl_addr);
atcsmu_write_scratch(llcache_ctrl & ~LLCACHE_REG_CTRL_EN_MASK);
writel(llcache_ctrl & ~LLCACHE_REG_CTRL_EN_MASK, ctrl_addr);
}
llcache_ctrl = readl(ctrl_addr);
if (enable != EXTRACT_FIELD(llcache_ctrl, LLCACHE_REG_CTRL_EN_MASK))
return SBI_EFAIL;
return SBI_OK;
}
static struct cache_ops andes_llcache_ops = {
.cache_flush_all = andes_llcache_flush_all,
.cache_enable = andes_llcache_enable,
};
static int andes_llcache_probe(const void *fdt, int nodeoff, const struct fdt_match *match)
{
int rc;
u64 llcache_base = 0, size;
struct andes_llcache *llcache;
struct cache_device *dev;
uint32_t llcache_cfg;
rc = fdt_get_node_addr_size(fdt, nodeoff, 0, &llcache_base, &size);
if (rc < 0 || !llcache_base || !size)
return SBI_ENODEV;
llcache = sbi_zalloc(sizeof(*llcache));
if (!llcache)
return SBI_ENOMEM;
dev = &llcache->dev;
dev->ops = &andes_llcache_ops;
rc = fdt_cache_add(fdt, nodeoff, dev);
if (rc) {
sbi_free(llcache);
return rc;
}
llcache->base = (void *)(ulong)llcache_base;
llcache_cfg = readl_relaxed((char *)llcache->base + LLCACHE_REG_CFG_OFFSET);
/* Configurations for V1/V0 memory map */
if (EXTRACT_FIELD(llcache_cfg, LLCACHE_REG_CFG_MAP_MASK)) {
llcache->cmd_stride = 0x1000;
llcache->status_stride = 0x1000;
llcache->status_core_stride = 0;
} else {
llcache->cmd_stride = 0x10;
llcache->status_stride = 0x0;
llcache->status_core_stride = 4;
}
if (sbi_hart_has_extension(sbi_scratch_thishart_ptr(), SBI_HART_EXT_SMEPMP)) {
rc = sbi_domain_root_add_memrange(
(unsigned long)llcache->base, (unsigned long)size, PAGE_SIZE,
SBI_DOMAIN_MEMREGION_MMIO |
SBI_DOMAIN_MEMREGION_SHARED_SURW_MRW);
if (rc)
return rc;
}
/* Wait for the hardware initialization done */
while (!andes_llcache_init_done(llcache))
;
return SBI_OK;
}
static const struct fdt_match andes_llcache_match[] = {
{ .compatible = "andestech,llcache" },
{},
};
const struct fdt_driver fdt_andes_llcache = {
.match_table = andes_llcache_match,
.init = andes_llcache_probe,
};