ARM: Unified PD,EC,SC,PT,SM,HIP,MBUF class.

This commit is contained in:
Udo Steinberg 2022-07-25 19:21:48 +02:00
parent 33f6645939
commit e5533fac12
29 changed files with 445 additions and 32 deletions

94
inc/aarch64/ec_arch.hpp Normal file
View File

@ -0,0 +1,94 @@
/*
* Execution Context (EC)
*
* Copyright (C) 2019-2023 Udo Steinberg, BedRock Systems, Inc.
*
* This file is part of the NOVA microhypervisor.
*
* NOVA is free software: you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 as
* published by the Free Software Foundation.
*
* NOVA is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License version 2 for more details.
*/
#pragma once
#include "assert.hpp"
#include "ec.hpp"
#include "extern.hpp"
#include "mtd.hpp"
#include "utcb.hpp"
class Ec_arch final : private Ec
{
friend class Ec;
private:
static constexpr auto needs_pio { false };
// Constructor: Kernel Thread
Ec_arch (cpu_t, cont_t);
// Constructor: HST EC
Ec_arch (bool, Fpu *, Space_obj *, Space_hst *, Space_pio *, cpu_t, unsigned long, uintptr_t, uintptr_t, void *);
// Constructor: GST EC
Ec_arch (bool, Fpu *, Space_obj *, Space_hst *, Vmcb *, cpu_t, unsigned long, uintptr_t);
static void handle_irq_kern() asm ("handle_irq_kern");
[[noreturn]]
static void handle_irq_user() asm ("handle_irq_user");
[[noreturn]]
static void handle_exc_kern (Exc_regs *) asm ("handle_exc_kern");
[[noreturn]]
static void handle_exc_user (Exc_regs *) asm ("handle_exc_user");
[[noreturn]]
static void ret_user_hypercall (Ec *);
[[noreturn]]
static void ret_user_exception (Ec *);
[[noreturn]]
static void ret_user_vmexit (Ec *);
[[noreturn]]
static void set_vmm_regs (Ec *);
ALWAYS_INLINE
inline void state_load (Ec *const self, Mtd_arch mtd)
{
assert (cont == ret_user_vmexit || cont == ret_user_exception);
self->get_utcb()->arch()->load (mtd, cpu_regs());
}
ALWAYS_INLINE
inline bool state_save (Ec *const self, Mtd_arch mtd)
{
assert (cont == ret_user_vmexit || cont == ret_user_exception);
return self->get_utcb()->arch()->save (mtd, cpu_regs(), self->get_obj());
}
[[noreturn]] ALWAYS_INLINE
inline void make_current()
{
uintptr_t dummy;
// Reset stack
asm volatile ("adrp %0, %1; mov sp, %0" : "=&r" (dummy) : "S" (&DSTK_TOP) : "memory");
// Become current EC and invoke continuation
(*cont)(current = this);
UNREACHED;
}
};

View File

