use alloc::string::String;
use alloc::sync::Arc;
use core::fmt;
use crate::core::error::{BusError, Error, Result};
use crate::core::hosts::{HostKind, HostObjects};
use crate::core::props::Props;
use crate::core::space::{MemResult, RamStore};
use crate::core::sync::{LockRank, Mutex};
pub trait Medium: Send + Sync + fmt::Debug {
fn capacity(&self) -> u64;
fn read_at(&self, offset: u64, dst: &mut [u8]) -> MemResult;
fn write_at(&self, offset: u64, src: &[u8]) -> MemResult;
fn flush(&self) -> MemResult {
Ok(())
}
fn is_read_only(&self) -> bool {
false
}
fn snapshot(&self) -> Snapshot {
Snapshot::Capture
}
fn describe(&self) -> String {
String::new()
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum Snapshot {
Capture,
Reference,
Refuse,
}
impl Snapshot {
#[must_use]
pub const fn as_str(self) -> &'static str {
match self {
Snapshot::Capture => "capture",
Snapshot::Reference => "reference",
Snapshot::Refuse => "refuse",
}
}
#[must_use]
pub fn from_name(name: &str) -> Option<Snapshot> {
match name {
"capture" => Some(Snapshot::Capture),
"reference" => Some(Snapshot::Reference),
"refuse" => Some(Snapshot::Refuse),
_ => None,
}
}
}
impl fmt::Display for Snapshot {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str(self.as_str())
}
}
impl Medium for RamStore {
fn capacity(&self) -> u64 {
self.len()
}
fn read_at(&self, offset: u64, dst: &mut [u8]) -> MemResult {
RamStore::read_at(self, offset, dst)
}
fn write_at(&self, offset: u64, src: &[u8]) -> MemResult {
RamStore::write_at(self, offset, src)
}
}
pub const KIND: HostKind = HostKind::new("ata-medium");
pub const MEDIUM_RANK: LockRank = LockRank::new(0x4c41);
pub struct MediumSlot {
medium: Mutex<Option<Arc<dyn Medium>>>,
}
impl fmt::Debug for MediumSlot {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("MediumSlot")
.field("occupied", &self.medium.lock().is_some())
.finish()
}
}
impl Default for MediumSlot {
fn default() -> MediumSlot {
MediumSlot::new()
}
}
impl MediumSlot {
#[must_use]
pub fn new() -> MediumSlot {
MediumSlot {
medium: Mutex::with_rank(MEDIUM_RANK, None),
}
}
#[must_use]
pub fn holding(medium: Arc<dyn Medium>) -> MediumSlot {
MediumSlot {
medium: Mutex::with_rank(MEDIUM_RANK, Some(medium)),
}
}
pub fn fit(&self, medium: Arc<dyn Medium>) -> bool {
let mut held = self.medium.lock();
if held.is_some() {
return false;
}
*held = Some(medium);
true
}
#[must_use]
pub fn take(&self) -> Option<Arc<dyn Medium>> {
self.medium.lock().take()
}
#[must_use]
pub fn is_occupied(&self) -> bool {
self.medium.lock().is_some()
}
}
pub fn attach(props: &Props, name: &str) -> Result<Arc<MediumSlot>> {
props.host(KIND, name, MediumSlot::new)
}
pub fn get(hosts: &HostObjects, name: &str) -> Result<Option<Arc<MediumSlot>>> {
hosts.get(KIND, name)
}
pub fn install(hosts: &HostObjects, name: &str, medium: Arc<dyn Medium>) -> Result<bool> {
let slot = hosts.open(KIND, name, MediumSlot::new)?;
Ok(slot.fit(medium))
}
#[must_use]
pub fn names(hosts: &HostObjects) -> alloc::vec::Vec<String> {
hosts.names(KIND)
}
#[must_use]
pub fn error_bit(e: BusError) -> u8 {
match e {
BusError::BadAccess => super::disk::ERR_IDNF,
BusError::Protected => super::disk::ERR_ABRT,
_ => super::disk::ERR_UNC,
}
}
pub(crate) fn error_at(offset: u64, e: BusError) -> Error {
Error::State(alloc::format!("{offset:#x}: {e}"))
}
#[cfg(test)]
mod tests {
use super::*;
use alloc::vec;
#[test]
fn a_ram_store_is_a_capturing_medium() {
let store = RamStore::new(1024);
assert_eq!(Medium::capacity(&store), 1024);
assert_eq!(store.snapshot(), Snapshot::Capture);
assert!(!store.is_read_only());
assert!(store.describe().is_empty());
assert!(Medium::write_at(&store, 512, &[1, 2, 3]).is_ok());
let mut got = vec![0u8; 3];
assert!(Medium::read_at(&store, 512, &mut got).is_ok());
assert_eq!(got, vec![1, 2, 3]);
assert!(store.flush().is_ok());
}
#[test]
fn a_read_past_the_end_is_bad_access_not_a_silent_zero() {
let store = RamStore::new(512);
let mut got = vec![0u8; 8];
assert_eq!(
Medium::read_at(&store, 510, &mut got),
Err(BusError::BadAccess)
);
assert_eq!(error_bit(BusError::BadAccess), super::super::disk::ERR_IDNF);
assert_eq!(error_bit(BusError::Unassigned), super::super::disk::ERR_UNC);
assert_eq!(error_bit(BusError::Retry), super::super::disk::ERR_UNC);
assert_eq!(error_bit(BusError::Protected), super::super::disk::ERR_ABRT);
}
#[test]
fn a_slot_hands_its_medium_over_exactly_once() {
let store: Arc<dyn Medium> = Arc::new(RamStore::new(512));
let slot = MediumSlot::new();
assert!(!slot.is_occupied());
assert!(slot.fit(Arc::clone(&store)));
assert!(slot.is_occupied());
assert!(!slot.fit(Arc::clone(&store)));
assert!(slot.take().is_some());
assert!(slot.take().is_none());
}
#[test]
fn a_policy_round_trips_through_its_name() {
for policy in [Snapshot::Capture, Snapshot::Reference, Snapshot::Refuse] {
assert_eq!(Snapshot::from_name(policy.as_str()), Some(policy));
}
assert_eq!(Snapshot::from_name("maybe"), None);
}
#[test]
fn a_host_installs_a_medium_under_a_slot_name() {
let hosts = HostObjects::new();
let store: Arc<dyn Medium> = Arc::new(RamStore::new(512));
assert!(install(&hosts, "hd0", Arc::clone(&store)).expect("installed"));
assert!(!install(&hosts, "hd0", store).expect("a second refused"));
assert_eq!(names(&hosts), vec![String::from("hd0")]);
let slot = get(&hosts, "hd0").expect("no type clash").expect("a slot");
assert!(slot.take().is_some());
assert!(get(&hosts, "hd1").expect("no type clash").is_none());
}
}