addr_monitor 0.2.0

Safe synchronization of a memory location
Documentation
//! Direct hardware access to address monitoring.

use core::arch::x86_64::_rdtsc;

use x86_intr::{
    arch::{_mm_monitor, _mm_mwait, _monitorx, _mwaitx, _umonitor, _umwait, mwaitx_no_timeout},
    is_cpuid_feature_detected,
};

/// Safe hardware monitor wrapper.
pub struct HardwareMonitor(());

impl HardwareMonitor {
    /// Sets up a monitored location.
    ///
    /// This operation is NOT free. It executes a very specific operation.
    ///
    /// # Panics
    ///
    /// If support is [`MonitorSupportBackend::None`].
    #[inline]
    pub fn new<T>(addr: *const T, support: MonitorSupport) -> Self {
        match support.inner {
            MonitorSupportBackend::Monitor => unsafe {
                _mm_monitor(addr.cast(), 0, 0);
            },
            MonitorSupportBackend::Monitorx => unsafe {
                _monitorx(addr.cast(), 0, 0);
            },
            MonitorSupportBackend::Waitpkg => unsafe {
                _umonitor(addr.cast());
            },
            MonitorSupportBackend::None => panic!("hardware monitors not supported on platform"),
        }

        Self(())
    }

    /// Waits for active monitor to wake.
    ///
    /// Does not guarantee the monitored address has changed.
    ///
    /// Can produce more efficient code on AMD processors.
    #[inline]
    pub fn wait(self, control: MonitorControl, support: MonitorSupport) {
        const DEFAULT_TIMEOUT: u64 = 65536;

        match support.inner {
            MonitorSupportBackend::Monitor => unsafe {
                _mm_mwait(0, control as u32);
            },
            MonitorSupportBackend::Monitorx => unsafe {
                mwaitx_no_timeout(control as u32);
            },
            MonitorSupportBackend::Waitpkg => unsafe {
                let deadline = _rdtsc() + DEFAULT_TIMEOUT;

                _umwait(control as u32, deadline);
            },
            MonitorSupportBackend::None => panic!("hardware monitors not supported on platform"),
        }
    }

    /// Waits for active monitor to wake.
    ///
    /// Does not guarantee the monitored address has changed or returns before timeout.
    ///
    /// Can produce more efficient code on Intel processors.
    pub fn wait_timeout(self, timeout: u64, control: MonitorControl, support: MonitorSupport) {
        match support.inner {
            MonitorSupportBackend::Monitor => unsafe {
                _mm_mwait(0, control as u32);
            },
            MonitorSupportBackend::Monitorx => unsafe {
                _mwaitx(0, control as u32, timeout as u32);
            },
            MonitorSupportBackend::Waitpkg => unsafe {
                let deadline = _rdtsc() + timeout;

                _umwait(control as u32, deadline);
            },
            MonitorSupportBackend::None => panic!("hardware monitors not supported on platform"),
        }
    }
}

/// Control register for hardware monitors.
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
#[repr(u32)]
pub enum MonitorControl {
    /// Optimize for power consumption during sleep.
    Power = 0,

    /// Optimize for fast wakeup during sleep.
    Performance = 1,
}

/// Enumerated monitor backends used for matching over implementations.
#[repr(u8)]
#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, Hash)]
#[non_exhaustive]
pub enum MonitorSupportBackend {
    /// No available monitors
    #[default]
    None,

    /// Userspace `waitpkg` feature (Intel)
    Waitpkg,

    /// Userspace `monitorx` feature (AMD)
    Monitorx,

    /// Privileged `monitor` feature
    Monitor,
}

/// Safe wrapper for supported monitor backends.
#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, Hash)]
#[repr(transparent)]
pub struct MonitorSupport {
    pub(crate) inner: MonitorSupportBackend,
}

impl MonitorSupport {
    /// Detect the best unprivileged monitor backend.
    pub fn platform() -> Self {
        let inner = if is_cpuid_feature_detected!("waitpkg") {
            MonitorSupportBackend::Waitpkg
        } else if is_cpuid_feature_detected!("monitorx") {
            MonitorSupportBackend::Monitorx
        } else {
            MonitorSupportBackend::None
        };

        Self { inner }
    }

    /// Construct without checking that the backend is supported.
    ///
    /// # Safety
    ///
    /// The processor must support the provided backend
    pub unsafe fn new_unchecked(inner: MonitorSupportBackend) -> Self {
        Self { inner }
    }

    /// Reinterpret as enum representation.
    pub fn backend(self) -> MonitorSupportBackend {
        self.inner
    }
}