use libnvme_sys::{nvme_id_ctrl, nvme_id_ns, nvme_lbaf};
use crate::util::fixed_ascii_to_str;
use crate::Result;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct NvmeVersion {
pub major: u16,
pub minor: u8,
pub tertiary: u8,
}
impl NvmeVersion {
pub(crate) fn from_raw(ver: u32) -> Self {
NvmeVersion {
major: (ver >> 16) as u16,
minor: (ver >> 8) as u8,
tertiary: ver as u8,
}
}
}
impl std::fmt::Display for NvmeVersion {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "{}.{}.{}", self.major, self.minor, self.tertiary)
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct LbaFormat {
pub metadata_size: u16,
pub data_size_bytes: u32,
pub relative_performance: u8,
}
impl LbaFormat {
pub(crate) fn from_raw(raw: nvme_lbaf) -> Self {
let data_size_bytes = if raw.ds == 0 { 0 } else { 1u32 << raw.ds };
LbaFormat {
metadata_size: raw.ms,
data_size_bytes,
relative_performance: raw.rp & 0x3,
}
}
}
pub struct IdentifyController {
pub(crate) inner: Box<nvme_id_ctrl>,
}
impl IdentifyController {
pub fn vendor_id(&self) -> u16 {
self.inner.vid
}
pub fn subsystem_vendor_id(&self) -> u16 {
self.inner.ssvid
}
pub fn serial_number(&self) -> Result<&str> {
fixed_ascii_to_str(&self.inner.sn)
}
pub fn model_number(&self) -> Result<&str> {
fixed_ascii_to_str(&self.inner.mn)
}
pub fn firmware_revision(&self) -> Result<&str> {
fixed_ascii_to_str(&self.inner.fr)
}
pub fn ieee_oui(&self) -> [u8; 3] {
self.inner.ieee
}
pub fn max_data_transfer_size_exp(&self) -> u8 {
self.inner.mdts
}
pub fn controller_id(&self) -> u16 {
self.inner.cntlid
}
pub fn nvme_version(&self) -> NvmeVersion {
NvmeVersion::from_raw(self.inner.ver)
}
pub fn controller_type(&self) -> u8 {
self.inner.cntrltype
}
pub fn fru_guid(&self) -> [u8; 16] {
self.inner.fguid
}
pub fn optional_admin_command_support(&self) -> u16 {
self.inner.oacs
}
pub fn abort_command_limit(&self) -> u8 {
self.inner.acl
}
pub fn async_event_request_limit(&self) -> u8 {
self.inner.aerl
}
pub fn firmware_updates(&self) -> u8 {
self.inner.frmw
}
pub fn log_page_attributes(&self) -> u8 {
self.inner.lpa
}
pub fn error_log_page_entries(&self) -> u8 {
self.inner.elpe
}
pub fn num_power_states(&self) -> u8 {
self.inner.npss
}
pub fn warning_temp_threshold_kelvin(&self) -> u16 {
self.inner.wctemp
}
pub fn critical_temp_threshold_kelvin(&self) -> u16 {
self.inner.cctemp
}
pub fn host_memory_buffer_preferred_size(&self) -> u32 {
self.inner.hmpre
}
pub fn host_memory_buffer_min_size(&self) -> u32 {
self.inner.hmmin
}
pub fn total_nvm_capacity_bytes(&self) -> u128 {
u128::from_le_bytes(self.inner.tnvmcap)
}
pub fn unallocated_nvm_capacity_bytes(&self) -> u128 {
u128::from_le_bytes(self.inner.unvmcap)
}
pub fn submission_queue_entry_size(&self) -> u8 {
self.inner.sqes
}
pub fn completion_queue_entry_size(&self) -> u8 {
self.inner.cqes
}
pub fn max_commands_outstanding(&self) -> u16 {
self.inner.maxcmd
}
pub fn num_namespaces(&self) -> u32 {
self.inner.nn
}
}
impl std::fmt::Debug for IdentifyController {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("IdentifyController")
.field("vendor_id", &format_args!("0x{:04x}", self.vendor_id()))
.field("model_number", &self.model_number().ok())
.field("serial_number", &self.serial_number().ok())
.field("firmware_revision", &self.firmware_revision().ok())
.field("nvme_version", &self.nvme_version())
.field("num_namespaces", &self.num_namespaces())
.finish()
}
}
pub struct IdentifyNamespace {
pub(crate) inner: Box<nvme_id_ns>,
}
impl IdentifyNamespace {
pub fn size_lbas(&self) -> u64 {
self.inner.nsze
}
pub fn capacity_lbas(&self) -> u64 {
self.inner.ncap
}
pub fn utilization_lbas(&self) -> u64 {
self.inner.nuse
}
pub fn features(&self) -> u8 {
self.inner.nsfeat
}
pub fn num_lba_formats(&self) -> u8 {
self.inner.nlbaf
}
pub fn formatted_lba_size(&self) -> u8 {
self.inner.flbas
}
pub fn metadata_capabilities(&self) -> u8 {
self.inner.mc
}
pub fn data_protection_capabilities(&self) -> u8 {
self.inner.dpc
}
pub fn data_protection_setting(&self) -> u8 {
self.inner.dps
}
pub fn multipath_capabilities(&self) -> u8 {
self.inner.nmic
}
pub fn reservation_capabilities(&self) -> u8 {
self.inner.rescap
}
pub fn nvm_capacity_bytes(&self) -> u128 {
u128::from_le_bytes(self.inner.nvmcap)
}
pub fn atomic_write_unit_normal(&self) -> u16 {
self.inner.nawun
}
pub fn nguid(&self) -> [u8; 16] {
self.inner.nguid
}
pub fn eui64(&self) -> [u8; 8] {
self.inner.eui64
}
pub fn lba_format(&self, index: u8) -> Option<LbaFormat> {
let raw = *self.inner.lbaf.get(usize::from(index))?;
let format = LbaFormat::from_raw(raw);
if format.data_size_bytes == 0 {
None
} else {
Some(format)
}
}
pub fn current_lba_format(&self) -> Option<LbaFormat> {
let idx = self.formatted_lba_size() & 0x0F;
self.lba_format(idx)
}
}
impl std::fmt::Debug for IdentifyNamespace {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("IdentifyNamespace")
.field("size_lbas", &self.size_lbas())
.field("capacity_lbas", &self.capacity_lbas())
.field("utilization_lbas", &self.utilization_lbas())
.field("current_lba_format", &self.current_lba_format())
.finish()
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn version_decodes_bit_layout() {
let v = NvmeVersion::from_raw(0x00010402);
assert_eq!(v.major, 1);
assert_eq!(v.minor, 4);
assert_eq!(v.tertiary, 2);
assert_eq!(format!("{v}"), "1.4.2");
}
#[test]
fn version_decodes_nvme_2_0() {
let v = NvmeVersion::from_raw(0x00020000);
assert_eq!(v.major, 2);
assert_eq!(v.minor, 0);
assert_eq!(v.tertiary, 0);
}
#[test]
fn lba_format_decodes_exponent() {
let raw = nvme_lbaf {
ms: 0,
ds: 12, rp: 0,
};
let f = LbaFormat::from_raw(raw);
assert_eq!(f.data_size_bytes, 4096);
assert_eq!(f.relative_performance, 0);
}
#[test]
fn lba_format_zero_ds_yields_zero_size() {
let raw = nvme_lbaf {
ms: 0,
ds: 0,
rp: 0,
};
assert_eq!(LbaFormat::from_raw(raw).data_size_bytes, 0);
}
#[test]
fn lba_format_masks_relative_performance() {
let raw = nvme_lbaf {
ms: 0,
ds: 9,
rp: 0xFB, };
assert_eq!(LbaFormat::from_raw(raw).relative_performance, 0x03);
}
}