summary refs log tree commit diff
diff options
context:
space:
mode:
-rw-r--r--aarch64/src/fdt.rs4
-rw-r--r--aarch64/src/lib.rs65
-rw-r--r--arch/src/lib.rs60
-rw-r--r--devices/src/lib.rs5
-rw-r--r--devices/src/serial.rs148
-rw-r--r--src/linux.rs45
-rw-r--r--src/main.rs129
-rw-r--r--sys_util/src/syslog.rs162
-rw-r--r--x86_64/src/lib.rs100
9 files changed, 572 insertions, 146 deletions
diff --git a/aarch64/src/fdt.rs b/aarch64/src/fdt.rs
index 74a3fdc..bb82382 100644
--- a/aarch64/src/fdt.rs
+++ b/aarch64/src/fdt.rs
@@ -28,8 +28,8 @@ use crate::AARCH64_RTC_SIZE;
 use devices::pl030::PL030_AMBA_ID;
 
 // These are serial device related constants.
+use crate::AARCH64_SERIAL_1_3_IRQ;
 use crate::AARCH64_SERIAL_ADDR;
-use crate::AARCH64_SERIAL_IRQ;
 use crate::AARCH64_SERIAL_SIZE;
 use crate::AARCH64_SERIAL_SPEED;
 
@@ -135,7 +135,7 @@ fn create_serial_node(fdt: &mut Vec<u8>) -> Result<()> {
     let serial_reg_prop = generate_prop64(&[AARCH64_SERIAL_ADDR, AARCH64_SERIAL_SIZE]);
     let irq = generate_prop32(&[
         GIC_FDT_IRQ_TYPE_SPI,
-        AARCH64_SERIAL_IRQ,
+        AARCH64_SERIAL_1_3_IRQ,
         IRQ_TYPE_EDGE_RISING,
     ]);
 
diff --git a/aarch64/src/lib.rs b/aarch64/src/lib.rs
index 9c41257..5e19d5d 100644
--- a/aarch64/src/lib.rs
+++ b/aarch64/src/lib.rs
@@ -2,16 +2,20 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
+use std::collections::BTreeMap;
 use std::error::Error as StdError;
 use std::ffi::{CStr, CString};
 use std::fmt::{self, Display};
 use std::fs::File;
-use std::io::{self, stdout};
+use std::io;
 use std::os::unix::io::FromRawFd;
 use std::sync::Arc;
 
 use arch::{RunnableLinuxVm, VmComponents};
-use devices::{Bus, BusError, PciConfigMmio, PciDevice, PciInterruptPin};
+use devices::{
+    get_serial_tty_string, Bus, BusError, PciConfigMmio, PciDevice, PciInterruptPin,
+    SerialParameters,
+};
 use io_jail::Minijail;
 use remain::sorted;
 use resources::SystemAllocator;
@@ -84,7 +88,8 @@ const AARCH64_SERIAL_SIZE: u64 = 0x8;
 const AARCH64_SERIAL_SPEED: u32 = 1843200;
 // The serial device gets the first interrupt line
 // Which gets mapped to the first SPI interrupt (physical 32).
-const AARCH64_SERIAL_IRQ: u32 = 0;
+const AARCH64_SERIAL_1_3_IRQ: u32 = 0;
+const AARCH64_SERIAL_2_4_IRQ: u32 = 2;
 
 // Place the RTC device at page 2
 const AARCH64_RTC_ADDR: u64 = 0x2000;
@@ -101,8 +106,8 @@ const AARCH64_PCI_CFG_SIZE: u64 = 0x1000000;
 const AARCH64_MMIO_BASE: u64 = 0x1010000;
 // Size of the whole MMIO region.
 const AARCH64_MMIO_SIZE: u64 = 0x100000;
-// Virtio devices start at SPI interrupt number 2
-const AARCH64_IRQ_BASE: u32 = 2;
+// Virtio devices start at SPI interrupt number 3
+const AARCH64_IRQ_BASE: u32 = 3;
 
 #[sorted]
 #[derive(Debug)]
@@ -115,6 +120,7 @@ pub enum Error {
     CreateGICFailure(sys_util::Error),
     CreateKvm(sys_util::Error),
     CreatePciRoot(arch::DeviceRegistrationError),
+    CreateSerialDevices(arch::DeviceRegistrationError),
     CreateSocket(io::Error),
     CreateVcpu(sys_util::Error),
     CreateVm(sys_util::Error),
@@ -145,6 +151,7 @@ impl Display for Error {
             CreateGICFailure(e) => write!(f, "failed to create GIC: {}", e),
             CreateKvm(e) => write!(f, "failed to open /dev/kvm: {}", e),
             CreatePciRoot(e) => write!(f, "failed to create a PCI root hub: {}", e),
+            CreateSerialDevices(e) => write!(f, "unable to create serial devices: {}", e),
             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),
@@ -187,6 +194,7 @@ impl arch::LinuxArch for AArch64 {
     fn build_vm<F, E>(
         mut components: VmComponents,
         _split_irqchip: bool,
+        serial_parameters: &BTreeMap<u8, SerialParameters>,
         create_devices: F,
     ) -> Result<RunnableLinuxVm>
     where
@@ -220,7 +228,6 @@ impl arch::LinuxArch for AArch64 {
         let vcpu_affinity = components.vcpu_affinity;
 
         let irq_chip = Self::create_irq_chip(&vm)?;
-        let mut cmdline = Self::get_base_linux_cmdline();
 
         let mut mmio_bus = devices::Bus::new();
 
@@ -236,7 +243,22 @@ impl arch::LinuxArch for AArch64 {
         // ARM doesn't really use the io bus like x86, so just create an empty bus.
         let io_bus = devices::Bus::new();
 
-        let stdio_serial = Self::add_arch_devs(&mut vm, &mut mmio_bus)?;
+        Self::add_arch_devs(&mut vm, &mut mmio_bus)?;
+
+        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, stdio_serial) = arch::add_serial_devices(
+            &mut mmio_bus,
+            &com_evt_1_3,
+            &com_evt_2_4,
+            &serial_parameters,
+        )
+        .map_err(Error::CreateSerialDevices)?;
+
+        vm.register_irqfd(&com_evt_1_3, AARCH64_SERIAL_1_3_IRQ)
+            .map_err(Error::RegisterIrqfd)?;
+        vm.register_irqfd(&com_evt_2_4, AARCH64_SERIAL_2_4_IRQ)
+            .map_err(Error::RegisterIrqfd)?;
 
         mmio_bus
             .insert(
@@ -247,6 +269,7 @@ impl arch::LinuxArch for AArch64 {
             )
             .map_err(Error::RegisterPci)?;
 
+        let mut cmdline = Self::get_base_linux_cmdline(stdio_serial_num);
         for param in components.extra_kernel_params {
             cmdline.insert_str(&param).map_err(Error::Cmdline)?;
         }
@@ -343,9 +366,13 @@ impl AArch64 {
     }
 
     /// This returns a base part of the kernel command for this architecture
-    fn get_base_linux_cmdline() -> kernel_cmdline::Cmdline {
+    fn get_base_linux_cmdline(stdio_serial_num: Option<u8>) -> kernel_cmdline::Cmdline {
         let mut cmdline = kernel_cmdline::Cmdline::new(sys_util::pagesize());
-        cmdline.insert_str("console=ttyS0 panic=-1").unwrap();
+        if stdio_serial_num.is_some() {
+            let tty_string = get_serial_tty_string(stdio_serial_num.unwrap());
+            cmdline.insert("console", &tty_string).unwrap();
+        }
+        cmdline.insert_str("panic=-1").unwrap();
         cmdline
     }
 
@@ -365,30 +392,16 @@ impl AArch64 {
     ///
     /// * `vm` - The vm to add irqs to.
     /// * `bus` - The bus to add devices to.
-    fn add_arch_devs(vm: &mut Vm, bus: &mut Bus) -> Result<Arc<Mutex<devices::Serial>>> {
+    fn add_arch_devs(vm: &mut Vm, bus: &mut Bus) -> Result<()> {
         let rtc_evt = EventFd::new().map_err(Error::CreateEventFd)?;
         vm.register_irqfd(&rtc_evt, AARCH64_RTC_IRQ)
             .map_err(Error::RegisterIrqfd)?;
 
-        let com_evt_1_3 = EventFd::new().map_err(Error::CreateEventFd)?;
-        vm.register_irqfd(&com_evt_1_3, AARCH64_SERIAL_IRQ)
-            .map_err(Error::RegisterIrqfd)?;
-        let serial = Arc::new(Mutex::new(devices::Serial::new_out(
-            com_evt_1_3.try_clone().map_err(Error::CloneEventFd)?,
-            Box::new(stdout()),
-        )));
-        bus.insert(
-            serial.clone(),
-            AARCH64_SERIAL_ADDR,
-            AARCH64_SERIAL_SIZE,
-            false,
-        )
-        .expect("failed to add serial device");
-
         let rtc = Arc::new(Mutex::new(devices::pl030::Pl030::new(rtc_evt)));
         bus.insert(rtc, AARCH64_RTC_ADDR, AARCH64_RTC_SIZE, false)
             .expect("failed to add rtc device");
-        Ok(serial)
+
+        Ok(())
     }
 
     /// The creates the interrupt controller device and optionally returns the fd for it.
diff --git a/arch/src/lib.rs b/arch/src/lib.rs
index 31a0e19..b9791c8 100644
--- a/arch/src/lib.rs
+++ b/arch/src/lib.rs
@@ -16,7 +16,7 @@ use std::sync::Arc;
 use devices::virtio::VirtioDevice;
 use devices::{
     Bus, BusDevice, BusError, PciDevice, PciDeviceError, PciInterruptPin, PciRoot, ProxyDevice,
-    Serial,
+    Serial, SerialParameters, DEFAULT_SERIAL_PARAMS, SERIAL_ADDR,
 };
 use io_jail::Minijail;
 use kvm::{IoeventAddress, Kvm, Vcpu, Vm};
@@ -42,7 +42,7 @@ pub struct RunnableLinuxVm {
     pub vm: Vm,
     pub kvm: Kvm,
     pub resources: SystemAllocator,
-    pub stdio_serial: Arc<Mutex<Serial>>,
+    pub stdio_serial: Option<Arc<Mutex<Serial>>>,
     pub exit_evt: EventFd,
     pub vcpus: Vec<Vcpu>,
     pub vcpu_affinity: Vec<usize>,
@@ -69,10 +69,12 @@ pub trait LinuxArch {
     ///
     /// * `components` - Parts to use to build the VM.
     /// * `split_irqchip` - whether to use a split IRQ chip (i.e. userspace PIT/PIC/IOAPIC)
+    /// * `serial_parameters` - definitions for how the serial devices should be configured.
     /// * `create_devices` - Function to generate a list of devices.
     fn build_vm<F, E>(
         components: VmComponents,
         split_irqchip: bool,
+        serial_parameters: &BTreeMap<u8, SerialParameters>,
         create_devices: F,
     ) -> Result<RunnableLinuxVm, Self::Error>
     where
@@ -91,6 +93,8 @@ pub enum DeviceRegistrationError {
     AllocateIrq,
     /// Could not create the mmio device to wrap a VirtioDevice.
     CreateMmioDevice(sys_util::Error),
+    //  Unable to create serial device from serial parameters
+    CreateSerialDevice(devices::SerialError),
     /// Could not create an event fd.
     EventFdCreate(sys_util::Error),
     /// Could not add a device to the mmio bus.
@@ -120,6 +124,7 @@ impl Display for DeviceRegistrationError {
             AllocateDeviceAddrs(e) => write!(f, "Allocating device addresses: {}", e),
             AllocateIrq => write!(f, "Allocating IRQ number"),
             CreateMmioDevice(e) => write!(f, "failed to create mmio device: {}", e),
+            CreateSerialDevice(e) => write!(f, "failed to create serial device: {}", e),
             Cmdline(e) => write!(f, "unable to add device to kernel command line: {}", e),
             EventFdCreate(e) => write!(f, "failed to create eventfd: {}", e),
             MmioInsert(e) => write!(f, "failed to add to mmio bus: {}", e),
@@ -212,6 +217,57 @@ pub fn generate_pci_root(
     Ok((root, pci_irqs, pid_labels))
 }
 
+/// 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.
+///
+/// # Arguments
+///
+/// * `io_bus` - Bus to add the devices to
+/// * `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.
+pub fn add_serial_devices(
+    io_bus: &mut Bus,
+    com_evt_1_3: &EventFd,
+    com_evt_2_4: &EventFd,
+    serial_parameters: &BTreeMap<u8, SerialParameters>,
+) -> Result<(Option<u8>, Option<Arc<Mutex<Serial>>>), DeviceRegistrationError> {
+    let mut stdio_serial_num = None;
+    let mut stdio_serial = None;
+
+    for x in 0..3 {
+        let com_evt = match x {
+            0 => com_evt_1_3,
+            1 => com_evt_2_4,
+            2 => com_evt_1_3,
+            3 => com_evt_2_4,
+            _ => com_evt_1_3,
+        };
+
+        let param = serial_parameters
+            .get(&(x + 1))
+            .unwrap_or(&DEFAULT_SERIAL_PARAMS[x as usize]);
+
+        let com = Arc::new(Mutex::new(
+            param
+                .create_serial_device(&com_evt)
+                .map_err(DeviceRegistrationError::CreateSerialDevice)?,
+        ));
+        io_bus
+            .insert(com.clone(), SERIAL_ADDR[x as usize], 0x8, false)
+            .unwrap();
+
+        if param.console {
+            stdio_serial_num = Some(x + 1);
+            stdio_serial = Some(com.clone());
+        }
+    }
+
+    Ok((stdio_serial_num, stdio_serial))
+}
+
 /// Errors for image loading.
 #[derive(Debug)]
 pub enum LoadImageError {
diff --git a/devices/src/lib.rs b/devices/src/lib.rs
index 3b2c0b2..bc9c8c1 100644
--- a/devices/src/lib.rs
+++ b/devices/src/lib.rs
@@ -34,7 +34,10 @@ pub use self::pit::{Pit, PitError};
 pub use self::pl030::Pl030;
 pub use self::proxy::Error as ProxyError;
 pub use self::proxy::ProxyDevice;
-pub use self::serial::Serial;
+pub use self::serial::Error as SerialError;
+pub use self::serial::{
+    get_serial_tty_string, Serial, SerialParameters, SerialType, DEFAULT_SERIAL_PARAMS, SERIAL_ADDR,
+};
 pub use self::usb::host_backend::host_backend_device_provider::HostBackendDeviceProvider;
 pub use self::usb::xhci::xhci_controller::XhciController;
 pub use self::virtio::VirtioPciDevice;
diff --git a/devices/src/serial.rs b/devices/src/serial.rs
index 7cb7611..4d12512 100644
--- a/devices/src/serial.rs
+++ b/devices/src/serial.rs
@@ -3,9 +3,12 @@
 // found in the LICENSE file.
 
 use std::collections::VecDeque;
-use std::io;
+use std::fmt::{self, Display};
+use std::io::{self, stdout};
+use std::path::PathBuf;
+use std::str::FromStr;
 
-use sys_util::{error, EventFd, Result};
+use sys_util::{error, syslog, EventFd, Result};
 
 use crate::BusDevice;
 
@@ -44,6 +47,147 @@ const DEFAULT_MODEM_CONTROL: u8 = 0x8; // Auxiliary output 2
 const DEFAULT_MODEM_STATUS: u8 = 0x20 | 0x10 | 0x80; // data ready, clear to send, carrier detect
 const DEFAULT_BAUD_DIVISOR: u16 = 12; // 9600 bps
 
+#[derive(Debug)]
+pub enum Error {
+    CloneEventFd(sys_util::Error),
+    InvalidSerialType(String),
+    Unimplemented(SerialType),
+}
+
+impl Display for Error {
+    #[remain::check]
+    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
+        use self::Error::*;
+
+        #[sorted]
+        match self {
+            CloneEventFd(e) => write!(f, "unable to clone an EventFd: {}", e),
+            InvalidSerialType(e) => write!(f, "invalid serial type: {}", e),
+            Unimplemented(e) => write!(f, "serial device type {} not implemented", e.to_string()),
+        }
+    }
+}
+
+/// Enum for possible type of serial devices
+#[derive(Debug)]
+pub enum SerialType {
+    File, // NOT IMPLEMENTED
+    Stdout,
+    Sink,
+    Syslog,
+    UnixSocket, // NOT IMPLEMENTED
+}
+
+impl Display for SerialType {
+    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
+        let s = match &self {
+            SerialType::File => "File".to_string(),
+            SerialType::Stdout => "Stdout".to_string(),
+            SerialType::Sink => "Sink".to_string(),
+            SerialType::Syslog => "Syslog".to_string(),
+            SerialType::UnixSocket => "UnixSocket".to_string(),
+        };
+
+        write!(f, "{}", s)
+    }
+}
+
+impl FromStr for SerialType {
+    type Err = Error;
+    fn from_str(s: &str) -> std::result::Result<Self, Self::Err> {
+        match s {
+            "file" | "File" => return Ok(SerialType::File),
+            "stdout" | "Stdout" => return Ok(SerialType::Stdout),
+            "sink" | "Sink" => return Ok(SerialType::Sink),
+            "syslog" | "Syslog" => return Ok(SerialType::Syslog),
+            "unix" | "UnixSocket" => return Ok(SerialType::UnixSocket),
+            _ => Err(Error::InvalidSerialType(s.to_string())),
+        }
+    }
+}
+
+/// Holds the parameters for a serial device
+#[derive(Debug)]
+pub struct SerialParameters {
+    pub type_: SerialType,
+    pub path: Option<PathBuf>,
+    pub num: u8,
+    pub console: bool,
+}
+
+impl SerialParameters {
+    /// Helper function to create a serial device from the defined parameters.
+    ///
+    /// # Arguments
+    /// * `evt_fd` - eventfd used for interrupt events
+    pub fn create_serial_device(&self, evt_fd: &EventFd) -> std::result::Result<Serial, Error> {
+        match self.type_ {
+            SerialType::Stdout => Ok(Serial::new_out(
+                evt_fd.try_clone().map_err(Error::CloneEventFd)?,
+                Box::new(stdout()),
+            )),
+            SerialType::Sink => Ok(Serial::new_sink(
+                evt_fd.try_clone().map_err(Error::CloneEventFd)?,
+            )),
+            SerialType::Syslog => Ok(Serial::new_out(
+                evt_fd.try_clone().map_err(Error::CloneEventFd)?,
+                Box::new(syslog::Syslogger::new(
+                    syslog::Priority::Info,
+                    syslog::Facility::Daemon,
+                )),
+            )),
+            SerialType::File => Err(Error::Unimplemented(SerialType::File)),
+            SerialType::UnixSocket => Err(Error::Unimplemented(SerialType::UnixSocket)),
+        }
+    }
+}
+
+// Structure for holding the default configuration of the serial devices.
+pub const DEFAULT_SERIAL_PARAMS: [SerialParameters; 4] = [
+    SerialParameters {
+        type_: SerialType::Stdout,
+        path: None,
+        num: 1,
+        console: true,
+    },
+    SerialParameters {
+        type_: SerialType::Sink,
+        path: None,
+        num: 2,
+        console: false,
+    },
+    SerialParameters {
+        type_: SerialType::Sink,
+        path: None,
+        num: 3,
+        console: false,
+    },
+    SerialParameters {
+        type_: SerialType::Sink,
+        path: None,
+        num: 4,
+        console: false,
+    },
+];
+
+/// Address for Serial ports in x86
+pub const SERIAL_ADDR: [u64; 4] = [0x3f8, 0x2f8, 0x3e8, 0x2e8];
+
+/// String representations of serial devices
+pub 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 {
+    match stdio_serial_num {
+        1 => SERIAL_TTY_STRINGS[0].to_string(),
+        2 => SERIAL_TTY_STRINGS[1].to_string(),
+        3 => SERIAL_TTY_STRINGS[2].to_string(),
+        4 => SERIAL_TTY_STRINGS[3].to_string(),
+        _ => SERIAL_TTY_STRINGS[0].to_string(),
+    }
+}
+
 /// Emulates serial COM ports commonly seen on x86 I/O ports 0x3f8/0x2f8/0x3e8/0x2e8.
 ///
 /// This can optionally write the guest's output to a Write trait object. To send input to the
diff --git a/src/linux.rs b/src/linux.rs
index 803a82e..979666e 100644
--- a/src/linux.rs
+++ b/src/linux.rs
@@ -789,7 +789,7 @@ fn create_virtio_devices(
 }
 
 fn create_devices(
-    cfg: Config,
+    cfg: &Config,
     mem: &GuestMemory,
     exit_evt: &EventFd,
     wayland_device_socket: WlControlRequestSocket,
@@ -1180,17 +1180,22 @@ pub fn run_config(cfg: Config) -> Result<()> {
     }
 
     let sandbox = cfg.sandbox;
-    let linux = Arch::build_vm(components, cfg.split_irqchip, |m, e| {
-        create_devices(
-            cfg,
-            m,
-            e,
-            wayland_device_socket,
-            balloon_device_socket,
-            &mut disk_device_sockets,
-            usb_provider,
-        )
-    })
+    let linux = Arch::build_vm(
+        components,
+        cfg.split_irqchip,
+        &cfg.serial_parameters,
+        |m, e| {
+            create_devices(
+                &cfg,
+                m,
+                e,
+                wayland_device_socket,
+                balloon_device_socket,
+                &mut disk_device_sockets,
+                usb_provider,
+            )
+        },
+    )
     .map_err(Error::BuildVm)?;
 
     let _render_node_host = ();
@@ -1401,13 +1406,15 @@ fn run_control(
                             warn!("error while reading stdin: {}", e);
                             let _ = poll_ctx.delete(&stdin_handle);
                         }
-                        Ok(count) => {
-                            linux
-                                .stdio_serial
-                                .lock()
-                                .queue_input_bytes(&out[..count])
-                                .expect("failed to queue bytes into serial port");
-                        }
+                        Ok(count) => match linux.stdio_serial {
+                            Some(ref stdio_serial) => {
+                                stdio_serial
+                                    .lock()
+                                    .queue_input_bytes(&out[..count])
+                                    .expect("failed to queue bytes into serial port");
+                            }
+                            None => {}
+                        },
                     }
                 }
                 Token::ChildSignal => {
diff --git a/src/main.rs b/src/main.rs
index 48c1edd..6b948e6 100644
--- a/src/main.rs
+++ b/src/main.rs
@@ -10,6 +10,7 @@ pub mod panic_hook;
 #[cfg(feature = "plugin")]
 pub mod plugin;
 
+use std::collections::BTreeMap;
 use std::fmt;
 use std::fs::{File, OpenOptions};
 use std::net;
@@ -20,6 +21,7 @@ use std::string::String;
 use std::thread::sleep;
 use std::time::Duration;
 
+use devices::{SerialParameters, SerialType};
 use msg_socket::{MsgReceiver, MsgSender, MsgSocket};
 use qcow::QcowFile;
 use sys_util::{
@@ -102,6 +104,8 @@ pub struct Config {
     software_tpm: bool,
     cras_audio: bool,
     null_audio: bool,
+    serial_parameters: BTreeMap<u8, SerialParameters>,
+    syslog_tag: Option<String>,
     virtio_single_touch: Option<TouchDeviceOption>,
     virtio_trackpad: Option<TouchDeviceOption>,
     virtio_mouse: Option<PathBuf>,
@@ -141,6 +145,8 @@ impl Default for Config {
             seccomp_policy_dir: PathBuf::from(SECCOMP_POLICY_DIR),
             cras_audio: false,
             null_audio: false,
+            serial_parameters: BTreeMap::new(),
+            syslog_tag: None,
             virtio_single_touch: None,
             virtio_trackpad: None,
             virtio_mouse: None,
@@ -221,6 +227,58 @@ fn parse_cpu_set(s: &str) -> argument::Result<Vec<usize>> {
     Ok(cpuset)
 }
 
+fn parse_serial_options(s: &str) -> argument::Result<SerialParameters> {
+    let mut serial_setting = SerialParameters {
+        type_: SerialType::Sink,
+        path: None,
+        num: 0,
+        console: false,
+    };
+
+    let opts = s
+        .split(",")
+        .map(|frag| frag.split("="))
+        .map(|mut kv| (kv.next().unwrap_or(""), kv.next().unwrap_or("")));
+
+    for (k, v) in opts {
+        match k {
+            "type" => {
+                serial_setting.type_ = v
+                    .parse::<SerialType>()
+                    .map_err(|e| argument::Error::UnknownArgument(format!("{}", e)))?
+            }
+            "num" => {
+                let num = v.parse::<u8>().map_err(|e| {
+                    argument::Error::Syntax(format!("serial device number is not parsable: {}", e))
+                })?;
+                if num < 1 || num > 4 {
+                    return Err(argument::Error::InvalidValue {
+                        value: num.to_string(),
+                        expected: "Serial port num must be between 1 - 4",
+                    });
+                }
+                serial_setting.num = num;
+            }
+            "console" => {
+                serial_setting.console = v.parse::<bool>().map_err(|e| {
+                    argument::Error::Syntax(format!(
+                        "serial device console is not parseable: {}",
+                        e
+                    ))
+                })?
+            }
+            _ => {
+                return Err(argument::Error::UnknownArgument(format!(
+                    "serial parameter {}",
+                    k
+                )));
+            }
+        }
+    }
+
+    Ok(serial_setting)
+}
+
 fn set_argument(cfg: &mut Config, name: &str, value: Option<&str>) -> argument::Result<()> {
     match name {
         "" => {
@@ -312,6 +370,38 @@ fn set_argument(cfg: &mut Config, name: &str, value: Option<&str>) -> argument::
         "null-audio" => {
             cfg.null_audio = true;
         }
+        "serial" => {
+            let serial_params = parse_serial_options(value.unwrap())?;
+            let num = serial_params.num;
+            if cfg.serial_parameters.contains_key(&num) {
+                return Err(argument::Error::TooManyArguments(format!(
+                    "serial num {}",
+                    num
+                )));
+            }
+
+            if serial_params.console {
+                for (_, params) in &cfg.serial_parameters {
+                    if params.console {
+                        return Err(argument::Error::TooManyArguments(format!(
+                            "serial device {} already set as console",
+                            params.num
+                        )));
+                    }
+                }
+            }
+
+            cfg.serial_parameters.insert(num, serial_params);
+        }
+        "syslog-tag" => {
+            if cfg.syslog_tag.is_some() {
+                return Err(argument::Error::TooManyArguments(
+                    "`syslog-tag` already given".to_owned(),
+                ));
+            }
+            syslog::set_proc_name(value.unwrap());
+            cfg.syslog_tag = Some(value.unwrap().to_owned());
+        }
         "root" | "disk" | "rwdisk" | "qcow" | "rwqcow" => {
             let disk_path = PathBuf::from(value.unwrap());
             if !disk_path.exists() {
@@ -702,6 +792,15 @@ fn run_vm(args: std::env::Args) -> std::result::Result<(), ()> {
           Argument::value("mac", "MAC", "MAC address for VM."),
           Argument::flag("cras-audio", "Add an audio device to the VM that plays samples through CRAS server"),
           Argument::flag("null-audio", "Add an audio device to the VM that plays samples to /dev/null"),
+          Argument::value("serial",
+                          "type=TYPE,[path=PATH,num=NUM,console]",
+                          "Comma seperated key=value pairs for setting up serial devices. Can be given more than once.
+                          Possible key values:
+                          type=(stdout,syslog,sink) - Where to route the serial device
+                          num=(1,2,3,4) - Serial Device Number
+                          console - Use this serial device as the guest console. 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."),
           Argument::value("wayland-sock", "PATH", "Path to the Wayland socket to use."),
           #[cfg(feature = "wl-dmabuf")]
           Argument::flag("wayland-dmabuf", "Enable support for DMABufs in Wayland device."),
@@ -1271,4 +1370,34 @@ mod tests {
     fn parse_cpu_set_extra_comma() {
         parse_cpu_set("0,1,2,").expect_err("parse should have failed");
     }
+
+    #[test]
+    fn parse_serial_vaild() {
+        parse_serial_options("type=syslog,num=1,console=true").expect("parse should have succeded");
+    }
+
+    #[test]
+    fn parse_serial_invalid_type() {
+        parse_serial_options("type=wormhole,num=1").expect_err("parse should have failed");
+    }
+
+    #[test]
+    fn parse_serial_invalid_num_upper() {
+        parse_serial_options("type=syslog,num=5").expect_err("parse should have failed");
+    }
+
+    #[test]
+    fn parse_serial_invalid_num_lower() {
+        parse_serial_options("type=syslog,num=0").expect_err("parse should have failed");
+    }
+
+    #[test]
+    fn parse_serial_invalid_num_string() {
+        parse_serial_options("type=syslog,num=number3").expect_err("parse should have failed");
+    }
+
+    #[test]
+    fn parse_serial_invalid_option() {
+        parse_serial_options("type=syslog,speed=lightspeed").expect_err("parse should have failed");
+    }
 }
diff --git a/sys_util/src/syslog.rs b/sys_util/src/syslog.rs
index 41b4b97..da23229 100644
--- a/sys_util/src/syslog.rs
+++ b/sys_util/src/syslog.rs
@@ -83,6 +83,7 @@ impl Display for Priority {
 /// The facility of a syslog message.
 ///
 /// See syslog man pages for information on their semantics.
+#[derive(Copy, Clone)]
 pub enum Facility {
     Kernel = 0,
     User = 1 << 3,
@@ -387,8 +388,8 @@ fn get_localtime() -> tm {
 /// # Arguments
 /// * `pri` - The `Priority` (i.e. severity) of the log message.
 /// * `fac` - The `Facility` of the log message. Usually `Facility::User` should be used.
-/// * `file_name` - Name of the file that generated the log.
-/// * `line` - Line number within `file_name` that generated the log.
+/// * `file_line` - Optional tuple of the name of the file that generated the
+///                 log and the line number within that file.
 /// * `args` - The log's message to record, in the form of `format_args!()`  return value
 ///
 /// # Examples
@@ -402,12 +403,11 @@ fn get_localtime() -> tm {
 /// #   }
 /// syslog::log(syslog::Priority::Error,
 ///             syslog::Facility::User,
-///             file!(),
-///             line!(),
+///             Some((file!(), line!())),
 ///             format_args!("hello syslog"));
 /// # }
 /// ```
-pub fn log(pri: Priority, fac: Facility, file_name: &str, line: u32, args: fmt::Arguments) {
+pub fn log(pri: Priority, fac: Facility, file_line: Option<(&str, u32)>, args: fmt::Arguments) {
     const MONTHS: [&str; 12] = [
         "Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec",
     ];
@@ -417,46 +417,52 @@ pub fn log(pri: Priority, fac: Facility, file_name: &str, line: u32, args: fmt::
     if let Some(socket) = &state.socket {
         let tm = get_localtime();
         let prifac = (pri as u8) | (fac as u8);
-        let (res, len) = {
+        let res = {
             let mut buf_cursor = Cursor::new(&mut buf[..]);
-            (
-                write!(
-                    &mut buf_cursor,
-                    "<{}>{} {:02} {:02}:{:02}:{:02} {}[{}]: [{}:{}] {}",
-                    prifac,
-                    MONTHS[tm.tm_mon as usize],
-                    tm.tm_mday,
-                    tm.tm_hour,
-                    tm.tm_min,
-                    tm.tm_sec,
-                    state.proc_name.as_ref().map(|s| s.as_ref()).unwrap_or("-"),
-                    getpid(),
-                    file_name,
-                    line,
-                    args
-                ),
-                buf_cursor.position() as usize,
+            write!(
+                &mut buf_cursor,
+                "<{}>{} {:02} {:02}:{:02}:{:02} {}[{}]: ",
+                prifac,
+                MONTHS[tm.tm_mon as usize],
+                tm.tm_mday,
+                tm.tm_hour,
+                tm.tm_min,
+                tm.tm_sec,
+                state.proc_name.as_ref().map(|s| s.as_ref()).unwrap_or("-"),
+                getpid()
             )
+            .and_then(|()| {
+                if let Some((file_name, line)) = &file_line {
+                    write!(&mut buf_cursor, " [{}:{}] ", file_name, line)
+                } else {
+                    Ok(())
+                }
+            })
+            .and_then(|()| write!(&mut buf_cursor, "{}", args))
+            .and_then(|()| Ok(buf_cursor.position() as usize))
         };
 
-        if res.is_ok() {
-            send_buf(&socket, &buf[..len]);
+        if let Ok(len) = &res {
+            send_buf(&socket, &buf[..*len])
         }
     }
 
-    let (res, len) = {
+    let res = {
         let mut buf_cursor = Cursor::new(&mut buf[..]);
-        (
-            writeln!(&mut buf_cursor, "[{}:{}:{}] {}", pri, file_name, line, args),
-            buf_cursor.position() as usize,
-        )
+        if let Some((file_name, line)) = &file_line {
+            write!(&mut buf_cursor, "[{}:{}:{}] ", pri, file_name, line)
+        } else {
+            Ok(())
+        }
+        .and_then(|()| writeln!(&mut buf_cursor, "{}", args))
+        .and_then(|()| Ok(buf_cursor.position() as usize))
     };
-    if res.is_ok() {
+    if let Ok(len) = &res {
         if let Some(file) = &mut state.file {
-            let _ = file.write_all(&buf[..len]);
+            let _ = file.write_all(&buf[..*len]);
         }
         if state.stderr {
-            let _ = stderr().write_all(&buf[..len]);
+            let _ = stderr().write_all(&buf[..*len]);
         }
     }
 }
@@ -467,7 +473,7 @@ pub fn log(pri: Priority, fac: Facility, file_name: &str, line: u32, args: fmt::
 #[macro_export]
 macro_rules! log {
     ($pri:expr, $($args:tt)+) => ({
-        $crate::syslog::log($pri, $crate::syslog::Facility::User, file!(), line!(), format_args!($($args)+))
+        $crate::syslog::log($pri, $crate::syslog::Facility::User, Some((file!(), line!())), format_args!($($args)+))
     })
 }
 
@@ -503,6 +509,44 @@ macro_rules! debug {
     ($($args:tt)+) => ($crate::log!($crate::syslog::Priority::Debug, $($args)*))
 }
 
+// Struct that implements io::Write to be used for writing directly to the syslog
+pub struct Syslogger {
+    buf: String,
+    priority: Priority,
+    facility: Facility,
+}
+
+impl Syslogger {
+    pub fn new(p: Priority, f: Facility) -> Syslogger {
+        Syslogger {
+            buf: String::new(),
+            priority: p,
+            facility: f,
+        }
+    }
+}
+
+impl io::Write for Syslogger {
+    fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
+        let parsed_str = String::from_utf8_lossy(buf);
+        self.buf.push_str(&parsed_str);
+
+        if let Some(last_newline_idx) = self.buf.rfind('\n') {
+            for line in self.buf[..last_newline_idx].lines() {
+                log(self.priority, self.facility, None, format_args!("{}", line));
+            }
+
+            self.buf.drain(..=last_newline_idx);
+        }
+
+        Ok(buf.len())
+    }
+
+    fn flush(&mut self) -> io::Result<()> {
+        Ok(())
+    }
+}
+
 #[cfg(test)]
 mod tests {
     use super::*;
@@ -535,8 +579,7 @@ mod tests {
         log(
             Priority::Error,
             Facility::User,
-            file!(),
-            line!(),
+            Some((file!(), line!())),
             format_args!("hello syslog"),
         );
     }
@@ -547,16 +590,14 @@ mod tests {
         log(
             Priority::Error,
             Facility::User,
-            file!(),
-            line!(),
+            Some((file!(), line!())),
             format_args!("before proc name"),
         );
         set_proc_name("sys_util-test");
         log(
             Priority::Error,
             Facility::User,
-            file!(),
-            line!(),
+            Some((file!(), line!())),
             format_args!("after proc name"),
         );
     }
@@ -579,8 +620,7 @@ mod tests {
         log(
             Priority::Error,
             Facility::User,
-            file!(),
-            line!(),
+            Some((file!(), line!())),
             format_args!("{}", TEST_STR),
         );
 
@@ -600,4 +640,42 @@ mod tests {
         info!("this is info {}", true);
         debug!("this is debug info {:?}", Some("helpful stuff"));
     }
+
+    #[test]
+    fn syslogger_char() {
+        init().unwrap();
+        let mut syslogger = Syslogger::new(Priority::Info, Facility::Daemon);
+
+        let string = "Writing chars to syslog";
+        for c in string.chars() {
+            syslogger.write(&[c as u8]).expect("error writing char");
+        }
+
+        syslogger
+            .write(&['\n' as u8])
+            .expect("error writing newline char");
+    }
+
+    #[test]
+    fn syslogger_line() {
+        init().unwrap();
+        let mut syslogger = Syslogger::new(Priority::Info, Facility::Daemon);
+
+        let s = "Writing string to syslog\n";
+        syslogger
+            .write(&s.as_bytes())
+            .expect("error writing string");
+    }
+
+    #[test]
+    fn syslogger_partial() {
+        init().unwrap();
+        let mut syslogger = Syslogger::new(Priority::Info, Facility::Daemon);
+
+        let s = "Writing partial string";
+        // Should not log because there is no newline character
+        syslogger
+            .write(&s.as_bytes())
+            .expect("error writing string");
+    }
 }
diff --git a/x86_64/src/lib.rs b/x86_64/src/lib.rs
index 23df5ea..eafdc61 100644
--- a/x86_64/src/lib.rs
+++ b/x86_64/src/lib.rs
@@ -54,18 +54,19 @@ mod mptable;
 mod regs;
 mod smbios;
 
+use std::collections::BTreeMap;
 use std::error::Error as StdError;
 use std::ffi::{CStr, CString};
 use std::fmt::{self, Display};
 use std::fs::File;
-use std::io::{self, stdout};
+use std::io;
 use std::mem;
 use std::sync::Arc;
 
 use crate::bootparam::boot_params;
 use crate::bootparam::E820_RAM;
 use arch::{RunnableLinuxVm, VmComponents};
-use devices::{PciConfigIo, PciDevice, PciInterruptPin};
+use devices::{get_serial_tty_string, PciConfigIo, PciDevice, PciInterruptPin, SerialParameters};
 use io_jail::Minijail;
 use kvm::*;
 use remain::sorted;
@@ -87,6 +88,7 @@ pub enum Error {
     CreatePciRoot(arch::DeviceRegistrationError),
     CreatePit(sys_util::Error),
     CreatePitDevice(devices::PitError),
+    CreateSerialDevices(arch::DeviceRegistrationError),
     CreateSocket(io::Error),
     CreateVcpu(sys_util::Error),
     CreateVm(sys_util::Error),
@@ -130,6 +132,7 @@ impl Display for Error {
             CreatePciRoot(e) => write!(f, "failed to create a PCI root hub: {}", e),
             CreatePit(e) => write!(f, "unable to create PIT: {}", e),
             CreatePitDevice(e) => write!(f, "unable to make PIT device: {}", e),
+            CreateSerialDevices(e) => write!(f, "unable to create serial devices: {}", e),
             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),
@@ -172,6 +175,8 @@ const ZERO_PAGE_OFFSET: u64 = 0x7000;
 const KERNEL_START_OFFSET: u64 = 0x200000;
 const CMDLINE_OFFSET: u64 = 0x20000;
 const CMDLINE_MAX_SIZE: u64 = KERNEL_START_OFFSET - CMDLINE_OFFSET;
+const X86_64_SERIAL_1_3_IRQ: u32 = 4;
+const X86_64_SERIAL_2_4_IRQ: u32 = 3;
 const X86_64_IRQ_BASE: u32 = 5;
 
 fn configure_system(
@@ -296,6 +301,7 @@ impl arch::LinuxArch for X8664arch {
     fn build_vm<F, E>(
         mut components: VmComponents,
         split_irqchip: bool,
+        serial_parameters: &BTreeMap<u8, SerialParameters>,
         create_devices: F,
     ) -> Result<RunnableLinuxVm>
     where
@@ -329,7 +335,6 @@ impl arch::LinuxArch for X8664arch {
         let vcpu_affinity = components.vcpu_affinity;
 
         let irq_chip = Self::create_irq_chip(&vm)?;
-        let mut cmdline = Self::get_base_linux_cmdline();
 
         let mut mmio_bus = devices::Bus::new();
 
@@ -342,13 +347,17 @@ impl arch::LinuxArch for X8664arch {
                 .map_err(Error::CreatePciRoot)?;
         let pci_bus = Arc::new(Mutex::new(PciConfigIo::new(pci)));
 
-        let (io_bus, stdio_serial) = Self::setup_io_bus(
+        let mut io_bus = Self::setup_io_bus(
             &mut vm,
             split_irqchip,
             exit_evt.try_clone().map_err(Error::CloneEventFd)?,
             Some(pci_bus.clone()),
         )?;
 
+        let (stdio_serial_num, stdio_serial) =
+            Self::setup_serial_devices(&mut vm, &mut io_bus, &serial_parameters)?;
+
+        let mut cmdline = Self::get_base_linux_cmdline(stdio_serial_num);
         for param in components.extra_kernel_params {
             cmdline.insert_str(&param).map_err(Error::Cmdline)?;
         }
@@ -533,11 +542,14 @@ impl X8664arch {
     }
 
     /// This returns a minimal kernel command for this architecture
-    fn get_base_linux_cmdline() -> kernel_cmdline::Cmdline {
+    fn get_base_linux_cmdline(stdio_serial_num: Option<u8>) -> kernel_cmdline::Cmdline {
         let mut cmdline = kernel_cmdline::Cmdline::new(CMDLINE_MAX_SIZE as usize);
-        cmdline
-            .insert_str("console=ttyS0 noacpi reboot=k panic=-1")
-            .unwrap();
+        if stdio_serial_num.is_some() {
+            let tty_string = get_serial_tty_string(stdio_serial_num.unwrap());
+            cmdline.insert("console", &tty_string).unwrap();
+        }
+        cmdline.insert_str("noacpi reboot=k panic=-1").unwrap();
+
         cmdline
     }
 
@@ -565,7 +577,7 @@ impl X8664arch {
         split_irqchip: bool,
         exit_evt: EventFd,
         pci: Option<Arc<Mutex<devices::PciConfigIo>>>,
-    ) -> Result<(devices::Bus, Arc<Mutex<devices::Serial>>)> {
+    ) -> Result<(devices::Bus)> {
         struct NoDevice;
         impl devices::BusDevice for NoDevice {
             fn debug_label(&self) -> String {
@@ -575,46 +587,6 @@ impl X8664arch {
 
         let mut io_bus = devices::Bus::new();
 
-        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 = Arc::new(Mutex::new(devices::Serial::new_out(
-            com_evt_1_3.try_clone().map_err(Error::CloneEventFd)?,
-            Box::new(stdout()),
-        )));
-        let nul_device = Arc::new(Mutex::new(NoDevice));
-        io_bus
-            .insert(stdio_serial.clone(), 0x3f8, 0x8, false)
-            .unwrap();
-        io_bus
-            .insert(
-                Arc::new(Mutex::new(devices::Serial::new_sink(
-                    com_evt_2_4.try_clone().map_err(Error::CloneEventFd)?,
-                ))),
-                0x2f8,
-                0x8,
-                false,
-            )
-            .unwrap();
-        io_bus
-            .insert(
-                Arc::new(Mutex::new(devices::Serial::new_sink(
-                    com_evt_1_3.try_clone().map_err(Error::CloneEventFd)?,
-                ))),
-                0x3e8,
-                0x8,
-                false,
-            )
-            .unwrap();
-        io_bus
-            .insert(
-                Arc::new(Mutex::new(devices::Serial::new_sink(
-                    com_evt_2_4.try_clone().map_err(Error::CloneEventFd)?,
-                ))),
-                0x2e8,
-                0x8,
-                false,
-            )
-            .unwrap();
         io_bus
             .insert(Arc::new(Mutex::new(devices::Cmos::new())), 0x70, 0x2, false)
             .unwrap();
@@ -629,6 +601,7 @@ impl X8664arch {
             )
             .unwrap();
 
+        let nul_device = Arc::new(Mutex::new(NoDevice));
         if split_irqchip {
             let pit_evt = EventFd::new().map_err(Error::CreateEventFd)?;
             let pit = Arc::new(Mutex::new(
@@ -664,12 +637,35 @@ impl X8664arch {
                 .unwrap();
         }
 
-        vm.register_irqfd(&com_evt_1_3, 4)
+        Ok(io_bus)
+    }
+
+    /// Sets up the serial devices for this platform. Returns the serial port number and serial
+    /// device to be used for stdout
+    ///
+    /// # Arguments
+    ///
+    /// * - `vm` the vm object
+    /// * - `io_bus` the I/O bus to add the devices to
+    /// * - `serial_parmaters` - definitions for how the serial devices should be configured
+    fn setup_serial_devices(
+        vm: &mut Vm,
+        io_bus: &mut devices::Bus,
+        serial_parameters: &BTreeMap<u8, SerialParameters>,
+    ) -> Result<(Option<u8>, Option<Arc<Mutex<devices::Serial>>>)> {
+        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, stdio_serial) =
+            arch::add_serial_devices(io_bus, &com_evt_1_3, &com_evt_2_4, &serial_parameters)
+                .map_err(Error::CreateSerialDevices)?;
+
+        vm.register_irqfd(&com_evt_1_3, X86_64_SERIAL_1_3_IRQ)
             .map_err(Error::RegisterIrqfd)?;
-        vm.register_irqfd(&com_evt_2_4, 3)
+        vm.register_irqfd(&com_evt_2_4, X86_64_SERIAL_2_4_IRQ)
             .map_err(Error::RegisterIrqfd)?;
 
-        Ok((io_bus, stdio_serial))
+        Ok((stdio_serial_num, stdio_serial))
     }
 
     /// Configures the vcpu and should be called once per vcpu from the vcpu's thread.