[VFS] Move victoria falls to rust. (Breaks voyageurs)

Move victoria falls to rust, which allows us to remove both the denali
and victoria falls C++ code. This disk driver appears to work properly
but has highlighted some instability in the voyageus xhci implementation
which now breaks.
This commit is contained in:
Drew Galbraith 2025-05-05 19:37:53 -07:00
parent f918966727
commit dc801786b1
37 changed files with 504 additions and 2065 deletions

View file

@ -1,19 +1,284 @@
use denali_client::DiskReader;
use mammoth::mem::MemoryRegion;
use core::cmp::min;
use crate::types::Superblock;
use alloc::{collections::BTreeMap, string::String, vec::Vec};
use denali_client::{DenaliClient, DiskBlock, DiskReader, ReadRequest};
use mammoth::{cap::Capability, debug, mem::MemoryRegion, zion::ZError};
use yellowstone_yunq::DenaliInfo;
use crate::types::{BlockGroupDescriptor, DirEntry, Inode, Superblock};
pub struct FileInfo {
pub inode: u32,
pub name: String,
}
/// Ext2 Driver with the ability to read files and directories from the given disk.
///
/// Implementation based on the information available at
/// https://www.nongnu.org/ext2-doc/ext2.html
pub struct Ext2Driver {
reader: DiskReader,
superblock_region: MemoryRegion,
bgdt_region: MemoryRegion,
/// Cache of the memory regions for the inode tables available indexed by
/// the block_group number.
inode_table_map: Vec<Option<MemoryRegion>>,
/// Cache of inode_num to memory capability.
/// This is particularly important for directories so we
/// don't iterate over the disk each time.
inode_cache: BTreeMap<u32, Capability>,
}
impl Ext2Driver {
pub fn new(mut reader: DiskReader) -> Self {
let super_block_mem = MemoryRegion::from_cap(reader.read(2, 2).unwrap()).unwrap();
let super_block: &Superblock = super_block_mem.as_ref();
let inodes = super_block.inodes_count;
let magic = super_block.magic;
mammoth::debug!("Superblock ({:#x}): inodes: {:#x}", magic, inodes);
Self { reader }
pub fn new(denali_info: DenaliInfo) -> Self {
let mut client = DenaliClient::new(Capability::take(denali_info.denali_endpoint));
// Calculate the absolute offset and size of the superblock. It is located at
// offset 1024 of the partition and is 1024 bytes long. (Mostly extra
// reserved space).
// Ref: https://www.nongnu.org/ext2-doc/ext2.html#def-superblock
let abs_superblock_start = denali_info.lba_offset + 2;
let abs_superblock_size = 2; // TODO: This assumes 512 bytes sectors.
let superblock_region = MemoryRegion::from_cap(Capability::take(
client
.read(&ReadRequest {
device_id: denali_info.device_id,
block: DiskBlock {
lba: abs_superblock_start,
size: abs_superblock_size,
},
})
.unwrap()
.memory,
))
.unwrap();
let superblock: &Superblock = superblock_region.as_ref();
assert!(superblock.is_valid());
let mut reader = DiskReader::new(
client,
denali_info.device_id,
denali_info.lba_offset,
superblock.sectors_per_block(),
);
let bgdt_region = MemoryRegion::from_cap(
reader
.read(superblock.bgdt_block_num(), superblock.bgdt_block_size())
.unwrap(),
)
.unwrap();
let mut inode_table_map = Vec::new();
inode_table_map.resize_with(superblock.num_block_groups() as usize, || None);
Self {
reader,
superblock_region,
bgdt_region,
inode_table_map,
inode_cache: BTreeMap::new(),
}
}
fn superblock(&self) -> &Superblock {
self.superblock_region.as_ref()
}
fn bgdt(&self) -> &[BlockGroupDescriptor] {
self.bgdt_region.slice()
}
/// Updates the cached inode tables to contain the inode table for
/// a specific group.
fn populate_inode_table_if_none(&mut self, block_group_num: usize) {
if let None = self.inode_table_map[block_group_num] {
debug!(
"Cache MISS on inode table for block_group {}",
block_group_num
);
let inode_table = self.bgdt()[block_group_num].inode_table;
self.inode_table_map[block_group_num] = Some(
MemoryRegion::from_cap(
self.reader
.read(
inode_table as u64,
self.superblock().inode_table_block_size(),
)
.unwrap(),
)
.unwrap(),
);
} else {
debug!(
"Cache HIT on inode table for block_group {}",
block_group_num
);
}
}
pub fn get_inode(&mut self, inode_num: u32) -> Inode {
// See the following for a description of finding an inode.
// https://www.nongnu.org/ext2-doc/ext2.html#idm140660447281728
let block_group_num = (inode_num - 1) / self.superblock().inodes_per_group;
self.populate_inode_table_if_none(block_group_num as usize);
let region = self.inode_table_map[block_group_num as usize]
.as_ref()
.unwrap();
let local_index = (inode_num - 1) % self.superblock().inodes_per_group;
let offset = self.superblock().inode_size() * local_index as u64;
unsafe { region.raw_ptr_at_offset::<Inode>(offset).read().clone() }
}
fn get_blocks_from_single_indirect(&mut self, block_num: u64, num_blocks: usize) -> Vec<u32> {
assert!(num_blocks <= 256);
let single_indr_block_mem =
MemoryRegion::from_cap(self.reader.read(block_num, 1).unwrap()).unwrap();
single_indr_block_mem.slice()[..num_blocks].to_vec()
}
fn get_blocks_from_double_indirect(&mut self, block_num: u64, num_blocks: usize) -> Vec<u32> {
assert!(num_blocks > 0 && num_blocks <= (256 * 256));
let num_dbl_indr = ((num_blocks - 1) / 256) + 1;
let dbl_indr_block_mem =
MemoryRegion::from_cap(self.reader.read(block_num, 1).unwrap()).unwrap();
let dbl_indr_blocks: &[u32] = dbl_indr_block_mem.slice();
let mut blocks_to_read = Vec::new();
for i in 0..num_dbl_indr {
let num_blocks_in_single = min(num_blocks - (256 * i), 256);
blocks_to_read.append(
&mut self.get_blocks_from_single_indirect(
dbl_indr_blocks[i] as u64,
num_blocks_in_single,
),
);
}
blocks_to_read
}
fn run_len_compress_blocks(&self, blocks: Vec<u32>) -> Vec<DiskBlock> {
let mut curr_block = DiskBlock {
lba: blocks[0] as u64,
size: 1,
};
let mut iter = blocks.into_iter();
iter.next();
let mut blocks = Vec::new();
while let Some(block) = iter.next() {
if block as u64 == (curr_block.lba + curr_block.size) {
curr_block.size += 1;
} else {
blocks.push(curr_block.clone());
curr_block.lba = block as u64;
curr_block.size = 1;
}
}
blocks.push(curr_block);
blocks
}
fn read_inode(&mut self, _inode_num: u32, inode: Inode) -> Result<Capability, ZError> {
// TODO: Cache this method using _inode_num
// TODO: This assumes 512 byte sectors.
let real_block_cnt = (inode.blocks as u64 - 1) / (self.superblock().block_size() / 512) + 1;
if inode.block[14] != 0 {
debug!("Can't handle triply indirect inodes yet.");
return Err(ZError::UNIMPLEMENTED);
}
let mut blocks_to_read = Vec::new();
for i in 0..min(12, real_block_cnt) {
blocks_to_read.push(inode.block[i as usize])
}
// Singly indirect block.
if inode.block[12] != 0 {
let num_blocks = min(256, real_block_cnt - 12) as usize;
blocks_to_read.append(
&mut self.get_blocks_from_single_indirect(inode.block[12] as u64, num_blocks),
);
}
// Doubly indirect block.
if inode.block[13] != 0 {
let num_blocks = min(256 * 256, real_block_cnt - 268) as usize;
blocks_to_read.append(
&mut self.get_blocks_from_double_indirect(inode.block[13] as u64, num_blocks),
);
};
self.reader
.read_many(&self.run_len_compress_blocks(blocks_to_read))
}
fn read_inode_into_mem(
&mut self,
inode_num: u32,
inode: Inode,
) -> Result<MemoryRegion, ZError> {
if !self.inode_cache.contains_key(&inode_num) {
debug!("Cache MISS for inode_num: {}", inode_num);
let inode_cap = self.read_inode(inode_num, inode)?;
self.inode_cache.insert(inode_num, inode_cap);
} else {
debug!("Cache HIT for inode_num: {}", inode_num);
}
MemoryRegion::from_cap(self.inode_cache[&inode_num].duplicate(Capability::PERMS_ALL)?)
}
pub fn read_file(&mut self, inode_num: u32) -> Result<Capability, ZError> {
let inode = self.get_inode(inode_num);
if (inode.mode & 0x8000) == 0 {
debug!("Reading non file.");
return Err(ZError::INVALID_ARGUMENT);
}
self.read_inode(inode_num, inode)
}
pub fn read_directory(&mut self, inode_num: u32) -> Result<Vec<FileInfo>, ZError> {
let inode = self.get_inode(inode_num);
if (inode.mode & 0x4000) == 0 {
let mode = inode.mode;
debug!("Reading non directory. Inode {:?}, Mode {}", inode, mode);
return Err(ZError::INVALID_ARGUMENT);
}
let dir = self.read_inode_into_mem(inode_num, inode)?;
let mut file_names = Vec::new();
let mut offset = 0;
while offset < dir.size() {
let dir_ptr: DirEntry = unsafe { dir.raw_ptr_at_offset::<DirEntry>(offset).read() };
let name = dir_ptr.name;
let file_name: String =
String::from_utf8(name[..dir_ptr.name_len as usize].to_vec()).unwrap();
file_names.push(FileInfo {
inode: dir_ptr.inode,
name: file_name,
});
offset += dir_ptr.record_length as u64;
}
Ok(file_names)
}
}

View file

@ -1,3 +1,5 @@
/// Superblock structure.
/// https://www.nongnu.org/ext2-doc/ext2.html#superblock
#[repr(C, packed)]
pub struct Superblock {
pub inodes_count: u32,
@ -28,3 +30,95 @@ pub struct Superblock {
pub first_ino: u32,
pub inode_size: u16,
}
impl Superblock {
pub fn is_valid(&self) -> bool {
self.magic == 0xEF53
}
pub fn sectors_per_block(&self) -> u64 {
1 << (self.log_block_size + 1)
}
pub fn block_size(&self) -> u64 {
1024 << self.log_block_size
}
pub fn bgdt_block_num(&self) -> u64 {
if self.block_size() == 1024 { 2 } else { 1 }
}
pub fn bgdt_block_size(&self) -> u64 {
(self.num_block_groups() * (size_of::<BlockGroupDescriptor>() as u64) - 1)
/ self.block_size()
+ 1
}
pub fn num_block_groups(&self) -> u64 {
(((self.blocks_count - 1) / self.blocks_per_group) + 1) as u64
}
pub fn inode_size(&self) -> u64 {
if self.rev_level >= 1 {
self.inode_size as u64
} else {
const DEFAULT_INODE_SIZE: u64 = 0x80;
DEFAULT_INODE_SIZE
}
}
pub fn inode_table_block_size(&self) -> u64 {
(self.inode_size() * self.inodes_per_group as u64) / self.block_size()
}
}
#[repr(C, packed)]
#[derive(Debug)]
pub struct BlockGroupDescriptor {
pub block_bitmap: u32,
pub inode_bitmap: u32,
pub inode_table: u32,
pub free_blocks_count: u16,
pub free_inodes_count: u16,
pub used_dirs_count: u16,
reserved: [u8; 14],
}
const _: () = assert!(size_of::<BlockGroupDescriptor>() == 32);
#[repr(C, packed)]
#[derive(Clone, Debug)]
pub struct Inode {
pub mode: u16,
pub uid: u16,
pub size: u32,
pub atime: u32,
pub ctime: u32,
pub mtime: u32,
pub dtime: u32,
pub gid: u16,
pub links_count: u16,
pub blocks: u32,
pub flags: u32,
pub osd1: u32,
pub block: [u32; 15],
pub generation: u32,
pub file_acl: u32,
pub dir_acl: u32,
pub faddr: u32,
pub osd2: [u32; 3],
}
const _: () = assert!(size_of::<Inode>() == 128);
pub const EXT2_FT_FILE: u8 = 0x1;
pub const EXT2_FT_DIR: u8 = 0x2;
#[repr(C, packed)]
pub struct DirEntry {
pub inode: u32,
pub record_length: u16,
pub name_len: u8,
pub file_type: u8,
pub name: [u8; 256],
}