Understanding and Practicing ARMv8 Bare Metal Code – The Concept of Bare Metal Code

This example code is derived from the sample code included in the ARM Development Studio:

Arm\Development\Studio 2022.0\examples\docs\baremetal_v8.

The following is an interpretation based on this type of example, gradually analyzing the implementation logic of bare metal code in conjunction with the characteristics of the ARMv8 architecture.

1. What is Bare Metal Code?

Bare Metal code refers to programs that run directly on hardware without relying on an operating system. It is typically used in embedded systems, requiring developers to manually configure processor states, manage memory, initialize peripherals, etc. The ARMv8-A architecture is a 64-bit architecture that supports both AArch64 and AArch32 execution states, with bare metal code primarily using AArch64 as it is the mainstream mode of ARMv8.

In the ARM Development Studio, bare metal examples usually include startup code, simple C programs (like Hello World), and scatter files that define memory layouts.

2. Structure of Example Code

The following is a typical structure of ARMv8 bare metal code examples, based on the style of ARM Development Studio:

(1) Startup Code (startup.s)

Startup code is usually written in assembly language, responsible for initializing the processor state and jumping to C code. Here is a simplified version:

assembly

    .section .text.boot, "ax"    // Define startup code section
    .global el3_entry            // Define entry point

el3_entry:
    // Set exception level (EL3 is the highest level, usually used for startup)
    MRS X0, CurrentEL           // Read current exception level
    CMP X0, #0xC                // Check if in EL3 (0b11 << 2)
    B.NE hang                   // If not in EL3, jump to infinite loop

    // Initialize stack pointer
    LDR X0, =0x04010000         // Stack top address (refer to scatter file)
    MOV SP, X0                  // Set stack pointer

    // Clear BSS section
    LDR X0, =__bss_start        // BSS section start address
    LDR X1, =__bss_end          // BSS section end address
clear_bss:
    CMP X0, X1
    B.GE bss_done
    STR XZR, [X0], #8           // Clear and increment address
    B clear_bss
bss_done:

    // Jump to C entry point
    BL main                     // Call main function
hang:
    B hang                      // Infinite loop to prevent return

Interpretation:

  • Exception Level (EL): ARMv8 has four exception levels (EL0-EL3), and bare metal code typically starts from EL3 (highest privilege).MRS instruction reads the current EL,CMP checks if correct.

  • Stack Pointer (SP): The stack is used for function calls and must be set manually. The address0x04010000 comes from the subsequent scatter file.

  • BSS Clearing: The BSS section stores uninitialized global variables and needs to be cleared at startup.XZR is the zero register.

  • Jump to main: After initialization, the C languagemain function is called.

(2) C Code (hello.c)

The C code implements specific functionality, here exemplified by outputting “Hello World”, which typically requires redirectingprintf to hardware (like UART):

c

#include<stdio.h>

// UART register address (hypothetical hardware address)
#define UART0_DR (*(volatile unsigned int*)0x09000000)

void uart_putc(char c){
    UART0_DR = c; // Write character to UART data register
}

int fputc(int ch, FILE *f){
    uart_putc((char)ch); // Redirect standard output to UART
    return ch;
}

int main(void){
    printf("Hello World!\n"); // Output to UART
    while(1); // Infinite loop to prevent program exit
    return 0;
}

Interpretation:

  • UART Output: In a bare metal environment, there is no operating system support forprintf, so character output must be implemented manually. Here, the assumed UART register address is0x09000000 (actual address should refer to the hardware manual).

  • fputc Redirection: Theprintf low-level call is redirected tofputc, implementing hardware output throughuart_putc.

  • Infinite Loop: Bare metal programs do not have operating system scheduling, somain needs to remain in an infinite loop after returning.

(3) Scatter File (scatter.txt)

The scatter file defines the memory layout, informing the linker how to allocate code and data:

ROM_LOAD 0x00000000 0x00010000 {
    ROM_EXEC +0x0 0x10000 {
        * (InRoot$$Sections)    // Startup code
        * (+RO)                // Read-only data (like code)
    }
    RAM_EXEC 0x04000000 0x10000 {
        * (+RW, +ZI)           // Read-write data and zero-initialized data
    }
    ARM_LIB_STACKHEAP 0x04010000 EMPTY 0x10000 {
        // Stack and heap space
    }
}

Interpretation:

  • ROM_LOAD: Program load address (usually Flash), starting from0x00000000.

  • ROM_EXEC: Read-only segment during execution, including startup code and constants.

  • RAM_EXEC: Read-write segment (like variables), located at0x04000000.

  • STACKHEAP: Stack and heap space, this address is used when initializing the stack pointer.

3. Compilation and Execution

Using the toolchain of ARM Development Studio (armclang and armlink) to compile:

bash

armclang -c -g --target=aarch64-arm-none-eabi -march=armv8-a startup.s
armclang -c -g --target=aarch64-arm-none-eabi -march=armv8-a hello.c
armlink --scatter=scatter.txt startup.o hello.o -o hello.axf --entry=el3_entry
  • Parameter Explanation:

    • –target=aarch64-arm-none-eabi: Specifies AArch64 architecture, no operating system.

    • -march=armv8-a: Specifies ARMv8-A architecture.

    • –entry=el3_entry: Specifies entry point asel3_entry.

At runtime, typically using ARM’s FVP (Fixed Virtual Platform) simulator:

FVP_Base_AEMvA -f hello.axf

4. ARMv8 Features Reflected in Code

  • AArch64 Registers: The code uses X0-X30 (64-bit registers) instead of AArch32’s R0-R15.

  • Exception Levels: Supports EL0-EL3, bare metal code starts from EL3 for full control.

  • Memory Management: No MMU configuration, directly using physical addresses, suitable for simple bare metal applications.

5. Advantages of Referencing ARM Development Studio Examples

The example code from ARM Development Studio (like Hello World) provides a standardized bare metal development process:

  • Modularity: Startup code is separated from C code, facilitating expansion.

  • Toolchain Support: Seamless integration witharmclang and armlink.

  • Hardware Abstraction: Flexibly define memory layout through scatter files.

If you need more complex examples (like interrupt handling or peripheral drivers), you can extend the above code, for example, by adding GIC (Generic Interrupt Controller) initialization.

6. Conclusion

The above example demonstrates the basic framework of ARMv8 bare metal code, from startup and initialization to output, referencing the typical style of ARM Development Studio. The challenges of bare metal development lie in hardware details (like register addresses) and manual resource management. Through this virtual environment, we can gain a deeper understanding of how the ARMv8 architecture works.

Understanding and Practicing ARMv8 Bare Metal Code - The Concept of Bare Metal Code

Leave a Comment