//! Multiboot v1 library //! //! This crate is partitially modified from `https://github.com/gz/rust-multiboot` && asterinas //! //! The main structs to interact with are [`Multiboot`] for the Multiboot information //! passed from the bootloader to the kernel at runtime and [`Header`] for the static //! information passed from the kernel to the bootloader in the kernel image. //! //! //! # Additional documentation //! * https://www.gnu.org/software/grub/manual/multiboot/multiboot.html //! * http://git.savannah.gnu.org/cgit/grub.git/tree/doc/multiboot.texi?h=multiboot //! //! [`Multiboot`]: information/struct.Multiboot.html //! [`Header`]: header/struct.Header.html #![no_std] use core::ffi::CStr; pub const MAGIC: u32 = 0x2BADB002; /// The ‘boot_device’ field. /// /// Partition numbers always start from zero. Unused partition /// bytes must be set to 0xFF. For example, if the disk is partitioned /// using a simple one-level DOS partitioning scheme, then /// ‘part’ contains the DOS partition number, and ‘part2’ and ‘part3’ /// are both 0xFF. As another example, if a disk is partitioned first into /// DOS partitions, and then one of those DOS partitions is subdivided /// into several BSD partitions using BSD's disklabel strategy, then ‘part1’ /// contains the DOS partition number, ‘part2’ contains the BSD sub-partition /// within that DOS partition, and ‘part3’ is 0xFF. /// #[derive(Debug, Clone, Copy)] #[repr(C)] pub struct BootDevice { /// Contains the bios drive number as understood by /// the bios INT 0x13 low-level disk interface: e.g. 0x00 for the /// first floppy disk or 0x80 for the first hard disk. pub drive: u8, /// Specifies the top-level partition number. pub partition1: u8, /// Specifies a sub-partition in the top-level partition pub partition2: u8, /// Specifies a sub-partition in the 2nd-level partition pub partition3: u8, } impl BootDevice { /// Is partition1 a valid partition? pub fn partition1_is_valid(&self) -> bool { self.partition1 != 0xff } /// Is partition2 a valid partition? pub fn partition2_is_valid(&self) -> bool { self.partition2 != 0xff } /// Is partition3 a valid partition? pub fn partition3_is_valid(&self) -> bool { self.partition3 != 0xff } } impl Default for BootDevice { fn default() -> Self { Self { drive: 0xff, partition1: 0xff, partition2: 0xff, partition3: 0xff, } } } /// Representation of Multiboot Information according to specification. /// /// Reference: https://www.gnu.org/software/grub/manual/multiboot/multiboot.html#Boot-information-format /// ///```text /// +-------------------+ /// 0 | flags | (required) /// +-------------------+ /// 4 | mem_lower | (present if flags[0] is set) /// 8 | mem_upper | (present if flags[0] is set) /// +-------------------+ /// 12 | boot_device | (present if flags[1] is set) /// +-------------------+ /// 16 | cmdline | (present if flags[2] is set) /// +-------------------+ /// 20 | mods_count | (present if flags[3] is set) /// 24 | mods_addr | (present if flags[3] is set) /// +-------------------+ /// 28 - 40 | syms | (present if flags[4] or /// | | flags[5] is set) /// +-------------------+ /// 44 | mmap_length | (present if flags[6] is set) /// 48 | mmap_addr | (present if flags[6] is set) /// +-------------------+ /// 52 | drives_length | (present if flags[7] is set) /// 56 | drives_addr | (present if flags[7] is set) /// +-------------------+ /// 60 | config_table | (present if flags[8] is set) /// +-------------------+ /// 64 | boot_loader_name | (present if flags[9] is set) /// +-------------------+ /// 68 | apm_table | (present if flags[10] is set) /// +-------------------+ /// 72 | vbe_control_info | (present if flags[11] is set) /// 76 | vbe_mode_info | /// 80 | vbe_mode | /// 82 | vbe_interface_seg | /// 84 | vbe_interface_off | /// 86 | vbe_interface_len | /// +-------------------+ /// 88 | framebuffer_addr | (present if flags[12] is set) /// 96 | framebuffer_pitch | /// 100 | framebuffer_width | /// 104 | framebuffer_height| /// 108 | framebuffer_bpp | /// 109 | framebuffer_type | /// 110-115 | color_info | /// +-------------------+ ///``` /// #[allow(dead_code)] #[derive(Debug, Copy, Clone)] #[repr(C, packed)] pub struct MultibootInfo { /// Indicate whether the below field exists. flags: u32, /// Physical memory low. mem_lower: u32, /// Physical memory high. mem_upper: u32, /// Indicates which BIOS disk device the boot loader loaded the OS image from. boot_device: BootDevice, /// Command line passed to kernel. cmdline: u32, /// Modules count. pub mods_count: u32, /// The start address of modules list, each module structure format: /// ```text /// +-------------------+ /// 0 | mod_start | /// 4 | mod_end | /// +-------------------+ /// 8 | string | /// +-------------------+ /// 12 | reserved (0) | /// +-------------------+ /// ``` mods_paddr: u32, /// If flags[4] = 1, then the field starting at byte 28 are valid: /// ```text /// +-------------------+ /// 28 | tabsize | /// 32 | strsize | /// 36 | addr | /// 40 | reserved (0) | /// +-------------------+ /// ``` /// These indicate where the symbol table from kernel image can be found. /// /// If flags[5] = 1, then the field starting at byte 28 are valid: /// ```text /// +-------------------+ /// 28 | num | /// 32 | size | /// 36 | addr | /// 40 | shndx | /// +-------------------+ /// ``` /// These indicate where the section header table from an ELF kernel is, /// the size of each entry, number of entries, and the string table used as the index of names. symbols: [u8; 16], memory_map_len: u32, memory_map_paddr: u32, drives_length: u32, drives_addr: u32, config_table: u32, /// bootloader name paddr pub boot_loader_name: u32, apm_table: u32, vbe_table: VbeInfo, pub framebuffer_table: FramebufferTable, } impl MultibootInfo { /// If true, then the `mem_upper` and `mem_lower` fields are valid. pub const FLAG_MEMORY_BOUNDS: u32 = 1 << 0; /// If true, then the `boot_device` field is valid. pub const FLAG_BOOT_DEVICE: u32 = 1 << 1; /// If true, then the `cmdline` field is valid. pub const FLAG_CMDLINE: u32 = 1 << 2; /// If true, then the `mods_count` and `mods_addr` fields are valid. pub const FLAG_MODULES: u32 = 1 << 3; /// If true, then the `symbols` field is valid. pub const FLAG_SYMBOLS: u32 = 1 << 4; pub unsafe fn memory_map(&self, ops: &'static dyn MultibootOps) -> MemoryEntryIter { let mmap_addr = ops.phys_2_virt(self.memory_map_paddr as usize); let mmap_len = self.memory_map_len as usize; MemoryEntryIter { cur_ptr: mmap_addr, region_end_vaddr: mmap_addr + mmap_len, } } pub unsafe fn modules(&self, ops: &'static dyn MultibootOps) -> Option { if !self.has_modules() { return None; } let mods_addr = ops.phys_2_virt(self.mods_paddr as usize); let end = mods_addr + (self.mods_count as usize) * core::mem::size_of::(); Some(ModulesIter { cur_ptr: mods_addr, region_end_vaddr: end, }) } pub unsafe fn cmdline(&self, ops: &'static dyn MultibootOps) -> Option<&str> { if !self.has_cmdline() { return None; } let cmdline_vaddr = ops.phys_2_virt(self.cmdline as usize); let cstr = CStr::from_ptr(cmdline_vaddr as *const i8); cstr.to_str().ok() } #[inline] pub fn has_memory_bounds(&self) -> bool { self.flags & Self::FLAG_MEMORY_BOUNDS != 0 } #[inline] pub fn has_boot_device(&self) -> bool { self.flags & Self::FLAG_BOOT_DEVICE != 0 } #[inline] pub fn has_cmdline(&self) -> bool { self.flags & Self::FLAG_CMDLINE != 0 } #[inline] pub fn has_modules(&self) -> bool { self.flags & Self::FLAG_MODULES != 0 } #[inline] pub fn has_symbols(&self) -> bool { self.flags & Self::FLAG_SYMBOLS != 0 } } pub trait MultibootOps { fn phys_2_virt(&self, paddr: usize) -> usize; } #[derive(Debug, Copy, Clone)] #[repr(C, packed)] pub struct VbeInfo { pub control_info: u32, pub mode_info: u32, pub mode: u16, pub interface_seg: u16, pub interface_off: u16, pub interface_len: u16, } #[derive(Debug, Copy, Clone)] #[repr(C, packed)] pub struct FramebufferTable { pub paddr: u64, pub pitch: u32, pub width: u32, pub height: u32, pub bpp: u8, pub typ: u8, color_info: ColorInfo, } impl FramebufferTable { /// Get the color info from this table. pub fn color_info(&self) -> Option { unsafe { match self.typ { 0 => Some(ColorInfoType::Palette(self.color_info.palette)), 1 => Some(ColorInfoType::Rgb(self.color_info.rgb)), 2 => Some(ColorInfoType::Text), _ => None, } } } } /// Safe wrapper for `ColorInfo` #[derive(Debug)] pub enum ColorInfoType { Palette(ColorInfoPalette), Rgb(ColorInfoRgb), Text, } /// Multiboot format for the frambuffer color info /// /// According to the spec, if type == 0, it's indexed color and /// /// +----------------------------------+ /// 110 | framebuffer_palette_addr | /// 114 | framebuffer_palette_num_colors | /// +----------------------------------+ /// /// The address points to an array of `ColorDescriptor`s. /// If type == 1, it's RGB and /// /// +----------------------------------+ ///110 | framebuffer_red_field_position | ///111 | framebuffer_red_mask_size | ///112 | framebuffer_green_field_position | ///113 | framebuffer_green_mask_size | ///114 | framebuffer_blue_field_position | ///115 | framebuffer_blue_mask_size | /// +----------------------------------+ /// /// (If type == 2, it's just text.) #[repr(C)] #[derive(Clone, Copy)] union ColorInfo { palette: ColorInfoPalette, rgb: ColorInfoRgb, _union_align: [u32; 2usize], } impl core::fmt::Debug for ColorInfo { fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { unsafe { f.debug_struct("ColorInfo") .field("palette", &self.palette) .field("rgb", &self.rgb) .finish() } } } // default type is 0, so indexed color impl Default for ColorInfo { fn default() -> Self { Self { palette: ColorInfoPalette { palette_addr: 0, palette_num_colors: 0, }, } } } /// Information for indexed color mode #[repr(C)] #[derive(Debug, Clone, Copy)] pub struct ColorInfoPalette { palette_addr: u32, palette_num_colors: u16, } /// Information for direct RGB color mode #[repr(C)] #[derive(Debug, Clone, Copy)] pub struct ColorInfoRgb { pub red_field_position: u8, pub red_mask_size: u8, pub green_field_position: u8, pub green_mask_size: u8, pub blue_field_position: u8, pub blue_mask_size: u8, } /// Types that define if the memory is usable or not. #[derive(Debug, PartialEq, Eq)] pub enum MemoryType { /// memory, available to OS Available = 1, /// reserved, not available (rom, mem map dev) Reserved = 2, /// ACPI Reclaim Memory ACPI = 3, /// ACPI NVS Memory NVS = 4, /// defective RAM modules Defect = 5, } /// A memory entry in the memory map header info region. /// /// The memory layout of the entry structure doesn't fit in any scheme /// provided by Rust: /// /// ```text /// +-------------------+ <- start of the struct pointer /// -4 | size | /// +-------------------+ /// 0 | base_addr | /// 8 | length | /// 16 | type | /// +-------------------+ /// ``` /// /// The start of a entry is not 64-bit aligned. Although the boot /// protocol may provide the `mmap_addr` 64-bit aligned when added with /// 4, it is not guaranteed. So we need to use pointer arithmetic to /// access the fields. pub struct MemoryEntry { ptr: usize, } impl MemoryEntry { pub fn size(&self) -> u32 { // SAFETY: the entry can only be contructed from a valid address. unsafe { (self.ptr as *const u32).read_unaligned() } } pub fn base_addr(&self) -> u64 { // SAFETY: the entry can only be contructed from a valid address. unsafe { ((self.ptr + 4) as *const u64).read_unaligned() } } pub fn length(&self) -> u64 { // SAFETY: the entry can only be contructed from a valid address. unsafe { ((self.ptr + 12) as *const u64).read_unaligned() } } pub fn memory_type(&self) -> MemoryType { let typ_val = unsafe { ((self.ptr + 20) as *const u8).read_unaligned() }; // The meaning of the values are however documented clearly by the manual. match typ_val { 1 => MemoryType::Available, 2 => MemoryType::Reserved, 3 => MemoryType::ACPI, 4 => MemoryType::NVS, 5 => MemoryType::Defect, _ => MemoryType::Reserved, } } } /// A memory entry iterator in the memory map header info region. #[derive(Debug, Copy, Clone)] pub struct MemoryEntryIter { cur_ptr: usize, region_end_vaddr: usize, } impl Iterator for MemoryEntryIter { type Item = MemoryEntry; fn next(&mut self) -> Option { if self.cur_ptr >= self.region_end_vaddr { return None; } let entry = MemoryEntry { ptr: self.cur_ptr }; self.cur_ptr += entry.size() as usize + 4; Some(entry) } } /// Multiboot format to information about module #[repr(C)] pub struct MBModule { /// Start address of module in memory. start: u32, /// End address of module in memory. end: u32, /// The `string` field provides an arbitrary string to be associated /// with that particular boot module. /// /// It is a zero-terminated ASCII string, just like the kernel command line. /// The `string` field may be 0 if there is no string associated with the module. /// Typically the string might be a command line (e.g. if the operating system /// treats boot modules as executable programs), or a pathname /// (e.g. if the operating system treats boot modules as files in a file system), /// but its exact use is specific to the operating system. string: u32, /// Must be zero. reserved: u32, } impl MBModule { #[inline] pub fn start(&self) -> u32 { self.start } #[inline] pub fn end(&self) -> u32 { self.end } pub fn string(&self) -> u32 { self.string } pub fn reserved(&self) -> u32 { self.reserved } } impl core::fmt::Debug for MBModule { fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result { write!( f, "MBModule {{ start: {}, end: {}, string: {}, reserved: {} }}", self.start, self.end, self.string, self.reserved ) } } #[derive(Debug, Copy, Clone)] pub struct ModulesIter { cur_ptr: usize, region_end_vaddr: usize, } impl Iterator for ModulesIter { type Item = MBModule; fn next(&mut self) -> Option { if self.cur_ptr >= self.region_end_vaddr { return None; } let mb_module = unsafe { (self.cur_ptr as *const MBModule).read() }; self.cur_ptr += core::mem::size_of::(); Some(mb_module) } }