@ -1,5 +1,5 @@
/*
* Space
* System-Call Interface
*
* Copyright (C) 2019-2023 Udo Steinberg, BedRock Systems, Inc.
*
@ -17,21 +17,5 @@
#pragma once
#include "atomic.hpp"
#include "kobject.hpp"
#include "status.hpp"
class Pd;
class Space_hst;
class Space : public Kobject
{
private:
Pd *const pd;
protected:
inline Space (Kobject::Subtype s, Pd *p) : Kobject (Kobject::Type::PD, s), pd (p) {}
public:
inline auto get_pd() const { return pd; }
};
template void Ec::send_msg<Ec_arch::ret_user_exception> (Ec *);
template void Ec::send_msg<Ec_arch::ret_user_vmexit> (Ec *);

201
src/aarch64/ec_arch.cpp Normal file
View File

@ -0,0 +1,201 @@
/*
* Execution Context (EC)
*
* Copyright (C) 2019-2023 Udo Steinberg, BedRock Systems, Inc.
*
* This file is part of the NOVA microhypervisor.
*
* NOVA is free software: you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 as
* published by the Free Software Foundation.
*
* NOVA is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License version 2 for more details.
*/
#include "assert.hpp"
#include "cpu.hpp"
#include "ec_arch.hpp"
#include "entry.hpp"
#include "event.hpp"
#include "extern.hpp"
#include "fpu.hpp"
#include "pd.hpp"
#include "sc.hpp"
#include "space_gst.hpp"
#include "space_hst.hpp"
#include "stdio.hpp"
#include "vmcb.hpp"
// Constructor: Kernel Thread
Ec_arch::Ec_arch (cpu_t c, cont_t x) : Ec (&Space_hst::nova, c, x) {}
// Constructor: HST EC
Ec_arch::Ec_arch (bool t, Fpu *f, Space_obj *obj, Space_hst *hst, Space_pio *pio, cpu_t c, unsigned long e, uintptr_t sp, uintptr_t hva, void *k) : Ec (t, f, obj, hst, pio, k, c, e, t ? send_msg<ret_user_exception> : nullptr)
{
assert (obj && hst && !pio && k);
trace (TRACE_CREATE, "EC:%p created (OBJ:%p HST:%p CPU:%u UTCB:%p %c)", static_cast<void *>(this), static_cast<void *>(obj), static_cast<void *>(hst), c, k, subtype == Kobject::Subtype::EC_LOCAL ? 'L' : 'G');
exc_regs().sp() = sp;
exc_regs().set_ep (Event::hst_arch + Event::Selector::STARTUP);
// Map UTCB
hst->update (hva, Kmem::ptr_to_phys (kpage), 0, Paging::Permissions (Paging::K | Paging::U | Paging::W | Paging::R), Memattr::ram());
}
// Constructor: GST EC
Ec_arch::Ec_arch (bool t, Fpu *f, Space_obj *obj, Space_hst *hst, Vmcb *v, cpu_t c, unsigned long e, uintptr_t sp) : Ec (t, f, obj, hst, v, nullptr, c, e, set_vmm_regs)
{
assert (obj && hst && v);
trace (TRACE_CREATE, "EC:%p created (OBJ:%p HST:%p CPU:%u VMCB:%p %c)", static_cast<void *>(this), static_cast<void *>(obj), static_cast<void *>(hst), c, static_cast<void *>(v), subtype == Kobject::Subtype::EC_VCPU_REAL ? 'R' : 'O');
exc_regs().sp() = sp;
exc_regs().set_ep (Event::gst_arch + Event::Selector::STARTUP);
}
// Factory: GST EC
Ec *Ec::create_gst (Status &s, Pd *pd, bool t, bool fpu, cpu_t cpu, unsigned long evt, uintptr_t sp, uintptr_t /*hva*/)
{
auto const obj { pd->get_obj() };
auto const hst { pd->get_hst() };
if (EXPECT_FALSE (!obj || !hst)) {
s = Status::ABORTED;
return nullptr;
}
// FIXME: Refcount updates
auto const f { fpu ? new (pd->fpu_cache) Fpu : nullptr };
auto const v { new Vmcb };
Ec *ec;
if (EXPECT_TRUE ((!fpu || f) && v && (ec = new (cache) Ec_arch (t, f, obj, hst, v, cpu, evt, sp))))
return ec;
delete v;
Fpu::operator delete (f, pd->fpu_cache);
s = Status::MEM_OBJ;
return nullptr;
}
void Ec::adjust_offset_ticks (uint64_t t)
{
if (subtype == Kobject::Subtype::EC_VCPU_OFFS)
regs.vmcb->tmr.cntvoff += t;
}
void Ec::handle_hazard (unsigned h, cont_t func)
{
if (EXPECT_FALSE (h & (Hazard::ILLEGAL | Hazard::RECALL | Hazard::SLEEP | Hazard::SCHED))) {
Cpu::preemption_point();
if (Cpu::hazard & Hazard::SLEEP) { // Reload
cont = func;
Cpu::fini();
}
if (Cpu::hazard & Hazard::SCHED) { // Reload
cont = func;
Scheduler::schedule();
}
if (h & Hazard::ILLEGAL)
kill ("Illegal execution state");
if (regs.hazard & Hazard::RECALL) { // Reload
regs.hazard.clr (Hazard::RECALL);
if (func == Ec_arch::ret_user_vmexit) {
exc_regs().set_ep (Event::gst_arch + Event::Selector::RECALL);
send_msg<Ec_arch::ret_user_vmexit> (this);
} else {
exc_regs().set_ep (Event::hst_arch + Event::Selector::RECALL);
send_msg<Ec_arch::ret_user_exception> (this);
}
}
}
// Point of no return after checking all diversions: this EC will run
if (EXPECT_FALSE (h & Hazard::FPU))
Cpu::hazard & Hazard::FPU ? Fpu::disable() : Fpu::enable();
}
void Ec_arch::ret_user_hypercall (Ec *const self)
{
auto const h { (Cpu::hazard ^ self->regs.hazard) & (Hazard::ILLEGAL | Hazard::RECALL | Hazard::FPU | Hazard::RCU | Hazard::SLEEP | Hazard::SCHED) };
if (EXPECT_FALSE (h))
self->handle_hazard (h, ret_user_hypercall);
trace (TRACE_CONT, "EC:%p %s to M:%#x IP:%#lx SP:%#lx", static_cast<void *>(self), __func__, self->exc_regs().mode(), self->exc_regs().ip(), self->exc_regs().sp());
if (Vmcb::current)
Vmcb::load_hst();
self->get_hst()->make_current();
asm volatile ("mov sp, %0;" EXPAND (LOAD_STATE ERET) : : "r" (&self->exc_regs()), "m" (self->exc_regs()));
UNREACHED;
}
void Ec_arch::ret_user_exception (Ec *const self)
{
auto const h { (Cpu::hazard ^ self->regs.hazard) & (Hazard::ILLEGAL | Hazard::RECALL | Hazard::FPU | Hazard::RCU | Hazard::SLEEP | Hazard::SCHED) };
if (EXPECT_FALSE (h))
self->handle_hazard (h, ret_user_exception);
trace (TRACE_CONT, "EC:%p %s to M:%#x IP:%#lx SP:%#lx", static_cast<void *>(self), __func__, self->exc_regs().mode(), self->exc_regs().ip(), self->exc_regs().sp());
if (Vmcb::current)
Vmcb::load_hst();
self->get_hst()->make_current();
asm volatile ("mov sp, %0;" EXPAND (LOAD_STATE ERET) : : "r" (&self->exc_regs()), "m" (self->exc_regs()));
UNREACHED;
}
void Ec_arch::ret_user_vmexit (Ec *const self)
{
auto const h { (Cpu::hazard ^ self->regs.hazard) & (Hazard::ILLEGAL | Hazard::RECALL | Hazard::FPU | Hazard::RCU | Hazard::SLEEP | Hazard::SCHED) };
if (EXPECT_FALSE (h))
self->handle_hazard (h, ret_user_vmexit);
trace (TRACE_CONT, "EC:%p %s to M:%#x IP:%#lx", static_cast<void *>(self), __func__, self->exc_regs().mode(), self->exc_regs().ip());
auto const v { self->regs.vmcb };
if (Vmcb::current != v)
v->load_gst(); // Restore full register state
else
v->load_tmr(); // Restore only vTMR PPI state
self->get_gst()->make_current();
asm volatile ("mov sp, %0;" EXPAND (LOAD_STATE ERET) : : "r" (&self->exc_regs()), "m" (self->exc_regs()));
UNREACHED;
}
void Ec_arch::set_vmm_regs (Ec *const self)
{
assert (self->is_vcpu());
assert (self->cpu == Cpu::id);
auto const v { self->regs.vmcb };
Cpu::set_vmm_regs (self->sys_regs().gpr, v->el2.hcr, v->el2.vpidr, v->el2.vmpidr, v->gic.elrsr);
send_msg<ret_user_vmexit> (self);
}

