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

Arm refactor #657

Merged
merged 7 commits into from
Apr 3, 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
26 changes: 26 additions & 0 deletions Cargo.lock

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

160 changes: 159 additions & 1 deletion src/arch/aarch64/mod.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,15 @@
use std::mem::size_of;

use bitflags::bitflags;
use uhyve_interface::{GuestPhysAddr, GuestVirtAddr};

use crate::{
consts::{BOOT_INFO_ADDR, BOOT_PGT},
mem::MmapMemory,
paging::PagetableError,
};

pub const RAM_START: u64 = 0x00;
pub const RAM_START: GuestPhysAddr = GuestPhysAddr::new(0x00);

pub const PT_DEVICE: u64 = 0x707;
pub const PT_PT: u64 = 0x713;
Expand All @@ -19,6 +28,16 @@ pub const MT_DEVICE_GRE: u64 = 2;
pub const MT_NORMAL_NC: u64 = 3;
pub const MT_NORMAL: u64 = 4;

/// Number of Offset bits of a virtual address for a 4 KiB page, which are shifted away to get its Page Frame Number (PFN).
const PAGE_BITS: usize = 12;
const PAGE_SIZE: usize = 1 << PAGE_BITS;

/// Number of bits of the index in each table (L0Table, L1Table, L2Table, L3Table).
const PAGE_MAP_BITS: usize = 9;

/// A mask where PAGE_MAP_BITS are set to calculate a table index.
const PAGE_MAP_MASK: u64 = 0x1FF;

#[inline(always)]
pub const fn mair(attr: u64, mt: u64) -> u64 {
attr << (mt * 8)
Expand Down Expand Up @@ -58,3 +77,142 @@ bitflags! {
const D_BIT = 0x00000200;
}
}

/// An entry in a L0 page table (coarses). Adapted from hermit-os/kernel.
#[derive(Clone, Copy, Debug)]
struct PageTableEntry {
/// Physical memory address this entry refers, combined with flags from PageTableEntryFlags.
physical_address_and_flags: GuestPhysAddr,
}

impl PageTableEntry {
/// Return the stored physical address.
pub fn address(&self) -> GuestPhysAddr {
// For other granules than 4KiB or hugepages we should check the DESCRIPTOR_TYPE bit and modify the address translation accordingly.
GuestPhysAddr(
self.physical_address_and_flags.as_u64() & !(PAGE_SIZE as u64 - 1) & !(u64::MAX << 48),
)
}
}
impl From<u64> for PageTableEntry {
fn from(i: u64) -> Self {
Self {
physical_address_and_flags: GuestPhysAddr::new(i),
}
}
}

/// Returns whether the given virtual address is a valid one in the AArch64 memory model.
///
/// Current AArch64 supports only 48-bit for virtual memory addresses.
/// The upper bits must always be 0 or 1 and indicate whether TBBR0 or TBBR1 contains the
/// base address. So always enforce 0 here.
fn is_valid_address(virtual_address: GuestVirtAddr) -> bool {
virtual_address < GuestVirtAddr(0x1_0000_0000_0000)
}

/// Converts a virtual address in the guest to a physical address in the guest
pub fn virt_to_phys(
addr: GuestVirtAddr,
mem: &MmapMemory,
pagetable_l0: GuestPhysAddr,
) -> Result<GuestPhysAddr, PagetableError> {
if !is_valid_address(addr) {
return Err(PagetableError::InvalidAddress);
}

// Assumptions:
// - We use 4KiB granule
// - We use maximum VA length
// => We have 4 level paging

// Safety:
// - We are only working in the vm's memory
// - the memory location of the pagetable is not altered by hermit.
// - Our indices can't be larger than 512, so we stay in the borders of the page.
// - We are page_aligned, and thus also PageTableEntry aligned.
let mut pagetable: &[PageTableEntry] =
unsafe { std::mem::transmute(mem.slice_at(pagetable_l0, PAGE_SIZE).unwrap()) };
// TODO: Depending on the virtual address length and granule (defined in TCR register by TG and TxSZ), we could reduce the number of pagetable walks. Hermit doesn't do this at the moment.
for level in 0..3 {
let table_index =
(addr.as_u64() >> PAGE_BITS >> ((3 - level) * PAGE_MAP_BITS) & PAGE_MAP_MASK) as usize;
let pte = PageTableEntry::from(pagetable[table_index]);
// TODO: We could stop here if we have a "Block Entry" (ARM equivalent to huge page). Currently not supported.

pagetable = unsafe { std::mem::transmute(mem.slice_at(pte.address(), PAGE_SIZE).unwrap()) };
}
let table_index = (addr.as_u64() >> PAGE_BITS & PAGE_MAP_MASK) as usize;
let pte = PageTableEntry::from(pagetable[table_index]);

Ok(pte.address())
}

