blob: c815ffc66b03cac63279802c7441daae403ae661 [file] [log] [blame]
// Copyright 2021 The Fuchsia Authors
//
// Use of this source code is governed by a MIT-style
// license that can be found in the LICENSE file or at
// https://opensource.org/licenses/MIT
#include <lib/arch/arm64/system.h>
#include <lib/zbi-format/driver-config.h>
#include <lib/zbi-format/zbi.h>
#include <lib/zbitl/view.h>
#include <stdio.h>
#include <phys/arch/arch-phys-info.h>
#include <phys/exception.h>
#include <phys/main.h>
#include <phys/stack.h>
#include "psci.h"
// The regs.h header is really only used by assembly code.
// This is just here to get the static_asserts done somewhere.
#include "regs.h"
// The vector table is defined in assembly (see exception.S).
extern "C" const uint32_t phys_exception[];
namespace {
template <typename Elr, typename Spsr, typename Sp>
struct ResumeRegs {
void operator()(PhysExceptionState& state, uint64_t pc, uint64_t sp, uint64_t psr) {
// The SPSR_ELx and ELR_ELx for the current EL are restored by ERET.
elr_.set_pc(pc);
elr_.Write();
spsr_.set_reg_value(psr);
spsr_.Write();
// The SP is more complicated. See below.
sp_(spsr_, sp);
}
Elr elr_;
Spsr spsr_;
Sp sp_;
};
template <typename Elr, typename Spsr, typename Sp>
ResumeRegs(Elr&&, Spsr&&, Sp&&) -> ResumeRegs<Elr, Spsr, Sp>;
// We assume SPSel is set in the current EL, so the SP that exception.S will
// restore will be SP_ELx. If we're returning to the same EL, there is nothing
// more to do. If we're returning to a lower EL, we have to set either SP_ELx
// for that EL or SP_EL0 depending on the SPSel bit being restored.
struct SpSameEl {
void set_sp(uint64_t pc) {}
void Write() {}
};
struct SpBadEl {
void set_sp(uint64_t pc) {}
void Write() { ZX_PANIC("cannot return to a higher EL!"); }
};
template <class El1, class El2, class El3>
constexpr auto ResumeSpElx = [](auto&& spsr, uint64_t sp) {
auto write = [sp](auto&& sp_elx) {
sp_elx.set_sp(sp);
sp_elx.Write();
};
if (spsr.spsel()) {
spsr.el().ForThisEl(El1(), El2(), El3(), write);
} else {
write(arch::ArmSpEl0());
}
};
constexpr auto ResumeSpEl1 = ResumeSpElx<SpSameEl, SpBadEl, SpBadEl>;
constexpr auto ResumeSpEl2 = ResumeSpElx<arch::ArmSpEl1, SpSameEl, SpBadEl>;
constexpr auto ResumeSpEl3 = ResumeSpElx<arch::ArmSpEl1, arch::ArmSpEl2, SpSameEl>;
const zbi_dcfg_arm_psci_driver_t* FindPsciConfig(EarlyBootZbi zbi) {
const zbi_dcfg_arm_psci_driver_t* cfg = nullptr;
for (auto [header, wrapped_payload] : zbi) {
if (header->type == ZBI_TYPE_KERNEL_DRIVER && header->extra == ZBI_KERNEL_DRIVER_ARM_PSCI) {
ktl::span payload = wrapped_payload.get();
if (payload.size() >= sizeof(*cfg)) {
// Keep looping. The Last one wins.
cfg = reinterpret_cast<const zbi_dcfg_arm_psci_driver_t*>(payload.data());
}
}
}
zbi.ignore_error();
return cfg;
}
ArchPhysInfo gArchPhysInfoStorage;
} // namespace
ArchPhysInfo* gArchPhysInfo;
void ArchSetUp(ktl::optional<EarlyBootZbi> zbi) {
gArchPhysInfo = &gArchPhysInfoStorage;
// Hereafter any machine exceptions should be handled.
ArmSetVbar(phys_exception);
if (zbi) {
ArmPsciSetup(FindPsciConfig(*zbi));
}
}
uint64_t PhysExceptionResume(PhysExceptionState& state, uint64_t pc, uint64_t sp, uint64_t psr) {
// Update the fields in the trap frame just for consistency. The PC and SPSR
// here are never used by the hardware, but the SP is used sometimes.
state.regs.pc = pc;
state.regs.sp = sp;
state.regs.cpsr = psr;
arch::ArmCurrentEl::Read().ForThisEl(
ResumeRegs{arch::ArmElrEl1(), arch::ArmSpsrEl1(), ResumeSpEl1},
ResumeRegs{arch::ArmElrEl2(), arch::ArmSpsrEl2(), ResumeSpEl2},
ResumeRegs{arch::ArmElrEl3(), arch::ArmSpsrEl3(), ResumeSpEl3},
// Set the CPU values to match what's now in the struct.
[&](auto&& resume_regs) { resume_regs(state, pc, sp, psr); });
return PHYS_EXCEPTION_RESUME;
}