#![allow(dead_code)]
use std::arch::x86_64::_rdtsc;
use std::sync::LazyLock;
use anyhow::anyhow;
use anyhow::Result;
use base::debug;
use base::error;
mod calibrate;
mod cpuid;
mod grouping;
pub use calibrate::*;
pub use cpuid::*;
fn rdtsc_safe() -> u64 {
unsafe { _rdtsc() }
}
static TSC_STATE: LazyLock<Option<TscState>> = LazyLock::new(|| match calibrate_tsc_state() {
Ok(tsc_state) => {
debug!("Using calibrated tsc frequency: {} Hz", tsc_state.frequency);
for (core, offset) in tsc_state.offsets.iter().enumerate() {
debug!("Core {} has tsc offset of {:?} ns", core, offset);
}
Some(tsc_state)
}
Err(e) => {
error!("Failed to calibrate tsc state: {:#}", e);
None
}
});
pub fn tsc_frequency() -> Result<u64> {
let state = TSC_STATE
.as_ref()
.ok_or(anyhow!("TSC calibration failed"))?;
Ok(state.frequency)
}
pub fn tsc_state() -> Result<TscState> {
Ok(TSC_STATE
.as_ref()
.ok_or(anyhow!("TSC calibration failed"))?
.clone())
}
#[derive(Default, Debug)]
pub struct TscSyncMitigations {
pub affinities: Vec<Option<Vec<usize>>>,
pub offsets: Vec<Option<u64>>,
}
impl TscSyncMitigations {
fn new(num_vcpus: usize) -> Self {
TscSyncMitigations {
affinities: vec![None; num_vcpus],
offsets: vec![None; num_vcpus],
}
}
pub fn get_vcpu_affinity(&self, cpu_id: usize) -> Option<Vec<usize>> {
self.affinities.get(cpu_id).unwrap().clone()
}
pub fn get_vcpu_tsc_offset(&self, cpu_id: usize) -> Option<u64> {
*self.offsets.get(cpu_id).unwrap()
}
}
pub fn get_tsc_sync_mitigations(tsc_state: &TscState, num_vcpus: usize) -> TscSyncMitigations {
tsc_sync_mitigations_inner(tsc_state, num_vcpus, rdtsc_safe)
}
fn tsc_sync_mitigations_inner(
tsc_state: &TscState,
num_vcpus: usize,
rdtsc: fn() -> u64,
) -> TscSyncMitigations {
let mut mitigations = TscSyncMitigations::new(num_vcpus);
if tsc_state.core_grouping.size() == 1 {
return mitigations;
}
let largest_group = tsc_state.core_grouping.largest_group();
let num_cores = tsc_state.offsets.len();
if largest_group.cores.len() >= num_vcpus {
let affinity: Vec<usize> = largest_group.cores.iter().map(|core| core.core).collect();
for i in 0..num_vcpus {
mitigations.affinities[i] = Some(affinity.clone());
}
} else {
let host_tsc_now = rdtsc();
for i in 0..num_vcpus {
let pinned_core = i % num_cores;
mitigations.affinities[i] = Some(vec![pinned_core]);
mitigations.offsets[i] = Some(
0u64.wrapping_sub(host_tsc_now)
.wrapping_add(tsc_state.offsets[pinned_core].1.wrapping_neg() as i64 as u64),
);
}
}
mitigations
}
#[cfg(test)]
mod tests {
use std::time::Duration;
use super::*;
use crate::tsc::grouping::CoreGroup;
use crate::tsc::grouping::CoreGrouping;
use crate::tsc::grouping::CoreOffset;
#[test]
fn test_sync_mitigation_set_offsets() {
let offsets = vec![(0, 0), (1, 1000), (2, -1000), (3, 2000)];
let state = TscState::new(1_000_000_000, offsets, Duration::from_nanos(20))
.expect("TscState::new should not fail for this test");
assert_eq!(
state.core_grouping,
CoreGrouping::new(vec![
CoreGroup {
cores: vec![CoreOffset {
core: 2,
offset: -1000
}]
},
CoreGroup {
cores: vec![CoreOffset { core: 0, offset: 0 }]
},
CoreGroup {
cores: vec![CoreOffset {
core: 1,
offset: 1000
}]
},
CoreGroup {
cores: vec![CoreOffset {
core: 3,
offset: 2000
}]
},
])
.expect("CoreGrouping::new should not fail here")
);
fn fake_rdtsc() -> u64 {
u64::MAX
}
let mitigations = tsc_sync_mitigations_inner(&state, 4, fake_rdtsc);
let expected = [1, 1u64.wrapping_sub(1000), 1001u64, 1u64.wrapping_sub(2000)];
for (i, expect) in expected.iter().enumerate() {
assert_eq!(
mitigations
.get_vcpu_tsc_offset(i)
.unwrap_or_else(|| panic!("core {i} should have an offset of {expect}")),
*expect
);
assert_eq!(
mitigations
.get_vcpu_affinity(i)
.unwrap_or_else(|| panic!("core {i} should have an affinity of [{i}]")),
vec![i]
);
}
}
#[test]
fn test_sync_mitigation_large_group() {
let offsets = vec![
(0, 0),
(1, -1000),
(2, 1000),
(3, -1000),
(4, 2000),
(5, -1000),
(6, 3000),
(7, -1000),
];
let state = TscState::new(1_000_000_000, offsets, Duration::from_nanos(20))
.expect("TscState::new should not fail for this test");
assert_eq!(
state.core_grouping,
CoreGrouping::new(vec![
CoreGroup {
cores: vec![
CoreOffset {
core: 1,
offset: -1000
},
CoreOffset {
core: 3,
offset: -1000
},
CoreOffset {
core: 5,
offset: -1000
},
CoreOffset {
core: 7,
offset: -1000
}
]
},
CoreGroup {
cores: vec![CoreOffset { core: 0, offset: 0 }]
},
CoreGroup {
cores: vec![CoreOffset {
core: 2,
offset: 1000
}]
},
CoreGroup {
cores: vec![CoreOffset {
core: 4,
offset: 2000
}]
},
CoreGroup {
cores: vec![CoreOffset {
core: 6,
offset: 3000
}]
},
])
.expect("CoreGrouping::new should not fail here")
);
fn fake_rdtsc() -> u64 {
u64::MAX
}
let num_vcpus = 4;
let mitigations = tsc_sync_mitigations_inner(&state, num_vcpus, fake_rdtsc);
let expected_affinity = vec![1, 3, 5, 7];
for i in 0..num_vcpus {
assert_eq!(
mitigations.get_vcpu_affinity(i).unwrap_or_else(|| panic!(
"core {i} should have an affinity of {expected_affinity:?}"
)),
expected_affinity
);
assert_eq!(mitigations.get_vcpu_tsc_offset(i), None);
}
}
#[test]
fn more_vcpus_than_cores() {
let offsets = vec![(0, 0), (1, 0), (2, 1000), (3, 2000)];
let state = TscState::new(1_000_000_000, offsets, Duration::from_nanos(20))
.expect("TscState::new should not fail for this test");
assert_eq!(
state.core_grouping,
CoreGrouping::new(vec![
CoreGroup {
cores: vec![
CoreOffset { core: 0, offset: 0 },
CoreOffset { core: 1, offset: 0 }
]
},
CoreGroup {
cores: vec![CoreOffset {
core: 2,
offset: 1000
}]
},
CoreGroup {
cores: vec![CoreOffset {
core: 3,
offset: 2000
}]
},
])
.expect("CoreGrouping::new should not fail here")
);
fn fake_rdtsc() -> u64 {
u64::MAX
}
let num_vcpus = 8;
let mitigations = tsc_sync_mitigations_inner(&state, num_vcpus, fake_rdtsc);
let expected_offsets = [1, 1, 1u64.wrapping_sub(1000), 1u64.wrapping_sub(2000)];
for i in 0..num_vcpus {
assert_eq!(
mitigations.get_vcpu_affinity(i).unwrap_or_else(|| panic!(
"core {} should have an affinity of {:?}",
i,
i % 4
)),
vec![i % 4]
);
assert_eq!(
mitigations.get_vcpu_tsc_offset(i).unwrap_or_else(|| panic!(
"core {} should have an offset of {:?}",
i,
expected_offsets[i % 4]
)),
expected_offsets[i % 4]
);
}
}
}