Skip to content

Commit 542c3b3

Browse files
committed
Add a persistent filesystem.
The FILESYSTEM object tracks all open files for you.
1 parent 6da079f commit 542c3b3

File tree

7 files changed

+209
-92
lines changed

7 files changed

+209
-92
lines changed

Cargo.lock

Lines changed: 1 addition & 2 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -49,7 +49,7 @@ postcard = "1.0"
4949
serde = { version = "1.0", default-features = false }
5050
menu = "0.3"
5151
chrono = { version = "0.4", default-features = false }
52-
embedded-sdmmc = { version = "0.6", default-features = false }
52+
embedded-sdmmc = { git = "https://github.com/rust-embedded-community/embedded-sdmmc-rs.git", rev = "2f14459", default-features = false }
5353
neotron-api = "0.1"
5454
neotron-loader = "0.1"
5555
vte = "0.12"

src/commands/fs.rs

Lines changed: 12 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
//! File Systems related commands for Neotron OS
22
3-
use crate::{bios, osprint, osprintln, Ctx};
3+
use crate::{osprint, osprintln, Ctx, FILESYSTEM};
44

55
pub static DIR_ITEM: menu::Item<Ctx> = menu::Item {
66
item_type: menu::ItemType::Callback {
@@ -49,17 +49,11 @@ pub static TYPE_ITEM: menu::Item<Ctx> = menu::Item {
4949

5050
/// Called when the "dir" command is executed.
5151
fn dir(_menu: &menu::Menu<Ctx>, _item: &menu::Item<Ctx>, _args: &[&str], _ctx: &mut Ctx) {
52-
fn work() -> Result<(), embedded_sdmmc::Error<bios::Error>> {
52+
fn work() -> Result<(), crate::fs::Error> {
5353
osprintln!("Listing files on Block Device 0, /");
54-
let bios_block = crate::fs::BiosBlock();
55-
let time = crate::fs::BiosTime();
56-
let mut mgr = embedded_sdmmc::VolumeManager::new(bios_block, time);
57-
// Open the first partition
58-
let volume = mgr.open_volume(embedded_sdmmc::VolumeIdx(0))?;
59-
let root_dir = mgr.open_root_dir(volume)?;
60-
let mut total_bytes = 0u64;
54+
let mut total_bytes = 0;
6155
let mut num_files = 0;
62-
mgr.iterate_dir(root_dir, |dir_entry| {
56+
FILESYSTEM.iterate_root_dir(|dir_entry| {
6357
let padding = 8 - dir_entry.name.base_name().len();
6458
for b in dir_entry.name.base_name() {
6559
let ch = *b as char;
@@ -124,17 +118,11 @@ fn load(_menu: &menu::Menu<Ctx>, _item: &menu::Item<Ctx>, args: &[&str], ctx: &m
124118

125119
/// Called when the "exec" command is executed.
126120
fn exec(_menu: &menu::Menu<Ctx>, _item: &menu::Item<Ctx>, args: &[&str], ctx: &mut Ctx) {
127-
fn work(ctx: &mut Ctx, filename: &str) -> Result<(), embedded_sdmmc::Error<bios::Error>> {
128-
let bios_block = crate::fs::BiosBlock();
129-
let time = crate::fs::BiosTime();
130-
let mut mgr = embedded_sdmmc::VolumeManager::new(bios_block, time);
131-
// Open the first partition
132-
let volume = mgr.open_volume(embedded_sdmmc::VolumeIdx(0))?;
133-
let root_dir = mgr.open_root_dir(volume)?;
134-
let file = mgr.open_file_in_dir(root_dir, filename, embedded_sdmmc::Mode::ReadOnly)?;
121+
fn work(ctx: &mut Ctx, filename: &str) -> Result<(), crate::fs::Error> {
122+
let file = FILESYSTEM.open_file(filename, embedded_sdmmc::Mode::ReadOnly)?;
135123
let buffer = ctx.tpa.as_slice_u8();
136-
let count = mgr.read(file, buffer)?;
137-
if count != mgr.file_length(file)? as usize {
124+
let count = file.read(buffer)?;
125+
if count != file.length() as usize {
138126
osprintln!("File too large! Max {} bytes allowed.", buffer.len());
139127
return Ok(());
140128
}
@@ -159,17 +147,11 @@ fn exec(_menu: &menu::Menu<Ctx>, _item: &menu::Item<Ctx>, args: &[&str], ctx: &m
159147

160148
/// Called when the "type" command is executed.
161149
fn typefn(_menu: &menu::Menu<Ctx>, _item: &menu::Item<Ctx>, args: &[&str], ctx: &mut Ctx) {
162-
fn work(ctx: &mut Ctx, filename: &str) -> Result<(), embedded_sdmmc::Error<bios::Error>> {
163-
let bios_block = crate::fs::BiosBlock();
164-
let time = crate::fs::BiosTime();
165-
let mut mgr = embedded_sdmmc::VolumeManager::new(bios_block, time);
166-
// Open the first partition
167-
let volume = mgr.open_volume(embedded_sdmmc::VolumeIdx(0))?;
168-
let root_dir = mgr.open_root_dir(volume)?;
169-
let file = mgr.open_file_in_dir(root_dir, filename, embedded_sdmmc::Mode::ReadOnly)?;
150+
fn work(ctx: &mut Ctx, filename: &str) -> Result<(), crate::fs::Error> {
151+
let file = FILESYSTEM.open_file(filename, embedded_sdmmc::Mode::ReadOnly)?;
170152
let buffer = ctx.tpa.as_slice_u8();
171-
let count = mgr.read(file, buffer)?;
172-
if count != mgr.file_length(file)? as usize {
153+
let count = file.read(buffer)?;
154+
if count != file.length() as usize {
173155
osprintln!("File too large! Max {} bytes allowed.", buffer.len());
174156
return Ok(());
175157
}

src/commands/sound.rs

Lines changed: 5 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
//! Sound related commands for Neotron OS
22
3-
use crate::{bios, osprint, osprintln, Ctx, API};
3+
use crate::{bios, osprint, osprintln, Ctx, API, FILESYSTEM};
44

55
pub static MIXER_ITEM: menu::Item<Ctx> = menu::Item {
66
item_type: menu::ItemType::Callback {
@@ -119,18 +119,9 @@ fn mixer(_menu: &menu::Menu<Ctx>, item: &menu::Item<Ctx>, args: &[&str], _ctx: &
119119

120120
/// Called when the "play" command is executed.
121121
fn play(_menu: &menu::Menu<Ctx>, _item: &menu::Item<Ctx>, args: &[&str], ctx: &mut Ctx) {
122-
fn play_inner(
123-
file_name: &str,
124-
scratch: &mut [u8],
125-
) -> Result<(), embedded_sdmmc::Error<bios::Error>> {
122+
fn play_inner(file_name: &str, scratch: &mut [u8]) -> Result<(), crate::fs::Error> {
126123
osprintln!("Loading /{} from Block Device 0", file_name);
127-
let bios_block = crate::fs::BiosBlock();
128-
let time = crate::fs::BiosTime();
129-
let mut mgr = embedded_sdmmc::VolumeManager::new(bios_block, time);
130-
// Open the first partition
131-
let volume = mgr.open_volume(embedded_sdmmc::VolumeIdx(0))?;
132-
let root_dir = mgr.open_root_dir(volume)?;
133-
let file = mgr.open_file_in_dir(root_dir, file_name, embedded_sdmmc::Mode::ReadOnly)?;
124+
let file = FILESYSTEM.open_file(file_name, embedded_sdmmc::Mode::ReadOnly)?;
134125

135126
osprintln!("Press Q to quit, P to pause/unpause...");
136127

@@ -142,9 +133,9 @@ fn play(_menu: &menu::Menu<Ctx>, _item: &menu::Item<Ctx>, args: &[&str], ctx: &m
142133

143134
let mut pause = false;
144135

145-
'playback: while !mgr.file_eof(file).unwrap() {
136+
'playback: while !file.is_eof() {
146137
if !pause {
147-
let bytes_read = mgr.read(file, buffer)?;
138+
let bytes_read = file.read(buffer)?;
148139
let mut buffer = &buffer[0..bytes_read];
149140
while !buffer.is_empty() {
150141
let slice = bios::FfiByteSlice::new(buffer);

src/fs.rs

Lines changed: 169 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,13 @@
11
//! Filesystem related types
22
33
use chrono::{Datelike, Timelike};
4+
use embedded_sdmmc::RawVolume;
45

5-
use crate::{bios, API};
6+
use crate::{bios, refcell::CsRefCell, API, FILESYSTEM};
67

8+
/// Represents a block device that reads/writes disk blocks using the BIOS.
9+
///
10+
/// Currently only block device 0 is supported.
711
pub struct BiosBlock();
812

913
impl embedded_sdmmc::BlockDevice for BiosBlock {
@@ -65,6 +69,7 @@ impl embedded_sdmmc::BlockDevice for BiosBlock {
6569
}
6670
}
6771

72+
/// A type that lets you fetch the current time from the BIOS.
6873
pub struct BiosTime();
6974

7075
impl embedded_sdmmc::TimeSource for BiosTime {
@@ -81,4 +86,167 @@ impl embedded_sdmmc::TimeSource for BiosTime {
8186
}
8287
}
8388

89+
/// The errors this module can produce
90+
#[derive(Debug)]
91+
pub enum Error {
92+
/// Filesystem error
93+
Io(embedded_sdmmc::Error<bios::Error>),
94+
}
95+
96+
impl From<embedded_sdmmc::Error<bios::Error>> for Error {
97+
fn from(value: embedded_sdmmc::Error<bios::Error>) -> Self {
98+
Error::Io(value)
99+
}
100+
}
101+
102+
/// Represents an open file
103+
pub struct File {
104+
inner: embedded_sdmmc::RawFile,
105+
}
106+
107+
impl File {
108+
/// Read from a file
109+
pub fn read(&self, buffer: &mut [u8]) -> Result<usize, Error> {
110+
FILESYSTEM.file_read(self, buffer)
111+
}
112+
113+
/// Are we at the end of the file
114+
pub fn is_eof(&self) -> bool {
115+
FILESYSTEM
116+
.file_eof(self)
117+
.expect("File handle should be valid")
118+
}
119+
120+
/// Seek to a position relative to the start of the file
121+
pub fn seek_from_start(&self, offset: u32) -> Result<(), Error> {
122+
FILESYSTEM.file_seek_from_start(self, offset)
123+
}
124+
125+
/// What is the length of this file?
126+
pub fn length(&self) -> u32 {
127+
FILESYSTEM
128+
.file_length(self)
129+
.expect("File handle should be valid")
130+
}
131+
}
132+
133+
impl Drop for File {
134+
fn drop(&mut self) {
135+
FILESYSTEM
136+
.close_raw_file(self.inner)
137+
.expect("Should only be dropping valid files!");
138+
}
139+
}
140+
141+
/// Represent all open files and filesystems
142+
pub struct Filesystem {
143+
volume_manager: CsRefCell<Option<embedded_sdmmc::VolumeManager<BiosBlock, BiosTime, 4, 4, 1>>>,
144+
first_volume: CsRefCell<Option<RawVolume>>,
145+
}
146+
147+
impl Filesystem {
148+
/// Create a new filesystem
149+
pub const fn new() -> Filesystem {
150+
Filesystem {
151+
volume_manager: CsRefCell::new(None),
152+
first_volume: CsRefCell::new(None),
153+
}
154+
}
155+
156+
/// Open a file on the filesystem
157+
pub fn open_file(&self, name: &str, mode: embedded_sdmmc::Mode) -> Result<File, Error> {
158+
let mut fs = self.volume_manager.lock();
159+
if fs.is_none() {
160+
*fs = Some(embedded_sdmmc::VolumeManager::new(BiosBlock(), BiosTime()));
161+
}
162+
let fs = fs.as_mut().unwrap();
163+
let mut volume = self.first_volume.lock();
164+
if volume.is_none() {
165+
*volume = Some(fs.open_raw_volume(embedded_sdmmc::VolumeIdx(0))?);
166+
}
167+
let volume = volume.unwrap();
168+
let mut root = fs.open_root_dir(volume)?.to_directory(fs);
169+
let file = root.open_file_in_dir(name, mode)?;
170+
let raw_file = file.to_raw_file();
171+
Ok(File { inner: raw_file })
172+
}
173+
174+
/// Walk through the root directory
175+
pub fn iterate_root_dir<F>(&self, f: F) -> Result<(), Error>
176+
where
177+
F: FnMut(&embedded_sdmmc::DirEntry),
178+
{
179+
let mut fs = self.volume_manager.lock();
180+
if fs.is_none() {
181+
*fs = Some(embedded_sdmmc::VolumeManager::new(BiosBlock(), BiosTime()));
182+
}
183+
let fs = fs.as_mut().unwrap();
184+
let mut volume = self.first_volume.lock();
185+
if volume.is_none() {
186+
*volume = Some(fs.open_raw_volume(embedded_sdmmc::VolumeIdx(0))?);
187+
}
188+
let volume = volume.unwrap();
189+
let mut root = fs.open_root_dir(volume)?.to_directory(fs);
190+
root.iterate_dir(f)?;
191+
Ok(())
192+
}
193+
194+
/// Read from an open file
195+
pub fn file_read(&self, file: &File, buffer: &mut [u8]) -> Result<usize, Error> {
196+
let mut fs = self.volume_manager.lock();
197+
if fs.is_none() {
198+
*fs = Some(embedded_sdmmc::VolumeManager::new(BiosBlock(), BiosTime()));
199+
}
200+
let fs = fs.as_mut().unwrap();
201+
let bytes_read = fs.read(file.inner, buffer)?;
202+
Ok(bytes_read)
203+
}
204+
205+
/// How large is a file?
206+
pub fn file_length(&self, file: &File) -> Result<u32, Error> {
207+
let mut fs = self.volume_manager.lock();
208+
if fs.is_none() {
209+
*fs = Some(embedded_sdmmc::VolumeManager::new(BiosBlock(), BiosTime()));
210+
}
211+
let fs = fs.as_mut().unwrap();
212+
let length = fs.file_length(file.inner)?;
213+
Ok(length)
214+
}
215+
216+
/// Seek a file with an offset from the start of the file.
217+
pub fn file_seek_from_start(&self, file: &File, offset: u32) -> Result<(), Error> {
218+
let mut fs = self.volume_manager.lock();
219+
if fs.is_none() {
220+
*fs = Some(embedded_sdmmc::VolumeManager::new(BiosBlock(), BiosTime()));
221+
}
222+
let fs = fs.as_mut().unwrap();
223+
fs.file_seek_from_start(file.inner, offset)?;
224+
Ok(())
225+
}
226+
227+
/// Are we at the end of the file
228+
pub fn file_eof(&self, file: &File) -> Result<bool, Error> {
229+
let mut fs = self.volume_manager.lock();
230+
if fs.is_none() {
231+
*fs = Some(embedded_sdmmc::VolumeManager::new(BiosBlock(), BiosTime()));
232+
}
233+
let fs = fs.as_mut().unwrap();
234+
let is_eof = fs.file_eof(file.inner)?;
235+
Ok(is_eof)
236+
}
237+
238+
/// Close an open file
239+
///
240+
/// Only used by File's drop impl.
241+
fn close_raw_file(&self, file: embedded_sdmmc::RawFile) -> Result<(), Error> {
242+
let mut fs = self.volume_manager.lock();
243+
if fs.is_none() {
244+
*fs = Some(embedded_sdmmc::VolumeManager::new(BiosBlock(), BiosTime()));
245+
}
246+
let fs = fs.as_mut().unwrap();
247+
fs.close_file(file)?;
248+
Ok(())
249+
}
250+
}
251+
84252
// End of file

src/lib.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,8 @@ static IS_PANIC: AtomicBool = AtomicBool::new(false);
5959
/// Our keyboard controller
6060
static STD_INPUT: CsRefCell<StdInput> = CsRefCell::new(StdInput::new());
6161

62+
static FILESYSTEM: fs::Filesystem = fs::Filesystem::new();
63+
6264
// ===========================================================================
6365
// Macros
6466
// ===========================================================================

0 commit comments

Comments
 (0)