Path: blob/main/devices/src/virtio/vhost_user_backend/vsock.rs
5394 views
// Copyright 2021 The ChromiumOS Authors1// Use of this source code is governed by a BSD-style license that can be2// found in the LICENSE file.34use std::convert::TryInto;5use std::fs::File;6use std::fs::OpenOptions;7use std::mem::size_of;8use std::num::Wrapping;9use std::os::unix::fs::OpenOptionsExt;10use std::path::Path;11use std::str;1213use anyhow::Context;14use argh::FromArgs;15use base::AsRawDescriptor;16use base::Event;17use base::RawDescriptor;18use base::SafeDescriptor;19use cros_async::Executor;20use data_model::Le64;21use vhost::Vhost;22use vhost::Vsock;23use vm_memory::GuestMemory;24use vmm_vhost::connection::Connection;25use vmm_vhost::message::VhostUserConfigFlags;26use vmm_vhost::message::VhostUserInflight;27use vmm_vhost::message::VhostUserMemoryRegion;28use vmm_vhost::message::VhostUserMigrationPhase;29use vmm_vhost::message::VhostUserProtocolFeatures;30use vmm_vhost::message::VhostUserSingleMemoryRegion;31use vmm_vhost::message::VhostUserTransferDirection;32use vmm_vhost::message::VhostUserVringAddrFlags;33use vmm_vhost::message::VhostUserVringState;34use vmm_vhost::Error;35use vmm_vhost::Result;36use vmm_vhost::SharedMemoryRegion;37use vmm_vhost::VHOST_USER_F_PROTOCOL_FEATURES;38use zerocopy::IntoBytes;3940use super::BackendConnection;41use crate::virtio::device_constants::vsock::NUM_QUEUES;42use crate::virtio::vhost_user_backend::handler::vmm_va_to_gpa;43use crate::virtio::vhost_user_backend::handler::MappingInfo;44use crate::virtio::vhost_user_backend::handler::VhostUserRegularOps;45use crate::virtio::vhost_user_backend::VhostUserDeviceBuilder;46use crate::virtio::Queue;47use crate::virtio::QueueConfig;4849const EVENT_QUEUE: usize = NUM_QUEUES - 1;5051struct VsockBackend {52queues: [QueueConfig; NUM_QUEUES],53vmm_maps: Option<Vec<MappingInfo>>,54mem: Option<GuestMemory>,5556handle: Vsock,57cid: u64,58protocol_features: VhostUserProtocolFeatures,59}6061/// A vhost-vsock device which handle is already opened. This allows the parent process to open the62/// vhost-vsock device, create this structure, and pass it to the child process so it doesn't need63/// the rights to open the vhost-vsock device itself.64pub struct VhostUserVsockDevice {65cid: u64,66handle: Vsock,67}6869impl VhostUserVsockDevice {70pub fn new<P: AsRef<Path>>(cid: u64, vhost_device: P) -> anyhow::Result<Self> {71let handle = Vsock::new(72OpenOptions::new()73.read(true)74.write(true)75.custom_flags(libc::O_CLOEXEC | libc::O_NONBLOCK)76.open(vhost_device.as_ref())77.with_context(|| {78format!(79"failed to open vhost-vsock device {}",80vhost_device.as_ref().display()81)82})?,83);8485Ok(Self { cid, handle })86}87}8889impl AsRawDescriptor for VhostUserVsockDevice {90fn as_raw_descriptor(&self) -> base::RawDescriptor {91self.handle.as_raw_descriptor()92}93}9495impl VhostUserDeviceBuilder for VhostUserVsockDevice {96fn build(self: Box<Self>, _ex: &Executor) -> anyhow::Result<Box<dyn vmm_vhost::Backend>> {97let backend = VsockBackend {98queues: [99QueueConfig::new(Queue::MAX_SIZE, 0),100QueueConfig::new(Queue::MAX_SIZE, 0),101QueueConfig::new(Queue::MAX_SIZE, 0),102],103vmm_maps: None,104mem: None,105handle: self.handle,106cid: self.cid,107protocol_features: VhostUserProtocolFeatures::MQ | VhostUserProtocolFeatures::CONFIG,108};109110Ok(Box::new(backend))111}112}113114fn convert_vhost_error(err: vhost::Error) -> Error {115use vhost::Error::*;116match err {117IoctlError(e) => Error::ReqHandlerError(e),118_ => Error::BackendInternalError,119}120}121122impl vmm_vhost::Backend for VsockBackend {123fn set_owner(&mut self) -> Result<()> {124self.handle.set_owner().map_err(convert_vhost_error)125}126127fn reset_owner(&mut self) -> Result<()> {128self.handle.reset_owner().map_err(convert_vhost_error)129}130131fn get_features(&mut self) -> Result<u64> {132// Add the vhost-user features that we support.133let features = self.handle.get_features().map_err(convert_vhost_error)?134| 1 << VHOST_USER_F_PROTOCOL_FEATURES;135Ok(features)136}137138fn set_features(&mut self, features: u64) -> Result<()> {139// Unset the vhost-user feature flags as they are not supported by the underlying vhost140// device.141let features = features & !(1 << VHOST_USER_F_PROTOCOL_FEATURES);142self.handle143.set_features(features)144.map_err(convert_vhost_error)145}146147fn get_protocol_features(&mut self) -> Result<VhostUserProtocolFeatures> {148Ok(self.protocol_features)149}150151fn set_protocol_features(&mut self, features: u64) -> Result<()> {152let unrequested_features = features & !self.protocol_features.bits();153if unrequested_features != 0 {154Err(Error::InvalidParam("unsupported protocol feature"))155} else {156Ok(())157}158}159160fn set_mem_table(161&mut self,162contexts: &[VhostUserMemoryRegion],163files: Vec<File>,164) -> Result<()> {165let (guest_mem, vmm_maps) = VhostUserRegularOps::set_mem_table(contexts, files)?;166167self.handle168.set_mem_table(&guest_mem)169.map_err(convert_vhost_error)?;170171self.mem = Some(guest_mem);172self.vmm_maps = Some(vmm_maps);173174Ok(())175}176177fn get_queue_num(&mut self) -> Result<u64> {178Ok(NUM_QUEUES as u64)179}180181fn set_vring_num(&mut self, index: u32, num: u32) -> Result<()> {182if index >= NUM_QUEUES as u32 || num == 0 || num > Queue::MAX_SIZE.into() {183return Err(Error::InvalidParam(184"set_vring_num: vring index or size out of range",185));186}187188// We checked these values already.189let index = index as usize;190let num = num as u16;191self.queues[index].set_size(num);192193// The last vq is an event-only vq that is not handled by the kernel.194if index == EVENT_QUEUE {195return Ok(());196}197198self.handle199.set_vring_num(index, num)200.map_err(convert_vhost_error)201}202203fn set_vring_addr(204&mut self,205index: u32,206flags: VhostUserVringAddrFlags,207descriptor: u64,208used: u64,209available: u64,210log: u64,211) -> Result<()> {212if index >= NUM_QUEUES as u32 {213return Err(Error::InvalidParam("set_vring_addr: index out of range"));214}215216let index = index as usize;217218let mem = self219.mem220.as_ref()221.ok_or(Error::InvalidParam("set_vring_addr: could not get mem"))?;222let maps = self.vmm_maps.as_ref().ok_or(Error::InvalidParam(223"set_vring_addr: could not get vmm_maps",224))?;225226let queue = &mut self.queues[index];227queue.set_desc_table(vmm_va_to_gpa(maps, descriptor)?);228queue.set_avail_ring(vmm_va_to_gpa(maps, available)?);229queue.set_used_ring(vmm_va_to_gpa(maps, used)?);230let log_addr = if flags.contains(VhostUserVringAddrFlags::VHOST_VRING_F_LOG) {231vmm_va_to_gpa(maps, log).map(Some)?232} else {233None234};235236if index == EVENT_QUEUE {237return Ok(());238}239240self.handle241.set_vring_addr(242mem,243queue.size(),244index,245flags.bits(),246queue.desc_table(),247queue.used_ring(),248queue.avail_ring(),249log_addr,250)251.map_err(convert_vhost_error)252}253254fn set_vring_base(&mut self, index: u32, base: u32) -> Result<()> {255if index >= NUM_QUEUES as u32 {256return Err(Error::InvalidParam("set_vring_base: index out of range"));257}258259let index = index as usize;260let base = base as u16;261262let queue = &mut self.queues[index];263queue.set_next_avail(Wrapping(base));264queue.set_next_used(Wrapping(base));265266if index == EVENT_QUEUE {267return Ok(());268}269270self.handle271.set_vring_base(index, base)272.map_err(convert_vhost_error)273}274275fn get_vring_base(&mut self, index: u32) -> Result<VhostUserVringState> {276if index >= NUM_QUEUES as u32 {277return Err(Error::InvalidParam("get_vring_base: index out of range"));278}279280let index = index as usize;281let next_avail = if index == EVENT_QUEUE {282self.queues[index].next_avail().0283} else {284self.handle285.get_vring_base(index)286.map_err(convert_vhost_error)?287};288289Ok(VhostUserVringState::new(index as u32, next_avail.into()))290}291292fn set_vring_kick(&mut self, index: u8, fd: Option<File>) -> Result<()> {293if index >= NUM_QUEUES as u8 {294return Err(Error::InvalidParam("set_vring_kick: index out of range"));295}296297let file = fd.ok_or(Error::InvalidParam("set_vring_kick: missing fd"))?;298let event = Event::from(SafeDescriptor::from(file));299let index = usize::from(index);300if index != EVENT_QUEUE {301self.handle302.set_vring_kick(index, &event)303.map_err(convert_vhost_error)?;304}305306Ok(())307}308309fn set_vring_call(&mut self, index: u8, fd: Option<File>) -> Result<()> {310if index >= NUM_QUEUES as u8 {311return Err(Error::InvalidParam("set_vring_call: index out of range"));312}313314let file = fd.ok_or(Error::InvalidParam("set_vring_call: missing fd"))?;315let event = Event::from(SafeDescriptor::from(file));316let index = usize::from(index);317if index != EVENT_QUEUE {318self.handle319.set_vring_call(index, &event)320.map_err(convert_vhost_error)?;321}322323Ok(())324}325326fn set_vring_err(&mut self, index: u8, fd: Option<File>) -> Result<()> {327if index >= NUM_QUEUES as u8 {328return Err(Error::InvalidParam("set_vring_err: index out of range"));329}330331let index = usize::from(index);332let file = fd.ok_or(Error::InvalidParam("set_vring_err: missing fd"))?;333334let event = Event::from(SafeDescriptor::from(file));335336if index == EVENT_QUEUE {337return Ok(());338}339340self.handle341.set_vring_err(index, &event)342.map_err(convert_vhost_error)343}344345fn set_vring_enable(&mut self, index: u32, enable: bool) -> Result<()> {346if index >= NUM_QUEUES as u32 {347return Err(Error::InvalidParam("vring index out of range"));348}349350self.queues[index as usize].set_ready(enable);351352if index == (EVENT_QUEUE) as u32 {353return Ok(());354}355356if self.queues[..EVENT_QUEUE].iter().all(|q| q.ready()) {357// All queues are ready. Start the device.358self.handle.set_cid(self.cid).map_err(convert_vhost_error)?;359self.handle.start().map_err(convert_vhost_error)360} else if !enable {361// If we just disabled a vring then stop the device.362self.handle.stop().map_err(convert_vhost_error)363} else {364Ok(())365}366}367368fn get_config(369&mut self,370offset: u32,371size: u32,372_flags: VhostUserConfigFlags,373) -> Result<Vec<u8>> {374let start: usize = offset375.try_into()376.map_err(|_| Error::InvalidParam("offset does not fit in usize"))?;377let end: usize = offset378.checked_add(size)379.and_then(|e| e.try_into().ok())380.ok_or(Error::InvalidParam("offset + size does not fit in usize"))?;381382if start >= size_of::<Le64>() || end > size_of::<Le64>() {383return Err(Error::InvalidParam(384"get_config: offset and/or size out of range",385));386}387388Ok(Le64::from(self.cid).as_bytes()[start..end].to_vec())389}390391fn set_config(392&mut self,393_offset: u32,394_buf: &[u8],395_flags: VhostUserConfigFlags,396) -> Result<()> {397Err(Error::InvalidOperation)398}399400fn set_backend_req_fd(&mut self, _vu_req: Connection) {401// We didn't set VhostUserProtocolFeatures::BACKEND_REQ402unreachable!("unexpected set_backend_req_fd");403}404405fn get_inflight_fd(406&mut self,407_inflight: &VhostUserInflight,408) -> Result<(VhostUserInflight, File)> {409Err(Error::InvalidOperation)410}411412fn set_inflight_fd(&mut self, _inflight: &VhostUserInflight, _file: File) -> Result<()> {413Err(Error::InvalidOperation)414}415416fn get_max_mem_slots(&mut self) -> Result<u64> {417Err(Error::InvalidOperation)418}419420fn add_mem_region(&mut self, _region: &VhostUserSingleMemoryRegion, _fd: File) -> Result<()> {421Err(Error::InvalidOperation)422}423424fn remove_mem_region(&mut self, _region: &VhostUserSingleMemoryRegion) -> Result<()> {425Err(Error::InvalidOperation)426}427428fn set_device_state_fd(429&mut self,430_transfer_direction: VhostUserTransferDirection,431_migration_phase: VhostUserMigrationPhase,432_fd: File,433) -> Result<Option<File>> {434Err(Error::InvalidOperation)435}436437fn check_device_state(&mut self) -> Result<()> {438Err(Error::InvalidOperation)439}440441fn get_shmem_config(&mut self) -> Result<Vec<SharedMemoryRegion>> {442Ok(Vec::new())443}444}445446#[derive(FromArgs)]447#[argh(subcommand, name = "vsock")]448/// Vsock device449pub struct Options {450#[argh(option, arg_name = "PATH", hidden_help)]451/// deprecated - please use --socket-path instead452socket: Option<String>,453#[argh(option, arg_name = "PATH")]454/// path to the vhost-user socket to bind to.455/// If this flag is set, --fd cannot be specified.456socket_path: Option<String>,457#[argh(option, arg_name = "FD")]458/// file descriptor of a connected vhost-user socket.459/// If this flag is set, --socket-path cannot be specified.460fd: Option<RawDescriptor>,461462#[argh(option, arg_name = "INT")]463/// the vsock context id for this device464cid: u64,465#[argh(466option,467default = "String::from(\"/dev/vhost-vsock\")",468arg_name = "PATH"469)]470/// path to the vhost-vsock control socket471vhost_socket: String,472}473474/// Returns an error if the given `args` is invalid or the device fails to run.475pub fn run_vsock_device(opts: Options) -> anyhow::Result<()> {476let ex = Executor::new().context("failed to create executor")?;477478let conn =479BackendConnection::from_opts(opts.socket.as_deref(), opts.socket_path.as_deref(), opts.fd)?;480481let vsock_device = Box::new(VhostUserVsockDevice::new(opts.cid, opts.vhost_socket)?);482483conn.run_device(ex, vsock_device)484}485486487