/** @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.
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 (( EFI_D_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 ((EFI_D_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 ((EFI_D_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 ((EFI_D_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; }