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#[derive(Debug)]
66pub enum StartupError<E> {
67    /// Live operating mode/INQUIRY could not be established.
68    Identity(receiver::Error<E>),
69    /// The target descriptor could not be read completely.
70    Read(drive::Error<E>),
71    /// BOOT, an old target or different build is still running.
72    TargetMismatch,
73}
74impl<E: core::fmt::Debug> core::fmt::Display for StartupError<E> {
75    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
76        match self {
77            Self::Identity(error) => write!(f, "{error}"),
78            Self::Read(error) => write!(f, "{error}"),
79            Self::TargetMismatch => {
80                f.write_str("live firmware identity or build differs from the selected target")
81            }
82        }
83    }
84}
85impl<E: core::fmt::Debug + 'static> std::error::Error for StartupError<E> {
86    fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
87        match self {
88            Self::Identity(source) => Some(source),
89            Self::Read(source) => Some(source),
90            Self::TargetMismatch => None,
91        }
92    }
93}
94
95pub(super) fn complete<T: drive::Transport>(
96    reads: &mut T,
97    image: &[u8],
98    result: Result<Verified, FlashError<T::Error>>,
99) -> Result<Completion, FlashError<T::Error>> {
100    let readback = match result {
101        Ok(verified) => ReadbackQuality::Complete(verified),
102        Err(FlashError::Incomplete {
103            checked,
104            unverified,
105        }) => ReadbackQuality::Limited {
106            checked,
107            unverified,
108        },
109        // Transfer failures, programming faults and mismatches are never
110        // downgraded by an identity check, and trigger no further commands.
111        Err(error) => return Err(error),
112    };
113    let startup = confirm_startup(reads, image).map_err(FlashError::Startup)?;
114    Ok(Completion { startup, readback })
115}
116
117fn confirm_startup<T: drive::Transport>(
118    reads: &mut T,
119    image: &[u8],
120) -> Result<Startup, StartupError<T::Error>> {
121    let expected = layout::DESCRIPTOR
122        .of(image)
123        .ok_or(StartupError::TargetMismatch)?;
124    let mode = receiver::detect_mode(reads).map_err(StartupError::Identity)?;
125    if mode.mode() != receiver::Mode::Firmware || mode.inquiry()[8..36] != expected[..28] {
126        return Err(StartupError::TargetMismatch);
127    }
128    let mut descriptor = [0; layout::DESCRIPTOR.len];
129    drive::read_memory(reads, layout::DESCRIPTOR.start as u32, &mut descriptor)
130        .map_err(StartupError::Read)?;
131    if descriptor[..0x58] != expected[..0x58] {
132        return Err(StartupError::TargetMismatch);
133    }
134    Ok(Startup {
135        inquiry: *mode.inquiry(),
136        descriptor: descriptor[..0x58]
137            .try_into()
138            .expect("fixed descriptor length"),
139    })
140}