diff --git a/multiboot2/Changelog.md b/multiboot2/Changelog.md index aad014ad..02938553 100644 --- a/multiboot2/Changelog.md +++ b/multiboot2/Changelog.md @@ -1,15 +1,24 @@ # CHANGELOG for crate `multiboot2` -## Unreleased +## 0.15.0 (2023-03-XX) - MSRV is 1.56.1 -- **BREAKING** fixed lifetime issues: `VBEInfoTag` is no longer static +- **BREAKING** fixed lifetime issues: `VBEInfoTag` is no longer `&static` +- **BREAKING:** `TagType` is now split into `TagTypeId` and `TagType` + - `TagTypeId` is a binary-compatible form of a Multiboot2 tag id + - `TagType` is a higher-level abstraction for either specified or custom tags + but not ABI compatible. - fixed another internal lifetime issue - `BootInformation::framebuffer_tag()` now returns `Option>` instead of `Option` which prevents a possible panic. If the `--unstable` feature is used, `UnknownFramebufferType` implements `core::error::Error`. - Fixed misleading documentation of the `BootInformation::efi_memory_map_tag` - tag. +- `BootInformation` now publicly exports the `get_tag` function allowing you to + work with custom tags. An example is given in the function documentation. + (check docs.rs). There is also a small unit test that you can use to learn + from. +- There exists a seamless integration between `u32`, `TagType`, and `TagTypeId` + via `From` and `PartialEq`-implementations. ## 0.14.2 (2023-03-17) - documentation fixes diff --git a/multiboot2/src/boot_loader_name.rs b/multiboot2/src/boot_loader_name.rs index c9fc73bd..ea2cb07c 100644 --- a/multiboot2/src/boot_loader_name.rs +++ b/multiboot2/src/boot_loader_name.rs @@ -1,4 +1,4 @@ -use crate::TagType; +use crate::TagTypeId; use core::str::Utf8Error; /// This tag contains the name of the bootloader that is booting the kernel. @@ -8,7 +8,7 @@ use core::str::Utf8Error; #[derive(Clone, Copy, Debug)] #[repr(C, packed)] // only repr(C) would add unwanted padding before first_section pub struct BootLoaderNameTag { - typ: TagType, + typ: TagTypeId, size: u32, /// Null-terminated UTF-8 string string: u8, @@ -47,7 +47,7 @@ mod tests { // size is: 4 bytes for tag + 4 bytes for size + length of null-terminated string let size = (4 + 4 + MSG.as_bytes().len() + 1) as u32; [ - &((TagType::BootLoaderName as u32).to_ne_bytes()), + &((TagType::BootLoaderName.val()).to_ne_bytes()), &size.to_ne_bytes(), MSG.as_bytes(), // Null Byte diff --git a/multiboot2/src/command_line.rs b/multiboot2/src/command_line.rs index 80ddf70b..978cbe0a 100644 --- a/multiboot2/src/command_line.rs +++ b/multiboot2/src/command_line.rs @@ -1,6 +1,6 @@ //! Module for [CommandLineTag]. -use crate::TagType; +use crate::TagTypeId; use core::mem; use core::slice; use core::str; @@ -12,7 +12,7 @@ use core::str; #[derive(Clone, Copy, Debug)] #[repr(C, packed)] // only repr(C) would add unwanted padding before first_section pub struct CommandLineTag { - typ: TagType, + typ: TagTypeId, size: u32, /// Null-terminated UTF-8 string string: u8, @@ -51,7 +51,7 @@ mod tests { // size is: 4 bytes for tag + 4 bytes for size + length of null-terminated string let size = (4 + 4 + MSG.as_bytes().len() + 1) as u32; [ - &((TagType::Cmdline as u32).to_ne_bytes()), + &((TagType::Cmdline.val()).to_ne_bytes()), &size.to_ne_bytes(), MSG.as_bytes(), // Null Byte diff --git a/multiboot2/src/efi.rs b/multiboot2/src/efi.rs index d22b323c..4a239f9d 100644 --- a/multiboot2/src/efi.rs +++ b/multiboot2/src/efi.rs @@ -1,12 +1,12 @@ //! All MBI tags related to (U)EFI. -use crate::TagType; +use crate::TagTypeId; /// EFI system table in 32 bit mode #[derive(Clone, Copy, Debug)] #[repr(C, packed)] // only repr(C) would add unwanted padding before first_section pub struct EFISdt32 { - typ: TagType, + typ: TagTypeId, size: u32, pointer: u32, } @@ -22,7 +22,7 @@ impl EFISdt32 { #[derive(Clone, Copy, Debug)] #[repr(C)] pub struct EFISdt64 { - typ: TagType, + typ: TagTypeId, size: u32, pointer: u64, } @@ -38,7 +38,7 @@ impl EFISdt64 { #[derive(Debug)] #[repr(C)] pub struct EFIImageHandle32 { - typ: TagType, + typ: TagTypeId, size: u32, pointer: u32, } @@ -54,7 +54,7 @@ impl EFIImageHandle32 { #[derive(Debug)] #[repr(C)] pub struct EFIImageHandle64 { - typ: TagType, + typ: TagTypeId, size: u32, pointer: u64, } diff --git a/multiboot2/src/elf_sections.rs b/multiboot2/src/elf_sections.rs index c89a37e4..a628079a 100644 --- a/multiboot2/src/elf_sections.rs +++ b/multiboot2/src/elf_sections.rs @@ -1,4 +1,5 @@ use crate::tag_type::Tag; +use crate::TagType; use core::fmt::{Debug, Formatter}; /// This tag contains section header table from an ELF kernel. @@ -11,7 +12,7 @@ pub struct ElfSectionsTag { } pub unsafe fn elf_sections_tag(tag: &Tag, offset: usize) -> ElfSectionsTag { - assert_eq!(9, tag.typ); + assert_eq!(TagType::ElfSections.val(), tag.typ); let es = ElfSectionsTag { inner: (tag as *const Tag).offset(1) as *const ElfSectionsTagInner, offset, diff --git a/multiboot2/src/image_load_addr.rs b/multiboot2/src/image_load_addr.rs index ab1d432c..a5300299 100644 --- a/multiboot2/src/image_load_addr.rs +++ b/multiboot2/src/image_load_addr.rs @@ -1,11 +1,11 @@ -use crate::TagType; +use crate::TagTypeId; /// If the image has relocatable header tag, this tag contains the image's /// base physical address. #[derive(Debug)] #[repr(C)] pub struct ImageLoadPhysAddr { - typ: TagType, + typ: TagTypeId, size: u32, load_base_addr: u32, } diff --git a/multiboot2/src/lib.rs b/multiboot2/src/lib.rs index f083d9ca..38af02bc 100644 --- a/multiboot2/src/lib.rs +++ b/multiboot2/src/lib.rs @@ -55,8 +55,8 @@ pub use memory_map::{ }; pub use module::{ModuleIter, ModuleTag}; pub use rsdp::{RsdpV1Tag, RsdpV2Tag}; -pub use tag_type::TagType; -use tag_type::{Tag, TagIter}; +use tag_type::TagIter; +pub use tag_type::{Tag, TagType, TagTypeId}; pub use vbe_info::{ VBECapabilities, VBEControlInfo, VBEDirectColorAttributes, VBEField, VBEInfoTag, VBEMemoryModel, VBEModeAttributes, VBEModeInfo, VBEWindowAttributes, @@ -218,14 +218,13 @@ impl BootInformation { /// Search for the ELF Sections tag. pub fn elf_sections_tag(&self) -> Option { - self.get_tag(TagType::ElfSections) + self.get_tag::(TagType::ElfSections) .map(|tag| unsafe { elf_sections::elf_sections_tag(tag, self.offset) }) } /// Search for the Memory map tag. pub fn memory_map_tag(&self) -> Option<&MemoryMapTag> { - self.get_tag(TagType::Mmap) - .map(|tag| unsafe { &*(tag as *const Tag as *const MemoryMapTag) }) + self.get_tag::(TagType::Mmap) } /// Get an iterator of all module tags. @@ -235,45 +234,39 @@ impl BootInformation { /// Search for the BootLoader name tag. pub fn boot_loader_name_tag(&self) -> Option<&BootLoaderNameTag> { - self.get_tag(TagType::BootLoaderName) - .map(|tag| unsafe { &*(tag as *const Tag as *const BootLoaderNameTag) }) + self.get_tag::(TagType::BootLoaderName) } /// Search for the Command line tag. pub fn command_line_tag(&self) -> Option<&CommandLineTag> { - self.get_tag(TagType::Cmdline) - .map(|tag| unsafe { &*(tag as *const Tag as *const CommandLineTag) }) + self.get_tag::(TagType::Cmdline) } /// 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) + self.get_tag::(TagType::Framebuffer) .map(framebuffer::framebuffer_tag) } /// Search for the EFI 32-bit SDT tag. pub fn efi_sdt_32_tag(&self) -> Option<&EFISdt32> { - self.get_tag(TagType::Efi32) - .map(|tag| unsafe { &*(tag as *const Tag as *const EFISdt32) }) + self.get_tag::(TagType::Efi32) } /// Search for the EFI 64-bit SDT tag. pub fn efi_sdt_64_tag(&self) -> Option<&EFISdt64> { - self.get_tag(TagType::Efi64) - .map(|tag| unsafe { &*(tag as *const Tag as *const EFISdt64) }) + self.get_tag::(TagType::Efi64) } /// Search for the (ACPI 1.0) RSDP tag. pub fn rsdp_v1_tag(&self) -> Option<&RsdpV1Tag> { - self.get_tag(TagType::AcpiV1) - .map(|tag| unsafe { &*(tag as *const Tag as *const RsdpV1Tag) }) + self.get_tag::(TagType::AcpiV1) } /// Search for the (ACPI 2.0 or later) RSDP tag. pub fn rsdp_v2_tag(&self) -> Option<&RsdpV2Tag> { - self.get_tag(TagType::AcpiV2) - .map(|tag| unsafe { &*(tag as *const Tag as *const RsdpV2Tag) }) + self.get_tag::(TagType::AcpiV2) } /// Search for the EFI Memory map tag, if the boot services were exited. @@ -283,44 +276,82 @@ impl BootInformation { pub fn efi_memory_map_tag(&self) -> Option<&EFIMemoryMapTag> { // If the EFIBootServicesNotExited is present, then we should not use // the memory map, as it could still be in use. - match self.get_tag(TagType::EfiBs) { + match self.get_tag::(TagType::EfiBs) { Some(_tag) => None, - None => self - .get_tag(TagType::EfiMmap) - .map(|tag| unsafe { &*(tag as *const Tag as *const EFIMemoryMapTag) }), + None => self.get_tag::(TagType::EfiMmap), } } /// Search for the EFI 32-bit image handle pointer. pub fn efi_32_ih(&self) -> Option<&EFIImageHandle32> { - self.get_tag(TagType::Efi32Ih) - .map(|tag| unsafe { &*(tag as *const Tag as *const EFIImageHandle32) }) + self.get_tag::(TagType::Efi32Ih) } /// Search for the EFI 64-bit image handle pointer. pub fn efi_64_ih(&self) -> Option<&EFIImageHandle64> { - self.get_tag(TagType::Efi64Ih) - .map(|tag| unsafe { &*(tag as *const Tag as *const EFIImageHandle64) }) + self.get_tag::(TagType::Efi64Ih) } /// Search for the Image Load Base Physical Address. pub fn load_base_addr(&self) -> Option<&ImageLoadPhysAddr> { - self.get_tag(TagType::LoadBaseAddr) - .map(|tag| unsafe { &*(tag as *const Tag as *const ImageLoadPhysAddr) }) + self.get_tag::(TagType::LoadBaseAddr) } /// Search for the VBE information tag. pub fn vbe_info_tag(&self) -> Option<&VBEInfoTag> { - self.get_tag(TagType::Vbe) - .map(|tag| unsafe { &*(tag as *const Tag as *const VBEInfoTag) }) + self.get_tag::(TagType::Vbe) } fn get(&self) -> &BootInformationInner { unsafe { &*self.inner } } - fn get_tag(&self, typ: TagType) -> Option<&Tag> { - self.tags().find(|tag| tag.typ == typ) + /// Public getter to find any Multiboot tag by its type, including + /// specified and custom ones. + /// + /// The parameter can be of type `u32`, [`TagType`], or [`TagTypeId`]. + /// + /// # Specified or Custom Tags + /// The Multiboot2 specification specifies a list of tags, see [`TagType`]. + /// However, it doesn't forbid to use custom tags. Because of this, there + /// exists the [`TagType`] abstraction. It is recommended to use this + /// getter only for custom tags. For specified tags, use getters, such as + /// [`Self::efi_64_ih`]. + /// + /// ## Use Custom Tags + /// The following example shows how you may use this interface to parse custom tags from + /// the MBI. Custom tags must be `Sized`. Hence, they are not allowed to contain a field such + /// as `name: [u8]`. + /// + /// **Belows example needs Rust 1.64 or newer because of std::ffi imports!** + /// ```ignore + /// use std::ffi::{c_char, CStr}; + /// use multiboot2::TagTypeId; + /// + /// #[repr(C, align(8))] + /// struct CustomTag { + /// // new type from the lib: has repr(u32) + /// tag: TagTypeId, + /// size: u32, + /// // begin of inline string + /// name: u8, + /// } + /// + /// let mbi = unsafe { multiboot2::load(0xdeadbeef).unwrap() }; + /// + /// let tag = mbi + /// // type definition from end user; must be `Sized`! + /// .get_tag::(0x1337) + /// .unwrap(); + /// let name = &tag.name as *const u8 as *const c_char; + /// let str = unsafe { CStr::from_ptr(name).to_str().unwrap() }; + /// assert_eq!(str, "name"); + /// ``` + pub fn get_tag>(&self, typ: TagType) -> Option<&Tag> { + let typ = typ.into(); + self.tags() + .find(|tag| tag.typ == typ) + .map(|tag| tag.cast_tag::()) } fn tags(&self) -> TagIter { @@ -330,16 +361,16 @@ impl BootInformation { impl BootInformationInner { fn has_valid_end_tag(&self) -> bool { - const END_TAG: Tag = Tag { - typ: TagType::End, + let end_tag_prototype: Tag = Tag { + typ: TagType::End.into(), size: 8, }; let self_ptr = self as *const _; - let end_tag_addr = self_ptr as usize + (self.total_size - END_TAG.size) as usize; + let end_tag_addr = self_ptr as usize + (self.total_size - end_tag_prototype.size) as usize; let end_tag = unsafe { &*(end_tag_addr as *const Tag) }; - end_tag.typ == END_TAG.typ && end_tag.size == END_TAG.size + end_tag.typ == end_tag_prototype.typ && end_tag.size == end_tag_prototype.size } } @@ -448,6 +479,7 @@ impl Reader { #[cfg(test)] mod tests { use super::*; + use std::{mem, slice}; #[test] fn no_tags() { @@ -1457,4 +1489,119 @@ mod tests { fn consumer(_e: E) {} consumer(MbiLoadError::IllegalAddress) } + + #[test] + fn custom_tag() { + const CUSTOM_TAG_ID: u32 = 0x1337; + + #[repr(C, align(8))] + struct Bytes([u8; 32]); + let bytes: Bytes = Bytes([ + 32, + 0, + 0, + 0, // total_size + 0, + 0, + 0, + 0, // reserved + // my custom tag + CUSTOM_TAG_ID.to_ne_bytes()[0], + CUSTOM_TAG_ID.to_ne_bytes()[1], + CUSTOM_TAG_ID.to_ne_bytes()[2], + CUSTOM_TAG_ID.to_ne_bytes()[3], + 13, + 0, + 0, + 0, // tag size + 110, + 97, + 109, + 101, // ASCII string 'name' + 0, + 0, + 0, + 0, // null byte + padding + 0, + 0, + 0, + 0, // end tag type + 8, + 0, + 0, + 0, // end tag size + ]); + let addr = bytes.0.as_ptr() as usize; + let bi = unsafe { load(addr) }; + let bi = bi.unwrap(); + assert_eq!(addr, bi.start_address()); + assert_eq!(addr + bytes.0.len(), bi.end_address()); + assert_eq!(bytes.0.len(), bi.total_size()); + + #[repr(C, align(8))] + struct CustomTag { + tag: TagTypeId, + size: u32, + name: u8, + } + + let tag = bi.get_tag::(CUSTOM_TAG_ID).unwrap(); + + // strlen without null byte + let strlen = tag.size as usize - mem::size_of::(); + let bytes = unsafe { slice::from_raw_parts((&tag.name) as *const u8, strlen) }; + let name = core::str::from_utf8(bytes).unwrap(); + assert_eq!(name, "name"); + } + + /// Tests that `get_tag` can consume multiple types that implement `Into` + #[test] + fn get_tag_into_variants() { + #[repr(C, align(8))] + struct Bytes([u8; 32]); + let bytes: Bytes = Bytes([ + 32, + 0, + 0, + 0, // total_size + 0, + 0, + 0, + 0, // reserved + TagType::Cmdline.val().to_ne_bytes()[0], + TagType::Cmdline.val().to_ne_bytes()[1], + TagType::Cmdline.val().to_ne_bytes()[2], + TagType::Cmdline.val().to_ne_bytes()[3], + 13, + 0, + 0, + 0, // tag size + 110, + 97, + 109, + 101, // ASCII string 'name' + 0, + 0, + 0, + 0, // null byte + padding + 0, + 0, + 0, + 0, // end tag type + 8, + 0, + 0, + 0, // end tag size + ]); + + let addr = bytes.0.as_ptr() as usize; + let bi = unsafe { load(addr) }; + let bi = bi.unwrap(); + + let _tag = bi.get_tag::(TagType::Cmdline).unwrap(); + + let _tag = bi.get_tag::(1).unwrap(); + + let _tag = bi.get_tag::(TagTypeId::new(1)).unwrap(); + } } diff --git a/multiboot2/src/memory_map.rs b/multiboot2/src/memory_map.rs index e8972947..20c1bb39 100644 --- a/multiboot2/src/memory_map.rs +++ b/multiboot2/src/memory_map.rs @@ -1,4 +1,4 @@ -use crate::TagType; +use crate::TagTypeId; use core::marker::PhantomData; /// This tag provides an initial host memory map. @@ -14,7 +14,7 @@ use core::marker::PhantomData; #[derive(Debug)] #[repr(C)] pub struct MemoryMapTag { - typ: TagType, + typ: TagTypeId, size: u32, entry_size: u32, entry_version: u32, @@ -126,7 +126,7 @@ impl<'a> Iterator for MemoryAreaIter<'a> { #[derive(Debug)] #[repr(C)] pub struct EFIMemoryMapTag { - typ: TagType, + typ: TagTypeId, size: u32, desc_size: u32, desc_version: u32, diff --git a/multiboot2/src/module.rs b/multiboot2/src/module.rs index aeea785f..555cf89a 100644 --- a/multiboot2/src/module.rs +++ b/multiboot2/src/module.rs @@ -1,4 +1,5 @@ use crate::tag_type::{Tag, TagIter, TagType}; +use crate::TagTypeId; use core::fmt::{Debug, Formatter}; use core::str::Utf8Error; @@ -7,7 +8,7 @@ use core::str::Utf8Error; #[derive(Clone, Copy)] #[repr(C, packed)] // only repr(C) would add unwanted padding near name_byte. pub struct ModuleTag { - typ: TagType, + typ: TagTypeId, size: u32, mod_start: u32, mod_end: u32, @@ -75,7 +76,7 @@ impl<'a> Iterator for ModuleIter<'a> { fn next(&mut self) -> Option<&'a ModuleTag> { self.iter - .find(|x| x.typ == TagType::Module) + .find(|tag| tag.typ == TagType::Module) .map(|tag| unsafe { &*(tag as *const Tag as *const ModuleTag) }) } } @@ -102,7 +103,7 @@ mod tests { // 4 bytes mod_start + 4 bytes mod_end let size = (4 + 4 + 4 + 4 + MSG.as_bytes().len() + 1) as u32; [ - &((TagType::Module as u32).to_ne_bytes()), + &((TagType::Module.val()).to_ne_bytes()), &size.to_ne_bytes(), &0_u32.to_ne_bytes(), &0_u32.to_ne_bytes(), diff --git a/multiboot2/src/rsdp.rs b/multiboot2/src/rsdp.rs index 9188f204..bc7ef1ef 100644 --- a/multiboot2/src/rsdp.rs +++ b/multiboot2/src/rsdp.rs @@ -8,7 +8,7 @@ //! //! Even though the bootloader should give the address of the real RSDP/XSDT, the checksum and //! signature should be manually verified. -use crate::TagType; +use crate::TagTypeId; use core::slice; use core::str; use core::str::Utf8Error; @@ -19,7 +19,7 @@ const RSDPV1_LENGTH: usize = 20; #[derive(Clone, Copy, Debug)] #[repr(C, packed)] pub struct RsdpV1Tag { - typ: TagType, + typ: TagTypeId, size: u32, signature: [u8; 8], checksum: u8, @@ -66,7 +66,7 @@ impl RsdpV1Tag { #[derive(Clone, Copy, Debug)] #[repr(C, packed)] pub struct RsdpV2Tag { - typ: TagType, + typ: TagTypeId, size: u32, signature: [u8; 8], checksum: u8, diff --git a/multiboot2/src/tag_type.rs b/multiboot2/src/tag_type.rs index 3f2e12a0..3d5132c3 100644 --- a/multiboot2/src/tag_type.rs +++ b/multiboot2/src/tag_type.rs @@ -1,126 +1,320 @@ -//! Module for [`TagType`]. +//! Module for the basic Multiboot2 tag and corresponding tag types. +//! +//! The relevant exports of this module are: +//! - [`TagTypeId`] +//! - [`TagType`] +//! - [`Tag`] use core::fmt::{Debug, Formatter}; use core::hash::Hash; use core::marker::PhantomData; -/// Possible types of a Tag in the Multiboot2 Information Structure (MBI), therefore the value -/// of the the `typ` property. The names and values are taken from the example C code -/// at the bottom of the Multiboot2 specification. -#[repr(u32)] -#[derive(Copy, Clone, Debug, Eq, Ord, PartialOrd, PartialEq, Hash)] +/// Serialized form of [`TagType`] that matches the binary representation +/// (`u32`). The abstraction corresponds to the `typ`/`type` field of a +/// Multiboot2 [`Tag`]. This type can easily be created from or converted to +/// [`TagType`]. +#[repr(transparent)] +#[derive(Copy, Clone, Debug, PartialOrd, PartialEq, Eq, Ord, Hash)] +pub struct TagTypeId(u32); + +impl TagTypeId { + /// Constructor. + pub fn new(val: u32) -> Self { + Self(val) + } +} + +/// Higher level abstraction for [`TagTypeId`] that assigns each possible value +/// to a specific semantic according to the specification. Additionally, it +/// allows to use the [`TagType::Custom`] variant. It is **not binary compatible** +/// with [`TagTypeId`]. +#[derive(Copy, Clone, Debug, PartialOrd, PartialEq, Eq, Ord, Hash)] pub enum TagType { - /// Marks the end of the tags. - End = 0, - /// Additional command line string. - /// For example `''` or `'--my-custom-option foo --provided by_grub`, if your GRUB config - /// contains `multiboot2 /boot/multiboot2-binary.elf --my-custom-option foo --provided by_grub` - Cmdline = 1, - /// Name of the bootloader, e.g. 'GRUB 2.04-1ubuntu44.2' - BootLoaderName = 2, - /// Additional Multiboot modules, which are BLOBs provided in memory. For example an initial - /// ram disk with essential drivers. - Module = 3, - /// ‘mem_lower’ and ‘mem_upper’ indicate the amount of lower and upper memory, respectively, - /// in kilobytes. Lower memory starts at address 0, and upper memory starts at address 1 - /// megabyte. The maximum possible value for lower memory is 640 kilobytes. The value returned - /// for upper memory is maximally the address of the first upper memory hole minus 1 megabyte. - /// It is not guaranteed to be this value. + /// Tag `0`: Marks the end of the tags. + End, + /// Tag `1`: Additional command line string. + /// For example `''` or `'--my-custom-option foo --provided by_grub`, if + /// your GRUB config contains `multiboot2 /boot/multiboot2-binary.elf --my-custom-option foo --provided by_grub` + Cmdline, + /// Tag `2`: Name of the bootloader, e.g. 'GRUB 2.04-1ubuntu44.2' + BootLoaderName, + /// Tag `3`: Additional Multiboot modules, which are BLOBs provided in + /// memory. For example an initial ram disk with essential drivers. + Module, + /// Tag `4`: ‘mem_lower’ and ‘mem_upper’ indicate the amount of lower and + /// upper memory, respectively, in kilobytes. Lower memory starts at + /// address 0, and upper memory starts at address 1 megabyte. The maximum + /// possible value for lower memory is 640 kilobytes. The value returned + /// for upper memory is maximally the address of the first upper memory + /// hole minus 1 megabyte. It is not guaranteed to be this value. /// - /// 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 (EFI boot services not terminated tag exists in - /// Multiboot2 information structure). - BasicMeminfo = 4, - /// This tag indicates which BIOS disk device the boot loader loaded the OS image from. If the - /// OS image was not loaded from a BIOS disk, then this tag must not be present. The operating - /// system may use this field as a hint for determining its own root device, but is not + /// 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 (EFI + /// boot services not terminated tag exists in Multiboot2 information + /// structure). + BasicMeminfo, + /// Tag `5`: This tag indicates which BIOS disk device the boot loader + /// loaded the OS image from. If the OS image was not loaded from a BIOS + /// disk, then this tag must not be present. The operating system may use + /// this field as a hint for determining its own root device, but is not /// required to. - Bootdev = 5, - /// Memory map. The map provided is guaranteed to list all standard RAM that should be - /// available for normal use. This type however includes the regions occupied by kernel, mbi, - /// segments and modules. Kernel must take care not to overwrite these regions. - // - // 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 (EFI boot services not terminated tag exists in - // Multiboot2 information structure). - Mmap = 6, - /// Contains the VBE control information returned by the VBE Function 00h and VBE mode - /// information returned by the VBE Function 01h, respectively. Note that VBE 3.0 defines - /// another protected mode interface which is incompatible with the old one. If you want to use the new protected mode interface, you will have to find the table yourself. - Vbe = 7, - /// Framebuffer. - Framebuffer = 8, - /// This tag contains section header table from an ELF kernel, the size of each entry, number - /// of entries, and the string table used as the index of names. They correspond to the - /// ‘shdr_*’ entries (‘shdr_num’, etc.) in the Executable and Linkable Format (ELF) - /// specification in the program header. - ElfSections = 9, - /// APM table. See Advanced Power Management (APM) BIOS Interface Specification, for more - /// information. - Apm = 10, - /// This tag contains pointer to i386 EFI system table. - Efi32 = 11, - /// This tag contains pointer to amd64 EFI system table. - Efi64 = 12, - /// This tag contains a copy of SMBIOS tables as well as their version. - Smbios = 13, - /// Also called "AcpiOld" in other multiboot2 implementations. - AcpiV1 = 14, - /// Refers to version 2 and later of Acpi. + Bootdev, + /// Tag `6`: Memory map. The map provided is guaranteed to list all + /// standard RAM that should be available for normal use. This type however + /// includes the regions occupied by kernel, mbi, segments and modules. + /// Kernel must take care not to overwrite these regions. + /// + /// 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 (EFI + /// boot services not terminated tag exists in Multiboot2 information + /// structure). + Mmap, + /// Tag `7`: Contains the VBE control information returned by the VBE + /// Function `0x00` and VBE mode information returned by the VBE Function + /// `0x01`, respectively. Note that VBE 3.0 defines another protected mode + /// interface which is incompatible with the old one. If you want to use + /// the new protected mode interface, you will have to find the table + /// yourself. + Vbe, + /// Tag `8`: Framebuffer. + Framebuffer, + /// Tag `9`: This tag contains section header table from an ELF kernel, the + /// size of each entry, number of entries, and the string table used as the + /// index of names. They correspond to the `shdr_*` entries (`shdr_num`, + /// etc.) in the Executable and Linkable Format (ELF) specification in the + /// program header. + ElfSections, + /// Tag `10`: APM table. See Advanced Power Management (APM) BIOS Interface + /// Specification, for more information. + Apm, + /// Tag `11`: This tag contains pointer to i386 EFI system table. + Efi32, + /// Tag `21`: This tag contains pointer to amd64 EFI system table. + Efi64, + /// Tag `13`: This tag contains a copy of SMBIOS tables as well as their + /// version. + Smbios, + /// Tag `14`: Also called "AcpiOld" in other multiboot2 implementations. + AcpiV1, + /// Tag `15`: Refers to version 2 and later of Acpi. /// Also called "AcpiNew" in other multiboot2 implementations. - AcpiV2 = 15, - /// This tag contains network information in the format specified as DHCP. It may be either a - /// real DHCP reply or just the configuration info in the same format. This tag appears once + AcpiV2, + /// Tag `16`: This tag contains network information in the format specified + /// as DHCP. It may be either a real DHCP reply or just the configuration + /// info in the same format. This tag appears once /// per card. - Network = 16, - /// This tag contains EFI memory map as per EFI specification. - /// 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 (EFI boot services not terminated tag exists in Multiboot2 information structure). - EfiMmap = 17, - /// This tag indicates ExitBootServices wasn't called. - EfiBs = 18, - /// This tag contains pointer to EFI i386 image handle. Usually it is boot loader image handle. - Efi32Ih = 19, - /// This tag contains pointer to EFI amd64 image handle. Usually it is boot loader image handle. - Efi64Ih = 20, - /// This tag contains image load base physical address. The spec tells - /// "It is provided only if image has relocatable header tag." but experience showed - /// that this is not true for at least GRUB 2. - LoadBaseAddr = 21, + Network, + /// Tag `17`: This tag contains EFI memory map as per EFI specification. + /// 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 (EFI + /// boot services not terminated tag exists in Multiboot2 information + /// structure). + EfiMmap, + /// Tag `18`: This tag indicates ExitBootServices wasn't called. + EfiBs, + /// Tag `19`: This tag contains pointer to EFI i386 image handle. Usually + /// it is boot loader image handle. + Efi32Ih, + /// Tag `20`: This tag contains pointer to EFI amd64 image handle. Usually + /// it is boot loader image handle. + Efi64Ih, + /// Tag `21`: This tag contains image load base physical address. The spec + /// tells *"It is provided only if image has relocatable header tag."* but + /// experience showed that this is not true for at least GRUB 2. + LoadBaseAddr, + /// Custom tag types `> 21`. The Multiboot2 spec doesn't explicitly allow + /// or disallow them. Bootloader and OS developers are free to use custom + /// tags. + Custom(u32), } -// each compare/equal direction must be implemented manually -impl PartialEq for TagType { - fn eq(&self, other: &u32) -> bool { - *self as u32 == *other +impl TagType { + /// Convenient wrapper to get the underlying `u32` representation of the tag. + pub fn val(&self) -> u32 { + u32::from(*self) + } +} + +/// Relevant `From`-implementations for conversions between `u32`, [´TagTypeId´] +/// and [´TagType´]. +mod primitive_conversion_impls { + use super::*; + + impl From for TagTypeId { + fn from(value: u32) -> Self { + // SAFETY: the type has repr(transparent) + unsafe { core::mem::transmute(value) } + } + } + + impl From for u32 { + fn from(value: TagTypeId) -> Self { + value.0 as _ + } + } + + impl From for TagType { + fn from(value: u32) -> Self { + match value { + 0 => TagType::End, + 1 => TagType::Cmdline, + 2 => TagType::BootLoaderName, + 3 => TagType::Module, + 4 => TagType::BasicMeminfo, + 5 => TagType::Bootdev, + 6 => TagType::Mmap, + 7 => TagType::Vbe, + 8 => TagType::Framebuffer, + 9 => TagType::ElfSections, + 10 => TagType::Apm, + 11 => TagType::Efi32, + 12 => TagType::Efi64, + 13 => TagType::Smbios, + 14 => TagType::AcpiV1, + 15 => TagType::AcpiV2, + 16 => TagType::Network, + 17 => TagType::EfiMmap, + 18 => TagType::EfiBs, + 19 => TagType::Efi32Ih, + 20 => TagType::Efi64Ih, + 21 => TagType::LoadBaseAddr, + c => TagType::Custom(c), + } + } + } + + impl From for u32 { + fn from(value: TagType) -> Self { + match value { + TagType::End => 0, + TagType::Cmdline => 1, + TagType::BootLoaderName => 2, + TagType::Module => 3, + TagType::BasicMeminfo => 4, + TagType::Bootdev => 5, + TagType::Mmap => 6, + TagType::Vbe => 7, + TagType::Framebuffer => 8, + TagType::ElfSections => 9, + TagType::Apm => 10, + TagType::Efi32 => 11, + TagType::Efi64 => 12, + TagType::Smbios => 13, + TagType::AcpiV1 => 14, + TagType::AcpiV2 => 15, + TagType::Network => 16, + TagType::EfiMmap => 17, + TagType::EfiBs => 18, + TagType::Efi32Ih => 19, + TagType::Efi64Ih => 20, + TagType::LoadBaseAddr => 21, + TagType::Custom(c) => c, + } + } } } -// each compare/equal direction must be implemented manually -impl PartialEq for u32 { - fn eq(&self, other: &TagType) -> bool { - *self == *other as u32 +/// `From`-implementations for conversions between [´TagTypeId´] and [´TagType´]. +mod intermediate_conversion_impls { + use super::*; + + impl From for TagType { + fn from(value: TagTypeId) -> Self { + let value = u32::from(value); + TagType::from(value) + } + } + + impl From for TagTypeId { + fn from(value: TagType) -> Self { + let value = u32::from(value); + TagTypeId::from(value) + } } } -/// All tags that could passed via the Multiboot2 information structure to a payload/program/kernel. -/// Better not confuse this with the Multiboot2 header tags. They are something different. +/// Implements `partial_eq` between [´TagTypeId´] and [´TagType´]. Two values +/// are equal if their `u32` representation is equal. Additionally, `u32` can +/// be compared with [´TagTypeId´]. +mod partial_eq_impls { + use super::*; + + impl PartialEq for TagType { + fn eq(&self, other: &TagTypeId) -> bool { + let this = u32::from(*self); + let that = u32::from(*other); + this == that + } + } + + // each compare/equal direction must be implemented manually + impl PartialEq for TagTypeId { + fn eq(&self, other: &TagType) -> bool { + other.eq(self) + } + } + + impl PartialEq for TagTypeId { + fn eq(&self, other: &u32) -> bool { + let this = u32::from(*self); + this == *other + } + } + + impl PartialEq for u32 { + fn eq(&self, other: &TagTypeId) -> bool { + other.eq(self) + } + } + + impl PartialEq for TagType { + fn eq(&self, other: &u32) -> bool { + let this = u32::from(*self); + this == *other + } + } + + impl PartialEq for u32 { + fn eq(&self, other: &TagType) -> bool { + other.eq(self) + } + } +} + +/// Common base structure for all tags that can be passed via the Multiboot2 +/// information structure (MBI) to a Multiboot2 payload/program/kernel. +/// +/// Do not confuse them with the Multiboot2 header tags. They are something +/// different. #[derive(Clone, Copy)] #[repr(C)] pub struct Tag { - // u32 value - pub typ: TagType, + pub typ: TagTypeId, // u32 pub size: u32, - // tag specific fields + // additional, tag specific fields +} + +impl Tag { + /// Casts the base tag to the specific tag type. + pub fn cast_tag<'a, T>(&self) -> &'a T { + unsafe { &*(self as *const Tag as *const T) } + } } impl Debug for Tag { fn fmt(&self, f: &mut Formatter<'_>) -> core::fmt::Result { - f.debug_struct("Tag") - .field("typ", &self.typ) - .field("typ (numeric)", &(self.typ as u32)) - .field("size", &(self.size)) - .finish() + let tag_type = TagType::from(self.typ); + + let mut debug = f.debug_struct("Tag"); + debug.field("typ", &tag_type); + + if !matches!(tag_type, TagType::Custom(_)) { + debug.field("typ (numeric)", &(u32::from(self.typ))); + } + + debug.field("size", &(self.size)); + + debug.finish() } } @@ -145,7 +339,8 @@ impl<'a> Iterator for TagIter<'a> { fn next(&mut self) -> Option<&'a Tag> { match unsafe { &*self.current } { &Tag { - typ: TagType::End, + // END-Tag + typ: TagTypeId(0), size: 8, } => None, // end tag tag => { @@ -163,6 +358,7 @@ impl<'a> Iterator for TagIter<'a> { #[cfg(test)] mod tests { use super::*; + use std::mem::{align_of, size_of}; #[test] fn test_hashset() { @@ -196,5 +392,41 @@ mod tests { fn test_partial_eq_u32() { assert_eq!(21, TagType::LoadBaseAddr); assert_eq!(TagType::LoadBaseAddr, 21); + assert_eq!(21, TagTypeId(21)); + assert_eq!(TagTypeId(21), 21); + assert_eq!(42, TagType::Custom(42)); + assert_eq!(TagType::Custom(42), 42); + } + + /// Tests the construction of [`TagTypeId`] from primitive `u32` values. + #[test] + #[allow(non_snake_case)] + fn test_TagTypeId() { + assert_eq!(size_of::(), size_of::()); + assert_eq!(align_of::(), align_of::()); + + for i in 0..50_u32 { + let val: TagTypeId = i.into(); + let val2: TagType = val.into(); + assert_eq!(val, val2); + } + + let tag_custom: u32 = 0x1337; + let tag_custom: TagTypeId = tag_custom.into(); + let tag_custom: TagType = tag_custom.into(); + matches!(tag_custom, TagType::Custom(0x1337)); + } + + /// Tests the construction of [`TagTypeId`] from primitive `u32` values for + /// specified and custom tags. + #[test] + #[allow(non_snake_case)] + fn test_from_and_to_tag_type_id() { + for i in 0..1_000 { + let tag_type_id = TagTypeId::new(i); + let tag_type_from_id = TagType::from(tag_type_id); + let tag_type_from_u16 = TagType::from(i); + assert_eq!(tag_type_from_id, tag_type_from_u16) + } } } diff --git a/multiboot2/src/vbe_info.rs b/multiboot2/src/vbe_info.rs index d441137a..64f53396 100644 --- a/multiboot2/src/vbe_info.rs +++ b/multiboot2/src/vbe_info.rs @@ -1,4 +1,4 @@ -use crate::TagType; +use crate::TagTypeId; use core::fmt; /// This tag contains VBE metadata, VBE controller information returned by the @@ -6,7 +6,7 @@ use core::fmt; #[derive(Debug, Copy, Clone)] #[repr(C, packed)] pub struct VBEInfoTag { - typ: TagType, + typ: TagTypeId, length: u32, /// Indicates current video mode in the format specified in VBE 3.0.