feature(xen): implement power management operations

This commit is contained in:
Alex Zenla 2024-06-28 10:13:01 -07:00
parent f5b4c66ec7
commit d3bd5a35c7
No known key found for this signature in database
GPG Key ID: 067B238899B51269
5 changed files with 287 additions and 3 deletions

View File

@ -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"

View File

@ -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(())
}

View File

@ -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<T> = std::result::Result<T, Error>;

View File

@ -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<File>,
semaphore: Arc<Semaphore>,
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<u32> {
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<u64> {
let _permit = self.semaphore.acquire().await.ok()?;
trace!(
@ -917,4 +950,141 @@ impl XenCall {
.await?;
Ok(())
}
pub async fn cpu_topology(&self) -> Result<Vec<SysctlCputopo>> {
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<SysctlPhysinfo> {
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<str>) -> 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<str>) -> 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(())
}
}

View File

@ -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],
}