blob: 23d3e1db80c746969b545876a431fd2488bdda99 [file] [log] [blame]
Jaewan Kimba8929b2023-01-13 11:13:29 +09001// Copyright 2023, The Android Open Source Project
2//
3// Licensed under the Apache License, Version 2.0 (the "License");
4// you may not use this file except in compliance with the License.
5// You may obtain a copy of the License at
6//
7// http://www.apache.org/licenses/LICENSE-2.0
8//
9// Unless required by applicable law or agreed to in writing, software
10// distributed under the License is distributed on an "AS IS" BASIS,
11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12// See the License for the specific language governing permissions and
13// limitations under the License.
14
15//! Support for the debug policy overlay in pvmfw
16
Jaewan Kimc03f6612023-02-20 00:06:26 +090017use alloc::{vec, vec::Vec};
Jaewan Kimba8929b2023-01-13 11:13:29 +090018use core::ffi::CStr;
19use core::fmt;
20use libfdt::FdtError;
21use log::info;
22
23#[derive(Debug, Clone)]
24pub enum DebugPolicyError {
25 /// The provided baseline FDT was invalid or malformed, so cannot access certain node/prop
26 Fdt(&'static str, FdtError),
27 /// The provided debug policy FDT was invalid or malformed.
28 DebugPolicyFdt(&'static str, FdtError),
29 /// The overlaid result FDT is invalid or malformed, and may be corrupted.
30 OverlaidFdt(&'static str, FdtError),
31}
32
33impl fmt::Display for DebugPolicyError {
34 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
35 match self {
36 Self::Fdt(s, e) => write!(f, "Invalid baseline FDT. {s}: {e}"),
37 Self::DebugPolicyFdt(s, e) => write!(f, "Invalid overlay FDT. {s}: {e}"),
38 Self::OverlaidFdt(s, e) => write!(f, "Invalid overlaid FDT. {s}: {e}"),
39 }
40 }
41}
42
43/// Applies the debug policy device tree overlay to the pVM DT.
44///
45/// # Safety
46///
47/// When an error is returned by this function, the input `Fdt` should be
48/// discarded as it may have have been partially corrupted during the overlay
49/// application process.
50unsafe fn apply_debug_policy(
51 fdt: &mut libfdt::Fdt,
52 debug_policy: &mut [u8],
53) -> Result<(), DebugPolicyError> {
54 let overlay = libfdt::Fdt::from_mut_slice(debug_policy)
55 .map_err(|e| DebugPolicyError::DebugPolicyFdt("Failed to load debug policy overlay", e))?;
56
57 fdt.unpack().map_err(|e| DebugPolicyError::Fdt("Failed to unpack", e))?;
58
59 let fdt = fdt
60 .apply_overlay(overlay)
61 .map_err(|e| DebugPolicyError::DebugPolicyFdt("Failed to apply overlay", e))?;
62
63 fdt.pack().map_err(|e| DebugPolicyError::OverlaidFdt("Failed to re-pack", e))
64}
65
Jaewan Kimc03f6612023-02-20 00:06:26 +090066/// Disables ramdump by removing crashkernel from bootargs in /chosen.
67fn disable_ramdump(fdt: &mut libfdt::Fdt) -> Result<(), DebugPolicyError> {
Jaewan Kimba8929b2023-01-13 11:13:29 +090068 let chosen_path = CStr::from_bytes_with_nul(b"/chosen\0").unwrap();
69 let bootargs_name = CStr::from_bytes_with_nul(b"bootargs\0").unwrap();
70
71 let chosen = match fdt
72 .node(chosen_path)
73 .map_err(|e| DebugPolicyError::Fdt("Failed to find /chosen", e))?
74 {
75 Some(node) => node,
76 None => return Ok(()),
77 };
78
79 let bootargs = match chosen
80 .getprop_str(bootargs_name)
81 .map_err(|e| DebugPolicyError::Fdt("Failed to find bootargs prop", e))?
82 {
83 Some(value) if !value.to_bytes().is_empty() => value,
84 _ => return Ok(()),
85 };
86
87 // TODO: Improve add 'crashkernel=17MB' only when it's unnecessary.
88 // Currently 'crashkernel=17MB' in virtualizationservice and passed by
89 // chosen node, because it's not exactly a debug policy but a
90 // configuration. However, it's actually microdroid specific
91 // so we need a way to generalize it.
92 let mut args = vec![];
93 for arg in bootargs.to_bytes().split(|byte| byte.is_ascii_whitespace()) {
94 if arg.is_empty() || arg.starts_with(b"crashkernel=") {
95 continue;
96 }
97 args.push(arg);
98 }
99 let mut new_bootargs = args.as_slice().join(&b" "[..]);
100 new_bootargs.push(b'\0');
101
102 // We've checked existence of /chosen node at the beginning.
103 let mut chosen_mut = fdt.node_mut(chosen_path).unwrap().unwrap();
104 chosen_mut.setprop(bootargs_name, new_bootargs.as_slice()).map_err(|e| {
105 DebugPolicyError::OverlaidFdt("Failed to remove crashkernel. FDT might be corrupted", e)
106 })
107}
108
109/// Returns true only if fdt has ramdump prop in the /avf/guest/common node with value <1>
110fn is_ramdump_enabled(fdt: &libfdt::Fdt) -> Result<bool, DebugPolicyError> {
111 let common = match fdt
112 .node(CStr::from_bytes_with_nul(b"/avf/guest/common\0").unwrap())
113 .map_err(|e| DebugPolicyError::DebugPolicyFdt("Failed to find /avf/guest/common node", e))?
114 {
115 Some(node) => node,
116 None => return Ok(false),
117 };
118
119 match common
120 .getprop_u32(CStr::from_bytes_with_nul(b"ramdump\0").unwrap())
121 .map_err(|e| DebugPolicyError::DebugPolicyFdt("Failed to find ramdump prop", e))?
122 {
123 Some(1) => Ok(true),
124 _ => Ok(false),
125 }
126}
127
Jaewan Kimc03f6612023-02-20 00:06:26 +0900128/// Enables console output by adding kernel.printk.devkmsg and kernel.console to bootargs.
129/// This uses hardcoded console name 'hvc0' and it should be match with microdroid's bootconfig.debuggable.
130fn enable_console_output(fdt: &mut libfdt::Fdt) -> Result<(), DebugPolicyError> {
131 let chosen_path = CStr::from_bytes_with_nul(b"/chosen\0").unwrap();
132 let bootargs_name = CStr::from_bytes_with_nul(b"bootargs\0").unwrap();
133
134 let chosen = match fdt
135 .node(chosen_path)
136 .map_err(|e| DebugPolicyError::Fdt("Failed to find /chosen", e))?
137 {
138 Some(node) => node,
139 None => return Ok(()),
140 };
141
142 let bootargs = match chosen
143 .getprop_str(bootargs_name)
144 .map_err(|e| DebugPolicyError::Fdt("Failed to find bootargs prop", e))?
145 {
146 Some(value) if !value.to_bytes().is_empty() => value,
147 _ => return Ok(()),
148 };
149
150 let mut new_bootargs = Vec::from(bootargs.to_bytes());
151 new_bootargs.extend_from_slice(b" printk.devkmsg=on console=hvc0\0");
152
153 // We'll set larger prop, and need to prepare some room first.
154 fdt.unpack().map_err(|e| DebugPolicyError::OverlaidFdt("Failed to unpack", e))?;
155
156 // We've checked existence of /chosen node at the beginning.
157 let mut chosen_mut = fdt.node_mut(chosen_path).unwrap().unwrap();
158 chosen_mut.setprop(bootargs_name, new_bootargs.as_slice()).map_err(|e| {
159 DebugPolicyError::OverlaidFdt("Failed to enabled console output. FDT might be corrupted", e)
160 })?;
161
162 fdt.pack().map_err(|e| DebugPolicyError::OverlaidFdt("Failed to pack", e))?;
163 Ok(())
164}
165
166/// Returns true only if fdt has log prop in the /avf/guest/common node with value <1>
167fn is_console_output_enabled(fdt: &libfdt::Fdt) -> Result<bool, DebugPolicyError> {
168 let common = match fdt
169 .node(CStr::from_bytes_with_nul(b"/avf/guest/common\0").unwrap())
170 .map_err(|e| DebugPolicyError::DebugPolicyFdt("Failed to find /avf/guest/common node", e))?
171 {
172 Some(node) => node,
173 None => return Ok(false),
174 };
175
176 match common
177 .getprop_u32(CStr::from_bytes_with_nul(b"log\0").unwrap())
178 .map_err(|e| DebugPolicyError::DebugPolicyFdt("Failed to find log prop", e))?
179 {
180 Some(1) => Ok(true),
181 _ => Ok(false),
182 }
183}
184
Jaewan Kimba8929b2023-01-13 11:13:29 +0900185/// Handles debug policies.
186///
187/// # Safety
188///
189/// This may corrupt the input `Fdt` when overlaying debug policy or applying
190/// ramdump configuration.
191pub unsafe fn handle_debug_policy(
192 fdt: &mut libfdt::Fdt,
193 debug_policy: Option<&mut [u8]>,
194) -> Result<(), DebugPolicyError> {
195 if let Some(dp) = debug_policy {
196 apply_debug_policy(fdt, dp)?;
197 }
198
199 // Handles ramdump in the debug policy
200 if is_ramdump_enabled(fdt)? {
201 info!("ramdump is enabled by debug policy");
Jaewan Kimc03f6612023-02-20 00:06:26 +0900202 } else {
203 disable_ramdump(fdt)?;
Jaewan Kimba8929b2023-01-13 11:13:29 +0900204 }
Jaewan Kimc03f6612023-02-20 00:06:26 +0900205
Jaewan Kim66f062e2023-02-25 01:07:43 +0900206 // Handles console output in the debug policy
Jaewan Kimc03f6612023-02-20 00:06:26 +0900207 if is_console_output_enabled(fdt)? {
208 enable_console_output(fdt)?;
209 info!("console output is enabled by debug policy");
210 }
211 Ok(())
Jaewan Kimba8929b2023-01-13 11:13:29 +0900212}