diff --git a/vhost/Cargo.toml b/vhost/Cargo.toml index b37ffd8..189c3d7 100644 --- a/vhost/Cargo.toml +++ b/vhost/Cargo.toml @@ -23,6 +23,7 @@ vhost-net = ["vhost-kern"] vhost-user = [] vhost-user-frontend = ["vhost-user"] vhost-user-backend = ["vhost-user"] +gpu-socket = ["vhost-user"] xen = ["vm-memory/xen"] postcopy = [] diff --git a/vhost/src/vhost_user/gpu_message.rs b/vhost/src/vhost_user/gpu_message.rs new file mode 100644 index 0000000..a070ce3 --- /dev/null +++ b/vhost/src/vhost_user/gpu_message.rs @@ -0,0 +1,171 @@ +// Copyright (C) 2024 Red Hat, Inc. +// SPDX-License-Identifier: Apache-2.0 + +//! Implementation parts of the protocol on the socket from VHOST_USER_SET_GPU_SOCKET +//! see: https://www.qemu.org/docs/master/interop/vhost-user-gpu.html + +use super::enum_value; +use crate::vhost_user::message::{MsgHeader, Req, VhostUserMsgValidator}; +use crate::vhost_user::Error; + +use std::fmt::Debug; +use std::marker::PhantomData; +use vm_memory::ByteValued; + +enum_value! { + /// Type of requests sending from gpu backends to gpu frontends. + #[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord)] + #[allow(non_camel_case_types, clippy::upper_case_acronyms)] + pub enum GpuBackendReq: u32 { + /// Get the supported protocol features bitmask. + GET_PROTOCOL_FEATURES = 1, + /// Enable protocol features using a bitmask. + SET_PROTOCOL_FEATURES = 2, + /// Get the preferred display configuration. + GET_DISPLAY_INFO = 3, + /// Set/show the cursor position. + CURSOR_POS = 4, + /// Set/hide the cursor. + CURSOR_POS_HIDE = 5, + /// Update the cursor shape and location. + CURSOR_UPDATE = 6, + /// Set the scanout resolution. + /// To disable a scanout, the dimensions width/height are set to 0. + SCANOUT = 7, + /// Update the scanout content. The data payload contains the graphical bits. + /// The display should be flushed and presented. + UPDATE = 8, + /// Set the scanout resolution/configuration, and share a DMABUF file descriptor for the + /// scanout content, which is passed as ancillary data. + /// To disable a scanout, the dimensions width/height are set to 0, there is no file + /// descriptor passed. + DMABUF_SCANOUT = 9, + /// The display should be flushed and presented according to updated region from + /// VhostUserGpuUpdate. + /// Note: there is no data payload, since the scanout is shared thanks to DMABUF, + /// that must have been set previously with VHOST_USER_GPU_DMABUF_SCANOUT. + DMABUF_UPDATE = 10, + /// Retrieve the EDID data for a given scanout. + /// This message requires the VHOST_USER_GPU_PROTOCOL_F_EDID protocol feature to be + /// supported. + GET_EDID = 11, + /// Same as DMABUF_SCANOUT, but also sends the dmabuf modifiers appended to the message, + /// which were not provided in the other message. + /// This message requires the VHOST_USER_GPU_PROTOCOL_F_DMABUF2 protocol feature to be + /// supported. + DMABUF_SCANOUT2 = 12, + } +} + +impl Req for GpuBackendReq {} + +// Bit mask for common message flags. +bitflags! { + /// Common message flags for vhost-user requests and replies. + pub struct VhostUserGpuHeaderFlag: u32 { + /// Mark message as reply. + const REPLY = 0x4; + } +} + +/// A vhost-user message consists of 3 header fields and an optional payload. All numbers are in the +/// machine native byte order. +#[repr(C, packed)] +#[derive(Clone, Copy)] +pub(super) struct VhostUserGpuMsgHeader { + request: u32, + flags: u32, + size: u32, + _r: PhantomData, +} + +impl Debug for VhostUserGpuMsgHeader { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.debug_struct("VhostUserMsgHeader") + .field("request", &{ self.request }) + .field("flags", &{ self.flags }) + .field("size", &{ self.size }) + .finish() + } +} + +impl PartialEq for VhostUserGpuMsgHeader { + fn eq(&self, other: &Self) -> bool { + self.request == other.request && self.flags == other.flags && self.size == other.size + } +} + +#[allow(dead_code)] +impl VhostUserGpuMsgHeader { + /// Create a new instance of `VhostUserMsgHeader`. + pub fn new(request: R, flags: u32, size: u32) -> Self { + VhostUserGpuMsgHeader { + request: request.into(), + flags, + size, + _r: PhantomData, + } + } + + /// Get message type. + pub fn get_code(&self) -> crate::vhost_user::Result { + R::try_from(self.request).map_err(|_| Error::InvalidMessage) + } + + /// Check whether it's a reply message. + pub fn is_reply(&self) -> bool { + (self.flags & VhostUserGpuHeaderFlag::REPLY.bits()) != 0 + } + + /// Mark message as reply. + pub fn set_reply(&mut self, is_reply: bool) { + if is_reply { + self.flags |= VhostUserGpuHeaderFlag::REPLY.bits(); + } else { + self.flags &= !VhostUserGpuHeaderFlag::REPLY.bits(); + } + } + + /// Check whether it's the reply message for the request `req`. + pub fn is_reply_for(&self, req: &VhostUserGpuMsgHeader) -> bool { + if let (Ok(code1), Ok(code2)) = (self.get_code(), req.get_code()) { + self.is_reply() && !req.is_reply() && code1 == code2 + } else { + false + } + } + + /// Get message size. + pub fn get_size(&self) -> u32 { + self.size + } + + /// Set message size. + pub fn set_size(&mut self, size: u32) { + self.size = size; + } +} + +impl Default for VhostUserGpuMsgHeader { + fn default() -> Self { + VhostUserGpuMsgHeader { + request: 0, + flags: 0, + size: 0, + _r: PhantomData, + } + } +} + +// SAFETY: Safe because all fields of VhostUserGpuMsgHeader are POD. +unsafe impl ByteValued for VhostUserGpuMsgHeader {} + +impl VhostUserMsgValidator for VhostUserGpuMsgHeader { + fn is_valid(&self) -> bool { + self.get_code().is_ok() && VhostUserGpuHeaderFlag::from_bits(self.flags).is_some() + } +} + +impl MsgHeader for VhostUserGpuMsgHeader { + type Request = R; +} diff --git a/vhost/src/vhost_user/mod.rs b/vhost/src/vhost_user/mod.rs index 502d2ce..66a9ff3 100644 --- a/vhost/src/vhost_user/mod.rs +++ b/vhost/src/vhost_user/mod.rs @@ -52,6 +52,8 @@ pub use self::backend_req_handler::{ mod backend_req; #[cfg(feature = "vhost-user-backend")] pub use self::backend_req::Backend; +#[cfg(feature = "gpu-socket")] +pub mod gpu_message; /// Errors for vhost-user operations #[derive(Debug)]