pub fn init_guest_mem(mem: &mut [u8]) {
let mem_addr = std::ptr::addr_of_mut!(mem[0]);

assert!(mem.len() >= BOOT_PGT.as_u64() as usize + 512 * size_of::<u64>());
let pgt_slice = unsafe {
std::slice::from_raw_parts_mut(mem_addr.offset(BOOT_PGT.as_u64() as isize) as *mut u64, 512)
};
pgt_slice.fill(0);
pgt_slice[0] = BOOT_PGT.as_u64() + 0x1000 + PT_PT;
pgt_slice[511] = BOOT_PGT.as_u64() + PT_PT + PT_SELF;

assert!(mem.len() >= BOOT_PGT.as_u64() as usize + 0x1000 + 512 * size_of::<u64>());
let pgt_slice = unsafe {
std::slice::from_raw_parts_mut(
mem_addr.offset(BOOT_PGT.as_u64() as isize + 0x1000) as *mut u64,
512,
)
};
pgt_slice.fill(0);
pgt_slice[0] = BOOT_PGT.as_u64() + 0x2000 + PT_PT;

assert!(mem.len() >= BOOT_PGT.as_u64() as usize + 0x2000 + 512 * size_of::<u64>());
let pgt_slice = unsafe {
std::slice::from_raw_parts_mut(
mem_addr.offset(BOOT_PGT.as_u64() as isize + 0x2000) as *mut u64,
512,
)
};
pgt_slice.fill(0);
pgt_slice[0] = BOOT_PGT.as_u64() + 0x3000 + PT_PT;
pgt_slice[1] = BOOT_PGT.as_u64() + 0x4000 + PT_PT;
pgt_slice[2] = BOOT_PGT.as_u64() + 0x5000 + PT_PT;

assert!(mem.len() >= BOOT_PGT.as_u64() as usize + 0x3000 + 512 * size_of::<u64>());
let pgt_slice = unsafe {
std::slice::from_raw_parts_mut(
mem_addr.offset(BOOT_PGT.as_u64() as isize + 0x3000) as *mut u64,
512,
)
};
pgt_slice.fill(0);
// map uhyve ports into the virtual address space
pgt_slice[0] = PT_MEM_CD;
// map BootInfo into the virtual address space
pgt_slice[BOOT_INFO_ADDR.as_u64() as usize / PAGE_SIZE] = BOOT_INFO_ADDR.as_u64() + PT_MEM;

assert!(mem.len() >= BOOT_PGT.as_u64() as usize + 0x4000 + 512 * size_of::<u64>());
let pgt_slice = unsafe {
std::slice::from_raw_parts_mut(
mem_addr.offset(BOOT_PGT.as_u64() as isize + 0x4000) as *mut u64,
512,
)
};
for (idx, i) in pgt_slice.iter_mut().enumerate() {
*i = 0x200000u64 + (idx * PAGE_SIZE) as u64 + PT_MEM;
}

assert!(mem.len() >= BOOT_PGT.as_u64() as usize + 0x5000 + 512 * size_of::<u64>());
let pgt_slice = unsafe {
std::slice::from_raw_parts_mut(
mem_addr.offset(BOOT_PGT.as_u64() as isize + 0x5000) as *mut u64,
512,
)
};
for (idx, i) in pgt_slice.iter_mut().enumerate() {
*i = 0x400000u64 + (idx * PAGE_SIZE) as u64 + PT_MEM;
}
}
24 changes: 12 additions & 12 deletions src/arch/x86_64/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ use x86_64::{
PhysAddr,
};

