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:
Stefan Kalkowski 2017-10-25 18:57:58 +02:00 committed by Christian Helmuth
parent 7f5bec5c0d
commit be4e34b6b5
16 changed files with 169 additions and 212 deletions

View File

@ -38,6 +38,20 @@ extern "C" void _core_start(void);
using namespace Kernel; 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() void Thread::_signal_context_kill_pending()
{ {
assert(_state == ACTIVE); 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, Thread::Thread(unsigned const priority, unsigned const quota,
char const * const label, bool core) char const * const label, bool core)
: :
Cpu_job(priority, quota), _fault_pd(0), _fault_addr(0), Cpu_job(priority, quota), _state(AWAITS_START),
_fault_writes(0), _state(AWAITS_START),
_signal_receiver(0), _label(label), _core(core), regs(core) { } _signal_receiver(0), _label(label), _core(core), regs(core) { }

View File

@ -24,10 +24,24 @@
namespace Kernel namespace Kernel
{ {
struct Thread_fault;
class Thread; class Thread;
class Core_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 * Kernel back-end for userland execution-contexts
*/ */
@ -53,9 +67,7 @@ class Kernel::Thread
}; };
Signal_context * _pager = nullptr; Signal_context * _pager = nullptr;
addr_t _fault_pd; Thread_fault _fault;
addr_t _fault_addr;
addr_t _fault_writes;
State _state; State _state;
Signal_receiver * _signal_receiver; Signal_receiver * _signal_receiver;
char const * const _label; char const * const _label;
@ -63,7 +75,6 @@ class Kernel::Thread
bool _paused = false; bool _paused = false;
bool _cancel_next_await_signal = false; bool _cancel_next_await_signal = false;
bool const _core = false; bool const _core = false;
bool _fault_exec = false;
/** /**
* Notice that another thread yielded the CPU to this thread * Notice that another thread yielded the CPU to this thread
@ -322,11 +333,8 @@ class Kernel::Thread
** Accessors ** ** Accessors **
***************/ ***************/
char const * label() const { return _label; } char const * label() const { return _label; }
addr_t fault_pd() const { return _fault_pd; } Thread_fault fault() const { return _fault; }
addr_t fault_addr() const { return _fault_addr; }
addr_t fault_writes() const { return _fault_writes; }
bool fault_exec() const { return _fault_exec; }
}; };

View File

@ -34,9 +34,11 @@ addr_t Ipc_pager::fault_ip() const { return _fault.ip; }
addr_t Ipc_pager::fault_addr() const { return _fault.addr; } 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; } 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(); Platform_thread * const pt = (Platform_thread *)badge();
if (pt && pt->pd()) if (pt && pt->pd())
warning("page fault, pager_object: pd='", pt->pd()->label(), warning("page fault, pager_object: pd='", pt->pd()->label(),
"' thread='", pt->label(), "' thread='", pt->label(), " ", pt->kernel_object()->fault());
"' ip=", Hex(pt->kernel_object()->regs->ip),
" pf-addr=", Hex(pt->kernel_object()->fault_addr()));
} }
void Pager_object::print(Output &out) const void Pager_object::print(Output &out) const

View File

@ -80,19 +80,8 @@ class Genode::Ipc_pager
{ {
protected: protected:
/** Kernel::Thread_fault _fault;
* Page-fault data that is read from the faulters thread registers Mapping _mapping;
*/
struct Fault_thread_regs
{
addr_t ip;
addr_t addr;
addr_t writes;
addr_t exec;
addr_t signal;
} _fault;
Mapping _mapping;
public: public:

View File

@ -50,10 +50,7 @@ void Pager_entrypoint::entry()
continue; continue;
} }
_fault.ip = pt->kernel_object()->regs->ip; _fault = pt->kernel_object()->fault();
_fault.addr = pt->kernel_object()->fault_addr();
_fault.writes = pt->kernel_object()->fault_writes();
_fault.exec = pt->kernel_object()->fault_exec();
/* try to resolve fault directly via local region managers */ /* try to resolve fault directly via local region managers */
if (po->pager(*this)) continue; if (po->pager(*this)) continue;

View File

@ -15,9 +15,12 @@
#include <base/internal/unmanaged_singleton.h> #include <base/internal/unmanaged_singleton.h>
#include <kernel/cpu.h> #include <kernel/cpu.h>
#include <kernel/thread.h>
#include <spec/arm/cpu_support.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; 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() { static Asid_allocator &alloc() {
return *unmanaged_singleton<Asid_allocator>(); } return *unmanaged_singleton<Asid_allocator>(); }
Genode::Arm_cpu::Mmu_context::Mmu_context(addr_t table) Arm_cpu::Mmu_context::Mmu_context(addr_t table)
: cidr((Genode::uint8_t)alloc().alloc()), ttbr0(Ttbr0::init(table)) { } : cidr((uint8_t)alloc().alloc()), ttbr0(Ttbr0::init(table)) { }
Genode::Arm_cpu::Mmu_context::~Mmu_context() Genode::Arm_cpu::Mmu_context::~Mmu_context()
@ -47,3 +50,36 @@ Genode::Arm_cpu::Mmu_context::~Mmu_context()
Cpu::Tlbiasid::write(id()); Cpu::Tlbiasid::write(id());
alloc().free(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;
};
}

