pub mod ctrl;
#[cfg(test)]
mod tests;
pub use ctrl::{Controller, MAX_IO_QUEUES, NVME_RANK, Namespace, Params, REGISTER_LEN};
use alloc::boxed::Box;
use alloc::string::{String, ToString};
use alloc::sync::Arc;
use core::fmt;
use crate::bus::pci::{Bar, Bars, Bdf, ConfigSpace, PciBus, PciFunction, buses, config};
use crate::core::device::{Device, DeviceClass, PropertySpec, RealizeCtx, ResetKind};
use crate::core::error::{Error, Result};
use crate::core::props::{Props, ValueKind};
use crate::core::space::{AddressSpace, MemAttrs, Perms, RamStore, Region, RegionRef};
use crate::core::state::{ChunkReader, ChunkWriter, Sink, Source};
use crate::core::sync::{LockRank, Mutex};
use crate::core::wire::WireSource;
use crate::dev::ata::{Medium, Snapshot, medium};
use crate::machine::realize::{BindCtx, Instance};
use crate::machine::validate::ClassSchema;
pub const CLASS_NAME: &str = "nvme.controller";
const STATE_VERSION: u32 = 1;
pub const DEFAULT_SLOT: &str = "nvme0";
pub mod pin {
pub const IRQ: &str = "irq";
}
const COMMAND_IMPLEMENTED: u16 = config::COMMAND_MEMORY | config::COMMAND_MASTER | COMMAND_INTX_OFF;
const COMMAND_INTX_OFF: u16 = 0x0400;
const STATUS_INTERRUPT: u8 = 0x08;
const CLASS_STORAGE: u8 = 0x01;
const SUBCLASS_NVM: u8 = 0x08;
const PROGIF_NVME: u8 = 0x02;
struct Function {
config: Mutex<ConfigSpace>,
bars: Bars,
ctrl: Arc<Controller>,
}
impl fmt::Debug for Function {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let mut s = f.debug_struct("Function");
match self.config.try_lock() {
Some(c) => s.field(
"command",
&(u16::from(c.byte(config::COMMAND)) | u16::from(c.byte(config::COMMAND + 1)) << 8),
),
None => s.field("command", &"<in use>"),
};
s.field("bars", &self.bars).finish()
}
}
impl Function {
fn fresh_config(vendor: u16, device: u16, revision: u8) -> ConfigSpace {
let mut c = ConfigSpace::new();
c.hardwire(config::VENDOR_ID, u32::from(vendor), 2);
c.hardwire(config::DEVICE_ID, u32::from(device), 2);
c.hardwire(config::COMMAND, 0x0000, 2);
c.hardwire(config::STATUS, 0x0200, 2);
c.hardwire(config::REVISION_ID, u32::from(revision), 1);
c.hardwire(config::CLASS_CODE, u32::from(PROGIF_NVME), 1);
c.hardwire(config::CLASS_CODE + 1, u32::from(SUBCLASS_NVM), 1);
c.hardwire(config::CLASS_CODE + 2, u32::from(CLASS_STORAGE), 1);
c.hardwire(config::HEADER_TYPE, 0x00, 1);
c.hardwire(config::INTERRUPT_PIN, 0x01, 1);
c.allow(config::COMMAND, 2);
c.allow(config::CACHE_LINE_SIZE, 1);
c.allow(config::LATENCY_TIMER, 1);
c.allow(config::INTERRUPT_LINE, 1);
c
}
fn command(&self) -> u16 {
let c = self.config.lock();
u16::from(c.byte(config::COMMAND)) | u16::from(c.byte(config::COMMAND + 1)) << 8
}
fn apply_command(&self, command: u16) {
self.ctrl.set_master(command & config::COMMAND_MASTER != 0);
self.ctrl.set_intx_disabled(command & COMMAND_INTX_OFF != 0);
}
}
impl PciFunction for Function {
fn config_read(&self, offset: u16, dst: &mut [u8], _attrs: MemAttrs) {
self.config.lock().read(offset, dst);
self.bars.config_read(offset, dst);
if self.ctrl.interrupt_pending() {
for (i, slot) in dst.iter_mut().enumerate() {
if offset.saturating_add(i as u16) == config::STATUS {
*slot |= STATUS_INTERRUPT;
}
}
}
if self.bars.is_stale() {
self.bars.sync(self.command(), false);
}
}
fn config_write(&self, offset: u16, src: &[u8], attrs: MemAttrs) {
if attrs.debug {
return;
}
let bars_moved = self.bars.config_write(offset, src);
let (command_moved, command) = {
let mut c = self.config.lock();
let moved = c.write(offset, src);
let raw =
u16::from(c.byte(config::COMMAND)) | u16::from(c.byte(config::COMMAND + 1)) << 8;
let kept = raw & COMMAND_IMPLEMENTED;
if kept != raw {
c.set_byte(config::COMMAND, kept as u8);
c.set_byte(config::COMMAND + 1, (kept >> 8) as u8);
}
(
moved
&& offset < config::COMMAND + 2
&& offset.saturating_add(src.len() as u16) > config::COMMAND,
kept,
)
};
if command_moved {
self.apply_command(command);
}
if bars_moved || command_moved || self.bars.is_stale() {
self.bars.sync(command, false);
}
}
}
#[derive(Debug)]
pub struct Nvme {
regs: Arc<Function>,
ctrl: Arc<Controller>,
bus: Arc<PciBus>,
at: Bdf,
vendor: u16,
device: u16,
revision: u8,
}
impl Nvme {
pub fn new(props: &Props) -> Result<Nvme> {
let mut r = props.reader();
let bus_name = r.or_str("bus", "pci0")?.to_string();
let device_no = r.or_range("device", 4u64, 0..=u64::from(crate::bus::pci::MAX_DEVICE))?;
let function_no = r.or_range(
"function",
0u64,
0..=u64::from(crate::bus::pci::MAX_FUNCTION),
)?;
let vendor = r.or_range("vendor-id", 0x1234u64, 0..=0xffff)?;
let device = r.or_range("device-id", 0x1122u64, 0..=0xffff)?;
let revision = r.or_range("revision", 0u64, 0..=255)?;
let size = r.or_size("size", 0)?;
let block = r.or_range("block", 512u64, 512..=4096)?;
let read_only = r.or("readonly", false)?;
let queues = r.or_range("queues", 4u64, 1..=u64::from(MAX_IO_QUEUES))?;
let serial = r.or_str("serial", "RSEMU0000000000000001")?.to_string();
let model = r.or_str("model", "RSEMU NVME CONTROLLER")?.to_string();
let firmware = r.or_str("firmware", "1.0")?.to_string();
let media = r.optional_media("image")?;
let slot = media.map(crate::core::props::Media::name);
let image = media.map(crate::core::props::Media::to_bytes);
r.finish()?;
if !block.is_power_of_two() {
return Err(Error::Config {
at: String::from(CLASS_NAME),
message: alloc::format!("a logical block is a power of two, and {block} is not"),
});
}
let lba_shift = block.trailing_zeros();
let supplied = match props.hosts() {
Some(hosts) => {
let name = slot.unwrap_or(DEFAULT_SLOT);
medium::get(hosts, name)?.and_then(|slot| slot.take())
}
None => None,
};
let bytes = match (&supplied, size, image.as_ref()) {
(Some(medium), _, _) => medium.capacity(),
(None, 0, Some(image)) => image.len() as u64,
(None, size, _) => size,
};
if bytes == 0 {
return Err(Error::Config {
at: String::from(CLASS_NAME),
message: String::from(
"a controller with no namespace has nothing to do: give it `size`, an `image` \
with bytes behind it, or a medium installed under its media slot",
),
});
}
let media: Arc<dyn Medium> = match supplied {
Some(medium) => medium,
None => {
let store = RamStore::new(bytes);
if let Some(image) = image {
if image.len() as u64 > bytes {
return Err(Error::Config {
at: String::from(CLASS_NAME),
message: alloc::format!(
"the bound image is {} byte(s) and the namespace holds {bytes}",
image.len()
),
});
}
RamStore::write_at(&store, 0, &image).map_err(|e| Error::Config {
at: String::from(CLASS_NAME),
message: alloc::format!("the bound image did not fit: {e}"),
})?;
}
Arc::new(store)
}
};
let ns = Namespace::new(media, lba_shift, read_only)?;
let bus = buses::attach(props, &bus_name)?;
let at = Bdf::new(0, device_no as u8, function_no as u8)?;
Nvme::with_bus(
bus,
at,
vendor as u16,
device as u16,
revision as u8,
ns,
Params {
vendor: vendor as u16,
subsystem_vendor: vendor as u16,
serial,
model,
firmware,
io_queues: queues as u16,
},
)
}
pub fn with_bus(
bus: Arc<PciBus>,
at: Bdf,
vendor: u16,
device: u16,
revision: u8,
ns: Namespace,
params: Params,
) -> Result<Nvme> {
let ctrl = Arc::new(Controller::new(ns, params));
let region: RegionRef = Arc::new(Region::io(
"nvme.regs",
REGISTER_LEN,
Arc::clone(&ctrl) as Arc<dyn crate::core::space::MemOps>,
));
let bars = Bars::new().with(
0,
Bar::memory(REGISTER_LEN).wide().decoding(region, Perms::RW),
)?;
Ok(Nvme {
regs: Arc::new(Function {
config: Mutex::with_rank(
LockRank::DEVICE,
Function::fresh_config(vendor, device, revision),
),
bars,
ctrl: Arc::clone(&ctrl),
}),
ctrl,
bus,
at,
vendor,
device,
revision,
})
}
#[must_use]
pub fn address(&self) -> Bdf {
self.at
}
#[must_use]
pub fn controller(&self) -> &Arc<Controller> {
&self.ctrl
}
#[must_use]
pub fn bars(&self) -> &Bars {
&self.regs.bars
}
#[must_use]
pub fn command(&self) -> u16 {
self.regs.command()
}
pub fn attach_space(
&self,
space: &Arc<AddressSpace>,
requester: crate::core::space::RequesterId,
) -> Result<()> {
self.ctrl.attach_space(space, requester);
self.regs.bars.install(space, self.regs.command())
}
}
pub static CLASS: DeviceClass = DeviceClass {
name: CLASS_NAME,
version: STATE_VERSION,
summary: "an NVM Express controller as a PCI function: the NVMe 1.4 register block, the \
admin and NVM command sets, and a PRP-walking bus master over one namespace",
properties: &[
PropertySpec {
name: "bus",
kind: ValueKind::Str,
required: false,
summary: "the PCI fabric this controller is on (default `pci0`)",
},
PropertySpec {
name: "device",
kind: ValueKind::Uint,
required: false,
summary: "the device number it answers at on bus 0 (default 4)",
},
PropertySpec {
name: "function",
kind: ValueKind::Uint,
required: false,
summary: "the function number, 0-7 (default 0)",
},
PropertySpec {
name: "vendor-id",
kind: ValueKind::Uint,
required: false,
summary: "the vendor identification (default 0x1234)",
},
PropertySpec {
name: "device-id",
kind: ValueKind::Uint,
required: false,
summary: "the device identification (default 0x1122)",
},
PropertySpec {
name: "revision",
kind: ValueKind::Uint,
required: false,
summary: "the revision identification byte (default 0)",
},
PropertySpec {
name: "image",
kind: ValueKind::Media,
required: false,
summary: "the media slot the namespace is bound to; a host medium under that name wins",
},
PropertySpec {
name: "size",
kind: ValueKind::Size,
required: false,
summary: "how many bytes the namespace holds, when no image says",
},
PropertySpec {
name: "block",
kind: ValueKind::Uint,
required: false,
summary: "bytes in a logical block: 512, 1024, 2048 or 4096 (default 512)",
},
PropertySpec {
name: "readonly",
kind: ValueKind::Bool,
required: false,
summary: "refuse every write, and say so in Identify Namespace's NSATTR",
},
PropertySpec {
name: "queues",
kind: ValueKind::Uint,
required: false,
summary: "how many I/O queue pairs the controller allocates (default 4)",
},
PropertySpec {
name: "serial",
kind: ValueKind::Str,
required: false,
summary: "the serial number Identify Controller reports",
},
PropertySpec {
name: "model",
kind: ValueKind::Str,
required: false,
summary: "the model number Identify Controller reports",
},
PropertySpec {
name: "firmware",
kind: ValueKind::Str,
required: false,
summary: "the firmware revision Identify Controller reports",
},
],
construct: |props| Ok(Box::new(Nvme::new(props)?)),
};
impl Device for Nvme {
fn class(&self) -> &'static DeviceClass {
&CLASS
}
fn realize(&self, _ctx: &mut RealizeCtx<'_>) -> Result<()> {
self.bus
.attach(self.at, Arc::clone(&self.regs) as Arc<dyn PciFunction>)
}
fn reset(&self, _kind: ResetKind) {
*self.regs.config.lock() = Function::fresh_config(self.vendor, self.device, self.revision);
self.regs.bars.reset();
self.ctrl.reset();
self.regs.bars.sync(self.regs.command(), true);
}
fn save(&self, w: &mut ChunkWriter<'_>) -> Result<()> {
let ns = self.ctrl.namespace();
match ns.snapshot() {
Snapshot::Capture => w.write_bytes(&ns.contents()?)?,
Snapshot::Reference => {
ns.flush()?;
w.write_bytes(ns.describe().as_bytes())?;
}
Snapshot::Refuse => {
return Err(Error::State(alloc::format!(
"this namespace's medium ({}) refuses to be snapshotted",
ns.describe()
)));
}
}
w.write_bytes(self.regs.config.lock().bytes())?;
for value in self.regs.bars.latches() {
w.write_u32(value)?;
}
self.ctrl.save(w)
}
fn load(&self, r: &mut ChunkReader<'_>) -> Result<()> {
let ns = self.ctrl.namespace();
let bytes: &[u8] = r.read_bytes()?;
match ns.snapshot() {
Snapshot::Capture => {
let held = ns.blocks() * ns.lba_bytes();
if bytes.len() as u64 != held {
return Err(Error::State(alloc::format!(
"the snapshot holds a namespace of {} byte(s), this one holds {held}",
bytes.len()
)));
}
ns.restore(bytes)?;
}
Snapshot::Reference => {
let want = ns.describe();
let got = core::str::from_utf8(bytes).unwrap_or("");
if got != want {
return Err(Error::State(alloc::format!(
"this snapshot was taken of `{got}` and this namespace is `{want}`"
)));
}
}
Snapshot::Refuse => {
return Err(Error::State(alloc::format!(
"this namespace's medium ({}) refuses to be snapshotted",
ns.describe()
)));
}
}
let config: &[u8] = r.read_bytes()?;
let mut latches = [0u32; Bars::COUNT as usize];
for slot in &mut latches {
*slot = r.read_u32()?;
}
{
let mut c = self.regs.config.lock();
*c = Function::fresh_config(self.vendor, self.device, self.revision);
c.restore(config);
}
self.regs.bars.set_latches(&latches);
self.ctrl.load(r)?;
let command = self.regs.command();
self.regs.apply_command(command);
self.regs.bars.sync(command, true);
Ok(())
}
fn connect(&self, port: &str, source: WireSource) -> Result<()> {
if port != pin::IRQ {
return Err(Error::Config {
at: String::from(port),
message: alloc::format!(
"an NVMe controller drives `{}` and nothing else",
pin::IRQ
),
});
}
self.ctrl.connect_irq(source);
Ok(())
}
fn announce(&self, _port: &str) {
self.ctrl.refresh_irq();
}
}
impl Instance for Nvme {
fn bind(&self, ctx: &BindCtx<'_>) -> Result<()> {
let space = ctx.space().ok_or_else(|| Error::Config {
at: ctx.path().to_string(),
message: String::from(
"an NVMe controller masters the memory its queues live in, and places its \
register block with a base address register: add `space = mem` to the object",
),
})?;
self.attach_space(space, ctx.requester())
}
}
pub fn register(registry: &mut crate::core::Registry) -> Result<()> {
registry.add(&CLASS)
}
pub fn bind(bindings: &mut crate::machine::Bindings) -> Result<()> {
bindings.bind(CLASS_NAME, |props| Ok(Arc::new(Nvme::new(props)?)))
}
#[must_use]
pub fn schema() -> ClassSchema {
use crate::machine::validate::{PortDir, PropSchema};
ClassSchema::new(CLASS_NAME)
.prop(PropSchema::new("bus", ValueKind::Str))
.prop(
PropSchema::new("device", ValueKind::Uint)
.range(0, u64::from(crate::bus::pci::MAX_DEVICE)),
)
.prop(
PropSchema::new("function", ValueKind::Uint)
.range(0, u64::from(crate::bus::pci::MAX_FUNCTION)),
)
.prop(PropSchema::new("vendor-id", ValueKind::Uint).range(0, 0xffff))
.prop(PropSchema::new("device-id", ValueKind::Uint).range(0, 0xffff))
.prop(PropSchema::new("revision", ValueKind::Uint).range(0, 255))
.prop(PropSchema::new("image", ValueKind::Media))
.prop(PropSchema::new("size", ValueKind::Size))
.prop(PropSchema::new("block", ValueKind::Uint).range(512, 4096))
.prop(PropSchema::new("readonly", ValueKind::Bool))
.prop(PropSchema::new("queues", ValueKind::Uint).range(1, u64::from(MAX_IO_QUEUES)))
.prop(PropSchema::new("serial", ValueKind::Str))
.prop(PropSchema::new("model", ValueKind::Str))
.prop(PropSchema::new("firmware", ValueKind::Str))
.port(pin::IRQ, PortDir::Out)
}