Better virtual file system, keyboard through MMIO&VirtIO
This commit is contained in:
@@ -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"] }
|
||||
|
||||
@@ -4,6 +4,7 @@ version = "0.1.0"
|
||||
edition = "2024"
|
||||
|
||||
[dependencies]
|
||||
io = { path = "../io" }
|
||||
bitflags = "2"
|
||||
|
||||
[features]
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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<T::Error>;
|
||||
}
|
||||
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)
|
||||
}
|
||||
}
|
||||
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)
|
||||
}
|
||||
}
|
||||
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)
|
||||
}
|
||||
|
||||
fn flush(&mut self) -> Result<(), Error<Self::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<P: AsRef<Path>>(
|
||||
&self,
|
||||
path: P,
|
||||
) -> Result<DirEntry<'a, T>, Error<T::Error>> {
|
||||
pub fn open_entry<P: AsRef<Path>>(&self, path: P) -> Result<DirEntry<'a, T>, Error<T::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<P: AsRef<Path>>(
|
||||
&self,
|
||||
path: P,
|
||||
) -> Result<File<'a, T>, Error<T::Error>> {
|
||||
pub fn open_file<P: AsRef<Path>>(&self, path: P) -> Result<File<'a, T>, Error<T::Error>> {
|
||||
if path.as_ref().is_absolute() {
|
||||
return self.fs.open_file(path);
|
||||
}
|
||||
|
||||
@@ -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<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; _];
|
||||
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<DirEntry<'a, T>, Error<T::Error>>;
|
||||
|
||||
fn next(&mut self) -> Option<Self::Item> {
|
||||
|
||||
@@ -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<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> {
|
||||
fn is_interrupted(&self) -> bool {
|
||||
match self {
|
||||
@@ -79,39 +72,8 @@ impl<T: core::fmt::Debug + IoError> IoError for Error<T> {
|
||||
fn new_write_zero_error() -> Self {
|
||||
Error::<T>::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::<T>::InvalidUTF8
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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<T>,
|
||||
@@ -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<T::Error>;
|
||||
}
|
||||
|
||||
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 {
|
||||
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<usize, Error<Self::Error>> {
|
||||
fn read(&mut self, buf: &mut [u8]) -> Result<usize, Self::Error> {
|
||||
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<usize, Error<Self::Error>> {
|
||||
fn write(&mut self, _buf: &[u8]) -> Result<usize, Self::Error> {
|
||||
todo!()
|
||||
}
|
||||
|
||||
fn flush(&mut self) -> Result<(), Error<Self::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<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> {
|
||||
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)
|
||||
}
|
||||
}
|
||||
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)
|
||||
}
|
||||
}
|
||||
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)
|
||||
}
|
||||
|
||||
fn flush(&mut self) -> Result<(), Error<Self::Error>> {
|
||||
fn flush(&mut self) -> Result<(), Self::Error> {
|
||||
self.raw.flush()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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<T> Fat32FileSystem<T> {
|
||||
}
|
||||
}
|
||||
impl<T: ReadSeek> Fat32FileSystem<T> {
|
||||
pub fn open_entry<P: AsRef<Path>>(
|
||||
&self,
|
||||
path: P,
|
||||
) -> Result<DirEntry<'_, T>, Error<T::Error>> {
|
||||
pub fn open_entry<P: AsRef<Path>>(&self, path: P) -> Result<DirEntry<'_, T>, Error<T::Error>> {
|
||||
let path = path.as_ref().as_str().trim_start_matches("/");
|
||||
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("/");
|
||||
self.root_directory().open_dir(path)
|
||||
}
|
||||
pub fn open_file<P: AsRef<Path>>(
|
||||
&self,
|
||||
path: P,
|
||||
) -> Result<File<'_, T>, Error<T::Error>> {
|
||||
pub fn open_file<P: AsRef<Path>>(&self, path: P) -> Result<File<'_, T>, Error<T::Error>> {
|
||||
let path = path.as_ref().as_str().trim_start_matches("/");
|
||||
self.root_directory().open_file(path)
|
||||
}
|
||||
|
||||
@@ -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<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 {
|
||||
fn as_ref(&self) -> &Path {
|
||||
self
|
||||
@@ -32,9 +43,17 @@ impl AsRef<str> 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<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")]
|
||||
#[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<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
10
crates/io/Cargo.toml
Normal 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
48
crates/io/src/error.rs
Normal 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",
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -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<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`.
|
||||
///
|
||||
@@ -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<Self::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::<Self::Error>::new_unexpected_eof_error())
|
||||
Err(Self::Error::new_unexpected_eof_error())
|
||||
}
|
||||
}
|
||||
|
||||
#[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;
|
||||
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<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() })?;
|
||||
|
||||
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<usize, Error<Self::Error>>;
|
||||
fn write(&mut self, buf: &[u8]) -> Result<usize, Self::Error>;
|
||||
|
||||
/// 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<Self::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::<Self::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<Self::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<u64, Error<Self::Error>>;
|
||||
fn seek(&mut self, pos: SeekFrom) -> Result<u64, Self::Error>;
|
||||
}
|
||||
|
||||
#[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<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)
|
||||
}
|
||||
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)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(all(feature = "std", not(target_arch = "riscv64")))]
|
||||
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)
|
||||
}
|
||||
|
||||
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)
|
||||
}
|
||||
|
||||
fn flush(&mut self) -> Result<(), Error<Self::Error>> {
|
||||
fn flush(&mut self) -> Result<(), Self::Error> {
|
||||
self.flush().map_err(Error::Io)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(all(feature = "std", not(target_arch = "riscv64")))]
|
||||
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)
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) trait ReadLeExt {
|
||||
pub trait ReadLeExt {
|
||||
type Error;
|
||||
fn read_u8(&mut self) -> Result<u8, Error<Self::Error>>;
|
||||
fn read_u16_le(&mut self) -> Result<u16, Error<Self::Error>>;
|
||||
fn read_u32_le(&mut self) -> Result<u32, Error<Self::Error>>;
|
||||
fn read_u8(&mut self) -> Result<u8, Self::Error>;
|
||||
fn read_u16_le(&mut self) -> Result<u16, Self::Error>;
|
||||
fn read_u32_le(&mut self) -> Result<u32, Self::Error>;
|
||||
}
|
||||
|
||||
impl<T: Read> ReadLeExt for T {
|
||||
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];
|
||||
self.read_exact(&mut buf)?;
|
||||
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];
|
||||
self.read_exact(&mut 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];
|
||||
self.read_exact(&mut buf)?;
|
||||
Ok(u32::from_le_bytes(buf))
|
||||
@@ -271,23 +277,23 @@ impl<T: Read> ReadLeExt for T {
|
||||
#[allow(unused)]
|
||||
pub(crate) trait WriteLeExt {
|
||||
type Error;
|
||||
fn write_u8(&mut self, n: u8) -> Result<(), Error<Self::Error>>;
|
||||
fn write_u16_le(&mut self, n: u16) -> Result<(), Error<Self::Error>>;
|
||||
fn write_u32_le(&mut self, n: u32) -> Result<(), Error<Self::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<T: Write> WriteLeExt for T {
|
||||
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])
|
||||
}
|
||||
|
||||
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())
|
||||
}
|
||||
|
||||
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())
|
||||
}
|
||||
}
|
||||
@@ -6,3 +6,4 @@ edition = "2024"
|
||||
[dependencies]
|
||||
os-std-macros = { path = "../os-std-macros" }
|
||||
shared = { path = "../shared", features = ["user"] }
|
||||
io = { path = "../io", features = ["alloc"] }
|
||||
|
||||
1
crates/os-std/src/io.rs
Normal file
1
crates/os-std/src/io.rs
Normal file
@@ -0,0 +1 @@
|
||||
pub use io::SeekFrom;
|
||||
@@ -2,6 +2,7 @@
|
||||
|
||||
extern crate alloc;
|
||||
|
||||
pub mod io;
|
||||
pub mod prelude;
|
||||
|
||||
pub use shared::fs;
|
||||
|
||||
@@ -5,6 +5,7 @@ edition = "2024"
|
||||
|
||||
[dependencies]
|
||||
bffs = { path = "../bffs" }
|
||||
io = { path = "../io" }
|
||||
|
||||
[features]
|
||||
kernel = []
|
||||
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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<u64> 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<P: AsRef<Path>>(path: P) -> u64 {
|
||||
pub fn open<P: AsRef<Path>>(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);
|
||||
}
|
||||
}
|
||||
|
||||
14
justfile
14
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" }}&
|
||||
|
||||
1
rustfmt.toml
Normal file
1
rustfmt.toml
Normal file
@@ -0,0 +1 @@
|
||||
edition = "2024"
|
||||
10
src/boot.rs
10
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();
|
||||
};
|
||||
|
||||
124
src/draw.rs
Normal file
124
src/draw.rs
Normal 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
118
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<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)]
|
||||
/// 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<u64, bffs::error::Error<Self::Error>> {
|
||||
fn seek(&mut self, pos: io::SeekFrom) -> Result<u64, Self::Error> {
|
||||
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<usize, bffs::error::Error<Self::Error>> {
|
||||
fn read(&mut self, buf: &mut [u8]) -> Result<usize, Self::Error> {
|
||||
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<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) }))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
|
||||
41
src/main.rs
41
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::<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)]
|
||||
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();
|
||||
}
|
||||
|
||||
@@ -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() }
|
||||
|
||||
@@ -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<Box<dyn Fn() + Send>>,
|
||||
pub entry: Option<Box<dyn Fn()>>,
|
||||
/// 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<u64, Box<dyn VirtualNode + Send>>,
|
||||
pub fd_table: HashMap<u64, Box<dyn VirtualNode>>,
|
||||
/// 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<u8> = 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) };
|
||||
|
||||
|
||||
@@ -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;
|
||||
|
||||
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 {
|
||||
unsafe { &mut *self.mutex.value.get() }
|
||||
}
|
||||
|
||||
@@ -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<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!()
|
||||
}
|
||||
|
||||
@@ -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,
|
||||
|
||||
73
src/tty.rs
Normal file
73
src/tty.rs
Normal 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 {}
|
||||
126
src/vga.rs
126
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<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
|
||||
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"};
|
||||
|
||||
67
src/virtio.rs
Normal file
67
src/virtio.rs
Normal 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
192
src/virtio/input.rs
Normal 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
120
src/virtual_console.rs
Normal 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()
|
||||
}
|
||||
}
|
||||
@@ -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<usize, ()>;
|
||||
}
|
||||
|
||||
pub trait VirtualFileSystem {
|
||||
fn open(&mut self, path: &Path) -> Result<Box<dyn VirtualNode + Send>, ()>;
|
||||
use crate::{fs::Disk, tty::Tty, vga::Vga};
|
||||
|
||||
pub trait VirtualNode: IoBase<Error = ()> + Read + Write + Seek + Debug {}
|
||||
|
||||
pub trait VirtualFileSystem: Debug {
|
||||
fn open(&mut self, path: &Path) -> Result<Box<dyn VirtualNode + '_>, ()>;
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct MainFileSystem {
|
||||
root: Box<dyn VirtualFileSystem>,
|
||||
mounts: HashMap<PathBuf, Box<dyn VirtualFileSystem>>,
|
||||
}
|
||||
|
||||
impl VirtualFileSystem for MainFileSystem {
|
||||
fn open(&mut self, path: &Path) -> Result<Box<dyn VirtualNode + Send>, ()> {
|
||||
for mount in self.mounts.iter() {
|
||||
impl MainFileSystem {
|
||||
pub fn mount(&mut self, path: PathBuf, fs: Box<dyn VirtualFileSystem>) {
|
||||
self.mounts.insert(path, fs);
|
||||
}
|
||||
}
|
||||
|
||||
impl VirtualFileSystem for MainFileSystem {
|
||||
fn open(&mut self, path: &Path) -> Result<Box<dyn VirtualNode + '_>, ()> {
|
||||
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!()
|
||||
}
|
||||
}
|
||||
|
||||
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 }))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
|
||||
);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user