Better virtual file system, keyboard through MMIO&VirtIO

This commit is contained in:
2026-03-05 14:41:28 +01:00
parent 041e544330
commit 9b6aec28f5
37 changed files with 1191 additions and 355 deletions

View File

@@ -1,6 +1,6 @@
[workspace] [workspace]
resolver = "3" resolver = "3"
members = ["crates/os-std", "crates/shared", "user/*"] members = ["crates/io","crates/os-std", "crates/shared", "user/*"]
[package] [package]
name = "kernel-rust" name = "kernel-rust"
@@ -13,6 +13,7 @@ kernel-macros = { path = "crates/kernel-macros" }
log = "0.4" log = "0.4"
critical-section = { version = "1", features = ["restore-state-bool"] } critical-section = { version = "1", features = ["restore-state-bool"] }
bffs = { path = "crates/bffs", features = ["alloc"] } bffs = { path = "crates/bffs", features = ["alloc"] }
io = { path = "crates/io", features = ["alloc"] }
shared = { path = "crates/shared", features = ["kernel"] } shared = { path = "crates/shared", features = ["kernel"] }
# ELF parsing helper # ELF parsing helper
goblin = { version = "0.7", default-features = false, features = ["elf32", "elf64", "endian_fd"] } goblin = { version = "0.7", default-features = false, features = ["elf32", "elf64", "endian_fd"] }

View File

@@ -4,6 +4,7 @@ version = "0.1.0"
edition = "2024" edition = "2024"
[dependencies] [dependencies]
io = { path = "../io" }
bitflags = "2" bitflags = "2"
[features] [features]

View File

