• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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