Skip to content

Commit

Permalink
implemented cpuid helper methods
Browse files Browse the repository at this point in the history
The helper methods are needed for adding AMD support

firecracker-microvm#815

Signed-off-by: Serban Iorga <seriorga@amazon.com>
  • Loading branch information
Serban Iorga committed Feb 1, 2019
1 parent dad541e commit 4f52227
Show file tree
Hide file tree
Showing 3 changed files with 326 additions and 0 deletions.
208 changes: 208 additions & 0 deletions cpuid/src/common.rs
Original file line number Diff line number Diff line change
@@ -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<CpuidResult, Error> {
// 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<kvm_cpuid_entry2> = 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"),
}
}
}
2 changes: 2 additions & 0 deletions cpuid/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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::*;
Expand Down
116 changes: 116 additions & 0 deletions kvm/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -914,6 +914,30 @@ impl CpuId {
}
}

/// Set the CpuId entries based on a supplied vector.
///
pub fn set_entries(&mut self, entries: &Vec<kvm_cpuid_entry2>) {
use std::mem::size_of;

let vec_size_bytes =
size_of::<kvm_cpuid2>() + (entries.len() * size_of::<kvm_cpuid_entry2>());
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 {
Expand All @@ -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;
Expand Down Expand Up @@ -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<u8> = 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);
}
}

0 comments on commit 4f52227

Please sign in to comment.