diff --git a/multiboot2-header/src/builder/header.rs b/multiboot2-header/src/builder/header.rs index 846ee9f8..a9c23dc3 100644 --- a/multiboot2-header/src/builder/header.rs +++ b/multiboot2-header/src/builder/header.rs @@ -244,7 +244,7 @@ mod tests { } #[test] - fn test_size_builder() { + fn test_builder() { let builder = Multiboot2HeaderBuilder::new(HeaderTagISA::I386); // Multiboot2 basic header + end tag let mut expected_len = 16 + 8; @@ -274,14 +274,28 @@ mod tests { 4096, RelocatableHeaderTagPreference::None, )); + expected_len += 0x18; + assert_eq!(builder.expected_len(), expected_len); println!("builder: {:#?}", builder); println!("expected_len: {} bytes", builder.expected_len()); let mb2_hdr_data = builder.build(); let mb2_hdr = mb2_hdr_data.as_ptr() as usize; - let mb2_hdr = unsafe { Multiboot2Header::from_addr(mb2_hdr) }; + let mb2_hdr = unsafe { Multiboot2Header::from_addr(mb2_hdr) } + .expect("the generated header to be loadable"); println!("{:#?}", mb2_hdr); + assert_eq!( + mb2_hdr.relocatable_tag().unwrap().flags(), + HeaderTagFlag::Required + ); + assert_eq!(mb2_hdr.relocatable_tag().unwrap().min_addr(), 0x1337); + assert_eq!(mb2_hdr.relocatable_tag().unwrap().max_addr(), 0xdeadbeef); + assert_eq!(mb2_hdr.relocatable_tag().unwrap().align(), 4096); + assert_eq!( + mb2_hdr.relocatable_tag().unwrap().preference(), + RelocatableHeaderTagPreference::None + ); /* you can write the binary to a file and a tool such as crate "bootinfo" will be able to fully parse the MB2 header diff --git a/multiboot2-header/src/relocatable.rs b/multiboot2-header/src/relocatable.rs index e2675cc4..09581b08 100644 --- a/multiboot2-header/src/relocatable.rs +++ b/multiboot2-header/src/relocatable.rs @@ -8,7 +8,7 @@ use core::mem::size_of; /// but not lower than min addr and ‘2’ means load image at highest possible /// address but not higher than max addr. #[repr(u32)] -#[derive(Copy, Clone, Debug)] +#[derive(Copy, Clone, Debug, PartialEq)] pub enum RelocatableHeaderTagPreference { /// Let boot loader decide. None = 0, diff --git a/multiboot2/Cargo.toml b/multiboot2/Cargo.toml index c323456c..6e6f1a1e 100644 --- a/multiboot2/Cargo.toml +++ b/multiboot2/Cargo.toml @@ -32,7 +32,10 @@ repository = "https://github.com/rust-osdev/multiboot2" documentation = "https://docs.rs/multiboot2" [features] -default = [] +# by default, builder is included +default = ["builder"] +std = [] +builder = ["std"] # Nightly-only features that will eventually be stabilized. unstable = [] diff --git a/multiboot2/src/boot_loader_name.rs b/multiboot2/src/boot_loader_name.rs index cb44814a..fb21d97b 100644 --- a/multiboot2/src/boot_loader_name.rs +++ b/multiboot2/src/boot_loader_name.rs @@ -1,8 +1,16 @@ -use crate::TagTrait; -use crate::{Tag, TagTypeId}; +use crate::{Tag, TagTrait, TagType, TagTypeId}; use core::fmt::{Debug, Formatter}; +use core::mem::size_of; use core::str::Utf8Error; +#[cfg(feature = "builder")] +use { + crate::builder::boxed_dst_tag, crate::builder::traits::StructAsBytes, alloc::boxed::Box, + alloc::vec::Vec, +}; + +const METADATA_SIZE: usize = size_of::() + size_of::(); + /// The bootloader name tag. #[derive(ptr_meta::Pointee)] #[repr(C, packed)] // only repr(C) would add unwanted padding before first_section @@ -14,6 +22,13 @@ pub struct BootLoaderNameTag { } impl BootLoaderNameTag { + #[cfg(feature = "builder")] + pub fn new(name: &str) -> Box { + let mut bytes: Vec<_> = name.bytes().collect(); + bytes.push(0); + boxed_dst_tag(TagType::BootLoaderName, &bytes) + } + /// Reads the name of the bootloader that is booting the kernel as Rust /// string slice without the null-byte. /// @@ -46,10 +61,15 @@ impl Debug for BootLoaderNameTag { impl TagTrait for BootLoaderNameTag { fn dst_size(base_tag: &Tag) -> usize { - // The size of the sized portion of the bootloader name tag. - let tag_base_size = 8; - assert!(base_tag.size >= 8); - base_tag.size as usize - tag_base_size + assert!(base_tag.size as usize >= METADATA_SIZE); + base_tag.size as usize - METADATA_SIZE + } +} + +#[cfg(feature = "builder")] +impl StructAsBytes for BootLoaderNameTag { + fn byte_size(&self) -> usize { + self.size.try_into().unwrap() } } @@ -85,4 +105,15 @@ mod tests { assert_eq!({ tag.typ }, TagType::BootLoaderName); assert_eq!(tag.name().expect("must be valid UTF-8"), MSG); } + + /// Test to generate a tag from a given string. + #[test] + #[cfg(feature = "builder")] + fn test_build_str() { + use crate::builder::traits::StructAsBytes; + + let tag = BootLoaderNameTag::new(MSG); + let bytes = tag.struct_as_bytes(); + assert_eq!(bytes, get_bytes()); + } } diff --git a/multiboot2/src/builder/information.rs b/multiboot2/src/builder/information.rs new file mode 100644 index 00000000..3f06cebb --- /dev/null +++ b/multiboot2/src/builder/information.rs @@ -0,0 +1,353 @@ +//! Exports item [`Multiboot2InformationBuilder`]. +use crate::builder::traits::StructAsBytes; +use crate::{ + BasicMemoryInfoTag, BootInformationInner, BootLoaderNameTag, CommandLineTag, + EFIBootServicesNotExited, EFIImageHandle32, EFIImageHandle64, EFIMemoryMapTag, EFISdt32, + EFISdt64, ElfSectionsTag, EndTag, FramebufferTag, ImageLoadPhysAddr, MemoryMapTag, ModuleTag, + RsdpV1Tag, RsdpV2Tag, SmbiosTag, +}; + +use alloc::boxed::Box; +use alloc::vec::Vec; +use core::mem::size_of; + +/// Builder to construct a valid Multiboot2 information dynamically at runtime. +/// The tags will appear in the order of their corresponding enumeration, +/// except for the END tag. +#[derive(Debug)] +pub struct Multiboot2InformationBuilder { + basic_memory_info_tag: Option, + boot_loader_name_tag: Option>, + command_line_tag: Option>, + efi_boot_services_not_exited: Option, + efi_image_handle32: Option, + efi_image_handle64: Option, + efi_memory_map_tag: Option>, + elf_sections_tag: Option>, + framebuffer_tag: Option>, + image_load_addr: Option, + memory_map_tag: Option>, + module_tags: Vec>, + efisdt32: Option, + efisdt64: Option, + rsdp_v1_tag: Option, + rsdp_v2_tag: Option, + smbios_tags: Vec>, +} + +impl Multiboot2InformationBuilder { + pub const fn new() -> Self { + Self { + basic_memory_info_tag: None, + boot_loader_name_tag: None, + command_line_tag: None, + efisdt32: None, + efisdt64: None, + efi_boot_services_not_exited: None, + efi_image_handle32: None, + efi_image_handle64: None, + efi_memory_map_tag: None, + elf_sections_tag: None, + framebuffer_tag: None, + image_load_addr: None, + memory_map_tag: None, + module_tags: Vec::new(), + rsdp_v1_tag: None, + rsdp_v2_tag: None, + smbios_tags: Vec::new(), + } + } + + /// Returns the size, if the value is a multiple of 8 or returns + /// the next number that is a multiple of 8. With this, one can + /// easily calculate the size of a Multiboot2 header, where + /// all the tags are 8-byte aligned. + const fn size_or_up_aligned(size: usize) -> usize { + let remainder = size % 8; + if remainder == 0 { + size + } else { + size + 8 - remainder + } + } + + /// Returns the expected length of the Multiboot2 header, + /// when the `build()`-method gets called. + pub fn expected_len(&self) -> usize { + let base_len = size_of::(); + // size_or_up_aligned not required, because length is 16 and the + // begin is 8 byte aligned => first tag automatically 8 byte aligned + let mut len = Self::size_or_up_aligned(base_len); + if let Some(tag) = &self.basic_memory_info_tag { + // we use size_or_up_aligned, because each tag will start at an 8 byte aligned address + len += Self::size_or_up_aligned(tag.byte_size()) + } + if let Some(tag) = &self.boot_loader_name_tag { + len += Self::size_or_up_aligned(tag.byte_size()) + } + if let Some(tag) = &self.command_line_tag { + len += Self::size_or_up_aligned(tag.byte_size()) + } + if let Some(tag) = &self.efisdt32 { + len += Self::size_or_up_aligned(tag.byte_size()) + } + if let Some(tag) = &self.efisdt64 { + len += Self::size_or_up_aligned(tag.byte_size()) + } + if let Some(tag) = &self.efi_boot_services_not_exited { + len += Self::size_or_up_aligned(tag.byte_size()) + } + if let Some(tag) = &self.efi_image_handle32 { + len += Self::size_or_up_aligned(tag.byte_size()) + } + if let Some(tag) = &self.efi_image_handle64 { + len += Self::size_or_up_aligned(tag.byte_size()) + } + if let Some(tag) = &self.efi_memory_map_tag { + len += Self::size_or_up_aligned(tag.byte_size()) + } + if let Some(tag) = &self.elf_sections_tag { + len += Self::size_or_up_aligned(tag.byte_size()) + } + if let Some(tag) = &self.framebuffer_tag { + len += Self::size_or_up_aligned(tag.byte_size()) + } + if let Some(tag) = &self.image_load_addr { + len += Self::size_or_up_aligned(tag.byte_size()) + } + if let Some(tag) = &self.memory_map_tag { + len += Self::size_or_up_aligned(tag.byte_size()) + } + for tag in &self.module_tags { + len += Self::size_or_up_aligned(tag.byte_size()) + } + if let Some(tag) = &self.rsdp_v1_tag { + len += Self::size_or_up_aligned(tag.byte_size()) + } + if let Some(tag) = &self.rsdp_v2_tag { + len += Self::size_or_up_aligned(tag.byte_size()) + } + for tag in &self.smbios_tags { + len += Self::size_or_up_aligned(tag.byte_size()) + } + // only here size_or_up_aligned is not important, because it is the last tag + len += size_of::(); + len + } + + /// Adds the bytes of a tag to the final Multiboot2 information byte vector. + /// Align should be true for all tags except the end tag. + fn build_add_bytes(dest: &mut Vec, source: &[u8], is_end_tag: bool) { + dest.extend(source); + if !is_end_tag { + let size = source.len(); + let size_to_8_align = Self::size_or_up_aligned(size); + let size_to_8_align_diff = size_to_8_align - size; + // fill zeroes so that next data block is 8-byte aligned + dest.extend([0].repeat(size_to_8_align_diff)); + } + } + + /// Constructs the bytes for a valid Multiboot2 information with the given properties. + /// The bytes can be casted to a Multiboot2 structure. + pub fn build(self) -> Vec { + let mut data = Vec::new(); + + Self::build_add_bytes( + &mut data, + // important that we write the correct expected length into the header! + &BootInformationInner::new(self.expected_len() as u32).struct_as_bytes(), + false, + ); + + if let Some(tag) = self.basic_memory_info_tag.as_ref() { + Self::build_add_bytes(&mut data, &tag.struct_as_bytes(), false) + } + if let Some(tag) = self.boot_loader_name_tag.as_ref() { + Self::build_add_bytes(&mut data, &tag.struct_as_bytes(), false) + } + if let Some(tag) = self.command_line_tag.as_ref() { + Self::build_add_bytes(&mut data, &tag.struct_as_bytes(), false) + } + if let Some(tag) = self.efisdt32.as_ref() { + Self::build_add_bytes(&mut data, &tag.struct_as_bytes(), false) + } + if let Some(tag) = self.efisdt64.as_ref() { + Self::build_add_bytes(&mut data, &tag.struct_as_bytes(), false) + } + if let Some(tag) = self.efi_boot_services_not_exited.as_ref() { + Self::build_add_bytes(&mut data, &tag.struct_as_bytes(), false) + } + if let Some(tag) = self.efi_image_handle32.as_ref() { + Self::build_add_bytes(&mut data, &tag.struct_as_bytes(), false) + } + if let Some(tag) = self.efi_image_handle64.as_ref() { + Self::build_add_bytes(&mut data, &tag.struct_as_bytes(), false) + } + if let Some(tag) = self.efi_memory_map_tag.as_ref() { + Self::build_add_bytes(&mut data, &tag.struct_as_bytes(), false) + } + if let Some(tag) = self.elf_sections_tag.as_ref() { + Self::build_add_bytes(&mut data, &tag.struct_as_bytes(), false) + } + if let Some(tag) = self.framebuffer_tag.as_ref() { + Self::build_add_bytes(&mut data, &tag.struct_as_bytes(), false) + } + if let Some(tag) = self.image_load_addr.as_ref() { + Self::build_add_bytes(&mut data, &tag.struct_as_bytes(), false) + } + if let Some(tag) = self.memory_map_tag.as_ref() { + Self::build_add_bytes(&mut data, &tag.struct_as_bytes(), false) + } + for tag in self.module_tags { + Self::build_add_bytes(&mut data, &tag.struct_as_bytes(), false) + } + if let Some(tag) = self.rsdp_v1_tag.as_ref() { + Self::build_add_bytes(&mut data, &tag.struct_as_bytes(), false) + } + if let Some(tag) = self.rsdp_v2_tag.as_ref() { + Self::build_add_bytes(&mut data, &tag.struct_as_bytes(), false) + } + for tag in self.smbios_tags { + Self::build_add_bytes(&mut data, &tag.struct_as_bytes(), false) + } + + Self::build_add_bytes(&mut data, &EndTag::default().struct_as_bytes(), true); + + data + } + + pub fn basic_memory_info_tag(&mut self, basic_memory_info_tag: BasicMemoryInfoTag) { + self.basic_memory_info_tag = Some(basic_memory_info_tag) + } + + pub fn bootloader_name_tag(&mut self, boot_loader_name_tag: Box) { + self.boot_loader_name_tag = Some(boot_loader_name_tag); + } + + pub fn command_line_tag(&mut self, command_line_tag: Box) { + self.command_line_tag = Some(command_line_tag); + } + + pub fn efisdt32(&mut self, efisdt32: EFISdt32) { + self.efisdt32 = Some(efisdt32); + } + + pub fn efisdt64(&mut self, efisdt64: EFISdt64) { + self.efisdt64 = Some(efisdt64); + } + + pub fn efi_boot_services_not_exited(&mut self) { + self.efi_boot_services_not_exited = Some(EFIBootServicesNotExited::new()); + } + + pub fn efi_image_handle32(&mut self, efi_image_handle32: EFIImageHandle32) { + self.efi_image_handle32 = Some(efi_image_handle32); + } + + pub fn efi_image_handle64(&mut self, efi_image_handle64: EFIImageHandle64) { + self.efi_image_handle64 = Some(efi_image_handle64); + } + + pub fn efi_memory_map_tag(&mut self, efi_memory_map_tag: Box) { + self.efi_memory_map_tag = Some(efi_memory_map_tag); + } + + pub fn elf_sections_tag(&mut self, elf_sections_tag: Box) { + self.elf_sections_tag = Some(elf_sections_tag); + } + + pub fn framebuffer_tag(&mut self, framebuffer_tag: Box) { + self.framebuffer_tag = Some(framebuffer_tag); + } + + pub fn image_load_addr(&mut self, image_load_addr: ImageLoadPhysAddr) { + self.image_load_addr = Some(image_load_addr); + } + + pub fn memory_map_tag(&mut self, memory_map_tag: Box) { + self.memory_map_tag = Some(memory_map_tag); + } + + pub fn add_module_tag(&mut self, module_tag: Box) { + self.module_tags.push(module_tag); + } + + pub fn rsdp_v1_tag(&mut self, rsdp_v1_tag: RsdpV1Tag) { + self.rsdp_v1_tag = Some(rsdp_v1_tag); + } + + pub fn rsdp_v2_tag(&mut self, rsdp_v2_tag: RsdpV2Tag) { + self.rsdp_v2_tag = Some(rsdp_v2_tag); + } + + pub fn add_smbios_tag(&mut self, smbios_tag: Box) { + self.smbios_tags.push(smbios_tag); + } +} + +#[cfg(test)] +mod tests { + use crate::builder::information::Multiboot2InformationBuilder; + use crate::{load, BasicMemoryInfoTag, CommandLineTag, ModuleTag}; + + #[test] + fn test_size_or_up_aligned() { + assert_eq!(0, Multiboot2InformationBuilder::size_or_up_aligned(0)); + assert_eq!(8, Multiboot2InformationBuilder::size_or_up_aligned(1)); + assert_eq!(8, Multiboot2InformationBuilder::size_or_up_aligned(8)); + assert_eq!(16, Multiboot2InformationBuilder::size_or_up_aligned(9)); + } + + #[test] + fn test_builder() { + let mut builder = Multiboot2InformationBuilder::new(); + // Multiboot2 basic information + end tag + let mut expected_len = 8 + 8; + assert_eq!(builder.expected_len(), expected_len); + + // the most simple tag + builder.basic_memory_info_tag(BasicMemoryInfoTag::new(640, 7 * 1024)); + expected_len += 16; + assert_eq!(builder.expected_len(), expected_len); + // a tag that has a dynamic size + builder.command_line_tag(CommandLineTag::new("test")); + expected_len += 8 + 5 + 3; // padding + assert_eq!(builder.expected_len(), expected_len); + // many modules + builder.add_module_tag(ModuleTag::new(0, 1234, "module1")); + expected_len += 16 + 8; + assert_eq!(builder.expected_len(), expected_len); + builder.add_module_tag(ModuleTag::new(5678, 6789, "module2")); + expected_len += 16 + 8; + assert_eq!(builder.expected_len(), expected_len); + + println!("builder: {:#?}", builder); + println!("expected_len: {} bytes", builder.expected_len()); + assert_eq!(builder.expected_len(), expected_len); + + let mb2i_data = builder.build(); + let mb2i_addr = mb2i_data.as_ptr() as usize; + let mb2i = unsafe { load(mb2i_addr) }.expect("the generated information to be readable"); + println!("{:#?}", mb2i); + assert_eq!(mb2i.basic_memory_info_tag().unwrap().memory_lower(), 640); + assert_eq!( + mb2i.basic_memory_info_tag().unwrap().memory_upper(), + 7 * 1024 + ); + assert_eq!( + mb2i.command_line_tag().unwrap().command_line().unwrap(), + "test" + ); + let mut modules = mb2i.module_tags(); + let module_1 = modules.next().unwrap(); + assert_eq!(module_1.start_address(), 0); + assert_eq!(module_1.end_address(), 1234); + assert_eq!(module_1.cmdline().unwrap(), "module1"); + let module_2 = modules.next().unwrap(); + assert_eq!(module_2.start_address(), 5678); + assert_eq!(module_2.end_address(), 6789); + assert_eq!(module_2.cmdline().unwrap(), "module2"); + assert!(modules.next().is_none()); + } +} diff --git a/multiboot2/src/builder/mod.rs b/multiboot2/src/builder/mod.rs new file mode 100644 index 00000000..474ddd5d --- /dev/null +++ b/multiboot2/src/builder/mod.rs @@ -0,0 +1,44 @@ +//! Module for the builder-feature. + +mod information; +pub(crate) mod traits; + +pub use information::Multiboot2InformationBuilder; + +use alloc::alloc::alloc; +use alloc::boxed::Box; +use core::alloc::Layout; +use core::mem::size_of; + +use crate::{TagTrait, TagTypeId}; + +/// Create a boxed tag with the given content. +pub(super) fn boxed_dst_tag + ?Sized>( + typ: impl Into, + content: &[u8], +) -> Box { + // based on https://stackoverflow.com/a/64121094/2192464 + let (layout, size_offset) = Layout::new::() + .extend(Layout::new::()) + .unwrap(); + let (layout, inner_offset) = layout + .extend(Layout::array::(content.len()).unwrap()) + .unwrap(); + let ptr = unsafe { alloc(layout) }; + assert!(!ptr.is_null()); + unsafe { + // initialize the content as good as we can + ptr.cast::().write(typ.into()); + ptr.add(size_offset).cast::().write( + (content.len() + size_of::() + size_of::()) + .try_into() + .unwrap(), + ); + // initialize body + let content_ptr = ptr.add(inner_offset); + for (idx, val) in content.iter().enumerate() { + content_ptr.add(idx).write(*val); + } + Box::from_raw(ptr_meta::from_raw_parts_mut(ptr as *mut (), content.len())) + } +} diff --git a/multiboot2/src/builder/traits.rs b/multiboot2/src/builder/traits.rs new file mode 100644 index 00000000..58d99a11 --- /dev/null +++ b/multiboot2/src/builder/traits.rs @@ -0,0 +1,27 @@ +//! Module for the helper trait [`StructAsBytes`]. + +/// Trait for all tags that helps to create a byte array from the tag. +/// Useful in builders to construct a byte vector that +/// represents the Multiboot2 information with all its tags. +pub(crate) trait StructAsBytes { + /// Returns the size in bytes of the struct. + /// This can be either the "size" field of tags or the compile-time size + /// (if known). + fn byte_size(&self) -> usize; + + /// Returns a byte pointer to the begin of the struct. + fn as_ptr(&self) -> *const u8 { + self as *const Self as *const u8 + } + + /// Returns the structure as a vector of its bytes. + /// The length is determined by [`Self::byte_size`]. + fn struct_as_bytes(&self) -> alloc::vec::Vec { + let ptr = self.as_ptr(); + let mut vec = alloc::vec::Vec::with_capacity(self.byte_size()); + for i in 0..self.byte_size() { + vec.push(unsafe { *ptr.add(i) }) + } + vec + } +} diff --git a/multiboot2/src/command_line.rs b/multiboot2/src/command_line.rs index ae08ba86..5f5232ff 100644 --- a/multiboot2/src/command_line.rs +++ b/multiboot2/src/command_line.rs @@ -1,9 +1,20 @@ //! Module for [CommandLineTag]. -use crate::{Tag, TagTrait, TagTypeId}; +use crate::{Tag, TagTrait, TagType, TagTypeId}; + +use core::convert::TryInto; use core::fmt::{Debug, Formatter}; +use core::mem; use core::str; +#[cfg(feature = "builder")] +use { + crate::builder::boxed_dst_tag, crate::builder::traits::StructAsBytes, alloc::boxed::Box, + alloc::vec::Vec, +}; + +pub(crate) const METADATA_SIZE: usize = mem::size_of::() + mem::size_of::(); + /// This tag contains the command line string. /// /// The string is a normal C-style UTF-8 zero-terminated string that can be @@ -18,6 +29,14 @@ pub struct CommandLineTag { } impl CommandLineTag { + /// Create a new command line tag from the given string. + #[cfg(feature = "builder")] + pub fn new(command_line: &str) -> Box { + let mut bytes: Vec<_> = command_line.bytes().collect(); + bytes.push(0); + boxed_dst_tag(TagType::Cmdline, &bytes) + } + /// Reads the command line of the kernel as Rust string slice without /// the null-byte. /// @@ -52,10 +71,15 @@ impl Debug for CommandLineTag { impl TagTrait for CommandLineTag { fn dst_size(base_tag: &Tag) -> usize { - // The size of the sized portion of the command line tag. - let tag_base_size = 8; - assert!(base_tag.size >= 8); - base_tag.size as usize - tag_base_size + assert!(base_tag.size as usize >= METADATA_SIZE); + base_tag.size as usize - METADATA_SIZE + } +} + +#[cfg(feature = "builder")] +impl StructAsBytes for CommandLineTag { + fn byte_size(&self) -> usize { + self.size.try_into().unwrap() } } @@ -91,4 +115,15 @@ mod tests { assert_eq!({ tag.typ }, TagType::Cmdline); assert_eq!(tag.command_line().expect("must be valid UTF-8"), MSG); } + + /// Test to generate a tag from a given string. + #[test] + #[cfg(feature = "builder")] + fn test_build_str() { + use crate::builder::traits::StructAsBytes; + + let tag = CommandLineTag::new(MSG); + let bytes = tag.struct_as_bytes(); + assert_eq!(bytes, get_bytes()); + } } diff --git a/multiboot2/src/efi.rs b/multiboot2/src/efi.rs index 4a239f9d..998bc2f0 100644 --- a/multiboot2/src/efi.rs +++ b/multiboot2/src/efi.rs @@ -1,6 +1,12 @@ //! All MBI tags related to (U)EFI. +use crate::TagType; use crate::TagTypeId; +use core::convert::TryInto; +use core::mem::size_of; + +#[cfg(feature = "builder")] +use crate::builder::traits::StructAsBytes; /// EFI system table in 32 bit mode #[derive(Clone, Copy, Debug)] @@ -12,12 +18,28 @@ pub struct EFISdt32 { } impl EFISdt32 { + /// Create a new tag to pass the EFI32 System Table pointer. + pub fn new(pointer: u32) -> Self { + Self { + typ: TagType::Efi32.into(), + size: size_of::().try_into().unwrap(), + pointer, + } + } + /// The physical address of a i386 EFI system table. pub fn sdt_address(&self) -> usize { self.pointer as usize } } +#[cfg(feature = "builder")] +impl StructAsBytes for EFISdt32 { + fn byte_size(&self) -> usize { + size_of::() + } +} + /// EFI system table in 64 bit mode #[derive(Clone, Copy, Debug)] #[repr(C)] @@ -28,12 +50,28 @@ pub struct EFISdt64 { } impl EFISdt64 { + /// Create a new tag to pass the EFI64 System Table pointer. + pub fn new(pointer: u64) -> Self { + Self { + typ: TagType::Efi64.into(), + size: size_of::().try_into().unwrap(), + pointer, + } + } + /// The physical address of a x86_64 EFI system table. pub fn sdt_address(&self) -> usize { self.pointer as usize } } +#[cfg(feature = "builder")] +impl StructAsBytes for EFISdt64 { + fn byte_size(&self) -> usize { + size_of::() + } +} + /// Contains pointer to boot loader image handle. #[derive(Debug)] #[repr(C)] @@ -44,12 +82,28 @@ pub struct EFIImageHandle32 { } impl EFIImageHandle32 { + #[cfg(feature = "builder")] + pub fn new(pointer: u32) -> Self { + Self { + typ: TagType::Efi32Ih.into(), + size: size_of::().try_into().unwrap(), + pointer, + } + } + /// Returns the physical address of the EFI image handle. pub fn image_handle(&self) -> usize { self.pointer as usize } } +#[cfg(feature = "builder")] +impl StructAsBytes for EFIImageHandle32 { + fn byte_size(&self) -> usize { + size_of::() + } +} + /// Contains pointer to boot loader image handle. #[derive(Debug)] #[repr(C)] @@ -60,8 +114,55 @@ pub struct EFIImageHandle64 { } impl EFIImageHandle64 { + #[cfg(feature = "builder")] + pub fn new(pointer: u64) -> Self { + Self { + typ: TagType::Efi64Ih.into(), + size: size_of::().try_into().unwrap(), + pointer, + } + } + /// Returns the physical address of the EFI image handle. pub fn image_handle(&self) -> usize { self.pointer as usize } } + +#[cfg(feature = "builder")] +impl StructAsBytes for EFIImageHandle64 { + fn byte_size(&self) -> usize { + size_of::() + } +} + +#[cfg(test)] +mod tests { + use super::{EFIImageHandle32, EFIImageHandle64, EFISdt32, EFISdt64}; + + const ADDR: usize = 0xABCDEF; + + #[test] + fn test_build_eftsdt32() { + let tag = EFISdt32::new(ADDR.try_into().unwrap()); + assert_eq!(tag.sdt_address(), ADDR); + } + + #[test] + fn test_build_eftsdt64() { + let tag = EFISdt64::new(ADDR.try_into().unwrap()); + assert_eq!(tag.sdt_address(), ADDR); + } + + #[test] + fn test_build_eftih32() { + let tag = EFIImageHandle32::new(ADDR.try_into().unwrap()); + assert_eq!(tag.image_handle(), ADDR); + } + + #[test] + fn test_build_eftih64() { + let tag = EFIImageHandle32::new(ADDR.try_into().unwrap()); + assert_eq!(tag.image_handle(), ADDR); + } +} diff --git a/multiboot2/src/elf_sections.rs b/multiboot2/src/elf_sections.rs index 1444294d..987e9e22 100644 --- a/multiboot2/src/elf_sections.rs +++ b/multiboot2/src/elf_sections.rs @@ -1,69 +1,85 @@ -use crate::tag_type::Tag; -use crate::TagType; +use crate::{Tag, TagTrait, TagType, TagTypeId}; + use core::fmt::{Debug, Formatter}; +use core::mem::size_of; use core::str::Utf8Error; +#[cfg(feature = "builder")] +use {crate::builder::boxed_dst_tag, crate::builder::traits::StructAsBytes, alloc::boxed::Box}; + +const METADATA_SIZE: usize = size_of::() + 4 * size_of::(); + /// This tag contains section header table from an ELF kernel. /// /// The sections iterator is provided via the `sections` method. -#[derive(Debug)] +#[derive(ptr_meta::Pointee)] +#[repr(C, packed)] pub struct ElfSectionsTag { - inner: *const ElfSectionsTagInner, - offset: usize, -} - -pub unsafe fn elf_sections_tag(tag: &Tag, offset: usize) -> ElfSectionsTag { - assert_eq!(TagType::ElfSections.val(), tag.typ); - let es = ElfSectionsTag { - inner: (tag as *const Tag).offset(1) as *const ElfSectionsTagInner, - offset, - }; - assert!((es.get().entry_size * es.get().shndx) <= tag.size); - es -} - -#[derive(Clone, Copy, Debug)] -#[repr(C, packed)] // only repr(C) would add unwanted padding at the end -struct ElfSectionsTagInner { + typ: TagTypeId, + pub(crate) size: u32, number_of_sections: u32, - entry_size: u32, - shndx: u32, // string table + pub(crate) entry_size: u32, + pub(crate) shndx: u32, // string table + sections: [u8], } impl ElfSectionsTag { + /// Create a new ElfSectionsTag with the given data. + #[cfg(feature = "builder")] + pub fn new(number_of_sections: u32, entry_size: u32, shndx: u32, sections: &[u8]) -> Box { + let mut bytes = [ + number_of_sections.to_le_bytes(), + entry_size.to_le_bytes(), + shndx.to_le_bytes(), + ] + .concat(); + bytes.extend_from_slice(sections); + boxed_dst_tag(TagType::ElfSections, &bytes) + } + /// Get an iterator of loaded ELF sections. - /// - /// # Examples - /// - /// ```rust,no_run - /// # let boot_info = unsafe { multiboot2::load(0xdeadbeef).unwrap() }; - /// if let Some(elf_tag) = boot_info.elf_sections_tag() { - /// let mut total = 0; - /// for section in elf_tag.sections() { - /// println!("Section: {:?}", section); - /// total += 1; - /// } - /// } - /// ``` - pub fn sections(&self) -> ElfSectionIter { - let string_section_offset = (self.get().shndx * self.get().entry_size) as isize; + pub(crate) fn sections(&self, offset: usize) -> ElfSectionIter { + let string_section_offset = (self.shndx * self.entry_size) as isize; let string_section_ptr = unsafe { self.first_section().offset(string_section_offset) as *const _ }; ElfSectionIter { current_section: self.first_section(), - remaining_sections: self.get().number_of_sections, - entry_size: self.get().entry_size, + remaining_sections: self.number_of_sections, + entry_size: self.entry_size, string_section: string_section_ptr, - offset: self.offset, + offset, } } fn first_section(&self) -> *const u8 { - (unsafe { self.inner.offset(1) }) as *const _ + &(self.sections[0]) as *const _ + } +} + +impl TagTrait for ElfSectionsTag { + fn dst_size(base_tag: &Tag) -> usize { + assert!(base_tag.size as usize >= METADATA_SIZE); + base_tag.size as usize - METADATA_SIZE + } +} + +#[cfg(feature = "builder")] +impl StructAsBytes for ElfSectionsTag { + fn byte_size(&self) -> usize { + self.size.try_into().unwrap() } +} - fn get(&self) -> &ElfSectionsTagInner { - unsafe { &*self.inner } +impl Debug for ElfSectionsTag { + fn fmt(&self, f: &mut Formatter<'_>) -> core::fmt::Result { + f.debug_struct("ElfSectionsTag") + .field("typ", &{ self.typ }) + .field("size", &{ self.size }) + .field("number_of_sections", &{ self.number_of_sections }) + .field("entry_size", &{ self.entry_size }) + .field("shndx", &{ self.shndx }) + .field("sections", &self.sections(0)) + .finish() } } diff --git a/multiboot2/src/framebuffer.rs b/multiboot2/src/framebuffer.rs index ec98caaa..ad4ceeb4 100644 --- a/multiboot2/src/framebuffer.rs +++ b/multiboot2/src/framebuffer.rs @@ -1,32 +1,189 @@ -use crate::tag_type::Tag; -use crate::Reader; +use crate::{Reader, Tag, TagTrait, TagType, TagTypeId}; + +use core::fmt::Debug; +use core::mem::size_of; use core::slice; use derive_more::Display; +#[cfg(feature = "builder")] +use { + crate::builder::boxed_dst_tag, crate::builder::traits::StructAsBytes, alloc::boxed::Box, + alloc::vec::Vec, +}; + +const METADATA_SIZE: usize = size_of::() + + 4 * size_of::() + + size_of::() + + size_of::() + + 2 * size_of::(); + /// The VBE Framebuffer information Tag. -#[derive(Debug, PartialEq, Eq)] -pub struct FramebufferTag<'a> { +#[derive(Eq, ptr_meta::Pointee)] +#[repr(C, packed)] +pub struct FramebufferTag { + typ: TagTypeId, + size: u32, + /// Contains framebuffer physical address. /// /// This field is 64-bit wide but bootloader should set it under 4GiB if /// possible for compatibility with payloads which aren’t aware of PAE or /// amd64. - pub address: u64, + address: u64, /// Contains the pitch in bytes. - pub pitch: u32, + pitch: u32, /// Contains framebuffer width in pixels. - pub width: u32, + width: u32, /// Contains framebuffer height in pixels. - pub height: u32, + height: u32, /// Contains number of bits per pixel. - pub bpp: u8, + bpp: u8, /// The type of framebuffer, one of: `Indexed`, `RGB` or `Text`. - pub buffer_type: FramebufferType<'a>, + type_no: u8, + + // In the multiboot spec, it has this listed as a u8 _NOT_ a u16. + // Reading the GRUB2 source code reveals it is in fact a u16. + _reserved: u16, + + buffer: [u8], +} + +impl FramebufferTag { + #[cfg(feature = "builder")] + pub fn new( + address: u64, + pitch: u32, + width: u32, + height: u32, + bpp: u8, + buffer_type: FramebufferType, + ) -> Box { + let mut bytes: Vec = address.to_le_bytes().into(); + bytes.extend(pitch.to_le_bytes()); + bytes.extend(width.to_le_bytes()); + bytes.extend(height.to_le_bytes()); + bytes.extend(bpp.to_le_bytes()); + bytes.extend(buffer_type.to_bytes()); + boxed_dst_tag(TagType::Framebuffer, &bytes) + } + + /// Contains framebuffer physical address. + /// + /// This field is 64-bit wide but bootloader should set it under 4GiB if + /// possible for compatibility with payloads which aren’t aware of PAE or + /// amd64. + pub fn address(&self) -> u64 { + self.address + } + + /// Contains the pitch in bytes. + pub fn pitch(&self) -> u32 { + self.pitch + } + + /// Contains framebuffer width in pixels. + pub fn width(&self) -> u32 { + self.width + } + + /// Contains framebuffer height in pixels. + pub fn height(&self) -> u32 { + self.height + } + + /// Contains number of bits per pixel. + pub fn bpp(&self) -> u8 { + self.bpp + } + + /// The type of framebuffer, one of: `Indexed`, `RGB` or `Text`. + pub fn buffer_type(&self) -> Result { + let mut reader = Reader::new(self.buffer.as_ptr()); + match self.type_no { + 0 => { + let num_colors = reader.read_u32(); + let palette = unsafe { + slice::from_raw_parts( + reader.current_address() as *const FramebufferColor, + num_colors as usize, + ) + } as &'static [FramebufferColor]; + Ok(FramebufferType::Indexed { palette }) + } + 1 => { + let red_pos = reader.read_u8(); // These refer to the bit positions of the LSB of each field + let red_mask = reader.read_u8(); // And then the length of the field from LSB to MSB + let green_pos = reader.read_u8(); + let green_mask = reader.read_u8(); + let blue_pos = reader.read_u8(); + let blue_mask = reader.read_u8(); + Ok(FramebufferType::RGB { + red: FramebufferField { + position: red_pos, + size: red_mask, + }, + green: FramebufferField { + position: green_pos, + size: green_mask, + }, + blue: FramebufferField { + position: blue_pos, + size: blue_mask, + }, + }) + } + 2 => Ok(FramebufferType::Text), + no => Err(UnknownFramebufferType(no)), + } + } +} + +impl TagTrait for FramebufferTag { + fn dst_size(base_tag: &Tag) -> usize { + assert!(base_tag.size as usize >= METADATA_SIZE); + base_tag.size as usize - METADATA_SIZE + } +} + +#[cfg(feature = "builder")] +impl StructAsBytes for FramebufferTag { + fn byte_size(&self) -> usize { + self.size.try_into().unwrap() + } +} + +impl Debug for FramebufferTag { + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { + f.debug_struct("FramebufferTag") + .field("typ", &{ self.typ }) + .field("size", &{ self.size }) + .field("buffer_type", &self.buffer_type()) + .field("address", &{ self.address }) + .field("pitch", &{ self.pitch }) + .field("width", &{ self.width }) + .field("height", &{ self.height }) + .field("bpp", &self.bpp) + .finish() + } +} + +impl PartialEq for FramebufferTag { + fn eq(&self, other: &Self) -> bool { + ({ self.typ } == { other.typ } + && { self.size } == { other.size } + && { self.address } == { other.address } + && { self.pitch } == { other.pitch } + && { self.width } == { other.width } + && { self.height } == { other.height } + && { self.bpp } == { other.bpp } + && { self.type_no } == { other.type_no } + && self.buffer == other.buffer) + } } /// Helper struct for [`FramebufferType`]. @@ -67,6 +224,35 @@ pub enum FramebufferType<'a> { Text, } +impl<'a> FramebufferType<'a> { + #[cfg(feature = "builder")] + fn to_bytes(&self) -> Vec { + let mut v = Vec::new(); + match self { + FramebufferType::Indexed { palette } => { + v.extend(0u8.to_le_bytes()); // type + v.extend(0u16.to_le_bytes()); // reserved + v.extend((palette.len() as u32).to_le_bytes()); + for color in palette.iter() { + v.extend(color.struct_as_bytes()); + } + } + FramebufferType::RGB { red, green, blue } => { + v.extend(1u8.to_le_bytes()); // type + v.extend(0u16.to_le_bytes()); // reserved + v.extend(red.struct_as_bytes()); + v.extend(green.struct_as_bytes()); + v.extend(blue.struct_as_bytes()); + } + FramebufferType::Text => { + v.extend(2u8.to_le_bytes()); // type + v.extend(0u16.to_le_bytes()); // reserved + } + } + v + } +} + /// An RGB color type field. #[derive(Debug, PartialEq, Eq)] pub struct FramebufferField { @@ -77,6 +263,13 @@ pub struct FramebufferField { pub size: u8, } +#[cfg(feature = "builder")] +impl StructAsBytes for FramebufferField { + fn byte_size(&self) -> usize { + size_of::() + } +} + /// A framebuffer color descriptor in the palette. #[derive(Clone, Copy, Debug, PartialEq, Eq)] #[repr(C, packed)] // only repr(C) would add unwanted padding at the end @@ -99,67 +292,9 @@ pub struct UnknownFramebufferType(u8); #[cfg(feature = "unstable")] impl core::error::Error for UnknownFramebufferType {} -/// Transforms a [`Tag`] into a [`FramebufferTag`]. -pub fn framebuffer_tag(tag: &Tag) -> Result { - let mut reader = Reader::new(tag as *const Tag); - reader.skip(8); - let address = reader.read_u64(); - let pitch = reader.read_u32(); - let width = reader.read_u32(); - let height = reader.read_u32(); - let bpp = reader.read_u8(); - let type_no = reader.read_u8(); - // In the multiboot spec, it has this listed as a u8 _NOT_ a u16. - // Reading the GRUB2 source code reveals it is in fact a u16. - reader.skip(2); - let buffer_type_id = match type_no { - 0 => Ok(FramebufferTypeId::Indexed), - 1 => Ok(FramebufferTypeId::RGB), - 2 => Ok(FramebufferTypeId::Text), - id => Err(UnknownFramebufferType(id)), - }?; - let buffer_type = match buffer_type_id { - FramebufferTypeId::Indexed => { - let num_colors = reader.read_u32(); - let palette = unsafe { - slice::from_raw_parts( - reader.current_address() as *const FramebufferColor, - num_colors as usize, - ) - } as &[FramebufferColor]; - FramebufferType::Indexed { palette } - } - FramebufferTypeId::RGB => { - let red_pos = reader.read_u8(); // These refer to the bit positions of the LSB of each field - let red_mask = reader.read_u8(); // And then the length of the field from LSB to MSB - let green_pos = reader.read_u8(); - let green_mask = reader.read_u8(); - let blue_pos = reader.read_u8(); - let blue_mask = reader.read_u8(); - FramebufferType::RGB { - red: FramebufferField { - position: red_pos, - size: red_mask, - }, - green: FramebufferField { - position: green_pos, - size: green_mask, - }, - blue: FramebufferField { - position: blue_pos, - size: blue_mask, - }, - } - } - FramebufferTypeId::Text => FramebufferType::Text, - }; - - Ok(FramebufferTag { - address, - pitch, - width, - height, - bpp, - buffer_type, - }) +#[cfg(feature = "builder")] +impl StructAsBytes for FramebufferColor { + fn byte_size(&self) -> usize { + size_of::() + } } diff --git a/multiboot2/src/image_load_addr.rs b/multiboot2/src/image_load_addr.rs index a5300299..f6071543 100644 --- a/multiboot2/src/image_load_addr.rs +++ b/multiboot2/src/image_load_addr.rs @@ -1,4 +1,9 @@ -use crate::TagTypeId; +use core::convert::TryInto; +use core::mem::size_of; + +#[cfg(feature = "builder")] +use crate::builder::traits::StructAsBytes; +use crate::tag_type::{TagType, TagTypeId}; /// If the image has relocatable header tag, this tag contains the image's /// base physical address. @@ -11,8 +16,37 @@ pub struct ImageLoadPhysAddr { } impl ImageLoadPhysAddr { + #[cfg(feature = "builder")] + pub fn new(load_base_addr: u32) -> Self { + Self { + typ: TagType::LoadBaseAddr.into(), + size: size_of::().try_into().unwrap(), + load_base_addr, + } + } + /// Returns the load base address. pub fn load_base_addr(&self) -> u32 { self.load_base_addr } } + +#[cfg(feature = "builder")] +impl StructAsBytes for ImageLoadPhysAddr { + fn byte_size(&self) -> usize { + size_of::() + } +} + +#[cfg(test)] +mod tests { + use super::ImageLoadPhysAddr; + + const ADDR: u32 = 0xABCDEF; + + #[test] + fn test_build_load_addr() { + let tag = ImageLoadPhysAddr::new(ADDR); + assert_eq!(tag.load_base_addr(), ADDR); + } +} diff --git a/multiboot2/src/lib.rs b/multiboot2/src/lib.rs index c40c4507..73a5d9a8 100644 --- a/multiboot2/src/lib.rs +++ b/multiboot2/src/lib.rs @@ -32,6 +32,9 @@ //! ## MSRV //! The MSRV is 1.56.1 stable. +#[cfg(feature = "builder")] +extern crate alloc; + // this crate can use std in tests only #[cfg_attr(test, macro_use)] #[cfg(test)] @@ -44,6 +47,8 @@ pub use ptr_meta::Pointee; use crate::framebuffer::UnknownFramebufferType; pub use boot_loader_name::BootLoaderNameTag; +#[cfg(feature = "builder")] +use builder::traits::StructAsBytes; pub use command_line::CommandLineTag; pub use efi::{EFIImageHandle32, EFIImageHandle64, EFISdt32, EFISdt64}; pub use elf_sections::{ @@ -52,8 +57,8 @@ pub use elf_sections::{ pub use framebuffer::{FramebufferColor, FramebufferField, FramebufferTag, FramebufferType}; pub use image_load_addr::ImageLoadPhysAddr; pub use memory_map::{ - BasicMemoryInfoTag, EFIMemoryAreaType, EFIMemoryDesc, EFIMemoryMapTag, MemoryArea, - MemoryAreaIter, MemoryAreaType, MemoryMapTag, + BasicMemoryInfoTag, EFIBootServicesNotExited, EFIMemoryAreaType, EFIMemoryDesc, + EFIMemoryMapTag, MemoryArea, MemoryAreaIter, MemoryAreaType, MemoryMapTag, }; pub use module::{ModuleIter, ModuleTag}; pub use rsdp::{RsdpV1Tag, RsdpV2Tag}; @@ -81,6 +86,9 @@ mod smbios; mod tag_type; mod vbe_info; +#[cfg(feature = "builder")] +pub mod builder; + /// Magic number that a multiboot2-compliant boot loader will store in `eax` register /// right before handoff to the payload (the kernel). This value can be used to check, /// that the kernel was indeed booted via multiboot2. @@ -197,6 +205,22 @@ struct BootInformationInner { _reserved: u32, } +impl BootInformationInner { + fn new(total_size: u32) -> Self { + Self { + total_size, + _reserved: 0, + } + } +} + +#[cfg(feature = "builder")] +impl StructAsBytes for BootInformationInner { + fn byte_size(&self) -> usize { + core::mem::size_of::() + } +} + impl BootInformation { /// Get the start address of the boot info. pub fn start_address(&self) -> usize { @@ -225,10 +249,26 @@ impl BootInformation { self.get_tag::(TagType::BasicMeminfo) } - /// Search for the ELF Sections tag. - pub fn elf_sections_tag(&self) -> Option { - self.get_tag::(TagType::ElfSections) - .map(|tag| unsafe { elf_sections::elf_sections_tag(tag, self.offset) }) + /// Search for the ELF Sections. + /// + /// # Examples + /// + /// ```rust,no_run + /// # let boot_info = unsafe { multiboot2::load(0xdeadbeef).unwrap() }; + /// if let Some(sections) = boot_info.elf_sections() { + /// let mut total = 0; + /// for section in sections { + /// println!("Section: {:?}", section); + /// total += 1; + /// } + /// } + /// ``` + pub fn elf_sections(&self) -> Option { + let tag = self.get_tag::(TagType::ElfSections); + tag.map(|t| { + assert!((t.entry_size * t.shndx) <= t.size); + t.sections(self.offset) + }) } /// Search for the Memory map tag. @@ -253,9 +293,12 @@ impl BootInformation { /// Search for the VBE framebuffer tag. The result is `Some(Err(e))`, if the /// framebuffer type is unknown, while the framebuffer tag is present. - pub fn framebuffer_tag(&self) -> Option> { - self.get_tag::(TagType::Framebuffer) - .map(framebuffer::framebuffer_tag) + pub fn framebuffer_tag(&self) -> Option> { + self.get_tag::(TagType::Framebuffer) + .map(|tag| match tag.buffer_type() { + Ok(_) => Ok(tag), + Err(e) => Err(e), + }) } /// Search for the EFI 32-bit SDT tag. @@ -437,20 +480,14 @@ impl fmt::Debug for BootInformation { .field("module_tags", &self.module_tags()); // usually this is REALLY big (thousands of tags) => skip it here - let elf_sections_tag_entries_count = self - .elf_sections_tag() - .map(|x| x.sections().count()) - .unwrap_or(0); + let elf_sections_tag_entries_count = self.elf_sections().map(|x| x.count()).unwrap_or(0); if elf_sections_tag_entries_count > ELF_SECTIONS_LIMIT { debug.field("elf_sections_tags (count)", &elf_sections_tag_entries_count); } else { debug.field( "elf_sections_tags", - &self - .elf_sections_tag() - .map(|x| x.sections()) - .unwrap_or_default(), + &self.elf_sections().unwrap_or_default(), ); } @@ -574,7 +611,7 @@ mod tests { assert_eq!(addr, bi.start_address()); assert_eq!(addr + bytes.0.len(), bi.end_address()); assert_eq!(bytes.0.len(), bi.total_size()); - assert!(bi.elf_sections_tag().is_none()); + assert!(bi.elf_sections().is_none()); assert!(bi.memory_map_tag().is_none()); assert!(bi.module_tags().next().is_none()); assert!(bi.boot_loader_name_tag().is_none()); @@ -598,7 +635,7 @@ mod tests { assert_eq!(addr, bi.start_address()); assert_eq!(addr + bytes.0.len(), bi.end_address()); assert_eq!(bytes.0.len(), bi.total_size()); - assert!(bi.elf_sections_tag().is_none()); + assert!(bi.elf_sections().is_none()); assert!(bi.memory_map_tag().is_none()); assert!(bi.module_tags().next().is_none()); assert!(bi.boot_loader_name_tag().is_none()); @@ -622,7 +659,7 @@ mod tests { assert_eq!(addr, bi.start_address()); assert_eq!(addr + bytes.0.len(), bi.end_address()); assert_eq!(bytes.0.len(), bi.total_size()); - assert!(bi.elf_sections_tag().is_none()); + assert!(bi.elf_sections().is_none()); assert!(bi.memory_map_tag().is_none()); assert!(bi.module_tags().next().is_none()); assert!(bi.boot_loader_name_tag().is_none()); @@ -649,7 +686,7 @@ mod tests { assert_eq!(addr, bi.start_address()); assert_eq!(addr + bytes.0.len(), bi.end_address()); assert_eq!(bytes.0.len(), bi.total_size()); - assert!(bi.elf_sections_tag().is_none()); + assert!(bi.elf_sections().is_none()); assert!(bi.memory_map_tag().is_none()); assert!(bi.module_tags().next().is_none()); assert_eq!( @@ -691,31 +728,34 @@ mod tests { assert_eq!(addr, bi.start_address()); assert_eq!(addr + bytes.0.len(), bi.end_address()); assert_eq!(bytes.0.len(), bi.total_size()); - use framebuffer::{FramebufferField, FramebufferTag, FramebufferType}; + use framebuffer::{FramebufferField, FramebufferType}; + assert!(bi.framebuffer_tag().is_some()); + let fbi = bi + .framebuffer_tag() + .expect("Framebuffer info should be available") + .expect("Framebuffer info type should be valid"); + assert_eq!(fbi.address(), 4244635648); + assert_eq!(fbi.pitch(), 5120); + assert_eq!(fbi.width(), 1280); + assert_eq!(fbi.height(), 720); + assert_eq!(fbi.bpp(), 32); assert_eq!( - bi.framebuffer_tag(), - Some(Ok(FramebufferTag { - address: 4244635648, - pitch: 5120, - width: 1280, - height: 720, - bpp: 32, - buffer_type: FramebufferType::RGB { - red: FramebufferField { - position: 16, - size: 8 - }, - green: FramebufferField { - position: 8, - size: 8 - }, - blue: FramebufferField { - position: 0, - size: 8 - } + fbi.buffer_type().unwrap(), + FramebufferType::RGB { + red: FramebufferField { + position: 16, + size: 8 + }, + green: FramebufferField { + position: 8, + size: 8 + }, + blue: FramebufferField { + position: 0, + size: 8 } - })) - ) + } + ); } #[test] @@ -753,12 +793,12 @@ mod tests { .framebuffer_tag() .expect("Framebuffer info should be available") .expect("Framebuffer info type should be valid"); - assert_eq!(fbi.address, 4244635648); - assert_eq!(fbi.pitch, 5120); - assert_eq!(fbi.width, 1280); - assert_eq!(fbi.height, 720); - assert_eq!(fbi.bpp, 32); - match fbi.buffer_type { + assert_eq!(fbi.address(), 4244635648); + assert_eq!(fbi.pitch(), 5120); + assert_eq!(fbi.width(), 1280); + assert_eq!(fbi.height(), 720); + assert_eq!(fbi.bpp(), 32); + match fbi.buffer_type().unwrap() { FramebufferType::Indexed { palette } => assert_eq!( palette, [ @@ -788,16 +828,6 @@ mod tests { } } - #[test] - /// Compile time test for `FramebufferTag`. - fn framebuffer_tag_size() { - use crate::FramebufferTag; - unsafe { - // 24 for the start + 24 for `FramebufferType`. - core::mem::transmute::<[u8; 48], FramebufferTag>([0u8; 48]); - } - } - #[test] fn vbe_info_tag() { //Taken from GRUB2 running in QEMU. @@ -1255,16 +1285,15 @@ mod tests { assert_eq!(addr, bi.start_address()); assert_eq!(addr + bytes.len(), bi.end_address()); assert_eq!(bytes.len(), bi.total_size()); - let es = bi.elf_sections_tag().unwrap(); - let mut s = es.sections(); - let s1 = s.next().expect("Should have one more section"); + let mut es = bi.elf_sections().unwrap(); + let s1 = es.next().expect("Should have one more section"); assert_eq!(".rodata", s1.name().expect("Should be valid utf-8")); assert_eq!(0xFFFF_8000_0010_0000, s1.start_address()); assert_eq!(0xFFFF_8000_0010_3000, s1.end_address()); assert_eq!(0x0000_0000_0000_3000, s1.size()); assert_eq!(ElfSectionFlags::ALLOCATED, s1.flags()); assert_eq!(ElfSectionType::ProgramSection, s1.section_type()); - let s2 = s.next().expect("Should have one more section"); + let s2 = es.next().expect("Should have one more section"); assert_eq!(".text", s2.name().expect("Should be valid utf-8")); assert_eq!(0xFFFF_8000_0010_3000, s2.start_address()); assert_eq!(0xFFFF_8000_0010_C000, s2.end_address()); @@ -1274,7 +1303,7 @@ mod tests { s2.flags() ); assert_eq!(ElfSectionType::ProgramSection, s2.section_type()); - let s3 = s.next().expect("Should have one more section"); + let s3 = es.next().expect("Should have one more section"); assert_eq!(".data", s3.name().expect("Should be valid utf-8")); assert_eq!(0xFFFF_8000_0010_C000, s3.start_address()); assert_eq!(0xFFFF_8000_0010_E000, s3.end_address()); @@ -1284,7 +1313,7 @@ mod tests { s3.flags() ); assert_eq!(ElfSectionType::ProgramSection, s3.section_type()); - let s4 = s.next().expect("Should have one more section"); + let s4 = es.next().expect("Should have one more section"); assert_eq!(".bss", s4.name().expect("Should be valid utf-8")); assert_eq!(0xFFFF_8000_0010_E000, s4.start_address()); assert_eq!(0xFFFF_8000_0011_3000, s4.end_address()); @@ -1294,7 +1323,7 @@ mod tests { s4.flags() ); assert_eq!(ElfSectionType::Uninitialized, s4.section_type()); - let s5 = s.next().expect("Should have one more section"); + let s5 = es.next().expect("Should have one more section"); assert_eq!(".data.rel.ro", s5.name().expect("Should be valid utf-8")); assert_eq!(0xFFFF_8000_0011_3000, s5.start_address()); assert_eq!(0xFFFF_8000_0011_3000, s5.end_address()); @@ -1304,28 +1333,28 @@ mod tests { s5.flags() ); assert_eq!(ElfSectionType::ProgramSection, s5.section_type()); - let s6 = s.next().expect("Should have one more section"); + let s6 = es.next().expect("Should have one more section"); assert_eq!(".symtab", s6.name().expect("Should be valid utf-8")); assert_eq!(0x0000_0000_0011_3000, s6.start_address()); assert_eq!(0x0000_0000_0011_5BE0, s6.end_address()); assert_eq!(0x0000_0000_0000_2BE0, s6.size()); assert_eq!(ElfSectionFlags::empty(), s6.flags()); assert_eq!(ElfSectionType::LinkerSymbolTable, s6.section_type()); - let s7 = s.next().expect("Should have one more section"); + let s7 = es.next().expect("Should have one more section"); assert_eq!(".strtab", s7.name().expect("Should be valid utf-8")); assert_eq!(0x0000_0000_0011_5BE0, s7.start_address()); assert_eq!(0x0000_0000_0011_9371, s7.end_address()); assert_eq!(0x0000_0000_0000_3791, s7.size()); assert_eq!(ElfSectionFlags::empty(), s7.flags()); assert_eq!(ElfSectionType::StringTable, s7.section_type()); - let s8 = s.next().expect("Should have one more section"); + let s8 = es.next().expect("Should have one more section"); assert_eq!(".shstrtab", s8.name().expect("Should be valid utf-8")); assert_eq!(string_addr, s8.start_address()); assert_eq!(string_addr + string_bytes.len() as u64, s8.end_address()); assert_eq!(string_bytes.len() as u64, s8.size()); assert_eq!(ElfSectionFlags::empty(), s8.flags()); assert_eq!(ElfSectionType::StringTable, s8.section_type()); - assert!(s.next().is_none()); + assert!(es.next().is_none()); let mut mm = bi.memory_map_tag().unwrap().available_memory_areas(); let mm1 = mm.next().unwrap(); assert_eq!(0x00000000, mm1.start_address()); @@ -1368,12 +1397,12 @@ mod tests { .framebuffer_tag() .expect("Framebuffer info should be available") .expect("Framebuffer info type should be valid"); - assert_eq!(fbi.address, 753664); - assert_eq!(fbi.pitch, 160); - assert_eq!(fbi.width, 80); - assert_eq!(fbi.height, 25); - assert_eq!(fbi.bpp, 16); - assert_eq!(fbi.buffer_type, FramebufferType::Text); + assert_eq!(fbi.address(), 753664); + assert_eq!(fbi.pitch(), 160); + assert_eq!(fbi.width(), 80); + assert_eq!(fbi.height(), 25); + assert_eq!(fbi.bpp(), 16); + assert_eq!(fbi.buffer_type(), Ok(FramebufferType::Text)); } #[test] @@ -1440,26 +1469,15 @@ mod tests { assert_eq!(addr, bi.start_address()); assert_eq!(addr + bytes.0.len(), bi.end_address()); assert_eq!(bytes.0.len(), bi.total_size()); - let es = bi.elf_sections_tag().unwrap(); - let mut s = es.sections(); - let s1 = s.next().expect("Should have one more section"); + let mut es = bi.elf_sections().unwrap(); + let s1 = es.next().expect("Should have one more section"); assert_eq!(".shstrtab", s1.name().expect("Should be valid utf-8")); assert_eq!(string_addr, s1.start_address()); assert_eq!(string_addr + string_bytes.0.len() as u64, s1.end_address()); assert_eq!(string_bytes.0.len() as u64, s1.size()); assert_eq!(ElfSectionFlags::empty(), s1.flags()); assert_eq!(ElfSectionType::StringTable, s1.section_type()); - assert!(s.next().is_none()); - } - - #[test] - /// Compile time test for `ElfSectionsTag`. - fn elf_sections_tag_size() { - use super::ElfSectionsTag; - unsafe { - // `ElfSectionsTagInner` is 12 bytes + 4 in the offset. - core::mem::transmute::<[u8; 16], ElfSectionsTag>([0u8; 16]); - } + assert!(es.next().is_none()); } #[test] @@ -1531,26 +1549,6 @@ mod tests { assert!(efi_mmap.is_none()); } - #[test] - /// Compile time test for `MemoryMapTag`. - fn e820_memory_map_tag_size() { - use super::MemoryMapTag; - unsafe { - // `MemoryMapTag` is 16 bytes without the 1st entry - core::mem::transmute::<[u8; 16], MemoryMapTag>([0u8; 16]); - } - } - - #[test] - /// Compile time test for `EFIMemoryMapTag`. - fn efi_memory_map_tag_size() { - use super::EFIMemoryMapTag; - unsafe { - // `EFIMemoryMapTag` is 16 bytes without the 1st entry - core::mem::transmute::<[u8; 16], EFIMemoryMapTag>([0u8; 16]); - } - } - #[test] #[cfg(feature = "unstable")] /// This test succeeds if it compiles. diff --git a/multiboot2/src/memory_map.rs b/multiboot2/src/memory_map.rs index 36c7203e..cb27d58b 100644 --- a/multiboot2/src/memory_map.rs +++ b/multiboot2/src/memory_map.rs @@ -1,5 +1,14 @@ -use crate::TagTypeId; +use crate::{Tag, TagTrait, TagType, TagTypeId}; + +use core::convert::TryInto; +use core::fmt::Debug; use core::marker::PhantomData; +use core::mem; + +#[cfg(feature = "builder")] +use {crate::builder::boxed_dst_tag, crate::builder::traits::StructAsBytes, alloc::boxed::Box}; + +const METADATA_SIZE: usize = mem::size_of::() + 3 * mem::size_of::(); /// This tag provides an initial host memory map. /// @@ -11,17 +20,28 @@ use core::marker::PhantomData; /// This tag may not be provided by some boot loaders on EFI platforms if EFI /// boot services are enabled and available for the loaded image (The EFI boot /// services tag may exist in the Multiboot2 boot information structure). -#[derive(Debug)] +#[derive(Debug, ptr_meta::Pointee)] #[repr(C)] pub struct MemoryMapTag { typ: TagTypeId, size: u32, entry_size: u32, entry_version: u32, - first_area: [MemoryArea; 0], + areas: [MemoryArea], } impl MemoryMapTag { + #[cfg(feature = "builder")] + pub fn new(areas: &[MemoryArea]) -> Box { + let entry_size: u32 = mem::size_of::().try_into().unwrap(); + let entry_version: u32 = 0; + let mut bytes = [entry_size.to_le_bytes(), entry_version.to_le_bytes()].concat(); + for area in areas { + bytes.extend(area.struct_as_bytes()); + } + boxed_dst_tag(TagType::Mmap, bytes.as_slice()) + } + /// Return an iterator over all memory areas that have the type /// [`MemoryAreaType::Available`]. pub fn available_memory_areas(&self) -> impl Iterator { @@ -32,21 +52,33 @@ impl MemoryMapTag { /// Return an iterator over all memory areas. pub fn memory_areas(&self) -> MemoryAreaIter { let self_ptr = self as *const MemoryMapTag; - let start_area = self.first_area.as_ptr(); - + let start_area = (&self.areas[0]) as *const MemoryArea; MemoryAreaIter { current_area: start_area as u64, // NOTE: `last_area` is only a bound, it doesn't necessarily point exactly to the last element - last_area: (self_ptr as u64 - + (self.size as u64 - core::mem::size_of::() as u64)), + last_area: (self_ptr as *const () as u64 + (self.size - self.entry_size) as u64), entry_size: self.entry_size, phantom: PhantomData, } } } +impl TagTrait for MemoryMapTag { + fn dst_size(base_tag: &Tag) -> usize { + assert!(base_tag.size as usize >= METADATA_SIZE); + base_tag.size as usize - METADATA_SIZE + } +} + +#[cfg(feature = "builder")] +impl StructAsBytes for MemoryMapTag { + fn byte_size(&self) -> usize { + self.size.try_into().unwrap() + } +} + /// A memory area entry descriptor. -#[derive(Debug)] +#[derive(Debug, Clone)] #[repr(C)] pub struct MemoryArea { base_addr: u64, @@ -56,6 +88,16 @@ pub struct MemoryArea { } impl MemoryArea { + /// Create a new MemoryArea. + pub fn new(base_addr: u64, length: u64, typ: MemoryAreaType) -> Self { + Self { + base_addr, + length, + typ, + _reserved: 0, + } + } + /// The start address of the memory region. pub fn start_address(&self) -> u64 { self.base_addr @@ -77,6 +119,13 @@ impl MemoryArea { } } +#[cfg(feature = "builder")] +impl StructAsBytes for MemoryArea { + fn byte_size(&self) -> usize { + mem::size_of::() + } +} + /// An enum of possible reported region types. /// Inside the Multiboot2 spec this is kind of hidden /// inside the implementation of `struct multiboot_mmap_entry`. @@ -139,7 +188,6 @@ impl<'a> Iterator for MemoryAreaIter<'a> { /// (which had a 24-bit address bus) could use, historically. /// Nowadays, much bigger chunks of continuous memory are available at higher /// addresses, but the Multiboot standard still references those two terms. -#[derive(Debug)] #[repr(C, packed)] pub struct BasicMemoryInfoTag { typ: TagTypeId, @@ -149,6 +197,15 @@ pub struct BasicMemoryInfoTag { } impl BasicMemoryInfoTag { + pub fn new(memory_lower: u32, memory_upper: u32) -> Self { + Self { + typ: TagType::BasicMeminfo.into(), + size: mem::size_of::().try_into().unwrap(), + memory_lower, + memory_upper, + } + } + pub fn memory_lower(&self) -> u32 { self.memory_lower } @@ -158,38 +215,89 @@ impl BasicMemoryInfoTag { } } +#[cfg(feature = "builder")] +impl StructAsBytes for BasicMemoryInfoTag { + fn byte_size(&self) -> usize { + mem::size_of::() + } +} + +impl Debug for BasicMemoryInfoTag { + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { + f.debug_struct("BasicMemoryInfoTag") + .field("typ", &{ self.typ }) + .field("size", &{ self.size }) + .field("memory_lower", &{ self.memory_lower }) + .field("memory_upper", &{ self.memory_upper }) + .finish() + } +} + +const EFI_METADATA_SIZE: usize = mem::size_of::() + 3 * mem::size_of::(); + /// EFI memory map as per EFI specification. -#[derive(Debug)] +#[derive(Debug, ptr_meta::Pointee)] #[repr(C)] pub struct EFIMemoryMapTag { typ: TagTypeId, size: u32, desc_size: u32, desc_version: u32, - first_desc: [EFIMemoryDesc; 0], + descs: [EFIMemoryDesc], } impl EFIMemoryMapTag { + #[cfg(feature = "builder")] + /// Create a new EFI memory map tag with the given memory descriptors. + /// Version and size can't be set because you're passing a slice of + /// EFIMemoryDescs, not the ones you might have gotten from the firmware. + pub fn new(descs: &[EFIMemoryDesc]) -> Box { + // update this when updating EFIMemoryDesc + const MEMORY_DESCRIPTOR_VERSION: u32 = 1; + let mut bytes = [ + (mem::size_of::() as u32).to_le_bytes(), + MEMORY_DESCRIPTOR_VERSION.to_le_bytes(), + ] + .concat(); + for desc in descs { + bytes.extend(desc.struct_as_bytes()); + } + boxed_dst_tag(TagType::EfiMmap, bytes.as_slice()) + } + /// Return an iterator over ALL marked memory areas. /// /// This differs from `MemoryMapTag` as for UEFI, the OS needs some non- /// available memory areas for tables and such. pub fn memory_areas(&self) -> EFIMemoryAreaIter { let self_ptr = self as *const EFIMemoryMapTag; - let start_area = self.first_desc.as_ptr(); + let start_area = (&self.descs[0]) as *const EFIMemoryDesc; EFIMemoryAreaIter { current_area: start_area as u64, // NOTE: `last_area` is only a bound, it doesn't necessarily point exactly to the last element - last_area: (self_ptr as u64 - + (self.size as u64 - core::mem::size_of::() as u64)), + last_area: (self_ptr as *const () as u64 + self.size as u64), entry_size: self.desc_size, phantom: PhantomData, } } } +impl TagTrait for EFIMemoryMapTag { + fn dst_size(base_tag: &Tag) -> usize { + assert!(base_tag.size as usize >= EFI_METADATA_SIZE); + base_tag.size as usize - EFI_METADATA_SIZE + } +} + +#[cfg(feature = "builder")] +impl StructAsBytes for EFIMemoryMapTag { + fn byte_size(&self) -> usize { + self.size.try_into().unwrap() + } +} + /// EFI Boot Memory Map Descriptor -#[derive(Debug)] +#[derive(Debug, Clone)] #[repr(C)] pub struct EFIMemoryDesc { typ: u32, @@ -200,6 +308,13 @@ pub struct EFIMemoryDesc { attr: u64, } +#[cfg(feature = "builder")] +impl StructAsBytes for EFIMemoryDesc { + fn byte_size(&self) -> usize { + mem::size_of::() + } +} + /// An enum of possible reported region types. #[derive(Debug, PartialEq, Eq)] pub enum EFIMemoryAreaType { @@ -254,6 +369,29 @@ pub enum EFIMemoryAreaType { EfiUnknown, } +impl From for u32 { + fn from(area: EFIMemoryAreaType) -> Self { + match area { + EFIMemoryAreaType::EfiReservedMemoryType => 0, + EFIMemoryAreaType::EfiLoaderCode => 1, + EFIMemoryAreaType::EfiLoaderData => 2, + EFIMemoryAreaType::EfiBootServicesCode => 3, + EFIMemoryAreaType::EfiBootServicesData => 4, + EFIMemoryAreaType::EfiRuntimeServicesCode => 5, + EFIMemoryAreaType::EfiRuntimeServicesData => 6, + EFIMemoryAreaType::EfiConventionalMemory => 7, + EFIMemoryAreaType::EfiUnusableMemory => 8, + EFIMemoryAreaType::EfiACPIReclaimMemory => 9, + EFIMemoryAreaType::EfiACPIMemoryNVS => 10, + EFIMemoryAreaType::EfiMemoryMappedIO => 11, + EFIMemoryAreaType::EfiMemoryMappedIOPortSpace => 12, + EFIMemoryAreaType::EfiPalCode => 13, + EFIMemoryAreaType::EfiPersistentMemory => 14, + EFIMemoryAreaType::EfiUnknown => panic!("unknown type"), + } + } +} + impl EFIMemoryDesc { /// The physical address of the memory region. pub fn physical_address(&self) -> u64 { @@ -294,14 +432,51 @@ impl EFIMemoryDesc { } } +impl Default for EFIMemoryDesc { + fn default() -> Self { + Self { + typ: EFIMemoryAreaType::EfiReservedMemoryType.into(), + _padding: 0, + phys_addr: 0, + virt_addr: 0, + num_pages: 0, + attr: 0, + } + } +} + /// EFI ExitBootServices was not called #[derive(Debug)] #[repr(C)] pub struct EFIBootServicesNotExited { - typ: u32, + typ: TagTypeId, size: u32, } +impl EFIBootServicesNotExited { + #[cfg(feature = "builder")] + pub fn new() -> Self { + Self::default() + } +} + +#[cfg(feature = "builder")] +impl Default for EFIBootServicesNotExited { + fn default() -> Self { + Self { + typ: TagType::EfiBs.into(), + size: mem::size_of::().try_into().unwrap(), + } + } +} + +#[cfg(feature = "builder")] +impl StructAsBytes for EFIBootServicesNotExited { + fn byte_size(&self) -> usize { + mem::size_of::() + } +} + /// An iterator over ALL EFI memory areas. #[derive(Clone, Debug)] pub struct EFIMemoryAreaIter<'a> { diff --git a/multiboot2/src/module.rs b/multiboot2/src/module.rs index 37fd12bb..d1db91ed 100644 --- a/multiboot2/src/module.rs +++ b/multiboot2/src/module.rs @@ -1,9 +1,17 @@ -use crate::tag_type::{Tag, TagIter, TagType}; -use crate::TagTrait; -use crate::TagTypeId; +use crate::{Tag, TagIter, TagTrait, TagType, TagTypeId}; + use core::fmt::{Debug, Formatter}; +use core::mem::size_of; use core::str::Utf8Error; +#[cfg(feature = "builder")] +use { + crate::builder::boxed_dst_tag, crate::builder::traits::StructAsBytes, alloc::boxed::Box, + alloc::vec::Vec, +}; + +const METADATA_SIZE: usize = size_of::() + 3 * size_of::(); + /// This tag indicates to the kernel what boot module was loaded along with /// the kernel image, and where it can be found. #[repr(C, packed)] // only repr(C) would add unwanted padding near name_byte. @@ -18,8 +26,21 @@ pub struct ModuleTag { } impl ModuleTag { + #[cfg(feature = "builder")] + pub fn new(start: u32, end: u32, cmdline: &str) -> Box { + let mut cmdline_bytes: Vec<_> = cmdline.bytes().collect(); + cmdline_bytes.push(0); + let start_bytes = start.to_le_bytes(); + let end_bytes = end.to_le_bytes(); + let mut content_bytes = [start_bytes, end_bytes].concat(); + content_bytes.extend_from_slice(&cmdline_bytes); + boxed_dst_tag(TagType::Module, &content_bytes) + } + /// Reads the command line of the boot module as Rust string slice without /// the null-byte. + /// This is an null-terminated UTF-8 string. If this returns `Err` then perhaps the memory + /// is invalid or the bootloader doesn't follow the spec. /// /// For example, this returns `"--test cmdline-option"`.if the GRUB config /// contains `"module2 /some_boot_module --test cmdline-option"`. @@ -47,10 +68,15 @@ impl ModuleTag { impl TagTrait for ModuleTag { fn dst_size(base_tag: &Tag) -> usize { - // The size of the sized portion of the module tag. - let tag_base_size = 16; - assert!(base_tag.size >= 8); - base_tag.size as usize - tag_base_size + assert!(base_tag.size as usize >= METADATA_SIZE); + base_tag.size as usize - METADATA_SIZE + } +} + +#[cfg(feature = "builder")] +impl StructAsBytes for ModuleTag { + fn byte_size(&self) -> usize { + self.size.try_into().unwrap() } } @@ -133,4 +159,15 @@ mod tests { assert_eq!({ tag.typ }, TagType::Module); assert_eq!(tag.cmdline().expect("must be valid UTF-8"), MSG); } + + /// Test to generate a tag from a given string. + #[test] + #[cfg(feature = "builder")] + fn test_build_str() { + use crate::builder::traits::StructAsBytes; + + let tag = ModuleTag::new(0, 0, MSG); + let bytes = tag.struct_as_bytes(); + assert_eq!(bytes, get_bytes()); + } } diff --git a/multiboot2/src/rsdp.rs b/multiboot2/src/rsdp.rs index bc7ef1ef..80bf5cf9 100644 --- a/multiboot2/src/rsdp.rs +++ b/multiboot2/src/rsdp.rs @@ -8,7 +8,12 @@ //! //! Even though the bootloader should give the address of the real RSDP/XSDT, the checksum and //! signature should be manually verified. -use crate::TagTypeId; +#[cfg(feature = "builder")] +use crate::builder::traits::StructAsBytes; +use crate::tag_type::{TagType, TagTypeId}; + +use core::convert::TryInto; +use core::mem::size_of; use core::slice; use core::str; use core::str::Utf8Error; @@ -29,6 +34,25 @@ pub struct RsdpV1Tag { } impl RsdpV1Tag { + #[cfg(feature = "builder")] + pub fn new( + signature: [u8; 8], + checksum: u8, + oem_id: [u8; 6], + revision: u8, + rsdt_address: u32, + ) -> Self { + Self { + typ: TagType::AcpiV1.into(), + size: size_of::().try_into().unwrap(), + signature, + checksum, + oem_id, + revision, + rsdt_address, + } + } + /// The "RSD PTR " marker signature. /// /// This is originally a 8-byte C string (not null terminated!) that must contain "RSD PTR " @@ -62,6 +86,13 @@ impl RsdpV1Tag { } } +#[cfg(feature = "builder")] +impl StructAsBytes for RsdpV1Tag { + fn byte_size(&self) -> usize { + size_of::() + } +} + /// This tag contains a copy of RSDP as defined per ACPI 2.0 or later specification. #[derive(Clone, Copy, Debug)] #[repr(C, packed)] @@ -72,7 +103,7 @@ pub struct RsdpV2Tag { checksum: u8, oem_id: [u8; 6], revision: u8, - _rsdt_address: u32, + rsdt_address: u32, length: u32, xsdt_address: u64, // This is the PHYSICAL address of the XSDT ext_checksum: u8, @@ -80,6 +111,33 @@ pub struct RsdpV2Tag { } impl RsdpV2Tag { + #[cfg(feature = "builder")] + #[allow(clippy::too_many_arguments)] + pub fn new( + signature: [u8; 8], + checksum: u8, + oem_id: [u8; 6], + revision: u8, + rsdt_address: u32, + length: u32, + xsdt_address: u64, + ext_checksum: u8, + ) -> Self { + Self { + typ: TagType::AcpiV2.into(), + size: size_of::().try_into().unwrap(), + signature, + checksum, + oem_id, + revision, + rsdt_address, + length, + xsdt_address, + ext_checksum, + _reserved: [0; 3], + } + } + /// The "RSD PTR " marker signature. /// /// This is originally a 8-byte C string (not null terminated!) that must contain "RSD PTR ". @@ -120,3 +178,10 @@ impl RsdpV2Tag { self.ext_checksum } } + +#[cfg(feature = "builder")] +impl StructAsBytes for RsdpV2Tag { + fn byte_size(&self) -> usize { + size_of::() + } +} diff --git a/multiboot2/src/smbios.rs b/multiboot2/src/smbios.rs index 12a4ec60..3cf4ff0f 100644 --- a/multiboot2/src/smbios.rs +++ b/multiboot2/src/smbios.rs @@ -1,7 +1,11 @@ -use crate::{Tag, TagTrait, TagTypeId}; +use crate::{Tag, TagTrait, TagType, TagTypeId}; +use core::convert::TryInto; use core::fmt::Debug; +#[cfg(feature = "builder")] +use {crate::builder::boxed_dst_tag, crate::builder::traits::StructAsBytes, alloc::boxed::Box}; + const METADATA_SIZE: usize = core::mem::size_of::() + core::mem::size_of::() + core::mem::size_of::() * 8; @@ -18,6 +22,15 @@ pub struct SmbiosTag { pub tables: [u8], } +impl SmbiosTag { + #[cfg(feature = "builder")] + pub fn new(major: u8, minor: u8, tables: &[u8]) -> Box { + let mut bytes = [major, minor, 0, 0, 0, 0, 0, 0].to_vec(); + bytes.extend(tables); + boxed_dst_tag(TagType::Smbios, &bytes) + } +} + impl TagTrait for SmbiosTag { fn dst_size(base_tag: &Tag) -> usize { assert!(base_tag.size as usize >= METADATA_SIZE); @@ -25,6 +38,13 @@ impl TagTrait for SmbiosTag { } } +#[cfg(feature = "builder")] +impl StructAsBytes for SmbiosTag { + fn byte_size(&self) -> usize { + self.size.try_into().unwrap() + } +} + impl Debug for SmbiosTag { fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { f.debug_struct("BootLoaderNameTag") @@ -66,4 +86,15 @@ mod tests { assert_eq!(tag.minor, 0); assert_eq!(tag.tables, [0xabu8; 24]); } + + /// Test to generate a tag. + #[test] + #[cfg(feature = "builder")] + fn test_build() { + use crate::builder::traits::StructAsBytes; + + let tag = SmbiosTag::new(3, 0, &[0xabu8; 24]); + let bytes = tag.struct_as_bytes(); + assert_eq!(bytes, get_bytes()); + } } diff --git a/multiboot2/src/tag_type.rs b/multiboot2/src/tag_type.rs index 42aaac77..82a40d50 100644 --- a/multiboot2/src/tag_type.rs +++ b/multiboot2/src/tag_type.rs @@ -5,6 +5,8 @@ //! - [`TagTypeId`] //! - [`TagType`] //! - [`Tag`] +#[cfg(feature = "builder")] +use crate::builder::traits::StructAsBytes; use crate::TagTrait; use core::fmt::{Debug, Formatter}; @@ -368,6 +370,13 @@ impl Default for EndTag { } } +#[cfg(feature = "builder")] +impl StructAsBytes for EndTag { + fn byte_size(&self) -> usize { + core::mem::size_of::() + } +} + #[derive(Clone, Debug)] pub struct TagIter<'a> { pub current: *const Tag,