Skip to main content

mediatek_optical/update/
flash.rs

1//! Shared transfer and verification over a caller's transport.
2
3use core::time::Duration;
4
5#[cfg(test)]
6use super::PreparedUpdate;
7use crate::drive::{self, Error, Transport};
8use crate::sense::{self, Triplet};
9use crate::{cdb, layout};
10
11/// The two transports a flash uses, both addressing the drive the update was
12/// prepared for. Prevent any other access to the drive throughout.
13pub struct FlashTransport<'a, R, W> {
14    /// Readiness polls, sense reads and read-back.
15    pub reads: &'a mut R,
16    /// The update session: PROBE, TEST UNIT READY, PREPARE, STREAM and
17    /// COMMIT. Must never retry a write.
18    pub writes: &'a mut W,
19}
20
21/// When a sense reply was read.
22#[derive(Clone, Copy, Debug, PartialEq, Eq)]
23pub enum Stage {
24    /// Right after COMMIT.
25    Commit,
26    /// Once the drive answered TEST UNIT READY again.
27    Settled,
28}
29
30/// A REQUEST SENSE outcome that does not fail the flash.
31#[derive(Debug)]
32pub enum Status<'a, E> {
33    /// A parsed sense that proves no programming failure.
34    Sense(Triplet),
35    /// A reply of this many bytes that does not parse as sense data.
36    Unparseable(usize),
37    /// The command failed; read-back verification decides.
38    Failed(&'a Error<E>),
39}
40
41/// Host timing and progress for receiver-bound update execution.
42pub trait FlashRuntime<E> {
43    /// Monotonic time from a fixed origin; must advance across
44    /// [`FlashRuntime::sleep`].
45    fn elapsed(&self) -> Duration;
46    /// Wait at least `duration`.
47    fn sleep(&mut self, duration: Duration);
48    /// The session is about to start; `bytes` will be streamed.
49    fn starting(&mut self, _bytes: usize) {}
50    /// A STREAM write completed.
51    fn progress(&mut self, _written: usize, _total: usize) {}
52    /// Explicit Recovery only: PREPARE failed, was not retried, and recovery
53    /// proceeds to the selected image. Hosts should persist the full error.
54    /// Normal Flash never invokes this callback and aborts on entry failure.
55    fn recovery_entry_failed(&mut self, _error: &Error<E>) {}
56    /// COMMIT failed. The drive programmed when the last chunk landed and may
57    /// answer COMMIT while it reinitializes, so the flash goes on to its
58    /// checks.
59    fn commit_failed(&mut self, _error: &Error<E>) {}
60    /// TEST UNIT READY right after COMMIT failed; expected while programming.
61    fn commit_not_ready(&mut self, _error: &Error<E>) {}
62    /// A REQUEST SENSE outcome that does not fail the flash.
63    fn status(&mut self, _stage: Stage, _status: Status<'_, E>) {}
64    /// The upload is complete; readiness polling starts.
65    fn uploaded(&mut self, _bytes: usize) {}
66    /// A readiness poll failed after `elapsed` of polling.
67    fn poll_failed(&mut self, _elapsed: Duration, _error: &Error<E>) {}
68    /// Read-back verification starts.
69    fn verifying(&mut self) {}
70    /// A chunk holding protected bytes could not be read back in full.
71    fn unverified(&mut self, _offset: usize, _length: usize, _error: &Error<E>) {}
72}
73
74/// What read-back verification proved.
75#[derive(Clone, Copy, Debug, PartialEq, Eq)]
76pub struct Verified {
77    /// Protected bytes compared, all equal.
78    pub checked: usize,
79    /// Protected ranges in the image; `0` when it has none to compare.
80    pub ranges: usize,
81}
82
83/// A failed flash. Every variant after [`FlashError::Entry`] may leave the
84/// drive holding partial or unverified firmware; nothing is retried or rolled
85/// back.
86#[derive(Debug)]
87#[non_exhaustive]
88pub enum FlashError<E> {
89    /// PROBE, TEST UNIT READY or PREPARE failed; no firmware data was sent. If
90    /// PREPARE was reached the drive may be in update mode.
91    Entry(Error<E>),
92    /// A STREAM write failed.
93    Transfer {
94        /// Offset of the failed chunk.
95        offset: usize,
96        /// Its length.
97        length: usize,
98        /// The command failure.
99        source: Error<E>,
100    },
101    /// REQUEST SENSE reported a programming failure.
102    Fault {
103        /// When the sense was read.
104        stage: Stage,
105        /// The sense.
106        sense: Triplet,
107    },
108    /// The drive did not answer TEST UNIT READY within
109    /// [`crate::DriveClass::ready_timeout`]; the last poll's error.
110    ReadyTimeout(Error<E>),
111    /// Readiness/read-back completed, but the selected target was not confirmed running.
112    Startup(super::StartupError<E>),
113    /// A protected byte read back differently.
114    Mismatch {
115        /// Offset of the first differing byte.
116        address: usize,
117        /// The byte read back.
118        read: u8,
119        /// The byte written.
120        wrote: u8,
121        /// Protected bytes that differ.
122        differing: usize,
123        /// Protected bytes compared.
124        checked: usize,
125    },
126    /// Chunks holding protected bytes could not be read back.
127    Incomplete {
128        /// Chunks not verified.
129        unverified: usize,
130        /// Protected bytes compared, all equal.
131        checked: usize,
132    },
133}
134
135impl<E: core::fmt::Debug> core::fmt::Display for FlashError<E> {
136    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
137        match self {
138            Self::Entry(e) => write!(f, "update entry failed before any firmware data: {e}"),
139            Self::Transfer {
140                offset,
141                length,
142                source,
143            } => write!(
144                f,
145                "firmware write at {offset:#x}, length {length}: {source}"
146            ),
147            Self::Fault { stage, sense } => write!(
148                f,
149                "{stage:?} sense {:02X}/{:02X}/{:02X} reports a flash fault",
150                sense.0, sense.1, sense.2
151            ),
152            Self::ReadyTimeout(e) => write!(f, "drive did not settle after the burn: {e}"),
153            Self::Startup(error) => write!(f, "target startup was not confirmed: {error}"),
154            Self::Mismatch {
155                address,
156                read,
157                wrote,
158                differing,
159                checked,
160            } => write!(
161                f,
162                "read-back differs at {address:#08x} (read {read:#04x}, wrote {wrote:#04x}); \
163                 {differing} of {checked} protected bytes differ"
164            ),
165            Self::Incomplete {
166                unverified,
167                checked,
168            } => write!(
169                f,
170                "{unverified} protected chunks unreadable; {checked} protected bytes verified"
171            ),
172        }
173    }
174}
175
176impl<E: core::fmt::Debug + 'static> std::error::Error for FlashError<E> {
177    fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
178        match self {
179            Self::Entry(source) | Self::ReadyTimeout(source) => Some(source),
180            Self::Transfer { source, .. } => Some(source),
181            Self::Startup(source) => Some(source),
182            _ => None,
183        }
184    }
185}
186
187/// REQUEST SENSE as the OEM sequence issues it: the reply length and its
188/// parse.
189fn request_sense<T: Transport>(t: &mut T) -> Result<(usize, Option<Triplet>), Error<T::Error>> {
190    let mut buf = [0; cdb::REQUEST_SENSE_LEN];
191    let n = drive::read(t, &cdb::request_sense(), &mut buf, 0)?;
192    Ok((n, sense::parse(&buf[..n])))
193}
194
195/// REQUEST SENSE at `stage`: a flash fault fails, anything else is reported.
196fn status<T: Transport, H: FlashRuntime<T::Error>>(
197    t: &mut T,
198    runtime: &mut H,
199    stage: Stage,
200) -> Result<(), FlashError<T::Error>> {
201    match request_sense(t) {
202        Ok((_, Some(sense))) if sense::is_flash_fault(sense.0) => {
203            Err(FlashError::Fault { stage, sense })
204        }
205        Ok((_, Some(sense))) => {
206            runtime.status(stage, Status::Sense(sense));
207            Ok(())
208        }
209        Ok((n, None)) => {
210            runtime.status(stage, Status::Unparseable(n));
211            Ok(())
212        }
213        Err(error) => {
214            runtime.status(stage, Status::Failed(&error));
215            Ok(())
216        }
217    }
218}
219
220#[cfg(test)]
221impl PreparedUpdate {
222    /// Write the update and prove it: PROBE, TEST UNIT READY, PREPARE, the
223    /// transfer in STREAM chunks, COMMIT, TEST UNIT READY and REQUEST SENSE;
224    /// then poll TEST UNIT READY until the drive has programmed, REQUEST SENSE
225    /// again, and read back every chunk, comparing the integrity-protected
226    /// ranges with the image.
227    ///
228    /// First capture a backup, enforce tray and consent policy and exclude
229    /// other users of the drive. A failure stops at once; nothing is retried.
230    pub(crate) fn flash<R, W, H>(
231        &self,
232        io: FlashTransport<'_, R, W>,
233        runtime: &mut H,
234    ) -> Result<Verified, FlashError<W::Error>>
235    where
236        W: Transport,
237        R: Transport<Error = W::Error>,
238        H: FlashRuntime<W::Error>,
239    {
240        Payload {
241            class: self.class,
242            image: &self.image,
243            transfer: self.transfer(),
244        }
245        .flash(io, runtime, ExecutionMode::Flash, cdb::Prepare::ModeOne)
246    }
247}
248
249/// Shared transfer and verification implementation; recovery supplies no family policy.
250pub(crate) struct Payload<'a> {
251    pub(crate) class: crate::DriveClass,
252    pub(crate) image: &'a [u8],
253    pub(crate) transfer: &'a [u8],
254}
255
256/// Host policy is separate from receiver encoding and entry-command evidence.
257/// Only the corresponding public prepared operation selects this mode.
258#[derive(Debug, Clone, Copy, PartialEq, Eq)]
259pub(crate) enum ExecutionMode {
260    Flash,
261    Recovery,
262}
263impl Payload<'_> {
264    pub(crate) fn flash<R, W, H>(
265        &self,
266        io: FlashTransport<'_, R, W>,
267        runtime: &mut H,
268        mode: ExecutionMode,
269        prepare: cdb::Prepare,
270    ) -> Result<Verified, FlashError<W::Error>>
271    where
272        W: Transport,
273        R: Transport<Error = W::Error>,
274        H: FlashRuntime<W::Error>,
275    {
276        let transfer = self.transfer;
277        runtime.starting(transfer.len());
278        let mut session = match mode {
279            ExecutionMode::Recovery => {
280                drive::enter_recovery_reporting(io.writes, prepare, |error| {
281                    runtime.recovery_entry_failed(error)
282                })
283            }
284            ExecutionMode::Flash => {
285                drive::enter_update_command(io.writes, prepare).map_err(FlashError::Entry)?
286            }
287        };
288        let chunk = self.class.chunk();
289        for (index, piece) in transfer.chunks(chunk).enumerate() {
290            let offset = index * chunk;
291            session
292                .write(offset as u32, piece)
293                .map_err(|source| FlashError::Transfer {
294                    offset,
295                    length: piece.len(),
296                    source,
297                })?;
298            runtime.progress(offset + piece.len(), transfer.len());
299        }
300        if let Err(error) = session.finish() {
301            runtime.commit_failed(&error);
302        }
303        if let Err(error) = drive::test_unit_ready(io.reads) {
304            runtime.commit_not_ready(&error);
305        }
306        status(io.reads, runtime, Stage::Commit)?;
307
308        runtime.uploaded(transfer.len());
309        let start = runtime.elapsed();
310        while let Err(error) = drive::test_unit_ready(io.reads) {
311            let waited = runtime.elapsed().saturating_sub(start);
312            runtime.poll_failed(waited, &error);
313            if waited >= self.class.ready_timeout() {
314                return Err(FlashError::ReadyTimeout(error));
315            }
316            runtime.sleep(self.class.poll_interval());
317        }
318        status(io.reads, runtime, Stage::Settled)?;
319
320        runtime.verifying();
321        self.verify(io.reads, runtime)
322    }
323
324    /// Execute and retain read-back quality, then require exact target startup.
325    pub(crate) fn flash_completed<R, W, H>(
326        &self,
327        io: FlashTransport<'_, R, W>,
328        runtime: &mut H,
329        mode: ExecutionMode,
330        prepare: cdb::Prepare,
331    ) -> Result<super::Completion, FlashError<W::Error>>
332    where
333        W: Transport,
334        R: Transport<Error = W::Error>,
335        H: FlashRuntime<W::Error>,
336    {
337        let result = self.flash(
338            FlashTransport {
339                reads: io.reads,
340                writes: io.writes,
341            },
342            runtime,
343            mode,
344            prepare,
345        );
346        super::completion::complete(io.reads, self.image, result)
347    }
348
349    /// Read back every chunk and compare the protected bytes.
350    fn verify<R: Transport, H: FlashRuntime<R::Error>>(
351        &self,
352        reads: &mut R,
353        runtime: &mut H,
354    ) -> Result<Verified, FlashError<R::Error>> {
355        let ranges = self.class.verification_ranges(self.image);
356        let protected = |at: usize| ranges.iter().any(|r| r.contains(&at));
357        let (mut checked, mut differing, mut unverified) = (0, 0, 0);
358        let mut first: Option<(usize, u8, u8)> = None;
359        let chunk = self.class.chunk();
360        let mut buf = vec![0; chunk];
361        for (index, piece) in self.image.chunks(chunk).enumerate() {
362            let offset = index * chunk;
363            let got = &mut buf[..piece.len()];
364            match drive::read_memory(reads, offset as u32, got) {
365                Ok(()) => {
366                    for (i, (&read, &wrote)) in got.iter().zip(piece).enumerate() {
367                        if !protected(offset + i) {
368                            continue;
369                        }
370                        checked += 1;
371                        if read != wrote {
372                            differing += 1;
373                            first.get_or_insert((offset + i, read, wrote));
374                        }
375                    }
376                }
377                Err(error) => {
378                    if (offset..offset + piece.len()).any(protected) {
379                        unverified += 1;
380                        runtime.unverified(offset, piece.len(), &error);
381                    }
382                }
383            }
384        }
385        if let Some((address, read, wrote)) = first {
386            return Err(FlashError::Mismatch {
387                address,
388                read,
389                wrote,
390                differing,
391                checked,
392            });
393        }
394        if !ranges.is_empty() && unverified > 0 {
395            return Err(FlashError::Incomplete {
396                unverified,
397                checked,
398            });
399        }
400        debug_assert_eq!(self.image.len(), layout::IMAGE_SIZE);
401        Ok(Verified {
402            checked,
403            ranges: ranges.len(),
404        })
405    }
406}