blob: 06e71a3d83b5d252c09a16ad12974e23988f2666 [file] [log] [blame]
/*
* SPDX-License-Identifier: BSD-3-Clause
*
* SPDX-FileCopyrightText: Copyright TF-RMM Contributors.
*/
#include <arch.h>
#include <arch_features.h>
#include <arch_helpers.h>
#include <debug.h>
#include <esr.h>
#include <memory_alloc.h>
#include <rec.h>
#include <smc-rmi.h>
#define SYSREG_CASE(reg) \
case ESR_EL2_SYSREG_##ID_AA64##reg##_EL1:
#define SYSREG_READ(reg) \
read_ID_AA64##reg##_EL1()
#define SYSREG_READ_CLEAR(reg) \
(read_ID_AA64##reg##_EL1() & \
~(ID_AA64##reg##_EL1_CLEAR))
#define SYSREG_READ_CLEAR_SET(reg) \
((read_ID_AA64##reg##_EL1() & \
~(ID_AA64##reg##_EL1_CLEAR)) | \
(ID_AA64##reg##_EL1_SET))
/* System registers ID_AA64xxx_EL1 feature clear masks and set values */
/*
* ID_AA64DFR0_EL1:
*
* Cleared fields:
* - Debug architecture version:
* set in ID_AA64DFR0_EL1_SET
* - Trace unit System registers not implemented
* - Number of breakpoints:
* set in ID_AA64DFR0_EL1_SET
* - PMU Snapshot extension not implemented
* - Number of watchpoints:
* set in ID_AA64DFR0_EL1_SET
* - Synchronous-exception-based event profiling not implemented
* - Number of breakpoints that are context-aware
* - Statistical Profiling Extension not implemented
* - Armv8.4 Self-hosted Trace Extension not implemented
* - Trace Buffer Extension not implemented
* - Branch Record Buffer Extension not implemented
* - Trace Buffer External Mode not implemented
*/
#define ID_AA64DFR0_EL1_CLEAR \
MASK(ID_AA64DFR0_EL1_DebugVer) | \
MASK(ID_AA64DFR0_EL1_TraceVer) | \
MASK(ID_AA64DFR0_EL1_BRPs) | \
MASK(ID_AA64DFR0_EL1_PMSS) | \
MASK(ID_AA64DFR0_EL1_WRPs) | \
MASK(ID_AA64DFR0_EL1_SEBEP) | \
MASK(ID_AA64DFR0_EL1_CTX_CMPS) | \
MASK(ID_AA64DFR0_EL1_PMSVer) | \
MASK(ID_AA64DFR0_EL1_TraceFilt) | \
MASK(ID_AA64DFR0_EL1_TraceBuffer) | \
MASK(ID_AA64DFR0_EL1_BRBE) | \
MASK(ID_AA64DFR0_EL1_ExtTrcBuff)
/*
* Set fields:
* - Armv8 debug architecture
* - Number of breakpoints: 2
* - Number of watchpoints: 2
*/
#define ID_AA64DFR0_EL1_SET \
INPLACE(ID_AA64DFR0_EL1_DebugVer, ID_AA64DFR0_EL1_Debugv8) | \
INPLACE(ID_AA64DFR0_EL1_BRPs, 1UL) | \
INPLACE(ID_AA64DFR0_EL1_WRPs, 1UL)
/*
* ID_AA64DFR1_EL1:
*
* Cleared fields:
* - Exception-based event profiling not implemented
* - PMU fixed-function instruction counter not implemented
*/
#define ID_AA64DFR1_EL1_CLEAR \
MASK(ID_AA64DFR1_EL1_EBEP) | \
MASK(ID_AA64DFR1_EL1_ICNTR)
/*
* ID_AA64PFR0_EL1:
*
* Cleared fields:
* - Activity Monitors Extension not implemented
*/
#define ID_AA64PFR0_EL1_CLEAR \
MASK(ID_AA64PFR0_EL1_AMU)
/*
* ID_AA64PFR1_EL1:
*
* Cleared fields:
* - Memory Tagging Extension is not implemented
*/
#define ID_AA64PFR1_EL1_CLEAR \
MASK(ID_AA64PFR1_EL1_MTE)
/*
* 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, value;
/*
* 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 bits [9:5] of ISS field cannot exceed 0b11111.
*/
rt = ESR_EL2_SYSREG_ISS_RT(esr);
/* Handle writes to XZR register */
if (rt == 31U) {
return true;
}
idreg = esr & ESR_EL2_SYSREG_MASK;
switch (idreg) {
SYSREG_CASE(AFR0)
value = SYSREG_READ(AFR0);
break;
SYSREG_CASE(AFR1)
value = SYSREG_READ(AFR1);
break;
SYSREG_CASE(DFR0)
value = SYSREG_READ_CLEAR_SET(DFR0);
break;
SYSREG_CASE(DFR1)
value = SYSREG_READ_CLEAR(DFR1);
break;
SYSREG_CASE(ISAR0)
value = SYSREG_READ(ISAR0);
break;
SYSREG_CASE(ISAR1)
value = SYSREG_READ(ISAR1);
break;
SYSREG_CASE(MMFR0)
value = SYSREG_READ(MMFR0);
break;
SYSREG_CASE(MMFR1)
value = SYSREG_READ(MMFR1);
break;
SYSREG_CASE(MMFR2)
value = SYSREG_READ(MMFR2);
break;
SYSREG_CASE(PFR0)
/*
* Workaround for TF-A trapping AMU registers access
* to EL3 in Realm state.
*/
value = SYSREG_READ_CLEAR(PFR0);
/*
* Clear SVE bits if architecture supports it but SVE is
* disabled for current realm.
*/
if ((EXTRACT(ID_AA64PFR0_EL1_SVE, value) != 0UL) &&
rec->realm_info.sve_enabled == false) {
value &= ~MASK(ID_AA64PFR0_EL1_SVE);
}
break;
SYSREG_CASE(PFR1)
value = SYSREG_READ_CLEAR(PFR1);
break;
SYSREG_CASE(ZFR0)
if (is_feat_sve_present() && rec->realm_info.sve_enabled) {
value = read_id_aa64zfr0_el1();
} else {
value = 0UL;
}
break;
default:
/* All other encodings are in the RES0 space */
value = 0UL;
}
rec->regs[rt] = value;
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)
{
/* Rt bits [9:5] of ISS field cannot exceed 0b11111 */
unsigned int rt = ESR_EL2_SYSREG_ISS_RT(esr);
/* Handle reads from XZR register */
if (rt == 31U) {
return 0UL;
}
return rec->regs[rt];
}
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.
* Rt bits [9:5] of ISS field cannot exceed 0b11111.
*/
unsigned int rt = ESR_EL2_SYSREG_ISS_RT(esr);
unsigned int __unused op0, op1, crn, crm, op2;
unsigned long __unused sysreg;
/* Check for 32-bit instruction trapped */
assert(ESR_IL(esr) != 0UL);
for (unsigned int i = 0U; i < ARRAY_LEN(sysreg_handlers); i++) {
const struct sysreg_handler *handler = &sysreg_handlers[i];
if ((esr & handler->esr_mask) == handler->esr_value) {
bool 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)) {
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;
}