diff --git a/README.md b/README.md index 1ef72785..30dddb8f 100644 --- a/README.md +++ b/README.md @@ -3,7 +3,7 @@ An experimental Multiboot 2 crate for ELF-64 kernels. It's still incomplete, so please open an issue if you're missing some functionality. Contributions welcome! -It uses the Multiboot 1.6 specification at http://nongnu.askapache.com/grub/phcoder/multiboot.pdf and the ELF 64 specification at http://www.uclibc.org/docs/elf-64-gen.pdf. +It uses the Multiboot 2.0 specification at https://www.gnu.org/software/grub/manual/multiboot2/multiboot.html and the ELF 64 specification at http://www.uclibc.org/docs/elf-64-gen.pdf. Below is the draft for a blog post about this. I don't plan to finish it but maybe it's helpful as documentation. diff --git a/src/lib.rs b/src/lib.rs index b354b23d..ee06fd83 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -30,9 +30,15 @@ pub use elf_sections::{ }; pub use framebuffer::{FramebufferColor, FramebufferField, FramebufferTag, FramebufferType}; use header::{Tag, TagIter}; -pub use memory_map::{MemoryArea, MemoryAreaIter, MemoryAreaType, MemoryMapTag}; +pub use memory_map::{ + EFIMemoryAreaType, EFIMemoryMapTag, EFIMemoryDesc, MemoryArea, MemoryAreaIter, + MemoryAreaType, MemoryMapTag, +}; pub use module::{ModuleIter, ModuleTag}; -pub use rsdp::{RsdpV1Tag, RsdpV2Tag}; +pub use rsdp::{ + EFIImageHandle32, EFIImageHandle64, EFISdt32, EFISdt64, ImageLoadPhysAddr, + RsdpV1Tag, RsdpV2Tag, +}; pub use vbe_info::{ VBECapabilities, VBEControlInfo, VBEDirectColorAttributes, VBEField, VBEInfoTag, VBEMemoryModel, VBEModeAttributes, VBEModeInfo, VBEWindowAttributes, @@ -170,6 +176,18 @@ impl BootInformation { self.get_tag(8).map(|tag| framebuffer::framebuffer_tag(tag)) } + /// Search for the EFI 32-bit SDT tag. + pub fn efi_sdt_32_tag<'a>(&self) -> Option<&'a EFISdt32> { + self.get_tag(11) + .map(|tag| unsafe { &*(tag as *const Tag as *const EFISdt32) }) + } + + /// Search for the EFI 64-bit SDT tag. + pub fn efi_sdt_64_tag<'a>(&self) -> Option<&'a EFISdt64> { + self.get_tag(12) + .map(|tag| unsafe { &*(tag as *const Tag as *const EFISdt64) }) + } + /// Search for the (ACPI 1.0) RSDP tag. pub fn rsdp_v1_tag<'a>(&self) -> Option<&'a RsdpV1Tag> { self.get_tag(14) @@ -182,6 +200,37 @@ impl BootInformation { .map(|tag| unsafe { &*(tag as *const Tag as *const RsdpV2Tag) }) } + /// Search for the EFI Memory map tag. + pub fn efi_memory_map_tag<'a>(&'a self) -> Option<&'a 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(18) { + Some(_tag) => None, + None => { + self.get_tag(17) + .map(|tag| unsafe { &*(tag as *const Tag as *const EFIMemoryMapTag) }) + }, + } + } + + /// Search for the EFI 32-bit image handle pointer. + pub fn efi_32_ih<'a>(&'a self) -> Option<&'a EFIImageHandle32> { + self.get_tag(19) + .map(|tag| unsafe { &*(tag as *const Tag as *const EFIImageHandle32) }) + } + + /// Search for the EFI 64-bit image handle pointer. + pub fn efi_64_ih<'a>(&'a self) -> Option<&'a EFIImageHandle64> { + self.get_tag(20) + .map(|tag| unsafe { &*(tag as *const Tag as *const EFIImageHandle64) }) + } + + /// Search for the Image Load Base Physical Address. + pub fn load_base_addr<'a>(&'a self) -> Option<&'a ImageLoadPhysAddr> { + self.get_tag(21) + .map(|tag| unsafe { &*(tag as *const Tag as *const ImageLoadPhysAddr) }) + } + /// Search for the VBE information tag. pub fn vbe_info_tag(&self) -> Option<&'static VBEInfoTag> { self.get_tag(7) @@ -1174,4 +1223,71 @@ mod tests { assert_eq!(ElfSectionType::StringTable, s1.section_type()); assert!(s.next().is_none()); } + + #[test] + fn efi_memory_map() { + use memory_map::EFIMemoryAreaType; + #[repr(C, align(8))] + struct Bytes([u8; 72]); + // test that the EFI memory map is detected. + let bytes: Bytes = Bytes([ + 72, 0, 0, 0, // size + 0, 0, 0, 0, // reserved + 17, 0, 0, 0, // EFI memory map type + 56, 0, 0, 0, // EFI memory map size + 48, 0, 0, 0, // EFI descriptor size + 1, 0, 0, 0, // EFI descriptor version, don't think this matters. + 7, 0, 0, 0, // Type: EfiConventionalMemory + 0, 0, 0, 0, // Padding + 0, 0, 16, 0,// Physical Address: should be 0x100000 + 0, 0, 0, 0, // Extension of physical address. + 0, 0, 16, 0,// Virtual Address: should be 0x100000 + 0, 0, 0, 0, // Extension of virtual address. + 4, 0, 0, 0, // 4 KiB Pages: 16 KiB + 0, 0, 0, 0, // Extension of pages + 0, 0, 0, 0, // Attributes of this memory range. + 0, 0, 0, 0, // Extension of attributes + 0, 0, 0, 0, // end tag type. + 8, 0, 0, 0, // end tag size. + ]); + let addr = bytes.0.as_ptr() as usize; + let boot_info = unsafe { load(addr) }; + assert_eq!(addr, boot_info.start_address()); + assert_eq!(addr + bytes.0.len(), boot_info.end_address()); + assert_eq!(bytes.0.len(), boot_info.total_size() as usize); + let efi_memory_map = boot_info.efi_memory_map_tag().unwrap(); + let mut efi_mmap_iter = efi_memory_map.memory_areas(); + let desc = efi_mmap_iter.next().unwrap(); + assert_eq!(desc.physical_address(), 0x100000); + assert_eq!(desc.size(), 16384); + assert_eq!(desc.typ(), EFIMemoryAreaType::EfiConventionalMemory); + // test that the EFI memory map is not detected if the boot services + // are not exited. + struct Bytes2([u8; 80]); + let bytes2: Bytes2 = Bytes2([ + 80, 0, 0, 0, // size + 0, 0, 0, 0, // reserved + 17, 0, 0, 0, // EFI memory map type + 56, 0, 0, 0, // EFI memory map size + 48, 0, 0, 0, // EFI descriptor size + 1, 0, 0, 0, // EFI descriptor version, don't think this matters. + 7, 0, 0, 0, // Type: EfiConventionalMemory + 0, 0, 0, 0, // Padding + 0, 0, 16, 0,// Physical Address: should be 0x100000 + 0, 0, 0, 0, // Extension of physical address. + 0, 0, 16, 0,// Virtual Address: should be 0x100000 + 0, 0, 0, 0, // Extension of virtual address. + 4, 0, 0, 0, // 4 KiB Pages: 16 KiB + 0, 0, 0, 0, // Extension of pages + 0, 0, 0, 0, // Attributes of this memory range. + 0, 0, 0, 0, // Extension of attributes + 18, 0, 0, 0, // Tag ExitBootServices not terminated. + 8, 0, 0, 0, // Tag ExitBootServices size. + 0, 0, 0, 0, // end tag type. + 8, 0, 0, 0, // end tag size. + ]); + let boot_info = unsafe { load(bytes2.0.as_ptr() as usize) }; + let efi_mmap = boot_info.efi_memory_map_tag(); + assert!(efi_mmap.is_none()); + } } diff --git a/src/memory_map.rs b/src/memory_map.rs index 0d1a2895..1fc53bbd 100644 --- a/src/memory_map.rs +++ b/src/memory_map.rs @@ -52,7 +52,7 @@ impl MemoryArea { /// The end address of the memory region. pub fn end_address(&self) -> u64 { - (self.base_addr + self.length) + self.base_addr + self.length } /// The size, in bytes, of the memory region. @@ -114,3 +114,169 @@ impl<'a> Iterator for MemoryAreaIter<'a> { } } } + +/// EFI memory map as per EFI specification. +#[derive(Debug)] +#[repr(C)] +pub struct EFIMemoryMapTag { + typ: u32, + size: u32, + desc_size: u32, + desc_version: u32, + first_desc: EFIMemoryDesc, +} + +impl EFIMemoryMapTag { + /// Return an iterator over ALL marked memory areas. + /// + /// This differs from `MemoryMapTag` as for UEFI, the OS needs some non- + /// available memory areas for tables and such. + pub fn memory_areas(&self) -> EFIMemoryAreaIter { + let self_ptr = self as *const EFIMemoryMapTag; + let start_area = (&self.first_desc) as *const EFIMemoryDesc; + EFIMemoryAreaIter { + current_area: start_area as u64, + last_area: (self_ptr as u64 + self.size as u64), + entry_size: self.desc_size, + phantom: PhantomData, + } + } +} + +/// EFI Boot Memory Map Descriptor +#[derive(Debug)] +#[repr(C)] +pub struct EFIMemoryDesc { + typ: u32, + _padding: u32, + phys_addr: u64, + virt_addr: u64, + num_pages: u64, + attr: u64, +} + +/// An enum of possible reported region types. +#[derive(Debug, PartialEq, Eq)] +pub enum EFIMemoryAreaType { + /// Unusable. + EfiReservedMemoryType, + /// Code area of a UEFI application. + EfiLoaderCode, + /// Data area of a UEFI application. + EfiLoaderData, + /// Code area of a UEFI Boot Service Driver. + EfiBootServicesCode, + /// Data area of a UEFI Boot Service Driver. + EfiBootServicesData, + /// Code area of a UEFI Runtime Driver. + /// + /// Must be preserved in working and ACPI S1-S3 states. + EfiRuntimeServicesCode, + /// Data area of a UEFI Runtime Driver. + /// + /// Must be preserved in working and ACPI S1-S3 states. + EfiRuntimeServicesData, + /// Available memory. + EfiConventionalMemory, + /// Memory with errors, treat as unusable. + EfiUnusableMemory, + /// Memory containing the ACPI tables. + /// + /// Must be preserved in working and ACPI S1-S3 states. + EfiACPIReclaimMemory, + /// Memory reserved by firmware. + /// + /// Must be preserved in working and ACPI S1-S3 states. + EfiACPIMemoryNVS, + /// Memory used by firmware for requesting memory mapping of IO. + /// + /// Should not be used by the OS. Use the ACPI tables for memory mapped IO + /// information. + EfiMemoryMappedIO, + /// Memory used to translate memory cycles to IO cycles. + /// + /// Should not be used by the OS. Use the ACPI tables for memory mapped IO + /// information. + EfiMemoryMappedIOPortSpace, + /// Memory used by the processor. + /// + /// Must be preserved in working and ACPI S1-S4 states. Processor defined + /// otherwise. + EfiPalCode, + /// Available memory supporting byte-addressable non-volatility. + EfiPersistentMemory, + /// Unknown region type, treat as unusable. + EfiUnknown, +} + +impl EFIMemoryDesc { + /// The physical address of the memory region. + pub fn physical_address(&self) -> u64 { + self.phys_addr + } + + /// The virtual address of the memory region. + pub fn virtual_address(&self) -> u64 { + self.virt_addr + } + + /// The size in bytes of the memory region. + pub fn size(&self) -> u64 { + // Spec says this is number of 4KiB pages. + self.num_pages * 4096 + } + + /// The type of the memory region. + pub fn typ(&self) -> EFIMemoryAreaType { + match self.typ { + 0 => EFIMemoryAreaType::EfiReservedMemoryType, + 1 => EFIMemoryAreaType::EfiLoaderCode, + 2 => EFIMemoryAreaType::EfiLoaderData, + 3 => EFIMemoryAreaType::EfiBootServicesCode, + 4 => EFIMemoryAreaType::EfiBootServicesData, + 5 => EFIMemoryAreaType::EfiRuntimeServicesCode, + 6 => EFIMemoryAreaType::EfiRuntimeServicesData, + 7 => EFIMemoryAreaType::EfiConventionalMemory, + 8 => EFIMemoryAreaType::EfiUnusableMemory, + 9 => EFIMemoryAreaType::EfiACPIReclaimMemory, + 10 => EFIMemoryAreaType::EfiACPIMemoryNVS, + 11 => EFIMemoryAreaType::EfiMemoryMappedIO, + 12 => EFIMemoryAreaType::EfiMemoryMappedIOPortSpace, + 13 => EFIMemoryAreaType::EfiPalCode, + 14 => EFIMemoryAreaType::EfiPersistentMemory, + _ => EFIMemoryAreaType::EfiUnknown, + } + } +} + +/// EFI ExitBootServices was not called +#[derive(Debug)] +#[repr(C)] +pub struct EFIBootServicesNotExited { + typ: u32, + size: u32, +} + + +/// An iterator over ALL EFI memory areas. +#[derive(Clone, Debug)] +pub struct EFIMemoryAreaIter<'a> { + current_area: u64, + last_area: u64, + entry_size: u32, + phantom: PhantomData<&'a EFIMemoryDesc>, +} + +impl<'a> Iterator for EFIMemoryAreaIter<'a> { + type Item = &'a EFIMemoryDesc; + fn next(&mut self) -> Option<&'a EFIMemoryDesc> { + if self.current_area > self.last_area { + None + } else { + let area = unsafe{&*(self.current_area as *const EFIMemoryDesc)}; + self.current_area = self.current_area + (self.entry_size as u64); + Some(area) + } + } +} + diff --git a/src/rsdp.rs b/src/rsdp.rs index 41218211..8be99c06 100644 --- a/src/rsdp.rs +++ b/src/rsdp.rs @@ -11,6 +11,66 @@ use core::str; const RSDPV1_LENGTH: usize = 20; +/// EFI system table in 32 bit mode +#[derive(Clone, Copy, Debug)] +#[repr(C, packed)] +pub struct EFISdt32 { + typ: u32, + size: u32, + pointer: u32, +} + +impl EFISdt32 { + /// The Physical address of a i386 EFI system table. + pub fn sdt_address(&self) -> usize { + self.pointer as usize + } +} + +/// EFI system table in 64 bit mode +#[derive(Clone, Copy, Debug)] +#[repr(C, packed)] +pub struct EFISdt64 { + typ: u32, + size: u32, + pointer: u64, +} + +impl EFISdt64 { + /// The Physical address of a x86_64 EFI system table. + pub fn sdt_address(&self) -> usize { + self.pointer as usize + } +} + +/// Contains pointer to boot loader image handle. +#[derive(Debug)] +#[repr(C)] +pub struct EFIImageHandle32 { + typ: u32, + size: u32, + pointer: u32, +} + +/// Contains pointer to boot loader image handle. +#[derive(Debug)] +#[repr(C)] +pub struct EFIImageHandle64 { + typ: u32, + size: u32, + pointer: u64, +} + +/// If the image has relocatable header tag, this tag contains the image's +/// base physical address. +#[derive(Debug)] +#[repr(C)] +pub struct ImageLoadPhysAddr { + typ: u32, + size: u32, + load_base_addr: u32, +} + /// This tag contains a copy of RSDP as defined per ACPI 1.0 specification. #[derive(Clone, Copy, Debug)] #[repr(C, packed)]