diff --git a/arch/mips/Kconfig.debug b/arch/mips/Kconfig.debug
index 6ef42edc7d67..323ad3ec643b 100644
--- a/arch/mips/Kconfig.debug
+++ b/arch/mips/Kconfig.debug
@@ -180,6 +180,15 @@ config DEBUG_LL
 	  this option should not be enabled for kernels that are intended to
 	  be portable.
 
+config DEBUG_LL_EXCEPT
+	bool "Kernel low-level debugging of exceptions"
+	depends on DEBUG_LL
+	help
+	  Say Y here to enable debugging prints for low-level exceptions.
+	  This is helpful if you are debugging a early boot crash when
+	  exceptions happens before trap_init(), or if you are debugging
+	  SMP bringup code.
+
 choice
 	prompt "Kernel low-level debugging port"
 	depends on DEBUG_LL
diff --git a/arch/mips/kernel/Makefile b/arch/mips/kernel/Makefile
index bfac35a64ae5..6641c3370e68 100644
--- a/arch/mips/kernel/Makefile
+++ b/arch/mips/kernel/Makefile
@@ -79,6 +79,7 @@ obj-$(CONFIG_MIPS32_N32)	+= scall64-n32.o signal_n32.o
 obj-$(CONFIG_MIPS32_O32)	+= scall64-o32.o signal_o32.o
 
 obj-$(CONFIG_DEBUG_LL)		+= debug.o
+obj-$(CONFIG_DEBUG_LL_EXCEPT)	+= debug-vec.o
 
 obj-$(CONFIG_KGDB)		+= kgdb.o
 obj-$(CONFIG_PROC_FS)		+= proc.o
