// 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, } 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 { 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>, worker_thread: Option>, features: u64, } impl Tpm { pub fn new(backend: Box, base_features: u64) -> Tpm { Tpm { backend: Some(backend), worker_thread: None, features: base_features, } } } impl VirtioDevice for Tpm { fn keep_rds(&self) -> Vec { 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, ) -> 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 = std::result::Result; #[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), }