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..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}; +use syn::{ + Attribute, FnArg, LitInt, Pat, PatType, Type, TypeReference, TypeSlice, punctuated::Punctuated, + token::Comma, +}; use walkdir::WalkDir; extern crate cbindgen; @@ -18,6 +21,9 @@ 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."); // Get linker script from environment variable if let Ok(linker_script) = std::env::var("DEP_HAL_LINKER_SCRIPT") { @@ -129,7 +135,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; } @@ -149,7 +155,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() { @@ -159,12 +167,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 { @@ -177,13 +189,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); @@ -205,7 +215,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() { @@ -215,12 +227,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 { @@ -233,13 +249,11 @@ fn collect_syscalls_export>(root: P) -> HashMap>(root: P) -> HashMap, + output: syn::ReturnType, +} + +#[derive(Debug)] +struct KernelModule { + name: String, + source_path: 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 manifest_dir = std::env::var("CARGO_MANIFEST_DIR").unwrap(); + + for entry in WalkDir::new(root) { + let entry = match entry { + Ok(entry) => entry, + 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") + { + let path = entry.path(); + + println!("Processing kernel module file: {}", path.display()); + + let contents = match std::fs::read_to_string(path) { + Ok(contents) => contents, + Err(e) => { + panic!("Failed to read module file {}: {e}", path.display()); + } + }; + + let file = match syn::parse_file(&contents) { + Ok(file) => file, + Err(e) => { + panic!("Failed to parse module file {}: {e}", path.display()); + } + }; + + let module_name = path + .file_stem() + .and_then(|s| s.to_str()) + .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(), + }; + //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(); + + //Store kernelmod_init function + if has_attribute(&item_fn.attrs, "kernelmod_init") { + if module.init_fn.is_some() { + panic!("Module {module_name} has multiple #[kernelmod_init] functions"); + } + module.init_fn = Some(fn_name.clone()); + } + //Store kernelmod_exit function + if has_attribute(&item_fn.attrs, "kernelmod_exit") { + if module.exit_fn.is_some() { + 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, + inputs: item_fn.sig.inputs.clone(), + output: item_fn.sig.output.clone(), + }); + } + } + } + + // 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(); + 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); + } + } + } + + // Deterministic ordering — critical for index agreement between kernel and uspace + modules.sort_by(|a, b| a.name.cmp(&b.name)); + + Ok(modules) +} + +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"); + 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, "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)?; + writeln!(file, "}}")?; + } + writeln!(file)?; + + // 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 { + let init_fn = module.init_fn.as_ref().unwrap(); + writeln!(file, " {}::{}();", module.name, init_fn)?; + } + writeln!(file, "}}")?; + writeln!(file)?; + + // 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 { + let exit_fn = module.exit_fn.as_ref().unwrap(); + 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) -> isize;" + )?; + 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 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 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, " 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)?; + + 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 + 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::PosixError;")?; + writeln!(file)?; + + // Running Jump Table index + let mut global_index = 0usize; + + // Generate per module uspace wrapper + 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 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() +} + +/// 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, + index: usize, +) -> Result<(), std::io::Error> { + let fn_name = &func.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)]")?; + 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(", "); + + 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 + 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)?; + + // 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, " }}")?; + writeln!(file, " }};")?; + writeln!( + file, + " const _: fn() = || {{ fn assert_copy() {{}} assert_copy::<{}>(); }};", + ok_type_str + )?; + writeln!(file)?; + + // Layout call arguments + writeln!(file, " let args = {} {{", args_struct_name)?; + for init in &field_inits { + writeln!(file, " {},", init)?; + } + writeln!(file, " }};")?; + writeln!(file)?; + + //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)?; + + // Parse syscall return to Result type + writeln!(file, " if result < 0 && result > -134 {{")?; + 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, " let val = unsafe {{")?; + 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::<{}>(),", + ok_type_str + )?; + writeln!(file, " );")?; + writeln!(file, " out.assume_init()")?; + writeln!(file, " }};")?; + writeln!(file, " Ok(val)")?; + + writeln!(file, " }}")?; + writeln!(file, " }}")?; + writeln!(file)?; + + 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, + ty: &Type, + field_inits: &mut Vec, +) -> Result<(), std::io::Error> { + match ty { + Type::Path(_) => { + 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() { + panic!( + "Mutable references not supported in kernelmod_call: {}", + name + ); + } + + match &*type_ref.elem { + // &[T] — structurally a slice, decomposed into (ptr, len) + Type::Slice(slice) => { + 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) => { + // &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 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)); + } + } + _ => { + // 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)); + } + } + } + _ => { + panic!( + "Unsupported type in kernelmod_call argument '{}': {}", + name, + ty.to_token_stream() + ); + } + } + + Ok(()) +} diff --git a/machine/arm/src/asm.rs b/machine/arm/src/asm.rs index 9f12f75..716b113 100644 --- a/machine/arm/src/asm.rs +++ b/machine/arm/src/asm.rs @@ -20,45 +20,93 @@ 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 + } }; } #[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; diff --git a/machine/testing/src/asm.rs b/machine/testing/src/asm.rs index 9322be2..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) => {{}}; - ($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; diff --git a/macros/src/lib.rs b/macros/src/lib.rs index 903945a..c04dde1 100644 --- a/macros/src/lib.rs +++ b/macros/src/lib.rs @@ -1,14 +1,19 @@ use quote::{ToTokens, format_ident}; -use syn::parse_macro_input; +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; #[proc_macro_attribute] 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(); @@ -60,7 +65,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", @@ -147,7 +151,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(), @@ -155,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) { @@ -172,14 +175,13 @@ 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 } 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 _: () = { @@ -206,9 +208,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 } }; @@ -220,3 +220,402 @@ 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 = 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() +} + +/// 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(); + + 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 pascal_name = snake_to_pascal(&fn_name_str); + let args_struct_name = syn::Ident::new(&format!("__{}Args", pascal_name), fn_name.span()); + + let ok_ty = validate_and_extract_result_ok_type(&input.sig.output)?; + + 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()); + + 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); + } + } + } + + // 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." + ); + } + }; + // 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)?; + + 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) -> isize { + 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, + ), +} + +/// 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 { + match ty { + 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; + // Compile-time Copy bound check — fails if the type is not Copy. + let _: fn() = || { fn assert_copy() {} assert_copy::<#ty>(); }; + }; + + 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.", + )); + } + + 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()); + + 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) => { + // &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()); + + 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)); + } + + // &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)) + } + _ => 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 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 + // 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: isize = 0; + unsafe { + core::ptr::copy_nonoverlapping( + &val as *const _ as *const u8, + &mut raw as *mut isize as *mut u8, + core::mem::size_of_val(&val), + ); + } + raw + } + Err(e) => e as isize, + } + }) + } + _ => 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 { + item +} + +#[proc_macro_attribute] +pub fn kernelmod_exit( + _attr: proc_macro::TokenStream, + item: proc_macro::TokenStream, +) -> proc_macro::TokenStream { + item +} diff --git a/src/error.rs b/src/error.rs new file mode 100644 index 0000000..2e8324a --- /dev/null +++ b/src/error.rs @@ -0,0 +1,364 @@ +/// 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) +/// 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)] +#[non_exhaustive] +#[repr(i16)] +pub enum PosixError { + /// 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 (EWOULDBLOCK is an alias for this value) + 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 (EDEADLOCK is an alias for this value) + 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, + /// Reserved (EWOULDBLOCK alias slot, use EAGAIN) + _RESERVED_41 = -41, + /// 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, + /// Reserved (EDEADLOCK alias slot, use EDEADLK) + _RESERVED_58 = -58, + /// 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 (ENOTSUP is an alias for this value) + 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, +} + +/// 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) -> i16 { + *self as i16 + } +} + +// ── Base conversion: i16 (matches #[repr(i16)]) ───────────────────── + +impl From for i16 { + #[inline] + fn from(err: PosixError) -> i16 { + err.to_errno() + } +} + +impl TryFrom for PosixError { + type Error = (); + + #[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 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) }) + } +} + +// ── 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 PosixError { + type Error = (); + + #[inline] + fn try_from(value: isize) -> Result { + let narrow = i16::try_from(value).map_err(|_| ())?; + Self::try_from(narrow) + } +} + +impl From for usize { + #[inline] + fn from(err: PosixError) -> usize { + err.to_errno() as usize + } +} diff --git a/src/lib.rs b/src/lib.rs index 844751b..f84f706 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -7,8 +7,10 @@ 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; @@ -33,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 new file mode 100644 index 0000000..fbc752a --- /dev/null +++ b/src/modules.rs @@ -0,0 +1,10 @@ +// Module declarations +include!(concat!(env!("OUT_DIR"), "/modules_kernel.rs")); + +pub(crate) fn init_modules() { + __init_modules() +} + +pub(crate) fn exit_modules() { + __exit_modules() +} diff --git a/src/modules/sample_module.rs b/src/modules/sample_module.rs new file mode 100644 index 0000000..fb92347 --- /dev/null +++ b/src/modules/sample_module.rs @@ -0,0 +1,28 @@ +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. 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] +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] +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(PosixError::EINVAL), + _ => Err(PosixError::EPERM), + } +} \ No newline at end of file diff --git a/src/uspace.rs b/src/uspace.rs index c0d1d8e..d762c0c 100644 --- a/src/uspace.rs +++ b/src/uspace.rs @@ -1,4 +1,5 @@ //! This module provides access to userspace structures and services. +include!(concat!(env!("OUT_DIR"), "/modules_uspace.rs")); use ::core::mem::transmute; diff --git a/src/uspace/.gitkeep b/src/uspace/.gitkeep new file mode 100644 index 0000000..e69de29