TF-RMM Release v0.1.0
This is the first external release of TF-RMM and provides a reference
implementation of Realm Management Monitor (RMM) as specified by the
RMM Beta0 specification[1].
The `docs/readme.rst` has more details about the project and
`docs/getting_started/getting-started.rst` has details on how to get
started with TF-RMM.
[1] https://developer.arm.com/documentation/den0137/1-0bet0/?lang=en
Signed-off-by: Soby Mathew <soby.mathew@arm.com>
Change-Id: I205ef14c015e4a37ae9ae1a64e4cd22eb8da746e
diff --git a/runtime/core/sysregs.c b/runtime/core/sysregs.c
new file mode 100644
index 0000000..55fac96
--- /dev/null
+++ b/runtime/core/sysregs.c
@@ -0,0 +1,222 @@
+/*
+ * SPDX-License-Identifier: BSD-3-Clause
+ *
+ * SPDX-FileCopyrightText: Copyright TF-RMM Contributors.
+ */
+
+#include <arch.h>
+#include <arch_helpers.h>
+#include <debug.h>
+#include <esr.h>
+#include <memory_alloc.h>
+#include <rec.h>
+#include <smc-rmi.h>
+
+#define SYSREG_READ_CASE(reg) \
+ case ESR_EL2_SYSREG_##reg: return read_##reg()
+
+static unsigned long read_idreg(unsigned int idreg)
+{
+ switch (idreg) {
+ SYSREG_READ_CASE(ID_AA64PFR0_EL1);
+ SYSREG_READ_CASE(ID_AA64PFR1_EL1);
+ /*
+ * TODO: not supported without SVE:
+ * SYSREG_READ_CASE(ID_AA64ZFR0_EL1);
+ */
+ SYSREG_READ_CASE(ID_AA64DFR0_EL1);
+ SYSREG_READ_CASE(ID_AA64DFR1_EL1);
+ SYSREG_READ_CASE(ID_AA64AFR0_EL1);
+ SYSREG_READ_CASE(ID_AA64AFR1_EL1);
+ SYSREG_READ_CASE(ID_AA64ISAR0_EL1);
+ SYSREG_READ_CASE(ID_AA64ISAR1_EL1);
+ SYSREG_READ_CASE(ID_AA64MMFR0_EL1);
+ SYSREG_READ_CASE(ID_AA64MMFR1_EL1);
+ SYSREG_READ_CASE(ID_AA64MMFR2_EL1);
+
+ default:
+ /* All other encodings are in the RES0 space */
+ return 0UL;
+ }
+}
+
+/*
+ * Handle ID_AA64XXX<n>_EL1 instructions
+ */
+static bool handle_id_sysreg_trap(struct rec *rec,
+ struct rmi_rec_exit *rec_exit,
+ unsigned long esr)
+{
+ unsigned int rt;
+ unsigned long idreg, mask;
+
+ /*
+ * We only set HCR_EL2.TID3 to trap ID registers at the moment and
+ * that only traps reads of registers. Seeing a write here indicates a
+ * consistency problem with the RMM and we should panic immediately.
+ */
+ assert(!ESR_EL2_SYSREG_IS_WRITE(esr));
+
+ /*
+ * Read Rt value from the issued instruction,
+ * the general-purpose register used for the transfer.
+ */
+ rt = ESR_EL2_SYSREG_ISS_RT(esr);
+
+ /* Handle writes to XZR register */
+ if (rt == 31U) {
+ return true;
+ }
+
+ idreg = esr & ESR_EL2_SYSREG_MASK;
+
+ if (idreg == ESR_EL2_SYSREG_ID_AA64ISAR1_EL1) {
+ /* Clear Address and Generic Authentication bits */
+ mask = (0xfUL << ESR_EL2_SYSREG_ID_AA64ISAR1_APA_SHIFT) |
+ (0xfUL << ESR_EL2_SYSREG_ID_AA64ISAR1_API_SHIFT) |
+ (0xfUL << ESR_EL2_SYSREG_ID_AA64ISAR1_GPA_SHIFT) |
+ (0xfUL << ESR_EL2_SYSREG_ID_AA64ISAR1_GPI_SHIFT);
+ /*
+ * Workaround for TF-A trapping AMU registers access
+ * to EL3 in Realm state
+ */
+ } else if (idreg == ESR_EL2_SYSREG_ID_AA64PFR0_EL1) {
+ /* Clear support for Activity Monitors Extension */
+ mask = MASK(ID_AA64PFR0_EL1_AMU);
+
+ /*
+ * Clear support for SVE. This is a temporary fix until RMM
+ * completely supports SVE.
+ */
+ mask |= MASK(ID_AA64PFR0_EL1_SVE);
+ } else {
+ mask = 0UL;
+ }
+
+ ARRAY_WRITE(rec->regs, rt, read_idreg(idreg) & ~mask);
+
+ return true;
+}
+
+static bool handle_icc_el1_sysreg_trap(struct rec *rec,
+ struct rmi_rec_exit *rec_exit,
+ unsigned long esr)
+{
+ __unused unsigned long sysreg = esr & ESR_EL2_SYSREG_MASK;
+
+ /*
+ * We should only have configured ICH_HCR_EL2 to trap on DIR and we
+ * always trap on the SGIRs following the architecture, so make sure
+ * we're not accidentally trapping on some other register here.
+ */
+ assert((sysreg == ESR_EL2_SYSREG_ICC_DIR) ||
+ (sysreg == ESR_EL2_SYSREG_ICC_SGI1R_EL1) ||
+ (sysreg == ESR_EL2_SYSREG_ICC_SGI0R_EL1));
+
+ /*
+ * The registers above should only trap to EL2 for writes, read
+ * instructions are not defined and should cause an Undefined exception
+ * at EL1.
+ */
+ assert(ESR_EL2_SYSREG_IS_WRITE(esr));
+
+ rec_exit->exit_reason = RMI_EXIT_SYNC;
+ rec_exit->esr = esr;
+ return false;
+}
+
+typedef bool (*sysreg_handler_fn)(struct rec *rec, struct rmi_rec_exit *rec_exit,
+ unsigned long esr);
+
+struct sysreg_handler {
+ unsigned long esr_mask;
+ unsigned long esr_value;
+ sysreg_handler_fn fn;
+};
+
+#define SYSREG_HANDLER(_mask, _value, _handler_fn) \
+ { .esr_mask = (_mask), .esr_value = (_value), .fn = _handler_fn }
+
+static const struct sysreg_handler sysreg_handlers[] = {
+ SYSREG_HANDLER(ESR_EL2_SYSREG_ID_MASK, ESR_EL2_SYSREG_ID, handle_id_sysreg_trap),
+ SYSREG_HANDLER(ESR_EL2_SYSREG_ICC_EL1_MASK, ESR_EL2_SYSREG_ICC_EL1, handle_icc_el1_sysreg_trap),
+ SYSREG_HANDLER(ESR_EL2_SYSREG_MASK, ESR_EL2_SYSREG_ICC_PMR_EL1, handle_icc_el1_sysreg_trap)
+};
+
+static unsigned long get_sysreg_write_value(struct rec *rec, unsigned long esr)
+{
+ unsigned int rt = esr_sysreg_rt(esr);
+ unsigned long val;
+
+ /* Handle reads from XZR register */
+ if (rt == 31U) {
+ return 0UL;
+ }
+
+ ARRAY_READ(rec->regs, rt, val);
+ return val;
+}
+
+static void emulate_sysreg_access_ns(struct rec *rec, struct rmi_rec_exit *rec_exit,
+ unsigned long esr)
+{
+ if (ESR_EL2_SYSREG_IS_WRITE(esr)) {
+ rec_exit->gprs[0] = get_sysreg_write_value(rec, esr);
+ }
+}
+
+/*
+ * Handle trapped MSR, MRS or System instruction execution
+ * in AArch64 state
+ */
+bool handle_sysreg_access_trap(struct rec *rec, struct rmi_rec_exit *rec_exit,
+ unsigned long esr)
+{
+ /*
+ * Read Rt value from the issued instruction,
+ * the general-purpose register used for the transfer.
+ */
+ unsigned int rt = ESR_EL2_SYSREG_ISS_RT(esr);
+ unsigned int i;
+ unsigned int __unused op0, op1, crn, crm, op2;
+ unsigned long __unused sysreg;
+
+ /* Check for 32-bit instruction trapped */
+ assert(ESR_IL(esr) != 0UL);
+
+ for (i = 0U; i < ARRAY_LEN(sysreg_handlers); i++) {
+ const struct sysreg_handler *handler = &sysreg_handlers[i];
+ bool handled;
+
+ if ((esr & handler->esr_mask) == handler->esr_value) {
+ handled = handler->fn(rec, rec_exit, esr);
+ if (!handled) {
+ emulate_sysreg_access_ns(rec, rec_exit, esr);
+ }
+ return handled;
+ }
+ }
+
+ /*
+ * For now, treat all unhandled accesses as RAZ/WI.
+ * Handle writes to XZR register.
+ */
+ if (!ESR_EL2_SYSREG_IS_WRITE(esr) && (rt != 31U)) {
+ ARRAY_WRITE(rec->regs, rt, 0UL);
+ }
+
+ sysreg = esr & ESR_EL2_SYSREG_MASK;
+
+ /* Extract sytem register encoding */
+ op0 = EXTRACT(ESR_EL2_SYSREG_TRAP_OP0, sysreg);
+ op1 = EXTRACT(ESR_EL2_SYSREG_TRAP_OP1, sysreg);
+ crn = EXTRACT(ESR_EL2_SYSREG_TRAP_CRN, sysreg);
+ crm = EXTRACT(ESR_EL2_SYSREG_TRAP_CRM, sysreg);
+ op2 = EXTRACT(ESR_EL2_SYSREG_TRAP_OP2, sysreg);
+
+ INFO("Unhandled %s S%u_%u_C%u_C%u_%u\n",
+ ESR_EL2_SYSREG_IS_WRITE(esr) ? "write" : "read",
+ op0, op1, crn, crm, op2);
+
+ return true;
+}