summary refs log tree commit diff
path: root/arch
diff options
context:
space:
mode:
authorDaniel Verkamp <dverkamp@chromium.org>2020-03-09 13:16:46 -0700
committerCommit Bot <commit-bot@chromium.org>2020-04-23 07:17:59 +0000
commita7b6a1c897205d8320482eb506167c5df66647b2 (patch)
tree10b3f4ba971bfbd0a03c258193bbd47b8c672aa4 /arch
parentb865810340f1b264b407137c4e69ad232194cc5e (diff)
downloadcrosvm-a7b6a1c897205d8320482eb506167c5df66647b2.tar
crosvm-a7b6a1c897205d8320482eb506167c5df66647b2.tar.gz
crosvm-a7b6a1c897205d8320482eb506167c5df66647b2.tar.bz2
crosvm-a7b6a1c897205d8320482eb506167c5df66647b2.tar.lz
crosvm-a7b6a1c897205d8320482eb506167c5df66647b2.tar.xz
crosvm-a7b6a1c897205d8320482eb506167c5df66647b2.tar.zst
crosvm-a7b6a1c897205d8320482eb506167c5df66647b2.zip
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>
Diffstat (limited to 'arch')
-rw-r--r--arch/src/lib.rs8
-rw-r--r--arch/src/serial.rs345
2 files changed, 286 insertions, 67 deletions
diff --git a/arch/src/lib.rs b/arch/src/lib.rs
index 9ba1f27..e5f27f3 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<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),
diff --git a/arch/src/serial.rs b/arch/src/serial.rs
index 3892c64..f24f4bc 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<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");
+    }
 }