Skip to main content

mediatek_optical/
drive.rs

1//! Command sequences over a caller-supplied [`Transport`]: identify, memory
2//! read and the flash session.
3//!
4//! ```no_run
5//! # use mediatek_optical::{cdb, drive};
6//! # fn run<T: drive::Transport>(t: &mut T, image: &[u8]) -> Result<(), drive::Error<T::Error>> {
7//! let id = drive::identify(t)?;
8//! assert!(id.is_mt19());
9//! let mut session = drive::enter_update(t)?;
10//! for (i, chunk) in image.chunks(cdb::CHUNK).enumerate() {
11//!     session.write((i * cdb::CHUNK) as u32, chunk)?;
12//! }
13//! session.finish()?;
14//! # Ok(()) }
15//! ```
16//!
17//! Requires the `drive` feature.
18
19use crate::{cdb, layout, Identity, INQUIRY_LEN};
20
21/// Largest offset a CDB can encode (24 bits).
22const FIELD_MAX: usize = 0xFF_FFFF;
23
24/// The data phase of one command.
25#[derive(Debug)]
26pub enum Data<'a> {
27    /// No data phase.
28    None,
29    /// Device to host: the drive fills up to `len()` bytes.
30    In(&'a mut [u8]),
31    /// Host to device: the drive receives every byte.
32    Out(&'a [u8]),
33}
34
35/// A SCSI pass-through.
36pub trait Transport {
37    /// Transport error.
38    type Error: core::fmt::Debug;
39
40    /// Execute one CDB. Returns the number of bytes transferred (`0` for
41    /// [`Data::None`]).
42    fn exec(&mut self, cdb: &[u8], data: Data<'_>) -> Result<usize, Self::Error>;
43
44    /// Sense `(key, asc, ascq)` of the most recent failed command, if any.
45    fn sense(&self) -> Option<(u8, u8, u8)>;
46}
47
48/// Errors from the command sequences.
49#[derive(Debug)]
50#[non_exhaustive]
51pub enum Error<E> {
52    /// The transport failed.
53    Transport(E),
54    /// The drive returned fewer bytes than required.
55    Short {
56        /// Bytes required.
57        expected: usize,
58        /// Bytes received.
59        actual: usize,
60    },
61    /// An offset or length does not fit its CDB field.
62    Oversize(usize),
63}
64
65#[cfg(feature = "std")]
66impl<E: core::fmt::Debug> std::error::Error for Error<E> {}
67
68impl<E: core::fmt::Debug> core::fmt::Display for Error<E> {
69    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
70        match self {
71            Self::Transport(e) => write!(f, "transport error: {e:?}"),
72            Self::Short { expected, actual } => {
73                write!(f, "short transfer: expected {expected} bytes, got {actual}")
74            }
75            Self::Oversize(n) => write!(f, "{n:#x} exceeds the CDB field"),
76        }
77    }
78}
79
80fn exec<T: Transport>(t: &mut T, cdb: &[u8], data: Data<'_>) -> Result<usize, Error<T::Error>> {
81    t.exec(cdb, data).map_err(Error::Transport)
82}
83
84/// Execute a data-in `cdb` that must return at least `min` bytes.
85pub(crate) fn read<T: Transport>(
86    t: &mut T,
87    cdb: &[u8],
88    buf: &mut [u8],
89    min: usize,
90) -> Result<usize, Error<T::Error>> {
91    let n = exec(t, cdb, Data::In(buf))?.min(buf.len());
92    if n < min {
93        return Err(Error::Short {
94            expected: min,
95            actual: n,
96        });
97    }
98    Ok(n)
99}
100
101/// Execute a data-out `cdb` that must transfer every byte of `data`.
102fn write<T: Transport>(t: &mut T, cdb: &[u8], data: &[u8]) -> Result<(), Error<T::Error>> {
103    let n = exec(t, cdb, Data::Out(data))?;
104    if n < data.len() {
105        return Err(Error::Short {
106            expected: data.len(),
107            actual: n,
108        });
109    }
110    Ok(())
111}
112
113fn field<E>(n: usize, max: usize) -> Result<u32, Error<E>> {
114    if n > max {
115        return Err(Error::Oversize(n));
116    }
117    Ok(n as u32)
118}
119
120/// Read the drive's [`Identity`]: INQUIRY, boot banner and identity
121/// descriptor. Changes no drive state.
122///
123/// Only INQUIRY is required. A drive that refuses the banner or descriptor
124/// read still identifies, with those fields empty; [`Identity::is_mt19`] then
125/// reports `false`.
126pub fn identify<T: Transport>(t: &mut T) -> Result<Identity, Error<T::Error>> {
127    let mut inquiry = [0; cdb::INQUIRY_ALLOC as usize];
128    read(
129        t,
130        &cdb::inquiry(cdb::INQUIRY_ALLOC),
131        &mut inquiry,
132        INQUIRY_LEN,
133    )?;
134    let mut banner = [0; layout::BANNER.len];
135    let banner_len =
136        read_exact(t, layout::BANNER.start as u32, &mut banner).map_or(0, |_| banner.len());
137    let mut descriptor = [0; layout::DESCRIPTOR.len];
138    let descriptor_len = read_exact(t, layout::DESCRIPTOR.start as u32, &mut descriptor)
139        .map_or(0, |_| descriptor.len());
140    Identity::parse(
141        &inquiry,
142        &banner[..banner_len],
143        &descriptor[..descriptor_len],
144    )
145    .ok_or(Error::Short {
146        expected: INQUIRY_LEN,
147        actual: 0,
148    })
149}
150
151fn read_exact<T: Transport>(t: &mut T, off: u32, buf: &mut [u8]) -> Result<(), Error<T::Error>> {
152    let len = field(buf.len(), FIELD_MAX)?;
153    field::<T::Error>(off as usize, FIELD_MAX)?;
154    let n = buf.len();
155    read(t, &cdb::read_memory(off, len), buf, n).map(|_| ())
156}
157
158/// Read `buf.len()` bytes of flash at file offset `off`. A short reply is
159/// [`Error::Short`]: a partial region is never returned as complete.
160///
161/// The boot page reads back as [`layout::BOOT_MIRROR`]; see the crate docs.
162pub fn read_memory<T: Transport>(
163    t: &mut T,
164    off: u32,
165    buf: &mut [u8],
166) -> Result<(), Error<T::Error>> {
167    read_exact(t, off, buf)
168}
169
170/// Read the GET CONFIGURATION reply for the single feature `code` into `buf`;
171/// returns the bytes received. The reply needs at least its 12-byte header.
172pub fn feature<T: Transport>(
173    t: &mut T,
174    code: u16,
175    buf: &mut [u8],
176) -> Result<usize, Error<T::Error>> {
177    let alloc = field(buf.len(), u16::MAX as usize)? as u16;
178    read(t, &cdb::get_configuration(code, alloc), buf, 12)
179}
180
181/// TEST UNIT READY. An empty tray fails with sense `02/3A`; see
182/// [`crate::sense::is_no_medium`].
183pub fn test_unit_ready<T: Transport>(t: &mut T) -> Result<(), Error<T::Error>> {
184    exec(t, &cdb::test_unit_ready(), Data::None).map(|_| ())
185}
186
187/// REQUEST SENSE as the OEM sequence issues it; the parsed `(key, asc, ascq)`,
188/// or `None` for an unparseable reply.
189pub fn request_sense<T: Transport>(
190    t: &mut T,
191) -> Result<Option<crate::sense::Triplet>, Error<T::Error>> {
192    let mut buf = [0; cdb::REQUEST_SENSE_LEN];
193    let n = read(t, &cdb::request_sense(), &mut buf, 0)?;
194    Ok(crate::sense::parse(&buf[..n]))
195}
196
197/// An open flash session, returned by [`enter_update`].
198///
199/// The drive programs flash when the last chunk of a complete image lands,
200/// before [`finish`](Self::finish). Dropping the session sends nothing and
201/// does not roll back earlier writes.
202pub struct Session<'a, T: Transport> {
203    t: &'a mut T,
204}
205
206impl<T: Transport> core::fmt::Debug for Session<'_, T> {
207    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
208        f.debug_struct("Session").finish_non_exhaustive()
209    }
210}
211
212impl<T: Transport> Session<'_, T> {
213    /// Write `data` at file offset `off`. `off` must fit in 24 bits and
214    /// `data.len()` in 16; the OEM sequence uses [`cdb::CHUNK`].
215    pub fn write(&mut self, off: u32, data: &[u8]) -> Result<(), Error<T::Error>> {
216        let len = field(data.len(), u16::MAX as usize)? as u16;
217        field::<T::Error>(off as usize, FIELD_MAX)?;
218        write(self.t, &cdb::transfer(off, len), data)
219    }
220
221    /// Send the commit handshake. The drive may answer with a transient CHECK
222    /// CONDITION while it reinitializes; callers verify the outcome with
223    /// [`test_unit_ready`], [`request_sense`] and a read-back.
224    pub fn finish(self) -> Result<(), Error<T::Error>> {
225        write(self.t, &cdb::finish(), &[])
226    }
227}
228
229/// Enter the flash session: PROBE (a [`cdb::PROBE_LEN`]-byte descriptor
230/// read), TEST UNIT READY, then PREPARE. Any failure stops before PREPARE.
231pub fn enter_update<T: Transport>(t: &mut T) -> Result<Session<'_, T>, Error<T::Error>> {
232    enter_update_command(t, cdb::Prepare::ModeOne)
233}
234
235pub(crate) fn enter_update_command<T: Transport>(
236    t: &mut T,
237    prepare: cdb::Prepare,
238) -> Result<Session<'_, T>, Error<T::Error>> {
239    let mut probe = [0; cdb::PROBE_LEN];
240    read(t, &cdb::probe(), &mut probe, cdb::PROBE_LEN)?;
241    test_unit_ready(t)?;
242    write(t, &prepare.command(), &[])?;
243    Ok(Session { t })
244}
245
246/// Enter an explicitly selected recovery receiver: PREPARE only.
247/// Unlike normal update entry, this requires no application descriptor or
248/// pre-write TEST UNIT READY. The caller must identify and validate the live
249/// receiver first. No write is retried.
250pub fn enter_recovery<T: Transport>(t: &mut T) -> Result<Session<'_, T>, Error<T::Error>> {
251    write(t, &cdb::enter_update(), &[])?;
252    Ok(Session { t })
253}
254
255/// Explicit recovery host policy: report one failed PREPARE and proceed to the
256/// stream. No retry, alternate entry command, status probe or reset is sent.
257/// Kept private so ordinary update entry cannot accidentally select this policy.
258#[cfg(feature = "image")]
259pub(crate) fn enter_recovery_reporting<T: Transport>(
260    t: &mut T,
261    prepare: cdb::Prepare,
262    mut failed: impl FnMut(&Error<T::Error>),
263) -> Session<'_, T> {
264    if let Err(error) = write(t, &prepare.command(), &[]) {
265        failed(&error);
266    }
267    Session { t }
268}
269
270#[cfg(test)]
271#[path = "drive_tests.rs"]
272mod tests;