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:
- They are source, not a recorded binary. They
#includeSoC.dtsifiles anddt-bindingsheaders through the C preprocessor, so the pipeline first runsclang -E(with Zephyr'sdts/andinclude/directories on the include path), then compiles with cleave's DTS compiler. Z.3 starts from QEMU's binary dump and a hand-written COM1, so it never needed this. - They carry
pinctrl-0andpinctrl-names, and the MSPM0 UART and I2C drivers need the pin configuration they name; also clocks. Track Z's generator writes no pinctrl macros, and no slice yet does (see What Track Z leaves). - Their configuration comes from the board's
_defconfigand the board's and SoC'sKconfig.defconfig, not from Z.6's short hand-checked table. Whether Z.6's approach stretches to a board configuration, or a fuller Kconfig reader is needed, is open (PLAN, open question 3). - The Zepto board arrives with Zephyr v4.5.0, past the current pin (PLAN, open question 1).
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:
- The same meaning in three places. P.2's rule still holds: every program
must parse as Crystal and mean the same thing. Crystal's
+raises on Int32 overflow, so the interpreter and the emitted code must check it too, or the subset admits only the wrapping operators (&+). A differential spec runs each example program three ways and requires the same output: compiled by Crystal on Linux, interpreted, and emitted and run onqemu_cortex_m0. That is Demonstrability; the speed claim is measured the same way, as cycles per loop iteration interpreted and emitted. - Memory for the tree. Glint's heap (3.4) takes whole 4 KiB pages, a
quarter of the Zepto's RAM each. The tree needs a fixed arena that is
emptied after each line or after
emit, with a stated node limit. - Saving to flash. Zephyr v4.4.2 has no flash driver for the MSPM0
(checked: nothing under
drivers/flashnames it). Until one exists, which would go upstream like S.5's I2C driver, emitted code can run from SRAM (the M0+ executes from SRAM) or be saved by the testbench through the BSL. Code saved to flash runs in place, soemiteither writes position- independent code or code for the address it will be saved at.
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
- xv6 §2.1 "Abstracting physical resources": "Some operating systems for embedded devices or real-time systems are organized in this way."
- Hailperin §3.5.1 "Earliest Deadline First Scheduling".
- Tanenbaum: MOS4 §1.4.6 "Embedded Operating Systems" / MOS5 §1.4.5 "The Internet of Things and Embedded Operating Systems".
- Rationale: xv6 names the library organization that Track S uses, in which the operating system is linked into the application rather than isolated from it. Tanenbaum's section on embedded systems describes the kind of target that organization suits. Hailperin's earliest-deadline-first section is the scheduling policy to reach for if a small target's work becomes time-critical. xv6 stays the spine because its §2.1 states, in one sentence, why giving up isolation is acceptable on such a device.