Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Initializes malloc stats #973

Merged
merged 1 commit into from
Sep 8, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
10 changes: 9 additions & 1 deletion src/obconf/src/lib.rs
Original file line number Diff line number Diff line change
@@ -1,11 +1,19 @@
#![no_std]

use core::num::NonZero;

pub use self::env::*;

mod env;

/// Information about the boot environment.
/// Contains information about the boot environment.
#[repr(C)]
pub enum BootEnv {
Vm(Vm),
}

/// Runtime configurations for the kernel.
#[repr(C)]
pub struct Config {
pub max_cpu: NonZero<usize>,
}
25 changes: 14 additions & 11 deletions src/obkrnl/src/config/mod.rs
Original file line number Diff line number Diff line change
@@ -1,30 +1,33 @@
use core::ptr::null;
use macros::elf_note;
use obconf::BootEnv;
use obconf::{BootEnv, Config};

#[cfg(target_arch = "aarch64")]
pub use self::aarch64::*;
#[cfg(target_arch = "x86_64")]
pub use self::x86_64::*;
pub use self::arch::*;

#[cfg(target_arch = "aarch64")]
mod aarch64;
#[cfg(target_arch = "x86_64")]
mod x86_64;
#[cfg_attr(target_arch = "aarch64", path = "aarch64.rs")]
#[cfg_attr(target_arch = "x86_64", path = "x86_64.rs")]
mod arch;

pub fn boot_env() -> &'static BootEnv {
// SAFETY: This is safe because the set_boot_env() requirements.
// SAFETY: This is safe because the setup() requirements.
unsafe { &*BOOT_ENV }
}

pub fn config() -> &'static Config {
// SAFETY: This is safe because the setup() requirements.
unsafe { &*CONFIG }
}

