2018-05-06 21:58:06 +00:00
|
|
|
// SPDX-License-Identifier: GPL-2.0+
|
2017-12-26 05:55:49 +00:00
|
|
|
/*
|
|
|
|
* Copyright (c) 2016-17 Microsemi Corporation.
|
|
|
|
* Padmarao Begari, Microsemi Corporation <padmarao.begari@microsemi.com>
|
|
|
|
*
|
|
|
|
* Copyright (C) 2017 Andes Technology Corporation
|
|
|
|
* Rick Chen, Andes Technology Corporation <rick@andestech.com>
|
2019-12-25 05:27:44 +00:00
|
|
|
*
|
|
|
|
* Copyright (C) 2019 Sean Anderson <seanga2@gmail.com>
|
2017-12-26 05:55:49 +00:00
|
|
|
*/
|
|
|
|
|
2022-12-07 11:42:35 +00:00
|
|
|
#include <linux/compat.h>
|
2017-12-26 05:55:49 +00:00
|
|
|
#include <common.h>
|
2020-08-01 15:15:39 +00:00
|
|
|
#include <efi_loader.h>
|
2019-12-28 17:45:07 +00:00
|
|
|
#include <hang.h>
|
2019-11-14 19:57:41 +00:00
|
|
|
#include <irq_func.h>
|
2020-10-31 03:38:53 +00:00
|
|
|
#include <asm/global_data.h>
|
2017-12-26 05:55:49 +00:00
|
|
|
#include <asm/ptrace.h>
|
|
|
|
#include <asm/system.h>
|
|
|
|
#include <asm/encoding.h>
|
2022-12-07 11:42:35 +00:00
|
|
|
#include <semihosting.h>
|
2017-12-26 05:55:49 +00:00
|
|
|
|
2020-08-01 15:15:39 +00:00
|
|
|
DECLARE_GLOBAL_DATA_PTR;
|
|
|
|
|
|
|
|
static void show_efi_loaded_images(uintptr_t epc)
|
|
|
|
{
|
|
|
|
efi_print_image_infos((void *)epc);
|
|
|
|
}
|
|
|
|
|
2019-12-25 05:27:44 +00:00
|
|
|
static void show_regs(struct pt_regs *regs)
|
|
|
|
{
|
|
|
|
#ifdef CONFIG_SHOW_REGS
|
2020-12-02 13:36:26 +00:00
|
|
|
printf("\nSP: " REG_FMT " GP: " REG_FMT " TP: " REG_FMT "\n",
|
2020-08-01 15:15:39 +00:00
|
|
|
regs->sp, regs->gp, regs->tp);
|
|
|
|
printf("T0: " REG_FMT " T1: " REG_FMT " T2: " REG_FMT "\n",
|
|
|
|
regs->t0, regs->t1, regs->t2);
|
|
|
|
printf("S0: " REG_FMT " S1: " REG_FMT " A0: " REG_FMT "\n",
|
|
|
|
regs->s0, regs->s1, regs->a0);
|
|
|
|
printf("A1: " REG_FMT " A2: " REG_FMT " A3: " REG_FMT "\n",
|
|
|
|
regs->a1, regs->a2, regs->a3);
|
|
|
|
printf("A4: " REG_FMT " A5: " REG_FMT " A6: " REG_FMT "\n",
|
|
|
|
regs->a4, regs->a5, regs->a6);
|
|
|
|
printf("A7: " REG_FMT " S2: " REG_FMT " S3: " REG_FMT "\n",
|
|
|
|
regs->a7, regs->s2, regs->s3);
|
|
|
|
printf("S4: " REG_FMT " S5: " REG_FMT " S6: " REG_FMT "\n",
|
|
|
|
regs->s4, regs->s5, regs->s6);
|
|
|
|
printf("S7: " REG_FMT " S8: " REG_FMT " S9: " REG_FMT "\n",
|
|
|
|
regs->s7, regs->s8, regs->s9);
|
|
|
|
printf("S10: " REG_FMT " S11: " REG_FMT " T3: " REG_FMT "\n",
|
|
|
|
regs->s10, regs->s11, regs->t3);
|
2020-12-02 13:36:26 +00:00
|
|
|
printf("T4: " REG_FMT " T5: " REG_FMT " T6: " REG_FMT "\n",
|
2020-08-01 15:15:39 +00:00
|
|
|
regs->t4, regs->t5, regs->t6);
|
2019-12-25 05:27:44 +00:00
|
|
|
#endif
|
|
|
|
}
|
|
|
|
|
2021-09-04 08:36:49 +00:00
|
|
|
/**
|
|
|
|
* instr_len() - get instruction length
|
|
|
|
*
|
|
|
|
* @i: low 16 bits of the instruction
|
|
|
|
* Return: number of u16 in instruction
|
|
|
|
*/
|
|
|
|
static int instr_len(u16 i)
|
|
|
|
{
|
|
|
|
if ((i & 0x03) != 0x03)
|
|
|
|
return 1;
|
|
|
|
/* Instructions with more than 32 bits are not yet specified */
|
|
|
|
return 2;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* show_code() - display code leading to exception
|
|
|
|
*
|
|
|
|
* @epc: program counter
|
|
|
|
*/
|
|
|
|
static void show_code(ulong epc)
|
|
|
|
{
|
|
|
|
u16 *pos = (u16 *)(epc & ~1UL);
|
|
|
|
int i, len = instr_len(*pos);
|
|
|
|
|
|
|
|
printf("\nCode: ");
|
|
|
|
for (i = -8; i; ++i)
|
|
|
|
printf("%04x ", pos[i]);
|
|
|
|
printf("(");
|
|
|
|
for (i = 0; i < len; ++i)
|
|
|
|
printf("%04x%s", pos[i], i + 1 == len ? ")\n" : " ");
|
|
|
|
}
|
|
|
|
|
2019-12-25 05:27:44 +00:00
|
|
|
static void _exit_trap(ulong code, ulong epc, ulong tval, struct pt_regs *regs)
|
2018-12-12 14:12:44 +00:00
|
|
|
{
|
|
|
|
static const char * const exception_code[] = {
|
|
|
|
"Instruction address misaligned",
|
|
|
|
"Instruction access fault",
|
|
|
|
"Illegal instruction",
|
|
|
|
"Breakpoint",
|
|
|
|
"Load address misaligned",
|
|
|
|
"Load access fault",
|
|
|
|
"Store/AMO address misaligned",
|
|
|
|
"Store/AMO access fault",
|
|
|
|
"Environment call from U-mode",
|
|
|
|
"Environment call from S-mode",
|
|
|
|
"Reserved",
|
|
|
|
"Environment call from M-mode",
|
|
|
|
"Instruction page fault",
|
|
|
|
"Load page fault",
|
|
|
|
"Reserved",
|
|
|
|
"Store/AMO page fault",
|
|
|
|
};
|
|
|
|
|
2019-12-25 05:27:44 +00:00
|
|
|
if (code < ARRAY_SIZE(exception_code))
|
|
|
|
printf("Unhandled exception: %s\n", exception_code[code]);
|
|
|
|
else
|
|
|
|
printf("Unhandled exception code: %ld\n", code);
|
2018-12-12 14:12:44 +00:00
|
|
|
|
2020-08-01 15:15:39 +00:00
|
|
|
printf("EPC: " REG_FMT " RA: " REG_FMT " TVAL: " REG_FMT "\n",
|
|
|
|
epc, regs->ra, tval);
|
riscv: Ensure gp is NULL or points to valid data
This ensures constructs like `if (gd & gd->...) { ... }` work when
accessing the global data pointer. Without this change, it was possible for
a very early trap to cause _exit_trap to directly or indirectly (through
printf) to read arbitrary memory. This could cause a second trap,
preventing show_regs from being printed.
printf (and specifically puts) uses gd to determine what function to print
with. These functions in turn use gd to find the serial device, etc.
However, before accessing gd, puts first checks to see if it is non-NULL.
This indicates an existing (perhaps undocumented) assumption that either gd
is NULL or it is completely valid.
Before this patch, gd either points to unexpected data (because it retains
the value it did from the prior-stage) or points to uninitialized data
(because it has not yet been initialized by board_init_f_init_reserve)
until the hart has acquired available_harts_lock. This can cause two
problems, depending on the value of gd->flags. If GD_FLG_SERIAL_READY is
unset, then some garbage data will be printed to stdout, but there will not
be a second trap. However, if GD_FLG_SERIAL_READY is set, then puts will
try to print with serial_puts, which will likely cause a second trap.
After this patch, gd is zero up until either a hart has set it in
wait_for_gd_init, or until it is set by arch_init_gd. This prevents its
usage before its data is initialized because both handle_trap and puts
ensure that gd is nonzero before using it. After gd has been set, it is OK
to access it because its data has been cleared (and so flags is valid).
XIP cannot use locks because flash is not writable. This leaves it
vulnerable to the same class of bugs regarding already-pending IPIs as
before this series. Fixing that would require finding another method of
synchronization, which is outside the scope of this series.
Fixes: 7c6ca03eae ("riscv: additional crash information")
Signed-off-by: Sean Anderson <seanga2@gmail.com>
Reviewed-by: Bin Meng <bin.meng@windriver.com>
Reviewed-by: Rick Chen <rick@andestech.com>
2020-09-21 11:51:40 +00:00
|
|
|
/* Print relocation adjustments, but only if gd is initialized */
|
|
|
|
if (gd && gd->flags & GD_FLG_RELOC)
|
2020-12-02 13:36:26 +00:00
|
|
|
printf("EPC: " REG_FMT " RA: " REG_FMT " reloc adjusted\n",
|
2020-08-01 15:15:39 +00:00
|
|
|
epc - gd->reloc_off, regs->ra - gd->reloc_off);
|
|
|
|
|
2019-12-25 05:27:44 +00:00
|
|
|
show_regs(regs);
|
2021-09-04 08:36:49 +00:00
|
|
|
show_code(epc);
|
2020-08-01 15:15:39 +00:00
|
|
|
show_efi_loaded_images(epc);
|
2020-12-02 13:36:26 +00:00
|
|
|
panic("\n");
|
2018-12-12 14:12:44 +00:00
|
|
|
}
|
2017-12-26 05:55:49 +00:00
|
|
|
|
|
|
|
int interrupt_init(void)
|
|
|
|
{
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* enable interrupts
|
|
|
|
*/
|
|
|
|
void enable_interrupts(void)
|
|
|
|
{
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* disable interrupts
|
|
|
|
*/
|
|
|
|
int disable_interrupts(void)
|
|
|
|
{
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2019-12-25 05:27:44 +00:00
|
|
|
ulong handle_trap(ulong cause, ulong epc, ulong tval, struct pt_regs *regs)
|
2017-12-26 05:55:49 +00:00
|
|
|
{
|
2018-12-03 05:27:40 +00:00
|
|
|
ulong is_irq, irq;
|
2017-12-26 05:55:49 +00:00
|
|
|
|
2020-09-26 05:50:36 +00:00
|
|
|
/* An UEFI application may have changed gd. Restore U-Boot's gd. */
|
|
|
|
efi_restore_gd();
|
|
|
|
|
2022-12-07 11:42:35 +00:00
|
|
|
if (cause == CAUSE_BREAKPOINT &&
|
|
|
|
CONFIG_IS_ENABLED(SEMIHOSTING_FALLBACK)) {
|
|
|
|
ulong pre_addr = epc - 4, post_addr = epc + 4;
|
|
|
|
|
|
|
|
/* Check for prior and post addresses to be in same page. */
|
|
|
|
if ((pre_addr & ~(PAGE_SIZE - 1)) ==
|
|
|
|
(post_addr & ~(PAGE_SIZE - 1))) {
|
|
|
|
u32 pre = *(u32 *)pre_addr;
|
|
|
|
u32 post = *(u32 *)post_addr;
|
|
|
|
|
|
|
|
/* Check for semihosting, i.e.:
|
|
|
|
* slli zero,zero,0x1f
|
|
|
|
* ebreak
|
|
|
|
* srai zero,zero,0x7
|
|
|
|
*/
|
|
|
|
if (pre == 0x01f01013 && post == 0x40705013) {
|
|
|
|
disable_semihosting();
|
|
|
|
epc += 4;
|
|
|
|
return epc;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2018-12-03 05:27:40 +00:00
|
|
|
is_irq = (cause & MCAUSE_INT);
|
|
|
|
irq = (cause & ~MCAUSE_INT);
|
|
|
|
|
|
|
|
if (is_irq) {
|
|
|
|
switch (irq) {
|
|
|
|
case IRQ_M_EXT:
|
|
|
|
case IRQ_S_EXT:
|
|
|
|
external_interrupt(0); /* handle external interrupt */
|
|
|
|
break;
|
|
|
|
case IRQ_M_TIMER:
|
|
|
|
case IRQ_S_TIMER:
|
|
|
|
timer_interrupt(0); /* handle timer interrupt */
|
|
|
|
break;
|
|
|
|
default:
|
2019-12-25 05:27:44 +00:00
|
|
|
_exit_trap(cause, epc, tval, regs);
|
2018-12-03 05:27:40 +00:00
|
|
|
break;
|
|
|
|
};
|
|
|
|
} else {
|
2019-12-25 05:27:44 +00:00
|
|
|
_exit_trap(cause, epc, tval, regs);
|
2018-12-03 05:27:40 +00:00
|
|
|
}
|
2017-12-26 05:55:49 +00:00
|
|
|
|
|
|
|
return epc;
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
*Entry Point for PLIC Interrupt Handler
|
|
|
|
*/
|
|
|
|
__attribute__((weak)) void external_interrupt(struct pt_regs *regs)
|
|
|
|
{
|
|
|
|
}
|
|
|
|
|
|
|
|
__attribute__((weak)) void timer_interrupt(struct pt_regs *regs)
|
|
|
|
{
|
|
|
|
}
|