Section 23.1: ARM Development Environment Setup

Setting up a proper ARM UEFI development environment requires cross-compilation toolchains, emulation tools, and debugging infrastructure. This section covers the complete setup process.

Overview

ARM UEFI development differs from x86 in several key ways:

graph TB
    subgraph "Development Host (x86_64)"
        TC[Cross Toolchain<br/>aarch64-linux-gnu-gcc]
        EDK[EDK2 Build System]
        QEMU[QEMU ARM64]
        DBG[Debug Tools<br/>GDB, OpenOCD]
    end

    subgraph "Target Platform (ARM64)"
        FW[UEFI Firmware]
        TFA[TF-A Runtime]
    end

    TC --> EDK
    EDK --> |Build| FW
    QEMU --> |Emulate| FW
    DBG --> |Debug| FW

    style TC fill:#9f9,stroke:#333
    style EDK fill:#9ff,stroke:#333

Toolchain Installation

# Ubuntu/Debian
sudo apt update
sudo apt install -y \
    gcc-aarch64-linux-gnu \
    g++-aarch64-linux-gnu \
    binutils-aarch64-linux-gnu \
    qemu-system-arm \
    device-tree-compiler \
    acpica-tools \
    uuid-dev \
    python3 \
    python3-distutils \
    git \
    make \
    gcc \
    g++

# Verify installation
aarch64-linux-gnu-gcc --version
qemu-system-aarch64 --version

Option 2: ARM Official Toolchain

# Download ARM GNU Toolchain (recommended for production)
TOOLCHAIN_VER="12.3.rel1"
TOOLCHAIN_URL="https://developer.arm.com/-/media/Files/downloads/gnu"

wget "${TOOLCHAIN_URL}/${TOOLCHAIN_VER}/binrel/arm-gnu-toolchain-${TOOLCHAIN_VER}-x86_64-aarch64-none-elf.tar.xz"
sudo tar xf arm-gnu-toolchain-${TOOLCHAIN_VER}-x86_64-aarch64-none-elf.tar.xz -C /opt/

# Add to PATH
echo 'export PATH=/opt/arm-gnu-toolchain-12.3.rel1-x86_64-aarch64-none-elf/bin:$PATH' >> ~/.bashrc
source ~/.bashrc

# Verify
aarch64-none-elf-gcc --version

Option 3: Linaro Toolchain

# Linaro toolchain (alternative)
LINARO_VER="7.5-2019.12"
wget https://releases.linaro.org/components/toolchain/binaries/${LINARO_VER}/aarch64-linux-gnu/gcc-linaro-${LINARO_VER}-x86_64_aarch64-linux-gnu.tar.xz

sudo tar xf gcc-linaro-${LINARO_VER}-x86_64_aarch64-linux-gnu.tar.xz -C /opt/
export PATH=/opt/gcc-linaro-${LINARO_VER}-x86_64_aarch64-linux-gnu/bin:$PATH

EDK2 Environment Setup

Clone Required Repositories

# Create workspace directory
mkdir -p ~/arm-uefi-workspace
cd ~/arm-uefi-workspace

# Clone EDK2 and submodules
git clone https://github.com/tianocore/edk2.git
cd edk2
git submodule update --init

# Clone edk2-platforms for real hardware
cd ..
git clone https://github.com/tianocore/edk2-platforms.git

# Clone edk2-non-osi for binary blobs (some platforms)
git clone https://github.com/tianocore/edk2-non-osi.git

Configure Build Environment

# Set up EDK2 environment
cd ~/arm-uefi-workspace/edk2
source edksetup.sh

# Build BaseTools
make -C BaseTools

# Create Conf/target.txt for ARM64
cat > Conf/target.txt << 'EOF'
ACTIVE_PLATFORM       = ArmVirtPkg/ArmVirtQemu.dsc
TARGET                = DEBUG
TARGET_ARCH           = AARCH64
TOOL_CHAIN_CONF       = Conf/tools_def.txt
TOOL_CHAIN_TAG        = GCC5
BUILD_RULE_CONF       = Conf/build_rule.txt
EOF

GCC5 Toolchain Configuration

EDK2 expects specific toolchain prefixes. Configure in Conf/tools_def.txt:

