Skip to content

Commit e3711c6

Browse files
0.9.0 —— armv7a 后端,以及它回答的那个宽度问题 (#9)
THE FIRST MACHINE HERE WHOSE PAGE-TABLE ENTRY IS NOT EIGHT BYTES. openarch was designed on three application-class 64-bit machines, and its page table interface carries an entry in an `arch_u64` while saying nothing about how one is STORED. `pte_encode.h` recorded the assumption as a statement of fact: "a page-table entry is 64 bits on every machine here". ARMv7-A's short-descriptor entry is 32 bits. The VALUE fits, so the carrier did not have to change; what was missing is a way to ASK. A kernel sizing a table from `sizeof(arch::pte)` builds one twice as large as the hardware walks, and the walker then reads every second word as an entry. Nothing diagnoses that — the table is well formed, the entries are correct, and the machine reads the gaps. `arch_pte_entry_bytes()` is the answer, and every backend implements it: 8 on riscv64, aarch64 and x86_64, 4 on armv7a, 0 on Cortex-M, where the refusal is already carried by `provides` withholding `openarch:address-space`. CORTEX-M COULD NOT SETTLE THIS, AND IT IS WORTH SAYING WHY. It is a 32-bit machine, which is what the plan expected to surface the assumption — but it has an MPU and no page table, so its pte group exists and refuses. A 32-bit machine WITHOUT paging leaves the question exactly where it was. The machine that settles it is 32-bit AND paged. ## The backend All four groups: cpu (TPIDRPRW/TPIDRURW, which unlike riscv's `tp` do not compete), pte (short-descriptor small pages), context (r4-r11, SP, LR — ten words, the smallest saved context here), trap (an eight-entry vector table of one instruction each). `srsdb`/`rfeia` RATHER THAN FOUR MODE STACKS. Each ARMv7-A exception is taken in its own processor mode with its own banked SP, so the obvious implementation requires a board to allocate and install four stacks before the first exception can be reported — four more things to get wrong in the code that runs before anything can report a fault. `srsdb` writes the return state onto the SVC stack from whichever mode is current, so the kernel's one stack serves every exception. THE RETURN OFFSET IS WRITTEN OUT PER EXCEPTION rather than folded into a macro parameter: 4 for IRQ, FIQ and prefetch abort, 8 for data abort, 0 for SVC and undefined. One wrong value resumes into the middle of an instruction. `openarch:preemption` IS WITHHELD. `arch_trap_switch` asks a trap to resume a different context; here the resumption address is on the SVC stack rather than in a register, so switching stacks mid-trap changes which frame `rfeia` pops and is well defined only if the resumed context was suspended through the same path. That is a real design and not one this backend has measured, so a consumer that needs it is refused by name at resolution. The Cortex-M backend already withholds `openarch:address-space` by the same mechanism. ## What is asserted `tests/pte_encoding` gains the armv7a bit patterns, read off the architecture manual rather than produced by the code under test, and the width property itself: the whole entry must lie in the low 32 bits. A new CI job boots a program on qemu `-M virt -cpu cortex-a15` that reads the width back THROUGH THE ABI and exits 0 only if it is 4 — the half a host test cannot reach. Measured before commit: `armv7a pte width 4, entry fits 32 bits`, exit 0. A second step asks whether the one-line usage REACHES the backend, for both armv7a triples. That is the check 0.8.0 shipped without: it carried a Cortex-M backend `backend-auto` never bound, and everything was green because nothing asked. The job's two fixtures are base64 rather than heredocs. A heredoc inside a YAML block scalar needs its terminator at column zero, which ends the block, and indenting it stops bash recognising it — both spellings fail, and the second fails at run time. Co-authored-by: speak-agent <248744407+speak-agent@users.noreply.github.com>
1 parent 2d9890d commit e3711c6

21 files changed

Lines changed: 761 additions & 9 deletions

File tree

.github/workflows/ci.yml

