feat: ring3

This commit is contained in:
Zoe
2026-08-28 10:14:39 -05:00
parent 8a71cdfcc5
commit b1f9dc0f8a
12 changed files with 458 additions and 120 deletions
-8
View File
@@ -127,14 +127,6 @@ impl LocalApic {
return Err(LocalApicError::NotBootSystemProcessor);
}
println!("BSP: {}", bsp);
println!("x2Apic: {}", x2apix);
println!("enabled: {}", enabled);
println!(
"xApic physical address: {:x}",
xapic_physical_addr.as_usize()
);
let access = match x2apix {
true => LocalApicAccess::X2Apic,
false => {
+24 -4
View File
@@ -11,6 +11,7 @@ pub enum CpuFeaturesError {
pub(crate) struct CpuFeatures {
pub nx_supported: bool,
pub nx_enabled: bool,
pub global_pages: bool,
pub physical_address_bits: u8,
pub virtual_address_bits: u8,
pub five_level_paging_active: bool,
@@ -23,22 +24,23 @@ pub fn detect_features_and_enable() -> Result<CpuFeatures, CpuFeaturesError> {
let mut features = CpuFeatures {
nx_supported: false,
nx_enabled: false,
global_pages: false,
physical_address_bits: 0,
virtual_address_bits: 0,
five_level_paging_active: false,
};
let cpuid_result = core::arch::x86_64::__cpuid_count(0x80000000, 0);
let cpuid_result = core::arch::x86_64::__cpuid(0x80000000);
if cpuid_result.eax < 0x80000008 {
return Err(CpuFeaturesError::CpuidFeaturesNotSupported);
}
let cpuid_result = core::arch::x86_64::__cpuid_count(0x80000001, 0);
let cpuid_result = core::arch::x86_64::__cpuid(0x80000001);
features.nx_supported = cpuid_result.edx & (1 << 20) != 0;
let cpuid_result = core::arch::x86_64::__cpuid_count(0x80000008, 0);
let cpuid_result = core::arch::x86_64::__cpuid(0x80000008);
features.physical_address_bits = (cpuid_result.eax & 0xFF) as u8;
if !(12..=52).contains(&features.physical_address_bits) {
@@ -57,8 +59,16 @@ pub fn detect_features_and_enable() -> Result<CpuFeatures, CpuFeaturesError> {
return Err(CpuFeaturesError::InvalidVirtualAddressWidth);
}
let cpuid_result = core::arch::x86_64::__cpuid(0x1);
features.global_pages = cpuid_result.edx & (1 << 13) != 0;
if features.global_pages {
let cr4 = read_cr4();
write_cr4(cr4 | 1 << 7);
}
if features.nx_supported {
let cpuid_result = core::arch::x86_64::__cpuid_count(0x1, 0);
let msr_supported = cpuid_result.edx & (1 << 5) != 0;
if !msr_supported {
@@ -78,6 +88,16 @@ pub fn detect_features_and_enable() -> Result<CpuFeatures, CpuFeaturesError> {
Ok(features)
}
fn write_cr4(value: usize) {
unsafe {
asm!(
"mov cr4, {}",
in(reg) value,
options(nostack, preserves_flags)
);
}
}
fn read_cr4() -> usize {
let value: usize;
+35 -6
View File
@@ -1,13 +1,15 @@
use core::arch::asm;
use crate::memory::VirtualAddr;
#[repr(C, align(8))]
struct Gdt {
entries: [u64; 5],
entries: [u64; 7],
}
impl Gdt {
pub const fn new() -> Self {
Self { entries: [0; 5] }
Self { entries: [0; 7] }
}
}
@@ -47,7 +49,9 @@ const DOUBLE_FAULT_STACK_SIZE: usize = 16 * 1024;
pub(super) const KERNEL_CODE_SELECTOR: u16 = 1 * 8;
pub(super) const KERNEL_DATA_SELECTOR: u16 = 2 * 8;
pub(super) const TSS_SELECTOR: u16 = 3 * 8;
pub(super) const USER_CODE_SELECTOR: u16 = (3 * 8) | 3;
pub(super) const USER_DATA_SELECTOR: u16 = (4 * 8) | 3;
pub(super) const TSS_SELECTOR: u16 = 5 * 8;
#[repr(align(16))]
#[allow(dead_code)] // field 0 is read, rust just cant tell
@@ -74,6 +78,8 @@ pub fn init() {
0,
kernel_code_descriptor(),
kernel_data_descriptor(),
user_code_descriptor(),
user_data_descriptor(),
tss_low,
tss_high,
],
@@ -84,6 +90,12 @@ pub fn init() {
}
}
pub fn set_kernel_stack(stack_top: VirtualAddr) {
unsafe {
TSS.privilege_stacks[0] = stack_top.as_usize() as u64;
}
}
fn gdt_reload() {
unsafe {
asm!(
@@ -119,17 +131,34 @@ fn gdt_reload() {
}
const PRESENT: u64 = 1 << 47;
const USER_DESCRIPTOR: u64 = 1 << 44;
const CODE_DATA_DESCRIPTOR: u64 = 1 << 44;
const USER_PRIVILEGE: u64 = 3 << 45;
const EXECUTABLE: u64 = 1 << 43;
const READ_WRITE: u64 = 1 << 41;
const GRANULARITY: u64 = 1 << 55;
const SIZE: u64 = 1 << 54;
const LONG_MODE: u64 = 1 << 53;
fn kernel_code_descriptor() -> u64 {
PRESENT | USER_DESCRIPTOR | EXECUTABLE | READ_WRITE | LONG_MODE
PRESENT | CODE_DATA_DESCRIPTOR | EXECUTABLE | READ_WRITE | LONG_MODE | GRANULARITY
}
fn kernel_data_descriptor() -> u64 {
PRESENT | USER_DESCRIPTOR | READ_WRITE
PRESENT | CODE_DATA_DESCRIPTOR | READ_WRITE | SIZE | GRANULARITY
}
fn user_code_descriptor() -> u64 {
PRESENT
| CODE_DATA_DESCRIPTOR
| USER_PRIVILEGE
| EXECUTABLE
| READ_WRITE
| LONG_MODE
| GRANULARITY
}
fn user_data_descriptor() -> u64 {
PRESENT | CODE_DATA_DESCRIPTOR | USER_PRIVILEGE | READ_WRITE | SIZE | GRANULARITY
}
fn tss_descriptor(tss: *const TaskStateSegment) -> [u64; 2] {
+12 -1
View File
@@ -46,11 +46,20 @@ fatal_with_error_code!(stack_segment_fault_handler, "STACK-SEGMENT FAULT");
fatal_with_error_code!(general_protection_handler, "GENERAL PROTECTION FAULT");
fatal_with_error_code!(alignment_check_handler, "ALIGNMENT CHECK");
extern "x86-interrupt" fn user_test_exit_handler(frame: InterruptStackFrame) {
if frame.code_segment & 0b11 != 3 {
panic!("user_test_exit_handler called from kernel");
}
println!("User test exit");
hcf();
}
pub(super) fn install(idt: &mut idt::Idt) {
idt.set_handler(0, divide_error_handler, 0);
idt.set_handler(1, debug_handler, 0);
idt.set_handler(2, non_maskable_interrupt_handler, 0);
idt.set_handler(3, breakpoint_handler, 0);
idt.set_user_handler(3, breakpoint_handler, 0);
idt.set_handler(6, invalid_opcode_handler, 0);
idt.set_handler(7, device_not_available_handler, 0);
idt.set_error_code_handler(8, double_fault_handler, 1);
@@ -63,6 +72,8 @@ pub(super) fn install(idt: &mut idt::Idt) {
idt.set_error_code_handler(17, alignment_check_handler, 0);
idt.set_handler(18, machine_check_handler, 0);
idt.set_handler(19, simd_floating_point_handler, 0);
idt.set_user_handler(0x80, user_test_exit_handler, 0);
}
fn read_cr2() -> u64 {
+11 -5
View File
@@ -20,7 +20,7 @@ impl IdtEntry {
const fn missing() -> Self {
return Self {
offset_low: 0,
code_selector: 0x08,
code_selector: 0,
ist: 0,
attributes: 0,
offset_middle: 0,
@@ -49,6 +49,8 @@ pub(super) struct InterruptStackFrame {
const INTERRUPT_GATE: u8 = 0b1110;
const PRESENT: u8 = 1 << 7;
const KERNEL_INTERRUPT_GATE: u8 = PRESENT | INTERRUPT_GATE;
const USER_DPL: u8 = 3 << 5;
const USER_INTERRUPT_GATE: u8 = PRESENT | USER_DPL | INTERRUPT_GATE;
pub(super) type Handler = extern "x86-interrupt" fn(InterruptStackFrame);
pub(super) type ErrorCodeHandler = extern "x86-interrupt" fn(InterruptStackFrame, u64);
@@ -65,7 +67,7 @@ impl Idt {
}
pub(super) fn set_handler(&mut self, vector: u8, handler: Handler, ist: u8) {
self.set_handler_address(vector, handler as usize, ist);
self.set_handler_address(vector, handler as usize, ist, KERNEL_INTERRUPT_GATE);
}
pub(super) fn set_error_code_handler(
@@ -74,17 +76,21 @@ impl Idt {
handler: ErrorCodeHandler,
ist: u8,
) {
self.set_handler_address(vector, handler as usize, ist);
self.set_handler_address(vector, handler as usize, ist, KERNEL_INTERRUPT_GATE);
}
fn set_handler_address(&mut self, vector: u8, address: usize, ist: u8) {
pub(super) fn set_user_handler(&mut self, vector: u8, handler: Handler, ist: u8) {
self.set_handler_address(vector, handler as usize, ist, USER_INTERRUPT_GATE);
}
fn set_handler_address(&mut self, vector: u8, address: usize, ist: u8, attributes: u8) {
self.entries[vector as usize] = IdtEntry {
offset_low: address as u16,
offset_middle: (address >> 16) as u16,
offset_high: (address >> 32) as u32,
code_selector: KERNEL_CODE_SELECTOR,
ist: ist & 0b111,
attributes: KERNEL_INTERRUPT_GATE,
attributes,
reserved: 0,
};
}
+1 -7
View File
@@ -17,6 +17,7 @@ const MASKED: u32 = 1 << 16;
pub const IOAPIC_VIRTUAL_ADDRESS: VirtualAddr = VirtualAddr::new(0xFFFF_FFFD_1000_0000);
#[derive(Debug)]
#[allow(unused)]
pub enum IoApicError {
IdMismatch { expected: u8, actual: u8 },
GsiOutsideRange,
@@ -73,13 +74,6 @@ impl IoApic {
actual: id,
});
}
println!("IOAPIC ID: {:#X}", id);
println!("IOAPIC version: {:#X}", version & 0xFF);
println!(
"IOAPIC redirection entry count: {:#X}",
io_apic.redirection_entry_count
);
Ok(io_apic)
}
+35
View File
@@ -46,6 +46,7 @@ pub fn init() -> ArchState {
}
#[derive(Debug)]
#[allow(unused)]
pub enum InterruptInitError {
InvalidLocalApicId,
InvalidLocalApicAddress,
@@ -162,6 +163,8 @@ pub fn init_interrupt_controller(
/// - The stack is currently mapped, writable, and 16-byte aligned
pub unsafe fn enter_kernel(stack_top: VirtualAddr, handoff: *mut KernelHandoff) -> ! {
unsafe {
gdt::set_kernel_stack(stack_top);
asm!(
"mov rsp, {stack_top}",
"xor rbp, rbp",
@@ -175,6 +178,38 @@ pub unsafe fn enter_kernel(stack_top: VirtualAddr, handoff: *mut KernelHandoff)
};
}
/// # Safety
///
/// - `user_instruction_pointer` and `user_stack_pointer` must be valid user mappings.
/// - The active address space must contain the kernel and supplied user mappings.
pub unsafe fn enter_user(
user_instruction_pointer: VirtualAddr,
user_stack_pointer: VirtualAddr,
) -> ! {
println!("Entering user mode");
unsafe {
asm!(
"mov ds, {user_data_selector:x}",
"mov es, {user_data_selector:x}",
"mov fs, {user_data_selector:x}",
"mov gs, {user_data_selector:x}", // ss is handled by iretq
"push {user_data_selector}",
"push {user_stack_pointer}",
"pushfq",
"push {user_code_selector}",
"push {user_instruction_pointer}",
"iretq",
user_data_selector = in(reg) gdt::USER_DATA_SELECTOR as usize,
user_code_selector = in(reg) gdt::USER_CODE_SELECTOR as usize,
user_instruction_pointer = in(reg) user_instruction_pointer.as_usize(),
user_stack_pointer = in(reg) user_stack_pointer.as_usize(),
options(noreturn)
);
}
}
pub fn halt() {
unsafe {
asm!("hlt");
+54 -1
View File
@@ -14,6 +14,7 @@ pub const PAGE_TABLE_ENTRIES: usize = 512;
#[derive(Clone, Copy, Debug)]
pub struct PagingConfig {
physical_address_bits: u8,
global_pages: bool,
nx_enabled: bool,
mode: PagingMode,
}
@@ -23,6 +24,7 @@ impl PagingConfig {
Self {
physical_address_bits: features.physical_address_bits,
nx_enabled: features.nx_enabled,
global_pages: features.global_pages,
mode: if features.five_level_paging_active {
PagingMode::FiveLevel
} else {
@@ -119,6 +121,7 @@ impl PageTableEntry {
const WRITABLE: u64 = 1 << 1;
const USER_ACCESSIBLE: u64 = 1 << 2;
const HUGE_PAGE: u64 = 1 << 7;
const GLOBAL: u64 = 1 << 8;
const NX: u64 = 1 << 63;
const WRITE_THROUGH: u64 = 1 << 3;
@@ -130,6 +133,7 @@ impl PageTableEntry {
permissions: PagePermissions,
cache_policy: CachePolicy,
config: PagingConfig,
global: bool,
) -> Result<Self, PageTableEntryError> {
if physical_address.as_usize() >= config.physical_address_limit() {
return Err(PageTableEntryError::PhysicalAddressTooLarge);
@@ -151,6 +155,10 @@ impl PageTableEntry {
value |= Self::NX;
}
if global && config.global_pages {
value |= Self::GLOBAL;
}
// TODO: these bit positions very by page size
// set PAT
match cache_policy {
@@ -200,6 +208,10 @@ impl PageTableEntry {
self.0 & Self::HUGE_PAGE != 0
}
fn is_global(&self) -> bool {
self.0 & Self::GLOBAL != 0
}
fn table_frame(&self, config: PagingConfig) -> Option<FrameAddr> {
if !self.is_present() || self.is_huge() {
return None;
@@ -254,8 +266,8 @@ pub(crate) enum PageTableCreateError {
pub struct PageTable {
pub direct_map: DirectMap,
frame: OwnedFrame,
config: PagingConfig,
frame: OwnedFrame,
}
impl PageTable {
@@ -279,6 +291,10 @@ impl PageTable {
})
}
pub const fn config(&self) -> PagingConfig {
self.config
}
fn is_active(&self) -> bool {
let cr3 = unsafe { read_cr3(self.config) };
@@ -395,6 +411,7 @@ impl PageTable {
permissions: PagePermissions,
allocator: &mut FrameAllocator,
cache_policy: CachePolicy,
global: bool,
) -> Result<(), MapError> {
let address = mapped_addr.as_usize();
@@ -411,6 +428,7 @@ impl PageTable {
permissions,
cache_policy,
self.config,
global,
)
.map_err(|error| match error {
PageTableEntryError::PhysicalAddressTooLarge => MapError::PhysicalAddressTooLarge,
@@ -644,6 +662,17 @@ impl PageTable {
Ok(frame)
}
pub fn copy_kernel_mappings_to(&self, destination: &mut PageTable) {
let src = self
.table(self.frame.frame_address())
.expect("source page table outside direct map");
let dest = destination
.table_mut(destination.frame.frame_address())
.expect("destination page table outside direct map");
dest[256..512].copy_from_slice(&src[256..512]);
}
fn flush_tlb_if_active(&self, page: VirtualAddr) {
debug_assert!(self.is_canonical(page.as_usize()));
@@ -702,6 +731,30 @@ impl PageTable {
unsafe { allocator.dealloc(self.frame) };
}
pub unsafe fn destroy_user(mut self, allocator: &mut FrameAllocator) {
assert!(!self.is_active(), "attempted to destroy active page table");
let root_frame = self.frame.frame_address();
for idx in 0..256 {
let entry = self.table(root_frame).expect("table outside direct map")[idx];
if !entry.is_present() || entry.is_huge() {
continue;
}
let child = entry
.table_frame(self.config)
.expect("invalid page table entry");
self.destroy_children(child, 1, allocator);
self.table_mut(root_frame)
.expect("table outside direct map")[idx] = PageTableEntry::null();
unsafe { allocator.dealloc(OwnedFrame::from_raw(child)) };
}
unsafe { allocator.dealloc(self.frame) };
}
}
fn translate_huge_page(