pub const NVME_CMD_FLUSH: u8 = 0x00;
pub const NVME_CMD_WRITE: u8 = 0x01;
pub const NVME_CMD_READ: u8 = 0x02;
pub const NVME_URING_CMD_IO: u32 = 0;
#[allow(dead_code)]
pub const NVME_URING_CMD_IO_VEC: u32 = 1;
#[repr(C)]
#[derive(Debug, Clone, Copy)]
pub struct NvmeUringCmd {
pub opcode: u8,
pub flags: u8,
pub rsvd1: u16,
pub nsid: u32,
pub cdw2: u32,
pub cdw3: u32,
pub metadata: u64,
pub addr: u64,
pub metadata_len: u32,
pub data_len: u32,
pub cdw10: u32,
pub cdw11: u32,
pub cdw12: u32,
pub cdw13: u32,
pub cdw14: u32,
pub cdw15: u32,
pub timeout_ms: u32,
pub rsvd2: u32,
}
const _: () = assert!(std::mem::size_of::<NvmeUringCmd>() == 72);
const _: () = assert!(std::mem::size_of::<NvmeUringCmd>() <= 80);
impl NvmeUringCmd {
pub fn read(nsid: u32, lba: u64, num_blocks: u16, buf_addr: u64, buf_len: u32) -> Self {
NvmeUringCmd {
opcode: NVME_CMD_READ,
flags: 0,
rsvd1: 0,
nsid,
cdw2: 0,
cdw3: 0,
metadata: 0,
addr: buf_addr,
metadata_len: 0,
data_len: buf_len,
cdw10: lba as u32, cdw11: (lba >> 32) as u32, cdw12: (num_blocks - 1) as u32, cdw13: 0,
cdw14: 0,
cdw15: 0,
timeout_ms: 0,
rsvd2: 0,
}
}
pub fn write(nsid: u32, lba: u64, num_blocks: u16, buf_addr: u64, buf_len: u32) -> Self {
NvmeUringCmd {
opcode: NVME_CMD_WRITE,
flags: 0,
rsvd1: 0,
nsid,
cdw2: 0,
cdw3: 0,
metadata: 0,
addr: buf_addr,
metadata_len: 0,
data_len: buf_len,
cdw10: lba as u32,
cdw11: (lba >> 32) as u32,
cdw12: (num_blocks - 1) as u32,
cdw13: 0,
cdw14: 0,
cdw15: 0,
timeout_ms: 0,
rsvd2: 0,
}
}
pub fn flush(nsid: u32) -> Self {
NvmeUringCmd {
opcode: NVME_CMD_FLUSH,
flags: 0,
rsvd1: 0,
nsid,
cdw2: 0,
cdw3: 0,
metadata: 0,
addr: 0,
metadata_len: 0,
data_len: 0,
cdw10: 0,
cdw11: 0,
cdw12: 0,
cdw13: 0,
cdw14: 0,
cdw15: 0,
timeout_ms: 0,
rsvd2: 0,
}
}
pub fn to_bytes(&self) -> [u8; 80] {
let mut buf = [0u8; 80];
unsafe {
std::ptr::copy_nonoverlapping(
self as *const Self as *const u8,
buf.as_mut_ptr(),
std::mem::size_of::<Self>(),
);
}
buf
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct NvmeDevice {
pub(crate) index: u16,
pub(crate) generation: u16,
}
impl NvmeDevice {
pub fn index(&self) -> usize {
self.index as usize
}
}
pub(crate) struct NvmeDeviceState {
pub fd_index: u32,
pub nsid: u32,
pub active: bool,
pub generation: u16,
pub in_flight: u32,
}
impl NvmeDeviceState {
pub fn new() -> Self {
NvmeDeviceState {
fd_index: u32::MAX,
nsid: 1,
active: false,
generation: 0,
in_flight: 0,
}
}
}
pub(crate) struct NvmeDeviceTable {
slots: Vec<NvmeDeviceState>,
free_list: Vec<u16>,
}
impl NvmeDeviceTable {
pub fn new(max_devices: u16) -> Self {
let mut slots = Vec::with_capacity(max_devices as usize);
let mut free_list = Vec::with_capacity(max_devices as usize);
for i in (0..max_devices).rev() {
slots.push(NvmeDeviceState::new());
free_list.push(i);
}
NvmeDeviceTable { slots, free_list }
}
pub fn allocate(&mut self) -> Option<u16> {
let index = self.free_list.pop()?;
let slot = &mut self.slots[index as usize];
slot.active = true;
Some(index)
}
pub fn release(&mut self, index: u16) {
let slot = &mut self.slots[index as usize];
slot.active = false;
slot.fd_index = u32::MAX;
slot.in_flight = 0;
slot.generation = slot.generation.wrapping_add(1);
self.free_list.push(index);
}
pub fn get(&self, index: u16) -> Option<&NvmeDeviceState> {
self.slots.get(index as usize).filter(|s| s.active)
}
pub fn get_mut(&mut self, index: u16) -> Option<&mut NvmeDeviceState> {
self.slots.get_mut(index as usize).filter(|s| s.active)
}
}
pub(crate) struct NvmeCmdSlab {
entries: Vec<NvmeCmdEntry>,
free_list: Vec<u16>,
}
struct NvmeCmdEntry {
device_index: u16,
in_use: bool,
}
impl NvmeCmdSlab {
pub fn new(capacity: u16) -> Self {
let mut entries = Vec::with_capacity(capacity as usize);
let mut free_list = Vec::with_capacity(capacity as usize);
for i in (0..capacity).rev() {
entries.push(NvmeCmdEntry {
device_index: 0,
in_use: false,
});
free_list.push(i);
}
NvmeCmdSlab { entries, free_list }
}
pub fn allocate(&mut self, device_index: u16) -> Option<u16> {
let idx = self.free_list.pop()?;
let entry = &mut self.entries[idx as usize];
entry.device_index = device_index;
entry.in_use = true;
Some(idx)
}
pub fn release(&mut self, idx: u16) -> u16 {
let entry = &mut self.entries[idx as usize];
let device_index = entry.device_index;
entry.in_use = false;
self.free_list.push(idx);
device_index
}
#[allow(dead_code)]
pub fn device_index(&self, idx: u16) -> u16 {
self.entries[idx as usize].device_index
}
pub fn in_use(&self, idx: u16) -> bool {
self.entries.get(idx as usize).is_some_and(|e| e.in_use)
}
}
#[derive(Debug, Clone)]
pub struct NvmeCompletion {
pub device: NvmeDevice,
pub seq: u32,
pub result: i32,
}
impl NvmeCompletion {
pub fn is_success(&self) -> bool {
self.result >= 0
}
}
#[derive(Clone, Debug)]
pub struct NvmeConfig {
pub max_devices: u16,
pub max_commands_in_flight: u16,
}
impl Default for NvmeConfig {
fn default() -> Self {
NvmeConfig {
max_devices: 4,
max_commands_in_flight: 256,
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn nvme_uring_cmd_size() {
assert_eq!(std::mem::size_of::<NvmeUringCmd>(), 72);
}
#[test]
fn nvme_uring_cmd_fits_in_80_bytes() {
assert!(std::mem::size_of::<NvmeUringCmd>() <= 80);
}
#[test]
fn read_command_fields() {
let cmd = NvmeUringCmd::read(1, 0x1000, 8, 0xDEAD_BEEF, 4096);
assert_eq!(cmd.opcode, NVME_CMD_READ);
assert_eq!(cmd.nsid, 1);
assert_eq!(cmd.cdw10, 0x1000); assert_eq!(cmd.cdw11, 0); assert_eq!(cmd.cdw12, 7); assert_eq!(cmd.addr, 0xDEAD_BEEF);
assert_eq!(cmd.data_len, 4096);
}
#[test]
fn write_command_fields() {
let cmd = NvmeUringCmd::write(1, 0x2000_0000_0000, 1, 0xCAFE, 512);
assert_eq!(cmd.opcode, NVME_CMD_WRITE);
assert_eq!(cmd.cdw10, 0); assert_eq!(cmd.cdw11, 0x2000); assert_eq!(cmd.cdw12, 0); }
#[test]
fn flush_command() {
let cmd = NvmeUringCmd::flush(1);
assert_eq!(cmd.opcode, NVME_CMD_FLUSH);
assert_eq!(cmd.nsid, 1);
assert_eq!(cmd.addr, 0);
assert_eq!(cmd.data_len, 0);
}
#[test]
fn to_bytes_roundtrip() {
let cmd = NvmeUringCmd::read(1, 42, 1, 0x1234, 512);
let bytes = cmd.to_bytes();
let recovered: NvmeUringCmd = unsafe { std::ptr::read(bytes.as_ptr() as *const _) };
assert_eq!(recovered.opcode, NVME_CMD_READ);
assert_eq!(recovered.nsid, 1);
assert_eq!(recovered.cdw10, 42);
assert_eq!(recovered.addr, 0x1234);
}
#[test]
fn device_table_alloc_release() {
let mut table = NvmeDeviceTable::new(4);
let a = table.allocate().unwrap();
let b = table.allocate().unwrap();
assert_ne!(a, b);
assert!(table.get(a).is_some());
table.release(a);
assert!(table.get(a).is_none());
let c = table.allocate().unwrap();
assert_eq!(c, a);
assert_eq!(table.get(c).unwrap().generation, 1);
}
#[test]
fn cmd_slab_alloc_release() {
let mut slab = NvmeCmdSlab::new(8);
let a = slab.allocate(0).unwrap();
let b = slab.allocate(1).unwrap();
assert!(slab.in_use(a));
assert!(slab.in_use(b));
assert_eq!(slab.device_index(a), 0);
assert_eq!(slab.device_index(b), 1);
let dev = slab.release(a);
assert_eq!(dev, 0);
assert!(!slab.in_use(a));
}
}