blob: 9058bd47053038eb81cf01a596f648adfb55a1cb [file] [log] [blame]
// Copyright 2020 The Chromium OS Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
use std::collections::BTreeMap;
use base::Event;
use devices::serial_device::{SerialHardware, SerialParameters, SerialType};
#[cfg(windows)]
use devices::Minijail;
use devices::{Bus, Serial};
use hypervisor::ProtectionType;
#[cfg(unix)]
use minijail::Minijail;
use remain::sorted;
use thiserror::Error as ThisError;
use crate::DeviceRegistrationError;
mod sys;
/// Add the default serial parameters for serial ports that have not already been specified.
///
/// This ensures that `serial_parameters` will contain parameters for each of the four PC-style
/// serial ports (COM1-COM4).
///
/// It also sets the first `SerialHardware::Serial` to be the default console device if no other
/// serial parameters exist with console=true and the first serial device has not already been
/// configured explicitly.
pub fn set_default_serial_parameters(
serial_parameters: &mut BTreeMap<(SerialHardware, u8), SerialParameters>,
is_vhost_user_console_enabled: bool,
) {
// If no console device exists and the first serial port has not been specified,
// set the first serial port as a stdout+stdin console.
let default_console = (SerialHardware::Serial, 1);
if !serial_parameters.iter().any(|(_, p)| p.console) && !is_vhost_user_console_enabled {
serial_parameters
.entry(default_console)
.or_insert(SerialParameters {
type_: SerialType::Stdout,
hardware: SerialHardware::Serial,
path: None,
input: None,
num: 1,
console: true,
earlycon: false,
stdin: true,
out_timestamp: false,
..Default::default()
});
}
// Ensure all four of the COM ports exist.
// If one of these four SerialHardware::Serial port was not configured by the user,
// set it up as a sink.
for num in 1..=4 {
let key = (SerialHardware::Serial, num);
serial_parameters.entry(key).or_insert(SerialParameters {
type_: SerialType::Sink,
hardware: SerialHardware::Serial,
path: None,
input: None,
num,
console: false,
earlycon: false,
stdin: false,
out_timestamp: false,
..Default::default()
});
}
}
/// Address for Serial ports in x86
pub const SERIAL_ADDR: [u64; 4] = [0x3f8, 0x2f8, 0x3e8, 0x2e8];
/// Adds serial devices to the provided bus based on the serial parameters given.
///
/// Only devices with hardware type `SerialHardware::Serial` are added by this function.
///
/// # Arguments
///
/// * `protected_vm` - VM protection mode.
/// * `io_bus` - Bus to add the devices to
/// * `com_evt_1_3` - event for com1 and com3
/// * `com_evt_1_4` - event for com2 and com4
/// * `serial_parameters` - definitions of serial parameter configurations.
/// * `serial_jail` - minijail object cloned for use with each serial device.
/// All four of the traditional PC-style serial ports (COM1-COM4) must be specified.
pub fn 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>,
) -> std::result::Result<(), DeviceRegistrationError> {
for com_num in 0..=3 {
let com_evt = match com_num {
0 => &com_evt_1_3,
1 => &com_evt_2_4,
2 => &com_evt_1_3,
3 => &com_evt_2_4,
_ => &com_evt_1_3,
};
let param = serial_parameters
.get(&(SerialHardware::Serial, com_num + 1))
.ok_or(DeviceRegistrationError::MissingRequiredSerialDevice(
com_num + 1,
))?;
let mut preserved_descriptors = Vec::new();
let com = param
.create_serial_device::<Serial>(protected_vm, com_evt, &mut preserved_descriptors)
.map_err(DeviceRegistrationError::CreateSerialDevice)?;
sys::add_serial_device(
com_num as usize,
com,
param,
serial_jail.as_ref(),
preserved_descriptors,
io_bus,
)?;
}
Ok(())
}
#[sorted]
#[derive(ThisError, Debug)]
pub enum GetSerialCmdlineError {
#[error("Error appending to cmdline: {0}")]
KernelCmdline(kernel_cmdline::Error),
#[error("Hardware {0} not supported as earlycon")]
UnsupportedEarlyconHardware(SerialHardware),
}
pub type GetSerialCmdlineResult<T> = std::result::Result<T, GetSerialCmdlineError>;
/// Add serial options to the provided `cmdline` based on `serial_parameters`.
/// `serial_io_type` should be "io" if the platform uses x86-style I/O ports for serial devices
/// or "mmio" if the serial ports are memory mapped.
// TODO(b/227407433): Support cases where vhost-user console is specified.
pub fn get_serial_cmdline(
cmdline: &mut kernel_cmdline::Cmdline,
serial_parameters: &BTreeMap<(SerialHardware, u8), SerialParameters>,
serial_io_type: &str,
) -> GetSerialCmdlineResult<()> {
match serial_parameters
.iter()
.filter(|(_, p)| p.console)
.map(|(k, _)| k)
.next()
{
Some((SerialHardware::Serial, num)) => {
cmdline
.insert("console", &format!("ttyS{}", num - 1))
.map_err(GetSerialCmdlineError::KernelCmdline)?;
}
Some((SerialHardware::VirtioConsole, num)) => {
cmdline
.insert("console", &format!("hvc{}", num - 1))
.map_err(GetSerialCmdlineError::KernelCmdline)?;
}
Some((SerialHardware::Debugcon, _)) => {}
None => {}
}
match serial_parameters
.iter()
.filter(|(_, p)| p.earlycon)
.map(|(k, _)| k)
.next()
{
Some((SerialHardware::Serial, num)) => {
if let Some(addr) = SERIAL_ADDR.get(*num as usize - 1) {
cmdline
.insert(
"earlycon",
&format!("uart8250,{},0x{:x}", serial_io_type, addr),
)
.map_err(GetSerialCmdlineError::KernelCmdline)?;
}
}
Some((hw, _num)) => {
return Err(GetSerialCmdlineError::UnsupportedEarlyconHardware(*hw));
}
None => {}
}
Ok(())
}
#[cfg(test)]
mod tests {
use super::*;
use kernel_cmdline::Cmdline;
#[test]
fn get_serial_cmdline_default() {
let mut cmdline = Cmdline::new(4096);
let mut serial_parameters = BTreeMap::new();
set_default_serial_parameters(&mut serial_parameters, false);
get_serial_cmdline(&mut cmdline, &serial_parameters, "io")
.expect("get_serial_cmdline failed");
let cmdline_str = cmdline.as_str();
assert!(cmdline_str.contains("console=ttyS0"));
}
#[test]
fn get_serial_cmdline_virtio_console() {
let mut cmdline = Cmdline::new(4096);
let mut serial_parameters = BTreeMap::new();
// Add a virtio-console device with console=true.
serial_parameters.insert(
(SerialHardware::VirtioConsole, 1),
SerialParameters {
type_: SerialType::Stdout,
hardware: SerialHardware::VirtioConsole,
path: None,
input: None,
num: 1,
console: true,
earlycon: false,
stdin: true,
out_timestamp: false,
debugcon_port: 0,
},
);
set_default_serial_parameters(&mut serial_parameters, false);
get_serial_cmdline(&mut cmdline, &serial_parameters, "io")
.expect("get_serial_cmdline failed");
let cmdline_str = cmdline.as_str();
assert!(cmdline_str.contains("console=hvc0"));
}
#[test]
fn get_serial_cmdline_virtio_console_serial_earlycon() {
let mut cmdline = Cmdline::new(4096);
let mut serial_parameters = BTreeMap::new();
// Add a virtio-console device with console=true.
serial_parameters.insert(
(SerialHardware::VirtioConsole, 1),
SerialParameters {
type_: SerialType::Stdout,
hardware: SerialHardware::VirtioConsole,
path: None,
input: None,
num: 1,
console: true,
earlycon: false,
stdin: true,
out_timestamp: false,
debugcon_port: 0,
},
);
// Override the default COM1 with an earlycon device.
serial_parameters.insert(
(SerialHardware::Serial, 1),
SerialParameters {
type_: SerialType::Stdout,
hardware: SerialHardware::Serial,
path: None,
input: None,
num: 1,
console: false,
earlycon: true,
stdin: false,
out_timestamp: false,
debugcon_port: 0,
},
);
set_default_serial_parameters(&mut serial_parameters, false);
get_serial_cmdline(&mut cmdline, &serial_parameters, "io")
.expect("get_serial_cmdline failed");
let cmdline_str = cmdline.as_str();
assert!(cmdline_str.contains("console=hvc0"));
assert!(cmdline_str.contains("earlycon=uart8250,io,0x3f8"));
}
#[test]
fn get_serial_cmdline_virtio_console_invalid_earlycon() {
let mut cmdline = Cmdline::new(4096);
let mut serial_parameters = BTreeMap::new();
// Try to add a virtio-console device with earlycon=true (unsupported).
serial_parameters.insert(
(SerialHardware::VirtioConsole, 1),
SerialParameters {
type_: SerialType::Stdout,
hardware: SerialHardware::VirtioConsole,
path: None,
input: None,
num: 1,
console: false,
earlycon: true,
stdin: true,
out_timestamp: false,
debugcon_port: 0,
},
);
set_default_serial_parameters(&mut serial_parameters, false);
get_serial_cmdline(&mut cmdline, &serial_parameters, "io")
.expect_err("get_serial_cmdline succeeded");
}
}