1use anyhow::{Context, Error};
6use async_lock::OnceCell;
7use fidl_fuchsia_boot as fboot;
8use fidl_fuchsia_feedback::{LastRebootInfoProviderMarker, RebootReason};
9use fidl_fuchsia_io as fio;
10use fuchsia_component::client::{connect_to_protocol, connect_to_protocol_sync};
11use fuchsia_fs::node::OpenError;
12use log::{debug, info};
13use zx_status::Status;
14
15const STARTED_ONCE: &str = "component-started-once";
17static HAS_STARNIX_SESSION_RESTARTED: OnceCell<bool> = OnceCell::new();
20static ANDROID_BOOTREASON: OnceCell<Result<String, Error>> = OnceCell::new();
22
23const LRIP_FIDL_TIMEOUT: zx::MonotonicDuration = zx::MonotonicDuration::INFINITE;
25
26async fn has_session_restarted(dir: Option<fio::DirectoryProxy>) -> bool {
29 match dir {
30 Some(dir) => {
31 match fuchsia_fs::directory::open_file(&dir, STARTED_ONCE, fio::Flags::FLAG_MUST_CREATE)
32 .await
33 {
34 Ok(_file) => false,
35 Err(OpenError::OpenError(Status::ALREADY_EXISTS)) => true,
36 Err(err) => {
37 info!("Failed to generate the file with err {err:#?}.");
38 false
39 }
40 }
41 }
42 None => false,
43 }
44}
45
46pub async fn get_or_init_android_bootreason(
48 dir: Option<fio::DirectoryProxy>,
49 android_provided_bootreason: Option<String>,
50) -> &'static Result<String, Error> {
51 ANDROID_BOOTREASON
52 .get_or_init(async || update_android_bootreason(dir, android_provided_bootreason).await)
53 .await
54}
55
56const ANDROID_BOOTREASON_BOOTLOADER_FILE: &str = "androidboot.bootreason";
64
65pub async fn get_bootloader_file_bootreason() -> Result<Option<String>, Error> {
69 let items =
70 connect_to_protocol::<fboot::ItemsMarker>().context("Failed to connect to boot items")?;
71
72 let Some(vmo) = items
73 .get_bootloader_file(ANDROID_BOOTREASON_BOOTLOADER_FILE)
74 .await
75 .context("FIDL: Failed to get bootloader file")?
76 else {
77 return Ok(None);
78 };
79
80 let size = vmo.get_content_size().context("Failed to get bootloader file content size")?;
82 let bytes = vmo.read_to_vec(0, size).context("Failed to read bootloader file vmo")?;
83
84 Ok(Some(String::from_utf8(bytes).context("Bootloader file is not valid UTF-8")?))
85}
86
87fn is_specific_bootloader_bootreason(reason: &str) -> bool {
88 const SPECIFIC_PREFIXES: &[&str] = &[
89 "reboot,uvlo",
90 "reboot,ocp",
91 "reboot,master_dc,reset",
92 "reboot,sys_ldo_ok",
93 "reboot,smpl_timeout",
94 "reboot,longkey",
95 "reboot,auto_fship,pmic",
96 ];
97 SPECIFIC_PREFIXES.iter().any(|prefix| reason.starts_with(prefix))
98}
99
100pub async fn update_android_bootreason(
103 dir: Option<fio::DirectoryProxy>,
104 android_provided_bootreason: Option<String>,
105) -> Result<String, Error> {
106 if *HAS_STARNIX_SESSION_RESTARTED.get_or_init(async || has_session_restarted(dir).await).await {
108 info!("Session restart observed, set android bootreason to kernel_panic.");
109 return Ok("kernel_panic".to_string());
110 }
111
112 if let Some(reason) = &android_provided_bootreason {
115 info!("Android bootloader provided bootreason: {reason}");
116 if is_specific_bootloader_bootreason(reason) {
117 return Ok(reason.clone());
118 }
119 }
120
121 info!("Converting LastRebootInfo to an android-friendly bootreason.");
122 let reboot_info_proxy = connect_to_protocol_sync::<LastRebootInfoProviderMarker>()?;
123 let deadline = zx::MonotonicInstant::after(LRIP_FIDL_TIMEOUT);
124 let reboot_info = reboot_info_proxy.get(deadline)?;
125
126 let bootreason = match reboot_info.reason {
127 Some(RebootReason::Unknown) => "reboot,unknown",
128 Some(RebootReason::Cold) => "reboot,cold",
129 Some(RebootReason::BriefPowerLoss) => "reboot,hard_reset",
130 Some(RebootReason::SpontaneousRebootDuringShutdown) => "reboot,hard_reset",
131 Some(RebootReason::Brownout) => "reboot,undervoltage",
132 Some(RebootReason::KernelPanic) => "kernel_panic",
133 Some(RebootReason::SystemOutOfMemory) => "kernel_panic,oom",
134 Some(RebootReason::HardwareWatchdogTimeout) => "watchdog",
135 Some(RebootReason::SoftwareWatchdogTimeout) => "watchdog,sw",
136 Some(RebootReason::SuspensionFailure) => "kernel_panic",
137 Some(RebootReason::RootJobTermination) => "kernel_panic",
138 Some(RebootReason::UserRequest) => "reboot,userrequested",
139 Some(RebootReason::UserRequestDeviceStuck) => "reboot,userrequested",
140 Some(RebootReason::UserHardReset) => "reboot,longkey,s2",
141 Some(RebootReason::DeveloperRequest) => "reboot,shell",
142 Some(RebootReason::RetrySystemUpdate) => "reboot,ota",
143 Some(RebootReason::HighTemperature) => "shutdown,thermal",
144 Some(RebootReason::SessionFailure) => "kernel_panic",
145 Some(RebootReason::SysmgrFailure) => "kernel_panic",
146 Some(RebootReason::FactoryDataReset) => "reboot,factory_reset",
147 Some(RebootReason::CriticalComponentFailure) => "kernel_panic",
148 Some(RebootReason::CriticalDriverFailure) => "kernel_panic",
149 Some(RebootReason::PowerLevelTransitionStuck) => "kernel_panic",
150 Some(RebootReason::ZbiSwap) => "reboot,normal",
151 Some(RebootReason::SystemUpdate) => "reboot,ota",
152 Some(RebootReason::NetstackMigration) => "reboot,normal",
153 Some(RebootReason::AndroidUnexpectedReason) => "reboot,normal",
154 Some(RebootReason::AndroidNoReason) => "reboot",
155 Some(RebootReason::AndroidRescueParty) => "reboot,rescueparty",
156 Some(RebootReason::AndroidCriticalProcessFailure) => "reboot,userspace_failed",
157 Some(RebootReason::BatteryDrained) => "shutdown,battery",
158 Some(RebootReason::__SourceBreaking { .. }) => "reboot,normal",
159 None => "reboot,unknown",
160 };
161 Ok(bootreason.to_string())
162}
163
164fn get_reboot_reason() -> Option<RebootReason> {
166 let reboot_info_proxy = connect_to_protocol_sync::<LastRebootInfoProviderMarker>().ok();
167 let deadline = zx::MonotonicInstant::after(LRIP_FIDL_TIMEOUT);
168 let reboot_info = reboot_info_proxy?.get(deadline);
169 match reboot_info {
170 Ok(info) => match info.reason {
171 Some(r) => Some(r),
172 None => {
173 info!("Failed to get the reboot reason.");
174 Some(RebootReason::unknown())
175 }
176 },
177 Err(e) => {
178 info!("Failed to get the reboot info: {:?}", e);
179 Some(RebootReason::unknown())
180 }
181 }
182}
183
184pub fn get_console_ramoops() -> Option<Vec<u8>> {
189 debug!("Getting console-ramoops contents");
190 if HAS_STARNIX_SESSION_RESTARTED.get().copied().unwrap_or(false) {
191 return Some(format!("Last Reboot Reason: Starnix Crash\n").as_bytes().to_vec());
192 }
193 match ANDROID_BOOTREASON.get() {
194 Some(Ok(reason)) => match reason.as_str() {
195 "kernel_panic" | "watchdog" | "watchdog,sw" => Some(
196 format!("Last Reboot Reason: {:?}\n", get_reboot_reason()?).as_bytes().to_vec(),
197 ),
198 _ => None,
199 },
200 Some(Err(e)) => {
201 info!("Failed to get android bootreason for console_ramoops: {:?}", e);
202 None
203 }
204 None => {
205 info!("Android bootreason not initialized.");
206 None
207 }
208 }
209}
210
211#[cfg(test)]
212mod tests {
213 use super::*;
214
215 #[test]
216 fn test_is_specific_bootloader_bootreason() {
217 for specific in [
218 "reboot,uvlo",
219 "reboot,uvlo,pmic,main",
220 "reboot,uvlo,pmic,sub",
221 "reboot,ocp,pmic,if",
222 "reboot,ocp2,pmic,sub",
223 "reboot,ocp3,pmic,if,usb",
224 "reboot,master_dc,reset",
225 "reboot,sys_ldo_ok,pmic,main",
226 "reboot,smpl_timeout,pmic,main",
227 "reboot,longkey,s2",
228 "reboot,auto_fship,pmic,if",
229 ] {
230 assert!(
231 is_specific_bootloader_bootreason(specific),
232 "Expected {specific} to pass through"
233 );
234 }
235
236 for non_specific in ["warm", "cold", "reboot,cold", "reboot,warm", "reboot"] {
237 assert!(
238 !is_specific_bootloader_bootreason(non_specific),
239 "Did not expect {non_specific} to pass through"
240 );
241 }
242 }
243}