1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
// Copyright 2024 The ChromiumOS Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

//! Multiboot specification 0.6.96 definitions
//!
//! <https://www.gnu.org/software/grub/manual/multiboot/multiboot.html>

use zerocopy::FromBytes;
use zerocopy::Immutable;
use zerocopy::IntoBytes;
use zerocopy::KnownLayout;

/// Magic value stored in EAX to indicate bootloader is Multiboot compliant.
pub const MULTIBOOT_BOOTLOADER_MAGIC: u32 = 0x2BADB002;

#[derive(Debug, Default, FromBytes, Immutable, IntoBytes, KnownLayout)]
#[repr(C, packed)]
pub struct MultibootInfo {
    pub flags: u32,

    pub mem_lower: u32,
    pub mem_upper: u32,

    pub boot_device: u32,

    pub cmdline: u32,

    pub mods_count: u32,
    pub mods_addr: u32,

    // TODO: add union for ELF + a.out symbols if needed
    pub syms: [u32; 4],

    pub mmap_length: u32,
    pub mmap_addr: u32,

    pub drives_length: u32,
    pub drives_addr: u32,

    pub config_table: u32,

    pub boot_loader_name: u32,

    pub apm_table: u32,

    pub vbe_control_info: u32,
    pub vbe_mode_info: u32,
    pub vbe_mode: u16,
    pub vbe_interface_seg: u16,
    pub vbe_interface_off: u16,
    pub vbe_interface_len: u16,

    pub framebuffer_addr: u64,
    pub framebuffer_pitch: u32,
    pub framebuffer_width: u32,
    pub framebuffer_height: u32,
    pub framebuffer_bpp: u8,
    pub framebuffer_type: u8,

    // TODO: add union for palette + RGB color info if needed
    pub color_info: [u8; 6],
}

impl MultibootInfo {
    pub const F_MEM: u32 = 1 << 0;
    pub const F_BOOT_DEVICE: u32 = 1 << 1;
    pub const F_CMDLINE: u32 = 1 << 2;
    pub const F_MODS: u32 = 1 << 3;
    pub const F_SYMS_AOUT: u32 = 1 << 4;
    pub const F_SYMS_ELF: u32 = 1 << 5;
    pub const F_MMAP: u32 = 1 << 6;
    pub const F_DRIVES: u32 = 1 << 7;
    pub const F_CONFIG_TABLE: u32 = 1 << 8;
    pub const F_BOOT_LOADER_NAME: u32 = 1 << 9;
    pub const F_APM_TABLE: u32 = 1 << 10;
    pub const F_VBE: u32 = 1 << 11;
    pub const F_FRAMEBUFFER: u32 = 1 << 12;
}

#[derive(Debug, Default, FromBytes, Immutable, IntoBytes, KnownLayout)]
#[repr(C, packed)]
pub struct MultibootMmapEntry {
    pub size: u32,
    pub base_addr: u64,
    pub length: u64,
    pub type_: u32,
}

#[cfg(test)]
mod tests {
    use std::mem::offset_of;
    use std::mem::size_of;

    use super::*;

    #[test]
    fn test_multiboot_info_offsets() {
        // Validate that multiboot_info field offsets match the spec.
        assert_eq!(0, offset_of!(MultibootInfo, flags));
        assert_eq!(4, offset_of!(MultibootInfo, mem_lower));
        assert_eq!(8, offset_of!(MultibootInfo, mem_upper));
        assert_eq!(8, offset_of!(MultibootInfo, mem_upper));
        assert_eq!(12, offset_of!(MultibootInfo, boot_device));
        assert_eq!(16, offset_of!(MultibootInfo, cmdline));
        assert_eq!(20, offset_of!(MultibootInfo, mods_count));
        assert_eq!(24, offset_of!(MultibootInfo, mods_addr));
        assert_eq!(28, offset_of!(MultibootInfo, syms));
        assert_eq!(44, offset_of!(MultibootInfo, mmap_length));
        assert_eq!(48, offset_of!(MultibootInfo, mmap_addr));
        assert_eq!(52, offset_of!(MultibootInfo, drives_length));
        assert_eq!(56, offset_of!(MultibootInfo, drives_addr));
        assert_eq!(60, offset_of!(MultibootInfo, config_table));
        assert_eq!(64, offset_of!(MultibootInfo, boot_loader_name));
        assert_eq!(68, offset_of!(MultibootInfo, apm_table));
        assert_eq!(72, offset_of!(MultibootInfo, vbe_control_info));
        assert_eq!(76, offset_of!(MultibootInfo, vbe_mode_info));
        assert_eq!(80, offset_of!(MultibootInfo, vbe_mode));
        assert_eq!(82, offset_of!(MultibootInfo, vbe_interface_seg));
        assert_eq!(84, offset_of!(MultibootInfo, vbe_interface_off));
        assert_eq!(86, offset_of!(MultibootInfo, vbe_interface_len));
        assert_eq!(88, offset_of!(MultibootInfo, framebuffer_addr));
        assert_eq!(96, offset_of!(MultibootInfo, framebuffer_pitch));
        assert_eq!(100, offset_of!(MultibootInfo, framebuffer_width));
        assert_eq!(104, offset_of!(MultibootInfo, framebuffer_height));
        assert_eq!(108, offset_of!(MultibootInfo, framebuffer_bpp));
        assert_eq!(109, offset_of!(MultibootInfo, framebuffer_type));
        assert_eq!(110, offset_of!(MultibootInfo, color_info));

        assert_eq!(size_of::<MultibootInfo>(), 116);
    }

    #[test]
    fn test_multiboot_mmap_entry_offsets() {
        // The spec defines the mmap entry structure in a confusing way (`size` at offset -4 and
        // `base_addr` at offset 0), but this does not match how both bootloaders and kernels have
        // implemented Multiboot (`size` at offset 0), so this does not exactly match the table in
        // the spec.
        assert_eq!(0, offset_of!(MultibootMmapEntry, size));
        assert_eq!(4, offset_of!(MultibootMmapEntry, base_addr));
        assert_eq!(12, offset_of!(MultibootMmapEntry, length));
        assert_eq!(20, offset_of!(MultibootMmapEntry, type_));

        assert_eq!(size_of::<MultibootMmapEntry>(), 24); // 20-byte e820 data + 4 bytes for size
    }
}