NOVA: implement pause using recall kernel feature

This commit is contained in:
Alexander Boettcher 2012-08-24 10:25:24 +02:00 committed by Norman Feske
parent 841a1fd579
commit 197a48a26c
13 changed files with 318 additions and 60 deletions

View File

@ -44,14 +44,31 @@ namespace Genode {
*/
Signal_context_capability _exception_sigh;
addr_t _pt_cleanup; /* portal selector for object cleanup/destruction */
/**
* Portal selector for object cleanup/destruction
*/
addr_t _pt_cleanup;
/**
* Semaphore selector to synchronize pause/state/resume operations
*/
addr_t _sm_state_notify;
addr_t _initial_esp;
addr_t _initial_eip;
struct {
struct Thread_state thread;
bool valid;
bool dead;
} _state;
void _copy_state(Nova::Utcb * utcb);
static void _page_fault_handler();
static void _startup_handler();
static void _invoke_handler();
static void _recall_handler();
public:
@ -99,12 +116,14 @@ namespace Genode {
/**
* Notify exception handler about the occurrence of an exception
*/
void submit_exception_signal()
bool submit_exception_signal()
{
if (!_exception_sigh.valid()) return;
if (!_exception_sigh.valid()) return false;
Signal_transmitter transmitter(_exception_sigh);
transmitter.submit();
return true;
}
/**
@ -112,8 +131,40 @@ namespace Genode {
*/
addr_t handler_address()
{
return reinterpret_cast<addr_t>(_invoke_handler);
return reinterpret_cast<addr_t>(_invoke_handler);
}
/**
* Return semaphore to block on until state of a recall is
* available.
*/
Native_capability notify_sm()
{
if (_state.valid)
return Native_capability::invalid_cap();
if (_state.dead)
return Native_capability::invalid_cap();
return Native_capability(_sm_state_notify);
}
/**
* Copy thread state of recalled thread.
*/
int copy_thread_state(Thread_state * state_dst)
{
if (!state_dst || !_state.valid) return -1;
*state_dst = _state.thread;
return 0;
}
/**
* Cancel blocking in a lock so that recall exception can take
* place.
*/
void cancel_blocking_client();
};

View File

@ -172,9 +172,12 @@ namespace Nova {
enum {
ACDB = 1 << 0, /* eax, ecx, edx, ebx */
EBSD = 1 << 1, /* ebp, esi, edi */
ESP = 1 << 2,
EIP = 1 << 3,
EFL = 1 << 4, /* eflags */
FSGS = 1 << 6,
CSSS = 1 << 7,
QUAL = 1 << 15, /* exit qualification */
CTRL = 1 << 16, /* execution controls */
INJ = 1 << 17, /* injection info */
@ -384,9 +387,13 @@ namespace Nova {
*/
struct Utcb
{
mword_t items; /* number of untyped items uses lowest 16 bit, number of typed items uses bit 16-31, bit 32+ are ignored on 64bit */
Crd crd_xlt; /* receive capability-range descriptor for translation */
Crd crd_rcv; /* receive capability-range descriptor for delegation */
/**
* Number of untyped items uses lowest 16 bit, number of typed items
* uses bit 16-31, bit 32+ are ignored on 64bit
*/
mword_t items;
Crd crd_xlt; /* receive capability-range descriptor for translation */
Crd crd_rcv; /* receive capability-range descriptor for delegation */
mword_t tls;
/**
@ -402,19 +409,39 @@ namespace Nova {
/* exception state */
struct {
mword_t mtd, instr_len, eip, eflags;
unsigned misc[4];
mword_t eax, ecx, edx, ebx;
mword_t esp, ebp, esi, edi;
mword_t mtd, instr_len, ip, flags;
unsigned intr_state, actv_state, inj_info, inj_error;
mword_t ax, cx, dx, bx;
mword_t sp, bp, si, di;
#ifdef __x86_64__
mword_t rxx[8];
mword_t r8, r9, r10, r11, r12, r13, r14, r15;
#endif
unsigned long long qual[2]; /* exit qualification */
unsigned ctrl[2];
unsigned long long tsc;
mword_t cr0, cr2, cr3, cr4;
// unsigned misc3[44];
};
#ifdef __x86_64__
mword_t cr8, reserved;
#endif
mword_t dr7, sysenter_cs, sysenter_sp, sysenter_ip;
struct {
unsigned short sel, ar;
unsigned limit;
mword_t base;
#ifdef __x86_32__
mword_t reserved;
#endif
} es, cs, ss, ds, fs, gs, ldtr, tr;
struct {
unsigned reserved0;
unsigned limit;
mword_t base;
#ifdef __x86_32__
mword_t reserved1;
#endif
} gdtr, idtr;
} __attribute__((packed));
};
struct Item {
@ -499,9 +526,10 @@ namespace Nova {
PT_SEL_PAGE_FAULT = 0xe,
PT_SEL_PARENT = 0x1a, /* convention on Genode */
PT_SEL_STARTUP = 0x1e,
PT_SEL_RECALL = 0x1f,
PD_SEL = 0x1b,
PD_SEL_CAP_LOCK = 0x1c, /* convention on Genode */
SM_SEL_EC_MAIN = 0x1c, /* convention on Genode */
SM_SEL_EC_CLIENT = 0x1c, /* convention on Genode */
SM_SEL_EC = 0x1d, /* convention on Genode */
};

View File

@ -1,3 +0,0 @@
SRC_CC = pager.cc
vpath pager.cc $(REP_DIR)/src/base/pager

View File

@ -0,0 +1,3 @@
SRC_CC = pager.cc x86_32/pager.cc
vpath %.cc $(REP_DIR)/src/base/pager

View File

@ -0,0 +1,3 @@
SRC_CC = pager.cc x86_64/pager.cc
vpath %.cc $(REP_DIR)/src/base/pager

View File

@ -46,7 +46,7 @@ void Ipc_pager::wait_for_fault()
Nova::Utcb *utcb = (Nova::Utcb *)Thread_base::myself()->utcb();
_fault_type = (Pf_type)utcb->qual[0];
_fault_addr = utcb->qual[1];
_fault_ip = utcb->eip;
_fault_ip = utcb->ip;
if (verbose_page_fault)
print_page_fault(_fault_type, _fault_addr, _fault_ip);

View File

@ -47,9 +47,13 @@ void Pager_object::_page_fault_handler()
PWRN("unresolvable page-fault at address 0x%lx, ip=0x%lx",
ipc_pager.fault_addr(), ipc_pager.fault_ip());
/* revoke paging capability, let thread die in kernel */
Nova::revoke(Obj_crd(obj->exc_pt_sel() + PT_SEL_PAGE_FAULT, 0),
true);
if (!obj->submit_exception_signal()) {
/* revoke paging capability, let thread die in kernel */
Nova::revoke(Obj_crd(obj->exc_pt_sel() + PT_SEL_PAGE_FAULT, 0),
true);
obj->_state.dead = true;
}
Utcb *utcb = (Utcb *)Thread_base::myself()->utcb();
utcb->set_msg_word(0);
}
@ -57,14 +61,41 @@ void Pager_object::_page_fault_handler()
ipc_pager.reply_and_wait_for_fault();
}
void Pager_object::_recall_handler()
{
Pager_object *obj = static_cast<Pager_object *>(Thread_base::myself());
Utcb *utcb = (Utcb *)Thread_base::myself()->utcb();
obj->_copy_state(utcb);
obj->_state.thread.ip = utcb->ip;
obj->_state.thread.sp = utcb->sp;
obj->_state.thread.eflags = utcb->flags;
obj->_state.thread.trapno = PT_SEL_RECALL;
obj->_state.valid = true;
if (sm_ctrl(obj->_sm_state_notify, SEMAPHORE_UP) != NOVA_OK)
PWRN("notify failed");
if (sm_ctrl(obj->exc_pt_sel() + SM_SEL_EC, SEMAPHORE_DOWNZERO) != NOVA_OK)
PWRN("blocking recall handler failed");
obj->_state.valid = false;
utcb->set_msg_word(0);
utcb->mtd = 0;
reply(Thread_base::myself()->stack_top());
}
void Pager_object::_startup_handler()
{
Pager_object *obj = static_cast<Pager_object *>(Thread_base::myself());
Utcb *utcb = (Utcb *)Thread_base::myself()->utcb();
utcb->eip = obj->_initial_eip;
utcb->esp = obj->_initial_esp;
utcb->ip = obj->_initial_eip;
utcb->sp = obj->_initial_esp;
utcb->mtd = Mtd::EIP | Mtd::ESP;
utcb->set_msg_word(0);
reply(Thread_base::myself()->stack_top());
@ -82,12 +113,13 @@ void Pager_object::_invoke_handler()
utcb->set_msg_word(0);
if (event == PT_SEL_STARTUP || event == PT_SEL_PAGE_FAULT ||
event == SM_SEL_EC) {
event == SM_SEL_EC || event == PT_SEL_RECALL) {
/**
* Caller is requesting the SM cap of main thread
* this object is paging - it is stored at SM_SEL_EC_MAIN
* Caller is requesting the SM cap of thread
* this object is paging - it is stored at SM_SEL_EC_CLIENT
*/
if (event == SM_SEL_EC) event = SM_SEL_EC_MAIN;
if (event == SM_SEL_EC) event = SM_SEL_EC_CLIENT;
bool res = utcb->append_item(Obj_crd(obj->exc_pt_sel() + event,
0), 0);
@ -99,13 +131,21 @@ void Pager_object::_invoke_handler()
}
void Pager_object::wake_up() { PDBG("not yet implemented"); }
void Pager_object::wake_up() { cancel_blocking(); }
void Pager_object::cancel_blocking_client() {
uint8_t res = sm_ctrl(exc_pt_sel() + SM_SEL_EC_CLIENT, SEMAPHORE_UP);
if (res != NOVA_OK)
PWRN("cancel blocking failed");
}
Pager_object::Pager_object(unsigned long badge)
: Thread_base("pager", PF_HANDLER_STACK_SIZE), _badge(badge)
{
_pt_cleanup = cap_selector_allocator()->alloc();
_pt_cleanup = cap_selector_allocator()->alloc();
_sm_state_notify = cap_selector_allocator()->alloc();
_state.valid = false;
_state.dead = false;
/* create portal for page-fault handler */
addr_t pd_sel = __core_pd_sel;
@ -129,19 +169,46 @@ Pager_object::Pager_object(unsigned long badge)
throw Create_startup_pt_failed();
}
/* Create portal for recall handler */
Mtd mtd(Mtd::ESP | Mtd::EIP | Mtd::ACDB | Mtd::EFL | Mtd::EBSD | Mtd::FSGS);
res = create_pt(exc_pt_sel() + PT_SEL_RECALL, pd_sel, _tid.ec_sel,
mtd, (addr_t)_recall_handler);
if (res) {
PERR("could not create recall portal, error = %u\n", res);
class Create_recall_pt_failed { };
throw Create_recall_pt_failed();
}
/* Create portal for final cleanup call used during destruction */
res = create_pt(_pt_cleanup, pd_sel, _tid.ec_sel, Mtd(0),
reinterpret_cast<addr_t>(_invoke_handler));
if (res)
PERR("could not create pager cleanup portal, error = %u\n",
res);
if (res) {
PERR("could not create pager cleanup portal, error = %u\n", res);
class Create_cleanup_pt_failed { };
throw Create_cleanup_pt_failed();
}
res = Nova::create_sm(_sm_state_notify, pd_sel, 0);
if (res != Nova::NOVA_OK) {
class Create_state_notifiy_sm_failed { };
throw Create_state_notifiy_sm_failed();
}
}
Pager_object::~Pager_object()
{
/* Revoke portals of Pager_object */
revoke(Obj_crd(exc_pt_sel() + PT_SEL_STARTUP, 0), true);
revoke(Obj_crd(exc_pt_sel() + PT_SEL_RECALL, 0), true);
revoke(Obj_crd(exc_pt_sel() + PT_SEL_PAGE_FAULT, 0), true);
/* Revoke semaphore cap to signal valid state after recall */
addr_t sm_cap = _sm_state_notify;
_sm_state_notify = Native_thread::INVALID_INDEX;
/* If pager is blocked wake him up */
sm_ctrl(sm_cap, SEMAPHORE_UP);
revoke(Obj_crd(sm_cap, 0), true);
/* Make sure nobody is in the handler anymore by doing an IPC to a
* local cap pointing to same serving thread (if not running in the
* context of the serving thread). When the call returns
@ -158,7 +225,7 @@ Pager_object::~Pager_object()
/* Revoke portal used for the cleanup call */
revoke(Obj_crd(_pt_cleanup, 0), true);
cap_selector_allocator()->free(_pt_cleanup, 0);
cap_selector_allocator()->free(sm_cap, 0);
}

View File

@ -0,0 +1,34 @@
/*
* \brief Copy thread state - x86_32
* \author Alexander Boettcher
* \date 2012-08-23
*/
/*
* Copyright (C) 2012-2012 Genode Labs GmbH
*
* This file is part of the Genode OS framework, which is distributed
* under the terms of the GNU General Public License version 2.
*/
/* Genode includes */
#include <base/pager.h>
/* NOVA includes */
#include <nova/syscalls.h>
using namespace Genode;
void Pager_object::_copy_state(Nova::Utcb * utcb)
{
_state.thread.ebp = utcb->bp;
_state.thread.eax = utcb->ax;
_state.thread.ebx = utcb->bx;
_state.thread.ecx = utcb->cx;
_state.thread.edx = utcb->dx;
_state.thread.esi = utcb->si;
_state.thread.edi = utcb->di;
_state.thread.gs = utcb->gs.sel;
_state.thread.fs = utcb->fs.sel;
}

View File

@ -0,0 +1,42 @@
/*
* \brief Copy thread state - x86_64
* \author Alexander Boettcher
* \date 2012-08-23
*/
/*
* Copyright (C) 2012-2012 Genode Labs GmbH
*
* This file is part of the Genode OS framework, which is distributed
* under the terms of the GNU General Public License version 2.
*/
/* Genode includes */
#include <base/pager.h>
/* NOVA includes */
#include <nova/syscalls.h>
using namespace Genode;
void Pager_object::_copy_state(Nova::Utcb * utcb)
{
_state.thread.rbp = utcb->bp;
_state.thread.rax = utcb->ax;
_state.thread.rbx = utcb->bx;
_state.thread.rcx = utcb->cx;
_state.thread.rdx = utcb->dx;
_state.thread.rsi = utcb->si;
_state.thread.rdi = utcb->di;
_state.thread.r8 = utcb->r8;
_state.thread.r9 = utcb->r9;
_state.thread.r10 = utcb->r10;
_state.thread.r11 = utcb->r11;
_state.thread.r12 = utcb->r12;
_state.thread.r13 = utcb->r13;
_state.thread.r14 = utcb->r14;
_state.thread.r15 = utcb->r15;
_state.thread.ss = utcb->ss.sel;
}

View File

@ -224,6 +224,8 @@ Rpc_entrypoint::Rpc_entrypoint(Cap_session *cap_session, size_t stack_size,
Nova::PT_SEL_PAGE_FAULT);
request_event_portal(pager_cap, _tid.exc_pt_sel,
Nova::SM_SEL_EC);
request_event_portal(pager_cap, _tid.exc_pt_sel,
Nova::PT_SEL_RECALL);
/**
* Request native thread cap, _thread_cap only a token.

View File

@ -132,6 +132,7 @@ void Thread_base::start()
request_event_portal(pager_cap, _tid.exc_pt_sel, PT_SEL_STARTUP);
request_event_portal(pager_cap, _tid.exc_pt_sel, PT_SEL_PAGE_FAULT);
request_event_portal(pager_cap, _tid.exc_pt_sel, SM_SEL_EC);
request_event_portal(pager_cap, _tid.exc_pt_sel, PT_SEL_RECALL);
}
/* request creation of SC to let thread run*/

View File

@ -98,11 +98,11 @@ static void page_fault_handler()
Utcb *utcb = (Utcb *)CORE_PAGER_UTCB_ADDR;
addr_t pf_addr = utcb->qual[1];
addr_t pf_eip = utcb->eip;
addr_t pf_esp = utcb->esp;
addr_t pf_ip = utcb->ip;
addr_t pf_sp = utcb->sp;
printf("\nPAGE-FAULT IN CORE: ADDR %lx IP %lx SP %lx stack trace follows...\n",
pf_addr, pf_eip, pf_esp);
pf_addr, pf_ip, pf_sp);
/* dump stack trace */
struct Core_img
@ -127,9 +127,9 @@ static void page_fault_handler()
};
int count = 1;
printf(" #%d %08lx %08lx\n", count++, pf_esp, pf_eip);
printf(" #%d %08lx %08lx\n", count++, pf_sp, pf_ip);
Core_img dump(pf_esp);
Core_img dump(pf_sp);
while (dump.ip_valid()) {
printf(" #%d %p %08lx\n", count++, dump.ip(), *dump.ip());
dump.next_ip();

View File

@ -26,6 +26,7 @@
/* NOVA includes */
#include <nova/syscalls.h>
#include <nova/util.h>
using namespace Genode;
@ -69,11 +70,11 @@ int Platform_thread::start(void *ip, void *sp, addr_t exc_base, bool vcpu)
/**
* Create semaphore required for Genode locking.
* It is created at the root pager exception base +
* SM_SEL_EC_MAIN and can be later on requested by the thread
* SM_SEL_EC_CLIENT and can be later on requested by the thread
* the same way as STARTUP and PAGEFAULT portal.
*/
uint8_t res = Nova::create_sm(_pager->exc_pt_sel() +
SM_SEL_EC_MAIN,
SM_SEL_EC_CLIENT,
_pd->pd_sel(), 0);
if (res != Nova::NOVA_OK) {
PERR("creation of semaphore for new thread failed %u",
@ -107,20 +108,32 @@ int Platform_thread::start(void *ip, void *sp, addr_t exc_base, bool vcpu)
addr_t pd_core_sel = Platform_pd::pd_core_sel();
addr_t sm_alloc_sel = _sel_exc_base + PD_SEL_CAP_LOCK;
addr_t sm_ec_sel = _sel_exc_base + SM_SEL_EC;
addr_t sm_ec_sel = _pager->exc_pt_sel() + SM_SEL_EC_CLIENT;
addr_t remap_src[] = { _pager->exc_pt_sel() + PT_SEL_PAGE_FAULT,
_pd->parent_pt_sel(),
_pager->exc_pt_sel() + PT_SEL_STARTUP };
_pager->exc_pt_sel() + PT_SEL_STARTUP,
_pager->exc_pt_sel() + PT_SEL_RECALL,
sm_ec_sel };
addr_t remap_dst[] = { PT_SEL_PAGE_FAULT,
PT_SEL_PARENT,
PT_SEL_STARTUP };
PT_SEL_STARTUP,
PT_SEL_RECALL,
SM_SEL_EC };
addr_t pd_sel;
Obj_crd initial_pts(_sel_exc_base, NUM_INITIAL_PT_LOG2);
uint8_t res;
/* Create lock for EC used by lock_helper */
res = create_sm(sm_ec_sel, pd_core_sel, 0);
if (res != NOVA_OK) {
PERR("could not create semaphore for new thread");
goto cleanup_base;
}
/* Remap portals to exception base window of first thread */
for (unsigned i = 0; i < sizeof(remap_dst)/sizeof(remap_dst[0]); i++) {
/* locally map portals to initial portal window */
if (map_local((Utcb *)Thread_base::myself()->utcb(),
@ -139,13 +152,6 @@ int Platform_thread::start(void *ip, void *sp, addr_t exc_base, bool vcpu)
goto cleanup_base;
}
/* Create lock for EC used by lock_helper */
res = create_sm(sm_ec_sel, pd_core_sel, 0);
if (res != NOVA_OK) {
PERR("could not create semaphore for new thread");
goto cleanup_base;
}
pd_sel = cap_selector_allocator()->alloc();
/* create task */
@ -192,6 +198,7 @@ int Platform_thread::start(void *ip, void *sp, addr_t exc_base, bool vcpu)
cap_selector_allocator()->free(pd_sel, 0);
cleanup_base:
revoke(Obj_crd(sm_ec_sel, 0));
revoke(Obj_crd(_sel_exc_base, NUM_INITIAL_PT_LOG2));
cap_selector_allocator()->free(_sel_exc_base, NUM_INITIAL_PT_LOG2);
_sel_exc_base = ~0UL;
@ -202,28 +209,51 @@ int Platform_thread::start(void *ip, void *sp, addr_t exc_base, bool vcpu)
Native_capability Platform_thread::pause()
{
PDBG("not implemented");
return Native_capability::invalid_cap();
if (!_pager)
return Native_capability::invalid_cap();
Native_capability notify_sm = _pager->notify_sm();
if (!notify_sm.valid()) return notify_sm;
if (Nova::ec_ctrl(_sel_ec()) != Nova::NOVA_OK)
return Native_capability::invalid_cap();
/* If the thread is blocked in the its own SM, get him out */
cancel_blocking();
return notify_sm;
}
void Platform_thread::resume()
{
uint8_t res = Nova::create_sc(_sel_sc(), _pd->pd_sel(), _sel_ec(),
Nova::Qpd());
if (res)
PDBG("create_sc returned %u", res);
using namespace Nova;
uint8_t res = create_sc(_sel_sc(), _pd->pd_sel(), _sel_ec(), Qpd());
if (res == NOVA_OK) return;
if (!_pager) return;
/* Thread was paused beforehand and blocked in pager - wake up pager */
_pager->wake_up();
}
int Platform_thread::state(Thread_state *state_dst)
{
PWRN("not implemented");
return -1;
if (!state_dst || !_pager) return -1;
int res = _pager->copy_thread_state(state_dst);
return res;
}
void Platform_thread::cancel_blocking() { PWRN("not implemented"); }
void Platform_thread::cancel_blocking()
{
if (!_pager) return;
_pager->cancel_blocking_client();
}
unsigned long Platform_thread::pager_object_badge() const { return ~0UL; }