Building and booting linux-based image from scratch on Pynq-z2 (zynq-7000 SoC) with TrustZone and Secure Boot support
This is an educational bring-up project intended as documentation for my future self and anyone interested in learning the full Zynq-7000 boot chain, Secure-boot and TrustZone integration.
The build system in this project intentionally avoids Yocto/Buildroot and instead uses simple Makefiles + shell scripts to expose every step of the process.
A follow-up production-oriented version of this project (Yocto-based) is being worked on here:
https://github.com/zakimadaoui/pynq-project2-custom-yocto-build-system
- Learn/Document how to Build a complete Linux system from scratch
- Understand the Zynq-7000 SoC boot process
- Learn about u-boot
- Understand and implement secure boot on Zynq-7000
- Learn about bringing-up and integrating a trusted execution environment like OPTEE alongside linux
- Learn anything else that comes up along the way :)
-
Basic bootable linux image
- patch Xilinx FSBL to add Pynq-Z2 board support
- patch u-boot to add Pynq-Z2 board support
- generate device-tree for pynq-z2 baord + basic IP in the PL using Xilinx tools
- build FSBL, device-tree, Linux kernel, U-boot, Busybox
- Create a basic initramfs that includes busybox, simple init scripts and glibc
- Assemble everything to a bootable image using Xilinx bootgen tool
-
Bootable image with a TEE alonside Linux. This is the most complex part — Zynq-7000 support for OP-TEE has been abandoned years ago and the Linux BSP assumes secure-world access ONLY, so significant work was required. That included:
- Patch OPTEE-OS to:
- Add Pynq-Z2 baord support
- Configure secure memory layout valid for pynq-z2 512MB DDR
- fix crashes in early secure-world boot
- Patch the Xilinx BSP within linux kernel to avoid accesses to secure registers/peripherals (which would cause exceptions at boot).
- Patch linux kernel configuration to allow enabling OPTEE driver for zynq-7000
- Craft a custom linux device tree with fixed clocks and only perepherals accessible from Non-secure world (without this kernel panics at boot at very early stages)
- Configure linux kernel with optee support
- Configure u-boot to boot to optee instead of linux, and optee to jump to Linux
- Configure optee to patch device tree at boot to add reserved and shared memory nodes
- Build optee_os, optee_client, optee_examples, FSBL, device-tree, u-boot, linux kernel, busybox
- Create initramfs with busybox, glibc, optee_client and optee_examples
- Assemble everything to a bootable image using Xilinx bootgen tool
- Patch OPTEE-OS to:
- Secure bootable Image
- Rust trustlets/secure services
This project provides a very simple makefile based buildsystem. But, before going into the make commands, the following host dependencies are needed.
sudo apt install git wget
sudo apt install gcc-arm-none-eabi
sudo apt install bison flex openssl libssl-dev
sudo apt-get install uuid-dev libgnutls28-dev
sudo apt install pkg-config meson ninja-build # needed for building dtcTBD: maybe a Dockerfile would be better at somepoint...
Use one of the following 3 commands, depending on what final image you are after
make simple_image # Linux only
make optee_image # OP-TEE + Linux
make secure_image # Enable secure boot chain (WIP)To clean any image build use:
make cleanIt is also possible to build individual components of the boot chain. For exmaple:
make fsbl
make uboot
make dtb
make kernel
make bootgen
make rootfs
#....The build outputs will be placed in a directory called artifacts
make simple_image produces artifacts/BOOT.bin
make optee_image produces artifacts/BOOT-optee.bin
make secure_image produces artifacts/BOOT-secure.bin
All you have to do is to copy the BOOT*.bin image to an SD card under the name BOOT.bin. The partion where BOOT.bin is copied must be created according to the instructions mentioned in How to format SD card for SD bootd section.
simple_image:
{
[bootloader]artifacts/fsbl.elf
artifacts/bitstream.bit
[load=0x04000000, startup=0x04000000] artifacts/u-boot.bin
[load=0x02000000] artifacts/boot.scr
[load=0x03000000] artifacts/zImage
[load=0x02A00000] artifacts/system.dtb
[load=0x05000000] artifacts/uInitrd
}
At boot the memory layout looks like this:
+---------------------------------------+ <- 0x0000_0000
| On-chip SRAM / OCM1(64KB) | # FSBL runs here first, then SRAM is free for use
| On-chip SRAM / OCM2(64KB) |
| On-chip SRAM / OCM3(64KB) |
+---------------------------------------+ <- 0x0003_0000
| Reserved |
+---------------------------------------+ <- 0x0010_0000 (DDR base)
| unused |
+---------------------------------------+ <- 0x0200_0000
| boot.scr |
+---------------------------------------+ <- 0x02A0_0000
| system.dtb |
+---------------------------------------+ <- 0x0300_0000
| zImage |
+---------------------------------------+ <- 0x0400_0000
| u-boot.bin |
+---------------------------------------+ <- 0x0500_0000
| uInitrd (initramfs) |
+---------------------------------------+ <- 0x05xx_xxxx
| Free / unused DDR |
+---------------------------------------+ <- 0x2000_0000 (end of 512MB DDR)
| Empty / Reserved |
+---------------------------------------+ <- 0xFFFF_0000
| On-chip SRAM / OCM4(64KB) |
+---------------------------------------+ <- 0xFFFF_FFFF
-
BootROM loads FSBL from non-volatile memory into OCM and executes it.
-
FSBL:
- Initializes clocks, DDR controller, and MIO.
- Loads the PL bitstream.
- Loads all remaining BIF artifacts into DDR at the specified BIF load addresses
- Jumps to U-Boot’s startup address (0x04000000).
-
U-Boot:
- Executes boot.scr.
- Updates the DTB (adds memory node, MAC address, chosen nodes, etc.).
- Passes the DTB pointer and initrd start/end to the kernel loader.
- Calls
bootzto start Linux with:- zImage at 0x03000000
- patched FDT pointer
- initrd at 0x05000000
- Executes boot.scr.
-
Linux kernel:
- Parses the DTB passed from U-Boot.
- Loads/initramfs from the initrd region.
- After consuming initrd, releases the memory region and the remainder of DDR becomes available as normal system RAM.
optee_image:
{
[bootloader]artifacts/fsbl.elf
artifacts/bitstream.bit
[load=0x04000000, startup=0x04000000] artifacts/u-boot.bin
[load=0x02000000] artifacts/boot_tee.scr
[load=0x03000000] artifacts/zImage
[load=0x02A00000] artifacts/system.dtb
[load=0x05000000] artifacts/uInitrd
[load=0x10000000] artifacts/uTee
}
At boot the memory layout looks like this:
+---------------------------------------+ <- 0x0000_0000
| On-chip SRAM / OCM1(64KB) | # FSBL runs here first, then SRAM is free for use
| On-chip SRAM / OCM2(64KB) |
| On-chip SRAM / OCM3(64KB) |
+---------------------------------------+ <- 0x0003_0000
| Reserved |
+---------------------------------------+ <- 0x0010_0000 (DDR base)
| unused |
+---------------------------------------+ <- 0x0200_0000
| boot.scr |
+---------------------------------------+ <- 0x02A0_0000
| system.dtb |
+---------------------------------------+ <- 0x0300_0000
| zImage |
+---------------------------------------+ <- 0x0400_0000
| u-boot.bin |
+---------------------------------------+ <- 0x0500_0000
| uInitrd (initramfs) |
+---------------------------------------+ <- 0x05xx_xxxx
| Free / unused DDR |
+---------------------------------------+ <- 0x1000_0000 (TZDRAM_BASE)
| uTee (optee_os) | | ^
| | TEE_RAM (4MB) | |
| TEE private secure | | |
| external memory +------------------+ |TZDRAM_SIZE (32MB)
| | | |
| | TA_RAM (28MB) | |
| | | |
+---------------------------------------+ v 0x1200_0000 (TEE_SHMEM_START)
| | | ^
| Non secure | SHM | | TEE_SHMEM_SIZE (1MB)
| shared memory | | |
+---------------------------------------+ v 0x1210_0000
| Free / unused DDR |
+---------------------------------------+ <- 0x1FFF_FFFF (end of 512MB DDR)
| Empty / Reserved |
+---------------------------------------+ <- 0xFFFF_0000
| On-chip SRAM / OCM4(64KB) |
+---------------------------------------+ <- 0xFFFF_FFFF
Similar to the simple boot process described earlier, but instead of u-boot handing-off to linux, u-boot hands-off to optee_os which:
- Sets up the secure monitor
- Sets up the secure-memory regions
- Configure which peripherals are secure/non-secure
- Configures hardware for secure world operations (Caches, MMU, SMP...)
- Create MMU table for secure world
- Extends the device-tree with an optee node, reserved memory area node and shared memory area node.
- enter monitor mode with SMC
- Switch NS bit to non-secure
- Switch CPU mode to Svc and jump to linux start forwarding the arguments from uboot
then the linux kernel starts its boot process in non-secure world
Git repos, see: https://xilinx-wiki.atlassian.net/wiki/spaces/A/pages/18842156/Fetch+Sources
| Link | Content |
|---|---|
| https://github.com/Xilinx/linux-xlnx.git | The Linux kernel with Xilinx patches and drivers |
| https://github.com/Xilinx/u-boot-xlnx.git | The u-boot bootloader with Xilinx patches and drivers |
| https://github.com/Xilinx/device-tree-xlnx.git | Device Tree generator plugin for xsdk |
| https://git.kernel.org/pub/scm/utils/dtc/dtc.git | Device Tree compiler (required to build U-Boot) |
| https://github.com/Xilinx/embeddedsw.git | Xilinx embeddedsw repository for bare-metal applications such as FSBL, PMU Firmware, PLM |
| https://pynq.readthedocs.io/en/v2.7.0/overlay_design_methodology/board_settings.html | Link to Pynq Z1 and Z2 Vivado board files |
C:\Xilinx\Vivado\2021.2\data\boards Where C:\Xilinx is the default path where Vivado is installed Add a new folder and name it ‘board_files’ Download Pynq board files from this link https://pynq.readthedocs.io/en/v2.7.0/overlay_design_methodology/board_settings.html Extract board files then copy them to ‘board_files’ folder Restart Vivado
https://www.youtube.com/watch?v=3D2-OPArCiA&list=PLAIW2DtZ4NI77mo2qFhvZKGZ1n1W8vWYY&index=16
- https://xilinx-wiki.atlassian.net/wiki/spaces/A/pages/18842279/Build+Device+Tree+Blob
- https://xilinx-wiki.atlassian.net/wiki/spaces/A/pages/341082130/Quick+guide+to+Debugging+Device+Tree+Generator+Issues
https://xilinx-wiki.atlassian.net/wiki/spaces/A/pages/18842482/Device+Tree+Tips
https://xilinx-wiki.atlassian.net/wiki/spaces/A/pages/18842385/How+to+format+SD+card+for+SD+boot
- https://optee.readthedocs.io/en/latest/building/gits/optee_os.html
- https://optee.readthedocs.io/en/latest/building/gits/optee_client.html
- https://optee.readthedocs.io/en/latest/building/gits/optee_examples/optee_examples.html
- https://optee.readthedocs.io/en/latest/building/trusted_applications.html
- https://optee.readthedocs.io/en/latest/building/optee_with_rust.html
I found this resource after finishing this whole project :), but well, it has great infromation, so here it is: https://www.linuxfromscratch.org/
To test u-boot
qemu-system-arm \
-M xilinx-zynq-a9 \
-m 1024M \
-serial mon:stdio \
-serial null \
-kernel artifacts/u-boot.elf
-nographic
# -sd sdcardfile Or to boot a Linux kernel:
qemu-system-arm \
-M xilinx-zynq-a9 \
-m 512M \
-serial mon:stdio \
-kernel artifacts/uImage \
-initrd artifacts/initramfs.cpio.gz \
-dtb artifacts/system.dtb \
-append "console=ttyPS0,115200 earlyprintk"
-nographic