mirror of
https://github.com/riscv-software-src/opensbi.git
synced 2026-09-08 18:21:29 +01:00
decode_pmaaddrx() reconstructs the NAPOT region start and size from a
pmaaddr CSR value using "1 << (k + 3)" and "1 << k". The integer
literal 1 has type int, so these shifts are performed in 32-bit
precision. Shifting a 32-bit value by 32 or more bits is undefined
behavior, and on RV64 the compiler emits sllw, which truncates the
shift amount modulo 32.
As a result, any PMA region with size >= 4 GiB (k >= 29) is decoded
incorrectly. For example, on the Andes QiLai SoC the PCIe region
0x1000000000 - 0x17ffffffff (pmaaddr = 0x4ffffffff, k = 32) is decoded
as an 8-byte region at 0x13fffffffc.
This is not merely cosmetic: decode_pmaaddrx() is used by
has_pma_region_overlap() and andes_sbi_free_pma(), so overlap checks
are performed against bogus ranges and freeing such an entry by its
physical address always fails.
Promote the shifts to unsigned long so they are performed in the
native register width.
Fixes: aa56084c4d ("platform: generic: andes: add a new Andes SBI call to set up a PMA entry")
Signed-off-by: Randolph Lin <randolph@andestech.com>
Tested-by: Benoît Monin <benoit.monin@bootlin.com>
Reviewed-by: Anup Patel <anup@brainfault.org>
Link: https://lore.kernel.org/r/20260729092317.2848665-1-randolph@andestech.com
Signed-off-by: Anup Patel <anup@brainfault.org>
370 lines
8.4 KiB
C
370 lines
8.4 KiB
C
// SPDX-License-Identifier: BSD-2-Clause
|
|
/*
|
|
* Copyright (C) 2023 Renesas Electronics Corp.
|
|
* Copyright (c) 2024 Andes Technology Corporation
|
|
*
|
|
* Authors:
|
|
* Ben Zong-You Xie <ben717@andestech.com>
|
|
* Lad Prabhakar <prabhakar.mahadev-lad.rj@bp.renesas.com>
|
|
*/
|
|
|
|
#include <andes/andes.h>
|
|
#include <andes/andes_pma.h>
|
|
#include <libfdt.h>
|
|
#include <sbi/riscv_asm.h>
|
|
#include <sbi/riscv_io.h>
|
|
#include <sbi/sbi_console.h>
|
|
#include <sbi/sbi_error.h>
|
|
#include <sbi_utils/fdt/fdt_helper.h>
|
|
|
|
static inline bool not_napot(unsigned long addr, unsigned long size)
|
|
{
|
|
return ((size & (size - 1)) || (addr & (size - 1)));
|
|
}
|
|
|
|
static inline bool is_pma_entry_disable(char pmaxcfg)
|
|
{
|
|
return (pmaxcfg & ANDES_PMACFG_ETYP_MASK) == ANDES_PMACFG_ETYP_OFF ?
|
|
true : false;
|
|
}
|
|
|
|
static char get_pmaxcfg(int entry_id)
|
|
{
|
|
unsigned int pmacfg_addr;
|
|
unsigned long pmacfg_val;
|
|
char *pmaxcfg;
|
|
|
|
#if __riscv_xlen == 64
|
|
pmacfg_addr = CSR_PMACFG0 + ((entry_id / 8) ? 2 : 0);
|
|
pmacfg_val = csr_read_num(pmacfg_addr);
|
|
pmaxcfg = (char *)&pmacfg_val + (entry_id % 8);
|
|
#elif __riscv_xlen == 32
|
|
pmacfg_addr = CSR_PMACFG0 + (entry_id / 4);
|
|
pmacfg_val = csr_read_num(pmacfg_addr);
|
|
pmaxcfg = (char *)&pmacfg_val + (entry_id % 4);
|
|
#else
|
|
#error "Unexpected __riscv_xlen"
|
|
#endif
|
|
return *pmaxcfg;
|
|
}
|
|
|
|
static void set_pmaxcfg(int entry_id, char flags)
|
|
{
|
|
unsigned int pmacfg_addr;
|
|
unsigned long pmacfg_val;
|
|
char *pmaxcfg;
|
|
|
|
#if __riscv_xlen == 64
|
|
pmacfg_addr = CSR_PMACFG0 + ((entry_id / 8) ? 2 : 0);
|
|
pmacfg_val = csr_read_num(pmacfg_addr);
|
|
pmaxcfg = (char *)&pmacfg_val + (entry_id % 8);
|
|
#elif __riscv_xlen == 32
|
|
pmacfg_addr = CSR_PMACFG0 + (entry_id / 4);
|
|
pmacfg_val = csr_read_num(pmacfg_addr);
|
|
pmaxcfg = (char *)&pmacfg_val + (entry_id % 4);
|
|
#else
|
|
#error "Unexpected __riscv_xlen"
|
|
#endif
|
|
*pmaxcfg = flags;
|
|
csr_write_num(pmacfg_addr, pmacfg_val);
|
|
}
|
|
|
|
static void decode_pmaaddrx(int entry_id, unsigned long *start,
|
|
unsigned long *size)
|
|
{
|
|
unsigned long pmaaddr;
|
|
int k;
|
|
|
|
/**
|
|
* Given $pmaaddr, let k = # of trailing 1s of $pmaaddr
|
|
* size = 2 ^ (k + 3)
|
|
* start = 4 * ($pmaaddr - (size / 8) + 1)
|
|
*/
|
|
pmaaddr = csr_read_num(CSR_PMAADDR0 + entry_id);
|
|
k = sbi_ffz(pmaaddr);
|
|
*size = 1UL << (k + 3);
|
|
*start = (pmaaddr - (1UL << k) + 1) << 2;
|
|
}
|
|
|
|
static bool has_pma_region_overlap(unsigned long start, unsigned long size)
|
|
{
|
|
unsigned long _start, _size, _end, end;
|
|
char pmaxcfg;
|
|
|
|
end = start + size - 1;
|
|
for (int i = 0; i < ANDES_MAX_PMA_REGIONS; i++) {
|
|
pmaxcfg = get_pmaxcfg(i);
|
|
if (is_pma_entry_disable(pmaxcfg))
|
|
continue;
|
|
|
|
decode_pmaaddrx(i, &_start, &_size);
|
|
_end = _start + _size - 1;
|
|
|
|
if (MAX(start, _start) <= MIN(end, _end)) {
|
|
sbi_printf(
|
|
"ERROR %s(): %#lx ~ %#lx overlaps with PMA%d: %#lx ~ %#lx\n",
|
|
__func__, start, end, i, _start, _end);
|
|
return true;
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
static unsigned long andes_pma_setup(const struct andes_pma_region *pma_region,
|
|
unsigned int entry_id)
|
|
{
|
|
unsigned long size = pma_region->size;
|
|
unsigned long addr = pma_region->pa;
|
|
unsigned long pmaaddr;
|
|
|
|
/* Check for a 4KiB granularity NAPOT region*/
|
|
if (size < ANDES_PMA_GRANULARITY || not_napot(addr, size) ||
|
|
!(pma_region->flags & ANDES_PMACFG_ETYP_NAPOT))
|
|
return SBI_EINVAL;
|
|
|
|
set_pmaxcfg(entry_id, pma_region->flags);
|
|
|
|
pmaaddr = (addr >> 2) + (size >> 3) - 1;
|
|
|
|
csr_write_num(CSR_PMAADDR0 + entry_id, pmaaddr);
|
|
|
|
return csr_read_num(CSR_PMAADDR0 + entry_id) == pmaaddr ?
|
|
pmaaddr : SBI_EINVAL;
|
|
}
|
|
|
|
static int andes_fdt_pma_resv(void *fdt, const struct andes_pma_region *pma,
|
|
unsigned int index, int parent)
|
|
{
|
|
int na = fdt_address_cells(fdt, 0);
|
|
int ns = fdt_size_cells(fdt, 0);
|
|
static bool dma_default = false;
|
|
fdt32_t addr_high, addr_low;
|
|
fdt32_t size_high, size_low;
|
|
int subnode, err;
|
|
fdt32_t reg[4];
|
|
fdt32_t *val;
|
|
char name[32];
|
|
|
|
addr_high = (u64)pma->pa >> 32;
|
|
addr_low = pma->pa;
|
|
size_high = (u64)pma->size >> 32;
|
|
size_low = pma->size;
|
|
|
|
if (na > 1 && addr_high) {
|
|
sbi_snprintf(name, sizeof(name),
|
|
"pma_resv%d@%x,%x",
|
|
index, addr_high, addr_low);
|
|
} else {
|
|
sbi_snprintf(name, sizeof(name),
|
|
"pma_resv%d@%x",
|
|
index, addr_low);
|
|
}
|
|
subnode = fdt_add_subnode(fdt, parent, name);
|
|
if (subnode < 0)
|
|
return subnode;
|
|
|
|
if (pma->shared_dma) {
|
|
err = fdt_setprop_string(fdt, subnode, "compatible",
|
|
"shared-dma-pool");
|
|
if (err < 0)
|
|
return err;
|
|
}
|
|
|
|
if (pma->no_map) {
|
|
err = fdt_setprop_empty(fdt, subnode, "no-map");
|
|
if (err < 0)
|
|
return err;
|
|
}
|
|
|
|
/* Linux allows single linux,dma-default region. */
|
|
if (pma->dma_default) {
|
|
if (dma_default)
|
|
return SBI_EINVAL;
|
|
|
|
err = fdt_setprop_empty(fdt, subnode, "linux,dma-default");
|
|
if (err < 0)
|
|
return err;
|
|
dma_default = true;
|
|
}
|
|
|
|
/* Encode the <reg> property value */
|
|
val = reg;
|
|
if (na > 1)
|
|
*val++ = cpu_to_fdt32(addr_high);
|
|
*val++ = cpu_to_fdt32(addr_low);
|
|
if (ns > 1)
|
|
*val++ = cpu_to_fdt32(size_high);
|
|
*val++ = cpu_to_fdt32(size_low);
|
|
|
|
err = fdt_setprop(fdt, subnode, "reg", reg,
|
|
(na + ns) * sizeof(fdt32_t));
|
|
if (err < 0)
|
|
return err;
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int andes_fdt_reserved_memory_fixup(void *fdt,
|
|
const struct andes_pma_region *pma,
|
|
unsigned int entry)
|
|
{
|
|
int parent;
|
|
|
|
/* Try to locate the reserved memory node */
|
|
parent = fdt_path_offset(fdt, "/reserved-memory");
|
|
if (parent < 0) {
|
|
int na = fdt_address_cells(fdt, 0);
|
|
int ns = fdt_size_cells(fdt, 0);
|
|
int err;
|
|
|
|
/* If such node does not exist, create one */
|
|
parent = fdt_add_subnode(fdt, 0, "reserved-memory");
|
|
if (parent < 0)
|
|
return parent;
|
|
|
|
err = fdt_setprop_empty(fdt, parent, "ranges");
|
|
if (err < 0)
|
|
return err;
|
|
|
|
err = fdt_setprop_u32(fdt, parent, "#size-cells", ns);
|
|
if (err < 0)
|
|
return err;
|
|
|
|
err = fdt_setprop_u32(fdt, parent, "#address-cells", na);
|
|
if (err < 0)
|
|
return err;
|
|
}
|
|
|
|
return andes_fdt_pma_resv(fdt, pma, entry, parent);
|
|
}
|
|
|
|
int andes_pma_setup_regions(void *fdt,
|
|
const struct andes_pma_region *pma_regions,
|
|
unsigned int pma_regions_count)
|
|
{
|
|
unsigned int dt_populate_cnt;
|
|
unsigned int i, j;
|
|
unsigned long pa;
|
|
int ret;
|
|
|
|
if (!pma_regions || !pma_regions_count)
|
|
return 0;
|
|
|
|
if (pma_regions_count > ANDES_MAX_PMA_REGIONS)
|
|
return SBI_EINVAL;
|
|
|
|
if (!andes_sbi_probe_pma())
|
|
return SBI_ENOTSUPP;
|
|
|
|
/* Configure the PMA regions */
|
|
dt_populate_cnt = 0;
|
|
for (i = 0; i < pma_regions_count; i++) {
|
|
pa = andes_pma_setup(&pma_regions[i], i);
|
|
if (pa == SBI_EINVAL)
|
|
return SBI_EINVAL;
|
|
else if (pma_regions[i].dt_populate)
|
|
dt_populate_cnt++;
|
|
}
|
|
|
|
if (!dt_populate_cnt)
|
|
return 0;
|
|
|
|
ret = fdt_open_into(fdt, fdt,
|
|
fdt_totalsize(fdt) + (64 * dt_populate_cnt));
|
|
if (ret < 0)
|
|
return ret;
|
|
|
|
for (i = 0, j = 0; i < pma_regions_count; i++) {
|
|
if (!pma_regions[i].dt_populate)
|
|
continue;
|
|
|
|
ret = andes_fdt_reserved_memory_fixup(fdt,
|
|
&pma_regions[i],
|
|
j++);
|
|
if (ret)
|
|
return ret;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
bool andes_sbi_probe_pma(void)
|
|
{
|
|
return (csr_read(CSR_MMSC_CFG) & MMSC_CFG_PPMA_MASK) ? true : false;
|
|
}
|
|
|
|
int andes_sbi_set_pma(unsigned long pa, unsigned long size, u8 flags)
|
|
{
|
|
unsigned int entry_id;
|
|
unsigned long rc;
|
|
char pmaxcfg;
|
|
struct andes_pma_region region;
|
|
|
|
if (!andes_sbi_probe_pma()) {
|
|
sbi_printf("ERROR %s(): Platform does not support PPMA.\n",
|
|
__func__);
|
|
return SBI_ERR_NOT_SUPPORTED;
|
|
}
|
|
|
|
if (has_pma_region_overlap(pa, size))
|
|
return SBI_ERR_INVALID_PARAM;
|
|
|
|
for (entry_id = 0; entry_id < ANDES_MAX_PMA_REGIONS; entry_id++) {
|
|
pmaxcfg = get_pmaxcfg(entry_id);
|
|
if (is_pma_entry_disable(pmaxcfg)) {
|
|
region.pa = pa;
|
|
region.size = size;
|
|
region.flags = flags;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (entry_id == ANDES_MAX_PMA_REGIONS) {
|
|
sbi_printf("ERROR %s(): All PMA entries have run out\n",
|
|
__func__);
|
|
return SBI_ERR_FAILED;
|
|
}
|
|
|
|
rc = andes_pma_setup(®ion, entry_id);
|
|
if (rc == SBI_EINVAL) {
|
|
sbi_printf("ERROR %s(): Failed to set PMAADDR%d\n",
|
|
__func__, entry_id);
|
|
return SBI_ERR_FAILED;
|
|
}
|
|
|
|
return SBI_SUCCESS;
|
|
}
|
|
|
|
int andes_sbi_free_pma(unsigned long pa)
|
|
{
|
|
unsigned long start, size;
|
|
char pmaxcfg;
|
|
|
|
if (!andes_sbi_probe_pma()) {
|
|
sbi_printf("ERROR %s(): Platform does not support PPMA.\n",
|
|
__func__);
|
|
return SBI_ERR_NOT_SUPPORTED;
|
|
}
|
|
|
|
for (int i = 0; i < ANDES_MAX_PMA_REGIONS; i++) {
|
|
pmaxcfg = get_pmaxcfg(i);
|
|
if (is_pma_entry_disable(pmaxcfg))
|
|
continue;
|
|
|
|
decode_pmaaddrx(i, &start, &size);
|
|
if (start != pa)
|
|
continue;
|
|
|
|
set_pmaxcfg(i, ANDES_PMACFG_ETYP_OFF);
|
|
csr_write_num(CSR_PMAADDR0 + i, 0);
|
|
|
|
return SBI_SUCCESS;
|
|
}
|
|
|
|
sbi_printf("ERROR %s(): Failed to find the entry with PA %#lx\n",
|
|
__func__, pa);
|
|
|
|
return SBI_ERR_FAILED;
|
|
}
|