/// # Safety
/// This function must be called immediately in the kernel entry point. After that it must never
/// be called again.
pub unsafe fn set_boot_env(env: &'static BootEnv) {
pub unsafe fn setup(env: &'static BootEnv, conf: &'static Config) {
BOOT_ENV = env;
CONFIG = conf;
}

static mut BOOT_ENV: *const BootEnv = null();
static mut CONFIG: *const Config = null();

#[elf_note(section = ".note.obkrnl.page-size", name = "obkrnl", ty = 0)]
static NOTE_PAGE_SIZE: [u8; size_of::<usize>()] = PAGE_SIZE.to_ne_bytes();
4 changes: 4 additions & 0 deletions src/obkrnl/src/context/aarch64.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,3 +8,7 @@ pub unsafe fn activate(_: *mut Context) {
pub unsafe fn thread() -> *const Thread {
todo!();
}

pub unsafe fn current() -> *const Context {
todo!();
}
51 changes: 44 additions & 7 deletions src/obkrnl/src/context/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,20 +12,16 @@ mod arch;
/// not safe to have a temporary a pointer or reference to this struct or its field because the CPU
/// might get interupted, which mean it is possible for the next instruction to get executed on
/// a different CPU if the interupt cause the CPU to switch the task.
///
/// We don't support `pc_cpuid` field here because it value is 100% unpredictable due to the above
/// reason. Once we have loaded `pc_cpuid` the next instruction might get executed on a different
/// CPU, which render the loaded value incorrect. The only way to prevent this issue is to disable
/// interupt before reading `pc_cpuid`, which can make the CPU missed some events from the other
/// hardwares.
pub struct Context {
cpu: usize, // pc_cpuid
thread: AtomicPtr<Thread>, // pc_curthread
}

impl Context {
/// See `pcpu_init` on the PS4 for a reference.
pub fn new(td: Arc<Thread>) -> Self {
pub fn new(cpu: usize, td: Arc<Thread>) -> Self {
Self {
cpu,
thread: AtomicPtr::new(Arc::into_raw(td).cast_mut()),
}
}
Expand All @@ -35,10 +31,28 @@ impl Context {
// it is going to be the same one since it represent the current thread.
let td = unsafe { self::arch::thread() };

// We cannot return a reference here because it requires 'static lifetime, which allow the
// caller to store it at a global level. Once the thread is destroyed that reference will be
// invalid.
unsafe { Arc::increment_strong_count(td) };
unsafe { Arc::from_raw(td) }
}

/// See `critical_enter` and `critical_exit` on the PS4 for a reference.
pub fn pin() -> PinnedContext {
// TODO: Verify if memory ordering here is correct. We need a call to self::arch::current()
// to execute after the thread is in a critical section. The CPU must not reorder this. Our
// current implementation follow how Drop on Arc is implemented.
let td = unsafe { self::arch::thread() };

unsafe { (*td).critical_sections().fetch_add(1, Ordering::Release) };
core::sync::atomic::fence(Ordering::Acquire);

// Once the thread is in a critical section it will never be switch a CPU so it is safe to
// keep a pointer to a context here.
PinnedContext(unsafe { self::arch::current() })
}

/// # Safety
/// The only place this method is safe to call is in the CPU entry point. Once this method
/// return this instance must outlive the CPU lifetime and it must never be accessed via this
Expand All @@ -54,3 +68,26 @@ impl Drop for Context {
unsafe { drop(Arc::from_raw(self.thread.load(Ordering::Relaxed))) };
}
}

/// RAII struct to pin the current thread to current CPU.
///
/// This struct must not implement [`Send`] and [`Sync`]. Currently it stored a pointer, which will
/// make it `!Send` and `!Sync`.
pub struct PinnedContext(*const Context);

impl PinnedContext {
pub fn cpu(&self) -> usize {
unsafe { (*self.0).cpu }
}
}

impl Drop for PinnedContext {
fn drop(&mut self) {
// TODO: Verify if memory ordering here is correct.
let td = unsafe { (*self.0).thread.load(Ordering::Relaxed) };

unsafe { (*td).critical_sections().fetch_sub(1, Ordering::Release) };

// TODO: Implement td_owepreempt.
}
}
26 changes: 23 additions & 3 deletions src/obkrnl/src/context/x86_64.rs
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ pub unsafe fn activate(cx: *mut Context) {
in("ecx") 0xc0000101u32,
in("edx") cx >> 32,
in("eax") cx,
options(preserves_flags, nostack)
options(nomem, preserves_flags, nostack)
);

// Clear FS and GS for user mode.
Expand All @@ -24,15 +24,15 @@ pub unsafe fn activate(cx: *mut Context) {
in("ecx") 0xc0000100u32,
in("edx") 0,
in("eax") 0,
options(preserves_flags, nostack)
options(nomem, preserves_flags, nostack)
);

asm!(
"wrmsr",
in("ecx") 0xc0000102u32,
in("edx") 0,
in("eax") 0,
options(preserves_flags, nostack)
options(nomem, preserves_flags, nostack)
);
}

Expand All @@ -50,3 +50,23 @@ pub unsafe fn thread() -> *const Thread {

td
}

pub unsafe fn current() -> *const Context {
// Load current GS.
let mut edx: u32;
let mut eax: u32;

asm!(
"rdmsr",
in("ecx") 0xc0000101u32,
out("edx") edx,
out("eax") eax,
options(pure, nomem, preserves_flags, nostack)
);

// Combine EDX and EAX.
let edx = edx as usize;
let eax = eax as usize;

((edx << 32) | eax) as *const Context
}
13 changes: 9 additions & 4 deletions src/obkrnl/src/main.rs
Original file line number Diff line number Diff line change
@@ -1,15 +1,15 @@
#![no_std]
#![cfg_attr(not(test), no_main)]

use crate::config::set_boot_env;
use crate::context::Context;
use crate::malloc::KernelHeap;
use crate::proc::Thread;
use alloc::sync::Arc;
use core::arch::asm;
use core::mem::zeroed;
use core::num::NonZero;
use core::panic::PanicInfo;
use obconf::BootEnv;
use obconf::{BootEnv, Config};

mod config;
mod console;
Expand Down Expand Up @@ -37,8 +37,13 @@ extern crate alloc;
#[allow(dead_code)]
#[cfg_attr(target_os = "none", no_mangle)]
extern "C" fn _start(env: &'static BootEnv) -> ! {
// TODO: Accept config from bootloader/hypervisor.
static CONFIG: Config = Config {
max_cpu: unsafe { NonZero::new_unchecked(1) },
};

// SAFETY: This is safe because we called it as the first thing here.
unsafe { set_boot_env(env) };
unsafe { crate::config::setup(env, &CONFIG) };

info!("Starting Obliteration Kernel.");

Expand All @@ -48,7 +53,7 @@ extern "C" fn _start(env: &'static BootEnv) -> ! {
// Setup CPU context. We use a different mechanism here. The PS4 put all of pcpu at a global
// level but we put it on each CPU stack instead.
let thread0 = Arc::new(thread0);
let mut cx = Context::new(thread0);
let mut cx = Context::new(0, thread0);

// SAFETY: We are in the main CPU entry point and we move all the remaining code after this into
// a dedicated no-return function.
Expand Down
40 changes: 36 additions & 4 deletions src/obkrnl/src/malloc/stage2.rs
Original file line number Diff line number Diff line change
@@ -1,17 +1,19 @@
use crate::config::PAGE_SIZE;
use crate::config::{config, PAGE_SIZE};
use crate::context::Context;
use crate::uma::UmaZone;
use alloc::string::ToString;
use alloc::sync::Arc;
use alloc::vec::Vec;
use core::alloc::Layout;
use core::sync::atomic::{AtomicU64, Ordering};

/// Stage 2 kernel heap.
///
/// This stage allocate a memory from a virtual memory management system. This struct is a merge of
/// `malloc_type` and `malloc_type_internal` structure.
pub struct Stage2 {
zones: [Vec<Arc<UmaZone>>; (usize::BITS - 1) as usize], // kmemsize + kmemzones
stats: Vec<Stats>, // mti_stats
}

impl Stage2 {
Expand Down Expand Up @@ -54,7 +56,14 @@ impl Stage2 {
zones
});

Self { zones }
// TODO: Is there a better way than this?
SuchAFuriousDeath marked this conversation as resolved.
Show resolved Hide resolved
let mut stats = Vec::with_capacity(config().max_cpu.get());

for _ in 0..config().max_cpu.get() {
stats.push(Stats::default());
}

Self { zones, stats }
}

/// See `malloc` on the PS4 for a reference.
Expand Down Expand Up @@ -82,8 +91,24 @@ impl Stage2 {
size
};

// TODO: There are more logic after this on the PS4.
self.zones[align][size >> Self::KMEM_ZSHIFT].alloc()
// Allocate a memory from UMA zone.
let zone = &self.zones[align][size >> Self::KMEM_ZSHIFT];
let mem = zone.alloc();

// Update stats.
let cx = Context::pin();
let stats = &self.stats[cx.cpu()];
let size = if mem.is_null() { 0 } else { zone.size() };

if size != 0 {
stats
.alloc_bytes
.fetch_add(size.try_into().unwrap(), Ordering::Relaxed);
stats.alloc_count.fetch_add(1, Ordering::Relaxed);
}

// TODO: How to update mts_size here since our zone table also indexed by alignment?
mem
} else {
todo!()
}
Expand All @@ -96,3 +121,10 @@ impl Stage2 {
todo!()
}
}

/// Implementation of `malloc_type_stats` structure.
#[derive(Default)]
struct Stats {
alloc_bytes: AtomicU64, // mts_memalloced
alloc_count: AtomicU64, // mts_numallocs
}
12 changes: 11 additions & 1 deletion src/obkrnl/src/proc/thread.rs
Original file line number Diff line number Diff line change
@@ -1,9 +1,12 @@
use core::sync::atomic::AtomicU32;

/// Implementation of `thread` structure.
///
/// All thread **must** run to completion once execution has been started otherwise resource will be
/// leak if the thread is dropped while its execution currently in the kernel space.
pub struct Thread {
active_interrupts: usize, // td_intr_nesting_level
critical_sections: AtomicU32, // td_critnest
active_interrupts: usize, // td_intr_nesting_level
}

impl Thread {
Expand All @@ -12,11 +15,18 @@ impl Thread {
/// responsibility to configure the thread after this so it have a proper states and trigger
/// necessary events.
pub unsafe fn new_bare() -> Self {
// td_critnest on the PS4 started with 1 but this does not work in our case because we use
// RAII to increase and decrease it.
Self {
critical_sections: AtomicU32::new(0),
active_interrupts: 0,
}
}

pub fn critical_sections(&self) -> &AtomicU32 {
&self.critical_sections
}

pub fn active_interrupts(&self) -> usize {
self.active_interrupts
}
Expand Down
13 changes: 10 additions & 3 deletions src/obkrnl/src/uma/mod.rs
Original file line number Diff line number Diff line change
@@ -1,12 +1,19 @@
use alloc::borrow::Cow;

/// Implementation of `uma_zone` structure.
pub struct UmaZone {}
pub struct UmaZone {
size: usize, // uz_size
}

impl UmaZone {
/// See `uma_zcreate` on the PS4 for a reference.
pub fn new(_: Cow<'static, str>, _: usize, _: usize) -> Self {
Self {}
pub fn new(_: Cow<'static, str>, size: usize, _: usize) -> Self {
// TODO: Check if size is allowed to be zero. If not, change it to NonZero<usize>.
Self { size }
}

pub fn size(&self) -> usize {
self.size
}

/// See `uma_zalloc_arg` on the PS4 for a reference.
Expand Down