xv6 §1, "Operating system interfaces". "An operating system provides services to user programs through an interface. Designing a good interface turns out to be difficult. On the one hand, we would like the interface to be simple and narrow because that makes it easier to get the implementation right. On the other hand, we may be tempted to offer many sophisticated features to applications. The trick in resolving this tension is to design interfaces that rely on a few mechanisms that can be combined to provide much generality."
Phase 1 builds the narrow interface everything else in Glint stands on: the
EFI-shaped platform interface described under Concept in
PLAN.md. By the end of the phase, Glint has a console,
files and keyboard input, all reached through one lookup mechanism. A small
shell uses nothing else, which shows the passage's claim working. The phase
starts from nothing but the toolchain probe recorded in the README's
Status. It ends with two EFI images that are built and checked but not yet
booted; Phase 2 boots them under the QEMU and U-Boot that Track T builds.
The shell's grammar and matcher come from Track P, which runs alongside this
phase.
All of Phase 1 runs in hosted mode. Every demonstration happens on Linux, on
the first two rungs of the plan's demonstration ladder: specs run against a
fake firmware, and checks of the built image files. It can therefore start
before Track T delivers. Both architectures land together in each slice,
because Phase 1's code is the same for aarch64 and x86_64 apart from the
target name passed to the compiler. Within the phase, slices follow build
dependencies, so a slice may cite an earlier section of the chapter than the
slice before it; across phases, the citations move forward through the book.
Terms such as GUID, PE32+ and ConOut are explained in the
glossary.
Slices
1.1 Project skeleton. The first check-in adds shard.yml, a
bin/glint-build executable that so far only answers --version, and
spec/spec_helper.cr. Nothing else can be built or tested until these
exist. They are kept apart from 1.2 so the build driver's shape can be
reviewed before it learns to compile anything. The skeleton declares no
dependency it does not yet use (cleave arrives with Track P's first grammar
rule, P.1), which is parsimony in its plainest form. It answers to the
phase's opening passage: the interface starts narrow. It is demonstrated by
shards build && bin/glint-build --version and a passing crystal spec.
1.2 First EFI images. This slice adds prelude/, the few Crystal
primitives (pointers, integers, fixed-size arrays) a program needs when
there is no garbage collector and no C library. It also adds
boot/efi_main.cr, whose efi_main does nothing but return EFI_SUCCESS.
glint-build learns to compile this for both architectures and to link
BOOTAA64.EFI and BOOTX64.EFI with a .reloc section, following the
method of BQ2's EfiAppBuilder. This is where the manual probe in the
README becomes a repeatable build. As xv6 §1 puts it, "An operating system
provides services to user programs through an interface", and Glint's first
interface is the one firmware offers it. An image that loads and returns is
the smallest claim that can be checked, so it comes before anything the
image might do. Starting with the smallest checkable claim is
Demonstrability put first. A spec opens both files and checks the PE32+
header, the machine type, subsystem 10 and the relocation directory.
1.3 A console through a fake firmware. This slice adds boot/efi.cr,
Crystal declarations for the parts of the UEFI system table Glint uses,
starting with ConOut. It also adds spec/support/fake_efi.cr, a system
table written in ordinary Crystal whose console records what it is given.
efi_main now prints "Hello from Glint" through ConOut. The same source
is linked into both EFI images and into the specs, which run it against the
fake table on Linux. xv6 §1 explains why the fake comes first: "When a
process needs to invoke a kernel service, it invokes a system call, one of
the calls in the operating system's interface." In hosted mode Glint's
system calls are the firmware's, and the fake lets them be exercised before
any firmware runs them. The fake is the first provider behind the platform
interface. Every later slice can now be demonstrated on Linux without
firmware, which is what Demonstrability asks of each one. It comes after 1.2
because an image must already load and return before it can print anything.
crystal spec asserts the captured text, and both images are rebuilt.
1.4 Calling C and being called back. This slice adds c/hello.c,
compiled with clang. A Crystal lib block (Crystal's way of declaring
functions written in C) declares the C function. A Crystal fun (a function
compiled with C's calling convention) is exported so the C code can call
back to print. Both the specs and the two images link the C object, and the
slice checks struct layout, variadic calls and the Microsoft calling
convention that UEFI uses on x86_64. Zephyr's drivers arrive in Phase 6 as C
code that calls into Crystal implementations of Zephyr's API, and the plan's
rule 4 says a new seam gets its own slice before anything depends on it.
This is that seam, proved on the smallest example before anything leans on
it, which is Loose Coupling. The passage is xv6 §1.5 "Real world": the Unix
interface "persists today in systems like BSD, Linux, and macOS". Interfaces
outlive their implementations, and here the implementation on the other side
is C. A spec prints "Hello from C via Glint", and both images link.
1.5 Finding services by GUID. In UEFI every service is a protocol named
by a GUID, found with LocateProtocol or with HandleProtocol on a handle.
boot/protocol.cr adds typed lookup: a generic method that takes a protocol
type and returns either a pointer of that type or an explicit "not found",
so no call site ever casts. The fake table learns to answer lookups, and
ConOut is now found by GUID instead of read from a fixed field. xv6 §1.2
gives the analogy: "A file descriptor is a small integer representing a
kernel-managed object that a process may read from or write to." For Glint,
a handle plus a GUID is that small name for an object someone else manages.
Every later provider is reached through this one mechanism. That serves
Parsimony in Tambassi's sense [40], reducing the kinds of
mechanism before their number. It also serves Loose Coupling, because it is
the seam that makes providers swappable. It comes after 1.4 because the fake
and the C seam it must serve are now in place, and before 1.6 and 1.7
because both find their protocols through it. A spec finds the console by
GUID and gets a clean "not found" for an absent GUID.
1.6 Reading files. fs/efi_file.cr adds open, read and close through
SIMPLE_FILE_SYSTEM, found with 1.5's lookup and with no new mechanism. The
fake serves files from spec/fixtures/esp/, a host directory standing in
for the EFI system partition. Its passage is xv6 §1.4: "The xv6 file system
provides data files, which contain uninterpreted byte arrays, and
directories, which contain named references to data files and other
directories." Files come before keyboard input because the shell's ls and
cat need them. Adding a second protocol without a second mechanism is
Parsimony. A spec reads spec/fixtures/esp/hello.txt byte for byte through
the interface.
1.7 Reading the keyboard. This slice adds console input through
SIMPLE_TEXT_INPUT (ConIn), found the same way. In the specs, the fake is
fed scripted keystrokes. xv6 §1.2 supplies the convention: "By convention, a
process reads from file descriptor 0 (standard input), writes output to file
descriptor 1 (standard output), and writes error messages to file descriptor
2 (standard error)." ConIn and ConOut are Glint's 0 and 1. Input comes
last among the protocols because only the shell consumes it. Scripted keys
keep the shell testable without a person at a keyboard, which keeps it
demonstrable (Demonstrability). A spec echoes a scripted line.
1.8 The shell. user/shell.cr reads a line from ConIn (1.7), reduces
it with the first rule of Track P's grammar, a call with literal arguments,
and runs it. help and echo use only the console (1.3). ls lists the
ESP root through SIMPLE_FILE_SYSTEM (1.6), and cat prints a file the
same way. Every line it accepts is also valid Crystal, which Track P's P.2
checks with Crystal's own parser, and in the EFI images the rule is matched
by P.4's generated matcher. Its passage is xv6 §1.1: "The shell is an
ordinary program that reads commands from the user and executes them."
Nothing in the shell is special: it uses only what 1.3–1.7 built and what
Track P supplies. That shows the phase's opening claim (a few mechanisms,
combined, give generality) rather than asserting it, which is
Demonstrability. Keeping its syntax to Crystal from the first command is
Clarity. Phase demo: a scripted shell session on the fake table in
crystal spec, with both images rebuilt and their headers checked.
What Phase 1 leaves behind
The phase leaves one lookup mechanism, three protocols (console out, console in, files), a C seam and two checked but unbooted images. Each slice's non-spec diff stays within the plan's size bound (aim under ~200 lines). Phase 2 starts from here.
References
- xv6 §1: "The trick in resolving this tension is to design interfaces that rely on a few mechanisms that can be combined to provide much generality." The slices also quote §1.1, §1.2, §1.4 and §1.5.
- Hailperin §1.2 "What Is an Operating System?": "An operating system is software that uses the hardware resources of a computer system to provide support for the execution of other software." Also §8.3.1 "File Descriptors".
- Tanenbaum: MOS4/5 §1.5 "Operating System Concepts"; MOS4/5 §1.6 "System Calls"; MOS4/5 §12.2.1 "Guiding Principles" [three principles for interface design: simplicity, completeness and efficient implementation. Simplicity is framed as perfection by subtraction, and completeness as doing what is needed and no more, with the least mechanism].
- Rationale: xv6 gives the narrow-interface argument and a shell to demonstrate it. Hailperin's definition casts EFI firmware as an OS in its own right, one that supports the execution of Glint. Tanenbaum's §12.2.1 states as interface principles the same parsimony tests the slice rules take from Tambassi (improvement by subtraction, no more than needed). Three independent sources agree, which grounds OCX's Parsimony virtue.