Lines changed: 102 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -550,6 +550,108 @@ jobs:
550550
# encoders are pure functions in per-architecture namespaces, so a host build
551551
# holds BOTH and compares them — which is what several of the assertions are,
552552
# and what no single-target build could express.
553+
armv7a:
554+
name: the entry is four bytes (armv7a)
555+
runs-on: ubuntu-24.04
556+
timeout-minutes: 40
557+
env:
558+
# 2026.9.4.2 is the release carrying the ARMv7-A target rows. The other
559+
# jobs pin 2026.9.4.1 because that is the oldest engine THEY need; pinning
560+
# the newest everywhere would state a dependency that does not exist.
561+
MCPP_VERSION: 2026.9.4.2
562+
XLINGS_VERSION: v2026.8.17.2
563+
XLINGS_NON_INTERACTIVE: '1'
564+
# THE TWO FIXTURES ARE base64 AND NOT HEREDOCS, DELIBERATELY. A heredoc
565+
# inside a YAML block scalar needs its terminator at column zero, which
566+
# ends the block; indenting the terminator stops bash recognising it. Both
567+
# spellings fail, and the second fails at run time rather than at parse
568+
# time. An opaque payload has neither problem.
569+
LINK_LD_B64: RU5UUlkoX3N0YXJ0KQpNRU1PUlkgeyBSQU0gKHJ3eCkgOiBPUklHSU4gPSAweDQwMDAwMDAwLCBMRU5HVEggPSAxNk0gfQpTRUNUSU9OUyB7CiAgLnRleHQgOiB7IEtFRVAoKigudGV4dC5zdGFydCkpICooLnRleHQqKSAqKC5yb2RhdGEqKSB9ID4gUkFNCiAgLmRhdGEgOiB7ICooLmRhdGEqKSB9ID4gUkFNCiAgLmJzcyAgOiB7ICooLmJzcyopICooQ09NTU9OKSB9ID4gUkFNCiAgLiA9IEFMSUdOKDE2KTsKICBfX3N0YWNrX3RvcCA9IE9SSUdJTihSQU0pICsgTEVOR1RIKFJBTSk7Cn0K
570+
MAIN_CPP_B64: aW1wb3J0IG1jcHBsaWJzLm9wZW5hcmNoOwpleHRlcm4gIkMiIHVuc2lnbmVkIGFyY2hfcHRlX2VudHJ5X2J5dGVzKHZvaWQpOwoKbmFtZXNwYWNlIHsKaW5saW5lIHZvaWQgc2VtaWhvc3QoaW50IG9wLCBjb25zdCB2b2lkKiBhcmcpIHsKICAgIHJlZ2lzdGVyIGludCByMCBhc20oInIwIikgPSBvcDsKICAgIHJlZ2lzdGVyIGNvbnN0IHZvaWQqIHIxIGFzbSgicjEiKSA9IGFyZzsKICAgIGFzbSB2b2xhdGlsZSgic3ZjIDB4MTIzNDU2IiA6OiAiciIocjApLCAiciIocjEpIDogIm1lbW9yeSIpOwp9CnZvaWQgcHJpbnQoY29uc3QgY2hhciogcykgeyBzZW1paG9zdCgweDA0LCBzKTsgfQpbW25vcmV0dXJuXV0gdm9pZCBxdWl0KGludCBjb2RlKSB7CiAgICBzdHJ1Y3QgeyB1bnNpZ25lZCByZWFzb24sIGNvZGU7IH0gYnsweDIwMDI2dSwgKHVuc2lnbmVkKWNvZGV9OwogICAgc2VtaWhvc3QoMHgyMCwgJmIpOwogICAgZm9yICg7Oykge30KfQp9CgpleHRlcm4gIkMiIHZvaWQga21haW4oKSB7CiAgICBjb25zdCB1bnNpZ25lZCB3ID0gYXJjaF9wdGVfZW50cnlfYnl0ZXMoKTsKICAgIGNvbnN0IGF1dG8gZSA9IGFyY2g6Om1ha2VfbGVhZigweDQwMDAxMDAwLCBhcmNoOjpwZXJtOjpyZWFkX3dyaXRlLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGFyY2g6Om1lbW9yeV90eXBlOjpub3JtYWwsIGZhbHNlKTsKICAgIGNvbnN0IGJvb2wgb2sgPSAodyA9PSA0KSAmJiBhcmNoOjppc192YWxpZChlKQogICAgICAgICAgICAgICAgICAmJiBhcmNoOjpwaHlzX29mKGUpID09IDB4NDAwMDEwMDAgJiYgKChlLmJpdHMgPj4gMzIpID09IDApOwogICAgcHJpbnQob2sgPyAiYXJtdjdhIHB0ZSB3aWR0aCA0LCBlbnRyeSBmaXRzIDMyIGJpdHNcbiIgOiAiYXJtdjdhIHB0ZSBXUk9OR1xuIik7CiAgICBxdWl0KG9rID8gMCA6IDEpOwp9Cgphc20oIi5zZWN0aW9uIC50ZXh0LnN0YXJ0LFwiYXhcIlxuIgogICAgIi5nbG9ibCBfc3RhcnRcbiIKICAgICJfc3RhcnQ6XG4iCiAgICAiICBsZHIgc3AsID1fX3N0YWNrX3RvcFxuIgogICAgIiAgYiBrbWFpblxuIik7Cg==
571+
steps:
572+
- uses: actions/checkout@v4
573+
574+
- name: Install xlings
575+
run: |
576+
curl -fsSL https://raw.githubusercontent.com/openxlings/xlings/main/tools/other/quick_install.sh \
577+
| bash -s "$XLINGS_VERSION"
578+
echo "$HOME/.xlings/subos/current/bin" >> "$GITHUB_PATH"
579+
580+
- name: Install mcpp
581+
run: |
582+
for attempt in 1 2 3 4 5 6; do
583+
xlings update >/dev/null 2>&1 || true
584+
if xlings install "mcpp@$MCPP_VERSION" -y; then break; fi
585+
echo "index not yet carrying mcpp@$MCPP_VERSION (attempt $attempt); waiting"
586+
sleep 60
587+
done
588+
mcpp --version
589+
590+
- name: Install the emulator
591+
run: |
592+
xlings install xim:qemu-arm -y
593+
XLINGS_HOME="$HOME/.mcpp/registry" xlings install xim:qemu-arm -y
594+
595+
# THE QUESTION THIS BACKEND EXISTS TO ANSWER, ASKED ON THE MACHINE.
596+
#
597+
# openarch was designed on three 64-bit machines with 64-bit page-table
598+
# entries, and `pte_encode.h` recorded that as a fact about every machine
599+
# here. ARMv7-A's short-descriptor entry is 32 bits. `tests/pte_encoding`
600+
# asserts the bit patterns against the architecture manual; this asserts
601+
# that a program running on the machine gets the same answer through the
602+
# ABI, which is the half a host test cannot reach.
603+
#
604+
# Cortex-M could not settle it: it is a 32-bit machine with no page table,
605+
# so its pte group exists and refuses.
606+
- name: A program on the machine reads back a four-byte entry
607+
run: |
608+
set -euo pipefail
609+
QEMU=$(ls "$HOME"/.xlings/data/xpkgs/xim-x-qemu-arm/*/bin/qemu-system-arm | sort -V | tail -1)
610+
D=$(mktemp -d); mkdir -p "$D/src"
611+
printf '[package]\nname="soc"\nversion="0.1.0"\n[build]\ntarget="armv7a-none-eabi"\nsources=["src/main.cpp"]\n[target.armv7a-none-eabi]\nsysroot=""\n[dependencies]\nopenarch = { path = "%s" }\n[targets.soc]\nkind="bin"\nmain="src/main.cpp"\n' \
612+
"$PWD" > "$D/mcpp.toml"
613+
printf 'import mcpp;\nint main() { mcpp::link_script("link.ld"); return 0; }\n' > "$D/build.mcpp"
614+
echo "$LINK_LD_B64" | base64 -d > "$D/link.ld"
615+
echo "$MAIN_CPP_B64" | base64 -d > "$D/src/main.cpp"
616+
( cd "$D" && mcpp build ) || { echo "::error::the armv7a probe did not build"; exit 1; }
617+
elf=$(find "$D/target" -type f -name soc | head -1)
618+
[ -n "$elf" ] || { echo "::error::no artefact"; exit 1; }
619+
# NOT `qemu | head`: `$?` after a pipeline is the last command's, so
620+
# the status assertion below would be vacuous.
621+
set +e
622+
timeout 30 "$QEMU" -M virt -cpu cortex-a15 -nographic -semihosting \
623+
-no-reboot -kernel "$elf" > "$D/qemu.log" 2>&1
624+
rc=$?
625+
set -e
626+
cat "$D/qemu.log"
627+
grep -q 'armv7a pte width 4' "$D/qemu.log" \
628+
|| { echo "::error::the machine did not report a four-byte entry"; exit 1; }
629+
[ "$rc" = "0" ] || { echo "::error::booted but exited $rc"; exit 1; }
630+
631+
# THE LESSON 0.8.0 SHIPPED: A BACKEND IN THE PACKAGE IS NOT A BACKEND THE
632+
# ONE-LINE USAGE CAN REACH. That release carried a Cortex-M backend which
633+
# `backend-auto` never bound, and everything was green because nothing
634+
# asked this question.
635+
- name: The one-line usage reaches the armv7a backend
636+
run: |
637+
set -euo pipefail
638+
n=0
639+
for t in armv7a-none-eabi armv7a-none-eabihf; do
640+
D=$(mktemp -d); mkdir -p "$D/src"
641+
printf '[package]\nname="oap"\nversion="0.1.0"\n[build]\ntarget="%s"\nsources=["src/main.cpp"]\n[target.%s]\nsysroot=""\n[dependencies]\nopenarch = { path = "%s" }\n' \
642+
"$t" "$t" "$PWD" > "$D/mcpp.toml"
643+
printf 'int main(){return 0;}\n' > "$D/src/main.cpp"
644+
( cd "$D" && mcpp build --verbose > v.log 2>&1 ) || true
645+
c=$(grep -c 'openarch-armv7a' "$D/v.log" || true)
646+
[ "$c" -gt 0 ] || {
647+
echo "::error::$t resolved no armv7a backend through backend-auto"
648+
tail -20 "$D/v.log"; exit 1; }
649+
echo " ok $t binds the backend ($c compile lines)"
650+
n=$((n+1)); rm -rf "$D"
651+
done
652+
# A count, because a loop that ran zero times also reaches this line.
653+
[ "$n" = "2" ] || { echo "::error::checked $n spellings, expected 2"; exit 1; }
654+
553655
host-encoders:
554656
name: the page-table encoders agree (${{ matrix.os }})
555657
runs-on: ${{ matrix.os }}

abi/include/openarch/abi.h

Lines changed: 25 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -112,10 +112,33 @@ int arch_pte_valid(arch_u64 bits);
112112
arch_u64 arch_pte_phys(arch_u64 bits);
113113

114114
/* Programs whatever the machine needs before a memory type is meaningful.
115-
* Empty on riscv64, where the type is in the entry; writes `MAIR_EL1` on
116-
* aarch64, where the entry holds only an index into it. */
115+
* Empty on riscv64 and armv7a, where the type is in the entry; writes
116+
* `MAIR_EL1` on aarch64, where the entry holds only an index into it. */
117117
void arch_pte_install_memory_attributes(void);
118118

119+
/* HOW WIDE THE MACHINE'S PAGE-TABLE ENTRY ACTUALLY IS, IN BYTES.
120+
*
121+
* The three functions above carry an entry in an `arch_u64` and say nothing
122+
* about how it is STORED. That was invisible while every backend was a 64-bit
123+
* machine with 64-bit entries, and `pte_encode.h` said so in as many words: "a
124+
* page-table entry is 64 bits on every machine here". It is not. ARMv7-A's
125+
* short-descriptor entry is 32 bits, and a caller who sizes a table from
126+
* `sizeof(arch::pte)` builds one twice as large as the hardware walks — every
127+
* second word read as an entry, with no diagnostic anywhere.
128+
*
129+
* The value fits in `arch_u64` on both, so the carrier did not have to change.
130+
* What was missing is a way to ASK, which is what this adds. A caller writes
131+
*
132+
* entries * arch_pte_entry_bytes()
133+
*
134+
* and gets the right size on both classes of machine.
135+
*
136+
* Returns 8 on riscv64, aarch64 and x86_64; 4 on armv7a. A backend with no
137+
* address space (Cortex-M) returns 0, which is the same answer its `provides`
138+
* already gives by withholding `openarch:address-space` — a caller that got
139+
* this far has a bug in its own layering. */
140+
arch_u32 arch_pte_entry_bytes(void);
141+
119142
/* ── openarch.trap ─────────────────────────────────────────────────────────
120143
*
121144
* ⚠️ THE LAYOUT IS FROZEN AND IS SHARED WITH ASSEMBLY. Each backend's entry

abi/include/openarch/pte_encode.h

Lines changed: 75 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,9 @@
3636
*
3737
* ⚠️ `arch_u64` THROUGHOUT, AND CROSS-PLATFORM CI IS WHAT MADE THAT NECESSARY.
3838
*
39-
* A page-table entry is 64 bits on every machine here. These constants were
39+
* A page-table entry is 64 bits on every machine here EXCEPT armv7a, whose
40+
* short-descriptor entry is 32 bits and fits in the low half; see that
41+
* namespace and `arch_pte_entry_bytes()`. These constants were
4042
* once `unsigned long`, which is 64 bits on the systems this was written on and
4143
* 32 on Windows — so `1UL << 53` was a shift wider than the type: undefined,
4244
* and in practice silently zero rather than an error. An encoder built there
@@ -308,6 +310,78 @@ inline arch_u64 entry_phys(arch_u64 bits) noexcept {
308310

309311
} // namespace x86_64
310312

313+
// ─── armv7a ────────────────────────────────────────────────────────────────
314+
//
315+
// THE FIRST MACHINE HERE WHOSE ENTRY IS NOT 64 BITS.
316+
//
317+
// ARMv7-A's short-descriptor format uses a 32-bit second-level entry. Every
318+
// constant below therefore fits in the low half of `arch_u64`, and the carrier
319+
// does not have to change: what changes is that a caller can no longer assume
320+
// the STORAGE is eight bytes. `arch_pte_entry_bytes()` is what answers that,
321+
// and this namespace is the reason it exists.
322+
//
323+
// The type is entirely in the entry, as on riscv64 and unlike aarch64: there
324+
// is no MAIR to program, so `arch_pte_install_memory_attributes` has nothing
325+
// to do on this machine.
326+
namespace armv7a {
327+
328+
// A small-page (4 KiB) second-level descriptor. Bit 1 selects the small-page
329+
// encoding and bit 0 is XN, so the two together are `0b10` for executable and
330+
// `0b11` for not — a pair of adjacent bits meaning unrelated things, which is
331+
// the kind of detail this layer exists to keep out of a kernel.
332+
inline constexpr arch_u64 kSmallPage = 1ULL << 1;
333+
inline constexpr arch_u64 kXn = 1ULL << 0;
334+
335+
inline constexpr arch_u64 kB = 1ULL << 2; // bufferable
336+
inline constexpr arch_u64 kC = 1ULL << 3; // cacheable
337+
inline constexpr arch_u64 kAp0 = 1ULL << 4;
338+
inline constexpr arch_u64 kAp1 = 1ULL << 5;
339+
inline constexpr arch_u64 kTex0 = 1ULL << 6;
340+
inline constexpr arch_u64 kAp2 = 1ULL << 9;
341+
inline constexpr arch_u64 kS = 1ULL << 10; // shareable
342+
343+
// Bits [31:12]. Written as a 32-bit mask because the descriptor is 32 bits;
344+
// `arch_u64` is the carrier, not the width.
345+
inline constexpr arch_u64 kAddrMask = 0xFFFFF000ULL;
346+
347+
// AP[2:1] with AP[0] set is the access-permission encoding used throughout.
348+
// `0b01` (AP2=0, AP1=0, AP0=1) is read/write at PL1 only; `0b011` adds
349+
// unprivileged access; setting AP2 makes the mapping read-only.
350+
inline arch_u64 encode_leaf(arch_u64 phys, int perm, int mt,
351+
bool user) noexcept {
352+
arch_u64 e = kSmallPage | kAp0;
353+
354+
const bool writable = (perm == 1 || perm == 3);
355+
const bool executable = (perm == 2 || perm == 3);
356+
357+
if (user) e |= kAp1; // reachable from PL0
358+
if (!writable) e |= kAp2; // read-only
359+
if (!executable) e |= kXn;
360+
361+
// Normal write-back write-allocate is TEX=0b001 with C and B set; Device
362+
// (shareable) is TEX=0b000, C=0, B=1. Anything unrecognised is treated as
363+
// Device, which is the restrictive choice: a mapping wrongly marked
364+
// cacheable is a silent corruption, one wrongly marked Device is slow.
365+
if (mt == 0) e |= kTex0 | kC | kB | kS;
366+
else e |= kB;
367+
368+
return e | (phys & kAddrMask);
369+
}
370+
371+
// A short descriptor is invalid when bits [1:0] are 0b00. Bit 1 alone
372+
// distinguishes a small page from the large-page and fault encodings, which is
373+
// why the test is on that bit and not on bit 0 — bit 0 is XN here.
374+
inline bool entry_valid(arch_u64 bits) noexcept {
375+
return (bits & kSmallPage) != 0;
376+
}
377+
378+
inline arch_u64 entry_phys(arch_u64 bits) noexcept {
379+
if (!entry_valid(bits)) return 0;
380+
return bits & kAddrMask;
381+
}
382+
383+
} // namespace armv7a
384+
311385
} // namespace arch
312386

313387
#endif

abi/mcpp.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@
2424
[package]
2525
namespace = "mcpplibs"
2626
name = "openarch-abi"
27-
version = "0.7.0"
27+
version = "0.9.0"
2828
description = "openarch's C ABI: the contract between the interface and an instruction set's backend"
2929
license = "Apache-2.0"
3030
authors = ["mcpplibs"]

backends/aarch64/mcpp.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@
1414
[package]
1515
namespace = "mcpplibs"
1616
name = "openarch-aarch64"
17-
version = "0.7.0"
17+
version = "0.9.0"
1818
description = "openarch's aarch64 backend: the instructions behind the ABI"
1919
license = "Apache-2.0"
2020
authors = ["mcpplibs"]

backends/aarch64/src/pte_impl.cpp

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,3 +33,5 @@ extern "C" void arch_pte_install_memory_attributes(void) {
3333
asm volatile("msr mair_el1, %0\n\tisb" :: "r"(kMair) : "memory");
3434
}
3535

36+
// AArch64 long-descriptor entries are eight bytes.
37+
extern "C" arch_u32 arch_pte_entry_bytes(void) { return 8; }

backends/armv7a/mcpp.toml

Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
# openarch's armv7a backend: instructions, and nothing else.
2+
#
3+
# THIS PACKAGE EXPORTS NO MODULE. It defines the C entry points declared in
4+
# `abi/include/openarch/abi.h` and is reached only through them, exactly as the
5+
# other four backends are.
6+
#
7+
# THE MACHINE THAT ANSWERED THE WIDTH QUESTION.
8+
#
9+
# openarch was designed on three application-class 64-bit machines, and its
10+
# page-table interface carried an entry in an `arch_u64` without ever saying how
11+
# one is stored — `pte_encode.h` recorded the assumption as a statement of fact:
12+
# "a page-table entry is 64 bits on every machine here". ARMv7-A is the first
13+
# 32-bit machine here WITH an address space, and its short-descriptor entry is
14+
# 32 bits. The value fits; the storage does not. `arch_pte_entry_bytes()` was
15+
# added for this backend and is implemented by all five.
16+
#
17+
# Cortex-M did not settle it and could not: M-profile has an MPU and no page
18+
# table, so its pte group exists and refuses. A 32-bit machine WITHOUT paging
19+
# leaves the question exactly where it was.
20+
[package]
21+
namespace = "mcpplibs"
22+
name = "openarch-armv7a"
23+
version = "0.9.0"
24+
description = "openarch's armv7a backend: the instructions behind the ABI, on the first machine here whose page-table entry is 32 bits"
25+
license = "Apache-2.0"
26+
authors = ["mcpplibs"]
27+
repo = "https://github.com/mcpplibs/openarch"
28+
29+
# `openarch:preemption` IS WITHHELD, AND THAT IS A STATEMENT ABOUT THIS MACHINE
30+
# RATHER THAN AN OMISSION.
31+
#
32+
# `arch_trap_switch` asks a trap to resume a different context. On riscv64 and
33+
# aarch64 the resumption address is a register the dispatcher saves and restores
34+
# around the switch; on Cortex-M a dedicated exception performs it. On ARMv7-A
35+
# it is written to the SVC stack by `srs`, so switching stacks mid-trap changes
36+
# which return frame is popped — well-defined only if the resumed context was
37+
# suspended through the same path. That is a real design, and it is not one
38+
# this backend has measured. A consumer that needs preemption is therefore
39+
# refused by name at resolution rather than linking against something that
40+
# compiles and misbehaves.
41+
#
42+
# This is the mechanism the Cortex-M backend already uses to withhold
43+
# `openarch:address-space`, applied to a different group.
44+
provides = ["openarch-backend",
45+
"openarch:address-space",
46+
"openarch:percpu-register"]
47+
48+
[build]
49+
sources = ["src/**"]
50+
51+
[targets.openarch-armv7a]
52+
kind = "lib"
53+
54+
[dependencies]
55+
openarch-abi = { path = "../../abi" }

backends/armv7a/src/context.S

Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
/* openarch.context — the armv7a backend's switch and trampoline.
2+
*
3+
* AAPCS names r4-r11 as callee-saved, plus SP (r13) and LR (r14). Ten words,
4+
* forty bytes — the smallest saved context of any backend here, and the reason
5+
* is the register file rather than any choice this file makes.
6+
*
7+
* d8-d15 ARE ALSO CALLEE-SAVED AND ARE DELIBERATELY NOT SAVED, which is the
8+
* interface's contract and not this backend's shortcut: riscv64 saves none of
9+
* fs0-fs11 and aarch64 none of d8-d15 for the same reason. A saved context in
10+
* openarch is INTEGER state; a kernel that lets its tasks use floating point
11+
* saves that state itself or compiles with floating point disabled.
12+
*
13+
* r9 IS SAVED AND RESTORED LIKE ANY OTHER. Some ARM ABIs reserve it as a
14+
* static base or a thread register, and a kernel built with those conventions
15+
* would want it left alone — but this backend cannot know which convention its
16+
* consumer used, and saving a register that did not need saving is harmless
17+
* where failing to save one is not.
18+
*/
19+
.syntax unified
20+
.arm
21+
22+
.section .text.arch_context_switch,"ax",%progbits
23+
.globl arch_context_switch
24+
.type arch_context_switch, %function
25+
.balign 4
26+
arch_context_switch: /* r0 = &from, r1 = &to */
27+
stm r0, {r4-r11} /* [0 .. 31] callee-saved */
28+
str sp, [r0, #32]
29+
str lr, [r0, #36]
30+
31+
ldm r1, {r4-r11}
32+
ldr sp, [r1, #32]
33+
ldr lr, [r1, #36]
34+
bx lr
35+
.size arch_context_switch, . - arch_context_switch
36+
37+
/* The initial return address of a context built by arch_context_init.
38+
*
39+
* Reached by `bx lr`, not by a call, so it has no caller and must not return.
40+
* r4 and r5 carry the entry point and its argument: they are callee-saved,
41+
* therefore restored by the switch above, which is exactly the property that
42+
* lets an argument survive a transfer restoring no argument register.
43+
*/
44+
.section .text.arch_context_entry,"ax",%progbits
45+
.globl arch_context_entry
46+
.type arch_context_entry, %function
47+
.balign 4
48+
arch_context_entry:
49+
mov r0, r5 /* arg */
50+
blx r4 /* entry */
51+
/* `entry` returning is a contract violation with nowhere to go. Spinning
52+
* is the honest response: it neither corrupts state nor pretends to
53+
* recover. */
54+
1: b 1b
55+
.size arch_context_entry, . - arch_context_entry
56+
57+
.section .note.GNU-stack,"",%progbits

0 commit comments

Comments
 (0)