blob: e3753afacab0fae8669ab17191eb71ba381c55a9 [file] [log] [blame]
// Copyright 2022 The Fuchsia Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include <lib/arch/arm64/feature.h>
#include <lib/arch/arm64/system.h>
namespace arch {
ArmCurrentEl ArmDropEl3() {
auto current_el = ArmCurrentEl::Read();
if (current_el.el() < 3) {
return current_el;
}
bool has_el2 = ArmIdAa64Pfr0El1::Read().el2() != ArmIdAa64Pfr0El1::El::kNone;
// Set up the basics in SCR_EL3 for enabling the next lower EL.
ArmScrEl3::Modify([has_el2](auto& scr) {
scr.set_rw(true) // Next lower EL is AArch64.
.set_ns(true) // EL<3 in Non-Secure state, no use Secure memory.
.set_hce(has_el2); // Enable HVC insn to escape from EL1 to EL2.
});
// Set up SPSR_EL3 so the ERET instruction will return to the next lower EL.
ArmSpsrEl3::Modify([has_el2](auto& spsr) {
// Return to ELx, using SP_ELx as SP.
spsr.set_m(has_el2 ? ArmSavedProgramStatusRegister::ExceptionLevel::kEl2h
: ArmSavedProgramStatusRegister::ExceptionLevel::kEl1h)
// Disable all kinds of exceptions.
.set_d(true)
.set_a(true)
.set_i(true)
.set_f(true);
});
// Do an ERET "in place", so it restores the SP and PC that we're already
// using. The only difference between dropping to EL2 and to EL1 here is
// whether SP_EL2 or SP_EL1 will be used. When EL2 is not supported, it's
// harmless to set SP_EL2 anyway. When EL2 is supported, we clobber SP_EL1
// on the presumption that EL2 code will be resetting EL1 state anyway.
uint64_t sp, pc;
__asm__ volatile(
R"""(
adr %[pc], 0f
mov %[sp], sp
msr elr_el3, %[pc]
msr sp_el2, %[sp]
msr sp_el1, %[sp]
isb
eret
0:
)"""
: [sp] "=r"(sp), [pc] "=r"(pc));
// Return the new EL we're running at now.
current_el = ArmCurrentEl::Read();
ZX_DEBUG_ASSERT(current_el.el() == (has_el2 ? 2 : 1));
return current_el;
}
} // namespace arch