Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
74 changes: 70 additions & 4 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ jobs:
# The specification is taken from the branch of the same name where one
# exists, so a change spanning both repositories is reviewed as a whole.
OPENKAL_BRANCH: ${{ github.head_ref || github.ref_name }}
MCPP_VERSION: 2026.11.12.1
MCPP_VERSION: 2026.11.15.1
XLINGS_VERSION: v2026.10.26.1
XLINGS_NON_INTERACTIVE: '1'
steps:
Expand Down Expand Up @@ -202,8 +202,11 @@ jobs:
}
subst mcpp.toml .spec
subst examples/hello/mcpp.toml ../../.spec
- name: Install OVMF and qemu
run: sudo apt-get update && sudo apt-get install -y --no-install-recommends ovmf qemu-system-x86
subst examples/std/mcpp.toml ../../.spec
# mtools and dosfstools make the writable FAT volume the Shell runs from (0.11.0), and read
# back what the program left on it.
- name: Install OVMF, qemu and the FAT tools
run: sudo apt-get update && sudo apt-get install -y --no-install-recommends ovmf qemu-system-x86 mtools dosfstools

- name: The example is a real EFI application
run: |
Expand Down Expand Up @@ -236,6 +239,69 @@ jobs:
grep -q "all held" uefi.log
[ "$status" -eq 85 ] || { echo "::error::QEMU ended with $status, not 85 (42 << 1 | 1)"; exit 1; }

# THROUGH THE UEFI SHELL (0.11.0). With no default boot file, OVMF starts its Shell, which runs
# startup.nsh from the volume: the program is started by name with a quoted argument, on a
# writable FAT volume, with a virtual random device for the firmware's RNG protocol. The
# example checks the Shell's arguments (`"a b"' is one), writes, reads, renames, lists and
# removes on its own volume, and fills bytes from the random source; the host then reads the
# file it left.
- name: Started by the UEFI Shell, it receives the Shell's arguments and uses its volume
run: |
set -euo pipefail
cd examples/hello
EFI=$(find target -name "hello.efi" | head -1)
rm -f shell.img
mkfs.vfat -C -n OKUEFI shell.img 65536 > /dev/null
printf '@echo -off\r\nfs0:\r\nhello.efi --shell "a b" c\r\n' > startup.nsh
mcopy -i shell.img "$EFI" ::/hello.efi
mcopy -i shell.img startup.nsh ::/startup.nsh
set +e
timeout 180 qemu-system-x86_64 -machine q35 -m 256 -nographic -no-reboot \
-drive if=pflash,format=raw,readonly=on,file=/usr/share/OVMF/OVMF_CODE_4M.fd \
-drive if=virtio,format=raw,file=shell.img -device virtio-rng-pci \
-device isa-debug-exit,iobase=0xf4,iosize=0x04 -net none < /dev/null > shell.log 2>&1
status=$?
set -e
tr -d '\000' < shell.log | sed 's/\x1b\[[0-9;]*[A-Za-z]//g' | grep -v '^Press ESC' | tail -70
grep -q "a quoted argument with a space is one argument" shell.log
grep -q "the firmware's random source fills bytes" shell.log
grep -q "all held" shell.log
[ "$status" -eq 85 ] || { echo "::error::QEMU ended with $status, not 85 (42 << 1 | 1)"; exit 1; }
mdir -i shell.img ::/okfs
mtype -i shell.img ::/okfs/kept.txt | tr -d '\r' | grep -qx "kept by openkal-uefi" \
|| { echo "::error::okfs/kept.txt was not left on the volume as written"; exit 1; }

# BENEATH THE C LIBRARY (0.11.0): examples/std, `import std' on openkal-platform's row with
# this implementation in place of the released one. The Shell's arguments reach `main', files
# are written through the standard library, and std::random_device draws on the firmware's
# source; mimalloc, the C library's allocator, warns when it finds no secure random source,
# and the warning is required to be absent.
- name: Beneath the C library, through the Shell, with no random-source warning
run: |
set -euo pipefail
cd examples/std
mcpp build --target x86_64-uefi
EFI=$(find target -name "std.efi" | head -1)
rm -f std.img
mkfs.vfat -C -n OKSTD std.img 65536 > /dev/null
printf '@echo -off\r\nfs0:\r\nstd.efi --shell "a b" c\r\n' > startup.nsh
mcopy -i std.img "$EFI" ::/std.efi
mcopy -i std.img startup.nsh ::/startup.nsh
set +e
timeout 180 qemu-system-x86_64 -machine q35 -m 1024 -nographic -no-reboot \
-drive if=pflash,format=raw,readonly=on,file=/usr/share/OVMF/OVMF_CODE_4M.fd \
-drive if=virtio,format=raw,file=std.img -device virtio-rng-pci \
-device isa-debug-exit,iobase=0xf4,iosize=0x04 -net none < /dev/null > std.log 2>&1
status=$?
set -e
tr -d '\000' < std.log | sed 's/\x1b\[[0-9;]*[A-Za-z]//g' | grep -v '^Press ESC' | tail -30
if grep -qi "secure randomness" std.log; then
echo "::error::the C library's allocator found no secure random source"; exit 1
fi
grep -q "all held" std.log
[ "$status" -eq 85 ] || { echo "::error::QEMU ended with $status, not 85 (42 << 1 | 1)"; exit 1; }
mtype -i std.img ::/okstd/written.txt | tr -d '\r' | grep -qx "written through the C library"

