blob: 3a90d939fea31c97994d388e271411b50971ab4e [file] [log] [blame]
// Copyright 2021 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::{cell::RefCell, collections::BTreeMap, fs::File, path::PathBuf, rc::Rc, sync::Arc};
use anyhow::{anyhow, bail, Context};
use argh::FromArgs;
use async_task::Task;
use base::{
clone_descriptor, error, warn, Event, FromRawDescriptor, IntoRawDescriptor, SafeDescriptor,
Timer, Tube, UnixSeqpacketListener, UnlinkUnixSeqpacketListener,
};
use cros_async::{AsyncTube, AsyncWrapper, EventAsync, Executor, IoSourceExt, TimerAsync};
use futures::{
future::{select, Either},
pin_mut,
};
use hypervisor::ProtectionType;
use sync::Mutex;
use vm_memory::GuestMemory;
use vmm_vhost::message::{VhostUserProtocolFeatures, VhostUserVirtioFeatures};
use crate::virtio::{
self, gpu,
vhost::user::device::handler::{DeviceRequestHandler, Doorbell, VhostUserBackend},
vhost::user::device::wl::parse_wayland_sock,
DescriptorChain, Gpu, GpuDisplayParameters, GpuParameters, Queue, QueueReader, VirtioDevice,
};
#[derive(Clone)]
struct SharedReader {
queue: Arc<Mutex<Queue>>,
doorbell: Arc<Mutex<Doorbell>>,
}
impl gpu::QueueReader for SharedReader {
fn pop(&self, mem: &GuestMemory) -> Option<DescriptorChain> {
self.queue.lock().pop(mem)
}
fn add_used(&self, mem: &GuestMemory, desc_index: u16, len: u32) {
self.queue.lock().add_used(mem, desc_index, len)
}
fn signal_used(&self, mem: &GuestMemory) {
self.queue.lock().trigger_interrupt(mem, &self.doorbell);
}
}
async fn run_ctrl_queue(
reader: SharedReader,
mem: GuestMemory,
kick_evt: EventAsync,
mut timer: TimerAsync,
state: Rc<RefCell<gpu::Frontend>>,
) {
loop {
if state.borrow().has_pending_fences() {
if let Err(e) = timer.reset(gpu::FENCE_POLL_INTERVAL, None) {
error!("Failed to reset fence timer: {}", e);
break;
}
let kick_value = kick_evt.next_val();
let timer_value = timer.next_val();
pin_mut!(kick_value);
pin_mut!(timer_value);
match select(kick_value, timer_value).await {
Either::Left((res, _)) => {
if let Err(e) = res {
error!("Failed to read kick event for ctrl queue: {}", e);
break;
}
}
Either::Right((res, _)) => {
if let Err(e) = res {
error!("Failed to read timer for ctrl queue: {}", e);
break;
}
}
}
} else if let Err(e) = kick_evt.next_val().await {
error!("Failed to read kick event for ctrl queue: {}", e);
break;
}
let mut state = state.borrow_mut();
let needs_interrupt = state.process_queue(&mem, &reader);
state.fence_poll();
if needs_interrupt {
reader.signal_used(&mem);
}
}
}
async fn run_display(
display: Box<dyn IoSourceExt<AsyncWrapper<SafeDescriptor>>>,
state: Rc<RefCell<gpu::Frontend>>,
) {
loop {
if let Err(e) = display.wait_readable().await {
error!(
"Failed to wait for display context to become readable: {}",
e
);
break;
}
if state.borrow_mut().process_display() {
break;
}
}
}
async fn run_resource_bridge(tube: Box<dyn IoSourceExt<Tube>>, state: Rc<RefCell<gpu::Frontend>>) {
loop {
if let Err(e) = tube.wait_readable().await {
error!(
"Failed to wait for resource bridge tube to become readable: {}",
e
);
break;
}
if let Err(e) = state.borrow_mut().process_resource_bridge(tube.as_source()) {
error!("Failed to process resource bridge: {:#}", e);
break;
}
}
}
fn cancel_task<R: 'static>(ex: &Executor, task: Task<R>) {
ex.spawn_local(task.cancel()).detach()
}
struct GpuBackend {
ex: Executor,
gpu: Rc<RefCell<Gpu>>,
resource_bridges: Arc<Mutex<Vec<Tube>>>,
acked_protocol_features: u64,
state: Option<Rc<RefCell<gpu::Frontend>>>,
fence_state: Arc<Mutex<gpu::FenceState>>,
display_worker: Option<Task<()>>,
workers: [Option<Task<()>>; Self::MAX_QUEUE_NUM],
}
impl VhostUserBackend for GpuBackend {
const MAX_QUEUE_NUM: usize = gpu::QUEUE_SIZES.len();
const MAX_VRING_LEN: u16 = gpu::QUEUE_SIZES[0];
type Error = anyhow::Error;
fn features(&self) -> u64 {
self.gpu.borrow().features() | VhostUserVirtioFeatures::PROTOCOL_FEATURES.bits()
}
fn ack_features(&mut self, value: u64) -> anyhow::Result<()> {
self.gpu.borrow_mut().ack_features(value);
Ok(())
}
fn acked_features(&self) -> u64 {
self.features()
}
fn protocol_features(&self) -> VhostUserProtocolFeatures {
VhostUserProtocolFeatures::CONFIG
| VhostUserProtocolFeatures::SLAVE_REQ
| VhostUserProtocolFeatures::MQ
}
fn ack_protocol_features(&mut self, features: u64) -> anyhow::Result<()> {
let unrequested_features = features & !self.protocol_features().bits();
if unrequested_features != 0 {
bail!("Unexpected protocol features: {:#x}", unrequested_features);
}
self.acked_protocol_features |= features;
Ok(())
}
fn acked_protocol_features(&self) -> u64 {
self.acked_protocol_features
}
fn read_config(&self, offset: u64, dst: &mut [u8]) {
self.gpu.borrow().read_config(offset, dst)
}
fn write_config(&self, offset: u64, data: &[u8]) {
self.gpu.borrow_mut().write_config(offset, data)
}
fn set_device_request_channel(&mut self, channel: File) -> anyhow::Result<()> {
let tube = AsyncTube::new(&self.ex, unsafe {
Tube::from_raw_descriptor(channel.into_raw_descriptor())
})
.context("failed to create AsyncTube")?;
// We need a PciAddress in order to initialize the pci bar but this isn't part of the
// vhost-user protocol. Instead we expect this to be the first message the crosvm main
// process sends us on the device tube.
let gpu = Rc::clone(&self.gpu);
self.ex
.spawn_local(async move {
let response = match tube.next().await {
Ok(addr) => gpu.borrow_mut().get_device_bars(addr),
Err(e) => {
error!("Failed to get `PciAddr` from tube: {}", e);
return;
}
};
if let Err(e) = tube.send(response).await {
error!("Failed to send `PciBarConfiguration`: {}", e);
}
let device_tube: Tube = match tube.next().await {
Ok(tube) => tube,
Err(e) => {
error!("Failed to get device tube: {}", e);
return;
}
};
gpu.borrow_mut().set_device_tube(device_tube);
})
.detach();
Ok(())
}
fn start_queue(
&mut self,
idx: usize,
queue: Queue,
mem: GuestMemory,
doorbell: Arc<Mutex<Doorbell>>,
kick_evt: Event,
) -> anyhow::Result<()> {
if let Some(task) = self.workers.get_mut(idx).and_then(Option::take) {
warn!("Starting new queue handler without stopping old handler");
cancel_task(&self.ex, task);
}
match idx {
// ctrl queue.
0 => {}
// We don't currently handle the cursor queue.
1 => return Ok(()),
_ => bail!("attempted to start unknown queue: {}", idx),
}
let kick_evt = EventAsync::new(kick_evt, &self.ex)
.context("failed to create EventAsync for kick_evt")?;
let reader = SharedReader {
queue: Arc::new(Mutex::new(queue)),
doorbell,
};
let state = if let Some(s) = self.state.as_ref() {
s.clone()
} else {
let fence_handler =
gpu::create_fence_handler(mem.clone(), reader.clone(), self.fence_state.clone());
let state = Rc::new(RefCell::new(
self.gpu
.borrow_mut()
.initialize_frontend(self.fence_state.clone(), fence_handler)
.ok_or_else(|| anyhow!("failed to initialize gpu frontend"))?,
));
self.state = Some(state.clone());
state
};
// Start handling the resource bridges if we haven't already.
for bridge in self.resource_bridges.lock().drain(..) {
let tube = self
.ex
.async_from(bridge)
.context("failed to create async tube")?;
self.ex
.spawn_local(run_resource_bridge(tube, state.clone()))
.detach();
}
// Start handling the display, if we haven't already.
if self.display_worker.is_none() {
let display = clone_descriptor(&*state.borrow_mut().display().borrow())
.map(|fd| {
// Safe because we just created this fd.
AsyncWrapper::new(unsafe { SafeDescriptor::from_raw_descriptor(fd) })
})
.context("failed to clone inner WaitContext for gpu display")
.and_then(|ctx| {
self.ex
.async_from(ctx)
.context("failed to create async WaitContext")
})?;
let task = self.ex.spawn_local(run_display(display, state.clone()));
self.display_worker = Some(task);
}
let timer = Timer::new()
.context("failed to create Timer")
.and_then(|t| TimerAsync::new(t, &self.ex).context("failed to create TimerAsync"))?;
let task = self
.ex
.spawn_local(run_ctrl_queue(reader, mem, kick_evt, timer, state));
self.workers[idx] = Some(task);
Ok(())
}
fn stop_queue(&mut self, idx: usize) {
if let Some(task) = self.workers.get_mut(idx).and_then(Option::take) {
cancel_task(&self.ex, task)
}
}
fn reset(&mut self) {
if let Some(task) = self.display_worker.take() {
cancel_task(&self.ex, task)
}
for task in self.workers.iter_mut().filter_map(Option::take) {
cancel_task(&self.ex, task)
}
}
}
fn gpu_parameters_from_str(input: &str) -> Result<GpuParameters, String> {
serde_json::from_str(input).map_err(|e| e.to_string())
}
#[derive(FromArgs)]
#[argh(description = "run gpu device")]
struct Options {
#[argh(
option,
description = "path to bind a listening vhost-user socket",
arg_name = "PATH"
)]
socket: String,
#[argh(
option,
description = "path to one or more Wayland sockets. The unnamed socket is \
used for displaying virtual screens while the named ones are used for IPC",
from_str_fn(parse_wayland_sock),
arg_name = "PATH[,name=NAME]"
)]
wayland_sock: Vec<(String, PathBuf)>,
#[argh(
option,
description = "path to one or more bridge sockets for communicating with \
other graphics devices (wayland, video, etc)",
arg_name = "PATH"
)]
resource_bridge: Vec<String>,
#[argh(option, description = " X11 display name to use", arg_name = "DISPLAY")]
x_display: Option<String>,
#[argh(
option,
from_str_fn(gpu_parameters_from_str),
default = "Default::default()",
description = "a JSON object of virtio-gpu parameters",
arg_name = "JSON"
)]
params: GpuParameters,
}
pub fn run_gpu_device(program_name: &str, args: &[&str]) -> anyhow::Result<()> {
let Options {
x_display,
params: mut gpu_parameters,
resource_bridge,
socket,
wayland_sock,
} = match Options::from_args(&[program_name], args) {
Ok(opts) => opts,
Err(e) => {
if e.status.is_err() {
bail!(e.output);
} else {
println!("{}", e.output);
}
return Ok(());
}
};
base::syslog::init().context("failed to initialize syslog")?;
let wayland_paths: BTreeMap<_, _> = wayland_sock.into_iter().collect();
let resource_bridge_listeners = resource_bridge
.into_iter()
.map(|p| {
UnixSeqpacketListener::bind(&p)
.map(UnlinkUnixSeqpacketListener)
.with_context(|| format!("failed to bind socket at path {}", p))
})
.collect::<anyhow::Result<Vec<_>>>()?;
if gpu_parameters.displays.is_empty() {
gpu_parameters
.displays
.push(GpuDisplayParameters::default());
}
let ex = Executor::new().context("failed to create executor")?;
// We don't know the order in which other devices are going to connect to the resource bridges
// so start listening for all of them on separate threads. Any devices that connect after the
// gpu device starts its queues will not have its resource bridges processed. In practice this
// should be fine since the devices that use the resource bridge always try to connect to the
// gpu device before handling messages from the VM.
let resource_bridges = Arc::new(Mutex::new(Vec::with_capacity(
resource_bridge_listeners.len(),
)));
for listener in resource_bridge_listeners {
let resource_bridges = Arc::clone(&resource_bridges);
ex.spawn_blocking(move || match listener.accept() {
Ok(stream) => resource_bridges.lock().push(Tube::new(stream)),
Err(e) => {
let path = listener
.path()
.unwrap_or_else(|_| PathBuf::from("{unknown}"));
error!(
"Failed to accept resource bridge connection for socket {}: {}",
path.display(),
e
);
}
})
.detach();
}
// TODO(b/232344535): Read side of the tube is ignored currently.
// Complete the implementation by polling `exit_evt_rdtube` and
// kill the sibling VM.
let (exit_evt_wrtube, _) =
Tube::directional_pair().context("failed to create vm event tube")?;
// Initialized later.
let gpu_device_tube = None;
let mut display_backends = vec![
virtio::DisplayBackend::X(x_display),
virtio::DisplayBackend::Stub,
];
if let Some(p) = wayland_paths.get("") {
display_backends.insert(0, virtio::DisplayBackend::Wayland(Some(p.to_owned())));
}
// These are only used when there is an input device.
let event_devices = Vec::new();
// This is only used in single-process mode, even for the regular gpu device.
let map_request = Arc::new(Mutex::new(None));
// The regular gpu device sets this to true when sandboxing is enabled. Assume that we
// are always sandboxed.
let external_blob = true;
let base_features = virtio::base_features(ProtectionType::Unprotected);
let channels = wayland_paths;
let gpu = Rc::new(RefCell::new(Gpu::new(
exit_evt_wrtube,
gpu_device_tube,
Vec::new(), // resource_bridges, handled separately by us
display_backends,
&gpu_parameters,
None,
event_devices,
map_request,
external_blob,
base_features,
channels,
)));
let backend = GpuBackend {
ex: ex.clone(),
gpu,
resource_bridges,
acked_protocol_features: 0,
state: None,
fence_state: Default::default(),
display_worker: None,
workers: Default::default(),
};
let handler = DeviceRequestHandler::new(backend);
// run_until() returns an Result<Result<..>> which the ? operator lets us flatten.
ex.run_until(handler.run(socket, &ex))?
}