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