use crate::{consts::*, mem::MmapMemory};
use crate::{consts::*, mem::MmapMemory, paging::PagetableError};

pub const RAM_START: GuestPhysAddr = GuestPhysAddr::new(0x00);
const MHZ_TO_HZ: u64 = 1000000;
Expand Down Expand Up @@ -192,16 +192,11 @@ pub fn initialize_pagetables(mem: &mut [u8]) {
}
}

#[derive(Error, Debug)]
pub enum PagetableError {
#[error("The accessed virtual address is not mapped")]
InvalidAddress,
}

/// Converts a virtual address in the guest to a physical address in the guest
pub fn virt_to_phys(
addr: GuestVirtAddr,
mem: &MmapMemory,
pagetable_l0: GuestPhysAddr,
) -> Result<GuestPhysAddr, PagetableError> {
/// Number of Offset bits of a virtual address for a 4 KiB page, which are shifted away to get its Page Frame Number (PFN).
pub const PAGE_BITS: u64 = 12;
Expand All @@ -210,7 +205,7 @@ pub fn virt_to_phys(
pub const PAGE_MAP_BITS: usize = 9;

let mut page_table =
unsafe { (mem.host_address(BOOT_PML4).unwrap() as *mut PageTable).as_mut() }.unwrap();
unsafe { (mem.host_address(pagetable_l0).unwrap() as *mut PageTable).as_mut() }.unwrap();
let mut page_bits = 39;
let mut entry = PageTableEntry::new();

Expand All @@ -237,6 +232,11 @@ pub fn virt_to_phys(
Ok(entry.addr() + (addr.as_u64() & !((!0u64) << PAGE_BITS)))
}

pub fn init_guest_mem(mem: &mut [u8]) {
// TODO: we should maybe return an error on failure (e.g., the memory is too small)
initialize_pagetables(mem);
}

#[cfg(test)]
mod tests {
use super::*;
Expand Down Expand Up @@ -380,25 +380,25 @@ mod tests {

// Get the address of the first entry in PML4 (the address of the PML4 itself)
let virt_addr = GuestVirtAddr::new(0xFFFFFFFFFFFFF000);
let p_addr = virt_to_phys(virt_addr, &mem).unwrap();
let p_addr = virt_to_phys(virt_addr, &mem, BOOT_PML4).unwrap();
assert_eq!(p_addr, BOOT_PML4);

// The last entry on the PML4 is the address of the PML4 with flags
let virt_addr = GuestVirtAddr::new(0xFFFFFFFFFFFFF000 | (4096 - 8));
let p_addr = virt_to_phys(virt_addr, &mem).unwrap();
let p_addr = virt_to_phys(virt_addr, &mem, BOOT_PML4).unwrap();
assert_eq!(
mem.read::<u64>(p_addr).unwrap(),
BOOT_PML4.as_u64() | (PageTableFlags::PRESENT | PageTableFlags::WRITABLE).bits()
);

// the first entry on the 3rd level entry in the pagetables is the address of the boot pdpte
let virt_addr = GuestVirtAddr::new(0xFFFFFFFFFFE00000);
let p_addr = virt_to_phys(virt_addr, &mem).unwrap();
let p_addr = virt_to_phys(virt_addr, &mem, BOOT_PML4).unwrap();
assert_eq!(p_addr, BOOT_PDPTE);

// the first entry on the 2rd level entry in the pagetables is the address of the boot pde
let virt_addr = GuestVirtAddr::new(0xFFFFFFFFC0000000);
let p_addr = virt_to_phys(virt_addr, &mem).unwrap();
let p_addr = virt_to_phys(virt_addr, &mem, BOOT_PML4).unwrap();
assert_eq!(p_addr, BOOT_PDE);
// That address points to a huge page
assert!(
Expand Down
14 changes: 7 additions & 7 deletions src/consts.rs
Original file line number Diff line number Diff line change
@@ -1,19 +1,19 @@
use x86_64::addr::PhysAddr;
use uhyve_interface::GuestPhysAddr;

pub const PAGE_SIZE: usize = 0x1000;
pub const GDT_KERNEL_CODE: u16 = 1;
pub const GDT_KERNEL_DATA: u16 = 2;
pub const APIC_DEFAULT_BASE: u64 = 0xfee00000;
pub const BOOT_GDT: PhysAddr = PhysAddr::new(0x1000);
pub const BOOT_GDT: GuestPhysAddr = GuestPhysAddr::new(0x1000);
pub const BOOT_GDT_NULL: usize = 0;
pub const BOOT_GDT_CODE: usize = 1;
pub const BOOT_GDT_DATA: usize = 2;
pub const BOOT_GDT_MAX: usize = 3;
pub const BOOT_PML4: PhysAddr = PhysAddr::new(0x10000);
pub const BOOT_PGT: PhysAddr = BOOT_PML4;
pub const BOOT_PDPTE: PhysAddr = PhysAddr::new(0x11000);
pub const BOOT_PDE: PhysAddr = PhysAddr::new(0x12000);
pub const BOOT_INFO_ADDR: PhysAddr = PhysAddr::new(0x9000);
pub const BOOT_PML4: GuestPhysAddr = GuestPhysAddr::new(0x10000);
pub const BOOT_PGT: GuestPhysAddr = BOOT_PML4;
pub const BOOT_PDPTE: GuestPhysAddr = GuestPhysAddr::new(0x11000);
pub const BOOT_PDE: GuestPhysAddr = GuestPhysAddr::new(0x12000);
pub const BOOT_INFO_ADDR: GuestPhysAddr = GuestPhysAddr::new(0x9000);
pub const EFER_SCE: u64 = 1; /* System Call Extensions */
pub const EFER_LME: u64 = 1 << 8; /* Long mode enable */
pub const EFER_LMA: u64 = 1 << 10; /* Long mode active (read-only) */
Expand Down
23 changes: 13 additions & 10 deletions src/hypercall.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ use std::{
use uhyve_interface::{parameters::*, GuestPhysAddr, Hypercall, HypercallAddress, MAX_ARGC_ENVC};

use crate::{
consts::BOOT_PML4,
mem::{MemoryError, MmapMemory},
virt_to_phys,
};
Expand Down Expand Up @@ -101,7 +102,7 @@ pub fn read(mem: &MmapMemory, sysread: &mut ReadPrams) {
unsafe {
let bytes_read = libc::read(
sysread.fd,
mem.host_address(virt_to_phys(sysread.buf, mem).unwrap())
mem.host_address(virt_to_phys(sysread.buf, mem, BOOT_PML4).unwrap())
.unwrap() as *mut libc::c_void,
sysread.len,
);
Expand All @@ -120,15 +121,17 @@ pub fn write(mem: &MmapMemory, syswrite: &WriteParams) -> io::Result<()> {
unsafe {
let step = libc::write(
syswrite.fd,
mem.host_address(virt_to_phys(syswrite.buf + bytes_written as u64, mem).unwrap())
.map_err(|e| match e {
MemoryError::BoundsViolation => {
unreachable!("Bounds violation after host_address function")
}
MemoryError::WrongMemoryError => {
Error::new(ErrorKind::AddrNotAvailable, e.to_string())
}
})? as *const libc::c_void,
mem.host_address(
virt_to_phys(syswrite.buf + bytes_written as u64, mem, BOOT_PML4).unwrap(),
)
.map_err(|e| match e {
MemoryError::BoundsViolation => {
unreachable!("Bounds violation after host_address function")
}
MemoryError::WrongMemoryError => {
Error::new(ErrorKind::AddrNotAvailable, e.to_string())
}
})? as *const libc::c_void,
syswrite.len - bytes_written,
);
if step >= 0 {
Expand Down
1 change: 1 addition & 0 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ pub mod macos;
pub use macos as os;
mod hypercall;
pub mod mem;
pub mod paging;
pub mod params;
#[cfg(target_os = "linux")]
pub mod shared_queue;
Expand Down
Loading
Loading