mycelium_kernel/arch/
x86_64.rs

1use bootloader_api::config::{BootloaderConfig, Mapping};
2use hal_core::boot::BootInfo;
3use hal_x86_64::{
4    cpu::{self, local::GsLocalData},
5    time, vga,
6};
7pub use hal_x86_64::{
8    cpu::{entropy::seed_rng, local::LocalKey, wait_for_interrupt},
9    mm, NAME,
10};
11
12mod acpi;
13mod boot;
14mod framebuf;
15pub mod interrupt;
16mod oops;
17pub mod pci;
18pub mod shell;
19pub use self::{
20    boot::ArchInfo,
21    oops::{oops, Oops},
22};
23
24#[cfg(test)]
25mod tests;
26
27pub type MinPageSize = mm::size::Size4Kb;
28
29pub static BOOTLOADER_CONFIG: BootloaderConfig = {
30    let mut config = BootloaderConfig::new_default();
31    config.mappings.physical_memory = Some(Mapping::Dynamic);
32    // the kernel is mapped into the higher half of the virtual address space.
33    config.mappings.dynamic_range_start = Some(0xFFFF_8000_0000_0000);
34    config.mappings.page_table_recursive = Some(Mapping::Dynamic);
35
36    config
37};
38
39#[cfg(target_os = "none")]
40bootloader_api::entry_point!(arch_entry, config = &BOOTLOADER_CONFIG);
41
42pub fn arch_entry(info: &'static mut bootloader_api::BootInfo) -> ! {
43    unsafe {
44        cpu::intrinsics::cli();
45    }
46
47    if let Some(offset) = info.physical_memory_offset.into_option() {
48        // Safety: i hate everything
49        unsafe {
50            vga::init_with_offset(offset);
51        }
52    }
53    /* else {
54        // lol we're hosed
55    } */
56
57    let (boot_info, archinfo) = boot::BootloaderApiBootInfo::from_bootloader(info);
58    crate::kernel_start(boot_info, archinfo);
59}
60
61pub fn init(_info: &impl BootInfo, archinfo: &ArchInfo) -> maitake::time::Clock {
62    pci::init_pci();
63
64    // init boot processor's core-local data
65    unsafe {
66        GsLocalData::init();
67    }
68    tracing::info!("set up the boot processor's local data");
69
70    if let Some(rsdp) = archinfo.rsdp_addr {
71        let acpi = acpi::acpi_tables(rsdp);
72        let platform_info = acpi.and_then(|acpi| acpi.platform_info());
73        match platform_info {
74            Ok(platform) => {
75                tracing::debug!("found ACPI platform info");
76                let irq_ctrl =
77                    interrupt::enable_hardware_interrupts(Some(&platform.interrupt_model));
78                acpi::bringup_smp(&platform)
79                    .expect("failed to bring up application processors! this is bad news!");
80                irq_ctrl
81            }
82            Err(error) => {
83                tracing::warn!(?error, "missing ACPI platform info");
84                interrupt::enable_hardware_interrupts(None)
85            }
86        }
87    } else {
88        // TODO(eliza): try using MP Table to bringup application processors?
89        tracing::warn!("no RSDP from bootloader, skipping SMP bringup");
90        interrupt::enable_hardware_interrupts(None)
91    };
92
93    time::Rdtsc::new()
94        .map_err(|error| {
95            tracing::warn!(%error, "RDTSC not supported");
96        })
97        .and_then(|rdtsc| {
98            rdtsc
99                .into_maitake_clock()
100                .inspect(|clock| tracing::info!(?clock, "calibrated RDTSC clock"))
101                .map_err(|error| tracing::warn!(%error, "could not calibrate RDTSC clock"))
102        })
103        .unwrap_or(interrupt::IDIOTIC_CLOCK)
104}
105
106// TODO(eliza): this is now in arch because it uses the serial port, would be
107// nice if that was cross platform...
108#[cfg(test)]
109pub fn run_tests() {
110    use hal_x86_64::serial;
111    let com1 = serial::com1().expect("if we're running tests, there ought to be a serial port");
112    let mk = || com1.lock();
113    match mycotest::runner::run_tests(mk) {
114        Ok(()) => qemu_exit(QemuExitCode::Success),
115        Err(_) => qemu_exit(QemuExitCode::Failed),
116    }
117}
118
119#[cfg(test)]
120#[derive(Debug, Clone, Copy, PartialEq, Eq)]
121#[repr(u32)]
122pub(crate) enum QemuExitCode {
123    Success = 0x10,
124    Failed = 0x11,
125}
126
127/// Exit using `isa-debug-exit`, for use in tests.
128///
129/// NOTE: This is a temporary mechanism until we get proper shutdown implemented.
130#[cfg(test)]
131pub(crate) fn qemu_exit(exit_code: QemuExitCode) -> ! {
132    let code = exit_code as u32;
133    unsafe {
134        cpu::Port::at(0xf4).writel(code);
135
136        // If the previous line didn't immediately trigger shutdown, hang.
137        cpu::halt()
138    }
139}