diff --git a/src/core.rs b/src/core.rs new file mode 100644 index 0000000..e03d830 --- /dev/null +++ b/src/core.rs @@ -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; diff --git a/src/core/address_space.rs b/src/core/address_space.rs new file mode 100644 index 0000000..d282369 --- /dev/null +++ b/src/core/address_space.rs @@ -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 +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>, +} + +/// 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(Option>) +where + T: Addressable + ?Sized; + +impl AddressSpaceRangesTree +where + T: Addressable + ?Sized, +{ + /// Add the given `addressable` into the AddressSpace tree. + fn insert(&mut self, addressable: Box, 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>, 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 +where + T: Addressable + ?Sized, +{ + /// The given range that this node store. + range: AddressRange, + /// The left child of the node. + left: Option>>, + /// The right child of the node. + right: Option>>, +} + +impl AddressSpaceRangeNode +where + T: Addressable + ?Sized, +{ + /// Create a new node instance for an AddressSpaeRangesTree with the given `range`. + fn new(range: AddressRange) -> 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>, 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 +where + T: Addressable + ?Sized, +{ + /// Data structure storing the device mapping. + memory_map: AddressSpaceRangesTree, +} + +impl AddressSpace +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, 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>, u32)> { + self.memory_map.get_mapped_object_to_address(address) + } +} + +impl Default for AddressSpace +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 = AddressSpace::new(); + + assert!(address_space.memory_map.0.is_none()) + } + + #[test] + fn fetch_from_empty() { + let address_space: AddressSpace = AddressSpace::new(); + + assert!(address_space.get_mapped_object_to_address(0x100).is_none()); + } + + #[test] + fn insert_on_empty() { + let mut address_space: AddressSpace = 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 = 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 = 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 = 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 = 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 = 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 = 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 = 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 = 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 = 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()); + } +} diff --git a/src/main.rs b/src/main.rs index 23a2acd..3b35055 100644 --- a/src/main.rs +++ b/src/main.rs @@ -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!"); }