X-Git-Url: https://git.proxmox.com/?a=blobdiff_plain;f=MdeModulePkg%2FBus%2FScsi%2FScsiDiskDxe%2FScsiDisk.c;h=fbdf927a1114fb5ef268cb3c8dd47850103da44f;hb=9d510e61fceee7b92955ef9a3c20343752d8ce3f;hp=e7abe544df58aa732962269400c33ab650629dd0;hpb=2bf87d82e95ed812504783468da26ea425b2a58b;p=mirror_edk2.git diff --git a/MdeModulePkg/Bus/Scsi/ScsiDiskDxe/ScsiDisk.c b/MdeModulePkg/Bus/Scsi/ScsiDiskDxe/ScsiDisk.c index e7abe544df..fbdf927a11 100644 --- a/MdeModulePkg/Bus/Scsi/ScsiDiskDxe/ScsiDisk.c +++ b/MdeModulePkg/Bus/Scsi/ScsiDiskDxe/ScsiDisk.c @@ -1,14 +1,8 @@ /** @file SCSI disk driver that layers on every SCSI IO protocol in the system. -Copyright (c) 2006 - 2015, Intel Corporation. All rights reserved.
-This program and the accompanying materials -are licensed and made available under the terms and conditions of the BSD License -which accompanies this distribution. The full text of the license may be found at -http://opensource.org/licenses/bsd-license.php - -THE PROGRAM IS DISTRIBUTED UNDER THE BSD LICENSE ON AN "AS IS" BASIS, -WITHOUT WARRANTIES OR REPRESENTATIONS OF ANY KIND, EITHER EXPRESS OR IMPLIED. +Copyright (c) 2006 - 2018, Intel Corporation. All rights reserved.
+SPDX-License-Identifier: BSD-2-Clause-Patent **/ @@ -79,9 +73,9 @@ FreeAlignedBuffer ( The user code starts with this function. - @param ImageHandle The firmware allocated handle for the EFI image. + @param ImageHandle The firmware allocated handle for the EFI image. @param 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. @@ -230,16 +224,27 @@ ScsiDiskDriverBindingStart ( return Status; } - ScsiDiskDevice->Signature = SCSI_DISK_DEV_SIGNATURE; - ScsiDiskDevice->ScsiIo = ScsiIo; - ScsiDiskDevice->BlkIo.Revision = EFI_BLOCK_IO_PROTOCOL_REVISION3; - ScsiDiskDevice->BlkIo.Media = &ScsiDiskDevice->BlkIoMedia; - ScsiDiskDevice->BlkIo.Media->IoAlign = ScsiIo->IoAlign; - ScsiDiskDevice->BlkIo.Reset = ScsiDiskReset; - ScsiDiskDevice->BlkIo.ReadBlocks = ScsiDiskReadBlocks; - ScsiDiskDevice->BlkIo.WriteBlocks = ScsiDiskWriteBlocks; - ScsiDiskDevice->BlkIo.FlushBlocks = ScsiDiskFlushBlocks; - ScsiDiskDevice->Handle = Controller; + ScsiDiskDevice->Signature = SCSI_DISK_DEV_SIGNATURE; + ScsiDiskDevice->ScsiIo = ScsiIo; + ScsiDiskDevice->BlkIo.Revision = EFI_BLOCK_IO_PROTOCOL_REVISION3; + ScsiDiskDevice->BlkIo.Media = &ScsiDiskDevice->BlkIoMedia; + ScsiDiskDevice->BlkIo.Media->IoAlign = ScsiIo->IoAlign; + ScsiDiskDevice->BlkIo.Reset = ScsiDiskReset; + ScsiDiskDevice->BlkIo.ReadBlocks = ScsiDiskReadBlocks; + ScsiDiskDevice->BlkIo.WriteBlocks = ScsiDiskWriteBlocks; + ScsiDiskDevice->BlkIo.FlushBlocks = ScsiDiskFlushBlocks; + ScsiDiskDevice->BlkIo2.Media = &ScsiDiskDevice->BlkIoMedia; + ScsiDiskDevice->BlkIo2.Reset = ScsiDiskResetEx; + ScsiDiskDevice->BlkIo2.ReadBlocksEx = ScsiDiskReadBlocksEx; + ScsiDiskDevice->BlkIo2.WriteBlocksEx = ScsiDiskWriteBlocksEx; + ScsiDiskDevice->BlkIo2.FlushBlocksEx = ScsiDiskFlushBlocksEx; + ScsiDiskDevice->EraseBlock.Revision = EFI_ERASE_BLOCK_PROTOCOL_REVISION; + ScsiDiskDevice->EraseBlock.EraseLengthGranularity = 1; + ScsiDiskDevice->EraseBlock.EraseBlocks = ScsiDiskEraseBlocks; + ScsiDiskDevice->UnmapInfo.MaxBlkDespCnt = 1; + ScsiDiskDevice->BlockLimitsVpdSupported = FALSE; + ScsiDiskDevice->Handle = Controller; + InitializeListHead (&ScsiDiskDevice->AsyncTaskQueue); ScsiIo->GetDeviceType (ScsiIo, &(ScsiDiskDevice->DeviceType)); switch (ScsiDiskDevice->DeviceType) { @@ -250,6 +255,7 @@ ScsiDiskDriverBindingStart ( case EFI_SCSI_TYPE_CDROM: ScsiDiskDevice->BlkIo.Media->BlockSize = 0x800; + ScsiDiskDevice->BlkIo.Media->ReadOnly = TRUE; MustReadCapacity = FALSE; break; } @@ -300,7 +306,8 @@ ScsiDiskDriverBindingStart ( Status = ScsiDiskDetectMedia (ScsiDiskDevice, MustReadCapacity, &Temp); if (!EFI_ERROR (Status)) { // - // Determine if Block IO should be produced on this controller handle + // Determine if Block IO & Block IO2 should be produced on this controller + // handle // if (DetermineInstallBlockIo(Controller)) { InitializeInstallDiskInfo(ScsiDiskDevice, Controller); @@ -308,11 +315,24 @@ ScsiDiskDriverBindingStart ( &Controller, &gEfiBlockIoProtocolGuid, &ScsiDiskDevice->BlkIo, + &gEfiBlockIo2ProtocolGuid, + &ScsiDiskDevice->BlkIo2, &gEfiDiskInfoProtocolGuid, &ScsiDiskDevice->DiskInfo, NULL ); if (!EFI_ERROR(Status)) { + if (DetermineInstallEraseBlock(ScsiDiskDevice, Controller)) { + Status = gBS->InstallProtocolInterface ( + &Controller, + &gEfiEraseBlockProtocolGuid, + EFI_NATIVE_INTERFACE, + &ScsiDiskDevice->EraseBlock + ); + if (EFI_ERROR(Status)) { + DEBUG ((EFI_D_ERROR, "ScsiDisk: Failed to install the Erase Block Protocol! Status = %r\n", Status)); + } + } ScsiDiskDevice->ControllerNameTable = NULL; AddUnicodeString2 ( "eng", @@ -330,7 +350,7 @@ ScsiDiskDriverBindingStart ( ); return EFI_SUCCESS; } - } + } } gBS->FreePool (ScsiDiskDevice->SenseData); @@ -342,7 +362,7 @@ ScsiDiskDriverBindingStart ( Controller ); return Status; - + } @@ -354,7 +374,7 @@ ScsiDiskDriverBindingStart ( restrictions for this service. DisconnectController() must follow these calling restrictions. If any other agent wishes to call Stop() it must also follow these calling restrictions. - + @param This Protocol instance pointer. @param ControllerHandle Handle of device to stop driver on @param NumberOfChildren Number of Handles in ChildHandleBuffer. If number of @@ -374,9 +394,10 @@ ScsiDiskDriverBindingStop ( IN EFI_HANDLE *ChildHandleBuffer OPTIONAL ) { - EFI_BLOCK_IO_PROTOCOL *BlkIo; - SCSI_DISK_DEV *ScsiDiskDevice; - EFI_STATUS Status; + EFI_BLOCK_IO_PROTOCOL *BlkIo; + EFI_ERASE_BLOCK_PROTOCOL *EraseBlock; + SCSI_DISK_DEV *ScsiDiskDevice; + EFI_STATUS Status; Status = gBS->OpenProtocol ( Controller, @@ -390,11 +411,42 @@ ScsiDiskDriverBindingStop ( return Status; } - ScsiDiskDevice = SCSI_DISK_DEV_FROM_THIS (BlkIo); + ScsiDiskDevice = SCSI_DISK_DEV_FROM_BLKIO (BlkIo); + + // + // Wait for the BlockIo2 requests queue to become empty + // + while (!IsListEmpty (&ScsiDiskDevice->AsyncTaskQueue)); + + // + // If Erase Block Protocol is installed, then uninstall this protocol. + // + Status = gBS->OpenProtocol ( + Controller, + &gEfiEraseBlockProtocolGuid, + (VOID **) &EraseBlock, + This->DriverBindingHandle, + Controller, + EFI_OPEN_PROTOCOL_GET_PROTOCOL + ); + + if (!EFI_ERROR (Status)) { + Status = gBS->UninstallProtocolInterface ( + Controller, + &gEfiEraseBlockProtocolGuid, + &ScsiDiskDevice->EraseBlock + ); + if (EFI_ERROR (Status)) { + return Status; + } + } + Status = gBS->UninstallMultipleProtocolInterfaces ( Controller, &gEfiBlockIoProtocolGuid, &ScsiDiskDevice->BlkIo, + &gEfiBlockIo2ProtocolGuid, + &ScsiDiskDevice->BlkIo2, &gEfiDiskInfoProtocolGuid, &ScsiDiskDevice->DiskInfo, NULL @@ -443,13 +495,17 @@ ScsiDiskReset ( OldTpl = gBS->RaiseTPL (TPL_CALLBACK); - ScsiDiskDevice = SCSI_DISK_DEV_FROM_THIS (This); + ScsiDiskDevice = SCSI_DISK_DEV_FROM_BLKIO (This); Status = ScsiDiskDevice->ScsiIo->ResetDevice (ScsiDiskDevice->ScsiIo); if (EFI_ERROR (Status)) { - Status = EFI_DEVICE_ERROR; - goto Done; + if (Status == EFI_UNSUPPORTED) { + Status = EFI_SUCCESS; + } else { + Status = EFI_DEVICE_ERROR; + goto Done; + } } if (!ExtendedVerification) { @@ -505,7 +561,8 @@ ScsiDiskReadBlocks ( MediaChange = FALSE; OldTpl = gBS->RaiseTPL (TPL_CALLBACK); - ScsiDiskDevice = SCSI_DISK_DEV_FROM_THIS (This); + ScsiDiskDevice = SCSI_DISK_DEV_FROM_BLKIO (This); + Media = ScsiDiskDevice->BlkIo.Media; if (!IS_DEVICE_FIXED(ScsiDiskDevice)) { @@ -522,14 +579,31 @@ ScsiDiskReadBlocks ( &ScsiDiskDevice->BlkIo, &ScsiDiskDevice->BlkIo ); - Status = EFI_MEDIA_CHANGED; + gBS->ReinstallProtocolInterface ( + ScsiDiskDevice->Handle, + &gEfiBlockIo2ProtocolGuid, + &ScsiDiskDevice->BlkIo2, + &ScsiDiskDevice->BlkIo2 + ); + if (DetermineInstallEraseBlock(ScsiDiskDevice, ScsiDiskDevice->Handle)) { + gBS->ReinstallProtocolInterface ( + ScsiDiskDevice->Handle, + &gEfiEraseBlockProtocolGuid, + &ScsiDiskDevice->EraseBlock, + &ScsiDiskDevice->EraseBlock + ); + } + if (Media->MediaPresent) { + Status = EFI_MEDIA_CHANGED; + } else { + Status = EFI_NO_MEDIA; + } goto Done; } } // // Get the intrinsic block size // - Media = ScsiDiskDevice->BlkIo.Media; BlockSize = Media->BlockSize; NumberOfBlocks = BufferSize / BlockSize; @@ -623,7 +697,8 @@ ScsiDiskWriteBlocks ( MediaChange = FALSE; OldTpl = gBS->RaiseTPL (TPL_CALLBACK); - ScsiDiskDevice = SCSI_DISK_DEV_FROM_THIS (This); + ScsiDiskDevice = SCSI_DISK_DEV_FROM_BLKIO (This); + Media = ScsiDiskDevice->BlkIo.Media; if (!IS_DEVICE_FIXED(ScsiDiskDevice)) { @@ -640,14 +715,31 @@ ScsiDiskWriteBlocks ( &ScsiDiskDevice->BlkIo, &ScsiDiskDevice->BlkIo ); - Status = EFI_MEDIA_CHANGED; + gBS->ReinstallProtocolInterface ( + ScsiDiskDevice->Handle, + &gEfiBlockIo2ProtocolGuid, + &ScsiDiskDevice->BlkIo2, + &ScsiDiskDevice->BlkIo2 + ); + if (DetermineInstallEraseBlock(ScsiDiskDevice, ScsiDiskDevice->Handle)) { + gBS->ReinstallProtocolInterface ( + ScsiDiskDevice->Handle, + &gEfiEraseBlockProtocolGuid, + &ScsiDiskDevice->EraseBlock, + &ScsiDiskDevice->EraseBlock + ); + } + if (Media->MediaPresent) { + Status = EFI_MEDIA_CHANGED; + } else { + Status = EFI_NO_MEDIA; + } goto Done; } } // // Get the intrinsic block size // - Media = ScsiDiskDevice->BlkIo.Media; BlockSize = Media->BlockSize; NumberOfBlocks = BufferSize / BlockSize; @@ -662,6 +754,11 @@ ScsiDiskWriteBlocks ( goto Done; } + if (Media->ReadOnly) { + Status = EFI_WRITE_PROTECTED; + goto Done; + } + if (BufferSize == 0) { Status = EFI_SUCCESS; goto Done; @@ -726,1082 +823,2615 @@ ScsiDiskFlushBlocks ( /** - Detect Device and read out capacity ,if error occurs, parse the sense key. + Reset SCSI Disk. - @param ScsiDiskDevice The pointer of SCSI_DISK_DEV - @param MustReadCapacity The flag about reading device capacity - @param MediaChange The pointer of flag indicates if media has changed + @param This The pointer of EFI_BLOCK_IO2_PROTOCOL. + @param ExtendedVerification The flag about if extend verificate. - @retval EFI_DEVICE_ERROR Indicates that error occurs - @retval EFI_SUCCESS Successfully to detect media + @retval EFI_SUCCESS The device was reset. + @retval EFI_DEVICE_ERROR The device is not functioning properly and could + not be reset. + @return EFI_STATUS is returned from EFI_SCSI_IO_PROTOCOL.ResetDevice(). **/ EFI_STATUS -ScsiDiskDetectMedia ( - IN SCSI_DISK_DEV *ScsiDiskDevice, - IN BOOLEAN MustReadCapacity, - OUT BOOLEAN *MediaChange +EFIAPI +ScsiDiskResetEx ( + IN EFI_BLOCK_IO2_PROTOCOL *This, + IN BOOLEAN ExtendedVerification ) { - EFI_STATUS Status; - EFI_SCSI_SENSE_DATA *SenseData; - UINTN NumberOfSenseKeys; - BOOLEAN NeedRetry; - BOOLEAN NeedReadCapacity; - UINT8 Retry; - UINT8 MaxRetry; - EFI_BLOCK_IO_MEDIA OldMedia; - UINTN Action; - EFI_EVENT TimeoutEvt; + EFI_TPL OldTpl; + SCSI_DISK_DEV *ScsiDiskDevice; + EFI_STATUS Status; - Status = EFI_SUCCESS; - SenseData = NULL; - NumberOfSenseKeys = 0; - Retry = 0; - MaxRetry = 3; - Action = ACTION_NO_ACTION; - NeedReadCapacity = FALSE; - *MediaChange = FALSE; - TimeoutEvt = NULL; + OldTpl = gBS->RaiseTPL (TPL_CALLBACK); - CopyMem (&OldMedia, ScsiDiskDevice->BlkIo.Media, sizeof (OldMedia)); + ScsiDiskDevice = SCSI_DISK_DEV_FROM_BLKIO2 (This); - Status = gBS->CreateEvent ( - EVT_TIMER, - TPL_CALLBACK, - NULL, - NULL, - &TimeoutEvt - ); - if (EFI_ERROR (Status)) { - return Status; - } + Status = ScsiDiskDevice->ScsiIo->ResetDevice (ScsiDiskDevice->ScsiIo); - Status = gBS->SetTimer (TimeoutEvt, TimerRelative, EFI_TIMER_PERIOD_SECONDS(120)); if (EFI_ERROR (Status)) { - goto EXIT; - } - - // - // Sending Test_Unit cmd to poll device status. - // If the sense data shows the drive is not ready or reset before, we need poll the device status again. - // We limit the upper boundary to 120 seconds. - // - while (EFI_ERROR (gBS->CheckEvent (TimeoutEvt))) { - Status = ScsiDiskTestUnitReady ( - ScsiDiskDevice, - &NeedRetry, - &SenseData, - &NumberOfSenseKeys - ); - if (!EFI_ERROR (Status)) { - Status = DetectMediaParsingSenseKeys ( - ScsiDiskDevice, - SenseData, - NumberOfSenseKeys, - &Action - ); - if (EFI_ERROR (Status)) { - goto EXIT; - } else if (Action == ACTION_RETRY_COMMAND_LATER) { - continue; - } else { - break; - } + if (Status == EFI_UNSUPPORTED) { + Status = EFI_SUCCESS; } else { - Retry++; - if (!NeedRetry || (Retry >= MaxRetry)) { - goto EXIT; - } - } - } - - if (EFI_ERROR (Status)) { - goto EXIT; - } - - // - // ACTION_NO_ACTION: need not read capacity - // other action code: need read capacity - // - if (Action == ACTION_READ_CAPACITY) { - NeedReadCapacity = TRUE; - } - - // - // either NeedReadCapacity is TRUE, or MustReadCapacity is TRUE, - // retrieve capacity via Read Capacity command - // - if (NeedReadCapacity || MustReadCapacity) { - // - // retrieve media information - // - for (Retry = 0; Retry < MaxRetry; Retry++) { - Status = ScsiDiskReadCapacity ( - ScsiDiskDevice, - &NeedRetry, - &SenseData, - &NumberOfSenseKeys - ); - if (!EFI_ERROR (Status)) { - // - // analyze sense key to action - // - Status = DetectMediaParsingSenseKeys ( - ScsiDiskDevice, - SenseData, - NumberOfSenseKeys, - &Action - ); - if (EFI_ERROR (Status)) { - // - // if Status is error, it may indicate crisis error, - // so return without retry. - // - goto EXIT; - } else if (Action == ACTION_RETRY_COMMAND_LATER) { - Retry = 0; - continue; - } else { - break; - } - } else { - Retry++; - if (!NeedRetry || (Retry >= MaxRetry)) { - goto EXIT; - } - } - } - - if (EFI_ERROR (Status)) { - goto EXIT; + Status = EFI_DEVICE_ERROR; + goto Done; } } - if (ScsiDiskDevice->BlkIo.Media->MediaId != OldMedia.MediaId) { - // - // Media change information got from the device - // - *MediaChange = TRUE; - } - - if (ScsiDiskDevice->BlkIo.Media->ReadOnly != OldMedia.ReadOnly) { - *MediaChange = TRUE; - ScsiDiskDevice->BlkIo.Media->MediaId += 1; - } - - if (ScsiDiskDevice->BlkIo.Media->BlockSize != OldMedia.BlockSize) { - *MediaChange = TRUE; - ScsiDiskDevice->BlkIo.Media->MediaId += 1; - } - - if (ScsiDiskDevice->BlkIo.Media->LastBlock != OldMedia.LastBlock) { - *MediaChange = TRUE; - ScsiDiskDevice->BlkIo.Media->MediaId += 1; + if (!ExtendedVerification) { + goto Done; } - if (ScsiDiskDevice->BlkIo.Media->MediaPresent != OldMedia.MediaPresent) { - if (ScsiDiskDevice->BlkIo.Media->MediaPresent) { - // - // when change from no media to media present, reset the MediaId to 1. - // - ScsiDiskDevice->BlkIo.Media->MediaId = 1; - } else { - // - // when no media, reset the MediaId to zero. - // - ScsiDiskDevice->BlkIo.Media->MediaId = 0; - } + Status = ScsiDiskDevice->ScsiIo->ResetBus (ScsiDiskDevice->ScsiIo); - *MediaChange = TRUE; + if (EFI_ERROR (Status)) { + Status = EFI_DEVICE_ERROR; + goto Done; } -EXIT: - if (TimeoutEvt != NULL) { - gBS->CloseEvent (TimeoutEvt); - } +Done: + gBS->RestoreTPL (OldTpl); return Status; } - /** - Send out Inquiry command to Device. - - @param ScsiDiskDevice The pointer of SCSI_DISK_DEV - @param NeedRetry Indicates if needs try again when error happens + The function is to Read Block from SCSI Disk. - @retval EFI_DEVICE_ERROR Indicates that error occurs - @retval EFI_SUCCESS Successfully to detect media + @param This The pointer of EFI_BLOCK_IO_PROTOCOL. + @param MediaId The Id of Media detected. + @param Lba The logic block address. + @param Token A pointer to the token associated with the transaction. + @param BufferSize The size of Buffer. + @param Buffer The buffer to fill the read out data. + + @retval EFI_SUCCESS The read request was queued if Token-> Event is + not NULL. The data was read correctly from the + device if theToken-> Event is NULL. + @retval EFI_DEVICE_ERROR The device reported an error while attempting + to perform the read operation. + @retval EFI_NO_MEDIA There is no media in the device. + @retval EFI_MEDIA_CHANGED The MediaId is not for the current media. + @retval EFI_BAD_BUFFER_SIZE The BufferSize parameter is not a multiple of + the intrinsic block size of the device. + @retval EFI_INVALID_PARAMETER The read request contains LBAs that are not + valid, or the buffer is not on proper + alignment. + @retval EFI_OUT_OF_RESOURCES The request could not be completed due to a + lack of resources. **/ EFI_STATUS -ScsiDiskInquiryDevice ( - IN OUT SCSI_DISK_DEV *ScsiDiskDevice, - OUT BOOLEAN *NeedRetry +EFIAPI +ScsiDiskReadBlocksEx ( + IN EFI_BLOCK_IO2_PROTOCOL *This, + IN UINT32 MediaId, + IN EFI_LBA Lba, + IN OUT EFI_BLOCK_IO2_TOKEN *Token, + IN UINTN BufferSize, + OUT VOID *Buffer ) { - UINT32 InquiryDataLength; - UINT8 SenseDataLength; - UINT8 HostAdapterStatus; - UINT8 TargetStatus; - EFI_SCSI_SENSE_DATA *SenseDataArray; - UINTN NumberOfSenseKeys; - EFI_STATUS Status; - UINT8 MaxRetry; - UINT8 Index; - EFI_SCSI_SUPPORTED_VPD_PAGES_VPD_PAGE *SupportedVpdPages; - EFI_SCSI_BLOCK_LIMITS_VPD_PAGE *BlockLimits; - UINTN PageLength; + SCSI_DISK_DEV *ScsiDiskDevice; + EFI_BLOCK_IO_MEDIA *Media; + EFI_STATUS Status; + UINTN BlockSize; + UINTN NumberOfBlocks; + BOOLEAN MediaChange; + EFI_TPL OldTpl; - InquiryDataLength = sizeof (EFI_SCSI_INQUIRY_DATA); - SenseDataLength = 0; + MediaChange = FALSE; + OldTpl = gBS->RaiseTPL (TPL_CALLBACK); + ScsiDiskDevice = SCSI_DISK_DEV_FROM_BLKIO2 (This); + Media = ScsiDiskDevice->BlkIo.Media; - Status = ScsiInquiryCommand ( - ScsiDiskDevice->ScsiIo, - SCSI_DISK_TIMEOUT, - NULL, - &SenseDataLength, - &HostAdapterStatus, - &TargetStatus, - (VOID *) &(ScsiDiskDevice->InquiryData), - &InquiryDataLength, - FALSE - ); - // - // no need to check HostAdapterStatus and TargetStatus - // - if ((Status == EFI_SUCCESS) || (Status == EFI_WARN_BUFFER_TOO_SMALL)) { - ParseInquiryData (ScsiDiskDevice); + if (!IS_DEVICE_FIXED(ScsiDiskDevice)) { - if (ScsiDiskDevice->DeviceType == EFI_SCSI_TYPE_DISK) { - // - // Check whether the device supports Block Limits VPD page (0xB0) - // - SupportedVpdPages = AllocateAlignedBuffer (ScsiDiskDevice, sizeof (EFI_SCSI_SUPPORTED_VPD_PAGES_VPD_PAGE)); - if (SupportedVpdPages == NULL) { - *NeedRetry = FALSE; - return EFI_DEVICE_ERROR; + Status = ScsiDiskDetectMedia (ScsiDiskDevice, FALSE, &MediaChange); + if (EFI_ERROR (Status)) { + Status = EFI_DEVICE_ERROR; + goto Done; + } + + if (MediaChange) { + gBS->ReinstallProtocolInterface ( + ScsiDiskDevice->Handle, + &gEfiBlockIoProtocolGuid, + &ScsiDiskDevice->BlkIo, + &ScsiDiskDevice->BlkIo + ); + gBS->ReinstallProtocolInterface ( + ScsiDiskDevice->Handle, + &gEfiBlockIo2ProtocolGuid, + &ScsiDiskDevice->BlkIo2, + &ScsiDiskDevice->BlkIo2 + ); + if (DetermineInstallEraseBlock(ScsiDiskDevice, ScsiDiskDevice->Handle)) { + gBS->ReinstallProtocolInterface ( + ScsiDiskDevice->Handle, + &gEfiEraseBlockProtocolGuid, + &ScsiDiskDevice->EraseBlock, + &ScsiDiskDevice->EraseBlock + ); } - ZeroMem (SupportedVpdPages, sizeof (EFI_SCSI_SUPPORTED_VPD_PAGES_VPD_PAGE)); - InquiryDataLength = sizeof (EFI_SCSI_SUPPORTED_VPD_PAGES_VPD_PAGE); - SenseDataLength = 0; - Status = ScsiInquiryCommandEx ( - ScsiDiskDevice->ScsiIo, - SCSI_DISK_TIMEOUT, - NULL, - &SenseDataLength, - &HostAdapterStatus, - &TargetStatus, - (VOID *) SupportedVpdPages, - &InquiryDataLength, - TRUE, - EFI_SCSI_PAGE_CODE_SUPPORTED_VPD - ); - if (!EFI_ERROR (Status)) { - PageLength = (SupportedVpdPages->PageLength2 << 8) - | SupportedVpdPages->PageLength1; - for (Index = 0; Index < PageLength; Index++) { - if (SupportedVpdPages->SupportedVpdPageList[Index] == EFI_SCSI_PAGE_CODE_BLOCK_LIMITS_VPD) { - break; - } - } + if (Media->MediaPresent) { + Status = EFI_MEDIA_CHANGED; + } else { + Status = EFI_NO_MEDIA; + } + goto Done; + } + } + // + // Get the intrinsic block size + // + BlockSize = Media->BlockSize; - // - // Query the Block Limits VPD page - // - if (Index < PageLength) { - BlockLimits = AllocateAlignedBuffer (ScsiDiskDevice, sizeof (EFI_SCSI_BLOCK_LIMITS_VPD_PAGE)); - if (BlockLimits == NULL) { - FreeAlignedBuffer (SupportedVpdPages, sizeof (EFI_SCSI_SUPPORTED_VPD_PAGES_VPD_PAGE)); - *NeedRetry = FALSE; - return EFI_DEVICE_ERROR; - } - ZeroMem (BlockLimits, sizeof (EFI_SCSI_BLOCK_LIMITS_VPD_PAGE)); - InquiryDataLength = sizeof (EFI_SCSI_BLOCK_LIMITS_VPD_PAGE); - SenseDataLength = 0; - Status = ScsiInquiryCommandEx ( - ScsiDiskDevice->ScsiIo, - SCSI_DISK_TIMEOUT, - NULL, - &SenseDataLength, - &HostAdapterStatus, - &TargetStatus, - (VOID *) BlockLimits, - &InquiryDataLength, - TRUE, - EFI_SCSI_PAGE_CODE_BLOCK_LIMITS_VPD - ); - if (!EFI_ERROR (Status)) { - ScsiDiskDevice->BlkIo.Media->OptimalTransferLengthGranularity = - (BlockLimits->OptimalTransferLengthGranularity2 << 8) | - BlockLimits->OptimalTransferLengthGranularity1; - } + NumberOfBlocks = BufferSize / BlockSize; - FreeAlignedBuffer (BlockLimits, sizeof (EFI_SCSI_BLOCK_LIMITS_VPD_PAGE)); - } - } + if (!(Media->MediaPresent)) { + Status = EFI_NO_MEDIA; + goto Done; + } - FreeAlignedBuffer (SupportedVpdPages, sizeof (EFI_SCSI_SUPPORTED_VPD_PAGES_VPD_PAGE)); - } + if (MediaId != Media->MediaId) { + Status = EFI_MEDIA_CHANGED; + goto Done; } - if (!EFI_ERROR (Status)) { - return EFI_SUCCESS; + if (Buffer == NULL) { + Status = EFI_INVALID_PARAMETER; + goto Done; + } - } else if (Status == EFI_NOT_READY) { - *NeedRetry = TRUE; - return EFI_DEVICE_ERROR; - - } else if ((Status == EFI_INVALID_PARAMETER) || (Status == EFI_UNSUPPORTED)) { - *NeedRetry = FALSE; - return EFI_DEVICE_ERROR; + if (BufferSize == 0) { + if ((Token != NULL) && (Token->Event != NULL)) { + Token->TransactionStatus = EFI_SUCCESS; + gBS->SignalEvent (Token->Event); + } + + Status = EFI_SUCCESS; + goto Done; } - // - // go ahead to check HostAdapterStatus and TargetStatus - // (EFI_TIMEOUT, EFI_DEVICE_ERROR) - // - Status = CheckHostAdapterStatus (HostAdapterStatus); - if ((Status == EFI_TIMEOUT) || (Status == EFI_NOT_READY)) { - *NeedRetry = TRUE; - return EFI_DEVICE_ERROR; - } else if (Status == EFI_DEVICE_ERROR) { - // - // reset the scsi channel - // - ScsiDiskDevice->ScsiIo->ResetBus (ScsiDiskDevice->ScsiIo); - *NeedRetry = FALSE; - return EFI_DEVICE_ERROR; + if (BufferSize % BlockSize != 0) { + Status = EFI_BAD_BUFFER_SIZE; + goto Done; } - Status = CheckTargetStatus (TargetStatus); - if (Status == EFI_NOT_READY) { - // - // reset the scsi device - // - ScsiDiskDevice->ScsiIo->ResetDevice (ScsiDiskDevice->ScsiIo); - *NeedRetry = TRUE; - return EFI_DEVICE_ERROR; + if (Lba > Media->LastBlock) { + Status = EFI_INVALID_PARAMETER; + goto Done; + } - } else if (Status == EFI_DEVICE_ERROR) { - *NeedRetry = FALSE; - return EFI_DEVICE_ERROR; + if ((Lba + NumberOfBlocks - 1) > Media->LastBlock) { + Status = EFI_INVALID_PARAMETER; + goto Done; } - - // - // if goes here, meant ScsiInquiryCommand() failed. - // if ScsiDiskRequestSenseKeys() succeeds at last, - // better retry ScsiInquiryCommand(). (by setting *NeedRetry = TRUE) - // - MaxRetry = 3; - for (Index = 0; Index < MaxRetry; Index++) { - Status = ScsiDiskRequestSenseKeys ( - ScsiDiskDevice, - NeedRetry, - &SenseDataArray, - &NumberOfSenseKeys, - TRUE - ); - if (!EFI_ERROR (Status)) { - *NeedRetry = TRUE; - return EFI_DEVICE_ERROR; - } - if (!*NeedRetry) { - return EFI_DEVICE_ERROR; - } + if ((Media->IoAlign > 1) && (((UINTN) Buffer & (Media->IoAlign - 1)) != 0)) { + Status = EFI_INVALID_PARAMETER; + goto Done; } + // - // ScsiDiskRequestSenseKeys() failed after several rounds of retry. - // set *NeedRetry = FALSE to avoid the outside caller try again. + // If all the parameters are valid, then perform read sectors command + // to transfer data from device to host. // - *NeedRetry = FALSE; - return EFI_DEVICE_ERROR; + if ((Token != NULL) && (Token->Event != NULL)) { + Token->TransactionStatus = EFI_SUCCESS; + Status = ScsiDiskAsyncReadSectors ( + ScsiDiskDevice, + Buffer, + Lba, + NumberOfBlocks, + Token + ); + } else { + Status = ScsiDiskReadSectors ( + ScsiDiskDevice, + Buffer, + Lba, + NumberOfBlocks + ); + } + +Done: + gBS->RestoreTPL (OldTpl); + return Status; } /** - To test device. - - When Test Unit Ready command succeeds, retrieve Sense Keys via Request Sense; - When Test Unit Ready command encounters any error caused by host adapter or - target, return error without retrieving Sense Keys. - - @param ScsiDiskDevice The pointer of SCSI_DISK_DEV - @param NeedRetry The pointer of flag indicates try again - @param SenseDataArray The pointer of an array of sense data - @param NumberOfSenseKeys The pointer of the number of sense data array + The function is to Write Block to SCSI Disk. - @retval EFI_DEVICE_ERROR Indicates that error occurs - @retval EFI_SUCCESS Successfully to test unit + @param This The pointer of EFI_BLOCK_IO_PROTOCOL. + @param MediaId The Id of Media detected. + @param Lba The logic block address. + @param Token A pointer to the token associated with the transaction. + @param BufferSize The size of Buffer. + @param Buffer The buffer to fill the read out data. + + @retval EFI_SUCCESS The data were written correctly to the device. + @retval EFI_WRITE_PROTECTED The device cannot be written to. + @retval EFI_NO_MEDIA There is no media in the device. + @retval EFI_MEDIA_CHANGED The MediaId is not for the current media. + @retval EFI_DEVICE_ERROR The device reported an error while attempting + to perform the write operation. + @retval EFI_BAD_BUFFER_SIZE The BufferSize parameter is not a multiple of + the intrinsic block size of the device. + @retval EFI_INVALID_PARAMETER The write request contains LBAs that are not + valid, or the buffer is not on proper + alignment. **/ EFI_STATUS -ScsiDiskTestUnitReady ( - IN SCSI_DISK_DEV *ScsiDiskDevice, - OUT BOOLEAN *NeedRetry, - OUT EFI_SCSI_SENSE_DATA **SenseDataArray, - OUT UINTN *NumberOfSenseKeys +EFIAPI +ScsiDiskWriteBlocksEx ( + IN EFI_BLOCK_IO2_PROTOCOL *This, + IN UINT32 MediaId, + IN EFI_LBA Lba, + IN OUT EFI_BLOCK_IO2_TOKEN *Token, + IN UINTN BufferSize, + IN VOID *Buffer ) { - EFI_STATUS Status; - UINT8 SenseDataLength; - UINT8 HostAdapterStatus; - UINT8 TargetStatus; - UINT8 Index; - UINT8 MaxRetry; - - SenseDataLength = (UINT8) (ScsiDiskDevice->SenseDataNumber * sizeof (EFI_SCSI_SENSE_DATA)); - *NumberOfSenseKeys = 0; + SCSI_DISK_DEV *ScsiDiskDevice; + EFI_BLOCK_IO_MEDIA *Media; + EFI_STATUS Status; + UINTN BlockSize; + UINTN NumberOfBlocks; + BOOLEAN MediaChange; + EFI_TPL OldTpl; - // - // Parameter 3 and 4: do not require sense data, retrieve it when needed. - // - Status = ScsiTestUnitReadyCommand ( - ScsiDiskDevice->ScsiIo, - SCSI_DISK_TIMEOUT, - ScsiDiskDevice->SenseData, - &SenseDataLength, - &HostAdapterStatus, - &TargetStatus + MediaChange = FALSE; + OldTpl = gBS->RaiseTPL (TPL_CALLBACK); + ScsiDiskDevice = SCSI_DISK_DEV_FROM_BLKIO2 (This); + Media = ScsiDiskDevice->BlkIo.Media; + + if (!IS_DEVICE_FIXED(ScsiDiskDevice)) { + + Status = ScsiDiskDetectMedia (ScsiDiskDevice, FALSE, &MediaChange); + if (EFI_ERROR (Status)) { + Status = EFI_DEVICE_ERROR; + goto Done; + } + + if (MediaChange) { + gBS->ReinstallProtocolInterface ( + ScsiDiskDevice->Handle, + &gEfiBlockIoProtocolGuid, + &ScsiDiskDevice->BlkIo, + &ScsiDiskDevice->BlkIo ); + gBS->ReinstallProtocolInterface ( + ScsiDiskDevice->Handle, + &gEfiBlockIo2ProtocolGuid, + &ScsiDiskDevice->BlkIo2, + &ScsiDiskDevice->BlkIo2 + ); + if (DetermineInstallEraseBlock(ScsiDiskDevice, ScsiDiskDevice->Handle)) { + gBS->ReinstallProtocolInterface ( + ScsiDiskDevice->Handle, + &gEfiEraseBlockProtocolGuid, + &ScsiDiskDevice->EraseBlock, + &ScsiDiskDevice->EraseBlock + ); + } + if (Media->MediaPresent) { + Status = EFI_MEDIA_CHANGED; + } else { + Status = EFI_NO_MEDIA; + } + goto Done; + } + } // - // no need to check HostAdapterStatus and TargetStatus + // Get the intrinsic block size // - if (Status == EFI_NOT_READY) { - *NeedRetry = TRUE; - return EFI_DEVICE_ERROR; + BlockSize = Media->BlockSize; - } else if ((Status == EFI_INVALID_PARAMETER) || (Status == EFI_UNSUPPORTED)) { - *NeedRetry = FALSE; - return EFI_DEVICE_ERROR; + NumberOfBlocks = BufferSize / BlockSize; + + if (!(Media->MediaPresent)) { + Status = EFI_NO_MEDIA; + goto Done; } - // - // go ahead to check HostAdapterStatus and TargetStatus(in case of EFI_DEVICE_ERROR) - // - Status = CheckHostAdapterStatus (HostAdapterStatus); - if ((Status == EFI_TIMEOUT) || (Status == EFI_NOT_READY)) { - *NeedRetry = TRUE; - return EFI_DEVICE_ERROR; + if (MediaId != Media->MediaId) { + Status = EFI_MEDIA_CHANGED; + goto Done; + } - } else if (Status == EFI_DEVICE_ERROR) { - // - // reset the scsi channel - // - ScsiDiskDevice->ScsiIo->ResetBus (ScsiDiskDevice->ScsiIo); - *NeedRetry = FALSE; - return EFI_DEVICE_ERROR; + if (Media->ReadOnly) { + Status = EFI_WRITE_PROTECTED; + goto Done; } - Status = CheckTargetStatus (TargetStatus); - if (Status == EFI_NOT_READY) { - // - // reset the scsi device - // - ScsiDiskDevice->ScsiIo->ResetDevice (ScsiDiskDevice->ScsiIo); - *NeedRetry = TRUE; - return EFI_DEVICE_ERROR; + if (BufferSize == 0) { + if ((Token != NULL) && (Token->Event != NULL)) { + Token->TransactionStatus = EFI_SUCCESS; + gBS->SignalEvent (Token->Event); + } - } else if (Status == EFI_DEVICE_ERROR) { - *NeedRetry = FALSE; - return EFI_DEVICE_ERROR; + Status = EFI_SUCCESS; + goto Done; } - if (SenseDataLength != 0) { - *NumberOfSenseKeys = SenseDataLength / sizeof (EFI_SCSI_SENSE_DATA); - *SenseDataArray = ScsiDiskDevice->SenseData; - return EFI_SUCCESS; + if (Buffer == NULL) { + Status = EFI_INVALID_PARAMETER; + goto Done; } - MaxRetry = 3; - for (Index = 0; Index < MaxRetry; Index++) { - Status = ScsiDiskRequestSenseKeys ( - ScsiDiskDevice, - NeedRetry, - SenseDataArray, - NumberOfSenseKeys, - FALSE - ); - if (!EFI_ERROR (Status)) { - return EFI_SUCCESS; - } + if (BufferSize % BlockSize != 0) { + Status = EFI_BAD_BUFFER_SIZE; + goto Done; + } - if (!*NeedRetry) { - return EFI_DEVICE_ERROR; - } + if (Lba > Media->LastBlock) { + Status = EFI_INVALID_PARAMETER; + goto Done; + } + + if ((Lba + NumberOfBlocks - 1) > Media->LastBlock) { + Status = EFI_INVALID_PARAMETER; + goto Done; + } + + if ((Media->IoAlign > 1) && (((UINTN) Buffer & (Media->IoAlign - 1)) != 0)) { + Status = EFI_INVALID_PARAMETER; + goto Done; } + // - // ScsiDiskRequestSenseKeys() failed after several rounds of retry. - // set *NeedRetry = FALSE to avoid the outside caller try again. + // if all the parameters are valid, then perform write sectors command + // to transfer data from device to host. // - *NeedRetry = FALSE; - return EFI_DEVICE_ERROR; + if ((Token != NULL) && (Token->Event != NULL)) { + Token->TransactionStatus = EFI_SUCCESS; + Status = ScsiDiskAsyncWriteSectors ( + ScsiDiskDevice, + Buffer, + Lba, + NumberOfBlocks, + Token + ); + } else { + Status = ScsiDiskWriteSectors ( + ScsiDiskDevice, + Buffer, + Lba, + NumberOfBlocks + ); + } + +Done: + gBS->RestoreTPL (OldTpl); + return Status; } /** - Parsing Sense Keys which got from request sense command. + Flush the Block Device. - @param ScsiDiskDevice The pointer of SCSI_DISK_DEV - @param SenseData The pointer of EFI_SCSI_SENSE_DATA - @param NumberOfSenseKeys The number of sense key - @param Action The pointer of action which indicates what is need to do next + @param This Indicates a pointer to the calling context. + @param Token A pointer to the token associated with the transaction. - @retval EFI_DEVICE_ERROR Indicates that error occurs - @retval EFI_SUCCESS Successfully to complete the parsing + @retval EFI_SUCCESS All outstanding data was written to the device. + @retval EFI_DEVICE_ERROR The device reported an error while attempting to + write data. + @retval EFI_WRITE_PROTECTED The device cannot be written to. + @retval EFI_NO_MEDIA There is no media in the device. + @retval EFI_MEDIA_CHANGED The MediaId is not for the current media. **/ EFI_STATUS -DetectMediaParsingSenseKeys ( - OUT SCSI_DISK_DEV *ScsiDiskDevice, - IN EFI_SCSI_SENSE_DATA *SenseData, - IN UINTN NumberOfSenseKeys, - OUT UINTN *Action +EFIAPI +ScsiDiskFlushBlocksEx ( + IN EFI_BLOCK_IO2_PROTOCOL *This, + IN OUT EFI_BLOCK_IO2_TOKEN *Token ) { - BOOLEAN RetryLater; + SCSI_DISK_DEV *ScsiDiskDevice; + EFI_BLOCK_IO_MEDIA *Media; + EFI_STATUS Status; + BOOLEAN MediaChange; + EFI_TPL OldTpl; - // - // Default is to read capacity, unless.. - // - *Action = ACTION_READ_CAPACITY; + MediaChange = FALSE; + OldTpl = gBS->RaiseTPL (TPL_CALLBACK); + ScsiDiskDevice = SCSI_DISK_DEV_FROM_BLKIO2 (This); + Media = ScsiDiskDevice->BlkIo.Media; - if (NumberOfSenseKeys == 0) { - if (ScsiDiskDevice->BlkIo.Media->MediaPresent == TRUE) { - *Action = ACTION_NO_ACTION; - } - return EFI_SUCCESS; - } + if (!IS_DEVICE_FIXED(ScsiDiskDevice)) { - if (!ScsiDiskHaveSenseKey (SenseData, NumberOfSenseKeys)) { - // - // No Sense Key returned from last submitted command - // - if (ScsiDiskDevice->BlkIo.Media->MediaPresent == TRUE) { - *Action = ACTION_NO_ACTION; + Status = ScsiDiskDetectMedia (ScsiDiskDevice, FALSE, &MediaChange); + if (EFI_ERROR (Status)) { + Status = EFI_DEVICE_ERROR; + goto Done; } - return EFI_SUCCESS; - } - if (ScsiDiskIsNoMedia (SenseData, NumberOfSenseKeys)) { - ScsiDiskDevice->BlkIo.Media->MediaPresent = FALSE; - ScsiDiskDevice->BlkIo.Media->LastBlock = 0; - *Action = ACTION_NO_ACTION; - DEBUG ((EFI_D_VERBOSE, "ScsiDisk: ScsiDiskIsNoMedia\n")); - return EFI_SUCCESS; + if (MediaChange) { + gBS->ReinstallProtocolInterface ( + ScsiDiskDevice->Handle, + &gEfiBlockIoProtocolGuid, + &ScsiDiskDevice->BlkIo, + &ScsiDiskDevice->BlkIo + ); + gBS->ReinstallProtocolInterface ( + ScsiDiskDevice->Handle, + &gEfiBlockIo2ProtocolGuid, + &ScsiDiskDevice->BlkIo2, + &ScsiDiskDevice->BlkIo2 + ); + if (DetermineInstallEraseBlock(ScsiDiskDevice, ScsiDiskDevice->Handle)) { + gBS->ReinstallProtocolInterface ( + ScsiDiskDevice->Handle, + &gEfiEraseBlockProtocolGuid, + &ScsiDiskDevice->EraseBlock, + &ScsiDiskDevice->EraseBlock + ); + } + if (Media->MediaPresent) { + Status = EFI_MEDIA_CHANGED; + } else { + Status = EFI_NO_MEDIA; + } + goto Done; + } } - if (ScsiDiskIsMediaChange (SenseData, NumberOfSenseKeys)) { - ScsiDiskDevice->BlkIo.Media->MediaId++; - DEBUG ((EFI_D_VERBOSE, "ScsiDisk: ScsiDiskIsMediaChange!\n")); - return EFI_SUCCESS; + if (!(Media->MediaPresent)) { + Status = EFI_NO_MEDIA; + goto Done; } - if (ScsiDiskIsResetBefore (SenseData, NumberOfSenseKeys)) { - *Action = ACTION_RETRY_COMMAND_LATER; - DEBUG ((EFI_D_VERBOSE, "ScsiDisk: ScsiDiskIsResetBefore!\n")); - return EFI_SUCCESS; + if (Media->ReadOnly) { + Status = EFI_WRITE_PROTECTED; + goto Done; } - if (ScsiDiskIsMediaError (SenseData, NumberOfSenseKeys)) { - DEBUG ((EFI_D_VERBOSE, "ScsiDisk: ScsiDiskIsMediaError\n")); - *Action = ACTION_RETRY_WITH_BACKOFF_ALGO; - return EFI_DEVICE_ERROR; - } + // + // Wait for the BlockIo2 requests queue to become empty + // + while (!IsListEmpty (&ScsiDiskDevice->AsyncTaskQueue)); - if (ScsiDiskIsHardwareError (SenseData, NumberOfSenseKeys)) { - DEBUG ((EFI_D_VERBOSE, "ScsiDisk: ScsiDiskIsHardwareError\n")); - *Action = ACTION_RETRY_WITH_BACKOFF_ALGO; - return EFI_DEVICE_ERROR; - } + Status = EFI_SUCCESS; - if (!ScsiDiskIsDriveReady (SenseData, NumberOfSenseKeys, &RetryLater)) { - if (RetryLater) { - *Action = ACTION_RETRY_COMMAND_LATER; - DEBUG ((EFI_D_VERBOSE, "ScsiDisk: ScsiDiskDriveNotReady!\n")); - return EFI_SUCCESS; - } - *Action = ACTION_NO_ACTION; - return EFI_DEVICE_ERROR; + // + // Signal caller event + // + if ((Token != NULL) && (Token->Event != NULL)) { + Token->TransactionStatus = EFI_SUCCESS; + gBS->SignalEvent (Token->Event); } - *Action = ACTION_RETRY_WITH_BACKOFF_ALGO; - DEBUG ((EFI_D_VERBOSE, "ScsiDisk: Sense Key = 0x%x ASC = 0x%x!\n", SenseData->Sense_Key, SenseData->Addnl_Sense_Code)); - return EFI_SUCCESS; +Done: + gBS->RestoreTPL (OldTpl); + return Status; } /** - Send read capacity command to device and get the device parameter. - - @param ScsiDiskDevice The pointer of SCSI_DISK_DEV - @param NeedRetry The pointer of flag indicates if need a retry - @param SenseDataArray The pointer of an array of sense data - @param NumberOfSenseKeys The number of sense key + Internal helper notify function which process the result of an asynchronous + SCSI UNMAP Command and signal the event passed from EraseBlocks. - @retval EFI_DEVICE_ERROR Indicates that error occurs - @retval EFI_SUCCESS Successfully to read capacity or sense data is received. + @param Event The instance of EFI_EVENT. + @param Context The parameter passed in. **/ -EFI_STATUS -ScsiDiskReadCapacity ( - IN OUT SCSI_DISK_DEV *ScsiDiskDevice, - OUT BOOLEAN *NeedRetry, - OUT EFI_SCSI_SENSE_DATA **SenseDataArray, - OUT UINTN *NumberOfSenseKeys +VOID +EFIAPI +ScsiDiskAsyncUnmapNotify ( + IN EFI_EVENT Event, + IN VOID *Context ) { - UINT8 HostAdapterStatus; - UINT8 TargetStatus; - EFI_STATUS CommandStatus; - EFI_STATUS Status; - UINT8 Index; - UINT8 MaxRetry; - UINT8 SenseDataLength; - UINT32 DataLength10; - UINT32 DataLength16; - EFI_SCSI_DISK_CAPACITY_DATA *CapacityData10; - EFI_SCSI_DISK_CAPACITY_DATA16 *CapacityData16; + SCSI_ERASEBLK_REQUEST *EraseBlkReq; + EFI_SCSI_IO_SCSI_REQUEST_PACKET *CommandPacket; + EFI_ERASE_BLOCK_TOKEN *Token; + EFI_STATUS Status; + + gBS->CloseEvent (Event); + + EraseBlkReq = (SCSI_ERASEBLK_REQUEST *) Context; + CommandPacket = &EraseBlkReq->CommandPacket; + Token = EraseBlkReq->Token; + Token->TransactionStatus = EFI_SUCCESS; + + Status = CheckHostAdapterStatus (CommandPacket->HostAdapterStatus); + if (EFI_ERROR(Status)) { + DEBUG (( + EFI_D_ERROR, + "ScsiDiskAsyncUnmapNotify: Host adapter indicating error status 0x%x.\n", + CommandPacket->HostAdapterStatus + )); + + Token->TransactionStatus = Status; + goto Done; + } - CapacityData10 = AllocateAlignedBuffer (ScsiDiskDevice, sizeof (EFI_SCSI_DISK_CAPACITY_DATA)); - if (CapacityData10 == NULL) { - *NeedRetry = FALSE; - return EFI_DEVICE_ERROR; + Status = CheckTargetStatus (CommandPacket->TargetStatus); + if (EFI_ERROR(Status)) { + DEBUG (( + EFI_D_ERROR, + "ScsiDiskAsyncUnmapNotify: Target indicating error status 0x%x.\n", + CommandPacket->HostAdapterStatus + )); + + Token->TransactionStatus = Status; + goto Done; } - CapacityData16 = AllocateAlignedBuffer (ScsiDiskDevice, sizeof (EFI_SCSI_DISK_CAPACITY_DATA16)); - if (CapacityData16 == NULL) { - FreeAlignedBuffer (CapacityData10, sizeof (EFI_SCSI_DISK_CAPACITY_DATA)); - *NeedRetry = FALSE; - return EFI_DEVICE_ERROR; + +Done: + RemoveEntryList (&EraseBlkReq->Link); + FreePool (CommandPacket->OutDataBuffer); + FreePool (EraseBlkReq->CommandPacket.Cdb); + FreePool (EraseBlkReq); + + gBS->SignalEvent (Token->Event); +} + +/** + Require the device server to cause one or more LBAs to be unmapped. + + @param ScsiDiskDevice The pointer of ScsiDiskDevice. + @param Lba The start block number. + @param Blocks Total block number to be unmapped. + @param Token The pointer to the token associated with the + non-blocking erase block request. + + @retval EFI_SUCCESS Target blocks have been successfully unmapped. + @retval EFI_DEVICE_ERROR Fail to unmap the target blocks. + +**/ +EFI_STATUS +ScsiDiskUnmap ( + IN SCSI_DISK_DEV *ScsiDiskDevice, + IN UINT64 Lba, + IN UINTN Blocks, + IN EFI_ERASE_BLOCK_TOKEN *Token OPTIONAL + ) +{ + EFI_SCSI_IO_PROTOCOL *ScsiIo; + SCSI_ERASEBLK_REQUEST *EraseBlkReq; + EFI_SCSI_IO_SCSI_REQUEST_PACKET *CommandPacket; + EFI_SCSI_DISK_UNMAP_BLOCK_DESP *BlkDespPtr; + EFI_STATUS Status; + EFI_STATUS ReturnStatus; + UINT8 *Cdb; + UINT32 MaxLbaCnt; + UINT32 MaxBlkDespCnt; + UINT32 BlkDespCnt; + UINT16 UnmapParamListLen; + VOID *UnmapParamList; + EFI_EVENT AsyncUnmapEvent; + EFI_TPL OldTpl; + + ScsiIo = ScsiDiskDevice->ScsiIo; + MaxLbaCnt = ScsiDiskDevice->UnmapInfo.MaxLbaCnt; + MaxBlkDespCnt = ScsiDiskDevice->UnmapInfo.MaxBlkDespCnt; + EraseBlkReq = NULL; + UnmapParamList = NULL; + AsyncUnmapEvent = NULL; + ReturnStatus = EFI_SUCCESS; + + if (Blocks / (UINTN) MaxLbaCnt > MaxBlkDespCnt) { + ReturnStatus = EFI_DEVICE_ERROR; + goto Done; } - SenseDataLength = 0; - DataLength10 = sizeof (EFI_SCSI_DISK_CAPACITY_DATA); - DataLength16 = sizeof (EFI_SCSI_DISK_CAPACITY_DATA16); - ZeroMem (CapacityData10, sizeof (EFI_SCSI_DISK_CAPACITY_DATA)); - ZeroMem (CapacityData16, sizeof (EFI_SCSI_DISK_CAPACITY_DATA16)); + EraseBlkReq = AllocateZeroPool (sizeof (SCSI_ERASEBLK_REQUEST)); + if (EraseBlkReq == NULL) { + ReturnStatus = EFI_DEVICE_ERROR; + goto Done; + } - *NumberOfSenseKeys = 0; - *NeedRetry = FALSE; + EraseBlkReq->CommandPacket.Cdb = AllocateZeroPool (0xA); + if (EraseBlkReq->CommandPacket.Cdb == NULL) { + ReturnStatus = EFI_DEVICE_ERROR; + goto Done; + } + + BlkDespCnt = (UINT32) ((Blocks - 1) / MaxLbaCnt + 1); + UnmapParamListLen = (UINT16) (sizeof (EFI_SCSI_DISK_UNMAP_PARAM_LIST_HEADER) + + BlkDespCnt * sizeof (EFI_SCSI_DISK_UNMAP_BLOCK_DESP)); + UnmapParamList = AllocateZeroPool (UnmapParamListLen); + if (UnmapParamList == NULL) { + ReturnStatus = EFI_DEVICE_ERROR; + goto Done; + } + + *((UINT16 *)UnmapParamList) = SwapBytes16 (UnmapParamListLen - 2); + *((UINT16 *)UnmapParamList + 1) = SwapBytes16 (UnmapParamListLen - sizeof (EFI_SCSI_DISK_UNMAP_PARAM_LIST_HEADER)); + + BlkDespPtr = (EFI_SCSI_DISK_UNMAP_BLOCK_DESP *)((UINT8 *)UnmapParamList + sizeof (EFI_SCSI_DISK_UNMAP_PARAM_LIST_HEADER)); + while (Blocks > 0) { + if (Blocks > MaxLbaCnt) { + *(UINT64 *)(&BlkDespPtr->Lba) = SwapBytes64 (Lba); + *(UINT32 *)(&BlkDespPtr->BlockNum) = SwapBytes32 (MaxLbaCnt); + Blocks -= MaxLbaCnt; + Lba += MaxLbaCnt; + } else { + *(UINT64 *)(&BlkDespPtr->Lba) = SwapBytes64 (Lba); + *(UINT32 *)(&BlkDespPtr->BlockNum) = SwapBytes32 ((UINT32) Blocks); + Blocks = 0; + } + + BlkDespPtr++; + } + CommandPacket = &EraseBlkReq->CommandPacket; + CommandPacket->Timeout = SCSI_DISK_TIMEOUT; + CommandPacket->OutDataBuffer = UnmapParamList; + CommandPacket->OutTransferLength = UnmapParamListLen; + CommandPacket->CdbLength = 0xA; + CommandPacket->DataDirection = EFI_SCSI_DATA_OUT; // - // submit Read Capacity(10) Command. If it returns capacity of FFFFFFFFh, - // 16 byte command should be used to access large hard disk >2TB + // Fill Cdb for UNMAP Command // - CommandStatus = ScsiReadCapacityCommand ( - ScsiDiskDevice->ScsiIo, - SCSI_DISK_TIMEOUT, - NULL, - &SenseDataLength, - &HostAdapterStatus, - &TargetStatus, - (VOID *) CapacityData10, - &DataLength10, - FALSE - ); + Cdb = CommandPacket->Cdb; + Cdb[0] = EFI_SCSI_OP_UNMAP; + WriteUnaligned16 ((UINT16 *)&Cdb[7], SwapBytes16 (UnmapParamListLen)); - ScsiDiskDevice->Cdb16Byte = FALSE; - if ((!EFI_ERROR (CommandStatus)) && (CapacityData10->LastLba3 == 0xff) && (CapacityData10->LastLba2 == 0xff) && - (CapacityData10->LastLba1 == 0xff) && (CapacityData10->LastLba0 == 0xff)) { + if ((Token != NULL) && (Token->Event != NULL)) { // - // use Read Capacity (16), Read (16) and Write (16) next when hard disk size > 2TB + // Non-blocking UNMAP request // - ScsiDiskDevice->Cdb16Byte = TRUE; + Status = gBS->CreateEvent ( + EVT_NOTIFY_SIGNAL, + TPL_NOTIFY, + ScsiDiskAsyncUnmapNotify, + EraseBlkReq, + &AsyncUnmapEvent + ); + if (EFI_ERROR(Status)) { + ReturnStatus = EFI_DEVICE_ERROR; + goto Done; + } + + OldTpl = gBS->RaiseTPL (TPL_NOTIFY); + InsertTailList (&ScsiDiskDevice->AsyncTaskQueue, &EraseBlkReq->Link); + gBS->RestoreTPL (OldTpl); + + EraseBlkReq->Token = Token; + + Status = ScsiIo->ExecuteScsiCommand ( + ScsiIo, + CommandPacket, + AsyncUnmapEvent + ); + if (EFI_ERROR(Status)) { + ReturnStatus = EFI_DEVICE_ERROR; + + OldTpl = gBS->RaiseTPL (TPL_NOTIFY); + RemoveEntryList (&EraseBlkReq->Link); + gBS->RestoreTPL (OldTpl); + + goto Done; + } else { + // + // Directly return if the non-blocking UNMAP request is queued. + // + return EFI_SUCCESS; + } + } else { // - // submit Read Capacity(16) Command to get parameter LogicalBlocksPerPhysicalBlock - // and LowestAlignedLba + // Blocking UNMAP request // - CommandStatus = ScsiReadCapacity16Command ( - ScsiDiskDevice->ScsiIo, - SCSI_DISK_TIMEOUT, - NULL, - &SenseDataLength, - &HostAdapterStatus, - &TargetStatus, - (VOID *) CapacityData16, - &DataLength16, - FALSE - ); + Status = ScsiIo->ExecuteScsiCommand ( + ScsiIo, + CommandPacket, + NULL + ); + if (EFI_ERROR(Status)) { + ReturnStatus = EFI_DEVICE_ERROR; + goto Done; + } } - // - // no need to check HostAdapterStatus and TargetStatus - // - if (CommandStatus == EFI_SUCCESS) { - GetMediaInfo (ScsiDiskDevice, CapacityData10, CapacityData16); - FreeAlignedBuffer (CapacityData10, sizeof (EFI_SCSI_DISK_CAPACITY_DATA)); - FreeAlignedBuffer (CapacityData16, sizeof (EFI_SCSI_DISK_CAPACITY_DATA16)); - return EFI_SUCCESS; - } + // + // Only blocking UNMAP request will reach here. + // + Status = CheckHostAdapterStatus (CommandPacket->HostAdapterStatus); + if (EFI_ERROR(Status)) { + DEBUG (( + EFI_D_ERROR, + "ScsiDiskUnmap: Host adapter indicating error status 0x%x.\n", + CommandPacket->HostAdapterStatus + )); + + ReturnStatus = EFI_DEVICE_ERROR; + goto Done; + } - FreeAlignedBuffer (CapacityData10, sizeof (EFI_SCSI_DISK_CAPACITY_DATA)); - FreeAlignedBuffer (CapacityData16, sizeof (EFI_SCSI_DISK_CAPACITY_DATA16)); + Status = CheckTargetStatus (CommandPacket->TargetStatus); + if (EFI_ERROR(Status)) { + DEBUG (( + EFI_D_ERROR, + "ScsiDiskUnmap: Target indicating error status 0x%x.\n", + CommandPacket->HostAdapterStatus + )); - if (CommandStatus == EFI_NOT_READY) { - *NeedRetry = TRUE; - return EFI_DEVICE_ERROR; - } else if ((CommandStatus == EFI_INVALID_PARAMETER) || (CommandStatus == EFI_UNSUPPORTED)) { - *NeedRetry = FALSE; - return EFI_DEVICE_ERROR; - } + ReturnStatus = EFI_DEVICE_ERROR; + goto Done; + } - // - // go ahead to check HostAdapterStatus and TargetStatus - // (EFI_TIMEOUT, EFI_DEVICE_ERROR, EFI_WARN_BUFFER_TOO_SMALL) - // - - Status = CheckHostAdapterStatus (HostAdapterStatus); - if ((Status == EFI_TIMEOUT) || (Status == EFI_NOT_READY)) { - *NeedRetry = TRUE; - return EFI_DEVICE_ERROR; - - } else if (Status == EFI_DEVICE_ERROR) { - // - // reset the scsi channel - // - ScsiDiskDevice->ScsiIo->ResetBus (ScsiDiskDevice->ScsiIo); - *NeedRetry = FALSE; - return EFI_DEVICE_ERROR; +Done: + if (EraseBlkReq != NULL) { + if (EraseBlkReq->CommandPacket.Cdb != NULL) { + FreePool (EraseBlkReq->CommandPacket.Cdb); + } + FreePool (EraseBlkReq); } - Status = CheckTargetStatus (TargetStatus); - if (Status == EFI_NOT_READY) { - // - // reset the scsi device - // - ScsiDiskDevice->ScsiIo->ResetDevice (ScsiDiskDevice->ScsiIo); - *NeedRetry = TRUE; - return EFI_DEVICE_ERROR; + if (UnmapParamList != NULL) { + FreePool (UnmapParamList); + } - } else if (Status == EFI_DEVICE_ERROR) { - *NeedRetry = FALSE; - return EFI_DEVICE_ERROR; + if (AsyncUnmapEvent != NULL) { + gBS->CloseEvent (AsyncUnmapEvent); + } + + return ReturnStatus; +} + +/** + Erase a specified number of device blocks. + + @param[in] This Indicates a pointer to the calling context. + @param[in] MediaId The media ID that the erase request is for. + @param[in] Lba The starting logical block address to be + erased. The caller is responsible for erasing + only legitimate locations. + @param[in, out] Token A pointer to the token associated with the + transaction. + @param[in] Size The size in bytes to be erased. This must be + a multiple of the physical block size of the + device. + + @retval EFI_SUCCESS The erase request was queued if Event is not + NULL. The data was erased correctly to the + device if the Event is NULL.to the device. + @retval EFI_WRITE_PROTECTED The device cannot be erased due to write + protection. + @retval EFI_DEVICE_ERROR The device reported an error while attempting + to perform the erase operation. + @retval EFI_INVALID_PARAMETER The erase request contains LBAs that are not + valid. + @retval EFI_NO_MEDIA There is no media in the device. + @retval EFI_MEDIA_CHANGED The MediaId is not for the current media. + +**/ +EFI_STATUS +EFIAPI +ScsiDiskEraseBlocks ( + IN EFI_ERASE_BLOCK_PROTOCOL *This, + IN UINT32 MediaId, + IN EFI_LBA Lba, + IN OUT EFI_ERASE_BLOCK_TOKEN *Token, + IN UINTN Size + ) +{ + SCSI_DISK_DEV *ScsiDiskDevice; + EFI_BLOCK_IO_MEDIA *Media; + EFI_STATUS Status; + UINTN BlockSize; + UINTN NumberOfBlocks; + BOOLEAN MediaChange; + EFI_TPL OldTpl; + + MediaChange = FALSE; + OldTpl = gBS->RaiseTPL (TPL_CALLBACK); + ScsiDiskDevice = SCSI_DISK_DEV_FROM_ERASEBLK (This); + + if (!IS_DEVICE_FIXED(ScsiDiskDevice)) { + Status = ScsiDiskDetectMedia (ScsiDiskDevice, FALSE, &MediaChange); + if (EFI_ERROR (Status)) { + Status = EFI_DEVICE_ERROR; + goto Done; + } + + if (MediaChange) { + gBS->ReinstallProtocolInterface ( + ScsiDiskDevice->Handle, + &gEfiBlockIoProtocolGuid, + &ScsiDiskDevice->BlkIo, + &ScsiDiskDevice->BlkIo + ); + gBS->ReinstallProtocolInterface ( + ScsiDiskDevice->Handle, + &gEfiBlockIo2ProtocolGuid, + &ScsiDiskDevice->BlkIo2, + &ScsiDiskDevice->BlkIo2 + ); + if (DetermineInstallEraseBlock(ScsiDiskDevice, ScsiDiskDevice->Handle)) { + gBS->ReinstallProtocolInterface ( + ScsiDiskDevice->Handle, + &gEfiEraseBlockProtocolGuid, + &ScsiDiskDevice->EraseBlock, + &ScsiDiskDevice->EraseBlock + ); + } + Status = EFI_MEDIA_CHANGED; + goto Done; + } } - // - // if goes here, meant ScsiReadCapacityCommand() failed. - // if ScsiDiskRequestSenseKeys() succeeds at last, - // better retry ScsiReadCapacityCommand(). (by setting *NeedRetry = TRUE) + // Get the intrinsic block size // - MaxRetry = 3; - for (Index = 0; Index < MaxRetry; Index++) { + Media = ScsiDiskDevice->BlkIo.Media; - Status = ScsiDiskRequestSenseKeys ( - ScsiDiskDevice, - NeedRetry, - SenseDataArray, - NumberOfSenseKeys, - TRUE - ); - if (!EFI_ERROR (Status)) { - return EFI_SUCCESS; + if (!(Media->MediaPresent)) { + Status = EFI_NO_MEDIA; + goto Done; + } + + if (MediaId != Media->MediaId) { + Status = EFI_MEDIA_CHANGED; + goto Done; + } + + if (Media->ReadOnly) { + Status = EFI_WRITE_PROTECTED; + goto Done; + } + + if (Size == 0) { + if ((Token != NULL) && (Token->Event != NULL)) { + Token->TransactionStatus = EFI_SUCCESS; + gBS->SignalEvent (Token->Event); + } + Status = EFI_SUCCESS; + goto Done; + } + + BlockSize = Media->BlockSize; + if ((Size % BlockSize) != 0) { + Status = EFI_INVALID_PARAMETER; + goto Done; + } + + NumberOfBlocks = Size / BlockSize; + if ((Lba + NumberOfBlocks - 1) > Media->LastBlock) { + Status = EFI_INVALID_PARAMETER; + goto Done; + } + + if ((Token != NULL) && (Token->Event != NULL)) { + Status = ScsiDiskUnmap (ScsiDiskDevice, Lba, NumberOfBlocks, Token); + } else { + Status = ScsiDiskUnmap (ScsiDiskDevice, Lba, NumberOfBlocks, NULL); + } + +Done: + gBS->RestoreTPL (OldTpl); + return Status; +} + + +/** + Detect Device and read out capacity ,if error occurs, parse the sense key. + + @param ScsiDiskDevice The pointer of SCSI_DISK_DEV + @param MustReadCapacity The flag about reading device capacity + @param MediaChange The pointer of flag indicates if media has changed + + @retval EFI_DEVICE_ERROR Indicates that error occurs + @retval EFI_SUCCESS Successfully to detect media + +**/ +EFI_STATUS +ScsiDiskDetectMedia ( + IN SCSI_DISK_DEV *ScsiDiskDevice, + IN BOOLEAN MustReadCapacity, + OUT BOOLEAN *MediaChange + ) +{ + EFI_STATUS Status; + EFI_SCSI_SENSE_DATA *SenseData; + UINTN NumberOfSenseKeys; + BOOLEAN NeedRetry; + BOOLEAN NeedReadCapacity; + UINT8 Retry; + UINT8 MaxRetry; + EFI_BLOCK_IO_MEDIA OldMedia; + UINTN Action; + EFI_EVENT TimeoutEvt; + + Status = EFI_SUCCESS; + SenseData = NULL; + NumberOfSenseKeys = 0; + Retry = 0; + MaxRetry = 3; + Action = ACTION_NO_ACTION; + NeedReadCapacity = FALSE; + *MediaChange = FALSE; + TimeoutEvt = NULL; + + CopyMem (&OldMedia, ScsiDiskDevice->BlkIo.Media, sizeof (OldMedia)); + + Status = gBS->CreateEvent ( + EVT_TIMER, + TPL_CALLBACK, + NULL, + NULL, + &TimeoutEvt + ); + if (EFI_ERROR (Status)) { + return Status; + } + + Status = gBS->SetTimer (TimeoutEvt, TimerRelative, EFI_TIMER_PERIOD_SECONDS(120)); + if (EFI_ERROR (Status)) { + goto EXIT; + } + + // + // Sending Test_Unit cmd to poll device status. + // If the sense data shows the drive is not ready or reset before, we need poll the device status again. + // We limit the upper boundary to 120 seconds. + // + while (EFI_ERROR (gBS->CheckEvent (TimeoutEvt))) { + Status = ScsiDiskTestUnitReady ( + ScsiDiskDevice, + &NeedRetry, + &SenseData, + &NumberOfSenseKeys + ); + if (!EFI_ERROR (Status)) { + Status = DetectMediaParsingSenseKeys ( + ScsiDiskDevice, + SenseData, + NumberOfSenseKeys, + &Action + ); + if (EFI_ERROR (Status)) { + goto EXIT; + } else if (Action == ACTION_RETRY_COMMAND_LATER) { + continue; + } else { + break; + } + } else { + Retry++; + if (!NeedRetry || (Retry >= MaxRetry)) { + goto EXIT; + } + } + } + + if (EFI_ERROR (Status)) { + goto EXIT; + } + + // + // ACTION_NO_ACTION: need not read capacity + // other action code: need read capacity + // + if (Action == ACTION_READ_CAPACITY) { + NeedReadCapacity = TRUE; + } + + // + // either NeedReadCapacity is TRUE, or MustReadCapacity is TRUE, + // retrieve capacity via Read Capacity command + // + if (NeedReadCapacity || MustReadCapacity) { + // + // retrieve media information + // + for (Retry = 0; Retry < MaxRetry; Retry++) { + Status = ScsiDiskReadCapacity ( + ScsiDiskDevice, + &NeedRetry, + &SenseData, + &NumberOfSenseKeys + ); + if (!EFI_ERROR (Status)) { + // + // analyze sense key to action + // + Status = DetectMediaParsingSenseKeys ( + ScsiDiskDevice, + SenseData, + NumberOfSenseKeys, + &Action + ); + if (EFI_ERROR (Status)) { + // + // if Status is error, it may indicate crisis error, + // so return without retry. + // + goto EXIT; + } else if (Action == ACTION_RETRY_COMMAND_LATER) { + Retry = 0; + continue; + } else { + break; + } + } else { + Retry++; + if (!NeedRetry || (Retry >= MaxRetry)) { + goto EXIT; + } + } + } + + if (EFI_ERROR (Status)) { + goto EXIT; + } + } + + if (ScsiDiskDevice->BlkIo.Media->MediaId != OldMedia.MediaId) { + // + // Media change information got from the device + // + *MediaChange = TRUE; + } + + if (ScsiDiskDevice->BlkIo.Media->ReadOnly != OldMedia.ReadOnly) { + *MediaChange = TRUE; + ScsiDiskDevice->BlkIo.Media->MediaId += 1; + } + + if (ScsiDiskDevice->BlkIo.Media->BlockSize != OldMedia.BlockSize) { + *MediaChange = TRUE; + ScsiDiskDevice->BlkIo.Media->MediaId += 1; + } + + if (ScsiDiskDevice->BlkIo.Media->LastBlock != OldMedia.LastBlock) { + *MediaChange = TRUE; + ScsiDiskDevice->BlkIo.Media->MediaId += 1; + } + + if (ScsiDiskDevice->BlkIo.Media->MediaPresent != OldMedia.MediaPresent) { + if (ScsiDiskDevice->BlkIo.Media->MediaPresent) { + // + // when change from no media to media present, reset the MediaId to 1. + // + ScsiDiskDevice->BlkIo.Media->MediaId = 1; + } else { + // + // when no media, reset the MediaId to zero. + // + ScsiDiskDevice->BlkIo.Media->MediaId = 0; + } + + *MediaChange = TRUE; + } + +EXIT: + if (TimeoutEvt != NULL) { + gBS->CloseEvent (TimeoutEvt); + } + return Status; +} + + +/** + Send out Inquiry command to Device. + + @param ScsiDiskDevice The pointer of SCSI_DISK_DEV + @param NeedRetry Indicates if needs try again when error happens + + @retval EFI_DEVICE_ERROR Indicates that error occurs + @retval EFI_SUCCESS Successfully to detect media + +**/ +EFI_STATUS +ScsiDiskInquiryDevice ( + IN OUT SCSI_DISK_DEV *ScsiDiskDevice, + OUT BOOLEAN *NeedRetry + ) +{ + UINT32 InquiryDataLength; + UINT8 SenseDataLength; + UINT8 HostAdapterStatus; + UINT8 TargetStatus; + EFI_SCSI_SENSE_DATA *SenseDataArray; + UINTN NumberOfSenseKeys; + EFI_STATUS Status; + UINT8 MaxRetry; + UINT8 Index; + EFI_SCSI_SUPPORTED_VPD_PAGES_VPD_PAGE *SupportedVpdPages; + EFI_SCSI_BLOCK_LIMITS_VPD_PAGE *BlockLimits; + UINTN PageLength; + + InquiryDataLength = sizeof (EFI_SCSI_INQUIRY_DATA); + SenseDataLength = 0; + + Status = ScsiInquiryCommand ( + ScsiDiskDevice->ScsiIo, + SCSI_DISK_TIMEOUT, + NULL, + &SenseDataLength, + &HostAdapterStatus, + &TargetStatus, + (VOID *) &(ScsiDiskDevice->InquiryData), + &InquiryDataLength, + FALSE + ); + // + // no need to check HostAdapterStatus and TargetStatus + // + if ((Status == EFI_SUCCESS) || (Status == EFI_WARN_BUFFER_TOO_SMALL)) { + ParseInquiryData (ScsiDiskDevice); + + if (ScsiDiskDevice->DeviceType == EFI_SCSI_TYPE_DISK) { + // + // Check whether the device supports Block Limits VPD page (0xB0) + // + SupportedVpdPages = AllocateAlignedBuffer (ScsiDiskDevice, sizeof (EFI_SCSI_SUPPORTED_VPD_PAGES_VPD_PAGE)); + if (SupportedVpdPages == NULL) { + *NeedRetry = FALSE; + return EFI_DEVICE_ERROR; + } + ZeroMem (SupportedVpdPages, sizeof (EFI_SCSI_SUPPORTED_VPD_PAGES_VPD_PAGE)); + InquiryDataLength = sizeof (EFI_SCSI_SUPPORTED_VPD_PAGES_VPD_PAGE); + SenseDataLength = 0; + Status = ScsiInquiryCommandEx ( + ScsiDiskDevice->ScsiIo, + SCSI_DISK_TIMEOUT, + NULL, + &SenseDataLength, + &HostAdapterStatus, + &TargetStatus, + (VOID *) SupportedVpdPages, + &InquiryDataLength, + TRUE, + EFI_SCSI_PAGE_CODE_SUPPORTED_VPD + ); + if (!EFI_ERROR (Status)) { + PageLength = (SupportedVpdPages->PageLength2 << 8) + | SupportedVpdPages->PageLength1; + + // + // Sanity checks for coping with broken devices + // + if (PageLength > sizeof SupportedVpdPages->SupportedVpdPageList) { + DEBUG ((EFI_D_WARN, + "%a: invalid PageLength (%u) in Supported VPD Pages page\n", + __FUNCTION__, (UINT32)PageLength)); + PageLength = 0; + } + + if ((PageLength > 0) && + (SupportedVpdPages->SupportedVpdPageList[0] != + EFI_SCSI_PAGE_CODE_SUPPORTED_VPD)) { + DEBUG ((EFI_D_WARN, + "%a: Supported VPD Pages page doesn't start with code 0x%02x\n", + __FUNCTION__, EFI_SCSI_PAGE_CODE_SUPPORTED_VPD)); + PageLength = 0; + } + + // + // Locate the code for the Block Limits VPD page + // + for (Index = 0; Index < PageLength; Index++) { + // + // Sanity check + // + if ((Index > 0) && + (SupportedVpdPages->SupportedVpdPageList[Index] <= + SupportedVpdPages->SupportedVpdPageList[Index - 1])) { + DEBUG ((EFI_D_WARN, + "%a: non-ascending code in Supported VPD Pages page @ %u\n", + __FUNCTION__, Index)); + Index = 0; + PageLength = 0; + break; + } + + if (SupportedVpdPages->SupportedVpdPageList[Index] == EFI_SCSI_PAGE_CODE_BLOCK_LIMITS_VPD) { + break; + } + } + + // + // Query the Block Limits VPD page + // + if (Index < PageLength) { + BlockLimits = AllocateAlignedBuffer (ScsiDiskDevice, sizeof (EFI_SCSI_BLOCK_LIMITS_VPD_PAGE)); + if (BlockLimits == NULL) { + FreeAlignedBuffer (SupportedVpdPages, sizeof (EFI_SCSI_SUPPORTED_VPD_PAGES_VPD_PAGE)); + *NeedRetry = FALSE; + return EFI_DEVICE_ERROR; + } + ZeroMem (BlockLimits, sizeof (EFI_SCSI_BLOCK_LIMITS_VPD_PAGE)); + InquiryDataLength = sizeof (EFI_SCSI_BLOCK_LIMITS_VPD_PAGE); + SenseDataLength = 0; + Status = ScsiInquiryCommandEx ( + ScsiDiskDevice->ScsiIo, + SCSI_DISK_TIMEOUT, + NULL, + &SenseDataLength, + &HostAdapterStatus, + &TargetStatus, + (VOID *) BlockLimits, + &InquiryDataLength, + TRUE, + EFI_SCSI_PAGE_CODE_BLOCK_LIMITS_VPD + ); + if (!EFI_ERROR (Status)) { + ScsiDiskDevice->BlkIo.Media->OptimalTransferLengthGranularity = + (BlockLimits->OptimalTransferLengthGranularity2 << 8) | + BlockLimits->OptimalTransferLengthGranularity1; + + ScsiDiskDevice->UnmapInfo.MaxLbaCnt = + (BlockLimits->MaximumUnmapLbaCount4 << 24) | + (BlockLimits->MaximumUnmapLbaCount3 << 16) | + (BlockLimits->MaximumUnmapLbaCount2 << 8) | + BlockLimits->MaximumUnmapLbaCount1; + ScsiDiskDevice->UnmapInfo.MaxBlkDespCnt = + (BlockLimits->MaximumUnmapBlockDescriptorCount4 << 24) | + (BlockLimits->MaximumUnmapBlockDescriptorCount3 << 16) | + (BlockLimits->MaximumUnmapBlockDescriptorCount2 << 8) | + BlockLimits->MaximumUnmapBlockDescriptorCount1; + ScsiDiskDevice->EraseBlock.EraseLengthGranularity = + (BlockLimits->OptimalUnmapGranularity4 << 24) | + (BlockLimits->OptimalUnmapGranularity3 << 16) | + (BlockLimits->OptimalUnmapGranularity2 << 8) | + BlockLimits->OptimalUnmapGranularity1; + if (BlockLimits->UnmapGranularityAlignmentValid != 0) { + ScsiDiskDevice->UnmapInfo.GranularityAlignment = + (BlockLimits->UnmapGranularityAlignment4 << 24) | + (BlockLimits->UnmapGranularityAlignment3 << 16) | + (BlockLimits->UnmapGranularityAlignment2 << 8) | + BlockLimits->UnmapGranularityAlignment1; + } + + if (ScsiDiskDevice->EraseBlock.EraseLengthGranularity == 0) { + // + // A value of 0 indicates that the optimal unmap granularity is + // not reported. + // + ScsiDiskDevice->EraseBlock.EraseLengthGranularity = 1; + } + + ScsiDiskDevice->BlockLimitsVpdSupported = TRUE; + } + + FreeAlignedBuffer (BlockLimits, sizeof (EFI_SCSI_BLOCK_LIMITS_VPD_PAGE)); + } + } + + FreeAlignedBuffer (SupportedVpdPages, sizeof (EFI_SCSI_SUPPORTED_VPD_PAGES_VPD_PAGE)); + } + } + + if (!EFI_ERROR (Status)) { + return EFI_SUCCESS; + + } else if (Status == EFI_NOT_READY) { + *NeedRetry = TRUE; + return EFI_DEVICE_ERROR; + + } else if ((Status == EFI_INVALID_PARAMETER) || (Status == EFI_UNSUPPORTED)) { + *NeedRetry = FALSE; + return EFI_DEVICE_ERROR; + } + // + // go ahead to check HostAdapterStatus and TargetStatus + // (EFI_TIMEOUT, EFI_DEVICE_ERROR) + // + + Status = CheckHostAdapterStatus (HostAdapterStatus); + if ((Status == EFI_TIMEOUT) || (Status == EFI_NOT_READY)) { + *NeedRetry = TRUE; + return EFI_DEVICE_ERROR; + } else if (Status == EFI_DEVICE_ERROR) { + // + // reset the scsi channel + // + ScsiDiskDevice->ScsiIo->ResetBus (ScsiDiskDevice->ScsiIo); + *NeedRetry = FALSE; + return EFI_DEVICE_ERROR; + } + + Status = CheckTargetStatus (TargetStatus); + if (Status == EFI_NOT_READY) { + // + // reset the scsi device + // + ScsiDiskDevice->ScsiIo->ResetDevice (ScsiDiskDevice->ScsiIo); + *NeedRetry = TRUE; + return EFI_DEVICE_ERROR; + + } else if (Status == EFI_DEVICE_ERROR) { + *NeedRetry = FALSE; + return EFI_DEVICE_ERROR; + } + + // + // if goes here, meant ScsiInquiryCommand() failed. + // if ScsiDiskRequestSenseKeys() succeeds at last, + // better retry ScsiInquiryCommand(). (by setting *NeedRetry = TRUE) + // + MaxRetry = 3; + for (Index = 0; Index < MaxRetry; Index++) { + Status = ScsiDiskRequestSenseKeys ( + ScsiDiskDevice, + NeedRetry, + &SenseDataArray, + &NumberOfSenseKeys, + TRUE + ); + if (!EFI_ERROR (Status)) { + *NeedRetry = TRUE; + return EFI_DEVICE_ERROR; + } + + if (!*NeedRetry) { + return EFI_DEVICE_ERROR; + } + } + // + // ScsiDiskRequestSenseKeys() failed after several rounds of retry. + // set *NeedRetry = FALSE to avoid the outside caller try again. + // + *NeedRetry = FALSE; + return EFI_DEVICE_ERROR; +} + +/** + To test device. + + When Test Unit Ready command succeeds, retrieve Sense Keys via Request Sense; + When Test Unit Ready command encounters any error caused by host adapter or + target, return error without retrieving Sense Keys. + + @param ScsiDiskDevice The pointer of SCSI_DISK_DEV + @param NeedRetry The pointer of flag indicates try again + @param SenseDataArray The pointer of an array of sense data + @param NumberOfSenseKeys The pointer of the number of sense data array + + @retval EFI_DEVICE_ERROR Indicates that error occurs + @retval EFI_SUCCESS Successfully to test unit + +**/ +EFI_STATUS +ScsiDiskTestUnitReady ( + IN SCSI_DISK_DEV *ScsiDiskDevice, + OUT BOOLEAN *NeedRetry, + OUT EFI_SCSI_SENSE_DATA **SenseDataArray, + OUT UINTN *NumberOfSenseKeys + ) +{ + EFI_STATUS Status; + UINT8 SenseDataLength; + UINT8 HostAdapterStatus; + UINT8 TargetStatus; + UINT8 Index; + UINT8 MaxRetry; + + SenseDataLength = (UINT8) (ScsiDiskDevice->SenseDataNumber * sizeof (EFI_SCSI_SENSE_DATA)); + *NumberOfSenseKeys = 0; + + // + // Parameter 3 and 4: do not require sense data, retrieve it when needed. + // + Status = ScsiTestUnitReadyCommand ( + ScsiDiskDevice->ScsiIo, + SCSI_DISK_TIMEOUT, + ScsiDiskDevice->SenseData, + &SenseDataLength, + &HostAdapterStatus, + &TargetStatus + ); + // + // no need to check HostAdapterStatus and TargetStatus + // + if (Status == EFI_NOT_READY) { + *NeedRetry = TRUE; + return EFI_DEVICE_ERROR; + + } else if ((Status == EFI_INVALID_PARAMETER) || (Status == EFI_UNSUPPORTED)) { + *NeedRetry = FALSE; + return EFI_DEVICE_ERROR; + } + // + // go ahead to check HostAdapterStatus and TargetStatus(in case of EFI_DEVICE_ERROR) + // + + Status = CheckHostAdapterStatus (HostAdapterStatus); + if ((Status == EFI_TIMEOUT) || (Status == EFI_NOT_READY)) { + *NeedRetry = TRUE; + return EFI_DEVICE_ERROR; + + } else if (Status == EFI_DEVICE_ERROR) { + // + // reset the scsi channel + // + ScsiDiskDevice->ScsiIo->ResetBus (ScsiDiskDevice->ScsiIo); + *NeedRetry = FALSE; + return EFI_DEVICE_ERROR; + } + + Status = CheckTargetStatus (TargetStatus); + if (Status == EFI_NOT_READY) { + // + // reset the scsi device + // + ScsiDiskDevice->ScsiIo->ResetDevice (ScsiDiskDevice->ScsiIo); + *NeedRetry = TRUE; + return EFI_DEVICE_ERROR; + + } else if (Status == EFI_DEVICE_ERROR) { + *NeedRetry = FALSE; + return EFI_DEVICE_ERROR; + } + + if (SenseDataLength != 0) { + *NumberOfSenseKeys = SenseDataLength / sizeof (EFI_SCSI_SENSE_DATA); + *SenseDataArray = ScsiDiskDevice->SenseData; + return EFI_SUCCESS; + } + + MaxRetry = 3; + for (Index = 0; Index < MaxRetry; Index++) { + Status = ScsiDiskRequestSenseKeys ( + ScsiDiskDevice, + NeedRetry, + SenseDataArray, + NumberOfSenseKeys, + FALSE + ); + if (!EFI_ERROR (Status)) { + return EFI_SUCCESS; + } + + if (!*NeedRetry) { + return EFI_DEVICE_ERROR; + } + } + // + // ScsiDiskRequestSenseKeys() failed after several rounds of retry. + // set *NeedRetry = FALSE to avoid the outside caller try again. + // + *NeedRetry = FALSE; + return EFI_DEVICE_ERROR; +} + +/** + Parsing Sense Keys which got from request sense command. + + @param ScsiDiskDevice The pointer of SCSI_DISK_DEV + @param SenseData The pointer of EFI_SCSI_SENSE_DATA + @param NumberOfSenseKeys The number of sense key + @param Action The pointer of action which indicates what is need to do next + + @retval EFI_DEVICE_ERROR Indicates that error occurs + @retval EFI_SUCCESS Successfully to complete the parsing + +**/ +EFI_STATUS +DetectMediaParsingSenseKeys ( + OUT SCSI_DISK_DEV *ScsiDiskDevice, + IN EFI_SCSI_SENSE_DATA *SenseData, + IN UINTN NumberOfSenseKeys, + OUT UINTN *Action + ) +{ + BOOLEAN RetryLater; + + // + // Default is to read capacity, unless.. + // + *Action = ACTION_READ_CAPACITY; + + if (NumberOfSenseKeys == 0) { + if (ScsiDiskDevice->BlkIo.Media->MediaPresent == TRUE) { + *Action = ACTION_NO_ACTION; + } + return EFI_SUCCESS; + } + + if (!ScsiDiskHaveSenseKey (SenseData, NumberOfSenseKeys)) { + // + // No Sense Key returned from last submitted command + // + if (ScsiDiskDevice->BlkIo.Media->MediaPresent == TRUE) { + *Action = ACTION_NO_ACTION; + } + return EFI_SUCCESS; + } + + if (ScsiDiskIsNoMedia (SenseData, NumberOfSenseKeys)) { + ScsiDiskDevice->BlkIo.Media->MediaPresent = FALSE; + ScsiDiskDevice->BlkIo.Media->LastBlock = 0; + *Action = ACTION_NO_ACTION; + DEBUG ((EFI_D_VERBOSE, "ScsiDisk: ScsiDiskIsNoMedia\n")); + return EFI_SUCCESS; + } + + if (ScsiDiskIsMediaChange (SenseData, NumberOfSenseKeys)) { + ScsiDiskDevice->BlkIo.Media->MediaId++; + DEBUG ((EFI_D_VERBOSE, "ScsiDisk: ScsiDiskIsMediaChange!\n")); + return EFI_SUCCESS; + } + + if (ScsiDiskIsResetBefore (SenseData, NumberOfSenseKeys)) { + *Action = ACTION_RETRY_COMMAND_LATER; + DEBUG ((EFI_D_VERBOSE, "ScsiDisk: ScsiDiskIsResetBefore!\n")); + return EFI_SUCCESS; + } + + if (ScsiDiskIsMediaError (SenseData, NumberOfSenseKeys)) { + DEBUG ((EFI_D_VERBOSE, "ScsiDisk: ScsiDiskIsMediaError\n")); + *Action = ACTION_RETRY_WITH_BACKOFF_ALGO; + return EFI_DEVICE_ERROR; + } + + if (ScsiDiskIsHardwareError (SenseData, NumberOfSenseKeys)) { + DEBUG ((EFI_D_VERBOSE, "ScsiDisk: ScsiDiskIsHardwareError\n")); + *Action = ACTION_RETRY_WITH_BACKOFF_ALGO; + return EFI_DEVICE_ERROR; + } + + if (!ScsiDiskIsDriveReady (SenseData, NumberOfSenseKeys, &RetryLater)) { + if (RetryLater) { + *Action = ACTION_RETRY_COMMAND_LATER; + DEBUG ((EFI_D_VERBOSE, "ScsiDisk: ScsiDiskDriveNotReady!\n")); + return EFI_SUCCESS; + } + *Action = ACTION_NO_ACTION; + return EFI_DEVICE_ERROR; + } + + *Action = ACTION_RETRY_WITH_BACKOFF_ALGO; + DEBUG ((EFI_D_VERBOSE, "ScsiDisk: Sense Key = 0x%x ASC = 0x%x!\n", SenseData->Sense_Key, SenseData->Addnl_Sense_Code)); + return EFI_SUCCESS; +} + + +/** + Send read capacity command to device and get the device parameter. + + @param ScsiDiskDevice The pointer of SCSI_DISK_DEV + @param NeedRetry The pointer of flag indicates if need a retry + @param SenseDataArray The pointer of an array of sense data + @param NumberOfSenseKeys The number of sense key + + @retval EFI_DEVICE_ERROR Indicates that error occurs + @retval EFI_SUCCESS Successfully to read capacity or sense data is received. + +**/ +EFI_STATUS +ScsiDiskReadCapacity ( + IN OUT SCSI_DISK_DEV *ScsiDiskDevice, + OUT BOOLEAN *NeedRetry, + OUT EFI_SCSI_SENSE_DATA **SenseDataArray, + OUT UINTN *NumberOfSenseKeys + ) +{ + UINT8 HostAdapterStatus; + UINT8 TargetStatus; + EFI_STATUS CommandStatus; + EFI_STATUS Status; + UINT8 Index; + UINT8 MaxRetry; + UINT8 SenseDataLength; + UINT32 DataLength10; + UINT32 DataLength16; + EFI_SCSI_DISK_CAPACITY_DATA *CapacityData10; + EFI_SCSI_DISK_CAPACITY_DATA16 *CapacityData16; + + CapacityData10 = AllocateAlignedBuffer (ScsiDiskDevice, sizeof (EFI_SCSI_DISK_CAPACITY_DATA)); + if (CapacityData10 == NULL) { + *NeedRetry = FALSE; + return EFI_DEVICE_ERROR; + } + CapacityData16 = AllocateAlignedBuffer (ScsiDiskDevice, sizeof (EFI_SCSI_DISK_CAPACITY_DATA16)); + if (CapacityData16 == NULL) { + FreeAlignedBuffer (CapacityData10, sizeof (EFI_SCSI_DISK_CAPACITY_DATA)); + *NeedRetry = FALSE; + return EFI_DEVICE_ERROR; + } + + SenseDataLength = 0; + DataLength10 = sizeof (EFI_SCSI_DISK_CAPACITY_DATA); + DataLength16 = sizeof (EFI_SCSI_DISK_CAPACITY_DATA16); + ZeroMem (CapacityData10, sizeof (EFI_SCSI_DISK_CAPACITY_DATA)); + ZeroMem (CapacityData16, sizeof (EFI_SCSI_DISK_CAPACITY_DATA16)); + + *NumberOfSenseKeys = 0; + *NeedRetry = FALSE; + + // + // submit Read Capacity(10) Command. If it returns capacity of FFFFFFFFh, + // 16 byte command should be used to access large hard disk >2TB + // + CommandStatus = ScsiReadCapacityCommand ( + ScsiDiskDevice->ScsiIo, + SCSI_DISK_TIMEOUT, + NULL, + &SenseDataLength, + &HostAdapterStatus, + &TargetStatus, + (VOID *) CapacityData10, + &DataLength10, + FALSE + ); + + ScsiDiskDevice->Cdb16Byte = FALSE; + if ((!EFI_ERROR (CommandStatus)) && (CapacityData10->LastLba3 == 0xff) && (CapacityData10->LastLba2 == 0xff) && + (CapacityData10->LastLba1 == 0xff) && (CapacityData10->LastLba0 == 0xff)) { + // + // use Read Capacity (16), Read (16) and Write (16) next when hard disk size > 2TB + // + ScsiDiskDevice->Cdb16Byte = TRUE; + // + // submit Read Capacity(16) Command to get parameter LogicalBlocksPerPhysicalBlock + // and LowestAlignedLba + // + CommandStatus = ScsiReadCapacity16Command ( + ScsiDiskDevice->ScsiIo, + SCSI_DISK_TIMEOUT, + NULL, + &SenseDataLength, + &HostAdapterStatus, + &TargetStatus, + (VOID *) CapacityData16, + &DataLength16, + FALSE + ); + } + + // + // no need to check HostAdapterStatus and TargetStatus + // + if (CommandStatus == EFI_SUCCESS) { + GetMediaInfo (ScsiDiskDevice, CapacityData10, CapacityData16); + FreeAlignedBuffer (CapacityData10, sizeof (EFI_SCSI_DISK_CAPACITY_DATA)); + FreeAlignedBuffer (CapacityData16, sizeof (EFI_SCSI_DISK_CAPACITY_DATA16)); + return EFI_SUCCESS; + } + + FreeAlignedBuffer (CapacityData10, sizeof (EFI_SCSI_DISK_CAPACITY_DATA)); + FreeAlignedBuffer (CapacityData16, sizeof (EFI_SCSI_DISK_CAPACITY_DATA16)); + + if (CommandStatus == EFI_NOT_READY) { + *NeedRetry = TRUE; + return EFI_DEVICE_ERROR; + } else if ((CommandStatus == EFI_INVALID_PARAMETER) || (CommandStatus == EFI_UNSUPPORTED)) { + *NeedRetry = FALSE; + return EFI_DEVICE_ERROR; + } + + // + // go ahead to check HostAdapterStatus and TargetStatus + // (EFI_TIMEOUT, EFI_DEVICE_ERROR, EFI_WARN_BUFFER_TOO_SMALL) + // + + Status = CheckHostAdapterStatus (HostAdapterStatus); + if ((Status == EFI_TIMEOUT) || (Status == EFI_NOT_READY)) { + *NeedRetry = TRUE; + return EFI_DEVICE_ERROR; + + } else if (Status == EFI_DEVICE_ERROR) { + // + // reset the scsi channel + // + ScsiDiskDevice->ScsiIo->ResetBus (ScsiDiskDevice->ScsiIo); + *NeedRetry = FALSE; + return EFI_DEVICE_ERROR; + } + + Status = CheckTargetStatus (TargetStatus); + if (Status == EFI_NOT_READY) { + // + // reset the scsi device + // + ScsiDiskDevice->ScsiIo->ResetDevice (ScsiDiskDevice->ScsiIo); + *NeedRetry = TRUE; + return EFI_DEVICE_ERROR; + + } else if (Status == EFI_DEVICE_ERROR) { + *NeedRetry = FALSE; + return EFI_DEVICE_ERROR; + } + + // + // if goes here, meant ScsiReadCapacityCommand() failed. + // if ScsiDiskRequestSenseKeys() succeeds at last, + // better retry ScsiReadCapacityCommand(). (by setting *NeedRetry = TRUE) + // + MaxRetry = 3; + for (Index = 0; Index < MaxRetry; Index++) { + + Status = ScsiDiskRequestSenseKeys ( + ScsiDiskDevice, + NeedRetry, + SenseDataArray, + NumberOfSenseKeys, + TRUE + ); + if (!EFI_ERROR (Status)) { + return EFI_SUCCESS; + } + + if (!*NeedRetry) { + return EFI_DEVICE_ERROR; + } + } + // + // ScsiDiskRequestSenseKeys() failed after several rounds of retry. + // set *NeedRetry = FALSE to avoid the outside caller try again. + // + *NeedRetry = FALSE; + return EFI_DEVICE_ERROR; +} + +/** + Check the HostAdapter status and re-interpret it in EFI_STATUS. + + @param HostAdapterStatus Host Adapter status + + @retval EFI_SUCCESS Host adapter is OK. + @retval EFI_TIMEOUT Timeout. + @retval EFI_NOT_READY Adapter NOT ready. + @retval EFI_DEVICE_ERROR Adapter device error. + +**/ +EFI_STATUS +CheckHostAdapterStatus ( + IN UINT8 HostAdapterStatus + ) +{ + switch (HostAdapterStatus) { + case EFI_EXT_SCSI_STATUS_HOST_ADAPTER_OK: + return EFI_SUCCESS; + + case EFI_EXT_SCSI_STATUS_HOST_ADAPTER_SELECTION_TIMEOUT: + case EFI_EXT_SCSI_STATUS_HOST_ADAPTER_TIMEOUT: + case EFI_EXT_SCSI_STATUS_HOST_ADAPTER_TIMEOUT_COMMAND: + return EFI_TIMEOUT; + + case EFI_EXT_SCSI_STATUS_HOST_ADAPTER_MESSAGE_REJECT: + case EFI_EXT_SCSI_STATUS_HOST_ADAPTER_PARITY_ERROR: + case EFI_EXT_SCSI_STATUS_HOST_ADAPTER_REQUEST_SENSE_FAILED: + case EFI_EXT_SCSI_STATUS_HOST_ADAPTER_DATA_OVERRUN_UNDERRUN: + case EFI_EXT_SCSI_STATUS_HOST_ADAPTER_BUS_RESET: + return EFI_NOT_READY; + + case EFI_EXT_SCSI_STATUS_HOST_ADAPTER_BUS_FREE: + case EFI_EXT_SCSI_STATUS_HOST_ADAPTER_PHASE_ERROR: + return EFI_DEVICE_ERROR; + + default: + return EFI_SUCCESS; + } +} + + +/** + Check the target status and re-interpret it in EFI_STATUS. + + @param TargetStatus Target status + + @retval EFI_NOT_READY Device is NOT ready. + @retval EFI_DEVICE_ERROR + @retval EFI_SUCCESS + +**/ +EFI_STATUS +CheckTargetStatus ( + IN UINT8 TargetStatus + ) +{ + switch (TargetStatus) { + case EFI_EXT_SCSI_STATUS_TARGET_GOOD: + case EFI_EXT_SCSI_STATUS_TARGET_CHECK_CONDITION: + case EFI_EXT_SCSI_STATUS_TARGET_CONDITION_MET: + return EFI_SUCCESS; + + case EFI_EXT_SCSI_STATUS_TARGET_INTERMEDIATE: + case EFI_EXT_SCSI_STATUS_TARGET_INTERMEDIATE_CONDITION_MET: + case EFI_EXT_SCSI_STATUS_TARGET_BUSY: + case EFI_EXT_SCSI_STATUS_TARGET_TASK_SET_FULL: + return EFI_NOT_READY; + + case EFI_EXT_SCSI_STATUS_TARGET_RESERVATION_CONFLICT: + return EFI_DEVICE_ERROR; + + default: + return EFI_SUCCESS; + } +} + + +/** + Retrieve all sense keys from the device. + + When encountering error during the process, if retrieve sense keys before + error encountered, it returns the sense keys with return status set to EFI_SUCCESS, + and NeedRetry set to FALSE; otherwize, return the proper return status. + + @param ScsiDiskDevice The pointer of SCSI_DISK_DEV + @param NeedRetry The pointer of flag indicates if need a retry + @param SenseDataArray The pointer of an array of sense data + @param NumberOfSenseKeys The number of sense key + @param AskResetIfError The flag indicates if need reset when error occurs + + @retval EFI_DEVICE_ERROR Indicates that error occurs + @retval EFI_SUCCESS Successfully to request sense key + +**/ +EFI_STATUS +ScsiDiskRequestSenseKeys ( + IN OUT SCSI_DISK_DEV *ScsiDiskDevice, + OUT BOOLEAN *NeedRetry, + OUT EFI_SCSI_SENSE_DATA **SenseDataArray, + OUT UINTN *NumberOfSenseKeys, + IN BOOLEAN AskResetIfError + ) +{ + EFI_SCSI_SENSE_DATA *PtrSenseData; + UINT8 SenseDataLength; + BOOLEAN SenseReq; + EFI_STATUS Status; + EFI_STATUS FallStatus; + UINT8 HostAdapterStatus; + UINT8 TargetStatus; + + FallStatus = EFI_SUCCESS; + SenseDataLength = (UINT8) sizeof (EFI_SCSI_SENSE_DATA); + + ZeroMem ( + ScsiDiskDevice->SenseData, + sizeof (EFI_SCSI_SENSE_DATA) * (ScsiDiskDevice->SenseDataNumber) + ); + + *NumberOfSenseKeys = 0; + *SenseDataArray = ScsiDiskDevice->SenseData; + Status = EFI_SUCCESS; + PtrSenseData = AllocateAlignedBuffer (ScsiDiskDevice, sizeof (EFI_SCSI_SENSE_DATA)); + if (PtrSenseData == NULL) { + return EFI_DEVICE_ERROR; + } + + for (SenseReq = TRUE; SenseReq;) { + ZeroMem (PtrSenseData, sizeof (EFI_SCSI_SENSE_DATA)); + Status = ScsiRequestSenseCommand ( + ScsiDiskDevice->ScsiIo, + SCSI_DISK_TIMEOUT, + PtrSenseData, + &SenseDataLength, + &HostAdapterStatus, + &TargetStatus + ); + if ((Status == EFI_SUCCESS) || (Status == EFI_WARN_BUFFER_TOO_SMALL)) { + FallStatus = EFI_SUCCESS; + + } else if ((Status == EFI_TIMEOUT) || (Status == EFI_NOT_READY)) { + *NeedRetry = TRUE; + FallStatus = EFI_DEVICE_ERROR; + + } else if ((Status == EFI_INVALID_PARAMETER) || (Status == EFI_UNSUPPORTED)) { + *NeedRetry = FALSE; + FallStatus = EFI_DEVICE_ERROR; + + } else if (Status == EFI_DEVICE_ERROR) { + if (AskResetIfError) { + ScsiDiskDevice->ScsiIo->ResetDevice (ScsiDiskDevice->ScsiIo); + } + + FallStatus = EFI_DEVICE_ERROR; + } + + if (EFI_ERROR (FallStatus)) { + if (*NumberOfSenseKeys != 0) { + *NeedRetry = FALSE; + Status = EFI_SUCCESS; + goto EXIT; + } else { + Status = EFI_DEVICE_ERROR; + goto EXIT; + } + } + + CopyMem (ScsiDiskDevice->SenseData + *NumberOfSenseKeys, PtrSenseData, SenseDataLength); + (*NumberOfSenseKeys) += 1; + + // + // no more sense key or number of sense keys exceeds predefined, + // skip the loop. + // + if ((PtrSenseData->Sense_Key == EFI_SCSI_SK_NO_SENSE) || + (*NumberOfSenseKeys == ScsiDiskDevice->SenseDataNumber)) { + SenseReq = FALSE; + } + } + +EXIT: + FreeAlignedBuffer (PtrSenseData, sizeof (EFI_SCSI_SENSE_DATA)); + return Status; +} + + +/** + Get information from media read capacity command. + + @param ScsiDiskDevice The pointer of SCSI_DISK_DEV + @param Capacity10 The pointer of EFI_SCSI_DISK_CAPACITY_DATA + @param Capacity16 The pointer of EFI_SCSI_DISK_CAPACITY_DATA16 + +**/ +VOID +GetMediaInfo ( + IN OUT SCSI_DISK_DEV *ScsiDiskDevice, + IN EFI_SCSI_DISK_CAPACITY_DATA *Capacity10, + IN EFI_SCSI_DISK_CAPACITY_DATA16 *Capacity16 + ) +{ + UINT8 *Ptr; + + if (!ScsiDiskDevice->Cdb16Byte) { + ScsiDiskDevice->BlkIo.Media->LastBlock = ((UINT32) Capacity10->LastLba3 << 24) | + (Capacity10->LastLba2 << 16) | + (Capacity10->LastLba1 << 8) | + Capacity10->LastLba0; + + ScsiDiskDevice->BlkIo.Media->BlockSize = (Capacity10->BlockSize3 << 24) | + (Capacity10->BlockSize2 << 16) | + (Capacity10->BlockSize1 << 8) | + Capacity10->BlockSize0; + ScsiDiskDevice->BlkIo.Media->LowestAlignedLba = 0; + ScsiDiskDevice->BlkIo.Media->LogicalBlocksPerPhysicalBlock = 0; + if (!ScsiDiskDevice->BlockLimitsVpdSupported) { + ScsiDiskDevice->UnmapInfo.MaxLbaCnt = (UINT32) ScsiDiskDevice->BlkIo.Media->LastBlock; + } + } else { + Ptr = (UINT8*)&ScsiDiskDevice->BlkIo.Media->LastBlock; + *Ptr++ = Capacity16->LastLba0; + *Ptr++ = Capacity16->LastLba1; + *Ptr++ = Capacity16->LastLba2; + *Ptr++ = Capacity16->LastLba3; + *Ptr++ = Capacity16->LastLba4; + *Ptr++ = Capacity16->LastLba5; + *Ptr++ = Capacity16->LastLba6; + *Ptr = Capacity16->LastLba7; + + ScsiDiskDevice->BlkIo.Media->BlockSize = (Capacity16->BlockSize3 << 24) | + (Capacity16->BlockSize2 << 16) | + (Capacity16->BlockSize1 << 8) | + Capacity16->BlockSize0; + + ScsiDiskDevice->BlkIo.Media->LowestAlignedLba = (Capacity16->LowestAlignLogic2 << 8) | + Capacity16->LowestAlignLogic1; + ScsiDiskDevice->BlkIo.Media->LogicalBlocksPerPhysicalBlock = (1 << Capacity16->LogicPerPhysical); + if (!ScsiDiskDevice->BlockLimitsVpdSupported) { + if (ScsiDiskDevice->BlkIo.Media->LastBlock > (UINT32) -1) { + ScsiDiskDevice->UnmapInfo.MaxLbaCnt = (UINT32) -1; + } else { + ScsiDiskDevice->UnmapInfo.MaxLbaCnt = (UINT32) ScsiDiskDevice->BlkIo.Media->LastBlock; + } + } + } + + ScsiDiskDevice->BlkIo.Media->MediaPresent = TRUE; +} + +/** + Parse Inquiry data. + + @param ScsiDiskDevice The pointer of SCSI_DISK_DEV + +**/ +VOID +ParseInquiryData ( + IN OUT SCSI_DISK_DEV *ScsiDiskDevice + ) +{ + ScsiDiskDevice->FixedDevice = (BOOLEAN) ((ScsiDiskDevice->InquiryData.Rmb == 1) ? 0 : 1); + ScsiDiskDevice->BlkIoMedia.RemovableMedia = (BOOLEAN) (!ScsiDiskDevice->FixedDevice); +} + +/** + Read sector from SCSI Disk. + + @param ScsiDiskDevice The pointer of SCSI_DISK_DEV + @param Buffer The buffer to fill in the read out data + @param Lba Logic block address + @param NumberOfBlocks The number of blocks to read + + @retval EFI_DEVICE_ERROR Indicates a device error. + @retval EFI_SUCCESS Operation is successful. + +**/ +EFI_STATUS +ScsiDiskReadSectors ( + IN SCSI_DISK_DEV *ScsiDiskDevice, + OUT VOID *Buffer, + IN EFI_LBA Lba, + IN UINTN NumberOfBlocks + ) +{ + UINTN BlocksRemaining; + UINT8 *PtrBuffer; + UINT32 BlockSize; + UINT32 ByteCount; + UINT32 MaxBlock; + UINT32 SectorCount; + UINT32 NextSectorCount; + UINT64 Timeout; + EFI_STATUS Status; + UINT8 Index; + UINT8 MaxRetry; + BOOLEAN NeedRetry; + + Status = EFI_SUCCESS; + + BlocksRemaining = NumberOfBlocks; + BlockSize = ScsiDiskDevice->BlkIo.Media->BlockSize; + + // + // limit the data bytes that can be transferred by one Read(10) or Read(16) Command + // + if (!ScsiDiskDevice->Cdb16Byte) { + MaxBlock = 0xFFFF; + } else { + MaxBlock = 0xFFFFFFFF; + } + + PtrBuffer = Buffer; + + while (BlocksRemaining > 0) { + + if (BlocksRemaining <= MaxBlock) { + if (!ScsiDiskDevice->Cdb16Byte) { + SectorCount = (UINT16) BlocksRemaining; + } else { + SectorCount = (UINT32) BlocksRemaining; + } + } else { + SectorCount = MaxBlock; + } + + ByteCount = SectorCount * BlockSize; + // + // |------------------------|-----------------|------------------|-----------------| + // | ATA Transfer Mode | Transfer Rate | SCSI Interface | Transfer Rate | + // |------------------------|-----------------|------------------|-----------------| + // | PIO Mode 0 | 3.3Mbytes/sec | SCSI-1 | 5Mbytes/sec | + // |------------------------|-----------------|------------------|-----------------| + // | PIO Mode 1 | 5.2Mbytes/sec | Fast SCSI | 10Mbytes/sec | + // |------------------------|-----------------|------------------|-----------------| + // | PIO Mode 2 | 8.3Mbytes/sec | Fast-Wide SCSI | 20Mbytes/sec | + // |------------------------|-----------------|------------------|-----------------| + // | PIO Mode 3 | 11.1Mbytes/sec | Ultra SCSI | 20Mbytes/sec | + // |------------------------|-----------------|------------------|-----------------| + // | PIO Mode 4 | 16.6Mbytes/sec | Ultra Wide SCSI | 40Mbytes/sec | + // |------------------------|-----------------|------------------|-----------------| + // | Single-word DMA Mode 0 | 2.1Mbytes/sec | Ultra2 SCSI | 40Mbytes/sec | + // |------------------------|-----------------|------------------|-----------------| + // | Single-word DMA Mode 1 | 4.2Mbytes/sec | Ultra2 Wide SCSI | 80Mbytes/sec | + // |------------------------|-----------------|------------------|-----------------| + // | Single-word DMA Mode 2 | 8.4Mbytes/sec | Ultra3 SCSI | 160Mbytes/sec | + // |------------------------|-----------------|------------------|-----------------| + // | Multi-word DMA Mode 0 | 4.2Mbytes/sec | Ultra-320 SCSI | 320Mbytes/sec | + // |------------------------|-----------------|------------------|-----------------| + // | Multi-word DMA Mode 1 | 13.3Mbytes/sec | Ultra-640 SCSI | 640Mbytes/sec | + // |------------------------|-----------------|------------------|-----------------| + // + // As ScsiDisk and ScsiBus driver are used to manage SCSI or ATAPI devices, we have to use + // the lowest transfer rate to calculate the possible maximum timeout value for each operation. + // From the above table, we could know 2.1Mbytes per second is lowest one. + // The timout value is rounded up to nearest integar and here an additional 30s is added + // to follow ATA spec in which it mentioned that the device may take up to 30s to respond + // commands in the Standby/Idle mode. + // + Timeout = EFI_TIMER_PERIOD_SECONDS (ByteCount / 2100000 + 31); + + MaxRetry = 2; + for (Index = 0; Index < MaxRetry; Index++) { + if (!ScsiDiskDevice->Cdb16Byte) { + Status = ScsiDiskRead10 ( + ScsiDiskDevice, + &NeedRetry, + Timeout, + PtrBuffer, + &ByteCount, + (UINT32) Lba, + SectorCount + ); + } else { + Status = ScsiDiskRead16 ( + ScsiDiskDevice, + &NeedRetry, + Timeout, + PtrBuffer, + &ByteCount, + Lba, + SectorCount + ); + } + if (!EFI_ERROR (Status)) { + break; + } + + if (!NeedRetry) { + return EFI_DEVICE_ERROR; + } + + // + // We need to retry. However, if ScsiDiskRead10() or ScsiDiskRead16() has + // lowered ByteCount on output, we must make sure that we lower + // SectorCount accordingly. SectorCount will be encoded in the CDB, and + // it is invalid to request more sectors in the CDB than the entire + // transfer (ie. ByteCount) can carry. + // + // In addition, ByteCount is only expected to go down, or stay unchaged. + // Therefore we don't need to update Timeout: the original timeout should + // accommodate shorter transfers too. + // + NextSectorCount = ByteCount / BlockSize; + if (NextSectorCount < SectorCount) { + SectorCount = NextSectorCount; + // + // Account for any rounding down. + // + ByteCount = SectorCount * BlockSize; + } + } + + if ((Index == MaxRetry) && (Status != EFI_SUCCESS)) { + return EFI_DEVICE_ERROR; + } + + // + // actual transferred sectors + // + SectorCount = ByteCount / BlockSize; + + Lba += SectorCount; + PtrBuffer = PtrBuffer + SectorCount * BlockSize; + BlocksRemaining -= SectorCount; + } + + return EFI_SUCCESS; +} + +/** + Write sector to SCSI Disk. + + @param ScsiDiskDevice The pointer of SCSI_DISK_DEV + @param Buffer The buffer of data to be written into SCSI Disk + @param Lba Logic block address + @param NumberOfBlocks The number of blocks to read + + @retval EFI_DEVICE_ERROR Indicates a device error. + @retval EFI_SUCCESS Operation is successful. + +**/ +EFI_STATUS +ScsiDiskWriteSectors ( + IN SCSI_DISK_DEV *ScsiDiskDevice, + IN VOID *Buffer, + IN EFI_LBA Lba, + IN UINTN NumberOfBlocks + ) +{ + UINTN BlocksRemaining; + UINT8 *PtrBuffer; + UINT32 BlockSize; + UINT32 ByteCount; + UINT32 MaxBlock; + UINT32 SectorCount; + UINT32 NextSectorCount; + UINT64 Timeout; + EFI_STATUS Status; + UINT8 Index; + UINT8 MaxRetry; + BOOLEAN NeedRetry; + + Status = EFI_SUCCESS; + + BlocksRemaining = NumberOfBlocks; + BlockSize = ScsiDiskDevice->BlkIo.Media->BlockSize; + + // + // limit the data bytes that can be transferred by one Read(10) or Read(16) Command + // + if (!ScsiDiskDevice->Cdb16Byte) { + MaxBlock = 0xFFFF; + } else { + MaxBlock = 0xFFFFFFFF; + } + + PtrBuffer = Buffer; + + while (BlocksRemaining > 0) { + + if (BlocksRemaining <= MaxBlock) { + if (!ScsiDiskDevice->Cdb16Byte) { + SectorCount = (UINT16) BlocksRemaining; + } else { + SectorCount = (UINT32) BlocksRemaining; + } + } else { + SectorCount = MaxBlock; + } + + ByteCount = SectorCount * BlockSize; + // + // |------------------------|-----------------|------------------|-----------------| + // | ATA Transfer Mode | Transfer Rate | SCSI Interface | Transfer Rate | + // |------------------------|-----------------|------------------|-----------------| + // | PIO Mode 0 | 3.3Mbytes/sec | SCSI-1 | 5Mbytes/sec | + // |------------------------|-----------------|------------------|-----------------| + // | PIO Mode 1 | 5.2Mbytes/sec | Fast SCSI | 10Mbytes/sec | + // |------------------------|-----------------|------------------|-----------------| + // | PIO Mode 2 | 8.3Mbytes/sec | Fast-Wide SCSI | 20Mbytes/sec | + // |------------------------|-----------------|------------------|-----------------| + // | PIO Mode 3 | 11.1Mbytes/sec | Ultra SCSI | 20Mbytes/sec | + // |------------------------|-----------------|------------------|-----------------| + // | PIO Mode 4 | 16.6Mbytes/sec | Ultra Wide SCSI | 40Mbytes/sec | + // |------------------------|-----------------|------------------|-----------------| + // | Single-word DMA Mode 0 | 2.1Mbytes/sec | Ultra2 SCSI | 40Mbytes/sec | + // |------------------------|-----------------|------------------|-----------------| + // | Single-word DMA Mode 1 | 4.2Mbytes/sec | Ultra2 Wide SCSI | 80Mbytes/sec | + // |------------------------|-----------------|------------------|-----------------| + // | Single-word DMA Mode 2 | 8.4Mbytes/sec | Ultra3 SCSI | 160Mbytes/sec | + // |------------------------|-----------------|------------------|-----------------| + // | Multi-word DMA Mode 0 | 4.2Mbytes/sec | Ultra-320 SCSI | 320Mbytes/sec | + // |------------------------|-----------------|------------------|-----------------| + // | Multi-word DMA Mode 1 | 13.3Mbytes/sec | Ultra-640 SCSI | 640Mbytes/sec | + // |------------------------|-----------------|------------------|-----------------| + // + // As ScsiDisk and ScsiBus driver are used to manage SCSI or ATAPI devices, we have to use + // the lowest transfer rate to calculate the possible maximum timeout value for each operation. + // From the above table, we could know 2.1Mbytes per second is lowest one. + // The timout value is rounded up to nearest integar and here an additional 30s is added + // to follow ATA spec in which it mentioned that the device may take up to 30s to respond + // commands in the Standby/Idle mode. + // + Timeout = EFI_TIMER_PERIOD_SECONDS (ByteCount / 2100000 + 31); + MaxRetry = 2; + for (Index = 0; Index < MaxRetry; Index++) { + if (!ScsiDiskDevice->Cdb16Byte) { + Status = ScsiDiskWrite10 ( + ScsiDiskDevice, + &NeedRetry, + Timeout, + PtrBuffer, + &ByteCount, + (UINT32) Lba, + SectorCount + ); + } else { + Status = ScsiDiskWrite16 ( + ScsiDiskDevice, + &NeedRetry, + Timeout, + PtrBuffer, + &ByteCount, + Lba, + SectorCount + ); + } + if (!EFI_ERROR (Status)) { + break; + } + + if (!NeedRetry) { + return EFI_DEVICE_ERROR; + } + + // + // We need to retry. However, if ScsiDiskWrite10() or ScsiDiskWrite16() + // has lowered ByteCount on output, we must make sure that we lower + // SectorCount accordingly. SectorCount will be encoded in the CDB, and + // it is invalid to request more sectors in the CDB than the entire + // transfer (ie. ByteCount) can carry. + // + // In addition, ByteCount is only expected to go down, or stay unchaged. + // Therefore we don't need to update Timeout: the original timeout should + // accommodate shorter transfers too. + // + NextSectorCount = ByteCount / BlockSize; + if (NextSectorCount < SectorCount) { + SectorCount = NextSectorCount; + // + // Account for any rounding down. + // + ByteCount = SectorCount * BlockSize; + } } - if (!*NeedRetry) { + if ((Index == MaxRetry) && (Status != EFI_SUCCESS)) { return EFI_DEVICE_ERROR; } - } - // - // ScsiDiskRequestSenseKeys() failed after several rounds of retry. - // set *NeedRetry = FALSE to avoid the outside caller try again. - // - *NeedRetry = FALSE; - return EFI_DEVICE_ERROR; -} - -/** - Check the HostAdapter status and re-interpret it in EFI_STATUS. - - @param HostAdapterStatus Host Adapter status - - @retval EFI_SUCCESS Host adapter is OK. - @retval EFI_TIMEOUT Timeout. - @retval EFI_NOT_READY Adapter NOT ready. - @retval EFI_DEVICE_ERROR Adapter device error. - -**/ -EFI_STATUS -CheckHostAdapterStatus ( - IN UINT8 HostAdapterStatus - ) -{ - switch (HostAdapterStatus) { - case EFI_EXT_SCSI_STATUS_HOST_ADAPTER_OK: - return EFI_SUCCESS; - - case EFI_EXT_SCSI_STATUS_HOST_ADAPTER_SELECTION_TIMEOUT: - case EFI_EXT_SCSI_STATUS_HOST_ADAPTER_TIMEOUT: - case EFI_EXT_SCSI_STATUS_HOST_ADAPTER_TIMEOUT_COMMAND: - return EFI_TIMEOUT; - - case EFI_EXT_SCSI_STATUS_HOST_ADAPTER_MESSAGE_REJECT: - case EFI_EXT_SCSI_STATUS_HOST_ADAPTER_PARITY_ERROR: - case EFI_EXT_SCSI_STATUS_HOST_ADAPTER_REQUEST_SENSE_FAILED: - case EFI_EXT_SCSI_STATUS_HOST_ADAPTER_DATA_OVERRUN_UNDERRUN: - case EFI_EXT_SCSI_STATUS_HOST_ADAPTER_BUS_RESET: - return EFI_NOT_READY; - - case EFI_EXT_SCSI_STATUS_HOST_ADAPTER_BUS_FREE: - case EFI_EXT_SCSI_STATUS_HOST_ADAPTER_PHASE_ERROR: - return EFI_DEVICE_ERROR; + // + // actual transferred sectors + // + SectorCount = ByteCount / BlockSize; - default: - return EFI_SUCCESS; + Lba += SectorCount; + PtrBuffer = PtrBuffer + SectorCount * BlockSize; + BlocksRemaining -= SectorCount; } -} + return EFI_SUCCESS; +} /** - Check the target status and re-interpret it in EFI_STATUS. + Asynchronously read sector from SCSI Disk. - @param TargetStatus Target status + @param ScsiDiskDevice The pointer of SCSI_DISK_DEV. + @param Buffer The buffer to fill in the read out data. + @param Lba Logic block address. + @param NumberOfBlocks The number of blocks to read. + @param Token A pointer to the token associated with the + non-blocking read request. - @retval EFI_NOT_READY Device is NOT ready. - @retval EFI_DEVICE_ERROR - @retval EFI_SUCCESS + @retval EFI_INVALID_PARAMETER Token is NULL or Token->Event is NULL. + @retval EFI_DEVICE_ERROR Indicates a device error. + @retval EFI_SUCCESS Operation is successful. **/ EFI_STATUS -CheckTargetStatus ( - IN UINT8 TargetStatus +ScsiDiskAsyncReadSectors ( + IN SCSI_DISK_DEV *ScsiDiskDevice, + OUT VOID *Buffer, + IN EFI_LBA Lba, + IN UINTN NumberOfBlocks, + IN EFI_BLOCK_IO2_TOKEN *Token ) { - switch (TargetStatus) { - case EFI_EXT_SCSI_STATUS_TARGET_GOOD: - case EFI_EXT_SCSI_STATUS_TARGET_CHECK_CONDITION: - case EFI_EXT_SCSI_STATUS_TARGET_CONDITION_MET: - return EFI_SUCCESS; - - case EFI_EXT_SCSI_STATUS_TARGET_INTERMEDIATE: - case EFI_EXT_SCSI_STATUS_TARGET_INTERMEDIATE_CONDITION_MET: - case EFI_EXT_SCSI_STATUS_TARGET_BUSY: - case EFI_EXT_SCSI_STATUS_TARGET_TASK_SET_FULL: - return EFI_NOT_READY; - - case EFI_EXT_SCSI_STATUS_TARGET_RESERVATION_CONFLICT: - return EFI_DEVICE_ERROR; + UINTN BlocksRemaining; + UINT8 *PtrBuffer; + UINT32 BlockSize; + UINT32 ByteCount; + UINT32 MaxBlock; + UINT32 SectorCount; + UINT64 Timeout; + SCSI_BLKIO2_REQUEST *BlkIo2Req; + EFI_STATUS Status; + EFI_TPL OldTpl; - default: - return EFI_SUCCESS; + if ((Token == NULL) || (Token->Event == NULL)) { + return EFI_INVALID_PARAMETER; } -} - -/** - Retrieve all sense keys from the device. - - When encountering error during the process, if retrieve sense keys before - error encountered, it returns the sense keys with return status set to EFI_SUCCESS, - and NeedRetry set to FALSE; otherwize, return the proper return status. + BlkIo2Req = AllocateZeroPool (sizeof (SCSI_BLKIO2_REQUEST)); + if (BlkIo2Req == NULL) { + return EFI_OUT_OF_RESOURCES; + } - @param ScsiDiskDevice The pointer of SCSI_DISK_DEV - @param NeedRetry The pointer of flag indicates if need a retry - @param SenseDataArray The pointer of an array of sense data - @param NumberOfSenseKeys The number of sense key - @param AskResetIfError The flag indicates if need reset when error occurs + BlkIo2Req->Token = Token; - @retval EFI_DEVICE_ERROR Indicates that error occurs - @retval EFI_SUCCESS Successfully to request sense key + OldTpl = gBS->RaiseTPL (TPL_NOTIFY); + InsertTailList (&ScsiDiskDevice->AsyncTaskQueue, &BlkIo2Req->Link); + gBS->RestoreTPL (OldTpl); -**/ -EFI_STATUS -ScsiDiskRequestSenseKeys ( - IN OUT SCSI_DISK_DEV *ScsiDiskDevice, - OUT BOOLEAN *NeedRetry, - OUT EFI_SCSI_SENSE_DATA **SenseDataArray, - OUT UINTN *NumberOfSenseKeys, - IN BOOLEAN AskResetIfError - ) -{ - EFI_SCSI_SENSE_DATA *PtrSenseData; - UINT8 SenseDataLength; - BOOLEAN SenseReq; - EFI_STATUS Status; - EFI_STATUS FallStatus; - UINT8 HostAdapterStatus; - UINT8 TargetStatus; + InitializeListHead (&BlkIo2Req->ScsiRWQueue); - FallStatus = EFI_SUCCESS; - SenseDataLength = (UINT8) sizeof (EFI_SCSI_SENSE_DATA); + Status = EFI_SUCCESS; - ZeroMem ( - ScsiDiskDevice->SenseData, - sizeof (EFI_SCSI_SENSE_DATA) * (ScsiDiskDevice->SenseDataNumber) - ); + BlocksRemaining = NumberOfBlocks; + BlockSize = ScsiDiskDevice->BlkIo.Media->BlockSize; - *NumberOfSenseKeys = 0; - *SenseDataArray = ScsiDiskDevice->SenseData; - Status = EFI_SUCCESS; - PtrSenseData = AllocateAlignedBuffer (ScsiDiskDevice, sizeof (EFI_SCSI_SENSE_DATA)); - if (PtrSenseData == NULL) { - return EFI_DEVICE_ERROR; + // + // Limit the data bytes that can be transferred by one Read(10) or Read(16) + // Command + // + if (!ScsiDiskDevice->Cdb16Byte) { + MaxBlock = 0xFFFF; + } else { + MaxBlock = 0xFFFFFFFF; } - for (SenseReq = TRUE; SenseReq;) { - ZeroMem (PtrSenseData, sizeof (EFI_SCSI_SENSE_DATA)); - Status = ScsiRequestSenseCommand ( - ScsiDiskDevice->ScsiIo, - SCSI_DISK_TIMEOUT, - PtrSenseData, - &SenseDataLength, - &HostAdapterStatus, - &TargetStatus - ); - if ((Status == EFI_SUCCESS) || (Status == EFI_WARN_BUFFER_TOO_SMALL)) { - FallStatus = EFI_SUCCESS; - - } else if ((Status == EFI_TIMEOUT) || (Status == EFI_NOT_READY)) { - *NeedRetry = TRUE; - FallStatus = EFI_DEVICE_ERROR; - - } else if ((Status == EFI_INVALID_PARAMETER) || (Status == EFI_UNSUPPORTED)) { - *NeedRetry = FALSE; - FallStatus = EFI_DEVICE_ERROR; - - } else if (Status == EFI_DEVICE_ERROR) { - if (AskResetIfError) { - ScsiDiskDevice->ScsiIo->ResetDevice (ScsiDiskDevice->ScsiIo); - } - - FallStatus = EFI_DEVICE_ERROR; - } + PtrBuffer = Buffer; - if (EFI_ERROR (FallStatus)) { - if (*NumberOfSenseKeys != 0) { - *NeedRetry = FALSE; - Status = EFI_SUCCESS; - goto EXIT; + while (BlocksRemaining > 0) { + + if (BlocksRemaining <= MaxBlock) { + if (!ScsiDiskDevice->Cdb16Byte) { + SectorCount = (UINT16) BlocksRemaining; } else { - Status = EFI_DEVICE_ERROR; - goto EXIT; + SectorCount = (UINT32) BlocksRemaining; } + } else { + SectorCount = MaxBlock; } - CopyMem (ScsiDiskDevice->SenseData + *NumberOfSenseKeys, PtrSenseData, SenseDataLength); - (*NumberOfSenseKeys) += 1; - + ByteCount = SectorCount * BlockSize; // - // no more sense key or number of sense keys exceeds predefined, - // skip the loop. + // |------------------------|-----------------|------------------|-----------------| + // | ATA Transfer Mode | Transfer Rate | SCSI Interface | Transfer Rate | + // |------------------------|-----------------|------------------|-----------------| + // | PIO Mode 0 | 3.3Mbytes/sec | SCSI-1 | 5Mbytes/sec | + // |------------------------|-----------------|------------------|-----------------| + // | PIO Mode 1 | 5.2Mbytes/sec | Fast SCSI | 10Mbytes/sec | + // |------------------------|-----------------|------------------|-----------------| + // | PIO Mode 2 | 8.3Mbytes/sec | Fast-Wide SCSI | 20Mbytes/sec | + // |------------------------|-----------------|------------------|-----------------| + // | PIO Mode 3 | 11.1Mbytes/sec | Ultra SCSI | 20Mbytes/sec | + // |------------------------|-----------------|------------------|-----------------| + // | PIO Mode 4 | 16.6Mbytes/sec | Ultra Wide SCSI | 40Mbytes/sec | + // |------------------------|-----------------|------------------|-----------------| + // | Single-word DMA Mode 0 | 2.1Mbytes/sec | Ultra2 SCSI | 40Mbytes/sec | + // |------------------------|-----------------|------------------|-----------------| + // | Single-word DMA Mode 1 | 4.2Mbytes/sec | Ultra2 Wide SCSI | 80Mbytes/sec | + // |------------------------|-----------------|------------------|-----------------| + // | Single-word DMA Mode 2 | 8.4Mbytes/sec | Ultra3 SCSI | 160Mbytes/sec | + // |------------------------|-----------------|------------------|-----------------| + // | Multi-word DMA Mode 0 | 4.2Mbytes/sec | Ultra-320 SCSI | 320Mbytes/sec | + // |------------------------|-----------------|------------------|-----------------| + // | Multi-word DMA Mode 1 | 13.3Mbytes/sec | Ultra-640 SCSI | 640Mbytes/sec | + // |------------------------|-----------------|------------------|-----------------| // - if ((PtrSenseData->Sense_Key == EFI_SCSI_SK_NO_SENSE) || - (*NumberOfSenseKeys == ScsiDiskDevice->SenseDataNumber)) { - SenseReq = FALSE; - } - } - -EXIT: - FreeAlignedBuffer (PtrSenseData, sizeof (EFI_SCSI_SENSE_DATA)); - return Status; -} - + // As ScsiDisk and ScsiBus driver are used to manage SCSI or ATAPI devices, + // we have to use the lowest transfer rate to calculate the possible + // maximum timeout value for each operation. + // From the above table, we could know 2.1Mbytes per second is lowest one. + // The timout value is rounded up to nearest integar and here an additional + // 30s is added to follow ATA spec in which it mentioned that the device + // may take up to 30s to respond commands in the Standby/Idle mode. + // + Timeout = EFI_TIMER_PERIOD_SECONDS (ByteCount / 2100000 + 31); -/** - Get information from media read capacity command. + if (!ScsiDiskDevice->Cdb16Byte) { + Status = ScsiDiskAsyncRead10 ( + ScsiDiskDevice, + Timeout, + 0, + PtrBuffer, + ByteCount, + (UINT32) Lba, + SectorCount, + BlkIo2Req, + Token + ); + } else { + Status = ScsiDiskAsyncRead16 ( + ScsiDiskDevice, + Timeout, + 0, + PtrBuffer, + ByteCount, + Lba, + SectorCount, + BlkIo2Req, + Token + ); + } + if (EFI_ERROR (Status)) { + // + // Some devices will return EFI_DEVICE_ERROR or EFI_TIMEOUT when the data + // length of a SCSI I/O command is too large. + // In this case, we retry sending the SCSI command with a data length + // half of its previous value. + // + if ((Status == EFI_DEVICE_ERROR) || (Status == EFI_TIMEOUT)) { + if ((MaxBlock > 1) && (SectorCount > 1)) { + MaxBlock = MIN (MaxBlock, SectorCount) >> 1; + continue; + } + } - @param ScsiDiskDevice The pointer of SCSI_DISK_DEV - @param Capacity10 The pointer of EFI_SCSI_DISK_CAPACITY_DATA - @param Capacity16 The pointer of EFI_SCSI_DISK_CAPACITY_DATA16 + OldTpl = gBS->RaiseTPL (TPL_NOTIFY); + if (IsListEmpty (&BlkIo2Req->ScsiRWQueue)) { + // + // Free the SCSI_BLKIO2_REQUEST structure only when there is no other + // SCSI sub-task running. Otherwise, it will be freed in the callback + // function ScsiDiskNotify(). + // + RemoveEntryList (&BlkIo2Req->Link); + FreePool (BlkIo2Req); + BlkIo2Req = NULL; + gBS->RestoreTPL (OldTpl); -**/ -VOID -GetMediaInfo ( - IN OUT SCSI_DISK_DEV *ScsiDiskDevice, - IN EFI_SCSI_DISK_CAPACITY_DATA *Capacity10, - IN EFI_SCSI_DISK_CAPACITY_DATA16 *Capacity16 - ) -{ - UINT8 *Ptr; + // + // It is safe to return error status to the caller, since there is no + // previous SCSI sub-task executing. + // + Status = EFI_DEVICE_ERROR; + goto Done; + } else { + gBS->RestoreTPL (OldTpl); - if (!ScsiDiskDevice->Cdb16Byte) { - ScsiDiskDevice->BlkIo.Media->LastBlock = (Capacity10->LastLba3 << 24) | - (Capacity10->LastLba2 << 16) | - (Capacity10->LastLba1 << 8) | - Capacity10->LastLba0; - - ScsiDiskDevice->BlkIo.Media->BlockSize = (Capacity10->BlockSize3 << 24) | - (Capacity10->BlockSize2 << 16) | - (Capacity10->BlockSize1 << 8) | - Capacity10->BlockSize0; - ScsiDiskDevice->BlkIo.Media->LowestAlignedLba = 0; - ScsiDiskDevice->BlkIo.Media->LogicalBlocksPerPhysicalBlock = 0; - } else { - Ptr = (UINT8*)&ScsiDiskDevice->BlkIo.Media->LastBlock; - *Ptr++ = Capacity16->LastLba0; - *Ptr++ = Capacity16->LastLba1; - *Ptr++ = Capacity16->LastLba2; - *Ptr++ = Capacity16->LastLba3; - *Ptr++ = Capacity16->LastLba4; - *Ptr++ = Capacity16->LastLba5; - *Ptr++ = Capacity16->LastLba6; - *Ptr = Capacity16->LastLba7; + // + // There are previous SCSI commands still running, EFI_SUCCESS should + // be returned to make sure that the caller does not free resources + // still using by these SCSI commands. + // + Status = EFI_SUCCESS; + goto Done; + } + } - ScsiDiskDevice->BlkIo.Media->BlockSize = (Capacity16->BlockSize3 << 24) | - (Capacity16->BlockSize2 << 16) | - (Capacity16->BlockSize1 << 8) | - Capacity16->BlockSize0; + // + // Sectors submitted for transfer + // + SectorCount = ByteCount / BlockSize; - ScsiDiskDevice->BlkIo.Media->LowestAlignedLba = (Capacity16->LowestAlignLogic2 << 8) | - Capacity16->LowestAlignLogic1; - ScsiDiskDevice->BlkIo.Media->LogicalBlocksPerPhysicalBlock = (1 << Capacity16->LogicPerPhysical); + Lba += SectorCount; + PtrBuffer = PtrBuffer + SectorCount * BlockSize; + BlocksRemaining -= SectorCount; } - ScsiDiskDevice->BlkIo.Media->MediaPresent = TRUE; -} + Status = EFI_SUCCESS; -/** - Parse Inquiry data. +Done: + if (BlkIo2Req != NULL) { + BlkIo2Req->LastScsiRW = TRUE; - @param ScsiDiskDevice The pointer of SCSI_DISK_DEV + OldTpl = gBS->RaiseTPL (TPL_NOTIFY); + if (IsListEmpty (&BlkIo2Req->ScsiRWQueue)) { + RemoveEntryList (&BlkIo2Req->Link); + FreePool (BlkIo2Req); + BlkIo2Req = NULL; -**/ -VOID -ParseInquiryData ( - IN OUT SCSI_DISK_DEV *ScsiDiskDevice - ) -{ - ScsiDiskDevice->FixedDevice = (BOOLEAN) ((ScsiDiskDevice->InquiryData.Rmb == 1) ? 0 : 1); - ScsiDiskDevice->BlkIoMedia.RemovableMedia = (BOOLEAN) (!ScsiDiskDevice->FixedDevice); + gBS->SignalEvent (Token->Event); + } + gBS->RestoreTPL (OldTpl); + } + + return Status; } /** - Read sector from SCSI Disk. + Asynchronously write sector to SCSI Disk. - @param ScsiDiskDevice The pointer of SCSI_DISK_DEV - @param Buffer The buffer to fill in the read out data - @param Lba Logic block address - @param NumberOfBlocks The number of blocks to read + @param ScsiDiskDevice The pointer of SCSI_DISK_DEV. + @param Buffer The buffer of data to be written into SCSI Disk. + @param Lba Logic block address. + @param NumberOfBlocks The number of blocks to read. + @param Token A pointer to the token associated with the + non-blocking read request. + @retval EFI_INVALID_PARAMETER Token is NULL or Token->Event is NULL @retval EFI_DEVICE_ERROR Indicates a device error. @retval EFI_SUCCESS Operation is successful. **/ EFI_STATUS -ScsiDiskReadSectors ( - IN SCSI_DISK_DEV *ScsiDiskDevice, - OUT VOID *Buffer, - IN EFI_LBA Lba, - IN UINTN NumberOfBlocks +ScsiDiskAsyncWriteSectors ( + IN SCSI_DISK_DEV *ScsiDiskDevice, + IN VOID *Buffer, + IN EFI_LBA Lba, + IN UINTN NumberOfBlocks, + IN EFI_BLOCK_IO2_TOKEN *Token ) { - UINTN BlocksRemaining; - UINT8 *PtrBuffer; - UINT32 BlockSize; - UINT32 ByteCount; - UINT32 MaxBlock; - UINT32 SectorCount; - UINT64 Timeout; - EFI_STATUS Status; - UINT8 Index; - UINT8 MaxRetry; - BOOLEAN NeedRetry; + UINTN BlocksRemaining; + UINT8 *PtrBuffer; + UINT32 BlockSize; + UINT32 ByteCount; + UINT32 MaxBlock; + UINT32 SectorCount; + UINT64 Timeout; + SCSI_BLKIO2_REQUEST *BlkIo2Req; + EFI_STATUS Status; + EFI_TPL OldTpl; + + if ((Token == NULL) || (Token->Event == NULL)) { + return EFI_INVALID_PARAMETER; + } + + BlkIo2Req = AllocateZeroPool (sizeof (SCSI_BLKIO2_REQUEST)); + if (BlkIo2Req == NULL) { + return EFI_OUT_OF_RESOURCES; + } + + BlkIo2Req->Token = Token; + + OldTpl = gBS->RaiseTPL (TPL_NOTIFY); + InsertTailList (&ScsiDiskDevice->AsyncTaskQueue, &BlkIo2Req->Link); + gBS->RestoreTPL (OldTpl); + + InitializeListHead (&BlkIo2Req->ScsiRWQueue); Status = EFI_SUCCESS; BlocksRemaining = NumberOfBlocks; BlockSize = ScsiDiskDevice->BlkIo.Media->BlockSize; - + // - // limit the data bytes that can be transferred by one Read(10) or Read(16) Command + // Limit the data bytes that can be transferred by one Read(10) or Read(16) + // Command // if (!ScsiDiskDevice->Cdb16Byte) { MaxBlock = 0xFFFF; @@ -1849,210 +3479,367 @@ ScsiDiskReadSectors ( // | Multi-word DMA Mode 1 | 13.3Mbytes/sec | Ultra-640 SCSI | 640Mbytes/sec | // |------------------------|-----------------|------------------|-----------------| // - // As ScsiDisk and ScsiBus driver are used to manage SCSI or ATAPI devices, we have to use - // the lowest transfer rate to calculate the possible maximum timeout value for each operation. + // As ScsiDisk and ScsiBus driver are used to manage SCSI or ATAPI devices, + // we have to use the lowest transfer rate to calculate the possible + // maximum timeout value for each operation. // From the above table, we could know 2.1Mbytes per second is lowest one. - // The timout value is rounded up to nearest integar and here an additional 30s is added - // to follow ATA spec in which it mentioned that the device may take up to 30s to respond - // commands in the Standby/Idle mode. + // The timout value is rounded up to nearest integar and here an additional + // 30s is added to follow ATA spec in which it mentioned that the device + // may take up to 30s to respond commands in the Standby/Idle mode. // Timeout = EFI_TIMER_PERIOD_SECONDS (ByteCount / 2100000 + 31); - MaxRetry = 2; - for (Index = 0; Index < MaxRetry; Index++) { - if (!ScsiDiskDevice->Cdb16Byte) { - Status = ScsiDiskRead10 ( - ScsiDiskDevice, - &NeedRetry, - Timeout, - PtrBuffer, - &ByteCount, - (UINT32) Lba, - SectorCount - ); - } else { - Status = ScsiDiskRead16 ( - ScsiDiskDevice, - &NeedRetry, - Timeout, - PtrBuffer, - &ByteCount, - Lba, - SectorCount - ); + if (!ScsiDiskDevice->Cdb16Byte) { + Status = ScsiDiskAsyncWrite10 ( + ScsiDiskDevice, + Timeout, + 0, + PtrBuffer, + ByteCount, + (UINT32) Lba, + SectorCount, + BlkIo2Req, + Token + ); + } else { + Status = ScsiDiskAsyncWrite16 ( + ScsiDiskDevice, + Timeout, + 0, + PtrBuffer, + ByteCount, + Lba, + SectorCount, + BlkIo2Req, + Token + ); + } + if (EFI_ERROR (Status)) { + // + // Some devices will return EFI_DEVICE_ERROR or EFI_TIMEOUT when the data + // length of a SCSI I/O command is too large. + // In this case, we retry sending the SCSI command with a data length + // half of its previous value. + // + if ((Status == EFI_DEVICE_ERROR) || (Status == EFI_TIMEOUT)) { + if ((MaxBlock > 1) && (SectorCount > 1)) { + MaxBlock = MIN (MaxBlock, SectorCount) >> 1; + continue; + } } - if (!EFI_ERROR (Status)) { - break; + + OldTpl = gBS->RaiseTPL (TPL_NOTIFY); + if (IsListEmpty (&BlkIo2Req->ScsiRWQueue)) { + // + // Free the SCSI_BLKIO2_REQUEST structure only when there is no other + // SCSI sub-task running. Otherwise, it will be freed in the callback + // function ScsiDiskNotify(). + // + RemoveEntryList (&BlkIo2Req->Link); + FreePool (BlkIo2Req); + BlkIo2Req = NULL; + gBS->RestoreTPL (OldTpl); + + // + // It is safe to return error status to the caller, since there is no + // previous SCSI sub-task executing. + // + Status = EFI_DEVICE_ERROR; + goto Done; + } else { + gBS->RestoreTPL (OldTpl); + + // + // There are previous SCSI commands still running, EFI_SUCCESS should + // be returned to make sure that the caller does not free resources + // still using by these SCSI commands. + // + Status = EFI_SUCCESS; + goto Done; } + } - if (!NeedRetry) { + // + // Sectors submitted for transfer + // + SectorCount = ByteCount / BlockSize; + + Lba += SectorCount; + PtrBuffer = PtrBuffer + SectorCount * BlockSize; + BlocksRemaining -= SectorCount; + } + + Status = EFI_SUCCESS; + +Done: + if (BlkIo2Req != NULL) { + BlkIo2Req->LastScsiRW = TRUE; + + OldTpl = gBS->RaiseTPL (TPL_NOTIFY); + if (IsListEmpty (&BlkIo2Req->ScsiRWQueue)) { + RemoveEntryList (&BlkIo2Req->Link); + FreePool (BlkIo2Req); + BlkIo2Req = NULL; + + gBS->SignalEvent (Token->Event); + } + gBS->RestoreTPL (OldTpl); + } + + return Status; +} + + +/** + Submit Read(10) command. + + @param ScsiDiskDevice The pointer of ScsiDiskDevice + @param NeedRetry The pointer of flag indicates if needs retry if error happens + @param Timeout The time to complete the command + @param DataBuffer The buffer to fill with the read out data + @param DataLength The length of buffer + @param StartLba The start logic block address + @param SectorCount The number of blocks to read + + @return EFI_STATUS is returned by calling ScsiRead10Command(). +**/ +EFI_STATUS +ScsiDiskRead10 ( + IN SCSI_DISK_DEV *ScsiDiskDevice, + OUT BOOLEAN *NeedRetry, + IN UINT64 Timeout, + OUT UINT8 *DataBuffer, + IN OUT UINT32 *DataLength, + IN UINT32 StartLba, + IN UINT32 SectorCount + ) +{ + UINT8 SenseDataLength; + EFI_STATUS Status; + EFI_STATUS ReturnStatus; + UINT8 HostAdapterStatus; + UINT8 TargetStatus; + UINTN Action; + + // + // Implement a backoff algorithem to resolve some compatibility issues that + // some SCSI targets or ATAPI devices couldn't correctly response reading/writing + // big data in a single operation. + // This algorithem will at first try to execute original request. If the request fails + // with media error sense data or else, it will reduce the transfer length to half and + // try again till the operation succeeds or fails with one sector transfer length. + // +BackOff: + *NeedRetry = FALSE; + Action = ACTION_NO_ACTION; + SenseDataLength = (UINT8) (ScsiDiskDevice->SenseDataNumber * sizeof (EFI_SCSI_SENSE_DATA)); + ReturnStatus = ScsiRead10Command ( + ScsiDiskDevice->ScsiIo, + Timeout, + ScsiDiskDevice->SenseData, + &SenseDataLength, + &HostAdapterStatus, + &TargetStatus, + DataBuffer, + DataLength, + StartLba, + SectorCount + ); + + if (ReturnStatus == EFI_NOT_READY || ReturnStatus == EFI_BAD_BUFFER_SIZE) { + *NeedRetry = TRUE; + return EFI_DEVICE_ERROR; + } else if ((ReturnStatus == EFI_INVALID_PARAMETER) || (ReturnStatus == EFI_UNSUPPORTED)) { + *NeedRetry = FALSE; + return ReturnStatus; + } + + // + // go ahead to check HostAdapterStatus and TargetStatus + // (EFI_TIMEOUT, EFI_DEVICE_ERROR, EFI_WARN_BUFFER_TOO_SMALL) + // + Status = CheckHostAdapterStatus (HostAdapterStatus); + if ((Status == EFI_TIMEOUT) || (Status == EFI_NOT_READY)) { + *NeedRetry = TRUE; + return EFI_DEVICE_ERROR; + } else if (Status == EFI_DEVICE_ERROR) { + // + // reset the scsi channel + // + ScsiDiskDevice->ScsiIo->ResetBus (ScsiDiskDevice->ScsiIo); + *NeedRetry = FALSE; + return EFI_DEVICE_ERROR; + } + + Status = CheckTargetStatus (TargetStatus); + if (Status == EFI_NOT_READY) { + // + // reset the scsi device + // + ScsiDiskDevice->ScsiIo->ResetDevice (ScsiDiskDevice->ScsiIo); + *NeedRetry = TRUE; + return EFI_DEVICE_ERROR; + } else if (Status == EFI_DEVICE_ERROR) { + *NeedRetry = FALSE; + return EFI_DEVICE_ERROR; + } + + if ((TargetStatus == EFI_EXT_SCSI_STATUS_TARGET_CHECK_CONDITION) || (EFI_ERROR (ReturnStatus))) { + DEBUG ((EFI_D_ERROR, "ScsiDiskRead10: Check Condition happened!\n")); + Status = DetectMediaParsingSenseKeys (ScsiDiskDevice, ScsiDiskDevice->SenseData, SenseDataLength / sizeof (EFI_SCSI_SENSE_DATA), &Action); + if (Action == ACTION_RETRY_COMMAND_LATER) { + *NeedRetry = TRUE; + return EFI_DEVICE_ERROR; + } else if (Action == ACTION_RETRY_WITH_BACKOFF_ALGO) { + if (SectorCount <= 1) { + // + // Jump out if the operation still fails with one sector transfer length. + // + *NeedRetry = FALSE; return EFI_DEVICE_ERROR; } - - } - - if ((Index == MaxRetry) && (Status != EFI_SUCCESS)) { + // + // Try again with half length if the sense data shows we need to retry. + // + SectorCount >>= 1; + *DataLength = SectorCount * ScsiDiskDevice->BlkIo.Media->BlockSize; + goto BackOff; + } else { + *NeedRetry = FALSE; return EFI_DEVICE_ERROR; } - - // - // actual transferred sectors - // - SectorCount = ByteCount / BlockSize; - - Lba += SectorCount; - PtrBuffer = PtrBuffer + SectorCount * BlockSize; - BlocksRemaining -= SectorCount; } - return EFI_SUCCESS; + return ReturnStatus; } + /** - Write sector to SCSI Disk. + Submit Write(10) Command. - @param ScsiDiskDevice The pointer of SCSI_DISK_DEV - @param Buffer The buffer of data to be written into SCSI Disk - @param Lba Logic block address - @param NumberOfBlocks The number of blocks to read + @param ScsiDiskDevice The pointer of ScsiDiskDevice + @param NeedRetry The pointer of flag indicates if needs retry if error happens + @param Timeout The time to complete the command + @param DataBuffer The buffer to fill with the read out data + @param DataLength The length of buffer + @param StartLba The start logic block address + @param SectorCount The number of blocks to write - @retval EFI_DEVICE_ERROR Indicates a device error. - @retval EFI_SUCCESS Operation is successful. + @return EFI_STATUS is returned by calling ScsiWrite10Command(). **/ EFI_STATUS -ScsiDiskWriteSectors ( - IN SCSI_DISK_DEV *ScsiDiskDevice, - IN VOID *Buffer, - IN EFI_LBA Lba, - IN UINTN NumberOfBlocks +ScsiDiskWrite10 ( + IN SCSI_DISK_DEV *ScsiDiskDevice, + OUT BOOLEAN *NeedRetry, + IN UINT64 Timeout, + IN UINT8 *DataBuffer, + IN OUT UINT32 *DataLength, + IN UINT32 StartLba, + IN UINT32 SectorCount ) { - UINTN BlocksRemaining; - UINT8 *PtrBuffer; - UINT32 BlockSize; - UINT32 ByteCount; - UINT32 MaxBlock; - UINT32 SectorCount; - UINT64 Timeout; - EFI_STATUS Status; - UINT8 Index; - UINT8 MaxRetry; - BOOLEAN NeedRetry; - - Status = EFI_SUCCESS; - - BlocksRemaining = NumberOfBlocks; - BlockSize = ScsiDiskDevice->BlkIo.Media->BlockSize; + EFI_STATUS Status; + EFI_STATUS ReturnStatus; + UINT8 SenseDataLength; + UINT8 HostAdapterStatus; + UINT8 TargetStatus; + UINTN Action; // - // limit the data bytes that can be transferred by one Read(10) or Read(16) Command + // Implement a backoff algorithem to resolve some compatibility issues that + // some SCSI targets or ATAPI devices couldn't correctly response reading/writing + // big data in a single operation. + // This algorithem will at first try to execute original request. If the request fails + // with media error sense data or else, it will reduce the transfer length to half and + // try again till the operation succeeds or fails with one sector transfer length. // - if (!ScsiDiskDevice->Cdb16Byte) { - MaxBlock = 0xFFFF; - } else { - MaxBlock = 0xFFFFFFFF; +BackOff: + *NeedRetry = FALSE; + Action = ACTION_NO_ACTION; + SenseDataLength = (UINT8) (ScsiDiskDevice->SenseDataNumber * sizeof (EFI_SCSI_SENSE_DATA)); + ReturnStatus = ScsiWrite10Command ( + ScsiDiskDevice->ScsiIo, + Timeout, + ScsiDiskDevice->SenseData, + &SenseDataLength, + &HostAdapterStatus, + &TargetStatus, + DataBuffer, + DataLength, + StartLba, + SectorCount + ); + if (ReturnStatus == EFI_NOT_READY || ReturnStatus == EFI_BAD_BUFFER_SIZE) { + *NeedRetry = TRUE; + return EFI_DEVICE_ERROR; + } else if ((ReturnStatus == EFI_INVALID_PARAMETER) || (ReturnStatus == EFI_UNSUPPORTED)) { + *NeedRetry = FALSE; + return ReturnStatus; } - PtrBuffer = Buffer; - - while (BlocksRemaining > 0) { - - if (BlocksRemaining <= MaxBlock) { - if (!ScsiDiskDevice->Cdb16Byte) { - SectorCount = (UINT16) BlocksRemaining; - } else { - SectorCount = (UINT32) BlocksRemaining; - } - } else { - SectorCount = MaxBlock; - } - - ByteCount = SectorCount * BlockSize; + // + // go ahead to check HostAdapterStatus and TargetStatus + // (EFI_TIMEOUT, EFI_DEVICE_ERROR, EFI_WARN_BUFFER_TOO_SMALL) + // + Status = CheckHostAdapterStatus (HostAdapterStatus); + if ((Status == EFI_TIMEOUT) || (Status == EFI_NOT_READY)) { + *NeedRetry = TRUE; + return EFI_DEVICE_ERROR; + } else if (Status == EFI_DEVICE_ERROR) { // - // |------------------------|-----------------|------------------|-----------------| - // | ATA Transfer Mode | Transfer Rate | SCSI Interface | Transfer Rate | - // |------------------------|-----------------|------------------|-----------------| - // | PIO Mode 0 | 3.3Mbytes/sec | SCSI-1 | 5Mbytes/sec | - // |------------------------|-----------------|------------------|-----------------| - // | PIO Mode 1 | 5.2Mbytes/sec | Fast SCSI | 10Mbytes/sec | - // |------------------------|-----------------|------------------|-----------------| - // | PIO Mode 2 | 8.3Mbytes/sec | Fast-Wide SCSI | 20Mbytes/sec | - // |------------------------|-----------------|------------------|-----------------| - // | PIO Mode 3 | 11.1Mbytes/sec | Ultra SCSI | 20Mbytes/sec | - // |------------------------|-----------------|------------------|-----------------| - // | PIO Mode 4 | 16.6Mbytes/sec | Ultra Wide SCSI | 40Mbytes/sec | - // |------------------------|-----------------|------------------|-----------------| - // | Single-word DMA Mode 0 | 2.1Mbytes/sec | Ultra2 SCSI | 40Mbytes/sec | - // |------------------------|-----------------|------------------|-----------------| - // | Single-word DMA Mode 1 | 4.2Mbytes/sec | Ultra2 Wide SCSI | 80Mbytes/sec | - // |------------------------|-----------------|------------------|-----------------| - // | Single-word DMA Mode 2 | 8.4Mbytes/sec | Ultra3 SCSI | 160Mbytes/sec | - // |------------------------|-----------------|------------------|-----------------| - // | Multi-word DMA Mode 0 | 4.2Mbytes/sec | Ultra-320 SCSI | 320Mbytes/sec | - // |------------------------|-----------------|------------------|-----------------| - // | Multi-word DMA Mode 1 | 13.3Mbytes/sec | Ultra-640 SCSI | 640Mbytes/sec | - // |------------------------|-----------------|------------------|-----------------| + // reset the scsi channel // - // As ScsiDisk and ScsiBus driver are used to manage SCSI or ATAPI devices, we have to use - // the lowest transfer rate to calculate the possible maximum timeout value for each operation. - // From the above table, we could know 2.1Mbytes per second is lowest one. - // The timout value is rounded up to nearest integar and here an additional 30s is added - // to follow ATA spec in which it mentioned that the device may take up to 30s to respond - // commands in the Standby/Idle mode. + ScsiDiskDevice->ScsiIo->ResetBus (ScsiDiskDevice->ScsiIo); + *NeedRetry = FALSE; + return EFI_DEVICE_ERROR; + } + + Status = CheckTargetStatus (TargetStatus); + if (Status == EFI_NOT_READY) { // - Timeout = EFI_TIMER_PERIOD_SECONDS (ByteCount / 2100000 + 31); - MaxRetry = 2; - for (Index = 0; Index < MaxRetry; Index++) { - if (!ScsiDiskDevice->Cdb16Byte) { - Status = ScsiDiskWrite10 ( - ScsiDiskDevice, - &NeedRetry, - Timeout, - PtrBuffer, - &ByteCount, - (UINT32) Lba, - SectorCount - ); - } else { - Status = ScsiDiskWrite16 ( - ScsiDiskDevice, - &NeedRetry, - Timeout, - PtrBuffer, - &ByteCount, - Lba, - SectorCount - ); - } - if (!EFI_ERROR (Status)) { - break; - } + // reset the scsi device + // + ScsiDiskDevice->ScsiIo->ResetDevice (ScsiDiskDevice->ScsiIo); + *NeedRetry = TRUE; + return EFI_DEVICE_ERROR; + } else if (Status == EFI_DEVICE_ERROR) { + *NeedRetry = FALSE; + return EFI_DEVICE_ERROR; + } - if (!NeedRetry) { + if ((TargetStatus == EFI_EXT_SCSI_STATUS_TARGET_CHECK_CONDITION) || (EFI_ERROR (ReturnStatus))) { + DEBUG ((EFI_D_ERROR, "ScsiDiskWrite10: Check Condition happened!\n")); + Status = DetectMediaParsingSenseKeys (ScsiDiskDevice, ScsiDiskDevice->SenseData, SenseDataLength / sizeof (EFI_SCSI_SENSE_DATA), &Action); + if (Action == ACTION_RETRY_COMMAND_LATER) { + *NeedRetry = TRUE; + return EFI_DEVICE_ERROR; + } else if (Action == ACTION_RETRY_WITH_BACKOFF_ALGO) { + if (SectorCount <= 1) { + // + // Jump out if the operation still fails with one sector transfer length. + // + *NeedRetry = FALSE; return EFI_DEVICE_ERROR; } - } - - if ((Index == MaxRetry) && (Status != EFI_SUCCESS)) { + // + // Try again with half length if the sense data shows we need to retry. + // + SectorCount >>= 1; + *DataLength = SectorCount * ScsiDiskDevice->BlkIo.Media->BlockSize; + goto BackOff; + } else { + *NeedRetry = FALSE; return EFI_DEVICE_ERROR; } - // - // actual transferred sectors - // - SectorCount = ByteCount / BlockSize; - - Lba += SectorCount; - PtrBuffer = PtrBuffer + SectorCount * BlockSize; - BlocksRemaining -= SectorCount; } - return EFI_SUCCESS; + return ReturnStatus; } /** - Submit Read(10) command. + Submit Read(16) command. @param ScsiDiskDevice The pointer of ScsiDiskDevice @param NeedRetry The pointer of flag indicates if needs retry if error happens @@ -2062,16 +3849,16 @@ ScsiDiskWriteSectors ( @param StartLba The start logic block address @param SectorCount The number of blocks to read - @return EFI_STATUS is returned by calling ScsiRead10Command(). + @return EFI_STATUS is returned by calling ScsiRead16Command(). **/ EFI_STATUS -ScsiDiskRead10 ( +ScsiDiskRead16 ( IN SCSI_DISK_DEV *ScsiDiskDevice, OUT BOOLEAN *NeedRetry, IN UINT64 Timeout, OUT UINT8 *DataBuffer, IN OUT UINT32 *DataLength, - IN UINT32 StartLba, + IN UINT64 StartLba, IN UINT32 SectorCount ) { @@ -2094,7 +3881,7 @@ BackOff: *NeedRetry = FALSE; Action = ACTION_NO_ACTION; SenseDataLength = (UINT8) (ScsiDiskDevice->SenseDataNumber * sizeof (EFI_SCSI_SENSE_DATA)); - ReturnStatus = ScsiRead10Command ( + ReturnStatus = ScsiRead16Command ( ScsiDiskDevice->ScsiIo, Timeout, ScsiDiskDevice->SenseData, @@ -2106,8 +3893,7 @@ BackOff: StartLba, SectorCount ); - - if (ReturnStatus == EFI_NOT_READY) { + if (ReturnStatus == EFI_NOT_READY || ReturnStatus == EFI_BAD_BUFFER_SIZE) { *NeedRetry = TRUE; return EFI_DEVICE_ERROR; } else if ((ReturnStatus == EFI_INVALID_PARAMETER) || (ReturnStatus == EFI_UNSUPPORTED)) { @@ -2146,7 +3932,7 @@ BackOff: } if ((TargetStatus == EFI_EXT_SCSI_STATUS_TARGET_CHECK_CONDITION) || (EFI_ERROR (ReturnStatus))) { - DEBUG ((EFI_D_ERROR, "ScsiDiskRead10: Check Condition happened!\n")); + DEBUG ((EFI_D_ERROR, "ScsiDiskRead16: Check Condition happened!\n")); Status = DetectMediaParsingSenseKeys (ScsiDiskDevice, ScsiDiskDevice->SenseData, SenseDataLength / sizeof (EFI_SCSI_SENSE_DATA), &Action); if (Action == ACTION_RETRY_COMMAND_LATER) { *NeedRetry = TRUE; @@ -2176,7 +3962,7 @@ BackOff: /** - Submit Write(10) Command. + Submit Write(16) Command. @param ScsiDiskDevice The pointer of ScsiDiskDevice @param NeedRetry The pointer of flag indicates if needs retry if error happens @@ -2186,17 +3972,17 @@ BackOff: @param StartLba The start logic block address @param SectorCount The number of blocks to write - @return EFI_STATUS is returned by calling ScsiWrite10Command(). + @return EFI_STATUS is returned by calling ScsiWrite16Command(). **/ EFI_STATUS -ScsiDiskWrite10 ( +ScsiDiskWrite16 ( IN SCSI_DISK_DEV *ScsiDiskDevice, OUT BOOLEAN *NeedRetry, IN UINT64 Timeout, IN UINT8 *DataBuffer, IN OUT UINT32 *DataLength, - IN UINT32 StartLba, + IN UINT64 StartLba, IN UINT32 SectorCount ) { @@ -2219,7 +4005,7 @@ BackOff: *NeedRetry = FALSE; Action = ACTION_NO_ACTION; SenseDataLength = (UINT8) (ScsiDiskDevice->SenseDataNumber * sizeof (EFI_SCSI_SENSE_DATA)); - ReturnStatus = ScsiWrite10Command ( + ReturnStatus = ScsiWrite16Command ( ScsiDiskDevice->ScsiIo, Timeout, ScsiDiskDevice->SenseData, @@ -2231,7 +4017,7 @@ BackOff: StartLba, SectorCount ); - if (ReturnStatus == EFI_NOT_READY) { + if (ReturnStatus == EFI_NOT_READY || ReturnStatus == EFI_BAD_BUFFER_SIZE) { *NeedRetry = TRUE; return EFI_DEVICE_ERROR; } else if ((ReturnStatus == EFI_INVALID_PARAMETER) || (ReturnStatus == EFI_UNSUPPORTED)) { @@ -2270,7 +4056,7 @@ BackOff: } if ((TargetStatus == EFI_EXT_SCSI_STATUS_TARGET_CHECK_CONDITION) || (EFI_ERROR (ReturnStatus))) { - DEBUG ((EFI_D_ERROR, "ScsiDiskWrite10: Check Condition happened!\n")); + DEBUG ((EFI_D_ERROR, "ScsiDiskWrite16: Check Condition happened!\n")); Status = DetectMediaParsingSenseKeys (ScsiDiskDevice, ScsiDiskDevice->SenseData, SenseDataLength / sizeof (EFI_SCSI_SENSE_DATA), &Action); if (Action == ACTION_RETRY_COMMAND_LATER) { *NeedRetry = TRUE; @@ -2300,249 +4086,758 @@ BackOff: /** - Submit Read(16) command. + Internal helper notify function in which determine whether retry of a SCSI + Read/Write command is needed and signal the event passed from Block I/O(2) if + the SCSI I/O operation completes. - @param ScsiDiskDevice The pointer of ScsiDiskDevice - @param NeedRetry The pointer of flag indicates if needs retry if error happens - @param Timeout The time to complete the command - @param DataBuffer The buffer to fill with the read out data - @param DataLength The length of buffer - @param StartLba The start logic block address - @param SectorCount The number of blocks to read + @param Event The instance of EFI_EVENT. + @param Context The parameter passed in. + +**/ +VOID +EFIAPI +ScsiDiskNotify ( + IN EFI_EVENT Event, + IN VOID *Context + ) +{ + EFI_STATUS Status; + SCSI_ASYNC_RW_REQUEST *Request; + SCSI_DISK_DEV *ScsiDiskDevice; + EFI_BLOCK_IO2_TOKEN *Token; + UINTN Action; + UINT32 OldDataLength; + UINT32 OldSectorCount; + UINT8 MaxRetry; + + gBS->CloseEvent (Event); + + Request = (SCSI_ASYNC_RW_REQUEST *) Context; + ScsiDiskDevice = Request->ScsiDiskDevice; + Token = Request->BlkIo2Req->Token; + OldDataLength = Request->DataLength; + OldSectorCount = Request->SectorCount; + MaxRetry = 2; + + // + // If previous sub-tasks already fails, no need to process this sub-task. + // + if (Token->TransactionStatus != EFI_SUCCESS) { + goto Exit; + } + + // + // Check HostAdapterStatus and TargetStatus + // (EFI_TIMEOUT, EFI_DEVICE_ERROR, EFI_WARN_BUFFER_TOO_SMALL) + // + Status = CheckHostAdapterStatus (Request->HostAdapterStatus); + if ((Status == EFI_TIMEOUT) || (Status == EFI_NOT_READY)) { + if (++Request->TimesRetry > MaxRetry) { + Token->TransactionStatus = EFI_DEVICE_ERROR; + goto Exit; + } else { + goto Retry; + } + } else if (Status == EFI_DEVICE_ERROR) { + // + // reset the scsi channel + // + ScsiDiskDevice->ScsiIo->ResetBus (ScsiDiskDevice->ScsiIo); + Token->TransactionStatus = EFI_DEVICE_ERROR; + goto Exit; + } + + Status = CheckTargetStatus (Request->TargetStatus); + if (Status == EFI_NOT_READY) { + // + // reset the scsi device + // + ScsiDiskDevice->ScsiIo->ResetDevice (ScsiDiskDevice->ScsiIo); + if (++Request->TimesRetry > MaxRetry) { + Token->TransactionStatus = EFI_DEVICE_ERROR; + goto Exit; + } else { + goto Retry; + } + } else if (Status == EFI_DEVICE_ERROR) { + Token->TransactionStatus = EFI_DEVICE_ERROR; + goto Exit; + } + + if (Request->TargetStatus == EFI_EXT_SCSI_STATUS_TARGET_CHECK_CONDITION) { + DEBUG ((EFI_D_ERROR, "ScsiDiskNotify: Check Condition happened!\n")); + + Status = DetectMediaParsingSenseKeys ( + ScsiDiskDevice, + Request->SenseData, + Request->SenseDataLength / sizeof (EFI_SCSI_SENSE_DATA), + &Action + ); + if (Action == ACTION_RETRY_COMMAND_LATER) { + if (++Request->TimesRetry > MaxRetry) { + Token->TransactionStatus = EFI_DEVICE_ERROR; + goto Exit; + } else { + goto Retry; + } + } else if (Action == ACTION_RETRY_WITH_BACKOFF_ALGO) { + if (Request->SectorCount <= 1) { + // + // Jump out if the operation still fails with one sector transfer + // length. + // + Token->TransactionStatus = EFI_DEVICE_ERROR; + goto Exit; + } + // + // Try again with two half length request if the sense data shows we need + // to retry. + // + Request->SectorCount >>= 1; + Request->DataLength = Request->SectorCount * ScsiDiskDevice->BlkIo.Media->BlockSize; + Request->TimesRetry = 0; + + goto Retry; + } else { + Token->TransactionStatus = EFI_DEVICE_ERROR; + goto Exit; + } + } + + // + // This sub-task succeeds, no need to retry. + // + goto Exit; + +Retry: + if (Request->InBuffer != NULL) { + // + // SCSI read command + // + if (!ScsiDiskDevice->Cdb16Byte) { + Status = ScsiDiskAsyncRead10 ( + ScsiDiskDevice, + Request->Timeout, + Request->TimesRetry, + Request->InBuffer, + Request->DataLength, + (UINT32) Request->StartLba, + Request->SectorCount, + Request->BlkIo2Req, + Token + ); + } else { + Status = ScsiDiskAsyncRead16 ( + ScsiDiskDevice, + Request->Timeout, + Request->TimesRetry, + Request->InBuffer, + Request->DataLength, + Request->StartLba, + Request->SectorCount, + Request->BlkIo2Req, + Token + ); + } + + if (EFI_ERROR (Status)) { + Token->TransactionStatus = EFI_DEVICE_ERROR; + goto Exit; + } else if (OldSectorCount != Request->SectorCount) { + // + // Original sub-task will be split into two new sub-tasks with smaller + // DataLength + // + if (!ScsiDiskDevice->Cdb16Byte) { + Status = ScsiDiskAsyncRead10 ( + ScsiDiskDevice, + Request->Timeout, + 0, + Request->InBuffer + Request->SectorCount * ScsiDiskDevice->BlkIo.Media->BlockSize, + OldDataLength - Request->DataLength, + (UINT32) Request->StartLba + Request->SectorCount, + OldSectorCount - Request->SectorCount, + Request->BlkIo2Req, + Token + ); + } else { + Status = ScsiDiskAsyncRead16 ( + ScsiDiskDevice, + Request->Timeout, + 0, + Request->InBuffer + Request->SectorCount * ScsiDiskDevice->BlkIo.Media->BlockSize, + OldDataLength - Request->DataLength, + Request->StartLba + Request->SectorCount, + OldSectorCount - Request->SectorCount, + Request->BlkIo2Req, + Token + ); + } + if (EFI_ERROR (Status)) { + Token->TransactionStatus = EFI_DEVICE_ERROR; + goto Exit; + } + } + } else { + // + // SCSI write command + // + if (!ScsiDiskDevice->Cdb16Byte) { + Status = ScsiDiskAsyncWrite10 ( + ScsiDiskDevice, + Request->Timeout, + Request->TimesRetry, + Request->OutBuffer, + Request->DataLength, + (UINT32) Request->StartLba, + Request->SectorCount, + Request->BlkIo2Req, + Token + ); + } else { + Status = ScsiDiskAsyncWrite16 ( + ScsiDiskDevice, + Request->Timeout, + Request->TimesRetry, + Request->OutBuffer, + Request->DataLength, + Request->StartLba, + Request->SectorCount, + Request->BlkIo2Req, + Token + ); + } + + if (EFI_ERROR (Status)) { + Token->TransactionStatus = EFI_DEVICE_ERROR; + goto Exit; + } else if (OldSectorCount != Request->SectorCount) { + // + // Original sub-task will be split into two new sub-tasks with smaller + // DataLength + // + if (!ScsiDiskDevice->Cdb16Byte) { + Status = ScsiDiskAsyncWrite10 ( + ScsiDiskDevice, + Request->Timeout, + 0, + Request->OutBuffer + Request->SectorCount * ScsiDiskDevice->BlkIo.Media->BlockSize, + OldDataLength - Request->DataLength, + (UINT32) Request->StartLba + Request->SectorCount, + OldSectorCount - Request->SectorCount, + Request->BlkIo2Req, + Token + ); + } else { + Status = ScsiDiskAsyncWrite16 ( + ScsiDiskDevice, + Request->Timeout, + 0, + Request->OutBuffer + Request->SectorCount * ScsiDiskDevice->BlkIo.Media->BlockSize, + OldDataLength - Request->DataLength, + Request->StartLba + Request->SectorCount, + OldSectorCount - Request->SectorCount, + Request->BlkIo2Req, + Token + ); + } + if (EFI_ERROR (Status)) { + Token->TransactionStatus = EFI_DEVICE_ERROR; + goto Exit; + } + } + } + +Exit: + RemoveEntryList (&Request->Link); + if ((IsListEmpty (&Request->BlkIo2Req->ScsiRWQueue)) && + (Request->BlkIo2Req->LastScsiRW)) { + // + // The last SCSI R/W command of a BlockIo2 request completes + // + RemoveEntryList (&Request->BlkIo2Req->Link); + FreePool (Request->BlkIo2Req); // Should be freed only once + gBS->SignalEvent (Token->Event); + } + + FreePool (Request->SenseData); + FreePool (Request); +} + + +/** + Submit Async Read(10) command. + + @param ScsiDiskDevice The pointer of ScsiDiskDevice. + @param Timeout The time to complete the command. + @param TimesRetry The number of times the command has been retried. + @param DataBuffer The buffer to fill with the read out data. + @param DataLength The length of buffer. + @param StartLba The start logic block address. + @param SectorCount The number of blocks to read. + @param BlkIo2Req The upstream BlockIo2 request. + @param Token The pointer to the token associated with the + non-blocking read request. + + @retval EFI_OUT_OF_RESOURCES The request could not be completed due to a + lack of resources. + @return others Status returned by calling + ScsiRead10CommandEx(). - @return EFI_STATUS is returned by calling ScsiRead16Command(). **/ EFI_STATUS -ScsiDiskRead16 ( +ScsiDiskAsyncRead10 ( IN SCSI_DISK_DEV *ScsiDiskDevice, - OUT BOOLEAN *NeedRetry, IN UINT64 Timeout, + IN UINT8 TimesRetry, OUT UINT8 *DataBuffer, - IN OUT UINT32 *DataLength, - IN UINT64 StartLba, - IN UINT32 SectorCount + IN UINT32 DataLength, + IN UINT32 StartLba, + IN UINT32 SectorCount, + IN OUT SCSI_BLKIO2_REQUEST *BlkIo2Req, + IN EFI_BLOCK_IO2_TOKEN *Token ) { - UINT8 SenseDataLength; - EFI_STATUS Status; - EFI_STATUS ReturnStatus; - UINT8 HostAdapterStatus; - UINT8 TargetStatus; - UINTN Action; + EFI_STATUS Status; + SCSI_ASYNC_RW_REQUEST *Request; + EFI_EVENT AsyncIoEvent; + EFI_TPL OldTpl; + + AsyncIoEvent = NULL; + + Request = AllocateZeroPool (sizeof (SCSI_ASYNC_RW_REQUEST)); + if (Request == NULL) { + return EFI_OUT_OF_RESOURCES; + } + + OldTpl = gBS->RaiseTPL (TPL_NOTIFY); + InsertTailList (&BlkIo2Req->ScsiRWQueue, &Request->Link); + gBS->RestoreTPL (OldTpl); + + Request->SenseDataLength = (UINT8) (6 * sizeof (EFI_SCSI_SENSE_DATA)); + Request->SenseData = AllocateZeroPool (Request->SenseDataLength); + if (Request->SenseData == NULL) { + Status = EFI_OUT_OF_RESOURCES; + goto ErrorExit; + } + + Request->ScsiDiskDevice = ScsiDiskDevice; + Request->Timeout = Timeout; + Request->TimesRetry = TimesRetry; + Request->InBuffer = DataBuffer; + Request->DataLength = DataLength; + Request->StartLba = StartLba; + Request->SectorCount = SectorCount; + Request->BlkIo2Req = BlkIo2Req; // - // Implement a backoff algorithem to resolve some compatibility issues that - // some SCSI targets or ATAPI devices couldn't correctly response reading/writing - // big data in a single operation. - // This algorithem will at first try to execute original request. If the request fails - // with media error sense data or else, it will reduce the transfer length to half and - // try again till the operation succeeds or fails with one sector transfer length. + // Create Event // -BackOff: - *NeedRetry = FALSE; - Action = ACTION_NO_ACTION; - SenseDataLength = (UINT8) (ScsiDiskDevice->SenseDataNumber * sizeof (EFI_SCSI_SENSE_DATA)); - ReturnStatus = ScsiRead16Command ( - ScsiDiskDevice->ScsiIo, - Timeout, - ScsiDiskDevice->SenseData, - &SenseDataLength, - &HostAdapterStatus, - &TargetStatus, - DataBuffer, - DataLength, - StartLba, - SectorCount - ); - if (ReturnStatus == EFI_NOT_READY) { - *NeedRetry = TRUE; - return EFI_DEVICE_ERROR; - } else if ((ReturnStatus == EFI_INVALID_PARAMETER) || (ReturnStatus == EFI_UNSUPPORTED)) { - *NeedRetry = FALSE; - return ReturnStatus; + Status = gBS->CreateEvent ( + EVT_NOTIFY_SIGNAL, + TPL_NOTIFY, + ScsiDiskNotify, + Request, + &AsyncIoEvent + ); + if (EFI_ERROR(Status)) { + goto ErrorExit; + } + + Status = ScsiRead10CommandEx ( + ScsiDiskDevice->ScsiIo, + Request->Timeout, + Request->SenseData, + &Request->SenseDataLength, + &Request->HostAdapterStatus, + &Request->TargetStatus, + Request->InBuffer, + &Request->DataLength, + (UINT32) Request->StartLba, + Request->SectorCount, + AsyncIoEvent + ); + if (EFI_ERROR(Status)) { + goto ErrorExit; + } + + return EFI_SUCCESS; + +ErrorExit: + if (AsyncIoEvent != NULL) { + gBS->CloseEvent (AsyncIoEvent); + } + + if (Request != NULL) { + if (Request->SenseData != NULL) { + FreePool (Request->SenseData); + } + + OldTpl = gBS->RaiseTPL (TPL_NOTIFY); + RemoveEntryList (&Request->Link); + gBS->RestoreTPL (OldTpl); + + FreePool (Request); + } + + return Status; +} + + +/** + Submit Async Write(10) command. + + @param ScsiDiskDevice The pointer of ScsiDiskDevice. + @param Timeout The time to complete the command. + @param TimesRetry The number of times the command has been retried. + @param DataBuffer The buffer contains the data to write. + @param DataLength The length of buffer. + @param StartLba The start logic block address. + @param SectorCount The number of blocks to write. + @param BlkIo2Req The upstream BlockIo2 request. + @param Token The pointer to the token associated with the + non-blocking read request. + + @retval EFI_OUT_OF_RESOURCES The request could not be completed due to a + lack of resources. + @return others Status returned by calling + ScsiWrite10CommandEx(). + +**/ +EFI_STATUS +ScsiDiskAsyncWrite10 ( + IN SCSI_DISK_DEV *ScsiDiskDevice, + IN UINT64 Timeout, + IN UINT8 TimesRetry, + IN UINT8 *DataBuffer, + IN UINT32 DataLength, + IN UINT32 StartLba, + IN UINT32 SectorCount, + IN OUT SCSI_BLKIO2_REQUEST *BlkIo2Req, + IN EFI_BLOCK_IO2_TOKEN *Token + ) +{ + EFI_STATUS Status; + SCSI_ASYNC_RW_REQUEST *Request; + EFI_EVENT AsyncIoEvent; + EFI_TPL OldTpl; + + AsyncIoEvent = NULL; + + Request = AllocateZeroPool (sizeof (SCSI_ASYNC_RW_REQUEST)); + if (Request == NULL) { + return EFI_OUT_OF_RESOURCES; + } + + OldTpl = gBS->RaiseTPL (TPL_NOTIFY); + InsertTailList (&BlkIo2Req->ScsiRWQueue, &Request->Link); + gBS->RestoreTPL (OldTpl); + + Request->SenseDataLength = (UINT8) (6 * sizeof (EFI_SCSI_SENSE_DATA)); + Request->SenseData = AllocateZeroPool (Request->SenseDataLength); + if (Request->SenseData == NULL) { + Status = EFI_OUT_OF_RESOURCES; + goto ErrorExit; + } + + Request->ScsiDiskDevice = ScsiDiskDevice; + Request->Timeout = Timeout; + Request->TimesRetry = TimesRetry; + Request->OutBuffer = DataBuffer; + Request->DataLength = DataLength; + Request->StartLba = StartLba; + Request->SectorCount = SectorCount; + Request->BlkIo2Req = BlkIo2Req; + + // + // Create Event + // + Status = gBS->CreateEvent ( + EVT_NOTIFY_SIGNAL, + TPL_NOTIFY, + ScsiDiskNotify, + Request, + &AsyncIoEvent + ); + if (EFI_ERROR(Status)) { + goto ErrorExit; + } + + Status = ScsiWrite10CommandEx ( + ScsiDiskDevice->ScsiIo, + Request->Timeout, + Request->SenseData, + &Request->SenseDataLength, + &Request->HostAdapterStatus, + &Request->TargetStatus, + Request->OutBuffer, + &Request->DataLength, + (UINT32) Request->StartLba, + Request->SectorCount, + AsyncIoEvent + ); + if (EFI_ERROR(Status)) { + goto ErrorExit; + } + + return EFI_SUCCESS; + +ErrorExit: + if (AsyncIoEvent != NULL) { + gBS->CloseEvent (AsyncIoEvent); + } + + if (Request != NULL) { + if (Request->SenseData != NULL) { + FreePool (Request->SenseData); + } + + OldTpl = gBS->RaiseTPL (TPL_NOTIFY); + RemoveEntryList (&Request->Link); + gBS->RestoreTPL (OldTpl); + + FreePool (Request); + } + + return Status; +} + + +/** + Submit Async Read(16) command. + + @param ScsiDiskDevice The pointer of ScsiDiskDevice. + @param Timeout The time to complete the command. + @param TimesRetry The number of times the command has been retried. + @param DataBuffer The buffer to fill with the read out data. + @param DataLength The length of buffer. + @param StartLba The start logic block address. + @param SectorCount The number of blocks to read. + @param BlkIo2Req The upstream BlockIo2 request. + @param Token The pointer to the token associated with the + non-blocking read request. + + @retval EFI_OUT_OF_RESOURCES The request could not be completed due to a + lack of resources. + @return others Status returned by calling + ScsiRead16CommandEx(). + +**/ +EFI_STATUS +ScsiDiskAsyncRead16 ( + IN SCSI_DISK_DEV *ScsiDiskDevice, + IN UINT64 Timeout, + IN UINT8 TimesRetry, + OUT UINT8 *DataBuffer, + IN UINT32 DataLength, + IN UINT64 StartLba, + IN UINT32 SectorCount, + IN OUT SCSI_BLKIO2_REQUEST *BlkIo2Req, + IN EFI_BLOCK_IO2_TOKEN *Token + ) +{ + EFI_STATUS Status; + SCSI_ASYNC_RW_REQUEST *Request; + EFI_EVENT AsyncIoEvent; + EFI_TPL OldTpl; + + AsyncIoEvent = NULL; + + Request = AllocateZeroPool (sizeof (SCSI_ASYNC_RW_REQUEST)); + if (Request == NULL) { + return EFI_OUT_OF_RESOURCES; } + OldTpl = gBS->RaiseTPL (TPL_NOTIFY); + InsertTailList (&BlkIo2Req->ScsiRWQueue, &Request->Link); + gBS->RestoreTPL (OldTpl); + + Request->SenseDataLength = (UINT8) (6 * sizeof (EFI_SCSI_SENSE_DATA)); + Request->SenseData = AllocateZeroPool (Request->SenseDataLength); + if (Request->SenseData == NULL) { + Status = EFI_OUT_OF_RESOURCES; + goto ErrorExit; + } + + Request->ScsiDiskDevice = ScsiDiskDevice; + Request->Timeout = Timeout; + Request->TimesRetry = TimesRetry; + Request->InBuffer = DataBuffer; + Request->DataLength = DataLength; + Request->StartLba = StartLba; + Request->SectorCount = SectorCount; + Request->BlkIo2Req = BlkIo2Req; + // - // go ahead to check HostAdapterStatus and TargetStatus - // (EFI_TIMEOUT, EFI_DEVICE_ERROR, EFI_WARN_BUFFER_TOO_SMALL) + // Create Event // - Status = CheckHostAdapterStatus (HostAdapterStatus); - if ((Status == EFI_TIMEOUT) || (Status == EFI_NOT_READY)) { - *NeedRetry = TRUE; - return EFI_DEVICE_ERROR; - } else if (Status == EFI_DEVICE_ERROR) { - // - // reset the scsi channel - // - ScsiDiskDevice->ScsiIo->ResetBus (ScsiDiskDevice->ScsiIo); - *NeedRetry = FALSE; - return EFI_DEVICE_ERROR; + Status = gBS->CreateEvent ( + EVT_NOTIFY_SIGNAL, + TPL_NOTIFY, + ScsiDiskNotify, + Request, + &AsyncIoEvent + ); + if (EFI_ERROR(Status)) { + goto ErrorExit; } - Status = CheckTargetStatus (TargetStatus); - if (Status == EFI_NOT_READY) { - // - // reset the scsi device - // - ScsiDiskDevice->ScsiIo->ResetDevice (ScsiDiskDevice->ScsiIo); - *NeedRetry = TRUE; - return EFI_DEVICE_ERROR; - } else if (Status == EFI_DEVICE_ERROR) { - *NeedRetry = FALSE; - return EFI_DEVICE_ERROR; + Status = ScsiRead16CommandEx ( + ScsiDiskDevice->ScsiIo, + Request->Timeout, + Request->SenseData, + &Request->SenseDataLength, + &Request->HostAdapterStatus, + &Request->TargetStatus, + Request->InBuffer, + &Request->DataLength, + Request->StartLba, + Request->SectorCount, + AsyncIoEvent + ); + if (EFI_ERROR(Status)) { + goto ErrorExit; } - if ((TargetStatus == EFI_EXT_SCSI_STATUS_TARGET_CHECK_CONDITION) || (EFI_ERROR (ReturnStatus))) { - DEBUG ((EFI_D_ERROR, "ScsiDiskRead16: Check Condition happened!\n")); - Status = DetectMediaParsingSenseKeys (ScsiDiskDevice, ScsiDiskDevice->SenseData, SenseDataLength / sizeof (EFI_SCSI_SENSE_DATA), &Action); - if (Action == ACTION_RETRY_COMMAND_LATER) { - *NeedRetry = TRUE; - return EFI_DEVICE_ERROR; - } else if (Action == ACTION_RETRY_WITH_BACKOFF_ALGO) { - if (SectorCount <= 1) { - // - // Jump out if the operation still fails with one sector transfer length. - // - *NeedRetry = FALSE; - return EFI_DEVICE_ERROR; - } - // - // Try again with half length if the sense data shows we need to retry. - // - SectorCount >>= 1; - *DataLength = SectorCount * ScsiDiskDevice->BlkIo.Media->BlockSize; - goto BackOff; - } else { - *NeedRetry = FALSE; - return EFI_DEVICE_ERROR; + return EFI_SUCCESS; + +ErrorExit: + if (AsyncIoEvent != NULL) { + gBS->CloseEvent (AsyncIoEvent); + } + + if (Request != NULL) { + if (Request->SenseData != NULL) { + FreePool (Request->SenseData); } + + OldTpl = gBS->RaiseTPL (TPL_NOTIFY); + RemoveEntryList (&Request->Link); + gBS->RestoreTPL (OldTpl); + + FreePool (Request); } - return ReturnStatus; + return Status; } /** - Submit Write(16) Command. - - @param ScsiDiskDevice The pointer of ScsiDiskDevice - @param NeedRetry The pointer of flag indicates if needs retry if error happens - @param Timeout The time to complete the command - @param DataBuffer The buffer to fill with the read out data - @param DataLength The length of buffer - @param StartLba The start logic block address - @param SectorCount The number of blocks to write - - @return EFI_STATUS is returned by calling ScsiWrite16Command(). + Submit Async Write(16) command. + + @param ScsiDiskDevice The pointer of ScsiDiskDevice. + @param Timeout The time to complete the command. + @param TimesRetry The number of times the command has been retried. + @param DataBuffer The buffer contains the data to write. + @param DataLength The length of buffer. + @param StartLba The start logic block address. + @param SectorCount The number of blocks to write. + @param BlkIo2Req The upstream BlockIo2 request. + @param Token The pointer to the token associated with the + non-blocking read request. + + @retval EFI_OUT_OF_RESOURCES The request could not be completed due to a + lack of resources. + @return others Status returned by calling + ScsiWrite16CommandEx(). **/ EFI_STATUS -ScsiDiskWrite16 ( +ScsiDiskAsyncWrite16 ( IN SCSI_DISK_DEV *ScsiDiskDevice, - OUT BOOLEAN *NeedRetry, IN UINT64 Timeout, + IN UINT8 TimesRetry, IN UINT8 *DataBuffer, - IN OUT UINT32 *DataLength, + IN UINT32 DataLength, IN UINT64 StartLba, - IN UINT32 SectorCount + IN UINT32 SectorCount, + IN OUT SCSI_BLKIO2_REQUEST *BlkIo2Req, + IN EFI_BLOCK_IO2_TOKEN *Token ) { - EFI_STATUS Status; - EFI_STATUS ReturnStatus; - UINT8 SenseDataLength; - UINT8 HostAdapterStatus; - UINT8 TargetStatus; - UINTN Action; + EFI_STATUS Status; + SCSI_ASYNC_RW_REQUEST *Request; + EFI_EVENT AsyncIoEvent; + EFI_TPL OldTpl; - // - // Implement a backoff algorithem to resolve some compatibility issues that - // some SCSI targets or ATAPI devices couldn't correctly response reading/writing - // big data in a single operation. - // This algorithem will at first try to execute original request. If the request fails - // with media error sense data or else, it will reduce the transfer length to half and - // try again till the operation succeeds or fails with one sector transfer length. - // -BackOff: - *NeedRetry = FALSE; - Action = ACTION_NO_ACTION; - SenseDataLength = (UINT8) (ScsiDiskDevice->SenseDataNumber * sizeof (EFI_SCSI_SENSE_DATA)); - ReturnStatus = ScsiWrite16Command ( - ScsiDiskDevice->ScsiIo, - Timeout, - ScsiDiskDevice->SenseData, - &SenseDataLength, - &HostAdapterStatus, - &TargetStatus, - DataBuffer, - DataLength, - StartLba, - SectorCount - ); - if (ReturnStatus == EFI_NOT_READY) { - *NeedRetry = TRUE; - return EFI_DEVICE_ERROR; - } else if ((ReturnStatus == EFI_INVALID_PARAMETER) || (ReturnStatus == EFI_UNSUPPORTED)) { - *NeedRetry = FALSE; - return ReturnStatus; + AsyncIoEvent = NULL; + + Request = AllocateZeroPool (sizeof (SCSI_ASYNC_RW_REQUEST)); + if (Request == NULL) { + return EFI_OUT_OF_RESOURCES; + } + + OldTpl = gBS->RaiseTPL (TPL_NOTIFY); + InsertTailList (&BlkIo2Req->ScsiRWQueue, &Request->Link); + gBS->RestoreTPL (OldTpl); + + Request->SenseDataLength = (UINT8) (6 * sizeof (EFI_SCSI_SENSE_DATA)); + Request->SenseData = AllocateZeroPool (Request->SenseDataLength); + if (Request->SenseData == NULL) { + Status = EFI_OUT_OF_RESOURCES; + goto ErrorExit; } + Request->ScsiDiskDevice = ScsiDiskDevice; + Request->Timeout = Timeout; + Request->TimesRetry = TimesRetry; + Request->OutBuffer = DataBuffer; + Request->DataLength = DataLength; + Request->StartLba = StartLba; + Request->SectorCount = SectorCount; + Request->BlkIo2Req = BlkIo2Req; + // - // go ahead to check HostAdapterStatus and TargetStatus - // (EFI_TIMEOUT, EFI_DEVICE_ERROR, EFI_WARN_BUFFER_TOO_SMALL) + // Create Event // - Status = CheckHostAdapterStatus (HostAdapterStatus); - if ((Status == EFI_TIMEOUT) || (Status == EFI_NOT_READY)) { - *NeedRetry = TRUE; - return EFI_DEVICE_ERROR; - } else if (Status == EFI_DEVICE_ERROR) { - // - // reset the scsi channel - // - ScsiDiskDevice->ScsiIo->ResetBus (ScsiDiskDevice->ScsiIo); - *NeedRetry = FALSE; - return EFI_DEVICE_ERROR; + Status = gBS->CreateEvent ( + EVT_NOTIFY_SIGNAL, + TPL_NOTIFY, + ScsiDiskNotify, + Request, + &AsyncIoEvent + ); + if (EFI_ERROR(Status)) { + goto ErrorExit; } - Status = CheckTargetStatus (TargetStatus); - if (Status == EFI_NOT_READY) { - // - // reset the scsi device - // - ScsiDiskDevice->ScsiIo->ResetDevice (ScsiDiskDevice->ScsiIo); - *NeedRetry = TRUE; - return EFI_DEVICE_ERROR; - } else if (Status == EFI_DEVICE_ERROR) { - *NeedRetry = FALSE; - return EFI_DEVICE_ERROR; + Status = ScsiWrite16CommandEx ( + ScsiDiskDevice->ScsiIo, + Request->Timeout, + Request->SenseData, + &Request->SenseDataLength, + &Request->HostAdapterStatus, + &Request->TargetStatus, + Request->OutBuffer, + &Request->DataLength, + Request->StartLba, + Request->SectorCount, + AsyncIoEvent + ); + if (EFI_ERROR(Status)) { + goto ErrorExit; } - if ((TargetStatus == EFI_EXT_SCSI_STATUS_TARGET_CHECK_CONDITION) || (EFI_ERROR (ReturnStatus))) { - DEBUG ((EFI_D_ERROR, "ScsiDiskWrite16: Check Condition happened!\n")); - Status = DetectMediaParsingSenseKeys (ScsiDiskDevice, ScsiDiskDevice->SenseData, SenseDataLength / sizeof (EFI_SCSI_SENSE_DATA), &Action); - if (Action == ACTION_RETRY_COMMAND_LATER) { - *NeedRetry = TRUE; - return EFI_DEVICE_ERROR; - } else if (Action == ACTION_RETRY_WITH_BACKOFF_ALGO) { - if (SectorCount <= 1) { - // - // Jump out if the operation still fails with one sector transfer length. - // - *NeedRetry = FALSE; - return EFI_DEVICE_ERROR; - } - // - // Try again with half length if the sense data shows we need to retry. - // - SectorCount >>= 1; - *DataLength = SectorCount * ScsiDiskDevice->BlkIo.Media->BlockSize; - goto BackOff; - } else { - *NeedRetry = FALSE; - return EFI_DEVICE_ERROR; + return EFI_SUCCESS; + +ErrorExit: + if (AsyncIoEvent != NULL) { + gBS->CloseEvent (AsyncIoEvent); + } + + if (Request != NULL) { + if (Request->SenseData != NULL) { + FreePool (Request->SenseData); } + + OldTpl = gBS->RaiseTPL (TPL_NOTIFY); + RemoveEntryList (&Request->Link); + gBS->RestoreTPL (OldTpl); + + FreePool (Request); } - return ReturnStatus; + return Status; } @@ -2693,7 +4988,7 @@ ScsiDiskIsHardwareError ( SensePtr = SenseData; for (Index = 0; Index < SenseCounts; Index++) { - + // // Sense Key is EFI_SCSI_SK_HARDWARE_ERROR (0x4) // @@ -2770,7 +5065,7 @@ ScsiDiskIsResetBefore ( SensePtr = SenseData; for (Index = 0; Index < SenseCounts; Index++) { - + // // Sense Key is EFI_SCSI_SK_UNIT_ATTENTION (0x6) // Additional Sense Code is EFI_SCSI_ASC_RESET (0x29) @@ -2791,7 +5086,7 @@ ScsiDiskIsResetBefore ( @param SenseData The pointer of EFI_SCSI_SENSE_DATA @param SenseCounts The number of sense key - @param RetryLater The flag means if need a retry + @param RetryLater The flag means if need a retry @retval TRUE Drive is ready. @retval FALSE Drive is NOT ready. @@ -2886,7 +5181,7 @@ ScsiDiskHaveSenseKey ( SensePtr = SenseData; for (Index = 0; Index < SenseCounts; Index++) { - + // // Sense Key is SK_NO_SENSE (0x0) // @@ -2932,19 +5227,19 @@ ReleaseScsiDiskDeviceResources ( } /** - Determine if Block Io should be produced. - + Determine if Block Io & Block Io2 should be produced. + @param ChildHandle Child Handle to retrieve Parent information. - - @retval TRUE Should produce Block Io. - @retval FALSE Should not produce Block Io. -**/ + @retval TRUE Should produce Block Io & Block Io2. + @retval FALSE Should not produce Block Io & Block Io2. + +**/ BOOLEAN DetermineInstallBlockIo ( IN EFI_HANDLE ChildHandle - ) + ) { EFI_SCSI_PASS_THRU_PROTOCOL *ScsiPassThru; EFI_EXT_SCSI_PASS_THRU_PROTOCOL *ExtScsiPassThru; @@ -2970,7 +5265,7 @@ DetermineInstallBlockIo ( return TRUE; } } - + return FALSE; } @@ -2979,23 +5274,23 @@ DetermineInstallBlockIo ( specified by ProtocolGuid is present on a ControllerHandle and opened by ChildHandle with an attribute of EFI_OPEN_PROTOCOL_BY_CHILD_CONTROLLER. If the ControllerHandle is found, then the protocol specified by ProtocolGuid - will be opened on it. - + will be opened on it. + @param ProtocolGuid ProtocolGuid pointer. @param ChildHandle Child Handle to retrieve Parent information. - -**/ + +**/ VOID * EFIAPI GetParentProtocol ( IN EFI_GUID *ProtocolGuid, IN EFI_HANDLE ChildHandle - ) + ) { UINTN Index; UINTN HandleCount; - VOID *Interface; + VOID *Interface; EFI_STATUS Status; EFI_HANDLE *HandleBuffer; @@ -3015,7 +5310,7 @@ GetParentProtocol ( } // - // Iterate to find who is parent handle that is opened with ProtocolGuid by ChildHandle + // Iterate to find who is parent handle that is opened with ProtocolGuid by ChildHandle // for (Index = 0; Index < HandleCount; Index++) { Status = EfiTestChildHandle (HandleBuffer[Index], ChildHandle, ProtocolGuid); @@ -3030,11 +5325,144 @@ GetParentProtocol ( gBS->FreePool (HandleBuffer); return NULL; -} +} + +/** + Determine if EFI Erase Block Protocol should be produced. + + @param ScsiDiskDevice The pointer of SCSI_DISK_DEV. + @param ChildHandle Handle of device. + + @retval TRUE Should produce EFI Erase Block Protocol. + @retval FALSE Should not produce EFI Erase Block Protocol. + +**/ +BOOLEAN +DetermineInstallEraseBlock ( + IN SCSI_DISK_DEV *ScsiDiskDevice, + IN EFI_HANDLE ChildHandle + ) +{ + UINT8 HostAdapterStatus; + UINT8 TargetStatus; + EFI_STATUS CommandStatus; + EFI_STATUS Status; + BOOLEAN UfsDevice; + BOOLEAN RetVal; + EFI_DEVICE_PATH_PROTOCOL *DevicePathNode; + UINT8 SenseDataLength; + UINT32 DataLength16; + EFI_SCSI_DISK_CAPACITY_DATA16 *CapacityData16; + + UfsDevice = FALSE; + RetVal = TRUE; + CapacityData16 = NULL; + + Status = gBS->HandleProtocol ( + ChildHandle, + &gEfiDevicePathProtocolGuid, + (VOID **) &DevicePathNode + ); + // + // Device Path protocol must be installed on the device handle. + // + ASSERT_EFI_ERROR (Status); + + while (!IsDevicePathEndType (DevicePathNode)) { + // + // For now, only support Erase Block Protocol on UFS devices. + // + if ((DevicePathNode->Type == MESSAGING_DEVICE_PATH) && + (DevicePathNode->SubType == MSG_UFS_DP)) { + UfsDevice = TRUE; + break; + } + + DevicePathNode = NextDevicePathNode (DevicePathNode); + } + if (!UfsDevice) { + RetVal = FALSE; + goto Done; + } + + // + // Check whether the erase functionality is enabled on the UFS device. + // + CapacityData16 = AllocateAlignedBuffer (ScsiDiskDevice, sizeof (EFI_SCSI_DISK_CAPACITY_DATA16)); + if (CapacityData16 == NULL) { + RetVal = FALSE; + goto Done; + } + + SenseDataLength = 0; + DataLength16 = sizeof (EFI_SCSI_DISK_CAPACITY_DATA16); + ZeroMem (CapacityData16, sizeof (EFI_SCSI_DISK_CAPACITY_DATA16)); + + CommandStatus = ScsiReadCapacity16Command ( + ScsiDiskDevice->ScsiIo, + SCSI_DISK_TIMEOUT, + NULL, + &SenseDataLength, + &HostAdapterStatus, + &TargetStatus, + (VOID *) CapacityData16, + &DataLength16, + FALSE + ); + + if (CommandStatus == EFI_SUCCESS) { + // + // Universal Flash Storage (UFS) Version 2.0 + // Section 11.3.9.2 + // Bits TPE and TPRZ should both be set to enable the erase feature on UFS. + // + if (((CapacityData16->LowestAlignLogic2 & BIT7) == 0) || + ((CapacityData16->LowestAlignLogic2 & BIT6) == 0)) { + DEBUG (( + EFI_D_VERBOSE, + "ScsiDisk EraseBlock: Either TPE or TPRZ is not set: 0x%x.\n", + CapacityData16->LowestAlignLogic2 + )); + + RetVal = FALSE; + goto Done; + } + } else { + DEBUG (( + EFI_D_VERBOSE, + "ScsiDisk EraseBlock: ReadCapacity16 failed with status %r.\n", + CommandStatus + )); + + RetVal = FALSE; + goto Done; + } + + // + // Check whether the UFS device server implements the UNMAP command. + // + if ((ScsiDiskDevice->UnmapInfo.MaxLbaCnt == 0) || + (ScsiDiskDevice->UnmapInfo.MaxBlkDespCnt == 0)) { + DEBUG (( + EFI_D_VERBOSE, + "ScsiDisk EraseBlock: The device server does not implement the UNMAP command.\n" + )); + + RetVal = FALSE; + goto Done; + } + +Done: + if (CapacityData16 != NULL) { + FreeAlignedBuffer (CapacityData16, sizeof (EFI_SCSI_DISK_CAPACITY_DATA16)); + } + + return RetVal; +} /** Provides inquiry information for the controller type. - + This function is used by the IDE bus driver to get inquiry data. Data format of Identify data is defined by the Interface GUID. @@ -3043,9 +5471,9 @@ GetParentProtocol ( @param[in, out] InquiryDataSize Pointer to the value for the inquiry data size. @retval EFI_SUCCESS The command was accepted without any errors. - @retval EFI_NOT_FOUND Device does not support this data class - @retval EFI_DEVICE_ERROR Error reading InquiryData from device - @retval EFI_BUFFER_TOO_SMALL InquiryDataSize not big enough + @retval EFI_NOT_FOUND Device does not support this data class + @retval EFI_DEVICE_ERROR Error reading InquiryData from device + @retval EFI_BUFFER_TOO_SMALL InquiryDataSize not big enough **/ EFI_STATUS @@ -3077,16 +5505,16 @@ ScsiDiskInfoInquiry ( This function is used by the IDE bus driver to get identify data. Data format of Identify data is defined by the Interface GUID. - @param[in] This Pointer to the EFI_DISK_INFO_PROTOCOL + @param[in] This Pointer to the EFI_DISK_INFO_PROTOCOL instance. @param[in, out] IdentifyData Pointer to a buffer for the identify data. @param[in, out] IdentifyDataSize Pointer to the value for the identify data size. @retval EFI_SUCCESS The command was accepted without any errors. - @retval EFI_NOT_FOUND Device does not support this data class - @retval EFI_DEVICE_ERROR Error reading IdentifyData from device - @retval EFI_BUFFER_TOO_SMALL IdentifyDataSize not big enough + @retval EFI_NOT_FOUND Device does not support this data class + @retval EFI_DEVICE_ERROR Error reading IdentifyData from device + @retval EFI_BUFFER_TOO_SMALL IdentifyDataSize not big enough **/ EFI_STATUS @@ -3102,7 +5530,7 @@ ScsiDiskInfoIdentify ( if (CompareGuid (&This->Interface, &gEfiDiskInfoScsiInterfaceGuid) || CompareGuid (&This->Interface, &gEfiDiskInfoUfsInterfaceGuid)) { // - // Physical SCSI bus does not support this data class. + // Physical SCSI bus does not support this data class. // return EFI_NOT_FOUND; } @@ -3120,8 +5548,8 @@ ScsiDiskInfoIdentify ( /** Provides sense data information for the controller type. - - This function is used by the IDE bus driver to get sense data. + + This function is used by the IDE bus driver to get sense data. Data format of Sense data is defined by the Interface GUID. @param[in] This Pointer to the EFI_DISK_INFO_PROTOCOL instance. @@ -3151,7 +5579,7 @@ ScsiDiskInfoSenseData ( /** This function is used by the IDE bus driver to get controller information. - @param[in] This Pointer to the EFI_DISK_INFO_PROTOCOL instance. + @param[in] This Pointer to the EFI_DISK_INFO_PROTOCOL instance. @param[out] IdeChannel Pointer to the Ide Channel number. Primary or secondary. @param[out] IdeDevice Pointer to the Ide Device number. Master or slave. @@ -3192,11 +5620,11 @@ ScsiDiskInfoWhichIde ( via SCSI Request Packet. @param ScsiDiskDevice The pointer of SCSI_DISK_DEV - + @retval EFI_SUCCESS The ATAPI device identify data were retrieved successfully. @retval others Some error occurred during the identification that ATAPI device. -**/ +**/ EFI_STATUS AtapiIdentifyDevice ( IN OUT SCSI_DISK_DEV *ScsiDiskDevice @@ -3232,8 +5660,8 @@ AtapiIdentifyDevice ( @param ScsiDiskDevice The pointer of SCSI_DISK_DEV. @param ChildHandle Child handle to install DiskInfo protocol. - -**/ + +**/ VOID InitializeInstallDiskInfo ( IN SCSI_DISK_DEV *ScsiDiskDevice, @@ -3249,7 +5677,7 @@ InitializeInstallDiskInfo ( Status = gBS->HandleProtocol (ChildHandle, &gEfiDevicePathProtocolGuid, (VOID **) &DevicePathNode); // - // Device Path protocol must be installed on the device handle. + // Device Path protocol must be installed on the device handle. // ASSERT_EFI_ERROR (Status); // @@ -3281,7 +5709,7 @@ InitializeInstallDiskInfo ( ScsiDiskDevice->Channel = AtapiDevicePath->PrimarySecondary; ScsiDiskDevice->Device = AtapiDevicePath->SlaveMaster; // - // Update the DiskInfo.Interface to IDE interface GUID for the physical ATAPI device. + // Update the DiskInfo.Interface to IDE interface GUID for the physical ATAPI device. // CopyGuid (&ScsiDiskDevice->DiskInfo.Interface, &gEfiDiskInfoIdeInterfaceGuid); } else { @@ -3292,7 +5720,7 @@ InitializeInstallDiskInfo ( ScsiDiskDevice->Channel = SataDevicePath->HBAPortNumber; ScsiDiskDevice->Device = SataDevicePath->PortMultiplierPortNumber; // - // Update the DiskInfo.Interface to AHCI interface GUID for the physical AHCI device. + // Update the DiskInfo.Interface to AHCI interface GUID for the physical AHCI device. // CopyGuid (&ScsiDiskDevice->DiskInfo.Interface, &gEfiDiskInfoAhciInterfaceGuid); }