Skip to content
Closed
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
22 changes: 21 additions & 1 deletion .github/workflows/rust.yml
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ jobs:
run: cargo test --workspace

qemu:
name: Build boot image
name: Build and boot smoke test
runs-on: ubuntu-latest
needs: check
steps:
Expand All @@ -40,3 +40,23 @@ jobs:
run: sudo apt-get update && sudo apt-get install -y qemu-system-x86
- name: Build boot image
run: cargo build --verbose
- name: Boot BIOS image and assert serial success marker
shell: bash
run: |
set +e
timeout --signal=TERM --kill-after=5s 20s cargo run -- bios > qemu.log 2>&1
status=$?
set -e
cat qemu.log

if ! grep -Fq "[BOOT_OK] Rustalis booted successfully." qemu.log; then
echo "Kernel did not emit the expected boot-success marker."
exit 1
fi

# A healthy kernel intentionally remains in its idle loop; timeout
# is therefore expected after the marker has been observed.
if [ "$status" -ne 0 ] && [ "$status" -ne 124 ]; then
echo "QEMU runner exited unexpectedly with status $status."
exit "$status"
fi
4 changes: 3 additions & 1 deletion kernel/src/arch/x86_64/interrupts.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,5 +14,7 @@ pub struct InterruptStackFrame {
pub fn init() {
// SAFETY: maskable interrupts stay disabled until an IDT and handlers
// have been installed. This prevents delivery to an incomplete table.
unsafe { asm!("cli", options(nomem, nostack, preserves_flags)); }
unsafe {
asm!("cli", options(nomem, nostack, preserves_flags));
}
}
21 changes: 16 additions & 5 deletions kernel/src/fs.rs
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,10 @@ impl<const FILES: usize> RamFs<FILES> {
if self.find(path).is_some() {
return Err(FsError::AlreadyExists);
}
let slot = self.entries.iter().position(|entry| !entry.used)
let slot = self
.entries
.iter()
.position(|entry| !entry.used)
.ok_or(FsError::NoFreeSlots)?;
let entry = &mut self.entries[slot];
entry.path[..path.len()].copy_from_slice(path.as_bytes());
Expand Down Expand Up @@ -100,16 +103,21 @@ impl<const FILES: usize> RamFs<FILES> {

/// Return the path of the Nth occupied slot, for simple directory listings.
pub fn path_at(&self, occupied_index: usize) -> Option<&str> {
self.entries.iter().filter(|entry| entry.used)
.nth(occupied_index).map(Entry::path)
self.entries
.iter()
.filter(|entry| entry.used)
.nth(occupied_index)
.map(Entry::path)
}

pub fn file_count(&self) -> usize {
self.entries.iter().filter(|entry| entry.used).count()
}

fn find(&self, path: &str) -> Option<usize> {
self.entries.iter().position(|entry| entry.used && entry.path() == path)
self.entries
.iter()
.position(|entry| entry.used && entry.path() == path)
}
}

Expand All @@ -120,7 +128,10 @@ fn validate_path(path: &str) -> Result<&str, FsError> {
if path.len() > MAX_PATH_BYTES {
return Err(FsError::PathTooLong);
}
if path.split('/').any(|part| part.is_empty() || part == "." || part == "..") {
if path
.split('/')
.any(|part| part.is_empty() || part == "." || part == "..")
{
return Err(FsError::InvalidPath);
}
Ok(path)
Expand Down
11 changes: 8 additions & 3 deletions kernel/src/main.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
#![no_std]
#![no_main]

use bootloader_api::{entry_point, BootInfo};
use bootloader_api::{BootInfo, entry_point};
use core::panic::PanicInfo;

mod arch;
Expand Down Expand Up @@ -34,10 +34,15 @@ fn kernel_main(boot_info: &'static mut BootInfo) -> ! {
logger::info("RAM filesystem initialized; /welcome.txt verified");
logger::info_u64("RAM filesystem files", ramfs.file_count() as u64);
} else {
logger::error("RAM filesystem smoke test failed");
logger::error("RAM filesystem smoke test failed; boot success withheld");
loop {
core::hint::spin_loop();
}
}

logger::info("Rustalis booted successfully.");
// This marker is a CI/debugging contract: emit it only after early init and
// the RAM filesystem smoke test have completed successfully.
logger::info("[BOOT_OK] Rustalis booted successfully.");
loop {
core::hint::spin_loop();
}
Expand Down
22 changes: 15 additions & 7 deletions src/main.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
use ovmf_prebuilt::{Arch, FileType, Prebuilt, Source};
use std::env;
use std::process::{exit, Command};
use std::process::{Command, exit};

fn main() {
let uefi_path = env!("UEFI_PATH");
Expand All @@ -24,15 +24,23 @@ fn main() {
cmd.args(["-serial", "mon:stdio", "-display", "none"]);

if uefi {
let prebuilt = Prebuilt::fetch(Source::LATEST, "target/ovmf")
.expect("failed to obtain OVMF firmware");
let prebuilt =
Prebuilt::fetch(Source::LATEST, "target/ovmf").expect("failed to obtain OVMF firmware");
let code = prebuilt.get_file(Arch::X64, FileType::Code);
let vars = prebuilt.get_file(Arch::X64, FileType::Vars);
cmd.arg("-drive").arg(format!("format=raw,file={uefi_path}"));
cmd.arg("-drive").arg(format!("if=pflash,format=raw,unit=0,file={},readonly=on", code.display()));
cmd.arg("-drive").arg(format!("if=pflash,format=raw,unit=1,file={},snapshot=on", vars.display()));
cmd.arg("-drive")
.arg(format!("format=raw,file={uefi_path}"));
cmd.arg("-drive").arg(format!(
"if=pflash,format=raw,unit=0,file={},readonly=on",
code.display()
));
cmd.arg("-drive").arg(format!(
"if=pflash,format=raw,unit=1,file={},snapshot=on",
vars.display()
));
} else {
cmd.arg("-drive").arg(format!("format=raw,file={bios_path}"));
cmd.arg("-drive")
.arg(format!("format=raw,file={bios_path}"));
}

let status = cmd.status().expect("failed to start qemu-system-x86_64");
Expand Down
Loading