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