use core::time::Duration;
#[cfg(test)]
use super::PreparedUpdate;
use crate::drive::{self, Error, Transport};
use crate::sense::{self, Triplet};
use crate::{cdb, layout};
pub struct FlashTransport<'a, R, W> {
pub reads: &'a mut R,
pub writes: &'a mut W,
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum Stage {
Commit,
Settled,
}
#[derive(Debug)]
pub enum Status<'a, E> {
Sense(Triplet),
Unparseable(usize),
Failed(&'a Error<E>),
}
pub trait FlashRuntime<E> {
fn elapsed(&self) -> Duration;
fn sleep(&mut self, duration: Duration);
fn starting(&mut self, _bytes: usize) {}
fn progress(&mut self, _written: usize, _total: usize) {}
fn recovery_entry_failed(&mut self, _error: &Error<E>) {}
fn commit_failed(&mut self, _error: &Error<E>) {}
fn commit_not_ready(&mut self, _error: &Error<E>) {}
fn status(&mut self, _stage: Stage, _status: Status<'_, E>) {}
fn uploaded(&mut self, _bytes: usize) {}
fn poll_failed(&mut self, _elapsed: Duration, _error: &Error<E>) {}
fn verifying(&mut self) {}
fn unverified(&mut self, _offset: usize, _length: usize, _error: &Error<E>) {}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub struct Verified {
pub checked: usize,
pub ranges: usize,
}
#[derive(Debug)]
#[non_exhaustive]
pub enum FlashError<E> {
Entry(Error<E>),
Transfer {
offset: usize,
length: usize,
source: Error<E>,
},
Fault {
stage: Stage,
sense: Triplet,
},
ReadyTimeout(Error<E>),
Startup(super::StartupError<E>),
Mismatch {
address: usize,
read: u8,
wrote: u8,
differing: usize,
checked: usize,
},
Incomplete {
unverified: usize,
checked: usize,
},
}
impl<E: core::fmt::Debug> core::fmt::Display for FlashError<E> {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
match self {
Self::Entry(e) => write!(f, "update entry failed before any firmware data: {e}"),
Self::Transfer {
offset,
length,
source,
} => write!(
f,
"firmware write at {offset:#x}, length {length}: {source}"
),
Self::Fault { stage, sense } => write!(
f,
"{stage:?} sense {:02X}/{:02X}/{:02X} reports a flash fault",
sense.0, sense.1, sense.2
),
Self::ReadyTimeout(e) => write!(f, "drive did not settle after the burn: {e}"),
Self::Startup(error) => write!(f, "target startup was not confirmed: {error}"),
Self::Mismatch {
address,
read,
wrote,
differing,
checked,
} => write!(
f,
"read-back differs at {address:#08x} (read {read:#04x}, wrote {wrote:#04x}); \
{differing} of {checked} protected bytes differ"
),
Self::Incomplete {
unverified,
checked,
} => write!(
f,
"{unverified} protected chunks unreadable; {checked} protected bytes verified"
),
}
}
}
impl<E: core::fmt::Debug + 'static> std::error::Error for FlashError<E> {
fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
match self {
Self::Entry(source) | Self::ReadyTimeout(source) => Some(source),
Self::Transfer { source, .. } => Some(source),
Self::Startup(source) => Some(source),
_ => None,
}
}
}
fn request_sense<T: Transport>(t: &mut T) -> Result<(usize, Option<Triplet>), Error<T::Error>> {
let mut buf = [0; cdb::REQUEST_SENSE_LEN];
let n = drive::read(t, &cdb::request_sense(), &mut buf, 0)?;
Ok((n, sense::parse(&buf[..n])))
}
fn status<T: Transport, H: FlashRuntime<T::Error>>(
t: &mut T,
runtime: &mut H,
stage: Stage,
) -> Result<(), FlashError<T::Error>> {
match request_sense(t) {
Ok((_, Some(sense))) if sense::is_flash_fault(sense.0) => {
Err(FlashError::Fault { stage, sense })
}
Ok((_, Some(sense))) => {
runtime.status(stage, Status::Sense(sense));
Ok(())
}
Ok((n, None)) => {
runtime.status(stage, Status::Unparseable(n));
Ok(())
}
Err(error) => {
runtime.status(stage, Status::Failed(&error));
Ok(())
}
}
}
#[cfg(test)]
impl PreparedUpdate {
pub(crate) fn flash<R, W, H>(
&self,
io: FlashTransport<'_, R, W>,
runtime: &mut H,
) -> Result<Verified, FlashError<W::Error>>
where
W: Transport,
R: Transport<Error = W::Error>,
H: FlashRuntime<W::Error>,
{
Payload {
class: self.class,
image: &self.image,
transfer: self.transfer(),
}
.flash(io, runtime, ExecutionMode::Flash, cdb::Prepare::ModeOne)
}
}
pub(crate) struct Payload<'a> {
pub(crate) class: crate::DriveClass,
pub(crate) image: &'a [u8],
pub(crate) transfer: &'a [u8],
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub(crate) enum ExecutionMode {
Flash,
Recovery,
}
impl Payload<'_> {
pub(crate) fn flash<R, W, H>(
&self,
io: FlashTransport<'_, R, W>,
runtime: &mut H,
mode: ExecutionMode,
prepare: cdb::Prepare,
) -> Result<Verified, FlashError<W::Error>>
where
W: Transport,
R: Transport<Error = W::Error>,
H: FlashRuntime<W::Error>,
{
let transfer = self.transfer;
runtime.starting(transfer.len());
let mut session = match mode {
ExecutionMode::Recovery => {
drive::enter_recovery_reporting(io.writes, prepare, |error| {
runtime.recovery_entry_failed(error)
})
}
ExecutionMode::Flash => {
drive::enter_update_command(io.writes, prepare).map_err(FlashError::Entry)?
}
};
let chunk = self.class.chunk();
for (index, piece) in transfer.chunks(chunk).enumerate() {
let offset = index * chunk;
session
.write(offset as u32, piece)
.map_err(|source| FlashError::Transfer {
offset,
length: piece.len(),
source,
})?;
runtime.progress(offset + piece.len(), transfer.len());
}
if let Err(error) = session.finish() {
runtime.commit_failed(&error);
}
if let Err(error) = drive::test_unit_ready(io.reads) {
runtime.commit_not_ready(&error);
}
status(io.reads, runtime, Stage::Commit)?;
runtime.uploaded(transfer.len());
let start = runtime.elapsed();
while let Err(error) = drive::test_unit_ready(io.reads) {
let waited = runtime.elapsed().saturating_sub(start);
runtime.poll_failed(waited, &error);
if waited >= self.class.ready_timeout() {
return Err(FlashError::ReadyTimeout(error));
}
runtime.sleep(self.class.poll_interval());
}
status(io.reads, runtime, Stage::Settled)?;
runtime.verifying();
self.verify(io.reads, runtime)
}
pub(crate) fn flash_completed<R, W, H>(
&self,
io: FlashTransport<'_, R, W>,
runtime: &mut H,
mode: ExecutionMode,
prepare: cdb::Prepare,
) -> Result<super::Completion, FlashError<W::Error>>
where
W: Transport,
R: Transport<Error = W::Error>,
H: FlashRuntime<W::Error>,
{
let result = self.flash(
FlashTransport {
reads: io.reads,
writes: io.writes,
},
runtime,
mode,
prepare,
);
super::completion::complete(io.reads, self.image, result)
}
fn verify<R: Transport, H: FlashRuntime<R::Error>>(
&self,
reads: &mut R,
runtime: &mut H,
) -> Result<Verified, FlashError<R::Error>> {
let ranges = self.class.verification_ranges(self.image);
let protected = |at: usize| ranges.iter().any(|r| r.contains(&at));
let (mut checked, mut differing, mut unverified) = (0, 0, 0);
let mut first: Option<(usize, u8, u8)> = None;
let chunk = self.class.chunk();
let mut buf = vec![0; chunk];
for (index, piece) in self.image.chunks(chunk).enumerate() {
let offset = index * chunk;
let got = &mut buf[..piece.len()];
match drive::read_memory(reads, offset as u32, got) {
Ok(()) => {
for (i, (&read, &wrote)) in got.iter().zip(piece).enumerate() {
if !protected(offset + i) {
continue;
}
checked += 1;
if read != wrote {
differing += 1;
first.get_or_insert((offset + i, read, wrote));
}
}
}
Err(error) => {
if (offset..offset + piece.len()).any(protected) {
unverified += 1;
runtime.unverified(offset, piece.len(), &error);
}
}
}
}
if let Some((address, read, wrote)) = first {
return Err(FlashError::Mismatch {
address,
read,
wrote,
differing,
checked,
});
}
if !ranges.is_empty() && unverified > 0 {
return Err(FlashError::Incomplete {
unverified,
checked,
});
}
debug_assert_eq!(self.image.len(), layout::IMAGE_SIZE);
Ok(Verified {
checked,
ranges: ranges.len(),
})
}
}