use nvme_cli_sys::{nvme_admin_cmd, nvme_admin_opcode::nvme_admin_get_log_page};
use serde::Serialize;
use std::fs::OpenOptions;
use std::io;
use std::mem::{size_of, zeroed};
use std::os::unix::io::AsRawFd;
#[repr(C, packed)]
#[derive(Debug, Copy, Clone)]
pub struct OcpSmartExtendedLog {
pub physical_media_units_written: [u8; 16],
pub physical_media_units_read: [u8; 16],
pub bad_user_nand_blocks_raw: [u8; 6],
pub bad_user_nand_blocks_normalized: u16,
pub bad_system_nand_blocks_raw: [u8; 6],
pub bad_system_nand_blocks_normalized: u16,
pub xor_recovery_count: u64,
pub uncorrectable_read_err_count: u64,
pub soft_ecc_err_count: u64,
pub end_to_end_detected_err: u32,
pub end_to_end_corrected_err: u32,
pub system_data_used_percent: u8,
pub refresh_counts: [u8; 7],
pub user_data_erase_count_max: u32,
pub user_data_erase_count_min: u32,
pub thermal_throttling_event_count: u8,
pub thermal_throttling_current_status: u8,
pub dssd_errata_version: u8,
pub dssd_point_version: [u8; 2],
pub dssd_minor_version: [u8; 2],
pub dssd_major_version: u8,
pub pcie_correctable_err_count: u64,
pub incomplete_shutdowns: u32,
rsvd116: [u8; 4],
pub percent_free_blocks: u8,
rsvd121: [u8; 7],
pub capacitor_health: u16,
pub nvme_base_errata_version: u8,
pub nvme_cmdset_errata_version: u8,
pub nvme_over_pcie_errata_version: u8,
pub nvme_mi_errata_version: u8,
rsvd134: [u8; 2],
pub unaligned_io: u64,
pub security_version: u64,
pub total_nuse: u64,
pub plp_start_count: [u8; 16],
pub endurance_estimate: [u8; 16],
pub pcie_link_retraining_count: u64,
pub power_state_change_count: u64,
pub lowest_permitted_fw_rev: u64,
pub total_media_dies: u16,
pub total_die_failure_tolerance: u16,
pub media_dies_offline: u16,
pub max_temperature_recorded: u8,
rsvd223: u8,
pub nand_avg_erase_count: u64,
pub command_timeouts: u32,
pub sys_area_program_fail_count_raw: u32,
pub sys_area_program_fail_count_normalized: u8,
rsvd241: [u8; 3],
pub sys_area_uncorr_read_count_raw: u32,
pub sys_area_uncorr_read_count_normalized: u8,
rsvd249: [u8; 3],
pub sys_area_erase_fail_count_raw: u32,
pub sys_area_erase_fail_count_normalized: u8,
rsvd257: [u8; 3],
pub max_peak_power_capability: u16,
pub current_max_avg_power: u16,
pub lifetime_power_consumed: [u8; 6],
pub dssd_firmware_revision: [u8; 8],
pub dssd_firmware_build_uuid: [u8; 16],
pub dssd_firmware_build_label: [u8; 64],
rsvd358: [u8; 136],
pub log_page_version: u16,
pub log_page_guid: [u8; 16],
}
const _: () = assert!(size_of::<OcpSmartExtendedLog>() == 512);
#[derive(Debug, Serialize)]
pub struct OcpSmartData {
pub nvme_name: String,
pub serial_number: String,
pub physical_media_units_written: u128,
pub physical_media_units_read: u128,
pub bad_user_nand_blocks_raw: u64,
pub bad_user_nand_blocks_normalized: u16,
pub bad_system_nand_blocks_raw: u64,
pub bad_system_nand_blocks_normalized: u16,
pub xor_recovery_count: u64,
pub uncorrectable_read_errors: u64,
pub soft_ecc_errors: u64,
pub e2e_errors_detected: u32,
pub e2e_errors_corrected: u32,
pub system_data_percent_used: u8,
pub user_data_erase_count_max: u32,
pub user_data_erase_count_min: u32,
pub nand_avg_erase_count: u64,
pub thermal_throttling_events: u8,
pub thermal_throttling_status: u8,
pub max_temperature_recorded: u8,
pub pcie_correctable_errors: u64,
pub pcie_link_retraining_count: u64,
pub incomplete_shutdowns: u32,
pub power_state_changes: u64,
pub percent_free_blocks: u8,
pub capacitor_health: u16,
pub unaligned_io: u64,
pub command_timeouts: u32,
pub security_version: u64,
pub lowest_permitted_fw_rev: u64,
pub total_nuse: u64,
pub plp_start_count: u128,
pub endurance_estimate: u128,
pub total_media_dies: u16,
pub total_die_failure_tolerance: u16,
pub media_dies_offline: u16,
pub sys_area_program_fail_count_raw: u32,
pub sys_area_program_fail_count_normalized: u8,
pub sys_area_uncorr_read_count_raw: u32,
pub sys_area_uncorr_read_count_normalized: u8,
pub sys_area_erase_fail_count_raw: u32,
pub sys_area_erase_fail_count_normalized: u8,
pub max_peak_power_capability: u16,
pub current_max_avg_power: u16,
pub lifetime_power_consumed: u64,
pub dssd_firmware_revision: String,
pub log_page_version: u16,
}
impl OcpSmartData {
pub fn new(nvme_name: String, serial_number: String, raw: &OcpSmartExtendedLog) -> Self {
let media_written = u128::from_le_bytes(raw.physical_media_units_written);
let media_read = u128::from_le_bytes(raw.physical_media_units_read);
let plp_count = u128::from_le_bytes(raw.plp_start_count);
let endurance = u128::from_le_bytes(raw.endurance_estimate);
let bad_user_blocks = u64::from_le_bytes([
raw.bad_user_nand_blocks_raw[0],
raw.bad_user_nand_blocks_raw[1],
raw.bad_user_nand_blocks_raw[2],
raw.bad_user_nand_blocks_raw[3],
raw.bad_user_nand_blocks_raw[4],
raw.bad_user_nand_blocks_raw[5],
0,
0,
]);
let bad_system_blocks = u64::from_le_bytes([
raw.bad_system_nand_blocks_raw[0],
raw.bad_system_nand_blocks_raw[1],
raw.bad_system_nand_blocks_raw[2],
raw.bad_system_nand_blocks_raw[3],
raw.bad_system_nand_blocks_raw[4],
raw.bad_system_nand_blocks_raw[5],
0,
0,
]);
let lifetime_power = u64::from_le_bytes([
raw.lifetime_power_consumed[0],
raw.lifetime_power_consumed[1],
raw.lifetime_power_consumed[2],
raw.lifetime_power_consumed[3],
raw.lifetime_power_consumed[4],
raw.lifetime_power_consumed[5],
0,
0,
]);
let fw_rev = String::from_utf8_lossy(&raw.dssd_firmware_revision)
.trim_end_matches('\0')
.trim()
.to_string();
Self {
nvme_name,
serial_number,
physical_media_units_written: media_written,
physical_media_units_read: media_read,
bad_user_nand_blocks_raw: bad_user_blocks,
bad_user_nand_blocks_normalized: raw.bad_user_nand_blocks_normalized,
bad_system_nand_blocks_raw: bad_system_blocks,
bad_system_nand_blocks_normalized: raw.bad_system_nand_blocks_normalized,
xor_recovery_count: raw.xor_recovery_count,
uncorrectable_read_errors: raw.uncorrectable_read_err_count,
soft_ecc_errors: raw.soft_ecc_err_count,
e2e_errors_detected: raw.end_to_end_detected_err,
e2e_errors_corrected: raw.end_to_end_corrected_err,
system_data_percent_used: raw.system_data_used_percent,
user_data_erase_count_max: raw.user_data_erase_count_max,
user_data_erase_count_min: raw.user_data_erase_count_min,
nand_avg_erase_count: raw.nand_avg_erase_count,
thermal_throttling_events: raw.thermal_throttling_event_count,
thermal_throttling_status: raw.thermal_throttling_current_status,
max_temperature_recorded: raw.max_temperature_recorded,
pcie_correctable_errors: raw.pcie_correctable_err_count,
pcie_link_retraining_count: raw.pcie_link_retraining_count,
incomplete_shutdowns: raw.incomplete_shutdowns,
power_state_changes: raw.power_state_change_count,
percent_free_blocks: raw.percent_free_blocks,
capacitor_health: raw.capacitor_health,
unaligned_io: raw.unaligned_io,
command_timeouts: raw.command_timeouts,
security_version: raw.security_version,
lowest_permitted_fw_rev: raw.lowest_permitted_fw_rev,
total_nuse: raw.total_nuse,
plp_start_count: plp_count,
endurance_estimate: endurance,
total_media_dies: raw.total_media_dies,
total_die_failure_tolerance: raw.total_die_failure_tolerance,
media_dies_offline: raw.media_dies_offline,
sys_area_program_fail_count_raw: raw.sys_area_program_fail_count_raw,
sys_area_program_fail_count_normalized: raw.sys_area_program_fail_count_normalized,
sys_area_uncorr_read_count_raw: raw.sys_area_uncorr_read_count_raw,
sys_area_uncorr_read_count_normalized: raw.sys_area_uncorr_read_count_normalized,
sys_area_erase_fail_count_raw: raw.sys_area_erase_fail_count_raw,
sys_area_erase_fail_count_normalized: raw.sys_area_erase_fail_count_normalized,
max_peak_power_capability: raw.max_peak_power_capability,
current_max_avg_power: raw.current_max_avg_power,
lifetime_power_consumed: lifetime_power,
dssd_firmware_revision: fw_rev,
log_page_version: raw.log_page_version,
}
}
}
pub fn read_ocp_smart_log(dev_path: &str) -> io::Result<OcpSmartExtendedLog> {
let file = OpenOptions::new().read(true).write(true).open(dev_path)?;
let fd = file.as_raw_fd();
let mut log: OcpSmartExtendedLog = unsafe { zeroed() };
let log_ptr = &mut log as *mut OcpSmartExtendedLog as u64;
let log_len = size_of::<OcpSmartExtendedLog>() as u32;
let log_id: u8 = 0xC0; let numd: u32 = log_len / 4 - 1;
let cdw10: u32 = (log_id as u32) | (numd << 16);
let mut cmd: nvme_admin_cmd = unsafe { zeroed() };
cmd.opcode = nvme_admin_get_log_page as u8;
cmd.nsid = 0xFFFF_FFFF;
cmd.addr = log_ptr;
cmd.data_len = log_len;
cmd.cdw10 = cdw10;
cmd.cdw11 = 0;
cmd.timeout_ms = 1000;
let ret = unsafe { nvme_cli_sys::nvme_ioctl_admin_cmd(fd, &mut cmd) };
match ret {
Ok(0) => {
const OCP_GUID: [u8; 16] = [
0xC5, 0xAF, 0x10, 0x28, 0xEA, 0xBF, 0xF2, 0xA4, 0x9C, 0x4F, 0x6F, 0x7C, 0xC9, 0x14,
0xD5, 0xAF,
];
if log.log_page_guid == [0u8; 16] {
return Err(io::Error::other(
"Device does not support OCP extended SMART log (invalid GUID)",
));
}
if log.log_page_guid != OCP_GUID {
return Err(io::Error::other(format!(
"Device does not support OCP extended SMART log (unexpected GUID: {:02X?})",
log.log_page_guid
)));
}
Ok(log)
}
Ok(status) => Err(io::Error::other(format!(
"OCP SMART log command failed, status={:#x}",
status
))),
Err(e) => Err(io::Error::other(e.to_string())),
}
}