mirror of
https://github.com/genodelabs/genode.git
synced 2025-04-08 20:05:54 +00:00
hw: unify mmu fault handling
Recent work related to issue 1723 showed that there is potential to get rid of code duplication in MMU fault handling especially with regard to ARM cpus.
This commit is contained in:
parent
7f5bec5c0d
commit
be4e34b6b5
@ -38,6 +38,20 @@ extern "C" void _core_start(void);
|
||||
using namespace Kernel;
|
||||
|
||||
|
||||
void Thread_fault::print(Genode::Output &out) const
|
||||
{
|
||||
Genode::print(out, "ip=", Genode::Hex(ip));
|
||||
Genode::print(out, " fault-addr=", Genode::Hex(addr));
|
||||
Genode::print(out, " type=");
|
||||
switch (type) {
|
||||
case WRITE: Genode::print(out, "write-fault"); return;
|
||||
case EXEC: Genode::print(out, "exec-fault"); return;
|
||||
case PAGE_MISSING: Genode::print(out, "no-page"); return;
|
||||
case UNKNOWN: Genode::print(out, "unknown"); return;
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
void Thread::_signal_context_kill_pending()
|
||||
{
|
||||
assert(_state == ACTIVE);
|
||||
@ -619,11 +633,29 @@ void Thread::_call()
|
||||
}
|
||||
|
||||
|
||||
void Thread::_mmu_exception()
|
||||
{
|
||||
_become_inactive(AWAITS_RESTART);
|
||||
Cpu::mmu_fault(*regs, _fault);
|
||||
_fault.ip = regs->ip;
|
||||
|
||||
if (_fault.type == Thread_fault::UNKNOWN) {
|
||||
Genode::error(*this, " raised unhandled MMU fault ", _fault);
|
||||
return;
|
||||
}
|
||||
|
||||
if (_core)
|
||||
Genode::error(*this, " raised a fault, which should never happen ",
|
||||
_fault);
|
||||
|
||||
if (_pager) _pager->submit(1);
|
||||
}
|
||||
|
||||
|
||||
Thread::Thread(unsigned const priority, unsigned const quota,
|
||||
char const * const label, bool core)
|
||||
:
|
||||
Cpu_job(priority, quota), _fault_pd(0), _fault_addr(0),
|
||||
_fault_writes(0), _state(AWAITS_START),
|
||||
Cpu_job(priority, quota), _state(AWAITS_START),
|
||||
_signal_receiver(0), _label(label), _core(core), regs(core) { }
|
||||
|
||||
|
||||
|
@ -24,10 +24,24 @@
|
||||
|
||||
namespace Kernel
|
||||
{
|
||||
struct Thread_fault;
|
||||
class Thread;
|
||||
class Core_thread;
|
||||
}
|
||||
|
||||
|
||||
struct Kernel::Thread_fault
|
||||
{
|
||||
enum Type { WRITE, EXEC, PAGE_MISSING, UNKNOWN };
|
||||
|
||||
addr_t ip = 0;
|
||||
addr_t addr = 0;
|
||||
Type type = UNKNOWN;
|
||||
|
||||
void print(Genode::Output &out) const;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Kernel back-end for userland execution-contexts
|
||||
*/
|
||||
@ -53,9 +67,7 @@ class Kernel::Thread
|
||||
};
|
||||
|
||||
Signal_context * _pager = nullptr;
|
||||
addr_t _fault_pd;
|
||||
addr_t _fault_addr;
|
||||
addr_t _fault_writes;
|
||||
Thread_fault _fault;
|
||||
State _state;
|
||||
Signal_receiver * _signal_receiver;
|
||||
char const * const _label;
|
||||
@ -63,7 +75,6 @@ class Kernel::Thread
|
||||
bool _paused = false;
|
||||
bool _cancel_next_await_signal = false;
|
||||
bool const _core = false;
|
||||
bool _fault_exec = false;
|
||||
|
||||
/**
|
||||
* Notice that another thread yielded the CPU to this thread
|
||||
@ -322,11 +333,8 @@ class Kernel::Thread
|
||||
** Accessors **
|
||||
***************/
|
||||
|
||||
char const * label() const { return _label; }
|
||||
addr_t fault_pd() const { return _fault_pd; }
|
||||
addr_t fault_addr() const { return _fault_addr; }
|
||||
addr_t fault_writes() const { return _fault_writes; }
|
||||
bool fault_exec() const { return _fault_exec; }
|
||||
char const * label() const { return _label; }
|
||||
Thread_fault fault() const { return _fault; }
|
||||
};
|
||||
|
||||
|
||||
|
@ -34,9 +34,11 @@ addr_t Ipc_pager::fault_ip() const { return _fault.ip; }
|
||||
|
||||
addr_t Ipc_pager::fault_addr() const { return _fault.addr; }
|
||||
|
||||
bool Ipc_pager::write_fault() const { return _fault.writes; }
|
||||
bool Ipc_pager::write_fault() const {
|
||||
return _fault.type == Kernel::Thread_fault::WRITE; }
|
||||
|
||||
bool Ipc_pager::exec_fault() const { return _fault.exec; }
|
||||
bool Ipc_pager::exec_fault() const {
|
||||
return _fault.type == Kernel::Thread_fault::EXEC; }
|
||||
|
||||
void Ipc_pager::set_reply_mapping(Mapping m) { _mapping = m; }
|
||||
|
||||
@ -67,9 +69,7 @@ void Pager_object::unresolved_page_fault_occurred()
|
||||
Platform_thread * const pt = (Platform_thread *)badge();
|
||||
if (pt && pt->pd())
|
||||
warning("page fault, pager_object: pd='", pt->pd()->label(),
|
||||
"' thread='", pt->label(),
|
||||
"' ip=", Hex(pt->kernel_object()->regs->ip),
|
||||
" pf-addr=", Hex(pt->kernel_object()->fault_addr()));
|
||||
"' thread='", pt->label(), " ", pt->kernel_object()->fault());
|
||||
}
|
||||
|
||||
void Pager_object::print(Output &out) const
|
||||
|
@ -80,19 +80,8 @@ class Genode::Ipc_pager
|
||||
{
|
||||
protected:
|
||||
|
||||
/**
|
||||
* Page-fault data that is read from the faulters thread registers
|
||||
*/
|
||||
struct Fault_thread_regs
|
||||
{
|
||||
addr_t ip;
|
||||
addr_t addr;
|
||||
addr_t writes;
|
||||
addr_t exec;
|
||||
addr_t signal;
|
||||
} _fault;
|
||||
|
||||
Mapping _mapping;
|
||||
Kernel::Thread_fault _fault;
|
||||
Mapping _mapping;
|
||||
|
||||
public:
|
||||
|
||||
|
@ -50,10 +50,7 @@ void Pager_entrypoint::entry()
|
||||
continue;
|
||||
}
|
||||
|
||||
_fault.ip = pt->kernel_object()->regs->ip;
|
||||
_fault.addr = pt->kernel_object()->fault_addr();
|
||||
_fault.writes = pt->kernel_object()->fault_writes();
|
||||
_fault.exec = pt->kernel_object()->fault_exec();
|
||||
_fault = pt->kernel_object()->fault();
|
||||
|
||||
/* try to resolve fault directly via local region managers */
|
||||
if (po->pager(*this)) continue;
|
||||
|
@ -15,9 +15,12 @@
|
||||
#include <base/internal/unmanaged_singleton.h>
|
||||
|
||||
#include <kernel/cpu.h>
|
||||
#include <kernel/thread.h>
|
||||
#include <spec/arm/cpu_support.h>
|
||||
|
||||
Genode::Arm_cpu::Context::Context(bool privileged)
|
||||
using namespace Genode;
|
||||
|
||||
Arm_cpu::Context::Context(bool privileged)
|
||||
{
|
||||
using Psr = Arm_cpu::Psr;
|
||||
|
||||
@ -31,14 +34,14 @@ Genode::Arm_cpu::Context::Context(bool privileged)
|
||||
}
|
||||
|
||||
|
||||
using Asid_allocator = Genode::Bit_allocator<256>;
|
||||
using Asid_allocator = Bit_allocator<256>;
|
||||
|
||||
static Asid_allocator &alloc() {
|
||||
return *unmanaged_singleton<Asid_allocator>(); }
|
||||
|
||||
|
||||
Genode::Arm_cpu::Mmu_context::Mmu_context(addr_t table)
|
||||
: cidr((Genode::uint8_t)alloc().alloc()), ttbr0(Ttbr0::init(table)) { }
|
||||
Arm_cpu::Mmu_context::Mmu_context(addr_t table)
|
||||
: cidr((uint8_t)alloc().alloc()), ttbr0(Ttbr0::init(table)) { }
|
||||
|
||||
|
||||
Genode::Arm_cpu::Mmu_context::~Mmu_context()
|
||||
@ -47,3 +50,36 @@ Genode::Arm_cpu::Mmu_context::~Mmu_context()
|
||||
Cpu::Tlbiasid::write(id());
|
||||
alloc().free(id());
|
||||
}
|
||||
|
||||
|
||||
using Thread_fault = Kernel::Thread_fault;
|
||||
|
||||
void Arm_cpu::mmu_fault(Context & c, Thread_fault & fault)
|
||||
{
|
||||
bool prefetch = c.cpu_exception == Context::PREFETCH_ABORT;
|
||||
fault.addr = prefetch ? Ifar::read() : Dfar::read();
|
||||
Fsr::access_t fsr = prefetch ? Ifsr::read() : Dfsr::read();
|
||||
|
||||
if (!prefetch && Dfsr::Wnr::get(fsr)) {
|
||||
fault.type = Thread_fault::WRITE;
|
||||
return;
|
||||
}
|
||||
|
||||
Cpu::mmu_fault_status(Fsr::Fs::get(fsr), fault);
|
||||
}
|
||||
|
||||
|
||||
void Arm_cpu::mmu_fault_status(Fsr::access_t fsr, Thread_fault & fault)
|
||||
{
|
||||
enum {
|
||||
FAULT_MASK = 0b11101,
|
||||
TRANSLATION = 0b00101,
|
||||
PERMISSION = 0b01101,
|
||||
};
|
||||
|
||||
switch(fsr & FAULT_MASK) {
|
||||
case TRANSLATION: fault.type = Thread_fault::PAGE_MISSING; return;
|
||||
case PERMISSION: fault.type = Thread_fault::EXEC; return;
|
||||
default: fault.type = Thread_fault::UNKNOWN;
|
||||
};
|
||||
}
|
||||
|
@ -28,6 +28,8 @@
|
||||
#include <board.h>
|
||||
#include <util.h>
|
||||
|
||||
namespace Kernel { struct Thread_fault; }
|
||||
|
||||
namespace Genode {
|
||||
using sizet_arithm_t = Genode::uint64_t;
|
||||
struct Arm_cpu;
|
||||
@ -137,48 +139,10 @@ struct Genode::Arm_cpu : public Hw::Arm_cpu
|
||||
}
|
||||
}
|
||||
|
||||
static bool in_fault(Context & c, addr_t & va, addr_t & w, bool & p)
|
||||
{
|
||||
/* translation fault on section */
|
||||
static constexpr Fsr::access_t section = 5;
|
||||
/* translation fault on page */
|
||||
static constexpr Fsr::access_t page = 7;
|
||||
/* permission fault on page */
|
||||
static constexpr Fsr::access_t permission = 0xf;
|
||||
static void mmu_fault(Context & c, Kernel::Thread_fault & fault);
|
||||
static void mmu_fault_status(Fsr::access_t fsr,
|
||||
Kernel::Thread_fault & fault);
|
||||
|
||||
if (c.cpu_exception == Context::PREFETCH_ABORT) {
|
||||
/* check if fault was caused by a translation miss */
|
||||
Ifsr::access_t const fs = Fsr::Fs::get(Ifsr::read());
|
||||
|
||||
if (fs == permission) {
|
||||
w = 0;
|
||||
va = Ifar::read();
|
||||
p = true;
|
||||
return true;
|
||||
}
|
||||
|
||||
if (fs != section && fs != page)
|
||||
return false;
|
||||
|
||||
/* fetch fault data */
|
||||
w = 0;
|
||||
va = Ifar::read();
|
||||
p = false;
|
||||
return true;
|
||||
} else {
|
||||
/* check if fault is of known type */
|
||||
Dfsr::access_t const fs = Fsr::Fs::get(Dfsr::read());
|
||||
if (fs != permission && fs != section && fs != page)
|
||||
return false;
|
||||
|
||||
/* fetch fault data */
|
||||
Dfsr::access_t const dfsr = Dfsr::read();
|
||||
w = Dfsr::Wnr::get(dfsr);
|
||||
va = Dfar::read();
|
||||
p = false;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
/*************
|
||||
** Dummies **
|
||||
|
@ -50,41 +50,6 @@ void Thread::exception(Cpu & cpu)
|
||||
}
|
||||
|
||||
|
||||
void Thread::_mmu_exception()
|
||||
{
|
||||
_become_inactive(AWAITS_RESTART);
|
||||
if (Cpu::in_fault(*regs, _fault_addr, _fault_writes, _fault_exec)) {
|
||||
_fault_pd = (addr_t)_pd->platform_pd();
|
||||
|
||||
/*
|
||||
* Core should never raise a page-fault. If this happens, print out an
|
||||
* error message with debug information.
|
||||
*/
|
||||
if (_core)
|
||||
Genode::error("page fault in core thread (", label(), "): "
|
||||
"ip=", Genode::Hex(regs->ip), " fault=", Genode::Hex(_fault_addr));
|
||||
|
||||
if (_pager) _pager->submit(1);
|
||||
return;
|
||||
}
|
||||
|
||||
char const *abort_type = "unknown";
|
||||
if (regs->cpu_exception == Cpu::Context::DATA_ABORT)
|
||||
abort_type = "data";
|
||||
if (regs->cpu_exception == Cpu::Context::PREFETCH_ABORT)
|
||||
abort_type = "prefetch";
|
||||
|
||||
Genode::error(*this, ": raised unhandled ",
|
||||
abort_type, " abort ",
|
||||
"DFSR=", Genode::Hex(Cpu::Dfsr::read()), " "
|
||||
"ISFR=", Genode::Hex(Cpu::Ifsr::read()), " "
|
||||
"DFAR=", Genode::Hex(Cpu::Dfar::read()), " "
|
||||
"ip=", Genode::Hex(regs->ip), " "
|
||||
"sp=", Genode::Hex(regs->sp), " "
|
||||
"exception=", Genode::Hex(regs->cpu_exception));
|
||||
}
|
||||
|
||||
|
||||
void Kernel::Thread::_call_update_data_region()
|
||||
{
|
||||
Cpu * const cpu = cpu_pool()->cpu(Cpu::executing_id());
|
||||
|
@ -14,6 +14,7 @@
|
||||
#include <util/bit_allocator.h>
|
||||
#include <base/internal/unmanaged_singleton.h>
|
||||
|
||||
#include <kernel/thread.h>
|
||||
#include <spec/cortex_a15/cpu.h>
|
||||
|
||||
using Asid_allocator = Genode::Bit_allocator<256>;
|
||||
@ -33,3 +34,22 @@ Genode::Cpu::Mmu_context::~Mmu_context()
|
||||
Cpu::Tlbiasid::write(id());
|
||||
alloc().free(id());
|
||||
}
|
||||
|
||||
|
||||
void Genode::Cpu::mmu_fault_status(Genode::Cpu::Fsr::access_t fsr,
|
||||
Kernel::Thread_fault & fault)
|
||||
{
|
||||
enum {
|
||||
FAULT_MASK = 0b111100,
|
||||
TRANSLATION = 0b000100,
|
||||
PERMISSION = 0b001100,
|
||||
};
|
||||
|
||||
using Fault = Kernel::Thread_fault;
|
||||
|
||||
switch(fsr & FAULT_MASK) {
|
||||
case TRANSLATION: fault.type = Fault::PAGE_MISSING; return;
|
||||
case PERMISSION: fault.type = Fault::EXEC; return;
|
||||
default: fault.type = Fault::UNKNOWN;
|
||||
};
|
||||
};
|
||||
|
@ -105,61 +105,8 @@ class Genode::Cpu : public Arm_v7_cpu
|
||||
Genode::uint8_t id() const { return Ttbr_64bit::Asid::get(ttbr0); }
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Return if the context is in a page fault due to translation miss
|
||||
*
|
||||
* \param va holds the virtual fault-address if call returns 1
|
||||
* \param w holds wether it's a write fault if call returns 1
|
||||
* \param p holds whether it's a permission fault if call returns 1
|
||||
*/
|
||||
static bool in_fault(Context & c, addr_t & va, addr_t & w, bool & p)
|
||||
{
|
||||
/* permission fault on page, 2nd level */
|
||||
static constexpr Fsr::access_t permission = 0b1111;
|
||||
|
||||
switch (c.cpu_exception) {
|
||||
|
||||
case Context::PREFETCH_ABORT:
|
||||
{
|
||||
/* check if fault was caused by a translation miss */
|
||||
Fsr::access_t const fs = Fsr::Fs::get(Ifsr::read());
|
||||
if (fs == permission) {
|
||||
w = 0;
|
||||
va = Ifar::read();
|
||||
p = true;
|
||||
return true;
|
||||
}
|
||||
|
||||
if ((fs & 0b11100) != 0b100) return false;
|
||||
|
||||
/* fetch fault data */
|
||||
w = 0;
|
||||
va = Ifar::read();
|
||||
p = false;
|
||||
return true;
|
||||
}
|
||||
|
||||
case Context::DATA_ABORT:
|
||||
{
|
||||
/* check if fault was caused by translation miss */
|
||||
Fsr::access_t const fs = Fsr::Fs::get(Dfsr::read());
|
||||
if ((fs != permission) && (fs & 0b11100) != 0b100)
|
||||
return false;
|
||||
|
||||
/* fetch fault data */
|
||||
Dfsr::access_t const dfsr = Dfsr::read();
|
||||
w = Dfsr::Wnr::get(dfsr);
|
||||
va = Dfar::read();
|
||||
p = false;
|
||||
return true;
|
||||
}
|
||||
|
||||
default:
|
||||
return false;
|
||||
};
|
||||
};
|
||||
|
||||
static void mmu_fault_status(Fsr::access_t fsr,
|
||||
Kernel::Thread_fault & fault);
|
||||
|
||||
/**
|
||||
* Return kernel name of the executing CPU
|
||||
|
@ -67,3 +67,10 @@ void Genode::Cpu::switch_to(Mmu_context & context)
|
||||
if (user /*&& sptbr != context.sptbr*/)
|
||||
Sptbr::write(context.sptbr);
|
||||
}
|
||||
|
||||
|
||||
void Genode::Cpu::mmu_fault(Context & c, Kernel::Thread_fault & f)
|
||||
{
|
||||
f.addr = Genode::Cpu::Sbadaddr::read();
|
||||
f.type = Kernel::Thread_fault::PAGE_MISSING;
|
||||
}
|
||||
|
@ -24,6 +24,8 @@
|
||||
#include <kernel/interface.h>
|
||||
#include <hw/spec/riscv/cpu.h>
|
||||
|
||||
namespace Kernel { struct Thread_fault; }
|
||||
|
||||
namespace Genode
|
||||
{
|
||||
/**
|
||||
@ -76,6 +78,7 @@ class Genode::Cpu : public Hw::Riscv_cpu
|
||||
static void invalidate_tlb_by_pid(unsigned const pid) { sfence(); }
|
||||
|
||||
void switch_to(Mmu_context & context);
|
||||
static void mmu_fault(Context & c, Kernel::Thread_fault & f);
|
||||
|
||||
static unsigned executing_id() { return 0; }
|
||||
static unsigned primary_id() { return 0; }
|
||||
|
@ -45,16 +45,6 @@ void Thread::exception(Cpu&)
|
||||
}
|
||||
|
||||
|
||||
void Thread::_mmu_exception()
|
||||
{
|
||||
_become_inactive(AWAITS_RESTART);
|
||||
_fault_pd = (addr_t)_pd->platform_pd();
|
||||
_fault_addr = Genode::Cpu::Sbadaddr::read();
|
||||
|
||||
if (_pager) _pager->submit(1);
|
||||
}
|
||||
|
||||
|
||||
void Thread::_call_update_pd()
|
||||
{
|
||||
Genode::Cpu::sfence();
|
||||
|
@ -13,6 +13,7 @@
|
||||
|
||||
/* core includes */
|
||||
#include <cpu.h>
|
||||
#include <kernel/thread.h>
|
||||
#include <kernel/pd.h>
|
||||
|
||||
extern int __tss;
|
||||
@ -56,3 +57,31 @@ void Genode::Cpu::Gdt::init()
|
||||
uint64_t const base = start;
|
||||
asm volatile ("lgdt %0" :: "m" (Pseudo_descriptor(limit, base)));
|
||||
}
|
||||
|
||||
|
||||
void Genode::Cpu::mmu_fault(Context & regs, Kernel::Thread_fault & fault)
|
||||
{
|
||||
using Fault = Kernel::Thread_fault::Type;
|
||||
|
||||
/*
|
||||
* Intel manual: 6.15 EXCEPTION AND INTERRUPT REFERENCE
|
||||
* Interrupt 14—Page-Fault Exception (#PF)
|
||||
*/
|
||||
enum {
|
||||
ERR_I = 1UL << 4,
|
||||
ERR_R = 1UL << 3,
|
||||
ERR_U = 1UL << 2,
|
||||
ERR_W = 1UL << 1,
|
||||
ERR_P = 1UL << 0,
|
||||
};
|
||||
|
||||
auto fault_lambda = [] (addr_t err) {
|
||||
if ((err & ERR_P) && (err & ERR_W)) return Fault::WRITE;
|
||||
if ((err & ERR_P) && (err & ERR_I)) return Fault::EXEC;
|
||||
if (err & ERR_P) return Fault::UNKNOWN;
|
||||
else return Fault::PAGE_MISSING;
|
||||
};
|
||||
|
||||
fault.addr = Genode::Cpu::Cr2::read();
|
||||
fault.type = fault_lambda(regs.errcode);
|
||||
}
|
||||
|
@ -29,6 +29,8 @@
|
||||
/* core includes */
|
||||
#include <fpu.h>
|
||||
|
||||
namespace Kernel { struct Thread_fault; }
|
||||
|
||||
namespace Genode {
|
||||
class Cpu;
|
||||
using sizet_arithm_t = __uint128_t;
|
||||
@ -124,6 +126,8 @@ class Genode::Cpu
|
||||
* \param context next CPU context
|
||||
*/
|
||||
inline void switch_to(Context & context, Mmu_context &);
|
||||
|
||||
static void mmu_fault(Context & regs, Kernel::Thread_fault & fault);
|
||||
};
|
||||
|
||||
|
||||
|
@ -25,40 +25,6 @@ void Kernel::Thread::_call_update_data_region() { }
|
||||
void Kernel::Thread::_call_update_instr_region() { }
|
||||
|
||||
|
||||
/*
|
||||
* Intel manual: 6.15 EXCEPTION AND INTERRUPT REFERENCE
|
||||
* Interrupt 14—Page-Fault Exception (#PF)
|
||||
*/
|
||||
enum {
|
||||
ERR_I = 1UL << 4,
|
||||
ERR_R = 1UL << 3,
|
||||
ERR_U = 1UL << 2,
|
||||
ERR_W = 1UL << 1,
|
||||
ERR_P = 1UL << 0,
|
||||
};
|
||||
|
||||
|
||||
void Kernel::Thread::_mmu_exception()
|
||||
{
|
||||
_become_inactive(AWAITS_RESTART);
|
||||
_fault_pd = (addr_t)_pd->platform_pd();
|
||||
_fault_addr = Genode::Cpu::Cr2::read();
|
||||
_fault_writes = (regs->errcode & ERR_P) && (regs->errcode & ERR_W);
|
||||
_fault_exec = (regs->errcode & ERR_P) && (regs->errcode & ERR_I);
|
||||
|
||||
/*
|
||||
* Core should never raise a page-fault. If this happens, print out an
|
||||
* error message with debug information.
|
||||
*/
|
||||
if (_pd == Kernel::core_pd())
|
||||
Genode::error("page fault in core thread (", label(), "): "
|
||||
"ip=", Genode::Hex(regs->ip), " fault=", Genode::Hex(_fault_addr));
|
||||
|
||||
if (_pager) _pager->submit(1);
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
void Kernel::Thread::_call_update_pd() { }
|
||||
|
||||
|
||||
|
Loading…
x
Reference in New Issue
Block a user