use super::flat::FlatView;
use alloc::vec::Vec;
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
#[non_exhaustive]
pub enum DispatchPolicy {
#[default]
Flat,
Dense {
page_bits: u32,
cover: u64,
},
Auto,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum DispatchEntry {
Unassigned,
SubPage,
Mapped(u32),
Direct(u32),
}
#[derive(Debug)]
pub struct Dispatch {
page_bits: u32,
cover: u64,
table: Vec<u32>,
}
const TAG_SHIFT: u32 = 30;
const TAG_MAPPED: u32 = 0;
const TAG_DIRECT: u32 = 1 << TAG_SHIFT;
const TAG_SUBPAGE: u32 = 2 << TAG_SHIFT;
const TAG_UNASSIGNED: u32 = 3 << TAG_SHIFT;
const INDEX_MASK: u32 = (1 << TAG_SHIFT) - 1;
impl Dispatch {
pub const MAX_ENTRIES: u64 = 1 << 20;
#[must_use]
pub fn build(view: &FlatView, policy: DispatchPolicy) -> Option<Dispatch> {
let (page_bits, cover) = match policy {
DispatchPolicy::Flat => return None,
DispatchPolicy::Dense { page_bits, cover } => (page_bits, cover),
DispatchPolicy::Auto => {
if view.len() < 8 {
return None;
}
(12, view.extent())
}
};
if page_bits == 0 || page_bits >= 64 || cover == 0 {
return None;
}
let pages = cover.div_ceil(1u64 << page_bits);
if pages > Self::MAX_ENTRIES {
return None;
}
let n = usize::try_from(pages).ok()?;
let page_size = 1u64 << page_bits;
let mut table = Vec::with_capacity(n);
for page in 0..pages {
let start = page << page_bits;
let end = start.saturating_add(page_size);
table.push(match view.find(start) {
Some(i) => {
let e = view.entry(i).expect("index from find");
if e.end() >= end {
let tag = if e.is_direct_ram() {
TAG_DIRECT
} else {
TAG_MAPPED
};
match u32::try_from(i) {
Ok(i) if i <= INDEX_MASK => tag | i,
_ => TAG_SUBPAGE,
}
} else {
TAG_SUBPAGE
}
}
None => {
let next_start = view
.entries()
.iter()
.find(|e| e.start() >= start)
.map(super::FlatEntry::start);
match next_start {
Some(s) if s < end => TAG_SUBPAGE,
_ => TAG_UNASSIGNED,
}
}
});
}
Some(Dispatch {
page_bits,
cover,
table,
})
}
#[inline]
#[must_use]
pub fn page_size(&self) -> u64 {
1u64 << self.page_bits
}
#[inline]
#[must_use]
pub fn cover(&self) -> u64 {
self.cover
}
#[inline]
#[must_use]
pub fn len(&self) -> usize {
self.table.len()
}
#[inline]
#[must_use]
pub fn is_empty(&self) -> bool {
self.table.is_empty()
}
#[inline]
#[must_use]
pub fn lookup(&self, addr: u64) -> Option<DispatchEntry> {
if addr >= self.cover {
return None;
}
let raw = *self
.table
.get(usize::try_from(addr >> self.page_bits).ok()?)?;
Some(match raw & !INDEX_MASK {
TAG_DIRECT => DispatchEntry::Direct(raw & INDEX_MASK),
TAG_SUBPAGE => DispatchEntry::SubPage,
TAG_UNASSIGNED => DispatchEntry::Unassigned,
_ => DispatchEntry::Mapped(raw & INDEX_MASK),
})
}
}