Эх сурвалжийг харах

Merge pull request #122 from rust-osdev/multiboot2-v15-dev

multiboot2-v0.15
Philipp Schuster 2 жил өмнө
parent
commit
725ea5c5db

+ 31 - 13
.github/workflows/_build-rust.yml

@@ -17,24 +17,43 @@ on:
         required: false
         default: x86_64-unknown-linux-gnu
         description: Rust target for the build step. Clippy and tests are still executed with the default target.
+      features:
+        type: string
+        required: false
+        # Make sure we always an empty string to "--features <FEATURES>"
+        default: '""'
+        description: Comma-separated String with additional Rust features relevant for a certain job.
       do-style-check:
         type: boolean
         required: false
         default: true
-        description: Whether style checks should be done.
+        description: Perform code and doc style checks.
       do-test:
         type: boolean
         required: false
         default: true
-        description: Whether tests should be executed.
+        description: Execute tests.
 
 jobs:
-  build:
+  check_rust:
     runs-on: ubuntu-latest
     steps:
       - name: Check out
         uses: actions/checkout@v3
-      - name: Install Rust
+      - name: Set up rustup cache
+        uses: actions/cache@v3
+        continue-on-error: false
+        with:
+          path: |
+            ~/.rustup/downloads
+            ~/.rustup/toolchains
+          # key: ${{ runner.os }}-rustup-${{ inputs.rust-version }}-${{ inputs.rust-target }}-${{ hashFiles('**/rustup-toolchain.toml') }}
+          key: ${{ runner.os }}-rustup-${{ inputs.rust-version }}-${{ inputs.rust-target }}
+      # The effect of this is must smaller than the cache for Cargo. However, it
+      # still saves a few seconds. Note that many CI runs within a week may
+      # quickly bring you close to the 10GB limit:
+      # https://docs.github.com/en/actions/using-workflows/caching-dependencies-to-speed-up-workflows#managing-caches
+      - name: Install Rust Toolchain via Rustup
         uses: actions-rs/toolchain@v1
         with:
           profile: minimal
@@ -42,7 +61,7 @@ jobs:
           override: true
           components: clippy, rustfmt
           target: ${{ inputs.rust-target }}
-      - name: Set up cargo cache
+      - name: Set up Cargo cache
         uses: actions/cache@v3
         continue-on-error: false
         with:
@@ -54,23 +73,22 @@ jobs:
             target/
           # We do not have a Cargo.lock here, so I hash Cargo.toml
           key: ${{ runner.os }}-rust-${{ inputs.rust-version }}-cargo-${{ hashFiles('**/Cargo.toml') }}
-          restore-keys: ${{ runner.os }}-cargo-${{ inputs.rust-version }}
       - run: cargo version
+      - name: Build (library)
+        run: cargo build --target ${{ inputs.rust-target }} --features ${{ inputs.features }}
+      - name: Build (all targets)
+        run: cargo build --all-targets --features ${{ inputs.features }}
       - name: Code Formatting
         if: ${{ inputs.do-style-check }}
         run: cargo fmt --all -- --check
-      - name: Build (library)
-        run: cargo build --target ${{ inputs.rust-target }}
-      - name: Build (all targets)
-        run: cargo build --all-targets
       - name: Code Style and Doc Style
         if: ${{ inputs.do-style-check }}
         run: |
-          cargo doc --document-private-items
-          cargo clippy --all-targets
+          cargo doc --document-private-items --features ${{ inputs.features }}
+          cargo clippy --all-targets --features ${{ inputs.features }}
       - name: Unit Test
         if: ${{ inputs.do-test }}
         run: |
           curl -LsSf https://get.nexte.st/latest/linux | tar zxf -
           chmod u+x cargo-nextest
-          ./cargo-nextest nextest run
+          ./cargo-nextest nextest run --features ${{ inputs.features }}

+ 6 - 0
.github/workflows/rust.yml

@@ -15,6 +15,7 @@ env:
   CARGO_TERM_COLOR: always
 
 jobs:
+  ### Regular Build  #########################
   build_msrv:
     name: build (msrv)
     uses: ./.github/workflows/_build-rust.yml
@@ -35,7 +36,9 @@ jobs:
     with:
       rust-version: nightly
       do-style-check: false
+      features: unstable
 
+  ### no-std Build   #########################
   build_nostd_msrv:
     name: build no_std (msrv)
     needs: build_msrv
@@ -62,7 +65,9 @@ jobs:
       rust-version: nightly
       do-style-check: false
       rust-target: thumbv7em-none-eabihf
+      features: unstable
 
