toaruos/kernel/arch/aarch64/arch.c
2022-08-06 18:24:06 +09:00

446 lines
14 KiB
C

/**
* @file kernel/arch/aarch64/arch.c
* @brief Global functions with arch-specific implementations.
*
* @copyright
* This file is part of ToaruOS and is released under the terms
* of the NCSA / University of Illinois License - see LICENSE.md
* Copyright (C) 2021-2022 K. Lange
*/
#include <stdint.h>
#include <kernel/process.h>
#include <kernel/string.h>
#include <kernel/printf.h>
#include <kernel/spinlock.h>
#include <kernel/mmu.h>
#include <kernel/arch/aarch64/regs.h>
#include <kernel/arch/aarch64/gic.h>
/**
* @brief Enter userspace.
*
* Called by process startup.
* Does not return.
*
* @param entrypoint Address to "return" to in userspace.
* @param argc Number of arguments to provide to the new process.
* @param argv Argument array to pass to the new process; make sure this is user-accessible!
* @param envp Environment strings array
* @param stack Userspace stack address.
*/
void arch_enter_user(uintptr_t entrypoint, int argc, char * argv[], char * envp[], uintptr_t stack) {
asm volatile(
"msr ELR_EL1, %0\n" /* entrypoint */
"msr SP_EL0, %1\n" /* stack */
"msr SPSR_EL1, %2\n" /* EL 0 */
::
"r"(entrypoint), "r"(stack), "r"(0));
register uint64_t x0 __asm__("x0") = argc;
register uint64_t x1 __asm__("x1") = (uintptr_t)argv;
register uint64_t x2 __asm__("x2") = (uintptr_t)envp;
register uint64_t x4 __asm__("x4") = (uintptr_t)this_core->sp_el1;
asm volatile(
"mov sp, x4\n"
"eret" :: "r"(x0), "r"(x1), "r"(x2), "r"(x4));
__builtin_unreachable();
}
static void _kill_it(uintptr_t addr, const char * action, const char * desc, size_t size) {
dprintf("core %d (pid=%d %s): invalid stack for signal %s (%#zx '%s' %zu)\n",
this_core->cpu_id, this_core->current_process->id, this_core->current_process->name, action, addr, desc, size);
task_exit(((128 + SIGSEGV) << 8) | SIGSEGV);
}
#define PUSH(stack, type, item) do { \
stack -= sizeof(type); \
if (!mmu_validate_user_pointer((void*)(uintptr_t)stack,sizeof(type),MMU_PTR_WRITE)) \
_kill_it((uintptr_t)stack,"entry",#item,sizeof(type)); \
*((volatile type *) stack) = item; \
} while (0)
#define POP(stack, type, item) do { \
if (!mmu_validate_user_pointer((void*)(uintptr_t)stack,sizeof(type),0)) \
_kill_it((uintptr_t)stack,"return",#item,sizeof(type)); \
item = *((volatile type *) stack); \
stack += sizeof(type); \
} while (0)
int arch_return_from_signal_handler(struct regs *r) {
uintptr_t spsr;
uintptr_t sp = r->user_sp;
/* Restore floating point */
POP(sp, uintptr_t, this_core->current_process->thread.context.saved[13]);
POP(sp, uintptr_t, this_core->current_process->thread.context.saved[12]);
for (int i = 0; i < 64; ++i) {
POP(sp, uint64_t, this_core->current_process->thread.fp_regs[63-i]);
}
arch_restore_floating((process_t*)this_core->current_process);
POP(sp, sigset_t, this_core->current_process->blocked_signals);
long originalSignal;
POP(sp, long, originalSignal);
/* Interrupt system call status */
POP(sp, long, this_core->current_process->interrupted_system_call);
/* Process state */
POP(sp, uintptr_t, spsr);
this_core->current_process->thread.context.saved[11] = (spsr & 0xf0000000);
asm volatile ("msr SPSR_EL1, %0" :: "r"(this_core->current_process->thread.context.saved[11]));
POP(sp, uintptr_t, this_core->current_process->thread.context.saved[10]);
asm volatile ("msr ELR_EL1, %0" :: "r"(this_core->current_process->thread.context.saved[10]));
/* Interrupt context registers */
POP(sp, struct regs, *r);
asm volatile ("msr SP_EL0, %0" :: "r"(r->user_sp));
return originalSignal;
}
/**
* @brief Enter a userspace signal handler.
*
* Similar to @c arch_enter_user but also setups up magic return addresses.
*
* Since signal handlers do to take complicated argument arrays, this only
* supplies a @p signum argument.
*
* Does not return.
*
* @param entrypoint Userspace address of the signal handler, set by the process.
* @param signum Signal number that caused this entry.
*/
void arch_enter_signal_handler(uintptr_t entrypoint, int signum, struct regs *r) {
uintptr_t sp = (r->user_sp - 128) & 0xFFFFFFFFFFFFFFF0;
/* Save essential registers */
PUSH(sp, struct regs, *r);
/* Save context that wasn't pushed above... */
asm volatile ("mrs %0, ELR_EL1" : "=r"(this_core->current_process->thread.context.saved[10]));
PUSH(sp, uintptr_t, this_core->current_process->thread.context.saved[10]);
asm volatile ("mrs %0, SPSR_EL1" : "=r"(this_core->current_process->thread.context.saved[11]));
PUSH(sp, uintptr_t, this_core->current_process->thread.context.saved[11]);
PUSH(sp, long, this_core->current_process->interrupted_system_call);
this_core->current_process->interrupted_system_call = 0;
PUSH(sp, long, signum);
PUSH(sp, sigset_t, this_core->current_process->blocked_signals);
struct signal_config * config = (struct signal_config*)&this_core->current_process->signals[signum];
this_core->current_process->blocked_signals |= config->mask | (config->flags & SA_NODEFER ? 0 : (1UL << signum));
/* Save floating point */
arch_save_floating((process_t*)this_core->current_process);
for (int i = 0; i < 64; ++i) {
PUSH(sp, uint64_t, this_core->current_process->thread.fp_regs[i]);
}
PUSH(sp, uintptr_t, this_core->current_process->thread.context.saved[12]);
PUSH(sp, uintptr_t, this_core->current_process->thread.context.saved[13]);
asm volatile(
"msr ELR_EL1, %0\n" /* entrypoint */
"msr SP_EL0, %1\n" /* stack */
"msr SPSR_EL1, %2\n" /* spsr from context */
::
"r"(entrypoint),
"r"(sp),
"r"(0));
register uint64_t x0 __asm__("x0") = signum;
register uint64_t x30 __asm__("x30") = 0x8DEADBEEF;
register uint64_t x4 __asm__("x4") = (uintptr_t)this_core->sp_el1;
asm volatile(
"mov sp, x4\n"
"eret\nnop\nnop" :: "r"(x0), "r"(x30), "r"(x4));
__builtin_unreachable();
}
/**
* @brief Save FPU registers for this thread.
*/
void arch_restore_floating(process_t * proc) {
asm volatile (
"ldr q0 , [%0, #(0 * 16)]\n"
"ldr q1 , [%0, #(1 * 16)]\n"
"ldr q2 , [%0, #(2 * 16)]\n"
"ldr q3 , [%0, #(3 * 16)]\n"
"ldr q4 , [%0, #(4 * 16)]\n"
"ldr q5 , [%0, #(5 * 16)]\n"
"ldr q6 , [%0, #(6 * 16)]\n"
"ldr q7 , [%0, #(7 * 16)]\n"
"ldr q8 , [%0, #(8 * 16)]\n"
"ldr q9 , [%0, #(9 * 16)]\n"
"ldr q10, [%0, #(10 * 16)]\n"
"ldr q11, [%0, #(11 * 16)]\n"
"ldr q12, [%0, #(12 * 16)]\n"
"ldr q13, [%0, #(13 * 16)]\n"
"ldr q14, [%0, #(14 * 16)]\n"
"ldr q15, [%0, #(15 * 16)]\n"
"ldr q16, [%0, #(16 * 16)]\n"
"ldr q17, [%0, #(17 * 16)]\n"
"ldr q18, [%0, #(18 * 16)]\n"
"ldr q19, [%0, #(19 * 16)]\n"
"ldr q20, [%0, #(20 * 16)]\n"
"ldr q21, [%0, #(21 * 16)]\n"
"ldr q22, [%0, #(22 * 16)]\n"
"ldr q23, [%0, #(23 * 16)]\n"
"ldr q24, [%0, #(24 * 16)]\n"
"ldr q25, [%0, #(25 * 16)]\n"
"ldr q26, [%0, #(26 * 16)]\n"
"ldr q27, [%0, #(27 * 16)]\n"
"ldr q28, [%0, #(28 * 16)]\n"
"ldr q29, [%0, #(29 * 16)]\n"
"ldr q30, [%0, #(30 * 16)]\n"
"ldr q31, [%0, #(31 * 16)]\n"
"msr fpcr, %1\n"
"msr fpsr, %2\n"
::"r"(&proc->thread.fp_regs),
"r"(proc->thread.context.saved[12]),
"r"(proc->thread.context.saved[13])
);
}
/**
* @brief Restore FPU registers for this thread.
*/
void arch_save_floating(process_t * proc) {
asm volatile (
"str q0 , [%2, #(0 * 16)]\n"
"str q1 , [%2, #(1 * 16)]\n"
"str q2 , [%2, #(2 * 16)]\n"
"str q3 , [%2, #(3 * 16)]\n"
"str q4 , [%2, #(4 * 16)]\n"
"str q5 , [%2, #(5 * 16)]\n"
"str q6 , [%2, #(6 * 16)]\n"
"str q7 , [%2, #(7 * 16)]\n"
"str q8 , [%2, #(8 * 16)]\n"
"str q9 , [%2, #(9 * 16)]\n"
"str q10, [%2, #(10 * 16)]\n"
"str q11, [%2, #(11 * 16)]\n"
"str q12, [%2, #(12 * 16)]\n"
"str q13, [%2, #(13 * 16)]\n"
"str q14, [%2, #(14 * 16)]\n"
"str q15, [%2, #(15 * 16)]\n"
"str q16, [%2, #(16 * 16)]\n"
"str q17, [%2, #(17 * 16)]\n"
"str q18, [%2, #(18 * 16)]\n"
"str q19, [%2, #(19 * 16)]\n"
"str q20, [%2, #(20 * 16)]\n"
"str q21, [%2, #(21 * 16)]\n"
"str q22, [%2, #(22 * 16)]\n"
"str q23, [%2, #(23 * 16)]\n"
"str q24, [%2, #(24 * 16)]\n"
"str q25, [%2, #(25 * 16)]\n"
"str q26, [%2, #(26 * 16)]\n"
"str q27, [%2, #(27 * 16)]\n"
"str q28, [%2, #(28 * 16)]\n"
"str q29, [%2, #(29 * 16)]\n"
"str q30, [%2, #(30 * 16)]\n"
"str q31, [%2, #(31 * 16)]\n"
"mrs %0, fpcr\n"
"mrs %1, fpsr\n"
:
"=r"(proc->thread.context.saved[12]),
"=r"(proc->thread.context.saved[13])
:"r"(&proc->thread.fp_regs)
:"memory");
}
/**
* @brief Prepare for a fatal event by stopping all other cores.
*/
void arch_fatal_prepare(void) {
if (processor_count > 1) {
gic_send_sgi(2,-1);
}
}
/**
* @brief Halt all processors, including this one.
* @see arch_fatal_prepare
*/
void arch_fatal(void) {
arch_fatal_prepare();
while (1) {
asm volatile ("wfi");
}
}
void arch_wakeup_others(void) {
#if 1
for (int i = 0; i < processor_count; i++) {
if (i == this_core->cpu_id) continue;
if (!processor_local_data[i].current_process || (processor_local_data[i].current_process != processor_local_data[i].kernel_idle_task)) continue;
gic_send_sgi(1,i);
}
#endif
}
/**
* @brief Reboot the computer.
*
* At least on 'virt', there's a system control
* register we can write to to reboot or at least
* do a full shutdown.
*/
long arch_reboot(void) {
return 0;
}
void aarch64_regs(struct regs *r) {
#define reg(a,b) printf(" X%02d=0x%016zx X%02d=0x%016zx\n",a,r->x ## a, b, r->x ## b)
reg(0,1);
reg(2,3);
reg(4,5);
reg(6,7);
reg(8,9);
reg(10,11);
reg(12,13);
reg(14,15);
reg(16,17);
reg(18,19);
reg(20,21);
reg(22,23);
reg(24,25);
reg(26,27);
reg(28,29);
printf(" X30=0x%016zx SP=0x%016zx\n", r->x30, r->user_sp);
#undef reg
}
void aarch64_context(process_t * proc) {
printf(" SP=0x%016zx BP(x29)=0x%016zx\n", proc->thread.context.sp, proc->thread.context.bp);
printf(" IP=0x%016zx TLSBASE=0x%016zx\n", proc->thread.context.ip, proc->thread.context.tls_base);
printf(" X19=0x%016zx X20=%016zx\n", proc->thread.context.saved[0], proc->thread.context.saved[1]);
printf(" X21=0x%016zx X22=%016zx\n", proc->thread.context.saved[2], proc->thread.context.saved[3]);
printf(" X23=0x%016zx X24=%016zx\n", proc->thread.context.saved[4], proc->thread.context.saved[5]);
printf(" X25=0x%016zx X26=%016zx\n", proc->thread.context.saved[6], proc->thread.context.saved[7]);
printf(" X27=0x%016zx X28=%016zx\n", proc->thread.context.saved[8], proc->thread.context.saved[9]);
printf(" ELR=0x%016zx SPSR=%016zx\n", proc->thread.context.saved[10], proc->thread.context.saved[11]);
printf("fpcr=0x%016zx fpsr=%016zx\n", proc->thread.context.saved[12], proc->thread.context.saved[13]);
}
/* Syscall parameter accessors */
void arch_syscall_return(struct regs * r, long retval) { r->x0 = retval; }
long arch_syscall_number(struct regs * r) { return r->x0; }
long arch_syscall_arg0(struct regs * r) { return r->x1; }
long arch_syscall_arg1(struct regs * r) { return r->x2; }
long arch_syscall_arg2(struct regs * r) { return r->x3; }
long arch_syscall_arg3(struct regs * r) { return r->x4; }
long arch_syscall_arg4(struct regs * r) { return r->x5; }
long arch_stack_pointer(struct regs * r) { return r->user_sp; }
long arch_user_ip(struct regs * r) { return r->x30; /* TODO this is wrong, this needs to come from ELR but we don't have that */ }
/* No port i/o on arm, but these are still littered around some
* drivers we need to remove... */
unsigned short inports(unsigned short _port) { return 0; }
unsigned int inportl(unsigned short _port) { return 0; }
unsigned char inportb(unsigned short _port) { return 0; }
void inportsm(unsigned short port, unsigned char * data, unsigned long size) {
}
void outports(unsigned short _port, unsigned short _data) {
}
void outportl(unsigned short _port, unsigned int _data) {
}
void outportb(unsigned short _port, unsigned char _data) {
}
void outportsm(unsigned short port, unsigned char * data, unsigned long size) {
}
void arch_framebuffer_initialize(void) {
/* I'm not sure we have any options here...
* lfbvideo calls this expecting it to fill in information
* on a preferred video mode; maybe dtb has that? */
}
char * _arch_args = NULL;
const char * arch_get_cmdline(void) {
/* this should be available from dtb directly as a string */
extern char * _arch_args;
return _arch_args ? _arch_args : "";
}
const char * arch_get_loader(void) {
return "";
}
/* These should probably assembly. */
void arch_enter_tasklet(void) {
asm volatile (
"ldp x0, x1, [sp], #16\n"
"br x1\n" ::: "memory");
__builtin_unreachable();
}
static spin_lock_t deadlock_lock = { 0 };
void _spin_panic(const char * lock_name, spin_lock_t * target) {
arch_fatal_prepare();
while (__sync_lock_test_and_set(deadlock_lock.latch, 0x01));
dprintf("core %d took over five seconds waiting to acquire %s (owner=%d in %s)\n",
this_core->cpu_id, lock_name, target->owner - 1, target->func);
//arch_dump_traceback();
__sync_lock_release(deadlock_lock.latch);
arch_fatal();
}
void arch_spin_lock_acquire(const char * name, spin_lock_t * target, const char * func) {
#if 0
uint64_t expire = arch_perf_timer() + 5000000UL * arch_cpu_mhz();
#endif
/* "loss of an exclusive monitor" is one of the things that causes an "event",
* so we spin on wfe to try to load-acquire the latch */
asm volatile (
"sevl\n" /* And to avoid multiple jumps, we put the wfe first, so sevl will slide past the first one */
"1:\n"
" wfe\n"
#if 0
);
/* Yes, we can splice these assembly snippets with the clock check, this works fine.
* If we've been trying to load the latch for five seconds, panic. */
if (arch_perf_timer() > expire) {
_spin_panic(name, target);
}
asm volatile (
#endif
"2:\n"
" ldaxr w2, [ %1 ]\n" /* Acquire exclusive monitor and load latch value */
" cbnz w2, 1b\n" /* If the latch value isn't 0, someone else owns the lock, go back to wfe and wait for them to release it */
" stxr w2, %0, [ %1 ]\n" /* Store our core number as the latch value. */
" cbnz w2, 2b\n" /* If we failed to exclusively store, try to load again */
::"r"(this_core->cpu_id+1),"r"(target->latch) : "x2","cc","memory");
/* Set these for compatibility because there's at least one place we check them;
* TODO: just use the latch value since we're setting it to the same thing anyway? */
target->owner = this_core->cpu_id + 1;
/* This is purely for debugging */
target->func = func;
}
void arch_spin_lock_release(spin_lock_t * target) {
/* Clear owner debug data */
target->owner = 0;
target->func = NULL;
/* Release the exclusive monitor and clear the latch value */
__atomic_store_n(target->latch, 0, __ATOMIC_RELEASE);
}