# ---------------------------------------------------------------------------
# The cross-compilation is performed FROM three systems, not only from Linux.
#
Expand Down Expand Up @@ -274,7 +340,7 @@ jobs:
# The specification is taken from the branch of the same name where one
# exists, so a change spanning both repositories is reviewed as a whole.
OPENKAL_BRANCH: ${{ github.head_ref || github.ref_name }}
MCPP_VERSION: 2026.11.12.1
MCPP_VERSION: 2026.11.15.1
XLINGS_VERSION: v2026.10.26.1
XLINGS_NON_INTERACTIVE: '1'
steps:
Expand Down
106 changes: 91 additions & 15 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -35,21 +35,66 @@ C library states `c-abi = false` for the row and calls openkal directly.
The firmware calls `efi_main`; this implementation records its tables, turns off
the boot manager's watchdog, runs the program's static initialisers (read from
the image's own `.ctors` and `.CRT` sections, there being no C runtime) and calls
`main(argc, argv, envp)`. The arguments are the image's load options split at
spaces; firmware that gave none gives one empty name.
`main(argc, argv, envp)`.

The arguments (0.11.0) are the UEFI Shell's where the Shell started the program:
it installs `EFI_SHELL_PARAMETERS_PROTOCOL` on the image handle with the command
line already divided, its quotes and its escape character `^` removed, and each
argument is made UTF-8. Started otherwise, the arguments are the image's load
options divided by the same rules: spaces and tabs separate, a double quote opens
or closes a part in which they do not, `^` takes the next character as it is.
0.10.0 divided the load options at spaces alone, so `"a b"` arrived as two
arguments with their quotes. Firmware that gave none gives one empty name.

| openkal | UEFI |
|---|---|
| `stream` | `SIMPLE_TEXT_OUTPUT_PROTOCOL` (UCS-2, `\n` as CR LF); input reports its end |
| `memory` | `AllocatePool` / `FreePool`, over-allocated for an alignment below a page; pages for a page or more, placed at the alignment from the memory map (0.10.0) |
| `env` | the load options; no variables, image, loader or system name (absent) |
| `env` | the Shell's `EFI_SHELL_PARAMETERS_PROTOCOL`, else the load options divided with quotes (0.11.0); no variables, image, loader or system name (absent) |
| `fs` | `SIMPLE_FILE_SYSTEM_PROTOCOL` of the volume the image was loaded from (`LoadedImage->DeviceHandle`), its root the one directory supplied, named `/`; `FILE_PROTOCOL` for opening, transfers, `GetInfo`/`SetInfo` (size, time, rename), `Delete`, and a directory's reads for a listing (0.11.0) |
| `random` | `RNG_PROTOCOL` (`LocateProtocol`, the source's default algorithm); firmware without one answers `kal_err_not_supported` (0.11.0) |
| `time` | `GetTime` for the wall clock; the processor's counter, measured against `Stall`, for the monotonic one; `Stall` for a sleep |
| `abort` | `Exit` with the status as a warning code, so a starter reads the number |

`process` waits for `fs` over the firmware's file system protocol, since a
program is started by name through a directory; the specification's property for
an environment with one task (`KAL_PROCESS_PROP_RUNS_TO_END`, openkal 0.25.1
clause 7.19) is what it will claim. The rest have no firmware service beneath them.
`process` is not provided. What it needs of `fs` is present from 0.11.0: a
program is found by name beneath a directory, and `LoadImage` and `StartImage`
start it, the second returning when the started image returns, which is the
specification's property for an environment with one task
(`KAL_PROCESS_PROP_RUNS_TO_END`, openkal 0.25.1 clause 7.19). What remains is the
interface itself: the vector passed as the started image's load options, quoted
so that the division above recovers it (clause 7.6); the three streams, which
`StartImage` cannot redirect; and the grants of clause 7.13. The rest have no
firmware service beneath them.

## The volume (0.11.0)

A name is openkal's, `/`-separated UTF-8, and becomes the firmware's,
`\`-separated UCS-2, one unit at a time; `.` is the directory itself. A handle is a
slot of a table and a generation, so a released word and a word never issued are
refused (clause 7.2); the table holds the firmware's file handle and what the
firmware has no place for (how a file was opened, a directory's name from the
root, which a rename names its destination by).

What the firmware has no service for is answered before it is asked (clause 6.2).
`kal_fs_props` claims `MODIFIED_TIME`, `CAPACITY` and `RENAME_NO_REPLACE`, and no
other position: a FAT volume compares names without regard to case and has no
links, locks, executable property or identity of a node. Links, hard links,
locks, the executable property, the resolved name and attenuation report
`kal_err_not_supported`, and an identity is left clear in `present`. A rename
over an existing name removes it first, so a rename is not claimed atomic; the
firmware runs one task, and nothing comes between the two, which is also what
makes an exclusive create, an append and a rename that does not replace one act
each. A name removed while another handle holds it is removed at once and the
held file then fails its transfers, so `REMOVE_OPEN` is not claimed. FAT records
a modification time to two seconds.

The firmware's source of random bytes is a protocol a driver installs, over the
processor's instruction or a virtual device, so whether there is one is known
only when the program runs. Clause 6.1 states an absent source as an absent
interface, which an application built once for every machine cannot be; the
interface is provided, and firmware without the protocol answers
`kal_err_not_supported` and fills nothing --- neither `kal_err_again`, which
states a source that will fill later, nor bytes from a clock.

## The board's exit

Expand All @@ -70,17 +115,27 @@ this repository's (examples/board), define it.

These structures are ABI. A field one slot out reads a neighbouring function
pointer, which calls something that exists with the wrong arguments. Every member
this implementation reaches is pinned by a `static_assert` on its offset.
this implementation reaches is pinned by a `static_assert` on its offset, the file
system, file, RNG and Shell parameters protocols of 0.11.0 included, with their
GUIDs from the UEFI and UEFI Shell specifications.

## Interfaces (0.10.0)
## Interfaces (0.11.0)

`[kernel-abi] provides-interfaces` states `openkal.abort`, `openkal.env`,
`openkal.exec`, `openkal.memory`, `openkal.stream` and `openkal.time` (0.9.0
stated the list as a key of `[package]`, where the engine does not read it).
`openkal.exec` is pages of type `EfiLoaderCode`, protected through the memory
attribute protocol where the firmware offers it: what a loader of images (the
ok-image runner carried as a UEFI application) places code in. A file cannot be
mapped (no `openkal.fs`), so `kal_exec_map_part` answers not supported.
`openkal.exec`, `openkal.fs`, `openkal.memory`, `openkal.random`,
`openkal.stream` and `openkal.time`, and `kal_interfaces()` answers the same
(0.9.0 stated the list as a key of `[package]`, where the engine does not read
it; 0.11.0 adds `openkal.fs` and `openkal.random`). `openkal.exec` is pages of
type `EfiLoaderCode`, protected through the memory attribute protocol where the
firmware offers it: what a loader of images (the ok-image runner carried as a
UEFI application) places code in. The firmware maps no file, so
`KAL_EXEC_PROP_FILE` is not claimed and `kal_exec_map_part` answers not
supported; a loader reads the file through `openkal.fs` and copies it.

The sources are compiled without stack probes (0.11.0): the firmware's stack is
committed in whole and nothing beneath this implementation defines `__chkstk`,
which a frame larger than a page otherwise calls on a row that does not state
`-mno-stack-arg-probe` for the program.

## Verified

Expand All @@ -101,5 +156,26 @@ error, throws and catches, and returns its status in 0.4 s with 1 GiB or more:
the carrier's allocator and the image's each place a 32 MiB arena at a 256 MiB
alignment.

0.11.0, measured with mcpp 2026.11.15.1 under QEMU's q35 with the edk2 firmware
(the same steps run in CI with Ubuntu's OVMF). Booted as `BOOTX64.EFI` from a
read-only volume, `examples/hello` lists the volume's root and reads its own
image through a file's stream; the firmware had no RNG protocol there, and the
source answered `kal_err_not_supported`. Started by the UEFI Shell from
`startup.nsh` as `hello.efi --shell "a b" c` on a writable FAT volume with a
virtual random device, it receives four arguments with `a b` whole; makes,
opens, lists and removes a directory; writes a file and reads it back, at an
offset and through its stream, shortens it, renames it within a directory and
into another, refuses a rename that does not replace and an exclusive create
onto names that exist, sets and reads a modification time, reads the volume's
capacity, and leaves `okfs/kept.txt`, which the host reads; the random source
fills bytes. `examples/std`, `import std` beneath openkal-musl 0.29.6 and
openkal-llvm-runtime 0.20.3 on openkal-platform 0.28.0's row with this
implementation in place of the released one, receives the Shell's arguments in
`main`, reports `/` as its working directory, lists it through
`std::filesystem`, writes and reads a file through `std::ofstream` and
`std::ifstream`, and draws from `std::random_device`; mimalloc's warning that it
is "unable to use secure randomness", printed above 0.10.0, is absent, and
appears again, with `std::random_device` failing, on firmware with no source.

[kal]: https://github.com/mcpplibs/openkal
[plat]: https://github.com/mcpplibs/openkal-platform
2 changes: 1 addition & 1 deletion examples/board/mcpp.toml
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,7 @@ driver = "compiler"
inputs = [{ file = "board/qemu-exit.S" }, { file = "runtime/mem.S" }]

[platform.layers]
"kernel-abi" = "openkal-uefi@0.10.0"
"kernel-abi" = "openkal-uefi@0.11.0"
"c-abi" = false
"c++-abi" = false
"compiler-runtime" = false
Loading
Loading