crash/loongarch64.c
Ming Wang 0f34aa46ca LoongArch64: Add 'irq' command support
Added support for the 'irq' series of commands in the LoongArch64
architecture, except for the 'irq -d' command, others can be used.

The result of using the 'irq' command without this patch is as follows:
crash> irq
 IRQ   IRQ_DESC/_DATA      IRQACTION      NAME
 ...
 16   9000000090423c00  9000000000f4c500
 17   9000000090423e00  9000000000f4c500
 18   9000000090495c00  9000000000f4c500
 19   9000000090494a00  9000000000f4c500
 20   9000000090496400  9000000090418480  "IPI"
 21   9000000090496200  9000000090418500  "timer"
 22   9000000090cb2600  9000000090d9c780  "acpi"
 23   9000000090cb3c00      (unused)
 24   9000000090cb1800      (unused)
 25   9000000090cb0800  900000009117f580  "loongson_i2c"
                        900000009117ee80  "loongson_i2c"
                        900000009117cc00  "loongson_i2c"
                        900000009117e800  "loongson_i2c"
                        900000009117c780  "loongson_i2c"
                        900000009117df00  "loongson_i2c"
...

Co-developed-by: Youling Tang <tangyouling@loongson.cn>
Signed-off-by: Youling Tang <tangyouling@loongson.cn>
Signed-off-by: Ming Wang <wangming01@loongson.cn>
2024-01-26 13:47:17 +09:00

1369 lines
37 KiB
C

