minerd: Add Linux MSR operations impl

This commit is contained in:
x
2025-12-23 10:39:43 +00:00
parent 03d12789bc
commit f2363a806b
8 changed files with 703 additions and 2 deletions

7
Cargo.lock generated
View File

@@ -4094,9 +4094,9 @@ checksum = "09edd9e8b54e49e587e4f6295a7d29c3ea94d469cb40ab8ca70b288248a81db2"
[[package]]
name = "libc"
version = "0.2.177"
version = "0.2.178"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2874a2af47a2325c2001a6e6fad9b16a53b802102b528163885171cf92b15976"
checksum = "37c93d8daa9d8a012fd8ab92f088405fb202ea0b6ab73ee2482ae66af4f42091"
[[package]]
name = "libloading"
@@ -4318,7 +4318,10 @@ dependencies = [
"darkfi-sdk",
"darkfi-serial",
"easy-parallel",
"libc",
"num-bigint",
"once_cell",
"parking_lot 0.12.5",
"randomx",
"serde",
"signal-hook",

View File

@@ -9,6 +9,10 @@ license = "AGPL-3.0-only"
edition = "2021"
[dependencies]
libc = "0.2.178"
parking_lot = "0.12.5"
once_cell = "1.21.3"
# Darkfi
darkfi = {path = "../../", features = ["async-daemonize", "validator", "rpc", "bs58"]}
darkfi-sdk = {path = "../../src/sdk"}

20
bin/minerd/src/hw/mod.rs Normal file
View File

@@ -0,0 +1,20 @@
/* This file is part of DarkFi (https://dark.fi)
*
* Copyright (C) 2020-2025 Dyne.org foundation
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
/// Model-Specific Registers
pub mod msr;

View File

@@ -0,0 +1,85 @@
/* This file is part of DarkFi (https://dark.fi)
*
* Copyright (C) 2020-2025 Dyne.org foundation
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
use std::{fmt, io};
pub type MsrResult<T> = Result<T, MsrError>;
/// Errors that can occur during MSR operations
#[derive(Debug)]
pub enum MsrError {
/// MSR module/driver is not available
NotAvailable(String),
/// Failed to read MSR
ReadError { reg: u32, cpu: i32, source: io::Error },
/// Failed to write MSR
WriteError { reg: u32, cpu: i32, source: io::Error },
/// No CPU units available
NoCpuUnits,
/// Permission denied
PermissionDenied(String),
/// Driver installation failed (Windows)
DriverError(String),
/// Generic IO error
Io(io::Error),
/// Platform not supported
PlatformNotSupported,
}
impl fmt::Display for MsrError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
MsrError::NotAvailable(msg) => write!(f, "MSR not available: {}", msg),
MsrError::ReadError { reg, cpu, source } => {
write!(f, "Failed to read MSR 0x{:08x} on CPU {}: {}", reg, cpu, source)
}
MsrError::WriteError { reg, cpu, source } => {
write!(f, "Failed to write MSR 0x{:08x} on CPU {}: {}", reg, cpu, source)
}
MsrError::NoCpuUnits => write!(f, "No CPU units available"),
MsrError::PermissionDenied(msg) => write!(f, "Permission denied: {}", msg),
MsrError::DriverError(msg) => write!(f, "Driver error: {}", msg),
MsrError::Io(err) => write!(f, "IO error: {}", err),
MsrError::PlatformNotSupported => write!(f, "Platform not supported"),
}
}
}
impl std::error::Error for MsrError {
fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
match self {
MsrError::ReadError { source, .. } => Some(source),
MsrError::WriteError { source, .. } => Some(source),
MsrError::Io(err) => Some(err),
_ => None,
}
}
}
impl From<io::Error> for MsrError {
fn from(err: io::Error) -> Self {
MsrError::Io(err)
}
}

View File

@@ -0,0 +1,214 @@
/* This file is part of DarkFi (https://dark.fi)
*
* Copyright (C) 2020-2025 Dyne.org foundation
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
//! Cross-platform module for reading and writing CPU Model Specific Registers
//!
//! ## Platform support
//!
//! - Linux: Uses `/dev/cpu*/msr` interface with automatic module loading
//! - Windows: Uses WinRing0 driver with automatic installation
//!
//! ## Requirements
//!
//! - Linux: root privileges and the `msr` kernel module
//! - Windows: Administrator privileges and `WinRing0x64.sys`
use std::sync::{Arc, Weak};
use once_cell::sync::Lazy;
use parking_lot::Mutex;
mod error;
mod msr_item;
use error::{MsrError, MsrResult};
use msr_item::MsrItem;
#[cfg(target_os = "linux")]
mod msr_linux;
/*
#[cfg(target_os = "windows")]
mod msr_win;
*/
#[cfg(target_os = "linux")]
use msr_linux::MsrImpl;
#[cfg(not(any(target_os = "linux")))]
use unsupported::MsrImpl;
/// Global weak reference to the MSR singleton
static INSTANCE: Lazy<Mutex<Weak<Msr>>> = Lazy::new(|| Mutex::new(Weak::new()));
/// MSR Interface
pub struct Msr {
inner: MsrImpl,
}
impl Msr {
/// Get or create the MSR singleton
///
/// Returns `None` if MSR is not available on this system.
pub fn get(units: Vec<i32>) -> Option<Arc<Self>> {
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
let msr = Arc::new(Self { inner: MsrImpl::new(units) });
if msr.is_available() {
*instance = Arc::downgrade(&msr);
Some(msr)
} else {
None
}
}
/// Create a new MSR instance without using the singleton.
///
/// Useful for testing or when multiple independent instances are needed.
pub fn new_instance(units: Vec<i32>) -> Self {
Self { inner: MsrImpl::new(units) }
}
/// Check if MSR operations are available
pub fn is_available(&self) -> bool {
self.inner.is_available()
}
/// Get the CPU units this MSR operates on
pub fn units(&self) -> &[i32] {
self.inner.units()
}
/// Write an MsrItem to a CPU
///
/// * `item`: The MSR item to write
/// * `cpu`: CPU index (-1 for default/first)
/// * `verbose`: Log warnings on failure
pub fn write_item(&self, item: &MsrItem, cpu: i32, verbose: bool) -> bool {
self.inner.write_item(item, cpu, verbose)
}
/// Write to an MSR register with optional mask
///
/// If a mask is provided, this performs a read-modify-write
///
/// * `reg`: MSR register address
/// * `value`: Value to write
/// * `cpu`: CPU index (-1 for default/first)
/// * `mask`: Bit mask for partial updates (use NO_MASK for full write)
/// * `verbose`: Log warnings on failure
pub fn write(&self, reg: u32, value: u64, cpu: i32, mask: u64, verbose: bool) -> bool {
self.inner.write(reg, value, cpu, mask, verbose)
}
/// Execute a callback for each CPU unit
///
/// The callback receives the CPU ID and should return `true` to continue
/// or `false` to abort.
pub fn write_all<F>(&self, callback: F) -> bool
where
F: FnMut(i32) -> bool + Send,
{
self.inner.write_all(callback)
}
/// Read an MSR register and return as MsrItem
///
/// * `reg`: MSR register address
/// * `cpu`: CPU index (-1 for default/first)
/// * `verbose`: Log warnings on failure
pub fn read(&self, reg: u32, cpu: i32, verbose: bool) -> Option<MsrItem> {
self.inner.read(reg, cpu, verbose)
}
/// Low-level MSR read
///
/// Returns the raw value or error
pub fn rdmsr(&self, reg: u32, cpu: i32) -> MsrResult<u64> {
self.inner.rdmsr(reg, cpu)
}
/// Low-level MSR write
///
/// Writes the value directly without masking
pub fn wrmsr(&self, reg: u32, value: u64, cpu: i32) -> MsrResult<()> {
self.inner.wrmsr(reg, value, cpu)
}
}
/// Impls for unsupported platforms.
#[cfg(not(any(target_os = "linux")))]
mod unsupported {
use super::*;
use tracing::warn;
pub struct MsrImpl {
units: Vec<i32>,
}
impl MsrImpl {
pub fn new(units: Vec<i32>) -> Self {
warn!("[msr] MSR not supported on this platform");
Self { units }
}
pub fn is_available(&self) -> bool {
false
}
pub fn units(&self) -> &[i32] {
&self.units
}
pub fn write_all<F>(&self, _callback: F) -> bool
where
F: FnMut(i32) -> bool,
{
false
}
pub fn rdmsr(&self, reg: u32, cpu: i32) -> MsrResult<u64> {
Err(crate::error::MsrError::PlatformNotSupported)
}
pub fn wrmsr(&self, _reg: u32, _value: u64, _cpu: i32) -> MsrResult<()> {
Err(crate::error::MsrError::PlatformNotSupported)
}
pub fn write(&self, _reg: u32, _value: u64, _cpu: i32, _mask: u64, _verbose: bool) -> bool {
false
}
pub fn write_item(&self, _item: &MsrItem, _cpu: i32, _verbose: bool) -> bool {
false
}
pub fn read(&self, _reg: u32, _cpu: i32, _verbose: bool) -> Option<MsrItem> {
None
}
}
}

View File

@@ -0,0 +1,175 @@
/* This file is part of DarkFi (https://dark.fi)
*
* Copyright (C) 2020-2025 Dyne.org foundation
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
use std::{fmt, str::FromStr};
/// Sentinel value indicating no mask should be applied
pub const NO_MASK: u64 = u64::MAX;
/// Represents a single MSR operation.
///
/// Contains the register address, value to write/read, and an optional
/// mask for partial register updates.
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct MsrItem {
/// The MSR register address
reg: u32,
/// The value to read/write
value: u64,
/// Mask for partial updates (`NO_MASK` means full update)
mask: u64,
}
impl Default for MsrItem {
fn default() -> Self {
Self { reg: 0, value: 0, mask: NO_MASK }
}
}
impl MsrItem {
/// Create a new MsrItem with optional mask
pub const fn new(reg: u32, value: u64) -> Self {
Self { reg, value, mask: NO_MASK }
}
/// Create a new MsrItem with a specific mask
pub const fn with_mask(reg: u32, value: u64, mask: u64) -> Self {
Self { reg, value, mask }
}
/// Check if this item is valid (register > 0)
#[inline]
pub const fn is_valid(&self) -> bool {
self.reg > 0
}
/// Get the register address
#[inline]
pub const fn reg(&self) -> u32 {
self.reg
}
/// Get the value
#[inline]
pub const fn value(&self) -> u64 {
self.value
}
/// Get the mask
#[inline]
pub const fn mask(&self) -> u64 {
self.mask
}
/// Check if this item has a mask
#[inline]
pub const fn has_mask(&self) -> bool {
self.mask != NO_MASK
}
/// Apply mask to combine old and new values.
///
/// The masked bits from `new_value` replace the corresponding bits in
/// `old_value`, while unmasked bits retain the `old_value`.
///
/// ```text
/// let old = 0xFF00_FF00;
/// let new = 0x1234_5678;
/// let mask = 0xFFFF_0000;
///
/// // Upper 16 bits from new, lower 16 bits from old:
/// assert_eq!(MsrItem::masked_value(old, new, mask), 0x1234_FF00);
/// ```
#[inline]
pub const fn masked_value(old_value: u64, new_value: u64, mask: u64) -> u64 {
(new_value & mask) | (old_value & !mask)
}
/// Set the value, useful for updating after a read
#[inline]
pub fn set_value(&mut self, value: u64) {
self.value = value;
}
}
/// Error type for parsing MsrItem from a string
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct ParseMsrItemError {
pub message: String,
}
impl fmt::Display for ParseMsrItemError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "Failed to parse MsrItem: {}", self.message)
}
}
impl std::error::Error for ParseMsrItemError {}
impl FromStr for MsrItem {
type Err = ParseMsrItemError;
/// Parse an MsrItem from a string in format `REG:VALUE` or `REG:VALUE:MASK`
///
/// Values can be decimal or hexadecimal.
fn from_str(s: &str) -> Result<Self, Self::Err> {
let parts: Vec<&str> = s.split(':').collect();
if parts.len() < 2 {
return Err(ParseMsrItemError {
message: "Expected format REG:VALUE or REG:VALUE:MASK".to_string(),
})
}
let reg = parse_number(parts[0])
.map_err(|e| ParseMsrItemError { message: format!("Invalid register: {}", e) })?
as u32;
let value = parse_number(parts[1])
.map_err(|e| ParseMsrItemError { message: format!("Invalid value: {}", e) })?;
let mask = if parts.len() > 2 {
parse_number(parts[2])
.map_err(|e| ParseMsrItemError { message: format!("Invalid mask: {}", e) })?
} else {
NO_MASK
};
Ok(Self { reg, value, mask })
}
}
/// Parse a number from string, supporting decimal and hexadecimal
fn parse_number(s: &str) -> Result<u64, std::num::ParseIntError> {
let s = s.trim();
if let Some(hex) = s.strip_prefix("0x").or_else(|| s.strip_prefix("0X")) {
u64::from_str_radix(hex, 16)
} else {
s.parse::<u64>()
}
}
impl fmt::Display for MsrItem {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
if self.mask != NO_MASK {
write!(f, "0x{:x}:0x{:x}:0x{:x}", self.reg, self.value, self.mask)
} else {
write!(f, "0x{:x}:0x{:x}", self.reg, self.value)
}
}
}

