Skip to content
walidbadarPublic

About

Linux and Zephyr Asymmetric Multiprocessing on a Raspberry Pi 4

Topics

Resources

Stars

2 stars

Watchers

0 watching

Forks

Latest commit

 

History

11 Commits

Folders and files

Repository files navigation

Linux and Zephyr Asymmetric Multiprocessing (AMP) on the Raspberry Pi 4

The BCM2711's four Cortex-A72 cores: Linux on cores 0-2, Zephyr on core 3

Linux runs on cores 0-2, Zephyr on core 3. Linux starts Zephyr with remoteproc, and the two talk rpmsg over virtio. No hypervisor: both share the GIC-400 and the memory system, and each owns its own peripherals.

Memory map

Range Owner
0x00200000 - 0x0fffffff Linux kernel, initrd and devicetree
0x10000000 - 0x2fffffff Linux CMA pool (512 MiB)
0x30000000 - 0x307fffff Zephyr image and RAM
0x30800000 - 0x3080ffff Zephyr console
0x30900000 - 0x3090ffff rpmsg vrings
0x30910000 - 0x309fffff rpmsg buffers (shared-dma-pool)

Linux sees 0x30000000 - 0x3090ffff and the buffer pool as no-map reserved memory. The firmware's devicetree asks for a 512 MiB CMA pool inside 0x0 - 0x30000000; Zephyr sits just above it, and U-Boot keeps the kernel, initrd and devicetree below 0x10000000 so the pool still fits.

How it works

The firmware parks cores 1-3 in a wfe loop polling their spin-table release addresses (0xd8, 0xe0, 0xe8, 0xf0). uboot/boot.cmd boots Linux with nr_cpus=3, which makes core 3 impossible for Linux, so it never touches core 3's release address. (maxcpus=3 is not enough: Linux still prepares every possible CPU and core 3 would end up in the kernel's holding pen.) The script also adds the reserved memory and a zephyr-amp node to the firmware's devicetree, on a copy: ${fdt_addr} is U-Boot's live control devicetree.

linux/rproc/cpu_rproc.c binds to that node, loads the Zephyr ELF into the reserved memory, writes its entry point to the CPU's release address and wakes it with sev. Zephyr comes up after Linux has set up the GIC distributor, and CONFIG_GIC_SAFE_CONFIG makes it leave the distributor as it is.

The driver has nothing specific to the Pi 4: any arm64 SoC with a spin-table CPU and a GICv2 can use it. Everything comes from the node:

zephyr-amp {
    compatible = "amp,cpu-rproc";
    cpus = <&cpu3>;
    memory-region = <&image>, <&vdev0vring0>, <&vdev0vring1>, <&vdev0buffer>;
    interrupts = <GIC_SPI 221 IRQ_TYPE_EDGE_RISING>,    /* remote CPU to Linux */
                 <GIC_SPI 220 IRQ_TYPE_EDGE_RISING>;    /* Linux to remote CPU */
    firmware-name = "zephyr-amp.elf";
};

Carveouts are named after their reserved-memory nodes, all no-map; vdev0buffer is a shared-dma-pool for the rpmsg buffers. The kick is routed to the GIC CPU interface numbered by the CPU's MPIDR Aff0.

Notifications use two SPIs that no device drives, through the zephyr,mbox-gic-sw mailbox: Linux sets SPI 220 pending for core 3, Zephyr sets SPI 221 pending for Linux. The shared memory is non-cacheable on both sides.

Zephyr side

apps/rpmsg is Zephyr's openamp_rsc_table sample (src/main_remote.c, copied with larger thread stacks for arm64): it answers the kernel's rpmsg_client_sample and echoes what is written to /dev/ttyRPMSG0. Its overlay moves RAM to 0x30000000, adds the console and IPC regions and disables every device that Linux owns (VideoCore mailbox and firmware, GPIO, UART, I2C, GENET, USB, RNG); prj.conf turns off SMP, CPU power management, power domains and PCIe. CONFIG_RAM_CONSOLE writes the console region and lists it in the resource table as a trace buffer, which Linux shows as /sys/kernel/debug/remoteproc/remoteproc0/trace0.

