From 292c1c6c583ad179211f4420676a543afb933b5d Mon Sep 17 00:00:00 2001 From: Andreea Florescu Date: Thu, 17 Sep 2020 13:51:21 +0300 Subject: [PATCH 001/139] Initial commit --- .gitmodules | 3 + CODEOWNERS | 2 + Cargo.toml | 8 ++ LICENSE-APACHE | 202 +++++++++++++++++++++++++++++++++++++++++++ README.md | 41 +++++++++ coverage_config.json | 5 ++ src/lib.rs | 6 ++ 7 files changed, 267 insertions(+) create mode 100644 .gitmodules create mode 100644 CODEOWNERS create mode 100644 Cargo.toml create mode 100644 LICENSE-APACHE create mode 100644 README.md create mode 100644 coverage_config.json create mode 100644 src/lib.rs diff --git a/.gitmodules b/.gitmodules new file mode 100644 index 0000000..bda97eb --- /dev/null +++ b/.gitmodules @@ -0,0 +1,3 @@ +[submodule "rust-vmm-ci"] + path = rust-vmm-ci + url = https://github.com/rust-vmm/rust-vmm-ci.git diff --git a/CODEOWNERS b/CODEOWNERS new file mode 100644 index 0000000..4d96c3f --- /dev/null +++ b/CODEOWNERS @@ -0,0 +1,2 @@ +# Add the list of code owners here (using their GitHub username) +* gatekeeper-PullAssigner diff --git a/Cargo.toml b/Cargo.toml new file mode 100644 index 0000000..0cebe72 --- /dev/null +++ b/Cargo.toml @@ -0,0 +1,8 @@ +[package] +name = "crate-template" +version = "0.1.0" +authors = [TODO] +license = "Apache-2.0 OR BSD-3-Clause" +edition = "2018" + +[dependencies] diff --git a/LICENSE-APACHE b/LICENSE-APACHE new file mode 100644 index 0000000..d645695 --- /dev/null +++ b/LICENSE-APACHE @@ -0,0 +1,202 @@ + + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. diff --git a/README.md b/README.md new file mode 100644 index 0000000..d2af0db --- /dev/null +++ b/README.md @@ -0,0 +1,41 @@ +# Crate Name + +## Design + +TODO: This section should have a high-level design of the crate. + +Some questions that might help in writing this section: +- What is the purpose of this crate? +- What are the main components of the crate? How do they interact which each + other? + +## Usage + +TODO: This section describes how the crate is used. + +Some questions that might help in writing this section: +- What traits do users need to implement? +- Does the crate have any default/optional features? What is each feature + doing? +- Is this crate used by other rust-vmm components? If yes, how? + +## Examples + +TODO: Usage examples. + +```rust +use my_crate; + +... +``` + +## License + +**!!!NOTICE**: The BSD-3-Clause license is not included in this template. +The license needs to be manually added because the text of the license file +also includes the copyright. The copyright can be different for different +crates. If the crate contains code from CrosVM, the crate must add the +CrosVM copyright which can be found +[here](https://chromium.googlesource.com/chromiumos/platform/crosvm/+/master/LICENSE). +For crates developed from scratch, the copyright is different and depends on +the contributors. diff --git a/coverage_config.json b/coverage_config.json new file mode 100644 index 0000000..6637851 --- /dev/null +++ b/coverage_config.json @@ -0,0 +1,5 @@ +{ + "coverage_score": 90, + "exclude_path": "", + "crate_features": "" +} diff --git a/src/lib.rs b/src/lib.rs new file mode 100644 index 0000000..845a298 --- /dev/null +++ b/src/lib.rs @@ -0,0 +1,6 @@ +#![deny(missing_docs)] +//! Dummy crate needs high-level documentation. +/// Dummy public function needs documentation. +pub fn it_works() { + assert!(true); +} From 86a7bd4351568a482bc4421235d9b9d13ba5c7f3 Mon Sep 17 00:00:00 2001 From: Sebastien Boeuf Date: Wed, 11 Sep 2019 12:22:44 -0700 Subject: [PATCH 002/139] Add vhost_user_backend crate The purpose of this new crate is to provide a common library to all vhost-user backend implementations. The more is handled by this library, the less duplication will need to happen in each vhost-user daemon. This crate relies a lot on vhost_rs, vm-memory and vm-virtio crates. Signed-off-by: Sebastien Boeuf --- src/lib.rs | 782 ++++++++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 777 insertions(+), 5 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index 845a298..4abd7d9 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,6 +1,778 @@ -#![deny(missing_docs)] -//! Dummy crate needs high-level documentation. -/// Dummy public function needs documentation. -pub fn it_works() { - assert!(true); +// Copyright 2019 Intel Corporation. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright 2019 Alibaba Cloud Computing. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 + +use std::fs::File; +use std::io; +use std::num::Wrapping; +use std::os::unix::io::{AsRawFd, FromRawFd, RawFd}; +use std::result; +use std::sync::{Arc, Mutex}; +use std::thread; +use vhost_rs::vhost_user::message::{ + VhostUserConfigFlags, VhostUserMemoryRegion, VhostUserProtocolFeatures, + VhostUserVirtioFeatures, VhostUserVringAddrFlags, VhostUserVringState, + VHOST_USER_CONFIG_OFFSET, VHOST_USER_CONFIG_SIZE, +}; +use vhost_rs::vhost_user::{ + Error as VhostUserError, Result as VhostUserResult, SlaveReqHandler, VhostUserSlaveReqHandler, +}; +use vm_memory::guest_memory::FileOffset; +use vm_memory::{GuestAddress, GuestMemoryMmap}; +use vm_virtio::{DescriptorChain, Queue}; +use vmm_sys_util::eventfd::EventFd; + +#[derive(Debug)] +/// Errors related to vhost-user daemon. +pub enum Error { + /// Failed creating vhost-user slave handler. + CreateSlaveReqHandler(VhostUserError), + /// Failed starting daemon thread. + StartDaemon(io::Error), + /// Failed waiting for daemon thread. + WaitDaemon(std::boxed::Box), + /// Failed handling a vhost-user request. + HandleRequest(VhostUserError), + /// Could not find the mapping from memory regions. + MissingMemoryMapping, + /// Failed to create epoll file descriptor. + EpollCreateFd(io::Error), + /// Failed to add a file descriptor to the epoll handler. + EpollCtl(io::Error), + /// Failed while waiting for events. + EpollWait(io::Error), + /// Failed to signal used queue. + SignalUsedQueue(io::Error), +} + +/// Result of vhost-user daemon operations. +pub type Result = std::result::Result; + +/// This trait must be implemented by the caller in order to provide backend +/// specific implementation. +pub trait VhostUserBackend: Send + Sync + 'static { + /// Number of queues. + fn num_queues(&self) -> usize; + + /// Depth of each queue. + fn max_queue_size(&self) -> usize; + + /// Virtio features. + fn features(&self) -> u64; + + /// This function gets called if the backend registered some additional + /// listeners onto specific file descriptors. The library can handle + /// virtqueues on its own, but does not know what to do with events + /// happening on custom listeners. + fn handle_event(&self, device_event: u16, evset: epoll::Events) -> Result; + + /// This function is responsible for the actual processing that needs to + /// happen when one of the virtqueues is available. + fn process_queue( + &self, + q_idx: u16, + avail_desc: &DescriptorChain, + mem: &GuestMemoryMmap, + ) -> Result; + + /// Get virtio device configuration. + fn get_config(&self, offset: u32, size: u32) -> Vec; + + /// Set virtio device configuration. + fn set_config(&self, offset: u32, buf: &[u8]); +} + +/// This structure is the public API the backend is allowed to interact with +/// in order to run a fully functional vhost-user daemon. +pub struct VhostUserDaemon { + name: String, + sock_path: String, + handler: Arc>>, + vring_handler: Arc>>, + main_thread: Option>>, +} + +impl VhostUserDaemon { + /// Create the daemon instance, providing the backend implementation of + /// VhostUserBackend. + /// Under the hood, this will start a dedicated thread responsible for + /// listening onto registered event. Those events can be vring events or + /// custom events from the backend, but they get to be registered later + /// during the sequence. + pub fn new(name: String, sock_path: String, backend: S) -> Result { + let handler = Arc::new(Mutex::new(VhostUserHandler::new(backend))); + let vring_handler = handler.lock().unwrap().get_vring_handler(); + + Ok(VhostUserDaemon { + name, + sock_path, + handler, + vring_handler, + main_thread: None, + }) + } + + /// Connect to the vhost-user socket and run a dedicated thread handling + /// all requests coming through this socket. This runs in an infinite loop + /// that should be terminating once the other end of the socket (the VMM) + /// disconnects. + pub fn start(&mut self) -> Result<()> { + let mut slave_handler = + SlaveReqHandler::connect(self.sock_path.as_str(), self.handler.clone()) + .map_err(Error::CreateSlaveReqHandler)?; + let handle = thread::Builder::new() + .name(self.name.clone()) + .spawn(move || loop { + slave_handler + .handle_request() + .map_err(Error::HandleRequest)?; + }) + .map_err(Error::StartDaemon)?; + + self.main_thread = Some(handle); + + Ok(()) + } + + /// Wait for the thread handling the vhost-user socket connection to + /// terminate. + pub fn wait(&mut self) -> Result<()> { + if let Some(handle) = self.main_thread.take() { + let _ = handle.join().map_err(Error::WaitDaemon)?; + } + Ok(()) + } + + /// Register a custom event only meaningful to the caller. When this event + /// is later triggered, and because only the caller knows what to do about + /// it, the backend implementation of `handle_event` will be called. + /// This lets entire control to the caller about what needs to be done for + /// this special event, without forcing it to run its own dedicated epoll + /// loop for it. + pub fn register_listener( + &self, + fd: RawFd, + ev_type: epoll::Events, + data: u64, + ) -> result::Result<(), io::Error> { + self.vring_handler + .lock() + .unwrap() + .register_listener(fd, ev_type, data) + } + + /// Unregister a custom event. If the custom event is triggered after this + /// function has been called, nothing will happen as it will be removed + /// from the list of file descriptors the epoll loop is listening to. + pub fn unregister_listener( + &self, + fd: RawFd, + ev_type: epoll::Events, + data: u64, + ) -> result::Result<(), io::Error> { + self.vring_handler + .lock() + .unwrap() + .unregister_listener(fd, ev_type, data) + } + + /// Trigger the processing of a virtqueue. This function is meant to be + /// used by the caller whenever it might need some available queues to + /// send data back to the guest. + /// A concrete example is a backend registering one extra listener for + /// data that needs to be sent to the guest. When the associated event + /// is triggered, the backend will be invoked through its `handle_event` + /// implementation. And in this case, the way to handle the event is to + /// call into `process_queue` to let it invoke the backend implementation + /// of `process_queue`. With this twisted trick, all common parts related + /// to the virtqueues can remain part of the library. + pub fn process_queue(&self, q_idx: u16) -> Result<()> { + self.vring_handler.lock().unwrap().process_queue(q_idx) + } +} + +struct AddrMapping { + vmm_addr: u64, + size: u64, +} + +struct Memory { + mappings: Vec, +} + +struct Vring { + queue: Queue, + kick: Option, + call: Option, + err: Option, + started: bool, + enabled: bool, +} + +impl Clone for Vring { + fn clone(&self) -> Self { + let kick = if let Some(c) = &self.kick { + Some(c.try_clone().unwrap()) + } else { + None + }; + + let call = if let Some(c) = &self.call { + Some(c.try_clone().unwrap()) + } else { + None + }; + + let err = if let Some(e) = &self.err { + Some(e.try_clone().unwrap()) + } else { + None + }; + + Vring { + queue: self.queue.clone(), + kick, + call, + err, + started: self.started, + enabled: self.enabled, + } + } +} + +impl Vring { + fn new(max_queue_size: u16) -> Self { + Vring { + queue: Queue::new(max_queue_size), + kick: None, + call: None, + err: None, + started: false, + enabled: false, + } + } +} + +struct VringEpollHandler { + backend: Arc, + vrings: Arc>>, + mem: Option, + epoll_fd: RawFd, +} + +impl VringEpollHandler { + fn update_memory(&mut self, mem: Option) { + self.mem = mem; + } + + fn process_queue(&self, q_idx: u16) -> Result<()> { + let vring = &mut self.vrings.lock().unwrap()[q_idx as usize]; + let mut used_desc_heads = vec![(0, 0); vring.queue.size as usize]; + let mut used_count = 0; + if let Some(mem) = &self.mem { + for avail_desc in vring.queue.iter(&mem) { + let used_len = self + .backend + .process_queue(q_idx, &avail_desc, &mem) + .unwrap(); + + used_desc_heads[used_count] = (avail_desc.index, used_len); + used_count += 1; + } + + for &(desc_index, len) in &used_desc_heads[..used_count] { + vring.queue.add_used(&mem, desc_index, len); + } + } + + if used_count > 0 { + if let Some(call) = &vring.call { + return call.write(1).map_err(Error::SignalUsedQueue); + } + } + + Ok(()) + } + + fn handle_event(&mut self, device_event: u16, evset: epoll::Events) -> Result { + let num_queues = self.vrings.lock().unwrap().len(); + match device_event as usize { + x if x < num_queues => { + if let Some(kick) = &self.vrings.lock().unwrap()[device_event as usize].kick { + kick.read().unwrap(); + } + + self.process_queue(device_event).unwrap(); + + Ok(false) + } + _ => self.backend.handle_event(device_event, evset), + } + } + + fn register_vring_listener(&self, q_idx: usize) -> result::Result<(), io::Error> { + if let Some(fd) = &self.vrings.lock().unwrap()[q_idx].kick { + self.register_listener(fd.as_raw_fd(), epoll::Events::EPOLLIN, q_idx as u64) + } else { + Ok(()) + } + } + + fn unregister_vring_listener(&self, q_idx: usize) -> result::Result<(), io::Error> { + if let Some(fd) = &self.vrings.lock().unwrap()[q_idx].kick { + self.unregister_listener(fd.as_raw_fd(), epoll::Events::EPOLLIN, q_idx as u64) + } else { + Ok(()) + } + } + + fn register_listener( + &self, + fd: RawFd, + ev_type: epoll::Events, + data: u64, + ) -> result::Result<(), io::Error> { + epoll::ctl( + self.epoll_fd, + epoll::ControlOptions::EPOLL_CTL_ADD, + fd, + epoll::Event::new(ev_type, data), + ) + } + + fn unregister_listener( + &self, + fd: RawFd, + ev_type: epoll::Events, + data: u64, + ) -> result::Result<(), io::Error> { + epoll::ctl( + self.epoll_fd, + epoll::ControlOptions::EPOLL_CTL_DEL, + fd, + epoll::Event::new(ev_type, data), + ) + } +} + +struct VringWorker { + handler: Arc>>, +} + +impl VringWorker { + fn run(&self, epoll_fd: RawFd) -> Result<()> { + const EPOLL_EVENTS_LEN: usize = 100; + let mut events = vec![epoll::Event::new(epoll::Events::empty(), 0); EPOLL_EVENTS_LEN]; + + 'epoll: loop { + let num_events = match epoll::wait(epoll_fd, -1, &mut events[..]) { + Ok(res) => res, + Err(e) => { + if e.kind() == io::ErrorKind::Interrupted { + // It's well defined from the epoll_wait() syscall + // documentation that the epoll loop can be interrupted + // before any of the requested events occurred or the + // timeout expired. In both those cases, epoll_wait() + // returns an error of type EINTR, but this should not + // be considered as a regular error. Instead it is more + // appropriate to retry, by calling into epoll_wait(). + continue; + } + return Err(Error::EpollWait(e)); + } + }; + + for event in events.iter().take(num_events) { + let evset = match epoll::Events::from_bits(event.events) { + Some(evset) => evset, + None => { + let evbits = event.events; + println!("epoll: ignoring unknown event set: 0x{:x}", evbits); + continue; + } + }; + + let ev_type = event.data as u16; + + if self.handler.lock().unwrap().handle_event(ev_type, evset)? { + break 'epoll; + } + } + } + + Ok(()) + } +} + +struct VhostUserHandler { + backend: Arc, + vring_handler: Arc>>, + owned: bool, + features_acked: bool, + acked_features: u64, + acked_protocol_features: u64, + num_queues: usize, + max_queue_size: usize, + memory: Option, + vrings: Arc>>, +} + +impl VhostUserHandler { + fn new(backend: S) -> Self { + let num_queues = backend.num_queues(); + let max_queue_size = backend.max_queue_size(); + + let arc_backend = Arc::new(backend); + let vrings = Arc::new(Mutex::new(vec![ + Vring::new(max_queue_size as u16); + num_queues + ])); + // Create the epoll file descriptor + let epoll_fd = epoll::create(true).unwrap(); + + let vring_handler = Arc::new(Mutex::new(VringEpollHandler { + backend: arc_backend.clone(), + vrings: vrings.clone(), + mem: None, + epoll_fd, + })); + let worker = VringWorker { + handler: vring_handler.clone(), + }; + + thread::Builder::new() + .name("vring_epoll_handler".to_string()) + .spawn(move || worker.run(epoll_fd)) + .unwrap(); + + VhostUserHandler { + backend: arc_backend, + vring_handler, + owned: false, + features_acked: false, + acked_features: 0, + acked_protocol_features: 0, + num_queues, + max_queue_size, + memory: None, + vrings, + } + } + + fn get_vring_handler(&self) -> Arc>> { + self.vring_handler.clone() + } + + fn vmm_va_to_gpa(&self, vmm_va: u64) -> Result { + if let Some(memory) = &self.memory { + for mapping in memory.mappings.iter() { + if vmm_va >= mapping.vmm_addr && vmm_va < mapping.vmm_addr + mapping.size { + return Ok(vmm_va - mapping.vmm_addr); + } + } + } + + Err(Error::MissingMemoryMapping) + } +} + +impl VhostUserSlaveReqHandler for VhostUserHandler { + fn set_owner(&mut self) -> VhostUserResult<()> { + if self.owned { + return Err(VhostUserError::InvalidOperation); + } + self.owned = true; + Ok(()) + } + + fn reset_owner(&mut self) -> VhostUserResult<()> { + self.owned = false; + self.features_acked = false; + self.acked_features = 0; + self.acked_protocol_features = 0; + Ok(()) + } + + fn get_features(&mut self) -> VhostUserResult { + Ok(self.backend.features()) + } + + fn set_features(&mut self, features: u64) -> VhostUserResult<()> { + if !self.owned || self.features_acked { + return Err(VhostUserError::InvalidOperation); + } else if (features & !self.backend.features()) != 0 { + return Err(VhostUserError::InvalidParam); + } + + self.acked_features = features; + self.features_acked = true; + + // If VHOST_USER_F_PROTOCOL_FEATURES has not been negotiated, + // the ring is initialized in an enabled state. + // If VHOST_USER_F_PROTOCOL_FEATURES has been negotiated, + // the ring is initialized in a disabled state. Client must not + // pass data to/from the backend until ring is enabled by + // VHOST_USER_SET_VRING_ENABLE with parameter 1, or after it has + // been disabled by VHOST_USER_SET_VRING_ENABLE with parameter 0. + let vring_enabled = + self.acked_features & VhostUserVirtioFeatures::PROTOCOL_FEATURES.bits() == 0; + for vring in self.vrings.lock().unwrap().iter_mut() { + vring.enabled = vring_enabled; + } + + Ok(()) + } + + fn get_protocol_features(&mut self) -> VhostUserResult { + Ok(VhostUserProtocolFeatures::all()) + } + + fn set_protocol_features(&mut self, features: u64) -> VhostUserResult<()> { + // Note: slave that reported VHOST_USER_F_PROTOCOL_FEATURES must + // support this message even before VHOST_USER_SET_FEATURES was + // called. + self.acked_protocol_features = features; + Ok(()) + } + + fn set_mem_table( + &mut self, + ctx: &[VhostUserMemoryRegion], + fds: &[RawFd], + ) -> VhostUserResult<()> { + // We need to create tuple of ranges from the list of VhostUserMemoryRegion + // that we get from the caller. + let mut regions: Vec<(GuestAddress, usize, Option)> = Vec::new(); + let mut mappings: Vec = Vec::new(); + + for (idx, region) in ctx.iter().enumerate() { + let g_addr = GuestAddress(region.guest_phys_addr); + let len = (region.memory_size + region.mmap_offset) as usize; + let file = unsafe { File::from_raw_fd(fds[idx]) }; + let f_off = FileOffset::new(file, 0); + + regions.push((g_addr, len, Some(f_off))); + mappings.push(AddrMapping { + vmm_addr: region.user_addr, + size: region.memory_size + region.mmap_offset, + }); + } + + let mem = GuestMemoryMmap::from_ranges_with_files(regions).unwrap(); + self.vring_handler.lock().unwrap().update_memory(Some(mem)); + self.memory = Some(Memory { mappings }); + + Ok(()) + } + + fn get_queue_num(&mut self) -> VhostUserResult { + Ok(self.num_queues as u64) + } + + fn set_vring_num(&mut self, index: u32, num: u32) -> VhostUserResult<()> { + if index as usize >= self.num_queues || num == 0 || num as usize > self.max_queue_size { + return Err(VhostUserError::InvalidParam); + } + self.vrings.lock().unwrap()[index as usize].queue.size = num as u16; + Ok(()) + } + + fn set_vring_addr( + &mut self, + index: u32, + _flags: VhostUserVringAddrFlags, + descriptor: u64, + used: u64, + available: u64, + _log: u64, + ) -> VhostUserResult<()> { + if index as usize >= self.num_queues { + return Err(VhostUserError::InvalidParam); + } + + if self.memory.is_some() { + let desc_table = self.vmm_va_to_gpa(descriptor).unwrap(); + let avail_ring = self.vmm_va_to_gpa(available).unwrap(); + let used_ring = self.vmm_va_to_gpa(used).unwrap(); + self.vrings.lock().unwrap()[index as usize].queue.desc_table = GuestAddress(desc_table); + self.vrings.lock().unwrap()[index as usize].queue.avail_ring = GuestAddress(avail_ring); + self.vrings.lock().unwrap()[index as usize].queue.used_ring = GuestAddress(used_ring); + Ok(()) + } else { + Err(VhostUserError::InvalidParam) + } + } + + fn set_vring_base(&mut self, index: u32, base: u32) -> VhostUserResult<()> { + self.vrings.lock().unwrap()[index as usize].queue.next_avail = Wrapping(base as u16); + self.vrings.lock().unwrap()[index as usize].queue.next_used = Wrapping(base as u16); + Ok(()) + } + + fn get_vring_base(&mut self, index: u32) -> VhostUserResult { + if index as usize >= self.num_queues { + return Err(VhostUserError::InvalidParam); + } + // Quotation from vhost-user spec: + // Client must start ring upon receiving a kick (that is, detecting + // that file descriptor is readable) on the descriptor specified by + // VHOST_USER_SET_VRING_KICK, and stop ring upon receiving + // VHOST_USER_GET_VRING_BASE. + self.vrings.lock().unwrap()[index as usize].started = false; + self.vring_handler + .lock() + .unwrap() + .unregister_vring_listener(index as usize) + .unwrap(); + + let next_avail = self.vrings.lock().unwrap()[index as usize] + .queue + .next_avail + .0 as u16; + + Ok(VhostUserVringState::new(index, u32::from(next_avail))) + } + + fn set_vring_kick(&mut self, index: u8, fd: Option) -> VhostUserResult<()> { + if index as usize >= self.num_queues { + return Err(VhostUserError::InvalidParam); + } + + if self.vrings.lock().unwrap()[index as usize].kick.is_some() { + // Close file descriptor set by previous operations. + let _ = unsafe { + libc::close( + self.vrings.lock().unwrap()[index as usize] + .kick + .take() + .unwrap() + .as_raw_fd(), + ) + }; + } + self.vrings.lock().unwrap()[index as usize].kick = + Some(unsafe { EventFd::from_raw_fd(fd.unwrap()) });; + + // Quotation from vhost-user spec: + // Client must start ring upon receiving a kick (that is, detecting + // that file descriptor is readable) on the descriptor specified by + // VHOST_USER_SET_VRING_KICK, and stop ring upon receiving + // VHOST_USER_GET_VRING_BASE. + // + // So we should add fd to event monitor(select, poll, epoll) here. + self.vrings.lock().unwrap()[index as usize].started = true; + self.vring_handler + .lock() + .unwrap() + .register_vring_listener(index as usize) + .unwrap(); + + Ok(()) + } + + fn set_vring_call(&mut self, index: u8, fd: Option) -> VhostUserResult<()> { + if index as usize >= self.num_queues { + return Err(VhostUserError::InvalidParam); + } + + if self.vrings.lock().unwrap()[index as usize].call.is_some() { + // Close file descriptor set by previous operations. + let _ = unsafe { + libc::close( + self.vrings.lock().unwrap()[index as usize] + .call + .take() + .unwrap() + .as_raw_fd(), + ) + }; + } + self.vrings.lock().unwrap()[index as usize].call = + Some(unsafe { EventFd::from_raw_fd(fd.unwrap()) }); + + Ok(()) + } + + fn set_vring_err(&mut self, index: u8, fd: Option) -> VhostUserResult<()> { + if index as usize >= self.num_queues { + return Err(VhostUserError::InvalidParam); + } + + if self.vrings.lock().unwrap()[index as usize].err.is_some() { + // Close file descriptor set by previous operations. + let _ = unsafe { + libc::close( + self.vrings.lock().unwrap()[index as usize] + .err + .take() + .unwrap() + .as_raw_fd(), + ) + }; + } + self.vrings.lock().unwrap()[index as usize].err = + Some(unsafe { EventFd::from_raw_fd(fd.unwrap()) }); + + Ok(()) + } + + fn set_vring_enable(&mut self, index: u32, enable: bool) -> VhostUserResult<()> { + // This request should be handled only when VHOST_USER_F_PROTOCOL_FEATURES + // has been negotiated. + if self.acked_features & VhostUserVirtioFeatures::PROTOCOL_FEATURES.bits() == 0 { + return Err(VhostUserError::InvalidOperation); + } else if index as usize >= self.num_queues { + return Err(VhostUserError::InvalidParam); + } + + // Slave must not pass data to/from the backend until ring is + // enabled by VHOST_USER_SET_VRING_ENABLE with parameter 1, + // or after it has been disabled by VHOST_USER_SET_VRING_ENABLE + // with parameter 0. + self.vrings.lock().unwrap()[index as usize].enabled = enable; + + Ok(()) + } + + fn get_config( + &mut self, + offset: u32, + size: u32, + _flags: VhostUserConfigFlags, + ) -> VhostUserResult> { + if self.acked_features & VhostUserProtocolFeatures::CONFIG.bits() == 0 { + return Err(VhostUserError::InvalidOperation); + } else if offset < VHOST_USER_CONFIG_OFFSET + || offset >= VHOST_USER_CONFIG_SIZE + || size > VHOST_USER_CONFIG_SIZE - VHOST_USER_CONFIG_OFFSET + || size + offset > VHOST_USER_CONFIG_SIZE + { + return Err(VhostUserError::InvalidParam); + } + + Ok(self.backend.get_config(offset, size)) + } + + fn set_config( + &mut self, + offset: u32, + buf: &[u8], + _flags: VhostUserConfigFlags, + ) -> VhostUserResult<()> { + let size = buf.len() as u32; + if self.acked_features & VhostUserProtocolFeatures::CONFIG.bits() == 0 { + return Err(VhostUserError::InvalidOperation); + } else if offset < VHOST_USER_CONFIG_OFFSET + || offset >= VHOST_USER_CONFIG_SIZE + || size > VHOST_USER_CONFIG_SIZE - VHOST_USER_CONFIG_OFFSET + || size + offset > VHOST_USER_CONFIG_SIZE + { + return Err(VhostUserError::InvalidParam); + } + + self.backend.set_config(offset, buf); + Ok(()) + } } From d34d77ae4e84c0d13455fdbc5ff0011e4983a36b Mon Sep 17 00:00:00 2001 From: Sebastien Boeuf Date: Mon, 16 Sep 2019 11:02:30 -0700 Subject: [PATCH 003/139] Replace Mutex with RwLock when possible Signed-off-by: Sebastien Boeuf --- src/lib.rs | 90 +++++++++++++++++++++++++++++------------------------- 1 file changed, 48 insertions(+), 42 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index 4abd7d9..d828e49 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -9,7 +9,7 @@ use std::io; use std::num::Wrapping; use std::os::unix::io::{AsRawFd, FromRawFd, RawFd}; use std::result; -use std::sync::{Arc, Mutex}; +use std::sync::{Arc, Mutex, RwLock}; use std::thread; use vhost_rs::vhost_user::message::{ VhostUserConfigFlags, VhostUserMemoryRegion, VhostUserProtocolFeatures, @@ -90,7 +90,7 @@ pub struct VhostUserDaemon { name: String, sock_path: String, handler: Arc>>, - vring_handler: Arc>>, + vring_handler: Arc>>, main_thread: Option>>, } @@ -158,7 +158,7 @@ impl VhostUserDaemon { data: u64, ) -> result::Result<(), io::Error> { self.vring_handler - .lock() + .read() .unwrap() .register_listener(fd, ev_type, data) } @@ -173,7 +173,7 @@ impl VhostUserDaemon { data: u64, ) -> result::Result<(), io::Error> { self.vring_handler - .lock() + .read() .unwrap() .unregister_listener(fd, ev_type, data) } @@ -189,7 +189,7 @@ impl VhostUserDaemon { /// of `process_queue`. With this twisted trick, all common parts related /// to the virtqueues can remain part of the library. pub fn process_queue(&self, q_idx: u16) -> Result<()> { - self.vring_handler.lock().unwrap().process_queue(q_idx) + self.vring_handler.read().unwrap().process_queue(q_idx) } } @@ -257,7 +257,7 @@ impl Vring { struct VringEpollHandler { backend: Arc, - vrings: Arc>>, + vrings: Arc>>, mem: Option, epoll_fd: RawFd, } @@ -268,7 +268,7 @@ impl VringEpollHandler { } fn process_queue(&self, q_idx: u16) -> Result<()> { - let vring = &mut self.vrings.lock().unwrap()[q_idx as usize]; + let vring = &mut self.vrings.write().unwrap()[q_idx as usize]; let mut used_desc_heads = vec![(0, 0); vring.queue.size as usize]; let mut used_count = 0; if let Some(mem) = &self.mem { @@ -296,11 +296,11 @@ impl VringEpollHandler { Ok(()) } - fn handle_event(&mut self, device_event: u16, evset: epoll::Events) -> Result { - let num_queues = self.vrings.lock().unwrap().len(); + fn handle_event(&self, device_event: u16, evset: epoll::Events) -> Result { + let num_queues = self.vrings.read().unwrap().len(); match device_event as usize { x if x < num_queues => { - if let Some(kick) = &self.vrings.lock().unwrap()[device_event as usize].kick { + if let Some(kick) = &self.vrings.read().unwrap()[device_event as usize].kick { kick.read().unwrap(); } @@ -313,7 +313,7 @@ impl VringEpollHandler { } fn register_vring_listener(&self, q_idx: usize) -> result::Result<(), io::Error> { - if let Some(fd) = &self.vrings.lock().unwrap()[q_idx].kick { + if let Some(fd) = &self.vrings.read().unwrap()[q_idx].kick { self.register_listener(fd.as_raw_fd(), epoll::Events::EPOLLIN, q_idx as u64) } else { Ok(()) @@ -321,7 +321,7 @@ impl VringEpollHandler { } fn unregister_vring_listener(&self, q_idx: usize) -> result::Result<(), io::Error> { - if let Some(fd) = &self.vrings.lock().unwrap()[q_idx].kick { + if let Some(fd) = &self.vrings.read().unwrap()[q_idx].kick { self.unregister_listener(fd.as_raw_fd(), epoll::Events::EPOLLIN, q_idx as u64) } else { Ok(()) @@ -358,7 +358,7 @@ impl VringEpollHandler { } struct VringWorker { - handler: Arc>>, + handler: Arc>>, } impl VringWorker { @@ -396,7 +396,7 @@ impl VringWorker { let ev_type = event.data as u16; - if self.handler.lock().unwrap().handle_event(ev_type, evset)? { + if self.handler.read().unwrap().handle_event(ev_type, evset)? { break 'epoll; } } @@ -408,7 +408,7 @@ impl VringWorker { struct VhostUserHandler { backend: Arc, - vring_handler: Arc>>, + vring_handler: Arc>>, owned: bool, features_acked: bool, acked_features: u64, @@ -416,7 +416,7 @@ struct VhostUserHandler { num_queues: usize, max_queue_size: usize, memory: Option, - vrings: Arc>>, + vrings: Arc>>, } impl VhostUserHandler { @@ -425,14 +425,14 @@ impl VhostUserHandler { let max_queue_size = backend.max_queue_size(); let arc_backend = Arc::new(backend); - let vrings = Arc::new(Mutex::new(vec![ + let vrings = Arc::new(RwLock::new(vec![ Vring::new(max_queue_size as u16); num_queues ])); // Create the epoll file descriptor let epoll_fd = epoll::create(true).unwrap(); - let vring_handler = Arc::new(Mutex::new(VringEpollHandler { + let vring_handler = Arc::new(RwLock::new(VringEpollHandler { backend: arc_backend.clone(), vrings: vrings.clone(), mem: None, @@ -461,7 +461,7 @@ impl VhostUserHandler { } } - fn get_vring_handler(&self) -> Arc>> { + fn get_vring_handler(&self) -> Arc>> { self.vring_handler.clone() } @@ -518,7 +518,7 @@ impl VhostUserSlaveReqHandler for VhostUserHandler { // been disabled by VHOST_USER_SET_VRING_ENABLE with parameter 0. let vring_enabled = self.acked_features & VhostUserVirtioFeatures::PROTOCOL_FEATURES.bits() == 0; - for vring in self.vrings.lock().unwrap().iter_mut() { + for vring in self.vrings.write().unwrap().iter_mut() { vring.enabled = vring_enabled; } @@ -561,7 +561,7 @@ impl VhostUserSlaveReqHandler for VhostUserHandler { } let mem = GuestMemoryMmap::from_ranges_with_files(regions).unwrap(); - self.vring_handler.lock().unwrap().update_memory(Some(mem)); + self.vring_handler.write().unwrap().update_memory(Some(mem)); self.memory = Some(Memory { mappings }); Ok(()) @@ -575,7 +575,7 @@ impl VhostUserSlaveReqHandler for VhostUserHandler { if index as usize >= self.num_queues || num == 0 || num as usize > self.max_queue_size { return Err(VhostUserError::InvalidParam); } - self.vrings.lock().unwrap()[index as usize].queue.size = num as u16; + self.vrings.write().unwrap()[index as usize].queue.size = num as u16; Ok(()) } @@ -596,9 +596,13 @@ impl VhostUserSlaveReqHandler for VhostUserHandler { let desc_table = self.vmm_va_to_gpa(descriptor).unwrap(); let avail_ring = self.vmm_va_to_gpa(available).unwrap(); let used_ring = self.vmm_va_to_gpa(used).unwrap(); - self.vrings.lock().unwrap()[index as usize].queue.desc_table = GuestAddress(desc_table); - self.vrings.lock().unwrap()[index as usize].queue.avail_ring = GuestAddress(avail_ring); - self.vrings.lock().unwrap()[index as usize].queue.used_ring = GuestAddress(used_ring); + self.vrings.write().unwrap()[index as usize] + .queue + .desc_table = GuestAddress(desc_table); + self.vrings.write().unwrap()[index as usize] + .queue + .avail_ring = GuestAddress(avail_ring); + self.vrings.write().unwrap()[index as usize].queue.used_ring = GuestAddress(used_ring); Ok(()) } else { Err(VhostUserError::InvalidParam) @@ -606,8 +610,10 @@ impl VhostUserSlaveReqHandler for VhostUserHandler { } fn set_vring_base(&mut self, index: u32, base: u32) -> VhostUserResult<()> { - self.vrings.lock().unwrap()[index as usize].queue.next_avail = Wrapping(base as u16); - self.vrings.lock().unwrap()[index as usize].queue.next_used = Wrapping(base as u16); + self.vrings.write().unwrap()[index as usize] + .queue + .next_avail = Wrapping(base as u16); + self.vrings.write().unwrap()[index as usize].queue.next_used = Wrapping(base as u16); Ok(()) } @@ -620,14 +626,14 @@ impl VhostUserSlaveReqHandler for VhostUserHandler { // that file descriptor is readable) on the descriptor specified by // VHOST_USER_SET_VRING_KICK, and stop ring upon receiving // VHOST_USER_GET_VRING_BASE. - self.vrings.lock().unwrap()[index as usize].started = false; + self.vrings.write().unwrap()[index as usize].started = false; self.vring_handler - .lock() + .read() .unwrap() .unregister_vring_listener(index as usize) .unwrap(); - let next_avail = self.vrings.lock().unwrap()[index as usize] + let next_avail = self.vrings.read().unwrap()[index as usize] .queue .next_avail .0 as u16; @@ -640,11 +646,11 @@ impl VhostUserSlaveReqHandler for VhostUserHandler { return Err(VhostUserError::InvalidParam); } - if self.vrings.lock().unwrap()[index as usize].kick.is_some() { + if self.vrings.read().unwrap()[index as usize].kick.is_some() { // Close file descriptor set by previous operations. let _ = unsafe { libc::close( - self.vrings.lock().unwrap()[index as usize] + self.vrings.write().unwrap()[index as usize] .kick .take() .unwrap() @@ -652,7 +658,7 @@ impl VhostUserSlaveReqHandler for VhostUserHandler { ) }; } - self.vrings.lock().unwrap()[index as usize].kick = + self.vrings.write().unwrap()[index as usize].kick = Some(unsafe { EventFd::from_raw_fd(fd.unwrap()) });; // Quotation from vhost-user spec: @@ -662,9 +668,9 @@ impl VhostUserSlaveReqHandler for VhostUserHandler { // VHOST_USER_GET_VRING_BASE. // // So we should add fd to event monitor(select, poll, epoll) here. - self.vrings.lock().unwrap()[index as usize].started = true; + self.vrings.write().unwrap()[index as usize].started = true; self.vring_handler - .lock() + .read() .unwrap() .register_vring_listener(index as usize) .unwrap(); @@ -677,11 +683,11 @@ impl VhostUserSlaveReqHandler for VhostUserHandler { return Err(VhostUserError::InvalidParam); } - if self.vrings.lock().unwrap()[index as usize].call.is_some() { + if self.vrings.write().unwrap()[index as usize].call.is_some() { // Close file descriptor set by previous operations. let _ = unsafe { libc::close( - self.vrings.lock().unwrap()[index as usize] + self.vrings.write().unwrap()[index as usize] .call .take() .unwrap() @@ -689,7 +695,7 @@ impl VhostUserSlaveReqHandler for VhostUserHandler { ) }; } - self.vrings.lock().unwrap()[index as usize].call = + self.vrings.write().unwrap()[index as usize].call = Some(unsafe { EventFd::from_raw_fd(fd.unwrap()) }); Ok(()) @@ -700,11 +706,11 @@ impl VhostUserSlaveReqHandler for VhostUserHandler { return Err(VhostUserError::InvalidParam); } - if self.vrings.lock().unwrap()[index as usize].err.is_some() { + if self.vrings.read().unwrap()[index as usize].err.is_some() { // Close file descriptor set by previous operations. let _ = unsafe { libc::close( - self.vrings.lock().unwrap()[index as usize] + self.vrings.write().unwrap()[index as usize] .err .take() .unwrap() @@ -712,7 +718,7 @@ impl VhostUserSlaveReqHandler for VhostUserHandler { ) }; } - self.vrings.lock().unwrap()[index as usize].err = + self.vrings.write().unwrap()[index as usize].err = Some(unsafe { EventFd::from_raw_fd(fd.unwrap()) }); Ok(()) @@ -731,7 +737,7 @@ impl VhostUserSlaveReqHandler for VhostUserHandler { // enabled by VHOST_USER_SET_VRING_ENABLE with parameter 1, // or after it has been disabled by VHOST_USER_SET_VRING_ENABLE // with parameter 0. - self.vrings.lock().unwrap()[index as usize].enabled = enable; + self.vrings.write().unwrap()[index as usize].enabled = enable; Ok(()) } From 9d05876d334fc7d2596375e436f733112081bc3a Mon Sep 17 00:00:00 2001 From: Sebastien Boeuf Date: Mon, 16 Sep 2019 11:31:23 -0700 Subject: [PATCH 004/139] Move to a per-queue RwLock Instead of locking every queues whenever something needs to be updated, this patch modifies the code design to lock each Vring independently. This allows for much finer granularity, and will allow multiple queues to be handled at the same time. Signed-off-by: Sebastien Boeuf --- src/lib.rs | 114 ++++++++++++++++++++++------------------------------- 1 file changed, 47 insertions(+), 67 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index d828e49..7b26946 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -189,7 +189,7 @@ impl VhostUserDaemon { /// of `process_queue`. With this twisted trick, all common parts related /// to the virtqueues can remain part of the library. pub fn process_queue(&self, q_idx: u16) -> Result<()> { - self.vring_handler.read().unwrap().process_queue(q_idx) + self.vring_handler.write().unwrap().process_queue(q_idx) } } @@ -211,37 +211,6 @@ struct Vring { enabled: bool, } -impl Clone for Vring { - fn clone(&self) -> Self { - let kick = if let Some(c) = &self.kick { - Some(c.try_clone().unwrap()) - } else { - None - }; - - let call = if let Some(c) = &self.call { - Some(c.try_clone().unwrap()) - } else { - None - }; - - let err = if let Some(e) = &self.err { - Some(e.try_clone().unwrap()) - } else { - None - }; - - Vring { - queue: self.queue.clone(), - kick, - call, - err, - started: self.started, - enabled: self.enabled, - } - } -} - impl Vring { fn new(max_queue_size: u16) -> Self { Vring { @@ -257,7 +226,7 @@ impl Vring { struct VringEpollHandler { backend: Arc, - vrings: Arc>>, + vrings: Vec>>, mem: Option, epoll_fd: RawFd, } @@ -267,8 +236,8 @@ impl VringEpollHandler { self.mem = mem; } - fn process_queue(&self, q_idx: u16) -> Result<()> { - let vring = &mut self.vrings.write().unwrap()[q_idx as usize]; + fn process_queue(&mut self, q_idx: u16) -> Result<()> { + let vring = &mut self.vrings[q_idx as usize].write().unwrap(); let mut used_desc_heads = vec![(0, 0); vring.queue.size as usize]; let mut used_count = 0; if let Some(mem) = &self.mem { @@ -296,11 +265,11 @@ impl VringEpollHandler { Ok(()) } - fn handle_event(&self, device_event: u16, evset: epoll::Events) -> Result { - let num_queues = self.vrings.read().unwrap().len(); + fn handle_event(&mut self, device_event: u16, evset: epoll::Events) -> Result { + let num_queues = self.vrings.len(); match device_event as usize { x if x < num_queues => { - if let Some(kick) = &self.vrings.read().unwrap()[device_event as usize].kick { + if let Some(kick) = &self.vrings[device_event as usize].read().unwrap().kick { kick.read().unwrap(); } @@ -313,7 +282,7 @@ impl VringEpollHandler { } fn register_vring_listener(&self, q_idx: usize) -> result::Result<(), io::Error> { - if let Some(fd) = &self.vrings.read().unwrap()[q_idx].kick { + if let Some(fd) = &self.vrings[q_idx].read().unwrap().kick { self.register_listener(fd.as_raw_fd(), epoll::Events::EPOLLIN, q_idx as u64) } else { Ok(()) @@ -321,7 +290,7 @@ impl VringEpollHandler { } fn unregister_vring_listener(&self, q_idx: usize) -> result::Result<(), io::Error> { - if let Some(fd) = &self.vrings.read().unwrap()[q_idx].kick { + if let Some(fd) = &self.vrings[q_idx].read().unwrap().kick { self.unregister_listener(fd.as_raw_fd(), epoll::Events::EPOLLIN, q_idx as u64) } else { Ok(()) @@ -396,7 +365,7 @@ impl VringWorker { let ev_type = event.data as u16; - if self.handler.read().unwrap().handle_event(ev_type, evset)? { + if self.handler.write().unwrap().handle_event(ev_type, evset)? { break 'epoll; } } @@ -416,7 +385,7 @@ struct VhostUserHandler { num_queues: usize, max_queue_size: usize, memory: Option, - vrings: Arc>>, + vrings: Vec>>, } impl VhostUserHandler { @@ -425,10 +394,7 @@ impl VhostUserHandler { let max_queue_size = backend.max_queue_size(); let arc_backend = Arc::new(backend); - let vrings = Arc::new(RwLock::new(vec![ - Vring::new(max_queue_size as u16); - num_queues - ])); + let vrings = vec![Arc::new(RwLock::new(Vring::new(max_queue_size as u16))); num_queues]; // Create the epoll file descriptor let epoll_fd = epoll::create(true).unwrap(); @@ -518,8 +484,8 @@ impl VhostUserSlaveReqHandler for VhostUserHandler { // been disabled by VHOST_USER_SET_VRING_ENABLE with parameter 0. let vring_enabled = self.acked_features & VhostUserVirtioFeatures::PROTOCOL_FEATURES.bits() == 0; - for vring in self.vrings.write().unwrap().iter_mut() { - vring.enabled = vring_enabled; + for vring in self.vrings.iter_mut() { + vring.write().unwrap().enabled = vring_enabled; } Ok(()) @@ -575,7 +541,7 @@ impl VhostUserSlaveReqHandler for VhostUserHandler { if index as usize >= self.num_queues || num == 0 || num as usize > self.max_queue_size { return Err(VhostUserError::InvalidParam); } - self.vrings.write().unwrap()[index as usize].queue.size = num as u16; + self.vrings[index as usize].write().unwrap().queue.size = num as u16; Ok(()) } @@ -596,13 +562,17 @@ impl VhostUserSlaveReqHandler for VhostUserHandler { let desc_table = self.vmm_va_to_gpa(descriptor).unwrap(); let avail_ring = self.vmm_va_to_gpa(available).unwrap(); let used_ring = self.vmm_va_to_gpa(used).unwrap(); - self.vrings.write().unwrap()[index as usize] + self.vrings[index as usize] + .write() + .unwrap() .queue .desc_table = GuestAddress(desc_table); - self.vrings.write().unwrap()[index as usize] + self.vrings[index as usize] + .write() + .unwrap() .queue .avail_ring = GuestAddress(avail_ring); - self.vrings.write().unwrap()[index as usize].queue.used_ring = GuestAddress(used_ring); + self.vrings[index as usize].write().unwrap().queue.used_ring = GuestAddress(used_ring); Ok(()) } else { Err(VhostUserError::InvalidParam) @@ -610,10 +580,12 @@ impl VhostUserSlaveReqHandler for VhostUserHandler { } fn set_vring_base(&mut self, index: u32, base: u32) -> VhostUserResult<()> { - self.vrings.write().unwrap()[index as usize] + self.vrings[index as usize] + .write() + .unwrap() .queue .next_avail = Wrapping(base as u16); - self.vrings.write().unwrap()[index as usize].queue.next_used = Wrapping(base as u16); + self.vrings[index as usize].write().unwrap().queue.next_used = Wrapping(base as u16); Ok(()) } @@ -626,14 +598,16 @@ impl VhostUserSlaveReqHandler for VhostUserHandler { // that file descriptor is readable) on the descriptor specified by // VHOST_USER_SET_VRING_KICK, and stop ring upon receiving // VHOST_USER_GET_VRING_BASE. - self.vrings.write().unwrap()[index as usize].started = false; + self.vrings[index as usize].write().unwrap().started = false; self.vring_handler .read() .unwrap() .unregister_vring_listener(index as usize) .unwrap(); - let next_avail = self.vrings.read().unwrap()[index as usize] + let next_avail = self.vrings[index as usize] + .read() + .unwrap() .queue .next_avail .0 as u16; @@ -646,11 +620,13 @@ impl VhostUserSlaveReqHandler for VhostUserHandler { return Err(VhostUserError::InvalidParam); } - if self.vrings.read().unwrap()[index as usize].kick.is_some() { + if self.vrings[index as usize].read().unwrap().kick.is_some() { // Close file descriptor set by previous operations. let _ = unsafe { libc::close( - self.vrings.write().unwrap()[index as usize] + self.vrings[index as usize] + .write() + .unwrap() .kick .take() .unwrap() @@ -658,7 +634,7 @@ impl VhostUserSlaveReqHandler for VhostUserHandler { ) }; } - self.vrings.write().unwrap()[index as usize].kick = + self.vrings[index as usize].write().unwrap().kick = Some(unsafe { EventFd::from_raw_fd(fd.unwrap()) });; // Quotation from vhost-user spec: @@ -668,7 +644,7 @@ impl VhostUserSlaveReqHandler for VhostUserHandler { // VHOST_USER_GET_VRING_BASE. // // So we should add fd to event monitor(select, poll, epoll) here. - self.vrings.write().unwrap()[index as usize].started = true; + self.vrings[index as usize].write().unwrap().started = true; self.vring_handler .read() .unwrap() @@ -683,11 +659,13 @@ impl VhostUserSlaveReqHandler for VhostUserHandler { return Err(VhostUserError::InvalidParam); } - if self.vrings.write().unwrap()[index as usize].call.is_some() { + if self.vrings[index as usize].write().unwrap().call.is_some() { // Close file descriptor set by previous operations. let _ = unsafe { libc::close( - self.vrings.write().unwrap()[index as usize] + self.vrings[index as usize] + .write() + .unwrap() .call .take() .unwrap() @@ -695,7 +673,7 @@ impl VhostUserSlaveReqHandler for VhostUserHandler { ) }; } - self.vrings.write().unwrap()[index as usize].call = + self.vrings[index as usize].write().unwrap().call = Some(unsafe { EventFd::from_raw_fd(fd.unwrap()) }); Ok(()) @@ -706,11 +684,13 @@ impl VhostUserSlaveReqHandler for VhostUserHandler { return Err(VhostUserError::InvalidParam); } - if self.vrings.read().unwrap()[index as usize].err.is_some() { + if self.vrings[index as usize].read().unwrap().err.is_some() { // Close file descriptor set by previous operations. let _ = unsafe { libc::close( - self.vrings.write().unwrap()[index as usize] + self.vrings[index as usize] + .write() + .unwrap() .err .take() .unwrap() @@ -718,7 +698,7 @@ impl VhostUserSlaveReqHandler for VhostUserHandler { ) }; } - self.vrings.write().unwrap()[index as usize].err = + self.vrings[index as usize].write().unwrap().err = Some(unsafe { EventFd::from_raw_fd(fd.unwrap()) }); Ok(()) @@ -737,7 +717,7 @@ impl VhostUserSlaveReqHandler for VhostUserHandler { // enabled by VHOST_USER_SET_VRING_ENABLE with parameter 1, // or after it has been disabled by VHOST_USER_SET_VRING_ENABLE // with parameter 0. - self.vrings.write().unwrap()[index as usize].enabled = enable; + self.vrings[index as usize].write().unwrap().enabled = enable; Ok(()) } From 418e00514df35250786f577d707d91519d2ddc01 Mon Sep 17 00:00:00 2001 From: Sebastien Boeuf Date: Mon, 16 Sep 2019 14:40:28 -0700 Subject: [PATCH 005/139] Make some trait functions as mutable Let's be realistic, the trait VhostUserBackend will need to have mutable self for some functions like handle_event, process_queue and set_config, which is the reason why this commit needs to introduce a RwLock on the backend instance that was passed around as a simple Arc. Signed-off-by: Sebastien Boeuf --- src/lib.rs | 30 ++++++++++++++++++------------ 1 file changed, 18 insertions(+), 12 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index 7b26946..ecac22c 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -66,12 +66,12 @@ pub trait VhostUserBackend: Send + Sync + 'static { /// listeners onto specific file descriptors. The library can handle /// virtqueues on its own, but does not know what to do with events /// happening on custom listeners. - fn handle_event(&self, device_event: u16, evset: epoll::Events) -> Result; + fn handle_event(&mut self, device_event: u16, evset: epoll::Events) -> Result; /// This function is responsible for the actual processing that needs to /// happen when one of the virtqueues is available. fn process_queue( - &self, + &mut self, q_idx: u16, avail_desc: &DescriptorChain, mem: &GuestMemoryMmap, @@ -81,7 +81,7 @@ pub trait VhostUserBackend: Send + Sync + 'static { fn get_config(&self, offset: u32, size: u32) -> Vec; /// Set virtio device configuration. - fn set_config(&self, offset: u32, buf: &[u8]); + fn set_config(&mut self, offset: u32, buf: &[u8]); } /// This structure is the public API the backend is allowed to interact with @@ -225,7 +225,7 @@ impl Vring { } struct VringEpollHandler { - backend: Arc, + backend: Arc>, vrings: Vec>>, mem: Option, epoll_fd: RawFd, @@ -244,6 +244,8 @@ impl VringEpollHandler { for avail_desc in vring.queue.iter(&mem) { let used_len = self .backend + .write() + .unwrap() .process_queue(q_idx, &avail_desc, &mem) .unwrap(); @@ -277,7 +279,11 @@ impl VringEpollHandler { Ok(false) } - _ => self.backend.handle_event(device_event, evset), + _ => self + .backend + .write() + .unwrap() + .handle_event(device_event, evset), } } @@ -376,7 +382,7 @@ impl VringWorker { } struct VhostUserHandler { - backend: Arc, + backend: Arc>, vring_handler: Arc>>, owned: bool, features_acked: bool, @@ -393,7 +399,7 @@ impl VhostUserHandler { let num_queues = backend.num_queues(); let max_queue_size = backend.max_queue_size(); - let arc_backend = Arc::new(backend); + let arc_backend = Arc::new(RwLock::new(backend)); let vrings = vec![Arc::new(RwLock::new(Vring::new(max_queue_size as u16))); num_queues]; // Create the epoll file descriptor let epoll_fd = epoll::create(true).unwrap(); @@ -462,13 +468,13 @@ impl VhostUserSlaveReqHandler for VhostUserHandler { } fn get_features(&mut self) -> VhostUserResult { - Ok(self.backend.features()) + Ok(self.backend.read().unwrap().features()) } fn set_features(&mut self, features: u64) -> VhostUserResult<()> { if !self.owned || self.features_acked { return Err(VhostUserError::InvalidOperation); - } else if (features & !self.backend.features()) != 0 { + } else if (features & !self.backend.read().unwrap().features()) != 0 { return Err(VhostUserError::InvalidParam); } @@ -635,7 +641,7 @@ impl VhostUserSlaveReqHandler for VhostUserHandler { }; } self.vrings[index as usize].write().unwrap().kick = - Some(unsafe { EventFd::from_raw_fd(fd.unwrap()) });; + Some(unsafe { EventFd::from_raw_fd(fd.unwrap()) }); // Quotation from vhost-user spec: // Client must start ring upon receiving a kick (that is, detecting @@ -738,7 +744,7 @@ impl VhostUserSlaveReqHandler for VhostUserHandler { return Err(VhostUserError::InvalidParam); } - Ok(self.backend.get_config(offset, size)) + Ok(self.backend.read().unwrap().get_config(offset, size)) } fn set_config( @@ -758,7 +764,7 @@ impl VhostUserSlaveReqHandler for VhostUserHandler { return Err(VhostUserError::InvalidParam); } - self.backend.set_config(offset, buf); + self.backend.write().unwrap().set_config(offset, buf); Ok(()) } } From ebb13544bccc407a6b389f9d5ef84654fd0f2102 Mon Sep 17 00:00:00 2001 From: Sebastien Boeuf Date: Mon, 16 Sep 2019 15:37:07 -0700 Subject: [PATCH 006/139] Allow for proper error propagation Signed-off-by: Sebastien Boeuf --- src/lib.rs | 281 +++++++++++++++++++++++++++++++++++------------------ 1 file changed, 185 insertions(+), 96 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index ecac22c..ada0129 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -4,6 +4,7 @@ // Copyright 2019 Alibaba Cloud Computing. All rights reserved. // SPDX-License-Identifier: Apache-2.0 +use std::error; use std::fs::File; use std::io; use std::num::Wrapping; @@ -27,6 +28,8 @@ use vmm_sys_util::eventfd::EventFd; #[derive(Debug)] /// Errors related to vhost-user daemon. pub enum Error { + /// Failed to create a new vhost-user handler. + NewVhostUserHandler(VhostUserHandlerError), /// Failed creating vhost-user slave handler. CreateSlaveReqHandler(VhostUserError), /// Failed starting daemon thread. @@ -35,20 +38,18 @@ pub enum Error { WaitDaemon(std::boxed::Box), /// Failed handling a vhost-user request. HandleRequest(VhostUserError), - /// Could not find the mapping from memory regions. - MissingMemoryMapping, - /// Failed to create epoll file descriptor. - EpollCreateFd(io::Error), - /// Failed to add a file descriptor to the epoll handler. - EpollCtl(io::Error), - /// Failed while waiting for events. - EpollWait(io::Error), - /// Failed to signal used queue. - SignalUsedQueue(io::Error), + /// Failed to handle the event. + HandleEvent(io::Error), + /// Failed to process queue. + ProcessQueue(VringEpollHandlerError), + /// Failed to register listener. + RegisterListener(io::Error), + /// Failed to unregister listener. + UnregisterListener(io::Error), } /// Result of vhost-user daemon operations. -pub type Result = std::result::Result; +pub type Result = result::Result; /// This trait must be implemented by the caller in order to provide backend /// specific implementation. @@ -66,7 +67,11 @@ pub trait VhostUserBackend: Send + Sync + 'static { /// listeners onto specific file descriptors. The library can handle /// virtqueues on its own, but does not know what to do with events /// happening on custom listeners. - fn handle_event(&mut self, device_event: u16, evset: epoll::Events) -> Result; + fn handle_event( + &mut self, + device_event: u16, + evset: epoll::Events, + ) -> result::Result; /// This function is responsible for the actual processing that needs to /// happen when one of the virtqueues is available. @@ -75,13 +80,13 @@ pub trait VhostUserBackend: Send + Sync + 'static { q_idx: u16, avail_desc: &DescriptorChain, mem: &GuestMemoryMmap, - ) -> Result; + ) -> result::Result; /// Get virtio device configuration. fn get_config(&self, offset: u32, size: u32) -> Vec; /// Set virtio device configuration. - fn set_config(&mut self, offset: u32, buf: &[u8]); + fn set_config(&mut self, offset: u32, buf: &[u8]) -> result::Result<(), io::Error>; } /// This structure is the public API the backend is allowed to interact with @@ -102,7 +107,9 @@ impl VhostUserDaemon { /// custom events from the backend, but they get to be registered later /// during the sequence. pub fn new(name: String, sock_path: String, backend: S) -> Result { - let handler = Arc::new(Mutex::new(VhostUserHandler::new(backend))); + let handler = Arc::new(Mutex::new( + VhostUserHandler::new(backend).map_err(Error::NewVhostUserHandler)?, + )); let vring_handler = handler.lock().unwrap().get_vring_handler(); Ok(VhostUserDaemon { @@ -151,31 +158,23 @@ impl VhostUserDaemon { /// This lets entire control to the caller about what needs to be done for /// this special event, without forcing it to run its own dedicated epoll /// loop for it. - pub fn register_listener( - &self, - fd: RawFd, - ev_type: epoll::Events, - data: u64, - ) -> result::Result<(), io::Error> { + pub fn register_listener(&self, fd: RawFd, ev_type: epoll::Events, data: u64) -> Result<()> { self.vring_handler .read() .unwrap() .register_listener(fd, ev_type, data) + .map_err(Error::RegisterListener) } /// Unregister a custom event. If the custom event is triggered after this /// function has been called, nothing will happen as it will be removed /// from the list of file descriptors the epoll loop is listening to. - pub fn unregister_listener( - &self, - fd: RawFd, - ev_type: epoll::Events, - data: u64, - ) -> result::Result<(), io::Error> { + pub fn unregister_listener(&self, fd: RawFd, ev_type: epoll::Events, data: u64) -> Result<()> { self.vring_handler .read() .unwrap() .unregister_listener(fd, ev_type, data) + .map_err(Error::RegisterListener) } /// Trigger the processing of a virtqueue. This function is meant to be @@ -189,7 +188,11 @@ impl VhostUserDaemon { /// of `process_queue`. With this twisted trick, all common parts related /// to the virtqueues can remain part of the library. pub fn process_queue(&self, q_idx: u16) -> Result<()> { - self.vring_handler.write().unwrap().process_queue(q_idx) + self.vring_handler + .write() + .unwrap() + .process_queue(q_idx) + .map_err(Error::ProcessQueue) } } @@ -224,6 +227,53 @@ impl Vring { } } +#[derive(Debug)] +/// Errors related to vring epoll handler. +pub enum VringEpollHandlerError { + /// Failed to process the queue from the backend. + ProcessQueueBackendProcessing(io::Error), + /// Failed to signal used queue. + SignalUsedQueue(io::Error), + /// Failed to read the event from kick EventFd. + HandleEventReadKick(io::Error), + /// Failed to handle the event from the backend. + HandleEventBackendHandling(io::Error), + /// Failed to register vring listener. + RegisterVringListener(io::Error), + /// Failed to unregister vring listener. + UnregisterVringListener(io::Error), +} + +impl std::fmt::Display for VringEpollHandlerError { + fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { + match self { + VringEpollHandlerError::ProcessQueueBackendProcessing(e) => { + write!(f, "failed processing queue from backend: {}", e) + } + VringEpollHandlerError::SignalUsedQueue(e) => { + write!(f, "failed signalling used queue: {}", e) + } + VringEpollHandlerError::HandleEventReadKick(e) => { + write!(f, "failed reading from kick eventfd: {}", e) + } + VringEpollHandlerError::HandleEventBackendHandling(e) => { + write!(f, "failed handling event from backend: {}", e) + } + VringEpollHandlerError::RegisterVringListener(e) => { + write!(f, "failed registering vring listener: {}", e) + } + VringEpollHandlerError::UnregisterVringListener(e) => { + write!(f, "failed unregistering vring listener: {}", e) + } + } + } +} + +impl error::Error for VringEpollHandlerError {} + +/// Result of vring epoll handler operations. +type VringEpollHandlerResult = std::result::Result; + struct VringEpollHandler { backend: Arc>, vrings: Vec>>, @@ -236,7 +286,7 @@ impl VringEpollHandler { self.mem = mem; } - fn process_queue(&mut self, q_idx: u16) -> Result<()> { + fn process_queue(&mut self, q_idx: u16) -> VringEpollHandlerResult<()> { let vring = &mut self.vrings[q_idx as usize].write().unwrap(); let mut used_desc_heads = vec![(0, 0); vring.queue.size as usize]; let mut used_count = 0; @@ -247,7 +297,7 @@ impl VringEpollHandler { .write() .unwrap() .process_queue(q_idx, &avail_desc, &mem) - .unwrap(); + .map_err(VringEpollHandlerError::ProcessQueueBackendProcessing)?; used_desc_heads[used_count] = (avail_desc.index, used_len); used_count += 1; @@ -260,44 +310,52 @@ impl VringEpollHandler { if used_count > 0 { if let Some(call) = &vring.call { - return call.write(1).map_err(Error::SignalUsedQueue); + call.write(1) + .map_err(VringEpollHandlerError::SignalUsedQueue)?; } } Ok(()) } - fn handle_event(&mut self, device_event: u16, evset: epoll::Events) -> Result { + fn handle_event( + &mut self, + device_event: u16, + evset: epoll::Events, + ) -> VringEpollHandlerResult { let num_queues = self.vrings.len(); match device_event as usize { x if x < num_queues => { if let Some(kick) = &self.vrings[device_event as usize].read().unwrap().kick { - kick.read().unwrap(); + kick.read() + .map_err(VringEpollHandlerError::HandleEventReadKick)?; } - self.process_queue(device_event).unwrap(); - + self.process_queue(device_event)?; Ok(false) } _ => self .backend .write() .unwrap() - .handle_event(device_event, evset), + .handle_event(device_event, evset) + .map_err(VringEpollHandlerError::HandleEventBackendHandling), } } - fn register_vring_listener(&self, q_idx: usize) -> result::Result<(), io::Error> { + fn register_vring_listener(&self, q_idx: usize) -> VringEpollHandlerResult<()> { if let Some(fd) = &self.vrings[q_idx].read().unwrap().kick { self.register_listener(fd.as_raw_fd(), epoll::Events::EPOLLIN, q_idx as u64) + .map_err(VringEpollHandlerError::RegisterVringListener) } else { Ok(()) } } - fn unregister_vring_listener(&self, q_idx: usize) -> result::Result<(), io::Error> { + fn unregister_vring_listener(&self, q_idx: usize) -> VringEpollHandlerResult<()> { if let Some(fd) = &self.vrings[q_idx].read().unwrap().kick { self.unregister_listener(fd.as_raw_fd(), epoll::Events::EPOLLIN, q_idx as u64) + .map_err(VringEpollHandlerError::UnregisterVringListener) } else { Ok(()) } @@ -332,12 +390,24 @@ impl VringEpollHandler { } } +#[derive(Debug)] +/// Errors related to vring worker. +enum VringWorkerError { + /// Failed while waiting for events. + EpollWait(io::Error), + /// Failed to handle event. + HandleEvent(VringEpollHandlerError), +} + +/// Result of vring worker operations. +type VringWorkerResult = std::result::Result; + struct VringWorker { handler: Arc>>, } impl VringWorker { - fn run(&self, epoll_fd: RawFd) -> Result<()> { + fn run(&self, epoll_fd: RawFd) -> VringWorkerResult<()> { const EPOLL_EVENTS_LEN: usize = 100; let mut events = vec![epoll::Event::new(epoll::Events::empty(), 0); EPOLL_EVENTS_LEN]; @@ -355,7 +425,7 @@ impl VringWorker { // appropriate to retry, by calling into epoll_wait(). continue; } - return Err(Error::EpollWait(e)); + return Err(VringWorkerError::EpollWait(e)); } }; @@ -371,7 +441,13 @@ impl VringWorker { let ev_type = event.data as u16; - if self.handler.write().unwrap().handle_event(ev_type, evset)? { + if self + .handler + .write() + .unwrap() + .handle_event(ev_type, evset) + .map_err(VringWorkerError::HandleEvent)? + { break 'epoll; } } @@ -381,6 +457,34 @@ impl VringWorker { } } +#[derive(Debug)] +/// Errors related to vhost-user handler. +pub enum VhostUserHandlerError { + /// Failed to create epoll file descriptor. + EpollCreateFd(io::Error), + /// Failed to spawn vring worker. + SpawnVringWorker(io::Error), + /// Could not find the mapping from memory regions. + MissingMemoryMapping, +} + +impl std::fmt::Display for VhostUserHandlerError { + fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { + match self { + VhostUserHandlerError::EpollCreateFd(e) => write!(f, "failed creating epoll fd: {}", e), + VhostUserHandlerError::SpawnVringWorker(e) => { + write!(f, "failed spawning the vring worker: {}", e) + } + VhostUserHandlerError::MissingMemoryMapping => write!(f, "Missing memory mapping"), + } + } +} + +impl error::Error for VhostUserHandlerError {} + +/// Result of vhost-user handler operations. +type VhostUserHandlerResult = std::result::Result; + struct VhostUserHandler { backend: Arc>, vring_handler: Arc>>, @@ -395,14 +499,14 @@ struct VhostUserHandler { } impl VhostUserHandler { - fn new(backend: S) -> Self { + fn new(backend: S) -> VhostUserHandlerResult { let num_queues = backend.num_queues(); let max_queue_size = backend.max_queue_size(); let arc_backend = Arc::new(RwLock::new(backend)); let vrings = vec![Arc::new(RwLock::new(Vring::new(max_queue_size as u16))); num_queues]; // Create the epoll file descriptor - let epoll_fd = epoll::create(true).unwrap(); + let epoll_fd = epoll::create(true).map_err(VhostUserHandlerError::EpollCreateFd)?; let vring_handler = Arc::new(RwLock::new(VringEpollHandler { backend: arc_backend.clone(), @@ -415,11 +519,11 @@ impl VhostUserHandler { }; thread::Builder::new() - .name("vring_epoll_handler".to_string()) + .name("vring_worker".to_string()) .spawn(move || worker.run(epoll_fd)) - .unwrap(); + .map_err(VhostUserHandlerError::SpawnVringWorker)?; - VhostUserHandler { + Ok(VhostUserHandler { backend: arc_backend, vring_handler, owned: false, @@ -430,14 +534,14 @@ impl VhostUserHandler { max_queue_size, memory: None, vrings, - } + }) } fn get_vring_handler(&self) -> Arc>> { self.vring_handler.clone() } - fn vmm_va_to_gpa(&self, vmm_va: u64) -> Result { + fn vmm_va_to_gpa(&self, vmm_va: u64) -> VhostUserHandlerResult { if let Some(memory) = &self.memory { for mapping in memory.mappings.iter() { if vmm_va >= mapping.vmm_addr && vmm_va < mapping.vmm_addr + mapping.size { @@ -446,7 +550,7 @@ impl VhostUserHandler { } } - Err(Error::MissingMemoryMapping) + Err(VhostUserHandlerError::MissingMemoryMapping) } } @@ -532,7 +636,9 @@ impl VhostUserSlaveReqHandler for VhostUserHandler { }); } - let mem = GuestMemoryMmap::from_ranges_with_files(regions).unwrap(); + let mem = GuestMemoryMmap::from_ranges_with_files(regions).map_err(|e| { + VhostUserError::ReqHandlerError(io::Error::new(io::ErrorKind::Other, e)) + })?; self.vring_handler.write().unwrap().update_memory(Some(mem)); self.memory = Some(Memory { mappings }); @@ -565,9 +671,15 @@ impl VhostUserSlaveReqHandler for VhostUserHandler { } if self.memory.is_some() { - let desc_table = self.vmm_va_to_gpa(descriptor).unwrap(); - let avail_ring = self.vmm_va_to_gpa(available).unwrap(); - let used_ring = self.vmm_va_to_gpa(used).unwrap(); + let desc_table = self.vmm_va_to_gpa(descriptor).map_err(|e| { + VhostUserError::ReqHandlerError(io::Error::new(io::ErrorKind::Other, e)) + })?; + let avail_ring = self.vmm_va_to_gpa(available).map_err(|e| { + VhostUserError::ReqHandlerError(io::Error::new(io::ErrorKind::Other, e)) + })?; + let used_ring = self.vmm_va_to_gpa(used).map_err(|e| { + VhostUserError::ReqHandlerError(io::Error::new(io::ErrorKind::Other, e)) + })?; self.vrings[index as usize] .write() .unwrap() @@ -609,7 +721,9 @@ impl VhostUserSlaveReqHandler for VhostUserHandler { .read() .unwrap() .unregister_vring_listener(index as usize) - .unwrap(); + .map_err(|e| { + VhostUserError::ReqHandlerError(io::Error::new(io::ErrorKind::Other, e)) + })?; let next_avail = self.vrings[index as usize] .read() @@ -626,22 +740,12 @@ impl VhostUserSlaveReqHandler for VhostUserHandler { return Err(VhostUserError::InvalidParam); } - if self.vrings[index as usize].read().unwrap().kick.is_some() { + if let Some(kick) = self.vrings[index as usize].write().unwrap().kick.take() { // Close file descriptor set by previous operations. - let _ = unsafe { - libc::close( - self.vrings[index as usize] - .write() - .unwrap() - .kick - .take() - .unwrap() - .as_raw_fd(), - ) - }; + let _ = unsafe { libc::close(kick.as_raw_fd()) }; } self.vrings[index as usize].write().unwrap().kick = - Some(unsafe { EventFd::from_raw_fd(fd.unwrap()) }); + fd.map(|x| unsafe { EventFd::from_raw_fd(x) }); // Quotation from vhost-user spec: // Client must start ring upon receiving a kick (that is, detecting @@ -655,7 +759,9 @@ impl VhostUserSlaveReqHandler for VhostUserHandler { .read() .unwrap() .register_vring_listener(index as usize) - .unwrap(); + .map_err(|e| { + VhostUserError::ReqHandlerError(io::Error::new(io::ErrorKind::Other, e)) + })?; Ok(()) } @@ -665,22 +771,12 @@ impl VhostUserSlaveReqHandler for VhostUserHandler { return Err(VhostUserError::InvalidParam); } - if self.vrings[index as usize].write().unwrap().call.is_some() { + if let Some(call) = self.vrings[index as usize].write().unwrap().call.take() { // Close file descriptor set by previous operations. - let _ = unsafe { - libc::close( - self.vrings[index as usize] - .write() - .unwrap() - .call - .take() - .unwrap() - .as_raw_fd(), - ) - }; + let _ = unsafe { libc::close(call.as_raw_fd()) }; } self.vrings[index as usize].write().unwrap().call = - Some(unsafe { EventFd::from_raw_fd(fd.unwrap()) }); + fd.map(|x| unsafe { EventFd::from_raw_fd(x) }); Ok(()) } @@ -690,22 +786,12 @@ impl VhostUserSlaveReqHandler for VhostUserHandler { return Err(VhostUserError::InvalidParam); } - if self.vrings[index as usize].read().unwrap().err.is_some() { + if let Some(err) = self.vrings[index as usize].write().unwrap().err.take() { // Close file descriptor set by previous operations. - let _ = unsafe { - libc::close( - self.vrings[index as usize] - .write() - .unwrap() - .err - .take() - .unwrap() - .as_raw_fd(), - ) - }; + let _ = unsafe { libc::close(err.as_raw_fd()) }; } self.vrings[index as usize].write().unwrap().err = - Some(unsafe { EventFd::from_raw_fd(fd.unwrap()) }); + fd.map(|x| unsafe { EventFd::from_raw_fd(x) }); Ok(()) } @@ -764,7 +850,10 @@ impl VhostUserSlaveReqHandler for VhostUserHandler { return Err(VhostUserError::InvalidParam); } - self.backend.write().unwrap().set_config(offset, buf); - Ok(()) + self.backend + .write() + .unwrap() + .set_config(offset, buf) + .map_err(VhostUserError::ReqHandlerError) } } From 539415a3b02f70e5711ccb70a64831bbe049680a Mon Sep 17 00:00:00 2001 From: Sebastien Boeuf Date: Tue, 17 Sep 2019 07:35:03 -0700 Subject: [PATCH 007/139] Remove useless started field The Queue structure already contains a field "ready" that can be used to track the status of the vrings. Signed-off-by: Sebastien Boeuf --- src/lib.rs | 12 ++++-------- 1 file changed, 4 insertions(+), 8 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index ada0129..3675050 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -210,7 +210,6 @@ struct Vring { kick: Option, call: Option, err: Option, - started: bool, enabled: bool, } @@ -221,7 +220,6 @@ impl Vring { kick: None, call: None, err: None, - started: false, enabled: false, } } @@ -711,12 +709,12 @@ impl VhostUserSlaveReqHandler for VhostUserHandler { if index as usize >= self.num_queues { return Err(VhostUserError::InvalidParam); } - // Quotation from vhost-user spec: + // Quote from vhost-user specification: // Client must start ring upon receiving a kick (that is, detecting // that file descriptor is readable) on the descriptor specified by // VHOST_USER_SET_VRING_KICK, and stop ring upon receiving // VHOST_USER_GET_VRING_BASE. - self.vrings[index as usize].write().unwrap().started = false; + self.vrings[index as usize].write().unwrap().queue.ready = false; self.vring_handler .read() .unwrap() @@ -747,14 +745,12 @@ impl VhostUserSlaveReqHandler for VhostUserHandler { self.vrings[index as usize].write().unwrap().kick = fd.map(|x| unsafe { EventFd::from_raw_fd(x) }); - // Quotation from vhost-user spec: + // Quote from vhost-user specification: // Client must start ring upon receiving a kick (that is, detecting // that file descriptor is readable) on the descriptor specified by // VHOST_USER_SET_VRING_KICK, and stop ring upon receiving // VHOST_USER_GET_VRING_BASE. - // - // So we should add fd to event monitor(select, poll, epoll) here. - self.vrings[index as usize].write().unwrap().started = true; + self.vrings[index as usize].write().unwrap().queue.ready = true; self.vring_handler .read() .unwrap() From d583fe193baeeb3647b605167e5b0f2c70dd3396 Mon Sep 17 00:00:00 2001 From: Sebastien Boeuf Date: Tue, 17 Sep 2019 07:43:22 -0700 Subject: [PATCH 008/139] Don't process disabled queues Every time an event is triggered, it needs to be read, but only based on the status of the vring (enabled or not) will decide if the queue needs to be processed. Signed-off-by: Sebastien Boeuf --- src/lib.rs | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/lib.rs b/src/lib.rs index 3675050..7a8aa02 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -329,6 +329,12 @@ impl VringEpollHandler { .map_err(VringEpollHandlerError::HandleEventReadKick)?; } + // If the vring is not enabled, it should not be processed. + // The event is only read to be discarded. + if !self.vrings[device_event as usize].read().unwrap().enabled { + return Ok(false); + } + self.process_queue(device_event)?; Ok(false) } From 9bf2418f1b4638b37529234e576d9428d0c8d46f Mon Sep 17 00:00:00 2001 From: Sebastien Boeuf Date: Mon, 23 Sep 2019 11:30:55 -0700 Subject: [PATCH 009/139] Make the backend a server The code needs to initialize a listener to accept connection from the VMM being the client in this case. Signed-off-by: Cathy Zhang Signed-off-by: Sebastien Boeuf --- src/lib.rs | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index 7a8aa02..676e222 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -18,7 +18,7 @@ use vhost_rs::vhost_user::message::{ VHOST_USER_CONFIG_OFFSET, VHOST_USER_CONFIG_SIZE, }; use vhost_rs::vhost_user::{ - Error as VhostUserError, Result as VhostUserResult, SlaveReqHandler, VhostUserSlaveReqHandler, + Error as VhostUserError, Result as VhostUserResult, SlaveListener, VhostUserSlaveReqHandler, }; use vm_memory::guest_memory::FileOffset; use vm_memory::{GuestAddress, GuestMemoryMmap}; @@ -30,6 +30,8 @@ use vmm_sys_util::eventfd::EventFd; pub enum Error { /// Failed to create a new vhost-user handler. NewVhostUserHandler(VhostUserHandlerError), + /// Failed creating vhost-user slave listener. + CreateSlaveListener(VhostUserError), /// Failed creating vhost-user slave handler. CreateSlaveReqHandler(VhostUserError), /// Failed starting daemon thread. @@ -126,9 +128,13 @@ impl VhostUserDaemon { /// that should be terminating once the other end of the socket (the VMM) /// disconnects. pub fn start(&mut self) -> Result<()> { - let mut slave_handler = - SlaveReqHandler::connect(self.sock_path.as_str(), self.handler.clone()) - .map_err(Error::CreateSlaveReqHandler)?; + let mut slave_listener = + SlaveListener::new(self.sock_path.as_str(), false, self.handler.clone()) + .map_err(Error::CreateSlaveListener)?; + let mut slave_handler = slave_listener + .accept() + .map_err(Error::CreateSlaveReqHandler)? + .unwrap(); let handle = thread::Builder::new() .name(self.name.clone()) .spawn(move || loop { From e28a6dfaabc8f19d06f7be2c68e513da2e7d5735 Mon Sep 17 00:00:00 2001 From: Sebastien Boeuf Date: Mon, 23 Sep 2019 11:36:52 -0700 Subject: [PATCH 010/139] Provide some default trait implementations We cannot expect every backend to support GET_CONFIG and SET_CONFIG commands. That's why this patch adds some default implementations for the trait VhostUserBackend regarding both get_config() and set_config() functions. Signed-off-by: Cathy Zhang Signed-off-by: Sebastien Boeuf --- src/lib.rs | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index 676e222..65a89b5 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -85,10 +85,18 @@ pub trait VhostUserBackend: Send + Sync + 'static { ) -> result::Result; /// Get virtio device configuration. - fn get_config(&self, offset: u32, size: u32) -> Vec; + /// A default implementation is provided as we cannot expect all backends + /// to implement this function. + fn get_config(&self, offset: u32, size: u32) -> Vec { + Vec::new() + } /// Set virtio device configuration. - fn set_config(&mut self, offset: u32, buf: &[u8]) -> result::Result<(), io::Error>; + /// A default implementation is provided as we cannot expect all backends + /// to implement this function. + fn set_config(&mut self, offset: u32, buf: &[u8]) -> result::Result<(), io::Error> { + Ok(()) + } } /// This structure is the public API the backend is allowed to interact with From cb5ba1999a2fc9aa20a6e3e5fc19a46037297965 Mon Sep 17 00:00:00 2001 From: Sebastien Boeuf Date: Mon, 23 Sep 2019 12:23:28 -0700 Subject: [PATCH 011/139] Give access to the EpollVringHandler By letting the consumer of this crate getting access to the vring handler, we will be able to let it perform several actions without producing a deadlock. Signed-off-by: Sebastien Boeuf --- src/lib.rs | 76 ++++++++++++++++++++---------------------------------- 1 file changed, 28 insertions(+), 48 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index 65a89b5..7244180 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -105,7 +105,6 @@ pub struct VhostUserDaemon { name: String, sock_path: String, handler: Arc>>, - vring_handler: Arc>>, main_thread: Option>>, } @@ -120,13 +119,11 @@ impl VhostUserDaemon { let handler = Arc::new(Mutex::new( VhostUserHandler::new(backend).map_err(Error::NewVhostUserHandler)?, )); - let vring_handler = handler.lock().unwrap().get_vring_handler(); Ok(VhostUserDaemon { name, sock_path, handler, - vring_handler, main_thread: None, }) } @@ -166,47 +163,11 @@ impl VhostUserDaemon { Ok(()) } - /// Register a custom event only meaningful to the caller. When this event - /// is later triggered, and because only the caller knows what to do about - /// it, the backend implementation of `handle_event` will be called. - /// This lets entire control to the caller about what needs to be done for - /// this special event, without forcing it to run its own dedicated epoll - /// loop for it. - pub fn register_listener(&self, fd: RawFd, ev_type: epoll::Events, data: u64) -> Result<()> { - self.vring_handler - .read() - .unwrap() - .register_listener(fd, ev_type, data) - .map_err(Error::RegisterListener) - } - - /// Unregister a custom event. If the custom event is triggered after this - /// function has been called, nothing will happen as it will be removed - /// from the list of file descriptors the epoll loop is listening to. - pub fn unregister_listener(&self, fd: RawFd, ev_type: epoll::Events, data: u64) -> Result<()> { - self.vring_handler - .read() - .unwrap() - .unregister_listener(fd, ev_type, data) - .map_err(Error::RegisterListener) - } - - /// Trigger the processing of a virtqueue. This function is meant to be - /// used by the caller whenever it might need some available queues to - /// send data back to the guest. - /// A concrete example is a backend registering one extra listener for - /// data that needs to be sent to the guest. When the associated event - /// is triggered, the backend will be invoked through its `handle_event` - /// implementation. And in this case, the way to handle the event is to - /// call into `process_queue` to let it invoke the backend implementation - /// of `process_queue`. With this twisted trick, all common parts related - /// to the virtqueues can remain part of the library. - pub fn process_queue(&self, q_idx: u16) -> Result<()> { - self.vring_handler - .write() - .unwrap() - .process_queue(q_idx) - .map_err(Error::ProcessQueue) + /// Retrieve the vring handler. This is necessary to perform further + /// actions like registering and unregistering some extra event file + /// descriptors, as well as forcing some vring to be processed. + pub fn get_vring_handler(&self) -> Arc>> { + self.handler.lock().unwrap().get_vring_handler() } } @@ -286,7 +247,7 @@ impl error::Error for VringEpollHandlerError {} /// Result of vring epoll handler operations. type VringEpollHandlerResult = std::result::Result; -struct VringEpollHandler { +pub struct VringEpollHandler { backend: Arc>, vrings: Vec>>, mem: Option, @@ -298,7 +259,17 @@ impl VringEpollHandler { self.mem = mem; } - fn process_queue(&mut self, q_idx: u16) -> VringEpollHandlerResult<()> { + /// Trigger the processing of a virtqueue. This function is meant to be + /// used by the caller whenever it might need some available queues to + /// send data back to the guest. + /// A concrete example is a backend registering one extra listener for + /// data that needs to be sent to the guest. When the associated event + /// is triggered, the backend will be invoked through its `handle_event` + /// implementation. And in this case, the way to handle the event is to + /// call into `process_queue` to let it invoke the backend implementation + /// of `process_queue`. With this twisted trick, all common parts related + /// to the virtqueues can remain part of the library. + pub fn process_queue(&mut self, q_idx: u16) -> VringEpollHandlerResult<()> { let vring = &mut self.vrings[q_idx as usize].write().unwrap(); let mut used_desc_heads = vec![(0, 0); vring.queue.size as usize]; let mut used_count = 0; @@ -379,7 +350,13 @@ impl VringEpollHandler { } } - fn register_listener( + /// Register a custom event only meaningful to the caller. When this event + /// is later triggered, and because only the caller knows what to do about + /// it, the backend implementation of `handle_event` will be called. + /// This lets entire control to the caller about what needs to be done for + /// this special event, without forcing it to run its own dedicated epoll + /// loop for it. + pub fn register_listener( &self, fd: RawFd, ev_type: epoll::Events, @@ -393,7 +370,10 @@ impl VringEpollHandler { ) } - fn unregister_listener( + /// Unregister a custom event. If the custom event is triggered after this + /// function has been called, nothing will happen as it will be removed + /// from the list of file descriptors the epoll loop is listening to. + pub fn unregister_listener( &self, fd: RawFd, ev_type: epoll::Events, From 702369cfd2d189fb1c763d043d296602c969565b Mon Sep 17 00:00:00 2001 From: Sebastien Boeuf Date: Mon, 23 Sep 2019 12:54:24 -0700 Subject: [PATCH 012/139] Pass a backend that can be modified This patch modifies the library so that a consumer can update the backend after it's been passed to the daemon. Signed-off-by: Sebastien Boeuf --- src/lib.rs | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index 7244180..f96fd17 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -115,7 +115,7 @@ impl VhostUserDaemon { /// listening onto registered event. Those events can be vring events or /// custom events from the backend, but they get to be registered later /// during the sequence. - pub fn new(name: String, sock_path: String, backend: S) -> Result { + pub fn new(name: String, sock_path: String, backend: Arc>) -> Result { let handler = Arc::new(Mutex::new( VhostUserHandler::new(backend).map_err(Error::NewVhostUserHandler)?, )); @@ -497,17 +497,16 @@ struct VhostUserHandler { } impl VhostUserHandler { - fn new(backend: S) -> VhostUserHandlerResult { - let num_queues = backend.num_queues(); - let max_queue_size = backend.max_queue_size(); + fn new(backend: Arc>) -> VhostUserHandlerResult { + let num_queues = backend.read().unwrap().num_queues(); + let max_queue_size = backend.read().unwrap().max_queue_size(); - let arc_backend = Arc::new(RwLock::new(backend)); let vrings = vec![Arc::new(RwLock::new(Vring::new(max_queue_size as u16))); num_queues]; // Create the epoll file descriptor let epoll_fd = epoll::create(true).map_err(VhostUserHandlerError::EpollCreateFd)?; let vring_handler = Arc::new(RwLock::new(VringEpollHandler { - backend: arc_backend.clone(), + backend: backend.clone(), vrings: vrings.clone(), mem: None, epoll_fd, @@ -522,7 +521,7 @@ impl VhostUserHandler { .map_err(VhostUserHandlerError::SpawnVringWorker)?; Ok(VhostUserHandler { - backend: arc_backend, + backend, vring_handler, owned: false, features_acked: false, From eea92c6a588e8ae880214cee8c41e6cac2ac6526 Mon Sep 17 00:00:00 2001 From: Cathy Zhang Date: Tue, 24 Sep 2019 13:32:20 +0800 Subject: [PATCH 013/139] Remove one checking from set_features The vhost-user protocol does not indicate set_features could not be issued more than once, the checking is not needed at all, and prevent communication between master and slave. Remove it to fix the issue. Signed-off-by: Cathy Zhang --- src/lib.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/lib.rs b/src/lib.rs index f96fd17..5efc98d 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -573,7 +573,7 @@ impl VhostUserSlaveReqHandler for VhostUserHandler { } fn set_features(&mut self, features: u64) -> VhostUserResult<()> { - if !self.owned || self.features_acked { + if !self.owned { return Err(VhostUserError::InvalidOperation); } else if (features & !self.backend.read().unwrap().features()) != 0 { return Err(VhostUserError::InvalidParam); From 27fe06a3bf6a8f2bfdb122997e379add9a7506ab Mon Sep 17 00:00:00 2001 From: Cathy Zhang Date: Tue, 24 Sep 2019 15:39:45 +0800 Subject: [PATCH 014/139] Update vmm_va_to_gpa with adding offset The original logic does not has any problem without offset, since the current offset is zero. However, if offset is not zero, while convert vmm address to backend process address, it needs to consider the offset. Signed-off-by: Cathy Zhang --- src/lib.rs | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index 5efc98d..053c137 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -174,6 +174,7 @@ impl VhostUserDaemon { struct AddrMapping { vmm_addr: u64, size: u64, + offset: u64, } struct Memory { @@ -542,7 +543,7 @@ impl VhostUserHandler { if let Some(memory) = &self.memory { for mapping in memory.mappings.iter() { if vmm_va >= mapping.vmm_addr && vmm_va < mapping.vmm_addr + mapping.size { - return Ok(vmm_va - mapping.vmm_addr); + return Ok(vmm_va - mapping.vmm_addr + mapping.offset); } } } @@ -629,7 +630,8 @@ impl VhostUserSlaveReqHandler for VhostUserHandler { regions.push((g_addr, len, Some(f_off))); mappings.push(AddrMapping { vmm_addr: region.user_addr, - size: region.memory_size + region.mmap_offset, + size: region.memory_size, + offset: region.mmap_offset, }); } From f4469754c531d5530ffb11bb764fa64a4441af52 Mon Sep 17 00:00:00 2001 From: Sebastien Boeuf Date: Tue, 24 Sep 2019 18:53:34 -0700 Subject: [PATCH 015/139] Fix remaining issues found in integration test This commit fixes all the remaining issues that were found as part of the integration with vhost-user-net. It fixes the way to notify that a vring is used, by using the proper EventFd. It removes the process_queue() function from the trait, since the complexity it was introducing was leading to deadlocks with mutexes. It moves the register/unregister functions for registering custom events from the backend, from the VringEpollHandler to the VringWorker. This allows for a lot of simplification and solve a deadlock issue. Signed-off-by: Sebastien Boeuf --- src/lib.rs | 366 ++++++++++++++++++++--------------------------------- 1 file changed, 135 insertions(+), 231 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index 053c137..006cc74 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -22,7 +22,7 @@ use vhost_rs::vhost_user::{ }; use vm_memory::guest_memory::FileOffset; use vm_memory::{GuestAddress, GuestMemoryMmap}; -use vm_virtio::{DescriptorChain, Queue}; +use vm_virtio::Queue; use vmm_sys_util::eventfd::EventFd; #[derive(Debug)] @@ -40,8 +40,6 @@ pub enum Error { WaitDaemon(std::boxed::Box), /// Failed handling a vhost-user request. HandleRequest(VhostUserError), - /// Failed to handle the event. - HandleEvent(io::Error), /// Failed to process queue. ProcessQueue(VringEpollHandlerError), /// Failed to register listener. @@ -65,6 +63,9 @@ pub trait VhostUserBackend: Send + Sync + 'static { /// Virtio features. fn features(&self) -> u64; + /// Update guest memory regions. + fn update_memory(&mut self, mem: GuestMemoryMmap) -> result::Result<(), io::Error>; + /// This function gets called if the backend registered some additional /// listeners onto specific file descriptors. The library can handle /// virtqueues on its own, but does not know what to do with events @@ -73,28 +74,20 @@ pub trait VhostUserBackend: Send + Sync + 'static { &mut self, device_event: u16, evset: epoll::Events, + vrings: &Vec>>, ) -> result::Result; - /// This function is responsible for the actual processing that needs to - /// happen when one of the virtqueues is available. - fn process_queue( - &mut self, - q_idx: u16, - avail_desc: &DescriptorChain, - mem: &GuestMemoryMmap, - ) -> result::Result; - /// Get virtio device configuration. /// A default implementation is provided as we cannot expect all backends /// to implement this function. - fn get_config(&self, offset: u32, size: u32) -> Vec { + fn get_config(&self, _offset: u32, _size: u32) -> Vec { Vec::new() } /// Set virtio device configuration. /// A default implementation is provided as we cannot expect all backends /// to implement this function. - fn set_config(&mut self, offset: u32, buf: &[u8]) -> result::Result<(), io::Error> { + fn set_config(&mut self, _offset: u32, _buf: &[u8]) -> result::Result<(), io::Error> { Ok(()) } } @@ -134,7 +127,7 @@ impl VhostUserDaemon { /// disconnects. pub fn start(&mut self) -> Result<()> { let mut slave_listener = - SlaveListener::new(self.sock_path.as_str(), false, self.handler.clone()) + SlaveListener::new(self.sock_path.as_str(), true, self.handler.clone()) .map_err(Error::CreateSlaveListener)?; let mut slave_handler = slave_listener .accept() @@ -163,11 +156,11 @@ impl VhostUserDaemon { Ok(()) } - /// Retrieve the vring handler. This is necessary to perform further + /// Retrieve the vring worker. This is necessary to perform further /// actions like registering and unregistering some extra event file - /// descriptors, as well as forcing some vring to be processed. - pub fn get_vring_handler(&self) -> Arc>> { - self.handler.lock().unwrap().get_vring_handler() + /// descriptors. + pub fn get_vring_worker(&self) -> Arc { + self.handler.lock().unwrap().get_vring_worker() } } @@ -181,7 +174,7 @@ struct Memory { mappings: Vec, } -struct Vring { +pub struct Vring { queue: Queue, kick: Option, call: Option, @@ -199,6 +192,18 @@ impl Vring { enabled: false, } } + + pub fn mut_queue(&mut self) -> &mut Queue { + &mut self.queue + } + + pub fn signal_used_queue(&self) -> result::Result<(), io::Error> { + if let Some(call) = self.call.as_ref() { + return call.write(1); + } + + Ok(()) + } } #[derive(Debug)] @@ -212,143 +217,105 @@ pub enum VringEpollHandlerError { HandleEventReadKick(io::Error), /// Failed to handle the event from the backend. HandleEventBackendHandling(io::Error), - /// Failed to register vring listener. - RegisterVringListener(io::Error), - /// Failed to unregister vring listener. - UnregisterVringListener(io::Error), } -impl std::fmt::Display for VringEpollHandlerError { - fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { - match self { - VringEpollHandlerError::ProcessQueueBackendProcessing(e) => { - write!(f, "failed processing queue from backend: {}", e) - } - VringEpollHandlerError::SignalUsedQueue(e) => { - write!(f, "failed signalling used queue: {}", e) - } - VringEpollHandlerError::HandleEventReadKick(e) => { - write!(f, "failed reading from kick eventfd: {}", e) - } - VringEpollHandlerError::HandleEventBackendHandling(e) => { - write!(f, "failed handling event from backend: {}", e) - } - VringEpollHandlerError::RegisterVringListener(e) => { - write!(f, "failed registering vring listener: {}", e) - } - VringEpollHandlerError::UnregisterVringListener(e) => { - write!(f, "failed unregistering vring listener: {}", e) - } - } - } -} - -impl error::Error for VringEpollHandlerError {} - /// Result of vring epoll handler operations. type VringEpollHandlerResult = std::result::Result; -pub struct VringEpollHandler { +struct VringEpollHandler { backend: Arc>, vrings: Vec>>, - mem: Option, - epoll_fd: RawFd, } impl VringEpollHandler { - fn update_memory(&mut self, mem: Option) { - self.mem = mem; - } - - /// Trigger the processing of a virtqueue. This function is meant to be - /// used by the caller whenever it might need some available queues to - /// send data back to the guest. - /// A concrete example is a backend registering one extra listener for - /// data that needs to be sent to the guest. When the associated event - /// is triggered, the backend will be invoked through its `handle_event` - /// implementation. And in this case, the way to handle the event is to - /// call into `process_queue` to let it invoke the backend implementation - /// of `process_queue`. With this twisted trick, all common parts related - /// to the virtqueues can remain part of the library. - pub fn process_queue(&mut self, q_idx: u16) -> VringEpollHandlerResult<()> { - let vring = &mut self.vrings[q_idx as usize].write().unwrap(); - let mut used_desc_heads = vec![(0, 0); vring.queue.size as usize]; - let mut used_count = 0; - if let Some(mem) = &self.mem { - for avail_desc in vring.queue.iter(&mem) { - let used_len = self - .backend - .write() - .unwrap() - .process_queue(q_idx, &avail_desc, &mem) - .map_err(VringEpollHandlerError::ProcessQueueBackendProcessing)?; - - used_desc_heads[used_count] = (avail_desc.index, used_len); - used_count += 1; - } - - for &(desc_index, len) in &used_desc_heads[..used_count] { - vring.queue.add_used(&mem, desc_index, len); - } - } - - if used_count > 0 { - if let Some(call) = &vring.call { - call.write(1) - .map_err(VringEpollHandlerError::SignalUsedQueue)?; - } - } - - Ok(()) - } - fn handle_event( - &mut self, + &self, device_event: u16, evset: epoll::Events, ) -> VringEpollHandlerResult { let num_queues = self.vrings.len(); - match device_event as usize { - x if x < num_queues => { - if let Some(kick) = &self.vrings[device_event as usize].read().unwrap().kick { - kick.read() - .map_err(VringEpollHandlerError::HandleEventReadKick)?; - } - - // If the vring is not enabled, it should not be processed. - // The event is only read to be discarded. - if !self.vrings[device_event as usize].read().unwrap().enabled { - return Ok(false); - } - - self.process_queue(device_event)?; - Ok(false) + if (device_event as usize) < num_queues { + if let Some(kick) = &self.vrings[device_event as usize].read().unwrap().kick { + kick.read() + .map_err(VringEpollHandlerError::HandleEventReadKick)?; } - _ => self - .backend - .write() - .unwrap() - .handle_event(device_event, evset) - .map_err(VringEpollHandlerError::HandleEventBackendHandling), - } - } - fn register_vring_listener(&self, q_idx: usize) -> VringEpollHandlerResult<()> { - if let Some(fd) = &self.vrings[q_idx].read().unwrap().kick { - self.register_listener(fd.as_raw_fd(), epoll::Events::EPOLLIN, q_idx as u64) - .map_err(VringEpollHandlerError::RegisterVringListener) - } else { - Ok(()) + // If the vring is not enabled, it should not be processed. + // The event is only read to be discarded. + if !self.vrings[device_event as usize].read().unwrap().enabled { + return Ok(false); + } } - } - fn unregister_vring_listener(&self, q_idx: usize) -> VringEpollHandlerResult<()> { - if let Some(fd) = &self.vrings[q_idx].read().unwrap().kick { - self.unregister_listener(fd.as_raw_fd(), epoll::Events::EPOLLIN, q_idx as u64) - .map_err(VringEpollHandlerError::UnregisterVringListener) - } else { - Ok(()) + self.backend + .write() + .unwrap() + .handle_event(device_event, evset, &self.vrings) + .map_err(VringEpollHandlerError::HandleEventBackendHandling) + } +} + +#[derive(Debug)] +/// Errors related to vring worker. +enum VringWorkerError { + /// Failed while waiting for events. + EpollWait(io::Error), + /// Failed to handle the event. + HandleEvent(VringEpollHandlerError), +} + +/// Result of vring worker operations. +type VringWorkerResult = std::result::Result; + +pub struct VringWorker { + epoll_fd: RawFd, +} + +impl VringWorker { + fn run(&self, handler: VringEpollHandler) -> VringWorkerResult<()> { + const EPOLL_EVENTS_LEN: usize = 100; + let mut events = vec![epoll::Event::new(epoll::Events::empty(), 0); EPOLL_EVENTS_LEN]; + + 'epoll: loop { + let num_events = match epoll::wait(self.epoll_fd, -1, &mut events[..]) { + Ok(res) => res, + Err(e) => { + if e.kind() == io::ErrorKind::Interrupted { + // It's well defined from the epoll_wait() syscall + // documentation that the epoll loop can be interrupted + // before any of the requested events occurred or the + // timeout expired. In both those cases, epoll_wait() + // returns an error of type EINTR, but this should not + // be considered as a regular error. Instead it is more + // appropriate to retry, by calling into epoll_wait(). + continue; + } + return Err(VringWorkerError::EpollWait(e)); + } + }; + + for event in events.iter().take(num_events) { + let evset = match epoll::Events::from_bits(event.events) { + Some(evset) => evset, + None => { + let evbits = event.events; + println!("epoll: ignoring unknown event set: 0x{:x}", evbits); + continue; + } + }; + + let ev_type = event.data as u16; + + if handler + .handle_event(ev_type, evset) + .map_err(VringWorkerError::HandleEvent)? + { + break 'epoll; + } + } } + + Ok(()) } /// Register a custom event only meaningful to the caller. When this event @@ -389,73 +356,6 @@ impl VringEpollHandler { } } -#[derive(Debug)] -/// Errors related to vring worker. -enum VringWorkerError { - /// Failed while waiting for events. - EpollWait(io::Error), - /// Failed to handle event. - HandleEvent(VringEpollHandlerError), -} - -/// Result of vring worker operations. -type VringWorkerResult = std::result::Result; - -struct VringWorker { - handler: Arc>>, -} - -impl VringWorker { - fn run(&self, epoll_fd: RawFd) -> VringWorkerResult<()> { - const EPOLL_EVENTS_LEN: usize = 100; - let mut events = vec![epoll::Event::new(epoll::Events::empty(), 0); EPOLL_EVENTS_LEN]; - - 'epoll: loop { - let num_events = match epoll::wait(epoll_fd, -1, &mut events[..]) { - Ok(res) => res, - Err(e) => { - if e.kind() == io::ErrorKind::Interrupted { - // It's well defined from the epoll_wait() syscall - // documentation that the epoll loop can be interrupted - // before any of the requested events occurred or the - // timeout expired. In both those cases, epoll_wait() - // returns an error of type EINTR, but this should not - // be considered as a regular error. Instead it is more - // appropriate to retry, by calling into epoll_wait(). - continue; - } - return Err(VringWorkerError::EpollWait(e)); - } - }; - - for event in events.iter().take(num_events) { - let evset = match epoll::Events::from_bits(event.events) { - Some(evset) => evset, - None => { - let evbits = event.events; - println!("epoll: ignoring unknown event set: 0x{:x}", evbits); - continue; - } - }; - - let ev_type = event.data as u16; - - if self - .handler - .write() - .unwrap() - .handle_event(ev_type, evset) - .map_err(VringWorkerError::HandleEvent)? - { - break 'epoll; - } - } - } - - Ok(()) - } -} - #[derive(Debug)] /// Errors related to vhost-user handler. pub enum VhostUserHandlerError { @@ -486,7 +386,7 @@ type VhostUserHandlerResult = std::result::Result; struct VhostUserHandler { backend: Arc>, - vring_handler: Arc>>, + worker: Arc, owned: bool, features_acked: bool, acked_features: u64, @@ -502,28 +402,30 @@ impl VhostUserHandler { let num_queues = backend.read().unwrap().num_queues(); let max_queue_size = backend.read().unwrap().max_queue_size(); - let vrings = vec![Arc::new(RwLock::new(Vring::new(max_queue_size as u16))); num_queues]; + let mut vrings: Vec>> = Vec::new(); + for _ in 0..num_queues { + let vring = Arc::new(RwLock::new(Vring::new(max_queue_size as u16))); + vrings.push(vring); + } + // Create the epoll file descriptor let epoll_fd = epoll::create(true).map_err(VhostUserHandlerError::EpollCreateFd)?; - let vring_handler = Arc::new(RwLock::new(VringEpollHandler { + let vring_handler = VringEpollHandler { backend: backend.clone(), vrings: vrings.clone(), - mem: None, - epoll_fd, - })); - let worker = VringWorker { - handler: vring_handler.clone(), }; + let vring_worker = Arc::new(VringWorker { epoll_fd }); + let worker = vring_worker.clone(); thread::Builder::new() .name("vring_worker".to_string()) - .spawn(move || worker.run(epoll_fd)) + .spawn(move || vring_worker.run(vring_handler)) .map_err(VhostUserHandlerError::SpawnVringWorker)?; Ok(VhostUserHandler { backend, - vring_handler, + worker, owned: false, features_acked: false, acked_features: 0, @@ -535,8 +437,8 @@ impl VhostUserHandler { }) } - fn get_vring_handler(&self) -> Arc>> { - self.vring_handler.clone() + fn get_vring_worker(&self) -> Arc { + self.worker.clone() } fn vmm_va_to_gpa(&self, vmm_va: u64) -> VhostUserHandlerResult { @@ -638,7 +540,13 @@ impl VhostUserSlaveReqHandler for VhostUserHandler { let mem = GuestMemoryMmap::from_ranges_with_files(regions).map_err(|e| { VhostUserError::ReqHandlerError(io::Error::new(io::ErrorKind::Other, e)) })?; - self.vring_handler.write().unwrap().update_memory(Some(mem)); + self.backend + .write() + .unwrap() + .update_memory(mem) + .map_err(|e| { + VhostUserError::ReqHandlerError(io::Error::new(io::ErrorKind::Other, e)) + })?; self.memory = Some(Memory { mappings }); Ok(()) @@ -716,13 +624,11 @@ impl VhostUserSlaveReqHandler for VhostUserHandler { // VHOST_USER_SET_VRING_KICK, and stop ring upon receiving // VHOST_USER_GET_VRING_BASE. self.vrings[index as usize].write().unwrap().queue.ready = false; - self.vring_handler - .read() - .unwrap() - .unregister_vring_listener(index as usize) - .map_err(|e| { - VhostUserError::ReqHandlerError(io::Error::new(io::ErrorKind::Other, e)) - })?; + if let Some(fd) = self.vrings[index as usize].read().unwrap().kick.as_ref() { + self.worker + .unregister_listener(fd.as_raw_fd(), epoll::Events::EPOLLIN, index as u64) + .map_err(VhostUserError::ReqHandlerError)?; + } let next_avail = self.vrings[index as usize] .read() @@ -752,13 +658,11 @@ impl VhostUserSlaveReqHandler for VhostUserHandler { // VHOST_USER_SET_VRING_KICK, and stop ring upon receiving // VHOST_USER_GET_VRING_BASE. self.vrings[index as usize].write().unwrap().queue.ready = true; - self.vring_handler - .read() - .unwrap() - .register_vring_listener(index as usize) - .map_err(|e| { - VhostUserError::ReqHandlerError(io::Error::new(io::ErrorKind::Other, e)) - })?; + if let Some(fd) = self.vrings[index as usize].read().unwrap().kick.as_ref() { + self.worker + .register_listener(fd.as_raw_fd(), epoll::Events::EPOLLIN, index as u64) + .map_err(VhostUserError::ReqHandlerError)?; + } Ok(()) } From 1af12219110976e6c5df3ade6cb73cb0b8222fd6 Mon Sep 17 00:00:00 2001 From: Sebastien Boeuf Date: Mon, 30 Sep 2019 10:17:01 -0700 Subject: [PATCH 016/139] Fix clippy issues Signed-off-by: Sebastien Boeuf --- src/lib.rs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index 006cc74..fe116c2 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -74,7 +74,7 @@ pub trait VhostUserBackend: Send + Sync + 'static { &mut self, device_event: u16, evset: epoll::Events, - vrings: &Vec>>, + vrings: &[Arc>], ) -> result::Result; /// Get virtio device configuration. @@ -626,7 +626,7 @@ impl VhostUserSlaveReqHandler for VhostUserHandler { self.vrings[index as usize].write().unwrap().queue.ready = false; if let Some(fd) = self.vrings[index as usize].read().unwrap().kick.as_ref() { self.worker - .unregister_listener(fd.as_raw_fd(), epoll::Events::EPOLLIN, index as u64) + .unregister_listener(fd.as_raw_fd(), epoll::Events::EPOLLIN, u64::from(index)) .map_err(VhostUserError::ReqHandlerError)?; } @@ -660,7 +660,7 @@ impl VhostUserSlaveReqHandler for VhostUserHandler { self.vrings[index as usize].write().unwrap().queue.ready = true; if let Some(fd) = self.vrings[index as usize].read().unwrap().kick.as_ref() { self.worker - .register_listener(fd.as_raw_fd(), epoll::Events::EPOLLIN, index as u64) + .register_listener(fd.as_raw_fd(), epoll::Events::EPOLLIN, u64::from(index)) .map_err(VhostUserError::ReqHandlerError)?; } From 6dce71c9484a7b1186d0673c57f65b321256bf63 Mon Sep 17 00:00:00 2001 From: Sergio Lopez Date: Wed, 23 Oct 2019 16:07:03 +0200 Subject: [PATCH 017/139] Fix VhostUserConfig payload management The VhostUserConfig carries a message with a payload, the contents of which depend on the kind of device being emulated. With this change, we calculate the offset of the payload within the message, check its size corresponds to the expected one, and pass it to the backend as a reference to a slice adjusted to the payload dimensions. The backend will be responsible of validating the payload, as it's the one aware of its expected contents. Signed-off-by: Sergio Lopez --- src/lib.rs | 22 ---------------------- 1 file changed, 22 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index fe116c2..fd78671 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -15,7 +15,6 @@ use std::thread; use vhost_rs::vhost_user::message::{ VhostUserConfigFlags, VhostUserMemoryRegion, VhostUserProtocolFeatures, VhostUserVirtioFeatures, VhostUserVringAddrFlags, VhostUserVringState, - VHOST_USER_CONFIG_OFFSET, VHOST_USER_CONFIG_SIZE, }; use vhost_rs::vhost_user::{ Error as VhostUserError, Result as VhostUserResult, SlaveListener, VhostUserSlaveReqHandler, @@ -721,16 +720,6 @@ impl VhostUserSlaveReqHandler for VhostUserHandler { size: u32, _flags: VhostUserConfigFlags, ) -> VhostUserResult> { - if self.acked_features & VhostUserProtocolFeatures::CONFIG.bits() == 0 { - return Err(VhostUserError::InvalidOperation); - } else if offset < VHOST_USER_CONFIG_OFFSET - || offset >= VHOST_USER_CONFIG_SIZE - || size > VHOST_USER_CONFIG_SIZE - VHOST_USER_CONFIG_OFFSET - || size + offset > VHOST_USER_CONFIG_SIZE - { - return Err(VhostUserError::InvalidParam); - } - Ok(self.backend.read().unwrap().get_config(offset, size)) } @@ -740,17 +729,6 @@ impl VhostUserSlaveReqHandler for VhostUserHandler { buf: &[u8], _flags: VhostUserConfigFlags, ) -> VhostUserResult<()> { - let size = buf.len() as u32; - if self.acked_features & VhostUserProtocolFeatures::CONFIG.bits() == 0 { - return Err(VhostUserError::InvalidOperation); - } else if offset < VHOST_USER_CONFIG_OFFSET - || offset >= VHOST_USER_CONFIG_SIZE - || size > VHOST_USER_CONFIG_SIZE - VHOST_USER_CONFIG_OFFSET - || size + offset > VHOST_USER_CONFIG_SIZE - { - return Err(VhostUserError::InvalidParam); - } - self.backend .write() .unwrap() From e22f9262ff105241dd64ca65da29cccdf05e89d2 Mon Sep 17 00:00:00 2001 From: Sergio Lopez Date: Wed, 23 Oct 2019 16:24:07 +0200 Subject: [PATCH 018/139] Move protocol_features to the backend Extend VhostUserBackend trait with protocol_features(), so device backend implementations can freely define which protocol features they want to support. Signed-off-by: Sergio Lopez --- src/lib.rs | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/lib.rs b/src/lib.rs index fd78671..c74d4c8 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -62,6 +62,9 @@ pub trait VhostUserBackend: Send + Sync + 'static { /// Virtio features. fn features(&self) -> u64; + /// Virtio protocol features. + fn protocol_features(&self) -> VhostUserProtocolFeatures; + /// Update guest memory regions. fn update_memory(&mut self, mem: GuestMemoryMmap) -> result::Result<(), io::Error>; @@ -501,7 +504,7 @@ impl VhostUserSlaveReqHandler for VhostUserHandler { } fn get_protocol_features(&mut self) -> VhostUserResult { - Ok(VhostUserProtocolFeatures::all()) + Ok(self.backend.read().unwrap().protocol_features()) } fn set_protocol_features(&mut self, features: u64) -> VhostUserResult<()> { From 1a9d3b75c61ffd82f63bc5465c5f8b165f838288 Mon Sep 17 00:00:00 2001 From: Sergio Lopez Date: Thu, 24 Oct 2019 20:35:10 +0200 Subject: [PATCH 019/139] Remove ownership check in set_features() set_features() fails with InvalidOperation if !self.owned. I don't see this as a requirement in the specification and, in fact, vm-virtio implementation for resetting the device calls SET_FEATURES just after RESET_OWNER. Signed-off-by: Sergio Lopez --- src/lib.rs | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index c74d4c8..b5be12b 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -478,9 +478,7 @@ impl VhostUserSlaveReqHandler for VhostUserHandler { } fn set_features(&mut self, features: u64) -> VhostUserResult<()> { - if !self.owned { - return Err(VhostUserError::InvalidOperation); - } else if (features & !self.backend.read().unwrap().features()) != 0 { + if (features & !self.backend.read().unwrap().features()) != 0 { return Err(VhostUserError::InvalidParam); } From 2055d112fb615f115e586175d19b2f567dfd7470 Mon Sep 17 00:00:00 2001 From: Sergio Lopez Date: Thu, 28 Nov 2019 12:56:35 +0100 Subject: [PATCH 020/139] Fix memory region offsetting The way in which offsets are currently use in memory regions is derived from QEMU's contrib/libvhost-user, but while this one works mainly by translating vmm va's to local va's, vm-memory expects us to use proper guest addresses and thus, define memory regions that actually match the guest's memory disposition. With this change, we create the memory regions with the proper length and offsets, extend AddrMapping to store the guest physical address, and use the latter instead of offset in vmm_va_to_gpa(). Signed-off-by: Sergio Lopez --- src/lib.rs | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index b5be12b..8a266dd 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -169,7 +169,7 @@ impl VhostUserDaemon { struct AddrMapping { vmm_addr: u64, size: u64, - offset: u64, + gpa_base: u64, } struct Memory { @@ -447,7 +447,7 @@ impl VhostUserHandler { if let Some(memory) = &self.memory { for mapping in memory.mappings.iter() { if vmm_va >= mapping.vmm_addr && vmm_va < mapping.vmm_addr + mapping.size { - return Ok(vmm_va - mapping.vmm_addr + mapping.offset); + return Ok(vmm_va - mapping.vmm_addr + mapping.gpa_base); } } } @@ -525,15 +525,15 @@ impl VhostUserSlaveReqHandler for VhostUserHandler { for (idx, region) in ctx.iter().enumerate() { let g_addr = GuestAddress(region.guest_phys_addr); - let len = (region.memory_size + region.mmap_offset) as usize; + let len = region.memory_size as usize; let file = unsafe { File::from_raw_fd(fds[idx]) }; - let f_off = FileOffset::new(file, 0); + let f_off = FileOffset::new(file, region.mmap_offset); regions.push((g_addr, len, Some(f_off))); mappings.push(AddrMapping { vmm_addr: region.user_addr, size: region.memory_size, - offset: region.mmap_offset, + gpa_base: region.guest_phys_addr, }); } From 4a61df604c2251462c90c624363332ee9468f3e3 Mon Sep 17 00:00:00 2001 From: Rob Bradford Date: Thu, 6 Feb 2020 11:21:21 +0000 Subject: [PATCH 021/139] Forward the error from main thread The main thread returns a Result with any errors from it. Although the error from the join itself was being returned the real error from the thread was being ignored so ensure that it is forwarded. Signed-off-by: Rob Bradford --- src/lib.rs | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index 8a266dd..fb8c7dc 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -153,9 +153,10 @@ impl VhostUserDaemon { /// terminate. pub fn wait(&mut self) -> Result<()> { if let Some(handle) = self.main_thread.take() { - let _ = handle.join().map_err(Error::WaitDaemon)?; + handle.join().map_err(Error::WaitDaemon)? + } else { + Ok(()) } - Ok(()) } /// Retrieve the vring worker. This is necessary to perform further From f92ade7f458eec4841ba2093004b85e71ff8b96d Mon Sep 17 00:00:00 2001 From: Rob Bradford Date: Thu, 6 Feb 2020 17:10:29 +0000 Subject: [PATCH 022/139] Wait on the worker thread Check the return value from the worker thread by saving the thread handle and waiting for it to return. Signed-off-by: Rob Bradford --- src/lib.rs | 25 +++++++++++++++++++++---- 1 file changed, 21 insertions(+), 4 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index fb8c7dc..c0db6a6 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -4,6 +4,9 @@ // Copyright 2019 Alibaba Cloud Computing. All rights reserved. // SPDX-License-Identifier: Apache-2.0 +#[macro_use] +extern crate log; + use std::error; use std::fs::File; use std::io; @@ -398,6 +401,7 @@ struct VhostUserHandler { max_queue_size: usize, memory: Option, vrings: Vec>>, + worker_thread: Option>>, } impl VhostUserHandler { @@ -421,10 +425,12 @@ impl VhostUserHandler { let vring_worker = Arc::new(VringWorker { epoll_fd }); let worker = vring_worker.clone(); - thread::Builder::new() - .name("vring_worker".to_string()) - .spawn(move || vring_worker.run(vring_handler)) - .map_err(VhostUserHandlerError::SpawnVringWorker)?; + let worker_thread = Some( + thread::Builder::new() + .name("vring_worker".to_string()) + .spawn(move || vring_worker.run(vring_handler)) + .map_err(VhostUserHandlerError::SpawnVringWorker)?, + ); Ok(VhostUserHandler { backend, @@ -437,6 +443,7 @@ impl VhostUserHandler { max_queue_size, memory: None, vrings, + worker_thread, }) } @@ -738,3 +745,13 @@ impl VhostUserSlaveReqHandler for VhostUserHandler { .map_err(VhostUserError::ReqHandlerError) } } + +impl Drop for VhostUserHandler { + fn drop(&mut self) { + if let Some(thread) = self.worker_thread.take() { + if let Err(e) = thread.join() { + error!("Error in vring worker: {:?}", e); + } + } + } +} From 92a3744b3b12d0c117da655bd37b19fb3cc5afd2 Mon Sep 17 00:00:00 2001 From: Rob Bradford Date: Tue, 11 Feb 2020 11:24:44 +0000 Subject: [PATCH 023/139] Add support for handling exiting of worker thread All backends currently provide their own implementation for triggering the worker thread to exit via an EventFd. Modify the VhostUserBackend trait to allow a common implementation strategy that backends can use to provide an EventFd (and optional id) that can be used to trigger the worker to exit. Signed-off-by: Rob Bradford --- src/lib.rs | 39 +++++++++++++++++++++++++++++++++++++-- 1 file changed, 37 insertions(+), 2 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index c0db6a6..06ddcf7 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -95,6 +95,14 @@ pub trait VhostUserBackend: Send + Sync + 'static { fn set_config(&mut self, _offset: u32, _buf: &[u8]) -> result::Result<(), io::Error> { Ok(()) } + + /// Provide an exit EventFd + /// When this EventFd is written to the worker thread will exit. An optional id may + /// also be provided, if it not provided then the exit event will be first event id + /// after the last queue + fn exit_event(&self) -> Option<(EventFd, Option)> { + None + } } /// This structure is the public API the backend is allowed to interact with @@ -231,6 +239,7 @@ type VringEpollHandlerResult = std::result::Result struct VringEpollHandler { backend: Arc>, vrings: Vec>>, + exit_event_id: Option, } impl VringEpollHandler { @@ -239,6 +248,10 @@ impl VringEpollHandler { device_event: u16, evset: epoll::Events, ) -> VringEpollHandlerResult { + if self.exit_event_id == Some(device_event) { + return Ok(true); + } + let num_queues = self.vrings.len(); if (device_event as usize) < num_queues { if let Some(kick) = &self.vrings[device_event as usize].read().unwrap().kick { @@ -371,6 +384,8 @@ pub enum VhostUserHandlerError { SpawnVringWorker(io::Error), /// Could not find the mapping from memory regions. MissingMemoryMapping, + /// Could not register exit event + RegisterExitEvent(io::Error), } impl std::fmt::Display for VhostUserHandlerError { @@ -381,6 +396,9 @@ impl std::fmt::Display for VhostUserHandlerError { write!(f, "failed spawning the vring worker: {}", e) } VhostUserHandlerError::MissingMemoryMapping => write!(f, "Missing memory mapping"), + VhostUserHandlerError::RegisterExitEvent(e) => { + write!(f, "Failed to register exit event: {}", e) + } } } } @@ -418,12 +436,29 @@ impl VhostUserHandler { // Create the epoll file descriptor let epoll_fd = epoll::create(true).map_err(VhostUserHandlerError::EpollCreateFd)?; + let vring_worker = Arc::new(VringWorker { epoll_fd }); + let worker = vring_worker.clone(); + + let exit_event_id = + if let Some((exit_event_fd, exit_event_id)) = backend.read().unwrap().exit_event() { + let exit_event_id = exit_event_id.unwrap_or(num_queues as u16); + worker + .register_listener( + exit_event_fd.as_raw_fd(), + epoll::Events::EPOLLIN, + u64::from(exit_event_id), + ) + .map_err(VhostUserHandlerError::RegisterExitEvent)?; + Some(exit_event_id) + } else { + None + }; + let vring_handler = VringEpollHandler { backend: backend.clone(), vrings: vrings.clone(), + exit_event_id, }; - let vring_worker = Arc::new(VringWorker { epoll_fd }); - let worker = vring_worker.clone(); let worker_thread = Some( thread::Builder::new() From 6d70ed20eeeace11c91873da786628d86d7ceaf5 Mon Sep 17 00:00:00 2001 From: Liu Bo Date: Wed, 12 Feb 2020 15:15:04 -0800 Subject: [PATCH 024/139] Add the ability to set slave req fd This adds the missing part of supporting virtiofs dax on the slave end, that is, receiving a socket pair fd from the master end to set up a communication channel for sending setupmapping & removemapping messages. Signed-off-by: Liu Bo --- src/lib.rs | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/src/lib.rs b/src/lib.rs index 06ddcf7..7df0069 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -20,7 +20,8 @@ use vhost_rs::vhost_user::message::{ VhostUserVirtioFeatures, VhostUserVringAddrFlags, VhostUserVringState, }; use vhost_rs::vhost_user::{ - Error as VhostUserError, Result as VhostUserResult, SlaveListener, VhostUserSlaveReqHandler, + Error as VhostUserError, Result as VhostUserResult, SlaveFsCacheReq, SlaveListener, + VhostUserSlaveReqHandler, }; use vm_memory::guest_memory::FileOffset; use vm_memory::{GuestAddress, GuestMemoryMmap}; @@ -103,6 +104,11 @@ pub trait VhostUserBackend: Send + Sync + 'static { fn exit_event(&self) -> Option<(EventFd, Option)> { None } + + /// Set slave fd. + /// A default implementation is provided as we cannot expect all backends + /// to implement this function. + fn set_slave_req_fd(&mut self, _vu_req: SlaveFsCacheReq) {} } /// This structure is the public API the backend is allowed to interact with @@ -779,6 +785,10 @@ impl VhostUserSlaveReqHandler for VhostUserHandler { .set_config(offset, buf) .map_err(VhostUserError::ReqHandlerError) } + + fn set_slave_req_fd(&mut self, vu_req: SlaveFsCacheReq) { + self.backend.write().unwrap().set_slave_req_fd(vu_req); + } } impl Drop for VhostUserHandler { From 3959bd6a01491374b0f0ac02685cae00d9fdcbf5 Mon Sep 17 00:00:00 2001 From: Sergio Lopez Date: Fri, 14 Feb 2020 06:27:47 -0500 Subject: [PATCH 025/139] Add helpers for EVENT_IDX Add helpers to Vring and VhostUserSlaveReqHandler for EVENT_IDX, so consumers of this crate can make use of this feature. Signed-off-by: Sergio Lopez --- src/lib.rs | 52 ++++++++++++++++++++++++++++++++++++++++++++++++---- 1 file changed, 48 insertions(+), 4 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index 7df0069..0e94da5 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -23,6 +23,7 @@ use vhost_rs::vhost_user::{ Error as VhostUserError, Result as VhostUserResult, SlaveFsCacheReq, SlaveListener, VhostUserSlaveReqHandler, }; +use virtio_bindings::bindings::virtio_ring::VIRTIO_RING_F_EVENT_IDX; use vm_memory::guest_memory::FileOffset; use vm_memory::{GuestAddress, GuestMemoryMmap}; use vm_virtio::Queue; @@ -69,6 +70,9 @@ pub trait VhostUserBackend: Send + Sync + 'static { /// Virtio protocol features. fn protocol_features(&self) -> VhostUserProtocolFeatures; + /// Tell the backend if EVENT_IDX has been negotiated. + fn set_event_idx(&mut self, enabled: bool); + /// Update guest memory regions. fn update_memory(&mut self, mem: GuestMemoryMmap) -> result::Result<(), io::Error>; @@ -200,6 +204,8 @@ pub struct Vring { call: Option, err: Option, enabled: bool, + event_idx: bool, + signalled_used: Option>, } impl Vring { @@ -210,6 +216,8 @@ impl Vring { call: None, err: None, enabled: false, + event_idx: false, + signalled_used: None, } } @@ -217,12 +225,41 @@ impl Vring { &mut self.queue } - pub fn signal_used_queue(&self) -> result::Result<(), io::Error> { - if let Some(call) = self.call.as_ref() { - return call.write(1); + pub fn set_event_idx(&mut self, enabled: bool) { + /* Also reset the last signalled event */ + self.signalled_used = None; + self.event_idx = enabled; + } + + pub fn needs_notification( + &mut self, + used_idx: Wrapping, + used_event: Option>, + ) -> bool { + if !self.event_idx { + return true; } - Ok(()) + let mut notify = true; + + if let Some(old_idx) = self.signalled_used { + if let Some(used_event) = used_event { + if (used_idx - used_event - Wrapping(1u16)) >= (used_idx - old_idx) { + notify = false; + } + } + } + + self.signalled_used = Some(used_idx); + notify + } + + pub fn signal_used_queue(&mut self) -> result::Result<(), io::Error> { + if let Some(call) = self.call.as_ref() { + call.write(1) + } else { + Ok(()) + } } } @@ -660,6 +697,13 @@ impl VhostUserSlaveReqHandler for VhostUserHandler { .queue .next_avail = Wrapping(base as u16); self.vrings[index as usize].write().unwrap().queue.next_used = Wrapping(base as u16); + + let event_idx: bool = (self.acked_features & (1 << VIRTIO_RING_F_EVENT_IDX)) != 0; + self.vrings[index as usize] + .write() + .unwrap() + .set_event_idx(event_idx); + self.backend.write().unwrap().set_event_idx(event_idx); Ok(()) } From 0d6d6166633c2e8970fc66ad91a38ef4f2579978 Mon Sep 17 00:00:00 2001 From: Rob Bradford Date: Wed, 19 Feb 2020 14:22:30 +0000 Subject: [PATCH 026/139] Don't report out socket broken errors This is a perfectly acceptable situation as it causes the backend to exit because the VMM has closed the connection. This addresses the rather ugly reporting of errors from the backend that appears interleaved with the output from the VMM. Signed-off-by: Rob Bradford --- src/lib.rs | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/lib.rs b/src/lib.rs index 0e94da5..6256e06 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -174,7 +174,11 @@ impl VhostUserDaemon { /// terminate. pub fn wait(&mut self) -> Result<()> { if let Some(handle) = self.main_thread.take() { - handle.join().map_err(Error::WaitDaemon)? + match handle.join().map_err(Error::WaitDaemon)? { + Ok(()) => Ok(()), + Err(Error::HandleRequest(VhostUserError::SocketBroken(_))) => Ok(()), + Err(e) => Err(e), + } } else { Ok(()) } From 31af3171dc436965a2d47151059374f85888e324 Mon Sep 17 00:00:00 2001 From: Sergio Lopez Date: Tue, 10 Mar 2020 05:43:13 -0400 Subject: [PATCH 027/139] Call get_used_event from needs_notification This change, combined with the compiler hint to inline get_used_event, shortens the window between the memory read and the actual check by calling get_used_event from needs_notification. Without it, when putting enough pressure on the vring, it's possible that a notification is wrongly omitted, causing the queue to stall. Signed-off-by: Sergio Lopez --- src/lib.rs | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index 6256e06..10184a1 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -235,11 +235,7 @@ impl Vring { self.event_idx = enabled; } - pub fn needs_notification( - &mut self, - used_idx: Wrapping, - used_event: Option>, - ) -> bool { + pub fn needs_notification(&mut self, mem: &GuestMemoryMmap, used_idx: Wrapping) -> bool { if !self.event_idx { return true; } @@ -247,7 +243,7 @@ impl Vring { let mut notify = true; if let Some(old_idx) = self.signalled_used { - if let Some(used_event) = used_event { + if let Some(used_event) = self.mut_queue().get_used_event(&mem) { if (used_idx - used_event - Wrapping(1u16)) >= (used_idx - old_idx) { notify = false; } From 96b4b49a1ca1f892c4ee31b765668f78a2b714a4 Mon Sep 17 00:00:00 2001 From: Sebastien Boeuf Date: Thu, 9 Apr 2020 13:14:05 +0200 Subject: [PATCH 028/139] Change handle_event as immutable By changing the mutability of this function, after adapting all backends, we should be able to implement multithreads with multiqueues support without hitting a bottleneck on the backend locking. Signed-off-by: Sebastien Boeuf --- src/lib.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index 10184a1..1be1cb1 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -81,7 +81,7 @@ pub trait VhostUserBackend: Send + Sync + 'static { /// virtqueues on its own, but does not know what to do with events /// happening on custom listeners. fn handle_event( - &mut self, + &self, device_event: u16, evset: epoll::Events, vrings: &[Arc>], @@ -310,7 +310,7 @@ impl VringEpollHandler { } self.backend - .write() + .read() .unwrap() .handle_event(device_event, evset, &self.vrings) .map_err(VringEpollHandlerError::HandleEventBackendHandling) From 0e32b1242cdbac3ba38c34009473d0be1ccd5a01 Mon Sep 17 00:00:00 2001 From: Sebastien Boeuf Date: Thu, 9 Apr 2020 10:52:54 +0200 Subject: [PATCH 029/139] Add the ability to start multiple threads In order to support multiqueues running on multiple threads for increasing the IO performances, this commit introduces a new function queues_per_thread() to the VhostUserBackend trait. This gives each backend implementation the opportunity to define which queues belong to which thread. Signed-off-by: Sebastien Boeuf --- src/lib.rs | 96 +++++++++++++++++++++++++++++++++++++++--------------- 1 file changed, 69 insertions(+), 27 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index 1be1cb1..611d9e7 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -113,6 +113,10 @@ pub trait VhostUserBackend: Send + Sync + 'static { /// A default implementation is provided as we cannot expect all backends /// to implement this function. fn set_slave_req_fd(&mut self, _vu_req: SlaveFsCacheReq) {} + + fn queues_per_thread(&self) -> Vec { + vec![0xffff_ffff] + } } /// This structure is the public API the backend is allowed to interact with @@ -453,22 +457,24 @@ type VhostUserHandlerResult = std::result::Result; struct VhostUserHandler { backend: Arc>, - worker: Arc, + workers: Vec>, owned: bool, features_acked: bool, acked_features: u64, acked_protocol_features: u64, num_queues: usize, max_queue_size: usize, + queues_per_thread: Vec, memory: Option, vrings: Vec>>, - worker_thread: Option>>, + worker_threads: Vec>>, } impl VhostUserHandler { fn new(backend: Arc>) -> VhostUserHandlerResult { let num_queues = backend.read().unwrap().num_queues(); let max_queue_size = backend.read().unwrap().max_queue_size(); + let queues_per_thread = backend.read().unwrap().queues_per_thread(); let mut vrings: Vec>> = Vec::new(); for _ in 0..num_queues { @@ -476,14 +482,18 @@ impl VhostUserHandler { vrings.push(vring); } - // Create the epoll file descriptor - let epoll_fd = epoll::create(true).map_err(VhostUserHandlerError::EpollCreateFd)?; + let mut workers = Vec::new(); + let mut worker_threads = Vec::new(); + for queues_mask in queues_per_thread.iter() { + // Create the epoll file descriptor + let epoll_fd = epoll::create(true).map_err(VhostUserHandlerError::EpollCreateFd)?; - let vring_worker = Arc::new(VringWorker { epoll_fd }); - let worker = vring_worker.clone(); + let vring_worker = Arc::new(VringWorker { epoll_fd }); + let worker = vring_worker.clone(); - let exit_event_id = - if let Some((exit_event_fd, exit_event_id)) = backend.read().unwrap().exit_event() { + let exit_event_id = if let Some((exit_event_fd, exit_event_id)) = + backend.read().unwrap().exit_event() + { let exit_event_id = exit_event_id.unwrap_or(num_queues as u16); worker .register_listener( @@ -497,36 +507,46 @@ impl VhostUserHandler { None }; - let vring_handler = VringEpollHandler { - backend: backend.clone(), - vrings: vrings.clone(), - exit_event_id, - }; + let mut thread_vrings: Vec>> = Vec::new(); + for (index, vring) in vrings.iter().enumerate() { + if (queues_mask >> index) & 1u64 == 1u64 { + thread_vrings.push(vring.clone()); + } + } - let worker_thread = Some( - thread::Builder::new() + let vring_handler = VringEpollHandler { + backend: backend.clone(), + vrings: thread_vrings, + exit_event_id, + }; + + let worker_thread = thread::Builder::new() .name("vring_worker".to_string()) .spawn(move || vring_worker.run(vring_handler)) - .map_err(VhostUserHandlerError::SpawnVringWorker)?, - ); + .map_err(VhostUserHandlerError::SpawnVringWorker)?; + + workers.push(worker); + worker_threads.push(worker_thread); + } Ok(VhostUserHandler { backend, - worker, + workers, owned: false, features_acked: false, acked_features: 0, acked_protocol_features: 0, num_queues, max_queue_size, + queues_per_thread, memory: None, vrings, - worker_thread, + worker_threads, }) } fn get_vring_worker(&self) -> Arc { - self.worker.clone() + self.workers[0].clone() } fn vmm_va_to_gpa(&self, vmm_va: u64) -> VhostUserHandlerResult { @@ -718,9 +738,20 @@ impl VhostUserSlaveReqHandler for VhostUserHandler { // VHOST_USER_GET_VRING_BASE. self.vrings[index as usize].write().unwrap().queue.ready = false; if let Some(fd) = self.vrings[index as usize].read().unwrap().kick.as_ref() { - self.worker - .unregister_listener(fd.as_raw_fd(), epoll::Events::EPOLLIN, u64::from(index)) - .map_err(VhostUserError::ReqHandlerError)?; + for (thread_index, queues_mask) in self.queues_per_thread.iter().enumerate() { + let shifted_queues_mask = queues_mask >> index; + if shifted_queues_mask & 1u64 == 1u64 { + let evt_idx = queues_mask.count_ones() - shifted_queues_mask.count_ones(); + self.workers[thread_index] + .unregister_listener( + fd.as_raw_fd(), + epoll::Events::EPOLLIN, + u64::from(evt_idx), + ) + .map_err(VhostUserError::ReqHandlerError)?; + break; + } + } } let next_avail = self.vrings[index as usize] @@ -752,9 +783,20 @@ impl VhostUserSlaveReqHandler for VhostUserHandler { // VHOST_USER_GET_VRING_BASE. self.vrings[index as usize].write().unwrap().queue.ready = true; if let Some(fd) = self.vrings[index as usize].read().unwrap().kick.as_ref() { - self.worker - .register_listener(fd.as_raw_fd(), epoll::Events::EPOLLIN, u64::from(index)) - .map_err(VhostUserError::ReqHandlerError)?; + for (thread_index, queues_mask) in self.queues_per_thread.iter().enumerate() { + let shifted_queues_mask = queues_mask >> index; + if shifted_queues_mask & 1u64 == 1u64 { + let evt_idx = queues_mask.count_ones() - shifted_queues_mask.count_ones(); + self.workers[thread_index] + .register_listener( + fd.as_raw_fd(), + epoll::Events::EPOLLIN, + u64::from(evt_idx), + ) + .map_err(VhostUserError::ReqHandlerError)?; + break; + } + } } Ok(()) @@ -837,7 +879,7 @@ impl VhostUserSlaveReqHandler for VhostUserHandler { impl Drop for VhostUserHandler { fn drop(&mut self) { - if let Some(thread) = self.worker_thread.take() { + for thread in self.worker_threads.drain(..) { if let Err(e) = thread.join() { error!("Error in vring worker: {:?}", e); } From d08282c080c35575aa7ed322e74a77b5dedb2f2c Mon Sep 17 00:00:00 2001 From: Sebastien Boeuf Date: Thu, 9 Apr 2020 11:27:41 +0200 Subject: [PATCH 030/139] Return a list of vring workers Now that multiple worker threads can be run from the backend crate, it is important that each backend implementation can access every worker thread. Signed-off-by: Sebastien Boeuf --- src/lib.rs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index 611d9e7..602fcda 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -191,8 +191,8 @@ impl VhostUserDaemon { /// Retrieve the vring worker. This is necessary to perform further /// actions like registering and unregistering some extra event file /// descriptors. - pub fn get_vring_worker(&self) -> Arc { - self.handler.lock().unwrap().get_vring_worker() + pub fn get_vring_workers(&self) -> Vec> { + self.handler.lock().unwrap().get_vring_workers() } } @@ -545,8 +545,8 @@ impl VhostUserHandler { }) } - fn get_vring_worker(&self) -> Arc { - self.workers[0].clone() + fn get_vring_workers(&self) -> Vec> { + self.workers.clone() } fn vmm_va_to_gpa(&self, vmm_va: u64) -> VhostUserHandlerResult { From 41f00984bfe91a52f9523ec0328384f6276457f2 Mon Sep 17 00:00:00 2001 From: Sebastien Boeuf Date: Thu, 9 Apr 2020 17:49:11 +0200 Subject: [PATCH 031/139] Allow for one exit_event per thread By adding the "thread_index" parameter to the function exit_event() from the VhostUserBackend trait, the backend crate now has the ability to ask the backend implementation about the exit event related to a specific thread. Signed-off-by: Sebastien Boeuf --- src/lib.rs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index 602fcda..969c7ac 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -105,7 +105,7 @@ pub trait VhostUserBackend: Send + Sync + 'static { /// When this EventFd is written to the worker thread will exit. An optional id may /// also be provided, if it not provided then the exit event will be first event id /// after the last queue - fn exit_event(&self) -> Option<(EventFd, Option)> { + fn exit_event(&self, _thread_index: usize) -> Option<(EventFd, Option)> { None } @@ -484,7 +484,7 @@ impl VhostUserHandler { let mut workers = Vec::new(); let mut worker_threads = Vec::new(); - for queues_mask in queues_per_thread.iter() { + for (thread_index, queues_mask) in queues_per_thread.iter().enumerate() { // Create the epoll file descriptor let epoll_fd = epoll::create(true).map_err(VhostUserHandlerError::EpollCreateFd)?; @@ -492,7 +492,7 @@ impl VhostUserHandler { let worker = vring_worker.clone(); let exit_event_id = if let Some((exit_event_fd, exit_event_id)) = - backend.read().unwrap().exit_event() + backend.read().unwrap().exit_event(thread_index) { let exit_event_id = exit_event_id.unwrap_or(num_queues as u16); worker From a0d7fb92000db4ba240da855f77f6264756025b5 Mon Sep 17 00:00:00 2001 From: Sebastien Boeuf Date: Thu, 9 Apr 2020 18:20:38 +0200 Subject: [PATCH 032/139] Provide the thread ID to handle_event() By adding a "thread_id" parameter to handle_event(), the backend crate can now indicate to the backend implementation which thread triggered the processing of some events. This is applied to vhost-user-net backend and allows for simplifying a lot the code since each thread is identical. Signed-off-by: Sebastien Boeuf --- src/lib.rs | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index 969c7ac..db58e04 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -85,6 +85,7 @@ pub trait VhostUserBackend: Send + Sync + 'static { device_event: u16, evset: epoll::Events, vrings: &[Arc>], + thread_id: usize, ) -> result::Result; /// Get virtio device configuration. @@ -287,6 +288,7 @@ struct VringEpollHandler { backend: Arc>, vrings: Vec>>, exit_event_id: Option, + thread_id: usize, } impl VringEpollHandler { @@ -316,7 +318,7 @@ impl VringEpollHandler { self.backend .read() .unwrap() - .handle_event(device_event, evset, &self.vrings) + .handle_event(device_event, evset, &self.vrings, self.thread_id) .map_err(VringEpollHandlerError::HandleEventBackendHandling) } } @@ -484,7 +486,7 @@ impl VhostUserHandler { let mut workers = Vec::new(); let mut worker_threads = Vec::new(); - for (thread_index, queues_mask) in queues_per_thread.iter().enumerate() { + for (thread_id, queues_mask) in queues_per_thread.iter().enumerate() { // Create the epoll file descriptor let epoll_fd = epoll::create(true).map_err(VhostUserHandlerError::EpollCreateFd)?; @@ -492,7 +494,7 @@ impl VhostUserHandler { let worker = vring_worker.clone(); let exit_event_id = if let Some((exit_event_fd, exit_event_id)) = - backend.read().unwrap().exit_event(thread_index) + backend.read().unwrap().exit_event(thread_id) { let exit_event_id = exit_event_id.unwrap_or(num_queues as u16); worker @@ -518,6 +520,7 @@ impl VhostUserHandler { backend: backend.clone(), vrings: thread_vrings, exit_event_id, + thread_id, }; let worker_thread = thread::Builder::new() From ea9161f4f34cdc7234d2cc2f1e8ba6ee9f98c804 Mon Sep 17 00:00:00 2001 From: Sergio Lopez Date: Fri, 24 Apr 2020 13:33:00 +0200 Subject: [PATCH 033/139] Create a vhost::Listener in advance Changes is vhost crate require VhostUserDaemon users to create and provide a vhost::Listener in advance. This allows us to adopt sandboxing strategies in the future, by being able to create the UNIX socket before switching to a restricted namespace. Update also the reference to vhost crate in Cargo.lock to point to the latest commit from the dragonball branch. Signed-off-by: Sergio Lopez --- src/lib.rs | 13 +++++-------- 1 file changed, 5 insertions(+), 8 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index db58e04..efdb77b 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -20,7 +20,7 @@ use vhost_rs::vhost_user::message::{ VhostUserVirtioFeatures, VhostUserVringAddrFlags, VhostUserVringState, }; use vhost_rs::vhost_user::{ - Error as VhostUserError, Result as VhostUserResult, SlaveFsCacheReq, SlaveListener, + Error as VhostUserError, Listener, Result as VhostUserResult, SlaveFsCacheReq, SlaveListener, VhostUserSlaveReqHandler, }; use virtio_bindings::bindings::virtio_ring::VIRTIO_RING_F_EVENT_IDX; @@ -124,7 +124,6 @@ pub trait VhostUserBackend: Send + Sync + 'static { /// in order to run a fully functional vhost-user daemon. pub struct VhostUserDaemon { name: String, - sock_path: String, handler: Arc>>, main_thread: Option>>, } @@ -136,14 +135,13 @@ impl VhostUserDaemon { /// listening onto registered event. Those events can be vring events or /// custom events from the backend, but they get to be registered later /// during the sequence. - pub fn new(name: String, sock_path: String, backend: Arc>) -> Result { + pub fn new(name: String, backend: Arc>) -> Result { let handler = Arc::new(Mutex::new( VhostUserHandler::new(backend).map_err(Error::NewVhostUserHandler)?, )); Ok(VhostUserDaemon { name, - sock_path, handler, main_thread: None, }) @@ -153,10 +151,9 @@ impl VhostUserDaemon { /// all requests coming through this socket. This runs in an infinite loop /// that should be terminating once the other end of the socket (the VMM) /// disconnects. - pub fn start(&mut self) -> Result<()> { - let mut slave_listener = - SlaveListener::new(self.sock_path.as_str(), true, self.handler.clone()) - .map_err(Error::CreateSlaveListener)?; + pub fn start(&mut self, listener: Listener) -> Result<()> { + let mut slave_listener = SlaveListener::new(listener, self.handler.clone()) + .map_err(Error::CreateSlaveListener)?; let mut slave_handler = slave_listener .accept() .map_err(Error::CreateSlaveReqHandler)? From ca25d972f0e2663c86495d0944eafa3c65f87e08 Mon Sep 17 00:00:00 2001 From: Bo Chen Date: Fri, 15 May 2020 11:31:35 -0700 Subject: [PATCH 034/139] Close leaked file descriptors Explicit call to 'close()' is required on file descriptors allocated from 'epoll::create()', which is missing for the 'EpollContext' and 'VringWorker'. This patch enforces to close the file descriptors by reusing the Drop trait of the 'File' struct. Signed-off-by: Bo Chen --- src/lib.rs | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index efdb77b..9cb3f63 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -333,7 +333,7 @@ enum VringWorkerError { type VringWorkerResult = std::result::Result; pub struct VringWorker { - epoll_fd: RawFd, + epoll_file: File, } impl VringWorker { @@ -342,7 +342,7 @@ impl VringWorker { let mut events = vec![epoll::Event::new(epoll::Events::empty(), 0); EPOLL_EVENTS_LEN]; 'epoll: loop { - let num_events = match epoll::wait(self.epoll_fd, -1, &mut events[..]) { + let num_events = match epoll::wait(self.epoll_file.as_raw_fd(), -1, &mut events[..]) { Ok(res) => res, Err(e) => { if e.kind() == io::ErrorKind::Interrupted { @@ -396,7 +396,7 @@ impl VringWorker { data: u64, ) -> result::Result<(), io::Error> { epoll::ctl( - self.epoll_fd, + self.epoll_file.as_raw_fd(), epoll::ControlOptions::EPOLL_CTL_ADD, fd, epoll::Event::new(ev_type, data), @@ -413,7 +413,7 @@ impl VringWorker { data: u64, ) -> result::Result<(), io::Error> { epoll::ctl( - self.epoll_fd, + self.epoll_file.as_raw_fd(), epoll::ControlOptions::EPOLL_CTL_DEL, fd, epoll::Event::new(ev_type, data), @@ -486,8 +486,10 @@ impl VhostUserHandler { for (thread_id, queues_mask) in queues_per_thread.iter().enumerate() { // Create the epoll file descriptor let epoll_fd = epoll::create(true).map_err(VhostUserHandlerError::EpollCreateFd)?; + // Use 'File' to enforce closing on 'epoll_fd' + let epoll_file = unsafe { File::from_raw_fd(epoll_fd) }; - let vring_worker = Arc::new(VringWorker { epoll_fd }); + let vring_worker = Arc::new(VringWorker { epoll_file }); let worker = vring_worker.clone(); let exit_event_id = if let Some((exit_event_fd, exit_event_id)) = From 309d0e91b801bef7a2e206bb37945e296a3c2bd4 Mon Sep 17 00:00:00 2001 From: Rob Bradford Date: Wed, 20 May 2020 10:40:54 +0100 Subject: [PATCH 035/139] Move EVENT_IDX handling Move the method that is used to decide whether the guest should be signalled into the Queue implementation from vm-virtio. This removes duplicated code between vhost_user_backend and the vm-virtio block implementation. Signed-off-by: Rob Bradford --- src/lib.rs | 30 +----------------------------- 1 file changed, 1 insertion(+), 29 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index 9cb3f63..fb2d6fc 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -210,8 +210,6 @@ pub struct Vring { call: Option, err: Option, enabled: bool, - event_idx: bool, - signalled_used: Option>, } impl Vring { @@ -222,8 +220,6 @@ impl Vring { call: None, err: None, enabled: false, - event_idx: false, - signalled_used: None, } } @@ -231,31 +227,6 @@ impl Vring { &mut self.queue } - pub fn set_event_idx(&mut self, enabled: bool) { - /* Also reset the last signalled event */ - self.signalled_used = None; - self.event_idx = enabled; - } - - pub fn needs_notification(&mut self, mem: &GuestMemoryMmap, used_idx: Wrapping) -> bool { - if !self.event_idx { - return true; - } - - let mut notify = true; - - if let Some(old_idx) = self.signalled_used { - if let Some(used_event) = self.mut_queue().get_used_event(&mem) { - if (used_idx - used_event - Wrapping(1u16)) >= (used_idx - old_idx) { - notify = false; - } - } - } - - self.signalled_used = Some(used_idx); - notify - } - pub fn signal_used_queue(&mut self) -> result::Result<(), io::Error> { if let Some(call) = self.call.as_ref() { call.write(1) @@ -724,6 +695,7 @@ impl VhostUserSlaveReqHandler for VhostUserHandler { self.vrings[index as usize] .write() .unwrap() + .mut_queue() .set_event_idx(event_idx); self.backend.write().unwrap().set_event_idx(event_idx); Ok(()) From 9d09d51fb401276cdc159e352b5ffd0c6948d19f Mon Sep 17 00:00:00 2001 From: Rob Bradford Date: Wed, 20 May 2020 15:59:08 +0100 Subject: [PATCH 036/139] Allow backends to know features that can be used Previous to adding a a trait method to inform the backends of the acked features backends can use features than the guest has not enabled which could lead to unpredictable results. Signed-off-by: Rob Bradford --- src/lib.rs | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/src/lib.rs b/src/lib.rs index fb2d6fc..6de5079 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -64,9 +64,12 @@ pub trait VhostUserBackend: Send + Sync + 'static { /// Depth of each queue. fn max_queue_size(&self) -> usize; - /// Virtio features. + /// Available virtio features. fn features(&self) -> u64; + /// Acked virtio features. + fn acked_features(&mut self, _features: u64) {} + /// Virtio protocol features. fn protocol_features(&self) -> VhostUserProtocolFeatures; @@ -577,6 +580,11 @@ impl VhostUserSlaveReqHandler for VhostUserHandler { vring.write().unwrap().enabled = vring_enabled; } + self.backend + .write() + .unwrap() + .acked_features(self.acked_features); + Ok(()) } From 25d7d68403c3a811a9961e9b84ba300c9306d65a Mon Sep 17 00:00:00 2001 From: Rob Bradford Date: Fri, 29 May 2020 15:30:18 +0100 Subject: [PATCH 037/139] Implement AsRawFd for VringWorker Signed-off-by: Rob Bradford --- src/lib.rs | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/lib.rs b/src/lib.rs index 6de5079..f627e5d 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -310,6 +310,12 @@ pub struct VringWorker { epoll_file: File, } +impl AsRawFd for VringWorker { + fn as_raw_fd(&self) -> RawFd { + self.epoll_file.as_raw_fd() + } +} + impl VringWorker { fn run(&self, handler: VringEpollHandler) -> VringWorkerResult<()> { const EPOLL_EVENTS_LEN: usize = 100; From 309d82bf2bb78cfe24eaed5c8051542e4fc8275a Mon Sep 17 00:00:00 2001 From: Arron Wang Date: Sun, 28 Jun 2020 05:58:44 +0000 Subject: [PATCH 038/139] Add Cargo.toml file Signed-off-by: Arron Wang --- Cargo.toml | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 0cebe72..264fc14 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,8 +1,16 @@ [package] -name = "crate-template" +name = "vhost_user_backend" version = "0.1.0" -authors = [TODO] -license = "Apache-2.0 OR BSD-3-Clause" +authors = ["The Cloud Hypervisor Authors"] edition = "2018" +license = "Apache-2.0" [dependencies] +epoll = ">=4.0.1" +libc = ">=0.2.39" +log = ">=0.4.6" +virtio-bindings = "0.1.0" +vm-memory = {version = ">=0.2.0", features = ["backend-mmap"]} +vm-virtio = { git = "https://github.com/cloud-hypervisor/vm-virtio", branch = "dragonball" } +vmm-sys-util = ">=0.3.1" +vhost_rs = { git = "https://github.com/cloud-hypervisor/vhost", branch = "dragonball", package = "vhost", features = ["vhost-user-slave"] } From 260a9e4e3107617393ab9ea4e3a82fc2b292d7e3 Mon Sep 17 00:00:00 2001 From: Sergio Lopez Date: Thu, 24 Sep 2020 12:50:28 +0200 Subject: [PATCH 039/139] Wrap GuestMemoryMmap in GuestMemoryAtomic GuestMemoryAtomic enables support for mutable memory maps, and also acts as a convenient wrapper around GuestMemory without its lifetime restrictions. Signed-off-by: Sergio Lopez --- Cargo.toml | 2 +- src/lib.rs | 16 +++++++++++++--- 2 files changed, 14 insertions(+), 4 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 264fc14..3e028dc 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -10,7 +10,7 @@ epoll = ">=4.0.1" libc = ">=0.2.39" log = ">=0.4.6" virtio-bindings = "0.1.0" -vm-memory = {version = ">=0.2.0", features = ["backend-mmap"]} +vm-memory = {version = ">=0.2.0", features = ["backend-mmap", "backend-atomic"]} vm-virtio = { git = "https://github.com/cloud-hypervisor/vm-virtio", branch = "dragonball" } vmm-sys-util = ">=0.3.1" vhost_rs = { git = "https://github.com/cloud-hypervisor/vhost", branch = "dragonball", package = "vhost", features = ["vhost-user-slave"] } diff --git a/src/lib.rs b/src/lib.rs index f627e5d..b452ffc 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -25,7 +25,7 @@ use vhost_rs::vhost_user::{ }; use virtio_bindings::bindings::virtio_ring::VIRTIO_RING_F_EVENT_IDX; use vm_memory::guest_memory::FileOffset; -use vm_memory::{GuestAddress, GuestMemoryMmap}; +use vm_memory::{GuestAddress, GuestMemoryAtomic, GuestMemoryMmap}; use vm_virtio::Queue; use vmm_sys_util::eventfd::EventFd; @@ -77,7 +77,10 @@ pub trait VhostUserBackend: Send + Sync + 'static { fn set_event_idx(&mut self, enabled: bool); /// Update guest memory regions. - fn update_memory(&mut self, mem: GuestMemoryMmap) -> result::Result<(), io::Error>; + fn update_memory( + &mut self, + atomic_mem: GuestMemoryAtomic, + ) -> result::Result<(), io::Error>; /// This function gets called if the backend registered some additional /// listeners onto specific file descriptors. The library can handle @@ -445,6 +448,7 @@ struct VhostUserHandler { max_queue_size: usize, queues_per_thread: Vec, memory: Option, + atomic_mem: GuestMemoryAtomic, vrings: Vec>>, worker_threads: Vec>>, } @@ -522,6 +526,7 @@ impl VhostUserHandler { max_queue_size, queues_per_thread, memory: None, + atomic_mem: GuestMemoryAtomic::new(GuestMemoryMmap::new()), vrings, worker_threads, }) @@ -633,10 +638,15 @@ impl VhostUserSlaveReqHandler for VhostUserHandler { let mem = GuestMemoryMmap::from_ranges_with_files(regions).map_err(|e| { VhostUserError::ReqHandlerError(io::Error::new(io::ErrorKind::Other, e)) })?; + + // Updating the inner GuestMemory object here will cause all our vrings to + // see the new one the next time they call to `atomic_mem.memory()`. + self.atomic_mem.lock().unwrap().replace(mem); + self.backend .write() .unwrap() - .update_memory(mem) + .update_memory(self.atomic_mem.clone()) .map_err(|e| { VhostUserError::ReqHandlerError(io::Error::new(io::ErrorKind::Other, e)) })?; From 09f4ba88610cccb546a038067259f9d66b5227c4 Mon Sep 17 00:00:00 2001 From: Sergio Lopez Date: Fri, 25 Sep 2020 13:38:21 +0200 Subject: [PATCH 040/139] Don't update next_used on set_vring_base The VHOST_USER_SET_VRING_BASE message is only intended to update the index for the next descriptor in the available ring. Signed-off-by: Sergio Lopez --- src/lib.rs | 1 - 1 file changed, 1 deletion(-) diff --git a/src/lib.rs b/src/lib.rs index b452ffc..67c5900 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -713,7 +713,6 @@ impl VhostUserSlaveReqHandler for VhostUserHandler { .unwrap() .queue .next_avail = Wrapping(base as u16); - self.vrings[index as usize].write().unwrap().queue.next_used = Wrapping(base as u16); let event_idx: bool = (self.acked_features & (1 << VIRTIO_RING_F_EVENT_IDX)) != 0; self.vrings[index as usize] From 9ad51070f4da6c467a7548f90af105d6d9eb780d Mon Sep 17 00:00:00 2001 From: Sergio Lopez Date: Fri, 25 Sep 2020 13:50:54 +0200 Subject: [PATCH 041/139] Switch to using rust-vmm's vhost and vm-virtio The main change is that Queue now requires a type parameter of something that implements GuestAddressSpace. As we already switched to using GuestMemoryAtomic in a previous commit, we just need to add the annotations. There's also a minor change to access Queue::next_avail using accessors instead of the field. Signed-off-by: Sergio Lopez --- Cargo.toml | 4 ++-- src/lib.rs | 23 +++++++++++++---------- 2 files changed, 15 insertions(+), 12 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 3e028dc..52bec93 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -11,6 +11,6 @@ libc = ">=0.2.39" log = ">=0.4.6" virtio-bindings = "0.1.0" vm-memory = {version = ">=0.2.0", features = ["backend-mmap", "backend-atomic"]} -vm-virtio = { git = "https://github.com/cloud-hypervisor/vm-virtio", branch = "dragonball" } +vm-virtio = { git = "https://github.com/rust-vmm/vm-virtio" } vmm-sys-util = ">=0.3.1" -vhost_rs = { git = "https://github.com/cloud-hypervisor/vhost", branch = "dragonball", package = "vhost", features = ["vhost-user-slave"] } +vhost_rs = { git = "https://github.com/rust-vmm/vhost", package = "vhost", features = ["vhost-user-slave"] } diff --git a/src/lib.rs b/src/lib.rs index 67c5900..084e5cd 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -10,7 +10,6 @@ extern crate log; use std::error; use std::fs::File; use std::io; -use std::num::Wrapping; use std::os::unix::io::{AsRawFd, FromRawFd, RawFd}; use std::result; use std::sync::{Arc, Mutex, RwLock}; @@ -211,7 +210,7 @@ struct Memory { } pub struct Vring { - queue: Queue, + queue: Queue>, kick: Option, call: Option, err: Option, @@ -219,9 +218,9 @@ pub struct Vring { } impl Vring { - fn new(max_queue_size: u16) -> Self { + fn new(atomic_mem: GuestMemoryAtomic, max_queue_size: u16) -> Self { Vring { - queue: Queue::new(max_queue_size), + queue: Queue::new(atomic_mem, max_queue_size), kick: None, call: None, err: None, @@ -229,7 +228,7 @@ impl Vring { } } - pub fn mut_queue(&mut self) -> &mut Queue { + pub fn mut_queue(&mut self) -> &mut Queue> { &mut self.queue } @@ -459,9 +458,14 @@ impl VhostUserHandler { let max_queue_size = backend.read().unwrap().max_queue_size(); let queues_per_thread = backend.read().unwrap().queues_per_thread(); + let atomic_mem = GuestMemoryAtomic::new(GuestMemoryMmap::new()); + let mut vrings: Vec>> = Vec::new(); for _ in 0..num_queues { - let vring = Arc::new(RwLock::new(Vring::new(max_queue_size as u16))); + let vring = Arc::new(RwLock::new(Vring::new( + atomic_mem.clone(), + max_queue_size as u16, + ))); vrings.push(vring); } @@ -526,7 +530,7 @@ impl VhostUserHandler { max_queue_size, queues_per_thread, memory: None, - atomic_mem: GuestMemoryAtomic::new(GuestMemoryMmap::new()), + atomic_mem, vrings, worker_threads, }) @@ -712,7 +716,7 @@ impl VhostUserSlaveReqHandler for VhostUserHandler { .write() .unwrap() .queue - .next_avail = Wrapping(base as u16); + .set_next_avail(base as u16); let event_idx: bool = (self.acked_features & (1 << VIRTIO_RING_F_EVENT_IDX)) != 0; self.vrings[index as usize] @@ -755,8 +759,7 @@ impl VhostUserSlaveReqHandler for VhostUserHandler { .read() .unwrap() .queue - .next_avail - .0 as u16; + .next_avail(); Ok(VhostUserVringState::new(index, u32::from(next_avail))) } From de6d5bffbe11dc9cb84775626b7ea0d36c7edd1a Mon Sep 17 00:00:00 2001 From: Sergio Lopez Date: Tue, 17 Nov 2020 15:31:22 +0100 Subject: [PATCH 042/139] Rename LICENSE-APACHE to LICENSE This crate is only covered by a single license, so rename LICENSE-APACHE as LICENSE to make it obvious. Signed-off-by: Sergio Lopez --- LICENSE-APACHE => LICENSE | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename LICENSE-APACHE => LICENSE (100%) diff --git a/LICENSE-APACHE b/LICENSE similarity index 100% rename from LICENSE-APACHE rename to LICENSE From 50e335404a3595d57574d480ee8766a9ad1895a7 Mon Sep 17 00:00:00 2001 From: Sergio Lopez Date: Tue, 17 Nov 2020 15:36:57 +0100 Subject: [PATCH 043/139] Update README.md Update README.md and include some minimal information about this crate. Signed-off-by: Sergio Lopez --- README.md | 41 +++++++---------------------------------- 1 file changed, 7 insertions(+), 34 deletions(-) diff --git a/README.md b/README.md index d2af0db..570ac8f 100644 --- a/README.md +++ b/README.md @@ -1,41 +1,14 @@ -# Crate Name +# vhost-user-backend ## Design -TODO: This section should have a high-level design of the crate. +This crate provides convenient abstractions for implementing `vhost-user` device server backends: -Some questions that might help in writing this section: -- What is the purpose of this crate? -- What are the main components of the crate? How do they interact which each - other? +- A vhost-user backend trait (`VhostUserBackend`) +- A public API for the backend to interact with (`VhostUserDaemon`) +- An structure including virtio queue related elements (`Vring`) +- A worker for receiving queue events and forwarding them to the backend. ## Usage -TODO: This section describes how the crate is used. - -Some questions that might help in writing this section: -- What traits do users need to implement? -- Does the crate have any default/optional features? What is each feature - doing? -- Is this crate used by other rust-vmm components? If yes, how? - -## Examples - -TODO: Usage examples. - -```rust -use my_crate; - -... -``` - -## License - -**!!!NOTICE**: The BSD-3-Clause license is not included in this template. -The license needs to be manually added because the text of the license file -also includes the copyright. The copyright can be different for different -crates. If the crate contains code from CrosVM, the crate must add the -CrosVM copyright which can be found -[here](https://chromium.googlesource.com/chromiumos/platform/crosvm/+/master/LICENSE). -For crates developed from scratch, the copyright is different and depends on -the contributors. +Users of this create are expected to implement the `VhostUserBackend` trait and to initialize the execution context by instantiating `VhostUserDaemon` and calling to its `start` method. From 796677d013bbb54d5ab6689eb422f9abac56b406 Mon Sep 17 00:00:00 2001 From: Sergio Lopez Date: Tue, 17 Nov 2020 16:17:31 +0100 Subject: [PATCH 044/139] Rename package to vhost-user-backend Rename package from vhost_user_backend to vhost-user-backend to accommodate to rust-vmm's conventions. Signed-off-by: Sergio Lopez --- Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Cargo.toml b/Cargo.toml index 52bec93..0755c55 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,5 +1,5 @@ [package] -name = "vhost_user_backend" +name = "vhost-user-backend" version = "0.1.0" authors = ["The Cloud Hypervisor Authors"] edition = "2018" From 37b0a24f96265e7359a62ba4a2150164d2366774 Mon Sep 17 00:00:00 2001 From: Sergio Lopez Date: Mon, 23 Nov 2020 12:07:15 +0100 Subject: [PATCH 045/139] Reorder dependencies in Cargo.toml Put depedencies in alphabetical order in Cargo.toml Signed-off-by: Sergio Lopez --- Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Cargo.toml b/Cargo.toml index 0755c55..c6b1c34 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -9,8 +9,8 @@ license = "Apache-2.0" epoll = ">=4.0.1" libc = ">=0.2.39" log = ">=0.4.6" +vhost_rs = { git = "https://github.com/rust-vmm/vhost", package = "vhost", features = ["vhost-user-slave"] } virtio-bindings = "0.1.0" vm-memory = {version = ">=0.2.0", features = ["backend-mmap", "backend-atomic"]} vm-virtio = { git = "https://github.com/rust-vmm/vm-virtio" } vmm-sys-util = ">=0.3.1" -vhost_rs = { git = "https://github.com/rust-vmm/vhost", package = "vhost", features = ["vhost-user-slave"] } From 70e72f6e87017aad7d497df981a84639d913ba95 Mon Sep 17 00:00:00 2001 From: Sergio Lopez Date: Mon, 23 Nov 2020 12:11:00 +0100 Subject: [PATCH 046/139] Update CODEOWNERS Add @jiangliu, @sboeuf and myself (@slp) to CODEOWNERS, to be automatically assigned as code reviewers, and remove 'gatekeeper-PullAssigner'. Signed-off-by: Sergio Lopez --- CODEOWNERS | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CODEOWNERS b/CODEOWNERS index 4d96c3f..acd5360 100644 --- a/CODEOWNERS +++ b/CODEOWNERS @@ -1,2 +1,2 @@ # Add the list of code owners here (using their GitHub username) -* gatekeeper-PullAssigner +* @jiangliu @slp From 3c97ba57a85e6d504c3513ce2037ba8505419408 Mon Sep 17 00:00:00 2001 From: Sergio Lopez Date: Thu, 26 Nov 2020 12:49:41 +0100 Subject: [PATCH 047/139] Add sboeuf to CODEOWNERS Despite what's stated in the previous commit, sboeuf was not added to CODEOWNERS. Add him now for good. Signed-off-by: Sergio Lopez --- CODEOWNERS | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CODEOWNERS b/CODEOWNERS index acd5360..ab19725 100644 --- a/CODEOWNERS +++ b/CODEOWNERS @@ -1,2 +1,2 @@ # Add the list of code owners here (using their GitHub username) -* @jiangliu @slp +* @jiangliu @sboeuf @slp From c512e8caa666db60be18ce53dfdc8704ffc95bdf Mon Sep 17 00:00:00 2001 From: Sergio Lopez Date: Thu, 25 Feb 2021 18:09:42 +0100 Subject: [PATCH 048/139] Add reference to rust-vmm-ci submodule Fixes #5 Signed-off-by: Sergio Lopez --- rust-vmm-ci | 1 + 1 file changed, 1 insertion(+) create mode 160000 rust-vmm-ci diff --git a/rust-vmm-ci b/rust-vmm-ci new file mode 160000 index 0000000..ebc7016 --- /dev/null +++ b/rust-vmm-ci @@ -0,0 +1 @@ +Subproject commit ebc701641fa57f78d03f3f5ecac617b7bf7470b4 From 89d7c60c19053708f7b2a9ed4dcf889d1cd100db Mon Sep 17 00:00:00 2001 From: Sebastien Boeuf Date: Mon, 8 Mar 2021 18:53:33 +0100 Subject: [PATCH 049/139] Cargo.toml: Update vhost crate name Instead of renaming the vhost crate with vhost_rs, let's keep the crate name. Signed-off-by: Sebastien Boeuf --- Cargo.toml | 2 +- src/lib.rs | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index c6b1c34..f533860 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -9,7 +9,7 @@ license = "Apache-2.0" epoll = ">=4.0.1" libc = ">=0.2.39" log = ">=0.4.6" -vhost_rs = { git = "https://github.com/rust-vmm/vhost", package = "vhost", features = ["vhost-user-slave"] } +vhost = { git = "https://github.com/rust-vmm/vhost", features = ["vhost-user-slave"] } virtio-bindings = "0.1.0" vm-memory = {version = ">=0.2.0", features = ["backend-mmap", "backend-atomic"]} vm-virtio = { git = "https://github.com/rust-vmm/vm-virtio" } diff --git a/src/lib.rs b/src/lib.rs index 084e5cd..69a2500 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -14,11 +14,11 @@ use std::os::unix::io::{AsRawFd, FromRawFd, RawFd}; use std::result; use std::sync::{Arc, Mutex, RwLock}; use std::thread; -use vhost_rs::vhost_user::message::{ +use vhost::vhost_user::message::{ VhostUserConfigFlags, VhostUserMemoryRegion, VhostUserProtocolFeatures, VhostUserVirtioFeatures, VhostUserVringAddrFlags, VhostUserVringState, }; -use vhost_rs::vhost_user::{ +use vhost::vhost_user::{ Error as VhostUserError, Listener, Result as VhostUserResult, SlaveFsCacheReq, SlaveListener, VhostUserSlaveReqHandler, }; From 46f2ad4fba87713b07adfb3c4a177d4c20a55fdc Mon Sep 17 00:00:00 2001 From: Sebastien Boeuf Date: Mon, 8 Mar 2021 18:51:50 +0100 Subject: [PATCH 050/139] lib: Fix codebase with VhostUserSlaveReqHandlerMut The original VhostUserSlaveReqHandler was changed to be immutable in the latest vhost crate. Along with that, a mutable version called VhostUserSlaveReqHandlerMut has been introduced. In order to keep the vhost_user_backend working, we replace VhostUserSlaveReqHandler with VhostUserSlaveReqHandlerMut. Signed-off-by: Sebastien Boeuf --- src/lib.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index 69a2500..05673db 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -20,7 +20,7 @@ use vhost::vhost_user::message::{ }; use vhost::vhost_user::{ Error as VhostUserError, Listener, Result as VhostUserResult, SlaveFsCacheReq, SlaveListener, - VhostUserSlaveReqHandler, + VhostUserSlaveReqHandlerMut, }; use virtio_bindings::bindings::virtio_ring::VIRTIO_RING_F_EVENT_IDX; use vm_memory::guest_memory::FileOffset; @@ -553,7 +553,7 @@ impl VhostUserHandler { } } -impl VhostUserSlaveReqHandler for VhostUserHandler { +impl VhostUserSlaveReqHandlerMut for VhostUserHandler { fn set_owner(&mut self) -> VhostUserResult<()> { if self.owned { return Err(VhostUserError::InvalidOperation); From a6e1e0fb6c7c7e87a21069716fbad2ab4292f0cc Mon Sep 17 00:00:00 2001 From: Sebastien Boeuf Date: Thu, 11 Mar 2021 19:18:13 +0100 Subject: [PATCH 051/139] lib: Set reply_ack based on acked protocol features In case VHOST_USER_PROTOCOL_F_REPLY_ACK has been negotiated, the reply ack must be explicitly set on the SlaveFsCacheReq handler. Signed-off-by: Sebastien Boeuf --- src/lib.rs | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/lib.rs b/src/lib.rs index 05673db..321054b 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -873,6 +873,10 @@ impl VhostUserSlaveReqHandlerMut for VhostUserHandler { } fn set_slave_req_fd(&mut self, vu_req: SlaveFsCacheReq) { + if self.acked_protocol_features & VhostUserProtocolFeatures::REPLY_ACK.bits() != 0 { + vu_req.set_reply_ack_flag(true); + } + self.backend.write().unwrap().set_slave_req_fd(vu_req); } } From cf306dfed9b1e7ac0c43d1091d3ebadf4e3b0142 Mon Sep 17 00:00:00 2001 From: Sebastien Boeuf Date: Mon, 8 Mar 2021 18:49:31 +0100 Subject: [PATCH 052/139] lib: Simplify the list of mappings There's no reason to hide the list of mappings behind a structure. We can instead store the list directly in VhostUserHandler structure. Signed-off-by: Sebastien Boeuf --- src/lib.rs | 16 +++++----------- 1 file changed, 5 insertions(+), 11 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index 321054b..5693a6c 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -205,10 +205,6 @@ struct AddrMapping { gpa_base: u64, } -struct Memory { - mappings: Vec, -} - pub struct Vring { queue: Queue>, kick: Option, @@ -446,7 +442,7 @@ struct VhostUserHandler { num_queues: usize, max_queue_size: usize, queues_per_thread: Vec, - memory: Option, + mappings: Vec, atomic_mem: GuestMemoryAtomic, vrings: Vec>>, worker_threads: Vec>>, @@ -529,7 +525,7 @@ impl VhostUserHandler { num_queues, max_queue_size, queues_per_thread, - memory: None, + mappings: Vec::new(), atomic_mem, vrings, worker_threads, @@ -541,13 +537,11 @@ impl VhostUserHandler { } fn vmm_va_to_gpa(&self, vmm_va: u64) -> VhostUserHandlerResult { - if let Some(memory) = &self.memory { - for mapping in memory.mappings.iter() { + for mapping in self.mappings.iter() { if vmm_va >= mapping.vmm_addr && vmm_va < mapping.vmm_addr + mapping.size { return Ok(vmm_va - mapping.vmm_addr + mapping.gpa_base); } } - } Err(VhostUserHandlerError::MissingMemoryMapping) } @@ -654,7 +648,7 @@ impl VhostUserSlaveReqHandlerMut for VhostUserHandler { .map_err(|e| { VhostUserError::ReqHandlerError(io::Error::new(io::ErrorKind::Other, e)) })?; - self.memory = Some(Memory { mappings }); + self.mappings = mappings; Ok(()) } @@ -684,7 +678,7 @@ impl VhostUserSlaveReqHandlerMut for VhostUserHandler { return Err(VhostUserError::InvalidParam); } - if self.memory.is_some() { + if !self.mappings.is_empty() { let desc_table = self.vmm_va_to_gpa(descriptor).map_err(|e| { VhostUserError::ReqHandlerError(io::Error::new(io::ErrorKind::Other, e)) })?; From cebfa3cd78bbad46fa728893c76dfaa45d879594 Mon Sep 17 00:00:00 2001 From: Sebastien Boeuf Date: Tue, 9 Mar 2021 10:41:17 +0100 Subject: [PATCH 053/139] lib: Implement VHOST_USER_PROTOCOL_F_CONFIGURE_MEM_SLOTS Relying on latest vhost version, this commit implements VHOST_USER_GET_MAX_MEM_SLOTS, VHOST_USER_ADD_MEM_REG and VHOST_USER_REM_MEM_REG commands from the protocol feature VHOST_USER_PROTOCOL_F_CONFIGURE_MEM_SLOTS. VHOST_USER_GET_MAX_MEM_SLOTS provides the maximum amount of memory slots that can be supported by the vhost-user backend. VHOST_USER_ADD_MEM_REG lets the frontend provide a new memory region that must be mapped by the backend. VHOST_USER_REM_MEM_REG lets the frontend request for the removal of an existing memory region that must be unmapped by the backend. Signed-off-by: Sebastien Boeuf --- src/lib.rs | 91 ++++++++++++++++++++++++++++++++++++++++++++++++++---- 1 file changed, 85 insertions(+), 6 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index 5693a6c..6ee4371 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -16,7 +16,8 @@ use std::sync::{Arc, Mutex, RwLock}; use std::thread; use vhost::vhost_user::message::{ VhostUserConfigFlags, VhostUserMemoryRegion, VhostUserProtocolFeatures, - VhostUserVirtioFeatures, VhostUserVringAddrFlags, VhostUserVringState, + VhostUserSingleMemoryRegion, VhostUserVirtioFeatures, VhostUserVringAddrFlags, + VhostUserVringState, }; use vhost::vhost_user::{ Error as VhostUserError, Listener, Result as VhostUserResult, SlaveFsCacheReq, SlaveListener, @@ -24,10 +25,15 @@ use vhost::vhost_user::{ }; use virtio_bindings::bindings::virtio_ring::VIRTIO_RING_F_EVENT_IDX; use vm_memory::guest_memory::FileOffset; -use vm_memory::{GuestAddress, GuestMemoryAtomic, GuestMemoryMmap}; +use vm_memory::{ + GuestAddress, GuestAddressSpace, GuestMemoryAtomic, GuestMemoryMmap, GuestRegionMmap, + MmapRegion, +}; use vm_virtio::Queue; use vmm_sys_util::eventfd::EventFd; +const MAX_MEM_SLOTS: u64 = 32; + #[derive(Debug)] /// Errors related to vhost-user daemon. pub enum Error { @@ -537,11 +543,11 @@ impl VhostUserHandler { } fn vmm_va_to_gpa(&self, vmm_va: u64) -> VhostUserHandlerResult { - for mapping in self.mappings.iter() { - if vmm_va >= mapping.vmm_addr && vmm_va < mapping.vmm_addr + mapping.size { - return Ok(vmm_va - mapping.vmm_addr + mapping.gpa_base); - } + for mapping in self.mappings.iter() { + if vmm_va >= mapping.vmm_addr && vmm_va < mapping.vmm_addr + mapping.size { + return Ok(vmm_va - mapping.vmm_addr + mapping.gpa_base); } + } Err(VhostUserHandlerError::MissingMemoryMapping) } @@ -873,6 +879,79 @@ impl VhostUserSlaveReqHandlerMut for VhostUserHandler { self.backend.write().unwrap().set_slave_req_fd(vu_req); } + + fn get_max_mem_slots(&mut self) -> VhostUserResult { + Ok(MAX_MEM_SLOTS) + } + + fn add_mem_region( + &mut self, + region: &VhostUserSingleMemoryRegion, + fd: RawFd, + ) -> VhostUserResult<()> { + let file = unsafe { File::from_raw_fd(fd) }; + let mmap_region = MmapRegion::from_file( + FileOffset::new(file, region.mmap_offset), + region.memory_size as usize, + ) + .map_err(|e| VhostUserError::ReqHandlerError(io::Error::new(io::ErrorKind::Other, e)))?; + let guest_region = Arc::new( + GuestRegionMmap::new(mmap_region, GuestAddress(region.guest_phys_addr)).map_err( + |e| VhostUserError::ReqHandlerError(io::Error::new(io::ErrorKind::Other, e)), + )?, + ); + + let mem = self + .atomic_mem + .memory() + .insert_region(guest_region) + .map_err(|e| { + VhostUserError::ReqHandlerError(io::Error::new(io::ErrorKind::Other, e)) + })?; + + self.atomic_mem.lock().unwrap().replace(mem); + + self.backend + .write() + .unwrap() + .update_memory(self.atomic_mem.clone()) + .map_err(|e| { + VhostUserError::ReqHandlerError(io::Error::new(io::ErrorKind::Other, e)) + })?; + + self.mappings.push(AddrMapping { + vmm_addr: region.user_addr, + size: region.memory_size, + gpa_base: region.guest_phys_addr, + }); + + Ok(()) + } + + fn remove_mem_region(&mut self, region: &VhostUserSingleMemoryRegion) -> VhostUserResult<()> { + let (mem, _) = self + .atomic_mem + .memory() + .remove_region(GuestAddress(region.guest_phys_addr), region.memory_size) + .map_err(|e| { + VhostUserError::ReqHandlerError(io::Error::new(io::ErrorKind::Other, e)) + })?; + + self.atomic_mem.lock().unwrap().replace(mem); + + self.backend + .write() + .unwrap() + .update_memory(self.atomic_mem.clone()) + .map_err(|e| { + VhostUserError::ReqHandlerError(io::Error::new(io::ErrorKind::Other, e)) + })?; + + self.mappings + .retain(|mapping| mapping.gpa_base != region.guest_phys_addr); + + Ok(()) + } } impl Drop for VhostUserHandler { From 4f31df77bd9e8bf910985bad830ceee9a6bb27f8 Mon Sep 17 00:00:00 2001 From: Ayaan Zaidi Date: Sun, 14 Mar 2021 09:46:26 +0530 Subject: [PATCH 054/139] Add default coverage config for x86_64 --- coverage_config_x86_64.json | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 coverage_config_x86_64.json diff --git a/coverage_config_x86_64.json b/coverage_config_x86_64.json new file mode 100644 index 0000000..a2e5cd1 --- /dev/null +++ b/coverage_config_x86_64.json @@ -0,0 +1,5 @@ +{ + "coverage_score": 0, + "exclude_path": "", + "crate_features": "" +} From b362c785c1fc3590022f31b7d57687cff7da42d4 Mon Sep 17 00:00:00 2001 From: Ayaan Zaidi Date: Sun, 14 Mar 2021 09:56:13 +0530 Subject: [PATCH 055/139] Fix build on aarch64+musl --- .cargo/config | 2 ++ 1 file changed, 2 insertions(+) create mode 100644 .cargo/config diff --git a/.cargo/config b/.cargo/config new file mode 100644 index 0000000..02cbaf3 --- /dev/null +++ b/.cargo/config @@ -0,0 +1,2 @@ +[target.aarch64-unknown-linux-musl] +rustflags = [ "-C", "target-feature=+crt-static", "-C", "link-arg=-lgcc"] From c2757c286127a4cbdb23bc39952a7bb18f3bd7d0 Mon Sep 17 00:00:00 2001 From: "dependabot-preview[bot]" <27856297+dependabot-preview[bot]@users.noreply.github.com> Date: Tue, 16 Mar 2021 06:56:48 +0000 Subject: [PATCH 056/139] Bump rust-vmm-ci from `ebc7016` to `24d66cd` Bumps [rust-vmm-ci](https://github.com/rust-vmm/rust-vmm-ci) from `ebc7016` to `24d66cd`. - [Release notes](https://github.com/rust-vmm/rust-vmm-ci/releases) - [Commits](https://github.com/rust-vmm/rust-vmm-ci/compare/ebc701641fa57f78d03f3f5ecac617b7bf7470b4...24d66cdae63d4aa7f8de01b616c015b97604a116) Signed-off-by: dependabot-preview[bot] --- rust-vmm-ci | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/rust-vmm-ci b/rust-vmm-ci index ebc7016..24d66cd 160000 --- a/rust-vmm-ci +++ b/rust-vmm-ci @@ -1 +1 @@ -Subproject commit ebc701641fa57f78d03f3f5ecac617b7bf7470b4 +Subproject commit 24d66cdae63d4aa7f8de01b616c015b97604a116 From 06214fc9cc5381a61c3d418ba4d5ba02161fdcae Mon Sep 17 00:00:00 2001 From: "dependabot-preview[bot]" <27856297+dependabot-preview[bot]@users.noreply.github.com> Date: Thu, 29 Apr 2021 16:08:41 +0000 Subject: [PATCH 057/139] Upgrade to GitHub-native Dependabot --- .github/dependabot.yml | 7 +++++++ 1 file changed, 7 insertions(+) create mode 100644 .github/dependabot.yml diff --git a/.github/dependabot.yml b/.github/dependabot.yml new file mode 100644 index 0000000..4fcd556 --- /dev/null +++ b/.github/dependabot.yml @@ -0,0 +1,7 @@ +version: 2 +updates: +- package-ecosystem: gitsubmodule + directory: "/" + schedule: + interval: daily + open-pull-requests-limit: 10 From 815858f13582e7b1237c39769ade66b380770cc5 Mon Sep 17 00:00:00 2001 From: Gaelan Steele Date: Wed, 19 May 2021 19:36:43 -0700 Subject: [PATCH 058/139] Add gitignore --- .gitignore | 2 ++ 1 file changed, 2 insertions(+) create mode 100644 .gitignore diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..fa8d85a --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +Cargo.lock +target From c9f591f393b333e2de7ee3a2cb14655c2fdccadf Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 15 Jun 2021 07:04:29 +0000 Subject: [PATCH 059/139] Bump rust-vmm-ci from `24d66cd` to `6591890` Bumps [rust-vmm-ci](https://github.com/rust-vmm/rust-vmm-ci) from `24d66cd` to `6591890`. - [Release notes](https://github.com/rust-vmm/rust-vmm-ci/releases) - [Commits](https://github.com/rust-vmm/rust-vmm-ci/compare/24d66cdae63d4aa7f8de01b616c015b97604a116...6591890ee181c75ef87d5601582db0dbc043ae07) --- updated-dependencies: - dependency-name: rust-vmm-ci dependency-type: direct:production ... Signed-off-by: dependabot[bot] --- rust-vmm-ci | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/rust-vmm-ci b/rust-vmm-ci index 24d66cd..6591890 160000 --- a/rust-vmm-ci +++ b/rust-vmm-ci @@ -1 +1 @@ -Subproject commit 24d66cdae63d4aa7f8de01b616c015b97604a116 +Subproject commit 6591890ee181c75ef87d5601582db0dbc043ae07 From 51e2cacc3a7eec2ca1bc4ce2fcd2c7ff4f63ed09 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 23 Jun 2021 01:09:30 +0000 Subject: [PATCH 060/139] Bump rust-vmm-ci from `6591890` to `d2ab3c0` Bumps [rust-vmm-ci](https://github.com/rust-vmm/rust-vmm-ci) from `6591890` to `d2ab3c0`. - [Release notes](https://github.com/rust-vmm/rust-vmm-ci/releases) - [Commits](https://github.com/rust-vmm/rust-vmm-ci/compare/6591890ee181c75ef87d5601582db0dbc043ae07...d2ab3c090833aec72eee7da1e3884032206b00e3) --- updated-dependencies: - dependency-name: rust-vmm-ci dependency-type: direct:production ... Signed-off-by: dependabot[bot] --- rust-vmm-ci | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/rust-vmm-ci b/rust-vmm-ci index 6591890..d2ab3c0 160000 --- a/rust-vmm-ci +++ b/rust-vmm-ci @@ -1 +1 @@ -Subproject commit 6591890ee181c75ef87d5601582db0dbc043ae07 +Subproject commit d2ab3c090833aec72eee7da1e3884032206b00e3 From 5f82c6b9be398c307a41ce4c4a6038ba5451a19f Mon Sep 17 00:00:00 2001 From: Gaelan Steele Date: Sun, 27 Jun 2021 19:43:06 -0700 Subject: [PATCH 061/139] Rename vm-virtio to virtio-queue. The former was split into multiple crates; everything we use is in virtio-queue. Signed-off-by: Gaelan Steele --- Cargo.toml | 2 +- src/lib.rs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index f533860..b920917 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -12,5 +12,5 @@ log = ">=0.4.6" vhost = { git = "https://github.com/rust-vmm/vhost", features = ["vhost-user-slave"] } virtio-bindings = "0.1.0" vm-memory = {version = ">=0.2.0", features = ["backend-mmap", "backend-atomic"]} -vm-virtio = { git = "https://github.com/rust-vmm/vm-virtio" } +virtio-queue = { git = "https://github.com/rust-vmm/vm-virtio" } vmm-sys-util = ">=0.3.1" diff --git a/src/lib.rs b/src/lib.rs index 6ee4371..4d81955 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -29,7 +29,7 @@ use vm_memory::{ GuestAddress, GuestAddressSpace, GuestMemoryAtomic, GuestMemoryMmap, GuestRegionMmap, MmapRegion, }; -use vm_virtio::Queue; +use virtio_queue::Queue; use vmm_sys_util::eventfd::EventFd; const MAX_MEM_SLOTS: u64 = 32; From 5b8655cfe39fe66055607a33ad7be19367f3142b Mon Sep 17 00:00:00 2001 From: Gaelan Steele Date: Sun, 27 Jun 2021 19:50:44 -0700 Subject: [PATCH 062/139] Update VhostUserSlaveReqHandlerMut impl to take Files, not RawFds. The new signatures come from an update to the vhost crate. This commit is intended as a simple, bikeshed-free fix, but I don't think it's the best way to handle this long-term. See below. In most cases, we were converting the RawFd into a File anyway, and the new code is trivial. In a few cases, though, we actually want an EventFd, which now requires this nasty construction: unsafe { EventFd::from_raw_fd(file.into_raw_fd()) This is safe--EventFd::from_raw_fd is unsafe becuase it expects to uniquely own its fd, an invariant File also has--but a little inelegant. Ideally, we would either: - change vmm-sys-util to provide a way to safely create an EventFd from a File (this would be trivial; EventFd actually stores its fd as a File internally), - pass around a generic "uniquely owned fd" struct (like that proposed by Rust RFC #3128) instead of a File, or - change vhost to pass us an EventFd instead of a File where appropriate (which, in practice, would mean implementing one of the previous options in vhost) Signed-off-by: Gaelan Steele --- src/lib.rs | 27 ++++++++++++++++----------- 1 file changed, 16 insertions(+), 11 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index 4d81955..d6985bb 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -11,6 +11,7 @@ use std::error; use std::fs::File; use std::io; use std::os::unix::io::{AsRawFd, FromRawFd, RawFd}; +use std::os::unix::prelude::IntoRawFd; use std::result; use std::sync::{Arc, Mutex, RwLock}; use std::thread; @@ -618,17 +619,16 @@ impl VhostUserSlaveReqHandlerMut for VhostUserHandler { fn set_mem_table( &mut self, ctx: &[VhostUserMemoryRegion], - fds: &[RawFd], + files: Vec, ) -> VhostUserResult<()> { // We need to create tuple of ranges from the list of VhostUserMemoryRegion // that we get from the caller. let mut regions: Vec<(GuestAddress, usize, Option)> = Vec::new(); let mut mappings: Vec = Vec::new(); - for (idx, region) in ctx.iter().enumerate() { + for (region, file) in ctx.iter().zip(files) { let g_addr = GuestAddress(region.guest_phys_addr); let len = region.memory_size as usize; - let file = unsafe { File::from_raw_fd(fds[idx]) }; let f_off = FileOffset::new(file, region.mmap_offset); regions.push((g_addr, len, Some(f_off))); @@ -764,7 +764,7 @@ impl VhostUserSlaveReqHandlerMut for VhostUserHandler { Ok(VhostUserVringState::new(index, u32::from(next_avail))) } - fn set_vring_kick(&mut self, index: u8, fd: Option) -> VhostUserResult<()> { + fn set_vring_kick(&mut self, index: u8, file: Option) -> VhostUserResult<()> { if index as usize >= self.num_queues { return Err(VhostUserError::InvalidParam); } @@ -773,8 +773,12 @@ impl VhostUserSlaveReqHandlerMut for VhostUserHandler { // Close file descriptor set by previous operations. let _ = unsafe { libc::close(kick.as_raw_fd()) }; } + // SAFETY: EventFd requires that it has sole ownership of its fd. So + // does File, so this is safe. + // Ideally, we'd have a generic way to refer to a uniquely-owned fd, + // such as that proposed by Rust RFC #3128. self.vrings[index as usize].write().unwrap().kick = - fd.map(|x| unsafe { EventFd::from_raw_fd(x) }); + file.map(|f| unsafe { EventFd::from_raw_fd(f.into_raw_fd()) }); // Quote from vhost-user specification: // Client must start ring upon receiving a kick (that is, detecting @@ -802,7 +806,7 @@ impl VhostUserSlaveReqHandlerMut for VhostUserHandler { Ok(()) } - fn set_vring_call(&mut self, index: u8, fd: Option) -> VhostUserResult<()> { + fn set_vring_call(&mut self, index: u8, file: Option) -> VhostUserResult<()> { if index as usize >= self.num_queues { return Err(VhostUserError::InvalidParam); } @@ -811,13 +815,14 @@ impl VhostUserSlaveReqHandlerMut for VhostUserHandler { // Close file descriptor set by previous operations. let _ = unsafe { libc::close(call.as_raw_fd()) }; } + // SAFETY: see comment in set_vring_kick() self.vrings[index as usize].write().unwrap().call = - fd.map(|x| unsafe { EventFd::from_raw_fd(x) }); + file.map(|f| unsafe { EventFd::from_raw_fd(f.into_raw_fd()) }); Ok(()) } - fn set_vring_err(&mut self, index: u8, fd: Option) -> VhostUserResult<()> { + fn set_vring_err(&mut self, index: u8, file: Option) -> VhostUserResult<()> { if index as usize >= self.num_queues { return Err(VhostUserError::InvalidParam); } @@ -826,8 +831,9 @@ impl VhostUserSlaveReqHandlerMut for VhostUserHandler { // Close file descriptor set by previous operations. let _ = unsafe { libc::close(err.as_raw_fd()) }; } + // SAFETY: see comment in set_vring_kick() self.vrings[index as usize].write().unwrap().err = - fd.map(|x| unsafe { EventFd::from_raw_fd(x) }); + file.map(|f| unsafe { EventFd::from_raw_fd(f.into_raw_fd()) }); Ok(()) } @@ -887,9 +893,8 @@ impl VhostUserSlaveReqHandlerMut for VhostUserHandler { fn add_mem_region( &mut self, region: &VhostUserSingleMemoryRegion, - fd: RawFd, + file: File, ) -> VhostUserResult<()> { - let file = unsafe { File::from_raw_fd(fd) }; let mmap_region = MmapRegion::from_file( FileOffset::new(file, region.mmap_offset), region.memory_size as usize, From b109e4d0109824d1734e6ca1aaf9667bc85626f5 Mon Sep 17 00:00:00 2001 From: Gaelan Steele Date: Mon, 28 Jun 2021 03:16:49 -0700 Subject: [PATCH 063/139] Remove extranous calls to libc::close. We were explicitly calling libc::close() on fds we were replacing; but since they were stored as EventFds, which close themselves on drop, this is unnecessary. Manually closing them is useless at best; at worse, if there's a race condition and another thread opens an fd between the manual close and the normal close on drop, we could have closed someone else's fd. --- src/lib.rs | 12 ------------ 1 file changed, 12 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index d6985bb..ef292c8 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -769,10 +769,6 @@ impl VhostUserSlaveReqHandlerMut for VhostUserHandler { return Err(VhostUserError::InvalidParam); } - if let Some(kick) = self.vrings[index as usize].write().unwrap().kick.take() { - // Close file descriptor set by previous operations. - let _ = unsafe { libc::close(kick.as_raw_fd()) }; - } // SAFETY: EventFd requires that it has sole ownership of its fd. So // does File, so this is safe. // Ideally, we'd have a generic way to refer to a uniquely-owned fd, @@ -811,10 +807,6 @@ impl VhostUserSlaveReqHandlerMut for VhostUserHandler { return Err(VhostUserError::InvalidParam); } - if let Some(call) = self.vrings[index as usize].write().unwrap().call.take() { - // Close file descriptor set by previous operations. - let _ = unsafe { libc::close(call.as_raw_fd()) }; - } // SAFETY: see comment in set_vring_kick() self.vrings[index as usize].write().unwrap().call = file.map(|f| unsafe { EventFd::from_raw_fd(f.into_raw_fd()) }); @@ -827,10 +819,6 @@ impl VhostUserSlaveReqHandlerMut for VhostUserHandler { return Err(VhostUserError::InvalidParam); } - if let Some(err) = self.vrings[index as usize].write().unwrap().err.take() { - // Close file descriptor set by previous operations. - let _ = unsafe { libc::close(err.as_raw_fd()) }; - } // SAFETY: see comment in set_vring_kick() self.vrings[index as usize].write().unwrap().err = file.map(|f| unsafe { EventFd::from_raw_fd(f.into_raw_fd()) }); From 2db5f82812e8ab80f87e5f014c6842247a33dece Mon Sep 17 00:00:00 2001 From: Gaelan Steele Date: Sun, 27 Jun 2021 19:52:58 -0700 Subject: [PATCH 064/139] Minimally implement {get,set}_inflight_fd. This implementation just returns Err(InvalidOperation), which is the correct response for a backend that hasn't negotiated the inflight feature. Eventually, it'd be nice if we allowed the backend to negotiate this feature; harshanavkis@8b44378 and slp@3346318 are two attempts at this. This is intended as a simple, bikeshed-free way to get things compiling again while we decide on, and implement, a way to actually support the inflight feature. Signed-off-by: Gaelan Steele --- src/lib.rs | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/src/lib.rs b/src/lib.rs index ef292c8..3564b06 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -945,6 +945,20 @@ impl VhostUserSlaveReqHandlerMut for VhostUserHandler { Ok(()) } + + fn get_inflight_fd( + &mut self, + _inflight: &vhost::vhost_user::message::VhostUserInflight, + ) -> VhostUserResult<(vhost::vhost_user::message::VhostUserInflight, File)> { + // Assume the backend hasn't negotiated the inflight feature; it + // wouldn't be correct for the backend to do so, as we don't (yet) + // provide a way for it to handle such requests. + Err(VhostUserError::InvalidOperation) + } + + fn set_inflight_fd(&mut self, _inflight: &vhost::vhost_user::message::VhostUserInflight, _file: File) -> VhostUserResult<()> { + Err(VhostUserError::InvalidOperation) + } } impl Drop for VhostUserHandler { From dc2c47d05d571303ce55b11d6e96871c614e130d Mon Sep 17 00:00:00 2001 From: Stefano Garzarella Date: Fri, 27 Aug 2021 14:00:53 +0200 Subject: [PATCH 065/139] CODEOWNERS: add stefano-garzarella Add myself to CODEOWNERS to help with reviews and maintaining this code. Signed-off-by: Stefano Garzarella --- CODEOWNERS | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CODEOWNERS b/CODEOWNERS index ab19725..fc291db 100644 --- a/CODEOWNERS +++ b/CODEOWNERS @@ -1,2 +1,2 @@ # Add the list of code owners here (using their GitHub username) -* @jiangliu @sboeuf @slp +* @jiangliu @sboeuf @slp @stefano-garzarella From e22aed6d5a2317290f4a1f077b45a143280ff34a Mon Sep 17 00:00:00 2001 From: Liu Jiang Date: Sun, 8 Aug 2021 22:49:52 +0800 Subject: [PATCH 066/139] Update denpendent crates to latest versions Update dependent crates to the latest versions: - vhost v0.1 - vm-memory v0.6 - vmm-sys-util v0.8 Signed-off-by: Liu Jiang --- Cargo.toml | 8 ++++---- src/lib.rs | 9 +++++++-- 2 files changed, 11 insertions(+), 6 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index b920917..2085046 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -9,8 +9,8 @@ license = "Apache-2.0" epoll = ">=4.0.1" libc = ">=0.2.39" log = ">=0.4.6" -vhost = { git = "https://github.com/rust-vmm/vhost", features = ["vhost-user-slave"] } -virtio-bindings = "0.1.0" -vm-memory = {version = ">=0.2.0", features = ["backend-mmap", "backend-atomic"]} +vhost = { version = "0.1", features = ["vhost-user-slave"] } +virtio-bindings = "0.1" virtio-queue = { git = "https://github.com/rust-vmm/vm-virtio" } -vmm-sys-util = ">=0.3.1" +vm-memory = {version = "0.6", features = ["backend-mmap", "backend-atomic"]} +vmm-sys-util = "0.8" diff --git a/src/lib.rs b/src/lib.rs index 3564b06..7c3a5b0 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -15,6 +15,7 @@ use std::os::unix::prelude::IntoRawFd; use std::result; use std::sync::{Arc, Mutex, RwLock}; use std::thread; + use vhost::vhost_user::message::{ VhostUserConfigFlags, VhostUserMemoryRegion, VhostUserProtocolFeatures, VhostUserSingleMemoryRegion, VhostUserVirtioFeatures, VhostUserVringAddrFlags, @@ -25,12 +26,12 @@ use vhost::vhost_user::{ VhostUserSlaveReqHandlerMut, }; use virtio_bindings::bindings::virtio_ring::VIRTIO_RING_F_EVENT_IDX; +use virtio_queue::Queue; use vm_memory::guest_memory::FileOffset; use vm_memory::{ GuestAddress, GuestAddressSpace, GuestMemoryAtomic, GuestMemoryMmap, GuestRegionMmap, MmapRegion, }; -use virtio_queue::Queue; use vmm_sys_util::eventfd::EventFd; const MAX_MEM_SLOTS: u64 = 32; @@ -956,7 +957,11 @@ impl VhostUserSlaveReqHandlerMut for VhostUserHandler { Err(VhostUserError::InvalidOperation) } - fn set_inflight_fd(&mut self, _inflight: &vhost::vhost_user::message::VhostUserInflight, _file: File) -> VhostUserResult<()> { + fn set_inflight_fd( + &mut self, + _inflight: &vhost::vhost_user::message::VhostUserInflight, + _file: File, + ) -> VhostUserResult<()> { Err(VhostUserError::InvalidOperation) } } From a001824c19d7568e187eeb7e1d8f416a7983a2cf Mon Sep 17 00:00:00 2001 From: Liu Jiang Date: Fri, 19 Feb 2021 11:03:52 +0800 Subject: [PATCH 067/139] Move trait VhostUserBackend to dedicated file Move the VhostUserBackend trait to a dedicated file, preparing for coming changes. Signed-off-by: Liu Jiang --- src/backend.rs | 86 ++++++++++++++++++++++++++++++++++++++++++++++++++ src/lib.rs | 74 ++----------------------------------------- 2 files changed, 89 insertions(+), 71 deletions(-) create mode 100644 src/backend.rs diff --git a/src/backend.rs b/src/backend.rs new file mode 100644 index 0000000..7d14144 --- /dev/null +++ b/src/backend.rs @@ -0,0 +1,86 @@ +// Copyright 2019 Intel Corporation. All Rights Reserved. +// Copyright 2019-2021 Alibaba Cloud. All rights reserved. +// +// SPDX-License-Identifier: Apache-2.0 + +use std::io; +use std::result; +use std::sync::{Arc, RwLock}; + +use vhost::vhost_user::message::VhostUserProtocolFeatures; +use vhost::vhost_user::SlaveFsCacheReq; +use vm_memory::{GuestMemoryAtomic, GuestMemoryMmap}; +use vmm_sys_util::eventfd::EventFd; + +use super::Vring; + +/// This trait must be implemented by the caller in order to provide backend +/// specific implementation. +pub trait VhostUserBackend: Send + Sync + 'static { + /// Number of queues. + fn num_queues(&self) -> usize; + + /// Depth of each queue. + fn max_queue_size(&self) -> usize; + + /// Available virtio features. + fn features(&self) -> u64; + + /// Acked virtio features. + fn acked_features(&mut self, _features: u64) {} + + /// Virtio protocol features. + fn protocol_features(&self) -> VhostUserProtocolFeatures; + + /// Tell the backend if EVENT_IDX has been negotiated. + fn set_event_idx(&mut self, enabled: bool); + + /// Update guest memory regions. + fn update_memory( + &mut self, + atomic_mem: GuestMemoryAtomic, + ) -> result::Result<(), io::Error>; + + /// This function gets called if the backend registered some additional + /// listeners onto specific file descriptors. The library can handle + /// virtqueues on its own, but does not know what to do with events + /// happening on custom listeners. + fn handle_event( + &self, + device_event: u16, + evset: epoll::Events, + vrings: &[Arc>], + thread_id: usize, + ) -> result::Result; + + /// Get virtio device configuration. + /// A default implementation is provided as we cannot expect all backends + /// to implement this function. + fn get_config(&self, _offset: u32, _size: u32) -> Vec { + Vec::new() + } + + /// Set virtio device configuration. + /// A default implementation is provided as we cannot expect all backends + /// to implement this function. + fn set_config(&mut self, _offset: u32, _buf: &[u8]) -> result::Result<(), io::Error> { + Ok(()) + } + + /// Provide an exit EventFd + /// When this EventFd is written to the worker thread will exit. An optional id may + /// also be provided, if it not provided then the exit event will be first event id + /// after the last queue + fn exit_event(&self, _thread_index: usize) -> Option<(EventFd, Option)> { + None + } + + /// Set slave fd. + /// A default implementation is provided as we cannot expect all backends + /// to implement this function. + fn set_slave_req_fd(&mut self, _vu_req: SlaveFsCacheReq) {} + + fn queues_per_thread(&self) -> Vec { + vec![0xffff_ffff] + } +} diff --git a/src/lib.rs b/src/lib.rs index 7c3a5b0..f7276f0 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -34,6 +34,9 @@ use vm_memory::{ }; use vmm_sys_util::eventfd::EventFd; +mod backend; +pub use backend::VhostUserBackend; + const MAX_MEM_SLOTS: u64 = 32; #[derive(Debug)] @@ -62,77 +65,6 @@ pub enum Error { /// Result of vhost-user daemon operations. pub type Result = result::Result; -/// This trait must be implemented by the caller in order to provide backend -/// specific implementation. -pub trait VhostUserBackend: Send + Sync + 'static { - /// Number of queues. - fn num_queues(&self) -> usize; - - /// Depth of each queue. - fn max_queue_size(&self) -> usize; - - /// Available virtio features. - fn features(&self) -> u64; - - /// Acked virtio features. - fn acked_features(&mut self, _features: u64) {} - - /// Virtio protocol features. - fn protocol_features(&self) -> VhostUserProtocolFeatures; - - /// Tell the backend if EVENT_IDX has been negotiated. - fn set_event_idx(&mut self, enabled: bool); - - /// Update guest memory regions. - fn update_memory( - &mut self, - atomic_mem: GuestMemoryAtomic, - ) -> result::Result<(), io::Error>; - - /// This function gets called if the backend registered some additional - /// listeners onto specific file descriptors. The library can handle - /// virtqueues on its own, but does not know what to do with events - /// happening on custom listeners. - fn handle_event( - &self, - device_event: u16, - evset: epoll::Events, - vrings: &[Arc>], - thread_id: usize, - ) -> result::Result; - - /// Get virtio device configuration. - /// A default implementation is provided as we cannot expect all backends - /// to implement this function. - fn get_config(&self, _offset: u32, _size: u32) -> Vec { - Vec::new() - } - - /// Set virtio device configuration. - /// A default implementation is provided as we cannot expect all backends - /// to implement this function. - fn set_config(&mut self, _offset: u32, _buf: &[u8]) -> result::Result<(), io::Error> { - Ok(()) - } - - /// Provide an exit EventFd - /// When this EventFd is written to the worker thread will exit. An optional id may - /// also be provided, if it not provided then the exit event will be first event id - /// after the last queue - fn exit_event(&self, _thread_index: usize) -> Option<(EventFd, Option)> { - None - } - - /// Set slave fd. - /// A default implementation is provided as we cannot expect all backends - /// to implement this function. - fn set_slave_req_fd(&mut self, _vu_req: SlaveFsCacheReq) {} - - fn queues_per_thread(&self) -> Vec { - vec![0xffff_ffff] - } -} - /// This structure is the public API the backend is allowed to interact with /// in order to run a fully functional vhost-user daemon. pub struct VhostUserDaemon { From cecd4ba5658365afb460dd7f2fd4058c56ac6e4e Mon Sep 17 00:00:00 2001 From: Liu Jiang Date: Fri, 19 Feb 2021 13:09:41 +0800 Subject: [PATCH 068/139] Refine VhostUserBackend for multi-threading Refine VhostUserBackend to support interior mutability to better support multi-threading. The previous version of VhostUserBackend has been renamed as VhostUserBackendMut, with an implementatio of impl VhostUserBackend for RwLock { } to ease transition. The change also improves code readability. Signed-off-by: Liu Jiang --- src/backend.rs | 508 ++++++++++++++++++++++++++++++++++++++++++++++--- src/lib.rs | 79 ++++---- 2 files changed, 510 insertions(+), 77 deletions(-) diff --git a/src/backend.rs b/src/backend.rs index 7d14144..14c4fe4 100644 --- a/src/backend.rs +++ b/src/backend.rs @@ -3,9 +3,25 @@ // // SPDX-License-Identifier: Apache-2.0 +//! Traits for vhost user backend servers to implement virtio data plain services. +//! +//! Define two traits for vhost user backend servers to implement virtio data plane services. +//! The only difference between the two traits is mutability. The [VhostUserBackend] trait is +//! designed with interior mutability, so the implementor may choose the suitable way to protect +//! itself from concurrent accesses. The [VhostUserBackendMut] is designed without interior +//! mutability, and an implementation of: +//! ```ignore +//! impl VhostUserBackend for RwLock { } +//! ``` +//! is provided for convenience. +//! +//! [VhostUserBackend]: trait.VhostUserBackend.html +//! [VhostUserBackendMut]: trait.VhostUserBackendMut.html + use std::io; +use std::ops::Deref; use std::result; -use std::sync::{Arc, RwLock}; +use std::sync::{Arc, Mutex, RwLock}; use vhost::vhost_user::message::VhostUserProtocolFeatures; use vhost::vhost_user::SlaveFsCacheReq; @@ -14,37 +30,82 @@ use vmm_sys_util::eventfd::EventFd; use super::Vring; -/// This trait must be implemented by the caller in order to provide backend -/// specific implementation. +/// Trait with interior mutability for vhost user backend servers to implement concrete services. +/// +/// To support multi-threading and asynchronous IO, we enforce `the Send + Sync + 'static`. +/// So there's no plan for support of "Rc" and "RefCell". pub trait VhostUserBackend: Send + Sync + 'static { - /// Number of queues. + /// Get number of queues supported. fn num_queues(&self) -> usize; - /// Depth of each queue. + /// Get maximum queue size supported. fn max_queue_size(&self) -> usize; - /// Available virtio features. + /// Get available virtio features. fn features(&self) -> u64; - /// Acked virtio features. - fn acked_features(&mut self, _features: u64) {} + /// Set acknowledged virtio features. + fn acked_features(&self, _features: u64) {} - /// Virtio protocol features. + /// Get available vhost protocol features. fn protocol_features(&self) -> VhostUserProtocolFeatures; - /// Tell the backend if EVENT_IDX has been negotiated. - fn set_event_idx(&mut self, enabled: bool); + /// Enable or disable the virtio EVENT_IDX feature + fn set_event_idx(&self, enabled: bool); + + /// Get virtio device configuration. + /// + /// A default implementation is provided as we cannot expect all backends to implement this + /// function. + fn get_config(&self, _offset: u32, _size: u32) -> Vec { + Vec::new() + } + + /// Set virtio device configuration. + /// + /// A default implementation is provided as we cannot expect all backends to implement this + /// function. + fn set_config(&self, _offset: u32, _buf: &[u8]) -> result::Result<(), io::Error> { + Ok(()) + } /// Update guest memory regions. fn update_memory( - &mut self, + &self, atomic_mem: GuestMemoryAtomic, ) -> result::Result<(), io::Error>; - /// This function gets called if the backend registered some additional - /// listeners onto specific file descriptors. The library can handle - /// virtqueues on its own, but does not know what to do with events - /// happening on custom listeners. + /// Set handler for communicating with the master by the slave communication channel. + /// + /// A default implementation is provided as we cannot expect all backends to implement this + /// function. + /// + /// TODO: this interface is designed only for vhost-user-fs, it should be refined. + fn set_slave_req_fd(&self, _vu_req: SlaveFsCacheReq) {} + + /// Get the map to map queue index to worker thread index. + /// + /// A return value of [2, 2, 4] means: the first two queues will be handled by worker thread 0, + /// the following two queues will be handled by worker thread 1, and the last four queues will + /// be handled by worker thread 2. + fn queues_per_thread(&self) -> Vec { + vec![0xffff_ffff] + } + + /// Provide an optional exit EventFd for the specified worker thread. + /// + /// If an (`EventFd`, `token`) pair is returned, the returned `EventFd` will be monitored for IO + /// events by using epoll with the specified `token`. When the returned EventFd is written to, + /// the worker thread will exit. + fn exit_event(&self, _thread_index: usize) -> Option<(EventFd, u16)> { + None + } + + /// Handle IO events for backend registered file descriptors. + /// + /// This function gets called if the backend registered some additional listeners onto specific + /// file descriptors. The library can handle virtqueues on its own, but does not know what to + /// do with events happening on custom listeners. fn handle_event( &self, device_event: u16, @@ -52,35 +113,422 @@ pub trait VhostUserBackend: Send + Sync + 'static { vrings: &[Arc>], thread_id: usize, ) -> result::Result; +} + +/// Trait without interior mutability for vhost user backend servers to implement concrete services. +pub trait VhostUserBackendMut: Send + Sync + 'static { + /// Get number of queues supported. + fn num_queues(&self) -> usize; + + /// Get maximum queue size supported. + fn max_queue_size(&self) -> usize; + + /// Get available virtio features. + fn features(&self) -> u64; + + /// Set acknowledged virtio features. + fn acked_features(&mut self, _features: u64) {} + + /// Get available vhost protocol features. + fn protocol_features(&self) -> VhostUserProtocolFeatures; + + /// Enable or disable the virtio EVENT_IDX feature + fn set_event_idx(&mut self, enabled: bool); /// Get virtio device configuration. - /// A default implementation is provided as we cannot expect all backends - /// to implement this function. + /// + /// A default implementation is provided as we cannot expect all backends to implement this + /// function. fn get_config(&self, _offset: u32, _size: u32) -> Vec { Vec::new() } /// Set virtio device configuration. - /// A default implementation is provided as we cannot expect all backends - /// to implement this function. + /// + /// A default implementation is provided as we cannot expect all backends to implement this + /// function. fn set_config(&mut self, _offset: u32, _buf: &[u8]) -> result::Result<(), io::Error> { Ok(()) } - /// Provide an exit EventFd - /// When this EventFd is written to the worker thread will exit. An optional id may - /// also be provided, if it not provided then the exit event will be first event id - /// after the last queue - fn exit_event(&self, _thread_index: usize) -> Option<(EventFd, Option)> { - None - } + /// Update guest memory regions. + fn update_memory( + &mut self, + atomic_mem: GuestMemoryAtomic, + ) -> result::Result<(), io::Error>; - /// Set slave fd. - /// A default implementation is provided as we cannot expect all backends - /// to implement this function. + /// Set handler for communicating with the master by the slave communication channel. + /// + /// A default implementation is provided as we cannot expect all backends to implement this + /// function. + /// + /// TODO: this interface is designed only for vhost-user-fs, it should be refined. fn set_slave_req_fd(&mut self, _vu_req: SlaveFsCacheReq) {} + /// Get the map to map queue index to worker thread index. + /// + /// A return value of [2, 2, 4] means: the first two queues will be handled by worker thread 0, + /// the following two queues will be handled by worker thread 1, and the last four queues will + /// be handled by worker thread 2. fn queues_per_thread(&self) -> Vec { vec![0xffff_ffff] } + + /// Provide an optional exit EventFd for the specified worker thread. + /// + /// If an (`EventFd`, `token`) pair is returned, the returned `EventFd` will be monitored for IO + /// events by using epoll with the specified `token`. When the returned EventFd is written to, + /// the worker thread will exit. + fn exit_event(&self, _thread_index: usize) -> Option<(EventFd, u16)> { + None + } + + /// Handle IO events for backend registered file descriptors. + /// + /// This function gets called if the backend registered some additional listeners onto specific + /// file descriptors. The library can handle virtqueues on its own, but does not know what to + /// do with events happening on custom listeners. + fn handle_event( + &mut self, + device_event: u16, + evset: epoll::Events, + vrings: &[Arc>], + thread_id: usize, + ) -> result::Result; +} + +impl VhostUserBackend for Arc { + fn num_queues(&self) -> usize { + self.deref().num_queues() + } + + fn max_queue_size(&self) -> usize { + self.deref().max_queue_size() + } + + fn features(&self) -> u64 { + self.deref().features() + } + + fn acked_features(&self, features: u64) { + self.deref().acked_features(features) + } + + fn protocol_features(&self) -> VhostUserProtocolFeatures { + self.deref().protocol_features() + } + + fn set_event_idx(&self, enabled: bool) { + self.deref().set_event_idx(enabled) + } + + fn get_config(&self, offset: u32, size: u32) -> Vec { + self.deref().get_config(offset, size) + } + + fn set_config(&self, offset: u32, buf: &[u8]) -> Result<(), io::Error> { + self.deref().set_config(offset, buf) + } + + fn update_memory( + &self, + atomic_mem: GuestMemoryAtomic, + ) -> Result<(), io::Error> { + self.deref().update_memory(atomic_mem) + } + + fn set_slave_req_fd(&self, vu_req: SlaveFsCacheReq) { + self.deref().set_slave_req_fd(vu_req) + } + + fn queues_per_thread(&self) -> Vec { + self.deref().queues_per_thread() + } + + fn exit_event(&self, thread_index: usize) -> Option<(EventFd, u16)> { + self.deref().exit_event(thread_index) + } + + fn handle_event( + &self, + device_event: u16, + evset: epoll::Events, + vrings: &[Arc>], + thread_id: usize, + ) -> Result { + self.deref() + .handle_event(device_event, evset, vrings, thread_id) + } +} + +impl VhostUserBackend for Mutex { + fn num_queues(&self) -> usize { + self.lock().unwrap().num_queues() + } + + fn max_queue_size(&self) -> usize { + self.lock().unwrap().max_queue_size() + } + + fn features(&self) -> u64 { + self.lock().unwrap().features() + } + + fn acked_features(&self, features: u64) { + self.lock().unwrap().acked_features(features) + } + + fn protocol_features(&self) -> VhostUserProtocolFeatures { + self.lock().unwrap().protocol_features() + } + + fn set_event_idx(&self, enabled: bool) { + self.lock().unwrap().set_event_idx(enabled) + } + + fn get_config(&self, offset: u32, size: u32) -> Vec { + self.lock().unwrap().get_config(offset, size) + } + + fn set_config(&self, offset: u32, buf: &[u8]) -> Result<(), io::Error> { + self.lock().unwrap().set_config(offset, buf) + } + + fn update_memory( + &self, + atomic_mem: GuestMemoryAtomic, + ) -> Result<(), io::Error> { + self.lock().unwrap().update_memory(atomic_mem) + } + + fn set_slave_req_fd(&self, vu_req: SlaveFsCacheReq) { + self.lock().unwrap().set_slave_req_fd(vu_req) + } + + fn queues_per_thread(&self) -> Vec { + self.lock().unwrap().queues_per_thread() + } + + fn exit_event(&self, thread_index: usize) -> Option<(EventFd, u16)> { + self.lock().unwrap().exit_event(thread_index) + } + + fn handle_event( + &self, + device_event: u16, + evset: epoll::Events, + vrings: &[Arc>], + thread_id: usize, + ) -> Result { + self.lock() + .unwrap() + .handle_event(device_event, evset, vrings, thread_id) + } +} + +impl VhostUserBackend for RwLock { + fn num_queues(&self) -> usize { + self.read().unwrap().num_queues() + } + + fn max_queue_size(&self) -> usize { + self.read().unwrap().max_queue_size() + } + + fn features(&self) -> u64 { + self.read().unwrap().features() + } + + fn acked_features(&self, features: u64) { + self.write().unwrap().acked_features(features) + } + + fn protocol_features(&self) -> VhostUserProtocolFeatures { + self.read().unwrap().protocol_features() + } + + fn set_event_idx(&self, enabled: bool) { + self.write().unwrap().set_event_idx(enabled) + } + + fn get_config(&self, offset: u32, size: u32) -> Vec { + self.read().unwrap().get_config(offset, size) + } + + fn set_config(&self, offset: u32, buf: &[u8]) -> Result<(), io::Error> { + self.write().unwrap().set_config(offset, buf) + } + + fn update_memory( + &self, + atomic_mem: GuestMemoryAtomic, + ) -> Result<(), io::Error> { + self.write().unwrap().update_memory(atomic_mem) + } + + fn set_slave_req_fd(&self, vu_req: SlaveFsCacheReq) { + self.write().unwrap().set_slave_req_fd(vu_req) + } + + fn queues_per_thread(&self) -> Vec { + self.read().unwrap().queues_per_thread() + } + + fn exit_event(&self, thread_index: usize) -> Option<(EventFd, u16)> { + self.read().unwrap().exit_event(thread_index) + } + + fn handle_event( + &self, + device_event: u16, + evset: epoll::Events, + vrings: &[Arc>], + thread_id: usize, + ) -> Result { + self.write() + .unwrap() + .handle_event(device_event, evset, vrings, thread_id) + } +} + +#[cfg(test)] +mod tests { + use super::*; + use epoll::Events; + use std::io::Error; + use std::sync::Mutex; + + struct MockVhostBackend { + events: u64, + event_idx: bool, + acked_features: u64, + } + + impl MockVhostBackend { + fn new() -> Self { + MockVhostBackend { + events: 0, + event_idx: false, + acked_features: 0, + } + } + } + + impl VhostUserBackendMut for MockVhostBackend { + fn num_queues(&self) -> usize { + 2 + } + + fn max_queue_size(&self) -> usize { + 256 + } + + fn features(&self) -> u64 { + 0xffff_ffff_ffff_ffff + } + + fn acked_features(&mut self, features: u64) { + self.acked_features = features; + } + + fn protocol_features(&self) -> VhostUserProtocolFeatures { + VhostUserProtocolFeatures::all() + } + + fn set_event_idx(&mut self, enabled: bool) { + self.event_idx = enabled; + } + + fn get_config(&self, offset: u32, size: u32) -> Vec { + assert_eq!(offset, 0x200); + assert_eq!(size, 8); + + vec![0xa5u8; 8] + } + + fn set_config(&mut self, offset: u32, buf: &[u8]) -> Result<(), Error> { + assert_eq!(offset, 0x200); + assert_eq!(buf.len(), 8); + assert_eq!(buf, &[0xa5u8; 8]); + + Ok(()) + } + + fn update_memory( + &mut self, + _atomic_mem: GuestMemoryAtomic, + ) -> Result<(), Error> { + Ok(()) + } + + fn set_slave_req_fd(&mut self, _vu_req: SlaveFsCacheReq) {} + + fn queues_per_thread(&self) -> Vec { + vec![1, 1] + } + + fn exit_event(&self, _thread_index: usize) -> Option<(EventFd, u16)> { + let event_fd = EventFd::new(0).unwrap(); + + Some((event_fd, 0x100)) + } + + fn handle_event( + &mut self, + _device_event: u16, + _evset: Events, + _vrings: &[Arc>], + _thread_id: usize, + ) -> Result { + self.events += 1; + + Ok(false) + } + } + + #[test] + fn test_new_mock_backend_mutex() { + let backend = Arc::new(Mutex::new(MockVhostBackend::new())); + + assert_eq!(backend.num_queues(), 2); + assert_eq!(backend.max_queue_size(), 256); + assert_eq!(backend.features(), 0xffff_ffff_ffff_ffff); + assert_eq!( + backend.protocol_features(), + VhostUserProtocolFeatures::all() + ); + assert_eq!(backend.queues_per_thread(), [1, 1]); + + assert_eq!(backend.get_config(0x200, 8), vec![0xa5; 8]); + backend.set_config(0x200, &vec![0xa5; 8]).unwrap(); + + backend.acked_features(0xffff); + assert_eq!(backend.lock().unwrap().acked_features, 0xffff); + + backend.set_event_idx(true); + assert_eq!(backend.lock().unwrap().event_idx, true); + } + + #[test] + fn test_new_mock_backend_rwlock() { + let backend = Arc::new(RwLock::new(MockVhostBackend::new())); + + assert_eq!(backend.num_queues(), 2); + assert_eq!(backend.max_queue_size(), 256); + assert_eq!(backend.features(), 0xffff_ffff_ffff_ffff); + assert_eq!( + backend.protocol_features(), + VhostUserProtocolFeatures::all() + ); + assert_eq!(backend.queues_per_thread(), [1, 1]); + + assert_eq!(backend.get_config(0x200, 8), vec![0xa5; 8]); + backend.set_config(0x200, &vec![0xa5; 8]).unwrap(); + + backend.acked_features(0xffff); + assert_eq!(backend.read().unwrap().acked_features, 0xffff); + + backend.set_event_idx(true); + assert_eq!(backend.read().unwrap().event_idx, true); + } } diff --git a/src/lib.rs b/src/lib.rs index f7276f0..81d23d5 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -34,8 +34,8 @@ use vm_memory::{ }; use vmm_sys_util::eventfd::EventFd; -mod backend; -pub use backend::VhostUserBackend; +pub mod backend; +pub use backend::{VhostUserBackend, VhostUserBackendMut}; const MAX_MEM_SLOTS: u64 = 32; @@ -73,14 +73,14 @@ pub struct VhostUserDaemon { main_thread: Option>>, } -impl VhostUserDaemon { +impl VhostUserDaemon { /// Create the daemon instance, providing the backend implementation of /// VhostUserBackend. /// Under the hood, this will start a dedicated thread responsible for /// listening onto registered event. Those events can be vring events or /// custom events from the backend, but they get to be registered later /// during the sequence. - pub fn new(name: String, backend: Arc>) -> Result { + pub fn new(name: String, backend: S) -> Result { let handler = Arc::new(Mutex::new( VhostUserHandler::new(backend).map_err(Error::NewVhostUserHandler)?, )); @@ -194,7 +194,7 @@ pub enum VringEpollHandlerError { type VringEpollHandlerResult = std::result::Result; struct VringEpollHandler { - backend: Arc>, + backend: S, vrings: Vec>>, exit_event_id: Option, thread_id: usize, @@ -225,8 +225,6 @@ impl VringEpollHandler { } self.backend - .read() - .unwrap() .handle_event(device_event, evset, &self.vrings, self.thread_id) .map_err(VringEpollHandlerError::HandleEventBackendHandling) } @@ -373,7 +371,7 @@ impl error::Error for VhostUserHandlerError {} type VhostUserHandlerResult = std::result::Result; struct VhostUserHandler { - backend: Arc>, + backend: S, workers: Vec>, owned: bool, features_acked: bool, @@ -388,11 +386,11 @@ struct VhostUserHandler { worker_threads: Vec>>, } -impl VhostUserHandler { - fn new(backend: Arc>) -> VhostUserHandlerResult { - let num_queues = backend.read().unwrap().num_queues(); - let max_queue_size = backend.read().unwrap().max_queue_size(); - let queues_per_thread = backend.read().unwrap().queues_per_thread(); +impl VhostUserHandler { + fn new(backend: S) -> VhostUserHandlerResult { + let num_queues = backend.num_queues(); + let max_queue_size = backend.max_queue_size(); + let queues_per_thread = backend.queues_per_thread(); let atomic_mem = GuestMemoryAtomic::new(GuestMemoryMmap::new()); @@ -416,21 +414,19 @@ impl VhostUserHandler { let vring_worker = Arc::new(VringWorker { epoll_file }); let worker = vring_worker.clone(); - let exit_event_id = if let Some((exit_event_fd, exit_event_id)) = - backend.read().unwrap().exit_event(thread_id) - { - let exit_event_id = exit_event_id.unwrap_or(num_queues as u16); - worker - .register_listener( - exit_event_fd.as_raw_fd(), - epoll::Events::EPOLLIN, - u64::from(exit_event_id), - ) - .map_err(VhostUserHandlerError::RegisterExitEvent)?; - Some(exit_event_id) - } else { - None - }; + let exit_event_id = + if let Some((exit_event_fd, exit_event_id)) = backend.exit_event(thread_id) { + worker + .register_listener( + exit_event_fd.as_raw_fd(), + epoll::Events::EPOLLIN, + u64::from(exit_event_id), + ) + .map_err(VhostUserHandlerError::RegisterExitEvent)?; + Some(exit_event_id) + } else { + None + }; let mut thread_vrings: Vec>> = Vec::new(); for (index, vring) in vrings.iter().enumerate() { @@ -487,7 +483,7 @@ impl VhostUserHandler { } } -impl VhostUserSlaveReqHandlerMut for VhostUserHandler { +impl VhostUserSlaveReqHandlerMut for VhostUserHandler { fn set_owner(&mut self) -> VhostUserResult<()> { if self.owned { return Err(VhostUserError::InvalidOperation); @@ -505,11 +501,11 @@ impl VhostUserSlaveReqHandlerMut for VhostUserHandler { } fn get_features(&mut self) -> VhostUserResult { - Ok(self.backend.read().unwrap().features()) + Ok(self.backend.features()) } fn set_features(&mut self, features: u64) -> VhostUserResult<()> { - if (features & !self.backend.read().unwrap().features()) != 0 { + if (features & !self.backend.features()) != 0 { return Err(VhostUserError::InvalidParam); } @@ -529,16 +525,13 @@ impl VhostUserSlaveReqHandlerMut for VhostUserHandler { vring.write().unwrap().enabled = vring_enabled; } - self.backend - .write() - .unwrap() - .acked_features(self.acked_features); + self.backend.acked_features(self.acked_features); Ok(()) } fn get_protocol_features(&mut self) -> VhostUserResult { - Ok(self.backend.read().unwrap().protocol_features()) + Ok(self.backend.protocol_features()) } fn set_protocol_features(&mut self, features: u64) -> VhostUserResult<()> { @@ -581,8 +574,6 @@ impl VhostUserSlaveReqHandlerMut for VhostUserHandler { self.atomic_mem.lock().unwrap().replace(mem); self.backend - .write() - .unwrap() .update_memory(self.atomic_mem.clone()) .map_err(|e| { VhostUserError::ReqHandlerError(io::Error::new(io::ErrorKind::Other, e)) @@ -657,7 +648,7 @@ impl VhostUserSlaveReqHandlerMut for VhostUserHandler { .unwrap() .mut_queue() .set_event_idx(event_idx); - self.backend.write().unwrap().set_event_idx(event_idx); + self.backend.set_event_idx(event_idx); Ok(()) } @@ -783,7 +774,7 @@ impl VhostUserSlaveReqHandlerMut for VhostUserHandler { size: u32, _flags: VhostUserConfigFlags, ) -> VhostUserResult> { - Ok(self.backend.read().unwrap().get_config(offset, size)) + Ok(self.backend.get_config(offset, size)) } fn set_config( @@ -793,8 +784,6 @@ impl VhostUserSlaveReqHandlerMut for VhostUserHandler { _flags: VhostUserConfigFlags, ) -> VhostUserResult<()> { self.backend - .write() - .unwrap() .set_config(offset, buf) .map_err(VhostUserError::ReqHandlerError) } @@ -804,7 +793,7 @@ impl VhostUserSlaveReqHandlerMut for VhostUserHandler { vu_req.set_reply_ack_flag(true); } - self.backend.write().unwrap().set_slave_req_fd(vu_req); + self.backend.set_slave_req_fd(vu_req); } fn get_max_mem_slots(&mut self) -> VhostUserResult { @@ -838,8 +827,6 @@ impl VhostUserSlaveReqHandlerMut for VhostUserHandler { self.atomic_mem.lock().unwrap().replace(mem); self.backend - .write() - .unwrap() .update_memory(self.atomic_mem.clone()) .map_err(|e| { VhostUserError::ReqHandlerError(io::Error::new(io::ErrorKind::Other, e)) @@ -866,8 +853,6 @@ impl VhostUserSlaveReqHandlerMut for VhostUserHandler { self.atomic_mem.lock().unwrap().replace(mem); self.backend - .write() - .unwrap() .update_memory(self.atomic_mem.clone()) .map_err(|e| { VhostUserError::ReqHandlerError(io::Error::new(io::ErrorKind::Other, e)) From e068e3311e3faab7c7fcff67c77786f2b0aa164d Mon Sep 17 00:00:00 2001 From: Liu Jiang Date: Fri, 19 Feb 2021 13:29:35 +0800 Subject: [PATCH 069/139] Move VhostUserHandler to handler.rs Move VhostUserHandler to handler.rs to ease maintenance. Signed-off-by: Liu Jiang --- src/handler.rs | 586 +++++++++++++++++++++++++++++++++++++++++++++++++ src/lib.rs | 584 +----------------------------------------------- 2 files changed, 594 insertions(+), 576 deletions(-) create mode 100644 src/handler.rs diff --git a/src/handler.rs b/src/handler.rs new file mode 100644 index 0000000..028e699 --- /dev/null +++ b/src/handler.rs @@ -0,0 +1,586 @@ +// Copyright 2019 Intel Corporation. All Rights Reserved. +// Copyright 2019-2021 Alibaba Cloud. All rights reserved. +// +// SPDX-License-Identifier: Apache-2.0 + +use std::error; +use std::fs::File; +use std::io; +use std::os::unix::io::{AsRawFd, FromRawFd}; +use std::sync::{Arc, RwLock}; +use std::thread; + +use vhost::vhost_user::message::{ + VhostUserConfigFlags, VhostUserMemoryRegion, VhostUserProtocolFeatures, + VhostUserVirtioFeatures, VhostUserVringAddrFlags, VhostUserVringState, +}; +use vhost::vhost_user::{Error as VhostUserError, Result as VhostUserResult, SlaveFsCacheReq}; +use virtio_bindings::bindings::virtio_ring::VIRTIO_RING_F_EVENT_IDX; +use vm_memory::{FileOffset, GuestAddress, GuestMemoryAtomic, GuestMemoryMmap}; +use vmm_sys_util::eventfd::EventFd; + +use super::*; + +const MAX_MEM_SLOTS: u64 = 32; + +#[derive(Debug)] +/// Errors related to vhost-user handler. +pub enum VhostUserHandlerError { + /// Failed to create epoll file descriptor. + EpollCreateFd(io::Error), + /// Failed to spawn vring worker. + SpawnVringWorker(io::Error), + /// Could not find the mapping from memory regions. + MissingMemoryMapping, + /// Could not register exit event + RegisterExitEvent(io::Error), +} + +impl std::fmt::Display for VhostUserHandlerError { + fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { + match self { + VhostUserHandlerError::EpollCreateFd(e) => write!(f, "failed creating epoll fd: {}", e), + VhostUserHandlerError::SpawnVringWorker(e) => { + write!(f, "failed spawning the vring worker: {}", e) + } + VhostUserHandlerError::MissingMemoryMapping => write!(f, "Missing memory mapping"), + VhostUserHandlerError::RegisterExitEvent(e) => { + write!(f, "Failed to register exit event: {}", e) + } + } + } +} + +impl error::Error for VhostUserHandlerError {} + +/// Result of vhost-user handler operations. +type VhostUserHandlerResult = std::result::Result; + +struct AddrMapping { + vmm_addr: u64, + size: u64, + gpa_base: u64, +} + +pub struct VhostUserHandler { + backend: S, + workers: Vec>, + owned: bool, + features_acked: bool, + acked_features: u64, + acked_protocol_features: u64, + num_queues: usize, + max_queue_size: usize, + queues_per_thread: Vec, + mappings: Vec, + atomic_mem: GuestMemoryAtomic, + vrings: Vec>>, + worker_threads: Vec>>, +} + +impl VhostUserHandler { + pub fn new(backend: S) -> VhostUserHandlerResult { + let num_queues = backend.num_queues(); + let max_queue_size = backend.max_queue_size(); + let queues_per_thread = backend.queues_per_thread(); + + let atomic_mem = GuestMemoryAtomic::new(GuestMemoryMmap::new()); + + let mut vrings: Vec>> = Vec::new(); + for _ in 0..num_queues { + let vring = Arc::new(RwLock::new(Vring::new( + atomic_mem.clone(), + max_queue_size as u16, + ))); + vrings.push(vring); + } + + let mut workers = Vec::new(); + let mut worker_threads = Vec::new(); + for (thread_id, queues_mask) in queues_per_thread.iter().enumerate() { + // Create the epoll file descriptor + let epoll_fd = epoll::create(true).map_err(VhostUserHandlerError::EpollCreateFd)?; + // Use 'File' to enforce closing on 'epoll_fd' + let epoll_file = unsafe { File::from_raw_fd(epoll_fd) }; + + let vring_worker = Arc::new(VringWorker { epoll_file }); + let worker = vring_worker.clone(); + + let exit_event_id = + if let Some((exit_event_fd, exit_event_id)) = backend.exit_event(thread_id) { + worker + .register_listener( + exit_event_fd.as_raw_fd(), + epoll::Events::EPOLLIN, + u64::from(exit_event_id), + ) + .map_err(VhostUserHandlerError::RegisterExitEvent)?; + Some(exit_event_id) + } else { + None + }; + + let mut thread_vrings: Vec>> = Vec::new(); + for (index, vring) in vrings.iter().enumerate() { + if (queues_mask >> index) & 1u64 == 1u64 { + thread_vrings.push(vring.clone()); + } + } + + let vring_handler = VringEpollHandler { + backend: backend.clone(), + vrings: thread_vrings, + exit_event_id, + thread_id, + }; + + let worker_thread = thread::Builder::new() + .name("vring_worker".to_string()) + .spawn(move || vring_worker.run(vring_handler)) + .map_err(VhostUserHandlerError::SpawnVringWorker)?; + + workers.push(worker); + worker_threads.push(worker_thread); + } + + Ok(VhostUserHandler { + backend, + workers, + owned: false, + features_acked: false, + acked_features: 0, + acked_protocol_features: 0, + num_queues, + max_queue_size, + queues_per_thread, + mappings: Vec::new(), + atomic_mem, + vrings, + worker_threads, + }) + } + + pub fn get_vring_workers(&self) -> Vec> { + self.workers.clone() + } + + fn vmm_va_to_gpa(&self, vmm_va: u64) -> VhostUserHandlerResult { + for mapping in self.mappings.iter() { + if vmm_va >= mapping.vmm_addr && vmm_va < mapping.vmm_addr + mapping.size { + return Ok(vmm_va - mapping.vmm_addr + mapping.gpa_base); + } + } + + Err(VhostUserHandlerError::MissingMemoryMapping) + } +} + +impl VhostUserSlaveReqHandlerMut for VhostUserHandler { + fn set_owner(&mut self) -> VhostUserResult<()> { + if self.owned { + return Err(VhostUserError::InvalidOperation); + } + self.owned = true; + Ok(()) + } + + fn reset_owner(&mut self) -> VhostUserResult<()> { + self.owned = false; + self.features_acked = false; + self.acked_features = 0; + self.acked_protocol_features = 0; + Ok(()) + } + + fn get_features(&mut self) -> VhostUserResult { + Ok(self.backend.features()) + } + + fn set_features(&mut self, features: u64) -> VhostUserResult<()> { + if (features & !self.backend.features()) != 0 { + return Err(VhostUserError::InvalidParam); + } + + self.acked_features = features; + self.features_acked = true; + + // If VHOST_USER_F_PROTOCOL_FEATURES has not been negotiated, + // the ring is initialized in an enabled state. + // If VHOST_USER_F_PROTOCOL_FEATURES has been negotiated, + // the ring is initialized in a disabled state. Client must not + // pass data to/from the backend until ring is enabled by + // VHOST_USER_SET_VRING_ENABLE with parameter 1, or after it has + // been disabled by VHOST_USER_SET_VRING_ENABLE with parameter 0. + let vring_enabled = + self.acked_features & VhostUserVirtioFeatures::PROTOCOL_FEATURES.bits() == 0; + for vring in self.vrings.iter_mut() { + vring.write().unwrap().enabled = vring_enabled; + } + + self.backend.acked_features(self.acked_features); + + Ok(()) + } + + fn get_protocol_features(&mut self) -> VhostUserResult { + Ok(self.backend.protocol_features()) + } + + fn set_protocol_features(&mut self, features: u64) -> VhostUserResult<()> { + // Note: slave that reported VHOST_USER_F_PROTOCOL_FEATURES must + // support this message even before VHOST_USER_SET_FEATURES was + // called. + self.acked_protocol_features = features; + Ok(()) + } + + fn set_mem_table( + &mut self, + ctx: &[VhostUserMemoryRegion], + files: Vec, + ) -> VhostUserResult<()> { + // We need to create tuple of ranges from the list of VhostUserMemoryRegion + // that we get from the caller. + let mut regions: Vec<(GuestAddress, usize, Option)> = Vec::new(); + let mut mappings: Vec = Vec::new(); + + for (region, file) in ctx.iter().zip(files) { + let g_addr = GuestAddress(region.guest_phys_addr); + let len = region.memory_size as usize; + let f_off = FileOffset::new(file, region.mmap_offset); + + regions.push((g_addr, len, Some(f_off))); + mappings.push(AddrMapping { + vmm_addr: region.user_addr, + size: region.memory_size, + gpa_base: region.guest_phys_addr, + }); + } + + let mem = GuestMemoryMmap::from_ranges_with_files(regions).map_err(|e| { + VhostUserError::ReqHandlerError(io::Error::new(io::ErrorKind::Other, e)) + })?; + + // Updating the inner GuestMemory object here will cause all our vrings to + // see the new one the next time they call to `atomic_mem.memory()`. + self.atomic_mem.lock().unwrap().replace(mem); + + self.backend + .update_memory(self.atomic_mem.clone()) + .map_err(|e| { + VhostUserError::ReqHandlerError(io::Error::new(io::ErrorKind::Other, e)) + })?; + self.mappings = mappings; + + Ok(()) + } + + fn get_queue_num(&mut self) -> VhostUserResult { + Ok(self.num_queues as u64) + } + + fn set_vring_num(&mut self, index: u32, num: u32) -> VhostUserResult<()> { + if index as usize >= self.num_queues || num == 0 || num as usize > self.max_queue_size { + return Err(VhostUserError::InvalidParam); + } + self.vrings[index as usize].write().unwrap().queue.size = num as u16; + Ok(()) + } + + fn set_vring_addr( + &mut self, + index: u32, + _flags: VhostUserVringAddrFlags, + descriptor: u64, + used: u64, + available: u64, + _log: u64, + ) -> VhostUserResult<()> { + if index as usize >= self.num_queues { + return Err(VhostUserError::InvalidParam); + } + + if !self.mappings.is_empty() { + let desc_table = self.vmm_va_to_gpa(descriptor).map_err(|e| { + VhostUserError::ReqHandlerError(io::Error::new(io::ErrorKind::Other, e)) + })?; + let avail_ring = self.vmm_va_to_gpa(available).map_err(|e| { + VhostUserError::ReqHandlerError(io::Error::new(io::ErrorKind::Other, e)) + })?; + let used_ring = self.vmm_va_to_gpa(used).map_err(|e| { + VhostUserError::ReqHandlerError(io::Error::new(io::ErrorKind::Other, e)) + })?; + self.vrings[index as usize] + .write() + .unwrap() + .queue + .desc_table = GuestAddress(desc_table); + self.vrings[index as usize] + .write() + .unwrap() + .queue + .avail_ring = GuestAddress(avail_ring); + self.vrings[index as usize].write().unwrap().queue.used_ring = GuestAddress(used_ring); + Ok(()) + } else { + Err(VhostUserError::InvalidParam) + } + } + + fn set_vring_base(&mut self, index: u32, base: u32) -> VhostUserResult<()> { + self.vrings[index as usize] + .write() + .unwrap() + .queue + .set_next_avail(base as u16); + + let event_idx: bool = (self.acked_features & (1 << VIRTIO_RING_F_EVENT_IDX)) != 0; + self.vrings[index as usize] + .write() + .unwrap() + .mut_queue() + .set_event_idx(event_idx); + self.backend.set_event_idx(event_idx); + Ok(()) + } + + fn get_vring_base(&mut self, index: u32) -> VhostUserResult { + if index as usize >= self.num_queues { + return Err(VhostUserError::InvalidParam); + } + // Quote from vhost-user specification: + // Client must start ring upon receiving a kick (that is, detecting + // that file descriptor is readable) on the descriptor specified by + // VHOST_USER_SET_VRING_KICK, and stop ring upon receiving + // VHOST_USER_GET_VRING_BASE. + self.vrings[index as usize].write().unwrap().queue.ready = false; + if let Some(fd) = self.vrings[index as usize].read().unwrap().kick.as_ref() { + for (thread_index, queues_mask) in self.queues_per_thread.iter().enumerate() { + let shifted_queues_mask = queues_mask >> index; + if shifted_queues_mask & 1u64 == 1u64 { + let evt_idx = queues_mask.count_ones() - shifted_queues_mask.count_ones(); + self.workers[thread_index] + .unregister_listener( + fd.as_raw_fd(), + epoll::Events::EPOLLIN, + u64::from(evt_idx), + ) + .map_err(VhostUserError::ReqHandlerError)?; + break; + } + } + } + + let next_avail = self.vrings[index as usize] + .read() + .unwrap() + .queue + .next_avail(); + + Ok(VhostUserVringState::new(index, u32::from(next_avail))) + } + + fn set_vring_kick(&mut self, index: u8, file: Option) -> VhostUserResult<()> { + if index as usize >= self.num_queues { + return Err(VhostUserError::InvalidParam); + } + + // SAFETY: EventFd requires that it has sole ownership of its fd. So + // does File, so this is safe. + // Ideally, we'd have a generic way to refer to a uniquely-owned fd, + // such as that proposed by Rust RFC #3128. + self.vrings[index as usize].write().unwrap().kick = + file.map(|f| unsafe { EventFd::from_raw_fd(f.into_raw_fd()) }); + + // Quote from vhost-user specification: + // Client must start ring upon receiving a kick (that is, detecting + // that file descriptor is readable) on the descriptor specified by + // VHOST_USER_SET_VRING_KICK, and stop ring upon receiving + // VHOST_USER_GET_VRING_BASE. + self.vrings[index as usize].write().unwrap().queue.ready = true; + if let Some(fd) = self.vrings[index as usize].read().unwrap().kick.as_ref() { + for (thread_index, queues_mask) in self.queues_per_thread.iter().enumerate() { + let shifted_queues_mask = queues_mask >> index; + if shifted_queues_mask & 1u64 == 1u64 { + let evt_idx = queues_mask.count_ones() - shifted_queues_mask.count_ones(); + self.workers[thread_index] + .register_listener( + fd.as_raw_fd(), + epoll::Events::EPOLLIN, + u64::from(evt_idx), + ) + .map_err(VhostUserError::ReqHandlerError)?; + break; + } + } + } + + Ok(()) + } + + fn set_vring_call(&mut self, index: u8, file: Option) -> VhostUserResult<()> { + if index as usize >= self.num_queues { + return Err(VhostUserError::InvalidParam); + } + + // SAFETY: see comment in set_vring_kick() + self.vrings[index as usize].write().unwrap().call = + file.map(|f| unsafe { EventFd::from_raw_fd(f.into_raw_fd()) }); + + Ok(()) + } + + fn set_vring_err(&mut self, index: u8, file: Option) -> VhostUserResult<()> { + if index as usize >= self.num_queues { + return Err(VhostUserError::InvalidParam); + } + + // SAFETY: see comment in set_vring_kick() + self.vrings[index as usize].write().unwrap().err = + file.map(|f| unsafe { EventFd::from_raw_fd(f.into_raw_fd()) }); + + Ok(()) + } + + fn set_vring_enable(&mut self, index: u32, enable: bool) -> VhostUserResult<()> { + // This request should be handled only when VHOST_USER_F_PROTOCOL_FEATURES + // has been negotiated. + if self.acked_features & VhostUserVirtioFeatures::PROTOCOL_FEATURES.bits() == 0 { + return Err(VhostUserError::InvalidOperation); + } else if index as usize >= self.num_queues { + return Err(VhostUserError::InvalidParam); + } + + // Slave must not pass data to/from the backend until ring is + // enabled by VHOST_USER_SET_VRING_ENABLE with parameter 1, + // or after it has been disabled by VHOST_USER_SET_VRING_ENABLE + // with parameter 0. + self.vrings[index as usize].write().unwrap().enabled = enable; + + Ok(()) + } + + fn get_config( + &mut self, + offset: u32, + size: u32, + _flags: VhostUserConfigFlags, + ) -> VhostUserResult> { + Ok(self.backend.get_config(offset, size)) + } + + fn set_config( + &mut self, + offset: u32, + buf: &[u8], + _flags: VhostUserConfigFlags, + ) -> VhostUserResult<()> { + self.backend + .set_config(offset, buf) + .map_err(VhostUserError::ReqHandlerError) + } + + fn set_slave_req_fd(&mut self, vu_req: SlaveFsCacheReq) { + if self.acked_protocol_features & VhostUserProtocolFeatures::REPLY_ACK.bits() != 0 { + vu_req.set_reply_ack_flag(true); + } + + self.backend.set_slave_req_fd(vu_req); + } + + fn get_max_mem_slots(&mut self) -> VhostUserResult { + Ok(MAX_MEM_SLOTS) + } + + fn add_mem_region( + &mut self, + region: &VhostUserSingleMemoryRegion, + file: File, + ) -> VhostUserResult<()> { + let mmap_region = MmapRegion::from_file( + FileOffset::new(file, region.mmap_offset), + region.memory_size as usize, + ) + .map_err(|e| VhostUserError::ReqHandlerError(io::Error::new(io::ErrorKind::Other, e)))?; + let guest_region = Arc::new( + GuestRegionMmap::new(mmap_region, GuestAddress(region.guest_phys_addr)).map_err( + |e| VhostUserError::ReqHandlerError(io::Error::new(io::ErrorKind::Other, e)), + )?, + ); + + let mem = self + .atomic_mem + .memory() + .insert_region(guest_region) + .map_err(|e| { + VhostUserError::ReqHandlerError(io::Error::new(io::ErrorKind::Other, e)) + })?; + + self.atomic_mem.lock().unwrap().replace(mem); + + self.backend + .update_memory(self.atomic_mem.clone()) + .map_err(|e| { + VhostUserError::ReqHandlerError(io::Error::new(io::ErrorKind::Other, e)) + })?; + + self.mappings.push(AddrMapping { + vmm_addr: region.user_addr, + size: region.memory_size, + gpa_base: region.guest_phys_addr, + }); + + Ok(()) + } + + fn remove_mem_region(&mut self, region: &VhostUserSingleMemoryRegion) -> VhostUserResult<()> { + let (mem, _) = self + .atomic_mem + .memory() + .remove_region(GuestAddress(region.guest_phys_addr), region.memory_size) + .map_err(|e| { + VhostUserError::ReqHandlerError(io::Error::new(io::ErrorKind::Other, e)) + })?; + + self.atomic_mem.lock().unwrap().replace(mem); + + self.backend + .update_memory(self.atomic_mem.clone()) + .map_err(|e| { + VhostUserError::ReqHandlerError(io::Error::new(io::ErrorKind::Other, e)) + })?; + + self.mappings + .retain(|mapping| mapping.gpa_base != region.guest_phys_addr); + + Ok(()) + } + + fn get_inflight_fd( + &mut self, + _inflight: &vhost::vhost_user::message::VhostUserInflight, + ) -> VhostUserResult<(vhost::vhost_user::message::VhostUserInflight, File)> { + // Assume the backend hasn't negotiated the inflight feature; it + // wouldn't be correct for the backend to do so, as we don't (yet) + // provide a way for it to handle such requests. + Err(VhostUserError::InvalidOperation) + } + + fn set_inflight_fd( + &mut self, + _inflight: &vhost::vhost_user::message::VhostUserInflight, + _file: File, + ) -> VhostUserResult<()> { + Err(VhostUserError::InvalidOperation) + } +} + +impl Drop for VhostUserHandler { + fn drop(&mut self) { + for thread in self.worker_threads.drain(..) { + if let Err(e) = thread.join() { + error!("Error in vring worker: {:?}", e); + } + } + } +} diff --git a/src/lib.rs b/src/lib.rs index 81d23d5..c0c0a7a 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -7,37 +7,31 @@ #[macro_use] extern crate log; -use std::error; use std::fs::File; use std::io; -use std::os::unix::io::{AsRawFd, FromRawFd, RawFd}; +use std::os::unix::io::{AsRawFd, RawFd}; use std::os::unix::prelude::IntoRawFd; use std::result; use std::sync::{Arc, Mutex, RwLock}; use std::thread; -use vhost::vhost_user::message::{ - VhostUserConfigFlags, VhostUserMemoryRegion, VhostUserProtocolFeatures, - VhostUserSingleMemoryRegion, VhostUserVirtioFeatures, VhostUserVringAddrFlags, - VhostUserVringState, -}; +use vhost::vhost_user::message::VhostUserSingleMemoryRegion; use vhost::vhost_user::{ - Error as VhostUserError, Listener, Result as VhostUserResult, SlaveFsCacheReq, SlaveListener, - VhostUserSlaveReqHandlerMut, + Error as VhostUserError, Listener, SlaveListener, VhostUserSlaveReqHandlerMut, }; -use virtio_bindings::bindings::virtio_ring::VIRTIO_RING_F_EVENT_IDX; use virtio_queue::Queue; -use vm_memory::guest_memory::FileOffset; use vm_memory::{ - GuestAddress, GuestAddressSpace, GuestMemoryAtomic, GuestMemoryMmap, GuestRegionMmap, - MmapRegion, + GuestAddressSpace, GuestMemoryAtomic, GuestMemoryMmap, GuestRegionMmap, MmapRegion, }; use vmm_sys_util::eventfd::EventFd; +use self::handler::VhostUserHandler; + pub mod backend; pub use backend::{VhostUserBackend, VhostUserBackendMut}; -const MAX_MEM_SLOTS: u64 = 32; +mod handler; +pub use self::handler::VhostUserHandlerError; #[derive(Debug)] /// Errors related to vhost-user daemon. @@ -139,12 +133,6 @@ impl VhostUserDaemon { } } -struct AddrMapping { - vmm_addr: u64, - size: u64, - gpa_base: u64, -} - pub struct Vring { queue: Queue>, kick: Option, @@ -336,559 +324,3 @@ impl VringWorker { ) } } - -#[derive(Debug)] -/// Errors related to vhost-user handler. -pub enum VhostUserHandlerError { - /// Failed to create epoll file descriptor. - EpollCreateFd(io::Error), - /// Failed to spawn vring worker. - SpawnVringWorker(io::Error), - /// Could not find the mapping from memory regions. - MissingMemoryMapping, - /// Could not register exit event - RegisterExitEvent(io::Error), -} - -impl std::fmt::Display for VhostUserHandlerError { - fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { - match self { - VhostUserHandlerError::EpollCreateFd(e) => write!(f, "failed creating epoll fd: {}", e), - VhostUserHandlerError::SpawnVringWorker(e) => { - write!(f, "failed spawning the vring worker: {}", e) - } - VhostUserHandlerError::MissingMemoryMapping => write!(f, "Missing memory mapping"), - VhostUserHandlerError::RegisterExitEvent(e) => { - write!(f, "Failed to register exit event: {}", e) - } - } - } -} - -impl error::Error for VhostUserHandlerError {} - -/// Result of vhost-user handler operations. -type VhostUserHandlerResult = std::result::Result; - -struct VhostUserHandler { - backend: S, - workers: Vec>, - owned: bool, - features_acked: bool, - acked_features: u64, - acked_protocol_features: u64, - num_queues: usize, - max_queue_size: usize, - queues_per_thread: Vec, - mappings: Vec, - atomic_mem: GuestMemoryAtomic, - vrings: Vec>>, - worker_threads: Vec>>, -} - -impl VhostUserHandler { - fn new(backend: S) -> VhostUserHandlerResult { - let num_queues = backend.num_queues(); - let max_queue_size = backend.max_queue_size(); - let queues_per_thread = backend.queues_per_thread(); - - let atomic_mem = GuestMemoryAtomic::new(GuestMemoryMmap::new()); - - let mut vrings: Vec>> = Vec::new(); - for _ in 0..num_queues { - let vring = Arc::new(RwLock::new(Vring::new( - atomic_mem.clone(), - max_queue_size as u16, - ))); - vrings.push(vring); - } - - let mut workers = Vec::new(); - let mut worker_threads = Vec::new(); - for (thread_id, queues_mask) in queues_per_thread.iter().enumerate() { - // Create the epoll file descriptor - let epoll_fd = epoll::create(true).map_err(VhostUserHandlerError::EpollCreateFd)?; - // Use 'File' to enforce closing on 'epoll_fd' - let epoll_file = unsafe { File::from_raw_fd(epoll_fd) }; - - let vring_worker = Arc::new(VringWorker { epoll_file }); - let worker = vring_worker.clone(); - - let exit_event_id = - if let Some((exit_event_fd, exit_event_id)) = backend.exit_event(thread_id) { - worker - .register_listener( - exit_event_fd.as_raw_fd(), - epoll::Events::EPOLLIN, - u64::from(exit_event_id), - ) - .map_err(VhostUserHandlerError::RegisterExitEvent)?; - Some(exit_event_id) - } else { - None - }; - - let mut thread_vrings: Vec>> = Vec::new(); - for (index, vring) in vrings.iter().enumerate() { - if (queues_mask >> index) & 1u64 == 1u64 { - thread_vrings.push(vring.clone()); - } - } - - let vring_handler = VringEpollHandler { - backend: backend.clone(), - vrings: thread_vrings, - exit_event_id, - thread_id, - }; - - let worker_thread = thread::Builder::new() - .name("vring_worker".to_string()) - .spawn(move || vring_worker.run(vring_handler)) - .map_err(VhostUserHandlerError::SpawnVringWorker)?; - - workers.push(worker); - worker_threads.push(worker_thread); - } - - Ok(VhostUserHandler { - backend, - workers, - owned: false, - features_acked: false, - acked_features: 0, - acked_protocol_features: 0, - num_queues, - max_queue_size, - queues_per_thread, - mappings: Vec::new(), - atomic_mem, - vrings, - worker_threads, - }) - } - - fn get_vring_workers(&self) -> Vec> { - self.workers.clone() - } - - fn vmm_va_to_gpa(&self, vmm_va: u64) -> VhostUserHandlerResult { - for mapping in self.mappings.iter() { - if vmm_va >= mapping.vmm_addr && vmm_va < mapping.vmm_addr + mapping.size { - return Ok(vmm_va - mapping.vmm_addr + mapping.gpa_base); - } - } - - Err(VhostUserHandlerError::MissingMemoryMapping) - } -} - -impl VhostUserSlaveReqHandlerMut for VhostUserHandler { - fn set_owner(&mut self) -> VhostUserResult<()> { - if self.owned { - return Err(VhostUserError::InvalidOperation); - } - self.owned = true; - Ok(()) - } - - fn reset_owner(&mut self) -> VhostUserResult<()> { - self.owned = false; - self.features_acked = false; - self.acked_features = 0; - self.acked_protocol_features = 0; - Ok(()) - } - - fn get_features(&mut self) -> VhostUserResult { - Ok(self.backend.features()) - } - - fn set_features(&mut self, features: u64) -> VhostUserResult<()> { - if (features & !self.backend.features()) != 0 { - return Err(VhostUserError::InvalidParam); - } - - self.acked_features = features; - self.features_acked = true; - - // If VHOST_USER_F_PROTOCOL_FEATURES has not been negotiated, - // the ring is initialized in an enabled state. - // If VHOST_USER_F_PROTOCOL_FEATURES has been negotiated, - // the ring is initialized in a disabled state. Client must not - // pass data to/from the backend until ring is enabled by - // VHOST_USER_SET_VRING_ENABLE with parameter 1, or after it has - // been disabled by VHOST_USER_SET_VRING_ENABLE with parameter 0. - let vring_enabled = - self.acked_features & VhostUserVirtioFeatures::PROTOCOL_FEATURES.bits() == 0; - for vring in self.vrings.iter_mut() { - vring.write().unwrap().enabled = vring_enabled; - } - - self.backend.acked_features(self.acked_features); - - Ok(()) - } - - fn get_protocol_features(&mut self) -> VhostUserResult { - Ok(self.backend.protocol_features()) - } - - fn set_protocol_features(&mut self, features: u64) -> VhostUserResult<()> { - // Note: slave that reported VHOST_USER_F_PROTOCOL_FEATURES must - // support this message even before VHOST_USER_SET_FEATURES was - // called. - self.acked_protocol_features = features; - Ok(()) - } - - fn set_mem_table( - &mut self, - ctx: &[VhostUserMemoryRegion], - files: Vec, - ) -> VhostUserResult<()> { - // We need to create tuple of ranges from the list of VhostUserMemoryRegion - // that we get from the caller. - let mut regions: Vec<(GuestAddress, usize, Option)> = Vec::new(); - let mut mappings: Vec = Vec::new(); - - for (region, file) in ctx.iter().zip(files) { - let g_addr = GuestAddress(region.guest_phys_addr); - let len = region.memory_size as usize; - let f_off = FileOffset::new(file, region.mmap_offset); - - regions.push((g_addr, len, Some(f_off))); - mappings.push(AddrMapping { - vmm_addr: region.user_addr, - size: region.memory_size, - gpa_base: region.guest_phys_addr, - }); - } - - let mem = GuestMemoryMmap::from_ranges_with_files(regions).map_err(|e| { - VhostUserError::ReqHandlerError(io::Error::new(io::ErrorKind::Other, e)) - })?; - - // Updating the inner GuestMemory object here will cause all our vrings to - // see the new one the next time they call to `atomic_mem.memory()`. - self.atomic_mem.lock().unwrap().replace(mem); - - self.backend - .update_memory(self.atomic_mem.clone()) - .map_err(|e| { - VhostUserError::ReqHandlerError(io::Error::new(io::ErrorKind::Other, e)) - })?; - self.mappings = mappings; - - Ok(()) - } - - fn get_queue_num(&mut self) -> VhostUserResult { - Ok(self.num_queues as u64) - } - - fn set_vring_num(&mut self, index: u32, num: u32) -> VhostUserResult<()> { - if index as usize >= self.num_queues || num == 0 || num as usize > self.max_queue_size { - return Err(VhostUserError::InvalidParam); - } - self.vrings[index as usize].write().unwrap().queue.size = num as u16; - Ok(()) - } - - fn set_vring_addr( - &mut self, - index: u32, - _flags: VhostUserVringAddrFlags, - descriptor: u64, - used: u64, - available: u64, - _log: u64, - ) -> VhostUserResult<()> { - if index as usize >= self.num_queues { - return Err(VhostUserError::InvalidParam); - } - - if !self.mappings.is_empty() { - let desc_table = self.vmm_va_to_gpa(descriptor).map_err(|e| { - VhostUserError::ReqHandlerError(io::Error::new(io::ErrorKind::Other, e)) - })?; - let avail_ring = self.vmm_va_to_gpa(available).map_err(|e| { - VhostUserError::ReqHandlerError(io::Error::new(io::ErrorKind::Other, e)) - })?; - let used_ring = self.vmm_va_to_gpa(used).map_err(|e| { - VhostUserError::ReqHandlerError(io::Error::new(io::ErrorKind::Other, e)) - })?; - self.vrings[index as usize] - .write() - .unwrap() - .queue - .desc_table = GuestAddress(desc_table); - self.vrings[index as usize] - .write() - .unwrap() - .queue - .avail_ring = GuestAddress(avail_ring); - self.vrings[index as usize].write().unwrap().queue.used_ring = GuestAddress(used_ring); - Ok(()) - } else { - Err(VhostUserError::InvalidParam) - } - } - - fn set_vring_base(&mut self, index: u32, base: u32) -> VhostUserResult<()> { - self.vrings[index as usize] - .write() - .unwrap() - .queue - .set_next_avail(base as u16); - - let event_idx: bool = (self.acked_features & (1 << VIRTIO_RING_F_EVENT_IDX)) != 0; - self.vrings[index as usize] - .write() - .unwrap() - .mut_queue() - .set_event_idx(event_idx); - self.backend.set_event_idx(event_idx); - Ok(()) - } - - fn get_vring_base(&mut self, index: u32) -> VhostUserResult { - if index as usize >= self.num_queues { - return Err(VhostUserError::InvalidParam); - } - // Quote from vhost-user specification: - // Client must start ring upon receiving a kick (that is, detecting - // that file descriptor is readable) on the descriptor specified by - // VHOST_USER_SET_VRING_KICK, and stop ring upon receiving - // VHOST_USER_GET_VRING_BASE. - self.vrings[index as usize].write().unwrap().queue.ready = false; - if let Some(fd) = self.vrings[index as usize].read().unwrap().kick.as_ref() { - for (thread_index, queues_mask) in self.queues_per_thread.iter().enumerate() { - let shifted_queues_mask = queues_mask >> index; - if shifted_queues_mask & 1u64 == 1u64 { - let evt_idx = queues_mask.count_ones() - shifted_queues_mask.count_ones(); - self.workers[thread_index] - .unregister_listener( - fd.as_raw_fd(), - epoll::Events::EPOLLIN, - u64::from(evt_idx), - ) - .map_err(VhostUserError::ReqHandlerError)?; - break; - } - } - } - - let next_avail = self.vrings[index as usize] - .read() - .unwrap() - .queue - .next_avail(); - - Ok(VhostUserVringState::new(index, u32::from(next_avail))) - } - - fn set_vring_kick(&mut self, index: u8, file: Option) -> VhostUserResult<()> { - if index as usize >= self.num_queues { - return Err(VhostUserError::InvalidParam); - } - - // SAFETY: EventFd requires that it has sole ownership of its fd. So - // does File, so this is safe. - // Ideally, we'd have a generic way to refer to a uniquely-owned fd, - // such as that proposed by Rust RFC #3128. - self.vrings[index as usize].write().unwrap().kick = - file.map(|f| unsafe { EventFd::from_raw_fd(f.into_raw_fd()) }); - - // Quote from vhost-user specification: - // Client must start ring upon receiving a kick (that is, detecting - // that file descriptor is readable) on the descriptor specified by - // VHOST_USER_SET_VRING_KICK, and stop ring upon receiving - // VHOST_USER_GET_VRING_BASE. - self.vrings[index as usize].write().unwrap().queue.ready = true; - if let Some(fd) = self.vrings[index as usize].read().unwrap().kick.as_ref() { - for (thread_index, queues_mask) in self.queues_per_thread.iter().enumerate() { - let shifted_queues_mask = queues_mask >> index; - if shifted_queues_mask & 1u64 == 1u64 { - let evt_idx = queues_mask.count_ones() - shifted_queues_mask.count_ones(); - self.workers[thread_index] - .register_listener( - fd.as_raw_fd(), - epoll::Events::EPOLLIN, - u64::from(evt_idx), - ) - .map_err(VhostUserError::ReqHandlerError)?; - break; - } - } - } - - Ok(()) - } - - fn set_vring_call(&mut self, index: u8, file: Option) -> VhostUserResult<()> { - if index as usize >= self.num_queues { - return Err(VhostUserError::InvalidParam); - } - - // SAFETY: see comment in set_vring_kick() - self.vrings[index as usize].write().unwrap().call = - file.map(|f| unsafe { EventFd::from_raw_fd(f.into_raw_fd()) }); - - Ok(()) - } - - fn set_vring_err(&mut self, index: u8, file: Option) -> VhostUserResult<()> { - if index as usize >= self.num_queues { - return Err(VhostUserError::InvalidParam); - } - - // SAFETY: see comment in set_vring_kick() - self.vrings[index as usize].write().unwrap().err = - file.map(|f| unsafe { EventFd::from_raw_fd(f.into_raw_fd()) }); - - Ok(()) - } - - fn set_vring_enable(&mut self, index: u32, enable: bool) -> VhostUserResult<()> { - // This request should be handled only when VHOST_USER_F_PROTOCOL_FEATURES - // has been negotiated. - if self.acked_features & VhostUserVirtioFeatures::PROTOCOL_FEATURES.bits() == 0 { - return Err(VhostUserError::InvalidOperation); - } else if index as usize >= self.num_queues { - return Err(VhostUserError::InvalidParam); - } - - // Slave must not pass data to/from the backend until ring is - // enabled by VHOST_USER_SET_VRING_ENABLE with parameter 1, - // or after it has been disabled by VHOST_USER_SET_VRING_ENABLE - // with parameter 0. - self.vrings[index as usize].write().unwrap().enabled = enable; - - Ok(()) - } - - fn get_config( - &mut self, - offset: u32, - size: u32, - _flags: VhostUserConfigFlags, - ) -> VhostUserResult> { - Ok(self.backend.get_config(offset, size)) - } - - fn set_config( - &mut self, - offset: u32, - buf: &[u8], - _flags: VhostUserConfigFlags, - ) -> VhostUserResult<()> { - self.backend - .set_config(offset, buf) - .map_err(VhostUserError::ReqHandlerError) - } - - fn set_slave_req_fd(&mut self, vu_req: SlaveFsCacheReq) { - if self.acked_protocol_features & VhostUserProtocolFeatures::REPLY_ACK.bits() != 0 { - vu_req.set_reply_ack_flag(true); - } - - self.backend.set_slave_req_fd(vu_req); - } - - fn get_max_mem_slots(&mut self) -> VhostUserResult { - Ok(MAX_MEM_SLOTS) - } - - fn add_mem_region( - &mut self, - region: &VhostUserSingleMemoryRegion, - file: File, - ) -> VhostUserResult<()> { - let mmap_region = MmapRegion::from_file( - FileOffset::new(file, region.mmap_offset), - region.memory_size as usize, - ) - .map_err(|e| VhostUserError::ReqHandlerError(io::Error::new(io::ErrorKind::Other, e)))?; - let guest_region = Arc::new( - GuestRegionMmap::new(mmap_region, GuestAddress(region.guest_phys_addr)).map_err( - |e| VhostUserError::ReqHandlerError(io::Error::new(io::ErrorKind::Other, e)), - )?, - ); - - let mem = self - .atomic_mem - .memory() - .insert_region(guest_region) - .map_err(|e| { - VhostUserError::ReqHandlerError(io::Error::new(io::ErrorKind::Other, e)) - })?; - - self.atomic_mem.lock().unwrap().replace(mem); - - self.backend - .update_memory(self.atomic_mem.clone()) - .map_err(|e| { - VhostUserError::ReqHandlerError(io::Error::new(io::ErrorKind::Other, e)) - })?; - - self.mappings.push(AddrMapping { - vmm_addr: region.user_addr, - size: region.memory_size, - gpa_base: region.guest_phys_addr, - }); - - Ok(()) - } - - fn remove_mem_region(&mut self, region: &VhostUserSingleMemoryRegion) -> VhostUserResult<()> { - let (mem, _) = self - .atomic_mem - .memory() - .remove_region(GuestAddress(region.guest_phys_addr), region.memory_size) - .map_err(|e| { - VhostUserError::ReqHandlerError(io::Error::new(io::ErrorKind::Other, e)) - })?; - - self.atomic_mem.lock().unwrap().replace(mem); - - self.backend - .update_memory(self.atomic_mem.clone()) - .map_err(|e| { - VhostUserError::ReqHandlerError(io::Error::new(io::ErrorKind::Other, e)) - })?; - - self.mappings - .retain(|mapping| mapping.gpa_base != region.guest_phys_addr); - - Ok(()) - } - - fn get_inflight_fd( - &mut self, - _inflight: &vhost::vhost_user::message::VhostUserInflight, - ) -> VhostUserResult<(vhost::vhost_user::message::VhostUserInflight, File)> { - // Assume the backend hasn't negotiated the inflight feature; it - // wouldn't be correct for the backend to do so, as we don't (yet) - // provide a way for it to handle such requests. - Err(VhostUserError::InvalidOperation) - } - - fn set_inflight_fd( - &mut self, - _inflight: &vhost::vhost_user::message::VhostUserInflight, - _file: File, - ) -> VhostUserResult<()> { - Err(VhostUserError::InvalidOperation) - } -} - -impl Drop for VhostUserHandler { - fn drop(&mut self) { - for thread in self.worker_threads.drain(..) { - if let Err(e) = thread.join() { - error!("Error in vring worker: {:?}", e); - } - } - } -} From bf86c747a770d1d7506d36b8ed3c12435d8aed14 Mon Sep 17 00:00:00 2001 From: Liu Jiang Date: Fri, 19 Feb 2021 14:46:37 +0800 Subject: [PATCH 070/139] Refactor VringWorker as VringEpollHandler Refactor VringWorker as VringEpollHandler and move to event_loop.rs. Signed-off-by: Liu Jiang --- src/event_loop.rs | 208 ++++++++++++++++++++++++++++++++++++++++++++++ src/handler.rs | 73 +++++----------- src/lib.rs | 178 ++------------------------------------- 3 files changed, 240 insertions(+), 219 deletions(-) create mode 100644 src/event_loop.rs diff --git a/src/event_loop.rs b/src/event_loop.rs new file mode 100644 index 0000000..e1e3791 --- /dev/null +++ b/src/event_loop.rs @@ -0,0 +1,208 @@ +// Copyright 2019 Intel Corporation. All Rights Reserved. +// Copyright 2019-2021 Alibaba Cloud. All rights reserved. +// +// SPDX-License-Identifier: Apache-2.0 + +use std::fmt::{Display, Formatter}; +use std::fs::File; +use std::io; +use std::os::unix::io::{AsRawFd, FromRawFd, RawFd}; +use std::result; +use std::sync::{Arc, RwLock}; + +use super::{VhostUserBackend, Vring}; + +/// Errors related to vring epoll event handling. +#[derive(Debug)] +pub enum VringEpollError { + /// Failed to create epoll file descriptor. + EpollCreateFd(io::Error), + /// Failed while waiting for events. + EpollWait(io::Error), + /// Could not register exit event + RegisterExitEvent(io::Error), + /// Failed to read the event from kick EventFd. + HandleEventReadKick(io::Error), + /// Failed to handle the event from the backend. + HandleEventBackendHandling(io::Error), +} + +impl Display for VringEpollError { + fn fmt(&self, f: &mut Formatter) -> std::fmt::Result { + match self { + VringEpollError::EpollCreateFd(e) => write!(f, "cannot create epoll fd: {}", e), + VringEpollError::EpollWait(e) => write!(f, "failed to wait for epoll event: {}", e), + VringEpollError::RegisterExitEvent(e) => write!(f, "cannot register exit event: {}", e), + VringEpollError::HandleEventReadKick(e) => { + write!(f, "cannot read vring kick event: {}", e) + } + VringEpollError::HandleEventBackendHandling(e) => { + write!(f, "failed to handle epoll event: {}", e) + } + } + } +} + +impl std::error::Error for VringEpollError {} + +/// Result of vring epoll operations. +pub type VringEpollResult = std::result::Result; + +/// Epoll event handler to manage and process epoll events for registered file descriptor. +/// +/// The `VringEpollHandler` structure provides interfaces to: +/// - add file descriptors to be monitored by the epoll fd +/// - remove registered file descriptors from the epoll fd +/// - run the event loop to handle pending events on the epoll fd +pub struct VringEpollHandler { + epoll_file: File, + backend: S, + vrings: Vec>>, + thread_id: usize, + exit_event_id: Option, +} + +impl VringEpollHandler { + /// Create a `VringEpollHandler` instance. + pub(crate) fn new( + backend: S, + vrings: Vec>>, + thread_id: usize, + ) -> VringEpollResult { + let epoll_fd = epoll::create(true).map_err(VringEpollError::EpollCreateFd)?; + let epoll_file = unsafe { File::from_raw_fd(epoll_fd) }; + let (exit_event_fd, exit_event_id) = match backend.exit_event(thread_id) { + Some((exit_event_fd, exit_event_id)) => { + (exit_event_fd.as_raw_fd(), Some(exit_event_id)) + } + None => (-1, None), + }; + let handler = VringEpollHandler { + epoll_file, + backend, + vrings, + thread_id, + exit_event_id, + }; + + if let Some(exit_event_id) = exit_event_id { + epoll::ctl( + handler.epoll_file.as_raw_fd(), + epoll::ControlOptions::EPOLL_CTL_ADD, + exit_event_fd, + epoll::Event::new(epoll::Events::EPOLLIN, u64::from(exit_event_id)), + ) + .map_err(VringEpollError::RegisterExitEvent)?; + } + + Ok(handler) + } + + /// Register an event into the epoll fd. + /// + /// When this event is later triggered, the backend implementation of `handle_event` will be + /// called. + pub fn register_listener( + &self, + fd: RawFd, + ev_type: epoll::Events, + data: u64, + ) -> result::Result<(), io::Error> { + epoll::ctl( + self.epoll_file.as_raw_fd(), + epoll::ControlOptions::EPOLL_CTL_ADD, + fd, + epoll::Event::new(ev_type, data), + ) + } + + /// Unregister an event from the epoll fd. + /// + /// If the event is triggered after this function has been called, the event will be silently + /// dropped. + pub fn unregister_listener( + &self, + fd: RawFd, + ev_type: epoll::Events, + data: u64, + ) -> result::Result<(), io::Error> { + epoll::ctl( + self.epoll_file.as_raw_fd(), + epoll::ControlOptions::EPOLL_CTL_DEL, + fd, + epoll::Event::new(ev_type, data), + ) + } + + /// Run the event poll loop to handle all pending events on registered fds. + /// + /// The event loop will be terminated once an event is received from the `exit event fd` + /// associated with the backend. + pub(crate) fn run(&self) -> VringEpollResult<()> { + const EPOLL_EVENTS_LEN: usize = 100; + let mut events = vec![epoll::Event::new(epoll::Events::empty(), 0); EPOLL_EVENTS_LEN]; + + 'epoll: loop { + let num_events = match epoll::wait(self.epoll_file.as_raw_fd(), -1, &mut events[..]) { + Ok(res) => res, + Err(e) => { + if e.kind() == io::ErrorKind::Interrupted { + // It's well defined from the epoll_wait() syscall + // documentation that the epoll loop can be interrupted + // before any of the requested events occurred or the + // timeout expired. In both those cases, epoll_wait() + // returns an error of type EINTR, but this should not + // be considered as a regular error. Instead it is more + // appropriate to retry, by calling into epoll_wait(). + continue; + } + return Err(VringEpollError::EpollWait(e)); + } + }; + + for event in events.iter().take(num_events) { + let evset = match epoll::Events::from_bits(event.events) { + Some(evset) => evset, + None => { + let evbits = event.events; + println!("epoll: ignoring unknown event set: 0x{:x}", evbits); + continue; + } + }; + + let ev_type = event.data as u16; + + // handle_event() returns true if an event is received from the exit event fd. + if self.handle_event(ev_type, evset)? { + break 'epoll; + } + } + } + + Ok(()) + } + + fn handle_event(&self, device_event: u16, evset: epoll::Events) -> VringEpollResult { + if self.exit_event_id == Some(device_event) { + return Ok(true); + } + + let num_queues = self.vrings.len(); + if (device_event as usize) < num_queues { + let vring = &self.vrings[device_event as usize].read().unwrap(); + if let Some(kick) = &vring.kick { + kick.read().map_err(VringEpollError::HandleEventReadKick)?; + } + + // If the vring is not enabled, it should not be processed. + // The event is only read to be discarded. + if !vring.enabled { + return Ok(false); + } + } + + self.backend + .handle_event(device_event, evset, &self.vrings, self.thread_id) + .map_err(VringEpollError::HandleEventBackendHandling) + } +} diff --git a/src/handler.rs b/src/handler.rs index 028e699..372ffb1 100644 --- a/src/handler.rs +++ b/src/handler.rs @@ -6,7 +6,7 @@ use std::error; use std::fs::File; use std::io; -use std::os::unix::io::{AsRawFd, FromRawFd}; +use std::os::unix::io::{AsRawFd, FromRawFd, IntoRawFd}; use std::sync::{Arc, RwLock}; use std::thread; @@ -26,27 +26,24 @@ const MAX_MEM_SLOTS: u64 = 32; #[derive(Debug)] /// Errors related to vhost-user handler. pub enum VhostUserHandlerError { - /// Failed to create epoll file descriptor. - EpollCreateFd(io::Error), + /// Failed to create vring worker. + CreateEpollHandler(VringEpollError), /// Failed to spawn vring worker. SpawnVringWorker(io::Error), /// Could not find the mapping from memory regions. MissingMemoryMapping, - /// Could not register exit event - RegisterExitEvent(io::Error), } impl std::fmt::Display for VhostUserHandlerError { fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { match self { - VhostUserHandlerError::EpollCreateFd(e) => write!(f, "failed creating epoll fd: {}", e), + VhostUserHandlerError::CreateEpollHandler(e) => { + write!(f, "failed to create vring epoll handler: {}", e) + } VhostUserHandlerError::SpawnVringWorker(e) => { write!(f, "failed spawning the vring worker: {}", e) } VhostUserHandlerError::MissingMemoryMapping => write!(f, "Missing memory mapping"), - VhostUserHandlerError::RegisterExitEvent(e) => { - write!(f, "Failed to register exit event: {}", e) - } } } } @@ -54,7 +51,7 @@ impl std::fmt::Display for VhostUserHandlerError { impl error::Error for VhostUserHandlerError {} /// Result of vhost-user handler operations. -type VhostUserHandlerResult = std::result::Result; +pub type VhostUserHandlerResult = std::result::Result; struct AddrMapping { vmm_addr: u64, @@ -64,7 +61,7 @@ struct AddrMapping { pub struct VhostUserHandler { backend: S, - workers: Vec>, + handlers: Vec>>, owned: bool, features_acked: bool, acked_features: u64, @@ -75,11 +72,11 @@ pub struct VhostUserHandler { mappings: Vec, atomic_mem: GuestMemoryAtomic, vrings: Vec>>, - worker_threads: Vec>>, + worker_threads: Vec>>, } impl VhostUserHandler { - pub fn new(backend: S) -> VhostUserHandlerResult { + pub(crate) fn new(backend: S) -> VhostUserHandlerResult { let num_queues = backend.num_queues(); let max_queue_size = backend.max_queue_size(); let queues_per_thread = backend.queues_per_thread(); @@ -95,31 +92,9 @@ impl VhostUserHandler { vrings.push(vring); } - let mut workers = Vec::new(); + let mut handlers = Vec::new(); let mut worker_threads = Vec::new(); for (thread_id, queues_mask) in queues_per_thread.iter().enumerate() { - // Create the epoll file descriptor - let epoll_fd = epoll::create(true).map_err(VhostUserHandlerError::EpollCreateFd)?; - // Use 'File' to enforce closing on 'epoll_fd' - let epoll_file = unsafe { File::from_raw_fd(epoll_fd) }; - - let vring_worker = Arc::new(VringWorker { epoll_file }); - let worker = vring_worker.clone(); - - let exit_event_id = - if let Some((exit_event_fd, exit_event_id)) = backend.exit_event(thread_id) { - worker - .register_listener( - exit_event_fd.as_raw_fd(), - epoll::Events::EPOLLIN, - u64::from(exit_event_id), - ) - .map_err(VhostUserHandlerError::RegisterExitEvent)?; - Some(exit_event_id) - } else { - None - }; - let mut thread_vrings: Vec>> = Vec::new(); for (index, vring) in vrings.iter().enumerate() { if (queues_mask >> index) & 1u64 == 1u64 { @@ -127,25 +102,23 @@ impl VhostUserHandler { } } - let vring_handler = VringEpollHandler { - backend: backend.clone(), - vrings: thread_vrings, - exit_event_id, - thread_id, - }; - + let handler = Arc::new( + VringEpollHandler::new(backend.clone(), thread_vrings, thread_id) + .map_err(VhostUserHandlerError::CreateEpollHandler)?, + ); + let handler2 = handler.clone(); let worker_thread = thread::Builder::new() .name("vring_worker".to_string()) - .spawn(move || vring_worker.run(vring_handler)) + .spawn(move || handler2.run()) .map_err(VhostUserHandlerError::SpawnVringWorker)?; - workers.push(worker); + handlers.push(handler); worker_threads.push(worker_thread); } Ok(VhostUserHandler { backend, - workers, + handlers, owned: false, features_acked: false, acked_features: 0, @@ -160,8 +133,8 @@ impl VhostUserHandler { }) } - pub fn get_vring_workers(&self) -> Vec> { - self.workers.clone() + pub(crate) fn get_epoll_handlers(&self) -> Vec>> { + self.handlers.clone() } fn vmm_va_to_gpa(&self, vmm_va: u64) -> VhostUserHandlerResult { @@ -359,7 +332,7 @@ impl VhostUserSlaveReqHandlerMut for VhostUserHandl let shifted_queues_mask = queues_mask >> index; if shifted_queues_mask & 1u64 == 1u64 { let evt_idx = queues_mask.count_ones() - shifted_queues_mask.count_ones(); - self.workers[thread_index] + self.handlers[thread_index] .unregister_listener( fd.as_raw_fd(), epoll::Events::EPOLLIN, @@ -403,7 +376,7 @@ impl VhostUserSlaveReqHandlerMut for VhostUserHandl let shifted_queues_mask = queues_mask >> index; if shifted_queues_mask & 1u64 == 1u64 { let evt_idx = queues_mask.count_ones() - shifted_queues_mask.count_ones(); - self.workers[thread_index] + self.handlers[thread_index] .register_listener( fd.as_raw_fd(), epoll::Events::EPOLLIN, diff --git a/src/lib.rs b/src/lib.rs index c0c0a7a..2add569 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -7,12 +7,9 @@ #[macro_use] extern crate log; -use std::fs::File; use std::io; -use std::os::unix::io::{AsRawFd, RawFd}; -use std::os::unix::prelude::IntoRawFd; use std::result; -use std::sync::{Arc, Mutex, RwLock}; +use std::sync::{Arc, Mutex}; use std::thread; use vhost::vhost_user::message::VhostUserSingleMemoryRegion; @@ -27,8 +24,11 @@ use vmm_sys_util::eventfd::EventFd; use self::handler::VhostUserHandler; -pub mod backend; -pub use backend::{VhostUserBackend, VhostUserBackendMut}; +mod backend; +pub use self::backend::{VhostUserBackend, VhostUserBackendMut}; + +mod event_loop; +pub use self::event_loop::{VringEpollError, VringEpollHandler, VringEpollResult}; mod handler; pub use self::handler::VhostUserHandlerError; @@ -49,7 +49,7 @@ pub enum Error { /// Failed handling a vhost-user request. HandleRequest(VhostUserError), /// Failed to process queue. - ProcessQueue(VringEpollHandlerError), + ProcessQueue(VringEpollError), /// Failed to register listener. RegisterListener(io::Error), /// Failed to unregister listener. @@ -128,8 +128,8 @@ impl VhostUserDaemon { /// Retrieve the vring worker. This is necessary to perform further /// actions like registering and unregistering some extra event file /// descriptors. - pub fn get_vring_workers(&self) -> Vec> { - self.handler.lock().unwrap().get_vring_workers() + pub fn get_epoll_handlers(&self) -> Vec>> { + self.handler.lock().unwrap().get_epoll_handlers() } } @@ -164,163 +164,3 @@ impl Vring { } } } - -#[derive(Debug)] -/// Errors related to vring epoll handler. -pub enum VringEpollHandlerError { - /// Failed to process the queue from the backend. - ProcessQueueBackendProcessing(io::Error), - /// Failed to signal used queue. - SignalUsedQueue(io::Error), - /// Failed to read the event from kick EventFd. - HandleEventReadKick(io::Error), - /// Failed to handle the event from the backend. - HandleEventBackendHandling(io::Error), -} - -/// Result of vring epoll handler operations. -type VringEpollHandlerResult = std::result::Result; - -struct VringEpollHandler { - backend: S, - vrings: Vec>>, - exit_event_id: Option, - thread_id: usize, -} - -impl VringEpollHandler { - fn handle_event( - &self, - device_event: u16, - evset: epoll::Events, - ) -> VringEpollHandlerResult { - if self.exit_event_id == Some(device_event) { - return Ok(true); - } - - let num_queues = self.vrings.len(); - if (device_event as usize) < num_queues { - if let Some(kick) = &self.vrings[device_event as usize].read().unwrap().kick { - kick.read() - .map_err(VringEpollHandlerError::HandleEventReadKick)?; - } - - // If the vring is not enabled, it should not be processed. - // The event is only read to be discarded. - if !self.vrings[device_event as usize].read().unwrap().enabled { - return Ok(false); - } - } - - self.backend - .handle_event(device_event, evset, &self.vrings, self.thread_id) - .map_err(VringEpollHandlerError::HandleEventBackendHandling) - } -} - -#[derive(Debug)] -/// Errors related to vring worker. -enum VringWorkerError { - /// Failed while waiting for events. - EpollWait(io::Error), - /// Failed to handle the event. - HandleEvent(VringEpollHandlerError), -} - -/// Result of vring worker operations. -type VringWorkerResult = std::result::Result; - -pub struct VringWorker { - epoll_file: File, -} - -impl AsRawFd for VringWorker { - fn as_raw_fd(&self) -> RawFd { - self.epoll_file.as_raw_fd() - } -} - -impl VringWorker { - fn run(&self, handler: VringEpollHandler) -> VringWorkerResult<()> { - const EPOLL_EVENTS_LEN: usize = 100; - let mut events = vec![epoll::Event::new(epoll::Events::empty(), 0); EPOLL_EVENTS_LEN]; - - 'epoll: loop { - let num_events = match epoll::wait(self.epoll_file.as_raw_fd(), -1, &mut events[..]) { - Ok(res) => res, - Err(e) => { - if e.kind() == io::ErrorKind::Interrupted { - // It's well defined from the epoll_wait() syscall - // documentation that the epoll loop can be interrupted - // before any of the requested events occurred or the - // timeout expired. In both those cases, epoll_wait() - // returns an error of type EINTR, but this should not - // be considered as a regular error. Instead it is more - // appropriate to retry, by calling into epoll_wait(). - continue; - } - return Err(VringWorkerError::EpollWait(e)); - } - }; - - for event in events.iter().take(num_events) { - let evset = match epoll::Events::from_bits(event.events) { - Some(evset) => evset, - None => { - let evbits = event.events; - println!("epoll: ignoring unknown event set: 0x{:x}", evbits); - continue; - } - }; - - let ev_type = event.data as u16; - - if handler - .handle_event(ev_type, evset) - .map_err(VringWorkerError::HandleEvent)? - { - break 'epoll; - } - } - } - - Ok(()) - } - - /// Register a custom event only meaningful to the caller. When this event - /// is later triggered, and because only the caller knows what to do about - /// it, the backend implementation of `handle_event` will be called. - /// This lets entire control to the caller about what needs to be done for - /// this special event, without forcing it to run its own dedicated epoll - /// loop for it. - pub fn register_listener( - &self, - fd: RawFd, - ev_type: epoll::Events, - data: u64, - ) -> result::Result<(), io::Error> { - epoll::ctl( - self.epoll_file.as_raw_fd(), - epoll::ControlOptions::EPOLL_CTL_ADD, - fd, - epoll::Event::new(ev_type, data), - ) - } - - /// Unregister a custom event. If the custom event is triggered after this - /// function has been called, nothing will happen as it will be removed - /// from the list of file descriptors the epoll loop is listening to. - pub fn unregister_listener( - &self, - fd: RawFd, - ev_type: epoll::Events, - data: u64, - ) -> result::Result<(), io::Error> { - epoll::ctl( - self.epoll_file.as_raw_fd(), - epoll::ControlOptions::EPOLL_CTL_DEL, - fd, - epoll::Event::new(ev_type, data), - ) - } -} From 27e29d0c3632beb2ef1361e4a2dc2aae8bf5f266 Mon Sep 17 00:00:00 2001 From: Liu Jiang Date: Mon, 9 Aug 2021 13:00:25 +0800 Subject: [PATCH 071/139] Refine documentation and remove unused code Refine documentation and remove unused code. Signed-off-by: Liu Jiang --- src/lib.rs | 44 +++++++++++++++++++++----------------------- 1 file changed, 21 insertions(+), 23 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index 2add569..7c962a2 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -4,6 +4,8 @@ // Copyright 2019 Alibaba Cloud Computing. All rights reserved. // SPDX-License-Identifier: Apache-2.0 +//! A simple framework to run a vhost-user backend service. + #[macro_use] extern crate log; @@ -48,19 +50,15 @@ pub enum Error { WaitDaemon(std::boxed::Box), /// Failed handling a vhost-user request. HandleRequest(VhostUserError), - /// Failed to process queue. - ProcessQueue(VringEpollError), - /// Failed to register listener. - RegisterListener(io::Error), - /// Failed to unregister listener. - UnregisterListener(io::Error), } /// Result of vhost-user daemon operations. pub type Result = result::Result; -/// This structure is the public API the backend is allowed to interact with -/// in order to run a fully functional vhost-user daemon. +/// Implement a simple framework to run a vhost user service daemon. +/// +/// This structure is the public API the backend is allowed to interact with in order to run +/// a fully functional vhost-user daemon. pub struct VhostUserDaemon { name: String, handler: Arc>>, @@ -68,12 +66,11 @@ pub struct VhostUserDaemon { } impl VhostUserDaemon { - /// Create the daemon instance, providing the backend implementation of - /// VhostUserBackend. - /// Under the hood, this will start a dedicated thread responsible for - /// listening onto registered event. Those events can be vring events or - /// custom events from the backend, but they get to be registered later - /// during the sequence. + /// Create the daemon instance, providing the backend implementation of `VhostUserBackend`. + /// + /// Under the hood, this will start a dedicated thread responsible for listening onto + /// registered event. Those events can be vring events or custom events from the backend, + /// but they get to be registered later during the sequence. pub fn new(name: String, backend: S) -> Result { let handler = Arc::new(Mutex::new( VhostUserHandler::new(backend).map_err(Error::NewVhostUserHandler)?, @@ -86,10 +83,11 @@ impl VhostUserDaemon { }) } - /// Connect to the vhost-user socket and run a dedicated thread handling - /// all requests coming through this socket. This runs in an infinite loop - /// that should be terminating once the other end of the socket (the VMM) - /// disconnects. + /// Connect to the vhost-user socket and run a dedicated thread handling all requests coming + /// through this socket. + /// + /// This runs in an infinite loop that should be terminating once the other end of the socket + /// (the VMM) disconnects. pub fn start(&mut self, listener: Listener) -> Result<()> { let mut slave_listener = SlaveListener::new(listener, self.handler.clone()) .map_err(Error::CreateSlaveListener)?; @@ -111,8 +109,7 @@ impl VhostUserDaemon { Ok(()) } - /// Wait for the thread handling the vhost-user socket connection to - /// terminate. + /// Wait for the thread handling the vhost-user socket connection to terminate. pub fn wait(&mut self) -> Result<()> { if let Some(handle) = self.main_thread.take() { match handle.join().map_err(Error::WaitDaemon)? { @@ -125,9 +122,10 @@ impl VhostUserDaemon { } } - /// Retrieve the vring worker. This is necessary to perform further - /// actions like registering and unregistering some extra event file - /// descriptors. + /// Retrieve the vring epoll handler. + /// + /// This is necessary to perform further actions like registering and unregistering some extra + /// event file descriptors. pub fn get_epoll_handlers(&self) -> Vec>> { self.handler.lock().unwrap().get_epoll_handlers() } From 18db4634f611c3a2690b219a05f7533ded57ca32 Mon Sep 17 00:00:00 2001 From: Liu Jiang Date: Mon, 9 Aug 2021 13:47:20 +0800 Subject: [PATCH 072/139] vring: formalize Vring interfaces Move struct Vring to dedicated file vring.rs and formalize interfaces to access `Vring` objects. Signed-off-by: Liu Jiang --- src/backend.rs | 12 ++--- src/event_loop.rs | 23 +++----- src/handler.rs | 81 ++++++++++------------------- src/lib.rs | 47 +++-------------- src/vring.rs | 130 ++++++++++++++++++++++++++++++++++++++++++++++ 5 files changed, 176 insertions(+), 117 deletions(-) create mode 100644 src/vring.rs diff --git a/src/backend.rs b/src/backend.rs index 14c4fe4..259e3c6 100644 --- a/src/backend.rs +++ b/src/backend.rs @@ -110,7 +110,7 @@ pub trait VhostUserBackend: Send + Sync + 'static { &self, device_event: u16, evset: epoll::Events, - vrings: &[Arc>], + vrings: &[Vring], thread_id: usize, ) -> result::Result; } @@ -192,7 +192,7 @@ pub trait VhostUserBackendMut: Send + Sync + 'static { &mut self, device_event: u16, evset: epoll::Events, - vrings: &[Arc>], + vrings: &[Vring], thread_id: usize, ) -> result::Result; } @@ -253,7 +253,7 @@ impl VhostUserBackend for Arc { &self, device_event: u16, evset: epoll::Events, - vrings: &[Arc>], + vrings: &[Vring], thread_id: usize, ) -> Result { self.deref() @@ -317,7 +317,7 @@ impl VhostUserBackend for Mutex { &self, device_event: u16, evset: epoll::Events, - vrings: &[Arc>], + vrings: &[Vring], thread_id: usize, ) -> Result { self.lock() @@ -382,7 +382,7 @@ impl VhostUserBackend for RwLock { &self, device_event: u16, evset: epoll::Events, - vrings: &[Arc>], + vrings: &[Vring], thread_id: usize, ) -> Result { self.write() @@ -477,7 +477,7 @@ mod tests { &mut self, _device_event: u16, _evset: Events, - _vrings: &[Arc>], + _vrings: &[Vring], _thread_id: usize, ) -> Result { self.events += 1; diff --git a/src/event_loop.rs b/src/event_loop.rs index e1e3791..f82f849 100644 --- a/src/event_loop.rs +++ b/src/event_loop.rs @@ -8,7 +8,6 @@ use std::fs::File; use std::io; use std::os::unix::io::{AsRawFd, FromRawFd, RawFd}; use std::result; -use std::sync::{Arc, RwLock}; use super::{VhostUserBackend, Vring}; @@ -57,18 +56,14 @@ pub type VringEpollResult = std::result::Result; pub struct VringEpollHandler { epoll_file: File, backend: S, - vrings: Vec>>, + vrings: Vec, thread_id: usize, exit_event_id: Option, } impl VringEpollHandler { /// Create a `VringEpollHandler` instance. - pub(crate) fn new( - backend: S, - vrings: Vec>>, - thread_id: usize, - ) -> VringEpollResult { + pub(crate) fn new(backend: S, vrings: Vec, thread_id: usize) -> VringEpollResult { let epoll_fd = epoll::create(true).map_err(VringEpollError::EpollCreateFd)?; let epoll_file = unsafe { File::from_raw_fd(epoll_fd) }; let (exit_event_fd, exit_event_id) = match backend.exit_event(thread_id) { @@ -187,16 +182,14 @@ impl VringEpollHandler { return Ok(true); } - let num_queues = self.vrings.len(); - if (device_event as usize) < num_queues { - let vring = &self.vrings[device_event as usize].read().unwrap(); - if let Some(kick) = &vring.kick { - kick.read().map_err(VringEpollError::HandleEventReadKick)?; - } + if (device_event as usize) < self.vrings.len() { + let vring = &self.vrings[device_event as usize]; + let enabled = vring + .read_kick() + .map_err(VringEpollError::HandleEventReadKick)?; // If the vring is not enabled, it should not be processed. - // The event is only read to be discarded. - if !vring.enabled { + if !enabled { return Ok(false); } } diff --git a/src/handler.rs b/src/handler.rs index 372ffb1..533c70b 100644 --- a/src/handler.rs +++ b/src/handler.rs @@ -6,19 +6,20 @@ use std::error; use std::fs::File; use std::io; -use std::os::unix::io::{AsRawFd, FromRawFd, IntoRawFd}; -use std::sync::{Arc, RwLock}; +use std::os::unix::io::AsRawFd; +use std::sync::Arc; use std::thread; use vhost::vhost_user::message::{ VhostUserConfigFlags, VhostUserMemoryRegion, VhostUserProtocolFeatures, - VhostUserVirtioFeatures, VhostUserVringAddrFlags, VhostUserVringState, + VhostUserSingleMemoryRegion, VhostUserVirtioFeatures, VhostUserVringAddrFlags, + VhostUserVringState, }; use vhost::vhost_user::{Error as VhostUserError, Result as VhostUserResult, SlaveFsCacheReq}; use virtio_bindings::bindings::virtio_ring::VIRTIO_RING_F_EVENT_IDX; use vm_memory::{FileOffset, GuestAddress, GuestMemoryAtomic, GuestMemoryMmap}; -use vmm_sys_util::eventfd::EventFd; +use super::event_loop::{VringEpollError, VringEpollResult}; use super::*; const MAX_MEM_SLOTS: u64 = 32; @@ -71,7 +72,7 @@ pub struct VhostUserHandler { queues_per_thread: Vec, mappings: Vec, atomic_mem: GuestMemoryAtomic, - vrings: Vec>>, + vrings: Vec, worker_threads: Vec>>, } @@ -80,22 +81,18 @@ impl VhostUserHandler { let num_queues = backend.num_queues(); let max_queue_size = backend.max_queue_size(); let queues_per_thread = backend.queues_per_thread(); - let atomic_mem = GuestMemoryAtomic::new(GuestMemoryMmap::new()); - let mut vrings: Vec>> = Vec::new(); + let mut vrings: Vec = Vec::new(); for _ in 0..num_queues { - let vring = Arc::new(RwLock::new(Vring::new( - atomic_mem.clone(), - max_queue_size as u16, - ))); + let vring = Vring::new(atomic_mem.clone(), max_queue_size as u16); vrings.push(vring); } let mut handlers = Vec::new(); let mut worker_threads = Vec::new(); for (thread_id, queues_mask) in queues_per_thread.iter().enumerate() { - let mut thread_vrings: Vec>> = Vec::new(); + let mut thread_vrings: Vec = Vec::new(); for (index, vring) in vrings.iter().enumerate() { if (queues_mask >> index) & 1u64 == 1u64 { thread_vrings.push(vring.clone()); @@ -186,8 +183,8 @@ impl VhostUserSlaveReqHandlerMut for VhostUserHandl // been disabled by VHOST_USER_SET_VRING_ENABLE with parameter 0. let vring_enabled = self.acked_features & VhostUserVirtioFeatures::PROTOCOL_FEATURES.bits() == 0; - for vring in self.vrings.iter_mut() { - vring.write().unwrap().enabled = vring_enabled; + for vring in self.vrings.iter() { + vring.set_enabled(vring_enabled); } self.backend.acked_features(self.acked_features); @@ -256,7 +253,7 @@ impl VhostUserSlaveReqHandlerMut for VhostUserHandl if index as usize >= self.num_queues || num == 0 || num as usize > self.max_queue_size { return Err(VhostUserError::InvalidParam); } - self.vrings[index as usize].write().unwrap().queue.size = num as u16; + self.vrings[index as usize].set_queue_size(num as u16); Ok(()) } @@ -283,17 +280,7 @@ impl VhostUserSlaveReqHandlerMut for VhostUserHandl let used_ring = self.vmm_va_to_gpa(used).map_err(|e| { VhostUserError::ReqHandlerError(io::Error::new(io::ErrorKind::Other, e)) })?; - self.vrings[index as usize] - .write() - .unwrap() - .queue - .desc_table = GuestAddress(desc_table); - self.vrings[index as usize] - .write() - .unwrap() - .queue - .avail_ring = GuestAddress(avail_ring); - self.vrings[index as usize].write().unwrap().queue.used_ring = GuestAddress(used_ring); + self.vrings[index as usize].set_queue_info(desc_table, avail_ring, used_ring); Ok(()) } else { Err(VhostUserError::InvalidParam) @@ -301,19 +288,12 @@ impl VhostUserSlaveReqHandlerMut for VhostUserHandl } fn set_vring_base(&mut self, index: u32, base: u32) -> VhostUserResult<()> { - self.vrings[index as usize] - .write() - .unwrap() - .queue - .set_next_avail(base as u16); - let event_idx: bool = (self.acked_features & (1 << VIRTIO_RING_F_EVENT_IDX)) != 0; - self.vrings[index as usize] - .write() - .unwrap() - .mut_queue() - .set_event_idx(event_idx); + + self.vrings[index as usize].set_queue_next_avail(base as u16); + self.vrings[index as usize].set_queue_event_idx(event_idx); self.backend.set_event_idx(event_idx); + Ok(()) } @@ -326,8 +306,8 @@ impl VhostUserSlaveReqHandlerMut for VhostUserHandl // that file descriptor is readable) on the descriptor specified by // VHOST_USER_SET_VRING_KICK, and stop ring upon receiving // VHOST_USER_GET_VRING_BASE. - self.vrings[index as usize].write().unwrap().queue.ready = false; - if let Some(fd) = self.vrings[index as usize].read().unwrap().kick.as_ref() { + self.vrings[index as usize].set_queue_ready(false); + if let Some(fd) = self.vrings[index as usize].get_ref().get_kick() { for (thread_index, queues_mask) in self.queues_per_thread.iter().enumerate() { let shifted_queues_mask = queues_mask >> index; if shifted_queues_mask & 1u64 == 1u64 { @@ -344,11 +324,7 @@ impl VhostUserSlaveReqHandlerMut for VhostUserHandl } } - let next_avail = self.vrings[index as usize] - .read() - .unwrap() - .queue - .next_avail(); + let next_avail = self.vrings[index as usize].queue_next_avail(); Ok(VhostUserVringState::new(index, u32::from(next_avail))) } @@ -362,16 +338,15 @@ impl VhostUserSlaveReqHandlerMut for VhostUserHandl // does File, so this is safe. // Ideally, we'd have a generic way to refer to a uniquely-owned fd, // such as that proposed by Rust RFC #3128. - self.vrings[index as usize].write().unwrap().kick = - file.map(|f| unsafe { EventFd::from_raw_fd(f.into_raw_fd()) }); + self.vrings[index as usize].set_kick(file); // Quote from vhost-user specification: // Client must start ring upon receiving a kick (that is, detecting // that file descriptor is readable) on the descriptor specified by // VHOST_USER_SET_VRING_KICK, and stop ring upon receiving // VHOST_USER_GET_VRING_BASE. - self.vrings[index as usize].write().unwrap().queue.ready = true; - if let Some(fd) = self.vrings[index as usize].read().unwrap().kick.as_ref() { + self.vrings[index as usize].set_queue_ready(true); + if let Some(fd) = self.vrings[index as usize].get_ref().get_kick() { for (thread_index, queues_mask) in self.queues_per_thread.iter().enumerate() { let shifted_queues_mask = queues_mask >> index; if shifted_queues_mask & 1u64 == 1u64 { @@ -396,9 +371,7 @@ impl VhostUserSlaveReqHandlerMut for VhostUserHandl return Err(VhostUserError::InvalidParam); } - // SAFETY: see comment in set_vring_kick() - self.vrings[index as usize].write().unwrap().call = - file.map(|f| unsafe { EventFd::from_raw_fd(f.into_raw_fd()) }); + self.vrings[index as usize].set_call(file); Ok(()) } @@ -408,9 +381,7 @@ impl VhostUserSlaveReqHandlerMut for VhostUserHandl return Err(VhostUserError::InvalidParam); } - // SAFETY: see comment in set_vring_kick() - self.vrings[index as usize].write().unwrap().err = - file.map(|f| unsafe { EventFd::from_raw_fd(f.into_raw_fd()) }); + self.vrings[index as usize].set_err(file); Ok(()) } @@ -428,7 +399,7 @@ impl VhostUserSlaveReqHandlerMut for VhostUserHandl // enabled by VHOST_USER_SET_VRING_ENABLE with parameter 1, // or after it has been disabled by VHOST_USER_SET_VRING_ENABLE // with parameter 0. - self.vrings[index as usize].write().unwrap().enabled = enable; + self.vrings[index as usize].set_enabled(enable); Ok(()) } diff --git a/src/lib.rs b/src/lib.rs index 7c962a2..e2c4c34 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,7 +1,6 @@ // Copyright 2019 Intel Corporation. All Rights Reserved. -// SPDX-License-Identifier: Apache-2.0 -// // Copyright 2019 Alibaba Cloud Computing. All rights reserved. +// // SPDX-License-Identifier: Apache-2.0 //! A simple framework to run a vhost-user backend service. @@ -14,15 +13,10 @@ use std::result; use std::sync::{Arc, Mutex}; use std::thread; -use vhost::vhost_user::message::VhostUserSingleMemoryRegion; use vhost::vhost_user::{ Error as VhostUserError, Listener, SlaveListener, VhostUserSlaveReqHandlerMut, }; -use virtio_queue::Queue; -use vm_memory::{ - GuestAddressSpace, GuestMemoryAtomic, GuestMemoryMmap, GuestRegionMmap, MmapRegion, -}; -use vmm_sys_util::eventfd::EventFd; +use vm_memory::{GuestAddressSpace, GuestRegionMmap, MmapRegion}; use self::handler::VhostUserHandler; @@ -30,11 +24,14 @@ mod backend; pub use self::backend::{VhostUserBackend, VhostUserBackendMut}; mod event_loop; -pub use self::event_loop::{VringEpollError, VringEpollHandler, VringEpollResult}; +pub use self::event_loop::VringEpollHandler; mod handler; pub use self::handler::VhostUserHandlerError; +mod vring; +pub use self::vring::{Vring, VringState}; + #[derive(Debug)] /// Errors related to vhost-user daemon. pub enum Error { @@ -130,35 +127,3 @@ impl VhostUserDaemon { self.handler.lock().unwrap().get_epoll_handlers() } } - -pub struct Vring { - queue: Queue>, - kick: Option, - call: Option, - err: Option, - enabled: bool, -} - -impl Vring { - fn new(atomic_mem: GuestMemoryAtomic, max_queue_size: u16) -> Self { - Vring { - queue: Queue::new(atomic_mem, max_queue_size), - kick: None, - call: None, - err: None, - enabled: false, - } - } - - pub fn mut_queue(&mut self) -> &mut Queue> { - &mut self.queue - } - - pub fn signal_used_queue(&mut self) -> result::Result<(), io::Error> { - if let Some(call) = self.call.as_ref() { - call.write(1) - } else { - Ok(()) - } - } -} diff --git a/src/vring.rs b/src/vring.rs new file mode 100644 index 0000000..7325bc0 --- /dev/null +++ b/src/vring.rs @@ -0,0 +1,130 @@ +// Copyright 2019 Intel Corporation. All Rights Reserved. +// Copyright 2021 Alibaba Cloud Computing. All rights reserved. +// +// SPDX-License-Identifier: Apache-2.0 + +//! Struct to maintain state information and manipulate vhost-user queues. + +use std::fs::File; +use std::io; +use std::os::unix::io::{FromRawFd, IntoRawFd}; +use std::sync::{Arc, RwLock, RwLockReadGuard, RwLockWriteGuard}; + +use virtio_queue::Queue; +use vm_memory::{GuestAddress, GuestMemoryAtomic, GuestMemoryMmap}; +use vmm_sys_util::eventfd::EventFd; + +/// Struct to maintain raw state information for a vhost-user queue. +pub struct VringState { + queue: Queue>, + kick: Option, + call: Option, + err: Option, + enabled: bool, +} + +impl VringState { + fn new(atomic_mem: GuestMemoryAtomic, max_queue_size: u16) -> Self { + VringState { + queue: Queue::new(atomic_mem, max_queue_size), + kick: None, + call: None, + err: None, + enabled: false, + } + } + + /// Get a mutable reference to the underlying `Queue` object. + pub fn get_queue_mut(&mut self) -> &mut Queue> { + &mut self.queue + } + + /// Get a immutable reference to the underlying kick event fd. + pub fn get_kick(&self) -> &Option { + &self.kick + } +} + +/// Struct to maintain state information and manipulate a vhost-user queue. +#[derive(Clone)] +pub struct Vring { + state: Arc>, +} + +impl Vring { + /// Get a immutable guard to the underlying raw `VringState` object. + pub fn get_ref(&self) -> RwLockReadGuard { + self.state.read().unwrap() + } + + /// Get a mutable guard to the underlying raw `VringState` object. + pub fn get_mut(&self) -> RwLockWriteGuard { + self.state.write().unwrap() + } + + pub(crate) fn new(mem: GuestMemoryAtomic, max_queue_size: u16) -> Self { + Vring { + state: Arc::new(RwLock::new(VringState::new(mem, max_queue_size))), + } + } + + pub(crate) fn set_enabled(&self, enabled: bool) { + self.get_mut().enabled = enabled; + } + + pub(crate) fn set_queue_info(&self, desc_table: u64, avail_ring: u64, used_ring: u64) { + let mut state = self.get_mut(); + + state.queue.desc_table = GuestAddress(desc_table); + state.queue.avail_ring = GuestAddress(avail_ring); + state.queue.used_ring = GuestAddress(used_ring); + } + + pub(crate) fn queue_next_avail(&self) -> u16 { + self.get_ref().queue.next_avail() + } + + pub(crate) fn set_queue_next_avail(&self, base: u16) { + self.get_mut().queue.set_next_avail(base); + } + + pub(crate) fn set_queue_size(&self, num: u16) { + self.get_mut().queue.size = num; + } + + pub(crate) fn set_queue_event_idx(&self, enabled: bool) { + self.get_mut().queue.set_event_idx(enabled); + } + + pub(crate) fn set_queue_ready(&self, ready: bool) { + self.get_mut().queue.ready = ready; + } + + pub(crate) fn set_kick(&self, file: Option) { + // SAFETY: + // EventFd requires that it has sole ownership of its fd. So does File, so this is safe. + // Ideally, we'd have a generic way to refer to a uniquely-owned fd, such as that proposed + // by Rust RFC #3128. + self.get_mut().kick = file.map(|f| unsafe { EventFd::from_raw_fd(f.into_raw_fd()) }); + } + + pub(crate) fn read_kick(&self) -> io::Result { + let state = self.get_ref(); + + if let Some(kick) = &state.kick { + kick.read()?; + } + + Ok(state.enabled) + } + + pub(crate) fn set_call(&self, file: Option) { + // SAFETY: see comment in set_kick() + self.get_mut().call = file.map(|f| unsafe { EventFd::from_raw_fd(f.into_raw_fd()) }); + } + + pub(crate) fn set_err(&self, file: Option) { + // SAFETY: see comment in set_kick() + self.get_mut().err = file.map(|f| unsafe { EventFd::from_raw_fd(f.into_raw_fd()) }); + } +} From 71db23c1848e49d3b9aa0985b1e84188b7f2a541 Mon Sep 17 00:00:00 2001 From: Liu Jiang Date: Mon, 9 Aug 2021 18:17:03 +0800 Subject: [PATCH 073/139] vring: introduce generic parameter type `M` Introduce genric type parameter type `M: GuestAddressSpace` for Vring and VringState. Signed-off-by: Liu Jiang --- src/vring.rs | 62 ++++++++++++++++++++++++++++++++++++++-------------- 1 file changed, 46 insertions(+), 16 deletions(-) diff --git a/src/vring.rs b/src/vring.rs index 7325bc0..a04abb5 100644 --- a/src/vring.rs +++ b/src/vring.rs @@ -8,25 +8,26 @@ use std::fs::File; use std::io; use std::os::unix::io::{FromRawFd, IntoRawFd}; +use std::result::Result; use std::sync::{Arc, RwLock, RwLockReadGuard, RwLockWriteGuard}; -use virtio_queue::Queue; -use vm_memory::{GuestAddress, GuestMemoryAtomic, GuestMemoryMmap}; +use virtio_queue::{Error as VirtQueError, Queue}; +use vm_memory::{GuestAddress, GuestAddressSpace, GuestMemoryAtomic, GuestMemoryMmap}; use vmm_sys_util::eventfd::EventFd; /// Struct to maintain raw state information for a vhost-user queue. -pub struct VringState { - queue: Queue>, +pub struct VringState { + queue: Queue, kick: Option, call: Option, err: Option, enabled: bool, } -impl VringState { - fn new(atomic_mem: GuestMemoryAtomic, max_queue_size: u16) -> Self { +impl VringState { + fn new(mem: M, max_queue_size: u16) -> Self { VringState { - queue: Queue::new(atomic_mem, max_queue_size), + queue: Queue::new(mem, max_queue_size), kick: None, call: None, err: None, @@ -34,12 +35,12 @@ impl VringState { } } - /// Get a mutable reference to the underlying `Queue` object. - pub fn get_queue_mut(&mut self) -> &mut Queue> { + /// Get a mutable reference to the underlying raw `Queue` object. + pub fn get_queue_mut(&mut self) -> &mut Queue { &mut self.queue } - /// Get a immutable reference to the underlying kick event fd. + /// Get a immutable reference to the kick event fd. pub fn get_kick(&self) -> &Option { &self.kick } @@ -47,22 +48,51 @@ impl VringState { /// Struct to maintain state information and manipulate a vhost-user queue. #[derive(Clone)] -pub struct Vring { - state: Arc>, +pub struct Vring> { + state: Arc>>, } -impl Vring { +impl Vring { /// Get a immutable guard to the underlying raw `VringState` object. - pub fn get_ref(&self) -> RwLockReadGuard { + pub fn get_ref(&self) -> RwLockReadGuard> { self.state.read().unwrap() } /// Get a mutable guard to the underlying raw `VringState` object. - pub fn get_mut(&self) -> RwLockWriteGuard { + pub fn get_mut(&self) -> RwLockWriteGuard> { self.state.write().unwrap() } - pub(crate) fn new(mem: GuestMemoryAtomic, max_queue_size: u16) -> Self { + /// Add an used descriptor into the used queue. + pub fn add_used(&self, desc_index: u16, len: u32) -> Result<(), VirtQueError> { + self.get_mut().get_queue_mut().add_used(desc_index, len) + } + + /// Notify the vhost-user master that used descriptors have been put into the used queue. + pub fn signal_used_queue(&self) -> io::Result<()> { + if let Some(call) = self.get_ref().call.as_ref() { + call.write(1) + } else { + Ok(()) + } + } + + /// Enable event notification for queue. + pub fn enable_notification(&self) -> Result { + self.get_mut().get_queue_mut().enable_notification() + } + + /// Disable event notification for queue. + pub fn disable_notification(&self) -> Result<(), VirtQueError> { + self.get_mut().get_queue_mut().disable_notification() + } + + /// Check whether a notification to the guest is needed. + pub fn needs_notification(&self) -> Result { + self.get_mut().get_queue_mut().needs_notification() + } + + pub(crate) fn new(mem: M, max_queue_size: u16) -> Self { Vring { state: Arc::new(RwLock::new(VringState::new(mem, max_queue_size))), } From ca2b434900ff5e0e1ec5eb1266e17801c4645aeb Mon Sep 17 00:00:00 2001 From: Liu Jiang Date: Mon, 9 Aug 2021 17:12:41 +0800 Subject: [PATCH 074/139] Implement Display for Error Implement Display for Error, and minor documentation enhancement. Signed-off-by: Liu Jiang --- Cargo.toml | 2 ++ coverage_config_x86_64.json | 2 +- src/lib.rs | 16 +++++++++++++++- 3 files changed, 18 insertions(+), 2 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 2085046..14dbcb2 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -2,6 +2,8 @@ name = "vhost-user-backend" version = "0.1.0" authors = ["The Cloud Hypervisor Authors"] +keywords = ["vhost-user", "virtio"] +description = "A framework to build vhost-user backend service daemon" edition = "2018" license = "Apache-2.0" diff --git a/coverage_config_x86_64.json b/coverage_config_x86_64.json index a2e5cd1..ffb5a5d 100644 --- a/coverage_config_x86_64.json +++ b/coverage_config_x86_64.json @@ -1,5 +1,5 @@ { - "coverage_score": 0, + "coverage_score": 50.8, "exclude_path": "", "crate_features": "" } diff --git a/src/lib.rs b/src/lib.rs index e2c4c34..db6a63f 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -8,6 +8,7 @@ #[macro_use] extern crate log; +use std::fmt::{Display, Formatter}; use std::io; use std::result; use std::sync::{Arc, Mutex}; @@ -49,10 +50,23 @@ pub enum Error { HandleRequest(VhostUserError), } +impl Display for Error { + fn fmt(&self, f: &mut Formatter) -> std::fmt::Result { + match self { + Error::NewVhostUserHandler(e) => write!(f, "cannot create vhost user handler: {}", e), + Error::CreateSlaveListener(e) => write!(f, "cannot create slave listener: {}", e), + Error::CreateSlaveReqHandler(e) => write!(f, "cannot create slave req handler: {}", e), + Error::StartDaemon(e) => write!(f, "failed to start daemon: {}", e), + Error::WaitDaemon(_e) => write!(f, "failed to wait for daemon exit"), + Error::HandleRequest(e) => write!(f, "failed to handle request: {}", e), + } + } +} + /// Result of vhost-user daemon operations. pub type Result = result::Result; -/// Implement a simple framework to run a vhost user service daemon. +/// Implement a simple framework to run a vhost-user service daemon. /// /// This structure is the public API the backend is allowed to interact with in order to run /// a fully functional vhost-user daemon. From 5dc56732f8fb2d6317329f652f9204f9ea36a6c5 Mon Sep 17 00:00:00 2001 From: Liu Jiang Date: Mon, 9 Aug 2021 23:03:26 +0800 Subject: [PATCH 075/139] Support guest memory dirty page tracking Add a generic type parameter `B: Bitmap` to several key structs to support guest memory dirty page tracking. Signed-off-by: Liu Jiang --- src/backend.rs | 58 ++++++++++++++++++----------------------------- src/event_loop.rs | 16 +++++++++---- src/handler.rs | 31 ++++++++++++++----------- src/lib.rs | 25 +++++++++++++------- 4 files changed, 68 insertions(+), 62 deletions(-) diff --git a/src/backend.rs b/src/backend.rs index 259e3c6..fdc6e1b 100644 --- a/src/backend.rs +++ b/src/backend.rs @@ -25,16 +25,16 @@ use std::sync::{Arc, Mutex, RwLock}; use vhost::vhost_user::message::VhostUserProtocolFeatures; use vhost::vhost_user::SlaveFsCacheReq; -use vm_memory::{GuestMemoryAtomic, GuestMemoryMmap}; +use vm_memory::bitmap::Bitmap; use vmm_sys_util::eventfd::EventFd; -use super::Vring; +use super::{Vring, GM}; /// Trait with interior mutability for vhost user backend servers to implement concrete services. /// /// To support multi-threading and asynchronous IO, we enforce `the Send + Sync + 'static`. /// So there's no plan for support of "Rc" and "RefCell". -pub trait VhostUserBackend: Send + Sync + 'static { +pub trait VhostUserBackend: Send + Sync + 'static { /// Get number of queues supported. fn num_queues(&self) -> usize; @@ -70,10 +70,7 @@ pub trait VhostUserBackend: Send + Sync + 'static { } /// Update guest memory regions. - fn update_memory( - &self, - atomic_mem: GuestMemoryAtomic, - ) -> result::Result<(), io::Error>; + fn update_memory(&self, mem: GM) -> result::Result<(), io::Error>; /// Set handler for communicating with the master by the slave communication channel. /// @@ -110,13 +107,13 @@ pub trait VhostUserBackend: Send + Sync + 'static { &self, device_event: u16, evset: epoll::Events, - vrings: &[Vring], + vrings: &[Vring>], thread_id: usize, ) -> result::Result; } /// Trait without interior mutability for vhost user backend servers to implement concrete services. -pub trait VhostUserBackendMut: Send + Sync + 'static { +pub trait VhostUserBackendMut: Send + Sync + 'static { /// Get number of queues supported. fn num_queues(&self) -> usize; @@ -152,10 +149,7 @@ pub trait VhostUserBackendMut: Send + Sync + 'static { } /// Update guest memory regions. - fn update_memory( - &mut self, - atomic_mem: GuestMemoryAtomic, - ) -> result::Result<(), io::Error>; + fn update_memory(&mut self, mem: GM) -> result::Result<(), io::Error>; /// Set handler for communicating with the master by the slave communication channel. /// @@ -192,12 +186,12 @@ pub trait VhostUserBackendMut: Send + Sync + 'static { &mut self, device_event: u16, evset: epoll::Events, - vrings: &[Vring], + vrings: &[Vring>], thread_id: usize, ) -> result::Result; } -impl VhostUserBackend for Arc { +impl, B: Bitmap + 'static> VhostUserBackend for Arc { fn num_queues(&self) -> usize { self.deref().num_queues() } @@ -230,11 +224,8 @@ impl VhostUserBackend for Arc { self.deref().set_config(offset, buf) } - fn update_memory( - &self, - atomic_mem: GuestMemoryAtomic, - ) -> Result<(), io::Error> { - self.deref().update_memory(atomic_mem) + fn update_memory(&self, mem: GM) -> Result<(), io::Error> { + self.deref().update_memory(mem) } fn set_slave_req_fd(&self, vu_req: SlaveFsCacheReq) { @@ -253,7 +244,7 @@ impl VhostUserBackend for Arc { &self, device_event: u16, evset: epoll::Events, - vrings: &[Vring], + vrings: &[Vring>], thread_id: usize, ) -> Result { self.deref() @@ -261,7 +252,7 @@ impl VhostUserBackend for Arc { } } -impl VhostUserBackend for Mutex { +impl, B: Bitmap + 'static> VhostUserBackend for Mutex { fn num_queues(&self) -> usize { self.lock().unwrap().num_queues() } @@ -294,11 +285,8 @@ impl VhostUserBackend for Mutex { self.lock().unwrap().set_config(offset, buf) } - fn update_memory( - &self, - atomic_mem: GuestMemoryAtomic, - ) -> Result<(), io::Error> { - self.lock().unwrap().update_memory(atomic_mem) + fn update_memory(&self, mem: GM) -> Result<(), io::Error> { + self.lock().unwrap().update_memory(mem) } fn set_slave_req_fd(&self, vu_req: SlaveFsCacheReq) { @@ -317,7 +305,7 @@ impl VhostUserBackend for Mutex { &self, device_event: u16, evset: epoll::Events, - vrings: &[Vring], + vrings: &[Vring>], thread_id: usize, ) -> Result { self.lock() @@ -326,7 +314,7 @@ impl VhostUserBackend for Mutex { } } -impl VhostUserBackend for RwLock { +impl, B: Bitmap + 'static> VhostUserBackend for RwLock { fn num_queues(&self) -> usize { self.read().unwrap().num_queues() } @@ -359,11 +347,8 @@ impl VhostUserBackend for RwLock { self.write().unwrap().set_config(offset, buf) } - fn update_memory( - &self, - atomic_mem: GuestMemoryAtomic, - ) -> Result<(), io::Error> { - self.write().unwrap().update_memory(atomic_mem) + fn update_memory(&self, mem: GM) -> Result<(), io::Error> { + self.write().unwrap().update_memory(mem) } fn set_slave_req_fd(&self, vu_req: SlaveFsCacheReq) { @@ -382,7 +367,7 @@ impl VhostUserBackend for RwLock { &self, device_event: u16, evset: epoll::Events, - vrings: &[Vring], + vrings: &[Vring>], thread_id: usize, ) -> Result { self.write() @@ -397,6 +382,7 @@ mod tests { use epoll::Events; use std::io::Error; use std::sync::Mutex; + use vm_memory::{GuestMemoryAtomic, GuestMemoryMmap}; struct MockVhostBackend { events: u64, @@ -414,7 +400,7 @@ mod tests { } } - impl VhostUserBackendMut for MockVhostBackend { + impl VhostUserBackendMut<()> for MockVhostBackend { fn num_queues(&self) -> usize { 2 } diff --git a/src/event_loop.rs b/src/event_loop.rs index f82f849..dcb2227 100644 --- a/src/event_loop.rs +++ b/src/event_loop.rs @@ -9,7 +9,9 @@ use std::io; use std::os::unix::io::{AsRawFd, FromRawFd, RawFd}; use std::result; -use super::{VhostUserBackend, Vring}; +use vm_memory::bitmap::Bitmap; + +use super::{VhostUserBackend, Vring, GM}; /// Errors related to vring epoll event handling. #[derive(Debug)] @@ -53,17 +55,21 @@ pub type VringEpollResult = std::result::Result; /// - add file descriptors to be monitored by the epoll fd /// - remove registered file descriptors from the epoll fd /// - run the event loop to handle pending events on the epoll fd -pub struct VringEpollHandler { +pub struct VringEpollHandler, B: Bitmap + 'static> { epoll_file: File, backend: S, - vrings: Vec, + vrings: Vec>>, thread_id: usize, exit_event_id: Option, } -impl VringEpollHandler { +impl, B: Bitmap + 'static> VringEpollHandler { /// Create a `VringEpollHandler` instance. - pub(crate) fn new(backend: S, vrings: Vec, thread_id: usize) -> VringEpollResult { + pub(crate) fn new( + backend: S, + vrings: Vec>>, + thread_id: usize, + ) -> VringEpollResult { let epoll_fd = epoll::create(true).map_err(VringEpollError::EpollCreateFd)?; let epoll_file = unsafe { File::from_raw_fd(epoll_fd) }; let (exit_event_fd, exit_event_id) = match backend.exit_event(thread_id) { diff --git a/src/handler.rs b/src/handler.rs index 533c70b..ad51a88 100644 --- a/src/handler.rs +++ b/src/handler.rs @@ -17,7 +17,9 @@ use vhost::vhost_user::message::{ }; use vhost::vhost_user::{Error as VhostUserError, Result as VhostUserResult, SlaveFsCacheReq}; use virtio_bindings::bindings::virtio_ring::VIRTIO_RING_F_EVENT_IDX; -use vm_memory::{FileOffset, GuestAddress, GuestMemoryAtomic, GuestMemoryMmap}; +use vm_memory::bitmap::Bitmap; +use vm_memory::mmap::NewBitmap; +use vm_memory::{FileOffset, GuestAddress, GuestMemoryMmap, GuestRegionMmap}; use super::event_loop::{VringEpollError, VringEpollResult}; use super::*; @@ -60,9 +62,9 @@ struct AddrMapping { gpa_base: u64, } -pub struct VhostUserHandler { +pub struct VhostUserHandler, B: Bitmap + 'static> { backend: S, - handlers: Vec>>, + handlers: Vec>>, owned: bool, features_acked: bool, acked_features: u64, @@ -71,19 +73,18 @@ pub struct VhostUserHandler { max_queue_size: usize, queues_per_thread: Vec, mappings: Vec, - atomic_mem: GuestMemoryAtomic, - vrings: Vec, + atomic_mem: GM, + vrings: Vec>>, worker_threads: Vec>>, } -impl VhostUserHandler { - pub(crate) fn new(backend: S) -> VhostUserHandlerResult { +impl + Clone, B: Bitmap + Clone + Send + Sync> VhostUserHandler { + pub(crate) fn new(backend: S, atomic_mem: GM) -> VhostUserHandlerResult { let num_queues = backend.num_queues(); let max_queue_size = backend.max_queue_size(); let queues_per_thread = backend.queues_per_thread(); - let atomic_mem = GuestMemoryAtomic::new(GuestMemoryMmap::new()); - let mut vrings: Vec = Vec::new(); + let mut vrings = Vec::new(); for _ in 0..num_queues { let vring = Vring::new(atomic_mem.clone(), max_queue_size as u16); vrings.push(vring); @@ -92,7 +93,7 @@ impl VhostUserHandler { let mut handlers = Vec::new(); let mut worker_threads = Vec::new(); for (thread_id, queues_mask) in queues_per_thread.iter().enumerate() { - let mut thread_vrings: Vec = Vec::new(); + let mut thread_vrings = Vec::new(); for (index, vring) in vrings.iter().enumerate() { if (queues_mask >> index) & 1u64 == 1u64 { thread_vrings.push(vring.clone()); @@ -129,8 +130,10 @@ impl VhostUserHandler { worker_threads, }) } +} - pub(crate) fn get_epoll_handlers(&self) -> Vec>> { +impl + Clone, B: Bitmap> VhostUserHandler { + pub(crate) fn get_epoll_handlers(&self) -> Vec>> { self.handlers.clone() } @@ -145,7 +148,9 @@ impl VhostUserHandler { } } -impl VhostUserSlaveReqHandlerMut for VhostUserHandler { +impl + Clone, B: NewBitmap + Clone> VhostUserSlaveReqHandlerMut + for VhostUserHandler +{ fn set_owner(&mut self) -> VhostUserResult<()> { if self.owned { return Err(VhostUserError::InvalidOperation); @@ -519,7 +524,7 @@ impl VhostUserSlaveReqHandlerMut for VhostUserHandl } } -impl Drop for VhostUserHandler { +impl, B: Bitmap> Drop for VhostUserHandler { fn drop(&mut self) { for thread in self.worker_threads.drain(..) { if let Err(e) = thread.join() { diff --git a/src/lib.rs b/src/lib.rs index db6a63f..a2b85e2 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,5 +1,5 @@ // Copyright 2019 Intel Corporation. All Rights Reserved. -// Copyright 2019 Alibaba Cloud Computing. All rights reserved. +// Copyright 2019-2021 Alibaba Cloud Computing. All rights reserved. // // SPDX-License-Identifier: Apache-2.0 @@ -17,7 +17,9 @@ use std::thread; use vhost::vhost_user::{ Error as VhostUserError, Listener, SlaveListener, VhostUserSlaveReqHandlerMut, }; -use vm_memory::{GuestAddressSpace, GuestRegionMmap, MmapRegion}; +use vm_memory::bitmap::Bitmap; +use vm_memory::mmap::NewBitmap; +use vm_memory::{GuestAddressSpace, GuestMemoryAtomic, GuestMemoryMmap, MmapRegion}; use self::handler::VhostUserHandler; @@ -33,6 +35,9 @@ pub use self::handler::VhostUserHandlerError; mod vring; pub use self::vring::{Vring, VringState}; +/// An alias for `GuestMemoryAtomic>` to simplify code. +type GM = GuestMemoryAtomic>; + #[derive(Debug)] /// Errors related to vhost-user daemon. pub enum Error { @@ -70,21 +75,25 @@ pub type Result = result::Result; /// /// This structure is the public API the backend is allowed to interact with in order to run /// a fully functional vhost-user daemon. -pub struct VhostUserDaemon { +pub struct VhostUserDaemon, B: Bitmap + 'static> { name: String, - handler: Arc>>, + handler: Arc>>, main_thread: Option>>, } -impl VhostUserDaemon { +impl + Clone, B: NewBitmap + Clone + Send + Sync> VhostUserDaemon { /// Create the daemon instance, providing the backend implementation of `VhostUserBackend`. /// /// Under the hood, this will start a dedicated thread responsible for listening onto /// registered event. Those events can be vring events or custom events from the backend, /// but they get to be registered later during the sequence. - pub fn new(name: String, backend: S) -> Result { + pub fn new( + name: String, + backend: S, + atomic_mem: GuestMemoryAtomic>, + ) -> Result { let handler = Arc::new(Mutex::new( - VhostUserHandler::new(backend).map_err(Error::NewVhostUserHandler)?, + VhostUserHandler::new(backend, atomic_mem).map_err(Error::NewVhostUserHandler)?, )); Ok(VhostUserDaemon { @@ -137,7 +146,7 @@ impl VhostUserDaemon { /// /// This is necessary to perform further actions like registering and unregistering some extra /// event file descriptors. - pub fn get_epoll_handlers(&self) -> Vec>> { + pub fn get_epoll_handlers(&self) -> Vec>> { self.handler.lock().unwrap().get_epoll_handlers() } } From f6b7e49e3f9ea079f9429707d72ea983f58b89b6 Mon Sep 17 00:00:00 2001 From: Liu Jiang Date: Wed, 11 Aug 2021 20:06:22 +0800 Subject: [PATCH 076/139] handler: send exit event when destructing VhostUserHandler waits for all working thread to exit in drop(), but there's no mechanism to notify the working threads to exit. So add VhostUserHandler::send_exit_event() to notify working threads to exit. Signed-off-by: Liu Jiang --- src/event_loop.rs | 59 +++++++++++++++++++++++++++++------------------ src/handler.rs | 13 +++++++++-- 2 files changed, 48 insertions(+), 24 deletions(-) diff --git a/src/event_loop.rs b/src/event_loop.rs index dcb2227..d8125f8 100644 --- a/src/event_loop.rs +++ b/src/event_loop.rs @@ -10,6 +10,7 @@ use std::os::unix::io::{AsRawFd, FromRawFd, RawFd}; use std::result; use vm_memory::bitmap::Bitmap; +use vmm_sys_util::eventfd::EventFd; use super::{VhostUserBackend, Vring, GM}; @@ -60,6 +61,7 @@ pub struct VringEpollHandler, B: Bitmap + 'static> { backend: S, vrings: Vec>>, thread_id: usize, + exit_event_fd: Option, exit_event_id: Option, } @@ -72,33 +74,46 @@ impl, B: Bitmap + 'static> VringEpollHandler { ) -> VringEpollResult { let epoll_fd = epoll::create(true).map_err(VringEpollError::EpollCreateFd)?; let epoll_file = unsafe { File::from_raw_fd(epoll_fd) }; - let (exit_event_fd, exit_event_id) = match backend.exit_event(thread_id) { - Some((exit_event_fd, exit_event_id)) => { - (exit_event_fd.as_raw_fd(), Some(exit_event_id)) - } - None => (-1, None), - }; - let handler = VringEpollHandler { - epoll_file, - backend, - vrings, - thread_id, - exit_event_id, - }; - if let Some(exit_event_id) = exit_event_id { - epoll::ctl( - handler.epoll_file.as_raw_fd(), - epoll::ControlOptions::EPOLL_CTL_ADD, - exit_event_fd, - epoll::Event::new(epoll::Events::EPOLLIN, u64::from(exit_event_id)), - ) - .map_err(VringEpollError::RegisterExitEvent)?; - } + let handler = match backend.exit_event(thread_id) { + Some((exit_event_fd, exit_event_id)) => { + epoll::ctl( + epoll_file.as_raw_fd(), + epoll::ControlOptions::EPOLL_CTL_ADD, + exit_event_fd.as_raw_fd(), + epoll::Event::new(epoll::Events::EPOLLIN, u64::from(exit_event_id)), + ) + .map_err(VringEpollError::RegisterExitEvent)?; + + VringEpollHandler { + epoll_file, + backend, + vrings, + thread_id, + exit_event_fd: Some(exit_event_fd), + exit_event_id: Some(exit_event_id), + } + } + None => VringEpollHandler { + epoll_file, + backend, + vrings, + thread_id, + exit_event_fd: None, + exit_event_id: None, + }, + }; Ok(handler) } + /// Send `exit event` to break the event loop. + pub fn send_exit_event(&self) { + if let Some(eventfd) = self.exit_event_fd.as_ref() { + let _ = eventfd.write(1); + } + } + /// Register an event into the epoll fd. /// /// When this event is later triggered, the backend implementation of `handle_event` will be diff --git a/src/handler.rs b/src/handler.rs index ad51a88..2bdb58d 100644 --- a/src/handler.rs +++ b/src/handler.rs @@ -132,11 +132,17 @@ impl + Clone, B: Bitmap + Clone + Send + Sync> VhostUserH } } -impl + Clone, B: Bitmap> VhostUserHandler { +impl, B: Bitmap> VhostUserHandler { pub(crate) fn get_epoll_handlers(&self) -> Vec>> { self.handlers.clone() } + pub(crate) fn send_exit_event(&self) { + for handler in self.handlers.iter() { + handler.send_exit_event(); + } + } + fn vmm_va_to_gpa(&self, vmm_va: u64) -> VhostUserHandlerResult { for mapping in self.mappings.iter() { if vmm_va >= mapping.vmm_addr && vmm_va < mapping.vmm_addr + mapping.size { @@ -148,7 +154,7 @@ impl + Clone, B: Bitmap> VhostUserHandler { } } -impl + Clone, B: NewBitmap + Clone> VhostUserSlaveReqHandlerMut +impl, B: NewBitmap + Clone> VhostUserSlaveReqHandlerMut for VhostUserHandler { fn set_owner(&mut self) -> VhostUserResult<()> { @@ -526,6 +532,9 @@ impl + Clone, B: NewBitmap + Clone> VhostUserSlaveReqHand impl, B: Bitmap> Drop for VhostUserHandler { fn drop(&mut self) { + // Signal all working threads to exit. + self.send_exit_event(); + for thread in self.worker_threads.drain(..) { if let Err(e) = thread.join() { error!("Error in vring worker: {:?}", e); From 6b685925a1f7e01469fe37c57c752ab75ba833a7 Mon Sep 17 00:00:00 2001 From: Liu Jiang Date: Wed, 11 Aug 2021 01:01:13 +0800 Subject: [PATCH 077/139] Add more unit test cases Add more unit test cases to improve code coverage. Signed-off-by: Liu Jiang --- Cargo.toml | 3 ++ src/backend.rs | 6 +-- src/event_loop.rs | 38 +++++++++++++++++++ src/lib.rs | 19 ++++++++++ src/vring.rs | 93 +++++++++++++++++++++++++++++++++++++++++++++++ 5 files changed, 156 insertions(+), 3 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 14dbcb2..ffe55c7 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -16,3 +16,6 @@ virtio-bindings = "0.1" virtio-queue = { git = "https://github.com/rust-vmm/vm-virtio" } vm-memory = {version = "0.6", features = ["backend-mmap", "backend-atomic"]} vmm-sys-util = "0.8" + +[dev-dependencies] +vm-memory = {version = "0.6", features = ["backend-mmap", "backend-atomic", "backend-bitmap"]} diff --git a/src/backend.rs b/src/backend.rs index fdc6e1b..ba8d725 100644 --- a/src/backend.rs +++ b/src/backend.rs @@ -377,21 +377,21 @@ impl, B: Bitmap + 'static> VhostUserBackend for RwL } #[cfg(test)] -mod tests { +pub mod tests { use super::*; use epoll::Events; use std::io::Error; use std::sync::Mutex; use vm_memory::{GuestMemoryAtomic, GuestMemoryMmap}; - struct MockVhostBackend { + pub struct MockVhostBackend { events: u64, event_idx: bool, acked_features: u64, } impl MockVhostBackend { - fn new() -> Self { + pub fn new() -> Self { MockVhostBackend { events: 0, event_idx: false, diff --git a/src/event_loop.rs b/src/event_loop.rs index d8125f8..eafe33b 100644 --- a/src/event_loop.rs +++ b/src/event_loop.rs @@ -220,3 +220,41 @@ impl, B: Bitmap + 'static> VringEpollHandler { .map_err(VringEpollError::HandleEventBackendHandling) } } + +#[cfg(test)] +mod tests { + use super::super::backend::tests::MockVhostBackend; + use super::*; + use std::sync::{Arc, Mutex}; + use vm_memory::{GuestAddress, GuestMemoryAtomic, GuestMemoryMmap}; + use vmm_sys_util::eventfd::EventFd; + + #[test] + fn test_vring_epoll_handler() { + let mem = GuestMemoryAtomic::new( + GuestMemoryMmap::<()>::from_ranges(&[(GuestAddress(0x100000), 0x10000)]).unwrap(), + ); + let vring = Vring::new(mem, 0x1000); + let backend = Arc::new(Mutex::new(MockVhostBackend::new())); + + let handler = VringEpollHandler::new(backend, vec![vring], 0x1).unwrap(); + assert!(handler.exit_event_id.is_some()); + + let eventfd = EventFd::new(0).unwrap(); + handler + .register_listener(eventfd.as_raw_fd(), epoll::Events::EPOLLIN, 1) + .unwrap(); + // Register an already registered fd. + handler + .register_listener(eventfd.as_raw_fd(), epoll::Events::EPOLLIN, 1) + .unwrap_err(); + + handler + .unregister_listener(eventfd.as_raw_fd(), epoll::Events::EPOLLIN, 1) + .unwrap(); + // unregister an already unregistered fd. + handler + .unregister_listener(eventfd.as_raw_fd(), epoll::Events::EPOLLIN, 1) + .unwrap_err(); + } +} diff --git a/src/lib.rs b/src/lib.rs index a2b85e2..ddcc740 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -150,3 +150,22 @@ impl + Clone, B: NewBitmap + Clone + Send + Sync> VhostUs self.handler.lock().unwrap().get_epoll_handlers() } } + +#[cfg(test)] +mod tests { + use super::backend::tests::MockVhostBackend; + use super::*; + use vm_memory::{GuestAddress, GuestMemoryAtomic, GuestMemoryMmap}; + + #[test] + fn test_new_daemon() { + let mem = GuestMemoryAtomic::new( + GuestMemoryMmap::<()>::from_ranges(&[(GuestAddress(0x100000), 0x10000)]).unwrap(), + ); + let backend = Arc::new(Mutex::new(MockVhostBackend::new())); + let daemon = VhostUserDaemon::new("test".to_owned(), backend, mem).unwrap(); + + assert_eq!(daemon.get_epoll_handlers().len(), 2); + //daemon.start(Listener::new()).unwrap(); + } +} diff --git a/src/vring.rs b/src/vring.rs index a04abb5..31bf4a7 100644 --- a/src/vring.rs +++ b/src/vring.rs @@ -158,3 +158,96 @@ impl Vring { self.get_mut().err = file.map(|f| unsafe { EventFd::from_raw_fd(f.into_raw_fd()) }); } } + +#[cfg(test)] +mod tests { + use super::*; + use std::os::unix::io::AsRawFd; + use vm_memory::bitmap::AtomicBitmap; + use vmm_sys_util::eventfd::EventFd; + + #[test] + fn test_new_vring() { + let mem = GuestMemoryAtomic::new( + GuestMemoryMmap::::from_ranges(&[(GuestAddress(0x100000), 0x10000)]) + .unwrap(), + ); + let vring = Vring::new(mem, 0x1000); + + assert!(vring.get_ref().get_kick().is_none()); + assert_eq!(vring.get_ref().enabled, false); + assert_eq!(vring.get_mut().get_queue_mut().ready, false); + assert_eq!(vring.get_mut().get_queue_mut().event_idx_enabled, false); + + vring.set_enabled(true); + assert_eq!(vring.get_ref().enabled, true); + + vring.set_queue_info(0x100100, 0x100200, 0x100300); + assert_eq!( + vring.get_mut().get_queue_mut().desc_table, + GuestAddress(0x100100) + ); + assert_eq!( + vring.get_mut().get_queue_mut().avail_ring, + GuestAddress(0x100200) + ); + assert_eq!( + vring.get_mut().get_queue_mut().used_ring, + GuestAddress(0x100300) + ); + + assert_eq!(vring.queue_next_avail(), 0); + vring.set_queue_next_avail(0x20); + assert_eq!(vring.queue_next_avail(), 0x20); + + vring.set_queue_size(0x200); + assert_eq!(vring.get_mut().get_queue_mut().size, 0x200); + + vring.set_queue_event_idx(true); + assert_eq!(vring.get_mut().get_queue_mut().event_idx_enabled, true); + + vring.set_queue_ready(true); + assert_eq!(vring.get_mut().get_queue_mut().ready, true); + } + + #[test] + fn test_vring_set_fd() { + let mem = GuestMemoryAtomic::new( + GuestMemoryMmap::<()>::from_ranges(&[(GuestAddress(0x100000), 0x10000)]).unwrap(), + ); + let vring = Vring::new(mem, 0x1000); + + vring.set_enabled(true); + assert_eq!(vring.get_ref().enabled, true); + + let eventfd = EventFd::new(0).unwrap(); + let file = unsafe { File::from_raw_fd(eventfd.as_raw_fd()) }; + assert!(vring.get_ref().kick.is_none()); + assert_eq!(vring.read_kick().unwrap(), true); + vring.set_kick(Some(file)); + eventfd.write(1).unwrap(); + assert_eq!(vring.read_kick().unwrap(), true); + assert!(vring.get_ref().kick.is_some()); + vring.set_kick(None); + assert!(vring.get_ref().kick.is_none()); + std::mem::forget(eventfd); + + let eventfd = EventFd::new(0).unwrap(); + let file = unsafe { File::from_raw_fd(eventfd.as_raw_fd()) }; + assert!(vring.get_ref().call.is_none()); + vring.set_call(Some(file)); + assert!(vring.get_ref().call.is_some()); + vring.set_call(None); + assert!(vring.get_ref().call.is_none()); + std::mem::forget(eventfd); + + let eventfd = EventFd::new(0).unwrap(); + let file = unsafe { File::from_raw_fd(eventfd.as_raw_fd()) }; + assert!(vring.get_ref().err.is_none()); + vring.set_err(Some(file)); + assert!(vring.get_ref().err.is_some()); + vring.set_err(None); + assert!(vring.get_ref().err.is_none()); + std::mem::forget(eventfd); + } +} From 6d04368c4641a4ffa94822f78bbdd9b5b04bb478 Mon Sep 17 00:00:00 2001 From: Liu Jiang Date: Fri, 13 Aug 2021 14:25:37 +0800 Subject: [PATCH 078/139] backend: set default type for VhostUserBackend Use () as default type for trait VhostUserBackend and VhostUserBackendMut. Signed-off-by: Liu Jiang --- coverage_config_x86_64.json | 2 +- src/backend.rs | 8 ++++---- src/lib.rs | 2 +- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/coverage_config_x86_64.json b/coverage_config_x86_64.json index ffb5a5d..0036d98 100644 --- a/coverage_config_x86_64.json +++ b/coverage_config_x86_64.json @@ -1,5 +1,5 @@ { - "coverage_score": 50.8, + "coverage_score": 77.4, "exclude_path": "", "crate_features": "" } diff --git a/src/backend.rs b/src/backend.rs index ba8d725..d15629e 100644 --- a/src/backend.rs +++ b/src/backend.rs @@ -34,7 +34,7 @@ use super::{Vring, GM}; /// /// To support multi-threading and asynchronous IO, we enforce `the Send + Sync + 'static`. /// So there's no plan for support of "Rc" and "RefCell". -pub trait VhostUserBackend: Send + Sync + 'static { +pub trait VhostUserBackend: Send + Sync + 'static { /// Get number of queues supported. fn num_queues(&self) -> usize; @@ -113,7 +113,7 @@ pub trait VhostUserBackend: Send + Sync + 'static { } /// Trait without interior mutability for vhost user backend servers to implement concrete services. -pub trait VhostUserBackendMut: Send + Sync + 'static { +pub trait VhostUserBackendMut: Send + Sync + 'static { /// Get number of queues supported. fn num_queues(&self) -> usize; @@ -486,7 +486,7 @@ pub mod tests { assert_eq!(backend.queues_per_thread(), [1, 1]); assert_eq!(backend.get_config(0x200, 8), vec![0xa5; 8]); - backend.set_config(0x200, &vec![0xa5; 8]).unwrap(); + backend.set_config(0x200, &[0xa5; 8]).unwrap(); backend.acked_features(0xffff); assert_eq!(backend.lock().unwrap().acked_features, 0xffff); @@ -509,7 +509,7 @@ pub mod tests { assert_eq!(backend.queues_per_thread(), [1, 1]); assert_eq!(backend.get_config(0x200, 8), vec![0xa5; 8]); - backend.set_config(0x200, &vec![0xa5; 8]).unwrap(); + backend.set_config(0x200, &[0xa5; 8]).unwrap(); backend.acked_features(0xffff); assert_eq!(backend.read().unwrap().acked_features, 0xffff); diff --git a/src/lib.rs b/src/lib.rs index ddcc740..1af2241 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -75,7 +75,7 @@ pub type Result = result::Result; /// /// This structure is the public API the backend is allowed to interact with in order to run /// a fully functional vhost-user daemon. -pub struct VhostUserDaemon, B: Bitmap + 'static> { +pub struct VhostUserDaemon, B: Bitmap + 'static = ()> { name: String, handler: Arc>>, main_thread: Option>>, From 156c3a480d82223c3fb1bf3f61b7dd894a88401b Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 27 Aug 2021 01:07:33 +0000 Subject: [PATCH 079/139] Bump rust-vmm-ci from `d2ab3c0` to `8901e77` Bumps [rust-vmm-ci](https://github.com/rust-vmm/rust-vmm-ci) from `d2ab3c0` to `8901e77`. - [Release notes](https://github.com/rust-vmm/rust-vmm-ci/releases) - [Commits](https://github.com/rust-vmm/rust-vmm-ci/compare/d2ab3c090833aec72eee7da1e3884032206b00e3...8901e7752288ae1061e2ee888a104c083a451668) --- updated-dependencies: - dependency-name: rust-vmm-ci dependency-type: direct:production ... Signed-off-by: dependabot[bot] --- rust-vmm-ci | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/rust-vmm-ci b/rust-vmm-ci index d2ab3c0..8901e77 160000 --- a/rust-vmm-ci +++ b/rust-vmm-ci @@ -1 +1 @@ -Subproject commit d2ab3c090833aec72eee7da1e3884032206b00e3 +Subproject commit 8901e7752288ae1061e2ee888a104c083a451668 From f92701d4118883a90724ec50c68aaab63b218b1e Mon Sep 17 00:00:00 2001 From: Laura Loghin Date: Mon, 30 Aug 2021 14:59:33 +0300 Subject: [PATCH 080/139] remove coverage_config.json Signed-off-by: Laura Loghin --- coverage_config.json | 5 ----- 1 file changed, 5 deletions(-) delete mode 100644 coverage_config.json diff --git a/coverage_config.json b/coverage_config.json deleted file mode 100644 index 6637851..0000000 --- a/coverage_config.json +++ /dev/null @@ -1,5 +0,0 @@ -{ - "coverage_score": 90, - "exclude_path": "", - "crate_features": "" -} From 5dde7f196b071bd97cbe2dc78d94ff11b8b9fa77 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 2 Sep 2021 01:12:54 +0000 Subject: [PATCH 081/139] Bump rust-vmm-ci from `8901e77` to `1311bfa` Bumps [rust-vmm-ci](https://github.com/rust-vmm/rust-vmm-ci) from `8901e77` to `1311bfa`. - [Release notes](https://github.com/rust-vmm/rust-vmm-ci/releases) - [Commits](https://github.com/rust-vmm/rust-vmm-ci/compare/8901e7752288ae1061e2ee888a104c083a451668...1311bfa03f3510a3aec6b69ae085f21e8e4a1e5e) --- updated-dependencies: - dependency-name: rust-vmm-ci dependency-type: direct:production ... Signed-off-by: dependabot[bot] --- rust-vmm-ci | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/rust-vmm-ci b/rust-vmm-ci index 8901e77..1311bfa 160000 --- a/rust-vmm-ci +++ b/rust-vmm-ci @@ -1 +1 @@ -Subproject commit 8901e7752288ae1061e2ee888a104c083a451668 +Subproject commit 1311bfa03f3510a3aec6b69ae085f21e8e4a1e5e From f0af5f7fc22a345e050123dc048c0639965fe652 Mon Sep 17 00:00:00 2001 From: Viresh Kumar Date: Fri, 3 Sep 2021 13:10:58 +0530 Subject: [PATCH 082/139] backend: Set rev for virtio-queue The builds are failing since the remote branch's name is changed to main from master. Fix it by adding the rev for virtio-queue. Signed-off-by: Viresh Kumar --- Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Cargo.toml b/Cargo.toml index ffe55c7..e5f2b66 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -13,7 +13,7 @@ libc = ">=0.2.39" log = ">=0.4.6" vhost = { version = "0.1", features = ["vhost-user-slave"] } virtio-bindings = "0.1" -virtio-queue = { git = "https://github.com/rust-vmm/vm-virtio" } +virtio-queue = { git = "https://github.com/rust-vmm/vm-virtio", rev = "6013dd9" } vm-memory = {version = "0.6", features = ["backend-mmap", "backend-atomic"]} vmm-sys-util = "0.8" From f363fb2b53abc13ca017ec472ac2f100be381783 Mon Sep 17 00:00:00 2001 From: Viresh Kumar Date: Mon, 6 Sep 2021 12:53:07 +0530 Subject: [PATCH 083/139] dependabot: Allow updating dependencies Allow crate updates on weekly basis. Signed-off-by: Viresh Kumar --- .github/dependabot.yml | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/.github/dependabot.yml b/.github/dependabot.yml index 4fcd556..addf8be 100644 --- a/.github/dependabot.yml +++ b/.github/dependabot.yml @@ -1,5 +1,13 @@ version: 2 updates: +- package-ecosystem: cargo + directory: "/" + schedule: + interval: weekly + open-pull-requests-limit: 3 + allow: + - dependency-type: direct + - dependency-type: indirect - package-ecosystem: gitsubmodule directory: "/" schedule: From ed929c0cbd8511499b518f738efc51a5a72e8aa3 Mon Sep 17 00:00:00 2001 From: Liu Jiang Date: Tue, 24 Aug 2021 20:15:55 +0800 Subject: [PATCH 084/139] vring: introduce trait VringT Introduce trait VringT, and provide three implementations of it: VringState, VringMutex, VringRwLock. Signed-off-by: Liu Jiang --- src/backend.rs | 14 +- src/event_loop.rs | 9 +- src/handler.rs | 20 ++- src/lib.rs | 8 +- src/vring.rs | 430 ++++++++++++++++++++++++++++++++++++---------- 5 files changed, 366 insertions(+), 115 deletions(-) diff --git a/src/backend.rs b/src/backend.rs index d15629e..5f7ed8a 100644 --- a/src/backend.rs +++ b/src/backend.rs @@ -28,7 +28,7 @@ use vhost::vhost_user::SlaveFsCacheReq; use vm_memory::bitmap::Bitmap; use vmm_sys_util::eventfd::EventFd; -use super::{Vring, GM}; +use super::{VringRwLock, GM}; /// Trait with interior mutability for vhost user backend servers to implement concrete services. /// @@ -107,7 +107,7 @@ pub trait VhostUserBackend: Send + Sync + 'static { &self, device_event: u16, evset: epoll::Events, - vrings: &[Vring>], + vrings: &[VringRwLock>], thread_id: usize, ) -> result::Result; } @@ -186,7 +186,7 @@ pub trait VhostUserBackendMut: Send + Sync + 'static { &mut self, device_event: u16, evset: epoll::Events, - vrings: &[Vring>], + vrings: &[VringRwLock>], thread_id: usize, ) -> result::Result; } @@ -244,7 +244,7 @@ impl, B: Bitmap + 'static> VhostUserBackend for Arc &self, device_event: u16, evset: epoll::Events, - vrings: &[Vring>], + vrings: &[VringRwLock>], thread_id: usize, ) -> Result { self.deref() @@ -305,7 +305,7 @@ impl, B: Bitmap + 'static> VhostUserBackend for Mut &self, device_event: u16, evset: epoll::Events, - vrings: &[Vring>], + vrings: &[VringRwLock>], thread_id: usize, ) -> Result { self.lock() @@ -367,7 +367,7 @@ impl, B: Bitmap + 'static> VhostUserBackend for RwL &self, device_event: u16, evset: epoll::Events, - vrings: &[Vring>], + vrings: &[VringRwLock>], thread_id: usize, ) -> Result { self.write() @@ -463,7 +463,7 @@ pub mod tests { &mut self, _device_event: u16, _evset: Events, - _vrings: &[Vring], + _vrings: &[VringRwLock], _thread_id: usize, ) -> Result { self.events += 1; diff --git a/src/event_loop.rs b/src/event_loop.rs index eafe33b..aa65767 100644 --- a/src/event_loop.rs +++ b/src/event_loop.rs @@ -12,7 +12,8 @@ use std::result; use vm_memory::bitmap::Bitmap; use vmm_sys_util::eventfd::EventFd; -use super::{VhostUserBackend, Vring, GM}; +use super::vring::VringT; +use super::{VhostUserBackend, VringRwLock, GM}; /// Errors related to vring epoll event handling. #[derive(Debug)] @@ -59,7 +60,7 @@ pub type VringEpollResult = std::result::Result; pub struct VringEpollHandler, B: Bitmap + 'static> { epoll_file: File, backend: S, - vrings: Vec>>, + vrings: Vec>>, thread_id: usize, exit_event_fd: Option, exit_event_id: Option, @@ -69,7 +70,7 @@ impl, B: Bitmap + 'static> VringEpollHandler { /// Create a `VringEpollHandler` instance. pub(crate) fn new( backend: S, - vrings: Vec>>, + vrings: Vec>>, thread_id: usize, ) -> VringEpollResult { let epoll_fd = epoll::create(true).map_err(VringEpollError::EpollCreateFd)?; @@ -234,7 +235,7 @@ mod tests { let mem = GuestMemoryAtomic::new( GuestMemoryMmap::<()>::from_ranges(&[(GuestAddress(0x100000), 0x10000)]).unwrap(), ); - let vring = Vring::new(mem, 0x1000); + let vring = VringRwLock::new(mem, 0x1000); let backend = Arc::new(Mutex::new(MockVhostBackend::new())); let handler = VringEpollHandler::new(backend, vec![vring], 0x1).unwrap(); diff --git a/src/handler.rs b/src/handler.rs index 2bdb58d..24e4ee8 100644 --- a/src/handler.rs +++ b/src/handler.rs @@ -15,14 +15,22 @@ use vhost::vhost_user::message::{ VhostUserSingleMemoryRegion, VhostUserVirtioFeatures, VhostUserVringAddrFlags, VhostUserVringState, }; -use vhost::vhost_user::{Error as VhostUserError, Result as VhostUserResult, SlaveFsCacheReq}; +use vhost::vhost_user::{ + Error as VhostUserError, Result as VhostUserResult, SlaveFsCacheReq, + VhostUserSlaveReqHandlerMut, +}; use virtio_bindings::bindings::virtio_ring::VIRTIO_RING_F_EVENT_IDX; use vm_memory::bitmap::Bitmap; use vm_memory::mmap::NewBitmap; -use vm_memory::{FileOffset, GuestAddress, GuestMemoryMmap, GuestRegionMmap}; +use vm_memory::{ + FileOffset, GuestAddress, GuestAddressSpace, GuestMemoryMmap, GuestRegionMmap, MmapRegion, +}; +use super::backend::VhostUserBackend; +use super::event_loop::VringEpollHandler; use super::event_loop::{VringEpollError, VringEpollResult}; -use super::*; +use super::vring::{VringRwLock, VringT}; +use super::GM; const MAX_MEM_SLOTS: u64 = 32; @@ -74,7 +82,7 @@ pub struct VhostUserHandler, B: Bitmap + 'static> { queues_per_thread: Vec, mappings: Vec, atomic_mem: GM, - vrings: Vec>>, + vrings: Vec>>, worker_threads: Vec>>, } @@ -86,7 +94,7 @@ impl + Clone, B: Bitmap + Clone + Send + Sync> VhostUserH let mut vrings = Vec::new(); for _ in 0..num_queues { - let vring = Vring::new(atomic_mem.clone(), max_queue_size as u16); + let vring = VringRwLock::new(atomic_mem.clone(), max_queue_size as u16); vrings.push(vring); } @@ -194,7 +202,7 @@ impl, B: NewBitmap + Clone> VhostUserSlaveReqHandlerMut // been disabled by VHOST_USER_SET_VRING_ENABLE with parameter 0. let vring_enabled = self.acked_features & VhostUserVirtioFeatures::PROTOCOL_FEATURES.bits() == 0; - for vring in self.vrings.iter() { + for vring in self.vrings.iter_mut() { vring.set_enabled(vring_enabled); } diff --git a/src/lib.rs b/src/lib.rs index 1af2241..6d8a271 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -14,12 +14,10 @@ use std::result; use std::sync::{Arc, Mutex}; use std::thread; -use vhost::vhost_user::{ - Error as VhostUserError, Listener, SlaveListener, VhostUserSlaveReqHandlerMut, -}; +use vhost::vhost_user::{Error as VhostUserError, Listener, SlaveListener}; use vm_memory::bitmap::Bitmap; use vm_memory::mmap::NewBitmap; -use vm_memory::{GuestAddressSpace, GuestMemoryAtomic, GuestMemoryMmap, MmapRegion}; +use vm_memory::{GuestMemoryAtomic, GuestMemoryMmap}; use self::handler::VhostUserHandler; @@ -33,7 +31,7 @@ mod handler; pub use self::handler::VhostUserHandlerError; mod vring; -pub use self::vring::{Vring, VringState}; +pub use self::vring::{VringRwLock, VringState}; /// An alias for `GuestMemoryAtomic>` to simplify code. type GM = GuestMemoryAtomic>; diff --git a/src/vring.rs b/src/vring.rs index 31bf4a7..0c91086 100644 --- a/src/vring.rs +++ b/src/vring.rs @@ -7,16 +7,109 @@ use std::fs::File; use std::io; +use std::ops::Deref; use std::os::unix::io::{FromRawFd, IntoRawFd}; use std::result::Result; -use std::sync::{Arc, RwLock, RwLockReadGuard, RwLockWriteGuard}; +use std::sync::{Arc, Mutex, MutexGuard, RwLock, RwLockReadGuard, RwLockWriteGuard}; use virtio_queue::{Error as VirtQueError, Queue}; use vm_memory::{GuestAddress, GuestAddressSpace, GuestMemoryAtomic, GuestMemoryMmap}; use vmm_sys_util::eventfd::EventFd; +/// Struct to hold a shared reference to the underlying `VringState` object. +pub enum VringStateGuard<'a, M: GuestAddressSpace> { + /// A reference to a `VringState` object. + StateObject(&'a VringState), + /// A `MutexGuard` for a `VringState` object. + MutexGuard(MutexGuard<'a, VringState>), + /// A `ReadGuard` for a `VringState` object. + RwLockReadGuard(RwLockReadGuard<'a, VringState>), +} + +impl<'a, M: GuestAddressSpace> Deref for VringStateGuard<'a, M> { + type Target = VringState; + + fn deref(&self) -> &Self::Target { + match self { + VringStateGuard::StateObject(v) => v, + VringStateGuard::MutexGuard(v) => v.deref(), + VringStateGuard::RwLockReadGuard(v) => v.deref(), + } + } +} + +/* +impl<'a, M: GuestAddressSpace> DerefMut for VringStateGuard<'a, M> { + fn deref_mut(&mut self) -> &mut Self::Target { + match self { + VringStateGuard::StateObject(v) => v, + VringStateGuard::MutexGuard(v) => v.deref_mut(), + } + } +} + */ + +pub trait VringT { + /// Create a new instance of Vring. + fn new(mem: M, max_queue_size: u16) -> Self; + + /// Get an immutable reference to the kick event fd. + fn get_ref(&self) -> VringStateGuard; + + /// Add an used descriptor into the used queue. + fn add_used(&mut self, desc_index: u16, len: u32) -> Result<(), VirtQueError>; + + /// Notify the vhost-user master that used descriptors have been put into the used queue. + fn signal_used_queue(&self) -> io::Result<()>; + + /// Enable event notification for queue. + fn enable_notification(&mut self) -> Result; + + /// Disable event notification for queue. + fn disable_notification(&mut self) -> Result<(), VirtQueError>; + + /// Check whether a notification to the guest is needed. + fn needs_notification(&mut self) -> Result; + + /// Set vring enabled state. + fn set_enabled(&mut self, enabled: bool); + + /// Set queue addresses for descriptor table, available ring and used ring. + fn set_queue_info(&mut self, desc_table: u64, avail_ring: u64, used_ring: u64); + + /// Get queue next avail head. + fn queue_next_avail(&self) -> u16; + + /// Set queue next avail head. + fn set_queue_next_avail(&mut self, base: u16); + + /// Set configured queue size. + fn set_queue_size(&mut self, num: u16); + + /// Enable/disable queue event index feature. + fn set_queue_event_idx(&mut self, enabled: bool); + + /// Set queue enabled state. + fn set_queue_ready(&mut self, ready: bool); + + /// Set `EventFd` for kick. + fn set_kick(&mut self, file: Option); + + /// Read event from the kick `EventFd`. + fn read_kick(&self) -> io::Result; + + /// Set `EventFd` for call. + fn set_call(&mut self, file: Option); + + /// Set `EventFd` for err. + fn set_err(&mut self, file: Option); +} + /// Struct to maintain raw state information for a vhost-user queue. -pub struct VringState { +/// +/// This struct maintains all information of a virito queue, and could be used as an `VringT` +/// object for single-threaded context. +pub struct VringState> { queue: Queue, kick: Option, call: Option, @@ -25,6 +118,23 @@ pub struct VringState { } impl VringState { + /// Get the `EventFd` for kick. + pub fn get_kick(&self) -> &Option { + &self.kick + } + + /// Get an immutable reference to the underlying raw `Queue` object. + pub fn get_queue(&self) -> &Queue { + &self.queue + } + + /// Get a mutable reference to the underlying raw `Queue` object. + pub fn get_queue_mut(&mut self) -> &mut Queue { + &mut self.queue + } +} + +impl VringT for VringState { fn new(mem: M, max_queue_size: u16) -> Self { VringState { queue: Queue::new(mem, max_queue_size), @@ -35,110 +145,73 @@ impl VringState { } } - /// Get a mutable reference to the underlying raw `Queue` object. - pub fn get_queue_mut(&mut self) -> &mut Queue { - &mut self.queue + fn get_ref(&self) -> VringStateGuard { + VringStateGuard::StateObject(self) } - /// Get a immutable reference to the kick event fd. - pub fn get_kick(&self) -> &Option { - &self.kick - } -} - -/// Struct to maintain state information and manipulate a vhost-user queue. -#[derive(Clone)] -pub struct Vring> { - state: Arc>>, -} - -impl Vring { - /// Get a immutable guard to the underlying raw `VringState` object. - pub fn get_ref(&self) -> RwLockReadGuard> { - self.state.read().unwrap() + fn add_used(&mut self, desc_index: u16, len: u32) -> Result<(), VirtQueError> { + self.queue.add_used(desc_index, len) } - /// Get a mutable guard to the underlying raw `VringState` object. - pub fn get_mut(&self) -> RwLockWriteGuard> { - self.state.write().unwrap() - } - - /// Add an used descriptor into the used queue. - pub fn add_used(&self, desc_index: u16, len: u32) -> Result<(), VirtQueError> { - self.get_mut().get_queue_mut().add_used(desc_index, len) - } - - /// Notify the vhost-user master that used descriptors have been put into the used queue. - pub fn signal_used_queue(&self) -> io::Result<()> { - if let Some(call) = self.get_ref().call.as_ref() { + fn signal_used_queue(&self) -> io::Result<()> { + if let Some(call) = self.call.as_ref() { call.write(1) } else { Ok(()) } } - /// Enable event notification for queue. - pub fn enable_notification(&self) -> Result { - self.get_mut().get_queue_mut().enable_notification() + fn enable_notification(&mut self) -> Result { + self.queue.enable_notification() } - /// Disable event notification for queue. - pub fn disable_notification(&self) -> Result<(), VirtQueError> { - self.get_mut().get_queue_mut().disable_notification() + fn disable_notification(&mut self) -> Result<(), VirtQueError> { + self.queue.disable_notification() } - /// Check whether a notification to the guest is needed. - pub fn needs_notification(&self) -> Result { - self.get_mut().get_queue_mut().needs_notification() + fn needs_notification(&mut self) -> Result { + self.queue.needs_notification() } - pub(crate) fn new(mem: M, max_queue_size: u16) -> Self { - Vring { - state: Arc::new(RwLock::new(VringState::new(mem, max_queue_size))), - } + fn set_enabled(&mut self, enabled: bool) { + self.enabled = enabled; } - pub(crate) fn set_enabled(&self, enabled: bool) { - self.get_mut().enabled = enabled; + fn set_queue_info(&mut self, desc_table: u64, avail_ring: u64, used_ring: u64) { + self.queue.desc_table = GuestAddress(desc_table); + self.queue.avail_ring = GuestAddress(avail_ring); + self.queue.used_ring = GuestAddress(used_ring); } - pub(crate) fn set_queue_info(&self, desc_table: u64, avail_ring: u64, used_ring: u64) { - let mut state = self.get_mut(); - - state.queue.desc_table = GuestAddress(desc_table); - state.queue.avail_ring = GuestAddress(avail_ring); - state.queue.used_ring = GuestAddress(used_ring); + fn queue_next_avail(&self) -> u16 { + self.queue.next_avail() } - pub(crate) fn queue_next_avail(&self) -> u16 { - self.get_ref().queue.next_avail() + fn set_queue_next_avail(&mut self, base: u16) { + self.queue.set_next_avail(base); } - pub(crate) fn set_queue_next_avail(&self, base: u16) { - self.get_mut().queue.set_next_avail(base); + fn set_queue_size(&mut self, num: u16) { + self.queue.size = num; } - pub(crate) fn set_queue_size(&self, num: u16) { - self.get_mut().queue.size = num; + fn set_queue_event_idx(&mut self, enabled: bool) { + self.queue.set_event_idx(enabled); } - pub(crate) fn set_queue_event_idx(&self, enabled: bool) { - self.get_mut().queue.set_event_idx(enabled); + fn set_queue_ready(&mut self, ready: bool) { + self.queue.ready = ready; } - pub(crate) fn set_queue_ready(&self, ready: bool) { - self.get_mut().queue.ready = ready; - } - - pub(crate) fn set_kick(&self, file: Option) { + fn set_kick(&mut self, file: Option) { // SAFETY: // EventFd requires that it has sole ownership of its fd. So does File, so this is safe. // Ideally, we'd have a generic way to refer to a uniquely-owned fd, such as that proposed // by Rust RFC #3128. - self.get_mut().kick = file.map(|f| unsafe { EventFd::from_raw_fd(f.into_raw_fd()) }); + self.kick = file.map(|f| unsafe { EventFd::from_raw_fd(f.into_raw_fd()) }); } - pub(crate) fn read_kick(&self) -> io::Result { + fn read_kick(&self) -> io::Result { let state = self.get_ref(); if let Some(kick) = &state.kick { @@ -148,14 +221,194 @@ impl Vring { Ok(state.enabled) } - pub(crate) fn set_call(&self, file: Option) { + fn set_call(&mut self, file: Option) { // SAFETY: see comment in set_kick() - self.get_mut().call = file.map(|f| unsafe { EventFd::from_raw_fd(f.into_raw_fd()) }); + self.call = file.map(|f| unsafe { EventFd::from_raw_fd(f.into_raw_fd()) }); } - pub(crate) fn set_err(&self, file: Option) { + fn set_err(&mut self, file: Option) { // SAFETY: see comment in set_kick() - self.get_mut().err = file.map(|f| unsafe { EventFd::from_raw_fd(f.into_raw_fd()) }); + self.err = file.map(|f| unsafe { EventFd::from_raw_fd(f.into_raw_fd()) }); + } +} + +/// A `VringState` object protected by Mutex for multi-threading context. +#[derive(Clone)] +pub struct VringMutex> { + state: Arc>>, +} + +impl VringMutex { + /// Get a mutable guard to the underlying raw `VringState` object. + fn lock(&self) -> MutexGuard> { + self.state.lock().unwrap() + } +} + +impl VringT for VringMutex { + fn new(mem: M, max_queue_size: u16) -> Self { + VringMutex { + state: Arc::new(Mutex::new(VringState::new(mem, max_queue_size))), + } + } + + fn get_ref(&self) -> VringStateGuard { + VringStateGuard::MutexGuard(self.state.lock().unwrap()) + } + + fn add_used(&mut self, desc_index: u16, len: u32) -> Result<(), VirtQueError> { + self.lock().add_used(desc_index, len) + } + + fn signal_used_queue(&self) -> io::Result<()> { + self.get_ref().signal_used_queue() + } + + fn enable_notification(&mut self) -> Result { + self.lock().enable_notification() + } + + fn disable_notification(&mut self) -> Result<(), VirtQueError> { + self.lock().disable_notification() + } + + fn needs_notification(&mut self) -> Result { + self.lock().needs_notification() + } + + fn set_enabled(&mut self, enabled: bool) { + self.lock().set_enabled(enabled) + } + + fn set_queue_info(&mut self, desc_table: u64, avail_ring: u64, used_ring: u64) { + self.lock() + .set_queue_info(desc_table, avail_ring, used_ring) + } + + fn queue_next_avail(&self) -> u16 { + self.get_ref().queue_next_avail() + } + + fn set_queue_next_avail(&mut self, base: u16) { + self.lock().set_queue_next_avail(base) + } + + fn set_queue_size(&mut self, num: u16) { + self.lock().set_queue_size(num); + } + + fn set_queue_event_idx(&mut self, enabled: bool) { + self.lock().set_queue_event_idx(enabled); + } + + fn set_queue_ready(&mut self, ready: bool) { + self.lock().set_queue_ready(ready); + } + + fn set_kick(&mut self, file: Option) { + self.lock().set_kick(file); + } + + fn read_kick(&self) -> io::Result { + self.get_ref().read_kick() + } + + fn set_call(&mut self, file: Option) { + self.lock().set_call(file) + } + + fn set_err(&mut self, file: Option) { + self.lock().set_err(file) + } +} + +/// A `VringState` object protected by RwLock for multi-threading context. +#[derive(Clone)] +pub struct VringRwLock> { + state: Arc>>, +} + +impl VringRwLock { + /// Get a mutable guard to the underlying raw `VringState` object. + fn write_lock(&self) -> RwLockWriteGuard> { + self.state.write().unwrap() + } +} + +impl VringT for VringRwLock { + fn new(mem: M, max_queue_size: u16) -> Self { + VringRwLock { + state: Arc::new(RwLock::new(VringState::new(mem, max_queue_size))), + } + } + + fn get_ref(&self) -> VringStateGuard { + VringStateGuard::RwLockReadGuard(self.state.read().unwrap()) + } + + fn add_used(&mut self, desc_index: u16, len: u32) -> Result<(), VirtQueError> { + self.write_lock().add_used(desc_index, len) + } + + fn signal_used_queue(&self) -> io::Result<()> { + self.get_ref().signal_used_queue() + } + + fn enable_notification(&mut self) -> Result { + self.write_lock().enable_notification() + } + + fn disable_notification(&mut self) -> Result<(), VirtQueError> { + self.write_lock().disable_notification() + } + + fn needs_notification(&mut self) -> Result { + self.write_lock().needs_notification() + } + + fn set_enabled(&mut self, enabled: bool) { + self.write_lock().set_enabled(enabled) + } + + fn set_queue_info(&mut self, desc_table: u64, avail_ring: u64, used_ring: u64) { + self.write_lock() + .set_queue_info(desc_table, avail_ring, used_ring) + } + + fn queue_next_avail(&self) -> u16 { + self.get_ref().queue_next_avail() + } + + fn set_queue_next_avail(&mut self, base: u16) { + self.write_lock().set_queue_next_avail(base) + } + + fn set_queue_size(&mut self, num: u16) { + self.write_lock().set_queue_size(num); + } + + fn set_queue_event_idx(&mut self, enabled: bool) { + self.write_lock().set_queue_event_idx(enabled); + } + + fn set_queue_ready(&mut self, ready: bool) { + self.write_lock().set_queue_ready(ready); + } + + fn set_kick(&mut self, file: Option) { + self.write_lock().set_kick(file); + } + + fn read_kick(&self) -> io::Result { + self.get_ref().read_kick() + } + + fn set_call(&mut self, file: Option) { + self.write_lock().set_call(file) + } + + fn set_err(&mut self, file: Option) { + self.write_lock().set_err(file) } } @@ -172,42 +425,33 @@ mod tests { GuestMemoryMmap::::from_ranges(&[(GuestAddress(0x100000), 0x10000)]) .unwrap(), ); - let vring = Vring::new(mem, 0x1000); + let mut vring = VringMutex::new(mem, 0x1000); assert!(vring.get_ref().get_kick().is_none()); assert_eq!(vring.get_ref().enabled, false); - assert_eq!(vring.get_mut().get_queue_mut().ready, false); - assert_eq!(vring.get_mut().get_queue_mut().event_idx_enabled, false); + assert_eq!(vring.lock().queue.ready, false); + assert_eq!(vring.lock().queue.event_idx_enabled, false); vring.set_enabled(true); assert_eq!(vring.get_ref().enabled, true); vring.set_queue_info(0x100100, 0x100200, 0x100300); - assert_eq!( - vring.get_mut().get_queue_mut().desc_table, - GuestAddress(0x100100) - ); - assert_eq!( - vring.get_mut().get_queue_mut().avail_ring, - GuestAddress(0x100200) - ); - assert_eq!( - vring.get_mut().get_queue_mut().used_ring, - GuestAddress(0x100300) - ); + assert_eq!(vring.lock().get_queue().desc_table, GuestAddress(0x100100)); + assert_eq!(vring.lock().get_queue().avail_ring, GuestAddress(0x100200)); + assert_eq!(vring.lock().get_queue().used_ring, GuestAddress(0x100300)); assert_eq!(vring.queue_next_avail(), 0); vring.set_queue_next_avail(0x20); assert_eq!(vring.queue_next_avail(), 0x20); vring.set_queue_size(0x200); - assert_eq!(vring.get_mut().get_queue_mut().size, 0x200); + assert_eq!(vring.lock().queue.size, 0x200); vring.set_queue_event_idx(true); - assert_eq!(vring.get_mut().get_queue_mut().event_idx_enabled, true); + assert_eq!(vring.lock().queue.event_idx_enabled, true); vring.set_queue_ready(true); - assert_eq!(vring.get_mut().get_queue_mut().ready, true); + assert_eq!(vring.lock().queue.ready, true); } #[test] @@ -215,7 +459,7 @@ mod tests { let mem = GuestMemoryAtomic::new( GuestMemoryMmap::<()>::from_ranges(&[(GuestAddress(0x100000), 0x10000)]).unwrap(), ); - let vring = Vring::new(mem, 0x1000); + let mut vring = VringMutex::new(mem, 0x1000); vring.set_enabled(true); assert_eq!(vring.get_ref().enabled, true); From e80fab8d98f19029d2e24e09946ab08991b62a8b Mon Sep 17 00:00:00 2001 From: Liu Jiang Date: Wed, 1 Sep 2021 11:19:39 +0800 Subject: [PATCH 085/139] vring: add generic parameter for VringT Enhance VhostUserBackend, VhostUserBackendMut, VringEpollHandler, VhostUserHandler and VhostUserDaemon to support generic type `V: VringT', so clients could choose different VringT implementations. Signed-off-by: Liu Jiang --- src/backend.rs | 46 ++++++++++++++++++++++++++++++++++------------ src/event_loop.rs | 30 +++++++++++++++++++++--------- src/handler.rs | 45 ++++++++++++++++++++++++++++++++++----------- src/lib.rs | 20 +++++++++++++++----- 4 files changed, 104 insertions(+), 37 deletions(-) diff --git a/src/backend.rs b/src/backend.rs index 5f7ed8a..c4371f9 100644 --- a/src/backend.rs +++ b/src/backend.rs @@ -28,13 +28,18 @@ use vhost::vhost_user::SlaveFsCacheReq; use vm_memory::bitmap::Bitmap; use vmm_sys_util::eventfd::EventFd; -use super::{VringRwLock, GM}; +use super::vring::VringT; +use super::GM; /// Trait with interior mutability for vhost user backend servers to implement concrete services. /// /// To support multi-threading and asynchronous IO, we enforce `the Send + Sync + 'static`. /// So there's no plan for support of "Rc" and "RefCell". -pub trait VhostUserBackend: Send + Sync + 'static { +pub trait VhostUserBackend: Send + Sync + 'static +where + V: VringT>, + B: Bitmap + 'static, +{ /// Get number of queues supported. fn num_queues(&self) -> usize; @@ -107,13 +112,17 @@ pub trait VhostUserBackend: Send + Sync + 'static { &self, device_event: u16, evset: epoll::Events, - vrings: &[VringRwLock>], + vrings: &[V], thread_id: usize, ) -> result::Result; } /// Trait without interior mutability for vhost user backend servers to implement concrete services. -pub trait VhostUserBackendMut: Send + Sync + 'static { +pub trait VhostUserBackendMut: Send + Sync + 'static +where + V: VringT>, + B: Bitmap + 'static, +{ /// Get number of queues supported. fn num_queues(&self) -> usize; @@ -186,12 +195,16 @@ pub trait VhostUserBackendMut: Send + Sync + 'static { &mut self, device_event: u16, evset: epoll::Events, - vrings: &[VringRwLock>], + vrings: &[V], thread_id: usize, ) -> result::Result; } -impl, B: Bitmap + 'static> VhostUserBackend for Arc { +impl, V, B> VhostUserBackend for Arc +where + V: VringT>, + B: Bitmap + 'static, +{ fn num_queues(&self) -> usize { self.deref().num_queues() } @@ -244,7 +257,7 @@ impl, B: Bitmap + 'static> VhostUserBackend for Arc &self, device_event: u16, evset: epoll::Events, - vrings: &[VringRwLock>], + vrings: &[V], thread_id: usize, ) -> Result { self.deref() @@ -252,7 +265,11 @@ impl, B: Bitmap + 'static> VhostUserBackend for Arc } } -impl, B: Bitmap + 'static> VhostUserBackend for Mutex { +impl, V, B> VhostUserBackend for Mutex +where + V: VringT>, + B: Bitmap + 'static, +{ fn num_queues(&self) -> usize { self.lock().unwrap().num_queues() } @@ -305,7 +322,7 @@ impl, B: Bitmap + 'static> VhostUserBackend for Mut &self, device_event: u16, evset: epoll::Events, - vrings: &[VringRwLock>], + vrings: &[V], thread_id: usize, ) -> Result { self.lock() @@ -314,7 +331,11 @@ impl, B: Bitmap + 'static> VhostUserBackend for Mut } } -impl, B: Bitmap + 'static> VhostUserBackend for RwLock { +impl, V, B> VhostUserBackend for RwLock +where + V: VringT>, + B: Bitmap + 'static, +{ fn num_queues(&self) -> usize { self.read().unwrap().num_queues() } @@ -367,7 +388,7 @@ impl, B: Bitmap + 'static> VhostUserBackend for RwL &self, device_event: u16, evset: epoll::Events, - vrings: &[VringRwLock>], + vrings: &[V], thread_id: usize, ) -> Result { self.write() @@ -379,6 +400,7 @@ impl, B: Bitmap + 'static> VhostUserBackend for RwL #[cfg(test)] pub mod tests { use super::*; + use crate::VringRwLock; use epoll::Events; use std::io::Error; use std::sync::Mutex; @@ -400,7 +422,7 @@ pub mod tests { } } - impl VhostUserBackendMut<()> for MockVhostBackend { + impl VhostUserBackendMut for MockVhostBackend { fn num_queues(&self) -> usize { 2 } diff --git a/src/event_loop.rs b/src/event_loop.rs index aa65767..869df85 100644 --- a/src/event_loop.rs +++ b/src/event_loop.rs @@ -6,14 +6,16 @@ use std::fmt::{Display, Formatter}; use std::fs::File; use std::io; +use std::marker::PhantomData; use std::os::unix::io::{AsRawFd, FromRawFd, RawFd}; use std::result; use vm_memory::bitmap::Bitmap; use vmm_sys_util::eventfd::EventFd; +use super::backend::VhostUserBackend; use super::vring::VringT; -use super::{VhostUserBackend, VringRwLock, GM}; +use super::GM; /// Errors related to vring epoll event handling. #[derive(Debug)] @@ -57,22 +59,29 @@ pub type VringEpollResult = std::result::Result; /// - add file descriptors to be monitored by the epoll fd /// - remove registered file descriptors from the epoll fd /// - run the event loop to handle pending events on the epoll fd -pub struct VringEpollHandler, B: Bitmap + 'static> { +pub struct VringEpollHandler +where + S: VhostUserBackend, + V: VringT>, + B: Bitmap + 'static, +{ epoll_file: File, backend: S, - vrings: Vec>>, + vrings: Vec, thread_id: usize, exit_event_fd: Option, exit_event_id: Option, + phantom: PhantomData, } -impl, B: Bitmap + 'static> VringEpollHandler { +impl VringEpollHandler +where + S: VhostUserBackend, + V: VringT>, + B: Bitmap + 'static, +{ /// Create a `VringEpollHandler` instance. - pub(crate) fn new( - backend: S, - vrings: Vec>>, - thread_id: usize, - ) -> VringEpollResult { + pub(crate) fn new(backend: S, vrings: Vec, thread_id: usize) -> VringEpollResult { let epoll_fd = epoll::create(true).map_err(VringEpollError::EpollCreateFd)?; let epoll_file = unsafe { File::from_raw_fd(epoll_fd) }; @@ -93,6 +102,7 @@ impl, B: Bitmap + 'static> VringEpollHandler { thread_id, exit_event_fd: Some(exit_event_fd), exit_event_id: Some(exit_event_id), + phantom: PhantomData, } } None => VringEpollHandler { @@ -102,6 +112,7 @@ impl, B: Bitmap + 'static> VringEpollHandler { thread_id, exit_event_fd: None, exit_event_id: None, + phantom: PhantomData, }, }; @@ -225,6 +236,7 @@ impl, B: Bitmap + 'static> VringEpollHandler { #[cfg(test)] mod tests { use super::super::backend::tests::MockVhostBackend; + use super::super::vring::VringRwLock; use super::*; use std::sync::{Arc, Mutex}; use vm_memory::{GuestAddress, GuestMemoryAtomic, GuestMemoryMmap}; diff --git a/src/handler.rs b/src/handler.rs index 24e4ee8..dca7f19 100644 --- a/src/handler.rs +++ b/src/handler.rs @@ -29,7 +29,7 @@ use vm_memory::{ use super::backend::VhostUserBackend; use super::event_loop::VringEpollHandler; use super::event_loop::{VringEpollError, VringEpollResult}; -use super::vring::{VringRwLock, VringT}; +use super::vring::VringT; use super::GM; const MAX_MEM_SLOTS: u64 = 32; @@ -70,9 +70,14 @@ struct AddrMapping { gpa_base: u64, } -pub struct VhostUserHandler, B: Bitmap + 'static> { +pub struct VhostUserHandler +where + S: VhostUserBackend, + V: VringT>, + B: Bitmap + 'static, +{ backend: S, - handlers: Vec>>, + handlers: Vec>>, owned: bool, features_acked: bool, acked_features: u64, @@ -82,11 +87,16 @@ pub struct VhostUserHandler, B: Bitmap + 'static> { queues_per_thread: Vec, mappings: Vec, atomic_mem: GM, - vrings: Vec>>, + vrings: Vec, worker_threads: Vec>>, } -impl + Clone, B: Bitmap + Clone + Send + Sync> VhostUserHandler { +impl VhostUserHandler +where + S: VhostUserBackend + Clone, + V: VringT> + Clone + Send + Sync + 'static, + B: Bitmap + Clone + Send + Sync, +{ pub(crate) fn new(backend: S, atomic_mem: GM) -> VhostUserHandlerResult { let num_queues = backend.num_queues(); let max_queue_size = backend.max_queue_size(); @@ -94,7 +104,7 @@ impl + Clone, B: Bitmap + Clone + Send + Sync> VhostUserH let mut vrings = Vec::new(); for _ in 0..num_queues { - let vring = VringRwLock::new(atomic_mem.clone(), max_queue_size as u16); + let vring = V::new(atomic_mem.clone(), max_queue_size as u16); vrings.push(vring); } @@ -140,8 +150,13 @@ impl + Clone, B: Bitmap + Clone + Send + Sync> VhostUserH } } -impl, B: Bitmap> VhostUserHandler { - pub(crate) fn get_epoll_handlers(&self) -> Vec>> { +impl VhostUserHandler +where + S: VhostUserBackend, + V: VringT>, + B: Bitmap, +{ + pub(crate) fn get_epoll_handlers(&self) -> Vec>> { self.handlers.clone() } @@ -162,8 +177,11 @@ impl, B: Bitmap> VhostUserHandler { } } -impl, B: NewBitmap + Clone> VhostUserSlaveReqHandlerMut - for VhostUserHandler +impl VhostUserSlaveReqHandlerMut for VhostUserHandler +where + S: VhostUserBackend, + V: VringT>, + B: NewBitmap + Clone, { fn set_owner(&mut self) -> VhostUserResult<()> { if self.owned { @@ -538,7 +556,12 @@ impl, B: NewBitmap + Clone> VhostUserSlaveReqHandlerMut } } -impl, B: Bitmap> Drop for VhostUserHandler { +impl Drop for VhostUserHandler +where + S: VhostUserBackend, + V: VringT>, + B: Bitmap, +{ fn drop(&mut self) { // Signal all working threads to exit. self.send_exit_event(); diff --git a/src/lib.rs b/src/lib.rs index 6d8a271..db152a6 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -31,7 +31,7 @@ mod handler; pub use self::handler::VhostUserHandlerError; mod vring; -pub use self::vring::{VringRwLock, VringState}; +pub use self::vring::{VringMutex, VringRwLock, VringState, VringT}; /// An alias for `GuestMemoryAtomic>` to simplify code. type GM = GuestMemoryAtomic>; @@ -73,13 +73,23 @@ pub type Result = result::Result; /// /// This structure is the public API the backend is allowed to interact with in order to run /// a fully functional vhost-user daemon. -pub struct VhostUserDaemon, B: Bitmap + 'static = ()> { +pub struct VhostUserDaemon +where + S: VhostUserBackend, + V: VringT> + Clone + Send + Sync + 'static, + B: Bitmap + 'static, +{ name: String, - handler: Arc>>, + handler: Arc>>, main_thread: Option>>, } -impl + Clone, B: NewBitmap + Clone + Send + Sync> VhostUserDaemon { +impl VhostUserDaemon +where + S: VhostUserBackend + Clone, + V: VringT> + Clone + Send + Sync + 'static, + B: NewBitmap + Clone + Send + Sync, +{ /// Create the daemon instance, providing the backend implementation of `VhostUserBackend`. /// /// Under the hood, this will start a dedicated thread responsible for listening onto @@ -144,7 +154,7 @@ impl + Clone, B: NewBitmap + Clone + Send + Sync> VhostUs /// /// This is necessary to perform further actions like registering and unregistering some extra /// event file descriptors. - pub fn get_epoll_handlers(&self) -> Vec>> { + pub fn get_epoll_handlers(&self) -> Vec>> { self.handler.lock().unwrap().get_epoll_handlers() } } From 5998cea89feadf14756fa73308fc460848db4e4a Mon Sep 17 00:00:00 2001 From: Liu Jiang Date: Wed, 1 Sep 2021 12:25:24 +0800 Subject: [PATCH 086/139] vring: add VringT::get_mut() Add VringT::get_mut() to get exclusive reference to underlying VringState object, so the clients could avoid repeatedly lock/unlock when using VringMutex and VringRwLock. Signed-off-by: Liu Jiang --- src/lib.rs | 4 +++- src/vring.rs | 50 +++++++++++++++++++++++++++++++++++++++++++------- 2 files changed, 46 insertions(+), 8 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index db152a6..403efcc 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -31,7 +31,9 @@ mod handler; pub use self::handler::VhostUserHandlerError; mod vring; -pub use self::vring::{VringMutex, VringRwLock, VringState, VringT}; +pub use self::vring::{ + VringMutex, VringRwLock, VringState, VringStateGuard, VringStateMutGuard, VringT, +}; /// An alias for `GuestMemoryAtomic>` to simplify code. type GM = GuestMemoryAtomic>; diff --git a/src/vring.rs b/src/vring.rs index 0c91086..9f9d0db 100644 --- a/src/vring.rs +++ b/src/vring.rs @@ -7,7 +7,7 @@ use std::fs::File; use std::io; -use std::ops::Deref; +use std::ops::{Deref, DerefMut}; use std::os::unix::io::{FromRawFd, IntoRawFd}; use std::result::Result; use std::sync::{Arc, Mutex, MutexGuard, RwLock, RwLockReadGuard, RwLockWriteGuard}; @@ -38,16 +38,37 @@ impl<'a, M: GuestAddressSpace> Deref for VringStateGuard<'a, M> { } } -/* -impl<'a, M: GuestAddressSpace> DerefMut for VringStateGuard<'a, M> { - fn deref_mut(&mut self) -> &mut Self::Target { +/// Struct to hold an exclusive reference to the underlying `VringState` object. +pub enum VringStateMutGuard<'a, M: GuestAddressSpace> { + /// A reference to a `VringState` object. + StateObject(&'a mut VringState), + /// A `MutexGuard` for a `VringState` object. + MutexGuard(MutexGuard<'a, VringState>), + /// A `WriteGuard` for a `VringState` object. + RwLockWriteGuard(RwLockWriteGuard<'a, VringState>), +} + +impl<'a, M: GuestAddressSpace> Deref for VringStateMutGuard<'a, M> { + type Target = VringState; + + fn deref(&self) -> &Self::Target { match self { - VringStateGuard::StateObject(v) => v, - VringStateGuard::MutexGuard(v) => v.deref_mut(), + VringStateMutGuard::StateObject(v) => v, + VringStateMutGuard::MutexGuard(v) => v.deref(), + VringStateMutGuard::RwLockWriteGuard(v) => v.deref(), + } + } +} + +impl<'a, M: GuestAddressSpace> DerefMut for VringStateMutGuard<'a, M> { + fn deref_mut(&mut self) -> &mut Self::Target { + match self { + VringStateMutGuard::StateObject(v) => v, + VringStateMutGuard::MutexGuard(v) => v.deref_mut(), + VringStateMutGuard::RwLockWriteGuard(v) => v.deref_mut(), } } } - */ pub trait VringT { /// Create a new instance of Vring. @@ -56,6 +77,9 @@ pub trait VringT { /// Get an immutable reference to the kick event fd. fn get_ref(&self) -> VringStateGuard; + /// Get a mutable reference to the kick event fd. + fn get_mut(&mut self) -> VringStateMutGuard; + /// Add an used descriptor into the used queue. fn add_used(&mut self, desc_index: u16, len: u32) -> Result<(), VirtQueError>; @@ -149,6 +173,10 @@ impl VringT for VringState { VringStateGuard::StateObject(self) } + fn get_mut(&mut self) -> VringStateMutGuard { + VringStateMutGuard::StateObject(self) + } + fn add_used(&mut self, desc_index: u16, len: u32) -> Result<(), VirtQueError> { self.queue.add_used(desc_index, len) } @@ -256,6 +284,10 @@ impl VringT for VringMutex { VringStateGuard::MutexGuard(self.state.lock().unwrap()) } + fn get_mut(&mut self) -> VringStateMutGuard { + VringStateMutGuard::MutexGuard(self.lock()) + } + fn add_used(&mut self, desc_index: u16, len: u32) -> Result<(), VirtQueError> { self.lock().add_used(desc_index, len) } @@ -346,6 +378,10 @@ impl VringT for VringRwLock { VringStateGuard::RwLockReadGuard(self.state.read().unwrap()) } + fn get_mut(&mut self) -> VringStateMutGuard { + VringStateMutGuard::RwLockWriteGuard(self.write_lock()) + } + fn add_used(&mut self, desc_index: u16, len: u32) -> Result<(), VirtQueError> { self.write_lock().add_used(desc_index, len) } From e8beb233b5a22c45ab6c475200382eab884fbcdf Mon Sep 17 00:00:00 2001 From: Liu Jiang Date: Wed, 1 Sep 2021 13:50:27 +0800 Subject: [PATCH 087/139] epoll: refine the way to manage event id Refine the way to manage epoll event id and simplify interfaces: - Change VhostUserBackend::exit_event() to return Option instead of Option<(EventFd, u16)>. - Delete VringEpollHandler::exit_event_id. - Add VringEpollHandler::register_event/unregister_event for internal use. - Make VringEpollHandler::register_listener/unregister_listener() for external users only, and 'data` range [0..backend.num_queues()] is reserved for queues and exit event. Signed-off-by: Liu Jiang --- coverage_config_x86_64.json | 2 +- src/backend.rs | 14 ++++----- src/event_loop.rs | 61 ++++++++++++++++++++++++++++--------- src/handler.rs | 8 ++--- 4 files changed, 57 insertions(+), 28 deletions(-) diff --git a/coverage_config_x86_64.json b/coverage_config_x86_64.json index 0036d98..f35323c 100644 --- a/coverage_config_x86_64.json +++ b/coverage_config_x86_64.json @@ -1,5 +1,5 @@ { - "coverage_score": 77.4, + "coverage_score": 79.1, "exclude_path": "", "crate_features": "" } diff --git a/src/backend.rs b/src/backend.rs index c4371f9..6fb19dd 100644 --- a/src/backend.rs +++ b/src/backend.rs @@ -99,7 +99,7 @@ where /// If an (`EventFd`, `token`) pair is returned, the returned `EventFd` will be monitored for IO /// events by using epoll with the specified `token`. When the returned EventFd is written to, /// the worker thread will exit. - fn exit_event(&self, _thread_index: usize) -> Option<(EventFd, u16)> { + fn exit_event(&self, _thread_index: usize) -> Option { None } @@ -182,7 +182,7 @@ where /// If an (`EventFd`, `token`) pair is returned, the returned `EventFd` will be monitored for IO /// events by using epoll with the specified `token`. When the returned EventFd is written to, /// the worker thread will exit. - fn exit_event(&self, _thread_index: usize) -> Option<(EventFd, u16)> { + fn exit_event(&self, _thread_index: usize) -> Option { None } @@ -249,7 +249,7 @@ where self.deref().queues_per_thread() } - fn exit_event(&self, thread_index: usize) -> Option<(EventFd, u16)> { + fn exit_event(&self, thread_index: usize) -> Option { self.deref().exit_event(thread_index) } @@ -314,7 +314,7 @@ where self.lock().unwrap().queues_per_thread() } - fn exit_event(&self, thread_index: usize) -> Option<(EventFd, u16)> { + fn exit_event(&self, thread_index: usize) -> Option { self.lock().unwrap().exit_event(thread_index) } @@ -380,7 +380,7 @@ where self.read().unwrap().queues_per_thread() } - fn exit_event(&self, thread_index: usize) -> Option<(EventFd, u16)> { + fn exit_event(&self, thread_index: usize) -> Option { self.read().unwrap().exit_event(thread_index) } @@ -475,10 +475,10 @@ pub mod tests { vec![1, 1] } - fn exit_event(&self, _thread_index: usize) -> Option<(EventFd, u16)> { + fn exit_event(&self, _thread_index: usize) -> Option { let event_fd = EventFd::new(0).unwrap(); - Some((event_fd, 0x100)) + Some(event_fd) } fn handle_event( diff --git a/src/event_loop.rs b/src/event_loop.rs index 869df85..6a70158 100644 --- a/src/event_loop.rs +++ b/src/event_loop.rs @@ -70,7 +70,6 @@ where vrings: Vec, thread_id: usize, exit_event_fd: Option, - exit_event_id: Option, phantom: PhantomData, } @@ -86,12 +85,13 @@ where let epoll_file = unsafe { File::from_raw_fd(epoll_fd) }; let handler = match backend.exit_event(thread_id) { - Some((exit_event_fd, exit_event_id)) => { + Some(exit_event_fd) => { + let id = backend.num_queues(); epoll::ctl( epoll_file.as_raw_fd(), epoll::ControlOptions::EPOLL_CTL_ADD, exit_event_fd.as_raw_fd(), - epoll::Event::new(epoll::Events::EPOLLIN, u64::from(exit_event_id)), + epoll::Event::new(epoll::Events::EPOLLIN, id as u64), ) .map_err(VringEpollError::RegisterExitEvent)?; @@ -101,7 +101,6 @@ where vrings, thread_id, exit_event_fd: Some(exit_event_fd), - exit_event_id: Some(exit_event_id), phantom: PhantomData, } } @@ -111,7 +110,6 @@ where vrings, thread_id, exit_event_fd: None, - exit_event_id: None, phantom: PhantomData, }, }; @@ -135,6 +133,38 @@ where fd: RawFd, ev_type: epoll::Events, data: u64, + ) -> result::Result<(), io::Error> { + // `data` range [0...num_queues] is reserved for queues and exit event. + if data <= self.backend.num_queues() as u64 { + Err(io::Error::from_raw_os_error(libc::EINVAL)) + } else { + self.register_event(fd, ev_type, data) + } + } + + /// Unregister an event from the epoll fd. + /// + /// If the event is triggered after this function has been called, the event will be silently + /// dropped. + pub fn unregister_listener( + &self, + fd: RawFd, + ev_type: epoll::Events, + data: u64, + ) -> result::Result<(), io::Error> { + // `data` range [0...num_queues] is reserved for queues and exit event. + if data <= self.backend.num_queues() as u64 { + Err(io::Error::from_raw_os_error(libc::EINVAL)) + } else { + self.unregister_event(fd, ev_type, data) + } + } + + pub(crate) fn register_event( + &self, + fd: RawFd, + ev_type: epoll::Events, + data: u64, ) -> result::Result<(), io::Error> { epoll::ctl( self.epoll_file.as_raw_fd(), @@ -144,11 +174,7 @@ where ) } - /// Unregister an event from the epoll fd. - /// - /// If the event is triggered after this function has been called, the event will be silently - /// dropped. - pub fn unregister_listener( + pub(crate) fn unregister_event( &self, fd: RawFd, ev_type: epoll::Events, @@ -211,7 +237,7 @@ where } fn handle_event(&self, device_event: u16, evset: epoll::Events) -> VringEpollResult { - if self.exit_event_id == Some(device_event) { + if self.exit_event_fd.is_some() && device_event as usize == self.backend.num_queues() { return Ok(true); } @@ -251,21 +277,28 @@ mod tests { let backend = Arc::new(Mutex::new(MockVhostBackend::new())); let handler = VringEpollHandler::new(backend, vec![vring], 0x1).unwrap(); - assert!(handler.exit_event_id.is_some()); let eventfd = EventFd::new(0).unwrap(); handler - .register_listener(eventfd.as_raw_fd(), epoll::Events::EPOLLIN, 1) + .register_listener(eventfd.as_raw_fd(), epoll::Events::EPOLLIN, 3) .unwrap(); // Register an already registered fd. + handler + .register_listener(eventfd.as_raw_fd(), epoll::Events::EPOLLIN, 3) + .unwrap_err(); + // Register an invalid data. handler .register_listener(eventfd.as_raw_fd(), epoll::Events::EPOLLIN, 1) .unwrap_err(); handler - .unregister_listener(eventfd.as_raw_fd(), epoll::Events::EPOLLIN, 1) + .unregister_listener(eventfd.as_raw_fd(), epoll::Events::EPOLLIN, 3) .unwrap(); // unregister an already unregistered fd. + handler + .unregister_listener(eventfd.as_raw_fd(), epoll::Events::EPOLLIN, 3) + .unwrap_err(); + // unregister an invalid data. handler .unregister_listener(eventfd.as_raw_fd(), epoll::Events::EPOLLIN, 1) .unwrap_err(); diff --git a/src/handler.rs b/src/handler.rs index dca7f19..672785b 100644 --- a/src/handler.rs +++ b/src/handler.rs @@ -350,7 +350,7 @@ where if shifted_queues_mask & 1u64 == 1u64 { let evt_idx = queues_mask.count_ones() - shifted_queues_mask.count_ones(); self.handlers[thread_index] - .unregister_listener( + .unregister_event( fd.as_raw_fd(), epoll::Events::EPOLLIN, u64::from(evt_idx), @@ -389,11 +389,7 @@ where if shifted_queues_mask & 1u64 == 1u64 { let evt_idx = queues_mask.count_ones() - shifted_queues_mask.count_ones(); self.handlers[thread_index] - .register_listener( - fd.as_raw_fd(), - epoll::Events::EPOLLIN, - u64::from(evt_idx), - ) + .register_event(fd.as_raw_fd(), epoll::Events::EPOLLIN, u64::from(evt_idx)) .map_err(VhostUserError::ReqHandlerError)?; break; } From f2e38e294a3a7f970ec87fa22ddc252498a60aff Mon Sep 17 00:00:00 2001 From: Liu Jiang Date: Wed, 8 Sep 2021 20:12:32 +0800 Subject: [PATCH 088/139] vring: do not take &mut self for VringT Backend::handle_event() takes an argument of `vrings: &[V]` with `V: VringT`, so methods of VringT should not take `&mut self`. Signed-off-by: Liu Jiang --- coverage_config_x86_64.json | 2 +- src/vring.rs | 168 ++++++++++++++++++------------------ 2 files changed, 84 insertions(+), 86 deletions(-) diff --git a/coverage_config_x86_64.json b/coverage_config_x86_64.json index f35323c..3c3f96d 100644 --- a/coverage_config_x86_64.json +++ b/coverage_config_x86_64.json @@ -1,5 +1,5 @@ { - "coverage_score": 79.1, + "coverage_score": 78.8, "exclude_path": "", "crate_features": "" } diff --git a/src/vring.rs b/src/vring.rs index 9f9d0db..3761e7e 100644 --- a/src/vring.rs +++ b/src/vring.rs @@ -18,8 +18,6 @@ use vmm_sys_util::eventfd::EventFd; /// Struct to hold a shared reference to the underlying `VringState` object. pub enum VringStateGuard<'a, M: GuestAddressSpace> { - /// A reference to a `VringState` object. - StateObject(&'a VringState), /// A `MutexGuard` for a `VringState` object. MutexGuard(MutexGuard<'a, VringState>), /// A `ReadGuard` for a `VringState` object. @@ -31,7 +29,6 @@ impl<'a, M: GuestAddressSpace> Deref for VringStateGuard<'a, M> { fn deref(&self) -> &Self::Target { match self { - VringStateGuard::StateObject(v) => v, VringStateGuard::MutexGuard(v) => v.deref(), VringStateGuard::RwLockReadGuard(v) => v.deref(), } @@ -40,8 +37,6 @@ impl<'a, M: GuestAddressSpace> Deref for VringStateGuard<'a, M> { /// Struct to hold an exclusive reference to the underlying `VringState` object. pub enum VringStateMutGuard<'a, M: GuestAddressSpace> { - /// A reference to a `VringState` object. - StateObject(&'a mut VringState), /// A `MutexGuard` for a `VringState` object. MutexGuard(MutexGuard<'a, VringState>), /// A `WriteGuard` for a `VringState` object. @@ -53,7 +48,6 @@ impl<'a, M: GuestAddressSpace> Deref for VringStateMutGuard<'a, M> { fn deref(&self) -> &Self::Target { match self { - VringStateMutGuard::StateObject(v) => v, VringStateMutGuard::MutexGuard(v) => v.deref(), VringStateMutGuard::RwLockWriteGuard(v) => v.deref(), } @@ -63,7 +57,6 @@ impl<'a, M: GuestAddressSpace> Deref for VringStateMutGuard<'a, M> { impl<'a, M: GuestAddressSpace> DerefMut for VringStateMutGuard<'a, M> { fn deref_mut(&mut self) -> &mut Self::Target { match self { - VringStateMutGuard::StateObject(v) => v, VringStateMutGuard::MutexGuard(v) => v.deref_mut(), VringStateMutGuard::RwLockWriteGuard(v) => v.deref_mut(), } @@ -78,55 +71,55 @@ pub trait VringT { fn get_ref(&self) -> VringStateGuard; /// Get a mutable reference to the kick event fd. - fn get_mut(&mut self) -> VringStateMutGuard; + fn get_mut(&self) -> VringStateMutGuard; /// Add an used descriptor into the used queue. - fn add_used(&mut self, desc_index: u16, len: u32) -> Result<(), VirtQueError>; + fn add_used(&self, desc_index: u16, len: u32) -> Result<(), VirtQueError>; /// Notify the vhost-user master that used descriptors have been put into the used queue. fn signal_used_queue(&self) -> io::Result<()>; /// Enable event notification for queue. - fn enable_notification(&mut self) -> Result; + fn enable_notification(&self) -> Result; /// Disable event notification for queue. - fn disable_notification(&mut self) -> Result<(), VirtQueError>; + fn disable_notification(&self) -> Result<(), VirtQueError>; /// Check whether a notification to the guest is needed. - fn needs_notification(&mut self) -> Result; + fn needs_notification(&self) -> Result; /// Set vring enabled state. - fn set_enabled(&mut self, enabled: bool); + fn set_enabled(&self, enabled: bool); /// Set queue addresses for descriptor table, available ring and used ring. - fn set_queue_info(&mut self, desc_table: u64, avail_ring: u64, used_ring: u64); + fn set_queue_info(&self, desc_table: u64, avail_ring: u64, used_ring: u64); /// Get queue next avail head. fn queue_next_avail(&self) -> u16; /// Set queue next avail head. - fn set_queue_next_avail(&mut self, base: u16); + fn set_queue_next_avail(&self, base: u16); /// Set configured queue size. - fn set_queue_size(&mut self, num: u16); + fn set_queue_size(&self, num: u16); /// Enable/disable queue event index feature. - fn set_queue_event_idx(&mut self, enabled: bool); + fn set_queue_event_idx(&self, enabled: bool); /// Set queue enabled state. - fn set_queue_ready(&mut self, ready: bool); + fn set_queue_ready(&self, ready: bool); /// Set `EventFd` for kick. - fn set_kick(&mut self, file: Option); + fn set_kick(&self, file: Option); /// Read event from the kick `EventFd`. fn read_kick(&self) -> io::Result; /// Set `EventFd` for call. - fn set_call(&mut self, file: Option); + fn set_call(&self, file: Option); /// Set `EventFd` for err. - fn set_err(&mut self, file: Option); + fn set_err(&self, file: Option); } /// Struct to maintain raw state information for a vhost-user queue. @@ -142,9 +135,15 @@ pub struct VringState> } impl VringState { - /// Get the `EventFd` for kick. - pub fn get_kick(&self) -> &Option { - &self.kick + /// Create a new instance of Vring. + fn new(mem: M, max_queue_size: u16) -> Self { + VringState { + queue: Queue::new(mem, max_queue_size), + kick: None, + call: None, + err: None, + enabled: false, + } } /// Get an immutable reference to the underlying raw `Queue` object. @@ -156,32 +155,14 @@ impl VringState { pub fn get_queue_mut(&mut self) -> &mut Queue { &mut self.queue } -} -impl VringT for VringState { - fn new(mem: M, max_queue_size: u16) -> Self { - VringState { - queue: Queue::new(mem, max_queue_size), - kick: None, - call: None, - err: None, - enabled: false, - } - } - - fn get_ref(&self) -> VringStateGuard { - VringStateGuard::StateObject(self) - } - - fn get_mut(&mut self) -> VringStateMutGuard { - VringStateMutGuard::StateObject(self) - } - - fn add_used(&mut self, desc_index: u16, len: u32) -> Result<(), VirtQueError> { + /// Add an used descriptor into the used queue. + pub fn add_used(&mut self, desc_index: u16, len: u32) -> Result<(), VirtQueError> { self.queue.add_used(desc_index, len) } - fn signal_used_queue(&self) -> io::Result<()> { + /// Notify the vhost-user master that used descriptors have been put into the used queue. + pub fn signal_used_queue(&self) -> io::Result<()> { if let Some(call) = self.call.as_ref() { call.write(1) } else { @@ -189,48 +170,64 @@ impl VringT for VringState { } } - fn enable_notification(&mut self) -> Result { + /// Enable event notification for queue. + pub fn enable_notification(&mut self) -> Result { self.queue.enable_notification() } - fn disable_notification(&mut self) -> Result<(), VirtQueError> { + /// Disable event notification for queue. + pub fn disable_notification(&mut self) -> Result<(), VirtQueError> { self.queue.disable_notification() } - fn needs_notification(&mut self) -> Result { + /// Check whether a notification to the guest is needed. + pub fn needs_notification(&mut self) -> Result { self.queue.needs_notification() } - fn set_enabled(&mut self, enabled: bool) { + /// Set vring enabled state. + pub fn set_enabled(&mut self, enabled: bool) { self.enabled = enabled; } - fn set_queue_info(&mut self, desc_table: u64, avail_ring: u64, used_ring: u64) { + /// Set queue addresses for descriptor table, available ring and used ring. + pub fn set_queue_info(&mut self, desc_table: u64, avail_ring: u64, used_ring: u64) { self.queue.desc_table = GuestAddress(desc_table); self.queue.avail_ring = GuestAddress(avail_ring); self.queue.used_ring = GuestAddress(used_ring); } + /// Get queue next avail head. fn queue_next_avail(&self) -> u16 { self.queue.next_avail() } + /// Set queue next avail head. fn set_queue_next_avail(&mut self, base: u16) { self.queue.set_next_avail(base); } + /// Set configured queue size. fn set_queue_size(&mut self, num: u16) { self.queue.size = num; } + /// Enable/disable queue event index feature. fn set_queue_event_idx(&mut self, enabled: bool) { self.queue.set_event_idx(enabled); } + /// Set queue enabled state. fn set_queue_ready(&mut self, ready: bool) { self.queue.ready = ready; } + /// Get the `EventFd` for kick. + pub fn get_kick(&self) -> &Option { + &self.kick + } + + /// Set `EventFd` for kick. fn set_kick(&mut self, file: Option) { // SAFETY: // EventFd requires that it has sole ownership of its fd. So does File, so this is safe. @@ -239,21 +236,22 @@ impl VringT for VringState { self.kick = file.map(|f| unsafe { EventFd::from_raw_fd(f.into_raw_fd()) }); } + /// Read event from the kick `EventFd`. fn read_kick(&self) -> io::Result { - let state = self.get_ref(); - - if let Some(kick) = &state.kick { + if let Some(kick) = &self.kick { kick.read()?; } - Ok(state.enabled) + Ok(self.enabled) } + /// Set `EventFd` for call. fn set_call(&mut self, file: Option) { // SAFETY: see comment in set_kick() self.call = file.map(|f| unsafe { EventFd::from_raw_fd(f.into_raw_fd()) }); } + /// Set `EventFd` for err. fn set_err(&mut self, file: Option) { // SAFETY: see comment in set_kick() self.err = file.map(|f| unsafe { EventFd::from_raw_fd(f.into_raw_fd()) }); @@ -284,11 +282,11 @@ impl VringT for VringMutex { VringStateGuard::MutexGuard(self.state.lock().unwrap()) } - fn get_mut(&mut self) -> VringStateMutGuard { + fn get_mut(&self) -> VringStateMutGuard { VringStateMutGuard::MutexGuard(self.lock()) } - fn add_used(&mut self, desc_index: u16, len: u32) -> Result<(), VirtQueError> { + fn add_used(&self, desc_index: u16, len: u32) -> Result<(), VirtQueError> { self.lock().add_used(desc_index, len) } @@ -296,23 +294,23 @@ impl VringT for VringMutex { self.get_ref().signal_used_queue() } - fn enable_notification(&mut self) -> Result { + fn enable_notification(&self) -> Result { self.lock().enable_notification() } - fn disable_notification(&mut self) -> Result<(), VirtQueError> { + fn disable_notification(&self) -> Result<(), VirtQueError> { self.lock().disable_notification() } - fn needs_notification(&mut self) -> Result { + fn needs_notification(&self) -> Result { self.lock().needs_notification() } - fn set_enabled(&mut self, enabled: bool) { + fn set_enabled(&self, enabled: bool) { self.lock().set_enabled(enabled) } - fn set_queue_info(&mut self, desc_table: u64, avail_ring: u64, used_ring: u64) { + fn set_queue_info(&self, desc_table: u64, avail_ring: u64, used_ring: u64) { self.lock() .set_queue_info(desc_table, avail_ring, used_ring) } @@ -321,23 +319,23 @@ impl VringT for VringMutex { self.get_ref().queue_next_avail() } - fn set_queue_next_avail(&mut self, base: u16) { + fn set_queue_next_avail(&self, base: u16) { self.lock().set_queue_next_avail(base) } - fn set_queue_size(&mut self, num: u16) { + fn set_queue_size(&self, num: u16) { self.lock().set_queue_size(num); } - fn set_queue_event_idx(&mut self, enabled: bool) { + fn set_queue_event_idx(&self, enabled: bool) { self.lock().set_queue_event_idx(enabled); } - fn set_queue_ready(&mut self, ready: bool) { + fn set_queue_ready(&self, ready: bool) { self.lock().set_queue_ready(ready); } - fn set_kick(&mut self, file: Option) { + fn set_kick(&self, file: Option) { self.lock().set_kick(file); } @@ -345,11 +343,11 @@ impl VringT for VringMutex { self.get_ref().read_kick() } - fn set_call(&mut self, file: Option) { + fn set_call(&self, file: Option) { self.lock().set_call(file) } - fn set_err(&mut self, file: Option) { + fn set_err(&self, file: Option) { self.lock().set_err(file) } } @@ -378,11 +376,11 @@ impl VringT for VringRwLock { VringStateGuard::RwLockReadGuard(self.state.read().unwrap()) } - fn get_mut(&mut self) -> VringStateMutGuard { + fn get_mut(&self) -> VringStateMutGuard { VringStateMutGuard::RwLockWriteGuard(self.write_lock()) } - fn add_used(&mut self, desc_index: u16, len: u32) -> Result<(), VirtQueError> { + fn add_used(&self, desc_index: u16, len: u32) -> Result<(), VirtQueError> { self.write_lock().add_used(desc_index, len) } @@ -390,23 +388,23 @@ impl VringT for VringRwLock { self.get_ref().signal_used_queue() } - fn enable_notification(&mut self) -> Result { + fn enable_notification(&self) -> Result { self.write_lock().enable_notification() } - fn disable_notification(&mut self) -> Result<(), VirtQueError> { + fn disable_notification(&self) -> Result<(), VirtQueError> { self.write_lock().disable_notification() } - fn needs_notification(&mut self) -> Result { + fn needs_notification(&self) -> Result { self.write_lock().needs_notification() } - fn set_enabled(&mut self, enabled: bool) { + fn set_enabled(&self, enabled: bool) { self.write_lock().set_enabled(enabled) } - fn set_queue_info(&mut self, desc_table: u64, avail_ring: u64, used_ring: u64) { + fn set_queue_info(&self, desc_table: u64, avail_ring: u64, used_ring: u64) { self.write_lock() .set_queue_info(desc_table, avail_ring, used_ring) } @@ -415,23 +413,23 @@ impl VringT for VringRwLock { self.get_ref().queue_next_avail() } - fn set_queue_next_avail(&mut self, base: u16) { + fn set_queue_next_avail(&self, base: u16) { self.write_lock().set_queue_next_avail(base) } - fn set_queue_size(&mut self, num: u16) { + fn set_queue_size(&self, num: u16) { self.write_lock().set_queue_size(num); } - fn set_queue_event_idx(&mut self, enabled: bool) { + fn set_queue_event_idx(&self, enabled: bool) { self.write_lock().set_queue_event_idx(enabled); } - fn set_queue_ready(&mut self, ready: bool) { + fn set_queue_ready(&self, ready: bool) { self.write_lock().set_queue_ready(ready); } - fn set_kick(&mut self, file: Option) { + fn set_kick(&self, file: Option) { self.write_lock().set_kick(file); } @@ -439,11 +437,11 @@ impl VringT for VringRwLock { self.get_ref().read_kick() } - fn set_call(&mut self, file: Option) { + fn set_call(&self, file: Option) { self.write_lock().set_call(file) } - fn set_err(&mut self, file: Option) { + fn set_err(&self, file: Option) { self.write_lock().set_err(file) } } @@ -461,7 +459,7 @@ mod tests { GuestMemoryMmap::::from_ranges(&[(GuestAddress(0x100000), 0x10000)]) .unwrap(), ); - let mut vring = VringMutex::new(mem, 0x1000); + let vring = VringMutex::new(mem, 0x1000); assert!(vring.get_ref().get_kick().is_none()); assert_eq!(vring.get_ref().enabled, false); @@ -495,7 +493,7 @@ mod tests { let mem = GuestMemoryAtomic::new( GuestMemoryMmap::<()>::from_ranges(&[(GuestAddress(0x100000), 0x10000)]).unwrap(), ); - let mut vring = VringMutex::new(mem, 0x1000); + let vring = VringMutex::new(mem, 0x1000); vring.set_enabled(true); assert_eq!(vring.get_ref().enabled, true); From 70f668a699d865f13ba40498897ad181a409bd41 Mon Sep 17 00:00:00 2001 From: Liu Jiang Date: Wed, 1 Sep 2021 22:51:22 +0800 Subject: [PATCH 089/139] doc: refine README.md Refine README.md, help is needed to improve documentation for publishing. Signed-off-by: Liu Jiang --- README.md | 105 ++++++++++++++++++++++++++++++++++++++++++++++++++---- 1 file changed, 98 insertions(+), 7 deletions(-) diff --git a/README.md b/README.md index 570ac8f..c9b7c92 100644 --- a/README.md +++ b/README.md @@ -2,13 +2,104 @@ ## Design -This crate provides convenient abstractions for implementing `vhost-user` device server backends: - -- A vhost-user backend trait (`VhostUserBackend`) -- A public API for the backend to interact with (`VhostUserDaemon`) -- An structure including virtio queue related elements (`Vring`) -- A worker for receiving queue events and forwarding them to the backend. +The `vhost-user-backend` crate provides a framework to implement `vhost-user` backend services, +which includes following external public APIs: +- A daemon control object (`VhostUserDaemon`) to start and stop the service daemon. +- A vhost-user backend trait (`VhostUserBackendMut`) to handle vhost-user control messages and virtio + messages. +- A vring access trait (`VringT`) to access virtio queues, and three implementations of the trait: + `VringState`, `VringMutex` and `VringRwLock`. ## Usage +The `vhost-user-backend` crate provides a framework to implement vhost-user backend services. The main interface provided by `vhost-user-backend` library is the `struct VhostUserDaemon`: +```rust +pub struct VhostUserDaemon +where + S: VhostUserBackend, + V: VringT> + Clone + Send + Sync + 'static, + B: Bitmap + 'static, +{ + pub fn new(name: String, backend: S, atomic_mem: GuestMemoryAtomic>) -> Result; + pub fn start(&mut self, listener: Listener) -> Result<()>; + pub fn wait(&mut self) -> Result<()>; + pub fn get_epoll_handlers(&self) -> Vec>>; +} +``` -Users of this create are expected to implement the `VhostUserBackend` trait and to initialize the execution context by instantiating `VhostUserDaemon` and calling to its `start` method. +### Create a `VhostUserDaemon` Instance +The `VhostUserDaemon::new()` creates an instance of `VhostUserDaemon` object. The client needs to +pass in an `VhostUserBackend` object, which will be used to configure the `VhostUserDaemon` +instance, handle control messages from the vhost-user master and handle virtio requests from +virtio queues. A group of working threads will be created to handle virtio requests from configured +virtio queues. + +### Start the `VhostUserDaemon` +The `VhostUserDaemon::start()` method waits for an incoming connection from the vhost-user masters +on the `listener`. Once a connection is ready, a main thread will be created to handle vhost-user +messages from the vhost-user master. + +### Stop the `VhostUserDaemon` +The `VhostUserDaemon::stop()` method waits for the main thread to exit. An exit event must be sent +to the main thread by writing to the `exit_event` EventFd before waiting for it to exit. + +### Threading Model +The main thread and virtio queue working threads will concurrently access the underlying virtio +queues, so all virtio queue in multi-threading model. But the main thread only accesses virtio +queues for configuration, so client could adopt locking policies to optimize for the virtio queue +working threads. + +## Example +Example code to handle virtio messages from a virtio queue: +```rust +impl VhostUserBackendMut for VhostUserService { + fn process_queue(&mut self, vring: &VringMutex) -> Result { + let mut used_any = false; + let mem = match &self.mem { + Some(m) => m.memory(), + None => return Err(Error::NoMemoryConfigured), + }; + + let mut vring_state = vring.get_mut(); + + while let Some(avail_desc) = vring_state + .get_queue_mut() + .iter() + .map_err(|_| Error::IterateQueue)? + .next() + { + // Process the request... + + if self.event_idx { + if vring_state.add_used(head_index, 0).is_err() { + warn!("Couldn't return used descriptors to the ring"); + } + + match vring_state.needs_notification() { + Err(_) => { + warn!("Couldn't check if queue needs to be notified"); + vring_state.signal_used_queue().unwrap(); + } + Ok(needs_notification) => { + if needs_notification { + vring_state.signal_used_queue().unwrap(); + } + } + } + } else { + if vring_state.add_used(head_index, 0).is_err() { + warn!("Couldn't return used descriptors to the ring"); + } + vring_state.signal_used_queue().unwrap(); + } + } + + Ok(used_any) + } +} +``` + +## License + +This project is licensed under + +- [Apache License](http://www.apache.org/licenses/LICENSE-2.0), Version 2.0 From a866ed1d66f92a4119cdce18d767114e62948fae Mon Sep 17 00:00:00 2001 From: Andreea Florescu Date: Fri, 10 Sep 2021 12:54:35 +0300 Subject: [PATCH 090/139] update dependabot formatting Signed-off-by: Andreea Florescu --- .github/dependabot.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/dependabot.yml b/.github/dependabot.yml index addf8be..c624534 100644 --- a/.github/dependabot.yml +++ b/.github/dependabot.yml @@ -11,5 +11,5 @@ updates: - package-ecosystem: gitsubmodule directory: "/" schedule: - interval: daily + interval: weekly open-pull-requests-limit: 10 From f4ee3226c0c23c1e431359114f142999fe814d8f Mon Sep 17 00:00:00 2001 From: Sergio Lopez Date: Thu, 23 Sep 2021 12:24:44 +0200 Subject: [PATCH 091/139] Cargo: update vhost dependency to v0.2.0 In addition to bringing the features from the new vhost crate, this fixes the breakage caused by rust-vmm/vmm-sys-util#135 Signed-off-by: Sergio Lopez --- Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Cargo.toml b/Cargo.toml index e5f2b66..450f1b7 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -11,7 +11,7 @@ license = "Apache-2.0" epoll = ">=4.0.1" libc = ">=0.2.39" log = ">=0.4.6" -vhost = { version = "0.1", features = ["vhost-user-slave"] } +vhost = { version = "0.2", features = ["vhost-user-slave"] } virtio-bindings = "0.1" virtio-queue = { git = "https://github.com/rust-vmm/vm-virtio", rev = "6013dd9" } vm-memory = {version = "0.6", features = ["backend-mmap", "backend-atomic"]} From a577bbc8689820f5c17cc4cd859eccca312b5d93 Mon Sep 17 00:00:00 2001 From: Sergio Lopez Date: Mon, 13 Sep 2021 13:38:20 +0200 Subject: [PATCH 092/139] vring: Add "get_call" method Add a "get_call" method to obtain the current value for the call EventFd. Signed-off-by: Sergio Lopez --- src/vring.rs | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/vring.rs b/src/vring.rs index 3761e7e..39aecee 100644 --- a/src/vring.rs +++ b/src/vring.rs @@ -251,6 +251,11 @@ impl VringState { self.call = file.map(|f| unsafe { EventFd::from_raw_fd(f.into_raw_fd()) }); } + /// Get the `EventFd` for call. + pub fn get_call(&self) -> &Option { + &self.call + } + /// Set `EventFd` for err. fn set_err(&mut self, file: Option) { // SAFETY: see comment in set_kick() From 4feeb77a206f667cd1aaf264ac5aa10fcbcc0e9c Mon Sep 17 00:00:00 2001 From: Sergio Lopez Date: Mon, 13 Sep 2021 13:39:10 +0200 Subject: [PATCH 093/139] Register listeners only with both call and kick The vhost-user protocol doesn't impose an order for the SET_VRING_KICK and SET_VRING_CALL messages, which implies it's valid to emit the first before the latter. With the current code, this means that if the VMM sends SET_VRING_KICK before SET_VRING_CALL, and the guest has already placed a request into the vring, we may miss signaling the guest as that request may be processed before we have an EventFd for "call". To fix this, delay listener registration until we have an EventFd for both call and kick, using "VringState::Queue.ready" as an indicator that the vring has not been initialized yet. Signed-off-by: Sergio Lopez --- src/handler.rs | 58 +++++++++++++++++++++++++++++++++++--------------- 1 file changed, 41 insertions(+), 17 deletions(-) diff --git a/src/handler.rs b/src/handler.rs index 672785b..bfaad88 100644 --- a/src/handler.rs +++ b/src/handler.rs @@ -175,6 +175,38 @@ where Err(VhostUserHandlerError::MissingMemoryMapping) } + + fn vring_needs_init(&self, vring: &V) -> bool { + let vring_state = vring.get_ref(); + + // If the vring wasn't initialized and we already have an EventFd for + // both VRING_KICK and VRING_CALL, initialize it now. + !vring_state.get_queue().ready + && vring_state.get_call().is_some() + && vring_state.get_kick().is_some() + } + + fn initialize_vring(&self, vring: &V, index: u8) -> VhostUserResult<()> { + assert!(vring.get_ref().get_call().is_some()); + assert!(vring.get_ref().get_kick().is_some()); + + if let Some(fd) = vring.get_ref().get_kick() { + for (thread_index, queues_mask) in self.queues_per_thread.iter().enumerate() { + let shifted_queues_mask = queues_mask >> index; + if shifted_queues_mask & 1u64 == 1u64 { + let evt_idx = queues_mask.count_ones() - shifted_queues_mask.count_ones(); + self.handlers[thread_index] + .register_event(fd.as_raw_fd(), epoll::Events::EPOLLIN, u64::from(evt_idx)) + .map_err(VhostUserError::ReqHandlerError)?; + break; + } + } + } + + self.vrings[index as usize].set_queue_ready(true); + + Ok(()) + } } impl VhostUserSlaveReqHandlerMut for VhostUserHandler @@ -361,6 +393,9 @@ where } } + self.vrings[index as usize].set_kick(None); + self.vrings[index as usize].set_call(None); + let next_avail = self.vrings[index as usize].queue_next_avail(); Ok(VhostUserVringState::new(index, u32::from(next_avail))) @@ -377,23 +412,8 @@ where // such as that proposed by Rust RFC #3128. self.vrings[index as usize].set_kick(file); - // Quote from vhost-user specification: - // Client must start ring upon receiving a kick (that is, detecting - // that file descriptor is readable) on the descriptor specified by - // VHOST_USER_SET_VRING_KICK, and stop ring upon receiving - // VHOST_USER_GET_VRING_BASE. - self.vrings[index as usize].set_queue_ready(true); - if let Some(fd) = self.vrings[index as usize].get_ref().get_kick() { - for (thread_index, queues_mask) in self.queues_per_thread.iter().enumerate() { - let shifted_queues_mask = queues_mask >> index; - if shifted_queues_mask & 1u64 == 1u64 { - let evt_idx = queues_mask.count_ones() - shifted_queues_mask.count_ones(); - self.handlers[thread_index] - .register_event(fd.as_raw_fd(), epoll::Events::EPOLLIN, u64::from(evt_idx)) - .map_err(VhostUserError::ReqHandlerError)?; - break; - } - } + if self.vring_needs_init(&self.vrings[index as usize]) { + self.initialize_vring(&self.vrings[index as usize], index)?; } Ok(()) @@ -406,6 +426,10 @@ where self.vrings[index as usize].set_call(file); + if self.vring_needs_init(&self.vrings[index as usize]) { + self.initialize_vring(&self.vrings[index as usize], index)?; + } + Ok(()) } From 17131359ebae207476d009a65b702f028b581622 Mon Sep 17 00:00:00 2001 From: Sergio Lopez Date: Mon, 13 Sep 2021 13:45:03 +0200 Subject: [PATCH 094/139] Reset the Queue on GET_VRING_BASE According to the vhost-user specs, we should start the vring upon receiving the first kick, and stop it when we receive GET_VRING_BASE. Strictly speaking, we should reset the underlying Queue on the first kick, but it's actually easier to simply do that in GET_VRING_BASE and be ready in case the guest re-initializes the vring. Signed-off-by: Sergio Lopez --- src/handler.rs | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/src/handler.rs b/src/handler.rs index bfaad88..5cba893 100644 --- a/src/handler.rs +++ b/src/handler.rs @@ -396,6 +396,14 @@ where self.vrings[index as usize].set_kick(None); self.vrings[index as usize].set_call(None); + // Strictly speaking, we should do this upon receiving the first kick, + // but it's actually easier to just do it here so we're ready in case + // the vring gets re-initialized by the guest. + self.vrings[index as usize] + .get_mut() + .get_queue_mut() + .reset(); + let next_avail = self.vrings[index as usize].queue_next_avail(); Ok(VhostUserVringState::new(index, u32::from(next_avail))) From 3242b37d3258c2072faa47d0d17097f4bc0966c7 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 20 Sep 2021 01:25:46 +0000 Subject: [PATCH 095/139] Update vmm-sys-util requirement from 0.8 to 0.9 Updates the requirements on [vmm-sys-util](https://github.com/rust-vmm/vmm-sys-util) to permit the latest version. - [Release notes](https://github.com/rust-vmm/vmm-sys-util/releases) - [Changelog](https://github.com/rust-vmm/vmm-sys-util/blob/main/CHANGELOG.md) - [Commits](https://github.com/rust-vmm/vmm-sys-util/compare/v0.8.0...v0.9.0) --- updated-dependencies: - dependency-name: vmm-sys-util dependency-type: direct:production ... Signed-off-by: dependabot[bot] --- Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Cargo.toml b/Cargo.toml index 450f1b7..79b30cd 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -15,7 +15,7 @@ vhost = { version = "0.2", features = ["vhost-user-slave"] } virtio-bindings = "0.1" virtio-queue = { git = "https://github.com/rust-vmm/vm-virtio", rev = "6013dd9" } vm-memory = {version = "0.6", features = ["backend-mmap", "backend-atomic"]} -vmm-sys-util = "0.8" +vmm-sys-util = "0.9" [dev-dependencies] vm-memory = {version = "0.6", features = ["backend-mmap", "backend-atomic", "backend-bitmap"]} From ecfb8467b1206a75118dcccaafb8939e057d4386 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 20 Sep 2021 01:19:42 +0000 Subject: [PATCH 096/139] Bump rust-vmm-ci from `1311bfa` to `f67ef4c` Bumps [rust-vmm-ci](https://github.com/rust-vmm/rust-vmm-ci) from `1311bfa` to `f67ef4c`. - [Release notes](https://github.com/rust-vmm/rust-vmm-ci/releases) - [Commits](https://github.com/rust-vmm/rust-vmm-ci/compare/1311bfa03f3510a3aec6b69ae085f21e8e4a1e5e...f67ef4c84d99facfdba70a97eb11a4364267eb2b) --- updated-dependencies: - dependency-name: rust-vmm-ci dependency-type: direct:production ... Signed-off-by: dependabot[bot] --- rust-vmm-ci | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/rust-vmm-ci b/rust-vmm-ci index 1311bfa..f67ef4c 160000 --- a/rust-vmm-ci +++ b/rust-vmm-ci @@ -1 +1 @@ -Subproject commit 1311bfa03f3510a3aec6b69ae085f21e8e4a1e5e +Subproject commit f67ef4c84d99facfdba70a97eb11a4364267eb2b From bd6b53348f06055abcb2b7254168d716b742f383 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 27 Sep 2021 01:20:50 +0000 Subject: [PATCH 097/139] Bump rust-vmm-ci from `f67ef4c` to `7f22582` Bumps [rust-vmm-ci](https://github.com/rust-vmm/rust-vmm-ci) from `f67ef4c` to `7f22582`. - [Release notes](https://github.com/rust-vmm/rust-vmm-ci/releases) - [Commits](https://github.com/rust-vmm/rust-vmm-ci/compare/f67ef4c84d99facfdba70a97eb11a4364267eb2b...7f22582590b5816878e7f3f860766979cab297a0) --- updated-dependencies: - dependency-name: rust-vmm-ci dependency-type: direct:production ... Signed-off-by: dependabot[bot] --- rust-vmm-ci | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/rust-vmm-ci b/rust-vmm-ci index f67ef4c..7f22582 160000 --- a/rust-vmm-ci +++ b/rust-vmm-ci @@ -1 +1 @@ -Subproject commit f67ef4c84d99facfdba70a97eb11a4364267eb2b +Subproject commit 7f22582590b5816878e7f3f860766979cab297a0 From f22c4af539b14d235b5bca8ab9715424076fc4d2 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 11 Oct 2021 01:37:16 +0000 Subject: [PATCH 098/139] Bump rust-vmm-ci from `7f22582` to `68d4dbf` Bumps [rust-vmm-ci](https://github.com/rust-vmm/rust-vmm-ci) from `7f22582` to `68d4dbf`. - [Release notes](https://github.com/rust-vmm/rust-vmm-ci/releases) - [Commits](https://github.com/rust-vmm/rust-vmm-ci/compare/7f22582590b5816878e7f3f860766979cab297a0...68d4dbf85c2ef54a440afa635d2f0db8635dcfb7) --- updated-dependencies: - dependency-name: rust-vmm-ci dependency-type: direct:production ... Signed-off-by: dependabot[bot] --- rust-vmm-ci | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/rust-vmm-ci b/rust-vmm-ci index 7f22582..68d4dbf 160000 --- a/rust-vmm-ci +++ b/rust-vmm-ci @@ -1 +1 @@ -Subproject commit 7f22582590b5816878e7f3f860766979cab297a0 +Subproject commit 68d4dbf85c2ef54a440afa635d2f0db8635dcfb7 From 6d5fccefc7a62a0636c13e748e19434f79ebeb80 Mon Sep 17 00:00:00 2001 From: Liu Jiang Date: Fri, 15 Oct 2021 10:11:07 +0800 Subject: [PATCH 099/139] epoll: use epoll wrapper from vmm-sys-util There's a wrapper for epoll from vmm-sys-util, so use the wrapper instead of the epoll crate directly. It may help to ease dependency management. Signed-off-by: Liu Jiang --- Cargo.toml | 1 - coverage_config_x86_64.json | 2 +- rust-vmm-ci | 2 +- src/backend.rs | 14 +++---- src/event_loop.rs | 73 ++++++++++++++++--------------------- src/handler.rs | 9 ++--- 6 files changed, 44 insertions(+), 57 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 79b30cd..6bcff46 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -8,7 +8,6 @@ edition = "2018" license = "Apache-2.0" [dependencies] -epoll = ">=4.0.1" libc = ">=0.2.39" log = ">=0.4.6" vhost = { version = "0.2", features = ["vhost-user-slave"] } diff --git a/coverage_config_x86_64.json b/coverage_config_x86_64.json index 3c3f96d..fe62685 100644 --- a/coverage_config_x86_64.json +++ b/coverage_config_x86_64.json @@ -1,5 +1,5 @@ { - "coverage_score": 78.8, + "coverage_score": 78.5, "exclude_path": "", "crate_features": "" } diff --git a/rust-vmm-ci b/rust-vmm-ci index 68d4dbf..8901e77 160000 --- a/rust-vmm-ci +++ b/rust-vmm-ci @@ -1 +1 @@ -Subproject commit 68d4dbf85c2ef54a440afa635d2f0db8635dcfb7 +Subproject commit 8901e7752288ae1061e2ee888a104c083a451668 diff --git a/src/backend.rs b/src/backend.rs index 6fb19dd..a2b7d23 100644 --- a/src/backend.rs +++ b/src/backend.rs @@ -26,6 +26,7 @@ use std::sync::{Arc, Mutex, RwLock}; use vhost::vhost_user::message::VhostUserProtocolFeatures; use vhost::vhost_user::SlaveFsCacheReq; use vm_memory::bitmap::Bitmap; +use vmm_sys_util::epoll::EventSet; use vmm_sys_util::eventfd::EventFd; use super::vring::VringT; @@ -111,7 +112,7 @@ where fn handle_event( &self, device_event: u16, - evset: epoll::Events, + evset: EventSet, vrings: &[V], thread_id: usize, ) -> result::Result; @@ -194,7 +195,7 @@ where fn handle_event( &mut self, device_event: u16, - evset: epoll::Events, + evset: EventSet, vrings: &[V], thread_id: usize, ) -> result::Result; @@ -256,7 +257,7 @@ where fn handle_event( &self, device_event: u16, - evset: epoll::Events, + evset: EventSet, vrings: &[V], thread_id: usize, ) -> Result { @@ -321,7 +322,7 @@ where fn handle_event( &self, device_event: u16, - evset: epoll::Events, + evset: EventSet, vrings: &[V], thread_id: usize, ) -> Result { @@ -387,7 +388,7 @@ where fn handle_event( &self, device_event: u16, - evset: epoll::Events, + evset: EventSet, vrings: &[V], thread_id: usize, ) -> Result { @@ -401,7 +402,6 @@ where pub mod tests { use super::*; use crate::VringRwLock; - use epoll::Events; use std::io::Error; use std::sync::Mutex; use vm_memory::{GuestMemoryAtomic, GuestMemoryMmap}; @@ -484,7 +484,7 @@ pub mod tests { fn handle_event( &mut self, _device_event: u16, - _evset: Events, + _evset: EventSet, _vrings: &[VringRwLock], _thread_id: usize, ) -> Result { diff --git a/src/event_loop.rs b/src/event_loop.rs index 6a70158..63e7883 100644 --- a/src/event_loop.rs +++ b/src/event_loop.rs @@ -4,13 +4,13 @@ // SPDX-License-Identifier: Apache-2.0 use std::fmt::{Display, Formatter}; -use std::fs::File; use std::io; use std::marker::PhantomData; -use std::os::unix::io::{AsRawFd, FromRawFd, RawFd}; +use std::os::unix::io::{AsRawFd, RawFd}; use std::result; use vm_memory::bitmap::Bitmap; +use vmm_sys_util::epoll::{ControlOperation, Epoll, EpollEvent, EventSet}; use vmm_sys_util::eventfd::EventFd; use super::backend::VhostUserBackend; @@ -65,7 +65,7 @@ where V: VringT>, B: Bitmap + 'static, { - epoll_file: File, + epoll: Epoll, backend: S, vrings: Vec, thread_id: usize, @@ -81,22 +81,21 @@ where { /// Create a `VringEpollHandler` instance. pub(crate) fn new(backend: S, vrings: Vec, thread_id: usize) -> VringEpollResult { - let epoll_fd = epoll::create(true).map_err(VringEpollError::EpollCreateFd)?; - let epoll_file = unsafe { File::from_raw_fd(epoll_fd) }; + let epoll = Epoll::new().map_err(VringEpollError::EpollCreateFd)?; let handler = match backend.exit_event(thread_id) { Some(exit_event_fd) => { let id = backend.num_queues(); - epoll::ctl( - epoll_file.as_raw_fd(), - epoll::ControlOptions::EPOLL_CTL_ADD, - exit_event_fd.as_raw_fd(), - epoll::Event::new(epoll::Events::EPOLLIN, id as u64), - ) - .map_err(VringEpollError::RegisterExitEvent)?; + epoll + .ctl( + ControlOperation::Add, + exit_event_fd.as_raw_fd(), + EpollEvent::new(EventSet::IN, id as u64), + ) + .map_err(VringEpollError::RegisterExitEvent)?; VringEpollHandler { - epoll_file, + epoll, backend, vrings, thread_id, @@ -105,7 +104,7 @@ where } } None => VringEpollHandler { - epoll_file, + epoll, backend, vrings, thread_id, @@ -131,7 +130,7 @@ where pub fn register_listener( &self, fd: RawFd, - ev_type: epoll::Events, + ev_type: EventSet, data: u64, ) -> result::Result<(), io::Error> { // `data` range [0...num_queues] is reserved for queues and exit event. @@ -149,7 +148,7 @@ where pub fn unregister_listener( &self, fd: RawFd, - ev_type: epoll::Events, + ev_type: EventSet, data: u64, ) -> result::Result<(), io::Error> { // `data` range [0...num_queues] is reserved for queues and exit event. @@ -163,29 +162,21 @@ where pub(crate) fn register_event( &self, fd: RawFd, - ev_type: epoll::Events, + ev_type: EventSet, data: u64, ) -> result::Result<(), io::Error> { - epoll::ctl( - self.epoll_file.as_raw_fd(), - epoll::ControlOptions::EPOLL_CTL_ADD, - fd, - epoll::Event::new(ev_type, data), - ) + self.epoll + .ctl(ControlOperation::Add, fd, EpollEvent::new(ev_type, data)) } pub(crate) fn unregister_event( &self, fd: RawFd, - ev_type: epoll::Events, + ev_type: EventSet, data: u64, ) -> result::Result<(), io::Error> { - epoll::ctl( - self.epoll_file.as_raw_fd(), - epoll::ControlOptions::EPOLL_CTL_DEL, - fd, - epoll::Event::new(ev_type, data), - ) + self.epoll + .ctl(ControlOperation::Delete, fd, EpollEvent::new(ev_type, data)) } /// Run the event poll loop to handle all pending events on registered fds. @@ -194,10 +185,10 @@ where /// associated with the backend. pub(crate) fn run(&self) -> VringEpollResult<()> { const EPOLL_EVENTS_LEN: usize = 100; - let mut events = vec![epoll::Event::new(epoll::Events::empty(), 0); EPOLL_EVENTS_LEN]; + let mut events = vec![EpollEvent::new(EventSet::empty(), 0); EPOLL_EVENTS_LEN]; 'epoll: loop { - let num_events = match epoll::wait(self.epoll_file.as_raw_fd(), -1, &mut events[..]) { + let num_events = match self.epoll.wait(-1, &mut events[..]) { Ok(res) => res, Err(e) => { if e.kind() == io::ErrorKind::Interrupted { @@ -215,7 +206,7 @@ where }; for event in events.iter().take(num_events) { - let evset = match epoll::Events::from_bits(event.events) { + let evset = match EventSet::from_bits(event.events) { Some(evset) => evset, None => { let evbits = event.events; @@ -224,7 +215,7 @@ where } }; - let ev_type = event.data as u16; + let ev_type = event.data() as u16; // handle_event() returns true if an event is received from the exit event fd. if self.handle_event(ev_type, evset)? { @@ -236,7 +227,7 @@ where Ok(()) } - fn handle_event(&self, device_event: u16, evset: epoll::Events) -> VringEpollResult { + fn handle_event(&self, device_event: u16, evset: EventSet) -> VringEpollResult { if self.exit_event_fd.is_some() && device_event as usize == self.backend.num_queues() { return Ok(true); } @@ -280,27 +271,27 @@ mod tests { let eventfd = EventFd::new(0).unwrap(); handler - .register_listener(eventfd.as_raw_fd(), epoll::Events::EPOLLIN, 3) + .register_listener(eventfd.as_raw_fd(), EventSet::IN, 3) .unwrap(); // Register an already registered fd. handler - .register_listener(eventfd.as_raw_fd(), epoll::Events::EPOLLIN, 3) + .register_listener(eventfd.as_raw_fd(), EventSet::IN, 3) .unwrap_err(); // Register an invalid data. handler - .register_listener(eventfd.as_raw_fd(), epoll::Events::EPOLLIN, 1) + .register_listener(eventfd.as_raw_fd(), EventSet::IN, 1) .unwrap_err(); handler - .unregister_listener(eventfd.as_raw_fd(), epoll::Events::EPOLLIN, 3) + .unregister_listener(eventfd.as_raw_fd(), EventSet::IN, 3) .unwrap(); // unregister an already unregistered fd. handler - .unregister_listener(eventfd.as_raw_fd(), epoll::Events::EPOLLIN, 3) + .unregister_listener(eventfd.as_raw_fd(), EventSet::IN, 3) .unwrap_err(); // unregister an invalid data. handler - .unregister_listener(eventfd.as_raw_fd(), epoll::Events::EPOLLIN, 1) + .unregister_listener(eventfd.as_raw_fd(), EventSet::IN, 1) .unwrap_err(); } } diff --git a/src/handler.rs b/src/handler.rs index 5cba893..889e959 100644 --- a/src/handler.rs +++ b/src/handler.rs @@ -25,6 +25,7 @@ use vm_memory::mmap::NewBitmap; use vm_memory::{ FileOffset, GuestAddress, GuestAddressSpace, GuestMemoryMmap, GuestRegionMmap, MmapRegion, }; +use vmm_sys_util::epoll::EventSet; use super::backend::VhostUserBackend; use super::event_loop::VringEpollHandler; @@ -196,7 +197,7 @@ where if shifted_queues_mask & 1u64 == 1u64 { let evt_idx = queues_mask.count_ones() - shifted_queues_mask.count_ones(); self.handlers[thread_index] - .register_event(fd.as_raw_fd(), epoll::Events::EPOLLIN, u64::from(evt_idx)) + .register_event(fd.as_raw_fd(), EventSet::IN, u64::from(evt_idx)) .map_err(VhostUserError::ReqHandlerError)?; break; } @@ -382,11 +383,7 @@ where if shifted_queues_mask & 1u64 == 1u64 { let evt_idx = queues_mask.count_ones() - shifted_queues_mask.count_ones(); self.handlers[thread_index] - .unregister_event( - fd.as_raw_fd(), - epoll::Events::EPOLLIN, - u64::from(evt_idx), - ) + .unregister_event(fd.as_raw_fd(), EventSet::IN, u64::from(evt_idx)) .map_err(VhostUserError::ReqHandlerError)?; break; } From b8dc1949261ff589daec20a3857a5a79eef04ba6 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 18 Oct 2021 01:21:21 +0000 Subject: [PATCH 100/139] Bump rust-vmm-ci from `8901e77` to `7931077` Bumps [rust-vmm-ci](https://github.com/rust-vmm/rust-vmm-ci) from `8901e77` to `7931077`. - [Release notes](https://github.com/rust-vmm/rust-vmm-ci/releases) - [Commits](https://github.com/rust-vmm/rust-vmm-ci/compare/8901e7752288ae1061e2ee888a104c083a451668...7931077cdc577edc20af54bf4786de750886fb85) --- updated-dependencies: - dependency-name: rust-vmm-ci dependency-type: direct:production ... Signed-off-by: dependabot[bot] --- rust-vmm-ci | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/rust-vmm-ci b/rust-vmm-ci index 8901e77..7931077 160000 --- a/rust-vmm-ci +++ b/rust-vmm-ci @@ -1 +1 @@ -Subproject commit 8901e7752288ae1061e2ee888a104c083a451668 +Subproject commit 7931077cdc577edc20af54bf4786de750886fb85 From fc1ce25e8a8cca11efd5b4f61c13a467b7232481 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 25 Oct 2021 01:37:29 +0000 Subject: [PATCH 101/139] Update vm-memory requirement from 0.6 to 0.7 Updates the requirements on [vm-memory](https://github.com/rust-vmm/vm-memory) to permit the latest version. - [Release notes](https://github.com/rust-vmm/vm-memory/releases) - [Changelog](https://github.com/rust-vmm/vm-memory/blob/main/CHANGELOG.md) - [Commits](https://github.com/rust-vmm/vm-memory/compare/v0.6.0...v0.7.0) --- updated-dependencies: - dependency-name: vm-memory dependency-type: direct:production ... Signed-off-by: dependabot[bot] --- Cargo.toml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 6bcff46..be5c95e 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -13,8 +13,8 @@ log = ">=0.4.6" vhost = { version = "0.2", features = ["vhost-user-slave"] } virtio-bindings = "0.1" virtio-queue = { git = "https://github.com/rust-vmm/vm-virtio", rev = "6013dd9" } -vm-memory = {version = "0.6", features = ["backend-mmap", "backend-atomic"]} +vm-memory = {version = "0.7", features = ["backend-mmap", "backend-atomic"]} vmm-sys-util = "0.9" [dev-dependencies] -vm-memory = {version = "0.6", features = ["backend-mmap", "backend-atomic", "backend-bitmap"]} +vm-memory = {version = "0.7", features = ["backend-mmap", "backend-atomic", "backend-bitmap"]} From ec8c2208073558b4cf3d471067c3be231d4bd4b0 Mon Sep 17 00:00:00 2001 From: Stefano Garzarella Date: Mon, 25 Oct 2021 18:13:44 +0200 Subject: [PATCH 102/139] Bump virtio-queue from `6013dd9` to `66cda80` Commit 66cda80 ("Update vm-memory requirement from 0.6 to 0.7") is required to build this crate with vm-memory v0.7 There were API changes since commit 6013dd9, so let's update code and tests. Signed-off-by: Stefano Garzarella --- Cargo.toml | 2 +- src/handler.rs | 2 +- src/vring.rs | 35 ++++++++++++++++++++++------------- 3 files changed, 24 insertions(+), 15 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index be5c95e..f69c592 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -12,7 +12,7 @@ libc = ">=0.2.39" log = ">=0.4.6" vhost = { version = "0.2", features = ["vhost-user-slave"] } virtio-bindings = "0.1" -virtio-queue = { git = "https://github.com/rust-vmm/vm-virtio", rev = "6013dd9" } +virtio-queue = { git = "https://github.com/rust-vmm/vm-virtio", rev = "66cda80" } vm-memory = {version = "0.7", features = ["backend-mmap", "backend-atomic"]} vmm-sys-util = "0.9" diff --git a/src/handler.rs b/src/handler.rs index 889e959..e7f84b5 100644 --- a/src/handler.rs +++ b/src/handler.rs @@ -182,7 +182,7 @@ where // If the vring wasn't initialized and we already have an EventFd for // both VRING_KICK and VRING_CALL, initialize it now. - !vring_state.get_queue().ready + !vring_state.get_queue().ready() && vring_state.get_call().is_some() && vring_state.get_kick().is_some() } diff --git a/src/vring.rs b/src/vring.rs index 39aecee..0207e82 100644 --- a/src/vring.rs +++ b/src/vring.rs @@ -192,9 +192,9 @@ impl VringState { /// Set queue addresses for descriptor table, available ring and used ring. pub fn set_queue_info(&mut self, desc_table: u64, avail_ring: u64, used_ring: u64) { - self.queue.desc_table = GuestAddress(desc_table); - self.queue.avail_ring = GuestAddress(avail_ring); - self.queue.used_ring = GuestAddress(used_ring); + self.queue.state.desc_table = GuestAddress(desc_table); + self.queue.state.avail_ring = GuestAddress(avail_ring); + self.queue.state.used_ring = GuestAddress(used_ring); } /// Get queue next avail head. @@ -209,7 +209,7 @@ impl VringState { /// Set configured queue size. fn set_queue_size(&mut self, num: u16) { - self.queue.size = num; + self.queue.set_size(num); } /// Enable/disable queue event index feature. @@ -219,7 +219,7 @@ impl VringState { /// Set queue enabled state. fn set_queue_ready(&mut self, ready: bool) { - self.queue.ready = ready; + self.queue.set_ready(ready); } /// Get the `EventFd` for kick. @@ -468,29 +468,38 @@ mod tests { assert!(vring.get_ref().get_kick().is_none()); assert_eq!(vring.get_ref().enabled, false); - assert_eq!(vring.lock().queue.ready, false); - assert_eq!(vring.lock().queue.event_idx_enabled, false); + assert_eq!(vring.lock().queue.ready(), false); + assert_eq!(vring.lock().queue.state.event_idx_enabled, false); vring.set_enabled(true); assert_eq!(vring.get_ref().enabled, true); vring.set_queue_info(0x100100, 0x100200, 0x100300); - assert_eq!(vring.lock().get_queue().desc_table, GuestAddress(0x100100)); - assert_eq!(vring.lock().get_queue().avail_ring, GuestAddress(0x100200)); - assert_eq!(vring.lock().get_queue().used_ring, GuestAddress(0x100300)); + assert_eq!( + vring.lock().get_queue().state.desc_table, + GuestAddress(0x100100) + ); + assert_eq!( + vring.lock().get_queue().state.avail_ring, + GuestAddress(0x100200) + ); + assert_eq!( + vring.lock().get_queue().state.used_ring, + GuestAddress(0x100300) + ); assert_eq!(vring.queue_next_avail(), 0); vring.set_queue_next_avail(0x20); assert_eq!(vring.queue_next_avail(), 0x20); vring.set_queue_size(0x200); - assert_eq!(vring.lock().queue.size, 0x200); + assert_eq!(vring.lock().queue.actual_size(), 0x200); vring.set_queue_event_idx(true); - assert_eq!(vring.lock().queue.event_idx_enabled, true); + assert_eq!(vring.lock().queue.state.event_idx_enabled, true); vring.set_queue_ready(true); - assert_eq!(vring.lock().queue.ready, true); + assert_eq!(vring.lock().queue.ready(), true); } #[test] From e10a471c6a32e62fae39ca29d4b8ffd5932ae70f Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 1 Nov 2021 01:21:48 +0000 Subject: [PATCH 103/139] Bump rust-vmm-ci from `7931077` to `b037be3` Bumps [rust-vmm-ci](https://github.com/rust-vmm/rust-vmm-ci) from `7931077` to `b037be3`. - [Release notes](https://github.com/rust-vmm/rust-vmm-ci/releases) - [Commits](https://github.com/rust-vmm/rust-vmm-ci/compare/7931077cdc577edc20af54bf4786de750886fb85...b037be339677c2f24b7ba676fc9ff893ad474305) --- updated-dependencies: - dependency-name: rust-vmm-ci dependency-type: direct:production ... Signed-off-by: dependabot[bot] --- rust-vmm-ci | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/rust-vmm-ci b/rust-vmm-ci index 7931077..b037be3 160000 --- a/rust-vmm-ci +++ b/rust-vmm-ci @@ -1 +1 @@ -Subproject commit 7931077cdc577edc20af54bf4786de750886fb85 +Subproject commit b037be339677c2f24b7ba676fc9ff893ad474305 From 4047c697470cc6c37e8e1835025b091d2b59c2f7 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 8 Nov 2021 01:17:21 +0000 Subject: [PATCH 104/139] Bump rust-vmm-ci from `b037be3` to `aee82cf` Bumps [rust-vmm-ci](https://github.com/rust-vmm/rust-vmm-ci) from `b037be3` to `aee82cf`. - [Release notes](https://github.com/rust-vmm/rust-vmm-ci/releases) - [Commits](https://github.com/rust-vmm/rust-vmm-ci/compare/b037be339677c2f24b7ba676fc9ff893ad474305...aee82cf0a405f2983ec493fcd55fda5a1ad03f38) --- updated-dependencies: - dependency-name: rust-vmm-ci dependency-type: direct:production ... Signed-off-by: dependabot[bot] --- rust-vmm-ci | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/rust-vmm-ci b/rust-vmm-ci index b037be3..aee82cf 160000 --- a/rust-vmm-ci +++ b/rust-vmm-ci @@ -1 +1 @@ -Subproject commit b037be339677c2f24b7ba676fc9ff893ad474305 +Subproject commit aee82cf0a405f2983ec493fcd55fda5a1ad03f38 From 264d46d4c7918b5ea4d826317dac984aed1203e7 Mon Sep 17 00:00:00 2001 From: Sergio Lopez Date: Fri, 3 Dec 2021 12:46:03 +0100 Subject: [PATCH 105/139] Update virtio-queue to rev cc1fa35 Update virtio-queue dependency to the latest available version, rev "cc1fa35". This one comes with a number of fixes and new features, such as QueueGuard and support for big-endian machines. Signed-off-by: Sergio Lopez --- Cargo.toml | 2 +- src/vring.rs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index f69c592..835be1f 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -12,7 +12,7 @@ libc = ">=0.2.39" log = ">=0.4.6" vhost = { version = "0.2", features = ["vhost-user-slave"] } virtio-bindings = "0.1" -virtio-queue = { git = "https://github.com/rust-vmm/vm-virtio", rev = "66cda80" } +virtio-queue = { git = "https://github.com/rust-vmm/vm-virtio", rev = "cc1fa35" } vm-memory = {version = "0.7", features = ["backend-mmap", "backend-atomic"]} vmm-sys-util = "0.9" diff --git a/src/vring.rs b/src/vring.rs index 0207e82..7594cca 100644 --- a/src/vring.rs +++ b/src/vring.rs @@ -493,7 +493,7 @@ mod tests { assert_eq!(vring.queue_next_avail(), 0x20); vring.set_queue_size(0x200); - assert_eq!(vring.lock().queue.actual_size(), 0x200); + assert_eq!(vring.lock().queue.state.size, 0x200); vring.set_queue_event_idx(true); assert_eq!(vring.lock().queue.state.event_idx_enabled, true); From e5a5f1fe346c9d4c8ee58dad7a412646d353c557 Mon Sep 17 00:00:00 2001 From: Liu Jiang Date: Sat, 18 Dec 2021 12:41:48 +0800 Subject: [PATCH 106/139] Refine VringStateGuard and VringStateMutGuard Previously VringStateGuard and VringStateMutGuard are defined as enum, which limits the extensibility of the interface. So convert them into traits by using the High Rank Trait Bound tricky. Signed-off-by: Liu Jiang --- Cargo.toml | 4 +-- src/vring.rs | 99 ++++++++++++++++++++++------------------------------ 2 files changed, 43 insertions(+), 60 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 835be1f..f568ab9 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -10,9 +10,9 @@ license = "Apache-2.0" [dependencies] libc = ">=0.2.39" log = ">=0.4.6" -vhost = { version = "0.2", features = ["vhost-user-slave"] } +vhost = { version = "0.3", features = ["vhost-user-slave"] } virtio-bindings = "0.1" -virtio-queue = { git = "https://github.com/rust-vmm/vm-virtio", rev = "cc1fa35" } +virtio-queue = "0.1" vm-memory = {version = "0.7", features = ["backend-mmap", "backend-atomic"]} vmm-sys-util = "0.9" diff --git a/src/vring.rs b/src/vring.rs index 7594cca..374890e 100644 --- a/src/vring.rs +++ b/src/vring.rs @@ -16,62 +16,29 @@ use virtio_queue::{Error as VirtQueError, Queue}; use vm_memory::{GuestAddress, GuestAddressSpace, GuestMemoryAtomic, GuestMemoryMmap}; use vmm_sys_util::eventfd::EventFd; -/// Struct to hold a shared reference to the underlying `VringState` object. -pub enum VringStateGuard<'a, M: GuestAddressSpace> { - /// A `MutexGuard` for a `VringState` object. - MutexGuard(MutexGuard<'a, VringState>), - /// A `ReadGuard` for a `VringState` object. - RwLockReadGuard(RwLockReadGuard<'a, VringState>), +/// Trait for objects returned by `VringT::get_ref()`. +pub trait VringStateGuard<'a, M: GuestAddressSpace> { + /// Type for guard returned by `VringT::get_ref()`. + type G: Deref>; } -impl<'a, M: GuestAddressSpace> Deref for VringStateGuard<'a, M> { - type Target = VringState; - - fn deref(&self) -> &Self::Target { - match self { - VringStateGuard::MutexGuard(v) => v.deref(), - VringStateGuard::RwLockReadGuard(v) => v.deref(), - } - } +/// Trait for objects returned by `VringT::get_mut()`. +pub trait VringStateMutGuard<'a, M: GuestAddressSpace> { + /// Type for guard returned by `VringT::get_mut()`. + type G: DerefMut>; } -/// Struct to hold an exclusive reference to the underlying `VringState` object. -pub enum VringStateMutGuard<'a, M: GuestAddressSpace> { - /// A `MutexGuard` for a `VringState` object. - MutexGuard(MutexGuard<'a, VringState>), - /// A `WriteGuard` for a `VringState` object. - RwLockWriteGuard(RwLockWriteGuard<'a, VringState>), -} - -impl<'a, M: GuestAddressSpace> Deref for VringStateMutGuard<'a, M> { - type Target = VringState; - - fn deref(&self) -> &Self::Target { - match self { - VringStateMutGuard::MutexGuard(v) => v.deref(), - VringStateMutGuard::RwLockWriteGuard(v) => v.deref(), - } - } -} - -impl<'a, M: GuestAddressSpace> DerefMut for VringStateMutGuard<'a, M> { - fn deref_mut(&mut self) -> &mut Self::Target { - match self { - VringStateMutGuard::MutexGuard(v) => v.deref_mut(), - VringStateMutGuard::RwLockWriteGuard(v) => v.deref_mut(), - } - } -} - -pub trait VringT { +pub trait VringT: + for<'a> VringStateGuard<'a, M> + for<'a> VringStateMutGuard<'a, M> +{ /// Create a new instance of Vring. fn new(mem: M, max_queue_size: u16) -> Self; /// Get an immutable reference to the kick event fd. - fn get_ref(&self) -> VringStateGuard; + fn get_ref(&self) -> >::G; /// Get a mutable reference to the kick event fd. - fn get_mut(&self) -> VringStateMutGuard; + fn get_mut(&self) -> >::G; /// Add an used descriptor into the used queue. fn add_used(&self, desc_index: u16, len: u32) -> Result<(), VirtQueError>; @@ -276,19 +243,27 @@ impl VringMutex { } } -impl VringT for VringMutex { +impl<'a, M: 'a + GuestAddressSpace> VringStateGuard<'a, M> for VringMutex { + type G = MutexGuard<'a, VringState>; +} + +impl<'a, M: 'a + GuestAddressSpace> VringStateMutGuard<'a, M> for VringMutex { + type G = MutexGuard<'a, VringState>; +} + +impl VringT for VringMutex { fn new(mem: M, max_queue_size: u16) -> Self { VringMutex { state: Arc::new(Mutex::new(VringState::new(mem, max_queue_size))), } } - fn get_ref(&self) -> VringStateGuard { - VringStateGuard::MutexGuard(self.state.lock().unwrap()) + fn get_ref(&self) -> >::G { + self.state.lock().unwrap() } - fn get_mut(&self) -> VringStateMutGuard { - VringStateMutGuard::MutexGuard(self.lock()) + fn get_mut(&self) -> >::G { + self.lock() } fn add_used(&self, desc_index: u16, len: u32) -> Result<(), VirtQueError> { @@ -370,19 +345,27 @@ impl VringRwLock { } } -impl VringT for VringRwLock { +impl<'a, M: 'a + GuestAddressSpace> VringStateGuard<'a, M> for VringRwLock { + type G = RwLockReadGuard<'a, VringState>; +} + +impl<'a, M: 'a + GuestAddressSpace> VringStateMutGuard<'a, M> for VringRwLock { + type G = RwLockWriteGuard<'a, VringState>; +} + +impl VringT for VringRwLock { fn new(mem: M, max_queue_size: u16) -> Self { VringRwLock { state: Arc::new(RwLock::new(VringState::new(mem, max_queue_size))), } } - fn get_ref(&self) -> VringStateGuard { - VringStateGuard::RwLockReadGuard(self.state.read().unwrap()) + fn get_ref(&self) -> >::G { + self.state.read().unwrap() } - fn get_mut(&self) -> VringStateMutGuard { - VringStateMutGuard::RwLockWriteGuard(self.write_lock()) + fn get_mut(&self) -> >::G { + self.write_lock() } fn add_used(&self, desc_index: u16, len: u32) -> Result<(), VirtQueError> { @@ -467,7 +450,7 @@ mod tests { let vring = VringMutex::new(mem, 0x1000); assert!(vring.get_ref().get_kick().is_none()); - assert_eq!(vring.get_ref().enabled, false); + assert_eq!(vring.get_mut().enabled, false); assert_eq!(vring.lock().queue.ready(), false); assert_eq!(vring.lock().queue.state.event_idx_enabled, false); @@ -514,7 +497,7 @@ mod tests { let eventfd = EventFd::new(0).unwrap(); let file = unsafe { File::from_raw_fd(eventfd.as_raw_fd()) }; - assert!(vring.get_ref().kick.is_none()); + assert!(vring.get_mut().kick.is_none()); assert_eq!(vring.read_kick().unwrap(), true); vring.set_kick(Some(file)); eventfd.write(1).unwrap(); From f805c3e1ffe0d29930094592e7a075262b0c54f9 Mon Sep 17 00:00:00 2001 From: Liu Jiang Date: Sat, 18 Dec 2021 19:56:59 +0800 Subject: [PATCH 107/139] Use std::io::Result to reduce duplicated code. Use std::io::Result to reduce duplicated code. Signed-off-by: Liu Jiang --- src/backend.rs | 43 +++++++++++++++++++------------------------ src/event_loop.rs | 31 +++++-------------------------- src/lib.rs | 6 ++---- 3 files changed, 26 insertions(+), 54 deletions(-) diff --git a/src/backend.rs b/src/backend.rs index a2b7d23..f7dddcc 100644 --- a/src/backend.rs +++ b/src/backend.rs @@ -18,9 +18,8 @@ //! [VhostUserBackend]: trait.VhostUserBackend.html //! [VhostUserBackendMut]: trait.VhostUserBackendMut.html -use std::io; +use std::io::Result; use std::ops::Deref; -use std::result; use std::sync::{Arc, Mutex, RwLock}; use vhost::vhost_user::message::VhostUserProtocolFeatures; @@ -71,12 +70,12 @@ where /// /// A default implementation is provided as we cannot expect all backends to implement this /// function. - fn set_config(&self, _offset: u32, _buf: &[u8]) -> result::Result<(), io::Error> { + fn set_config(&self, _offset: u32, _buf: &[u8]) -> Result<()> { Ok(()) } /// Update guest memory regions. - fn update_memory(&self, mem: GM) -> result::Result<(), io::Error>; + fn update_memory(&self, mem: GM) -> Result<()>; /// Set handler for communicating with the master by the slave communication channel. /// @@ -115,7 +114,7 @@ where evset: EventSet, vrings: &[V], thread_id: usize, - ) -> result::Result; + ) -> Result; } /// Trait without interior mutability for vhost user backend servers to implement concrete services. @@ -154,12 +153,12 @@ where /// /// A default implementation is provided as we cannot expect all backends to implement this /// function. - fn set_config(&mut self, _offset: u32, _buf: &[u8]) -> result::Result<(), io::Error> { + fn set_config(&mut self, _offset: u32, _buf: &[u8]) -> Result<()> { Ok(()) } /// Update guest memory regions. - fn update_memory(&mut self, mem: GM) -> result::Result<(), io::Error>; + fn update_memory(&mut self, mem: GM) -> Result<()>; /// Set handler for communicating with the master by the slave communication channel. /// @@ -198,7 +197,7 @@ where evset: EventSet, vrings: &[V], thread_id: usize, - ) -> result::Result; + ) -> Result; } impl, V, B> VhostUserBackend for Arc @@ -234,11 +233,11 @@ where self.deref().get_config(offset, size) } - fn set_config(&self, offset: u32, buf: &[u8]) -> Result<(), io::Error> { + fn set_config(&self, offset: u32, buf: &[u8]) -> Result<()> { self.deref().set_config(offset, buf) } - fn update_memory(&self, mem: GM) -> Result<(), io::Error> { + fn update_memory(&self, mem: GM) -> Result<()> { self.deref().update_memory(mem) } @@ -260,7 +259,7 @@ where evset: EventSet, vrings: &[V], thread_id: usize, - ) -> Result { + ) -> Result { self.deref() .handle_event(device_event, evset, vrings, thread_id) } @@ -299,11 +298,11 @@ where self.lock().unwrap().get_config(offset, size) } - fn set_config(&self, offset: u32, buf: &[u8]) -> Result<(), io::Error> { + fn set_config(&self, offset: u32, buf: &[u8]) -> Result<()> { self.lock().unwrap().set_config(offset, buf) } - fn update_memory(&self, mem: GM) -> Result<(), io::Error> { + fn update_memory(&self, mem: GM) -> Result<()> { self.lock().unwrap().update_memory(mem) } @@ -325,7 +324,7 @@ where evset: EventSet, vrings: &[V], thread_id: usize, - ) -> Result { + ) -> Result { self.lock() .unwrap() .handle_event(device_event, evset, vrings, thread_id) @@ -365,11 +364,11 @@ where self.read().unwrap().get_config(offset, size) } - fn set_config(&self, offset: u32, buf: &[u8]) -> Result<(), io::Error> { + fn set_config(&self, offset: u32, buf: &[u8]) -> Result<()> { self.write().unwrap().set_config(offset, buf) } - fn update_memory(&self, mem: GM) -> Result<(), io::Error> { + fn update_memory(&self, mem: GM) -> Result<()> { self.write().unwrap().update_memory(mem) } @@ -391,7 +390,7 @@ where evset: EventSet, vrings: &[V], thread_id: usize, - ) -> Result { + ) -> Result { self.write() .unwrap() .handle_event(device_event, evset, vrings, thread_id) @@ -402,7 +401,6 @@ where pub mod tests { use super::*; use crate::VringRwLock; - use std::io::Error; use std::sync::Mutex; use vm_memory::{GuestMemoryAtomic, GuestMemoryMmap}; @@ -454,7 +452,7 @@ pub mod tests { vec![0xa5u8; 8] } - fn set_config(&mut self, offset: u32, buf: &[u8]) -> Result<(), Error> { + fn set_config(&mut self, offset: u32, buf: &[u8]) -> Result<()> { assert_eq!(offset, 0x200); assert_eq!(buf.len(), 8); assert_eq!(buf, &[0xa5u8; 8]); @@ -462,10 +460,7 @@ pub mod tests { Ok(()) } - fn update_memory( - &mut self, - _atomic_mem: GuestMemoryAtomic, - ) -> Result<(), Error> { + fn update_memory(&mut self, _atomic_mem: GuestMemoryAtomic) -> Result<()> { Ok(()) } @@ -487,7 +482,7 @@ pub mod tests { _evset: EventSet, _vrings: &[VringRwLock], _thread_id: usize, - ) -> Result { + ) -> Result { self.events += 1; Ok(false) diff --git a/src/event_loop.rs b/src/event_loop.rs index 63e7883..d680e8f 100644 --- a/src/event_loop.rs +++ b/src/event_loop.rs @@ -4,10 +4,9 @@ // SPDX-License-Identifier: Apache-2.0 use std::fmt::{Display, Formatter}; -use std::io; +use std::io::{self, Result}; use std::marker::PhantomData; use std::os::unix::io::{AsRawFd, RawFd}; -use std::result; use vm_memory::bitmap::Bitmap; use vmm_sys_util::epoll::{ControlOperation, Epoll, EpollEvent, EventSet}; @@ -127,12 +126,7 @@ where /// /// When this event is later triggered, the backend implementation of `handle_event` will be /// called. - pub fn register_listener( - &self, - fd: RawFd, - ev_type: EventSet, - data: u64, - ) -> result::Result<(), io::Error> { + pub fn register_listener(&self, fd: RawFd, ev_type: EventSet, data: u64) -> Result<()> { // `data` range [0...num_queues] is reserved for queues and exit event. if data <= self.backend.num_queues() as u64 { Err(io::Error::from_raw_os_error(libc::EINVAL)) @@ -145,12 +139,7 @@ where /// /// If the event is triggered after this function has been called, the event will be silently /// dropped. - pub fn unregister_listener( - &self, - fd: RawFd, - ev_type: EventSet, - data: u64, - ) -> result::Result<(), io::Error> { + pub fn unregister_listener(&self, fd: RawFd, ev_type: EventSet, data: u64) -> Result<()> { // `data` range [0...num_queues] is reserved for queues and exit event. if data <= self.backend.num_queues() as u64 { Err(io::Error::from_raw_os_error(libc::EINVAL)) @@ -159,22 +148,12 @@ where } } - pub(crate) fn register_event( - &self, - fd: RawFd, - ev_type: EventSet, - data: u64, - ) -> result::Result<(), io::Error> { + pub(crate) fn register_event(&self, fd: RawFd, ev_type: EventSet, data: u64) -> Result<()> { self.epoll .ctl(ControlOperation::Add, fd, EpollEvent::new(ev_type, data)) } - pub(crate) fn unregister_event( - &self, - fd: RawFd, - ev_type: EventSet, - data: u64, - ) -> result::Result<(), io::Error> { + pub(crate) fn unregister_event(&self, fd: RawFd, ev_type: EventSet, data: u64) -> Result<()> { self.epoll .ctl(ControlOperation::Delete, fd, EpollEvent::new(ev_type, data)) } diff --git a/src/lib.rs b/src/lib.rs index 403efcc..2fd80af 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -9,8 +9,6 @@ extern crate log; use std::fmt::{Display, Formatter}; -use std::io; -use std::result; use std::sync::{Arc, Mutex}; use std::thread; @@ -48,7 +46,7 @@ pub enum Error { /// Failed creating vhost-user slave handler. CreateSlaveReqHandler(VhostUserError), /// Failed starting daemon thread. - StartDaemon(io::Error), + StartDaemon(std::io::Error), /// Failed waiting for daemon thread. WaitDaemon(std::boxed::Box), /// Failed handling a vhost-user request. @@ -69,7 +67,7 @@ impl Display for Error { } /// Result of vhost-user daemon operations. -pub type Result = result::Result; +pub type Result = std::result::Result; /// Implement a simple framework to run a vhost-user service daemon. /// From a6c815d631d23fa685efb641b12b43e685158dc2 Mon Sep 17 00:00:00 2001 From: Liu Jiang Date: Sat, 18 Dec 2021 20:46:56 +0800 Subject: [PATCH 108/139] Avoid unwrap() in VhostUserDaemon::start() The SlaveListener::accept() may return Ok(None), handle this case to avoid unexpected panic. Signed-off-by: Liu Jiang --- src/lib.rs | 22 +++++++++++++++++----- 1 file changed, 17 insertions(+), 5 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index 2fd80af..9fdb2ac 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -12,7 +12,7 @@ use std::fmt::{Display, Formatter}; use std::sync::{Arc, Mutex}; use std::thread; -use vhost::vhost_user::{Error as VhostUserError, Listener, SlaveListener}; +use vhost::vhost_user::{Error as VhostUserError, Listener, SlaveListener, SlaveReqHandler}; use vm_memory::bitmap::Bitmap; use vm_memory::mmap::NewBitmap; use vm_memory::{GuestMemoryAtomic, GuestMemoryMmap}; @@ -116,13 +116,12 @@ where /// /// This runs in an infinite loop that should be terminating once the other end of the socket /// (the VMM) disconnects. + // TODO: the current implementation has limitations that only one incoming connection will be + // handled from the listener. Should it be enhanced to support reconnection? pub fn start(&mut self, listener: Listener) -> Result<()> { let mut slave_listener = SlaveListener::new(listener, self.handler.clone()) .map_err(Error::CreateSlaveListener)?; - let mut slave_handler = slave_listener - .accept() - .map_err(Error::CreateSlaveReqHandler)? - .unwrap(); + let mut slave_handler = self.accept(&mut slave_listener)?; let handle = thread::Builder::new() .name(self.name.clone()) .spawn(move || loop { @@ -137,6 +136,19 @@ where Ok(()) } + fn accept( + &self, + slave_listener: &mut SlaveListener>>, + ) -> Result>>> { + loop { + match slave_listener.accept() { + Err(e) => return Err(Error::CreateSlaveListener(e)), + Ok(Some(v)) => return Ok(v), + Ok(None) => continue, + } + } + } + /// Wait for the thread handling the vhost-user socket connection to terminate. pub fn wait(&mut self) -> Result<()> { if let Some(handle) = self.main_thread.take() { From eb393cf3db79ca838adf8cd84b54f7aaf68839f7 Mon Sep 17 00:00:00 2001 From: Liu Jiang Date: Sat, 18 Dec 2021 23:01:51 +0800 Subject: [PATCH 109/139] Simple syntax only change to keep some order Some idea tools advises to keep all methods in the same order as trait definition when implementation the trait for structure. Signed-off-by: Liu Jiang --- src/handler.rs | 68 +++++++++++++++++++++++++------------------------- 1 file changed, 34 insertions(+), 34 deletions(-) diff --git a/src/handler.rs b/src/handler.rs index e7f84b5..55022c2 100644 --- a/src/handler.rs +++ b/src/handler.rs @@ -262,18 +262,6 @@ where Ok(()) } - fn get_protocol_features(&mut self) -> VhostUserResult { - Ok(self.backend.protocol_features()) - } - - fn set_protocol_features(&mut self, features: u64) -> VhostUserResult<()> { - // Note: slave that reported VHOST_USER_F_PROTOCOL_FEATURES must - // support this message even before VHOST_USER_SET_FEATURES was - // called. - self.acked_protocol_features = features; - Ok(()) - } - fn set_mem_table( &mut self, ctx: &[VhostUserMemoryRegion], @@ -315,10 +303,6 @@ where Ok(()) } - fn get_queue_num(&mut self) -> VhostUserResult { - Ok(self.num_queues as u64) - } - fn set_vring_num(&mut self, index: u32, num: u32) -> VhostUserResult<()> { if index as usize >= self.num_queues || num == 0 || num as usize > self.max_queue_size { return Err(VhostUserError::InvalidParam); @@ -448,6 +432,22 @@ where Ok(()) } + fn get_protocol_features(&mut self) -> VhostUserResult { + Ok(self.backend.protocol_features()) + } + + fn set_protocol_features(&mut self, features: u64) -> VhostUserResult<()> { + // Note: slave that reported VHOST_USER_F_PROTOCOL_FEATURES must + // support this message even before VHOST_USER_SET_FEATURES was + // called. + self.acked_protocol_features = features; + Ok(()) + } + + fn get_queue_num(&mut self) -> VhostUserResult { + Ok(self.num_queues as u64) + } + fn set_vring_enable(&mut self, index: u32, enable: bool) -> VhostUserResult<()> { // This request should be handled only when VHOST_USER_F_PROTOCOL_FEATURES // has been negotiated. @@ -494,6 +494,24 @@ where self.backend.set_slave_req_fd(vu_req); } + fn get_inflight_fd( + &mut self, + _inflight: &vhost::vhost_user::message::VhostUserInflight, + ) -> VhostUserResult<(vhost::vhost_user::message::VhostUserInflight, File)> { + // Assume the backend hasn't negotiated the inflight feature; it + // wouldn't be correct for the backend to do so, as we don't (yet) + // provide a way for it to handle such requests. + Err(VhostUserError::InvalidOperation) + } + + fn set_inflight_fd( + &mut self, + _inflight: &vhost::vhost_user::message::VhostUserInflight, + _file: File, + ) -> VhostUserResult<()> { + Err(VhostUserError::InvalidOperation) + } + fn get_max_mem_slots(&mut self) -> VhostUserResult { Ok(MAX_MEM_SLOTS) } @@ -561,24 +579,6 @@ where Ok(()) } - - fn get_inflight_fd( - &mut self, - _inflight: &vhost::vhost_user::message::VhostUserInflight, - ) -> VhostUserResult<(vhost::vhost_user::message::VhostUserInflight, File)> { - // Assume the backend hasn't negotiated the inflight feature; it - // wouldn't be correct for the backend to do so, as we don't (yet) - // provide a way for it to handle such requests. - Err(VhostUserError::InvalidOperation) - } - - fn set_inflight_fd( - &mut self, - _inflight: &vhost::vhost_user::message::VhostUserInflight, - _file: File, - ) -> VhostUserResult<()> { - Err(VhostUserError::InvalidOperation) - } } impl Drop for VhostUserHandler From 72d754b9ee97c6b4c48cc4211b68d7319e460f21 Mon Sep 17 00:00:00 2001 From: Liu Jiang Date: Sat, 18 Dec 2021 23:06:13 +0800 Subject: [PATCH 110/139] Simplify trait/impl bound declarations Currently trait bound declarations is a little over complex when defines traits or implements traits/structs. Let's simplfy it. Signed-off-by: Liu Jiang --- src/backend.rs | 7 +++---- src/event_loop.rs | 23 ++++++++++------------- src/handler.rs | 41 +++++++++++++++++------------------------ src/lib.rs | 10 +++------- 4 files changed, 33 insertions(+), 48 deletions(-) diff --git a/src/backend.rs b/src/backend.rs index f7dddcc..25881c2 100644 --- a/src/backend.rs +++ b/src/backend.rs @@ -33,9 +33,8 @@ use super::GM; /// Trait with interior mutability for vhost user backend servers to implement concrete services. /// -/// To support multi-threading and asynchronous IO, we enforce `the Send + Sync + 'static`. -/// So there's no plan for support of "Rc" and "RefCell". -pub trait VhostUserBackend: Send + Sync + 'static +/// To support multi-threading and asynchronous IO, we enforce `Send + Sync` bound. +pub trait VhostUserBackend: Send + Sync where V: VringT>, B: Bitmap + 'static, @@ -118,7 +117,7 @@ where } /// Trait without interior mutability for vhost user backend servers to implement concrete services. -pub trait VhostUserBackendMut: Send + Sync + 'static +pub trait VhostUserBackendMut: Send + Sync where V: VringT>, B: Bitmap + 'static, diff --git a/src/event_loop.rs b/src/event_loop.rs index d680e8f..e4d7147 100644 --- a/src/event_loop.rs +++ b/src/event_loop.rs @@ -58,12 +58,7 @@ pub type VringEpollResult = std::result::Result; /// - add file descriptors to be monitored by the epoll fd /// - remove registered file descriptors from the epoll fd /// - run the event loop to handle pending events on the epoll fd -pub struct VringEpollHandler -where - S: VhostUserBackend, - V: VringT>, - B: Bitmap + 'static, -{ +pub struct VringEpollHandler { epoll: Epoll, backend: S, vrings: Vec, @@ -72,6 +67,15 @@ where phantom: PhantomData, } +impl VringEpollHandler { + /// Send `exit event` to break the event loop. + pub fn send_exit_event(&self) { + if let Some(eventfd) = self.exit_event_fd.as_ref() { + let _ = eventfd.write(1); + } + } +} + impl VringEpollHandler where S: VhostUserBackend, @@ -115,13 +119,6 @@ where Ok(handler) } - /// Send `exit event` to break the event loop. - pub fn send_exit_event(&self) { - if let Some(eventfd) = self.exit_event_fd.as_ref() { - let _ = eventfd.write(1); - } - } - /// Register an event into the epoll fd. /// /// When this event is later triggered, the backend implementation of `handle_event` will be diff --git a/src/handler.rs b/src/handler.rs index 55022c2..f61faaa 100644 --- a/src/handler.rs +++ b/src/handler.rs @@ -71,12 +71,7 @@ struct AddrMapping { gpa_base: u64, } -pub struct VhostUserHandler -where - S: VhostUserBackend, - V: VringT>, - B: Bitmap + 'static, -{ +pub struct VhostUserHandler { backend: S, handlers: Vec>>, owned: bool, @@ -92,11 +87,12 @@ where worker_threads: Vec>>, } +// Ensure VhostUserHandler: Clone + Send + Sync + 'static. impl VhostUserHandler where - S: VhostUserBackend + Clone, + S: VhostUserBackend + Clone + 'static, V: VringT> + Clone + Send + Sync + 'static, - B: Bitmap + Clone + Send + Sync, + B: Bitmap + Clone + Send + Sync + 'static, { pub(crate) fn new(backend: S, atomic_mem: GM) -> VhostUserHandlerResult { let num_queues = backend.num_queues(); @@ -151,16 +147,7 @@ where } } -impl VhostUserHandler -where - S: VhostUserBackend, - V: VringT>, - B: Bitmap, -{ - pub(crate) fn get_epoll_handlers(&self) -> Vec>> { - self.handlers.clone() - } - +impl VhostUserHandler { pub(crate) fn send_exit_event(&self) { for handler in self.handlers.iter() { handler.send_exit_event(); @@ -176,6 +163,17 @@ where Err(VhostUserHandlerError::MissingMemoryMapping) } +} + +impl VhostUserHandler +where + S: VhostUserBackend, + V: VringT>, + B: Bitmap, +{ + pub(crate) fn get_epoll_handlers(&self) -> Vec>> { + self.handlers.clone() + } fn vring_needs_init(&self, vring: &V) -> bool { let vring_state = vring.get_ref(); @@ -581,12 +579,7 @@ where } } -impl Drop for VhostUserHandler -where - S: VhostUserBackend, - V: VringT>, - B: Bitmap, -{ +impl Drop for VhostUserHandler { fn drop(&mut self) { // Signal all working threads to exit. self.send_exit_event(); diff --git a/src/lib.rs b/src/lib.rs index 9fdb2ac..a63a7fd 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -73,12 +73,7 @@ pub type Result = std::result::Result; /// /// This structure is the public API the backend is allowed to interact with in order to run /// a fully functional vhost-user daemon. -pub struct VhostUserDaemon -where - S: VhostUserBackend, - V: VringT> + Clone + Send + Sync + 'static, - B: Bitmap + 'static, -{ +pub struct VhostUserDaemon { name: String, handler: Arc>>, main_thread: Option>>, @@ -86,7 +81,7 @@ where impl VhostUserDaemon where - S: VhostUserBackend + Clone, + S: VhostUserBackend + Clone + 'static, V: VringT> + Clone + Send + Sync + 'static, B: NewBitmap + Clone + Send + Sync, { @@ -167,6 +162,7 @@ where /// This is necessary to perform further actions like registering and unregistering some extra /// event file descriptors. pub fn get_epoll_handlers(&self) -> Vec>> { + // Do not expect poisoned lock. self.handler.lock().unwrap().get_epoll_handlers() } } From 4e16112122fb0e8f603105cf9a33f50b2cabfe0c Mon Sep 17 00:00:00 2001 From: Liu Jiang Date: Sat, 18 Dec 2021 11:29:09 +0800 Subject: [PATCH 111/139] Refine test case to increase code coverage Try to increase code coverage by adding more test code. But it actually causes dramatic decreases in code coverage:( Signed-off-by: Liu Jiang --- Cargo.toml | 1 + coverage_config_x86_64.json | 2 +- src/backend.rs | 21 ++++++++++++++++++++- src/lib.rs | 31 ++++++++++++++++++++++++++++--- 4 files changed, 50 insertions(+), 5 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index f568ab9..a06a02e 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -18,3 +18,4 @@ vmm-sys-util = "0.9" [dev-dependencies] vm-memory = {version = "0.7", features = ["backend-mmap", "backend-atomic", "backend-bitmap"]} +tempfile = "3.2.0" diff --git a/coverage_config_x86_64.json b/coverage_config_x86_64.json index fe62685..ff7665f 100644 --- a/coverage_config_x86_64.json +++ b/coverage_config_x86_64.json @@ -1,5 +1,5 @@ { - "coverage_score": 78.5, + "coverage_score": 49.6, "exclude_path": "", "crate_features": "" } diff --git a/src/backend.rs b/src/backend.rs index 25881c2..98c5780 100644 --- a/src/backend.rs +++ b/src/backend.rs @@ -401,7 +401,7 @@ pub mod tests { use super::*; use crate::VringRwLock; use std::sync::Mutex; - use vm_memory::{GuestMemoryAtomic, GuestMemoryMmap}; + use vm_memory::{GuestAddress, GuestMemoryAtomic, GuestMemoryMmap}; pub struct MockVhostBackend { events: u64, @@ -509,6 +509,13 @@ pub mod tests { backend.set_event_idx(true); assert_eq!(backend.lock().unwrap().event_idx, true); + + let _ = backend.exit_event(0).unwrap(); + + let mem = GuestMemoryAtomic::new( + GuestMemoryMmap::<()>::from_ranges(&[(GuestAddress(0x100000), 0x10000)]).unwrap(), + ); + backend.update_memory(mem).unwrap(); } #[test] @@ -532,5 +539,17 @@ pub mod tests { backend.set_event_idx(true); assert_eq!(backend.read().unwrap().event_idx, true); + + let _ = backend.exit_event(0).unwrap(); + + let mem = GuestMemoryAtomic::new( + GuestMemoryMmap::<()>::from_ranges(&[(GuestAddress(0x100000), 0x10000)]).unwrap(), + ); + backend.update_memory(mem.clone()).unwrap(); + + let vring = VringRwLock::new(mem, 0x1000); + backend + .handle_event(0x1, EventSet::IN, &[vring], 0) + .unwrap(); } } diff --git a/src/lib.rs b/src/lib.rs index a63a7fd..4851ed9 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -171,6 +171,8 @@ where mod tests { use super::backend::tests::MockVhostBackend; use super::*; + use std::os::unix::net::UnixStream; + use std::sync::Barrier; use vm_memory::{GuestAddress, GuestMemoryAtomic, GuestMemoryMmap}; #[test] @@ -179,9 +181,32 @@ mod tests { GuestMemoryMmap::<()>::from_ranges(&[(GuestAddress(0x100000), 0x10000)]).unwrap(), ); let backend = Arc::new(Mutex::new(MockVhostBackend::new())); - let daemon = VhostUserDaemon::new("test".to_owned(), backend, mem).unwrap(); + let mut daemon = VhostUserDaemon::new("test".to_owned(), backend, mem).unwrap(); - assert_eq!(daemon.get_epoll_handlers().len(), 2); - //daemon.start(Listener::new()).unwrap(); + let handlers = daemon.get_epoll_handlers(); + assert_eq!(handlers.len(), 2); + + let barrier = Arc::new(Barrier::new(2)); + let tmpdir = tempfile::tempdir().unwrap(); + let mut path = tmpdir.path().to_path_buf(); + path.push("socket"); + + let barrier2 = barrier.clone(); + let path1 = path.clone(); + let thread = thread::spawn(move || { + barrier2.wait(); + let socket = UnixStream::connect(&path1).unwrap(); + barrier2.wait(); + drop(socket) + }); + + let listener = Listener::new(&path, false).unwrap(); + barrier.wait(); + daemon.start(listener).unwrap(); + barrier.wait(); + // Above process generates a `HandleRequest(PartialMessage)` error. + daemon.wait().unwrap_err(); + daemon.wait().unwrap(); + thread.join().unwrap(); } } From 48070ccb7ca66a517117227e4fb9a8bcafecc3a5 Mon Sep 17 00:00:00 2001 From: Liu Jiang Date: Sat, 18 Dec 2021 22:38:10 +0800 Subject: [PATCH 112/139] Add test case to cover VhostUserHandler Previous patch causes dramatic code coverage decrease, it actually disclose some issue in the code coverage test. Then we add test case to cover VhostUserHandler, it actually increases the code coverage. Signed-off-by: Liu Jiang --- Cargo.toml | 2 + coverage_config_x86_64.json | 2 +- tests/vhost-user-server.rs | 297 ++++++++++++++++++++++++++++++++++++ 3 files changed, 300 insertions(+), 1 deletion(-) create mode 100644 tests/vhost-user-server.rs diff --git a/Cargo.toml b/Cargo.toml index a06a02e..b26632a 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -17,5 +17,7 @@ vm-memory = {version = "0.7", features = ["backend-mmap", "backend-atomic"]} vmm-sys-util = "0.9" [dev-dependencies] +nix = "0.22" +vhost = { version = "0.3", features = ["vhost-user-master", "vhost-user-slave"] } vm-memory = {version = "0.7", features = ["backend-mmap", "backend-atomic", "backend-bitmap"]} tempfile = "3.2.0" diff --git a/coverage_config_x86_64.json b/coverage_config_x86_64.json index ff7665f..7abd6c6 100644 --- a/coverage_config_x86_64.json +++ b/coverage_config_x86_64.json @@ -1,5 +1,5 @@ { - "coverage_score": 49.6, + "coverage_score": 87.7, "exclude_path": "", "crate_features": "" } diff --git a/tests/vhost-user-server.rs b/tests/vhost-user-server.rs new file mode 100644 index 0000000..3065647 --- /dev/null +++ b/tests/vhost-user-server.rs @@ -0,0 +1,297 @@ +use std::ffi::CString; +use std::fs::File; +use std::io::Result; +use std::os::unix::io::{AsRawFd, FromRawFd}; +use std::os::unix::net::UnixStream; +use std::path::Path; +use std::sync::{Arc, Barrier, Mutex}; +use std::thread; + +use vhost::vhost_user::message::{ + VhostUserConfigFlags, VhostUserHeaderFlag, VhostUserInflight, VhostUserProtocolFeatures, +}; +use vhost::vhost_user::{Listener, Master, SlaveFsCacheReq, VhostUserMaster}; +use vhost::{VhostBackend, VhostUserMemoryRegionInfo, VringConfigData}; +use vhost_user_backend::{VhostUserBackendMut, VhostUserDaemon, VringRwLock}; +use vm_memory::{ + FileOffset, GuestAddress, GuestAddressSpace, GuestMemory, GuestMemoryAtomic, GuestMemoryMmap, +}; +use vmm_sys_util::epoll::EventSet; +use vmm_sys_util::eventfd::EventFd; + +struct MockVhostBackend { + events: u64, + event_idx: bool, + acked_features: u64, +} + +impl MockVhostBackend { + fn new() -> Self { + MockVhostBackend { + events: 0, + event_idx: false, + acked_features: 0, + } + } +} + +impl VhostUserBackendMut for MockVhostBackend { + fn num_queues(&self) -> usize { + 2 + } + + fn max_queue_size(&self) -> usize { + 256 + } + + fn features(&self) -> u64 { + 0xffff_ffff_ffff_ffff + } + + fn acked_features(&mut self, features: u64) { + self.acked_features = features; + } + + fn protocol_features(&self) -> VhostUserProtocolFeatures { + VhostUserProtocolFeatures::all() + } + + fn set_event_idx(&mut self, enabled: bool) { + self.event_idx = enabled; + } + + fn get_config(&self, offset: u32, size: u32) -> Vec { + assert_eq!(offset, 0x200); + assert_eq!(size, 8); + + vec![0xa5u8; 8] + } + + fn set_config(&mut self, offset: u32, buf: &[u8]) -> Result<()> { + assert_eq!(offset, 0x200); + assert_eq!(buf, &[0xa5u8; 8]); + + Ok(()) + } + + fn update_memory(&mut self, atomic_mem: GuestMemoryAtomic) -> Result<()> { + let mem = atomic_mem.memory(); + let region = mem.find_region(GuestAddress(0x100000)).unwrap(); + assert_eq!(region.size(), 0x100000); + Ok(()) + } + + fn set_slave_req_fd(&mut self, _vu_req: SlaveFsCacheReq) {} + + fn queues_per_thread(&self) -> Vec { + vec![1, 1] + } + + fn exit_event(&self, _thread_index: usize) -> Option { + let event_fd = EventFd::new(0).unwrap(); + + Some(event_fd) + } + + fn handle_event( + &mut self, + _device_event: u16, + _evset: EventSet, + _vrings: &[VringRwLock], + _thread_id: usize, + ) -> Result { + self.events += 1; + + Ok(false) + } +} + +fn setup_master(path: &Path, barrier: Arc) -> Master { + barrier.wait(); + let mut master = Master::connect(path, 1).unwrap(); + master.set_hdr_flags(VhostUserHeaderFlag::NEED_REPLY); + // Wait before issue service requests. + barrier.wait(); + + let features = master.get_features().unwrap(); + let proto = master.get_protocol_features().unwrap(); + master.set_features(features).unwrap(); + master.set_protocol_features(proto).unwrap(); + assert!(proto.contains(VhostUserProtocolFeatures::REPLY_ACK)); + + master +} + +fn vhost_user_client(path: &Path, barrier: Arc) { + barrier.wait(); + let mut master = Master::connect(path, 1).unwrap(); + master.set_hdr_flags(VhostUserHeaderFlag::NEED_REPLY); + // Wait before issue service requests. + barrier.wait(); + + let features = master.get_features().unwrap(); + let proto = master.get_protocol_features().unwrap(); + master.set_features(features).unwrap(); + master.set_protocol_features(proto).unwrap(); + assert!(proto.contains(VhostUserProtocolFeatures::REPLY_ACK)); + + let queue_num = master.get_queue_num().unwrap(); + assert_eq!(queue_num, 2); + + master.set_owner().unwrap(); + //master.set_owner().unwrap_err(); + master.reset_owner().unwrap(); + master.reset_owner().unwrap(); + master.set_owner().unwrap(); + + master.set_features(features).unwrap(); + master.set_protocol_features(proto).unwrap(); + assert!(proto.contains(VhostUserProtocolFeatures::REPLY_ACK)); + + let memfd = nix::sys::memfd::memfd_create( + &CString::new("test").unwrap(), + nix::sys::memfd::MemFdCreateFlag::empty(), + ) + .unwrap(); + let file = unsafe { File::from_raw_fd(memfd) }; + file.set_len(0x100000).unwrap(); + let file_offset = FileOffset::new(file, 0); + let mem = GuestMemoryMmap::<()>::from_ranges_with_files(&[( + GuestAddress(0x100000), + 0x100000, + Some(file_offset), + )]) + .unwrap(); + let addr = mem.get_host_address(GuestAddress(0x100000)).unwrap() as u64; + let reg = mem.find_region(GuestAddress(0x100000)).unwrap(); + let fd = reg.file_offset().unwrap(); + let regions = [VhostUserMemoryRegionInfo { + guest_phys_addr: 0x100000, + memory_size: 0x100000, + userspace_addr: addr, + mmap_offset: 0, + mmap_handle: fd.file().as_raw_fd(), + }]; + master.set_mem_table(®ions).unwrap(); + + master.set_vring_num(0, 256).unwrap(); + + let config = VringConfigData { + queue_max_size: 256, + queue_size: 256, + flags: 0, + desc_table_addr: addr, + used_ring_addr: addr + 0x10000, + avail_ring_addr: addr + 0x20000, + log_addr: None, + }; + master.set_vring_addr(0, &config).unwrap(); + + let eventfd = EventFd::new(0).unwrap(); + master.set_vring_kick(0, &eventfd).unwrap(); + master.set_vring_call(0, &eventfd).unwrap(); + master.set_vring_err(0, &eventfd).unwrap(); + master.set_vring_enable(0, true).unwrap(); + + let buf = [0u8; 8]; + let (_cfg, data) = master + .get_config(0x200, 8, VhostUserConfigFlags::empty(), &buf) + .unwrap(); + assert_eq!(&data, &[0xa5u8; 8]); + master + .set_config(0x200, VhostUserConfigFlags::empty(), &data) + .unwrap(); + + let (tx, _rx) = UnixStream::pair().unwrap(); + master.set_slave_request_fd(&tx).unwrap(); + + let state = master.get_vring_base(0).unwrap(); + master.set_vring_base(0, state as u16).unwrap(); + + assert_eq!(master.get_max_mem_slots().unwrap(), 32); + let region = VhostUserMemoryRegionInfo { + guest_phys_addr: 0x800000, + memory_size: 0x100000, + userspace_addr: addr, + mmap_offset: 0, + mmap_handle: fd.file().as_raw_fd(), + }; + master.add_mem_region(®ion).unwrap(); + master.remove_mem_region(®ion).unwrap(); +} + +fn vhost_user_server(cb: fn(&Path, Arc)) { + let mem = GuestMemoryAtomic::new(GuestMemoryMmap::<()>::new()); + let backend = Arc::new(Mutex::new(MockVhostBackend::new())); + let mut daemon = VhostUserDaemon::new("test".to_owned(), backend, mem).unwrap(); + + let barrier = Arc::new(Barrier::new(2)); + let tmpdir = tempfile::tempdir().unwrap(); + let mut path = tmpdir.path().to_path_buf(); + path.push("socket"); + + let barrier2 = barrier.clone(); + let path1 = path.clone(); + let thread = thread::spawn(move || cb(&path1, barrier2)); + + let listener = Listener::new(&path, false).unwrap(); + barrier.wait(); + daemon.start(listener).unwrap(); + barrier.wait(); + + // handle service requests from clients. + thread.join().unwrap(); +} + +#[test] +fn test_vhost_user_server() { + vhost_user_server(vhost_user_client); +} + +fn vhost_user_enable(path: &Path, barrier: Arc) { + let master = setup_master(path, barrier); + master.set_owner().unwrap(); + master.set_owner().unwrap_err(); +} + +#[test] +fn test_vhost_user_enable() { + vhost_user_server(vhost_user_enable); +} + +fn vhost_user_set_inflight(path: &Path, barrier: Arc) { + let mut master = setup_master(path, barrier); + let eventfd = EventFd::new(0).unwrap(); + // No implementation for inflight_fd yet. + let inflight = VhostUserInflight { + mmap_size: 0x100000, + mmap_offset: 0, + num_queues: 1, + queue_size: 256, + }; + master + .set_inflight_fd(&inflight, eventfd.as_raw_fd()) + .unwrap_err(); +} + +#[test] +fn test_vhost_user_set_inflight() { + vhost_user_server(vhost_user_set_inflight); +} + +fn vhost_user_get_inflight(path: &Path, barrier: Arc) { + let mut master = setup_master(path, barrier); + // No implementation for inflight_fd yet. + let inflight = VhostUserInflight { + mmap_size: 0x100000, + mmap_offset: 0, + num_queues: 1, + queue_size: 256, + }; + assert!(master.get_inflight_fd(&inflight).is_err()); +} + +#[test] +fn test_vhost_user_get_inflight() { + vhost_user_server(vhost_user_get_inflight); +} From 79a6c58569ecfc373e1ffde54c382250773f393f Mon Sep 17 00:00:00 2001 From: Sergio Lopez Date: Wed, 22 Dec 2021 13:26:00 +0100 Subject: [PATCH 113/139] Add an initial CHANGELOG.md for v0.1.0 Add an initial CHANGELOG.md using the rust-vmm template, indicating that v0.1.0 is the first release Signed-off-by: Sergio Lopez --- CHANGELOG.md | 12 ++++++++++++ 1 file changed, 12 insertions(+) create mode 100644 CHANGELOG.md diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 0000000..cbcb4b2 --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,12 @@ +# Changelog +## [Unreleased] + +### Added + +### Fixed + +### Deprecated + +## [v0.1.0] + +First release From c193fa966dfc51611df48eae58c0ed5ba0ab67d8 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 27 Dec 2021 01:22:22 +0000 Subject: [PATCH 114/139] Update nix requirement from 0.22 to 0.23 Updates the requirements on [nix](https://github.com/nix-rust/nix) to permit the latest version. - [Release notes](https://github.com/nix-rust/nix/releases) - [Changelog](https://github.com/nix-rust/nix/blob/master/CHANGELOG.md) - [Commits](https://github.com/nix-rust/nix/compare/v0.22.0...v0.23.1) --- updated-dependencies: - dependency-name: nix dependency-type: direct:production ... Signed-off-by: dependabot[bot] --- Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Cargo.toml b/Cargo.toml index b26632a..08912b1 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -17,7 +17,7 @@ vm-memory = {version = "0.7", features = ["backend-mmap", "backend-atomic"]} vmm-sys-util = "0.9" [dev-dependencies] -nix = "0.22" +nix = "0.23" vhost = { version = "0.3", features = ["vhost-user-master", "vhost-user-slave"] } vm-memory = {version = "0.7", features = ["backend-mmap", "backend-atomic", "backend-bitmap"]} tempfile = "3.2.0" From 8fc1a935a0512c9a198f209cc23b0df587258aaa Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 17 Jan 2022 01:25:57 +0000 Subject: [PATCH 115/139] Bump rust-vmm-ci from `aee82cf` to `b6858b2` Bumps [rust-vmm-ci](https://github.com/rust-vmm/rust-vmm-ci) from `aee82cf` to `b6858b2`. - [Release notes](https://github.com/rust-vmm/rust-vmm-ci/releases) - [Commits](https://github.com/rust-vmm/rust-vmm-ci/compare/aee82cf0a405f2983ec493fcd55fda5a1ad03f38...b6858b232d5b7c2ecbabe1f3696dacdd7e56444d) --- updated-dependencies: - dependency-name: rust-vmm-ci dependency-type: direct:production ... Signed-off-by: dependabot[bot] --- rust-vmm-ci | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/rust-vmm-ci b/rust-vmm-ci index aee82cf..b6858b2 160000 --- a/rust-vmm-ci +++ b/rust-vmm-ci @@ -1 +1 @@ -Subproject commit aee82cf0a405f2983ec493fcd55fda5a1ad03f38 +Subproject commit b6858b232d5b7c2ecbabe1f3696dacdd7e56444d From 4204d5237d5cb489d849af3e208bd24fb0e7c0f9 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 24 Jan 2022 01:15:20 +0000 Subject: [PATCH 116/139] Bump rust-vmm-ci from `b6858b2` to `d216a46` Bumps [rust-vmm-ci](https://github.com/rust-vmm/rust-vmm-ci) from `b6858b2` to `d216a46`. - [Release notes](https://github.com/rust-vmm/rust-vmm-ci/releases) - [Commits](https://github.com/rust-vmm/rust-vmm-ci/compare/b6858b232d5b7c2ecbabe1f3696dacdd7e56444d...d216a46879096a4f1e83af3a7b6c90c26e1a688d) --- updated-dependencies: - dependency-name: rust-vmm-ci dependency-type: direct:production ... Signed-off-by: dependabot[bot] --- rust-vmm-ci | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/rust-vmm-ci b/rust-vmm-ci index b6858b2..d216a46 160000 --- a/rust-vmm-ci +++ b/rust-vmm-ci @@ -1 +1 @@ -Subproject commit b6858b232d5b7c2ecbabe1f3696dacdd7e56444d +Subproject commit d216a46879096a4f1e83af3a7b6c90c26e1a688d From 9517235567e3255e1f5aa4225d02fce14fee44ee Mon Sep 17 00:00:00 2001 From: Sergii Glushchenko Date: Mon, 24 Jan 2022 11:07:34 +0100 Subject: [PATCH 117/139] Fix clippy warnings and adjust test coverage Signed-off-by: Sergii Glushchenko --- coverage_config_x86_64.json | 2 +- src/backend.rs | 4 ++-- src/vring.rs | 18 +++++++++--------- 3 files changed, 12 insertions(+), 12 deletions(-) diff --git a/coverage_config_x86_64.json b/coverage_config_x86_64.json index 7abd6c6..38e6dec 100644 --- a/coverage_config_x86_64.json +++ b/coverage_config_x86_64.json @@ -1,5 +1,5 @@ { - "coverage_score": 87.7, + "coverage_score": 85.0, "exclude_path": "", "crate_features": "" } diff --git a/src/backend.rs b/src/backend.rs index 98c5780..e9b333c 100644 --- a/src/backend.rs +++ b/src/backend.rs @@ -508,7 +508,7 @@ pub mod tests { assert_eq!(backend.lock().unwrap().acked_features, 0xffff); backend.set_event_idx(true); - assert_eq!(backend.lock().unwrap().event_idx, true); + assert!(backend.lock().unwrap().event_idx); let _ = backend.exit_event(0).unwrap(); @@ -538,7 +538,7 @@ pub mod tests { assert_eq!(backend.read().unwrap().acked_features, 0xffff); backend.set_event_idx(true); - assert_eq!(backend.read().unwrap().event_idx, true); + assert!(backend.read().unwrap().event_idx); let _ = backend.exit_event(0).unwrap(); diff --git a/src/vring.rs b/src/vring.rs index 374890e..e5aaf69 100644 --- a/src/vring.rs +++ b/src/vring.rs @@ -450,12 +450,12 @@ mod tests { let vring = VringMutex::new(mem, 0x1000); assert!(vring.get_ref().get_kick().is_none()); - assert_eq!(vring.get_mut().enabled, false); - assert_eq!(vring.lock().queue.ready(), false); - assert_eq!(vring.lock().queue.state.event_idx_enabled, false); + assert!(!vring.get_mut().enabled); + assert!(!vring.lock().queue.ready()); + assert!(!vring.lock().queue.state.event_idx_enabled); vring.set_enabled(true); - assert_eq!(vring.get_ref().enabled, true); + assert!(vring.get_ref().enabled); vring.set_queue_info(0x100100, 0x100200, 0x100300); assert_eq!( @@ -479,10 +479,10 @@ mod tests { assert_eq!(vring.lock().queue.state.size, 0x200); vring.set_queue_event_idx(true); - assert_eq!(vring.lock().queue.state.event_idx_enabled, true); + assert!(vring.lock().queue.state.event_idx_enabled); vring.set_queue_ready(true); - assert_eq!(vring.lock().queue.ready(), true); + assert!(vring.lock().queue.ready()); } #[test] @@ -493,15 +493,15 @@ mod tests { let vring = VringMutex::new(mem, 0x1000); vring.set_enabled(true); - assert_eq!(vring.get_ref().enabled, true); + assert!(vring.get_ref().enabled); let eventfd = EventFd::new(0).unwrap(); let file = unsafe { File::from_raw_fd(eventfd.as_raw_fd()) }; assert!(vring.get_mut().kick.is_none()); - assert_eq!(vring.read_kick().unwrap(), true); + assert!(vring.read_kick().unwrap()); vring.set_kick(Some(file)); eventfd.write(1).unwrap(); - assert_eq!(vring.read_kick().unwrap(), true); + assert!(vring.read_kick().unwrap()); assert!(vring.get_ref().kick.is_some()); vring.set_kick(None); assert!(vring.get_ref().kick.is_none()); From 1c4ee49a89025bc67d5b5c7ea3718a09e656097e Mon Sep 17 00:00:00 2001 From: Sebastien Boeuf Date: Wed, 2 Feb 2022 09:35:55 +0100 Subject: [PATCH 118/139] Implement AsRawFd for VringEpollHandler In order to let the consumer of the crate access directly the file descriptor related to the VringEpollHandler, we implement the AsRawFd trait. Signed-off-by: Sebastien Boeuf --- coverage_config_x86_64.json | 2 +- src/event_loop.rs | 8 ++++++++ 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/coverage_config_x86_64.json b/coverage_config_x86_64.json index 38e6dec..374cba7 100644 --- a/coverage_config_x86_64.json +++ b/coverage_config_x86_64.json @@ -1,5 +1,5 @@ { - "coverage_score": 85.0, + "coverage_score": 85.1, "exclude_path": "", "crate_features": "" } diff --git a/src/event_loop.rs b/src/event_loop.rs index e4d7147..9083e13 100644 --- a/src/event_loop.rs +++ b/src/event_loop.rs @@ -226,6 +226,12 @@ where } } +impl AsRawFd for VringEpollHandler { + fn as_raw_fd(&self) -> RawFd { + self.epoll.as_raw_fd() + } +} + #[cfg(test)] mod tests { use super::super::backend::tests::MockVhostBackend; @@ -269,5 +275,7 @@ mod tests { handler .unregister_listener(eventfd.as_raw_fd(), EventSet::IN, 1) .unwrap_err(); + // Check we retrieve the correct file descriptor + assert_eq!(handler.as_raw_fd(), handler.epoll.as_raw_fd()); } } From bbc892ba4526bdf8101252f7aa51832d1f2eeabd Mon Sep 17 00:00:00 2001 From: Sebastien Boeuf Date: Wed, 2 Feb 2022 16:09:25 +0100 Subject: [PATCH 119/139] lib: Allow the daemon to run as a client In order to support vhost-user client mode, we introduce a new method start_client() to VhostUserDaemon. It allows the daemon to connect to the VMM side running as the server in this case. Signed-off-by: Sebastien Boeuf --- coverage_config_x86_64.json | 2 +- src/lib.rs | 88 ++++++++++++++++++++++++++++++------- 2 files changed, 74 insertions(+), 16 deletions(-) diff --git a/coverage_config_x86_64.json b/coverage_config_x86_64.json index 374cba7..38e6dec 100644 --- a/coverage_config_x86_64.json +++ b/coverage_config_x86_64.json @@ -1,5 +1,5 @@ { - "coverage_score": 85.1, + "coverage_score": 85.0, "exclude_path": "", "crate_features": "" } diff --git a/src/lib.rs b/src/lib.rs index 4851ed9..c65a19e 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -106,7 +106,39 @@ where }) } - /// Connect to the vhost-user socket and run a dedicated thread handling all requests coming + /// Run a dedicated thread handling all requests coming through the socket. + /// This runs in an infinite loop that should be terminating once the other + /// end of the socket (the VMM) hangs up. + /// + /// This function is the common code for starting a new daemon, no matter if + /// it acts as a client or a server. + fn start_daemon( + &mut self, + mut handler: SlaveReqHandler>>, + ) -> Result<()> { + let handle = thread::Builder::new() + .name(self.name.clone()) + .spawn(move || loop { + handler.handle_request().map_err(Error::HandleRequest)?; + }) + .map_err(Error::StartDaemon)?; + + self.main_thread = Some(handle); + + Ok(()) + } + + /// Connect to the vhost-user socket and run a dedicated thread handling + /// all requests coming through this socket. This runs in an infinite loop + /// that should be terminating once the other end of the socket (the VMM) + /// hangs up. + pub fn start_client(&mut self, socket_path: &str) -> Result<()> { + let slave_handler = SlaveReqHandler::connect(socket_path, self.handler.clone()) + .map_err(Error::CreateSlaveReqHandler)?; + self.start_daemon(slave_handler) + } + + /// Listen to the vhost-user socket and run a dedicated thread handling all requests coming /// through this socket. /// /// This runs in an infinite loop that should be terminating once the other end of the socket @@ -116,19 +148,8 @@ where pub fn start(&mut self, listener: Listener) -> Result<()> { let mut slave_listener = SlaveListener::new(listener, self.handler.clone()) .map_err(Error::CreateSlaveListener)?; - let mut slave_handler = self.accept(&mut slave_listener)?; - let handle = thread::Builder::new() - .name(self.name.clone()) - .spawn(move || loop { - slave_handler - .handle_request() - .map_err(Error::HandleRequest)?; - }) - .map_err(Error::StartDaemon)?; - - self.main_thread = Some(handle); - - Ok(()) + let slave_handler = self.accept(&mut slave_listener)?; + self.start_daemon(slave_handler) } fn accept( @@ -171,7 +192,7 @@ where mod tests { use super::backend::tests::MockVhostBackend; use super::*; - use std::os::unix::net::UnixStream; + use std::os::unix::net::{UnixListener, UnixStream}; use std::sync::Barrier; use vm_memory::{GuestAddress, GuestMemoryAtomic, GuestMemoryMmap}; @@ -209,4 +230,41 @@ mod tests { daemon.wait().unwrap(); thread.join().unwrap(); } + + #[test] + fn test_new_daemon_client() { + let mem = GuestMemoryAtomic::new( + GuestMemoryMmap::<()>::from_ranges(&[(GuestAddress(0x100000), 0x10000)]).unwrap(), + ); + let backend = Arc::new(Mutex::new(MockVhostBackend::new())); + let mut daemon = VhostUserDaemon::new("test".to_owned(), backend, mem).unwrap(); + + let handlers = daemon.get_epoll_handlers(); + assert_eq!(handlers.len(), 2); + + let barrier = Arc::new(Barrier::new(2)); + let tmpdir = tempfile::tempdir().unwrap(); + let mut path = tmpdir.path().to_path_buf(); + path.push("socket"); + + let barrier2 = barrier.clone(); + let path1 = path.clone(); + let thread = thread::spawn(move || { + let listener = UnixListener::bind(&path1).unwrap(); + barrier2.wait(); + let (stream, _) = listener.accept().unwrap(); + barrier2.wait(); + drop(stream) + }); + + barrier.wait(); + daemon + .start_client(path.as_path().to_str().unwrap()) + .unwrap(); + barrier.wait(); + // Above process generates a `HandleRequest(PartialMessage)` error. + daemon.wait().unwrap_err(); + daemon.wait().unwrap(); + thread.join().unwrap(); + } } From 6e66d2c1a4c460fa7306bd890e7a99e64a0254d6 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 28 Feb 2022 01:13:24 +0000 Subject: [PATCH 120/139] Bump rust-vmm-ci from `d216a46` to `99fe2eb` Bumps [rust-vmm-ci](https://github.com/rust-vmm/rust-vmm-ci) from `d216a46` to `99fe2eb`. - [Release notes](https://github.com/rust-vmm/rust-vmm-ci/releases) - [Commits](https://github.com/rust-vmm/rust-vmm-ci/compare/d216a46879096a4f1e83af3a7b6c90c26e1a688d...99fe2eb2e05d1b2cbeed6fb00b754e8f1c5b2f81) --- updated-dependencies: - dependency-name: rust-vmm-ci dependency-type: direct:production ... Signed-off-by: dependabot[bot] --- rust-vmm-ci | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/rust-vmm-ci b/rust-vmm-ci index d216a46..99fe2eb 160000 --- a/rust-vmm-ci +++ b/rust-vmm-ci @@ -1 +1 @@ -Subproject commit d216a46879096a4f1e83af3a7b6c90c26e1a688d +Subproject commit 99fe2eb2e05d1b2cbeed6fb00b754e8f1c5b2f81 From ebb6d93fe0043be9ebca97eaf281a02762ffb527 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 7 Mar 2022 01:26:42 +0000 Subject: [PATCH 121/139] Update virtio-queue requirement from 0.1 to 0.2 Updates the requirements on [virtio-queue](https://github.com/rust-vmm/vm-virtio) to permit the latest version. - [Release notes](https://github.com/rust-vmm/vm-virtio/releases) - [Commits](https://github.com/rust-vmm/vm-virtio/compare/virtio-queue-v0.1.0...virtio-queue-v0.2.0) --- updated-dependencies: - dependency-name: virtio-queue dependency-type: direct:production ... Signed-off-by: dependabot[bot] --- Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Cargo.toml b/Cargo.toml index 08912b1..cfb60c9 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -12,7 +12,7 @@ libc = ">=0.2.39" log = ">=0.4.6" vhost = { version = "0.3", features = ["vhost-user-slave"] } virtio-bindings = "0.1" -virtio-queue = "0.1" +virtio-queue = "0.2" vm-memory = {version = "0.7", features = ["backend-mmap", "backend-atomic"]} vmm-sys-util = "0.9" From 355a9ce13c2238da8ccd9f63eeca8fa15d067988 Mon Sep 17 00:00:00 2001 From: Sebastien Boeuf Date: Mon, 7 Mar 2022 10:41:54 +0100 Subject: [PATCH 122/139] Release v0.2.0 This release implements AsRawFd for VringEpollHandler, as well as adds a way for running the vhost-user daemon as a client. Both of these are required by Cloud Hypervisor to rely on the vhost-user-backend crate. Fixes #62 Signed-off-by: Sebastien Boeuf --- CHANGELOG.md | 9 ++++++++- Cargo.toml | 2 +- 2 files changed, 9 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index cbcb4b2..8f06f64 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,13 @@ ### Deprecated -## [v0.1.0] +## v0.2.0 + +### Added + +- Ability to run the daemon as a client +- VringEpollHandler implements AsRawFd + +## v0.1.0 First release diff --git a/Cargo.toml b/Cargo.toml index cfb60c9..7bacf04 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "vhost-user-backend" -version = "0.1.0" +version = "0.2.0" authors = ["The Cloud Hypervisor Authors"] keywords = ["vhost-user", "virtio"] description = "A framework to build vhost-user backend service daemon" From 4fde808bd83bbe6f8409897db5ee299fa15daaae Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 21 Mar 2022 01:27:54 +0000 Subject: [PATCH 123/139] Update vhost requirement from 0.3 to 0.4 Updates the requirements on [vhost](https://github.com/rust-vmm/vhost) to permit the latest version. - [Release notes](https://github.com/rust-vmm/vhost/releases) - [Changelog](https://github.com/rust-vmm/vhost/blob/main/CHANGELOG.md) - [Commits](https://github.com/rust-vmm/vhost/compare/v0.3.0...v0.4.0) --- updated-dependencies: - dependency-name: vhost dependency-type: direct:production ... Signed-off-by: dependabot[bot] --- Cargo.toml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 7bacf04..f283307 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -10,7 +10,7 @@ license = "Apache-2.0" [dependencies] libc = ">=0.2.39" log = ">=0.4.6" -vhost = { version = "0.3", features = ["vhost-user-slave"] } +vhost = { version = "0.4", features = ["vhost-user-slave"] } virtio-bindings = "0.1" virtio-queue = "0.2" vm-memory = {version = "0.7", features = ["backend-mmap", "backend-atomic"]} @@ -18,6 +18,6 @@ vmm-sys-util = "0.9" [dev-dependencies] nix = "0.23" -vhost = { version = "0.3", features = ["vhost-user-master", "vhost-user-slave"] } +vhost = { version = "0.4", features = ["vhost-user-master", "vhost-user-slave"] } vm-memory = {version = "0.7", features = ["backend-mmap", "backend-atomic", "backend-bitmap"]} tempfile = "3.2.0" From 9f1bd2b99e8da3440b3ea5d8a425722e6a37d7d6 Mon Sep 17 00:00:00 2001 From: Sebastien Boeuf Date: Fri, 25 Mar 2022 16:16:08 +0100 Subject: [PATCH 124/139] Release v0.3.0 This release includes the latest rust-vmm/vhost crate (v0.4.0). Signed-off-by: Sebastien Boeuf --- CHANGELOG.md | 8 ++++++++ Cargo.toml | 2 +- 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 8f06f64..3ee1607 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,10 +3,18 @@ ### Added +### Changed + ### Fixed ### Deprecated +## v0.3.0 + +### Changed + +- Moved to rust-vmm/vhost v0.4.0 + ## v0.2.0 ### Added diff --git a/Cargo.toml b/Cargo.toml index f283307..8b3878d 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "vhost-user-backend" -version = "0.2.0" +version = "0.3.0" authors = ["The Cloud Hypervisor Authors"] keywords = ["vhost-user", "virtio"] description = "A framework to build vhost-user backend service daemon" From 7545d23e241f6c971023ddd880a183c735d0ab74 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 25 Apr 2022 02:19:14 +0000 Subject: [PATCH 125/139] Update nix requirement from 0.23 to 0.24 Updates the requirements on [nix](https://github.com/nix-rust/nix) to permit the latest version. - [Release notes](https://github.com/nix-rust/nix/releases) - [Changelog](https://github.com/nix-rust/nix/blob/master/CHANGELOG.md) - [Commits](https://github.com/nix-rust/nix/compare/v0.23.0...v0.24.1) --- updated-dependencies: - dependency-name: nix dependency-type: direct:production ... Signed-off-by: dependabot[bot] --- Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Cargo.toml b/Cargo.toml index 8b3878d..5e4b192 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -17,7 +17,7 @@ vm-memory = {version = "0.7", features = ["backend-mmap", "backend-atomic"]} vmm-sys-util = "0.9" [dev-dependencies] -nix = "0.23" +nix = "0.24" vhost = { version = "0.4", features = ["vhost-user-master", "vhost-user-slave"] } vm-memory = {version = "0.7", features = ["backend-mmap", "backend-atomic", "backend-bitmap"]} tempfile = "3.2.0" From 12b37dcb0739365cde14f65a0c47546a1dfd20cd Mon Sep 17 00:00:00 2001 From: Rob Bradford Date: Mon, 11 Apr 2022 09:33:24 +0100 Subject: [PATCH 126/139] build: Relax vm-memory dependency This allows the use of newer versions of the vm-memory crate when combined with other dependencies. Signed-off-by: Rob Bradford --- Cargo.toml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 5e4b192..e5e339e 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -13,11 +13,11 @@ log = ">=0.4.6" vhost = { version = "0.4", features = ["vhost-user-slave"] } virtio-bindings = "0.1" virtio-queue = "0.2" -vm-memory = {version = "0.7", features = ["backend-mmap", "backend-atomic"]} +vm-memory = {version = ">=0.7", features = ["backend-mmap", "backend-atomic"]} vmm-sys-util = "0.9" [dev-dependencies] nix = "0.24" vhost = { version = "0.4", features = ["vhost-user-master", "vhost-user-slave"] } -vm-memory = {version = "0.7", features = ["backend-mmap", "backend-atomic", "backend-bitmap"]} +vm-memory = {version = ">=0.7", features = ["backend-mmap", "backend-atomic", "backend-bitmap"]} tempfile = "3.2.0" From 14f58eda14076e973704d4f904850be1146fbb05 Mon Sep 17 00:00:00 2001 From: Rob Bradford Date: Wed, 11 May 2022 10:28:10 +0100 Subject: [PATCH 127/139] build: Bump virtio-queue version to 0.3 Signed-off-by: Rob Bradford --- Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Cargo.toml b/Cargo.toml index e5e339e..df41ffc 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -12,7 +12,7 @@ libc = ">=0.2.39" log = ">=0.4.6" vhost = { version = "0.4", features = ["vhost-user-slave"] } virtio-bindings = "0.1" -virtio-queue = "0.2" +virtio-queue = "0.3" vm-memory = {version = ">=0.7", features = ["backend-mmap", "backend-atomic"]} vmm-sys-util = "0.9" From ea2e3647f58fa64abe2e3eb4a989c25b9840e104 Mon Sep 17 00:00:00 2001 From: Sergio Lopez Date: Wed, 1 Jun 2022 10:33:33 +0200 Subject: [PATCH 128/139] Release v0.4.0 This release aligns its dependencies with rust-vmm's vhost infrastructure by moving to virtio-queue v0.3.0 and relaxing the vm-memory dependency to require ">=0.7". Signed-off-by: Sergio Lopez --- CHANGELOG.md | 7 +++++++ Cargo.toml | 2 +- 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 3ee1607..b91d91f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,6 +9,13 @@ ### Deprecated +## v0.4.0 + +### Changed + +- Moved to rust-vmm/virtio-queue v0.3.0 +- Relaxed rust-vmm/vm-memory dependency to require ">=0.7" + ## v0.3.0 ### Changed diff --git a/Cargo.toml b/Cargo.toml index df41ffc..8f4b607 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "vhost-user-backend" -version = "0.3.0" +version = "0.4.0" authors = ["The Cloud Hypervisor Authors"] keywords = ["vhost-user", "virtio"] description = "A framework to build vhost-user backend service daemon" From 5dc8b56185b06eaeac3c8e7290253dd0b3823dc0 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 27 Jun 2022 02:06:11 +0000 Subject: [PATCH 129/139] build(deps): update virtio-queue requirement from 0.3 to 0.4 Updates the requirements on [virtio-queue](https://github.com/rust-vmm/vm-virtio) to permit the latest version. - [Release notes](https://github.com/rust-vmm/vm-virtio/releases) - [Commits](https://github.com/rust-vmm/vm-virtio/compare/virtio-queue-v0.3.0...virtio-queue-v0.4.0) --- updated-dependencies: - dependency-name: virtio-queue dependency-type: direct:production ... Signed-off-by: dependabot[bot] --- Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Cargo.toml b/Cargo.toml index 8f4b607..27f9153 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -12,7 +12,7 @@ libc = ">=0.2.39" log = ">=0.4.6" vhost = { version = "0.4", features = ["vhost-user-slave"] } virtio-bindings = "0.1" -virtio-queue = "0.3" +virtio-queue = "0.4" vm-memory = {version = ">=0.7", features = ["backend-mmap", "backend-atomic"]} vmm-sys-util = "0.9" From 9ff4acb259f85f3442c025cdc898fa1e2cad11a0 Mon Sep 17 00:00:00 2001 From: Sergio Lopez Date: Tue, 5 Jul 2022 10:05:57 +0200 Subject: [PATCH 130/139] Release v0.5.0 This release aligns its dependencies with rust-vmm's vhost infrastructure by moving to virtio-queue v0.4.0. Signed-off-by: Sergio Lopez --- CHANGELOG.md | 6 ++++++ Cargo.toml | 2 +- 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index b91d91f..8e38b62 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,6 +9,12 @@ ### Deprecated +## v0.5.0 + +### Changed + +- Moved to rust-vmm/virtio-queue v0.4.0 + ## v0.4.0 ### Changed diff --git a/Cargo.toml b/Cargo.toml index 27f9153..8e2de50 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "vhost-user-backend" -version = "0.4.0" +version = "0.5.0" authors = ["The Cloud Hypervisor Authors"] keywords = ["vhost-user", "virtio"] description = "A framework to build vhost-user backend service daemon" From 6749863466f009a5802b7eab0d3c0afa129a0221 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 18 Jul 2022 01:35:24 +0000 Subject: [PATCH 131/139] build(deps): update vmm-sys-util requirement from 0.9 to 0.10 Updates the requirements on [vmm-sys-util](https://github.com/rust-vmm/vmm-sys-util) to permit the latest version. - [Release notes](https://github.com/rust-vmm/vmm-sys-util/releases) - [Changelog](https://github.com/rust-vmm/vmm-sys-util/blob/main/CHANGELOG.md) - [Commits](https://github.com/rust-vmm/vmm-sys-util/compare/v0.9.0...v0.10.0) --- updated-dependencies: - dependency-name: vmm-sys-util dependency-type: direct:production ... Signed-off-by: dependabot[bot] --- Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Cargo.toml b/Cargo.toml index 8e2de50..e5a146f 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -14,7 +14,7 @@ vhost = { version = "0.4", features = ["vhost-user-slave"] } virtio-bindings = "0.1" virtio-queue = "0.4" vm-memory = {version = ">=0.7", features = ["backend-mmap", "backend-atomic"]} -vmm-sys-util = "0.9" +vmm-sys-util = "0.10" [dev-dependencies] nix = "0.24" From 9f5a27225b82b43432176641a3fed286ae8c2bb2 Mon Sep 17 00:00:00 2001 From: Jiang Liu Date: Tue, 19 Jul 2022 11:49:10 +0800 Subject: [PATCH 132/139] Prepare for releasing v0.5.1 Prepare for releasing v0.5.1 to upgrade vmm-sys-util to 0.10.0 Signed-off-by: Liu Jiang --- CHANGELOG.md | 5 +++++ Cargo.toml | 2 +- 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 8e38b62..af31974 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,6 +9,11 @@ ### Deprecated +## v0.5.1 + +### Changed +- Moved to rust-vmm/vmm-sys-util 0.10.0 + ## v0.5.0 ### Changed diff --git a/Cargo.toml b/Cargo.toml index e5a146f..b40dc62 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "vhost-user-backend" -version = "0.5.0" +version = "0.5.1" authors = ["The Cloud Hypervisor Authors"] keywords = ["vhost-user", "virtio"] description = "A framework to build vhost-user backend service daemon" From a94e14e81a486b2b0eac64164fe7d30e13f6b4d7 Mon Sep 17 00:00:00 2001 From: Sebastien Boeuf Date: Wed, 20 Jul 2022 14:35:33 +0200 Subject: [PATCH 133/139] handler: Fix vring initialization logic Since it's the guest decision to rely on polling or interrupts to be notified about used descriptors in the used ring, we can't expect an EventFd to be set through SET_VRING_CALL if the guest chose the polling method. On the other hand, we always expect an EventFd to be provided through SET_VRING_KICK since the current way of handling new descriptors is exclusively by receiving an event through an eventfd (no polling mode has been implemented). That's why a vring should be initialized based on its state and if the EventFd related to VRING_KICK has been set. Signed-off-by: Sebastien Boeuf --- src/handler.rs | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/src/handler.rs b/src/handler.rs index f61faaa..a3bd4ca 100644 --- a/src/handler.rs +++ b/src/handler.rs @@ -179,14 +179,11 @@ where let vring_state = vring.get_ref(); // If the vring wasn't initialized and we already have an EventFd for - // both VRING_KICK and VRING_CALL, initialize it now. - !vring_state.get_queue().ready() - && vring_state.get_call().is_some() - && vring_state.get_kick().is_some() + // VRING_KICK, initialize it now. + !vring_state.get_queue().ready() && vring_state.get_kick().is_some() } fn initialize_vring(&self, vring: &V, index: u8) -> VhostUserResult<()> { - assert!(vring.get_ref().get_call().is_some()); assert!(vring.get_ref().get_kick().is_some()); if let Some(fd) = vring.get_ref().get_kick() { From 8cfe7cec2c6da03353d5ba13c2a5f863febaca34 Mon Sep 17 00:00:00 2001 From: Andreea Florescu Date: Thu, 21 Jul 2022 13:03:59 +0000 Subject: [PATCH 134/139] integrate virtio-queue The virtio-queue interface is updated to take into consideration error cases and to export a single Queue. Signed-off-by: Andreea Florescu --- Cargo.toml | 2 +- src/backend.rs | 2 +- src/event_loop.rs | 2 +- src/handler.rs | 13 +++++- src/vring.rs | 111 +++++++++++++++++++++++++++------------------- 5 files changed, 79 insertions(+), 51 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index b40dc62..0195ca7 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -12,7 +12,7 @@ libc = ">=0.2.39" log = ">=0.4.6" vhost = { version = "0.4", features = ["vhost-user-slave"] } virtio-bindings = "0.1" -virtio-queue = "0.4" +virtio-queue = "0.5.0" vm-memory = {version = ">=0.7", features = ["backend-mmap", "backend-atomic"]} vmm-sys-util = "0.10" diff --git a/src/backend.rs b/src/backend.rs index e9b333c..23c6fa5 100644 --- a/src/backend.rs +++ b/src/backend.rs @@ -547,7 +547,7 @@ pub mod tests { ); backend.update_memory(mem.clone()).unwrap(); - let vring = VringRwLock::new(mem, 0x1000); + let vring = VringRwLock::new(mem, 0x1000).unwrap(); backend .handle_event(0x1, EventSet::IN, &[vring], 0) .unwrap(); diff --git a/src/event_loop.rs b/src/event_loop.rs index 9083e13..db19781 100644 --- a/src/event_loop.rs +++ b/src/event_loop.rs @@ -246,7 +246,7 @@ mod tests { let mem = GuestMemoryAtomic::new( GuestMemoryMmap::<()>::from_ranges(&[(GuestAddress(0x100000), 0x10000)]).unwrap(), ); - let vring = VringRwLock::new(mem, 0x1000); + let vring = VringRwLock::new(mem, 0x1000).unwrap(); let backend = Arc::new(Mutex::new(MockVhostBackend::new())); let handler = VringEpollHandler::new(backend, vec![vring], 0x1).unwrap(); diff --git a/src/handler.rs b/src/handler.rs index a3bd4ca..0b3c417 100644 --- a/src/handler.rs +++ b/src/handler.rs @@ -20,6 +20,7 @@ use vhost::vhost_user::{ VhostUserSlaveReqHandlerMut, }; use virtio_bindings::bindings::virtio_ring::VIRTIO_RING_F_EVENT_IDX; +use virtio_queue::{Error as VirtQueError, QueueT}; use vm_memory::bitmap::Bitmap; use vm_memory::mmap::NewBitmap; use vm_memory::{ @@ -38,6 +39,8 @@ const MAX_MEM_SLOTS: u64 = 32; #[derive(Debug)] /// Errors related to vhost-user handler. pub enum VhostUserHandlerError { + /// Failed to create a `Vring`. + CreateVring(VirtQueError), /// Failed to create vring worker. CreateEpollHandler(VringEpollError), /// Failed to spawn vring worker. @@ -49,6 +52,9 @@ pub enum VhostUserHandlerError { impl std::fmt::Display for VhostUserHandlerError { fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { match self { + VhostUserHandlerError::CreateVring(e) => { + write!(f, "failed to create vring: {}", e) + } VhostUserHandlerError::CreateEpollHandler(e) => { write!(f, "failed to create vring epoll handler: {}", e) } @@ -101,7 +107,8 @@ where let mut vrings = Vec::new(); for _ in 0..num_queues { - let vring = V::new(atomic_mem.clone(), max_queue_size as u16); + let vring = V::new(atomic_mem.clone(), max_queue_size as u16) + .map_err(VhostUserHandlerError::CreateVring)?; vrings.push(vring); } @@ -329,7 +336,9 @@ where let used_ring = self.vmm_va_to_gpa(used).map_err(|e| { VhostUserError::ReqHandlerError(io::Error::new(io::ErrorKind::Other, e)) })?; - self.vrings[index as usize].set_queue_info(desc_table, avail_ring, used_ring); + self.vrings[index as usize] + .set_queue_info(desc_table, avail_ring, used_ring) + .map_err(|_| VhostUserError::InvalidParam)?; Ok(()) } else { Err(VhostUserError::InvalidParam) diff --git a/src/vring.rs b/src/vring.rs index e5aaf69..0783676 100644 --- a/src/vring.rs +++ b/src/vring.rs @@ -12,7 +12,7 @@ use std::os::unix::io::{FromRawFd, IntoRawFd}; use std::result::Result; use std::sync::{Arc, Mutex, MutexGuard, RwLock, RwLockReadGuard, RwLockWriteGuard}; -use virtio_queue::{Error as VirtQueError, Queue}; +use virtio_queue::{Error as VirtQueError, Queue, QueueT}; use vm_memory::{GuestAddress, GuestAddressSpace, GuestMemoryAtomic, GuestMemoryMmap}; use vmm_sys_util::eventfd::EventFd; @@ -32,7 +32,9 @@ pub trait VringT: for<'a> VringStateGuard<'a, M> + for<'a> VringStateMutGuard<'a, M> { /// Create a new instance of Vring. - fn new(mem: M, max_queue_size: u16) -> Self; + fn new(mem: M, max_queue_size: u16) -> Result + where + Self: Sized; /// Get an immutable reference to the kick event fd. fn get_ref(&self) -> >::G; @@ -59,7 +61,12 @@ pub trait VringT: fn set_enabled(&self, enabled: bool); /// Set queue addresses for descriptor table, available ring and used ring. - fn set_queue_info(&self, desc_table: u64, avail_ring: u64, used_ring: u64); + fn set_queue_info( + &self, + desc_table: u64, + avail_ring: u64, + used_ring: u64, + ) -> Result<(), VirtQueError>; /// Get queue next avail head. fn queue_next_avail(&self) -> u16; @@ -94,38 +101,41 @@ pub trait VringT: /// This struct maintains all information of a virito queue, and could be used as an `VringT` /// object for single-threaded context. pub struct VringState> { - queue: Queue, + queue: Queue, kick: Option, call: Option, err: Option, enabled: bool, + mem: M, } impl VringState { /// Create a new instance of Vring. - fn new(mem: M, max_queue_size: u16) -> Self { - VringState { - queue: Queue::new(mem, max_queue_size), + fn new(mem: M, max_queue_size: u16) -> Result { + Ok(VringState { + queue: Queue::new(max_queue_size)?, kick: None, call: None, err: None, enabled: false, - } + mem, + }) } /// Get an immutable reference to the underlying raw `Queue` object. - pub fn get_queue(&self) -> &Queue { + pub fn get_queue(&self) -> &Queue { &self.queue } /// Get a mutable reference to the underlying raw `Queue` object. - pub fn get_queue_mut(&mut self) -> &mut Queue { + pub fn get_queue_mut(&mut self) -> &mut Queue { &mut self.queue } /// Add an used descriptor into the used queue. pub fn add_used(&mut self, desc_index: u16, len: u32) -> Result<(), VirtQueError> { - self.queue.add_used(desc_index, len) + self.queue + .add_used(self.mem.memory().deref(), desc_index, len) } /// Notify the vhost-user master that used descriptors have been put into the used queue. @@ -139,17 +149,17 @@ impl VringState { /// Enable event notification for queue. pub fn enable_notification(&mut self) -> Result { - self.queue.enable_notification() + self.queue.enable_notification(self.mem.memory().deref()) } /// Disable event notification for queue. pub fn disable_notification(&mut self) -> Result<(), VirtQueError> { - self.queue.disable_notification() + self.queue.disable_notification(self.mem.memory().deref()) } /// Check whether a notification to the guest is needed. pub fn needs_notification(&mut self) -> Result { - self.queue.needs_notification() + self.queue.needs_notification(self.mem.memory().deref()) } /// Set vring enabled state. @@ -158,10 +168,18 @@ impl VringState { } /// Set queue addresses for descriptor table, available ring and used ring. - pub fn set_queue_info(&mut self, desc_table: u64, avail_ring: u64, used_ring: u64) { - self.queue.state.desc_table = GuestAddress(desc_table); - self.queue.state.avail_ring = GuestAddress(avail_ring); - self.queue.state.used_ring = GuestAddress(used_ring); + pub fn set_queue_info( + &mut self, + desc_table: u64, + avail_ring: u64, + used_ring: u64, + ) -> Result<(), VirtQueError> { + self.queue + .try_set_desc_table_address(GuestAddress(desc_table))?; + self.queue + .try_set_avail_ring_address(GuestAddress(avail_ring))?; + self.queue + .try_set_used_ring_address(GuestAddress(used_ring)) } /// Get queue next avail head. @@ -252,10 +270,10 @@ impl<'a, M: 'a + GuestAddressSpace> VringStateMutGuard<'a, M> for VringMutex } impl VringT for VringMutex { - fn new(mem: M, max_queue_size: u16) -> Self { - VringMutex { - state: Arc::new(Mutex::new(VringState::new(mem, max_queue_size))), - } + fn new(mem: M, max_queue_size: u16) -> Result { + Ok(VringMutex { + state: Arc::new(Mutex::new(VringState::new(mem, max_queue_size)?)), + }) } fn get_ref(&self) -> >::G { @@ -290,7 +308,12 @@ impl VringT for VringMutex { self.lock().set_enabled(enabled) } - fn set_queue_info(&self, desc_table: u64, avail_ring: u64, used_ring: u64) { + fn set_queue_info( + &self, + desc_table: u64, + avail_ring: u64, + used_ring: u64, + ) -> Result<(), VirtQueError> { self.lock() .set_queue_info(desc_table, avail_ring, used_ring) } @@ -354,10 +377,10 @@ impl<'a, M: 'a + GuestAddressSpace> VringStateMutGuard<'a, M> for VringRwLock } impl VringT for VringRwLock { - fn new(mem: M, max_queue_size: u16) -> Self { - VringRwLock { - state: Arc::new(RwLock::new(VringState::new(mem, max_queue_size))), - } + fn new(mem: M, max_queue_size: u16) -> Result { + Ok(VringRwLock { + state: Arc::new(RwLock::new(VringState::new(mem, max_queue_size)?)), + }) } fn get_ref(&self) -> >::G { @@ -392,7 +415,12 @@ impl VringT for VringRwLock { self.write_lock().set_enabled(enabled) } - fn set_queue_info(&self, desc_table: u64, avail_ring: u64, used_ring: u64) { + fn set_queue_info( + &self, + desc_table: u64, + avail_ring: u64, + used_ring: u64, + ) -> Result<(), VirtQueError> { self.write_lock() .set_queue_info(desc_table, avail_ring, used_ring) } @@ -447,39 +475,30 @@ mod tests { GuestMemoryMmap::::from_ranges(&[(GuestAddress(0x100000), 0x10000)]) .unwrap(), ); - let vring = VringMutex::new(mem, 0x1000); + let vring = VringMutex::new(mem, 0x1000).unwrap(); assert!(vring.get_ref().get_kick().is_none()); assert!(!vring.get_mut().enabled); assert!(!vring.lock().queue.ready()); - assert!(!vring.lock().queue.state.event_idx_enabled); + assert!(!vring.lock().queue.event_idx_enabled()); vring.set_enabled(true); assert!(vring.get_ref().enabled); - vring.set_queue_info(0x100100, 0x100200, 0x100300); - assert_eq!( - vring.lock().get_queue().state.desc_table, - GuestAddress(0x100100) - ); - assert_eq!( - vring.lock().get_queue().state.avail_ring, - GuestAddress(0x100200) - ); - assert_eq!( - vring.lock().get_queue().state.used_ring, - GuestAddress(0x100300) - ); + vring.set_queue_info(0x100100, 0x100200, 0x100300).unwrap(); + assert_eq!(vring.lock().get_queue().desc_table(), 0x100100); + assert_eq!(vring.lock().get_queue().avail_ring(), 0x100200); + assert_eq!(vring.lock().get_queue().used_ring(), 0x100300); assert_eq!(vring.queue_next_avail(), 0); vring.set_queue_next_avail(0x20); assert_eq!(vring.queue_next_avail(), 0x20); vring.set_queue_size(0x200); - assert_eq!(vring.lock().queue.state.size, 0x200); + assert_eq!(vring.lock().queue.size(), 0x200); vring.set_queue_event_idx(true); - assert!(vring.lock().queue.state.event_idx_enabled); + assert!(vring.lock().queue.event_idx_enabled()); vring.set_queue_ready(true); assert!(vring.lock().queue.ready()); @@ -490,7 +509,7 @@ mod tests { let mem = GuestMemoryAtomic::new( GuestMemoryMmap::<()>::from_ranges(&[(GuestAddress(0x100000), 0x10000)]).unwrap(), ); - let vring = VringMutex::new(mem, 0x1000); + let vring = VringMutex::new(mem, 0x1000).unwrap(); vring.set_enabled(true); assert!(vring.get_ref().enabled); From b056e0efc73dafc12380705a16a720eeafb4b089 Mon Sep 17 00:00:00 2001 From: Sebastien Boeuf Date: Fri, 29 Jul 2022 15:01:41 +0200 Subject: [PATCH 135/139] Release v0.6.0 This release includes: - Fix vring initialization logic - Bump to virtio-queue v0.5.0 Signed-off-by: Sebastien Boeuf --- CHANGELOG.md | 10 ++++++++++ Cargo.toml | 2 +- 2 files changed, 11 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index af31974..f778d3e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,6 +9,16 @@ ### Deprecated +## v0.6.0 + +### Changed + +- Moved to rust-vmm/virtio-queue v0.5.0 + +### Fixed + +- Fixed vring initialization logic + ## v0.5.1 ### Changed diff --git a/Cargo.toml b/Cargo.toml index 0195ca7..a3ddd3f 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "vhost-user-backend" -version = "0.5.1" +version = "0.6.0" authors = ["The Cloud Hypervisor Authors"] keywords = ["vhost-user", "virtio"] description = "A framework to build vhost-user backend service daemon" From 610d4ee95cd6d1c68b46be2b2f202769233b6237 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 15 Aug 2022 01:34:57 +0000 Subject: [PATCH 136/139] build(deps): update nix requirement from 0.24 to 0.25 Updates the requirements on [nix](https://github.com/nix-rust/nix) to permit the latest version. - [Release notes](https://github.com/nix-rust/nix/releases) - [Changelog](https://github.com/nix-rust/nix/blob/master/CHANGELOG.md) - [Commits](https://github.com/nix-rust/nix/compare/v0.24.0...v0.25.0) --- updated-dependencies: - dependency-name: nix dependency-type: direct:production ... Signed-off-by: dependabot[bot] --- Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Cargo.toml b/Cargo.toml index a3ddd3f..9b554c9 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -17,7 +17,7 @@ vm-memory = {version = ">=0.7", features = ["backend-mmap", "backend-atomic"]} vmm-sys-util = "0.10" [dev-dependencies] -nix = "0.24" +nix = "0.25" vhost = { version = "0.4", features = ["vhost-user-master", "vhost-user-slave"] } vm-memory = {version = ">=0.7", features = ["backend-mmap", "backend-atomic", "backend-bitmap"]} tempfile = "3.2.0" From 917e85a3d5d73edd4d2004d10724c7e6a63dce43 Mon Sep 17 00:00:00 2001 From: Sergio Lopez Date: Fri, 9 Sep 2022 10:12:33 +0200 Subject: [PATCH 137/139] Update dependencies and switch to caret versions Update libc, log, vhost, virtio-queue and vm-memory dependencies. Also, take this opportunity to specify the dependencies with caret versions. Switching to a newer vhost required some small changes to accommodate the fact that VhostUserError::InvalidOperation now requires an argument. Signed-off-by: Sergio Lopez --- Cargo.toml | 14 +++++++------- src/handler.rs | 10 ++++++---- 2 files changed, 13 insertions(+), 11 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 9b554c9..f26a30c 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -8,16 +8,16 @@ edition = "2018" license = "Apache-2.0" [dependencies] -libc = ">=0.2.39" -log = ">=0.4.6" -vhost = { version = "0.4", features = ["vhost-user-slave"] } +libc = "0.2.39" +log = "0.4.17" +vhost = { version = "0.5", features = ["vhost-user-slave"] } virtio-bindings = "0.1" -virtio-queue = "0.5.0" -vm-memory = {version = ">=0.7", features = ["backend-mmap", "backend-atomic"]} +virtio-queue = "0.6" +vm-memory = { version = "0.9", features = ["backend-mmap", "backend-atomic"] } vmm-sys-util = "0.10" [dev-dependencies] nix = "0.25" -vhost = { version = "0.4", features = ["vhost-user-master", "vhost-user-slave"] } -vm-memory = {version = ">=0.7", features = ["backend-mmap", "backend-atomic", "backend-bitmap"]} +vhost = { version = "0.5", features = ["vhost-user-master", "vhost-user-slave"] } +vm-memory = { version = "0.9", features = ["backend-mmap", "backend-atomic", "backend-bitmap"] } tempfile = "3.2.0" diff --git a/src/handler.rs b/src/handler.rs index 0b3c417..41f8107 100644 --- a/src/handler.rs +++ b/src/handler.rs @@ -220,7 +220,7 @@ where { fn set_owner(&mut self) -> VhostUserResult<()> { if self.owned { - return Err(VhostUserError::InvalidOperation); + return Err(VhostUserError::InvalidOperation("already claimed")); } self.owned = true; Ok(()) @@ -456,7 +456,9 @@ where // This request should be handled only when VHOST_USER_F_PROTOCOL_FEATURES // has been negotiated. if self.acked_features & VhostUserVirtioFeatures::PROTOCOL_FEATURES.bits() == 0 { - return Err(VhostUserError::InvalidOperation); + return Err(VhostUserError::InvalidOperation( + "protocol features not set", + )); } else if index as usize >= self.num_queues { return Err(VhostUserError::InvalidParam); } @@ -505,7 +507,7 @@ where // Assume the backend hasn't negotiated the inflight feature; it // wouldn't be correct for the backend to do so, as we don't (yet) // provide a way for it to handle such requests. - Err(VhostUserError::InvalidOperation) + Err(VhostUserError::InvalidOperation("not supported")) } fn set_inflight_fd( @@ -513,7 +515,7 @@ where _inflight: &vhost::vhost_user::message::VhostUserInflight, _file: File, ) -> VhostUserResult<()> { - Err(VhostUserError::InvalidOperation) + Err(VhostUserError::InvalidOperation("not supported")) } fn get_max_mem_slots(&mut self) -> VhostUserResult { From d6c8008bbd688ecd12b12b0248e43b13dc190bf1 Mon Sep 17 00:00:00 2001 From: Sergio Lopez Date: Wed, 14 Sep 2022 09:50:46 +0100 Subject: [PATCH 138/139] Release v0.7.0 This release includes: - Started using caret dependencies - Updated dependency nix 0.24 -> 0.25 - Updated depepdency log 0.4.6 -> 0.4.17 - Updated dependency vhost 0.4 -> 0.5 - Updated dependency virtio-queue 0.5.0 -> 0.6 Signed-off-by: Sergio Lopez --- CHANGELOG.md | 11 +++++++++++ Cargo.toml | 2 +- 2 files changed, 12 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index f778d3e..24c203d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,6 +9,17 @@ ### Deprecated +## v0.7.0 + +### Changed + +- Started using caret dependencies +- Updated dependency nix 0.24 -> 0.25 +- Updated depepdency log 0.4.6 -> 0.4.17 +- Updated dependency vhost 0.4 -> 0.5 +- Updated dependency virtio-queue 0.5.0 -> 0.6 +- Updated dependency vm-memory 0.7 -> 0.9 + ## v0.6.0 ### Changed diff --git a/Cargo.toml b/Cargo.toml index f26a30c..7d9fee2 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "vhost-user-backend" -version = "0.6.0" +version = "0.7.0" authors = ["The Cloud Hypervisor Authors"] keywords = ["vhost-user", "virtio"] description = "A framework to build vhost-user backend service daemon" From edae1a52578046c5f8737d583475126686ece280 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 19 Sep 2022 01:34:20 +0000 Subject: [PATCH 139/139] build(deps): bump rust-vmm-ci from `99fe2eb` to `5f36cc9` Bumps [rust-vmm-ci](https://github.com/rust-vmm/rust-vmm-ci) from `99fe2eb` to `5f36cc9`. - [Release notes](https://github.com/rust-vmm/rust-vmm-ci/releases) - [Commits](https://github.com/rust-vmm/rust-vmm-ci/compare/99fe2eb2e05d1b2cbeed6fb00b754e8f1c5b2f81...5f36cc9604fbe8beb2dc9cc27d2a551300b741a5) --- updated-dependencies: - dependency-name: rust-vmm-ci dependency-type: direct:production ... Signed-off-by: dependabot[bot] --- rust-vmm-ci | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/rust-vmm-ci b/rust-vmm-ci index 99fe2eb..5f36cc9 160000 --- a/rust-vmm-ci +++ b/rust-vmm-ci @@ -1 +1 @@ -Subproject commit 99fe2eb2e05d1b2cbeed6fb00b754e8f1c5b2f81 +Subproject commit 5f36cc9604fbe8beb2dc9cc27d2a551300b741a5