blob: 2fe91f58016bd3c874fdeca19d1c08ed5e1ac7e3 [file] [log] [blame] [edit]
/*
* Copyright 2017, Data61, CSIRO (ABN 41 687 119 230)
*
* SPDX-License-Identifier: BSD-2-Clause
*/
#include <sel4/sel4.h>
#include <sel4utils/arch/util.h>
#include <sel4utils/helpers.h>
#include <sel4runtime.h>
#include <sel4test/test.h>
#include <stdarg.h>
#include <stdlib.h>
#include <assert.h>
#include <utils/util.h>
#include <vka/capops.h>
#include <vka/ipcbuffer.h>
#include <sel4platsupport/timer.h>
#include "helpers.h"
#include "test.h"
char __attribute__((aligned(16))) process_tls[1024 * 16];
int check_zeroes(seL4_Word addr, seL4_Word size_bytes)
{
test_assert_fatal(IS_ALIGNED(addr, sizeof(seL4_Word)));
test_assert_fatal(IS_ALIGNED(size_bytes, sizeof(seL4_Word)));
seL4_Word *p = (void *)addr;
seL4_Word size_words = size_bytes / sizeof(seL4_Word);
while (size_words--) {
if (*p++ != 0) {
ZF_LOGE("Found non-zero at position %ld: %lu\n", ((long)p) - (addr), (unsigned long)p[-1]);
return 0;
}
}
return 1;
}
/* Determine whether a given slot in the init thread's CSpace is empty by
* examining the error when moving a slot onto itself.
*
* Serves as == 0 comparator for caps.
*/
int is_slot_empty(env_t env, seL4_Word slot)
{
int error;
error = cnode_move(env, slot, slot);
/* cnode_move first check if the destination is empty and raise
* seL4_DeleteFirst is it is not
* The is check if the source is empty and raise seL4_FailedLookup if it is
*/
assert(error == seL4_DeleteFirst || error == seL4_FailedLookup);
return (error == seL4_FailedLookup);
}
seL4_Word get_free_slot(env_t env)
{
seL4_CPtr slot;
UNUSED int error = vka_cspace_alloc(&env->vka, &slot);
assert(!error);
return slot;
}
int cnode_copy(env_t env, seL4_CPtr src, seL4_CPtr dest, seL4_CapRights_t rights)
{
cspacepath_t src_path, dest_path;
vka_cspace_make_path(&env->vka, src, &src_path);
vka_cspace_make_path(&env->vka, dest, &dest_path);
return vka_cnode_copy(&dest_path, &src_path, rights);
}
int cnode_delete(env_t env, seL4_CPtr slot)
{
cspacepath_t path;
vka_cspace_make_path(&env->vka, slot, &path);
return vka_cnode_delete(&path);
}
int cnode_mint(env_t env, seL4_CPtr src, seL4_CPtr dest, seL4_CapRights_t rights, seL4_Word badge)
{
cspacepath_t src_path, dest_path;
vka_cspace_make_path(&env->vka, src, &src_path);
vka_cspace_make_path(&env->vka, dest, &dest_path);
return vka_cnode_mint(&dest_path, &src_path, rights, badge);
}
int cnode_move(env_t env, seL4_CPtr src, seL4_CPtr dest)
{
cspacepath_t src_path, dest_path;
vka_cspace_make_path(&env->vka, src, &src_path);
vka_cspace_make_path(&env->vka, dest, &dest_path);
return vka_cnode_move(&dest_path, &src_path);
}
int cnode_mutate(env_t env, seL4_CPtr src, seL4_CPtr dest)
{
cspacepath_t src_path, dest_path;
vka_cspace_make_path(&env->vka, src, &src_path);
vka_cspace_make_path(&env->vka, dest, &dest_path);
return vka_cnode_mutate(&dest_path, &src_path, seL4_NilData);
}
int cnode_cancelBadgedSends(env_t env, seL4_CPtr cap)
{
cspacepath_t path;
vka_cspace_make_path(&env->vka, cap, &path);
return vka_cnode_cancelBadgedSends(&path);
}
int cnode_revoke(env_t env, seL4_CPtr cap)
{
cspacepath_t path;
vka_cspace_make_path(&env->vka, cap, &path);
return vka_cnode_revoke(&path);
}
int cnode_rotate(env_t env, seL4_CPtr src, seL4_CPtr pivot, seL4_CPtr dest)
{
cspacepath_t src_path, dest_path, pivot_path;
vka_cspace_make_path(&env->vka, src, &src_path);
vka_cspace_make_path(&env->vka, dest, &dest_path);
vka_cspace_make_path(&env->vka, pivot, &pivot_path);
return vka_cnode_rotate(&dest_path, seL4_NilData, &pivot_path, seL4_NilData, &src_path);
}
int cnode_savecaller(env_t env, seL4_CPtr cap)
{
cspacepath_t path;
vka_cspace_make_path(&env->vka, cap, &path);
#ifndef CONFIG_KERNEL_MCS
return vka_cnode_saveCaller(&path);
#else
ZF_LOGF("Should not be called");
return 0;
#endif
}
void set_cap_receive_path(env_t env, seL4_CPtr slot)
{
cspacepath_t path;
vka_cspace_make_path(&env->vka, slot, &path);
return vka_set_cap_receive_path(&path);
}
int are_tcbs_distinct(seL4_CPtr tcb1, seL4_CPtr tcb2)
{
seL4_UserContext regs;
/* Initialise regs to prevent compiler warning. */
int error = seL4_TCB_ReadRegisters(tcb1, 0, 0, 1, &regs);
if (error) {
return -1;
}
for (int i = 0; i < 2; ++i) {
sel4utils_set_instruction_pointer(&regs, i);
error = seL4_TCB_WriteRegisters(tcb1, 0, 0, 1, &regs);
/* Check that we had permission to do that and the cap was a TCB. */
if (error) {
return -1;
}
error = seL4_TCB_ReadRegisters(tcb2, 0, 0, 1, &regs);
/* Check that we had permission to do that and the cap was a TCB. */
if (error) {
return -1;
} else if (sel4utils_get_instruction_pointer(regs) != i) {
return 1;
}
}
return 0;
}
void create_helper_process_custom_asid(env_t env, helper_thread_t *thread, seL4_CPtr asid)
{
UNUSED int error;
error = vka_alloc_endpoint(&env->vka, &thread->local_endpoint);
assert(error == 0);
thread->is_process = true;
sel4utils_process_config_t config = process_config_default_simple(&env->simple, "", OUR_PRIO - 1);
config = process_config_asid_pool(config, asid);
config = process_config_noelf(config, NULL, 0);
config = process_config_create_cnode(config, TEST_PROCESS_CSPACE_SIZE_BITS);
config = process_config_create_vspace(config, env->regions, env->num_regions);
vka_object_t fault_endpoint = { .cptr = env->endpoint };
config = process_config_fault_endpoint(config, fault_endpoint);
error = sel4utils_configure_process_custom(&thread->process, &env->vka, &env->vspace,
config);
assert(error == 0);
/* copy the elf reservations we need into the current process */
memcpy(thread->regions, env->regions, sizeof(sel4utils_elf_region_t) * env->num_regions);
thread->num_regions = env->num_regions;
/* clone data/code into vspace */
for (int i = 0; i < env->num_regions; i++) {
error = sel4utils_bootstrap_clone_into_vspace(&env->vspace, &thread->process.vspace, thread->regions[i].reservation);
assert(error == 0);
}
thread->thread = thread->process.thread;
assert(error == 0);
}
void create_helper_process(env_t env, helper_thread_t *thread)
{
create_helper_process_custom_asid(env, thread, env->asid_pool);
}
NORETURN static void signal_helper_finished(seL4_CPtr local_endpoint, int val)
{
seL4_MessageInfo_t info = seL4_MessageInfo_new(0, 0, 0, 1);
seL4_SetMR(0, val);
while (true) {
seL4_Call(local_endpoint, info);
}
}
NORETURN static void helper_thread(int argc, char **argv)
{
helper_fn_t entry_point = (void *) atol(argv[0]);
seL4_CPtr local_endpoint = (seL4_CPtr) atol(argv[1]);
seL4_Word args[HELPER_THREAD_MAX_ARGS] = {0};
for (int i = 2; i < argc && i - 2 < HELPER_THREAD_MAX_ARGS; i++) {
assert(argv[i] != NULL);
args[i - 2] = atol(argv[i]);
}
/* run the thread */
int result = entry_point(args[0], args[1], args[2], args[3]);
signal_helper_finished(local_endpoint, result);
/* does not return */
}
NORETURN static void helper_process(int argc, char **argv)
{
uintptr_t new_tp = sel4runtime_move_initial_tls(process_tls);
assert(new_tp != (uintptr_t)NULL);
helper_thread(argc, argv);
}
extern uintptr_t sel4_vsyscall[];
void start_helper(env_t env, helper_thread_t *thread, helper_fn_t entry_point,
seL4_Word arg0, seL4_Word arg1, seL4_Word arg2, seL4_Word arg3)
{
UNUSED int error;
seL4_CPtr local_endpoint;
if (thread->is_process) {
/* copy the local endpoint */
cspacepath_t path;
vka_cspace_make_path(&env->vka, thread->local_endpoint.cptr, &path);
local_endpoint = sel4utils_copy_path_to_process(&thread->process, path);
} else {
local_endpoint = thread->local_endpoint.cptr;
}
sel4utils_create_word_args(thread->args_strings, thread->args, HELPER_THREAD_TOTAL_ARGS,
(seL4_Word) entry_point, local_endpoint,
arg0, arg1, arg2, arg3);
if (thread->is_process) {
thread->process.entry_point = (void *)helper_thread;
error = sel4utils_spawn_process_v(&thread->process, &env->vka, &env->vspace,
HELPER_THREAD_TOTAL_ARGS, thread->args, 0);
assert(error == 0);
/* sel4utils_spawn_process_v has created a stack frame that contains, amongst other
things, our arguments. Since we are going to be running a clone of this vspace
we would like to not call _start as this will result in initializing the C library
a second time. As we know the argument count and where argv will start we can
construct a register (or stack) layout that will allow us to pretend to be doing
a function call to helper_thread. */
seL4_UserContext context = {};
uintptr_t argv_base = (uintptr_t)thread->process.thread.initial_stack_pointer + sizeof(long);
uintptr_t aligned_stack_pointer = ALIGN_DOWN((uintptr_t)thread->process.thread.initial_stack_pointer,
STACK_CALL_ALIGNMENT);
error = sel4utils_arch_init_context_with_args((sel4utils_thread_entry_fn)helper_process,
(void *)HELPER_THREAD_TOTAL_ARGS,
(void *)argv_base, NULL, false, (void *)aligned_stack_pointer,
&context, &env->vka, &env->vspace, &thread->process.vspace);
assert(error == 0);
error = seL4_TCB_WriteRegisters(thread->process.thread.tcb.cptr, 1, 0, sizeof(seL4_UserContext) / sizeof(seL4_Word),
&context);
assert(error == 0);
} else {
error = sel4utils_start_thread(&thread->thread, (sel4utils_thread_entry_fn)helper_thread,
(void *) HELPER_THREAD_TOTAL_ARGS, (void *) thread->args, 1);
assert(error == 0);
}
}
void cleanup_helper(env_t env, helper_thread_t *thread)
{
seL4_TCB_Suspend(thread->thread.tcb.cptr);
vka_free_object(&env->vka, &thread->local_endpoint);
if (thread->is_process) {
/* free the regions (no need to unmap, as the
* entry address space / cspace is being destroyed */
for (int i = 0; i < thread->num_regions; i++) {
vspace_free_reservation(&thread->process.vspace, thread->regions[i].reservation);
}
thread->process.fault_endpoint.cptr = 0;
sel4utils_destroy_process(&thread->process, &env->vka);
} else {
sel4utils_clean_up_thread(&env->vka, &env->vspace, &thread->thread);
}
}
void create_helper_thread(env_t env, helper_thread_t *thread)
{
create_helper_thread_custom_stack(env, thread, BYTES_TO_4K_PAGES(CONFIG_SEL4UTILS_STACK_SIZE));
}
void create_helper_thread_custom_stack(env_t env, helper_thread_t *thread, size_t stack_pages)
{
UNUSED int error;
error = vka_alloc_endpoint(&env->vka, &thread->local_endpoint);
assert(error == 0);
thread->is_process = false;
thread->fault_endpoint = env->endpoint;
seL4_Word data = api_make_guard_skip_word(seL4_WordBits - env->cspace_size_bits);
sel4utils_thread_config_t config = thread_config_default(&env->simple, env->cspace_root, data, env->endpoint,
OUR_PRIO - 1);
config = thread_config_stack_size(config, stack_pages);
error = sel4utils_configure_thread_config(&env->vka, &env->vspace, &env->vspace,
config, &thread->thread);
assert(error == 0);
}
int wait_for_helper(helper_thread_t *thread)
{
seL4_Word badge;
api_recv(thread->local_endpoint.cptr, &badge, thread->thread.reply.cptr);
return seL4_GetMR(0);
}
void set_helper_priority(env_t env, helper_thread_t *thread, seL4_Word prio)
{
UNUSED int error;
error = seL4_TCB_SetPriority(thread->thread.tcb.cptr, env->tcb, prio);
assert(error == seL4_NoError);
}
void set_helper_mcp(env_t env, helper_thread_t *thread, seL4_Word mcp)
{
UNUSED int error;
error = seL4_TCB_SetMCPriority(thread->thread.tcb.cptr, env->tcb, mcp);
assert(error == seL4_NoError);
}
void set_helper_affinity(UNUSED env_t env, helper_thread_t *thread, seL4_Word affinity)
{
#ifdef CONFIG_KERNEL_MCS
seL4_Time timeslice = CONFIG_BOOT_THREAD_TIME_SLICE * US_IN_S;
int error = seL4_SchedControl_Configure(simple_get_sched_ctrl(&env->simple, affinity),
thread->thread.sched_context.cptr,
timeslice, timeslice, 0, 0);
ZF_LOGF_IF(error, "Failed to configure scheduling context");
#elif CONFIG_MAX_NUM_NODES > 1
int error = seL4_TCB_SetAffinity(thread->thread.tcb.cptr, affinity);
ZF_LOGF_IF(error, "Failed to set tcb affinity");
#endif
}
seL4_CPtr get_helper_tcb(helper_thread_t *thread)
{
return thread->thread.tcb.cptr;
}
seL4_CPtr get_helper_reply(helper_thread_t *thread)
{
return thread->thread.reply.cptr;
}
seL4_CPtr get_helper_sched_context(helper_thread_t *thread)
{
return thread->thread.sched_context.cptr;
}
uintptr_t get_helper_ipc_buffer_addr(helper_thread_t *thread)
{
return thread->thread.ipc_buffer_addr;
}
uintptr_t get_helper_initial_stack_pointer(helper_thread_t *thread)
{
return (uintptr_t)thread->thread.initial_stack_pointer;
}
static void sel4test_send_time_request(seL4_CPtr ep, uint64_t ns, sel4test_output_t request_type,
timeout_type_t timeout_type)
{
seL4_MessageInfo_t tag;
seL4_SetMR(0, request_type);
switch (request_type) {
case SEL4TEST_TIME_TIMEOUT:
seL4_SetMR(1, timeout_type);
sel4utils_64_set_mr(2, ns);
tag = seL4_MessageInfo_new(0, 0, 0, (seL4_Uint32) SEL4UTILS_64_WORDS + 2);
break;
case SEL4TEST_TIME_TIMESTAMP:
case SEL4TEST_TIME_RESET:
tag = seL4_MessageInfo_new(0, 0, 0, 1);
break;
default:
ZF_LOGE("Invalid time request\n");
break;
}
seL4_Call(ep, tag);
}
void sleep_busy(env_t env, uint64_t ns)
{
uint64_t start = sel4test_timestamp(env);
uint64_t now = sel4test_timestamp(env);
int same = 0;
while (now < start + ns) {
if (now == start) {
same++;
if (same == 10000) {
ZF_LOGE("Timer hasn't moved in 10000 iterations, are you handling interrupts?");
}
} else {
same = 0;
}
now = sel4test_timestamp(env);
}
}
inline void sel4test_sleep(env_t env, uint64_t ns)
{
/*
* sleep is meant to block the calling thread for at least @ns. RPC costs and
* delivering timer notifications are not accounted for. Due to the fact that
* sleep requests are RPC calls on the same env->ep, only one test can request a sleep
* on a time. It is possible, however, that 2 (or more) threads request a sleep,
* being serialised, and wait on the same env->timer_notification at the same time,
* in which case the first thread in the queue will only be notified and not the
* other(s). This is a limitation, and the current interface won't handle it. Only
* one thread can request/wait/sleep/wakeup on a time.
*/
sel4test_send_time_request(env->endpoint, ns, SEL4TEST_TIME_TIMEOUT, TIMEOUT_RELATIVE);
/* The tests have a timer_notification that they can wait on by default.
* sel4-driver will notify us on timer_notification when it gets a timer interrupt
*/
seL4_Wait(env->timer_notification.cptr, NULL);
}
inline void sel4test_periodic_start(env_t env, uint64_t ns)
{
sel4test_send_time_request(env->endpoint, ns, SEL4TEST_TIME_TIMEOUT, TIMEOUT_PERIODIC);
}
uint64_t sel4test_timestamp(env_t env)
{
/*
* Request a timestamp from sel4test-driver. The request is sent over the fault ep,
* and, being synchronous, sel4test-driver sends back the timestamp in the RPC reply.
* RPC costs are not accounted for.
*/
uint64_t time = 0;
sel4test_send_time_request(env->endpoint, 0, SEL4TEST_TIME_TIMESTAMP, 0);
time = sel4utils_64_get_mr(1);
return time;
}
inline void sel4test_timer_reset(env_t env)
{
sel4test_send_time_request(env->endpoint, 0, SEL4TEST_TIME_RESET, 0);
}
inline void sel4test_ntfn_timer_wait(env_t env)
{
seL4_Wait(env->timer_notification.cptr, NULL);
}
int set_helper_sched_params(UNUSED env_t env, UNUSED helper_thread_t *thread, UNUSED uint64_t budget,
UNUSED uint64_t period, UNUSED seL4_Word badge)
{
seL4_Word refills = 0;
if (budget < period) {
#ifdef CONFIG_KERNEL_MCS
refills = seL4_MaxExtraRefills(seL4_MinSchedContextBits);
#endif
}
return api_sched_ctrl_configure(simple_get_sched_ctrl(&env->simple, 0),
thread->thread.sched_context.cptr,
budget, period, refills, badge);
}
int create_passive_thread(env_t env, helper_thread_t *passive, helper_fn_t fn, seL4_CPtr ep,
seL4_Word arg1, seL4_Word arg2, seL4_Word arg3)
{
create_helper_thread(env, passive);
return start_passive_thread(env, passive, fn, ep, arg1, arg2, arg3);
}
int start_passive_thread(env_t env, helper_thread_t *passive, helper_fn_t fn, seL4_CPtr ep,
seL4_Word arg1, seL4_Word arg2, seL4_Word arg3)
{
start_helper(env, passive, fn, ep, arg1, arg2, arg3);
/* Wait for helper to signal it has initialised */
ZF_LOGD("Wait for passive thread to init");
seL4_Wait(ep, NULL);
ZF_LOGD("Done");
/* convert to passive */
return api_sc_unbind(passive->thread.sched_context.cptr);
}
int restart_after_syscall(env_t env, helper_thread_t *helper)
{
/* save and resume helper->*/
seL4_UserContext regs;
int error = seL4_TCB_ReadRegisters(helper->thread.tcb.cptr, false, 0,
sizeof(seL4_UserContext) / sizeof(seL4_Word), &regs);
test_eq(error, seL4_NoError);
/* skip the call */
sel4utils_set_instruction_pointer(&regs, sel4utils_get_instruction_pointer(regs) + ARCH_SYSCALL_INSTRUCTION_SIZE);
error = seL4_TCB_WriteRegisters(helper->thread.tcb.cptr, true, 0,
sizeof(seL4_UserContext) / sizeof(seL4_Word), &regs);
test_eq(error, seL4_NoError);
return 0;
}
void set_helper_tfep(env_t env, helper_thread_t *thread, seL4_CPtr tfep)
{
ZF_LOGF_IF(!config_set(CONFIG_KERNEL_MCS), "Unsupported on non MCS kernel");
#ifdef CONFIG_KERNEL_MCS
int error = seL4_TCB_SetTimeoutEndpoint(thread->thread.tcb.cptr, tfep);
if (error != seL4_NoError) {
ZF_LOGF("Failed to set tfep\n");
}
#endif
}