vmm, virtio-console: Move input reading into virtio-console thread

Move the processing of the input from stdin, PTY or file from the VMM
thread to the existing virtio-console thread. The handling of the resize
of a virtio-console has not changed but the name of the struct used to
support that has been renamed to reflect its usage.

Fixes: #3060

Signed-off-by: Rob Bradford <robert.bradford@intel.com>
This commit is contained in:
Rob Bradford 2021-09-02 15:56:37 +00:00
parent 0d01eac1d4
commit c2144b5690
4 changed files with 179 additions and 150 deletions

View File

@ -14,9 +14,9 @@ use libc::EFD_NONBLOCK;
use seccompiler::{apply_filter, SeccompAction};
use std::cmp;
use std::collections::VecDeque;
use std::fs::File;
use std::io;
use std::io::Write;
use std::ops::DerefMut;
use std::io::{Read, Write};
use std::os::unix::io::AsRawFd;
use std::result;
use std::sync::atomic::{AtomicBool, AtomicU64, Ordering};
@ -40,6 +40,8 @@ const OUTPUT_QUEUE_EVENT: u16 = EPOLL_HELPER_EVENT_LAST + 2;
const INPUT_EVENT: u16 = EPOLL_HELPER_EVENT_LAST + 3;
// Console configuration change event is triggered.
const CONFIG_EVENT: u16 = EPOLL_HELPER_EVENT_LAST + 4;
// File written to (input ready)
const FILE_EVENT: u16 = EPOLL_HELPER_EVENT_LAST + 5;
//Console size feature bit
const VIRTIO_CONSOLE_F_SIZE: u64 = 0;
@ -61,7 +63,7 @@ struct ConsoleEpollHandler {
mem: GuestMemoryAtomic<GuestMemoryMmap>,
interrupt_cb: Arc<dyn VirtioInterrupt>,
in_buffer: Arc<Mutex<VecDeque<u8>>>,
out: Arc<Mutex<Box<dyn io::Write + Send + Sync + 'static>>>,
endpoint: Endpoint,
input_queue_evt: EventFd,
output_queue_evt: EventFd,
input_evt: EventFd,
@ -70,6 +72,42 @@ struct ConsoleEpollHandler {
pause_evt: EventFd,
}
pub enum Endpoint {
File(File),
FilePair(File, File),
Null,
}
impl Endpoint {
fn out_file(&self) -> Option<&File> {
match self {
Self::File(f) => Some(f),
Self::FilePair(f, _) => Some(f),
Self::Null => None,
}
}
fn in_file(&self) -> Option<&File> {
match self {
Self::File(_) => None,
Self::FilePair(_, f) => Some(f),
Self::Null => None,
}
}
}
impl Clone for Endpoint {
fn clone(&self) -> Self {
match self {
Self::File(f) => Self::File(f.try_clone().unwrap()),
Self::FilePair(f_out, f_in) => {
Self::FilePair(f_out.try_clone().unwrap(), f_in.try_clone().unwrap())
}
Self::Null => Self::Null,
}
}
}
impl ConsoleEpollHandler {
/*
* Each port of virtio console device has one receive
@ -127,14 +165,10 @@ impl ConsoleEpollHandler {
let mem = self.mem.memory();
for avail_desc in trans_queue.iter(&mem) {
let len;
let mut out = self.out.lock().unwrap();
let _ = mem.write_to(
avail_desc.addr,
&mut out.deref_mut(),
avail_desc.len as usize,
);
let _ = out.flush();
if let Some(ref mut out) = self.endpoint.out_file() {
let _ = mem.write_to(avail_desc.addr, out, avail_desc.len as usize);
let _ = out.flush();
}
len = avail_desc.len;
used_desc_heads[used_count] = (avail_desc.index, len);
used_count += 1;
@ -165,6 +199,9 @@ impl ConsoleEpollHandler {
helper.add_event(self.output_queue_evt.as_raw_fd(), OUTPUT_QUEUE_EVENT)?;
helper.add_event(self.input_evt.as_raw_fd(), INPUT_EVENT)?;
helper.add_event(self.config_evt.as_raw_fd(), CONFIG_EVENT)?;
if let Some(in_file) = self.endpoint.in_file() {
helper.add_event(in_file.as_raw_fd(), FILE_EVENT)?;
}
helper.run(paused, paused_sync, self)?;
Ok(())
@ -217,6 +254,22 @@ impl EpollHelperHandler for ConsoleEpollHandler {
return true;
}
}
FILE_EVENT => {
let mut input = [0u8; 64];
if let Some(ref mut in_file) = self.endpoint.in_file() {
if let Ok(count) = in_file.read(&mut input) {
let mut in_buffer = self.in_buffer.lock().unwrap();
in_buffer.extend(&input[..count]);
}
if self.process_input_queue() {
if let Err(e) = self.signal_used_queue() {
error!("Failed to signal used queue: {:?}", e);
return true;
}
}
}
}
_ => {
error!("Unknown event for virtio-console");
return true;
@ -226,22 +279,14 @@ impl EpollHelperHandler for ConsoleEpollHandler {
}
}
/// Input device.
pub struct ConsoleInput {
input_evt: EventFd,
/// Resize handler
pub struct ConsoleResizer {
config_evt: EventFd,
in_buffer: Arc<Mutex<VecDeque<u8>>>,
config: Arc<Mutex<VirtioConsoleConfig>>,
acked_features: AtomicU64,
}
impl ConsoleInput {
pub fn queue_input_bytes(&self, input: &[u8]) {
let mut in_buffer = self.in_buffer.lock().unwrap();
in_buffer.extend(input);
let _ = self.input_evt.write(1);
}
impl ConsoleResizer {
pub fn update_console_size(&self, cols: u16, rows: u16) {
if self
.acked_features
@ -276,9 +321,10 @@ pub struct Console {
common: VirtioCommon,
id: String,
config: Arc<Mutex<VirtioConsoleConfig>>,
input: Arc<ConsoleInput>,
out: Arc<Mutex<Box<dyn io::Write + Send + Sync + 'static>>>,
resizer: Arc<ConsoleResizer>,
endpoint: Endpoint,
seccomp_action: SeccompAction,
in_buffer: Arc<Mutex<VecDeque<u8>>>,
}
#[derive(Versionize)]
@ -295,25 +341,22 @@ impl Console {
/// Create a new virtio console device that gets random data from /dev/urandom.
pub fn new(
id: String,
out: Box<dyn io::Write + Send + Sync + 'static>,
endpoint: Endpoint,
cols: u16,
rows: u16,
iommu: bool,
seccomp_action: SeccompAction,
) -> io::Result<(Console, Arc<ConsoleInput>)> {
) -> io::Result<(Console, Arc<ConsoleResizer>)> {
let mut avail_features = 1u64 << VIRTIO_F_VERSION_1 | 1u64 << VIRTIO_CONSOLE_F_SIZE;
if iommu {
avail_features |= 1u64 << VIRTIO_F_IOMMU_PLATFORM;
}
let input_evt = EventFd::new(EFD_NONBLOCK).unwrap();
let config_evt = EventFd::new(EFD_NONBLOCK).unwrap();
let console_config = Arc::new(Mutex::new(VirtioConsoleConfig::new(cols, rows)));
let console_input = Arc::new(ConsoleInput {
input_evt,
let resizer = Arc::new(ConsoleResizer {
config_evt,
in_buffer: Arc::new(Mutex::new(VecDeque::new())),
config: console_config.clone(),
acked_features: AtomicU64::new(0),
});
@ -330,11 +373,12 @@ impl Console {
},
id,
config: console_config,
input: console_input.clone(),
out: Arc::new(Mutex::new(out)),
resizer: resizer.clone(),
endpoint,
seccomp_action,
in_buffer: Arc::new(Mutex::new(VecDeque::new())),
},
console_input,
resizer,
))
}
@ -343,7 +387,7 @@ impl Console {
avail_features: self.common.avail_features,
acked_features: self.common.acked_features,
config: *(self.config.lock().unwrap()),
in_buffer: self.input.in_buffer.lock().unwrap().clone().into(),
in_buffer: self.in_buffer.lock().unwrap().clone().into(),
}
}
@ -351,7 +395,7 @@ impl Console {
self.common.avail_features = state.avail_features;
self.common.acked_features = state.acked_features;
*(self.config.lock().unwrap()) = state.config;
*(self.input.in_buffer.lock().unwrap()) = state.in_buffer.clone().into();
*(self.in_buffer.lock().unwrap()) = state.in_buffer.clone().into();
}
}
@ -393,7 +437,7 @@ impl VirtioDevice for Console {
mut queue_evts: Vec<EventFd>,
) -> ActivateResult {
self.common.activate(&queues, &queue_evts, &interrupt_cb)?;
self.input
self.resizer
.acked_features
.store(self.common.acked_features, Ordering::Relaxed);
@ -404,17 +448,18 @@ impl VirtioDevice for Console {
}
let (kill_evt, pause_evt) = self.common.dup_eventfds();
let input_evt = EventFd::new(EFD_NONBLOCK).unwrap();
let mut handler = ConsoleEpollHandler {
queues,
mem,
interrupt_cb,
in_buffer: self.input.in_buffer.clone(),
out: self.out.clone(),
in_buffer: self.in_buffer.clone(),
endpoint: self.endpoint.clone(),
input_queue_evt: queue_evts.remove(0),
output_queue_evt: queue_evts.remove(0),
input_evt: self.input.input_evt.try_clone().unwrap(),
config_evt: self.input.config_evt.try_clone().unwrap(),
input_evt,
config_evt: self.resizer.config_evt.try_clone().unwrap(),
kill_evt,
pause_evt,
};

View File

@ -73,7 +73,7 @@ use seccompiler::SeccompAction;
use std::collections::HashMap;
use std::convert::TryInto;
use std::fs::{read_link, File, OpenOptions};
use std::io::{self, sink, stdout, Seek, SeekFrom};
use std::io::{self, stdout, Seek, SeekFrom};
use std::mem::zeroed;
use std::num::Wrapping;
use std::os::unix::fs::OpenOptionsExt;
@ -88,7 +88,7 @@ use vfio_ioctls::{VfioContainer, VfioDevice};
use virtio_devices::transport::VirtioPciDevice;
use virtio_devices::transport::VirtioTransport;
use virtio_devices::vhost_user::VhostUserConfig;
use virtio_devices::{DmaRemapping, IommuMapping};
use virtio_devices::{DmaRemapping, Endpoint, IommuMapping};
use virtio_devices::{VirtioSharedMemory, VirtioSharedMemoryList};
use vm_allocator::SystemAllocator;
#[cfg(feature = "kvm")]
@ -535,7 +535,7 @@ pub struct Console {
serial: Option<Arc<Mutex<Serial>>>,
#[cfg(target_arch = "aarch64")]
serial: Option<Arc<Mutex<Pl011>>>,
virtio_console_input: Option<Arc<virtio_devices::ConsoleInput>>,
console_resizer: Option<Arc<virtio_devices::ConsoleResizer>>,
input: Option<ConsoleInput>,
}
@ -552,21 +552,9 @@ impl Console {
Ok(())
}
pub fn queue_input_bytes_console(&self, out: &[u8]) {
if self.virtio_console_input.is_some() {
self.virtio_console_input
.as_ref()
.unwrap()
.queue_input_bytes(out);
}
}
pub fn update_console_size(&self, cols: u16, rows: u16) {
if self.virtio_console_input.is_some() {
self.virtio_console_input
.as_ref()
.unwrap()
.update_console_size(cols, rows)
if let Some(resizer) = self.console_resizer.as_ref() {
resizer.update_console_size(cols, rows)
}
}
@ -1695,6 +1683,81 @@ impl DeviceManager {
self.modify_mode(f.as_raw_fd(), |t| t.c_lflag &= !(ICANON | ECHO | ISIG))
}
fn add_virtio_console_device(
&mut self,
virtio_devices: &mut Vec<(VirtioDeviceArc, bool, String)>,
console_pty: Option<PtyPair>,
) -> DeviceManagerResult<Option<Arc<virtio_devices::ConsoleResizer>>> {
let console_config = self.config.lock().unwrap().console.clone();
let endpoint = match console_config.mode {
ConsoleOutputMode::File => {
let file = File::create(console_config.file.as_ref().unwrap())
.map_err(DeviceManagerError::ConsoleOutputFileOpen)?;
Endpoint::File(file)
}
ConsoleOutputMode::Pty => {
if let Some(pty) = console_pty {
self.config.lock().unwrap().console.file = Some(pty.path.clone());
let file = pty.main.try_clone().unwrap();
self.console_pty = Some(Arc::new(Mutex::new(pty)));
Endpoint::FilePair(file.try_clone().unwrap(), file)
} else {
let (main, mut sub, path) =
create_pty(false).map_err(DeviceManagerError::ConsolePtyOpen)?;
self.set_raw_mode(&mut sub)
.map_err(DeviceManagerError::SetPtyRaw)?;
self.config.lock().unwrap().console.file = Some(path.clone());
let file = main.try_clone().unwrap();
self.console_pty = Some(Arc::new(Mutex::new(PtyPair { main, sub, path })));
Endpoint::FilePair(file.try_clone().unwrap(), file)
}
}
ConsoleOutputMode::Tty => {
// If an interactive TTY then we can accept input
if unsafe { libc::isatty(libc::STDIN_FILENO) == 1 } {
Endpoint::FilePair(
// Duplicating the file descriptors like this is needed as otherwise
// they will be closed on a reboot and the numbers reused
unsafe { File::from_raw_fd(libc::dup(libc::STDOUT_FILENO)) },
unsafe { File::from_raw_fd(libc::dup(libc::STDIN_FILENO)) },
)
} else {
Endpoint::File(unsafe { File::from_raw_fd(libc::dup(libc::STDOUT_FILENO)) })
}
}
ConsoleOutputMode::Null => Endpoint::Null,
ConsoleOutputMode::Off => return Ok(None),
};
let (col, row) = get_win_size();
let id = String::from(CONSOLE_DEVICE_NAME);
let (virtio_console_device, console_resizer) = virtio_devices::Console::new(
id.clone(),
endpoint,
col,
row,
self.force_iommu | console_config.iommu,
self.seccomp_action.clone(),
)
.map_err(DeviceManagerError::CreateVirtioConsole)?;
let virtio_console_device = Arc::new(Mutex::new(virtio_console_device));
virtio_devices.push((
Arc::clone(&virtio_console_device) as VirtioDeviceArc,
console_config.iommu,
id.clone(),
));
// Fill the device tree with a new node. In case of restore, we
// know there is nothing to do, so we can simply override the
// existing entry.
self.device_tree
.lock()
.unwrap()
.insert(id.clone(), device_node!(id, virtio_console_device));
Ok(Some(console_resizer))
}
fn add_console_device(
&mut self,
interrupt_manager: &Arc<dyn InterruptManager<GroupConfig = LegacyIrqGroupConfig>>,
@ -1702,6 +1765,7 @@ impl DeviceManager {
serial_pty: Option<PtyPair>,
console_pty: Option<PtyPair>,
) -> DeviceManagerResult<Arc<Console>> {
let console_config = self.config.lock().unwrap().console.clone();
let serial_config = self.config.lock().unwrap().serial.clone();
let serial_writer: Option<Box<dyn io::Write + Send>> = match serial_config.mode {
ConsoleOutputMode::File => Some(Box::new(
@ -1736,66 +1800,7 @@ impl DeviceManager {
None
};
// Create serial and virtio-console
let console_config = self.config.lock().unwrap().console.clone();
let console_writer: Option<Box<dyn io::Write + Send + Sync>> = match console_config.mode {
ConsoleOutputMode::File => Some(Box::new(
File::create(console_config.file.as_ref().unwrap())
.map_err(DeviceManagerError::ConsoleOutputFileOpen)?,
)),
ConsoleOutputMode::Pty => {
if let Some(pty) = console_pty {
self.config.lock().unwrap().console.file = Some(pty.path.clone());
let writer = pty.main.try_clone().unwrap();
self.console_pty = Some(Arc::new(Mutex::new(pty)));
Some(Box::new(writer))
} else {
let (main, mut sub, path) =
create_pty(false).map_err(DeviceManagerError::ConsolePtyOpen)?;
self.set_raw_mode(&mut sub)
.map_err(DeviceManagerError::SetPtyRaw)?;
self.config.lock().unwrap().console.file = Some(path.clone());
let writer = main.try_clone().unwrap();
self.console_pty = Some(Arc::new(Mutex::new(PtyPair { main, sub, path })));
Some(Box::new(writer))
}
}
ConsoleOutputMode::Tty => Some(Box::new(stdout())),
ConsoleOutputMode::Null => Some(Box::new(sink())),
ConsoleOutputMode::Off => None,
};
let (col, row) = get_win_size();
let virtio_console_input = if let Some(writer) = console_writer {
let id = String::from(CONSOLE_DEVICE_NAME);
let (virtio_console_device, virtio_console_input) = virtio_devices::Console::new(
id.clone(),
writer,
col,
row,
self.force_iommu | console_config.iommu,
self.seccomp_action.clone(),
)
.map_err(DeviceManagerError::CreateVirtioConsole)?;
let virtio_console_device = Arc::new(Mutex::new(virtio_console_device));
virtio_devices.push((
Arc::clone(&virtio_console_device) as VirtioDeviceArc,
console_config.iommu,
id.clone(),
));
// Fill the device tree with a new node. In case of restore, we
// know there is nothing to do, so we can simply override the
// existing entry.
self.device_tree
.lock()
.unwrap()
.insert(id.clone(), device_node!(id, virtio_console_device));
Some(virtio_console_input)
} else {
None
};
let console_resizer = self.add_virtio_console_device(virtio_devices, console_pty)?;
let input = if serial_config.mode.input_enabled() {
Some(ConsoleInput::Serial)
@ -1807,8 +1812,8 @@ impl DeviceManager {
Ok(Arc::new(Console {
serial,
virtio_console_input,
input,
console_resizer,
}))
}

View File

@ -150,8 +150,7 @@ pub enum EpollDispatch {
Stdin = 2,
Api = 3,
ActivateVirtioDevices = 4,
ConsolePty = 5,
SerialPty = 6,
SerialPty = 5,
Unknown,
}
@ -164,8 +163,7 @@ impl From<u64> for EpollDispatch {
2 => Stdin,
3 => Api,
4 => ActivateVirtioDevices,
5 => ConsolePty,
6 => SerialPty,
5 => SerialPty,
_ => Unknown,
}
}
@ -324,10 +322,6 @@ impl Vmm {
let reset_evt = EventFd::new(EFD_NONBLOCK).map_err(Error::EventFdCreate)?;
let activate_evt = EventFd::new(EFD_NONBLOCK).map_err(Error::EventFdCreate)?;
if unsafe { libc::isatty(libc::STDIN_FILENO as i32) } != 0 {
epoll.add_stdin().map_err(Error::Epoll)?;
}
epoll
.add_event(&exit_evt, EpollDispatch::Exit)
.map_err(Error::Epoll)?;
@ -400,11 +394,14 @@ impl Vmm {
.add_event(&serial_pty.main, EpollDispatch::SerialPty)
.map_err(VmError::EventfdError)?;
};
if let Some(console_pty) = vm.console_pty() {
self.epoll
.add_event(&console_pty.main, EpollDispatch::ConsolePty)
.map_err(VmError::EventfdError)?;
};
if matches!(
vm_config.lock().unwrap().serial.mode,
config::ConsoleOutputMode::Tty
) && unsafe { libc::isatty(libc::STDIN_FILENO as i32) } != 0
{
self.epoll.add_stdin().map_err(VmError::EventfdError)?;
}
self.vm = Some(vm);
}
}
@ -1302,7 +1299,7 @@ impl Vmm {
.map_err(Error::ActivateVirtioDevices)?;
}
}
event @ (EpollDispatch::ConsolePty | EpollDispatch::SerialPty) => {
event @ EpollDispatch::SerialPty => {
if let Some(ref vm) = self.vm {
vm.handle_pty(event).map_err(Error::Pty)?;
}

View File

@ -1928,15 +1928,6 @@ impl Vm {
.map_err(Error::Console)?;
}
};
} else if matches!(event, EpollDispatch::ConsolePty) {
if let Some(mut pty) = dm.console_pty() {
let mut out = [0u8; 64];
let count = pty.main.read(&mut out).map_err(Error::PtyConsole)?;
let console = dm.console();
if console.input_enabled() {
console.queue_input_bytes_console(&out[..count])
}
};
}
Ok(())
@ -1964,15 +1955,6 @@ impl Vm {
.console()
.queue_input_bytes_serial(&out[..count])
.map_err(Error::Console)?;
} else if matches!(
self.config.lock().unwrap().console.mode,
ConsoleOutputMode::Tty
) {
self.device_manager
.lock()
.unwrap()
.console()
.queue_input_bytes_console(&out[..count])
}
Ok(())