blob: 959120b4ffead5eed21bfa46c0adf43de4d80a67 [file]
// Copyright 2023 The Fuchsia Authors
//
// 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 <lib/arch/asm.h>
#include <lib/arch/riscv64/exception-asm.h>
#include <phys/runtime/exception.h>
#include <phys/runtime/stack.h>
#include <zircon/tls.h>
#include "riscv64.h"
.function ArchPhysExceptionEntry, global, cfi=custom, align=4
.cfi.stvec
// On entry the CFI state thinks the CFA is the SP. But the SP is the
// interrupted SP. Instead we'll move to a dedicated stack used only for
// exceptions. (There's just one, so nested any exceptions will just
// start over reusing it from the top.) So the SP we're about to switch
// to is what we'd really like to call the CFA. That value is not in any
// register.
#define EXC_ENTRY_CFA (phys_exception_stack + BOOT_STACK_SIZE - PHYS_EXCEPTION_STATE_SIZE)
#if BOOT_STACK_SIZE % BOOT_STACK_ALIGN != 0
#error "BOOT_STACK_SIZE not aligned"
#endif
// TODO(mcgrathr): DW_CFA_def_cfa_expression with DW_OP_addr could just point
// to its fixed address. But the assembler doesn't know how to generate that
// and .cfi_escape only takes byte values and can't generate a relocation for
// the address. The GNU assembler has a .cfi_val_encoded_addr directive that
// generates a DW_OP_addr expression just like that for you, but that is only
// for DW_CFA_val_expression (a register rule, not the CFA) and LLVM doesn't
// implement that anyway. So the CFA is inaccurate at the first instruction.
//.cfi_def_cfa_encoded_addr EXC_ENTRY_CFA
// We don't keep anything in sscratch, so we can clobber it freely.
// This is the only way to free up a register to start saving things.
csrw sscratch, sp
// Now we can reset the SP to the reserved exception stack, just below where
// the registers will be saved. This is the SP value that's the CFA.
lla sp, EXC_ENTRY_CFA
.cfi_def_cfa_register sp
// Save a register and update CFI to say where it is.
.macro sd_cfa n
sd x\n, (\n * 8)(sp)
.cfi_offset x\n, \n * 8
.endm
// x0 is the always-zero register, so slot 0 is used for the PC.
sd_cfa 1
// x2 is the SP, which is actually stashed in sscratch, so skip it for now.
sd_cfa 3
sd_cfa 4
sd_cfa 5
sd_cfa 6
sd_cfa 7
sd_cfa 8
sd_cfa 9
sd_cfa 10
sd_cfa 11
sd_cfa 12
sd_cfa 13
sd_cfa 14
sd_cfa 15
sd_cfa 16
sd_cfa 17
sd_cfa 18
sd_cfa 19
sd_cfa 20
sd_cfa 21
sd_cfa 22
sd_cfa 23
sd_cfa 24
sd_cfa 25
sd_cfa 26
sd_cfa 27
sd_cfa 28
sd_cfa 29
sd_cfa 30
sd_cfa 31
// Now that all the normal registers have been saved, we can use
// some for scratch.
// Fetch the interrupted PC from the CSR.
csrr a1, sepc
// Recover the interrupted SP we stashed, and clear sscratch to be tidy.
csrrw a0, sscratch, zero
// Store the PC in slot 0, and tell CFI it's there.
sd a1, (sp)
.cfi_offset 64, 0
// Store the SP in slot 2, and tell CFI it's there.
sd a0, (2 * 8)(sp)
.cfi_offset sp, 2 * 8
#if __has_feature(shadow_call_Stack)
// Reset the shadow call stack to the one reserved for phys exceptions.
lla shadow_call_sp, phys_exception_shadow_call_stack // Grows up.
#endif
// Reset the thread pointer, though nothing should have changed it.
lla tp, boot_thread_pointer
// We now have complete CFI representing all the general registers so a
// debugger can unwind through this frame and back to the interrupted code.
// A normal call into C++ code will permit unwinding back into this frame.
// All the ABI stacks have been set up safely so normal C++ code can run.
// Load the argument for the C++ entry point.
mv a0, sp
// Make a first frame pointer record on the exception stack that links to
// the interrupted state's FP and PC. This differs from .prologue.fp 0, a1
// only in that CFI rules for fp and a1 are unchanged.
add sp, sp, -16
.cfi_adjust_cfa_offset 16
sd fp, (sp)
sd a1, 8(sp)
add fp, sp, 16
// Show the interrupted PC as the earliest caller on the shadow call stack.
// A backtrace via the exception shadow call stack won't go past there like
// the frame pointer might, but it will at least clearly bottom out at the
// exception PC.
#if __has_feature(shadow_call_stack)
sd a1, (shadow_call_sp)
add shadow_call_sp, shadow_call_sp, 8
#endif
call ArchPhysException
// If it returns the magic number, we can resume.
li a1, PHYS_EXCEPTION_RESUME
beq a0, a1, .Lresume_from_exception
// Otherwise it really shouldn't have returned. Trigger an exception reentry.
// What else?
.label PhysExceptionHandler_returned_BUG, global
unimp
j PhysExceptionHandler_returned_BUG
.Lresume_from_exception:
// Pop the frame pointer link pair pushed above.
add sp, sp, 16
.cfi_adjust_cfa_offset -16
// The handler code should have restored the SP it got on entry.
// Since the FP points above its link pair, they should now match again.
bne sp, fp, PhysExceptionHandler_returned_BUG
// Reload the interrupted PC (as modified) into sepc.
ld a0, (sp)
csrw sepc, a0
#ifndef __clang__ // TODO(https://fxbug.dev/42073127)
.cfi_register 64, sepc
#endif
.macro ld_cfa n
ld x\n, (\n * 8)(sp)
.cfi_same_value x\n
.endm
// x0 is the always-zero register, so slot 0 is used for the PC.
ld_cfa 1
// x2 is the SP, which must be done last.
ld_cfa 3
ld_cfa 4
ld_cfa 5
ld_cfa 6
ld_cfa 7
ld_cfa 8
ld_cfa 9
ld_cfa 10
ld_cfa 11
ld_cfa 12
ld_cfa 13
ld_cfa 14
ld_cfa 15
ld_cfa 16
ld_cfa 17
ld_cfa 18
ld_cfa 19
ld_cfa 20
ld_cfa 21
ld_cfa 22
ld_cfa 23
ld_cfa 24
ld_cfa 25
ld_cfa 26
ld_cfa 27
ld_cfa 28
ld_cfa 29
ld_cfa 30
ld_cfa 31
// Finally, we can load the SP from itself, and all the registers are back.
ld_cfa 2
// Then we simply resume at sepc, changing modes according to sstatus.
sret
.end_function