Skip to main content

mediatek_optical/update/
flash.rs

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