PMP (Physical Memory Protection) is a feature of the RISC-V Privileged Architecture spec, that allows defining region(s) of the address space to be protected in a variety of ways: ranges for M mode can be protected against access from lower privilege levels, and M mode can be locked out of accessig to memory reserved for lower privilege levels. Limits on Read, Write, and Execute are allowed. In coreboot, we protect against Write and Execute of PMP code from lower levels, but allow Reading, so as to ease data structure access. PMP is not a security boundary, it is an accident prevention device. PMP is used here to protect persistent ramstage code that is used to support SBI, e.g. printk and some data structures. It also protects the SBI stacks. Note that there is one stack per hart. There are 512- and 1024-hart SoC's being built today, so the stack should be kept small. PMP is not a general purpose protection mechanism and it is easy to get around it. For example, S mode can stage a DMA that overwrites all the M mode code. PMP is, rather, a way to avoid simple accidents. It is understood that PMP depends on proper OS behavior to implement true SBI security (personal conversation with a RISC-V architect). Think of PMP as "Protection Minus Protection". PMP is also a very limited resource, as defined in the architecture. This language is instructive: "PMP entries are described by an 8-bit configuration register and one XLEN-bit address register. Some PMP settings additionally use the address register associated with the preceding PMP entry. Up to 16 PMP entries are supported. If any PMP entries are implemented, then all PMP CSRs must be implemented, but all PMP CSR fields are WARL and may be hardwired to zero. PMP CSRs are only accessible to M-mode." In other words if you implement PMP even a little, you have to impelement it all; but you can implement it in part by simply returning 0 for a pmpcfg. Also, PMP address registers (pmpaddr) don't have to implement all the bits. On a SiFive FU740, for example, PMP only implements bits 33:0, i.e. a 34 bit address. PMPs are just packed with all kinds of special cases. There are no requirements that you read back what you wrote to the pmpaddr registers. The earlier PMP code would die if the read did not match the write, but, since pmpaddr are WARL, that was not correct. An SoC can just decide it only does 4096-byte granularity, on TOR PMP types, and that is your problem if you wanted finer granulatiry. SoC's don't have to implement all the high order bits either. And, to reiterate, there is no requirement about which of the pmpcfg are implemented. Implementing just pmpcfg15 is allowed. The coreboot SBI code was written before PMP existed. In order for coreboot SBI code to work, this patch is necessary. With this change, a simple S-mode payload that calls SBI putchar works: 1: li a7, 1 li a0, 48 ecall j 1b Without this change, it will not work. Getting this to build on RV32 required changes to the API, as it was incorrect. In RV32, PMP entries are 34 bits. Hence, the setup_pmp needed to accept u64. So, uinptr_t can not be used, as on 32 bits they are only 32 bit numbers. The internal API uses uintptr_t, but the exported API uses u64, so external code does not have to think about right shifts on base and size. Errors are detected: an error in base and size will result in a BIOS_EMERG print, but not a panic. Boots not bricks if possible. There are small changes to the internal API to reduce stack pressure: there's no need to have two pmpcfg_t on the stack when one will do. TEST: Linux now boots partly on the SiFive unmatched. There are changes in flight on the coreboot SBI that will allow Linux to boot further, but they are out of scope for this patch. Currently, clk_ignore_unused is required, this requires a separate patch. Change-Id: I6edce139d340783148cbb446cde004ba96e67944 Signed-off-by: Ronald G Minnich <rminnich@gmail.com> Reviewed-on: https://review.coreboot.org/c/coreboot/+/81153 Tested-by: build bot (Jenkins) <no-reply@coreboot.org> Reviewed-by: Philipp Hug <philipp@hug.cx>
391 lines
9.5 KiB
C
391 lines
9.5 KiB
C
/* SPDX-License-Identifier: GPL-2.0-only */
|
|
|
|
#include <arch/encoding.h>
|
|
#include <stdint.h>
|
|
#include <arch/pmp.h>
|
|
#include <console/console.h>
|
|
#include <commonlib/helpers.h>
|
|
|
|
#define GRANULE (1 << PMP_SHIFT)
|
|
|
|
/*
|
|
* This structure is used to temporarily record PMP
|
|
* configuration information.
|
|
*/
|
|
struct pmpcfg {
|
|
/* used to record the value of pmpcfg[i] */
|
|
uintptr_t cfg;
|
|
/*
|
|
* When generating a TOR type configuration,
|
|
* the previous entry needs to record the starting address.
|
|
* used to record the value of pmpaddr[i - 1]
|
|
*/
|
|
uintptr_t previous_address;
|
|
/* used to record the value of pmpaddr[i] */
|
|
uintptr_t address;
|
|
};
|
|
|
|
/* This variable is used to record which entries have been used. */
|
|
static uintptr_t pmp_entry_used_mask;
|
|
|
|
/* The architectural spec says that up to 16 PMP entries are
|
|
* available.
|
|
* "Up to 16 PMP entries are supported. If any PMP entries are
|
|
* implemented, then all PMP CSRs must be implemented,
|
|
* but all PMP CSR fields are WARL and may be hardwired to zero."
|
|
*/
|
|
int pmp_entries_num(void)
|
|
{
|
|
return 16;
|
|
}
|
|
|
|
/* helper function used to read pmpcfg[idx] */
|
|
static uintptr_t read_pmpcfg(int idx)
|
|
{
|
|
#if __riscv_xlen == 32
|
|
int shift = 8 * (idx & 3);
|
|
switch (idx >> 2) {
|
|
case 0:
|
|
return (read_csr(pmpcfg0) >> shift) & 0xff;
|
|
case 1:
|
|
return (read_csr(pmpcfg1) >> shift) & 0xff;
|
|
case 2:
|
|
return (read_csr(pmpcfg2) >> shift) & 0xff;
|
|
case 3:
|
|
return (read_csr(pmpcfg3) >> shift) & 0xff;
|
|
}
|
|
#elif __riscv_xlen == 64
|
|
int shift = 8 * (idx & 7);
|
|
switch (idx >> 3) {
|
|
case 0:
|
|
return (read_csr(pmpcfg0) >> shift) & 0xff;
|
|
case 1:
|
|
return (read_csr(pmpcfg2) >> shift) & 0xff;
|
|
}
|
|
#endif
|
|
return -1;
|
|
}
|
|
|
|
/* helper function used to write pmpcfg[idx] */
|
|
static void write_pmpcfg(int idx, uintptr_t cfg)
|
|
{
|
|
uintptr_t old;
|
|
uintptr_t new;
|
|
#if __riscv_xlen == 32
|
|
int shift = 8 * (idx & 3);
|
|
switch (idx >> 2) {
|
|
case 0:
|
|
old = read_csr(pmpcfg0);
|
|
new = (old & ~((uintptr_t)0xff << shift))
|
|
| ((cfg & 0xff) << shift);
|
|
write_csr(pmpcfg0, new);
|
|
break;
|
|
case 1:
|
|
old = read_csr(pmpcfg1);
|
|
new = (old & ~((uintptr_t)0xff << shift))
|
|
| ((cfg & 0xff) << shift);
|
|
write_csr(pmpcfg1, new);
|
|
break;
|
|
case 2:
|
|
old = read_csr(pmpcfg2);
|
|
new = (old & ~((uintptr_t)0xff << shift))
|
|
| ((cfg & 0xff) << shift);
|
|
write_csr(pmpcfg2, new);
|
|
break;
|
|
case 3:
|
|
old = read_csr(pmpcfg3);
|
|
new = (old & ~((uintptr_t)0xff << shift))
|
|
| ((cfg & 0xff) << shift);
|
|
write_csr(pmpcfg3, new);
|
|
break;
|
|
}
|
|
#elif __riscv_xlen == 64
|
|
int shift = 8 * (idx & 7);
|
|
switch (idx >> 3) {
|
|
case 0:
|
|
old = read_csr(pmpcfg0);
|
|
new = (old & ~((uintptr_t)0xff << shift))
|
|
| ((cfg & 0xff) << shift);
|
|
write_csr(pmpcfg0, new);
|
|
printk(BIOS_INFO, "%s(%d, %lx) = %lx\n", __func__, idx, cfg, read_csr(pmpcfg0));
|
|
break;
|
|
case 1:
|
|
old = read_csr(pmpcfg2);
|
|
new = (old & ~((uintptr_t)0xff << shift))
|
|
| ((cfg & 0xff) << shift);
|
|
write_csr(pmpcfg2, new);
|
|
printk(BIOS_INFO, "%s(%d, %lx) = %lx\n", __func__, idx, cfg, read_csr(pmpcfg2));
|
|
break;
|
|
}
|
|
#endif
|
|
if (read_pmpcfg(idx) != cfg) {
|
|
printk(BIOS_WARNING, "%s: PMPcfg%d: Wrote %lx, read %lx\n", __func__, idx, cfg, read_pmpcfg(idx));
|
|
die("PMPcfg write failed");
|
|
}
|
|
}
|
|
|
|
/* helper function used to read pmpaddr[idx] */
|
|
static uintptr_t read_pmpaddr(int idx)
|
|
{
|
|
switch (idx) {
|
|
case 0:
|
|
return read_csr(pmpaddr0);
|
|
case 1:
|
|
return read_csr(pmpaddr1);
|
|
case 2:
|
|
return read_csr(pmpaddr2);
|
|
case 3:
|
|
return read_csr(pmpaddr3);
|
|
case 4:
|
|
return read_csr(pmpaddr4);
|
|
case 5:
|
|
return read_csr(pmpaddr5);
|
|
case 6:
|
|
return read_csr(pmpaddr6);
|
|
case 7:
|
|
return read_csr(pmpaddr7);
|
|
case 8:
|
|
return read_csr(pmpaddr8);
|
|
case 9:
|
|
return read_csr(pmpaddr9);
|
|
case 10:
|
|
return read_csr(pmpaddr10);
|
|
case 11:
|
|
return read_csr(pmpaddr11);
|
|
case 12:
|
|
return read_csr(pmpaddr12);
|
|
case 13:
|
|
return read_csr(pmpaddr13);
|
|
case 14:
|
|
return read_csr(pmpaddr14);
|
|
case 15:
|
|
return read_csr(pmpaddr15);
|
|
}
|
|
return -1;
|
|
}
|
|
|
|
/* helper function used to write pmpaddr[idx] */
|
|
static void write_pmpaddr(int idx, uintptr_t val)
|
|
{
|
|
switch (idx) {
|
|
case 0:
|
|
write_csr(pmpaddr0, val);
|
|
break;
|
|
case 1:
|
|
write_csr(pmpaddr1, val);
|
|
break;
|
|
case 2:
|
|
write_csr(pmpaddr2, val);
|
|
break;
|
|
case 3:
|
|
write_csr(pmpaddr3, val);
|
|
break;
|
|
case 4:
|
|
write_csr(pmpaddr4, val);
|
|
break;
|
|
case 5:
|
|
write_csr(pmpaddr5, val);
|
|
break;
|
|
case 6:
|
|
write_csr(pmpaddr6, val);
|
|
break;
|
|
case 7:
|
|
write_csr(pmpaddr7, val);
|
|
break;
|
|
case 8:
|
|
write_csr(pmpaddr8, val);
|
|
break;
|
|
case 9:
|
|
write_csr(pmpaddr9, val);
|
|
break;
|
|
case 10:
|
|
write_csr(pmpaddr10, val);
|
|
break;
|
|
case 11:
|
|
write_csr(pmpaddr11, val);
|
|
break;
|
|
case 12:
|
|
write_csr(pmpaddr12, val);
|
|
break;
|
|
case 13:
|
|
write_csr(pmpaddr13, val);
|
|
break;
|
|
case 14:
|
|
write_csr(pmpaddr14, val);
|
|
break;
|
|
case 15:
|
|
write_csr(pmpaddr15, val);
|
|
break;
|
|
}
|
|
|
|
printk(BIOS_INFO, "%s(%d, %lx) = %lx\n", __func__, idx, val, read_pmpaddr(idx));
|
|
/* The PMP is not required to return what we wrote. On some SoC, many bits are cleared. */
|
|
if (read_pmpaddr(idx) != val) {
|
|
printk(BIOS_WARNING, "%s: PMPaddr%d: Wrote %lx, read %lx\n", __func__,
|
|
idx, val, read_pmpaddr(idx));
|
|
}
|
|
}
|
|
|
|
/* Generate a PMP configuration for all memory */
|
|
static void generate_pmp_all(struct pmpcfg *p)
|
|
{
|
|
p->cfg = PMP_NAPOT | PMP_R | PMP_W | PMP_X;
|
|
p->previous_address = 0;
|
|
p->address = (uintptr_t) -1;
|
|
}
|
|
|
|
/* Generate a PMP configuration of type NA4/NAPOT */
|
|
static void generate_pmp_napot(struct pmpcfg *p, uintptr_t base, uintptr_t size, u8 flags)
|
|
{
|
|
flags = flags & (PMP_R | PMP_W | PMP_X | PMP_L);
|
|
p->cfg = flags | (size > GRANULE ? PMP_NAPOT : PMP_NA4);
|
|
p->previous_address = 0;
|
|
p->address = (base + (size / 2 - 1));
|
|
}
|
|
|
|
/* Generate a PMP configuration of type TOR */
|
|
static void generate_pmp_range(struct pmpcfg *p, uintptr_t base, uintptr_t size, u8 flags)
|
|
{
|
|
flags = flags & (PMP_R | PMP_W | PMP_X | PMP_L);
|
|
p->cfg = flags | PMP_TOR;
|
|
p->previous_address = base;
|
|
p->address = (base + size);
|
|
}
|
|
|
|
/*
|
|
* Generate a PMP configuration.
|
|
* reminder: base and size are 34 bit numbers on RV32.
|
|
*/
|
|
static int generate_pmp(struct pmpcfg *p, u64 base, u64 size, u8 flags)
|
|
{
|
|
/* Convert the byte address and byte size to units of 32-bit words */
|
|
uintptr_t b = (uintptr_t) base >> PMP_SHIFT, s = (uintptr_t) size >> PMP_SHIFT;
|
|
#if __riscv_xlen == 32
|
|
/* verify that base + size fits in 34 bits */
|
|
if ((base + size - 1) >> 34) {
|
|
printk(BIOS_EMERG, "%s: base (%llx) + size (%llx) - 1 is more than 34 bits\n",
|
|
__func__, base, size);
|
|
return 1;
|
|
}
|
|
#endif
|
|
/* if base is -1, that means "match all" */
|
|
if (base == (u64)-1) {
|
|
generate_pmp_all(p);
|
|
} else if (IS_POWER_OF_2(size) && (size >= 4) && ((base & (size - 1)) == 0)) {
|
|
generate_pmp_napot(p, b, s, flags);
|
|
} else {
|
|
generate_pmp_range(p, b, s, flags);
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
/*
|
|
* find empty PMP entry by type
|
|
* TOR type configuration requires two consecutive PMP entries,
|
|
* others requires one.
|
|
*/
|
|
static int find_empty_pmp_entry(int is_range)
|
|
{
|
|
int free_entries = 0;
|
|
for (int i = 0; i < pmp_entries_num(); i++) {
|
|
if (pmp_entry_used_mask & (1 << i))
|
|
free_entries = 0;
|
|
else
|
|
free_entries++;
|
|
if (is_range && (free_entries == 2))
|
|
return i;
|
|
if (!is_range && (free_entries == 1))
|
|
return i;
|
|
}
|
|
die("Too many PMP configurations, no free entries can be used!");
|
|
return -1;
|
|
}
|
|
|
|
/*
|
|
* mark PMP entry has be used
|
|
* this function need be used with find_entry_pmp_entry
|
|
*
|
|
* n = find_empty_pmp_entry(is_range)
|
|
* ... // PMP set operate
|
|
* mask_pmp_entry_used(n);
|
|
*/
|
|
static void mask_pmp_entry_used(int idx)
|
|
{
|
|
pmp_entry_used_mask |= 1 << idx;
|
|
}
|
|
|
|
/* reset PMP setting */
|
|
void reset_pmp(void)
|
|
{
|
|
for (int i = 0; i < pmp_entries_num(); i++) {
|
|
if (read_pmpcfg(i) & PMP_L)
|
|
die("Some PMP configurations are locked and cannot be reset!");
|
|
write_pmpcfg(i, 0);
|
|
write_pmpaddr(i, 0);
|
|
}
|
|
}
|
|
|
|
/*
|
|
* set up PMP record
|
|
* Why are these u64 and not uintptr_t?
|
|
* because, per the spec:
|
|
* The Sv32 page-based virtual-memory scheme described in Section 4.3
|
|
* supports 34-bit physical addresses for RV32, so the PMP scheme must
|
|
* support addresses wider than XLEN for RV32.
|
|
* Yes, in RV32, these are 34-bit numbers.
|
|
* Rather than require every future user of these to remember that,
|
|
* this ABI is 64 bits.
|
|
* generate_pmp will check for out of range values.
|
|
*/
|
|
void setup_pmp(u64 base, u64 size, u8 flags)
|
|
{
|
|
struct pmpcfg p;
|
|
int is_range, n;
|
|
|
|
if (generate_pmp(&p, base, size, flags))
|
|
return;
|
|
|
|
is_range = ((p.cfg & PMP_A) == PMP_TOR);
|
|
|
|
n = find_empty_pmp_entry(is_range);
|
|
|
|
/*
|
|
* NOTE! you MUST write the cfg register first, or on (e.g.)
|
|
* the SiFive FU740, it will not take all the bits.
|
|
* This is different than QEMU. NASTY!
|
|
*/
|
|
write_pmpcfg(n, p.cfg);
|
|
|
|
write_pmpaddr(n, p.address);
|
|
if (is_range)
|
|
write_pmpaddr(n - 1, p.previous_address);
|
|
|
|
mask_pmp_entry_used(n);
|
|
if (is_range)
|
|
mask_pmp_entry_used(n - 1);
|
|
}
|
|
|
|
/*
|
|
* close_pmp will "close" the pmp.
|
|
* This consists of adding the "match every address" entry.
|
|
* This should be the last pmp function that is called.
|
|
* Because we can not be certain that there is not some reason for it
|
|
* NOT to be last, we do not check -- perhaps, later, a check would
|
|
* make sense, but, for now, we do not check.
|
|
* If previous code has used up all pmp entries, print a warning
|
|
* and continue.
|
|
* The huge constant for the memory size may seem a bit odd here.
|
|
* Recall that PMP is to protect a *limited* number of M mode
|
|
* memory ranges from S and U modes. Therefore, the last range
|
|
* entry should cover all possible addresses, up to
|
|
* an architectural limit. It is entirely acceptable
|
|
* for it to cover memory that does not exist -- PMP
|
|
* protects M mode, nothing more.
|
|
* Think of this range as the final catch-all else
|
|
* in an if-then-else.
|
|
*/
|
|
void close_pmp(void)
|
|
{
|
|
setup_pmp((u64)-1, 0, PMP_R|PMP_W|PMP_X);
|
|
}
|