View File

@@ -0,0 +1,197 @@
/* This file is part of DarkFi (https://dark.fi)
*
* Copyright (C) 2020-2025 Dyne.org foundation
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
//! Linux-specific MSR implementation using `/dev/cpu/*/msr`
use std::{
fs::{File, OpenOptions},
io::{self, Write},
os::fd::AsRawFd,
process::Command,
};
use tracing::warn;
use super::{msr_item::NO_MASK, MsrError, MsrItem, MsrResult};
pub struct MsrImpl {
available: bool,
units: Vec<i32>,
}
impl MsrImpl {
/// Create a new Linux MSR interface
pub fn new(units: Vec<i32>) -> Self {
let available = Self::msr_allow_writes() || Self::msr_modprobe();
if !available {
warn!("[msr] MSR kernel module not available");
}
Self { available, units }
}
/// Check if MSR operations are available
pub fn is_available(&self) -> bool {
self.available
}
/// Get the CPU units
pub fn units(&self) -> &[i32] {
&self.units
}
/// Execute a callback for each CPU unit
pub fn write_all<F>(&self, mut callback: F) -> bool
where
F: FnMut(i32) -> bool,
{
for &cpu in &self.units {
if !callback(cpu) {
return false;
}
}
true
}
/// Read an MSR register
pub fn rdmsr(&self, reg: u32, cpu: i32) -> MsrResult<u64> {
let cpu_id = self.resolve_cpu(cpu)?;
let path = format!("/dev/cpu/{}/msr", cpu_id);
let file =
File::open(&path).map_err(|e| MsrError::ReadError { reg, cpu: cpu_id, source: e })?;
let mut value = 0u64;
let bytes_read = unsafe {
libc::pread(
file.as_raw_fd(),
&mut value as *mut u64 as *mut libc::c_void,
std::mem::size_of::<u64>(),
reg as libc::off_t,
)
};
if bytes_read == std::mem::size_of::<u64>() as isize {
Ok(value)
} else {
Err(MsrError::ReadError { reg, cpu: cpu_id, source: io::Error::last_os_error() })
}
}
/// Write an MSR register
pub fn wrmsr(&self, reg: u32, value: u64, cpu: i32) -> MsrResult<()> {
let cpu_id = self.resolve_cpu(cpu)?;
let path = format!("/dev/cpu/{}/msr", cpu_id);
let file = OpenOptions::new()
.write(true)
.open(&path)
.map_err(|e| MsrError::WriteError { reg, cpu: cpu_id, source: e })?;
let bytes_written = unsafe {
libc::pwrite(
file.as_raw_fd(),
&value as *const u64 as *const libc::c_void,
std::mem::size_of::<u64>(),
reg as libc::off_t,
)
};
if bytes_written == std::mem::size_of::<u64>() as isize {
Ok(())
} else {
Err(MsrError::WriteError { reg, cpu: cpu_id, source: io::Error::last_os_error() })
}
}
/// Write MSR with mask support (read-modify-write)
pub fn write(&self, reg: u32, value: u64, cpu: i32, mask: u64, verbose: bool) -> bool {
let write_value = if mask != NO_MASK {
match self.rdmsr(reg, cpu) {
Ok(old_value) => MsrItem::masked_value(old_value, value, mask),
Err(e) => {
if verbose {
warn!("[msr] Cannot read MSR 0x{:08x}: {}", reg, e);
}
return false
}
}
} else {
value
};
match self.wrmsr(reg, write_value, cpu) {
Ok(()) => true,
Err(e) => {
if verbose {
warn!("[msr] Cannot set MSR 0x{:08x} to 0x{:016x}: {}", reg, write_value, e);
}
false
}
}
}
/// Write an MsrItem
pub fn write_item(&self, item: &MsrItem, cpu: i32, verbose: bool) -> bool {
self.write(item.reg(), item.value(), cpu, item.mask(), verbose)
}
/// Read MSR and return as MsrItem
pub fn read(&self, reg: u32, cpu: i32, verbose: bool) -> Option<MsrItem> {
match self.rdmsr(reg, cpu) {
Ok(value) => Some(MsrItem::new(reg, value)),
Err(e) => {
if verbose {
warn!("[msr] Cannot read MSR 0x{:08x}: {}", reg, e);
}
None
}
}
}
/// Resolve CPU index (-1 means use first available)
fn resolve_cpu(&self, cpu: i32) -> MsrResult<i32> {
if cpu < 0 {
self.units.first().copied().ok_or(MsrError::NoCpuUnits)
} else {
Ok(cpu)
}
}
/// Try to enable MSR writes via sysfs
fn msr_allow_writes() -> bool {
OpenOptions::new()
.write(true)
.truncate(true)
.open("/sys/module/msr/parameters/allow_writes")
.and_then(|mut file| file.write_all(b"on"))
.is_ok()
}
/// Try to load MSR module via modprobe
fn msr_modprobe() -> bool {
Command::new("/sbin/modprobe")
.args(["msr", "allow_writes=on"])
.stdout(std::process::Stdio::null())
.stderr(std::process::Stdio::null())
.status()
.map(|status| status.success())
.unwrap_or(false)
}
}

View File

@@ -35,6 +35,9 @@ use darkfi_sdk::crypto::keypair::{Address, Keypair, Network, StandardAddress};
/// Miner benchmarking related methods
pub mod benchmark;
/// Hardware interfaces
pub mod hw;
/// darkfid JSON-RPC related methods
mod rpc;
use rpc::{polling_task, DarkfidRpcClient};