From 30e382d7df56f603a2e68ec463fba5369478f41b Mon Sep 17 00:00:00 2001 From: Jakob Fuchs Date: Mon, 16 Jun 2025 23:08:05 +0200 Subject: [PATCH 1/9] first draft --- kernel/build.rs | 5 +++ kernel/src/lib.rs | 3 ++ kernel/src/modules.rs | 21 ++++++++++ kernel/src/modules/kernelmods.rs | 41 +++++++++++++++++++ .../src/modules/kernelmods/sample_module.rs | 26 ++++++++++++ 5 files changed, 96 insertions(+) create mode 100644 kernel/src/modules.rs create mode 100644 kernel/src/modules/kernelmods.rs create mode 100644 kernel/src/modules/kernelmods/sample_module.rs diff --git a/kernel/build.rs b/kernel/build.rs index 92540aa..c28586e 100644 --- a/kernel/build.rs +++ b/kernel/build.rs @@ -18,6 +18,11 @@ fn main() { generate_syscall_map("src").expect("Failed to generate syscall map."); } +fn generate_modules() -> Result<(), std::io::Error> { + let mut file = File::create("../include/syscalls.map.gen.h")?; + Ok(()) +} + fn generate_syscall_map>(root: P) -> Result<(), std::io::Error> { let syscalls = collect_syscalls(root); diff --git a/kernel/src/lib.rs b/kernel/src/lib.rs index 59dfc90..55da976 100644 --- a/kernel/src/lib.rs +++ b/kernel/src/lib.rs @@ -2,6 +2,7 @@ //! The kernel is organized as a microkernel. #![cfg_attr(all(not(test), not(doctest), not(doc), not(kani)), no_std)] +extern crate alloc; mod macros; #[macro_use] @@ -16,6 +17,8 @@ mod syscalls; mod time; mod uspace; +mod modules; + use core::ffi::c_char; /// The memory map entry type. diff --git a/kernel/src/modules.rs b/kernel/src/modules.rs new file mode 100644 index 0000000..600b8ce --- /dev/null +++ b/kernel/src/modules.rs @@ -0,0 +1,21 @@ +pub mod kernelmods; + +use alloc::boxed::Box; +use alloc::vec::Vec; +use crate::utils::KernelError; + +trait KernelModule { + fn init(&mut self) -> Result<(), KernelError>; + fn exit(&mut self) -> Result<(), KernelError>; + fn name(&self) -> &'static str; +} + + +fn init_modules() -> Result<(), KernelError> { + kernelmods::init_modules() +} + +fn exit_modules() -> Result<(), KernelError> { + kernelmods::exit_modules() +} + diff --git a/kernel/src/modules/kernelmods.rs b/kernel/src/modules/kernelmods.rs new file mode 100644 index 0000000..b85215e --- /dev/null +++ b/kernel/src/modules/kernelmods.rs @@ -0,0 +1,41 @@ +mod sample_module; + +use crate::modules::kernelmods::sample_module::SampleModule; +use crate::modules::KernelModule; +use crate::sync::spinlock::SpinLock; +use crate::utils::KernelError; + +//Lock to guarantee race condition free access +static LOCK: SpinLock = SpinLock::new(); + +//Generate per Module +static mut MODULE_A: SampleModule = SampleModule::new(); +static mut MODULE_B: SampleModule = SampleModule::new(); + + + +pub(super) fn init_modules() -> Result<(), KernelError> { + //SAFETY: All kernel modules are private to this generated file and are secured using a common lock, therefor no race conditions can appear + unsafe { + LOCK.lock(); + let res = MODULE_A.init(); + if res.is_err() {LOCK.unlock();return res;} + let res = MODULE_B.init(); + if res.is_err() {LOCK.unlock();return res;} + LOCK.unlock(); + } + Ok(()) +} + +pub(super) fn exit_modules() -> Result<(), KernelError> { + //SAFETY: All kernel modules are private to this generated file and are secured using a common lock, therefor no race conditions can appear + unsafe { + LOCK.lock(); + let res = MODULE_A.exit(); + if res.is_err() {LOCK.unlock();return res;} + let res = MODULE_B.exit(); + if res.is_err() {LOCK.unlock();return res;} + LOCK.unlock(); + } + Ok(()) +} \ No newline at end of file diff --git a/kernel/src/modules/kernelmods/sample_module.rs b/kernel/src/modules/kernelmods/sample_module.rs new file mode 100644 index 0000000..8d1d43f --- /dev/null +++ b/kernel/src/modules/kernelmods/sample_module.rs @@ -0,0 +1,26 @@ +use crate::modules::KernelModule; +use crate::utils::KernelError; + +#[derive(Default)] +pub(super) struct SampleModule { + value: u32 +} + +impl KernelModule for SampleModule { + fn init(&mut self) -> Result<(), KernelError> { + Ok(()) + } + + fn exit(&mut self) -> Result<(), KernelError> { + Ok(()) + } + + fn name(&self) -> &'static str { + "sample Module" + } +} +impl SampleModule { + pub(crate) const fn new() -> Self { + SampleModule { value: 0 } + } +} \ No newline at end of file From 846ebc69e3b5127d95c475482fa0c2ea8a9e0638 Mon Sep 17 00:00:00 2001 From: Jakob Fuchs Date: Thu, 18 Dec 2025 18:24:02 +0100 Subject: [PATCH 2/9] proc macros --- kernel/macros/Cargo.toml | 2 +- kernel/macros/src/lib.rs | 62 ++++++++++++++- kernel/src/lib.rs | 78 +++++++++---------- kernel/src/modules.rs | 2 - .../src/modules/kernelmods/sample_module.rs | 29 ++----- 5 files changed, 106 insertions(+), 67 deletions(-) diff --git a/kernel/macros/Cargo.toml b/kernel/macros/Cargo.toml index bf51acc..911ff88 100644 --- a/kernel/macros/Cargo.toml +++ b/kernel/macros/Cargo.toml @@ -8,5 +8,5 @@ proc-macro = true [dependencies] quote = "1.0.40" -syn = "2.0.100" +syn = { version="2.0.100", features = ["full"] } proc-macro2 = "1.0.95" diff --git a/kernel/macros/src/lib.rs b/kernel/macros/src/lib.rs index 903945a..18417f2 100644 --- a/kernel/macros/src/lib.rs +++ b/kernel/macros/src/lib.rs @@ -1,5 +1,7 @@ +use quote::quote; use quote::{ToTokens, format_ident}; -use syn::parse_macro_input; +use syn::{parse_macro_input, FnArg}; +use syn::ItemFn; use proc_macro2::TokenStream; @@ -220,3 +222,61 @@ fn syscall_handler_fn(item: &syn::ItemFn) -> TokenStream { #(#size_checks)* } } + +#[proc_macro_attribute] +pub fn kernelmod_call(_attr: proc_macro::TokenStream, item: proc_macro::TokenStream) -> proc_macro::TokenStream { + let input_fn = parse_macro_input!(item as ItemFn); + + let fn_name = &input_fn.sig.ident; + let wrapper_name = format_ident!("{}_wrapper", fn_name); + let fn_body = &input_fn.block; + let vis = &input_fn.vis; + let sig = &input_fn.sig; + + // Extract argument types and names + let mut arg_types = Vec::new(); + let mut arg_names = Vec::new(); + + for input in &input_fn.sig.inputs { + if let FnArg::Typed(pat_type) = input { + arg_types.push(&pat_type.ty); + arg_names.push(&pat_type.pat); + } + } + + // 1. Create a C-compatible struct for safely casting the pointer + let args_struct_name = format_ident!("_{}_Args", fn_name); + let args_struct = quote! { + #[repr(C)] + struct #args_struct_name { + #(#arg_names: #arg_types),* + } + }; + + + let wrapper_fn = quote! { + #[doc(hidden)] + pub unsafe fn #wrapper_name(ptr: *const u8) { + let args = &*(ptr as *const #args_struct_name); + #fn_name( #( args.#arg_names ),* ); + } + }; + + // 3. Output everything: The original function + The wrapper + The struct + let output = quote! { + #args_struct + + #wrapper_fn + + #vis #sig { + #fn_body + } + }; + + proc_macro::TokenStream::from(output) +} + +#[proc_macro_attribute] +pub fn kernel_init(_attr: proc_macro::TokenStream, item: proc_macro::TokenStream) -> proc_macro::TokenStream { + item +} \ No newline at end of file diff --git a/kernel/src/lib.rs b/kernel/src/lib.rs index 55da976..517345a 100644 --- a/kernel/src/lib.rs +++ b/kernel/src/lib.rs @@ -1,26 +1,27 @@ //! This is the default kernel of the osiris operating system. //! The kernel is organized as a microkernel. -#![cfg_attr(all(not(test), not(doctest), not(doc), not(kani)), no_std)] -extern crate alloc; +#![cfg_attr(freestanding, no_std)] -mod macros; #[macro_use] -mod utils; -mod faults; -mod mem; -mod print; -mod sched; -mod services; -mod sync; -mod syscalls; -mod time; -mod uspace; - -mod modules; +pub mod macros; +#[macro_use] +pub mod utils; +pub mod faults; +pub mod mem; +pub mod print; +pub mod sched; +pub mod services; +pub mod sync; +pub mod syscalls; +pub mod time; +pub mod uspace; +pub mod modules; use core::ffi::c_char; +use hal::Machinelike; + /// The memory map entry type. /// /// This structure shall be compatible with the multiboot_memory_map_t struct at @@ -56,9 +57,11 @@ pub struct BootInfo { #[unsafe(no_mangle)] pub unsafe extern "C" fn kernel_init(boot_info: *const BootInfo) -> ! { // Initialize basic hardware and the logging system. - hal::init(); + hal::Machine::init(); + + //hal::asm::disable_interrupts(); - hal::bench_start(); + hal::Machine::bench_start(); let boot_info = unsafe { &*boot_info }; @@ -66,40 +69,33 @@ pub unsafe extern "C" fn kernel_init(boot_info: *const BootInfo) -> ! { print::print_boot_info(boot_info); // Initialize the memory allocator. - if let Err(_e) = mem::init_memory(boot_info) { - panic!("[Kernel] Error: failed to initialize memory allocator."); + if let Err(e) = mem::init_memory(boot_info) { + panic!( + "[Kernel] Error: failed to initialize memory allocator. Error: {:?}", + e + ); } // Initialize the services. - if let Err(_e) = services::init_services() { - panic!("[Kernel] Error: failed to initialize services."); + if let Err(e) = services::init_services() { + panic!( + "[Kernel] Error: failed to initialize services. Error: {:?}", + e + ); } - let (cyc, ns) = hal::bench_end(); + sched::enable_scheduler(false); + + let (cyc, ns) = hal::Machine::bench_end(); kprintln!( "[Osiris] Init took {} cycles taking {} ms", cyc, ns / 1e6f32 ); - sched::enable_scheduler(); - - loop {} -} - -/// The panic handler. -#[cfg(all(not(test), not(doctest), not(doc), target_os = "none"))] -#[panic_handler] -fn panic(info: &core::panic::PanicInfo) -> ! { - kprintln!("**************************** PANIC ****************************"); - kprintln!(""); - kprintln!("Message: {}", info.message()); + sched::enable_scheduler(true); - if let Some(location) = info.location() { - kprintln!("Location: {}:{}", location.file(), location.line()); + loop { + hal::asm::nop!(); } - - kprintln!("**************************** PANIC ****************************"); - - hal::panic::panic_handler(info); -} +} \ No newline at end of file diff --git a/kernel/src/modules.rs b/kernel/src/modules.rs index 600b8ce..6bc7ec5 100644 --- a/kernel/src/modules.rs +++ b/kernel/src/modules.rs @@ -1,7 +1,5 @@ pub mod kernelmods; -use alloc::boxed::Box; -use alloc::vec::Vec; use crate::utils::KernelError; trait KernelModule { diff --git a/kernel/src/modules/kernelmods/sample_module.rs b/kernel/src/modules/kernelmods/sample_module.rs index 8d1d43f..e740892 100644 --- a/kernel/src/modules/kernelmods/sample_module.rs +++ b/kernel/src/modules/kernelmods/sample_module.rs @@ -1,26 +1,11 @@ -use crate::modules::KernelModule; -use crate::utils::KernelError; +use macros::{kernel_init, kernelmod_call}; -#[derive(Default)] -pub(super) struct SampleModule { - value: u32 +#[kernel_init] +fn init() { + } -impl KernelModule for SampleModule { - fn init(&mut self) -> Result<(), KernelError> { - Ok(()) - } - - fn exit(&mut self) -> Result<(), KernelError> { - Ok(()) - } - - fn name(&self) -> &'static str { - "sample Module" - } -} -impl SampleModule { - pub(crate) const fn new() -> Self { - SampleModule { value: 0 } - } +#[kernelmod_call] +fn call(target: i32) { + } \ No newline at end of file From 7f0c0bead8cf5f523124a975df27ef30fc06f691 Mon Sep 17 00:00:00 2001 From: Jakob Fuchs Date: Tue, 13 Jan 2026 21:44:17 +0100 Subject: [PATCH 3/9] proc macro draft for kernel mods --- kernel/build.rs | 12 +- kernel/macros/src/lib.rs | 302 ++++++++++++++++-- kernel/src/modules/kernelmods.rs | 20 +- .../src/modules/kernelmods/sample_module.rs | 25 +- 4 files changed, 307 insertions(+), 52 deletions(-) diff --git a/kernel/build.rs b/kernel/build.rs index 80f4321..3fbe79f 100644 --- a/kernel/build.rs +++ b/kernel/build.rs @@ -1,4 +1,4 @@ -use std::{collections::HashMap, fs::File, path::Path}; +use std::{collections::HashMap, env, fs::File, path::Path}; extern crate rand; extern crate syn; @@ -18,6 +18,8 @@ fn main() { generate_syscall_map("src/syscalls").expect("Failed to generate syscall map."); + generate_modules().expect("Failed to generate Kernelmodules."); + // Get linker script from environment variable if let Ok(linker_script) = std::env::var("DEP_HAL_LINKER_SCRIPT") { println!("cargo:rustc-link-arg=-T{linker_script}"); @@ -30,8 +32,14 @@ fn main() { } } + + fn generate_modules() -> Result<(), std::io::Error> { - let mut file = File::create("../include/syscalls.map.gen.h")?; + let manifest_dir = env::var("CARGO_MANIFEST_DIR").unwrap(); + let modules_dir = Path::new(&manifest_dir).join("src/modules/kernelmods"); + let output_file = modules_dir.parent().unwrap().join("kernelmods.rs"); + + Ok(()) } diff --git a/kernel/macros/src/lib.rs b/kernel/macros/src/lib.rs index 18417f2..fbc6d5b 100644 --- a/kernel/macros/src/lib.rs +++ b/kernel/macros/src/lib.rs @@ -1,9 +1,10 @@ -use quote::quote; +use quote::{quote, quote_spanned}; use quote::{ToTokens, format_ident}; -use syn::{parse_macro_input, FnArg}; +use syn::{parse_macro_input, parse_quote, Error, FnArg, GenericArgument, Pat, PatType, PathArguments, ReturnType, Type, TypeReference, TypeSlice}; use syn::ItemFn; use proc_macro2::TokenStream; +use syn::spanned::Spanned; #[proc_macro_attribute] pub fn service( @@ -223,60 +224,295 @@ fn syscall_handler_fn(item: &syn::ItemFn) -> TokenStream { } } + #[proc_macro_attribute] pub fn kernelmod_call(_attr: proc_macro::TokenStream, item: proc_macro::TokenStream) -> proc_macro::TokenStream { - let input_fn = parse_macro_input!(item as ItemFn); + let input = parse_macro_input!(item as ItemFn); + + match generate_wrapper(input) { + Ok(tokens) => proc_macro::TokenStream::from(tokens), + Err(e) => proc_macro::TokenStream::from(e.to_compile_error()), + } +} + +fn generate_wrapper(input: ItemFn) -> Result { + let fn_name = &input.sig.ident; + let fn_vis = &input.vis; + let wrapper_name = syn::Ident::new(&format!("{}_wrapper", fn_name), fn_name.span()); + let args_struct_name = syn::Ident::new(&format!("{}Args", fn_name), fn_name.span()); - let fn_name = &input_fn.sig.ident; - let wrapper_name = format_ident!("{}_wrapper", fn_name); - let fn_body = &input_fn.block; - let vis = &input_fn.vis; - let sig = &input_fn.sig; + validate_return_type(&input.sig.output)?; - // Extract argument types and names - let mut arg_types = Vec::new(); + let mut arg_fields = Vec::new(); let mut arg_names = Vec::new(); + let mut arg_reconstructions = Vec::new(); + + for arg in &input.sig.inputs { + let (name, ty) = match arg { + FnArg::Typed(PatType { pat, ty, .. }) => { + let name = match &**pat { + Pat::Ident(ident) => &ident.ident, + _ => return Err(Error::new_spanned(pat, "Expected simple identifier pattern")), + }; + (name, ty) + } + FnArg::Receiver(_) => { + return Err(Error::new_spanned(arg, "Methods with 'self' are not supported")); + } + }; + + arg_names.push(name.clone()); - for input in &input_fn.sig.inputs { - if let FnArg::Typed(pat_type) = input { - arg_types.push(&pat_type.ty); - arg_names.push(&pat_type.pat); + match validate_and_generate_field(name, ty)? { + ArgFieldInfo::Direct(field, reconstruction) => { + arg_fields.push(field); + arg_reconstructions.push(reconstruction); + } + ArgFieldInfo::Slice(ptr_field, len_field, reconstruction) => { + arg_fields.push(ptr_field); + arg_fields.push(len_field); + arg_reconstructions.push(reconstruction); + } } } - // 1. Create a C-compatible struct for safely casting the pointer - let args_struct_name = format_ident!("_{}_Args", fn_name); - let args_struct = quote! { + let return_handling = generate_return_handling(&input.sig.output)?; + + let original_fn = &input; + + let output = quote! { + #original_fn + #[repr(C)] struct #args_struct_name { - #(#arg_names: #arg_types),* + #(#arg_fields),* + } + + #fn_vis unsafe extern "C" fn #wrapper_name(args_ptr: *const u8) -> usize { + let args = &*(args_ptr as *const #args_struct_name); + + #(#arg_reconstructions)* + + let result = #fn_name(#(#arg_names),*); + + #return_handling } }; + Ok(output) +} + +enum ArgFieldInfo { + Direct(proc_macro2::TokenStream, proc_macro2::TokenStream), + Slice(proc_macro2::TokenStream, proc_macro2::TokenStream, proc_macro2::TokenStream), +} + +fn validate_and_generate_field( + name: &syn::Ident, + ty: &Type, +) -> Result { + match ty { + Type::Path(type_path) => { + let type_name = type_path.path.segments.last() + .ok_or_else(|| Error::new_spanned(ty, "Invalid type path"))? + .ident + .to_string(); + + if is_valid_primitive(&type_name) { + let field = quote! { #name: #ty }; + let reconstruction = quote! { let #name = args.#name; }; + return Ok(ArgFieldInfo::Direct(field, reconstruction)); + } + + let field = quote! { #name: #ty }; + let reconstruction = quote! { + let #name = args.#name; + let _: fn() = || { fn assert_copy() {} assert_copy::<#ty>(); }; + }; - let wrapper_fn = quote! { - #[doc(hidden)] - pub unsafe fn #wrapper_name(ptr: *const u8) { - let args = &*(ptr as *const #args_struct_name); - #fn_name( #( args.#arg_names ),* ); + Ok(ArgFieldInfo::Direct(field, reconstruction)) } - }; + Type::Reference(TypeReference { elem, mutability, .. }) => { + if mutability.is_some() { + return Err(Error::new_spanned( + ty, + "Mutable references are not supported. Only immutable references are allowed." + )); + } - // 3. Output everything: The original function + The wrapper + The struct - let output = quote! { - #args_struct - - #wrapper_fn + match &**elem { + Type::Slice(TypeSlice { elem: slice_elem, .. }) => { + let ptr_name = syn::Ident::new(&format!("{}_ptr", name), name.span()); + let len_name = syn::Ident::new(&format!("{}_len", name), name.span()); - #vis #sig { - #fn_body + let ptr_field = quote! { #ptr_name: *const #slice_elem }; + let len_field = quote! { #len_name: usize }; + let reconstruction = quote! { + let #name = unsafe { + core::slice::from_raw_parts(args.#ptr_name, args.#len_name) + }; + }; + + Ok(ArgFieldInfo::Slice(ptr_field, len_field, reconstruction)) + } + Type::Path(path) => { + // Check for &str + if path.path.is_ident("str") { + let ptr_name = syn::Ident::new(&format!("{}_ptr", name), name.span()); + let len_name = syn::Ident::new(&format!("{}_len", name), name.span()); + + let ptr_field = quote! { #ptr_name: *const u8 }; + let len_field = quote! { #len_name: usize }; + let reconstruction = quote! { + let #name = unsafe { + let bytes = core::slice::from_raw_parts(args.#ptr_name, args.#len_name); + core::str::from_utf8_unchecked(bytes) + }; + }; + + return Ok(ArgFieldInfo::Slice(ptr_field, len_field, reconstruction)); + } + + // Reference to struct - store as thin pointer + let field = quote! { #name: *const #path }; + let reconstruction = quote! { let #name = unsafe { &*args.#name }; }; + Ok(ArgFieldInfo::Direct(field, reconstruction)) + } + _ => Err(Error::new_spanned( + ty, + "Unsupported reference type. Only references to structs, slices (&[T]), and &str are supported." + )) + } } - }; + _ => Err(Error::new_spanned( + ty, + "Unsupported argument type. Supported types are:\n\ + - Primitive types (at most usize)\n\ + - Structs implementing Copy (at most usize)\n\ + - References to structs (&T)\n\ + - Slices (&[T])\n\ + - String slices (&str)" + )) + } +} + +fn is_valid_primitive(type_name: &str) -> bool { + matches!( + type_name, + "u8" | "u16" | "u32" | "u64" | "usize" | + "i8" | "i16" | "i32" | "i64" | "isize" | + "bool" | "char" + ) +} + +fn validate_return_type(return_type: &ReturnType) -> Result<(),Error> { + match return_type { + ReturnType::Default => { + return Err(Error::new_spanned( + return_type, + "Function must return Result where T is a primitive type at most usize or ()" + )); + } + ReturnType::Type(_, ty) => { + if let Type::Path(type_path) = &**ty { + let last_segment = type_path.path.segments.last() + .ok_or_else(|| Error::new_spanned(ty, "Invalid return type path"))?; + + if last_segment.ident != "Result" { + return Err(Error::new_spanned( + ty, + "Function must return Result" + )); + } + + if let PathArguments::AngleBracketed(args) = &last_segment.arguments { + if args.args.len() != 2 { + return Err(Error::new_spanned( + ty, + "Result must have exactly 2 type parameters: Result" + )); + } + + if let Some(GenericArgument::Type(Type::Path(err_path))) = args.args.iter().nth(1) { + if !err_path.path.is_ident("UnixError") { + return Err(Error::new_spanned( + err_path, + "Error type must be UnixError" + )); + } + } + + return Ok(()); + } + + return Err(Error::new_spanned( + ty, + "Invalid Result type. Expected Result" + )); + } + + Err(Error::new_spanned( + ty, + "Return type must be Result" + )) + } + } +} + +fn generate_return_handling(return_type: &ReturnType) -> Result { + match return_type { + ReturnType::Type(_, ty) => { + if let Type::Path(type_path) = &**ty { + if let Some(last_segment) = type_path.path.segments.last() { + if let PathArguments::AngleBracketed(args) = &last_segment.arguments { + if let Some(GenericArgument::Type(ok_type)) = args.args.first() { + if let Type::Tuple(tuple) = ok_type { + if tuple.elems.is_empty() { + return Ok(quote! { + match result { + Ok(()) => 0, + Err(e) => e as usize, + } + }); + } + } + + // Check if ok_type is a primitive that can be cast to usize + if let Type::Path(ok_path) = ok_type { + if let Some(ident) = ok_path.path.get_ident() { + let type_str = ident.to_string(); + if is_valid_primitive(&type_str) { + return Ok(quote! { + match result { + Ok(val) => val as usize, + Err(e) => e as usize, + } + }); + } + } + } + + return Err(Error::new_spanned( + ok_type, + "Return type T in Result must be a primitive type at most usize or unit ()" + )); + } + } + } + } + } + _ => {} + } - proc_macro::TokenStream::from(output) + Err(Error::new_spanned(return_type, "Invalid return type")) } #[proc_macro_attribute] pub fn kernel_init(_attr: proc_macro::TokenStream, item: proc_macro::TokenStream) -> proc_macro::TokenStream { item +} + +#[proc_macro_attribute] +pub fn kernel_deinit(_attr: proc_macro::TokenStream, item: proc_macro::TokenStream) -> proc_macro::TokenStream { + item } \ No newline at end of file diff --git a/kernel/src/modules/kernelmods.rs b/kernel/src/modules/kernelmods.rs index b85215e..124db10 100644 --- a/kernel/src/modules/kernelmods.rs +++ b/kernel/src/modules/kernelmods.rs @@ -1,6 +1,7 @@ mod sample_module; -use crate::modules::kernelmods::sample_module::SampleModule; +use std::ffi::c_int; +use macros::syscall_handler; use crate::modules::KernelModule; use crate::sync::spinlock::SpinLock; use crate::utils::KernelError; @@ -8,20 +9,15 @@ use crate::utils::KernelError; //Lock to guarantee race condition free access static LOCK: SpinLock = SpinLock::new(); -//Generate per Module -static mut MODULE_A: SampleModule = SampleModule::new(); -static mut MODULE_B: SampleModule = SampleModule::new(); - - +#[syscall_handler[num=3]] +pub fn dispatch(call: usize, args: *mut u8) -> c_int { + 0 +} pub(super) fn init_modules() -> Result<(), KernelError> { //SAFETY: All kernel modules are private to this generated file and are secured using a common lock, therefor no race conditions can appear unsafe { LOCK.lock(); - let res = MODULE_A.init(); - if res.is_err() {LOCK.unlock();return res;} - let res = MODULE_B.init(); - if res.is_err() {LOCK.unlock();return res;} LOCK.unlock(); } Ok(()) @@ -31,10 +27,6 @@ pub(super) fn exit_modules() -> Result<(), KernelError> { //SAFETY: All kernel modules are private to this generated file and are secured using a common lock, therefor no race conditions can appear unsafe { LOCK.lock(); - let res = MODULE_A.exit(); - if res.is_err() {LOCK.unlock();return res;} - let res = MODULE_B.exit(); - if res.is_err() {LOCK.unlock();return res;} LOCK.unlock(); } Ok(()) diff --git a/kernel/src/modules/kernelmods/sample_module.rs b/kernel/src/modules/kernelmods/sample_module.rs index e740892..330b735 100644 --- a/kernel/src/modules/kernelmods/sample_module.rs +++ b/kernel/src/modules/kernelmods/sample_module.rs @@ -1,11 +1,30 @@ -use macros::{kernel_init, kernelmod_call}; +use macros::{kernel_deinit, kernel_init, kernelmod_call}; #[kernel_init] fn init() { } -#[kernelmod_call] -fn call(target: i32) { +#[kernel_deinit] +fn deinit() { +} + + +struct Test { + a: i64, + b: i64, +} +enum UnixError { + Unknown = -1, + InvalidArgument = -22, + NotFound = -2, +} +#[kernelmod_call] +fn call(target: i32) -> Result { + match target { + 1 => Ok(0), + 2 => Err(UnixError::InvalidArgument), + _ => Err(UnixError::Unknown), + } } \ No newline at end of file From a80e41b134695304991c084cacabc538ce9e092c Mon Sep 17 00:00:00 2001 From: Jakob Fuchs Date: Wed, 28 Jan 2026 13:32:22 +0100 Subject: [PATCH 4/9] kernelmod generation+userspace wrappers --- .cargo/config.toml | 13 +- .gitignore | 2 + build.rs | 407 ++++++++++++++++- machine/arm/src/asm.rs | 64 ++- macros/src/lib.rs | 6 +- src/error.rs | 574 ++++++++++++++++++++++++ src/lib.rs | 2 + src/modules.rs | 4 +- src/modules/kernelmods/sample_module.rs | 6 +- src/uspace.rs | 1 + src/uspace/.gitkeep | 0 src/utils.rs | 3 + 12 files changed, 1048 insertions(+), 34 deletions(-) create mode 100644 src/error.rs create mode 100644 src/uspace/.gitkeep diff --git a/.cargo/config.toml b/.cargo/config.toml index b73ed2c..277c768 100644 --- a/.cargo/config.toml +++ b/.cargo/config.toml @@ -3,14 +3,21 @@ xtask = "--config xtasks/.cargo/config.toml run -p xtask --release --" [env] +OSIRIS_ARM_HAL = "stm32l4xx" +OSIRIS_ARM_STM32L4XX_VARIANT = "r5zi" +OSIRIS_DEBUG_UART = "LPUART1" +OSIRIS_DEBUG_RUNTIMESYMBOLS = "false" +OSIRIS_TUNING_ENABLEFPU = "false" +OSIRIS_TUNING_APPSTACKSIZE = "2048" +OSIRIS_TUNING_APPMEMSIZE = "8192" [build] -target = "host-tuple" - -[target] +target = "thumbv7em-none-eabi" [target.'cfg(target_os = "none")'] rustflags = ["-C", "link-arg=--entry=main",] +[target] + [target.thumbv7em-none-eabi] rustflags = ["-C", "relocation-model=ropi-rwpi"] diff --git a/.gitignore b/.gitignore index aeeb6ba..64ca6df 100644 --- a/.gitignore +++ b/.gitignore @@ -10,3 +10,5 @@ symbols.map compile_commands.json .cache/ *.img +src/modules/kernelmods.rs +src/uspace/kernelmods.rs \ No newline at end of file diff --git a/build.rs b/build.rs index a571440..e3c86bd 100644 --- a/build.rs +++ b/build.rs @@ -18,6 +18,8 @@ fn main() { generate_syscall_map("src/syscalls").expect("Failed to generate syscall map."); generate_syscalls_export("src/syscalls").expect("Failed to generate syscall exports."); + generate_kernelmods("src/modules/").expect("Failed to generate kernel modules."); + generate_userspace_kernelmods("src/modules/").expect("Failed to generate userspace kernel module wrappers."); // Get linker script from environment variable if let Ok(linker_script) = std::env::var("DEP_HAL_LINKER_SCRIPT") { @@ -65,19 +67,19 @@ fn get_arg_names(args: &str) -> String { ", ".to_owned() + &args.chars().fold("".to_owned(), |mut acc, char| { - if char.eq(&' ') { - in_arg_name = false; - return acc; - } - if char.eq(&',') { - in_arg_name = true; - return acc + ", "; - } - if in_arg_name { - acc.push(char); - } - acc - }) + if char.eq(&' ') { + in_arg_name = false; + return acc; + } + if char.eq(&',') { + in_arg_name = true; + return acc + ", "; + } + if in_arg_name { + acc.push(char); + } + acc + }) } fn generate_syscall_map>(root: P) -> Result<(), std::io::Error> { @@ -251,3 +253,382 @@ fn collect_syscalls_export>(root: P) -> HashMap, + output: syn::ReturnType, +} + +#[derive(Debug)] +struct KernelModule { + name: String, + init_fn: Option, + exit_fn: Option, + call_fns: Vec, +} + +fn has_attribute(attrs: &[Attribute], attr_name: &str) -> bool { + attrs.iter().any(|attr| attr.path().is_ident(attr_name)) +} + +fn collect_kernel_modules(root: &str) -> Result, std::io::Error> { + let mut modules = Vec::new(); + let mut folder = root.to_string(); + folder.push_str("kernelmods/"); + for entry in WalkDir::new(folder) { + let entry = match entry { + Ok(entry) => entry, + Err(_) => continue, + }; + + if entry.file_type().is_file() && entry.path().extension().map_or(false, |ext| ext == "rs") { + let path = entry.path(); + + println!("Processing kernel module file: {}", path.display()); + + let contents = match std::fs::read_to_string(path) { + Ok(contents) => contents, + Err(_) => continue, + }; + + let file = match syn::parse_file(&contents) { + Ok(file) => file, + Err(_) => continue, + }; + + let module_name = path + .file_stem() + .and_then(|s| s.to_str()) + .unwrap_or("unknown") + .to_string(); + + let mut module = KernelModule { + name: module_name.clone(), + init_fn: None, + exit_fn: None, + call_fns: Vec::new(), + }; + + for item in file.items { + if let syn::Item::Fn(item_fn) = item { + let fn_name = item_fn.sig.ident.to_string(); + + if has_attribute(&item_fn.attrs, "kernel_init") { + if module.init_fn.is_some() { + println!("cargo::warning=Module {module_name} has multiple init functions"); + } + module.init_fn = Some(fn_name.clone()); + } + + if has_attribute(&item_fn.attrs, "kernel_deinit") { + if module.exit_fn.is_some() { + println!("cargo::warning=Module {module_name} has multiple exit functions"); + } + module.exit_fn = Some(fn_name.clone()); + } + + if has_attribute(&item_fn.attrs, "kernelmod_call") { + module.call_fns.push(KernelModuleCallFn { + name: fn_name, + inputs: item_fn.sig.inputs.clone(), + output: item_fn.sig.output.clone(), + }); + } + } + } + + // Only add module if it has at least one of the attributes + if module.init_fn.is_some() || module.exit_fn.is_some() || !module.call_fns.is_empty() { + modules.push(module); + } + } + } + + Ok(modules) +} + +fn generate_kernelmods(root: &str) -> Result<(), std::io::Error> { + let modules = collect_kernel_modules(root)?; + println!("Generating kernel mods for {}", root); + let out_dir = root; + let out_path = Path::new(&out_dir).join("kernelmods.rs"); + let mut file = File::create(out_path)?; + + // 1. Header + writeln!(file, "// This file is @generated by build.rs. Do not edit!")?; + writeln!(file, "// Generated kernel module system")?; + writeln!(file, "use macros::syscall_handler;")?; + writeln!(file)?; + + // Generate mod declarations + writeln!(file, "// Module declarations")?; + for module in &modules { + writeln!(file, "mod {};", module.name)?; + } + writeln!(file)?; + + // 2. init_modules() function + writeln!(file, "/// Initialize all kernel modules")?; + writeln!(file, "pub fn init_modules() {{")?; + + for module in &modules { + if let Some(ref init_fn) = module.init_fn { + writeln!(file, " {}::{}();", module.name, init_fn)?; + } + } + writeln!(file, "}}")?; + writeln!(file)?; + + // 3. exit_modules() function + writeln!(file, "/// Deinitialize all kernel modules")?; + writeln!(file, "pub fn exit_modules() {{")?; + for module in &modules { + if let Some(ref exit_fn) = module.exit_fn { + writeln!(file, " {}::{}();", module.name, exit_fn)?; + } + } + writeln!(file, "}}")?; + writeln!(file)?; + + // 4. Lookup table with function pointers + writeln!(file, "/// Lookup table for kernel module call wrappers")?; + writeln!(file, "type KernelModCallFn = unsafe fn(*const u8) -> usize;")?; + writeln!(file)?; + writeln!(file, "const KERNELMOD_CALL_TABLE: &[KernelModCallFn] = &[")?; + + for module in &modules { + for call_fn in &module.call_fns { + writeln!(file, " {}::__{}_wrapper,", module.name, call_fn.name)?; + } + } + + writeln!(file, "];")?; + writeln!(file)?; + + // 5. execute_kernelmodcall function + writeln!(file, "/// Execute a kernel module call by index")?; + writeln!(file, "#[syscall_handler(num = 255)]")?; + writeln!(file, "pub fn execute_kernelmodcall(index: usize, data: *const u8) -> i32 {{")?; + writeln!(file, " if index >= KERNELMOD_CALL_TABLE.len() {{")?; + writeln!(file, " return -1;")?; + writeln!(file, " }}")?; + writeln!(file)?; + writeln!(file, " let func = KERNELMOD_CALL_TABLE[index];")?; + writeln!(file, " unsafe {{")?; + writeln!(file, " let result = func(data);")?; + writeln!(file, " result as i32")?; + writeln!(file, " }}")?; + writeln!(file, "}}")?; + + println!("cargo::warning=Generated kernel modules file with {} modules", modules.len()); + + Ok(()) +} +// ===== Userspace Wrapper Generation ===== + +use syn::{Pat, PatType, Type, TypeReference, TypeSlice}; + +fn generate_userspace_kernelmods(root: &str) -> Result<(), std::io::Error> { + let modules = collect_kernel_modules(root)?; + + let out_path = Path::new("src/uspace").join("kernelmods.rs"); + let mut file = File::create(out_path)?; + + // Header + writeln!(file, "// This file is @generated by build.rs. Do not edit!")?; + writeln!(file, "// Generated userspace kernel module wrappers")?; + writeln!(file)?; + writeln!(file, "use crate::error::UnixError;")?; + writeln!(file)?; + + // Track global index across all modules + let mut global_index = 0usize; + + // Generate each module + for module in &modules { + if module.call_fns.is_empty() { + continue; + } + + writeln!(file, "pub mod {} {{", module.name)?; + writeln!(file, " use super::*;")?; + writeln!(file)?; + + // Generate each function in the module + for call_fn in &module.call_fns { + generate_userspace_function(&mut file, &call_fn, global_index)?; + global_index += 1; + } + + writeln!(file, "}}")?; + writeln!(file)?; + } + + println!("cargo::warning=Generated userspace kernel modules file with {} functions", global_index); + + Ok(()) +} + +fn generate_userspace_function( + file: &mut File, + func: &KernelModuleCallFn, + index: usize, +) -> Result<(), std::io::Error> { + let fn_name = &func.name; + let args_struct_name = format!("{}Args", fn_name); + + // Generate the Args struct + writeln!(file, " #[repr(C)]")?; + writeln!(file, " struct {} {{", args_struct_name)?; + + let mut arg_names = Vec::new(); + let mut field_inits = Vec::new(); + + for arg in &func.inputs { + if let FnArg::Typed(pat_type) = arg { + if let Pat::Ident(pat_ident) = &*pat_type.pat { + let name = pat_ident.ident.to_string(); + arg_names.push(name.clone()); + + generate_args_struct_field(file, &name, &pat_type.ty, &mut field_inits)?; + } + } + } + + writeln!(file, " }}")?; + writeln!(file)?; + + // Generate the function signature + let inputs_str = func.inputs + .iter() + .map(|arg| arg.to_token_stream().to_string()) + .collect::>() + .join(", "); + + // Extract return type + let return_type_str = match &func.output { + syn::ReturnType::Default => "Result<(), UnixError>".to_string(), + syn::ReturnType::Type(_, ty) => ty.to_token_stream().to_string(), + }; + + writeln!(file, " pub fn {}({}) -> {} {{", fn_name, inputs_str, return_type_str)?; + + // Create args struct + writeln!(file, " let args = {} {{", args_struct_name)?; + for init in &field_inits { + writeln!(file, " {},", init)?; + } + writeln!(file, " }};")?; + writeln!(file)?; + + // Make syscall + writeln!(file, " let result = unsafe {{")?; + writeln!(file, " hal::asm::syscall!(255, {}, &args as *const _ as *const u8)", index)?; + writeln!(file, " }};")?; + writeln!(file)?; + + // Convert i32 result to Result + writeln!(file, " // Convert i32 result to Result")?; + writeln!(file, " if result < 0 && result > -134 {{")?; + writeln!(file, " let err: UnixError = UnixError::try_from(result)")?; + writeln!(file, " .unwrap_or(UnixError::ENOSYS);")?; + writeln!(file, " Err(err)")?; + writeln!(file, " }} else {{")?; + + // Determine success return type + let success_type = extract_success_type(&func.output); + if success_type == "()" { + writeln!(file, " Ok(())")?; + } else { + writeln!(file, " Ok(result as {})", success_type)?; + } + + writeln!(file, " }}")?; + writeln!(file, " }}")?; + writeln!(file)?; + + Ok(()) +} + +fn generate_args_struct_field( + file: &mut File, + name: &str, + ty: &Type, + field_inits: &mut Vec, +) -> Result<(), std::io::Error> { + match ty { + Type::Path(_) => { + // Direct field (primitive or Copy type) + let ty_str = ty.to_token_stream().to_string(); + writeln!(file, " {}: {},", name, ty_str)?; + field_inits.push(name.to_string()); + } + Type::Reference(type_ref) => { + if type_ref.mutability.is_some() { + println!("cargo::warning=Mutable references not supported in kernelmod_call: {}", name); + } + + match &*type_ref.elem { + Type::Slice(slice) => { + // &[T] -> ptr + len + let elem_ty = slice.elem.to_token_stream().to_string(); + writeln!(file, " {}_ptr: *const {},", name, elem_ty)?; + writeln!(file, " {}_len: usize,", name)?; + field_inits.push(format!("{}_ptr: {}.as_ptr()", name, name)); + field_inits.push(format!("{}_len: {}.len()", name, name)); + } + Type::Path(path) => { + // Check for &str + if path.path.is_ident("str") { + writeln!(file, " {}_ptr: *const u8,", name)?; + writeln!(file, " {}_len: usize,", name)?; + field_inits.push(format!("{}_ptr: {}.as_ptr()", name, name)); + field_inits.push(format!("{}_len: {}.len()", name, name)); + } else { + // &T -> *const T + let ty_str = path.to_token_stream().to_string(); + writeln!(file, " {}: *const {},", name, ty_str)?; + field_inits.push(format!("{}: {} as *const _", name, name)); + } + } + _ => { + // Fallback: &T -> *const T + let elem_ty = type_ref.elem.to_token_stream().to_string(); + writeln!(file, " {}: *const {},", name, elem_ty)?; + field_inits.push(format!("{}: {} as *const _", name, name)); + } + } + } + _ => { + println!("cargo::warning=Unsupported type in kernelmod_call: {}", ty.to_token_stream()); + let ty_str = ty.to_token_stream().to_string(); + writeln!(file, " {}: {},", name, ty_str)?; + field_inits.push(name.to_string()); + } + } + + Ok(()) +} + +fn extract_success_type(return_type: &syn::ReturnType) -> String { + match return_type { + syn::ReturnType::Default => "()".to_string(), + syn::ReturnType::Type(_, ty) => { + // Parse Result to extract T + if let Type::Path(type_path) = &**ty { + if let Some(segment) = type_path.path.segments.last() { + if segment.ident == "Result" { + if let syn::PathArguments::AngleBracketed(args) = &segment.arguments { + if let Some(syn::GenericArgument::Type(ok_type)) = args.args.first() { + return ok_type.to_token_stream().to_string(); + } + } + } + } + } + "()".to_string() + } + } +} \ No newline at end of file diff --git a/machine/arm/src/asm.rs b/machine/arm/src/asm.rs index 9f12f75..3f65a7b 100644 --- a/machine/arm/src/asm.rs +++ b/machine/arm/src/asm.rs @@ -20,34 +20,78 @@ pub use crate::__macro_nop as nop; #[macro_export] macro_rules! __macro_syscall { ($num:expr) => { - use core::arch::asm; + { + let result: isize; unsafe { - asm!("svc {0}", const $num); + core::arch::asm!( + "svc #{0}", // const $num is operand 0 + const $num, + lateout("r0") result, + clobber_abi("C"), + ); } + result + } }; ($num:expr, $arg0:expr) => { - use core::arch::asm; + { + let result: isize; unsafe { - asm!("mov r0, {0}", "svc {1}", in(reg)$arg0, const $num); + core::arch::asm!( + "svc #{0}", // const $num is operand 1 (after r0) + const $num, + inout("r0") $arg0 => result, + clobber_abi("C"), + ); } + result + } }; ($num:expr, $arg0:expr, $arg1:expr) => { - use core::arch::asm; + { + let result: isize; unsafe { - asm!("mov r0, {0}", "mov r1, {1}", "svc {2}", in(reg)$arg0, in(reg)$arg1, const $num); + core::arch::asm!( + "svc #{0}", // const $num is operand 2 (after r0, r1) + const $num, + inout("r0") $arg0 => result, + in("r1") $arg1, + clobber_abi("C"), + ); } + result + } }; ($num:expr, $arg0:expr, $arg1:expr, $arg2:expr) => { - use core::arch::asm; + { + let result: isize; unsafe { - asm!("mov r0, {0}", "mov r1, {1}", "mov r2, {2}", "svc {3}", in(reg)$arg0, in(reg)$arg1, in(reg)$arg2, const $num); + core::arch::asm!( + "svc #{0}", // const $num is operand 3 + const $num, + inout("r0") $arg0 => result, + in("r1") $arg1, + in("r2") $arg2, + clobber_abi("C"), + ); } + result + } }; ($num:expr, $arg0:expr, $arg1:expr, $arg2:expr, $arg3:expr) => { - use core::arch::asm; + let result: isize; unsafe { - asm!("mov r0, {0}", "mov r1, {1}", "mov r2, {2}", "mov r3, {3}", "svc {4}", in(reg)$arg0, in(reg)$arg1, in(reg)$arg2, in(reg)$arg3, const $num); + core::arch::asm!( + "svc #{0}", // const $num is operand 4 + const $num, + inout("r0") $arg0 => result, + in("r1") $arg1, + in("r2") $arg2, + in("r3") $arg3, + clobber_abi("C"), + ); } + result }; } diff --git a/macros/src/lib.rs b/macros/src/lib.rs index fbc6d5b..8beeb2e 100644 --- a/macros/src/lib.rs +++ b/macros/src/lib.rs @@ -238,8 +238,8 @@ pub fn kernelmod_call(_attr: proc_macro::TokenStream, item: proc_macro::TokenStr fn generate_wrapper(input: ItemFn) -> Result { let fn_name = &input.sig.ident; let fn_vis = &input.vis; - let wrapper_name = syn::Ident::new(&format!("{}_wrapper", fn_name), fn_name.span()); - let args_struct_name = syn::Ident::new(&format!("{}Args", fn_name), fn_name.span()); + let wrapper_name = syn::Ident::new(&format!("__{}_wrapper", fn_name), fn_name.span()); + let args_struct_name = syn::Ident::new(&format!("__{}Args", fn_name), fn_name.span()); validate_return_type(&input.sig.output)?; @@ -288,7 +288,7 @@ fn generate_wrapper(input: ItemFn) -> Result { #(#arg_fields),* } - #fn_vis unsafe extern "C" fn #wrapper_name(args_ptr: *const u8) -> usize { + #fn_vis unsafe fn #wrapper_name(args_ptr: *const u8) -> usize { let args = &*(args_ptr as *const #args_struct_name); #(#arg_reconstructions)* diff --git a/src/error.rs b/src/error.rs new file mode 100644 index 0000000..966ec54 --- /dev/null +++ b/src/error.rs @@ -0,0 +1,574 @@ +/// Unix error codes enum covering all standard errno values +/// Values are stored as negative integers matching kernel return values +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] +#[repr(i32)] +pub enum UnixError { + /// Operation not permitted + EPERM = -1, + /// No such file or directory + ENOENT = -2, + /// No such process + ESRCH = -3, + /// Interrupted system call + EINTR = -4, + /// I/O error + EIO = -5, + /// No such device or address + ENXIO = -6, + /// Argument list too long + E2BIG = -7, + /// Exec format error + ENOEXEC = -8, + /// Bad file number + EBADF = -9, + /// No child processes + ECHILD = -10, + /// Try again + EAGAIN = -11, + /// Out of memory + ENOMEM = -12, + /// Permission denied + EACCES = -13, + /// Bad address + EFAULT = -14, + /// Block device required + ENOTBLK = -15, + /// Device or resource busy + EBUSY = -16, + /// File exists + EEXIST = -17, + /// Cross-device link + EXDEV = -18, + /// No such device + ENODEV = -19, + /// Not a directory + ENOTDIR = -20, + /// Is a directory + EISDIR = -21, + /// Invalid argument + EINVAL = -22, + /// File table overflow + ENFILE = -23, + /// Too many open files + EMFILE = -24, + /// Not a typewriter + ENOTTY = -25, + /// Text file busy + ETXTBSY = -26, + /// File too large + EFBIG = -27, + /// No space left on device + ENOSPC = -28, + /// Illegal seek + ESPIPE = -29, + /// Read-only file system + EROFS = -30, + /// Too many links + EMLINK = -31, + /// Broken pipe + EPIPE = -32, + /// Math argument out of domain of func + EDOM = -33, + /// Math result not representable + ERANGE = -34, + /// Resource deadlock would occur + EDEADLK = -35, + /// File name too long + ENAMETOOLONG = -36, + /// No record locks available + ENOLCK = -37, + /// Function not implemented + ENOSYS = -38, + /// Directory not empty + ENOTEMPTY = -39, + /// Too many symbolic links encountered + ELOOP = -40, + /// No message of desired type + ENOMSG = -42, + /// Identifier removed + EIDRM = -43, + /// Channel number out of range + ECHRNG = -44, + /// Level 2 not synchronized + EL2NSYNC = -45, + /// Level 3 halted + EL3HLT = -46, + /// Level 3 reset + EL3RST = -47, + /// Link number out of range + ELNRNG = -48, + /// Protocol driver not attached + EUNATCH = -49, + /// No CSI structure available + ENOCSI = -50, + /// Level 2 halted + EL2HLT = -51, + /// Invalid exchange + EBADE = -52, + /// Invalid request descriptor + EBADR = -53, + /// Exchange full + EXFULL = -54, + /// No anode + ENOANO = -55, + /// Invalid request code + EBADRQC = -56, + /// Invalid slot + EBADSLT = -57, + /// Bad font file format + EBFONT = -59, + /// Device not a stream + ENOSTR = -60, + /// No data available + ENODATA = -61, + /// Timer expired + ETIME = -62, + /// Out of streams resources + ENOSR = -63, + /// Machine is not on the network + ENONET = -64, + /// Package not installed + ENOPKG = -65, + /// Object is remote + EREMOTE = -66, + /// Link has been severed + ENOLINK = -67, + /// Advertise error + EADV = -68, + /// Srmount error + ESRMNT = -69, + /// Communication error on send + ECOMM = -70, + /// Protocol error + EPROTO = -71, + /// Multihop attempted + EMULTIHOP = -72, + /// RFS specific error + EDOTDOT = -73, + /// Not a data message + EBADMSG = -74, + /// Value too large for defined data type + EOVERFLOW = -75, + /// Name not unique on network + ENOTUNIQ = -76, + /// File descriptor in bad state + EBADFD = -77, + /// Remote address changed + EREMCHG = -78, + /// Can not access a needed shared library + ELIBACC = -79, + /// Accessing a corrupted shared library + ELIBBAD = -80, + /// .lib section in a.out corrupted + ELIBSCN = -81, + /// Attempting to link in too many shared libraries + ELIBMAX = -82, + /// Cannot exec a shared library directly + ELIBEXEC = -83, + /// Illegal byte sequence + EILSEQ = -84, + /// Interrupted system call should be restarted + ERESTART = -85, + /// Streams pipe error + ESTRPIPE = -86, + /// Too many users + EUSERS = -87, + /// Socket operation on non-socket + ENOTSOCK = -88, + /// Destination address required + EDESTADDRREQ = -89, + /// Message too long + EMSGSIZE = -90, + /// Protocol wrong type for socket + EPROTOTYPE = -91, + /// Protocol not available + ENOPROTOOPT = -92, + /// Protocol not supported + EPROTONOSUPPORT = -93, + /// Socket type not supported + ESOCKTNOSUPPORT = -94, + /// Operation not supported on transport endpoint + EOPNOTSUPP = -95, + /// Protocol family not supported + EPFNOSUPPORT = -96, + /// Address family not supported by protocol + EAFNOSUPPORT = -97, + /// Address already in use + EADDRINUSE = -98, + /// Cannot assign requested address + EADDRNOTAVAIL = -99, + /// Network is down + ENETDOWN = -100, + /// Network is unreachable + ENETUNREACH = -101, + /// Network dropped connection because of reset + ENETRESET = -102, + /// Software caused connection abort + ECONNABORTED = -103, + /// Connection reset by peer + ECONNRESET = -104, + /// No buffer space available + ENOBUFS = -105, + /// Transport endpoint is already connected + EISCONN = -106, + /// Transport endpoint is not connected + ENOTCONN = -107, + /// Cannot send after transport endpoint shutdown + ESHUTDOWN = -108, + /// Too many references: cannot splice + ETOOMANYREFS = -109, + /// Connection timed out + ETIMEDOUT = -110, + /// Connection refused + ECONNREFUSED = -111, + /// Host is down + EHOSTDOWN = -112, + /// No route to host + EHOSTUNREACH = -113, + /// Operation already in progress + EALREADY = -114, + /// Operation now in progress + EINPROGRESS = -115, + /// Stale file handle + ESTALE = -116, + /// Structure needs cleaning + EUCLEAN = -117, + /// Not a XENIX named type file + ENOTNAM = -118, + /// No XENIX semaphores available + ENAVAIL = -119, + /// Is a named type file + EISNAM = -120, + /// Remote I/O error + EREMOTEIO = -121, + /// Quota exceeded + EDQUOT = -122, + /// No medium found + ENOMEDIUM = -123, + /// Wrong medium type + EMEDIUMTYPE = -124, + /// Operation Canceled + ECANCELED = -125, + /// Required key not available + ENOKEY = -126, + /// Key has expired + EKEYEXPIRED = -127, + /// Key has been revoked + EKEYREVOKED = -128, + /// Key was rejected by service + EKEYREJECTED = -129, + /// Owner died + EOWNERDEAD = -130, + /// State not recoverable + ENOTRECOVERABLE = -131, + /// Operation not possible due to RF-kill + ERFKILL = -132, + /// Memory page has hardware error + EHWPOISON = -133, +} + +impl UnixError { + /// Convert to errno value (returns the stored negative value) + #[inline] + pub fn to_errno(&self) -> i32 { + *self as i32 + } +} + +impl From for i32 { + fn from(err: UnixError) -> i32 { + err.to_errno() + } +} + +impl TryFrom for UnixError { + type Error = (); + + fn try_from(value: i32) -> Result { + // Only accept negative values (syscall error returns) + if value >= 0 { + return Err(()); + } + + match value { + -1 => Ok(UnixError::EPERM), + -2 => Ok(UnixError::ENOENT), + -3 => Ok(UnixError::ESRCH), + -4 => Ok(UnixError::EINTR), + -5 => Ok(UnixError::EIO), + -6 => Ok(UnixError::ENXIO), + -7 => Ok(UnixError::E2BIG), + -8 => Ok(UnixError::ENOEXEC), + -9 => Ok(UnixError::EBADF), + -10 => Ok(UnixError::ECHILD), + -11 => Ok(UnixError::EAGAIN), + -12 => Ok(UnixError::ENOMEM), + -13 => Ok(UnixError::EACCES), + -14 => Ok(UnixError::EFAULT), + -15 => Ok(UnixError::ENOTBLK), + -16 => Ok(UnixError::EBUSY), + -17 => Ok(UnixError::EEXIST), + -18 => Ok(UnixError::EXDEV), + -19 => Ok(UnixError::ENODEV), + -20 => Ok(UnixError::ENOTDIR), + -21 => Ok(UnixError::EISDIR), + -22 => Ok(UnixError::EINVAL), + -23 => Ok(UnixError::ENFILE), + -24 => Ok(UnixError::EMFILE), + -25 => Ok(UnixError::ENOTTY), + -26 => Ok(UnixError::ETXTBSY), + -27 => Ok(UnixError::EFBIG), + -28 => Ok(UnixError::ENOSPC), + -29 => Ok(UnixError::ESPIPE), + -30 => Ok(UnixError::EROFS), + -31 => Ok(UnixError::EMLINK), + -32 => Ok(UnixError::EPIPE), + -33 => Ok(UnixError::EDOM), + -34 => Ok(UnixError::ERANGE), + -35 => Ok(UnixError::EDEADLK), + -36 => Ok(UnixError::ENAMETOOLONG), + -37 => Ok(UnixError::ENOLCK), + -38 => Ok(UnixError::ENOSYS), + -39 => Ok(UnixError::ENOTEMPTY), + -40 => Ok(UnixError::ELOOP), + -42 => Ok(UnixError::ENOMSG), + -43 => Ok(UnixError::EIDRM), + -44 => Ok(UnixError::ECHRNG), + -45 => Ok(UnixError::EL2NSYNC), + -46 => Ok(UnixError::EL3HLT), + -47 => Ok(UnixError::EL3RST), + -48 => Ok(UnixError::ELNRNG), + -49 => Ok(UnixError::EUNATCH), + -50 => Ok(UnixError::ENOCSI), + -51 => Ok(UnixError::EL2HLT), + -52 => Ok(UnixError::EBADE), + -53 => Ok(UnixError::EBADR), + -54 => Ok(UnixError::EXFULL), + -55 => Ok(UnixError::ENOANO), + -56 => Ok(UnixError::EBADRQC), + -57 => Ok(UnixError::EBADSLT), + -59 => Ok(UnixError::EBFONT), + -60 => Ok(UnixError::ENOSTR), + -61 => Ok(UnixError::ENODATA), + -62 => Ok(UnixError::ETIME), + -63 => Ok(UnixError::ENOSR), + -64 => Ok(UnixError::ENONET), + -65 => Ok(UnixError::ENOPKG), + -66 => Ok(UnixError::EREMOTE), + -67 => Ok(UnixError::ENOLINK), + -68 => Ok(UnixError::EADV), + -69 => Ok(UnixError::ESRMNT), + -70 => Ok(UnixError::ECOMM), + -71 => Ok(UnixError::EPROTO), + -72 => Ok(UnixError::EMULTIHOP), + -73 => Ok(UnixError::EDOTDOT), + -74 => Ok(UnixError::EBADMSG), + -75 => Ok(UnixError::EOVERFLOW), + -76 => Ok(UnixError::ENOTUNIQ), + -77 => Ok(UnixError::EBADFD), + -78 => Ok(UnixError::EREMCHG), + -79 => Ok(UnixError::ELIBACC), + -80 => Ok(UnixError::ELIBBAD), + -81 => Ok(UnixError::ELIBSCN), + -82 => Ok(UnixError::ELIBMAX), + -83 => Ok(UnixError::ELIBEXEC), + -84 => Ok(UnixError::EILSEQ), + -85 => Ok(UnixError::ERESTART), + -86 => Ok(UnixError::ESTRPIPE), + -87 => Ok(UnixError::EUSERS), + -88 => Ok(UnixError::ENOTSOCK), + -89 => Ok(UnixError::EDESTADDRREQ), + -90 => Ok(UnixError::EMSGSIZE), + -91 => Ok(UnixError::EPROTOTYPE), + -92 => Ok(UnixError::ENOPROTOOPT), + -93 => Ok(UnixError::EPROTONOSUPPORT), + -94 => Ok(UnixError::ESOCKTNOSUPPORT), + -95 => Ok(UnixError::EOPNOTSUPP), + -96 => Ok(UnixError::EPFNOSUPPORT), + -97 => Ok(UnixError::EAFNOSUPPORT), + -98 => Ok(UnixError::EADDRINUSE), + -99 => Ok(UnixError::EADDRNOTAVAIL), + -100 => Ok(UnixError::ENETDOWN), + -101 => Ok(UnixError::ENETUNREACH), + -102 => Ok(UnixError::ENETRESET), + -103 => Ok(UnixError::ECONNABORTED), + -104 => Ok(UnixError::ECONNRESET), + -105 => Ok(UnixError::ENOBUFS), + -106 => Ok(UnixError::EISCONN), + -107 => Ok(UnixError::ENOTCONN), + -108 => Ok(UnixError::ESHUTDOWN), + -109 => Ok(UnixError::ETOOMANYREFS), + -110 => Ok(UnixError::ETIMEDOUT), + -111 => Ok(UnixError::ECONNREFUSED), + -112 => Ok(UnixError::EHOSTDOWN), + -113 => Ok(UnixError::EHOSTUNREACH), + -114 => Ok(UnixError::EALREADY), + -115 => Ok(UnixError::EINPROGRESS), + -116 => Ok(UnixError::ESTALE), + -117 => Ok(UnixError::EUCLEAN), + -118 => Ok(UnixError::ENOTNAM), + -119 => Ok(UnixError::ENAVAIL), + -120 => Ok(UnixError::EISNAM), + -121 => Ok(UnixError::EREMOTEIO), + -122 => Ok(UnixError::EDQUOT), + -123 => Ok(UnixError::ENOMEDIUM), + -124 => Ok(UnixError::EMEDIUMTYPE), + -125 => Ok(UnixError::ECANCELED), + -126 => Ok(UnixError::ENOKEY), + -127 => Ok(UnixError::EKEYEXPIRED), + -128 => Ok(UnixError::EKEYREVOKED), + -129 => Ok(UnixError::EKEYREJECTED), + -130 => Ok(UnixError::EOWNERDEAD), + -131 => Ok(UnixError::ENOTRECOVERABLE), + -132 => Ok(UnixError::ERFKILL), + -133 => Ok(UnixError::EHWPOISON), + _ => Err(()), + } + } +} + +impl TryFrom for UnixError { + type Error = (); + + fn try_from(value: isize) -> Result { + // Only accept negative values (syscall error returns) + if value >= 0 { + return Err(()); + } + + match value { + -1 => Ok(UnixError::EPERM), + -2 => Ok(UnixError::ENOENT), + -3 => Ok(UnixError::ESRCH), + -4 => Ok(UnixError::EINTR), + -5 => Ok(UnixError::EIO), + -6 => Ok(UnixError::ENXIO), + -7 => Ok(UnixError::E2BIG), + -8 => Ok(UnixError::ENOEXEC), + -9 => Ok(UnixError::EBADF), + -10 => Ok(UnixError::ECHILD), + -11 => Ok(UnixError::EAGAIN), + -12 => Ok(UnixError::ENOMEM), + -13 => Ok(UnixError::EACCES), + -14 => Ok(UnixError::EFAULT), + -15 => Ok(UnixError::ENOTBLK), + -16 => Ok(UnixError::EBUSY), + -17 => Ok(UnixError::EEXIST), + -18 => Ok(UnixError::EXDEV), + -19 => Ok(UnixError::ENODEV), + -20 => Ok(UnixError::ENOTDIR), + -21 => Ok(UnixError::EISDIR), + -22 => Ok(UnixError::EINVAL), + -23 => Ok(UnixError::ENFILE), + -24 => Ok(UnixError::EMFILE), + -25 => Ok(UnixError::ENOTTY), + -26 => Ok(UnixError::ETXTBSY), + -27 => Ok(UnixError::EFBIG), + -28 => Ok(UnixError::ENOSPC), + -29 => Ok(UnixError::ESPIPE), + -30 => Ok(UnixError::EROFS), + -31 => Ok(UnixError::EMLINK), + -32 => Ok(UnixError::EPIPE), + -33 => Ok(UnixError::EDOM), + -34 => Ok(UnixError::ERANGE), + -35 => Ok(UnixError::EDEADLK), + -36 => Ok(UnixError::ENAMETOOLONG), + -37 => Ok(UnixError::ENOLCK), + -38 => Ok(UnixError::ENOSYS), + -39 => Ok(UnixError::ENOTEMPTY), + -40 => Ok(UnixError::ELOOP), + -42 => Ok(UnixError::ENOMSG), + -43 => Ok(UnixError::EIDRM), + -44 => Ok(UnixError::ECHRNG), + -45 => Ok(UnixError::EL2NSYNC), + -46 => Ok(UnixError::EL3HLT), + -47 => Ok(UnixError::EL3RST), + -48 => Ok(UnixError::ELNRNG), + -49 => Ok(UnixError::EUNATCH), + -50 => Ok(UnixError::ENOCSI), + -51 => Ok(UnixError::EL2HLT), + -52 => Ok(UnixError::EBADE), + -53 => Ok(UnixError::EBADR), + -54 => Ok(UnixError::EXFULL), + -55 => Ok(UnixError::ENOANO), + -56 => Ok(UnixError::EBADRQC), + -57 => Ok(UnixError::EBADSLT), + -59 => Ok(UnixError::EBFONT), + -60 => Ok(UnixError::ENOSTR), + -61 => Ok(UnixError::ENODATA), + -62 => Ok(UnixError::ETIME), + -63 => Ok(UnixError::ENOSR), + -64 => Ok(UnixError::ENONET), + -65 => Ok(UnixError::ENOPKG), + -66 => Ok(UnixError::EREMOTE), + -67 => Ok(UnixError::ENOLINK), + -68 => Ok(UnixError::EADV), + -69 => Ok(UnixError::ESRMNT), + -70 => Ok(UnixError::ECOMM), + -71 => Ok(UnixError::EPROTO), + -72 => Ok(UnixError::EMULTIHOP), + -73 => Ok(UnixError::EDOTDOT), + -74 => Ok(UnixError::EBADMSG), + -75 => Ok(UnixError::EOVERFLOW), + -76 => Ok(UnixError::ENOTUNIQ), + -77 => Ok(UnixError::EBADFD), + -78 => Ok(UnixError::EREMCHG), + -79 => Ok(UnixError::ELIBACC), + -80 => Ok(UnixError::ELIBBAD), + -81 => Ok(UnixError::ELIBSCN), + -82 => Ok(UnixError::ELIBMAX), + -83 => Ok(UnixError::ELIBEXEC), + -84 => Ok(UnixError::EILSEQ), + -85 => Ok(UnixError::ERESTART), + -86 => Ok(UnixError::ESTRPIPE), + -87 => Ok(UnixError::EUSERS), + -88 => Ok(UnixError::ENOTSOCK), + -89 => Ok(UnixError::EDESTADDRREQ), + -90 => Ok(UnixError::EMSGSIZE), + -91 => Ok(UnixError::EPROTOTYPE), + -92 => Ok(UnixError::ENOPROTOOPT), + -93 => Ok(UnixError::EPROTONOSUPPORT), + -94 => Ok(UnixError::ESOCKTNOSUPPORT), + -95 => Ok(UnixError::EOPNOTSUPP), + -96 => Ok(UnixError::EPFNOSUPPORT), + -97 => Ok(UnixError::EAFNOSUPPORT), + -98 => Ok(UnixError::EADDRINUSE), + -99 => Ok(UnixError::EADDRNOTAVAIL), + -100 => Ok(UnixError::ENETDOWN), + -101 => Ok(UnixError::ENETUNREACH), + -102 => Ok(UnixError::ENETRESET), + -103 => Ok(UnixError::ECONNABORTED), + -104 => Ok(UnixError::ECONNRESET), + -105 => Ok(UnixError::ENOBUFS), + -106 => Ok(UnixError::EISCONN), + -107 => Ok(UnixError::ENOTCONN), + -108 => Ok(UnixError::ESHUTDOWN), + -109 => Ok(UnixError::ETOOMANYREFS), + -110 => Ok(UnixError::ETIMEDOUT), + -111 => Ok(UnixError::ECONNREFUSED), + -112 => Ok(UnixError::EHOSTDOWN), + -113 => Ok(UnixError::EHOSTUNREACH), + -114 => Ok(UnixError::EALREADY), + -115 => Ok(UnixError::EINPROGRESS), + -116 => Ok(UnixError::ESTALE), + -117 => Ok(UnixError::EUCLEAN), + -118 => Ok(UnixError::ENOTNAM), + -119 => Ok(UnixError::ENAVAIL), + -120 => Ok(UnixError::EISNAM), + -121 => Ok(UnixError::EREMOTEIO), + -122 => Ok(UnixError::EDQUOT), + -123 => Ok(UnixError::ENOMEDIUM), + -124 => Ok(UnixError::EMEDIUMTYPE), + -125 => Ok(UnixError::ECANCELED), + -126 => Ok(UnixError::ENOKEY), + -127 => Ok(UnixError::EKEYEXPIRED), + -128 => Ok(UnixError::EKEYREVOKED), + -129 => Ok(UnixError::EKEYREJECTED), + -130 => Ok(UnixError::EOWNERDEAD), + -131 => Ok(UnixError::ENOTRECOVERABLE), + -132 => Ok(UnixError::ERFKILL), + -133 => Ok(UnixError::EHWPOISON), + _ => Err(()), + } + } +} \ No newline at end of file diff --git a/src/lib.rs b/src/lib.rs index 844751b..86e43f3 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -15,6 +15,8 @@ pub mod sync; pub mod syscalls; pub mod time; pub mod uspace; +pub mod modules; +mod error; use hal::Machinelike; use interface::BootInfo; diff --git a/src/modules.rs b/src/modules.rs index 6bc7ec5..2b6b75d 100644 --- a/src/modules.rs +++ b/src/modules.rs @@ -9,11 +9,11 @@ trait KernelModule { } -fn init_modules() -> Result<(), KernelError> { +fn init_modules() { kernelmods::init_modules() } -fn exit_modules() -> Result<(), KernelError> { +fn exit_modules() { kernelmods::exit_modules() } diff --git a/src/modules/kernelmods/sample_module.rs b/src/modules/kernelmods/sample_module.rs index 330b735..6daabef 100644 --- a/src/modules/kernelmods/sample_module.rs +++ b/src/modules/kernelmods/sample_module.rs @@ -1,12 +1,12 @@ use macros::{kernel_deinit, kernel_init, kernelmod_call}; #[kernel_init] -fn init() { +pub(super) fn init() { } #[kernel_deinit] -fn deinit() { +pub(super) fn deinit() { } @@ -21,7 +21,7 @@ enum UnixError { NotFound = -2, } #[kernelmod_call] -fn call(target: i32) -> Result { +pub(super) fn call(target: i32) -> Result { match target { 1 => Ok(0), 2 => Err(UnixError::InvalidArgument), diff --git a/src/uspace.rs b/src/uspace.rs index c0d1d8e..f3e5550 100644 --- a/src/uspace.rs +++ b/src/uspace.rs @@ -1,4 +1,5 @@ //! This module provides access to userspace structures and services. +mod kernelmods; use ::core::mem::transmute; diff --git a/src/uspace/.gitkeep b/src/uspace/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/src/utils.rs b/src/utils.rs index 8d4144f..79a387b 100644 --- a/src/utils.rs +++ b/src/utils.rs @@ -56,6 +56,7 @@ pub enum KernelError { HalError(hal::Error), } + /// Debug msg implementation for KernelError. impl Debug for KernelError { fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { @@ -75,3 +76,5 @@ impl From for KernelError { KernelError::HalError(err) } } + + From 2db9535caffada47e29639349499b8dc4a9fa65e Mon Sep 17 00:00:00 2001 From: Jakob Fuchs Date: Mon, 23 Feb 2026 23:48:29 +0100 Subject: [PATCH 5/9] implemented review feedback --- .cargo/config.toml | 13 +- build.rs | 68 ++++++--- macros/src/lib.rs | 180 +++++++++--------------- src/modules.rs | 8 +- src/modules/kernelmods/sample_module.rs | 30 ---- src/modules/sample_module.rs | 27 ++++ src/uspace.rs | 2 +- 7 files changed, 152 insertions(+), 176 deletions(-) delete mode 100644 src/modules/kernelmods/sample_module.rs create mode 100644 src/modules/sample_module.rs diff --git a/.cargo/config.toml b/.cargo/config.toml index 277c768..b73ed2c 100644 --- a/.cargo/config.toml +++ b/.cargo/config.toml @@ -3,21 +3,14 @@ xtask = "--config xtasks/.cargo/config.toml run -p xtask --release --" [env] -OSIRIS_ARM_HAL = "stm32l4xx" -OSIRIS_ARM_STM32L4XX_VARIANT = "r5zi" -OSIRIS_DEBUG_UART = "LPUART1" -OSIRIS_DEBUG_RUNTIMESYMBOLS = "false" -OSIRIS_TUNING_ENABLEFPU = "false" -OSIRIS_TUNING_APPSTACKSIZE = "2048" -OSIRIS_TUNING_APPMEMSIZE = "8192" [build] -target = "thumbv7em-none-eabi" +target = "host-tuple" + +[target] [target.'cfg(target_os = "none")'] rustflags = ["-C", "link-arg=--entry=main",] -[target] - [target.thumbv7em-none-eabi] rustflags = ["-C", "relocation-model=ropi-rwpi"] diff --git a/build.rs b/build.rs index e3c86bd..dcd01c8 100644 --- a/build.rs +++ b/build.rs @@ -264,6 +264,7 @@ struct KernelModuleCallFn { #[derive(Debug)] struct KernelModule { name: String, + source_path: String, init_fn: Option, exit_fn: Option, call_fns: Vec, @@ -275,9 +276,9 @@ fn has_attribute(attrs: &[Attribute], attr_name: &str) -> bool { fn collect_kernel_modules(root: &str) -> Result, std::io::Error> { let mut modules = Vec::new(); - let mut folder = root.to_string(); - folder.push_str("kernelmods/"); - for entry in WalkDir::new(folder) { + let manifest_dir = std::env::var("CARGO_MANIFEST_DIR").unwrap(); + + for entry in WalkDir::new(root) { let entry = match entry { Ok(entry) => entry, Err(_) => continue, @@ -304,8 +305,16 @@ fn collect_kernel_modules(root: &str) -> Result, std::io::Erro .unwrap_or("unknown") .to_string(); + // Compute absolute path for include!() in generated code + let abs_path = Path::new(&manifest_dir).join(path); + let source_path = abs_path.canonicalize() + .unwrap_or(abs_path) + .to_string_lossy() + .replace('\\', "/"); + let mut module = KernelModule { name: module_name.clone(), + source_path, init_fn: None, exit_fn: None, call_fns: Vec::new(), @@ -322,7 +331,7 @@ fn collect_kernel_modules(root: &str) -> Result, std::io::Erro module.init_fn = Some(fn_name.clone()); } - if has_attribute(&item_fn.attrs, "kernel_deinit") { + if has_attribute(&item_fn.attrs, "kernel_exit") { if module.exit_fn.is_some() { println!("cargo::warning=Module {module_name} has multiple exit functions"); } @@ -351,9 +360,8 @@ fn collect_kernel_modules(root: &str) -> Result, std::io::Erro fn generate_kernelmods(root: &str) -> Result<(), std::io::Error> { let modules = collect_kernel_modules(root)?; - println!("Generating kernel mods for {}", root); - let out_dir = root; - let out_path = Path::new(&out_dir).join("kernelmods.rs"); + let out_dir = std::env::var("OUT_DIR").unwrap(); + let out_path = Path::new(&out_dir).join("modules_kernel.rs"); let mut file = File::create(out_path)?; // 1. Header @@ -362,16 +370,18 @@ fn generate_kernelmods(root: &str) -> Result<(), std::io::Error> { writeln!(file, "use macros::syscall_handler;")?; writeln!(file)?; - // Generate mod declarations - writeln!(file, "// Module declarations")?; + // Include module source files via include! macro + writeln!(file, "// Module inclusions")?; for module in &modules { - writeln!(file, "mod {};", module.name)?; + writeln!(file, "mod {} {{", module.name)?; + writeln!(file, " include!(\"{}\");", module.source_path)?; + writeln!(file, "}}")?; } writeln!(file)?; // 2. init_modules() function writeln!(file, "/// Initialize all kernel modules")?; - writeln!(file, "pub fn init_modules() {{")?; + writeln!(file, "pub fn __init_modules() {{")?; for module in &modules { if let Some(ref init_fn) = module.init_fn { @@ -382,8 +392,8 @@ fn generate_kernelmods(root: &str) -> Result<(), std::io::Error> { writeln!(file)?; // 3. exit_modules() function - writeln!(file, "/// Deinitialize all kernel modules")?; - writeln!(file, "pub fn exit_modules() {{")?; + writeln!(file, "/// Exit all kernel modules")?; + writeln!(file, "pub fn __exit_modules() {{")?; for module in &modules { if let Some(ref exit_fn) = module.exit_fn { writeln!(file, " {}::{}();", module.name, exit_fn)?; @@ -433,7 +443,8 @@ use syn::{Pat, PatType, Type, TypeReference, TypeSlice}; fn generate_userspace_kernelmods(root: &str) -> Result<(), std::io::Error> { let modules = collect_kernel_modules(root)?; - let out_path = Path::new("src/uspace").join("kernelmods.rs"); + let out_dir = std::env::var("OUT_DIR").unwrap(); + let out_path = Path::new(&out_dir).join("modules_uspace.rs"); let mut file = File::create(out_path)?; // Header @@ -471,13 +482,27 @@ fn generate_userspace_kernelmods(root: &str) -> Result<(), std::io::Error> { Ok(()) } +fn snake_to_pascal(s: &str) -> String { + s.split('_') + .filter(|part| !part.is_empty()) + .map(|part| { + let mut chars = part.chars(); + match chars.next() { + Some(c) => c.to_uppercase().to_string() + chars.as_str(), + None => String::new(), + } + }) + .collect() +} + fn generate_userspace_function( file: &mut File, func: &KernelModuleCallFn, index: usize, ) -> Result<(), std::io::Error> { let fn_name = &func.name; - let args_struct_name = format!("{}Args", fn_name); + let pascal_name = snake_to_pascal(fn_name); + let args_struct_name = format!("{}Args", pascal_name); // Generate the Args struct writeln!(file, " #[repr(C)]")?; @@ -567,7 +592,7 @@ fn generate_args_struct_field( } Type::Reference(type_ref) => { if type_ref.mutability.is_some() { - println!("cargo::warning=Mutable references not supported in kernelmod_call: {}", name); + panic!("Mutable references not supported in kernelmod_call: {}", name); } match &*type_ref.elem { @@ -602,10 +627,7 @@ fn generate_args_struct_field( } } _ => { - println!("cargo::warning=Unsupported type in kernelmod_call: {}", ty.to_token_stream()); - let ty_str = ty.to_token_stream().to_string(); - writeln!(file, " {}: {},", name, ty_str)?; - field_inits.push(name.to_string()); + panic!("Unsupported type in kernelmod_call argument '{}': {}", name, ty.to_token_stream()); } } @@ -628,7 +650,11 @@ fn extract_success_type(return_type: &syn::ReturnType) -> String { } } } - "()".to_string() + panic!( + "kernelmod_call: cannot extract success type from return type '{}'. \ + Return type must be Result (type aliases are not supported in build.rs codegen).", + ty.to_token_stream() + ); } } } \ No newline at end of file diff --git a/macros/src/lib.rs b/macros/src/lib.rs index 8beeb2e..9c3fe8d 100644 --- a/macros/src/lib.rs +++ b/macros/src/lib.rs @@ -1,6 +1,6 @@ use quote::{quote, quote_spanned}; use quote::{ToTokens, format_ident}; -use syn::{parse_macro_input, parse_quote, Error, FnArg, GenericArgument, Pat, PatType, PathArguments, ReturnType, Type, TypeReference, TypeSlice}; +use syn::{parse_macro_input, Error, FnArg, Pat, PatType, ReturnType, Type, TypeReference, TypeSlice}; use syn::ItemFn; use proc_macro2::TokenStream; @@ -158,7 +158,7 @@ fn syscall_handler_fn(item: &syn::ItemFn) -> TokenStream { "syscall_handler: function {name} has too many arguments (max is {SYSCALL_MAX_ARGS})" ), ) - .to_compile_error(); + .to_compile_error(); } let ret_check = match is_return_type_register_sized_check(item) { @@ -175,7 +175,7 @@ fn syscall_handler_fn(item: &syn::ItemFn) -> TokenStream { "syscall_handler: function {name} has too many arguments (max is {SYSCALL_MAX_ARGS})" ), ) - .to_compile_error(); + .to_compile_error(); } types } @@ -228,18 +228,42 @@ fn syscall_handler_fn(item: &syn::ItemFn) -> TokenStream { #[proc_macro_attribute] pub fn kernelmod_call(_attr: proc_macro::TokenStream, item: proc_macro::TokenStream) -> proc_macro::TokenStream { let input = parse_macro_input!(item as ItemFn); - + match generate_wrapper(input) { Ok(tokens) => proc_macro::TokenStream::from(tokens), Err(e) => proc_macro::TokenStream::from(e.to_compile_error()), } } +fn snake_to_pascal(s: &str) -> String { + s.split('_') + .filter(|part| !part.is_empty()) + .map(|part| { + let mut chars = part.chars(); + match chars.next() { + Some(c) => c.to_uppercase().to_string() + chars.as_str(), + None => String::new(), + } + }) + .collect() +} + fn generate_wrapper(input: ItemFn) -> Result { let fn_name = &input.sig.ident; + let fn_name_str = fn_name.to_string(); + + // Reject function names containing uppercase letters to avoid naming collisions + if fn_name_str.chars().any(|c| c.is_uppercase()) { + return Err(Error::new_spanned( + fn_name, + "kernelmod_call: function names must be snake_case (no uppercase letters allowed)" + )); + } + let fn_vis = &input.vis; let wrapper_name = syn::Ident::new(&format!("__{}_wrapper", fn_name), fn_name.span()); - let args_struct_name = syn::Ident::new(&format!("__{}Args", fn_name), fn_name.span()); + let pascal_name = snake_to_pascal(&fn_name_str); + let args_struct_name = syn::Ident::new(&format!("__{}Args", pascal_name), fn_name.span()); validate_return_type(&input.sig.output)?; @@ -312,20 +336,17 @@ fn validate_and_generate_field( ty: &Type, ) -> Result { match ty { - Type::Path(type_path) => { - let type_name = type_path.path.segments.last() - .ok_or_else(|| Error::new_spanned(ty, "Invalid type path"))? - .ident - .to_string(); - - if is_valid_primitive(&type_name) { - let field = quote! { #name: #ty }; - let reconstruction = quote! { let #name = args.#name; }; - return Ok(ArgFieldInfo::Direct(field, reconstruction)); - } - + Type::Path(_type_path) => { let field = quote! { #name: #ty }; + let size_check = quote_spanned! { ty.span() => + const _: () = { + if core::mem::size_of::<#ty>() > core::mem::size_of::() { + panic!("kernelmod_call: argument type is bigger than usize. arguments must fit in a register."); + } + }; + }; let reconstruction = quote! { + #size_check let #name = args.#name; let _: fn() = || { fn assert_copy() {} assert_copy::<#ty>(); }; }; @@ -396,115 +417,52 @@ fn validate_and_generate_field( } } -fn is_valid_primitive(type_name: &str) -> bool { - matches!( - type_name, - "u8" | "u16" | "u32" | "u64" | "usize" | - "i8" | "i16" | "i32" | "i64" | "isize" | - "bool" | "char" - ) -} - fn validate_return_type(return_type: &ReturnType) -> Result<(),Error> { match return_type { ReturnType::Default => { - return Err(Error::new_spanned( - return_type, - "Function must return Result where T is a primitive type at most usize or ()" - )); - } - ReturnType::Type(_, ty) => { - if let Type::Path(type_path) = &**ty { - let last_segment = type_path.path.segments.last() - .ok_or_else(|| Error::new_spanned(ty, "Invalid return type path"))?; - - if last_segment.ident != "Result" { - return Err(Error::new_spanned( - ty, - "Function must return Result" - )); - } - - if let PathArguments::AngleBracketed(args) = &last_segment.arguments { - if args.args.len() != 2 { - return Err(Error::new_spanned( - ty, - "Result must have exactly 2 type parameters: Result" - )); - } - - if let Some(GenericArgument::Type(Type::Path(err_path))) = args.args.iter().nth(1) { - if !err_path.path.is_ident("UnixError") { - return Err(Error::new_spanned( - err_path, - "Error type must be UnixError" - )); - } - } - - return Ok(()); - } - - return Err(Error::new_spanned( - ty, - "Invalid Result type. Expected Result" - )); - } - Err(Error::new_spanned( - ty, - "Return type must be Result" + return_type, + "kernelmod_call: function must have an explicit return type (expected Result)" )) } + ReturnType::Type(_, _) => { + // Don't try to validate the type by name — type aliases like + // `type MyResult = Result` would fail name-based checks. + // Instead, let the compiler verify compatibility through the generated code. + Ok(()) + } } } fn generate_return_handling(return_type: &ReturnType) -> Result { match return_type { - ReturnType::Type(_, ty) => { - if let Type::Path(type_path) = &**ty { - if let Some(last_segment) = type_path.path.segments.last() { - if let PathArguments::AngleBracketed(args) = &last_segment.arguments { - if let Some(GenericArgument::Type(ok_type)) = args.args.first() { - if let Type::Tuple(tuple) = ok_type { - if tuple.elems.is_empty() { - return Ok(quote! { - match result { - Ok(()) => 0, - Err(e) => e as usize, - } - }); - } - } - - // Check if ok_type is a primitive that can be cast to usize - if let Type::Path(ok_path) = ok_type { - if let Some(ident) = ok_path.path.get_ident() { - let type_str = ident.to_string(); - if is_valid_primitive(&type_str) { - return Ok(quote! { - match result { - Ok(val) => val as usize, - Err(e) => e as usize, - } - }); - } - } - } - - return Err(Error::new_spanned( - ok_type, - "Return type T in Result must be a primitive type at most usize or unit ()" - )); + ReturnType::Type(_, _ty) => { + // Don't try to inspect the return type by name — type aliases would break. + // Instead, generate generic code that works for any Result where + // T fits in a register. The compiler enforces type compatibility. + Ok(quote! { + match result { + Ok(val) => { + assert!( + core::mem::size_of_val(&val) <= core::mem::size_of::(), + "kernelmod_call: Ok return type is bigger than usize. must fit in a register." + ); + let mut raw: usize = 0; + unsafe { + core::ptr::copy_nonoverlapping( + &val as *const _ as *const u8, + &mut raw as *mut usize as *mut u8, + core::mem::size_of_val(&val), + ); } + raw } + Err(e) => e as usize, } - } + }) } - _ => {} + _ => Err(Error::new_spanned(return_type, "Invalid return type")) } - - Err(Error::new_spanned(return_type, "Invalid return type")) } #[proc_macro_attribute] @@ -513,6 +471,6 @@ pub fn kernel_init(_attr: proc_macro::TokenStream, item: proc_macro::TokenStream } #[proc_macro_attribute] -pub fn kernel_deinit(_attr: proc_macro::TokenStream, item: proc_macro::TokenStream) -> proc_macro::TokenStream { +pub fn kernel_exit(_attr: proc_macro::TokenStream, item: proc_macro::TokenStream) -> proc_macro::TokenStream { item } \ No newline at end of file diff --git a/src/modules.rs b/src/modules.rs index 2b6b75d..f27ba1a 100644 --- a/src/modules.rs +++ b/src/modules.rs @@ -1,4 +1,6 @@ -pub mod kernelmods; +// Module declarations +include!(concat!(env!("OUT_DIR"), "/modules_kernel.rs")); + use crate::utils::KernelError; @@ -10,10 +12,10 @@ trait KernelModule { fn init_modules() { - kernelmods::init_modules() + __init_modules() } fn exit_modules() { - kernelmods::exit_modules() + __exit_modules() } diff --git a/src/modules/kernelmods/sample_module.rs b/src/modules/kernelmods/sample_module.rs deleted file mode 100644 index 6daabef..0000000 --- a/src/modules/kernelmods/sample_module.rs +++ /dev/null @@ -1,30 +0,0 @@ -use macros::{kernel_deinit, kernel_init, kernelmod_call}; - -#[kernel_init] -pub(super) fn init() { - -} - -#[kernel_deinit] -pub(super) fn deinit() { - -} - - -struct Test { - a: i64, - b: i64, -} -enum UnixError { - Unknown = -1, - InvalidArgument = -22, - NotFound = -2, -} -#[kernelmod_call] -pub(super) fn call(target: i32) -> Result { - match target { - 1 => Ok(0), - 2 => Err(UnixError::InvalidArgument), - _ => Err(UnixError::Unknown), - } -} \ No newline at end of file diff --git a/src/modules/sample_module.rs b/src/modules/sample_module.rs new file mode 100644 index 0000000..0ad50df --- /dev/null +++ b/src/modules/sample_module.rs @@ -0,0 +1,27 @@ +use macros::{kernel_exit, kernel_init, kernelmod_call}; +/// This is a sample kernel module. It doesn't do anything useful, but it serves as an example of how to write a kernel module and how to use the macros. +/// The kernelmodule system offers 3 macros for integrating modules into the kernel. While building, the build system will then automatically generate a userspace API compatible to the currently installed kernelmodules. +/// Below, stubs of the three types of methods can be found. Node that the signature needs to match the one provided in the samples and especially that thbe return type of the kernelmodule_call needs to be at most register sized. +/// The macros are used to detect the modules during build. Below, the macros are commented out to avoid inclusion of this sample module. + + +#[kernel_init] +pub fn init() { + /// This function is called once on kernel startup for each module. It should be used for initializing any state the module requires. Currently, there are no guarantees for the initialization order. +} + +#[kernel_exit] +pub fn exit() { + /// This function is called once on kernel shutdown for each module. It should be used for cleaning up any state the module requires. Currently, there are no guarantees for the exit order. +} + +#[kernelmod_call] +pub fn call(target: i32) -> Result { + /// This function represents a kernel module call. The build system generates an equivalent userspace wrapper. + /// References are transmitted using pointers, slices and string slices are internally transmitted as pointers and lengths + match target { + 1 => Ok(0), + 2 => Err(UnixError::InvalidArgument), + _ => Err(UnixError::Unknown), + } +} \ No newline at end of file diff --git a/src/uspace.rs b/src/uspace.rs index f3e5550..d762c0c 100644 --- a/src/uspace.rs +++ b/src/uspace.rs @@ -1,5 +1,5 @@ //! This module provides access to userspace structures and services. -mod kernelmods; +include!(concat!(env!("OUT_DIR"), "/modules_uspace.rs")); use ::core::mem::transmute; From 4bdda9acbfae6d50249b7a0c0dc305d8645b63e0 Mon Sep 17 00:00:00 2001 From: Jakob Fuchs Date: Sun, 1 Mar 2026 21:41:06 +0100 Subject: [PATCH 6/9] Refactored error+review feedback --- build.rs | 229 +++++++++++++--------- machine/arm/src/asm.rs | 14 +- machine/testing/src/asm.rs | 8 +- macros/src/lib.rs | 86 ++++---- src/error.rs | 370 ++++++++--------------------------- src/lib.rs | 2 +- src/modules/sample_module.rs | 31 +-- 7 files changed, 291 insertions(+), 449 deletions(-) diff --git a/build.rs b/build.rs index dcd01c8..9150210 100644 --- a/build.rs +++ b/build.rs @@ -7,7 +7,7 @@ extern crate walkdir; use cfg_aliases::cfg_aliases; use quote::ToTokens; use std::io::Write; -use syn::{Attribute, FnArg, LitInt, punctuated::Punctuated, token::Comma}; +use syn::{Attribute, FnArg, LitInt, punctuated::Punctuated, token::Comma,Pat, PatType, Type, TypeReference, TypeSlice}; use walkdir::WalkDir; extern crate cbindgen; @@ -18,8 +18,8 @@ fn main() { generate_syscall_map("src/syscalls").expect("Failed to generate syscall map."); generate_syscalls_export("src/syscalls").expect("Failed to generate syscall exports."); - generate_kernelmods("src/modules/").expect("Failed to generate kernel modules."); - generate_userspace_kernelmods("src/modules/").expect("Failed to generate userspace kernel module wrappers."); + generate_modules_kernel("src/modules/").expect("Failed to generate kernel modules."); + generate_modules_uspace("src/modules/").expect("Failed to generate userspace kernel module wrappers."); // Get linker script from environment variable if let Ok(linker_script) = std::env::var("DEP_HAL_LINKER_SCRIPT") { @@ -131,7 +131,7 @@ fn is_syscall(attrs: &[Attribute], name: &str) -> Option { }); if let Err(e) = result { - println!("cargo::warning=Failed to parse syscall arguments for `{name}`, {e}"); + panic!("Failed to parse syscall arguments for `{name}`, {e}"); return None; } @@ -151,7 +151,9 @@ fn collect_syscalls>(root: P) -> HashMap { for entry in WalkDir::new(&root) { let entry = match entry { Ok(entry) => entry, - Err(_) => continue, + Err(e) => { + panic!("Failed to read directory entry in syscalls: {e}"); + } }; if entry.file_type().is_file() { @@ -161,12 +163,16 @@ fn collect_syscalls>(root: P) -> HashMap { let contents = match std::fs::read_to_string(path) { Ok(contents) => contents, - Err(_) => continue, + Err(e) => { + panic!("Failed to read file {}: {e}", path.display()); + } }; let file = match syn::parse_file(&contents) { Ok(file) => file, - Err(_) => continue, + Err(e) => { + panic!("Failed to parse file {}: {e}", path.display()); + } }; for item in file.items { @@ -179,13 +185,11 @@ fn collect_syscalls>(root: P) -> HashMap { if let Some(num) = is_syscall(&item.attrs, &name) { if syscalls.contains_key(&name) { - println!("cargo::warning=Duplicate syscall handler: {name}"); - continue; + panic!("Duplicate syscall handler: {name}"); } if numbers.contains_key(&num) { - println!("cargo::warning=Duplicate syscall number: {num} for {name}"); - continue; + panic!("Duplicate syscall number: {num} for {name}"); } syscalls.insert(name.clone(), num); @@ -207,7 +211,9 @@ fn collect_syscalls_export>(root: P) -> HashMap entry, - Err(_) => continue, + Err(e) => { + panic!("Failed to read directory entry in syscalls_export: {e}"); + } }; if entry.file_type().is_file() { @@ -217,12 +223,16 @@ fn collect_syscalls_export>(root: P) -> HashMap contents, - Err(_) => continue, + Err(e) => { + panic!("Failed to read file {}: {e}", path.display()); + } }; let file = match syn::parse_file(&contents) { Ok(file) => file, - Err(_) => continue, + Err(e) => { + panic!("Failed to parse file {}: {e}", path.display()); + } }; for item in file.items { @@ -235,13 +245,11 @@ fn collect_syscalls_export>(root: P) -> HashMap>(root: P) -> HashMap bool { fn collect_kernel_modules(root: &str) -> Result, std::io::Error> { let mut modules = Vec::new(); let manifest_dir = std::env::var("CARGO_MANIFEST_DIR").unwrap(); - + for entry in WalkDir::new(root) { let entry = match entry { Ok(entry) => entry, - Err(_) => continue, + Err(e) => { + panic!("Failed to read directory entry in modules: {e}"); + } }; if entry.file_type().is_file() && entry.path().extension().map_or(false, |ext| ext == "rs") { @@ -291,12 +303,16 @@ fn collect_kernel_modules(root: &str) -> Result, std::io::Erro let contents = match std::fs::read_to_string(path) { Ok(contents) => contents, - Err(_) => continue, + Err(e) => { + panic!("Failed to read module file {}: {e}", path.display()); + } }; let file = match syn::parse_file(&contents) { Ok(file) => file, - Err(_) => continue, + Err(e) => { + panic!("Failed to parse module file {}: {e}", path.display()); + } }; let module_name = path @@ -319,25 +335,26 @@ fn collect_kernel_modules(root: &str) -> Result, std::io::Erro exit_fn: None, call_fns: Vec::new(), }; - + //Collect relevant functions for item in file.items { if let syn::Item::Fn(item_fn) = item { let fn_name = item_fn.sig.ident.to_string(); - - if has_attribute(&item_fn.attrs, "kernel_init") { + + //Store kernelmod_init function + if has_attribute(&item_fn.attrs, "kernelmod_init") { if module.init_fn.is_some() { - println!("cargo::warning=Module {module_name} has multiple init functions"); + panic!("Module {module_name} has multiple #[kernelmod_init] functions"); } module.init_fn = Some(fn_name.clone()); } - - if has_attribute(&item_fn.attrs, "kernel_exit") { + //Store kernelmod_exit function + if has_attribute(&item_fn.attrs, "kernelmod_exit") { if module.exit_fn.is_some() { - println!("cargo::warning=Module {module_name} has multiple exit functions"); + panic!("Module {module_name} has multiple #[kernelmod_exit] functions"); } module.exit_fn = Some(fn_name.clone()); } - + //Collect all call functions if has_attribute(&item_fn.attrs, "kernelmod_call") { module.call_fns.push(KernelModuleCallFn { name: fn_name, @@ -349,7 +366,14 @@ fn collect_kernel_modules(root: &str) -> Result, std::io::Erro } // Only add module if it has at least one of the attributes - if module.init_fn.is_some() || module.exit_fn.is_some() || !module.call_fns.is_empty() { + let has_any_attr = module.init_fn.is_some() || module.exit_fn.is_some() || !module.call_fns.is_empty(); + if has_any_attr { + if module.init_fn.is_none() { + panic!("Module {module_name} is missing a #[kernelmod_init] function"); + } + if module.exit_fn.is_none() { + panic!("Module {module_name} is missing a #[kernelmod_exit] function"); + } modules.push(module); } } @@ -358,7 +382,8 @@ fn collect_kernel_modules(root: &str) -> Result, std::io::Erro Ok(modules) } -fn generate_kernelmods(root: &str) -> Result<(), std::io::Error> { +fn generate_modules_kernel(root: &str) -> Result<(), std::io::Error> { + //generates module handling and dispatch in kernelspace let modules = collect_kernel_modules(root)?; let out_dir = std::env::var("OUT_DIR").unwrap(); let out_path = Path::new(&out_dir).join("modules_kernel.rs"); @@ -368,10 +393,12 @@ fn generate_kernelmods(root: &str) -> Result<(), std::io::Error> { writeln!(file, "// This file is @generated by build.rs. Do not edit!")?; writeln!(file, "// Generated kernel module system")?; writeln!(file, "use macros::syscall_handler;")?; + writeln!(file, "use crate::error::PosixError;")?; writeln!(file)?; // Include module source files via include! macro writeln!(file, "// Module inclusions")?; + //Include each module as a submodule for module in &modules { writeln!(file, "mod {} {{", module.name)?; writeln!(file, " include!(\"{}\");", module.source_path)?; @@ -379,25 +406,23 @@ fn generate_kernelmods(root: &str) -> Result<(), std::io::Error> { } writeln!(file)?; - // 2. init_modules() function + // 2. __init_modules() function, internal method wrapped by externally facing init_modules() in modules.rs writeln!(file, "/// Initialize all kernel modules")?; writeln!(file, "pub fn __init_modules() {{")?; for module in &modules { - if let Some(ref init_fn) = module.init_fn { - writeln!(file, " {}::{}();", module.name, init_fn)?; - } + let init_fn = module.init_fn.as_ref().unwrap(); + writeln!(file, " {}::{}();", module.name, init_fn)?; } writeln!(file, "}}")?; writeln!(file)?; - // 3. exit_modules() function + // 3. __exit_modules() function, internal method wrapped by externally facing exit_modules() in modules.rs writeln!(file, "/// Exit all kernel modules")?; writeln!(file, "pub fn __exit_modules() {{")?; for module in &modules { - if let Some(ref exit_fn) = module.exit_fn { - writeln!(file, " {}::{}();", module.name, exit_fn)?; - } + let exit_fn = module.exit_fn.as_ref().unwrap(); + writeln!(file, " {}::{}();", module.name, exit_fn)?; } writeln!(file, "}}")?; writeln!(file)?; @@ -417,30 +442,28 @@ fn generate_kernelmods(root: &str) -> Result<(), std::io::Error> { writeln!(file, "];")?; writeln!(file)?; - // 5. execute_kernelmodcall function + // 5. execute_kernelmodcall function for call dispatch writeln!(file, "/// Execute a kernel module call by index")?; writeln!(file, "#[syscall_handler(num = 255)]")?; writeln!(file, "pub fn execute_kernelmodcall(index: usize, data: *const u8) -> i32 {{")?; writeln!(file, " if index >= KERNELMOD_CALL_TABLE.len() {{")?; - writeln!(file, " return -1;")?; + writeln!(file, " return PosixError::ENOSYS as i32;")?; writeln!(file, " }}")?; writeln!(file)?; writeln!(file, " let func = KERNELMOD_CALL_TABLE[index];")?; + writeln!(file, " // SAFETY: index is a function address collected from the existing modules during build.")?; + writeln!(file, " // data is a pointer constructed by the userspace wrapper, which is automatically generated from the kernelmodule definition")?; writeln!(file, " unsafe {{")?; writeln!(file, " let result = func(data);")?; writeln!(file, " result as i32")?; writeln!(file, " }}")?; writeln!(file, "}}")?; - - println!("cargo::warning=Generated kernel modules file with {} modules", modules.len()); - Ok(()) } // ===== Userspace Wrapper Generation ===== -use syn::{Pat, PatType, Type, TypeReference, TypeSlice}; -fn generate_userspace_kernelmods(root: &str) -> Result<(), std::io::Error> { +fn generate_modules_uspace(root: &str) -> Result<(), std::io::Error> { let modules = collect_kernel_modules(root)?; let out_dir = std::env::var("OUT_DIR").unwrap(); @@ -451,13 +474,13 @@ fn generate_userspace_kernelmods(root: &str) -> Result<(), std::io::Error> { writeln!(file, "// This file is @generated by build.rs. Do not edit!")?; writeln!(file, "// Generated userspace kernel module wrappers")?; writeln!(file)?; - writeln!(file, "use crate::error::UnixError;")?; + writeln!(file, "use crate::error::PosixError;")?; writeln!(file)?; - // Track global index across all modules + // Running Jump Table index let mut global_index = 0usize; - // Generate each module + // Generate per module uspace wrapper for module in &modules { if module.call_fns.is_empty() { continue; @@ -531,16 +554,40 @@ fn generate_userspace_function( .map(|arg| arg.to_token_stream().to_string()) .collect::>() .join(", "); - - // Extract return type + let return_type_str = match &func.output { - syn::ReturnType::Default => "Result<(), UnixError>".to_string(), + syn::ReturnType::Default => "Result<(), PosixError>".to_string(), syn::ReturnType::Type(_, ty) => ty.to_token_stream().to_string(), }; writeln!(file, " pub fn {}({}) -> {} {{", fn_name, inputs_str, return_type_str)?; - // Create args struct + // Compile-time size checks for value-type arguments + for arg in &func.inputs { + if let FnArg::Typed(pat_type) = arg { + if let Type::Path(_) = &*pat_type.ty { + let ty_str = pat_type.ty.to_token_stream().to_string(); + writeln!(file, " const _: () = {{")?; + writeln!(file, " if core::mem::size_of::<{}>() > core::mem::size_of::() {{", ty_str)?; + writeln!(file, " panic!(\"kernelmod_call: argument type is bigger than usize. arguments must fit in a register.\");")?; + writeln!(file, " }}")?; + writeln!(file, " }};")?; + writeln!(file, " fn __assert_copy_{}() {{ fn check() {{}} check::<{}>(); }}", pat_type.pat.to_token_stream(), ty_str)?; + } + } + } + writeln!(file)?; + + writeln!(file, " const _: () = {{")?; + writeln!(file, " trait __RetOkSize {{ type Ok; }}")?; + writeln!(file, " impl __RetOkSize for core::result::Result {{ type Ok = T; }}")?; + writeln!(file, " if core::mem::size_of::<<{} as __RetOkSize>::Ok>() > core::mem::size_of::() {{", return_type_str)?; + writeln!(file, " panic!(\"kernelmod_call: Ok return type is bigger than usize. must fit in a register.\");")?; + writeln!(file, " }}")?; + writeln!(file, " }};")?; + writeln!(file)?; + + // Layout call arguments writeln!(file, " let args = {} {{", args_struct_name)?; for init in &field_inits { writeln!(file, " {},", init)?; @@ -548,27 +595,33 @@ fn generate_userspace_function( writeln!(file, " }};")?; writeln!(file)?; - // Make syscall - writeln!(file, " let result = unsafe {{")?; + //Execute syscall with running index + writeln!(file, " let result: isize = unsafe {{")?; writeln!(file, " hal::asm::syscall!(255, {}, &args as *const _ as *const u8)", index)?; writeln!(file, " }};")?; writeln!(file)?; - // Convert i32 result to Result - writeln!(file, " // Convert i32 result to Result")?; + // Parse syscall return to Result type writeln!(file, " if result < 0 && result > -134 {{")?; - writeln!(file, " let err: UnixError = UnixError::try_from(result)")?; - writeln!(file, " .unwrap_or(UnixError::ENOSYS);")?; + writeln!(file, " let err: PosixError = PosixError::try_from(result)")?; + writeln!(file, " .unwrap_or(PosixError::ENOSYS);")?; writeln!(file, " Err(err)")?; writeln!(file, " }} else {{")?; - - // Determine success return type - let success_type = extract_success_type(&func.output); - if success_type == "()" { - writeln!(file, " Ok(())")?; - } else { - writeln!(file, " Ok(result as {})", success_type)?; - } + + writeln!(file, " trait __RetOkType {{ type Ok; }}")?; + writeln!(file, " impl __RetOkType for core::result::Result {{ type Ok = T; }}")?; + writeln!(file)?; + writeln!(file, " // SAFETY: the compile-time assert above guarantees the Ok type fits in usize.")?; + writeln!(file, " let val = unsafe {{")?; + writeln!(file, " let mut out = core::mem::MaybeUninit::<<{} as __RetOkType>::Ok>::zeroed();", return_type_str)?; + writeln!(file, " core::ptr::copy_nonoverlapping(")?; + writeln!(file, " &result as *const _ as *const u8,")?; + writeln!(file, " out.as_mut_ptr() as *mut u8,")?; + writeln!(file, " core::mem::size_of::<<{} as __RetOkType>::Ok>(),", return_type_str)?; + writeln!(file, " );")?; + writeln!(file, " out.assume_init()")?; + writeln!(file, " }};")?; + writeln!(file, " Ok(val)")?; writeln!(file, " }}")?; writeln!(file, " }}")?; @@ -577,6 +630,14 @@ fn generate_userspace_function( Ok(()) } +/// Generates args-struct fields and initializers for a single argument. +/// +/// Dispatch is purely structural (AST node kind), never by type name: +/// - `Type::Path` → value type, single field (compiler validates size + Copy) +/// - `Type::Reference` +/// - `Type::Slice` → fat pointer decomposed into (ptr, len) fields +/// - `is_ident("str")` → same layout as &[u8], language primitive +/// - other `Type::Path` → thin pointer, single field fn generate_args_struct_field( file: &mut File, name: &str, @@ -585,7 +646,6 @@ fn generate_args_struct_field( ) -> Result<(), std::io::Error> { match ty { Type::Path(_) => { - // Direct field (primitive or Copy type) let ty_str = ty.to_token_stream().to_string(); writeln!(file, " {}: {},", name, ty_str)?; field_inits.push(name.to_string()); @@ -596,8 +656,8 @@ fn generate_args_struct_field( } match &*type_ref.elem { + // &[T] — structurally a slice, decomposed into (ptr, len) Type::Slice(slice) => { - // &[T] -> ptr + len let elem_ty = slice.elem.to_token_stream().to_string(); writeln!(file, " {}_ptr: *const {},", name, elem_ty)?; writeln!(file, " {}_len: usize,", name)?; @@ -605,14 +665,14 @@ fn generate_args_struct_field( field_inits.push(format!("{}_len: {}.len()", name, name)); } Type::Path(path) => { - // Check for &str + // &str — `str` is a language primitive (unsized), same wire layout as &[u8] if path.path.is_ident("str") { writeln!(file, " {}_ptr: *const u8,", name)?; writeln!(file, " {}_len: usize,", name)?; field_inits.push(format!("{}_ptr: {}.as_ptr()", name, name)); field_inits.push(format!("{}_len: {}.len()", name, name)); } else { - // &T -> *const T + // &T where T is a sized type — stored as a thin pointer let ty_str = path.to_token_stream().to_string(); writeln!(file, " {}: *const {},", name, ty_str)?; field_inits.push(format!("{}: {} as *const _", name, name)); @@ -632,29 +692,4 @@ fn generate_args_struct_field( } Ok(()) -} - -fn extract_success_type(return_type: &syn::ReturnType) -> String { - match return_type { - syn::ReturnType::Default => "()".to_string(), - syn::ReturnType::Type(_, ty) => { - // Parse Result to extract T - if let Type::Path(type_path) = &**ty { - if let Some(segment) = type_path.path.segments.last() { - if segment.ident == "Result" { - if let syn::PathArguments::AngleBracketed(args) = &segment.arguments { - if let Some(syn::GenericArgument::Type(ok_type)) = args.args.first() { - return ok_type.to_token_stream().to_string(); - } - } - } - } - } - panic!( - "kernelmod_call: cannot extract success type from return type '{}'. \ - Return type must be Result (type aliases are not supported in build.rs codegen).", - ty.to_token_stream() - ); - } - } } \ No newline at end of file diff --git a/machine/arm/src/asm.rs b/machine/arm/src/asm.rs index 3f65a7b..4a4f099 100644 --- a/machine/arm/src/asm.rs +++ b/machine/arm/src/asm.rs @@ -79,6 +79,7 @@ macro_rules! __macro_syscall { } }; ($num:expr, $arg0:expr, $arg1:expr, $arg2:expr, $arg3:expr) => { + { let result: isize; unsafe { core::arch::asm!( @@ -92,17 +93,18 @@ macro_rules! __macro_syscall { ); } result + } }; } #[cfg(feature = "host")] #[macro_export] macro_rules! __macro_syscall { - ($num:expr) => {{}}; - ($num:expr, $arg0:expr) => {{}}; - ($num:expr, $arg0:expr, $arg1:expr) => {{}}; - ($num:expr, $arg0:expr, $arg1:expr, $arg2:expr) => {{}}; - ($num:expr, $arg0:expr, $arg1:expr, $arg2:expr, $arg3:expr) => {{}}; + ($num:expr) => { 0isize }; + ($num:expr, $arg0:expr) => {{ 0isize }}; + ($num:expr, $arg0:expr, $arg1:expr) => {{ 0isize }}; + ($num:expr, $arg0:expr, $arg1:expr, $arg2:expr) => {{ 0isize }}; + ($num:expr, $arg0:expr, $arg1:expr, $arg2:expr, $arg3:expr) => {{ 0isize }}; } pub use crate::__macro_syscall as syscall; @@ -209,4 +211,4 @@ macro_rules! __macro_fault_do_not_use_under_any_circumstances { () => {{}}; } -pub use crate::__macro_fault_do_not_use_under_any_circumstances as fault_do_not_use_under_any_circumstances; +pub use crate::__macro_fault_do_not_use_under_any_circumstances as fault_do_not_use_under_any_circumstances; \ No newline at end of file diff --git a/machine/testing/src/asm.rs b/machine/testing/src/asm.rs index 9322be2..64633a8 100644 --- a/machine/testing/src/asm.rs +++ b/machine/testing/src/asm.rs @@ -12,10 +12,10 @@ pub use crate::__macro_nop as nop; #[macro_export] macro_rules! __macro_syscall { ($num:expr) => {{}}; - ($num:expr, $arg0:expr) => {{}}; - ($num:expr, $arg0:expr, $arg1:expr) => {{}}; - ($num:expr, $arg0:expr, $arg1:expr, $arg2:expr) => {{}}; - ($num:expr, $arg0:expr, $arg1:expr, $arg2:expr, $arg3:expr) => {{}}; + ($num:expr, $arg0:expr) => {{0isize}}; + ($num:expr, $arg0:expr, $arg1:expr) => {{0isize}}; + ($num:expr, $arg0:expr, $arg1:expr, $arg2:expr) => {{0isize}}; + ($num:expr, $arg0:expr, $arg1:expr, $arg2:expr, $arg3:expr) => {{0isize}}; } pub use crate::__macro_syscall as syscall; diff --git a/macros/src/lib.rs b/macros/src/lib.rs index 9c3fe8d..7fdf939 100644 --- a/macros/src/lib.rs +++ b/macros/src/lib.rs @@ -11,7 +11,6 @@ pub fn service( attr: proc_macro::TokenStream, item: proc_macro::TokenStream, ) -> proc_macro::TokenStream { - // This macro should be used to annotate a service struct. let item = syn::parse_macro_input!(item as syn::ItemStruct); let service_name = item.ident.clone(); @@ -63,7 +62,6 @@ fn is_return_type_register_sized_check( ) -> Result { let ret_ty = match &item.sig.output { syn::ReturnType::Default => { - // no "-> Type" present return Err(syn::Error::new_spanned( &item.sig.output, "syscall_handler: missing return type; expected a register‐sized type", @@ -150,7 +148,6 @@ fn syscall_handler_fn(item: &syn::ItemFn) -> TokenStream { let name = item.sig.ident.to_string().to_uppercase(); let num_args = item.sig.inputs.len(); - // Check if the function has a valid signature. So args <= 4 and return type is u32. if num_args > SYSCALL_MAX_ARGS { return syn::Error::new( item.sig.ident.span(), @@ -182,7 +179,6 @@ fn syscall_handler_fn(item: &syn::ItemFn) -> TokenStream { Err(e) => return e.to_compile_error(), }; - // Check if each argument type is valid and fits in a register. let size_checks: Vec = types.iter().map(|ty| { quote::quote! { const _: () = { @@ -209,9 +205,7 @@ fn syscall_handler_fn(item: &syn::ItemFn) -> TokenStream { let wrapper = quote::quote! { #[unsafe(no_mangle)] pub extern "C" fn #wrapper_name(svc_args: *const core::ffi::c_uint) -> core::ffi::c_int { - // This function needs to extract the arguments from the pointer and call the original function by passing the arguments as actual different parameters. let args = unsafe { svc_args as *const usize }; - // Call the original function with the extracted arguments. #call } }; @@ -248,11 +242,10 @@ fn snake_to_pascal(s: &str) -> String { .collect() } -fn generate_wrapper(input: ItemFn) -> Result { +fn generate_wrapper(input: ItemFn) -> Result { let fn_name = &input.sig.ident; let fn_name_str = fn_name.to_string(); - // Reject function names containing uppercase letters to avoid naming collisions if fn_name_str.chars().any(|c| c.is_uppercase()) { return Err(Error::new_spanned( fn_name, @@ -300,25 +293,42 @@ fn generate_wrapper(input: ItemFn) -> Result { } } + let ret_size_check = match &input.sig.output { + ReturnType::Type(_, ty) => { + quote! { + const _: () = { + trait __RetOkSize { type Ok; } + impl __RetOkSize for core::result::Result { type Ok = T; } + if core::mem::size_of::<<#ty as __RetOkSize>::Ok>() > core::mem::size_of::() { + panic!("kernelmod_call: Ok return type is bigger than usize. must fit in a register."); + } + }; + } + } + _ => quote! {}, + }; + let return_handling = generate_return_handling(&input.sig.output)?; let original_fn = &input; let output = quote! { #original_fn - + + #ret_size_check + #[repr(C)] struct #args_struct_name { #(#arg_fields),* } - + #fn_vis unsafe fn #wrapper_name(args_ptr: *const u8) -> usize { let args = &*(args_ptr as *const #args_struct_name); - + #(#arg_reconstructions)* - + let result = #fn_name(#(#arg_names),*); - + #return_handling } }; @@ -331,12 +341,20 @@ enum ArgFieldInfo { Slice(proc_macro2::TokenStream, proc_macro2::TokenStream, proc_macro2::TokenStream), } +/// Generates the args-struct field(s) and reconstruction code for a single argument. +/// +/// Dispatch is purely structural (AST node kind), never by type name: +/// - `Type::Path` → value type, single field, compile-time size + Copy check +/// - `Type::Reference` +/// - `Type::Slice` → fat pointer decomposed into (ptr, len) fields +/// - `is_ident("str")` → same layout as &[u8], language primitive check +/// - other `Type::Path` → thin pointer, single field fn validate_and_generate_field( name: &syn::Ident, ty: &Type, -) -> Result { +) -> Result { match ty { - Type::Path(_type_path) => { + Type::Path(_) => { let field = quote! { #name: #ty }; let size_check = quote_spanned! { ty.span() => const _: () = { @@ -345,9 +363,10 @@ fn validate_and_generate_field( } }; }; - let reconstruction = quote! { + let reconstruction = quote! { #size_check let #name = args.#name; + // Compile-time Copy bound check — fails if the type is not Copy. let _: fn() = || { fn assert_copy() {} assert_copy::<#ty>(); }; }; @@ -362,6 +381,7 @@ fn validate_and_generate_field( } match &**elem { + // &[T] — structurally a slice, decomposed into (ptr, len) Type::Slice(TypeSlice { elem: slice_elem, .. }) => { let ptr_name = syn::Ident::new(&format!("{}_ptr", name), name.span()); let len_name = syn::Ident::new(&format!("{}_len", name), name.span()); @@ -369,15 +389,15 @@ fn validate_and_generate_field( let ptr_field = quote! { #ptr_name: *const #slice_elem }; let len_field = quote! { #len_name: usize }; let reconstruction = quote! { - let #name = unsafe { - core::slice::from_raw_parts(args.#ptr_name, args.#len_name) + let #name = unsafe { + core::slice::from_raw_parts(args.#ptr_name, args.#len_name) }; }; Ok(ArgFieldInfo::Slice(ptr_field, len_field, reconstruction)) } Type::Path(path) => { - // Check for &str + // &str — `str` is a language primitive (unsized), same wire layout as &[u8] if path.path.is_ident("str") { let ptr_name = syn::Ident::new(&format!("{}_ptr", name), name.span()); let len_name = syn::Ident::new(&format!("{}_len", name), name.span()); @@ -385,7 +405,7 @@ fn validate_and_generate_field( let ptr_field = quote! { #ptr_name: *const u8 }; let len_field = quote! { #len_name: usize }; let reconstruction = quote! { - let #name = unsafe { + let #name = unsafe { let bytes = core::slice::from_raw_parts(args.#ptr_name, args.#len_name); core::str::from_utf8_unchecked(bytes) }; @@ -394,7 +414,7 @@ fn validate_and_generate_field( return Ok(ArgFieldInfo::Slice(ptr_field, len_field, reconstruction)); } - // Reference to struct - store as thin pointer + // &T where T is a sized type — stored as a thin pointer let field = quote! { #name: *const #path }; let reconstruction = quote! { let #name = unsafe { &*args.#name }; }; Ok(ArgFieldInfo::Direct(field, reconstruction)) @@ -417,36 +437,30 @@ fn validate_and_generate_field( } } -fn validate_return_type(return_type: &ReturnType) -> Result<(),Error> { +fn validate_return_type(return_type: &ReturnType) -> Result<(), Error> { match return_type { ReturnType::Default => { Err(Error::new_spanned( return_type, - "kernelmod_call: function must have an explicit return type (expected Result)" + "kernelmod_call: function must have an explicit return type (expected Result)" )) } ReturnType::Type(_, _) => { - // Don't try to validate the type by name — type aliases like - // `type MyResult = Result` would fail name-based checks. - // Instead, let the compiler verify compatibility through the generated code. Ok(()) } } } -fn generate_return_handling(return_type: &ReturnType) -> Result { +fn generate_return_handling(return_type: &ReturnType) -> Result { match return_type { ReturnType::Type(_, _ty) => { - // Don't try to inspect the return type by name — type aliases would break. - // Instead, generate generic code that works for any Result where - // T fits in a register. The compiler enforces type compatibility. Ok(quote! { + // SAFETY: `val` is guaranteed at most `size_of::()` by the + // compile-time const assert emitted alongside this wrapper. + // `raw` is zero-initialised so unwritten bytes remain zero. + // Both pointers are stack-local, valid, aligned, and non-overlapping. match result { Ok(val) => { - assert!( - core::mem::size_of_val(&val) <= core::mem::size_of::(), - "kernelmod_call: Ok return type is bigger than usize. must fit in a register." - ); let mut raw: usize = 0; unsafe { core::ptr::copy_nonoverlapping( @@ -466,11 +480,11 @@ fn generate_return_handling(return_type: &ReturnType) -> Result proc_macro::TokenStream { +pub fn kernelmod_init(_attr: proc_macro::TokenStream, item: proc_macro::TokenStream) -> proc_macro::TokenStream { item } #[proc_macro_attribute] -pub fn kernel_exit(_attr: proc_macro::TokenStream, item: proc_macro::TokenStream) -> proc_macro::TokenStream { +pub fn kernelmod_exit(_attr: proc_macro::TokenStream, item: proc_macro::TokenStream) -> proc_macro::TokenStream { item } \ No newline at end of file diff --git a/src/error.rs b/src/error.rs index 966ec54..a6561f8 100644 --- a/src/error.rs +++ b/src/error.rs @@ -1,8 +1,13 @@ -/// Unix error codes enum covering all standard errno values -/// Values are stored as negative integers matching kernel return values +/// Unix error codes enum covering all standard errno values. +/// Values are stored as negative integers matching kernel return values. +/// +/// Source: Linux kernel include/uapi/asm-generic/errno-base.h (1–34) +/// and include/uapi/asm-generic/errno.h (35–133). +/// Valid for x86 (32/64-bit) and ARM (32/64-bit). #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] -#[repr(i32)] -pub enum UnixError { +#[non_exhaustive] +#[repr(i16)] +pub enum PosixError { /// Operation not permitted EPERM = -1, /// No such file or directory @@ -23,7 +28,7 @@ pub enum UnixError { EBADF = -9, /// No child processes ECHILD = -10, - /// Try again + /// Try again (EWOULDBLOCK is an alias for this value) EAGAIN = -11, /// Out of memory ENOMEM = -12, @@ -71,7 +76,7 @@ pub enum UnixError { EDOM = -33, /// Math result not representable ERANGE = -34, - /// Resource deadlock would occur + /// Resource deadlock would occur (EDEADLOCK is an alias for this value) EDEADLK = -35, /// File name too long ENAMETOOLONG = -36, @@ -83,6 +88,8 @@ pub enum UnixError { ENOTEMPTY = -39, /// Too many symbolic links encountered ELOOP = -40, + /// Reserved (EWOULDBLOCK alias slot, use EAGAIN) + _RESERVED_41 = -41, /// No message of desired type ENOMSG = -42, /// Identifier removed @@ -115,6 +122,8 @@ pub enum UnixError { EBADRQC = -56, /// Invalid slot EBADSLT = -57, + /// Reserved (EDEADLOCK alias slot, use EDEADLK) + _RESERVED_58 = -58, /// Bad font file format EBFONT = -59, /// Device not a stream @@ -187,7 +196,7 @@ pub enum UnixError { EPROTONOSUPPORT = -93, /// Socket type not supported ESOCKTNOSUPPORT = -94, - /// Operation not supported on transport endpoint + /// Operation not supported on transport endpoint (ENOTSUP is an alias for this value) EOPNOTSUPP = -95, /// Protocol family not supported EPFNOSUPPORT = -96, @@ -267,308 +276,89 @@ pub enum UnixError { EHWPOISON = -133, } -impl UnixError { - /// Convert to errno value (returns the stored negative value) +/// Convenience alias: EWOULDBLOCK is EAGAIN in Linux +pub const EWOULDBLOCK: PosixError = PosixError::EAGAIN; +/// Convenience alias: EDEADLOCK is EDEADLK in Linux +pub const EDEADLOCK: PosixError = PosixError::EDEADLK; +/// Convenience alias: ENOTSUP is EOPNOTSUPP in Linux +pub const ENOTSUP: PosixError = PosixError::EOPNOTSUPP; + +// ── Inherent methods ───────────────────────────────────────────────── + +impl PosixError { + /// Bounds for Try Impls: The bounds are both inclusive + const MIN: i16 = -133; + const MAX: i16 = -1; + #[inline] - pub fn to_errno(&self) -> i32 { - *self as i32 + pub fn to_errno(&self) -> i16 { + *self as i16 } } -impl From for i32 { - fn from(err: UnixError) -> i32 { +// ── Base conversion: i16 (matches #[repr(i16)]) ───────────────────── + +impl From for i16 { + #[inline] + fn from(err: PosixError) -> i16 { err.to_errno() } } -impl TryFrom for UnixError { +impl TryFrom for PosixError { type Error = (); - fn try_from(value: i32) -> Result { - // Only accept negative values (syscall error returns) - if value >= 0 { + #[inline] + fn try_from(value: i16) -> Result { + if value < Self::MIN || value > Self::MAX { return Err(()); } + // SAFETY: + // The range of error values is padded with reserved fields to be continous, so every value in the range is a valid PosixError variant + // The bounds are validated against the provided MIN...MAX range + Ok(unsafe { core::mem::transmute(value) }) + } +} - match value { - -1 => Ok(UnixError::EPERM), - -2 => Ok(UnixError::ENOENT), - -3 => Ok(UnixError::ESRCH), - -4 => Ok(UnixError::EINTR), - -5 => Ok(UnixError::EIO), - -6 => Ok(UnixError::ENXIO), - -7 => Ok(UnixError::E2BIG), - -8 => Ok(UnixError::ENOEXEC), - -9 => Ok(UnixError::EBADF), - -10 => Ok(UnixError::ECHILD), - -11 => Ok(UnixError::EAGAIN), - -12 => Ok(UnixError::ENOMEM), - -13 => Ok(UnixError::EACCES), - -14 => Ok(UnixError::EFAULT), - -15 => Ok(UnixError::ENOTBLK), - -16 => Ok(UnixError::EBUSY), - -17 => Ok(UnixError::EEXIST), - -18 => Ok(UnixError::EXDEV), - -19 => Ok(UnixError::ENODEV), - -20 => Ok(UnixError::ENOTDIR), - -21 => Ok(UnixError::EISDIR), - -22 => Ok(UnixError::EINVAL), - -23 => Ok(UnixError::ENFILE), - -24 => Ok(UnixError::EMFILE), - -25 => Ok(UnixError::ENOTTY), - -26 => Ok(UnixError::ETXTBSY), - -27 => Ok(UnixError::EFBIG), - -28 => Ok(UnixError::ENOSPC), - -29 => Ok(UnixError::ESPIPE), - -30 => Ok(UnixError::EROFS), - -31 => Ok(UnixError::EMLINK), - -32 => Ok(UnixError::EPIPE), - -33 => Ok(UnixError::EDOM), - -34 => Ok(UnixError::ERANGE), - -35 => Ok(UnixError::EDEADLK), - -36 => Ok(UnixError::ENAMETOOLONG), - -37 => Ok(UnixError::ENOLCK), - -38 => Ok(UnixError::ENOSYS), - -39 => Ok(UnixError::ENOTEMPTY), - -40 => Ok(UnixError::ELOOP), - -42 => Ok(UnixError::ENOMSG), - -43 => Ok(UnixError::EIDRM), - -44 => Ok(UnixError::ECHRNG), - -45 => Ok(UnixError::EL2NSYNC), - -46 => Ok(UnixError::EL3HLT), - -47 => Ok(UnixError::EL3RST), - -48 => Ok(UnixError::ELNRNG), - -49 => Ok(UnixError::EUNATCH), - -50 => Ok(UnixError::ENOCSI), - -51 => Ok(UnixError::EL2HLT), - -52 => Ok(UnixError::EBADE), - -53 => Ok(UnixError::EBADR), - -54 => Ok(UnixError::EXFULL), - -55 => Ok(UnixError::ENOANO), - -56 => Ok(UnixError::EBADRQC), - -57 => Ok(UnixError::EBADSLT), - -59 => Ok(UnixError::EBFONT), - -60 => Ok(UnixError::ENOSTR), - -61 => Ok(UnixError::ENODATA), - -62 => Ok(UnixError::ETIME), - -63 => Ok(UnixError::ENOSR), - -64 => Ok(UnixError::ENONET), - -65 => Ok(UnixError::ENOPKG), - -66 => Ok(UnixError::EREMOTE), - -67 => Ok(UnixError::ENOLINK), - -68 => Ok(UnixError::EADV), - -69 => Ok(UnixError::ESRMNT), - -70 => Ok(UnixError::ECOMM), - -71 => Ok(UnixError::EPROTO), - -72 => Ok(UnixError::EMULTIHOP), - -73 => Ok(UnixError::EDOTDOT), - -74 => Ok(UnixError::EBADMSG), - -75 => Ok(UnixError::EOVERFLOW), - -76 => Ok(UnixError::ENOTUNIQ), - -77 => Ok(UnixError::EBADFD), - -78 => Ok(UnixError::EREMCHG), - -79 => Ok(UnixError::ELIBACC), - -80 => Ok(UnixError::ELIBBAD), - -81 => Ok(UnixError::ELIBSCN), - -82 => Ok(UnixError::ELIBMAX), - -83 => Ok(UnixError::ELIBEXEC), - -84 => Ok(UnixError::EILSEQ), - -85 => Ok(UnixError::ERESTART), - -86 => Ok(UnixError::ESTRPIPE), - -87 => Ok(UnixError::EUSERS), - -88 => Ok(UnixError::ENOTSOCK), - -89 => Ok(UnixError::EDESTADDRREQ), - -90 => Ok(UnixError::EMSGSIZE), - -91 => Ok(UnixError::EPROTOTYPE), - -92 => Ok(UnixError::ENOPROTOOPT), - -93 => Ok(UnixError::EPROTONOSUPPORT), - -94 => Ok(UnixError::ESOCKTNOSUPPORT), - -95 => Ok(UnixError::EOPNOTSUPP), - -96 => Ok(UnixError::EPFNOSUPPORT), - -97 => Ok(UnixError::EAFNOSUPPORT), - -98 => Ok(UnixError::EADDRINUSE), - -99 => Ok(UnixError::EADDRNOTAVAIL), - -100 => Ok(UnixError::ENETDOWN), - -101 => Ok(UnixError::ENETUNREACH), - -102 => Ok(UnixError::ENETRESET), - -103 => Ok(UnixError::ECONNABORTED), - -104 => Ok(UnixError::ECONNRESET), - -105 => Ok(UnixError::ENOBUFS), - -106 => Ok(UnixError::EISCONN), - -107 => Ok(UnixError::ENOTCONN), - -108 => Ok(UnixError::ESHUTDOWN), - -109 => Ok(UnixError::ETOOMANYREFS), - -110 => Ok(UnixError::ETIMEDOUT), - -111 => Ok(UnixError::ECONNREFUSED), - -112 => Ok(UnixError::EHOSTDOWN), - -113 => Ok(UnixError::EHOSTUNREACH), - -114 => Ok(UnixError::EALREADY), - -115 => Ok(UnixError::EINPROGRESS), - -116 => Ok(UnixError::ESTALE), - -117 => Ok(UnixError::EUCLEAN), - -118 => Ok(UnixError::ENOTNAM), - -119 => Ok(UnixError::ENAVAIL), - -120 => Ok(UnixError::EISNAM), - -121 => Ok(UnixError::EREMOTEIO), - -122 => Ok(UnixError::EDQUOT), - -123 => Ok(UnixError::ENOMEDIUM), - -124 => Ok(UnixError::EMEDIUMTYPE), - -125 => Ok(UnixError::ECANCELED), - -126 => Ok(UnixError::ENOKEY), - -127 => Ok(UnixError::EKEYEXPIRED), - -128 => Ok(UnixError::EKEYREVOKED), - -129 => Ok(UnixError::EKEYREJECTED), - -130 => Ok(UnixError::EOWNERDEAD), - -131 => Ok(UnixError::ENOTRECOVERABLE), - -132 => Ok(UnixError::ERFKILL), - -133 => Ok(UnixError::EHWPOISON), - _ => Err(()), - } +// ── Widening conversions built on the i16 base ────────────────────── + +impl From for i32 { + #[inline] + fn from(err: PosixError) -> i32 { + err.to_errno() as i32 + } +} + +impl TryFrom for PosixError { + type Error = (); + + #[inline] + fn try_from(value: i32) -> Result { + let narrow = i16::try_from(value).map_err(|_| ())?; + Self::try_from(narrow) + } +} + +impl From for isize { + #[inline] + fn from(err: PosixError) -> isize { + err.to_errno() as isize } } -impl TryFrom for UnixError { +impl TryFrom for PosixError { type Error = (); + #[inline] fn try_from(value: isize) -> Result { - // Only accept negative values (syscall error returns) - if value >= 0 { - return Err(()); - } + let narrow = i16::try_from(value).map_err(|_| ())?; + Self::try_from(narrow) + } +} - match value { - -1 => Ok(UnixError::EPERM), - -2 => Ok(UnixError::ENOENT), - -3 => Ok(UnixError::ESRCH), - -4 => Ok(UnixError::EINTR), - -5 => Ok(UnixError::EIO), - -6 => Ok(UnixError::ENXIO), - -7 => Ok(UnixError::E2BIG), - -8 => Ok(UnixError::ENOEXEC), - -9 => Ok(UnixError::EBADF), - -10 => Ok(UnixError::ECHILD), - -11 => Ok(UnixError::EAGAIN), - -12 => Ok(UnixError::ENOMEM), - -13 => Ok(UnixError::EACCES), - -14 => Ok(UnixError::EFAULT), - -15 => Ok(UnixError::ENOTBLK), - -16 => Ok(UnixError::EBUSY), - -17 => Ok(UnixError::EEXIST), - -18 => Ok(UnixError::EXDEV), - -19 => Ok(UnixError::ENODEV), - -20 => Ok(UnixError::ENOTDIR), - -21 => Ok(UnixError::EISDIR), - -22 => Ok(UnixError::EINVAL), - -23 => Ok(UnixError::ENFILE), - -24 => Ok(UnixError::EMFILE), - -25 => Ok(UnixError::ENOTTY), - -26 => Ok(UnixError::ETXTBSY), - -27 => Ok(UnixError::EFBIG), - -28 => Ok(UnixError::ENOSPC), - -29 => Ok(UnixError::ESPIPE), - -30 => Ok(UnixError::EROFS), - -31 => Ok(UnixError::EMLINK), - -32 => Ok(UnixError::EPIPE), - -33 => Ok(UnixError::EDOM), - -34 => Ok(UnixError::ERANGE), - -35 => Ok(UnixError::EDEADLK), - -36 => Ok(UnixError::ENAMETOOLONG), - -37 => Ok(UnixError::ENOLCK), - -38 => Ok(UnixError::ENOSYS), - -39 => Ok(UnixError::ENOTEMPTY), - -40 => Ok(UnixError::ELOOP), - -42 => Ok(UnixError::ENOMSG), - -43 => Ok(UnixError::EIDRM), - -44 => Ok(UnixError::ECHRNG), - -45 => Ok(UnixError::EL2NSYNC), - -46 => Ok(UnixError::EL3HLT), - -47 => Ok(UnixError::EL3RST), - -48 => Ok(UnixError::ELNRNG), - -49 => Ok(UnixError::EUNATCH), - -50 => Ok(UnixError::ENOCSI), - -51 => Ok(UnixError::EL2HLT), - -52 => Ok(UnixError::EBADE), - -53 => Ok(UnixError::EBADR), - -54 => Ok(UnixError::EXFULL), - -55 => Ok(UnixError::ENOANO), - -56 => Ok(UnixError::EBADRQC), - -57 => Ok(UnixError::EBADSLT), - -59 => Ok(UnixError::EBFONT), - -60 => Ok(UnixError::ENOSTR), - -61 => Ok(UnixError::ENODATA), - -62 => Ok(UnixError::ETIME), - -63 => Ok(UnixError::ENOSR), - -64 => Ok(UnixError::ENONET), - -65 => Ok(UnixError::ENOPKG), - -66 => Ok(UnixError::EREMOTE), - -67 => Ok(UnixError::ENOLINK), - -68 => Ok(UnixError::EADV), - -69 => Ok(UnixError::ESRMNT), - -70 => Ok(UnixError::ECOMM), - -71 => Ok(UnixError::EPROTO), - -72 => Ok(UnixError::EMULTIHOP), - -73 => Ok(UnixError::EDOTDOT), - -74 => Ok(UnixError::EBADMSG), - -75 => Ok(UnixError::EOVERFLOW), - -76 => Ok(UnixError::ENOTUNIQ), - -77 => Ok(UnixError::EBADFD), - -78 => Ok(UnixError::EREMCHG), - -79 => Ok(UnixError::ELIBACC), - -80 => Ok(UnixError::ELIBBAD), - -81 => Ok(UnixError::ELIBSCN), - -82 => Ok(UnixError::ELIBMAX), - -83 => Ok(UnixError::ELIBEXEC), - -84 => Ok(UnixError::EILSEQ), - -85 => Ok(UnixError::ERESTART), - -86 => Ok(UnixError::ESTRPIPE), - -87 => Ok(UnixError::EUSERS), - -88 => Ok(UnixError::ENOTSOCK), - -89 => Ok(UnixError::EDESTADDRREQ), - -90 => Ok(UnixError::EMSGSIZE), - -91 => Ok(UnixError::EPROTOTYPE), - -92 => Ok(UnixError::ENOPROTOOPT), - -93 => Ok(UnixError::EPROTONOSUPPORT), - -94 => Ok(UnixError::ESOCKTNOSUPPORT), - -95 => Ok(UnixError::EOPNOTSUPP), - -96 => Ok(UnixError::EPFNOSUPPORT), - -97 => Ok(UnixError::EAFNOSUPPORT), - -98 => Ok(UnixError::EADDRINUSE), - -99 => Ok(UnixError::EADDRNOTAVAIL), - -100 => Ok(UnixError::ENETDOWN), - -101 => Ok(UnixError::ENETUNREACH), - -102 => Ok(UnixError::ENETRESET), - -103 => Ok(UnixError::ECONNABORTED), - -104 => Ok(UnixError::ECONNRESET), - -105 => Ok(UnixError::ENOBUFS), - -106 => Ok(UnixError::EISCONN), - -107 => Ok(UnixError::ENOTCONN), - -108 => Ok(UnixError::ESHUTDOWN), - -109 => Ok(UnixError::ETOOMANYREFS), - -110 => Ok(UnixError::ETIMEDOUT), - -111 => Ok(UnixError::ECONNREFUSED), - -112 => Ok(UnixError::EHOSTDOWN), - -113 => Ok(UnixError::EHOSTUNREACH), - -114 => Ok(UnixError::EALREADY), - -115 => Ok(UnixError::EINPROGRESS), - -116 => Ok(UnixError::ESTALE), - -117 => Ok(UnixError::EUCLEAN), - -118 => Ok(UnixError::ENOTNAM), - -119 => Ok(UnixError::ENAVAIL), - -120 => Ok(UnixError::EISNAM), - -121 => Ok(UnixError::EREMOTEIO), - -122 => Ok(UnixError::EDQUOT), - -123 => Ok(UnixError::ENOMEDIUM), - -124 => Ok(UnixError::EMEDIUMTYPE), - -125 => Ok(UnixError::ECANCELED), - -126 => Ok(UnixError::ENOKEY), - -127 => Ok(UnixError::EKEYEXPIRED), - -128 => Ok(UnixError::EKEYREVOKED), - -129 => Ok(UnixError::EKEYREJECTED), - -130 => Ok(UnixError::EOWNERDEAD), - -131 => Ok(UnixError::ENOTRECOVERABLE), - -132 => Ok(UnixError::ERFKILL), - -133 => Ok(UnixError::EHWPOISON), - _ => Err(()), - } +impl From for usize { + #[inline] + fn from(err: PosixError) -> usize { + err.to_errno() as usize } } \ No newline at end of file diff --git a/src/lib.rs b/src/lib.rs index 86e43f3..510cf18 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -16,7 +16,7 @@ pub mod syscalls; pub mod time; pub mod uspace; pub mod modules; -mod error; +pub mod error; use hal::Machinelike; use interface::BootInfo; diff --git a/src/modules/sample_module.rs b/src/modules/sample_module.rs index 0ad50df..0db035c 100644 --- a/src/modules/sample_module.rs +++ b/src/modules/sample_module.rs @@ -1,27 +1,28 @@ -use macros::{kernel_exit, kernel_init, kernelmod_call}; -/// This is a sample kernel module. It doesn't do anything useful, but it serves as an example of how to write a kernel module and how to use the macros. -/// The kernelmodule system offers 3 macros for integrating modules into the kernel. While building, the build system will then automatically generate a userspace API compatible to the currently installed kernelmodules. -/// Below, stubs of the three types of methods can be found. Node that the signature needs to match the one provided in the samples and especially that thbe return type of the kernelmodule_call needs to be at most register sized. -/// The macros are used to detect the modules during build. Below, the macros are commented out to avoid inclusion of this sample module. +use macros::{kernelmod_exit, kernelmod_init, kernelmod_call}; +use crate::error::PosixError; +// This is a sample kernel module. It doesn't do anything useful, but it serves as an example of how to write a kernel module and how to use the macros. +// The kernelmodule system offers 3 macros for integrating modules into the kernel. While building, the build system will then automatically generate a userspace API compatible to the currently installed kernelmodules. +// Below, stubs of the three types of methods can be found. Node that the signature needs to match the one provided in the samples and especially that thbe return type of the kernelmodule_call needs to be at most register sized. +// The macros are used to detect the modules during build. Below, the macros are commented out to avoid inclusion of this sample module. -#[kernel_init] +//#[kernelmod_init] pub fn init() { - /// This function is called once on kernel startup for each module. It should be used for initializing any state the module requires. Currently, there are no guarantees for the initialization order. + // This function is called once on kernel startup for each module. It should be used for initializing any state the module requires. Currently, there are no guarantees for the initialization order. } -#[kernel_exit] +//#[kernelmod_exit] pub fn exit() { - /// This function is called once on kernel shutdown for each module. It should be used for cleaning up any state the module requires. Currently, there are no guarantees for the exit order. + // This function is called once on kernel shutdown for each module. It should be used for cleaning up any state the module requires. Currently, there are no guarantees for the exit order. } -#[kernelmod_call] -pub fn call(target: i32) -> Result { - /// This function represents a kernel module call. The build system generates an equivalent userspace wrapper. - /// References are transmitted using pointers, slices and string slices are internally transmitted as pointers and lengths +//#[kernelmod_call] +pub fn call(target: i32) -> Result { + // This function represents a kernel module call. The build system generates an equivalent userspace wrapper. + // References are transmitted using pointers, slices and string slices are internally transmitted as pointers and lengths match target { 1 => Ok(0), - 2 => Err(UnixError::InvalidArgument), - _ => Err(UnixError::Unknown), + 2 => Err(PosixError::EINVAL), + _ => Err(PosixError::EPERM), } } \ No newline at end of file From aef491da63d4ab49dd3cc869981607b7d1834270 Mon Sep 17 00:00:00 2001 From: Jakob Fuchs Date: Sun, 1 Mar 2026 21:51:41 +0100 Subject: [PATCH 7/9] fixed Doc comment for POSIX Errors --- src/error.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/error.rs b/src/error.rs index a6561f8..5b201b1 100644 --- a/src/error.rs +++ b/src/error.rs @@ -1,4 +1,4 @@ -/// Unix error codes enum covering all standard errno values. +/// Posix error codes enum covering all standard errno values. /// Values are stored as negative integers matching kernel return values. /// /// Source: Linux kernel include/uapi/asm-generic/errno-base.h (1–34) From b1458b2438bb636a66106f260414d8fffcf9edc3 Mon Sep 17 00:00:00 2001 From: Jakob Fuchs Date: Mon, 2 Mar 2026 15:51:22 +0100 Subject: [PATCH 8/9] Implemented copilot feedback --- build.rs | 72 +++++++++++---- machine/testing/src/asm.rs | 2 +- macros/src/lib.rs | 170 ++++++++++++++++++++++++++++------- src/error.rs | 2 +- src/lib.rs | 2 +- src/modules.rs | 12 +-- src/modules/sample_module.rs | 8 +- 7 files changed, 200 insertions(+), 68 deletions(-) diff --git a/build.rs b/build.rs index 9150210..f35c161 100644 --- a/build.rs +++ b/build.rs @@ -287,7 +287,7 @@ fn has_attribute(attrs: &[Attribute], attr_name: &str) -> bool { fn collect_kernel_modules(root: &str) -> Result, std::io::Error> { let mut modules = Vec::new(); let manifest_dir = std::env::var("CARGO_MANIFEST_DIR").unwrap(); - + for entry in WalkDir::new(root) { let entry = match entry { Ok(entry) => entry, @@ -339,7 +339,7 @@ fn collect_kernel_modules(root: &str) -> Result, std::io::Erro for item in file.items { if let syn::Item::Fn(item_fn) = item { let fn_name = item_fn.sig.ident.to_string(); - + //Store kernelmod_init function if has_attribute(&item_fn.attrs, "kernelmod_init") { if module.init_fn.is_some() { @@ -379,6 +379,9 @@ fn collect_kernel_modules(root: &str) -> Result, std::io::Erro } } + // Deterministic ordering — critical for index agreement between kernel and uspace + modules.sort_by(|a, b| a.name.cmp(&b.name)); + Ok(modules) } @@ -429,7 +432,7 @@ fn generate_modules_kernel(root: &str) -> Result<(), std::io::Error> { // 4. Lookup table with function pointers writeln!(file, "/// Lookup table for kernel module call wrappers")?; - writeln!(file, "type KernelModCallFn = unsafe fn(*const u8) -> usize;")?; + writeln!(file, "type KernelModCallFn = unsafe fn(*const u8) -> isize;")?; writeln!(file)?; writeln!(file, "const KERNELMOD_CALL_TABLE: &[KernelModCallFn] = &[")?; @@ -453,10 +456,8 @@ fn generate_modules_kernel(root: &str) -> Result<(), std::io::Error> { writeln!(file, " let func = KERNELMOD_CALL_TABLE[index];")?; writeln!(file, " // SAFETY: index is a function address collected from the existing modules during build.")?; writeln!(file, " // data is a pointer constructed by the userspace wrapper, which is automatically generated from the kernelmodule definition")?; - writeln!(file, " unsafe {{")?; - writeln!(file, " let result = func(data);")?; - writeln!(file, " result as i32")?; - writeln!(file, " }}")?; + writeln!(file, " // The isize -> i32 truncation is safe: Ok values are at most usize-sized and error codes fit in i16.")?; + writeln!(file, " unsafe {{ func(data) as i32 }}")?; writeln!(file, "}}")?; Ok(()) } @@ -518,6 +519,39 @@ fn snake_to_pascal(s: &str) -> String { .collect() } +/// Extracts the Ok type from a `Result` return type at the AST level. +/// Returns Some(T_string) if the return type is Result, None otherwise. +fn extract_result_ok_type(output: &syn::ReturnType) -> Option { + let ty = match output { + syn::ReturnType::Type(_, ty) => ty, + syn::ReturnType::Default => return None, + }; + + let type_path = match ty.as_ref() { + Type::Path(tp) => tp, + _ => return None, + }; + + let last_seg = type_path.path.segments.last()?; + if last_seg.ident != "Result" { + return None; + } + + let args = match &last_seg.arguments { + syn::PathArguments::AngleBracketed(ab) => &ab.args, + _ => return None, + }; + + // First type argument is the Ok type + for arg in args.iter().take(1) { + if let syn::GenericArgument::Type(t) = arg { + return Some(t.to_token_stream().to_string()); + } + } + + None +} + fn generate_userspace_function( file: &mut File, func: &KernelModuleCallFn, @@ -554,12 +588,16 @@ fn generate_userspace_function( .map(|arg| arg.to_token_stream().to_string()) .collect::>() .join(", "); - + let return_type_str = match &func.output { syn::ReturnType::Default => "Result<(), PosixError>".to_string(), syn::ReturnType::Type(_, ty) => ty.to_token_stream().to_string(), }; + // Extract the Ok type directly from the AST for clear compile-time checks + let ok_type_str = extract_result_ok_type(&func.output) + .unwrap_or_else(|| "()".to_string()); + writeln!(file, " pub fn {}({}) -> {} {{", fn_name, inputs_str, return_type_str)?; // Compile-time size checks for value-type arguments @@ -578,13 +616,13 @@ fn generate_userspace_function( } writeln!(file)?; + // Compile-time checks on the Ok type using the extracted type directly writeln!(file, " const _: () = {{")?; - writeln!(file, " trait __RetOkSize {{ type Ok; }}")?; - writeln!(file, " impl __RetOkSize for core::result::Result {{ type Ok = T; }}")?; - writeln!(file, " if core::mem::size_of::<<{} as __RetOkSize>::Ok>() > core::mem::size_of::() {{", return_type_str)?; + writeln!(file, " if core::mem::size_of::<{}>() > core::mem::size_of::() {{", ok_type_str)?; writeln!(file, " panic!(\"kernelmod_call: Ok return type is bigger than usize. must fit in a register.\");")?; writeln!(file, " }}")?; writeln!(file, " }};")?; + writeln!(file, " const _: fn() = || {{ fn assert_copy() {{}} assert_copy::<{}>(); }};", ok_type_str)?; writeln!(file)?; // Layout call arguments @@ -607,17 +645,15 @@ fn generate_userspace_function( writeln!(file, " .unwrap_or(PosixError::ENOSYS);")?; writeln!(file, " Err(err)")?; writeln!(file, " }} else {{")?; - - writeln!(file, " trait __RetOkType {{ type Ok; }}")?; - writeln!(file, " impl __RetOkType for core::result::Result {{ type Ok = T; }}")?; - writeln!(file)?; - writeln!(file, " // SAFETY: the compile-time assert above guarantees the Ok type fits in usize.")?; + + // Reconstruct the Ok value from the isize result using the extracted type directly + writeln!(file, " // SAFETY: the compile-time assert above guarantees the Ok type fits in isize.")?; writeln!(file, " let val = unsafe {{")?; - writeln!(file, " let mut out = core::mem::MaybeUninit::<<{} as __RetOkType>::Ok>::zeroed();", return_type_str)?; + writeln!(file, " let mut out = core::mem::MaybeUninit::<{}>::zeroed();", ok_type_str)?; writeln!(file, " core::ptr::copy_nonoverlapping(")?; writeln!(file, " &result as *const _ as *const u8,")?; writeln!(file, " out.as_mut_ptr() as *mut u8,")?; - writeln!(file, " core::mem::size_of::<<{} as __RetOkType>::Ok>(),", return_type_str)?; + writeln!(file, " core::mem::size_of::<{}>(),", ok_type_str)?; writeln!(file, " );")?; writeln!(file, " out.assume_init()")?; writeln!(file, " }};")?; diff --git a/machine/testing/src/asm.rs b/machine/testing/src/asm.rs index 64633a8..c41cf80 100644 --- a/machine/testing/src/asm.rs +++ b/machine/testing/src/asm.rs @@ -11,7 +11,7 @@ pub use crate::__macro_nop as nop; /// Macro for doing a system call. #[macro_export] macro_rules! __macro_syscall { - ($num:expr) => {{}}; + ($num:expr) => {{0isize}}; ($num:expr, $arg0:expr) => {{0isize}}; ($num:expr, $arg0:expr, $arg1:expr) => {{0isize}}; ($num:expr, $arg0:expr, $arg1:expr, $arg2:expr) => {{0isize}}; diff --git a/macros/src/lib.rs b/macros/src/lib.rs index 7fdf939..b57c7b7 100644 --- a/macros/src/lib.rs +++ b/macros/src/lib.rs @@ -1,6 +1,6 @@ use quote::{quote, quote_spanned}; use quote::{ToTokens, format_ident}; -use syn::{parse_macro_input, Error, FnArg, Pat, PatType, ReturnType, Type, TypeReference, TypeSlice}; +use syn::{parse_macro_input, Error, FnArg, GenericArgument, Pat, PatType, PathArguments, ReturnType, Type, TypeReference, TypeSlice}; use syn::ItemFn; use proc_macro2::TokenStream; @@ -242,6 +242,121 @@ fn snake_to_pascal(s: &str) -> String { .collect() } +/// Structurally validates that the return type is `Result` and +/// extracts the `T` (Ok type). +/// +/// Checks performed (all at the AST / syntactic level): +/// 1. A return type is present (not `-> ()`-less). +/// 2. The outermost type path ends with `Result`. +/// 3. `Result` has exactly two generic type arguments. +/// 4. The second type argument's path ends with `PosixError`. +/// +/// Returns the first generic argument (`T`) on success so the caller can emit +/// targeted size / Copy checks against it. +fn validate_and_extract_result_ok_type(return_type: &ReturnType) -> Result { + // 1. Must have an explicit return type. + let ret_ty = match return_type { + ReturnType::Default => { + return Err(Error::new_spanned( + return_type, + "kernelmod_call: function must have an explicit return type.\n\ + Expected `Result` where T: Copy + size_of::() <= size_of::()" + )); + } + ReturnType::Type(_, ty) => ty, + }; + + // 2. Outer type must be a path whose last segment is `Result`. + let type_path = match ret_ty.as_ref() { + Type::Path(tp) => tp, + _ => { + return Err(Error::new_spanned( + ret_ty, + "kernelmod_call: return type must be `Result`.\n\ + Got a non-path type (e.g. reference, tuple, etc.)." + )); + } + }; + + let last_segment = type_path.path.segments.last().ok_or_else(|| { + Error::new_spanned(&type_path.path, "kernelmod_call: empty type path in return position") + })?; + + if last_segment.ident != "Result" { + return Err(Error::new_spanned( + &last_segment.ident, + format!( + "kernelmod_call: return type must be `Result`, but found `{}`.\n\ + Hint: the outermost type must be `Result`.", + last_segment.ident + ) + )); + } + + // 3. Must have angle-bracketed generics with exactly 2 type arguments. + let args = match &last_segment.arguments { + PathArguments::AngleBracketed(ab) => &ab.args, + PathArguments::None => { + return Err(Error::new_spanned( + last_segment, + "kernelmod_call: `Result` must have type parameters.\n\ + Expected `Result`." + )); + } + PathArguments::Parenthesized(_) => { + return Err(Error::new_spanned( + last_segment, + "kernelmod_call: `Result` must use angle-bracket generics.\n\ + Expected `Result`, not `Result(...)`." + )); + } + }; + + let type_args: Vec<&Type> = args + .iter() + .filter_map(|arg| match arg { + GenericArgument::Type(ty) => Some(ty), + _ => None, + }) + .collect(); + + if type_args.len() != 2 { + return Err(Error::new_spanned( + last_segment, + format!( + "kernelmod_call: `Result` must have exactly 2 type parameters, found {}.\n\ + Expected `Result`.", + type_args.len() + ) + )); + } + + let ok_ty = type_args[0]; + let err_ty = type_args[1]; + + // 4. The error type must be (or end with) `PosixError`. + let err_ident = match err_ty { + Type::Path(tp) => tp.path.segments.last().map(|s| &s.ident), + _ => None, + }; + + match err_ident { + Some(ident) if ident == "PosixError" => {} + _ => { + return Err(Error::new_spanned( + err_ty, + format!( + "kernelmod_call: error type must be `PosixError`, but found `{}`.\n\ + Expected `Result`.", + err_ty.to_token_stream() + ) + )); + } + } + + Ok(ok_ty.clone()) +} + fn generate_wrapper(input: ItemFn) -> Result { let fn_name = &input.sig.ident; let fn_name_str = fn_name.to_string(); @@ -258,7 +373,7 @@ fn generate_wrapper(input: ItemFn) -> Result { let pascal_name = snake_to_pascal(&fn_name_str); let args_struct_name = syn::Ident::new(&format!("__{}Args", pascal_name), fn_name.span()); - validate_return_type(&input.sig.output)?; + let ok_ty = validate_and_extract_result_ok_type(&input.sig.output)?; let mut arg_fields = Vec::new(); let mut arg_names = Vec::new(); @@ -293,19 +408,22 @@ fn generate_wrapper(input: ItemFn) -> Result { } } - let ret_size_check = match &input.sig.output { - ReturnType::Type(_, ty) => { - quote! { - const _: () = { - trait __RetOkSize { type Ok; } - impl __RetOkSize for core::result::Result { type Ok = T; } - if core::mem::size_of::<<#ty as __RetOkSize>::Ok>() > core::mem::size_of::() { - panic!("kernelmod_call: Ok return type is bigger than usize. must fit in a register."); - } - }; + // Emit compile-time checks against the *extracted* Ok type directly. + // This gives clear errors instead of opaque trait-resolution failures. + let ret_size_check = quote_spanned! { ok_ty.span() => + const _: () = { + if core::mem::size_of::<#ok_ty>() > core::mem::size_of::() { + panic!( + "kernelmod_call: the Ok type in Result is larger than usize. \ + T must fit in a single register." + ); } - } - _ => quote! {}, + }; + // Compile-time Copy bound check on T. + const _: fn() = || { + fn assert_copy() {} + assert_copy::<#ok_ty>(); + }; }; let return_handling = generate_return_handling(&input.sig.output)?; @@ -322,7 +440,7 @@ fn generate_wrapper(input: ItemFn) -> Result { #(#arg_fields),* } - #fn_vis unsafe fn #wrapper_name(args_ptr: *const u8) -> usize { + #fn_vis unsafe fn #wrapper_name(args_ptr: *const u8) -> isize { let args = &*(args_ptr as *const #args_struct_name); #(#arg_reconstructions)* @@ -437,41 +555,27 @@ fn validate_and_generate_field( } } -fn validate_return_type(return_type: &ReturnType) -> Result<(), Error> { - match return_type { - ReturnType::Default => { - Err(Error::new_spanned( - return_type, - "kernelmod_call: function must have an explicit return type (expected Result)" - )) - } - ReturnType::Type(_, _) => { - Ok(()) - } - } -} - fn generate_return_handling(return_type: &ReturnType) -> Result { match return_type { ReturnType::Type(_, _ty) => { Ok(quote! { - // SAFETY: `val` is guaranteed at most `size_of::()` by the + // SAFETY: `val` is guaranteed at most `size_of::()` by the // compile-time const assert emitted alongside this wrapper. // `raw` is zero-initialised so unwritten bytes remain zero. // Both pointers are stack-local, valid, aligned, and non-overlapping. match result { Ok(val) => { - let mut raw: usize = 0; + let mut raw: isize = 0; unsafe { core::ptr::copy_nonoverlapping( &val as *const _ as *const u8, - &mut raw as *mut usize as *mut u8, + &mut raw as *mut isize as *mut u8, core::mem::size_of_val(&val), ); } raw } - Err(e) => e as usize, + Err(e) => e as isize, } }) } diff --git a/src/error.rs b/src/error.rs index 5b201b1..ef12439 100644 --- a/src/error.rs +++ b/src/error.rs @@ -314,7 +314,7 @@ impl TryFrom for PosixError { return Err(()); } // SAFETY: - // The range of error values is padded with reserved fields to be continous, so every value in the range is a valid PosixError variant + // The range of error values is padded with reserved fields to be continuous, so every value in the range is a valid PosixError variant // The bounds are validated against the provided MIN...MAX range Ok(unsafe { core::mem::transmute(value) }) } diff --git a/src/lib.rs b/src/lib.rs index 510cf18..f259047 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -35,7 +35,7 @@ pub unsafe extern "C" fn kernel_init(boot_info: *const BootInfo) -> ! { // Initialize basic hardware and the logging system. hal::Machine::init(); hal::Machine::bench_start(); - + modules::init_modules(); if boot_info.is_null() || !boot_info.is_aligned() { panic!("[Kernel] Error: boot_info pointer is null or unaligned."); } diff --git a/src/modules.rs b/src/modules.rs index f27ba1a..8333bb1 100644 --- a/src/modules.rs +++ b/src/modules.rs @@ -2,20 +2,12 @@ include!(concat!(env!("OUT_DIR"), "/modules_kernel.rs")); -use crate::utils::KernelError; -trait KernelModule { - fn init(&mut self) -> Result<(), KernelError>; - fn exit(&mut self) -> Result<(), KernelError>; - fn name(&self) -> &'static str; -} - - -fn init_modules() { +pub(crate) fn init_modules() { __init_modules() } -fn exit_modules() { +pub(crate)fn exit_modules() { __exit_modules() } diff --git a/src/modules/sample_module.rs b/src/modules/sample_module.rs index 0db035c..fb92347 100644 --- a/src/modules/sample_module.rs +++ b/src/modules/sample_module.rs @@ -2,21 +2,21 @@ use macros::{kernelmod_exit, kernelmod_init, kernelmod_call}; use crate::error::PosixError; // This is a sample kernel module. It doesn't do anything useful, but it serves as an example of how to write a kernel module and how to use the macros. // The kernelmodule system offers 3 macros for integrating modules into the kernel. While building, the build system will then automatically generate a userspace API compatible to the currently installed kernelmodules. -// Below, stubs of the three types of methods can be found. Node that the signature needs to match the one provided in the samples and especially that thbe return type of the kernelmodule_call needs to be at most register sized. +// Below, stubs of the three types of methods can be found. Note that the signature needs to match the one provided in the samples and especially that the return type of the kernelmodule_call needs to be at most register sized. // The macros are used to detect the modules during build. Below, the macros are commented out to avoid inclusion of this sample module. -//#[kernelmod_init] +#[kernelmod_init] pub fn init() { // This function is called once on kernel startup for each module. It should be used for initializing any state the module requires. Currently, there are no guarantees for the initialization order. } -//#[kernelmod_exit] +#[kernelmod_exit] pub fn exit() { // This function is called once on kernel shutdown for each module. It should be used for cleaning up any state the module requires. Currently, there are no guarantees for the exit order. } -//#[kernelmod_call] +#[kernelmod_call] pub fn call(target: i32) -> Result { // This function represents a kernel module call. The build system generates an equivalent userspace wrapper. // References are transmitted using pointers, slices and string slices are internally transmitted as pointers and lengths From 876aad723d9d86a9afd0b7893cd50d43c5938604 Mon Sep 17 00:00:00 2001 From: Jakob Fuchs Date: Tue, 3 Mar 2026 00:37:17 +0100 Subject: [PATCH 9/9] Formatting --- build.rs | 167 +++++++++++++++++++++++++++---------- machine/arm/src/asm.rs | 6 +- machine/testing/src/asm.rs | 10 +-- macros/src/lib.rs | 89 +++++++++++++------- src/error.rs | 4 +- src/lib.rs | 4 +- src/modules.rs | 7 +- src/utils.rs | 3 - 8 files changed, 196 insertions(+), 94 deletions(-) diff --git a/build.rs b/build.rs index f35c161..43db05d 100644 --- a/build.rs +++ b/build.rs @@ -7,7 +7,10 @@ extern crate walkdir; use cfg_aliases::cfg_aliases; use quote::ToTokens; use std::io::Write; -use syn::{Attribute, FnArg, LitInt, punctuated::Punctuated, token::Comma,Pat, PatType, Type, TypeReference, TypeSlice}; +use syn::{ + Attribute, FnArg, LitInt, Pat, PatType, Type, TypeReference, TypeSlice, punctuated::Punctuated, + token::Comma, +}; use walkdir::WalkDir; extern crate cbindgen; @@ -19,7 +22,8 @@ fn main() { generate_syscall_map("src/syscalls").expect("Failed to generate syscall map."); generate_syscalls_export("src/syscalls").expect("Failed to generate syscall exports."); generate_modules_kernel("src/modules/").expect("Failed to generate kernel modules."); - generate_modules_uspace("src/modules/").expect("Failed to generate userspace kernel module wrappers."); + generate_modules_uspace("src/modules/") + .expect("Failed to generate userspace kernel module wrappers."); // Get linker script from environment variable if let Ok(linker_script) = std::env::var("DEP_HAL_LINKER_SCRIPT") { @@ -67,19 +71,19 @@ fn get_arg_names(args: &str) -> String { ", ".to_owned() + &args.chars().fold("".to_owned(), |mut acc, char| { - if char.eq(&' ') { - in_arg_name = false; - return acc; - } - if char.eq(&',') { - in_arg_name = true; - return acc + ", "; - } - if in_arg_name { - acc.push(char); - } - acc - }) + if char.eq(&' ') { + in_arg_name = false; + return acc; + } + if char.eq(&',') { + in_arg_name = true; + return acc + ", "; + } + if in_arg_name { + acc.push(char); + } + acc + }) } fn generate_syscall_map>(root: P) -> Result<(), std::io::Error> { @@ -296,7 +300,8 @@ fn collect_kernel_modules(root: &str) -> Result, std::io::Erro } }; - if entry.file_type().is_file() && entry.path().extension().map_or(false, |ext| ext == "rs") { + if entry.file_type().is_file() && entry.path().extension().map_or(false, |ext| ext == "rs") + { let path = entry.path(); println!("Processing kernel module file: {}", path.display()); @@ -323,7 +328,8 @@ fn collect_kernel_modules(root: &str) -> Result, std::io::Erro // Compute absolute path for include!() in generated code let abs_path = Path::new(&manifest_dir).join(path); - let source_path = abs_path.canonicalize() + let source_path = abs_path + .canonicalize() .unwrap_or(abs_path) .to_string_lossy() .replace('\\', "/"); @@ -366,7 +372,8 @@ fn collect_kernel_modules(root: &str) -> Result, std::io::Erro } // Only add module if it has at least one of the attributes - let has_any_attr = module.init_fn.is_some() || module.exit_fn.is_some() || !module.call_fns.is_empty(); + let has_any_attr = + module.init_fn.is_some() || module.exit_fn.is_some() || !module.call_fns.is_empty(); if has_any_attr { if module.init_fn.is_none() { panic!("Module {module_name} is missing a #[kernelmod_init] function"); @@ -432,7 +439,10 @@ fn generate_modules_kernel(root: &str) -> Result<(), std::io::Error> { // 4. Lookup table with function pointers writeln!(file, "/// Lookup table for kernel module call wrappers")?; - writeln!(file, "type KernelModCallFn = unsafe fn(*const u8) -> isize;")?; + writeln!( + file, + "type KernelModCallFn = unsafe fn(*const u8) -> isize;" + )?; writeln!(file)?; writeln!(file, "const KERNELMOD_CALL_TABLE: &[KernelModCallFn] = &[")?; @@ -448,22 +458,33 @@ fn generate_modules_kernel(root: &str) -> Result<(), std::io::Error> { // 5. execute_kernelmodcall function for call dispatch writeln!(file, "/// Execute a kernel module call by index")?; writeln!(file, "#[syscall_handler(num = 255)]")?; - writeln!(file, "pub fn execute_kernelmodcall(index: usize, data: *const u8) -> i32 {{")?; + writeln!( + file, + "pub fn execute_kernelmodcall(index: usize, data: *const u8) -> i32 {{" + )?; writeln!(file, " if index >= KERNELMOD_CALL_TABLE.len() {{")?; writeln!(file, " return PosixError::ENOSYS as i32;")?; writeln!(file, " }}")?; writeln!(file)?; writeln!(file, " let func = KERNELMOD_CALL_TABLE[index];")?; - writeln!(file, " // SAFETY: index is a function address collected from the existing modules during build.")?; - writeln!(file, " // data is a pointer constructed by the userspace wrapper, which is automatically generated from the kernelmodule definition")?; - writeln!(file, " // The isize -> i32 truncation is safe: Ok values are at most usize-sized and error codes fit in i16.")?; + writeln!( + file, + " // SAFETY: index is a function address collected from the existing modules during build." + )?; + writeln!( + file, + " // data is a pointer constructed by the userspace wrapper, which is automatically generated from the kernelmodule definition" + )?; + writeln!( + file, + " // The isize -> i32 truncation is safe: Ok values are at most usize-sized and error codes fit in i16." + )?; writeln!(file, " unsafe {{ func(data) as i32 }}")?; writeln!(file, "}}")?; Ok(()) } // ===== Userspace Wrapper Generation ===== - fn generate_modules_uspace(root: &str) -> Result<(), std::io::Error> { let modules = collect_kernel_modules(root)?; @@ -501,7 +522,10 @@ fn generate_modules_uspace(root: &str) -> Result<(), std::io::Error> { writeln!(file)?; } - println!("cargo::warning=Generated userspace kernel modules file with {} functions", global_index); + println!( + "cargo::warning=Generated userspace kernel modules file with {} functions", + global_index + ); Ok(()) } @@ -583,7 +607,8 @@ fn generate_userspace_function( writeln!(file)?; // Generate the function signature - let inputs_str = func.inputs + let inputs_str = func + .inputs .iter() .map(|arg| arg.to_token_stream().to_string()) .collect::>() @@ -595,10 +620,13 @@ fn generate_userspace_function( }; // Extract the Ok type directly from the AST for clear compile-time checks - let ok_type_str = extract_result_ok_type(&func.output) - .unwrap_or_else(|| "()".to_string()); + let ok_type_str = extract_result_ok_type(&func.output).unwrap_or_else(|| "()".to_string()); - writeln!(file, " pub fn {}({}) -> {} {{", fn_name, inputs_str, return_type_str)?; + writeln!( + file, + " pub fn {}({}) -> {} {{", + fn_name, inputs_str, return_type_str + )?; // Compile-time size checks for value-type arguments for arg in &func.inputs { @@ -606,11 +634,23 @@ fn generate_userspace_function( if let Type::Path(_) = &*pat_type.ty { let ty_str = pat_type.ty.to_token_stream().to_string(); writeln!(file, " const _: () = {{")?; - writeln!(file, " if core::mem::size_of::<{}>() > core::mem::size_of::() {{", ty_str)?; - writeln!(file, " panic!(\"kernelmod_call: argument type is bigger than usize. arguments must fit in a register.\");")?; + writeln!( + file, + " if core::mem::size_of::<{}>() > core::mem::size_of::() {{", + ty_str + )?; + writeln!( + file, + " panic!(\"kernelmod_call: argument type is bigger than usize. arguments must fit in a register.\");" + )?; writeln!(file, " }}")?; writeln!(file, " }};")?; - writeln!(file, " fn __assert_copy_{}() {{ fn check() {{}} check::<{}>(); }}", pat_type.pat.to_token_stream(), ty_str)?; + writeln!( + file, + " fn __assert_copy_{}() {{ fn check() {{}} check::<{}>(); }}", + pat_type.pat.to_token_stream(), + ty_str + )?; } } } @@ -618,11 +658,22 @@ fn generate_userspace_function( // Compile-time checks on the Ok type using the extracted type directly writeln!(file, " const _: () = {{")?; - writeln!(file, " if core::mem::size_of::<{}>() > core::mem::size_of::() {{", ok_type_str)?; - writeln!(file, " panic!(\"kernelmod_call: Ok return type is bigger than usize. must fit in a register.\");")?; + writeln!( + file, + " if core::mem::size_of::<{}>() > core::mem::size_of::() {{", + ok_type_str + )?; + writeln!( + file, + " panic!(\"kernelmod_call: Ok return type is bigger than usize. must fit in a register.\");" + )?; writeln!(file, " }}")?; writeln!(file, " }};")?; - writeln!(file, " const _: fn() = || {{ fn assert_copy() {{}} assert_copy::<{}>(); }};", ok_type_str)?; + writeln!( + file, + " const _: fn() = || {{ fn assert_copy() {{}} assert_copy::<{}>(); }};", + ok_type_str + )?; writeln!(file)?; // Layout call arguments @@ -635,25 +686,46 @@ fn generate_userspace_function( //Execute syscall with running index writeln!(file, " let result: isize = unsafe {{")?; - writeln!(file, " hal::asm::syscall!(255, {}, &args as *const _ as *const u8)", index)?; + writeln!( + file, + " hal::asm::syscall!(255, {}, &args as *const _ as *const u8)", + index + )?; writeln!(file, " }};")?; writeln!(file)?; // Parse syscall return to Result type writeln!(file, " if result < 0 && result > -134 {{")?; - writeln!(file, " let err: PosixError = PosixError::try_from(result)")?; + writeln!( + file, + " let err: PosixError = PosixError::try_from(result)" + )?; writeln!(file, " .unwrap_or(PosixError::ENOSYS);")?; writeln!(file, " Err(err)")?; writeln!(file, " }} else {{")?; // Reconstruct the Ok value from the isize result using the extracted type directly - writeln!(file, " // SAFETY: the compile-time assert above guarantees the Ok type fits in isize.")?; + writeln!( + file, + " // SAFETY: the compile-time assert above guarantees the Ok type fits in isize." + )?; writeln!(file, " let val = unsafe {{")?; - writeln!(file, " let mut out = core::mem::MaybeUninit::<{}>::zeroed();", ok_type_str)?; + writeln!( + file, + " let mut out = core::mem::MaybeUninit::<{}>::zeroed();", + ok_type_str + )?; writeln!(file, " core::ptr::copy_nonoverlapping(")?; - writeln!(file, " &result as *const _ as *const u8,")?; + writeln!( + file, + " &result as *const _ as *const u8," + )?; writeln!(file, " out.as_mut_ptr() as *mut u8,")?; - writeln!(file, " core::mem::size_of::<{}>(),", ok_type_str)?; + writeln!( + file, + " core::mem::size_of::<{}>(),", + ok_type_str + )?; writeln!(file, " );")?; writeln!(file, " out.assume_init()")?; writeln!(file, " }};")?; @@ -688,7 +760,10 @@ fn generate_args_struct_field( } Type::Reference(type_ref) => { if type_ref.mutability.is_some() { - panic!("Mutable references not supported in kernelmod_call: {}", name); + panic!( + "Mutable references not supported in kernelmod_call: {}", + name + ); } match &*type_ref.elem { @@ -723,9 +798,13 @@ fn generate_args_struct_field( } } _ => { - panic!("Unsupported type in kernelmod_call argument '{}': {}", name, ty.to_token_stream()); + panic!( + "Unsupported type in kernelmod_call argument '{}': {}", + name, + ty.to_token_stream() + ); } } Ok(()) -} \ No newline at end of file +} diff --git a/machine/arm/src/asm.rs b/machine/arm/src/asm.rs index 4a4f099..716b113 100644 --- a/machine/arm/src/asm.rs +++ b/machine/arm/src/asm.rs @@ -100,7 +100,9 @@ macro_rules! __macro_syscall { #[cfg(feature = "host")] #[macro_export] macro_rules! __macro_syscall { - ($num:expr) => { 0isize }; + ($num:expr) => { + 0isize + }; ($num:expr, $arg0:expr) => {{ 0isize }}; ($num:expr, $arg0:expr, $arg1:expr) => {{ 0isize }}; ($num:expr, $arg0:expr, $arg1:expr, $arg2:expr) => {{ 0isize }}; @@ -211,4 +213,4 @@ macro_rules! __macro_fault_do_not_use_under_any_circumstances { () => {{}}; } -pub use crate::__macro_fault_do_not_use_under_any_circumstances as fault_do_not_use_under_any_circumstances; \ No newline at end of file +pub use crate::__macro_fault_do_not_use_under_any_circumstances as fault_do_not_use_under_any_circumstances; diff --git a/machine/testing/src/asm.rs b/machine/testing/src/asm.rs index c41cf80..72114e4 100644 --- a/machine/testing/src/asm.rs +++ b/machine/testing/src/asm.rs @@ -11,11 +11,11 @@ pub use crate::__macro_nop as nop; /// Macro for doing a system call. #[macro_export] macro_rules! __macro_syscall { - ($num:expr) => {{0isize}}; - ($num:expr, $arg0:expr) => {{0isize}}; - ($num:expr, $arg0:expr, $arg1:expr) => {{0isize}}; - ($num:expr, $arg0:expr, $arg1:expr, $arg2:expr) => {{0isize}}; - ($num:expr, $arg0:expr, $arg1:expr, $arg2:expr, $arg3:expr) => {{0isize}}; + ($num:expr) => {{ 0isize }}; + ($num:expr, $arg0:expr) => {{ 0isize }}; + ($num:expr, $arg0:expr, $arg1:expr) => {{ 0isize }}; + ($num:expr, $arg0:expr, $arg1:expr, $arg2:expr) => {{ 0isize }}; + ($num:expr, $arg0:expr, $arg1:expr, $arg2:expr, $arg3:expr) => {{ 0isize }}; } pub use crate::__macro_syscall as syscall; diff --git a/macros/src/lib.rs b/macros/src/lib.rs index b57c7b7..c04dde1 100644 --- a/macros/src/lib.rs +++ b/macros/src/lib.rs @@ -1,7 +1,10 @@ -use quote::{quote, quote_spanned}; use quote::{ToTokens, format_ident}; -use syn::{parse_macro_input, Error, FnArg, GenericArgument, Pat, PatType, PathArguments, ReturnType, Type, TypeReference, TypeSlice}; +use quote::{quote, quote_spanned}; use syn::ItemFn; +use syn::{ + Error, FnArg, GenericArgument, Pat, PatType, PathArguments, ReturnType, Type, TypeReference, + TypeSlice, parse_macro_input, +}; use proc_macro2::TokenStream; use syn::spanned::Spanned; @@ -218,9 +221,11 @@ fn syscall_handler_fn(item: &syn::ItemFn) -> TokenStream { } } - #[proc_macro_attribute] -pub fn kernelmod_call(_attr: proc_macro::TokenStream, item: proc_macro::TokenStream) -> proc_macro::TokenStream { +pub fn kernelmod_call( + _attr: proc_macro::TokenStream, + item: proc_macro::TokenStream, +) -> proc_macro::TokenStream { let input = parse_macro_input!(item as ItemFn); match generate_wrapper(input) { @@ -260,7 +265,7 @@ fn validate_and_extract_result_ok_type(return_type: &ReturnType) -> Result` where T: Copy + size_of::() <= size_of::()" + Expected `Result` where T: Copy + size_of::() <= size_of::()", )); } ReturnType::Type(_, ty) => ty, @@ -273,13 +278,16 @@ fn validate_and_extract_result_ok_type(return_type: &ReturnType) -> Result`.\n\ - Got a non-path type (e.g. reference, tuple, etc.)." + Got a non-path type (e.g. reference, tuple, etc.).", )); } }; let last_segment = type_path.path.segments.last().ok_or_else(|| { - Error::new_spanned(&type_path.path, "kernelmod_call: empty type path in return position") + Error::new_spanned( + &type_path.path, + "kernelmod_call: empty type path in return position", + ) })?; if last_segment.ident != "Result" { @@ -289,7 +297,7 @@ fn validate_and_extract_result_ok_type(return_type: &ReturnType) -> Result`, but found `{}`.\n\ Hint: the outermost type must be `Result`.", last_segment.ident - ) + ), )); } @@ -300,14 +308,14 @@ fn validate_and_extract_result_ok_type(return_type: &ReturnType) -> Result`." + Expected `Result`.", )); } PathArguments::Parenthesized(_) => { return Err(Error::new_spanned( last_segment, "kernelmod_call: `Result` must use angle-bracket generics.\n\ - Expected `Result`, not `Result(...)`." + Expected `Result`, not `Result(...)`.", )); } }; @@ -327,7 +335,7 @@ fn validate_and_extract_result_ok_type(return_type: &ReturnType) -> Result`.", type_args.len() - ) + ), )); } @@ -349,7 +357,7 @@ fn validate_and_extract_result_ok_type(return_type: &ReturnType) -> Result`.", err_ty.to_token_stream() - ) + ), )); } } @@ -364,7 +372,7 @@ fn generate_wrapper(input: ItemFn) -> Result { if fn_name_str.chars().any(|c| c.is_uppercase()) { return Err(Error::new_spanned( fn_name, - "kernelmod_call: function names must be snake_case (no uppercase letters allowed)" + "kernelmod_call: function names must be snake_case (no uppercase letters allowed)", )); } @@ -384,12 +392,20 @@ fn generate_wrapper(input: ItemFn) -> Result { FnArg::Typed(PatType { pat, ty, .. }) => { let name = match &**pat { Pat::Ident(ident) => &ident.ident, - _ => return Err(Error::new_spanned(pat, "Expected simple identifier pattern")), + _ => { + return Err(Error::new_spanned( + pat, + "Expected simple identifier pattern", + )); + } }; (name, ty) } FnArg::Receiver(_) => { - return Err(Error::new_spanned(arg, "Methods with 'self' are not supported")); + return Err(Error::new_spanned( + arg, + "Methods with 'self' are not supported", + )); } }; @@ -456,7 +472,11 @@ fn generate_wrapper(input: ItemFn) -> Result { enum ArgFieldInfo { Direct(proc_macro2::TokenStream, proc_macro2::TokenStream), - Slice(proc_macro2::TokenStream, proc_macro2::TokenStream, proc_macro2::TokenStream), + Slice( + proc_macro2::TokenStream, + proc_macro2::TokenStream, + proc_macro2::TokenStream, + ), } /// Generates the args-struct field(s) and reconstruction code for a single argument. @@ -467,10 +487,7 @@ enum ArgFieldInfo { /// - `Type::Slice` → fat pointer decomposed into (ptr, len) fields /// - `is_ident("str")` → same layout as &[u8], language primitive check /// - other `Type::Path` → thin pointer, single field -fn validate_and_generate_field( - name: &syn::Ident, - ty: &Type, -) -> Result { +fn validate_and_generate_field(name: &syn::Ident, ty: &Type) -> Result { match ty { Type::Path(_) => { let field = quote! { #name: #ty }; @@ -490,17 +507,21 @@ fn validate_and_generate_field( Ok(ArgFieldInfo::Direct(field, reconstruction)) } - Type::Reference(TypeReference { elem, mutability, .. }) => { + Type::Reference(TypeReference { + elem, mutability, .. + }) => { if mutability.is_some() { return Err(Error::new_spanned( ty, - "Mutable references are not supported. Only immutable references are allowed." + "Mutable references are not supported. Only immutable references are allowed.", )); } match &**elem { // &[T] — structurally a slice, decomposed into (ptr, len) - Type::Slice(TypeSlice { elem: slice_elem, .. }) => { + Type::Slice(TypeSlice { + elem: slice_elem, .. + }) => { let ptr_name = syn::Ident::new(&format!("{}_ptr", name), name.span()); let len_name = syn::Ident::new(&format!("{}_len", name), name.span()); @@ -539,8 +560,8 @@ fn validate_and_generate_field( } _ => Err(Error::new_spanned( ty, - "Unsupported reference type. Only references to structs, slices (&[T]), and &str are supported." - )) + "Unsupported reference type. Only references to structs, slices (&[T]), and &str are supported.", + )), } } _ => Err(Error::new_spanned( @@ -550,8 +571,8 @@ fn validate_and_generate_field( - Structs implementing Copy (at most usize)\n\ - References to structs (&T)\n\ - Slices (&[T])\n\ - - String slices (&str)" - )) + - String slices (&str)", + )), } } @@ -579,16 +600,22 @@ fn generate_return_handling(return_type: &ReturnType) -> Result Err(Error::new_spanned(return_type, "Invalid return type")) + _ => Err(Error::new_spanned(return_type, "Invalid return type")), } } #[proc_macro_attribute] -pub fn kernelmod_init(_attr: proc_macro::TokenStream, item: proc_macro::TokenStream) -> proc_macro::TokenStream { +pub fn kernelmod_init( + _attr: proc_macro::TokenStream, + item: proc_macro::TokenStream, +) -> proc_macro::TokenStream { item } #[proc_macro_attribute] -pub fn kernelmod_exit(_attr: proc_macro::TokenStream, item: proc_macro::TokenStream) -> proc_macro::TokenStream { +pub fn kernelmod_exit( + _attr: proc_macro::TokenStream, + item: proc_macro::TokenStream, +) -> proc_macro::TokenStream { item -} \ No newline at end of file +} diff --git a/src/error.rs b/src/error.rs index ef12439..2e8324a 100644 --- a/src/error.rs +++ b/src/error.rs @@ -313,7 +313,7 @@ impl TryFrom for PosixError { if value < Self::MIN || value > Self::MAX { return Err(()); } - // SAFETY: + // SAFETY: // The range of error values is padded with reserved fields to be continuous, so every value in the range is a valid PosixError variant // The bounds are validated against the provided MIN...MAX range Ok(unsafe { core::mem::transmute(value) }) @@ -361,4 +361,4 @@ impl From for usize { fn from(err: PosixError) -> usize { err.to_errno() as usize } -} \ No newline at end of file +} diff --git a/src/lib.rs b/src/lib.rs index f259047..f84f706 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -7,16 +7,16 @@ mod macros; #[macro_use] mod utils; +pub mod error; mod faults; mod mem; +pub mod modules; pub mod print; pub mod sched; pub mod sync; pub mod syscalls; pub mod time; pub mod uspace; -pub mod modules; -pub mod error; use hal::Machinelike; use interface::BootInfo; diff --git a/src/modules.rs b/src/modules.rs index 8333bb1..fbc752a 100644 --- a/src/modules.rs +++ b/src/modules.rs @@ -1,13 +1,10 @@ // Module declarations include!(concat!(env!("OUT_DIR"), "/modules_kernel.rs")); - - -pub(crate) fn init_modules() { +pub(crate) fn init_modules() { __init_modules() } -pub(crate)fn exit_modules() { +pub(crate) fn exit_modules() { __exit_modules() } - diff --git a/src/utils.rs b/src/utils.rs index 79a387b..8d4144f 100644 --- a/src/utils.rs +++ b/src/utils.rs @@ -56,7 +56,6 @@ pub enum KernelError { HalError(hal::Error), } - /// Debug msg implementation for KernelError. impl Debug for KernelError { fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { @@ -76,5 +75,3 @@ impl From for KernelError { KernelError::HalError(err) } } - -