/* $NetBSD: locore.S,v 1.9 2019/06/16 07:42:52 maxv Exp $ */ /*- * Copyright (c) 2014 The NetBSD Foundation, Inc. * All rights reserved. * * This code is derived from software contributed to The NetBSD Foundation * by Matt Thomas of 3am Software Foundry. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE * POSSIBILITY OF SUCH DAMAGE. */ #include #include "assym.h" .globl _C_LABEL(exception_userexit) .globl _C_LABEL(cpu_Debugger_insn) ENTRY_NP(start) // We get loaded and starting running at or near 0, not where we // should be. We need to construct an initial PDETAB #ifdef _LP64 li t0, SR_U64|SR_S64 li t1, SR_IM|SR_VM|SR_EI csrs sstatus, t0 #else li t1, SR_IM|SR_VM|SR_U64|SR_S64|R_EI #endif csrc sstatus, t1 li s11, VM_MAX_KERNEL_ADDRESS li s10, PAGE_SIZE li s9, USPACE /* * XXX XXX XXX: This is completely broken and wrong, we should map only * the kernel sections, and the direct map should be mapped later in C. */ #if 0 #if 0 // The kernel doesn't use gp/_gp since we'd have to reload it on // each exception. PTR_LA gp, _C_LABEL(_gp) #endif PTR_LA a0, _C_LABEL(__bss_start) PTR_LA s1, _C_LABEL(_end) li a1, 0 add s1, s1, s10 // PAGE_SIZE addi s1, s1, -1 // -1 == PAGE_MASK neg a1, a0 // -PAGE_SIZE and s1, s1, a1 // s1 is page aligned end of kernel // s1 = uarea add s2, s1, s9 // s2 = first PDE page #ifdef _LP64 add s3, s2, s10 // s3 = second PDE page (RV64 only) #else move s3, 22 #endif add s4, s3, s10 // s4 = first kernel PTE page add s5, s1, s9 // s5 = kernel_end sub a2, s5, a0 call memset // zero through kernel_end // As a temporary hack, word 0 contains the amount of memory in MB INT_L a7, (zero) // load memory size slli a7, a7, (20-PGSHIFT) // convert MB to pages .L01: PTR_LA t0, physmem INT_S a7, (t0) // store it in physmem li t4, PTE_V | PTE_SX | PTE_SW | PTE_SR | PTE_G #ifdef _LP64 REG_S t4, 0(s2) // keep a mapping for the first 8GB. or t0, s3, t4 // point to next page or t0, t0, PTE_T // tranverse it. REG_S t0, -SZREG(s3) // store in highest first level PDE #endif #if (VM_MIN_KERNEL_ADDRESS >> XSEGSHIFT) != (VM_MAX_KERNEL_ADDRESS >> XSEGSHIFT) #error VM_MIN_KERNEL_ADDRESS not in same first level PDE as VM_MAX_KERNEL_ADDRESS #endif // We allocated the kernel first PTE page so let's insert in the // page table. For now, we assume it's in the same PDE page as the // direct-mapped memory. or t0, s4, t4 or t0, t0, PTE_T #if ((VM_MIN_KERNEL_ADDRESS >> SEGSHIFT) & (NPDEPG-1)) * SZREG li t1, ((VM_MIN_KERNEL_ADDRESS >> SEGSHIFT) & (NPDEPG-1)) * SZREG add t1, t1, s3 REG_S t0, 0(t1) #else REG_S t0, 0(s3) #endif li t0, ((VM_MAX_KERNEL_ADDRESS >> SEGSHIFT) & (NPDEPG-1)) * SZREG add s3, s3, t0 srli a7, a7, (SEGSHIFT-PGSHIFT) // pages to segments li t3, NBSEG // load for ease // // Fill in the PDEs to direct map memory. // .Lfill: REG_S t4, 0(s3) // store PDE add t4, t4, t3 // advance PA in PDE to next segment add s3, s3, SZREG // advance to next PDE slot addi a7, a7, -1 // count down segment bgtz a6, .Lfill // loop if more #endif csrw sptbr, s1 // set the page table base li t0, SR_VM csrs sstatus, t0 // Enable VM // We should have a VM so let's start using our real addresses lui t0, %hi(.Lmmu_on) // load hi part of absolute address jr t0, %lo(.Lmmu_on) // jump to absolute address .Lmmu_on: // MMU is on! csrw sscratch, zero // zero in sscratch to mark kernel PTR_LA tp, _C_LABEL(lwp0) // put curlwp in tp PTR_LA a0, _C_LABEL(cpu_exception_handler) csrw stvec, a0 PTR_S s1, L_PCB(tp) // set uarea of lwp (already zeroed) addi sp, s2, -TF_LEN // switch to new stack PTR_S sp, L_MD_UTF(tp) // store pointer to empty trapframe PTR_LA t1, _C_LABEL(kernel_pmap_store) add t2, s2, s11 // PA -> VA PTR_S t2, PM_MD_PDETAB(t1) // VA of kernel PDETAB PTR_S s2, PM_MD_PTBR(t1) // PA of kernel PDETAB // Now we should ready to start initializing the kernel. PTR_LA a0, _C_LABEL(start) // kernel_start add a1, s5, s11 // kernel_end call _C_LABEL(init_riscv) // do MD startup tail _C_LABEL(main) // and transfer to main // not reached END(start) // // struct lwp *cpu_switchto(struct lwp *oldl, struct lwp *newl, bool returning); // ENTRY_NP(cpu_switchto) addi sp, sp, -TF_LEN // allocate trapframe beqz a0, .Lswitchto_newlwp // can skip saving oldl state? REG_S ra, TF_RA(sp) // save return address REG_S s0, TF_S0(sp) // save callee saved address REG_S s1, TF_S1(sp) // save callee saved address REG_S s2, TF_S2(sp) // save callee saved address REG_S s3, TF_S3(sp) // save callee saved address REG_S s4, TF_S4(sp) // save callee saved address REG_S s5, TF_S5(sp) // save callee saved address REG_S s6, TF_S6(sp) // save callee saved address REG_S s7, TF_S7(sp) // save callee saved address REG_S s8, TF_S8(sp) // save callee saved address REG_S s9, TF_S9(sp) // save callee saved address REG_S s10, TF_S10(sp) // save callee saved address REG_S s11, TF_S11(sp) // save callee saved address csrr t4, sstatus // get status for intr state REG_S t4, TF_SR(sp) // save it REG_S sp, L_MD_KTF(a0) // record trapframe pointer .Lswitchto_newlwp: csrrci t0, sstatus, SR_EI // # disable interrupts move tp, a1 // # put the new lwp in thread pointer PTR_L t1, L_CPU(tp) // # get curcpu PTR_S tp, CI_CURLWP(t1) // # update curcpu with the new curlwp REG_L sp, L_MD_KTF(tp) // # load its kernel stack pointer REG_L t4, TF_SR(sp) // # fetch status register csrw sstatus, t4 // # restore it (and interrupts?) REG_L s0, TF_S0(sp) // restore callee saved REG_L s1, TF_S1(sp) // restore callee saved REG_L s2, TF_S2(sp) // restore callee saved REG_L s3, TF_S3(sp) // restore callee saved REG_L s4, TF_S4(sp) // restore callee saved REG_L s5, TF_S5(sp) // restore callee saved REG_L s6, TF_S6(sp) // restore callee saved REG_L s7, TF_S7(sp) // restore callee saved REG_L s8, TF_S8(sp) // restore callee saved REG_L s9, TF_S9(sp) // restore callee saved REG_L s10, TF_S10(sp) // restore callee saved REG_L s11, TF_S11(sp) // restore callee saved REG_L ra, TF_RA(sp) // restore return address addi sp, sp, TF_LEN // remove trapframe // a0 = oldl // a1 = curcpu() // tp = newl ret END(cpu_switchto) ENTRY_NP(cpu_lwp_trampoline) move a1, tp // get new lwp call _C_LABEL(lwp_startup) // call lwp startup move a0, s1 // get saved arg jalr s0 // call saved func // If the saved func returns, we are returning to user land. j _C_LABEL(exception_userexit) END(cpu_lwp_trampoline) ENTRY_NP(cpu_fast_switchto_cleanup) INT_L t0, CI_MTX_COUNT(a1) // get mutex count REG_L ra, CALLFRAME_RA(sp) // get return address REG_L a0, CALLFRAME_S0(sp) // get pinned LWP addi t0, t0, 1 // increment mutex count INT_S t0, CI_MTX_COUNT(a1) // save it PTR_S zero, L_CTXSWTCH(a0) // clear l_ctxswitch addi sp, sp, CALLFRAME_SIZ // remove callframe #if IPL_SCHED != IPL_HIGH tail _C_LABEL(splhigh) // go back to IPL HIGH #else ret // just return #endif END(cpu_fast_switchto_cleanup) // // void cpu_fast_switchto(struct lwp *, int s); // ENTRY_NP(cpu_fast_switchto) addi sp, sp, -(TF_LEN + CALLFRAME_SIZ) REG_S a0, (TF_LEN + CALLFRAME_S0)(sp) REG_S ra, (TF_LEN + CALLFRAME_RA)(sp) PTR_LA t2, _C_LABEL(cpu_fast_switchto_cleanup) REG_S t2, TF_RA(sp) // return to someplace else REG_S s0, TF_S0(sp) // save callee saved register REG_S s1, TF_S1(sp) // save callee saved register REG_S s2, TF_S2(sp) // save callee saved register REG_S s3, TF_S3(sp) // save callee saved register REG_S s4, TF_S4(sp) // save callee saved register REG_S s5, TF_S5(sp) // save callee saved register REG_S s6, TF_S6(sp) // save callee saved register REG_S s7, TF_S7(sp) // save callee saved register REG_S s8, TF_S8(sp) // save callee saved register REG_S s9, TF_S9(sp) // save callee saved register REG_S s10, TF_S10(sp) // save callee saved register REG_S s11, TF_S11(sp) // save callee saved register csrr t4, sstatus // get status register (for intr state) REG_S t4, TF_SR(sp) // save it move s0, tp // remember curlwp move s1, sp // remember kernel stack #if 0 csrrci t0, sstatus, SR_EI // disable interrupts #endif PTR_L t1, L_CPU(tp) // get curcpu() PTR_S sp, L_MD_KTF(tp) // save trapframe ptr in oldlwp move tp, a0 // set thread pointer to newlwp PTR_S tp, CI_CURLWP(t1) // update curlwp PTR_L sp, L_MD_KTF(tp) // switch to its stack #if 0 csrw sstatus, t0 // reenable interrupts #endif call _C_LABEL(softint_dispatch) #if 0 csrrci t0, sstatus, SR_EI // disable interrupts #endif PTR_L t1, L_CPU(tp) // get curcpu() again move tp, s0 // return to pinned lwp PTR_S tp, CI_CURLWP(t1) // restore curlwp #if 0 csrw sstatus, t0 // reeanble interrupts #endif move sp, s1 // restore stack pointer REG_L ra, (TF_RA + CALLFRAME_RA)(sp) // get return address REG_L s0, TF_S0(sp) // restore register we used REG_L s1, TF_S1(sp) // restore register we used addi sp, sp, TF_LEN+CALLFRAME_SIZ // drop trapframe/callframe ret // return END(cpu_fast_switchto) // RISCV only has a simple exception handler handles both synchronous traps // and interrupts. ENTRY_NP(cpu_exception_handler) csrrw tp, sscratch, tp // swap scratch and thread pointer beqz tp, .Lexception_kernel // tp == 0, already on kernel stack // // The execption happened while user code was executing. We need to // get the pointer to the user trapframe from the LWP md area. Then we // save t1 and tp so we have a register to work with and to get curlwp // into tp. We also save the saved SP into the trapframe. // Upon entry on an exception from user, sscratch will contain curlwp. // REG_S sp, L_MD_USP(tp) // save user stack pointer temporarily PTR_L sp, L_MD_UTF(sp) // trapframe pointer loaded REG_S t1, TF_T1(sp) // save t1 REG_L t1, L_MD_USP(tp) // get user stack pointer REG_S t1, TF_SP(sp) // save thread pointer in trapframe csrrw t1, sscratch, zero // swap saved thread pointer with 0 REG_L t1, TF_TP(sp) // save thread pointer in trapframe li t1, 0 // indicate user exception j .Lexception_common // // The exception happened while we were already in the kernel. That // means tp already has curlwp and sp has the kernel stack pointer so // just need to restore it and then adjust it down for space for the // trap frame. We save t1 so we can use it the original sp into the // trapframe for use by the exception exiting code. // .Lexception_kernel: csrrw tp, sscratch, zero // get back our thread pointer addi sp, sp, -TF_LEN // allocate stack frame REG_S t1, TF_T1(sp) // save t1 addi t1, sp, TF_LEN REG_S t1, TF_SP(sp) // save SP li t1, 1 // indicate kernel exception .Lexception_common: // Now we save all the temporary registers into the trapframe since // they will most certainly be changed. REG_S ra, TF_RA(sp) // save return address REG_S gp, TF_GP(sp) // save gp REG_S a0, TF_A0(sp) // save a0 REG_S a1, TF_A1(sp) // save a1 REG_S a2, TF_A2(sp) // save a2 REG_S a3, TF_A3(sp) // save a3 REG_S a4, TF_A4(sp) // save a4 REG_S a5, TF_A5(sp) // save a5 REG_S a6, TF_A6(sp) // save a6 REG_S a7, TF_A7(sp) // save a7 REG_S t0, TF_T0(sp) // save t0 // t1 is already saved REG_S t2, TF_T2(sp) // save t2 REG_S t3, TF_T3(sp) // save t3 REG_S t4, TF_T4(sp) // save t4 REG_S t5, TF_T5(sp) // save t5 REG_S t6, TF_T6(sp) // save t6 // Now we get the move a0, sp // trapframe pointer csrr a1, sepc // get execption pc csrr a2, sstatus // get status csrr a3, scause // get cause REG_S a1, TF_PC(sp) INT_S a2, TF_SR(sp) INT_S a3, TF_CAUSE(sp) // save cause // Now we've saved the trapfame, the cause is still in a3. bltz a3, intr_handler // MSB is set if interrupt // badaddr is only relavent for non-interrupts csrr a4, sbadaddr // get badaddr REG_S a4, TF_BADADDR(sp) beqz t1, trap_user // this was a user trap // This was a kernel exception call _C_LABEL(cpu_trap) // just call trap to handle it exception_kernexit: // If we got here, we are returning from a kernel exception (either a // trap or interrupt). Simply return the volatile registers and the // exception PC and status, load the saved SP from the trapframe, and // return from the exception csrrci zero, sstatus, SR_EI // disable interrupts REG_L ra, TF_RA(sp) // restore return address REG_L gp, TF_GP(sp) // restore gp REG_L a0, TF_A0(sp) // restore a0 REG_L a1, TF_A1(sp) // restore a1 REG_L a2, TF_A2(sp) // restore a2 REG_L a3, TF_A3(sp) // restore a3 REG_L a4, TF_A4(sp) // restore a4 REG_L a5, TF_A5(sp) // restore a5 REG_L a6, TF_A6(sp) // restore a6 REG_L a7, TF_A7(sp) // restore a7 REG_L t2, TF_T2(sp) // restore t2 REG_L t3, TF_T3(sp) // restore t3 REG_L t4, TF_T4(sp) // restore t4 REG_L t5, TF_T3(sp) // restore t5 REG_L t6, TF_T4(sp) // restore t6 REG_L t0, TF_PC(sp) // fetch execption PC REG_L t1, TF_SR(sp) // fetch status csrw sepc, t0 // restore execption PC csrw sstatus, t1 // restore status REG_L t0, TF_T0(sp) // restore t0 REG_L t1, TF_T1(sp) // restore t1 REG_L sp, TF_SP(sp) // restore SP sret // and we're done trap_user: REG_S s0, TF_S0(sp) // only save from userland REG_S s1, TF_S1(sp) // only save from userland REG_S s2, TF_S2(sp) // only save from userland REG_S s3, TF_S3(sp) // only save from userland REG_S s4, TF_S4(sp) // only save from userland REG_S s5, TF_S5(sp) // only save from userland REG_S s6, TF_S6(sp) // only save from userland REG_S s7, TF_S7(sp) // only save from userland REG_S s8, TF_S8(sp) // only save from userland REG_S s9, TF_S9(sp) // only save from userland REG_S s10, TF_S10(sp) // only save from userland REG_S s11, TF_S11(sp) // only save from userland csrsi sstatus, SR_EI // reenable interrupts li t0, CAUSE_SYSCALL // let's see if this was a syscall beq a3, t0, trap_syscall // yes it was call _C_LABEL(cpu_trap) // nope, just a regular trap _C_LABEL(exception_userexit): INT_L t0, L_MD_ASTPENDING(tp) // ast pending? bnez t0, trap_doast // yes, handle it. csrrci zero, sstatus, SR_EI // disable interrupts csrw sscratch, tp // show we are coming from userland REG_L tp, TF_TP(sp) // only restore from userland REG_L s0, TF_S0(sp) // only restore from userland REG_L s1, TF_S1(sp) // only restore from userland REG_L s2, TF_S2(sp) // only restore from userland REG_L s3, TF_S3(sp) // only restore from userland REG_L s4, TF_S4(sp) // only restore from userland REG_L s5, TF_S5(sp) // only restore from userland REG_L s6, TF_S6(sp) // only restore from userland REG_L s7, TF_S7(sp) // only restore from userland REG_L s8, TF_S8(sp) // only restore from userland REG_L s9, TF_S9(sp) // only restore from userland REG_L s10, TF_S10(sp) // only restore from userland REG_L s11, TF_S11(sp) // only restore from userland j exception_kernexit trap_syscall: .L0: PTR_L ra, exception_userexit PTR_L t0, L_PROC(tp) // get proc struct PTR_L t0, P_MD_SYSCALL(t0) // get syscall address from proc jr t0 // and jump to it intr_usersave: REG_S s0, TF_S0(sp) // only save from userland REG_S s1, TF_S1(sp) // only save from userland REG_S s2, TF_S2(sp) // only save from userland REG_S s3, TF_S3(sp) // only save from userland REG_S s4, TF_S4(sp) // only save from userland REG_S s5, TF_S5(sp) // only save from userland REG_S s6, TF_S6(sp) // only save from userland REG_S s7, TF_S7(sp) // only save from userland REG_S s8, TF_S8(sp) // only save from userland REG_S s9, TF_S9(sp) // only save from userland REG_S s10, TF_S10(sp) // only save from userland REG_S s11, TF_S11(sp) // only save from userland PTR_LA ra, exception_userexit trap_doast: move a0, sp // only argument is trapframe tail _C_LABEL(cpu_ast) intr_user: call _C_LABEL(cpu_intr) // handle interrupt INT_L t0, L_MD_ASTPENDING(tp) // get astpending bnez t0, intr_usersave // if one is pending, deal with in csrw sscratch, tp // show we are coming from userland REG_L tp, TF_TP(sp) // restore thread pointer j exception_kernexit // do standard exception exit intr_handler: beqz t1, intr_user call _C_LABEL(cpu_intr) j exception_kernexit END(cpu_exception_handler) // int cpu_set_onfault(struct faultbuf *fb, register_t retval) // ENTRY(cpu_set_onfault) REG_S ra, FB_RA(a0) REG_S s0, FB_S0(a0) REG_S s1, FB_S1(a0) REG_S s2, FB_S2(a0) REG_S s3, FB_S3(a0) REG_S s4, FB_S4(a0) REG_S s5, FB_S5(a0) REG_S s6, FB_S6(a0) REG_S s7, FB_S7(a0) REG_S s8, FB_S8(a0) REG_S s9, FB_S9(a0) REG_S s10, FB_S10(a0) REG_S s11, FB_S11(a0) REG_S sp, FB_SP(a0) REG_S a1, FB_A0(a0) PTR_S a0, L_MD_ONFAULT(tp) li a0, 0 ret END(cpu_set_onfault) ENTRY(setjmp) REG_S ra, FB_RA(a0) REG_S s0, FB_S0(a0) REG_S s1, FB_S1(a0) REG_S s2, FB_S2(a0) REG_S s3, FB_S3(a0) REG_S s4, FB_S4(a0) REG_S s5, FB_S5(a0) REG_S s6, FB_S6(a0) REG_S s7, FB_S7(a0) REG_S s8, FB_S8(a0) REG_S s9, FB_S9(a0) REG_S s10, FB_S10(a0) REG_S s11, FB_S11(a0) REG_S sp, FB_SP(a0) li a0, 0 ret END(setjmp) ENTRY(longjmp) REG_L ra, FB_RA(a0) REG_L s0, FB_S0(a0) REG_L s1, FB_S1(a0) REG_L s2, FB_S2(a0) REG_L s3, FB_S3(a0) REG_L s4, FB_S4(a0) REG_L s5, FB_S5(a0) REG_L s6, FB_S6(a0) REG_L s7, FB_S7(a0) REG_L s8, FB_S8(a0) REG_L s9, FB_S9(a0) REG_L s10, FB_S10(a0) REG_L s11, FB_S11(a0) REG_L sp, FB_SP(a0) move a0, a1 ret END(longjmp) ENTRY_NP(cpu_Debugger) cpu_Debugger_insn: sbreak ret END(cpu_Debugger)