From 8aef372b117425dfcc24404c266ec5323684db0f Mon Sep 17 00:00:00 2001 From: x Date: Tue, 23 Dec 2025 13:58:16 +0000 Subject: [PATCH] minerd: MSR support --- bin/minerd/minerd.toml | 3 + bin/minerd/src/hw/mod.rs | 184 ++++++++++++++++++++++++++++++++++- bin/minerd/src/hw/msr/mod.rs | 37 ++++++- bin/minerd/src/main.rs | 19 +++- 4 files changed, 237 insertions(+), 6 deletions(-) diff --git a/bin/minerd/minerd.toml b/bin/minerd/minerd.toml index fad2e3f8c..4454544d2 100644 --- a/bin/minerd/minerd.toml +++ b/bin/minerd/minerd.toml @@ -21,6 +21,9 @@ secure = false # PoW miner number of threads to use #threads = 4 +# Assign full L3 cache to CPU +cache_qos = false + # Polling rate to ask darkfid for mining jobs #polling_rate = 2 diff --git a/bin/minerd/src/hw/mod.rs b/bin/minerd/src/hw/mod.rs index 3231bb513..c685c53f2 100644 --- a/bin/minerd/src/hw/mod.rs +++ b/bin/minerd/src/hw/mod.rs @@ -16,8 +16,190 @@ * along with this program. If not, see . */ -/// CPU detection +use std::collections::HashSet; + +use tracing::{debug, error, info, warn}; + +/// CPU detection pub mod cpuid; pub mod cpuid; +use cpuid::{CpuInfo, CpuThreads}; /// Model-Specific Registers pub mod msr; +use msr::{msr_presets, Msr, MsrItem, MsrPreset, NO_MASK}; + +// MSR registers for Cache QoS +const IA32_PQR_ASSOC: u32 = 0xC8F; // PQR (Platform QoS Resource) Association +const IA32_L3_QOS_MASK_1: u32 = 0xC91; // L3 Cache QoS Mask for COS 1 + +// Class of Service assignments +const COS_FULL_CACHE: u64 = 0; // COS 0 = full L3 cache access +const COS_LIMITED_CACHE: u64 = 1 << 32; // COS 1 = limited L3 cache (bit 32 sets COS in PQR_ASSOC) + +#[derive(Default)] +pub struct RxMsr { + is_initialized: bool, + is_enabled: bool, + cache_qos: bool, + saved_items: Vec, +} + +impl RxMsr { + pub fn new() -> Self { + Self { is_initialized: false, is_enabled: false, cache_qos: false, saved_items: vec![] } + } + + pub fn init(&mut self, cache_qos: bool, threads: usize, save: bool) -> bool { + if self.is_initialized { + return self.is_enabled + } + + self.is_initialized = true; + self.is_enabled = false; + + // Detect CPU and find MSR preset + let cpu = CpuInfo::detect(); + let msr_preset = if cpu.is_amd() && cpu.zen_generation.is_some() { + msr_presets::get_preset(MsrPreset::from_zen(cpu.zen_generation.unwrap())) + } else if cpu.is_intel() { + msr_presets::get_preset(MsrPreset::Intel) + } else { + msr_presets::get_preset(MsrPreset::None) + }; + + if msr_preset.is_empty() { + return false + } + + self.cache_qos = cache_qos; + if self.cache_qos && !cpu.has_cat_l3 { + warn!("This CPU doesn't support cat_l3"); + self.cache_qos = false; + } + + self.is_enabled = self.wrmsr(msr_preset, threads, self.cache_qos, save); + if self.is_enabled { + info!("[msr] MSR register values set successfully"); + } else { + error!("[msr] Failed to apply MSR mod, hashrate will be low"); + } + + self.is_enabled + } + + pub fn destroy(&mut self) { + if !self.is_initialized { + return + } + + self.is_initialized = false; + self.is_enabled = false; + + if self.saved_items.is_empty() { + return + } + + let saved_items = std::mem::take(&mut self.saved_items); + + if !self.wrmsr(&saved_items, 0, self.cache_qos, false) { + error!("[msr] Failed to restore to initial state"); + } + } + + fn wrmsr( + &mut self, + preset: &[MsrItem], + threads: usize, // TODO: This should be a slice of threads + cache_qos: bool, + save: bool, + ) -> bool { + let msr = Msr::get(); + if msr.is_none() { + return false + } + let msr = msr.unwrap(); + + if save { + self.saved_items.reserve(preset.len()); + for i in preset { + if let Some(item) = msr.read(i.reg(), -1, true) { + if !item.is_valid() { + self.saved_items.clear(); + return false + } + + self.saved_items.push(item); + } + } + } + + // Which CPU cores will have access top the full L3 cache + // TODO: Check xmrig/crypto/rx/RxMsr.cpp::wsmsr() + //let cpu_threads = CpuThreads::detect(); + //let units: HashSet = cpu_threads.thread_ids().into_iter().collect(); + + let cache_enabled: HashSet = HashSet::new(); + //let mut cache_qos_disabled = threads.is_empty(); + let cache_qos_disabled = true; + + /* + if cache_qos && !cache_qos_disabled { + for thread in threads { + let affinity = thread.affinity(); + // If some thread has no affinity or wrong affinity, + // disable cache QoS + if affinity < 0 || !units.contains(&affinity) { + cache_qos_disabled = true; + warn!( + "Cache QoS can only be enabled when all mining threads have affinity set" + ); + } + break + } + cache_enabled.insert(affinity); + } + */ + + // Apply MSR values to all CPUs + msr.write_all(|cpu| { + debug!("msr.write_all cpu={} get_cpu={}", cpu, get_cpu(cpu)); + for item in preset { + if !msr.write(item.reg(), item.value(), get_cpu(cpu), item.mask(), true) { + return false + } + } + + if !cache_qos { + return true + } + + // Cache QoS configuration + if cache_qos_disabled || cache_enabled.contains(&cpu) { + // Assign Class of Service 0 (full L3 cache) to this CPU + return msr.write(IA32_PQR_ASSOC, COS_FULL_CACHE, get_cpu(cpu), NO_MASK, true) + } + + // For CPUs not running mining threads: + // Disable L3 cache for Class of Service 1 + if !msr.write(IA32_L3_QOS_MASK_1, 0, get_cpu(cpu), NO_MASK, true) { + // Some CPUs don't allow setting it to all zeros + if !msr.write(IA32_L3_QOS_MASK_1, 1, get_cpu(cpu), NO_MASK, true) { + return false + } + } + + // Assign Class of Service 1 (limited cache) to this CPU + msr.write(IA32_PQR_ASSOC, COS_LIMITED_CACHE, get_cpu(cpu), NO_MASK, true) + }) + } +} + +#[cfg(target_os = "windows")] +const fn get_cpu(cpu: i32) -> i32 { + -1 +} + +#[cfg(not(target_os = "windows"))] +const fn get_cpu(cpu: i32) -> i32 { + cpu +} diff --git a/bin/minerd/src/hw/msr/mod.rs b/bin/minerd/src/hw/msr/mod.rs index e19bbef1c..399f5b549 100644 --- a/bin/minerd/src/hw/msr/mod.rs +++ b/bin/minerd/src/hw/msr/mod.rs @@ -33,12 +33,15 @@ use std::sync::{Arc, Weak}; use once_cell::sync::Lazy; use parking_lot::Mutex; +use super::CpuThreads; + mod error; mod msr_item; -mod msr_presets; +pub(super) mod msr_presets; use error::{MsrError, MsrResult}; -use msr_item::MsrItem; +pub use msr_item::{MsrItem, NO_MASK}; +pub use msr_presets::MsrPreset; #[cfg(target_os = "linux")] mod msr_linux; @@ -66,7 +69,7 @@ impl Msr { /// Get or create the MSR singleton /// /// Returns `None` if MSR is not available on this system. - pub fn get(units: Vec) -> Option> { + pub fn get() -> Option> { let mut instance = INSTANCE.lock(); // Try to upgrade the weak reference @@ -76,7 +79,33 @@ impl Msr { } } - // Create new instance + // Autodetect CPU units + let units = CpuThreads::detect().thread_ids(); + + let msr = Arc::new(Self { inner: MsrImpl::new(units) }); + + if msr.is_available() { + *instance = Arc::downgrade(&msr); + Some(msr) + } else { + None + } + } + + /// Get or create the MSR singleton with specific CPU units + /// + /// Returns `None` if MSR is not available on this system. + pub fn get_with_units(units: Vec) -> Option> { + let mut instance = INSTANCE.lock(); + + // Try to upgrade the weak reference + if let Some(msr) = instance.upgrade() { + if msr.is_available() { + return Some(msr); + } + } + + // Create new instance with provided units let msr = Arc::new(Self { inner: MsrImpl::new(units) }); if msr.is_available() { diff --git a/bin/minerd/src/main.rs b/bin/minerd/src/main.rs index 3e17d7a26..62ae70aaf 100644 --- a/bin/minerd/src/main.rs +++ b/bin/minerd/src/main.rs @@ -32,7 +32,11 @@ use darkfi_sdk::{ pasta::pallas, }; -use minerd::{benchmark::benchmark, hw::cpuid::CpuInfo, MinerNodeConfig, Minerd}; +use minerd::{ + benchmark::benchmark, + hw::{cpuid::CpuInfo, RxMsr}, + MinerNodeConfig, Minerd, +}; const CONFIG_FILE: &str = "minerd.toml"; const CONFIG_FILE_CONTENTS: &str = include_str!("../minerd.toml"); @@ -65,6 +69,10 @@ struct Args { /// PoW miner number of threads to use threads: usize, + #[structopt(long)] + /// Assign full L3 cache to CPU + cache_qos: bool, + #[structopt(short, long, default_value = "2")] /// Polling rate to ask darkfid for mining jobs polling_rate: u64, @@ -88,6 +96,10 @@ struct Args { #[structopt(long)] /// Print CPU information cpuid: bool, + + #[structopt(long)] + /// Perform oneshot hashrate boost ops + boost: bool, } #[derive(Clone, Debug, Deserialize, StructOpt, StructOptToml)] @@ -166,6 +178,11 @@ async fn realmain(args: Args, ex: ExecutorPtr) -> Result<()> { return Ok(()) } + if args.boost { + let mut rxmsr = RxMsr::new(); + rxmsr.init(args.cache_qos, args.threads, true); + } + // Run system hashrate benchmark if requested if let Some(nonces) = args.bench { return benchmark(!args.light_mode, args.large_pages, args.secure, args.threads, nonces)