204 lines
5.2 KiB
C++
204 lines
5.2 KiB
C++
/*
|
|
* \brief VMM cpu object
|
|
* \author Stefan Kalkowski
|
|
* \date 2019-07-18
|
|
*/
|
|
|
|
/*
|
|
* Copyright (C) 2019 Genode Labs GmbH
|
|
*
|
|
* This file is part of the Genode OS framework, which is distributed
|
|
* under the terms of the GNU Affero General Public License version 3.
|
|
*/
|
|
#include <cpu.h>
|
|
#include <vm.h>
|
|
#include <psci.h>
|
|
|
|
using Vmm::Cpu_base;
|
|
using Vmm::Cpu;
|
|
using Vmm::Gic;
|
|
|
|
Genode::Lock & Vmm::lock() { static Genode::Lock l {}; return l; }
|
|
|
|
|
|
Cpu_base::System_register::System_register(unsigned op0,
|
|
unsigned crn,
|
|
unsigned op1,
|
|
unsigned crm,
|
|
unsigned op2,
|
|
const char * name,
|
|
bool writeable,
|
|
Genode::addr_t v,
|
|
Genode::Avl_tree<System_register> & tree)
|
|
: _encoding(Iss::value(op0, crn, op1, crm, op2)),
|
|
_name(name),
|
|
_writeable(writeable),
|
|
_value(v)
|
|
{
|
|
tree.insert(this);
|
|
}
|
|
|
|
|
|
bool Cpu_base::_handle_sys_reg()
|
|
{
|
|
using Iss = System_register::Iss;
|
|
|
|
Iss::access_t v = _state.esr_el2;
|
|
System_register * reg = _reg_tree.first();
|
|
if (reg) reg = reg->find_by_encoding(Iss::mask_encoding(v));
|
|
|
|
if (!reg) {
|
|
Genode::error("ignore unknown system register access @ ip=", (void*)_state.ip, ":");
|
|
Genode::error(Iss::Direction::get(v) ? "read" : "write",
|
|
": "
|
|
"op0=", Iss::Opcode0::get(v), " "
|
|
"op1=", Iss::Opcode1::get(v), " "
|
|
"r", Iss::Register::get(v), " "
|
|
"crn=", Iss::Crn::get(v), " "
|
|
"crm=", Iss::Crm::get(v), " ",
|
|
"op2=", Iss::Opcode2::get(v));
|
|
if (Iss::Direction::get(v)) _state.reg(Iss::Register::get(v), 0);
|
|
_state.ip += sizeof(Genode::uint32_t);
|
|
return false;
|
|
}
|
|
|
|
if (Iss::Direction::get(v)) { /* read access */
|
|
_state.reg(Iss::Register::get(v), reg->read());
|
|
} else { /* write access */
|
|
if (!reg->writeable()) {
|
|
Genode::error("writing to system register ",
|
|
reg->name(), " not allowed!");
|
|
return false;
|
|
}
|
|
reg->write(_state.reg(Iss::Register::get(v)));
|
|
}
|
|
_state.ip += sizeof(Genode::uint32_t);
|
|
return true;
|
|
}
|
|
|
|
|
|
void Cpu_base::_handle_wfi()
|
|
{
|
|
_state.ip += sizeof(Genode::uint32_t);
|
|
|
|
if (_state.esr_el2 & 1) return; /* WFE */
|
|
|
|
_active = false;
|
|
_timer.schedule_timeout();
|
|
}
|
|
|
|
|
|
void Cpu_base::_handle_sync()
|
|
{
|
|
/* check device number*/
|
|
switch (Esr::Ec::get(_state.esr_el2)) {
|
|
case Esr::Ec::HVC:
|
|
_handle_hyper_call();
|
|
break;
|
|
case Esr::Ec::MRC_MCR: [[fallthrough]];
|
|
case Esr::Ec::MRS_MSR:
|
|
_handle_sys_reg();
|
|
break;
|
|
case Esr::Ec::DA:
|
|
_handle_data_abort();
|
|
break;
|
|
case Esr::Ec::WFI:
|
|
_handle_wfi();
|
|
return;
|
|
case Esr::Ec::BRK:
|
|
_handle_brk();
|
|
return;
|
|
default:
|
|
throw Exception("Unknown trap: ",
|
|
Esr::Ec::get(_state.esr_el2));
|
|
};
|
|
}
|
|
|
|
|
|
void Cpu_base::_handle_irq()
|
|
{
|
|
switch (_state.irqs.last_irq) {
|
|
case VTIMER_IRQ:
|
|
_timer.handle_irq();
|
|
break;
|
|
default:
|
|
_gic.handle_irq();
|
|
};
|
|
}
|
|
|
|
|
|
void Cpu_base::_handle_hyper_call()
|
|
{
|
|
switch(_state.reg(0)) {
|
|
case Psci::PSCI_VERSION:
|
|
_state.reg(0, Psci::VERSION);
|
|
return;
|
|
case Psci::MIGRATE_INFO_TYPE:
|
|
_state.reg(0, Psci::NOT_SUPPORTED);
|
|
return;
|
|
case Psci::PSCI_FEATURES:
|
|
_state.reg(0, Psci::NOT_SUPPORTED);
|
|
return;
|
|
case Psci::CPU_ON:
|
|
_vm.cpu((unsigned)_state.reg(1), [&] (Cpu & cpu) {
|
|
cpu.state().ip = _state.reg(2);
|
|
cpu.state().reg(0, _state.reg(3));
|
|
cpu.run();
|
|
});
|
|
_state.reg(0, Psci::SUCCESS);
|
|
return;
|
|
default:
|
|
Genode::warning("unknown hypercall! ", cpu_id());
|
|
dump();
|
|
};
|
|
}
|
|
|
|
|
|
void Cpu_base::_handle_data_abort()
|
|
{
|
|
_vm.bus().handle_memory_access(*static_cast<Cpu*>(this));
|
|
_state.ip += sizeof(Genode::uint32_t);
|
|
}
|
|
|
|
|
|
void Cpu_base::_update_state()
|
|
{
|
|
if (!_gic.pending_irq()) return;
|
|
|
|
_active = true;
|
|
_timer.cancel_timeout();
|
|
}
|
|
|
|
|
|
unsigned Cpu_base::cpu_id() const { return _vcpu_id.id; }
|
|
void Cpu_base::run() { _vm_session.run(_vcpu_id); }
|
|
void Cpu_base::pause() { _vm_session.pause(_vcpu_id); }
|
|
bool Cpu_base::active() const { return _active; }
|
|
Cpu_base::State & Cpu_base::state() const { return _state; }
|
|
Gic::Gicd_banked & Cpu_base::gic() { return _gic; }
|
|
|
|
|
|
void Cpu_base::recall()
|
|
{
|
|
Genode::Signal_transmitter(_vm_handler).submit();
|
|
};
|
|
|
|
|
|
Cpu_base::Cpu_base(Vm & vm,
|
|
Genode::Vm_connection & vm_session,
|
|
Mmio_bus & bus,
|
|
Gic & gic,
|
|
Genode::Env & env,
|
|
Genode::Heap & heap,
|
|
Genode::Entrypoint & ep)
|
|
: _vm(vm),
|
|
_vm_session(vm_session),
|
|
_heap(heap),
|
|
_vm_handler(*this, ep, *this, &Cpu_base::_handle_nothing),
|
|
_vcpu_id(_vm_session.with_upgrade([&]() {
|
|
return _vm_session.create_vcpu(heap, env, _vm_handler);
|
|
})),
|
|
_state(*((State*)env.rm().attach(_vm_session.cpu_state(_vcpu_id)))),
|
|
_gic(*this, gic, bus),
|
|
_timer(env, ep, _gic.irq(VTIMER_IRQ), *this) { }
|