use core::sync::atomic::{AtomicU32, Ordering};
use alloc::{
borrow::Cow,
collections::btree_map::{BTreeMap, Entry},
string::{String, ToString},
vec,
vec::Vec,
};
use crate::info;
use embassy_sync::{
blocking_mutex::raw::CriticalSectionRawMutex,
channel::Channel,
mutex::Mutex,
semaphore::{FairSemaphore, Semaphore},
signal::Signal,
};
use crate::asynchronous::{
BlockDevice, boot_sector,
directory::{DirectoryEntry, DirectoryIterator},
directory_entry::{self},
error::ExFatError,
file::{File, Metadata, OpenOptions},
file_system::{ExFatResult, FileSystem},
};
static REQ: Channel<CriticalSectionRawMutex, Req, 8> = Channel::new();
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
#[derive(thiserror::Error, Debug, Clone, Copy)]
pub enum Error {
#[error("io error code {error_code}")]
Io { error_code: u8 },
#[error("no sd card")]
NoCard,
#[error("directory entry ({0:?})")]
DirectoryEntry(#[from] directory_entry::Error),
#[error("boot sector not valid exFAT ({0:?})")]
NotExFat(#[from] boot_sector::Error),
#[error("end of fat chain")]
EndOfFatChain,
#[error("invalid uft16 string encountered ({reason})")]
InvalidUtf16String { reason: &'static str },
#[error("invalid file name ({reason})")]
InvalidFileName { reason: &'static str },
#[error("invalid file system ({reason})")]
InvalidFileSystem { reason: &'static str },
#[error("file not found")]
FileNotFound,
#[error("directory not found")]
DirectoryNotFound,
#[error("invalid utf8 bytes")]
Utf8Error,
#[error("disk is full")]
DiskFull,
#[error("invalid cluster id ({0})")]
InvalidClusterId(u32),
#[error("invalid sector id ({0})")]
InvalidSectorId(u32),
#[error("directory is not empty")]
DirectoryNotEmpty,
#[error("write not enabled")]
WriteNotEnabled,
#[error("read not enabled")]
ReadNotEnabled,
#[error("file already exists")]
AlreadyExists,
#[error("cannot seek past the end of the valid data in the file")]
SeekOutOfRange,
#[error("attempt to change the allocation bitmap to a value with no effect")]
InvalidAllocation {
allocated: bool,
allocated_new: bool,
index: usize,
lba: u32,
},
#[error("the combination of flags set when opening the file is not valid")]
InvalidOptions,
#[error("unexpected response from file system actor")]
UnexpectedResponse,
#[error("no more file handles")]
ExhaustedFileHandles,
#[error("invalid file handle")]
InvalidFileHandle,
#[error("unexpected error occured: {0}")]
Unexpected(&'static str),
}
impl<E> From<ExFatError<E>> for Error
where
E: BlockDeviceError,
{
fn from(value: ExFatError<E>) -> Self {
match value {
ExFatError::Io(e) => {
if e.no_card() {
Error::NoCard
} else {
Error::Io {
error_code: e.to_error_code(),
}
}
}
ExFatError::AlreadyExists => Error::AlreadyExists,
ExFatError::DirectoryEntry(e) => Error::DirectoryEntry(e),
ExFatError::DirectoryNotEmpty => Error::DirectoryNotEmpty,
ExFatError::DirectoryNotFound => Error::DirectoryNotFound,
ExFatError::DiskFull => Error::DiskFull,
ExFatError::EndOfFatChain => Error::EndOfFatChain,
ExFatError::FileNotFound => Error::FileNotFound,
ExFatError::InvalidAllocation {
allocated,
allocated_new,
index,
lba,
} => Error::InvalidAllocation {
allocated,
allocated_new,
index,
lba,
},
ExFatError::InvalidClusterId(id) => Error::InvalidClusterId(id),
ExFatError::InvalidFileName { reason } => Error::InvalidFileName { reason },
ExFatError::InvalidFileSystem { reason } => Error::InvalidFileSystem { reason },
ExFatError::InvalidOptions => Error::InvalidOptions,
ExFatError::InvalidSectorId(id) => Error::InvalidSectorId(id),
ExFatError::InvalidUtf16String { reason } => Error::InvalidUtf16String { reason },
ExFatError::NotExFat(e) => Error::NotExFat(e),
ExFatError::ReadNotEnabled => Error::ReadNotEnabled,
ExFatError::SeekOutOfRange => Error::SeekOutOfRange,
ExFatError::Utf8Error => Error::Utf8Error,
ExFatError::WriteNotEnabled => Error::WriteNotEnabled,
ExFatError::Unexpected(reason) => Error::Unexpected(reason),
}
}
}
impl OpenOptions {
pub async fn open(&self, path: &str) -> Result<FileHandle, Error> {
let token = ReplyPool::acquire().await;
let req = Req {
op: Op::OpenFile {
path: path.to_string(),
options: self.clone(),
},
reply: token,
};
REQ.send(req).await;
let resp = ReplyPool::wait(token).await?;
match resp {
Resp::FileOpen { handle } => Ok(handle),
_ => Err(Error::UnexpectedResponse),
}
}
}
enum Op {
ReadToString { path: String },
Read { path: String },
OpenFile { path: String, options: OpenOptions },
ReadFile { handle: FileHandle, len: usize },
ReadFileToEnd { handle: FileHandle },
ReadFileToString { handle: FileHandle },
WriteFile { handle: FileHandle, buffer: Vec<u8> },
SeekFile { handle: FileHandle, position: u64 },
CloseFile { handle: FileHandle },
FlushFile { handle: FileHandle },
Metadata { handle: FileHandle },
OpenDirectory { path: String },
DirectoryNextEntry { handle: DirectoryHandle },
Exists { path: String },
RemoveFile { path: String },
CreateDirectory { path: String },
Copy { from_path: String, to_path: String },
Rename { from_path: String, to_path: String },
RemoveDir { path: String },
Write { path: String, buffer: Vec<u8> },
}
struct Req {
op: Op,
reply: ReplyToken,
}
pub struct DirectoryHandle(u32);
impl DirectoryHandle {
pub async fn next_entry(&self) -> Result<Option<DirectoryEntry>, Error> {
let token = ReplyPool::acquire().await;
let req = Req {
op: Op::DirectoryNextEntry {
handle: DirectoryHandle(self.0),
},
reply: token,
};
REQ.send(req).await;
let resp = ReplyPool::wait(token).await?;
match resp {
Resp::DirectoryEntry { data } => Ok(data),
_ => Err(Error::UnexpectedResponse),
}
}
}
pub struct FileHandle(u32);
impl FileHandle {
pub async fn read(&self, buf: &mut [u8]) -> Result<Option<usize>, Error> {
let token = ReplyPool::acquire().await;
let req = Req {
op: Op::ReadFile {
handle: FileHandle(self.0),
len: buf.len(),
},
reply: token,
};
REQ.send(req).await;
let resp = ReplyPool::wait(token).await?;
match resp {
Resp::Read { data } => {
if data.is_empty() {
Ok(None)
} else {
buf.copy_from_slice(&data);
Ok(Some(data.len()))
}
}
_ => Err(Error::UnexpectedResponse),
}
}
pub async fn read_to_end(&self) -> Result<Vec<u8>, Error> {
let token = ReplyPool::acquire().await;
let req = Req {
op: Op::ReadFileToEnd {
handle: FileHandle(self.0),
},
reply: token,
};
REQ.send(req).await;
let resp = ReplyPool::wait(token).await?;
match resp {
Resp::Read { data } => Ok(data),
_ => Err(Error::UnexpectedResponse),
}
}
pub async fn read_to_string(&self) -> Result<String, Error> {
let token = ReplyPool::acquire().await;
let req = Req {
op: Op::ReadFileToString {
handle: FileHandle(self.0),
},
reply: token,
};
REQ.send(req).await;
let resp = ReplyPool::wait(token).await?;
match resp {
Resp::ReadToString { data } => Ok(data),
_ => Err(Error::UnexpectedResponse),
}
}
pub async fn write(&self, buffer: impl Into<Cow<'_, [u8]>>) -> Result<(), Error> {
let buffer: Cow<'_, [u8]> = buffer.into();
if buffer.is_empty() {
return Ok(());
}
let token = ReplyPool::acquire().await;
let req = Req {
op: Op::WriteFile {
handle: FileHandle(self.0),
buffer: buffer.into(),
},
reply: token,
};
REQ.send(req).await;
let resp = ReplyPool::wait(token).await?;
match resp {
Resp::Ok => Ok(()),
_ => Err(Error::UnexpectedResponse),
}
}
pub async fn seek(&self, position: u64) -> Result<(), Error> {
let token = ReplyPool::acquire().await;
let req = Req {
op: Op::SeekFile {
handle: FileHandle(self.0),
position,
},
reply: token,
};
REQ.send(req).await;
let resp = ReplyPool::wait(token).await?;
match resp {
Resp::Ok => Ok(()),
_ => Err(Error::UnexpectedResponse),
}
}
pub async fn metdata(&self) -> Result<Metadata, Error> {
let token = ReplyPool::acquire().await;
let req = Req {
op: Op::Metadata {
handle: FileHandle(self.0),
},
reply: token,
};
REQ.send(req).await;
let resp = ReplyPool::wait(token).await?;
match resp {
Resp::Metadata { data } => Ok(data),
_ => Err(Error::UnexpectedResponse),
}
}
pub async fn close(self) -> Result<(), Error> {
let token = ReplyPool::acquire().await;
let req = Req {
op: Op::CloseFile {
handle: FileHandle(self.0),
},
reply: token,
};
REQ.send(req).await;
let resp = ReplyPool::wait(token).await?;
match resp {
Resp::Ok => Ok(()),
_ => Err(Error::UnexpectedResponse),
}
}
pub async fn flush(&self) -> Result<(), Error> {
let token = ReplyPool::acquire().await;
let req = Req {
op: Op::FlushFile {
handle: FileHandle(self.0),
},
reply: token,
};
REQ.send(req).await;
let resp = ReplyPool::wait(token).await?;
match resp {
Resp::Ok => Ok(()),
_ => Err(Error::UnexpectedResponse),
}
}
}
impl Drop for FileHandle {
fn drop(&mut self) {
}
}
pub async fn open(path: &str, options: OpenOptions) -> Result<FileHandle, Error> {
let token = ReplyPool::acquire().await;
let req = Req {
op: Op::OpenFile {
path: path.to_string(),
options,
},
reply: token,
};
REQ.send(req).await;
let resp = ReplyPool::wait(token).await?;
match resp {
Resp::FileOpen { handle } => Ok(handle),
_ => Err(Error::UnexpectedResponse),
}
}
pub async fn read_to_string(path: &str) -> Result<String, Error> {
let token = ReplyPool::acquire().await;
let req = Req {
op: Op::ReadToString {
path: path.to_string(),
},
reply: token,
};
REQ.send(req).await;
let resp = ReplyPool::wait(token).await?;
match resp {
Resp::ReadToString { data: string } => Ok(string),
_ => Err(Error::UnexpectedResponse),
}
}
pub async fn read(path: &str) -> Result<Vec<u8>, Error> {
let token = ReplyPool::acquire().await;
let req = Req {
op: Op::Read {
path: path.to_string(),
},
reply: token,
};
REQ.send(req).await;
let resp = ReplyPool::wait(token).await?;
match resp {
Resp::Read { data } => Ok(data),
_ => Err(Error::UnexpectedResponse),
}
}
pub async fn read_dir(path: &str) -> Result<DirectoryHandle, Error> {
let token = ReplyPool::acquire().await;
let req = Req {
op: Op::OpenDirectory {
path: path.to_string(),
},
reply: token,
};
REQ.send(req).await;
let resp = ReplyPool::wait(token).await?;
match resp {
Resp::DirectoryOpen { handle } => Ok(handle),
_ => Err(Error::UnexpectedResponse),
}
}
pub async fn exists(path: &str) -> Result<bool, Error> {
let token = ReplyPool::acquire().await;
let req = Req {
op: Op::Exists {
path: path.to_string(),
},
reply: token,
};
REQ.send(req).await;
let resp = ReplyPool::wait(token).await?;
match resp {
Resp::Exists { exists } => Ok(exists),
_ => Err(Error::UnexpectedResponse),
}
}
pub async fn remove_file(path: &str) -> Result<(), Error> {
let token = ReplyPool::acquire().await;
let req = Req {
op: Op::RemoveFile {
path: path.to_string(),
},
reply: token,
};
REQ.send(req).await;
let resp = ReplyPool::wait(token).await?;
match resp {
Resp::Ok => Ok(()),
_ => Err(Error::UnexpectedResponse),
}
}
pub async fn create_directory(path: &str) -> Result<(), Error> {
let token = ReplyPool::acquire().await;
let req = Req {
op: Op::CreateDirectory {
path: path.to_string(),
},
reply: token,
};
REQ.send(req).await;
let resp = ReplyPool::wait(token).await?;
match resp {
Resp::Ok => Ok(()),
_ => Err(Error::UnexpectedResponse),
}
}
pub async fn copy(path_from: &str, path_to: &str) -> Result<(), Error> {
let token = ReplyPool::acquire().await;
let req = Req {
op: Op::Copy {
from_path: path_from.to_string(),
to_path: path_to.to_string(),
},
reply: token,
};
REQ.send(req).await;
let resp = ReplyPool::wait(token).await?;
match resp {
Resp::Ok => Ok(()),
_ => Err(Error::UnexpectedResponse),
}
}
pub async fn rename(path_from: &str, path_to: &str) -> Result<(), Error> {
let token = ReplyPool::acquire().await;
let req = Req {
op: Op::Rename {
from_path: path_from.to_string(),
to_path: path_to.to_string(),
},
reply: token,
};
REQ.send(req).await;
let resp = ReplyPool::wait(token).await?;
match resp {
Resp::Ok => Ok(()),
_ => Err(Error::UnexpectedResponse),
}
}
pub async fn remove_directory(path: &str) -> Result<(), Error> {
let token = ReplyPool::acquire().await;
let req = Req {
op: Op::RemoveDir {
path: path.to_string(),
},
reply: token,
};
REQ.send(req).await;
let resp = ReplyPool::wait(token).await?;
match resp {
Resp::Ok => Ok(()),
_ => Err(Error::UnexpectedResponse),
}
}
pub async fn write(path: &str, buffer: impl Into<Cow<'_, [u8]>>) -> Result<(), Error> {
let token = ReplyPool::acquire().await;
let buffer: Cow<'_, [u8]> = buffer.into();
let req = Req {
op: Op::Write {
path: path.to_string(),
buffer: buffer.into(),
},
reply: token,
};
REQ.send(req).await;
let resp = ReplyPool::wait(token).await?;
match resp {
Resp::Ok => Ok(()),
_ => Err(Error::UnexpectedResponse),
}
}
#[derive(Clone, Copy, Debug)]
struct ReplyToken {
pub slot: u8,
pub seq: u32,
}
struct ReplySignal {
seq: u32,
resp: Result<Resp, Error>,
}
const REPLY_SLOTS: usize = 4;
static REPLY_SIGNALS: [Signal<CriticalSectionRawMutex, ReplySignal>; REPLY_SLOTS] =
[Signal::new(), Signal::new(), Signal::new(), Signal::new()];
static FREE_SLOTS: FairSemaphore<CriticalSectionRawMutex, REPLY_SLOTS> =
FairSemaphore::new(REPLY_SLOTS);
static SLOT_BITMAP: Mutex<CriticalSectionRawMutex, u32> = Mutex::new(0);
static SEQ: AtomicU32 = AtomicU32::new(1);
fn claim_slot_locked(bitmap: &mut u32) -> u8 {
for i in 0..REPLY_SLOTS {
let mask = 1u32 << i;
if (*bitmap & mask) == 0 {
*bitmap |= mask;
return i as u8;
}
}
0
}
fn release_slot_locked(bitmap: &mut u32, slot: u8) {
let mask = 1u32 << (slot as u32);
*bitmap &= !mask;
}
enum Resp {
Ok,
ReadToString { data: String },
Read { data: Vec<u8> },
FileOpen { handle: FileHandle },
DirectoryOpen { handle: DirectoryHandle },
Metadata { data: Metadata },
DirectoryEntry { data: Option<DirectoryEntry> },
Exists { exists: bool },
}
struct Handles<T> {
handles: BTreeMap<u32, T>,
next_handle: u32,
}
impl<T> Handles<T> {
fn new() -> Self {
Self {
handles: BTreeMap::new(),
next_handle: 0,
}
}
pub fn clear(&mut self) {
self.handles.clear();
}
pub fn add(&mut self, item: T) -> Result<u32, Error> {
let mut counter: u32 = 0;
loop {
let handle = self.next_handle.wrapping_add(1);
self.next_handle = handle;
match self.handles.entry(handle) {
Entry::Occupied(_) => {
}
Entry::Vacant(v) => {
v.insert(item); return Ok(handle);
}
}
counter += 1;
if counter == u32::MAX {
return Err(Error::ExhaustedFileHandles);
}
}
}
pub fn remove(&mut self, handle: u32) -> Result<T, Error> {
self.handles.remove(&handle).ok_or(Error::InvalidFileHandle)
}
pub fn get(&mut self, handle: u32) -> Result<&mut T, Error> {
self.handles
.get_mut(&handle)
.ok_or(Error::InvalidFileHandle)
}
}
struct ReplyPool;
impl ReplyPool {
pub async fn acquire() -> ReplyToken {
FREE_SLOTS.acquire(1).await.unwrap();
let slot = {
let mut bm = SLOT_BITMAP.lock().await;
claim_slot_locked(&mut bm)
};
let seq = SEQ.fetch_add(1, Ordering::Relaxed);
ReplyToken { slot, seq }
}
pub fn _try_acquire() -> Option<ReplyToken> {
FREE_SLOTS.try_acquire(1)?;
let slot = {
let mut bm = SLOT_BITMAP.try_lock().ok()?;
claim_slot_locked(&mut bm)
};
let seq = SEQ.fetch_add(1, Ordering::Relaxed);
Some(ReplyToken { slot, seq })
}
pub async fn wait(token: ReplyToken) -> Result<Resp, Error> {
let slot = token.slot as usize;
loop {
let ReplySignal { seq, resp } = REPLY_SIGNALS[slot].wait().await;
if seq == token.seq {
{
let mut bm = SLOT_BITMAP.lock().await;
release_slot_locked(&mut bm, token.slot);
}
FREE_SLOTS.release(1);
return resp;
}
}
}
pub fn complete(token: ReplyToken, resp: Result<Resp, Error>) {
let signal = ReplySignal {
seq: token.seq,
resp,
};
REPLY_SIGNALS[token.slot as usize].signal(signal);
}
}
pub trait BlockDeviceError {
fn to_error_code(&self) -> u8;
fn no_card(&self) -> bool;
}
struct FsManager<D, const SIZE: usize, const N: usize>
where
D: BlockDevice<SIZE>,
{
dev: Option<D>,
file_system: Option<FileSystem<D, SIZE, N>>,
files: Handles<File>,
directories: Handles<DirectoryIterator<SIZE>>,
}
impl<D, const SIZE: usize, const N: usize> FsManager<D, SIZE, N>
where
D: BlockDevice<SIZE>,
{
pub fn new(dev: D) -> Self {
Self {
dev: Some(dev),
file_system: None,
files: Handles::new(),
directories: Handles::new(),
}
}
pub async fn mount(
&mut self,
) -> ExFatResult<
(
&mut FileSystem<D, SIZE, N>,
&mut Handles<File>,
&mut Handles<DirectoryIterator<SIZE>>,
),
D,
SIZE,
> {
if let Some(dev) = self.dev.take() {
let mut file_system = FileSystem::new(dev);
info!("mounting file system");
match file_system.mount().await {
Ok(()) => {
self.files.clear();
self.file_system = Some(file_system);
Ok((
self.file_system.as_mut().unwrap(),
&mut self.files,
&mut self.directories,
))
}
Err(e) => {
let dev = file_system.unmount();
self.dev = Some(dev);
return Err(e);
}
}
} else if let Some(file_system) = self.file_system.as_mut() {
return Ok((file_system, &mut self.files, &mut self.directories));
} else {
panic!("neither dev or file_system are set")
}
}
pub fn unmount(&mut self) {
if let Some(file_system) = self.file_system.take() {
let dev = file_system.unmount();
self.dev = Some(dev);
}
}
}
pub async fn fs_actor_task<D, const SIZE: usize, const N: usize>(device: D)
where
D: BlockDevice<SIZE>,
D::Error: BlockDeviceError,
{
let mut fs_manager: FsManager<D, SIZE, N> = FsManager::new(device);
let rx = REQ.receiver();
loop {
let Req { op, reply } = rx.receive().await;
match handle_req(op, &mut fs_manager).await {
Ok(resp) => {
ReplyPool::complete(reply, Ok(resp));
}
Err(Error::NoCard) => {
fs_manager.unmount();
info!("no card, unmounted");
ReplyPool::complete(reply, Err(Error::NoCard))
}
Err(e) => {
ReplyPool::complete(reply, Err(e));
}
}
}
}
async fn handle_req<D, const SIZE: usize, const N: usize>(
op: Op,
fs_manager: &mut FsManager<D, SIZE, N>,
) -> Result<Resp, Error>
where
D: BlockDevice<SIZE>,
D::Error: BlockDeviceError,
{
let (file_system, files, dirs) = fs_manager.mount().await?;
let resp = match op {
Op::ReadToString { path } => {
let data = file_system.read_to_string(&path).await?;
Resp::ReadToString { data }
}
Op::Read { path } => {
let data = file_system.read(&path).await?;
Resp::Read { data }
}
Op::OpenFile { path, options } => {
let file = file_system.open(&path, options).await?;
let handle = FileHandle(files.add(file).unwrap());
Resp::FileOpen { handle }
}
Op::ReadFileToString { handle } => {
let file = files.get(handle.0)?;
let data = file.read_to_string(file_system).await?;
Resp::ReadToString { data }
}
Op::ReadFileToEnd { handle } => {
let file = files.get(handle.0)?;
let mut data = Vec::new();
file.read_to_end(file_system, &mut data).await?;
Resp::Read { data }
}
Op::SeekFile { handle, position } => {
let file = files.get(handle.0)?;
file.seek(file_system, position).await?;
Resp::Ok
}
Op::ReadFile { handle, len } => {
let file = files.get(handle.0)?;
let mut data = vec![0u8; len];
file.read(file_system, &mut data).await?;
Resp::Read { data }
}
Op::WriteFile { handle, buffer } => {
let file = files.get(handle.0)?;
file.write(file_system, &buffer).await?;
Resp::Ok
}
Op::CloseFile { handle } => {
let file = files.remove(handle.0)?;
file.close(file_system).await?;
Resp::Ok
}
Op::FlushFile { handle } => {
let file = files.get(handle.0)?;
file.flush(file_system).await?;
Resp::Ok
}
Op::Metadata { handle } => {
let file = files.get(handle.0)?;
let data = file.metadata();
Resp::Metadata { data }
}
Op::OpenDirectory { path } => {
let dir = file_system.read_dir(&path).await?;
let handle = DirectoryHandle(dirs.add(dir).unwrap());
Resp::DirectoryOpen { handle }
}
Op::DirectoryNextEntry { handle } => {
let dir = dirs.get(handle.0)?;
let data = dir.next_entry(file_system).await?;
Resp::DirectoryEntry { data }
}
Op::Exists { path } => {
let exists = file_system.exists(&path).await?;
Resp::Exists { exists }
}
Op::RemoveFile { path } => {
file_system.remove_file(&path).await?;
Resp::Ok
}
Op::CreateDirectory { path } => {
file_system.create_directory(&path).await?;
Resp::Ok
}
Op::Copy { from_path, to_path } => {
file_system.copy(&from_path, &to_path).await?;
Resp::Ok
}
Op::Rename { from_path, to_path } => {
file_system.rename(&from_path, &to_path).await?;
Resp::Ok
}
Op::RemoveDir { path } => {
file_system.remove_dir(&path).await?;
Resp::Ok
}
Op::Write { path, buffer } => {
file_system.write(&path, &buffer).await?;
Resp::Ok
}
};
Ok(resp)
}