use rvm_types::{GuestPhysAddr, PhysAddr, RvmError, RvmResult};
pub const PAGE_SIZE: usize = 4096;
const ENTRIES_PER_TABLE: usize = 512;
#[allow(dead_code)]
const L1_BLOCK_SIZE: u64 = 1 << 30;
const L2_BLOCK_SIZE: u64 = 1 << 21;
mod s2_desc {
pub const VALID: u64 = 1 << 0;
pub const TABLE: u64 = 1 << 1;
pub const AF: u64 = 1 << 10;
pub const MEM_ATTR_SHIFT: u32 = 2;
pub const SH_SHIFT: u32 = 8;
pub const MEM_ATTR_NORMAL_WB: u64 = 0xF << MEM_ATTR_SHIFT;
pub const MEM_ATTR_DEVICE: u64 = 0x0 << MEM_ATTR_SHIFT;
pub const SH_INNER: u64 = 3 << SH_SHIFT;
pub const SH_OUTER: u64 = 2 << SH_SHIFT;
pub const S2AP_SHIFT: u32 = 6;
pub const S2AP_RW: u64 = 3 << S2AP_SHIFT;
#[allow(dead_code)]
pub const S2AP_RO: u64 = 1 << S2AP_SHIFT;
pub const XN: u64 = 1 << 54;
}
#[repr(C, align(4096))]
pub struct Stage2PageTable {
l1_table: [u64; ENTRIES_PER_TABLE],
l2_tables: [[u64; ENTRIES_PER_TABLE]; Self::MAX_L2_TABLES],
next_l2: usize,
}
impl Stage2PageTable {
const MAX_L2_TABLES: usize = 4;
#[must_use]
pub const fn new() -> Self {
Self {
l1_table: [0; ENTRIES_PER_TABLE],
l2_tables: [[0; ENTRIES_PER_TABLE]; Self::MAX_L2_TABLES],
next_l2: 0,
}
}
pub fn l1_base_addr(&self) -> u64 {
self.l1_table.as_ptr() as u64
}
pub fn map_2mb_block(&mut self, ipa: u64, pa: u64, attrs: u64) -> RvmResult<()> {
if ipa & (L2_BLOCK_SIZE - 1) != 0 || pa & (L2_BLOCK_SIZE - 1) != 0 {
return Err(RvmError::AlignmentError);
}
let l1_index = ((ipa >> 30) & 0x1FF) as usize;
let l2_index = ((ipa >> 21) & 0x1FF) as usize;
if self.l1_table[l1_index] & s2_desc::VALID == 0 {
self.alloc_l2_for(l1_index)?;
}
let l2_idx = self.l1_to_l2_index(l1_index);
if self.l2_tables[l2_idx][l2_index] & s2_desc::VALID != 0 {
return Err(RvmError::MemoryOverlap);
}
let descriptor = (pa & 0x0000_FFFF_FFE0_0000) | attrs | s2_desc::AF | s2_desc::VALID;
self.l2_tables[l2_idx][l2_index] = descriptor;
Ok(())
}
pub fn map_ram_2mb(&mut self, ipa: u64, pa: u64) -> RvmResult<()> {
let attrs = s2_desc::MEM_ATTR_NORMAL_WB | s2_desc::SH_INNER | s2_desc::S2AP_RW;
self.map_2mb_block(ipa, pa, attrs)
}
pub fn map_device_2mb(&mut self, ipa: u64, pa: u64) -> RvmResult<()> {
let attrs = s2_desc::MEM_ATTR_DEVICE | s2_desc::SH_OUTER | s2_desc::S2AP_RW | s2_desc::XN;
self.map_2mb_block(ipa, pa, attrs)
}
pub fn identity_map_ram(&mut self, size: u64) -> RvmResult<()> {
if size & (L2_BLOCK_SIZE - 1) != 0 {
return Err(RvmError::InternalError);
}
let mut addr: u64 = 0;
while addr < size {
self.map_ram_2mb(addr, addr)?;
addr += L2_BLOCK_SIZE;
}
Ok(())
}
pub fn identity_map_devices(&mut self) -> RvmResult<()> {
let base: u64 = 0x0800_0000;
let end: u64 = 0x0A00_0000;
let mut addr = base;
while addr < end {
self.map_device_2mb(addr, addr)?;
addr += L2_BLOCK_SIZE;
}
Ok(())
}
fn alloc_l2_for(&mut self, l1_index: usize) -> RvmResult<()> {
if self.next_l2 >= Self::MAX_L2_TABLES {
return Err(RvmError::OutOfMemory);
}
let l2_idx = self.next_l2;
self.next_l2 += 1;
self.l2_tables[l2_idx] = [0; ENTRIES_PER_TABLE];
let l2_addr = self.l2_tables[l2_idx].as_ptr() as u64;
self.l1_table[l1_index] = (l2_addr & 0x0000_FFFF_FFFF_F000)
| s2_desc::TABLE
| s2_desc::VALID
| (l2_idx as u64) << 56;
Ok(())
}
fn l1_to_l2_index(&self, l1_index: usize) -> usize {
((self.l1_table[l1_index] >> 56) & 0xFF) as usize
}
}
pub struct Aarch64Mmu {
page_table: Stage2PageTable,
installed: bool,
vmid: u16,
}
impl Aarch64Mmu {
#[must_use]
pub const fn new_with_vmid(vmid: u16) -> Self {
Self {
page_table: Stage2PageTable::new(),
installed: false,
vmid,
}
}
#[must_use]
pub const fn new() -> Self {
Self {
page_table: Stage2PageTable::new(),
installed: false,
vmid: 0,
}
}
#[must_use]
pub const fn vmid(&self) -> u16 {
self.vmid
}
pub fn page_table_mut(&mut self) -> &mut Stage2PageTable {
&mut self.page_table
}
pub fn page_table(&self) -> &Stage2PageTable {
&self.page_table
}
pub unsafe fn install(&mut self) {
let base = self.page_table.l1_base_addr();
super::boot::configure_vtcr_el2();
super::boot::set_vttbr_el2(base, self.vmid);
super::boot::invalidate_stage2_tlb();
self.installed = true;
}
}
impl crate::MmuOps for Aarch64Mmu {
fn map_page(&mut self, guest: GuestPhysAddr, host: PhysAddr) -> RvmResult<()> {
let ipa = guest.as_u64();
let pa = host.as_u64();
if ipa & (L2_BLOCK_SIZE - 1) != 0 || pa & (L2_BLOCK_SIZE - 1) != 0 {
return Err(RvmError::AlignmentError);
}
self.page_table.map_ram_2mb(ipa, pa)
}
fn unmap_page(&mut self, guest: GuestPhysAddr) -> RvmResult<()> {
let l1_index = ((guest.as_u64() >> 30) & 0x1FF) as usize;
let l2_index = ((guest.as_u64() >> 21) & 0x1FF) as usize;
if self.page_table.l1_table[l1_index] & s2_desc::VALID == 0 {
return Err(RvmError::InternalError);
}
let l2_idx = self.page_table.l1_to_l2_index(l1_index);
if self.page_table.l2_tables[l2_idx][l2_index] & s2_desc::VALID == 0 {
return Err(RvmError::InternalError);
}
self.page_table.l2_tables[l2_idx][l2_index] = 0;
Ok(())
}
fn translate(&self, guest: GuestPhysAddr) -> RvmResult<PhysAddr> {
let l1_index = ((guest.as_u64() >> 30) & 0x1FF) as usize;
let l2_index = ((guest.as_u64() >> 21) & 0x1FF) as usize;
if self.page_table.l1_table[l1_index] & s2_desc::VALID == 0 {
return Err(RvmError::InternalError);
}
let l2_idx = self.page_table.l1_to_l2_index(l1_index);
let entry = self.page_table.l2_tables[l2_idx][l2_index];
if entry & s2_desc::VALID == 0 {
return Err(RvmError::InternalError);
}
let block_pa = entry & 0x0000_FFFF_FFE0_0000;
let offset = guest.as_u64() & (L2_BLOCK_SIZE - 1);
Ok(PhysAddr::new(block_pa | offset))
}
fn flush_tlb(&mut self, _guest: GuestPhysAddr, _page_count: usize) -> RvmResult<()> {
super::boot::invalidate_stage2_tlb();
Ok(())
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_stage2_page_table_new() {
let pt = Stage2PageTable::new();
assert_eq!(pt.next_l2, 0);
for entry in &pt.l1_table {
assert_eq!(*entry, 0);
}
}
#[test]
fn test_l2_block_size() {
assert_eq!(L2_BLOCK_SIZE, 2 * 1024 * 1024); }
#[test]
fn test_l1_block_size() {
assert_eq!(L1_BLOCK_SIZE, 1024 * 1024 * 1024); }
#[test]
fn test_entries_per_table() {
assert_eq!(ENTRIES_PER_TABLE, 512);
}
#[test]
fn test_s2_descriptor_bits() {
assert_eq!(s2_desc::VALID, 1);
assert_eq!(s2_desc::TABLE, 2);
assert_eq!(s2_desc::AF, 1 << 10);
}
#[test]
fn test_stage2_alignment() {
assert_eq!(core::mem::align_of::<Stage2PageTable>(), 4096,);
}
#[test]
fn test_aarch64_mmu_new() {
let mmu = Aarch64Mmu::new();
assert!(!mmu.installed);
}
}