blob: ca591cd99836f83773effab18c48cff961309738 [file] [log] [blame] [edit]
// Copyright 2018 The ChromiumOS Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
use std::collections::BTreeMap;
use std::io;
use std::io::Read;
use std::io::Write;
use std::ops::BitOrAssign;
use anyhow::anyhow;
use anyhow::Context;
use base::error;
use base::Event;
use base::EventToken;
use base::RawDescriptor;
use base::WaitContext;
use base::WorkerThread;
use remain::sorted;
use thiserror::Error;
use vm_memory::GuestMemory;
use super::DescriptorChain;
use super::DeviceType;
use super::Interrupt;
use super::Queue;
use super::VirtioDevice;
// A single queue of size 2. The guest kernel driver will enqueue a single
// descriptor chain containing one command buffer and one response buffer at a
// time.
const QUEUE_SIZE: u16 = 2;
const QUEUE_SIZES: &[u16] = &[QUEUE_SIZE];
// Maximum command or response message size permitted by this device
// implementation. Named to match the equivalent constant in Linux's tpm.h.
// There is no hard requirement that the value is the same but it makes sense.
const TPM_BUFSIZE: usize = 4096;
struct Worker {
interrupt: Interrupt,
queue: Queue,
backend: Box<dyn TpmBackend>,
}
pub trait TpmBackend: Send {
fn execute_command<'a>(&'a mut self, command: &[u8]) -> &'a [u8];
}
impl Worker {
fn perform_work(&mut self, desc: &mut DescriptorChain) -> Result<u32> {
let available_bytes = desc.reader.available_bytes();
if available_bytes > TPM_BUFSIZE {
return Err(Error::CommandTooLong {
size: available_bytes,
});
}
let mut command = vec![0u8; available_bytes];
desc.reader.read_exact(&mut command).map_err(Error::Read)?;
let response = self.backend.execute_command(&command);
if response.len() > TPM_BUFSIZE {
return Err(Error::ResponseTooLong {
size: response.len(),
});
}
let writer_len = desc.writer.available_bytes();
if response.len() > writer_len {
return Err(Error::BufferTooSmall {
size: writer_len,
required: response.len(),
});
}
desc.writer.write_all(response).map_err(Error::Write)?;
Ok(desc.writer.bytes_written() as u32)
}
fn process_queue(&mut self) -> NeedsInterrupt {
let mut needs_interrupt = NeedsInterrupt::No;
while let Some(mut avail_desc) = self.queue.pop() {
let len = match self.perform_work(&mut avail_desc) {
Ok(len) => len,
Err(err) => {
error!("{}", err);
0
}
};
self.queue.add_used(avail_desc, len);
needs_interrupt = NeedsInterrupt::Yes;
}
needs_interrupt
}
fn run(mut self, kill_evt: Event) -> anyhow::Result<()> {
#[derive(EventToken, Debug)]
enum Token {
// A request is ready on the queue.
QueueAvailable,
// Check if any interrupts need to be re-asserted.
InterruptResample,
// The parent thread requested an exit.
Kill,
}
let wait_ctx = WaitContext::build_with(&[
(self.queue.event(), Token::QueueAvailable),
(&kill_evt, Token::Kill),
])
.context("WaitContext::build_with")?;
if let Some(resample_evt) = self.interrupt.get_resample_evt() {
wait_ctx
.add(resample_evt, Token::InterruptResample)
.context("WaitContext::add")?;
}
loop {
let events = wait_ctx.wait().context("WaitContext::wait")?;
let mut needs_interrupt = NeedsInterrupt::No;
for event in events.iter().filter(|e| e.is_readable) {
match event.token {
Token::QueueAvailable => {
self.queue.event().wait().context("Event::wait")?;
needs_interrupt |= self.process_queue();
}
Token::InterruptResample => {
self.interrupt.interrupt_resample();
}
Token::Kill => return Ok(()),
}
}
if needs_interrupt == NeedsInterrupt::Yes {
self.queue.trigger_interrupt();
}
}
}
}
/// Virtio vTPM device.
pub struct Tpm {
backend: Option<Box<dyn TpmBackend>>,
worker_thread: Option<WorkerThread<()>>,
features: u64,
}
impl Tpm {
pub fn new(backend: Box<dyn TpmBackend>, base_features: u64) -> Tpm {
Tpm {
backend: Some(backend),
worker_thread: None,
features: base_features,
}
}
}
impl VirtioDevice for Tpm {
fn keep_rds(&self) -> Vec<RawDescriptor> {
Vec::new()
}
fn device_type(&self) -> DeviceType {
DeviceType::Tpm
}
fn queue_max_sizes(&self) -> &[u16] {
QUEUE_SIZES
}
fn features(&self) -> u64 {
self.features
}
fn activate(
&mut self,
_mem: GuestMemory,
interrupt: Interrupt,
mut queues: BTreeMap<usize, Queue>,
) -> anyhow::Result<()> {
if queues.len() != 1 {
return Err(anyhow!("expected 1 queue, got {}", queues.len()));
}
let queue = queues.pop_first().unwrap().1;
let backend = self.backend.take().context("no backend in vtpm")?;
let worker = Worker {
interrupt,
queue,
backend,
};
self.worker_thread = Some(WorkerThread::start("v_tpm", |kill_evt| {
if let Err(e) = worker.run(kill_evt) {
error!("virtio-tpm worker failed: {:#}", e);
}
}));
Ok(())
}
}
#[derive(PartialEq, Eq)]
enum NeedsInterrupt {
Yes,
No,
}
impl BitOrAssign for NeedsInterrupt {
fn bitor_assign(&mut self, rhs: NeedsInterrupt) {
if rhs == NeedsInterrupt::Yes {
*self = NeedsInterrupt::Yes;
}
}
}
type Result<T> = std::result::Result<T, Error>;
#[sorted]
#[derive(Error, Debug)]
enum Error {
#[error("vtpm response buffer is too small: {size} < {required} bytes")]
BufferTooSmall { size: usize, required: usize },
#[error("vtpm command is too long: {size} > {} bytes", TPM_BUFSIZE)]
CommandTooLong { size: usize },
#[error("vtpm failed to read from guest memory: {0}")]
Read(io::Error),
#[error(
"vtpm simulator generated a response that is unexpectedly long: {size} > {} bytes",
TPM_BUFSIZE
)]
ResponseTooLong { size: usize },
#[error("vtpm failed to write to guest memory: {0}")]
Write(io::Error),
}