Bare-metal lightweight NVMe driver for OS development.
- NVMe controller initialisation and shutdown
- Admin queue and I/O queue management
- Namespace enumeration and block I/O (read/write/flush)
- SMART and error log retrieval
- Trim (dataset management), write zeroes, compare, verify
- Firmware download and commit
- Sanitise operations (block erase, crypto erase, overwrite)
- Dynamic page size support via
CAP.MPSMIN
Implement the Dma trait to provide DMA allocation and MMIO mapping:
impl Dma for MyDma {
unsafe fn alloc(&self, size: usize, align: usize) -> Option<usize> { /* physically contiguous */ }
unsafe fn free(&self, addr: usize, size: usize, align: usize) { }
unsafe fn map_mmio(&self, phys: usize, size: usize) -> Option<usize> { }
unsafe fn unmap_mmio(&self, virt: usize, size: usize) { }
fn virt_to_phys(&self, va: usize) -> usize { }
fn page_size(&self) -> usize { /* >= 4096, must be >= NVMe CAP.MPSMIN */ }
}Then initialise from PCI BAR0:
let dev = NVMeDev::new(pci_bar0_addr, MyDma)?;
// Access controller info
let ctrl = dev.ctrl();
println!("Model: {}", ctrl.data().model);
println!("Serial: {}", ctrl.data().serial);
// Read from namespace 1
if let Some(ns) = dev.ns(1) {
let mut buf = vec![0u8; ns.blk_sz()];
ns.read(0, &mut buf)?;
}The alloc function receives alignment requirements per allocation. NVMe requires page-aligned buffers for most operations:
| Structure | Align |
|---|---|
| Submission Queue | Page |
| Completion Queue | Page |
| PRP List | Page |
| Data buffers | Page |
The driver automatically uses max(Dma::page_size(), CAP.MPSMIN) for alignment.
new(mmio: usize, alloc: A)- Initialise controllerctrl()- Get controller handlens(nsid: u32)- Get namespace by IDns_list()- List all namespaces
data()- Controller info (serial, model, firmware, max transfer size)new_ioq(size)/rm_ioq(qid)- Manage I/O queuesset_ioq_cnt(count)- Set number of I/O queuessmart_log()/error_log(entries)- Retrieve logsshutdown()/resume()- Power managementblock_erase()/crypto_erase()/overwrite(passes, invert)- Sanitise
id()/blk_sz()/blk_cnt()- Namespace inforead(lba, buf)/write(lba, buf)- Block I/Oflush()- Flush volatile write cachetrim(lba, blocks)- Deallocate blockswrite_zeroes(lba, blocks)- Zero blocks without transfercompare(lba, buf)/verify(lba, blocks)- Data verification