Skip to content

Commit aa515f2

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 0988ff5 commit aa515f2

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::parade_retimer;
5456
use crate::power;
@@ -738,6 +740,22 @@ fn print_versions(ec: &CrosEc) {
738740
}
739741
#[cfg(feature = "hidapi")]
740742
print_dp_hdmi_details(false);
743+
744+
#[cfg(target_os = "linux")]
745+
for i in 0..4 {
746+
let device = format!("/dev/nvme{i}");
747+
match nvme::get_nvme_firmware_version(&device) {
748+
Ok(dev) => {
749+
println!("NVMe Device: {}", device);
750+
println!(" Model Number: {}", dev.model_number);
751+
println!(" Firmware Version: {}", dev.firmware_version);
752+
}
753+
Err(_e) => {
754+
// TODO: Maybe print errors but ignore "Not such file or directory"
755+
// eprintln!("Failed to get firmware version for {}: {}", device, e);
756+
}
757+
}
758+
}
741759
}
742760

743761
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')
54+
.trim()
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)