diff --git a/Cargo.toml b/Cargo.toml index a45603cc..e5f0a7ab 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -8,6 +8,7 @@ edition = "2021" [dependencies] yggdrasil-abi = { git = "https://git.alnyan.me/yggdrasil/yggdrasil-abi.git" } vfs = { path = "lib/vfs" } +vfs-macros = { path = "lib/vfs/macros" } aarch64-cpu = "9.3.1" atomic_enum = "0.2.0" @@ -18,4 +19,4 @@ spinning_top = "0.2.5" static_assertions = "1.1.0" tock-registers = "0.8.1" -elf = { version = "0.7.2", default-features = false } +elf = { version = "0.7.2", path = "../../rust-elf", default-features = false, features = ["no_std_stream"] } diff --git a/lib/vfs/Cargo.toml b/lib/vfs/Cargo.toml index 733a15cb..0802b412 100644 --- a/lib/vfs/Cargo.toml +++ b/lib/vfs/Cargo.toml @@ -7,5 +7,5 @@ edition = "2021" [dependencies] yggdrasil-abi = { git = "https://git.alnyan.me/yggdrasil/yggdrasil-abi.git" } -macros = { path = "macros" } +vfs-macros = { path = "macros" } bitflags = "2.3.3" diff --git a/lib/vfs/macros/Cargo.toml b/lib/vfs/macros/Cargo.toml index 721b63a9..7959b357 100644 --- a/lib/vfs/macros/Cargo.toml +++ b/lib/vfs/macros/Cargo.toml @@ -1,5 +1,5 @@ [package] -name = "macros" +name = "vfs-macros" version = "0.1.0" edition = "2021" diff --git a/lib/vfs/macros/src/lib.rs b/lib/vfs/macros/src/lib.rs index b011e3d2..59296dee 100644 --- a/lib/vfs/macros/src/lib.rs +++ b/lib/vfs/macros/src/lib.rs @@ -1,7 +1,7 @@ use std::collections::HashSet; use proc_macro::TokenStream; -use proc_macro2::Ident; +use proc_macro2::{Ident, Span}; use proc_macro_crate::{crate_name, FoundCrate}; use quote::{quote, ToTokens}; use syn::{parse_macro_input, ImplItem, ItemImpl}; @@ -14,9 +14,11 @@ pub fn auto_vnode_impl(attr: TokenStream, input: TokenStream) -> TokenStream { let current_crate = crate_name("vfs").unwrap(); let vfs_crate = match current_crate { - FoundCrate::Itself => quote!(crate), - FoundCrate::Name(name) => quote!( #name ), + FoundCrate::Itself => Ident::new("crate", Span::call_site()), + FoundCrate::Name(name) => Ident::new(&name, Span::call_site()), }; + let vfs_crate = quote! { #vfs_crate }; + let mut impl_item = parse_macro_input!(input as ItemImpl); let behavior = if attr.is_empty() { "unimplemented".to_string() diff --git a/lib/vfs/macros/src/vnode_impl.rs b/lib/vfs/macros/src/vnode_impl.rs index 939d902a..91126cfc 100644 --- a/lib/vfs/macros/src/vnode_impl.rs +++ b/lib/vfs/macros/src/vnode_impl.rs @@ -6,7 +6,7 @@ use quote::quote; fn impl_open(vfs: &TS2, behavior: &TS2) -> TS2 { quote! { fn open(&mut self, _node: &#vfs::VnodeRef, _opts: yggdrasil_abi::io::OpenFlags) - -> Result { + -> Result { #behavior } } @@ -31,7 +31,7 @@ fn impl_create(vfs: &TS2, behavior: &TS2) -> TS2 { fn impl_write(vfs: &TS2, behavior: &TS2) -> TS2 { quote! { - fn write(&mut self, _node: &#vfs::VnodeRef, _pos: usize, _data: &[u8]) + fn write(&mut self, _node: &#vfs::VnodeRef, _pos: u64, _data: &[u8]) -> Result { #behavior } @@ -40,7 +40,7 @@ fn impl_write(vfs: &TS2, behavior: &TS2) -> TS2 { fn impl_read(vfs: &TS2, behavior: &TS2) -> TS2 { quote! { - fn read(&mut self, _node: &#vfs::VnodeRef, _pos: usize, _data: &mut [u8]) + fn read(&mut self, _node: &#vfs::VnodeRef, _pos: u64, _data: &mut [u8]) -> Result { #behavior } diff --git a/lib/vfs/src/char.rs b/lib/vfs/src/char.rs index 91351f7e..fe3aa635 100644 --- a/lib/vfs/src/char.rs +++ b/lib/vfs/src/char.rs @@ -1,4 +1,4 @@ -use macros::auto_vnode_impl; +use vfs_macros::auto_vnode_impl; use yggdrasil_abi::{error::Error, io::OpenFlags}; use crate::{node::VnodeImpl, VnodeRef}; @@ -20,7 +20,7 @@ impl CharDeviceWrapper { #[auto_vnode_impl(error)] impl VnodeImpl for CharDeviceWrapper { - fn open(&mut self, _node: &VnodeRef, _opts: OpenFlags) -> Result { + fn open(&mut self, _node: &VnodeRef, _opts: OpenFlags) -> Result { Ok(0) } @@ -28,11 +28,11 @@ impl VnodeImpl for CharDeviceWrapper { Ok(()) } - fn read(&mut self, _node: &VnodeRef, _pos: usize, data: &mut [u8]) -> Result { + fn read(&mut self, _node: &VnodeRef, _pos: u64, data: &mut [u8]) -> Result { self.device.read(true, data) } - fn write(&mut self, _node: &VnodeRef, _pos: usize, data: &[u8]) -> Result { + fn write(&mut self, _node: &VnodeRef, _pos: u64, data: &[u8]) -> Result { self.device.write(true, data) } } diff --git a/lib/vfs/src/file.rs b/lib/vfs/src/file.rs index e3116880..a7a8e9d5 100644 --- a/lib/vfs/src/file.rs +++ b/lib/vfs/src/file.rs @@ -6,7 +6,7 @@ use yggdrasil_abi::error::Error; use crate::{ node::{VnodeKind, VnodeRef}, - Read, Write, + Read, Seek, SeekFrom, Write, }; bitflags! { @@ -20,7 +20,7 @@ pub type FileRef = Rc>; pub struct NormalFile { vnode: VnodeRef, - pos: usize, + pos: u64, } pub enum FileInner { @@ -33,7 +33,7 @@ pub struct File { } impl File { - pub fn normal(vnode: VnodeRef, pos: usize, flags: FileFlags) -> FileRef { + pub fn normal(vnode: VnodeRef, pos: u64, flags: FileFlags) -> FileRef { Rc::new(RefCell::new(Self { inner: FileInner::Normal(NormalFile { vnode, pos }), flags, @@ -51,7 +51,7 @@ impl Write for File { FileInner::Normal(inner) => { let count = inner.vnode.write(inner.pos, data)?; if inner.vnode.kind() != VnodeKind::Char { - inner.pos += count; + inner.pos += count as u64; } Ok(count) } @@ -69,7 +69,7 @@ impl Read for File { FileInner::Normal(inner) => { let count = inner.vnode.read(inner.pos, data)?; if inner.vnode.kind() != VnodeKind::Char { - inner.pos += count; + inner.pos += count as u64; } Ok(count) } @@ -77,6 +77,31 @@ impl Read for File { } } +impl Seek for File { + fn seek(&mut self, pos: SeekFrom) -> Result { + match &mut self.inner { + FileInner::Normal(inner) => { + // TODO check if the file is actually seekable + + let size = inner.vnode.size()?; + let pos = match pos { + SeekFrom::Start(offset) => { + if offset > size { + todo!(); + } + offset + } + SeekFrom::End(0) => size, + _ => todo!(), + }; + inner.pos = pos; + + Ok(pos) + } + } + } +} + impl Drop for File { fn drop(&mut self) { match &mut self.inner { diff --git a/lib/vfs/src/lib.rs b/lib/vfs/src/lib.rs index 4e005159..ce7a02f6 100644 --- a/lib/vfs/src/lib.rs +++ b/lib/vfs/src/lib.rs @@ -17,8 +17,16 @@ pub(crate) mod node; pub use self::block::BlockDevice; pub use self::char::{CharDevice, CharDeviceWrapper}; pub use file::{File, FileFlags, FileRef}; +pub use fs::Filesystem; pub use ioctx::IoContext; -pub use node::{Vnode, VnodeImpl, VnodeKind, VnodeRef, VnodeWeak}; +pub use node::{Vnode, VnodeDump, VnodeImpl, VnodeKind, VnodeRef, VnodeWeak}; + +#[derive(Debug)] +pub enum SeekFrom { + Start(u64), + End(i64), + Current(i64), +} pub trait Write { fn write(&mut self, data: &[u8]) -> Result; @@ -26,4 +34,31 @@ pub trait Write { pub trait Read { fn read(&mut self, data: &mut [u8]) -> Result; + + fn read_exact(&mut self, data: &mut [u8]) -> Result<(), Error> { + default_read_exact(self, data) + } +} + +pub trait Seek { + fn seek(&mut self, pos: SeekFrom) -> Result; +} + +fn default_read_exact(this: &mut R, mut buf: &mut [u8]) -> Result<(), Error> { + while !buf.is_empty() { + match this.read(buf) { + Ok(0) => break, + Ok(n) => { + let tmp = buf; + buf = &mut tmp[n..]; + } + Err(e) => todo!("default_read_exact: {:?}", e), + } + } + + if !buf.is_empty() { + todo!("default_read_exact unexpected eof") + } else { + Ok(()) + } } diff --git a/lib/vfs/src/node.rs b/lib/vfs/src/node.rs index bb439309..8c695531 100644 --- a/lib/vfs/src/node.rs +++ b/lib/vfs/src/node.rs @@ -11,11 +11,18 @@ use alloc::{ }; use yggdrasil_abi::{error::Error, io::OpenFlags}; -use crate::file::{File, FileFlags, FileRef}; +use crate::{ + file::{File, FileFlags, FileRef}, + fs::Filesystem, +}; pub type VnodeRef = Rc; pub type VnodeWeak = Weak; +pub struct VnodeDump { + node: VnodeRef, +} + #[derive(Debug, Clone, Copy, PartialEq)] pub enum VnodeKind { Directory, @@ -34,16 +41,21 @@ pub struct Vnode { tree: RefCell, kind: VnodeKind, data: RefCell>>, + fs: RefCell>>, } pub trait VnodeImpl { fn create(&mut self, at: &VnodeRef, name: &str, kind: VnodeKind) -> Result; - fn open(&mut self, node: &VnodeRef, opts: OpenFlags) -> Result; + fn open(&mut self, node: &VnodeRef, opts: OpenFlags) -> Result; fn close(&mut self, node: &VnodeRef) -> Result<(), Error>; - fn read(&mut self, node: &VnodeRef, pos: usize, data: &mut [u8]) -> Result; - fn write(&mut self, node: &VnodeRef, pos: usize, data: &[u8]) -> Result; + fn read(&mut self, node: &VnodeRef, pos: u64, data: &mut [u8]) -> Result; + fn write(&mut self, node: &VnodeRef, pos: u64, data: &[u8]) -> Result; + + fn size(&mut self, _node: &VnodeRef) -> Result { + unimplemented!() + } } impl Vnode { @@ -56,6 +68,7 @@ impl Vnode { }), kind, data: RefCell::new(None), + fs: RefCell::new(None), }) } @@ -74,6 +87,11 @@ impl Vnode { self.data.borrow_mut() } + #[inline] + pub fn fs(&self) -> Option> { + self.fs.borrow().clone() + } + pub fn parent(self: &VnodeRef) -> VnodeRef { match &self.tree.borrow().parent { Some(parent) => parent.upgrade().unwrap(), @@ -85,6 +103,10 @@ impl Vnode { self.data.borrow_mut().replace(data); } + pub fn set_fs(&self, data: Rc) { + self.fs.replace(Some(data)); + } + #[inline] pub fn is_directory(&self) -> bool { self.kind == VnodeKind::Directory @@ -186,7 +208,33 @@ impl Vnode { } } - pub fn write(self: &VnodeRef, pos: usize, buf: &[u8]) -> Result { + pub fn create(self: &VnodeRef, name: &str, kind: VnodeKind) -> Result { + if self.kind != VnodeKind::Directory { + todo!(); + } + if name.contains('/') { + return Err(Error::InvalidArgument); + } + + match self.lookup_or_load(name) { + Err(Error::DoesNotExist) => {} + Ok(_) => return Err(Error::AlreadyExists), + e => return e, + }; + + if let Some(ref mut data) = *self.data() { + let vnode = data.create(self, name, kind)?; + if let Some(fs) = self.fs() { + vnode.set_fs(fs); + } + self.add_child(vnode.clone()); + Ok(vnode) + } else { + Err(Error::NotImplemented) + } + } + + pub fn write(self: &VnodeRef, pos: u64, buf: &[u8]) -> Result { if self.kind == VnodeKind::Directory { todo!(); } @@ -198,7 +246,7 @@ impl Vnode { } } - pub fn read(self: &VnodeRef, pos: usize, buf: &mut [u8]) -> Result { + pub fn read(self: &VnodeRef, pos: u64, buf: &mut [u8]) -> Result { if self.kind == VnodeKind::Directory { todo!(); } @@ -209,6 +257,14 @@ impl Vnode { todo!() } } + + pub fn size(self: &VnodeRef) -> Result { + if let Some(ref mut data) = *self.data() { + data.size(self) + } else { + todo!(); + } + } } impl fmt::Debug for Vnode { @@ -223,3 +279,15 @@ impl fmt::Debug for Vnode { write!(f, "[{} {}]", prefix, self.name) } } + +impl VnodeDump { + pub fn new(node: VnodeRef) -> Self { + Self { node } + } +} + +impl fmt::Debug for VnodeDump { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + self.node.dump(f, 0) + } +} diff --git a/src/arch/aarch64/devtree.rs b/src/arch/aarch64/devtree.rs index f18ad736..3f39a8d0 100644 --- a/src/arch/aarch64/devtree.rs +++ b/src/arch/aarch64/devtree.rs @@ -126,7 +126,7 @@ fn find_node<'a>(at: TNode<'a>, path: &str) -> Option> { fn dump_node(node: &TNode, depth: usize, level: LogLevel) { fn indent(level: LogLevel, depth: usize) { for _ in 0..depth { - log_print!(level, " "); + log_print_raw!(level, " "); } } @@ -138,18 +138,18 @@ fn dump_node(node: &TNode, depth: usize, level: LogLevel) { } indent(level, depth); - log_print!(level, "{:?} {{\n", node_name); + log_print_raw!(level, "{:?} {{\n", node_name); for prop in node.props() { indent(level, depth + 1); let name = prop.name().unwrap(); - log_print!(level, "{name:?} = "); + log_print_raw!(level, "{name:?} = "); match name { - "compatible" | "stdout-path" => log_print!(level, "{:?}", prop.str().unwrap()), - _ => log_print!(level, "{:x?}", prop.raw()), + "compatible" | "stdout-path" => log_print_raw!(level, "{:?}", prop.str().unwrap()), + _ => log_print_raw!(level, "{:x?}", prop.raw()), } - log_print!(level, "\n"); + log_print_raw!(level, "\n"); } for child in node.children() { @@ -157,5 +157,5 @@ fn dump_node(node: &TNode, depth: usize, level: LogLevel) { } indent(level, depth); - log_print!(level, "}}\n"); + log_print_raw!(level, "}}\n"); } diff --git a/src/arch/aarch64/mod.rs b/src/arch/aarch64/mod.rs index 9bfe55b7..13feb4f1 100644 --- a/src/arch/aarch64/mod.rs +++ b/src/arch/aarch64/mod.rs @@ -4,6 +4,7 @@ use core::sync::atomic::Ordering; use aarch64_cpu::registers::{DAIF, ID_AA64MMFR0_EL1, SCTLR_EL1, TCR_EL1, TTBR0_EL1, TTBR1_EL1}; use abi::error::Error; +use fdt_rs::prelude::PropReader; use plat_qemu::PLATFORM; use tock_registers::interfaces::{ReadWriteable, Readable, Writeable}; @@ -15,7 +16,7 @@ use crate::{ }, debug, device::platform::Platform, - fs::devfs, + fs::{devfs, Initrd, INITRD_DATA}, mem::{ heap, phys::{self, reserved::reserve_region, PageUsage, PhysicalMemoryRegion}, @@ -135,6 +136,16 @@ impl AArch64 { unsafe fn init_physical_memory(&self, dtb_phys: usize) -> Result<(), Error> { let dt = self.device_tree(); + if let Some(initrd) = INITRD_DATA.try_get() { + reserve_region( + "initrd", + PhysicalMemoryRegion { + base: initrd.phys_page_start, + size: initrd.phys_page_len, + }, + ); + } + reserve_region( "dtb", PhysicalMemoryRegion { @@ -148,6 +159,41 @@ impl AArch64 { } } +fn setup_initrd() { + let dt = ARCHITECTURE.device_tree(); + let Some(chosen) = dt.node_by_path("/chosen") else { + return; + }; + + let Some(initrd_start) = devtree::find_prop(&chosen, "linux,initrd-start") else { + return; + }; + let Some(initrd_end) = devtree::find_prop(&chosen, "linux,initrd-end") else { + return; + }; + + let initrd_start = initrd_start.u64(0).unwrap() as usize; + let initrd_end = initrd_end.u64(0).unwrap() as usize; + + let start_aligned = initrd_start & !0xFFF; + let end_aligned = initrd_end & !0xFFF; + + let data = unsafe { + core::slice::from_raw_parts( + initrd_start.virtualize() as *const _, + initrd_end - initrd_start, + ) + }; + + let initrd = Initrd { + phys_page_start: start_aligned, + phys_page_len: end_aligned - start_aligned, + data, + }; + + INITRD_DATA.init(initrd); +} + /// AArch64 kernel main entry point pub fn kernel_main(dtb_phys: usize) -> ! { // NOTE it is critical that the code does not panic until the debug is set up, otherwise no @@ -167,6 +213,9 @@ pub fn kernel_main(dtb_phys: usize) -> ! { exception::init_exceptions(); + // Setup initrd + setup_initrd(); + debugln!("Initializing {} platform", PLATFORM.name()); unsafe { ARCHITECTURE diff --git a/src/debug.rs b/src/debug.rs index 456c315a..67ed93ea 100644 --- a/src/debug.rs +++ b/src/debug.rs @@ -99,6 +99,26 @@ impl fmt::Write for DebugPrinter { } } +pub fn hex_dump(level: LogLevel, addr_offset: usize, data: &[u8]) { + for (i, b) in data.iter().enumerate() { + if i % 16 == 0 { + log_print_raw!(level, "{:X}: ", addr_offset + i) + } + + log_print_raw!(level, "{:02X}", data[i]); + + if i % 16 == 15 { + log_print_raw!(level, "\n"); + } else if i % 2 == 1 { + log_print_raw!(level, " "); + } + } + + if data.len() % 16 != 0 { + log_print_raw!(level, "\n"); + } +} + /// Initializes the debug logging faclities. /// /// # Panics diff --git a/src/fs/mod.rs b/src/fs/mod.rs index 65c874be..5998278b 100644 --- a/src/fs/mod.rs +++ b/src/fs/mod.rs @@ -1,3 +1,14 @@ //! Filesystem implementations +use crate::util::OneTimeInit; + +pub struct Initrd { + pub phys_page_start: usize, + pub phys_page_len: usize, + pub data: &'static [u8], +} + +pub static INITRD_DATA: OneTimeInit = OneTimeInit::new(); + pub mod devfs; +pub mod tar; diff --git a/src/fs/tar.rs b/src/fs/tar.rs new file mode 100644 index 00000000..92ed38ee --- /dev/null +++ b/src/fs/tar.rs @@ -0,0 +1,332 @@ +use abi::error::Error; +use alloc::{boxed::Box, rc::Rc}; +use vfs::{Filesystem, Vnode, VnodeImpl, VnodeKind, VnodeRef}; +use vfs_macros::auto_vnode_impl; +use yggdrasil_abi::io::OpenFlags; + +use crate::util::OneTimeInit; + +#[repr(C)] +pub struct OctalField { + data: [u8; N], +} + +#[repr(C)] +pub struct TarString { + data: [u8; N], +} + +pub struct TarIterator<'a> { + data: &'a [u8], + offset: usize, + zero_blocks: usize, +} + +#[repr(packed)] +pub struct TarEntry { + name: TarString<100>, + mode: OctalField<8>, + uid: OctalField<8>, + gid: OctalField<8>, + size: OctalField<12>, + mtime: OctalField<12>, + checksum: OctalField<8>, + type_: u8, + link_name: [u8; 100], + magic: [u8; 8], + user: [u8; 32], + group: [u8; 32], + dev_major: OctalField<8>, + dev_minor: OctalField<8>, + prefix: [u8; 155], + __pad: [u8; 12], +} + +impl<'a> TarIterator<'a> { + pub const fn new(data: &'a [u8]) -> Self { + Self { + data, + offset: 0, + zero_blocks: 0, + } + } +} + +impl<'a> Iterator for TarIterator<'a> { + type Item = Result<(&'a TarEntry, Option<&'a [u8]>), Error>; + + fn next(&mut self) -> Option { + loop { + if self.offset + 512 > self.data.len() { + break None; + } + + let hdr_ptr = &self.data[self.offset..]; + let hdr = unsafe { core::mem::transmute::<*const u8, &TarEntry>(hdr_ptr.as_ptr()) }; + + if hdr.is_empty() { + if self.zero_blocks == 1 { + self.offset = self.data.len(); + return None; + } + self.zero_blocks += 1; + continue; + } + + let (data, size_aligned) = match hdr.type_ { + 0 | b'0' => { + let size = usize::from(&hdr.size); + + if self.offset + 512 + size > self.data.len() { + return Some(Err(Error::InvalidArgument)); + } + + let data = &self.data[self.offset + 512..self.offset + 512 + size]; + let size_aligned = (size + 511) & !511; + + (Some(data), size_aligned) + } + // Directory + b'5' => (None, 0), + _ => todo!("Unknown node kind: {}", hdr.type_), + }; + self.offset += size_aligned + 512; + + break Some(Ok((hdr, data))); + } + } +} + +impl From<&OctalField> for usize { + fn from(value: &OctalField) -> Self { + let mut acc = 0; + for i in 0..N { + if value.data[i] == 0 { + break; + } + acc <<= 3; + acc |= (value.data[i] - b'0') as usize; + } + acc + } +} + +impl TarString { + pub fn as_str(&self) -> Result<&str, Error> { + core::str::from_utf8(&self.data[..self.len()]).map_err(|_| Error::InvalidArgument) + } + + pub fn len(&self) -> usize { + for i in 0..N { + if self.data[i] == 0 { + return i; + } + } + N + } +} + +impl TarEntry { + pub fn is_empty(&self) -> bool { + self.name.data[0] == 0 + } + + pub fn node_kind(&self) -> VnodeKind { + match self.type_ { + 0 | b'0' => VnodeKind::Regular, + b'5' => VnodeKind::Directory, + _ => todo!(), + } + } +} + +pub struct TarFilesystem { + root: OneTimeInit, +} + +impl Filesystem for TarFilesystem { + fn dev(self: Rc) -> Option<&'static dyn vfs::BlockDevice> { + todo!() + } + + fn root(self: Rc) -> Result { + self.root.try_get().cloned().ok_or(Error::DoesNotExist) + } + + fn data(&self) -> Option> { + todo!() + } +} + +struct DirInode; +struct RegularInode { + data: &'static [u8], +} + +#[auto_vnode_impl] +impl VnodeImpl for DirInode { + fn create(&mut self, _at: &VnodeRef, name: &str, kind: VnodeKind) -> Result { + let child = Vnode::new(name, kind); + match kind { + VnodeKind::Directory => child.set_data(Box::new(DirInode)), + VnodeKind::Regular => (), + _ => todo!(), + } + Ok(child) + } +} + +#[auto_vnode_impl] +impl VnodeImpl for RegularInode { + fn open(&mut self, node: &VnodeRef, opts: OpenFlags) -> Result { + if opts.is_write() { + panic!("TODO: tarfs write"); + } + + Ok(0) + } + + fn close(&mut self, node: &VnodeRef) -> Result<(), Error> { + Ok(()) + } + + fn read(&mut self, node: &VnodeRef, pos: u64, data: &mut [u8]) -> Result { + let pos = pos as usize; + if pos > self.data.len() { + return Err(Error::InvalidFile); + } + let mut rem = core::cmp::min(self.data.len() - pos, data.len()); + data[..rem].copy_from_slice(&self.data[pos..pos + rem]); + Ok(rem) + } + + fn size(&mut self, _node: &VnodeRef) -> Result { + Ok(self.data.len() as u64) + } +} + +impl TarFilesystem { + fn make_path( + self: &Rc, + at: &VnodeRef, + path: &str, + create: bool, + ) -> Result { + debugln!("make_path {:?}", path); + if path.is_empty() { + return Ok(at.clone()); + } + let (element, rest) = abi::path::split_left(path); + assert!(!element.is_empty()); + + let node = at.lookup(element); + let node = match node { + Some(node) => node, + None => { + if !create { + debugln!("path {:?} does not exist", path); + return Err(Error::DoesNotExist); + } + + infoln!("Create {:?}", element); + at.create(element, VnodeKind::Directory)? + } + }; + + if rest.is_empty() { + Ok(node) + } else { + self.make_path(&node, rest, create) + } + } + + fn create_node_initial( + self: &Rc, + name: &str, + hdr: &TarEntry, + data: Option<&[u8]>, + ) -> VnodeRef { + assert!(!name.is_empty()); + assert!(!name.contains('/')); + + let kind = hdr.node_kind(); + let node = Vnode::new(name, kind); + node.set_fs(self.clone()); + + match kind { + VnodeKind::Directory => node.set_data(Box::new(DirInode)), + VnodeKind::Regular => {} + _ => todo!(), + } + + node + } + + fn from_slice_internal(self: &Rc, tar_data: &'static [u8]) -> Result { + let root = Vnode::new("", VnodeKind::Directory); + root.set_fs(self.clone()); + root.set_data(Box::new(DirInode)); + + // 1. Create paths in tar + for item in TarIterator::new(tar_data) { + let Ok((hdr, data)) = item else { + warnln!("Tar image is truncated"); + return Err(Error::InvalidArgument); + }; + + let path = hdr.name.as_str()?.trim_matches('/'); + infoln!("path = {:?}", path); + let (dirname, filename) = abi::path::split_right(path); + let parent = self.make_path(&root, dirname, true)?; + let node = self.create_node_initial(filename, hdr, data); + + parent.add_child(node); + } + + // 2. Associate files with their data + for item in TarIterator::new(tar_data) { + let Ok((hdr, data)) = item else { + panic!("Unreachable"); + }; + if hdr.node_kind() == VnodeKind::Regular { + let data = data.unwrap(); + let path = hdr.name.as_str()?.trim_matches('/'); + let node = self.make_path(&root, path, false)?; + node.set_data(Box::new(RegularInode { data })); + } + } + + Ok(root) + } + + pub fn from_slice(tar_data: &'static [u8]) -> Result, Error> { + let fs = Rc::new(TarFilesystem { + root: OneTimeInit::new(), + }); + let root = fs.from_slice_internal(tar_data)?; + fs.root.init(root); + + Ok(fs) + } +} + +// pub fn init() { +// let Some(initrd) = INITRD_DATA.try_get() else { +// warnln!("No initrd found"); +// return; +// }; +// +// let fs = match TarFilesystem::from_slice(initrd.data) { +// Ok(fs) => fs, +// Err(err) => { +// warnln!("Could not initialize tar filesystem: {:?}", err); +// return; +// } +// }; +// +// let r = fs.root().unwrap(); +// let dump = VnodeDump::new(r); +// infoln!("{:?}", dump); +// +// todo!() +// } diff --git a/src/main.rs b/src/main.rs index db79e719..f966da6b 100644 --- a/src/main.rs +++ b/src/main.rs @@ -15,7 +15,15 @@ extern crate yggdrasil_abi as abi; +use core::ops::DerefMut; + +use abi::{ + error::Error, + io::{OpenFlags, RawFd}, +}; +use fs::{devfs, tar::TarFilesystem, INITRD_DATA}; use task::process::Process; +use vfs::{Filesystem, IoContext, Read, VnodeRef}; extern crate alloc; @@ -34,6 +42,12 @@ pub mod syscall; pub mod task; pub mod util; +fn setup_root() -> Result { + let initrd_data = INITRD_DATA.get(); + let fs = TarFilesystem::from_slice(initrd_data.data)?; + fs.root() +} + /// Entry point for common kernel code. /// /// # Note @@ -41,15 +55,40 @@ pub mod util; /// This function is meant to be used as a kernel-space process after all the platform-specific /// initialization has finished. pub fn kernel_main() { + let root = match setup_root() { + Ok(root) => root, + Err(err) => { + warnln!("Could not setup root from initrd: {:?}", err); + return; + } + }; + + let devfs_root = devfs::root(); + let tty_node = devfs_root.lookup("ttyS0").unwrap(); + + let ioctx = IoContext::new(root); + let node = ioctx.find(None, "/init", false).unwrap(); + let file = node.open(OpenFlags::new().read()).unwrap(); + + { + let user_init = proc::exec::load_elf(file, &["/init"]).unwrap(); + let mut io = user_init.io.lock(); + io.set_ioctx(ioctx); + let stdout = tty_node.open(OpenFlags::new().write()).unwrap(); + let stderr = stdout.clone(); + + io.set_file(RawFd::STDOUT, stdout).unwrap(); + io.set_file(RawFd::STDERR, stderr).unwrap(); + drop(io); + user_init.enqueue_somewhere(); + } + // static USER_PROGRAM: &[u8] = include_bytes!(concat!( // "../../target/aarch64-unknown-yggdrasil/", // env!("PROFILE"), // "/test_program" // )); - // let devfs_root = devfs::root(); - // let tty_node = devfs_root.lookup("ttyS0").unwrap(); - // let ioctx = IoContext::new(devfs_root.clone()); // // Spawn a test user task @@ -61,15 +100,6 @@ pub fn kernel_main() { // // Setup I/O for the process // // let mut io = proc.io.lock(); // // io.set_file(RawFd::STDOUT, todo!()).unwrap(); - // { - // let mut io = proc.io.lock(); - // io.set_ioctx(ioctx); - // let stdout = tty_node.open(OpenFlags::new().write()).unwrap(); - // let stderr = stdout.clone(); - - // io.set_file(RawFd::STDOUT, stdout).unwrap(); - // io.set_file(RawFd::STDERR, stderr).unwrap(); - // } // proc.enqueue_somewhere(); // } diff --git a/src/proc/exec.rs b/src/proc/exec.rs index b265304c..9c2d33fb 100644 --- a/src/proc/exec.rs +++ b/src/proc/exec.rs @@ -3,6 +3,7 @@ use core::mem::size_of; use abi::error::Error; use alloc::rc::Rc; +use vfs::{FileRef, Read, Seek}; use crate::{ arch::aarch64::context::TaskContext, @@ -66,13 +67,13 @@ fn setup_args(space: &mut AddressSpace, virt: usize, args: &[&str]) -> Result<() Ok(()) } -/// Sets up a userspace structure from a slice defining an ELF binary -pub fn create_from_memory(data: &[u8], args: &[&str]) -> Result, Error> { +fn setup_binary( + mut space: AddressSpace, + entry: usize, + args: &[&str], +) -> Result, Error> { const USER_STACK_PAGES: usize = 8; - let mut space = AddressSpace::new_empty()?; - let elf_entry = proc::load_elf_from_memory(&mut space, data); - let virt_stack_base = 0x10000000; // 0x1000 of guard page let virt_args_base = virt_stack_base + (USER_STACK_PAGES + 1) * 0x1000; @@ -88,10 +89,16 @@ pub fn create_from_memory(data: &[u8], args: &[&str]) -> Result, Err setup_args(&mut space, virt_args_base, args)?; - debugln!("Entry: {:#x}", elf_entry); + debugln!( + "Entry: {:#x}, Stack: {:#x}..{:#x}, Args: {:#x}", + entry, + virt_stack_base, + virt_stack_base + USER_STACK_PAGES * 0x1000, + virt_args_base + ); let context = TaskContext::user( - elf_entry, + entry, virt_args_base, space.physical_address(), virt_stack_base + USER_STACK_PAGES * 0x1000, @@ -99,3 +106,18 @@ pub fn create_from_memory(data: &[u8], args: &[&str]) -> Result, Err Ok(Process::new_with_context(Some(space), context)) } + +pub fn load_elf(file: FileRef, args: &[&str]) -> Result, Error> { + let mut space = AddressSpace::new_empty()?; + let elf_entry = proc::load_elf_from_file(&mut space, file)?; + + setup_binary(space, elf_entry, args) +} + +/// Sets up a userspace structure from a slice defining an ELF binary +pub fn create_from_memory(data: &[u8], args: &[&str]) -> Result, Error> { + let mut space = AddressSpace::new_empty()?; + let elf_entry = proc::load_elf_from_memory(&mut space, data); + + setup_binary(space, elf_entry, args) +} diff --git a/src/proc/mod.rs b/src/proc/mod.rs index e61e6f8f..d4691f24 100644 --- a/src/proc/mod.rs +++ b/src/proc/mod.rs @@ -4,22 +4,76 @@ pub mod exec; pub mod io; pub mod wait; -use aarch64_cpu::registers::TTBR0_EL1; +use aarch64_cpu::registers::{TCR_EL1::A1::TTBR0, TTBR0_EL1}; +use alloc::rc::Rc; use elf::{ abi::{PF_W, PF_X, PT_LOAD}, endian::AnyEndian, - ElfBytes, + ElfBytes, ElfStream, ParseError, }; use tock_registers::interfaces::Writeable; +use vfs::{FileRef, Read, Seek, SeekFrom}; +use yggdrasil_abi::error::Error; use crate::{ arch::aarch64::table::tlb_flush_vaae1, + debug::hex_dump, mem::{ phys::{self, PageUsage}, table::{AddressSpace, PageAttributes}, + ConvertAddress, }, }; +#[derive(Clone, Copy)] +pub struct FileReader<'a> { + file: &'a FileRef, +} + +impl elf::io_traits::Read for FileReader<'_> { + fn read(&mut self, buf: &mut [u8]) -> Result { + todo!() + } + + fn read_exact(&mut self, buf: &mut [u8]) -> Result<(), elf::io_traits::StreamError> { + self.file + .borrow_mut() + .read_exact(buf) + .map_err(conv_stream_error) + } +} + +impl elf::io_traits::Seek for FileReader<'_> { + fn seek(&mut self, pos: elf::io_traits::SeekFrom) -> Result { + self.file + .borrow_mut() + .seek(conv_seek_from(pos)) + .map_err(conv_stream_error) + } +} + +#[inline] +fn conv_stream_error(v: Error) -> elf::io_traits::StreamError { + elf::io_traits::StreamError { + message: "Elf read error", + } +} + +#[inline] +fn conv_seek_from(v: elf::io_traits::SeekFrom) -> SeekFrom { + match v { + elf::io_traits::SeekFrom::End(off) => SeekFrom::End(off), + elf::io_traits::SeekFrom::Start(off) => SeekFrom::Start(off), + _ => todo!(), + } +} + +#[inline] +fn from_parse_error(v: ParseError) -> Error { + warnln!("ELF loading error: {:?}", v); + Error::InvalidFile +} + fn load_segment(space: &mut AddressSpace, addr: usize, data: &[u8], memsz: usize, elf_attrs: u32) { let attrs = match (elf_attrs & PF_W, elf_attrs & PF_X) { (0, 0) => PageAttributes::AP_BOTH_READONLY, @@ -62,6 +116,113 @@ fn load_segment(space: &mut AddressSpace, addr: usize, data: &[u8], memsz: usize } } +fn load_bytes( + space: &mut AddressSpace, + addr: usize, + mut src: F, + len: usize, + elf_attrs: u32, +) -> Result<(), Error> +where + F: FnMut(usize, &mut [u8]) -> Result<(), Error>, +{ + // TODO check for crazy addresses here + + let attrs = match (elf_attrs & PF_W, elf_attrs & PF_X) { + (0, 0) => PageAttributes::AP_BOTH_READONLY, + (_, 0) => PageAttributes::AP_BOTH_READWRITE, + (0, _) => PageAttributes::AP_BOTH_READONLY, + (_, _) => PageAttributes::AP_BOTH_READWRITE, + }; + + let dst_page_off = addr & 0xFFF; + let dst_page_aligned = addr & !0xFFF; + let mut off = 0usize; + let mut rem = len; + + while rem != 0 { + let page_idx = (dst_page_off + off) / 0x1000; + let page_off = (dst_page_off + off) % 0x1000; + let count = core::cmp::min(rem, 0x1000 - page_off); + + let virt_page = dst_page_aligned + page_idx * 0x1000; + assert_eq!(virt_page & 0xFFF, 0); + if let Some(_) = space.translate(virt_page) { + // Handle these cases + todo!(); + } + + let phys_page = phys::alloc_page(PageUsage::Used)?; + debugln!("map {:#x} -> {:#x}", virt_page, phys_page); + space.map_page(virt_page, phys_page, attrs)?; + + let dst_slice = unsafe { + let addr = (phys_page + page_off).virtualize(); + + core::slice::from_raw_parts_mut(addr as *mut u8, count) + }; + + src(off, dst_slice)?; + debugln!("{:#x} (off = {}):", virt_page + page_off, page_off); + // hex_dump( + // crate::debug::LogLevel::Debug, + // virt_page + page_off, + // dst_slice, + // ); + + rem -= count; + off += count; + } + + Ok(()) +} + +pub fn load_elf_from_file(space: &mut AddressSpace, file: FileRef) -> Result { + let file = FileReader { file: &file }; + + let elf = ElfStream::::open_stream(file).map_err(from_parse_error)?; + + for phdr in elf.segments() { + if phdr.p_type != PT_LOAD { + continue; + } + + debugln!("LOAD {:#x}", phdr.p_vaddr); + + if phdr.p_filesz > 0 { + load_bytes( + space, + phdr.p_vaddr as usize, + |off, dst| { + let mut source = file.file.borrow_mut(); + source.seek(SeekFrom::Start(phdr.p_offset + off as u64))?; + source.read_exact(dst) + }, + phdr.p_filesz as usize, + phdr.p_flags, + )?; + } + + if phdr.p_memsz > phdr.p_filesz { + let addr = (phdr.p_vaddr + phdr.p_filesz) as usize; + let len = (phdr.p_memsz - phdr.p_filesz) as usize; + + load_bytes( + space, + addr, + |_, dst| { + dst.fill(0); + Ok(()) + }, + len, + phdr.p_flags, + )?; + } + } + + Ok(elf.ehdr.e_entry as usize) +} + /// Loads an ELF image into the address space from a slice pub fn load_elf_from_memory(space: &mut AddressSpace, src: &[u8]) -> usize { // Map the address space temporarily diff --git a/src/util.rs b/src/util.rs index f05a9c59..cda37d61 100644 --- a/src/util.rs +++ b/src/util.rs @@ -69,6 +69,14 @@ impl OneTimeInit { unsafe { (*self.value.get()).assume_init_ref() } } + + pub fn try_get(&self) -> Option<&T> { + if self.state.load(Ordering::Acquire) { + Some(self.get()) + } else { + None + } + } } impl StaticVector {