summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--include/sbi/sbi_domain.h147
-rw-r--r--include/sbi/sbi_platform.h20
-rw-r--r--lib/sbi/objects.mk1
-rw-r--r--lib/sbi/sbi_domain.c380
-rw-r--r--lib/sbi/sbi_init.c20
5 files changed, 568 insertions, 0 deletions
diff --git a/include/sbi/sbi_domain.h b/include/sbi/sbi_domain.h
new file mode 100644
index 0000000..4bb08dc
--- /dev/null
+++ b/include/sbi/sbi_domain.h
@@ -0,0 +1,147 @@
+/*
+ * SPDX-License-Identifier: BSD-2-Clause
+ *
+ * Copyright (c) 2020 Western Digital Corporation or its affiliates.
+ *
+ * Authors:
+ * Anup Patel <anup.patel@wdc.com>
+ */
+
+#ifndef __SBI_DOMAIN_H__
+#define __SBI_DOMAIN_H__
+
+#include <sbi/sbi_types.h>
+#include <sbi/sbi_hartmask.h>
+
+struct sbi_scratch;
+
+/** Domain access types */
+enum sbi_domain_access {
+ SBI_DOMAIN_READ = (1UL << 0),
+ SBI_DOMAIN_WRITE = (1UL << 1),
+ SBI_DOMAIN_EXECUTE = (1UL << 2),
+ SBI_DOMAIN_MMIO = (1UL << 3)
+};
+
+/** Representation of OpenSBI domain memory region */
+struct sbi_domain_memregion {
+ /**
+ * Size of memory region as power of 2
+ * It has to be minimum 3 and maximum __riscv_xlen
+ */
+ unsigned long order;
+ /**
+ * Base address of memory region
+ * It must be 2^order aligned address
+ */
+ unsigned long base;
+ /** Flags representing memory region attributes */
+#define SBI_DOMAIN_MEMREGION_READABLE (1UL << 0)
+#define SBI_DOMAIN_MEMREGION_WRITEABLE (1UL << 1)
+#define SBI_DOMAIN_MEMREGION_EXECUTABLE (1UL << 2)
+#define SBI_DOMAIN_MEMREGION_MMIO (1UL << 3)
+#define SBI_DOMAIN_MEMREGION_MMODE (1UL << 4)
+ unsigned long flags;
+};
+
+/** Maximum number of domains */
+#define SBI_DOMAIN_MAX_INDEX 32
+
+/** Representation of OpenSBI domain */
+struct sbi_domain {
+ /**
+ * Logical index of this domain
+ * Note: This set by sbi_domain_finalize() in the coldboot path
+ */
+ u32 index;
+ /**
+ * HARTs assigned to this domain
+ * Note: This set by sbi_domain_init() and sbi_domain_finalize()
+ * in the coldboot path
+ */
+ struct sbi_hartmask assigned_harts;
+ /** Name of this domain */
+ char name[64];
+ /** Possible HARTs in this domain */
+ const struct sbi_hartmask *possible_harts;
+ /** Array of memory regions terminated by a region with order zero */
+ struct sbi_domain_memregion *regions;
+ /** HART id of the HART booting this domain */
+ u32 boot_hartid;
+ /** Arg1 (or 'a1' register) of next booting stage for this domain */
+ unsigned long next_arg1;
+ /** Address of next booting stage for this domain */
+ unsigned long next_addr;
+ /** Privilege mode of next booting stage for this domain */
+ unsigned long next_mode;
+ /** Is domain allowed to reset the system */
+ bool system_reset_allowed;
+};
+
+/** HART id to domain table */
+extern struct sbi_domain *hartid_to_domain_table[];
+
+/** Get pointer to sbi_domain from HART id */
+#define sbi_hartid_to_domain(__hartid) \
+ hartid_to_domain_table[__hartid]
+
+/** Get pointer to sbi_domain for current HART */
+#define sbi_domain_thishart_ptr() \
+ sbi_hartid_to_domain(current_hartid())
+
+/** Index to domain table */
+extern struct sbi_domain *domidx_to_domain_table[];
+
+/** Get pointer to sbi_domain from index */
+#define sbi_index_to_domain(__index) \
+ domidx_to_domain_table[__index]
+
+/** Iterate over each domain */
+#define sbi_domain_for_each(__i, __d) \
+ for ((__i) = 0; ((__d) = sbi_index_to_domain(__i)); (__i)++)
+
+/** Iterate over each memory region of a domain */
+#define sbi_domain_for_each_memregion(__d, __r) \
+ for ((__r) = (__d)->regions; (__r)->order; (__r)++)
+
+/**
+ * Check whether given HART is assigned to specified domain
+ * @param dom pointer to domain
+ * @param hartid the HART ID
+ * @return TRUE if HART is assigned to domain otherwise FALSE
+ */
+bool sbi_domain_is_assigned_hart(const struct sbi_domain *dom, u32 hartid);
+
+/**
+ * Get ulong assigned HART mask for given domain and HART base ID
+ * @param dom pointer to domain
+ * @param hbase the HART base ID
+ * @return ulong possible HART mask
+ * Note: the return ulong mask will be set to zero on failure.
+ */
+ulong sbi_domain_get_assigned_hartmask(const struct sbi_domain *dom,
+ ulong hbase);
+
+/** Initialize a domain memory region as firmware region */
+void sbi_domain_memregion_initfw(struct sbi_domain_memregion *reg);
+
+/**
+ * Check whether we can access specified address for given mode and
+ * memory region flags under a domain
+ * @param dom pointer to domain
+ * @param addr the address to be checked
+ * @param mode the privilege mode of access
+ * @param access_flags bitmask of domain access types (enum sbi_domain_access)
+ * @return TRUE if access allowed otherwise FALSE
+ */
+bool sbi_domain_check_addr(const struct sbi_domain *dom,
+ unsigned long addr, unsigned long mode,
+ unsigned long access_flags);
+
+/** Finalize domain tables and startup non-root domains */
+int sbi_domain_finalize(struct sbi_scratch *scratch, u32 cold_hartid);
+
+/** Initialize domains */
+int sbi_domain_init(struct sbi_scratch *scratch, u32 cold_hartid);
+
+#endif
diff --git a/include/sbi/sbi_platform.h b/include/sbi/sbi_platform.h
index f2c3237..e1355d8 100644
--- a/include/sbi/sbi_platform.h
+++ b/include/sbi/sbi_platform.h
@@ -44,6 +44,7 @@
#include <sbi/sbi_scratch.h>
#include <sbi/sbi_version.h>
+struct sbi_domain;
struct sbi_trap_info;
/** Possible feature flags of a platform */
@@ -89,6 +90,9 @@ struct sbi_platform_operations {
*/
int (*misa_get_xlen)(void);
+ /** Get domain pointer for given HART id */
+ struct sbi_domain *(*domain_get)(u32 hartid);
+
/** Write a character to the platform console output */
void (*console_putc)(char ch);
/** Read a character from the platform console input */
@@ -448,6 +452,22 @@ static inline int sbi_platform_misa_xlen(const struct sbi_platform *plat)
}
/**
+ * Get domain pointer for given HART
+ *
+ * @param plat pointer to struct sbi_platform
+ * @param hartid shorthand letter for CPU extensions
+ *
+ * @return non-NULL domain pointer on success and NULL on failure
+ */
+static inline struct sbi_domain *sbi_platform_domain_get(
+ const struct sbi_platform *plat, u32 hartid)
+{
+ if (plat && sbi_platform_ops(plat)->domain_get)
+ return sbi_platform_ops(plat)->domain_get(hartid);
+ return NULL;
+}
+
+/**
* Write a character to the platform console output
*
* @param plat pointer to struct sbi_platform
diff --git a/lib/sbi/objects.mk b/lib/sbi/objects.mk
index fa808a0..6f2c06f 100644
--- a/lib/sbi/objects.mk
+++ b/lib/sbi/objects.mk
@@ -15,6 +15,7 @@ libsbi-objs-y += riscv_locks.o
libsbi-objs-y += sbi_bitmap.o
libsbi-objs-y += sbi_bitops.o
libsbi-objs-y += sbi_console.o
+libsbi-objs-y += sbi_domain.o
libsbi-objs-y += sbi_ecall.o
libsbi-objs-y += sbi_ecall_base.o
libsbi-objs-y += sbi_ecall_hsm.o
diff --git a/lib/sbi/sbi_domain.c b/lib/sbi/sbi_domain.c
new file mode 100644
index 0000000..86dfefd
--- /dev/null
+++ b/lib/sbi/sbi_domain.c
@@ -0,0 +1,380 @@
+/*
+ * SPDX-License-Identifier: BSD-2-Clause
+ *
+ * Copyright (c) 2020 Western Digital Corporation or its affiliates.
+ *
+ * Authors:
+ * Anup Patel <anup.patel@wdc.com>
+ */
+
+#include <sbi/riscv_asm.h>
+#include <sbi/sbi_domain.h>
+#include <sbi/sbi_hartmask.h>
+#include <sbi/sbi_hsm.h>
+#include <sbi/sbi_math.h>
+#include <sbi/sbi_platform.h>
+#include <sbi/sbi_scratch.h>
+#include <sbi/sbi_string.h>
+
+struct sbi_domain *hartid_to_domain_table[SBI_HARTMASK_MAX_BITS] = { 0 };
+struct sbi_domain *domidx_to_domain_table[SBI_DOMAIN_MAX_INDEX] = { 0 };
+
+static u32 domain_count = 0;
+
+static struct sbi_hartmask root_hmask = { 0 };
+
+#define ROOT_FW_REGION 0
+#define ROOT_ALL_REGION 1
+#define ROOT_END_REGION 2
+static struct sbi_domain_memregion root_memregs[ROOT_END_REGION + 1] = { 0 };
+
+static struct sbi_domain root = {
+ .name = "root",
+ .possible_harts = &root_hmask,
+ .regions = root_memregs,
+ .system_reset_allowed = TRUE,
+};
+
+bool sbi_domain_is_assigned_hart(const struct sbi_domain *dom, u32 hartid)
+{
+ if (dom)
+ return sbi_hartmask_test_hart(hartid, &dom->assigned_harts);
+
+ return FALSE;
+}
+
+ulong sbi_domain_get_assigned_hartmask(const struct sbi_domain *dom,
+ ulong hbase)
+{
+ ulong ret, bword, boff;
+
+ if (!dom)
+ return 0;
+
+ bword = BIT_WORD(hbase);
+ boff = BIT_WORD_OFFSET(hbase);
+
+ ret = sbi_hartmask_bits(&dom->assigned_harts)[bword++] >> boff;
+ if (boff && bword < BIT_WORD(SBI_HARTMASK_MAX_BITS)) {
+ ret |= (sbi_hartmask_bits(&dom->assigned_harts)[bword] &
+ (BIT(boff) - 1UL)) << (BITS_PER_LONG - boff);
+ }
+
+ return ret;
+}
+
+void sbi_domain_memregion_initfw(struct sbi_domain_memregion *reg)
+{
+ if (!reg)
+ return;
+
+ sbi_memcpy(reg, &root_memregs[ROOT_FW_REGION], sizeof(*reg));
+}
+
+bool sbi_domain_check_addr(const struct sbi_domain *dom,
+ unsigned long addr, unsigned long mode,
+ unsigned long access_flags)
+{
+ bool mmio = FALSE;
+ struct sbi_domain_memregion *reg;
+ unsigned long rstart, rend, rflags, rwx = 0;
+
+ if (!dom)
+ return FALSE;
+
+ if (access_flags & SBI_DOMAIN_READ)
+ rwx |= SBI_DOMAIN_MEMREGION_READABLE;
+ if (access_flags & SBI_DOMAIN_WRITE)
+ rwx |= SBI_DOMAIN_MEMREGION_WRITEABLE;
+ if (access_flags & SBI_DOMAIN_EXECUTE)
+ rwx |= SBI_DOMAIN_MEMREGION_EXECUTABLE;
+ if (access_flags & SBI_DOMAIN_MMIO)
+ mmio = TRUE;
+
+ sbi_domain_for_each_memregion(dom, reg) {
+ rflags = reg->flags;
+ if (mode == PRV_M && !(rflags & SBI_DOMAIN_MEMREGION_MMODE))
+ continue;
+
+ rstart = reg->base;
+ rend = (reg->order < __riscv_xlen) ?
+ rstart + ((1UL << reg->order) - 1) : -1UL;
+ if (rstart <= addr && addr <= rend) {
+ if ((mmio && !(rflags & SBI_DOMAIN_MEMREGION_MMIO)) ||
+ (!mmio && (rflags & SBI_DOMAIN_MEMREGION_MMIO)))
+ return FALSE;
+ return ((rflags & rwx) == rwx) ? TRUE : FALSE;
+ }
+ }
+
+ return (mode == PRV_M) ? TRUE : FALSE;
+}
+
+/* Check if region complies with constraints */
+static bool is_region_valid(const struct sbi_domain_memregion *reg)
+{
+ if (reg->order < 3 || __riscv_xlen < reg->order)
+ return FALSE;
+
+ if (reg->base & (BIT(reg->order) - 1))
+ return FALSE;
+
+ return TRUE;
+}
+
+/** Check if regionA is sub-region of regionB */
+static bool is_region_subset(const struct sbi_domain_memregion *regA,
+ const struct sbi_domain_memregion *regB)
+{
+ ulong regA_start = regA->base;
+ ulong regA_end = regA->base + (BIT(regA->order) - 1);
+ ulong regB_start = regB->base;
+ ulong regB_end = regB->base + (BIT(regA->order) - 1);
+
+ if ((regB_start <= regA_start) &&
+ (regA_start < regB_end) &&
+ (regB_start < regA_end) &&
+ (regA_end <= regB_end))
+ return TRUE;
+
+ return FALSE;
+}
+
+/** Check if regionA conflicts regionB */
+static bool is_region_conflict(const struct sbi_domain_memregion *regA,
+ const struct sbi_domain_memregion *regB)
+{
+ if ((is_region_subset(regA, regB) || is_region_subset(regB, regA)) &&
+ regA->flags == regB->flags)
+ return TRUE;
+
+ return FALSE;
+}
+
+/** Check if regionA should be placed before regionB */
+static bool is_region_before(const struct sbi_domain_memregion *regA,
+ const struct sbi_domain_memregion *regB)
+{
+ if (regA->order < regB->order)
+ return TRUE;
+
+ if ((regA->order == regB->order) &&
+ (regA->base < regB->base))
+ return TRUE;
+
+ return FALSE;
+}
+
+static int sanitize_domain(const struct sbi_platform *plat,
+ struct sbi_domain *dom)
+{
+ u32 i, j, count;
+ bool have_fw_reg;
+ struct sbi_domain_memregion treg, *reg, *reg1;
+
+ /* Check possible HARTs */
+ if (!dom->possible_harts)
+ return SBI_EINVAL;
+ sbi_hartmask_for_each_hart(i, dom->possible_harts) {
+ if (sbi_platform_hart_invalid(plat, i))
+ return SBI_EINVAL;
+ };
+
+ /* Check memory regions */
+ if (!dom->regions)
+ return SBI_EINVAL;
+ sbi_domain_for_each_memregion(dom, reg) {
+ if (!is_region_valid(reg))
+ return SBI_EINVAL;
+ }
+
+ /* Count memory regions and check presence of firmware region */
+ count = 0;
+ have_fw_reg = FALSE;
+ sbi_domain_for_each_memregion(dom, reg) {
+ if (reg->order == root_memregs[ROOT_FW_REGION].order &&
+ reg->base == root_memregs[ROOT_FW_REGION].base &&
+ reg->flags == root_memregs[ROOT_FW_REGION].flags)
+ have_fw_reg = TRUE;
+ count++;
+ }
+ if (!have_fw_reg)
+ return SBI_EINVAL;
+
+ /* Sort the memory regions */
+ for (i = 0; i < (count - 1); i++) {
+ reg = &dom->regions[i];
+ for (j = i + 1; j < count; j++) {
+ reg1 = &dom->regions[j];
+
+ if (is_region_conflict(reg1, reg))
+ return SBI_EINVAL;
+
+ if (!is_region_before(reg1, reg))
+ continue;
+
+ sbi_memcpy(&treg, reg1, sizeof(treg));
+ sbi_memcpy(reg1, reg, sizeof(treg));
+ sbi_memcpy(reg, &treg, sizeof(treg));
+ }
+ }
+
+ /*
+ * We don't need to check boot HART id of domain because if boot
+ * HART id is not possible/assigned to this domain then it won't
+ * be started at boot-time by sbi_domain_finalize().
+ */
+
+ /*
+ * Check next mode
+ *
+ * We only allow next mode to be S-mode or U-mode.so that we can
+ * protect M-mode context and enforce checks on memory accesses.
+ */
+ if (dom->next_mode != PRV_S &&
+ dom->next_mode != PRV_U)
+ return SBI_EINVAL;
+
+ /* Check next address and next mode*/
+ if (!sbi_domain_check_addr(dom, dom->next_addr, dom->next_mode,
+ SBI_DOMAIN_EXECUTE))
+ return SBI_EINVAL;
+
+ return 0;
+}
+
+int sbi_domain_finalize(struct sbi_scratch *scratch, u32 cold_hartid)
+{
+ int rc;
+ u32 i, j, dhart;
+ bool dom_exists;
+ struct sbi_domain *dom, *tdom;
+ const struct sbi_platform *plat = sbi_platform_ptr(scratch);
+
+ /* Discover domains */
+ for (i = 0; i < SBI_HARTMASK_MAX_BITS; i++) {
+ /* Ignore invalid HART */
+ if (sbi_platform_hart_invalid(plat, i))
+ continue;
+
+ /* Get domain assigned to HART */
+ dom = sbi_platform_domain_get(plat, i);
+ if (!dom)
+ continue;
+
+ /* Check if domain already discovered */
+ dom_exists = FALSE;
+ sbi_domain_for_each(j, tdom) {
+ if (tdom == dom) {
+ dom_exists = TRUE;
+ break;
+ }
+ }
+
+ /* Newly discovered domain */
+ if (!dom_exists) {
+ /*
+ * Ensure that we have room for Domain Index to
+ * HART ID mapping
+ */
+ if (domain_count <= SBI_DOMAIN_MAX_INDEX)
+ return SBI_ENOSPC;
+
+ /* Sanitize discovered domain */
+ rc = sanitize_domain(plat, dom);
+ if (rc)
+ return rc;
+
+ /* Assign index to domain */
+ dom->index = domain_count++;
+ domidx_to_domain_table[dom->index] = dom;
+
+ /* Clear assigned HARTs of domain */
+ sbi_hartmask_clear_all(&dom->assigned_harts);
+ }
+
+ /* Assign domain to HART if HART is a possible HART */
+ if (sbi_hartmask_test_hart(i, dom->possible_harts)) {
+ tdom = hartid_to_domain_table[i];
+ if (tdom)
+ sbi_hartmask_clear_hart(i,
+ &tdom->assigned_harts);
+ hartid_to_domain_table[i] = dom;
+ sbi_hartmask_set_hart(i, &dom->assigned_harts);
+ }
+ }
+
+ /* Startup boot HART of domains */
+ sbi_domain_for_each(i, dom) {
+ /* Domain boot HART */
+ dhart = dom->boot_hartid;
+
+ /* Ignore if boot HART not possible for this domain */
+ if (!sbi_hartmask_test_hart(i, dom->possible_harts))
+ continue;
+
+ /* Ignore if boot HART assigned different domain */
+ if (sbi_hartid_to_domain(dhart) != dom ||
+ !sbi_hartmask_test_hart(i, &dom->assigned_harts))
+ continue;
+
+ /* Startup boot HART of domain */
+ if (dhart == cold_hartid) {
+ scratch->next_addr = dom->next_addr;
+ scratch->next_mode = dom->next_mode;
+ scratch->next_arg1 = dom->next_arg1;
+ } else {
+ rc = sbi_hsm_hart_start(scratch, dhart, dom->next_addr,
+ dom->next_mode, dom->next_arg1);
+ if (rc)
+ return rc;
+ }
+ }
+
+ return 0;
+}
+
+int sbi_domain_init(struct sbi_scratch *scratch, u32 cold_hartid)
+{
+ u32 i;
+ const struct sbi_platform *plat = sbi_platform_ptr(scratch);
+
+ /* Root domain firmware memory region */
+ root_memregs[ROOT_FW_REGION].order = log2roundup(scratch->fw_size);
+ root_memregs[ROOT_FW_REGION].base = scratch->fw_start &
+ ~((1UL << root_memregs[0].order) - 1UL);
+ root_memregs[ROOT_FW_REGION].flags = 0;
+
+ /* Root domain allow everything memory region */
+ root_memregs[ROOT_ALL_REGION].order = __riscv_xlen;
+ root_memregs[ROOT_ALL_REGION].base = 0;
+ root_memregs[ROOT_ALL_REGION].flags = (SBI_DOMAIN_MEMREGION_READABLE |
+ SBI_DOMAIN_MEMREGION_WRITEABLE |
+ SBI_DOMAIN_MEMREGION_EXECUTABLE);
+
+ /* Root domain memory region end */
+ root_memregs[ROOT_END_REGION].order = 0;
+
+ /* Root domain boot HART id is same as coldboot HART id */
+ root.boot_hartid = cold_hartid;
+
+ /* Root domain next booting stage details */
+ root.next_arg1 = scratch->next_arg1;
+ root.next_addr = scratch->next_addr;
+ root.next_mode = scratch->next_mode;
+
+ /* Select root domain for all valid HARTs */
+ for (i = 0; i < SBI_HARTMASK_MAX_BITS; i++) {
+ if (sbi_platform_hart_invalid(plat, i))
+ continue;
+ sbi_hartmask_set_hart(i, &root_hmask);
+ hartid_to_domain_table[i] = &root;
+ sbi_hartmask_set_hart(i, &root.assigned_harts);
+ }
+
+ /* Set root domain index */
+ root.index = domain_count++;
+ domidx_to_domain_table[root.index] = &root;
+
+ return 0;
+}
diff --git a/lib/sbi/sbi_init.c b/lib/sbi/sbi_init.c
index 5cedb15..406cb3f 100644
--- a/lib/sbi/sbi_init.c
+++ b/lib/sbi/sbi_init.c
@@ -12,6 +12,7 @@
#include <sbi/riscv_barrier.h>
#include <sbi/riscv_locks.h>
#include <sbi/sbi_console.h>
+#include <sbi/sbi_domain.h>
#include <sbi/sbi_ecall.h>
#include <sbi/sbi_hart.h>
#include <sbi/sbi_hartmask.h>
@@ -169,6 +170,11 @@ static void __noreturn init_coldboot(struct sbi_scratch *scratch, u32 hartid)
if (rc)
sbi_hart_hang();
+ /* Note: This has to be second thing in coldboot init sequence */
+ rc = sbi_domain_init(scratch, hartid);
+ if (rc)
+ sbi_hart_hang();
+
init_count_offset = sbi_scratch_alloc_offset(__SIZEOF_POINTER__,
"INIT_COUNT");
if (!init_count_offset)
@@ -210,10 +216,24 @@ static void __noreturn init_coldboot(struct sbi_scratch *scratch, u32 hartid)
if (rc)
sbi_hart_hang();
+ /*
+ * Note: Finalize domains after HSM initialization so that we
+ * can startup non-root domains.
+ * Note: Finalize domains before HART PMP configuration so
+ * that we use correct domain for configuring PMP.
+ */
+ rc = sbi_domain_finalize(scratch, hartid);
+ if (rc)
+ sbi_hart_hang();
+
rc = sbi_hart_pmp_configure(scratch);
if (rc)
sbi_hart_hang();
+ /*
+ * Note: Platform final initialization should be last so that
+ * it sees correct domain assignment and PMP configuration.
+ */
rc = sbi_platform_final_init(plat, TRUE);
if (rc)
sbi_hart_hang();