blob: 4d125125daf623d1735e24eaf6f35ca84422ab1d [file] [log] [blame]
// Copyright 2017 The Chromium OS Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
use std::collections::VecDeque;
use std::fmt::{self, Display};
use std::io::{self, stdout};
use std::path::PathBuf;
use std::str::FromStr;
use sys_util::{error, syslog, EventFd, Result};
use crate::BusDevice;
const LOOP_SIZE: usize = 0x40;
const DATA: u8 = 0;
const IER: u8 = 1;
const IIR: u8 = 2;
const LCR: u8 = 3;
const MCR: u8 = 4;
const LSR: u8 = 5;
const MSR: u8 = 6;
const SCR: u8 = 7;
const DLAB_LOW: u8 = 0;
const DLAB_HIGH: u8 = 1;
const IER_RECV_BIT: u8 = 0x1;
const IER_THR_BIT: u8 = 0x2;
const IER_FIFO_BITS: u8 = 0x0f;
const IIR_FIFO_BITS: u8 = 0xc0;
const IIR_NONE_BIT: u8 = 0x1;
const IIR_THR_BIT: u8 = 0x2;
const IIR_RECV_BIT: u8 = 0x4;
const LSR_DATA_BIT: u8 = 0x1;
const LSR_EMPTY_BIT: u8 = 0x20;
const LSR_IDLE_BIT: u8 = 0x40;
const MCR_LOOP_BIT: u8 = 0x10;
const DEFAULT_INTERRUPT_IDENTIFICATION: u8 = IIR_NONE_BIT; // no pending interrupt
const DEFAULT_LINE_STATUS: u8 = LSR_EMPTY_BIT | LSR_IDLE_BIT; // THR empty and line is idle
const DEFAULT_LINE_CONTROL: u8 = 0x3; // 8-bits per character
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
/// guest, use `queue_input_bytes`.
pub struct Serial {
interrupt_enable: u8,
interrupt_identification: u8,
interrupt_evt: EventFd,
line_control: u8,
line_status: u8,
modem_control: u8,
modem_status: u8,
scratch: u8,
baud_divisor: u16,
in_buffer: VecDeque<u8>,
out: Option<Box<dyn io::Write + Send>>,
}
impl Serial {
fn new(interrupt_evt: EventFd, out: Option<Box<dyn io::Write + Send>>) -> Serial {
Serial {
interrupt_enable: 0,
interrupt_identification: DEFAULT_INTERRUPT_IDENTIFICATION,
interrupt_evt,
line_control: DEFAULT_LINE_CONTROL,
line_status: DEFAULT_LINE_STATUS,
modem_control: DEFAULT_MODEM_CONTROL,
modem_status: DEFAULT_MODEM_STATUS,
scratch: 0,
baud_divisor: DEFAULT_BAUD_DIVISOR,
in_buffer: VecDeque::new(),
out,
}
}
/// Constructs a Serial port ready for output.
pub fn new_out(interrupt_evt: EventFd, out: Box<dyn io::Write + Send>) -> Serial {
Self::new(interrupt_evt, Some(out))
}
/// Constructs a Serial port with no connected output.
pub fn new_sink(interrupt_evt: EventFd) -> Serial {
Self::new(interrupt_evt, None)
}
/// Queues raw bytes for the guest to read and signals the interrupt if the line status would
/// change.
pub fn queue_input_bytes(&mut self, c: &[u8]) -> Result<()> {
if !self.is_loop() {
self.in_buffer.extend(c);
self.recv_data()?;
}
Ok(())
}
fn is_dlab_set(&self) -> bool {
(self.line_control & 0x80) != 0
}
fn is_recv_intr_enabled(&self) -> bool {
(self.interrupt_enable & IER_RECV_BIT) != 0
}
fn is_thr_intr_enabled(&self) -> bool {
(self.interrupt_enable & IER_THR_BIT) != 0
}
fn is_loop(&self) -> bool {
(self.modem_control & MCR_LOOP_BIT) != 0
}
fn add_intr_bit(&mut self, bit: u8) {
self.interrupt_identification &= !IIR_NONE_BIT;
self.interrupt_identification |= bit;
}
fn del_intr_bit(&mut self, bit: u8) {
self.interrupt_identification &= !bit;
if self.interrupt_identification == 0x0 {
self.interrupt_identification = IIR_NONE_BIT;
}
}
fn thr_empty(&mut self) -> Result<()> {
if self.is_thr_intr_enabled() {
self.add_intr_bit(IIR_THR_BIT);
self.trigger_interrupt()?
}
Ok(())
}
fn recv_data(&mut self) -> Result<()> {
if self.is_recv_intr_enabled() {
self.add_intr_bit(IIR_RECV_BIT);
self.trigger_interrupt()?
}
self.line_status |= LSR_DATA_BIT;
Ok(())
}
fn trigger_interrupt(&mut self) -> Result<()> {
self.interrupt_evt.write(1)
}
fn iir_reset(&mut self) {
self.interrupt_identification = DEFAULT_INTERRUPT_IDENTIFICATION;
}
fn handle_write(&mut self, offset: u8, v: u8) -> Result<()> {
match offset as u8 {
DLAB_LOW if self.is_dlab_set() => {
self.baud_divisor = (self.baud_divisor & 0xff00) | v as u16
}
DLAB_HIGH if self.is_dlab_set() => {
self.baud_divisor = (self.baud_divisor & 0x00ff) | ((v as u16) << 8)
}
DATA => {
if self.is_loop() {
if self.in_buffer.len() < LOOP_SIZE {
self.in_buffer.push_back(v);
self.recv_data()?;
}
} else {
if let Some(out) = self.out.as_mut() {
out.write_all(&[v])?;
out.flush()?;
}
self.thr_empty()?;
}
}
IER => self.interrupt_enable = v & IER_FIFO_BITS,
LCR => self.line_control = v,
MCR => self.modem_control = v,
SCR => self.scratch = v,
_ => {}
}
Ok(())
}
}
impl BusDevice for Serial {
fn debug_label(&self) -> String {
"serial".to_owned()
}
fn write(&mut self, offset: u64, data: &[u8]) {
if data.len() != 1 {
return;
}
if let Err(e) = self.handle_write(offset as u8, data[0]) {
error!("serial failed write: {}", e);
}
}
fn read(&mut self, offset: u64, data: &mut [u8]) {
if data.len() != 1 {
return;
}
data[0] = match offset as u8 {
DLAB_LOW if self.is_dlab_set() => self.baud_divisor as u8,
DLAB_HIGH if self.is_dlab_set() => (self.baud_divisor >> 8) as u8,
DATA => {
self.del_intr_bit(IIR_RECV_BIT);
if self.in_buffer.len() <= 1 {
self.line_status &= !LSR_DATA_BIT;
}
self.in_buffer.pop_front().unwrap_or_default()
}
IER => self.interrupt_enable,
IIR => {
let v = self.interrupt_identification | IIR_FIFO_BITS;
self.iir_reset();
v
}
LCR => self.line_control,
MCR => self.modem_control,
LSR => self.line_status,
MSR => self.modem_status,
SCR => self.scratch,
_ => 0,
};
}
}
#[cfg(test)]
mod tests {
use super::*;
use std::io;
use std::sync::Arc;
use sync::Mutex;
#[derive(Clone)]
struct SharedBuffer {
buf: Arc<Mutex<Vec<u8>>>,
}
impl SharedBuffer {
fn new() -> SharedBuffer {
SharedBuffer {
buf: Arc::new(Mutex::new(Vec::new())),
}
}
}
impl io::Write for SharedBuffer {
fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
self.buf.lock().write(buf)
}
fn flush(&mut self) -> io::Result<()> {
self.buf.lock().flush()
}
}
#[test]
fn serial_output() {
let intr_evt = EventFd::new().unwrap();
let serial_out = SharedBuffer::new();
let mut serial = Serial::new_out(intr_evt, Box::new(serial_out.clone()));
serial.write(DATA as u64, &['a' as u8]);
serial.write(DATA as u64, &['b' as u8]);
serial.write(DATA as u64, &['c' as u8]);
assert_eq!(
serial_out.buf.lock().as_slice(),
&['a' as u8, 'b' as u8, 'c' as u8]
);
}
#[test]
fn serial_input() {
let intr_evt = EventFd::new().unwrap();
let serial_out = SharedBuffer::new();
let mut serial =
Serial::new_out(intr_evt.try_clone().unwrap(), Box::new(serial_out.clone()));
serial.write(IER as u64, &[IER_RECV_BIT]);
serial
.queue_input_bytes(&['a' as u8, 'b' as u8, 'c' as u8])
.unwrap();
assert_eq!(intr_evt.read(), Ok(1));
let mut data = [0u8; 1];
serial.read(DATA as u64, &mut data[..]);
assert_eq!(data[0], 'a' as u8);
serial.read(DATA as u64, &mut data[..]);
assert_eq!(data[0], 'b' as u8);
serial.read(DATA as u64, &mut data[..]);
assert_eq!(data[0], 'c' as u8);
}
}