use std::collections::BTreeMap;
#[cfg(feature = "seccomp_trace")]
use base::debug;
use base::Event;
use devices::serial_device::SerialHardware;
use devices::serial_device::SerialParameters;
use devices::serial_device::SerialType;
use devices::Bus;
use devices::Serial;
use hypervisor::ProtectionType;
#[cfg(feature = "seccomp_trace")]
use jail::read_jail_addr;
#[cfg(windows)]
use jail::FakeMinijailStub as Minijail;
#[cfg(any(target_os = "android", target_os = "linux"))]
use minijail::Minijail;
use remain::sorted;
use thiserror::Error as ThisError;
use crate::DeviceRegistrationError;
mod sys;
pub fn set_default_serial_parameters(
serial_parameters: &mut BTreeMap<(SerialHardware, u8), SerialParameters>,
is_vhost_user_console_enabled: bool,
) {
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,
name: None,
path: None,
input: None,
num: 1,
console: true,
earlycon: false,
stdin: true,
out_timestamp: false,
..Default::default()
});
}
for num in 1..=4 {
let key = (SerialHardware::Serial, num);
serial_parameters.entry(key).or_insert(SerialParameters {
type_: SerialType::Sink,
hardware: SerialHardware::Serial,
name: None,
path: None,
input: None,
num,
console: false,
earlycon: false,
stdin: false,
out_timestamp: false,
..Default::default()
});
}
}
pub const SERIAL_ADDR: [u64; 4] = [0x3f8, 0x2f8, 0x3e8, 0x2e8];
pub struct SerialDeviceInfo {
pub address: u64,
pub size: u64,
pub irq: u32,
}
pub fn 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> {
let mut devices = Vec::new();
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 (irq, com_evt) = (com_evt.0, com_evt.1);
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>(protection_type, com_evt, &mut preserved_descriptors)
.map_err(DeviceRegistrationError::CreateSerialDevice)?;
#[cfg(any(target_os = "android", target_os = "linux"))]
let serial_jail = if let Some(serial_jail) = serial_jail.as_ref() {
let jail_clone = serial_jail
.try_clone()
.map_err(DeviceRegistrationError::CloneJail)?;
#[cfg(feature = "seccomp_trace")]
debug!(
"seccomp_trace {{\"event\": \"minijail_clone\", \"src_jail_addr\": \"0x{:x}\", \"dst_jail_addr\": \"0x{:x}\"}}",
read_jail_addr(serial_jail),
read_jail_addr(&jail_clone)
);
Some(jail_clone)
} else {
None
};
#[cfg(windows)]
let serial_jail = None;
let com = sys::add_serial_device(
com,
param,
serial_jail,
preserved_descriptors,
#[cfg(feature = "swap")]
swap_controller,
)?;
let address = SERIAL_ADDR[usize::from(com_num)];
let size = 0x8; io_bus.insert(com, address, size).unwrap();
devices.push(SerialDeviceInfo { address, size, irq })
}
Ok(devices)
}
#[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>;
pub fn get_serial_cmdline(
cmdline: &mut kernel_cmdline::Cmdline,
serial_parameters: &BTreeMap<(SerialHardware, u8), SerialParameters>,
serial_io_type: &str,
serial_devices: &[SerialDeviceInfo],
) -> GetSerialCmdlineResult<()> {
for serial_parameter in serial_parameters
.iter()
.filter(|(_, p)| p.console)
.map(|(k, _)| k)
{
match serial_parameter {
(SerialHardware::Serial, num) => {
cmdline
.insert("console", &format!("ttyS{}", num - 1))
.map_err(GetSerialCmdlineError::KernelCmdline)?;
}
(SerialHardware::VirtioConsole, num) => {
cmdline
.insert("console", &format!("hvc{}", num - 1))
.map_err(GetSerialCmdlineError::KernelCmdline)?;
}
(SerialHardware::Debugcon, _) => {}
}
}
match serial_parameters
.iter()
.filter(|(_, p)| p.earlycon)
.map(|(k, _)| k)
.next()
{
Some((SerialHardware::Serial, num)) => {
if let Some(serial_device) = serial_devices.get(*num as usize - 1) {
cmdline
.insert(
"earlycon",
&format!("uart8250,{},0x{:x}", serial_io_type, serial_device.address),
)
.map_err(GetSerialCmdlineError::KernelCmdline)?;
}
}
Some((hw, _num)) => {
return Err(GetSerialCmdlineError::UnsupportedEarlyconHardware(*hw));
}
None => {}
}
Ok(())
}
#[cfg(test)]
mod tests {
use devices::BusType;
use kernel_cmdline::Cmdline;
use super::*;
#[test]
fn get_serial_cmdline_default() {
let mut cmdline = Cmdline::new();
let mut serial_parameters = BTreeMap::new();
let io_bus = Bus::new(BusType::Io);
let evt1_3 = Event::new().unwrap();
let evt2_4 = Event::new().unwrap();
set_default_serial_parameters(&mut serial_parameters, false);
let serial_devices = add_serial_devices(
ProtectionType::Unprotected,
&io_bus,
(4, &evt1_3),
(3, &evt2_4),
&serial_parameters,
None,
#[cfg(feature = "swap")]
&mut None,
)
.unwrap();
get_serial_cmdline(&mut cmdline, &serial_parameters, "io", &serial_devices)
.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();
let mut serial_parameters = BTreeMap::new();
let io_bus = Bus::new(BusType::Io);
let evt1_3 = Event::new().unwrap();
let evt2_4 = Event::new().unwrap();
serial_parameters.insert(
(SerialHardware::VirtioConsole, 1),
SerialParameters {
type_: SerialType::Stdout,
hardware: SerialHardware::VirtioConsole,
name: None,
path: None,
input: None,
num: 1,
console: true,
earlycon: false,
stdin: true,
out_timestamp: false,
debugcon_port: 0,
pci_address: None,
},
);
set_default_serial_parameters(&mut serial_parameters, false);
let serial_devices = add_serial_devices(
ProtectionType::Unprotected,
&io_bus,
(4, &evt1_3),
(3, &evt2_4),
&serial_parameters,
None,
#[cfg(feature = "swap")]
&mut None,
)
.unwrap();
get_serial_cmdline(&mut cmdline, &serial_parameters, "io", &serial_devices)
.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();
let mut serial_parameters = BTreeMap::new();
let io_bus = Bus::new(BusType::Io);
let evt1_3 = Event::new().unwrap();
let evt2_4 = Event::new().unwrap();
serial_parameters.insert(
(SerialHardware::VirtioConsole, 1),
SerialParameters {
type_: SerialType::Stdout,
hardware: SerialHardware::VirtioConsole,
name: None,
path: None,
input: None,
num: 1,
console: true,
earlycon: false,
stdin: true,
out_timestamp: false,
debugcon_port: 0,
pci_address: None,
},
);
serial_parameters.insert(
(SerialHardware::Serial, 1),
SerialParameters {
type_: SerialType::Stdout,
hardware: SerialHardware::Serial,
name: None,
path: None,
input: None,
num: 1,
console: false,
earlycon: true,
stdin: false,
out_timestamp: false,
debugcon_port: 0,
pci_address: None,
},
);
set_default_serial_parameters(&mut serial_parameters, false);
let serial_devices = add_serial_devices(
ProtectionType::Unprotected,
&io_bus,
(4, &evt1_3),
(3, &evt2_4),
&serial_parameters,
None,
#[cfg(feature = "swap")]
&mut None,
)
.unwrap();
get_serial_cmdline(&mut cmdline, &serial_parameters, "io", &serial_devices)
.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();
let mut serial_parameters = BTreeMap::new();
let io_bus = Bus::new(BusType::Io);
let evt1_3 = Event::new().unwrap();
let evt2_4 = Event::new().unwrap();
serial_parameters.insert(
(SerialHardware::VirtioConsole, 1),
SerialParameters {
type_: SerialType::Stdout,
hardware: SerialHardware::VirtioConsole,
name: None,
path: None,
input: None,
num: 1,
console: false,
earlycon: true,
stdin: true,
out_timestamp: false,
debugcon_port: 0,
pci_address: None,
},
);
set_default_serial_parameters(&mut serial_parameters, false);
let serial_devices = add_serial_devices(
ProtectionType::Unprotected,
&io_bus,
(4, &evt1_3),
(3, &evt2_4),
&serial_parameters,
None,
#[cfg(feature = "swap")]
&mut None,
)
.unwrap();
get_serial_cmdline(&mut cmdline, &serial_parameters, "io", &serial_devices)
.expect_err("get_serial_cmdline succeeded");
}
}