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