/** @file | |
DiskIo driver that lays on every BlockIo protocol in the system. | |
DiskIo converts a block oriented device to a byte oriented device. | |
Disk access may have to handle unaligned request about sector boundaries. | |
There are three cases: | |
UnderRun - The first byte is not on a sector boundary or the read request is | |
less than a sector in length. | |
Aligned - A read of N contiguous sectors. | |
OverRun - The last byte is not on a sector boundary. | |
Copyright (c) 2006 - 2018, Intel Corporation. All rights reserved.<BR> | |
SPDX-License-Identifier: BSD-2-Clause-Patent | |
**/ | |
#include "DiskIo.h" | |
// | |
// Driver binding protocol implementation for DiskIo driver. | |
// | |
EFI_DRIVER_BINDING_PROTOCOL gDiskIoDriverBinding = { | |
DiskIoDriverBindingSupported, | |
DiskIoDriverBindingStart, | |
DiskIoDriverBindingStop, | |
0xa, | |
NULL, | |
NULL | |
}; | |
// | |
// Template for DiskIo private data structure. | |
// The pointer to BlockIo protocol interface is assigned dynamically. | |
// | |
DISK_IO_PRIVATE_DATA gDiskIoPrivateDataTemplate = { | |
DISK_IO_PRIVATE_DATA_SIGNATURE, | |
{ | |
EFI_DISK_IO_PROTOCOL_REVISION, | |
DiskIoReadDisk, | |
DiskIoWriteDisk | |
}, | |
{ | |
EFI_DISK_IO2_PROTOCOL_REVISION, | |
DiskIo2Cancel, | |
DiskIo2ReadDiskEx, | |
DiskIo2WriteDiskEx, | |
DiskIo2FlushDiskEx | |
} | |
}; | |
/** | |
Test to see if this driver supports ControllerHandle. | |
@param This Protocol instance pointer. | |
@param ControllerHandle Handle of device to test | |
@param RemainingDevicePath Optional parameter use to pick a specific child | |
device to start. | |
@retval EFI_SUCCESS This driver supports this device | |
@retval EFI_ALREADY_STARTED This driver is already running on this device | |
@retval other This driver does not support this device | |
**/ | |
EFI_STATUS | |
EFIAPI | |
DiskIoDriverBindingSupported ( | |
IN EFI_DRIVER_BINDING_PROTOCOL *This, | |
IN EFI_HANDLE ControllerHandle, | |
IN EFI_DEVICE_PATH_PROTOCOL *RemainingDevicePath OPTIONAL | |
) | |
{ | |
EFI_STATUS Status; | |
EFI_BLOCK_IO_PROTOCOL *BlockIo; | |
// | |
// Open the IO Abstraction(s) needed to perform the supported test. | |
// | |
Status = gBS->OpenProtocol ( | |
ControllerHandle, | |
&gEfiBlockIoProtocolGuid, | |
(VOID **)&BlockIo, | |
This->DriverBindingHandle, | |
ControllerHandle, | |
EFI_OPEN_PROTOCOL_BY_DRIVER | |
); | |
if (EFI_ERROR (Status)) { | |
return Status; | |
} | |
// | |
// Close the I/O Abstraction(s) used to perform the supported test. | |
// | |
gBS->CloseProtocol ( | |
ControllerHandle, | |
&gEfiBlockIoProtocolGuid, | |
This->DriverBindingHandle, | |
ControllerHandle | |
); | |
return EFI_SUCCESS; | |
} | |
/** | |
Start this driver on ControllerHandle by opening a Block IO protocol and | |
installing a Disk IO protocol on ControllerHandle. | |
@param This Protocol instance pointer. | |
@param ControllerHandle Handle of device to bind driver to | |
@param RemainingDevicePath Optional parameter use to pick a specific child | |
device to start. | |
@retval EFI_SUCCESS This driver is added to ControllerHandle | |
@retval EFI_ALREADY_STARTED This driver is already running on ControllerHandle | |
@retval other This driver does not support this device | |
**/ | |
EFI_STATUS | |
EFIAPI | |
DiskIoDriverBindingStart ( | |
IN EFI_DRIVER_BINDING_PROTOCOL *This, | |
IN EFI_HANDLE ControllerHandle, | |
IN EFI_DEVICE_PATH_PROTOCOL *RemainingDevicePath OPTIONAL | |
) | |
{ | |
EFI_STATUS Status; | |
DISK_IO_PRIVATE_DATA *Instance; | |
EFI_TPL OldTpl; | |
Instance = NULL; | |
OldTpl = gBS->RaiseTPL (TPL_CALLBACK); | |
// | |
// Connect to the Block IO and Block IO2 interface on ControllerHandle. | |
// | |
Status = gBS->OpenProtocol ( | |
ControllerHandle, | |
&gEfiBlockIoProtocolGuid, | |
(VOID **)&gDiskIoPrivateDataTemplate.BlockIo, | |
This->DriverBindingHandle, | |
ControllerHandle, | |
EFI_OPEN_PROTOCOL_BY_DRIVER | |
); | |
if (EFI_ERROR (Status)) { | |
goto ErrorExit1; | |
} | |
Status = gBS->OpenProtocol ( | |
ControllerHandle, | |
&gEfiBlockIo2ProtocolGuid, | |
(VOID **)&gDiskIoPrivateDataTemplate.BlockIo2, | |
This->DriverBindingHandle, | |
ControllerHandle, | |
EFI_OPEN_PROTOCOL_BY_DRIVER | |
); | |
if (EFI_ERROR (Status)) { | |
gDiskIoPrivateDataTemplate.BlockIo2 = NULL; | |
} | |
// | |
// Initialize the Disk IO device instance. | |
// | |
Instance = AllocateCopyPool (sizeof (DISK_IO_PRIVATE_DATA), &gDiskIoPrivateDataTemplate); | |
if (Instance == NULL) { | |
Status = EFI_OUT_OF_RESOURCES; | |
goto ErrorExit; | |
} | |
// | |
// The BlockSize and IoAlign of BlockIo and BlockIo2 should equal. | |
// | |
ASSERT ( | |
(Instance->BlockIo2 == NULL) || | |
((Instance->BlockIo->Media->IoAlign == Instance->BlockIo2->Media->IoAlign) && | |
(Instance->BlockIo->Media->BlockSize == Instance->BlockIo2->Media->BlockSize) | |
) | |
); | |
InitializeListHead (&Instance->TaskQueue); | |
EfiInitializeLock (&Instance->TaskQueueLock, TPL_NOTIFY); | |
Instance->SharedWorkingBuffer = AllocateAlignedPages ( | |
EFI_SIZE_TO_PAGES (PcdGet32 (PcdDiskIoDataBufferBlockNum) * Instance->BlockIo->Media->BlockSize), | |
Instance->BlockIo->Media->IoAlign | |
); | |
if (Instance->SharedWorkingBuffer == NULL) { | |
Status = EFI_OUT_OF_RESOURCES; | |
goto ErrorExit; | |
} | |
// | |
// Install protocol interfaces for the Disk IO device. | |
// | |
if (Instance->BlockIo2 != NULL) { | |
Status = gBS->InstallMultipleProtocolInterfaces ( | |
&ControllerHandle, | |
&gEfiDiskIoProtocolGuid, | |
&Instance->DiskIo, | |
&gEfiDiskIo2ProtocolGuid, | |
&Instance->DiskIo2, | |
NULL | |
); | |
} else { | |
Status = gBS->InstallMultipleProtocolInterfaces ( | |
&ControllerHandle, | |
&gEfiDiskIoProtocolGuid, | |
&Instance->DiskIo, | |
NULL | |
); | |
} | |
ErrorExit: | |
if (EFI_ERROR (Status)) { | |
if ((Instance != NULL) && (Instance->SharedWorkingBuffer != NULL)) { | |
FreeAlignedPages ( | |
Instance->SharedWorkingBuffer, | |
EFI_SIZE_TO_PAGES (PcdGet32 (PcdDiskIoDataBufferBlockNum) * Instance->BlockIo->Media->BlockSize) | |
); | |
} | |
if (Instance != NULL) { | |
FreePool (Instance); | |
} | |
gBS->CloseProtocol ( | |
ControllerHandle, | |
&gEfiBlockIoProtocolGuid, | |
This->DriverBindingHandle, | |
ControllerHandle | |
); | |
} | |
ErrorExit1: | |
gBS->RestoreTPL (OldTpl); | |
return Status; | |
} | |
/** | |
Stop this driver on ControllerHandle by removing Disk IO protocol and closing | |
the Block IO protocol on ControllerHandle. | |
@param This Protocol instance pointer. | |
@param ControllerHandle Handle of device to stop driver on | |
@param NumberOfChildren Number of Handles in ChildHandleBuffer. If number of | |
children is zero stop the entire bus driver. | |
@param ChildHandleBuffer List of Child Handles to Stop. | |
@retval EFI_SUCCESS This driver is removed ControllerHandle | |
@retval other This driver was not removed from this device | |
**/ | |
EFI_STATUS | |
EFIAPI | |
DiskIoDriverBindingStop ( | |
IN EFI_DRIVER_BINDING_PROTOCOL *This, | |
IN EFI_HANDLE ControllerHandle, | |
IN UINTN NumberOfChildren, | |
IN EFI_HANDLE *ChildHandleBuffer | |
) | |
{ | |
EFI_STATUS Status; | |
EFI_DISK_IO_PROTOCOL *DiskIo; | |
EFI_DISK_IO2_PROTOCOL *DiskIo2; | |
DISK_IO_PRIVATE_DATA *Instance; | |
BOOLEAN AllTaskDone; | |
// | |
// Get our context back. | |
// | |
Status = gBS->OpenProtocol ( | |
ControllerHandle, | |
&gEfiDiskIoProtocolGuid, | |
(VOID **)&DiskIo, | |
This->DriverBindingHandle, | |
ControllerHandle, | |
EFI_OPEN_PROTOCOL_GET_PROTOCOL | |
); | |
if (EFI_ERROR (Status)) { | |
return Status; | |
} | |
Status = gBS->OpenProtocol ( | |
ControllerHandle, | |
&gEfiDiskIo2ProtocolGuid, | |
(VOID **)&DiskIo2, | |
This->DriverBindingHandle, | |
ControllerHandle, | |
EFI_OPEN_PROTOCOL_GET_PROTOCOL | |
); | |
if (EFI_ERROR (Status)) { | |
DiskIo2 = NULL; | |
} | |
Instance = DISK_IO_PRIVATE_DATA_FROM_DISK_IO (DiskIo); | |
if (DiskIo2 != NULL) { | |
// | |
// Call BlockIo2::Reset() to terminate any in-flight non-blocking I/O requests | |
// | |
ASSERT (Instance->BlockIo2 != NULL); | |
Status = Instance->BlockIo2->Reset (Instance->BlockIo2, FALSE); | |
if (EFI_ERROR (Status)) { | |
return Status; | |
} | |
Status = gBS->UninstallMultipleProtocolInterfaces ( | |
ControllerHandle, | |
&gEfiDiskIoProtocolGuid, | |
&Instance->DiskIo, | |
&gEfiDiskIo2ProtocolGuid, | |
&Instance->DiskIo2, | |
NULL | |
); | |
} else { | |
Status = gBS->UninstallMultipleProtocolInterfaces ( | |
ControllerHandle, | |
&gEfiDiskIoProtocolGuid, | |
&Instance->DiskIo, | |
NULL | |
); | |
} | |
if (!EFI_ERROR (Status)) { | |
do { | |
EfiAcquireLock (&Instance->TaskQueueLock); | |
AllTaskDone = IsListEmpty (&Instance->TaskQueue); | |
EfiReleaseLock (&Instance->TaskQueueLock); | |
} while (!AllTaskDone); | |
FreeAlignedPages ( | |
Instance->SharedWorkingBuffer, | |
EFI_SIZE_TO_PAGES (PcdGet32 (PcdDiskIoDataBufferBlockNum) * Instance->BlockIo->Media->BlockSize) | |
); | |
Status = gBS->CloseProtocol ( | |
ControllerHandle, | |
&gEfiBlockIoProtocolGuid, | |
This->DriverBindingHandle, | |
ControllerHandle | |
); | |
ASSERT_EFI_ERROR (Status); | |
if (DiskIo2 != NULL) { | |
Status = gBS->CloseProtocol ( | |
ControllerHandle, | |
&gEfiBlockIo2ProtocolGuid, | |
This->DriverBindingHandle, | |
ControllerHandle | |
); | |
ASSERT_EFI_ERROR (Status); | |
} | |
FreePool (Instance); | |
} | |
return Status; | |
} | |
/** | |
Destroy the sub task. | |
@param Instance Pointer to the DISK_IO_PRIVATE_DATA. | |
@param Subtask Subtask. | |
@return LIST_ENTRY * Pointer to the next link of subtask. | |
**/ | |
LIST_ENTRY * | |
DiskIoDestroySubtask ( | |
IN DISK_IO_PRIVATE_DATA *Instance, | |
IN DISK_IO_SUBTASK *Subtask | |
) | |
{ | |
LIST_ENTRY *Link; | |
if (Subtask->Task != NULL) { | |
EfiAcquireLock (&Subtask->Task->SubtasksLock); | |
} | |
Link = RemoveEntryList (&Subtask->Link); | |
if (Subtask->Task != NULL) { | |
EfiReleaseLock (&Subtask->Task->SubtasksLock); | |
} | |
if (!Subtask->Blocking) { | |
if (Subtask->WorkingBuffer != NULL) { | |
FreeAlignedPages ( | |
Subtask->WorkingBuffer, | |
Subtask->Length < Instance->BlockIo->Media->BlockSize | |
? EFI_SIZE_TO_PAGES (Instance->BlockIo->Media->BlockSize) | |
: EFI_SIZE_TO_PAGES (Subtask->Length) | |
); | |
} | |
if (Subtask->BlockIo2Token.Event != NULL) { | |
gBS->CloseEvent (Subtask->BlockIo2Token.Event); | |
} | |
} | |
FreePool (Subtask); | |
return Link; | |
} | |
/** | |
The callback for the BlockIo2 ReadBlocksEx/WriteBlocksEx. | |
@param Event Event whose notification function is being invoked. | |
@param Context The pointer to the notification function's context, | |
which points to the DISK_IO_SUBTASK instance. | |
**/ | |
VOID | |
EFIAPI | |
DiskIo2OnReadWriteComplete ( | |
IN EFI_EVENT Event, | |
IN VOID *Context | |
) | |
{ | |
DISK_IO_SUBTASK *Subtask; | |
DISK_IO2_TASK *Task; | |
EFI_STATUS TransactionStatus; | |
DISK_IO_PRIVATE_DATA *Instance; | |
Subtask = (DISK_IO_SUBTASK *)Context; | |
TransactionStatus = Subtask->BlockIo2Token.TransactionStatus; | |
Task = Subtask->Task; | |
Instance = Task->Instance; | |
ASSERT (Subtask->Signature == DISK_IO_SUBTASK_SIGNATURE); | |
ASSERT (Instance->Signature == DISK_IO_PRIVATE_DATA_SIGNATURE); | |
ASSERT (Task->Signature == DISK_IO2_TASK_SIGNATURE); | |
if ((Subtask->WorkingBuffer != NULL) && !EFI_ERROR (TransactionStatus) && | |
(Task->Token != NULL) && !Subtask->Write | |
) | |
{ | |
CopyMem (Subtask->Buffer, Subtask->WorkingBuffer + Subtask->Offset, Subtask->Length); | |
} | |
DiskIoDestroySubtask (Instance, Subtask); | |
if (EFI_ERROR (TransactionStatus) || IsListEmpty (&Task->Subtasks)) { | |
if (Task->Token != NULL) { | |
// | |
// Signal error status once the subtask is failed. | |
// Or signal the last status once the last subtask is finished. | |
// | |
Task->Token->TransactionStatus = TransactionStatus; | |
gBS->SignalEvent (Task->Token->Event); | |
// | |
// Mark token to NULL indicating the Task is a dead task. | |
// | |
Task->Token = NULL; | |
} | |
} | |
} | |
/** | |
Create the subtask. | |
@param Write TRUE: Write request; FALSE: Read request. | |
@param Lba The starting logical block address to read from on the device. | |
@param Offset The starting byte offset to read from the LBA. | |
@param Length The number of bytes to read from the device. | |
@param WorkingBuffer The aligned buffer to hold the data for reading or writing. | |
@param Buffer The buffer to hold the data for reading or writing. | |
@param Blocking TRUE: Blocking request; FALSE: Non-blocking request. | |
@return A pointer to the created subtask. | |
**/ | |
DISK_IO_SUBTASK * | |
DiskIoCreateSubtask ( | |
IN BOOLEAN Write, | |
IN UINT64 Lba, | |
IN UINT32 Offset, | |
IN UINTN Length, | |
IN VOID *WorkingBuffer OPTIONAL, | |
IN VOID *Buffer, | |
IN BOOLEAN Blocking | |
) | |
{ | |
DISK_IO_SUBTASK *Subtask; | |
EFI_STATUS Status; | |
Subtask = AllocateZeroPool (sizeof (DISK_IO_SUBTASK)); | |
if (Subtask == NULL) { | |
return NULL; | |
} | |
Subtask->Signature = DISK_IO_SUBTASK_SIGNATURE; | |
Subtask->Write = Write; | |
Subtask->Lba = Lba; | |
Subtask->Offset = Offset; | |
Subtask->Length = Length; | |
Subtask->WorkingBuffer = WorkingBuffer; | |
Subtask->Buffer = Buffer; | |
Subtask->Blocking = Blocking; | |
if (!Blocking) { | |
Status = gBS->CreateEvent ( | |
EVT_NOTIFY_SIGNAL, | |
TPL_NOTIFY, | |
DiskIo2OnReadWriteComplete, | |
Subtask, | |
&Subtask->BlockIo2Token.Event | |
); | |
if (EFI_ERROR (Status)) { | |
FreePool (Subtask); | |
return NULL; | |
} | |
} | |
DEBUG (( | |
DEBUG_BLKIO, | |
" %c:Lba/Offset/Length/WorkingBuffer/Buffer = %016lx/%08x/%08x/%08x/%08x\n", | |
Write ? 'W' : 'R', | |
Lba, | |
Offset, | |
Length, | |
WorkingBuffer, | |
Buffer | |
)); | |
return Subtask; | |
} | |
/** | |
Create the subtask list. | |
@param Instance Pointer to the DISK_IO_PRIVATE_DATA. | |
@param Write TRUE: Write request; FALSE: Read request. | |
@param Offset The starting byte offset to read from the device. | |
@param BufferSize The size in bytes of Buffer. The number of bytes to read from the device. | |
@param Buffer A pointer to the buffer for the data. | |
@param Blocking TRUE: Blocking request; FALSE: Non-blocking request. | |
@param SharedWorkingBuffer The aligned buffer to hold the data for reading or writing. | |
@param Subtasks The subtask list header. | |
@retval TRUE The subtask list is created successfully. | |
@retval FALSE The subtask list is not created. | |
**/ | |
BOOLEAN | |
DiskIoCreateSubtaskList ( | |
IN DISK_IO_PRIVATE_DATA *Instance, | |
IN BOOLEAN Write, | |
IN UINT64 Offset, | |
IN UINTN BufferSize, | |
IN VOID *Buffer, | |
IN BOOLEAN Blocking, | |
IN VOID *SharedWorkingBuffer, | |
IN OUT LIST_ENTRY *Subtasks | |
) | |
{ | |
UINT32 BlockSize; | |
UINT32 IoAlign; | |
UINT64 Lba; | |
UINT64 OverRunLba; | |
UINT32 UnderRun; | |
UINT32 OverRun; | |
UINT8 *BufferPtr; | |
UINTN Length; | |
UINTN DataBufferSize; | |
DISK_IO_SUBTASK *Subtask; | |
VOID *WorkingBuffer; | |
LIST_ENTRY *Link; | |
DEBUG ((DEBUG_BLKIO, "DiskIo: Create subtasks for task: Offset/BufferSize/Buffer = %016lx/%08x/%08x\n", Offset, BufferSize, Buffer)); | |
BlockSize = Instance->BlockIo->Media->BlockSize; | |
IoAlign = Instance->BlockIo->Media->IoAlign; | |
if (IoAlign == 0) { | |
IoAlign = 1; | |
} | |
Lba = DivU64x32Remainder (Offset, BlockSize, &UnderRun); | |
BufferPtr = (UINT8 *)Buffer; | |
// | |
// Special handling for zero BufferSize | |
// | |
if (BufferSize == 0) { | |
Subtask = DiskIoCreateSubtask (Write, Lba, UnderRun, 0, NULL, BufferPtr, Blocking); | |
if (Subtask == NULL) { | |
goto Done; | |
} | |
InsertTailList (Subtasks, &Subtask->Link); | |
return TRUE; | |
} | |
if (UnderRun != 0) { | |
Length = MIN (BlockSize - UnderRun, BufferSize); | |
if (Blocking) { | |
WorkingBuffer = SharedWorkingBuffer; | |
} else { | |
WorkingBuffer = AllocateAlignedPages (EFI_SIZE_TO_PAGES (BlockSize), IoAlign); | |
if (WorkingBuffer == NULL) { | |
goto Done; | |
} | |
} | |
if (Write) { | |
// | |
// A half write operation can be splitted to a blocking block-read and half write operation | |
// This can simplify the sub task processing logic | |
// | |
Subtask = DiskIoCreateSubtask (FALSE, Lba, 0, BlockSize, NULL, WorkingBuffer, TRUE); | |
if (Subtask == NULL) { | |
goto Done; | |
} | |
InsertTailList (Subtasks, &Subtask->Link); | |
} | |
Subtask = DiskIoCreateSubtask (Write, Lba, UnderRun, Length, WorkingBuffer, BufferPtr, Blocking); | |
if (Subtask == NULL) { | |
goto Done; | |
} | |
InsertTailList (Subtasks, &Subtask->Link); | |
BufferPtr += Length; | |
Offset += Length; | |
BufferSize -= Length; | |
Lba++; | |
} | |
OverRunLba = Lba + DivU64x32Remainder (BufferSize, BlockSize, &OverRun); | |
BufferSize -= OverRun; | |
if (OverRun != 0) { | |
if (Blocking) { | |
WorkingBuffer = SharedWorkingBuffer; | |
} else { | |
WorkingBuffer = AllocateAlignedPages (EFI_SIZE_TO_PAGES (BlockSize), IoAlign); | |
if (WorkingBuffer == NULL) { | |
goto Done; | |
} | |
} | |
if (Write) { | |
// | |
// A half write operation can be splitted to a blocking block-read and half write operation | |
// This can simplify the sub task processing logic | |
// | |
Subtask = DiskIoCreateSubtask (FALSE, OverRunLba, 0, BlockSize, NULL, WorkingBuffer, TRUE); | |
if (Subtask == NULL) { | |
goto Done; | |
} | |
InsertTailList (Subtasks, &Subtask->Link); | |
} | |
Subtask = DiskIoCreateSubtask (Write, OverRunLba, 0, OverRun, WorkingBuffer, BufferPtr + BufferSize, Blocking); | |
if (Subtask == NULL) { | |
goto Done; | |
} | |
InsertTailList (Subtasks, &Subtask->Link); | |
} | |
if (OverRunLba > Lba) { | |
// | |
// If the DiskIo maps directly to a BlockIo device do the read. | |
// | |
if (ALIGN_POINTER (BufferPtr, IoAlign) == BufferPtr) { | |
Subtask = DiskIoCreateSubtask (Write, Lba, 0, BufferSize, NULL, BufferPtr, Blocking); | |
if (Subtask == NULL) { | |
goto Done; | |
} | |
InsertTailList (Subtasks, &Subtask->Link); | |
BufferPtr += BufferSize; | |
Offset += BufferSize; | |
BufferSize -= BufferSize; | |
} else { | |
if (Blocking) { | |
// | |
// Use the allocated buffer instead of the original buffer | |
// to avoid alignment issue. | |
// | |
for ( ; Lba < OverRunLba; Lba += PcdGet32 (PcdDiskIoDataBufferBlockNum)) { | |
DataBufferSize = MIN (BufferSize, PcdGet32 (PcdDiskIoDataBufferBlockNum) * BlockSize); | |
Subtask = DiskIoCreateSubtask (Write, Lba, 0, DataBufferSize, SharedWorkingBuffer, BufferPtr, Blocking); | |
if (Subtask == NULL) { | |
goto Done; | |
} | |
InsertTailList (Subtasks, &Subtask->Link); | |
BufferPtr += DataBufferSize; | |
Offset += DataBufferSize; | |
BufferSize -= DataBufferSize; | |
} | |
} else { | |
WorkingBuffer = AllocateAlignedPages (EFI_SIZE_TO_PAGES (BufferSize), IoAlign); | |
if (WorkingBuffer == NULL) { | |
// | |
// If there is not enough memory, downgrade to blocking access | |
// | |
DEBUG ((DEBUG_VERBOSE, "DiskIo: No enough memory so downgrade to blocking access\n")); | |
if (!DiskIoCreateSubtaskList (Instance, Write, Offset, BufferSize, BufferPtr, TRUE, SharedWorkingBuffer, Subtasks)) { | |
goto Done; | |
} | |
} else { | |
Subtask = DiskIoCreateSubtask (Write, Lba, 0, BufferSize, WorkingBuffer, BufferPtr, Blocking); | |
if (Subtask == NULL) { | |
goto Done; | |
} | |
InsertTailList (Subtasks, &Subtask->Link); | |
} | |
BufferPtr += BufferSize; | |
Offset += BufferSize; | |
BufferSize -= BufferSize; | |
} | |
} | |
} | |
ASSERT (BufferSize == 0); | |
return TRUE; | |
Done: | |
// | |
// Remove all the subtasks. | |
// | |
for (Link = GetFirstNode (Subtasks); !IsNull (Subtasks, Link); ) { | |
Subtask = CR (Link, DISK_IO_SUBTASK, Link, DISK_IO_SUBTASK_SIGNATURE); | |
Link = DiskIoDestroySubtask (Instance, Subtask); | |
} | |
return FALSE; | |
} | |
/** | |
Terminate outstanding asynchronous requests to a device. | |
@param This Indicates a pointer to the calling context. | |
@retval EFI_SUCCESS All outstanding requests were successfully terminated. | |
@retval EFI_DEVICE_ERROR The device reported an error while performing the cancel | |
operation. | |
**/ | |
EFI_STATUS | |
EFIAPI | |
DiskIo2Cancel ( | |
IN EFI_DISK_IO2_PROTOCOL *This | |
) | |
{ | |
DISK_IO_PRIVATE_DATA *Instance; | |
DISK_IO2_TASK *Task; | |
LIST_ENTRY *Link; | |
Instance = DISK_IO_PRIVATE_DATA_FROM_DISK_IO2 (This); | |
EfiAcquireLock (&Instance->TaskQueueLock); | |
for (Link = GetFirstNode (&Instance->TaskQueue) | |
; !IsNull (&Instance->TaskQueue, Link) | |
; Link = GetNextNode (&Instance->TaskQueue, Link) | |
) | |
{ | |
Task = CR (Link, DISK_IO2_TASK, Link, DISK_IO2_TASK_SIGNATURE); | |
if (Task->Token != NULL) { | |
Task->Token->TransactionStatus = EFI_ABORTED; | |
gBS->SignalEvent (Task->Token->Event); | |
// | |
// Set Token to NULL so that the further BlockIo2 responses will be ignored | |
// | |
Task->Token = NULL; | |
} | |
} | |
EfiReleaseLock (&Instance->TaskQueueLock); | |
return EFI_SUCCESS; | |
} | |
/** | |
Remove the completed tasks from Instance->TaskQueue. Completed tasks are those who don't have any subtasks. | |
@param Instance Pointer to the DISK_IO_PRIVATE_DATA. | |
@retval TRUE The Instance->TaskQueue is empty after the completed tasks are removed. | |
@retval FALSE The Instance->TaskQueue is not empty after the completed tasks are removed. | |
**/ | |
BOOLEAN | |
DiskIo2RemoveCompletedTask ( | |
IN DISK_IO_PRIVATE_DATA *Instance | |
) | |
{ | |
BOOLEAN QueueEmpty; | |
LIST_ENTRY *Link; | |
DISK_IO2_TASK *Task; | |
QueueEmpty = TRUE; | |
EfiAcquireLock (&Instance->TaskQueueLock); | |
for (Link = GetFirstNode (&Instance->TaskQueue); !IsNull (&Instance->TaskQueue, Link); ) { | |
Task = CR (Link, DISK_IO2_TASK, Link, DISK_IO2_TASK_SIGNATURE); | |
if (IsListEmpty (&Task->Subtasks)) { | |
Link = RemoveEntryList (&Task->Link); | |
ASSERT (Task->Token == NULL); | |
FreePool (Task); | |
} else { | |
Link = GetNextNode (&Instance->TaskQueue, Link); | |
QueueEmpty = FALSE; | |
} | |
} | |
EfiReleaseLock (&Instance->TaskQueueLock); | |
return QueueEmpty; | |
} | |
/** | |
Common routine to access the disk. | |
@param Instance Pointer to the DISK_IO_PRIVATE_DATA. | |
@param Write TRUE: Write operation; FALSE: Read operation. | |
@param MediaId ID of the medium to access. | |
@param Offset The starting byte offset on the logical block I/O device to access. | |
@param Token A pointer to the token associated with the transaction. | |
If this field is NULL, synchronous/blocking IO is performed. | |
@param BufferSize The size in bytes of Buffer. The number of bytes to read from the device. | |
@param Buffer A pointer to the destination buffer for the data. | |
The caller is responsible either having implicit or explicit ownership of the buffer. | |
**/ | |
EFI_STATUS | |
DiskIo2ReadWriteDisk ( | |
IN DISK_IO_PRIVATE_DATA *Instance, | |
IN BOOLEAN Write, | |
IN UINT32 MediaId, | |
IN UINT64 Offset, | |
IN EFI_DISK_IO2_TOKEN *Token, | |
IN UINTN BufferSize, | |
IN UINT8 *Buffer | |
) | |
{ | |
EFI_STATUS Status; | |
EFI_BLOCK_IO_PROTOCOL *BlockIo; | |
EFI_BLOCK_IO2_PROTOCOL *BlockIo2; | |
EFI_BLOCK_IO_MEDIA *Media; | |
LIST_ENTRY *Link; | |
LIST_ENTRY *NextLink; | |
LIST_ENTRY Subtasks; | |
DISK_IO_SUBTASK *Subtask; | |
DISK_IO2_TASK *Task; | |
EFI_TPL OldTpl; | |
BOOLEAN Blocking; | |
BOOLEAN SubtaskBlocking; | |
LIST_ENTRY *SubtasksPtr; | |
Task = NULL; | |
BlockIo = Instance->BlockIo; | |
BlockIo2 = Instance->BlockIo2; | |
Media = BlockIo->Media; | |
Status = EFI_SUCCESS; | |
Blocking = (BOOLEAN)((Token == NULL) || (Token->Event == NULL)); | |
if (Blocking) { | |
// | |
// Wait till pending async task is completed. | |
// | |
while (!DiskIo2RemoveCompletedTask (Instance)) { | |
} | |
SubtasksPtr = &Subtasks; | |
} else { | |
DiskIo2RemoveCompletedTask (Instance); | |
Task = AllocatePool (sizeof (DISK_IO2_TASK)); | |
if (Task == NULL) { | |
return EFI_OUT_OF_RESOURCES; | |
} | |
EfiAcquireLock (&Instance->TaskQueueLock); | |
InsertTailList (&Instance->TaskQueue, &Task->Link); | |
EfiReleaseLock (&Instance->TaskQueueLock); | |
Task->Signature = DISK_IO2_TASK_SIGNATURE; | |
Task->Instance = Instance; | |
Task->Token = Token; | |
EfiInitializeLock (&Task->SubtasksLock, TPL_NOTIFY); | |
SubtasksPtr = &Task->Subtasks; | |
} | |
InitializeListHead (SubtasksPtr); | |
if (!DiskIoCreateSubtaskList (Instance, Write, Offset, BufferSize, Buffer, Blocking, Instance->SharedWorkingBuffer, SubtasksPtr)) { | |
if (Task != NULL) { | |
FreePool (Task); | |
} | |
return EFI_OUT_OF_RESOURCES; | |
} | |
ASSERT (!IsListEmpty (SubtasksPtr)); | |
OldTpl = gBS->RaiseTPL (TPL_CALLBACK); | |
for ( Link = GetFirstNode (SubtasksPtr), NextLink = GetNextNode (SubtasksPtr, Link) | |
; !IsNull (SubtasksPtr, Link) | |
; Link = NextLink, NextLink = GetNextNode (SubtasksPtr, NextLink) | |
) | |
{ | |
Subtask = CR (Link, DISK_IO_SUBTASK, Link, DISK_IO_SUBTASK_SIGNATURE); | |
Subtask->Task = Task; | |
SubtaskBlocking = Subtask->Blocking; | |
ASSERT ((Subtask->Length % Media->BlockSize == 0) || (Subtask->Length < Media->BlockSize)); | |
if (Subtask->Write) { | |
// | |
// Write | |
// | |
if (Subtask->WorkingBuffer != NULL) { | |
// | |
// A sub task before this one should be a block read operation, causing the WorkingBuffer filled with the entire one block data. | |
// | |
CopyMem (Subtask->WorkingBuffer + Subtask->Offset, Subtask->Buffer, Subtask->Length); | |
} | |
if (SubtaskBlocking) { | |
Status = BlockIo->WriteBlocks ( | |
BlockIo, | |
MediaId, | |
Subtask->Lba, | |
(Subtask->Length % Media->BlockSize == 0) ? Subtask->Length : Media->BlockSize, | |
(Subtask->WorkingBuffer != NULL) ? Subtask->WorkingBuffer : Subtask->Buffer | |
); | |
} else { | |
Status = BlockIo2->WriteBlocksEx ( | |
BlockIo2, | |
MediaId, | |
Subtask->Lba, | |
&Subtask->BlockIo2Token, | |
(Subtask->Length % Media->BlockSize == 0) ? Subtask->Length : Media->BlockSize, | |
(Subtask->WorkingBuffer != NULL) ? Subtask->WorkingBuffer : Subtask->Buffer | |
); | |
} | |
} else { | |
// | |
// Read | |
// | |
if (SubtaskBlocking) { | |
Status = BlockIo->ReadBlocks ( | |
BlockIo, | |
MediaId, | |
Subtask->Lba, | |
(Subtask->Length % Media->BlockSize == 0) ? Subtask->Length : Media->BlockSize, | |
(Subtask->WorkingBuffer != NULL) ? Subtask->WorkingBuffer : Subtask->Buffer | |
); | |
if (!EFI_ERROR (Status) && (Subtask->WorkingBuffer != NULL)) { | |
CopyMem (Subtask->Buffer, Subtask->WorkingBuffer + Subtask->Offset, Subtask->Length); | |
} | |
} else { | |
Status = BlockIo2->ReadBlocksEx ( | |
BlockIo2, | |
MediaId, | |
Subtask->Lba, | |
&Subtask->BlockIo2Token, | |
(Subtask->Length % Media->BlockSize == 0) ? Subtask->Length : Media->BlockSize, | |
(Subtask->WorkingBuffer != NULL) ? Subtask->WorkingBuffer : Subtask->Buffer | |
); | |
} | |
} | |
if (SubtaskBlocking || EFI_ERROR (Status)) { | |
// | |
// Make sure the subtask list only contains non-blocking subtasks. | |
// Remove failed non-blocking subtasks as well because the callback won't be called. | |
// | |
DiskIoDestroySubtask (Instance, Subtask); | |
} | |
if (EFI_ERROR (Status)) { | |
break; | |
} | |
} | |
gBS->RaiseTPL (TPL_NOTIFY); | |
// | |
// Remove all the remaining subtasks when failure. | |
// We shouldn't remove all the tasks because the non-blocking requests have been submitted and cannot be canceled. | |
// | |
if (EFI_ERROR (Status)) { | |
while (!IsNull (SubtasksPtr, NextLink)) { | |
Subtask = CR (NextLink, DISK_IO_SUBTASK, Link, DISK_IO_SUBTASK_SIGNATURE); | |
NextLink = DiskIoDestroySubtask (Instance, Subtask); | |
} | |
} | |
// | |
// It's possible that the non-blocking subtasks finish before raising TPL to NOTIFY, | |
// so the subtasks list might be empty at this point. | |
// | |
if (!Blocking && IsListEmpty (SubtasksPtr)) { | |
EfiAcquireLock (&Instance->TaskQueueLock); | |
RemoveEntryList (&Task->Link); | |
EfiReleaseLock (&Instance->TaskQueueLock); | |
if (!EFI_ERROR (Status) && (Task->Token != NULL)) { | |
// | |
// Task->Token should be set to NULL by the DiskIo2OnReadWriteComplete | |
// It it's not, that means the non-blocking request was downgraded to blocking request. | |
// | |
DEBUG ((DEBUG_VERBOSE, "DiskIo: Non-blocking request was downgraded to blocking request, signal event directly.\n")); | |
Task->Token->TransactionStatus = Status; | |
gBS->SignalEvent (Task->Token->Event); | |
} | |
FreePool (Task); | |
} | |
gBS->RestoreTPL (OldTpl); | |
return Status; | |
} | |
/** | |
Reads a specified number of bytes from a device. | |
@param This Indicates a pointer to the calling context. | |
@param MediaId ID of the medium to be read. | |
@param Offset The starting byte offset on the logical block I/O device to read from. | |
@param Token A pointer to the token associated with the transaction. | |
If this field is NULL, synchronous/blocking IO is performed. | |
@param BufferSize The size in bytes of Buffer. The number of bytes to read from the device. | |
@param Buffer A pointer to the destination buffer for the data. | |
The caller is responsible either having implicit or explicit ownership of the buffer. | |
@retval EFI_SUCCESS If Event is NULL (blocking I/O): The data was read correctly from the device. | |
If Event is not NULL (asynchronous I/O): The request was successfully queued for processing. | |
Event will be signaled upon completion. | |
@retval EFI_DEVICE_ERROR The device reported an error while performing the write. | |
@retval EFI_NO_MEDIA There is no medium in the device. | |
@retval EFI_MEDIA_CHNAGED The MediaId is not for the current medium. | |
@retval EFI_INVALID_PARAMETER The read request contains device addresses that are not valid for the device. | |
@retval EFI_OUT_OF_RESOURCES The request could not be completed due to a lack of resources. | |
**/ | |
EFI_STATUS | |
EFIAPI | |
DiskIo2ReadDiskEx ( | |
IN EFI_DISK_IO2_PROTOCOL *This, | |
IN UINT32 MediaId, | |
IN UINT64 Offset, | |
IN OUT EFI_DISK_IO2_TOKEN *Token, | |
IN UINTN BufferSize, | |
OUT VOID *Buffer | |
) | |
{ | |
return DiskIo2ReadWriteDisk ( | |
DISK_IO_PRIVATE_DATA_FROM_DISK_IO2 (This), | |
FALSE, | |
MediaId, | |
Offset, | |
Token, | |
BufferSize, | |
(UINT8 *)Buffer | |
); | |
} | |
/** | |
Writes a specified number of bytes to a device. | |
@param This Indicates a pointer to the calling context. | |
@param MediaId ID of the medium to be written. | |
@param Offset The starting byte offset on the logical block I/O device to write to. | |
@param Token A pointer to the token associated with the transaction. | |
If this field is NULL, synchronous/blocking IO is performed. | |
@param BufferSize The size in bytes of Buffer. The number of bytes to write to the device. | |
@param Buffer A pointer to the buffer containing the data to be written. | |
@retval EFI_SUCCESS If Event is NULL (blocking I/O): The data was written correctly to the device. | |
If Event is not NULL (asynchronous I/O): The request was successfully queued for processing. | |
Event will be signaled upon completion. | |
@retval EFI_WRITE_PROTECTED The device cannot be written to. | |
@retval EFI_DEVICE_ERROR The device reported an error while performing the write operation. | |
@retval EFI_NO_MEDIA There is no medium in the device. | |
@retval EFI_MEDIA_CHNAGED The MediaId is not for the current medium. | |
@retval EFI_INVALID_PARAMETER The write request contains device addresses that are not valid for the device. | |
@retval EFI_OUT_OF_RESOURCES The request could not be completed due to a lack of resources. | |
**/ | |
EFI_STATUS | |
EFIAPI | |
DiskIo2WriteDiskEx ( | |
IN EFI_DISK_IO2_PROTOCOL *This, | |
IN UINT32 MediaId, | |
IN UINT64 Offset, | |
IN OUT EFI_DISK_IO2_TOKEN *Token, | |
IN UINTN BufferSize, | |
IN VOID *Buffer | |
) | |
{ | |
return DiskIo2ReadWriteDisk ( | |
DISK_IO_PRIVATE_DATA_FROM_DISK_IO2 (This), | |
TRUE, | |
MediaId, | |
Offset, | |
Token, | |
BufferSize, | |
(UINT8 *)Buffer | |
); | |
} | |
/** | |
The callback for the BlockIo2 FlushBlocksEx. | |
@param Event Event whose notification function is being invoked. | |
@param Context The pointer to the notification function's context, | |
which points to the DISK_IO2_FLUSH_TASK instance. | |
**/ | |
VOID | |
EFIAPI | |
DiskIo2OnFlushComplete ( | |
IN EFI_EVENT Event, | |
IN VOID *Context | |
) | |
{ | |
DISK_IO2_FLUSH_TASK *Task; | |
gBS->CloseEvent (Event); | |
Task = (DISK_IO2_FLUSH_TASK *)Context; | |
ASSERT (Task->Signature == DISK_IO2_FLUSH_TASK_SIGNATURE); | |
Task->Token->TransactionStatus = Task->BlockIo2Token.TransactionStatus; | |
gBS->SignalEvent (Task->Token->Event); | |
FreePool (Task); | |
} | |
/** | |
Flushes all modified data to the physical device. | |
@param This Indicates a pointer to the calling context. | |
@param Token A pointer to the token associated with the transaction. | |
If this field is NULL, synchronous/blocking IO is performed. | |
@retval EFI_SUCCESS If Event is NULL (blocking I/O): The data was flushed successfully to the device. | |
If Event is not NULL (asynchronous I/O): The request was successfully queued for processing. | |
Event will be signaled upon completion. | |
@retval EFI_WRITE_PROTECTED The device cannot be written to. | |
@retval EFI_DEVICE_ERROR The device reported an error while performing the write operation. | |
@retval EFI_NO_MEDIA There is no medium in the device. | |
@retval EFI_OUT_OF_RESOURCES The request could not be completed due to a lack of resources. | |
**/ | |
EFI_STATUS | |
EFIAPI | |
DiskIo2FlushDiskEx ( | |
IN EFI_DISK_IO2_PROTOCOL *This, | |
IN OUT EFI_DISK_IO2_TOKEN *Token | |
) | |
{ | |
EFI_STATUS Status; | |
DISK_IO2_FLUSH_TASK *Task; | |
DISK_IO_PRIVATE_DATA *Private; | |
Private = DISK_IO_PRIVATE_DATA_FROM_DISK_IO2 (This); | |
if ((Token != NULL) && (Token->Event != NULL)) { | |
Task = AllocatePool (sizeof (DISK_IO2_FLUSH_TASK)); | |
if (Task == NULL) { | |
return EFI_OUT_OF_RESOURCES; | |
} | |
Status = gBS->CreateEvent ( | |
EVT_NOTIFY_SIGNAL, | |
TPL_CALLBACK, | |
DiskIo2OnFlushComplete, | |
Task, | |
&Task->BlockIo2Token.Event | |
); | |
if (EFI_ERROR (Status)) { | |
FreePool (Task); | |
return Status; | |
} | |
Task->Signature = DISK_IO2_FLUSH_TASK_SIGNATURE; | |
Task->Token = Token; | |
Status = Private->BlockIo2->FlushBlocksEx (Private->BlockIo2, &Task->BlockIo2Token); | |
if (EFI_ERROR (Status)) { | |
gBS->CloseEvent (Task->BlockIo2Token.Event); | |
FreePool (Task); | |
} | |
} else { | |
Status = Private->BlockIo2->FlushBlocksEx (Private->BlockIo2, NULL); | |
} | |
return Status; | |
} | |
/** | |
Read BufferSize bytes from Offset into Buffer. | |
Reads may support reads that are not aligned on | |
sector boundaries. There are three cases: | |
UnderRun - The first byte is not on a sector boundary or the read request is | |
less than a sector in length. | |
Aligned - A read of N contiguous sectors. | |
OverRun - The last byte is not on a sector boundary. | |
@param This Protocol instance pointer. | |
@param MediaId Id of the media, changes every time the media is replaced. | |
@param Offset The starting byte offset to read from | |
@param BufferSize Size of Buffer | |
@param Buffer Buffer containing read data | |
@retval EFI_SUCCESS The data was read correctly from the device. | |
@retval EFI_DEVICE_ERROR The device reported an error while performing the read. | |
@retval EFI_NO_MEDIA There is no media in the device. | |
@retval EFI_MEDIA_CHNAGED The MediaId does not matched the current device. | |
@retval EFI_INVALID_PARAMETER The read request contains device addresses that are not | |
valid for the device. | |
**/ | |
EFI_STATUS | |
EFIAPI | |
DiskIoReadDisk ( | |
IN EFI_DISK_IO_PROTOCOL *This, | |
IN UINT32 MediaId, | |
IN UINT64 Offset, | |
IN UINTN BufferSize, | |
OUT VOID *Buffer | |
) | |
{ | |
return DiskIo2ReadWriteDisk ( | |
DISK_IO_PRIVATE_DATA_FROM_DISK_IO (This), | |
FALSE, | |
MediaId, | |
Offset, | |
NULL, | |
BufferSize, | |
(UINT8 *)Buffer | |
); | |
} | |
/** | |
Writes BufferSize bytes from Buffer into Offset. | |
Writes may require a read modify write to support writes that are not | |
aligned on sector boundaries. There are three cases: | |
UnderRun - The first byte is not on a sector boundary or the write request | |
is less than a sector in length. Read modify write is required. | |
Aligned - A write of N contiguous sectors. | |
OverRun - The last byte is not on a sector boundary. Read modified write | |
required. | |
@param This Protocol instance pointer. | |
@param MediaId Id of the media, changes every time the media is replaced. | |
@param Offset The starting byte offset to read from | |
@param BufferSize Size of Buffer | |
@param Buffer Buffer containing read data | |
@retval EFI_SUCCESS The data was written correctly to the device. | |
@retval EFI_WRITE_PROTECTED The device can not be written to. | |
@retval EFI_DEVICE_ERROR The device reported an error while performing the write. | |
@retval EFI_NO_MEDIA There is no media in the device. | |
@retval EFI_MEDIA_CHNAGED The MediaId does not matched the current device. | |
@retval EFI_INVALID_PARAMETER The write request contains device addresses that are not | |
valid for the device. | |
**/ | |
EFI_STATUS | |
EFIAPI | |
DiskIoWriteDisk ( | |
IN EFI_DISK_IO_PROTOCOL *This, | |
IN UINT32 MediaId, | |
IN UINT64 Offset, | |
IN UINTN BufferSize, | |
IN VOID *Buffer | |
) | |
{ | |
return DiskIo2ReadWriteDisk ( | |
DISK_IO_PRIVATE_DATA_FROM_DISK_IO (This), | |
TRUE, | |
MediaId, | |
Offset, | |
NULL, | |
BufferSize, | |
(UINT8 *)Buffer | |
); | |
} | |
/** | |
The user Entry Point for module DiskIo. The user code starts with this function. | |
@param[in] ImageHandle The firmware allocated handle for the EFI image. | |
@param[in] SystemTable A pointer to the EFI System Table. | |
@retval EFI_SUCCESS The entry point is executed successfully. | |
@retval other Some error occurs when executing this entry point. | |
**/ | |
EFI_STATUS | |
EFIAPI | |
InitializeDiskIo ( | |
IN EFI_HANDLE ImageHandle, | |
IN EFI_SYSTEM_TABLE *SystemTable | |
) | |
{ | |
EFI_STATUS Status; | |
// | |
// Install driver model protocol(s). | |
// | |
Status = EfiLibInstallDriverBindingComponentName2 ( | |
ImageHandle, | |
SystemTable, | |
&gDiskIoDriverBinding, | |
ImageHandle, | |
&gDiskIoComponentName, | |
&gDiskIoComponentName2 | |
); | |
ASSERT_EFI_ERROR (Status); | |
return Status; | |
} |