Skip to content

Commit 607f70c

Browse files
committed
feat(x86_64): add PVH support
1 parent b2a28b3 commit 607f70c

17 files changed

Lines changed: 418 additions & 67 deletions

File tree

.vscode/settings.json

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -16,13 +16,13 @@
1616
"--message-format=json",
1717
"--all-targets",
1818
"-Zbuild-std=core,alloc",
19-
"--target=aarch64-unknown-none-softfloat",
20-
"--target=riscv64gc-unknown-none-elf",
19+
// "--target=aarch64-unknown-none-softfloat",
20+
// "--target=riscv64gc-unknown-none-elf",
2121
"--target=x86_64-unknown-none",
2222
],
2323
"rust-analyzer.check.targets": [
24-
"aarch64-unknown-none-softfloat",
25-
"riscv64gc-unknown-none-elf",
24+
// "aarch64-unknown-none-softfloat",
25+
// "riscv64gc-unknown-none-elf",
2626
"x86_64-unknown-none",
2727
],
2828
}

Cargo.lock

Lines changed: 11 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -338,6 +338,7 @@ features = [
338338

339339
[target.'cfg(target_arch = "x86_64")'.dependencies]
340340
free-list = { version = "0.3", features = ["x86_64"] }
341+
pvh = "0.1"
341342
raw-cpuid = "11"
342343
uart_16550 = { version = "0.6", features = ["embedded-io"] }
343344
x86_64 = "0.15"

src/arch/x86_64/kernel/mod.rs

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ use x86_64::registers::control::{Cr0, Cr4};
1212
use crate::arch::x86_64::kernel::core_local::*;
1313
use crate::env::{self, is_uhyve};
1414

15+
pub mod pvh;
1516
#[cfg(feature = "acpi")]
1617
pub mod acpi;
1718
pub mod apic;
@@ -192,7 +193,8 @@ unsafe extern "C" fn pre_init(boot_info: Option<&'static RawBootInfo>, cpu_id: u
192193
}
193194

194195
if cpu_id == 0 {
195-
env::set_boot_info(*boot_info.unwrap());
196+
// env::set_boot_info(*boot_info.unwrap());
197+
println!("Foo");
196198

197199
crate::boot_processor_main()
198200
} else {

src/arch/x86_64/kernel/pvh/gdt.rs

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
use x86_64::structures::gdt::{Descriptor, GlobalDescriptorTable, SegmentSelector};
2+
3+
const GDT_LEN: usize = 3;
4+
5+
pub(super) static GDT: GlobalDescriptorTable<GDT_LEN> = Gdt::gdt();
6+
7+
pub(super) static GDT_PTR: DescriptorTablePointer<'static, GDT_LEN> =
8+
DescriptorTablePointer::new(&GDT);
9+
10+
pub(super) struct Gdt;
11+
12+
impl Gdt {
13+
const fn create() -> (
14+
GlobalDescriptorTable<GDT_LEN>,
15+
SegmentSelector,
16+
SegmentSelector,
17+
) {
18+
let mut gdt = GlobalDescriptorTable::empty();
19+
let kernel_code_selector = gdt.append(Descriptor::kernel_code_segment());
20+
let kernel_data_selector = gdt.append(Descriptor::kernel_data_segment());
21+
(gdt, kernel_code_selector, kernel_data_selector)
22+
}
23+
24+
pub(super) const fn gdt() -> GlobalDescriptorTable<GDT_LEN> {
25+
Self::create().0
26+
}
27+
28+
pub(super) const fn kernel_code_selector() -> SegmentSelector {
29+
Self::create().1
30+
}
31+
32+
pub(super) const fn kernel_data_selector() -> SegmentSelector {
33+
Self::create().2
34+
}
35+
}
36+
37+
#[derive(Debug, Clone, Copy)]
38+
#[repr(C, packed(2))]
39+
pub(super) struct DescriptorTablePointer<'a, const MAX: usize> {
40+
limit: u16,
41+
base: &'a GlobalDescriptorTable<MAX>,
42+
}
43+
44+
impl<'a, const MAX: usize> DescriptorTablePointer<'a, MAX> {
45+
const fn new(gdt: &'a GlobalDescriptorTable<MAX>) -> Self {
46+
Self {
47+
limit: gdt.limit(),
48+
base: gdt,
49+
}
50+
}
51+
}

src/arch/x86_64/kernel/pvh/mod.rs

Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,76 @@
1+
mod gdt;
2+
mod page_tables;
3+
mod stack;
4+
5+
use core::sync::atomic::{AtomicU32, Ordering};
6+
7+
use hermit_entry::boot_info::{BootInfo, HardwareInfo, LoadInfo, PlatformInfo, SerialPortBase};
8+
use pvh::start_info::reader::{IdentityMap, StartInfoReader};
9+
10+
use self::stack::{STACK, Stack};
11+
use crate::env::setboot_info2;
12+
use crate::kernel::pre_init;
13+
14+
/// The PVH entry point.
15+
#[unsafe(naked)]
16+
pub(crate) unsafe extern "C" fn pvh_start32() -> ! {
17+
core::arch::naked_asm!(
18+
".code32",
19+
include_str!("pvh_start32.s"),
20+
".code64",
21+
22+
level_4_table = sym page_tables::LEVEL_4_TABLE,
23+
gdt_ptr = sym gdt::GDT_PTR,
24+
kernel_data_selector = const gdt::Gdt::kernel_data_selector().0,
25+
26+
stack = sym STACK,
27+
stack_size = const size_of::<Stack>(),
28+
kernel_code_selector = const gdt::Gdt::kernel_code_selector().0,
29+
rust_start = sym rust_start,
30+
);
31+
}
32+
33+
pvh::xen_elfnote_phys32_entry!(pvh_start32);
34+
35+
/// The native ELF entry point.
36+
#[unsafe(no_mangle)]
37+
#[unsafe(naked)]
38+
unsafe extern "C" fn _start() -> ! {
39+
core::arch::naked_asm!("2: jmp 2b");
40+
}
41+
42+
static START_INFO_PADDR: AtomicU32 = AtomicU32::new(0);
43+
44+
pub fn start_info<'a>() -> StartInfoReader<'a, IdentityMap> {
45+
let paddr = START_INFO_PADDR.load(Ordering::Relaxed);
46+
unsafe { StartInfoReader::from_paddr_identity(paddr).unwrap() }
47+
}
48+
49+
/// The Rust entry point.
50+
unsafe extern "C" fn rust_start(start_info_paddr: u32) -> ! {
51+
START_INFO_PADDR.store(start_info_paddr, Ordering::Relaxed);
52+
53+
let start_info = start_info();
54+
55+
println!("Start info:\n{start_info:#?}");
56+
57+
let boot_info = BootInfo {
58+
hardware_info: HardwareInfo {
59+
phys_addr_range: 0..0,
60+
serial_port_base: SerialPortBase::new(0x3f8),
61+
device_tree: None,
62+
},
63+
// This is not used by the kernel anymore.
64+
load_info: LoadInfo {
65+
kernel_image_addr_range: 0..0,
66+
tls_info: None,
67+
},
68+
platform_info: PlatformInfo::Fdt,
69+
};
70+
71+
println!("boot_info = {boot_info:#x?}");
72+
73+
setboot_info2(boot_info);
74+
75+
unsafe { pre_init(None, 0) }
76+
}
Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
//! Page Tables.
2+
//!
3+
//! This module defines the page tables that we switch to by setting `CR3` to
4+
//! `LEVEL_4_TABLE`. Specifically, we map the first GiB of virtual memory using
5+
//! 512 2-MiB pages. 2-MiB pages are supported on every x86-64 CPU.
6+
//!
7+
//! # Current implementation
8+
//!
9+
//! Some page tables need to point to other page tables, but also contain flags.
10+
//! We do this by adding the flags as bytes to the pointer, which is possible in
11+
//! const-eval. The resulting expression is relocatable.
12+
//!
13+
//! Casting pointers to integers is not possible in const-eval. Asserting that
14+
//! all flag bits in the address are 0 is not possible. Using a bitwise OR (`|`)
15+
//! operation cannot be expressed and would not be relocatable.
16+
//!
17+
//! For details, see this discussion: [rust-lang/rust#51910 (comment)].
18+
//!
19+
//! [rust-lang/rust#51910 (comment)]: https://github.com/rust-lang/rust/issues/51910#issuecomment-1013271838
20+
21+
use core::ptr;
22+
23+
use x86_64::structures::paging::{PageSize, PageTableFlags, Size2MiB};
24+
25+
const TABLE_FLAGS: PageTableFlags = PageTableFlags::PRESENT.union(PageTableFlags::WRITABLE);
26+
const PAGE_FLAGS: PageTableFlags = TABLE_FLAGS.union(PageTableFlags::HUGE_PAGE);
27+
28+
pub(super) static mut LEVEL_4_TABLE: PageTable = {
29+
let flags = TABLE_FLAGS.bits() as usize;
30+
31+
let mut page_table = [ptr::null_mut(); _];
32+
33+
page_table[0] = (&raw mut LEVEL_3_TABLE).wrapping_byte_add(flags).cast();
34+
35+
PageTable(page_table)
36+
};
37+
38+
static mut LEVEL_3_TABLE: PageTable = {
39+
let flags = TABLE_FLAGS.bits() as usize;
40+
41+
let mut page_table = [ptr::null_mut(); _];
42+
43+
page_table[0] = (&raw mut LEVEL_2_TABLE).wrapping_byte_add(flags).cast();
44+
45+
PageTable(page_table)
46+
};
47+
48+
static mut LEVEL_2_TABLE: PageTable = {
49+
let flags: usize = PAGE_FLAGS.bits() as usize;
50+
51+
let mut page_table = [ptr::null_mut(); _];
52+
53+
let mut i = 0;
54+
while i < page_table.len() {
55+
let addr = i * Size2MiB::SIZE as usize;
56+
page_table[i] = ptr::with_exposed_provenance_mut(addr + flags);
57+
i += 1;
58+
}
59+
60+
PageTable(page_table)
61+
};
62+
63+
#[repr(align(0x1000))]
64+
#[repr(C)]
65+
pub(super) struct PageTable([*mut (); 512]);
Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
// Load level 4 page table into cr3
2+
mov eax, offset {level_4_table}
3+
mov cr3, eax
4+
cli
5+
6+
// Enable physical address extensions
7+
.set CR4_FLAGS_PHYSICAL_ADDRESS_EXTENSION, 1 << 5
8+
mov eax, cr4
9+
or eax, CR4_FLAGS_PHYSICAL_ADDRESS_EXTENSION
10+
mov cr4, eax
11+
12+
// Switch to compatibility mode
13+
.set EFER_MSR, 0xc0000080
14+
.set EFER_FLAGS_LONG_MODE_ENABLE, 1 << 8
15+
mov ecx, EFER_MSR
16+
rdmsr
17+
or eax, EFER_FLAGS_LONG_MODE_ENABLE
18+
wrmsr
19+
20+
// Enable paging and protected mode
21+
.set CR0_FLAGS_PROTECTED_MODE_ENABLE, 1
22+
.set CR0_FLAGS_PAGING, 1 << 31
23+
mov eax, cr0
24+
or eax, CR0_FLAGS_PROTECTED_MODE_ENABLE | CR0_FLAGS_PAGING
25+
mov cr0, eax
26+
27+
28+
# Set CR0 (PM-bit is already set)
29+
mov eax, cr0
30+
and eax, ~(1 << 2) # disable FPU emulation
31+
or eax, (1 << 1) # enable FPU montitoring
32+
and eax, ~(1 << 30) # enable caching
33+
and eax, ~(1 << 29) # disable write through caching
34+
and eax, ~(1 << 16) # allow kernel write access to read-only pages
35+
or eax, (1 << 31) # enable paging
36+
mov cr0, eax
37+
38+
// Load the GDT
39+
lgdt [offset {gdt_ptr}]
40+
41+
// Load the segment registers
42+
mov ax, {kernel_data_selector}
43+
mov ds, eax
44+
mov es, eax
45+
mov fs, eax
46+
mov gs, eax
47+
mov ss, eax
48+
49+
// ebx contains the start info physical address
50+
// Jump to rust_start with paddr as first argument
51+
mov edi, ebx
52+
53+
// Set up the stack
54+
mov esp, offset {stack}
55+
add esp, {stack_size}
56+
57+
// We need to do a far jump, but LLVM does not support absolute far jumps
58+
// in Intel syntax yet: https://github.com/llvm/llvm-project/issues/46048
59+
// We could switch to AT&T syntax for the unsupported line of code but
60+
// using far returns is more flexible anyway.
61+
push {kernel_code_selector}
62+
mov eax, offset {rust_start}
63+
push eax
64+
retf
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
use core::mem::MaybeUninit;
2+
3+
pub(super) static mut STACK: MaybeUninit<Stack> = MaybeUninit::uninit();
4+
5+
#[repr(C, align(0x1000))]
6+
pub(super) struct Stack([u8; 0x8000]);

src/arch/x86_64/kernel/serial.rs

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ use crate::errno::Errno;
1414
#[cfg(feature = "pci")]
1515
const SERIAL_IRQ: u8 = 4;
1616

17-
static UART_DEVICE: Lazy<InterruptTicketMutex<UartDevice>> =
17+
pub static UART_DEVICE: Lazy<InterruptTicketMutex<UartDevice>> =
1818
Lazy::new(|| unsafe { InterruptTicketMutex::new(UartDevice::new()) });
1919

2020
struct UartDevice {
@@ -24,11 +24,12 @@ struct UartDevice {
2424

2525
impl UartDevice {
2626
pub unsafe fn new() -> Self {
27-
let base = crate::env::boot_info()
28-
.hardware_info
29-
.serial_port_base
30-
.unwrap()
31-
.get();
27+
// let base = crate::env::boot_info()
28+
// .hardware_info
29+
// .serial_port_base
30+
// .unwrap()
31+
// .get();
32+
let base = 0x3f8;
3233
let mut uart = unsafe { Uart16550::new_port(base).unwrap() };
3334
uart.init(Config::default()).ok();
3435
// Once we have a fallback destination for output,

0 commit comments

Comments
 (0)