Skip to content

Commit 76bd2bc

Browse files
Japabuclaude
andcommitted
Merge origin/main: the ARM64 port, stages 0 to 3 (#524)
Conflicts, each resolved onto main's layout with this branch's change kept: - rust: pinned at 64a2050c484, this branch's 23783dc3524 merged with main's c34ecdf0ab6 in the fork (`wt-toyos-logtrack`), and merged into `toyos-inbox` (645d1c70732). `git merge-base --is-ancestor` gives 0 for both pins against it and for it against `origin/toyos-inbox`. Its `compiler/` is c34ecdf0ab6's byte for byte; the inbox head 26f662d303a was not taken because it also carries x86-64's switch to rust-lld, another branch's compiler change. - panic.rs: main moved `halt_all_cpus` here from `apic.rs`; this branch's version stops the other CPUs first and has no wait for `/log`, so `wait_for_log_file`, `LOG_FILE_DRAIN` and `LOG_DRAIN_EXPIRED` go from panic.rs as they went from apic.rs, and `tests/common/power.rs`'s check for that string, which the kernel no longer prints, goes with them. - serial.rs: this branch's two locks and burst writers over main's `arch::console_uart`, `serial_lock::BackendLock` and `IrqGuard`. The burst is `console_uart::TX_BURST`: 16 on the 16550, whose THRE means an empty FIFO, and 1 on the PL011, whose TXFF clear promises one byte. - clock.rs: the clock page is published from main's `set_counter`. - paging.rs: `translate_writable` on main's x86-64 `AddressSpace`, and an uninhabited stub on AArch64's. - virtio.rs: `publish` and `Doorbell` on main's barriers: the doorbell is an `Mmio` write, which orders the idx before it. - loader/tls.rs: main lays out variant I (TP+0 the DTV pointer, TP+8 zeroed); `toyos_abi::TCB_TID` is TP+8 there, so the tid write does not touch the DTV pointer. - toyos-window goes to 0.20.0, past main's 0.19.0; toyos-abi 0.16.0 and toyos 0.18.0 are already past main's. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_014iqcj4jDKpaiDX8B7CMvmK
2 parents a171958 + 5e446e5 commit 76bd2bc

278 files changed

Lines changed: 9117 additions & 2673 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

‎Cargo.lock‎

Lines changed: 4 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

‎Cargo.toml‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,7 @@ members = [
3737
"toyos-inspect",
3838
"toyos-keymap",
3939
"toyos-ld",
40+
"toyos-libc-copies",
4041
"toyos-logstream",
4142
"toyos-manifest",
4243
"toyos-mdns",

‎NOTICE‎

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -267,6 +267,32 @@ instead — which would put it inside the "comes with QEMU" allowance and delete
267267
6,291,456 bytes from the repository.
268268

269269

270+
271+
aavmf/*.fd — EDK II firmware for QEMU `virt`, BSD-2-Clause-Patent AND Apache-2.0
272+
------------------------------------------------------------
273+
274+
AAVMF_CODE.fd 67,108,864 bytes
275+
sha256 47765fe344818cbc464b1c14ae658fb4b854f5c2ceffa982411731eb4865594d
276+
AAVMF_VARS.fd 67,108,864 bytes
277+
sha256 b3b855c5a80310168051164986855692d1bdb06e67619856177965cd87c6774f
278+
279+
Copyright (c) 2019, TianoCore and contributors
280+
Licence text: licenses/BSD-2-Clause-Patent-EDK2.txt
281+
Copyright 1995-2023 The OpenSSL Project Authors
282+
Licence text: licenses/Apache-2.0-OpenSSL.txt
283+
SPDX-License-Identifier: BSD-2-Clause-Patent AND Apache-2.0
284+
285+
QEMU's own prebuilt ArmVirtQemu firmware, unmodified: `edk2-aarch64-code.fd`
286+
and `edk2-arm-vars.fd` as QEMU 11.1.0 installs them under `share/qemu/`,
287+
whose version string reads `edk2-stable202408-prebuilt.qemu.org` (a
288+
`DEBUG_GCC5` build of 2024-09-12). The variable store is the template: every
289+
boot gives it a writable snapshot QEMU discards, because this `DEBUG` build
290+
asserts on a read-only store. QEMU builds it with `NETWORK_TLS_ENABLE`
291+
(`roms/edk2-build.config`, `[opts.common]`), and edk2-stable202408's
292+
ArmVirtQemu links edk2's bundled OpenSSL either way (`OpensslLib` with TLS,
293+
`OpensslLibCrypto` without): OpenSSL 3.0.9, whose `LICENSE.txt` is the
294+
Apache-2.0 text above and which carries no `NOTICE`.
295+
270296
tests/fixtures/gbae-v0.2.0-* — gbae, MIT
271297
-----------------------------------------
272298

‎aavmf/AAVMF_CODE.fd‎

64 MB
Binary file not shown.

‎aavmf/AAVMF_VARS.fd‎

64 MB
Binary file not shown.

‎bootloader/.cargo/config.toml‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,4 +10,4 @@ rustflags = [
1010
]
1111

1212
[target.x86_64-unknown-uefi]
13-
linker = "toyos-ld"
13+
linker = "toyos-ld"

‎bootloader/rust-toolchain.toml‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,2 @@
11
[toolchain]
2-
targets = ["x86_64-unknown-uefi"]
2+
targets = ["x86_64-unknown-uefi", "aarch64-unknown-uefi"]

‎bootloader/src/arch/aarch64.rs‎

Lines changed: 138 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,138 @@
1+
//! AArch64: the loader's every instruction Rust has no portable spelling for.
2+
3+
use toyos_abi::boot::KernelArgs;
4+
use toyos_bootmap::Typing;
5+
6+
/// The machine the kernel image must be built for: the loader's own.
7+
pub const ELF_MACHINE: toyos_elf::Machine = toyos_elf::Machine::Aarch64;
8+
9+
/// How the boot map's descriptors are encoded.
10+
pub use toyos_bootmap::aarch64 as encoding;
11+
12+
/// How the boot map types memory: by firmware's map, since AArch64 has no
13+
/// range registers to do it and every descriptor names its own type.
14+
pub fn typing(write_back: &[(u64, u64)]) -> Typing<'_> {
15+
Typing::ByMap(write_back)
16+
}
17+
18+
/// The generic timer's virtual count, `CNTVCT_EL0`.
19+
pub fn counter() -> u64 {
20+
let count: u64;
21+
// SAFETY: reads a counter EL1 and EL2 may always read; the `ISB` keeps the
22+
// read in program order.
23+
unsafe { core::arch::asm!("isb", "mrs {}, cntvct_el0", out(reg) count, options(nomem, nostack, preserves_flags)) };
24+
count
25+
}
26+
27+
/// What the loader's report says beside the counter: its rate. Where it counts
28+
/// from is firmware's to say and no register here does.
29+
pub fn counter_origin() -> alloc::string::String {
30+
let hz: u64;
31+
// SAFETY: reads a register EL1 and EL2 may always read.
32+
unsafe { core::arch::asm!("mrs {}, cntfrq_el0", out(reg) hz, options(nomem, nostack, preserves_flags)) };
33+
alloc::format!("CNTFRQ_EL0 {hz} Hz; the counter's origin is firmware's")
34+
}
35+
36+
/// What the loader says about the CPU as firmware handed it over, or why the
37+
/// kernel cannot run on it: entered at EL2 on a CPU without FEAT_E2H0,
38+
/// `HCR_EL2.E2H` is RES1, so the kernel's entry cannot clear it and every
39+
/// `_el1` register its drop programs would be EL2's own. Refused here, where
40+
/// the console still prints; the entry's read-back of `HCR_EL2` stays as the
41+
/// last line of defence.
42+
pub fn cpu_as_entered() -> Result<Option<alloc::string::String>, alloc::string::String> {
43+
let current: u64;
44+
// SAFETY: reads `CurrentEL`, which EL1 and above may read.
45+
unsafe { core::arch::asm!("mrs {}, currentel", out(reg) current, options(nomem, nostack, preserves_flags)) };
46+
let el = (current >> 2) & 0b11;
47+
if el != 2 {
48+
return Ok(Some(alloc::format!("CPU: entered at EL{el}")));
49+
}
50+
let (hcr, mmfr4): (u64, u64);
51+
// SAFETY: at EL2 both are readable. `ID_AA64MMFR4_EL1` by its encoding,
52+
// `S3_0_C0_C7_4`, which sits in the ID space an older CPU reads as zero.
53+
unsafe {
54+
core::arch::asm!("mrs {}, hcr_el2", out(reg) hcr, options(nomem, nostack, preserves_flags));
55+
core::arch::asm!("mrs {}, S3_0_C0_C7_4", out(reg) mmfr4, options(nomem, nostack, preserves_flags));
56+
}
57+
let e2h = (hcr >> 34) & 1;
58+
let e2h0 = (mmfr4 >> 24) & 0xF;
59+
let state = alloc::format!("CPU: entered at EL2, HCR_EL2.E2H {e2h}, ID_AA64MMFR4_EL1.E2H0 {e2h0:#x}");
60+
if e2h0 != 0 {
61+
return Err(alloc::format!(
62+
"{state}: REFUSED, HCR_EL2.E2H is RES1 on a CPU without FEAT_E2H0, and the kernel's \
63+
drop to EL1 needs it clear"
64+
));
65+
}
66+
Ok(Some(alloc::format!("{state}: the kernel's entry writes E2H clear")))
67+
}
68+
69+
/// The smallest data cache line on this machine, from `CTR_EL0.DminLine`.
70+
fn line() -> u64 {
71+
let ctr: u64;
72+
// SAFETY: reads `CTR_EL0`, which every level may read.
73+
unsafe { core::arch::asm!("mrs {}, ctr_el0", out(reg) ctr, options(nomem, nostack, preserves_flags)) };
74+
4 << ((ctr >> 16) & 0xF)
75+
}
76+
77+
/// Every line of `[at, at + len)` cleaned to the point of coherency and
78+
/// invalidated, then `DSB SY` for their completion.
79+
pub fn write_back(at: u64, len: usize) {
80+
let step = line();
81+
let mut addr = at & !(step - 1);
82+
while addr < at + len as u64 {
83+
// SAFETY: `DC CIVAC` cleans and invalidates the line holding an
84+
// address the caller allocated; it changes no memory's contents.
85+
unsafe { core::arch::asm!("dc civac, {}", in(reg) addr, options(nostack, preserves_flags)) };
86+
addr += step;
87+
}
88+
// SAFETY: a barrier; waits for the maintenance above to complete.
89+
unsafe { core::arch::asm!("dsb sy", options(nostack, preserves_flags)) };
90+
}
91+
92+
/// AArch64 has no I/O port space: every caller checks [`pio::EXISTS`] first.
93+
pub mod pio {
94+
pub const EXISTS: bool = false;
95+
96+
/// # Safety
97+
/// Never called: [`EXISTS`] is false.
98+
pub unsafe fn outw(_port: u16, _value: u16) {
99+
unreachable!("AArch64 has no I/O port space")
100+
}
101+
102+
pub fn inw(_port: u16) -> u16 {
103+
unreachable!("AArch64 has no I/O port space")
104+
}
105+
}
106+
107+
/// Hand the CPU to the kernel as firmware left it — its exception level, its
108+
/// identity tables — at the image's physical entry, with `x0 = args`. The
109+
/// kernel's entry switches to the boot map (`args.boot_pml4_addr`) itself (`kernel/src/arch/aarch64/boot.rs`),
110+
/// because at EL2 only the kernel's own drop to EL1 can install it.
111+
///
112+
/// The image is cleaned to the point of coherency first: that entry fetches
113+
/// instructions with the MMU off for a few of them, straight from memory.
114+
///
115+
/// # Safety
116+
/// `args.boot_pml4_addr` is the boot map, `image` is the relocated kernel image, `entry_offset`
117+
/// is its entry point's offset in it, and `args` stays where it is until the
118+
/// kernel copies it.
119+
pub unsafe fn enter_kernel(image: (u64, u64), entry_offset: u64, args: &KernelArgs) -> ! {
120+
write_back(image.0, image.1 as usize);
121+
let entry = image.0 + entry_offset;
122+
// SAFETY: interrupts masked for good — the kernel's vectors are not
123+
// installed yet — then every instruction cache line invalidated against
124+
// the image just cleaned, and a branch to its entry with `x0 = args`, the
125+
// boot protocol `toyos-abi::boot` and the kernel's `_start` define.
126+
unsafe {
127+
core::arch::asm!(
128+
"msr daifset, #0xf",
129+
"ic iallu",
130+
"dsb ish",
131+
"isb",
132+
"br {entry}",
133+
entry = in(reg) entry,
134+
in("x0") args as *const KernelArgs,
135+
options(noreturn),
136+
);
137+
}
138+
}

‎bootloader/src/arch/mod.rs‎

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
//! The machine the loader runs on, and the only part of it that knows which one.
2+
3+
#[cfg_attr(target_arch = "x86_64", path = "x86_64.rs")]
4+
#[cfg_attr(target_arch = "aarch64", path = "aarch64.rs")]
5+
mod imp;
6+
7+
pub use imp::*;

‎bootloader/src/arch/x86_64.rs‎

Lines changed: 119 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,119 @@
1+
//! x86-64: the loader's every instruction Rust has no portable spelling for.
2+
3+
use toyos_abi::boot::KernelArgs;
4+
5+
/// The machine the kernel image must be built for: the loader's own.
6+
pub const ELF_MACHINE: toyos_elf::Machine = toyos_elf::Machine::X86_64;
7+
8+
/// How the boot map's entries are encoded.
9+
pub use toyos_bootmap::x86_64 as encoding;
10+
11+
/// How the boot map types memory: by the MTRRs firmware programmed, beneath
12+
/// entries that select plain memory.
13+
pub fn typing(_write_back: &[(u64, u64)]) -> toyos_bootmap::Typing<'_> {
14+
toyos_bootmap::Typing::Firmware
15+
}
16+
17+
/// The time-stamp counter, which counts from reset.
18+
pub fn counter() -> u64 {
19+
// SAFETY: RDTSC reads a counter and nothing else; every x86-64 has it.
20+
unsafe { core::arch::x86_64::_rdtsc() }
21+
}
22+
23+
/// What the loader's report says beside the counter: `IA32_TSC_ADJUST`,
24+
/// where CPUID says the CPU has it — every write to the TSC since reset is
25+
/// added to it (Intel SDM Vol. 3B, "Time-Stamp Counter Adjustment"), so zero
26+
/// is a counter firmware never wrote and the TSC is time since power-on.
27+
pub fn counter_origin() -> alloc::string::String {
28+
let max = core::arch::x86_64::__cpuid(0).eax;
29+
// Leaf 7 exists when the maximum leaf reaches it.
30+
if max < 7 || core::arch::x86_64::__cpuid_count(7, 0).ebx & (1 << 1) == 0 {
31+
return alloc::string::String::from("IA32_TSC_ADJUST not on this CPU");
32+
}
33+
let (lo, hi): (u32, u32);
34+
// SAFETY: the loader runs at CPL 0, and CPUID.07H:EBX[1] says the MSR exists.
35+
unsafe {
36+
core::arch::asm!("rdmsr", in("ecx") 0x3bu32, out("eax") lo, out("edx") hi, options(nomem, nostack))
37+
};
38+
alloc::format!("IA32_TSC_ADJUST {}", ((u64::from(hi) << 32) | u64::from(lo)) as i64)
39+
}
40+
41+
/// `CLFLUSH`'s line on every x86-64 part.
42+
const LINE: u64 = 64;
43+
44+
/// Every line of `[at, at + len)` written back out of this CPU's caches and
45+
/// every other CPU's, before this returns.
46+
pub fn write_back(at: u64, len: usize) {
47+
let mut line = at & !(LINE - 1);
48+
while line < at + len as u64 {
49+
// SAFETY: `CLFLUSH` writes back and invalidates the line containing the
50+
// address and touches nothing else; the caller names memory it
51+
// allocated, and the instruction faults on nothing a canonical address
52+
// can be.
53+
unsafe {
54+
core::arch::asm!("clflush [{addr}]", addr = in(reg) line as *const u8, options(nostack, preserves_flags));
55+
}
56+
line += LINE;
57+
}
58+
// SAFETY: `SFENCE` orders those writebacks ahead of whatever ends this
59+
// machine; it touches no memory or register.
60+
unsafe { core::arch::asm!("sfence", options(nostack, preserves_flags)) };
61+
}
62+
63+
/// What the loader says about the CPU as firmware handed it over, or why the
64+
/// kernel cannot run on it. A UEFI x86-64 loader runs in long mode at CPL 0,
65+
/// the state the kernel's entry takes, so there is nothing to say or refuse.
66+
pub fn cpu_as_entered() -> Result<Option<alloc::string::String>, alloc::string::String> {
67+
Ok(None)
68+
}
69+
70+
/// The I/O port space the chipset's TCO block answers in.
71+
pub mod pio {
72+
/// Whether this architecture has an I/O port space at all.
73+
pub const EXISTS: bool = true;
74+
75+
/// # Safety
76+
/// No fault in Ring 0; the caller owns which device answers at `port` and
77+
/// what the word commands it to do. `kernel/src/arch/x86_64/cpu.rs` states
78+
/// the same contract for the same instruction.
79+
pub unsafe fn outw(port: u16, value: u16) {
80+
// SAFETY: the caller's contract.
81+
unsafe {
82+
core::arch::asm!("out dx, ax", in("dx") port, in("ax") value, options(nomem, nostack, preserves_flags))
83+
};
84+
}
85+
86+
/// One word from an I/O port; safe because a read has no value a caller can
87+
/// get wrong, as `kernel/src/arch/x86_64/cpu.rs`'s `inw` is.
88+
pub fn inw(port: u16) -> u16 {
89+
let value: u16;
90+
// SAFETY: one instruction into the declared output, no memory operand.
91+
unsafe {
92+
core::arch::asm!("in ax, dx", out("ax") value, in("dx") port, options(nomem, nostack, preserves_flags));
93+
}
94+
value
95+
}
96+
}
97+
98+
/// Switch to the boot map at `args.boot_pml4_addr` and jump to the kernel
99+
/// image's entry through the high half, handing it `args`.
100+
///
101+
/// # Safety
102+
/// The boot map identity-maps the memory this code and its stack run from and
103+
/// maps the kernel image at `PHYS_OFFSET`; `image` is that relocated image,
104+
/// `entry_offset` its entry point's offset in it, and `args` stays where it is
105+
/// until the kernel copies it.
106+
pub unsafe fn enter_kernel(image: (u64, u64), entry_offset: u64, args: &KernelArgs) -> ! {
107+
let root = args.boot_pml4_addr;
108+
let entry = crate::PHYS_OFFSET + image.0 + entry_offset;
109+
// SAFETY: the caller's contract: the switch keeps this code and stack
110+
// mapped, and the jump lands in the image it mapped.
111+
unsafe { core::arch::asm!("mov cr3, {}", in(reg) root, options(nostack)) };
112+
// SAFETY: `kernel.elf`'s entry point takes `&KernelArgs` in `rdi` by the boot
113+
// protocol `toyos-abi::boot` and the kernel side of it define between them —
114+
// `sysv64`, because this target's own `"C"` is the Microsoft convention —
115+
// and this bootloader has no way to check the callee's signature, only to
116+
// keep its own side of that contract.
117+
let entry: extern "sysv64" fn(&KernelArgs) -> ! = unsafe { core::mem::transmute(entry) };
118+
entry(args)
119+
}

0 commit comments

Comments
 (0)