Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
28 commits
Select commit Hold shift + click to select a range
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

11 changes: 8 additions & 3 deletions hal-core/src/addr.rs
Original file line number Diff line number Diff line change
Expand Up @@ -334,14 +334,19 @@ macro_rules! impl_addrs {
pub fn as_ptr<T>(self) -> *mut T {
Address::as_ptr(self)
}

#[inline]
pub const fn as_usize(self) -> usize {
self.0 as usize
}
}
)+
}
}

impl PAddr {
#[inline]
pub fn from_usize_checked(u: usize) -> Result<Self, InvalidAddress> {
pub const fn from_usize_checked(u: usize) -> Result<Self, InvalidAddress> {
#[cfg(target_arch = "x86_64")]
{
const MASK: usize = 0xFFF0_0000_0000_0000;
Expand All @@ -359,7 +364,7 @@ impl PAddr {

impl VAddr {
#[inline]
pub fn from_usize_checked(u: usize) -> Result<Self, InvalidAddress> {
pub const fn from_usize_checked(u: usize) -> Result<Self, InvalidAddress> {
#[cfg(target_arch = "x86_64")]
{
// sign extend 47th bit
Expand Down Expand Up @@ -404,7 +409,7 @@ impl_addrs! {
}

impl InvalidAddress {
fn new(addr: usize, msg: &'static str) -> Self {
const fn new(addr: usize, msg: &'static str) -> Self {
Self { msg, addr }
}
}
Expand Down
12 changes: 12 additions & 0 deletions hal-core/src/mem/page.rs
Original file line number Diff line number Diff line change
Expand Up @@ -439,6 +439,11 @@ impl<A: Address, S: Size> Page<A, S> {
Self { base, size }
}

/// Returns the page number in `S`-sized pages.
pub fn number(&self) -> usize {
self.base.as_usize() / self.size.as_usize()
}

pub fn base_addr(&self) -> A {
self.base
}
Expand Down Expand Up @@ -666,6 +671,13 @@ impl<S: Size + fmt::Display> fmt::Debug for NotAligned<S> {
}
}

impl<S: Size + fmt::Display> fmt::Display for NotAligned<S> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let Self { size } = self;
write!(f, "address not aligned on a {size}-sized boundary")
}
}

// === impl TranslateError ===

impl<S: Size> From<&'static str> for TranslateError<S> {
Expand Down
1 change: 1 addition & 0 deletions hal-x86_64/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ hal-core = { path = "../hal-core" }
mycelium-util = { path = "../util" }
mycelium-trace = { path = "../trace" }
mycotest = { path = "../mycotest"}
pin-project = "1"
rand_core = { version = "0.6.4", default_features = false, optional = true }
raw-cpuid = "10.6.0"
tracing = { git = "https://github.com/tokio-rs/tracing", default_features = false, features = ["attributes"] }
Expand Down
2 changes: 1 addition & 1 deletion hal-x86_64/src/control_regs.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ use mycelium_util::bits::bitfield;
pub mod cr3 {
use super::*;
use crate::{mm::size::Size4Kb, PAddr};
use hal_core::{mem::page::Page, Address};
use hal_core::mem::page::Page;

#[derive(Copy, Clone, Eq, PartialEq)]
pub struct Flags(u64);
Expand Down
13 changes: 3 additions & 10 deletions hal-x86_64/src/cpu.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,9 @@ pub mod intrinsics;
#[cfg(feature = "alloc")]
pub mod local;
pub mod msr;
pub use self::msr::Msr;
pub mod smp;
pub mod topology;
pub use self::{msr::Msr, topology::Topology};

#[repr(transparent)]
pub struct Port {
Expand Down Expand Up @@ -230,18 +232,9 @@ impl bits::FromBits<u8> for Ring {
// === impl DtablePtr ===

impl DtablePtr {
pub(crate) fn new<T>(t: &'static T) -> Self {
unsafe {
// safety: the `'static` lifetime ensures the pointed dtable is
// never going away
Self::new_unchecked(t)
}
}

pub(crate) unsafe fn new_unchecked<T>(t: &T) -> Self {
let limit = (mem::size_of::<T>() - 1) as u16;
let base = t as *const _ as *const ();

Self { limit, base }
}
}
Expand Down
17 changes: 13 additions & 4 deletions hal-x86_64/src/cpu/local.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
use super::Msr;
use super::{topology::Processor, Msr};
use alloc::boxed::Box;
use core::{
arch::asm,
Expand All @@ -11,13 +11,16 @@ use mycelium_util::{fmt, sync::Lazy};

#[repr(C)]
#[derive(Debug)]
#[pin_project::pin_project]
pub struct GsLocalData {
/// This *must* be the first field of the local data struct, because we read
/// from `gs:0x0` to get the local data's address.
_self: *const Self,
magic: usize,
processor: Processor,
/// Because this struct is self-referential, it may not be `Unpin`.
_must_pin: PhantomPinned,

/// Arbitrary user data.
///
// TODO(eliza): consider storing this in some kind of heap allocated tree
Expand All @@ -35,12 +38,13 @@ impl GsLocalData {
const MAGIC: usize = 0xC0FFEE;
pub const MAX_LOCAL_KEYS: usize = 64;

const fn new() -> Self {
pub(crate) const fn new(processor: Processor) -> Self {
#[allow(clippy::declare_interior_mutable_const)] // array initializer
const LOCAL_SLOT_INIT: AtomicPtr<()> = AtomicPtr::new(ptr::null_mut());
Self {
_self: ptr::null(),
_must_pin: PhantomPinned,
processor,
magic: Self::MAGIC,
userdata: [LOCAL_SLOT_INIT; Self::MAX_LOCAL_KEYS],
}
Expand Down Expand Up @@ -78,6 +82,10 @@ impl GsLocalData {
.expect("GsLocalData::current() called before local data was initialized on this core!")
}

pub fn processor_info(&self) -> &Processor {
&self.processor
}

/// Access a local key on this CPU core's local data.
pub fn with<T, U>(&self, key: &LocalKey<T>, f: impl FnOnce(&T) -> U) -> U {
let idx = *key.idx.get();
Expand Down Expand Up @@ -108,14 +116,15 @@ impl GsLocalData {
///
/// This should only be called a single time per CPU core.
#[track_caller]
pub fn init() {
pub(crate) fn init(self: Pin<Box<Self>>) {
if Self::has_local_data() {
tracing::warn!("this CPU core already has local data initialized!");
debug_assert!(false, "this CPU core already has local data initialized!");
return;
}

let ptr = Box::into_raw(Box::new(Self::new()));
let this = unsafe { Pin::into_inner_unchecked(self) };
let ptr = Box::into_raw(this);
tracing::trace!(?ptr, "initializing local data");
unsafe {
// set up self reference
Expand Down
210 changes: 210 additions & 0 deletions hal-x86_64/src/cpu/smp.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,210 @@
use crate::{
control_regs::{Cr0, Cr4},
cpu::{
self,
msr::{Efer, Msr},
topology::{self, Processor},
Ring,
},
interrupt::apic::local::{IpiKind, IpiTarget, LocalApic},
mm::PhysPage,
segment,
};
use core::{
arch::global_asm,
sync::atomic::{AtomicU64, Ordering},
};
use hal_core::PAddr;
use mycelium_util::bits;

impl Processor {
#[tracing::instrument(name = "bringup_ap", skip(bsp_lapic), err(Display))]
pub fn bringup_ap(&mut self, bsp_lapic: &LocalApic) -> Result<(), &'static str> {
tracing::info!("bringing up application processor...");

// TODO(eliza): check that this is only called by the BSP
let trampoline_page = PhysPage::starting_at_fixed(AP_TRAMPOLINE_ADDR).map_err(|error| {
tracing::error!(addr = ?AP_TRAMPOLINE_ADDR, %error, "AP trampoline address invalid!");
"AP trampoline address invalid"
})?;

if self.is_boot_processor {
return Err(
"called `bringup_ap` on the BSP! what are you doing and how did this happen?",
);
}

if self.state != topology::State::Idle {
return Err("AP is not idle");
}

tracing::info!("sending INIT IPI to AP {}...", self.lapic_id);
bsp_lapic
.send_ipi(
IpiTarget::ApicId(self.lapic_id as u8),
IpiKind::Init { assert: true },
)
// TODO(eliza): do nice error contexts some day...
.map_err(|_| "failed to send INIT IPI")?;

tracing::info!(?trampoline_page, "sending SIPI to AP {}...", self.lapic_id);

// TODO(eliza): ensure trampoline page is mapped nicely.
unsafe {
AP_SPINLOCK.store(0, Ordering::SeqCst);
};

bsp_lapic
.send_ipi(
IpiTarget::ApicId(self.lapic_id as u8),
IpiKind::Startup(trampoline_page),
)
.map_err(|_| "failed to send SIPI")?;

// TODO(eliza): spin waiting for AP to start...
tracing::info!("waiting for AP to start...");
while unsafe { AP_SPINLOCK.load(Ordering::SeqCst) == 0 } {
// spin
tracing::trace!("waiting...");
core::hint::spin_loop();
}

self.state = topology::State::Running;
// TODO(eliza): AP should call init processor on itself...
// ap.init_processor(gdt)

Ok(())
}
}

const AP_TRAMPOLINE_ADDR: PAddr = match PAddr::from_usize_checked(0x8000) {
Ok(addr) => addr,
Err(_) => panic!("invalid AP trampoline address!"),
};

extern "C" {
#[link_name = "ap_spinlock"]
static AP_SPINLOCK: AtomicU64;
}

global_asm! {
// /!\ EXTREMELY MESSED UP HACK: stick this in the `.boot-first-stage`
// section that's defined by the `bootloader` crate's linker script, so that
// it gets linked into 16-bit memory. we don't control the linker script, so
// we can't define our own section and stick it in the right place, but we
// can piggyback off of `bootloader`'s linker script.
//
// OBVIOUSLY THIS WILL CRASH AND BURN IF YOU ARENT LINKING WITH `bootloader`
// BUT WHATEVER LOL THATS NOT MY PROBLEM,,,
".section .boot-first-stage, \"wx\"",
".code16",
".org {trampoline_addr}",
".align 4096",
".global ap_trampoline",
".global ap_trampoline_end",
".global ap_spinlock",
"ap_trampoline:",
" jmp ap_start",
" .nops 8",
"ap_spinlock: .quad 0",
"ap_pml4: .quad 0",

"ap_start:",
" cli",

// zero segment registers
" xor %ax, %ax",
" mov %ax, %ds",
" mov %ax, %es",
" mov %ax, %ss",

// initialize stack pointer to an invalid (null) value
" mov $0x0, %sp",

// setup page table
"mov (ap_pml4), %eax ",
"mov (%eax), %edi",
"mov %edi, %cr3",

// init FPU
" fninit",

// load 32-bit GDT
" lgdt (gdt32_ptr)",

// set CR4 flags
" mov %cr4, %eax",
" or {cr4flags}, %eax",
" mov %eax, %cr4",

// enable long mode in EFER
" mov {efer_num}, %ecx",
" rdmsr",
" or {efer_bits}, %eax",
" wrmsr",

// set CR0 flags to enable paging and write protection
" mov %cr0, %ebx",
" or {cr0flags}, %ebx",
" mov %ebx, %cr0",

// far jump to enable Long Mode and load CS with 64 bit segment
" jmp $gdt32_kernel_code, $ap_long_mode",
// 32-bit GDT
".align 16",
"gdt32:",
" .long 0, 0",
"gdt32_kernel_code:",
" .quad {gdt32_code}", // code segment
"gdt32_kernel_data:",
" .quad {gdt32_data}", // data segment
" .long 0x00000068, 0x00CF8900", // TSS
"gdt32_ptr:",
" .word gdt32_ptr - gdt32 - 1", // size
" .word gdt32", // offset
"ap_trampoline_end:",
".code64",
"ap_long_mode:",
" mov %rax, %ds",
" mov %rax, %es",
" mov %rax, %fs",
" mov %rax, %fs",
" mov %rax, %gs",
" mov %rax, %ss",

// set spinlock ready
// " movq $1, (ap_spinlock)",
// TODO(eliza): setup ap stack
trampoline_addr = const AP_TRAMPOLINE_ADDR.as_usize(),
cr4flags = const AP_CR4,
cr0flags = const AP_CR0,
efer_num = const Msr::ia32_efer().num,
efer_bits = const EFER_LONG_MODE,
gdt32_code = const segment::Descriptor::code_32()
.with_ring(Ring::Ring0).bits(),
gdt32_data = const segment::Descriptor::data_flat_16()
.bits(),
// spinlock_ready = const 1,
options(att_syntax)
}

/// Initial CR4 flags to set for an application processor.
const AP_CR4: u32 = bits::Pack64::pack_in(0)
.set_all(&Cr4::PAGE_SIZE_EXTENSION)
.set_all(&Cr4::PHYSICAL_ADDRESS_EXTENSION)
.set_all(&Cr4::PAGE_GLOBAL_ENABLE)
.set_all(&Cr4::OSFXSR)
.bits() as u32;

/// Initial CR0 flags to set for an application processor.
const AP_CR0: u32 = bits::Pack64::pack_in(0)
.set_all(&Cr0::PROTECTED_MODE_ENABLE)
.set_all(&Cr0::PAGING_ENABLE)
.set_all(&Cr0::WRITE_PROTECT)
.bits() as u32;

/// EFER bits to enable long mode
const EFER_LONG_MODE: u32 = bits::Pack64::pack_in(0)
.set_all(&Efer::LONG_MODE_ENABLE)
.set_all(&Efer::NO_EXECUTE_ENABLE)
.bits() as u32;
Loading