diff --git a/Cargo.lock b/Cargo.lock index 33f2d56852..1422dcf6d7 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -139,6 +139,7 @@ name = "gpu_buffer" version = "0.1.0" dependencies = [ "data_model 0.1.0", + "sys_util 0.1.0", ] [[package]] diff --git a/Cargo.toml b/Cargo.toml index dfc58f42d0..dead362bb0 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -12,6 +12,7 @@ panic = 'abort' [features] plugin = ["plugin_proto", "crosvm_plugin", "protobuf"] default-no-sandbox = [] +wl-dmabuf = ["devices/wl-dmabuf", "gpu_buffer"] [dependencies] arch = { path = "arch" } diff --git a/devices/Cargo.toml b/devices/Cargo.toml index 54b9487472..4581594be3 100644 --- a/devices/Cargo.toml +++ b/devices/Cargo.toml @@ -3,6 +3,9 @@ name = "devices" version = "0.1.0" authors = ["The Chromium OS Authors"] +[features] +wl-dmabuf = [] + [dependencies] byteorder = "*" data_model = { path = "../data_model" } diff --git a/devices/src/virtio/wl.rs b/devices/src/virtio/wl.rs index 2c34e4e95a..a8b26035a0 100644 --- a/devices/src/virtio/wl.rs +++ b/devices/src/virtio/wl.rs @@ -39,6 +39,8 @@ use std::fs::File; use std::io::{self, Seek, SeekFrom, Read}; use std::mem::{size_of, size_of_val}; use std::os::unix::io::{AsRawFd, RawFd}; +#[cfg(feature = "wl-dmabuf")] +use std::os::unix::io::FromRawFd; use std::os::unix::net::{UnixDatagram, UnixStream}; use std::path::{PathBuf, Path}; use std::rc::Rc; @@ -48,6 +50,9 @@ use std::sync::atomic::{AtomicUsize, Ordering}; use std::thread; use std::time::Duration; +#[cfg(feature = "wl-dmabuf")] +use libc::dup; + use data_model::*; use data_model::VolatileMemoryError; @@ -65,8 +70,12 @@ const VIRTIO_WL_CMD_VFD_RECV: u32 = 259; const VIRTIO_WL_CMD_VFD_NEW_CTX: u32 = 260; const VIRTIO_WL_CMD_VFD_NEW_PIPE: u32 = 261; const VIRTIO_WL_CMD_VFD_HUP: u32 = 262; +#[cfg(feature = "wl-dmabuf")] +const VIRTIO_WL_CMD_VFD_NEW_DMABUF: u32 = 263; const VIRTIO_WL_RESP_OK: u32 = 4096; const VIRTIO_WL_RESP_VFD_NEW: u32 = 4097; +#[cfg(feature = "wl-dmabuf")] +const VIRTIO_WL_RESP_VFD_NEW_DMABUF: u32 = 4098; const VIRTIO_WL_RESP_ERR: u32 = 4352; const VIRTIO_WL_RESP_OUT_OF_MEMORY: u32 = 4353; const VIRTIO_WL_RESP_INVALID_ID: u32 = 4354; @@ -126,6 +135,30 @@ fn parse_new_pipe(addr: GuestAddress, mem: &GuestMemory) -> WlResult { }) } +#[cfg(feature = "wl-dmabuf")] +fn parse_new_dmabuf(addr: GuestAddress, mem: &GuestMemory) -> WlResult { + const ID_OFFSET: u64 = 8; + const WIDTH_OFFSET: u64 = 28; + const HEIGHT_OFFSET: u64 = 32; + const FORMAT_OFFSET: u64 = 36; + + let id: Le32 = mem.read_obj_from_addr(mem.checked_offset(addr, ID_OFFSET) + .ok_or(WlError::CheckedOffset)?)?; + let width: Le32 = mem.read_obj_from_addr(mem.checked_offset(addr, WIDTH_OFFSET) + .ok_or(WlError::CheckedOffset)?)?; + let height: Le32 = mem.read_obj_from_addr(mem.checked_offset(addr, HEIGHT_OFFSET) + .ok_or(WlError::CheckedOffset)?)?; + let format: Le32 = mem.read_obj_from_addr(mem.checked_offset(addr, FORMAT_OFFSET) + .ok_or(WlError::CheckedOffset)?)?; + Ok(WlOp::NewDmabuf { + id: id.into(), + width: width.into(), + height: height.into(), + format: format.into(), + }) +} + + fn parse_send(addr: GuestAddress, len: u32, mem: &GuestMemory) -> WlResult { const ID_OFFSET: u64 = 8; const VFD_COUNT_OFFSET: u64 = 12; @@ -165,6 +198,8 @@ fn parse_desc(desc: &DescriptorChain, mem: &GuestMemory) -> WlResult { VIRTIO_WL_CMD_VFD_SEND => parse_send(desc.addr, desc.len, mem), VIRTIO_WL_CMD_VFD_NEW_CTX => Ok(WlOp::NewCtx { id: parse_id(desc.addr, mem)? }), VIRTIO_WL_CMD_VFD_NEW_PIPE => parse_new_pipe(desc.addr, mem), + #[cfg(feature = "wl-dmabuf")] + VIRTIO_WL_CMD_VFD_NEW_DMABUF => parse_new_dmabuf(desc.addr, mem), v => Ok(WlOp::InvalidCommand { op_type: v }), } } @@ -195,6 +230,38 @@ fn encode_vfd_new(desc_mem: VolatileSlice, Ok(size_of::() as u32) } +#[cfg(feature = "wl-dmabuf")] +fn encode_vfd_new_dmabuf(desc_mem: VolatileSlice, + vfd_id: u32, + flags: u32, + pfn: u64, + size: u32, + stride: u32) + -> WlResult { + let ctrl_vfd_new_dmabuf = CtrlVfdNewDmabuf { + hdr: CtrlHeader { + type_: Le32::from(VIRTIO_WL_RESP_VFD_NEW_DMABUF), + flags: Le32::from(0), + }, + id: Le32::from(vfd_id), + flags: Le32::from(flags), + pfn: Le64::from(pfn), + size: Le32::from(size), + width: Le32::from(0), + height: Le32::from(0), + format: Le32::from(0), + stride0: Le32::from(stride), + stride1: Le32::from(0), + stride2: Le32::from(0), + offset0: Le32::from(0), + offset1: Le32::from(0), + offset2: Le32::from(0), + }; + + desc_mem.get_ref(0)?.store(ctrl_vfd_new_dmabuf); + Ok(size_of::() as u32) +} + fn encode_vfd_recv(desc_mem: VolatileSlice, vfd_id: u32, data: &[u8], @@ -249,6 +316,14 @@ fn encode_resp(desc_mem: VolatileSlice, resp: WlResp) -> WlResult { size, resp, } => encode_vfd_new(desc_mem, resp, id, flags, pfn, size), + #[cfg(feature = "wl-dmabuf")] + WlResp::VfdNewDmabuf { + id, + flags, + pfn, + size, + stride, + } => encode_vfd_new_dmabuf(desc_mem, id, flags, pfn, size, stride), WlResp::VfdRecv { id, data, vfds } => encode_vfd_recv(desc_mem, id, data, vfds), WlResp::VfdHup { id } => encode_vfd_hup(desc_mem, id), r => { @@ -358,6 +433,29 @@ struct CtrlVfdNew { unsafe impl DataInit for CtrlVfdNew {} +#[repr(C)] +#[derive(Copy, Clone)] +#[cfg(feature = "wl-dmabuf")] +struct CtrlVfdNewDmabuf { + hdr: CtrlHeader, + id: Le32, + flags: Le32, + pfn: Le64, + size: Le32, + width: Le32, + height: Le32, + format: Le32, + stride0: Le32, + stride1: Le32, + stride2: Le32, + offset0: Le32, + offset1: Le32, + offset2: Le32, +} + +#[cfg(feature = "wl-dmabuf")] +unsafe impl DataInit for CtrlVfdNewDmabuf {} + #[repr(C)] #[derive(Copy, Clone)] struct CtrlVfdRecv { @@ -390,6 +488,8 @@ enum WlOp { }, NewCtx { id: u32 }, NewPipe { id: u32, flags: u32 }, + #[cfg(feature = "wl-dmabuf")] + NewDmabuf { id: u32, width: u32, height: u32, format: u32 }, InvalidCommand { op_type: u32 }, } @@ -406,6 +506,14 @@ enum WlResp<'a> { // is important for the `get_code` method. resp: bool, }, + #[cfg(feature = "wl-dmabuf")] + VfdNewDmabuf { + id: u32, + flags: u32, + pfn: u64, + size: u32, + stride: u32, + }, VfdRecv { id: u32, data: &'a [u8], @@ -431,6 +539,8 @@ impl<'a> WlResp<'a> { VIRTIO_WL_CMD_VFD_NEW } } + #[cfg(feature = "wl-dmabuf")] + &WlResp::VfdNewDmabuf { .. } => VIRTIO_WL_RESP_VFD_NEW_DMABUF, &WlResp::VfdRecv { .. } => VIRTIO_WL_CMD_VFD_RECV, &WlResp::VfdHup { .. } => VIRTIO_WL_CMD_VFD_HUP, &WlResp::Err(_) => VIRTIO_WL_RESP_ERR, @@ -505,6 +615,26 @@ impl WlVfd { } } + #[cfg(feature = "wl-dmabuf")] + fn dmabuf(vm: VmRequester, width: u32, height: u32, format: u32) -> WlResult<(WlVfd, u32)> { + let allocate_and_register_gpu_memory_response = + vm.request(VmRequest::AllocateAndRegisterGpuMemory { width: width, + height: height, + format: format })?; + match allocate_and_register_gpu_memory_response { + VmResponse::AllocateAndRegisterGpuMemory { fd, pfn, slot, stride } => { + let mut vfd = WlVfd::default(); + // Duplicate FD for shared memory instance. + let raw_fd = unsafe { File::from_raw_fd(dup(fd.as_raw_fd())) }; + let vfd_shm = SharedMemory::from_raw_fd(raw_fd).map_err(WlError::NewAlloc)?; + vfd.guest_shared_memory = Some((vfd_shm.size(), vfd_shm.into())); + vfd.slot = Some((slot, pfn, vm)); + Ok((vfd, stride)) + } + _ => Err(WlError::VmBadResponse), + } + } + fn pipe_remote_read_local_write() -> WlResult { let (read_pipe, write_pipe) = pipe(true).map_err(WlError::NewPipe)?; let mut vfd = WlVfd::default(); @@ -792,6 +922,32 @@ impl WlState { } } + #[cfg(feature = "wl-dmabuf")] + fn new_dmabuf(&mut self, id: u32, width: u32, height: u32, format: u32) -> WlResult { + if id & VFD_ID_HOST_MASK != 0 { + return Ok(WlResp::InvalidId); + } + + match self.vfds.entry(id) { + Entry::Vacant(entry) => { + let (vfd, stride) = WlVfd::dmabuf(self.vm.clone(), + width, + height, + format)?; + let resp = WlResp::VfdNewDmabuf { + id: id, + flags: 0, + pfn: vfd.pfn().unwrap_or_default(), + size: vfd.size().unwrap_or_default() as u32, + stride: stride, + }; + entry.insert(vfd); + Ok(resp) + } + Entry::Occupied(_) => Ok(WlResp::InvalidId), + } + } + fn new_context(&mut self, id: u32) -> WlResult { if id & VFD_ID_HOST_MASK != 0 { return Ok(WlResp::InvalidId); @@ -956,6 +1112,8 @@ impl WlState { } WlOp::NewCtx { id } => self.new_context(id), WlOp::NewPipe { id, flags } => self.new_pipe(id, flags), + #[cfg(feature = "wl-dmabuf")] + WlOp::NewDmabuf { id, width, height, format } => self.new_dmabuf(id, width, height, format), WlOp::InvalidCommand { op_type } => { warn!("unexpected command {}", op_type); Ok(WlResp::InvalidCommand) diff --git a/gpu_buffer/Cargo.toml b/gpu_buffer/Cargo.toml index 11b47c492f..239283dab6 100644 --- a/gpu_buffer/Cargo.toml +++ b/gpu_buffer/Cargo.toml @@ -5,3 +5,4 @@ authors = ["The Chromium OS Authors"] [dependencies] data_model = { path = "../data_model" } +sys_util = { path = "../sys_util" } diff --git a/gpu_buffer/src/lib.rs b/gpu_buffer/src/lib.rs index 6d0cfa9a89..fb69eeb4cf 100644 --- a/gpu_buffer/src/lib.rs +++ b/gpu_buffer/src/lib.rs @@ -31,7 +31,10 @@ //! ``` extern crate data_model; +#[macro_use] +extern crate sys_util; +pub mod rendernode; mod raw; use std::os::raw::c_void; diff --git a/gpu_buffer/src/rendernode.rs b/gpu_buffer/src/rendernode.rs new file mode 100644 index 0000000000..ce89a158a4 --- /dev/null +++ b/gpu_buffer/src/rendernode.rs @@ -0,0 +1,106 @@ +// Copyright 2018 The Chromium OS Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +use std::ffi::CString; +use std::fs::{File, OpenOptions}; +use std::os::raw::{c_char, c_int, c_uint, c_ulonglong}; +use std::path::Path; +use std::ptr::null_mut; + +use sys_util::ioctl_with_mut_ref; + +const DRM_IOCTL_BASE: c_uint = 0x64; + +#[repr(C)] +#[derive(Copy, Clone)] +struct drm_version { + version_major: c_int, + version_minor: c_int, + version_patchlevel: c_int, + name_len: c_ulonglong, + name: *mut c_char, + date_len: c_ulonglong, + date: *mut c_char, + desc_len: c_ulonglong, + desc: *mut c_char, +} + +ioctl_iowr_nr!(DRM_IOCTL_VERSION, DRM_IOCTL_BASE, 0x0, drm_version); + +fn get_drm_device_name(fd: &File) -> Result { + let mut version = drm_version { + version_major: 0, + version_minor: 0, + version_patchlevel: 0, + name_len: 0, + name: null_mut(), + date_len: 0, + date: null_mut(), + desc_len: 0, + desc: null_mut(), + }; + + // Get the length of the device name. + if unsafe { ioctl_with_mut_ref(fd, DRM_IOCTL_VERSION(), &mut version) } < 0 { + return Err(()); + } + + // Enough bytes to hold the device name and terminating null character. + let mut name_bytes: Vec = vec![0; (version.name_len + 1) as usize]; + let mut version = drm_version { + version_major: 0, + version_minor: 0, + version_patchlevel: 0, + name_len: name_bytes.len() as c_ulonglong, + name: name_bytes.as_mut_ptr() as *mut c_char, + date_len: 0, + date: null_mut(), + desc_len: 0, + desc: null_mut(), + }; + + // Safe as no more than name_len + 1 bytes will be written to name. + if unsafe { ioctl_with_mut_ref(fd, DRM_IOCTL_VERSION(), &mut version) } < 0 { + return Err(()); + } + + Ok(CString::new(&name_bytes[..(version.name_len as usize)]) + .map_err(|_| ())? + .into_string().map_err(|_| ())?) +} + + +/// Returns a `fd` for an opened rendernode device, while filtering out specified +/// undesired drivers. +pub fn open_device(undesired: &[&str]) -> Result { + const DRM_DIR_NAME: &str = "/dev/dri"; + const DRM_MAX_MINOR: u32 = 15; + const RENDER_NODE_START: u32 = 128; + + for n in RENDER_NODE_START..(RENDER_NODE_START + DRM_MAX_MINOR + 1) { + let path = Path::new(DRM_DIR_NAME).join(format!("renderD{}", n)); + + if let Ok(fd) = OpenOptions::new().read(true).write(true).open(path) { + if let Ok(name) = get_drm_device_name(&fd) { + if !undesired.iter().any(|item| *item == name) { + return Ok(fd); + } + } + } + } + + Err(()) +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + #[ignore] // no access to /dev/dri + fn open_rendernode_device() { + let undesired: &[&str] = &["bad_driver", "another_bad_driver"]; + open_device(undesired).expect("failed to open rendernode"); + } +} diff --git a/src/linux.rs b/src/linux.rs index 6192478b80..8aae432d8d 100644 --- a/src/linux.rs +++ b/src/linux.rs @@ -18,6 +18,8 @@ use std::thread::JoinHandle; use libc; use libc::c_int; +#[cfg(feature = "wl-dmabuf")] +use libc::EINVAL; use device_manager; use devices; @@ -29,7 +31,9 @@ use qcow::{self, QcowFile}; use sys_util::*; use sys_util; use vhost; -use vm_control::VmRequest; +use vm_control::{VmRequest, GpuMemoryAllocator}; +#[cfg(feature = "wl-dmabuf")] +use gpu_buffer; use Config; use DiskType; @@ -48,6 +52,7 @@ pub enum Error { CloneEventFd(sys_util::Error), Cmdline(kernel_cmdline::Error), CreateEventFd(sys_util::Error), + CreateGpuBufferDevice, CreateGuestMemory(Box), CreateIrqChip(Box), CreateKvm(sys_util::Error), @@ -66,6 +71,7 @@ pub enum Error { NetDeviceNew(devices::virtio::NetError), NoVarEmpty, OpenKernel(PathBuf, io::Error), + OpenGpuBufferDevice, PollContextAdd(sys_util::Error), QcowDeviceCreate(qcow::Error), RegisterBalloon(device_manager::Error), @@ -99,6 +105,7 @@ impl fmt::Display for Error { &Error::CloneEventFd(ref e) => write!(f, "failed to clone eventfd: {:?}", e), &Error::Cmdline(ref e) => write!(f, "the given kernel command line was invalid: {}", e), &Error::CreateEventFd(ref e) => write!(f, "failed to create eventfd: {:?}", e), + &Error::CreateGpuBufferDevice => write!(f, "failed to create GPU buffer device"), &Error::CreateGuestMemory(ref e) => write!(f, "failed to create guest memory: {:?}", e), &Error::CreateIrqChip(ref e) => { write!(f, "failed to create in-kernel IRQ chip: {:?}", e) @@ -123,6 +130,7 @@ impl fmt::Display for Error { &Error::OpenKernel(ref p, ref e) => { write!(f, "failed to open kernel image {:?}: {}", p, e) } + &Error::OpenGpuBufferDevice => write!(f, "failed to open GPU buffer device"), &Error::PollContextAdd(ref e) => write!(f, "failed to add fd to poll context: {:?}", e), &Error::QcowDeviceCreate(ref e) => { write!(f, "failed to read qcow formatted file {:?}", e) @@ -543,6 +551,56 @@ fn run_vcpu(vcpu: Vcpu, .map_err(Error::SpawnVcpu) } +#[cfg(feature = "wl-dmabuf")] +struct GpuBufferDevice { + device: gpu_buffer::Device, +} + +#[cfg(feature = "wl-dmabuf")] +impl GpuMemoryAllocator for GpuBufferDevice { + fn allocate(&self, width: u32, height: u32, format: u32) -> sys_util::Result<(File, u32)> { + let buffer = match self.device.create_buffer( + width, + height, + gpu_buffer::Format::from(format), + // Linear layout is a requirement as virtio wayland guest expects + // this for CPU access to the buffer. Scanout and texturing are + // optional as the consumer (wayland compositor) is expected to + // fall-back to a less efficient meachnisms for presentation if + // neccesary. In practice, linear buffers for commonly used formats + // will also support scanout and texturing. + gpu_buffer::Flags::empty().use_linear(true)) { + Ok(v) => v, + Err(_) => return Err(sys_util::Error::new(EINVAL)), + }; + // We only support the first plane. Buffers with more planes are not + // a problem but additional planes will not be registered for access + // from guest. + let fd = match buffer.export_plane_fd(0) { + Ok(v) => v, + Err(e) => return Err(sys_util::Error::new(e)), + }; + + Ok((fd, buffer.stride())) + } +} + +#[cfg(feature = "wl-dmabuf")] +fn create_gpu_memory_allocator() -> Result>> { + let undesired: &[&str] = &["vgem"]; + let fd = gpu_buffer::rendernode::open_device(undesired) + .map_err(|_| Error::OpenGpuBufferDevice)?; + let device = gpu_buffer::Device::new(fd) + .map_err(|_| Error::CreateGpuBufferDevice)?; + info!("created GPU buffer device for DMABuf allocations"); + Ok(Some(Box::new(GpuBufferDevice { device }))) +} + +#[cfg(not(feature = "wl-dmabuf"))] +fn create_gpu_memory_allocator() -> Result>> { + Ok(None) +} + fn run_control(vm: &mut Vm, control_sockets: Vec, next_dev_pfn: &mut u64, @@ -552,7 +610,8 @@ fn run_control(vm: &mut Vm, kill_signaled: Arc, vcpu_handles: Vec>, balloon_host_socket: UnixDatagram, - _irqchip_fd: Option) + _irqchip_fd: Option, + gpu_memory_allocator: Option>) -> Result<()> { const MAX_VM_FD_RECV: usize = 1; @@ -638,8 +697,13 @@ fn run_control(vm: &mut Vm, Ok(request) => { let mut running = true; let response = - request.execute(vm, next_dev_pfn, - &mut running, &balloon_host_socket); + request.execute(vm, + next_dev_pfn, + &mut running, + &balloon_host_socket, + gpu_memory_allocator.as_ref().map(|v| { + v.as_ref() + })); if let Err(e) = response.send(&mut scm, socket.as_ref()) { error!("failed to send VmResponse: {:?}", e); } @@ -751,6 +815,12 @@ pub fn run_config(cfg: Config) -> Result<()> { &mut control_sockets, balloon_device_socket)?; + let gpu_memory_allocator = if cfg.wayland_dmabuf { + create_gpu_memory_allocator()? + } else { + None + }; + for param in &cfg.params { cmdline.insert_str(¶m).map_err(Error::Cmdline)?; } @@ -787,5 +857,6 @@ pub fn run_config(cfg: Config) -> Result<()> { kill_signaled, vcpu_handles, balloon_host_socket, - irq_chip) + irq_chip, + gpu_memory_allocator) } diff --git a/src/main.rs b/src/main.rs index f8ed7155a0..7c248e204c 100644 --- a/src/main.rs +++ b/src/main.rs @@ -29,6 +29,8 @@ extern crate data_model; extern crate plugin_proto; #[cfg(feature = "plugin")] extern crate protobuf; +#[cfg(feature = "wl-dmabuf")] +extern crate gpu_buffer; pub mod argument; pub mod linux; @@ -71,6 +73,7 @@ pub struct Config { mac_address: Option, vhost_net: bool, wayland_socket_path: Option, + wayland_dmabuf: bool, socket_path: Option, multiprocess: bool, seccomp_policy_dir: PathBuf, @@ -92,6 +95,7 @@ impl Default for Config { mac_address: None, vhost_net: false, wayland_socket_path: None, + wayland_dmabuf: false, socket_path: None, multiprocess: !cfg!(feature = "default-no-sandbox"), seccomp_policy_dir: PathBuf::from(SECCOMP_POLICY_DIR), @@ -269,6 +273,10 @@ fn set_argument(cfg: &mut Config, name: &str, value: Option<&str>) -> argument:: } cfg.wayland_socket_path = Some(wayland_socket_path); } + #[cfg(feature = "wl-dmabuf")] + "enable-wayland-dmabuf" => { + cfg.wayland_dmabuf = true + }, "socket" => { if cfg.socket_path.is_some() { return Err(argument::Error::TooManyArguments("`socket` already given".to_owned())); @@ -363,6 +371,8 @@ fn run_vm(args: std::env::Args) -> std::result::Result<(), ()> { Argument::value("wayland-group", "GROUP", "Name of the group with access to the Wayland socket."), + #[cfg(feature = "wl-dmabuf")] + Argument::flag("wayland-dmabuf", "Enable support for DMABufs in Wayland device."), Argument::short_value('s', "socket", "PATH", diff --git a/vm_control/src/lib.rs b/vm_control/src/lib.rs index ec0522e865..3837bb45af 100644 --- a/vm_control/src/lib.rs +++ b/vm_control/src/lib.rs @@ -17,15 +17,16 @@ extern crate libc; extern crate sys_util; use std::fs::File; +use std::io::{Seek, SeekFrom}; use std::os::unix::io::{AsRawFd, RawFd}; use std::os::unix::net::UnixDatagram; use std::result; -use libc::{ERANGE, EINVAL}; +use libc::{ERANGE, EINVAL, ENODEV}; use byteorder::{LittleEndian, WriteBytesExt}; use data_model::{DataInit, Le32, Le64, VolatileMemory}; -use sys_util::{EventFd, Error as SysError, MmapError, MemoryMapping, Scm, GuestAddress}; +use sys_util::{EventFd, Result, Error as SysError, MmapError, MemoryMapping, Scm, GuestAddress}; use kvm::{IoeventAddress, Vm}; #[derive(Debug, PartialEq)] @@ -80,13 +81,17 @@ pub enum VmRequest { RegisterMemory(MaybeOwnedFd, usize), /// Unregister the given memory slot that was previously registereed with `RegisterMemory`. UnregisterMemory(u32), + /// Allocate GPU buffer of a given size/format and register the memory into guest address space. + /// The response variant is `VmResponse::AllocateAndRegisterGpuMemory` + AllocateAndRegisterGpuMemory { width: u32, height: u32, format: u32 }, } const VM_REQUEST_TYPE_EXIT: u32 = 1; const VM_REQUEST_TYPE_REGISTER_MEMORY: u32 = 2; const VM_REQUEST_TYPE_UNREGISTER_MEMORY: u32 = 3; const VM_REQUEST_TYPE_BALLOON_ADJUST: u32 = 4; -const VM_REQUEST_SIZE: usize = 24; +const VM_REQUEST_TYPE_ALLOCATE_AND_REGISTER_GPU_MEMORY: u32 = 5; +const VM_REQUEST_SIZE: usize = 32; #[repr(C)] #[derive(Clone, Copy, Default)] @@ -95,11 +100,51 @@ struct VmRequestStruct { slot: Le32, size: Le64, num_pages: Le32, + width: Le32, + height: Le32, + format: Le32, } // Safe because it only has data and has no implicit padding. unsafe impl DataInit for VmRequestStruct {} +fn register_memory(vm: &mut Vm, next_mem_pfn: &mut u64, fd: &AsRawFd, size: usize) -> Result<(u64, u32)> { + let mmap = match MemoryMapping::from_fd(fd, size) { + Ok(v) => v, + Err(MmapError::SystemCallFailed(e)) => return Err(e), + _ => return Err(SysError::new(EINVAL)), + }; + let pfn = *next_mem_pfn; + let slot = + match vm.add_device_memory(GuestAddress(pfn << 12), mmap, false, false) { + Ok(v) => v, + Err(e) => return Err(e), + }; + // TODO(zachr): Use a smarter allocation strategy. The current strategy is just + // bumping this pointer, meaning the remove operation does not free any address + // space. Given enough allocations, device memory may run out of address space and + // collide with guest memory or MMIO address space. There is currently nothing in + // place to limit the amount of address space used by device memory. + *next_mem_pfn += (((size + 0x7ff) >> 12) + 1) as u64; + + Ok((pfn, slot)) +} + +/// Trait that needs to be implemented in order to service GPU memory allocation +/// requests. Implementations are expected to support some set of buffer sizes and +/// formats but every possible combination is not required. +pub trait GpuMemoryAllocator { + /// Allocates GPU memory for a buffer of a specific size and format. The memory + /// layout for the returned buffer must be linear. A file handle and the stride + /// for the buffer are returned on success. + /// + /// # Arguments + /// * `width` - Width of buffer. + /// * `height` - Height of buffer. + /// * `format` - Fourcc format of buffer. + fn allocate(&self, width: u32, height: u32, format: u32) -> Result<(File, u32)>; +} + impl VmRequest { /// Receive a `VmRequest` from the given socket. /// @@ -128,6 +173,12 @@ impl VmRequest { VM_REQUEST_TYPE_BALLOON_ADJUST => { Ok(VmRequest::BalloonAdjust(req.num_pages.to_native() as i32)) }, + VM_REQUEST_TYPE_ALLOCATE_AND_REGISTER_GPU_MEMORY => { + Ok(VmRequest::AllocateAndRegisterGpuMemory { width: req.width.to_native(), + height: req.height.to_native(), + format: req.format.to_native() + }) + }, _ => Err(VmControlError::InvalidType), } } @@ -157,6 +208,12 @@ impl VmRequest { req.type_ = Le32::from(VM_REQUEST_TYPE_BALLOON_ADJUST); req.num_pages = Le32::from(pages as u32); }, + &VmRequest::AllocateAndRegisterGpuMemory { width, height, format } => { + req.type_ = Le32::from(VM_REQUEST_TYPE_ALLOCATE_AND_REGISTER_GPU_MEMORY); + req.width = Le32::from(width as u32); + req.height = Le32::from(height as u32); + req.format = Le32::from(format as u32); + }, _ => return Err(VmControlError::InvalidType), } let mut buf = [0; VM_REQUEST_SIZE]; @@ -178,7 +235,8 @@ impl VmRequest { /// `VmResponse` with the intended purpose of sending the response back over the socket that /// received this `VmRequest`. pub fn execute(&self, vm: &mut Vm, next_mem_pfn: &mut u64, running: &mut bool, - balloon_host_socket: &UnixDatagram) -> VmResponse { + balloon_host_socket: &UnixDatagram, + gpu_memory_allocator: Option<&GpuMemoryAllocator>) -> VmResponse { *running = true; match self { &VmRequest::Exit => { @@ -198,26 +256,9 @@ impl VmRequest { } } &VmRequest::RegisterMemory(ref fd, size) => { - let mmap = match MemoryMapping::from_fd(fd, size) { - Ok(v) => v, - Err(MmapError::SystemCallFailed(e)) => return VmResponse::Err(e), - _ => return VmResponse::Err(SysError::new(EINVAL)), - }; - let pfn = *next_mem_pfn; - let slot = - match vm.add_device_memory(GuestAddress(pfn << 12), mmap, false, false) { - Ok(slot) => slot, - Err(e) => return VmResponse::Err(e), - }; - // TODO(zachr): Use a smarter allocation strategy. The current strategy is just - // bumping this pointer, meaning the remove operation does not free any address - // space. Given enough allocations, device memory may run out of address space and - // collide with guest memory or MMIO address space. There is currently nothing in - // place to limit the amount of address space used by device memory. - *next_mem_pfn += (((size + 0x7ff) >> 12) + 1) as u64; - VmResponse::RegisterMemory { - pfn: pfn, - slot: slot, + match register_memory(vm, next_mem_pfn, fd, size) { + Ok((pfn, slot)) => VmResponse::RegisterMemory { pfn, slot }, + Err(e) => VmResponse::Err(e), } } &VmRequest::UnregisterMemory(slot) => { @@ -234,7 +275,31 @@ impl VmRequest { Ok(_) => VmResponse::Ok, Err(_) => VmResponse::Err(SysError::last()), } - }, + } + &VmRequest::AllocateAndRegisterGpuMemory {width, height, format} => { + let allocator = match gpu_memory_allocator { + Some(v) => v, + None => return VmResponse::Err(SysError::new(ENODEV)), + }; + let (mut fd, stride) = match allocator.allocate(width, height, format) { + Ok(v) => v, + Err(e) => return VmResponse::Err(e), + }; + // Determine size of buffer using 0 byte seek from end. This is preferred over + // `stride * height` as it's not limited to packed pixel formats. + let size = match fd.seek(SeekFrom::End(0)) { + Ok(v) => v, + Err(e) => return VmResponse::Err(SysError::from(e)), + }; + match register_memory(vm, next_mem_pfn, &fd, size as usize) { + Ok((pfn, slot)) => VmResponse::AllocateAndRegisterGpuMemory { + fd: MaybeOwnedFd::Owned(fd), + pfn, + slot, + stride }, + Err(e) => VmResponse::Err(e), + } + } } } } @@ -242,7 +307,6 @@ impl VmRequest { /// Indication of success or failure of a `VmRequest`. /// /// Success is usually indicated `VmResponse::Ok` unless there is data associated with the response. -#[derive(Debug, PartialEq)] pub enum VmResponse { /// Indicates the request was executed successfully. Ok, @@ -251,11 +315,15 @@ pub enum VmResponse { /// The request to register memory into guest address space was successfully done at page frame /// number `pfn` and memory slot number `slot`. RegisterMemory { pfn: u64, slot: u32 }, + /// The request to allocate and register GPU memory into guest address space was successfully + /// done at page frame number `pfn` and memory slot number `slot` for buffer with `stride`. + AllocateAndRegisterGpuMemory { fd: MaybeOwnedFd, pfn: u64, slot: u32, stride: u32 }, } const VM_RESPONSE_TYPE_OK: u32 = 1; const VM_RESPONSE_TYPE_ERR: u32 = 2; const VM_RESPONSE_TYPE_REGISTER_MEMORY: u32 = 3; +const VM_RESPONSE_TYPE_ALLOCATE_AND_REGISTER_GPU_MEMORY: u32 = 4; const VM_RESPONSE_SIZE: usize = 24; #[repr(C)] @@ -265,7 +333,7 @@ struct VmResponseStruct { errno: Le32, pfn: Le64, slot: Le32, - padding: Le32, + stride: Le32, } // Safe because it only has data and has no implicit padding. @@ -296,6 +364,15 @@ impl VmResponse { slot: resp.slot.into(), }) } + VM_RESPONSE_TYPE_ALLOCATE_AND_REGISTER_GPU_MEMORY => { + let fd = fds.pop().ok_or(VmControlError::ExpectFd)?; + Ok(VmResponse::AllocateAndRegisterGpuMemory { + fd: MaybeOwnedFd::Owned(fd), + pfn: resp.pfn.into(), + slot: resp.slot.into(), + stride: resp.stride.into() + }) + } _ => Err(VmControlError::InvalidType), } } @@ -306,6 +383,8 @@ impl VmResponse { /// execution. pub fn send(&self, scm: &mut Scm, s: &UnixDatagram) -> VmControlResult<()> { let mut resp = VmResponseStruct::default(); + let mut fd_buf = [0; 1]; + let mut fd_len = 0; match self { &VmResponse::Ok => resp.type_ = Le32::from(VM_RESPONSE_TYPE_OK), &VmResponse::Err(e) => { @@ -317,10 +396,18 @@ impl VmResponse { resp.pfn = Le64::from(pfn); resp.slot = Le32::from(slot); } + &VmResponse::AllocateAndRegisterGpuMemory {ref fd, pfn, slot, stride } => { + fd_buf[0] = fd.as_raw_fd(); + fd_len = 1; + resp.type_ = Le32::from(VM_RESPONSE_TYPE_ALLOCATE_AND_REGISTER_GPU_MEMORY); + resp.pfn = Le64::from(pfn); + resp.slot = Le32::from(slot); + resp.stride = Le32::from(stride); + } } let mut buf = [0; VM_RESPONSE_SIZE]; buf.as_mut().get_ref(0).unwrap().store(resp); - scm.send(s, &[buf.as_ref()], &[]) + scm.send(s, &[buf.as_ref()], &fd_buf[..fd_len]) .map_err(|e| VmControlError::Send(e))?; Ok(()) } @@ -419,7 +506,7 @@ mod tests { fn request_invalid_type() { let (s1, s2) = UnixDatagram::pair().expect("failed to create socket pair"); let mut scm = Scm::new(1); - scm.send(&s2, &[[12; VM_RESPONSE_SIZE].as_ref()], &[]) + scm.send(&s2, &[[12; VM_REQUEST_SIZE].as_ref()], &[]) .unwrap(); match VmRequest::recv(&mut scm, &s1) { Err(VmControlError::InvalidType) => {} @@ -427,13 +514,34 @@ mod tests { } } + #[test] + fn request_allocate_and_register_gpu_memory() { + let (s1, s2) = UnixDatagram::pair().expect("failed to create socket pair"); + let mut scm = Scm::new(1); + let gpu_width: u32 = 32; + let gpu_height: u32 = 32; + let gpu_format: u32 = 0x34325258; + let r = VmRequest::AllocateAndRegisterGpuMemory { width: gpu_width, height: gpu_height, format: gpu_format }; + r.send(&mut scm, &s1).unwrap(); + match VmRequest::recv(&mut scm, &s2).unwrap() { + VmRequest::AllocateAndRegisterGpuMemory {width, height, format} => { + assert_eq!(width, gpu_width); + assert_eq!(height, gpu_width); + assert_eq!(format, gpu_format); + } + _ => panic!("recv wrong request variant"), + } + } + #[test] fn resp_ok() { let (s1, s2) = UnixDatagram::pair().expect("failed to create socket pair"); let mut scm = Scm::new(1); VmResponse::Ok.send(&mut scm, &s1).unwrap(); - let r = VmResponse::recv(&mut scm, &s2).unwrap(); - assert_eq!(r, VmResponse::Ok); + match VmResponse::recv(&mut scm, &s2).unwrap() { + VmResponse::Ok => {} + _ => panic!("recv wrong response variant"), + } } #[test] @@ -442,18 +550,29 @@ mod tests { let mut scm = Scm::new(1); let r1 = VmResponse::Err(SysError::new(libc::EDESTADDRREQ)); r1.send(&mut scm, &s1).unwrap(); - let r2 = VmResponse::recv(&mut scm, &s2).unwrap(); - assert_eq!(r1, r2); + match VmResponse::recv(&mut scm, &s2).unwrap() { + VmResponse::Err(e) => { + assert_eq!(e, SysError::new(libc::EDESTADDRREQ)); + } + _ => panic!("recv wrong response variant"), + } } #[test] fn resp_memory() { let (s1, s2) = UnixDatagram::pair().expect("failed to create socket pair"); let mut scm = Scm::new(1); - let r1 = VmResponse::RegisterMemory { pfn: 55, slot: 66 }; + let memory_pfn = 55; + let memory_slot = 66; + let r1 = VmResponse::RegisterMemory { pfn: memory_pfn, slot: memory_slot }; r1.send(&mut scm, &s1).unwrap(); - let r2 = VmResponse::recv(&mut scm, &s2).unwrap(); - assert_eq!(r1, r2); + match VmResponse::recv(&mut scm, &s2).unwrap() { + VmResponse::RegisterMemory { pfn, slot } => { + assert_eq!(pfn, memory_pfn); + assert_eq!(slot, memory_slot); + } + _ => panic!("recv wrong response variant"), + } } #[test] @@ -461,8 +580,12 @@ mod tests { let (s1, _) = UnixDatagram::pair().expect("failed to create socket pair"); let mut scm = Scm::new(1); s1.shutdown(Shutdown::Both).unwrap(); - let r = VmResponse::recv(&mut scm, &s1); - assert_eq!(r, Err(VmControlError::BadSize(0))); + match VmResponse::recv(&mut scm, &s1) { + Err(e) => { + assert_eq!(e, VmControlError::BadSize(0)); + } + _ => panic!("recv wrong response"), + } } #[test] @@ -470,8 +593,12 @@ mod tests { let (s1, s2) = UnixDatagram::pair().expect("failed to create socket pair"); let mut scm = Scm::new(1); scm.send(&s2, &[[12; 7].as_ref()], &[]).unwrap(); - let r = VmResponse::recv(&mut scm, &s1); - assert_eq!(r, Err(VmControlError::BadSize(7))); + match VmResponse::recv(&mut scm, &s1) { + Err(e) => { + assert_eq!(e, VmControlError::BadSize(7)); + } + _ => panic!("recv wrong response"), + } } #[test] @@ -480,7 +607,39 @@ mod tests { let mut scm = Scm::new(1); scm.send(&s2, &[[12; VM_RESPONSE_SIZE].as_ref()], &[]) .unwrap(); - let r = VmResponse::recv(&mut scm, &s1); - assert_eq!(r, Err(VmControlError::InvalidType)); + match VmResponse::recv(&mut scm, &s1) { + Err(e) => { + assert_eq!(e, VmControlError::InvalidType); + } + _ => panic!("recv wrong response"), + } + } + + #[test] + fn resp_allocate_and_register_gpu_memory() { + if !kernel_has_memfd() { return; } + let (s1, s2) = UnixDatagram::pair().expect("failed to create socket pair"); + let mut scm = Scm::new(1); + let shm_size: usize = 4096; + let mut shm = SharedMemory::new(None).unwrap(); + shm.set_size(shm_size as u64).unwrap(); + let memory_pfn = 55; + let memory_slot = 66; + let gpu_stride = 32; + let r1 = VmResponse::AllocateAndRegisterGpuMemory { + fd: MaybeOwnedFd::Borrowed(shm.as_raw_fd()), + pfn: memory_pfn, + slot: memory_slot, + stride: gpu_stride }; + r1.send(&mut scm, &s1).unwrap(); + match VmResponse::recv(&mut scm, &s2).unwrap() { + VmResponse::AllocateAndRegisterGpuMemory { fd, pfn, slot, stride } => { + assert!(fd.as_raw_fd() >= 0); + assert_eq!(pfn, memory_pfn); + assert_eq!(slot, memory_slot); + assert_eq!(stride, gpu_stride); + } + _ => panic!("recv wrong response variant"), + } } }