nvme-mi-dev provides the jigsaw pieces for modelling NVMe devices that can be inspected via NVMe-MI. Initial integrations expose the NVMe-MI endpoint using mctp-dev.
As an example by way of implementation, a test setup that was used for mctp-dev creates two namespaces in a subsystem with one controller, one PCIe port, and one two-wire port. One of the namespaces is attached to the controller:
use nvme_mi_dev::nvme::{ManagementEndpoint, PCIePort, PortType, Subsystem, SubsystemInfo, TwoWirePort};
async fn nvme_mi<'a>(router: &'a Router<'a>) -> std::io::Result<()> {
let mut l = router.listener(mctp::MCTP_TYPE_NVME)?;
let mut subsys = Subsystem::new(SubsystemInfo::environment());
let ppid = subsys
.add_port(PortType::PCIe(PCIePort::new()))
.expect("Unable to create PCIe port");
let ctlrid = subsys
.add_controller(ppid)
.expect("Unable to create controller");
let nsid = subsys
.add_namespace(1024)
.expect("Unable to create namespace");
subsys
.add_namespace(2048)
.expect("Unable to create namespace");
subsys
.controller_mut(ctlrid)
.attach_namespace(nsid)
.unwrap_or_else(|_| {
panic!(
"Unable to attach namespace {:?} to controller {:?}",
nsid, ctlrid
)
});
let twpid = subsys
.add_port(PortType::TwoWire(TwoWirePort::new()))
.expect("Unable to create TwoWire port");
let mut mep = ManagementEndpoint::new(twpid);
let mut buf = [0u8; 4224];
loop {
let Ok((_typ, ic, msg, resp)) = l.recv(&mut buf).await else {
debug!("recv() failed");
continue;
};
mep.handle_async(&mut subsys, msg, ic, resp).await;
}
}
The public APIs are largely concerned with modelling the device. Details of MI message (de)serialisation are left to the implementation, which will respond to queries based on the properties of the provided model.
As the NVMe specifications largely relate to hardware specifications and implementations, the model needs some input to customise it to the vendor. The following environment variables can be used at build time:
NVME_MI_DEV_IEEE_OUI
Must be set in the IEEE RA hexadecimal representation, e.g:
export NVME_MI_DEV_IEEE_OUI=ac-de-48
NVME_MI_DEV_PCI_VID
NVME_MI_DEV_PCI_DID
NVME_MI_DEV_PCI_SVID
NVME_MI_DEV_PCI_SDID
All must be 16-bit values in base-16 representation, e.g:
export NVME_MI_DEV_PCI_VID=ffff
export NVME_MI_DEV_PCI_DID=ffff
export NVME_MI_DEV_PCI_SVID=ffff
export NVME_MI_DEV_PCI_SDID=ffff
The example values above are provided as defaults if the variables are not exported in the environment. However, they are not valid identifiers:
- The
ac-de-48
OUI is allocated as private and used in documentation examples - The
ffff
PCI ID values represent aborted reads
Builds using these values must not be distributed.