2020-02-15 00:46:36 +00:00
|
|
|
// Copyright 2020 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::BTreeMap;
|
|
|
|
use std::fmt::{self, Display};
|
2020-05-19 07:36:39 +00:00
|
|
|
use std::fs::{File, OpenOptions};
|
2020-07-08 07:19:55 +00:00
|
|
|
use std::io::{self, stdin, stdout, ErrorKind};
|
|
|
|
use std::os::unix::net::UnixDatagram;
|
2020-02-15 00:46:36 +00:00
|
|
|
use std::path::PathBuf;
|
|
|
|
use std::str::FromStr;
|
|
|
|
use std::sync::Arc;
|
|
|
|
|
2020-10-09 08:20:52 +00:00
|
|
|
use base::{error, info, read_raw_stdin, syslog, AsRawDescriptor, Event, RawDescriptor};
|
2020-03-25 20:54:50 +00:00
|
|
|
use devices::{Bus, ProxyDevice, Serial, SerialDevice};
|
2020-06-19 14:19:48 +00:00
|
|
|
use minijail::Minijail;
|
2020-02-15 00:46:36 +00:00
|
|
|
use sync::Mutex;
|
|
|
|
|
|
|
|
use crate::DeviceRegistrationError;
|
|
|
|
|
|
|
|
#[derive(Debug)]
|
|
|
|
pub enum Error {
|
2020-09-16 22:29:20 +00:00
|
|
|
CloneEvent(base::Error),
|
2020-02-15 00:46:36 +00:00
|
|
|
FileError(std::io::Error),
|
2020-03-09 20:16:46 +00:00
|
|
|
InvalidSerialHardware(String),
|
2020-02-15 00:46:36 +00:00
|
|
|
InvalidSerialType(String),
|
|
|
|
PathRequired,
|
2020-07-08 07:19:55 +00:00
|
|
|
SocketCreateFailed,
|
2020-02-15 00:46:36 +00:00
|
|
|
Unimplemented(SerialType),
|
|
|
|
}
|
|
|
|
|
|
|
|
impl Display for Error {
|
|
|
|
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
|
|
|
use self::Error::*;
|
|
|
|
|
|
|
|
match self {
|
2020-09-16 22:29:20 +00:00
|
|
|
CloneEvent(e) => write!(f, "unable to clone an Event: {}", e),
|
2020-02-15 00:46:36 +00:00
|
|
|
FileError(e) => write!(f, "unable to open/create file: {}", e),
|
2020-03-09 20:16:46 +00:00
|
|
|
InvalidSerialHardware(e) => write!(f, "invalid serial hardware: {}", e),
|
2020-02-15 00:46:36 +00:00
|
|
|
InvalidSerialType(e) => write!(f, "invalid serial type: {}", e),
|
|
|
|
PathRequired => write!(f, "serial device type file requires a path"),
|
2020-07-08 07:19:55 +00:00
|
|
|
SocketCreateFailed => write!(f, "failed to create unbound socket"),
|
2020-02-15 00:46:36 +00:00
|
|
|
Unimplemented(e) => write!(f, "serial device type {} not implemented", e.to_string()),
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/// Enum for possible type of serial devices
|
2020-03-09 20:16:46 +00:00
|
|
|
#[derive(Clone, Debug)]
|
2020-02-15 00:46:36 +00:00
|
|
|
pub enum SerialType {
|
|
|
|
File,
|
|
|
|
Stdout,
|
|
|
|
Sink,
|
|
|
|
Syslog,
|
2020-07-08 07:19:55 +00:00
|
|
|
UnixSocket,
|
2020-02-15 00:46:36 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
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" => Ok(SerialType::File),
|
|
|
|
"stdout" | "Stdout" => Ok(SerialType::Stdout),
|
|
|
|
"sink" | "Sink" => Ok(SerialType::Sink),
|
|
|
|
"syslog" | "Syslog" => Ok(SerialType::Syslog),
|
|
|
|
"unix" | "UnixSocket" => Ok(SerialType::UnixSocket),
|
|
|
|
_ => Err(Error::InvalidSerialType(s.to_string())),
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-03-09 20:16:46 +00:00
|
|
|
/// 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())),
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-07-08 07:19:55 +00:00
|
|
|
struct WriteSocket {
|
|
|
|
sock: UnixDatagram,
|
|
|
|
path: PathBuf,
|
|
|
|
buf: String,
|
|
|
|
}
|
|
|
|
|
|
|
|
const BUF_CAPACITY: usize = 1024;
|
|
|
|
|
|
|
|
impl WriteSocket {
|
|
|
|
pub fn new(s: UnixDatagram, p: PathBuf) -> WriteSocket {
|
|
|
|
WriteSocket {
|
|
|
|
sock: s,
|
|
|
|
path: p,
|
|
|
|
buf: String::with_capacity(BUF_CAPACITY),
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// |send_buf| uses an immutable |self|, even though its |sock| is possibly
|
|
|
|
// going be modified. This is needed to allow the mutating of |buf| between
|
|
|
|
// calls to |send_buf| in the io::Write impl.
|
|
|
|
pub fn send_buf(&self, buf: &[u8]) -> io::Result<usize> {
|
|
|
|
// It's possible we aren't yet connected to the socket. Always try once
|
|
|
|
// to reconnect if sending fails.
|
|
|
|
const SEND_RETRY: usize = 2;
|
|
|
|
let mut sent = 0;
|
|
|
|
for _ in 0..SEND_RETRY {
|
|
|
|
match self.sock.send(&buf[..]) {
|
|
|
|
Ok(bytes_sent) => {
|
|
|
|
sent = bytes_sent;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
Err(e) => match e.kind() {
|
|
|
|
ErrorKind::ConnectionRefused
|
|
|
|
| ErrorKind::ConnectionReset
|
|
|
|
| ErrorKind::ConnectionAborted
|
|
|
|
| ErrorKind::NotConnected
|
|
|
|
| ErrorKind::Other => match self.sock.connect(self.path.as_path()) {
|
|
|
|
Ok(_) => {}
|
|
|
|
Err(e) => {
|
|
|
|
info!("Couldn't connect {:?}", e);
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
},
|
|
|
|
_ => {
|
|
|
|
info!("Send error: {:?}", e);
|
|
|
|
}
|
|
|
|
},
|
|
|
|
}
|
|
|
|
}
|
|
|
|
Ok(sent)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
impl io::Write for WriteSocket {
|
|
|
|
fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
|
|
|
|
let parsed_str = String::from_utf8_lossy(buf);
|
|
|
|
|
|
|
|
let last_newline_idx = match parsed_str.rfind('\n') {
|
|
|
|
Some(newline_idx) => Some(self.buf.len() + newline_idx),
|
|
|
|
None => None,
|
|
|
|
};
|
|
|
|
self.buf.push_str(&parsed_str);
|
|
|
|
|
|
|
|
match last_newline_idx {
|
|
|
|
Some(last_newline_idx) => {
|
|
|
|
for line in (self.buf[..last_newline_idx]).lines() {
|
|
|
|
if self.send_buf(line.as_bytes()).is_err() {
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
self.buf.drain(..=last_newline_idx);
|
|
|
|
}
|
|
|
|
None => {
|
|
|
|
if self.buf.len() >= BUF_CAPACITY {
|
|
|
|
if let Err(e) = self.send_buf(self.buf.as_bytes()) {
|
|
|
|
info!("Couldn't send full buffer. {:?}", e);
|
|
|
|
}
|
|
|
|
self.buf.clear();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
Ok(buf.len())
|
|
|
|
}
|
|
|
|
|
|
|
|
fn flush(&mut self) -> io::Result<()> {
|
|
|
|
Ok(())
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-02-15 00:46:36 +00:00
|
|
|
/// Holds the parameters for a serial device
|
2020-03-09 20:16:46 +00:00
|
|
|
#[derive(Clone, Debug)]
|
2020-02-15 00:46:36 +00:00
|
|
|
pub struct SerialParameters {
|
|
|
|
pub type_: SerialType,
|
2020-03-09 20:16:46 +00:00
|
|
|
pub hardware: SerialHardware,
|
2020-02-15 00:46:36 +00:00
|
|
|
pub path: Option<PathBuf>,
|
|
|
|
pub input: Option<PathBuf>,
|
|
|
|
pub num: u8,
|
|
|
|
pub console: bool,
|
2020-03-09 20:16:46 +00:00
|
|
|
pub earlycon: bool,
|
2020-02-15 00:46:36 +00:00
|
|
|
pub stdin: bool,
|
|
|
|
}
|
|
|
|
|
|
|
|
impl SerialParameters {
|
|
|
|
/// Helper function to create a serial device from the defined parameters.
|
|
|
|
///
|
|
|
|
/// # Arguments
|
2020-09-16 22:29:20 +00:00
|
|
|
/// * `evt` - event used for interrupt events
|
2020-10-21 05:12:20 +00:00
|
|
|
/// * `keep_rds` - Vector of descriptors required by this device if it were sandboxed
|
|
|
|
/// in a child process. `evt` will always be added to this vector by
|
|
|
|
/// this function.
|
2020-03-25 20:54:50 +00:00
|
|
|
pub fn create_serial_device<T: SerialDevice>(
|
2020-02-15 00:46:36 +00:00
|
|
|
&self,
|
2020-10-06 17:51:12 +00:00
|
|
|
protected_vm: bool,
|
2020-09-16 22:29:20 +00:00
|
|
|
evt: &Event,
|
2020-10-21 05:12:20 +00:00
|
|
|
keep_rds: &mut Vec<RawDescriptor>,
|
2020-03-25 20:54:50 +00:00
|
|
|
) -> std::result::Result<T, Error> {
|
2020-09-16 22:29:20 +00:00
|
|
|
let evt = evt.try_clone().map_err(Error::CloneEvent)?;
|
2020-10-21 05:12:20 +00:00
|
|
|
keep_rds.push(evt.as_raw_descriptor());
|
2020-02-15 00:46:36 +00:00
|
|
|
let input: Option<Box<dyn io::Read + Send>> = if let Some(input_path) = &self.input {
|
|
|
|
let input_file = File::open(input_path.as_path()).map_err(Error::FileError)?;
|
2020-10-21 05:12:20 +00:00
|
|
|
keep_rds.push(input_file.as_raw_descriptor());
|
2020-02-15 00:46:36 +00:00
|
|
|
Some(Box::new(input_file))
|
|
|
|
} else if self.stdin {
|
2020-10-21 05:12:20 +00:00
|
|
|
keep_rds.push(stdin().as_raw_descriptor());
|
2020-02-15 00:46:36 +00:00
|
|
|
// This wrapper is used in place of the libstd native version because we don't want
|
|
|
|
// buffering for stdin.
|
|
|
|
struct StdinWrapper;
|
|
|
|
impl io::Read for StdinWrapper {
|
|
|
|
fn read(&mut self, out: &mut [u8]) -> io::Result<usize> {
|
|
|
|
read_raw_stdin(out).map_err(|e| e.into())
|
|
|
|
}
|
|
|
|
}
|
|
|
|
Some(Box::new(StdinWrapper))
|
|
|
|
} else {
|
|
|
|
None
|
|
|
|
};
|
2020-03-25 21:38:17 +00:00
|
|
|
let output: Option<Box<dyn io::Write + Send>> = match self.type_ {
|
2020-02-15 00:46:36 +00:00
|
|
|
SerialType::Stdout => {
|
2020-10-21 05:12:20 +00:00
|
|
|
keep_rds.push(stdout().as_raw_descriptor());
|
2020-03-25 21:38:17 +00:00
|
|
|
Some(Box::new(stdout()))
|
2020-02-15 00:46:36 +00:00
|
|
|
}
|
2020-03-25 21:38:17 +00:00
|
|
|
SerialType::Sink => None,
|
2020-02-15 00:46:36 +00:00
|
|
|
SerialType::Syslog => {
|
2020-10-21 05:12:20 +00:00
|
|
|
syslog::push_fds(keep_rds);
|
2020-03-25 21:38:17 +00:00
|
|
|
Some(Box::new(syslog::Syslogger::new(
|
|
|
|
syslog::Priority::Info,
|
|
|
|
syslog::Facility::Daemon,
|
|
|
|
)))
|
2020-02-15 00:46:36 +00:00
|
|
|
}
|
|
|
|
SerialType::File => match &self.path {
|
|
|
|
Some(path) => {
|
2020-05-19 07:36:39 +00:00
|
|
|
let file = OpenOptions::new()
|
|
|
|
.append(true)
|
|
|
|
.create(true)
|
|
|
|
.open(path.as_path())
|
|
|
|
.map_err(Error::FileError)?;
|
2020-10-21 05:12:20 +00:00
|
|
|
keep_rds.push(file.as_raw_descriptor());
|
2020-03-25 21:38:17 +00:00
|
|
|
Some(Box::new(file))
|
2020-02-15 00:46:36 +00:00
|
|
|
}
|
2020-03-25 21:38:17 +00:00
|
|
|
None => return Err(Error::PathRequired),
|
2020-02-15 00:46:36 +00:00
|
|
|
},
|
2020-07-08 07:19:55 +00:00
|
|
|
SerialType::UnixSocket => match &self.path {
|
|
|
|
Some(path) => {
|
2020-10-23 05:00:28 +00:00
|
|
|
let sock = match UnixDatagram::bind(path) {
|
2020-07-08 07:19:55 +00:00
|
|
|
Ok(sock) => sock,
|
|
|
|
Err(e) => {
|
2020-10-23 05:00:28 +00:00
|
|
|
if e.kind() == ErrorKind::AddrInUse {
|
|
|
|
// In most cases vmlog_forwarder will
|
|
|
|
// have already bound this address and
|
|
|
|
// this error is expected. This
|
|
|
|
// unbound socket will be connected on
|
|
|
|
// first write.
|
|
|
|
UnixDatagram::unbound().map_err(Error::FileError)?
|
|
|
|
} else {
|
|
|
|
error!("Couldn't bind: {:?}", e);
|
|
|
|
return Err(Error::FileError(e));
|
|
|
|
}
|
2020-07-08 07:19:55 +00:00
|
|
|
}
|
|
|
|
};
|
2020-10-21 05:12:20 +00:00
|
|
|
keep_rds.push(sock.as_raw_descriptor());
|
2020-07-08 07:19:55 +00:00
|
|
|
Some(Box::new(WriteSocket::new(sock, path.to_path_buf())))
|
|
|
|
}
|
|
|
|
None => return Err(Error::PathRequired),
|
|
|
|
},
|
2020-03-25 21:38:17 +00:00
|
|
|
};
|
2020-10-21 05:12:20 +00:00
|
|
|
Ok(T::new(protected_vm, evt, input, output, keep_rds.to_vec()))
|
2020-02-15 00:46:36 +00:00
|
|
|
}
|
2020-07-08 07:19:55 +00:00
|
|
|
|
|
|
|
pub fn add_bind_mounts(&self, jail: &mut Minijail) -> Result<(), minijail::Error> {
|
|
|
|
if let Some(path) = &self.path {
|
|
|
|
match self.type_ {
|
|
|
|
SerialType::UnixSocket => {
|
|
|
|
if let Some(parent) = path.as_path().parent() {
|
|
|
|
if parent.exists() {
|
|
|
|
info!("Bind mounting dir {}", parent.display());
|
|
|
|
jail.mount_bind(parent, parent, true)?;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
_ => {}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
Ok(())
|
|
|
|
}
|
2020-02-15 00:46:36 +00:00
|
|
|
}
|
|
|
|
|
2020-03-09 20:16:46 +00:00
|
|
|
/// 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,
|
|
|
|
});
|
|
|
|
}
|
|
|
|
}
|
2020-02-15 00:46:36 +00:00
|
|
|
|
|
|
|
/// Address for Serial ports in x86
|
|
|
|
pub const SERIAL_ADDR: [u64; 4] = [0x3f8, 0x2f8, 0x3e8, 0x2e8];
|
|
|
|
|
2020-03-09 20:16:46 +00:00
|
|
|
/// 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.
|
2020-02-15 00:46:36 +00:00
|
|
|
///
|
|
|
|
/// # Arguments
|
|
|
|
///
|
|
|
|
/// * `io_bus` - Bus to add the devices to
|
2020-09-16 22:29:20 +00:00
|
|
|
/// * `com_evt_1_3` - event for com1 and com3
|
|
|
|
/// * `com_evt_1_4` - event for com2 and com4
|
2020-02-15 00:46:36 +00:00
|
|
|
/// * `io_bus` - Bus to add the devices to
|
2020-03-09 20:16:46 +00:00
|
|
|
/// * `serial_parameters` - definitions of serial parameter configurations.
|
|
|
|
/// All four of the traditional PC-style serial ports (COM1-COM4) must be specified.
|
2020-02-15 00:46:36 +00:00
|
|
|
pub fn add_serial_devices(
|
2020-10-06 17:51:12 +00:00
|
|
|
protected_vm: bool,
|
2020-02-15 00:46:36 +00:00
|
|
|
io_bus: &mut Bus,
|
2020-09-16 22:29:20 +00:00
|
|
|
com_evt_1_3: &Event,
|
|
|
|
com_evt_2_4: &Event,
|
2020-03-09 20:16:46 +00:00
|
|
|
serial_parameters: &BTreeMap<(SerialHardware, u8), SerialParameters>,
|
2020-02-15 00:46:36 +00:00
|
|
|
serial_jail: Option<Minijail>,
|
2020-03-09 20:16:46 +00:00
|
|
|
) -> Result<(), DeviceRegistrationError> {
|
2020-02-15 00:46:36 +00:00
|
|
|
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
|
2020-03-09 20:16:46 +00:00
|
|
|
.get(&(SerialHardware::Serial, x + 1))
|
|
|
|
.ok_or(DeviceRegistrationError::MissingRequiredSerialDevice(x + 1))?;
|
2020-02-15 00:46:36 +00:00
|
|
|
|
|
|
|
let mut preserved_fds = Vec::new();
|
|
|
|
let com = param
|
2020-10-06 17:51:12 +00:00
|
|
|
.create_serial_device::<Serial>(protected_vm, &com_evt, &mut preserved_fds)
|
2020-02-15 00:46:36 +00:00
|
|
|
.map_err(DeviceRegistrationError::CreateSerialDevice)?;
|
|
|
|
|
|
|
|
match serial_jail.as_ref() {
|
|
|
|
Some(jail) => {
|
|
|
|
let com = Arc::new(Mutex::new(
|
|
|
|
ProxyDevice::new(com, &jail, preserved_fds)
|
|
|
|
.map_err(DeviceRegistrationError::ProxyDeviceCreation)?,
|
|
|
|
));
|
|
|
|
io_bus
|
|
|
|
.insert(com.clone(), SERIAL_ADDR[x as usize], 0x8, false)
|
|
|
|
.unwrap();
|
|
|
|
}
|
|
|
|
None => {
|
|
|
|
let com = Arc::new(Mutex::new(com));
|
|
|
|
io_bus
|
|
|
|
.insert(com.clone(), SERIAL_ADDR[x as usize], 0x8, false)
|
|
|
|
.unwrap();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-03-09 20:16:46 +00:00
|
|
|
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");
|
|
|
|
}
|
2020-02-15 00:46:36 +00:00
|
|
|
}
|