Login
Track S: Small targets
Login

xv6 §2.1, "Abstracting physical resources". "Some operating systems for embedded devices or real-time systems are organized in this way". "This way" means the operating system is a library that applications link with.

On a microcontroller there is no UEFI firmware to provide Glint's platform interface, so Track S has Zephyr provide it instead. The first target is the BeagleConnect Zepto: a TI MSPM0L1117 with a Cortex-M0+ core, 16 KiB of RAM and 128 KiB of flash. It is small enough to show that the platform interface scales down. The track starts after Phase 6, because it needs Phase 6's Crystal replacements for Zephyr's build generators (6.8) and the compat/zephyr seam (6.9).

On larger machines Glint moves from firmware drivers to its own, as Run modes in the plan describes. Firmware drivers come first because they work everywhere, and Glint's own (native or Zephyr) drivers replace them once Glint needs interrupts, concurrency or speed. On the Zepto there is no firmware to start from, so there is no such transition. Zephyr's drivers provide the interface from the first instruction. Glint's code, linked with Zephyr, forms one ordinary Zephyr firmware image in flash. It is not an EFI image and has no PE loader; only the in-memory interface keeps the EFI shape. The image is not a bootloader and does not need one. The chip runs it from flash at reset. Updates go through TI's bootstrap loader (BSL), a loader in the chip's ROM that runs instead of the image when the BSL_INVOKE pin (PA18) is held high during reset. It writes flash over the UART on PA10/PA11 or over I2C, and because it lives in ROM it cannot be erased, so the board cannot be bricked. A second loader such as MCUboot would take a large share of 128 KiB and need two image slots, so it is left out.

Only the protocols Glint uses are provided: console output and input over the UART, LocateProtocol, and an I2C protocol shaped like the I2C master protocol of the UEFI Platform Initialization specification. Glint uses that same I2C shape on larger machines, where U-Boot offers no I2C through EFI and a Zephyr driver provides it. So one interface covers I2C everywhere. There is no file system. The Phase 1 shell finds no SIMPLE_FILE_SYSTEM, leaves out ls and cat using 1.5's "not found" result, and gains an i2c command instead. Other drivers are added only when needed.

Mainline Zephyr already has a board definition for the Zepto, beagleconnect_zepto, with its UART console, GPIO, ADC and watchdog, flashing through OpenOCD. Its I2C driver is in review upstream as Zephyr pull request #117311.

Demonstrations run on a testbench: a BeagleY-AI with a regular Linux distribution, connected to the Zepto through its HAT-compatible connector (UART and I2C) and SWD (the Arm debug port), with BSL_INVOKE and NRST on BeagleY-AI GPIOs. The BeagleBoard Imaging Utility (bb-imager-rs, a Rust tool) flashes the Zepto through the BSL over UART or I2C, and OpenOCD drives SWD for debugging.

Provisional slices.

The devicetree on a small target

On the Zepto the devicetree is used only at compile time, as Zephyr itself uses it. Track Z's generators (Z.3–Z.5, extended in 6.8) turn the build devicetree into devicetree_generated.h, and Zephyr's drivers compile against it: addresses, register layouts and speeds become constants, each device's configuration is a const structure in flash, and a node that is disabled, or has no driver, produces no code or data at all. The image carries no FDT blob and no devicetree parser. Neither of the run-time pieces the UEFI machines add is linked in: Glint's reader for the firmware's devicetree (2.4a), which exists there to check the build class and to describe the machine to a guest, and late binding (principle 6), which exists to let one build serve several machines. A microcontroller image is built for one board and runs only on it.

The build devicetree is Zephyr's own board devicetree, as principle 6 says for MCUs: boards/qemu/cortex_m0/qemu_cortex_m0.dts for S.2 and S.3, and boards/beagle/beagleconnect_zepto/beagleconnect_zepto.dts from S.4 on. These differ from Track Z's inputs in four ways, each of which needs something Track Z does not yet build:

Each of these lands in the first slice that needs it. S.2 needs the preprocessing, the board configuration and pinctrl, because the emulated board is an nRF51 whose UART names its pins too (qemu_cortex_m0-pinctrl.dtsi). S.4 adds the MSPM0's pinctrl and clock properties.

Slices

S.1 Crystal on a Cortex-M0+. This slice cross-compiles Glint's prelude and Phase 1 code for the Cortex-M0+ (ARMv6-M, Thumb instructions only) and reports the code and data size. Crystal 1.18 has no thumbv6m target, but --target arm-none-eabi --mcpu cortex-m0plus --mattr +thumb-mode produces Thumb code, as a probe confirmed. It comes first because whether Crystal and Glint's code fit 16 KiB of RAM decides everything after it, and the size report is the cheapest evidence there is (Demonstrability).

