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(any(target_os = "android", target_os = "linux"))]
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 name: None,
51 path: None,
52 input: None,
53 num: 1,
54 console: true,
55 earlycon: false,
56 stdin: true,
57 out_timestamp: false,
58 ..Default::default()
59 });
60 }
61
62 // Ensure all four of the COM ports exist.
63 // If one of these four SerialHardware::Serial port was not configured by the user,
64 // set it up as a sink.
65 for num in 1..=4 {
66 let key = (SerialHardware::Serial, num);
67 serial_parameters.entry(key).or_insert(SerialParameters {
68 type_: SerialType::Sink,
69 hardware: SerialHardware::Serial,
70 name: None,
71 path: None,
72 input: None,
73 num,
74 console: false,
75 earlycon: false,
76 stdin: false,
77 out_timestamp: false,
78 ..Default::default()
79 });
80 }
81 }
82
83 /// Address for Serial ports in x86
84 pub const SERIAL_ADDR: [u64; 4] = [0x3f8, 0x2f8, 0x3e8, 0x2e8];
85
86 /// Information about a serial device (16550-style UART) created by `add_serial_devices()`.
87 pub struct SerialDeviceInfo {
88 /// Address of the device on the bus.
89 /// This is the I/O bus on x86 machines and MMIO otherwise.
90 pub address: u64,
91
92 /// Size of the device's address space on the bus.
93 pub size: u64,
94
95 /// IRQ number of the device.
96 pub irq: u32,
97 }
98
99 /// Adds serial devices to the provided bus based on the serial parameters given.
100 ///
101 /// Only devices with hardware type `SerialHardware::Serial` are added by this function.
102 ///
103 /// # Arguments
104 ///
105 /// * `protection_type` - VM protection mode.
106 /// * `io_bus` - Bus to add the devices to
107 /// * `com_evt_1_3` - irq and event for com1 and com3
108 /// * `com_evt_1_4` - irq and event for com2 and com4
109 /// * `serial_parameters` - definitions of serial parameter configurations.
110 /// * `serial_jail` - minijail object cloned for use with each serial device. All four of the
111 /// traditional PC-style serial ports (COM1-COM4) must be specified.
add_serial_devices( protection_type: ProtectionType, io_bus: &Bus, com_evt_1_3: (u32, &Event), com_evt_2_4: (u32, &Event), serial_parameters: &BTreeMap<(SerialHardware, u8), SerialParameters>, #[cfg_attr(windows, allow(unused_variables))] serial_jail: Option<Minijail>, #[cfg(feature = "swap")] swap_controller: &mut Option<swap::SwapController>, ) -> std::result::Result<Vec<SerialDeviceInfo>, DeviceRegistrationError>112 pub fn add_serial_devices(
113 protection_type: ProtectionType,
114 io_bus: &Bus,
115 com_evt_1_3: (u32, &Event),
116 com_evt_2_4: (u32, &Event),
117 serial_parameters: &BTreeMap<(SerialHardware, u8), SerialParameters>,
118 #[cfg_attr(windows, allow(unused_variables))] serial_jail: Option<Minijail>,
119 #[cfg(feature = "swap")] swap_controller: &mut Option<swap::SwapController>,
120 ) -> std::result::Result<Vec<SerialDeviceInfo>, DeviceRegistrationError> {
121 let mut devices = Vec::new();
122 for com_num in 0..=3 {
123 let com_evt = match com_num {
124 0 => &com_evt_1_3,
125 1 => &com_evt_2_4,
126 2 => &com_evt_1_3,
127 3 => &com_evt_2_4,
128 _ => &com_evt_1_3,
129 };
130
131 let (irq, com_evt) = (com_evt.0, com_evt.1);
132
133 let param = serial_parameters
134 .get(&(SerialHardware::Serial, com_num + 1))
135 .ok_or(DeviceRegistrationError::MissingRequiredSerialDevice(
136 com_num + 1,
137 ))?;
138
139 let mut preserved_descriptors = Vec::new();
140 let com = param
141 .create_serial_device::<Serial>(protection_type, com_evt, &mut preserved_descriptors)
142 .map_err(DeviceRegistrationError::CreateSerialDevice)?;
143
144 #[cfg(any(target_os = "android", target_os = "linux"))]
145 let serial_jail = if let Some(serial_jail) = serial_jail.as_ref() {
146 let jail_clone = serial_jail
147 .try_clone()
148 .map_err(DeviceRegistrationError::CloneJail)?;
149 #[cfg(feature = "seccomp_trace")]
150 debug!(
151 "seccomp_trace {{\"event\": \"minijail_clone\", \"src_jail_addr\": \"0x{:x}\", \"dst_jail_addr\": \"0x{:x}\"}}",
152 read_jail_addr(serial_jail),
153 read_jail_addr(&jail_clone)
154 );
155 Some(jail_clone)
156 } else {
157 None
158 };
159 #[cfg(windows)]
160 let serial_jail = None;
161
162 let com = sys::add_serial_device(
163 com,
164 param,
165 serial_jail,
166 preserved_descriptors,
167 #[cfg(feature = "swap")]
168 swap_controller,
169 )?;
170
171 let address = SERIAL_ADDR[usize::from(com_num)];
172 let size = 0x8; // 16550 UART uses 8 bytes of address space.
173 io_bus.insert(com, address, size).unwrap();
174 devices.push(SerialDeviceInfo { address, size, irq })
175 }
176
177 Ok(devices)
178 }
179
180 #[sorted]
181 #[derive(ThisError, Debug)]
182 pub enum GetSerialCmdlineError {
183 #[error("Error appending to cmdline: {0}")]
184 KernelCmdline(kernel_cmdline::Error),
185 #[error("Hardware {0} not supported as earlycon")]
186 UnsupportedEarlyconHardware(SerialHardware),
187 }
188
189 pub type GetSerialCmdlineResult<T> = std::result::Result<T, GetSerialCmdlineError>;
190
191 /// Add serial options to the provided `cmdline` based on `serial_parameters`.
192 /// `serial_io_type` should be "io" if the platform uses x86-style I/O ports for serial devices
193 /// or "mmio" if the serial ports are memory mapped.
194 // 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, serial_devices: &[SerialDeviceInfo], ) -> GetSerialCmdlineResult<()>195 pub fn get_serial_cmdline(
196 cmdline: &mut kernel_cmdline::Cmdline,
197 serial_parameters: &BTreeMap<(SerialHardware, u8), SerialParameters>,
198 serial_io_type: &str,
199 serial_devices: &[SerialDeviceInfo],
200 ) -> GetSerialCmdlineResult<()> {
201 for serial_parameter in serial_parameters
202 .iter()
203 .filter(|(_, p)| p.console)
204 .map(|(k, _)| k)
205 {
206 match serial_parameter {
207 (SerialHardware::Serial, num) => {
208 cmdline
209 .insert("console", &format!("ttyS{}", num - 1))
210 .map_err(GetSerialCmdlineError::KernelCmdline)?;
211 }
212 (SerialHardware::VirtioConsole, num) => {
213 cmdline
214 .insert("console", &format!("hvc{}", num - 1))
215 .map_err(GetSerialCmdlineError::KernelCmdline)?;
216 }
217 (SerialHardware::Debugcon, _) => {}
218 }
219 }
220
221 match serial_parameters
222 .iter()
223 .filter(|(_, p)| p.earlycon)
224 .map(|(k, _)| k)
225 .next()
226 {
227 Some((SerialHardware::Serial, num)) => {
228 if let Some(serial_device) = serial_devices.get(*num as usize - 1) {
229 cmdline
230 .insert(
231 "earlycon",
232 &format!("uart8250,{},0x{:x}", serial_io_type, serial_device.address),
233 )
234 .map_err(GetSerialCmdlineError::KernelCmdline)?;
235 }
236 }
237 Some((hw, _num)) => {
238 return Err(GetSerialCmdlineError::UnsupportedEarlyconHardware(*hw));
239 }
240 None => {}
241 }
242
243 Ok(())
244 }
245
246 #[cfg(test)]
247 mod tests {
248 use devices::BusType;
249 use kernel_cmdline::Cmdline;
250
251 use super::*;
252
253 #[test]
get_serial_cmdline_default()254 fn get_serial_cmdline_default() {
255 let mut cmdline = Cmdline::new();
256 let mut serial_parameters = BTreeMap::new();
257 let io_bus = Bus::new(BusType::Io);
258 let evt1_3 = Event::new().unwrap();
259 let evt2_4 = Event::new().unwrap();
260
261 set_default_serial_parameters(&mut serial_parameters, false);
262 let serial_devices = add_serial_devices(
263 ProtectionType::Unprotected,
264 &io_bus,
265 (4, &evt1_3),
266 (3, &evt2_4),
267 &serial_parameters,
268 None,
269 #[cfg(feature = "swap")]
270 &mut None,
271 )
272 .unwrap();
273 get_serial_cmdline(&mut cmdline, &serial_parameters, "io", &serial_devices)
274 .expect("get_serial_cmdline failed");
275
276 let cmdline_str = cmdline.as_str();
277 assert!(cmdline_str.contains("console=ttyS0"));
278 }
279
280 #[test]
get_serial_cmdline_virtio_console()281 fn get_serial_cmdline_virtio_console() {
282 let mut cmdline = Cmdline::new();
283 let mut serial_parameters = BTreeMap::new();
284 let io_bus = Bus::new(BusType::Io);
285 let evt1_3 = Event::new().unwrap();
286 let evt2_4 = Event::new().unwrap();
287
288 // Add a virtio-console device with console=true.
289 serial_parameters.insert(
290 (SerialHardware::VirtioConsole, 1),
291 SerialParameters {
292 type_: SerialType::Stdout,
293 hardware: SerialHardware::VirtioConsole,
294 num: 1,
295 console: true,
296 stdin: true,
297 ..Default::default()
298 },
299 );
300
301 set_default_serial_parameters(&mut serial_parameters, false);
302 let serial_devices = add_serial_devices(
303 ProtectionType::Unprotected,
304 &io_bus,
305 (4, &evt1_3),
306 (3, &evt2_4),
307 &serial_parameters,
308 None,
309 #[cfg(feature = "swap")]
310 &mut None,
311 )
312 .unwrap();
313 get_serial_cmdline(&mut cmdline, &serial_parameters, "io", &serial_devices)
314 .expect("get_serial_cmdline failed");
315
316 let cmdline_str = cmdline.as_str();
317 assert!(cmdline_str.contains("console=hvc0"));
318 }
319
320 #[test]
get_serial_cmdline_virtio_console_serial_earlycon()321 fn get_serial_cmdline_virtio_console_serial_earlycon() {
322 let mut cmdline = Cmdline::new();
323 let mut serial_parameters = BTreeMap::new();
324 let io_bus = Bus::new(BusType::Io);
325 let evt1_3 = Event::new().unwrap();
326 let evt2_4 = Event::new().unwrap();
327
328 // Add a virtio-console device with console=true.
329 serial_parameters.insert(
330 (SerialHardware::VirtioConsole, 1),
331 SerialParameters {
332 type_: SerialType::Stdout,
333 hardware: SerialHardware::VirtioConsole,
334 num: 1,
335 console: true,
336 stdin: true,
337 ..Default::default()
338 },
339 );
340
341 // Override the default COM1 with an earlycon device.
342 serial_parameters.insert(
343 (SerialHardware::Serial, 1),
344 SerialParameters {
345 type_: SerialType::Stdout,
346 hardware: SerialHardware::Serial,
347 num: 1,
348 earlycon: true,
349 ..Default::default()
350 },
351 );
352
353 set_default_serial_parameters(&mut serial_parameters, false);
354 let serial_devices = add_serial_devices(
355 ProtectionType::Unprotected,
356 &io_bus,
357 (4, &evt1_3),
358 (3, &evt2_4),
359 &serial_parameters,
360 None,
361 #[cfg(feature = "swap")]
362 &mut None,
363 )
364 .unwrap();
365 get_serial_cmdline(&mut cmdline, &serial_parameters, "io", &serial_devices)
366 .expect("get_serial_cmdline failed");
367
368 let cmdline_str = cmdline.as_str();
369 assert!(cmdline_str.contains("console=hvc0"));
370 assert!(cmdline_str.contains("earlycon=uart8250,io,0x3f8"));
371 }
372
373 #[test]
get_serial_cmdline_virtio_console_invalid_earlycon()374 fn get_serial_cmdline_virtio_console_invalid_earlycon() {
375 let mut cmdline = Cmdline::new();
376 let mut serial_parameters = BTreeMap::new();
377 let io_bus = Bus::new(BusType::Io);
378 let evt1_3 = Event::new().unwrap();
379 let evt2_4 = Event::new().unwrap();
380
381 // Try to add a virtio-console device with earlycon=true (unsupported).
382 serial_parameters.insert(
383 (SerialHardware::VirtioConsole, 1),
384 SerialParameters {
385 type_: SerialType::Stdout,
386 hardware: SerialHardware::VirtioConsole,
387 num: 1,
388 earlycon: true,
389 stdin: true,
390 ..Default::default()
391 },
392 );
393
394 set_default_serial_parameters(&mut serial_parameters, false);
395 let serial_devices = add_serial_devices(
396 ProtectionType::Unprotected,
397 &io_bus,
398 (4, &evt1_3),
399 (3, &evt2_4),
400 &serial_parameters,
401 None,
402 #[cfg(feature = "swap")]
403 &mut None,
404 )
405 .unwrap();
406 get_serial_cmdline(&mut cmdline, &serial_parameters, "io", &serial_devices)
407 .expect_err("get_serial_cmdline succeeded");
408 }
409 }
410