From 9b6aec28f56069473d9509ad6a80e64026bcfc78 Mon Sep 17 00:00:00 2001 From: Julien THILLARD Date: Thu, 5 Mar 2026 14:41:28 +0100 Subject: [PATCH] Better virtual file system, keyboard through MMIO&VirtIO --- Cargo.toml | 3 +- crates/bffs/Cargo.toml | 1 + crates/bffs/src/boot_sector.rs | 3 +- crates/bffs/src/dir.rs | 37 +++-- crates/bffs/src/entry.rs | 9 +- crates/bffs/src/error.rs | 46 +----- crates/bffs/src/file.rs | 26 +-- crates/bffs/src/lib.rs | 14 +- crates/bffs/src/path.rs | 83 +++++++++- crates/io/Cargo.toml | 10 ++ crates/io/src/error.rs | 48 ++++++ crates/{bffs/src/io.rs => io/src/lib.rs} | 68 ++++---- crates/os-std/Cargo.toml | 1 + crates/os-std/src/io.rs | 1 + crates/os-std/src/lib.rs | 1 + crates/shared/Cargo.toml | 1 + crates/shared/src/fs.rs | 7 + crates/shared/src/syscall.rs | 28 +++- justfile | 14 +- rustfmt.toml | 1 + src/boot.rs | 10 +- src/draw.rs | 124 +++++++++++++++ src/fs.rs | 118 +++++++++----- src/interrupt.rs | 54 ++++++- src/main.rs | 41 +++-- src/panic_handler.rs | 45 +++--- src/process.rs | 18 ++- src/sync.rs | 4 +- src/syscall.rs | 22 --- src/time.rs | 6 +- src/tty.rs | 73 +++++++++ src/vga.rs | 126 ++------------- src/virtio.rs | 67 ++++++++ src/virtio/input.rs | 192 +++++++++++++++++++++++ src/virtual_console.rs | 120 ++++++++++++++ src/virtual_fs.rs | 112 +++++++++++-- user/test_pic/src/main.rs | 12 +- 37 files changed, 1191 insertions(+), 355 deletions(-) create mode 100644 crates/io/Cargo.toml create mode 100644 crates/io/src/error.rs rename crates/{bffs/src/io.rs => io/src/lib.rs} (82%) create mode 100644 crates/os-std/src/io.rs create mode 100644 rustfmt.toml create mode 100644 src/draw.rs create mode 100644 src/tty.rs create mode 100644 src/virtio.rs create mode 100644 src/virtio/input.rs create mode 100644 src/virtual_console.rs diff --git a/Cargo.toml b/Cargo.toml index 8123588..ca968b0 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [workspace] resolver = "3" -members = ["crates/os-std", "crates/shared", "user/*"] +members = ["crates/io","crates/os-std", "crates/shared", "user/*"] [package] name = "kernel-rust" @@ -13,6 +13,7 @@ kernel-macros = { path = "crates/kernel-macros" } log = "0.4" critical-section = { version = "1", features = ["restore-state-bool"] } bffs = { path = "crates/bffs", features = ["alloc"] } +io = { path = "crates/io", features = ["alloc"] } shared = { path = "crates/shared", features = ["kernel"] } # ELF parsing helper goblin = { version = "0.7", default-features = false, features = ["elf32", "elf64", "endian_fd"] } diff --git a/crates/bffs/Cargo.toml b/crates/bffs/Cargo.toml index dc2f319..e19533a 100644 --- a/crates/bffs/Cargo.toml +++ b/crates/bffs/Cargo.toml @@ -4,6 +4,7 @@ version = "0.1.0" edition = "2024" [dependencies] +io = { path = "../io" } bitflags = "2" [features] diff --git a/crates/bffs/src/boot_sector.rs b/crates/bffs/src/boot_sector.rs index 3d29d6e..1cfe0e0 100644 --- a/crates/bffs/src/boot_sector.rs +++ b/crates/bffs/src/boot_sector.rs @@ -1,4 +1,5 @@ -use crate::{error::Error, io::{Read, ReadLeExt}}; +use crate::error::Error; +use io::{Read, ReadLeExt}; #[derive(Debug, Clone, Copy)] pub struct Fat32BootSector { diff --git a/crates/bffs/src/dir.rs b/crates/bffs/src/dir.rs index b78042d..8a56d92 100644 --- a/crates/bffs/src/dir.rs +++ b/crates/bffs/src/dir.rs @@ -1,11 +1,11 @@ use crate::{ + Fat32FileSystem, ReadSeek, ReadWriteSeek, entry::{DirEntry, DirectoryIterator}, error::Error, file::{File, RawFile}, - io::{self, IoBase, Read, Seek, Write}, path::Path, - Fat32FileSystem, ReadSeek, ReadWriteSeek, }; +use io::{self, IoBase, Read, Seek, Write}; pub struct Dir<'a, T> { raw: RawFile<'a, T>, @@ -13,24 +13,24 @@ pub struct Dir<'a, T> { } impl<'a, T: IoBase> IoBase for Dir<'a, T> { - type Error = T::Error; + type Error = Error; } impl<'a, T: ReadSeek> Seek for Dir<'a, T> { - fn seek(&mut self, pos: io::SeekFrom) -> Result> { + fn seek(&mut self, pos: io::SeekFrom) -> Result { self.raw.seek(pos) } } impl<'a, T: ReadSeek> Read for Dir<'a, T> { - fn read(&mut self, buf: &mut [u8]) -> Result> { + fn read(&mut self, buf: &mut [u8]) -> Result { self.raw.read(buf) } } impl<'a, T: ReadWriteSeek> Write for Dir<'a, T> { - fn write(&mut self, buf: &[u8]) -> Result> { + fn write(&mut self, buf: &[u8]) -> Result { self.raw.write(buf) } - fn flush(&mut self) -> Result<(), Error> { + fn flush(&mut self) -> Result<(), Self::Error> { self.raw.flush() } } @@ -44,25 +44,28 @@ impl<'a, T> Dir<'a, T> { } } impl<'a, T: ReadSeek> Dir<'a, T> { - pub fn open_entry>( - &self, - path: P, - ) -> Result, Error> { + pub fn open_entry>(&self, path: P) -> Result, Error> { if path.as_ref().is_absolute() { return self.fs.open_entry(path); } + let (start, entryname) = path.as_ref().split_path(); for file in self.iter() { let f = file?; - if f.name_is(path.as_ref().as_str()) { - return Ok(f); + if f.name_is(start) { + if let Some(entry_name) = entryname { + if f.is_dir() { + return f.to_dir().open_entry(entry_name); + } else { + return Err(Error::NotFound); + } + } else { + return Ok(f); + } } } Err(Error::NotFound) } - pub fn open_file>( - &self, - path: P, - ) -> Result, Error> { + pub fn open_file>(&self, path: P) -> Result, Error> { if path.as_ref().is_absolute() { return self.fs.open_file(path); } diff --git a/crates/bffs/src/entry.rs b/crates/bffs/src/entry.rs index c4f70b9..cf893dc 100644 --- a/crates/bffs/src/entry.rs +++ b/crates/bffs/src/entry.rs @@ -2,14 +2,15 @@ use core::{array::IntoIter, iter::Copied, str::Utf8Error}; use core::{char::DecodeUtf16, marker::PhantomData, slice::Iter}; use crate::{ + Fat32FileSystem, ReadSeek, consts::FATAttr, dir::Dir, error::Error, file::{File, RawFile}, - io::{IoBase, Read, ReadLeExt}, - Fat32FileSystem, ReadSeek, }; +use io::{IoBase, Read, ReadLeExt}; + #[cfg(feature = "alloc")] use alloc::borrow::ToOwned; #[cfg(feature = "alloc")] @@ -32,7 +33,7 @@ pub struct FatDirEntry { } impl FatDirEntry { - pub fn deserialize(reader: &mut T) -> Result> { + pub fn deserialize(reader: &mut T) -> Result { let mut name = [0u8; _]; reader.read_exact(&mut name)?; let attr = reader.read_u8()?; @@ -224,7 +225,7 @@ impl<'a, T> DirectoryIterator<'a, T> { } } -impl<'a, T: ReadSeek> Iterator for DirectoryIterator<'a, T> { +impl<'a, T: ReadSeek + 'a> Iterator for DirectoryIterator<'a, T> { type Item = Result, Error>; fn next(&mut self) -> Option { diff --git a/crates/bffs/src/error.rs b/crates/bffs/src/error.rs index 1fa5c6b..6c0dbe7 100644 --- a/crates/bffs/src/error.rs +++ b/crates/bffs/src/error.rs @@ -1,3 +1,5 @@ +use io::error::IoError; + /// Error enum with all errors that can be returned by functions from this crate /// /// Generic parameter `T` is a type of external error returned by the user provided storage @@ -55,15 +57,6 @@ impl core::fmt::Display for Error { } } -/// Trait that should be implemented by errors returned from the user supplied storage. -/// -/// Implementations for `std::io::Error` and `()` are provided by this crate. -pub trait IoError: core::fmt::Debug { - fn is_interrupted(&self) -> bool; - fn new_unexpected_eof_error() -> Self; - fn new_write_zero_error() -> Self; -} - impl IoError for Error { fn is_interrupted(&self) -> bool { match self { @@ -79,39 +72,8 @@ impl IoError for Error { fn new_write_zero_error() -> Self { Error::::WriteZero } -} -impl IoError for () { - fn is_interrupted(&self) -> bool { - false - } - - fn new_unexpected_eof_error() -> Self { - // empty - } - - fn new_write_zero_error() -> Self { - // empty - } -} - -#[cfg(all(feature = "std", not(target_arch = "riscv64")))] -impl IoError for std::io::Error { - fn is_interrupted(&self) -> bool { - self.kind() == std::io::ErrorKind::Interrupted - } - - fn new_unexpected_eof_error() -> Self { - Self::new( - std::io::ErrorKind::UnexpectedEof, - "failed to fill whole buffer", - ) - } - - fn new_write_zero_error() -> Self { - Self::new( - std::io::ErrorKind::WriteZero, - "failed to write whole buffer", - ) + fn new_invalid_utf8_error() -> Self { + Error::::InvalidUTF8 } } diff --git a/crates/bffs/src/file.rs b/crates/bffs/src/file.rs index e38ef53..0325066 100644 --- a/crates/bffs/src/file.rs +++ b/crates/bffs/src/file.rs @@ -1,10 +1,11 @@ use crate::{ + Fat32FileSystem, ReadSeek, ReadWriteSeek, consts::{FAT32_BAD_CLUSTER, FAT32_END_OF_CHAIN}, error::Error, - io::{self, IoBase, Read, Seek, Write}, - Fat32FileSystem, ReadSeek, ReadWriteSeek, }; +use io::{self, IoBase, Read, Seek, Write}; + #[derive(Debug, Clone)] pub struct RawFile<'a, T> { pub(crate) fs: &'a Fat32FileSystem, @@ -30,11 +31,11 @@ impl<'a, T> RawFile<'a, T> { } impl<'a, T: IoBase> IoBase for RawFile<'a, T> { - type Error = T::Error; + type Error = Error; } impl<'a, T: ReadSeek> Seek for RawFile<'a, T> { - fn seek(&mut self, pos: io::SeekFrom) -> Result> { + fn seek(&mut self, pos: io::SeekFrom) -> Result { let new_pos = match pos { io::SeekFrom::Start(s) => s, io::SeekFrom::Current(c) => (self.pos as i64 + c) as u64, @@ -66,7 +67,7 @@ impl<'a, T: ReadSeek> Seek for RawFile<'a, T> { } impl<'a, T: ReadSeek> Read for RawFile<'a, T> { - fn read(&mut self, buf: &mut [u8]) -> Result> { + fn read(&mut self, buf: &mut [u8]) -> Result { let max_len = self .size .map(|size| core::cmp::min(buf.len(), size as usize - self.pos as usize)) @@ -115,40 +116,41 @@ impl<'a, T: ReadSeek> Read for RawFile<'a, T> { } } impl<'a, T: ReadWriteSeek> Write for RawFile<'a, T> { - fn write(&mut self, _buf: &[u8]) -> Result> { + fn write(&mut self, _buf: &[u8]) -> Result { todo!() } - fn flush(&mut self) -> Result<(), Error> { + fn flush(&mut self) -> Result<(), Self::Error> { todo!() } } #[allow(unused)] +#[derive(Debug)] pub struct File<'a, T> { raw: RawFile<'a, T>, fs: &'a Fat32FileSystem, } impl<'a, T: IoBase> IoBase for File<'a, T> { - type Error = T::Error; + type Error = Error; } impl<'a, T: ReadSeek> Seek for File<'a, T> { - fn seek(&mut self, pos: io::SeekFrom) -> Result> { + fn seek(&mut self, pos: io::SeekFrom) -> Result { self.raw.seek(pos) } } impl<'a, T: ReadSeek> Read for File<'a, T> { - fn read(&mut self, buf: &mut [u8]) -> Result> { + fn read(&mut self, buf: &mut [u8]) -> Result { self.raw.read(buf) } } impl<'a, T: ReadWriteSeek> Write for File<'a, T> { - fn write(&mut self, buf: &[u8]) -> Result> { + fn write(&mut self, buf: &[u8]) -> Result { self.raw.write(buf) } - fn flush(&mut self) -> Result<(), Error> { + fn flush(&mut self) -> Result<(), Self::Error> { self.raw.flush() } } diff --git a/crates/bffs/src/lib.rs b/crates/bffs/src/lib.rs index 610b25c..ba52fb0 100644 --- a/crates/bffs/src/lib.rs +++ b/crates/bffs/src/lib.rs @@ -11,10 +11,11 @@ use crate::{ entry::{DirEntry, FatEntry}, error::Error, file::{File, RawFile}, - io::{Read, ReadLeExt, Seek, Write}, path::Path, }; +use io::{Read, ReadLeExt, Seek, Write}; + #[cfg(feature = "alloc")] extern crate alloc; @@ -24,7 +25,6 @@ pub mod dir; pub mod entry; pub mod error; pub mod file; -pub mod io; pub mod path; pub trait ReadSeek: Read + Seek {} @@ -136,10 +136,7 @@ impl Fat32FileSystem { } } impl Fat32FileSystem { - pub fn open_entry>( - &self, - path: P, - ) -> Result, Error> { + pub fn open_entry>(&self, path: P) -> Result, Error> { let path = path.as_ref().as_str().trim_start_matches("/"); self.root_directory().open_entry(path) } @@ -147,10 +144,7 @@ impl Fat32FileSystem { let path = path.as_ref().as_str().trim_start_matches("/"); self.root_directory().open_dir(path) } - pub fn open_file>( - &self, - path: P, - ) -> Result, Error> { + pub fn open_file>(&self, path: P) -> Result, Error> { let path = path.as_ref().as_str().trim_start_matches("/"); self.root_directory().open_file(path) } diff --git a/crates/bffs/src/path.rs b/crates/bffs/src/path.rs index 486f5e6..524e480 100644 --- a/crates/bffs/src/path.rs +++ b/crates/bffs/src/path.rs @@ -1,9 +1,13 @@ -use core::ops::Deref; +#[cfg(feature = "alloc")] +use core::{borrow::Borrow, ops::Deref}; +#[cfg(feature = "alloc")] +use alloc::borrow::ToOwned; #[cfg(feature = "alloc")] use alloc::string::String; #[repr(transparent)] +#[derive(Debug)] pub struct Path { inner: str, } @@ -19,6 +23,13 @@ impl AsRef for str { } } +#[cfg(feature = "alloc")] +impl AsRef for String { + fn as_ref(&self) -> &Path { + self.as_str().as_ref() + } +} + impl AsRef for Path { fn as_ref(&self) -> &Path { self @@ -32,9 +43,17 @@ impl AsRef for Path { } impl Path { + pub fn new(path: &str) -> &Self { + path.as_ref() + } + pub fn split_path(&self) -> (&str, Option<&Path>) { if let Some((start, end)) = self.inner.split_once("/") { - (start, Some(end.into())) + if end.is_empty() { + (start, None) + } else { + (start, Some(end.into())) + } } else { (&self.inner, None) } @@ -48,9 +67,46 @@ impl Path { pub fn is_relative(&self) -> bool { !self.is_absolute() } + pub fn is_empty(&self) -> bool { + self.inner.is_empty() + } + pub fn is_root(&self) -> bool { + self.as_str() == "/" + } + + pub fn starts_with>(&self, other: P) -> bool { + let this = self.split_path(); + let other = other.as_ref().split_path(); + if this.0 == other.0 { + if let Some(this) = this.1 + && let Some(other) = other.1 + { + this.starts_with(other) + } else { + other.1.is_none() + } + } else { + false + } + } + pub fn without>(&self, other: P) -> &Path { + let this = self.split_path(); + let other = other.as_ref().split_path(); + if this.0 == other.0 { + match (this.1, other.1) { + (None, None) => Path::new(""), + (None, Some(_)) => todo!(), + (Some(p), None) => p, + (Some(this), Some(other)) => this.without(other), + } + } else { + todo!() + } + } } #[cfg(feature = "alloc")] +#[derive(Debug, Hash, PartialEq, Eq)] pub struct PathBuf { inner: String, } @@ -63,3 +119,26 @@ impl Deref for PathBuf { self.inner.as_str().into() } } + +#[cfg(feature = "alloc")] +impl AsRef for PathBuf { + fn as_ref(&self) -> &Path { + self.inner.as_ref() + } +} + +#[cfg(feature = "alloc")] +impl Borrow for PathBuf { + fn borrow(&self) -> &str { + &self.inner + } +} + +#[cfg(feature = "alloc")] +impl From<&str> for PathBuf { + fn from(val: &str) -> Self { + PathBuf { + inner: val.to_owned(), + } + } +} diff --git a/crates/io/Cargo.toml b/crates/io/Cargo.toml new file mode 100644 index 0000000..783eee8 --- /dev/null +++ b/crates/io/Cargo.toml @@ -0,0 +1,10 @@ +[package] +name = "io" +version = "0.1.0" +edition = "2024" + +[dependencies] + +[features] +alloc = [] +std = ["alloc"] diff --git a/crates/io/src/error.rs b/crates/io/src/error.rs new file mode 100644 index 0000000..19dec69 --- /dev/null +++ b/crates/io/src/error.rs @@ -0,0 +1,48 @@ +/// Trait that should be implemented by errors returned from the user supplied storage. +/// +/// Implementations for `std::io::Error` and `()` are provided by this crate. +pub trait IoError: core::fmt::Debug { + fn is_interrupted(&self) -> bool; + fn new_unexpected_eof_error() -> Self; + fn new_write_zero_error() -> Self; + fn new_invalid_utf8_error() -> Self; +} + +impl IoError for () { + fn is_interrupted(&self) -> bool { + false + } + + fn new_unexpected_eof_error() -> Self { + // empty + } + + fn new_write_zero_error() -> Self { + // empty + } + + fn new_invalid_utf8_error() -> Self { + // empty + } +} + +#[cfg(all(feature = "std", not(target_arch = "riscv64")))] +impl IoError for std::io::Error { + fn is_interrupted(&self) -> bool { + self.kind() == std::io::ErrorKind::Interrupted + } + + fn new_unexpected_eof_error() -> Self { + Self::new( + std::io::ErrorKind::UnexpectedEof, + "failed to fill whole buffer", + ) + } + + fn new_write_zero_error() -> Self { + Self::new( + std::io::ErrorKind::WriteZero, + "failed to write whole buffer", + ) + } +} diff --git a/crates/bffs/src/io.rs b/crates/io/src/lib.rs similarity index 82% rename from crates/bffs/src/io.rs rename to crates/io/src/lib.rs index c328aef..58a4757 100644 --- a/crates/bffs/src/io.rs +++ b/crates/io/src/lib.rs @@ -1,4 +1,10 @@ -use crate::error::{Error, IoError}; +#![cfg_attr(any(not(feature = "std"), target_arch = "riscv64"), no_std)] +pub mod error; + +use error::IoError; + +#[cfg(feature = "alloc")] +extern crate alloc; #[cfg(feature = "alloc")] use alloc::string::String; @@ -41,7 +47,7 @@ pub trait Read: IoBase { /// then it must be guaranteed that no bytes were read. /// An error for which `IoError::is_interrupted` returns true is non-fatal and the read operation should be retried /// if there is nothing else to do. - fn read(&mut self, buf: &mut [u8]) -> Result>; + fn read(&mut self, buf: &mut [u8]) -> Result; /// Read the exact number of bytes required to fill `buf`. /// @@ -58,7 +64,7 @@ pub trait Read: IoBase { /// /// If this function returns an error, it is unspecified how many bytes it has read, but it will never read more /// than would be necessary to completely fill the buffer. - fn read_exact(&mut self, mut buf: &mut [u8]) -> Result<(), Error> { + fn read_exact(&mut self, mut buf: &mut [u8]) -> Result<(), Self::Error> { while !buf.is_empty() { match self.read(buf) { Ok(0) => break, @@ -73,12 +79,12 @@ pub trait Read: IoBase { if buf.is_empty() { Ok(()) } else { - Err(Error::::new_unexpected_eof_error()) + Err(Self::Error::new_unexpected_eof_error()) } } #[cfg(feature = "alloc")] - fn read_to_end(&mut self, buf: &mut Vec) -> Result> { + fn read_to_end(&mut self, buf: &mut Vec) -> Result { const CHUNK_SIZE: usize = 32; let start_len = buf.len(); loop { @@ -93,11 +99,11 @@ pub trait Read: IoBase { } #[cfg(feature = "alloc")] - fn read_to_string(&mut self, buf: &mut String) -> Result> { + fn read_to_string(&mut self, buf: &mut String) -> Result { let read = self.read_to_end(unsafe { buf.as_mut_vec() })?; if str::from_utf8(buf.as_bytes()).is_err() { - Err(Error::InvalidUTF8) + Err(Self::Error::new_invalid_utf8_error()) } else { Ok(read) } @@ -115,7 +121,7 @@ pub trait Write: IoBase { /// Each call to write may generate an I/O error indicating that the operation could not be completed. If an error /// is returned then no bytes in the buffer were written to this writer. /// It is not considered an error if the entire buffer could not be written to this writer. - fn write(&mut self, buf: &[u8]) -> Result>; + fn write(&mut self, buf: &[u8]) -> Result; /// Attempts to write an entire buffer into this writer. /// @@ -129,11 +135,11 @@ pub trait Write: IoBase { /// /// This function will return the first error for which `IoError::is_interrupted` method returns false that `write` /// returns. - fn write_all(&mut self, mut buf: &[u8]) -> Result<(), Error> { + fn write_all(&mut self, mut buf: &[u8]) -> Result<(), Self::Error> { while !buf.is_empty() { match self.write(buf) { Ok(0) => { - return Err(Error::::new_write_zero_error()); + return Err(Self::Error::new_write_zero_error()); } Ok(n) => buf = &buf[n..], Err(ref e) if e.is_interrupted() => {} @@ -148,7 +154,7 @@ pub trait Write: IoBase { /// # Errors /// /// It is considered an error if not all bytes could be written due to I/O errors or EOF being reached. - fn flush(&mut self) -> Result<(), Error>; + fn flush(&mut self) -> Result<(), Self::Error>; } /// Enumeration of possible methods to seek within an I/O object. @@ -177,7 +183,7 @@ pub trait Seek: IoBase { /// /// # Errors /// Seeking to a negative offset is considered an error. - fn seek(&mut self, pos: SeekFrom) -> Result>; + fn seek(&mut self, pos: SeekFrom) -> Result; } #[cfg(all(feature = "std", not(target_arch = "riscv64")))] @@ -209,59 +215,59 @@ impl IoBase for std::fs::File { #[cfg(all(feature = "std", not(target_arch = "riscv64")))] impl> Read for T { - fn read(&mut self, buf: &mut [u8]) -> Result> { + fn read(&mut self, buf: &mut [u8]) -> Result { self.read(buf).map_err(Error::Io) } - fn read_exact(&mut self, buf: &mut [u8]) -> Result<(), Error> { + fn read_exact(&mut self, buf: &mut [u8]) -> Result<(), Self::Error> { self.read_exact(buf).map_err(Error::Io) } } #[cfg(all(feature = "std", not(target_arch = "riscv64")))] impl> Write for T { - fn write(&mut self, buf: &[u8]) -> Result> { + fn write(&mut self, buf: &[u8]) -> Result { self.write(buf).map_err(Error::Io) } - fn write_all(&mut self, buf: &[u8]) -> Result<(), Error> { + fn write_all(&mut self, buf: &[u8]) -> Result<(), Self::Error> { self.write_all(buf).map_err(Error::Io) } - fn flush(&mut self) -> Result<(), Error> { + fn flush(&mut self) -> Result<(), Self::Error> { self.flush().map_err(Error::Io) } } #[cfg(all(feature = "std", not(target_arch = "riscv64")))] impl> Seek for T { - fn seek(&mut self, pos: SeekFrom) -> Result> { + fn seek(&mut self, pos: SeekFrom) -> Result { self.seek(pos.into()).map_err(Error::Io) } } -pub(crate) trait ReadLeExt { +pub trait ReadLeExt { type Error; - fn read_u8(&mut self) -> Result>; - fn read_u16_le(&mut self) -> Result>; - fn read_u32_le(&mut self) -> Result>; + fn read_u8(&mut self) -> Result; + fn read_u16_le(&mut self) -> Result; + fn read_u32_le(&mut self) -> Result; } impl ReadLeExt for T { type Error = ::Error; - fn read_u8(&mut self) -> Result> { + fn read_u8(&mut self) -> Result { let mut buf = [0_u8; 1]; self.read_exact(&mut buf)?; Ok(buf[0]) } - fn read_u16_le(&mut self) -> Result> { + fn read_u16_le(&mut self) -> Result { let mut buf = [0_u8; 2]; self.read_exact(&mut buf)?; Ok(u16::from_le_bytes(buf)) } - fn read_u32_le(&mut self) -> Result> { + fn read_u32_le(&mut self) -> Result { let mut buf = [0_u8; 4]; self.read_exact(&mut buf)?; Ok(u32::from_le_bytes(buf)) @@ -271,23 +277,23 @@ impl ReadLeExt for T { #[allow(unused)] pub(crate) trait WriteLeExt { type Error; - fn write_u8(&mut self, n: u8) -> Result<(), Error>; - fn write_u16_le(&mut self, n: u16) -> Result<(), Error>; - fn write_u32_le(&mut self, n: u32) -> Result<(), Error>; + fn write_u8(&mut self, n: u8) -> Result<(), Self::Error>; + fn write_u16_le(&mut self, n: u16) -> Result<(), Self::Error>; + fn write_u32_le(&mut self, n: u32) -> Result<(), Self::Error>; } impl WriteLeExt for T { type Error = ::Error; - fn write_u8(&mut self, n: u8) -> Result<(), Error> { + fn write_u8(&mut self, n: u8) -> Result<(), Self::Error> { self.write_all(&[n]) } - fn write_u16_le(&mut self, n: u16) -> Result<(), Error> { + fn write_u16_le(&mut self, n: u16) -> Result<(), Self::Error> { self.write_all(&n.to_le_bytes()) } - fn write_u32_le(&mut self, n: u32) -> Result<(), Error> { + fn write_u32_le(&mut self, n: u32) -> Result<(), Self::Error> { self.write_all(&n.to_le_bytes()) } } diff --git a/crates/os-std/Cargo.toml b/crates/os-std/Cargo.toml index 4074eab..d2575ec 100644 --- a/crates/os-std/Cargo.toml +++ b/crates/os-std/Cargo.toml @@ -6,3 +6,4 @@ edition = "2024" [dependencies] os-std-macros = { path = "../os-std-macros" } shared = { path = "../shared", features = ["user"] } +io = { path = "../io", features = ["alloc"] } diff --git a/crates/os-std/src/io.rs b/crates/os-std/src/io.rs new file mode 100644 index 0000000..079c8bb --- /dev/null +++ b/crates/os-std/src/io.rs @@ -0,0 +1 @@ +pub use io::SeekFrom; diff --git a/crates/os-std/src/lib.rs b/crates/os-std/src/lib.rs index 211a0a8..8797a23 100644 --- a/crates/os-std/src/lib.rs +++ b/crates/os-std/src/lib.rs @@ -2,6 +2,7 @@ extern crate alloc; +pub mod io; pub mod prelude; pub use shared::fs; diff --git a/crates/shared/Cargo.toml b/crates/shared/Cargo.toml index 081c69b..ce1b63c 100644 --- a/crates/shared/Cargo.toml +++ b/crates/shared/Cargo.toml @@ -5,6 +5,7 @@ edition = "2024" [dependencies] bffs = { path = "../bffs" } +io = { path = "../io" } [features] kernel = [] diff --git a/crates/shared/src/fs.rs b/crates/shared/src/fs.rs index 49badb7..b8f3fcc 100644 --- a/crates/shared/src/fs.rs +++ b/crates/shared/src/fs.rs @@ -1,9 +1,16 @@ +#[derive(Debug)] pub struct File { fd: u64, } impl File { + /// # Safety + /// The file descriptor must be valid pub unsafe fn new(fd: u64) -> Self { Self { fd } } + + pub fn as_fd(&self) -> u64 { + self.fd + } } diff --git a/crates/shared/src/syscall.rs b/crates/shared/src/syscall.rs index 2aa07b8..debcb12 100644 --- a/crates/shared/src/syscall.rs +++ b/crates/shared/src/syscall.rs @@ -1,10 +1,15 @@ use core::{alloc::Layout, time::Duration}; use bffs::path::Path; +use io::SeekFrom; + +use crate::fs::File; #[repr(u64)] pub enum SysCall { + Write = 1, Open = 2, + Seek = 8, Alloc = 40, Dealloc = 41, Exit = 60, @@ -17,7 +22,9 @@ pub enum SysCall { impl From for SysCall { fn from(value: u64) -> Self { match value { + 1 => SysCall::Write, 2 => SysCall::Open, + 8 => SysCall::Seek, 40 => SysCall::Alloc, 41 => SysCall::Dealloc, 60 => SysCall::Exit, @@ -128,12 +135,29 @@ pub fn dealloc(ptr: *mut u8, layout: core::alloc::Layout) { syscall!(SysCall::Dealloc, ptr as u64, size as u64, align as u64); } } -pub fn open>(path: P) -> u64 { +pub fn open>(path: P) -> File { unsafe { let path_str = path.as_ref().as_str(); let ptr = path_str.as_ptr(); let size = path_str.len(); let (fd, ..) = syscall!(SysCall::Open, ptr as u64, size as u64); - fd + File::new(fd) + } +} +pub fn write(file: &mut File, buf: &[u8]) { + unsafe { + let ptr = buf.as_ptr(); + let size = buf.len(); + syscall!(SysCall::Write, file.as_fd(), ptr as u64, size as u64); + } +} +pub fn seek(file: &mut File, seek: SeekFrom) { + unsafe { + let (discriminant, value) = match seek { + SeekFrom::Start(v) => (0, v), + SeekFrom::End(v) => (1, v as u64), + SeekFrom::Current(v) => (2, v as u64), + }; + syscall!(SysCall::Seek, file.as_fd(), discriminant, value); } } diff --git a/justfile b/justfile index 0034426..5252ad1 100644 --- a/justfile +++ b/justfile @@ -17,7 +17,7 @@ build_user_prog prog: cp {{ bin_path / prog }} {{ "mnt/usr/bin" / prog }} # riscv64-elf-objcopy -O binary {{ bin_path / prog }} {{ "mnt/usr/bin" / prog }} -build: mount_filesystem (map_dir "user" f"just cargo_flags=\"{{cargo_flags}}\" build_user_prog") +build: mount_filesystem (map_dir "user" f"just release=\"{{release}}\" cargo_flags=\"{{cargo_flags}}\" build_user_prog") cargo b {{ cargo_flags }} just sync_filesystem @@ -29,7 +29,17 @@ map_dir dir recipe: {{ recipe }} $file ; \ done -qemu := "qemu-system-riscv64 -machine virt -device bochs-display -bios none -m 512M -device loader,file=disk.img,addr=0x90000000" +qemu := "qemu-system-riscv64 \ + -machine virt \ + -device bochs-display \ + -global virtio-mmio.force-legacy=false \ + -device virtio-keyboard-device,bus=virtio-mmio-bus.0 \ + -device loader,file=disk.img,addr=0x90000000 \ + -bios none -m 512M" + +# \ +# -d guest_errors,unimp \ +# -trace \"virtio*\"" perf: build {{ qemu }} -perfmap -kernel {{ bin_path / "kernel-rust" }}& diff --git a/rustfmt.toml b/rustfmt.toml new file mode 100644 index 0000000..f216078 --- /dev/null +++ b/rustfmt.toml @@ -0,0 +1 @@ +edition = "2024" diff --git a/src/boot.rs b/src/boot.rs index 5fb3487..e6274e9 100644 --- a/src/boot.rs +++ b/src/boot.rs @@ -7,7 +7,12 @@ use core::arch::naked_asm; use crate::{ clear_csr, interrupt::{setup_machine_trap_handler, setup_supervisor_trap_handler}, - mret, set_csr, supervisor_mode_entry, write_csr, + mret, set_csr, supervisor_mode_entry, + virtio::{ + Virtqueue, + input::{VirtioInputDriver, init_plic_m_mode}, + }, + write_csr, }; pub mod sbi; @@ -45,6 +50,9 @@ pub extern "C" fn machine_mode_entry() { set_csr!(mideleg, 1 << 5); set_csr!(medeleg, 1 << 8); + // Delegate PLIC keyboard interrupt + set_csr!(mideleg, 1 << 9); + unsafe { setup_supervisor_trap_handler(); }; diff --git a/src/draw.rs b/src/draw.rs new file mode 100644 index 0000000..b127dbd --- /dev/null +++ b/src/draw.rs @@ -0,0 +1,124 @@ +use kernel_macros::include_font_plate; + +/// 24-bit RGB color used by the framebuffer. +#[repr(transparent)] +#[derive(Clone, Copy)] +pub struct Color(u32); + +#[allow(unused)] +impl Color { + pub const fn from_rgb(r: u8, g: u8, b: u8) -> Self { + Self((r as u32) << 16 | (g as u32) << 8 | b as u32) + } + + pub fn as_bytes(&self) -> [u8; 4] { + self.0.to_ne_bytes() + } + + pub const WHITE: Color = Color::from_rgb(255, 255, 255); + pub const GRAY: Color = Color::from_rgb(128, 128, 128); + pub const LIGHT_GRAY: Color = Color::from_rgb(128, 64, 64); + pub const BLACK: Color = Color::from_rgb(0, 0, 0); + pub const RED: Color = Color::from_rgb(255, 0, 0); + pub const GREEN: Color = Color::from_rgb(0, 255, 0); + pub const BLUE: Color = Color::from_rgb(0, 0, 255); +} + +pub trait Draw { + /// # Safety + /// `x` must be less than `WIDTH` and `y` must be less than `HEIGHT` + /// Write a single pixel into the framebuffer (unsafe). + /// + /// Caller must ensure `x < WIDTH` and `y < HEIGHT`. + unsafe fn write_pixel_unsafe(&mut self, x: u16, y: u16, color: Color); + + fn get_width(&self) -> usize; + fn get_height(&self) -> usize; + + /// Draw a single character with a background color at (x,y). + /// + /// Uses the embedded font plate to render glyphs into the framebuffer. + unsafe fn draw_char_bg(&mut self, x: u16, y: u16, c: char, color: Color, bg_color: Color) { + let c = if (c as u8 > b'~') || ((c as u8) < b' ') { + b'/' - b' ' + } else { + c as u8 - b' ' + }; + + // Get char position within font plate + let char_x = (c as usize % 32) * FONT_WIDTH; + let char_y = (c as usize / 32) * FONT_HEIGHT; + + for i in 0..(FONT_WIDTH as u16) { + for j in 0..(FONT_HEIGHT as u16) { + let xx = x + i; + let yy = y + j; + + if xx < (self.get_width() as u16) && yy < (self.get_height() as u16) { + if unsafe { Self::font_plate_index(char_x as u16 + i, char_y as u16 + j) } { + unsafe { self.write_pixel_unsafe(xx, yy, color) } + } else { + unsafe { self.write_pixel_unsafe(xx, yy, bg_color) } + } + } + } + } + } + + /// # Safety + /// The text must have a length that can fit within a `u16` + /// Draw a UTF-8 string at the given position. + /// + /// Newlines (`\n`) advance `y` by `FONT_HEIGHT` and carriage return (`\r`) + /// resets to the starting `x` position. + unsafe fn draw_string>( + &mut self, + x: u16, + mut y: u16, + str: T, + color: Color, + bg_color: Color, + ) { + let mut current_x = x; + str.as_ref().chars().for_each(|c| unsafe { + match c { + '\n' => { + current_x = x; + y += FONT_HEIGHT as u16; + } + '\r' => { + current_x = x; + } + c => { + self.draw_char_bg(current_x, y, c, color, bg_color); + current_x += FONT_WIDTH as u16; + } + } + }); + } + + /// Fill the entire framebuffer with a single color. + fn clear_screen(&mut self, color: Color) { + for y in 0..self.get_height() { + for x in 0..self.get_width() { + unsafe { self.write_pixel_unsafe(x as u16, y as u16, color) }; + } + } + } + + /// Return whether a pixel inside the embedded font plate is set. + unsafe fn font_plate_index(x: u16, y: u16) -> bool { + let pixel_index = (y as usize) * FONTPLATE_WIDTH + (x as usize); + let byte_index = pixel_index / 8; + let bit_index = pixel_index % 8; + + (FONTPLATE[byte_index] >> bit_index) & 0b1 == 0b1 + } +} + +pub const FONT_WIDTH: usize = 6; +pub const FONT_HEIGHT: usize = 13; +pub const FONTPLATE_WIDTH: usize = 32 * FONT_WIDTH; +pub const FONTPLATE_HEIGHT: usize = 3 * FONT_HEIGHT; +pub const FONTPLATE_SIZE: usize = FONTPLATE_WIDTH * FONTPLATE_HEIGHT / 8; +pub static FONTPLATE: [u8; FONTPLATE_SIZE] = include_font_plate! {"assets/fontplate.png"}; diff --git a/src/fs.rs b/src/fs.rs index 122a54d..58c7afb 100644 --- a/src/fs.rs +++ b/src/fs.rs @@ -2,44 +2,18 @@ //! //! Implements a minimal disk backend and exposes a global FILE_SYSTEM used by //! the kernel to load user binaries. -use core::{cell::UnsafeCell, ops::Deref}; -use bffs::{ - io::{IoBase, Read, Seek}, - Fat32FileSystem, -}; +use core::fmt::Debug; + +use alloc::boxed::Box; +use bffs::{Fat32FileSystem, ReadSeek, entry::DirEntry, file::File}; + +use io::{IoBase, Read, Seek, Write}; + +use crate::virtual_fs::{VirtualFileSystem, VirtualNode}; const DISK_ADDR: *const u8 = 0x9000_0000 as *const _; -/// Lazy holder for the kernel's filesystem instance. -/// -/// The inner `UnsafeCell` allows one-time initialization at early boot while -/// exposing a shared `&'static` reference through `Deref` once initialized. -pub struct FSTemp(UnsafeCell>>); -unsafe impl Sync for FSTemp {} - -impl FSTemp { - /// Initialize the global filesystem from the in-memory disk image. - /// - /// Safety: must be called exactly once during early kernel initialization - /// before any other filesystem operations occur. - pub unsafe fn init(&self) { - unsafe { - *self.0.get() = Some(Fat32FileSystem::new(Disk::new(1024 * 1024 * 16)).unwrap()); - } - } -} - -impl Deref for FSTemp { - type Target = Fat32FileSystem; - - fn deref(&self) -> &Self::Target { - unsafe { (&*self.0.get()).as_ref().unwrap_unchecked() } - } -} - -pub static FAT32_FILE_SYSTEM: FSTemp = FSTemp(UnsafeCell::new(None)); - #[derive(Debug)] /// Simple disk backend that reads from a fixed memory region. /// @@ -62,11 +36,11 @@ impl IoBase for Disk { } impl Seek for Disk { - fn seek(&mut self, pos: bffs::io::SeekFrom) -> Result> { + fn seek(&mut self, pos: io::SeekFrom) -> Result { match pos { - bffs::io::SeekFrom::Start(pos) => self.pos = pos, - bffs::io::SeekFrom::End(_) => unimplemented!(), - bffs::io::SeekFrom::Current(pos) => self.pos = (self.pos as i64 + pos) as u64, + io::SeekFrom::Start(pos) => self.pos = pos, + io::SeekFrom::End(_) => unimplemented!(), + io::SeekFrom::Current(pos) => self.pos = (self.pos as i64 + pos) as u64, } Ok(self.pos) } @@ -74,7 +48,7 @@ impl Seek for Disk { impl Read for Disk { /// Read bytes from the in-memory disk image into `buf`. - fn read(&mut self, buf: &mut [u8]) -> Result> { + fn read(&mut self, buf: &mut [u8]) -> Result { if self.pos >= self.size { return Ok(0); } @@ -90,3 +64,69 @@ impl Read for Disk { Ok(size) } } + +#[derive(Debug)] +pub struct Fat32VirtualNode<'a, T> { + kind: Fat32VirtualNodeType<'a, T>, + _entry: DirEntry<'a, T>, +} +#[derive(Debug)] +enum Fat32VirtualNodeType<'a, T> { + Dir, + File(File<'a, T>), +} + +impl<'a, T: ReadSeek> Fat32VirtualNode<'a, T> { + pub unsafe fn new(entry: DirEntry<'a, T>) -> Self { + let kind = if entry.is_dir() { + Fat32VirtualNodeType::Dir + } else { + Fat32VirtualNodeType::File(entry.to_file()) + }; + Self { + kind, + _entry: entry, + } + } +} + +impl VirtualNode for Fat32VirtualNode<'_, T> {} + +impl IoBase for Fat32VirtualNode<'_, T> { + type Error = (); +} + +impl Seek for Fat32VirtualNode<'_, T> { + fn seek(&mut self, pos: io::SeekFrom) -> Result { + match &mut self.kind { + Fat32VirtualNodeType::Dir => todo!(), + Fat32VirtualNodeType::File(file) => file.seek(pos).map_err(|_| ()), + } + } +} + +impl Read for Fat32VirtualNode<'_, T> { + fn read(&mut self, buf: &mut [u8]) -> Result { + match &mut self.kind { + Fat32VirtualNodeType::Dir => unimplemented!(), + Fat32VirtualNodeType::File(file) => file.read(buf).map_err(|_| ()), + } + } +} + +impl Write for Fat32VirtualNode<'_, T> { + fn write(&mut self, _buf: &[u8]) -> Result { + todo!() + } + + fn flush(&mut self) -> Result<(), Self::Error> { + todo!() + } +} + +impl VirtualFileSystem for Fat32FileSystem { + fn open(&mut self, path: &bffs::path::Path) -> Result, ()> { + let entry = self.open_entry(path).unwrap(); + Ok(Box::new(unsafe { Fat32VirtualNode::new(entry) })) + } +} diff --git a/src/interrupt.rs b/src/interrupt.rs index 01fd2d1..4a2d602 100644 --- a/src/interrupt.rs +++ b/src/interrupt.rs @@ -4,18 +4,22 @@ //! This module contains the low-level trap handlers for machine and supervisor //! modes and the syscall dispatch implementation used by user processes. use alloc::str; +use io::SeekFrom; use log::info; use shared::syscall::SysCall; use crate::{ + KBD_DRIVER, boot::sbi::{EextensionID, TimerFunctionID}, - clear_csr, - process::{exit_process, sleep, ExecutionContext}, + clear_csr, println, + process::{ExecutionContext, exit_process, sleep}, read_csr, riscv::disable_interrupt, scheduler::SCHEDULER, set_csr, syscall, - time::{setup_next_timer_interrupt, IRQ_M_TIMER}, + time::{IRQ_M_EXTERNAL, IRQ_M_TIMER, setup_next_timer_interrupt}, + virtio::input::S_MODE_CLAIM_COMPLETE, + virtual_fs::{FILE_SYSTEM, VirtualFileSystem}, write_csr, }; use core::{alloc::Layout, arch::naked_asm, time::Duration}; @@ -89,6 +93,9 @@ unsafe extern "C" fn machine_trap_handler( setup_next_timer_interrupt(); set_csr!(mip, 1 << 5); } + 11 => { + set_csr!(mip, 1 << 9); + } _ => {} } } @@ -123,7 +130,7 @@ unsafe extern "C" fn supervisor_trap_handler( match syscall { SysCall::Open => { let path = unsafe { str::from_raw_parts(a1 as *const u8, a2 as usize) }; - let virtual_node = syscall::open(path, false).unwrap(); + let virtual_node = unsafe { FILE_SYSTEM.open(path.as_ref()).unwrap() }; let mut scheduler = SCHEDULER.lock(); let current_process = scheduler.get_current_process(); @@ -132,6 +139,29 @@ unsafe extern "C" fn supervisor_trap_handler( current_process.next_fd += 1; unsafe { (*interrupt_state).a[0] = fd }; } + SysCall::Write => { + let fd = a1; + let buf = + unsafe { core::slice::from_raw_parts(a2 as *const u8, a3 as usize) }; + + let mut scheduler = SCHEDULER.lock(); + let current_process = scheduler.get_current_process(); + let vnode = current_process.fd_table.get_mut(&fd).unwrap(); + vnode.write(buf).unwrap(); + } + SysCall::Seek => { + let fd = a1; + let seek = match a2 { + 0 => SeekFrom::Start(a3), + 1 => SeekFrom::End(a3 as i64), + 2 => SeekFrom::Current(a3 as i64), + _ => unimplemented!(), + }; + let mut scheduler = SCHEDULER.lock(); + let current_process = scheduler.get_current_process(); + let vnode = current_process.fd_table.get_mut(&fd).unwrap(); + vnode.seek(seek).unwrap(); + } SysCall::Alloc => { let layout = Layout::from_size_align(a1 as usize, a2 as usize).unwrap(); // Allocate memory and put the pointer in a0 @@ -176,6 +206,20 @@ unsafe extern "C" fn supervisor_trap_handler( timer_interrupt(); SCHEDULER.lock().schedule(&mut interrupt_state); } + 9 => { + println!("click"); + let irq = core::ptr::read_volatile(S_MODE_CLAIM_COMPLETE); + + if irq != 0 { + // ... Traiter l'interruption VirtIO ici ... + + // 2. Écrire l'IRQ (Complete) <--- INDISPENSABLE + core::ptr::write_volatile(S_MODE_CLAIM_COMPLETE, irq); + KBD_DRIVER.handle_interrupt(); + } else { + panic!() + } + } _ => {} } } @@ -186,11 +230,13 @@ unsafe extern "C" fn supervisor_trap_handler( pub unsafe fn setup_machine_trap_handler() { write_csr!(mtvec, _machine_mode_trap); set_csr!(mie, IRQ_M_TIMER); + set_csr!(mie, IRQ_M_EXTERNAL); } /// Install the supervisor-mode trap entry point and configure periodic timer. pub unsafe fn setup_supervisor_trap_handler() { write_csr!(stvec, _supervisor_mode_trap); + set_csr!(sie, 1 << 9); setup_timer_interrupt(); } diff --git a/src/main.rs b/src/main.rs index 69d8d35..e8f86da 100644 --- a/src/main.rs +++ b/src/main.rs @@ -5,32 +5,29 @@ #![no_std] #![no_main] // #![warn(clippy::pedantic)] -#![feature( - riscv_ext_intrinsics, - const_trait_impl, - iter_map_windows, - str_from_raw_parts, - macro_metavar_expr, - macro_metavar_expr_concat, - ptr_metadata -)] +#![allow(static_mut_refs)] +#![feature(riscv_ext_intrinsics, str_from_raw_parts)] + +use core::sync::atomic::AtomicBool; use alloc::boxed::Box; use embedded_alloc::LlffHeap as Heap; use log::info; use crate::{ - fs::FAT32_FILE_SYSTEM, io::init_log, riscv::enable_supervisor_interrupt, - scheduler::{idle, SCHEDULER}, + scheduler::{SCHEDULER, idle}, user::{proc2, test}, - vga::{Color, Vga}, + vga::Vga, + virtio::{Virtqueue, input::{VirtioInputDriver, init_plic_m_mode}}, + virtual_fs::init_file_system, }; extern crate alloc; mod boot; mod critical_section; +mod draw; mod fs; mod interrupt; mod io; @@ -41,30 +38,41 @@ mod scheduler; mod sync; mod syscall; mod time; +mod tty; mod uart; mod user; mod vga; +mod virtio; +mod virtual_console; mod virtual_fs; pub const HEAP_SIZE: usize = 1024 * 1024 * 32; // 32Mo RAM #[global_allocator] static HEAP: Heap = Heap::empty(); +static HEAP_INITIALIZED: AtomicBool = AtomicBool::new(false); + // Usize is assumed to be an u64 in the whole kernel const _: () = assert!(core::mem::size_of::() == core::mem::size_of::()); +// 1. Allouer de la mémoire statique alignée pour la queue +static mut KBD_QUEUE: Virtqueue = unsafe { core::mem::zeroed() }; +// 2. Initialisation (adresse 0x10001000 typique pour QEMU virt machine) +pub static mut KBD_DRIVER: VirtioInputDriver = + unsafe { VirtioInputDriver::new(0x10001000, &mut KBD_QUEUE) }; #[unsafe(no_mangle)] pub extern "C" fn supervisor_mode_entry() { unsafe { embedded_alloc::init!(HEAP, HEAP_SIZE); + HEAP_INITIALIZED.store(true, core::sync::atomic::Ordering::Relaxed); init_log().unwrap(); Vga::init(); - FAT32_FILE_SYSTEM.init(); SCHEDULER.lock().init(); + init_file_system(); } info!("Hello World !"); - unsafe { Vga::draw_string(10, 10, "Hello World !", Color::WHITE, Color::BLACK) }; + // unsafe { Vga.draw_string(10, 10, "Hello World !", Color::WHITE, Color::BLACK) }; SCHEDULER.lock().create_process(Box::new(test), "proc1"); SCHEDULER.lock().create_process(Box::new(proc2), "proc2"); @@ -74,5 +82,10 @@ pub extern "C" fn supervisor_mode_entry() { .create_process_from_file("/usr/bin/test_pic"); enable_supervisor_interrupt(); + + unsafe { + KBD_DRIVER.init(); + init_plic_m_mode(); + } idle(); } diff --git a/src/panic_handler.rs b/src/panic_handler.rs index 9b868ca..fba74b6 100644 --- a/src/panic_handler.rs +++ b/src/panic_handler.rs @@ -7,29 +7,38 @@ use core::arch::riscv64::wfi; use alloc::{format, string::ToString}; use log::error; -use crate::vga::{Color, Vga, FONT_HEIGHT}; +use crate::{ + HEAP, HEAP_INITIALIZED, + draw::{Color, Draw, FONT_HEIGHT}, + uart::write_uart, + vga::Vga, +}; #[panic_handler] /// Kernel panic handler that displays the panic message on the framebuffer and halts. fn panic(panic_info: &core::panic::PanicInfo) -> ! { - error!("PANIC !"); - let mut panic_message = panic_info.message().to_string(); - if let Some(location) = panic_info.location() { - panic_message = format!("{panic_message} at {}:{}", location.file(), location.line()); - } - error!("{panic_message}"); + if !HEAP_INITIALIZED.load(core::sync::atomic::Ordering::Relaxed) { + write_uart("EARLY PANIC !"); + } else { + error!("PANIC !"); + let mut panic_message = panic_info.message().to_string(); + if let Some(location) = panic_info.location() { + panic_message = format!("{panic_message} at {}:{}", location.file(), location.line()); + } + error!("{panic_message}"); - Vga::clear_screen(Color::WHITE); - unsafe { Vga::draw_string(0, 0, "PANIC !", Color::BLACK, Color::WHITE) }; - unsafe { - Vga::draw_string( - 0, - FONT_HEIGHT as u16, - panic_message, - Color::BLACK, - Color::WHITE, - ) - }; + Vga.clear_screen(Color::WHITE); + unsafe { Vga.draw_string(0, 0, "PANIC !", Color::BLACK, Color::WHITE) }; + unsafe { + Vga.draw_string( + 0, + FONT_HEIGHT as u16, + panic_message, + Color::BLACK, + Color::WHITE, + ) + }; + } loop { unsafe { wfi() } diff --git a/src/process.rs b/src/process.rs index c0cbb17..90912a6 100644 --- a/src/process.rs +++ b/src/process.rs @@ -10,17 +10,17 @@ use core::time::Duration; use alloc::{boxed::Box, format, string::String, vec::Vec}; -use bffs::{io::Read, path::Path}; +use bffs::path::Path; use goblin::elf::reloc::R_RISCV_RELATIVE; use hashbrown::HashMap; +use log::info; use shared::syscall::exit; use crate::{ - fs::FAT32_FILE_SYSTEM, println, - scheduler::{Scheduler, SCHEDULER}, + scheduler::{SCHEDULER, Scheduler}, time::elapsed_time_since_startup, - virtual_fs::VirtualNode, + virtual_fs::{FILE_SYSTEM, VirtualFileSystem, VirtualNode}, }; /// Size of the stack allocated to each process (in 64-bit words). @@ -84,7 +84,7 @@ pub struct Process { /// Current state of the process. pub state: ProcessState, /// Optional entry point for the process code. - pub entry: Option>, + pub entry: Option>, /// Wake time for sleeping processes. pub wake_time: Duration, /// Saved execution context. @@ -92,7 +92,7 @@ pub struct Process { /// Process stack. pub stack: [u64; STACK_SIZE], /// File descriptor table. - pub fd_table: HashMap>, + pub fd_table: HashMap>, /// Next available file descriptor. pub next_fd: u64, } @@ -164,7 +164,9 @@ impl Scheduler { let name = path.as_str(); // Open and read the binary file - let mut bin = FAT32_FILE_SYSTEM.open_file(path).unwrap(); + info!("ue"); + let mut bin = unsafe { FILE_SYSTEM.open(path).unwrap() }; + info!("ue"); println!("Creating process"); let mut content: Vec = Vec::new(); bin.read_to_end(&mut content).unwrap(); @@ -190,7 +192,7 @@ impl Scheduler { if min_vaddr != u64::MAX { let size = (max_vaddr - min_vaddr) as usize; - use alloc::alloc::{alloc_zeroed, Layout}; + use alloc::alloc::{Layout, alloc_zeroed}; let layout = Layout::from_size_align(size, 0x1000).unwrap(); let base = unsafe { alloc_zeroed(layout) }; diff --git a/src/sync.rs b/src/sync.rs index 4d3c178..1173e20 100644 --- a/src/sync.rs +++ b/src/sync.rs @@ -45,7 +45,7 @@ impl Drop for MutexGuard<'_, T> { } } -impl<'a, T> Deref for MutexGuard<'a, T> { +impl Deref for MutexGuard<'_, T> { type Target = T; fn deref(&self) -> &Self::Target { @@ -53,7 +53,7 @@ impl<'a, T> Deref for MutexGuard<'a, T> { } } -impl<'a, T> DerefMut for MutexGuard<'a, T> { +impl DerefMut for MutexGuard<'_, T> { fn deref_mut(&mut self) -> &mut Self::Target { unsafe { &mut *self.mutex.value.get() } } diff --git a/src/syscall.rs b/src/syscall.rs index 4929ab7..20e4ced 100644 --- a/src/syscall.rs +++ b/src/syscall.rs @@ -1,13 +1,5 @@ use core::alloc::Layout; -use alloc::boxed::Box; -use bffs::{error::Error, path::Path}; - -use crate::{ - fs::{Disk, FAT32_FILE_SYSTEM}, - virtual_fs::VirtualNode, -}; - pub unsafe fn alloc(layout: Layout) -> *mut u8 { unsafe { alloc::alloc::alloc(layout) } } @@ -15,17 +7,3 @@ pub unsafe fn alloc(layout: Layout) -> *mut u8 { pub unsafe fn dealloc(ptr: *mut u8, layout: core::alloc::Layout) { unsafe { alloc::alloc::dealloc(ptr, layout) } } - -pub fn open>( - path: P, - in_kernel: bool, -) -> Result, Error<::Error>> { - let path = path.as_ref(); - let file = match path.split_path() { - ("dev", path) => { - todo!() - } - _ => FAT32_FILE_SYSTEM.open_file(path)?, - }; - todo!() -} diff --git a/src/time.rs b/src/time.rs index 1846fc7..be0bb77 100644 --- a/src/time.rs +++ b/src/time.rs @@ -7,14 +7,16 @@ use core::{ }; use crate::{ + draw::{Color, Draw, FONT_WIDTH}, set_csr, - vga::{Color, Vga, FONT_WIDTH, WIDTH}, + vga::{Vga, WIDTH}, }; /// Supervisor timer interrupt enable bit for the SIE CSR. pub const IRQ_S_TIMER: u8 = 1 << 5; /// Machine timer interrupt enable bit for the MIE CSR (not used here, but provided for completeness). pub const IRQ_M_TIMER: u8 = 1 << 7; +pub const IRQ_M_EXTERNAL: u64 = 1 << 11; /// Memory-mapped address for the CLINT timer compare register. const CLINT_TIMER_CMP: *mut u64 = 0x0200_4000 as *mut u64; @@ -60,7 +62,7 @@ pub fn timer_interrupt() { let formatted_time = format!("{:02}:{:02}:{:02}", hours, minutes, seconds); unsafe { - Vga::draw_string( + Vga.draw_string( (WIDTH - formatted_time.len() * FONT_WIDTH) as u16, 0, formatted_time, diff --git a/src/tty.rs b/src/tty.rs new file mode 100644 index 0000000..d56fdef --- /dev/null +++ b/src/tty.rs @@ -0,0 +1,73 @@ +use core::cell::RefCell; + +use alloc::{boxed::Box, rc::Rc}; +use io::{IoBase, Read, Seek, Write}; + +use crate::{ + virtual_console::VirtualConsole, + virtual_fs::{VirtualFileSystem, VirtualNode}, +}; + +#[derive(Debug)] +pub struct Tty { + console: Rc>, +} + +impl Tty { + pub fn new() -> Self { + Self { + console: RefCell::new(VirtualConsole::new()).into(), + } + } +} + +#[derive(Debug)] +struct TtyNode { + console: Rc>, +} + +impl VirtualFileSystem for Tty { + fn open( + &mut self, + path: &bffs::path::Path, + ) -> Result, ()> { + if !path.is_empty() { + Err(()) + } else { + Ok(Box::new(TtyNode { + console: self.console.clone(), + })) + } + } +} + +impl IoBase for TtyNode { + type Error = (); +} + +impl Read for TtyNode { + fn read(&mut self, _buf: &mut [u8]) -> Result { + unimplemented!() + } +} + +impl Seek for TtyNode { + fn seek(&mut self, _pos: io::SeekFrom) -> Result { + unimplemented!() + } +} + +impl Write for TtyNode { + fn write(&mut self, buf: &[u8]) -> Result { + self.console + .borrow_mut() + .write_str(str::from_utf8(buf).unwrap()); + Ok(buf.len()) + } + + fn flush(&mut self) -> Result<(), Self::Error> { + todo!() + } +} + +impl VirtualNode for TtyNode {} diff --git a/src/vga.rs b/src/vga.rs index 02790b0..212baa1 100644 --- a/src/vga.rs +++ b/src/vga.rs @@ -2,9 +2,10 @@ //! //! Provides primitives to initialize the Bochs-compatible frame buffer and //! draw text using an embedded font plate. -use kernel_macros::include_font_plate; use log::info; +use crate::draw::{Color, Draw}; + const PCI_ECAM_BASE_ADDRESS: *mut u32 = 0x30000000 as *mut _; const BOCHS_DISPLAY_BASE_ADDRESS: *mut u32 = 0x50000000 as *mut _; const BOCHS_CONFIG_BASE_ADDRESS: *mut u16 = 0x40000000 as *mut _; @@ -12,28 +13,8 @@ pub const VGA_ADDRESS: *mut Color = BOCHS_DISPLAY_BASE_ADDRESS as *mut Color; pub const WIDTH: usize = 1600; pub const HEIGHT: usize = 900; -/// 24-bit RGB color used by the framebuffer. -#[repr(transparent)] -#[derive(Clone, Copy)] -pub struct Color(u32); - -#[allow(unused)] -impl Color { - pub const fn from_rgb(r: u8, g: u8, b: u8) -> Self { - Self((r as u32) << 16 | (g as u32) << 8 | b as u32) - } - - pub const WHITE: Color = Color::from_rgb(255, 255, 255); - pub const GRAY: Color = Color::from_rgb(128, 128, 128); - pub const LIGHT_GRAY: Color = Color::from_rgb(128, 64, 64); - pub const BLACK: Color = Color::from_rgb(0, 0, 0); - pub const RED: Color = Color::from_rgb(255, 0, 0); - pub const GREEN: Color = Color::from_rgb(0, 255, 0); - pub const BLUE: Color = Color::from_rgb(0, 0, 255); -} - /// Framebuffer driver type providing text rendering helpers. -pub struct Vga {} +pub struct Vga; impl Vga { /// Initialize the Bochs framebuffer and configure VGA parameters. @@ -42,7 +23,7 @@ impl Vga { /// programs the Bochs config registers accordingly. pub unsafe fn init() { for i in 0..32 { - let addr = PCI_ECAM_BASE_ADDRESS.wrapping_byte_add(i << 11); + let addr = PCI_ECAM_BASE_ADDRESS.wrapping_byte_add(i << 15); let header = unsafe { core::ptr::read_volatile(addr) }; if header >> 16 == 0x1111 && header & 0xFFFF == 0x1234 { info!("VGA Bochs PCI found"); @@ -71,101 +52,26 @@ impl Vga { core::ptr::write_volatile(BOCHS_CONFIG_BASE_ADDRESS.byte_add(0x508), 0x41) }; - Vga::clear_screen(Color::BLACK); + Vga.clear_screen(Color::BLACK); } - /// # Safety - /// `x` must be less than `WIDTH` and `y` must be less than `HEIGHT` - /// Write a single pixel into the framebuffer (unsafe). - /// - /// Caller must ensure `x < WIDTH` and `y < HEIGHT`. - pub unsafe fn write_pixel_unsafe(x: u16, y: u16, color: Color) { + pub unsafe fn write_u8_unsafe(offset: usize, value: u8) { + unsafe { *(VGA_ADDRESS.byte_add(offset) as *mut u8) = value } + } +} + +impl Draw for Vga { + unsafe fn write_pixel_unsafe(&mut self, x: u16, y: u16, color: Color) { let pixel_index = x as usize + y as usize * WIDTH; unsafe { *VGA_ADDRESS.add(pixel_index) = color } } - /// Draw a single character with a background color at (x,y). - /// - /// Uses the embedded font plate to render glyphs into the framebuffer. - pub unsafe fn draw_char_bg(x: u16, y: u16, c: char, color: Color, bg_color: Color) { - let c = if (c as u8 > b'~') || ((c as u8) < b' ') { - b'/' - b' ' - } else { - c as u8 - b' ' - }; - - // Get char position within font plate - let char_x = (c as usize % 32) * FONT_WIDTH; - let char_y = (c as usize / 32) * FONT_HEIGHT; - - for i in 0..(FONT_WIDTH as u16) { - for j in 0..(FONT_HEIGHT as u16) { - let xx = x + i; - let yy = y + j; - - if xx < (WIDTH as u16) && yy < (HEIGHT as u16) { - if unsafe { Self::font_plate_index(char_x as u16 + i, char_y as u16 + j) } { - unsafe { Self::write_pixel_unsafe(xx, yy, color) } - } else { - unsafe { Self::write_pixel_unsafe(xx, yy, bg_color) } - } - } - } - } + fn get_width(&self) -> usize { + WIDTH } - /// # Safety - /// The text must have a length that can fit within a `u16` - /// Draw a UTF-8 string at the given position. - /// - /// Newlines (`\n`) advance `y` by `FONT_HEIGHT` and carriage return (`\r`) - /// resets to the starting `x` position. - pub unsafe fn draw_string>( - x: u16, - mut y: u16, - str: T, - color: Color, - bg_color: Color, - ) { - let mut current_x = x; - str.as_ref().chars().for_each(|c| unsafe { - match c { - '\n' => { - current_x = x; - y += FONT_HEIGHT as u16; - } - '\r' => { - current_x = x; - } - c => { - Self::draw_char_bg(current_x, y, c, color, bg_color); - current_x += FONT_WIDTH as u16; - } - } - }); - } - - /// Fill the entire framebuffer with a single color. - pub fn clear_screen(color: Color) { - for i in 0..WIDTH * HEIGHT { - unsafe { *VGA_ADDRESS.add(i) = color } - } - } - - /// Return whether a pixel inside the embedded font plate is set. - unsafe fn font_plate_index(x: u16, y: u16) -> bool { - let pixel_index = (y as usize) * FONTPLATE_WIDTH + (x as usize); - let byte_index = pixel_index / 8; - let bit_index = pixel_index % 8; - - (FONTPLATE[byte_index] >> bit_index) & 0b1 == 0b1 + fn get_height(&self) -> usize { + HEIGHT } } - -pub const FONT_WIDTH: usize = 6; -pub const FONT_HEIGHT: usize = 13; -pub const FONTPLATE_WIDTH: usize = 32 * FONT_WIDTH; -pub const FONTPLATE_HEIGHT: usize = 3 * FONT_HEIGHT; -pub const FONTPLATE_SIZE: usize = FONTPLATE_WIDTH * FONTPLATE_HEIGHT / 8; -pub static FONTPLATE: [u8; FONTPLATE_SIZE] = include_font_plate! {"assets/fontplate.png"}; diff --git a/src/virtio.rs b/src/virtio.rs new file mode 100644 index 0000000..d149f36 --- /dev/null +++ b/src/virtio.rs @@ -0,0 +1,67 @@ +pub mod input; + +use core::sync::atomic::AtomicU16; + +// --- Constantes VirtIO --- +const VIRTIO_MMIO_MAGIC_VALUE: usize = 0x000; +const VIRTIO_MMIO_VERSION: usize = 0x004; +const VIRTIO_MMIO_DEVICE_ID: usize = 0x008; +const VIRTIO_MMIO_STATUS: usize = 0x070; +const VIRTIO_MMIO_GUEST_PAGE_SIZE: usize = 0x028; +const VIRTIO_MMIO_QUEUE_SEL: usize = 0x030; +const VIRTIO_MMIO_QUEUE_NUM_MAX: usize = 0x034; +const VIRTIO_MMIO_QUEUE_NUM: usize = 0x038; +const VIRTIO_MMIO_QUEUE_PFN: usize = 0x040; +const VIRTIO_MMIO_INTERRUPT_ACK: usize = 0x064; + +const STATUS_ACKNOWLEDGE: u32 = 1; +const STATUS_DRIVER: u32 = 2; +const STATUS_DRIVER_OK: u32 = 4; +const STATUS_FEATURES_OK: u32 = 8; + +const QUEUE_SIZE: usize = 128; // Puissance de 2 obligatoire + +// --- Structures de la Virtqueue --- +#[repr(C, align(4096))] +pub struct Virtqueue { + pub descriptors: [Descriptor; QUEUE_SIZE], + pub available: AvailableRing, + pub used: UsedRing, +} + +#[repr(C)] +pub struct Descriptor { + pub addr: u64, + pub len: u32, + pub flags: u16, + pub next: u16, +} + +#[repr(C)] +pub struct AvailableRing { + pub flags: u16, + pub idx: AtomicU16, + pub ring: [u16; QUEUE_SIZE], +} + +#[repr(C, align(4))] +pub struct UsedRing { + pub flags: u16, + pub idx: AtomicU16, + pub ring: [UsedElement; QUEUE_SIZE], +} + +#[repr(C)] +pub struct UsedElement { + pub id: u32, + pub len: u32, +} + +// --- Structure de l'événement Clavier --- +#[repr(C)] +#[derive(Copy, Clone, Debug)] +pub struct VirtioInputEvent { + pub event_type: u16, + pub code: u16, + pub value: u32, +} diff --git a/src/virtio/input.rs b/src/virtio/input.rs new file mode 100644 index 0000000..af9ee4e --- /dev/null +++ b/src/virtio/input.rs @@ -0,0 +1,192 @@ +use core::{ + ptr::{read_volatile, write_volatile}, + sync::atomic::Ordering, +}; + +use crate::{ + println, + uart::write_char_uart, + virtio::{ + QUEUE_SIZE, STATUS_ACKNOWLEDGE, STATUS_DRIVER, STATUS_DRIVER_OK, VIRTIO_MMIO_INTERRUPT_ACK, + VIRTIO_MMIO_QUEUE_NUM, VIRTIO_MMIO_QUEUE_NUM_MAX, VIRTIO_MMIO_QUEUE_PFN, + VIRTIO_MMIO_QUEUE_SEL, VIRTIO_MMIO_STATUS, VirtioInputEvent, Virtqueue, + }, +}; +pub struct VirtioInputDriver { + base_addr: usize, + queue: &'static mut Virtqueue, + event_pool: [VirtioInputEvent; QUEUE_SIZE], + last_used_idx: u16, +} + +impl VirtioInputDriver { + pub const unsafe fn new(base_addr: usize, queue_mem: &'static mut Virtqueue) -> Self { + Self { + base_addr, + queue: queue_mem, + event_pool: [VirtioInputEvent { + event_type: 0, + code: 0, + value: 0, + }; QUEUE_SIZE], + last_used_idx: 0, + } + } + + pub unsafe fn init(&mut self) { + unsafe { + // 1. Reset & Status (Ack + Driver) + self.write_reg(0x070, 0); + self.write_reg(0x070, 1 | 2); + + // 2. Négociation Features (Obligatoire en Modern pour débloquer les queues) + self.write_reg(0x024, 1); // Select Page 1 + let f1 = self.read_reg(0x010); + self.write_reg(0x020, f1 | 1); // On accepte VERSION_1 (Bit 32 global) + + self.write_reg(0x070, 1 | 2 | 8); // STATUS_FEATURES_OK + if (self.read_reg(0x070) & 8) == 0 { + panic!("Features rejected"); + } + + // 3. Configuration de la Queue + self.write_reg(0x030, 0); // Select Queue 0 + let max = self.read_reg(0x034); // QUEUE_NUM_MAX + self.write_reg(0x038, QUEUE_SIZE as u32); + + // 4. Envoi des adresses 64 bits (Plus besoin de PFN !) + let desc_addr = &self.queue.descriptors as *const _ as u64; + self.write_reg(0x080, (desc_addr & 0xffffffff) as u32); + self.write_reg(0x084, (desc_addr >> 32) as u32); + + let avail_addr = &self.queue.available as *const _ as u64; + self.write_reg(0x090, (avail_addr & 0xffffffff) as u32); + self.write_reg(0x094, (avail_addr >> 32) as u32); + + let used_addr = &self.queue.used as *const _ as u64; + self.write_reg(0x0a0, (used_addr & 0xffffffff) as u32); + self.write_reg(0x0a4, (used_addr >> 32) as u32); + + // 5. REMPLISSAGE INITIAL + for i in 0..QUEUE_SIZE { + self.queue.descriptors[i].addr = &self.event_pool[i] as *const _ as u64; + self.queue.descriptors[i].len = core::mem::size_of::() as u32; + self.queue.descriptors[i].flags = 2; // Writeable + self.queue.available.ring[i] = i as u16; + } + self.queue + .available + .idx + .store(QUEUE_SIZE as u16, Ordering::Release); + + // 6. ACTIVATION (L'étape que tout le monde oublie en Modern) + self.write_reg(0x044, 1); // QUEUE_READY + + // 7. DRIVER_OK + self.write_reg(0x070, 1 | 2 | 4 | 8); + + self.activate_queue(); + } + } + unsafe fn activate_queue(&mut self) { + unsafe { + // 1. Sélectionner la queue + self.write_reg(0x030, 0); + + // 2. Écrire la taille (doit correspondre à tes structures Rust) + self.write_reg(0x038, QUEUE_SIZE as u32); + + // 3. Écrire les adresses (ORDRE CRITIQUE : LOW puis HIGH) + let desc_addr = &self.queue.descriptors as *const _ as u64; + self.write_reg(0x080, desc_addr as u32); // DescLow + self.write_reg(0x084, (desc_addr >> 32) as u32); // DescHigh + + let avail_addr = &self.queue.available as *const _ as u64; + self.write_reg(0x090, avail_addr as u32); // AvailLow + self.write_reg(0x094, (avail_addr >> 32) as u32); // AvailHigh + + let used_addr = &self.queue.used as *const _ as u64; + self.write_reg(0x0a0, used_addr as u32); // UsedLow + self.write_reg(0x0a4, (used_addr >> 32) as u32); // UsedHigh + + // 4. LE KICK : Activer la queue + self.write_reg(0x044, 1); // QUEUE_READY = 1 + + // 5. SYNC : On s'assure que le périphérique a bien pris le READY + if self.read_reg(0x044) == 0 { + panic!("La queue refuse de passer en READY. Vérifiez les adresses !"); + } + } + } + + /// Appelé lors d'une interruption clavier + pub fn handle_interrupt(&mut self) { + let used_idx = self.queue.used.idx.load(Ordering::Acquire); + + while self.last_used_idx != used_idx { + let ring_slot = self.last_used_idx as usize % QUEUE_SIZE; + let used_elem = &self.queue.used.ring[ring_slot]; + + let event = &self.event_pool[used_elem.id as usize]; + + if event.event_type == 1 { + // EV_KEY + self.on_key(event.code, event.value); + } + + // Recyclage du descripteur : on le rend disponible à nouveau + let avail_idx = self.queue.available.idx.load(Ordering::Relaxed) as usize % QUEUE_SIZE; + self.queue.available.ring[avail_idx] = used_elem.id as u16; + self.queue.available.idx.fetch_add(1, Ordering::Release); + + self.last_used_idx = self.last_used_idx.wrapping_add(1); + } + + // Acquitter l'interruption + unsafe { + self.write_reg(VIRTIO_MMIO_INTERRUPT_ACK, 1); + } + } + + fn on_key(&self, code: u16, value: u32) { + let state = match value { + 1 => "Pressed", + 0 => "Released", + 2 => "Repeat", + _ => "Unknown", + }; + // write_char_uart((b'0' + (code / 10) as u8 % 10) as char); + // write_char_uart((b'0' + (code % 10) as u8) as char); + write_char_uart(code as u8 as char); + write_char_uart('\n'); + write_char_uart('\r'); + // Ici, implémentez votre conversion Scancode -> ASCII + // println!("Key Code: {} - State: {}", code, state); + } + + unsafe fn write_reg(&self, offset: usize, val: u32) { + unsafe { write_volatile((self.base_addr + offset) as *mut u32, val) }; + } + + unsafe fn read_reg(&self, offset: usize) -> u32 { + unsafe { read_volatile((self.base_addr + offset) as *const u32) } + } +} + +pub const PLIC_BASE: usize = 0x0c00_0000; +pub const IRQ_VIRTIO: u32 = 1; + +pub const S_MODE_CLAIM_COMPLETE: *mut u32 = 0x0c20_1004 as *mut u32; +pub unsafe fn init_plic_m_mode() { + // 1. Priority : identique pour tous les modes + let priority_ptr = (PLIC_BASE + 4 * IRQ_VIRTIO as usize) as *mut u32; + unsafe { priority_ptr.write_volatile(1) }; + + // 2. Enable : Pour Hart 0 M-Mode, l'offset est 0x2000 + let enable_ptr = (PLIC_BASE + 0x2080) as *mut u32; + unsafe { enable_ptr.write_volatile(1 << IRQ_VIRTIO) }; + + // 3. Threshold : Pour Hart 0 M-Mode, l'offset est 0x200000 + let threshold_ptr = (PLIC_BASE + 0x201000) as *mut u32; + unsafe { threshold_ptr.write_volatile(0) }; +} diff --git a/src/virtual_console.rs b/src/virtual_console.rs new file mode 100644 index 0000000..b41162b --- /dev/null +++ b/src/virtual_console.rs @@ -0,0 +1,120 @@ +use alloc::boxed::Box; +use io::SeekFrom; + +use crate::{ + draw::{Color, Draw, FONT_HEIGHT, FONT_WIDTH}, + vga::{self, Vga}, + virtual_fs::{self, FILE_SYSTEM, VirtualFileSystem}, +}; + +const TAB_SIZE: u64 = 4; + +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +struct Cursor { + x: u64, + y: u64, +} + +impl Cursor { + pub fn new() -> Self { + Self { x: 0, y: 0 } + } +} + +#[derive(Debug)] +pub struct VirtualConsole { + cursor: Cursor, + framebuffer: Box, +} + +impl VirtualConsole { + pub fn new() -> Self { + VirtualConsole { + cursor: Cursor::new(), + framebuffer: unsafe { FILE_SYSTEM.open("/dev/fb0".as_ref()).unwrap() }, + } + } + + pub fn write_str(&mut self, s: &str) { + s.chars().for_each(|c| self.write_char(c)); + } + + pub fn write_char(&mut self, c: char) { + let mut last_cursor = self.cursor; + match c { + '\n' => { + self.cursor.x = 0; + self.cursor.y += 1; + } + '\r' => { + self.cursor.x = 0; + } + '\x08' if self.cursor.x > 0 => { + // Backspace + self.cursor.x -= 1; + for y in 0..FONT_HEIGHT as u16 { + for x in 0..FONT_WIDTH as u16 { + unsafe { + self.write_pixel_unsafe( + (self.cursor.x * FONT_WIDTH as u64) as u16 + x, + (self.cursor.y * FONT_HEIGHT as u64) as u16 + y, + Color::BLACK, + ) + } + } + } + } + '\t' => { + self.cursor.x = (self.cursor.x / TAB_SIZE + 1) * TAB_SIZE; + } + _ if c <= 127 as char && c >= 32 as char => { + unsafe { + self.draw_char_bg( + (self.cursor.x * FONT_WIDTH as u64) as u16, + (self.cursor.y * FONT_HEIGHT as u64) as u16, + c, + Color::WHITE, + Color::BLACK, + ) + }; + self.cursor.x += 1; + } + _ => {} + } + if self.cursor.x as usize * FONT_WIDTH >= vga::WIDTH { + self.cursor.x = 0; + self.cursor.y += 1; + } + if self.cursor.y as usize * FONT_HEIGHT >= vga::HEIGHT { + self.line_up(); + if last_cursor.y > 0 { + last_cursor.y -= 1; + } + } + if last_cursor != self.cursor { + self.move_cursor_line(last_cursor); + } + } + + fn line_up(&mut self) {} + fn move_cursor_line(&mut self, _last: Cursor) {} +} + +impl Draw for VirtualConsole { + unsafe fn write_pixel_unsafe(&mut self, x: u16, y: u16, color: crate::draw::Color) { + self.framebuffer + .seek(SeekFrom::Start( + (y as u64 * self.get_width() as u64 + x as u64) * size_of::() as u64, + )) + .unwrap(); + self.framebuffer.write(&color.as_bytes()).unwrap(); + } + + fn get_width(&self) -> usize { + Vga.get_width() + } + + fn get_height(&self) -> usize { + Vga.get_height() + } +} diff --git a/src/virtual_fs.rs b/src/virtual_fs.rs index 37ccaed..78f7743 100644 --- a/src/virtual_fs.rs +++ b/src/virtual_fs.rs @@ -1,24 +1,116 @@ +use core::{cell::LazyCell, fmt::Debug}; + use alloc::boxed::Box; -use bffs::path::{Path, PathBuf}; +use bffs::{ + Fat32FileSystem, + path::{Path, PathBuf}, +}; use hashbrown::HashMap; +use io::{IoBase, Read, Seek, Write}; -pub trait VirtualNode { - fn read(&mut self, buf: &mut [u8]) -> Result; -} - -pub trait VirtualFileSystem { - fn open(&mut self, path: &Path) -> Result, ()>; +use crate::{fs::Disk, tty::Tty, vga::Vga}; + +pub trait VirtualNode: IoBase + Read + Write + Seek + Debug {} + +pub trait VirtualFileSystem: Debug { + fn open(&mut self, path: &Path) -> Result, ()>; } +#[derive(Debug)] pub struct MainFileSystem { + root: Box, mounts: HashMap>, } +impl MainFileSystem { + pub fn mount(&mut self, path: PathBuf, fs: Box) { + self.mounts.insert(path, fs); + } +} + impl VirtualFileSystem for MainFileSystem { - fn open(&mut self, path: &Path) -> Result, ()> { - for mount in self.mounts.iter() { - + fn open(&mut self, path: &Path) -> Result, ()> { + let mut max = &mut self.root; + let mut max_path = Path::new("/"); + let mut path_remaining = path; + for (mount, fs) in self.mounts.iter_mut() { + if path.starts_with(mount) && mount.starts_with(max_path) { + max = fs; + max_path = mount; + path_remaining = path.without(mount); + } } + max.open(path_remaining) + } +} + +pub static mut FILE_SYSTEM: LazyCell = LazyCell::new(|| MainFileSystem { + root: Box::new(Fat32FileSystem::new(Disk::new(1024 * 1024 * 16)).unwrap()), + mounts: HashMap::new(), +}); + +pub unsafe fn init_file_system() { + unsafe { + FILE_SYSTEM.mount("/dev/fb0".into(), Box::new(VGAFileSystem)); + FILE_SYSTEM.mount("/dev/tty0".into(), Box::new(Tty::new())); + } +} + +#[derive(Debug)] +struct VGAFileSystem; + +#[derive(Debug)] +struct VGAVirtualNode { + position: u64, +} + +impl VirtualNode for VGAVirtualNode {} + +impl IoBase for VGAVirtualNode { + type Error = (); +} + +impl Seek for VGAVirtualNode { + fn seek(&mut self, pos: io::SeekFrom) -> Result { + self.position = match pos { + io::SeekFrom::Start(v) => v, + io::SeekFrom::End(v) => { + ((crate::vga::WIDTH * crate::vga::HEIGHT * size_of::()) as i64 + + v) as u64 + } + io::SeekFrom::Current(v) => (self.position as i64 + v) as u64, + }; + Ok(self.position) + } +} + +impl Read for VGAVirtualNode { + fn read(&mut self, _buf: &mut [u8]) -> Result { todo!() } } + +impl Write for VGAVirtualNode { + fn write(&mut self, buf: &[u8]) -> Result { + let start = self.position; + buf.iter().for_each(|val| { + unsafe { Vga::write_u8_unsafe(self.position as usize, *val) }; + self.position += 1; + }); + Ok((self.position - start) as usize) + } + + fn flush(&mut self) -> Result<(), Self::Error> { + todo!() + } +} + +impl VirtualFileSystem for VGAFileSystem { + fn open(&mut self, path: &Path) -> Result, ()> { + if !path.is_empty() { + Err(()) + } else { + Ok(Box::new(VGAVirtualNode { position: 0 })) + } + } +} diff --git a/user/test_pic/src/main.rs b/user/test_pic/src/main.rs index 7ee438e..4e6f3b0 100644 --- a/user/test_pic/src/main.rs +++ b/user/test_pic/src/main.rs @@ -1,5 +1,7 @@ #![no_std] #![no_main] + +use os_std::syscall; os_std::custom_std_setup! {} fn main() { @@ -9,5 +11,13 @@ fn main() { for _ in 0..100 { test.push('C'); } - println!("Hello from PIC program loaded dynamically with custom std and a better justfile, and syscalls ! {}", test); + // let mut file = syscall::open("/dev/fb0"); + // syscall::seek(&mut file, SeekFrom::End(-3)); + // syscall::write(&mut file, &[255; 6400 * 50]); + let mut file = syscall::open("/dev/tty0"); + syscall::write(&mut file, b"Hi !\nnice tty\x08"); + println!( + "Hello from PIC program loaded dynamically with custom std and a better justfile, and syscalls ! {}", + test + ); }