summaryrefslogtreecommitdiff
path: root/src/arch/riscv
diff options
context:
space:
mode:
authorXiang Wang <wxjstz@126.com>2018-09-11 15:53:36 +0800
committerPatrick Georgi <pgeorgi@google.com>2018-10-11 10:56:54 +0000
commit4356e09235b911ad0c66f7467e25f6a88e823009 (patch)
treea430040c577e63efdf37bbe07e7da32fe2414728 /src/arch/riscv
parenta08475e9abe48393453560a7bdd9ffc040be7845 (diff)
riscv: add physical memory protection (PMP) support
These codes are written by me based on the privileged instruction set. I tested it by qemu/riscv-probe. Change-Id: I2e9e0c94e6518f63ade7680a3ce68bacfae219d4 Signed-off-by: Xiang Wang <wxjstz@126.com> Reviewed-on: https://review.coreboot.org/28569 Reviewed-by: Philipp Hug <philipp@hug.cx> Reviewed-by: Jonathan Neuschäfer <j.neuschaefer@gmx.net> Tested-by: build bot (Jenkins) <no-reply@coreboot.org>
Diffstat (limited to 'src/arch/riscv')
-rw-r--r--src/arch/riscv/Makefile.inc3
-rw-r--r--src/arch/riscv/include/arch/pmp.h31
-rw-r--r--src/arch/riscv/pmp.c321
3 files changed, 355 insertions, 0 deletions
diff --git a/src/arch/riscv/Makefile.inc b/src/arch/riscv/Makefile.inc
index a2bd763cd9..d1354e1249 100644
--- a/src/arch/riscv/Makefile.inc
+++ b/src/arch/riscv/Makefile.inc
@@ -54,6 +54,7 @@ bootblock-y += mcall.c
bootblock-y += virtual_memory.c
bootblock-y += boot.c
bootblock-y += misc.c
+bootblock-y += pmp.c
bootblock-y += \
$(top)/src/lib/memchr.c \
$(top)/src/lib/memcmp.c \
@@ -82,6 +83,7 @@ ifeq ($(CONFIG_ARCH_ROMSTAGE_RISCV),y)
romstage-y += boot.c
romstage-y += stages.c
romstage-y += misc.c
+romstage-y += pmp.c
romstage-y += \
$(top)/src/lib/memchr.c \
$(top)/src/lib/memcmp.c \
@@ -120,6 +122,7 @@ ramstage-y += misc.c
ramstage-y += boot.c
ramstage-y += tables.c
ramstage-y += payload.S
+ramstage-y += pmp.c
ramstage-y += \
$(top)/src/lib/memchr.c \
$(top)/src/lib/memcmp.c \
diff --git a/src/arch/riscv/include/arch/pmp.h b/src/arch/riscv/include/arch/pmp.h
new file mode 100644
index 0000000000..f671c0297b
--- /dev/null
+++ b/src/arch/riscv/include/arch/pmp.h
@@ -0,0 +1,31 @@
+/*
+ * This file is part of the coreboot project.
+ *
+ * Copyright (C) 2018 HardenedLinux
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; version 2 of the License.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ */
+
+#ifndef __RISCV_PMP_H__
+#define __RISCV_PMP_H__
+
+/*
+ * this function needs to be implemented by a specific SoC.
+ * return number of PMP entries for current hart
+ */
+extern 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);
+
+#endif /* __RISCV_PMP_H__ */
diff --git a/src/arch/riscv/pmp.c b/src/arch/riscv/pmp.c
new file mode 100644
index 0000000000..5e32f9ca23
--- /dev/null
+++ b/src/arch/riscv/pmp.c
@@ -0,0 +1,321 @@
+/*
+ * This file is part of the coreboot project.
+ *
+ * Copyright (C) 2018 HardenedLinux
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; version 2 of the License.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ */
+
+#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.
+ */
+typedef struct {
+ /* 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;
+} pmpcfg_t;
+
+/* This variable is used to record which entries have been used. */
+static uintptr_t pmp_entry_used_mask;
+
+/* 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);
+ break;
+ case 1:
+ old = read_csr(pmpcfg2);
+ new = (old & ~((uintptr_t)0xff << shift))
+ | ((cfg & 0xff) << shift);
+ write_csr(pmpcfg2, new);
+ break;
+ }
+#endif
+ if (read_pmpcfg(idx) != cfg)
+ die("write pmpcfg failure!");
+}
+
+/* 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;
+ }
+ if (read_pmpaddr(idx) != val)
+ die("write pmpaddr failure");
+}
+
+/* Generate a PMP configuration of type NA4/NAPOT */
+static pmpcfg_t generate_pmp_napot(
+ uintptr_t base, uintptr_t size, uintptr_t flags)
+{
+ pmpcfg_t 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;
+}
+
+/* Generate a PMP configuration of type TOR */
+static pmpcfg_t generate_pmp_range(
+ uintptr_t base, uintptr_t size, uintptr_t flags)
+{
+ pmpcfg_t 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;
+}
+
+/* Generate a PMP configuration */
+static pmpcfg_t generate_pmp(uintptr_t base, uintptr_t size, uintptr_t 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);
+}
+
+/*
+ * 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 */
+void setup_pmp(uintptr_t base, uintptr_t size, uintptr_t flags)
+{
+ pmpcfg_t p;
+ int is_range, n;
+
+ p = generate_pmp(base, size, flags);
+ is_range = ((p.cfg & PMP_A) == PMP_TOR);
+
+ n = find_empty_pmp_entry(is_range);
+
+ 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);
+}