use alloc::string::String;
use alloc::sync::{Arc, Weak};
use alloc::vec;
use alloc::vec::Vec;
use core::fmt;
use crate::core::error::{BusError, Result};
use crate::core::space::{
AccessConstraints, AddressSpace, MemAttrs, MemOps, MemResult, RequesterId,
};
use crate::core::state::{Sink, Source};
use crate::core::sync::{AtomicBool, AtomicU32, LockRank, Mutex, Ordering};
use crate::core::value::{Endian, Width};
use crate::core::wire::{Level, WireSource};
use crate::dev::ata::{Medium, Snapshot};
pub const NVME_RANK: LockRank = LockRank::new(0x5a00);
pub const REGISTER_LEN: u64 = 0x2000;
const DOORBELL_BASE: u64 = 0x1000;
const DOORBELL_STRIDE: u64 = 4;
pub const MAX_IO_QUEUES: u16 = 8;
const QUEUE_SLOTS: usize = MAX_IO_QUEUES as usize + 1;
const SQE_LEN: u64 = 64;
const CQE_LEN: u64 = 16;
const MAX_QUEUE_ENTRIES: u32 = 1024;
const MAX_ADMIN_ENTRIES: u32 = 4096;
const CAP_TIMEOUT: u32 = 1;
const MDTS: u8 = 10;
const MAX_TRANSFER: u64 = 4096 << MDTS;
const MAX_STRUCTURE: u64 = 64 * 1024;
const ZERO_CHUNK: u64 = 64 * 1024;
const MAX_PRP_LISTS: u32 = 64;
const MAX_COMMANDS_PER_RUN: u32 = QUEUE_SLOTS as u32 * MAX_ADMIN_ENTRIES;
const VERSION: u32 = 0x0001_0400;
const REG_CAP: u64 = 0x00;
const REG_VS: u64 = 0x08;
const REG_INTMS: u64 = 0x0c;
const REG_INTMC: u64 = 0x10;
const REG_CC: u64 = 0x14;
const REG_CSTS: u64 = 0x1c;
const REG_NSSR: u64 = 0x20;
const REG_AQA: u64 = 0x24;
const REG_ASQ: u64 = 0x28;
const REG_ACQ: u64 = 0x30;
const CC_EN: u32 = 1 << 0;
const CC_CSS_SHIFT: u32 = 4;
const CC_MPS_SHIFT: u32 = 7;
const CC_SHN_SHIFT: u32 = 15;
const CC_IOSQES_SHIFT: u32 = 17;
const CC_IOCQES_SHIFT: u32 = 21;
const CC_MASK: u32 = 0x01ff_f8f1;
const CSTS_RDY: u32 = 1 << 0;
const CSTS_CFS: u32 = 1 << 1;
const CSTS_SHST_COMPLETE: u32 = 0b10 << 2;
const CSTS_SHST_MASK: u32 = 0b11 << 2;
const ST_SUCCESS: u16 = 0x0000;
const ST_INVALID_OPCODE: u16 = 0x0001;
const ST_INVALID_FIELD: u16 = 0x0002;
const ST_DATA_TRANSFER: u16 = 0x0004;
const ST_INVALID_NAMESPACE: u16 = 0x000b;
const ST_PRP_OFFSET: u16 = 0x0013;
const ST_LBA_RANGE: u16 = 0x0080;
const ST_CQ_INVALID: u16 = 0x0100;
const ST_INVALID_QID: u16 = 0x0101;
const ST_INVALID_QSIZE: u16 = 0x0102;
const ST_AER_LIMIT: u16 = 0x0105;
const ST_INVALID_VECTOR: u16 = 0x0108;
const ST_INVALID_LOG_PAGE: u16 = 0x0109;
const ST_INVALID_QUEUE_DELETION: u16 = 0x010c;
const ST_WRITE_FAULT: u16 = 0x0280;
const ST_UNRECOVERED_READ: u16 = 0x0281;
const ADMIN_DELETE_SQ: u8 = 0x00;
const ADMIN_CREATE_SQ: u8 = 0x01;
const ADMIN_GET_LOG_PAGE: u8 = 0x02;
const ADMIN_DELETE_CQ: u8 = 0x04;
const ADMIN_CREATE_CQ: u8 = 0x05;
const ADMIN_IDENTIFY: u8 = 0x06;
const ADMIN_ABORT: u8 = 0x08;
const ADMIN_SET_FEATURES: u8 = 0x09;
const ADMIN_GET_FEATURES: u8 = 0x0a;
const ADMIN_ASYNC_EVENT: u8 = 0x0c;
const NVM_FLUSH: u8 = 0x00;
const NVM_WRITE: u8 = 0x01;
const NVM_READ: u8 = 0x02;
const NVM_WRITE_ZEROES: u8 = 0x08;
const FEATURE_NUM_QUEUES: u8 = 0x07;
const FEATURE_ASYNC_CONFIG: u8 = 0x0b;
const AER_LIMIT: u32 = 1;
const IDENTIFY_LEN: u64 = 4096;
pub struct Namespace {
media: Arc<dyn Medium>,
lba_shift: u32,
blocks: u64,
read_only: bool,
}
impl fmt::Debug for Namespace {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("Namespace")
.field("blocks", &self.blocks)
.field("lba_bytes", &self.lba_bytes())
.field("read_only", &self.read_only)
.finish()
}
}
impl Namespace {
pub fn new(media: Arc<dyn Medium>, lba_shift: u32, read_only: bool) -> Result<Namespace> {
if !(9..=12).contains(&lba_shift) {
return Err(crate::core::error::Error::Config {
at: String::from("nvme.namespace"),
message: alloc::format!(
"a logical block is 512, 1024, 2048 or 4096 bytes, not 2^{lba_shift}"
),
});
}
let bytes = media.capacity();
let lba = 1u64 << lba_shift;
if bytes == 0 || !bytes.is_multiple_of(lba) {
return Err(crate::core::error::Error::Config {
at: String::from("nvme.namespace"),
message: alloc::format!(
"a namespace holds a whole number of {lba}-byte blocks, and {bytes} bytes is \
not a whole number of them"
),
});
}
Ok(Namespace {
read_only: read_only || media.is_read_only(),
media,
lba_shift,
blocks: bytes / lba,
})
}
#[must_use]
pub fn lba_bytes(&self) -> u64 {
1 << self.lba_shift
}
#[must_use]
pub fn blocks(&self) -> u64 {
self.blocks
}
#[must_use]
pub fn medium(&self) -> &Arc<dyn Medium> {
&self.media
}
#[must_use]
pub fn is_read_only(&self) -> bool {
self.read_only
}
pub fn contents(&self) -> Result<Vec<u8>> {
let mut out = vec![0u8; self.media.capacity() as usize];
self.media
.read_at(0, &mut out)
.map_err(|e| crate::core::error::Error::State(alloc::format!("nvme namespace: {e}")))?;
Ok(out)
}
#[must_use]
pub fn snapshot(&self) -> Snapshot {
self.media.snapshot()
}
#[must_use]
pub fn describe(&self) -> String {
self.media.describe()
}
pub fn flush(&self) -> Result<()> {
self.media
.flush()
.map_err(|e| crate::core::error::Error::State(alloc::format!("nvme namespace: {e}")))
}
pub fn restore(&self, bytes: &[u8]) -> Result<()> {
self.media
.write_at(0, bytes)
.map_err(|e| crate::core::error::Error::State(alloc::format!("nvme namespace: {e}")))
}
fn range(&self, slba: u64, count: u64) -> Option<(u64, u64)> {
let end = slba.checked_add(count)?;
if end > self.blocks {
return None;
}
Some((slba << self.lba_shift, count << self.lba_shift))
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
struct SubQueue {
base: u64,
entries: u32,
head: u32,
tail: u32,
cqid: u16,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
struct CompQueue {
base: u64,
entries: u32,
head: u32,
tail: u32,
phase: bool,
vector: u16,
interrupts: bool,
}
impl CompQueue {
fn has_room(&self) -> bool {
self.entries > 0 && (self.tail + 1) % self.entries != self.head
}
fn pending(&self) -> bool {
self.head != self.tail
}
}
#[derive(Debug, Clone, Copy)]
struct State {
cc: u32,
csts: u32,
intms: u32,
aqa: u32,
asq: u64,
acq: u64,
aer: u32,
sq: [Option<SubQueue>; QUEUE_SLOTS],
cq: [Option<CompQueue>; QUEUE_SLOTS],
}
impl State {
const fn new() -> State {
State {
cc: 0,
csts: 0,
intms: 0,
aqa: 0,
asq: 0,
acq: 0,
aer: 0,
sq: [None; QUEUE_SLOTS],
cq: [None; QUEUE_SLOTS],
}
}
fn page(&self) -> u64 {
1u64 << (12 + ((self.cc >> CC_MPS_SHIFT) & 0xf))
}
fn controller_reset(&mut self) {
self.sq = [None; QUEUE_SLOTS];
self.cq = [None; QUEUE_SLOTS];
self.aer = 0;
self.csts &= !(CSTS_RDY | CSTS_SHST_MASK);
}
}
struct Link {
space: Option<Weak<AddressSpace>>,
irq: Option<WireSource>,
}
impl fmt::Debug for Link {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("Link")
.field("space", &self.space.is_some())
.field("irq", &self.irq.is_some())
.finish()
}
}
#[derive(Debug, Clone)]
pub struct Params {
pub vendor: u16,
pub subsystem_vendor: u16,
pub serial: String,
pub model: String,
pub firmware: String,
pub io_queues: u16,
}
impl Default for Params {
fn default() -> Params {
Params {
vendor: 0,
subsystem_vendor: 0,
serial: String::from("RSEMU0000000000000001"),
model: String::from("RSEMU NVME CONTROLLER"),
firmware: String::from("1.0"),
io_queues: 4,
}
}
}
pub struct Controller {
ns: Namespace,
params: Params,
state: Mutex<State>,
link: Mutex<Link>,
requester: AtomicU32,
irq_level: AtomicU32,
master: AtomicBool,
intx_disabled: AtomicBool,
busy: AtomicBool,
}
impl fmt::Debug for Controller {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let mut s = f.debug_struct("Controller");
s.field("namespace", &self.ns);
match self.state.try_lock() {
Some(state) => s
.field("cc", &state.cc)
.field("csts", &state.csts)
.finish_non_exhaustive(),
None => s.field("state", &"<in use>").finish_non_exhaustive(),
}
}
}
impl Controller {
#[must_use]
pub fn new(ns: Namespace, params: Params) -> Controller {
let params = Params {
io_queues: params.io_queues.clamp(1, MAX_IO_QUEUES),
..params
};
Controller {
ns,
params,
state: Mutex::with_rank(NVME_RANK, State::new()),
link: Mutex::with_rank(
LockRank::WIRE,
Link {
space: None,
irq: None,
},
),
requester: AtomicU32::new(RequesterId::ANONYMOUS.0),
irq_level: AtomicU32::new(0),
master: AtomicBool::new(false),
intx_disabled: AtomicBool::new(false),
busy: AtomicBool::new(false),
}
}
#[must_use]
pub fn namespace(&self) -> &Namespace {
&self.ns
}
#[must_use]
pub fn params(&self) -> &Params {
&self.params
}
pub fn attach_space(&self, space: &Arc<AddressSpace>, requester: RequesterId) {
let mut link = self.link.lock();
link.space = Some(Arc::downgrade(space));
drop(link);
self.requester.store(requester.0, Ordering::Relaxed);
}
pub fn connect_irq(&self, source: WireSource) {
self.link.lock().irq = Some(source);
self.refresh_irq();
}
pub fn set_master(&self, enabled: bool) {
self.master.store(enabled, Ordering::Relaxed);
}
pub fn set_intx_disabled(&self, disabled: bool) {
self.intx_disabled.store(disabled, Ordering::Relaxed);
self.refresh_irq();
}
#[must_use]
pub fn interrupt_pending(&self) -> bool {
self.irq_level.load(Ordering::Relaxed) != 0
}
#[must_use]
pub fn irq_level(&self) -> Level {
Level::from_bool(self.interrupt_pending() && !self.intx_disabled.load(Ordering::Relaxed))
}
fn space(&self) -> Option<Arc<AddressSpace>> {
self.link.lock().space.as_ref().and_then(Weak::upgrade)
}
fn attrs(&self) -> MemAttrs {
MemAttrs::DEFAULT.with_requester(RequesterId(self.requester.load(Ordering::Relaxed)))
}
}
fn le32(bytes: &[u8]) -> u32 {
u32::from_le_bytes([bytes[0], bytes[1], bytes[2], bytes[3]])
}
fn le64(bytes: &[u8]) -> u64 {
let mut out = [0u8; 8];
out.copy_from_slice(&bytes[..8]);
u64::from_le_bytes(out)
}
fn ascii(dst: &mut [u8], text: &str) {
dst.fill(b' ');
for (slot, byte) in dst.iter_mut().zip(text.bytes()) {
*slot = if byte.is_ascii_graphic() || byte == b' ' {
byte
} else {
b' '
};
}
}
#[derive(Debug, Clone, Copy)]
struct Command {
opcode: u8,
psdt: u8,
cid: u16,
nsid: u32,
prp1: u64,
prp2: u64,
cdw10: u32,
cdw11: u32,
cdw12: u32,
}
impl Command {
fn parse(raw: &[u8; SQE_LEN as usize]) -> Command {
let cdw0 = le32(&raw[0..4]);
Command {
opcode: cdw0 as u8,
psdt: ((cdw0 >> 14) & 0x3) as u8,
cid: (cdw0 >> 16) as u16,
nsid: le32(&raw[4..8]),
prp1: le64(&raw[24..32]),
prp2: le64(&raw[32..40]),
cdw10: le32(&raw[40..44]),
cdw11: le32(&raw[44..48]),
cdw12: le32(&raw[48..52]),
}
}
}
#[derive(Debug, Clone, Copy)]
struct Job {
sqid: u16,
cqid: u16,
addr: u64,
sqhd: u32,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
enum After {
Irq,
Shutdown,
Run,
}
impl Controller {
pub fn refresh_irq(&self) {
let pending = {
let state = self.state.lock();
state.csts & CSTS_RDY != 0
&& state.cq.iter().flatten().any(|cq| {
cq.interrupts && cq.pending() && state.intms & (1 << (cq.vector & 31)) == 0
})
};
self.irq_level.store(u32::from(pending), Ordering::Relaxed);
let level = self.irq_level();
let out = self.link.lock().irq.clone();
if let Some(out) = out {
out.set(level);
}
}
pub fn reset(&self) {
*self.state.lock() = State::new();
self.busy.store(false, Ordering::Relaxed);
self.master.store(false, Ordering::Relaxed);
self.intx_disabled.store(false, Ordering::Relaxed);
self.refresh_irq();
}
fn cap(&self) -> u64 {
let low = u64::from(MAX_QUEUE_ENTRIES - 1) | (1 << 16) | (u64::from(CAP_TIMEOUT) << 24);
let high = (1u64 << 5) | (4 << 20);
low | (high << 32)
}
fn read_dword(&self, offset: u64) -> u32 {
match offset {
REG_CAP => self.cap() as u32,
0x04 => (self.cap() >> 32) as u32,
REG_VS => VERSION,
REG_INTMS | REG_INTMC => self.state.lock().intms,
REG_CC => self.state.lock().cc,
REG_CSTS => self.state.lock().csts,
REG_AQA => self.state.lock().aqa,
REG_ASQ => self.state.lock().asq as u32,
0x2c => (self.state.lock().asq >> 32) as u32,
REG_ACQ => self.state.lock().acq as u32,
0x34 => (self.state.lock().acq >> 32) as u32,
REG_NSSR => 0,
_ => 0,
}
}
fn write_dword(&self, offset: u64, value: u32) {
if offset >= DOORBELL_BASE {
self.doorbell(offset, value);
return;
}
let after = {
let mut state = self.state.lock();
match offset {
REG_INTMS => {
state.intms |= value;
After::Irq
}
REG_INTMC => {
state.intms &= !value;
After::Irq
}
REG_CC => Controller::write_cc(&mut state, value),
REG_AQA if state.cc & CC_EN == 0 => {
state.aqa = value & 0x0fff_0fff;
After::Irq
}
REG_ASQ if state.cc & CC_EN == 0 => {
state.asq = (state.asq & 0xffff_ffff_0000_0000) | u64::from(value);
After::Irq
}
0x2c if state.cc & CC_EN == 0 => {
state.asq = (state.asq & 0xffff_ffff) | (u64::from(value) << 32);
After::Irq
}
REG_ACQ if state.cc & CC_EN == 0 => {
state.acq = (state.acq & 0xffff_ffff_0000_0000) | u64::from(value);
After::Irq
}
0x34 if state.cc & CC_EN == 0 => {
state.acq = (state.acq & 0xffff_ffff) | (u64::from(value) << 32);
After::Irq
}
_ => After::Irq,
}
};
match after {
After::Irq => self.refresh_irq(),
After::Run => {
self.run();
self.refresh_irq();
}
After::Shutdown => self.shutdown(),
}
}
fn write_cc(state: &mut State, value: u32) -> After {
let value = value & CC_MASK;
let shn = (value >> CC_SHN_SHIFT) & 0x3;
let enabled = state.cc & CC_EN != 0;
let wanted = value & CC_EN != 0;
match (enabled, wanted) {
(false, true) => {
state.cc = value;
Controller::enable(state);
After::Irq
}
(true, false) => {
state.cc = value;
state.controller_reset();
After::Irq
}
(true, true) => {
const SHN_MASK: u32 = 0x3 << CC_SHN_SHIFT;
state.cc = (state.cc & !SHN_MASK) | (value & SHN_MASK);
if shn != 0 {
After::Shutdown
} else {
After::Irq
}
}
(false, false) => {
state.cc = value;
After::Irq
}
}
}
fn enable(state: &mut State) {
let iosqes = (state.cc >> CC_IOSQES_SHIFT) & 0xf;
let iocqes = (state.cc >> CC_IOCQES_SHIFT) & 0xf;
let mps = (state.cc >> CC_MPS_SHIFT) & 0xf;
let css = (state.cc >> CC_CSS_SHIFT) & 0x7;
let asqs = (state.aqa & 0xfff) + 1;
let acqs = ((state.aqa >> 16) & 0xfff) + 1;
let page = 1u64 << (12 + mps);
let ok = iosqes == 6
&& iocqes == 4
&& css == 0
&& mps <= 4
&& asqs >= 2
&& acqs >= 2
&& asqs <= MAX_ADMIN_ENTRIES
&& acqs <= MAX_ADMIN_ENTRIES
&& state.asq != 0
&& state.acq != 0
&& state.asq.is_multiple_of(page)
&& state.acq.is_multiple_of(page);
if !ok {
state.csts |= CSTS_CFS;
return;
}
state.sq[0] = Some(SubQueue {
base: state.asq,
entries: asqs,
head: 0,
tail: 0,
cqid: 0,
});
state.cq[0] = Some(CompQueue {
base: state.acq,
entries: acqs,
head: 0,
tail: 0,
phase: true,
vector: 0,
interrupts: true,
});
state.csts |= CSTS_RDY;
}
fn shutdown(&self) {
let _ = self.ns.media.flush();
{
let mut state = self.state.lock();
state.csts = (state.csts & !CSTS_SHST_MASK) | CSTS_SHST_COMPLETE;
}
self.refresh_irq();
}
fn doorbell(&self, offset: u64, value: u32) {
let byte = offset - DOORBELL_BASE;
if !byte.is_multiple_of(DOORBELL_STRIDE) {
return;
}
let index = byte / DOORBELL_STRIDE;
let qid = (index / 2) as usize;
let is_cq = index % 2 == 1;
if qid >= QUEUE_SLOTS {
return;
}
let after = {
let mut state = self.state.lock();
if state.csts & CSTS_RDY == 0 {
return;
}
let slot = if is_cq {
state.cq[qid].map(|cq| cq.entries)
} else {
state.sq[qid].map(|sq| sq.entries)
};
match slot {
Some(entries) if value >= entries => {
state.csts |= CSTS_CFS;
After::Irq
}
Some(_) if is_cq => {
if let Some(cq) = state.cq[qid].as_mut() {
cq.head = value;
}
After::Run
}
Some(_) => {
if let Some(sq) = state.sq[qid].as_mut() {
sq.tail = value;
}
After::Run
}
None => return,
}
};
match after {
After::Run => {
self.run();
self.refresh_irq();
}
_ => self.refresh_irq(),
}
}
fn has_work(&self) -> bool {
let state = self.state.lock();
Controller::pick(&state).is_some()
}
fn pick(state: &State) -> Option<(usize, SubQueue)> {
if state.csts & (CSTS_RDY | CSTS_CFS) != CSTS_RDY {
return None;
}
for (qid, slot) in state.sq.iter().enumerate() {
let Some(sq) = *slot else { continue };
if sq.head == sq.tail || sq.entries == 0 {
continue;
}
if let Some(cq) = state.cq.get(usize::from(sq.cqid)).copied().flatten()
&& cq.has_room()
{
return Some((qid, sq));
}
}
None
}
fn next_job(&self) -> Option<Job> {
let mut state = self.state.lock();
let (qid, sq) = Controller::pick(&state)?;
let addr = sq.base + u64::from(sq.head) * SQE_LEN;
let head = (sq.head + 1) % sq.entries;
if let Some(slot) = state.sq[qid].as_mut() {
slot.head = head;
}
Some(Job {
sqid: qid as u16,
cqid: sq.cqid,
addr,
sqhd: head,
})
}
pub fn run(&self) {
if !self.master.load(Ordering::Relaxed) {
return;
}
if self.busy.swap(true, Ordering::AcqRel) {
return;
}
let mut budget = MAX_COMMANDS_PER_RUN;
loop {
while let Some(job) = self.next_job() {
self.execute(&job);
budget -= 1;
if budget == 0 {
break;
}
}
self.busy.store(false, Ordering::Release);
if budget == 0 || !self.has_work() || self.busy.swap(true, Ordering::AcqRel) {
break;
}
}
}
fn execute(&self, job: &Job) {
let Some(space) = self.space() else {
self.fatal();
return;
};
let mut raw = [0u8; SQE_LEN as usize];
if space.read_bytes(job.addr, &mut raw, self.attrs()).is_err() {
self.fatal();
return;
}
let cmd = Command::parse(&raw);
let done = if job.sqid == 0 {
self.admin(&space, &cmd)
} else {
self.nvm(&space, &cmd)
};
if let Some((status, dw0)) = done {
self.complete(&space, job, cmd.cid, status, dw0);
}
}
fn complete(&self, space: &AddressSpace, job: &Job, cid: u16, status: u16, dw0: u32) {
let placed = {
let mut state = self.state.lock();
match state
.cq
.get_mut(usize::from(job.cqid))
.and_then(Option::as_mut)
{
Some(cq) if cq.has_room() => {
let addr = cq.base + u64::from(cq.tail) * CQE_LEN;
let phase = cq.phase;
cq.tail += 1;
if cq.tail == cq.entries {
cq.tail = 0;
cq.phase = !cq.phase;
}
Some((addr, phase))
}
_ => None,
}
};
let Some((addr, phase)) = placed else {
return;
};
let mut entry = [0u8; CQE_LEN as usize];
entry[0..4].copy_from_slice(&dw0.to_le_bytes());
let dw2 = u32::from(job.sqhd as u16) | (u32::from(job.sqid) << 16);
entry[8..12].copy_from_slice(&dw2.to_le_bytes());
let dw3 = u32::from(cid) | (u32::from(phase) << 16) | (u32::from(status) << 17);
entry[12..16].copy_from_slice(&dw3.to_le_bytes());
if space.write_bytes(addr, &entry, self.attrs()).is_err() {
self.fatal();
}
}
fn fatal(&self) {
self.state.lock().csts |= CSTS_CFS;
}
}
impl Controller {
fn prp_chunks(
&self,
space: &AddressSpace,
prp1: u64,
prp2: u64,
len: u64,
page: u64,
) -> core::result::Result<Vec<(u64, u64)>, u16> {
let mut out: Vec<(u64, u64)> = Vec::new();
if len == 0 {
return Ok(out);
}
let offset = prp1 & (page - 1);
if !offset.is_multiple_of(4) {
return Err(ST_PRP_OFFSET);
}
let first = core::cmp::min(len, page - offset);
out.push((prp1, first));
let mut remaining = len - first;
if remaining == 0 {
return Ok(out);
}
if !prp2.is_multiple_of(page) {
return Err(ST_PRP_OFFSET);
}
if remaining <= page {
out.push((prp2, remaining));
return Ok(out);
}
let per_page = page / 8;
let mut list = prp2;
let mut lists = 0u32;
loop {
lists += 1;
if lists > MAX_PRP_LISTS {
return Err(ST_DATA_TRANSFER);
}
let mut chained = false;
for i in 0..per_page {
let mut raw = [0u8; 8];
if space
.read_bytes(list + i * 8, &mut raw, self.attrs())
.is_err()
{
return Err(ST_DATA_TRANSFER);
}
let entry = u64::from_le_bytes(raw);
if !entry.is_multiple_of(page) {
return Err(ST_PRP_OFFSET);
}
if i + 1 == per_page && remaining > page {
list = entry;
chained = true;
break;
}
let take = core::cmp::min(remaining, page);
out.push((entry, take));
remaining -= take;
if remaining == 0 {
return Ok(out);
}
}
if !chained {
return Err(ST_DATA_TRANSFER);
}
}
}
fn scatter(&self, space: &AddressSpace, cmd: &Command, data: &[u8], page: u64) -> u16 {
let chunks = match self.prp_chunks(space, cmd.prp1, cmd.prp2, data.len() as u64, page) {
Ok(chunks) => chunks,
Err(status) => return status,
};
let mut at = 0usize;
for (addr, len) in chunks {
let n = len as usize;
if space
.write_bytes(addr, &data[at..at + n], self.attrs())
.is_err()
{
return ST_DATA_TRANSFER;
}
at += n;
}
ST_SUCCESS
}
fn transfer(
&self,
space: &AddressSpace,
cmd: &Command,
offset: u64,
len: u64,
write: bool,
) -> u16 {
let chunks = match self.prp_chunks(space, cmd.prp1, cmd.prp2, len, self.page()) {
Ok(chunks) => chunks,
Err(status) => return status,
};
let mut at = offset;
for (addr, chunk) in chunks {
let mut buf = vec![0u8; chunk as usize];
if write {
if space.read_bytes(addr, &mut buf, self.attrs()).is_err() {
return ST_DATA_TRANSFER;
}
if self.ns.media.write_at(at, &buf).is_err() {
return ST_WRITE_FAULT;
}
} else {
if self.ns.media.read_at(at, &mut buf).is_err() {
return ST_UNRECOVERED_READ;
}
if space.write_bytes(addr, &buf, self.attrs()).is_err() {
return ST_DATA_TRANSFER;
}
}
at += chunk;
}
ST_SUCCESS
}
fn page(&self) -> u64 {
self.state.lock().page()
}
}
impl Controller {
fn admin(&self, space: &AddressSpace, cmd: &Command) -> Option<(u16, u32)> {
Some(match cmd.opcode {
ADMIN_CREATE_CQ => self.create_cq(cmd),
ADMIN_CREATE_SQ => self.create_sq(cmd),
ADMIN_DELETE_CQ => self.delete_cq(cmd),
ADMIN_DELETE_SQ => self.delete_sq(cmd),
ADMIN_IDENTIFY => self.identify(space, cmd),
ADMIN_SET_FEATURES => self.set_features(cmd),
ADMIN_GET_FEATURES => self.get_features(cmd),
ADMIN_GET_LOG_PAGE => self.get_log_page(space, cmd),
ADMIN_ABORT => (ST_SUCCESS, 1),
ADMIN_ASYNC_EVENT => return self.async_event(),
_ => (ST_INVALID_OPCODE, 0),
})
}
fn create_cq(&self, cmd: &Command) -> (u16, u32) {
let qid = (cmd.cdw10 & 0xffff) as usize;
let entries = ((cmd.cdw10 >> 16) & 0xffff) + 1;
let contiguous = cmd.cdw11 & 1 != 0;
let interrupts = cmd.cdw11 & 2 != 0;
let vector = (cmd.cdw11 >> 16) as u16;
if qid == 0 || qid > usize::from(self.params.io_queues) {
return (ST_INVALID_QID, 0);
}
if !(2..=MAX_QUEUE_ENTRIES).contains(&entries) {
return (ST_INVALID_QSIZE, 0);
}
if !contiguous {
return (ST_INVALID_FIELD, 0);
}
if vector >= 32 {
return (ST_INVALID_VECTOR, 0);
}
let page = self.page();
if !cmd.prp1.is_multiple_of(page) || cmd.prp1 == 0 {
return (ST_PRP_OFFSET, 0);
}
let mut state = self.state.lock();
if state.cq[qid].is_some() {
return (ST_INVALID_QID, 0);
}
state.cq[qid] = Some(CompQueue {
base: cmd.prp1,
entries,
head: 0,
tail: 0,
phase: true,
vector,
interrupts,
});
(ST_SUCCESS, 0)
}
fn create_sq(&self, cmd: &Command) -> (u16, u32) {
let qid = (cmd.cdw10 & 0xffff) as usize;
let entries = ((cmd.cdw10 >> 16) & 0xffff) + 1;
let contiguous = cmd.cdw11 & 1 != 0;
let cqid = (cmd.cdw11 >> 16) as u16;
if qid == 0 || qid > usize::from(self.params.io_queues) {
return (ST_INVALID_QID, 0);
}
if !(2..=MAX_QUEUE_ENTRIES).contains(&entries) {
return (ST_INVALID_QSIZE, 0);
}
if !contiguous {
return (ST_INVALID_FIELD, 0);
}
let page = self.page();
if !cmd.prp1.is_multiple_of(page) || cmd.prp1 == 0 {
return (ST_PRP_OFFSET, 0);
}
let mut state = self.state.lock();
if usize::from(cqid) >= QUEUE_SLOTS || state.cq[usize::from(cqid)].is_none() {
return (ST_CQ_INVALID, 0);
}
if state.sq[qid].is_some() {
return (ST_INVALID_QID, 0);
}
state.sq[qid] = Some(SubQueue {
base: cmd.prp1,
entries,
head: 0,
tail: 0,
cqid,
});
(ST_SUCCESS, 0)
}
fn delete_sq(&self, cmd: &Command) -> (u16, u32) {
let qid = (cmd.cdw10 & 0xffff) as usize;
if qid == 0 || qid >= QUEUE_SLOTS {
return (ST_INVALID_QID, 0);
}
let mut state = self.state.lock();
if state.sq[qid].take().is_none() {
return (ST_INVALID_QID, 0);
}
(ST_SUCCESS, 0)
}
fn delete_cq(&self, cmd: &Command) -> (u16, u32) {
let qid = (cmd.cdw10 & 0xffff) as usize;
if qid == 0 || qid >= QUEUE_SLOTS {
return (ST_INVALID_QID, 0);
}
let mut state = self.state.lock();
if state.cq[qid].is_none() {
return (ST_INVALID_QID, 0);
}
if state
.sq
.iter()
.flatten()
.any(|sq| usize::from(sq.cqid) == qid)
{
return (ST_INVALID_QUEUE_DELETION, 0);
}
state.cq[qid] = None;
(ST_SUCCESS, 0)
}
fn identify(&self, space: &AddressSpace, cmd: &Command) -> (u16, u32) {
if cmd.psdt != 0 {
return (ST_INVALID_FIELD, 0);
}
let data = match cmd.cdw10 & 0xff {
0x00 => {
if cmd.nsid != 1 {
return (ST_INVALID_NAMESPACE, 0);
}
self.identify_namespace()
}
0x01 => self.identify_controller(),
0x02 => {
let mut list = vec![0u8; IDENTIFY_LEN as usize];
if cmd.nsid < 1 {
list[0..4].copy_from_slice(&1u32.to_le_bytes());
}
list
}
_ => return (ST_INVALID_FIELD, 0),
};
(self.scatter(space, cmd, &data, self.page()), 0)
}
fn set_features(&self, cmd: &Command) -> (u16, u32) {
match (cmd.cdw10 & 0xff) as u8 {
FEATURE_NUM_QUEUES => (ST_SUCCESS, self.allocated_queues()),
FEATURE_ASYNC_CONFIG => (ST_SUCCESS, 0),
_ => (ST_INVALID_FIELD, 0),
}
}
fn get_features(&self, cmd: &Command) -> (u16, u32) {
match (cmd.cdw10 & 0xff) as u8 {
FEATURE_NUM_QUEUES => (ST_SUCCESS, self.allocated_queues()),
FEATURE_ASYNC_CONFIG => (ST_SUCCESS, 0),
_ => (ST_INVALID_FIELD, 0),
}
}
fn allocated_queues(&self) -> u32 {
let n = u32::from(self.params.io_queues - 1);
n | (n << 16)
}
fn get_log_page(&self, space: &AddressSpace, cmd: &Command) -> (u16, u32) {
if cmd.psdt != 0 {
return (ST_INVALID_FIELD, 0);
}
let lid = (cmd.cdw10 & 0xff) as u8;
let numd = u64::from((cmd.cdw10 >> 16) & 0xffff) | (u64::from(cmd.cdw11 & 0xffff) << 16);
let len = (numd + 1) * 4;
if !matches!(lid, 0x01..=0x03) {
return (ST_INVALID_LOG_PAGE, 0);
}
if len > MAX_STRUCTURE {
return (ST_INVALID_FIELD, 0);
}
let data = vec![0u8; len as usize];
(self.scatter(space, cmd, &data, self.page()), 0)
}
fn async_event(&self) -> Option<(u16, u32)> {
let mut state = self.state.lock();
if state.aer >= AER_LIMIT {
return Some((ST_AER_LIMIT, 0));
}
state.aer += 1;
None
}
fn identify_controller(&self) -> Vec<u8> {
let mut d = vec![0u8; IDENTIFY_LEN as usize];
d[0..2].copy_from_slice(&self.params.vendor.to_le_bytes());
d[2..4].copy_from_slice(&self.params.subsystem_vendor.to_le_bytes());
ascii(&mut d[4..24], &self.params.serial);
ascii(&mut d[24..64], &self.params.model);
ascii(&mut d[64..72], &self.params.firmware);
d[77] = MDTS;
d[80..84].copy_from_slice(&VERSION.to_le_bytes());
d[258] = 3;
d[259] = (AER_LIMIT - 1) as u8;
d[512] = 0x66;
d[513] = 0x44;
d[514..516].copy_from_slice(&1u16.to_le_bytes());
d[516..520].copy_from_slice(&1u32.to_le_bytes());
d[520..522].copy_from_slice(&0x0008u16.to_le_bytes());
d[525] = 1;
d
}
fn identify_namespace(&self) -> Vec<u8> {
let mut d = vec![0u8; IDENTIFY_LEN as usize];
let blocks = self.ns.blocks();
d[0..8].copy_from_slice(&blocks.to_le_bytes());
d[8..16].copy_from_slice(&blocks.to_le_bytes());
d[16..24].copy_from_slice(&blocks.to_le_bytes());
d[25] = 0;
d[26] = 0;
d[99] = u8::from(self.ns.is_read_only());
let lbaf0 = (self.ns.lba_shift) << 16;
d[128..132].copy_from_slice(&lbaf0.to_le_bytes());
d
}
}
impl Controller {
fn nvm(&self, space: &AddressSpace, cmd: &Command) -> Option<(u16, u32)> {
let all = cmd.nsid == 0xffff_ffff;
if cmd.nsid != 1 && !(all && cmd.opcode == NVM_FLUSH) {
return Some((ST_INVALID_NAMESPACE, 0));
}
Some(match cmd.opcode {
NVM_FLUSH => match self.ns.media.flush() {
Ok(()) => (ST_SUCCESS, 0),
Err(_) => (ST_WRITE_FAULT, 0),
},
NVM_READ => self.read(space, cmd),
NVM_WRITE => self.write(space, cmd),
NVM_WRITE_ZEROES => self.write_zeroes(cmd),
_ => (ST_INVALID_OPCODE, 0),
})
}
fn extent(cmd: &Command) -> (u64, u64) {
let slba = u64::from(cmd.cdw10) | (u64::from(cmd.cdw11) << 32);
let blocks = u64::from(cmd.cdw12 & 0xffff) + 1;
(slba, blocks)
}
fn read(&self, space: &AddressSpace, cmd: &Command) -> (u16, u32) {
if cmd.psdt != 0 {
return (ST_INVALID_FIELD, 0);
}
let (slba, blocks) = Controller::extent(cmd);
let Some((offset, len)) = self.ns.range(slba, blocks) else {
return (ST_LBA_RANGE, 0);
};
if len > MAX_TRANSFER {
return (ST_INVALID_FIELD, 0);
}
(self.transfer(space, cmd, offset, len, false), 0)
}
fn write(&self, space: &AddressSpace, cmd: &Command) -> (u16, u32) {
if cmd.psdt != 0 {
return (ST_INVALID_FIELD, 0);
}
let (slba, blocks) = Controller::extent(cmd);
let Some((offset, len)) = self.ns.range(slba, blocks) else {
return (ST_LBA_RANGE, 0);
};
if len > MAX_TRANSFER {
return (ST_INVALID_FIELD, 0);
}
if self.ns.is_read_only() {
return (ST_WRITE_FAULT, 0);
}
(self.transfer(space, cmd, offset, len, true), 0)
}
fn write_zeroes(&self, cmd: &Command) -> (u16, u32) {
let (slba, blocks) = Controller::extent(cmd);
let Some((offset, len)) = self.ns.range(slba, blocks) else {
return (ST_LBA_RANGE, 0);
};
if self.ns.is_read_only() {
return (ST_WRITE_FAULT, 0);
}
let zeroes = vec![0u8; ZERO_CHUNK as usize];
let mut at = 0u64;
while at < len {
let n = core::cmp::min(ZERO_CHUNK, len - at);
if self
.ns
.media
.write_at(offset + at, &zeroes[..n as usize])
.is_err()
{
return (ST_WRITE_FAULT, 0);
}
at += n;
}
(ST_SUCCESS, 0)
}
}
impl Controller {
pub fn save<S: Sink + ?Sized>(&self, w: &mut S) -> Result<()> {
let state = *self.state.lock();
w.write_u32(state.cc)?;
w.write_u32(state.csts)?;
w.write_u32(state.intms)?;
w.write_u32(state.aqa)?;
w.write_u64(state.asq)?;
w.write_u64(state.acq)?;
w.write_u32(state.aer)?;
for slot in &state.sq {
match slot {
Some(sq) => {
w.write_bool(true)?;
w.write_u64(sq.base)?;
w.write_u32(sq.entries)?;
w.write_u32(sq.head)?;
w.write_u32(sq.tail)?;
w.write_u16(sq.cqid)?;
}
None => w.write_bool(false)?,
}
}
for slot in &state.cq {
match slot {
Some(cq) => {
w.write_bool(true)?;
w.write_u64(cq.base)?;
w.write_u32(cq.entries)?;
w.write_u32(cq.head)?;
w.write_u32(cq.tail)?;
w.write_u16(cq.vector)?;
w.write_bool(cq.phase)?;
w.write_bool(cq.interrupts)?;
}
None => w.write_bool(false)?,
}
}
Ok(())
}
pub fn load<'a, S: Source<'a> + ?Sized>(&self, r: &mut S) -> Result<()> {
let mut state = State::new();
state.cc = r.read_u32()? & CC_MASK;
state.csts = r.read_u32()? & 0x3f;
state.intms = r.read_u32()?;
state.aqa = r.read_u32()? & 0x0fff_0fff;
state.asq = r.read_u64()?;
state.acq = r.read_u64()?;
state.aer = r.read_u32()?.min(AER_LIMIT);
let bad = |what: &str| crate::core::error::Error::State(alloc::format!("nvme: {what}"));
for slot in &mut state.sq {
if !r.read_bool()? {
continue;
}
let sq = SubQueue {
base: r.read_u64()?,
entries: r.read_u32()?,
head: r.read_u32()?,
tail: r.read_u32()?,
cqid: r.read_u16()?,
};
if !(2..=MAX_ADMIN_ENTRIES).contains(&sq.entries)
|| sq.head >= sq.entries
|| sq.tail >= sq.entries
|| usize::from(sq.cqid) >= QUEUE_SLOTS
{
return Err(bad("a submission queue no controller could have built"));
}
*slot = Some(sq);
}
for slot in &mut state.cq {
if !r.read_bool()? {
continue;
}
let cq = CompQueue {
base: r.read_u64()?,
entries: r.read_u32()?,
head: r.read_u32()?,
tail: r.read_u32()?,
vector: r.read_u16()?,
phase: r.read_bool()?,
interrupts: r.read_bool()?,
};
if !(2..=MAX_ADMIN_ENTRIES).contains(&cq.entries)
|| cq.head >= cq.entries
|| cq.tail >= cq.entries
|| cq.vector >= 32
{
return Err(bad("a completion queue no controller could have built"));
}
*slot = Some(cq);
}
for sq in state.sq.iter().flatten() {
if state.cq[usize::from(sq.cqid)].is_none() {
return Err(bad(
"a submission queue names a completion queue that is not there",
));
}
}
*self.state.lock() = state;
self.refresh_irq();
Ok(())
}
}
impl MemOps for Controller {
fn read(&self, offset: u64, dst: &mut [u8], _attrs: MemAttrs) -> MemResult {
if offset.saturating_add(dst.len() as u64) > REGISTER_LEN {
return Err(BusError::BadAccess);
}
match dst.len() {
4 => {
if !offset.is_multiple_of(4) {
return Err(BusError::BadAccess);
}
dst.copy_from_slice(&self.read_dword(offset).to_le_bytes());
}
8 => {
if !offset.is_multiple_of(8) {
return Err(BusError::BadAccess);
}
let value = u64::from(self.read_dword(offset))
| (u64::from(self.read_dword(offset + 4)) << 32);
dst.copy_from_slice(&value.to_le_bytes());
}
_ => return Err(BusError::BadAccess),
}
Ok(())
}
fn write(&self, offset: u64, src: &[u8], attrs: MemAttrs) -> MemResult {
if attrs.debug {
return Err(BusError::BadAccess);
}
if offset.saturating_add(src.len() as u64) > REGISTER_LEN {
return Err(BusError::BadAccess);
}
match src.len() {
4 => {
if !offset.is_multiple_of(4) {
return Err(BusError::BadAccess);
}
self.write_dword(offset, le32(src));
}
8 => {
if !offset.is_multiple_of(8) {
return Err(BusError::BadAccess);
}
let value = le64(src);
self.write_dword(offset, value as u32);
self.write_dword(offset + 4, (value >> 32) as u32);
}
_ => return Err(BusError::BadAccess),
}
Ok(())
}
fn constraints(&self) -> AccessConstraints {
AccessConstraints::IO
.with_widths(Width::U32, Width::U64)
.with_natural_alignment(true)
.with_endian(Endian::Little)
}
}