Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Identity-mapped pagetable support (UEFI boot) #924

Closed
wants to merge 24 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
24 commits
Select commit Hold shift + click to select a range
94d0f89
Map SMP bootcode to allocated physical address instead of fixed one
sarahspberrypi Dec 1, 2023
7a0650e
fix path to physicalmem::allocate
sarahspberrypi Dec 1, 2023
72aba63
[DO NOT MERGE]: use UEFI hermit-entry
sarahspberrypi Dec 1, 2023
a7b3281
paging updated to support identity-mapped/offset pagetables, make roo…
sarahspberrypi Dec 1, 2023
75630d5
added error handling (normal sized page requested but only huge pages…
sarahspberrypi Dec 1, 2023
61ef32a
fix format
sarahspberrypi Dec 1, 2023
dfd2fca
feat(physicalmem x86_64): add function to allocate physical memory ou…
sarahspberrypi Dec 1, 2023
d189cd5
fix(physicalmem x86_64): add missing imports and change allocate func…
sarahspberrypi Dec 1, 2023
29e5a96
feat(x86_64 uefi): add RSDP detection for ACPI tables
sarahspberrypi Dec 1, 2023
91a8e4a
is_uefi now returns a bool instead of a Result, function calls are ad…
sarahspberrypi Dec 1, 2023
040eb19
(feat) add functionality to set the Multiple APIC Descriptor Table (M…
sarahspberrypi Dec 1, 2023
8b5d2a3
revert SMP boot for BIOS systems to previous way, WIP for UEFI system…
sarahspberrypi Dec 13, 2023
c444c99
revert SMP boot for BIOS systems to previous way, WIP for UEFI system…
sarahspberrypi Dec 13, 2023
205ffd5
Merge branch 'impl'
sarahspberrypi Jan 4, 2024
9ae65a3
add explanatory comments
sarahspberrypi Jan 4, 2024
6a3b4c5
change is_uefi to return a bool
sarahspberrypi Jan 4, 2024
b715bd6
adjust ACPI for UEFI via its own init_uefi function and remove mappin…
sarahspberrypi Jan 4, 2024
79dbeed
UEFI systems don't need to remap address spaces for scheduling
sarahspberrypi Jan 4, 2024
c674e2c
add more debug functionality for pages
sarahspberrypi Jan 19, 2024
f08d754
add parsing of SSDT for windows systems
sarahspberrypi Jan 19, 2024
dc07a18
adapt ssdt to be of correct type
sarahspberrypi Jan 19, 2024
981a492
code cleanup, removal of unnecessary traces
sarahspberrypi Mar 26, 2024
0b8fcfb
add SMP functionality
sarahspberrypi Mar 26, 2024
788d920
add VGA functionality
sarahspberrypi Mar 26, 2024
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 1 addition & 2 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

3 changes: 3 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -147,3 +147,6 @@ members = [
exclude = [
"hermit-builtins",
]

[patch.crates-io]
hermit-entry = { git = "https://github.com/sarahspberrypi/hermit-entry.git" }
142 changes: 141 additions & 1 deletion src/arch/x86_64/kernel/acpi.rs
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,7 @@ static SLP_TYPA: OnceCell<u8> = OnceCell::new();

/// The "Root System Description Pointer" structure providing pointers to all other ACPI tables.
#[repr(C, packed)]
#[derive(Debug)]
struct AcpiRsdp {
signature: [u8; 8],
checksum: u8,
Expand Down Expand Up @@ -87,6 +88,9 @@ impl AcpiSdtHeader {
fn signature(&self) -> &str {
str::from_utf8(&self.signature).unwrap()
}
pub fn header_start_address(&self) -> usize {
self as *const _ as usize
}
}

/// A convenience structure to work with an ACPI table.
Expand Down Expand Up @@ -309,6 +313,16 @@ fn detect_rsdp(start_address: PhysAddr, end_address: PhysAddr) -> Result<&'stati
/// Detects ACPI support of the computer system.
/// Returns a reference to the ACPI RSDP within the Ok() if successful or an empty Err() on failure.
fn detect_acpi() -> Result<&'static AcpiRsdp, ()> {
// For UEFI Systems, RSDP detection takes place in the bootloader and the tables are already mapped so we only need to return a proper reference to the table
if crate::arch::kernel::is_uefi() {
let rsdp = crate::arch::kernel::get_rsdp_addr();
trace!("RSDP detected successfully at {rsdp:#x?}");
let rsdp = unsafe { &*(rsdp as *const AcpiRsdp) };
if &rsdp.signature != b"RSD PTR " {
panic!("RSDP Address not valid!");
}
return Ok(rsdp);
}
// Get the address of the EBDA.
let frame =
PhysFrame::<BasePageSize>::containing_address(x86_64::PhysAddr::new(EBDA_PTR_LOCATION.0));
Expand Down Expand Up @@ -412,7 +426,26 @@ fn parse_fadt(fadt: AcpiTable<'_>) {
} else {
PhysAddr(fadt_table.dsdt.into())
};
let dsdt = AcpiTable::map(dsdt_address);
let dsdt = if !crate::arch::kernel::is_uefi() {
AcpiTable::map(dsdt_address)
} else {
// For UEFI Systems, the tables are already mapped so we only need to return a proper reference to the table
let table = unsafe { (dsdt_address.0 as *const AcpiSdtHeader).as_ref().unwrap() };
let mut length = table.length as u64;
let res = length % BasePageSize::SIZE;
//ACPI tables are 4KiB aligned, so the length can span the entire pagetable
//-> necessary since the tables are dropped (and virtual memory is deallocated) after use
//somehow, this table is larger than 4KiB, so we bumb it up to the entire page
if res != 0 {
length += 0x1000 - res;
}

AcpiTable {
header: table,
allocated_virtual_address: VirtAddr(dsdt_address.0),
allocated_length: length as usize,
}
};

// Check it.
assert!(
Expand Down Expand Up @@ -527,3 +560,110 @@ pub fn init() {
}
}
}

pub fn init_uefi() {
// Retrieve the RSDP and get a pointer to either the XSDT (64-bit) or RSDT (32-bit), whichever is available.
// Both are called RSDT in the following.
let rsdp = detect_acpi().expect("Hermit requires an ACPI-compliant system");
let rsdt_physical_address = if rsdp.revision >= 2 {
PhysAddr(rsdp.xsdt_physical_address)
} else {
PhysAddr(rsdp.rsdt_physical_address.into())
};

//Load RSDT
let rsdt = &unsafe { *(rsdt_physical_address.0 as *const AcpiSdtHeader) };

// The RSDT contains pointers to all available ACPI tables.
// Iterate through them.
let mut current_address = rsdt_physical_address.0 as usize + mem::size_of::<AcpiRsdp>();
trace!("RSDT start address at {current_address:#x}");
let nr_entries =
(rsdt.length as usize - mem::size_of::<AcpiRsdp>()) / mem::size_of::<AcpiSdtHeader>();
let end_addr = current_address + nr_entries * mem::size_of::<AcpiSdtHeader>();
while current_address < end_addr {
trace!("current_address: {current_address:#x}");

// Depending on the RSDP revision, either an XSDT or an RSDT has been chosen above.
// The XSDT contains 64-bit pointers whereas the RSDT has 32-bit pointers.
let table_physical_address = if rsdp.revision >= 2 {
let address = PhysAddr(unsafe { ptr::read_unaligned(current_address as *const u64) });
current_address += mem::size_of::<u64>();
address
} else {
let address =
PhysAddr((unsafe { ptr::read_unaligned(current_address as *const u32) }).into());
current_address += mem::size_of::<u32>();
address
};

let table = unsafe {
(table_physical_address.0 as *const AcpiSdtHeader)
.as_ref()
.unwrap()
};

let signature = table.signature();

debug!("Found ACPI table: {signature:#?}");

if signature == "APIC" {
// This is a "Multiple APIC Descriptor Table" (MADT) aka "APIC Table"
// Check and save the entire APIC table for the get_apic_table() call
assert!(
verify_checksum(table.header_start_address(), table.length as usize).is_ok(),
"MADT at {table_physical_address:#x} has invalid checksum"
);
//ACPI tables are 4KiB aligned, so the length can span the entire pagetable
//-> necessary since the tables are dropped (and virtual memory is deallocated) after use
let mut length = table.length as u64;
if length < 0x1000 {
length = 0x1000;
}

let madt: AcpiTable<'static> = AcpiTable {
header: table,
allocated_virtual_address: VirtAddr(table_physical_address.0),
allocated_length: length as usize,
};
MADT.set(madt).unwrap();
} else if signature == "FACP" {
// This is the "Fixed ACPI Description Table" (FADT) aka "Fixed ACPI Control Pointer" (FACP)
// Check and parse this table for the poweroff() call
assert!(
verify_checksum(table.header_start_address(), table.length as usize).is_ok(),
"FADT at {table_physical_address:#x} has invalid checksum"
);
//ACPI tables are 4KiB aligned, so the length can span the entire pagetable
//-> necessary since the tables are dropped (and virtual memory is deallocated) after use
let mut length = table.length as u64;
if length < 0x1000 {
length = 0x1000;
}
let fadt: AcpiTable<'static> = AcpiTable {
header: table,
allocated_virtual_address: VirtAddr(table_physical_address.0),
allocated_length: length as usize,
};

parse_fadt(fadt);
} else if signature == "SSDT" {
assert!(
verify_checksum(table.header_start_address(), table.length as usize).is_ok(),
"SSDT at {table_physical_address:p} has invalid checksum"
);

let mut length = table.length as u64;
if length < 0x1000 {
length = 0x1000;
}

let ssdt: AcpiTable<'static> = AcpiTable {
header: table,
allocated_virtual_address: VirtAddr(table_physical_address.0),
allocated_length: length as usize,
};
parse_ssdt(ssdt);
}
}
}
87 changes: 59 additions & 28 deletions src/arch/x86_64/kernel/apic.rs
Original file line number Diff line number Diff line change
Expand Up @@ -246,13 +246,18 @@ pub fn local_apic_id_count() -> u32 {
}

