| /* |
| * 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, ®s); |
| if (error) { |
| return -1; |
| } |
| |
| for (int i = 0; i < 2; ++i) { |
| sel4utils_set_instruction_pointer(®s, i); |
| error = seL4_TCB_WriteRegisters(tcb1, 0, 0, 1, ®s); |
| |
| /* 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, ®s); |
| |
| /* 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), ®s); |
| test_eq(error, seL4_NoError); |
| |
| /* skip the call */ |
| sel4utils_set_instruction_pointer(®s, 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), ®s); |
| 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 |
| } |