diff --git a/clippy.toml b/clippy.toml index 295a5291..2fc6e189 100644 --- a/clippy.toml +++ b/clippy.toml @@ -1 +1 @@ -doc-valid-idents = ["..", "x86_64", "AArch64", "ARMv8"] +doc-valid-idents = ["..", "x86_64", "AArch64", "ARMv8", "Ser2Mem"] diff --git a/oro-common-proc/src/lib.rs b/oro-common-proc/src/lib.rs index 60a1b5d9..3ab77ade 100644 --- a/oro-common-proc/src/lib.rs +++ b/oro-common-proc/src/lib.rs @@ -13,6 +13,9 @@ mod enum_as; mod enum_iterator; mod gdb_autoload; mod paste; +mod ser2mem; + +pub(crate) mod syn_ex; /// Derive macro for the `EnumIterator` trait. /// @@ -83,3 +86,21 @@ pub fn enum_as_u32(input: proc_macro::TokenStream) -> proc_macro::TokenStream { pub fn gdb_autoload_inline(input: proc_macro::TokenStream) -> proc_macro::TokenStream { self::gdb_autoload::gdb_autoload_inline(input) } + +/// Automatically derives seralization for a structure to be deserialized via a pointer +/// cast. +/// +/// Used exclusively by the boot protocol; its use should not be used for anything else. +/// +/// **This macro only works within the `oro_common` crate.** It is not intended to be +/// used outside of the boot protocol, and thus won't compile in any other crate due to +/// the usage of the `crate` keyword. It is not advisable to try to work around this; +/// `ser2mem` is very unsafe and has a lot of restrictions, and there's no guarantee that +/// all of those restrictions are checked by the proc macros or implementations. +#[proc_macro_derive(Ser2Mem)] +pub fn derive_ser2mem(input: proc_macro::TokenStream) -> proc_macro::TokenStream { + match self::ser2mem::derive_ser2mem(input) { + Ok(is) => is, + Err(e) => e.to_compile_error().into(), + } +} diff --git a/oro-common-proc/src/ser2mem.rs b/oro-common-proc/src/ser2mem.rs new file mode 100644 index 00000000..6d7ab42a --- /dev/null +++ b/oro-common-proc/src/ser2mem.rs @@ -0,0 +1,249 @@ +//! Implements the `#[derive(Ser2Mem)]` derive proc macro. +//! +//! This macro generates implementations of the serializer +//! used to write Rust structures directly to another point +//! in memory, linearly, with proper alignment and padding +//! in order to be read by the kernel via a simple virtual +//! memory mapping and pointer cast. +//! +//! It's use is exclusively for the boot protocol and should +//! not be used for anything else. +#![allow(clippy::missing_docs_in_private_items)] + +use crate::syn_ex::{ReprArgs, ReprAttribute}; +use quote::quote; +use syn::{Data, DataEnum, DataStruct, DeriveInput, Error, Ident}; + +pub fn derive_ser2mem(input: proc_macro::TokenStream) -> syn::Result { + let input: DeriveInput = syn::parse(input)?; + + let repr_args = match input.repr_args() { + Ok(ra) => ra, + Err(e) => { + return Err(Error::new_spanned( + input, + format!( + "Ser2Mem requires a #[repr(...)] attribute, but one could not be parsed: {e}", + ), + )); + } + }; + + if repr_args.packed { + return Err(Error::new( + repr_args.span, + "Ser2Mem does not support packed structs", + )); + } + + if !input.generics.params.is_empty() { + return Err(Error::new_spanned( + input.generics.params, + "Ser2Mem does not support generic types", + )); + } + + match input.data { + Data::Enum(d) => derive_ser2mem_enum(&input.ident, &d, &repr_args), + Data::Struct(d) => derive_ser2mem_struct(&input.ident, &d, &repr_args), + Data::Union(_) => { + Err(Error::new_spanned( + input, + "Ser2Mem cannot be derived for unions", + )) + } + } +} + +fn derive_ser2mem_enum( + ident: &Ident, + data: &DataEnum, + repr_args: &ReprArgs, +) -> syn::Result { + // SAFETY(qix-): There isn't any *technical* reason in terms of implementing the serialization + // SAFETY(qix-): why these checks are needed but it keeps the boot protocol types consistent, + // SAFETY(qix-): especially over time. + if let Some(abi) = &repr_args.abi { + match ["u8", "u16", "u32", "u64"] + .into_iter() + .find_map(|x| if abi == x { Some(abi.clone()) } else { None }) + { + Some(abi) => abi, + None => { + return Err(Error::new_spanned( + abi, + "Ser2Mem enums require a #[repr(u8|u16|u32|u64)] attribute, but an \ + unsupported base type was given", + )); + } + } + } else { + return Err(Error::new( + repr_args.span, + "Ser2Mem enums require a #[repr(u8|u16|u32|u64)] attribute with a supported base \ + type, but no ABI was given", + )); + }; + + for variant in &data.variants { + if !variant.fields.is_empty() { + return Err(Error::new_spanned( + variant, + "Ser2Mem can only be derived for unit variant enums", + )); + } + + if variant.discriminant.is_none() { + return Err(Error::new_spanned( + variant, + "Ser2Mem can only be derived for enums with explicit discriminants", + )); + } + } + + Ok(quote! { + const _: () = { + #[automatically_derived] + unsafe impl crate::ser2mem::Serialize for #ident { + type Output = Self; + + #[inline] + unsafe fn serialize(self, _s: &mut S) -> Result { + Ok(self) + } + } + }; + }.into()) +} + +#[allow(clippy::too_many_lines)] +fn derive_ser2mem_struct( + ident: &Ident, + data: &DataStruct, + repr_args: &ReprArgs, +) -> syn::Result { + if let Some(abi) = &repr_args.abi { + if abi != "C" { + return Err(Error::new_spanned( + abi, + "Ser2Mem structs require a #[repr(C, ...)] ABI specifier, but a different ABI was \ + given", + )); + } + } else { + return Err(Error::new( + repr_args.span, + "Ser2Mem structs require a #[repr(...)] attribute with a C ABI specifier, but no ABI \ + was given", + )); + } + + let proxy_ident = Ident::new(&format!("{ident}Proxy"), ident.span()); + let mut needs_lifetime = false; + + let mut proxy_fields = vec![]; + let mut field_serializations = vec![]; + let mut field_writes = vec![]; + + for field in &data.fields { + let ident = field.ident.as_ref().ok_or_else(|| { + Error::new_spanned(field, "Ser2Mem can only be derived for named fields") + })?; + + let ty = &field.ty; + + // If the type is a static const reference to an array of T... + let proxy_ty = if let syn::Type::Reference(syn::TypeReference { + elem, + lifetime, + mutability, + .. + }) = ty + { + if mutability.is_some() { + return Err(Error::new_spanned( + ty, + "Ser2Mem reference fields must be immutable", + )); + } + + if lifetime.is_none() || matches!(lifetime, Some(lt) if lt.ident != "static") { + return Err(Error::new_spanned( + ty, + "Ser2Mem reference fields must be 'static", + )); + } + + if let syn::Type::Slice(syn::TypeSlice { elem, .. }) = (*elem).as_ref() { + // We need to pass in a lifetime since we use `DynIter` in the proxy struct. + needs_lifetime = true; + quote! { + crate::ser2mem::DynIter<'iter, <#elem as crate::ser2mem::Proxy>::Proxy<'iter>> + } + } else { + quote! { + #elem + } + } + } else { + quote! { + #ty + } + }; + + let temp_ident = Ident::new(&format!("{ident}__out"), ident.span()); + + proxy_fields.push(quote! { + pub #ident: #proxy_ty, + }); + + field_serializations.push(quote! { + let #temp_ident = self.#ident.serialize(s)?; + }); + + field_writes.push(quote! { + #ident: #temp_ident, + }); + } + + let lt = if needs_lifetime { + quote! { <'iter> } + } else { + quote! {} + }; + + Ok(quote! { + const _: () = { + #[automatically_derived] + pub struct #proxy_ident #lt { + #(#proxy_fields)* + } + + #[automatically_derived] + impl crate::ser2mem::Proxy for #ident { + type Proxy<'iter> = #proxy_ident #lt; + } + + #[automatically_derived] + unsafe impl #lt crate::ser2mem::Serialize for #proxy_ident #lt { + type Output = &'static #ident; + + #[allow(non_snake_case)] + unsafe fn serialize(self, s: &mut S) -> Result { + let layout = ::core::alloc::Layout::new::<#ident>(); + + #(#field_serializations)* + + let base = s.align_to(layout.align())?; + + s.write(#ident { + #(#field_writes)* + })?; + + Ok(&*base.cast()) + } + } + }; + } + .into()) +} diff --git a/oro-common-proc/src/syn_ex/attr.rs b/oro-common-proc/src/syn_ex/attr.rs new file mode 100644 index 00000000..7cb123e4 --- /dev/null +++ b/oro-common-proc/src/syn_ex/attr.rs @@ -0,0 +1,33 @@ +//! Utilities for working with attributes on certain [`syn`] types. + +/// Allows high-level querying and interacting with attributes on +/// certain [`syn`] types. +pub trait Attributes { + /// Returns an attribute given its name. The name can be + /// a path, such as `foo::bar`. + /// + /// Returns `None` if the attribute is not present. + fn get_attribute(&self, name: &str) -> Option<&syn::Attribute>; +} + +impl Attributes for syn::DeriveInput { + fn get_attribute(&self, name: &str) -> Option<&syn::Attribute> { + let name_segments = name.split("::").collect::>(); + self.attrs.iter().find(|attr| { + let mut name_segments = name_segments.iter(); + let mut attr_segments = attr.path().segments.iter(); + + loop { + match (name_segments.next(), attr_segments.next()) { + (Some(name), Some(attr)) => { + if attr.ident != name { + return false; + } + } + (None, Some(_)) | (Some(_), None) => return false, + (None, None) => return true, + } + } + }) + } +} diff --git a/oro-common-proc/src/syn_ex/mod.rs b/oro-common-proc/src/syn_ex/mod.rs new file mode 100644 index 00000000..3686bc94 --- /dev/null +++ b/oro-common-proc/src/syn_ex/mod.rs @@ -0,0 +1,10 @@ +//! Provides extension traits and implementations for common, high-level +//! operations on [`syn`] types. + +mod attr; +mod repr; + +pub use self::{ + attr::Attributes, + repr::{ReprArgs, ReprAttribute}, +}; diff --git a/oro-common-proc/src/syn_ex/repr.rs b/oro-common-proc/src/syn_ex/repr.rs new file mode 100644 index 00000000..e58e9c78 --- /dev/null +++ b/oro-common-proc/src/syn_ex/repr.rs @@ -0,0 +1,78 @@ +//! Implements high-level operations on [`syn`] types for the `#[repr(...)]` attribute. + +use super::Attributes; + +/// Provides high-level operations on [`syn`] types for the `#[repr(...)]` attribute. +pub trait ReprAttribute { + /// Returns the [`ReprArgs`] of the type, if present. + /// + /// Returns an error if the attribute is not present, or cannot be found. + fn repr_args(&self) -> syn::Result; +} + +/// Represents the arguments of a `#[repr(...)]` attribute. +pub struct ReprArgs { + /// The span of the entire attribute. + pub span: proc_macro2::Span, + /// The ABI, if specified. + /// + /// ``` + /// #[repr(u8)] + /// ^^ + /// ``` + pub abi: Option, + /// Whether the type is packed. + /// + /// ``` + /// #[repr(packed)] + /// ^^^^^^ + /// ``` + pub packed: bool, +} + +impl syn::parse::Parse for ReprArgs { + fn parse(input: syn::parse::ParseStream) -> syn::Result { + let span = input.span(); + let mut abi = None; + let mut packed = false; + + let mut first = true; + + while !input.is_empty() { + if first { + first = false; + } else { + input.parse::()?; + } + + let ident: syn::Ident = input.parse()?; + + if ident == "packed" { + if packed { + return Err(syn::Error::new_spanned(ident, "duplicate packed specifier")); + } + + packed = true; + } else { + if abi.is_some() { + return Err(syn::Error::new_spanned(ident, "duplicate ABI specifier")); + } + + abi = Some(ident); + } + } + + Ok(Self { span, abi, packed }) + } +} + +impl ReprAttribute for T +where + T: Attributes + syn::spanned::Spanned, +{ + fn repr_args(&self) -> syn::Result { + self.get_attribute("repr") + .ok_or_else(|| syn::Error::new(self.span(), "no #[repr(...)] attribute found"))? + .parse_args() + } +} diff --git a/oro-common/src/boot.rs b/oro-common/src/boot.rs index 16117b53..4f30b3b6 100644 --- a/oro-common/src/boot.rs +++ b/oro-common/src/boot.rs @@ -6,10 +6,19 @@ //! that is handled by passing information to the kernel via //! architecture-specific transfer stubs. -#[repr(C, align(4096))] +use crate::ser2mem::Ser2Mem; + +/// Root structure of the kernel boot protocol, sent to the kernel via +/// serialization to memory. +/// +/// # Safety +/// Do not instantiate this structure yourself; you probably won't +/// be able to anyway, but it's meant to be instantiated via its +/// [`crate::ser2mem::Proxy`] implementation (auto-generated via +/// the `#[derive(Ser2Mem)]` macro) and then serialized to memory. +#[derive(Ser2Mem, Debug)] +#[repr(C)] pub struct BootConfig { /// The total number of cores being booted. pub core_count: u64, - /// The physical address of the top of the page frame allocator stack. - pub pfa_stack_top: u64, } diff --git a/oro-common/src/init.rs b/oro-common/src/init.rs index c845bd6d..c7400f78 100644 --- a/oro-common/src/init.rs +++ b/oro-common/src/init.rs @@ -11,6 +11,7 @@ //! sequence; please read _and understand_ the documentation for the //! [`boot_to_kernel`] function before calling it. use crate::{ + boot::BootConfig, dbg, elf::{ElfSegment, ElfSegmentType}, mem::{ @@ -465,6 +466,13 @@ where "architecture prepared master page tables" ); + // Write the boot config. + // TODO(qix-) + #[allow(clippy::no_effect_underscore_binding)] + let _boot_config = ::Proxy { + core_count: *num_instances, + }; + // Store the kernel address space handle and entry point for cloning later. KERNEL_ADDRESS_SPACE = Proxy::from(kernel_mapper); KERNEL_ENTRY_POINT = kernel_elf.entry_point(); diff --git a/oro-common/src/lib.rs b/oro-common/src/lib.rs index c1327194..85ea9c8c 100644 --- a/oro-common/src/lib.rs +++ b/oro-common/src/lib.rs @@ -21,7 +21,12 @@ internal_features, rustdoc::private_doc_tests )] -#![feature(const_trait_impl, core_intrinsics, debug_closure_helpers)] +#![feature( + const_trait_impl, + core_intrinsics, + debug_closure_helpers, + more_qualified_paths +)] #![cfg_attr(debug_assertions, feature(naked_functions))] #[cfg(debug_assertions)] @@ -33,8 +38,10 @@ pub mod proc; pub mod sync; pub(crate) mod arch; +pub(crate) mod boot; pub(crate) mod dbg; pub(crate) mod init; +pub(crate) mod ser2mem; pub(crate) mod util; pub use self::{ diff --git a/oro-common/src/mem/pfa/filo.rs b/oro-common/src/mem/pfa/filo.rs index 2ad110ce..b92270d8 100644 --- a/oro-common/src/mem/pfa/filo.rs +++ b/oro-common/src/mem/pfa/filo.rs @@ -45,12 +45,26 @@ where M: FiloPageFrameManager, { /// Creates a new FILO page frame allocator. + #[inline] pub fn new(manager: M) -> Self { Self { manager, last_free: u64::MAX, } } + + /// Creates a new FILO page frame allocator with the given + /// last-free page frame address. + #[inline] + pub fn with_last_free(manager: M, last_free: u64) -> Self { + Self { manager, last_free } + } + + /// Returns the last-free page frame address. + #[inline] + pub fn last_free(&self) -> u64 { + self.last_free + } } unsafe impl PageFrameAllocate for FiloPageFrameAllocator diff --git a/oro-common/src/proc.rs b/oro-common/src/proc.rs index c039f469..2e6af483 100644 --- a/oro-common/src/proc.rs +++ b/oro-common/src/proc.rs @@ -1,7 +1,7 @@ //! Provides re-exports and supporting types for all proc-macros //! used by the Oro kernel. -pub use oro_common_proc::*; +pub use oro_common_proc::{gdb_autoload_inline, paste, AsU32, AsU64, EnumIterator}; /// Allows the unit variants of an enum to be iterated over. /// diff --git a/oro-common/src/ser2mem.rs b/oro-common/src/ser2mem.rs new file mode 100644 index 00000000..2053a8a1 --- /dev/null +++ b/oro-common/src/ser2mem.rs @@ -0,0 +1,149 @@ +//! Implements the internals of the `#[derive(Ser2Mem)]` derive proc macro. +//! +//! This module is only intended to be used by the [`crate::boot`] protocol +//! and thus must not be exported by the crate. +//! +//! # Safety +//! Ser2Mem is very unsafe. It has a lot of restrictions, and there's no guarantee +//! that all of those restrictions are checked by the proc macros or implementations. +//! +//! **It is not advisable to use outside of its intended purpose: the boot protocol.** + +pub use oro_common_proc::Ser2Mem; + +/// A namespace-preserving proxy trait that hides auto-derived (proc-macro generated) +/// iterator proxy structures when generating the `Ser2Mem` implementation for +/// a Rust struct. +pub trait Proxy { + /// The proxy type. This is initialized by the commons lib and used to write + /// the struct to memory. + type Proxy<'a>: ?Sized; +} + +impl Proxy for &'_ T { + type Proxy<'a> = ::Proxy<'a>; +} + +/// Serializes a type to memory via a [`Serializer`]. +/// +/// # Safety +/// **DO NOT MANUALLY IMPLEMENT THIS TRAIT.** Use the `#[derive(Ser2Mem)]` macro. +/// Doing this manually is extremely difficult and error-prone. +pub unsafe trait Serialize: Sized { + /// The type that is returned from the serializer and written to memory. + type Output: Sized + 'static; + + /// Serializes this type byte-wise to the given [`Serializer`]. + /// + /// # Safety + /// The caller MUST NOT de-reference the reference returned by this function. + /// It is **NOT VALID** for the pre-boot stage and will **IMMEDIATELY** invoke + /// undefined behavior. + unsafe fn serialize(self, s: &mut S) -> Result; +} + +/// A dynamic dispatch iterator wrapper. +#[allow(dead_code)] +pub struct DynIter<'a, T: Serialize>(&'a mut dyn Iterator); + +impl<'a, T: Serialize> Iterator for DynIter<'a, T> { + type Item = T; + + #[inline] + fn next(&mut self) -> Option { + self.0.next() + } +} + +unsafe impl<'a, T: Serialize> Serialize for DynIter<'a, T> { + type Output = &'static [T::Output]; + + #[inline] + unsafe fn serialize(self, s: &mut S) -> Result { + let layout = core::alloc::Layout::new::(); + let base = s.align_to(layout.align())?; + + let mut count = 0; + for item in self.0 { + // SAFETY(qix-): This is safe because 1) we're guaranteed to be #[repr(C)], which + // SAFETY(qix-): 2) guarantees the size is a multiple of the alignment. + item.serialize(s)?; + count += 1; + } + + Ok(core::slice::from_raw_parts(base.cast(), count)) + } +} + +#[allow(clippy::missing_docs_in_private_items)] +macro_rules! impl_primitives { + ($($T:ty),*) => { + $( + unsafe impl Serialize for $T { + type Output = Self; + + #[inline] + unsafe fn serialize(self, _s: &mut S) -> Result { + Ok(self) + } + } + + impl Proxy for $T { + type Proxy<'iter> = Self; + } + )* + } +} + +impl_primitives![u8, u16, u32, u64, usize, i8, i16, i32, i64, isize]; + +/// Writes values, linearly, to memory (similar to a cursor). Supports alignment. +/// +/// # Safety +/// Implementations of this trait must ensure that the written memory is available +/// to the kernel via a simple virtual memory mapping and pointer cast. +/// +/// All writes via `write` must adhere to the alignment and padding requirements +/// of the type. While alignment is not necessary to be enforced in the pre-boot +/// stage, it must be correct when read from the kernel. + +pub unsafe trait Serializer { + /// The type of error returned by the serializer. + type Error: Sized + core::fmt::Debug + 'static; + + /// Writes the given bytes to the stream. + /// + /// **This method purposefully does not return the target address of the written bytes.** + /// This is to help make sure it's useless without first calling [`Self::align_to()`]. + /// + /// # Safety + /// Must ONLY be called by auto-derived implementations of `Serialize`. + /// + /// Callers must ensure that the bytes are properly aligned, padded, + /// and otherwise representable data that can be cast to Rust types + /// in the kernel. + unsafe fn write_bytes(&mut self, bytes: &[u8]) -> Result<(), Self::Error>; + + /// Aligns the stream to the given alignment. + /// + /// This method returns the **target** (in-kernel) virtual address of + /// the first byte **after** alignment. + /// + /// # Safety + /// Must ONLY be called by auto-derived implementations of `Serialize`. + /// + /// Callers must ensure that the alignment is correct for the type + /// being written. + unsafe fn align_to(&mut self, align: usize) -> Result<*const (), Self::Error>; + + /// Writes, byte-wise, the given type to the stream. + /// + /// # Safety + /// Must ONLY be called by auto-derived implementations of `Serialize`. + unsafe fn write(&mut self, value: T) -> Result<(), Self::Error> { + self.write_bytes(core::slice::from_raw_parts( + core::ptr::from_ref(&value).cast::(), + core::mem::size_of::(), + )) + } +}