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
//! # Symmetric Multi-Processing
//!
//! On x86 multicore systems, only the first (boot) core starts executing on boot.
//! The other application processors have to be started manually using the IPC mechanism of the [LApic].

use core::arch::global_asm;
use core::ptr::addr_of;
use core::slice;

use crate::arch::cpu;
use crate::arch::int::apic::{INVALID_ID, LAPIC_IDS};
use crate::arch::int::lapic::LAPIC;
use crate::arch::int::{self};
use crate::arch::pit::Timer;
use crate::start_high;

/// Placeholder address, replaced by the [[start_high]] address
const SETUP_HIGH_PLACEHOLDER: [u8; 4] = 0xdeadbeefu32.to_ne_bytes();
/// Destination, where the startup code should be copied
const RELOCATE_DST: u32 = 0x8000;
const _: () = assert!(RELOCATE_DST & !0xff000 == 0);

/// Boot the other symmetric multi-processor cores
pub fn boot() {
    assert!(!int::enabled() && cpu::online().count_ones() == 1);

    serial!("Booting other cores!");

    relocate_setup();

    // Calculate Init-IPI vector based on address of relocated setup_ap
    let vector = (RELOCATE_DST as usize >> 12) as u8;

    // Boot cores one-by-one (prevents booting more cores than we can handle)
    for (core, lapic_id) in LAPIC_IDS.iter().copied().enumerate() {
        if lapic_id == INVALID_ID || lapic_id == LAPIC.id() {
            continue;
        }

        serial!("boot {core}: {lapic_id}");

        // Send Init-IPI to AP
        LAPIC.send_init(lapic_id);
        while !LAPIC.delivered() {}
        // wait at least 10ms
        Timer::new(10_000).wait();

        // Send Startup-IPI twice
        LAPIC.send_startup(lapic_id, vector);
        while !LAPIC.delivered() {}

        // wait at least 200us
        Timer::new(200).wait();

        LAPIC.send_startup(lapic_id, vector);
        while !LAPIC.delivered() {}
    }
}

/// Relocate the real mode setup code
///
/// The application processors (APs) start in real mode, which means that your setup
/// code must be placed within the first megabyte -- your operating system resides
/// currently at a much higher address (1 MiB), so the code has to be copied
/// down there first.
///
/// Luckily, the code in `setup_ap` can be relocated by copying -- because it
/// does not use any absolute addressing (except when jumping to the protected
/// mode function [start_high]).
/// The function must be copied to the address of [RELOCATE_DST],
/// so that the APs can start there.
///
/// # Note
/// You could also tell the linker script to put the code directly
/// at the appropriate place, but unfortunately the Qemu multiboot
/// implementation (via `-kernel` parameter) can't handle it properly.
fn relocate_setup() {
    let setup_ap = unsafe { addr_of!(SETUP_AP_BEGIN)..addr_of!(SETUP_AP_END) };
    assert!(setup_ap.start < setup_ap.end, "invalid setup code");

    let setup_ap_high = unsafe { addr_of!(setup_start_high) };

    let len = unsafe { setup_ap.end.offset_from(setup_ap.start) as usize };
    let relocate_src = unsafe { slice::from_raw_parts(setup_ap.start, len) };
    let relocate_dst = unsafe { slice::from_raw_parts_mut(RELOCATE_DST as *mut u8, len) };

    serial!("relocate {len} bytes");
    relocate_dst.copy_from_slice(relocate_src);

    let high_jump = unsafe { addr_of!(start_high) };
    let high_off = unsafe { setup_ap_high.offset_from(setup_ap.start) as usize };
    let high_dst = &mut relocate_dst[high_off..high_off + 4];

    assert!(high_dst == SETUP_HIGH_PLACEHOLDER);

    high_dst.copy_from_slice(&(high_jump as u32).to_ne_bytes());
}

extern "C" {
    static SETUP_AP_BEGIN: u8;
    static SETUP_AP_END: u8;
    static setup_start_high: u8;
}

global_asm!(include_str!("../setup_ap.s"), options(raw));