114
src/aarch64/ec_exc.cpp Normal file
View File

@ -0,0 +1,114 @@
/*
* Execution Context (EC)
*
* Copyright (C) 2019-2023 Udo Steinberg, BedRock Systems, Inc.
*
* This file is part of the NOVA microhypervisor.
*
* NOVA is free software: you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 as
* published by the Free Software Foundation.
*
* NOVA is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License version 2 for more details.
*/
#include "assert.hpp"
#include "ec_arch.hpp"
#include "event.hpp"
#include "fpu.hpp"
#include "interrupt.hpp"
#include "pd.hpp"
#include "smc.hpp"
#include "stdio.hpp"
#include "vmcb.hpp"
void Ec::fpu_load()
{
assert (fpu);
fpu->load();
regs.hazard.set (Hazard::FPU);
}
void Ec::fpu_save()
{
assert (fpu);
fpu->save();
regs.hazard.clr (Hazard::FPU);
}
void Ec_arch::handle_exc_kern (Exc_regs *r)
{
auto const iss { r->el2.esr & BIT_RANGE (24, 0) };
switch (r->ep()) {
case 0x21: // Inst Abort
case 0x25: // Data Abort
trace (TRACE_KILL, "KERN %s abort (ISS:%#lx) at IP:%#lx FAR:%#lx", r->ep() == 0x21 ? "INST" : "DATA", iss, r->el2.elr, r->el2.far);
break;
default:
trace (TRACE_KILL, "KERN exception %#lx (ISS:%#lx) at IP:%#lx", r->ep(), iss, r->el2.elr);
break;
}
current->kill ("exception");
}
void Ec_arch::handle_exc_user (Exc_regs *r)
{
auto const esr { static_cast<uint32_t>(r->el2.esr) };
Ec *const self { current };
bool resolved { false };
// SVC #0 from AArch64 state
if (EXPECT_TRUE (esr == (VAL_SHIFT (0x15, 26) | BIT (25) | 0)))
(*syscall[r->sys.gpr[0] & BIT_RANGE (3, 0)])(self);
// SVC #1 from AArch64 state
else if (esr == (VAL_SHIFT (0x15, 26) | BIT (25) | 1))
resolved = self->get_obj() == Pd::root->get_obj() && Cpu::feature (Cpu::Cpu_feature::EL3) && Smc::proxy (r->sys.gpr);
// FPU Access
else if (r->ep() == 0x7)
resolved = switch_fpu (self);
trace (TRACE_EXCEPTION, "EC:%p %s %#lx at M:%#x IP:%#lx", static_cast<void *>(self), self->is_vcpu() ? "VMX" : "EXC", r->ep(), r->mode(), r->el2.elr);
if (self->is_vcpu()) {
self->regs.vmcb->save_gst();
resolved ? ret_user_vmexit (self) : send_msg<ret_user_vmexit> (self);
} else
resolved ? ret_user_exception (self) : send_msg<ret_user_exception> (self);
}
void Ec_arch::handle_irq_kern()
{
Interrupt::handler (false);
}
void Ec_arch::handle_irq_user()
{
Ec *const self { current };
Event::Selector evt = Interrupt::handler (self->is_vcpu());
if (!self->is_vcpu())
ret_user_exception (self);
self->regs.vmcb->save_gst();
if (evt == Event::Selector::NONE)
ret_user_vmexit (self);
assert (self->regs.vmcb->tmr.cntv_act);
self->exc_regs().set_ep (Event::gst_arch + evt);
send_msg<ret_user_vmexit> (self);
}

