use super::{buffer::BlockBuffer, traits::BlockDevice};
use crate::{
bmalloc::AbsoluteBN,
error::{Ext4Error, Ext4Result},
};
const CACHE_ENTRIES: usize = 4;
struct CacheLine {
block_id: Option<AbsoluteBN>,
dirty: bool,
referenced: bool,
buffer: BlockBuffer,
}
impl CacheLine {
fn new() -> Self {
Self {
block_id: None,
dirty: false,
referenced: false,
buffer: BlockBuffer::new(),
}
}
fn is_empty(&self) -> bool {
self.block_id.is_none()
}
}
pub(super) struct BlockDev<B: BlockDevice> {
dev: B,
entries: [CacheLine; CACHE_ENTRIES],
active: usize,
clock: usize,
}
impl<B: BlockDevice> BlockDev<B> {
pub fn new(dev: B) -> Self {
Self {
dev,
entries: core::array::from_fn(|_| CacheLine::new()),
active: 0,
clock: 0,
}
}
pub fn _with_buffer(dev: B, buffer: BlockBuffer) -> Ext4Result<Self> {
if buffer.len() < 512 {
return Err(Ext4Error::buffer_too_small(buffer.len(), 512));
}
let mut slf = Self::new(dev);
slf.entries[0].buffer = buffer;
Ok(slf)
}
pub fn _open(&mut self) -> Ext4Result<()> {
self.dev.open()
}
pub fn _close(&mut self) -> Ext4Result<()> {
self.flush()?;
self.dev.close()
}
pub fn read_block(&mut self, block_id: AbsoluteBN) -> Ext4Result<()> {
for (i, entry) in self.entries.iter_mut().enumerate() {
if !entry.is_empty() && entry.block_id == Some(block_id) {
entry.referenced = true;
self.active = i;
return Ok(());
}
}
let idx = self.clock_evict()?;
self.dev
.read(self.entries[idx].buffer.as_mut_slice(), block_id, 1)?;
self.entries[idx].block_id = Some(block_id);
self.entries[idx].dirty = false;
self.entries[idx].referenced = true;
self.active = idx;
Ok(())
}
pub fn write_block(&mut self, block_id: AbsoluteBN) -> Ext4Result<()> {
if self.dev.is_readonly() {
return Err(Ext4Error::read_only());
}
let active = &mut self.entries[self.active];
self.dev.write(active.buffer.as_slice(), block_id, 1)?;
active.block_id = Some(block_id);
active.dirty = false;
active.referenced = true;
Ok(())
}
pub fn read_blocks(
&mut self,
buffer: &mut [u8],
block_id: AbsoluteBN,
count: u32,
) -> Ext4Result<()> {
let block_size = self.dev.block_size() as usize;
let required_size = block_size * count as usize;
if buffer.len() < required_size {
return Err(Ext4Error::buffer_too_small(buffer.len(), required_size));
}
self.dev.read(buffer, block_id, count)
}
pub fn write_blocks(
&mut self,
buffer: &[u8],
block_id: AbsoluteBN,
count: u32,
) -> Ext4Result<()> {
if self.dev.is_readonly() {
return Err(Ext4Error::read_only());
}
let block_size = self.dev.block_size() as usize;
let required_size = block_size * count as usize;
if buffer.len() < required_size {
return Err(Ext4Error::buffer_too_small(buffer.len(), required_size));
}
self.dev.write(buffer, block_id, count)
}
pub fn buffer(&self) -> &[u8] {
self.entries[self.active].buffer.as_slice()
}
pub fn buffer_mut(&mut self) -> &mut [u8] {
self.entries[self.active].dirty = true;
self.entries[self.active].buffer.as_mut_slice()
}
pub fn invalidate_cache(&mut self) {
for entry in self.entries.iter_mut() {
entry.block_id = None;
entry.dirty = false;
entry.referenced = false;
}
}
pub(crate) fn cache_clean_block(
&mut self,
block_id: AbsoluteBN,
data: &[u8; crate::config::BLOCK_SIZE],
) -> Ext4Result<()> {
for (i, entry) in self.entries.iter_mut().enumerate() {
if !entry.is_empty() && entry.block_id == Some(block_id) {
entry.buffer.as_mut_slice().copy_from_slice(data);
entry.dirty = false;
entry.referenced = true;
self.active = i;
return Ok(());
}
}
let idx = self.clock_evict()?;
self.entries[idx]
.buffer
.as_mut_slice()
.copy_from_slice(data);
self.entries[idx].block_id = Some(block_id);
self.entries[idx].dirty = false;
self.entries[idx].referenced = true;
Ok(())
}
pub fn flush(&mut self) -> Ext4Result<()> {
for entry in self.entries.iter_mut() {
if entry.dirty && !entry.is_empty() {
self.dev
.write(entry.buffer.as_slice(), entry.block_id.unwrap(), 1)?;
entry.dirty = false;
}
}
self.dev.flush()
}
pub fn total_blocks(&self) -> u64 {
self.dev.total_blocks()
}
pub fn block_size(&self) -> u32 {
self.dev.block_size()
}
pub fn _device(&self) -> &B {
&self.dev
}
pub fn device_mut(&mut self) -> &mut B {
&mut self.dev
}
fn clock_evict(&mut self) -> Ext4Result<usize> {
for _ in 0..(CACHE_ENTRIES * 2) {
let idx = self.clock;
self.clock = (self.clock + 1) % CACHE_ENTRIES;
if self.entries[idx].is_empty() {
self.active = idx;
return Ok(idx);
}
if self.entries[idx].referenced {
self.entries[idx].referenced = false;
continue;
}
if self.entries[idx].dirty {
let bid = self.entries[idx].block_id.unwrap();
self.dev
.write(self.entries[idx].buffer.as_slice(), bid, 1)?;
self.entries[idx].dirty = false;
}
self.entries[idx].block_id = None;
self.entries[idx].referenced = false;
self.active = idx;
return Ok(idx);
}
let idx = self.clock;
self.clock = (self.clock + 1) % CACHE_ENTRIES;
if self.entries[idx].dirty {
let bid = self.entries[idx].block_id.unwrap();
self.dev
.write(self.entries[idx].buffer.as_slice(), bid, 1)?;
self.entries[idx].dirty = false;
}
self.entries[idx].block_id = None;
self.entries[idx].referenced = false;
self.active = idx;
Ok(idx)
}
}