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));