From 113bd0e187705ce48019228fa83841d8f07a90c8 Mon Sep 17 00:00:00 2001 From: Dorinda Bassey Date: Wed, 10 Apr 2024 16:24:25 +0200 Subject: [PATCH] vhost_user: Add GpuBackend::get_display_info Add a get_display_info() method and the related reply structs to send VHOST_USER_GPU_GET_DISPLAY_INFO and receive the reply. Signed-off-by: Dorinda Bassey Signed-off-by: Matej Hrica --- vhost/src/vhost_user/gpu_backend_req.rs | 143 +++++++++++++++++++++++- vhost/src/vhost_user/gpu_message.rs | 78 +++++++++++++ 2 files changed, 219 insertions(+), 2 deletions(-) diff --git a/vhost/src/vhost_user/gpu_backend_req.rs b/vhost/src/vhost_user/gpu_backend_req.rs index c2d16c6..681c623 100644 --- a/vhost/src/vhost_user/gpu_backend_req.rs +++ b/vhost/src/vhost_user/gpu_backend_req.rs @@ -1,17 +1,70 @@ // Copyright (C) 2024 Red Hat, Inc. // SPDX-License-Identifier: Apache-2.0 +use std::io; +use std::os::fd::RawFd; use std::os::unix::net::UnixStream; use std::sync::{Arc, Mutex, MutexGuard}; +use vm_memory::ByteValued; + +use crate::vhost_user; use crate::vhost_user::connection::Endpoint; use crate::vhost_user::gpu_message::*; +use crate::vhost_user::message::VhostUserMsgValidator; +use crate::vhost_user::Error; struct BackendInternal { + sock: Endpoint>, // whether the endpoint has encountered any failure error: Option, } +fn io_err_convert_fn(info: &str) -> impl Fn(vhost_user::Error) -> io::Error + '_ { + move |e| io::Error::new(io::ErrorKind::Other, format!("{info}: {e}")) +} + +impl BackendInternal { + fn check_state(&self) -> io::Result { + match self.error { + Some(e) => Err(io_err_convert_fn("check_state")(Error::SocketBroken( + io::Error::from_raw_os_error(e), + ))), + None => Ok(0), + } + } + + fn send_header( + &mut self, + request: GpuBackendReq, + fds: Option<&[RawFd]>, + ) -> io::Result> { + self.check_state()?; + let hdr = VhostUserGpuMsgHeader::new(request, 0, 0); + self.sock + .send_header(&hdr, fds) + .map_err(io_err_convert_fn("send_header"))?; + Ok(hdr) + } + + // Note that there is no VHOST_USER_PROTOCOL_F_REPLY_ACK for this protocol, some messages always + // expect a reply/ack and others don't expect a reply/ack at all. + fn recv_reply( + &mut self, + hdr: &VhostUserGpuMsgHeader, + ) -> io::Result { + self.check_state()?; + let (reply, body, rfds) = self + .sock + .recv_body::() + .map_err(io_err_convert_fn("recv_body"))?; + if !reply.is_reply_for(hdr) || rfds.is_some() || !body.is_valid() { + return Err(io_err_convert_fn("Unexpected reply")(Error::InvalidMessage)); + } + Ok(body) + } +} + /// Proxy for sending messages from the backend to the fronted /// over the socket obtained from VHOST_USER_GPU_SET_SOCKET. /// The protocol is documented here: https://www.qemu.org/docs/master/interop/vhost-user-gpu.html @@ -22,9 +75,12 @@ pub struct GpuBackend { } impl GpuBackend { - fn new(_ep: Endpoint>) -> Self { + fn new(ep: Endpoint>) -> Self { Self { - node: Arc::new(Mutex::new(BackendInternal { error: None })), + node: Arc::new(Mutex::new(BackendInternal { + sock: ep, + error: None, + })), } } @@ -32,6 +88,15 @@ impl GpuBackend { self.node.lock().unwrap() } + /// Send the VHOST_USER_GPU_GET_DISPLAY_INFO message to the frontend and wait for a reply. + /// Get the preferred display configuration. + pub fn get_display_info(&self) -> io::Result { + let mut node = self.node(); + + let hdr = node.send_header(GpuBackendReq::GET_DISPLAY_INFO, None)?; + node.recv_reply(&hdr) + } + /// Create a new instance from a `UnixStream` object. pub fn from_stream(sock: UnixStream) -> Self { Self::new(Endpoint::>::from_stream(sock)) @@ -46,6 +111,45 @@ impl GpuBackend { #[cfg(test)] mod tests { use super::*; + use std::mem::size_of; + use std::thread; + fn frontend_backend_pair() -> (Endpoint>, GpuBackend) { + let (backend, frontend) = UnixStream::pair().unwrap(); + let backend = GpuBackend::from_stream(backend); + let frontend = Endpoint::from_stream(frontend); + + (frontend, backend) + } + + fn assert_hdr( + hdr: &VhostUserGpuMsgHeader, + expected_req_code: GpuBackendReq, + expected_size: usize, + ) { + let size: u32 = expected_size.try_into().unwrap(); + assert_eq!( + hdr, + &VhostUserGpuMsgHeader::new(GpuBackendReq::GET_DISPLAY_INFO, 0, size) + ); + } + + fn reply_with_msg( + frontend: &mut Endpoint>, + req_hdr: &VhostUserGpuMsgHeader, + reply_body: &R, + ) where + R: ByteValued, + { + let response_hdr = VhostUserGpuMsgHeader::new( + req_hdr.get_code().unwrap(), + VhostUserGpuHeaderFlag::REPLY.bits(), + size_of::() as u32, + ); + + frontend + .send_message(&response_hdr, reply_body, None) + .unwrap(); + } #[test] fn test_gpu_backend_req_set_failed() { @@ -55,4 +159,39 @@ mod tests { backend.set_failed(libc::EAGAIN); assert_eq!(backend.node().error, Some(libc::EAGAIN)); } + + #[test] + fn test_get_display_info() { + let (mut frontend, backend) = frontend_backend_pair(); + + let expected_response = { + let mut resp = VirtioGpuRespDisplayInfo { + hdr: Default::default(), + pmodes: Default::default(), + }; + resp.pmodes[0] = VirtioGpuDisplayOne { + r: VirtioGpuRect { + x: 0, + y: 0, + width: 640, + height: 480, + }, + enabled: 1, + flags: 0, + }; + resp + }; + + let sender_thread = thread::spawn(move || { + let response = backend.get_display_info().unwrap(); + assert_eq!(response, expected_response); + }); + + let (hdr, fds) = frontend.recv_header().unwrap(); + assert!(fds.is_none()); + assert_hdr(&hdr, GpuBackendReq::GET_DISPLAY_INFO, 0); + + reply_with_msg(&mut frontend, &hdr, &expected_response); + sender_thread.join().expect("Failed to send!"); + } } diff --git a/vhost/src/vhost_user/gpu_message.rs b/vhost/src/vhost_user/gpu_message.rs index 38ad7ae..02faf0a 100644 --- a/vhost/src/vhost_user/gpu_message.rs +++ b/vhost/src/vhost_user/gpu_message.rs @@ -170,3 +170,81 @@ impl MsgHeader for VhostUserGpuMsgHeader { type Request = R; const MAX_MSG_SIZE: usize = u32::MAX as usize; } + +/// The virtio_gpu_ctrl_hdr from virtio specification +/// Defined here because some GpuBackend commands return virtio structs, which contain this header. +#[derive(Copy, Clone, Debug, Default, PartialEq, Eq)] +#[repr(C)] +pub struct VirtioGpuCtrlHdr { + /// Specifies the type of the driver request (VIRTIO_GPU_CMD_*) + /// or device response (VIRTIO_GPU_RESP_*). + pub type_: u32, + /// Request / response flags. + pub flags: u32, + /// Set VIRTIO_GPU_FLAG_FENCE bit in the response + pub fence_id: u64, + /// Rendering context (used in 3D mode only). + pub ctx_id: u32, + /// ring_idx indicates the value of a context-specific ring index. + /// The minimum value is 0 and maximum value is 63 (inclusive). + pub ring_idx: u8, + /// padding of the structure + pub padding: [u8; 3], +} + +// SAFETY: Safe because all fields are POD. +unsafe impl ByteValued for VirtioGpuCtrlHdr {} + +/// The virtio_gpu_rect struct from virtio specification. +/// Part of the reply for GpuBackend::get_display_info +#[derive(Copy, Clone, Debug, Default, PartialEq, Eq)] +#[repr(C)] +pub struct VirtioGpuRect { + /// The position field x describes how the displays are arranged + pub x: u32, + /// The position field y describes how the displays are arranged + pub y: u32, + /// Display resolution width + pub width: u32, + /// Display resolution height + pub height: u32, +} + +// SAFETY: Safe because all fields are POD. +unsafe impl ByteValued for VirtioGpuRect {} + +/// The virtio_gpu_display_one struct from virtio specification. +/// Part of the reply for GpuBackend::get_display_info +#[derive(Copy, Clone, Debug, Default, PartialEq, Eq)] +#[repr(C)] +pub struct VirtioGpuDisplayOne { + /// Preferred display resolutions and display positions relative to each other + pub r: VirtioGpuRect, + /// The enabled field is set when the user enabled the display. + pub enabled: u32, + /// The display flags + pub flags: u32, +} + +// SAFETY: Safe because all fields are POD. +unsafe impl ByteValued for VirtioGpuDisplayOne {} + +/// Constant for maximum number of scanouts, defined in the virtio specification. +pub const VIRTIO_GPU_MAX_SCANOUTS: usize = 16; + +/// The virtio_gpu_resp_display_info from the virtio specification. +/// This it the reply from GpuBackend::get_display_info +#[derive(Copy, Clone, Debug, Default, PartialEq, Eq)] +#[repr(C)] +pub struct VirtioGpuRespDisplayInfo { + /// The fixed header struct + pub hdr: VirtioGpuCtrlHdr, + /// pmodes contains whether the scanout is enabled and what + /// its preferred position and size is + pub pmodes: [VirtioGpuDisplayOne; VIRTIO_GPU_MAX_SCANOUTS], +} + +// SAFETY: Safe because all fields are POD. +unsafe impl ByteValued for VirtioGpuRespDisplayInfo {} + +impl VhostUserMsgValidator for VirtioGpuRespDisplayInfo {}