A real embedded system will need initialization before any other code is executed. You will create a minimal reset handler, putting all but one processor to sleep, and executing the application on just one processor.
Create a new file,
startup_el3.s, with the following contents:
.section BOOT,"ax" // Define an executable ELF section, BOOT
.type el3_entry, "function"
MRS x0, MPIDR_EL1 // Read Affinity register
AND x0, x0, #0xFFFF // Mask off Aff0/Aff1 fields
CBZ x0, boot // Branch to boot if Aff0/Aff1 are zero (Core 0 of Cluster 0)
sleep: // Else put processor to sleep
MSR CPTR_EL3, xzr // Clear all trap bits
// Branch to scatter loading and C library init code
Build the startup code with:
armclang -c -g --target=aarch64-arm-none-eabi -march=armv8-a startup_el3.s
The compiler identifies
.s files as assembler source.
register provides a CPU identification mechanism. The
Aff1 bitfields let us check which numbered processor in a cluster the code is running on. This startup code sends all but one processor to sleep.
Setting CPTR_EL3 to zero disables various instruction traps which allows the C library init code to proceed.
Modify the scatter file so that the startup code goes into the root region
ROM_EXEC. This must be located as the
FIRST section in the region, so that it is at exactly
0x0, and so is executed when the processors start.
startup_el3.o (BOOT, +FIRST)
Link the objects, specifying the symbol
el3_entry as the entry point.
armlink --scatter=scatter.txt hello.o startup_el3.o -o hello.axf --entry=el3_entry
The entry point is used by the linker to determine which code is necessary to keep. It is also used by debuggers to know where to start execution from.
You can now successfully execute on the FVP without the additional
pctl.startup parameter from before.
FVP_Base_AEMv8 -a hello.axf
A single “Hello World!” message is displayed.