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
use bootloader_api::config::{BootloaderConfig, Mapping};
use hal_core::boot::BootInfo;
use hal_x86_64::{
    cpu::{self, local::GsLocalData},
    time, vga,
};
pub use hal_x86_64::{
    cpu::{entropy::seed_rng, local::LocalKey, wait_for_interrupt},
    mm, NAME,
};

mod acpi;
mod boot;
mod framebuf;
pub mod interrupt;
mod oops;
pub mod pci;
pub mod shell;
pub use self::{
    boot::ArchInfo,
    oops::{oops, Oops},
};

#[cfg(test)]
mod tests;

pub type MinPageSize = mm::size::Size4Kb;

pub static BOOTLOADER_CONFIG: BootloaderConfig = {
    let mut config = BootloaderConfig::new_default();
    config.mappings.physical_memory = Some(Mapping::Dynamic);
    // the kernel is mapped into the higher half of the virtual address space.
    config.mappings.dynamic_range_start = Some(0xFFFF_8000_0000_0000);
    config.mappings.page_table_recursive = Some(Mapping::Dynamic);

    config
};

#[cfg(target_os = "none")]
bootloader_api::entry_point!(arch_entry, config = &BOOTLOADER_CONFIG);

pub fn arch_entry(info: &'static mut bootloader_api::BootInfo) -> ! {
    unsafe {
        cpu::intrinsics::cli();
    }

    if let Some(offset) = info.physical_memory_offset.into_option() {
        // Safety: i hate everything
        unsafe {
            vga::init_with_offset(offset);
        }
    }
    /* else {
        // lol we're hosed
    } */

    let (boot_info, archinfo) = boot::BootloaderApiBootInfo::from_bootloader(info);
    crate::kernel_start(boot_info, archinfo);
}

pub fn init(_info: &impl BootInfo, archinfo: &ArchInfo) -> maitake::time::Clock {
    pci::init_pci();

    // init boot processor's core-local data
    unsafe {
        GsLocalData::init();
    }
    tracing::info!("set up the boot processor's local data");

    if let Some(rsdp) = archinfo.rsdp_addr {
        let acpi = acpi::acpi_tables(rsdp);
        let platform_info = acpi.and_then(|acpi| acpi.platform_info());
        match platform_info {
            Ok(platform) => {
                tracing::debug!("found ACPI platform info");
                let irq_ctrl =
                    interrupt::enable_hardware_interrupts(Some(&platform.interrupt_model));
                acpi::bringup_smp(&platform)
                    .expect("failed to bring up application processors! this is bad news!");
                irq_ctrl
            }
            Err(error) => {
                tracing::warn!(?error, "missing ACPI platform info");
                interrupt::enable_hardware_interrupts(None)
            }
        }
    } else {
        // TODO(eliza): try using MP Table to bringup application processors?
        tracing::warn!("no RSDP from bootloader, skipping SMP bringup");
        interrupt::enable_hardware_interrupts(None)
    };

    time::Rdtsc::new()
        .map_err(|error| {
            tracing::warn!(%error, "RDTSC not supported");
        })
        .and_then(|rdtsc| {
            rdtsc
                .into_maitake_clock()
                .inspect(|clock| tracing::info!(?clock, "calibrated RDTSC clock"))
                .map_err(|error| tracing::warn!(%error, "could not calibrate RDTSC clock"))
        })
        .unwrap_or(interrupt::IDIOTIC_CLOCK)
}

// TODO(eliza): this is now in arch because it uses the serial port, would be
// nice if that was cross platform...
#[cfg(test)]
pub fn run_tests() {
    use hal_x86_64::serial;
    let com1 = serial::com1().expect("if we're running tests, there ought to be a serial port");
    let mk = || com1.lock();
    match mycotest::runner::run_tests(mk) {
        Ok(()) => qemu_exit(QemuExitCode::Success),
        Err(_) => qemu_exit(QemuExitCode::Failed),
    }
}

#[cfg(test)]
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[repr(u32)]
pub(crate) enum QemuExitCode {
    Success = 0x10,
    Failed = 0x11,
}

/// Exit using `isa-debug-exit`, for use in tests.
///
/// NOTE: This is a temporary mechanism until we get proper shutdown implemented.
#[cfg(test)]
pub(crate) fn qemu_exit(exit_code: QemuExitCode) -> ! {
    let code = exit_code as u32;
    unsafe {
        cpu::Port::at(0xf4).writel(code);

        // If the previous line didn't immediately trigger shutdown, hang.
        cpu::halt()
    }
}