From Reset to Control: Disabling Interrupts on ARM Bare Metal

Published: (January 2, 2026 at 01:59 AM EST)
3 min read
Source: Dev.to

Source: Dev.to

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 starts 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 _vectors to _start
  • _start contained 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:

ModeValue
User0x10
FIQ0x11
IRQ0x12
Supervisor0x13
Abort0x17
Undefined0x1B
System0x1F

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

    .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 the 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 will build on this minimal bootstrap to construct a usable C runtime for bare‑metal ARM systems.

Back to Blog

Related posts

Read more »