+  ### Style Checks + Doc #####################
   style_msrv:
     name: style (msrv)
     needs: build_msrv
@@ -89,3 +94,4 @@ jobs:
       rust-version: nightly
       do-style-check: true
       do-test: false
+      features: unstable

+ 1 - 1
multiboot2-header/Cargo.toml

@@ -9,7 +9,7 @@ authors = [
     "Philipp Schuster <phip1611@gmail.com>"
 ]
 license = "MIT/Apache-2.0"
-edition = "2018"
+edition = "2021"
 categories = [
     "no-std",
     "parsing",

+ 3 - 0
multiboot2-header/Changelog.md

@@ -1,5 +1,8 @@
 # CHANGELOG for crate `multiboot2-header`
 
+## Unreleased
+- MSRV is 1.56.1
+
 ## v0.2.0 (2022-05-03)
 - **BREAKING** renamed `EntryHeaderTag` to `EntryAddressHeaderTag`
 - **BREAKING** some paths changed from `multiboot2_header::header` to `multiboot2_header::builder`

+ 1 - 1
multiboot2-header/README.md

@@ -68,7 +68,7 @@ You may need a special linker script to place this in a LOAD segment with a file
 See specification.
 
 ## MSRV
-The MSRV is 1.52.1 stable.
+The MSRV is 1.56.1 stable.
 
 ## License & Contribution
 

+ 2 - 2
multiboot2-header/src/builder/information_request.rs

@@ -21,7 +21,7 @@ pub struct InformationRequestHeaderTagBuilder {
 #[cfg(feature = "builder")]
 impl InformationRequestHeaderTagBuilder {
     /// New builder.
-    #[allow(clippy::missing_const_for_fn)] // TODO remove once MSRV is higher than 1.52.1
+    #[allow(clippy::missing_const_for_fn)] // TODO remove once MSRV is higher than 1.65.0
     pub fn new(flag: HeaderTagFlag) -> Self {
         Self {
             irs: BTreeSet::new(),
@@ -31,7 +31,7 @@ impl InformationRequestHeaderTagBuilder {
 
     /// Returns the expected length of the information request tag,
     /// when the `build`-method gets called.
-    #[allow(clippy::missing_const_for_fn)] // TODO remove once MSRV is higher than 1.52.1
+    #[allow(clippy::missing_const_for_fn)] // TODO remove once MSRV is higher than 1.65.0
     pub fn expected_len(&self) -> usize {
         let basic_header_size = size_of::<InformationRequestHeaderTag<0>>();
         let req_tags_size = self.irs.len() * size_of::<MbiTagType>();

+ 1 - 1
multiboot2-header/src/lib.rs

@@ -31,7 +31,7 @@
 //! ```
 //!
 //! ## MSRV
-//! The MSRV is 1.52.1 stable.
+//! The MSRV is 1.56.1 stable.
 
 #![no_std]
 #![deny(rustdoc::all)]

+ 1 - 1
multiboot2/Cargo.toml

@@ -14,7 +14,7 @@ authors = [
     "Philipp Schuster <phip1611@gmail.com>"
 ]
 license = "MIT/Apache-2.0"
-edition = "2018"
+edition = "2021"
 categories = [
     "no-std",
     "parsing",

+ 20 - 0
multiboot2/Changelog.md

@@ -1,5 +1,25 @@
 # CHANGELOG for crate `multiboot2`
 
+## 0.15.0 (2023-03-XX)
+- MSRV is 1.56.1
+- **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`
+- `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
 - `MbiLoadError` now implements `Display`

+ 1 - 1
multiboot2/README.md

@@ -31,7 +31,7 @@ other fields  | variable
 All tags and the mbi itself are 8-byte aligned. The last tag must be the _end tag_, which is a tag of type `0` and size `8`.
 
 ## MSRV
-The MSRV is 1.52.1 stable.
+The MSRV is 1.56.1 stable.
 
 ## License & Contribution
 

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

+ 38 - 11
multiboot2/src/framebuffer.rs

@@ -1,6 +1,7 @@
 use crate::tag_type::Tag;
 use crate::Reader;
 use core::slice;
+use derive_more::Display;
 
 /// The VBE Framebuffer information Tag.
 #[derive(Debug, PartialEq, Eq)]
@@ -28,6 +29,17 @@ pub struct FramebufferTag<'a> {
     pub buffer_type: FramebufferType<'a>,
 }
 
+/// Helper struct for [`FramebufferType`].
+#[derive(Debug, PartialEq, Eq)]
+#[repr(u8)]
+#[allow(clippy::upper_case_acronyms)]
+pub enum FramebufferTypeId {
+    Indexed = 0,
+    RGB = 1,
+    Text = 2,
+    // spec says: there may be more variants in the future
+}
+
 /// The type of framebuffer.
 #[derive(Debug, PartialEq, Eq)]
 pub enum FramebufferType<'a> {
@@ -79,7 +91,16 @@ pub struct FramebufferColor {
     pub blue: u8,
 }
 
-pub fn framebuffer_tag(tag: &Tag) -> FramebufferTag {
+/// Error when an unknown [`FramebufferTypeId`] is found.
+#[derive(Debug, Copy, Clone, Display, PartialEq, Eq)]
+#[display(fmt = "Unknown framebuffer type _0")]
+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<FramebufferTag, UnknownFramebufferType> {
     let mut reader = Reader::new(tag as *const Tag);
     reader.skip(8);
     let address = reader.read_u64();
@@ -88,20 +109,27 @@ pub fn framebuffer_tag(tag: &Tag) -> FramebufferTag {
     let height = reader.read_u32();
     let bpp = reader.read_u8();
     let type_no = reader.read_u8();
-    reader.skip(2); // 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.
-    let buffer_type = match type_no {
-        0 => {
+    // 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 &'static [FramebufferColor];
+            } as &[FramebufferColor];
             FramebufferType::Indexed { palette }
         }
-        1 => {
+        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();
@@ -123,16 +151,15 @@ pub fn framebuffer_tag(tag: &Tag) -> FramebufferTag {
                 },
             }
         }
-        2 => FramebufferType::Text,
-        _ => panic!("Unknown framebuffer type: {}", type_no),
+        FramebufferTypeId::Text => FramebufferType::Text,
     };
 
-    FramebufferTag {
+    Ok(FramebufferTag {
         address,
         pitch,
         width,
         height,
         bpp,
         buffer_type,
-    }
+    })
 }

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

+ 203 - 45
multiboot2/src/lib.rs

@@ -30,7 +30,7 @@
 //! ```
 //!
 //! ## MSRV
-//! The MSRV is 1.52.1 stable.
+//! The MSRV is 1.56.1 stable.
 
 // this crate can use std in tests only
 #[cfg_attr(test, macro_use)]
@@ -40,6 +40,7 @@ extern crate std;
 use core::fmt;
 use derive_more::Display;
 
+use crate::framebuffer::UnknownFramebufferType;
 pub use boot_loader_name::BootLoaderNameTag;
 pub use command_line::CommandLineTag;
 pub use efi::{EFIImageHandle32, EFIImageHandle64, EFISdt32, EFISdt64};
@@ -54,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,
@@ -217,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.
@@ -234,88 +234,124 @@ 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.
-    pub fn framebuffer_tag(&self) -> Option<FramebufferTag> {
-        self.get_tag(TagType::Framebuffer)
+    /// 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::<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.
+    /// Search for the EFI Memory map tag, if the boot services were exited.
+    /// Otherwise, if the [`TagType::EfiBs`] tag is present, this returns `None`
+    /// as it is strictly recommended to get the memory map from the `uefi`
+    /// services.
     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<&'static VBEInfoTag> {
-        self.get_tag(TagType::Vbe)
-            .map(|tag| unsafe { &*(tag as *const Tag as *const VBEInfoTag) })
+    pub fn vbe_info_tag(&self) -> Option<&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 {
@@ -325,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
     }
 }
 
@@ -443,6 +479,7 @@ impl Reader {
 #[cfg(test)]
 mod tests {
     use super::*;
+    use std::{mem, slice};
 
     #[test]
     fn no_tags() {
@@ -588,7 +625,7 @@ mod tests {
         use framebuffer::{FramebufferField, FramebufferTag, FramebufferType};
         assert_eq!(
             bi.framebuffer_tag(),
-            Some(FramebufferTag {
+            Some(Ok(FramebufferTag {
                 address: 4244635648,
                 pitch: 5120,
                 width: 1280,
@@ -608,7 +645,7 @@ mod tests {
                         size: 8
                     }
                 }
-            })
+            }))
         )
     }
 
@@ -643,7 +680,10 @@ mod tests {
         assert_eq!(bytes.0.len(), bi.total_size());
         use framebuffer::{FramebufferColor, FramebufferType};
         assert!(bi.framebuffer_tag().is_some());
-        let fbi = bi.framebuffer_tag().unwrap();
+        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);
@@ -1255,7 +1295,10 @@ mod tests {
         );
 
         // Test the Framebuffer tag
-        let fbi = bi.framebuffer_tag().unwrap();
+        let fbi = bi
+            .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);
@@ -1446,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.