diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 86a105b..2ee0d2e 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -63,14 +63,25 @@ jobs: with: key: ${{ github.repository }}-src-uboot path: ${{ github.workspace }}/src + # libc6-dev-armhf-cross is not for U-Boot or the OP-TEE core (both are + # freestanding): the optee-examples step below builds libteec and the + # example host apps, which are hosted userspace and need the armhf libc + # headers. It is only a Recommends of the cross gcc, which + # --no-install-recommends drops. - name: install deps run: | sudo apt-get update -qq sudo apt-get install -y -qq --no-install-recommends \ - gcc-arm-linux-gnueabihf build-essential bison flex libssl-dev \ - libgnutls28-dev device-tree-compiler python3 python3-dev \ - python3-setuptools python3-pyelftools swig uuid-dev git + gcc-arm-linux-gnueabihf libc6-dev-armhf-cross build-essential \ + bison flex libssl-dev libgnutls28-dev device-tree-compiler \ + python3 python3-dev python3-setuptools python3-pyelftools \ + python3-cryptography swig uuid-dev git + # make uboot builds OP-TEE first (Makefile prerequisite) and packs it + # into u-boot.img, binman's u-boot.itb. - run: make uboot + # The example TAs build against the dev kit the OP-TEE build just + # exported, so this job is the cheapest place for them. + - run: make optee-examples - uses: actions/upload-artifact@v4 with: name: u-boot @@ -80,6 +91,11 @@ jobs: out/u-boot-rockchip-usb47*.bin out/rv1106_download.bin if-no-files-found: error + - uses: actions/upload-artifact@v4 + with: + name: optee-examples-staging + path: out/optee-examples-staging + if-no-files-found: error kernel: name: kernel @@ -163,8 +179,12 @@ jobs: with: { name: kernel, path: out } - uses: actions/download-artifact@v4 with: { name: npu-staging, path: out/npu-staging } + - uses: actions/download-artifact@v4 + with: { name: optee-examples-staging, path: out/optee-examples-staging } - name: restore permissions lost by the artifact round trip - run: chmod +x out/npu-staging/usr/bin/* || true + run: | + chmod +x out/npu-staging/usr/bin/* || true + chmod +x out/optee-examples-staging/usr/bin/* || true - run: scripts/build-rootfs.sh - run: scripts/mk-image.sh - name: image summary diff --git a/Makefile b/Makefile index c07c58e..4e1d31a 100644 --- a/Makefile +++ b/Makefile @@ -1,7 +1,11 @@ # luckfox-linux -- a glibc Linux system for the Luckfox Pico Max (RV1106G3) # # make build everything and produce flashable images -# make uboot U-Boot (mainline + the in-review RV1106 series) +# make optee OP-TEE OS (upstream, plat-rockchip rv1106) + TA dev kit +# make optee-examples upstream example TAs and host apps, for exercising +# the secure world end to end from userspace +# make uboot U-Boot (mainline + the in-review RV1106 series), with +# OP-TEE packed into the boot FIT # make kernel Rockchip 6.6 kernel, RKNPU built in # make npu glibc librknnmrt.so.2 and headers # make rootfs Debian armhf root filesystem @@ -16,14 +20,22 @@ export BOARD S := scripts -.PHONY: all uboot kernel npu rootfs images clean distclean deps shell info check +.PHONY: all optee optee-examples uboot kernel npu rootfs images clean distclean deps shell info check all: images deps: @$(S)/install-deps.sh -uboot: +optee: + @$(S)/build-optee.sh + +# The example TAs build against the dev kit that make optee exports. +optee-examples: optee + @$(S)/build-optee-examples.sh + +# binman packs tee-raw.bin into u-boot.itb, so OP-TEE builds first. +uboot: optee @$(S)/build-uboot.sh kernel: @@ -32,7 +44,7 @@ kernel: npu: @$(S)/build-npu.sh -rootfs: kernel npu +rootfs: kernel npu optee-examples @$(S)/build-rootfs.sh images: uboot rootfs diff --git a/README.md b/README.md index 7efa52c..fe288ee 100644 --- a/README.md +++ b/README.md @@ -18,6 +18,7 @@ make # -> out/luckfox-pico-max-sdcard.img | **init** | systemd, with networkd/resolved/timesyncd wired up | | **kernel** | Rockchip `develop-6.6` + a devicetree and config written for this board | | **bootloader** | Mainline U-Boot with standard boot (`extlinux.conf`), not the 2017.09 vendor fork | +| **secure world** | Upstream OP-TEE as the secure monitor, built from source; `/dev/tee0` and `tee-supplicant` ready | | **NPU** | `rknpu` built into the kernel, plus a **glibc-adapted `librknnmrt.so.2`** | | **memory** | zram swap, tuned sysctls: a full systemd userspace idles around 45 MB of the 256 MB | | **cpufreq** | `ondemand` over 408 MHz - 1.2 GHz, throttling to a cooling device at 85 C | @@ -95,6 +96,70 @@ actually belong to the NPU node (its ACLK is a gate on a mux with no divider, so the NPU is simply whatever `clk_500m_src` is, and 594 MHz is GPLL/2), which is what the fragment is there to explain. +## Secure world (OP-TEE) + +The image boots with a real secure world: **upstream OP-TEE**, built from +source, no `rv1106_tee_ta` blob from rkbin. The RV1106 port landed in +`plat-rockchip` upstream (the pinned commit is that very change), so the whole +thing is one plain `make PLATFORM=rockchip-rv1106`. + +The boot chain becomes: BootROM loads `idbloader.img` (rkbin DDR init + SPL); +the SPL loads `u-boot.img`, which is now binman's `u-boot.itb`, a FIT holding +OP-TEE and U-Boot proper; the SPL enters OP-TEE, which sets up the secure +world and returns to U-Boot in the normal world; U-Boot then boots Linux via +`extlinux.conf` as before. This is mainline U-Boot's stock +`CONFIG_SPL_OPTEE_IMAGE` flow, the same one the RK3229/RK3288 use, with two +board-side adjustments: + +- the FIT's op-tee load address is moved from the vendor blob's `0x08400000` + to `0x03d00000`, where upstream OP-TEE links (`CFG_TZDRAM_START`, matching + the vendor firmware layout); +- OP-TEE is built with `CFG_DT_ADDR` unset, so it takes the control DTB + address the mainline SPL hands it in `r2` instead of the fixed address the + vendor SPL flow needs. + +The memory window `[0x03d00000, 0x04e00000)` -- 16 MB TZDRAM + 1 MB static +shared memory -- is reserved `no-map` in the kernel devicetree, and U-Boot's +staging addresses keep clear of it. `make check` holds all of those numbers +together. + +On the Linux side `CONFIG_OPTEE` gives `/dev/tee0` (clients) and +`/dev/teepriv0` (supplicant); the standard rootfs profile ships Debian's +`tee-supplicant` and `libteec2`, so the userspace half is ready the moment a +TA needs it. The kernel's `psci { method = "smc" }` calls, which previously +had no monitor to land in, are answered by OP-TEE's ARM32 PSCI backend. + +### Trying it out + +The standard rootfs ships the upstream OP-TEE examples, built by +`make optee-examples` from the pinned `optee_examples` against this tree's TA +dev kit: the host apps land in `/usr/bin/optee_example_*` and the signed TAs +in `/lib/optee_armtz/`, which is where Debian's `tee-supplicant` loads them +from. End to end, on the board: + +``` +# optee_example_hello_world # session + invoke: prints 42, then 43 +# optee_example_random # entropy from the secure world +# optee_example_secure_storage # TEE storage, round-trips through + # tee-supplicant's REE FS RPC to /var/lib/tee +``` + +`optee_example_aes`, `optee_example_acipher ` and +`optee_example_hotp` exercise crypto inside a TA. The first invocation of +each example is when its TA gets loaded (supplicant fetch, signature check), +so expect a beat of latency and a burst of secure-console traces. + +OP-TEE is a **debug build**: `CFG_TEE_CORE_DEBUG=y` (assertions, lock checks, +verbose aborts) with core and TA trace levels at 3 (error+info+debug), so the +secure console on ttyS2 narrates session setup and TA loading as the examples +run. Level 4 would add flow tracing on every SMC and drown the 115200 +console. For a release build turn both levels back to 1 in +`scripts/build-optee.sh`. + +One consequence to know about: a kernel from this tree expects to run in the +normal world. Boot it with a pre-OP-TEE `u-boot.img` and the PSCI probe's SMC +has no monitor to catch it; reflash both halves together. + ## CPU frequency, and why it stops at 1.2 GHz The RV1106 is a 1.6 GHz part and `rv1106.dtsi` has the OPPs to prove it, but @@ -130,8 +195,9 @@ make # uboot + kernel + npu + rootfs + images make info # what is pinned, what is built ``` -Individual stages: `make uboot`, `make kernel`, `make npu`, `make rootfs`, -`make images`. Knobs, all overridable from the environment: +Individual stages: `make optee`, `make optee-examples`, `make uboot`, +`make kernel`, `make npu`, `make rootfs`, `make images`. Knobs, all +overridable from the environment: ```bash ROOTFS_PROFILE=dev make # minimal | standard | dev (adds a native toolchain) diff --git a/board/luckfox-pico-max/kernel/config/optee.config b/board/luckfox-pico-max/kernel/config/optee.config new file mode 100644 index 0000000..1c0d8db --- /dev/null +++ b/board/luckfox-pico-max/kernel/config/optee.config @@ -0,0 +1,15 @@ +# The non-secure side of OP-TEE. The boot FIT runs OP-TEE as the secure +# monitor and enters the kernel in the normal world; this is the driver that +# talks to it over SMC and exposes /dev/tee0 + /dev/teepriv0. +CONFIG_TEE=y +CONFIG_OPTEE=y + +# rv1106.dtsi declares psci { method = "smc" }, and with a monitor in place +# those calls finally have somewhere to go: OP-TEE's ARM32 PSCI backend +# (CFG_PSCI_ARM32) answers them. Single core, so this buys the version +# handshake, not SMP. +CONFIG_ARM_PSCI=y + +# /dev/hwrng from the OP-TEE RNG service, when a TA provides one. Probes via +# the TEE bus, so it stays inert until then. +CONFIG_HW_RANDOM_OPTEE=y diff --git a/board/luckfox-pico-max/kernel/dts/rv1106g3-luckfox-pico-max.dts b/board/luckfox-pico-max/kernel/dts/rv1106g3-luckfox-pico-max.dts index 9502312..790b7bd 100644 --- a/board/luckfox-pico-max/kernel/dts/rv1106g3-luckfox-pico-max.dts +++ b/board/luckfox-pico-max/kernel/dts/rv1106g3-luckfox-pico-max.dts @@ -80,6 +80,37 @@ function = "activity"; }; }; + + reserved-memory { + #address-cells = <1>; + #size-cells = <1>; + ranges; + + /* + * OP-TEE: 16 MB of TZDRAM at 0x03d00000 (the plat-rockchip + * rv1106 default, matching the vendor firmware layout) plus + * 1 MB of static shared memory right above it. no-map, so the + * kernel never allocates inside it -- the secure region does + * not forgive: the first allocation that lands there hangs + * the machine on a REGISTER_SHM call. U-Boot's staging + * addresses in pico-max.env avoid the same window. + */ + optee@3d00000 { + reg = <0x03d00000 0x01100000>; + no-map; + }; + }; +}; + +/* --------------------------------------------------------------- OP-TEE --- */ + +/* + * The SMC driver for the secure world the boot FIT started. rv1106.dtsi + * carries the node disabled; the TZDRAM it talks about is reserved no-map in + * the reserved-memory node above. + */ +&optee { + status = "okay"; }; /* ------------------------------------------------------------------ CPU --- */ diff --git a/board/luckfox-pico-max/uboot/tree/arch/arm/dts/rv1106-luckfox-pico-max-u-boot.dtsi b/board/luckfox-pico-max/uboot/tree/arch/arm/dts/rv1106-luckfox-pico-max-u-boot.dtsi index 2d75277..226e5b4 100644 --- a/board/luckfox-pico-max/uboot/tree/arch/arm/dts/rv1106-luckfox-pico-max-u-boot.dtsi +++ b/board/luckfox-pico-max/uboot/tree/arch/arm/dts/rv1106-luckfox-pico-max-u-boot.dtsi @@ -2,6 +2,25 @@ #include "rockchip-u-boot.dtsi" +#if defined(CONFIG_SPL_FIT) && defined(CONFIG_SPL_OPTEE_IMAGE) +/* + * rockchip-u-boot.dtsi hardcodes the op-tee FIT node's load address to + * SDRAM + 0x8400000, where Rockchip's proprietary TEE blob historically + * lived. Upstream OP-TEE's rv1106 flavor is linked for CFG_TZDRAM_START = + * 0x03d00000 (the vendor firmware layout, rkbin RV1106TOS.ini), and + * tee-raw.bin is position-dependent: the SPL jumps to the load address, so + * the FIT has to put it where it was linked. + */ +&fit_template { + images { + op-tee { + load = <0x03d00000>; + entry = <0x03d00000>; + }; + }; +}; +#endif + / { chosen { /* diff --git a/board/luckfox-pico-max/uboot/tree/board/luckfox/pico/pico-max.env b/board/luckfox-pico-max/uboot/tree/board/luckfox/pico/pico-max.env index 598d3f1..bc37f27 100644 --- a/board/luckfox-pico-max/uboot/tree/board/luckfox/pico/pico-max.env +++ b/board/luckfox-pico-max/uboot/tree/board/luckfox/pico/pico-max.env @@ -1,8 +1,16 @@ +/* + * Staging addresses. OP-TEE owns [0x03d00000, 0x04e00000) -- 16 MB of TZDRAM + * plus 1 MB of static shared memory -- and the kernel devicetree reserves it + * no-map, so everything U-Boot stages has to sit outside that window. The + * fdt/ramdisk/script/pxefile group used to start at 0x04000000, inside it; + * writes there die on the secure region once OP-TEE is up. check.sh holds + * these addresses against the reservation. + */ kernel_addr_r=0x00800000 -fdt_addr_r=0x04000000 -ramdisk_addr_r=0x04400000 -scriptaddr=0x04500000 -pxefile_addr_r=0x04600000 +fdt_addr_r=0x05000000 +scriptaddr=0x05100000 +pxefile_addr_r=0x05200000 +ramdisk_addr_r=0x05300000 fdtfile=rv1106g3-luckfox-pico-max.dtb /* diff --git a/board/luckfox-pico-max/uboot/tree/configs/luckfox-pico-max-rv1106_defconfig b/board/luckfox-pico-max/uboot/tree/configs/luckfox-pico-max-rv1106_defconfig index cebadcb..1bde561 100644 --- a/board/luckfox-pico-max/uboot/tree/configs/luckfox-pico-max-rv1106_defconfig +++ b/board/luckfox-pico-max/uboot/tree/configs/luckfox-pico-max-rv1106_defconfig @@ -42,7 +42,8 @@ CONFIG_SPL_NO_BSS_LIMIT=y # CONFIG_EFI_LOADER is not set CONFIG_SPL_MTD=y CONFIG_SPL_SPI_NAND_LOAD=y -CONFIG_SPL_LEGACY_IMAGE_CRC_CHECK=y +CONFIG_SPL_LOAD_FIT=y +CONFIG_SPL_OPTEE_IMAGE=y CONFIG_SPL_SHOW_ERRORS=y CONFIG_SYS_SPI_U_BOOT_OFFS=0x140000 CONFIG_SYS_MMCSD_RAW_MODE_U_BOOT_SECTOR=0x800 diff --git a/rootfs/packages/standard.list b/rootfs/packages/standard.list index a1f6296..c93bb4f 100644 --- a/rootfs/packages/standard.list +++ b/rootfs/packages/standard.list @@ -1,4 +1,9 @@ # On top of minimal.list: the tools you reach for in the first hour on a board. +# tee-supplicant serves the secure world's storage/RPC requests over +# /dev/teepriv0 (its unit is device-activated, so it costs nothing until +# OP-TEE asks); libteec2 is the client library TEE applications link against. +tee-supplicant +libteec2 curl wget htop diff --git a/scripts/build-optee-examples.sh b/scripts/build-optee-examples.sh new file mode 100755 index 0000000..66bc29d --- /dev/null +++ b/scripts/build-optee-examples.sh @@ -0,0 +1,100 @@ +#!/usr/bin/env bash +# +# Build the upstream OP-TEE example TAs and their host applications, so the +# secure world can be exercised end to end from userspace on the board: +# +# optee_example_hello_world open a session, invoke a command +# optee_example_random entropy from the secure world +# optee_example_aes AES encrypt/decrypt in a TA +# optee_example_acipher RSA keygen + encrypt in a TA +# optee_example_hotp RFC 4226 one-time passwords +# optee_example_secure_storage TEE-backed storage; this one round-trips +# through tee-supplicant's REE FS RPC, i.e. +# kernel, supplicant and /var/lib/tee too +# +# Outputs into $OUT/optee-examples-staging, laid out as a rootfs overlay: +# +# usr/bin/optee_example_* host apps (CAs) +# usr/lib/optee_armtz/*.ta the TAs. Debian's tee-supplicant loads TAs +# from /lib/optee_armtz (upstream default, +# Debian does not change it), and /lib is +# usr/lib on a merged-usr system. +# +# The TAs build against the dev kit that build-optee.sh exports, so they are +# signed with that tree's default key and match the core's configuration. +# The host apps link against a libteec built here from the pinned +# optee_client -- build-time only: on the board they resolve the same +# libteec.so.2 soname to Debian's libteec2 package. + +. "$(dirname "$0")/lib.sh" + +need git make "${CROSS_COMPILE}gcc" "${CROSS_COMPILE}readelf" python3 + +# The TA dev kit signs TAs with sign_encrypt.py, which needs both of these. +python3 -c 'import elftools, cryptography' 2>/dev/null \ + || die "python3-pyelftools/python3-cryptography is missing (run scripts/install-deps.sh)" + +devkit="$OUT/optee/export-ta_arm32" +[ -f "$devkit/mk/ta_dev_kit.mk" ] || die "no TA dev kit at $devkit (make optee)" + +client="$(fetch optee_client "$OPTEE_CLIENT_URL" "$OPTEE_CLIENT_REF")" +examples="$(fetch optee_examples "$OPTEE_EXAMPLES_URL" "$OPTEE_EXAMPLES_REF")" + +# --- libteec, for the host apps to link against ------------------------------ + +bc="$OUT/optee-client" +rm -rf "$bc"; mkdir -p "$bc" + +log "building libteec ($OPTEE_CLIENT_REF, link-time only)" +make -C "$client" O="$bc" \ + CROSS_COMPILE="$CROSS_COMPILE" \ + build-libteec >/dev/null + +# What a host app's Makefile expects under TEEC_EXPORT: include/ and lib/. +texp="$bc/export" +mkdir -p "$texp/include" "$texp/lib" +cp -f "$client/libteec/include"/*.h "$texp/include/" +cp -d "$bc/libteec"/libteec.so* "$texp/lib/" + +# --- the examples ------------------------------------------------------------- + +# A curated list rather than the whole tree: plugins couples to a supplicant +# plugin path baked into Debian's build, and the remaining crypto examples +# (sha, ecdh, ecdsa, sign_verify) add nothing these six do not already show. +want="hello_world random aes acipher hotp secure_storage" + +for ex in $want; do + log "building example $ex" + make -C "$examples/$ex/host" --no-builtin-variables \ + CROSS_COMPILE="$CROSS_COMPILE" \ + TEEC_EXPORT="$texp" >/dev/null + make -C "$examples/$ex/ta" \ + CROSS_COMPILE="$CROSS_COMPILE" \ + TA_DEV_KIT_DIR="$devkit" >/dev/null +done + +# --- staging ------------------------------------------------------------------ + +stage="$OUT/optee-examples-staging" +rm -rf "$stage" +mkdir -p "$stage/usr/bin" "$stage/usr/lib/optee_armtz" + +for ex in $want; do + ca="$examples/$ex/host/optee_example_$ex" + [ -f "$ca" ] || die "example $ex built no host binary" + + # The board resolves libteec through Debian's libteec2, so the link must + # ask for the soname that package provides -- catch a mismatch here, not + # as a loader error on the device. + "${CROSS_COMPILE}readelf" -d "$ca" | grep -q 'NEEDED.*\[libteec\.so\.2\]' \ + || die "optee_example_$ex does not link libteec.so.2" + + install -m 0755 "$ca" "$stage/usr/bin/" + + tas=("$examples/$ex/ta/"*.ta) + [ -f "${tas[0]}" ] || die "example $ex built no TA" + install -m 0444 "${tas[@]}" "$stage/usr/lib/optee_armtz/" +done + +log "optee examples staging tree:" +find "$stage" -type f | sed "s|$stage||" | sort diff --git a/scripts/build-optee.sh b/scripts/build-optee.sh new file mode 100755 index 0000000..016cd53 --- /dev/null +++ b/scripts/build-optee.sh @@ -0,0 +1,78 @@ +#!/usr/bin/env bash +# +# Build OP-TEE OS for the board. The RV1106 port is upstream (plat-rockchip, +# flavor rv1106), so this is a plain source build -- no Rockchip blob, no +# fork, no patches. +# +# Outputs, all in $OUT: +# tee-raw.bin the OP-TEE core without the OPTE image header. binman packs +# it into u-boot.itb as the FIT's op-tee node, and the SPL +# jumps straight to its load address -- with the header in +# front, the header would execute as code and hang. +# tee.elf the same core with symbols, for a debugger. +# +# Also exports the TA development kit to $OUT/optee/export-ta_arm32, which +# build-optee-examples.sh consumes to build the example TAs. +# +# The memory layout is the platform default and matches the vendor firmware's +# (rkbin RV1106TOS.ini): TZDRAM at 0x03d00000 (16 MB) with 1 MB of static +# shared memory right above it, i.e. [0x03d00000, 0x04e00000). Three other +# places depend on that window and check.sh holds them all together: +# +# - the op-tee load address in rv1106-luckfox-pico-max-u-boot.dtsi +# - the reserved-memory carve-out in rv1106g3-luckfox-pico-max.dts +# - the U-Boot staging addresses in pico-max.env, which must stay outside it + +. "$(dirname "$0")/lib.sh" + +need git make "${CROSS_COMPILE}gcc" python3 + +# gen_tee_bin.py, which produces tee-raw.bin from tee.elf, needs pyelftools. +python3 -c 'import elftools' 2>/dev/null \ + || die "python3-pyelftools is missing (run scripts/install-deps.sh)" + +optee="$(fetch optee_os "$OPTEE_URL" "$OPTEE_REF")" + +b="$OUT/optee" +mkdir -p "$b" + +# Build the core targets plus the TA dev kit; build-optee-examples.sh builds +# the example TAs and host apps against the exported dev kit, and the in-tree +# sample TAs stay out of the build. +# +# CFG_DT_ADDR=n drops the platform's forced DTB address (0x08000000). That +# default exists for the vendor SPL, which passes garbage in r2; mainline SPL +# passes the FIT's control DTB address in r2 (common/spl/spl_optee.S), and +# with CFG_DT_ADDR unset OP-TEE believes it. The same handoff puts the +# non-secure entry point in lr = CONFIG_TEXT_BASE = 0x00200000, which is +# exactly the platform's CFG_NS_ENTRY_ADDR default. +# +# This is a debug build: CFG_TEE_CORE_DEBUG keeps assertions and lock checks +# in (it is also the upstream default, forced here so a default change cannot +# silently drop them), and the trace levels are turned up so the secure +# console on ttyS2 actually narrates what the core and the TAs do. Level 3 is +# error+info+debug -- level 4 would add flow tracing on every SMC, which +# drowns a 115200 console. Turn both back down to 1 for a release build. +log "building op-tee (PLATFORM=rockchip-rv1106, debug, core log level 3)" +make -C "$optee" O="$b" \ + PLATFORM=rockchip-rv1106 \ + CFG_ARM32_core=y \ + CROSS_COMPILE="$CROSS_COMPILE" \ + CROSS_COMPILE_core="$CROSS_COMPILE" \ + CROSS_COMPILE_ta_arm32="$CROSS_COMPILE" \ + CFG_DT_ADDR=n \ + CFG_TEE_CORE_DEBUG=y \ + CFG_TEE_CORE_LOG_LEVEL=3 \ + CFG_TEE_TA_LOG_LEVEL=3 \ + NOWERROR=1 \ + -j"$JOBS" \ + "$b/core/tee-raw.bin" "$b/core/tee.elf" ta_dev_kit + +cp -f "$b/core/tee-raw.bin" "$OUT/tee-raw.bin" +cp -f "$b/core/tee.elf" "$OUT/tee.elf" + +[ -f "$b/export-ta_arm32/mk/ta_dev_kit.mk" ] \ + || die "ta_dev_kit did not export $b/export-ta_arm32" + +log "op-tee artifacts in $OUT (TA dev kit in $b/export-ta_arm32):" +ls -la "$OUT/tee-raw.bin" "$OUT/tee.elf" diff --git a/scripts/build-rootfs.sh b/scripts/build-rootfs.sh index a7f5f13..66984b4 100755 --- a/scripts/build-rootfs.sh +++ b/scripts/build-rootfs.sh @@ -22,6 +22,12 @@ fi kver="$(cat "$OUT/kernel.release" 2>/dev/null || true)" [ -n "$kver" ] || die "build the kernel first (make kernel): no $OUT/kernel.release" [ -d "$OUT/npu-staging" ] || die "build the npu userspace first (make npu)" +# The OP-TEE examples only go where libteec2 goes: the standard package set. +# A minimal rootfs would ship host binaries whose libteec.so.2 never resolves. +if [ "$ROOTFS_PROFILE" != minimal ]; then + [ -d "$OUT/optee-examples-staging" ] \ + || die "build the op-tee examples first (make optee-examples)" +fi # --- package set ------------------------------------------------------------ @@ -57,6 +63,9 @@ ovl="$OUT/rootfs-overlay" rm -rf "$ovl"; mkdir -p "$ovl" tar -C "$TOP/rootfs/overlay" -cf - . | tar -C "$ovl" -xf - tar -C "$OUT/npu-staging" -cf - . | tar -C "$ovl" -xf - +if [ "$ROOTFS_PROFILE" != minimal ]; then + tar -C "$OUT/optee-examples-staging" -cf - . | tar -C "$ovl" -xf - +fi # Kernel modules and the devicetree/kernel the bootloader will read. # diff --git a/scripts/build-uboot.sh b/scripts/build-uboot.sh index 577c9b2..3eb12b0 100755 --- a/scripts/build-uboot.sh +++ b/scripts/build-uboot.sh @@ -6,14 +6,21 @@ # Outputs, all in $OUT: # idbloader.img TPL(rkbin DDR init) + SPL, written at the offset # the BootROM reads (LBA 64 on SD, 0x40000 in NAND) -# u-boot.img U-Boot proper, as a legacy uImage: binman only -# wraps it in a FIT for arm64 or with OP-TEE +# u-boot.img binman's u-boot.itb: a FIT with OP-TEE (BL32) +# and U-Boot proper. The SPL loads it, enters +# OP-TEE, and OP-TEE returns to U-Boot in the +# non-secure world. # u-boot-rockchip-usb47*.bin maskrom RAM-boot images, for `rockusb` . "$(dirname "$0")/lib.sh" need git make "${CROSS_COMPILE}gcc" bison flex python3 swig +# The secure world rides in the FIT, so it builds first (make uboot orders +# this through the optee prerequisite). +tee="$OUT/tee-raw.bin" +[ -f "$tee" ] || die "no tee-raw.bin (make optee)" + rkbin="$(fetch rkbin "$RKBIN_URL" "$RKBIN_REF")" ub="$(fetch u-boot "$UBOOT_URL" "$UBOOT_REF")" @@ -103,10 +110,12 @@ python3 - "$ub/configs/$UBOOT_DEFCONFIG" "$OUT/u-boot/.config" <<-'PY' PY log "building u-boot" +# TEE lands in binman as -a tee-os-path (see the Makefile's binman rule) and +# fills the FIT's op-tee node. make -C "$ub" O="$OUT/u-boot" CROSS_COMPILE="$CROSS_COMPILE" \ - ROCKCHIP_TPL="$ddr" -j"$JOBS" + ROCKCHIP_TPL="$ddr" TEE="$tee" -j"$JOBS" -for f in idbloader.img u-boot.img u-boot-rockchip-usb471.bin u-boot-rockchip-usb472.bin; do +for f in idbloader.img u-boot-rockchip-usb471.bin u-boot-rockchip-usb472.bin; do if [ -f "$OUT/u-boot/$f" ]; then cp -f "$OUT/u-boot/$f" "$OUT/$f" else @@ -114,6 +123,19 @@ for f in idbloader.img u-boot.img u-boot-rockchip-usb471.bin u-boot-rockchip-usb fi done +# With CONFIG_SPL_OPTEE_IMAGE the SPL payload is binman's u-boot.itb, not the +# legacy uImage the Makefile also produces. It ships under the u-boot.img name +# because everything downstream -- mk-image.sh, flash.sh, the NAND partition +# map -- knows the payload by that name, and the SPL identifies the format by +# magic, not by filename. +[ -f "$OUT/u-boot/u-boot.itb" ] || die "u-boot did not produce u-boot.itb" +cp -f "$OUT/u-boot/u-boot.itb" "$OUT/u-boot.img" + +# The NAND slot for U-Boot is fixed; the SD gap is checked by mk-image.sh. +itbsize="$(stat -c %s "$OUT/u-boot.img")" +[ "$itbsize" -le $(( NAND_UBOOT_SIZE )) ] \ + || die "u-boot.img is $itbsize bytes, the NAND slot holds $(( NAND_UBOOT_SIZE ))" + # The Rockchip usbplug loader, needed to write the flash over USB. It is a # prebuilt from rkbin, not something we compile, but it belongs next to the # images it flashes. diff --git a/scripts/check.sh b/scripts/check.sh index 7a7fff3..cf5f008 100755 --- a/scripts/check.sh +++ b/scripts/check.sh @@ -64,6 +64,34 @@ else echo " ok the SD u-boot offset agrees between board.env and the defconfig" fi +log "op-tee memory window" +# One address, four files, three toolchains: OP-TEE links at CFG_TZDRAM_START, +# the boot FIT has to load it there, the kernel has to keep out of the whole +# TZDRAM+SHM window, and so do U-Boot's staging addresses. Nothing but this +# check keeps them together. +fit_load="$(sed -nE 's/^[[:space:]]*load = <(0x[0-9a-fA-F]+)>;$/\1/p' \ + "$ubt/arch/arm/dts/$dtb_name-u-boot.dtsi" | head -1)" +read -r tee_base tee_size < <(sed -nE \ + 's/^[[:space:]]*reg = <(0x[0-9a-fA-F]+) (0x[0-9a-fA-F]+)>;.*/\1 \2/p' \ + "$BOARD_DIR/kernel/dts/$KERNEL_DTS.dts") +if [ -z "$fit_load" ] || [ -z "$tee_base" ]; then + echo ' FAIL cannot find the op-tee load address or the reserved-memory carve-out'; fail=1 +elif [ $(( fit_load )) != $(( tee_base )) ]; then + echo " FAIL the FIT loads OP-TEE at $fit_load, the kernel reserves $tee_base"; fail=1 +else + echo " ok OP-TEE loads at $tee_base and the kernel reserves it" +fi +if [ -n "$tee_base" ]; then + bad_addr=0 + while read -r var addr; do + if [ $(( addr >= tee_base && addr < tee_base + tee_size )) -eq 1 ]; then + echo " FAIL $var=$addr is inside the OP-TEE window [$tee_base, +$tee_size)"; bad_addr=1 + fi + done < <(sed -nE 's/^(kernel_addr_r|fdt_addr_r|ramdisk_addr_r|scriptaddr|pxefile_addr_r)=(0x[0-9a-fA-F]+)$/\1 \2/p' "$pico_env") + [ "$bad_addr" -eq 0 ] && echo ' ok the U-Boot staging addresses stay clear of the OP-TEE window' + fail=$(( fail | bad_addr )) +fi + log "source patches" # Ensure every patch gets applied shopt -s nullglob diff --git a/scripts/install-deps.sh b/scripts/install-deps.sh index c7f246a..0d760ec 100755 --- a/scripts/install-deps.sh +++ b/scripts/install-deps.sh @@ -7,7 +7,7 @@ pkgs=( gcc-arm-linux-gnueabihf g++-arm-linux-gnueabihf libc6-dev-armhf-cross build-essential bc bison flex libssl-dev libgnutls28-dev device-tree-compiler python3 python3-dev python3-setuptools - python3-pyelftools swig libpython3-dev + python3-pyelftools python3-cryptography swig libpython3-dev # u-boot binman/tooling uuid-dev liblz4-tool lz4 zstd # rootfs. eatmydata is for mmdebstrap's hook of the same name, which drops diff --git a/scripts/lib.sh b/scripts/lib.sh index f1021a2..3bf732f 100755 --- a/scripts/lib.sh +++ b/scripts/lib.sh @@ -39,6 +39,22 @@ KERNEL_REF="${KERNEL_REF:-1ba51b059f25533c5529b7f68186190b47d6a7b3}" RKBIN_URL="${RKBIN_URL:-https://github.com/rockchip-linux/rkbin.git}" RKBIN_REF="${RKBIN_REF:-ecb4fcbe954edf38b3ae037d5de6d9f5bccf81f4}" +# Upstream OP-TEE OS. The pin is the very commit that added the RV1106 to +# plat-rockchip; no tagged release carries it yet. Move to a release tag once +# one exists. +OPTEE_URL="${OPTEE_URL:-https://github.com/OP-TEE/optee_os.git}" +OPTEE_REF="${OPTEE_REF:-ed18ba2d11ce4b447e45e26b9065e77228f8f0de}" + +# OP-TEE client library and example TAs, pinned to the latest tagged release +# (the OPTEE_REF above sits between 4.9.0 and the next tag). optee_client is +# a build-time dependency only: the examples' host binaries link against its +# libteec at build time but run against Debian's libteec2 on the board -- +# both carry the same libteec.so.2 soname and the stable TEEC_* GP Client API. +OPTEE_CLIENT_URL="${OPTEE_CLIENT_URL:-https://github.com/OP-TEE/optee_client.git}" +OPTEE_CLIENT_REF="${OPTEE_CLIENT_REF:-4.9.0}" +OPTEE_EXAMPLES_URL="${OPTEE_EXAMPLES_URL:-https://github.com/linaro-swg/optee_examples.git}" +OPTEE_EXAMPLES_REF="${OPTEE_EXAMPLES_REF:-4.9.0}" + # RKNPU2 userspace runtime and headers. RKNPU2_URL="${RKNPU2_URL:-https://github.com/airockchip/rknn-toolkit2.git}" RKNPU2_REF="${RKNPU2_REF:-v2.3.2}"