mirror of
https://chromium.googlesource.com/crosvm/crosvm
synced 2024-11-28 17:44:10 +00:00
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:
parent
b865810340
commit
a7b6a1c897
8 changed files with 403 additions and 106 deletions
|
@ -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(¶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<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
|
||||
}
|
||||
|
|
|
@ -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),
|
||||
|
|
|
@ -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");
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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>,
|
||||
|
|
31
src/linux.rs
31
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::<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)?);
|
||||
|
|
60
src/main.rs
60
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<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."),
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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(¶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<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.
|
||||
|
|
Loading…
Reference in a new issue