use core::fmt::Debug;
use patina_pi::dxe_services;
use r_efi::efi;
use crate::error;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum Error {
InvalidStateTransition,
BlockOutsideRange,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum IoBlock {
Unallocated(dxe_services::IoSpaceDescriptor),
Allocated(dxe_services::IoSpaceDescriptor),
}
#[derive(Debug)]
pub enum StateTransition {
Add(dxe_services::GcdIoType),
Remove,
Allocate(efi::Handle, Option<efi::Handle>),
Free,
}
#[derive(Debug, PartialEq)]
pub enum IoBlockSplit<'a> {
Same(&'a mut IoBlock),
Before(&'a mut IoBlock, IoBlock),
After(&'a mut IoBlock, IoBlock),
Middle(&'a mut IoBlock, IoBlock, IoBlock),
}
impl IoBlock {
pub fn merge(&mut self, other: &mut IoBlock) -> bool {
if self.is_same_state(other) && self.end() == other.start() {
self.as_mut().length += other.as_ref().length;
other.as_mut().length = 0;
true
} else {
false
}
}
pub fn split(&mut self, base_address: usize, len: usize) -> Result<IoBlockSplit<'_>, Error> {
let start = base_address;
let end = base_address + len;
if !(self.start() <= start && start < end && end <= self.end()) {
return Err(Error::BlockOutsideRange);
}
if self.start() == start && end == self.end() {
return Ok(IoBlockSplit::Same(self));
}
if self.start() == start && end < self.end() {
let mut next = IoBlock::clone(self);
self.as_mut().base_address = base_address as u64;
self.as_mut().length = len as u64;
next.as_mut().base_address = end as u64;
next.as_mut().length -= len as u64;
return Ok(IoBlockSplit::Before(self, next));
}
if self.start() < start && end == self.end() {
let mut next = IoBlock::clone(self);
self.as_mut().length -= len as u64;
next.as_mut().base_address = base_address as u64;
next.as_mut().length = len as u64;
return Ok(IoBlockSplit::After(self, next));
}
if self.start() < start && end < self.end() {
let mut next = IoBlock::clone(self);
let mut last = IoBlock::clone(self);
self.as_mut().length = (start - self.start()) as u64;
next.as_mut().base_address = base_address as u64;
next.as_mut().length = len as u64;
last.as_mut().length = (last.end() - end) as u64;
last.as_mut().base_address = end as u64;
return Ok(IoBlockSplit::Middle(self, next, last));
}
unreachable!()
}
pub fn split_state_transition(
&mut self,
base_address: usize,
len: usize,
transition: StateTransition,
) -> Result<IoBlockSplit<'_>, Error> {
let mut split = self.split(base_address, len)?;
match &mut split {
IoBlockSplit::Same(mb) => {
mb.state_transition(transition)?;
}
IoBlockSplit::Before(mb, next) => {
if let Err(e) = mb.state_transition(transition) {
mb.merge(next);
error!(e);
}
}
IoBlockSplit::After(prev, mb) => {
if let Err(e) = mb.state_transition(transition) {
prev.merge(mb);
error!(e)
}
}
IoBlockSplit::Middle(prev, mb, next) => {
if let Err(e) = mb.state_transition(transition) {
mb.merge(next);
prev.merge(mb);
error!(e)
}
}
}
Ok(split)
}
pub fn is_same_state(&self, other: &IoBlock) -> bool {
matches!((self, other),
(IoBlock::Unallocated(self_desc), IoBlock::Unallocated(other_desc)) |
(IoBlock::Allocated(self_desc), IoBlock::Allocated(other_desc))
if self_desc.io_type == other_desc.io_type
&& self_desc.device_handle == other_desc.device_handle
&& self_desc.image_handle == other_desc.image_handle
)
}
pub fn state_transition(&mut self, transition: StateTransition) -> Result<(), Error> {
match transition {
StateTransition::Add(io_type) => self.add_transition(io_type),
StateTransition::Remove => self.remove_transition(),
StateTransition::Allocate(image_handle, device_handle) => {
self.allocate_transition(image_handle, device_handle)
}
StateTransition::Free => self.free_transition(),
}
}
pub fn add_transition(&mut self, io_type: dxe_services::GcdIoType) -> Result<(), Error> {
match self {
Self::Unallocated(id)
if id.io_type == dxe_services::GcdIoType::NonExistent
&& io_type != dxe_services::GcdIoType::NonExistent =>
{
id.io_type = io_type;
Ok(())
}
_ => Err(Error::InvalidStateTransition),
}
}
pub fn remove_transition(&mut self) -> Result<(), Error> {
match self {
Self::Unallocated(id) if id.io_type != dxe_services::GcdIoType::NonExistent => {
id.io_type = dxe_services::GcdIoType::NonExistent;
Ok(())
}
_ => Err(Error::InvalidStateTransition),
}
}
pub fn allocate_transition(
&mut self,
image_handle: efi::Handle,
device_handle: Option<efi::Handle>,
) -> Result<(), Error> {
match self {
Self::Unallocated(id) if id.io_type != dxe_services::GcdIoType::NonExistent => {
id.image_handle = image_handle;
if let Some(device_handle) = device_handle {
id.device_handle = device_handle;
}
*self = Self::Allocated(*id);
Ok(())
}
_ => Err(Error::InvalidStateTransition),
}
}
pub fn free_transition(&mut self) -> Result<(), Error> {
match self {
Self::Allocated(id) if id.io_type != dxe_services::GcdIoType::NonExistent => {
id.image_handle = 0 as efi::Handle;
id.device_handle = 0 as efi::Handle;
*self = Self::Unallocated(*id);
Ok(())
}
_ => Err(Error::InvalidStateTransition),
}
}
pub fn start(&self) -> usize {
self.as_ref().base_address as usize
}
pub fn end(&self) -> usize {
(self.as_ref().base_address + self.as_ref().length) as usize
}
pub fn len(&self) -> usize {
self.as_ref().length as usize
}
#[allow(dead_code)]
pub fn is_empty(&self) -> bool {
self.len() == 0
}
}
impl AsRef<dxe_services::IoSpaceDescriptor> for IoBlock {
fn as_ref(&self) -> &dxe_services::IoSpaceDescriptor {
match self {
IoBlock::Unallocated(msd) | IoBlock::Allocated(msd) => msd,
}
}
}
impl AsMut<dxe_services::IoSpaceDescriptor> for IoBlock {
fn as_mut(&mut self) -> &mut dxe_services::IoSpaceDescriptor {
match self {
IoBlock::Unallocated(msd) | IoBlock::Allocated(msd) => msd,
}
}
}
#[cfg(test)]
mod io_block_tests {
use core::panic;
use super::*;
use dxe_services::{GcdIoType, IoSpaceDescriptor};
#[test]
fn test_blocks_can_merge() {
let mut block1 = IoBlock::Allocated(IoSpaceDescriptor {
base_address: 0,
length: 10,
io_type: GcdIoType::NonExistent,
device_handle: core::ptr::null_mut(),
image_handle: core::ptr::null_mut(),
});
let mut block2 = IoBlock::Allocated(IoSpaceDescriptor {
base_address: 10,
length: 10,
io_type: GcdIoType::NonExistent,
device_handle: core::ptr::null_mut(),
image_handle: core::ptr::null_mut(),
});
assert!(block1.merge(&mut block2));
assert_eq!(block1.as_ref().length, 20);
assert_eq!(block2.as_ref().length, 0);
let mut block3 = IoBlock::Unallocated(IoSpaceDescriptor {
base_address: 20,
length: 10,
io_type: GcdIoType::NonExistent,
device_handle: core::ptr::null_mut(),
image_handle: core::ptr::null_mut(),
});
assert!(!block1.merge(&mut block3));
assert_eq!(block1.len(), 20);
assert_eq!(block3.len(), 10);
}
#[test]
fn test_blocks_can_split() {
let mut block = IoBlock::Allocated(IoSpaceDescriptor {
base_address: 10,
length: 10,
io_type: GcdIoType::NonExistent,
device_handle: core::ptr::null_mut(),
image_handle: core::ptr::null_mut(),
});
assert_eq!(Err(Error::BlockOutsideRange), block.split(5, 10));
assert_eq!(Err(Error::BlockOutsideRange), block.split(15, 10));
match block.clone().split(10, 10).unwrap() {
IoBlockSplit::Same(_) => {}
_ => panic!("Expected Same"),
}
match block.clone().split(10, 5).unwrap() {
IoBlockSplit::Before(before, after) => {
assert_eq!(before.len(), 5);
assert_eq!(after.len(), 5);
}
_ => panic!("Expected Before"),
}
match block.clone().split(12, 5).unwrap() {
IoBlockSplit::Middle(before, middle, after) => {
assert_eq!(before.len(), 2);
assert_eq!(middle.len(), 5);
assert_eq!(after.len(), 3);
}
_ => panic!("Expected Middle"),
}
match block.clone().split(15, 5).unwrap() {
IoBlockSplit::After(before, after) => {
assert_eq!(before.len(), 5);
assert_eq!(after.len(), 5);
}
_ => panic!("Expected After"),
}
}
#[test]
fn test_abort_split_transition() {
let mut block = IoBlock::Allocated(IoSpaceDescriptor {
base_address: 10,
length: 10,
io_type: GcdIoType::NonExistent,
device_handle: core::ptr::null_mut(),
image_handle: core::ptr::null_mut(),
});
let block_check = block;
let status = block.split_state_transition(10, 10, StateTransition::Add(GcdIoType::Io));
assert_eq!(status, Err(Error::InvalidStateTransition));
assert_eq!(block, block_check);
let status = block.split_state_transition(10, 5, StateTransition::Free);
assert_eq!(status, Err(Error::InvalidStateTransition));
assert_eq!(block, block_check);
let status = block.split_state_transition(15, 5, StateTransition::Allocate(0 as efi::Handle, None));
assert_eq!(status, Err(Error::InvalidStateTransition));
assert_eq!(block, block_check);
let status = block.split_state_transition(12, 5, StateTransition::Remove);
assert_eq!(status, Err(Error::InvalidStateTransition));
assert_eq!(block, block_check);
}
#[test]
fn test_transition_types() {
let block = IoBlock::Unallocated(IoSpaceDescriptor {
base_address: 50,
length: 50,
io_type: GcdIoType::NonExistent,
device_handle: core::ptr::null_mut(),
image_handle: core::ptr::null_mut(),
});
if let Ok(IoBlockSplit::Before(b1, b2)) =
block.clone().split_state_transition(50, 25, StateTransition::Add(GcdIoType::Io))
{
assert_eq!(b1.as_ref().io_type, GcdIoType::Io);
assert_eq!(b2.as_ref().io_type, GcdIoType::NonExistent);
} else {
panic!("Expected Ok. Test add transition failed.");
}
let mut b1 = block;
b1.as_mut().io_type = GcdIoType::Io;
if let Ok(IoBlockSplit::Before(b1, b2)) = b1.split_state_transition(50, 25, StateTransition::Remove) {
assert_eq!(b1.as_ref().io_type, GcdIoType::NonExistent);
assert_eq!(b2.as_ref().io_type, GcdIoType::Io);
} else {
panic!("Expected Ok. Test remove transition failed.");
}
let mut b1 = block;
b1.as_mut().io_type = GcdIoType::Io;
if let Ok(IoBlockSplit::Before(b1, b2)) =
b1.split_state_transition(50, 25, StateTransition::Allocate(0 as efi::Handle, Some(1 as efi::Handle)))
{
match (b1, b2) {
(
IoBlock::Allocated(IoSpaceDescriptor { base_address: 50, length: 25, .. }),
IoBlock::Unallocated(IoSpaceDescriptor { base_address: 75, length: 25, .. }),
) => {}
_ => panic!("Expected Allocated, Unallocated"),
}
} else {
panic!("Expected Ok. Test allocate transition failed.");
}
let mut b1 = IoBlock::Allocated(*block.clone().as_ref());
b1.as_mut().io_type = GcdIoType::Io;
if let Ok(IoBlockSplit::Before(b1, b2)) = b1.split_state_transition(50, 25, StateTransition::Free) {
match (b1, b2) {
(
IoBlock::Unallocated(IoSpaceDescriptor { base_address: 50, length: 25, .. }),
IoBlock::Allocated(IoSpaceDescriptor { base_address: 75, length: 25, .. }),
) => {}
_ => panic!("Expected Unallocated, Allocated"),
}
} else {
panic!("Expected Ok. Test free transition failed.");
}
}
}