Newer
Older
AMI-Aptio-BIOS-Reversed / Nvme / Nvme.md
@Ajax Dong Ajax Dong 2 days ago 14 KB Init

Nvme

Function Table

Address Name Description
NvmeDriverEntryPoint
NvmeMainEntry
NvmeTimerCallback1
NvmeTimerCallback2
NvmeDetectAndStart
NvmeInitController
NvmeSubmitAdminCommand
NvmePassThru
NvmeReadSectors
NvmeCopyUnicodeToAscii
NvmeMmioRead32
NvmeMmioWrite32
NvmeAdminSetFeatures
NvmeInstallBlockIo
Globals - populated at module entry
extern EFI_HANDLE gImageHandle;
qword_9040 - 4920 bytes
qword_9020 - 4096 bytes
qword_9018 - 48 bytes
qword_9038 - protocol table
ImageHandle_0 @ 0x8FA0
byte_8FC0 UINT8 gNvmeLegacyNsNext; // byte_8FD8 - next free NS slot
byte_8FC0 bit 0
Protocol GUIDs stored in .rdata / known from table at 0x8D80
EFI_GUID gEfiSmmIoProtocolGuid = {0x8E,0xC6,0x9D,0xBD,0x4C,0x9D,0x94,0xDB,0x65,0xAC,0xC5,0xC3,0x32,0x28,0xB8,0xB2};
0x3D4 ModuleEntryPoint (NVMe Driver Entry Point)
EFI_STATUS EFIAPI
Initialize SMM I/O protocol and debug infrastructure
NvmeInitSmmIo ();
Enable debug assertions and output for the driver
if ((INT8)((UINT8)NvmeGetCmosAddress (0xF9C4)) >= 0) {
Delay / stall for port initialization
UINT16 StallStatus = IoRead16 (0x80);
EFI_STATUS NvmeMainEntry (
GUID for MemoryOverwriteRequestControl variable (MorControl)
EFI_GUID MorGuid = {0xE20939BE, 0x7AD8, 0x4D6F, {0xA0, 0xE1, 0x3A, 0xF5, 0x6E, 0xE0, 0xE1, 0xA4}};
Allocate 4920 bytes for NVME_CONTROLLER
Status = gBS->AllocatePool (EfiBootServicesData, 4920, &gNvmeControllerBuffer);
Allocate 4096-byte identify buffer
Status = gBS->AllocatePool (EfiBootServicesData, 4096, &gNvmeIdentifyBuffer);
Allocate 48-byte features buffer
Status = gBS->AllocatePool (EfiBootServicesData, 48, &gNvmeFeaturesBuffer);
Allocate 0-sized protocol buffer (just handle table)
Status = gBS->AllocatePool (EfiBootServicesData, 0, &gNvmeProtocolBuffer);
Install timer callback for SMI watchdog (sub_494C)
Status = gBS->SetTimer (
8 * 10ms?
Open the driver binding protocol on our image handles
Status = gBS->OpenProtocol (
Install second timer callback (sub_4A40)
Open protocol on second table (unk_8E60)
Install the component name / driver family protocol
gNvmeBlockIoHandle = NULL;
Register the legacy SMM NVMe handler
Status = gBS->RegisterProtocolNotify (
Set MemoryOverwriteRequestControl variable
UINT16 MorData = 1;
Sends SMI/SW SMI to update controller context
Clear and set up the SW SMI data buffer (gNvmeControllerBuffer)
Locate the SMM communication protocol and trigger SMI
Status = gBS->LocateProtocol (&gEfiSmmCommunicationProtocolGuid, NULL, &SmmCommunicationProtocol);
Prepare communication buffer
Send the communication
Signal the timer event completion
Iterates all NVME partitions and records legacy device info
Get list of all handles with EFI_NVM_EXPRESS_PASS_THRU protocol
Status = gBS->LocateHandleBuffer (
Iterate up to 32 handles, for each
for (Index = 0; Index < HandleCount && Index < 32; Index++) {
Prepare the name string for "AMI NVMe BUS Driver" component
Copy the NVME_CONTROLLER data (424 bytes @ 0x8D80 area)
Copy the controller's namespace bitfield into identify buffer
Controller = (NVME_CONTROLLER*)NvmeControllerData;
Enumerate namespace list
LIST_ENTRY *NamespaceList = &Controller->NamespaceList;
Identify namespace data via protocol
NvmeIdentifyNamespace (Controller, Ns);
Copy namespace data into the scratch buffer
Copy media info from identify
Mark if last node
if (Node->ForwardLink == NamespaceList) {
Called from DriverBinding.Start
EFI_STATUS NvmeDetectAndStart (
Ensure device does not already have NvmePassThru installed
Already has pass-thru protocol skip NVMe detection
goto SKIP_NVME;
Open DevicePath protocol (required for NVMe)
Open PCI_IO protocol
Detect the NVME device - open controller
if (Status != EFI_ALREADY_STARTED) {
Initialize the controller structure
Status = NvmeInitController (ControllerHandle, This->DriverBindingHandle, &Controller);
Allocate namespace data buffer (4096 bytes)
UINT64 NamespaceBuffer;
Set up IO queues
Status = NvmeCreateIoSubmissionQueue (Controller, NamespaceBuffer, 1, 0);
Set up IO completion queues
NvmeSetQueueInterruptCoalescing (Controller);
Set additional queue configuration
Status = gBS->AllocatePool (EfiBootServicesData, 0x8000, &Controller->IoCompletionQueue);
Set max queue entries
if (Controller->Version < 0x10100) {
Create IO completion queue
Status = NvmeCreateIoCompletionQueue (Controller, Controller->AdminSubmissionQueue, 1, QueueSize);
Create child protocol handle
VOID *ChildBuffer;
Set up pass-through protocol instance
PassthruProtocol = (EFI_NVM_EXPRESS_PASS_THRU*)ChildBuffer;
Wire up protocol function pointers
Install the protocol
Status = gBS->InstallMultipleProtocolInterfaces (
Initialize the namespace list (linked list head)
InitializeListHead (&Controller->NamespaceList);
If RemainingDevicePath provided, process it
if (RemainingDevicePath != NULL) {
while (TRUE) {
Iterate to find all namespaces
LIST_ENTRY *FirstEntry = Controller->NamespaceList.ForwardLink;
only one entry
Get the namespace node
NVME_NAMESPACE EntryNs = (NVME_NAMESPACE)((UINT8*)FirstEntry - OFFSET_OF_NS_ENTRY);
Try to locate and call external protocols for advanced features
if (gNvmeBlockIoHandle != NULL) {
Block I/O installation
if (gNvmeBlockIoHandle == NULL) {
Try SMM communication protocol for BlockIoSmm
if **(gNvmeSmmCommProtocol == NULL gNvmeSmmCommProtocol != NULL) {**
fallback }
Install Block I/O and Disk I/O protocols
Status = NvmeInstallBlockIo (This, Controller);
Install driver diagnostic protocol
Register for end of DXE notification
if (gNvmeDriverBindingHandle == NULL) {
If already started, just open pass-through on handle
Set up I/O completion and SQs
Register with SMM if needed
Parses PCI IO, identifies controller capabilities, allocates admin
EFI_STATUS NvmeInitController (
Allocate controller structure (424 bytes)
Status = gBS->AllocatePool (EfiBootServicesData, sizeof (NVME_CONTROLLER), (VOID)&Ctrl);**
Allocate PCI IO structure (24 bytes)
Status = gBS->AllocatePool (EfiBootServicesData, 24, (VOID)&Ctrl->PciIo);**
Initialize namespace linked list
InitializeListHead (&Ctrl->NamespaceList);
Open PCI_IO protocol to access the NVMe BAR
Read PCI config space for MMIO base address (BAR 0)
Status = TempPciIo->Pci.Read (TempPciIo, EfiPciIoWidthUint16, 0x24, 1, &Ctrl->MaxQueueEntries);
Read capabilities (BAR size)
UINT32 BarValue;
Parse PCI IO BAR 0 to get MMIO base via GetBarAttributes
Read the NVMe controller registers from MMIO
UINT8 IdentifyBuffer[64];
BAR0 0, // offset to NVMe regs
Decode NVMe capability register (CAP)
CAP high
Get max page entries and set namespace capacity
Check controller ready status
if ((Ctrl->PageSize & 1) == 0) {
Clear CSTS.CFS if set
if ((NvmeGetCapability (Ctrl, 20) & 1) != 0) {
Wait for controller ready (CSTS.RDY)
Configure the controller (CC register)
Allocate admin submission queue (ASQ) buffer
Allocate physically contiguous pages for admin queues
Map the buffer
Allocate second buffer for admin completion queue
Set admin queue sizes (ASQ and ACQ entries)
if (Ctrl->MaxQueueEntries < 0x100) {
Zero the admin queues
Set AQA (Admin Queue Attributes) register
NvmeSetCapability (Ctrl, 36
Enable the controller (CC.EN = 1)
Allocate scratch buffer for admin commands (72 bytes)
Status = gBS->AllocatePool (EfiBootServicesData, 72, &Ctrl->AdminCmdBuf);
Enable the controller with set features
Wait for ready
Ctrl = (NVME_CONTROLLER*)Ctrl; // keep consistent
Submit admin command to set features (number of queues)
Status = NvmeAdminSetFeatures (Ctrl, Ctrl->AdminCmdBuf);
Install the pass-through protocol on the controller handle
Wraps the command into a submission queue entry, writes doorbell.
EFI_STATUS NvmeSubmitAdminCommand (
The admin queue is now busy
if (Controller->AdminBusy) {
Get the current completion queue phase and index
if (CmdType) {
Flip the phase tag
Get the submission entry
CompletionIndex = CompletionHead;
Wait for a free slot in the submission queue
UINT32 TimeoutMs = 100 * Cmd->TimeoutMs;
Check if slot is available
if (((UINT16*)SubmissionEntry)[6] == CmdId &&
Check phase tag
UINT8 PhaseTag = CmdType ? QueueInfo[6] : QueueInfo[11];
Check for controller fatal error
if ((NvmeGetCapability (Controller, 28) & 2) != 0) {
Set doorbell register
CompletionIndex = ((UINT16*)SubmissionEntry)[4];
Write the submission queue tail doorbell
NvmeMmioWrite (
Copy completion result
if (CplResult != NULL) {
Advance the submission queue tail
Main command dispatch for read/write/admin commands.
Determine if admin or I/O command
if (Command->IsAdmin) {
Admin command -> submit via admin submission queue
Status = NvmeSubmitAdminCommand (Controller, Command, NULL);
switch (Command->Opcode) {
EFI_STATUS NvmeReadSectors (
Prepare the command
NvmeZeroMem (&Cmd, sizeof (Cmd));
namespace will be assigned
Map the buffer for DMA
DataPhys = (UINT64)Buffer; // for simplicity, assume mapped
PRP2 needed
VOID NvmeCopyUnicodeToAscii (
Basic ASCII-safe unicode copy
while (*UnicodeStr) {
UINT32 NvmeMmioRead32 (
if (Controller->PciIo != NULL) {
VOID NvmeMmioWrite32 (
Sets features and triggers admin queue doorbell (ASQ tail doorbell)
EFI_STATUS NvmeAdminSetFeatures (
already in progress
NCQR = 1, NSQR = 1 (one IO CQ, one IO SQ)
Called after NVMe detection completes.
EFI_STATUS NvmeInstallBlockIo (
Get list of handles with NVME pass-thru protocol
Allocate the Block IO protocol structure (72 bytes)
VOID *BlockIoBuffer;
Error condition, already reported
NvmeDebugPrint (0x80000000LL, L"\nASSERT_EFI_ERROR (Status = %r)\n", Status);
Iterate over all handles to install block I/O for each namespace
for (Index = 0; Index < HandleCount; Index++) {
Parse the namespace from the controller
NVME_CONTROLLER Ctrl = (NVME_CONTROLLER)NvmeProtocolData;
Prep the admin command for identify namespace
NvmeZeroMem (BlockIoBuffer, 72);
Send IDENTIFY (ACTIVE) command (Opcode=6, CNS=2)
Send IDENTIFY (NS, CNS=0) to get namespace details
Enable asynchronous event notification
Determine the controller timeout for completion
Wait for command completion (CSTS phase bit)
while (((NvmeMmioRead32 (Ctrl, 28) & 0xC) != 8)) {
0x77F0 NvmeSubmitIoCommand Submit an I/O command to the SQ
Handles command slot allocation, doorbell registration.
0x79EC NvmeProcessCompletionQueue Process I/O completion queue
Reaps completions, re-arms the CQ doorbell, re-submits if needed.
0x2834 NvmeCreateDeviceNode Create a namespace device node
Walks the namespace list, allocates BlockIO/DevicePath protocols.
0x14A8 NvmeFreeDeviceMemory Free all allocated controller resources
Uninstalls protocols, frees buffers, maps, and the controller itself.
0x3120 NvmeSetQueueInterruptCoalescing Set IO queue interrupt
Configures the controller's interrupt coalescing registers.
0x244C NvmeCreateIoSubmissionQueue Create IO Submission Queue
Allocates the queue, maps to physical address, sends admin command.
0x6A1C NvmeCreateIoCompletionQueue Create IO Completion Queue
Allocates and maps the completion queue, enables controller.
0x3304 NvmeSecurityInit Security initialization (BlockSID)
Issues SAT3 Freeze Lock and BlockSID commands over pass-through.
0x4E80 NvmeLegacySmmHandler Handle legacy SMM event
Triggered at end of DXE to install legacy device protocols.
0x4C80 NvmeLegacyDeviceInstall Install NVMe legacy device protocol
Sends AQ commands for namespace flush then configures legacy path.
0x43F4 NvmeIdentifyNamespace Identify and record namespace info
Performs IDENTIFY command on a namespace via SMM communication.
0x467C NvmeLegacySmmProtocolInstall Install legacy SMM protocol
Adds the legacy NVMe device protocol to the SMM communication.

Generated by HR650X BIOS Decompilation Project