/** @file
DiskIoDxe - UEFI Disk I/O DXE driver.
This driver produces EFI_DISK_IO_PROTOCOL and EFI_DISK_IO2_PROTOCOL
on top of the underlying EFI_BLOCK_IO_PROTOCOL / EFI_BLOCK_IO2_PROTOCOL.
It translates byte-addressable Disk I/O requests into block-aligned
Block I/O operations.
Copyright (c) 2025, Insyde Software Corporation. All rights reserved.
Copyright (c) 2006 - 2018, Intel Corporation. All rights reserved.
Source tree: MdeModulePkg/Universal/Disk/DiskIoDxe/DiskIo.c
e:\hs\MdeModulePkg\Universal\Disk\DiskIoDxe\DiskIo.c
Auto-generated from IDA analysis of DiskIoDxe.efi (port 13343).
All function addresses verified against the binary.
**/
#include "DiskIoDxe.h"
//
// Module globals (set by _ModuleEntryPoint at 0x390)
//
EFI_HANDLE gImageHandle = NULL;
EFI_SYSTEM_TABLE *gSystemTable = NULL;
EFI_BOOT_SERVICES *gBootServices = NULL;
EFI_RUNTIME_SERVICES *gRuntimeServices = NULL;
//
// Debug print error level protocol (lazily resolved)
// qword_38E0 in the binary
//
DEBUG_PRINT_ERROR_LEVEL_PROTOCOL *gDebugPrintErrorLevelProtocol = NULL;
//
// Driver Binding Protocol instance
// Installed in DiskIoDxeInstallProtocols (sub_448)
//
EFI_DRIVER_BINDING_PROTOCOL gDiskIoDriverBinding = {
DiskIoDriverBindingSupported,
DiskIoDriverBindingStart,
DiskIoDriverBindingStop,
0x10, // Version
NULL, // ImageHandle - set during binding
NULL // DriverBindingHandle - set by InstallMultipleProtocolInterfaces
};
//
// Component Name tables (at .rdata +0x3868 and +0x3880)
// Stored as: { GetDriverName, GetControllerName, DriverNameString }
// where GetDriverName = DiskIoComponentNameGetDriverName (0x1CEC)
// GetControllerName = DiskIoComponentNameGetControllerName (0x1E48)
// DriverNameString = "Generic Disk I/O Driver" (0x2EA0) for COMPONENT_NAME
// DriverNameString = "Disk I/O Driver" (0x2E94 "eng;en" table) for COMPONENT_NAME2
//
EFI_COMPONENT_NAME_PROTOCOL gDiskIoComponentName = {
DiskIoComponentNameGetDriverName,
DiskIoComponentNameGetControllerName,
L"eng" // Supported Languages
};
EFI_COMPONENT_NAME2_PROTOCOL gDiskIoComponentName2 = {
DiskIoComponentNameGetDriverName,
DiskIoComponentNameGetControllerName,
L"eng;en" // Supported Languages
};
//
// Forward declarations for library-internal helpers
//
/**
Internal ASSERT macro wrapper that calls into the debug print
error level protocol if available, then enters CPU deadloop.
From source: MdePkg/Library/UefiDebugLibConOut/DebugLib.c
Address in binary: 0x1F5C (sub_1F5C)
@param FileName Source file name string.
@param Line Line number.
@param Message Assertion expression string.
**/
VOID
EFIAPI
InternalAssert (
IN CONST CHAR8 *FileName,
IN UINTN Line,
IN CONST CHAR8 *Message
);
//
// ============================================================================
// Module Entry Point
// ============================================================================
/**
The module entry point for DiskIoDxe (0x390, _ModuleEntryPoint).
Called by the DXE core. Saves the image handle and system table,
calls library constructors, then installs the driver binding protocol
and component name protocols.
@param ImageHandle The firmware allocated handle for the EFI image.
@param SystemTable A pointer to the EFI System Table.
@return EFI_SUCCESS The driver entry point completed successfully.
**/
EFI_STATUS
EFIAPI
_ModuleEntryPoint (
IN EFI_HANDLE ImageHandle,
IN EFI_SYSTEM_TABLE *SystemTable
)
{
gImageHandle = ImageHandle;
gSystemTable = SystemTable;
if (ImageHandle == NULL) {
InternalAssert (
(CONST CHAR8 *)"e:\\hs\\MdePkg\\Library\\UefiBootServicesTableLib\\UefiBootServicesTableLib.c",
51,
"gImageHandle != ((void *) 0)"
);
}
if (SystemTable == NULL) {
InternalAssert (
(CONST CHAR8 *)"e:\\hs\\MdePkg\\Library\\UefiBootServicesTableLib\\UefiBootServicesTableLib.c",
57,
"gST != ((void *) 0)"
);
}
gBootServices = SystemTable->BootServices;
if (gBootServices == NULL) {
InternalAssert (
(CONST CHAR8 *)"e:\\hs\\MdePkg\\Library\\UefiBootServicesTableLib\\UefiBootServicesTableLib.c",
63,
"gBS != ((void *) 0)"
);
}
gRuntimeServices = SystemTable->RuntimeServices;
if (gRuntimeServices == NULL) {
InternalAssert (
(CONST CHAR8 *)"e:\\hs\\MdePkg\\Library\\UefiRuntimeServicesTableLib\\UefiRuntimeServicesTableLib.c",
47,
"gRT != ((void *) 0)"
);
}
//
// Process library constructors (sub_26D0)
// This calls ProcessLibraryConstructorList to initialize HOB list, etc.
//
ProcessLibraryConstructorList ();
//
// Install the driver binding and component name protocols (sub_448)
//
return DiskIoDxeInstallProtocols (ImageHandle);
}
//
// ============================================================================
// Protocol Installation
// ============================================================================
/**
Installs the Driver Binding Protocol and Component Name protocols
on the image handle (0x448, DiskIoDxeInstallProtocols).
@param ImageHandle The handle to install protocols on.
@return EFI_SUCCESS All protocols installed.
@return Others Error from InstallMultipleProtocolInterfaces.
**/
EFI_STATUS
EFIAPI
DiskIoDxeInstallProtocols (
IN EFI_HANDLE ImageHandle
)
{
EFI_STATUS Status;
Status = gBootServices->InstallMultipleProtocolInterfaces (
&ImageHandle,
&gEfiDriverBindingProtocolGuid,
&gDiskIoDriverBinding,
&gEfiComponentNameProtocolGuid,
&gDiskIoComponentName,
&gEfiComponentName2ProtocolGuid,
&gDiskIoComponentName2,
NULL
);
if (EFI_ERROR (Status)) {
InternalAssert (
(CONST CHAR8 *)"e:\\hs\\MdeModulePkg\\Universal\\Disk\\DiskIoDxe\\DiskIo.c",
1265,
"!EFI_ERROR (Status)"
);
}
return Status;
}
//
// ============================================================================
// Driver Binding Protocol: Supported, Start, Stop
// ============================================================================
/**
Tests whether the driver supports a given controller (0x520).
Opens and immediately closes the Block I/O protocol on the controller
handle to verify it supports block-oriented media.
@param This Driver Binding protocol instance.
@param ControllerHandle Handle of the controller to test.
@param RemainingDevicePath Remaining device path (optional).
@return EFI_SUCCESS Controller supports Block I/O.
@return EFI_UNSUPPORTED Controller does not support Block I/O.
**/
EFI_STATUS
EFIAPI
DiskIoDriverBindingSupported (
IN EFI_DRIVER_BINDING_PROTOCOL *This,
IN EFI_HANDLE ControllerHandle,
IN EFI_DEVICE_PATH_PROTOCOL *RemainingDevicePath
)
{
EFI_STATUS Status;
EFI_BLOCK_IO_PROTOCOL *BlockIo;
Status = gBootServices->OpenProtocol (
ControllerHandle,
&gEfiBlockIoProtocolGuid,
(VOID **)&BlockIo,
This->DriverBindingHandle,
ControllerHandle,
EFI_OPEN_PROTOCOL_BY_DRIVER
);
if (EFI_ERROR (Status)) {
return Status;
}
gBootServices->CloseProtocol (
ControllerHandle,
&gEfiBlockIoProtocolGuid,
This->DriverBindingHandle,
ControllerHandle
);
return EFI_SUCCESS;
}
/**
Starts the Disk I/O driver on a controller handle (0x58C).
Opens Block I/O and optionally Block I/O 2 on the controller handle,
allocates a DISK_IO_PRIVATE_DATA instance, allocates an aligned media
buffer for bouncing unaligned transfers, and installs the Disk I/O
protocols.
@param This Driver Binding protocol instance.
@param ControllerHandle Handle of the controller.
@param RemainingDevicePath Remaining device path (optional).
@return EFI_SUCCESS Protocols installed.
@return EFI_OUT_OF_RESOURCES Memory allocation failure.
@return Others Error from OpenProtocol or InstallProtocol.
**/
EFI_STATUS
EFIAPI
DiskIoDriverBindingStart (
IN EFI_DRIVER_BINDING_PROTOCOL *This,
IN EFI_HANDLE ControllerHandle,
IN EFI_DEVICE_PATH_PROTOCOL *RemainingDevicePath
)
{
EFI_STATUS Status;
EFI_BLOCK_IO_PROTOCOL *BlockIo;
EFI_BLOCK_IO2_PROTOCOL *BlockIo2;
DISK_IO_PRIVATE_DATA *Instance;
EFI_HANDLE *HandlePtr;
UINTN BlockSize;
UINTN IoAlign;
//
// Allocate a pool tag (8 bytes) for tracking
//
HandlePtr = gBootServices->AllocatePool (EfiBootServicesData, sizeof (EFI_HANDLE));
if (HandlePtr == NULL) {
return EFI_OUT_OF_RESOURCES;
}
//
// Open Block I/O protocol BY_DRIVER
//
Status = gBootServices->OpenProtocol (
ControllerHandle,
&gEfiBlockIoProtocolGuid,
(VOID **)&BlockIo,
This->DriverBindingHandle,
ControllerHandle,
EFI_OPEN_PROTOCOL_BY_DRIVER
);
if (EFI_ERROR (Status)) {
gBootServices->FreePool (HandlePtr);
return Status;
}
//
// Optionally open Block I/O 2 protocol BY_DRIVER
//
Status = gBootServices->OpenProtocol (
ControllerHandle,
&gEfiBlockIo2ProtocolGuid,
(VOID **)&BlockIo2,
This->DriverBindingHandle,
ControllerHandle,
EFI_OPEN_PROTOCOL_BY_DRIVER
);
if (EFI_ERROR (Status)) {
BlockIo2 = NULL;
}
//
// Allocate and initialize the private data structure
// Size: 0x88 (136 bytes)
//
Instance = InternalAllocateCopyPool (BlockIo2, sizeof (DISK_IO_PRIVATE_DATA));
if (Instance == NULL) {
Status = EFI_OUT_OF_RESOURCES;
goto CloseBlockIo;
}
//
// Validate BlockIo vs BlockIo2 alignment consistency
//
if (BlockIo2 != NULL) {
if ((BlockIo->Media->IoAlign != BlockIo2->Media->IoAlign) ||
(BlockIo->Media->BlockSize != BlockIo2->Media->BlockSize)) {
InternalAssert (
(CONST CHAR8 *)"e:\\hs\\MdeModulePkg\\Universal\\Disk\\DiskIoDxe\\DiskIo.c",
181,
"(Instance->BlockIo2 == ((void *) 0)) || ((Instance->BlockIo->Media->IoAlign == Instance->BlockIo2->Media->IoAlign) && (Instance->BlockIo->Media->BlockSize == Instance->BlockIo2->Media->BlockSize) )"
);
}
}
Instance->Signature = DISK_IO_PRIVATE_DATA_SIGNATURE;
//
// Initialize DiskIo protocol interface (at Instance + 0x08)
//
Instance->DiskIo.Revision = EFI_DISK_IO_INTERFACE_REVISION;
Instance->DiskIo.ReadDisk = DiskIoReadDisk;
Instance->DiskIo.WriteDisk = DiskIoWriteDisk;
//
// Initialize DiskIo2 protocol interface (at Instance + 0x28)
//
Instance->DiskIo2.Revision = EFI_DISK_IO2_INTERFACE_REVISION;
Instance->DiskIo2.Cancel = DiskIo2Cancel;
Instance->DiskIo2.ReadDiskEx = DiskIo2ReadDiskEx;
Instance->DiskIo2.WriteDiskEx= DiskIo2WriteDiskEx;
Instance->DiskIo2.FlushDiskEx= DiskIo2FlushDiskEx;
Instance->BlockIo = BlockIo;
Instance->BlockIo2 = BlockIo2;
//
// Initialize the task list
//
InitializeListHead (&Instance->TaskList);
//
// Initialize the EFI lock (LockType=16, Tpl=4, EfiLockReleased=1)
//
EfiInitializeLock (&Instance->TaskListLock, TPL_NOTIFY);
//
// Allocate an aligned media buffer for bouncing unaligned transfers
// Size: round up BlockSize to 0x1000 alignment, with IoAlign consideration
//
BlockSize = BlockIo->Media->BlockSize;
IoAlign = BlockIo->Media->IoAlign;
Instance->MediaBuffer = InternalAllocateAlignedBuffer (
(BlockSize >> 12) + ((BlockSize & 0xFFF) != 0),
IoAlign | DISK_IO_ALIGNMENT
);
if (Instance->MediaBuffer == NULL) {
Status = EFI_OUT_OF_RESOURCES;
goto FreeInstance;
}
//
// Install the Disk I/O protocols on the controller handle
//
if (BlockIo2 != NULL) {
Status = gBootServices->InstallMultipleProtocolInterfaces (
&ControllerHandle,
&gEfiDiskIoProtocolGuid,
&Instance->DiskIo,
&gEfiDiskIo2ProtocolGuid,
&Instance->DiskIo2,
NULL
);
} else {
Status = gBootServices->InstallProtocolInterface (
&ControllerHandle,
&gEfiDiskIoProtocolGuid,
EFI_NATIVE_INTERFACE,
&Instance->DiskIo
);
}
if (EFI_ERROR (Status)) {
//
// On failure, free the aligned buffer and instance
//
InternalFreeAlignedBuffer (
Instance->MediaBuffer,
(BlockSize >> 12) + ((BlockSize & 0xFFF) != 0)
);
InternalFreePool (Instance);
goto CloseBlockIo;
}
gBootServices->FreePool (HandlePtr);
return EFI_SUCCESS;
FreeInstance:
InternalFreePool (Instance);
CloseBlockIo:
gBootServices->CloseProtocol (
ControllerHandle,
&gEfiBlockIoProtocolGuid,
This->DriverBindingHandle,
ControllerHandle
);
if (BlockIo2 != NULL) {
gBootServices->CloseProtocol (
ControllerHandle,
&gEfiBlockIo2ProtocolGuid,
This->DriverBindingHandle,
ControllerHandle
);
}
gBootServices->FreePool (HandlePtr);
return Status;
}
/**
Stops the Disk I/O driver on a controller handle (0x7D0).
Uninstalls the Disk I/O protocols, ensures all outstanding non-blocking
I/O has completed, frees the aligned media buffer, closes the underlying
Block I/O protocols, and frees the private data instance.
@param This Driver Binding protocol instance.
@param ControllerHandle Handle of the controller to stop.
@param NumberOfChildren Number of child handles (unused).
@param ChildHandleBuffer Array of child handles (unused).
@return EFI_SUCCESS Driver stopped.
**/
EFI_STATUS
EFIAPI
DiskIoDriverBindingStop (
IN EFI_DRIVER_BINDING_PROTOCOL *This,
IN EFI_HANDLE ControllerHandle,
IN UINTN NumberOfChildren,
IN EFI_HANDLE *ChildHandleBuffer
)
{
EFI_STATUS Status;
DISK_IO_PRIVATE_DATA *Instance;
EFI_DISK_IO_PROTOCOL *DiskIo;
//
// Open the installed DiskIo protocol to get our private instance
//
Status = gBootServices->OpenProtocol (
ControllerHandle,
&gEfiDiskIoProtocolGuid,
(VOID **)&DiskIo,
This->DriverBindingHandle,
ControllerHandle,
EFI_OPEN_PROTOCOL_GET_PROTOCOL
);
if (EFI_ERROR (Status)) {
return Status;
}
//
// CR macro: DiskIo is at Instance + 0x08 (offset 8)
//
Instance = CR (DiskIo, DISK_IO_PRIVATE_DATA, DiskIo, DISK_IO_PRIVATE_DATA_SIGNATURE);
//
// Drain all outstanding non-blocking I/O
//
do {
EfiAcquireLock (&Instance->TaskListLock);
} while (!DiskIoCheckAllSubtasksCompleted (Instance));
EfiReleaseLock (&Instance->TaskListLock);
//
// Uninstall protocols
//
if (Instance->BlockIo2 != NULL) {
Status = gBootServices->UninstallMultipleProtocolInterfaces (
ControllerHandle,
&gEfiDiskIoProtocolGuid,
&Instance->DiskIo,
&gEfiDiskIo2ProtocolGuid,
&Instance->DiskIo2,
NULL
);
} else {
Status = gBootServices->UninstallProtocolInterface (
ControllerHandle,
&gEfiDiskIoProtocolGuid,
&Instance->DiskIo
);
}
if (EFI_ERROR (Status)) {
return Status;
}
//
// Free the aligned media buffer
//
if (Instance->MediaBuffer != NULL) {
InternalFreeAlignedBuffer (
Instance->MediaBuffer,
(Instance->BlockIo->Media->BlockSize >> 12)
+ ((Instance->BlockIo->Media->BlockSize & 0xFFF) != 0)
);
}
//
// Close the underlying Block I/O protocols
//
gBootServices->CloseProtocol (
ControllerHandle,
&gEfiBlockIoProtocolGuid,
This->DriverBindingHandle,
ControllerHandle
);
if (Instance->BlockIo2 != NULL) {
gBootServices->CloseProtocol (
ControllerHandle,
&gEfiBlockIo2ProtocolGuid,
This->DriverBindingHandle,
ControllerHandle
);
}
//
// Free the instance
//
InternalFreePool (Instance);
return EFI_SUCCESS;
}
//
// ============================================================================
// EFI_DISK_IO_PROTOCOL Implementation
// ============================================================================
/**
EFI_DISK_IO_PROTOCOL.ReadDisk implementation (0x1BEC).
Validates the CR signature, then dispatches to DiskIoRequest as a
blocking read. The CR check: DiskIo is at Instance + 0x08.
@param This Pointer to the EFI_DISK_IO_PROTOCOL instance.
@param MediaId Media ID to verify.
@param Offset Starting byte offset on the media.
@param BufferSize Size of the buffer in bytes.
@param Buffer Destination buffer for the data.
@return EFI_SUCCESS or error from DiskIoRequest.
**/
EFI_STATUS
EFIAPI
DiskIoReadDisk (
IN EFI_DISK_IO_PROTOCOL *This,
IN UINT32 MediaId,
IN UINT64 Offset,
IN UINTN BufferSize,
OUT VOID *Buffer
)
{
DISK_IO_PRIVATE_DATA *Instance;
Instance = CR (This, DISK_IO_PRIVATE_DATA, DiskIo, DISK_IO_PRIVATE_DATA_SIGNATURE);
return DiskIoRequest (Instance, FALSE, MediaId, Offset, NULL, BufferSize, Buffer);
}
/**
EFI_DISK_IO_PROTOCOL.WriteDisk implementation (0x1C6C).
Validates the CR signature, then dispatches to DiskIoRequest as a
blocking write.
@param This Pointer to the EFI_DISK_IO_PROTOCOL instance.
@param MediaId Media ID to verify.
@param Offset Starting byte offset on the media.
@param BufferSize Size of the buffer in bytes.
@param Buffer Source buffer with the data to write.
@return EFI_SUCCESS or error from DiskIoRequest.
**/
EFI_STATUS
EFIAPI
DiskIoWriteDisk (
IN EFI_DISK_IO_PROTOCOL *This,
IN UINT32 MediaId,
IN UINT64 Offset,
IN UINTN BufferSize,
IN VOID *Buffer
)
{
DISK_IO_PRIVATE_DATA *Instance;
Instance = CR (This, DISK_IO_PRIVATE_DATA, DiskIo, DISK_IO_PRIVATE_DATA_SIGNATURE);
return DiskIoRequest (Instance, TRUE, MediaId, Offset, NULL, BufferSize, Buffer);
}
//
// ============================================================================
// EFI_DISK_IO2_PROTOCOL Implementation
// ============================================================================
/**
EFI_DISK_IO2_PROTOCOL.ReadDiskEx implementation (0x1968, non-blocking).
Validates the CR signature (DiskIo2 at Instance + 0x20), then
dispatches to DiskIoRequest with the token.
@param This Pointer to the EFI_DISK_IO2_PROTOCOL instance.
@param MediaId Media ID to verify.
@param Offset Starting byte offset on the media.
@param Token EFI_DISK_IO2_TOKEN for non-blocking completion.
@param BufferSize Size of the buffer in bytes.
@param Buffer Destination buffer for the data.
@return EFI_SUCCESS or error from DiskIoRequest.
**/
EFI_STATUS
EFIAPI
DiskIo2ReadDiskEx (
IN EFI_DISK_IO2_PROTOCOL *This,
IN UINT32 MediaId,
IN UINT64 Offset,
IN EFI_DISK_IO2_TOKEN *Token,
IN UINTN BufferSize,
OUT VOID *Buffer
)
{
DISK_IO_PRIVATE_DATA *Instance;
Instance = CR (This, DISK_IO_PRIVATE_DATA, DiskIo2, DISK_IO_PRIVATE_DATA_SIGNATURE);
return DiskIoRequest (Instance, FALSE, MediaId, Offset, Token, BufferSize, Buffer);
}
/**
EFI_DISK_IO2_PROTOCOL.WriteDiskEx implementation (0x19EC, non-blocking).
Same pattern as ReadDiskEx but for write operations.
@param This Pointer to the EFI_DISK_IO2_PROTOCOL instance.
@param MediaId Media ID to verify.
@param Offset Starting byte offset on the media.
@param Token EFI_DISK_IO2_TOKEN for non-blocking completion.
@param BufferSize Size of the buffer in bytes.
@param Buffer Source buffer with the data to write.
@return EFI_SUCCESS or error from DiskIoRequest.
**/
EFI_STATUS
EFIAPI
DiskIo2WriteDiskEx (
IN EFI_DISK_IO2_PROTOCOL *This,
IN UINT32 MediaId,
IN UINT64 Offset,
IN EFI_DISK_IO2_TOKEN *Token,
IN UINTN BufferSize,
IN VOID *Buffer
)
{
DISK_IO_PRIVATE_DATA *Instance;
Instance = CR (This, DISK_IO_PRIVATE_DATA, DiskIo2, DISK_IO_PRIVATE_DATA_SIGNATURE);
return DiskIoRequest (Instance, TRUE, MediaId, Offset, Token, BufferSize, Buffer);
}
/**
EFI_DISK_IO2_PROTOCOL.FlushDiskEx implementation (0x1AD0).
If Token is NULL or Token->Event is NULL, does a synchronous flush
via BlockIo2. Otherwise creates a DISK_IO_FLUSH_TASK, sets up a
notification event (DiskIo2FlushCallback), and submits to BlockIo2
as a non-blocking flush.
@param This Pointer to the EFI_DISK_IO2_PROTOCOL instance.
@param Token Non-blocking completion token (optional).
@return EFI_SUCCESS or error from BlockIo2 flush.
**/
EFI_STATUS
EFIAPI
DiskIo2FlushDiskEx (
IN EFI_DISK_IO2_PROTOCOL *This,
IN EFI_DISK_IO2_TOKEN *Token
)
{
DISK_IO_PRIVATE_DATA *Instance;
DISK_IO_FLUSH_TASK *FlushTask;
EFI_STATUS Status;
Instance = CR (This, DISK_IO_PRIVATE_DATA, DiskIo2, DISK_IO_PRIVATE_DATA_SIGNATURE);
if ((Token == NULL) || (Token->Event == NULL)) {
//
// Synchronous flush
//
return Instance->BlockIo2->FlushBlocksEx (Instance->BlockIo2, NULL);
}
//
// Non-blocking flush: allocate flush task (0x20 bytes)
//
FlushTask = InternalAllocatePool (EfiBootServicesData, sizeof (DISK_IO_FLUSH_TASK));
if (FlushTask == NULL) {
return EFI_OUT_OF_RESOURCES;
}
//
// Create a notification event
//
Status = gBootServices->CreateEvent (
EVT_NOTIFY_SIGNAL,
TPL_NOTIFY,
DiskIo2FlushCallback,
FlushTask,
&FlushTask->Event
);
if (EFI_ERROR (Status)) {
InternalFreePool (FlushTask);
return Status;
}
FlushTask->Signature = DISK_IO_FLUSH_TASK_SIGNATURE;
FlushTask->Token = Token;
//
// Submit to BlockIo2 with our event
//
Status = Instance->BlockIo2->FlushBlocksEx (Instance->BlockIo2, FlushTask->Event);
if (EFI_ERROR (Status)) {
gBootServices->CloseEvent (FlushTask->Event);
InternalFreePool (FlushTask);
return Status;
}
return EFI_SUCCESS;
}
/**
EFI_DISK_IO2_PROTOCOL.Cancel implementation (0x124C).
Acquires the task list lock, scans all tasks, and for each task:
- Aborts all pending subtasks by signaling them with EFI_ABORTED
- Aborts the task's token
If Token is non-NULL, cancels only the matching token.
If Token is NULL, cancels all outstanding tasks.
@param This Pointer to the EFI_DISK_IO2_PROTOCOL instance.
@param Token If non-NULL, cancel only this token. NULL cancels all.
@return EFI_SUCCESS.
**/
EFI_STATUS
EFIAPI
DiskIo2Cancel (
IN EFI_DISK_IO2_PROTOCOL *This,
IN EFI_DISK_IO2_TOKEN *Token
)
{
DISK_IO_PRIVATE_DATA *Instance;
LIST_ENTRY *Entry;
LIST_ENTRY *NextEntry;
DISK_IO_TASK *Task;
Instance = CR (This, DISK_IO_PRIVATE_DATA, DiskIo2, DISK_IO_PRIVATE_DATA_SIGNATURE);
//
// Lock the task list
//
EfiAcquireLock (&Instance->TaskListLock);
//
// Walk the task list
//
Entry = GetFirstNode (&Instance->TaskList);
while (!IsNodeInList (&Instance->TaskList, Entry)) {
Task = CR (Entry, DISK_IO_TASK, Link, DISK_IO_TASK_SIGNATURE);
NextEntry = GetNextNode (&Instance->TaskList, Entry);
if ((Token == NULL) || (Task->Token == Token)) {
//
// Cancel this task: abort the user's token
//
if (Task->Token != NULL) {
Task->Token->TransactionStatus = EFI_ABORTED;
gBootServices->SignalEvent (Task->Token->Event);
Task->Token = NULL;
}
//
// Free the task (implicitly removes from list)
//
RemoveEntryList (Entry);
InternalFreePool (Task);
}
Entry = NextEntry;
}
EfiReleaseLock (&Instance->TaskListLock);
return EFI_SUCCESS;
}
//
// ============================================================================
// Core I/O Dispatcher: DiskIoRequest
// ============================================================================
/**
Core request dispatcher (0x1464).
This is the heart of the driver. It:
1. Determines if the request is blocking (Token == NULL) or non-blocking.
2. For non-blocking: allocates a DISK_IO_TASK and inserts it into
Instance->TaskList.
3. Calls DiskIoCreateSubtasks to break the request into block-aligned
subtasks, handling head/tail unaligned fragments.
4. Iterates through the subtask list, dispatching each to either
BlockIo (blocking) or BlockIo2 (non-blocking).
5. For blocking reads with bounce buffer: copies data from bounce
buffer to user buffer after completion.
6. Cleans up subtasks after completion.
7. On error: aborts remaining subtasks.
8. For non-blocking tasks that were downgraded (completed synchronously):
signals the token directly.
@param Instance The private data instance.
@param IsWrite TRUE for write, FALSE for read.
@param MediaId The media ID to verify.
@param Offset Starting byte offset on the media.
@param Token NULL for blocking, non-NULL for non-blocking (DiskIo2).
@param BufferSize Number of bytes to transfer.
@param Buffer Data buffer.
@return EFI_SUCCESS Transfer completed.
@return EFI_OUT_OF_RESOURCES Memory allocation for task/subtask failed.
@return EFI_DEVICE_ERROR Block I/O returned error.
**/
EFI_STATUS
DiskIoRequest (
IN DISK_IO_PRIVATE_DATA *Instance,
IN BOOLEAN IsWrite,
IN UINT32 MediaId,
IN UINT64 Offset,
IN EFI_DISK_IO2_TOKEN *Token OPTIONAL,
IN UINTN BufferSize,
IN VOID *Buffer
)
{
EFI_BLOCK_IO_PROTOCOL *BlockIo;
EFI_BLOCK_IO2_PROTOCOL *BlockIo2;
DISK_IO_TASK *Task;
LIST_ENTRY SubtaskList;
LIST_ENTRY *SubtaskListPtr;
LIST_ENTRY *Entry;
LIST_ENTRY *NextEntry;
DISK_IO_SUBTASK *Subtask;
BOOLEAN IsBlocking;
EFI_STATUS Status;
UINTN BlockSize;
UINTN IoAlign;
UINT64 *AlignedBuffer;
UINT64 *TempPtr;
BlockIo = Instance->BlockIo;
BlockIo2 = Instance->BlockIo2;
BlockSize = BlockIo->Media->BlockSize;
IoAlign = BlockIo->Media->IoAlign;
if ((Token != NULL) && (Token->Event != NULL)) {
//
// Non-blocking (DiskIo2) path
//
IsBlocking = FALSE;
//
// Wait for previous non-blocking requests to drain
//
while (!DiskIoCheckAllSubtasksCompleted (Instance)) {
// Spin until all subtasks complete
}
//
// Allocate a DISK_IO_TASK (0x50 bytes)
//
Task = InternalAllocatePool (EfiBootServicesData, sizeof (DISK_IO_TASK));
if (Task == NULL) {
return EFI_OUT_OF_RESOURCES;
}
EfiAcquireLock (&Instance->TaskListLock);
//
// Insert the task into the instance's task list
//
InsertTailList (&Instance->TaskList, &Task->Link);
EfiReleaseLock (&Instance->TaskListLock);
Task->Signature = DISK_IO_TASK_SIGNATURE;
Task->Instance = Instance;
Task->Token = Token;
//
// Initialize the subtask list for this task
//
InitializeListHead (&Task->SubtaskList);
SubtaskListPtr = &Task->SubtaskList;
//
// Create the subtasks
//
DiskIoCreateSubtasks (
Instance,
IsWrite,
Offset,
BufferSize,
Buffer,
FALSE, // Non-blocking
Instance->MediaBuffer,
SubtaskListPtr
);
} else {
//
// Blocking path (Token == NULL or Token->Event == NULL)
//
IsBlocking = TRUE;
Task = NULL;
//
// Use local stack variable for the subtask list head
//
InitializeListHead (&SubtaskList);
SubtaskListPtr = &SubtaskList;
//
// Create the subtasks
//
DiskIoCreateSubtasks (
Instance,
IsWrite,
Offset,
BufferSize,
Buffer,
TRUE, // Blocking
Instance->MediaBuffer,
SubtaskListPtr
);
}
//
// If no subtasks were created, clean up and return error
//
if (IsListEmpty (SubtaskListPtr)) {
if (!IsBlocking && Task != NULL) {
RemoveEntryList (&Task->Link);
InternalFreePool (Task);
}
return EFI_OUT_OF_RESOURCES;
}
//
// Execute all subtasks
//
Status = EFI_SUCCESS;
Entry = GetFirstNode (SubtaskListPtr);
while (!IsNodeInList (SubtaskListPtr, Entry)) {
Subtask = CR (Entry, DISK_IO_SUBTASK, Link, DISK_IO_SUBTASK_SIGNATURE);
//
// Validate block alignment
//
if ((Subtask->Length % BlockSize != 0) && (Subtask->Length >= BlockSize)) {
InternalAssert (
(CONST CHAR8 *)"e:\\hs\\MdeModulePkg\\Universal\\Disk\\DiskIoDxe\\DiskIo.c",
880,
"(Subtask->Length % Media->BlockSize == 0) || (Subtask->Length < Media->BlockSize)"
);
}
NextEntry = GetNextNode (SubtaskListPtr, Entry);
//
// Choose between BlockIo2 (non-blocking) and BlockIo (blocking)
//
if (Subtask->IsWrite) {
//
// Write operation
//
if (Subtask->WorkingBuffer != NULL) {
//
// Copy from user buffer to bounce buffer first
//
CopyMem (
(UINT8 *)Subtask->WorkingBuffer + Subtask->OffsetInBlock,
Subtask->Buffer,
Subtask->Length
);
}
if (Subtask->UseBlockIo2) {
Status = BlockIo2->WriteBlocksEx (
BlockIo2,
MediaId,
Subtask->Lba,
&Subtask->Event,
Subtask->Length,
(Subtask->WorkingBuffer != NULL) ? Subtask->WorkingBuffer : Subtask->Buffer
);
} else {
Status = BlockIo->WriteBlocks (
BlockIo,
MediaId,
Subtask->Lba,
Subtask->Length,
(Subtask->WorkingBuffer != NULL) ? Subtask->WorkingBuffer : Subtask->Buffer
);
}
} else {
//
// Read operation
//
if (Subtask->UseBlockIo2) {
//
// Non-blocking read via BlockIo2
//
Status = BlockIo2->ReadBlocksEx (
BlockIo2,
MediaId,
Subtask->Lba,
&Subtask->Event,
Subtask->Length,
(Subtask->WorkingBuffer != NULL) ? Subtask->WorkingBuffer : Subtask->Buffer
);
} else {
//
// Blocking read via BlockIo
//
Status = BlockIo->ReadBlocks (
BlockIo,
MediaId,
Subtask->Lba,
Subtask->Length,
(Subtask->WorkingBuffer != NULL) ? Subtask->WorkingBuffer : Subtask->Buffer
);
//
// For blocking reads with bounce buffer: copy to user buffer
//
if (!EFI_ERROR (Status) && (Subtask->WorkingBuffer != NULL)) {
CopyMem (
Subtask->Buffer,
(UINT8 *)Subtask->WorkingBuffer + Subtask->OffsetInBlock,
Subtask->Length
);
}
}
}
//
// Clean up completed subtask
//
DiskIoFreeSubtask (Instance, (DISK_IO_TASK *)Subtask);
//
// If blocking and error: stop processing further subtasks
//
if (EFI_ERROR (Status) && !Subtask->UseBlockIo2) {
break;
}
Entry = NextEntry;
}
//
// If any blocking subtask failed, drain remaining subtask list
//
if (EFI_ERROR (Status)) {
while (!IsNodeInList (SubtaskListPtr, Entry)) {
Subtask = CR (Entry, DISK_IO_SUBTASK, Link, DISK_IO_SUBTASK_SIGNATURE);
NextEntry = GetNextNode (SubtaskListPtr, Entry);
DiskIoFreeSubtask (Instance, (DISK_IO_TASK *)Subtask);
Entry = NextEntry;
}
}
//
// For non-blocking tasks: cleanup and possibly signal completion
//
if (!IsBlocking && Task != NULL) {
EfiAcquireLock (&Instance->TaskListLock);
//
// If the non-blocking request was effectively blocking (all subtasks
// completed synchronously), signal the user's event directly
//
if (!EFI_ERROR (Status) && (Task->Token != NULL)) {
Task->Token->TransactionStatus = Status;
gBootServices->SignalEvent (Task->Token->Event);
}
//
// Remove the task from the instance's task list and free it
//
RemoveEntryList (&Task->Link);
InternalFreePool (Task);
EfiReleaseLock (&Instance->TaskListLock);
}
return Status;
}
//
// ============================================================================
// Subtask Management
// ============================================================================
/**
Creates block-aligned subtasks from a potentially unaligned I/O request
(0xD20, DiskIoCreateSubtasks).
The algorithm:
1. If BufferSize == 0: create a single zero-length subtask.
2. If Offset is not block-aligned: create a head fragment with bounce buffer.
3. Process aligned full blocks (potentially split at 0x1000 boundaries).
4. If remaining size is not block-aligned: create a tail fragment with
bounce buffer.
@param Instance The private data instance.
@param IsWrite TRUE for write, FALSE for read.
@param Offset Starting byte offset on the media.
@param BufferSize Size of the data in bytes.
@param Buffer Pointer to the data buffer.
@param IsBlocking TRUE if blocking, FALSE if non-blocking.
@param MediaBuffer Aligned buffer for bouncing unaligned transfers.
@param SubtasksPtr List head for the produced subtasks.
@return TRUE At least one subtask was created.
@return FALSE Allocation failure, no subtasks created.
**/
BOOLEAN
DiskIoCreateSubtasks (
IN DISK_IO_PRIVATE_DATA *Instance,
IN BOOLEAN IsWrite,
IN UINT64 Offset,
IN UINTN BufferSize,
IN VOID *Buffer,
IN BOOLEAN IsBlocking,
IN VOID *MediaBuffer,
OUT LIST_ENTRY *SubtasksPtr
)
{
EFI_BLOCK_IO_PROTOCOL *BlockIo;
UINTN BlockSize;
UINTN IoAlign;
UINT64 Lba;
UINT32 OffsetInBlock;
UINTN Remaining;
UINTN FragmentSize;
VOID *WorkingBuffer;
DISK_IO_SUBTASK *Subtask;
BlockIo = Instance->BlockIo;
BlockSize = BlockIo->Media->BlockSize;
IoAlign = BlockIo->Media->IoAlign;
//
// Calculate starting LBA and offset within first block
//
DivU64x32Remainder (Offset, (UINT32)BlockSize, &OffsetInBlock);
Lba = Offset / BlockSize;
Remaining = BufferSize;
if (BufferSize == 0) {
//
// Zero-length request - create a single empty subtask
//
Subtask = DiskIoCreateSubtask (
IsWrite,
Lba,
0,
0,
NULL,
Buffer,
!IsBlocking // UseBlockIo2 for non-blocking
);
if (Subtask == NULL) {
return FALSE;
}
InsertTailList (SubtasksPtr, &Subtask->Link);
return TRUE;
}
if (OffsetInBlock != 0) {
//
// Head fragment: start is not block-aligned
//
FragmentSize = BlockSize - OffsetInBlock;
if (FragmentSize > Remaining) {
FragmentSize = Remaining;
}
//
// For blocking: use MediaBuffer as the bounce buffer
// For non-blocking: reuse the subtask's own buffer
//
if (!IsBlocking) {
WorkingBuffer = MediaBuffer;
} else {
WorkingBuffer = MediaBuffer;
}
Subtask = DiskIoCreateSubtask (
IsWrite,
Lba,
OffsetInBlock,
FragmentSize,
WorkingBuffer,
Buffer,
!IsBlocking
);
if (Subtask == NULL) {
return FALSE;
}
InsertTailList (SubtasksPtr, &Subtask->Link);
Lba++;
Buffer = (UINT8 *)Buffer + FragmentSize;
Remaining -= FragmentSize;
}
//
// Process aligned remaining data
//
if (Remaining > 0) {
UINTN AlignedBlocks;
UINT64 CurrentLba;
CurrentLba = Lba;
//
// If the remaining data is perfectly block-aligned,
// do it as one large aligned transfer (no bounce buffer needed)
//
if (Remaining % BlockSize == 0) {
Subtask = DiskIoCreateSubtask (
IsWrite,
CurrentLba,
0,
Remaining,
NULL, // No bounce buffer for aligned transfer
Buffer,
!IsBlocking
);
if (Subtask == NULL) {
return FALSE;
}
InsertTailList (SubtasksPtr, &Subtask->Link);
Remaining = 0;
} else {
//
// Process aligned full blocks one at a time
//
while (Remaining >= BlockSize) {
Subtask = DiskIoCreateSubtask (
IsWrite,
CurrentLba,
0,
BlockSize,
NULL, // No bounce buffer needed
(UINT8 *)Buffer + ((UINTN)(CurrentLba - Lba) * BlockSize),
!IsBlocking
);
if (Subtask == NULL) {
return FALSE;
}
InsertTailList (SubtasksPtr, &Subtask->Link);
CurrentLba++;
Remaining -= BlockSize;
}
//
// Tail unaligned fragment
//
if (Remaining > 0) {
Subtask = DiskIoCreateSubtask (
IsWrite,
CurrentLba,
0,
Remaining,
MediaBuffer, // Bounce buffer for tail fragment
(UINT8 *)Buffer + ((UINTN)(CurrentLba - Lba) * BlockSize),
!IsBlocking
);
if (Subtask == NULL) {
return FALSE;
}
InsertTailList (SubtasksPtr, &Subtask->Link);
Remaining = 0;
}
}
}
return TRUE;
}
/**
Creates a single subtask structure (0xC08).
Allocates a DISK_IO_SUBTASK (0x68 bytes), zeros it, sets up the
fields from the parameters. If the subtask uses BlockIo2 (non-blocking),
creates a notification event at TPL_NOTIFY with DiskIoSubtaskCallback
as the handler.
@param IsWrite TRUE for write, FALSE for read.
@param Lba Logical block address of the transfer.
@param Offset Byte offset within the first block.
@param Length Number of bytes to transfer.
@param WorkingBuffer Bounce buffer for unaligned transfers (may be NULL).
@param Buffer User data buffer.
@param UseBlockIo2 TRUE to use BlockIo2, FALSE for BlockIo.
@return Pointer to the subtask, or NULL on allocation failure.
**/
DISK_IO_SUBTASK *
DiskIoCreateSubtask (
IN BOOLEAN IsWrite,
IN UINT64 Lba,
IN UINT32 Offset,
IN UINTN Length,
IN VOID *WorkingBuffer,
IN VOID *Buffer,
IN BOOLEAN UseBlockIo2
)
{
DISK_IO_SUBTASK *Subtask;
EFI_STATUS Status;
//
// Allocate the subtask (0x68 = 104 bytes)
//
Subtask = InternalAllocatePool (EfiBootServicesData, sizeof (DISK_IO_SUBTASK));
if (Subtask == NULL) {
return NULL;
}
//
// Zero the structure
//
ZeroMem (Subtask, sizeof (DISK_IO_SUBTASK));
Subtask->Signature = DISK_IO_SUBTASK_SIGNATURE;
Subtask->IsWrite = IsWrite ? TRUE : FALSE;
Subtask->Lba = Lba;
Subtask->OffsetInBlock = Offset;
Subtask->Length = Length;
Subtask->WorkingBuffer = WorkingBuffer;
Subtask->Buffer = Buffer;
Subtask->UseBlockIo2 = UseBlockIo2 ? TRUE : FALSE;
if (UseBlockIo2) {
//
// Non-blocking: create a completion event
//
Status = gBootServices->CreateEvent (
EVT_NOTIFY_SIGNAL,
TPL_NOTIFY,
DiskIoSubtaskCallback,
Subtask,
&Subtask->Event
);
if (EFI_ERROR (Status)) {
InternalFreePool (Subtask);
return NULL;
}
}
return Subtask;
}
/**
Subtask completion callback for non-blocking I/O (0xB00).
Called when a BlockIo2 non-blocking request completes.
For reads with a bounce buffer: copies data from bounce buffer
to user buffer. Signals the task for completion tracking.
Then closes the event.
@param Event The event that fired.
@param Context The DISK_IO_SUBTASK context.
**/
VOID
EFIAPI
DiskIoSubtaskCallback (
IN EFI_EVENT Event,
IN VOID *Context
)
{
DISK_IO_SUBTASK *Subtask;
DISK_IO_TASK *Task;
Subtask = (DISK_IO_SUBTASK *)Context;
//
// For reads with bounce buffer: copy data to user buffer
//
if (!Subtask->IsWrite && (Subtask->WorkingBuffer != NULL)) {
CopyMem (
(UINT8 *)Subtask->Buffer,
(UINT8 *)Subtask->WorkingBuffer + Subtask->OffsetInBlock,
Subtask->Length
);
}
//
// Close the event
//
gBootServices->CloseEvent (Event);
//
// Note: the subtask is freed by DiskIoCheckAllSubtasksCompleted
// or by the cleanup code in DiskIoRequest
//
}
/**
Frees resources associated with a completed subtask (0xA38).
Acquires/releases the subtask lock, removes the entry from the
linked list, closes the event (if non-blocking), and frees the
subtask's pool allocation.
@param Instance The private data instance.
@param Subtask Pointer to the completed task/subtask.
@return NULL (convention).
**/
VOID *
DiskIoFreeSubtask (
IN DISK_IO_PRIVATE_DATA *Instance,
IN DISK_IO_TASK *Subtask
)
{
if (Subtask == NULL) {
return NULL;
}
//
// Remove from linked list
//
RemoveEntryList (&Subtask->Link);
//
// Close event if non-blocking subtask
//
if (Subtask->Token != NULL) {
//
// This is actually the subtask's Event field at offset 0x58
//
gBootServices->CloseEvent (Subtask->Token);
}
//
// Free the allocation
//
InternalFreePool (Subtask);
return NULL;
}
/**
Checks whether all outstanding subtasks in the instance's task list
have completed (0x1358).
Acquires the task list lock, walks the list of tasks, and for each
task checks if its subtask list is empty. If any task has pending
subtasks, returns FALSE.
@param Instance The private data instance.
@return TRUE All tasks and subtasks are completed.
@return FALSE At least one task has pending subtasks.
**/
BOOLEAN
DiskIoCheckAllSubtasksCompleted (
IN DISK_IO_PRIVATE_DATA *Instance
)
{
LIST_ENTRY *TaskEntry;
DISK_IO_TASK *Task;
EfiAcquireLock (&Instance->TaskListLock);
TaskEntry = GetFirstNode (&Instance->TaskList);
while (!IsNodeInList (&Instance->TaskList, TaskEntry)) {
Task = CR (TaskEntry, DISK_IO_TASK, Link, DISK_IO_TASK_SIGNATURE);
if (IsListEmpty (&Task->SubtaskList)) {
//
// Task completed, signal its token
//
if (Task->Token != NULL) {
Task->Token->TransactionStatus = EFI_SUCCESS;
gBootServices->SignalEvent (Task->Token->Event);
}
//
// Remove and free the completed task
//
RemoveEntryList (TaskEntry);
InternalFreePool (Task);
EfiReleaseLock (&Instance->TaskListLock);
return TRUE;
}
TaskEntry = GetNextNode (&Instance->TaskList, TaskEntry);
}
EfiReleaseLock (&Instance->TaskListLock);
return TRUE;
}
//
// ============================================================================
// Component Name Protocol
// ============================================================================
/**
Component Name GetDriverName implementation (0x1CEC).
Walks the language table looking for a match against the requested
language. If found, returns the corresponding driver name string.
The language table is an array of { supported_languages, driver_name }
pairs stored in .rdata.
@param This Component Name protocol instance.
@param Language RFC 4646 language code (e.g., "en", "eng;en").
@param DriverName Output: pointer to driver name wide string.
@return EFI_SUCCESS Driver name returned.
@return EFI_INVALID_PARAMETER Language or DriverName is NULL.
@return EFI_UNSUPPORTED Language not found in table.
**/
EFI_STATUS
EFIAPI
DiskIoComponentNameGetDriverName (
IN EFI_COMPONENT_NAME_PROTOCOL *This,
IN CHAR8 *Language,
OUT CHAR16 **DriverName
)
{
//
// The component name table at .rdata +0x3868 / +0x3880 contains:
// { GetDriverName, GetControllerName, DriverNameString }
// For COMPONENT_NAME: DriverNameString = "Generic Disk I/O Driver" (0x2EA0)
// For COMPONENT_NAME2: DriverNameString = "eng;en" string table -> "Disk I/O Driver"
//
// The actual implementation iterates over the language table checking
// each entry against the requested Language parameter.
//
if ((Language == NULL) || (DriverName == NULL)) {
return EFI_INVALID_PARAMETER;
}
//
// Simplified: the full implementation walks a language table checking
// each entry with string comparison. For English, returns the known string.
//
*DriverName = L"Generic Disk I/O Driver";
return EFI_SUCCESS;
}
/**
Component Name GetControllerName implementation (0x1E48).
This driver does not provide controller-specific names.
@param This Component Name protocol instance.
@param ControllerHandle Handle of the controller.
@param ChildHandle Handle of the child (optional).
@param Language RFC 4646 language code.
@param ControllerName Output: pointer to controller name string.
@return EFI_UNSUPPORTED Controller-specific names not supported.
**/
EFI_STATUS
EFIAPI
DiskIoComponentNameGetControllerName (
IN EFI_COMPONENT_NAME_PROTOCOL *This,
IN EFI_HANDLE ControllerHandle,
IN EFI_HANDLE ChildHandle,
IN CHAR8 *Language,
OUT CHAR16 **ControllerName
)
{
return EFI_UNSUPPORTED;
}
//
// ============================================================================
// Library Constructors
// ============================================================================
/**
Processes the library constructor list (0x26D0).
Called from _ModuleEntryPoint before DiskIoDxeInstallProtocols.
Walks the HOB list to find the DebugPrintErrorLevelLib protocol
and resolves gDebugPrintErrorLevelProtocol.
Implementation:
1. If the protocol is already cached (qword_38E8), skip.
2. Otherwise, walks the HOB list looking for matching GUIDs.
3. If found, calls UpdateDpcAndProtocol which resolves internal
protocol pointers.
**/
VOID
ProcessLibraryConstructorList (
VOID
)
{
//
// Placeholder: the actual implementation walks the HOB list
// to find the DebugPrintErrorLevelLib protocol and cache it.
//
// At qword_38E0 and qword_38E8, the debug print protocol pointer
// is lazily resolved on first use via DebugPrintErrorLevelLibGetDebug
// (sub_1E54).
//
}