/* loongarch64.c - core analysis suite
*
* Copyright (C) 2021 Loongson Technology Co., Ltd.
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*/
#ifdef LOONGARCH64
#include <elf.h>
#include "defs.h"
/* from arch/loongarch/include/asm/ptrace.h */
struct loongarch64_pt_regs {
/* Saved main processor registers. */
unsigned long regs[32];
/* Saved special registers. */
unsigned long csr_crmd;
unsigned long csr_prmd;
unsigned long csr_euen;
unsigned long csr_ecfg;
unsigned long csr_estat;
unsigned long csr_epc;
unsigned long csr_badvaddr;
unsigned long orig_a0;
};
struct loongarch64_unwind_frame {
unsigned long sp;
unsigned long pc;
unsigned long ra;
};
static int loongarch64_pgd_vtop(ulong *pgd, ulong vaddr,
physaddr_t *paddr, int verbose);
static int loongarch64_uvtop(struct task_context *tc, ulong vaddr,
physaddr_t *paddr, int verbose);
static int loongarch64_kvtop(struct task_context *tc, ulong kvaddr,
physaddr_t *paddr, int verbose);
static int loongarch64_translate_pte(ulong pte, void *physaddr,
ulonglong pte64);
static void loongarch64_cmd_mach(void);
static void loongarch64_display_machine_stats(void);
static void loongarch64_back_trace_cmd(struct bt_info *bt);
static void loongarch64_analyze_function(ulong start, ulong offset,
struct loongarch64_unwind_frame *current,
struct loongarch64_unwind_frame *previous);
static void loongarch64_dump_backtrace_entry(struct bt_info *bt,
struct syment *sym, struct loongarch64_unwind_frame *current,
struct loongarch64_unwind_frame *previous, int level);
static void loongarch64_dump_exception_stack(struct bt_info *bt, char *pt_regs);
static int loongarch64_is_exception_entry(struct syment *sym);
static void loongarch64_display_full_frame(struct bt_info *bt,
struct loongarch64_unwind_frame *current,
struct loongarch64_unwind_frame *previous);
static void loongarch64_stackframe_init(void);
static void loongarch64_get_stack_frame(struct bt_info *bt, ulong *pcp, ulong *spp);
static int loongarch64_get_dumpfile_stack_frame(struct bt_info *bt,
ulong *nip, ulong *ksp);
static int loongarch64_get_frame(struct bt_info *bt, ulong *pcp, ulong *spp);
static int loongarch64_init_active_task_regs(void);
static int loongarch64_get_crash_notes(void);
static int loongarch64_get_elf_notes(void);
/*
* 3 Levels paging PAGE_SIZE=16KB
* PGD | PMD | PTE | OFFSET |
* 11 | 11 | 11 | 14 |
*/
/* From arch/loongarch/include/asm/pgtable{,-64}.h */
typedef struct { ulong pgd; } pgd_t;
typedef struct { ulong pmd; } pmd_t;
typedef struct { ulong pte; } pte_t;
#define TASK_SIZE64 (1UL << 40)
#define PMD_SHIFT (PAGESHIFT() + (PAGESHIFT() - 3))
#define PMD_SIZE (1UL << PMD_SHIFT)
#define PMD_MASK (~(PMD_SIZE - 1))
#define PGDIR_SHIFT (PMD_SHIFT + (PAGESHIFT() - 3))
#define PGDIR_SIZE (1UL << PGDIR_SHIFT)
#define PGDIR_MASK (~(PGDIR_SIZE - 1))
#define PTRS_PER_PTE (1UL << (PAGESHIFT() - 3))
#define PTRS_PER_PMD PTRS_PER_PTE
#define PTRS_PER_PGD PTRS_PER_PTE
#define USER_PTRS_PER_PGD ((TASK_SIZE64 / PGDIR_SIZE)?(TASK_SIZE64 / PGDIR_SIZE) : 1)
#define pte_index(addr) (((addr) >> PAGESHIFT()) & (PTRS_PER_PTE - 1))
#define pmd_index(addr) (((addr) >> PMD_SHIFT) & (PTRS_PER_PMD - 1))
#define pgd_index(addr) (((addr) >> PGDIR_SHIFT) & (PTRS_PER_PGD - 1))
#define LOONGARCH64_CPU_RIXI (1UL << 23) /* CPU has TLB Read/eXec Inhibit */
#define LOONGARCH64_EF_R0 0
#define LOONGARCH64_EF_RA 1
#define LOONGARCH64_EF_SP 3
#define LOONGARCH64_EF_FP 22
#define LOONGARCH64_EF_CSR_EPC 32
#define LOONGARCH64_EF_CSR_BADVADDR 33
#define LOONGARCH64_EF_CSR_CRMD 34
#define LOONGARCH64_EF_CSR_PRMD 35
#define LOONGARCH64_EF_CSR_EUEN 36
#define LOONGARCH64_EF_CSR_ECFG 37
#define LOONGARCH64_EF_CSR_ESTAT 38
static struct machine_specific loongarch64_machine_specific = { 0 };
/*
* Holds registers during the crash.
*/
static struct loongarch64_pt_regs *panic_task_regs;
/*
* Check and print the flags on the page
*/
static void
check_page_flags(ulong pte)
{
#define CHECK_PAGE_FLAG(flag) \
if ((_PAGE_##flag) && (pte & _PAGE_##flag)) \
fprintf(fp, "%s" #flag, others++ ? "|" : "")
int others = 0;
fprintf(fp, "(");
if (pte) {
CHECK_PAGE_FLAG(VALID);
CHECK_PAGE_FLAG(DIRTY);
CHECK_PAGE_FLAG(PLV);
/* Determine whether it is a huge page format */
if (pte & _PAGE_HGLOBAL) {
CHECK_PAGE_FLAG(HUGE);
CHECK_PAGE_FLAG(HGLOBAL);
} else {
CHECK_PAGE_FLAG(GLOBAL);
}
CHECK_PAGE_FLAG(PRESENT);
CHECK_PAGE_FLAG(WRITE);
CHECK_PAGE_FLAG(PROTNONE);
CHECK_PAGE_FLAG(SPECIAL);
CHECK_PAGE_FLAG(NO_READ);
CHECK_PAGE_FLAG(NO_EXEC);
CHECK_PAGE_FLAG(RPLV);
} else {
fprintf(fp, "no mapping");
}
fprintf(fp, ")\n");
}
/*
* Translate a PTE, returning TRUE if the page is present.
* If a physaddr pointer is passed in, don't print anything.
*/
static int
loongarch64_translate_pte(ulong pte, void *physaddr, ulonglong unused)
{
char ptebuf[BUFSIZE];
char physbuf[BUFSIZE];
char buf1[BUFSIZE];
char buf2[BUFSIZE];
char buf3[BUFSIZE];
char *arglist[MAXARGS];
int page_present;
int c, len1, len2, len3;
ulong paddr;
paddr = PTOB(pte >> _PFN_SHIFT);
page_present = !!(pte & _PAGE_PRESENT);
if (physaddr) {
*(ulong *)physaddr = paddr;
return page_present;
}
sprintf(ptebuf, "%lx", pte);
len1 = MAX(strlen(ptebuf), strlen("PTE"));
fprintf(fp, "%s ", mkstring(buf1, len1, CENTER | LJUST, "PTE"));
if (!page_present) {
swap_location(pte, buf1);
if ((c = parse_line(buf1, arglist)) != 3)
error(FATAL, "cannot determine swap location\n");
len2 = MAX(strlen(arglist[0]), strlen("SWAP"));
len3 = MAX(strlen(arglist[2]), strlen("OFFSET"));
fprintf(fp, "%s %s\n",
mkstring(buf2, len2, CENTER|LJUST, "SWAP"),
mkstring(buf3, len3, CENTER|LJUST, "OFFSET"));
strcpy(buf2, arglist[0]);
strcpy(buf3, arglist[2]);
fprintf(fp, "%s %s %s\n",
mkstring(ptebuf, len1, CENTER|RJUST, NULL),
mkstring(buf2, len2, CENTER|RJUST, NULL),
mkstring(buf3, len3, CENTER|RJUST, NULL));
return page_present;
}
sprintf(physbuf, "%lx", paddr);
len2 = MAX(strlen(physbuf), strlen("PHYSICAL"));
fprintf(fp, "%s ", mkstring(buf1, len2, CENTER | LJUST, "PHYSICAL"));
fprintf(fp, "FLAGS\n");
fprintf(fp, "%s %s ",
mkstring(ptebuf, len1, CENTER | RJUST, NULL),
mkstring(physbuf, len2, CENTER | RJUST, NULL));
check_page_flags(pte);
return page_present;
}
/*
* Identify and print the segment name to which the virtual address belongs
*/
static void
get_segment_name(ulong vaddr, int verbose)
{
const char * segment;
if (verbose) {
if (vaddr < 0x4000000000000000lu)
segment = "xuvrange";
else if (vaddr < 0x8000000000000000lu)
segment = "xsprange";
else if (vaddr < 0xc000000000000000lu)
segment = "xkprange";
else
segment = "xkvrange";
fprintf(fp, "SEGMENT: %s\n", segment);
}
}
/*
* Virtual to physical memory translation. This function will be called
* by both loongarch64_kvtop and loongarch64_uvtop.
*/
static int
loongarch64_pgd_vtop(ulong *pgd, ulong vaddr, physaddr_t *paddr, int verbose)
{
ulong *pgd_ptr, pgd_val;
ulong *pmd_ptr, pmd_val;
ulong *pte_ptr, pte_val;
get_segment_name(vaddr, verbose);
if (IS_XKPRANGE(vaddr)) {
*paddr = VTOP(vaddr);
return TRUE;
}
if (verbose)
fprintf(fp, "PAGE DIRECTORY: %016lx\n", (ulong)pgd);
pgd_ptr = pgd + pgd_index(vaddr);
FILL_PGD(PAGEBASE(pgd), KVADDR, PAGESIZE());
pgd_val = ULONG(machdep->pgd + PAGEOFFSET(pgd_ptr));
if (verbose)
fprintf(fp, " PGD: %16lx => %16lx\n", (ulong)pgd_ptr, pgd_val);
if (!pgd_val)
goto no_page;
pmd_ptr = (ulong *)(VTOP(pgd_val) + sizeof(pmd_t) * pmd_index(vaddr));
FILL_PMD(PAGEBASE(pmd_ptr), PHYSADDR, PAGESIZE());
pmd_val = ULONG(machdep->pmd + PAGEOFFSET(pmd_ptr));
if (verbose)
fprintf(fp, " PMD: %016lx => %016lx\n", (ulong)pmd_ptr, pmd_val);
if (!pmd_val)
goto no_page;
pte_ptr = (ulong *)(VTOP(pmd_val) + sizeof(pte_t) * pte_index(vaddr));
FILL_PTBL(PAGEBASE(pte_ptr), PHYSADDR, PAGESIZE());
pte_val = ULONG(machdep->ptbl + PAGEOFFSET(pte_ptr));
if (verbose)
fprintf(fp, " PTE: %016lx => %016lx\n", (ulong)pte_ptr, pte_val);
if (!pte_val)
goto no_page;
if (!(pte_val & _PAGE_PRESENT)) {
if (verbose) {
fprintf(fp, "\n");
loongarch64_translate_pte((ulong)pte_val, 0, pte_val);
}
return FALSE;
}
*paddr = PTOB(pte_val >> _PFN_SHIFT) + PAGEOFFSET(vaddr);
if (verbose) {
fprintf(fp, " PAGE: %016lx\n\n", PAGEBASE(*paddr));
loongarch64_translate_pte(pte_val, 0, 0);
}
return TRUE;
no_page:
fprintf(fp, "invalid\n");
return FALSE;
}
/* Translates a user virtual address to its physical address. cmd_vtop() sets
* the verbose flag so that the pte translation gets displayed; all other
* callers quietly accept the translation.
*/
static int
loongarch64_uvtop(struct task_context *tc, ulong vaddr, physaddr_t *paddr, int verbose)
{
ulong mm, active_mm;
ulong *pgd;
if (!tc)
error(FATAL, "current context invalid\n");
*paddr = 0;
if (is_kernel_thread(tc->task) && IS_KVADDR(vaddr)) {
readmem(tc->task + OFFSET(task_struct_active_mm),
KVADDR, &active_mm, sizeof(void *),
"task active_mm contents", FAULT_ON_ERROR);
if (!active_mm)
error(FATAL,
"no active_mm for this kernel thread\n");
readmem(active_mm + OFFSET(mm_struct_pgd),
KVADDR, &pgd, sizeof(long),
"mm_struct pgd", FAULT_ON_ERROR);
} else {
if ((mm = task_mm(tc->task, TRUE)))
pgd = ULONG_PTR(tt->mm_struct + OFFSET(mm_struct_pgd));
else
readmem(tc->mm_struct + OFFSET(mm_struct_pgd),
KVADDR, &pgd, sizeof(long), "mm_struct pgd",
FAULT_ON_ERROR);
}
return loongarch64_pgd_vtop(pgd, vaddr, paddr, verbose);;
}
/* Translates a user virtual address to its physical address. cmd_vtop() sets
* the verbose flag so that the pte translation gets displayed; all other
* callers quietly accept the translation.
*/
static int
loongarch64_kvtop(struct task_context *tc, ulong kvaddr, physaddr_t *paddr, int verbose)
{
if (!IS_KVADDR(kvaddr))
return FALSE;
if (!verbose) {
if (IS_XKPRANGE(kvaddr)) {
*paddr = VTOP(kvaddr);
return TRUE;
}
}
return loongarch64_pgd_vtop((ulong *)vt->kernel_pgd[0], kvaddr, paddr,
verbose);
}
/*
* Machine dependent command.
*/
static void
loongarch64_cmd_mach(void)
{
int c;
while ((c = getopt(argcnt, args, "cmo")) != EOF) {
switch (c) {
case 'c':
case 'm':
case 'o':
option_not_supported(c);
break;
default:
argerrs++;
break;
}
}
if (argerrs)
cmd_usage(pc->curcmd, SYNOPSIS);
loongarch64_display_machine_stats();
}
/*
* "mach" command output.
*/
static void
loongarch64_display_machine_stats(void)
{
struct new_utsname *uts;
char buf[BUFSIZE];
ulong mhz;
uts = &kt->utsname;
fprintf(fp, " MACHINE TYPE: %s\n", uts->machine);
fprintf(fp, " MEMORY SIZE: %s\n", get_memory_size(buf));
fprintf(fp, " CPUS: %d\n", get_cpus_to_display());
fprintf(fp, " PROCESSOR SPEED: ");
if ((mhz = machdep->processor_speed()))
fprintf(fp, "%ld Mhz\n", mhz);
else
fprintf(fp, "(unknown)\n");
fprintf(fp, " HZ: %d\n", machdep->hz);
fprintf(fp, " PAGE SIZE: %d\n", PAGESIZE());
fprintf(fp, " KERNEL STACK SIZE: %ld\n", STACKSIZE());
}
/*
* Unroll a kernel stack.
*/
static void
loongarch64_back_trace_cmd(struct bt_info *bt)
{
struct loongarch64_unwind_frame current, previous;
struct loongarch64_pt_regs *regs;
char pt_regs[SIZE(pt_regs)];
int level = 0;
int invalid_ok = 1;
if (bt->flags & BT_REGS_NOT_FOUND)
return;
previous.sp = previous.pc = previous.ra = 0;
current.pc = bt->instptr;
current.sp = bt->stkptr;
current.ra = 0;
if (!INSTACK(current.sp, bt))
return;
if (bt->machdep) {
regs = (struct loongarch64_pt_regs *)bt->machdep;
previous.pc = current.ra = regs->regs[LOONGARCH64_EF_RA];
}
while (current.sp <= bt->stacktop - 32 - SIZE(pt_regs)) {
struct syment *symbol = NULL;
ulong offset;
if (CRASHDEBUG(8))
fprintf(fp, "level %d pc %#lx ra %#lx sp %lx\n",
level, current.pc, current.ra, current.sp);
if (!IS_KVADDR(current.pc) && !invalid_ok)
return;
symbol = value_search(current.pc, &offset);
if (!symbol && !invalid_ok) {
error(FATAL, "PC is unknown symbol (%lx)", current.pc);
return;
}
invalid_ok = 0;
/*
* If we get an address which points to the start of a
* function, then it could one of the following:
*
* - we are dealing with a noreturn function. The last call
* from a noreturn function has an ra which points to the
* start of the function after it. This is common in the
* oops callchain because of die() which is annotated as
* noreturn.
*
* - we have taken an exception at the start of this function.
* In this case we already have the RA in current.ra.
*
* - we are in one of these routines which appear with zero
* offset in manually-constructed stack frames:
*
* * ret_from_exception
* * ret_from_irq
* * ret_from_fork
* * ret_from_kernel_thread
*/
if (symbol && !STRNEQ(symbol->name, "ret_from") && !offset &&
!current.ra && current.sp < bt->stacktop - 32 - SIZE(pt_regs)) {
if (CRASHDEBUG(8))
fprintf(fp, "zero offset at %s, try previous symbol\n",
symbol->name);
symbol = value_search(current.pc - 4, &offset);
if (!symbol) {
error(FATAL, "PC is unknown symbol (%lx)", current.pc);
return;
}
}
if (symbol && loongarch64_is_exception_entry(symbol)) {
GET_STACK_DATA(current.sp, pt_regs, sizeof(pt_regs));
regs = (struct loongarch64_pt_regs *) (pt_regs + OFFSET(pt_regs_regs));
previous.ra = regs->regs[LOONGARCH64_EF_RA];
previous.sp = regs->regs[LOONGARCH64_EF_SP];
current.ra = regs->csr_epc;
if (CRASHDEBUG(8))
fprintf(fp, "exception pc %#lx ra %#lx sp %lx\n",
previous.pc, previous.ra, previous.sp);
/* The PC causing the exception may have been invalid */
invalid_ok = 1;
} else if (symbol) {
loongarch64_analyze_function(symbol->value, offset, &current, &previous);
} else {
/*
* The current PC is invalid. Assume that the code
* jumped through a invalid pointer and that the SP has
* not been adjusted.
*/
previous.sp = current.sp;
}
if (symbol)
loongarch64_dump_backtrace_entry(bt, symbol, &current, &previous, level++);
current.pc = current.ra;
current.sp = previous.sp;
current.ra = previous.ra;
if (CRASHDEBUG(8))
fprintf(fp, "next %d pc %#lx ra %#lx sp %lx\n",
level, current.pc, current.ra, current.sp);
previous.sp = previous.pc = previous.ra = 0;
}
}
static void
loongarch64_analyze_function(ulong start, ulong offset,
struct loongarch64_unwind_frame *current,
struct loongarch64_unwind_frame *previous)
{
ulong i;
ulong rapos = 0;
ulong spadjust = 0;
uint32_t *funcbuf, *ip;
if (CRASHDEBUG(8))
fprintf(fp, "%s: start %#lx offset %#lx\n",
__func__, start, offset);
if (!offset) {
previous->sp = current->sp;
return;
}
ip = funcbuf = (uint32_t *)GETBUF(offset);
if (!readmem(start, KVADDR, funcbuf, offset,
"loongarch64_analyze_function", RETURN_ON_ERROR)) {
FREEBUF(funcbuf);
error(WARNING, "Cannot read function at %16lx\n", start);
return;
}
for (i = 0; i < offset; i += 4) {
ulong insn = *ip & 0xffffffff;
ulong si12 = (insn >> 10) & 0xfff; /* bit[10:21] */
if (CRASHDEBUG(8))
fprintf(fp, "insn @ %#lx = %#lx\n", start + i, insn);
if ((insn & 0xffc003ff) == 0x02800063 || /* addi.w sp,sp,si12 */
(insn & 0xffc003ff) == 0x02c00063) { /* addi.d sp,sp,si12 */
if (!(si12 & 0x800)) /* si12 < 0 */
break;
spadjust += 0x1000 - si12;
if (CRASHDEBUG(8))
fprintf(fp, "si12 =%lu ,spadjust = %lu\n", si12, spadjust);
} else if ((insn & 0xffc003ff) == 0x29800061 || /* st.w ra,sp,si12 */
(insn & 0xffc003ff) == 0x29c00061) { /* st.d ra,sp,si12 */
rapos = current->sp + si12;
if (CRASHDEBUG(8))
fprintf(fp, "rapos %lx\n", rapos);
break;
}
ip++;
}
FREEBUF(funcbuf);
previous->sp = current->sp + spadjust;
if (rapos && !readmem(rapos, KVADDR, &current->ra,
sizeof(current->ra), "RA from stack",
RETURN_ON_ERROR)) {
error(FATAL, "Cannot read RA from stack %lx", rapos);
return;
}
}
static void
loongarch64_dump_backtrace_entry(struct bt_info *bt, struct syment *sym,
struct loongarch64_unwind_frame *current,
struct loongarch64_unwind_frame *previous, int level)
{
const char *name = sym ? sym->name : "(invalid)";
struct load_module *lm;
char *name_plus_offset = NULL;
struct syment *symp;
ulong symbol_offset;
char buf[BUFSIZE];
char pt_regs[SIZE(pt_regs)];
if (bt->flags & BT_SYMBOL_OFFSET) {
symp = value_search(current->pc, &symbol_offset);
if (symp && symbol_offset)
name_plus_offset =
value_to_symstr(current->pc, buf, bt->radix);
}
fprintf(fp, "%s#%d [%016lx] %s at %016lx", level < 10 ? " " : "", level,
current->sp, name_plus_offset ? name_plus_offset : name,
current->pc);
if (module_symbol(current->pc, NULL, &lm, NULL, 0))
fprintf(fp, " [%s]", lm->mod_name);
fprintf(fp, "\n");
/*
* 'bt -l', get a line number associated with a current pc address.
*/
if (bt->flags & BT_LINE_NUMBERS) {
get_line_number(current->pc, buf, FALSE);
if (strlen(buf))
fprintf(fp, " %s\n", buf);
}
if (sym && loongarch64_is_exception_entry(sym)) {
GET_STACK_DATA(current->sp, &pt_regs, SIZE(pt_regs));
loongarch64_dump_exception_stack(bt, pt_regs);
}
/* bt -f */
if (bt->flags & BT_FULL) {
fprintf(fp, " "
"[PC: %016lx RA: %016lx SP: %016lx SIZE: %ld]\n",
current->pc, current->ra, current->sp,
previous->sp - current->sp);
loongarch64_display_full_frame(bt, current, previous);
}
}
static void
loongarch64_dump_exception_stack(struct bt_info *bt, char *pt_regs)
{
struct loongarch64_pt_regs *regs;
int i;
char buf[BUFSIZE];
regs = (struct loongarch64_pt_regs *) (pt_regs + OFFSET(pt_regs_regs));
for (i = 0; i < 32; i += 4) {
fprintf(fp, " $%2d : %016lx %016lx %016lx %016lx\n",
i, regs->regs[i], regs->regs[i+1],
regs->regs[i+2], regs->regs[i+3]);
}
value_to_symstr(regs->csr_epc, buf, 16);
fprintf(fp, " epc : %016lx %s\n", regs->csr_epc, buf);
value_to_symstr(regs->regs[LOONGARCH64_EF_RA], buf, 16);
fprintf(fp, " ra : %016lx %s\n", regs->regs[LOONGARCH64_EF_RA], buf);
fprintf(fp, " CSR crmd : %016lx\n", regs->csr_crmd);
fprintf(fp, " CSR prmd : %016lx\n", regs->csr_prmd);
fprintf(fp, " CSR ecfg : %016lx\n", regs->csr_ecfg);
fprintf(fp, " CSR estat: %016lx\n", regs->csr_estat);
fprintf(fp, " CSR euen : %016lx\n", regs->csr_euen);
fprintf(fp, " BadVA : %016lx\n", regs->csr_badvaddr);
}
static int
loongarch64_is_exception_entry(struct syment *sym)
{
return STREQ(sym->name, "ret_from_exception") ||
STREQ(sym->name, "ret_from_irq") ||
STREQ(sym->name, "work_resched") ||
STREQ(sym->name, "handle_sys");
}
/*
* 'bt -f' commend output
* Display all stack data contained in a frame
*/
static void
loongarch64_display_full_frame(struct bt_info *bt, struct loongarch64_unwind_frame *current,
struct loongarch64_unwind_frame *previous)
{
int i, u_idx;
ulong *up;
ulong words, addr;
char buf[BUFSIZE];
if (previous->sp < current->sp)
return;
if (!(INSTACK(previous->sp, bt) && INSTACK(current->sp, bt)))
return;
words = (previous->sp - current->sp) / sizeof(ulong) + 1;
addr = current->sp;
u_idx = (current->sp - bt->stackbase) / sizeof(ulong);
for (i = 0; i < words; i++, u_idx++) {
if (!(i & 1))
fprintf(fp, "%s %lx: ", i ? "\n" : "", addr);
up = (ulong *)(&bt->stackbuf[u_idx*sizeof(ulong)]);
fprintf(fp, "%s ", format_stack_entry(bt, buf, *up, 0));
addr += sizeof(ulong);
}
fprintf(fp, "\n");
}
static void
loongarch64_stackframe_init(void)
{
long task_struct_thread = MEMBER_OFFSET("task_struct", "thread");
long thread_reg03_sp = MEMBER_OFFSET("thread_struct", "reg03");
long thread_reg01_ra = MEMBER_OFFSET("thread_struct", "reg01");
if ((task_struct_thread == INVALID_OFFSET) ||
(thread_reg03_sp == INVALID_OFFSET) ||
(thread_reg01_ra == INVALID_OFFSET)) {
error(FATAL,
"cannot determine thread_struct offsets\n");
return;
}
ASSIGN_OFFSET(task_struct_thread_reg03) =
task_struct_thread + thread_reg03_sp;
ASSIGN_OFFSET(task_struct_thread_reg01) =
task_struct_thread + thread_reg01_ra;
MEMBER_OFFSET_INIT(elf_prstatus_pr_reg, "elf_prstatus", "pr_reg");
STRUCT_SIZE_INIT(note_buf, "note_buf_t");
}
/*
* Get a stack frame combination of pc and ra from the most relevant spot.
*/
static void
loongarch64_get_stack_frame(struct bt_info *bt, ulong *pcp, ulong *spp)
{
ulong ksp, nip;
int ret = 0;
nip = ksp = 0;
bt->machdep = NULL;
if (DUMPFILE() && is_task_active(bt->task)) {
ret = loongarch64_get_dumpfile_stack_frame(bt, &nip, &ksp);
}
else {
ret = loongarch64_get_frame(bt, &nip, &ksp);
}
if (!ret)
error(WARNING, "cannot determine starting stack frame for task %lx\n",
bt->task);
if (pcp)
*pcp = nip;
if (spp)
*spp = ksp;
}
/*
* Get the starting point for the active cpu in a diskdump.
*/
static int
loongarch64_get_dumpfile_stack_frame(struct bt_info *bt, ulong *nip, ulong *ksp)
{
const struct machine_specific *ms = machdep->machspec;
struct loongarch64_pt_regs *regs;
ulong epc, sp;
if (!ms->crash_task_regs) {
bt->flags |= BT_REGS_NOT_FOUND;
return FALSE;
}
/*
* We got registers for panic task from crash_notes. Just return them.
*/
regs = &ms->crash_task_regs[bt->tc->processor];
epc = regs->csr_epc;
sp = regs->regs[LOONGARCH64_EF_SP];
if (!epc && !sp) {
bt->flags |= BT_REGS_NOT_FOUND;
return FALSE;
}
if (nip)
*nip = epc;
if (ksp)
*ksp = sp;
bt->machdep = regs;
return TRUE;
}
/*
* Do the work for loongarch64_get_stack_frame() for non-active tasks.
* Get SP and PC values for idle tasks.
*/
static int
loongarch64_get_frame(struct bt_info *bt, ulong *pcp, ulong *spp)
{
if (!bt->tc || !(tt->flags & THREAD_INFO))
return FALSE;
if (!readmem(bt->task + OFFSET(task_struct_thread_reg01),
KVADDR, pcp, sizeof(*pcp),
"thread_struct.regs01",
RETURN_ON_ERROR)) {
return FALSE;
}
if (!readmem(bt->task + OFFSET(task_struct_thread_reg03),
KVADDR, spp, sizeof(*spp),
"thread_struct.regs03",
RETURN_ON_ERROR)) {
return FALSE;
}
return TRUE;
}
static int
loongarch64_init_active_task_regs(void)
{
int retval;
retval = loongarch64_get_crash_notes();
if (retval == TRUE)
return retval;
return loongarch64_get_elf_notes();
}
/*
* Retrieve task registers for the time of the crash.
*/
static int
loongarch64_get_crash_notes(void)
{
struct machine_specific *ms = machdep->machspec;
ulong crash_notes;
Elf64_Nhdr *note;
ulong offset;
char *buf, *p;
ulong *notes_ptrs;
ulong i;
/*
* crash_notes contains per cpu memory for storing cpu states
* in case of system crash.
*/
if (!symbol_exists("crash_notes"))
return FALSE;
crash_notes = symbol_value("crash_notes");
notes_ptrs = (ulong *)GETBUF(kt->cpus*sizeof(notes_ptrs[0]));
/*
* Read crash_notes for the first CPU. crash_notes are in standard ELF
* note format.
*/
if (!readmem(crash_notes, KVADDR, &notes_ptrs[kt->cpus-1],
sizeof(notes_ptrs[kt->cpus-1]), "crash_notes",
RETURN_ON_ERROR)) {
error(WARNING, "cannot read crash_notes\n");
FREEBUF(notes_ptrs);
return FALSE;
}
if (symbol_exists("__per_cpu_offset")) {
/*
* Add __per_cpu_offset for each cpu to form the pointer to the notes
*/
for (i = 0; i < kt->cpus; i++)
notes_ptrs[i] = notes_ptrs[kt->cpus-1] + kt->__per_cpu_offset[i];
}
buf = GETBUF(SIZE(note_buf));
if (!(panic_task_regs = calloc((size_t)kt->cpus, sizeof(*panic_task_regs))))
error(FATAL, "cannot calloc panic_task_regs space\n");
for (i = 0; i < kt->cpus; i++) {
if (!readmem(notes_ptrs[i], KVADDR, buf, SIZE(note_buf), "note_buf_t",
RETURN_ON_ERROR)) {
error(WARNING,
"cannot find NT_PRSTATUS note for cpu: %d\n", i);
goto fail;
}
/*
* Do some sanity checks for this note before reading registers from it.
*/
note = (Elf64_Nhdr *)buf;
p = buf + sizeof(Elf64_Nhdr);
/*
* dumpfiles created with qemu won't have crash_notes, but there will
* be elf notes; dumpfiles created by kdump do not create notes for
* offline cpus.
*/
if (note->n_namesz == 0 && (DISKDUMP_DUMPFILE() || KDUMP_DUMPFILE())) {
if (DISKDUMP_DUMPFILE())
note = diskdump_get_prstatus_percpu(i);
else if (KDUMP_DUMPFILE())
note = netdump_get_prstatus_percpu(i);
if (note) {
/*
* SIZE(note_buf) accounts for a "final note", which is a
* trailing empty elf note header.
*/
long notesz = SIZE(note_buf) - sizeof(Elf64_Nhdr);
if (sizeof(Elf64_Nhdr) + roundup(note->n_namesz, 4) +
note->n_descsz == notesz)
BCOPY((char *)note, buf, notesz);
} else {
error(WARNING,
"cannot find NT_PRSTATUS note for cpu: %d\n", i);
continue;
}
}
/*
* Check the sanity of NT_PRSTATUS note only for each online cpu.
*/
if (note->n_type != NT_PRSTATUS) {
error(WARNING, "invalid NT_PRSTATUS note (n_type != NT_PRSTATUS)\n");
goto fail;
}
if (!STRNEQ(p, "CORE")) {
error(WARNING, "invalid NT_PRSTATUS note (name != \"CORE\"\n");
goto fail;
}
/*
* Find correct location of note data. This contains elf_prstatus
* structure which has registers etc. for the crashed task.
*/
offset = sizeof(Elf64_Nhdr);
offset = roundup(offset + note->n_namesz, 4);
p = buf + offset; /* start of elf_prstatus */
BCOPY(p + OFFSET(elf_prstatus_pr_reg), &panic_task_regs[i],
sizeof(panic_task_regs[i]));
}
/*
* And finally we have the registers for the crashed task. This is
* used later on when dumping backtrace.
*/
ms->crash_task_regs = panic_task_regs;
FREEBUF(buf);
FREEBUF(notes_ptrs);
return TRUE;
fail:
FREEBUF(buf);
FREEBUF(notes_ptrs);
free(panic_task_regs);
return FALSE;
}
static int
loongarch64_get_elf_notes(void)
{
struct machine_specific *ms = machdep->machspec;
int i;
if (!DISKDUMP_DUMPFILE() && !KDUMP_DUMPFILE())
return FALSE;
panic_task_regs = calloc(kt->cpus, sizeof(*panic_task_regs));
if (!panic_task_regs)
error(FATAL, "cannot calloc panic_task_regs space\n");
for (i = 0; i < kt->cpus; i++) {
Elf64_Nhdr *note = NULL;
size_t len;
if (DISKDUMP_DUMPFILE())
note = diskdump_get_prstatus_percpu(i);
else if (KDUMP_DUMPFILE())
note = netdump_get_prstatus_percpu(i);
if (!note) {
error(WARNING,
"cannot find NT_PRSTATUS note for cpu: %d\n", i);
continue;
}
len = sizeof(Elf64_Nhdr);
len = roundup(len + note->n_namesz, 4);
BCOPY((char *)note + len + OFFSET(elf_prstatus_pr_reg),
&panic_task_regs[i], sizeof(panic_task_regs[i]));
}
ms->crash_task_regs = panic_task_regs;
return TRUE;
}
/*
* Accept or reject a symbol from the kernel namelist.
*/
static int
loongarch64_verify_symbol(const char *name, ulong value, char type)
{
if (!strncmp(name, ".L", 2) || !strncmp(name, "L0", 2))
return FALSE;
if (CRASHDEBUG(8) && name && strlen(name))
fprintf(fp, "%08lx %s\n", value, name);
if (STREQ(name, "_text") || STREQ(name, "_stext"))
machdep->flags |= KSYMS_START;
return (name && strlen(name) && (machdep->flags & KSYMS_START) &&
!STRNEQ(name, "__func__.") && !STRNEQ(name, "__crc_"));
}
/*
* Override smp_num_cpus if possible and necessary.
*/
static int
loongarch64_get_smp_cpus(void)
{
return (get_cpus_online() > 0) ? get_cpus_online() : kt->cpus;
}
static ulong
loongarch64_get_page_size(void)
{
return memory_page_size();
}
/*
* Determine where vmalloc'd memory starts.
*/
static ulong
loongarch64_vmalloc_start(void)
{
return first_vmalloc_address();
}
/*
* Calculate and return the speed of the processor.
*/
static ulong
loongarch64_processor_speed(void)
{
unsigned long cpu_hz = 0;
if (machdep->mhz)
return (machdep->mhz);
if (symbol_exists("cpu_clock_freq")) {
get_symbol_data("cpu_clock_freq", sizeof(int), &cpu_hz);
if (cpu_hz)
return(machdep->mhz = cpu_hz/1000000);
}
return 0;
}
/*
* Checks whether given task is valid task address.
*/
static int
loongarch64_is_task_addr(ulong task)
{
if (tt->flags & THREAD_INFO)
return IS_KVADDR(task);
return (IS_KVADDR(task) && ALIGNED_STACK_OFFSET(task) == 0);
}
/*
* 'help -m/M' command output
*/
void
loongarch64_dump_machdep_table(ulong arg)
{
int others = 0;
fprintf(fp, " flags: %lx (", machdep->flags);
if (machdep->flags & KSYMS_START)
fprintf(fp, "%sKSYMS_START", others++ ? "|" : "");
fprintf(fp, ")\n");
fprintf(fp, " kvbase: %lx\n", machdep->kvbase);
fprintf(fp, " identity_map_base: %lx\n", machdep->identity_map_base);
fprintf(fp, " pagesize: %d\n", machdep->pagesize);
fprintf(fp, " pageshift: %d\n", machdep->pageshift);
fprintf(fp, " pagemask: %llx\n", machdep->pagemask);
fprintf(fp, " pageoffset: %lx\n", machdep->pageoffset);
fprintf(fp, " pgdir_shift: %d\n", PGDIR_SHIFT);
fprintf(fp, " ptrs_per_pgd: %lu\n", PTRS_PER_PGD);
fprintf(fp, " ptrs_per_pte: %ld\n", PTRS_PER_PTE);
fprintf(fp, " stacksize: %ld\n", machdep->stacksize);
fprintf(fp, " hz: %d\n", machdep->hz);
fprintf(fp, " memsize: %ld (0x%lx)\n",
machdep->memsize, machdep->memsize);
fprintf(fp, " bits: %d\n", machdep->bits);
fprintf(fp, " back_trace: loongarch64_back_trace_cmd()\n");
fprintf(fp, " processor_speed: loongarch64_processor_speed()\n");
fprintf(fp, " uvtop: loongarch64_uvtop()\n");
fprintf(fp, " kvtop: loongarch64_kvtop()\n");
fprintf(fp, " get_stack_frame: loongarch64_get_stack_frame()\n");
fprintf(fp, " get_stackbase: generic_get_stackbase()\n");
fprintf(fp, " get_stacktop: generic_get_stacktop()\n");
fprintf(fp, " translate_pte: loongarch64_translate_pte()\n");
fprintf(fp, " memory_size: generic_memory_size()\n");
fprintf(fp, " vmalloc_start: loongarch64_vmalloc_start()\n");
fprintf(fp, " is_task_addr: loongarch64_is_task_addr()\n");
fprintf(fp, " verify_symbol: loongarch64_verify_symbol()\n");
fprintf(fp, " dis_filter: generic_dis_filter()\n");
fprintf(fp, " dump_irq: generic_dump_irq()\n");
fprintf(fp, " show_interrupts: generic_show_interrupts()\n");
fprintf(fp, " get_irq_affinity: generic_get_irq_affinity()\n");
fprintf(fp, " cmd_mach: loongarch64_cmd_mach()\n");
fprintf(fp, " get_smp_cpus: loongarch64_get_smp_cpus()\n");
fprintf(fp, " is_kvaddr: generic_is_kvaddr()\n");
fprintf(fp, " is_uvaddr: generic_is_uvaddr()\n");
fprintf(fp, " verify_paddr: generic_verify_paddr()\n");
fprintf(fp, " init_kernel_pgd: NULL\n");
fprintf(fp, " value_to_symbol: generic_machdep_value_to_symbol()\n");
fprintf(fp, " line_number_hooks: NULL\n");
fprintf(fp, " last_pgd_read: %lx\n", machdep->last_pgd_read);
fprintf(fp, " last_pmd_read: %lx\n", machdep->last_pmd_read);
fprintf(fp, " last_ptbl_read: %lx\n", machdep->last_ptbl_read);
fprintf(fp, " pgd: %lx\n", (ulong)machdep->pgd);
fprintf(fp, " pmd: %lx\n", (ulong)machdep->pmd);
fprintf(fp, " ptbl: %lx\n", (ulong)machdep->ptbl);
fprintf(fp, " section_size_bits: %ld\n", machdep->section_size_bits);
fprintf(fp, " max_physmem_bits: %ld\n", machdep->max_physmem_bits);
fprintf(fp, " sections_per_root: %ld\n", machdep->sections_per_root);
fprintf(fp, " machspec: %lx\n", (ulong)machdep->machspec);
}
static void
pt_level_alloc(char **lvl, char *name)
{
size_t sz = PAGESIZE();
void *pointer = malloc(sz);
if (!pointer)
error(FATAL, name);
*lvl = pointer;
}
void
loongarch64_init(int when)
{
switch (when) {
case SETUP_ENV:
machdep->process_elf_notes = process_elf64_notes;
break;
case PRE_SYMTAB:
machdep->verify_symbol = loongarch64_verify_symbol;
machdep->machspec = &loongarch64_machine_specific;
if (pc->flags & KERNEL_DEBUG_QUERY)
return;
machdep->last_pgd_read = 0;
machdep->last_pmd_read = 0;
machdep->last_ptbl_read = 0;
machdep->verify_paddr = generic_verify_paddr;
machdep->ptrs_per_pgd = PTRS_PER_PGD;
break;
case PRE_GDB:
machdep->pagesize = loongarch64_get_page_size();
machdep->pageshift = ffs(machdep->pagesize) - 1;
machdep->pageoffset = machdep->pagesize - 1;
machdep->pagemask = ~((ulonglong)machdep->pageoffset);
if (machdep->pagesize >= 16384)
machdep->stacksize = machdep->pagesize;
else
machdep->stacksize = machdep->pagesize * 2;
pt_level_alloc(&machdep->pgd, "cannot malloc pgd space.");
pt_level_alloc(&machdep->pmd, "cannot malloc pmd space.");
pt_level_alloc(&machdep->ptbl, "cannot malloc ptbl space.");
machdep->kvbase = 0x8000000000000000lu;
machdep->identity_map_base = machdep->kvbase;
machdep->is_kvaddr = generic_is_kvaddr;
machdep->is_uvaddr = generic_is_uvaddr;
machdep->uvtop = loongarch64_uvtop;
machdep->kvtop = loongarch64_kvtop;
machdep->cmd_mach = loongarch64_cmd_mach;
machdep->back_trace = loongarch64_back_trace_cmd;
machdep->get_stack_frame = loongarch64_get_stack_frame;
machdep->vmalloc_start = loongarch64_vmalloc_start;
machdep->processor_speed = loongarch64_processor_speed;
machdep->get_stackbase = generic_get_stackbase;
machdep->get_stacktop = generic_get_stacktop;
machdep->translate_pte = loongarch64_translate_pte;
machdep->memory_size = generic_memory_size;
machdep->is_task_addr = loongarch64_is_task_addr;
machdep->get_smp_cpus = loongarch64_get_smp_cpus;
machdep->dis_filter = generic_dis_filter;
machdep->dump_irq = generic_dump_irq;
machdep->show_interrupts = generic_show_interrupts;
machdep->get_irq_affinity = generic_get_irq_affinity;
machdep->value_to_symbol = generic_machdep_value_to_symbol;
machdep->init_kernel_pgd = NULL;
break;
case POST_GDB:
machdep->section_size_bits = _SECTION_SIZE_BITS;
machdep->max_physmem_bits = _MAX_PHYSMEM_BITS;
if (symbol_exists("irq_desc"))
ARRAY_LENGTH_INIT(machdep->nr_irqs, irq_desc,
"irq_desc", NULL, 0);
else if (kernel_symbol_exists("nr_irqs"))
get_symbol_data("nr_irqs", sizeof(unsigned int),
&machdep->nr_irqs);
loongarch64_stackframe_init();
if (!machdep->hz)
machdep->hz = 250;
break;
case POST_VM:
/*
* crash_notes contains machine specific information about the
* crash. In particular, it contains CPU registers at the time
* of the crash. We need this information to extract correct
* backtraces from the panic task.
*/
if (!ACTIVE() && !loongarch64_init_active_task_regs())
error(WARNING,"cannot retrieve registers for active task%s\n\n",
kt->cpus > 1 ? "s" : "");
break;
}
}
void
loongarch64_display_regs_from_elf_notes(int cpu, FILE *ofp)
{
const struct machine_specific *ms = machdep->machspec;
struct loongarch64_pt_regs *regs;
if (!ms->crash_task_regs) {
error(INFO, "registers not collected for cpu %d\n", cpu);
return;
}
regs = &ms->crash_task_regs[cpu];
if (!regs->regs[LOONGARCH64_EF_SP] && !regs->csr_epc) {
error(INFO, "registers not collected for cpu %d\n", cpu);
return;
}
fprintf(ofp,
" R0: %016lx R1: %016lx R2: %016lx\n"
" R3: %016lx R4: %016lx R5: %016lx\n"
" R6: %016lx R7: %016lx R8: %016lx\n"
" R9: %016lx R10: %016lx R11: %016lx\n"
" R12: %016lx R13: %016lx R14: %016lx\n"
" R15: %016lx R16: %016lx R17: %016lx\n"
" R18: %016lx R19: %016lx R20: %016lx\n"
" R21: %016lx R22: %016lx R23: %016lx\n"
" R24: %016lx R25: %016lx R26: %016lx\n"
" R27: %016lx R28: %016lx R29: %016lx\n"
" R30: %016lx R31: %016lx\n"
" CSR epc : %016lx CSR badv: %016lx\n"
" CSR crmd: %08lx CSR prmd: %08lx\n"
" CSR ecfg: %08lx CSR estat: %08lx\n"
" CSR eneu: %08lx",
regs->regs[LOONGARCH64_EF_R0],
regs->regs[LOONGARCH64_EF_R0 + 1],
regs->regs[LOONGARCH64_EF_R0 + 2],
regs->regs[LOONGARCH64_EF_R0 + 3],
regs->regs[LOONGARCH64_EF_R0 + 4],
regs->regs[LOONGARCH64_EF_R0 + 5],
regs->regs[LOONGARCH64_EF_R0 + 6],
regs->regs[LOONGARCH64_EF_R0 + 7],
regs->regs[LOONGARCH64_EF_R0 + 8],
regs->regs[LOONGARCH64_EF_R0 + 9],
regs->regs[LOONGARCH64_EF_R0 + 10],
regs->regs[LOONGARCH64_EF_R0 + 11],
regs->regs[LOONGARCH64_EF_R0 + 12],
regs->regs[LOONGARCH64_EF_R0 + 13],
regs->regs[LOONGARCH64_EF_R0 + 14],
regs->regs[LOONGARCH64_EF_R0 + 15],
regs->regs[LOONGARCH64_EF_R0 + 16],
regs->regs[LOONGARCH64_EF_R0 + 17],
regs->regs[LOONGARCH64_EF_R0 + 18],
regs->regs[LOONGARCH64_EF_R0 + 19],
regs->regs[LOONGARCH64_EF_R0 + 20],
regs->regs[LOONGARCH64_EF_R0 + 21],
regs->regs[LOONGARCH64_EF_R0 + 22],
regs->regs[LOONGARCH64_EF_R0 + 23],
regs->regs[LOONGARCH64_EF_R0 + 24],
regs->regs[LOONGARCH64_EF_R0 + 25],
regs->regs[LOONGARCH64_EF_R0 + 26],
regs->regs[LOONGARCH64_EF_R0 + 27],
regs->regs[LOONGARCH64_EF_R0 + 28],
regs->regs[LOONGARCH64_EF_R0 + 29],
regs->regs[LOONGARCH64_EF_R0 + 30],
regs->regs[LOONGARCH64_EF_R0 + 31],
regs->csr_epc,
regs->csr_badvaddr,
regs->csr_crmd,
regs->csr_prmd,
regs->csr_ecfg,
regs->csr_estat,
regs->csr_euen);
}
#else /* !LOONGARCH64 */
#include "defs.h"
void
loongarch64_display_regs_from_elf_notes(int cpu, FILE *ofp)
{
return;
}
#endif /* !LOONGARCH64 */