# Verify GCC5 settings in tools_def.txt
grep -A5 "GCC5_AARCH64" Conf/tools_def.txt

# If using distribution gcc, create symlinks or set prefix
export GCC5_AARCH64_PREFIX=aarch64-linux-gnu-

# Alternatively, for ARM official toolchain
export GCC5_AARCH64_PREFIX=aarch64-none-elf-

Building ArmVirtQemu

Basic Build

cd ~/arm-uefi-workspace/edk2
source edksetup.sh

# Build DEBUG version
build -a AARCH64 -t GCC5 -p ArmVirtPkg/ArmVirtQemu.dsc -b DEBUG

# Build RELEASE version
build -a AARCH64 -t GCC5 -p ArmVirtPkg/ArmVirtQemu.dsc -b RELEASE

# Output location
ls Build/ArmVirtQemu-AARCH64/DEBUG_GCC5/FV/
# QEMU_EFI.fd - Combined SEC + UEFI firmware
# QEMU_VARS.fd - Variable store template

Build Options

# Enable Secure Boot support
build -a AARCH64 -t GCC5 -p ArmVirtPkg/ArmVirtQemu.dsc \
    -D SECURE_BOOT_ENABLE=TRUE

# Enable TPM2 support
build -a AARCH64 -t GCC5 -p ArmVirtPkg/ArmVirtQemu.dsc \
    -D TPM2_ENABLE=TRUE

# Enable HTTP Boot
build -a AARCH64 -t GCC5 -p ArmVirtPkg/ArmVirtQemu.dsc \
    -D NETWORK_HTTP_BOOT_ENABLE=TRUE

# Combined options
build -a AARCH64 -t GCC5 -p ArmVirtPkg/ArmVirtQemu.dsc -b DEBUG \
    -D SECURE_BOOT_ENABLE=TRUE \
    -D TPM2_ENABLE=TRUE \
    -D NETWORK_HTTP_BOOT_ENABLE=TRUE \
    -D DEBUG_ON_SERIAL_PORT=TRUE

QEMU Setup and Testing

Basic QEMU Invocation

# Create flash images
FLASH_SIZE=$((64 * 1024 * 1024))  # 64MB
dd if=/dev/zero of=flash0.img bs=1 count=$FLASH_SIZE
dd if=/dev/zero of=flash1.img bs=1 count=$FLASH_SIZE
dd if=Build/ArmVirtQemu-AARCH64/DEBUG_GCC5/FV/QEMU_EFI.fd of=flash0.img conv=notrunc

# Run QEMU
qemu-system-aarch64 \
    -M virt \
    -cpu cortex-a72 \
    -m 2G \
    -drive if=pflash,format=raw,file=flash0.img \
    -drive if=pflash,format=raw,file=flash1.img \
    -serial stdio \
    -net none

Advanced QEMU Configuration

#!/bin/bash
# run-qemu-arm.sh - Comprehensive QEMU launch script

WORKSPACE=~/arm-uefi-workspace
FW_DIR=${WORKSPACE}/edk2/Build/ArmVirtQemu-AARCH64/DEBUG_GCC5/FV

# Create working directory
mkdir -p ${WORKSPACE}/qemu-run
cd ${WORKSPACE}/qemu-run

# Prepare flash images
cp ${FW_DIR}/QEMU_EFI.fd flash0.img
dd if=/dev/zero of=flash1.img bs=64M count=1

# Create a small FAT filesystem for EFI Shell scripts
dd if=/dev/zero of=esp.img bs=1M count=64
mkfs.vfat esp.img
mkdir -p mnt
sudo mount esp.img mnt
sudo mkdir -p mnt/EFI/BOOT
sudo cp ${FW_DIR}/../Shell.efi mnt/EFI/BOOT/BOOTAA64.EFI 2>/dev/null || true
sudo umount mnt

# Run QEMU with full options
qemu-system-aarch64 \
    -M virt,secure=on,virtualization=on,gic-version=3 \
    -cpu cortex-a76 \
    -smp cpus=4 \
    -m 4G \
    -drive if=pflash,format=raw,file=flash0.img,readonly=on \
    -drive if=pflash,format=raw,file=flash1.img \
    -drive if=virtio,format=raw,file=esp.img \
    -device virtio-net-pci,netdev=net0 \
    -netdev user,id=net0,hostfwd=tcp::2222-:22 \
    -device virtio-gpu-pci \
    -device usb-ehci \
    -device usb-kbd \
    -device usb-mouse \
    -serial stdio \
    -monitor telnet:127.0.0.1:4444,server,nowait \
    -d guest_errors \
    "$@"

