1 // Copyright 2020 The ChromiumOS Authors
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
4
5 use std::collections::BTreeMap;
6
7 #[cfg(feature = "seccomp_trace")]
8 use base::debug;
9 use base::Event;
10 use devices::serial_device::SerialHardware;
11 use devices::serial_device::SerialParameters;
12 use devices::serial_device::SerialType;
13 use devices::Bus;
14 use devices::Serial;
15 use hypervisor::ProtectionType;
16 #[cfg(feature = "seccomp_trace")]
17 use jail::read_jail_addr;
18 #[cfg(windows)]
19 use jail::FakeMinijailStub as Minijail;
20 #[cfg(unix)]
21 use minijail::Minijail;
22 use remain::sorted;
23 use thiserror::Error as ThisError;
24
25 use crate::DeviceRegistrationError;
26
27 mod sys;
28
29 /// Add the default serial parameters for serial ports that have not already been specified.
30 ///
31 /// This ensures that `serial_parameters` will contain parameters for each of the four PC-style
32 /// serial ports (COM1-COM4).
33 ///
34 /// It also sets the first `SerialHardware::Serial` to be the default console device if no other
35 /// serial parameters exist with console=true and the first serial device has not already been
36 /// configured explicitly.
set_default_serial_parameters( serial_parameters: &mut BTreeMap<(SerialHardware, u8), SerialParameters>, is_vhost_user_console_enabled: bool, )37 pub fn set_default_serial_parameters(
38 serial_parameters: &mut BTreeMap<(SerialHardware, u8), SerialParameters>,
39 is_vhost_user_console_enabled: bool,
40 ) {
41 // If no console device exists and the first serial port has not been specified,
42 // set the first serial port as a stdout+stdin console.
43 let default_console = (SerialHardware::Serial, 1);
44 if !serial_parameters.iter().any(|(_, p)| p.console) && !is_vhost_user_console_enabled {
45 serial_parameters
46 .entry(default_console)
47 .or_insert(SerialParameters {
48 type_: SerialType::Stdout,
49 hardware: SerialHardware::Serial,
50 path: None,
51 input: None,
52 num: 1,
53 console: true,
54 earlycon: false,
55 stdin: true,
56 out_timestamp: false,
57 ..Default::default()
58 });
59 }
60
61 // Ensure all four of the COM ports exist.
62 // If one of these four SerialHardware::Serial port was not configured by the user,
63 // set it up as a sink.
64 for num in 1..=4 {
65 let key = (SerialHardware::Serial, num);
66 serial_parameters.entry(key).or_insert(SerialParameters {
67 type_: SerialType::Sink,
68 hardware: SerialHardware::Serial,
69 path: None,
70 input: None,
71 num,
72 console: false,
73 earlycon: false,
74 stdin: false,
75 out_timestamp: false,
76 ..Default::default()
77 });
78 }
79 }
80
81 /// Address for Serial ports in x86
82 pub const SERIAL_ADDR: [u64; 4] = [0x3f8, 0x2f8, 0x3e8, 0x2e8];
83
84 /// Adds serial devices to the provided bus based on the serial parameters given.
85 ///
86 /// Only devices with hardware type `SerialHardware::Serial` are added by this function.
87 ///
88 /// # Arguments
89 ///
90 /// * `protection_type` - VM protection mode.
91 /// * `io_bus` - Bus to add the devices to
92 /// * `com_evt_1_3` - event for com1 and com3
93 /// * `com_evt_1_4` - event for com2 and com4
94 /// * `serial_parameters` - definitions of serial parameter configurations.
95 /// * `serial_jail` - minijail object cloned for use with each serial device.
96 /// All four of the traditional PC-style serial ports (COM1-COM4) must be specified.
add_serial_devices( protection_type: ProtectionType, io_bus: &Bus, com_evt_1_3: &Event, com_evt_2_4: &Event, serial_parameters: &BTreeMap<(SerialHardware, u8), SerialParameters>, #[cfg_attr(windows, allow(unused_variables))] serial_jail: Option<Minijail>, #[cfg(feature = "swap")] swap_controller: Option<&swap::SwapController>, ) -> std::result::Result<(), DeviceRegistrationError>97 pub fn add_serial_devices(
98 protection_type: ProtectionType,
99 io_bus: &Bus,
100 com_evt_1_3: &Event,
101 com_evt_2_4: &Event,
102 serial_parameters: &BTreeMap<(SerialHardware, u8), SerialParameters>,
103 #[cfg_attr(windows, allow(unused_variables))] serial_jail: Option<Minijail>,
104 #[cfg(feature = "swap")] swap_controller: Option<&swap::SwapController>,
105 ) -> std::result::Result<(), DeviceRegistrationError> {
106 for com_num in 0..=3 {
107 let com_evt = match com_num {
108 0 => &com_evt_1_3,
109 1 => &com_evt_2_4,
110 2 => &com_evt_1_3,
111 3 => &com_evt_2_4,
112 _ => &com_evt_1_3,
113 };
114
115 let param = serial_parameters
116 .get(&(SerialHardware::Serial, com_num + 1))
117 .ok_or(DeviceRegistrationError::MissingRequiredSerialDevice(
118 com_num + 1,
119 ))?;
120
121 let mut preserved_descriptors = Vec::new();
122 let com = param
123 .create_serial_device::<Serial>(protection_type, com_evt, &mut preserved_descriptors)
124 .map_err(DeviceRegistrationError::CreateSerialDevice)?;
125
126 #[cfg(unix)]
127 let serial_jail = if let Some(serial_jail) = serial_jail.as_ref() {
128 let jail_clone = serial_jail
129 .try_clone()
130 .map_err(DeviceRegistrationError::CloneJail)?;
131 #[cfg(feature = "seccomp_trace")]
132 debug!(
133 "seccomp_trace {{\"event\": \"minijail_clone\", \"src_jail_addr\": \"0x{:x}\", \"dst_jail_addr\": \"0x{:x}\"}}",
134 read_jail_addr(serial_jail),
135 read_jail_addr(&jail_clone)
136 );
137 Some(jail_clone)
138 } else {
139 None
140 };
141 #[cfg(windows)]
142 let serial_jail = None;
143
144 sys::add_serial_device(
145 com_num as usize,
146 com,
147 param,
148 serial_jail,
149 preserved_descriptors,
150 io_bus,
151 #[cfg(feature = "swap")]
152 swap_controller,
153 )?;
154 }
155
156 Ok(())
157 }
158
159 #[sorted]
160 #[derive(ThisError, Debug)]
161 pub enum GetSerialCmdlineError {
162 #[error("Error appending to cmdline: {0}")]
163 KernelCmdline(kernel_cmdline::Error),
164 #[error("Hardware {0} not supported as earlycon")]
165 UnsupportedEarlyconHardware(SerialHardware),
166 }
167
168 pub type GetSerialCmdlineResult<T> = std::result::Result<T, GetSerialCmdlineError>;
169
170 /// Add serial options to the provided `cmdline` based on `serial_parameters`.
171 /// `serial_io_type` should be "io" if the platform uses x86-style I/O ports for serial devices
172 /// or "mmio" if the serial ports are memory mapped.
173 // TODO(b/227407433): Support cases where vhost-user console is specified.
get_serial_cmdline( cmdline: &mut kernel_cmdline::Cmdline, serial_parameters: &BTreeMap<(SerialHardware, u8), SerialParameters>, serial_io_type: &str, ) -> GetSerialCmdlineResult<()>174 pub fn get_serial_cmdline(
175 cmdline: &mut kernel_cmdline::Cmdline,
176 serial_parameters: &BTreeMap<(SerialHardware, u8), SerialParameters>,
177 serial_io_type: &str,
178 ) -> GetSerialCmdlineResult<()> {
179 match serial_parameters
180 .iter()
181 .filter(|(_, p)| p.console)
182 .map(|(k, _)| k)
183 .next()
184 {
185 Some((SerialHardware::Serial, num)) => {
186 cmdline
187 .insert("console", &format!("ttyS{}", num - 1))
188 .map_err(GetSerialCmdlineError::KernelCmdline)?;
189 }
190 Some((SerialHardware::VirtioConsole, num)) => {
191 cmdline
192 .insert("console", &format!("hvc{}", num - 1))
193 .map_err(GetSerialCmdlineError::KernelCmdline)?;
194 }
195 Some((SerialHardware::Debugcon, _)) => {}
196 None => {}
197 }
198
199 match serial_parameters
200 .iter()
201 .filter(|(_, p)| p.earlycon)
202 .map(|(k, _)| k)
203 .next()
204 {
205 Some((SerialHardware::Serial, num)) => {
206 if let Some(addr) = SERIAL_ADDR.get(*num as usize - 1) {
207 cmdline
208 .insert(
209 "earlycon",
210 &format!("uart8250,{},0x{:x}", serial_io_type, addr),
211 )
212 .map_err(GetSerialCmdlineError::KernelCmdline)?;
213 }
214 }
215 Some((hw, _num)) => {
216 return Err(GetSerialCmdlineError::UnsupportedEarlyconHardware(*hw));
217 }
218 None => {}
219 }
220
221 Ok(())
222 }
223
224 #[cfg(test)]
225 mod tests {
226 use kernel_cmdline::Cmdline;
227
228 use super::*;
229
230 #[test]
get_serial_cmdline_default()231 fn get_serial_cmdline_default() {
232 let mut cmdline = Cmdline::new(4096);
233 let mut serial_parameters = BTreeMap::new();
234
235 set_default_serial_parameters(&mut serial_parameters, false);
236 get_serial_cmdline(&mut cmdline, &serial_parameters, "io")
237 .expect("get_serial_cmdline failed");
238
239 let cmdline_str = cmdline.as_str();
240 assert!(cmdline_str.contains("console=ttyS0"));
241 }
242
243 #[test]
get_serial_cmdline_virtio_console()244 fn get_serial_cmdline_virtio_console() {
245 let mut cmdline = Cmdline::new(4096);
246 let mut serial_parameters = BTreeMap::new();
247
248 // Add a virtio-console device with console=true.
249 serial_parameters.insert(
250 (SerialHardware::VirtioConsole, 1),
251 SerialParameters {
252 type_: SerialType::Stdout,
253 hardware: SerialHardware::VirtioConsole,
254 path: None,
255 input: None,
256 num: 1,
257 console: true,
258 earlycon: false,
259 stdin: true,
260 out_timestamp: false,
261 debugcon_port: 0,
262 },
263 );
264
265 set_default_serial_parameters(&mut serial_parameters, false);
266 get_serial_cmdline(&mut cmdline, &serial_parameters, "io")
267 .expect("get_serial_cmdline failed");
268
269 let cmdline_str = cmdline.as_str();
270 assert!(cmdline_str.contains("console=hvc0"));
271 }
272
273 #[test]
get_serial_cmdline_virtio_console_serial_earlycon()274 fn get_serial_cmdline_virtio_console_serial_earlycon() {
275 let mut cmdline = Cmdline::new(4096);
276 let mut serial_parameters = BTreeMap::new();
277
278 // Add a virtio-console device with console=true.
279 serial_parameters.insert(
280 (SerialHardware::VirtioConsole, 1),
281 SerialParameters {
282 type_: SerialType::Stdout,
283 hardware: SerialHardware::VirtioConsole,
284 path: None,
285 input: None,
286 num: 1,
287 console: true,
288 earlycon: false,
289 stdin: true,
290 out_timestamp: false,
291 debugcon_port: 0,
292 },
293 );
294
295 // Override the default COM1 with an earlycon device.
296 serial_parameters.insert(
297 (SerialHardware::Serial, 1),
298 SerialParameters {
299 type_: SerialType::Stdout,
300 hardware: SerialHardware::Serial,
301 path: None,
302 input: None,
303 num: 1,
304 console: false,
305 earlycon: true,
306 stdin: false,
307 out_timestamp: false,
308 debugcon_port: 0,
309 },
310 );
311
312 set_default_serial_parameters(&mut serial_parameters, false);
313 get_serial_cmdline(&mut cmdline, &serial_parameters, "io")
314 .expect("get_serial_cmdline failed");
315
316 let cmdline_str = cmdline.as_str();
317 assert!(cmdline_str.contains("console=hvc0"));
318 assert!(cmdline_str.contains("earlycon=uart8250,io,0x3f8"));
319 }
320
321 #[test]
get_serial_cmdline_virtio_console_invalid_earlycon()322 fn get_serial_cmdline_virtio_console_invalid_earlycon() {
323 let mut cmdline = Cmdline::new(4096);
324 let mut serial_parameters = BTreeMap::new();
325
326 // Try to add a virtio-console device with earlycon=true (unsupported).
327 serial_parameters.insert(
328 (SerialHardware::VirtioConsole, 1),
329 SerialParameters {
330 type_: SerialType::Stdout,
331 hardware: SerialHardware::VirtioConsole,
332 path: None,
333 input: None,
334 num: 1,
335 console: false,
336 earlycon: true,
337 stdin: true,
338 out_timestamp: false,
339 debugcon_port: 0,
340 },
341 );
342
343 set_default_serial_parameters(&mut serial_parameters, false);
344 get_serial_cmdline(&mut cmdline, &serial_parameters, "io")
345 .expect_err("get_serial_cmdline succeeded");
346 }
347 }
348