From 07d53dd50b5a8567d0df74be43853430a5542302 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rom=C3=A1n=20C=C3=A1rdenas=20Rodr=C3=ADguez?= Date: Thu, 22 Oct 2026 14:47:33 +0900 Subject: [PATCH 1/5] feat: userland is bin crate; awkernel is lib crate --- .cargo/config.toml | 42 +++++++++--------- Cargo.toml | 1 + Makefile | 2 +- README.md | 5 ++- awkernel_macros/Cargo.toml | 12 ++++++ awkernel_macros/src/lib.rs | 74 ++++++++++++++++++++++++++++++++ kernel/Cargo.toml | 9 ++-- kernel/src/{main.rs => lib.rs} | 20 +++++++-- userland/Cargo.toml | 26 ++++++++++- userland/src/{lib.rs => main.rs} | 8 +++- 10 files changed, 164 insertions(+), 35 deletions(-) create mode 100644 awkernel_macros/Cargo.toml create mode 100644 awkernel_macros/src/lib.rs rename kernel/src/{main.rs => lib.rs} (90%) rename userland/src/{lib.rs => main.rs} (90%) diff --git a/.cargo/config.toml b/.cargo/config.toml index 20f8233cf..93879653c 100644 --- a/.cargo/config.toml +++ b/.cargo/config.toml @@ -1,9 +1,9 @@ [alias] -x86 = "build --package awkernel --no-default-features --features x86 --target targets/x86_64-kernel.json -Zbuild-std=core,alloc,compiler_builtins -Zbuild-std-features=compiler-builtins-mem -Zjson-target-spec" -aarch64 = "build --package awkernel --no-default-features --features aarch64 --target targets/aarch64-kernel.json -Zbuild-std=core,alloc,compiler_builtins -Zbuild-std-features=compiler-builtins-mem -Zjson-target-spec" -rv32 = "build --package awkernel --no-default-features --features rv32 --target riscv32imac-unknown-none-elf -Zbuild-std=core,alloc,compiler_builtins -Zbuild-std-features=compiler-builtins-mem -Zjson-target-spec" -rv64 = "build --package awkernel --no-default-features --features rv64 --target riscv64gc-unknown-none-elf -Zbuild-std=core,alloc,compiler_builtins -Zbuild-std-features=compiler-builtins-mem -Zjson-target-spec" -std = "build --package awkernel --no-default-features --features std" +x86 = "build --package userland --no-default-features --features x86 --target targets/x86_64-kernel.json -Zbuild-std=core,alloc,compiler_builtins -Zbuild-std-features=compiler-builtins-mem -Zjson-target-spec" +aarch64 = "build --package userland --no-default-features --features aarch64 --target targets/aarch64-kernel.json -Zbuild-std=core,alloc,compiler_builtins -Zbuild-std-features=compiler-builtins-mem -Zjson-target-spec" +rv32 = "build --package userland --no-default-features --features rv32 --target riscv32imac-unknown-none-elf -Zbuild-std=core,alloc,compiler_builtins -Zbuild-std-features=compiler-builtins-mem -Zjson-target-spec" +rv64 = "build --package userland --no-default-features --features rv64 --target riscv64gc-unknown-none-elf -Zbuild-std=core,alloc,compiler_builtins -Zbuild-std-features=compiler-builtins-mem -Zjson-target-spec" +std = "build --package userland --no-default-features --features std" test_awkernel_lib = "test --package awkernel_lib --no-default-features --features std" test_awkernel_async_lib = "test --package awkernel_async_lib --no-default-features --features std" @@ -12,25 +12,25 @@ test_smoltcp = "test --package smoltcp --no-default-features --features std --fe test_affinity_btree_queue = "test --package affinity_btree_queue" test_rd_gen_to_dags = "test --package rd_gen_to_dags --no-default-features --features std --features milliseconds" -clippy_x86 = "clippy --package awkernel --no-default-features --features x86 --target targets/x86_64-kernel.json -Zbuild-std=core,alloc,compiler_builtins -Zbuild-std-features=compiler-builtins-mem -Zjson-target-spec" -clippy_raspi = "clippy --package awkernel --no-default-features --features raspi --target targets/aarch64-kernel.json -Zbuild-std=core,alloc,compiler_builtins -Zbuild-std-features=compiler-builtins-mem -Zjson-target-spec" -clippy_raspi5 = "clippy --package awkernel --no-default-features --features raspi5 --target targets/aarch64-kernel.json -Zbuild-std=core,alloc,compiler_builtins -Zbuild-std-features=compiler-builtins-mem -Zjson-target-spec" -clippy_aarch64_virt = "clippy --package awkernel --no-default-features --features aarch64_virt --target targets/aarch64-kernel.json -Zbuild-std=core,alloc,compiler_builtins -Zbuild-std-features=compiler-builtins-mem -Zjson-target-spec" -clippy_std = "clippy --package awkernel --no-default-features --features std" -clippy_rv32 = "clippy --package awkernel --no-default-features --features rv32 --target riscv32imac-unknown-none-elf -Zbuild-std=core,alloc,compiler_builtins -Zbuild-std-features=compiler-builtins-mem -Zjson-target-spec" -clippy_rv64 = "clippy --package awkernel --no-default-features --features rv64 --target riscv64gc-unknown-none-elf -Zbuild-std=core,alloc,compiler_builtins -Zbuild-std-features=compiler-builtins-mem -Zjson-target-spec" +clippy_x86 = "clippy --package userland --no-default-features --features x86 --target targets/x86_64-kernel.json -Zbuild-std=core,alloc,compiler_builtins -Zbuild-std-features=compiler-builtins-mem -Zjson-target-spec" +clippy_raspi = "clippy --package userland --no-default-features --features raspi --target targets/aarch64-kernel.json -Zbuild-std=core,alloc,compiler_builtins -Zbuild-std-features=compiler-builtins-mem -Zjson-target-spec" +clippy_raspi5 = "clippy --package userland --no-default-features --features raspi5 --target targets/aarch64-kernel.json -Zbuild-std=core,alloc,compiler_builtins -Zbuild-std-features=compiler-builtins-mem -Zjson-target-spec" +clippy_aarch64_virt = "clippy --package userland --no-default-features --features aarch64_virt --target targets/aarch64-kernel.json -Zbuild-std=core,alloc,compiler_builtins -Zbuild-std-features=compiler-builtins-mem -Zjson-target-spec" +clippy_std = "clippy --package userland --no-default-features --features std" +clippy_rv32 = "clippy --package userland --no-default-features --features rv32 --target riscv32imac-unknown-none-elf -Zbuild-std=core,alloc,compiler_builtins -Zbuild-std-features=compiler-builtins-mem -Zjson-target-spec" +clippy_rv64 = "clippy --package userland --no-default-features --features rv64 --target riscv64gc-unknown-none-elf -Zbuild-std=core,alloc,compiler_builtins -Zbuild-std-features=compiler-builtins-mem -Zjson-target-spec" clippy_rd_gen_to_dags = "clippy --package rd_gen_to_dags --no-default-features --features std --features milliseconds" -check_aarch64 = "check --package awkernel --no-default-features --features aarch64 --target targets/aarch64-kernel.json -Zbuild-std=core,alloc,compiler_builtins -Zbuild-std-features=compiler-builtins-mem -Zjson-target-spec" -check_x86 = "check --package awkernel --no-default-features --features x86 --target targets/x86_64-kernel.json -Zbuild-std=core,alloc,compiler_builtins -Zbuild-std-features=compiler-builtins-mem -Zjson-target-spec" -check_rv32 = "check --package awkernel --no-default-features --features rv32 --target riscv32imac-unknown-none-elf -Zbuild-std=core,alloc,compiler_builtins -Zbuild-std-features=compiler-builtins-mem -Zjson-target-spec" -check_rv64 = "check --package awkernel --no-default-features --features rv64 --target riscv64gc-unknown-none-elf -Zbuild-std=core,alloc,compiler_builtins -Zbuild-std-features=compiler-builtins-mem -Zjson-target-spec" -check_no_std = "check --package awkernel -Zbuild-std=core,alloc,compiler_builtins -Zbuild-std-features=compiler-builtins-mem --quiet --message-format=json -Zjson-target-spec" -check_std = "check --package awkernel --no-default-features --features std" +check_aarch64 = "check --package userland --no-default-features --features aarch64 --target targets/aarch64-kernel.json -Zbuild-std=core,alloc,compiler_builtins -Zbuild-std-features=compiler-builtins-mem -Zjson-target-spec" +check_x86 = "check --package userland --no-default-features --features x86 --target targets/x86_64-kernel.json -Zbuild-std=core,alloc,compiler_builtins -Zbuild-std-features=compiler-builtins-mem -Zjson-target-spec" +check_rv32 = "check --package userland --no-default-features --features rv32 --target riscv32imac-unknown-none-elf -Zbuild-std=core,alloc,compiler_builtins -Zbuild-std-features=compiler-builtins-mem -Zjson-target-spec" +check_rv64 = "check --package userland --no-default-features --features rv64 --target riscv64gc-unknown-none-elf -Zbuild-std=core,alloc,compiler_builtins -Zbuild-std-features=compiler-builtins-mem -Zjson-target-spec" +check_no_std = "check --package userland -Zbuild-std=core,alloc,compiler_builtins -Zbuild-std-features=compiler-builtins-mem --quiet --message-format=json -Zjson-target-spec" +check_std = "check --package userland --no-default-features --features std" -doc_raspi = "doc --package awkernel --no-default-features --features raspi --target targets/aarch64-kernel.json -Zbuild-std=core,alloc,compiler_builtins -Zbuild-std-features=compiler-builtins-mem -Zjson-target-spec" -doc_aarch64_virt = "doc --package awkernel --no-default-features --features aarch64_virt --target targets/aarch64-kernel.json -Zbuild-std=core,alloc,compiler_builtins -Zbuild-std-features=compiler-builtins-mem -Zjson-target-spec" -doc_x86 = "doc --package awkernel --no-default-features --features x86 --target targets/x86_64-kernel.json -Zbuild-std=core,alloc,compiler_builtins -Zbuild-std-features=compiler-builtins-mem -Zjson-target-spec" +doc_raspi = "doc --package userland --no-default-features --features raspi --target targets/aarch64-kernel.json -Zbuild-std=core,alloc,compiler_builtins -Zbuild-std-features=compiler-builtins-mem -Zjson-target-spec" +doc_aarch64_virt = "doc --package userland --no-default-features --features aarch64_virt --target targets/aarch64-kernel.json -Zbuild-std=core,alloc,compiler_builtins -Zbuild-std-features=compiler-builtins-mem -Zjson-target-spec" +doc_x86 = "doc --package userland --no-default-features --features x86 --target targets/x86_64-kernel.json -Zbuild-std=core,alloc,compiler_builtins -Zbuild-std-features=compiler-builtins-mem -Zjson-target-spec" udeps_x86 = "udeps --package awkernel --no-default-features --features x86 --target x86_64-unknown-linux-gnu" udeps_raspi = "udeps --package awkernel --no-default-features --features x86 --target x86_64-unknown-linux-gnu" diff --git a/Cargo.toml b/Cargo.toml index 65a89b195..b58acb6ad 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -19,6 +19,7 @@ members = [ "applications/tests/*", "smoltcp", "awkernel_futures_macro", + "awkernel_macros", ] resolver = "2" diff --git a/Makefile b/Makefile index 28a00f6b5..496280c19 100644 --- a/Makefile +++ b/Makefile @@ -247,7 +247,7 @@ check_std: FORCE cargo +$(RUSTV) check_std run-std: - cargo +$(RUSTV) run --package awkernel --no-default-features --features std $(OPT) + cargo +$(RUSTV) run --package userland --no-default-features --features std $(OPT) # Docs docs: FORCE diff --git a/README.md b/README.md index b87090200..ece034644 100644 --- a/README.md +++ b/README.md @@ -41,6 +41,8 @@ $ cargo binstall mdbook-mermaid - verified library for awkernel_async_lib - pure Rust (no dependencies on external functions and no inline assembly) - [awkernel_futures_macro](./awkernel_futures_macro/) +- [awkernel_macros](./awkernel_macros/) + - `#[awkernel::main]` entry-point macro - [awkernel_drivers](./awkernel_drivers/) - [awkernel_aarch64](./awkernel_aarch64/) - [userland](./userland/) @@ -60,7 +62,8 @@ graph TD; kernel-->awkernel_aarch64; kernel-->awkernel_drivers; awkernel_drivers-->awkernel_lib; - kernel-->userland; + userland-->kernel; + kernel-->awkernel_macros; ``` Applications can use `awkernel_async_lib`, `awkernel_lib`, and `awkernel_drivers`. diff --git a/awkernel_macros/Cargo.toml b/awkernel_macros/Cargo.toml new file mode 100644 index 000000000..dbe89a4eb --- /dev/null +++ b/awkernel_macros/Cargo.toml @@ -0,0 +1,12 @@ +[package] +name = "awkernel_macros" +version = "0.1.0" +edition = "2021" + +[lib] +proc-macro = true + +[dependencies] +proc-macro2 = "1.0.60" +quote = "1.0" +syn = { version = "2.0.52", features = ["full"] } diff --git a/awkernel_macros/src/lib.rs b/awkernel_macros/src/lib.rs new file mode 100644 index 000000000..913133362 --- /dev/null +++ b/awkernel_macros/src/lib.rs @@ -0,0 +1,74 @@ +//! Procedural macros for Awkernel. + +use proc_macro::TokenStream; +use quote::quote; +use syn::{parse_macro_input, spanned::Spanned, ItemFn}; + +/// Marks an `async fn` as the entry point of the userland. +/// +/// The kernel spawns it as the first task once it has booted. +/// The function must be `async`, take no arguments, and return +/// `Result<(), Cow<'static, str>>`. +/// +/// ```ignore +/// #[awkernel::main] +/// async fn main() -> Result<(), alloc::borrow::Cow<'static, str>> { +/// Ok(()) +/// } +/// ``` +#[proc_macro_attribute] +pub fn main(args: TokenStream, item: TokenStream) -> TokenStream { + let func = parse_macro_input!(item as ItemFn); + + if !args.is_empty() { + return syn::Error::new( + proc_macro2::TokenStream::from(args).span(), + "`#[awkernel::main]` takes no arguments", + ) + .to_compile_error() + .into(); + } + if func.sig.asyncness.is_none() { + return syn::Error::new(func.sig.span(), "the entry point must be an `async fn`") + .to_compile_error() + .into(); + } + if !func.sig.inputs.is_empty() { + return syn::Error::new( + func.sig.inputs.span(), + "the entry point must not take arguments", + ) + .to_compile_error() + .into(); + } + if !func.sig.generics.params.is_empty() { + return syn::Error::new( + func.sig.generics.span(), + "the entry point must not be generic", + ) + .to_compile_error() + .into(); + } + + let name = &func.sig.ident; + + // The symbol is resolved at link time by the kernel (`awkernel::UserMain`). + quote! { + #func + + #[no_mangle] + pub fn __awkernel_user_main() -> ::core::pin::Pin< + ::alloc::boxed::Box< + dyn ::core::future::Future< + Output = ::core::result::Result< + (), + ::alloc::borrow::Cow<'static, str>, + >, + > + ::core::marker::Send, + >, + > { + ::alloc::boxed::Box::pin(#name()) + } + } + .into() +} diff --git a/kernel/Cargo.toml b/kernel/Cargo.toml index 92f90438d..b65065e90 100644 --- a/kernel/Cargo.toml +++ b/kernel/Cargo.toml @@ -43,8 +43,8 @@ optional = true version = "0.2" optional = true -[dependencies.userland] -path = "../userland" +[dependencies.awkernel_macros] +path = "../awkernel_macros" [dependencies.awkernel_async_lib] path = "../awkernel_async_lib" @@ -79,7 +79,7 @@ optional = true default = ["x86"] heap-tlsf = ["awkernel_lib/heap-tlsf"] heap-wf-alloc = ["awkernel_lib/heap-wf-alloc"] -perf = ["awkernel_async_lib/perf", "userland/perf"] +perf = ["awkernel_async_lib/perf"] period-index-propagation = [ "perf", "awkernel_async_lib/period-index-propagation", @@ -150,6 +150,3 @@ aarch64_virt = [ ] spinlock = ["awkernel_lib/spinlock"] linux = [] -test_clustered_edf = ["userland/test_clustered_edf"] -test_dvfs = ["userland/test_dvfs"] -test_raspi_dvfs = ["userland/test_raspi_dvfs"] diff --git a/kernel/src/main.rs b/kernel/src/lib.rs similarity index 90% rename from kernel/src/main.rs rename to kernel/src/lib.rs index c0299d28a..b79b4f1d3 100644 --- a/kernel/src/main.rs +++ b/kernel/src/lib.rs @@ -4,19 +4,21 @@ //! It can execute async/await applications in kernel space safely. #![cfg_attr(feature = "x86", feature(abi_x86_interrupt))] -#![no_main] #![allow(unused_features)] #![feature(allocator_api)] #![cfg_attr(not(feature = "std"), no_std)] extern crate alloc; +use alloc::boxed::Box; use awkernel_async_lib::{ scheduler::{wake_task, SchedulerType}, - task, + task::{self, TaskResult}, }; use core::{ fmt::Debug, + future::Future, + pin::Pin, sync::atomic::{AtomicBool, AtomicU16, Ordering}, }; use kernel_info::KernelInfo; @@ -28,6 +30,18 @@ mod kernel_info; #[cfg(not(feature = "std"))] mod nostd; +pub use awkernel_macros::main; + +/// Signature of the symbol defined by `#[awkernel::main]`. +pub type UserMain = fn() -> Pin + Send>>; + +extern "Rust" { + fn __awkernel_user_main() -> Pin + Send>>; +} + +// Fails to compile if the extern declaration drifts from `UserMain`. +const _: UserMain = || unsafe { __awkernel_user_main() }; + static PRIMARY_READY: AtomicBool = AtomicBool::new(false); static NUM_READY_WORKER: AtomicU16 = AtomicU16::new(0); #[cfg(feature = "period-index-propagation")] @@ -63,7 +77,7 @@ fn main(kernel_info: KernelInfo) { // Userland. task::spawn( "main".into(), - async move { userland::main().await }, + unsafe { __awkernel_user_main() }, SchedulerType::PrioritizedFIFO(31), ); diff --git a/userland/Cargo.toml b/userland/Cargo.toml index 8f9984241..71ee88c72 100644 --- a/userland/Cargo.toml +++ b/userland/Cargo.toml @@ -5,9 +5,17 @@ edition = "2021" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html +[[bin]] +name = "awkernel" +path = "src/main.rs" + [dependencies] log = "0.4" +[dependencies.awkernel] +path = "../kernel" +default-features = false + [dependencies.awkernel_async_lib] path = "../awkernel_async_lib" @@ -88,7 +96,23 @@ optional = true [features] default = [] -perf = ["awkernel_services/perf"] +perf = ["awkernel/perf", "awkernel_services/perf"] + +# Kernel configuration (forwarded to the `awkernel` kernel crate) +x86 = ["awkernel/x86"] +aarch64 = ["awkernel/aarch64"] +raspi = ["awkernel/raspi"] +raspi5 = ["awkernel/raspi5"] +aarch64_virt = ["awkernel/aarch64_virt"] +rv32 = ["awkernel/rv32"] +rv64 = ["awkernel/rv64"] +std = ["awkernel/std"] +debug = ["awkernel/debug"] +heap-tlsf = ["awkernel/heap-tlsf"] +heap-wf-alloc = ["awkernel/heap-wf-alloc"] +spinlock = ["awkernel/spinlock"] +linux = ["awkernel/linux"] +period-index-propagation = ["awkernel/period-index-propagation"] # Evaluation applications rd_gen_to_dags = ["dep:rd_gen_to_dags"] diff --git a/userland/src/lib.rs b/userland/src/main.rs similarity index 90% rename from userland/src/lib.rs rename to userland/src/main.rs index cdd26b95f..a05504479 100644 --- a/userland/src/lib.rs +++ b/userland/src/main.rs @@ -1,10 +1,14 @@ -#![no_std] +#![cfg_attr(not(feature = "std"), no_std)] +#![no_main] extern crate alloc; +// Links the kernel's entry points and panic handler. +extern crate awkernel; use alloc::borrow::Cow; -pub async fn main() -> Result<(), Cow<'static, str>> { +#[awkernel::main] +async fn main() -> Result<(), Cow<'static, str>> { awkernel_services::run().await; #[cfg(feature = "autoware")] From 4419855e7badc7be904d2145303f0072bb5ef07a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rom=C3=A1n=20C=C3=A1rdenas=20Rodr=C3=ADguez?= Date: Thu, 22 Oct 2026 15:11:18 +0900 Subject: [PATCH 2/5] feat(x86): compile mpboot with kernel/build.rs --- .vscode/settings.json | 10 ++- Makefile | 16 ++-- README.md | 22 +++--- kernel/asm/x86/Makefile | 21 ----- kernel/build.rs | 108 ++++++++++++++++++++++++++ kernel/src/arch/x86_64/kernel_main.rs | 2 +- scripts/ci.sh | 5 -- 7 files changed, 134 insertions(+), 50 deletions(-) delete mode 100644 kernel/asm/x86/Makefile create mode 100644 kernel/build.rs diff --git a/.vscode/settings.json b/.vscode/settings.json index 44a84d156..39b9509f0 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -2,18 +2,22 @@ "rust-analyzer.showUnlinkedFileNotification": false, "tlaplus.tlc.modelChecker.options": "-workers auto", "editor.formatOnSave": true, + // The cargo aliases build `--package userland`, which has no default + // architecture: pass it explicitly (the features are forwarded to `awkernel`). // For AArch64 Virt // "rust-analyzer.cargo.buildScripts.overrideCommand": [ // "cargo", // "+nightly-2026-07-20", // "check_no_std", // "--target=targets/aarch64-kernel.json", + // "--features=aarch64_virt", // ], // "rust-analyzer.check.overrideCommand": [ // "cargo", // "+nightly-2026-07-20", // "check_no_std", // "--target=targets/aarch64-kernel.json", + // "--features=aarch64_virt", // ], // "rust-analyzer.cargo.features": [ // "aarch64_virt" @@ -24,12 +28,14 @@ "+nightly-2026-07-20", "check_no_std", "--target=targets/x86_64-kernel.json", + "--features=x86", ], "rust-analyzer.check.overrideCommand": [ "cargo", "+nightly-2026-07-20", "check_no_std", "--target=targets/x86_64-kernel.json", + "--features=x86", ], "rust-analyzer.cargo.features": [ "x86" @@ -39,6 +45,7 @@ // "cargo", // "+nightly-2026-07-20", // "check_std", + // "--features=std", // "--quiet", // "--message-format=json" // ], @@ -46,10 +53,11 @@ // "cargo", // "+nightly-2026-07-20", // "check_std", + // "--features=std", // "--quiet", // "--message-format=json" // ], // "rust-analyzer.cargo.features": [ // "std" // ], -} +} \ No newline at end of file diff --git a/Makefile b/Makefile index 496280c19..7056116af 100644 --- a/Makefile +++ b/Makefile @@ -33,8 +33,6 @@ ASM_FILE_DEP_AARCH64=kernel/asm/aarch64/exception.S ASM_FILE_AARCH64=kernel/asm/aarch64/boot.S ASM_OBJ_AARCH64=boot_aarch64.o -X86ASM=kernel/asm/x86 - ifndef $(CC) CC = clang endif @@ -61,7 +59,7 @@ all: aarch64 x86_64 riscv32 riscv64 std check: check_aarch64 check_x86_64 check_std check_riscv32 check_riscv64 -clippy: $(X86ASM) +clippy: cargo +$(RUSTV) clippy_x86 cargo +$(RUSTV) clippy_raspi cargo +$(RUSTV) clippy_raspi5 @@ -71,7 +69,7 @@ clippy: $(X86ASM) cargo +$(RUSTV) clippy_std cargo +$(RUSTV) clippy_rd_gen_to_dags -udeps: $(X86ASM) +udeps: cargo +$(RUSTV) udeps_x86 cargo: target/aarch64-kernel/$(BUILD)/awkernel kernel-x86_64.elf std @@ -139,10 +137,10 @@ gdb-aarch64-virt: x86_64: x86_64_uefi.img -check_x86_64: $(X86ASM) +check_x86_64: cargo +$(RUSTV) check_x86 -kernel-x86_64.elf: $(X86ASM) FORCE +kernel-x86_64.elf: FORCE RUSTFLAGS="$(RUSTC_MISC_ARGS)" cargo +$(RUSTV) x86 $(OPT) python3 scripts/embed_debug_info.py $@ @@ -152,9 +150,6 @@ x86_64_boot.img: kernel-x86_64.elf x86_64_uefi.img: kernel-x86_64.elf RUSTFLAGS="$(RUSTC_MISC_ARGS)" cargo +$(RUSTV) run --release --package x86bootdisk --no-default-features --features uefi -- --kernel $< --output $@ --pxe x86_64_uefi_pxe_boot --boot-type uefi -$(X86ASM): FORCE - $(MAKE) -C $@ - OVMF_PATH := $(shell cat ${HOME}/.ovmfpath) QEMU_X86_ARGS= -drive if=pflash,format=raw,readonly=on,file=${OVMF_PATH}/code.fd @@ -271,9 +266,8 @@ fmt: FORCE # Clean clean: FORCE - rm -rf *.o *.elf $(AARCH64_BSP_LD) *.img kernel/asm/x86/*.o x86_64_uefi_pxe_boot + rm -rf *.o *.elf $(AARCH64_BSP_LD) *.img x86_64_uefi_pxe_boot cargo clean - $(MAKE) -C $(X86ASM) clean ## QEMU Monitor diff --git a/README.md b/README.md index ece034644..d24b011e5 100644 --- a/README.md +++ b/README.md @@ -98,7 +98,6 @@ etc ### x86_64 ```text -$ make kernel/asm/x86 $ cargo doc_x86 $ ls target/x86_64-kernel/doc/awkernel/index.html $ ls target/aarch64-kernel/doc/awkernel_lib/index.html @@ -123,28 +122,29 @@ Debug build. $ make x86_64 ``` -#### Developing for x86_64 on macOS +#### Building x86_64 on Apple Silicon (or another non-x86_64 host) -`kernel/asm/x86/mpboot.S` mixes 16/32/64-bit code and needs a real GNU assembler and linker. -By default, Apple systems provide an alias so `gcc` points to `clang`. -Also, modern Apple Silicon processors are aarch64. -Therefore, we must install a GNU x86_64 ELF cross toolchain: +The x86_64 build includes `kernel/asm/x86/mpboot.S`, an assembly file that mixes 16-, 32-, and 64-bit code. The build script assembles and links it with GNU tools, then uses `objcopy` to create a binary image. A Rust target alone does not provide these tools. + +On Apple Silicon, install an x86_64 ELF GNU toolchain: ```text $ brew install x86_64-elf-gcc x86_64-elf-binutils ``` -Then, when building your application, point to this specific cross toolchain: +The build script automatically uses the `x86_64-elf-` tool prefix if `x86_64-elf-gcc` is available. Otherwise, it tries the host tools (`gcc`, `ld`, and `objcopy`), which may not support this build on a non-x86_64 host. + +To select a toolchain with a different prefix, pass that prefix as `CROSS_COMPILE`. For example, if its tools are named `my-x86_64-elf-gcc`, `my-x86_64-elf-ld`, and `my-x86_64-elf-objcopy`, run: ```text -$ make x86_64 RELEASE=1 CROSS_COMPILE=x86_64-elf- +$ make x86_64 RELEASE=1 CROSS_COMPILE=my-x86_64-elf- ``` > [!IMPORTANT] > -> Do not remove the trailing `-` - -This way, Awkernel will use the `x86_64-elf-gcc` compiler and the `x86_64-elf-ld` linker. +> `CROSS_COMPILE` is a prefix, not a path to the compiler. Keep the trailing `-` so the build can append `gcc`, `ld`, and `objcopy` to it. +> +> For example, `CROSS_COMPILE=x86_64-elf-` selects `x86_64-elf-gcc`, `x86_64-elf-ld`, and `x86_64-elf-objcopy`. ### Boot diff --git a/kernel/asm/x86/Makefile b/kernel/asm/x86/Makefile deleted file mode 100644 index 7d779142c..000000000 --- a/kernel/asm/x86/Makefile +++ /dev/null @@ -1,21 +0,0 @@ -ASM_FILE_X86=mpboot.S -ASM_OBJ_X86=mpboot.o -ASM_ELF_X86=mpboot.elf -ASM_IMG_X86=mpboot.img - -CROSS_COMPILE ?= - -all: $(ASM_IMG_X86) - -$(ASM_OBJ_X86): $(ASM_FILE_X86) - $(CROSS_COMPILE)gcc -m32 -fno-pie -nostdlib -c $< -o $@ - -# See kernel::arch::x86_64::kernel_main::NON_PRIMARY_START -$(ASM_ELF_X86): $(ASM_OBJ_X86) - $(CROSS_COMPILE)ld -m elf_i386 -N -e _start_cpu -Ttext 0 $< -o $@ - -%.img: %.elf - rust-objcopy -O binary $< $@ - -clean: - rm -f *.o *.img *.elf diff --git a/kernel/build.rs b/kernel/build.rs new file mode 100644 index 000000000..8bf14bb2c --- /dev/null +++ b/kernel/build.rs @@ -0,0 +1,108 @@ +//! Publishes the linker script of the selected architecture as `awkernel.ld`. +//! +//! The script is copied to `OUT_DIR`, which is added to the native library +//! search path. Cargo forwards that path to the final link of every crate that +//! depends on `awkernel`, so a userland crate only has to pass +//! `-Tawkernel.ld`, e.g. with `println!("cargo:rustc-link-arg-bins=-Tawkernel.ld")` +//! in its own `build.rs`. +//! +//! AArch64 is not covered yet: its script is a template (`#INITADDR#`) and it +//! also needs `boot.S` assembled and linked. + +use std::{ + env, fs, + path::{Path, PathBuf}, + process::Command, +}; + +fn main() { + println!("cargo:rerun-if-changed=build.rs"); + println!("cargo:rerun-if-changed=ld"); + println!("cargo:rerun-if-env-changed=CROSS_COMPILE"); + + if env::var_os("CARGO_FEATURE_X86").is_some() { + build_x86_64_mpboot(); + } + + let script = if env::var_os("CARGO_FEATURE_X86").is_some() { + "x86_64-link.lds" + } else if env::var_os("CARGO_FEATURE_RV32").is_some() { + "rv32-link.lds" + } else if env::var_os("CARGO_FEATURE_RV64").is_some() { + "rv64-link.lds" + } else { + return; + }; + + let manifest_dir = + PathBuf::from(env::var_os("CARGO_MANIFEST_DIR").expect("CARGO_MANIFEST_DIR is not set")); + let out_dir = PathBuf::from(env::var_os("OUT_DIR").expect("OUT_DIR is not set")); + + fs::copy( + manifest_dir.join("ld").join(script), + out_dir.join("awkernel.ld"), + ) + .expect("failed to copy the linker script"); + + println!("cargo:rustc-link-search=native={}", out_dir.display()); +} + +/// Builds the real-mode trampoline of the non-primary CPUs as `OUT_DIR/mpboot.img`. +/// +/// It mixes 16/32/64-bit code and needs a GNU 32-bit ELF `gcc` and `ld`. +/// Set `CROSS_COMPILE` (e.g. `x86_64-elf-`) to choose the toolchain explicitly. +/// Otherwise `x86_64-elf-` is used if installed (e.g. macOS), else the native one. +fn build_x86_64_mpboot() { + println!("cargo:rerun-if-changed=asm/x86/mpboot.S"); + + let manifest_dir = + PathBuf::from(env::var_os("CARGO_MANIFEST_DIR").expect("CARGO_MANIFEST_DIR is not set")); + let out_dir = PathBuf::from(env::var_os("OUT_DIR").expect("OUT_DIR is not set")); + let prefix = env::var("CROSS_COMPILE").unwrap_or_else(|_| detect_prefix()); + + let src = manifest_dir.join("asm/x86/mpboot.S"); + let obj = out_dir.join("mpboot.o"); + let elf = out_dir.join("mpboot.elf"); + let img = out_dir.join("mpboot.img"); + + run(Command::new(format!("{prefix}gcc")) + .args(["-m32", "-fno-pie", "-nostdlib", "-c"]) + .arg(&src) + .arg("-o") + .arg(&obj)); + + // See kernel::arch::x86_64::kernel_main::NON_PRIMARY_START + run(Command::new(format!("{prefix}ld")) + .args(["-m", "elf_i386", "-N", "-e", "_start_cpu", "-Ttext", "0"]) + .arg(&obj) + .arg("-o") + .arg(&elf)); + + run(Command::new(format!("{prefix}objcopy")) + .args(["-O", "binary"]) + .arg(&elf) + .arg(&img)); + + assert!(Path::new(&img).exists(), "mpboot.img was not generated"); +} + +fn detect_prefix() -> String { + let cross = "x86_64-elf-"; + let found = Command::new(format!("{cross}gcc")) + .arg("--version") + .output() + .is_ok_and(|o| o.status.success()); + + if found { + cross.to_string() + } else { + String::new() + } +} + +fn run(cmd: &mut Command) { + let status = cmd + .status() + .unwrap_or_else(|e| panic!("failed to run {cmd:?}: {e} (is CROSS_COMPILE set?)")); + assert!(status.success(), "{cmd:?} failed"); +} diff --git a/kernel/src/arch/x86_64/kernel_main.rs b/kernel/src/arch/x86_64/kernel_main.rs index 551379e00..72c2b9975 100644 --- a/kernel/src/arch/x86_64/kernel_main.rs +++ b/kernel/src/arch/x86_64/kernel_main.rs @@ -485,7 +485,7 @@ const CPU_ID_POS: u64 = CR3_POS + 8; fn write_boot_images(offset: u64, mpboot_start: u64) { // Calculate address. - let mpboot = include_bytes!("../../../asm/x86/mpboot.img"); + let mpboot = include_bytes!(concat!(env!("OUT_DIR"), "/mpboot.img")); let mpboot_phy_addr = VirtAddr::new(mpboot_start + offset); let main_addr = VirtAddr::new(NON_PRIMARY_KERNEL_MAIN + offset + mpboot_start); diff --git a/scripts/ci.sh b/scripts/ci.sh index 039dc7998..d59106db7 100755 --- a/scripts/ci.sh +++ b/scripts/ci.sh @@ -5,11 +5,6 @@ set -eux export RUSTUP_TOOLCHAIN="${RUSTUP_TOOLCHAIN:-nightly-2026-07-20}" -# Forwarded to `make` (and its recursive invocation in kernel/asm/x86) so that -# building mpboot.S works on hosts without a native 32-bit ELF gcc/ld, e.g. -# macOS: CROSS_COMPILE=x86_64-elf- ./scripts/ci.sh (see README.md). -export CROSS_COMPILE="${CROSS_COMPILE:-}" - cargo fmt --check for bsp in aarch64_virt raspi5 raspi4 raspi3; do From ba81bec674fb198770c94c5ecca5cd617329c673 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rom=C3=A1n=20C=C3=A1rdenas=20Rodr=C3=ADguez?= Date: Fri, 23 Oct 2026 10:39:07 +0900 Subject: [PATCH 3/5] Add template external userland as example --- userland_extern_template/.cargo/config.toml | 10 ++ userland_extern_template/.gitignore | 4 + userland_extern_template/Cargo.toml | 29 ++++ userland_extern_template/Makefile | 139 ++++++++++++++++++ userland_extern_template/README.md | 37 +++++ userland_extern_template/build.rs | 4 + userland_extern_template/rust-toolchain.toml | 2 + .../scripts/embed_debug_info.py | 44 ++++++ userland_extern_template/src/main.rs | 18 +++ .../targets/x86_64-kernel.json | 22 +++ 10 files changed, 309 insertions(+) create mode 100644 userland_extern_template/.cargo/config.toml create mode 100644 userland_extern_template/.gitignore create mode 100644 userland_extern_template/Cargo.toml create mode 100644 userland_extern_template/Makefile create mode 100644 userland_extern_template/README.md create mode 100644 userland_extern_template/build.rs create mode 100644 userland_extern_template/rust-toolchain.toml create mode 100644 userland_extern_template/scripts/embed_debug_info.py create mode 100644 userland_extern_template/src/main.rs create mode 100644 userland_extern_template/targets/x86_64-kernel.json diff --git a/userland_extern_template/.cargo/config.toml b/userland_extern_template/.cargo/config.toml new file mode 100644 index 000000000..1701188d1 --- /dev/null +++ b/userland_extern_template/.cargo/config.toml @@ -0,0 +1,10 @@ +[alias] +x86 = "build --no-default-features --features x86 --target targets/x86_64-kernel.json -Zbuild-std=core,alloc,compiler_builtins -Zbuild-std-features=compiler-builtins-mem -Zjson-target-spec" +rv32 = "build --no-default-features --features rv32 --target riscv32imac-unknown-none-elf -Zbuild-std=core,alloc,compiler_builtins -Zbuild-std-features=compiler-builtins-mem -Zjson-target-spec" +rv64 = "build --no-default-features --features rv64 --target riscv64gc-unknown-none-elf -Zbuild-std=core,alloc,compiler_builtins -Zbuild-std-features=compiler-builtins-mem -Zjson-target-spec" +check_x86 = "check --no-default-features --features x86 --target targets/x86_64-kernel.json -Zbuild-std=core,alloc,compiler_builtins -Zbuild-std-features=compiler-builtins-mem -Zjson-target-spec" +check_rv32 = "check --no-default-features --features rv32 --target riscv32imac-unknown-none-elf -Zbuild-std=core,alloc,compiler_builtins -Zbuild-std-features=compiler-builtins-mem -Zjson-target-spec" +check_rv64 = "check --no-default-features --features rv64 --target riscv64gc-unknown-none-elf -Zbuild-std=core,alloc,compiler_builtins -Zbuild-std-features=compiler-builtins-mem -Zjson-target-spec" +clippy_x86 = "clippy --no-default-features --features x86 --target targets/x86_64-kernel.json -Zbuild-std=core,alloc,compiler_builtins -Zbuild-std-features=compiler-builtins-mem -Zjson-target-spec" +clippy_rv32 = "clippy --no-default-features --features rv32 --target riscv32imac-unknown-none-elf -Zbuild-std=core,alloc,compiler_builtins -Zbuild-std-features=compiler-builtins-mem -Zjson-target-spec" +clippy_rv64 = "clippy --no-default-features --features rv64 --target riscv64gc-unknown-none-elf -Zbuild-std=core,alloc,compiler_builtins -Zbuild-std-features=compiler-builtins-mem -Zjson-target-spec" diff --git a/userland_extern_template/.gitignore b/userland_extern_template/.gitignore new file mode 100644 index 000000000..bf95c3389 --- /dev/null +++ b/userland_extern_template/.gitignore @@ -0,0 +1,4 @@ +/target +kernel-*.elf +*.img +/x86_64_uefi_pxe_boot diff --git a/userland_extern_template/Cargo.toml b/userland_extern_template/Cargo.toml new file mode 100644 index 000000000..b70363e4f --- /dev/null +++ b/userland_extern_template/Cargo.toml @@ -0,0 +1,29 @@ +[package] +name = "userland_template" +version = "0.1.0" +edition = "2021" + +# Not a member of the Awkernel workspace. +[workspace] + +[[bin]] +name = "awkernel" +path = "src/main.rs" + +[features] +debug = ["awkernel/debug"] +# Select exactly one architecture (the Makefile does it). +x86 = ["awkernel/x86"] +rv32 = ["awkernel/rv32"] +rv64 = ["awkernel/rv64"] + +[dependencies] +log = "0.4" + +[dependencies.awkernel] +path = "../kernel" +default-features = false + +[dependencies.awkernel_async_lib] +path = "../awkernel_async_lib" +default-features = false diff --git a/userland_extern_template/Makefile b/userland_extern_template/Makefile new file mode 100644 index 000000000..a8930820b --- /dev/null +++ b/userland_extern_template/Makefile @@ -0,0 +1,139 @@ +ifeq ($(RELEASE), 1) + OPT = --release + BUILD = release +else + BUILD = debug +endif + +RUSTC_MISC_ARGS = -C panic=unwind + +# RUSTFLAGS is set explicitly so rustflags inherited from parent +# .cargo/config.toml files do not leak into the build. +RUSTV = $(shell sed -n 's/^channel *= *"\(.*\)"/\1/p' rust-toolchain.toml) + +all: x86_64 riscv32 riscv64 + +check: check_x86_64 check_riscv32 check_riscv64 + +clippy: + cargo +$(RUSTV) clippy_x86 + cargo +$(RUSTV) clippy_rv32 + cargo +$(RUSTV) clippy_rv64 + +cargo: kernel-x86_64.elf + +FORCE: + +# x86_64 + +# Debug builds enable the `debug` feature, as in the root Makefile. +ifneq ($(RELEASE), 1) + X86_OPT = --features debug +endif + +x86_64: x86_64_uefi.img + +check_x86_64: FORCE + cargo +$(RUSTV) check_x86 + +kernel-x86_64.elf: FORCE + RUSTFLAGS="$(RUSTC_MISC_ARGS)" cargo +$(RUSTV) x86 $(OPT) $(X86_OPT) + cp target/x86_64-kernel/$(BUILD)/awkernel $@ + python3 scripts/embed_debug_info.py $@ + +# The boot-disk tool is installed once into target/x86bootdisk. +# Override the source with X86BOOTDISK_SRC="--git "; `make clean-tool` reinstalls it. +X86BOOTDISK_SRC ?= --path $(abspath ../x86bootdisk) +X86BOOTDISK = $(CURDIR)/target/x86bootdisk/bin/x86bootdisk + +$(X86BOOTDISK): + # Install from a neutral dir so the kernel target and build-std in .cargo/config.toml do not apply to this host tool. + cd / && RUSTUP_TOOLCHAIN=$(RUSTV) cargo install $(X86BOOTDISK_SRC) --root $(CURDIR)/target/x86bootdisk --no-default-features --features uefi + +x86_64_uefi.img: kernel-x86_64.elf | $(X86BOOTDISK) + $(X86BOOTDISK) --kernel $< --output $@ --pxe x86_64_uefi_pxe_boot --boot-type uefi + +OVMF_PATH := $(CURDIR)/target/ovmf/x64 + +QEMU_X86_ARGS= -drive if=pflash,format=raw,readonly=on,file=${OVMF_PATH}/code.fd +QEMU_X86_ARGS+= -drive if=pflash,format=raw,file=${OVMF_PATH}/vars_qemu.fd +QEMU_X86_ARGS+= -drive format=raw,file=x86_64_uefi.img +QEMU_X86_ARGS+= -machine q35 +QEMU_X86_ARGS+= -device qemu-xhci,id=xhci +QEMU_X86_ARGS+= -serial stdio -smp 4 -monitor telnet::5556,server,nowait +QEMU_X86_ARGS+= -m 4G -smp cpus=16 +QEMU_X86_ARGS+= -object memory-backend-ram,size=1G,id=m0 +QEMU_X86_ARGS+= -object memory-backend-ram,size=1G,id=m1 +QEMU_X86_ARGS+= -object memory-backend-ram,size=1G,id=m2 +QEMU_X86_ARGS+= -object memory-backend-ram,size=1G,id=m3 +QEMU_X86_ARGS+= -numa node,memdev=m0,cpus=0-3,nodeid=0 +QEMU_X86_ARGS+= -numa node,memdev=m1,cpus=4-7,nodeid=1 +QEMU_X86_ARGS+= -numa node,memdev=m2,cpus=8-11,nodeid=2 +QEMU_X86_ARGS+= -numa node,memdev=m3,cpus=12-15,nodeid=3 + +QEMU_X86_NET_ARGS=$(QEMU_X86_ARGS) +QEMU_X86_NET_ARGS+= -netdev user,id=net0,hostfwd=udp::4445-:2000 +QEMU_X86_NET_ARGS+= -device e1000e,netdev=net0,mac=12:34:56:11:22:33 +QEMU_X86_NET_ARGS+= -object filter-dump,id=net0,netdev=net0,file=packets_net0.pcap +QEMU_X86_NET_ARGS+= -netdev user,id=net1,hostfwd=udp::4446-:2001 +QEMU_X86_NET_ARGS+= -device virtio-net-pci,netdev=net1,mac=12:34:56:11:22:34,disable-legacy=on,disable-modern=off,speed=1000,duplex=full +QEMU_X86_NET_ARGS+= -object filter-dump,id=net1,netdev=net1,file=packets_net1.pcap + +qemu-x86_64-net: + cp ${OVMF_PATH}/vars.fd ${OVMF_PATH}/vars_qemu.fd + qemu-system-x86_64 $(QEMU_X86_NET_ARGS) + +qemu-x86_64: + cp ${OVMF_PATH}/vars.fd ${OVMF_PATH}/vars_qemu.fd + qemu-system-x86_64 $(QEMU_X86_ARGS) + +debug-x86_64: + cp ${OVMF_PATH}/vars.fd ${OVMF_PATH}/vars_qemu.fd + qemu-system-x86_64 $(QEMU_X86_ARGS) -s -S + +qemu-x86_64-nographic: + cp ${OVMF_PATH}/vars.fd ${OVMF_PATH}/vars_qemu.fd + qemu-system-x86_64 $(QEMU_X86_ARGS) -nographic + +# riscv32 + +riscv32: + RUSTFLAGS="$(RUSTC_MISC_ARGS)" cargo +$(RUSTV) rv32 $(OPT) + +check_riscv32: FORCE + cargo +$(RUSTV) check_rv32 + +qemu-riscv32: + qemu-system-riscv32 -machine virt -bios none -kernel target/riscv32imac-unknown-none-elf/$(BUILD)/awkernel -m 1G -nographic -smp 4 -monitor telnet::5556,server,nowait + +# riscv64 + +riscv64: + RUSTFLAGS="$(RUSTC_MISC_ARGS)" cargo +$(RUSTV) rv64 $(OPT) + +check_riscv64: FORCE + cargo +$(RUSTV) check_rv64 + +qemu-riscv64: + qemu-system-riscv64 -machine virt -bios none -kernel target/riscv64gc-unknown-none-elf/$(BUILD)/awkernel -m 1G -nographic -smp 4 -monitor telnet::5556,server,nowait + +# Format + +fmt: FORCE + cargo +$(RUSTV) fmt + +# Clean + +clean: FORCE + rm -rf *.elf *.img x86_64_uefi_pxe_boot + cargo clean + +clean-tool: FORCE + rm -rf target/x86bootdisk + +## QEMU Monitor + +monitor: FORCE + telnet localhost 5556 + +.PHONY: all check clippy cargo x86_64 riscv32 riscv64 qemu-x86_64 qemu-x86_64-net qemu-x86_64-nographic debug-x86_64 qemu-riscv32 qemu-riscv64 diff --git a/userland_extern_template/README.md b/userland_extern_template/README.md new file mode 100644 index 000000000..6ba2aaa16 --- /dev/null +++ b/userland_extern_template/README.md @@ -0,0 +1,37 @@ +# userland_template + +Template for a userland that lives outside the Awkernel workspace and depends +on the `awkernel` kernel crate. One crate supports x86_64, riscv32 and riscv64 +(AArch64 is not supported yet: the kernel does not publish its linker script +and `boot.S` for out-of-tree crates). + +- `kernel/build.rs` copies the architecture's linker script to `OUT_DIR` as + `awkernel.ld` and adds it to the library search path. +- `build.rs` here passes `-Tawkernel.ld` to the linker. +- `Cargo.toml` features `x86`, `rv32`, `rv64` (selected by the cargo aliases in `.cargo/config.toml`) forward to the matching + `awkernel` feature; enable exactly one. +- `targets/x86_64-kernel.json` is the x86_64 kernel target; riscv uses the + built-in `riscv32imac`/`riscv64gc` bare-metal targets. +- `src/main.rs` marks its entry point with `#[awkernel::main]`. +- `scripts/embed_debug_info.py` is used for x86_64 builds (needs `pyelftools`). + +## Build + +The interface mirrors the top-level `Makefile` (`RELEASE=1` for release): + +```sh +make # x86_64, riscv32 and riscv64 +make x86_64 # kernel-x86_64.elf, x86_64_uefi.img, x86_64_uefi_pxe_boot/ +make riscv64 # or riscv32 +make check clippy fmt # also check_x86_64, check_riscv32, check_riscv64 +make qemu-x86_64 # run the image built by `make x86_64` (does not rebuild); also qemu-x86_64-net, qemu-x86_64-nographic, debug-x86_64 +make qemu-riscv64 # or qemu-riscv32; run the ELF built by `make riscv64` +make clean # clean-tool also removes the installed x86bootdisk +``` + +`kernel/build.rs` assembles `mpboot.S` for x86_64; it uses `x86_64-elf-gcc` if +installed, else `gcc` (override with `CROSS_COMPILE`). + +The x86_64 image is made by the `x86bootdisk` tool, installed once into +`target/x86bootdisk` with `cargo install`. Its source defaults to +`../x86bootdisk`; override with `X86BOOTDISK_SRC="--git "`. diff --git a/userland_extern_template/build.rs b/userland_extern_template/build.rs new file mode 100644 index 000000000..39c77ce74 --- /dev/null +++ b/userland_extern_template/build.rs @@ -0,0 +1,4 @@ +fn main() { + // `awkernel.ld` is provided by the kernel crate's build script. + println!("cargo:rustc-link-arg-bins=-Tawkernel.ld"); +} diff --git a/userland_extern_template/rust-toolchain.toml b/userland_extern_template/rust-toolchain.toml new file mode 100644 index 000000000..859b7d487 --- /dev/null +++ b/userland_extern_template/rust-toolchain.toml @@ -0,0 +1,2 @@ +[toolchain] +channel = "nightly-2026-07-20" diff --git a/userland_extern_template/scripts/embed_debug_info.py b/userland_extern_template/scripts/embed_debug_info.py new file mode 100644 index 000000000..643d2f3e4 --- /dev/null +++ b/userland_extern_template/scripts/embed_debug_info.py @@ -0,0 +1,44 @@ +import argparse +from elftools.elf.elffile import ELFFile + +def main(): + parser = argparse.ArgumentParser(description="Copy the data of each debug section to the rodata-segment") + parser.add_argument("kernel", help="Kernel ELF file") + args = parser.parse_args() + + with open(args.kernel, "r+b") as f: + elf = ELFFile(f) + + # Find the segment that contains ".debug" section assuming it is defined in the linker script + debug_section = elf.get_section_by_name(".debug") + assert debug_section is not None + target_segment = next((segment for segment in elf.iter_segments() if segment.section_in_segment(debug_section)), None) + if target_segment is None: return + + # Copy the data of each debug section to the target segment + debug_sections = {section.name : section for section in elf.iter_sections() if section.name.startswith(".debug")} + symtab = elf.get_section_by_name(".symtab") + + f.seek(0) + image = bytearray(f.read()) + + for name, section in debug_sections.items(): + start_sym = symtab.get_symbol_by_name(f'__{name[1:]}_start') + size_sym = symtab.get_symbol_by_name(f'__{name[1:]}_size') + + if start_sym is None or size_sym is None: + continue + + addr_start_sym = start_sym[0]['st_value'] + addr_size_sym = size_sym[0]['st_value'] + + offset = target_segment['p_offset'] + (addr_start_sym - target_segment['p_vaddr']) + data = section.data() + assert len(data) == addr_size_sym + image[offset:offset+len(data)] = data + + f.seek(0) + f.write(image) + +if __name__ == '__main__': + main() \ No newline at end of file diff --git a/userland_extern_template/src/main.rs b/userland_extern_template/src/main.rs new file mode 100644 index 000000000..9d1133ac9 --- /dev/null +++ b/userland_extern_template/src/main.rs @@ -0,0 +1,18 @@ +#![no_std] +#![no_main] + +extern crate alloc; +// Links the kernel's entry points and panic handler. +extern crate awkernel; + +use alloc::borrow::Cow; +use awkernel_async_lib::sleep; +use core::time::Duration; + +#[awkernel::main] +async fn main() -> Result<(), Cow<'static, str>> { + loop { + log::info!("Hello from an out-of-tree userland!"); + sleep(Duration::from_secs(1)).await; + } +} diff --git a/userland_extern_template/targets/x86_64-kernel.json b/userland_extern_template/targets/x86_64-kernel.json new file mode 100644 index 000000000..3a8cc65ce --- /dev/null +++ b/userland_extern_template/targets/x86_64-kernel.json @@ -0,0 +1,22 @@ +{ + "llvm-target": "x86_64-unknown-none", + "data-layout": "e-m:e-p270:32:32-p271:32:32-p272:64:64-i64:64-i128:128-f80:128-n8:16:32:64-S128", + "arch": "x86_64", + "target-endian": "little", + "target-pointer-width": 64, + "target-c-int-width": 32, + "os": "none", + "executables": true, + "linker-flavor": "ld.lld", + "linker": "rust-lld", + "disable-redzone": true, + "post-link-args": { + "ld.lld": [ + "-z", + "norelro", + "--static", + "--gc-sections" + ] + }, + "vendor": "TIER IV, inc." +} \ No newline at end of file From 0ada1466e24bdf3bcb2d3fdd415b573802cab51a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rom=C3=A1n=20C=C3=A1rdenas=20Rodr=C3=ADguez?= Date: Fri, 23 Oct 2026 11:22:48 +0900 Subject: [PATCH 4/5] feat(aarch64): add assembly via global_asm! --- Makefile | 27 +--- kernel/asm/aarch64/boot.S | 4 +- kernel/build.rs | 128 ++++++++++++------ kernel/src/arch/aarch64/kernel_main.rs | 12 +- targets/aarch64-kernel.json | 5 +- userland_extern_template/.cargo/config.toml | 3 + .../targets/aarch64-kernel.json | 28 ++++ 7 files changed, 134 insertions(+), 73 deletions(-) create mode 100644 userland_extern_template/targets/aarch64-kernel.json diff --git a/Makefile b/Makefile index 7056116af..8f9b6f703 100644 --- a/Makefile +++ b/Makefile @@ -6,37 +6,22 @@ else OPT = --features debug endif -# 2MiB Stack -STACKSIZE = 1024 * 1024 * 2 - ifeq ($(BSP),raspi3) RUSTC_MISC_ARGS = -C target-cpu=cortex-a53 - INITADDR = 0x80000 AARCH64_OPT = $(OPT) --features raspi else ifeq ($(BSP),raspi4) RUSTC_MISC_ARGS = -C target-cpu=cortex-a72 - INITADDR = 0x80000 AARCH64_OPT = $(OPT) --features raspi else ifeq ($(BSP),raspi5) RUSTC_MISC_ARGS = -C target-cpu=cortex-a76 - INITADDR = 0x80000 AARCH64_OPT = $(OPT) --features raspi5 else ifeq ($(BSP),aarch64_virt) RUSTC_MISC_ARGS = -C target-cpu=cortex-a72 - INITADDR = 0x40080000 AARCH64_OPT = $(OPT) --features aarch64_virt endif RUSTC_MISC_ARGS += -C panic=unwind -ASM_FILE_DEP_AARCH64=kernel/asm/aarch64/exception.S -ASM_FILE_AARCH64=kernel/asm/aarch64/boot.S -ASM_OBJ_AARCH64=boot_aarch64.o - -ifndef $(CC) - CC = clang -endif - ifndef $(LD) LD = rust-lld -flavor gnu # LD = ld.gold @@ -47,8 +32,6 @@ QEMUPORT=5556 # Link scripts LINKERDIR=kernel/ld -AARCH64_LD=$(LINKERDIR)/aarch64-link.lds -AARCH64_BSP_LD=$(LINKERDIR)/aarch64-link-bsp.lds X86_64_LD=$(LINKERDIR)/x86_64-link.lds RV32_LD=$(LINKERDIR)/rv32-link.lds RV64_LD=$(LINKERDIR)/rv64-link.lds @@ -82,19 +65,13 @@ aarch64: kernel8.img check_aarch64: FORCE cargo +$(RUSTV) check_aarch64 $(AARCH64_OPT) -target/aarch64-kernel/$(BUILD)/awkernel: $(ASM_OBJ_AARCH64) $(AARCH64_BSP_LD) FORCE +target/aarch64-kernel/$(BUILD)/awkernel: FORCE RUSTFLAGS="$(RUSTC_MISC_ARGS)" cargo +$(RUSTV) aarch64 $(AARCH64_OPT) python3 scripts/embed_debug_info.py $@ kernel8.img: target/aarch64-kernel/$(BUILD)/awkernel rust-objcopy -O binary target/aarch64-kernel/$(BUILD)/awkernel $@ -$(ASM_OBJ_AARCH64): $(ASM_FILE_AARCH64) $(ASM_FILE_DEP_AARCH64) - $(CC) --target=aarch64-elf -c $< -o $@ -DSTACKSIZE="$(STACKSIZE)" - -$(AARCH64_BSP_LD): $(AARCH64_LD) - sed "s/#INITADDR#/$(INITADDR)/" $(AARCH64_LD) > $@ - QEMU_AARCH64_ARGS= -kernel kernel8.img QEMU_AARCH64_ARGS+= -serial stdio QEMU_AARCH64_ARGS+= -monitor telnet::$(QEMUPORT),server,nowait # -d int @@ -266,7 +243,7 @@ fmt: FORCE # Clean clean: FORCE - rm -rf *.o *.elf $(AARCH64_BSP_LD) *.img x86_64_uefi_pxe_boot + rm -rf *.o *.elf *.img x86_64_uefi_pxe_boot cargo clean diff --git a/kernel/asm/aarch64/boot.S b/kernel/asm/aarch64/boot.S index c3b5092e1..b99f052de 100644 --- a/kernel/asm/aarch64/boot.S +++ b/kernel/asm/aarch64/boot.S @@ -10,7 +10,7 @@ _start: and x2, x2, #0xFF add x3, x2, #1 - mov x4, #(STACKSIZE) + mov x4, #({STACKSIZE}) mul x3, x3, x4 ldr x1, =__stack_memory @@ -91,5 +91,3 @@ _start: .loop: wfe b .loop - -#include "exception.S" diff --git a/kernel/build.rs b/kernel/build.rs index 8bf14bb2c..cef2429de 100644 --- a/kernel/build.rs +++ b/kernel/build.rs @@ -6,8 +6,10 @@ //! `-Tawkernel.ld`, e.g. with `println!("cargo:rustc-link-arg-bins=-Tawkernel.ld")` //! in its own `build.rs`. //! -//! AArch64 is not covered yet: its script is a template (`#INITADDR#`) and it -//! also needs `boot.S` assembled and linked. +//! The AArch64 script is a template: `#INITADDR#` is replaced by the load +//! address of the selected board (`raspi`, `raspi5` or `aarch64_virt`), or by +//! the `INITADDR` environment variable if no board is selected. +//! Its startup code (`boot.S`) is assembled by `global_asm!`. use std::{ env, fs, @@ -15,34 +17,81 @@ use std::{ process::Command, }; +fn has_feature(name: &str) -> bool { + env::var_os(format!("CARGO_FEATURE_{name}")).is_some() +} + +fn run_command(cmd: &mut Command) { + let status = cmd + .status() + .unwrap_or_else(|e| panic!("failed to run {cmd:?}: {e}")); + assert!(status.success(), "{cmd:?} failed"); +} + fn main() { println!("cargo:rerun-if-changed=build.rs"); println!("cargo:rerun-if-changed=ld"); - println!("cargo:rerun-if-env-changed=CROSS_COMPILE"); - if env::var_os("CARGO_FEATURE_X86").is_some() { - build_x86_64_mpboot(); + if has_feature("AARCH64") { + aarch64(); + } else if has_feature("X86") { + x86_64(); + } else if has_feature("RV32") { + riscv32(); + } else if has_feature("RV64") { + riscv64(); } +} + +fn x86_64() { + println!("cargo:rerun-if-env-changed=CROSS_COMPILE"); - let script = if env::var_os("CARGO_FEATURE_X86").is_some() { - "x86_64-link.lds" - } else if env::var_os("CARGO_FEATURE_RV32").is_some() { - "rv32-link.lds" - } else if env::var_os("CARGO_FEATURE_RV64").is_some() { - "rv64-link.lds" + build_x86_64_mpboot(); + publish_linker_script("x86_64-link.lds", None); +} + +fn aarch64() { + println!("cargo:rerun-if-env-changed=INITADDR"); + + // The script is a template that needs the load address of the board. + // Without a board feature, it is taken from the `INITADDR` environment variable. + let init_addr = if has_feature("AARCH64_VIRT") { + "0x40080000".to_string() + } else if has_feature("RASPI") || has_feature("RASPI5") { + "0x80000".to_string() + } else if let Ok(addr) = env::var("INITADDR") { + addr } else { + // Without a board or `INITADDR` (e.g. a plain `cargo check`), no script is published. return; }; + publish_linker_script("aarch64-link.lds", Some(("#INITADDR#", &init_addr))); +} + +fn riscv32() { + publish_linker_script("rv32-link.lds", None); +} + +fn riscv64() { + publish_linker_script("rv64-link.lds", None); +} + +/// Copies `ld/