diff --git a/crates/xen/xencall/Cargo.toml b/crates/xen/xencall/Cargo.toml index 1f0bf06..8ab2bbc 100644 --- a/crates/xen/xencall/Cargo.toml +++ b/crates/xen/xencall/Cargo.toml @@ -33,3 +33,7 @@ path = "examples/domain_create.rs" [[example]] name = "xencall-version-capabilities" path = "examples/version_capabilities.rs" + +[[example]] +name = "xencall-power-management" +path = "examples/power_management.rs" diff --git a/crates/xen/xencall/examples/power_management.rs b/crates/xen/xencall/examples/power_management.rs new file mode 100644 index 0000000..716ac77 --- /dev/null +++ b/crates/xen/xencall/examples/power_management.rs @@ -0,0 +1,19 @@ +use xencall::error::Result; +use xencall::sys::CpuId; +use xencall::XenCall; + +#[tokio::main] +async fn main() -> Result<()> { + env_logger::init(); + + let call = XenCall::open(0)?; + let physinfo = call.phys_info().await?; + println!("{:?}", physinfo); + let topology = call.cpu_topology().await?; + println!("{:?}", topology); + call.set_cpufreq_gov(CpuId::All, "performance").await?; + call.set_cpufreq_gov(CpuId::Single(0), "performance") + .await?; + call.set_turbo_mode(CpuId::All, true).await?; + Ok(()) +} diff --git a/crates/xen/xencall/src/error.rs b/crates/xen/xencall/src/error.rs index 36aaaa3..f6633c2 100644 --- a/crates/xen/xencall/src/error.rs +++ b/crates/xen/xencall/src/error.rs @@ -14,6 +14,8 @@ pub enum Error { PopulatePhysmapFailed, #[error("mmap batch failed: {0}")] MmapBatchFailed(nix::errno::Errno), + #[error("specified value is too long")] + ValueTooLong, } pub type Result = std::result::Result; diff --git a/crates/xen/xencall/src/lib.rs b/crates/xen/xencall/src/lib.rs index 68ed8cb..f2496db 100644 --- a/crates/xen/xencall/src/lib.rs +++ b/crates/xen/xencall/src/lib.rs @@ -25,9 +25,13 @@ use std::ffi::{c_long, c_uint, c_ulong, c_void}; use std::sync::Arc; use std::time::Duration; use sys::{ - E820Entry, ForeignMemoryMap, PhysdevMapPirq, VcpuGuestContextAny, HYPERVISOR_PHYSDEV_OP, - PHYSDEVOP_MAP_PIRQ, XEN_DOMCTL_MAX_INTERFACE_VERSION, XEN_DOMCTL_MIN_INTERFACE_VERSION, - XEN_MEM_SET_MEMORY_MAP, + CpuId, E820Entry, ForeignMemoryMap, PhysdevMapPirq, Sysctl, SysctlCputopo, SysctlCputopoinfo, + SysctlPhysinfo, SysctlPmOp, SysctlPmOpValue, SysctlSetCpuFreqGov, SysctlValue, + VcpuGuestContextAny, HYPERVISOR_PHYSDEV_OP, HYPERVISOR_SYSCTL, PHYSDEVOP_MAP_PIRQ, + XEN_DOMCTL_MAX_INTERFACE_VERSION, XEN_DOMCTL_MIN_INTERFACE_VERSION, XEN_MEM_SET_MEMORY_MAP, + XEN_SYSCTL_CPUTOPOINFO, XEN_SYSCTL_MAX_INTERFACE_VERSION, XEN_SYSCTL_MIN_INTERFACE_VERSION, + XEN_SYSCTL_PHYSINFO, XEN_SYSCTL_PM_OP, XEN_SYSCTL_PM_OP_DISABLE_TURBO, + XEN_SYSCTL_PM_OP_ENABLE_TURBO, }; use tokio::sync::Semaphore; use tokio::time::sleep; @@ -42,6 +46,7 @@ pub struct XenCall { pub handle: Arc, semaphore: Arc, domctl_interface_version: u32, + sysctl_interface_version: u32, } impl XenCall { @@ -52,10 +57,12 @@ impl XenCall { .open("/dev/xen/privcmd")?; let domctl_interface_version = XenCall::detect_domctl_interface_version(&handle, current_domid)?; + let sysctl_interface_version = XenCall::detect_sysctl_interface_version(&handle)?; Ok(XenCall { handle: Arc::new(handle), semaphore: Arc::new(Semaphore::new(1)), domctl_interface_version, + sysctl_interface_version, }) } @@ -83,6 +90,32 @@ impl XenCall { Err(Error::XenVersionUnsupported) } + fn detect_sysctl_interface_version(handle: &File) -> Result { + for version in XEN_SYSCTL_MIN_INTERFACE_VERSION..XEN_SYSCTL_MAX_INTERFACE_VERSION + 1 { + let mut sysctl = Sysctl { + cmd: XEN_SYSCTL_CPUTOPOINFO, + interface_version: version, + value: SysctlValue { + cputopoinfo: SysctlCputopoinfo { + num_cpus: 0, + handle: null_mut(), + }, + }, + }; + unsafe { + let mut call = Hypercall { + op: HYPERVISOR_SYSCTL, + arg: [addr_of_mut!(sysctl) as u64, 0, 0, 0, 0], + }; + let result = sys::hypercall(handle.as_raw_fd(), &mut call).unwrap_or(-1); + if result == 0 { + return Ok(version); + } + } + } + Err(Error::XenVersionUnsupported) + } + pub async fn mmap(&self, addr: u64, len: u64) -> Option { let _permit = self.semaphore.acquire().await.ok()?; trace!( @@ -917,4 +950,141 @@ impl XenCall { .await?; Ok(()) } + + pub async fn cpu_topology(&self) -> Result> { + let mut sysctl = Sysctl { + cmd: XEN_SYSCTL_CPUTOPOINFO, + interface_version: self.sysctl_interface_version, + value: SysctlValue { + cputopoinfo: SysctlCputopoinfo { + num_cpus: 0, + handle: null_mut(), + }, + }, + }; + self.hypercall1(HYPERVISOR_SYSCTL, addr_of_mut!(sysctl) as c_ulong) + .await?; + let cpus = unsafe { sysctl.value.cputopoinfo.num_cpus }; + let mut topos = vec![ + SysctlCputopo { + core: 0, + socket: 0, + node: 0 + }; + cpus as usize + ]; + let mut sysctl = Sysctl { + cmd: XEN_SYSCTL_CPUTOPOINFO, + interface_version: self.sysctl_interface_version, + value: SysctlValue { + cputopoinfo: SysctlCputopoinfo { + num_cpus: cpus, + handle: topos.as_mut_ptr(), + }, + }, + }; + self.hypercall1(HYPERVISOR_SYSCTL, addr_of_mut!(sysctl) as c_ulong) + .await?; + Ok(topos) + } + + pub async fn phys_info(&self) -> Result { + let mut sysctl = Sysctl { + cmd: XEN_SYSCTL_PHYSINFO, + interface_version: self.sysctl_interface_version, + value: SysctlValue { + phys_info: SysctlPhysinfo::default(), + }, + }; + self.hypercall1(HYPERVISOR_SYSCTL, addr_of_mut!(sysctl) as c_ulong) + .await?; + Ok(unsafe { sysctl.value.phys_info }) + } + + pub async fn set_cpufreq_gov(&self, cpuid: CpuId, gov: impl AsRef) -> Result<()> { + match cpuid { + CpuId::All => { + let phys_info = self.phys_info().await?; + for cpuid in 0..phys_info.max_cpu_id + 1 { + self.do_set_cpufreq_gov(cpuid, gov.as_ref()).await?; + } + } + + CpuId::Single(id) => { + self.do_set_cpufreq_gov(id, gov).await?; + } + } + Ok(()) + } + + async fn do_set_cpufreq_gov(&self, cpuid: u32, gov: impl AsRef) -> Result<()> { + let governor = gov.as_ref().as_bytes().to_vec(); + if governor.len() > 15 { + return Err(Error::ValueTooLong); + } + + let mut scaling_governor = [0u8; 16]; + + // leave space for the last byte to be zero at all times. + for i in 0..15usize { + if i >= governor.len() { + break; + } + scaling_governor[i] = governor[i]; + } + + let mut sysctl = Sysctl { + cmd: XEN_SYSCTL_PM_OP, + interface_version: self.sysctl_interface_version, + value: SysctlValue { + pm_op: SysctlPmOp { + cmd: XEN_SYSCTL_PM_OP_ENABLE_TURBO, + cpuid, + value: SysctlPmOpValue { + set_gov: SysctlSetCpuFreqGov { scaling_governor }, + }, + }, + }, + }; + self.hypercall1(HYPERVISOR_SYSCTL, addr_of_mut!(sysctl) as c_ulong) + .await?; + Ok(()) + } + + pub async fn set_turbo_mode(&self, cpuid: CpuId, enable: bool) -> Result<()> { + match cpuid { + CpuId::All => { + let phys_info = self.phys_info().await?; + for cpuid in 0..phys_info.max_cpu_id + 1 { + self.do_set_turbo_mode(cpuid, enable).await?; + } + } + + CpuId::Single(id) => { + self.do_set_turbo_mode(id, enable).await?; + } + } + Ok(()) + } + + async fn do_set_turbo_mode(&self, cpuid: u32, enable: bool) -> Result<()> { + let mut sysctl = Sysctl { + cmd: XEN_SYSCTL_PM_OP, + interface_version: self.sysctl_interface_version, + value: SysctlValue { + pm_op: SysctlPmOp { + cmd: if enable { + XEN_SYSCTL_PM_OP_ENABLE_TURBO + } else { + XEN_SYSCTL_PM_OP_DISABLE_TURBO + }, + cpuid, + value: SysctlPmOpValue { pad: [0u8; 128] }, + }, + }, + }; + self.hypercall1(HYPERVISOR_SYSCTL, addr_of_mut!(sysctl) as c_ulong) + .await?; + Ok(()) + } } diff --git a/crates/xen/xencall/src/sys.rs b/crates/xen/xencall/src/sys.rs index 9df2b5d..32ca833 100644 --- a/crates/xen/xencall/src/sys.rs +++ b/crates/xen/xencall/src/sys.rs @@ -712,3 +712,92 @@ pub struct HvmContext { pub struct PagingMempool { pub size: u64, } + +#[repr(C)] +#[derive(Clone, Copy, Debug)] +pub struct SysctlCputopo { + pub core: u32, + pub socket: u32, + pub node: u32, +} + +#[repr(C)] +#[derive(Clone, Copy, Debug)] +pub struct SysctlSetCpuFreqGov { + pub scaling_governor: [u8; 16], +} + +#[repr(C)] +#[derive(Clone, Copy)] +pub union SysctlPmOpValue { + pub set_gov: SysctlSetCpuFreqGov, + pub opt_smt: u32, + pub pad: [u8; 128], +} + +#[repr(C)] +#[derive(Clone, Copy)] +pub struct SysctlPmOp { + pub cmd: u32, + pub cpuid: u32, + pub value: SysctlPmOpValue, +} + +#[repr(C)] +#[derive(Clone, Copy, Debug)] +pub struct SysctlCputopoinfo { + pub num_cpus: u32, + pub handle: *mut SysctlCputopo, +} + +#[repr(C)] +pub union SysctlValue { + pub cputopoinfo: SysctlCputopoinfo, + pub pm_op: SysctlPmOp, + pub phys_info: SysctlPhysinfo, + pub pad: [u8; 128], +} + +#[repr(C)] +pub struct Sysctl { + pub cmd: u32, + pub interface_version: u32, + pub value: SysctlValue, +} + +pub const XEN_SYSCTL_PHYSINFO: u32 = 3; +pub const XEN_SYSCTL_PM_OP: u32 = 12; +pub const XEN_SYSCTL_CPUTOPOINFO: u32 = 16; + +pub const XEN_SYSCTL_MIN_INTERFACE_VERSION: u32 = 0x00000015; +pub const XEN_SYSCTL_MAX_INTERFACE_VERSION: u32 = 0x00000020; +pub const XEN_SYSCTL_PM_OP_SET_SCHED_OPT_STMT: u32 = 0x21; +pub const XEN_SYSCTL_PM_OP_ENABLE_TURBO: u32 = 0x26; +pub const XEN_SYSCTL_PM_OP_DISABLE_TURBO: u32 = 0x27; + +#[derive(Clone, Copy, Debug)] +pub enum CpuId { + All, + Single(u32), +} + +#[repr(C)] +#[derive(Clone, Copy, Debug, Default)] +pub struct SysctlPhysinfo { + pub threads_per_core: u32, + pub cores_per_socket: u32, + pub nr_cpus: u32, + pub max_cpu_id: u32, + pub nr_nodes: u32, + pub max_node_id: u32, + pub cpu_khz: u32, + pub capabilities: u32, + pub arch_capabilities: u32, + pub pad: u32, + pub total_pages: u64, + pub free_pages: u64, + pub scrub_pages: u64, + pub outstanding_pages: u64, + pub max_mfn: u64, + pub hw_cap: [u32; 8], +}