multiapp¶
one firmware that runs the others
Overview¶
One image that runs the other apps as Zephyr loadable extensions (llext,
doc/multiapp.md), with the USB classes they need living in the image
itself. capture, rfprobe, wpan, stream, osmosdr and
link run as extensions, built from their own sources and loaded from their
files on a FAT volume in the flash, with copies built into the image as the
fallback; hello (ext/hello.c) is the loader check (hello atomic
tests atomics in PSRAM from both CPUs).
An extension’s code and data are loaded into PSRAM; the code runs at the
instruction bus alias of its PSRAM copy (CONFIG_LLEXT_EXEC_ALIAS, on the
Zephyr branch west.yml pins). One app runs per boot: an app cannot be
stopped cleanly, so starting another one reboots into it. USB enumerates once
per boot too: the classes an app needs besides the CDC-ACM shell and DFU
(stream’s CDC-NCM, osmosdr’s vendor class) come with a reboot into it, as
does the internal RAM area that osmosdr and the 802.15.4 driver take turns on.
Building and flashing¶
west build -d build/multiapp --sysbuild -b xiao_esp32s3/esp32s3/procpu esp-sdr-zephyr/apps/multiapp
west flash -d build/multiapp # first install
west dfu --storage -d build/multiapp # the apps' files: the FAT volume
Later, west dfu -d build/multiapp updates the image and west dfu
--storage the volume; a new image needs its volume too (see “Apps as
files”). west dfu --esptool [--storage] writes MCUboot, the image (and the
volume) with esptool, without the boot button: the running app hands the USB
port to the USB-Serial-JTAG first (shell command usj).
Then, on the CDC-ACM shell:
uart:~$ app list
hello 3448 bytes, file 3448 bytes
capture 5124 bytes, file 5124 bytes
rfprobe 8596 bytes, file 8596 bytes
wpan 18216 bytes, file 18216 bytes
stream 30264 bytes, file 30264 bytes
osmosdr 17900 bytes, file 17900 bytes
link 81320 bytes, file 81320 bytes
uart:~$ app run rfprobe
rfprobe: rebooting into it (eject the drive first: the PC may not have written it out)
and, on the shell of the boot into it:
uart:~$ app info
host 0.1.0, build 8a61693d
boot: rfprobe, start 0, USB profile 0x0
rfprobe (file): text 3002 at 0x42167c5c, data 448, bss 4, rodata 872, 6067 bytes in heaps
command probe
uart:~$ probe f 2437
f 2437 ret 0
The app’s root commands (probe here) answer as in the standalone app.
app run <name> reboots into the app unless no app runs yet and USB already
has its classes; CONFIG_MULTIAPP_AUTOSTART names an app to start at every
boot. app info shows the host’s build id, the stack its main() used and
the loaded app’s origin, sizes and commands; app crash what ended the
previous boot (fatal error, watchdog, how far a boot into an app got) and the
USB thread’s stack use in the last DFU download.
Apps as files¶
The upper 4 MB of the flash hold a FAT volume with one <name>.llext per
app (src/drive.c). Every boot reads it before USB starts: app list
shows each app’s file next to the built-in copy (differs when they are
not the same), and the boot’s app is loaded from its file when the file is
good, else from the built-in copy. “Good” is checked before llext sees the
file, from a trailer the build appends (apptrail.py): size, CRC-32, the
stack the app needs, and the host’s build id (buildid.py), so only files
from the same build load. CONFIG_MULTIAPP_EMBEDDED=n drops the built-in
copies (165 KB of flash); a boot then runs an app only from its file.
In a boot without an app the PC sees the volume as a USB drive next to the
shell. Copy files from build/multiapp/multiapp/llext/files/ onto it, eject
it (the PC may cache what it writes), then app run the app. Writes are
slow, about 60 kB/s: every 4 KiB flash page is erased and rewritten. west
dfu --storage writes the whole volume (mkfat.py builds its image).
Adding an app¶
One line in CMakeLists.txt:
multiapp_extension(rfprobe STACK 2048 SOURCES ${apps_dir}/rfprobe/src/main.c)
The build puts include/multiapp/ext.h in front of every source (shell
commands, SYS_INIT, log modules, memory slabs and net_buf pools become
things an extension can do) and exports from the host exactly what the
extensions import (exports.py). APP <dir> gives the extension its app’s
own CONFIG_APP_* values (appconfig.py), USB the classes it needs,
AREA its internal RAM buffers (ESP_SDR_HIGH_BUF_DEFINE()) in the area.
The host needs the app’s other Kconfig options; a missing function fails the
host’s link.
Testing¶
sample.esp_sdr.multiapp.hil checks a board on the desk: the image and its
volume go on with scripts/twister-flash.py, the board boots into hello
from its file, and pytest/test_multiapp.py checks the files, the boot and
hello’s commands (doc/testing.md).