QEMU GIC Versions

# GICv2 (older platforms)
-M virt,gic-version=2

# GICv3 (modern servers)
-M virt,gic-version=3

# GICv4 (virtualization optimized)
-M virt,gic-version=max

Debugging Setup

GDB with QEMU

# Terminal 1: Run QEMU with GDB server
qemu-system-aarch64 \
    -M virt \
    -cpu cortex-a72 \
    -m 2G \
    -drive if=pflash,format=raw,file=flash0.img \
    -serial stdio \
    -s -S  # -s: GDB on port 1234, -S: pause at start

# Terminal 2: Connect GDB
aarch64-linux-gnu-gdb

# In GDB
(gdb) target remote localhost:1234
(gdb) add-symbol-file Build/ArmVirtQemu-AARCH64/DEBUG_GCC5/AARCH64/ArmVirtPkg/PrePi/ArmVirtPrePiUniCoreRelocatable/DEBUG/ArmVirtPrePiUniCoreRelocatable.debug 0x40000000
(gdb) break CEntryPoint
(gdb) continue

Debug Print Configuration

// In platform DSC file, configure debug output
[PcdsFixedAtBuild]
  # Serial debug output
  gEfiMdePkgTokenSpaceGuid.PcdDebugPropertyMask|0x2F
  gEfiMdePkgTokenSpaceGuid.PcdDebugPrintErrorLevel|0x8040004F
  gEfiMdePkgTokenSpaceGuid.PcdReportStatusCodePropertyMask|0x07

  # UART configuration for QEMU virt
  gArmPlatformTokenSpaceGuid.PcdSerialDbgRegisterBase|0x09000000
  gEfiMdeModulePkgTokenSpaceGuid.PcdSerialRegisterBase|0x09000000

OpenOCD for Hardware Debugging

# Install OpenOCD
sudo apt install openocd

# Example OpenOCD configuration for Raspberry Pi 4
# rpi4.cfg
source [find interface/ftdi/olimex-arm-usb-ocd-h.cfg]
adapter speed 1000

set _CHIPNAME bcm2711
set _DAP_TAPID 0x4ba00477

transport select jtag

jtag newtap $_CHIPNAME cpu -irlen 4 -expected-id $_DAP_TAPID

set _TARGETNAME $_CHIPNAME.a72
dap create $_CHIPNAME.dap -chain-position $_CHIPNAME.cpu
target create $_TARGETNAME.0 aarch64 -dap $_CHIPNAME.dap -coreid 0
target create $_TARGETNAME.1 aarch64 -dap $_CHIPNAME.dap -coreid 1
target create $_TARGETNAME.2 aarch64 -dap $_CHIPNAME.dap -coreid 2
target create $_TARGETNAME.3 aarch64 -dap $_CHIPNAME.dap -coreid 3

# Run OpenOCD
openocd -f rpi4.cfg

Docker-Based Development

# Dockerfile for ARM UEFI development
FROM ubuntu:22.04

ENV DEBIAN_FRONTEND=noninteractive

