Bare-metal execution on ARMv7 begins at the reset vector, long before any C environment exists. When a Cortex-A9 leaves reset under QEMU’s vexpress-a9 model, the processor enters Supervisor mode with interrupts masked and the MMU disabled. The stack pointer is undefined, no memory sections are initialized, and no handlers are installed. Execution begins only with a defined program counter and CPSR value.
This post examines that earliest stage of execution and shows how a minimal block of startup assembly takes control after reset: explicitly masking interrupts, verifying the processor’s mode, and halting in a known state. This establishes a predictable baseline before introducing stacks, memory initialization, and eventually a C runtime.
From Reset Vector to _start
At reset, ARMv7 defines the following initial conditions:
-
Mode: Supervisor (
0b10011) - IRQ mask (CPSR.I): 1 (disabled)
- FIQ mask (CPSR.F): 1 (disabled)
- Instruction set: ARM state
- MMU: Disabled
Although interrupts are architecturally masked at reset, early startup code should not rely on this implicit state. Explicitly disabling interrupts ensures a deterministic environment before installing a vector table or exception handlers.
The previous post Bare Metal ARM Boot: Understanding the Reset Vector and First Instructions established a minimal vector table and reset vector:
- Vector table placed at address 0x0 in flash
- Reset vector branches from
_vectorsto_start -
_startcontained an infinite loop
This post builds on that example by giving _start its first real responsibility: enforcing interrupt masking before halting.
CPSR Overview
The Current Program Status Register (CPSR) controls key aspects of execution:
- Bits [31:28] — Condition flags (N, Z, C, V)
- Bit 7 — IRQ disable (I)
- Bit 6 — FIQ disable (F)
- Bits [4:0] — Mode bits
Common mode encodings:
-
0x10— User -
0x11— FIQ -
0x12— IRQ -
0x13— Supervisor -
0x17— Abort -
0x1B— Undefined -
0x1F— System
Disabling Interrupts Explicitly
The cpsid instruction (Change Processor State, Interrupt Disable) provides direct control over interrupt masking:
-
cpsid i— Disable IRQ -
cpsid f— Disable FIQ -
cpsid if— Disable both
cpsid is a privileged instruction, so it can execute only in modes such as Supervisor. Issuing cpsid if at startup prevents accidental exception entry until valid handlers are in place.
Minimal Startup Assembly
startup.s:
.section .vectors, "ax"
.global _vectors
_vectors:
b _start @ Reset vector: branch to startup
.section .text
.global _start
_start:
@ Disable IRQ and FIQ interrupts
cpsid if
@ Infinite loop
halt:
b halt
Verifying Behavior with GDB
A breakpoint confirms that execution flows from the reset vector into _start:
(gdb) break _start
Breakpoint 1 at 0x4: file startup.s, line 9.
(gdb) continue
Continuing.
Breakpoint 1, _start () at startup.s:9
9 cpsid if
Examining the CPSR before and after executing cpsid if:
(gdb) info registers cpsr
cpsr 0x400001d3 1073742291
(gdb) stepi
10 b .
(gdb) info registers cpsr
cpsr 0x400001d3 1073742291
The CPSR value remains unchanged because interrupts were already masked at reset.
Binary view:
(gdb) print/t $cpsr
$1 = 1000000000000000000000111010011
Interpretation:
- Bits[4:0] =
10011→ Supervisor mode - Bit 6 = 1 → FIQ disabled
- Bit 7 = 1 → IRQ disabled
This confirms that startup code is executing in a privileged mode with both interrupt sources masked.
Conclusion
The processor now transitions cleanly from reset into startup assembly that establishes a controlled execution state. Interrupt masking is explicitly enforced, and the processor mode is verified. With this foundation in place, subsequent steps - stack setup, .data and .bss initialization, and entry into main() can be introduced safely.
The next post builds on this minimal bootstrap to construct a usable C runtime for bare-metal ARM systems.
Top comments (0)