diff --git a/arch/mips/kernel/debug-vec.S b/arch/mips/kernel/debug-vec.S
new file mode 100644
index 000000000000..4530bf3d5b75
--- /dev/null
+++ b/arch/mips/kernel/debug-vec.S
@@ -0,0 +1,194 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+/*
+ *  Copyright (C) 2023, Jiaxun Yang <jiaxun.yang@flygoat.com>
+ *  MIPS Low level exception vectors and handler
+ */
+
+#include <asm/addrspace.h>
+#include <asm/asm.h>
+#include <asm/asm-offsets.h>
+#include <asm/asmmacro.h>
+
+
+.balign 0x1000
+LEAF(debug_ll_vecs)
+	.set	push
+	.set	noreorder
+	move	a0, zero
+	PTR_LA  t9, debug_ll_exception
+	jalr	t9
+	nop
+	b	.
+	nop
+
+.org	0x80
+	move	a0, zero
+	PTR_LA  t9, debug_ll_exception
+	jalr	t9
+	nop
+	b	.
+	nop
+
+.org	0x100
+	move	a0, zero
+	PTR_LA  t9, debug_ll_exception
+	jalr	t9
+	nop
+	b	.
+	nop
+
+.org	0x180
+	move	a0, zero
+	PTR_LA  t9, debug_ll_exception
+	jalr	t9
+	nop
+	b	.
+	nop
+
+.org	0x200
+	move	a0, zero
+	PTR_LA  t9, debug_ll_exception
+	jalr	t9
+	nop
+	b	.
+	nop
+
+.org	0x280
+	move	a0, zero
+	PTR_LA  t9, debug_ll_exception
+	jalr	t9
+	nop
+	b	.
+	nop
+
+.org	0x300
+	move	a0, zero
+	PTR_LA  t9, debug_ll_exception
+	jalr	t9
+	nop
+	b	.
+	nop
+
+.org	0x380
+	move	a0, zero
+	PTR_LA  t9, debug_ll_exception
+	jalr	t9
+	nop
+	b	.
+	nop
+
+.org	0x400
+	move	a0, zero
+	PTR_LA  t9, debug_ll_exception
+	jalr	t9
+	nop
+	b	.
+	nop
+
+.org	0x480
+	move	a0, zero
+	PTR_LA  t9, debug_ll_exception
+	jalr	t9
+	nop
+	b	.
+	nop
+
+	.set	pop
+END(debug_ll_vecs)
+
+
+/**
+ * debug_ll_exception() - dump relevant exception state to LL outputs
+ * @a0: pointer to NULL-terminated ASCII string naming the exception
+ *
+ * If a0 is NULL, it will print out exception vector number calculated
+ * from the ra insted. This is useful for debugging exceptions that
+ * happen before the exception handler is set up, or routed from BEV.
+ */
+LEAF(debug_ll_exception)
+	move		k0, ra
+	move		k1, a0
+	PTR_LA		t3, printascii
+
+	PTR_LA		a0, str_newline
+	jalr		t3  /* Call printascii */
+	PTR_LA		a0, str_exp
+	jalr		t3  /* Call printascii */
+	beqz		k1, 1f
+	move		a0, k1
+	jalr		t3  /* Call printascii */
+	b		2f
+1:
+	PTR_SRL		a0, k0, 7
+	PTR_SLL		a0, a0, 7
+	PTR_LA		t2, printhexl
+	jalr		t2
+2:
+	PTR_LA		a0, str_newline
+	jalr		t3  /* Call printascii */
+	PTR_LA		a0, str_newline
+	jalr		t3  /* Call printascii */
+
+#define DUMP_COP0_REG(reg, name, sz, _mfc0)		\
+	PTR_LA		a0, 8f;				\
+	jalr		t3;				\
+	_mfc0		a0, reg;			\
+	PTR_LA		t2, printhex##sz;		\
+	jalr		t2;				\
+	PTR_LA		a0, str_newline;		\
+	jalr		t3;				\
+	TEXT(name)
+
+#if defined(CONFIG_SMP) && defined(CONFIG_CPU_MIPSR6)
+	DUMP_COP0_REG(CP0_GLOBALNUMBER, "GlobalNumber: 0x", 4, mfc0)
+#endif
+#if MIPS_ISA_REV >= 2
+	DUMP_COP0_REG(CP0_EBASE,    "EBase:    0x", l, MFC0)
+#endif
+#ifdef CONFIG_64BIT
+	DUMP_COP0_REG(CP0_XCONTEXT, "Xcontext: 0x", 8, dmfc0)
+#else
+	DUMP_COP0_REG(CP0_CONTEXT,  "Context:  0x", 4, mfc0)
+#endif
+	DUMP_COP0_REG(CP0_CAUSE,    "Cause:    0x", 4, mfc0)
+	DUMP_COP0_REG(CP0_STATUS,   "Status:   0x", 4, mfc0)
+	DUMP_COP0_REG(CP0_EPC,      "EPC:      0x", l, MFC0)
+	DUMP_COP0_REG(CP0_BADVADDR, "BadVAddr: 0x", l, MFC0)
+#if MIPS_ISA_REV >= 6
+	DUMP_COP0_REG(CP0_BADINSTR, "BadInstr: 0x", 4, mfc0)
+#endif
+	PTR_LA		a0, str_newline
+	jalr		t3  /* Call printascii */
+	jr		k0
+	END(debug_ll_exception)
+
+.pushsection	.rodata.str
+str_exp: .asciiz "Low level exception: "
+str_newline: .asciiz "\r\n"
+.popsection
+
+NESTED(setup_debug_ll_exception, 0, ra)
+	PTR_LA		t0, debug_ll_vecs
+#if MIPS_ISA_REV >= 2
+	/* Set ebase to debug_ll_vecs */
+#if defined (CONFIG_64BIT) && !defined(KBUILD_64BIT_SYM32)
+	ori		t0, MIPS_EBASE_WG
+#endif
+	MTC0		t0, CP0_EBASE
+#else
+	/* Copy debug_ll_vecs to start of KSEG0 */
+	PTR_LI		t1, CKSEG0
+	PTR_ADDIU	t2, t0, 0x400 /* Only copy 0x400 as that is what reserved on old systems */
+copy_word:
+	PTR_LW		t3, 0(t0)
+	PTR_SW		t3, 0(t1)
+	PTR_ADDIU	t0, t0, PTRSIZE
+	PTR_ADDIU	t1, t1, PTRSIZE
+	PTR_BNE		t0, t2, copy_word
+#endif
+	/* Clear BEV bit in status register */
+	mfc0		t0, CP0_STATUS
+	and		t0, t0, ~ST0_BEV
+	mtc0		t0, CP0_STATUS
+	jr		ra
+	END(setup_debug_ll_exception)
diff --git a/arch/mips/kernel/head.S b/arch/mips/kernel/head.S
index b825ed4476c7..9be1c4ce6324 100644
--- a/arch/mips/kernel/head.S
+++ b/arch/mips/kernel/head.S
@@ -106,6 +106,10 @@ NESTED(kernel_entry, 16, sp)			# kernel entry point
 	LONG_S		a2, fw_arg2
 	LONG_S		a3, fw_arg3
 
+#ifdef CONFIG_DEBUG_LL_EXCEPT
+	jal		setup_debug_ll_exception
+#endif
+
 	MTC0		zero, CP0_CONTEXT	# clear context register
 #ifdef CONFIG_64BIT
 	MTC0		zero, CP0_XCONTEXT
