Skip to main content

mediatek_optical/update/
completion.rs

1//! Completion evidence shared by Flash and Recovery.
2
3use super::{FlashError, Verified};
4use crate::{drive, layout, receiver, INQUIRY_LEN};
5
6/// What protected-region read-back established. Limited verification must be
7/// reported as such even when the exact target has started.
8#[derive(Clone, Copy, Debug, PartialEq, Eq)]
9pub enum ReadbackQuality {
10    /// All protected ranges were read and matched.
11    Complete(Verified),
12    /// Some protected chunks were unreadable; every byte read matched.
13    Limited {
14        /// Protected bytes compared, all equal.
15        checked: usize,
16        /// Protected chunks that could not be read in full.
17        unverified: usize,
18    },
19}
20
21/// Exact selected-target startup, established after programming settled.
22#[derive(Clone, Debug, PartialEq, Eq)]
23pub struct Startup {
24    inquiry: [u8; INQUIRY_LEN],
25    descriptor: [u8; 0x58],
26}
27impl Startup {
28    /// Live standard INQUIRY whose vendor, product and revision match the target.
29    pub fn inquiry(&self) -> &[u8; INQUIRY_LEN] {
30        &self.inquiry
31    }
32    /// Live model, revision and build metadata matched against the target.
33    /// The device-written descriptor tail is deliberately excluded.
34    pub fn descriptor(&self) -> &[u8; 0x58] {
35        &self.descriptor
36    }
37}
38
39/// A completed operation with exact target startup and explicit read-back quality.
40#[derive(Clone, Debug, PartialEq, Eq)]
41pub struct Completion {
42    /// Evidence that the selected firmware is now running.
43    pub startup: Startup,
44    /// How much of the programmed firmware could be verified by read-back.
45    pub readback: ReadbackQuality,
46}
47impl Completion {
48    /// Compatibility with callers that require complete protected read-back.
49    /// Limited verification remains an error through the legacy API.
50    pub(crate) fn into_verified<E>(self) -> Result<Verified, FlashError<E>> {
51        match self.readback {
52            ReadbackQuality::Complete(verified) => Ok(verified),
53            ReadbackQuality::Limited {
54                checked,
55                unverified,
56            } => Err(FlashError::Incomplete {
57                checked,
58                unverified,
59            }),
60        }
61    }
62}
63
64/// Why exact selected-target startup could not be established.
65///
66/// Hosts must keep a wildcard arm; the set of variants may grow.
67///
68/// ```compile_fail,E0004
69/// fn f(e: mediatek_optical::update::StartupError<()>) -> u8 { match e { mediatek_optical::update::StartupError::Identity { .. } => 0, mediatek_optical::update::StartupError::Read { .. } => 1, mediatek_optical::update::StartupError::TargetMismatch { .. } => 2, } }
70/// ```
71#[derive(Debug)]
72#[non_exhaustive]
73pub enum StartupError<E> {
74    /// Live operating mode/INQUIRY could not be established.
75    Identity(receiver::Error<E>),
76    /// The target descriptor could not be read completely.
77    Read(drive::Error<E>),
78    /// BOOT, an old target or different build is still running.
79    TargetMismatch,
80}
81impl<E: core::fmt::Debug> core::fmt::Display for StartupError<E> {
82    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
83        match self {
84            Self::Identity(error) => write!(f, "{error}"),
85            Self::Read(error) => write!(f, "{error}"),
86            Self::TargetMismatch => {
87                f.write_str("live firmware identity or build differs from the selected target")
88            }
89        }
90    }
91}
92impl<E: core::fmt::Debug + 'static> std::error::Error for StartupError<E> {
93    fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
94        match self {
95            Self::Identity(source) => Some(source),
96            Self::Read(source) => Some(source),
97            Self::TargetMismatch => None,
98        }
99    }
100}
101
102pub(super) fn complete<T: drive::Transport>(
103    reads: &mut T,
104    image: &[u8],
105    result: Result<Verified, FlashError<T::Error>>,
106) -> Result<Completion, FlashError<T::Error>> {
107    let readback = match result {
108        Ok(verified) => ReadbackQuality::Complete(verified),
109        Err(FlashError::Incomplete {
110            checked,
111            unverified,
112        }) => ReadbackQuality::Limited {
113            checked,
114            unverified,
115        },
116        // Transfer failures, programming faults and mismatches are never
117        // downgraded by an identity check, and trigger no further commands.
118        Err(error) => return Err(error),
119    };
120    let startup = confirm_startup(reads, image).map_err(FlashError::Startup)?;
121    Ok(Completion { startup, readback })
122}
123
124fn confirm_startup<T: drive::Transport>(
125    reads: &mut T,
126    image: &[u8],
127) -> Result<Startup, StartupError<T::Error>> {
128    let expected = layout::DESCRIPTOR
129        .of(image)
130        .ok_or(StartupError::TargetMismatch)?;
131    let mode = receiver::detect_mode(reads).map_err(StartupError::Identity)?;
132    if mode.mode() != receiver::Mode::Firmware || mode.inquiry()[8..36] != expected[..28] {
133        return Err(StartupError::TargetMismatch);
134    }
135    let mut descriptor = [0; layout::DESCRIPTOR.len];
136    drive::read_memory(reads, layout::DESCRIPTOR.start as u32, &mut descriptor)
137        .map_err(StartupError::Read)?;
138    if descriptor[..0x58] != expected[..0x58] {
139        return Err(StartupError::TargetMismatch);
140    }
141    Ok(Startup {
142        inquiry: *mode.inquiry(),
143        descriptor: descriptor[..0x58]
144            .try_into()
145            .expect("fixed descriptor length"),
146    })
147}