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 <dbassey@redhat.com>
Signed-off-by: Matej Hrica <mhrica@redhat.com>
This commit is contained in:
Dorinda Bassey 2024-04-10 16:24:25 +02:00 committed by Sergio López
parent ab0c088bb6
commit 113bd0e187
2 changed files with 219 additions and 2 deletions

View file

@ -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<VhostUserGpuMsgHeader<GpuBackendReq>>,
// whether the endpoint has encountered any failure
error: Option<i32>,
}
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<u64> {
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<VhostUserGpuMsgHeader<GpuBackendReq>> {
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<V: ByteValued + Sized + Default + VhostUserMsgValidator>(
&mut self,
hdr: &VhostUserGpuMsgHeader<GpuBackendReq>,
) -> io::Result<V> {
self.check_state()?;
let (reply, body, rfds) = self
.sock
.recv_body::<V>()
.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<VhostUserGpuMsgHeader<GpuBackendReq>>) -> Self {
fn new(ep: Endpoint<VhostUserGpuMsgHeader<GpuBackendReq>>) -> 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<VirtioGpuRespDisplayInfo> {
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::<VhostUserGpuMsgHeader<GpuBackendReq>>::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<VhostUserGpuMsgHeader<GpuBackendReq>>, 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<GpuBackendReq>,
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<R>(
frontend: &mut Endpoint<VhostUserGpuMsgHeader<GpuBackendReq>>,
req_hdr: &VhostUserGpuMsgHeader<GpuBackendReq>,
reply_body: &R,
) where
R: ByteValued,
{
let response_hdr = VhostUserGpuMsgHeader::new(
req_hdr.get_code().unwrap(),
VhostUserGpuHeaderFlag::REPLY.bits(),
size_of::<R>() 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!");
}
}

View file

@ -170,3 +170,81 @@ impl<R: Req> MsgHeader for VhostUserGpuMsgHeader<R> {
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 {}