summaryrefslogtreecommitdiffstats
path: root/meta-egvirt/recipes-extended/vhost-device-console/vhost-device-console-0.1.0/src
diff options
context:
space:
mode:
Diffstat (limited to 'meta-egvirt/recipes-extended/vhost-device-console/vhost-device-console-0.1.0/src')
-rwxr-xr-x[-rw-r--r--]meta-egvirt/recipes-extended/vhost-device-console/vhost-device-console-0.1.0/src/backend.rs376
-rwxr-xr-x[-rw-r--r--]meta-egvirt/recipes-extended/vhost-device-console/vhost-device-console-0.1.0/src/console.rs88
-rwxr-xr-x[-rw-r--r--]meta-egvirt/recipes-extended/vhost-device-console/vhost-device-console-0.1.0/src/main.rs63
-rwxr-xr-x[-rw-r--r--]meta-egvirt/recipes-extended/vhost-device-console/vhost-device-console-0.1.0/src/vhu_console.rs1431
-rwxr-xr-xmeta-egvirt/recipes-extended/vhost-device-console/vhost-device-console-0.1.0/src/virtio_console.rs60
5 files changed, 1339 insertions, 679 deletions
diff --git a/meta-egvirt/recipes-extended/vhost-device-console/vhost-device-console-0.1.0/src/backend.rs b/meta-egvirt/recipes-extended/vhost-device-console/vhost-device-console-0.1.0/src/backend.rs
index 15b50a38..62d6745c 100644..100755
--- a/meta-egvirt/recipes-extended/vhost-device-console/vhost-device-console-0.1.0/src/backend.rs
+++ b/meta-egvirt/recipes-extended/vhost-device-console/vhost-device-console-0.1.0/src/backend.rs
@@ -1,22 +1,22 @@
// VIRTIO CONSOLE Emulation via vhost-user
//
-// Copyright 2023 VIRTUAL OPEN SYSTEMS SAS. All Rights Reserved.
+// Copyright 2023-2024 VIRTUAL OPEN SYSTEMS SAS. All Rights Reserved.
// Timos Ampelikiotis <t.ampelikiotis@virtualopensystems.com>
//
// SPDX-License-Identifier: Apache-2.0 or BSD-3-Clause
use log::{error, info, warn};
-use std::process::exit;
+use std::any::Any;
+use std::collections::HashMap;
+use std::path::PathBuf;
use std::sync::{Arc, RwLock};
-use std::thread::{spawn, JoinHandle};
+use std::thread::Builder;
-use clap::Parser;
use thiserror::Error as ThisError;
-use vhost::{vhost_user, vhost_user::Listener};
use vhost_user_backend::VhostUserDaemon;
use vm_memory::{GuestMemoryAtomic, GuestMemoryMmap};
-use crate::console::{ConsoleController};
+use crate::console::{BackendType, ConsoleController};
use crate::vhu_console::VhostUserConsoleBackend;
pub(crate) type Result<T> = std::result::Result<T, Error>;
@@ -26,186 +26,288 @@ pub(crate) type Result<T> = std::result::Result<T, Error>;
pub(crate) enum Error {
#[error("Invalid socket count: {0}")]
SocketCountInvalid(usize),
- #[error("Failed to join threads")]
- FailedJoiningThreads,
- #[error("Could not create console controller: {0}")]
- CouldNotCreateConsoleController(crate::console::Error),
#[error("Could not create console backend: {0}")]
CouldNotCreateBackend(crate::vhu_console::Error),
#[error("Could not create daemon: {0}")]
CouldNotCreateDaemon(vhost_user_backend::Error),
-}
-
-#[derive(Parser, Debug)]
-#[clap(author, version, about, long_about = None)]
-struct ConsoleArgs {
- /// Location of vhost-user Unix domain socket. This is suffixed by 0,1,2..socket_count-1.
- #[clap(short, long)]
- socket_path: String,
-
- /// A console device name to be used for reading (ex. vconsole, console0, console1, ... etc.)
- #[clap(short = 'i', long)]
- console_path: String,
-
- /// Number of guests (sockets) to connect to.
- #[clap(short = 'c', long, default_value_t = 1)]
- socket_count: u32,
+ #[error("Fatal error: {0}")]
+ ServeFailed(vhost_user_backend::Error),
+ #[error("Thread `{0}` panicked")]
+ ThreadPanic(String, Box<dyn Any + Send>),
+ #[error("Error using multiple sockets with Nested backend")]
+ WrongBackendSocket,
}
#[derive(PartialEq, Debug)]
-struct ConsoleConfiguration {
- socket_path: String,
- socket_count: u32,
- console_path: String,
+pub struct VuConsoleConfig {
+ pub(crate) socket_path: PathBuf,
+ pub(crate) backend: BackendType,
+ pub(crate) tcp_port: String,
+ pub(crate) socket_count: u32,
}
-impl TryFrom<ConsoleArgs> for ConsoleConfiguration {
- type Error = Error;
+impl VuConsoleConfig {
+ pub fn generate_socket_paths(&self) -> Vec<PathBuf> {
+ let socket_file_name = self
+ .socket_path
+ .file_name()
+ .expect("socket_path has no filename.");
+ let socket_file_parent = self
+ .socket_path
+ .parent()
+ .expect("socket_path has no parent directory.");
+
+ let make_socket_path = |i: u32| -> PathBuf {
+ let mut file_name = socket_file_name.to_os_string();
+ file_name.push(std::ffi::OsStr::new(&i.to_string()));
+ socket_file_parent.join(&file_name)
+ };
- fn try_from(args: ConsoleArgs) -> Result<Self> {
+ (0..self.socket_count).map(make_socket_path).collect()
+ }
- if args.socket_count == 0 {
- return Err(Error::SocketCountInvalid(0));
- }
+ pub fn generate_tcp_addrs(&self) -> Vec<String> {
+ let tcp_port_base = self.tcp_port.clone();
- let console_path = args.console_path.trim().to_string();
+ let make_tcp_port = |i: u32| -> String {
+ let port_num: u32 = tcp_port_base.clone().parse().unwrap();
+ "127.0.0.1:".to_owned() + &(port_num + i).to_string()
+ };
- Ok(ConsoleConfiguration {
- socket_path: args.socket_path,
- socket_count: args.socket_count,
- console_path,
- })
+ (0..self.socket_count).map(make_tcp_port).collect()
}
}
-fn start_backend(args: ConsoleArgs) -> Result<()> {
-
- println!("start_backend function!\n");
-
- let config = ConsoleConfiguration::try_from(args).unwrap();
- let mut handles = Vec::new();
+// This is the public API through which an external program starts the
+/// vhost-device-console backend server.
+pub(crate) fn start_backend_server(
+ socket: PathBuf,
+ tcp_addr: String,
+ backend: BackendType,
+) -> Result<()> {
+ loop {
+ let controller = ConsoleController::new(backend);
+ let arc_controller = Arc::new(RwLock::new(controller));
+ let vu_console_backend = Arc::new(RwLock::new(
+ VhostUserConsoleBackend::new(arc_controller).map_err(Error::CouldNotCreateBackend)?,
+ ));
+
+ let mut daemon = VhostUserDaemon::new(
+ String::from("vhost-device-console-backend"),
+ vu_console_backend.clone(),
+ GuestMemoryAtomic::new(GuestMemoryMmap::new()),
+ )
+ .map_err(Error::CouldNotCreateDaemon)?;
+
+ let vring_workers = daemon.get_epoll_handlers();
+ vu_console_backend
+ .read()
+ .unwrap()
+ .set_vring_worker(&vring_workers[0]);
+
+ // Start the corresponding console thread
+ let read_handle = if backend == BackendType::Nested {
+ VhostUserConsoleBackend::start_console_thread(&vu_console_backend)
+ } else {
+ VhostUserConsoleBackend::start_tcp_console_thread(&vu_console_backend, tcp_addr.clone())
+ };
- for _ in 0..config.socket_count {
- let socket = config.socket_path.to_owned();
- let console_path = config.console_path.to_owned();
+ daemon.serve(&socket).map_err(Error::ServeFailed)?;
- let handle: JoinHandle<Result<()>> = spawn(move || loop {
- // A separate thread is spawned for each socket and console connect to a separate guest.
- // These are run in an infinite loop to not require the daemon to be restarted once a
- // guest exits.
- //
- // There isn't much value in complicating code here to return an error from the
- // threads, and so the code uses unwrap() instead. The panic on a thread won't cause
- // trouble to other threads/guests or the main() function and should be safe for the
- // daemon.
+ // Kill console input thread
+ vu_console_backend.read().unwrap().kill_console_thread();
- let controller =
- ConsoleController::new(console_path.clone()).map_err(Error::CouldNotCreateConsoleController)?;
- let arc_controller = Arc::new(RwLock::new(controller));
- let vu_console_backend = Arc::new(RwLock::new(
- VhostUserConsoleBackend::new(arc_controller).map_err(Error::CouldNotCreateBackend)?,
- ));
+ // Wait for read thread to exit
+ match read_handle.join() {
+ Ok(_) => info!("The read thread returned successfully"),
+ Err(e) => warn!("The read thread returned the error: {:?}", e),
+ }
+ }
+}
- let mut daemon = VhostUserDaemon::new(
- String::from("vhost-device-console-backend"),
- vu_console_backend.clone(),
- GuestMemoryAtomic::new(GuestMemoryMmap::new()),
- )
- .map_err(Error::CouldNotCreateDaemon)?;
-
- /* Start the read thread -- need to handle it after termination */
- let vring_workers = daemon.get_epoll_handlers();
- vu_console_backend.read()
- .unwrap()
- .set_vring_worker(&vring_workers[0]);
- VhostUserConsoleBackend::start_console_thread(&vu_console_backend);
-
- let listener = Listener::new(socket.clone(), true).unwrap();
- daemon.start(listener).unwrap();
-
- match daemon.wait() {
- Ok(()) => {
- info!("Stopping cleanly.");
- }
- Err(vhost_user_backend::Error::HandleRequest(
- vhost_user::Error::PartialMessage | vhost_user::Error::Disconnected,
- )) => {
- info!("vhost-user connection closed with partial message. If the VM is shutting down, this is expected behavior; otherwise, it might be a bug.");
- }
- Err(e) => {
- warn!("Error running daemon: {:?}", e);
- }
- }
-
- // No matter the result, we need to shut down the worker thread.
- vu_console_backend.read().unwrap().exit_event.write(1).unwrap();
- });
-
- handles.push(handle);
+pub fn start_backend(config: VuConsoleConfig) -> Result<()> {
+ let mut handles = HashMap::new();
+ let (senders, receiver) = std::sync::mpsc::channel();
+ let tcp_addrs = config.generate_tcp_addrs();
+ let backend = config.backend;
+
+ for (thread_id, (socket, tcp_addr)) in config
+ .generate_socket_paths()
+ .into_iter()
+ .zip(tcp_addrs.iter())
+ .enumerate()
+ {
+ let tcp_addr = tcp_addr.clone();
+ info!("thread_id: {}, socket: {:?}", thread_id, socket);
+
+ let name = format!("vhu-console-{}", tcp_addr);
+ let sender = senders.clone();
+ let handle = Builder::new()
+ .name(name.clone())
+ .spawn(move || {
+ let result = std::panic::catch_unwind(move || {
+ start_backend_server(socket, tcp_addr.to_string(), backend)
+ });
+
+ // Notify the main thread that we are done.
+ sender.send(thread_id).unwrap();
+
+ result.map_err(|e| Error::ThreadPanic(name, e))?
+ })
+ .unwrap();
+ handles.insert(thread_id, handle);
}
- for handle in handles {
- handle.join().map_err(|_| Error::FailedJoiningThreads)??;
+ while !handles.is_empty() {
+ let thread_id = receiver.recv().unwrap();
+ handles
+ .remove(&thread_id)
+ .unwrap()
+ .join()
+ .map_err(std::panic::resume_unwind)
+ .unwrap()?;
}
Ok(())
}
-pub(crate) fn console_init() {
- env_logger::init();
- println!("Console_init function!");
- if let Err(e) = start_backend(ConsoleArgs::parse()) {
- error!("{e}");
- exit(1);
- }
-}
-
#[cfg(test)]
mod tests {
use super::*;
+ use crate::ConsoleArgs;
+ use assert_matches::assert_matches;
#[test]
- fn test_console_configuration_try_from_valid_args() {
+ fn test_console_valid_configuration_nested() {
let args = ConsoleArgs {
- socket_path: String::from("/path/to/socket"),
- console_path: String::from("vconsole"),
- socket_count: 3,
+ socket_path: String::from("/tmp/vhost.sock").into(),
+ backend: BackendType::Nested,
+ tcp_port: String::from("12345"),
+ socket_count: 1,
};
- let result = ConsoleConfiguration::try_from(args);
+ assert!(VuConsoleConfig::try_from(args).is_ok());
+ }
- assert!(result.is_ok());
+ #[test]
+ fn test_console_invalid_configuration_nested_1() {
+ let args = ConsoleArgs {
+ socket_path: String::from("/tmp/vhost.sock").into(),
+ backend: BackendType::Nested,
+ tcp_port: String::from("12345"),
+ socket_count: 0,
+ };
- let config = result.unwrap();
- assert_eq!(config.socket_path, "/path/to/socket");
- assert_eq!(config.console_path, "vconsole");
- assert_eq!(config.socket_count, 3);
+ assert_matches!(
+ VuConsoleConfig::try_from(args),
+ Err(Error::SocketCountInvalid(0))
+ );
}
#[test]
- fn test_console_configuration_try_from_invalid_args() {
- // Test with socket_count = 0
- let args_invalid_count = ConsoleArgs {
- socket_path: String::from("/path/to/socket"),
- console_path: String::from("vconsole"),
- socket_count: 0,
+ fn test_console_invalid_configuration_nested_2() {
+ let args = ConsoleArgs {
+ socket_path: String::from("/tmp/vhost.sock").into(),
+ backend: BackendType::Nested,
+ tcp_port: String::from("12345"),
+ socket_count: 2,
+ };
+
+ assert_matches!(
+ VuConsoleConfig::try_from(args),
+ Err(Error::WrongBackendSocket)
+ );
+ }
+
+ #[test]
+ fn test_console_valid_configuration_network_1() {
+ let args = ConsoleArgs {
+ socket_path: String::from("/tmp/vhost.sock").into(),
+ backend: BackendType::Network,
+ tcp_port: String::from("12345"),
+ socket_count: 1,
};
- let result_invalid_count = ConsoleConfiguration::try_from(args_invalid_count);
- assert!(result_invalid_count.is_err());
+ assert!(VuConsoleConfig::try_from(args).is_ok());
}
#[test]
- fn test_start_backend_success() {
- // Test start_backend with valid arguments
+ fn test_console_valid_configuration_network_2() {
let args = ConsoleArgs {
- socket_path: String::from("/path/to/socket"),
- console_path: String::from("vconsole"),
+ socket_path: String::from("/tmp/vhost.sock").into(),
+ backend: BackendType::Network,
+ tcp_port: String::from("12345"),
socket_count: 2,
};
- let result = start_backend(args);
+ assert!(VuConsoleConfig::try_from(args).is_ok());
+ }
+
+ fn test_backend_start_and_stop(args: ConsoleArgs) {
+ let config = VuConsoleConfig::try_from(args).expect("Wrong config");
+
+ let tcp_addrs = config.generate_tcp_addrs();
+ let backend = config.backend;
+
+ for (_socket, tcp_addr) in config
+ .generate_socket_paths()
+ .into_iter()
+ .zip(tcp_addrs.iter())
+ {
+ let controller = ConsoleController::new(backend);
+ let arc_controller = Arc::new(RwLock::new(controller));
+ let vu_console_backend = Arc::new(RwLock::new(
+ VhostUserConsoleBackend::new(arc_controller)
+ .map_err(Error::CouldNotCreateBackend)
+ .expect("Fail create vhuconsole backend"),
+ ));
+
+ let mut _daemon = VhostUserDaemon::new(
+ String::from("vhost-device-console-backend"),
+ vu_console_backend.clone(),
+ GuestMemoryAtomic::new(GuestMemoryMmap::new()),
+ )
+ .map_err(Error::CouldNotCreateDaemon)
+ .expect("Failed create daemon");
+
+ // Start the corresponinding console thread
+ let read_handle = if backend == BackendType::Nested {
+ VhostUserConsoleBackend::start_console_thread(&vu_console_backend)
+ } else {
+ VhostUserConsoleBackend::start_tcp_console_thread(
+ &vu_console_backend,
+ tcp_addr.clone(),
+ )
+ };
+
+ // Kill console input thread
+ vu_console_backend.read().unwrap().kill_console_thread();
+
+ // Wait for read thread to exit
+ assert_matches!(read_handle.join(), Ok(_));
+ }
+ }
+ #[test]
+ fn test_start_net_backend_success() {
+ let args = ConsoleArgs {
+ socket_path: String::from("/tmp/vhost.sock").into(),
+ backend: BackendType::Network,
+ tcp_port: String::from("12345"),
+ socket_count: 1,
+ };
+
+ test_backend_start_and_stop(args);
+ }
+
+ #[test]
+ fn test_start_nested_backend_success() {
+ let args = ConsoleArgs {
+ socket_path: String::from("/tmp/vhost.sock").into(),
+ backend: BackendType::Nested,
+ tcp_port: String::from("12345"),
+ socket_count: 1,
+ };
- assert!(result.is_ok());
+ test_backend_start_and_stop(args);
}
}
diff --git a/meta-egvirt/recipes-extended/vhost-device-console/vhost-device-console-0.1.0/src/console.rs b/meta-egvirt/recipes-extended/vhost-device-console/vhost-device-console-0.1.0/src/console.rs
index 2e2972b2..079fe0cb 100644..100755
--- a/meta-egvirt/recipes-extended/vhost-device-console/vhost-device-console-0.1.0/src/console.rs
+++ b/meta-egvirt/recipes-extended/vhost-device-console/vhost-device-console-0.1.0/src/console.rs
@@ -1,86 +1,44 @@
-// CAN backend device
+// Console backend device
//
-// Copyright 2023 VIRTUAL OPEN SYSTEMS SAS. All Rights Reserved.
+// Copyright 2023-2024 VIRTUAL OPEN SYSTEMS SAS. All Rights Reserved.
// Timos Ampelikiotis <t.ampelikiotis@virtualopensystems.com>
//
// SPDX-License-Identifier: Apache-2.0 or BSD-3-Clause
-use log::{warn, error};
-use std::sync::{Arc, RwLock};
+use crate::virtio_console::VirtioConsoleConfig;
+use clap::ValueEnum;
+use log::trace;
-use thiserror::Error as ThisError;
-use vm_memory::{ByteValued, Le16};
-
-use crate::vhu_console::{VirtioConsoleConfig, VirtioConsoleControl};
-
-type Result<T> = std::result::Result<T, Error>;
-
-#[derive(Copy, Clone, Debug, PartialEq, ThisError)]
-pub(crate) enum Error {
- #[error("Console not enabled yet")]
- ConsoleNotEnabled,
+#[derive(ValueEnum, Clone, Copy, Default, Debug, Eq, PartialEq)]
+pub enum BackendType {
+ #[default]
+ Nested,
+ Network,
}
#[derive(Debug)]
pub(crate) struct ConsoleController {
config: VirtioConsoleConfig,
- pub console_name: String,
+ pub backend: BackendType,
+ pub exit: bool,
}
impl ConsoleController {
- // Creates a new controller corresponding to `device`.
- pub(crate) fn new(console_name: String) -> Result<ConsoleController> {
-
- let console_name = console_name.to_owned();
- println!("console_name: {:?}", console_name);
-
- Ok(ConsoleController {
+ pub(crate) fn new(backend: BackendType) -> ConsoleController {
+ ConsoleController {
config: VirtioConsoleConfig {
- cols: 20.into(),
- rows: 20.into(),
- max_nr_ports: 1.into(),
- emerg_wr: 64.into(),
- },
- console_name,
- })
+ cols: 20.into(),
+ rows: 20.into(),
+ max_nr_ports: 1.into(),
+ emerg_wr: 64.into(),
+ },
+ backend,
+ exit: false,
+ }
}
pub(crate) fn config(&self) -> &VirtioConsoleConfig {
- log::trace!("Get config\n");
+ trace!("Get config\n");
&self.config
}
-
- pub(crate) fn operation(&self, tx_request: VirtioConsoleControl) -> Result<()> {
- log::trace!("Console operation\n");
- Ok(())
- }
}
-
-#[cfg(test)]
-mod tests {
- use super::*;
-
- #[test]
- fn test_new_console_controller() {
- let console_name = String::from("test_console");
- let controller = ConsoleController::new(console_name.clone());
-
- assert!(controller.is_ok());
-
- let controller = controller.unwrap();
- assert_eq!(controller.console_name, "test_console");
- }
-
- #[test]
- fn test_console_controller_config() {
- let console_name = String::from("test_console");
- let controller = ConsoleController::new(console_name).unwrap();
-
- let config = controller.config();
- assert_eq!(config.cols, 20);
- assert_eq!(config.rows, 20);
- assert_eq!(config.max_nr_ports, 1);
- assert_eq!(config.emerg_wr, 64);
- }
-}
-
diff --git a/meta-egvirt/recipes-extended/vhost-device-console/vhost-device-console-0.1.0/src/main.rs b/meta-egvirt/recipes-extended/vhost-device-console/vhost-device-console-0.1.0/src/main.rs
index cceafb4f..216a01e6 100644..100755
--- a/meta-egvirt/recipes-extended/vhost-device-console/vhost-device-console-0.1.0/src/main.rs
+++ b/meta-egvirt/recipes-extended/vhost-device-console/vhost-device-console-0.1.0/src/main.rs
@@ -1,18 +1,69 @@
// VIRTIO CONSOLE Emulation via vhost-user
//
-// Copyright 2023 VIRTUAL OPEN SYSTEMS SAS. All Rights Reserved.
+// Copyright 2023-2024 VIRTUAL OPEN SYSTEMS SAS. All Rights Reserved.
// Timos Ampelikiotis <t.ampelikiotis@virtualopensystems.com>
//
// SPDX-License-Identifier: Apache-2.0 or BSD-3-Clause
-#[cfg(target_env = "gnu")]
mod backend;
-#[cfg(target_env = "gnu")]
mod console;
-#[cfg(target_env = "gnu")]
mod vhu_console;
+mod virtio_console;
+use crate::console::BackendType;
+use clap::Parser;
+use log::error;
+use std::path::PathBuf;
+use std::process::exit;
+
+pub(crate) type Result<T> = std::result::Result<T, Error>;
+use crate::backend::{start_backend, Error, VuConsoleConfig};
+
+#[derive(Parser, Debug)]
+#[clap(author, version, about, long_about = None)]
+struct ConsoleArgs {
+ /// Location of vhost-user Unix domain socket. This is suffixed by 0,1,2..socket_count-1.
+ #[clap(short = 's', long, value_name = "SOCKET")]
+ socket_path: PathBuf,
+
+ /// Number of guests (sockets) to connect to.
+ #[clap(short = 'c', long, default_value_t = 1)]
+ socket_count: u32,
+
+ /// Console backend (Network, Nested) to be used.
+ #[clap(short = 'b', long, value_enum, default_value = "nested")]
+ backend: BackendType,
+
+ /// Initial tcp port to be used with "network" backend. If socket_count is N then
+ /// the following tcp ports will be created: tcp_port, tcp_port + 1, ..., tcp_port + (N - 1).
+ #[clap(short = 'p', long, value_name = "PORT", default_value = "12345")]
+ tcp_port: String,
+}
+
+impl TryFrom<ConsoleArgs> for VuConsoleConfig {
+ type Error = Error;
+
+ fn try_from(args: ConsoleArgs) -> Result<Self> {
+ if args.socket_count == 0 {
+ return Err(Error::SocketCountInvalid(0));
+ }
+
+ if (args.backend == BackendType::Nested) && (args.socket_count != 1) {
+ return Err(Error::WrongBackendSocket);
+ }
+
+ Ok(VuConsoleConfig {
+ socket_path: args.socket_path,
+ backend: args.backend,
+ tcp_port: args.tcp_port,
+ socket_count: args.socket_count,
+ })
+ }
+}
-#[cfg(target_env = "gnu")]
fn main() {
- backend::console_init()
+ env_logger::init();
+ if let Err(e) = VuConsoleConfig::try_from(ConsoleArgs::parse()).and_then(start_backend) {
+ error!("{e}");
+ exit(1);
+ }
}
diff --git a/meta-egvirt/recipes-extended/vhost-device-console/vhost-device-console-0.1.0/src/vhu_console.rs b/meta-egvirt/recipes-extended/vhost-device-console/vhost-device-console-0.1.0/src/vhu_console.rs
index ebddb00d..d5cca74a 100644..100755
--- a/meta-egvirt/recipes-extended/vhost-device-console/vhost-device-console-0.1.0/src/vhu_console.rs
+++ b/meta-egvirt/recipes-extended/vhost-device-console/vhost-device-console-0.1.0/src/vhu_console.rs
@@ -1,22 +1,31 @@
// vhost device console
//
-// Copyright 2023 VIRTUAL OPEN SYSTEMS SAS. All Rights Reserved.
+// Copyright 2023-2024 VIRTUAL OPEN SYSTEMS SAS. All Rights Reserved.
// Timos Ampelikiotis <t.ampelikiotis@virtualopensystems.com>
//
// SPDX-License-Identifier: Apache-2.0 or BSD-3-Clause
-use log::{warn, error};
-use std::mem::size_of;
+use crate::console::{BackendType, ConsoleController};
+use crate::virtio_console::{
+ VirtioConsoleControl, VIRTIO_CONSOLE_CONSOLE_PORT, VIRTIO_CONSOLE_DEVICE_READY,
+ VIRTIO_CONSOLE_F_MULTIPORT, VIRTIO_CONSOLE_PORT_ADD, VIRTIO_CONSOLE_PORT_NAME,
+ VIRTIO_CONSOLE_PORT_OPEN, VIRTIO_CONSOLE_PORT_READY,
+};
+use console::Key;
+use crossterm::terminal::{disable_raw_mode, enable_raw_mode};
+use log::{error, trace};
+use nix::sys::select::{select, FdSet};
+use std::os::fd::AsRawFd;
use std::slice::from_raw_parts;
use std::sync::{Arc, RwLock};
+use std::thread::JoinHandle;
use std::{
convert,
io::{self, Result as IoResult},
};
-use std::io::{Write};
-use std::os::fd::AsRawFd;
use thiserror::Error as ThisError;
use vhost::vhost_user::message::{VhostUserProtocolFeatures, VhostUserVirtioFeatures};
+use vhost_user_backend::VringEpollHandler;
use vhost_user_backend::{VhostUserBackendMut, VringRwLock, VringT};
use virtio_bindings::bindings::virtio_config::{VIRTIO_F_NOTIFY_ON_EMPTY, VIRTIO_F_VERSION_1};
use virtio_bindings::bindings::virtio_ring::{
@@ -24,76 +33,61 @@ use virtio_bindings::bindings::virtio_ring::{
};
use virtio_queue::{DescriptorChain, QueueOwnedT};
use vm_memory::{
- ByteValued, Bytes, GuestAddressSpace, GuestMemoryAtomic, GuestMemoryLoadGuard,
- GuestMemoryMmap, Le16, Le32,
+ ByteValued, GuestAddressSpace, GuestMemoryAtomic, GuestMemoryLoadGuard, GuestMemoryMmap,
};
use vmm_sys_util::epoll::EventSet;
use vmm_sys_util::eventfd::{EventFd, EFD_NONBLOCK};
-use vhost_user_backend::VringEpollHandler;
-use crate::console::{ConsoleController};
-use std::thread::{JoinHandle, spawn};
-use queues::{Queue, IsQueue};
-use std::thread;
-use std::time::Duration;
use console::Term;
-
-/// Feature bit numbers
-pub const VIRTIO_CONSOLE_F_SIZE: u16 = 0;
-pub const VIRTIO_CONSOLE_F_MULTIPORT: u16 = 1;
-pub const VIRTIO_CONSOLE_F_EMERG_WRITE: u16 = 2;
+use queues::{IsQueue, Queue};
+use std::io::Read;
+use std::io::Write;
+use std::net::TcpListener;
+use std::thread::spawn;
/// Virtio configuration
const QUEUE_SIZE: usize = 128;
const NUM_QUEUES: usize = 4;
-/// Queues
+/// Queue events
const RX_QUEUE: u16 = 0;
const TX_QUEUE: u16 = 1;
const CTRL_RX_QUEUE: u16 = 2;
const CTRL_TX_QUEUE: u16 = 3;
-const BACKEND_EFD: u16 = (NUM_QUEUES + 1) as u16;
-const BACKEND_RX_EFD: u16 = (NUM_QUEUES + 2) as u16;
+/// The two following events are used to help the vhu_console
+/// backend trigger events to itself. For example:
+/// a) BACKEND_RX_EFD is being triggered when the backend
+/// has new data to send to the RX queue.
+/// b) BACKEND_CTRL_RX_EFD event is used when the backend
+/// needs to write to the RX control queue.
+const BACKEND_RX_EFD: u16 = (NUM_QUEUES + 1) as u16;
+const BACKEND_CTRL_RX_EFD: u16 = (NUM_QUEUES + 2) as u16;
-/// Console virtio control messages
-const VIRTIO_CONSOLE_DEVICE_READY: u16 = 0 ;
-const VIRTIO_CONSOLE_PORT_ADD: u16 = 1;
-const VIRTIO_CONSOLE_PORT_REMOVE: u16 = 2;
-const VIRTIO_CONSOLE_PORT_READY: u16 = 3;
-const VIRTIO_CONSOLE_CONSOLE_PORT: u16 = 4;
-const VIRTIO_CONSOLE_RESIZE: u16 = 5;
-const VIRTIO_CONSOLE_PORT_OPEN: u16 = 6;
-const VIRTIO_CONSOLE_PORT_NAME: u16 = 7;
+/// Port name - Need to be updated when MULTIPORT feature
+/// is supported for more than one devices.
+const PORT_NAME: &[u8] = b"org.test.foo!";
type Result<T> = std::result::Result<T, Error>;
#[derive(Copy, Clone, Debug, PartialEq, ThisError)]
pub(crate) enum Error {
- #[error("Failed to handle event, didn't match EPOLLIN")]
- HandleEventNotEpollIn,
#[error("Failed to handle unknown event")]
HandleEventUnknown,
- #[error("Received unexpected write only descriptor at index {0}")]
- UnexpectedWriteOnlyDescriptor(usize),
- #[error("Received unexpected readable descriptor at index {0}")]
- UnexpectedReadableDescriptor(usize),
- #[error("Invalid descriptor count {0}")]
- UnexpectedDescriptorCount(usize),
- #[error("Invalid descriptor size, expected: {0}, found: {1}")]
- UnexpectedDescriptorSize(usize, u32),
- #[error("Descriptor not found")]
- DescriptorNotFound,
- #[error("Failed to send notification")]
- NotificationFailed,
+ #[error("Descriptor not found")]
+ DescriptorNotFound,
+ #[error("Failed to send notification")]
+ NotificationFailed,
#[error("Descriptor read failed")]
DescriptorReadFailed,
#[error("Descriptor write failed")]
DescriptorWriteFailed,
+ #[error("Add used element in vring {0} failed")]
+ AddUsedElemFailed(u16),
#[error("Failed to create new EventFd")]
EventFdFailed,
- #[error("Failed to remove rx queue")]
- EmptyQueue,
+ #[error("Failed to add control message in the internal queue")]
+ RxCtrlQueueAddFailed,
}
impl convert::From<Error> for io::Error {
@@ -102,53 +96,19 @@ impl convert::From<Error> for io::Error {
}
}
-/// Virtio Console Config
-#[derive(Copy, Clone, Debug, Default, PartialEq)]
-#[repr(C)]
-pub(crate) struct VirtioConsoleConfig {
- pub cols: Le16,
- pub rows: Le16,
- pub max_nr_ports: Le32,
- pub emerg_wr: Le32,
-}
-
-// SAFETY: The layout of the structure is fixed and can be initialized by
-// reading its content from byte array.
-unsafe impl ByteValued for VirtioConsoleConfig {}
-
-#[derive(Copy, Clone, Debug, Default, PartialEq)]
-#[repr(C)]
-pub(crate) struct VirtioConsoleControl {
- pub id: Le32,
- pub event: Le16,
- pub value: Le16,
-}
-
-use std::io::Cursor;
-
-impl VirtioConsoleControl {
- fn to_le_bytes(&self) -> Vec<u8> {
- let mut buffer = Vec::new();
-
- buffer.extend_from_slice(&self.id.to_native().to_le_bytes());
- buffer.extend_from_slice(&self.event.to_native().to_le_bytes());
- buffer.extend_from_slice(&self.value.to_native().to_le_bytes());
- buffer
- }
-}
-
// SAFETY: The layout of the structure is fixed and can be initialized by
// reading its content from byte array.
unsafe impl ByteValued for VirtioConsoleControl {}
pub(crate) struct VhostUserConsoleBackend {
controller: Arc<RwLock<ConsoleController>>,
- acked_features: u64,
+ acked_features: u64,
event_idx: bool,
- rx_fifo: Queue<VirtioConsoleControl>,
+ rx_ctrl_fifo: Queue<VirtioConsoleControl>,
+ rx_data_fifo: Queue<String>,
pub(crate) ready: bool,
pub(crate) ready_to_write: bool,
- pub(crate) output_buffer: String,
+ pub(crate) output_queue: Queue<String>,
pub(crate) rx_event: EventFd,
pub(crate) rx_ctrl_event: EventFd,
pub(crate) exit_event: EventFd,
@@ -160,13 +120,14 @@ type ConsoleDescriptorChain = DescriptorChain<GuestMemoryLoadGuard<GuestMemoryMm
impl VhostUserConsoleBackend {
pub(crate) fn new(controller: Arc<RwLock<ConsoleController>>) -> Result<Self> {
Ok(VhostUserConsoleBackend {
- controller: controller,
+ controller,
event_idx: false,
- rx_fifo: Queue::new(),
- acked_features: 0x0,
- ready: false,
- ready_to_write: false,
- output_buffer: String::new(),
+ rx_ctrl_fifo: Queue::new(),
+ rx_data_fifo: Queue::new(),
+ acked_features: 0x0,
+ ready: false,
+ ready_to_write: false,
+ output_queue: Queue::new(),
rx_event: EventFd::new(EFD_NONBLOCK).map_err(|_| Error::EventFdFailed)?,
rx_ctrl_event: EventFd::new(EFD_NONBLOCK).map_err(|_| Error::EventFdFailed)?,
exit_event: EventFd::new(EFD_NONBLOCK).map_err(|_| Error::EventFdFailed)?,
@@ -174,292 +135,229 @@ impl VhostUserConsoleBackend {
})
}
- fn check_features (&self, features: u16) -> bool {
- (self.acked_features & (1 << features)) != 0
- }
-
- fn print_console_frame (&self, control_msg: VirtioConsoleControl) {
- println!("id 0x{:x}", control_msg.id.to_native());
- println!("event 0x{:x}", control_msg.event.to_native());
- println!("value 0x{:x}", control_msg.value.to_native());
+ fn print_console_frame(&self, control_msg: VirtioConsoleControl) {
+ trace!("id 0x{:x}", control_msg.id.to_native());
+ trace!("event 0x{:x}", control_msg.event.to_native());
+ trace!("value 0x{:x}", control_msg.value.to_native());
}
fn process_rx_requests(
- &mut self,
- requests: Vec<ConsoleDescriptorChain>,
- vring: &VringRwLock
- ) -> Result<bool> {
- log::trace!("process_rx_requests");
-
+ &mut self,
+ requests: Vec<ConsoleDescriptorChain>,
+ vring: &VringRwLock,
+ ) -> Result<bool> {
if requests.is_empty() {
- log::trace!("requests.is_empty");
- vring.signal_used_queue();
return Ok(true);
}
- log::trace!("requests.len: {:?}", requests.len());
- let desc_chain = &requests[0];
- let descriptors: Vec<_> = desc_chain.clone().collect();
-
- log::trace!("descriptors.len(): {:?}", descriptors.len());
- if descriptors.len() != 1 {
- log::trace!("Error::UnexpectedDescriptorCount");
- return Err(Error::UnexpectedDescriptorCount(descriptors.len()));
- }
-
- let desc_request = descriptors[0];
- if !desc_request.is_write_only() {
- log::trace!("!desc_request.is_write_only()");
- return Err(Error::UnexpectedReadableDescriptor(1));
- }
-
- // TODO: if buffer is more than the the desc_request length,
- // write the remaining in the next chain.
- log::trace!("desc_request.len(): {}", desc_request.len());
- let response = self.output_buffer.clone();
+ for desc_chain in requests {
+ let atomic_mem = self.mem.as_ref().unwrap().memory();
+ let mut writer = desc_chain
+ .clone()
+ .writer(&atomic_mem)
+ .map_err(|_| Error::DescriptorWriteFailed)?;
+
+ let response: String = match self.rx_data_fifo.remove() {
+ Ok(item) => item,
+ _ => {
+ return Ok(false);
+ }
+ };
- desc_chain
- .memory()
- .write_slice(response.as_bytes(), desc_request.addr())
- .map_err(|_| Error::DescriptorWriteFailed)?;
+ for b in response.bytes() {
+ writer
+ .write_obj::<u8>(b)
+ .map_err(|_| Error::DescriptorWriteFailed)?;
+ }
- if vring.add_used(desc_chain.head_index(), response.as_bytes().len() as u32).is_err() {
- warn!("Couldn't return used descriptors to the ring");
+ vring
+ .add_used(desc_chain.head_index(), writer.bytes_written() as u32)
+ .map_err(|_| Error::AddUsedElemFailed(RX_QUEUE))?;
}
Ok(true)
}
fn process_tx_requests(
- &self,
- requests: Vec<ConsoleDescriptorChain>,
- vring: &VringRwLock
- ) -> Result<bool> {
- log::trace!("process_tx_requests");
-
+ &mut self,
+ requests: Vec<ConsoleDescriptorChain>,
+ vring: &VringRwLock,
+ ) -> Result<bool> {
if requests.is_empty() {
- log::trace!("requests.is_empty");
return Ok(true);
}
- log::trace!("requests.len: {:?}", requests.len());
for desc_chain in requests {
- let descriptors: Vec<_> = desc_chain.clone().collect();
-
- log::trace!("descriptors.len(): {:?}", descriptors.len());
- if descriptors.len() != 1 {
- log::trace!("Error::UnexpectedDescriptorCount");
- return Err(Error::UnexpectedDescriptorCount(descriptors.len()));
- }
-
- let desc_request = descriptors[0];
- if desc_request.is_write_only() {
- log::trace!("Error::UnexpectedReadOnlyDescriptor");
- return Err(Error::UnexpectedWriteOnlyDescriptor(0));
- }
-
- log::trace!("desc_request.len(): {}", desc_request.len());
- let desc_len = desc_request.len();
-
- let mut buffer = [0 as u8; 4096];
-
- let request = desc_chain
- .memory()
- .read_slice(&mut buffer, desc_request.addr())
+ let atomic_mem = self.mem.as_ref().unwrap().memory();
+ let mut reader = desc_chain
+ .clone()
+ .reader(&atomic_mem)
.map_err(|_| Error::DescriptorReadFailed)?;
- let new_buff = &buffer[0..desc_len as usize];
-
- let my_string = String::from_utf8(new_buff.to_vec()).unwrap();
- log::trace!("{}", my_string);
- print!("{}", my_string);
- io::stdout().flush().unwrap(); // Ensure the prompt is displayed.
+ let mut tx_data: Vec<u8> = Vec::new();
+ let data_len = reader.available_bytes();
+ for _i in 0..data_len {
+ let data_byte = reader
+ .read_obj::<u8>()
+ .map_err(|_| Error::DescriptorReadFailed)?;
+ tx_data.push(data_byte);
+ }
- if vring.add_used(desc_chain.head_index(), desc_request.len()).is_err() {
- log::trace!("Couldn't return used descriptors to the ring");
- warn!("Couldn't return used descriptors to the ring");
+ let my_string = String::from_utf8(tx_data).unwrap();
+ if self.controller.read().unwrap().backend == BackendType::Nested {
+ print!("{}", my_string);
+ io::stdout().flush().unwrap();
+ } else {
+ self.output_queue
+ .add(my_string)
+ .expect("Failed to add element in the output queue");
+ //.map_err(|_| Error::RxCtrlQueueAddFailed)?;
}
+
+ vring
+ .add_used(desc_chain.head_index(), reader.bytes_read() as u32)
+ .map_err(|_| Error::AddUsedElemFailed(TX_QUEUE))?;
}
Ok(true)
}
fn process_ctrl_rx_requests(
- &mut self,
- requests: Vec<ConsoleDescriptorChain>,
- vring: &VringRwLock,
- ) -> Result<bool> {
- log::trace!("process_ctrl_rx_requests");
+ &mut self,
+ requests: Vec<ConsoleDescriptorChain>,
+ vring: &VringRwLock,
+ ) -> Result<bool> {
+ let mut used_flag = false;
if requests.is_empty() {
- log::trace!("requests.is_empty()");
return Ok(true);
}
- log::trace!("\trequests.len(): {}", requests.len());
for desc_chain in requests {
+ let atomic_mem = self.mem.as_ref().unwrap().memory();
+ let mut writer = desc_chain
+ .clone()
+ .writer(&atomic_mem)
+ .map_err(|_| Error::DescriptorWriteFailed)?;
+
+ let ctrl_msg: VirtioConsoleControl = match self.rx_ctrl_fifo.remove() {
+ Ok(item) => {
+ used_flag = true;
+ item
+ }
+ _ => {
+ return Ok(used_flag);
+ }
+ };
- let descriptors: Vec<_> = desc_chain.clone().collect();
+ self.print_console_frame(ctrl_msg);
- log::trace!("descriptors.len(): {:?}", descriptors.len());
- if descriptors.len() < 1 {
- warn!("Error::UnexpectedDescriptorCount");
- return Err(Error::UnexpectedDescriptorCount(descriptors.len()));
- }
+ let mut buffer: Vec<u8> = Vec::new();
+ buffer.extend_from_slice(&ctrl_msg.to_le_bytes());
- log::trace!("self.rx_fifo.size(): {}", self.rx_fifo.size());
- let ctrl_msg: VirtioConsoleControl = match self.rx_fifo.remove() {
- Ok(item) => item,
- _ => {
- log::trace!("No rx elements");
- return Ok(false)
- },
- };
-
- let desc_request = descriptors[0];
- if !desc_request.is_write_only() {
- warn!("Error::UnexpectedWriteOnlyDescriptor");
- return Err(Error::UnexpectedWriteOnlyDescriptor(0));
- }
+ if ctrl_msg.event.to_native() == VIRTIO_CONSOLE_PORT_NAME {
+ buffer.extend_from_slice(PORT_NAME);
+ };
- if (desc_request.len() as usize) < size_of::<VirtioConsoleControl>() {
- log::trace!("UnexpectedDescriptorSize, len = {:?}", desc_request.len());
- return Err(Error::UnexpectedDescriptorSize(
- size_of::<VirtioConsoleControl>(),
- desc_request.len(),
- ));
- }
+ writer
+ .write(buffer.as_slice())
+ .map_err(|_| Error::DescriptorWriteFailed)?;
- log::trace!("desc_request.len(): {}", desc_request.len());
- self.print_console_frame(ctrl_msg);
-
- let mut buffer: Vec<u8> = Vec::new();
- buffer.extend_from_slice(&ctrl_msg.to_le_bytes());
-
- if ctrl_msg.event.to_native() == VIRTIO_CONSOLE_PORT_NAME {
- let string_bytes = "org.fedoraproject.console.foo!".as_bytes();
- buffer.extend_from_slice(string_bytes);
- };
-
- desc_chain
- .memory()
- .write_slice(&buffer, desc_request.addr())
- .map_err(|_| Error::DescriptorWriteFailed)?;
-
- if vring.add_used(desc_chain.head_index(), desc_request.len()).is_err() {
- log::trace!("Couldn't return used descriptors to the ring");
- warn!("Couldn't return used descriptors to the ring");
- }
+ vring
+ .add_used(desc_chain.head_index(), writer.bytes_written() as u32)
+ .map_err(|_| Error::AddUsedElemFailed(CTRL_RX_QUEUE))?;
}
Ok(true)
}
- fn handle_control_msg (
- &mut self,
- vring: &VringRwLock,
- ctrl_msg: VirtioConsoleControl
- ) -> Result<()> {
-
- let mut ctrl_msg_reply = VirtioConsoleControl {
- id: 0.into(),
- event: 0.into(),
- value: 1.into(),
- };
- match ctrl_msg.event.to_native() {
- VIRTIO_CONSOLE_DEVICE_READY => {
- log::trace!("VIRTIO_CONSOLE_DEVICE_READY");
- self.ready = true;
- ctrl_msg_reply.event = VIRTIO_CONSOLE_PORT_ADD.into();
- self.rx_fifo.add(ctrl_msg_reply);
- self.process_ctrl_rx_queue(vring)?;
- },
- VIRTIO_CONSOLE_PORT_READY => {
- log::trace!("VIRTIO_CONSOLE_PORT_READY");
- ctrl_msg_reply.event = VIRTIO_CONSOLE_CONSOLE_PORT.into();
- self.rx_fifo.add(ctrl_msg_reply.clone());
- self.process_ctrl_rx_queue(vring)?;
-
- ctrl_msg_reply.event = VIRTIO_CONSOLE_PORT_NAME.into();
- self.rx_fifo.add(ctrl_msg_reply.clone());
- self.process_ctrl_rx_queue(vring)?;
-
- ctrl_msg_reply.event = VIRTIO_CONSOLE_PORT_OPEN.into();
- self.rx_fifo.add(ctrl_msg_reply.clone());
- self.process_ctrl_rx_queue(vring)?;
- },
- VIRTIO_CONSOLE_PORT_OPEN => {
- log::trace!("VIRTIO_CONSOLE_PORT_OPEN");
- },
- _ => {
- log::trace!("Uknown control event");
- return Err(Error::HandleEventUnknown);
- }
- };
- Ok(())
- }
+ fn handle_control_msg(&mut self, ctrl_msg: VirtioConsoleControl) -> Result<()> {
+ let mut ctrl_msg_reply = VirtioConsoleControl {
+ id: 0.into(),
+ event: 0.into(),
+ value: 1.into(),
+ };
+ match ctrl_msg.event.to_native() {
+ VIRTIO_CONSOLE_DEVICE_READY => {
+ trace!("VIRTIO_CONSOLE_DEVICE_READY");
- fn process_ctrl_tx_requests(
- &mut self,
- requests: Vec<ConsoleDescriptorChain>,
- vring: &VringRwLock,
- rx_ctrl_vring: &VringRwLock
- ) -> Result<bool> {
- log::trace!("process_ctrl_tx_requests");
+ if ctrl_msg.value != 1 {
+ trace!("Guest failure in adding device");
+ return Ok(());
+ }
- if requests.is_empty() {
- log::trace!("requests.is_empty()");
- return Ok(true);
- }
+ self.ready = true;
+ ctrl_msg_reply.event = VIRTIO_CONSOLE_PORT_ADD.into();
+ self.rx_ctrl_fifo
+ .add(ctrl_msg_reply)
+ .map_err(|_| Error::RxCtrlQueueAddFailed)?;
+ }
+ VIRTIO_CONSOLE_PORT_READY => {
+ trace!("VIRTIO_CONSOLE_PORT_READY");
- for desc_chain in requests {
- let descriptors: Vec<_> = desc_chain.clone().collect();
+ if ctrl_msg.value != 1 {
+ trace!("Guest failure in adding port for device");
+ return Ok(());
+ }
- if descriptors.len() < 1 {
- warn!("Error::UnexpectedDescriptorCount");
- return Err(Error::UnexpectedDescriptorCount(descriptors.len()));
- }
+ ctrl_msg_reply.event = VIRTIO_CONSOLE_CONSOLE_PORT.into();
+ self.rx_ctrl_fifo
+ .add(ctrl_msg_reply)
+ .map_err(|_| Error::RxCtrlQueueAddFailed)?;
- log::trace!("descriptors.len(): {:?}", descriptors.len());
+ ctrl_msg_reply.event = VIRTIO_CONSOLE_PORT_NAME.into();
+ self.rx_ctrl_fifo
+ .add(ctrl_msg_reply)
+ .map_err(|_| Error::RxCtrlQueueAddFailed)?;
- let desc_request = descriptors[0];
- if desc_request.is_write_only() {
- log::trace!("This is write only");
- return Err(Error::UnexpectedWriteOnlyDescriptor(0));
- } else {
- log::trace!("This is read only");
- }
-
- if desc_request.len() as usize != size_of::<VirtioConsoleControl>() {
- log::trace!("UnexpectedDescriptorSize, len = {:?}", desc_request.len());
- return Err(Error::UnexpectedDescriptorSize(
- size_of::<VirtioConsoleControl>(),
- desc_request.len(),
- ));
+ ctrl_msg_reply.event = VIRTIO_CONSOLE_PORT_OPEN.into();
+ self.rx_ctrl_fifo
+ .add(ctrl_msg_reply)
+ .map_err(|_| Error::RxCtrlQueueAddFailed)?;
+ }
+ VIRTIO_CONSOLE_PORT_OPEN => {
+ trace!("VIRTIO_CONSOLE_PORT_OPEN");
+ }
+ _ => {
+ trace!("Uknown control event");
+ return Err(Error::HandleEventUnknown);
}
+ };
+ Ok(())
+ }
+
+ fn process_ctrl_tx_requests(
+ &mut self,
+ requests: Vec<ConsoleDescriptorChain>,
+ vring: &VringRwLock,
+ ) -> Result<bool> {
+ if requests.is_empty() {
+ return Ok(true);
+ }
- log::trace!("desc_request.len: {}", desc_request.len());
+ for desc_chain in requests {
+ let atomic_mem = self.mem.as_ref().unwrap().memory();
+ let mut reader = desc_chain
+ .clone()
+ .reader(&atomic_mem)
+ .map_err(|_| Error::DescriptorReadFailed)?;
- let mut request = desc_chain
- .memory()
- .read_obj::<VirtioConsoleControl>(desc_request.addr())
+ let request = reader
+ .read_obj::<VirtioConsoleControl>()
.map_err(|_| Error::DescriptorReadFailed)?;
- self.print_console_frame(request);
+ // Print the receive console frame
+ self.print_console_frame(request);
- self.handle_control_msg(rx_ctrl_vring, request);
+ // Process the received control frame
+ self.handle_control_msg(request)?;
- if let Some(event_fd) = rx_ctrl_vring.get_ref().get_kick() {
- self.rx_ctrl_event.write(1).unwrap();
- } else {
- // Handle the case where `state` is `None`.
- log::trace!("EventFd is not available.");
- }
+ // trigger a kick to the CTRL_RT_QUEUE
+ self.rx_ctrl_event.write(1).unwrap();
- if vring.add_used(desc_chain.head_index(), desc_request.len()).is_err() {
- log::trace!("Couldn't return used descriptors to the ring");
- warn!("Couldn't return used descriptors to the ring");
- }
+ vring
+ .add_used(desc_chain.head_index(), reader.bytes_read() as u32)
+ .map_err(|_| Error::AddUsedElemFailed(CTRL_TX_QUEUE))?;
}
Ok(true)
@@ -467,7 +365,6 @@ impl VhostUserConsoleBackend {
/// Process the messages in the vring and dispatch replies
fn process_rx_queue(&mut self, vring: &VringRwLock) -> Result<()> {
- log::trace!("process_rx_queue");
let requests: Vec<_> = vring
.get_mut()
.get_queue_mut()
@@ -477,20 +374,15 @@ impl VhostUserConsoleBackend {
if self.process_rx_requests(requests, vring)? {
// Send notification once all the requests are processed
- log::trace!("Send notification once all the requests of queue 0 are processed");
vring
.signal_used_queue()
- .map_err(|_| {
- log::trace!("NotificationFailed");
- Error::NotificationFailed
- })?;
+ .map_err(|_| Error::NotificationFailed)?;
}
- Ok(())
+ Ok(())
}
/// Process the messages in the vring and dispatch replies
- fn process_tx_queue(&self, vring: &VringRwLock) -> Result<()> {
- log::trace!("process_tx_queue");
+ fn process_tx_queue(&mut self, vring: &VringRwLock) -> Result<()> {
let requests: Vec<_> = vring
.get_mut()
.get_queue_mut()
@@ -500,21 +392,15 @@ impl VhostUserConsoleBackend {
if self.process_tx_requests(requests, vring)? {
// Send notification once all the requests are processed
- log::trace!("Send notification once all the requests of queue 1 are processed");
vring
.signal_used_queue()
- .map_err(|_| {
- log::trace!("signal_used_queue error");
- Error::NotificationFailed
- })?;
+ .map_err(|_| Error::NotificationFailed)?;
}
-
Ok(())
}
/// Process the messages in the vring and dispatch replies
fn process_ctrl_rx_queue(&mut self, vring: &VringRwLock) -> Result<()> {
- log::trace!("process_ctrl_rx_queue");
let requests: Vec<_> = vring
.get_mut()
.get_queue_mut()
@@ -523,18 +409,16 @@ impl VhostUserConsoleBackend {
.collect();
if self.process_ctrl_rx_requests(requests, vring)? {
- log::trace!("Send notification once all the requests of queue 2 are processed");
// Send notification once all the requests are processed
vring
.signal_used_queue()
.map_err(|_| Error::NotificationFailed)?;
}
- Ok(())
+ Ok(())
}
/// Process the messages in the vring and dispatch replies
- fn process_ctrl_tx_queue(&mut self, vring: &VringRwLock, rx_ctrl_vring: &VringRwLock) -> Result<()> {
- log::trace!("process_ctrl_tx_queue");
+ fn process_ctrl_tx_queue(&mut self, vring: &VringRwLock) -> Result<()> {
let requests: Vec<_> = vring
.get_mut()
.get_queue_mut()
@@ -542,113 +426,257 @@ impl VhostUserConsoleBackend {
.map_err(|_| Error::DescriptorNotFound)?
.collect();
- if self.process_ctrl_tx_requests(requests, vring, rx_ctrl_vring)? {
+ if self.process_ctrl_tx_requests(requests, vring)? {
// Send notification once all the requests are processed
vring
.signal_used_queue()
.map_err(|_| Error::NotificationFailed)?;
}
- Ok(())
+ Ok(())
}
/// Set self's VringWorker.
pub(crate) fn set_vring_worker(
- &self,
- vring_worker: &Arc<VringEpollHandler<Arc<RwLock<VhostUserConsoleBackend>>, VringRwLock, ()>>,
+ &self,
+ vring_worker: &Arc<VringEpollHandler<Arc<RwLock<VhostUserConsoleBackend>>>>,
) {
- let rx_event_fd = self.rx_event.as_raw_fd();
- vring_worker
- .register_listener(
- rx_event_fd,
- EventSet::IN,
- u64::from(BACKEND_EFD))
+ let rx_event_fd = self.rx_event.as_raw_fd();
+ vring_worker
+ .register_listener(rx_event_fd, EventSet::IN, u64::from(BACKEND_RX_EFD))
.unwrap();
- let rx_ctrl_event_fd = self.rx_ctrl_event.as_raw_fd();
- vring_worker
+ let rx_ctrl_event_fd = self.rx_ctrl_event.as_raw_fd();
+ vring_worker
.register_listener(
- rx_ctrl_event_fd,
- EventSet::IN,
- u64::from(BACKEND_RX_EFD))
+ rx_ctrl_event_fd,
+ EventSet::IN,
+ u64::from(BACKEND_CTRL_RX_EFD),
+ )
.unwrap();
}
+ pub(crate) fn start_tcp_console_thread(
+ vhu_console: &Arc<RwLock<VhostUserConsoleBackend>>,
+ tcplisener_str: String,
+ ) -> JoinHandle<Result<()>> {
+ let vhu_console = Arc::clone(vhu_console);
+ spawn(move || {
+ loop {
+ let ready = vhu_console.read().unwrap().ready_to_write;
+ let exit = vhu_console.read().unwrap().controller.read().unwrap().exit;
+
+ if exit {
+ trace!("Thread exits!");
+ break;
+ } else if ready {
+ let listener = match TcpListener::bind(tcplisener_str.clone()) {
+ Ok(listener) => listener,
+ Err(e) => {
+ eprintln!("Failed to bind to {}: {}", tcplisener_str, e);
+ return Ok(());
+ }
+ };
+ listener.set_nonblocking(true).expect("Non-blocking error");
+
+ println!("Server listening on address: {}", tcplisener_str.clone());
+ for stream in listener.incoming() {
+ match stream {
+ Ok(mut stream) => {
+ trace!("New connection");
+ stream.set_nonblocking(true).expect("Non-blocking error");
+
+ let mut buffer = [0; 1024];
+ loop {
+ let exit =
+ vhu_console.read().unwrap().controller.read().unwrap().exit;
+ if exit {
+ trace!("Thread exits!");
+ return Ok(());
+ }
+ // Write to the stream
+ if vhu_console.read().unwrap().output_queue.size() > 0 {
+ let byte_stream = vhu_console
+ .write()
+ .unwrap()
+ .output_queue
+ .remove()
+ .expect("Error removing element from output queue")
+ .into_bytes();
+ if let Err(e) = stream.write_all(&byte_stream) {
+ eprintln!("Error writing to stream: {}", e);
+ }
+ }
+ match stream.read(&mut buffer) {
+ Ok(bytes_read) => {
+ if bytes_read == 0 {
+ println!("Close connection");
+ break;
+ }
+ trace!(
+ "Received: {}",
+ String::from_utf8_lossy(&buffer[..bytes_read])
+ );
+ let input_buffer =
+ String::from_utf8_lossy(&buffer[..bytes_read])
+ .to_string();
+ vhu_console
+ .write()
+ .unwrap()
+ .rx_data_fifo
+ .add(input_buffer)
+ .unwrap();
+ vhu_console.write().unwrap().rx_event.write(1).unwrap();
+ }
+ Err(ref e) if e.kind() == io::ErrorKind::WouldBlock => {
+ continue;
+ }
+ Err(ref e)
+ if e.kind() == io::ErrorKind::BrokenPipe
+ || e.kind() == io::ErrorKind::ConnectionReset =>
+ {
+ trace!("Stream has been closed.");
+ break;
+ }
+ Err(e) => {
+ eprintln!("Error reading from socket: {}", e);
+ }
+ }
+ }
+ }
+ Err(ref e) if e.kind() == io::ErrorKind::WouldBlock => {
+ let exit =
+ vhu_console.read().unwrap().controller.read().unwrap().exit;
+ if exit {
+ trace!("Thread exits!");
+ return Ok(());
+ }
+ continue;
+ }
+ Err(e) => {
+ eprintln!("Error accepting connection: {}", e);
+ break;
+ }
+ }
+ }
+ }
+ }
+ Ok(())
+ })
+ }
+
/// Start console thread.
pub(crate) fn start_console_thread(
- vhu_console: &Arc<RwLock<VhostUserConsoleBackend>>,
- ) {
+ vhu_console: &Arc<RwLock<VhostUserConsoleBackend>>,
+ ) -> JoinHandle<Result<()>> {
+ let vhu_console = Arc::clone(vhu_console);
+
+ let exit_eventfd = vhu_console.read().unwrap().exit_event.as_raw_fd();
+ // Spawn a new thread to handle input.
+ spawn(move || {
+ let term = Term::stdout();
+ let mut fdset = FdSet::new();
+ fdset.insert(term.as_raw_fd());
+ fdset.insert(exit_eventfd);
+ let max_fd = fdset.highest().expect("Failed to read fdset!") + 1;
- let vhu_console = Arc::clone(&vhu_console);
- print!("Enter text and press Enter: ");
-
- // Spawn a new thread to handle input.
- spawn( move || {
- loop {
+ loop {
let ready = vhu_console.read().unwrap().ready_to_write;
- if ready {
-
- let term = Term::stdout();
- let character = term.read_char().unwrap();
- log::trace!("You entered: {}", character);
+ let exit = vhu_console.read().unwrap().controller.read().unwrap().exit;
- // Pass the data to vhu_console and trigger an EventFd
- vhu_console.write().unwrap().output_buffer = character.to_string();
- vhu_console.write().unwrap().rx_event.write(1).unwrap();
+ if exit {
+ trace!("Exit!");
+ break;
+ } else if ready {
+ let mut fdset_clone = fdset;
+ enable_raw_mode().expect("Raw mode error");
+
+ match select(Some(max_fd), Some(&mut fdset_clone), None, None, None) {
+ Ok(_num_fds) => {
+ let exit = vhu_console.read().unwrap().controller.read().unwrap().exit;
+ if (fdset_clone.contains(exit_eventfd)) && exit {
+ trace!("Exit!");
+ break;
+ }
+
+ if fdset_clone.contains(term.as_raw_fd()) {
+ if let Some(character) = match term.read_key().unwrap() {
+ Key::Char(character) => Some(character),
+ Key::Enter => Some('\n'),
+ Key::Tab => Some('\t'),
+ Key::Backspace => Some('\u{8}'),
+ _ => None,
+ } {
+ // Pass the data to vhu_console and trigger an EventFd
+ let input_buffer = character.to_string();
+ vhu_console
+ .write()
+ .unwrap()
+ .rx_data_fifo
+ .add(input_buffer)
+ .unwrap();
+ vhu_console.write().unwrap().rx_event.write(1).unwrap();
+ }
+ }
+ }
+ Err(e) => {
+ eprintln!("Error in select: {}", e);
+ break;
+ }
+ }
}
- }
- });
+ }
+
+ disable_raw_mode().expect("Raw mode error");
+ Ok(())
+ })
+ }
+ pub fn kill_console_thread(&self) {
+ trace!("Kill thread");
+ self.controller.write().unwrap().exit = true;
+ self.exit_event.write(1).unwrap();
}
}
/// VhostUserBackendMut trait methods
-impl VhostUserBackendMut<VringRwLock, ()>
- for VhostUserConsoleBackend
-{
+impl VhostUserBackendMut for VhostUserConsoleBackend {
+ type Vring = VringRwLock;
+ type Bitmap = ();
+
fn num_queues(&self) -> usize {
- log::trace!("num_queues: {:?}", NUM_QUEUES);
NUM_QUEUES
}
fn max_queue_size(&self) -> usize {
- log::trace!("max_queue_size: {:?}", QUEUE_SIZE);
QUEUE_SIZE
}
fn features(&self) -> u64 {
- // this matches the current libvhost defaults except VHOST_F_LOG_ALL
- let features = 1 << VIRTIO_F_VERSION_1
+ 1 << VIRTIO_F_VERSION_1
| 1 << VIRTIO_F_NOTIFY_ON_EMPTY
| 1 << VIRTIO_RING_F_EVENT_IDX
- | 1 << VIRTIO_CONSOLE_F_EMERG_WRITE
| 1 << VIRTIO_RING_F_INDIRECT_DESC
- | 1 << VIRTIO_CONSOLE_F_MULTIPORT // This could be disabled
- | VhostUserVirtioFeatures::PROTOCOL_FEATURES.bits();
-
- log::trace!("vhu_can->features: {:x}", features);
- features
+ | 1 << VIRTIO_CONSOLE_F_MULTIPORT
+ | VhostUserVirtioFeatures::PROTOCOL_FEATURES.bits()
}
- fn acked_features(&mut self, _features: u64) {
- log::trace!("\nacked_features: 0x{:x}\n", _features);
- self.acked_features = _features;
- }
+ fn acked_features(&mut self, features: u64) {
+ self.acked_features = features;
+ }
fn protocol_features(&self) -> VhostUserProtocolFeatures {
- let protocol_features = VhostUserProtocolFeatures::MQ
+ VhostUserProtocolFeatures::MQ
| VhostUserProtocolFeatures::CONFIG
- | VhostUserProtocolFeatures::REPLY_ACK;
-
- log::trace!("protocol_features: {:x}", protocol_features);
- protocol_features
+ | VhostUserProtocolFeatures::REPLY_ACK
}
fn get_config(&self, offset: u32, size: u32) -> Vec<u8> {
// SAFETY: The layout of the structure is fixed and can be initialized by
// reading its content from byte array.
- log::trace!("vhu_can->get_config");
unsafe {
from_raw_parts(
- self.controller.write().unwrap()
+ self.controller
+ .write()
+ .unwrap()
.config()
.as_slice()
.as_ptr()
@@ -660,11 +688,10 @@ impl VhostUserBackendMut<VringRwLock, ()>
}
fn set_event_idx(&mut self, enabled: bool) {
- dbg!(self.event_idx = enabled);
+ self.event_idx = enabled;
}
fn update_memory(&mut self, mem: GuestMemoryAtomic<GuestMemoryMmap>) -> IoResult<()> {
- log::trace!("update_memory\n");
self.mem = Some(mem);
Ok(())
}
@@ -672,93 +699,81 @@ impl VhostUserBackendMut<VringRwLock, ()>
fn handle_event(
&mut self,
device_event: u16,
- evset: EventSet,
+ _evset: EventSet,
vrings: &[VringRwLock],
_thread_id: usize,
- ) -> IoResult<bool> {
- log::trace!("\nhandle_event:");
-
- if device_event == RX_QUEUE {
- log::trace!("RX_QUEUE\n");
- return Ok(false);
- };
-
- if device_event == CTRL_RX_QUEUE {
- log::trace!("CTRL_RX_QUEUE\n");
- if !self.ready {
- return Ok(false);
- }
- };
-
- let vring = if device_event == BACKEND_EFD {
- log::trace!("BACKEND_EFD\n");
- &vrings[RX_QUEUE as usize]
- } else if device_event == BACKEND_RX_EFD {
- log::trace!("BACKEND_RX_EFD\n");
- &vrings[CTRL_RX_QUEUE as usize]
- } else {
- &vrings[device_event as usize]
- };
+ ) -> IoResult<()> {
+ if device_event == RX_QUEUE {
+ // Check if there are any available data
+ if self.rx_data_fifo.size() == 0 {
+ return Ok(());
+ }
+ };
+
+ if device_event == CTRL_RX_QUEUE {
+ // Check if there are any available data and the device is ready
+ if (!self.ready) || (self.rx_ctrl_fifo.size() == 0) {
+ return Ok(());
+ }
+ };
+
+ let vring = if device_event == BACKEND_RX_EFD {
+ &vrings[RX_QUEUE as usize]
+ } else if device_event == BACKEND_CTRL_RX_EFD {
+ &vrings[CTRL_RX_QUEUE as usize]
+ } else {
+ &vrings[device_event as usize]
+ };
if self.event_idx {
- // vm-virtio's Queue implementation only checks avail_index
- // once, so to properly support EVENT_IDX we need to keep
- // calling process_request_queue() until it stops finding
- // new requests on the queue.
loop {
vring.disable_notification().unwrap();
match device_event {
+ RX_QUEUE => self.process_rx_queue(vring),
TX_QUEUE => {
- self.ready_to_write = true;
+ self.ready_to_write = true;
self.process_tx_queue(vring)
- },
+ }
CTRL_RX_QUEUE => self.process_ctrl_rx_queue(vring),
- CTRL_TX_QUEUE => {
- let rx_ctrl_vring = &vrings[CTRL_RX_QUEUE as usize];
- self.process_ctrl_tx_queue(vring, rx_ctrl_vring)
- },
- BACKEND_EFD => {
- let _ = self.rx_event.read();
- self.process_rx_queue(vring)
- },
+ CTRL_TX_QUEUE => self.process_ctrl_tx_queue(vring),
BACKEND_RX_EFD => {
- let _ = self.rx_ctrl_event.read();
- self.process_ctrl_rx_queue(vring)
- },
- _ => Err(Error::HandleEventUnknown.into()),
+ let _ = self.rx_event.read();
+ self.process_rx_queue(vring)
+ }
+ BACKEND_CTRL_RX_EFD => {
+ let _ = self.rx_ctrl_event.read();
+ self.process_ctrl_rx_queue(vring)
+ }
+ _ => Err(Error::HandleEventUnknown),
}?;
if !vring.enable_notification().unwrap() {
break;
}
}
- } else {
- // Without EVENT_IDX, a single call is enough.
+ } else {
match device_event {
+ RX_QUEUE => self.process_rx_queue(vring),
TX_QUEUE => {
- self.ready_to_write = true;
+ self.ready_to_write = true;
self.process_tx_queue(vring)
- },
+ }
CTRL_RX_QUEUE => self.process_ctrl_rx_queue(vring),
- CTRL_TX_QUEUE => {
- let rx_ctrl_vring = &vrings[CTRL_RX_QUEUE as usize];
- self.process_ctrl_tx_queue(vring, rx_ctrl_vring)
- },
- BACKEND_EFD => {
- let _ = self.rx_event.read();
- self.process_rx_queue(vring)
- },
+ CTRL_TX_QUEUE => self.process_ctrl_tx_queue(vring),
BACKEND_RX_EFD => {
- let _ = self.rx_ctrl_event.read();
- self.process_ctrl_rx_queue(vring)
- },
- _ => Err(Error::HandleEventUnknown.into()),
+ let _ = self.rx_event.read();
+ self.process_rx_queue(vring)
+ }
+ BACKEND_CTRL_RX_EFD => {
+ let _ = self.rx_ctrl_event.read();
+ self.process_ctrl_rx_queue(vring)
+ }
+ _ => Err(Error::HandleEventUnknown),
}?;
}
- Ok(false)
+ Ok(())
}
fn exit_event(&self, _thread_index: usize) -> Option<EventFd> {
- dbg!("exit_event\n");
self.exit_event.try_clone().ok()
}
}
@@ -766,29 +781,503 @@ impl VhostUserBackendMut<VringRwLock, ()>
#[cfg(test)]
mod tests {
use super::*;
+ use virtio_bindings::virtio_ring::{VRING_DESC_F_NEXT, VRING_DESC_F_WRITE};
+ use virtio_queue::{mock::MockSplitQueue, Descriptor, Queue};
+ use vm_memory::{Bytes, GuestAddress, GuestMemoryAtomic, GuestMemoryMmap};
+
+ #[test]
+ fn test_vhost_user_console_backend_creation() {
+ let console_controller = Arc::new(RwLock::new(ConsoleController::new(BackendType::Nested)));
+ let vhost_user_console_backend = VhostUserConsoleBackend::new(console_controller)
+ .expect("Failed create vhuconsole backend");
+
+ assert_eq!(vhost_user_console_backend.acked_features, 0);
+ assert!(!vhost_user_console_backend.event_idx);
+ assert!(!vhost_user_console_backend.ready);
+ assert!(!vhost_user_console_backend.ready_to_write);
+ }
+
+ #[test]
+ fn test_virtio_console_empty_handle_request() {
+ let console_controller = Arc::new(RwLock::new(ConsoleController::new(BackendType::Nested)));
+ let mut vu_console_backend = VhostUserConsoleBackend::new(console_controller)
+ .expect("Failed create vhuconsole backend");
+
+ // Artificial memory
+ let mem = GuestMemoryAtomic::new(
+ GuestMemoryMmap::<()>::from_ranges(&[(GuestAddress(0), 0x1000)]).unwrap(),
+ );
+
+ // Update memory
+ vu_console_backend.update_memory(mem.clone()).unwrap();
+
+ // Artificial Vring
+ let vring = VringRwLock::new(mem, 0x1000).unwrap();
+ vring.set_queue_info(0x100, 0x200, 0x300).unwrap();
+ vring.set_queue_ready(true);
+ let list_vrings = [vring.clone(), vring.clone(), vring.clone(), vring.clone()];
+
+ vu_console_backend
+ .handle_event(RX_QUEUE, EventSet::IN, &list_vrings, 0)
+ .unwrap();
+
+ vu_console_backend
+ .handle_event(TX_QUEUE, EventSet::IN, &list_vrings, 0)
+ .unwrap();
+
+ vu_console_backend
+ .handle_event(CTRL_RX_QUEUE, EventSet::IN, &list_vrings, 0)
+ .unwrap();
+
+ vu_console_backend
+ .handle_event(CTRL_TX_QUEUE, EventSet::IN, &list_vrings, 0)
+ .unwrap();
+
+ vu_console_backend
+ .handle_event(BACKEND_RX_EFD, EventSet::IN, &list_vrings, 0)
+ .unwrap();
+
+ vu_console_backend
+ .handle_event(BACKEND_CTRL_RX_EFD, EventSet::IN, &list_vrings, 0)
+ .unwrap();
+ }
+
+ #[test]
+ fn test_virtio_console_empty_requests() {
+ let console_controller = Arc::new(RwLock::new(ConsoleController::new(BackendType::Nested)));
+ let mut vu_console_backend = VhostUserConsoleBackend::new(console_controller)
+ .expect("Failed create vhuconsole backend");
+
+ // Artificial memory
+ let mem = GuestMemoryAtomic::new(
+ GuestMemoryMmap::<()>::from_ranges(&[(GuestAddress(0), 0x1000)]).unwrap(),
+ );
+
+ // Artificial Vring
+ let vring = VringRwLock::new(mem.clone(), 0x1000).unwrap();
+
+ // Empty descriptor chain should be ignored
+ assert!(vu_console_backend
+ .process_rx_requests(Vec::<ConsoleDescriptorChain>::new(), &vring)
+ .is_ok());
+ assert!(vu_console_backend
+ .process_tx_requests(Vec::<ConsoleDescriptorChain>::new(), &vring)
+ .is_ok());
+ assert!(vu_console_backend
+ .process_ctrl_rx_requests(Vec::<ConsoleDescriptorChain>::new(), &vring)
+ .is_ok());
+ assert!(vu_console_backend
+ .process_ctrl_tx_requests(Vec::<ConsoleDescriptorChain>::new(), &vring)
+ .is_ok());
+ }
+
+ fn build_desc_chain(
+ mem: &GuestMemoryMmap,
+ count: u16,
+ flags: Vec<u16>,
+ len: u32,
+ ) -> ConsoleDescriptorChain {
+ let vq = MockSplitQueue::new(mem, 16);
+ let mut desc_vec = Vec::new();
+
+ //Create a descriptor chain with @count descriptors.
+ for i in 0..count {
+ let desc_flags = if i < count - 1 {
+ flags[i as usize] | VRING_DESC_F_NEXT as u16
+ } else {
+ flags[i as usize] & !VRING_DESC_F_NEXT as u16
+ };
+
+ let desc = Descriptor::new((0x100 * (i + 1)) as u64, len, desc_flags, i + 1);
+ desc_vec.push(desc);
+ }
+
+ vq.add_desc_chains(&desc_vec, 0).unwrap();
+
+ // Create descriptor chain from pre-filled memory
+ vq.create_queue::<Queue>()
+ .unwrap()
+ .iter(GuestMemoryAtomic::new(mem.clone()).memory())
+ .unwrap()
+ .next()
+ .unwrap()
+ }
#[test]
- fn test_virtio_console_control_byte_valued() {
- let control = VirtioConsoleControl {
- id: Le32::from(1),
- event: Le16::from(2),
- value: Le16::from(3),
+ fn test_virtio_console_ctrl_rx_request() {
+ let console_controller = Arc::new(RwLock::new(ConsoleController::new(BackendType::Nested)));
+ let mut vu_console_backend = VhostUserConsoleBackend::new(console_controller)
+ .expect("Failed create vhuconsole backend");
+
+ // Artificial memory
+ let mem = GuestMemoryMmap::<()>::from_ranges(&[(GuestAddress(0), 0x1000)]).unwrap();
+
+ // Test 1: Empty queue
+ let mem1 = GuestMemoryAtomic::new(mem.clone());
+ let vring = VringRwLock::new(mem1.clone(), 0x1000).unwrap();
+ assert!(vu_console_backend
+ .process_ctrl_rx_requests(vec![], &vring)
+ .unwrap());
+
+ // Test 2: Found no rx elements
+ let desc_chain = build_desc_chain(&mem, 1, vec![0], 0x200);
+ let mem1 = GuestMemoryAtomic::new(mem.clone());
+ let vring = VringRwLock::new(mem1.clone(), 0x1000).unwrap();
+ vu_console_backend.update_memory(mem1).unwrap();
+
+ assert!(!vu_console_backend
+ .process_ctrl_rx_requests(vec![desc_chain], &vring)
+ .unwrap());
+
+ // Test 3: empty queue
+ let desc_chain = build_desc_chain(&mem, 1, vec![VRING_DESC_F_WRITE as u16], 0x200);
+ let mem1 = GuestMemoryAtomic::new(mem.clone());
+ let vring = VringRwLock::new(mem1.clone(), 0x1000).unwrap();
+ vu_console_backend.update_memory(mem1).unwrap();
+ assert!(!vu_console_backend
+ .process_ctrl_rx_requests(vec![desc_chain.clone()], &vring)
+ .unwrap());
+
+ // Test 4: the written desc reply
+ let ctrl_msg = VirtioConsoleControl {
+ id: 0.into(),
+ event: VIRTIO_CONSOLE_PORT_ADD.into(),
+ value: 1.into(),
};
+ let _ = vu_console_backend.rx_ctrl_fifo.add(ctrl_msg);
- let bytes = control.to_le_bytes();
+ assert!(vu_console_backend
+ .process_ctrl_rx_requests(vec![desc_chain.clone()], &vring)
+ .unwrap());
- assert_eq!(bytes.len(), 10);
+ let ctrl_msg_reply = desc_chain
+ .memory()
+ .read_obj::<VirtioConsoleControl>(vm_memory::GuestAddress(0x100_u64))
+ .map_err(|_| Error::DescriptorReadFailed)
+ .unwrap();
+
+ assert_eq!(ctrl_msg.id, ctrl_msg_reply.id);
+ assert_eq!(ctrl_msg.event, ctrl_msg_reply.event);
+ assert_eq!(ctrl_msg.value, ctrl_msg_reply.value);
+
+ // Test 5: if message is VIRTIO_CONSOLE_PORT_NAME
+ let desc_chain = build_desc_chain(&mem, 1, vec![VRING_DESC_F_WRITE as u16], 0x200);
+ let mem1 = GuestMemoryAtomic::new(mem.clone());
+ let vring = VringRwLock::new(mem1.clone(), 0x1000).unwrap();
+ vu_console_backend.update_memory(mem1).unwrap();
+
+ let ctrl_msg = VirtioConsoleControl {
+ id: 0.into(),
+ event: VIRTIO_CONSOLE_PORT_NAME.into(),
+ value: 1.into(),
+ };
+ let _ = vu_console_backend.rx_ctrl_fifo.add(ctrl_msg);
+
+ assert!(vu_console_backend
+ .process_ctrl_rx_requests(vec![desc_chain.clone()], &vring)
+ .unwrap());
}
#[test]
- fn test_vhost_user_console_backend_creation() {
- let console_controller = Arc::new(RwLock::new(ConsoleController::new(String::from("test_console")).unwrap()));
- let vhost_user_console_backend = VhostUserConsoleBackend::new(console_controller).unwrap();
+ fn test_virtio_console_ctrl_tx_request() {
+ let console_controller = Arc::new(RwLock::new(ConsoleController::new(BackendType::Nested)));
+ let mut vu_console_backend = VhostUserConsoleBackend::new(console_controller)
+ .expect("Failed create vhuconsole backend");
+
+ // Artificial memory
+ let mem = GuestMemoryMmap::<()>::from_ranges(&[(GuestAddress(0), 0x1000)]).unwrap();
+
+ // Test 1: Empty queue
+ let mem1 = GuestMemoryAtomic::new(mem.clone());
+ let vring = VringRwLock::new(mem1.clone(), 0x1000).unwrap();
+ assert!(vu_console_backend
+ .process_ctrl_tx_requests(vec![], &vring)
+ .unwrap());
+
+ // Test 2: Found no descriptors
+ let desc_chain = build_desc_chain(&mem, 1, vec![VRING_DESC_F_WRITE as u16], 0x200);
+ let mem1 = GuestMemoryAtomic::new(mem.clone());
+ let vring = VringRwLock::new(mem1.clone(), 0x1000).unwrap();
+ vu_console_backend.update_memory(mem1).unwrap();
+
+ assert_eq!(
+ vu_console_backend
+ .process_ctrl_tx_requests(vec![desc_chain], &vring)
+ .unwrap_err(),
+ Error::DescriptorReadFailed
+ );
+
+ // Test 3: Smaller descriptor len than a console ctrl message
+ let desc_chain = build_desc_chain(&mem, 1, vec![0], 0x2);
+ let mem1 = GuestMemoryAtomic::new(mem.clone());
+ let vring = VringRwLock::new(mem1.clone(), 0x1000).unwrap();
+ vu_console_backend.update_memory(mem1).unwrap();
+ assert_eq!(
+ vu_console_backend
+ .process_ctrl_tx_requests(vec![desc_chain.clone()], &vring)
+ .unwrap_err(),
+ Error::DescriptorReadFailed
+ );
+
+ // Test 4: Complete function successfully -- VIRTIO_CONSOLE_PORT_READY message
+ let desc_chain = build_desc_chain(&mem, 1, vec![0], 0x8);
+ let mem1 = GuestMemoryAtomic::new(mem.clone());
+ let vring = VringRwLock::new(mem1.clone(), 0x1000).unwrap();
+ vu_console_backend.update_memory(mem1).unwrap();
+ assert!(vu_console_backend
+ .process_ctrl_tx_requests(vec![desc_chain.clone()], &vring)
+ .unwrap());
+ }
- assert_eq!(vhost_user_console_backend.acked_features, 0);
- assert_eq!(vhost_user_console_backend.event_idx, false);
- assert_eq!(vhost_user_console_backend.ready, false);
- assert_eq!(vhost_user_console_backend.ready_to_write, false);
- assert_eq!(vhost_user_console_backend.output_buffer, String::new());
+ #[test]
+ fn test_virtio_console_handle_control_msg() {
+ let console_controller = Arc::new(RwLock::new(ConsoleController::new(BackendType::Nested)));
+ let mut vu_console_backend = VhostUserConsoleBackend::new(console_controller)
+ .expect("Failed create vhuconsole backend");
+
+ // Artificial memory & update device's memory
+ let mem = GuestMemoryMmap::<()>::from_ranges(&[(GuestAddress(0), 0x1000)]).unwrap();
+ let mem_1 = GuestMemoryAtomic::new(mem.clone());
+ vu_console_backend.update_memory(mem_1.clone()).unwrap();
+
+ // Test 1: Empty queue
+ let ctrl_msg_1 = VirtioConsoleControl {
+ id: 0.into(),
+ event: VIRTIO_CONSOLE_DEVICE_READY.into(),
+ value: 1.into(),
+ };
+
+ let ctrl_msg_2 = VirtioConsoleControl {
+ id: 0.into(),
+ event: VIRTIO_CONSOLE_PORT_READY.into(),
+ value: 1.into(),
+ };
+
+ let ctrl_msg_3 = VirtioConsoleControl {
+ id: 0.into(),
+ event: VIRTIO_CONSOLE_PORT_OPEN.into(),
+ value: 1.into(),
+ };
+
+ let ctrl_msg_err = VirtioConsoleControl {
+ id: 0.into(),
+ event: 4.into(),
+ value: 1.into(),
+ };
+
+ assert!(vu_console_backend.handle_control_msg(ctrl_msg_3).is_ok());
+
+ assert_eq!(
+ vu_console_backend
+ .handle_control_msg(ctrl_msg_err)
+ .unwrap_err(),
+ Error::HandleEventUnknown
+ );
+
+ assert!(vu_console_backend.handle_control_msg(ctrl_msg_1).is_ok());
+
+ // Update memory
+ let mem_1 = GuestMemoryAtomic::new(mem.clone());
+ vu_console_backend.update_memory(mem_1.clone()).unwrap();
+
+ assert!(vu_console_backend.handle_control_msg(ctrl_msg_2).is_ok());
+ }
+
+ #[test]
+ fn test_virtio_console_tx_request() {
+ let console_controller = Arc::new(RwLock::new(ConsoleController::new(BackendType::Nested)));
+ let mut vu_console_backend = VhostUserConsoleBackend::new(console_controller)
+ .expect("Failed create vhuconsole backend");
+
+ // Artificial memory
+ let mem = GuestMemoryMmap::<()>::from_ranges(&[(GuestAddress(0), 0x1000)]).unwrap();
+
+ // Test 1: Empty queue
+ let mem1 = GuestMemoryAtomic::new(mem.clone());
+ let vring = VringRwLock::new(mem1, 0x1000).unwrap();
+ assert!(vu_console_backend
+ .process_tx_requests(vec![], &vring)
+ .is_ok());
+
+ // Test 2: Empty buffer
+ let desc_chain = build_desc_chain(&mem, 1, vec![0], 0x200);
+ let mem1 = GuestMemoryAtomic::new(mem.clone());
+ let vring = VringRwLock::new(mem1.clone(), 0x1000).unwrap();
+ vu_console_backend.update_memory(mem1).unwrap();
+ assert!(vu_console_backend
+ .process_tx_requests(vec![desc_chain], &vring)
+ .is_ok());
+
+ // Test 3: Fill message to the buffer
+ let desc_chain = build_desc_chain(&mem, 1, vec![0], 0x200);
+ let desc_addr = desc_chain.clone().collect::<Vec<_>>()[0].addr();
+
+ // Build the Vec with the desired string
+ let mut buffer: Vec<u8> = Vec::new();
+ let string_bytes = "Hello!".as_bytes();
+ buffer.extend_from_slice(string_bytes);
+
+ // Write a new buffer into the desc_chain
+ desc_chain.memory().write_slice(&buffer, desc_addr).unwrap();
+
+ // Verify that it is written
+ let mut read_buffer: Vec<u8> = vec![0; 0x200];
+ desc_chain
+ .memory()
+ .read_slice(&mut read_buffer, desc_addr)
+ .expect("Failed to read");
+ let read_buffer: Vec<u8> = read_buffer.iter().take(buffer.len()).copied().collect();
+
+ assert_eq!(
+ String::from_utf8(read_buffer).unwrap(),
+ String::from_utf8(buffer).unwrap()
+ );
+
+ assert!(vu_console_backend
+ .process_tx_requests(vec![desc_chain], &vring)
+ .is_ok());
+ }
+
+ #[test]
+ fn test_virtio_console_tx_request_network() {
+ let console_controller =
+ Arc::new(RwLock::new(ConsoleController::new(BackendType::Network)));
+ let mut vu_console_backend = VhostUserConsoleBackend::new(console_controller)
+ .expect("Failed create vhuconsole backend");
+
+ // Artificial memory
+ let mem = GuestMemoryMmap::<()>::from_ranges(&[(GuestAddress(0), 0x1000)]).unwrap();
+ let desc_chain = build_desc_chain(&mem, 1, vec![0], 0x200);
+ let mem1 = GuestMemoryAtomic::new(mem.clone());
+ let vring = VringRwLock::new(mem1.clone(), 0x1000).unwrap();
+
+ // Test: Fill message to the buffer
+ vu_console_backend.update_memory(mem1).unwrap();
+ let desc_addr = desc_chain.clone().collect::<Vec<_>>()[0].addr();
+
+ // Build the Vec with the desired string
+ let mut buffer: Vec<u8> = Vec::new();
+ let string_bytes = "Hello!".as_bytes();
+ buffer.extend_from_slice(string_bytes);
+
+ // Write a new buffer into the desc_chain
+ desc_chain.memory().write_slice(&buffer, desc_addr).unwrap();
+
+ // Verify that it is written
+ let mut read_buffer: Vec<u8> = vec![0; 0x200];
+ desc_chain
+ .memory()
+ .read_slice(&mut read_buffer, desc_addr)
+ .expect("Failed to read");
+ let read_buffer: Vec<u8> = read_buffer.iter().take(buffer.len()).copied().collect();
+
+ assert_eq!(
+ String::from_utf8(read_buffer).unwrap(),
+ String::from_utf8(buffer).unwrap()
+ );
+
+ assert!(vu_console_backend
+ .process_tx_requests(vec![desc_chain], &vring)
+ .is_ok());
+ }
+
+ #[test]
+ fn test_virtio_console_rx_request() {
+ let console_controller = Arc::new(RwLock::new(ConsoleController::new(BackendType::Nested)));
+ let mut vu_console_backend = VhostUserConsoleBackend::new(console_controller)
+ .expect("Failed create vhuconsole backend");
+
+ // Artificial memory
+ let mem = GuestMemoryMmap::<()>::from_ranges(&[(GuestAddress(0), 0x1000)]).unwrap();
+
+ // Test 1: Empty queue
+ let mem1 = GuestMemoryAtomic::new(mem.clone());
+ let vring = VringRwLock::new(mem1, 0x1000).unwrap();
+ assert!(vu_console_backend
+ .process_rx_requests(vec![], &vring)
+ .is_ok());
+
+ // Test 2: Empty buffer
+ let desc_chain = build_desc_chain(&mem, 1, vec![0], 0x200);
+ let mem1 = GuestMemoryAtomic::new(mem.clone());
+ let vring = VringRwLock::new(mem1.clone(), 0x1000).unwrap();
+ vu_console_backend.update_memory(mem1).unwrap();
+ assert!(!vu_console_backend
+ .process_rx_requests(vec![desc_chain], &vring)
+ .unwrap());
+
+ // Test 3: Fill message to the buffer. The descriptor should be write-only
+ let desc_chain = build_desc_chain(&mem, 1, vec![0], 0x200);
+ let mem1 = GuestMemoryAtomic::new(mem.clone());
+ let vring = VringRwLock::new(mem1.clone(), 0x1000).unwrap();
+ vu_console_backend.update_memory(mem1).unwrap();
+
+ let input_buffer = "Hello!".to_string();
+ let _ = vu_console_backend.rx_data_fifo.add(input_buffer.clone());
+ assert_eq!(
+ vu_console_backend
+ .process_rx_requests(vec![desc_chain], &vring)
+ .unwrap_err(),
+ Error::DescriptorWriteFailed
+ );
+
+ // Test 4: Fill message to the buffer. Everything should work!
+ let desc_chain = build_desc_chain(&mem, 1, vec![VRING_DESC_F_WRITE as u16], 0x200);
+ let mem1 = GuestMemoryAtomic::new(mem.clone());
+ let vring = VringRwLock::new(mem1.clone(), 0x1000).unwrap();
+ vu_console_backend.update_memory(mem1).unwrap();
+
+ let input_buffer = "Hello!".to_string();
+ let _ = vu_console_backend.rx_data_fifo.add(input_buffer.clone());
+ assert!(vu_console_backend
+ .process_rx_requests(vec![desc_chain.clone()], &vring)
+ .unwrap());
+
+ // Test 5: Verify written data
+ let desc_addr = GuestAddress(0x100);
+ let mut read_buffer: Vec<u8> = vec![0; 0x100];
+ desc_chain
+ .memory()
+ .read_slice(&mut read_buffer, desc_addr)
+ .expect("Failed to read");
+
+ let read_buffer: Vec<u8> = read_buffer
+ .iter()
+ .take(input_buffer.len())
+ .copied()
+ .collect();
+
+ assert_eq!(String::from_utf8(read_buffer).unwrap(), input_buffer);
+ }
+
+ #[test]
+ fn test_virtio_console_start_tcp_console_thread() {
+ let console_controller = Arc::new(RwLock::new(ConsoleController::new(BackendType::Nested)));
+ let vu_console_backend = Arc::new(RwLock::new(
+ VhostUserConsoleBackend::new(console_controller)
+ .expect("Failed create vhuconsole backend"),
+ ));
+ let tcp_addr = "127.0.0.1:12345".to_string();
+
+ let read_handle = VhostUserConsoleBackend::start_tcp_console_thread(
+ &vu_console_backend,
+ tcp_addr.clone(),
+ );
+ vu_console_backend.read().unwrap().kill_console_thread();
+ assert!(read_handle.join().is_ok());
+ }
+
+ #[test]
+ fn test_virtio_console_start_nested_console_thread() {
+ let console_controller = Arc::new(RwLock::new(ConsoleController::new(BackendType::Nested)));
+ let vu_console_backend = Arc::new(RwLock::new(
+ VhostUserConsoleBackend::new(console_controller)
+ .expect("Failed create vhuconsole backend"),
+ ));
+
+ let read_handle = VhostUserConsoleBackend::start_console_thread(&vu_console_backend);
+
+ vu_console_backend.read().unwrap().kill_console_thread();
+ assert!(read_handle.join().is_ok());
}
}
diff --git a/meta-egvirt/recipes-extended/vhost-device-console/vhost-device-console-0.1.0/src/virtio_console.rs b/meta-egvirt/recipes-extended/vhost-device-console/vhost-device-console-0.1.0/src/virtio_console.rs
new file mode 100755
index 00000000..323473f6
--- /dev/null
+++ b/meta-egvirt/recipes-extended/vhost-device-console/vhost-device-console-0.1.0/src/virtio_console.rs
@@ -0,0 +1,60 @@
+// Console virtio bindings
+//
+// Copyright 2023-2024 VIRTUAL OPEN SYSTEMS SAS. All Rights Reserved.
+// Timos Ampelikiotis <t.ampelikiotis@virtualopensystems.com>
+//
+// SPDX-License-Identifier: Apache-2.0 or BSD-3-Clause
+
+use vm_memory::{ByteValued, Le16, Le32};
+
+/// Feature bit numbers
+#[allow(dead_code)]
+pub const VIRTIO_CONSOLE_F_SIZE: u16 = 0;
+pub const VIRTIO_CONSOLE_F_MULTIPORT: u16 = 1;
+#[allow(dead_code)]
+pub const VIRTIO_CONSOLE_F_EMERG_WRITE: u16 = 2;
+
+/// Console virtio control messages
+pub const VIRTIO_CONSOLE_DEVICE_READY: u16 = 0;
+pub const VIRTIO_CONSOLE_PORT_ADD: u16 = 1;
+#[allow(dead_code)]
+pub const VIRTIO_CONSOLE_PORT_REMOVE: u16 = 2;
+pub const VIRTIO_CONSOLE_PORT_READY: u16 = 3;
+pub const VIRTIO_CONSOLE_CONSOLE_PORT: u16 = 4;
+#[allow(dead_code)]
+pub const VIRTIO_CONSOLE_RESIZE: u16 = 5;
+pub const VIRTIO_CONSOLE_PORT_OPEN: u16 = 6;
+pub const VIRTIO_CONSOLE_PORT_NAME: u16 = 7;
+
+/// Virtio Console Config
+#[derive(Copy, Clone, Debug, Default, PartialEq)]
+#[repr(C)]
+pub(crate) struct VirtioConsoleConfig {
+ pub cols: Le16,
+ pub rows: Le16,
+ pub max_nr_ports: Le32,
+ pub emerg_wr: Le32,
+}
+
+// SAFETY: The layout of the structure is fixed and can be initialized by
+// reading its content from byte array.
+unsafe impl ByteValued for VirtioConsoleConfig {}
+
+#[derive(Copy, Clone, Debug, Default, PartialEq)]
+#[repr(C)]
+pub(crate) struct VirtioConsoleControl {
+ pub id: Le32,
+ pub event: Le16,
+ pub value: Le16,
+}
+
+impl VirtioConsoleControl {
+ pub fn to_le_bytes(self) -> Vec<u8> {
+ let mut buffer = Vec::new();
+
+ buffer.extend_from_slice(&self.id.to_native().to_le_bytes());
+ buffer.extend_from_slice(&self.event.to_native().to_le_bytes());
+ buffer.extend_from_slice(&self.value.to_native().to_le_bytes());
+ buffer
+ }
+}