summaryrefslogtreecommitdiff
path: root/src/arch
diff options
context:
space:
mode:
Diffstat (limited to 'src/arch')
-rw-r--r--src/arch/riscv/include/arch/pmp.h10
-rw-r--r--src/arch/riscv/payload.c27
-rw-r--r--src/arch/riscv/pmp.c142
3 files changed, 147 insertions, 32 deletions
diff --git a/src/arch/riscv/include/arch/pmp.h b/src/arch/riscv/include/arch/pmp.h
index b25fc965bc..f98adf724a 100644
--- a/src/arch/riscv/include/arch/pmp.h
+++ b/src/arch/riscv/include/arch/pmp.h
@@ -14,7 +14,13 @@ int pmp_entries_num(void);
/* reset PMP setting */
void reset_pmp(void);
-/* set up PMP record */
-void setup_pmp(uintptr_t base, uintptr_t size, uintptr_t flags);
+/*
+ * set up PMP record
+ * reminder: base and size are 34-bits on RV32.
+ */
+void setup_pmp(u64 base, u64 size, u8 flags);
+
+/* write the last PMP record, i.e. the "default" case. */
+void close_pmp(void);
#endif /* __RISCV_PMP_H__ */
diff --git a/src/arch/riscv/payload.c b/src/arch/riscv/payload.c
index 443975bf40..7c6e0f4f98 100644
--- a/src/arch/riscv/payload.c
+++ b/src/arch/riscv/payload.c
@@ -1,9 +1,11 @@
/* SPDX-License-Identifier: GPL-2.0-only */
+#include <cbmem.h>
#include <program_loading.h>
#include <stdint.h>
#include <arch/boot.h>
#include <arch/encoding.h>
+#include <arch/pmp.h>
#include <arch/smp/atomic.h>
#include <console/console.h>
#include <mcall.h>
@@ -37,10 +39,35 @@ void run_payload(struct prog *prog, void *fdt, int payload_mode)
void (*doit)(int hart_id, void *fdt) = prog_entry(prog);
int hart_id = read_csr(mhartid);
uintptr_t status = read_csr(mstatus);
+ extern void *_text, *_estack;
status = INSERT_FIELD(status, MSTATUS_MPIE, 0);
switch (payload_mode) {
case RISCV_PAYLOAD_MODE_S:
+ /*
+ * Set up a PMP to protect coreboot, then close the PMPs.
+ * If a mainboard or SoC needs other ranges
+ * set up, they should do so before this point,
+ * as close_pmp puts in a "match all" entry, and
+ * PMPs are processed in linear order.
+ */
+
+ /*
+ * On this code path, coreboot is providing the coreboot SBI, and must
+ * protect the ramstage, from _text to _estack, from S and U
+ * modes. Because the number of PMP registers may be very
+ * small, make this an NAPOT area. The linker scripts
+ * should round _text and _estack to 4K.
+ */
+ setup_pmp((u64)(uintptr_t) _text,
+ (u64)(uintptr_t) _estack - (u64)(uintptr_t) _text, 0);
+
+ /*
+ * All pmp operations should be finished when close_pmp is called.
+ * Presently, this requirement is not enforced.
+ */
+ close_pmp();
+
status = INSERT_FIELD(status, MSTATUS_MPP, PRV_S);
/* Trap vector base address point to the payload */
write_csr(stvec, doit);
diff --git a/src/arch/riscv/pmp.c b/src/arch/riscv/pmp.c
index 85af408f6f..b643441e62 100644
--- a/src/arch/riscv/pmp.c
+++ b/src/arch/riscv/pmp.c
@@ -28,6 +28,17 @@ struct pmpcfg {
/* 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)
{
@@ -96,17 +107,21 @@ static void write_pmpcfg(int idx, uintptr_t cfg)
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)
- die("write pmpcfg failure!");
+ 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] */
@@ -202,41 +217,66 @@ static void write_pmpaddr(int idx, uintptr_t val)
write_csr(pmpaddr15, val);
break;
}
- if (read_pmpaddr(idx) != val)
- die("write pmpaddr failure");
+
+ 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 struct pmpcfg generate_pmp_napot(
- uintptr_t base, uintptr_t size, uintptr_t flags)
+static void generate_pmp_napot(struct pmpcfg *p, uintptr_t base, uintptr_t size, u8 flags)
{
- struct pmpcfg p;
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)) >> PMP_SHIFT;
- return p;
+ 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 struct pmpcfg generate_pmp_range(
- uintptr_t base, uintptr_t size, uintptr_t flags)
+static void generate_pmp_range(struct pmpcfg *p, uintptr_t base, uintptr_t size, u8 flags)
{
- struct pmpcfg p;
flags = flags & (PMP_R | PMP_W | PMP_X | PMP_L);
- p.cfg = flags | PMP_TOR;
- p.previous_address = base >> PMP_SHIFT;
- p.address = (base + size) >> PMP_SHIFT;
- return p;
+ p->cfg = flags | PMP_TOR;
+ p->previous_address = base;
+ p->address = (base + size);
}
-/* Generate a PMP configuration */
-static struct pmpcfg generate_pmp(uintptr_t base, uintptr_t size, uintptr_t flags)
+/*
+ * 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)
{
- if (IS_POWER_OF_2(size) && (size >= 4) && ((base & (size - 1)) == 0))
- return generate_pmp_napot(base, size, flags);
- else
- return generate_pmp_range(base, size, 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;
}
/*
@@ -279,30 +319,72 @@ 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!");
+ die("Some PMP configurations are locked and cannot be reset!");
write_pmpcfg(i, 0);
write_pmpaddr(i, 0);
}
}
-/* set up PMP record */
-void setup_pmp(uintptr_t base, uintptr_t size, uintptr_t flags)
+/*
+ * 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;
- p = generate_pmp(base, size, flags);
+ 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);
- write_pmpcfg(n, p.cfg);
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);
+}