use crate::mmu::PAGE_SIZE;
pub const TLB_INDEX_BITS: u32 = 6;
pub const TLB_ENTRIES: usize = 1 << TLB_INDEX_BITS;
const PAGE_MASK: u64 = PAGE_SIZE - 1;
const INVALID_TAG: u64 = u64::MAX;
#[repr(C)]
#[derive(Debug, Clone, Copy)]
pub struct TlbEntry {
pub tag: u64,
pub guest_to_host_offset: u64,
}
impl TlbEntry {
const fn invalid() -> Self {
Self {
tag: INVALID_TAG,
guest_to_host_offset: 0,
}
}
pub const fn tag_of(addr: u64) -> u64 {
addr & !PAGE_MASK
}
}
#[repr(C)]
#[derive(Debug, Clone)]
pub struct TranslationCache {
pub entries: [TlbEntry; TLB_ENTRIES],
}
impl Default for TranslationCache {
fn default() -> Self {
Self {
entries: [TlbEntry::invalid(); TLB_ENTRIES],
}
}
}
impl TranslationCache {
pub const fn index(addr: u64) -> usize {
((addr >> PAGE_SIZE.trailing_zeros()) as usize) & (TLB_ENTRIES - 1)
}
pub fn flush(&mut self) {
self.entries.fill(TlbEntry::invalid());
}
pub fn insert(&mut self, addr: u64, host: *mut u8) {
let tag = TlbEntry::tag_of(addr);
self.entries[Self::index(addr)] = TlbEntry {
tag,
guest_to_host_offset: (host as u64).wrapping_sub(tag),
};
}
pub fn lookup(&self, addr: u64) -> Option<*mut u8> {
let entry = self.entries[Self::index(addr)];
(entry.tag == TlbEntry::tag_of(addr))
.then(|| addr.wrapping_add(entry.guest_to_host_offset) as *mut u8)
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn a_fresh_table_translates_nothing() {
let tlb = TranslationCache::default();
assert!(tlb.lookup(0).is_none());
assert!(tlb.lookup(!PAGE_MASK).is_none());
}
#[test]
fn an_inserted_page_translates_every_byte_in_it() {
let mut page = vec![0u8; PAGE_SIZE as usize];
let host = page.as_mut_ptr();
let mut tlb = TranslationCache::default();
tlb.insert(0x1234, host);
assert_eq!(tlb.lookup(0x1000), Some(host));
assert_eq!(tlb.lookup(0x1fff), Some(unsafe { host.add(0xfff) }));
assert!(tlb.lookup(0x2000).is_none());
}
#[test]
fn a_flush_forgets_everything() {
let mut page = vec![0u8; PAGE_SIZE as usize];
let mut tlb = TranslationCache::default();
tlb.insert(0x1000, page.as_mut_ptr());
tlb.flush();
assert!(tlb.lookup(0x1000).is_none());
}
#[test]
fn pages_a_multiple_of_the_table_size_apart_share_a_slot() {
let stride = PAGE_SIZE * TLB_ENTRIES as u64;
assert_eq!(
TranslationCache::index(0x1000),
TranslationCache::index(0x1000 + stride)
);
let mut page = vec![0u8; PAGE_SIZE as usize];
let mut tlb = TranslationCache::default();
tlb.insert(0x1000, page.as_mut_ptr());
tlb.insert(0x1000 + stride, page.as_mut_ptr());
assert!(tlb.lookup(0x1000).is_none());
assert!(tlb.lookup(0x1000 + stride).is_some());
}
}