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: 37 additions & 37 deletions .github/workflows/ci.yml

Large diffs are not rendered by default.

14 changes: 7 additions & 7 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,11 @@ An implementation of [openkal][kal] on the RISC-V Supervisor Binary Interface.

```toml
[dependencies]
openkal = "0.9.0"
openkal-opensbi = "0.3.0"
openkal = "0.13.0"
openkal-opensbi = "0.7.0"
```

## The portable RISC-V backend, as distinct from a board's own
## The portable RISC-V backend, as distinct from a board's own

A board-supplied backend writes to a device address, and that address is a board
fact. The same binary on a second RISC-V machine writes to something that is not
Expand Down Expand Up @@ -44,7 +44,7 @@ None of the five is a deviation. Clause 6.1 makes an interface an implementation
does not provide absent at the link, so a program requiring one is refused when
it is built rather than when it runs.

⚠️ **`time` used to be on that list, with a reason, and the reason was wrong.**
**`time` used to be on that list, with a reason, and the reason was wrong.**

It read: SBI can arm a timer interrupt, which is a mechanism for a kernel rather
than a clock a program can read. The first half is true. The second does not
Expand All @@ -60,7 +60,7 @@ kernel beneath:
t0=333572 t1=381292 ADVANCES
```

The conclusion got rechecked and the reason beside it did not. The two
The conclusion got rechecked and the reason beside it did not. The two
minutes that refuted it had been available for as long as the file existed.

`time` is therefore provided: a monotonic count, an exact granularity, and a
Expand Down Expand Up @@ -103,7 +103,7 @@ run here carried a copy: `examples/hello` had one, the C++ runtime's
`same-source` example had a second, and the specification's conformance suite
would have needed a third. None of those is a property of a program.

⚠️ **A program that states one as well states it twice.** The fact reaches
**A program that states one as well states it twice.** The fact reaches
consumers transitively, and two linker scripts are both applied — which fails as
overlapping output sections and says nothing about there being two:

Expand All @@ -126,7 +126,7 @@ v2.0, and older firmware answers `SBI_ERR_NOT_SUPPORTED` — on which every writ
would silently transfer nothing. The legacy one-character extension is the
fallback.

⚠️ **No function-local `static`.** A guarded local static compiles to
**No function-local `static`.** A guarded local static compiles to
`__cxa_guard_acquire`/`__cxa_guard_release`, which a freestanding target has no
runtime to supply. Measured: the link fails naming both. `-fno-threadsafe-statics`
would also silence it, but a flag that has to be remembered is weaker than a
Expand Down
12 changes: 6 additions & 6 deletions board.ld
Original file line number Diff line number Diff line change
@@ -1,15 +1,15 @@
/* The memory map of a program this implementation starts, and the symbols its
* startup object reads.
*
* ⭐⭐ THIS FILE IS A BOARD FACT AND THEREFORE BELONGS TO THIS PACKAGE.
* THIS FILE IS A BOARD FACT AND THEREFORE BELONGS TO THIS PACKAGE.
*
* Until now every program that wanted to run over OpenSBI carried its own copy:
* `examples/hello` had one, `openkal-llvm-runtime/examples/same-source` had a
* second, and the specification's conformance suite would have needed a third.
* The load address is not a property of any of them — it is where this firmware
* hands control over, which is precisely what this package exists to know.
*
* ⚠️ Measured 2026-08-23: the conformance suite BUILDS for `riscv64-none-elf`
* Measured 2026-08-23: the conformance suite BUILDS for `riscv64-none-elf`
* against this implementation and then produces an image whose entry point is
* 0x0, because nothing placed it. The suite is written to run in a program that
* carries no other runtime, and asking it to carry a board's memory map would
Expand All @@ -19,7 +19,7 @@
* ⇒ Supplied through `build.mcpp`, which reaches the CONSUMER's link line. A
* program adds nothing and gets a layout that works.
*
* ⚠️ THE SIZES ARE GENEROUS RATHER THAN MINIMAL, and that is the one judgement
* THE SIZES ARE GENEROUS RATHER THAN MINIMAL, and that is the one judgement
* in this file. A C program printing a string needs neither 256 KiB of stack
* nor 16 MiB of heap; a program carrying a C library and a C++ standard library
* does, and it exhausts the smaller figures during its own initialisation —
Expand All @@ -32,15 +32,15 @@
* OpenSBI occupies 0x80000000 upward and hands control to the next stage at
* 0x80200000, which is why this address and not the start of RAM.
*
* ⚠️ FIRMWARE JUMPS TO THE LOWEST LOADED ADDRESS AND NOT TO THE ENTRY THE IMAGE
* FIRMWARE JUMPS TO THE LOWEST LOADED ADDRESS AND NOT TO THE ENTRY THE IMAGE
* RECORDS. Measured 2026-08-23, after an attempt to put the ELF header inside
* the first loaded segment so that `__ehdr_start` would be usable: the entry
* moved to 0x80200270, OpenSBI still announced `Next Address 0x80200000`, and
* the machine hung executing the header as instructions. So the first thing at
* the load address has to be the first instruction, and everything that would
* otherwise be read out of the program headers is named here instead.
*
* ⚠️ FOUR THINGS BEYOND THE MINIMUM, AND EACH IS SOMETHING A C++ PROGRAM HAS
* FOUR THINGS BEYOND THE MINIMUM, AND EACH IS SOMETHING A C++ PROGRAM HAS
* THAT A C ONE DOES NOT.
*
* The initialiser arrays. Every static object with a constructor puts a
Expand Down Expand Up @@ -127,7 +127,7 @@ SECTIONS {
/* 256 KiB of stack. See the note above. */
. = ALIGN(16); . = . + 0x40000; __stack_top = .;

/* AND THE HEAP, WHICH FOR THIS PROGRAM CANNOT BE THE IMPLEMENTATION'S
/* AND THE HEAP, WHICH FOR THIS PROGRAM CANNOT BE THE IMPLEMENTATION'S
* DEFAULT.
*
* openkal-opensbi carries a 64 KiB static region for a program that
Expand Down
4 changes: 2 additions & 2 deletions build.mcpp
Original file line number Diff line number Diff line change
@@ -1,13 +1,13 @@
import mcpp;

// THE BOARD'S MEMORY MAP, PUT ON THE CONSUMER'S LINK LINE.
// THE BOARD'S MEMORY MAP, PUT ON THE CONSUMER'S LINK LINE.
//
// `board.ld` records where this firmware hands control over and what a program
// started that way needs its layout to contain. Both are facts about this
// environment rather than about any program, which is why they are here and not
// copied into each one — see the head of that file for what the copies were.
//
// ⚠️ `link_script` reaches the CONSUMER's link line, and a relative path in
// `link_script` reaches the CONSUMER's link line, and a relative path in
// `ldflags` would resolve against the build directory instead of against this
// package. That is the same reason `openkal-macos` supplies its stub through a
// `link_search` rather than a path in a flag.
Expand Down
2 changes: 1 addition & 1 deletion examples/hello/mcpp.toml
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ runner = ["qemu-system-riscv64", "-machine", "virt", "-nographic",
"-no-reboot", "-bios", "default", "-kernel"]

[dependencies]
# ⚠️ THE FORM MUST MATCH THE ONE THE IMPLEMENTATION USES, not merely the
# THE FORM MUST MATCH THE ONE THE IMPLEMENTATION USES, not merely the
# version. mcpp refuses a graph in which one package reaches a dependency by
# version and another by git:
#
Expand Down
4 changes: 2 additions & 2 deletions examples/hello/src/main.cpp
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
// openkal over SBI, with no C library and no board package beneath it.
//
// ⚠️ The entry point is `_start` and not `main`: nothing here supplies a C
// The entry point is `_start` and not `main`: nothing here supplies a C
// runtime, so there is no crt0 to call one. OpenSBI hands control to the image
// at its load address in supervisor mode with a stack that the linker script
// below establishes.
Expand All @@ -27,7 +27,7 @@ extern "C" void kmain() {
say(p ? "heap ok\n" : "heap exhausted\n");
kal_free(p, 64, 16);

// ⚠️ THE ASSERTION IS THAT IT MOVES, NOT THAT IT READS.
// THE ASSERTION IS THAT IT MOVES, NOT THAT IT READS.
//
// A clock that returns a constant reads perfectly well and is worthless,
// and it is the exact failure the comment this interface replaced was
Expand Down
10 changes: 5 additions & 5 deletions mcpp.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# openkal on the RISC-V Supervisor Binary Interface.
#
# THE PORTABLE RISC-V BACKEND, AS DISTINCT FROM A BOARD'S OWN.
# THE PORTABLE RISC-V BACKEND, AS DISTINCT FROM A BOARD'S OWN.
#
# A board-supplied backend writes to a device address, and that address is a
# board fact: the same binary on a second RISC-V machine writes to something
Expand All @@ -14,7 +14,7 @@
[package]
namespace = "mcpplibs"
name = "openkal-opensbi"
version = "0.6.0"
version = "0.7.0"
description = "An implementation of openkal on the RISC-V Supervisor Binary Interface, portable across every machine whose firmware provides one"
license = "Apache-2.0"

Expand All @@ -33,9 +33,9 @@ repo = "https://github.com/mcpplibs/openkal-opensbi"
# The contract, not an implementation of it. Declaring it turns a version
# mismatch into a resolution-time message rather than a link-time one.
[dependencies]
openkal = "0.12.0"
openkal = "0.13.0"

# WHAT RECEIVES CONTROL, WHICH IS A STATEMENT ABOUT THE PROGRAM.
# WHAT RECEIVES CONTROL, WHICH IS A STATEMENT ABOUT THE PROGRAM.
#
# A program that already carries a runtime has an entry object of its own and
# must not get a second. A program that does not — one whose C library is
Expand All @@ -61,7 +61,7 @@ flags = [
"-fno-asynchronous-unwind-tables"] },
]

# ⚠️ TWO FIGURES THAT BELONG TO THE MACHINE, DECLARED RATHER THAN ASSUMED.
# TWO FIGURES THAT BELONG TO THE MACHINE, DECLARED RATHER THAN ASSUMED.
#
# The heap size is a build input because SBI provides no allocator and the
# region therefore has to come from somewhere. 64 KiB is enough for the
Expand Down
4 changes: 2 additions & 2 deletions src/env.cpp
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
// openkal.env on the RISC-V Supervisor Binary Interface.
//
// ⚠️ EVERY ANSWER HERE IS EMPTY, AND THAT IS AN IMPLEMENTATION RATHER THAN A
// EVERY ANSWER HERE IS EMPTY, AND THAT IS AN IMPLEMENTATION RATHER THAN A
// STUB. THE DIFFERENCE IS THE ONE CLAUSE 6.2 TURNS ON.
//
// Clause 6.2 forbids the arrangement this file could be mistaken for: "an
Expand All @@ -18,7 +18,7 @@
// this one returns for the first of none, by the same rule, and no caller needs
// a special case for either.
//
// ⚠️ WHY IT IS EMPTY, WHICH IS A FACT ABOUT THE ENTRY CONTRACT AND NOT ABOUT SBI
// WHY IT IS EMPTY, WHICH IS A FACT ABOUT THE ENTRY CONTRACT AND NOT ABOUT SBI
//
// Firmware enters the image at its load address with a hart identifier and a
// device tree in registers. Neither is a command line. A device tree CAN carry
Expand Down
20 changes: 10 additions & 10 deletions src/kal.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
//
// SBI has no such property. The console here is a call into firmware that
// already knows the machine, so one binary runs under OpenSBI on QEMU's `virt`
// and on a real board without being rebuilt. That makes this the portable
// and on a real board without being rebuilt. That makes this the portable
// RISC-V backend and the board's the specific one — and a project picks by
// which property it needs, not by which is better.
//
Expand All @@ -22,7 +22,7 @@
// clause 6.2 says the remedy for an operation that cannot be provided is that
// its absence be expressed by its absence rather than by a run-time refusal.
//
// ⚠️ `time` USED TO BE ON THAT LIST, WITH A REASON, AND THE REASON WAS WRONG.
// `time` USED TO BE ON THAT LIST, WITH A REASON, AND THE REASON WAS WRONG.
//
// It read: SBI can arm a timer interrupt, which is a mechanism for a kernel
// rather than a clock a program can read. The first half is true; the second
Expand All @@ -42,12 +42,12 @@ constexpr kal_uintptr kStdin = 0;
constexpr kal_uintptr kStdout = 1;
constexpr kal_uintptr kStderr = 2;

// ⚠️ Probed once rather than assumed. DBCN arrived in SBI v2.0, and firmware
// Probed once rather than assumed. DBCN arrived in SBI v2.0, and firmware
// older than that answers `SBI_ERR_NOT_SUPPORTED` — on which every write would
// silently transfer nothing. The legacy extension is one character per trap and
// is deprecated, which is exactly why it is the fallback and not the default.
//
// ⚠️ A tri-state file-scope variable, and NOT a function-local `static`.
// A tri-state file-scope variable, and NOT a function-local `static`.
//
// A guarded local static compiles to `__cxa_guard_acquire`/`__cxa_guard_release`
// — thread-safe initialisation supplied by the C++ runtime, which a
Expand Down Expand Up @@ -104,15 +104,15 @@ kal_intptr write_all(const unsigned char* p, kal_uintptr n) {
// silently wrong is a simulation. Exhaustion is a defined outcome — `kal_alloc`
// returns null — and every caller already has to handle it.
//
// ⚠️ `kal_free` therefore does nothing, and that is stated rather than hidden.
// `kal_free` therefore does nothing, and that is stated rather than hidden.
// A program whose allocation pattern needs reuse should place a real allocator
// above this one; that is a policy decision, and openkal carries mechanism.
alignas(16) unsigned char g_heap[OPENKAL_OPENSBI_HEAP_BYTES];
kal_uintptr g_used = 0;

} // namespace

// AND THE PROGRAM MAY SAY WHERE INSTEAD, FOR THE SAME REASON IT SAYS WHERE
// AND THE PROGRAM MAY SAY WHERE INSTEAD, FOR THE SAME REASON IT SAYS WHERE
// THE STACK IS.
//
// The region above is a static array, which means its size is in the image and
Expand All @@ -121,7 +121,7 @@ kal_uintptr g_used = 0;
// carries a C library and a C++ standard library allocates during its own
// initialisation, before `main`, and 64 KiB does not survive it.
//
// ⚠️ AND THE WAY THAT SHOWS IS NOT A DIAGNOSTIC. Measured 2026-08-23: a
// AND THE WAY THAT SHOWS IS NOT A DIAGNOSTIC. Measured 2026-08-23: a
// bare-metal `import std;` program linked, started, and printed NOTHING — the
// allocator ran out inside the standard library's static initialisation, before
// any stream existed to report it on. A message would have needed the very
Expand All @@ -146,7 +146,7 @@ namespace {
struct region { unsigned char* base; kal_uintptr size; };

region heap_region() {
// ⚠️ `+` on each: these are arrays, and comparing two arrays directly is
// `+` on each: these are arrays, and comparing two arrays directly is
// deprecated in this dialect because it compares addresses while reading
// like a comparison of contents. Decaying them says which was meant.
if (+__heap_start != nullptr && +__heap_end > +__heap_start)
Expand Down Expand Up @@ -183,7 +183,7 @@ kal_stream kal_stderr(void) { return kal_stream{kStderr}; }

kal_intptr kal_stream_write(kal_stream s, const void* buf, kal_uintptr n) {
if (s.h != kStdout && s.h != kStderr) return -kal_err_invalid;
// ⚠️ Both streams reach the same console. SBI has one, and reporting two
// Both streams reach the same console. SBI has one, and reporting two
// that are secretly one would be a claim the firmware cannot honour.
return write_all(static_cast<const unsigned char*>(buf), n);
}
Expand Down Expand Up @@ -250,7 +250,7 @@ void kal_free(void*, kal_uintptr, kal_uintptr) {}

// ── The interfaces this machine does not have are absent, and stay absent ───
//
// ⚠️ NO `kal_fs_props`, NO `kal_task_props`, AND THAT IS THE MECHANISM RATHER
// NO `kal_fs_props`, NO `kal_task_props`, AND THAT IS THE MECHANISM RATHER
// THAN AN OMISSION.
//
// 0.1.3 defined both as zero, because a capability-querying program failed to
Expand Down
2 changes: 1 addition & 1 deletion src/sbi.h
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
* SBI is the interface a RISC-V supervisor has to the firmware beneath it. It
* is invoked with `ecall`: the extension identifier in a7, the function
* identifier in a6, arguments in a0-a5, and a two-word result in a0 (an error
* code) and a1 (a value). ⚠️ That shape is the same two-word return openkal
* code) and a1 (a value). That shape is the same two-word return openkal
* specifies for its own results, and for the same reason — it is what crosses a
* privilege boundary in registers on this architecture.
*
Expand Down
Loading
Loading