2017-05-02 01:00:12 +00:00
|
|
|
// 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;
|
2019-04-17 19:51:25 +00:00
|
|
|
use std::fmt::{self, Display};
|
2019-05-17 20:57:04 +00:00
|
|
|
use std::fs::File;
|
2019-12-13 02:58:50 +00:00
|
|
|
use std::io::{self, stdin, stdout, Read, Write};
|
2019-08-13 18:20:14 +00:00
|
|
|
use std::os::unix::io::{AsRawFd, RawFd};
|
2019-04-17 19:51:25 +00:00
|
|
|
use std::path::PathBuf;
|
|
|
|
use std::str::FromStr;
|
2019-12-13 02:58:50 +00:00
|
|
|
use std::sync::atomic::{AtomicU8, Ordering};
|
|
|
|
use std::sync::mpsc::{channel, Receiver, TryRecvError};
|
2019-08-13 18:20:14 +00:00
|
|
|
use std::sync::Arc;
|
2019-12-13 02:58:50 +00:00
|
|
|
use std::thread::{self};
|
2017-05-02 01:00:12 +00:00
|
|
|
|
2019-12-13 02:58:50 +00:00
|
|
|
use sys_util::{error, read_raw_stdin, syslog, EventFd, Result};
|
2017-05-02 01:00:12 +00:00
|
|
|
|
2019-03-08 23:57:49 +00:00
|
|
|
use crate::BusDevice;
|
2017-05-02 01:00:12 +00:00
|
|
|
|
|
|
|
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;
|
|
|
|
|
2019-05-20 20:37:54 +00:00
|
|
|
const MCR_DTR_BIT: u8 = 0x01; // Data Terminal Ready
|
|
|
|
const MCR_RTS_BIT: u8 = 0x02; // Request to Send
|
|
|
|
const MCR_OUT1_BIT: u8 = 0x04;
|
|
|
|
const MCR_OUT2_BIT: u8 = 0x08;
|
2017-05-02 01:00:12 +00:00
|
|
|
const MCR_LOOP_BIT: u8 = 0x10;
|
|
|
|
|
2019-05-20 20:37:54 +00:00
|
|
|
const MSR_CTS_BIT: u8 = 0x10; // Clear to Send
|
|
|
|
const MSR_DSR_BIT: u8 = 0x20; // Data Set Ready
|
|
|
|
const MSR_RI_BIT: u8 = 0x40; // Ring Indicator
|
|
|
|
const MSR_DCD_BIT: u8 = 0x80; // Data Carrier Detect
|
|
|
|
|
2017-05-02 01:00:12 +00:00
|
|
|
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
|
2019-05-20 20:37:54 +00:00
|
|
|
const DEFAULT_MODEM_CONTROL: u8 = MCR_OUT2_BIT;
|
|
|
|
const DEFAULT_MODEM_STATUS: u8 = MSR_DSR_BIT | MSR_CTS_BIT | MSR_DCD_BIT;
|
2017-05-02 01:00:12 +00:00
|
|
|
const DEFAULT_BAUD_DIVISOR: u16 = 12; // 9600 bps
|
|
|
|
|
2019-04-17 19:51:25 +00:00
|
|
|
#[derive(Debug)]
|
|
|
|
pub enum Error {
|
|
|
|
CloneEventFd(sys_util::Error),
|
|
|
|
InvalidSerialType(String),
|
2019-05-17 20:57:04 +00:00
|
|
|
PathRequired,
|
|
|
|
FileError(std::io::Error),
|
2019-04-17 19:51:25 +00:00
|
|
|
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),
|
2019-05-17 20:57:04 +00:00
|
|
|
FileError(e) => write!(f, "Unable to open/create file: {}", e),
|
2019-04-17 19:51:25 +00:00
|
|
|
InvalidSerialType(e) => write!(f, "invalid serial type: {}", e),
|
2019-05-17 20:57:04 +00:00
|
|
|
PathRequired => write!(f, "serial device type file requires a path"),
|
2019-04-17 19:51:25 +00:00
|
|
|
Unimplemented(e) => write!(f, "serial device type {} not implemented", e.to_string()),
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/// Enum for possible type of serial devices
|
|
|
|
#[derive(Debug)]
|
|
|
|
pub enum SerialType {
|
2019-05-17 20:57:04 +00:00
|
|
|
File,
|
2019-04-17 19:51:25 +00:00
|
|
|
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 {
|
2019-06-05 21:50:46 +00:00
|
|
|
"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),
|
2019-04-17 19:51:25 +00:00
|
|
|
_ => 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>,
|
Serial: add input path overriding stdin for --serial
Allowing the input to be specified for file-based serial ports allows
the user of pipes as input/output. That enables kgdb over serial.
TEST=
Build a kernel with support for gdb
```
make x86_64_defconfig
make kvmconfig
./scripts/config --enable GDB_SCRIPTS
./scripts/config --enable KGDB
./scripts/config --enable KGDB_SERIAL_CONSOLE
./scripts/config --enable KGDB_LOW_LEVEL_TRAP
./scripts/config --enable KGDB_KDB
./scripts/config --enable KDB_KEYBOARD
./scripts/config --enable GDB_SCRIPTS
./scripts/config --set-val KDB_CONTINUE_CATASTROPHIC 0
make -j33
```
Setup devices for PTYs
To make sure crosvm doesn't create an ordinary file if socat is started
after it, create these named pipes first:
```
mkfifo ~/console_{in,out} ~/kgdb_{in,out}
```
Set up two PTYs: ~/kgdb for the debugger, and ~/serial for the console.
PTY ~/kgdb connects to ~/kgdb{in,out}, and ~/serial connects to
~/console{in,out}
```
socat -d -d -d \
'PIPE:$HOME/console_out,rdonly=1,nonblock=1,ignoreeof=1!!PIPE:$HOME/console_in,wronly=1' \
PTY,link=$HOME/serial,ctty,raw,echo=0
socat -d -d -d \
'PIPE:$HOME/kgdb_out,rdonly=1,nonblock=1,ignoreeof=1!!PIPE:$HOME/kgdb_in,wronly=1' \
PTY,link=$HOME/kgdb,ctty,raw,echo=0
```
Start crosvm with serial ports pointed at ~/console{in,out} and ~/kgdb{in,out}.
```
cargo run run -p 'init=/bin/sh panic=0 kgdboc=ttyS1,115200 kgdbwait kgdbcon' \
--serial type=file,path=$HOME/console_out,num=1,console=true,stdin=false,input=$HOME/console_in \
--serial type=file,path=$HOME/kgdb_out,input=$HOME/kgdb_in,num=2,console=false,stdin=false \
-r ~/rootfs.img \
~/src/linux/arch/x86/boot/bzImage
```
Start GDB
```
gdb vmlinux -ex "target remote /home/dgreid/kgdb"
```
To break into gdb, open up the serial console, mount /proc and send a SysRq
```
minicom -D ~/serial
mount -t proc none /proc
echo g > /proc/sysrq-trigger
```
Change-Id: I18a9c1087d38301df49de08eeae2f8559b03463a
Reviewed-on: https://chromium-review.googlesource.com/c/chromiumos/platform/crosvm/+/2151856
Reviewed-by: Daniel Verkamp <dverkamp@chromium.org>
Tested-by: Dylan Reid <dgreid@chromium.org>
Commit-Queue: Dylan Reid <dgreid@chromium.org>
2020-04-14 16:40:41 +00:00
|
|
|
pub input: Option<PathBuf>,
|
2019-04-17 19:51:25 +00:00
|
|
|
pub num: u8,
|
|
|
|
pub console: bool,
|
2019-08-01 21:40:03 +00:00
|
|
|
pub stdin: bool,
|
2019-04-17 19:51:25 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
impl SerialParameters {
|
|
|
|
/// Helper function to create a serial device from the defined parameters.
|
|
|
|
///
|
|
|
|
/// # Arguments
|
|
|
|
/// * `evt_fd` - eventfd used for interrupt events
|
2019-08-13 18:20:14 +00:00
|
|
|
/// * `keep_fds` - Vector of FDs required by this device if it were sandboxed in a child
|
|
|
|
/// process. `evt_fd` will always be added to this vector by this function.
|
|
|
|
pub fn create_serial_device(
|
|
|
|
&self,
|
|
|
|
evt_fd: &EventFd,
|
|
|
|
keep_fds: &mut Vec<RawFd>,
|
|
|
|
) -> std::result::Result<Serial, Error> {
|
|
|
|
let evt_fd = evt_fd.try_clone().map_err(Error::CloneEventFd)?;
|
|
|
|
keep_fds.push(evt_fd.as_raw_fd());
|
Serial: add input path overriding stdin for --serial
Allowing the input to be specified for file-based serial ports allows
the user of pipes as input/output. That enables kgdb over serial.
TEST=
Build a kernel with support for gdb
```
make x86_64_defconfig
make kvmconfig
./scripts/config --enable GDB_SCRIPTS
./scripts/config --enable KGDB
./scripts/config --enable KGDB_SERIAL_CONSOLE
./scripts/config --enable KGDB_LOW_LEVEL_TRAP
./scripts/config --enable KGDB_KDB
./scripts/config --enable KDB_KEYBOARD
./scripts/config --enable GDB_SCRIPTS
./scripts/config --set-val KDB_CONTINUE_CATASTROPHIC 0
make -j33
```
Setup devices for PTYs
To make sure crosvm doesn't create an ordinary file if socat is started
after it, create these named pipes first:
```
mkfifo ~/console_{in,out} ~/kgdb_{in,out}
```
Set up two PTYs: ~/kgdb for the debugger, and ~/serial for the console.
PTY ~/kgdb connects to ~/kgdb{in,out}, and ~/serial connects to
~/console{in,out}
```
socat -d -d -d \
'PIPE:$HOME/console_out,rdonly=1,nonblock=1,ignoreeof=1!!PIPE:$HOME/console_in,wronly=1' \
PTY,link=$HOME/serial,ctty,raw,echo=0
socat -d -d -d \
'PIPE:$HOME/kgdb_out,rdonly=1,nonblock=1,ignoreeof=1!!PIPE:$HOME/kgdb_in,wronly=1' \
PTY,link=$HOME/kgdb,ctty,raw,echo=0
```
Start crosvm with serial ports pointed at ~/console{in,out} and ~/kgdb{in,out}.
```
cargo run run -p 'init=/bin/sh panic=0 kgdboc=ttyS1,115200 kgdbwait kgdbcon' \
--serial type=file,path=$HOME/console_out,num=1,console=true,stdin=false,input=$HOME/console_in \
--serial type=file,path=$HOME/kgdb_out,input=$HOME/kgdb_in,num=2,console=false,stdin=false \
-r ~/rootfs.img \
~/src/linux/arch/x86/boot/bzImage
```
Start GDB
```
gdb vmlinux -ex "target remote /home/dgreid/kgdb"
```
To break into gdb, open up the serial console, mount /proc and send a SysRq
```
minicom -D ~/serial
mount -t proc none /proc
echo g > /proc/sysrq-trigger
```
Change-Id: I18a9c1087d38301df49de08eeae2f8559b03463a
Reviewed-on: https://chromium-review.googlesource.com/c/chromiumos/platform/crosvm/+/2151856
Reviewed-by: Daniel Verkamp <dverkamp@chromium.org>
Tested-by: Dylan Reid <dgreid@chromium.org>
Commit-Queue: Dylan Reid <dgreid@chromium.org>
2020-04-14 16:40:41 +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)?;
|
|
|
|
keep_fds.push(input_file.as_raw_fd());
|
|
|
|
Some(Box::new(input_file))
|
|
|
|
} else if self.stdin {
|
2020-03-08 01:26:27 +00:00
|
|
|
keep_fds.push(stdin().as_raw_fd());
|
|
|
|
// 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
|
|
|
|
};
|
2019-04-17 19:51:25 +00:00
|
|
|
match self.type_ {
|
2019-08-13 18:20:14 +00:00
|
|
|
SerialType::Stdout => {
|
|
|
|
keep_fds.push(stdout().as_raw_fd());
|
2020-03-08 01:26:27 +00:00
|
|
|
Ok(Serial::new(evt_fd, input, Some(Box::new(stdout()))))
|
2019-08-13 18:20:14 +00:00
|
|
|
}
|
2020-03-08 01:26:27 +00:00
|
|
|
SerialType::Sink => Ok(Serial::new(evt_fd, input, None)),
|
2019-08-13 18:20:14 +00:00
|
|
|
SerialType::Syslog => {
|
|
|
|
syslog::push_fds(keep_fds);
|
2020-03-08 01:26:27 +00:00
|
|
|
Ok(Serial::new(
|
2019-08-13 18:20:14 +00:00
|
|
|
evt_fd,
|
2020-03-08 01:26:27 +00:00
|
|
|
input,
|
|
|
|
Some(Box::new(syslog::Syslogger::new(
|
2019-08-13 18:20:14 +00:00
|
|
|
syslog::Priority::Info,
|
|
|
|
syslog::Facility::Daemon,
|
2020-03-08 01:26:27 +00:00
|
|
|
))),
|
2019-08-13 18:20:14 +00:00
|
|
|
))
|
|
|
|
}
|
2019-05-17 20:57:04 +00:00
|
|
|
SerialType::File => match &self.path {
|
2019-08-13 18:20:14 +00:00
|
|
|
Some(path) => {
|
|
|
|
let file = File::create(path.as_path()).map_err(Error::FileError)?;
|
|
|
|
keep_fds.push(file.as_raw_fd());
|
2020-03-08 01:26:27 +00:00
|
|
|
Ok(Serial::new(evt_fd, input, Some(Box::new(file))))
|
2019-08-13 18:20:14 +00:00
|
|
|
}
|
Serial: add input path overriding stdin for --serial
Allowing the input to be specified for file-based serial ports allows
the user of pipes as input/output. That enables kgdb over serial.
TEST=
Build a kernel with support for gdb
```
make x86_64_defconfig
make kvmconfig
./scripts/config --enable GDB_SCRIPTS
./scripts/config --enable KGDB
./scripts/config --enable KGDB_SERIAL_CONSOLE
./scripts/config --enable KGDB_LOW_LEVEL_TRAP
./scripts/config --enable KGDB_KDB
./scripts/config --enable KDB_KEYBOARD
./scripts/config --enable GDB_SCRIPTS
./scripts/config --set-val KDB_CONTINUE_CATASTROPHIC 0
make -j33
```
Setup devices for PTYs
To make sure crosvm doesn't create an ordinary file if socat is started
after it, create these named pipes first:
```
mkfifo ~/console_{in,out} ~/kgdb_{in,out}
```
Set up two PTYs: ~/kgdb for the debugger, and ~/serial for the console.
PTY ~/kgdb connects to ~/kgdb{in,out}, and ~/serial connects to
~/console{in,out}
```
socat -d -d -d \
'PIPE:$HOME/console_out,rdonly=1,nonblock=1,ignoreeof=1!!PIPE:$HOME/console_in,wronly=1' \
PTY,link=$HOME/serial,ctty,raw,echo=0
socat -d -d -d \
'PIPE:$HOME/kgdb_out,rdonly=1,nonblock=1,ignoreeof=1!!PIPE:$HOME/kgdb_in,wronly=1' \
PTY,link=$HOME/kgdb,ctty,raw,echo=0
```
Start crosvm with serial ports pointed at ~/console{in,out} and ~/kgdb{in,out}.
```
cargo run run -p 'init=/bin/sh panic=0 kgdboc=ttyS1,115200 kgdbwait kgdbcon' \
--serial type=file,path=$HOME/console_out,num=1,console=true,stdin=false,input=$HOME/console_in \
--serial type=file,path=$HOME/kgdb_out,input=$HOME/kgdb_in,num=2,console=false,stdin=false \
-r ~/rootfs.img \
~/src/linux/arch/x86/boot/bzImage
```
Start GDB
```
gdb vmlinux -ex "target remote /home/dgreid/kgdb"
```
To break into gdb, open up the serial console, mount /proc and send a SysRq
```
minicom -D ~/serial
mount -t proc none /proc
echo g > /proc/sysrq-trigger
```
Change-Id: I18a9c1087d38301df49de08eeae2f8559b03463a
Reviewed-on: https://chromium-review.googlesource.com/c/chromiumos/platform/crosvm/+/2151856
Reviewed-by: Daniel Verkamp <dverkamp@chromium.org>
Tested-by: Dylan Reid <dgreid@chromium.org>
Commit-Queue: Dylan Reid <dgreid@chromium.org>
2020-04-14 16:40:41 +00:00
|
|
|
_ => Err(Error::PathRequired),
|
2019-05-17 20:57:04 +00:00
|
|
|
},
|
2019-04-17 19:51:25 +00:00
|
|
|
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,
|
Serial: add input path overriding stdin for --serial
Allowing the input to be specified for file-based serial ports allows
the user of pipes as input/output. That enables kgdb over serial.
TEST=
Build a kernel with support for gdb
```
make x86_64_defconfig
make kvmconfig
./scripts/config --enable GDB_SCRIPTS
./scripts/config --enable KGDB
./scripts/config --enable KGDB_SERIAL_CONSOLE
./scripts/config --enable KGDB_LOW_LEVEL_TRAP
./scripts/config --enable KGDB_KDB
./scripts/config --enable KDB_KEYBOARD
./scripts/config --enable GDB_SCRIPTS
./scripts/config --set-val KDB_CONTINUE_CATASTROPHIC 0
make -j33
```
Setup devices for PTYs
To make sure crosvm doesn't create an ordinary file if socat is started
after it, create these named pipes first:
```
mkfifo ~/console_{in,out} ~/kgdb_{in,out}
```
Set up two PTYs: ~/kgdb for the debugger, and ~/serial for the console.
PTY ~/kgdb connects to ~/kgdb{in,out}, and ~/serial connects to
~/console{in,out}
```
socat -d -d -d \
'PIPE:$HOME/console_out,rdonly=1,nonblock=1,ignoreeof=1!!PIPE:$HOME/console_in,wronly=1' \
PTY,link=$HOME/serial,ctty,raw,echo=0
socat -d -d -d \
'PIPE:$HOME/kgdb_out,rdonly=1,nonblock=1,ignoreeof=1!!PIPE:$HOME/kgdb_in,wronly=1' \
PTY,link=$HOME/kgdb,ctty,raw,echo=0
```
Start crosvm with serial ports pointed at ~/console{in,out} and ~/kgdb{in,out}.
```
cargo run run -p 'init=/bin/sh panic=0 kgdboc=ttyS1,115200 kgdbwait kgdbcon' \
--serial type=file,path=$HOME/console_out,num=1,console=true,stdin=false,input=$HOME/console_in \
--serial type=file,path=$HOME/kgdb_out,input=$HOME/kgdb_in,num=2,console=false,stdin=false \
-r ~/rootfs.img \
~/src/linux/arch/x86/boot/bzImage
```
Start GDB
```
gdb vmlinux -ex "target remote /home/dgreid/kgdb"
```
To break into gdb, open up the serial console, mount /proc and send a SysRq
```
minicom -D ~/serial
mount -t proc none /proc
echo g > /proc/sysrq-trigger
```
Change-Id: I18a9c1087d38301df49de08eeae2f8559b03463a
Reviewed-on: https://chromium-review.googlesource.com/c/chromiumos/platform/crosvm/+/2151856
Reviewed-by: Daniel Verkamp <dverkamp@chromium.org>
Tested-by: Dylan Reid <dgreid@chromium.org>
Commit-Queue: Dylan Reid <dgreid@chromium.org>
2020-04-14 16:40:41 +00:00
|
|
|
input: None,
|
2019-04-17 19:51:25 +00:00
|
|
|
num: 1,
|
|
|
|
console: true,
|
2019-08-01 21:40:03 +00:00
|
|
|
stdin: true,
|
2019-04-17 19:51:25 +00:00
|
|
|
},
|
|
|
|
SerialParameters {
|
|
|
|
type_: SerialType::Sink,
|
|
|
|
path: None,
|
Serial: add input path overriding stdin for --serial
Allowing the input to be specified for file-based serial ports allows
the user of pipes as input/output. That enables kgdb over serial.
TEST=
Build a kernel with support for gdb
```
make x86_64_defconfig
make kvmconfig
./scripts/config --enable GDB_SCRIPTS
./scripts/config --enable KGDB
./scripts/config --enable KGDB_SERIAL_CONSOLE
./scripts/config --enable KGDB_LOW_LEVEL_TRAP
./scripts/config --enable KGDB_KDB
./scripts/config --enable KDB_KEYBOARD
./scripts/config --enable GDB_SCRIPTS
./scripts/config --set-val KDB_CONTINUE_CATASTROPHIC 0
make -j33
```
Setup devices for PTYs
To make sure crosvm doesn't create an ordinary file if socat is started
after it, create these named pipes first:
```
mkfifo ~/console_{in,out} ~/kgdb_{in,out}
```
Set up two PTYs: ~/kgdb for the debugger, and ~/serial for the console.
PTY ~/kgdb connects to ~/kgdb{in,out}, and ~/serial connects to
~/console{in,out}
```
socat -d -d -d \
'PIPE:$HOME/console_out,rdonly=1,nonblock=1,ignoreeof=1!!PIPE:$HOME/console_in,wronly=1' \
PTY,link=$HOME/serial,ctty,raw,echo=0
socat -d -d -d \
'PIPE:$HOME/kgdb_out,rdonly=1,nonblock=1,ignoreeof=1!!PIPE:$HOME/kgdb_in,wronly=1' \
PTY,link=$HOME/kgdb,ctty,raw,echo=0
```
Start crosvm with serial ports pointed at ~/console{in,out} and ~/kgdb{in,out}.
```
cargo run run -p 'init=/bin/sh panic=0 kgdboc=ttyS1,115200 kgdbwait kgdbcon' \
--serial type=file,path=$HOME/console_out,num=1,console=true,stdin=false,input=$HOME/console_in \
--serial type=file,path=$HOME/kgdb_out,input=$HOME/kgdb_in,num=2,console=false,stdin=false \
-r ~/rootfs.img \
~/src/linux/arch/x86/boot/bzImage
```
Start GDB
```
gdb vmlinux -ex "target remote /home/dgreid/kgdb"
```
To break into gdb, open up the serial console, mount /proc and send a SysRq
```
minicom -D ~/serial
mount -t proc none /proc
echo g > /proc/sysrq-trigger
```
Change-Id: I18a9c1087d38301df49de08eeae2f8559b03463a
Reviewed-on: https://chromium-review.googlesource.com/c/chromiumos/platform/crosvm/+/2151856
Reviewed-by: Daniel Verkamp <dverkamp@chromium.org>
Tested-by: Dylan Reid <dgreid@chromium.org>
Commit-Queue: Dylan Reid <dgreid@chromium.org>
2020-04-14 16:40:41 +00:00
|
|
|
input: None,
|
2019-04-17 19:51:25 +00:00
|
|
|
num: 2,
|
|
|
|
console: false,
|
2019-08-01 21:40:03 +00:00
|
|
|
stdin: false,
|
2019-04-17 19:51:25 +00:00
|
|
|
},
|
|
|
|
SerialParameters {
|
|
|
|
type_: SerialType::Sink,
|
|
|
|
path: None,
|
Serial: add input path overriding stdin for --serial
Allowing the input to be specified for file-based serial ports allows
the user of pipes as input/output. That enables kgdb over serial.
TEST=
Build a kernel with support for gdb
```
make x86_64_defconfig
make kvmconfig
./scripts/config --enable GDB_SCRIPTS
./scripts/config --enable KGDB
./scripts/config --enable KGDB_SERIAL_CONSOLE
./scripts/config --enable KGDB_LOW_LEVEL_TRAP
./scripts/config --enable KGDB_KDB
./scripts/config --enable KDB_KEYBOARD
./scripts/config --enable GDB_SCRIPTS
./scripts/config --set-val KDB_CONTINUE_CATASTROPHIC 0
make -j33
```
Setup devices for PTYs
To make sure crosvm doesn't create an ordinary file if socat is started
after it, create these named pipes first:
```
mkfifo ~/console_{in,out} ~/kgdb_{in,out}
```
Set up two PTYs: ~/kgdb for the debugger, and ~/serial for the console.
PTY ~/kgdb connects to ~/kgdb{in,out}, and ~/serial connects to
~/console{in,out}
```
socat -d -d -d \
'PIPE:$HOME/console_out,rdonly=1,nonblock=1,ignoreeof=1!!PIPE:$HOME/console_in,wronly=1' \
PTY,link=$HOME/serial,ctty,raw,echo=0
socat -d -d -d \
'PIPE:$HOME/kgdb_out,rdonly=1,nonblock=1,ignoreeof=1!!PIPE:$HOME/kgdb_in,wronly=1' \
PTY,link=$HOME/kgdb,ctty,raw,echo=0
```
Start crosvm with serial ports pointed at ~/console{in,out} and ~/kgdb{in,out}.
```
cargo run run -p 'init=/bin/sh panic=0 kgdboc=ttyS1,115200 kgdbwait kgdbcon' \
--serial type=file,path=$HOME/console_out,num=1,console=true,stdin=false,input=$HOME/console_in \
--serial type=file,path=$HOME/kgdb_out,input=$HOME/kgdb_in,num=2,console=false,stdin=false \
-r ~/rootfs.img \
~/src/linux/arch/x86/boot/bzImage
```
Start GDB
```
gdb vmlinux -ex "target remote /home/dgreid/kgdb"
```
To break into gdb, open up the serial console, mount /proc and send a SysRq
```
minicom -D ~/serial
mount -t proc none /proc
echo g > /proc/sysrq-trigger
```
Change-Id: I18a9c1087d38301df49de08eeae2f8559b03463a
Reviewed-on: https://chromium-review.googlesource.com/c/chromiumos/platform/crosvm/+/2151856
Reviewed-by: Daniel Verkamp <dverkamp@chromium.org>
Tested-by: Dylan Reid <dgreid@chromium.org>
Commit-Queue: Dylan Reid <dgreid@chromium.org>
2020-04-14 16:40:41 +00:00
|
|
|
input: None,
|
2019-04-17 19:51:25 +00:00
|
|
|
num: 3,
|
|
|
|
console: false,
|
2019-08-01 21:40:03 +00:00
|
|
|
stdin: false,
|
2019-04-17 19:51:25 +00:00
|
|
|
},
|
|
|
|
SerialParameters {
|
|
|
|
type_: SerialType::Sink,
|
|
|
|
path: None,
|
Serial: add input path overriding stdin for --serial
Allowing the input to be specified for file-based serial ports allows
the user of pipes as input/output. That enables kgdb over serial.
TEST=
Build a kernel with support for gdb
```
make x86_64_defconfig
make kvmconfig
./scripts/config --enable GDB_SCRIPTS
./scripts/config --enable KGDB
./scripts/config --enable KGDB_SERIAL_CONSOLE
./scripts/config --enable KGDB_LOW_LEVEL_TRAP
./scripts/config --enable KGDB_KDB
./scripts/config --enable KDB_KEYBOARD
./scripts/config --enable GDB_SCRIPTS
./scripts/config --set-val KDB_CONTINUE_CATASTROPHIC 0
make -j33
```
Setup devices for PTYs
To make sure crosvm doesn't create an ordinary file if socat is started
after it, create these named pipes first:
```
mkfifo ~/console_{in,out} ~/kgdb_{in,out}
```
Set up two PTYs: ~/kgdb for the debugger, and ~/serial for the console.
PTY ~/kgdb connects to ~/kgdb{in,out}, and ~/serial connects to
~/console{in,out}
```
socat -d -d -d \
'PIPE:$HOME/console_out,rdonly=1,nonblock=1,ignoreeof=1!!PIPE:$HOME/console_in,wronly=1' \
PTY,link=$HOME/serial,ctty,raw,echo=0
socat -d -d -d \
'PIPE:$HOME/kgdb_out,rdonly=1,nonblock=1,ignoreeof=1!!PIPE:$HOME/kgdb_in,wronly=1' \
PTY,link=$HOME/kgdb,ctty,raw,echo=0
```
Start crosvm with serial ports pointed at ~/console{in,out} and ~/kgdb{in,out}.
```
cargo run run -p 'init=/bin/sh panic=0 kgdboc=ttyS1,115200 kgdbwait kgdbcon' \
--serial type=file,path=$HOME/console_out,num=1,console=true,stdin=false,input=$HOME/console_in \
--serial type=file,path=$HOME/kgdb_out,input=$HOME/kgdb_in,num=2,console=false,stdin=false \
-r ~/rootfs.img \
~/src/linux/arch/x86/boot/bzImage
```
Start GDB
```
gdb vmlinux -ex "target remote /home/dgreid/kgdb"
```
To break into gdb, open up the serial console, mount /proc and send a SysRq
```
minicom -D ~/serial
mount -t proc none /proc
echo g > /proc/sysrq-trigger
```
Change-Id: I18a9c1087d38301df49de08eeae2f8559b03463a
Reviewed-on: https://chromium-review.googlesource.com/c/chromiumos/platform/crosvm/+/2151856
Reviewed-by: Daniel Verkamp <dverkamp@chromium.org>
Tested-by: Dylan Reid <dgreid@chromium.org>
Commit-Queue: Dylan Reid <dgreid@chromium.org>
2020-04-14 16:40:41 +00:00
|
|
|
input: None,
|
2019-04-17 19:51:25 +00:00
|
|
|
num: 4,
|
|
|
|
console: false,
|
2019-08-01 21:40:03 +00:00
|
|
|
stdin: false,
|
2019-04-17 19:51:25 +00:00
|
|
|
},
|
|
|
|
];
|
|
|
|
|
|
|
|
/// 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(),
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2017-05-02 01:00:12 +00:00
|
|
|
/// 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
|
2019-12-13 02:58:50 +00:00
|
|
|
/// guest, use `queue_input_bytes` directly, or give a Read trait object which will be used queue
|
|
|
|
/// bytes when `used_command` is called.
|
2017-05-02 01:00:12 +00:00
|
|
|
pub struct Serial {
|
2019-12-13 02:58:50 +00:00
|
|
|
// Serial port registers
|
|
|
|
interrupt_enable: Arc<AtomicU8>,
|
2017-05-02 01:00:12 +00:00
|
|
|
interrupt_identification: u8,
|
|
|
|
interrupt_evt: EventFd,
|
|
|
|
line_control: u8,
|
|
|
|
line_status: u8,
|
|
|
|
modem_control: u8,
|
|
|
|
modem_status: u8,
|
|
|
|
scratch: u8,
|
|
|
|
baud_divisor: u16,
|
2019-12-13 02:58:50 +00:00
|
|
|
|
|
|
|
// Host input/output
|
2017-05-02 01:00:12 +00:00
|
|
|
in_buffer: VecDeque<u8>,
|
2019-12-13 02:58:50 +00:00
|
|
|
in_channel: Option<Receiver<u8>>,
|
|
|
|
input: Option<Box<dyn io::Read + Send>>,
|
2019-03-09 00:56:14 +00:00
|
|
|
out: Option<Box<dyn io::Write + Send>>,
|
2017-05-02 01:00:12 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
impl Serial {
|
2019-12-13 02:58:50 +00:00
|
|
|
fn new(
|
|
|
|
interrupt_evt: EventFd,
|
|
|
|
input: Option<Box<dyn io::Read + Send>>,
|
|
|
|
out: Option<Box<dyn io::Write + Send>>,
|
|
|
|
) -> Serial {
|
2017-05-02 01:00:12 +00:00
|
|
|
Serial {
|
2019-12-13 02:58:50 +00:00
|
|
|
interrupt_enable: Default::default(),
|
2017-05-02 01:00:12 +00:00
|
|
|
interrupt_identification: DEFAULT_INTERRUPT_IDENTIFICATION,
|
2018-10-03 17:22:32 +00:00
|
|
|
interrupt_evt,
|
2017-05-02 01:00:12 +00:00
|
|
|
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,
|
2019-12-13 02:58:50 +00:00
|
|
|
in_buffer: Default::default(),
|
|
|
|
in_channel: None,
|
|
|
|
input,
|
2018-10-03 17:22:32 +00:00
|
|
|
out,
|
2017-05-02 01:00:12 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-12-13 02:58:50 +00:00
|
|
|
/// Constructs a Serial port ready for input and output.
|
|
|
|
///
|
|
|
|
/// The stream `input` should not block, instead returning 0 bytes if are no bytes available.
|
|
|
|
pub fn new_in_out(
|
|
|
|
interrupt_evt: EventFd,
|
|
|
|
input: Box<dyn io::Read + Send>,
|
|
|
|
out: Box<dyn io::Write + Send>,
|
|
|
|
) -> Serial {
|
|
|
|
Self::new(interrupt_evt, Some(input), Some(out))
|
|
|
|
}
|
|
|
|
|
|
|
|
/// Constructs a Serial port ready for output but not input.
|
2019-03-09 00:56:14 +00:00
|
|
|
pub fn new_out(interrupt_evt: EventFd, out: Box<dyn io::Write + Send>) -> Serial {
|
2019-12-13 02:58:50 +00:00
|
|
|
Self::new(interrupt_evt, None, Some(out))
|
2017-05-02 01:00:12 +00:00
|
|
|
}
|
|
|
|
|
2019-12-13 02:58:50 +00:00
|
|
|
/// Constructs a Serial port with no connected input or output.
|
2017-05-02 01:00:12 +00:00
|
|
|
pub fn new_sink(interrupt_evt: EventFd) -> Serial {
|
2019-12-13 02:58:50 +00:00
|
|
|
Self::new(interrupt_evt, None, None)
|
2017-05-02 01:00:12 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
/// Queues raw bytes for the guest to read and signals the interrupt if the line status would
|
2019-12-13 02:58:50 +00:00
|
|
|
/// change. These bytes will be read by the guest before any bytes from the input stream that
|
|
|
|
/// have not already been queued.
|
2017-05-02 01:00:12 +00:00
|
|
|
pub fn queue_input_bytes(&mut self, c: &[u8]) -> Result<()> {
|
2019-12-13 02:58:50 +00:00
|
|
|
if !c.is_empty() && !self.is_loop() {
|
2017-05-02 01:00:12 +00:00
|
|
|
self.in_buffer.extend(c);
|
2019-12-13 02:58:50 +00:00
|
|
|
self.set_data_bit();
|
|
|
|
self.trigger_recv_interrupt()?;
|
2017-05-02 01:00:12 +00:00
|
|
|
}
|
2019-12-13 02:58:50 +00:00
|
|
|
|
2017-05-02 01:00:12 +00:00
|
|
|
Ok(())
|
|
|
|
}
|
|
|
|
|
2019-12-13 02:58:50 +00:00
|
|
|
fn spawn_input_thread(&mut self) {
|
|
|
|
let mut rx = match self.input.take() {
|
|
|
|
Some(input) => input,
|
|
|
|
None => return,
|
|
|
|
};
|
|
|
|
|
|
|
|
let (send_channel, recv_channel) = channel();
|
|
|
|
|
|
|
|
// The interrupt enable and interrupt event are used to trigger the guest serial driver to
|
|
|
|
// read the serial device, which will give the VCPU threads time to queue input bytes from
|
|
|
|
// the input thread's buffer, changing the serial device state accordingly.
|
|
|
|
let interrupt_enable = self.interrupt_enable.clone();
|
|
|
|
let interrupt_evt = match self.interrupt_evt.try_clone() {
|
|
|
|
Ok(e) => e,
|
|
|
|
Err(e) => {
|
|
|
|
error!("failed to clone interrupt eventfd: {}", e);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
|
|
|
// The input thread runs in detached mode and will exit when channel is disconnected because
|
|
|
|
// the serial device has been dropped. Initial versions of this kept a `JoinHandle` and had
|
|
|
|
// the drop implementation of serial join on this thread, but the input thread can block
|
|
|
|
// indefinitely depending on the `Box<io::Read>` implementation.
|
|
|
|
let res = thread::Builder::new()
|
|
|
|
.name(format!("{} input thread", self.debug_label()))
|
|
|
|
.spawn(move || {
|
|
|
|
let mut rx_buf = [0u8; 1];
|
|
|
|
loop {
|
|
|
|
match rx.read(&mut rx_buf) {
|
|
|
|
Ok(0) => break, // Assume the stream of input has ended.
|
|
|
|
Ok(_) => {
|
|
|
|
if send_channel.send(rx_buf[0]).is_err() {
|
|
|
|
// The receiver has disconnected.
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
if (interrupt_enable.load(Ordering::SeqCst) & IER_RECV_BIT) != 0 {
|
|
|
|
interrupt_evt.write(1).unwrap();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
Err(e) => {
|
|
|
|
// Being interrupted is not an error, but everything else is.
|
|
|
|
if e.kind() != io::ErrorKind::Interrupted {
|
|
|
|
error!(
|
|
|
|
"failed to read for bytes to queue into serial device: {}",
|
|
|
|
e
|
|
|
|
);
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
});
|
|
|
|
if let Err(e) = res {
|
|
|
|
error!("failed to spawn input thread: {}", e);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
self.in_channel = Some(recv_channel);
|
|
|
|
}
|
|
|
|
|
|
|
|
fn handle_input_thread(&mut self) {
|
|
|
|
if self.input.is_some() {
|
|
|
|
self.spawn_input_thread();
|
|
|
|
}
|
|
|
|
|
|
|
|
loop {
|
|
|
|
let in_channel = match self.in_channel.as_ref() {
|
|
|
|
Some(v) => v,
|
|
|
|
None => return,
|
|
|
|
};
|
|
|
|
match in_channel.try_recv() {
|
|
|
|
Ok(byte) => {
|
|
|
|
self.queue_input_bytes(&[byte]).unwrap();
|
|
|
|
}
|
|
|
|
Err(TryRecvError::Empty) => break,
|
|
|
|
Err(TryRecvError::Disconnected) => {
|
|
|
|
self.in_channel = None;
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-08-13 18:20:14 +00:00
|
|
|
/// Gets the interrupt eventfd used to interrupt the driver when it needs to respond to this
|
|
|
|
/// device.
|
|
|
|
pub fn interrupt_eventfd(&self) -> &EventFd {
|
|
|
|
&self.interrupt_evt
|
|
|
|
}
|
|
|
|
|
2017-05-02 01:00:12 +00:00
|
|
|
fn is_dlab_set(&self) -> bool {
|
|
|
|
(self.line_control & 0x80) != 0
|
|
|
|
}
|
|
|
|
|
|
|
|
fn is_recv_intr_enabled(&self) -> bool {
|
2019-12-13 02:58:50 +00:00
|
|
|
(self.interrupt_enable.load(Ordering::SeqCst) & IER_RECV_BIT) != 0
|
2017-05-02 01:00:12 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
fn is_thr_intr_enabled(&self) -> bool {
|
2019-12-13 02:58:50 +00:00
|
|
|
(self.interrupt_enable.load(Ordering::SeqCst) & IER_THR_BIT) != 0
|
2017-05-02 01:00:12 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
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;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-12-13 02:58:50 +00:00
|
|
|
fn trigger_thr_empty(&mut self) -> Result<()> {
|
2017-05-02 01:00:12 +00:00
|
|
|
if self.is_thr_intr_enabled() {
|
|
|
|
self.add_intr_bit(IIR_THR_BIT);
|
|
|
|
self.trigger_interrupt()?
|
|
|
|
}
|
|
|
|
Ok(())
|
|
|
|
}
|
|
|
|
|
2019-12-13 02:58:50 +00:00
|
|
|
fn trigger_recv_interrupt(&mut self) -> Result<()> {
|
2017-05-02 01:00:12 +00:00
|
|
|
if self.is_recv_intr_enabled() {
|
2019-12-13 02:58:50 +00:00
|
|
|
// Only bother triggering the interrupt if the identification bit wasn't set or
|
|
|
|
// acknowledged.
|
|
|
|
if self.interrupt_identification & IIR_RECV_BIT == 0 {
|
|
|
|
self.add_intr_bit(IIR_RECV_BIT);
|
|
|
|
self.trigger_interrupt()?
|
|
|
|
}
|
2017-05-02 01:00:12 +00:00
|
|
|
}
|
|
|
|
Ok(())
|
|
|
|
}
|
|
|
|
|
|
|
|
fn trigger_interrupt(&mut self) -> Result<()> {
|
|
|
|
self.interrupt_evt.write(1)
|
|
|
|
}
|
|
|
|
|
2019-12-13 02:58:50 +00:00
|
|
|
fn set_data_bit(&mut self) {
|
|
|
|
self.line_status |= LSR_DATA_BIT;
|
|
|
|
}
|
|
|
|
|
2017-05-02 01:00:12 +00:00
|
|
|
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);
|
2019-12-13 02:58:50 +00:00
|
|
|
self.set_data_bit();
|
|
|
|
self.trigger_recv_interrupt()?;
|
2017-05-02 01:00:12 +00:00
|
|
|
}
|
|
|
|
} else {
|
|
|
|
if let Some(out) = self.out.as_mut() {
|
|
|
|
out.write_all(&[v])?;
|
|
|
|
out.flush()?;
|
|
|
|
}
|
2019-12-13 02:58:50 +00:00
|
|
|
self.trigger_thr_empty()?;
|
2017-05-02 01:00:12 +00:00
|
|
|
}
|
|
|
|
}
|
2019-12-13 02:58:50 +00:00
|
|
|
IER => self
|
|
|
|
.interrupt_enable
|
|
|
|
.store(v & IER_FIFO_BITS, Ordering::SeqCst),
|
2017-05-02 01:00:12 +00:00
|
|
|
LCR => self.line_control = v,
|
|
|
|
MCR => self.modem_control = v,
|
|
|
|
SCR => self.scratch = v,
|
2017-05-31 01:45:05 +00:00
|
|
|
_ => {}
|
2017-05-02 01:00:12 +00:00
|
|
|
}
|
|
|
|
Ok(())
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
impl BusDevice for Serial {
|
2019-01-24 03:04:43 +00:00
|
|
|
fn debug_label(&self) -> String {
|
|
|
|
"serial".to_owned()
|
|
|
|
}
|
|
|
|
|
2017-05-02 01:00:12 +00:00
|
|
|
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]) {
|
2019-02-13 01:51:26 +00:00
|
|
|
error!("serial failed write: {}", e);
|
2017-05-02 01:00:12 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
fn read(&mut self, offset: u64, data: &mut [u8]) {
|
|
|
|
if data.len() != 1 {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2019-12-13 02:58:50 +00:00
|
|
|
self.handle_input_thread();
|
|
|
|
|
2017-05-02 01:00:12 +00:00
|
|
|
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()
|
|
|
|
}
|
2019-12-13 02:58:50 +00:00
|
|
|
IER => self.interrupt_enable.load(Ordering::SeqCst),
|
2017-05-02 01:00:12 +00:00
|
|
|
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,
|
2019-05-20 20:37:54 +00:00
|
|
|
MSR => {
|
|
|
|
if self.is_loop() {
|
|
|
|
let mut msr =
|
|
|
|
self.modem_status & !(MSR_DSR_BIT | MSR_CTS_BIT | MSR_RI_BIT | MSR_DCD_BIT);
|
|
|
|
if self.modem_control & MCR_DTR_BIT != 0 {
|
|
|
|
msr |= MSR_DSR_BIT;
|
|
|
|
}
|
|
|
|
if self.modem_control & MCR_RTS_BIT != 0 {
|
|
|
|
msr |= MSR_CTS_BIT;
|
|
|
|
}
|
|
|
|
if self.modem_control & MCR_OUT1_BIT != 0 {
|
|
|
|
msr |= MSR_RI_BIT;
|
|
|
|
}
|
|
|
|
if self.modem_control & MCR_OUT2_BIT != 0 {
|
|
|
|
msr |= MSR_DCD_BIT;
|
|
|
|
}
|
|
|
|
msr
|
|
|
|
} else {
|
|
|
|
self.modem_status
|
|
|
|
}
|
|
|
|
}
|
2017-05-02 01:00:12 +00:00
|
|
|
SCR => self.scratch,
|
2017-05-31 01:45:05 +00:00
|
|
|
_ => 0,
|
2017-05-02 01:00:12 +00:00
|
|
|
};
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
#[cfg(test)]
|
|
|
|
mod tests {
|
|
|
|
use super::*;
|
|
|
|
use std::io;
|
2018-12-04 07:37:46 +00:00
|
|
|
use std::sync::Arc;
|
|
|
|
|
|
|
|
use sync::Mutex;
|
2017-05-02 01:00:12 +00:00
|
|
|
|
|
|
|
#[derive(Clone)]
|
|
|
|
struct SharedBuffer {
|
|
|
|
buf: Arc<Mutex<Vec<u8>>>,
|
|
|
|
}
|
|
|
|
|
|
|
|
impl SharedBuffer {
|
|
|
|
fn new() -> SharedBuffer {
|
2018-10-03 17:22:32 +00:00
|
|
|
SharedBuffer {
|
|
|
|
buf: Arc::new(Mutex::new(Vec::new())),
|
|
|
|
}
|
2017-05-02 01:00:12 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
impl io::Write for SharedBuffer {
|
|
|
|
fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
|
2018-12-04 07:37:46 +00:00
|
|
|
self.buf.lock().write(buf)
|
2017-05-02 01:00:12 +00:00
|
|
|
}
|
|
|
|
fn flush(&mut self) -> io::Result<()> {
|
2018-12-04 07:37:46 +00:00
|
|
|
self.buf.lock().flush()
|
2017-05-02 01:00:12 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
#[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]);
|
2018-10-03 17:22:32 +00:00
|
|
|
assert_eq!(
|
2018-12-04 07:37:46 +00:00
|
|
|
serial_out.buf.lock().as_slice(),
|
2018-10-03 17:22:32 +00:00
|
|
|
&['a' as u8, 'b' as u8, 'c' as u8]
|
|
|
|
);
|
2017-05-02 01:00:12 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
fn serial_input() {
|
|
|
|
let intr_evt = EventFd::new().unwrap();
|
|
|
|
let serial_out = SharedBuffer::new();
|
|
|
|
|
2018-10-03 17:22:32 +00:00
|
|
|
let mut serial =
|
|
|
|
Serial::new_out(intr_evt.try_clone().unwrap(), Box::new(serial_out.clone()));
|
2017-05-02 01:00:12 +00:00
|
|
|
|
|
|
|
serial.write(IER as u64, &[IER_RECV_BIT]);
|
2018-10-03 17:22:32 +00:00
|
|
|
serial
|
|
|
|
.queue_input_bytes(&['a' as u8, 'b' as u8, 'c' as u8])
|
|
|
|
.unwrap();
|
2017-05-02 01:00:12 +00:00
|
|
|
|
|
|
|
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);
|
|
|
|
}
|
|
|
|
}
|