| // Copyright 2016 The Fuchsia Authors |
| // Copyright (c) 2008 Travis Geiselbrecht |
| // |
| // Use of this source code is governed by a MIT-style |
| // license that can be found in the LICENSE file or at |
| // https://opensource.org/licenses/MIT |
| |
| #include <arch/arm64.h> |
| #include <arch/arm64/mp.h> |
| #include <debug.h> |
| #include <kernel/thread.h> |
| #include <stdlib.h> |
| #include <string.h> |
| #include <sys/types.h> |
| #include <trace.h> |
| |
| #define LOCAL_TRACE 0 |
| |
| // Register state layout used by arm64_context_switch(). |
| struct context_switch_frame { |
| uint64_t tpidr_el0; |
| uint64_t tpidrro_el0; |
| uint64_t r19; |
| uint64_t r20; |
| uint64_t r21; |
| uint64_t r22; |
| uint64_t r23; |
| uint64_t r24; |
| uint64_t r25; |
| uint64_t r26; |
| uint64_t r27; |
| uint64_t r28; |
| uint64_t r29; |
| uint64_t lr; |
| }; |
| |
| // assert that the context switch frame is a multiple of 16 to maintain |
| // stack alignment requirements per ABI |
| static_assert(sizeof(context_switch_frame) % 16 == 0, ""); |
| |
| extern void arm64_context_switch(addr_t* old_sp, addr_t new_sp); |
| |
| void arch_thread_initialize(thread_t* t, vaddr_t entry_point) { |
| // zero out the entire arch state |
| t->arch = {}; |
| |
| // create a default stack frame on the stack |
| vaddr_t stack_top = t->stack.top; |
| |
| // make sure the top of the stack is 16 byte aligned for EABI compliance |
| stack_top = ROUNDDOWN(stack_top, 16); |
| t->stack.top = stack_top; |
| |
| struct context_switch_frame* frame = (struct context_switch_frame*)(stack_top); |
| frame--; |
| |
| // fill in the entry point |
| frame->lr = entry_point; |
| |
| // This is really a global (boot-time) constant value. |
| // But it's stored in each thread struct to satisfy the |
| // compiler ABI (TPIDR_EL1 + ZX_TLS_STACK_GUARD_OFFSET). |
| t->arch.stack_guard = get_current_thread()->arch.stack_guard; |
| |
| // set the stack pointer |
| t->arch.sp = (vaddr_t)frame; |
| #if __has_feature(safe_stack) |
| t->arch.unsafe_sp = |
| ROUNDDOWN(t->stack.unsafe_base + t->stack.size, 16); |
| #endif |
| |
| // Initialize the debug state to a valid initial state. |
| for (size_t i = 0; i < ARM64_MAX_HW_BREAKPOINTS; i++) { |
| t->arch.debug_state.hw_bps[i].dbgbcr =(0b10u << ARM64_DBGBCR_PMC_SHIFT) | ARM64_DBGBCR_BAS; |
| t->arch.debug_state.hw_bps[i].dbgbvr = 0; |
| } |
| } |
| |
| __NO_SAFESTACK void arch_thread_construct_first(thread_t* t) { |
| // Propagate the values from the fake arch_thread that the thread |
| // pointer points to now (set up in start.S) into the real thread |
| // structure being set up now. |
| thread_t* fake = get_current_thread(); |
| t->arch.stack_guard = fake->arch.stack_guard; |
| t->arch.unsafe_sp = fake->arch.unsafe_sp; |
| |
| // make sure the thread saves a copy of the current cpu pointer |
| t->arch.current_percpu_ptr = arm64_read_percpu_ptr(); |
| |
| // Force the thread pointer immediately to the real struct. This way |
| // our callers don't have to avoid safe-stack code or risk losing track |
| // of the unsafe_sp value. The caller's unsafe_sp value is visible at |
| // TPIDR_EL1 + ZX_TLS_UNSAFE_SP_OFFSET as expected, though TPIDR_EL1 |
| // happens to have changed. (We're assuming that the compiler doesn't |
| // decide to cache the TPIDR_EL1 value across this function call, which |
| // would be pointless since it's just one instruction to fetch it afresh.) |
| set_current_thread(t); |
| } |
| |
| __NO_SAFESTACK void arch_context_switch(thread_t* oldthread, |
| thread_t* newthread) { |
| LTRACEF("old %p (%s), new %p (%s)\n", oldthread, oldthread->name, newthread, newthread->name); |
| DSB; /* broadcast tlb operations in case the thread moves to another cpu */ |
| |
| /* set the current cpu pointer in the new thread's structure so it can be |
| * restored on exception entry. |
| */ |
| newthread->arch.current_percpu_ptr = arm64_read_percpu_ptr(); |
| |
| arm64_fpu_context_switch(oldthread, newthread); |
| arm64_debug_state_context_switch(oldthread, newthread); |
| arm64_context_switch(&oldthread->arch.sp, newthread->arch.sp); |
| } |
| |
| void arch_dump_thread(thread_t* t) { |
| if (t->state != THREAD_RUNNING) { |
| dprintf(INFO, "\tarch: "); |
| dprintf(INFO, "sp 0x%lx\n", t->arch.sp); |
| } |
| } |
| |
| void* arch_thread_get_blocked_fp(struct thread* t) { |
| if (!WITH_FRAME_POINTERS) |
| return nullptr; |
| |
| struct context_switch_frame* frame = (struct context_switch_frame*)t->arch.sp; |
| |
| return (void*)frame->r29; |
| } |
| |
| void arm64_debug_state_context_switch(thread *old_thread, thread *new_thread) { |
| // If the new thread has debug state, then install it, replacing the current contents. |
| if (unlikely(new_thread->arch.track_debug_state)) { |
| arm64_write_hw_debug_regs(&new_thread->arch.debug_state); |
| arm64_enable_debug_state(); |
| return; |
| } |
| |
| // If the old thread had debug state running and the new one doesn't use it, disable the |
| // debug capabilities. We don't need to clear the state because if a new thread being |
| // scheduled needs them, then it will overwrite the state. |
| if (unlikely(old_thread->arch.track_debug_state)) { |
| arm64_disable_debug_state(); |
| } |
| } |