fix: bugs

This commit is contained in:
Faynot
2026-07-06 00:25:49 +03:00
parent 1b38c7f445
commit c092a81331
23 changed files with 2502 additions and 123 deletions

View File

@@ -7,25 +7,29 @@ pub static mut IDT: crate::cpu::idt::InterruptDescriptorTable = crate::cpu::idt:
pub const TLB_SHOOTDOWN_VECTOR: u8 = 0xFD;
global_asm!(
".global page_fault_stub",
"page_fault_stub:",
"push rax",
"push rcx",
"push rdx",
"push rbx",
"push rbp",
"push rsi",
"push rdi",
"push r8",
"push r9",
"push r10",
"push r11",
"push r12",
"push r13",
"push r14",
"push r15",
macro_rules! exception_stub {
($name:ident, $handler:ident) => {
concat!(
".global ", stringify!($name), "\n",
stringify!($name), ":\n",
"push rax\npush rcx\npush rdx\npush rbx\npush rbp\n",
"push rsi\npush rdi\npush r8\npush r9\npush r10\n",
"push r11\npush r12\npush r13\npush r14\npush r15\n",
"mov rdi, [rsp + 15*8]\n",
"call ", stringify!($handler), "\n",
"pop r15\npop r14\npop r13\npop r12\npop r11\n",
"pop r10\npop r9\npop r8\npop rdi\npop rsi\n",
"pop rbp\npop rbx\npop rdx\npop rcx\npop rax\n",
"add rsp, 8\n",
"iretq\n",
)
};
}
global_asm!(
exception_stub!(page_fault_stub, rust_page_fault_handler),
exception_stub!(gpf_stub, rust_gpf_handler),
exception_stub!(double_fault_stub, rust_double_fault_handler),
".global tlb_shootdown_stub",
"tlb_shootdown_stub:",
"push rax",
@@ -43,12 +47,7 @@ global_asm!(
"push r13",
"push r14",
"push r15",
"call rust_tlb_shootdown_handler",
"mov rdi, [rsp + 15*8]",
"call rust_page_fault_handler",
"pop r15",
"pop r14",
"pop r13",
@@ -64,36 +63,202 @@ global_asm!(
"pop rdx",
"pop rcx",
"pop rax",
"add rsp, 8",
"iretq"
);
unsafe extern "C" {
fn page_fault_stub();
fn gpf_stub();
fn double_fault_stub();
fn tlb_shootdown_stub();
}
pub fn init_idt() {
pub fn init_early_exceptions() {
unsafe {
let idt_mut_ptr = core::ptr::addr_of_mut!(IDT);
(*idt_mut_ptr).set_handler(14, page_fault_stub as u64);
let idt = core::ptr::addr_of_mut!(IDT);
let early: [u64; 32] = [
early_handler_0 as u64, early_handler_1 as u64,
early_handler_2 as u64, early_handler_3 as u64,
early_handler_4 as u64, early_handler_5 as u64,
early_handler_6 as u64, early_handler_7 as u64,
early_handler_8 as u64, early_handler_9 as u64,
early_handler_10 as u64, early_handler_11 as u64,
early_handler_12 as u64, early_handler_13 as u64,
early_handler_14 as u64, early_handler_15 as u64,
early_handler_16 as u64, early_handler_17 as u64,
early_handler_18 as u64, early_handler_19 as u64,
early_handler_20 as u64, early_handler_21 as u64,
early_handler_22 as u64, early_handler_23 as u64,
early_handler_24 as u64, early_handler_25 as u64,
early_handler_26 as u64, early_handler_27 as u64,
early_handler_28 as u64, early_handler_29 as u64,
early_handler_30 as u64, early_handler_31 as u64,
];
for (v, &handler) in early.iter().enumerate() {
(*idt).set_handler(v as u8, handler);
}
// Override with proper handlers for vectors 8, 13, 14 and TLB IPI.
(*idt).set_handler(8, double_fault_stub as u64);
(*idt).set_handler(13, gpf_stub as u64);
(*idt).set_handler(14, page_fault_stub as u64);
(*idt).set_handler(TLB_SHOOTDOWN_VECTOR, tlb_shootdown_stub as u64);
(*idt_mut_ptr).set_handler(TLB_SHOOTDOWN_VECTOR, tlb_shootdown_stub as u64);
let idt_static_ref: &'static crate::cpu::idt::InterruptDescriptorTable = &*core::ptr::addr_of!(IDT);
idt_static_ref.load();
let ptr: &'static crate::cpu::idt::InterruptDescriptorTable = &*core::ptr::addr_of!(IDT);
ptr.load();
}
}
pub fn process_deferred_mmu_events() {
let mut vmm_guard = KERNEL_SPACE.lock();
if let Some(space) = vmm_guard.as_mut() {
space.process_pending_revocations();
pub fn init_idt() {
// init_early_exceptions already loaded the IDT; this just overrides
// vectors that the full kernel needs. Calling lidt again is harmless.
unsafe {
let idt = core::ptr::addr_of_mut!(IDT);
(*idt).set_handler(14, page_fault_stub as u64);
(*idt).set_handler(TLB_SHOOTDOWN_VECTOR, tlb_shootdown_stub as u64);
let ptr: &'static crate::cpu::idt::InterruptDescriptorTable = &*core::ptr::addr_of!(IDT);
ptr.load();
}
}
#[unsafe(no_mangle)]
pub extern "C" fn rust_gpf_handler(error_code: u64) -> ! {
crate::debug::serial::write_global(format_args!(
"\n!!! GENERAL PROTECTION FAULT !!! error_code={:#x}\n\
CPU halted.\n",
error_code
));
loop {
unsafe { asm!("cli; hlt", options(nomem, nostack, preserves_flags)); }
}
}
#[unsafe(no_mangle)]
pub extern "C" fn rust_double_fault_handler(error_code: u64) -> ! {
crate::debug::serial::write_global(format_args!(
"\n!!! DOUBLE FAULT !!! error_code={:#x}\n\
CPU halted.\n",
error_code
));
loop {
unsafe { asm!("cli; hlt", options(nomem, nostack, preserves_flags)); }
}
}
global_asm!(r#"
.altmacro
.macro early_stub vec
.globl early_handler_\vec
.balign 16
early_handler_\vec:
push 0 /* dummy error code */
push \vec /* vector number */
jmp early_common
.endm
early_stub 0
early_stub 1
early_stub 2
early_stub 3
early_stub 4
early_stub 5
early_stub 6
early_stub 7
early_stub 8
early_stub 9
early_stub 10
early_stub 11
early_stub 12
early_stub 13
early_stub 14
early_stub 15
early_stub 16
early_stub 17
early_stub 18
early_stub 19
early_stub 20
early_stub 21
early_stub 22
early_stub 23
early_stub 24
early_stub 25
early_stub 26
early_stub 27
early_stub 28
early_stub 29
early_stub 30
early_stub 31
early_common:
push rax
push rcx
push rdx
push rbx
push rbp
push rsi
push rdi
push r8
push r9
push r10
push r11
push r12
push r13
push r14
push r15
mov rdi, [rsp + 15*8] /* vector number */
mov rsi, [rsp + 16*8] /* error code (or dummy 0) */
call rust_early_exception_handler
/* never returns */
"#);
unsafe extern "C" {
fn early_handler_0();
fn early_handler_1();
fn early_handler_2();
fn early_handler_3();
fn early_handler_4();
fn early_handler_5();
fn early_handler_6();
fn early_handler_7();
fn early_handler_8();
fn early_handler_9();
fn early_handler_10();
fn early_handler_11();
fn early_handler_12();
fn early_handler_13();
fn early_handler_14();
fn early_handler_15();
fn early_handler_16();
fn early_handler_17();
fn early_handler_18();
fn early_handler_19();
fn early_handler_20();
fn early_handler_21();
fn early_handler_22();
fn early_handler_23();
fn early_handler_24();
fn early_handler_25();
fn early_handler_26();
fn early_handler_27();
fn early_handler_28();
fn early_handler_29();
fn early_handler_30();
fn early_handler_31();
}
#[unsafe(no_mangle)]
pub extern "C" fn rust_early_exception_handler(vector: u64, _error_code: u64) -> ! {
crate::debug::serial::write_global(format_args!(
"\n!!! EARLY EXCEPTION !!! vector={} error_code={:#x}\n\
CPU halted.\n",
vector, _error_code
));
loop {
unsafe { asm!("cli; hlt", options(nomem, nostack, preserves_flags)); }
}
}
#[unsafe(no_mangle)]
pub extern "C" fn rust_page_fault_handler(error_code: u64) {
let fault_addr: u64;
@@ -105,13 +270,18 @@ pub extern "C" fn rust_page_fault_handler(error_code: u64) {
let present = (error_code & 0x1) != 0;
let virt_addr = VirtAddr(fault_addr);
process_deferred_mmu_events();
// Acquire KERNEL_SPACE lock once for both deferred events and fault handling.
// TODO SMP: switch to RwLock so concurrent read-only faults are not
// serialised. `handle_fault` is read-only for COW faults but mutates
// the VMA tree on demand-paging — a lock-free VMA tree or per-region
// locks would be ideal.
let mut vmm_guard = KERNEL_SPACE.lock();
if let Some(space) = vmm_guard.as_mut() {
space.process_pending_revocations();
match space.handle_fault(virt_addr, write) {
Ok(_) => return,
Ok(_) => {},
Err(e) => {
panic!(
"KERNEL PANIC: Unprocessed failure of virtual memory (Page Fault)!\n\

View File

@@ -36,15 +36,13 @@ macro_rules! log {
let _ = writeln!($console, $($arg)*);
// Serial debug output
let mut sp = unsafe { $crate::debug::serial::SerialPort::init() };
let _ = writeln!(
sp,
"{}[{:>5}]\x1b[0m {:<8} | {}",
$crate::debug::serial::write_global(format_args!(
"{}[{:>5}]\x1b[0m {:<8} | {}\n",
$level.serial_color_code(),
"LOG",
$module,
format_args!($($arg)*)
);
));
}};
}

View File

@@ -1,4 +1,6 @@
use core::arch::asm;
use core::fmt::Write;
use crate::mem::allocator::Locked;
pub struct SerialPort(u16);
@@ -7,16 +9,14 @@ impl SerialPort {
pub unsafe fn init() -> Self {
let port = Self::COM1;
// В новых версиях Rust даже внутри unsafe fn
// вызовы других unsafe функций требуют явного блока
unsafe {
outb(port + 1, 0x00); // Disable interrupts
outb(port + 3, 0x80); // Enable DLAB
outb(port + 0, 0x03); // Divisor 3 (38400 baud)
outb(port + 1, 0x00);
outb(port + 3, 0x03); // 8 bits, no parity, 1 stop bit
outb(port + 2, 0xC7); // Enable FIFO
outb(port + 4, 0x0B); // IRQs enabled, RTS/DSR set
outb(port + 3, 0x80);
outb(port + 0, 0x03);
outb(port + 1, 0x00);
outb(port + 3, 0x03);
outb(port + 2, 0xC7);
outb(port + 4, 0x0B);
}
SerialPort(port)
}
@@ -38,6 +38,20 @@ impl core::fmt::Write for SerialPort {
}
}
static SERIAL_PORT: Locked<Option<SerialPort>> = Locked::new(None);
pub fn init_global() {
let mut guard = SERIAL_PORT.lock();
*guard = Some(unsafe { SerialPort::init() });
}
pub fn write_global(args: core::fmt::Arguments) {
let mut guard = SERIAL_PORT.lock();
if let Some(ref mut sp) = *guard {
let _ = sp.write_fmt(args);
}
}
unsafe fn outb(port: u16, val: u8) {
unsafe {
asm!("out dx, al", in("dx") port, in("al") val, options(nomem, nostack, preserves_flags));

View File

@@ -180,18 +180,31 @@ unsafe extern "C" fn kmain() -> ! {
let mut console = tty::Console::new(&fb, KERNEL_FONT);
console.clear();
debug::serial::init_global();
cpu::interrupts::init_early_exceptions();
info!(console, "BOOT", "LIS4 Kernel Starting...");
unsafe { mem::pmm::BitmapPMM::init(&mmap_res, hhdm_offset); }
info!(console, "MEM", "Primary Physical Memory Manager (BitmapPMM) initialized.");
cpu::lapic::init(hhdm_offset);
info!(console, "LAPIC", "Local APIC initialized.");
// debug: locate the free page
info!(console, "BOOT", "alloc_frame...");
let p4_phys = mem::pmm::alloc_frame().expect("OOM: Failed to allocate P4 table");
let p4 = unsafe { &mut *p4_phys.to_virt(hhdm_offset).as_mut_ptr::<PageTable>() };
info!(console, "BOOT", "alloc_frame ok: phys=0x{:x}", p4_phys.0);
let virt = p4_phys.to_virt(hhdm_offset);
info!(console, "BOOT", "virt=0x{:x}", virt.0);
let p4 = unsafe { &mut *virt.as_mut_ptr::<PageTable>() };
info!(console, "BOOT", "zeroing page...");
unsafe { core::ptr::write_bytes(p4 as *mut _ as *mut u8, 0, 4096); }
info!(console, "BOOT", "zero done");
let flags = PageTableFlags::PRESENT | PageTableFlags::WRITABLE;
for entry in mmap_res.entries() {
for (i, entry) in mmap_res.entries().iter().enumerate() {
let phys = PhysAddr(entry.base);
let virt_hhdm = phys.to_virt(hhdm_offset);
p4.map_region(virt_hhdm, phys, entry.length, flags, hhdm_offset);
@@ -379,6 +392,7 @@ unsafe extern "C" fn kmain() -> ! {
}
fn hcf() -> ! {
unsafe { asm!("cli", options(nomem, nostack, preserves_flags)); }
loop {
unsafe { asm!("hlt", options(nomem, nostack, preserves_flags)); }
}
@@ -386,7 +400,6 @@ fn hcf() -> ! {
#[panic_handler]
fn rust_panic(info: &core::panic::PanicInfo) -> ! {
let mut sp = unsafe { debug::serial::SerialPort::init() };
let _ = writeln!(sp, "KERNEL PANIC: {:?}", info);
debug::serial::write_global(format_args!("KERNEL PANIC: {:?}\n", info));
hcf();
}

View File

@@ -55,8 +55,14 @@ struct ListNode {
const BLOCK_SIZES: &[usize] = &[8, 16, 32, 64, 128, 256, 512, 1024, 2048];
struct LargeBlockNode {
size: usize,
next: Option<&'static mut LargeBlockNode>,
}
pub struct SlabAllocator {
list_heads: [Option<&'static mut ListNode>; BLOCK_SIZES.len()],
large_block_free: Option<&'static mut LargeBlockNode>,
heap_start: usize,
heap_end: usize,
next_bump: usize,
@@ -66,6 +72,7 @@ impl SlabAllocator {
pub const fn new() -> Self {
Self {
list_heads: [None, None, None, None, None, None, None, None, None],
large_block_free: None,
heap_start: 0,
heap_end: 0,
next_bump: 0,
@@ -84,11 +91,28 @@ impl SlabAllocator {
}
fn fallback_alloc(&mut self, layout: Layout) -> *mut u8 {
let size = layout.size().max(layout.align());
if size > 2048 {
let mut field: *mut Option<&'static mut LargeBlockNode> = &mut self.large_block_free;
unsafe {
while let Some(ref mut node) = *field {
if node.size >= size {
let node_ptr: *mut LargeBlockNode = *node;
let next = node.next.take();
*field = next;
return node_ptr as *mut u8;
}
field = &mut node.next;
}
}
}
let alloc_start = (self.next_bump + layout.align() - 1) & !(layout.align() - 1);
let alloc_end = alloc_start.checked_add(layout.size()).unwrap_or(self.heap_end + 1);
if alloc_end > self.heap_end {
null_mut() // Out of memory
null_mut()
} else {
self.next_bump = alloc_end;
alloc_start as *mut u8
@@ -139,6 +163,17 @@ unsafe impl GlobalAlloc for Locked<SlabAllocator> {
}
}
None => {
// Large block: add to free list for reuse
let size = layout.size().max(layout.align());
let new_node = LargeBlockNode {
size,
next: allocator.large_block_free.take(),
};
let new_node_ptr = ptr as *mut LargeBlockNode;
unsafe {
new_node_ptr.write(new_node);
allocator.large_block_free = Some(&mut *new_node_ptr);
}
}
}
}

View File

@@ -94,7 +94,6 @@ pub enum PMRequest {
size_pages: usize,
channel_id: u16,
},
/// Sentinel — never pushed onto the queue; result of unpack(0).
None,
}
@@ -160,30 +159,18 @@ impl PMRequest {
// Response type
/// Result returned by `PMActor::process_messages()` for each completed request.
#[derive(Debug, Clone, Copy)]
pub struct PMResponse {
/// Channel to route this response to. `0` = discard.
pub channel_id: u16,
/// The actual outcome.
pub result: PMResult,
}
/// Outcome of a single PM operation.
#[derive(Debug, Clone, Copy)]
pub enum PMResult {
/// Memory was allocated. `cap` is the strong capability to the region.
/// `order` is the buddy order — **must** be passed back to `Free`.
Allocated { cap: Capability, order: usize },
/// The actor had insufficient free pages.
/// Future: ballooning subsystem intercepts this and retries.
OutOfMemory { size_pages: usize },
/// A fixed sub-region was carved successfully.
Carved { cap: Capability },
/// Free completed (no capability issued — memory returned to buddy pool).
Freed { pages_returned: usize },
}
@@ -194,7 +181,6 @@ pub enum PMResult {
/// `pop` — exactly one consumer, no locking on the read side.
pub struct PMActorQueue {
buffer: [AtomicU64; QUEUE_SIZE],
/// Pad to separate producer-written `tail` from consumer-read `head`.
_pad0: [u8; 64],
head: AtomicUsize,
_pad1: [u8; 64],
@@ -269,15 +255,10 @@ impl PMActorQueue {
/// Only `process_messages()` ever mutates `buddy` and `queue.head`.
/// This is enforced by taking `&mut self` on `process_messages`.
pub struct PMActor {
/// Unique identity within the actor federation.
pub actor_id: u64,
/// Root strong capability over the entire managed physical range.
pub root_untyped: Capability,
/// `(inclusive_start, exclusive_end)` physical addresses.
pub managed_range: (PhysAddr, PhysAddr),
/// Inbox — producers write here, actor reads here.
queue: PMActorQueue,
/// Local buddy allocator. Only ever touched in `process_messages`.
buddy: BuddyAllocator,
}
@@ -325,10 +306,16 @@ impl PMActor {
/// `buddy` and `queue.head` are mutated — no other thread touches them.
/// The only shared state is the queue's `tail`, which is written by producers
/// via `AtomicUsize::compare_exchange_weak`, never by this path.
/// Maximum requests to process in a single `process_messages` call.
/// Prevents kernel starvation when the inbox is deep.
const MAX_MESSAGES_PER_CALL: usize = 64;
pub fn process_messages(&mut self) -> Vec<PMResponse> {
let mut responses = Vec::new();
let mut remaining = Self::MAX_MESSAGES_PER_CALL;
while let Some(req) = self.queue.pop() {
remaining -= 1;
let resp = match req {
PMRequest::Allocate { size_pages, token_sig, channel_id } => {
self.handle_allocate(size_pages, token_sig, channel_id)
@@ -342,12 +329,13 @@ impl PMActor {
PMRequest::None => continue,
};
// Only push responses that need routing.
// Free responses (channel_id == 0) are still pushed so callers can
// audit completion if needed; they may simply drop them.
if let Some(r) = resp {
responses.push(r);
}
if remaining == 0 {
break;
}
}
responses
@@ -434,9 +422,6 @@ impl PMActor {
self.buddy.free(local_frame_idx, order);
// Free never needs routing — return None to skip Vec push, or push with
// channel_id=0 for audit purposes. We skip to avoid unnecessary allocation.
// Callers that need a Free-complete signal should use a separate mechanism.
None
}
@@ -446,7 +431,6 @@ impl PMActor {
size_pages: usize,
channel_id: u16,
) -> Option<PMResponse> {
// Validate offset + size within range.
let range_pages = self.buddy.total_pages();
if offset_pages >= range_pages
|| size_pages == 0

View File

@@ -69,12 +69,21 @@ pub fn init() {
#[inline(always)]
pub fn get_router() -> &'static PMRouter {
// INVARIANT: `is_ready` is set once in `init()` and never cleared.
// The Acquire load on `is_ready` synchronises-with the Release store in
// `init()`, making the `Option::Some` write visible. Because nothing
// ever writes `None` or clears `is_ready`, the TOCTOU window between
// the load and the `unwrap_unchecked` is safe.
if ROUTER.is_ready.load(Ordering::Acquire) {
unsafe {
(*ROUTER.inner.get()).as_ref().unwrap_unchecked()
}
} else {
panic!("FATAL: PMRouter is accessed before initialization!")
#[cold]
fn not_initialized() -> ! {
panic!("FATAL: PMRouter is accessed before initialization!")
}
not_initialized()
}
}

View File

@@ -8,7 +8,6 @@ pub struct BitmapPMM {
ref_counts: &'static mut [u16],
total_pages: usize,
used_pages: usize,
/// Byte index hint: next search starts here to amortise O(N) scans.
last_byte: usize,
}
@@ -59,6 +58,7 @@ impl BitmapPMM {
last_byte: 0,
};
// Free all USABLE pages (gap pages not covered by any entry stay locked).
for entry in mmap.entries() {
if entry.entry_type == limine::memory_map::EntryType::USABLE {
for addr in (entry.base..entry.base + entry.length).step_by(PAGE_SIZE as usize) {
@@ -67,6 +67,7 @@ impl BitmapPMM {
}
}
// Metadata pages sit inside a USABLE region — lock them back.
let meta_end = (meta_phys + total_meta_size as u64 + PAGE_SIZE - 1) & !(PAGE_SIZE - 1);
for addr in (meta_phys..meta_end).step_by(PAGE_SIZE as usize) {
pmm.lock_frame(PhysAddr(addr));

View File

@@ -56,7 +56,7 @@ fn local_tlb_flush_asid(asid: u16) {
unsafe {
core::arch::asm!(
"invpcid {ty}, [{desc}]",
ty = in(reg) 1u64, // type 1 = single-context flush
ty = in(reg) 1u64,
desc = in(reg) &desc,
options(nostack, preserves_flags),
);
@@ -69,24 +69,14 @@ fn local_tlb_flush_asid(asid: u16) {
// Error type
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum VmError {
/// PMM returned `None` — no physical frames available.
OutOfMemory,
/// The requested virtual range overlaps an existing VMA.
RegionOverlap,
/// No VMA covers the given address.
RegionNotFound,
/// Address or size is not a multiple of 4096.
InvalidAlignment,
/// Size is zero, or `virt + size` would overflow.
InvalidRange,
/// Write fault on a read-only VMA, or exec fault on a NX VMA.
PermissionDenied,
/// Page-fault in a non-lazy (already-eager or fixed) region — hardware bug
/// or an exploit attempt; the faulting task must be killed.
UnexpectedFault,
/// Address outside the x86-64 canonical range.
NonCanonical,
/// ASID pool exhausted (> 4094 simultaneous address spaces).
AsidExhausted,
}
@@ -143,23 +133,10 @@ impl VmaFlags {
#[derive(Debug)]
pub enum VmaBacking {
/// Anonymous pages (stack, heap, BSS).
/// Index `i` → frame for `virt_start + i * 4096`. `None` = not yet faulted in.
Anonymous(Vec<Option<PhysAddr>>),
/// Fixed physical range. Frames are **not** freed on unmap.
/// Used for MMIO, identity-mapped RAM, framebuffer, DMA buffers.
Physical(PhysAddr),
/// Zero-copy borrow of another actor's frames.
///
/// `phys_base` is the physical address of the first page; the mapping covers
/// exactly `(virt_end - virt_start) / 4096` pages.
/// Frames are owned by `owner_cap` and **never** freed by this VMA.
Shared {
/// Token of the capability that owns the frames.
owner_cap: u64,
/// Physical base (first page of the shared region).
phys_base: PhysAddr,
},
}
@@ -251,13 +228,13 @@ impl AsidAllocator {
}
}
}
None // pool truly exhausted
None
}
/// Return an ASID to the pool.
fn free(&mut self, asid: u16) {
if asid == 0 || asid >= 4095 {
return; // sentinel values — never freed
return;
}
let word = asid as usize / 32;
let bit = asid as usize % 32;
@@ -498,15 +475,6 @@ impl AddressSpace {
Self { asid, pml4_phys, regions: Vec::new(), hhdm }
}
// ─── Anonymous / fixed mapping ─────────────────────────────────────────
/// Map `size` bytes of virtual space starting at `virt`.
///
/// | `phys` | `VmaFlags::LAZY` | Behaviour |
/// |-----------|------------------|-----------------------------------------------|
/// | `Some(p)` | any | Fixed physical (MMIO / identity / DMA) |
/// | `None` | not set | Eager anonymous — allocate + zero + map now |
/// | `None` | set | Lazy anonymous — map frames on first fault |
pub fn map_region(
&mut self,
virt: VirtAddr,
@@ -645,6 +613,8 @@ impl AddressSpace {
let old_frame = frames[page_idx].expect("COW fault on unmapped page");
let new_frame = pmm::alloc_frame().ok_or(VmError::OutOfMemory)?;
debug_assert!(old_frame != new_frame, "COW: old and new frame are the same!");
unsafe {
core::ptr::copy_nonoverlapping(
old_frame.to_virt(hhdm).as_ptr::<u8>(),
@@ -793,7 +763,15 @@ pub fn tlb_flush_asid(asid: u16) {
let active_cpus = ACTIVE_CPUS_MASK.load(Ordering::Acquire);
let current_core = crate::cpu::lapic::current_core_id();
let target_mask = active_cpus & !(1u64 << current_core);
// u64 can only represent cores 063. APIC IDs may be >= 64 (CPUID
// returns up to 255), so guard the shift to avoid UB (panic in debug,
// wrap in release). When SMP with >64 cores is implemented, switch to
// a wider mask or a dynamic list of active APIC IDs.
let target_mask = if current_core < 64 {
active_cpus & !(1u64 << current_core)
} else {
active_cpus
};
if target_mask == 0 {
return;
@@ -830,7 +808,12 @@ pub fn handle_tlb_shootdown_ipi() {
local_tlb_flush_asid(asid);
let current_core = crate::cpu::lapic::current_core_id();
SHOOTDOWN_ACK.fetch_or(1u64 << current_core, Ordering::AcqRel);
// Only cores < 64 can ACK in a u64 mask. Cores >= 64 are not
// representable; their ACK would wrap and corrupt the mask.
// TODO: widen to u128 or use per-core ACK slots for >64 core SMP.
if current_core < 64 {
SHOOTDOWN_ACK.fetch_or(1u64 << current_core, Ordering::AcqRel);
}
}
// Global kernel address space