From 4f522270d0188795fe0ac7d9409fcd3f007de992 Mon Sep 17 00:00:00 2001 From: Serban Iorga Date: Wed, 23 Jan 2019 15:16:19 +0200 Subject: [PATCH] implemented cpuid helper methods The helper methods are needed for adding AMD support https://github.com/firecracker-microvm/firecracker/issues/815 Signed-off-by: Serban Iorga --- cpuid/src/common.rs | 208 ++++++++++++++++++++++++++++++++++++++++++++ cpuid/src/lib.rs | 2 + kvm/src/lib.rs | 116 ++++++++++++++++++++++++ 3 files changed, 326 insertions(+) create mode 100644 cpuid/src/common.rs diff --git a/cpuid/src/common.rs b/cpuid/src/common.rs new file mode 100644 index 000000000000..167ebf32da7a --- /dev/null +++ b/cpuid/src/common.rs @@ -0,0 +1,208 @@ +// Copyright 2018 Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 + +#[cfg(target_arch = "x86")] +use std::arch::x86::CpuidResult; +#[cfg(target_arch = "x86_64")] +use std::arch::x86_64::CpuidResult; + +#[cfg(target_arch = "x86")] +use std::arch::x86::__get_cpuid_max; +#[cfg(target_arch = "x86_64")] +use std::arch::x86_64::__get_cpuid_max; + +#[cfg(target_arch = "x86")] +use std::arch::x86::__cpuid_count; +#[cfg(target_arch = "x86_64")] +use std::arch::x86_64::__cpuid_count; + +use kvm::CpuId; +use kvm_bindings::kvm_cpuid_entry2; + +const INTEL: &[u8; 12] = b"GenuineIntel"; +const AMD: &[u8; 12] = b"AuthenticAMD"; + +const EXT_FUNCTION: u32 = 0x80000000; + +pub enum Error { + InvalidParameters(String), + NotSupported, + SizeLimitExceeded, +} + +#[cfg(any(target_arch = "x86", target_arch = "x86_64"))] +fn get_cpuid(function: u32, count: u32) -> Result { + // For x86 the host supports the `cpuid` instruction if SSE is enabled. Otherwise it's hard to check. + // TODO: replace with validation based on `has_cpuid()` when it becomes stable: + // https://doc.rust-lang.org/core/arch/x86/fn.has_cpuid.html + #[cfg(target_arch = "x86")] + { + #[cfg(not(target_feature = "sse"))] + { + return Err(Error::NotSupported); + } + } + + // this is safe because the host supports the `cpuid` instruction + let max_function = unsafe { __get_cpuid_max(function & EXT_FUNCTION).0 }; + if function > max_function { + return Err(Error::InvalidParameters(format!( + "Function not supported: 0x{:x}", + function + ))); + } + + // this is safe because the host supports the `cpuid` instruction + let entry = unsafe { __cpuid_count(function, count) }; + if entry.eax == 0 && entry.ebx == 0 && entry.ecx == 0 && entry.edx == 0 { + return Err(Error::InvalidParameters(format!( + "Invalid count: {}", + count + ))); + } + + Ok(entry) +} + +/// Extracts the CPU vendor id from leaf 0x0. +/// +#[cfg(any(target_arch = "x86", target_arch = "x86_64"))] +pub fn get_vendor_id() -> Result<[u8; 12], Error> { + match get_cpuid(0, 0) { + Ok(vendor_entry) => { + let bytes: [u8; 12] = unsafe { + std::mem::transmute([vendor_entry.ebx, vendor_entry.edx, vendor_entry.ecx]) + }; + Ok(bytes) + } + Err(_e) => Err(Error::NotSupported), + } +} + +/// Replaces the `cpuid` entries corresponding to `function` with the entries from the host's cpuid. +/// +#[cfg(any(target_arch = "x86", target_arch = "x86_64"))] +pub fn emulate_host_cpuid_function(cpuid: &mut CpuId, function: u32) -> Result<(), Error> { + // copy all the CpuId entries, except for the ones with the provided function + let mut entries: Vec = Vec::new(); + for entry in cpuid.mut_entries_slice().iter() { + if entry.function != function { + entries.push(*entry); + } + } + + // add all the host leaves with the provided function + let mut count: u32 = 0; + while let Ok(entry) = get_cpuid(function, count) { + // check if there's enough space to add a new entry to the cpuid + if entries.len() == kvm::MAX_KVM_CPUID_ENTRIES { + return Err(Error::SizeLimitExceeded); + } + + entries.push(kvm_cpuid_entry2 { + function: function, + index: count, + flags: 0, + eax: entry.eax, + ebx: entry.ebx, + ecx: entry.ecx, + edx: entry.edx, + padding: [0, 0, 0], + }); + count += 1; + } + + cpuid.set_entries(&entries); + Ok(()) +} + +#[cfg(test)] +mod tests { + use common::*; + use kvm::CpuId; + + #[cfg(any(target_arch = "x86", target_arch = "x86_64"))] + fn get_topoext_fn() -> u32 { + let vendor_id = get_vendor_id(); + assert!(vendor_id.is_ok()); + let function = match &vendor_id.ok().unwrap() { + INTEL => 0x4, + AMD => 0x8000001d, + _ => 0, + }; + assert!(function != 0); + + function + } + + #[test] + #[cfg(any(target_arch = "x86", target_arch = "x86_64"))] + fn get_cpu_id_test() { + // get_cpu_id should work correctly here + let topoext_fn = get_topoext_fn(); + + // check that get_cpuid works for valid parameters + match get_cpuid(topoext_fn, 0) { + Ok(topoext_entry) => { + assert!(topoext_entry.eax != 0); + } + _ => panic!("Wrong behavior"), + } + + // check that get_cpuid returns correct error for invalid `function` + match get_cpuid(0x90000000, 0) { + Err(Error::InvalidParameters(s)) => { + assert!(s == "Function not supported: 0x90000000"); + } + _ => panic!("Wrong behavior"), + } + + // check that get_cpuid returns correct error for invalid `count` + match get_cpuid(topoext_fn, 100) { + Err(Error::InvalidParameters(s)) => { + assert!(s == "Invalid count: 100"); + } + _ => panic!("Wrong behavior"), + } + } + + #[test] + #[cfg(any(target_arch = "x86", target_arch = "x86_64"))] + fn get_vendor_id_test() { + let vendor_id = get_vendor_id(); + assert!(vendor_id.is_ok()); + assert!(match &vendor_id.ok().unwrap() { + INTEL => true, + AMD => true, + _ => false, + }); + } + + #[test] + #[cfg(any(target_arch = "x86", target_arch = "x86_64"))] + fn emulate_host_cpuid_function_test() { + // try to emulate the extended cache topology leaves + let topoext_fn = get_topoext_fn(); + + // check that it behaves correctly for TOPOEXT function + let mut cpuid = CpuId::new(1); + cpuid.mut_entries_slice()[0].function = topoext_fn; + emulate_host_cpuid_function(&mut cpuid, topoext_fn); + let entries = cpuid.mut_entries_slice(); + assert!(entries.len() > 1); + let mut count = 0; + for entry in entries.iter_mut() { + assert!(entry.function == topoext_fn); + assert!(entry.index == count); + assert!(entry.eax != 0); + count = count + 1; + } + + // check that it returns Err when there are too many entries + let mut cpuid = CpuId::new(kvm::MAX_KVM_CPUID_ENTRIES); + match emulate_host_cpuid_function(&mut cpuid, topoext_fn) { + Err(Error::SizeLimitExceeded) => {} + _ => panic!("Wrong behavior"), + } + } +} diff --git a/cpuid/src/lib.rs b/cpuid/src/lib.rs index bf4c44b6316e..c5f8e556c458 100644 --- a/cpuid/src/lib.rs +++ b/cpuid/src/lib.rs @@ -22,6 +22,8 @@ mod cpu_leaf; /// Follows a T2 template in setting up the CPUID. pub mod t2_template; +mod common; + use brand_string::BrandString; use brand_string::Reg as BsReg; use cpu_leaf::*; diff --git a/kvm/src/lib.rs b/kvm/src/lib.rs index 28687163bc5d..6bbd2bde976f 100644 --- a/kvm/src/lib.rs +++ b/kvm/src/lib.rs @@ -914,6 +914,30 @@ impl CpuId { } } + /// Set the CpuId entries based on a supplied vector. + /// + pub fn set_entries(&mut self, entries: &Vec) { + use std::mem::size_of; + + let vec_size_bytes = + size_of::() + (entries.len() * size_of::()); + self.bytes = vec![0; vec_size_bytes]; + self.allocated_len = entries.len(); + let kvm_cpuid: &mut kvm_cpuid2 = unsafe { + // We have ensured in new that there is enough space for the structure so this + // conversion is safe. + &mut *(self.bytes.as_ptr() as *mut kvm_cpuid2) + }; + kvm_cpuid.nent = entries.len() as u32; + + unsafe { + kvm_cpuid + .entries + .as_mut_slice(entries.len()) + .copy_from_slice(entries.as_slice()) + }; + } + /// Get a pointer so it can be passed to the kernel. Using this pointer is unsafe. /// pub fn as_ptr(&self) -> *const kvm_cpuid2 { @@ -933,7 +957,9 @@ mod tests { use super::*; + use kvm_cpuid_entry2; use memory_model::{GuestAddress, GuestMemory}; + use CpuId; // as per https://github.com/torvalds/linux/blob/master/arch/x86/include/asm/fpu/internal.h pub const KVM_FPU_CWD: usize = 0x37f; @@ -1482,4 +1508,94 @@ mod tests { let kvm = Kvm::new().unwrap(); assert_eq!(kvm.get_api_version(), KVM_API_VERSION as i32); } + + #[test] + fn test_cpu_id_new() { + let num_entries = 5; + let cpuid = CpuId::new(num_entries); + + // check that the cpuid contains `num_entries` empty entries + let expected_bytes_len = 208; + assert!(cpuid.bytes.len() == expected_bytes_len); + + let mut expected_bytes = vec![0; expected_bytes_len]; + expected_bytes[0] = num_entries as u8; + assert!(cpuid.bytes == expected_bytes); + } + + #[test] + fn test_cpu_id_mut_entries_slice() { + let num_entries = 5; + let expected_bytes_len = 208; + let mut cpuid = CpuId::new(num_entries); + + { + // check that the CpuId has been initialized correctly: + // there should be `num_entries` empty entries + assert!(cpuid.allocated_len == num_entries); + assert!(cpuid.bytes.len() == expected_bytes_len); + let entries = cpuid.mut_entries_slice(); + assert!(entries.len() == num_entries); + for entry in entries.iter() { + assert!(entry.function == 0); + assert!(entry.index == 0); + assert!(entry.flags == 0); + assert!(entry.eax == 0); + assert!(entry.ebx == 0); + assert!(entry.ecx == 0); + assert!(entry.edx == 0); + assert!(entry.padding == [0, 0, 0]); + } + + // modify the first entry + entries[0].function = 0x4; + entries[0].index = 0; + entries[0].flags = 1; + entries[0].eax = 0b1100000; + } + + // check if the bytes are as expected + let mut expected_bytes: Vec = vec![0; expected_bytes_len]; + expected_bytes[0] = num_entries as u8; + expected_bytes[8] = 4; + expected_bytes[16] = 1; + expected_bytes[20] = 96; + assert!(cpuid.bytes == expected_bytes); + } + + #[test] + fn test_cpu_id_set_entries() { + let num_entries = 4; + let mut cpuid = CpuId::new(num_entries); + + // add entry + let mut entries = cpuid.mut_entries_slice().to_vec(); + entries.insert( + 0, + kvm_cpuid_entry2 { + function: 0x4, + index: 0, + flags: 1, + eax: 0b1100000, + ebx: 0, + ecx: 0, + edx: 0, + padding: [0, 0, 0], + }, + ); + cpuid.set_entries(&entries); + + // check that the cpuid contains the new entry + let expected_bytes_len = 208; + assert!(cpuid.bytes.len() == expected_bytes_len); + assert!(cpuid.allocated_len == num_entries + 1); + assert!(cpuid.mut_entries_slice().len() == num_entries + 1); + + let mut expected_bytes = vec![0; expected_bytes_len]; + expected_bytes[0] = 5; + expected_bytes[8] = 4; + expected_bytes[16] = 1; + expected_bytes[20] = 96; + assert!(cpuid.bytes == expected_bytes); + } }