Sync computers
This commit is contained in:
2
.gdbinit
2
.gdbinit
@@ -3,4 +3,4 @@ target remote localhost:1234
|
||||
# break machine_mode_entry
|
||||
break *0x800bf000
|
||||
add-symbol-file target/riscv64/debug/test_pic 0x800bf000
|
||||
c
|
||||
# c
|
||||
|
||||
@@ -12,7 +12,8 @@ embedded-alloc = "0.7"
|
||||
kernel-macros = { path = "crates/kernel-macros" }
|
||||
log = "0.4"
|
||||
critical-section = { version = "1", features = ["restore-state-bool"] }
|
||||
bffs = { path = "../../../code/bffs", features = ["alloc"] }
|
||||
shared = { path = "crates/shared" }
|
||||
bffs = { path = "crates/bffs", features = ["alloc"] }
|
||||
shared = { path = "crates/shared", features = ["kernel"] }
|
||||
# ELF parsing helper
|
||||
goblin = { version = "0.7", default-features = false, features = ["elf32", "elf64", "endian_fd"] }
|
||||
hashbrown = "0.16"
|
||||
|
||||
11
crates/bffs/Cargo.toml
Normal file
11
crates/bffs/Cargo.toml
Normal file
@@ -0,0 +1,11 @@
|
||||
[package]
|
||||
name = "bffs"
|
||||
version = "0.1.0"
|
||||
edition = "2024"
|
||||
|
||||
[dependencies]
|
||||
bitflags = "2"
|
||||
|
||||
[features]
|
||||
alloc = []
|
||||
std = ["alloc"]
|
||||
2
crates/bffs/justfile
Normal file
2
crates/bffs/justfile
Normal file
@@ -0,0 +1,2 @@
|
||||
run:
|
||||
cargo r --features std
|
||||
165
crates/bffs/src/boot_sector.rs
Normal file
165
crates/bffs/src/boot_sector.rs
Normal file
@@ -0,0 +1,165 @@
|
||||
use crate::{error::Error, io::{Read, ReadLeExt}};
|
||||
|
||||
#[derive(Debug, Clone, Copy)]
|
||||
pub struct Fat32BootSector {
|
||||
// --- Jump + OEM ---
|
||||
pub jump_boot: [u8; 3],
|
||||
pub oem_name: [u8; 8],
|
||||
|
||||
// --- BPB (BIOS Parameter Block) ---
|
||||
pub bytes_per_sector: u16,
|
||||
pub sectors_per_cluster: u8,
|
||||
pub reserved_sector_count: u16,
|
||||
pub num_fats: u8,
|
||||
pub root_entry_count: u16,
|
||||
pub total_sectors_16: u16,
|
||||
pub media: u8,
|
||||
pub fat_size_16: u16,
|
||||
pub sectors_per_track: u16,
|
||||
pub num_heads: u16,
|
||||
pub hidden_sectors: u32,
|
||||
pub total_sectors_32: u32,
|
||||
|
||||
// --- FAT32 Extended BPB ---
|
||||
pub fat_size_32: u32,
|
||||
pub ext_flags: u16,
|
||||
pub fs_version: u16,
|
||||
pub root_cluster: u32,
|
||||
pub fs_info: u16,
|
||||
pub bk_boot_sec: u16,
|
||||
pub reserved: [u8; 12],
|
||||
|
||||
// --- FAT32 Drive Info ---
|
||||
pub drive_number: u8,
|
||||
pub reserved1: u8,
|
||||
pub boot_signature: u8,
|
||||
pub volume_id: u32,
|
||||
pub volume_label: [u8; 11],
|
||||
pub fs_type: [u8; 8],
|
||||
|
||||
// --- Boot code ---
|
||||
pub boot_code: [u8; 420],
|
||||
|
||||
// --- Signature ---
|
||||
pub boot_sector_signature: u16, // 0xAA55
|
||||
}
|
||||
|
||||
impl core::fmt::Display for Fat32BootSector {
|
||||
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
|
||||
f.debug_struct("Fat32BootSector")
|
||||
.field("jump_boot", &self.jump_boot)
|
||||
.field("oem_name", &self.get_oem_name())
|
||||
.field("bytes_per_sector", &self.bytes_per_sector)
|
||||
.field("sectors_per_cluster", &self.sectors_per_cluster)
|
||||
.field("reserved_sector_count", &self.reserved_sector_count)
|
||||
.field("num_fats", &self.num_fats)
|
||||
.field("root_entry_count", &self.root_entry_count)
|
||||
.field("total_sectors_16", &self.total_sectors_16)
|
||||
.field("media", &self.media)
|
||||
.field("fat_size_16", &self.fat_size_16)
|
||||
.field("sectors_per_track", &self.sectors_per_track)
|
||||
.field("num_heads", &self.num_heads)
|
||||
.field("hidden_sectors", &self.hidden_sectors)
|
||||
.field("total_sectors_32", &self.total_sectors_32)
|
||||
.field("fat_size_32", &self.fat_size_32)
|
||||
.field("ext_flags", &self.ext_flags)
|
||||
.field("fs_version", &self.fs_version)
|
||||
.field("root_cluster", &self.root_cluster)
|
||||
.field("fs_info", &self.fs_info)
|
||||
.field("bk_boot_sec", &self.bk_boot_sec)
|
||||
.field("reserved", &self.reserved)
|
||||
.field("drive_number", &self.drive_number)
|
||||
.field("reserved1", &self.reserved1)
|
||||
.field("boot_signature", &self.boot_signature)
|
||||
.field("volume_id", &self.volume_id)
|
||||
.field("volume_label", &self.get_volume_label())
|
||||
.field("fs_type", &self.get_fs_type())
|
||||
.field("boot_code", &"[_; 420]")
|
||||
.field("boot_sector_signature", &self.boot_sector_signature)
|
||||
.finish()
|
||||
}
|
||||
}
|
||||
|
||||
impl Fat32BootSector {
|
||||
pub fn get_oem_name(&self) -> Result<&str, core::str::Utf8Error> {
|
||||
str::from_utf8(&self.oem_name)
|
||||
}
|
||||
pub fn get_volume_label(&self) -> Result<&str, core::str::Utf8Error> {
|
||||
str::from_utf8(&self.volume_label)
|
||||
}
|
||||
pub fn get_fs_type(&self) -> Result<&str, core::str::Utf8Error> {
|
||||
str::from_utf8(&self.fs_type)
|
||||
}
|
||||
}
|
||||
|
||||
impl Fat32BootSector {
|
||||
pub fn deserialize<T: Read>(disk: &mut T) -> Result<Self, Error<T::Error>> {
|
||||
let mut jump_boot = [0u8; _];
|
||||
disk.read_exact(&mut jump_boot)?;
|
||||
let mut oem_name = [0u8; _];
|
||||
disk.read_exact(&mut oem_name)?;
|
||||
let bytes_per_sector = disk.read_u16_le()?;
|
||||
let sectors_per_cluster = disk.read_u8()?;
|
||||
let reserved_sector_count = disk.read_u16_le()?;
|
||||
let num_fats = disk.read_u8()?;
|
||||
let root_entry_count = disk.read_u16_le()?;
|
||||
let total_sectors_16 = disk.read_u16_le()?;
|
||||
let media = disk.read_u8()?;
|
||||
let fat_size_16 = disk.read_u16_le()?;
|
||||
let sectors_per_track = disk.read_u16_le()?;
|
||||
let num_heads = disk.read_u16_le()?;
|
||||
let hidden_sectors = disk.read_u32_le()?;
|
||||
let total_sectors_32 = disk.read_u32_le()?;
|
||||
let fat_size_32 = disk.read_u32_le()?;
|
||||
let ext_flags = disk.read_u16_le()?;
|
||||
let fs_version = disk.read_u16_le()?;
|
||||
let root_cluster = disk.read_u32_le()?;
|
||||
let fs_info = disk.read_u16_le()?;
|
||||
let bk_boot_sec = disk.read_u16_le()?;
|
||||
let mut reserved = [0u8; _];
|
||||
disk.read_exact(&mut reserved)?;
|
||||
let drive_number = disk.read_u8()?;
|
||||
let reserved1 = disk.read_u8()?;
|
||||
let boot_signature = disk.read_u8()?;
|
||||
let volume_id = disk.read_u32_le()?;
|
||||
let mut volume_label = [0u8; _];
|
||||
disk.read_exact(&mut volume_label)?;
|
||||
let mut fs_type = [0u8; _];
|
||||
disk.read_exact(&mut fs_type)?;
|
||||
let mut boot_code = [0u8; _];
|
||||
disk.read_exact(&mut boot_code)?;
|
||||
let boot_sector_signature = disk.read_u16_le()?;
|
||||
|
||||
Ok(Self {
|
||||
jump_boot,
|
||||
oem_name,
|
||||
bytes_per_sector,
|
||||
sectors_per_cluster,
|
||||
reserved_sector_count,
|
||||
num_fats,
|
||||
root_entry_count,
|
||||
total_sectors_16,
|
||||
media,
|
||||
fat_size_16,
|
||||
sectors_per_track,
|
||||
num_heads,
|
||||
hidden_sectors,
|
||||
total_sectors_32,
|
||||
fat_size_32,
|
||||
ext_flags,
|
||||
fs_version,
|
||||
root_cluster,
|
||||
fs_info,
|
||||
bk_boot_sec,
|
||||
reserved,
|
||||
drive_number,
|
||||
reserved1,
|
||||
boot_signature,
|
||||
volume_id,
|
||||
volume_label,
|
||||
fs_type,
|
||||
boot_code,
|
||||
boot_sector_signature,
|
||||
})
|
||||
}
|
||||
}
|
||||
18
crates/bffs/src/consts.rs
Normal file
18
crates/bffs/src/consts.rs
Normal file
@@ -0,0 +1,18 @@
|
||||
use bitflags::bitflags;
|
||||
|
||||
bitflags! {
|
||||
pub struct FATAttr: u8 {
|
||||
const READ_ONLY = 0x01;
|
||||
const HIDDEN = 0x02;
|
||||
const SYSTEM = 0x04;
|
||||
const VOLUME_ID = 0x08;
|
||||
const DIRECTORY = 0x10;
|
||||
const ARCHIVE = 0x20;
|
||||
const LFN = 0x0F;
|
||||
}
|
||||
}
|
||||
|
||||
pub const FAT32_CLUSTER_MASK: u32 = 0x0FFFFFFF;
|
||||
pub const FAT32_FREE_CLUSTER: u32 = 0x00000000;
|
||||
pub const FAT32_BAD_CLUSTER: u32 = 0x0FFFFFF7;
|
||||
pub const FAT32_END_OF_CHAIN: u32 = 0x0FFFFFF8;
|
||||
103
crates/bffs/src/dir.rs
Normal file
103
crates/bffs/src/dir.rs
Normal file
@@ -0,0 +1,103 @@
|
||||
use crate::{
|
||||
entry::{DirEntry, DirectoryIterator},
|
||||
error::Error,
|
||||
file::{File, RawFile},
|
||||
io::{self, IoBase, Read, Seek, Write},
|
||||
path::Path,
|
||||
Fat32FileSystem, ReadSeek, ReadWriteSeek,
|
||||
};
|
||||
|
||||
pub struct Dir<'a, T> {
|
||||
raw: RawFile<'a, T>,
|
||||
fs: &'a Fat32FileSystem<T>,
|
||||
}
|
||||
|
||||
impl<'a, T: IoBase> IoBase for Dir<'a, T> {
|
||||
type Error = T::Error;
|
||||
}
|
||||
impl<'a, T: ReadSeek> Seek for Dir<'a, T> {
|
||||
fn seek(&mut self, pos: io::SeekFrom) -> Result<u64, Error<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>> {
|
||||
self.raw.read(buf)
|
||||
}
|
||||
}
|
||||
impl<'a, T: ReadWriteSeek> Write for Dir<'a, T> {
|
||||
fn write(&mut self, buf: &[u8]) -> Result<usize, Error<Self::Error>> {
|
||||
self.raw.write(buf)
|
||||
}
|
||||
|
||||
fn flush(&mut self) -> Result<(), Error<Self::Error>> {
|
||||
self.raw.flush()
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, T> Dir<'a, T> {
|
||||
pub fn new(raw: RawFile<'a, T>, fs: &'a Fat32FileSystem<T>) -> Self {
|
||||
Self { raw, fs }
|
||||
}
|
||||
pub fn iter(&self) -> DirectoryIterator<'a, T> {
|
||||
DirectoryIterator::new(RawFile::new(self.fs, self.raw.first_cluster(), None))
|
||||
}
|
||||
}
|
||||
impl<'a, T: ReadSeek> Dir<'a, T> {
|
||||
pub fn open_entry<'b, P: Into<Path<'b>>>(
|
||||
&self,
|
||||
path: P,
|
||||
) -> Result<DirEntry<'a, T>, Error<T::Error>> {
|
||||
let path = path.into();
|
||||
if path.is_absolute() {
|
||||
return self.fs.open_entry(path);
|
||||
}
|
||||
for file in self.iter() {
|
||||
let f = file?;
|
||||
if f.name_is(path.as_str()) {
|
||||
return Ok(f);
|
||||
}
|
||||
}
|
||||
Err(Error::NotFound)
|
||||
}
|
||||
pub fn open_file<'b, P: Into<Path<'b>>>(
|
||||
&self,
|
||||
path: P,
|
||||
) -> Result<File<'a, T>, Error<T::Error>> {
|
||||
let path = path.into();
|
||||
if path.is_absolute() {
|
||||
return self.fs.open_file(path);
|
||||
}
|
||||
let (start, dirname) = path.split_path();
|
||||
let entry = self.open_entry(start)?;
|
||||
match dirname {
|
||||
Some(name) => {
|
||||
if !entry.is_dir() {
|
||||
return Err(Error::NotFound);
|
||||
}
|
||||
entry.to_dir().open_file(name)
|
||||
}
|
||||
None => {
|
||||
if !entry.is_file() {
|
||||
return Err(Error::NotFound);
|
||||
}
|
||||
Ok(entry.to_file())
|
||||
}
|
||||
}
|
||||
}
|
||||
pub fn open_dir<'b, P: Into<Path<'b>>>(&self, path: P) -> Result<Self, Error<T::Error>> {
|
||||
let path = path.into();
|
||||
if path.is_absolute() {
|
||||
return self.fs.open_dir(path);
|
||||
}
|
||||
let (start, dirname) = path.split_path();
|
||||
let entry = self.open_entry(start)?;
|
||||
if !entry.is_dir() {
|
||||
return Err(Error::NotFound);
|
||||
}
|
||||
match dirname {
|
||||
Some(name) => entry.to_dir().open_dir(name),
|
||||
None => Ok(entry.to_dir()),
|
||||
}
|
||||
}
|
||||
}
|
||||
346
crates/bffs/src/entry.rs
Normal file
346
crates/bffs/src/entry.rs
Normal file
@@ -0,0 +1,346 @@
|
||||
use core::{array::IntoIter, iter::Copied, str::Utf8Error};
|
||||
use core::{char::DecodeUtf16, marker::PhantomData, slice::Iter};
|
||||
|
||||
use crate::{
|
||||
consts::FATAttr,
|
||||
dir::Dir,
|
||||
error::Error,
|
||||
file::{File, RawFile},
|
||||
io::{IoBase, Read, ReadLeExt},
|
||||
Fat32FileSystem, ReadSeek,
|
||||
};
|
||||
|
||||
#[cfg(feature = "alloc")]
|
||||
use alloc::borrow::ToOwned;
|
||||
#[cfg(feature = "alloc")]
|
||||
use alloc::string::String;
|
||||
|
||||
#[derive(Debug, Clone, Copy)]
|
||||
pub struct FatDirEntry {
|
||||
pub name: [u8; 11],
|
||||
pub attr: u8,
|
||||
pub nt_reserved: u8,
|
||||
pub creation_time_tenth: u8,
|
||||
pub creation_time: u16,
|
||||
pub creation_date: u16,
|
||||
pub last_access_date: u16,
|
||||
pub first_cluster_high: u16,
|
||||
pub write_time: u16,
|
||||
pub write_date: u16,
|
||||
pub first_cluster_low: u16,
|
||||
pub file_size: u32,
|
||||
}
|
||||
|
||||
impl FatDirEntry {
|
||||
pub fn deserialize<T: Read>(reader: &mut T) -> Result<Self, Error<T::Error>> {
|
||||
let mut name = [0u8; _];
|
||||
reader.read_exact(&mut name)?;
|
||||
let attr = reader.read_u8()?;
|
||||
let nt_reserved = reader.read_u8()?;
|
||||
let creation_time_tenth = reader.read_u8()?;
|
||||
let creation_time = reader.read_u16_le()?;
|
||||
let creation_date = reader.read_u16_le()?;
|
||||
let last_access_date = reader.read_u16_le()?;
|
||||
let first_cluster_high = reader.read_u16_le()?;
|
||||
let write_time = reader.read_u16_le()?;
|
||||
let write_date = reader.read_u16_le()?;
|
||||
let first_cluster_low = reader.read_u16_le()?;
|
||||
let file_size = reader.read_u32_le()?;
|
||||
Ok(Self {
|
||||
name,
|
||||
attr,
|
||||
nt_reserved,
|
||||
creation_time_tenth,
|
||||
creation_time,
|
||||
creation_date,
|
||||
last_access_date,
|
||||
first_cluster_high,
|
||||
write_time,
|
||||
write_date,
|
||||
first_cluster_low,
|
||||
file_size,
|
||||
})
|
||||
}
|
||||
|
||||
pub fn is_dir(&self) -> bool {
|
||||
(self.attr & FATAttr::DIRECTORY.bits()) != 0
|
||||
}
|
||||
|
||||
pub fn is_file(&self) -> bool {
|
||||
!self.is_dir()
|
||||
}
|
||||
|
||||
pub fn first_cluster(&self) -> u32 {
|
||||
((self.first_cluster_high as u32) << 16) | (self.first_cluster_low as u32)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy)]
|
||||
pub struct FatLfnEntry {
|
||||
pub sequence_number: u8,
|
||||
pub name_part1: [u16; 5],
|
||||
pub attr: u8,
|
||||
pub type_u: u8,
|
||||
pub checksum: u8,
|
||||
pub name_part2: [u16; 6],
|
||||
pub first_cluster: u16,
|
||||
pub name_part3: [u16; 2],
|
||||
}
|
||||
|
||||
impl From<FatDirEntry> for FatLfnEntry {
|
||||
fn from(value: FatDirEntry) -> Self {
|
||||
let sequence_number = value.name[0];
|
||||
let name_part1 = [
|
||||
u16::from_le_bytes([value.name[1], value.name[2]]),
|
||||
u16::from_le_bytes([value.name[3], value.name[4]]),
|
||||
u16::from_le_bytes([value.name[5], value.name[6]]),
|
||||
u16::from_le_bytes([value.name[7], value.name[8]]),
|
||||
u16::from_le_bytes([value.name[9], value.name[10]]),
|
||||
];
|
||||
let name_part2 = [
|
||||
value.creation_time,
|
||||
value.creation_date,
|
||||
value.last_access_date,
|
||||
value.first_cluster_high,
|
||||
value.write_time,
|
||||
value.write_date,
|
||||
];
|
||||
let name_part3 = [
|
||||
value.file_size as u16,
|
||||
(value.file_size >> u16::BITS) as u16,
|
||||
];
|
||||
Self {
|
||||
sequence_number,
|
||||
name_part1,
|
||||
attr: value.attr,
|
||||
type_u: value.nt_reserved,
|
||||
checksum: value.creation_time_tenth,
|
||||
name_part2,
|
||||
first_cluster: value.first_cluster_low,
|
||||
name_part3,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub type FatEntry = u32;
|
||||
|
||||
#[derive(Debug, Clone, Copy)]
|
||||
pub struct LongFileNameBuilder<T> {
|
||||
inner: [u16; 256],
|
||||
_phantom: PhantomData<T>,
|
||||
}
|
||||
|
||||
impl<T: IoBase> LongFileNameBuilder<T> {
|
||||
pub fn new() -> Self {
|
||||
Self {
|
||||
inner: [0; _],
|
||||
_phantom: PhantomData,
|
||||
}
|
||||
}
|
||||
pub fn push(&mut self, entry: &FatLfnEntry, index: usize) {
|
||||
// Each name entry are 13 bytes long
|
||||
let char_offset = index * 13;
|
||||
|
||||
if char_offset <= 256 {
|
||||
// First segment
|
||||
self.inner[char_offset..char_offset + 5].copy_from_slice(&entry.name_part1);
|
||||
|
||||
if char_offset + 13 <= 256 {
|
||||
// Second segment
|
||||
self.inner[char_offset + 5..char_offset + 11].copy_from_slice(&entry.name_part2);
|
||||
|
||||
// Third segment
|
||||
self.inner[char_offset + 11..char_offset + 13].copy_from_slice(&entry.name_part3);
|
||||
} else {
|
||||
// If the name is longer than 247 characters, the last entry is only 9 characters long.
|
||||
self.inner[char_offset + 5..char_offset + 9]
|
||||
.copy_from_slice(&entry.name_part2[..4]);
|
||||
}
|
||||
}
|
||||
}
|
||||
#[cfg(feature = "alloc")]
|
||||
pub fn to_string(&self) -> Result<String, Error<T::Error>> {
|
||||
self.iter().try_collect::<String>()
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: IoBase> IntoIterator for LongFileNameBuilder<T> {
|
||||
type Item = <LongFileNameIterator<T, IntoIter<u16, 256>> as Iterator>::Item;
|
||||
|
||||
type IntoIter = LongFileNameIterator<T, IntoIter<u16, 256>>;
|
||||
|
||||
fn into_iter(self) -> Self::IntoIter {
|
||||
LongFileNameIterator {
|
||||
iterator: char::decode_utf16(self.inner),
|
||||
_phantom: PhantomData,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: IoBase> LongFileNameBuilder<T> {
|
||||
pub fn iter(&self) -> LongFileNameIterator<T, Copied<Iter<'_, u16>>> {
|
||||
LongFileNameIterator {
|
||||
iterator: char::decode_utf16(self.inner.iter().copied()),
|
||||
_phantom: PhantomData,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub struct LongFileNameIterator<T, I: Iterator<Item = u16>> {
|
||||
iterator: DecodeUtf16<I>,
|
||||
_phantom: PhantomData<T>,
|
||||
}
|
||||
|
||||
impl<T: IoBase, I: Iterator<Item = u16>> Iterator for LongFileNameIterator<T, I> {
|
||||
type Item = Result<char, Error<T::Error>>;
|
||||
|
||||
fn next(&mut self) -> Option<Self::Item> {
|
||||
match self.iterator.next()? {
|
||||
Ok(value) => {
|
||||
if value == '\0' {
|
||||
None
|
||||
} else {
|
||||
Some(Ok(value))
|
||||
}
|
||||
}
|
||||
Err(_) => Some(Err(Error::UnsupportedFileNameCharacter)),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: IoBase> Default for LongFileNameBuilder<T> {
|
||||
fn default() -> Self {
|
||||
Self::new()
|
||||
}
|
||||
}
|
||||
|
||||
pub struct DirectoryIterator<'a, T> {
|
||||
directory_file: RawFile<'a, T>,
|
||||
}
|
||||
|
||||
impl<'a, T> DirectoryIterator<'a, T> {
|
||||
pub fn new(directory_file: RawFile<'a, T>) -> Self {
|
||||
Self { directory_file }
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, T: ReadSeek> Iterator for DirectoryIterator<'a, T> {
|
||||
type Item = Result<DirEntry<'a, T>, Error<T::Error>>;
|
||||
|
||||
fn next(&mut self) -> Option<Self::Item> {
|
||||
let mut lfn_builder = LongFileNameBuilder::new();
|
||||
let mut has_lfn = false;
|
||||
|
||||
loop {
|
||||
let entry = match FatDirEntry::deserialize(&mut self.directory_file) {
|
||||
Ok(v) => v,
|
||||
Err(e) => return Some(Err(e)),
|
||||
};
|
||||
|
||||
// There is no entry after this one
|
||||
if entry.name[0] == 0x00 {
|
||||
return None;
|
||||
}
|
||||
|
||||
// The file was suppressed
|
||||
if entry.name[0] == 0xE5 {
|
||||
return self.next();
|
||||
}
|
||||
|
||||
if entry.attr == FATAttr::LFN.bits() {
|
||||
// Current entry is a LongFileName entry
|
||||
let lfn: FatLfnEntry = entry.into();
|
||||
|
||||
let index = ((lfn.sequence_number & 0x1F) - 1) as usize;
|
||||
lfn_builder.push(&lfn, index);
|
||||
has_lfn = true;
|
||||
} else {
|
||||
// Check if the entry is "." or ".."
|
||||
if entry.name[0] == b'.'
|
||||
&& (entry.name[1] == b'.' || entry.name[1] == b' ')
|
||||
&& entry.name.iter().skip(2).all(|c| *c == b' ')
|
||||
{
|
||||
return self.next();
|
||||
} else {
|
||||
return Some(Ok(DirEntry::new(
|
||||
entry,
|
||||
entry.name,
|
||||
has_lfn.then_some(lfn_builder),
|
||||
self.directory_file.fs,
|
||||
)));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct DirEntry<'a, T> {
|
||||
entry: FatDirEntry,
|
||||
short_name: [u8; 11],
|
||||
long_name: Option<LongFileNameBuilder<T>>,
|
||||
fs: &'a Fat32FileSystem<T>,
|
||||
}
|
||||
|
||||
impl<'a, T: IoBase> DirEntry<'a, T> {
|
||||
fn new(
|
||||
entry: FatDirEntry,
|
||||
short_name: [u8; 11],
|
||||
long_name: Option<LongFileNameBuilder<T>>,
|
||||
fs: &'a Fat32FileSystem<T>,
|
||||
) -> Self {
|
||||
Self {
|
||||
entry,
|
||||
short_name,
|
||||
long_name,
|
||||
fs,
|
||||
}
|
||||
}
|
||||
#[cfg(feature = "alloc")]
|
||||
pub fn name(&self) -> Result<String, Error<T::Error>> {
|
||||
if let Some(long_name) = self.long_name() {
|
||||
long_name.to_string()
|
||||
} else {
|
||||
self.short_name()
|
||||
.map(|n| n.to_owned())
|
||||
.map_err(|_| Error::UnsupportedFileNameCharacter)
|
||||
}
|
||||
}
|
||||
pub fn long_name(&self) -> Option<&LongFileNameBuilder<T>> {
|
||||
self.long_name.as_ref()
|
||||
}
|
||||
pub fn short_name(&self) -> Result<&str, Utf8Error> {
|
||||
str::from_utf8(&self.short_name)
|
||||
}
|
||||
pub fn name_is(&self, name: &str) -> bool {
|
||||
if let Some(long_name) = self.long_name() {
|
||||
long_name
|
||||
.iter()
|
||||
.eq_by(name.chars(), |a, b| a.is_ok_and(|a| a == b))
|
||||
} else {
|
||||
self.short_name().is_ok_and(|n| n == name)
|
||||
}
|
||||
}
|
||||
pub fn is_dir(&self) -> bool {
|
||||
self.entry.is_dir()
|
||||
}
|
||||
pub fn is_file(&self) -> bool {
|
||||
self.entry.is_file()
|
||||
}
|
||||
pub fn to_dir(&self) -> Dir<'a, T> {
|
||||
Dir::new(
|
||||
RawFile::new(self.fs, self.entry.first_cluster(), None),
|
||||
self.fs,
|
||||
)
|
||||
}
|
||||
pub fn to_file(&self) -> File<'a, T> {
|
||||
File::new(
|
||||
RawFile::new(
|
||||
self.fs,
|
||||
self.entry.first_cluster(),
|
||||
Some(self.entry.file_size),
|
||||
),
|
||||
self.fs,
|
||||
)
|
||||
}
|
||||
}
|
||||
117
crates/bffs/src/error.rs
Normal file
117
crates/bffs/src/error.rs
Normal file
@@ -0,0 +1,117 @@
|
||||
/// 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
|
||||
#[derive(Debug)]
|
||||
#[non_exhaustive]
|
||||
pub enum Error<T> {
|
||||
/// A user provided storage instance returned an error during an input/output operation.
|
||||
Io(T),
|
||||
/// A read operation cannot be completed because an end of a file has been reached prematurely.
|
||||
UnexpectedEof,
|
||||
/// A write operation cannot be completed because `Write::write` returned 0.
|
||||
WriteZero,
|
||||
/// A parameter was incorrect.
|
||||
InvalidInput,
|
||||
/// A requested file or directory has not been found.
|
||||
NotFound,
|
||||
/// A file or a directory with the same name already exists.
|
||||
AlreadyExists,
|
||||
/// An operation cannot be finished because a directory is not empty.
|
||||
DirectoryIsNotEmpty,
|
||||
/// File system internal structures are corrupted/invalid.
|
||||
CorruptedFileSystem,
|
||||
/// There is not enough free space on the storage to finish the requested operation.
|
||||
NotEnoughSpace,
|
||||
/// The provided file name is either too long or empty.
|
||||
InvalidFileNameLength,
|
||||
/// The provided file name contains an invalid character.
|
||||
UnsupportedFileNameCharacter,
|
||||
/// The file content contains invalid UTF-8 characters.
|
||||
InvalidUTF8,
|
||||
}
|
||||
|
||||
impl<T: IoError> From<T> for Error<T> {
|
||||
fn from(error: T) -> Self {
|
||||
Error::Io(error)
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: core::fmt::Display> core::fmt::Display for Error<T> {
|
||||
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
|
||||
match self {
|
||||
Error::Io(io_error) => write!(f, "IO error: {}", io_error),
|
||||
Error::UnexpectedEof => write!(f, "Unexpected end of file"),
|
||||
Error::NotEnoughSpace => write!(f, "Not enough space"),
|
||||
Error::WriteZero => write!(f, "Write zero"),
|
||||
Error::InvalidInput => write!(f, "Invalid input"),
|
||||
Error::InvalidFileNameLength => write!(f, "Invalid file name length"),
|
||||
Error::UnsupportedFileNameCharacter => write!(f, "Unsupported file name character"),
|
||||
Error::DirectoryIsNotEmpty => write!(f, "Directory is not empty"),
|
||||
Error::NotFound => write!(f, "No such file or directory"),
|
||||
Error::AlreadyExists => write!(f, "File or directory already exists"),
|
||||
Error::CorruptedFileSystem => write!(f, "Corrupted file system"),
|
||||
Error::InvalidUTF8 => write!(f, "File contains invalid UTF-8 characters"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// 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 {
|
||||
Error::<T>::Io(io_error) => io_error.is_interrupted(),
|
||||
_ => false,
|
||||
}
|
||||
}
|
||||
|
||||
fn new_unexpected_eof_error() -> Self {
|
||||
Error::<T>::UnexpectedEof
|
||||
}
|
||||
|
||||
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",
|
||||
)
|
||||
}
|
||||
}
|
||||
160
crates/bffs/src/file.rs
Normal file
160
crates/bffs/src/file.rs
Normal file
@@ -0,0 +1,160 @@
|
||||
use crate::{
|
||||
consts::{FAT32_BAD_CLUSTER, FAT32_END_OF_CHAIN},
|
||||
error::Error,
|
||||
io::{self, IoBase, Read, Seek, Write},
|
||||
Fat32FileSystem, ReadSeek, ReadWriteSeek,
|
||||
};
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct RawFile<'a, T> {
|
||||
pub(crate) fs: &'a Fat32FileSystem<T>,
|
||||
first_cluster: u32,
|
||||
current_cluster: u32,
|
||||
pos: u64,
|
||||
size: Option<u32>,
|
||||
}
|
||||
|
||||
impl<'a, T> RawFile<'a, T> {
|
||||
pub fn new(fs: &'a Fat32FileSystem<T>, start_cluster: u32, size: Option<u32>) -> Self {
|
||||
Self {
|
||||
fs,
|
||||
first_cluster: start_cluster,
|
||||
current_cluster: start_cluster,
|
||||
pos: 0,
|
||||
size,
|
||||
}
|
||||
}
|
||||
pub fn first_cluster(&self) -> u32 {
|
||||
self.first_cluster
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, T: IoBase> IoBase for RawFile<'a, T> {
|
||||
type Error = T::Error;
|
||||
}
|
||||
|
||||
impl<'a, T: ReadSeek> Seek for RawFile<'a, T> {
|
||||
fn seek(&mut self, pos: io::SeekFrom) -> Result<u64, Error<Self::Error>> {
|
||||
let new_pos = match pos {
|
||||
io::SeekFrom::Start(s) => s,
|
||||
io::SeekFrom::Current(c) => (self.pos as i64 + c) as u64,
|
||||
io::SeekFrom::End(e) => {
|
||||
let s = self.size.unwrap_or(0) as i64;
|
||||
(s + e) as u64
|
||||
}
|
||||
};
|
||||
|
||||
// Position changed, compute the new cluster
|
||||
if new_pos != self.pos {
|
||||
let cluster_size = self.fs.cluster_size();
|
||||
|
||||
let cluster_index = new_pos / cluster_size;
|
||||
|
||||
// Find the new cluster from the start
|
||||
let mut c = self.first_cluster;
|
||||
for _ in 0..cluster_index {
|
||||
c = self.fs.get_next_cluster(c)?;
|
||||
if c >= FAT32_BAD_CLUSTER {
|
||||
break;
|
||||
}
|
||||
}
|
||||
self.current_cluster = c;
|
||||
self.pos = new_pos;
|
||||
}
|
||||
Ok(self.pos)
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, T: ReadSeek> Read for RawFile<'a, T> {
|
||||
fn read(&mut self, buf: &mut [u8]) -> Result<usize, Error<Self::Error>> {
|
||||
let max_len = self
|
||||
.size
|
||||
.map(|size| core::cmp::min(buf.len(), size as usize - self.pos as usize))
|
||||
.unwrap_or(buf.len());
|
||||
let mut total_read = 0;
|
||||
let cluster_size = self.fs.cluster_size();
|
||||
|
||||
while total_read < max_len {
|
||||
// Compute how much can be read in the current cluster
|
||||
let offset_in_cluster = self.pos % cluster_size;
|
||||
let remaining_in_cluster = cluster_size - offset_in_cluster;
|
||||
|
||||
let to_read = core::cmp::min(max_len - total_read, remaining_in_cluster as usize);
|
||||
|
||||
// Read from disk
|
||||
let physical_offset =
|
||||
self.fs.cluster_to_offset(self.current_cluster) + offset_in_cluster;
|
||||
self.fs
|
||||
.device
|
||||
.borrow_mut()
|
||||
.seek(io::SeekFrom::Start(physical_offset))?;
|
||||
let read = self
|
||||
.fs
|
||||
.device
|
||||
.borrow_mut()
|
||||
.read(&mut buf[total_read..total_read + to_read])?;
|
||||
|
||||
total_read += read;
|
||||
self.pos += read as u64;
|
||||
|
||||
if read != to_read {
|
||||
// The read call didn't read all bytes to fill buf, we can return early
|
||||
return Ok(total_read);
|
||||
}
|
||||
|
||||
// At the end of a cluster, go to the next one for the next read operation
|
||||
if self.pos.is_multiple_of(cluster_size) {
|
||||
if self.current_cluster >= FAT32_END_OF_CHAIN {
|
||||
break; // End of the fat file
|
||||
}
|
||||
self.current_cluster = self.fs.get_next_cluster(self.current_cluster)?;
|
||||
}
|
||||
}
|
||||
|
||||
Ok(total_read)
|
||||
}
|
||||
}
|
||||
impl<'a, T: ReadWriteSeek> Write for RawFile<'a, T> {
|
||||
fn write(&mut self, _buf: &[u8]) -> Result<usize, Error<Self::Error>> {
|
||||
todo!()
|
||||
}
|
||||
|
||||
fn flush(&mut self) -> Result<(), Error<Self::Error>> {
|
||||
todo!()
|
||||
}
|
||||
}
|
||||
|
||||
#[allow(unused)]
|
||||
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;
|
||||
}
|
||||
impl<'a, T: ReadSeek> Seek for File<'a, T> {
|
||||
fn seek(&mut self, pos: io::SeekFrom) -> Result<u64, Error<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>> {
|
||||
self.raw.read(buf)
|
||||
}
|
||||
}
|
||||
impl<'a, T: ReadWriteSeek> Write for File<'a, T> {
|
||||
fn write(&mut self, buf: &[u8]) -> Result<usize, Error<Self::Error>> {
|
||||
self.raw.write(buf)
|
||||
}
|
||||
|
||||
fn flush(&mut self) -> Result<(), Error<Self::Error>> {
|
||||
self.raw.flush()
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, T> File<'a, T> {
|
||||
pub fn new(raw: RawFile<'a, T>, fs: &'a Fat32FileSystem<T>) -> Self {
|
||||
Self { raw, fs }
|
||||
}
|
||||
}
|
||||
293
crates/bffs/src/io.rs
Normal file
293
crates/bffs/src/io.rs
Normal file
@@ -0,0 +1,293 @@
|
||||
use crate::error::{Error, IoError};
|
||||
|
||||
#[cfg(feature = "alloc")]
|
||||
use alloc::string::String;
|
||||
#[cfg(feature = "alloc")]
|
||||
use alloc::vec::Vec;
|
||||
|
||||
/// Provides IO error as an associated type.
|
||||
///
|
||||
/// Must be implemented for all types that also implement at least one of the following traits: `Read`, `Write`,
|
||||
/// `Seek`.
|
||||
pub trait IoBase {
|
||||
/// Type of errors returned by input/output operations.
|
||||
type Error: IoError;
|
||||
}
|
||||
|
||||
/// The `Read` trait allows for reading bytes from a source.
|
||||
///
|
||||
/// It is based on the `std::io::Read` trait.
|
||||
pub trait Read: IoBase {
|
||||
/// Pull some bytes from this source into the specified buffer, returning how many bytes were read.
|
||||
///
|
||||
/// This function does not provide any guarantees about whether it blocks waiting for data, but if an object needs
|
||||
/// to block for a read and cannot, it will typically signal this via an Err return value.
|
||||
///
|
||||
/// If the return value of this method is `Ok(n)`, then it must be guaranteed that `0 <= n <= buf.len()`. A nonzero
|
||||
/// `n` value indicates that the buffer buf has been filled in with n bytes of data from this source. If `n` is
|
||||
/// `0`, then it can indicate one of two scenarios:
|
||||
///
|
||||
/// 1. This reader has reached its "end of file" and will likely no longer be able to produce bytes. Note that this
|
||||
/// does not mean that the reader will always no longer be able to produce bytes.
|
||||
/// 2. The buffer specified was 0 bytes in length.
|
||||
///
|
||||
/// It is not an error if the returned value `n` is smaller than the buffer size, even when the reader is not at
|
||||
/// the end of the stream yet. This may happen for example because fewer bytes are actually available right now
|
||||
/// (e. g. being close to end-of-file) or because `read()` was interrupted by a signal.
|
||||
///
|
||||
/// # Errors
|
||||
///
|
||||
/// If this function encounters any form of I/O or other error, an error will be returned. If an error is returned
|
||||
/// 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>>;
|
||||
|
||||
/// Read the exact number of bytes required to fill `buf`.
|
||||
///
|
||||
/// This function reads as many bytes as necessary to completely fill the specified buffer `buf`.
|
||||
///
|
||||
/// # Errors
|
||||
///
|
||||
/// If this function encounters an error for which `IoError::is_interrupted` returns true then the error is ignored
|
||||
/// and the operation will continue.
|
||||
///
|
||||
/// If this function encounters an end of file before completely filling the buffer, it returns an error
|
||||
/// instantiated by a call to `IoError::new_unexpected_eof_error`. The contents of `buf` are unspecified in this
|
||||
/// case.
|
||||
///
|
||||
/// 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>> {
|
||||
while !buf.is_empty() {
|
||||
match self.read(buf) {
|
||||
Ok(0) => break,
|
||||
Ok(n) => {
|
||||
let tmp = buf;
|
||||
buf = &mut tmp[n..];
|
||||
}
|
||||
Err(ref e) if e.is_interrupted() => {}
|
||||
Err(e) => return Err(e),
|
||||
}
|
||||
}
|
||||
if buf.is_empty() {
|
||||
Ok(())
|
||||
} else {
|
||||
Err(Error::<Self::Error>::new_unexpected_eof_error())
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "alloc")]
|
||||
fn read_to_end(&mut self, buf: &mut Vec<u8>) -> Result<usize, Error<Self::Error>> {
|
||||
const CHUNK_SIZE: usize = 32;
|
||||
let start_len = buf.len();
|
||||
loop {
|
||||
let mut chunk_buf = [0; CHUNK_SIZE];
|
||||
let read = self.read(&mut chunk_buf)?;
|
||||
buf.extend_from_slice(&chunk_buf[..read]);
|
||||
|
||||
if read == 0 {
|
||||
return Ok(buf.len() - start_len);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "alloc")]
|
||||
fn read_to_string(&mut self, buf: &mut String) -> Result<usize, Error<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)
|
||||
} else {
|
||||
Ok(read)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// The `Write` trait allows for writing bytes into the sink.
|
||||
///
|
||||
/// It is based on the `std::io::Write` trait.
|
||||
pub trait Write: IoBase {
|
||||
/// Write a buffer into this writer, returning how many bytes were written.
|
||||
///
|
||||
/// # Errors
|
||||
///
|
||||
/// 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>>;
|
||||
|
||||
/// Attempts to write an entire buffer into this writer.
|
||||
///
|
||||
/// This method will continuously call `write` until there is no more data to be written or an error is returned.
|
||||
/// Errors for which `IoError::is_interrupted` method returns true are being skipped. This method will not return
|
||||
/// until the entire buffer has been successfully written or such an error occurs.
|
||||
/// If `write` returns 0 before the entire buffer has been written this method will return an error instantiated by
|
||||
/// a call to `IoError::new_write_zero_error`.
|
||||
///
|
||||
/// # Errors
|
||||
///
|
||||
/// 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>> {
|
||||
while !buf.is_empty() {
|
||||
match self.write(buf) {
|
||||
Ok(0) => {
|
||||
return Err(Error::<Self::Error>::new_write_zero_error());
|
||||
}
|
||||
Ok(n) => buf = &buf[n..],
|
||||
Err(ref e) if e.is_interrupted() => {}
|
||||
Err(e) => return Err(e),
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Flush this output stream, ensuring that all intermediately buffered contents reach their destination.
|
||||
///
|
||||
/// # 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>>;
|
||||
}
|
||||
|
||||
/// Enumeration of possible methods to seek within an I/O object.
|
||||
///
|
||||
/// It is based on the `std::io::SeekFrom` enum.
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
pub enum SeekFrom {
|
||||
/// Sets the offset to the provided number of bytes.
|
||||
Start(u64),
|
||||
/// Sets the offset to the size of this object plus the specified number of bytes.
|
||||
End(i64),
|
||||
/// Sets the offset to the current position plus the specified number of bytes.
|
||||
Current(i64),
|
||||
}
|
||||
|
||||
/// The `Seek` trait provides a cursor which can be moved within a stream of bytes.
|
||||
///
|
||||
/// It is based on the `std::io::Seek` trait.
|
||||
pub trait Seek: IoBase {
|
||||
/// Seek to an offset, in bytes, in a stream.
|
||||
///
|
||||
/// A seek beyond the end of a stream or to a negative position is not allowed.
|
||||
///
|
||||
/// If the seek operation completed successfully, this method returns the new position from the start of the
|
||||
/// stream. That position can be used later with `SeekFrom::Start`.
|
||||
///
|
||||
/// # Errors
|
||||
/// Seeking to a negative offset is considered an error.
|
||||
fn seek(&mut self, pos: SeekFrom) -> Result<u64, Error<Self::Error>>;
|
||||
}
|
||||
|
||||
#[cfg(all(feature = "std", not(target_arch = "riscv64")))]
|
||||
impl From<SeekFrom> for std::io::SeekFrom {
|
||||
fn from(from: SeekFrom) -> Self {
|
||||
match from {
|
||||
SeekFrom::Start(n) => std::io::SeekFrom::Start(n),
|
||||
SeekFrom::End(n) => std::io::SeekFrom::End(n),
|
||||
SeekFrom::Current(n) => std::io::SeekFrom::Current(n),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(all(feature = "std", not(target_arch = "riscv64")))]
|
||||
impl From<std::io::SeekFrom> for SeekFrom {
|
||||
fn from(from: std::io::SeekFrom) -> Self {
|
||||
match from {
|
||||
std::io::SeekFrom::Start(n) => SeekFrom::Start(n),
|
||||
std::io::SeekFrom::End(n) => SeekFrom::End(n),
|
||||
std::io::SeekFrom::Current(n) => SeekFrom::Current(n),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(all(feature = "std", not(target_arch = "riscv64")))]
|
||||
impl IoBase for std::fs::File {
|
||||
type Error = std::io::Error;
|
||||
}
|
||||
|
||||
#[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>> {
|
||||
self.read(buf).map_err(Error::Io)
|
||||
}
|
||||
fn read_exact(&mut self, buf: &mut [u8]) -> Result<(), Error<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>> {
|
||||
self.write(buf).map_err(Error::Io)
|
||||
}
|
||||
|
||||
fn write_all(&mut self, buf: &[u8]) -> Result<(), Error<Self::Error>> {
|
||||
self.write_all(buf).map_err(Error::Io)
|
||||
}
|
||||
|
||||
fn flush(&mut self) -> Result<(), Error<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>> {
|
||||
self.seek(pos.into()).map_err(Error::Io)
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) 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>>;
|
||||
}
|
||||
|
||||
impl<T: Read> ReadLeExt for T {
|
||||
type Error = <Self as IoBase>::Error;
|
||||
|
||||
fn read_u8(&mut self) -> Result<u8, Error<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>> {
|
||||
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>> {
|
||||
let mut buf = [0_u8; 4];
|
||||
self.read_exact(&mut buf)?;
|
||||
Ok(u32::from_le_bytes(buf))
|
||||
}
|
||||
}
|
||||
|
||||
#[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>>;
|
||||
}
|
||||
|
||||
impl<T: Write> WriteLeExt for T {
|
||||
type Error = <Self as IoBase>::Error;
|
||||
|
||||
fn write_u8(&mut self, n: u8) -> Result<(), Error<Self::Error>> {
|
||||
self.write_all(&[n])
|
||||
}
|
||||
|
||||
fn write_u16_le(&mut self, n: u16) -> Result<(), Error<Self::Error>> {
|
||||
self.write_all(&n.to_le_bytes())
|
||||
}
|
||||
|
||||
fn write_u32_le(&mut self, n: u32) -> Result<(), Error<Self::Error>> {
|
||||
self.write_all(&n.to_le_bytes())
|
||||
}
|
||||
}
|
||||
157
crates/bffs/src/lib.rs
Normal file
157
crates/bffs/src/lib.rs
Normal file
@@ -0,0 +1,157 @@
|
||||
#![feature(iterator_try_collect, iter_order_by)]
|
||||
#![cfg_attr(any(not(feature = "std"), target_arch = "riscv64"), no_std)]
|
||||
|
||||
use core::cell::RefCell;
|
||||
use core::fmt::Display;
|
||||
|
||||
use crate::{
|
||||
boot_sector::Fat32BootSector,
|
||||
consts::FAT32_CLUSTER_MASK,
|
||||
dir::Dir,
|
||||
entry::{DirEntry, FatEntry},
|
||||
error::Error,
|
||||
file::{File, RawFile},
|
||||
io::{Read, ReadLeExt, Seek, Write},
|
||||
path::Path,
|
||||
};
|
||||
|
||||
#[cfg(feature = "alloc")]
|
||||
extern crate alloc;
|
||||
|
||||
pub mod boot_sector;
|
||||
pub mod consts;
|
||||
pub mod dir;
|
||||
pub mod entry;
|
||||
pub mod error;
|
||||
pub mod file;
|
||||
pub mod io;
|
||||
pub mod path;
|
||||
|
||||
pub trait ReadSeek: Read + Seek {}
|
||||
impl<T: Read + Seek> ReadSeek for T {}
|
||||
pub trait WriteSeek: Write + Seek {}
|
||||
impl<T: Write + Seek> WriteSeek for T {}
|
||||
pub trait ReadWriteSeek: Read + Write + Seek {}
|
||||
impl<T: Read + Write + Seek> ReadWriteSeek for T {}
|
||||
|
||||
#[derive(Debug, Clone, Copy)]
|
||||
pub struct Fat32FsInfo {
|
||||
pub lead_signature: u32,
|
||||
pub reserved1: [u8; 480],
|
||||
pub struct_signature: u32,
|
||||
pub free_count: u32,
|
||||
pub next_free: u32,
|
||||
pub reserved2: [u8; 12],
|
||||
pub trail_signature: u32,
|
||||
}
|
||||
|
||||
impl Fat32FsInfo {
|
||||
pub fn deserialize<T: Read>(disk: &mut T) -> Result<Self, Error<T::Error>> {
|
||||
let lead_signature = disk.read_u32_le()?;
|
||||
let mut reserved1 = [0u8; _];
|
||||
disk.read_exact(&mut reserved1)?;
|
||||
let struct_signature = disk.read_u32_le()?;
|
||||
let free_count = disk.read_u32_le()?;
|
||||
let next_free = disk.read_u32_le()?;
|
||||
let mut reserved2 = [0u8; _];
|
||||
disk.read_exact(&mut reserved2)?;
|
||||
let trail_signature = disk.read_u32_le()?;
|
||||
Ok(Self {
|
||||
lead_signature,
|
||||
reserved1,
|
||||
struct_signature,
|
||||
free_count,
|
||||
next_free,
|
||||
reserved2,
|
||||
trail_signature,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct Fat32FileSystem<T> {
|
||||
device: RefCell<T>,
|
||||
pub boot_sector: Fat32BootSector,
|
||||
}
|
||||
|
||||
impl<T> Display for Fat32FileSystem<T> {
|
||||
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
|
||||
self.boot_sector.fmt(f)
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: ReadSeek> Fat32FileSystem<T> {
|
||||
pub fn new(mut device: T) -> Result<Self, Error<T::Error>> {
|
||||
device.seek(io::SeekFrom::Start(0))?;
|
||||
|
||||
let boot_sector = Fat32BootSector::deserialize(&mut device)?;
|
||||
Ok(Self {
|
||||
device: device.into(),
|
||||
boot_sector,
|
||||
})
|
||||
}
|
||||
/// Get the next cluster from the current one
|
||||
fn get_next_cluster(&self, current_cluster: u32) -> Result<u32, Error<T::Error>> {
|
||||
let fat_offset =
|
||||
self.fat_start_offset() + (current_cluster as u64 * size_of::<FatEntry>() as u64);
|
||||
self.device
|
||||
.borrow_mut()
|
||||
.seek(io::SeekFrom::Start(fat_offset))?;
|
||||
|
||||
let next = self.device.borrow_mut().read_u32_le()? & FAT32_CLUSTER_MASK;
|
||||
Ok(next)
|
||||
}
|
||||
}
|
||||
impl<T> Fat32FileSystem<T> {
|
||||
/// Start offset of the FAT
|
||||
pub fn fat_start_offset(&self) -> u64 {
|
||||
(self.boot_sector.reserved_sector_count as u64) * (self.boot_sector.bytes_per_sector as u64)
|
||||
}
|
||||
/// Start offset for data
|
||||
fn data_start_offset(&self) -> u64 {
|
||||
let fat_size = self.boot_sector.fat_size_32 as u64;
|
||||
let fats = (self.boot_sector.num_fats as u64) * fat_size;
|
||||
|
||||
fats * self.boot_sector.bytes_per_sector as u64 + self.fat_start_offset()
|
||||
}
|
||||
|
||||
/// Convert a cluster number to an offset
|
||||
fn cluster_to_offset(&self, cluster: u32) -> u64 {
|
||||
let cluster_offset = (cluster.saturating_sub(2)) as u64
|
||||
* self.boot_sector.sectors_per_cluster as u64
|
||||
* self.boot_sector.bytes_per_sector as u64;
|
||||
|
||||
self.data_start_offset() + cluster_offset
|
||||
}
|
||||
|
||||
fn cluster_size(&self) -> u64 {
|
||||
(self.boot_sector.sectors_per_cluster as u64) * (self.boot_sector.bytes_per_sector as u64)
|
||||
}
|
||||
|
||||
pub fn root_directory(&self) -> Dir<'_, T> {
|
||||
Dir::new(
|
||||
RawFile::new(self, self.boot_sector.root_cluster, None),
|
||||
self,
|
||||
)
|
||||
}
|
||||
}
|
||||
impl<T: ReadSeek> Fat32FileSystem<T> {
|
||||
pub fn open_entry<'b, P: Into<Path<'b>>>(
|
||||
&self,
|
||||
path: P,
|
||||
) -> Result<DirEntry<'_, T>, Error<T::Error>> {
|
||||
let path = path.into().as_str().trim_start_matches("/");
|
||||
self.root_directory().open_entry(path)
|
||||
}
|
||||
pub fn open_dir<'b, P: Into<Path<'b>>>(&self, path: P) -> Result<Dir<'_, T>, Error<T::Error>> {
|
||||
let path = path.into().as_str().trim_start_matches("/");
|
||||
self.root_directory().open_dir(path)
|
||||
}
|
||||
pub fn open_file<'b, P: Into<Path<'b>>>(
|
||||
&self,
|
||||
path: P,
|
||||
) -> Result<File<'_, T>, Error<T::Error>> {
|
||||
let path = path.into().as_str().trim_start_matches("/");
|
||||
self.root_directory().open_file(path)
|
||||
}
|
||||
}
|
||||
34
crates/bffs/src/main.rs
Normal file
34
crates/bffs/src/main.rs
Normal file
@@ -0,0 +1,34 @@
|
||||
#![cfg_attr(any(not(feature = "std"), target_arch = "riscv64"), no_std)]
|
||||
|
||||
#[cfg(all(feature = "std", not(target_arch = "riscv64")))]
|
||||
pub fn main() {
|
||||
use bffs::{dir::Dir, io::Read, Fat32FileSystem, ReadSeek};
|
||||
let file = std::fs::File::open("disk.img").unwrap();
|
||||
let fs = Fat32FileSystem::new(file).unwrap();
|
||||
// println!("{:#?}", fs);
|
||||
|
||||
// walk(fs.root_directory());
|
||||
|
||||
let mut f = fs.open_file("/usr/bin/test_pic").unwrap();
|
||||
let mut content = Vec::new();
|
||||
f.read_to_end(&mut content).unwrap();
|
||||
println!("file content len: {}", content.len());
|
||||
|
||||
pub fn walk<T: ReadSeek>(dir: Dir<'_, T>) {
|
||||
for entry in dir.iter() {
|
||||
let entry = entry.unwrap();
|
||||
println!("file {}", entry.name().unwrap());
|
||||
if entry.is_dir() {
|
||||
walk(entry.to_dir())
|
||||
} else {
|
||||
let mut file = entry.to_file();
|
||||
let mut content = String::new();
|
||||
file.read_to_string(&mut content).unwrap();
|
||||
println!("file content {}", content);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(any(not(feature = "std"), target_arch = "riscv64"))]
|
||||
pub fn main() {}
|
||||
33
crates/bffs/src/path.rs
Normal file
33
crates/bffs/src/path.rs
Normal file
@@ -0,0 +1,33 @@
|
||||
pub struct Path<'a> {
|
||||
inner: &'a str,
|
||||
}
|
||||
|
||||
impl<'a> From<&'a str> for Path<'a> {
|
||||
fn from(value: &'a str) -> Self {
|
||||
Self { inner: value }
|
||||
}
|
||||
}
|
||||
impl<'a> AsRef<str> for Path<'a> {
|
||||
fn as_ref(&self) -> &str {
|
||||
self.inner
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> Path<'a> {
|
||||
pub fn split_path(&self) -> (&'a str, Option<Path<'a>>) {
|
||||
if let Some((start, end)) = self.inner.split_once("/") {
|
||||
(start, Some(end.into()))
|
||||
} else {
|
||||
(self.inner, None)
|
||||
}
|
||||
}
|
||||
pub fn as_str(&self) -> &'a str {
|
||||
self.inner
|
||||
}
|
||||
pub fn is_absolute(&self) -> bool {
|
||||
self.inner.starts_with("/")
|
||||
}
|
||||
pub fn is_relative(&self) -> bool {
|
||||
!self.is_absolute()
|
||||
}
|
||||
}
|
||||
@@ -5,4 +5,4 @@ edition = "2024"
|
||||
|
||||
[dependencies]
|
||||
os-std-macros = { path = "../os-std-macros" }
|
||||
shared = { path = "../shared" }
|
||||
shared = { path = "../shared", features = ["user"] }
|
||||
|
||||
@@ -4,6 +4,7 @@ extern crate alloc;
|
||||
|
||||
pub mod prelude;
|
||||
|
||||
pub use shared::fs;
|
||||
pub use shared::syscall;
|
||||
|
||||
#[macro_export]
|
||||
|
||||
@@ -4,3 +4,8 @@ version = "0.1.0"
|
||||
edition = "2024"
|
||||
|
||||
[dependencies]
|
||||
bffs = { path = "../bffs" }
|
||||
|
||||
[features]
|
||||
kernel = []
|
||||
user = []
|
||||
|
||||
9
crates/shared/src/fs.rs
Normal file
9
crates/shared/src/fs.rs
Normal file
@@ -0,0 +1,9 @@
|
||||
pub struct File {
|
||||
fd: u64,
|
||||
}
|
||||
|
||||
impl File {
|
||||
pub unsafe fn new(fd: u64) -> Self {
|
||||
Self { fd }
|
||||
}
|
||||
}
|
||||
@@ -1,3 +1,4 @@
|
||||
#![no_std]
|
||||
|
||||
pub mod fs;
|
||||
pub mod syscall;
|
||||
|
||||
@@ -1,7 +1,10 @@
|
||||
use core::{alloc::Layout, time::Duration};
|
||||
|
||||
use bffs::path::Path;
|
||||
|
||||
#[repr(u64)]
|
||||
pub enum SysCall {
|
||||
Open = 2,
|
||||
Alloc = 40,
|
||||
Dealloc = 41,
|
||||
Exit = 60,
|
||||
@@ -14,6 +17,7 @@ pub enum SysCall {
|
||||
impl From<u64> for SysCall {
|
||||
fn from(value: u64) -> Self {
|
||||
match value {
|
||||
2 => SysCall::Open,
|
||||
40 => SysCall::Alloc,
|
||||
41 => SysCall::Dealloc,
|
||||
60 => SysCall::Exit,
|
||||
@@ -124,3 +128,12 @@ 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<'a, P: Into<Path<'a>>>(path: P) -> u64 {
|
||||
unsafe {
|
||||
let path_str = path.into().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
|
||||
}
|
||||
}
|
||||
|
||||
6
justfile
6
justfile
@@ -13,6 +13,7 @@ sync_filesystem:
|
||||
|
||||
build_user_prog prog:
|
||||
RUSTFLAGS="-C relocation-model=pic -C link-arg=-Tuser.ld -C link-arg=-pie" cargo b {{ cargo_flags }} --package {{ prog }}
|
||||
riscv64-elf-strip {{ bin_path / prog }}
|
||||
cp {{ bin_path / prog }} {{ "mnt/usr/bin" / prog }}
|
||||
# riscv64-elf-objcopy -O binary {{ bin_path / prog }} {{ "mnt/usr/bin" / prog }}
|
||||
|
||||
@@ -30,6 +31,11 @@ map_dir dir recipe:
|
||||
|
||||
qemu := "qemu-system-riscv64 -machine virt -device bochs-display -bios none -m 512M -device loader,file=disk.img,addr=0x90000000"
|
||||
|
||||
perf: build
|
||||
{{ qemu }} -perfmap -kernel {{ bin_path / "kernel-rust" }}&
|
||||
perf record --output=/tmp/perf.data --call-graph=dwarf -F 999 -p $(pidof qemu-system-riscv64) -- sleep 20; exit 0
|
||||
cd /tmp && hotspot perf.data
|
||||
|
||||
gdb: build
|
||||
{{ qemu }} -s -S -kernel {{ bin_path / "kernel-rust" }}&
|
||||
gf2
|
||||
|
||||
23
src/fs.rs
23
src/fs.rs
@@ -2,12 +2,17 @@
|
||||
//!
|
||||
//! 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 core::{
|
||||
cell::{LazyCell, UnsafeCell},
|
||||
ops::Deref,
|
||||
};
|
||||
|
||||
use bffs::{
|
||||
io::{IoBase, Read, Seek},
|
||||
path::Path,
|
||||
Fat32FileSystem,
|
||||
};
|
||||
use hashbrown::HashMap;
|
||||
|
||||
const DISK_ADDR: *const u8 = 0x9000_0000 as *const _;
|
||||
|
||||
@@ -79,10 +84,20 @@ impl Read for Disk {
|
||||
return Ok(0);
|
||||
}
|
||||
let size = usize::min(buf.len(), (self.size - self.pos) as usize);
|
||||
(0..size).for_each(|i| {
|
||||
buf[i] = unsafe { *DISK_ADDR.byte_add(i + self.pos as usize) };
|
||||
});
|
||||
unsafe {
|
||||
core::ptr::copy_nonoverlapping(
|
||||
DISK_ADDR.byte_add(self.pos as usize),
|
||||
buf.as_mut_ptr(),
|
||||
size,
|
||||
);
|
||||
}
|
||||
self.pos += size as u64;
|
||||
Ok(size)
|
||||
}
|
||||
}
|
||||
|
||||
pub struct KernelFDTable(LazyCell<HashMap<u64, Path<'static>>>);
|
||||
|
||||
unsafe impl Sync for KernelFDTable {}
|
||||
|
||||
pub static KERNEL_FILE_DESCRIPTOR_TABLE: KernelFDTable = KernelFDTable(LazyCell::new(HashMap::new));
|
||||
|
||||
@@ -14,7 +14,7 @@ use crate::{
|
||||
read_csr,
|
||||
riscv::disable_interrupt,
|
||||
scheduler::scheduler_without_ret,
|
||||
set_csr,
|
||||
set_csr, syscall,
|
||||
time::{setup_next_timer_interrupt, IRQ_M_TIMER},
|
||||
write_csr,
|
||||
};
|
||||
@@ -57,7 +57,6 @@ unsafe extern "C" fn machine_trap_handler(
|
||||
c if c == EextensionID::Time as usize => match fid {
|
||||
c if c == TimerFunctionID::SetTimer as usize => {
|
||||
clear_csr!(mip, 1 << 5);
|
||||
// setup_next_timer_interrupt();
|
||||
}
|
||||
_ => {}
|
||||
},
|
||||
@@ -122,16 +121,21 @@ unsafe extern "C" fn supervisor_trap_handler(
|
||||
let a3: u64 = unsafe { (*interrupt_state).a[3] };
|
||||
let syscall: SysCall = syscall_u64.into();
|
||||
match syscall {
|
||||
SysCall::Open => {
|
||||
let path = unsafe { str::from_raw_parts(a1 as *const u8, a2 as usize) };
|
||||
let fd = syscall::open(path, false);
|
||||
unsafe { (*interrupt_state).a[0] = fd.unwrap() };
|
||||
}
|
||||
SysCall::Alloc => {
|
||||
let layout = Layout::from_size_align(a1 as usize, a2 as usize).unwrap();
|
||||
// Allocate memory and put the pointer in a0
|
||||
unsafe { (*interrupt_state).a[0] = alloc::alloc::alloc(layout) as u64 };
|
||||
unsafe { (*interrupt_state).a[0] = syscall::alloc(layout) as u64 };
|
||||
}
|
||||
SysCall::Dealloc => {
|
||||
let ptr = a1 as *mut u8;
|
||||
let layout = Layout::from_size_align(a2 as usize, a3 as usize).unwrap();
|
||||
// Free memory
|
||||
unsafe { alloc::alloc::dealloc(ptr, layout) };
|
||||
unsafe { syscall::dealloc(ptr, layout) };
|
||||
}
|
||||
SysCall::Exit => exit_process(&mut interrupt_state),
|
||||
SysCall::NanoSleep => sleep(Duration::new(a1, a2 as u32), &mut interrupt_state),
|
||||
|
||||
@@ -15,6 +15,7 @@
|
||||
ptr_metadata
|
||||
)]
|
||||
|
||||
use alloc::boxed::Box;
|
||||
use embedded_alloc::LlffHeap as Heap;
|
||||
use log::info;
|
||||
|
||||
@@ -38,6 +39,7 @@ mod panic_handler;
|
||||
mod process;
|
||||
mod riscv;
|
||||
mod scheduler;
|
||||
mod syscall;
|
||||
mod time;
|
||||
mod uart;
|
||||
mod user;
|
||||
@@ -63,8 +65,8 @@ pub extern "C" fn supervisor_mode_entry() {
|
||||
info!("Hello World !");
|
||||
unsafe { Vga::draw_string(10, 10, "Hello World !", Color::WHITE, Color::BLACK) };
|
||||
|
||||
create_process(&test, "proc1");
|
||||
create_process(&proc2, "proc2");
|
||||
create_process(Box::new(test), "proc1");
|
||||
create_process(Box::new(proc2), "proc2");
|
||||
|
||||
create_process_from_file("/usr/bin/test_pic");
|
||||
|
||||
|
||||
@@ -12,6 +12,7 @@ use core::time::Duration;
|
||||
use alloc::{boxed::Box, format, string::String, vec::Vec};
|
||||
use bffs::{io::Read, path::Path};
|
||||
use goblin::elf::reloc::R_RISCV_RELATIVE;
|
||||
use hashbrown::HashMap;
|
||||
use shared::syscall::exit;
|
||||
|
||||
use crate::{
|
||||
@@ -82,13 +83,15 @@ pub struct Process {
|
||||
/// Current state of the process.
|
||||
pub state: ProcessState,
|
||||
/// Optional entry point for the process code.
|
||||
pub entry: Option<&'static dyn Fn()>,
|
||||
pub entry: Option<Box<dyn Fn()>>,
|
||||
/// Wake time for sleeping processes.
|
||||
pub wake_time: Duration,
|
||||
/// Saved execution context.
|
||||
pub ctx: ExecutionContext,
|
||||
/// Process stack.
|
||||
pub stack: [u64; STACK_SIZE],
|
||||
/// File descriptor table.
|
||||
pub fd_table: HashMap<u64, Path<'static>>
|
||||
}
|
||||
|
||||
impl core::fmt::Debug for Process {
|
||||
@@ -130,13 +133,19 @@ pub fn create_process_from_file<'a, T: Into<Path<'a>>>(path: T) -> i64 {
|
||||
|
||||
// Open and read the binary file
|
||||
let mut bin = FILE_SYSTEM.open_file(path).unwrap();
|
||||
println!("Creating process");
|
||||
let mut content: Vec<u8> = Vec::new();
|
||||
bin.read_to_end(&mut content).unwrap();
|
||||
|
||||
println!("Loading binary at address: {:x?}", content.as_ptr());
|
||||
println!(
|
||||
"Loading binary at address: {:x?}, length: {}",
|
||||
content.as_ptr(),
|
||||
content.len()
|
||||
);
|
||||
|
||||
// If ELF, use goblin to load PT_LOAD segments and apply relocations
|
||||
if let Ok(gelf) = goblin::elf::Elf::parse(&content) {
|
||||
println!("Parsed");
|
||||
// Determine memory bounds from program headers
|
||||
let mut min_vaddr = u64::MAX;
|
||||
let mut max_vaddr = 0u64;
|
||||
@@ -179,6 +188,7 @@ pub fn create_process_from_file<'a, T: Into<Path<'a>>>(path: T) -> i64 {
|
||||
}
|
||||
}
|
||||
}
|
||||
println!("Copied");
|
||||
|
||||
// Apply relocations using our parser (handles RELA entries)
|
||||
for rela in gelf.dynrelas.iter() {
|
||||
@@ -193,15 +203,16 @@ pub fn create_process_from_file<'a, T: Into<Path<'a>>>(path: T) -> i64 {
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
println!("Relocated");
|
||||
|
||||
// Entry point
|
||||
let entry_va = gelf.entry;
|
||||
let entry_addr = unsafe { base.add((entry_va - min_vaddr) as usize) } as *const u8;
|
||||
let entry_fn =
|
||||
unsafe { core::mem::transmute::<*const u8, extern "C" fn()>(entry_addr) };
|
||||
let wrapper = Box::leak(Box::new(move || {
|
||||
let wrapper = Box::new(move || {
|
||||
entry_fn();
|
||||
}));
|
||||
});
|
||||
println!("Program loaded at : {:x?}", entry_addr);
|
||||
return create_process(wrapper, name);
|
||||
}
|
||||
@@ -210,9 +221,9 @@ pub fn create_process_from_file<'a, T: Into<Path<'a>>>(path: T) -> i64 {
|
||||
// Fallback: treat the file as a raw binary blob and execute in-place
|
||||
let entry_point =
|
||||
unsafe { core::mem::transmute::<*const u8, extern "C" fn()>(Vec::leak(content).as_ptr()) };
|
||||
let wrapper = Box::leak(Box::new(move || {
|
||||
let wrapper = Box::new(move || {
|
||||
entry_point();
|
||||
}));
|
||||
});
|
||||
|
||||
create_process(wrapper, name)
|
||||
}
|
||||
@@ -236,7 +247,7 @@ pub fn create_process_from_file<'a, T: Into<Path<'a>>>(path: T) -> i64 {
|
||||
///
|
||||
/// The provided `code` function will be executed when the process is first
|
||||
/// scheduled. Returns the new PID, or -1 if the process table is full.
|
||||
pub fn create_process<T: Into<String>, F: Fn()>(code: &'static F, name: T) -> i64 {
|
||||
pub fn create_process<T: Into<String>, F: Fn() + 'static>(code: Box<F>, name: T) -> i64 {
|
||||
// Search for a free slot in the process table
|
||||
let mut next_pid = 0;
|
||||
while next_pid < PROCESS_COUNT && unsafe { PROCESS_TABLE[next_pid].state != ProcessState::Dead }
|
||||
@@ -262,7 +273,7 @@ pub fn create_process<T: Into<String>, F: Fn()>(code: &'static F, name: T) -> i6
|
||||
|
||||
// Configure execution context
|
||||
// a0 contains the pointer to the function to execute
|
||||
process.ctx.a[0] = process.entry.as_ref().unwrap_unchecked() as *const &dyn Fn() as u64;
|
||||
process.ctx.a[0] = &raw const *process.entry.as_ref().unwrap_unchecked() as u64;
|
||||
|
||||
// mepc points to process_launcher which will call the function
|
||||
process.ctx.mepc = process_launcher as *const _;
|
||||
@@ -295,7 +306,7 @@ pub fn create_process<T: Into<String>, F: Fn()>(code: &'static F, name: T) -> i6
|
||||
/// This function is installed into the process `mepc` so that when the new
|
||||
/// process is scheduled it will run this launcher which calls the user
|
||||
/// function and ensures the process exits cleanly.
|
||||
extern "C" fn process_launcher(code: *const &dyn Fn()) {
|
||||
extern "C" fn process_launcher(code: *const Box<dyn Fn()>) {
|
||||
// SAFETY: The code pointer was initialized in create_process
|
||||
// and points to a valid function.
|
||||
unsafe { (*code)() };
|
||||
|
||||
@@ -105,7 +105,7 @@ macro_rules! mret {
|
||||
}
|
||||
|
||||
#[unsafe(naked)]
|
||||
pub extern "C" fn exit_qemu() {
|
||||
pub extern "C" fn exit_qemu() -> ! {
|
||||
naked_asm!(
|
||||
"
|
||||
li a0, 0x100000
|
||||
|
||||
@@ -5,7 +5,8 @@
|
||||
//! and a simple round-robin scheduler used by the kernel.
|
||||
use core::{arch::riscv64::wfi, array, cell::LazyCell, time::Duration};
|
||||
|
||||
use alloc::string::String;
|
||||
use alloc::{boxed::Box, string::String};
|
||||
use hashbrown::HashMap;
|
||||
use log::info;
|
||||
|
||||
use crate::{
|
||||
@@ -43,6 +44,7 @@ pub static mut PROCESS_TABLE: LazyCell<[Process; PROCESS_COUNT]> = LazyCell::new
|
||||
},
|
||||
stack: [0; _],
|
||||
entry: None,
|
||||
fd_table: HashMap::new()
|
||||
})
|
||||
});
|
||||
|
||||
@@ -71,7 +73,7 @@ pub fn scheduler_init() {
|
||||
}
|
||||
}
|
||||
|
||||
create_process(&idle, "idle");
|
||||
create_process(Box::new(idle), "idle");
|
||||
unsafe {
|
||||
PROCESS_TABLE[0].state = ProcessState::Active;
|
||||
}
|
||||
|
||||
27
src/syscall.rs
Normal file
27
src/syscall.rs
Normal file
@@ -0,0 +1,27 @@
|
||||
use core::alloc::Layout;
|
||||
|
||||
use bffs::{error::Error, path::Path};
|
||||
|
||||
use crate::fs::{Disk, FILE_SYSTEM};
|
||||
|
||||
pub unsafe fn alloc(layout: Layout) -> *mut u8 {
|
||||
unsafe { alloc::alloc::alloc(layout) }
|
||||
}
|
||||
|
||||
pub unsafe fn dealloc(ptr: *mut u8, layout: core::alloc::Layout) {
|
||||
unsafe { alloc::alloc::dealloc(ptr, layout) }
|
||||
}
|
||||
|
||||
pub fn open<'a, P: Into<Path<'a>>>(
|
||||
path: P,
|
||||
in_kernel: bool,
|
||||
) -> Result<u64, Error<<Disk as bffs::io::IoBase>::Error>> {
|
||||
let path = path.into();
|
||||
let file = match path.split_path() {
|
||||
("dev", path) => {
|
||||
unimplemented!()
|
||||
}
|
||||
_ => FILE_SYSTEM.open_file(path)?,
|
||||
};
|
||||
Ok(42)
|
||||
}
|
||||
@@ -6,8 +6,8 @@ fn main() {
|
||||
let mut test = String::new();
|
||||
test.push('A');
|
||||
test.push('B');
|
||||
for _ in 0..50 {
|
||||
for _ in 0..100 {
|
||||
test.push('C');
|
||||
}
|
||||
println!("Hello from PIC program loaded dynamically with custom std and a better justfile, and syscalls !");
|
||||
println!("Hello from PIC program loaded dynamically with custom std and a better justfile, and syscalls ! {}", test);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user