Implement AddressSpace

Add the first implementation to represent an address space (through the
`AddressSpace` struct), that can be used by different objects which have
their own inner memory map.
The implementation use an AVLTree (right now it's just an ordinary BST,
as rebalancing has not been implemented).
This commit is contained in:
Victor Mignot 2024-05-02 13:33:33 +02:00
parent 78852a7cc6
commit f28056da82
Signed by: dala
GPG key ID: 5E7F2CE1BEAFED3D
3 changed files with 475 additions and 1 deletions

2
src/core.rs Normal file
View file

@ -0,0 +1,2 @@
//! The core module contains each software component that is used to run the main features of a RISC-V hart and common devices.
pub mod address_space;

464
src/core/address_space.rs Normal file
View file

@ -0,0 +1,464 @@
//! The address space module provides an allowing developpers and users to
//! provide the `AddressSpace` struct, used for devices and buses that have their own memory map.
//! Thus, they can have their own set of other devices mapped to their `AddressSpace`.
use std::{cmp::max, sync::RwLock};
/// The potential errors that can be returned from operations within the address space.
pub enum AddressSpaceError {
/// Trying to map an object on an already use memory range.
RangeOverridingError,
}
/// The trait that an object should implement to be mapped within the address space.
/// Its goal is to call callbacks functions when specific operations are done within the
/// range to which the object is mapped.
pub trait Addressable {
/// Get the size of the mapped memory range for the device.
fn memory_range_size(&self) -> u32;
}
/// An elemental address range stored within the address space tree and the object mapped
/// to this range.
struct AddressRange<T>
where
T: Addressable + ?Sized,
{
/// The first address of the range.
start: u32,
/// The last address of the range.
end: u32,
/// A `RwLock` to the object mapped to the range. Allowing thus to deal with Read or Write operation within the Address Space.
mapped_object: RwLock<Box<T>>,
}
/// The underlying structure storing all the devices mapped to an address space.
/// It's a self-balancing Binary Search Tree (more specifically an AVL Tree) where each node
/// represents an address range to which is mapped an object.
struct AddressSpaceRangesTree<T>(Option<AddressSpaceRangeNode<T>>)
where
T: Addressable + ?Sized;
impl<T> AddressSpaceRangesTree<T>
where
T: Addressable + ?Sized,
{
/// Add the given `addressable` into the AddressSpace tree.
fn insert(&mut self, addressable: Box<T>, address: u32) -> Result<(), AddressSpaceError> {
let new_node = AddressSpaceRangeNode::new(AddressRange {
start: address,
end: address + addressable.memory_range_size() - 1,
mapped_object: RwLock::new(addressable),
});
if let Some(root) = self.0.as_mut() {
root.insert(new_node)
} else {
self.0 = Some(new_node);
Ok(())
}
}
/// Returns the object mapped to the given `address`.
fn get_mapped_object_to_address(&self, address: u32) -> Option<(&RwLock<Box<T>>, u32)> {
if let Some(root_node) = self.0.as_ref() {
root_node.get_object_from_address(address)
} else {
None
}
}
/// Create a new tree instance.
fn new() -> Self {
AddressSpaceRangesTree(None)
}
}
/// Represent a node of an AddressSpaceRangesTree.
struct AddressSpaceRangeNode<T>
where
T: Addressable + ?Sized,
{
/// The given range that this node store.
range: AddressRange<T>,
/// The left child of the node.
left: Option<Box<AddressSpaceRangeNode<T>>>,
/// The right child of the node.
right: Option<Box<AddressSpaceRangeNode<T>>>,
}
impl<T> AddressSpaceRangeNode<T>
where
T: Addressable + ?Sized,
{
/// Create a new node instance for an AddressSpaeRangesTree with the given `range`.
fn new(range: AddressRange<T>) -> Self {
AddressSpaceRangeNode {
range,
left: None,
right: None,
}
}
/// Return the height of the current node within the tree.
fn get_height(&self) -> usize {
if self.left.is_none() && self.right.is_none() {
0
} else if self.left.is_none() {
self.right.as_ref().unwrap().get_height() + 1
} else if self.right.is_none() {
self.left.as_ref().unwrap().get_height() + 1
} else {
max(
self.left.as_ref().unwrap().get_height(),
self.right.as_ref().unwrap().get_height(),
) + 1
}
}
/// Take the subtree where this node is the root and return whether this subtree is balanced or not.
/// The subtree is unbalanced if the difference in height between the left and the right child is superior to one.
/// This method is mainly called to check if the tree should be rebalanced to optimize the next operations.
fn is_unbalanced(&self) -> bool {
match (self.left.as_ref(), self.right.as_ref()) {
(None, None) => false,
(None, Some(right)) => right.get_height() > 1,
(Some(left), None) => left.get_height() > 1,
(Some(left), Some(right)) => left.get_height().abs_diff(right.get_height()) > 1,
}
}
/// Rebalance this node subtree if it's unbalanced.
fn rebalance_if_needed(&mut self) {
if !self.is_unbalanced() {}
// TODO: Add tree rebalancing
}
/// If this node has none, set the `new_node` as its left child.
/// Else, insert the `new_node` into the left child subtree.
fn insert_into_or_set_left_child(&mut self, new_node: Self) -> Result<(), AddressSpaceError> {
match self.left.as_mut() {
Some(node) => node.insert(new_node),
None => {
self.left = Some(Box::new(new_node));
Ok(())
}
}
}
/// If this node has none, set the `new_node` as its right child.
/// Else, insert the `new_node` into the right child subtree.
fn insert_into_or_set_right_child(&mut self, new_node: Self) -> Result<(), AddressSpaceError> {
match self.right.as_mut() {
Some(node) => node.insert(new_node),
None => {
self.right = Some(Box::new(new_node));
Ok(())
}
}
}
/// Insert the given `new_node` into the this node subtree.
fn insert(&mut self, new_node: Self) -> Result<(), AddressSpaceError> {
if self.range.start > new_node.range.end {
self.insert_into_or_set_left_child(new_node)
} else if self.range.end < new_node.range.start {
self.insert_into_or_set_right_child(new_node)
} else {
Err(AddressSpaceError::RangeOverridingError)
}?;
self.rebalance_if_needed();
Ok(())
}
/// Return the object mapped to the given `address`.
fn get_object_from_address(&self, address: u32) -> Option<(&RwLock<Box<T>>, u32)> {
if address >= self.range.start && address <= self.range.end {
Some((&self.range.mapped_object, self.range.start))
} else if address < self.range.start {
if let Some(left_child) = self.left.as_ref() {
left_child.get_object_from_address(address)
} else {
None
}
} else if address > self.range.end {
if let Some(right_child) = self.right.as_ref() {
right_child.get_object_from_address(address)
} else {
None
}
} else {
None
}
}
}
/// Represent a 32-bit address space to which elements of different nature can be mapped to specific address ranges.
/// From there, one will be able to the device mappe to a specific address.
pub struct AddressSpace<T>
where
T: Addressable + ?Sized,
{
/// Data structure storing the device mapping.
memory_map: AddressSpaceRangesTree<T>,
}
impl<T> AddressSpace<T>
where
T: Addressable + ?Sized,
{
/// Create a new address space object.
pub fn new() -> Self {
AddressSpace {
memory_map: AddressSpaceRangesTree::new(),
}
}
/// Map the given memory-mappable `object` to the specified `address` and `range`.
pub fn map_object(&mut self, object: Box<T>, address: u32) -> Result<(), AddressSpaceError> {
self.memory_map.insert(object, address)
}
/// Return the object mapped to the given `address` on the address space.
pub fn get_mapped_object_to_address(&self, address: u32) -> Option<(&RwLock<Box<T>>, u32)> {
self.memory_map.get_mapped_object_to_address(address)
}
}
impl<T> Default for AddressSpace<T>
where
T: Addressable + ?Sized,
{
fn default() -> Self {
AddressSpace::new()
}
}
#[cfg(test)]
mod tests {
use super::{AddressSpace, Addressable};
#[derive(Clone, Copy)]
struct TestObject {
size: u32,
}
impl TestObject {
fn new(size: u32) -> Self {
TestObject { size }
}
}
impl Addressable for TestObject {
fn memory_range_size(&self) -> u32 {
self.size
}
}
#[test]
fn empty_is_none() {
let address_space: AddressSpace<TestObject> = AddressSpace::new();
assert!(address_space.memory_map.0.is_none())
}
#[test]
fn fetch_from_empty() {
let address_space: AddressSpace<TestObject> = AddressSpace::new();
assert!(address_space.get_mapped_object_to_address(0x100).is_none());
}
#[test]
fn insert_on_empty() {
let mut address_space: AddressSpace<TestObject> = AddressSpace::new();
let object = TestObject::new(0x10);
let _ = address_space.map_object(Box::new(object), 0x100);
assert!(address_space.memory_map.0.is_some());
for address in 0x100..0x10 {
let mapped_device = address_space.get_mapped_object_to_address(address);
assert!(mapped_device.is_some());
assert_eq!(
mapped_device.unwrap().0.read().unwrap().memory_range_size(),
0x10
);
}
}
#[test]
fn overriding_insert() {
let mut address_space: AddressSpace<TestObject> = AddressSpace::new();
let object1 = TestObject::new(0x10);
let object2 = TestObject::new(0x10);
assert!(address_space.map_object(Box::new(object1), 0x100).is_ok());
assert!(address_space.map_object(Box::new(object2), 0x105).is_err());
}
#[test]
fn left_insert() {
let mut address_space: AddressSpace<TestObject> = AddressSpace::new();
let object1 = TestObject::new(0x1);
let object2 = TestObject::new(0x1);
let _ = address_space.map_object(Box::new(object1), 0x110);
let _ = address_space.map_object(Box::new(object2), 0x100);
assert_eq!(
address_space.memory_map.0.as_ref().unwrap().range.start,
0x110
);
assert!(address_space.memory_map.0.as_ref().unwrap().right.is_none());
assert_eq!(
address_space
.memory_map
.0
.unwrap()
.left
.unwrap()
.range
.start,
0x100
);
}
#[test]
fn right_insert() {
let mut address_space: AddressSpace<TestObject> = AddressSpace::new();
let object1 = TestObject::new(0x1);
let object2 = TestObject::new(0x1);
let _ = address_space.map_object(Box::new(object1), 0x100);
let _ = address_space.map_object(Box::new(object2), 0x110);
assert_eq!(
address_space.memory_map.0.as_ref().unwrap().range.start,
0x100
);
assert!(address_space.memory_map.0.as_ref().unwrap().left.is_none());
assert_eq!(
address_space
.memory_map
.0
.unwrap()
.right
.unwrap()
.range
.start,
0x110
);
}
#[test]
fn linear_left_height() {
let mut address_space: AddressSpace<TestObject> = AddressSpace::new();
let object1 = TestObject::new(0x1);
let object2 = TestObject::new(0x1);
let object3 = TestObject::new(0x1);
let _ = address_space.map_object(Box::new(object1), 0x200);
let _ = address_space.map_object(Box::new(object2), 0x150);
let _ = address_space.map_object(Box::new(object3), 0x100);
assert_eq!(address_space.memory_map.0.unwrap().get_height(), 2);
}
#[test]
fn linear_right_height() {
let mut address_space: AddressSpace<TestObject> = AddressSpace::new();
let object1 = TestObject::new(0x1);
let object2 = TestObject::new(0x1);
let object3 = TestObject::new(0x1);
let _ = address_space.map_object(Box::new(object1), 0x100);
let _ = address_space.map_object(Box::new(object2), 0x150);
let _ = address_space.map_object(Box::new(object3), 0x200);
assert_eq!(address_space.memory_map.0.unwrap().get_height(), 2);
}
#[test]
fn balanced_tree_height() {
let mut address_space: AddressSpace<TestObject> = AddressSpace::new();
let object_root = TestObject::new(0x1);
let object_left = TestObject::new(0x1);
let object_right = TestObject::new(0x1);
let object_left_left = TestObject::new(0x1);
let object_right_right = TestObject::new(0x1);
let _ = address_space.map_object(Box::new(object_root), 0x100);
let _ = address_space.map_object(Box::new(object_left), 0x50);
let _ = address_space.map_object(Box::new(object_right), 0x150);
let _ = address_space.map_object(Box::new(object_left_left), 0x0);
let _ = address_space.map_object(Box::new(object_right_right), 0x200);
assert_eq!(address_space.memory_map.0.unwrap().get_height(), 2);
}
#[test]
fn unbalanced_tree_height() {
let mut address_space: AddressSpace<TestObject> = AddressSpace::new();
let object_root = TestObject::new(0x1);
let object_left = TestObject::new(0x1);
let object_right = TestObject::new(0x1);
let object_left_left = TestObject::new(0x1);
let object_left_left_left = TestObject::new(0x1);
let _ = address_space.map_object(Box::new(object_root), 0x100);
let _ = address_space.map_object(Box::new(object_left), 0x50);
let _ = address_space.map_object(Box::new(object_right), 0x150);
let _ = address_space.map_object(Box::new(object_left_left), 0x25);
let _ = address_space.map_object(Box::new(object_left_left_left), 0x0);
assert_eq!(address_space.memory_map.0.unwrap().get_height(), 3);
}
#[test]
fn unbalanced_check() {
let mut address_space: AddressSpace<TestObject> = AddressSpace::new();
let object_root = TestObject::new(0x1);
let object_left = TestObject::new(0x1);
let object_right = TestObject::new(0x1);
let object_left_left = TestObject::new(0x1);
let object_left_left_left = TestObject::new(0x1);
let _ = address_space.map_object(Box::new(object_root), 0x100);
let _ = address_space.map_object(Box::new(object_left), 0x50);
let _ = address_space.map_object(Box::new(object_right), 0x150);
let _ = address_space.map_object(Box::new(object_left_left), 0x25);
let _ = address_space.map_object(Box::new(object_left_left_left), 0x0);
assert!(address_space.memory_map.0.unwrap().is_unbalanced())
}
#[test]
fn balanced_check() {
let mut address_space: AddressSpace<TestObject> = AddressSpace::new();
let object_root = TestObject::new(0x1);
let object_left = TestObject::new(0x1);
let object_right = TestObject::new(0x1);
let object_left_left = TestObject::new(0x1);
let object_right_right = TestObject::new(0x1);
let _ = address_space.map_object(Box::new(object_root), 0x100);
let _ = address_space.map_object(Box::new(object_left), 0x50);
let _ = address_space.map_object(Box::new(object_right), 0x150);
let _ = address_space.map_object(Box::new(object_left_left), 0x0);
let _ = address_space.map_object(Box::new(object_right_right), 0x200);
assert!(!address_space.memory_map.0.unwrap().is_unbalanced());
}
}

View file

@ -1,7 +1,15 @@
//! dremu is a RISC-V 32-bits CPU emulator.
#![deny(warnings)]
#![deny(
warnings,
missing_docs,
clippy::all,
clippy::missing_docs_in_private_items,
rustdoc::missing_crate_level_docs
)]
mod address_space;
pub mod core;
fn main() {
println!("Hello, world!");
}