use alloc::boxed::Box;
use alloc::collections::btree_map::BTreeMap;
use alloc::string::String;
use alloc::vec::Vec;
use crate::drivers::nvme::memory::Dma;
use crate::drivers::nvme::NvmeStats;
use super::cmd::NvmeCommand;
use super::memory::DmaSlice;
use super::{queues::*, NvmeNamespace};
use core::error::Error;
use core::hint::spin_loop;
#[allow(unused, clippy::upper_case_acronyms)]
#[derive(Copy, Clone, Debug)]
pub enum NvmeRegs32 {
VS = 0x8, INTMS = 0xC, INTMC = 0x10, CC = 0x14, CSTS = 0x1C, NSSR = 0x20, AQA = 0x24, CMBLOC = 0x38, CMBSZ = 0x3C, BPINFO = 0x40, BPRSEL = 0x44, BPMBL = 0x48, CMBSTS = 0x58, PMRCAP = 0xE00, PMRCTL = 0xE04, PMRSTS = 0xE08, PMREBS = 0xE0C, PMRSWTP = 0xE10, }
#[allow(unused, clippy::upper_case_acronyms)]
#[derive(Copy, Clone, Debug)]
pub enum NvmeRegs64 {
CAP = 0x0, ASQ = 0x28, ACQ = 0x30, CMBMSC = 0x50, PMRMSC = 0xE14, }
#[allow(non_camel_case_types)]
#[derive(Copy, Clone, Debug)]
pub enum NvmeArrayRegs {
SQyTDBL,
CQyHDBL,
}
#[repr(C, packed)]
#[derive(Debug, Clone, Copy)]
#[allow(unused)]
struct IdentifyNamespaceData {
pub nsze: u64,
pub ncap: u64,
nuse: u64,
nsfeat: u8,
pub nlbaf: u8,
pub flbas: u8,
mc: u8,
dpc: u8,
dps: u8,
nmic: u8,
rescap: u8,
fpi: u8,
dlfeat: u8,
nawun: u16,
nawupf: u16,
nacwu: u16,
nabsn: u16,
nabo: u16,
nabspf: u16,
noiob: u16,
nvmcap: u128,
npwg: u16,
npwa: u16,
npdg: u16,
npda: u16,
nows: u16,
_rsvd1: [u8; 18],
anagrpid: u32,
_rsvd2: [u8; 3],
nsattr: u8,
nvmsetid: u16,
endgid: u16,
nguid: [u8; 16],
eui64: u64,
pub lba_format_support: [u32; 16],
_rsvd3: [u8; 192],
vendor_specific: [u8; 3712],
}
pub struct NvmeQueuePair {
pub id: u16,
pub sub_queue: NvmeSubQueue,
comp_queue: NvmeCompQueue,
}
impl NvmeQueuePair {
pub fn submit_io(&mut self, data: &impl DmaSlice, mut lba: u64, write: bool) -> usize {
let mut reqs = 0;
for chunk in data.chunks(2 * 4096) {
let blocks = (chunk.slice.len() as u64 + 512 - 1) / 512;
let addr = chunk.phys_addr as u64;
let bytes = blocks * 512;
let ptr1 = if bytes <= 4096 {
0
} else {
addr + 4096 };
let entry = if write {
NvmeCommand::io_write(
self.id << 11 | self.sub_queue.tail as u16,
1,
lba,
blocks as u16 - 1,
addr,
ptr1,
)
} else {
NvmeCommand::io_read(
self.id << 11 | self.sub_queue.tail as u16,
1,
lba,
blocks as u16 - 1,
addr,
ptr1,
)
};
if let Some(tail) = self.sub_queue.submit_checked(entry) {
unsafe {
core::ptr::write_volatile(self.sub_queue.doorbell as *mut u32, tail as u32);
}
} else {
log::error!("queue full");
return reqs;
}
lba += blocks;
reqs += 1;
}
reqs
}
pub fn complete_io(&mut self, n: usize) -> Option<u16> {
assert!(n > 0);
let (tail, c_entry, _) = self.comp_queue.complete_n(n);
unsafe {
core::ptr::write_volatile(self.comp_queue.doorbell as *mut u32, tail as u32);
}
self.sub_queue.head = c_entry.sq_head as usize;
let status = c_entry.status >> 1;
if status != 0 {
log::error!(
"Status: 0x{:x}, Status Code 0x{:x}, Status Code Type: 0x{:x}",
status,
status & 0xFF,
(status >> 8) & 0x7
);
log::error!("{:?}", c_entry);
return None;
}
Some(c_entry.sq_head)
}
pub fn quick_poll(&mut self) -> Option<()> {
if let Some((tail, c_entry, _)) = self.comp_queue.complete() {
unsafe {
core::ptr::write_volatile(self.comp_queue.doorbell as *mut u32, tail as u32);
}
self.sub_queue.head = c_entry.sq_head as usize;
let status = c_entry.status >> 1;
if status != 0 {
log::error!(
"Status: 0x{:x}, Status Code 0x{:x}, Status Code Type: 0x{:x}",
status,
status & 0xFF,
(status >> 8) & 0x7
);
log::error!("{:?}", c_entry);
}
return Some(());
}
None
}
}
#[allow(unused)]
pub struct NvmeDevice {
addr: *mut u8,
len: usize,
dstrd: u16,
admin_sq: NvmeSubQueue,
admin_cq: NvmeCompQueue,
io_sq: NvmeSubQueue,
io_cq: NvmeCompQueue,
buffer: Dma<u8>, prp_list: Dma<[u64; 512]>, pub namespaces: BTreeMap<u32, NvmeNamespace>,
pub stats: NvmeStats,
q_id: u16,
}
unsafe impl Send for NvmeDevice {}
unsafe impl Sync for NvmeDevice {}
#[allow(unused)]
impl NvmeDevice {
pub fn init(addr: usize, len: usize) -> Result<Self, Box<dyn Error>> {
let mut dev = Self {
addr: addr as *mut u8,
dstrd: {
unsafe {
((core::ptr::read_volatile(
(addr as usize + NvmeRegs64::CAP as usize) as *const u64,
) >> 32)
& 0b1111) as u16
}
},
len,
admin_sq: NvmeSubQueue::new(QUEUE_LENGTH, 0)?,
admin_cq: NvmeCompQueue::new(QUEUE_LENGTH, 0)?,
io_sq: NvmeSubQueue::new(QUEUE_LENGTH, 0)?,
io_cq: NvmeCompQueue::new(QUEUE_LENGTH, 0)?,
buffer: Dma::allocate(4096)?,
prp_list: Dma::allocate(8 * 512)?,
namespaces: BTreeMap::new(),
stats: NvmeStats::default(),
q_id: 1,
};
for i in 1..512 {
dev.prp_list[i - 1] = (dev.buffer.phys + i * 4096) as u64;
}
log::info!("CAP: 0x{:x}", dev.get_reg64(NvmeRegs64::CAP as u64));
log::info!("VS: 0x{:x}", dev.get_reg32(NvmeRegs32::VS as u32));
log::info!("CC: 0x{:x}", dev.get_reg32(NvmeRegs32::CC as u32));
log::info!("Disabling controller");
let ctrl_config = dev.get_reg32(NvmeRegs32::CC as u32) & 0xFFFF_FFFE;
dev.set_reg32(NvmeRegs32::CC as u32, ctrl_config);
loop {
let csts = dev.get_reg32(NvmeRegs32::CSTS as u32);
if csts & 1 == 1 {
spin_loop();
} else {
break;
}
}
dev.set_reg64(NvmeRegs64::ASQ as u32, dev.admin_sq.get_addr() as u64);
dev.set_reg64(NvmeRegs64::ACQ as u32, dev.admin_cq.get_addr() as u64);
dev.set_reg32(
NvmeRegs32::AQA as u32,
(QUEUE_LENGTH as u32 - 1) << 16 | (QUEUE_LENGTH as u32 - 1),
);
let mut cc = dev.get_reg32(NvmeRegs32::CC as u32);
cc &= 0xFF00_000F;
cc |= (4 << 20) | (6 << 16);
dev.set_reg32(NvmeRegs32::CC as u32, cc);
log::info!("Enabling controller");
let ctrl_config = dev.get_reg32(NvmeRegs32::CC as u32) | 1;
dev.set_reg32(NvmeRegs32::CC as u32, ctrl_config);
loop {
let csts = dev.get_reg32(NvmeRegs32::CSTS as u32);
if csts & 1 == 0 {
spin_loop();
} else {
break;
}
}
let q_id = dev.q_id;
let addr = dev.io_cq.get_addr();
log::info!("Requesting i/o completion queue");
let comp = dev.submit_and_complete_admin(|c_id, _| {
NvmeCommand::create_io_completion_queue(c_id, q_id, addr, (QUEUE_LENGTH - 1) as u16)
})?;
let addr = dev.io_sq.get_addr();
log::info!("Requesting i/o submission queue");
let comp = dev.submit_and_complete_admin(|c_id, _| {
NvmeCommand::create_io_submission_queue(
c_id,
q_id,
addr,
(QUEUE_LENGTH - 1) as u16,
q_id,
)
})?;
dev.q_id += 1;
Ok(dev)
}
pub fn identify_controller(&mut self) -> Result<(), Box<dyn Error>> {
log::info!("Trying to identify controller");
let _entry = self.submit_and_complete_admin(NvmeCommand::identify_controller);
log::info!("Dumping identify controller");
let mut serial = String::new();
let data = &self.buffer;
for &b in &data[4..24] {
if b == 0 {
break;
}
serial.push(b as char);
}
let mut model = String::new();
for &b in &data[24..64] {
if b == 0 {
break;
}
model.push(b as char);
}
let mut firmware = String::new();
for &b in &data[64..72] {
if b == 0 {
break;
}
firmware.push(b as char);
}
log::info!(
" - Model: {} Serial: {} Firmware: {}",
model.trim(),
serial.trim(),
firmware.trim()
);
Ok(())
}
pub fn create_io_queue_pair(&mut self, len: usize) -> Result<NvmeQueuePair, Box<dyn Error>> {
let q_id = self.q_id;
log::info!("Requesting i/o queue pair with id {q_id}");
let offset = 0x1000 + ((4 << self.dstrd) * (2 * q_id + 1) as usize);
assert!(offset <= self.len - 4, "SQ doorbell offset out of bounds");
let dbl = self.addr as usize + offset;
let comp_queue = NvmeCompQueue::new(len, dbl)?;
let comp = self.submit_and_complete_admin(|c_id, _| {
NvmeCommand::create_io_completion_queue(
c_id,
q_id,
comp_queue.get_addr(),
(len - 1) as u16,
)
})?;
let dbl = self.addr as usize + 0x1000 + ((4 << self.dstrd) * (2 * q_id) as usize);
let sub_queue = NvmeSubQueue::new(len, dbl)?;
let comp = self.submit_and_complete_admin(|c_id, _| {
NvmeCommand::create_io_submission_queue(
c_id,
q_id,
sub_queue.get_addr(),
(len - 1) as u16,
q_id,
)
})?;
self.q_id += 1;
Ok(NvmeQueuePair {
id: q_id,
sub_queue,
comp_queue,
})
}
pub fn delete_io_queue_pair(&mut self, qpair: NvmeQueuePair) -> Result<(), Box<dyn Error>> {
log::info!("Deleting i/o queue pair with id {}", qpair.id);
self.submit_and_complete_admin(|c_id, _| {
NvmeCommand::delete_io_submission_queue(c_id, qpair.id)
})?;
self.submit_and_complete_admin(|c_id, _| {
NvmeCommand::delete_io_completion_queue(c_id, qpair.id)
})?;
Ok(())
}
pub fn identify_namespace_list(&mut self, base: u32) -> Vec<u32> {
self.submit_and_complete_admin(|c_id, addr| {
NvmeCommand::identify_namespace_list(c_id, addr, base)
});
let data: &[u32] =
unsafe { core::slice::from_raw_parts(self.buffer.virt as *const u32, 1024) };
data.iter()
.copied()
.take_while(|&id| id != 0)
.collect::<Vec<u32>>()
}
pub fn identify_namespace(&mut self, id: u32) -> (NvmeNamespace, u64) {
self.submit_and_complete_admin(|c_id, addr| {
NvmeCommand::identify_namespace(c_id, addr, id)
});
let namespace_data: IdentifyNamespaceData =
unsafe { *(self.buffer.virt as *const IdentifyNamespaceData) };
let size = namespace_data.nsze;
let blocks = namespace_data.ncap;
let flba_idx = (namespace_data.flbas & 0xF) as usize;
let flba_data = (namespace_data.lba_format_support[flba_idx] >> 16) & 0xFF;
let block_size = if !(9..32).contains(&flba_data) {
0
} else {
1 << flba_data
};
log::info!("Namespace {id}, Size: {size}, Blocks: {blocks}, Block size: {block_size}");
let namespace = NvmeNamespace {
id,
blocks,
block_size,
};
self.namespaces.insert(id, namespace);
(namespace, blocks * block_size)
}
pub fn write(&mut self, data: &impl DmaSlice, mut lba: u64) -> Result<(), Box<dyn Error>> {
for chunk in data.chunks(2 * 4096) {
let blocks = (chunk.slice.len() as u64 + 512 - 1) / 512;
self.namespace_io(1, blocks, lba, chunk.phys_addr as u64, true)?;
lba += blocks;
}
Ok(())
}
pub fn read(&mut self, dest: &impl DmaSlice, mut lba: u64) -> Result<(), Box<dyn Error>> {
for chunk in dest.chunks(2 * 4096) {
let blocks = (chunk.slice.len() as u64 + 512 - 1) / 512;
self.namespace_io(1, blocks, lba, chunk.phys_addr as u64, false)?;
lba += blocks;
}
Ok(())
}
pub fn write_copied(&mut self, data: &[u8], mut lba: u64) -> Result<(), Box<dyn Error>> {
let ns = *self.namespaces.get(&1).unwrap();
for chunk in data.chunks(128 * 4096) {
self.buffer[..chunk.len()].copy_from_slice(chunk);
let blocks = (chunk.len() as u64 + ns.block_size - 1) / ns.block_size;
self.namespace_io(1, blocks, lba, self.buffer.phys as u64, true)?;
lba += blocks;
}
Ok(())
}
pub fn read_copied(&mut self, dest: &mut [u8], mut lba: u64) -> Result<(), Box<dyn Error>> {
let ns = *self.namespaces.get(&1).unwrap();
for chunk in dest.chunks_mut(128 * 4096) {
let blocks = (chunk.len() as u64 + ns.block_size - 1) / ns.block_size;
self.namespace_io(1, blocks, lba, self.buffer.phys as u64, false)?;
lba += blocks;
chunk.copy_from_slice(&self.buffer[..chunk.len()]);
}
Ok(())
}
fn submit_io(
&mut self,
ns: &NvmeNamespace,
addr: u64,
blocks: u64,
lba: u64,
write: bool,
) -> Option<usize> {
assert!(blocks > 0);
assert!(blocks <= 0x1_0000);
let q_id = 1;
let bytes = blocks * ns.block_size;
let ptr1 = if bytes <= 4096 {
0
} else if bytes <= 8192 {
addr + 4096 } else {
let offset = (addr - self.buffer.phys as u64) / 8;
self.prp_list.phys as u64 + offset
};
let entry = if write {
NvmeCommand::io_write(
self.io_sq.tail as u16,
ns.id,
lba,
blocks as u16 - 1,
addr,
ptr1,
)
} else {
NvmeCommand::io_read(
self.io_sq.tail as u16,
ns.id,
lba,
blocks as u16 - 1,
addr,
ptr1,
)
};
self.io_sq.submit_checked(entry)
}
fn complete_io(&mut self, step: u64) -> Option<u16> {
let q_id = 1;
let (tail, c_entry, _) = self.io_cq.complete_n(step as usize);
self.write_reg_idx(NvmeArrayRegs::CQyHDBL, q_id as u16, tail as u32);
let status = c_entry.status >> 1;
if status != 0 {
log::error!(
"Status: 0x{:x}, Status Code 0x{:x}, Status Code Type: 0x{:x}",
status,
status & 0xFF,
(status >> 8) & 0x7
);
log::error!("{:?}", c_entry);
return None;
}
self.stats.completions += 1;
Some(c_entry.sq_head)
}
pub fn batched_write(
&mut self,
ns_id: u32,
data: &[u8],
mut lba: u64,
batch_len: u64,
) -> Result<(), Box<dyn Error>> {
let ns = *self.namespaces.get(&ns_id).unwrap();
let block_size = 512;
let q_id = 1;
for chunk in data.chunks(4096) {
self.buffer[..chunk.len()].copy_from_slice(chunk);
let tail = self.io_sq.tail;
let batch_len = core::cmp::min(batch_len, chunk.len() as u64 / block_size);
let batch_size = chunk.len() as u64 / batch_len;
let blocks = batch_size / ns.block_size;
for i in 0..batch_len {
if let Some(tail) = self.submit_io(
&ns,
self.buffer.phys as u64 + i * batch_size,
blocks,
lba,
true,
) {
self.stats.submissions += 1;
self.write_reg_idx(NvmeArrayRegs::SQyTDBL, q_id as u16, tail as u32);
} else {
log::error!("tail: {tail}, batch_len: {batch_len}, batch_size: {batch_size}, blocks: {blocks}");
}
lba += blocks;
}
self.io_sq.head = self.complete_io(batch_len).unwrap() as usize;
}
Ok(())
}
pub fn batched_read(
&mut self,
ns_id: u32,
data: &mut [u8],
mut lba: u64,
batch_len: u64,
) -> Result<(), Box<dyn Error>> {
let ns = *self.namespaces.get(&ns_id).unwrap();
let block_size = 512;
let q_id = 1;
for chunk in data.chunks_mut(4096) {
let tail = self.io_sq.tail;
let batch_len = core::cmp::min(batch_len, chunk.len() as u64 / block_size);
let batch_size = chunk.len() as u64 / batch_len;
let blocks = batch_size / ns.block_size;
for i in 0..batch_len {
if let Some(tail) = self.submit_io(
&ns,
self.buffer.phys as u64 + i * batch_size,
blocks,
lba,
false,
) {
self.stats.submissions += 1;
self.write_reg_idx(NvmeArrayRegs::SQyTDBL, q_id as u16, tail as u32);
} else {
log::error!("tail: {tail}, batch_len: {batch_len}, batch_size: {batch_size}, blocks: {blocks}");
}
lba += blocks;
}
self.io_sq.head = self.complete_io(batch_len).unwrap() as usize;
chunk.copy_from_slice(&self.buffer[..chunk.len()]);
}
Ok(())
}
#[inline(always)]
fn namespace_io(
&mut self,
ns_id: u32,
blocks: u64,
lba: u64,
addr: u64,
write: bool,
) -> Result<(), Box<dyn Error>> {
assert!(blocks > 0);
assert!(blocks <= 0x1_0000);
let q_id = 1;
let bytes = blocks * 512;
let ptr1 = if bytes <= 4096 {
0
} else if bytes <= 8192 {
addr + 4096 } else {
self.prp_list.phys as u64
};
let entry = if write {
NvmeCommand::io_write(
self.io_sq.tail as u16,
ns_id,
lba,
blocks as u16 - 1,
addr,
ptr1,
)
} else {
NvmeCommand::io_read(
self.io_sq.tail as u16,
ns_id,
lba,
blocks as u16 - 1,
addr,
ptr1,
)
};
let tail = self.io_sq.submit(entry);
self.stats.submissions += 1;
self.write_reg_idx(NvmeArrayRegs::SQyTDBL, q_id as u16, tail as u32);
self.io_sq.head = self.complete_io(1).unwrap() as usize;
Ok(())
}
fn submit_and_complete_admin<F: FnOnce(u16, usize) -> NvmeCommand>(
&mut self,
cmd_init: F,
) -> Result<NvmeCompletion, Box<dyn Error>> {
let cid = self.admin_sq.tail;
let tail = self.admin_sq.submit(cmd_init(cid as u16, self.buffer.phys));
self.write_reg_idx(NvmeArrayRegs::SQyTDBL, 0, tail as u32);
let (head, entry, _) = self.admin_cq.complete_spin();
self.write_reg_idx(NvmeArrayRegs::CQyHDBL, 0, head as u32);
let status = entry.status >> 1;
if status != 0 {
log::error!(
"Status: 0x{:x}, Status Code 0x{:x}, Status Code Type: 0x{:x}",
status,
status & 0xFF,
(status >> 8) & 0x7
);
return Err("Requesting i/o completion queue failed".into());
}
Ok(entry)
}
pub fn clear_namespace(&mut self, ns_id: Option<u32>) {
let ns_id = if let Some(ns_id) = ns_id {
assert!(self.namespaces.contains_key(&ns_id));
ns_id
} else {
0xFFFF_FFFF
};
self.submit_and_complete_admin(|c_id, _| NvmeCommand::format_nvm(c_id, ns_id));
}
fn write_reg_idx(&self, reg: NvmeArrayRegs, qid: u16, val: u32) {
match reg {
NvmeArrayRegs::SQyTDBL => unsafe {
core::ptr::write_volatile(
(self.addr as usize + 0x1000 + ((4 << self.dstrd) * (2 * qid)) as usize)
as *mut u32,
val,
);
},
NvmeArrayRegs::CQyHDBL => unsafe {
core::ptr::write_volatile(
(self.addr as usize + 0x1000 + ((4 << self.dstrd) * (2 * qid + 1)) as usize)
as *mut u32,
val,
);
},
}
}
fn set_reg32(&self, reg: u32, value: u32) {
assert!(reg as usize <= self.len - 4, "memory access out of bounds");
unsafe {
core::ptr::write_volatile((self.addr as usize + reg as usize) as *mut u32, value);
}
}
fn get_reg32(&self, reg: u32) -> u32 {
assert!(reg as usize <= self.len - 4, "memory access out of bounds");
unsafe { core::ptr::read_volatile((self.addr as usize + reg as usize) as *mut u32) }
}
fn set_reg64(&self, reg: u32, value: u64) {
assert!(reg as usize <= self.len - 8, "memory access out of bounds");
unsafe {
core::ptr::write_volatile((self.addr as usize + reg as usize) as *mut u64, value);
}
}
fn get_reg64(&self, reg: u64) -> u64 {
assert!(reg as usize <= self.len - 8, "memory access out of bounds");
unsafe { core::ptr::read_volatile((self.addr as usize + reg as usize) as *mut u64) }
}
}