zephyr/, drivers/, dts/ and modules/ make this directory a Zephyr module carrying a copy of the zephyr,mbox-gic-sw driver and binding (zephyrproject-rtos/zephyr#124185). The copy is used only when the Zephyr tree lacks them; delete it once the pinned Zephyr has the driver.

The trace entry does not build on 64-bit in Zephyr itself, so zephyr/patches/ carries the fix until it is upstream; west patch apply applies it and west patch reverse takes it out again.

west patch apply
west build -b rpi_4b rpi4-amp/apps/rpmsg
strip --strip-debug -o zephyr-amp.elf build/zephyr/zephyr.elf

remoteproc needs the section headers to find .resource_table, so strip only the debug information. In a workspace whose manifest is not this repository, point west patch at the patches with -b <ws>/rpi4-amp/zephyr/patches -l <ws>/rpi4-amp/zephyr/patches.yml, and if the workspace does not list them, add the modules by hand: -- -DEXTRA_ZEPHYR_MODULES="<ws>/modules/hal/cmsis;<ws>/modules/hal/libmetal;<ws>/modules/lib/open-amp;<ws>/rpi4-amp".

U-Boot

The Pi firmware starts U-Boot instead of the kernel, and U-Boot runs uboot/boot.cmd. Build it with rpi_arm64_defconfig: it uses the devicetree the firmware passes in, with the config.txt overlays and the cmdline.txt command line, which the boot script edits. rpi_4_defconfig embeds its own devicetree instead and does not work with the script.

git clone --depth 1 --branch v2026.07 https://source.denx.de/u-boot/u-boot.git
cd u-boot
make CROSS_COMPILE=aarch64-linux-gnu- rpi_arm64_defconfig
make CROSS_COMPILE=aarch64-linux-gnu- -j$(nproc)

Host packages: an aarch64 cross compiler (gcc-aarch64-linux-gnu), bison, flex, and the OpenSSL and GnuTLS development headers (libssl-dev and libgnutls28-dev on Debian/Ubuntu, openssl-devel and gnutls-devel on Fedora).

Copy u-boot.bin to the FAT partition of the Raspberry Pi OS card and add to the [all] section of config.txt:

arm_64bit=1
kernel=u-boot.bin
enable_uart=1

enable_uart=1 puts the U-Boot and Linux console on the GPIO 14/15 serial pins (115200 baud). Delete the kernel= line to boot Raspberry Pi OS directly again.

Linux side

Target: Raspberry Pi OS (64-bit) with U-Boot as above. The boot script loads kernel8.img and initramfs8 from the FAT partition (mmc 0:1) and uses the devicetree and command line the firmware passes in ${fdt_addr}.

python3 scripts/mkscr.py uboot/boot.cmd boot.scr

Copy boot.scr and zephyr-amp.elf to the FAT partition, then at the U-Boot prompt:

load mmc 0:1 ${scriptaddr} boot.scr; source ${scriptaddr}

To run it on every boot, store it as the boot command (U-Boot keeps its environment in uboot.env on the FAT partition):

setenv bootcmd 'load mmc 0:1 ${scriptaddr} boot.scr; source ${scriptaddr}'
saveenv

amp_extra_args is appended to the kernel command line; for a root shell on the serial console without a login: setenv amp_extra_args "init=/bin/bash console=ttyS0,115200".

The Raspberry Pi OS kernel has no virtio, rpmsg or remoteproc. linux/rproc/ builds them as external modules from the kernel sources of the running version, plus the remoteproc driver. Upstream remoteproc is built-in only; the two glue files split its virtio half into a module of its own and export what it needs. On the Pi (headers and gcc come with Raspberry Pi OS):

linux/rproc/get-sources.sh <linux-6.18.50 tree>
make -C linux/rproc
sudo linux/rproc/load.sh                  # zephyr-amp.elf in /boot/firmware
dmesg | grep rpmsg_client_sample          # "incoming msg 100", "goodbye!"
sudo cat /sys/kernel/debug/remoteproc/remoteproc0/trace0    # Zephyr's console

Files in place

The boot partition of the SD card (mmc 0:1 in U-Boot, /boot/firmware in Linux); everything not marked new is stock Raspberry Pi OS:

bootfs/
├── config.txt              arm_64bit=1, kernel=u-boot.bin, enable_uart=1 added
├── cmdline.txt             unchanged; boot.scr appends nr_cpus=3
├── start4.elf, fixup4.dat  firmware
├── bcm2711-rpi-4-b.dtb     loaded by the firmware; boot.scr edits a copy
├── overlays/
├── kernel8.img             loaded by boot.scr
├── initramfs8              loaded by boot.scr
├── u-boot.bin              new: rpi_arm64_defconfig
├── uboot.env               new: written by saveenv
├── boot.scr                new: uboot/boot.cmd through scripts/mkscr.py
└── zephyr-amp.elf          new: apps/rpmsg, strip --strip-debug

The remoteproc modules on the Pi, after get-sources.sh and make:

linux/rproc/
├── cpu_rproc.c, remoteproc_core_exports.c, remoteproc_virtio_module.c
├── Kbuild, Makefile, load.sh, get-sources.sh
├── virtio*.c, rpmsg_*.c, remoteproc_*.c, ...   copied kernel sources (git-ignored)
└── virtio_ring.ko, virtio_core.ko, rpmsg_core.ko, rpmsg_ns.ko, virtio_rpmsg_bus.ko,
    rpmsg_char.ko, rpmsg_ctrl.ko, rpmsg_tty.ko, rpmsg_client_sample.ko, remoteproc.ko,
    rproc_virtio.ko, cpu_rproc.ko

Limits

  • No isolation: either side can overwrite the other's memory.
  • Core 3 cannot be stopped (no PSCI on the Pi 4): stopping the remote fails, and restarting Zephyr needs a reboot.
  • The console wraps after 64 KiB.
  • The doorbell SPIs must stay unused by hardware; SPI 220/221 are above every SPI the Pi 4 devicetree uses.

License

The kernel code in linux/rproc/ is GPL-2.0-only, as Linux requires for a module; the kernel sources get-sources.sh copies keep their own licenses. apps/rpmsg/src/main_remote.c is Zephyr's sample under its original copyright. Everything else is Apache-2.0.

doc/cover.png uses Tux by Larry Ewing, created with The GIMP, and the Zephyr logo. Linux is a registered trademark of Linus Torvalds. Zephyr and the Zephyr logo are trademarks of The Linux Foundation.

About

Linux and Zephyr Asymmetric Multiprocessing on a Raspberry Pi 4

Topics

Resources

Stars

2 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages