diff --git a/Cargo.lock b/Cargo.lock index 6da228b8b..2dd5bb8fc 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -243,6 +243,9 @@ dependencies = [ "hypervisor", "libc", "log", + "phf", + "thiserror", + "tpm", "versionize", "versionize_derive", "vm-device", @@ -758,6 +761,48 @@ dependencies = [ "wait-timeout", ] +[[package]] +name = "phf" +version = "0.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "928c6535de93548188ef63bb7c4036bd415cd8f36ad25af44b9789b2ee72a48c" +dependencies = [ + "phf_macros", + "phf_shared", +] + +[[package]] +name = "phf_generator" +version = "0.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b1181c94580fa345f50f19d738aaa39c0ed30a600d95cb2d3e23f94266f14fbf" +dependencies = [ + "phf_shared", + "rand", +] + +[[package]] +name = "phf_macros" +version = "0.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "92aacdc5f16768709a569e913f7451034034178b05bdc8acda226659a3dccc66" +dependencies = [ + "phf_generator", + "phf_shared", + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "phf_shared" +version = "0.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e1fb5f6f826b772a8d4c0394209441e7d37cbbb967ae9c7e0e8134365c9ee676" +dependencies = [ + "siphasher", +] + [[package]] name = "pkg-config" version = "0.3.26" @@ -882,6 +927,21 @@ dependencies = [ "proc-macro2", ] +[[package]] +name = "rand" +version = "0.8.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404" +dependencies = [ + "rand_core", +] + +[[package]] +name = "rand_core" +version = "0.6.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" + [[package]] name = "rate_limiter" version = "0.1.0" @@ -1065,6 +1125,12 @@ dependencies = [ "libc", ] +[[package]] +name = "siphasher" +version = "0.3.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7bd3e3206899af3f8b12af284fafc038cc1dc2b41d1b89dd17297221c5d225de" + [[package]] name = "smallvec" version = "1.10.0" diff --git a/devices/Cargo.toml b/devices/Cargo.toml index f30dc8e26..3f7a7cd9a 100644 --- a/devices/Cargo.toml +++ b/devices/Cargo.toml @@ -13,6 +13,9 @@ byteorder = "1.4.3" hypervisor = { path = "../hypervisor" } libc = "0.2.137" log = "0.4.17" +phf = { version = "0.11", features = ["macros"] } +thiserror = "1.0.30" +tpm = { path = "../tpm" } versionize = "0.1.6" versionize_derive = "0.1.4" vm-device = { path = "../vm-device" } diff --git a/devices/src/lib.rs b/devices/src/lib.rs index c265b0262..cd8291801 100644 --- a/devices/src/lib.rs +++ b/devices/src/lib.rs @@ -19,6 +19,7 @@ pub mod interrupt_controller; #[cfg(target_arch = "x86_64")] pub mod ioapic; pub mod legacy; +pub mod tpm; pub use self::acpi::{AcpiGedDevice, AcpiPmTimerDevice, AcpiShutdownDevice}; diff --git a/devices/src/tpm.rs b/devices/src/tpm.rs new file mode 100644 index 000000000..e38700358 --- /dev/null +++ b/devices/src/tpm.rs @@ -0,0 +1,470 @@ +// Copyright © 2022, Microsoft Corporation +// +// SPDX-License-Identifier: Apache-2.0 +// + +use anyhow::anyhow; +#[cfg(target_arch = "aarch64")] +use arch::aarch64::layout::{TPM_SIZE, TPM_START}; +#[cfg(target_arch = "x86_64")] +use arch::x86_64::layout::{TPM_SIZE, TPM_START}; +use phf::phf_map; +use std::cmp; +use std::sync::{Arc, Barrier}; +use thiserror::Error; +use tpm::emulator::{BackendCmd, Emulator}; +use tpm::TPM_CRB_BUFFER_MAX; +use tpm::TPM_SUCCESS; +use vm_device::BusDevice; + +#[derive(Error, Debug)] +pub enum Error { + #[error("Emulator doesn't implement min required capabilities: {0}")] + CheckCaps(#[source] anyhow::Error), + #[error("Failed to initialize tpm: {0}")] + Init(#[source] anyhow::Error), + #[error("Failed to deliver tpm Command: {0}")] + DeliverRequest(#[source] anyhow::Error), +} +type Result = anyhow::Result; + +/* crb 32-bit registers */ +const CRB_LOC_STATE: u32 = 0x0; +//Register Fields +// Field => (start, length) +// start: lowest bit in the bit field numbered from 0 +// length: length of the bit field +const CRB_LOC_STATE_FIELDS: phf::Map<&str, [u32; 2]> = phf_map! { + "tpmEstablished" => [0, 1], + "locAssigned" => [1,1], + "activeLocality"=> [2, 3], + "reserved" => [5, 2], + "tpmRegValidSts" => [7, 1] +}; +const CRB_LOC_CTRL: u32 = 0x08; +const CRB_LOC_CTRL_REQUEST_ACCESS: u32 = 1 << 0; +const CRB_LOC_CTRL_RELINQUISH: u32 = 1 << 1; +const CRB_LOC_CTRL_RESET_ESTABLISHMENT_BIT: u32 = 1 << 3; +const CRB_LOC_STS: u32 = 0x0C; +const CRB_LOC_STS_FIELDS: phf::Map<&str, [u32; 2]> = phf_map! { + "Granted" => [0, 1], + "beenSeized" => [1,1] +}; +const CRB_INTF_ID: u32 = 0x30; +const CRB_INTF_ID_FIELDS: phf::Map<&str, [u32; 2]> = phf_map! { + "InterfaceType" => [0, 4], + "InterfaceVersion" => [4, 4], + "CapLocality" => [8, 1], + "CapCRBIdleBypass" => [9, 1], + "Reserved1" => [10, 1], + "CapDataXferSizeSupport" => [11, 2], + "CapFIFO" => [13, 1], + "CapCRB" => [14, 1], + "CapIFRes" => [15, 2], + "InterfaceSelector" => [17, 2], + "IntfSelLock" => [19, 1], + "Reserved2" => [20, 4], + "RID" => [24, 8] +}; +const CRB_INTF_ID2: u32 = 0x34; +const CRB_INTF_ID2_FIELDS: phf::Map<&str, [u32; 2]> = phf_map! { + "VID" => [0, 16], + "DID" => [16, 16] +}; +const CRB_CTRL_REQ: u32 = 0x40; +const CRB_CTRL_REQ_CMD_READY: u32 = 1 << 0; +const CRB_CTRL_REQ_GO_IDLE: u32 = 1 << 1; +const CRB_CTRL_STS: u32 = 0x44; +const CRB_CTRL_STS_FIELDS: phf::Map<&str, [u32; 2]> = phf_map! { + "tpmSts" => [0, 1], + "tpmIdle" => [1, 1] +}; +const CRB_CTRL_CANCEL: u32 = 0x48; +const CRB_CANCEL_INVOKE: u32 = 1 << 0; +const CRB_CTRL_START: u32 = 0x4C; +const CRB_START_INVOKE: u32 = 1 << 0; +const CRB_CTRL_CMD_LADDR: u32 = 0x5C; +const CRB_CTRL_CMD_HADDR: u32 = 0x60; +const CRB_CTRL_RSP_SIZE: u32 = 0x64; +const CRB_CTRL_RSP_ADDR: u32 = 0x68; +const CRB_DATA_BUFFER: u32 = 0x80; + +const TPM_CRB_NO_LOCALITY: u32 = 0xff; + +const TPM_CRB_ADDR_BASE: u32 = TPM_START.0 as u32; +const TPM_CRB_ADDR_SIZE: usize = TPM_SIZE as usize; + +const TPM_CRB_R_MAX: u32 = CRB_DATA_BUFFER; + +// CRB Protocol details +const CRB_INTF_TYPE_CRB_ACTIVE: u32 = 0b1; +const CRB_INTF_VERSION_CRB: u32 = 0b1; +const CRB_INTF_CAP_LOCALITY_0_ONLY: u32 = 0b0; +const CRB_INTF_CAP_IDLE_FAST: u32 = 0b0; +const CRB_INTF_CAP_XFER_SIZE_64: u32 = 0b11; +const CRB_INTF_CAP_FIFO_NOT_SUPPORTED: u32 = 0b0; +const CRB_INTF_CAP_CRB_SUPPORTED: u32 = 0b1; +const CRB_INTF_IF_SELECTOR_CRB: u32 = 0b1; +const PCI_VENDOR_ID_IBM: u32 = 0x1014; +const CRB_CTRL_CMD_SIZE_REG: u32 = 0x58; +const CRB_CTRL_CMD_SIZE: usize = TPM_CRB_ADDR_SIZE - CRB_DATA_BUFFER as usize; + +fn get_fields_map(reg: u32) -> phf::Map<&'static str, [u32; 2]> { + match reg { + CRB_LOC_STATE => CRB_LOC_STATE_FIELDS, + CRB_LOC_STS => CRB_LOC_STS_FIELDS, + CRB_INTF_ID => CRB_INTF_ID_FIELDS, + CRB_INTF_ID2 => CRB_INTF_ID2_FIELDS, + CRB_CTRL_STS => CRB_CTRL_STS_FIELDS, + _ => { + panic!( + "Fields in '{:?}' register were accessed which are Invalid", + reg + ); + } + } +} + +/// Set a particular field in a Register +fn set_reg_field(regs: &mut [u32; TPM_CRB_R_MAX as usize], reg: u32, field: &str, value: u32) { + let reg_fields = get_fields_map(reg); + if reg_fields.contains_key(field) { + let start = reg_fields.get(field).unwrap()[0]; + let len = reg_fields.get(field).unwrap()[1]; + let mask = (!(0_u32) >> (32 - len)) << start; + regs[reg as usize] = (regs[reg as usize] & !mask) | ((value << start) & mask); + } else { + error!( + "Failed to tpm Register. {:?} is not a valid field in Reg {:#X}", + field, reg + ) + } +} + +/// Get the value of a particular field in a Register +fn get_reg_field(regs: &[u32; TPM_CRB_R_MAX as usize], reg: u32, field: &str) -> u32 { + let reg_fields = get_fields_map(reg); + if reg_fields.contains_key(field) { + let start = reg_fields.get(field).unwrap()[0]; + let len = reg_fields.get(field).unwrap()[1]; + let mask = (!(0_u32) >> (32 - len)) << start; + (regs[reg as usize] & mask) >> start + } else { + // TODO: Sensible return value if fields do not exist + 0x0 + } +} + +fn locality_from_addr(addr: u32) -> u8 { + (addr >> 12) as u8 +} + +pub struct Tpm { + emulator: Emulator, + cmd: Option, + regs: [u32; TPM_CRB_R_MAX as usize], + backend_buff_size: usize, + data_buff: [u8; TPM_CRB_BUFFER_MAX], + data_buff_len: usize, +} + +impl Tpm { + pub fn new(path: String) -> Result { + let emulator = Emulator::new(path) + .map_err(|e| Error::Init(anyhow!("Failed while initializing tpm Emulator: {:?}", e)))?; + let mut tpm = Tpm { + emulator, + cmd: None, + regs: [0; TPM_CRB_R_MAX as usize], + backend_buff_size: TPM_CRB_BUFFER_MAX, + data_buff: [0; TPM_CRB_BUFFER_MAX], + data_buff_len: 0, + }; + tpm.reset()?; + Ok(tpm) + } + + fn get_active_locality(&mut self) -> u32 { + if get_reg_field(&self.regs, CRB_LOC_STATE, "locAssigned") == 0 { + return TPM_CRB_NO_LOCALITY; + } + get_reg_field(&self.regs, CRB_LOC_STATE, "activeLocality") + } + + fn request_completed(&mut self, result: isize) { + self.regs[CRB_CTRL_START as usize] = !CRB_START_INVOKE; + if result != 0 { + set_reg_field(&mut self.regs, CRB_CTRL_STS, "tpmSts", 1); + } + } + + fn reset(&mut self) -> Result<()> { + let cur_buff_size = self.emulator.get_buffer_size().unwrap(); + self.regs = [0; TPM_CRB_R_MAX as usize]; + set_reg_field(&mut self.regs, CRB_LOC_STATE, "tpmRegValidSts", 1); + set_reg_field(&mut self.regs, CRB_CTRL_STS, "tpmIdle", 1); + set_reg_field( + &mut self.regs, + CRB_INTF_ID, + "InterfaceType", + CRB_INTF_TYPE_CRB_ACTIVE, + ); + set_reg_field( + &mut self.regs, + CRB_INTF_ID, + "InterfaceVersion", + CRB_INTF_VERSION_CRB, + ); + set_reg_field( + &mut self.regs, + CRB_INTF_ID, + "CapLocality", + CRB_INTF_CAP_LOCALITY_0_ONLY, + ); + set_reg_field( + &mut self.regs, + CRB_INTF_ID, + "CapCRBIdleBypass", + CRB_INTF_CAP_IDLE_FAST, + ); + set_reg_field( + &mut self.regs, + CRB_INTF_ID, + "CapDataXferSizeSupport", + CRB_INTF_CAP_XFER_SIZE_64, + ); + set_reg_field( + &mut self.regs, + CRB_INTF_ID, + "CapFIFO", + CRB_INTF_CAP_FIFO_NOT_SUPPORTED, + ); + set_reg_field( + &mut self.regs, + CRB_INTF_ID, + "CapCRB", + CRB_INTF_CAP_CRB_SUPPORTED, + ); + set_reg_field( + &mut self.regs, + CRB_INTF_ID, + "InterfaceSelector", + CRB_INTF_IF_SELECTOR_CRB, + ); + set_reg_field(&mut self.regs, CRB_INTF_ID, "RID", 0b0000); + set_reg_field(&mut self.regs, CRB_INTF_ID2, "VID", PCI_VENDOR_ID_IBM); + + self.regs[CRB_CTRL_CMD_SIZE_REG as usize] = CRB_CTRL_CMD_SIZE as u32; + self.regs[CRB_CTRL_CMD_LADDR as usize] = TPM_CRB_ADDR_BASE + CRB_DATA_BUFFER; + self.regs[CRB_CTRL_RSP_SIZE as usize] = CRB_CTRL_CMD_SIZE as u32; + self.regs[CRB_CTRL_RSP_ADDR as usize] = TPM_CRB_ADDR_BASE + CRB_DATA_BUFFER; + + self.backend_buff_size = cmp::min(cur_buff_size, TPM_CRB_BUFFER_MAX); + + if let Err(e) = self.emulator.startup_tpm(self.backend_buff_size) { + return Err(Error::Init(anyhow!( + "Failed while running Startup TPM. Error: {:?}", + e + ))); + } + Ok(()) + } +} + +//impl BusDevice for TPM +impl BusDevice for Tpm { + fn read(&mut self, _base: u64, offset: u64, data: &mut [u8]) { + let mut offset: u32 = offset as u32; + let read_len: usize = data.len(); + + if offset >= CRB_DATA_BUFFER + && (offset + read_len as u32) < (CRB_DATA_BUFFER + self.data_buff.len() as u32) + { + // Read from Data Buffer + let start: usize = (offset as usize) - (CRB_DATA_BUFFER as usize); + let end: usize = start + read_len; + data[..].clone_from_slice(&self.data_buff[start..end]); + } else { + offset &= 0xff; + let mut val = self.regs[offset as usize]; + + if offset == CRB_LOC_STATE && !self.emulator.get_established_flag() { + val |= 0x1; + } + + if data.len() <= 4 { + data.clone_from_slice(val.to_ne_bytes()[0..read_len].as_ref()); + } else { + error!( + "Invalid tpm read: offset {:#X}, data length {:?}", + offset, + data.len() + ); + } + } + debug!( + "MMIO Read: offset {:#X} len {:?} val = {:02X?} ", + offset, + data.len(), + data + ); + } + + fn write(&mut self, _base: u64, offset: u64, data: &[u8]) -> Option> { + debug!( + "MMIO Write: offset {:#X} len {:?} input data {:02X?}", + offset, + data.len(), + data + ); + let mut offset: u32 = offset as u32; + if offset < CRB_DATA_BUFFER { + offset &= 0xff; + } + let locality = locality_from_addr(offset) as u32; + let write_len = data.len(); + + if offset >= CRB_DATA_BUFFER + && (offset + write_len as u32) < (CRB_DATA_BUFFER + self.data_buff.len() as u32) + { + let start: usize = (offset as usize) - (CRB_DATA_BUFFER as usize); + if start == 0 { + // If filling data_buff at index 0, reset length to 0 + self.data_buff_len = 0; + self.data_buff.fill(0); + } + let end: usize = start + data.len(); + self.data_buff[start..end].clone_from_slice(data); + self.data_buff_len += data.len(); + } else { + // Ctrl Commands that take more than 4 bytes as input are not yet supported + // CTRL_RSP_ADDR usually gets 8 byte write request. Last 4 bytes are zeros. + if write_len > 4 && offset != CRB_CTRL_RSP_ADDR { + error!( + "Invalid tpm write: offset {:#X}, data length {}", + offset, + data.len() + ); + return None; + } + + let mut input: [u8; 4] = [0; 4]; + input.copy_from_slice(&data[0..4]); + let v = u32::from_le_bytes(input); + + match offset { + CRB_CTRL_CMD_SIZE_REG => { + self.regs[CRB_CTRL_CMD_SIZE_REG as usize] = v; + } + CRB_CTRL_CMD_LADDR => { + self.regs[CRB_CTRL_CMD_LADDR as usize] = v; + } + CRB_CTRL_CMD_HADDR => { + self.regs[CRB_CTRL_CMD_HADDR as usize] = v; + } + CRB_CTRL_RSP_SIZE => { + self.regs[CRB_CTRL_RSP_SIZE as usize] = v; + } + CRB_CTRL_RSP_ADDR => { + self.regs[CRB_CTRL_RSP_ADDR as usize] = v; + } + CRB_CTRL_REQ => match v { + CRB_CTRL_REQ_CMD_READY => { + set_reg_field(&mut self.regs, CRB_CTRL_STS, "tpmIdle", 0); + } + CRB_CTRL_REQ_GO_IDLE => { + set_reg_field(&mut self.regs, CRB_CTRL_STS, "tpmIdle", 1); + } + _ => { + error!("Invalid value passed to CRTL_REQ register"); + return None; + } + }, + CRB_CTRL_CANCEL => { + if v == CRB_CANCEL_INVOKE + && (self.regs[CRB_CTRL_START as usize] & CRB_START_INVOKE != 0) + { + if let Err(e) = self.emulator.cancel_cmd() { + error!("Failed to run cancel command. Error: {:?}", e); + } + } + } + CRB_CTRL_START => { + if v == CRB_START_INVOKE + && ((self.regs[CRB_CTRL_START as usize] & CRB_START_INVOKE) == 0) + && self.get_active_locality() == locality + { + self.regs[CRB_CTRL_START as usize] |= CRB_START_INVOKE; + + self.cmd = Some(BackendCmd { + locality: locality as u8, + input: self.data_buff[0..self.data_buff_len].to_vec(), + input_len: cmp::min(self.data_buff_len, TPM_CRB_BUFFER_MAX), + output: self.data_buff.to_vec(), + output_len: TPM_CRB_BUFFER_MAX, + selftest_done: false, + }); + + let mut cmd = self.cmd.as_ref().unwrap().clone(); + let output = self.emulator.deliver_request(&mut cmd).map_err(|e| { + Error::DeliverRequest(anyhow!( + "Failed to deliver tpm request. Error :{:?}", + e + )) + }); + //TODO: drop the copy here + self.data_buff.fill(0); + self.data_buff.clone_from_slice(output.unwrap().as_slice()); + + self.request_completed(TPM_SUCCESS as isize); + } + } + CRB_LOC_CTRL => { + warn!( + "CRB_LOC_CTRL locality to write = {:?} val = {:?}", + locality, v + ); + match v { + CRB_LOC_CTRL_RESET_ESTABLISHMENT_BIT => {} + CRB_LOC_CTRL_RELINQUISH => { + set_reg_field(&mut self.regs, CRB_LOC_STATE, "locAssigned", 0); + set_reg_field(&mut self.regs, CRB_LOC_STS, "Granted", 0); + } + CRB_LOC_CTRL_REQUEST_ACCESS => { + set_reg_field(&mut self.regs, CRB_LOC_STS, "Granted", 1); + set_reg_field(&mut self.regs, CRB_LOC_STS, "beenSeized", 0); + set_reg_field(&mut self.regs, CRB_LOC_STATE, "locAssigned", 1); + } + _ => { + error!("Invalid value to write in CRB_LOC_CTRL {:#X} ", v); + } + } + } + _ => { + error!( + "Invalid tpm write: offset {:#X}, data length {:?}", + offset, + data.len() + ); + } + } + } + None + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_set_get_reg_field() { + let mut regs: [u32; TPM_CRB_R_MAX as usize] = [0; TPM_CRB_R_MAX as usize]; + set_reg_field(&mut regs, CRB_INTF_ID, "RID", 0xAC); + assert_eq!( + get_reg_field(®s, CRB_INTF_ID, "RID"), + 0xAC, + concat!("Test: ", stringify!(set_get_reg_field)) + ); + } +}