kernel/
main.rs

1#![no_std]
2#![no_main]
3#![feature(abi_x86_interrupt)]
4#![feature(linked_list_cursors)]
5#![feature(btree_extract_if)]
6#![feature(custom_test_frameworks)]
7#![test_runner(crate::testing::test_runner)]
8#![reexport_test_harness_main = "test_main"]
9// fix warnings when testing (since we are not using the normal `kernel_main`)
10#![cfg_attr(test, allow(dead_code))]
11#![cfg_attr(test, allow(unused_imports))]
12
13extern crate alloc;
14
15// boot assembly code
16// starts in protected mode, setup long mode and jumps to kernel_main
17core::arch::global_asm!(include_str!("boot.S"));
18
19#[macro_use]
20// import first so that macros are available in other modules
21mod macros;
22
23mod acpi;
24mod cmdline;
25mod collections;
26mod cpu;
27mod devices;
28mod executable;
29mod fs;
30mod graphics;
31mod hw;
32mod io;
33mod memory_management;
34mod multiboot2;
35mod net;
36mod panic_handler;
37mod power;
38mod process;
39mod sync;
40mod testing;
41mod utils;
42
43use alloc::vec::Vec;
44use cpu::{
45    gdt,
46    interrupts::{self, apic},
47};
48use executable::elf::Elf;
49use increasing_heap_allocator::HeapStats;
50use io::console;
51use kernel_user_link::{
52    file::{BlockingMode, OpenOptions},
53    FD_STDERR, FD_STDIN, FD_STDOUT,
54};
55use memory_management::virtual_memory_mapper;
56use multiboot2::MultiBoot2Info;
57use process::scheduler;
58use tracing::{error, info};
59
60use crate::{
61    devices::clock,
62    memory_management::{
63        kernel_heap_allocator::ALLOCATOR,
64        memory_layout::{self, MemSize, KERNEL_HEAP_SIZE, PAGE_4K},
65        physical_page_allocator, virtual_space,
66    },
67    process::Process,
68};
69
70fn finish_boot() {
71    let physical_pages_stats = physical_page_allocator::stats();
72    let free_mem = MemSize(physical_pages_stats.0 * PAGE_4K);
73    let used_mem = MemSize(physical_pages_stats.1 * PAGE_4K);
74    // this stats is recorded at this point, meaning that we could have allocated a lot,
75    //  but then it got freed we don't record that
76    let HeapStats {
77        allocated,
78        free_size,
79        heap_size,
80    } = ALLOCATOR.stats();
81    info!("Boot finished!");
82    memory_layout::display_kernel_map();
83    info!("Free memory: {}", free_mem);
84    info!(
85        "Used memory: {} ({:0.3}%)",
86        used_mem,
87        used_mem.0 as f64 / (used_mem.0 + free_mem.0) as f64 * 100.
88    );
89    info!("Free heap: {}", MemSize(free_size));
90    info!(
91        "Used heap: {} ({:0.3}%)",
92        MemSize(allocated),
93        allocated as f64 / heap_size as f64 * 100.
94    );
95    info!(
96        "From possible heap: {} ({:0.3}%)",
97        MemSize(KERNEL_HEAP_SIZE),
98        allocated as f64 / KERNEL_HEAP_SIZE as f64 * 100.
99    );
100    virtual_space::debug_blocks();
101    info!("");
102}
103
104fn load_init_process() {
105    let mut init_file = fs::File::open("/init").expect("Could not find `init` file");
106    let elf = Elf::load(&mut init_file).expect("Could not load init file");
107    let mut process = Process::allocate_process(
108        0,
109        &elf,
110        &mut init_file,
111        Vec::new(),
112        fs::Directory::open("/").expect("No root"),
113    )
114    .expect("Could not allocate process for `init`");
115    assert_eq!(process.id(), 0, "Must be the first process");
116
117    // add the console to `init` manually, after that processes will either inherit it or open a pipe or something
118    // to act as STDIN/STDOUT/STDERR
119    let mut console = fs::File::open_blocking(
120        "/devices/console",
121        BlockingMode::Line,
122        OpenOptions::READ | OpenOptions::WRITE,
123    )
124    .expect("Could not find `/devices/console`");
125    // mark it as `terminal`
126    console.set_terminal(true);
127    process.attach_fs_node_to_fd(FD_STDIN, console.clone_inherit());
128    process.attach_fs_node_to_fd(FD_STDOUT, console.clone_inherit());
129    process.attach_fs_node_to_fd(FD_STDERR, console);
130
131    info!("Added `init` process pid={}", process.id());
132    scheduler::push_process(process);
133}
134
135#[link_section = ".text"]
136#[no_mangle]
137#[cfg(not(test))]
138/// `multiboot_info` is essentially `'static`, since it won't ever be removed from the memory
139/// since we don't exit `main` at all.
140pub extern "C" fn kernel_main(multiboot_info: &'static MultiBoot2Info) -> ! {
141    // uart setup require `cmdline`
142    cmdline::init(multiboot_info);
143    // init console first, so if we panicked, we can still see the output
144    console::early_init();
145    console::tracing::init();
146    cmdline::print_cmdline_parse(multiboot_info);
147    info!("{}", multiboot_info);
148    // must be called before any pages can be allocated
149    physical_page_allocator::init(multiboot_info);
150    // must be called next, before GDT, and this must be called before any heap allocations
151    virtual_memory_mapper::init_kernel_vm();
152    // require heap allocation
153    console::tracing::move_to_dynamic_buffer();
154    // must be called before interrupts
155    gdt::init_kernel_gdt();
156    interrupts::init_interrupts();
157    // mount devices map before initializing them
158    devices::init_devices_mapping();
159    let bios_tables = acpi::init_acpi_tables(multiboot_info);
160    info!("BIOS tables: {}", bios_tables);
161    apic::init(bios_tables);
162    // must be done after APIC is initialized
163    acpi::init();
164    clock::init(bios_tables);
165
166    // APIC timer interrupt rely on the clock, so it must be initialized after the clock
167    // and interrupts should be disabled until
168    unsafe { cpu::set_interrupts() };
169    devices::init_legacy_devices();
170    graphics::vga::init(multiboot_info.framebuffer());
171    console::init_late_device(multiboot_info.framebuffer());
172    devices::probe_pci_devices();
173    let disk_load_success = match fs::create_disk_mapping(0) {
174        Ok(()) => true,
175        Err(e) => {
176            error!("Could not create disk mapping: {:?}", e);
177            false
178        }
179    };
180    finish_boot();
181    // -- BOOT FINISHED --
182
183    if disk_load_success {
184        load_init_process();
185    } else {
186        info!("No disk is available, so can't load `init` process, the system will just wait like this...");
187    }
188
189    // this will return on shutdown, the sequence is initiated by `power::start_shutdown`
190    scheduler::schedule();
191    // continue the shutdown process
192    power::finish_power_sequence();
193}
194
195#[link_section = ".text"]
196#[no_mangle]
197#[cfg(test)]
198pub extern "C" fn kernel_main(multiboot_info: &MultiBoot2Info) -> ! {
199    // perform necessary initialization, then call the test
200    console::early_init();
201    physical_page_allocator::init(multiboot_info);
202    virtual_memory_mapper::init_kernel_vm();
203
204    test_main();
205
206    loop {
207        unsafe {
208            cpu::halt();
209        }
210    }
211}