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 <zachr@chromium.org>
Tested-by: kokoro <noreply+kokoro@google.com>
Commit-Queue: Daniel Verkamp <dverkamp@chromium.org>
This commit is contained in:
Daniel Verkamp 2020-03-09 13:16:46 -07:00 committed by Commit Bot
parent b865810340
commit a7b6a1c897
8 changed files with 403 additions and 106 deletions

View file

@ -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<u8, SerialParameters>,
serial_parameters: &BTreeMap<(SerialHardware, u8), SerialParameters>,
serial_jail: Option<Minijail>,
create_devices: F,
) -> Result<RunnableLinuxVm>
@ -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(&param).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<u8>) -> 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
}

View file

@ -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<u8, SerialParameters>,
serial_parameters: &BTreeMap<(SerialHardware, u8), SerialParameters>,
serial_jail: Option<Minijail>,
create_devices: F,
) -> Result<RunnableLinuxVm, Self::Error>
@ -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),

View file

@ -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<Self, Self::Err> {
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<PathBuf>,
pub input: Option<PathBuf>,
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<u8, SerialParameters>,
serial_parameters: &BTreeMap<(SerialHardware, u8), SerialParameters>,
serial_jail: Option<Minijail>,
) -> Result<Option<u8>, 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<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.
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");
}
}

View file

@ -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<Ac97Parameters>,
pub serial_parameters: BTreeMap<u8, SerialParameters>,
pub serial_parameters: BTreeMap<(SerialHardware, u8), SerialParameters>,
pub syslog_tag: Option<String>,
pub virtio_single_touch: Option<TouchDeviceOption>,
pub virtio_trackpad: Option<TouchDeviceOption>,

View file

@ -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::<Console>(&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<Vec<VirtioDeviceStub>> {
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)?);

View file

@ -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<Ac97Parameters> {
fn parse_serial_options(s: &str) -> argument::Result<SerialParameters> {
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<SerialParameters> {
for (k, v) in opts {
match k {
"hardware" => {
serial_setting.hardware = v
.parse::<SerialHardware>()
.map_err(|e| argument::Error::UnknownArgument(format!("{}", e)))?
}
"type" => {
serial_setting.type_ = v
.parse::<SerialType>()
@ -340,6 +347,14 @@ fn parse_serial_options(s: &str) -> argument::Result<SerialParameters> {
))
})?
}
"earlycon" => {
serial_setting.earlycon = v.parse::<bool>().map_err(|e| {
argument::Error::Syntax(format!(
"serial device earlycon is not parseable: {}",
e,
))
})?
}
"stdin" => {
serial_setting.stdin = v.parse::<bool>().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."),

View file

@ -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);

View file

@ -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<u8, SerialParameters>,
serial_parameters: &BTreeMap<(SerialHardware, u8), SerialParameters>,
serial_jail: Option<Minijail>,
create_devices: F,
) -> Result<RunnableLinuxVm>
@ -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(&param).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<u8>) -> 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<GsiRelay>,
serial_parameters: &BTreeMap<u8, SerialParameters>,
serial_parameters: &BTreeMap<(SerialHardware, u8), SerialParameters>,
serial_jail: Option<Minijail>,
) -> Result<Option<u8>> {
) -> 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.