RUN apt-get update && apt-get install -y \
    build-essential \
    uuid-dev \
    iasl \
    git \
    gcc-aarch64-linux-gnu \
    g++-aarch64-linux-gnu \
    python3 \
    python3-distutils \
    qemu-system-arm \
    device-tree-compiler \
    && rm -rf /var/lib/apt/lists/*

ENV GCC5_AARCH64_PREFIX=aarch64-linux-gnu-

WORKDIR /workspace

# Clone EDK2
RUN git clone https://github.com/tianocore/edk2.git && \
    cd edk2 && \
    git submodule update --init && \
    make -C BaseTools

COPY build.sh /workspace/
RUN chmod +x /workspace/build.sh

ENTRYPOINT ["/workspace/build.sh"]
# build.sh
#!/bin/bash
cd /workspace/edk2
source edksetup.sh
build -a AARCH64 -t GCC5 -p ArmVirtPkg/ArmVirtQemu.dsc -b DEBUG "$@"
# Build and run container
docker build -t arm-uefi-dev .
docker run -v $(pwd)/output:/workspace/output arm-uefi-dev

VS Code Configuration

Extensions

// .vscode/extensions.json
{
    "recommendations": [
        "ms-vscode.cpptools",
        "webfreak.debug",
        "marus25.cortex-debug",
        "dan-c-underwood.arm"
    ]
}

Launch Configuration

// .vscode/launch.json
{
    "version": "0.2.0",
    "configurations": [
        {
            "name": "QEMU ARM64 Debug",
            "type": "cppdbg",
            "request": "launch",
            "program": "${workspaceFolder}/edk2/Build/ArmVirtQemu-AARCH64/DEBUG_GCC5/FV/QEMU_EFI.fd",
            "miDebuggerServerAddress": "localhost:1234",
            "miDebuggerPath": "aarch64-linux-gnu-gdb",
            "cwd": "${workspaceFolder}",
            "setupCommands": [
                {
                    "text": "set architecture aarch64"
                },
                {
                    "text": "add-symbol-file ${workspaceFolder}/edk2/Build/ArmVirtQemu-AARCH64/DEBUG_GCC5/AARCH64/ArmVirtPkg/PrePi/ArmVirtPrePiUniCoreRelocatable/DEBUG/ArmVirtPrePiUniCoreRelocatable.debug"
                }
            ]
        }
    ]
}

Build Tasks

// .vscode/tasks.json
{
    "version": "2.0.0",
    "tasks": [
        {
            "label": "Build ARM64 DEBUG",
            "type": "shell",
            "command": "source edksetup.sh && build -a AARCH64 -t GCC5 -p ArmVirtPkg/ArmVirtQemu.dsc -b DEBUG",
            "options": {
                "cwd": "${workspaceFolder}/edk2"
            },
            "group": {
                "kind": "build",
                "isDefault": true
            }
        },
        {
            "label": "Run QEMU",
            "type": "shell",
            "command": "${workspaceFolder}/scripts/run-qemu.sh",
            "problemMatcher": []
        }
    ]
}

Environment Variables Summary

# Add to ~/.bashrc or ~/.profile
export WORKSPACE=~/arm-uefi-workspace
export EDK2_PATH=${WORKSPACE}/edk2
export PACKAGES_PATH=${WORKSPACE}/edk2:${WORKSPACE}/edk2-platforms:${WORKSPACE}/edk2-non-osi
export GCC5_AARCH64_PREFIX=aarch64-linux-gnu-

# Build aliases
alias edk2-setup='cd ${EDK2_PATH} && source edksetup.sh'
alias build-arm64='build -a AARCH64 -t GCC5'
alias build-virt='build-arm64 -p ArmVirtPkg/ArmVirtQemu.dsc -b DEBUG'

Verification Checklist

# Verify complete environment
echo "=== Toolchain ===" && aarch64-linux-gnu-gcc --version | head -1
echo "=== QEMU ===" && qemu-system-aarch64 --version | head -1
echo "=== Python ===" && python3 --version
echo "=== IASL ===" && iasl -v | head -1
echo "=== DTC ===" && dtc --version
echo "=== EDK2 ===" && [ -d ~/arm-uefi-workspace/edk2 ] && echo "OK" || echo "Missing"
echo "=== BaseTools ===" && [ -f ~/arm-uefi-workspace/edk2/BaseTools/Source/C/bin/GenFw ] && echo "OK" || echo "Need to build"

Common Issues and Solutions

Issue Cause Solution
“No rule to make target ‘GenFw’” BaseTools not built Run make -C BaseTools
“aarch64-linux-gnu-gcc: not found” Toolchain not in PATH Export GCC5_AARCH64_PREFIX
QEMU hangs at startup Wrong flash image size Ensure 64MB flash images
GDB cannot connect QEMU not started with -s Add -s -S to QEMU command
“ASSERT_EFI_ERROR” Debug assertion Check serial output for details

References


Next: Section 23.2: ARM Boot Architecture - Understanding ARM exception levels and boot flow.


Back to top

UEFI Development Guide is not affiliated with the UEFI Forum. Content is provided for educational purposes.

This site uses Just the Docs, a documentation theme for Jekyll.