S.2 A Zephyr EFI provider in emulation. This slice builds a Zephyr application that places a system table in memory, serves console output and input over Zephyr's UART driver, and implements LocateProtocol. It runs on Zephyr's emulated qemu_cortex_m0 board, since QEMU has no MSPM0. It is built with Glint's own generators (6.8) through the compat/zephyr seam (6.9), from the board's own devicetree, preprocessed with clang -E, and with the devicetree used only at compile time (see The devicetree on a small target). It comes after S.1 because it needs Glint's code compiled for the core. Supplying the whole platform interface from different code is Loose Coupling at its limit.

S.3 The shell in emulation. This slice runs Phase 1's shell, unchanged, on S.2's provider. With no file system protocol present, ls and cat drop out cleanly. It comes after S.2 because it is the first check that the provider is complete enough. Running the same shell over a third kind of provider, after the fake table and real firmware, is Demonstrability of Loose Coupling.

S.4 The provider on the Zepto. This slice moves S.2's provider from emulation to the Zepto, using Zephyr's existing beagleconnect_zepto board and its UART console unchanged. It adds what the MSPM0 UART needs beyond S.2's emulated board: its pinctrl and clock properties. It comes after S.3 because the provider is proven in emulation before hardware is involved. Using the upstream board definition, rather than keeping board knowledge in Glint, is the plan's No BSPs rule, and it keeps Glint loosely coupled to hardware that Zephyr maintains (Loose Coupling).

S.5 I2C on the Zepto. This slice brings in Zephyr's MSPM0 I2C driver, pinned to the pull request under review (#117311) until it merges, enables I2C in the Zepto's board configuration, and adds the I2C master protocol to S.2's provider. It comes after S.4 because it needs the board running. Testing on the Zepto and any fix the driver needs go to that pull request, helping it land, rather than into a copy in Glint, so there is one driver, which is Parsimony.

S.6 The testbench. This slice adds the BeagleY-AI side: scripts that hold BSL_INVOKE, pulse NRST, flash the image with bb-imager-rs, attach OpenOCD over SWD with the board's own configuration when debugging is needed, and pass when an expected line appears on the UART, in the manner of Track T's T.11. It comes after S.5 because there is now a full image to flash. It turns rung-4 hardware demonstrations into a command, which is Demonstrability.

S.7 The shell on the Zepto. This slice runs the Phase 1 shell on the Zepto with its new i2c command, which scans the bus and reads a register from a device on the QWIIC connector. It comes last because it uses everything before it. Running unchanged code on the smallest target is the track's Demonstrability of Loose Coupling. Track demo: the shell runs on a 16 KiB microcontroller, flashed and checked by the testbench, with Zephyr standing in for EFI.

S.8 An interpreter that can emit Thumb code *(proposed by the maintainer, ocx-console 5091084ac5; not planned in detail and not started).* "The parser should live as an interpreter, as the command shell, but also have an emit method that walks the ast to generate m0+ code that can be saved, loaded and run for fast performance." Track P's grammar grows past single calls to a small Crystal subset with def, local variables, if, while, integer arithmetic and calls to the shell's commands. The same grammar table and P.4 matcher parse it into a tree, and the shell evaluates the tree directly. emit walks the same tree and writes Cortex-M0+ (Thumb) code, which can be saved to flash, loaded and run. It comes after S.7 because it extends the shell that S.7 puts on the Zepto. When it is tasked it probably splits in two, the interpreter first and emit second. The maintainer wants it, and its first pieces need neither the new toolchain nor hardware, so they start before S.1–S.7: the tree arena (S.8a), QEMU's Cortex-M0 microbit machine in Glint's QEMU build, which S.2 needs too (S.8b), and emit for the first nodes, run on that machine (S.8c). Those three are merged, and S.8 is parked there: the maintainer wants the book's phases first.

What it has to settle first:

Size, an estimate until S.1 and later slices measure it on the M0+. Thumb sizes below are guesses. The one measured figure is today's x86_64 image at its current unoptimised build (build/x86_64/glint.o, trunk e14fbf508e): the shell's commands 21.1 KiB of code, the matcher with its bytecode 20.7 KiB, the lexer 4.9 KiB. Optimised Thumb code is usually much smaller than that, but by how much is not checked.

Part Flash RAM
Zephyr: kernel, UART console, I2C (unverified) 10–16 KiB 3–5 KiB
Glint's prelude, platform interface, shell and matcher 15–25 KiB 2–4 KiB
Interpreter: larger grammar, tree, evaluator 12–24 KiB 5–8 KiB (tree arena, variables, call stack)
emit: Thumb encoder, branch fix-ups 6–12 KiB 1–4 KiB (output buffer)
Room for saved programs 8–16 KiB —
Total 51–93 of 128 KiB 11–21 of 16 KiB

Flash fits even at the high end. RAM does not: the high end is over 16 KiB, so the slice needs a measured budget, a small fixed tree arena, and emit writing straight to its destination rather than through a buffer.

References