View File

@ -28,6 +28,8 @@
#include <board.h> #include <board.h>
#include <util.h> #include <util.h>
namespace Kernel { struct Thread_fault; }
namespace Genode { namespace Genode {
using sizet_arithm_t = Genode::uint64_t; using sizet_arithm_t = Genode::uint64_t;
struct Arm_cpu; 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) static void mmu_fault(Context & c, Kernel::Thread_fault & fault);
{ static void mmu_fault_status(Fsr::access_t fsr,
/* translation fault on section */ Kernel::Thread_fault & fault);
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;
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 ** ** Dummies **

View File

@ -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() void Kernel::Thread::_call_update_data_region()
{ {
Cpu * const cpu = cpu_pool()->cpu(Cpu::executing_id()); Cpu * const cpu = cpu_pool()->cpu(Cpu::executing_id());

View File

@ -14,6 +14,7 @@
#include <util/bit_allocator.h> #include <util/bit_allocator.h>
#include <base/internal/unmanaged_singleton.h> #include <base/internal/unmanaged_singleton.h>
#include <kernel/thread.h>
#include <spec/cortex_a15/cpu.h> #include <spec/cortex_a15/cpu.h>
using Asid_allocator = Genode::Bit_allocator<256>; using Asid_allocator = Genode::Bit_allocator<256>;
@ -33,3 +34,22 @@ Genode::Cpu::Mmu_context::~Mmu_context()
Cpu::Tlbiasid::write(id()); Cpu::Tlbiasid::write(id());
alloc().free(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;
};
};

View File

@ -105,61 +105,8 @@ class Genode::Cpu : public Arm_v7_cpu
Genode::uint8_t id() const { return Ttbr_64bit::Asid::get(ttbr0); } Genode::uint8_t id() const { return Ttbr_64bit::Asid::get(ttbr0); }
}; };
static void mmu_fault_status(Fsr::access_t fsr,
/** Kernel::Thread_fault & fault);
* 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;
};
};
/** /**
* Return kernel name of the executing CPU * Return kernel name of the executing CPU

View File

@ -67,3 +67,10 @@ void Genode::Cpu::switch_to(Mmu_context & context)
if (user /*&& sptbr != context.sptbr*/) if (user /*&& sptbr != context.sptbr*/)
Sptbr::write(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;
}

View File

@ -24,6 +24,8 @@
#include <kernel/interface.h> #include <kernel/interface.h>
#include <hw/spec/riscv/cpu.h> #include <hw/spec/riscv/cpu.h>
namespace Kernel { struct Thread_fault; }
namespace Genode namespace Genode
{ {
/** /**
@ -76,6 +78,7 @@ class Genode::Cpu : public Hw::Riscv_cpu
static void invalidate_tlb_by_pid(unsigned const pid) { sfence(); } static void invalidate_tlb_by_pid(unsigned const pid) { sfence(); }
void switch_to(Mmu_context & context); void switch_to(Mmu_context & context);
static void mmu_fault(Context & c, Kernel::Thread_fault & f);
static unsigned executing_id() { return 0; } static unsigned executing_id() { return 0; }
static unsigned primary_id() { return 0; } static unsigned primary_id() { return 0; }

View File

@ -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() void Thread::_call_update_pd()
{ {
Genode::Cpu::sfence(); Genode::Cpu::sfence();

View File

@ -13,6 +13,7 @@
/* core includes */ /* core includes */
#include <cpu.h> #include <cpu.h>
#include <kernel/thread.h>
#include <kernel/pd.h> #include <kernel/pd.h>
extern int __tss; extern int __tss;
@ -56,3 +57,31 @@ void Genode::Cpu::Gdt::init()
uint64_t const base = start; uint64_t const base = start;
asm volatile ("lgdt %0" :: "m" (Pseudo_descriptor(limit, base))); 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 14Page-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);
}

View File

@ -29,6 +29,8 @@
/* core includes */ /* core includes */
#include <fpu.h> #include <fpu.h>
namespace Kernel { struct Thread_fault; }
namespace Genode { namespace Genode {
class Cpu; class Cpu;
using sizet_arithm_t = __uint128_t; using sizet_arithm_t = __uint128_t;
@ -124,6 +126,8 @@ class Genode::Cpu
* \param context next CPU context * \param context next CPU context
*/ */
inline void switch_to(Context & context, Mmu_context &); inline void switch_to(Context & context, Mmu_context &);
static void mmu_fault(Context & regs, Kernel::Thread_fault & fault);
}; };

View File

@ -25,40 +25,6 @@ void Kernel::Thread::_call_update_data_region() { }
void Kernel::Thread::_call_update_instr_region() { } void Kernel::Thread::_call_update_instr_region() { }
/*
* Intel manual: 6.15 EXCEPTION AND INTERRUPT REFERENCE
* Interrupt 14Page-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() { } void Kernel::Thread::_call_update_pd() { }