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:
parent
ab0c088bb6
commit
113bd0e187
2 changed files with 219 additions and 2 deletions
|
|
@ -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!");
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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 {}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue