summary refs log blame commit diff
path: root/x86_64/src/cpuid.rs
blob: a42b6622da73af3735ab1b8414511ae9ec43f68c (plain) (tree)
1
2
3
4
5
6
7
8
9
10



                                                                         

                              



             






                                              
                                   


                                                          





                                                                                           


     


                                                                  







                       

 





                                                                               
                                                                                


                                                                                                





                               

                                                
                          
                              




                                                           


                                                                    

                                                                        




                                                                           









                                                   

                        







                                                   


                                         




















                                                                                                   

                              
                                                 
 
                                                       

                               
                                               

 


































                                          






                                           
                                           
 




                                                
                                                                 










                                                                            

     
// Copyright 2017 The Chromium OS Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

use std::fmt::{self, Display};
use std::result;

use kvm;
use sys_util;

#[derive(Debug, PartialEq)]
pub enum Error {
    GetSupportedCpusFailed(sys_util::Error),
    SetSupportedCpusFailed(sys_util::Error),
}
pub type Result<T> = result::Result<T, Error>;

impl std::error::Error for Error {}

impl Display for Error {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        use self::Error::*;

        match self {
            GetSupportedCpusFailed(e) => write!(f, "GetSupportedCpus ioctl failed: {}", e),
            SetSupportedCpusFailed(e) => write!(f, "SetSupportedCpus ioctl failed: {}", e),
        }
    }
}

// This function is implemented in C because stable rustc does not
// support inline assembly.
extern "C" {
    fn host_cpuid(
        func: u32,
        func2: u32,
        rEax: *mut u32,
        rEbx: *mut u32,
        rEcx: *mut u32,
        rEdx: *mut u32,
    ) -> ();
}

// CPUID bits in ebx, ecx, and edx.
const EBX_CLFLUSH_CACHELINE: u32 = 8; // Flush a cache line size.
const EBX_CLFLUSH_SIZE_SHIFT: u32 = 8; // Bytes flushed when executing CLFLUSH.
const EBX_CPU_COUNT_SHIFT: u32 = 16; // Index of this CPU.
const EBX_CPUID_SHIFT: u32 = 24; // Index of this CPU.
const ECX_EPB_SHIFT: u32 = 3; // "Energy Performance Bias" bit.
const ECX_TSC_DEADLINE_TIMER_SHIFT: u32 = 24; // TSC deadline mode of APIC timer
const ECX_HYPERVISOR_SHIFT: u32 = 31; // Flag to be set when the cpu is running on a hypervisor.
const EDX_HTT_SHIFT: u32 = 28; // Hyper Threading Enabled.

fn filter_cpuid(
    cpu_id: u64,
    cpu_count: u64,
    kvm_cpuid: &mut kvm::CpuId,
    kvm: &kvm::Kvm,
) -> Result<()> {
    let entries = kvm_cpuid.mut_entries_slice();

    for entry in entries {
        match entry.function {
            1 => {
                // X86 hypervisor feature
                if entry.index == 0 {
                    entry.ecx |= 1 << ECX_HYPERVISOR_SHIFT;
                }
                if kvm.check_extension(kvm::Cap::TscDeadlineTimer) {
                    entry.ecx |= 1 << ECX_TSC_DEADLINE_TIMER_SHIFT;
                }
                entry.ebx = (cpu_id << EBX_CPUID_SHIFT) as u32
                    | (EBX_CLFLUSH_CACHELINE << EBX_CLFLUSH_SIZE_SHIFT);
                if cpu_count > 1 {
                    entry.ebx |= (cpu_count as u32) << EBX_CPU_COUNT_SHIFT;
                    entry.edx |= 1 << EDX_HTT_SHIFT;
                }
            }
            2 | 0x80000005 | 0x80000006 => unsafe {
                host_cpuid(
                    entry.function,
                    0,
                    &mut entry.eax as *mut u32,
                    &mut entry.ebx as *mut u32,
                    &mut entry.ecx as *mut u32,
                    &mut entry.edx as *mut u32,
                );
            },
            4 => {
                unsafe {
                    host_cpuid(
                        entry.function,
                        entry.index,
                        &mut entry.eax as *mut u32,
                        &mut entry.ebx as *mut u32,
                        &mut entry.ecx as *mut u32,
                        &mut entry.edx as *mut u32,
                    );
                }
                entry.eax &= !0xFC000000;
            }
            6 => {
                // Clear X86 EPB feature.  No frequency selection in the hypervisor.
                entry.ecx &= !(1 << ECX_EPB_SHIFT);
            }
            _ => (),
        }
    }

    Ok(())
}

/// Sets up the cpuid entries for the given vcpu.  Can fail if there are too many CPUs specified or
/// if an ioctl returns an error.
///
/// # Arguments
///
/// * `kvm` - `Kvm` structure created with KVM_CREATE_VM ioctl.
/// * `vcpu` - `Vcpu` for setting CPU ID.
/// * `cpu_id` - The index of the CPU `vcpu` is for.
/// * `nrcpus` - The number of vcpus being used by this VM.
pub fn setup_cpuid(kvm: &kvm::Kvm, vcpu: &kvm::Vcpu, cpu_id: u64, nrcpus: u64) -> Result<()> {
    let mut kvm_cpuid = kvm
        .get_supported_cpuid()
        .map_err(Error::GetSupportedCpusFailed)?;

    filter_cpuid(cpu_id, nrcpus, &mut kvm_cpuid, kvm)?;

    vcpu.set_cpuid2(&kvm_cpuid)
        .map_err(Error::SetSupportedCpusFailed)
}

/// get host cpu max physical address bits
pub fn phy_max_address_bits() -> u32 {
    let mut eax: u32 = 0;
    let mut ebx: u32 = 0;
    let mut ecx: u32 = 0;
    let mut edx: u32 = 0;
    let mut phys_bits: u32 = 36;

    unsafe {
        host_cpuid(
            0x80000000,
            0,
            &mut eax as *mut u32,
            &mut ebx as *mut u32,
            &mut ecx as *mut u32,
            &mut edx as *mut u32,
        );
    }
    if eax >= 0x80000008 {
        unsafe {
            host_cpuid(
                0x80000008,
                0,
                &mut eax as *mut u32,
                &mut ebx as *mut u32,
                &mut ecx as *mut u32,
                &mut edx as *mut u32,
            );
        }
        phys_bits = eax & 0xff;
    }

    phys_bits
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn feature_and_vendor_name() {
        let mut cpuid = kvm::CpuId::new(2);
        let kvm = kvm::Kvm::new().unwrap();

        let entries = cpuid.mut_entries_slice();
        entries[0].function = 0;
        entries[1].function = 1;
        entries[1].ecx = 0x10;
        entries[1].edx = 0;
        assert_eq!(Ok(()), filter_cpuid(1, 2, &mut cpuid, &kvm));

        let entries = cpuid.mut_entries_slice();
        assert_eq!(entries[0].function, 0);
        assert_eq!(1, (entries[1].ebx >> EBX_CPUID_SHIFT) & 0x000000ff);
        assert_eq!(2, (entries[1].ebx >> EBX_CPU_COUNT_SHIFT) & 0x000000ff);
        assert_eq!(
            EBX_CLFLUSH_CACHELINE,
            (entries[1].ebx >> EBX_CLFLUSH_SIZE_SHIFT) & 0x000000ff
        );
        assert_ne!(0, entries[1].ecx & (1 << ECX_HYPERVISOR_SHIFT));
        assert_ne!(0, entries[1].edx & (1 << EDX_HTT_SHIFT));
    }
}