use std::ffi::c_void;
use libnvme_sys::{
nvme_copy, nvme_copy_args, nvme_copy_range, nvme_dsm, nvme_dsm_args, nvme_dsm_range, nvme_io,
nvme_io_args, nvme_io_passthru, nvme_ns_get_fd,
};
use crate::error::{check_ret, Error, Result};
use crate::namespace::Namespace;
const OPC_FLUSH: u8 = libnvme_sys::nvme_cmd_flush as u8;
const OPC_WRITE: u8 = libnvme_sys::nvme_cmd_write as u8;
const OPC_READ: u8 = libnvme_sys::nvme_cmd_read as u8;
const OPC_WRITE_UNCOR: u8 = libnvme_sys::nvme_cmd_write_uncor as u8;
const OPC_COMPARE: u8 = libnvme_sys::nvme_cmd_compare as u8;
const OPC_WRITE_ZEROES: u8 = libnvme_sys::nvme_cmd_write_zeroes as u8;
const OPC_VERIFY: u8 = libnvme_sys::nvme_cmd_verify as u8;
const CTRL_DTYPE_STREAMS: u16 = 1 << 4;
const CTRL_NSZ: u16 = 1 << 7;
const CTRL_STC: u16 = 1 << 8;
const CTRL_DEAC: u16 = 1 << 9;
const CTRL_PRINFO_PRCHK_REF: u16 = 1 << 10;
const CTRL_PRINFO_PRCHK_APP: u16 = 1 << 11;
const CTRL_PRINFO_PRCHK_GUARD: u16 = 1 << 12;
const CTRL_PRINFO_PRACT: u16 = 1 << 13;
const CTRL_FUA: u16 = 1 << 14;
const CTRL_LR: u16 = 1 << 15;
#[derive(Default, Clone)]
struct IoOpts {
control: u16,
dsm_hint: u8,
dspec: u16,
apptag: u16,
appmask: u16,
reftag: u32,
reftag_u64: u64,
storage_tag: u64,
sts: u8,
pif: u8,
timeout_ms: u32,
}
impl IoOpts {
fn apply_to(&self, args: &mut nvme_io_args) {
args.control = self.control;
args.dsm = self.dsm_hint;
args.dspec = self.dspec;
args.apptag = self.apptag;
args.appmask = self.appmask;
args.reftag = self.reftag;
args.reftag_u64 = self.reftag_u64;
args.storage_tag = self.storage_tag;
args.sts = self.sts;
args.pif = self.pif;
args.timeout = self.timeout_ms;
}
}
fn encode_nlb(nlb: u32) -> Result<u16> {
if nlb == 0 || nlb > 65_536 {
return Err(invalid(format!("nlb must be 1..=65536, got {nlb}")));
}
Ok((nlb - 1) as u16)
}
fn invalid(msg: impl Into<String>) -> Error {
Error::Os(std::io::Error::new(
std::io::ErrorKind::InvalidInput,
msg.into(),
))
}
fn ns_fd(ns: &Namespace<'_>) -> Result<std::os::raw::c_int> {
let fd = unsafe { nvme_ns_get_fd(ns_raw(ns)) };
if fd < 0 {
return Err(Error::Os(std::io::Error::last_os_error()));
}
Ok(fd)
}
fn ns_raw(ns: &Namespace<'_>) -> libnvme_sys::nvme_ns_t {
ns.raw_handle()
}
fn check_buf_len(buf_len: usize, nlb: u32, lba_size: u32) -> Result<()> {
let want = u64::from(nlb) * u64::from(lba_size);
if buf_len as u64 != want {
return Err(invalid(format!(
"buffer length {} bytes does not match nlb*lba_size = {}*{} = {}",
buf_len, nlb, lba_size, want
)));
}
Ok(())
}
fn base_args(
fd: std::os::raw::c_int,
nsid: u32,
slba: u64,
nlb_enc: u16,
data: *mut c_void,
data_len: u32,
) -> nvme_io_args {
let mut args: nvme_io_args = unsafe { std::mem::zeroed() };
args.args_size = std::mem::size_of::<nvme_io_args>() as i32;
args.fd = fd;
args.nsid = nsid;
args.slba = slba;
args.nlb = nlb_enc;
args.data = data;
args.data_len = data_len;
args
}
macro_rules! io_data_setters {
() => {
pub fn fua(mut self) -> Self {
self.opts.control |= CTRL_FUA;
self
}
pub fn limited_retry(mut self) -> Self {
self.opts.control |= CTRL_LR;
self
}
pub fn protection_action(mut self) -> Self {
self.opts.control |= CTRL_PRINFO_PRACT;
self
}
pub fn check_reftag(mut self) -> Self {
self.opts.control |= CTRL_PRINFO_PRCHK_REF;
self
}
pub fn check_apptag(mut self) -> Self {
self.opts.control |= CTRL_PRINFO_PRCHK_APP;
self
}
pub fn check_guard(mut self) -> Self {
self.opts.control |= CTRL_PRINFO_PRCHK_GUARD;
self
}
pub fn ref_tag(mut self, tag: u32) -> Self {
self.opts.reftag = tag;
self
}
pub fn ref_tag_u64(mut self, tag: u64) -> Self {
self.opts.reftag_u64 = tag;
self
}
pub fn app_tag(mut self, tag: u16) -> Self {
self.opts.apptag = tag;
self
}
pub fn app_mask(mut self, mask: u16) -> Self {
self.opts.appmask = mask;
self
}
pub fn dataset_mgmt(mut self, dsm: u8) -> Self {
self.opts.dsm_hint = dsm;
self
}
pub fn directive(mut self, dspec: u16) -> Self {
self.opts.dspec = dspec;
self
}
pub fn streams(mut self) -> Self {
self.opts.control |= CTRL_DTYPE_STREAMS;
self
}
pub fn storage_tag(mut self, tag: u64) -> Self {
self.opts.storage_tag = tag;
self
}
pub fn check_storage_tag(mut self) -> Self {
self.opts.control |= CTRL_STC;
self
}
pub fn storage_tag_size(mut self, sts: u8) -> Self {
self.opts.sts = sts;
self
}
pub fn pi_format(mut self, pif: u8) -> Self {
self.opts.pif = pif;
self
}
pub fn timeout_ms(mut self, ms: u32) -> Self {
self.opts.timeout_ms = ms;
self
}
};
}
pub struct Read<'a, 'r> {
ns: &'a Namespace<'r>,
slba: u64,
nlb: u32,
data: &'a mut [u8],
metadata: Option<&'a mut [u8]>,
opts: IoOpts,
}
impl<'a, 'r> Read<'a, 'r> {
pub(crate) fn new(ns: &'a Namespace<'r>, slba: u64, nlb: u32, data: &'a mut [u8]) -> Self {
Read {
ns,
slba,
nlb,
data,
metadata: None,
opts: IoOpts::default(),
}
}
pub fn metadata(mut self, md: &'a mut [u8]) -> Self {
self.metadata = Some(md);
self
}
io_data_setters!();
pub fn execute(mut self) -> Result<u32> {
check_buf_len(self.data.len(), self.nlb, self.ns.lba_size())?;
let nlb_enc = encode_nlb(self.nlb)?;
let fd = ns_fd(self.ns)?;
let data_ptr = self.data.as_mut_ptr() as *mut c_void;
let data_len = self.data.len() as u32;
let mut result: u32 = 0;
let mut args = base_args(fd, self.ns.nsid(), self.slba, nlb_enc, data_ptr, data_len);
if let Some(md) = self.metadata.as_deref_mut() {
args.metadata = md.as_mut_ptr() as *mut c_void;
args.metadata_len = md.len() as u32;
}
args.result = &mut result;
self.opts.apply_to(&mut args);
let ret = unsafe { nvme_io(&mut args, OPC_READ) };
check_ret(ret)?;
Ok(result)
}
}
pub struct Write<'a, 'r> {
ns: &'a Namespace<'r>,
slba: u64,
nlb: u32,
data: &'a [u8],
metadata: Option<&'a [u8]>,
opts: IoOpts,
}
impl<'a, 'r> Write<'a, 'r> {
pub(crate) fn new(ns: &'a Namespace<'r>, slba: u64, nlb: u32, data: &'a [u8]) -> Self {
Write {
ns,
slba,
nlb,
data,
metadata: None,
opts: IoOpts::default(),
}
}
pub fn metadata(mut self, md: &'a [u8]) -> Self {
self.metadata = Some(md);
self
}
io_data_setters!();
pub fn execute(self) -> Result<u32> {
check_buf_len(self.data.len(), self.nlb, self.ns.lba_size())?;
let nlb_enc = encode_nlb(self.nlb)?;
let fd = ns_fd(self.ns)?;
let data_ptr = self.data.as_ptr() as *mut c_void;
let data_len = self.data.len() as u32;
let mut result: u32 = 0;
let mut args = base_args(fd, self.ns.nsid(), self.slba, nlb_enc, data_ptr, data_len);
if let Some(md) = self.metadata {
args.metadata = md.as_ptr() as *mut c_void;
args.metadata_len = md.len() as u32;
}
args.result = &mut result;
self.opts.apply_to(&mut args);
let ret = unsafe { nvme_io(&mut args, OPC_WRITE) };
check_ret(ret)?;
Ok(result)
}
}
pub struct Compare<'a, 'r> {
ns: &'a Namespace<'r>,
slba: u64,
nlb: u32,
data: &'a [u8],
metadata: Option<&'a [u8]>,
opts: IoOpts,
}
impl<'a, 'r> Compare<'a, 'r> {
pub(crate) fn new(ns: &'a Namespace<'r>, slba: u64, nlb: u32, data: &'a [u8]) -> Self {
Compare {
ns,
slba,
nlb,
data,
metadata: None,
opts: IoOpts::default(),
}
}
pub fn metadata(mut self, md: &'a [u8]) -> Self {
self.metadata = Some(md);
self
}
io_data_setters!();
pub fn execute(self) -> Result<u32> {
check_buf_len(self.data.len(), self.nlb, self.ns.lba_size())?;
let nlb_enc = encode_nlb(self.nlb)?;
let fd = ns_fd(self.ns)?;
let data_ptr = self.data.as_ptr() as *mut c_void;
let data_len = self.data.len() as u32;
let mut result: u32 = 0;
let mut args = base_args(fd, self.ns.nsid(), self.slba, nlb_enc, data_ptr, data_len);
if let Some(md) = self.metadata {
args.metadata = md.as_ptr() as *mut c_void;
args.metadata_len = md.len() as u32;
}
args.result = &mut result;
self.opts.apply_to(&mut args);
let ret = unsafe { nvme_io(&mut args, OPC_COMPARE) };
check_ret(ret)?;
Ok(result)
}
}
macro_rules! io_nodata_setters {
() => {
pub fn fua(mut self) -> Self {
self.opts.control |= CTRL_FUA;
self
}
pub fn limited_retry(mut self) -> Self {
self.opts.control |= CTRL_LR;
self
}
pub fn protection_action(mut self) -> Self {
self.opts.control |= CTRL_PRINFO_PRACT;
self
}
pub fn check_reftag(mut self) -> Self {
self.opts.control |= CTRL_PRINFO_PRCHK_REF;
self
}
pub fn check_apptag(mut self) -> Self {
self.opts.control |= CTRL_PRINFO_PRCHK_APP;
self
}
pub fn check_guard(mut self) -> Self {
self.opts.control |= CTRL_PRINFO_PRCHK_GUARD;
self
}
pub fn ref_tag(mut self, tag: u32) -> Self {
self.opts.reftag = tag;
self
}
pub fn app_tag(mut self, tag: u16) -> Self {
self.opts.apptag = tag;
self
}
pub fn app_mask(mut self, mask: u16) -> Self {
self.opts.appmask = mask;
self
}
pub fn timeout_ms(mut self, ms: u32) -> Self {
self.opts.timeout_ms = ms;
self
}
};
}
pub struct Verify<'a, 'r> {
ns: &'a Namespace<'r>,
slba: u64,
nlb: u32,
opts: IoOpts,
}
impl<'a, 'r> Verify<'a, 'r> {
pub(crate) fn new(ns: &'a Namespace<'r>, slba: u64, nlb: u32) -> Self {
Verify {
ns,
slba,
nlb,
opts: IoOpts::default(),
}
}
io_nodata_setters!();
pub fn execute(self) -> Result<u32> {
let nlb_enc = encode_nlb(self.nlb)?;
let fd = ns_fd(self.ns)?;
let mut result: u32 = 0;
let mut args = base_args(
fd,
self.ns.nsid(),
self.slba,
nlb_enc,
std::ptr::null_mut(),
0,
);
args.result = &mut result;
self.opts.apply_to(&mut args);
let ret = unsafe { nvme_io(&mut args, OPC_VERIFY) };
check_ret(ret)?;
Ok(result)
}
}
pub struct WriteZeroes<'a, 'r> {
ns: &'a Namespace<'r>,
slba: u64,
nlb: u32,
opts: IoOpts,
}
impl<'a, 'r> WriteZeroes<'a, 'r> {
pub(crate) fn new(ns: &'a Namespace<'r>, slba: u64, nlb: u32) -> Self {
WriteZeroes {
ns,
slba,
nlb,
opts: IoOpts::default(),
}
}
io_nodata_setters!();
pub fn deallocate(mut self) -> Self {
self.opts.control |= CTRL_DEAC;
self
}
pub fn no_deallocate_after_zero(mut self) -> Self {
self.opts.control |= CTRL_NSZ;
self
}
pub fn execute(self) -> Result<u32> {
let nlb_enc = encode_nlb(self.nlb)?;
let fd = ns_fd(self.ns)?;
let mut result: u32 = 0;
let mut args = base_args(
fd,
self.ns.nsid(),
self.slba,
nlb_enc,
std::ptr::null_mut(),
0,
);
args.result = &mut result;
self.opts.apply_to(&mut args);
let ret = unsafe { nvme_io(&mut args, OPC_WRITE_ZEROES) };
check_ret(ret)?;
Ok(result)
}
}
pub struct WriteUncorrectable<'a, 'r> {
ns: &'a Namespace<'r>,
slba: u64,
nlb: u32,
opts: IoOpts,
}
impl<'a, 'r> WriteUncorrectable<'a, 'r> {
pub(crate) fn new(ns: &'a Namespace<'r>, slba: u64, nlb: u32) -> Self {
WriteUncorrectable {
ns,
slba,
nlb,
opts: IoOpts::default(),
}
}
pub fn timeout_ms(mut self, ms: u32) -> Self {
self.opts.timeout_ms = ms;
self
}
pub fn execute(self) -> Result<u32> {
let nlb_enc = encode_nlb(self.nlb)?;
let fd = ns_fd(self.ns)?;
let mut result: u32 = 0;
let mut args = base_args(
fd,
self.ns.nsid(),
self.slba,
nlb_enc,
std::ptr::null_mut(),
0,
);
args.result = &mut result;
self.opts.apply_to(&mut args);
let ret = unsafe { nvme_io(&mut args, OPC_WRITE_UNCOR) };
check_ret(ret)?;
Ok(result)
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct DsmAttr(u32);
impl DsmAttr {
pub const INTEGRAL_READ: Self = Self(1 << 0);
pub const INTEGRAL_WRITE: Self = Self(1 << 1);
pub const DEALLOCATE: Self = Self(1 << 2);
pub const fn from_bits(bits: u32) -> Self {
Self(bits)
}
pub const fn bits(self) -> u32 {
self.0
}
}
impl std::ops::BitOr for DsmAttr {
type Output = Self;
fn bitor(self, rhs: Self) -> Self {
Self(self.0 | rhs.0)
}
}
impl std::ops::BitOrAssign for DsmAttr {
fn bitor_assign(&mut self, rhs: Self) {
self.0 |= rhs.0;
}
}
#[derive(Debug, Clone, Copy)]
pub struct DsmRange {
pub context: u32,
pub length: u32,
pub slba: u64,
}
impl DsmRange {
pub fn new(slba: u64, length: u32) -> Self {
let length = length.saturating_sub(1);
DsmRange {
context: 0,
length,
slba,
}
}
pub fn with_context(mut self, context: u32) -> Self {
self.context = context;
self
}
}
pub struct Dsm<'a, 'r> {
ns: &'a Namespace<'r>,
attrs: DsmAttr,
ranges: Option<&'a [DsmRange]>,
timeout_ms: u32,
}
impl<'a, 'r> Dsm<'a, 'r> {
pub(crate) fn new(ns: &'a Namespace<'r>, attrs: DsmAttr) -> Self {
Dsm {
ns,
attrs,
ranges: None,
timeout_ms: 0,
}
}
pub fn ranges(mut self, ranges: &'a [DsmRange]) -> Self {
self.ranges = Some(ranges);
self
}
pub fn timeout_ms(mut self, ms: u32) -> Self {
self.timeout_ms = ms;
self
}
pub fn execute(self) -> Result<u32> {
let ranges = self.ranges.unwrap_or(&[]);
if ranges.is_empty() {
return Err(invalid("DSM requires at least one range"));
}
if ranges.len() > 256 {
return Err(invalid(format!(
"DSM supports at most 256 ranges, got {}",
ranges.len()
)));
}
let mut raw: Vec<nvme_dsm_range> = ranges
.iter()
.map(|r| nvme_dsm_range {
cattr: r.context.to_le(),
nlb: r.length.to_le(),
slba: r.slba.to_le(),
})
.collect();
let fd = ns_fd(self.ns)?;
let mut result: u32 = 0;
let mut args = nvme_dsm_args {
result: &mut result,
dsm: raw.as_mut_ptr(),
args_size: std::mem::size_of::<nvme_dsm_args>() as i32,
fd,
timeout: self.timeout_ms,
nsid: self.ns.nsid(),
attrs: self.attrs.bits(),
nr_ranges: raw.len() as u16,
};
let ret = unsafe { nvme_dsm(&mut args) };
check_ret(ret)?;
Ok(result)
}
}
#[derive(Debug, Clone, Copy, Default)]
pub struct CopyRange {
pub slba: u64,
pub nlb: u16,
pub eilbrt: u32,
pub elbat: u16,
pub elbatm: u16,
}
impl CopyRange {
pub fn new(slba: u64, nlb: u16) -> Self {
CopyRange {
slba,
nlb,
..Default::default()
}
}
pub fn ref_tag(mut self, eilbrt: u32) -> Self {
self.eilbrt = eilbrt;
self
}
pub fn app_tag(mut self, elbat: u16, mask: u16) -> Self {
self.elbat = elbat;
self.elbatm = mask;
self
}
}
pub struct Copy<'a, 'r> {
ns: &'a Namespace<'r>,
sdlba: u64,
ranges: &'a [CopyRange],
ilbrt: u32,
ilbrt_u64: u64,
lbat: u16,
lbatm: u16,
prinfor: u8,
prinfow: u8,
fua: bool,
lr: bool,
dtype: u8,
dspec: u16,
format: u8,
timeout_ms: u32,
}
impl<'a, 'r> Copy<'a, 'r> {
pub(crate) fn new(ns: &'a Namespace<'r>, sdlba: u64, ranges: &'a [CopyRange]) -> Self {
Copy {
ns,
sdlba,
ranges,
ilbrt: 0,
ilbrt_u64: 0,
lbat: 0,
lbatm: 0,
prinfor: 0,
prinfow: 0,
fua: false,
lr: false,
dtype: 0,
dspec: 0,
format: 0,
timeout_ms: 0,
}
}
pub fn fua(mut self) -> Self {
self.fua = true;
self
}
pub fn limited_retry(mut self) -> Self {
self.lr = true;
self
}
pub fn dest_ref_tag(mut self, tag: u32) -> Self {
self.ilbrt = tag;
self
}
pub fn dest_ref_tag_u64(mut self, tag: u64) -> Self {
self.ilbrt_u64 = tag;
self
}
pub fn dest_app_tag(mut self, tag: u16, mask: u16) -> Self {
self.lbat = tag;
self.lbatm = mask;
self
}
pub fn prinfo_read(mut self, prinfo: u8) -> Self {
self.prinfor = prinfo;
self
}
pub fn prinfo_write(mut self, prinfo: u8) -> Self {
self.prinfow = prinfo;
self
}
pub fn descriptor_format(mut self, format: u8) -> Self {
self.format = format;
self
}
pub fn directive(mut self, dtype: u8, dspec: u16) -> Self {
self.dtype = dtype;
self.dspec = dspec;
self
}
pub fn timeout_ms(mut self, ms: u32) -> Self {
self.timeout_ms = ms;
self
}
pub fn execute(self) -> Result<u32> {
if self.ranges.is_empty() {
return Err(invalid("Copy requires at least one source range"));
}
if self.ranges.len() > 128 {
return Err(invalid(format!(
"Copy supports at most 128 ranges, got {}",
self.ranges.len()
)));
}
let mut raw: Vec<nvme_copy_range> = self
.ranges
.iter()
.map(|r| {
let nlb_enc = r.nlb.saturating_sub(1);
nvme_copy_range {
rsvd0: [0u8; 8],
slba: r.slba.to_le(),
nlb: nlb_enc.to_le(),
rsvd18: [0u8; 6],
eilbrt: r.eilbrt.to_le(),
elbat: r.elbat.to_le(),
elbatm: r.elbatm.to_le(),
}
})
.collect();
let fd = ns_fd(self.ns)?;
let mut result: u32 = 0;
let mut args = nvme_copy_args {
sdlba: self.sdlba,
result: &mut result,
copy: raw.as_mut_ptr(),
args_size: std::mem::size_of::<nvme_copy_args>() as i32,
fd,
timeout: self.timeout_ms,
nsid: self.ns.nsid(),
ilbrt: self.ilbrt,
lr: self.lr as i32,
fua: self.fua as i32,
nr: (raw.len() - 1) as u16, dspec: self.dspec,
lbatm: self.lbatm,
lbat: self.lbat,
prinfor: self.prinfor,
prinfow: self.prinfow,
dtype: self.dtype,
format: self.format,
ilbrt_u64: self.ilbrt_u64,
};
let ret = unsafe { nvme_copy(&mut args) };
check_ret(ret)?;
Ok(result)
}
}
pub(crate) fn flush(ns: &Namespace<'_>) -> Result<()> {
let fd = ns_fd(ns)?;
let mut result: u32 = 0;
let ret = unsafe {
nvme_io_passthru(
fd,
OPC_FLUSH,
0,
0,
ns.nsid(),
0,
0,
0,
0,
0,
0,
0,
0,
0,
std::ptr::null_mut(),
0,
std::ptr::null_mut(),
0,
&mut result,
)
};
check_ret(ret)
}