xref: /aosp_15_r20/external/crosvm/arch/src/serial.rs (revision bb4ee6a4ae7042d18b07a98463b9c8b875e44b39)
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