Browse Source

Merge pull request #118 from rust-osdev/custom-tags

multiboot2: Allow to get Custom Tags
Philipp Schuster 2 years ago
parent
commit
4b35d585b2

+ 12 - 3
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<Result<FramebufferTag, UnknownFramebufferType>>` instead of
   `Option<FramebufferTag>` 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

+ 3 - 3
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

+ 3 - 3
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

+ 5 - 5
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,
 }

+ 2 - 1
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,

+ 2 - 2
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,
 }

+ 183 - 36
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<ElfSectionsTag> {
-        self.get_tag(TagType::ElfSections)
+        self.get_tag::<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::<MemoryMapTag, _>(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::<BootLoaderNameTag, _>(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::<CommandLineTag, _>(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<Result<FramebufferTag, UnknownFramebufferType>> {
-        self.get_tag(TagType::Framebuffer)
+        self.get_tag::<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::<EFISdt32, _>(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::<EFISdt64, _>(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::<RsdpV1Tag, _>(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::<RsdpV2Tag, _>(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::<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::<EFIMemoryMapTag, _>(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::<EFIImageHandle32, _>(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::<EFIImageHandle64, _>(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::<ImageLoadPhysAddr, _>(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::<VBEInfoTag, _>(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::<CustomTag, _>(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<Tag, TagType: Into<TagTypeId>>(&self, typ: TagType) -> Option<&Tag> {
+        let typ = typ.into();
+        self.tags()
+            .find(|tag| tag.typ == typ)
+            .map(|tag| tag.cast_tag::<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: core::error::Error>(_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::<CustomTag, _>(CUSTOM_TAG_ID).unwrap();
+
+        // strlen without null byte
+        let strlen = tag.size as usize - mem::size_of::<CommandLineTag>();
+        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<TagTypeId>`
+    #[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::<CommandLineTag, _>(TagType::Cmdline).unwrap();
+
+        let _tag = bi.get_tag::<CommandLineTag, _>(1).unwrap();
+
+        let _tag = bi.get_tag::<CommandLineTag, _>(TagTypeId::new(1)).unwrap();
+    }
 }

+ 3 - 3
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,

+ 4 - 3
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(),

+ 3 - 3
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,

+ 330 - 98
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<u32> 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<u32> for TagTypeId {
+        fn from(value: u32) -> Self {
+            // SAFETY: the type has repr(transparent)
+            unsafe { core::mem::transmute(value) }
+        }
+    }
+
+    impl From<TagTypeId> for u32 {
+        fn from(value: TagTypeId) -> Self {
+            value.0 as _
+        }
+    }
+
+    impl From<u32> 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<TagType> 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<TagType> 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<TagTypeId> for TagType {
+        fn from(value: TagTypeId) -> Self {
+            let value = u32::from(value);
+            TagType::from(value)
+        }
+    }
+
+    impl From<TagType> 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<TagTypeId> 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<TagType> for TagTypeId {
+        fn eq(&self, other: &TagType) -> bool {
+            other.eq(self)
+        }
+    }
+
+    impl PartialEq<u32> for TagTypeId {
+        fn eq(&self, other: &u32) -> bool {
+            let this = u32::from(*self);
+            this == *other
+        }
+    }
+
+    impl PartialEq<TagTypeId> for u32 {
+        fn eq(&self, other: &TagTypeId) -> bool {
+            other.eq(self)
+        }
+    }
+
+    impl PartialEq<u32> for TagType {
+        fn eq(&self, other: &u32) -> bool {
+            let this = u32::from(*self);
+            this == *other
+        }
+    }
+
+    impl PartialEq<TagType> 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::<TagTypeId>(), size_of::<u32>());
+        assert_eq!(align_of::<TagTypeId>(), align_of::<u32>());
+
+        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)
+        }
     }
 }

+ 2 - 2
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.