Files
MontaukOS/kernel/src/Drivers/Storage/Nvme.cpp
T

831 lines
30 KiB
C++

/*
* Nvme.cpp
* NVM Express (NVMe) storage driver
* Copyright (c) 2026 Daniel Hammer
*/
#include "Nvme.hpp"
#include "BlockDevice.hpp"
#include <Pci/Pci.hpp>
#include <Terminal/Terminal.hpp>
#include <CppLib/Stream.hpp>
#include <Memory/HHDM.hpp>
#include <Memory/Paging.hpp>
#include <Memory/PageFrameAllocator.hpp>
#include <Libraries/Memory.hpp>
#include <Hal/Apic/Interrupts.hpp>
#include <Hal/Apic/IoApic.hpp>
using namespace Kt;
namespace Drivers::Storage::Nvme {
// -------------------------------------------------------------------------
// Driver state
// -------------------------------------------------------------------------
static bool g_initialized = false;
static volatile uint8_t* g_mmioBase = nullptr;
static uint32_t g_doorbellStride = 0; // in bytes (4 << DSTRD)
static uint16_t g_maxQueueEntries = 0; // CAP.MQES + 1
// Admin queue state
static SqEntry* g_adminSq = nullptr;
static CqEntry* g_adminCq = nullptr;
static uint64_t g_adminSqPhys = 0;
static uint64_t g_adminCqPhys = 0;
static uint16_t g_adminSqTail = 0;
static uint16_t g_adminCqHead = 0;
static uint8_t g_adminCqPhase = 1; // Expected phase bit starts at 1
static uint16_t g_adminCmdId = 0;
// I/O queue state (single I/O queue pair)
static SqEntry* g_ioSq = nullptr;
static CqEntry* g_ioCq = nullptr;
static uint64_t g_ioSqPhys = 0;
static uint64_t g_ioCqPhys = 0;
static uint16_t g_ioSqTail = 0;
static uint16_t g_ioCqHead = 0;
static uint8_t g_ioCqPhase = 1;
static uint16_t g_ioCmdId = 0;
static uint16_t g_ioSqDepth = 0;
static uint16_t g_ioCqDepth = 0;
// Namespace state
static int g_nsCount = 0;
static NamespaceInfo g_namespaces[MAX_NAMESPACES] = {};
// Controller identify data
static uint32_t g_mdts = 0; // Max Data Transfer Size in pages (0 = unlimited)
// -------------------------------------------------------------------------
// Register access
// -------------------------------------------------------------------------
static void WriteReg32(uint32_t reg, uint32_t value) {
*(volatile uint32_t*)(g_mmioBase + reg) = value;
}
static uint32_t ReadReg32(uint32_t reg) {
return *(volatile uint32_t*)(g_mmioBase + reg);
}
static void WriteReg64(uint32_t reg, uint64_t value) {
*(volatile uint32_t*)(g_mmioBase + reg) = (uint32_t)(value & 0xFFFFFFFF);
*(volatile uint32_t*)(g_mmioBase + reg + 4) = (uint32_t)(value >> 32);
}
static uint64_t ReadReg64(uint32_t reg) {
uint32_t lo = *(volatile uint32_t*)(g_mmioBase + reg);
uint32_t hi = *(volatile uint32_t*)(g_mmioBase + reg + 4);
return (uint64_t)lo | ((uint64_t)hi << 32);
}
// -------------------------------------------------------------------------
// Doorbell registers
// NVMe spec: SQ y Tail Doorbell offset = 0x1000 + (2y * doorbellStride)
// CQ y Head Doorbell offset = 0x1000 + ((2y+1) * doorbellStride)
// -------------------------------------------------------------------------
static void WriteSqTailDoorbell(uint16_t queueId, uint16_t value) {
uint32_t offset = 0x1000 + (2 * queueId) * g_doorbellStride;
*(volatile uint32_t*)(g_mmioBase + offset) = value;
}
static void WriteCqHeadDoorbell(uint16_t queueId, uint16_t value) {
uint32_t offset = 0x1000 + (2 * queueId + 1) * g_doorbellStride;
*(volatile uint32_t*)(g_mmioBase + offset) = value;
}
// -------------------------------------------------------------------------
// DMA buffer allocation
// -------------------------------------------------------------------------
static void* AllocateDmaBuffer(uint64_t& outPhys, int pages = 1) {
void* virt;
if (pages == 1) {
virt = Memory::g_pfa->AllocateZeroed();
} else {
virt = Memory::g_pfa->ReallocConsecutive(nullptr, pages);
memset(virt, 0, pages * 0x1000);
}
outPhys = Memory::SubHHDM(virt);
return virt;
}
// -------------------------------------------------------------------------
// Admin command submission
// -------------------------------------------------------------------------
static void SubmitAdminCommand(SqEntry& cmd) {
cmd.CommandId = g_adminCmdId++;
g_adminSq[g_adminSqTail] = cmd;
g_adminSqTail = (g_adminSqTail + 1) % ADMIN_QUEUE_DEPTH;
WriteSqTailDoorbell(0, g_adminSqTail);
}
static bool WaitAdminCompletion(CqEntry& out) {
for (int i = 0; i < 5000000; i++) {
CqEntry* cqe = &g_adminCq[g_adminCqHead];
uint16_t status = cqe->Status;
// Check phase bit matches expected
if ((status & CQE_PHASE_BIT) == g_adminCqPhase) {
out = *cqe;
// Advance CQ head
g_adminCqHead++;
if (g_adminCqHead >= ADMIN_QUEUE_DEPTH) {
g_adminCqHead = 0;
g_adminCqPhase ^= 1; // Toggle expected phase
}
WriteCqHeadDoorbell(0, g_adminCqHead);
// Check status code (bits 15:1, 0 = success)
if (status & CQE_STATUS_MASK) {
KernelLogStream(ERROR, "NVMe") << "Admin command failed, status="
<< base::hex << (uint64_t)(status >> 1);
return false;
}
return true;
}
asm volatile("" ::: "memory");
}
KernelLogStream(ERROR, "NVMe") << "Admin command timeout";
return false;
}
// Submit an admin command and wait for completion
static bool AdminCommand(SqEntry& cmd, CqEntry& cqe) {
SubmitAdminCommand(cmd);
return WaitAdminCompletion(cqe);
}
// -------------------------------------------------------------------------
// I/O command submission
// -------------------------------------------------------------------------
static void SubmitIoCommand(SqEntry& cmd) {
cmd.CommandId = g_ioCmdId++;
g_ioSq[g_ioSqTail] = cmd;
g_ioSqTail = (g_ioSqTail + 1) % g_ioSqDepth;
WriteSqTailDoorbell(1, g_ioSqTail);
}
static bool WaitIoCompletion(CqEntry& out) {
for (int i = 0; i < 5000000; i++) {
CqEntry* cqe = &g_ioCq[g_ioCqHead];
uint16_t status = cqe->Status;
if ((status & CQE_PHASE_BIT) == g_ioCqPhase) {
out = *cqe;
g_ioCqHead++;
if (g_ioCqHead >= g_ioCqDepth) {
g_ioCqHead = 0;
g_ioCqPhase ^= 1;
}
WriteCqHeadDoorbell(1, g_ioCqHead);
if (status & CQE_STATUS_MASK) {
KernelLogStream(ERROR, "NVMe") << "I/O command failed, status="
<< base::hex << (uint64_t)(status >> 1);
return false;
}
return true;
}
asm volatile("" ::: "memory");
}
KernelLogStream(ERROR, "NVMe") << "I/O command timeout";
return false;
}
// -------------------------------------------------------------------------
// Controller disable/enable
// -------------------------------------------------------------------------
static bool DisableController() {
uint32_t cc = ReadReg32(REG_CC);
cc &= ~CC_EN;
WriteReg32(REG_CC, cc);
// Wait for CSTS.RDY to become 0
for (int i = 0; i < 5000000; i++) {
uint32_t csts = ReadReg32(REG_CSTS);
if (csts & CSTS_CFS) {
KernelLogStream(ERROR, "NVMe") << "Controller fatal status during disable";
return false;
}
if (!(csts & CSTS_RDY)) {
return true;
}
asm volatile("" ::: "memory");
}
KernelLogStream(ERROR, "NVMe") << "Controller disable timeout";
return false;
}
static bool EnableController() {
// Configure CC: enable, NVM command set, 4 KiB pages,
// SQ entry size = 64 bytes (2^6), CQ entry size = 16 bytes (2^4)
uint32_t cc = CC_EN | CC_CSS_NVM | CC_AMS_RR | CC_SHN_NONE;
cc |= (0u << CC_MPS_SHIFT); // MPS = 0 => 4 KiB pages
cc |= (6u << CC_IOSQES_SHIFT); // SQ entry = 2^6 = 64 bytes
cc |= (4u << CC_IOCQES_SHIFT); // CQ entry = 2^4 = 16 bytes
WriteReg32(REG_CC, cc);
// Wait for CSTS.RDY
for (int i = 0; i < 5000000; i++) {
uint32_t csts = ReadReg32(REG_CSTS);
if (csts & CSTS_CFS) {
KernelLogStream(ERROR, "NVMe") << "Controller fatal status during enable";
return false;
}
if (csts & CSTS_RDY) {
return true;
}
asm volatile("" ::: "memory");
}
KernelLogStream(ERROR, "NVMe") << "Controller enable timeout";
return false;
}
// -------------------------------------------------------------------------
// Admin queue setup
// -------------------------------------------------------------------------
static bool SetupAdminQueues() {
// Allocate Admin Submission Queue (ADMIN_QUEUE_DEPTH * 64 bytes)
// At 32 entries * 64 bytes = 2048 bytes, fits in 1 page
g_adminSq = (SqEntry*)AllocateDmaBuffer(g_adminSqPhys);
// Allocate Admin Completion Queue (ADMIN_QUEUE_DEPTH * 16 bytes)
// At 32 entries * 16 bytes = 512 bytes, fits in 1 page
g_adminCq = (CqEntry*)AllocateDmaBuffer(g_adminCqPhys);
g_adminSqTail = 0;
g_adminCqHead = 0;
g_adminCqPhase = 1;
g_adminCmdId = 0;
// Set Admin Queue Attributes
// AQA: bits 27:16 = ACQS (CQ size - 1), bits 11:0 = ASQS (SQ size - 1)
uint32_t aqa = ((ADMIN_QUEUE_DEPTH - 1) << 16) | (ADMIN_QUEUE_DEPTH - 1);
WriteReg32(REG_AQA, aqa);
// Set Admin SQ and CQ base addresses
WriteReg64(REG_ASQ, g_adminSqPhys);
WriteReg64(REG_ACQ, g_adminCqPhys);
return true;
}
// -------------------------------------------------------------------------
// Identify Controller
// -------------------------------------------------------------------------
static bool IdentifyController() {
uint64_t identPhys;
uint8_t* identData = (uint8_t*)AllocateDmaBuffer(identPhys);
SqEntry cmd = {};
cmd.Opcode = ADMIN_IDENTIFY;
cmd.Nsid = 0;
cmd.Prp1 = identPhys;
cmd.Prp2 = 0;
cmd.Cdw10 = IDENTIFY_CNS_CONTROLLER;
CqEntry cqe;
if (!AdminCommand(cmd, cqe)) {
KernelLogStream(ERROR, "NVMe") << "Identify Controller failed";
Memory::g_pfa->Free(identData);
return false;
}
// Parse controller data (4096 bytes)
// Bytes 24-63: Serial Number (20 bytes, space-padded ASCII)
// Bytes 64-103: Model Number (40 bytes, space-padded ASCII)
// Bytes 104-111: Firmware Revision (8 bytes)
// Byte 77: MDTS (Maximum Data Transfer Size, in units of CAP.MPSMIN pages)
// Model string
char model[41];
memcpy(model, identData + 24, 40);
model[40] = '\0';
// Trim trailing spaces
for (int i = 39; i >= 0; i--) {
if (model[i] == ' ' || model[i] == '\0') {
model[i] = '\0';
} else {
break;
}
}
// MDTS
uint8_t mdtsPower = identData[77];
if (mdtsPower > 0) {
g_mdts = (1u << mdtsPower); // In minimum page size units (4 KiB pages)
} else {
g_mdts = 0; // No limit
}
// NN (Number of Namespaces): bytes 516-519
uint32_t nn = *(uint32_t*)(identData + 516);
KernelLogStream(OK, "NVMe") << "Controller: " << model;
KernelLogStream(INFO, "NVMe") << "MDTS: " << (g_mdts ? (uint64_t)(g_mdts * 4) : 0)
<< " KiB, Namespaces: " << (uint64_t)nn;
// Copy model to all namespaces (will be used during registration)
for (int i = 0; i < MAX_NAMESPACES; i++) {
memcpy(g_namespaces[i].Model, model, 41);
}
// Enumerate active namespaces
if (nn > (uint32_t)MAX_NAMESPACES) nn = MAX_NAMESPACES;
for (uint32_t nsid = 1; nsid <= nn; nsid++) {
// Identify Namespace
uint64_t nsIdentPhys;
uint8_t* nsIdentData = (uint8_t*)AllocateDmaBuffer(nsIdentPhys);
SqEntry nsCmd = {};
nsCmd.Opcode = ADMIN_IDENTIFY;
nsCmd.Nsid = nsid;
nsCmd.Prp1 = nsIdentPhys;
nsCmd.Prp2 = 0;
nsCmd.Cdw10 = IDENTIFY_CNS_NAMESPACE;
CqEntry nsCqe;
if (!AdminCommand(nsCmd, nsCqe)) {
KernelLogStream(WARNING, "NVMe") << "Identify Namespace " << (uint64_t)nsid << " failed";
Memory::g_pfa->Free(nsIdentData);
continue;
}
// Bytes 0-7: NSZE (Namespace Size in LBAs)
uint64_t nsze = *(uint64_t*)(nsIdentData + 0);
if (nsze == 0) {
Memory::g_pfa->Free(nsIdentData);
continue;
}
// Byte 26: NLBAF (Number of LBA Formats, 0-based)
// Byte 25:24 - FLBAS (Formatted LBA Size)
// bits 3:0 = index of the LBA format in use
uint8_t flbas = nsIdentData[26];
uint8_t lbaFmtIdx = flbas & 0x0F;
// LBA Format descriptors start at byte 128, each is 4 bytes
// Bits 23:16 = LBADS (LBA Data Size as power of 2)
uint32_t lbaFmt = *(uint32_t*)(nsIdentData + 128 + lbaFmtIdx * 4);
uint8_t lbads = (lbaFmt >> 16) & 0xFF;
uint32_t sectorSize = (1u << lbads);
int idx = g_nsCount;
g_namespaces[idx].Active = true;
g_namespaces[idx].Nsid = nsid;
g_namespaces[idx].SectorCount = nsze;
g_namespaces[idx].SectorSize = sectorSize;
// Compute max transfer in blocks
if (g_mdts > 0) {
g_namespaces[idx].MaxTransferBlocks = (g_mdts * 0x1000) / sectorSize;
} else {
// Conservative default: 128 sectors (64 KiB for 512-byte sectors)
g_namespaces[idx].MaxTransferBlocks = 128;
}
g_nsCount++;
uint64_t sizeBytes = nsze * sectorSize;
uint64_t sizeMB = sizeBytes / (1024 * 1024);
uint64_t sizeGB = sizeMB / 1024;
if (sizeGB > 0) {
KernelLogStream(OK, "NVMe") << "Namespace " << (uint64_t)nsid
<< ": " << sizeGB << " GiB (" << (uint64_t)sectorSize << " B/sector)";
} else {
KernelLogStream(OK, "NVMe") << "Namespace " << (uint64_t)nsid
<< ": " << sizeMB << " MiB (" << (uint64_t)sectorSize << " B/sector)";
}
Memory::g_pfa->Free(nsIdentData);
}
Memory::g_pfa->Free(identData);
return g_nsCount > 0;
}
// -------------------------------------------------------------------------
// Create I/O queues
// -------------------------------------------------------------------------
static bool SetNumberOfQueues(uint16_t& sqCount, uint16_t& cqCount) {
SqEntry cmd = {};
cmd.Opcode = ADMIN_SET_FEATURES;
cmd.Cdw10 = FEATURE_NUM_QUEUES;
// CDW11: bits 31:16 = number of CQs requested (0-based)
// bits 15:0 = number of SQs requested (0-based)
cmd.Cdw11 = ((uint32_t)(cqCount - 1) << 16) | (sqCount - 1);
CqEntry cqe;
if (!AdminCommand(cmd, cqe)) {
return false;
}
// Result DW0 contains allocated counts (0-based)
sqCount = (cqe.Result & 0xFFFF) + 1;
cqCount = ((cqe.Result >> 16) & 0xFFFF) + 1;
return true;
}
static bool CreateIoQueues() {
// Request 1 SQ + 1 CQ
uint16_t sqCount = 1;
uint16_t cqCount = 1;
if (!SetNumberOfQueues(sqCount, cqCount)) {
KernelLogStream(ERROR, "NVMe") << "Set Number of Queues failed";
return false;
}
KernelLogStream(INFO, "NVMe") << "Allocated " << (uint64_t)sqCount
<< " SQ(s), " << (uint64_t)cqCount << " CQ(s)";
// Determine queue depths (capped by controller max)
g_ioSqDepth = IO_QUEUE_DEPTH;
g_ioCqDepth = IO_QUEUE_DEPTH;
if (g_ioSqDepth > g_maxQueueEntries) g_ioSqDepth = g_maxQueueEntries;
if (g_ioCqDepth > g_maxQueueEntries) g_ioCqDepth = g_maxQueueEntries;
// Allocate I/O CQ
int cqPages = ((uint32_t)g_ioCqDepth * sizeof(CqEntry) + 0xFFF) / 0x1000;
g_ioCq = (CqEntry*)AllocateDmaBuffer(g_ioCqPhys, cqPages);
g_ioCqHead = 0;
g_ioCqPhase = 1;
// Create I/O Completion Queue (queue ID = 1)
{
SqEntry cmd = {};
cmd.Opcode = ADMIN_CREATE_IO_CQ;
cmd.Prp1 = g_ioCqPhys;
// CDW10: bits 31:16 = queue size (0-based), bits 15:0 = queue ID
cmd.Cdw10 = ((uint32_t)(g_ioCqDepth - 1) << 16) | 1;
// CDW11: bit 0 = physically contiguous, bit 1 = interrupts enabled
// bits 31:16 = interrupt vector
cmd.Cdw11 = (1u << 0) | (1u << 1) | (0u << 16);
CqEntry cqe;
if (!AdminCommand(cmd, cqe)) {
KernelLogStream(ERROR, "NVMe") << "Create I/O CQ failed";
return false;
}
}
// Allocate I/O SQ
int sqPages = ((uint32_t)g_ioSqDepth * sizeof(SqEntry) + 0xFFF) / 0x1000;
g_ioSq = (SqEntry*)AllocateDmaBuffer(g_ioSqPhys, sqPages);
g_ioSqTail = 0;
g_ioCmdId = 0;
// Create I/O Submission Queue (queue ID = 1, linked to CQ ID = 1)
{
SqEntry cmd = {};
cmd.Opcode = ADMIN_CREATE_IO_SQ;
cmd.Prp1 = g_ioSqPhys;
// CDW10: bits 31:16 = queue size (0-based), bits 15:0 = queue ID
cmd.Cdw10 = ((uint32_t)(g_ioSqDepth - 1) << 16) | 1;
// CDW11: bit 0 = physically contiguous, bits 31:16 = CQ ID
cmd.Cdw11 = (1u << 0) | (1u << 16);
CqEntry cqe;
if (!AdminCommand(cmd, cqe)) {
KernelLogStream(ERROR, "NVMe") << "Create I/O SQ failed";
return false;
}
}
KernelLogStream(OK, "NVMe") << "I/O queues created (depth " << (uint64_t)g_ioSqDepth << ")";
return true;
}
// -------------------------------------------------------------------------
// Interrupt handler
// -------------------------------------------------------------------------
static void HandleInterrupt(uint8_t irq) {
(void)irq;
// NVMe uses polling-based completion in this driver.
// The interrupt handler just acknowledges the interrupt.
// Completions are consumed in the polling loops above.
}
// -------------------------------------------------------------------------
// MSI setup
// -------------------------------------------------------------------------
static bool SetupMsi(uint8_t bus, uint8_t dev, uint8_t func) {
uint8_t cap = Pci::FindCapability(bus, dev, func, Pci::PCI_CAP_MSI);
if (cap == 0) {
KernelLogStream(INFO, "NVMe") << "MSI capability not found";
return false;
}
uint16_t msgCtrl = Pci::LegacyRead16(bus, dev, func, cap + 2);
bool is64bit = (msgCtrl & (1 << 7)) != 0;
Pci::LegacyWrite32(bus, dev, func, cap + 4, MSI_ADDR_BASE);
if (is64bit) {
Pci::LegacyWrite32(bus, dev, func, cap + 8, 0);
Pci::LegacyWrite16(bus, dev, func, cap + 12, MSI_VECTOR);
} else {
Pci::LegacyWrite16(bus, dev, func, cap + 8, MSI_VECTOR);
}
msgCtrl &= ~(0x70); // Single message
msgCtrl |= (1 << 0); // MSI Enable
Pci::LegacyWrite16(bus, dev, func, cap + 2, msgCtrl);
uint16_t pciCmd = Pci::LegacyRead16(bus, dev, func, (uint8_t)Pci::PCI_REG_COMMAND);
pciCmd |= Pci::PCI_CMD_INTX_DISABLE;
Pci::LegacyWrite16(bus, dev, func, (uint8_t)Pci::PCI_REG_COMMAND, pciCmd);
Hal::RegisterIrqHandler(MSI_IRQ, HandleInterrupt);
KernelLogStream(OK, "NVMe") << "MSI enabled: vector " << base::dec << (uint64_t)MSI_VECTOR;
return true;
}
// -------------------------------------------------------------------------
// Probe (PCI driver entry point)
// -------------------------------------------------------------------------
bool Probe(const Pci::PciDevice& dev) {
if (g_initialized) return false;
KernelLogStream(OK, "NVMe") << "Found NVMe controller at PCI "
<< base::hex << (uint64_t)dev.Bus << ":"
<< (uint64_t)dev.Device << "." << (uint64_t)dev.Function
<< " (" << (uint64_t)dev.VendorId << ":" << (uint64_t)dev.DeviceId << ")";
// NVMe uses BAR0 for MMIO registers
uint64_t mmioPhys = Pci::ReadBar0(dev.Bus, dev.Device, dev.Function);
if (mmioPhys == 0) {
KernelLogStream(ERROR, "NVMe") << "BAR0 is zero";
return false;
}
KernelLogStream(INFO, "NVMe") << "BAR0 physical: " << base::hex << mmioPhys;
// Map MMIO region. NVMe requires at least the controller registers (0x1000)
// plus doorbell registers. Map 16 KiB to cover admin + a few I/O doorbells.
constexpr uint64_t MmioSize = 0x4000;
for (uint64_t offset = 0; offset < MmioSize; offset += 0x1000) {
Memory::VMM::g_paging->MapMMIO(mmioPhys + offset, Memory::HHDM(mmioPhys + offset));
}
g_mmioBase = (volatile uint8_t*)Memory::HHDM(mmioPhys);
// Enable bus mastering and memory space
Pci::EnableBusMaster(dev.Bus, dev.Device, dev.Function);
// Read capabilities
uint64_t cap = ReadReg64(REG_CAP);
g_maxQueueEntries = (uint16_t)((cap & CAP_MQES_MASK) + 1);
uint32_t dstrd = (uint32_t)((cap & CAP_DSTRD_MASK) >> CAP_DSTRD_SHIFT);
g_doorbellStride = 4u << dstrd;
// Read version
uint32_t vs = ReadReg32(REG_VS);
uint32_t vsMajor = (vs >> 16) & 0xFFFF;
uint32_t vsMinor = (vs >> 8) & 0xFF;
uint32_t vsTertiary = vs & 0xFF;
KernelLogStream(INFO, "NVMe") << "Version: " << base::dec
<< (uint64_t)vsMajor << "." << (uint64_t)vsMinor << "." << (uint64_t)vsTertiary;
KernelLogStream(INFO, "NVMe") << "Max queue entries: " << (uint64_t)g_maxQueueEntries
<< ", Doorbell stride: " << (uint64_t)g_doorbellStride << " bytes";
// Step 1: Disable controller
if (!DisableController()) {
KernelLogStream(ERROR, "NVMe") << "Failed to disable controller";
return false;
}
// Step 2: Set up MSI
bool hasMsi = SetupMsi(dev.Bus, dev.Device, dev.Function);
if (!hasMsi) {
uint8_t irqLine = Pci::LegacyRead8(dev.Bus, dev.Device, dev.Function,
(uint8_t)Pci::PCI_REG_INTERRUPT);
if (irqLine != 0xFF) {
KernelLogStream(INFO, "NVMe") << "Using legacy IRQ " << base::dec << (uint64_t)irqLine;
Hal::RegisterIrqHandler(irqLine, HandleInterrupt);
Hal::IoApic::UnmaskIrq(Hal::IoApic::GetGsiForIrq(irqLine));
}
}
// Step 3: Set up admin queues
if (!SetupAdminQueues()) {
KernelLogStream(ERROR, "NVMe") << "Failed to set up admin queues";
return false;
}
// Step 4: Enable controller
if (!EnableController()) {
KernelLogStream(ERROR, "NVMe") << "Failed to enable controller";
return false;
}
KernelLogStream(OK, "NVMe") << "Controller enabled and ready";
// Step 5: Identify controller and namespaces
if (!IdentifyController()) {
KernelLogStream(ERROR, "NVMe") << "Failed to identify controller/namespaces";
return false;
}
// Step 6: Create I/O queues
if (!CreateIoQueues()) {
KernelLogStream(ERROR, "NVMe") << "Failed to create I/O queues";
return false;
}
// Step 7: Register namespaces as block devices
for (int i = 0; i < g_nsCount; i++) {
Storage::BlockDevice bdev = {};
bdev.ReadSectors = [](void* ctx, uint64_t lba, uint32_t count, void* buffer) -> bool {
return ReadSectors((int)(uintptr_t)ctx, lba, count, buffer);
};
bdev.WriteSectors = [](void* ctx, uint64_t lba, uint32_t count, const void* buffer) -> bool {
return WriteSectors((int)(uintptr_t)ctx, lba, count, buffer);
};
bdev.Ctx = (void*)(uintptr_t)i;
bdev.SectorCount = g_namespaces[i].SectorCount;
bdev.SectorSize = (uint16_t)g_namespaces[i].SectorSize;
bdev.Kind = Storage::BLOCK_KIND_NVME;
memcpy(bdev.Model, g_namespaces[i].Model, 41);
Storage::RegisterBlockDevice(bdev);
}
g_initialized = true;
KernelLogStream(OK, "NVMe") << "Initialization complete: "
<< base::dec << (uint64_t)g_nsCount << " namespace(s) ready";
return true;
}
// -------------------------------------------------------------------------
// Public API: Read/Write
// -------------------------------------------------------------------------
bool IsInitialized() {
return g_initialized;
}
int GetNamespaceCount() {
return g_nsCount;
}
const NamespaceInfo* GetNamespaceInfo(int ns) {
if (ns < 0 || ns >= g_nsCount || !g_namespaces[ns].Active) {
return nullptr;
}
return &g_namespaces[ns];
}
bool ReadSectors(int ns, uint64_t lba, uint32_t count, void* buffer) {
if (!g_initialized || ns < 0 || ns >= g_nsCount || !g_namespaces[ns].Active) {
return false;
}
if (count == 0 || buffer == nullptr) return false;
// Limit to max transfer size
uint32_t maxBlocks = g_namespaces[ns].MaxTransferBlocks;
if (count > maxBlocks) {
KernelLogStream(ERROR, "NVMe") << "ReadSectors: count " << count
<< " exceeds max " << maxBlocks;
return false;
}
uint32_t sectorSize = g_namespaces[ns].SectorSize;
uint32_t totalBytes = count * sectorSize;
int pagesNeeded = (totalBytes + 0xFFF) / 0x1000;
uint64_t dmaPhys;
void* dmaVirt = AllocateDmaBuffer(dmaPhys, pagesNeeded);
// Build NVMe Read command
SqEntry cmd = {};
cmd.Opcode = IO_CMD_READ;
cmd.Nsid = g_namespaces[ns].Nsid;
cmd.Prp1 = dmaPhys;
// PRP2: If transfer spans more than one page, set PRP2
if (totalBytes > 0x1000) {
// For transfers spanning exactly 2 pages, PRP2 = second page address
// For transfers spanning more pages, PRP2 should point to a PRP list.
// Since our DMA buffer is physically contiguous, we can point to the
// second page directly for 2-page transfers. For larger transfers,
// we build a PRP list.
if (pagesNeeded == 2) {
cmd.Prp2 = dmaPhys + 0x1000;
} else {
// Build a PRP list (array of physical page addresses)
uint64_t prpListPhys;
uint64_t* prpList = (uint64_t*)AllocateDmaBuffer(prpListPhys);
for (int i = 1; i < pagesNeeded; i++) {
prpList[i - 1] = dmaPhys + (uint64_t)i * 0x1000;
}
cmd.Prp2 = prpListPhys;
}
}
// CDW10-11: Starting LBA (64-bit)
cmd.Cdw10 = (uint32_t)(lba & 0xFFFFFFFF);
cmd.Cdw11 = (uint32_t)(lba >> 32);
// CDW12: bits 15:0 = Number of Logical Blocks (0-based)
cmd.Cdw12 = count - 1;
SubmitIoCommand(cmd);
CqEntry cqe;
bool ok = WaitIoCompletion(cqe);
if (ok) {
memcpy(buffer, dmaVirt, totalBytes);
}
// Free PRP list if we allocated one
if (pagesNeeded > 2 && cmd.Prp2 != 0 && cmd.Prp2 != dmaPhys + 0x1000) {
Memory::g_pfa->Free((void*)Memory::HHDM(cmd.Prp2));
}
Memory::g_pfa->Free(dmaVirt, pagesNeeded);
return ok;
}
bool WriteSectors(int ns, uint64_t lba, uint32_t count, const void* buffer) {
if (!g_initialized || ns < 0 || ns >= g_nsCount || !g_namespaces[ns].Active) {
return false;
}
if (count == 0 || buffer == nullptr) return false;
uint32_t maxBlocks = g_namespaces[ns].MaxTransferBlocks;
if (count > maxBlocks) {
KernelLogStream(ERROR, "NVMe") << "WriteSectors: count " << count
<< " exceeds max " << maxBlocks;
return false;
}
uint32_t sectorSize = g_namespaces[ns].SectorSize;
uint32_t totalBytes = count * sectorSize;
int pagesNeeded = (totalBytes + 0xFFF) / 0x1000;
uint64_t dmaPhys;
void* dmaVirt = AllocateDmaBuffer(dmaPhys, pagesNeeded);
memcpy(dmaVirt, buffer, totalBytes);
// Build NVMe Write command
SqEntry cmd = {};
cmd.Opcode = IO_CMD_WRITE;
cmd.Nsid = g_namespaces[ns].Nsid;
cmd.Prp1 = dmaPhys;
if (totalBytes > 0x1000) {
if (pagesNeeded == 2) {
cmd.Prp2 = dmaPhys + 0x1000;
} else {
uint64_t prpListPhys;
uint64_t* prpList = (uint64_t*)AllocateDmaBuffer(prpListPhys);
for (int i = 1; i < pagesNeeded; i++) {
prpList[i - 1] = dmaPhys + (uint64_t)i * 0x1000;
}
cmd.Prp2 = prpListPhys;
}
}
cmd.Cdw10 = (uint32_t)(lba & 0xFFFFFFFF);
cmd.Cdw11 = (uint32_t)(lba >> 32);
cmd.Cdw12 = count - 1;
SubmitIoCommand(cmd);
CqEntry cqe;
bool ok = WaitIoCompletion(cqe);
if (pagesNeeded > 2 && cmd.Prp2 != 0 && cmd.Prp2 != dmaPhys + 0x1000) {
Memory::g_pfa->Free((void*)Memory::HHDM(cmd.Prp2));
}
Memory::g_pfa->Free(dmaVirt, pagesNeeded);
return ok;
}
};