Hello World on ZynqMP R5 with Zephyr OS

Table of Contents

The ZynqMP SoC has two Cortex-R5F cores sitting alongside the quad-core Cortex-A53 APU. Most people leave them unused. Here’s how to bring them up with Zephyr RTOS and communicate with Linux over RPMsg — the entire workflow from a running Linux system, no JTAG required.

Why R5 + Zephyr

The R5 cores run at 500MHz and are completely independent from the Linux scheduler. No arch timer interference. No PREEMPT_RT wakeup path. When a hardware interrupt fires, the R5 handles it directly — GIC dispatch to your ISR, nothing in between. That’s the architecture you want for sub-100µs determinism.

Zephyr gives you:

  • A real RTOS scheduler on top of the bare hardware
  • Built-in OpenAMP/RPMsg for communication back to Linux
  • A standard west build system you can run on the ZynqMP board itself
  • No Vitis, no SDK installer, no Windows VM

Prerequisites

Everything runs on the ZynqMP board running Debian trixie. You need:

apt install python3-pip cmake ninja-build gcc-arm-none-eabi
pip install west pyelftools jsonschema PyYAML

The Zephyr SDK (aarch64 native build, runs on the A53):

wget https://github.com/zephyrproject-rtos/sdk-ng/releases/download/v0.17.0/\
zephyr-sdk-0.17.0_linux-aarch64.tar.xz
tar xf zephyr-sdk-0.17.0_linux-aarch64.tar.xz -C /opt/
cd /opt/zephyr-sdk-0.17.0 && ./setup.sh

Kernel requirements

Your Linux kernel needs remoteproc and RPMsg support. Check:

zcat /proc/config.gz | grep -E "REMOTEPROC|XLNX_R5|RPMSG"

You want:

CONFIG_REMOTEPROC=y
CONFIG_XLNX_R5_REMOTEPROC=y
CONFIG_RPMSG=y
CONFIG_RPMSG_CHAR=y
CONFIG_RPMSG_VIRTIO=y

The device tree also needs the R5 subsystem node — the key section in system-user.dtsi:

#include <dt-bindings/power/xlnx-zynqmp-power.h>

/ {
    reserved-memory {
        #address-cells = <2>;
        #size-cells = <2>;
        ranges;

        rproc_0_fw_image: rproc_0_fw@4000000 {
            no-map;
            reg = <0x0 0x04000000 0x0 0x00100000>;
        };
        rpu0vdev0buffer: rpu0vdev0buffer@4100000 {
            no-map;
            reg = <0x0 0x04100000 0x0 0x100000>;
        };
        rpu0vdev0vring0: rpu0vdev0vring0@4200000 {
            no-map;
            reg = <0x0 0x04200000 0x0 0x80000>;
        };
        rpu0vdev0vring1: rpu0vdev0vring1@4280000 {
            no-map;
            reg = <0x0 0x04280000 0x0 0x80000>;
        };
    };
};

&amba {
    remoteproc@ffe00000 {
        compatible = "xlnx,zynqmp-r5fss";
        xlnx,cluster-mode = <0>;
        xlnx,tcm-mode = <0>;
        #address-cells = <2>;
        #size-cells = <2>;
        ranges = <0x0 0x00000 0x0 0xffe00000 0x0 0x10000>,
                 <0x0 0x20000 0x0 0xffe20000 0x0 0x10000>,
                 <0x1 0x00000 0x0 0xffe90000 0x0 0x10000>,
                 <0x1 0x20000 0x0 0xffeb0000 0x0 0x10000>;

        r5f@0 {
            compatible = "xlnx,zynqmp-r5f";
            reg = <0x0 0x00000 0x0 0x10000>,
                  <0x0 0x20000 0x0 0x10000>;
            reg-names = "atcm0", "btcm0";
            power-domains = <&zynqmp_firmware PD_RPU_0>,
                            <&zynqmp_firmware PD_R5_0_ATCM>,
                            <&zynqmp_firmware PD_R5_0_BTCM>;
            memory-region = <&rproc_0_fw_image>, <&rpu0vdev0buffer>,
                            <&rpu0vdev0vring0>,  <&rpu0vdev0vring1>;
            mboxes = <&ipi_mailbox_rpu0 0>, <&ipi_mailbox_rpu0 1>;
            mbox-names = "tx", "rx";
        };
    };
};

When it works, dmesg confirms:

remoteproc remoteproc0: ffe00000.r5f is available

The Zephyr app

Initialize the workspace (run once):

mkdir ~/zephyr-workspace && cd ~/zephyr-workspace
west init && west update
source zephyr/zephyr-env.sh

The Hello World for R5 is the openamp_rsc_table sample — it boots Zephyr on the R5 and sets up an RPMsg shell back to Linux:

cd zephyr
west build -b zynqmp_rpu \
    samples/subsys/ipc/openamp_rsc_table

Build time on the A53: about 45 seconds.

Deploy

cp build/zephyr/zephyr.elf /lib/firmware/hello_r5.elf

# Stop any previous run
echo stop > /sys/class/remoteproc/remoteproc0/state 2>/dev/null; sleep 1

# Load and start
echo hello_r5.elf > /sys/class/remoteproc/remoteproc0/firmware
echo start > /sys/class/remoteproc/remoteproc0/state

# Check
cat /sys/class/remoteproc/remoteproc0/state
# running

dmesg shows the full boot sequence:

remoteproc remoteproc0: powering up ffe00000.r5f
remoteproc remoteproc0: Booting fw image hello_r5.elf, size ...
virtio_rpmsg_bus virtio0: rpmsg host is online
remoteproc remoteproc0: remote processor ffe00000.r5f is now up

Talk to it

The RPMsg shell appears as a tty device:

ls /dev/ttyRPMSG*
# /dev/ttyRPMSG0

cat /dev/ttyRPMSG0
# *** Booting Zephyr OS build v4.4.99 ***
# Zephyr Shell on R5

Or interactively:

screen /dev/ttyRPMSG0

You’re now talking to a Zephyr shell running on the R5F at 500MHz, completely independent of Linux scheduling.

Stop the R5

echo stop > /sys/class/remoteproc/remoteproc0/state

Linux gets full hardware ownership back immediately.

What’s next

A “hello world” on R5 is just the beginning. The R5 can:

  • Handle hardware interrupts from PL (FPGA) peripherals with sub-10µs latency — impossible to guarantee from Linux userspace
  • Run a TTP/fieldbus controller handler deterministically while Linux handles networking, logging, and OTA updates
  • Provide a hard real-time floor under an otherwise standard Linux system

The full source will be provided soon!


Tested on Trenz TE0821 (ZU3EG) with Linux 6.12 PREEMPT_RT, Zephyr 4.4.99, Zephyr SDK 1.0.0-beta1.