fn init_ioapic_address(phys_addr: PhysAddr) {
let ioapic_address = virtualmem::allocate(BasePageSize::SIZE as usize).unwrap();
IOAPIC_ADDRESS.set(ioapic_address).unwrap();
debug!("Mapping IOAPIC at {phys_addr:p} to virtual address {ioapic_address:p}",);
if !crate::kernel::is_uefi() {
let ioapic_address = virtualmem::allocate(BasePageSize::SIZE as usize).unwrap();
IOAPIC_ADDRESS.set(ioapic_address).unwrap();
debug!("Mapping IOAPIC at {phys_addr:p} to virtual address {ioapic_address:p}",);

let mut flags = PageTableEntryFlags::empty();
flags.device().writable().execute_disable();
paging::map::<BasePageSize>(ioapic_address, phys_addr, 1, flags);
let mut flags = PageTableEntryFlags::empty();
flags.device().writable().execute_disable();
paging::map::<BasePageSize>(ioapic_address, phys_addr, 1, flags);
} else {
// UEFI systems have already id mapped everything, so we can just set the physical address as the virtual one
IOAPIC_ADDRESS.set(VirtAddr(phys_addr.as_u64())).unwrap();
}
}

#[cfg(not(feature = "acpi"))]
Expand Down Expand Up @@ -455,16 +460,23 @@ pub fn init() {
if !processor::supports_x2apic() {
// We use the traditional xAPIC mode available on all x86-64 CPUs.
// It uses a mapped page for communication.
let local_apic_address = virtualmem::allocate(BasePageSize::SIZE as usize).unwrap();
LOCAL_APIC_ADDRESS.set(local_apic_address).unwrap();
debug!(
"Mapping Local APIC at {:p} to virtual address {:p}",
local_apic_physical_address, local_apic_address
);
if crate::kernel::is_uefi() {
//already id mapped in UEFI systems, just use the physical address as virtual one
LOCAL_APIC_ADDRESS
.set(VirtAddr(local_apic_physical_address.as_u64()))
.unwrap();
} else {
let local_apic_address = virtualmem::allocate(BasePageSize::SIZE as usize).unwrap();
LOCAL_APIC_ADDRESS.set(local_apic_address).unwrap();
debug!(
"Mapping Local APIC at {:p} to virtual address {:p}",
local_apic_physical_address, local_apic_address
);

let mut flags = PageTableEntryFlags::empty();
flags.device().writable().execute_disable();
paging::map::<BasePageSize>(local_apic_address, local_apic_physical_address, 1, flags);
let mut flags = PageTableEntryFlags::empty();
flags.device().writable().execute_disable();
paging::map::<BasePageSize>(local_apic_address, local_apic_physical_address, 1, flags);
}
}

// Set gates to ISRs for the APIC interrupts we are going to enable.
Expand Down Expand Up @@ -683,6 +695,8 @@ pub fn init_next_processor_variables() {
pub fn boot_application_processors() {
use core::hint;

use x86_64::structures::paging::Translate;

use super::{raw_boot_info, start};

let smp_boot_code = include_bytes!(concat!(core::env!("OUT_DIR"), "/boot.bin"));
Expand All @@ -693,20 +707,37 @@ pub fn boot_application_processors() {
"SMP Boot Code is larger than a page"
);
debug!("SMP boot code is {} bytes long", smp_boot_code.len());
// We can only allocate full pages of physmem

// Identity-map the boot code page and copy over the code.
debug!(
"Mapping SMP boot code to physical and virtual address {:p}",
SMP_BOOT_CODE_ADDRESS
);
let mut flags = PageTableEntryFlags::empty();
flags.normal().writable();
paging::map::<BasePageSize>(
SMP_BOOT_CODE_ADDRESS,
PhysAddr(SMP_BOOT_CODE_ADDRESS.as_u64()),
1,
flags,
);
if crate::kernel::is_uefi() {
// Since UEFI already provides identity-mapped pagetables, we only have to check whether the physical address 0x8000 really is mapped to the virtual address 0x8000
unsafe {
let pt = crate::arch::mm::paging::identity_mapped_page_table();
let virt_addr = 0x8000 as u64;
let phys_addr = pt
.translate_addr(x86_64::VirtAddr::new(virt_addr))
.unwrap()
.as_u64();
assert_eq!(
phys_addr, virt_addr,
"0x8000 is not identity-mapped for SMP boot code!"
)
}
} else {
// Identity-map the boot code page and copy over the code.
debug!(
"Mapping SMP boot code to physical and virtual address {:p}",
SMP_BOOT_CODE_ADDRESS
);
let mut flags = PageTableEntryFlags::empty();
flags.normal().writable();
paging::map::<BasePageSize>(
SMP_BOOT_CODE_ADDRESS,
PhysAddr(SMP_BOOT_CODE_ADDRESS.as_u64()),
1,
flags,
);
}
unsafe {
ptr::copy_nonoverlapping(
smp_boot_code.as_ptr(),
Expand Down
32 changes: 30 additions & 2 deletions src/arch/x86_64/kernel/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -67,12 +67,34 @@ pub fn get_limit() -> usize {
boot_info().hardware_info.phys_addr_range.end as usize
}

pub fn get_start() -> usize {
boot_info().hardware_info.phys_addr_range.start as usize
}

pub fn is_uefi() -> bool {
match boot_info().platform_info {
PlatformInfo::Uefi { .. } => true,
PlatformInfo::LinuxBootParams { .. } => false,
PlatformInfo::Uhyve { .. } => false,
PlatformInfo::Multiboot { .. } => false,
}
}

// RSDP has to be located in the bootloader for UEFI systems, so we can retrieve the address from boot info
pub fn get_rsdp_addr() -> u64 {
match boot_info().platform_info {
PlatformInfo::Uefi { rsdp_addr } => rsdp_addr,
_ => 0,
}
}

pub fn get_mbinfo() -> VirtAddr {
match boot_info().platform_info {
PlatformInfo::Multiboot {
multiboot_info_addr,
..
} => VirtAddr(multiboot_info_addr.get()),
PlatformInfo::Uefi { .. } => VirtAddr(0),
PlatformInfo::LinuxBootParams { .. } => VirtAddr(0),
PlatformInfo::Uhyve { .. } => VirtAddr(0),
}
Expand All @@ -83,6 +105,7 @@ pub fn get_possible_cpus() -> u32 {
use core::cmp;

match boot_info().platform_info {
PlatformInfo::Uefi { .. } => apic::local_apic_id_count(),
PlatformInfo::LinuxBootParams { .. } => apic::local_apic_id_count(),
PlatformInfo::Multiboot { .. } => apic::local_apic_id_count(),
// FIXME: Remove get_processor_count after a transition period for uhyve 0.1.3 adoption
Expand All @@ -106,6 +129,7 @@ pub fn get_processor_count() -> u32 {
pub fn is_uhyve_with_pci() -> bool {
match boot_info().platform_info {
PlatformInfo::Multiboot { .. } => false,
PlatformInfo::Uefi { .. } => false,
PlatformInfo::LinuxBootParams { .. } => false,
PlatformInfo::Uhyve { has_pci, .. } => has_pci,
}
Expand All @@ -115,7 +139,7 @@ pub fn args() -> Option<&'static str> {
match boot_info().platform_info {
PlatformInfo::Multiboot { command_line, .. } => command_line,
PlatformInfo::LinuxBootParams { command_line, .. } => command_line,
PlatformInfo::Uhyve { .. } => None,
PlatformInfo::Uhyve { .. } | PlatformInfo::Uefi { .. } => None,
}
}

Expand Down Expand Up @@ -175,7 +199,11 @@ pub fn boot_processor_init() {
}
if !env::is_uhyve() {
#[cfg(feature = "acpi")]
acpi::init();
if is_uefi() {
acpi::init_uefi();
} else {
acpi::init();
}
}

apic::init();
Expand Down
3 changes: 2 additions & 1 deletion src/arch/x86_64/kernel/processor.rs
Original file line number Diff line number Diff line change
Expand Up @@ -373,6 +373,7 @@ impl CpuFrequency {
fn detect_from_uhyve() -> Result<u16, ()> {
match boot_info().platform_info {
PlatformInfo::Multiboot { .. } => Err(()),
PlatformInfo::Uefi { .. } => Err(()),
PlatformInfo::LinuxBootParams { .. } => Err(()),
PlatformInfo::Uhyve { cpu_freq, .. } => Ok(u16::try_from(
cpu_freq.map(NonZeroU32::get).unwrap_or_default() / 1000,
Expand Down Expand Up @@ -1016,7 +1017,7 @@ pub fn shutdown() -> ! {
Err(()) => {
match boot_info().platform_info {
PlatformInfo::LinuxBootParams { .. } => triple_fault(),
PlatformInfo::Multiboot { .. } => {
PlatformInfo::Multiboot { .. } | PlatformInfo::Uefi { .. } => {
// Try QEMU's debug exit
let exit_handler = qemu_exit::X86::new(0xf4, 3);
exit_handler.exit_success()
Expand Down
Loading