use alloc::collections::BTreeMap;
use alloc::string::{String, ToString};
use alloc::sync::Arc;
use alloc::vec::Vec;
use crate::core::error::{BusError, Error, Result};
use crate::core::space::{
AddressSpace, Mapping as SpaceMapping, MappingId, MemAttrs, Perms, RamStore, Region,
};
use crate::core::state::{Sink, Source};
use crate::core::sync::{self, LockRank};
pub const PAGE_SIZE: u64 = 4096;
pub type Prot = Perms;
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct MappingInfo {
pub base: u64,
pub len: u64,
pub prot: Prot,
pub name: String,
}
#[derive(Debug)]
struct Vma {
len: u64,
prot: Prot,
store: Arc<RamStore>,
shared: bool,
mapping: MappingId,
name: String,
}
#[derive(Debug)]
struct Backing {
store: Arc<RamStore>,
shared: bool,
}
impl Vma {
fn effective(&self) -> Perms {
if self.shared {
self.prot.without(Perms::WRITE)
} else {
self.prot
}
}
}
#[derive(Debug)]
struct Inner {
vmas: BTreeMap<u64, Vma>,
hint: u64,
floor: u64,
}
#[derive(Debug)]
pub struct UserMemory {
space: Arc<AddressSpace>,
inner: sync::Mutex<Inner>,
}
impl UserMemory {
#[must_use]
pub fn new(bits: u32) -> UserMemory {
let space = AddressSpace::new("user", bits);
let top = if bits >= 64 {
!(PAGE_SIZE - 1)
} else {
1u64 << bits
};
UserMemory {
space: Arc::new(space),
inner: sync::Mutex::with_rank(
LockRank::MACHINE,
Inner {
vmas: BTreeMap::new(),
hint: top,
floor: PAGE_SIZE,
},
),
}
}
#[must_use]
pub fn space(&self) -> &Arc<AddressSpace> {
&self.space
}
pub fn set_placement(&self, floor: u64, top: u64) -> Result<()> {
if !floor.is_multiple_of(PAGE_SIZE) || !top.is_multiple_of(PAGE_SIZE) || floor >= top {
return Err(config(alloc::format!(
"placement range {floor:#x}..{top:#x} must be page aligned and non-empty"
)));
}
let mut inner = self.inner.lock();
inner.floor = floor;
inner.hint = top;
Ok(())
}
pub fn map_at(&self, base: u64, len: u64, prot: Prot, name: &str) -> Result<()> {
self.check_range(base, len)?;
let mut inner = self.inner.lock();
self.unmap_locked(&mut inner, base, len)?;
self.insert_locked(&mut inner, base, len, prot, name, None)
}
pub fn map(&self, len: u64, prot: Prot, name: &str) -> Result<u64> {
if len == 0 || !len.is_multiple_of(PAGE_SIZE) {
return Err(config(alloc::format!(
"mapping length {len:#x} must be a non-zero multiple of {PAGE_SIZE:#x}"
)));
}
let mut inner = self.inner.lock();
let base = Self::free_range(&inner, len)?;
self.insert_locked(&mut inner, base, len, prot, name, None)?;
Ok(base)
}
pub fn find_free(&self, len: u64) -> Result<u64> {
Self::free_range(&self.inner.lock(), len)
}
pub fn unmap(&self, base: u64, len: u64) -> Result<()> {
self.check_range(base, len)?;
let mut inner = self.inner.lock();
self.unmap_locked(&mut inner, base, len)
}
pub fn protect(&self, base: u64, len: u64, prot: Prot) -> Result<()> {
self.check_range(base, len)?;
let end = base + len;
let mut inner = self.inner.lock();
for vbase in Self::overlapping(&inner, base, end) {
let vend = vbase + inner.vmas[&vbase].len;
if base <= vbase && end >= vend {
let vma = inner.vmas.get_mut(&vbase).expect("just looked it up");
vma.prot = prot;
let effective = vma.effective();
let id = vma.mapping;
self.space.topology().reprotect(id, effective)?;
continue;
}
let Some(vma) = inner.vmas.remove(&vbase) else {
continue;
};
self.space.topology().unmap(vma.mapping)?;
let head = base.max(vbase);
let tail = end.min(vend);
let src = |offset| Some((Arc::clone(&vma.store), offset));
if vbase < head {
self.insert_locked(&mut inner, vbase, head - vbase, vma.prot, &vma.name, src(0))?;
}
self.insert_locked(
&mut inner,
head,
tail - head,
prot,
&vma.name,
src(head - vbase),
)?;
if vend > tail {
self.insert_locked(
&mut inner,
tail,
vend - tail,
vma.prot,
&vma.name,
src(tail - vbase),
)?;
}
}
Ok(())
}
pub fn resolve_write_fault(&self, addr: u64) -> Result<bool> {
let mut inner = self.inner.lock();
let Some((&vbase, vma)) = inner.vmas.range(..=addr).next_back() else {
return Ok(false);
};
if vbase + vma.len <= addr || !vma.prot.contains(Prot::WRITE) || !vma.shared {
return Ok(false);
}
self.break_cow_locked(&mut inner, vbase)?;
Ok(true)
}
#[must_use]
pub fn is_shared(&self, addr: u64) -> bool {
let inner = self.inner.lock();
inner
.vmas
.range(..=addr)
.next_back()
.is_some_and(|(base, vma)| *base + vma.len > addr && vma.shared)
}
#[must_use]
pub fn mappings(&self) -> Vec<MappingInfo> {
let inner = self.inner.lock();
inner
.vmas
.iter()
.map(|(base, vma)| MappingInfo {
base: *base,
len: vma.len,
prot: vma.prot,
name: vma.name.clone(),
})
.collect()
}
#[must_use]
pub fn mapping_at(&self, addr: u64) -> Option<MappingInfo> {
let inner = self.inner.lock();
inner
.vmas
.range(..=addr)
.next_back()
.filter(|(base, vma)| **base + vma.len > addr)
.map(|(base, vma)| MappingInfo {
base: *base,
len: vma.len,
prot: vma.prot,
name: vma.name.clone(),
})
}
#[must_use]
pub fn mapped_bytes(&self) -> u64 {
let inner = self.inner.lock();
inner.vmas.values().map(|vma| vma.len).sum()
}
pub fn read_bytes(&self, addr: u64, dst: &mut [u8]) -> Result<()> {
self.check_prot(addr, dst.len() as u64, Prot::READ)?;
self.space.read_bytes(addr, dst, MemAttrs::DEFAULT)?;
Ok(())
}
pub fn write_bytes(&self, addr: u64, src: &[u8]) -> Result<()> {
self.check_prot(addr, src.len() as u64, Prot::WRITE)?;
{
let mut inner = self.inner.lock();
self.break_cow_range(&mut inner, addr, src.len() as u64)?;
}
self.space.write_bytes(addr, src, MemAttrs::DEFAULT)?;
Ok(())
}
pub fn init_bytes(&self, addr: u64, src: &[u8]) -> Result<()> {
{
let mut inner = self.inner.lock();
self.break_cow_range(&mut inner, addr, src.len() as u64)?;
}
let (store, offset) = self.store_at(addr, src.len() as u64)?;
store.write_at(offset, src)?;
Ok(())
}
pub fn read_u64(&self, addr: u64) -> Result<u64> {
let mut buf = [0u8; 8];
self.read_bytes(addr, &mut buf)?;
Ok(u64::from_le_bytes(buf))
}
pub fn write_u64(&self, addr: u64, value: u64) -> Result<()> {
self.write_bytes(addr, &value.to_le_bytes())
}
pub fn duplicate(&self) -> Result<Arc<UserMemory>> {
let bits = self.space.bits();
let copy = Arc::new(UserMemory::new(bits));
let (floor, hint, ranges) = {
let mut inner = self.inner.lock();
let bases: Vec<u64> = inner.vmas.keys().copied().collect();
let mut ranges = Vec::with_capacity(bases.len());
for base in bases {
let vma = inner.vmas.get_mut(&base).expect("a base we just listed");
vma.shared = true;
let (id, effective) = (vma.mapping, vma.effective());
let (len, prot, name, store) =
(vma.len, vma.prot, vma.name.clone(), Arc::clone(&vma.store));
self.space.topology().reprotect(id, effective)?;
ranges.push((base, len, prot, name, store));
}
(inner.floor, inner.hint, ranges)
};
{
let mut inner = copy.inner.lock();
inner.floor = floor;
inner.hint = hint;
for (base, len, prot, name, store) in ranges {
copy.insert_store_locked(
&mut inner,
base,
len,
prot,
&name,
Backing {
store,
shared: true,
},
)?;
}
}
Ok(copy)
}
pub fn save<S: Sink + ?Sized>(&self, sink: &mut S) -> Result<()> {
let (floor, hint) = {
let inner = self.inner.lock();
(inner.floor, inner.hint)
};
sink.write_u32(self.space.bits())?;
sink.write_u64(floor)?;
sink.write_u64(hint)?;
let maps = self.mappings();
sink.write_seq_len(maps.len() as u64)?;
for info in maps {
sink.write_u64(info.base)?;
sink.write_u64(info.len)?;
sink.write_u8(info.prot.0)?;
sink.write_str(&info.name)?;
let mut buf = alloc::vec![0u8; info.len as usize];
self.raw_read(info.base, &mut buf)?;
sink.write_all(&buf)?;
}
Ok(())
}
pub fn load<'a, S: Source<'a> + ?Sized>(&self, source: &mut S) -> Result<()> {
let bits = source.read_u32()?;
if bits != self.space.bits() {
return Err(Error::State(alloc::format!(
"snapshot holds a {bits}-bit map and this one is {}-bit",
self.space.bits()
)));
}
let floor = source.read_u64()?;
let hint = source.read_u64()?;
for info in self.mappings() {
self.unmap(info.base, info.len)?;
}
{
let mut inner = self.inner.lock();
inner.floor = floor;
inner.hint = hint;
}
let count = source.read_seq_len(25)?;
for _ in 0..count {
let base = source.read_u64()?;
let len = source.read_u64()?;
let prot = Perms(source.read_u8()?);
let name = source.read_str()?;
let bytes = source.take(usize::try_from(len).map_err(|_| {
Error::State(alloc::format!(
"mapping of {len:#x} bytes does not fit in memory"
))
})?)?;
self.map_at(base, len, prot, name)?;
self.init_bytes(base, bytes)?;
}
Ok(())
}
fn raw_read(&self, addr: u64, dst: &mut [u8]) -> Result<()> {
let (store, offset) = self.store_at(addr, dst.len() as u64)?;
store.read_at(offset, dst)?;
Ok(())
}
fn store_at(&self, addr: u64, len: u64) -> Result<(Arc<RamStore>, u64)> {
let inner = self.inner.lock();
let Some((base, vma)) = inner.vmas.range(..=addr).next_back() else {
return Err(Error::Bus(BusError::Unassigned));
};
let offset = addr - *base;
if offset.checked_add(len).is_none_or(|end| end > vma.len) {
return Err(Error::Bus(BusError::Unassigned));
}
Ok((Arc::clone(&vma.store), offset))
}
fn insert_locked(
&self,
inner: &mut Inner,
base: u64,
len: u64,
prot: Prot,
name: &str,
src: Option<(Arc<RamStore>, u64)>,
) -> Result<()> {
let store = Arc::new(RamStore::new(len));
if let Some((from, offset)) = src {
copy_between(&from, offset, &store, 0, len)?;
}
self.insert_store_locked(
inner,
base,
len,
prot,
name,
Backing {
store,
shared: false,
},
)
}
fn insert_store_locked(
&self,
inner: &mut Inner,
base: u64,
len: u64,
prot: Prot,
name: &str,
backing: Backing,
) -> Result<()> {
let Backing { store, shared } = backing;
let effective = if shared {
prot.without(Perms::WRITE)
} else {
prot
};
let region = Region::ram(name, Arc::clone(&store));
let mapping = self
.space
.topology()
.map_with_perms(region, base, effective)?;
inner.vmas.insert(
base,
Vma {
len,
prot,
store,
shared,
mapping,
name: name.to_string(),
},
);
Ok(())
}
fn break_cow_locked(&self, inner: &mut Inner, vbase: u64) -> Result<()> {
let Some(vma) = inner.vmas.get_mut(&vbase) else {
return Ok(());
};
if !vma.shared {
return Ok(());
}
let private = Arc::new(RamStore::new(vma.len));
copy_between(&vma.store, 0, &private, 0, vma.len)?;
vma.store = private;
vma.shared = false;
let region = Region::ram(&vma.name, Arc::clone(&vma.store));
self.space.topology().replace(
vma.mapping,
SpaceMapping::new(region, vbase).with_perms(vma.prot),
)
}
fn break_cow_range(&self, inner: &mut Inner, base: u64, len: u64) -> Result<()> {
if len == 0 {
return Ok(());
}
let end = base.saturating_add(len);
for vbase in Self::overlapping(inner, base, end) {
self.break_cow_locked(inner, vbase)?;
}
Ok(())
}
fn unmap_locked(&self, inner: &mut Inner, base: u64, len: u64) -> Result<()> {
let end = base + len;
for vbase in Self::overlapping(inner, base, end) {
let Some(vma) = inner.vmas.remove(&vbase) else {
continue;
};
self.space.topology().unmap(vma.mapping)?;
let vend = vbase + vma.len;
if vbase < base {
self.insert_locked(
inner,
vbase,
base - vbase,
vma.prot,
&vma.name,
Some((Arc::clone(&vma.store), 0)),
)?;
}
if vend > end {
self.insert_locked(
inner,
end,
vend - end,
vma.prot,
&vma.name,
Some((Arc::clone(&vma.store), end - vbase)),
)?;
}
}
Ok(())
}
fn overlapping(inner: &Inner, base: u64, end: u64) -> Vec<u64> {
let mut out = Vec::new();
if let Some((vbase, vma)) = inner.vmas.range(..base).next_back()
&& *vbase + vma.len > base
{
out.push(*vbase);
}
out.extend(inner.vmas.range(base..end).map(|(vbase, _)| *vbase));
out
}
fn free_range(inner: &Inner, len: u64) -> Result<u64> {
let mut candidate_end = inner.hint;
loop {
if candidate_end < inner.floor.saturating_add(len) {
return Err(config(alloc::format!(
"no free range of {len:#x} bytes above {:#x}",
inner.floor
)));
}
let base = candidate_end - len;
let clash = inner
.vmas
.range(..=base)
.next_back()
.filter(|(vbase, vma)| **vbase + vma.len > base)
.map(|(vbase, _)| *vbase)
.or_else(|| {
inner
.vmas
.range(base..candidate_end)
.next()
.map(|(vbase, _)| *vbase)
});
match clash {
Some(vbase) => candidate_end = vbase,
None => return Ok(base),
}
}
}
fn check_range(&self, base: u64, len: u64) -> Result<()> {
if !base.is_multiple_of(PAGE_SIZE) || !len.is_multiple_of(PAGE_SIZE) {
return Err(config(alloc::format!(
"range {base:#x}..+{len:#x} must be aligned to {PAGE_SIZE:#x}"
)));
}
let end = base.checked_add(len).ok_or_else(|| {
config(alloc::format!(
"range {base:#x}..+{len:#x} wraps the address space"
))
})?;
if self.space.bits() < 64 && end > (1u64 << self.space.bits()) {
return Err(config(alloc::format!(
"range {base:#x}..{end:#x} does not fit a {}-bit address space",
self.space.bits()
)));
}
Ok(())
}
fn check_prot(&self, addr: u64, len: u64, want: Prot) -> Result<()> {
if len == 0 {
return Ok(());
}
let end = addr
.checked_add(len)
.ok_or(Error::Bus(BusError::Unassigned))?;
let inner = self.inner.lock();
let mut cursor = addr;
while cursor < end {
let Some((base, vma)) = inner.vmas.range(..=cursor).next_back() else {
return Err(Error::Bus(BusError::Unassigned));
};
let vend = *base + vma.len;
if vend <= cursor {
return Err(Error::Bus(BusError::Unassigned));
}
if !vma.prot.contains(want) {
return Err(Error::Bus(BusError::BadAccess));
}
cursor = vend;
}
Ok(())
}
}
fn copy_between(
from: &RamStore,
from_off: u64,
to: &RamStore,
to_off: u64,
len: u64,
) -> Result<()> {
const CHUNK: u64 = 64 * 1024;
let mut buf = alloc::vec![0u8; usize::try_from(len.min(CHUNK)).unwrap_or(0)];
let mut done = 0u64;
while done < len {
let n = (len - done).min(CHUNK);
let piece = &mut buf[..usize::try_from(n).map_err(|_| Error::Bus(BusError::BadAccess))?];
from.read_at(from_off + done, piece)?;
to.write_at(to_off + done, piece)?;
done += n;
}
Ok(())
}
fn config(message: String) -> Error {
Error::Config {
at: "user memory".to_string(),
message,
}
}