Skip to content

Commit 0207842

Browse files
committed
Dump NVMe firwmare version on Linux
Just temporary, should get it from libsmartmon once that's ready so that we can get it for a broader range of devices and on more operating systems (Windows, FreeBSD). Signed-off-by: Daniel Schaefer <[email protected]>
1 parent eec1354 commit 0207842

File tree

3 files changed

+105
-0
lines changed

3 files changed

+105
-0
lines changed

framework_lib/src/commandline/mod.rs

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,8 @@ use crate::ec_binary;
4949
use crate::esrt;
5050
#[cfg(feature = "rusb")]
5151
use crate::inputmodule::check_inputmodule_version;
52+
#[cfg(target_os = "linux")]
53+
use crate::nvme;
5254
use crate::os_specific;
5355
use crate::power;
5456
use crate::smbios;
@@ -722,6 +724,22 @@ fn print_versions(ec: &CrosEc) {
722724
}
723725
#[cfg(feature = "hidapi")]
724726
print_dp_hdmi_details(false);
727+
728+
#[cfg(target_os = "linux")]
729+
for i in 0..4 {
730+
let device = format!("/dev/nvme{i}");
731+
match nvme::get_nvme_firmware_version(&device) {
732+
Ok(dev) => {
733+
println!("NVMe Device: {}", device);
734+
println!(" Model Number: {}", dev.model_number);
735+
println!(" Firmware Version: {}", dev.firmware_version);
736+
}
737+
Err(_e) => {
738+
// TODO: Maybe print errors but ignore "Not such file or directory"
739+
// eprintln!("Failed to get firmware version for {}: {}", device, e);
740+
}
741+
}
742+
}
725743
}
726744

727745
fn print_esrt() {

framework_lib/src/lib.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,8 @@ pub mod audio_card;
1919
pub mod camera;
2020
#[cfg(feature = "rusb")]
2121
pub mod inputmodule;
22+
#[cfg(target_os = "linux")]
23+
pub mod nvme;
2224
#[cfg(feature = "hidapi")]
2325
pub mod touchpad;
2426
#[cfg(feature = "hidapi")]

framework_lib/src/nvme.rs

Lines changed: 85 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,85 @@
1+
use std::error::Error;
2+
use std::fs::File;
3+
use std::os::unix::io::AsRawFd;
4+
5+
use nix::ioctl_readwrite;
6+
7+
// Constants from the NVMe specification for the Identify Controller data structure.
8+
const IDENTIFY_BUFFER_SIZE: usize = 4096;
9+
const MODEL_NUMBER_OFFSET: usize = 24;
10+
const MODEL_NUMBER_LEN: usize = 40;
11+
const FIRMWARE_REV_OFFSET: usize = 64;
12+
const FIRMWARE_REV_LEN: usize = 8;
13+
14+
// NVMe Admin Command opcodes and parameters.
15+
const NVME_ADMIN_IDENTIFY_OPCODE: u8 = 0x06;
16+
const NVME_IDENTIFY_CNS_CTRL: u32 = 0x01; // CNS value for "Identify Controller"
17+
18+
#[repr(C)]
19+
#[derive(Debug, Default)]
20+
struct NvmeAdminCmd {
21+
opcode: u8,
22+
flags: u8,
23+
rsvd1: u16,
24+
nsid: u32,
25+
cdw2: u32,
26+
cdw3: u32,
27+
metadata: u64,
28+
addr: u64,
29+
metadata_len: u32,
30+
data_len: u32,
31+
cdw10: u32,
32+
cdw11: u32,
33+
cdw12: u32,
34+
cdw13: u32,
35+
cdw14: u32,
36+
cdw15: u32,
37+
timeout_ms: u32,
38+
result: u32,
39+
}
40+
41+
ioctl_readwrite!(nvme_admin_cmd_ioctl, b'N', 0x41, NvmeAdminCmd);
42+
43+
/// A struct to hold the retrieved device information.
44+
#[derive(Debug)]
45+
pub struct NvmeInfo {
46+
pub model_number: String,
47+
pub firmware_version: String,
48+
}
49+
50+
fn parse_string(buffer: &[u8], offset: usize, len: usize) -> String {
51+
let bytes = &buffer[offset..offset + len];
52+
String::from_utf8_lossy(bytes)
53+
.trim_end_matches('\0') // Remove null padding
54+
.trim() // Remove leading/trailing whitespace
55+
.to_string()
56+
}
57+
58+
/// Sends an NVMe Identify Controller command and returns the firmware version.
59+
pub fn get_nvme_firmware_version(device_path: &str) -> Result<NvmeInfo, Box<dyn Error>> {
60+
let file = File::open(device_path)
61+
.map_err(|e| format!("Failed to open NVMe device {}: {}", device_path, e))?;
62+
let fd = file.as_raw_fd();
63+
64+
let mut buffer = vec![0u8; IDENTIFY_BUFFER_SIZE];
65+
let mut cmd = NvmeAdminCmd {
66+
opcode: NVME_ADMIN_IDENTIFY_OPCODE,
67+
addr: buffer.as_mut_ptr() as u64,
68+
data_len: buffer.len() as u32,
69+
cdw10: NVME_IDENTIFY_CNS_CTRL,
70+
..Default::default()
71+
};
72+
73+
let status = unsafe { nvme_admin_cmd_ioctl(fd, &mut cmd)? };
74+
if status != 0 {
75+
return Err(format!("NVMe command failed with status code: {:#x}", status).into());
76+
}
77+
78+
let model_number = parse_string(&buffer, MODEL_NUMBER_OFFSET, MODEL_NUMBER_LEN);
79+
let firmware_version = parse_string(&buffer, FIRMWARE_REV_OFFSET, FIRMWARE_REV_LEN);
80+
81+
Ok(NvmeInfo {
82+
model_number,
83+
firmware_version,
84+
})
85+
}

0 commit comments

Comments
 (0)