starnix_core/device/android/
bootloader_message_store.rs

1// Copyright 2024 The Fuchsia Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
5use crate::device::remote_block_device::RemoteBlockDevice;
6use crate::task::CurrentTask;
7use anyhow::{Error, anyhow};
8use bstr::{BStr, ByteSlice as _};
9use starnix_logging::log_info;
10use starnix_sync::{Locked, Unlocked};
11use static_assertions::const_assert_eq;
12use std::sync::{Arc, Weak};
13use zerocopy::{FromBytes, Immutable, KnownLayout};
14
15/// Android passes bootloader messages across reboot via a special "misc" partition.  Since Starnix
16/// acts as a de-facto bootloader for Android, we need to be able to read these bootloader messages,
17/// which is done through this interface.
18///
19/// This is a temporary hack which is only necessary because we implement Android FDR in Starnix and
20/// never boot into Android's recovery mode (which normally handles the bootloader messages).  We
21/// can remove this when we eventually let Android's recovery mode drive FDR.
22#[derive(Debug)]
23pub struct AndroidBootloaderMessageStore(Weak<RemoteBlockDevice>);
24
25// See bootable/recovery/bootloader_message for the canonical format.
26#[repr(C)]
27#[derive(Copy, Clone, Immutable, KnownLayout, FromBytes)]
28struct BootloaderMessageRaw {
29    command: [u8; 32],
30    _status: [u8; 32],
31    recovery: [u8; 768],
32    _stage: [u8; 32],
33    _reserved: [u8; 1184],
34}
35
36fn read_null_terminated(buf: &[u8]) -> &BStr {
37    if let Some((prefix, _)) = buf.split_once_str(&[0u8]) {
38        prefix.as_bstr()
39    } else {
40        buf.as_bstr()
41    }
42}
43
44#[derive(Debug, Default, Eq, PartialEq)]
45pub enum BootloaderMessage {
46    /// Reboot into recovery, passing the list of string flags.
47    BootRecovery(Vec<String>),
48    #[default]
49    Unknown,
50}
51
52impl TryFrom<BootloaderMessageRaw> for BootloaderMessage {
53    type Error = anyhow::Error;
54
55    fn try_from(value: BootloaderMessageRaw) -> Result<Self, Self::Error> {
56        let command = read_null_terminated(&value.command).to_str()?;
57        if command.is_empty() {
58            return Err(anyhow!("No command written to bootloader messages"));
59        }
60        match command {
61            "boot-recovery" => {
62                let recovery_args = read_null_terminated(&value.recovery);
63                let mut args = vec![];
64                for arg in recovery_args.split_str("\n") {
65                    args.push(arg.to_str()?.to_owned());
66                }
67                Ok(BootloaderMessage::BootRecovery(args))
68            }
69            _ => {
70                log_info!("Unrecognized bootloader command {command}");
71                Ok(BootloaderMessage::Unknown)
72            }
73        }
74    }
75}
76
77impl AndroidBootloaderMessageStore {
78    pub fn new(device: &Arc<RemoteBlockDevice>) -> Self {
79        Self(Arc::downgrade(device))
80    }
81
82    pub fn read_bootloader_message(&self) -> Result<BootloaderMessage, Error> {
83        if let Some(device) = self.0.upgrade() {
84            const_assert_eq!(std::mem::size_of::<BootloaderMessageRaw>(), 2048);
85            let mut buf = vec![0u8; 2048];
86            device.read(0, &mut buf)?;
87            BootloaderMessageRaw::read_from_bytes(&buf[..])
88                .map_err(|_| anyhow!("Failed to deserialize bootloader message"))
89                .and_then(|raw| BootloaderMessage::try_from(raw))
90        } else {
91            Err(anyhow!("Can't read bootloader message; device is inaccessible"))
92        }
93    }
94}
95
96/// If a remote block device named "misc" is created, keep track of it; this is used by Android
97/// to pass boot parameters to the bootloader.  Since Starnix is acting as a de-facto bootloader
98/// for Android, we need to be able to peek into these messages.
99/// Note that this might never be initialized (if the "misc" device never gets registered).
100pub fn android_bootloader_message_store_init(
101    _locked: &mut Locked<Unlocked>,
102    current_task: &CurrentTask,
103) {
104    let kernel = current_task.kernel().clone();
105    current_task.kernel().remote_block_device_registry.on_device_added(Box::new(
106        move |name, _minor, device| {
107            if name == "misc" {
108                log_info!(
109                    "'misc' remote block device detected; setting as bootloader message store"
110                );
111                kernel.expando.get_or_init::<AndroidBootloaderMessageStore>(|| {
112                    AndroidBootloaderMessageStore::new(device)
113                });
114            }
115        },
116    ));
117}
118
119#[cfg(test)]
120mod tests {
121    use super::*;
122    use zerocopy::FromZeros as _;
123
124    #[test]
125    fn test_read_null_terminated() {
126        assert_eq!(read_null_terminated(b""), b"".as_bstr());
127        assert_eq!(read_null_terminated(b"\0"), b"".as_bstr());
128        assert_eq!(read_null_terminated(b"foo\0bar\0"), b"foo".as_bstr());
129        assert_eq!(read_null_terminated(b"foo\0\0"), b"foo".as_bstr());
130    }
131
132    #[test]
133    fn test_parse_bootloader_message() {
134        let mut raw = BootloaderMessageRaw::new_zeroed();
135        raw.command[..14].copy_from_slice(b"boot-recovery\0");
136        raw.recovery[..12].copy_from_slice(b"foo\nbar\nbaz\0");
137        let message = BootloaderMessage::try_from(raw).unwrap();
138        assert_eq!(
139            message,
140            BootloaderMessage::BootRecovery(vec![
141                "foo".to_string(),
142                "bar".to_string(),
143                "baz".to_string()
144            ])
145        );
146
147        let mut raw = BootloaderMessageRaw::new_zeroed();
148        raw.command[..9].copy_from_slice(b"blahblah\0");
149        let message = BootloaderMessage::try_from(raw).unwrap();
150        assert_eq!(message, BootloaderMessage::Unknown);
151
152        let raw = BootloaderMessageRaw::new_zeroed();
153        BootloaderMessage::try_from(raw).unwrap_err();
154    }
155}