From 1b7d256037956db030b7ff6738d4636337d1c1dc Mon Sep 17 00:00:00 2001 From: Joshua Job Date: Thu, 2 May 2024 14:55:46 -0700 Subject: [PATCH 1/3] MSRs: Move to generic MSR access ahead of KVM MSR implementation --- src/main.rs | 52 +++++++++++++++---------- src/msr.rs | 109 ++++++++++++++++++++++++++++------------------------ 2 files changed, 89 insertions(+), 72 deletions(-) diff --git a/src/main.rs b/src/main.rs index 3ca8f70..d8ac6e4 100644 --- a/src/main.rs +++ b/src/main.rs @@ -4,13 +4,12 @@ use cpuinfo::facts::{FactSet, Facter, GenericFact}; use cpuinfo::layout::LeafDesc; -use cpuinfo::msr::MSRValue; +use cpuinfo::msr::MsrStore; use cpuinfo::*; use enum_dispatch::enum_dispatch; use msr::MSRDesc; use serde::{Deserialize, Serialize}; use std::collections::BTreeMap; -use std::convert::TryFrom; use std::error::Error; use std::fmt; use structopt::StructOpt; @@ -77,12 +76,19 @@ impl Command for Disp { } } - if MSRDesc::is_available() && !self.skip_msr { - println!("MSRS:"); - for msr in &config.msrs { - match msr.into_value() { - Ok(value) => println!("{}", value), - Err(err) => println!("{} Error : {}", msr, err), + if !self.skip_msr { + if msr::LINUX_MSR_AVAILIBLE { + match msr::LinuxMsrStore::new() { + Ok(linux_store) => { + println!("MSRS:"); + for msr in &config.msrs { + match linux_store.get_value(msr) { + Ok(value) => println!("{}", value), + Err(err) => println!("{} Error : {}", msr, err), + } + } + } + Err(e) => println!("Error checking all msrs: {}", e), } } } @@ -101,6 +107,7 @@ struct Facts { fn collect_facts( config: &Definition, cpuid_selected: CpuidType, + msr_store: Box, ) -> Result, Box> { let mut ret: Vec = config .cpuids @@ -113,15 +120,9 @@ fn collect_facts( }) .collect(); - let use_kvm = match cpuid_selected { - CpuidType::Func(_) => false, - #[cfg(all(target_os = "linux", feature = "kvm"))] - CpuidType::KvmInfo(_) => true, - }; - - if MSRDesc::is_available() && !use_kvm { + if msr_store.is_empty() { for msr in &config.msrs { - if let Ok(value) = MSRValue::try_from(msr) { + if let Ok(value) = msr_store.get_value(msr) { let mut facts = value.collect_facts(); for fact in &mut facts { fact.add_path("msr"); @@ -149,23 +150,32 @@ impl Command for Facts { } }; - let cpuid_source = { + let (cpuid_source, msr_source): (_, Box) = { #[cfg(all(target_os = "linux", feature = "kvm"))] { if let Some(kvm_info) = kvm_option { - kvm_info.into() + ( + kvm_info.into(), + Box::new(msr::EmptyMSR {}) as Box, + ) } else { - CpuidType::func() + ( + CpuidType::func(), + Box::new(msr::LinuxMsrStore::new()?) as Box, + ) } } #[cfg(any(not(target_os = "linux"), not(feature = "kvm")))] { - CpuidType::func() + ( + CpuidType::func(), + Box::new(msr::LinuxMsrStore::new()?) as Box, + ) } }; println!( "{}", - serde_yaml::to_string(&collect_facts(config, cpuid_source)?)? + serde_yaml::to_string(&collect_facts(config, cpuid_source, msr_source)?)? ); Ok(()) } diff --git a/src/msr.rs b/src/msr.rs index 6e7ce75..a3c8f8e 100644 --- a/src/msr.rs +++ b/src/msr.rs @@ -4,11 +4,9 @@ use super::bitfield::{self, Facter}; use super::facts::{self, GenericFact}; use serde::{Deserialize, Serialize}; +use std::fs; use std::vec::Vec; -use std::{ - convert::{self, TryInto}, - error, fmt, io, -}; +use std::{convert, error, fmt, io}; #[derive(Debug)] pub enum Error { @@ -39,54 +37,73 @@ impl convert::From for Error { } } -/// Wraps a general description of an MSR -#[derive(Serialize, Deserialize, Debug)] -pub struct MSRDesc { - pub name: String, - pub address: u32, - pub fields: Vec, +pub trait MsrStore { + fn is_empty(&self) -> bool; + fn get_value<'a>(&self, desc: &'a MSRDesc) -> std::result::Result, Error>; } -impl MSRDesc { - #[cfg(all(target_os = "linux", feature = "use_msr"))] - pub fn get_value(&self) -> Result { - use std::{ - fs, - io::{Read, Seek}, - }; - - let mut file = fs::OpenOptions::new() - .read(true) - .open("/dev/cpu/0/msr") - .map_err(|e| match e.kind() { - io::ErrorKind::NotFound => Error::NotAvailible, - io::ErrorKind::PermissionDenied => Error::NotAvailible, - _ => Error::IOError(e), - })?; - file.seek(io::SeekFrom::Start(self.address.into()))?; - let mut msr_bytes = [u8::MIN; 8]; - file.read_exact(&mut msr_bytes)?; - Ok(u64::from_le_bytes(msr_bytes)) - } - #[cfg(all(target_os = "linux", feature = "use_msr"))] - pub fn is_available() -> bool { +pub struct EmptyMSR {} + +impl MsrStore for EmptyMSR { + fn is_empty(&self) -> bool { true } - - #[cfg(any(not(target_os = "linux"), not(feature = "use_msr")))] - pub fn get_value(&self) -> Result { + fn get_value<'a>(&self, _desc: &'a MSRDesc) -> std::result::Result, Error> { Err(Error::NotAvailible) } - #[cfg(any(not(target_os = "linux"), not(feature = "use_msr")))] - pub fn is_available() -> bool { - false +} + +#[cfg(all(target_os = "linux", feature = "use_msr"))] +pub const LINUX_MSR_AVAILIBLE: bool = true; +#[cfg(any(not(target_os = "linux"), not(feature = "use_msr")))] +pub const LINUX_MSR_AVAILIBLE: bool = false; + +#[cfg(all(target_os = "linux", feature = "use_msr"))] +pub struct LinuxMsrStore { + msr_device: fs::File, +} + +#[cfg(all(target_os = "linux", feature = "use_msr"))] +impl LinuxMsrStore { + pub fn new() -> Result { + Ok(LinuxMsrStore { + msr_device: fs::OpenOptions::new() + .read(true) + .open("/dev/cpu/0/msr") + .map_err(|e| match e.kind() { + io::ErrorKind::NotFound => Error::NotAvailible, + io::ErrorKind::PermissionDenied => Error::NotAvailible, + _ => Error::IOError(e), + })?, + }) } +} - pub fn into_value(&self) -> Result { - self.try_into() +#[cfg(all(target_os = "linux", feature = "use_msr"))] +impl MsrStore for LinuxMsrStore { + fn is_empty(&self) -> bool { + false + } + fn get_value<'a>(&self, desc: &'a MSRDesc) -> std::result::Result, Error> { + use std::os::unix::fs::FileExt; + let mut msr_bytes = [u8::MIN; 8]; + self.msr_device + .read_at(&mut msr_bytes, desc.address.into())?; + Ok(MSRValue { + desc, + value: u64::from_le_bytes(msr_bytes), + }) } } +/// Wraps a general description of an MSR +#[derive(Serialize, Deserialize, Debug)] +pub struct MSRDesc { + pub name: String, + pub address: u32, + pub fields: Vec, +} + impl fmt::Display for MSRDesc { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { write!(f, "{}: {:#x}", self.name, self.address) @@ -98,16 +115,6 @@ pub struct MSRValue<'a> { pub value: u64, } -impl<'a> convert::TryFrom<&'a MSRDesc> for MSRValue<'a> { - type Error = Error; - fn try_from(desc: &'a MSRDesc) -> Result> { - Ok(MSRValue { - desc, - value: desc.get_value()?, - }) - } -} - impl<'a, T: From + From + From> facts::Facter> for MSRValue<'a> { fn collect_facts(&self) -> Vec> { let value = self.value.into(); From 7dc22357141bcf33fca4a05d20b0e7cc82ef80fd Mon Sep 17 00:00:00 2001 From: Joshua Job Date: Thu, 2 May 2024 17:09:12 -0700 Subject: [PATCH 2/3] kvm: Added KVM MSR Store access --- Cargo.lock | 23 ++++++++++------ Cargo.toml | 4 +-- src/kvm.rs | 52 ++++++++++++++++++++++++++++++++++++- src/main.rs | 75 +++++++++++++++++++++++++++++++++++++---------------- src/msr.rs | 5 ---- 5 files changed, 120 insertions(+), 39 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 04f1bda..26313a5 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -34,6 +34,12 @@ version = "1.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" +[[package]] +name = "bitflags" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cf4b9d6a944f767f8e5e0db018570623c85f3d925ac718db4e06d0187adb21c1" + [[package]] name = "clap" version = "2.34.0" @@ -42,7 +48,7 @@ checksum = "a0610544180c38b88101fecf2dd634b174a62eef6946f84dfc6a7127512b381c" dependencies = [ "ansi_term", "atty", - "bitflags", + "bitflags 1.3.2", "strsim", "textwrap", "unicode-width", @@ -109,19 +115,20 @@ dependencies = [ [[package]] name = "kvm-bindings" -version = "0.6.0" +version = "0.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "efe70e65a5b092161d17f5005b66e5eefe7a94a70c332e755036fc4af78c4e79" +checksum = "a82e7e8725a39a0015e511a46cc1f7d90cecc180db1610c4d0d4339a9e48bd21" dependencies = [ "vmm-sys-util", ] [[package]] name = "kvm-ioctls" -version = "0.12.0" +version = "0.17.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c3a321cabd827642499c77e27314f388dd83a717a5ca716b86476fb947f73ae4" +checksum = "bedae2ca4a531bebe311abaf9691f5cc14eaa21475243caa2e39c43bb872947d" dependencies = [ + "bitflags 2.5.0", "kvm-bindings", "libc", "vmm-sys-util", @@ -313,11 +320,11 @@ checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f" [[package]] name = "vmm-sys-util" -version = "0.11.0" +version = "0.12.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cc06a16ee8ebf0d9269aed304030b0d20a866b8b3dd3d4ce532596ac567a0d24" +checksum = "1d1435039746e20da4f8d507a72ee1b916f7b4b05af7a91c093d2c6561934ede" dependencies = [ - "bitflags", + "bitflags 1.3.2", "libc", ] diff --git a/Cargo.toml b/Cargo.toml index 9c03074..7422ba1 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -13,8 +13,8 @@ serde_yaml = "0.8" enum_dispatch = "0.3.8" [target.'cfg(target_os = "linux")'.dependencies] -kvm-ioctls = { version = "0.12.0", optional = true } -kvm-bindings = { version = "0.6.0", features = ["fam-wrappers"], optional = true } +kvm-ioctls = { version = "0.17", optional = true } +kvm-bindings = { version = "0.8", features = ["fam-wrappers"], optional = true } [features] default = ["use_msr", "kvm"] diff --git a/src/kvm.rs b/src/kvm.rs index 66c7b3e..dbd954b 100644 --- a/src/kvm.rs +++ b/src/kvm.rs @@ -1,6 +1,9 @@ +use crate::msr::{self, MSRValue, MsrStore}; + use super::CpuidDB; use core::arch::x86_64::CpuidResult; -use kvm_bindings::{KVM_CPUID_FLAG_SIGNIFCANT_INDEX, KVM_MAX_CPUID_ENTRIES}; +use kvm_bindings::{kvm_msr_entry, Msrs, KVM_CPUID_FLAG_SIGNIFCANT_INDEX, KVM_MAX_CPUID_ENTRIES}; +use std::error::Error; /** Wrap information from kvm * @@ -39,3 +42,50 @@ impl CpuidDB for KvmInfo { }) } } + +pub struct KvmMsrInfo { + msr_info: kvm_bindings::Msrs, +} + +impl KvmMsrInfo { + pub fn new(kvm: &kvm_ioctls::Kvm) -> Result> { + let msr_features = kvm.get_msr_feature_index_list()?; + let mut msrs = Msrs::from_entries( + &msr_features + .as_slice() + .iter() + .map(|&index| kvm_msr_entry { + index, + ..Default::default() + }) + .collect::>(), + )?; + kvm.get_msrs(&mut msrs)?; + Ok(KvmMsrInfo { msr_info: msrs }) + } +} + +impl MsrStore for KvmMsrInfo { + fn is_empty(&self) -> bool { + false + } + fn get_value<'a>( + &self, + desc: &'a crate::msr::MSRDesc, + ) -> std::result::Result, crate::msr::Error> { + self.msr_info + .as_slice() + .iter() + .find_map(|entry| { + if entry.index == desc.address { + Some(MSRValue { + desc, + value: entry.data, + }) + } else { + None + } + }) + .ok_or_else(|| msr::Error::NotAvailible {}) + } +} diff --git a/src/main.rs b/src/main.rs index d8ac6e4..0e17f9c 100644 --- a/src/main.rs +++ b/src/main.rs @@ -76,8 +76,10 @@ impl Command for Disp { } } + #[cfg(feature = "use_msr")] if !self.skip_msr { - if msr::LINUX_MSR_AVAILIBLE { + #[cfg(all(target_os = "linux"))] + { match msr::LinuxMsrStore::new() { Ok(linux_store) => { println!("MSRS:"); @@ -91,6 +93,25 @@ impl Command for Disp { Err(e) => println!("Error checking all msrs: {}", e), } } + #[cfg(all(target_os = "linux", feature = "kvm"))] + if !self.skip_kvm { + use cpuinfo::kvm::KvmMsrInfo; + use kvm_ioctls::Kvm; + println!("KVM-MSR:"); + if let Err(e) = { + let kvm = Kvm::new()?; + let kvm_msr = KvmMsrInfo::new(&kvm)?; + for msr in &config.msrs { + match kvm_msr.get_value(msr) { + Ok(value) => println!("{}", value), + Err(err) => println!("{} Error : {}", msr, err), + } + } + Ok::<_, Box>(()) + } { + println!("Error Processing KVM-MSR: {}", e); + } + } } Ok(()) } @@ -120,7 +141,7 @@ fn collect_facts( }) .collect(); - if msr_store.is_empty() { + if !msr_store.is_empty() { for msr in &config.msrs { if let Ok(value) = msr_store.get_value(msr) { let mut facts = value.collect_facts(); @@ -137,41 +158,49 @@ fn collect_facts( impl Command for Facts { fn run(&self, config: &Definition) -> Result<(), Box> { - #[cfg(all(target_os = "linux", feature = "kvm"))] - let kvm_option = { - use cpuinfo::kvm::KvmInfo; - use kvm_ioctls::Kvm; - if self.use_kvm { - println!("using kvm"); - let kvm = Kvm::new()?; - Some(KvmInfo::new(&kvm)?) - } else { - None - } - }; - let (cpuid_source, msr_source): (_, Box) = { #[cfg(all(target_os = "linux", feature = "kvm"))] { - if let Some(kvm_info) = kvm_option { + if self.use_kvm { + use cpuinfo::kvm::KvmInfo; + use kvm::KvmMsrInfo; + use kvm_ioctls::Kvm; + let kvm = Kvm::new()?; ( - kvm_info.into(), - Box::new(msr::EmptyMSR {}) as Box, + KvmInfo::new(&kvm)?.into(), + Box::new(KvmMsrInfo::new(&kvm)?) as Box, ) } else { - ( - CpuidType::func(), - Box::new(msr::LinuxMsrStore::new()?) as Box, - ) + let msr = { + #[cfg(feature = "use_msr")] + { + Box::new(msr::LinuxMsrStore::new()?) as Box + } + #[cfg(not(feature = "use_msr"))] + { + Box::new(msr::EmptyMSR {}) + } + }; + (CpuidType::func(), msr) } } - #[cfg(any(not(target_os = "linux"), not(feature = "kvm")))] + #[cfg(all(target_os = "linux", not(feature = "kvm"), feature = "use_msr"))] { ( CpuidType::func(), Box::new(msr::LinuxMsrStore::new()?) as Box, ) } + #[cfg(all( + not(feature = "kvm"), + any(not(target_os = "linux"), not(feature = "use_msr")) + ))] + { + ( + CpuidType::func(), + Box::new(msr::EmptyMSR {}) as Box, + ) + } }; println!( "{}", diff --git a/src/msr.rs b/src/msr.rs index a3c8f8e..c5fe973 100644 --- a/src/msr.rs +++ b/src/msr.rs @@ -53,11 +53,6 @@ impl MsrStore for EmptyMSR { } } -#[cfg(all(target_os = "linux", feature = "use_msr"))] -pub const LINUX_MSR_AVAILIBLE: bool = true; -#[cfg(any(not(target_os = "linux"), not(feature = "use_msr")))] -pub const LINUX_MSR_AVAILIBLE: bool = false; - #[cfg(all(target_os = "linux", feature = "use_msr"))] pub struct LinuxMsrStore { msr_device: fs::File, From b5d869a3fe3a8fbbeede54dd0d577179ec42b94a Mon Sep 17 00:00:00 2001 From: Joshua Job Date: Thu, 2 May 2024 17:33:31 -0700 Subject: [PATCH 3/3] MSR: Fixed several issues related to inclusion Some platforms and feature lists can end up with partial inclusions. --- src/main.rs | 15 +++++++------ src/msr.rs | 65 ++++++++++++++++++++++++++++------------------------- 2 files changed, 42 insertions(+), 38 deletions(-) diff --git a/src/main.rs b/src/main.rs index 0e17f9c..cf5d732 100644 --- a/src/main.rs +++ b/src/main.rs @@ -40,6 +40,7 @@ struct Disp { #[cfg(all(target_os = "linux", feature = "kvm"))] #[structopt(long)] skip_kvm: bool, + #[cfg(feature = "use_msr")] #[structopt(long)] skip_msr: bool, } @@ -78,9 +79,9 @@ impl Command for Disp { #[cfg(feature = "use_msr")] if !self.skip_msr { - #[cfg(all(target_os = "linux"))] + #[cfg(target_os = "linux")] { - match msr::LinuxMsrStore::new() { + match msr::linux::LinuxMsrStore::new() { Ok(linux_store) => { println!("MSRS:"); for msr in &config.msrs { @@ -174,7 +175,7 @@ impl Command for Facts { let msr = { #[cfg(feature = "use_msr")] { - Box::new(msr::LinuxMsrStore::new()?) as Box + Box::new(msr::linux::LinuxMsrStore::new()?) as Box } #[cfg(not(feature = "use_msr"))] { @@ -188,12 +189,12 @@ impl Command for Facts { { ( CpuidType::func(), - Box::new(msr::LinuxMsrStore::new()?) as Box, + Box::new(msr::linux::LinuxMsrStore::new()?) as Box, ) } - #[cfg(all( - not(feature = "kvm"), - any(not(target_os = "linux"), not(feature = "use_msr")) + #[cfg(any( + not(target_os = "linux"), + all(not(feature = "kvm"), not(feature = "use_msr")) ))] { ( diff --git a/src/msr.rs b/src/msr.rs index c5fe973..278d923 100644 --- a/src/msr.rs +++ b/src/msr.rs @@ -4,7 +4,6 @@ use super::bitfield::{self, Facter}; use super::facts::{self, GenericFact}; use serde::{Deserialize, Serialize}; -use std::fs; use std::vec::Vec; use std::{convert, error, fmt, io}; @@ -54,40 +53,44 @@ impl MsrStore for EmptyMSR { } #[cfg(all(target_os = "linux", feature = "use_msr"))] -pub struct LinuxMsrStore { - msr_device: fs::File, -} +pub mod linux { + use super::*; + use std::fs; + use std::io; -#[cfg(all(target_os = "linux", feature = "use_msr"))] -impl LinuxMsrStore { - pub fn new() -> Result { - Ok(LinuxMsrStore { - msr_device: fs::OpenOptions::new() - .read(true) - .open("/dev/cpu/0/msr") - .map_err(|e| match e.kind() { - io::ErrorKind::NotFound => Error::NotAvailible, - io::ErrorKind::PermissionDenied => Error::NotAvailible, - _ => Error::IOError(e), - })?, - }) + pub struct LinuxMsrStore { + msr_device: fs::File, } -} -#[cfg(all(target_os = "linux", feature = "use_msr"))] -impl MsrStore for LinuxMsrStore { - fn is_empty(&self) -> bool { - false + impl LinuxMsrStore { + pub fn new() -> Result { + Ok(LinuxMsrStore { + msr_device: fs::OpenOptions::new() + .read(true) + .open("/dev/cpu/0/msr") + .map_err(|e| match e.kind() { + io::ErrorKind::NotFound => Error::NotAvailible, + io::ErrorKind::PermissionDenied => Error::NotAvailible, + _ => Error::IOError(e), + })?, + }) + } } - fn get_value<'a>(&self, desc: &'a MSRDesc) -> std::result::Result, Error> { - use std::os::unix::fs::FileExt; - let mut msr_bytes = [u8::MIN; 8]; - self.msr_device - .read_at(&mut msr_bytes, desc.address.into())?; - Ok(MSRValue { - desc, - value: u64::from_le_bytes(msr_bytes), - }) + + impl MsrStore for LinuxMsrStore { + fn is_empty(&self) -> bool { + false + } + fn get_value<'a>(&self, desc: &'a MSRDesc) -> std::result::Result, Error> { + use std::os::unix::fs::FileExt; + let mut msr_bytes = [u8::MIN; 8]; + self.msr_device + .read_at(&mut msr_bytes, desc.address.into())?; + Ok(MSRValue { + desc, + value: u64::from_le_bytes(msr_bytes), + }) + } } }