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')
-rw-r--r--meta-egvirt/recipes-extended/vhost-device-console/vhost-device-console-0.1.0/src/backend.rs211
-rw-r--r--meta-egvirt/recipes-extended/vhost-device-console/vhost-device-console-0.1.0/src/console.rs86
-rw-r--r--meta-egvirt/recipes-extended/vhost-device-console/vhost-device-console-0.1.0/src/main.rs18
-rw-r--r--meta-egvirt/recipes-extended/vhost-device-console/vhost-device-console-0.1.0/src/vhu_console.rs794
4 files changed, 1109 insertions, 0 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
new file mode 100644
index 00000000..15b50a38
--- /dev/null
+++ b/meta-egvirt/recipes-extended/vhost-device-console/vhost-device-console-0.1.0/src/backend.rs
@@ -0,0 +1,211 @@
+// VIRTIO CONSOLE Emulation via vhost-user
+//
+// Copyright 2023 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::sync::{Arc, RwLock};
+use std::thread::{spawn, JoinHandle};
+
+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::vhu_console::VhostUserConsoleBackend;
+
+pub(crate) type Result<T> = std::result::Result<T, Error>;
+
+#[derive(Debug, ThisError)]
+/// Errors related to low level Console helpers
+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,
+}
+
+#[derive(PartialEq, Debug)]
+struct ConsoleConfiguration {
+ socket_path: String,
+ socket_count: u32,
+ console_path: String,
+}
+
+impl TryFrom<ConsoleArgs> for ConsoleConfiguration {
+ type Error = Error;
+
+ fn try_from(args: ConsoleArgs) -> Result<Self> {
+
+ if args.socket_count == 0 {
+ return Err(Error::SocketCountInvalid(0));
+ }
+
+ let console_path = args.console_path.trim().to_string();
+
+ Ok(ConsoleConfiguration {
+ socket_path: args.socket_path,
+ socket_count: args.socket_count,
+ console_path,
+ })
+ }
+}
+
+fn start_backend(args: ConsoleArgs) -> Result<()> {
+
+ println!("start_backend function!\n");
+
+ let config = ConsoleConfiguration::try_from(args).unwrap();
+ let mut handles = Vec::new();
+
+ for _ in 0..config.socket_count {
+ let socket = config.socket_path.to_owned();
+ let console_path = config.console_path.to_owned();
+
+ 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.
+
+ 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)?,
+ ));
+
+ 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);
+ }
+
+ for handle in handles {
+ handle.join().map_err(|_| Error::FailedJoiningThreads)??;
+ }
+
+ 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::*;
+
+ #[test]
+ fn test_console_configuration_try_from_valid_args() {
+ let args = ConsoleArgs {
+ socket_path: String::from("/path/to/socket"),
+ console_path: String::from("vconsole"),
+ socket_count: 3,
+ };
+
+ let result = ConsoleConfiguration::try_from(args);
+
+ assert!(result.is_ok());
+
+ let config = result.unwrap();
+ assert_eq!(config.socket_path, "/path/to/socket");
+ assert_eq!(config.console_path, "vconsole");
+ assert_eq!(config.socket_count, 3);
+ }
+
+ #[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,
+ };
+
+ let result_invalid_count = ConsoleConfiguration::try_from(args_invalid_count);
+ assert!(result_invalid_count.is_err());
+ }
+
+ #[test]
+ fn test_start_backend_success() {
+ // Test start_backend with valid arguments
+ let args = ConsoleArgs {
+ socket_path: String::from("/path/to/socket"),
+ console_path: String::from("vconsole"),
+ socket_count: 2,
+ };
+
+ let result = start_backend(args);
+
+ assert!(result.is_ok());
+ }
+}
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
new file mode 100644
index 00000000..2e2972b2
--- /dev/null
+++ b/meta-egvirt/recipes-extended/vhost-device-console/vhost-device-console-0.1.0/src/console.rs
@@ -0,0 +1,86 @@
+// CAN backend device
+//
+// Copyright 2023 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 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(Debug)]
+pub(crate) struct ConsoleController {
+ config: VirtioConsoleConfig,
+ pub console_name: String,
+}
+
+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 {
+ config: VirtioConsoleConfig {
+ cols: 20.into(),
+ rows: 20.into(),
+ max_nr_ports: 1.into(),
+ emerg_wr: 64.into(),
+ },
+ console_name,
+ })
+ }
+
+ pub(crate) fn config(&self) -> &VirtioConsoleConfig {
+ log::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
new file mode 100644
index 00000000..cceafb4f
--- /dev/null
+++ b/meta-egvirt/recipes-extended/vhost-device-console/vhost-device-console-0.1.0/src/main.rs
@@ -0,0 +1,18 @@
+// VIRTIO CONSOLE Emulation via vhost-user
+//
+// Copyright 2023 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;
+
+#[cfg(target_env = "gnu")]
+fn main() {
+ backend::console_init()
+}
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
new file mode 100644
index 00000000..ebddb00d
--- /dev/null
+++ b/meta-egvirt/recipes-extended/vhost-device-console/vhost-device-console-0.1.0/src/vhu_console.rs
@@ -0,0 +1,794 @@
+// vhost device console
+//
+// Copyright 2023 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 std::slice::from_raw_parts;
+use std::sync::{Arc, RwLock};
+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::{VhostUserBackendMut, VringRwLock, VringT};
+use virtio_bindings::bindings::virtio_config::{VIRTIO_F_NOTIFY_ON_EMPTY, VIRTIO_F_VERSION_1};
+use virtio_bindings::bindings::virtio_ring::{
+ VIRTIO_RING_F_EVENT_IDX, VIRTIO_RING_F_INDIRECT_DESC,
+};
+use virtio_queue::{DescriptorChain, QueueOwnedT};
+use vm_memory::{
+ ByteValued, Bytes, GuestAddressSpace, GuestMemoryAtomic, GuestMemoryLoadGuard,
+ GuestMemoryMmap, Le16, Le32,
+};
+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;
+
+/// Virtio configuration
+const QUEUE_SIZE: usize = 128;
+const NUM_QUEUES: usize = 4;
+
+/// Queues
+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;
+
+
+/// 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;
+
+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 read failed")]
+ DescriptorReadFailed,
+ #[error("Descriptor write failed")]
+ DescriptorWriteFailed,
+ #[error("Failed to create new EventFd")]
+ EventFdFailed,
+ #[error("Failed to remove rx queue")]
+ EmptyQueue,
+}
+
+impl convert::From<Error> for io::Error {
+ fn from(e: Error) -> Self {
+ io::Error::new(io::ErrorKind::Other, e)
+ }
+}
+
+/// 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,
+ event_idx: bool,
+ rx_fifo: Queue<VirtioConsoleControl>,
+ pub(crate) ready: bool,
+ pub(crate) ready_to_write: bool,
+ pub(crate) output_buffer: String,
+ pub(crate) rx_event: EventFd,
+ pub(crate) rx_ctrl_event: EventFd,
+ pub(crate) exit_event: EventFd,
+ mem: Option<GuestMemoryAtomic<GuestMemoryMmap>>,
+}
+
+type ConsoleDescriptorChain = DescriptorChain<GuestMemoryLoadGuard<GuestMemoryMmap<()>>>;
+
+impl VhostUserConsoleBackend {
+ pub(crate) fn new(controller: Arc<RwLock<ConsoleController>>) -> Result<Self> {
+ Ok(VhostUserConsoleBackend {
+ controller: controller,
+ event_idx: false,
+ rx_fifo: Queue::new(),
+ acked_features: 0x0,
+ ready: false,
+ ready_to_write: false,
+ output_buffer: String::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)?,
+ mem: None,
+ })
+ }
+
+ 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 process_rx_requests(
+ &mut self,
+ requests: Vec<ConsoleDescriptorChain>,
+ vring: &VringRwLock
+ ) -> Result<bool> {
+ log::trace!("process_rx_requests");
+
+ 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();
+
+ desc_chain
+ .memory()
+ .write_slice(response.as_bytes(), desc_request.addr())
+ .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");
+ }
+
+ Ok(true)
+ }
+
+ fn process_tx_requests(
+ &self,
+ requests: Vec<ConsoleDescriptorChain>,
+ vring: &VringRwLock
+ ) -> Result<bool> {
+ log::trace!("process_tx_requests");
+
+ 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())
+ .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.
+
+ 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");
+ }
+ }
+
+ Ok(true)
+ }
+
+ fn process_ctrl_rx_requests(
+ &mut self,
+ requests: Vec<ConsoleDescriptorChain>,
+ vring: &VringRwLock,
+ ) -> Result<bool> {
+ log::trace!("process_ctrl_rx_requests");
+
+ if requests.is_empty() {
+ log::trace!("requests.is_empty()");
+ return Ok(true);
+ }
+ log::trace!("\trequests.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 {
+ warn!("Error::UnexpectedDescriptorCount");
+ return Err(Error::UnexpectedDescriptorCount(descriptors.len()));
+ }
+
+ 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 (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(),
+ ));
+ }
+
+ 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");
+ }
+ }
+
+ 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 process_ctrl_tx_requests(
+ &mut self,
+ requests: Vec<ConsoleDescriptorChain>,
+ vring: &VringRwLock,
+ rx_ctrl_vring: &VringRwLock
+ ) -> Result<bool> {
+ log::trace!("process_ctrl_tx_requests");
+
+ if requests.is_empty() {
+ log::trace!("requests.is_empty()");
+ return Ok(true);
+ }
+
+ for desc_chain in requests {
+ let descriptors: Vec<_> = desc_chain.clone().collect();
+
+ if descriptors.len() < 1 {
+ warn!("Error::UnexpectedDescriptorCount");
+ return Err(Error::UnexpectedDescriptorCount(descriptors.len()));
+ }
+
+ log::trace!("descriptors.len(): {:?}", descriptors.len());
+
+ 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(),
+ ));
+ }
+
+ log::trace!("desc_request.len: {}", desc_request.len());
+
+ let mut request = desc_chain
+ .memory()
+ .read_obj::<VirtioConsoleControl>(desc_request.addr())
+ .map_err(|_| Error::DescriptorReadFailed)?;
+
+ self.print_console_frame(request);
+
+ self.handle_control_msg(rx_ctrl_vring, 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.");
+ }
+
+ 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");
+ }
+ }
+
+ Ok(true)
+ }
+
+ /// 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()
+ .iter(self.mem.as_ref().unwrap().memory())
+ .map_err(|_| Error::DescriptorNotFound)?
+ .collect();
+
+ 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
+ })?;
+ }
+ Ok(())
+ }
+
+ /// Process the messages in the vring and dispatch replies
+ fn process_tx_queue(&self, vring: &VringRwLock) -> Result<()> {
+ log::trace!("process_tx_queue");
+ let requests: Vec<_> = vring
+ .get_mut()
+ .get_queue_mut()
+ .iter(self.mem.as_ref().unwrap().memory())
+ .map_err(|_| Error::DescriptorNotFound)?
+ .collect();
+
+ 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
+ })?;
+ }
+
+ 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()
+ .iter(self.mem.as_ref().unwrap().memory())
+ .map_err(|_| Error::DescriptorNotFound)?
+ .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(())
+ }
+
+ /// 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");
+ let requests: Vec<_> = vring
+ .get_mut()
+ .get_queue_mut()
+ .iter(self.mem.as_ref().unwrap().memory())
+ .map_err(|_| Error::DescriptorNotFound)?
+ .collect();
+
+ if self.process_ctrl_tx_requests(requests, vring, rx_ctrl_vring)? {
+ // Send notification once all the requests are processed
+ vring
+ .signal_used_queue()
+ .map_err(|_| Error::NotificationFailed)?;
+ }
+ Ok(())
+ }
+
+ /// Set self's VringWorker.
+ pub(crate) fn set_vring_worker(
+ &self,
+ vring_worker: &Arc<VringEpollHandler<Arc<RwLock<VhostUserConsoleBackend>>, VringRwLock, ()>>,
+ ) {
+ let rx_event_fd = self.rx_event.as_raw_fd();
+ vring_worker
+ .register_listener(
+ rx_event_fd,
+ EventSet::IN,
+ u64::from(BACKEND_EFD))
+ .unwrap();
+
+ 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))
+ .unwrap();
+ }
+
+ /// Start console thread.
+ pub(crate) fn start_console_thread(
+ vhu_console: &Arc<RwLock<VhostUserConsoleBackend>>,
+ ) {
+
+ let vhu_console = Arc::clone(&vhu_console);
+ print!("Enter text and press Enter: ");
+
+ // Spawn a new thread to handle input.
+ spawn( move || {
+ 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);
+
+ // 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();
+ }
+ }
+ });
+ }
+}
+
+/// VhostUserBackendMut trait methods
+impl VhostUserBackendMut<VringRwLock, ()>
+ for VhostUserConsoleBackend
+{
+ 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_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
+ }
+
+ fn acked_features(&mut self, _features: u64) {
+ log::trace!("\nacked_features: 0x{:x}\n", _features);
+ self.acked_features = _features;
+ }
+
+ fn protocol_features(&self) -> VhostUserProtocolFeatures {
+ let protocol_features = VhostUserProtocolFeatures::MQ
+ | VhostUserProtocolFeatures::CONFIG
+ | VhostUserProtocolFeatures::REPLY_ACK;
+
+ log::trace!("protocol_features: {:x}", protocol_features);
+ protocol_features
+ }
+
+ 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()
+ .config()
+ .as_slice()
+ .as_ptr()
+ .offset(offset as isize) as *const _ as *const _,
+ size as usize,
+ )
+ .to_vec()
+ }
+ }
+
+ fn set_event_idx(&mut self, enabled: bool) {
+ dbg!(self.event_idx = enabled);
+ }
+
+ fn update_memory(&mut self, mem: GuestMemoryAtomic<GuestMemoryMmap>) -> IoResult<()> {
+ log::trace!("update_memory\n");
+ self.mem = Some(mem);
+ Ok(())
+ }
+
+ fn handle_event(
+ &mut self,
+ device_event: u16,
+ 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]
+ };
+
+ 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 {
+ TX_QUEUE => {
+ 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)
+ },
+ BACKEND_RX_EFD => {
+ let _ = self.rx_ctrl_event.read();
+ self.process_ctrl_rx_queue(vring)
+ },
+ _ => Err(Error::HandleEventUnknown.into()),
+ }?;
+ if !vring.enable_notification().unwrap() {
+ break;
+ }
+ }
+ } else {
+ // Without EVENT_IDX, a single call is enough.
+ match device_event {
+ TX_QUEUE => {
+ 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)
+ },
+ BACKEND_RX_EFD => {
+ let _ = self.rx_ctrl_event.read();
+ self.process_ctrl_rx_queue(vring)
+ },
+ _ => Err(Error::HandleEventUnknown.into()),
+ }?;
+ }
+ Ok(false)
+ }
+
+ fn exit_event(&self, _thread_index: usize) -> Option<EventFd> {
+ dbg!("exit_event\n");
+ self.exit_event.try_clone().ok()
+ }
+}
+
+#[cfg(test)]
+mod tests {
+ use super::*;
+
+ #[test]
+ fn test_virtio_console_control_byte_valued() {
+ let control = VirtioConsoleControl {
+ id: Le32::from(1),
+ event: Le16::from(2),
+ value: Le16::from(3),
+ };
+
+ let bytes = control.to_le_bytes();
+
+ assert_eq!(bytes.len(), 10);
+ }
+
+ #[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();
+
+ 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());
+ }
+}