@@ -1,4 +1,5 @@
use crate::{error::Error, io::{Read, ReadLeExt}}; use crate::error::Error;
use io::{Read, ReadLeExt};
#[derive(Debug, Clone, Copy)] #[derive(Debug, Clone, Copy)]
pub struct Fat32BootSector { pub struct Fat32BootSector {

View File

@@ -1,11 +1,11 @@
use crate::{ use crate::{
Fat32FileSystem, ReadSeek, ReadWriteSeek,
entry::{DirEntry, DirectoryIterator}, entry::{DirEntry, DirectoryIterator},
error::Error, error::Error,
file::{File, RawFile}, file::{File, RawFile},
io::{self, IoBase, Read, Seek, Write},
path::Path, path::Path,
Fat32FileSystem, ReadSeek, ReadWriteSeek,
}; };
use io::{self, IoBase, Read, Seek, Write};
pub struct Dir<'a, T> { pub struct Dir<'a, T> {
raw: RawFile<'a, T>, raw: RawFile<'a, T>,
@@ -13,24 +13,24 @@ pub struct Dir<'a, T> {
} }
impl<'a, T: IoBase> IoBase for Dir<'a, T> { impl<'a, T: IoBase> IoBase for Dir<'a, T> {
type Error = T::Error; type Error = Error<T::Error>;
} }
impl<'a, T: ReadSeek> Seek for Dir<'a, T> { impl<'a, T: ReadSeek> Seek for Dir<'a, T> {
fn seek(&mut self, pos: io::SeekFrom) -> Result<u64, Error<Self::Error>> { fn seek(&mut self, pos: io::SeekFrom) -> Result<u64, Self::Error> {
self.raw.seek(pos) self.raw.seek(pos)
} }
} }
impl<'a, T: ReadSeek> Read for Dir<'a, T> { impl<'a, T: ReadSeek> Read for Dir<'a, T> {
fn read(&mut self, buf: &mut [u8]) -> Result<usize, Error<Self::Error>> { fn read(&mut self, buf: &mut [u8]) -> Result<usize, Self::Error> {
self.raw.read(buf) self.raw.read(buf)
} }
} }
impl<'a, T: ReadWriteSeek> Write for Dir<'a, T> { impl<'a, T: ReadWriteSeek> Write for Dir<'a, T> {
fn write(&mut self, buf: &[u8]) -> Result<usize, Error<Self::Error>> { fn write(&mut self, buf: &[u8]) -> Result<usize, Self::Error> {
self.raw.write(buf) self.raw.write(buf)
} }
fn flush(&mut self) -> Result<(), Error<Self::Error>> { fn flush(&mut self) -> Result<(), Self::Error> {
self.raw.flush() self.raw.flush()
} }
} }
@@ -44,25 +44,28 @@ impl<'a, T> Dir<'a, T> {
} }
} }
impl<'a, T: ReadSeek> Dir<'a, T> { impl<'a, T: ReadSeek> Dir<'a, T> {
pub fn open_entry<P: AsRef<Path>>( pub fn open_entry<P: AsRef<Path>>(&self, path: P) -> Result<DirEntry<'a, T>, Error<T::Error>> {
&self,
path: P,
) -> Result<DirEntry<'a, T>, Error<T::Error>> {
if path.as_ref().is_absolute() { if path.as_ref().is_absolute() {
return self.fs.open_entry(path); return self.fs.open_entry(path);
} }
let (start, entryname) = path.as_ref().split_path();
for file in self.iter() { for file in self.iter() {
let f = file?; let f = file?;
if f.name_is(path.as_ref().as_str()) { if f.name_is(start) {
return Ok(f); 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) Err(Error::NotFound)
} }
pub fn open_file<P: AsRef<Path>>( pub fn open_file<P: AsRef<Path>>(&self, path: P) -> Result<File<'a, T>, Error<T::Error>> {
&self,
path: P,
) -> Result<File<'a, T>, Error<T::Error>> {
if path.as_ref().is_absolute() { if path.as_ref().is_absolute() {
return self.fs.open_file(path); return self.fs.open_file(path);
} }

View File

@@ -2,14 +2,15 @@ use core::{array::IntoIter, iter::Copied, str::Utf8Error};
use core::{char::DecodeUtf16, marker::PhantomData, slice::Iter}; use core::{char::DecodeUtf16, marker::PhantomData, slice::Iter};
use crate::{ use crate::{
Fat32FileSystem, ReadSeek,
consts::FATAttr, consts::FATAttr,
dir::Dir, dir::Dir,
error::Error, error::Error,
file::{File, RawFile}, file::{File, RawFile},
io::{IoBase, Read, ReadLeExt},
Fat32FileSystem, ReadSeek,
}; };
use io::{IoBase, Read, ReadLeExt};
#[cfg(feature = "alloc")] #[cfg(feature = "alloc")]
use alloc::borrow::ToOwned; use alloc::borrow::ToOwned;
#[cfg(feature = "alloc")] #[cfg(feature = "alloc")]
@@ -32,7 +33,7 @@ pub struct FatDirEntry {
} }
impl FatDirEntry { impl FatDirEntry {
pub fn deserialize<T: Read>(reader: &mut T) -> Result<Self, Error<T::Error>> { pub fn deserialize<T: Read>(reader: &mut T) -> Result<Self, T::Error> {
let mut name = [0u8; _]; let mut name = [0u8; _];
reader.read_exact(&mut name)?; reader.read_exact(&mut name)?;
let attr = reader.read_u8()?; 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<DirEntry<'a, T>, Error<T::Error>>; type Item = Result<DirEntry<'a, T>, Error<T::Error>>;
fn next(&mut self) -> Option<Self::Item> { fn next(&mut self) -> Option<Self::Item> {

View File

@@ -1,3 +1,5 @@
use io::error::IoError;
/// Error enum with all errors that can be returned by functions from this crate /// 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 /// Generic parameter `T` is a type of external error returned by the user provided storage
@@ -55,15 +57,6 @@ impl<T: core::fmt::Display> core::fmt::Display for Error<T> {
} }
} }
/// 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<T: core::fmt::Debug + IoError> IoError for Error<T> { impl<T: core::fmt::Debug + IoError> IoError for Error<T> {
fn is_interrupted(&self) -> bool { fn is_interrupted(&self) -> bool {
match self { match self {
@@ -79,39 +72,8 @@ impl<T: core::fmt::Debug + IoError> IoError for Error<T> {
fn new_write_zero_error() -> Self { fn new_write_zero_error() -> Self {
Error::<T>::WriteZero Error::<T>::WriteZero
} }
}
impl IoError for () { fn new_invalid_utf8_error() -> Self {
fn is_interrupted(&self) -> bool { Error::<T>::InvalidUTF8
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",
)
} }
} }

View File

@@ -1,10 +1,11 @@
use crate::{ use crate::{
Fat32FileSystem, ReadSeek, ReadWriteSeek,
consts::{FAT32_BAD_CLUSTER, FAT32_END_OF_CHAIN}, consts::{FAT32_BAD_CLUSTER, FAT32_END_OF_CHAIN},
error::Error, error::Error,
io::{self, IoBase, Read, Seek, Write},
Fat32FileSystem, ReadSeek, ReadWriteSeek,
}; };
use io::{self, IoBase, Read, Seek, Write};
#[derive(Debug, Clone)] #[derive(Debug, Clone)]
pub struct RawFile<'a, T> { pub struct RawFile<'a, T> {
pub(crate) fs: &'a Fat32FileSystem<T>, pub(crate) fs: &'a Fat32FileSystem<T>,
@@ -30,11 +31,11 @@ impl<'a, T> RawFile<'a, T> {
} }
impl<'a, T: IoBase> IoBase for RawFile<'a, T> { impl<'a, T: IoBase> IoBase for RawFile<'a, T> {
type Error = T::Error; type Error = Error<T::Error>;
} }
impl<'a, T: ReadSeek> Seek for RawFile<'a, T> { impl<'a, T: ReadSeek> Seek for RawFile<'a, T> {
fn seek(&mut self, pos: io::SeekFrom) -> Result<u64, Error<Self::Error>> { fn seek(&mut self, pos: io::SeekFrom) -> Result<u64, Self::Error> {
let new_pos = match pos { let new_pos = match pos {
io::SeekFrom::Start(s) => s, io::SeekFrom::Start(s) => s,
io::SeekFrom::Current(c) => (self.pos as i64 + c) as u64, 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> { impl<'a, T: ReadSeek> Read for RawFile<'a, T> {
fn read(&mut self, buf: &mut [u8]) -> Result<usize, Error<Self::Error>> { fn read(&mut self, buf: &mut [u8]) -> Result<usize, Self::Error> {
let max_len = self let max_len = self
.size .size
.map(|size| core::cmp::min(buf.len(), size as usize - self.pos as usize)) .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> { impl<'a, T: ReadWriteSeek> Write for RawFile<'a, T> {
fn write(&mut self, _buf: &[u8]) -> Result<usize, Error<Self::Error>> { fn write(&mut self, _buf: &[u8]) -> Result<usize, Self::Error> {
todo!() todo!()
} }
fn flush(&mut self) -> Result<(), Error<Self::Error>> { fn flush(&mut self) -> Result<(), Self::Error> {
todo!() todo!()
} }
} }
#[allow(unused)] #[allow(unused)]
#[derive(Debug)]
pub struct File<'a, T> { pub struct File<'a, T> {
raw: RawFile<'a, T>, raw: RawFile<'a, T>,
fs: &'a Fat32FileSystem<T>, fs: &'a Fat32FileSystem<T>,
} }
impl<'a, T: IoBase> IoBase for File<'a, T> { impl<'a, T: IoBase> IoBase for File<'a, T> {
type Error = T::Error; type Error = Error<T::Error>;
} }
impl<'a, T: ReadSeek> Seek for File<'a, T> { impl<'a, T: ReadSeek> Seek for File<'a, T> {
fn seek(&mut self, pos: io::SeekFrom) -> Result<u64, Error<Self::Error>> { fn seek(&mut self, pos: io::SeekFrom) -> Result<u64, Self::Error> {
self.raw.seek(pos) self.raw.seek(pos)
} }
} }
impl<'a, T: ReadSeek> Read for File<'a, T> { impl<'a, T: ReadSeek> Read for File<'a, T> {
fn read(&mut self, buf: &mut [u8]) -> Result<usize, Error<Self::Error>> { fn read(&mut self, buf: &mut [u8]) -> Result<usize, Self::Error> {
self.raw.read(buf) self.raw.read(buf)
} }
} }
impl<'a, T: ReadWriteSeek> Write for File<'a, T> { impl<'a, T: ReadWriteSeek> Write for File<'a, T> {
fn write(&mut self, buf: &[u8]) -> Result<usize, Error<Self::Error>> { fn write(&mut self, buf: &[u8]) -> Result<usize, Self::Error> {
self.raw.write(buf) self.raw.write(buf)
} }
fn flush(&mut self) -> Result<(), Error<Self::Error>> { fn flush(&mut self) -> Result<(), Self::Error> {
self.raw.flush() self.raw.flush()
} }
} }

View File

@@ -11,10 +11,11 @@ use crate::{
entry::{DirEntry, FatEntry}, entry::{DirEntry, FatEntry},
error::Error, error::Error,
file::{File, RawFile}, file::{File, RawFile},
io::{Read, ReadLeExt, Seek, Write},
path::Path, path::Path,
}; };
use io::{Read, ReadLeExt, Seek, Write};
#[cfg(feature = "alloc")] #[cfg(feature = "alloc")]
extern crate alloc; extern crate alloc;
@@ -24,7 +25,6 @@ pub mod dir;
pub mod entry; pub mod entry;
pub mod error; pub mod error;
pub mod file; pub mod file;
pub mod io;
pub mod path; pub mod path;
pub trait ReadSeek: Read + Seek {} pub trait ReadSeek: Read + Seek {}
@@ -136,10 +136,7 @@ impl<T> Fat32FileSystem<T> {
} }
} }
impl<T: ReadSeek> Fat32FileSystem<T> { impl<T: ReadSeek> Fat32FileSystem<T> {
pub fn open_entry<P: AsRef<Path>>( pub fn open_entry<P: AsRef<Path>>(&self, path: P) -> Result<DirEntry<'_, T>, Error<T::Error>> {
&self,
path: P,
) -> Result<DirEntry<'_, T>, Error<T::Error>> {
let path = path.as_ref().as_str().trim_start_matches("/"); let path = path.as_ref().as_str().trim_start_matches("/");
self.root_directory().open_entry(path) self.root_directory().open_entry(path)
} }
@@ -147,10 +144,7 @@ impl<T: ReadSeek> Fat32FileSystem<T> {
let path = path.as_ref().as_str().trim_start_matches("/"); let path = path.as_ref().as_str().trim_start_matches("/");
self.root_directory().open_dir(path) self.root_directory().open_dir(path)
} }
pub fn open_file<P: AsRef<Path>>( pub fn open_file<P: AsRef<Path>>(&self, path: P) -> Result<File<'_, T>, Error<T::Error>> {
&self,
path: P,
) -> Result<File<'_, T>, Error<T::Error>> {
let path = path.as_ref().as_str().trim_start_matches("/"); let path = path.as_ref().as_str().trim_start_matches("/");
self.root_directory().open_file(path) self.root_directory().open_file(path)
} }

View File

@@ -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")] #[cfg(feature = "alloc")]
use alloc::string::String; use alloc::string::String;
#[repr(transparent)] #[repr(transparent)]
#[derive(Debug)]
pub struct Path { pub struct Path {
inner: str, inner: str,
} }
@@ -19,6 +23,13 @@ impl AsRef<Path> for str {
} }
} }
#[cfg(feature = "alloc")]
impl AsRef<Path> for String {
fn as_ref(&self) -> &Path {
self.as_str().as_ref()
}
}
impl AsRef<Path> for Path { impl AsRef<Path> for Path {
fn as_ref(&self) -> &Path { fn as_ref(&self) -> &Path {
self self
@@ -32,9 +43,17 @@ impl AsRef<str> for Path {
} }
impl Path { impl Path {
pub fn new(path: &str) -> &Self {
path.as_ref()
}
pub fn split_path(&self) -> (&str, Option<&Path>) { pub fn split_path(&self) -> (&str, Option<&Path>) {
if let Some((start, end)) = self.inner.split_once("/") { 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 { } else {
(&self.inner, None) (&self.inner, None)
} }
@@ -48,9 +67,46 @@ impl Path {
pub fn is_relative(&self) -> bool { pub fn is_relative(&self) -> bool {
!self.is_absolute() !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<P: AsRef<Self>>(&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<P: AsRef<Self>>(&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")] #[cfg(feature = "alloc")]
#[derive(Debug, Hash, PartialEq, Eq)]
pub struct PathBuf { pub struct PathBuf {
inner: String, inner: String,
} }
@@ -63,3 +119,26 @@ impl Deref for PathBuf {
self.inner.as_str().into() self.inner.as_str().into()
} }
} }
#[cfg(feature = "alloc")]
impl AsRef<Path> for PathBuf {
fn as_ref(&self) -> &Path {
self.inner.as_ref()
}
}
#[cfg(feature = "alloc")]
impl Borrow<str> 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(),
}
}
}

10
crates/io/Cargo.toml Normal file
View File

@@ -0,0 +1,10 @@
[package]
name = "io"
version = "0.1.0"
edition = "2024"
[dependencies]
[features]
alloc = []
std = ["alloc"]

48
crates/io/src/error.rs Normal file
View File

@@ -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",
)
}
}

View File

@@ -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")] #[cfg(feature = "alloc")]
use alloc::string::String; use alloc::string::String;
@@ -41,7 +47,7 @@ pub trait Read: IoBase {
/// then it must be guaranteed that no bytes were read. /// 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 /// 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. /// if there is nothing else to do.
fn read(&mut self, buf: &mut [u8]) -> Result<usize, Error<Self::Error>>; fn read(&mut self, buf: &mut [u8]) -> Result<usize, Self::Error>;
/// Read the exact number of bytes required to fill `buf`. /// 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 /// 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. /// than would be necessary to completely fill the buffer.
fn read_exact(&mut self, mut buf: &mut [u8]) -> Result<(), Error<Self::Error>> { fn read_exact(&mut self, mut buf: &mut [u8]) -> Result<(), Self::Error> {
while !buf.is_empty() { while !buf.is_empty() {
match self.read(buf) { match self.read(buf) {
Ok(0) => break, Ok(0) => break,
@@ -73,12 +79,12 @@ pub trait Read: IoBase {
if buf.is_empty() { if buf.is_empty() {
Ok(()) Ok(())
} else { } else {
Err(Error::<Self::Error>::new_unexpected_eof_error()) Err(Self::Error::new_unexpected_eof_error())
} }
} }
#[cfg(feature = "alloc")] #[cfg(feature = "alloc")]
fn read_to_end(&mut self, buf: &mut Vec<u8>) -> Result<usize, Error<Self::Error>> { fn read_to_end(&mut self, buf: &mut Vec<u8>) -> Result<usize, Self::Error> {
const CHUNK_SIZE: usize = 32; const CHUNK_SIZE: usize = 32;
let start_len = buf.len(); let start_len = buf.len();
loop { loop {
@@ -93,11 +99,11 @@ pub trait Read: IoBase {
} }
#[cfg(feature = "alloc")] #[cfg(feature = "alloc")]
fn read_to_string(&mut self, buf: &mut String) -> Result<usize, Error<Self::Error>> { fn read_to_string(&mut self, buf: &mut String) -> Result<usize, Self::Error> {
let read = self.read_to_end(unsafe { buf.as_mut_vec() })?; let read = self.read_to_end(unsafe { buf.as_mut_vec() })?;
if str::from_utf8(buf.as_bytes()).is_err() { if str::from_utf8(buf.as_bytes()).is_err() {
Err(Error::InvalidUTF8) Err(Self::Error::new_invalid_utf8_error())
} else { } else {
Ok(read) 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 /// 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. /// 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. /// It is not considered an error if the entire buffer could not be written to this writer.
fn write(&mut self, buf: &[u8]) -> Result<usize, Error<Self::Error>>; fn write(&mut self, buf: &[u8]) -> Result<usize, Self::Error>;
/// Attempts to write an entire buffer into this writer. /// 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` /// This function will return the first error for which `IoError::is_interrupted` method returns false that `write`
/// returns. /// returns.
fn write_all(&mut self, mut buf: &[u8]) -> Result<(), Error<Self::Error>> { fn write_all(&mut self, mut buf: &[u8]) -> Result<(), Self::Error> {
while !buf.is_empty() { while !buf.is_empty() {
match self.write(buf) { match self.write(buf) {
Ok(0) => { Ok(0) => {
return Err(Error::<Self::Error>::new_write_zero_error()); return Err(Self::Error::new_write_zero_error());
} }
Ok(n) => buf = &buf[n..], Ok(n) => buf = &buf[n..],
Err(ref e) if e.is_interrupted() => {} Err(ref e) if e.is_interrupted() => {}
@@ -148,7 +154,7 @@ pub trait Write: IoBase {
/// # Errors /// # Errors
/// ///
/// It is considered an error if not all bytes could be written due to I/O errors or EOF being reached. /// 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<Self::Error>>; fn flush(&mut self) -> Result<(), Self::Error>;
} }
/// Enumeration of possible methods to seek within an I/O object. /// Enumeration of possible methods to seek within an I/O object.
@@ -177,7 +183,7 @@ pub trait Seek: IoBase {
/// ///
/// # Errors /// # Errors
/// Seeking to a negative offset is considered an error. /// Seeking to a negative offset is considered an error.
fn seek(&mut self, pos: SeekFrom) -> Result<u64, Error<Self::Error>>; fn seek(&mut self, pos: SeekFrom) -> Result<u64, Self::Error>;
} }
#[cfg(all(feature = "std", not(target_arch = "riscv64")))] #[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")))] #[cfg(all(feature = "std", not(target_arch = "riscv64")))]
impl<T: std::io::Read + IoBase<Error = std::io::Error>> Read for T { impl<T: std::io::Read + IoBase<Error = std::io::Error>> Read for T {
fn read(&mut self, buf: &mut [u8]) -> Result<usize, Error<Self::Error>> { fn read(&mut self, buf: &mut [u8]) -> Result<usize, Self::Error> {
self.read(buf).map_err(Error::Io) self.read(buf).map_err(Error::Io)
} }
fn read_exact(&mut self, buf: &mut [u8]) -> Result<(), Error<Self::Error>> { fn read_exact(&mut self, buf: &mut [u8]) -> Result<(), Self::Error> {
self.read_exact(buf).map_err(Error::Io) self.read_exact(buf).map_err(Error::Io)
} }
} }
#[cfg(all(feature = "std", not(target_arch = "riscv64")))] #[cfg(all(feature = "std", not(target_arch = "riscv64")))]
impl<T: std::io::Write + IoBase<Error = std::io::Error>> Write for T { impl<T: std::io::Write + IoBase<Error = std::io::Error>> Write for T {
fn write(&mut self, buf: &[u8]) -> Result<usize, Error<Self::Error>> { fn write(&mut self, buf: &[u8]) -> Result<usize, Self::Error> {
self.write(buf).map_err(Error::Io) self.write(buf).map_err(Error::Io)
} }
fn write_all(&mut self, buf: &[u8]) -> Result<(), Error<Self::Error>> { fn write_all(&mut self, buf: &[u8]) -> Result<(), Self::Error> {
self.write_all(buf).map_err(Error::Io) self.write_all(buf).map_err(Error::Io)
} }
fn flush(&mut self) -> Result<(), Error<Self::Error>> { fn flush(&mut self) -> Result<(), Self::Error> {
self.flush().map_err(Error::Io) self.flush().map_err(Error::Io)
} }
} }
#[cfg(all(feature = "std", not(target_arch = "riscv64")))] #[cfg(all(feature = "std", not(target_arch = "riscv64")))]
impl<T: std::io::Seek + IoBase<Error = std::io::Error>> Seek for T { impl<T: std::io::Seek + IoBase<Error = std::io::Error>> Seek for T {
fn seek(&mut self, pos: SeekFrom) -> Result<u64, Error<Self::Error>> { fn seek(&mut self, pos: SeekFrom) -> Result<u64, Self::Error> {
self.seek(pos.into()).map_err(Error::Io) self.seek(pos.into()).map_err(Error::Io)
} }
} }
pub(crate) trait ReadLeExt { pub trait ReadLeExt {
type Error; type Error;
fn read_u8(&mut self) -> Result<u8, Error<Self::Error>>; fn read_u8(&mut self) -> Result<u8, Self::Error>;
fn read_u16_le(&mut self) -> Result<u16, Error<Self::Error>>; fn read_u16_le(&mut self) -> Result<u16, Self::Error>;
fn read_u32_le(&mut self) -> Result<u32, Error<Self::Error>>; fn read_u32_le(&mut self) -> Result<u32, Self::Error>;
} }
impl<T: Read> ReadLeExt for T { impl<T: Read> ReadLeExt for T {
type Error = <Self as IoBase>::Error; type Error = <Self as IoBase>::Error;
fn read_u8(&mut self) -> Result<u8, Error<Self::Error>> { fn read_u8(&mut self) -> Result<u8, Self::Error> {
let mut buf = [0_u8; 1]; let mut buf = [0_u8; 1];
self.read_exact(&mut buf)?; self.read_exact(&mut buf)?;
Ok(buf[0]) Ok(buf[0])
} }
fn read_u16_le(&mut self) -> Result<u16, Error<Self::Error>> { fn read_u16_le(&mut self) -> Result<u16, Self::Error> {
let mut buf = [0_u8; 2]; let mut buf = [0_u8; 2];
self.read_exact(&mut buf)?; self.read_exact(&mut buf)?;
Ok(u16::from_le_bytes(buf)) Ok(u16::from_le_bytes(buf))
} }
fn read_u32_le(&mut self) -> Result<u32, Error<Self::Error>> { fn read_u32_le(&mut self) -> Result<u32, Self::Error> {
let mut buf = [0_u8; 4]; let mut buf = [0_u8; 4];
self.read_exact(&mut buf)?; self.read_exact(&mut buf)?;
Ok(u32::from_le_bytes(buf)) Ok(u32::from_le_bytes(buf))
@@ -271,23 +277,23 @@ impl<T: Read> ReadLeExt for T {
#[allow(unused)] #[allow(unused)]
pub(crate) trait WriteLeExt { pub(crate) trait WriteLeExt {
type Error; type Error;
fn write_u8(&mut self, n: u8) -> Result<(), Error<Self::Error>>; fn write_u8(&mut self, n: u8) -> Result<(), Self::Error>;
fn write_u16_le(&mut self, n: u16) -> Result<(), Error<Self::Error>>; fn write_u16_le(&mut self, n: u16) -> Result<(), Self::Error>;
fn write_u32_le(&mut self, n: u32) -> Result<(), Error<Self::Error>>; fn write_u32_le(&mut self, n: u32) -> Result<(), Self::Error>;
} }
impl<T: Write> WriteLeExt for T { impl<T: Write> WriteLeExt for T {
type Error = <Self as IoBase>::Error; type Error = <Self as IoBase>::Error;
fn write_u8(&mut self, n: u8) -> Result<(), Error<Self::Error>> { fn write_u8(&mut self, n: u8) -> Result<(), Self::Error> {
self.write_all(&[n]) self.write_all(&[n])
} }
fn write_u16_le(&mut self, n: u16) -> Result<(), Error<Self::Error>> { fn write_u16_le(&mut self, n: u16) -> Result<(), Self::Error> {
self.write_all(&n.to_le_bytes()) self.write_all(&n.to_le_bytes())
} }
fn write_u32_le(&mut self, n: u32) -> Result<(), Error<Self::Error>> { fn write_u32_le(&mut self, n: u32) -> Result<(), Self::Error> {
self.write_all(&n.to_le_bytes()) self.write_all(&n.to_le_bytes())
} }
} }

View File

@@ -6,3 +6,4 @@ edition = "2024"
[dependencies] [dependencies]
os-std-macros = { path = "../os-std-macros" } os-std-macros = { path = "../os-std-macros" }
shared = { path = "../shared", features = ["user"] } shared = { path = "../shared", features = ["user"] }
io = { path = "../io", features = ["alloc"] }

1
crates/os-std/src/io.rs Normal file
View File

@@ -0,0 +1 @@
pub use io::SeekFrom;

View File

@@ -2,6 +2,7 @@
extern crate alloc; extern crate alloc;
pub mod io;
pub mod prelude; pub mod prelude;
pub use shared::fs; pub use shared::fs;

View File

@@ -5,6 +5,7 @@ edition = "2024"
[dependencies] [dependencies]
bffs = { path = "../bffs" } bffs = { path = "../bffs" }
io = { path = "../io" }
[features] [features]
kernel = [] kernel = []

View File

@@ -1,9 +1,16 @@
#[derive(Debug)]
pub struct File { pub struct File {
fd: u64, fd: u64,
} }
impl File { impl File {
/// # Safety
/// The file descriptor must be valid
pub unsafe fn new(fd: u64) -> Self { pub unsafe fn new(fd: u64) -> Self {
Self { fd } Self { fd }
} }
pub fn as_fd(&self) -> u64 {
self.fd
}
} }

View File

@@ -1,10 +1,15 @@
use core::{alloc::Layout, time::Duration}; use core::{alloc::Layout, time::Duration};
use bffs::path::Path; use bffs::path::Path;
use io::SeekFrom;
use crate::fs::File;
#[repr(u64)] #[repr(u64)]
pub enum SysCall { pub enum SysCall {
Write = 1,
Open = 2, Open = 2,
Seek = 8,
Alloc = 40, Alloc = 40,
Dealloc = 41, Dealloc = 41,
Exit = 60, Exit = 60,
@@ -17,7 +22,9 @@ pub enum SysCall {
impl From<u64> for SysCall { impl From<u64> for SysCall {
fn from(value: u64) -> Self { fn from(value: u64) -> Self {
match value { match value {
1 => SysCall::Write,
2 => SysCall::Open, 2 => SysCall::Open,
8 => SysCall::Seek,
40 => SysCall::Alloc, 40 => SysCall::Alloc,
41 => SysCall::Dealloc, 41 => SysCall::Dealloc,
60 => SysCall::Exit, 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); syscall!(SysCall::Dealloc, ptr as u64, size as u64, align as u64);
} }
} }
pub fn open<P: AsRef<Path>>(path: P) -> u64 { pub fn open<P: AsRef<Path>>(path: P) -> File {
unsafe { unsafe {
let path_str = path.as_ref().as_str(); let path_str = path.as_ref().as_str();
let ptr = path_str.as_ptr(); let ptr = path_str.as_ptr();
let size = path_str.len(); let size = path_str.len();
let (fd, ..) = syscall!(SysCall::Open, ptr as u64, size as u64); 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);
} }
} }

View File

@@ -17,7 +17,7 @@ build_user_prog prog:
cp {{ bin_path / prog }} {{ "mnt/usr/bin" / prog }} cp {{ bin_path / prog }} {{ "mnt/usr/bin" / prog }}
# riscv64-elf-objcopy -O binary {{ 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 }} cargo b {{ cargo_flags }}
just sync_filesystem just sync_filesystem
@@ -29,7 +29,17 @@ map_dir dir recipe:
{{ recipe }} $file ; \ {{ recipe }} $file ; \
done 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 perf: build
{{ qemu }} -perfmap -kernel {{ bin_path / "kernel-rust" }}& {{ qemu }} -perfmap -kernel {{ bin_path / "kernel-rust" }}&

1
rustfmt.toml Normal file
View File

@@ -0,0 +1 @@
edition = "2024"

View File

@@ -7,7 +7,12 @@ use core::arch::naked_asm;
use crate::{ use crate::{
clear_csr, clear_csr,
interrupt::{setup_machine_trap_handler, setup_supervisor_trap_handler}, 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; pub mod sbi;
@@ -45,6 +50,9 @@ pub extern "C" fn machine_mode_entry() {
set_csr!(mideleg, 1 << 5); set_csr!(mideleg, 1 << 5);
set_csr!(medeleg, 1 << 8); set_csr!(medeleg, 1 << 8);
// Delegate PLIC keyboard interrupt
set_csr!(mideleg, 1 << 9);
unsafe { unsafe {
setup_supervisor_trap_handler(); setup_supervisor_trap_handler();
}; };

124
src/draw.rs Normal file
View File

@@ -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<T: AsRef<str>>(
&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"};

118
src/fs.rs
View File

@@ -2,44 +2,18 @@
//! //!
//! Implements a minimal disk backend and exposes a global FILE_SYSTEM used by //! Implements a minimal disk backend and exposes a global FILE_SYSTEM used by
//! the kernel to load user binaries. //! the kernel to load user binaries.
use core::{cell::UnsafeCell, ops::Deref};
use bffs::{ use core::fmt::Debug;
io::{IoBase, Read, Seek},
Fat32FileSystem, 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 _; 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<Option<Fat32FileSystem<Disk>>>);
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<Disk>;
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)] #[derive(Debug)]
/// Simple disk backend that reads from a fixed memory region. /// Simple disk backend that reads from a fixed memory region.
/// ///
@@ -62,11 +36,11 @@ impl IoBase for Disk {
} }
impl Seek for Disk { impl Seek for Disk {
fn seek(&mut self, pos: bffs::io::SeekFrom) -> Result<u64, bffs::error::Error<Self::Error>> { fn seek(&mut self, pos: io::SeekFrom) -> Result<u64, Self::Error> {
match pos { match pos {
bffs::io::SeekFrom::Start(pos) => self.pos = pos, io::SeekFrom::Start(pos) => self.pos = pos,
bffs::io::SeekFrom::End(_) => unimplemented!(), io::SeekFrom::End(_) => unimplemented!(),
bffs::io::SeekFrom::Current(pos) => self.pos = (self.pos as i64 + pos) as u64, io::SeekFrom::Current(pos) => self.pos = (self.pos as i64 + pos) as u64,
} }
Ok(self.pos) Ok(self.pos)
} }
@@ -74,7 +48,7 @@ impl Seek for Disk {
impl Read for Disk { impl Read for Disk {
/// Read bytes from the in-memory disk image into `buf`. /// Read bytes from the in-memory disk image into `buf`.
fn read(&mut self, buf: &mut [u8]) -> Result<usize, bffs::error::Error<Self::Error>> { fn read(&mut self, buf: &mut [u8]) -> Result<usize, Self::Error> {
if self.pos >= self.size { if self.pos >= self.size {
return Ok(0); return Ok(0);
} }
@@ -90,3 +64,69 @@ impl Read for Disk {
Ok(size) 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<T: ReadSeek + Debug> VirtualNode for Fat32VirtualNode<'_, T> {}
impl<T> IoBase for Fat32VirtualNode<'_, T> {
type Error = ();
}
impl<T: ReadSeek + Debug> Seek for Fat32VirtualNode<'_, T> {
fn seek(&mut self, pos: io::SeekFrom) -> Result<u64, Self::Error> {
match &mut self.kind {
Fat32VirtualNodeType::Dir => todo!(),
Fat32VirtualNodeType::File(file) => file.seek(pos).map_err(|_| ()),
}
}
}
impl<T: ReadSeek + Debug> Read for Fat32VirtualNode<'_, T> {
fn read(&mut self, buf: &mut [u8]) -> Result<usize, ()> {
match &mut self.kind {
Fat32VirtualNodeType::Dir => unimplemented!(),
Fat32VirtualNodeType::File(file) => file.read(buf).map_err(|_| ()),
}
}
}
impl<T: ReadSeek + Debug> Write for Fat32VirtualNode<'_, T> {
fn write(&mut self, _buf: &[u8]) -> Result<usize, ()> {
todo!()
}
fn flush(&mut self) -> Result<(), Self::Error> {
todo!()
}
}
impl<T: ReadSeek + Debug> VirtualFileSystem for Fat32FileSystem<T> {
fn open(&mut self, path: &bffs::path::Path) -> Result<Box<dyn VirtualNode + '_>, ()> {
let entry = self.open_entry(path).unwrap();
Ok(Box::new(unsafe { Fat32VirtualNode::new(entry) }))
}
}

View File

@@ -4,18 +4,22 @@
//! This module contains the low-level trap handlers for machine and supervisor //! This module contains the low-level trap handlers for machine and supervisor
//! modes and the syscall dispatch implementation used by user processes. //! modes and the syscall dispatch implementation used by user processes.
use alloc::str; use alloc::str;
use io::SeekFrom;
use log::info; use log::info;
use shared::syscall::SysCall; use shared::syscall::SysCall;
use crate::{ use crate::{
KBD_DRIVER,
boot::sbi::{EextensionID, TimerFunctionID}, boot::sbi::{EextensionID, TimerFunctionID},
clear_csr, clear_csr, println,
process::{exit_process, sleep, ExecutionContext}, process::{ExecutionContext, exit_process, sleep},
read_csr, read_csr,
riscv::disable_interrupt, riscv::disable_interrupt,
scheduler::SCHEDULER, scheduler::SCHEDULER,
set_csr, syscall, 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, write_csr,
}; };
use core::{alloc::Layout, arch::naked_asm, time::Duration}; use core::{alloc::Layout, arch::naked_asm, time::Duration};
@@ -89,6 +93,9 @@ unsafe extern "C" fn machine_trap_handler(
setup_next_timer_interrupt(); setup_next_timer_interrupt();
set_csr!(mip, 1 << 5); set_csr!(mip, 1 << 5);
} }
11 => {
set_csr!(mip, 1 << 9);
}
_ => {} _ => {}
} }
} }
@@ -123,7 +130,7 @@ unsafe extern "C" fn supervisor_trap_handler(
match syscall { match syscall {
SysCall::Open => { SysCall::Open => {
let path = unsafe { str::from_raw_parts(a1 as *const u8, a2 as usize) }; 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 mut scheduler = SCHEDULER.lock();
let current_process = scheduler.get_current_process(); let current_process = scheduler.get_current_process();
@@ -132,6 +139,29 @@ unsafe extern "C" fn supervisor_trap_handler(
current_process.next_fd += 1; current_process.next_fd += 1;
unsafe { (*interrupt_state).a[0] = fd }; 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 => { SysCall::Alloc => {
let layout = Layout::from_size_align(a1 as usize, a2 as usize).unwrap(); let layout = Layout::from_size_align(a1 as usize, a2 as usize).unwrap();
// Allocate memory and put the pointer in a0 // Allocate memory and put the pointer in a0
@@ -176,6 +206,20 @@ unsafe extern "C" fn supervisor_trap_handler(
timer_interrupt(); timer_interrupt();
SCHEDULER.lock().schedule(&mut interrupt_state); 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() { pub unsafe fn setup_machine_trap_handler() {
write_csr!(mtvec, _machine_mode_trap); write_csr!(mtvec, _machine_mode_trap);
set_csr!(mie, IRQ_M_TIMER); set_csr!(mie, IRQ_M_TIMER);
set_csr!(mie, IRQ_M_EXTERNAL);
} }
/// Install the supervisor-mode trap entry point and configure periodic timer. /// Install the supervisor-mode trap entry point and configure periodic timer.
pub unsafe fn setup_supervisor_trap_handler() { pub unsafe fn setup_supervisor_trap_handler() {
write_csr!(stvec, _supervisor_mode_trap); write_csr!(stvec, _supervisor_mode_trap);
set_csr!(sie, 1 << 9);
setup_timer_interrupt(); setup_timer_interrupt();
} }

View File

@@ -5,32 +5,29 @@
#![no_std] #![no_std]
#![no_main] #![no_main]
// #![warn(clippy::pedantic)] // #![warn(clippy::pedantic)]
#![feature( #![allow(static_mut_refs)]
riscv_ext_intrinsics, #![feature(riscv_ext_intrinsics, str_from_raw_parts)]
const_trait_impl,
iter_map_windows, use core::sync::atomic::AtomicBool;
str_from_raw_parts,
macro_metavar_expr,
macro_metavar_expr_concat,
ptr_metadata
)]
use alloc::boxed::Box; use alloc::boxed::Box;
use embedded_alloc::LlffHeap as Heap; use embedded_alloc::LlffHeap as Heap;
use log::info; use log::info;
use crate::{ use crate::{
fs::FAT32_FILE_SYSTEM,
io::init_log, io::init_log,
riscv::enable_supervisor_interrupt, riscv::enable_supervisor_interrupt,
scheduler::{idle, SCHEDULER}, scheduler::{SCHEDULER, idle},
user::{proc2, test}, user::{proc2, test},
vga::{Color, Vga}, vga::Vga,
virtio::{Virtqueue, input::{VirtioInputDriver, init_plic_m_mode}},
virtual_fs::init_file_system,
}; };
extern crate alloc; extern crate alloc;
mod boot; mod boot;
mod critical_section; mod critical_section;
mod draw;
mod fs; mod fs;
mod interrupt; mod interrupt;
mod io; mod io;
@@ -41,30 +38,41 @@ mod scheduler;
mod sync; mod sync;
mod syscall; mod syscall;
mod time; mod time;
mod tty;
mod uart; mod uart;
mod user; mod user;
mod vga; mod vga;
mod virtio;
mod virtual_console;
mod virtual_fs; mod virtual_fs;
pub const HEAP_SIZE: usize = 1024 * 1024 * 32; // 32Mo RAM pub const HEAP_SIZE: usize = 1024 * 1024 * 32; // 32Mo RAM
#[global_allocator] #[global_allocator]
static HEAP: Heap = Heap::empty(); static HEAP: Heap = Heap::empty();
static HEAP_INITIALIZED: AtomicBool = AtomicBool::new(false);
// Usize is assumed to be an u64 in the whole kernel // Usize is assumed to be an u64 in the whole kernel
const _: () = assert!(core::mem::size_of::<usize>() == core::mem::size_of::<u64>()); const _: () = assert!(core::mem::size_of::<usize>() == core::mem::size_of::<u64>());
// 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)] #[unsafe(no_mangle)]
pub extern "C" fn supervisor_mode_entry() { pub extern "C" fn supervisor_mode_entry() {
unsafe { unsafe {
embedded_alloc::init!(HEAP, HEAP_SIZE); embedded_alloc::init!(HEAP, HEAP_SIZE);
HEAP_INITIALIZED.store(true, core::sync::atomic::Ordering::Relaxed);
init_log().unwrap(); init_log().unwrap();
Vga::init(); Vga::init();
FAT32_FILE_SYSTEM.init();
SCHEDULER.lock().init(); SCHEDULER.lock().init();
init_file_system();
} }
info!("Hello World !"); 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(test), "proc1");
SCHEDULER.lock().create_process(Box::new(proc2), "proc2"); 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"); .create_process_from_file("/usr/bin/test_pic");
enable_supervisor_interrupt(); enable_supervisor_interrupt();
unsafe {
KBD_DRIVER.init();
init_plic_m_mode();
}
idle(); idle();
} }

View File

@@ -7,29 +7,38 @@ use core::arch::riscv64::wfi;
use alloc::{format, string::ToString}; use alloc::{format, string::ToString};
use log::error; 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] #[panic_handler]
/// Kernel panic handler that displays the panic message on the framebuffer and halts. /// Kernel panic handler that displays the panic message on the framebuffer and halts.
fn panic(panic_info: &core::panic::PanicInfo) -> ! { fn panic(panic_info: &core::panic::PanicInfo) -> ! {
error!("PANIC !"); if !HEAP_INITIALIZED.load(core::sync::atomic::Ordering::Relaxed) {
let mut panic_message = panic_info.message().to_string(); write_uart("EARLY PANIC !");
if let Some(location) = panic_info.location() { } else {
panic_message = format!("{panic_message} at {}:{}", location.file(), location.line()); error!("PANIC !");
} let mut panic_message = panic_info.message().to_string();
error!("{panic_message}"); 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); Vga.clear_screen(Color::WHITE);
unsafe { Vga::draw_string(0, 0, "PANIC !", Color::BLACK, Color::WHITE) }; unsafe { Vga.draw_string(0, 0, "PANIC !", Color::BLACK, Color::WHITE) };
unsafe { unsafe {
Vga::draw_string( Vga.draw_string(
0, 0,
FONT_HEIGHT as u16, FONT_HEIGHT as u16,
panic_message, panic_message,
Color::BLACK, Color::BLACK,
Color::WHITE, Color::WHITE,
) )
}; };
}
loop { loop {
unsafe { wfi() } unsafe { wfi() }

View File

@@ -10,17 +10,17 @@
use core::time::Duration; use core::time::Duration;
use alloc::{boxed::Box, format, string::String, vec::Vec}; 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 goblin::elf::reloc::R_RISCV_RELATIVE;
use hashbrown::HashMap; use hashbrown::HashMap;
use log::info;
use shared::syscall::exit; use shared::syscall::exit;
use crate::{ use crate::{
fs::FAT32_FILE_SYSTEM,
println, println,
scheduler::{Scheduler, SCHEDULER}, scheduler::{SCHEDULER, Scheduler},
time::elapsed_time_since_startup, 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). /// Size of the stack allocated to each process (in 64-bit words).
@@ -84,7 +84,7 @@ pub struct Process {
/// Current state of the process. /// Current state of the process.
pub state: ProcessState, pub state: ProcessState,
/// Optional entry point for the process code. /// Optional entry point for the process code.
pub entry: Option<Box<dyn Fn() + Send>>, pub entry: Option<Box<dyn Fn()>>,
/// Wake time for sleeping processes. /// Wake time for sleeping processes.
pub wake_time: Duration, pub wake_time: Duration,
/// Saved execution context. /// Saved execution context.
@@ -92,7 +92,7 @@ pub struct Process {
/// Process stack. /// Process stack.
pub stack: [u64; STACK_SIZE], pub stack: [u64; STACK_SIZE],
/// File descriptor table. /// File descriptor table.
pub fd_table: HashMap<u64, Box<dyn VirtualNode + Send>>, pub fd_table: HashMap<u64, Box<dyn VirtualNode>>,
/// Next available file descriptor. /// Next available file descriptor.
pub next_fd: u64, pub next_fd: u64,
} }
@@ -164,7 +164,9 @@ impl Scheduler {
let name = path.as_str(); let name = path.as_str();
// Open and read the binary file // 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"); println!("Creating process");
let mut content: Vec<u8> = Vec::new(); let mut content: Vec<u8> = Vec::new();
bin.read_to_end(&mut content).unwrap(); bin.read_to_end(&mut content).unwrap();
@@ -190,7 +192,7 @@ impl Scheduler {
if min_vaddr != u64::MAX { if min_vaddr != u64::MAX {
let size = (max_vaddr - min_vaddr) as usize; 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 layout = Layout::from_size_align(size, 0x1000).unwrap();
let base = unsafe { alloc_zeroed(layout) }; let base = unsafe { alloc_zeroed(layout) };

View File

@@ -45,7 +45,7 @@ impl<T> Drop for MutexGuard<'_, T> {
} }
} }
impl<'a, T> Deref for MutexGuard<'a, T> { impl<T> Deref for MutexGuard<'_, T> {
type Target = T; type Target = T;
fn deref(&self) -> &Self::Target { 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<T> DerefMut for MutexGuard<'_, T> {
fn deref_mut(&mut self) -> &mut Self::Target { fn deref_mut(&mut self) -> &mut Self::Target {
unsafe { &mut *self.mutex.value.get() } unsafe { &mut *self.mutex.value.get() }
} }

View File

@@ -1,13 +1,5 @@
use core::alloc::Layout; 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 { pub unsafe fn alloc(layout: Layout) -> *mut u8 {
unsafe { alloc::alloc::alloc(layout) } 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) { pub unsafe fn dealloc(ptr: *mut u8, layout: core::alloc::Layout) {
unsafe { alloc::alloc::dealloc(ptr, layout) } unsafe { alloc::alloc::dealloc(ptr, layout) }
} }
pub fn open<P: AsRef<Path>>(
path: P,
in_kernel: bool,
) -> Result<Box<dyn VirtualNode + Send>, Error<<Disk as bffs::io::IoBase>::Error>> {
let path = path.as_ref();
let file = match path.split_path() {
("dev", path) => {
todo!()
}
_ => FAT32_FILE_SYSTEM.open_file(path)?,
};
todo!()
}

View File

@@ -7,14 +7,16 @@ use core::{
}; };
use crate::{ use crate::{
draw::{Color, Draw, FONT_WIDTH},
set_csr, set_csr,
vga::{Color, Vga, FONT_WIDTH, WIDTH}, vga::{Vga, WIDTH},
}; };
/// Supervisor timer interrupt enable bit for the SIE CSR. /// Supervisor timer interrupt enable bit for the SIE CSR.
pub const IRQ_S_TIMER: u8 = 1 << 5; pub const IRQ_S_TIMER: u8 = 1 << 5;
/// Machine timer interrupt enable bit for the MIE CSR (not used here, but provided for completeness). /// 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_TIMER: u8 = 1 << 7;
pub const IRQ_M_EXTERNAL: u64 = 1 << 11;
/// Memory-mapped address for the CLINT timer compare register. /// Memory-mapped address for the CLINT timer compare register.
const CLINT_TIMER_CMP: *mut u64 = 0x0200_4000 as *mut u64; 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); let formatted_time = format!("{:02}:{:02}:{:02}", hours, minutes, seconds);
unsafe { unsafe {
Vga::draw_string( Vga.draw_string(
(WIDTH - formatted_time.len() * FONT_WIDTH) as u16, (WIDTH - formatted_time.len() * FONT_WIDTH) as u16,
0, 0,
formatted_time, formatted_time,

73
src/tty.rs Normal file
View File

@@ -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<RefCell<VirtualConsole>>,
}
impl Tty {
pub fn new() -> Self {
Self {
console: RefCell::new(VirtualConsole::new()).into(),
}
}
}
#[derive(Debug)]
struct TtyNode {
console: Rc<RefCell<VirtualConsole>>,
}
impl VirtualFileSystem for Tty {
fn open(
&mut self,
path: &bffs::path::Path,
) -> Result<alloc::boxed::Box<dyn crate::virtual_fs::VirtualNode + '_>, ()> {
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<usize, Self::Error> {
unimplemented!()
}
}
impl Seek for TtyNode {
fn seek(&mut self, _pos: io::SeekFrom) -> Result<u64, Self::Error> {
unimplemented!()
}
}
impl Write for TtyNode {
fn write(&mut self, buf: &[u8]) -> Result<usize, Self::Error> {
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 {}

View File

@@ -2,9 +2,10 @@
//! //!
//! Provides primitives to initialize the Bochs-compatible frame buffer and //! Provides primitives to initialize the Bochs-compatible frame buffer and
//! draw text using an embedded font plate. //! draw text using an embedded font plate.
use kernel_macros::include_font_plate;
use log::info; use log::info;
use crate::draw::{Color, Draw};
const PCI_ECAM_BASE_ADDRESS: *mut u32 = 0x30000000 as *mut _; const PCI_ECAM_BASE_ADDRESS: *mut u32 = 0x30000000 as *mut _;
const BOCHS_DISPLAY_BASE_ADDRESS: *mut u32 = 0x50000000 as *mut _; const BOCHS_DISPLAY_BASE_ADDRESS: *mut u32 = 0x50000000 as *mut _;
const BOCHS_CONFIG_BASE_ADDRESS: *mut u16 = 0x40000000 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 WIDTH: usize = 1600;
pub const HEIGHT: usize = 900; 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. /// Framebuffer driver type providing text rendering helpers.
pub struct Vga {} pub struct Vga;
impl Vga { impl Vga {
/// Initialize the Bochs framebuffer and configure VGA parameters. /// Initialize the Bochs framebuffer and configure VGA parameters.
@@ -42,7 +23,7 @@ impl Vga {
/// programs the Bochs config registers accordingly. /// programs the Bochs config registers accordingly.
pub unsafe fn init() { pub unsafe fn init() {
for i in 0..32 { 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) }; let header = unsafe { core::ptr::read_volatile(addr) };
if header >> 16 == 0x1111 && header & 0xFFFF == 0x1234 { if header >> 16 == 0x1111 && header & 0xFFFF == 0x1234 {
info!("VGA Bochs PCI found"); info!("VGA Bochs PCI found");
@@ -71,101 +52,26 @@ impl Vga {
core::ptr::write_volatile(BOCHS_CONFIG_BASE_ADDRESS.byte_add(0x508), 0x41) core::ptr::write_volatile(BOCHS_CONFIG_BASE_ADDRESS.byte_add(0x508), 0x41)
}; };
Vga::clear_screen(Color::BLACK); Vga.clear_screen(Color::BLACK);
} }
/// # Safety pub unsafe fn write_u8_unsafe(offset: usize, value: u8) {
/// `x` must be less than `WIDTH` and `y` must be less than `HEIGHT` unsafe { *(VGA_ADDRESS.byte_add(offset) as *mut u8) = value }
/// 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) { 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; let pixel_index = x as usize + y as usize * WIDTH;
unsafe { *VGA_ADDRESS.add(pixel_index) = color } unsafe { *VGA_ADDRESS.add(pixel_index) = color }
} }
/// Draw a single character with a background color at (x,y). fn get_width(&self) -> usize {
/// WIDTH
/// 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) }
}
}
}
}
} }
/// # Safety fn get_height(&self) -> usize {
/// The text must have a length that can fit within a `u16` HEIGHT
/// 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<T: AsRef<str>>(
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
} }
} }
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"};

67
src/virtio.rs Normal file
View File

@@ -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,
}

192
src/virtio/input.rs Normal file
View File

@@ -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::<VirtioInputEvent>() 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) };
}

120
src/virtual_console.rs Normal file
View File

@@ -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<dyn virtual_fs::VirtualNode>,
}
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::<Color>() 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()
}
}

View File

@@ -1,24 +1,116 @@
use core::{cell::LazyCell, fmt::Debug};
use alloc::boxed::Box; use alloc::boxed::Box;
use bffs::path::{Path, PathBuf}; use bffs::{
Fat32FileSystem,
path::{Path, PathBuf},
};
use hashbrown::HashMap; use hashbrown::HashMap;
use io::{IoBase, Read, Seek, Write};
pub trait VirtualNode { use crate::{fs::Disk, tty::Tty, vga::Vga};
fn read(&mut self, buf: &mut [u8]) -> Result<usize, ()>;
} pub trait VirtualNode: IoBase<Error = ()> + Read + Write + Seek + Debug {}
pub trait VirtualFileSystem { pub trait VirtualFileSystem: Debug {
fn open(&mut self, path: &Path) -> Result<Box<dyn VirtualNode + Send>, ()>; fn open(&mut self, path: &Path) -> Result<Box<dyn VirtualNode + '_>, ()>;
} }
#[derive(Debug)]
pub struct MainFileSystem { pub struct MainFileSystem {
root: Box<dyn VirtualFileSystem>,
mounts: HashMap<PathBuf, Box<dyn VirtualFileSystem>>, mounts: HashMap<PathBuf, Box<dyn VirtualFileSystem>>,
} }
impl MainFileSystem {
pub fn mount(&mut self, path: PathBuf, fs: Box<dyn VirtualFileSystem>) {
self.mounts.insert(path, fs);
}
}
impl VirtualFileSystem for MainFileSystem { impl VirtualFileSystem for MainFileSystem {
fn open(&mut self, path: &Path) -> Result<Box<dyn VirtualNode + Send>, ()> { fn open(&mut self, path: &Path) -> Result<Box<dyn VirtualNode + '_>, ()> {
for mount in self.mounts.iter() { 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<MainFileSystem> = 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<u64, Self::Error> {
self.position = match pos {
io::SeekFrom::Start(v) => v,
io::SeekFrom::End(v) => {
((crate::vga::WIDTH * crate::vga::HEIGHT * size_of::<crate::draw::Color>()) 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<usize, ()> {
todo!() todo!()
} }
} }
impl Write for VGAVirtualNode {
fn write(&mut self, buf: &[u8]) -> Result<usize, ()> {
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<Box<dyn VirtualNode + '_>, ()> {
if !path.is_empty() {
Err(())
} else {
Ok(Box::new(VGAVirtualNode { position: 0 }))
}
}
}

View File

@@ -1,5 +1,7 @@
#![no_std] #![no_std]
#![no_main] #![no_main]
use os_std::syscall;
os_std::custom_std_setup! {} os_std::custom_std_setup! {}
fn main() { fn main() {
@@ -9,5 +11,13 @@ fn main() {
for _ in 0..100 { for _ in 0..100 {
test.push('C'); 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
);
} }