View File

@ -45,12 +45,15 @@ vector_table:
.org vector_table + 0x200 // Synchronous
DECR_STACK
SAVE_STATE_EXC
b 1f
mov x0, sp
adr lr, 1f
b handle_exc_kern
.org vector_table + 0x280 // IRQ
DECR_STACK
SAVE_STATE
b 1f
adr lr, 1f
b handle_irq_kern
.org vector_table + 0x300 // FIQ
b .
@ -58,43 +61,61 @@ vector_table:
.org vector_table + 0x380 // SError
DECR_STACK
SAVE_STATE_EXC
b .
mov x0, sp
adr lr, 1f
b handle_exc_kern
/*
* Entry from EL0/EL1 with EL1 using AArch64
*/
.org vector_table + 0x400 // Synchronous
SAVE_STATE_EXC
b .
mov x0, sp
adrp x1, DSTK_TOP
mov sp, x1
b handle_exc_user
.org vector_table + 0x480 // IRQ
SAVE_STATE
b .
adrp x1, DSTK_TOP
mov sp, x1
b handle_irq_user
.org vector_table + 0x500 // FIQ
b .
.org vector_table + 0x580 // SError
SAVE_STATE_EXC
b .
mov x0, sp
adrp x1, DSTK_TOP
mov sp, x1
b handle_exc_user
/*
* Entry from EL0/EL1 with EL1 using AArch32
*/
.org vector_table + 0x600 // Synchronous
SAVE_STATE_EXC
b .
mov x0, sp
adrp x1, DSTK_TOP
mov sp, x1
b handle_exc_user
.org vector_table + 0x680 // IRQ
SAVE_STATE
b .
adrp x1, DSTK_TOP
mov sp, x1
b handle_irq_user
.org vector_table + 0x700 // FIQ
b .
.org vector_table + 0x780 // SError
SAVE_STATE_EXC
b .
mov x0, sp
adrp x1, DSTK_TOP
mov sp, x1
b handle_exc_user
/*
* Exit to EL2

View File

@ -15,8 +15,10 @@
* GNU General Public License version 2 for more details.
*/
#include "ec.hpp"
#include "fpu.hpp"
void Fpu::fini()
{
Ec::switch_fpu (nullptr);
}

View File

@ -18,7 +18,6 @@
#include "acpi.hpp"
#include "assert.hpp"
#include "gicc.hpp"
#include "ptab_hpt.hpp"
#include "space_hst.hpp"
#include "stdio.hpp"

View File

@ -21,7 +21,6 @@
#include "gicd.hpp"
#include "lock_guard.hpp"
#include "lowlevel.hpp"
#include "ptab_hpt.hpp"
#include "space_hst.hpp"
#include "stdio.hpp"

View File

@ -17,7 +17,6 @@
#include "acpi.hpp"
#include "gich.hpp"
#include "ptab_hpt.hpp"
#include "space_hst.hpp"
#include "stdio.hpp"

View File

@ -21,7 +21,6 @@
#include "gicd.hpp"
#include "gicr.hpp"
#include "lowlevel.hpp"
#include "ptab_hpt.hpp"
#include "space_hst.hpp"
#include "stdio.hpp"

View File

@ -29,6 +29,7 @@
#include "hip.hpp"
#include "interrupt.hpp"
#include "multiboot.hpp"
#include "ptab_hpt.hpp"
#include "sm.hpp"
#include "space_hst.hpp"
#include "space_obj.hpp"