arch/
serial.rs

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
5use std::collections::BTreeMap;
6
7#[cfg(feature = "seccomp_trace")]
8use base::debug;
9use base::Event;
10use devices::serial_device::SerialHardware;
11use devices::serial_device::SerialParameters;
12use devices::serial_device::SerialType;
13use devices::Bus;
14use devices::Serial;
15use hypervisor::ProtectionType;
16#[cfg(feature = "seccomp_trace")]
17use jail::read_jail_addr;
18#[cfg(windows)]
19use jail::FakeMinijailStub as Minijail;
20#[cfg(any(target_os = "android", target_os = "linux"))]
21use minijail::Minijail;
22use remain::sorted;
23use thiserror::Error as ThisError;
24
25use crate::DeviceRegistrationError;
26
27mod 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.
37pub 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
84pub const SERIAL_ADDR: [u64; 4] = [0x3f8, 0x2f8, 0x3e8, 0x2e8];
85
86/// Information about a serial device (16550-style UART) created by `add_serial_devices()`.
87pub 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.
112pub 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)]
182pub 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
189pub 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.
195pub 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)]
247mod tests {
248    use devices::BusType;
249    use kernel_cmdline::Cmdline;
250
251    use super::*;
252
253    #[test]
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]
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]
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]
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}