Johnny Bakker
Johnny Bakker

Reputation: 5

JTAG Unable to fetch EL3 registers in EL2 ARM64

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?

Debugging in EL3:

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

Answers (1)

Johnny Bakker
Johnny Bakker

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

Related Questions