From a7b6a1c897205d8320482eb506167c5df66647b2 Mon Sep 17 00:00:00 2001 From: Daniel Verkamp Date: Mon, 9 Mar 2020 13:16:46 -0700 Subject: [PATCH] arch, main: add virtio-console parsing and creation This allows the creation of virtio-console devices using the new hardware=virtio-console parameter to the --serial option. Also add support for the serial earlycon option, which allows using virtio-console as the main console device with a traditional serial device as the early console. This allows logging during early boot before PCI device discovery (when virtio-console devices are set up). BUG=chromium:1059924 TEST=crosvm run -r vm_rootfs.img \ --serial hardware=serial,type=stdout,console=false,earlycon=true \ --serial hardware=virtio-console,type=stdout,console=true,stdin=true \ vm_kernel Change-Id: Iff48800272b154d49b1da00f3914799089268afe Reviewed-on: https://chromium-review.googlesource.com/c/chromiumos/platform/crosvm/+/2127322 Reviewed-by: Zach Reizner Tested-by: kokoro Commit-Queue: Daniel Verkamp --- aarch64/src/lib.rs | 23 +-- arch/src/lib.rs | 8 +- arch/src/serial.rs | 345 ++++++++++++++++++++++++++++++++++++--------- src/crosvm.rs | 4 +- src/linux.rs | 31 +++- src/main.rs | 60 ++++++-- tests/boot.rs | 7 +- x86_64/src/lib.rs | 31 ++-- 8 files changed, 403 insertions(+), 106 deletions(-) diff --git a/aarch64/src/lib.rs b/aarch64/src/lib.rs index 67e37b465c..1116b691e9 100644 --- a/aarch64/src/lib.rs +++ b/aarch64/src/lib.rs @@ -11,7 +11,10 @@ use std::io; use std::os::unix::io::FromRawFd; use std::sync::Arc; -use arch::{get_serial_tty_string, RunnableLinuxVm, SerialParameters, VmComponents, VmImage}; +use arch::{ + get_serial_cmdline, GetSerialCmdlineError, RunnableLinuxVm, SerialHardware, SerialParameters, + VmComponents, VmImage, +}; use devices::{Bus, BusError, PciConfigMmio, PciDevice, PciInterruptPin}; use io_jail::Minijail; use remain::sorted; @@ -121,6 +124,7 @@ pub enum Error { CreateSocket(io::Error), CreateVcpu(sys_util::Error), CreateVm(sys_util::Error), + GetSerialCmdline(GetSerialCmdlineError), InitrdLoadFailure(arch::LoadImageError), KernelLoadFailure(arch::LoadImageError), KernelMissing, @@ -153,6 +157,7 @@ impl Display for Error { CreateSocket(e) => write!(f, "failed to create socket: {}", e), CreateVcpu(e) => write!(f, "failed to create VCPU: {}", e), CreateVm(e) => write!(f, "failed to create vm: {}", e), + GetSerialCmdline(e) => write!(f, "failed to get serial cmdline: {}", e), InitrdLoadFailure(e) => write!(f, "initrd cound not be loaded: {}", e), KernelLoadFailure(e) => write!(f, "kernel cound not be loaded: {}", e), KernelMissing => write!(f, "aarch64 requires a kernel"), @@ -194,7 +199,7 @@ impl arch::LinuxArch for AArch64 { mut components: VmComponents, _split_irqchip: bool, _ioapic_device_socket: VmIrqRequestSocket, - serial_parameters: &BTreeMap, + serial_parameters: &BTreeMap<(SerialHardware, u8), SerialParameters>, serial_jail: Option, create_devices: F, ) -> Result @@ -259,11 +264,11 @@ impl arch::LinuxArch for AArch64 { let com_evt_1_3 = EventFd::new().map_err(Error::CreateEventFd)?; let com_evt_2_4 = EventFd::new().map_err(Error::CreateEventFd)?; - let stdio_serial_num = arch::add_serial_devices( + arch::add_serial_devices( &mut mmio_bus, &com_evt_1_3, &com_evt_2_4, - &serial_parameters, + serial_parameters, serial_jail, ) .map_err(Error::CreateSerialDevices)?; @@ -282,7 +287,9 @@ impl arch::LinuxArch for AArch64 { ) .map_err(Error::RegisterPci)?; - let mut cmdline = Self::get_base_linux_cmdline(stdio_serial_num); + let mut cmdline = Self::get_base_linux_cmdline(); + get_serial_cmdline(&mut cmdline, serial_parameters, "mmio") + .map_err(Error::GetSerialCmdline)?; for param in components.extra_kernel_params { cmdline.insert_str(¶m).map_err(Error::Cmdline)?; } @@ -385,12 +392,8 @@ impl AArch64 { } /// This returns a base part of the kernel command for this architecture - fn get_base_linux_cmdline(stdio_serial_num: Option) -> kernel_cmdline::Cmdline { + fn get_base_linux_cmdline() -> kernel_cmdline::Cmdline { let mut cmdline = kernel_cmdline::Cmdline::new(sys_util::pagesize()); - if let Some(stdio_serial_num) = stdio_serial_num { - let tty_string = get_serial_tty_string(stdio_serial_num); - cmdline.insert("console", &tty_string).unwrap(); - } cmdline.insert_str("panic=-1").unwrap(); cmdline } diff --git a/arch/src/lib.rs b/arch/src/lib.rs index 9ba1f270a8..e5f27f3594 100644 --- a/arch/src/lib.rs +++ b/arch/src/lib.rs @@ -29,7 +29,8 @@ use sys_util::{syslog, EventFd, GuestAddress, GuestMemory, GuestMemoryError}; use vm_control::VmIrqRequestSocket; pub use serial::{ - add_serial_devices, get_serial_tty_string, SerialParameters, SerialType, SERIAL_ADDR, + add_serial_devices, get_serial_cmdline, set_default_serial_parameters, GetSerialCmdlineError, + SerialHardware, SerialParameters, SerialType, SERIAL_ADDR, }; pub enum VmImage { @@ -97,7 +98,7 @@ pub trait LinuxArch { components: VmComponents, split_irqchip: bool, ioapic_device_socket: VmIrqRequestSocket, - serial_parameters: &BTreeMap, + serial_parameters: &BTreeMap<(SerialHardware, u8), SerialParameters>, serial_jail: Option, create_devices: F, ) -> Result @@ -128,6 +129,8 @@ pub enum DeviceRegistrationError { EventFdClone(sys_util::Error), /// Could not create an event fd. EventFdCreate(sys_util::Error), + /// Missing a required serial device. + MissingRequiredSerialDevice(u8), /// Could not add a device to the mmio bus. MmioInsert(BusError), /// Failed to register ioevent with VM. @@ -159,6 +162,7 @@ impl Display for DeviceRegistrationError { Cmdline(e) => write!(f, "unable to add device to kernel command line: {}", e), EventFdClone(e) => write!(f, "failed to clone eventfd: {}", e), EventFdCreate(e) => write!(f, "failed to create eventfd: {}", e), + MissingRequiredSerialDevice(n) => write!(f, "missing required serial device {}", n), MmioInsert(e) => write!(f, "failed to add to mmio bus: {}", e), RegisterIoevent(e) => write!(f, "failed to register ioevent to VM: {}", e), RegisterIrqfd(e) => write!(f, "failed to register irq eventfd to VM: {}", e), diff --git a/arch/src/serial.rs b/arch/src/serial.rs index 3892c64e1c..f24f4bc467 100644 --- a/arch/src/serial.rs +++ b/arch/src/serial.rs @@ -22,6 +22,7 @@ use crate::DeviceRegistrationError; pub enum Error { CloneEventFd(sys_util::Error), FileError(std::io::Error), + InvalidSerialHardware(String), InvalidSerialType(String), PathRequired, Unimplemented(SerialType), @@ -34,6 +35,7 @@ impl Display for Error { match self { CloneEventFd(e) => write!(f, "unable to clone an EventFd: {}", e), FileError(e) => write!(f, "unable to open/create file: {}", e), + InvalidSerialHardware(e) => write!(f, "invalid serial hardware: {}", e), InvalidSerialType(e) => write!(f, "invalid serial type: {}", e), PathRequired => write!(f, "serial device type file requires a path"), Unimplemented(e) => write!(f, "serial device type {} not implemented", e.to_string()), @@ -42,7 +44,7 @@ impl Display for Error { } /// Enum for possible type of serial devices -#[derive(Debug)] +#[derive(Clone, Debug)] pub enum SerialType { File, Stdout, @@ -79,14 +81,45 @@ impl FromStr for SerialType { } } +/// Serial device hardware types +#[derive(Copy, Clone, Debug, PartialEq, Eq, PartialOrd, Ord)] +pub enum SerialHardware { + Serial, // Standard PC-style (8250/16550 compatible) UART + VirtioConsole, // virtio-console device +} + +impl Display for SerialHardware { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + let s = match &self { + SerialHardware::Serial => "serial".to_string(), + SerialHardware::VirtioConsole => "virtio-console".to_string(), + }; + + write!(f, "{}", s) + } +} + +impl FromStr for SerialHardware { + type Err = Error; + fn from_str(s: &str) -> std::result::Result { + match s { + "serial" => Ok(SerialHardware::Serial), + "virtio-console" => Ok(SerialHardware::VirtioConsole), + _ => Err(Error::InvalidSerialHardware(s.to_string())), + } + } +} + /// Holds the parameters for a serial device -#[derive(Debug)] +#[derive(Clone, Debug)] pub struct SerialParameters { pub type_: SerialType, + pub hardware: SerialHardware, pub path: Option, pub input: Option, pub num: u8, pub console: bool, + pub earlycon: bool, pub stdin: bool, } @@ -149,60 +182,59 @@ impl SerialParameters { } } -// Structure for holding the default configuration of the serial devices. -const DEFAULT_SERIAL_PARAMS: [SerialParameters; 4] = [ - SerialParameters { - type_: SerialType::Stdout, - path: None, - input: None, - num: 1, - console: true, - stdin: true, - }, - SerialParameters { - type_: SerialType::Sink, - path: None, - input: None, - num: 2, - console: false, - stdin: false, - }, - SerialParameters { - type_: SerialType::Sink, - path: None, - input: None, - num: 3, - console: false, - stdin: false, - }, - SerialParameters { - type_: SerialType::Sink, - path: None, - input: None, - num: 4, - console: false, - stdin: false, - }, -]; +/// 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>, +) { + // 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) { + 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, + }); + } + + // 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, + }); + } +} /// Address for Serial ports in x86 pub const SERIAL_ADDR: [u64; 4] = [0x3f8, 0x2f8, 0x3e8, 0x2e8]; -/// String representations of serial devices -const SERIAL_TTY_STRINGS: [&str; 4] = ["ttyS0", "ttyS1", "ttyS2", "ttyS3"]; - -/// Helper function to get the tty string of a serial device based on the port number. Will default -/// to ttyS0 if an invalid number is given. -pub fn get_serial_tty_string(stdio_serial_num: u8) -> String { - stdio_serial_num - .checked_sub(1) - .and_then(|i| SERIAL_TTY_STRINGS.get(i as usize)) - .unwrap_or(&SERIAL_TTY_STRINGS[0]) - .to_string() -} - -/// Adds serial devices to the provided bus based on the serial parameters given. Returns the serial -/// port number and serial device to be used for stdout if defined. +/// 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 /// @@ -210,17 +242,15 @@ pub fn get_serial_tty_string(stdio_serial_num: u8) -> String { /// * `com_evt_1_3` - eventfd for com1 and com3 /// * `com_evt_1_4` - eventfd for com2 and com4 /// * `io_bus` - Bus to add the devices to -/// * `serial_parameters` - definitions of serial parameter configuationis. If a setting is not -/// provided for a port, then it will use the default configuation. +/// * `serial_parameters` - definitions of serial parameter configurations. +/// All four of the traditional PC-style serial ports (COM1-COM4) must be specified. pub fn add_serial_devices( io_bus: &mut Bus, com_evt_1_3: &EventFd, com_evt_2_4: &EventFd, - serial_parameters: &BTreeMap, + serial_parameters: &BTreeMap<(SerialHardware, u8), SerialParameters>, serial_jail: Option, -) -> Result, DeviceRegistrationError> { - let mut stdio_serial_num = None; - +) -> Result<(), DeviceRegistrationError> { for x in 0..=3 { let com_evt = match x { 0 => com_evt_1_3, @@ -231,12 +261,8 @@ pub fn add_serial_devices( }; let param = serial_parameters - .get(&(x + 1)) - .unwrap_or(&DEFAULT_SERIAL_PARAMS[x as usize]); - - if param.console { - stdio_serial_num = Some(x + 1); - } + .get(&(SerialHardware::Serial, x + 1)) + .ok_or(DeviceRegistrationError::MissingRequiredSerialDevice(x + 1))?; let mut preserved_fds = Vec::new(); let com = param @@ -262,5 +288,194 @@ pub fn add_serial_devices( } } - Ok(stdio_serial_num) + Ok(()) +} + +#[derive(Debug)] +pub enum GetSerialCmdlineError { + KernelCmdline(kernel_cmdline::Error), + UnsupportedEarlyconHardware(SerialHardware), +} + +impl Display for GetSerialCmdlineError { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + use self::GetSerialCmdlineError::*; + + match self { + KernelCmdline(e) => write!(f, "error appending to cmdline: {}", e), + UnsupportedEarlyconHardware(hw) => { + write!(f, "hardware {} not supported as earlycon", hw) + } + } + } +} + +pub type GetSerialCmdlineResult = std::result::Result; + +/// 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. +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)?; + } + 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); + 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, + }, + ); + + set_default_serial_parameters(&mut serial_parameters); + 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, + }, + ); + + // 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, + }, + ); + + set_default_serial_parameters(&mut serial_parameters); + 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, + }, + ); + + set_default_serial_parameters(&mut serial_parameters); + get_serial_cmdline(&mut cmdline, &serial_parameters, "io") + .expect_err("get_serial_cmdline succeeded"); + } } diff --git a/src/crosvm.rs b/src/crosvm.rs index 75e384ff03..49a08c0cda 100644 --- a/src/crosvm.rs +++ b/src/crosvm.rs @@ -16,7 +16,7 @@ use std::os::unix::io::RawFd; use std::path::{Path, PathBuf}; use std::str::FromStr; -use arch::{Pstore, SerialParameters}; +use arch::{Pstore, SerialHardware, SerialParameters}; use devices::virtio::fs::passthrough; #[cfg(feature = "gpu")] use devices::virtio::gpu::GpuParameters; @@ -193,7 +193,7 @@ pub struct Config { pub display_window_keyboard: bool, pub display_window_mouse: bool, pub ac97_parameters: Vec, - pub serial_parameters: BTreeMap, + pub serial_parameters: BTreeMap<(SerialHardware, u8), SerialParameters>, pub syslog_tag: Option, pub virtio_single_touch: Option, pub virtio_trackpad: Option, diff --git a/src/linux.rs b/src/linux.rs index c469e0c0f7..8690ff7e08 100644 --- a/src/linux.rs +++ b/src/linux.rs @@ -29,7 +29,7 @@ use libc::{self, c_int, gid_t, uid_t}; #[cfg(feature = "gpu")] use devices::virtio::EventDevice; -use devices::virtio::{self, VirtioDevice}; +use devices::virtio::{self, Console, VirtioDevice}; use devices::{ self, Ac97Backend, Ac97Dev, HostBackendDeviceProvider, PciDevice, VfioContainer, VfioDevice, VfioPciDevice, VirtioPciDevice, XhciController, @@ -61,7 +61,10 @@ use vm_control::{ }; use crate::{Config, DiskOption, Executable, SharedDir, SharedDirKind, TouchDeviceOption}; -use arch::{self, LinuxArch, RunnableLinuxVm, VirtioDeviceStub, VmComponents, VmImage}; +use arch::{ + self, LinuxArch, RunnableLinuxVm, SerialHardware, SerialParameters, VirtioDeviceStub, + VmComponents, VmImage, +}; #[cfg(any(target_arch = "arm", target_arch = "aarch64"))] use aarch64::AArch64 as Arch; @@ -82,6 +85,7 @@ pub enum Error { ChownTpmStorage(sys_util::Error), CloneEventFd(sys_util::Error), CreateAc97(devices::PciDeviceError), + CreateConsole(arch::serial::Error), CreateDiskError(disk::Error), CreateEventFd(sys_util::Error), CreatePollContext(sys_util::Error), @@ -166,6 +170,7 @@ impl Display for Error { ChownTpmStorage(e) => write!(f, "failed to chown tpm storage: {}", e), CloneEventFd(e) => write!(f, "failed to clone eventfd: {}", e), CreateAc97(e) => write!(f, "failed to create ac97 device: {}", e), + CreateConsole(e) => write!(f, "failed to create console device: {}", e), CreateDiskError(e) => write!(f, "failed to create virtual disk: {}", e), CreateEventFd(e) => write!(f, "failed to create eventfd: {}", e), CreatePollContext(e) => write!(f, "failed to create poll context: {}", e), @@ -963,6 +968,19 @@ fn create_pmem_device( }) } +fn create_console_device(cfg: &Config, param: &SerialParameters) -> DeviceResult { + let mut keep_fds = Vec::new(); + let evt = EventFd::new().map_err(Error::CreateEventFd)?; + let dev = param + .create_serial_device::(&evt, &mut keep_fds) + .map_err(Error::CreateConsole)?; + + Ok(VirtioDeviceStub { + dev: Box::new(dev), + jail: simple_jail(&cfg, "serial")?, // TODO(dverkamp): use a separate policy for console? + }) +} + // gpu_device_socket is not used when GPU support is disabled. #[cfg_attr(not(feature = "gpu"), allow(unused_variables))] fn create_virtio_devices( @@ -979,6 +997,15 @@ fn create_virtio_devices( ) -> DeviceResult> { let mut devs = Vec::new(); + for (_, param) in cfg + .serial_parameters + .iter() + .filter(|(_k, v)| v.hardware == SerialHardware::VirtioConsole) + { + let dev = create_console_device(cfg, param)?; + devs.push(dev); + } + for disk in &cfg.disks { let disk_device_socket = disk_device_sockets.remove(0); devs.push(create_block_device(cfg, disk, disk_device_socket)?); diff --git a/src/main.rs b/src/main.rs index 9775d5f799..557c630256 100644 --- a/src/main.rs +++ b/src/main.rs @@ -17,7 +17,7 @@ use std::string::String; use std::thread::sleep; use std::time::Duration; -use arch::{Pstore, SerialParameters, SerialType}; +use arch::{set_default_serial_parameters, Pstore, SerialHardware, SerialParameters, SerialType}; use audio_streams::StreamEffect; use crosvm::{ argument::{self, print_help, set_arguments, Argument}, @@ -301,10 +301,12 @@ fn parse_ac97_options(s: &str) -> argument::Result { fn parse_serial_options(s: &str) -> argument::Result { let mut serial_setting = SerialParameters { type_: SerialType::Sink, + hardware: SerialHardware::Serial, path: None, input: None, num: 1, console: false, + earlycon: false, stdin: false, }; @@ -315,6 +317,11 @@ fn parse_serial_options(s: &str) -> argument::Result { for (k, v) in opts { match k { + "hardware" => { + serial_setting.hardware = v + .parse::() + .map_err(|e| argument::Error::UnknownArgument(format!("{}", e)))? + } "type" => { serial_setting.type_ = v .parse::() @@ -340,6 +347,14 @@ fn parse_serial_options(s: &str) -> argument::Result { )) })? } + "earlycon" => { + serial_setting.earlycon = v.parse::().map_err(|e| { + argument::Error::Syntax(format!( + "serial device earlycon is not parseable: {}", + e, + )) + })? + } "stdin" => { serial_setting.stdin = v.parse::().map_err(|e| { argument::Error::Syntax(format!("serial device stdin is not parseable: {}", e)) @@ -549,10 +564,11 @@ fn set_argument(cfg: &mut Config, name: &str, value: Option<&str>) -> argument:: "serial" => { let serial_params = parse_serial_options(value.unwrap())?; let num = serial_params.num; - if cfg.serial_parameters.contains_key(&num) { + let key = (serial_params.hardware, num); + if cfg.serial_parameters.contains_key(&key) { return Err(argument::Error::TooManyArguments(format!( - "serial num {}", - num + "serial hardware {} num {}", + serial_params.hardware, num, ))); } @@ -560,8 +576,29 @@ fn set_argument(cfg: &mut Config, name: &str, value: Option<&str>) -> argument:: for params in cfg.serial_parameters.values() { if params.console { return Err(argument::Error::TooManyArguments(format!( - "serial device {} already set as console", - params.num + "{} device {} already set as console", + params.hardware, params.num, + ))); + } + } + } + + if serial_params.earlycon { + // Only SerialHardware::Serial supports earlycon= currently. + match serial_params.hardware { + SerialHardware::Serial => {} + _ => { + return Err(argument::Error::InvalidValue { + value: serial_params.hardware.to_string().to_owned(), + expected: String::from("earlycon not supported for hardware"), + }); + } + } + for params in cfg.serial_parameters.values() { + if params.earlycon { + return Err(argument::Error::TooManyArguments(format!( + "{} device {} already set as earlycon", + params.hardware, params.num, ))); } } @@ -570,13 +607,13 @@ fn set_argument(cfg: &mut Config, name: &str, value: Option<&str>) -> argument:: if serial_params.stdin { if let Some(previous_stdin) = cfg.serial_parameters.values().find(|sp| sp.stdin) { return Err(argument::Error::TooManyArguments(format!( - "serial device {} already connected to standard input", - previous_stdin.num + "{} device {} already connected to standard input", + previous_stdin.hardware, previous_stdin.num, ))); } } - cfg.serial_parameters.insert(num, serial_params); + cfg.serial_parameters.insert(key, serial_params); } "syslog-tag" => { if cfg.syslog_tag.is_some() { @@ -1231,6 +1268,7 @@ fn validate_arguments(cfg: &mut Config) -> std::result::Result<(), argument::Err } } } + set_default_serial_parameters(&mut cfg.serial_parameters); Ok(()) } @@ -1281,14 +1319,16 @@ fn run_vm(args: std::env::Args) -> std::result::Result<(), ()> { capture - Enable audio capture capture_effects - | separated effects to be enabled for recording. The only supported effect value now is EchoCancellation or aec."), Argument::value("serial", - "type=TYPE,[num=NUM,path=PATH,input=PATH,console,stdin]", + "type=TYPE,[hardware=HW,num=NUM,path=PATH,input=PATH,console,earlycon,stdin]", "Comma separated key=value pairs for setting up serial devices. Can be given more than once. Possible key values: type=(stdout,syslog,sink,file) - Where to route the serial device + hardware=(serial,virtio-console) - Which type of serial hardware to emulate. Defaults to 8250 UART (serial). num=(1,2,3,4) - Serial Device Number. If not provided, num will default to 1. path=PATH - The path to the file to write to when type=file input=PATH - The path to the file to read from when not stdin console - Use this serial device as the guest console. Can only be given once. Will default to first serial port if not provided. + earlycon - Use this serial device as the early console. Can only be given once. stdin - Direct standard input to this serial device. Can only be given once. Will default to first serial port if not provided. "), Argument::value("syslog-tag", "TAG", "When logging to syslog, use the provided tag."), diff --git a/tests/boot.rs b/tests/boot.rs index c60ba4b6bb..9c2da3c3bb 100644 --- a/tests/boot.rs +++ b/tests/boot.rs @@ -13,7 +13,7 @@ use std::sync::Once; use libc::{cpu_set_t, sched_getaffinity}; -use arch::{SerialParameters, SerialType}; +use arch::{set_default_serial_parameters, SerialHardware, SerialParameters, SerialType}; use crosvm::{linux, Config, Executable}; use sys_util::syslog; @@ -228,16 +228,19 @@ fn boot() { let mut c = Config::default(); c.sandbox = false; c.serial_parameters.insert( - 1, + (SerialHardware::Serial, 1), SerialParameters { type_: SerialType::Sink, + hardware: SerialHardware::Serial, path: None, input: None, num: 1, console: false, + earlycon: false, stdin: false, }, ); + set_default_serial_parameters(&mut c.serial_parameters); c.executable_path = Some(Executable::Kernel(kernel_path)); let r = linux::run_config(c); diff --git a/x86_64/src/lib.rs b/x86_64/src/lib.rs index c073223d24..95f7835a01 100644 --- a/x86_64/src/lib.rs +++ b/x86_64/src/lib.rs @@ -54,7 +54,10 @@ use std::mem; use std::sync::Arc; use crate::bootparam::boot_params; -use arch::{get_serial_tty_string, RunnableLinuxVm, SerialParameters, VmComponents, VmImage}; +use arch::{ + get_serial_cmdline, GetSerialCmdlineError, RunnableLinuxVm, SerialHardware, SerialParameters, + VmComponents, VmImage, +}; use devices::split_irqchip_common::GsiRelay; use devices::{ Ioapic, PciConfigIo, PciDevice, PciInterruptPin, Pic, IOAPIC_BASE_ADDRESS, @@ -90,6 +93,7 @@ pub enum Error { CreateVm(sys_util::Error), E820Configuration, EnableSplitIrqchip(sys_util::Error), + GetSerialCmdline(GetSerialCmdlineError), KernelOffsetPastEnd, LoadBios(io::Error), LoadBzImage(bzimage::Error), @@ -139,6 +143,7 @@ impl Display for Error { CreateVm(e) => write!(f, "failed to create VM: {}", e), E820Configuration => write!(f, "invalid e820 setup params"), EnableSplitIrqchip(e) => write!(f, "failed to enable split irqchip: {}", e), + GetSerialCmdline(e) => write!(f, "failed to get serial cmdline: {}", e), KernelOffsetPastEnd => write!(f, "the kernel extends past the end of RAM"), LoadBios(e) => write!(f, "error loading bios: {}", e), LoadBzImage(e) => write!(f, "error loading kernel bzImage: {}", e), @@ -331,7 +336,7 @@ impl arch::LinuxArch for X8664arch { mut components: VmComponents, split_irqchip: bool, ioapic_device_socket: VmIrqRequestSocket, - serial_parameters: &BTreeMap, + serial_parameters: &BTreeMap<(SerialHardware, u8), SerialParameters>, serial_jail: Option, create_devices: F, ) -> Result @@ -421,7 +426,7 @@ impl arch::LinuxArch for X8664arch { suspend_evt.try_clone().map_err(Error::CloneEventFd)?, )?; - let stdio_serial_num = Self::setup_serial_devices( + Self::setup_serial_devices( &mut vm, &mut io_bus, &mut gsi_relay, @@ -459,7 +464,11 @@ impl arch::LinuxArch for X8664arch { match components.vm_image { VmImage::Bios(ref mut bios) => Self::load_bios(&mem, bios)?, VmImage::Kernel(ref mut kernel_image) => { - let mut cmdline = Self::get_base_linux_cmdline(stdio_serial_num); + let mut cmdline = Self::get_base_linux_cmdline(); + + get_serial_cmdline(&mut cmdline, serial_parameters, "io") + .map_err(Error::GetSerialCmdline)?; + for param in components.extra_kernel_params { cmdline.insert_str(¶m).map_err(Error::Cmdline)?; } @@ -719,12 +728,8 @@ impl X8664arch { } /// This returns a minimal kernel command for this architecture - fn get_base_linux_cmdline(stdio_serial_num: Option) -> kernel_cmdline::Cmdline { + fn get_base_linux_cmdline() -> kernel_cmdline::Cmdline { let mut cmdline = kernel_cmdline::Cmdline::new(CMDLINE_MAX_SIZE as usize); - if let Some(stdio_serial_num) = stdio_serial_num { - let tty_string = get_serial_tty_string(stdio_serial_num); - cmdline.insert("console", &tty_string).unwrap(); - } cmdline.insert_str("pci=noacpi reboot=k panic=-1").unwrap(); cmdline @@ -848,13 +853,13 @@ impl X8664arch { vm: &mut Vm, io_bus: &mut devices::Bus, gsi_relay: &mut Option, - serial_parameters: &BTreeMap, + serial_parameters: &BTreeMap<(SerialHardware, u8), SerialParameters>, serial_jail: Option, - ) -> Result> { + ) -> Result<()> { let com_evt_1_3 = EventFd::new().map_err(Error::CreateEventFd)?; let com_evt_2_4 = EventFd::new().map_err(Error::CreateEventFd)?; - let stdio_serial_num = arch::add_serial_devices( + arch::add_serial_devices( io_bus, &com_evt_1_3, &com_evt_2_4, @@ -873,7 +878,7 @@ impl X8664arch { .map_err(Error::RegisterIrqfd)?; } - Ok(stdio_serial_num) + Ok(()) } /// Configures the vcpu and should be called once per vcpu from the vcpu's thread.