Reputation: 5
I recently started experimenting with some baremetal development on my Raspberry PI zero 2W. I'm trying to get debugging and semihosting to work using a j-link debugger and OpenOCD. I use VSCode as my code editor and got the debugger working. After I edited my armstub to start the kernel at EL2 it fails to fetch the EL3 registers. Can anyone tell me why this is happening?
When i try debugging in EL2 mode no registers are shown and got a message in debug window that it failed to fetch EL3 registers. I've tried setting several bits in the registers of the processor but no luck.
armstub8.S (remove everything from _start until in_el2 to boot in EL3):
#define BIT(x) (1 << (x))
.global _start
// Drop from EL3 to EL2
_start:
// Set up Secure Configuration Register (SCR_EL3)
// NS = 1 -> Switch to Non-Secure world
// RW = 1 -> AArch64 execution state
// IRQ and FIQ = 0 -> Enable IRQ and FIQ in lower ELs
mov x0, #(1 << 0) // NS (Non-Secure)
orr x0, x0, #(1 << 10) // RW (AArch64 Execution)
orr x0, x0, #(1 << 15) // Set EDPD: External Debug Protection Disable (Allow debugging in lower ELs)
msr SCR_EL3, x0 // Write to SCR_EL3
mrs x0, MDCR_EL2 // Read current MDCR_EL2 value
orr x0, x0, #(1 << 11) // Set TDRA: Enable EL2 debug register access
orr x0, x0, #(1 << 10) // Set TDOSA: Allow debug exceptions
orr x0, x0, #(1 << 3) // Set TDA: Allow EL1 debug access from EL2
msr MDCR_EL2, x0 // Write back modified value
// Set up the Saved Program Status Register (SPSR_EL3)
// M[3:0] = 0b1001 (EL2h mode)
// I = 0 -> Enable IRQ
// F = 0 -> Enable FIQ
// D = 0 -> Enable Debug Exceptions
mov x0, #(9 << 0) // EL2h mode
orr x0, x0, #(7 << 6)
msr SPSR_EL3, x0 // Write to SPSR_EL3
// Set ELR_EL3 to the kernel entry point in EL2
ldr x0, =in_el2
msr ELR_EL3, x0
// Execute exception return (eret) -> Switch to EL2
eret
in_el2:
// Disable interrupts
//msr daifset, #0xf
// Park cores 1, 2, and 3 by placing them into a spin loop
mrs x0, MPIDR_EL1 // Read MPIDR (multiprocessor ID register)
and x0, x0, #0x3 // Mask off core ID
cmp x0, #0 // Is this core 0?
b.eq boot_core0 // If core 0, jump to boot_core0
b park_core // Otherwise, park the core
park_core:
wfi // Wait for interrupt (parked)
b park_core // Stay in loop
boot_core0:
// Core 0 continues boot
ldr x1, =0x80000 // Load kernel entry address
br x1 // Jump to kernel
.ltorg
.org 0xf0
.globl stub_magic
stub_magic:
.word 0x5afe570b
.org 0xf4
.globl stub_version
stub_version:
.word 0
.org 0xfc
.globl kernel_entry32
kernel_entry32:
.word 0x80000
start.S:
.section ".text.boot"
.global _start
_start:
// Set up the stack pointer
ldr x0, =__main_stack_
mov sp, x0
// Zero out the BSS section
ldr x1, =__bss_start
ldr x2, =__bss_end
mov x3, #0 // Zero value
zero_bss:
cmp x1, x2
b.ge jump_to_kernel
str x3, [x1], #8 // Store zero and increment address
b zero_bss
jump_to_kernel:
// Jump to kernel_main
bl kernel_main
hang:
wfi // Wait for interrupt (low-power idle)
b hang // Infinite loop
kernel.c:
void kernel_main(void)
{
unsigned int current_el;
asm volatile ("mrs %0, CurrentEL" : "=r" (current_el));
current_el = (current_el >> 2) & 0b11;
while (1) { }
}
link.ld
ENTRY(_start)
SECTIONS
{
. = 0x80000;
__main_stack_ = .;
.text : {
KEEP(*(.text.boot))
*(.text*)
}
.rodata : {
*(.rodata*)
}
PROVIDE(_data = .);
.data : {
*(.data*)
}
.bss (NOLOAD): ALIGN(16) {
__bss_start = .;
*(.bss*)
__bss_end = .;
}
/DISCARD/ : { *(.comment) *(.gnu*) *(.note*) *(.eh_frame*) }
}
config.txt
enable_jtag_gpio=1
arm_64bit=1
kernel_old=1
armstub=armstub8.bin
kernel=kernel8.img
openocd command: openocd -f /interface/jlink.cfg -f raspi.conf -d3 -c "init; arm semihosting enable"
raspi.conf:
transport select jtag
reset_config trst_and_srst
adapter_khz 4000
jtag_ntrst_delay 500
if { [info exists CHIPNAME] } {
set _CHIPNAME $CHIPNAME
} else {
set _CHIPNAME rpi3
}
if { [info exists DAP_TAPID] } {
set _DAP_TAPID $DAP_TAPID
} else {
set _DAP_TAPID 0x4ba00477
}
jtag newtap $_CHIPNAME tap -irlen 4 -ircapture 0x1 -irmask 0xf -expected-id $_DAP_TAPID -enable
dap create $_CHIPNAME.dap -chain-position $_CHIPNAME.tap
set _TARGETNAME $_CHIPNAME.a53
set _CTINAME $_CHIPNAME.cti
set DBGBASE {0x80010000 0x80012000 0x80014000 0x80016000}
set CTIBASE {0x80018000 0x80019000 0x8001a000 0x8001b000}
set _cores 4
for { set _core 0 } { $_core < $_cores } { incr _core } {
cti create $_CTINAME.$_core -dap $_CHIPNAME.dap -ap-num 0 \
-ctibase [lindex $CTIBASE $_core]
target create $_TARGETNAME.$_core aarch64 \
-dap $_CHIPNAME.dap -coreid $_core \
-dbgbase [lindex $DBGBASE $_core] -cti $_CTINAME.$_core
$_TARGETNAME.$_core configure -event reset-assert-post "aarch64 dbginit"
}
launch.json
{
"version": "0.2.0",
"configurations": [
{
"name": "(gdb) Attach",
"type": "cppdbg",
"cwd": "${workspaceFolder}",
"request": "launch",
"program": "${workspaceFolder}/kernel8.elf",
"MIMode": "gdb",
"miDebuggerPath": "C:\\Tools\\arm-gnu-toolchain\\bin\\aarch64-none-elf-gdb.exe",
"miDebuggerArgs": "kernel8.elf",
"targetArchitecture": "arm64",
"setupCommands": [
{ "text": "target remote :3333" },
{ "text": "monitor init" },
{ "text": "monitor reset halt" },
{ "text": "monitor arm semihosting enable" },
{ "text": "load C:/Dev/baremetal/kernel8.elf" },
{ "text": "break *0x80000" },
//{ "text": "monitor reg pc 0x80000" },
],
}
]
}
EDIT: armstub8.S (After removing none existed registers)
#define BIT(x) (1 << (x))
#define SCR_RW BIT(10)
#define SCR_HCE BIT(8)
#define SCR_SMD BIT(7)
#define SCR_RES1_5 BIT(5)
#define SCR_RES1_4 BIT(4)
#define SCR_NS BIT(0)
#define SCR_VAL \
(SCR_RW | SCR_RES1_5 | SCR_RES1_4 | SCR_NS)
#define SPSR_EL3_D BIT(9)
#define SPSR_EL3_A BIT(8)
#define SPSR_EL3_I BIT(7)
#define SPSR_EL3_F BIT(6)
#define SPSR_EL3_MODE_EL2H (BIT(3) | BIT(0))
#define SPSR_EL3_VAL \
(SPSR_EL3_D | SPSR_EL3_A | SPSR_EL3_I | SPSR_EL3_F | SPSR_EL3_MODE_EL2H)
#define HCR_RW BIT(31)
#define HCR_VAL \
(HCR_RW)
#define SCTLR_RESERVED \
(BIT(23) | BIT(22) | BIT(20) | BIT(11))
#define SCTLR_VAL \
(SCTLR_RESERVED)
#define MDCR_VAL (0)
.global _start
// Drop from EL3 to EL2
_start:
ldr x0, =MDCR_VAL
msr MDCR_EL3, x0
ldr x0, =MDCR_VAL
msr MDCR_EL2, x0
ldr x0, =SCTLR_VAL
msr SCTLR_EL1, x0
ldr x0, =SCTLR_VAL
msr SCTLR_EL2, x0
ldr x0, =HCR_VAL
msr HCR_EL2, x0
ldr x0, =SCR_VAL
msr SCR_EL3, x0
ldr x0, =SPSR_EL3_VAL
msr SPSR_EL3, x0
ldr x0, =in_el2
msr ELR_EL3, x0
eret
in_el2:
// Disable interrupts
//msr daifset, #0xf
// Park cores 1, 2, and 3 by placing them into a spin loop
mrs x0, MPIDR_EL1 // Read MPIDR (multiprocessor ID register)
and x0, x0, #0x3 // Mask off core ID
cmp x0, #0 // Is this core 0?
b.eq boot_core0 // If core 0, jump to boot_core0
b park_core // Otherwise, park the core
park_core:
wfi // Wait for interrupt (parked)
b park_core // Stay in loop
boot_core0:
// Core 0 continues boot
ldr x1, =0x80000 // Load kernel entry address
br x1 // Jump to kernel
.ltorg
.org 0xf0
.globl stub_magic
stub_magic:
.word 0x5afe570b
.org 0xf4
.globl stub_version
stub_version:
.word 0
.org 0xfc
.globl kernel_entry32
kernel_entry32:
.word 0x80000
OpenOCD log
Debug: 120466 44834 armv8.c:1883 armv8_get_gdb_reg_list(): Creating Aarch64 register list for target bcm2837.cpu0
Debug: 120539 44845 armv8_dpm.c:661 dpmv8_read_reg(): READ: ESR_EL2, 96000000
Debug: 120540 44849 gdb_server.c:400 gdb_log_outgoing_packet(): [bcm2837.cpu0] {1} sending packet: $00000096#8f
Debug: 120613 44861 gdb_server.c:383 gdb_log_incoming_packet(): [bcm2837.cpu0] {1} received packet: p49
Debug: 120614 44865 armv8.c:1883 armv8_get_gdb_reg_list(): Creating Aarch64 register list for target bcm2837.cpu0
Debug: 120687 44874 armv8_dpm.c:661 dpmv8_read_reg(): READ: SPSR_EL2, fa1de37b
Debug: 120688 44877 gdb_server.c:400 gdb_log_outgoing_packet(): [bcm2837.cpu0] {1} sending packet: $7be31dfa#8d
Debug: 120689 44882 gdb_server.c:383 gdb_log_incoming_packet(): [bcm2837.cpu0] {1} received packet: p4a
Debug: 120690 44888 armv8.c:1883 armv8_get_gdb_reg_list(): Creating Aarch64 register list for target bcm2837.cpu0
Error: 120727 44895 armv8_dpm.c:251 dpmv8_exec_opcode(): Opcode 0xd53e4020, DSCR.ERR=1, DSCR.EL=2
Debug: 120854 44907 armv8_dpm.c:1327 armv8_dpm_handle_exception(): Exception taken to EL 2, DLR=0x58a1fded2e9eba00 DSPSR=0x000003c9
Debug: 120855 44913 armv8_dpm.c:551 armv8_dpm_modeswitch(): restoring mode, cpsr = 0x000003c9
Debug: 120856 44917 armv8_dpm.c:589 armv8_dpm_modeswitch(): target_el = 2, last_el = 2
Error: 120857 44921 armv8_dpm.c:680 dpmv8_read_reg(): Failed to read ELR_EL3 register
Debug: 120858 44924 gdb_server.c:1488 gdb_error(): Reporting 0 to GDB as generic error
Debug: 120859 44928 gdb_server.c:400 gdb_log_outgoing_packet(): [bcm2837.cpu0] {1} sending packet: $E0E#ba
Updated start.S
#include "sysregs.h"
.section ".text.boot"
.global _start
_start:
ldr x0, =SCTLR_VAL
msr SCTLR_EL1, x0
ldr x0, =SCTLR_VAL
msr SCTLR_EL2, x0
ldr x0, =HCR_VAL
msr HCR_EL2, x0
ldr x0, =SCR_VAL
msr SCR_EL3, x0
ldr x0, =SPSR_EL3_VAL
msr SPSR_EL3, x0
ldr x0, =in_el2
msr ELR_EL3, x0
isb
eret
in_el2:
isb
mrs x0, MPIDR_EL1
and x0, x0, #0x3
cmp x0, #0
b.eq setup_stack
b hang
setup_stack:
ldr x0, =__main_stack_
mov sp, x0
// Load bss start and end address into registers
ldr x1, =__bss_start
ldr x2, =__bss_end
mov x3, #0
zero_bss:
cmp x1, x2
b.ge jump_to_kernel
str x3, [x1], #8
b zero_bss
jump_to_kernel:
bl main
hang:
wfi
b hang
Upvotes: 0
Views: 37
Reputation: 5
As @Siguza said it is not possible to debug the EL3
registers from EL2
. But I found a solution for my problem. It is possible to tell gdb which registers to fetch by creating a XML file like this:
<?xml version="1.0"?>
<!DOCTYPE target SYSTEM "gdb-target.dtd">
<target>
<architecture>aarch64</architecture>
<feature name="org.gnu.gdb.aarch64.core">
<reg name="x0" bitsize="64"/>
<reg name="x1" bitsize="64"/>
<reg name="x2" bitsize="64"/>
<reg name="x3" bitsize="64"/>
<reg name="x4" bitsize="64"/>
<reg name="x5" bitsize="64"/>
<reg name="x6" bitsize="64"/>
<reg name="x7" bitsize="64"/>
<reg name="x8" bitsize="64"/>
<reg name="x9" bitsize="64"/>
<reg name="x10" bitsize="64"/>
<reg name="x11" bitsize="64"/>
<reg name="x12" bitsize="64"/>
<reg name="x13" bitsize="64"/>
<reg name="x14" bitsize="64"/>
<reg name="x15" bitsize="64"/>
<reg name="x16" bitsize="64"/>
<reg name="x17" bitsize="64"/>
<reg name="x18" bitsize="64"/>
<reg name="x19" bitsize="64"/>
<reg name="x20" bitsize="64"/>
<reg name="x21" bitsize="64"/>
<reg name="x22" bitsize="64"/>
<reg name="x23" bitsize="64"/>
<reg name="x24" bitsize="64"/>
<reg name="x25" bitsize="64"/>
<reg name="x26" bitsize="64"/>
<reg name="x27" bitsize="64"/>
<reg name="x28" bitsize="64"/>
<reg name="x29" bitsize="64"/>
<reg name="x30" bitsize="64"/>
<reg name="sp" bitsize="64"/>
<reg name="pc" bitsize="64"/>
</feature>
</target>
And tell gdb to use the target description using the set tdesc filename <target>.xml
command.
After adding this command to my launch.json file VSCode was able to fetch the specified registers in EL3
and EL2
mode.
launch.json
{
// Use IntelliSense to learn about possible attributes.
// Hover to view descriptions of existing attributes.
// For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
"version": "0.2.0",
"configurations": [
{
"name": "(gdb) Attach",
"type": "cppdbg",
"cwd": "${workspaceFolder}",
"request": "launch",
"program": "${workspaceFolder}/kernel8.elf",
"MIMode": "gdb",
"miDebuggerPath": "C:\\Tools\\arm-gnu-toolchain\\bin\\aarch64-none-elf-gdb.exe",
"miDebuggerArgs": "kernel8.elf",
"targetArchitecture": "arm64",
"setupCommands": [
{ "text": "target remote :3333" },
{ "text": "monitor init" },
{ "text": "monitor reset halt" },
//{ "text": "monitor arm semihosting enable" },
{ "text": "load C:/Dev/baremetal/kernel8.elf" },
{ "text": "set tdesc filename C:/Dev/baremetal/target.xml" },
{ "text": "break *0x80000" },
//{ "text": "monitor reg x0 0x1" },
],
"logging": {
"trace": true,
"engineLogging": true,
"programOutput": true,
"traceResponse": true
},
"externalConsole": false
}
]
}
Upvotes: 0