aos/lib/aos/dispatch.c
Daniel Schwyn 6d444bf552 Main handout
Signed-off-by: Daniel Schwyn <daniel.schwyn@inf.ethz.ch>
2022-03-03 14:57:51 +01:00

558 lines
17 KiB
C

/**
* \file
* \brief Dispatcher implementation.
*/
/*
* Copyright (c) 2007, 2008, 2009, 2010, 2011, 2012, ETH Zurich.
* All rights reserved.
*
* This file is distributed under the terms in the attached LICENSE file.
* If you do not find this file, copies can be found by writing to:
* ETH Zurich D-INFK, Haldeneggsteig 4, CH-8092 Zurich. Attn: Systems Group.
*/
#include <inttypes.h>
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <aos/aos.h>
#include <aos/dispatch.h>
#include <aos/dispatcher_arch.h>
#include <aos/curdispatcher_arch.h>
#include <aos/caddr.h>
#include <aos/debug.h>
#include <aos/deferred.h>
#include <barrelfish_kpi/cpu_arch.h>
#include "threads_priv.h"
#include <aos/notificator.h>
#include <aos/systime.h>
#include <aos/lmp_chan.h>
#if defined(__GNUC__) && !defined(__clang__) && !defined(__ICC) \
&& (defined(__x86_64__) || (defined(__i386__)))
// Disable SSE/MMX -- this code context switches those registers
# pragma GCC target ("no-mmx,no-sse,no-sse2,no-sse3,no-sse4.1,no-sse4.2,no-sse4,no-sse4a,no-3dnow")
#endif
void disp_run(dispatcher_handle_t handle);
void disp_lrpc(struct lmp_endpoint *ep, uint32_t bufpos,
uintptr_t arg1, uintptr_t arg2, uintptr_t arg3, uintptr_t arg4,
dispatcher_handle_t handle);
void disp_pagefault(dispatcher_handle_t handle, lvaddr_t fault_address,
uintptr_t error, lvaddr_t ip);
void disp_pagefault_disabled(dispatcher_handle_t handle, lvaddr_t fault_address,
uintptr_t error, lvaddr_t ip);
void disp_trap(dispatcher_handle_t handle, uintptr_t irq, uintptr_t error,
lvaddr_t ip);
static inline void assert_print(const char *str)
{
sys_print(str, strlen(str));
}
static uint64_t run_counter = 0;
uint64_t disp_run_counter(void)
{
return run_counter;
}
/**
* \brief Run entry point
*
* This function is called from assembly code when the kernel enters us to
* give us the CPU.
*
* \param disp Dispatcher
*/
void disp_run(dispatcher_handle_t handle)
{
#ifdef __x86_64__
struct dispatcher_x86_64 *disp_priv = get_dispatcher_x86_64(handle);
/* load compatibility dispatcher segment to FS */
__asm volatile("mov %%ax, %%fs"
: /* No outputs */
: "a" (disp_priv->disp_seg_selector));
#endif
// We can't call printf(), so do this silly thing...
// assert_print("FIXME: infinite while loop\n");
// while(1);
struct dispatcher_generic* disp_gen = get_dispatcher_generic(handle);
struct dispatcher_shared_generic* disp =
get_dispatcher_shared_generic(handle);
assert_disabled(disp->disabled);
++run_counter;
disp_gen->timeslice++;
// Never let 0 be a valid timeslice number
if(disp_gen->timeslice == 0) {
disp_gen->timeslice++;
}
// Trigger any deferred events
trigger_deferred_events_disabled(handle, disp->systime);
#ifdef CONFIG_INTERCONNECT_DRIVER_LMP
// Check for incoming LMP messages
if (disp->lmp_delivered != disp->lmp_seen) {
lmp_endpoints_poll_disabled(handle);
}
// Trigger any send events for LMP channels
lmp_channels_retry_send_disabled(handle);
#endif // CONFIG_INTERCONNECT_DRIVER_LMP
// Check polled channels
poll_channels_disabled(handle);
check_notificators_disabled(handle);
// Run, saving state of previous thread if required
thread_run_disabled(handle);
// NOTREACHED
assert_disabled(!"disp_run: thread_run() returned!\n");
for(;;);
}
/**
* \brief LRPC entry point
*
* This function is called from assembly code when the kernel enters us to
* give us the CPU and deliver an LRPC message. The dispatcher is disabled.
*
* \param ep LMP endpoint structure
* \param bufpos Reserved position in endpoint message buffer
* \param arg1 Message payload
* \param arg2 Message payload
* \param arg3 Message payload
* \param arg4 Message payload
* \param handle Dispatcher pointer
*
* \note Dispatcher pointer comes last here, because AB was too lazy to shuffle
* registers through the whole LRPC path when adding it.
*/
void disp_lrpc(struct lmp_endpoint *ep, uint32_t bufpos,
uintptr_t arg1, uintptr_t arg2, uintptr_t arg3, uintptr_t arg4,
dispatcher_handle_t handle)
{
struct dispatcher_shared_generic* disp =
get_dispatcher_shared_generic(handle);
/* deliver LRPC message to buffer */
lmp_endpoint_store_lrpc_disabled(ep, bufpos, arg1, arg2, arg3, arg4);
// set hint
disp->lmp_hint = (lvaddr_t)&ep->k - (lvaddr_t)handle;
disp_run(handle);
}
/**
* \brief Initialise the dispatcher, while disabled
*
* This function is called to setup the dispatcher structure while still
* disabled.
*
* \param disp Dispatcher
*/
void disp_init_disabled(dispatcher_handle_t handle)
{
assert_disabled(handle != 0);
struct dispatcher_generic* disp_gen = get_dispatcher_generic(handle);
struct dispatcher_shared_generic* disp =
get_dispatcher_shared_generic(handle);
assert_disabled(disp->disabled);
// Initialize entry points (and LDT on x86_64)
disp_arch_init(handle);
disp_gen->timeslice = 1;
systime_frequency = disp->systime_frequency;
// Initialize important capability pointers
if (disp_gen->dcb_cap.slot == 0) {
disp_gen->dcb_cap.cnode = cnode_task;
disp_gen->dcb_cap.slot = TASKCN_SLOT_DISPATCHER;
}
disp_gen->cleanupthread = NULL;
thread_mutex_init(&disp_gen->cleanupthread_lock);
}
/**
* \brief Yield the dispatcher's CPU
*
* This function yields the CPU. It may only be called while disabled.
*
* \param disp Current dispatcher
*/
void disp_yield_disabled(dispatcher_handle_t handle)
{
struct dispatcher_shared_generic* disp =
get_dispatcher_shared_generic(handle);
assert_disabled(disp->disabled);
#ifdef CONFIG_DEBUG_DEADLOCKS
disp->yieldcount++;
#endif
// FIXME: This perticular trace event is breaking as it is running
// into problems due to assumptions about segment register %fs
// trace_event(TRACE_SUBSYS_THREADS, TRACE_EVENT_THREADS_SYS_YIELD,
// 2);
assert_disabled(disp->disabled);
sys_yield(CPTR_NULL);
assert_disabled(disp->disabled);
assert_print("dispatcher PANIC: sys_yield returned");
for (;;);
}
/**
* \brief Disable the dispatcher
*
* This function disables the current dispatcher, returning a pointer to it.
* The dispatcher must be enabled.
*
* While the dispatcher is disabled, the current thread cannot be preempted,
* and no incoming LMP messages can be received.
*/
dispatcher_handle_t disp_disable(void)
{
dispatcher_handle_t handle = curdispatcher();
struct dispatcher_shared_generic* disp =
get_dispatcher_shared_generic(handle);
assert_disabled(disp->disabled == 0);
disp->disabled = 1;
return handle;
}
/**
* \brief Try to disable the dispatcher
*
* This function disables the current dispatcher if it's enabled
* and returns a pointer to it.
*
* While the dispatcher is disabled, the current thread cannot be preempted,
* and no incoming LMP messages can be received.
*
* \param was_enabled True, if the dispatcher was enabled
*/
dispatcher_handle_t disp_try_disable(bool *was_enabled)
{
dispatcher_handle_t handle = curdispatcher();
struct dispatcher_shared_generic* disp =
get_dispatcher_shared_generic(handle);
#ifdef __k1om__ // K1om GCC 4.7.0 does not support __atomic_* functions
*was_enabled = __sync_bool_compare_and_swap(&disp->disabled, 0, 1);
#else
*was_enabled = !__atomic_test_and_set(&disp->disabled, __ATOMIC_SEQ_CST);
#endif
return handle;
}
/**
* \brief Re-enable the dispatcher
*
* This function re-enables the current dispatcher.
* The dispatcher must be disabled.
*/
void disp_enable(dispatcher_handle_t handle)
{
assert_disabled(handle == curdispatcher());
struct dispatcher_shared_generic* disp =
get_dispatcher_shared_generic(handle);
assert_disabled(disp->disabled == 1);
disp->disabled = 0;
}
/**
* \brief Return a pointer to the name field of the current dispatcher
*
* May be called when the dispatcher is either enabled or disabled.
*
* \returns a string of at most #DISP_NAME_LEN characters,
* which may not be nul-terminated.
*/
const char *disp_name(void)
{
dispatcher_handle_t handle = curdispatcher();
struct dispatcher_shared_generic* disp =
get_dispatcher_shared_generic(handle);
return disp->name;
}
#ifdef __k1om__
/**
* \brief Returns the Xeon Phi ID this dispatcher is running on
*
* May be called when the dispatcher is either enabled or disabled.
*
* \returns unsigned integer containing the Xeon Phi Id
*/
uint8_t disp_xeon_phi_id(void)
{
dispatcher_handle_t handle = curdispatcher();
struct dispatcher_shared_generic* disp =
get_dispatcher_shared_generic(handle);
return disp->xeon_phi_id;
}
#endif
/**
* \brief Page fault entry point
*
* This function is called from assembly code when the kernel
* enters us to report a page fault while enabled.
*
* \param handle Dispatcher
* \param fault_address Fault address
* \param error CPU error code
* \param ip Faulting instruction pointer
*/
void disp_pagefault(dispatcher_handle_t handle, lvaddr_t fault_address,
uintptr_t error, lvaddr_t ip)
{
struct dispatcher_shared_generic* disp =
get_dispatcher_shared_generic(handle);
struct dispatcher_generic *disp_gen = get_dispatcher_generic(handle);
arch_registers_state_t *regs = dispatcher_get_enabled_save_area(handle);
enum pagefault_exception_type fault_type;
// FIXME: this logic is x86-specific. it needs to be moved into an arch file
#if defined(__x86_64__) || defined(__i386__)
const uintptr_t ERR_PF_PRESENT = (1 << 0);
const uintptr_t ERR_PF_READ_WRITE = (1 << 1);
const uintptr_t ERR_PF_USER_SUPERVISOR = (1 << 2);
const uintptr_t ERR_PF_RESERVED = (1 << 3);
const uintptr_t ERR_PF_INSTRUCTION = (1 << 4);
if ((error & ERR_PF_INSTRUCTION) != 0) {
fault_type = PAGEFLT_EXEC;
} else if ((error & ERR_PF_READ_WRITE) != 0) {
fault_type = PAGEFLT_WRITE;
} else {
fault_type = PAGEFLT_READ;
}
#elif defined(__ARM_ARCH_8A__)
const uintptr_t EC_IABT = 0x20;
const uintptr_t EC_DABT = 0x24;
const uintptr_t ISS_WRITE_NOT_READ = (1 << 6);
uintptr_t ec = error >> 26;
if (ec == EC_IABT) {
fault_type = PAGEFLT_EXEC;
}
else if ((ec == EC_DABT) && (error & ISS_WRITE_NOT_READ)) {
fault_type = PAGEFLT_WRITE;
}
else if (ec == EC_DABT) {
fault_type = PAGEFLT_READ;
}
else {
fault_type = PAGEFLT_NULL;
}
#else
assert_print("Warning: don't know how to determine fault type on this arch!\n");
fault_type = PAGEFLT_NULL;
#endif
// sanity-check IP in save area
assert_disabled(ip == registers_get_ip(regs));
// sanity-check that we were on a thread
assert_disabled(disp_gen->current != NULL);
// try to deliver exception
thread_deliver_exception_disabled(handle, EXCEPT_PAGEFAULT, fault_type,
(void *)fault_address, regs);
// it returned: this means the exception couldn't be delivered and the
// thread is dead. better tell them what happened...
static char str[256];
snprintf(str, sizeof(str), "%.*s.%d: unhandled page fault (error code 0x%"
PRIxPTR ") on %" PRIxPTR " at IP %" PRIxPTR "\n",
DISP_NAME_LEN, disp->name, disp_get_current_core_id(), error, fault_address, ip);
assert_print(str);
// dump hw page tables
//debug_dump_hw_ptables();
#if defined(__x86_64__) || defined(__i386__)
snprintf(str, sizeof(str), "%s page fault due to %s%s, while in %s mode%s\n",
error & ERR_PF_READ_WRITE ? "write" : "read",
error & ERR_PF_PRESENT ? "access violation" : "page not present",
error & ERR_PF_RESERVED ? ", reserved bits set in page table"
: "",
error & ERR_PF_USER_SUPERVISOR ? "user" : "supervisor",
error & ERR_PF_INSTRUCTION ? ", by instruction fetch" : "");
assert_print(str);
#endif
// print out stuff
debug_print_save_area(regs);
debug_dump(regs);
//debug_call_chain(regs);
// run something else
thread_run_disabled(handle);
}
/**
* \brief Disabled page fault entry point
*
* This function is called from assembly code when the kernel enters us to
* report a page fault while disabled.
*
* \param handle Dispatcher
* \param fault_address Fault address
* \param error CPU error code
* \param ip Faulting instruction pointer
*/
void disp_pagefault_disabled(dispatcher_handle_t handle, lvaddr_t fault_address,
uintptr_t error, lvaddr_t ip)
{
struct dispatcher_shared_generic *disp =
get_dispatcher_shared_generic(handle);
static char str[256];
snprintf(str, 256, "%.*s: page fault WHILE DISABLED"
" (error code 0x%" PRIxPTR ") on %" PRIxPTR " at IP %" PRIxPTR "\n",
DISP_NAME_LEN, disp->name, error, fault_address, ip);
assert_print(str);
if(fault_address == 0) {
assert_print("NULL pointer dereferenced!\n");
}
// NOTE: Based on which code is is causing page fault, only assert_print
// is safe bet to print anything here. Anything else would cause
// page fault in itself.
assert_disabled(disp->disabled);
// FIXME: Make sure that following are using assert_print to avoid
// loop of disabled pagefaults
// arch_registers_state_t *regs = dispatcher_get_trap_save_area(handle);
// debug_print_save_area(regs);
// disabled by AB, because we can get into a loop of disabled pagefaults
// debug_dump(regs);
// debug_return_addresses();
for(;;);
}
#include <aos/aos.h>
#include <barrelfish_kpi/capabilities.h>
/**
* \brief Trap entry point
*
* This function is called from assembly code when the kernel enters us to
* report a trap taken while we were enabled.
*
* \param handle Dispatcher
* \param irq Trap vector
* \param error CPU error code
* \param ip Faulting instruction pointer
*/
void disp_trap(dispatcher_handle_t handle, uintptr_t irq, uintptr_t error,
lvaddr_t ip)
{
struct dispatcher_shared_generic* disp =
get_dispatcher_shared_generic(handle);
arch_registers_state_t *regs = dispatcher_get_trap_save_area(handle);
struct dispatcher_generic *disp_gen = get_dispatcher_generic(handle);
// Must've happened on a thread
struct thread *t = disp_gen->current;
assert_disabled(t != NULL);
// sanity-check IP in save area
// not valid for debug exceptions?
// assert_disabled(ip == registers_get_ip(regs));
enum exception_type type;
#if defined(__i386__) || defined(__x86_64__)
switch (irq) {
case IDT_DB:
type = EXCEPT_SINGLESTEP;
break;
case IDT_BP:
type = EXCEPT_BREAKPOINT;
break;
default:
type = EXCEPT_OTHER;
break;
}
#else
type = EXCEPT_OTHER; // XXX
#endif
// deliver exception (shouldn't return)
thread_deliver_exception_disabled(handle, type, irq, (void *)ip, regs);
// if it failed, say something
static char str[256];
snprintf(str, sizeof(str), "%.*s: unhandled trap (IRQ %" PRIuPTR
", error code 0x%" PRIxPTR ") at IP %" PRIxPTR "\n",
DISP_NAME_LEN, disp->name, irq, error, ip);
assert_print(str);
// print out stuff
debug_print_save_area(regs);
//debug_call_chain(regs);
// run something else
thread_run_disabled(handle);
}
void
disp_assert_fail(const char *exp, const char *file, const char *func, const char *line)
{
const char *dispname = disp_name();
// We can't call printf(), so do this silly thing...
assert_print("Dispatcher assertion failed in ");
for(int i = 0; i < DISP_NAME_LEN && dispname[i] != '\0'; i++) {
sys_print(&dispname[i], 1);
}
assert_print(": ");
assert_print(exp);
assert_print(", function ");
assert_print(func);
assert_print(", file ");
assert_print(file);
assert_print(", line ");
assert_print(line);
assert_print(".\n");
for(;;);
}
void
disp_warn_fail(const char *exp, const char *file, const char *func, const char *line)
{
const char *dispname = disp_name();
// We can't call printf(), so do this silly thing...
assert_print("Dispatcher warning in ");
for(int i = 0; i < DISP_NAME_LEN && dispname[i] != '\0'; i++) {
sys_print(&dispname[i], 1);
}
assert_print(": ");
assert_print(exp);
assert_print(", function ");
assert_print(func);
assert_print(", file ");
assert_print(file);
assert_print(", line ");
assert_print(line);
assert_print(".\n");
}