X-Git-Url: https://git.proxmox.com/?a=blobdiff_plain;f=MdeModulePkg%2FBus%2FScsi%2FScsiDiskDxe%2FScsiDisk.c;h=fbdf927a1114fb5ef268cb3c8dd47850103da44f;hb=9d510e61fceee7b92955ef9a3c20343752d8ce3f;hp=8f9a6256e9b16f58037e4fd6056d67f38b77b7d9;hpb=d716651f8c80b4dce9c85301e948de5df963c186;p=mirror_edk2.git diff --git a/MdeModulePkg/Bus/Scsi/ScsiDiskDxe/ScsiDisk.c b/MdeModulePkg/Bus/Scsi/ScsiDiskDxe/ScsiDisk.c index 8f9a6256e9..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 - 2009, 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 **/ @@ -32,14 +26,56 @@ EFI_DISK_INFO_PROTOCOL gScsiDiskInfoProtocolTemplate = { ScsiDiskInfoWhichIde }; +/** + Allocates an aligned buffer for SCSI disk. + + This function allocates an aligned buffer for the SCSI disk to perform + SCSI IO operations. The alignment requirement is from SCSI IO interface. + + @param ScsiDiskDevice The SCSI disk involved for the operation. + @param BufferSize The request buffer size. + + @return A pointer to the aligned buffer or NULL if the allocation fails. + +**/ +VOID * +AllocateAlignedBuffer ( + IN SCSI_DISK_DEV *ScsiDiskDevice, + IN UINTN BufferSize + ) +{ + return AllocateAlignedPages (EFI_SIZE_TO_PAGES (BufferSize), ScsiDiskDevice->ScsiIo->IoAlign); +} + +/** + Frees an aligned buffer for SCSI disk. + + This function frees an aligned buffer for the SCSI disk to perform + SCSI IO operations. + + @param Buffer The aligned buffer to be freed. + @param BufferSize The request buffer size. + +**/ +VOID +FreeAlignedBuffer ( + IN VOID *Buffer, + IN UINTN BufferSize + ) +{ + if (Buffer != NULL) { + FreeAlignedPages (Buffer, EFI_SIZE_TO_PAGES (BufferSize)); + } +} + /** The user Entry Point for module ScsiDisk. 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. @@ -166,6 +202,9 @@ ScsiDiskDriverBindingStart ( UINT8 Index; UINT8 MaxRetry; BOOLEAN NeedRetry; + BOOLEAN MustReadCapacity; + + MustReadCapacity = TRUE; ScsiDiskDevice = (SCSI_DISK_DEV *) AllocateZeroPool (sizeof (SCSI_DISK_DEV)); if (ScsiDiskDevice == NULL) { @@ -185,23 +224,39 @@ ScsiDiskDriverBindingStart ( return Status; } - ScsiDiskDevice->Signature = SCSI_DISK_DEV_SIGNATURE; - ScsiDiskDevice->ScsiIo = ScsiIo; - ScsiDiskDevice->BlkIo.Media = &ScsiDiskDevice->BlkIoMedia; - 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) { case EFI_SCSI_TYPE_DISK: ScsiDiskDevice->BlkIo.Media->BlockSize = 0x200; + MustReadCapacity = TRUE; break; case EFI_SCSI_TYPE_CDROM: ScsiDiskDevice->BlkIo.Media->BlockSize = 0x800; + ScsiDiskDevice->BlkIo.Media->ReadOnly = TRUE; + MustReadCapacity = FALSE; break; } // @@ -248,10 +303,11 @@ ScsiDiskDriverBindingStart ( // The second parameter "TRUE" means must // retrieve media capacity // - Status = ScsiDiskDetectMedia (ScsiDiskDevice, TRUE, &Temp); + 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); @@ -259,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", @@ -281,7 +350,7 @@ ScsiDiskDriverBindingStart ( ); return EFI_SUCCESS; } - } + } } gBS->FreePool (ScsiDiskDevice->SenseData); @@ -293,7 +362,7 @@ ScsiDiskDriverBindingStart ( Controller ); return Status; - + } @@ -305,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 @@ -325,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, @@ -341,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 @@ -394,16 +495,30 @@ 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)) { + if (Status == EFI_UNSUPPORTED) { + Status = EFI_SUCCESS; + } else { + Status = EFI_DEVICE_ERROR; + goto Done; + } + } + if (!ExtendedVerification) { goto Done; } Status = ScsiDiskDevice->ScsiIo->ResetBus (ScsiDiskDevice->ScsiIo); + if (EFI_ERROR (Status)) { + Status = EFI_DEVICE_ERROR; + goto Done; + } + Done: gBS->RestoreTPL (OldTpl); return Status; @@ -444,18 +559,10 @@ ScsiDiskReadBlocks ( BOOLEAN MediaChange; EFI_TPL OldTpl; - MediaChange = FALSE; - if (Buffer == NULL) { - return EFI_INVALID_PARAMETER; - } - - if (BufferSize == 0) { - return EFI_SUCCESS; - } - - OldTpl = gBS->RaiseTPL (TPL_CALLBACK); - - ScsiDiskDevice = SCSI_DISK_DEV_FROM_THIS (This); + MediaChange = FALSE; + OldTpl = gBS->RaiseTPL (TPL_CALLBACK); + ScsiDiskDevice = SCSI_DISK_DEV_FROM_BLKIO (This); + Media = ScsiDiskDevice->BlkIo.Media; if (!IS_DEVICE_FIXED(ScsiDiskDevice)) { @@ -472,12 +579,31 @@ ScsiDiskReadBlocks ( &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; } } // // Get the intrinsic block size // - Media = ScsiDiskDevice->BlkIo.Media; BlockSize = Media->BlockSize; NumberOfBlocks = BufferSize / BlockSize; @@ -492,6 +618,16 @@ ScsiDiskReadBlocks ( goto Done; } + if (Buffer == NULL) { + Status = EFI_INVALID_PARAMETER; + goto Done; + } + + if (BufferSize == 0) { + Status = EFI_SUCCESS; + goto Done; + } + if (BufferSize % BlockSize != 0) { Status = EFI_BAD_BUFFER_SIZE; goto Done; @@ -559,18 +695,10 @@ ScsiDiskWriteBlocks ( BOOLEAN MediaChange; EFI_TPL OldTpl; - MediaChange = FALSE; - if (Buffer == NULL) { - return EFI_INVALID_PARAMETER; - } - - if (BufferSize == 0) { - return EFI_SUCCESS; - } - - OldTpl = gBS->RaiseTPL (TPL_CALLBACK); - - ScsiDiskDevice = SCSI_DISK_DEV_FROM_THIS (This); + MediaChange = FALSE; + OldTpl = gBS->RaiseTPL (TPL_CALLBACK); + ScsiDiskDevice = SCSI_DISK_DEV_FROM_BLKIO (This); + Media = ScsiDiskDevice->BlkIo.Media; if (!IS_DEVICE_FIXED(ScsiDiskDevice)) { @@ -587,12 +715,31 @@ ScsiDiskWriteBlocks ( &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; } } // // Get the intrinsic block size // - Media = ScsiDiskDevice->BlkIo.Media; BlockSize = Media->BlockSize; NumberOfBlocks = BufferSize / BlockSize; @@ -607,6 +754,21 @@ ScsiDiskWriteBlocks ( goto Done; } + if (Media->ReadOnly) { + Status = EFI_WRITE_PROTECTED; + goto Done; + } + + if (BufferSize == 0) { + Status = EFI_SUCCESS; + goto Done; + } + + if (Buffer == NULL) { + Status = EFI_INVALID_PARAMETER; + goto Done; + } + if (BufferSize % BlockSize != 0) { Status = EFI_BAD_BUFFER_SIZE; goto Done; @@ -661,375 +823,2845 @@ 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_STATUS ReadCapacityStatus; - EFI_SCSI_SENSE_DATA *SenseData; - UINTN NumberOfSenseKeys; - BOOLEAN NeedRetry; - BOOLEAN NeedReadCapacity; - UINT8 Index; - UINT8 MaxRetry; - EFI_BLOCK_IO_MEDIA OldMedia; - UINTN Action; + EFI_TPL OldTpl; + SCSI_DISK_DEV *ScsiDiskDevice; + EFI_STATUS Status; - Status = EFI_SUCCESS; - ReadCapacityStatus = EFI_SUCCESS; - SenseData = NULL; - NumberOfSenseKeys = 0; - NeedReadCapacity = FALSE; - CopyMem (&OldMedia, ScsiDiskDevice->BlkIo.Media, sizeof (OldMedia)); - *MediaChange = FALSE; - MaxRetry = 3; + OldTpl = gBS->RaiseTPL (TPL_CALLBACK); - for (Index = 0; Index < MaxRetry; Index++) { - Status = ScsiDiskTestUnitReady ( - ScsiDiskDevice, - &NeedRetry, - &SenseData, - &NumberOfSenseKeys - ); - if (!EFI_ERROR (Status)) { - break; - } + ScsiDiskDevice = SCSI_DISK_DEV_FROM_BLKIO2 (This); - if (!NeedRetry) { - return Status; + Status = ScsiDiskDevice->ScsiIo->ResetDevice (ScsiDiskDevice->ScsiIo); + + if (EFI_ERROR (Status)) { + if (Status == EFI_UNSUPPORTED) { + Status = EFI_SUCCESS; + } else { + Status = EFI_DEVICE_ERROR; + goto Done; } } - if ((Index == MaxRetry) && EFI_ERROR (Status)) { - return EFI_DEVICE_ERROR; + if (!ExtendedVerification) { + goto Done; } - Status = DetectMediaParsingSenseKeys ( - ScsiDiskDevice, - SenseData, - NumberOfSenseKeys, - &Action - ); + Status = ScsiDiskDevice->ScsiIo->ResetBus (ScsiDiskDevice->ScsiIo); + if (EFI_ERROR (Status)) { - return Status; - } - // - // ACTION_NO_ACTION: need not read capacity - // other action code: need read capacity - // - if (Action == ACTION_NO_ACTION) { - NeedReadCapacity = FALSE; - } else { - NeedReadCapacity = TRUE; + Status = EFI_DEVICE_ERROR; + goto Done; } - // - // either NeedReadCapacity is TRUE, or MustReadCapacity is TRUE, - // retrieve capacity via Read Capacity command - // - if (NeedReadCapacity || MustReadCapacity) { - // - // retrieve media information - // - MaxRetry = 3; - for (Index = 0; Index < MaxRetry; Index++) { +Done: + gBS->RestoreTPL (OldTpl); + return Status; +} - ReadCapacityStatus = ScsiDiskReadCapacity ( - ScsiDiskDevice, - &NeedRetry, - &SenseData, - &NumberOfSenseKeys - ); - if (EFI_ERROR (ReadCapacityStatus) && !NeedRetry) { - return EFI_DEVICE_ERROR; - } - // - // analyze sense key to action - // - Status = DetectMediaParsingSenseKeys ( - ScsiDiskDevice, - SenseData, - NumberOfSenseKeys, - &Action - ); - // - // if Status is error, it may indicate crisis error, - // so return without retry. - // - if (EFI_ERROR (Status)) { - return Status; - } +/** + The function is to Read Block from SCSI Disk. - switch (Action) { - case ACTION_NO_ACTION: - // - // no retry - // - Index = MaxRetry; - break; + @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. - case ACTION_RETRY_COMMAND_LATER: - // - // retry the ReadCapacity later and continuously, until the condition - // no longer emerges. - // stall time is 100000us, or say 0.1 second. - // - gBS->Stall (100000); - Index = 0; - break; +**/ +EFI_STATUS +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 + ) +{ + SCSI_DISK_DEV *ScsiDiskDevice; + EFI_BLOCK_IO_MEDIA *Media; + EFI_STATUS Status; + UINTN BlockSize; + UINTN NumberOfBlocks; + BOOLEAN MediaChange; + EFI_TPL OldTpl; - default: - // - // other cases, just retry the command - // - break; - } - } + MediaChange = FALSE; + OldTpl = gBS->RaiseTPL (TPL_CALLBACK); + ScsiDiskDevice = SCSI_DISK_DEV_FROM_BLKIO2 (This); + Media = ScsiDiskDevice->BlkIo.Media; - if ((Index == MaxRetry) && EFI_ERROR (ReadCapacityStatus)) { - return EFI_DEVICE_ERROR; - } - } + if (!IS_DEVICE_FIXED(ScsiDiskDevice)) { - if (ScsiDiskDevice->BlkIo.Media->MediaId != OldMedia.MediaId) { - // - // Media change information got from the device - // - *MediaChange = TRUE; - } + Status = ScsiDiskDetectMedia (ScsiDiskDevice, FALSE, &MediaChange); + if (EFI_ERROR (Status)) { + Status = EFI_DEVICE_ERROR; + goto Done; + } - if (ScsiDiskDevice->BlkIo.Media->ReadOnly != OldMedia.ReadOnly) { - *MediaChange = TRUE; - ScsiDiskDevice->BlkIo.Media->MediaId += 1; - } + 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; + } + } + // + // Get the intrinsic block size + // + BlockSize = Media->BlockSize; + + NumberOfBlocks = BufferSize / BlockSize; + + if (!(Media->MediaPresent)) { + Status = EFI_NO_MEDIA; + goto Done; + } + + if (MediaId != Media->MediaId) { + Status = EFI_MEDIA_CHANGED; + goto Done; + } + + if (Buffer == NULL) { + Status = EFI_INVALID_PARAMETER; + goto Done; + } + + if (BufferSize == 0) { + if ((Token != NULL) && (Token->Event != NULL)) { + Token->TransactionStatus = EFI_SUCCESS; + gBS->SignalEvent (Token->Event); + } + + Status = EFI_SUCCESS; + goto Done; + } + + if (BufferSize % BlockSize != 0) { + Status = EFI_BAD_BUFFER_SIZE; + goto Done; + } + + 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; + } + + // + // If all the parameters are valid, then perform read sectors command + // to transfer data from device to host. + // + 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; +} + +/** + The function is to Write Block to SCSI Disk. + + @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 +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 + ) +{ + 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_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; + } + } + // + // Get the intrinsic block size + // + BlockSize = Media->BlockSize; + + NumberOfBlocks = BufferSize / BlockSize; + + 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 (BufferSize == 0) { + if ((Token != NULL) && (Token->Event != NULL)) { + Token->TransactionStatus = EFI_SUCCESS; + gBS->SignalEvent (Token->Event); + } + + Status = EFI_SUCCESS; + goto Done; + } + + if (Buffer == NULL) { + Status = EFI_INVALID_PARAMETER; + goto Done; + } + + if (BufferSize % BlockSize != 0) { + Status = EFI_BAD_BUFFER_SIZE; + goto Done; + } + + 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; + } + + // + // if all the parameters are valid, then perform write sectors command + // to transfer data from device to host. + // + 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; +} + +/** + Flush the Block Device. + + @param This Indicates a pointer to the calling context. + @param Token A pointer to the token associated with the transaction. + + @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 +EFIAPI +ScsiDiskFlushBlocksEx ( + IN EFI_BLOCK_IO2_PROTOCOL *This, + IN OUT EFI_BLOCK_IO2_TOKEN *Token + ) +{ + SCSI_DISK_DEV *ScsiDiskDevice; + EFI_BLOCK_IO_MEDIA *Media; + EFI_STATUS Status; + BOOLEAN MediaChange; + EFI_TPL OldTpl; + + 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; + } + } + + if (!(Media->MediaPresent)) { + Status = EFI_NO_MEDIA; + goto Done; + } + + if (Media->ReadOnly) { + Status = EFI_WRITE_PROTECTED; + goto Done; + } + + // + // Wait for the BlockIo2 requests queue to become empty + // + while (!IsListEmpty (&ScsiDiskDevice->AsyncTaskQueue)); + + Status = EFI_SUCCESS; + + // + // Signal caller event + // + if ((Token != NULL) && (Token->Event != NULL)) { + Token->TransactionStatus = EFI_SUCCESS; + gBS->SignalEvent (Token->Event); + } + +Done: + gBS->RestoreTPL (OldTpl); + return Status; +} + + +/** + Internal helper notify function which process the result of an asynchronous + SCSI UNMAP Command and signal the event passed from EraseBlocks. + + @param Event The instance of EFI_EVENT. + @param Context The parameter passed in. + +**/ +VOID +EFIAPI +ScsiDiskAsyncUnmapNotify ( + IN EFI_EVENT Event, + IN VOID *Context + ) +{ + 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; + } + + 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; + } + +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; + } + + EraseBlkReq = AllocateZeroPool (sizeof (SCSI_ERASEBLK_REQUEST)); + if (EraseBlkReq == NULL) { + ReturnStatus = EFI_DEVICE_ERROR; + goto Done; + } + + 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; + // + // Fill Cdb for UNMAP Command + // + Cdb = CommandPacket->Cdb; + Cdb[0] = EFI_SCSI_OP_UNMAP; + WriteUnaligned16 ((UINT16 *)&Cdb[7], SwapBytes16 (UnmapParamListLen)); + + if ((Token != NULL) && (Token->Event != NULL)) { + // + // Non-blocking UNMAP request + // + 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 { + // + // Blocking UNMAP request + // + Status = ScsiIo->ExecuteScsiCommand ( + ScsiIo, + CommandPacket, + NULL + ); + if (EFI_ERROR(Status)) { + ReturnStatus = EFI_DEVICE_ERROR; + goto Done; + } + } + + // + // 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; + } + + Status = CheckTargetStatus (CommandPacket->TargetStatus); + if (EFI_ERROR(Status)) { + DEBUG (( + EFI_D_ERROR, + "ScsiDiskUnmap: Target indicating error status 0x%x.\n", + CommandPacket->HostAdapterStatus + )); + + ReturnStatus = EFI_DEVICE_ERROR; + goto Done; + } + +Done: + if (EraseBlkReq != NULL) { + if (EraseBlkReq->CommandPacket.Cdb != NULL) { + FreePool (EraseBlkReq->CommandPacket.Cdb); + } + FreePool (EraseBlkReq); + } + + if (UnmapParamList != NULL) { + FreePool (UnmapParamList); + } + + 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; + } + } + // + // Get the intrinsic block size + // + Media = ScsiDiskDevice->BlkIo.Media; + + 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; + } - if (ScsiDiskDevice->BlkIo.Media->BlockSize != OldMedia.BlockSize) { - *MediaChange = TRUE; - ScsiDiskDevice->BlkIo.Media->MediaId += 1; + // + // 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; } - if (ScsiDiskDevice->BlkIo.Media->LastBlock != OldMedia.LastBlock) { - *MediaChange = TRUE; - ScsiDiskDevice->BlkIo.Media->MediaId += 1; + 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; } - 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; + 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; + } + // - // when no media, reset the MediaId to zero. + // 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. // - ScsiDiskDevice->BlkIo.Media->MediaId = 0; + // 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; + } } - *MediaChange = TRUE; + 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; } +/** + Asynchronously 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. + @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 +ScsiDiskAsyncReadSectors ( + IN SCSI_DISK_DEV *ScsiDiskDevice, + OUT 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; + 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 + // + 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); + + 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; + } + } + + 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; + } + } + + // + // 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; +} /** - Send out Inquiry command to Device. + Asynchronously write sector to SCSI Disk. - @param ScsiDiskDevice The pointer of SCSI_DISK_DEV - @param NeedRetry Indicates if needs try again when error happens + @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_DEVICE_ERROR Indicates that error occurs - @retval EFI_SUCCESS Successfully to detect media + @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 -ScsiDiskInquiryDevice ( - IN OUT SCSI_DISK_DEV *ScsiDiskDevice, - OUT BOOLEAN *NeedRetry +ScsiDiskAsyncWriteSectors ( + IN SCSI_DISK_DEV *ScsiDiskDevice, + IN VOID *Buffer, + IN EFI_LBA Lba, + IN UINTN NumberOfBlocks, + IN EFI_BLOCK_IO2_TOKEN *Token ) { - UINT32 InquiryDataLength; - UINT8 SenseDataLength; - UINT8 HostAdapterStatus; - UINT8 TargetStatus; - EFI_SCSI_SENSE_DATA *SenseDataArray; - UINTN NumberOfSenseKeys; - EFI_STATUS Status; - UINT8 MaxRetry; - UINT8 Index; + UINTN BlocksRemaining; + UINT8 *PtrBuffer; + UINT32 BlockSize; + UINT32 ByteCount; + UINT32 MaxBlock; + UINT32 SectorCount; + UINT64 Timeout; + SCSI_BLKIO2_REQUEST *BlkIo2Req; + EFI_STATUS Status; + EFI_TPL OldTpl; - InquiryDataLength = sizeof (EFI_SCSI_INQUIRY_DATA); - SenseDataLength = 0; + if ((Token == NULL) || (Token->Event == NULL)) { + return EFI_INVALID_PARAMETER; + } - Status = ScsiInquiryCommand ( - ScsiDiskDevice->ScsiIo, - EFI_TIMER_PERIOD_SECONDS (1), - NULL, - &SenseDataLength, - &HostAdapterStatus, - &TargetStatus, - (VOID *) &(ScsiDiskDevice->InquiryData), - &InquiryDataLength, - FALSE - ); + 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 + // + 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; // - // no need to check HostAdapterStatus and TargetStatus + // |------------------------|-----------------|------------------|-----------------| + // | 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 ((Status == EFI_SUCCESS) || (Status == EFI_WARN_BUFFER_TOO_SMALL)) { - ParseInquiryData (ScsiDiskDevice); - 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) { + // 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); + + 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)) { // - // reset the scsi channel + // 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. // - ScsiDiskDevice->ScsiIo->ResetBus (ScsiDiskDevice->ScsiIo); - *NeedRetry = FALSE; - return EFI_DEVICE_ERROR; - } + if ((Status == EFI_DEVICE_ERROR) || (Status == EFI_TIMEOUT)) { + if ((MaxBlock > 1) && (SectorCount > 1)) { + MaxBlock = MIN (MaxBlock, SectorCount) >> 1; + continue; + } + } + + 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; + } + } - Status = CheckTargetStatus (TargetStatus); - if (Status == EFI_NOT_READY) { // - // reset the scsi device + // Sectors submitted for transfer // - ScsiDiskDevice->ScsiIo->ResetDevice (ScsiDiskDevice->ScsiIo); - *NeedRetry = TRUE; - return EFI_DEVICE_ERROR; + SectorCount = ByteCount / BlockSize; - } else if (Status == EFI_DEVICE_ERROR) { - *NeedRetry = FALSE; - return EFI_DEVICE_ERROR; + Lba += SectorCount; + PtrBuffer = PtrBuffer + SectorCount * BlockSize; + BlocksRemaining -= SectorCount; } - - // - // 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; + 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); } - // - // 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. + return Status; +} - 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 +/** + Submit Read(10) command. - @retval EFI_DEVICE_ERROR Indicates that error occurs - @retval EFI_SUCCESS Successfully to test unit + @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 -ScsiDiskTestUnitReady ( - IN SCSI_DISK_DEV *ScsiDiskDevice, - OUT BOOLEAN *NeedRetry, - OUT EFI_SCSI_SENSE_DATA **SenseDataArray, - OUT UINTN *NumberOfSenseKeys +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 ) { - EFI_STATUS Status; UINT8 SenseDataLength; + EFI_STATUS Status; + EFI_STATUS ReturnStatus; UINT8 HostAdapterStatus; UINT8 TargetStatus; - UINT8 Index; - UINT8 MaxRetry; - - SenseDataLength = 0; - *NumberOfSenseKeys = 0; + UINTN Action; // - // Parameter 3 and 4: do not require sense data, retrieve it when needed. - // - Status = ScsiTestUnitReadyCommand ( - ScsiDiskDevice->ScsiIo, - EFI_TIMER_PERIOD_SECONDS (1), - NULL, - &SenseDataLength, - &HostAdapterStatus, - &TargetStatus - ); - // - // no need to check HostAdapterStatus and TargetStatus + // 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 (Status == EFI_NOT_READY) { +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 ((Status == EFI_INVALID_PARAMETER) || (Status == EFI_UNSUPPORTED)) { + } else if ((ReturnStatus == EFI_INVALID_PARAMETER) || (ReturnStatus == EFI_UNSUPPORTED)) { *NeedRetry = FALSE; - return EFI_DEVICE_ERROR; + return ReturnStatus; } + // - // go ahead to check HostAdapterStatus and TargetStatus(in case of 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 @@ -1047,214 +3679,237 @@ ScsiDiskTestUnitReady ( ScsiDiskDevice->ScsiIo->ResetDevice (ScsiDiskDevice->ScsiIo); *NeedRetry = TRUE; return EFI_DEVICE_ERROR; - } else if (Status == EFI_DEVICE_ERROR) { *NeedRetry = FALSE; return EFI_DEVICE_ERROR; } - MaxRetry = 3; - for (Index = 0; Index < MaxRetry; Index++) { - Status = ScsiDiskRequestSenseKeys ( - ScsiDiskDevice, - NeedRetry, - SenseDataArray, - NumberOfSenseKeys, - FALSE - ); - if (!EFI_ERROR (Status)) { - return EFI_SUCCESS; - } - - if (!*NeedRetry) { + 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; + } + // + // 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; } } - // - // ScsiDiskRequestSenseKeys() failed after several rounds of retry. - // set *NeedRetry = FALSE to avoid the outside caller try again. - // - *NeedRetry = FALSE; - return EFI_DEVICE_ERROR; + + return ReturnStatus; } + /** - Parsing Sense Keys which got from request sense command. + Submit Write(10) 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 + @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 that error occurs - @retval EFI_SUCCESS Successfully to complete the parsing + @return EFI_STATUS is returned by calling ScsiWrite10Command(). **/ EFI_STATUS -DetectMediaParsingSenseKeys ( - OUT SCSI_DISK_DEV *ScsiDiskDevice, - IN EFI_SCSI_SENSE_DATA *SenseData, - IN UINTN NumberOfSenseKeys, - OUT UINTN *Action +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 ) { - BOOLEAN RetryLater; + EFI_STATUS Status; + EFI_STATUS ReturnStatus; + UINT8 SenseDataLength; + UINT8 HostAdapterStatus; + UINT8 TargetStatus; + UINTN Action; // - // Default is to read capacity, unless.. + // 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. // - *Action = ACTION_READ_CAPACITY; - - if (NumberOfSenseKeys == 0) { - *Action = ACTION_NO_ACTION; - return EFI_SUCCESS; +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; } - if (!ScsiDiskHaveSenseKey (SenseData, NumberOfSenseKeys)) { + // + // 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) { // - // No Sense Key returned from last submitted command + // reset the scsi channel // - *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; - return EFI_SUCCESS; - } - - if (ScsiDiskIsMediaChange (SenseData, NumberOfSenseKeys)) { - ScsiDiskDevice->BlkIo.Media->MediaId++; - return EFI_SUCCESS; + ScsiDiskDevice->ScsiIo->ResetBus (ScsiDiskDevice->ScsiIo); + *NeedRetry = FALSE; + return EFI_DEVICE_ERROR; } - if (ScsiDiskIsMediaError (SenseData, NumberOfSenseKeys)) { - ScsiDiskDevice->BlkIo.Media->MediaPresent = FALSE; - ScsiDiskDevice->BlkIo.Media->LastBlock = 0; + Status = CheckTargetStatus (TargetStatus); + if (Status == EFI_NOT_READY) { + // + // reset the scsi device + // + ScsiDiskDevice->ScsiIo->ResetDevice (ScsiDiskDevice->ScsiIo); + *NeedRetry = TRUE; return EFI_DEVICE_ERROR; - } - - if (ScsiDiskIsHardwareError (SenseData, NumberOfSenseKeys)) { + } else if (Status == EFI_DEVICE_ERROR) { + *NeedRetry = FALSE; return EFI_DEVICE_ERROR; } - if (!ScsiDiskIsDriveReady (SenseData, NumberOfSenseKeys, &RetryLater)) { - if (RetryLater) { - *Action = ACTION_RETRY_COMMAND_LATER; - return EFI_SUCCESS; + 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; + } + // + // 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_DEVICE_ERROR; } - return EFI_SUCCESS; + return ReturnStatus; } /** - 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 + Submit Read(16) command. - @retval EFI_DEVICE_ERROR Indicates that error occurs - @retval EFI_SUCCESS Successfully to read capacity + @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 ScsiRead16Command(). **/ EFI_STATUS -ScsiDiskReadCapacity ( - IN OUT SCSI_DISK_DEV *ScsiDiskDevice, - OUT BOOLEAN *NeedRetry, - OUT EFI_SCSI_SENSE_DATA **SenseDataArray, - OUT UINTN *NumberOfSenseKeys +ScsiDiskRead16 ( + IN SCSI_DISK_DEV *ScsiDiskDevice, + OUT BOOLEAN *NeedRetry, + IN UINT64 Timeout, + OUT UINT8 *DataBuffer, + IN OUT UINT32 *DataLength, + IN UINT64 StartLba, + IN UINT32 SectorCount ) { - UINT8 HostAdapterStatus; - UINT8 TargetStatus; - EFI_STATUS CommandStatus; - EFI_STATUS Status; - UINT8 Index; - UINT8 MaxRetry; - UINT8 SenseDataLength; - UINT8 ScsiVersion; - UINT32 DataLength10; - UINT32 DataLength16; - EFI_SCSI_DISK_CAPACITY_DATA CapacityData10; - EFI_SCSI_DISK_CAPACITY_DATA16 CapacityData16; - - - 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)); + UINT8 SenseDataLength; + EFI_STATUS Status; + EFI_STATUS ReturnStatus; + UINT8 HostAdapterStatus; + UINT8 TargetStatus; + UINTN Action; - *NumberOfSenseKeys = 0; + // + // 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; - ScsiVersion = (UINT8)(ScsiDiskDevice->InquiryData.Version & 0x03); + 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 || ReturnStatus == EFI_BAD_BUFFER_SIZE) { + *NeedRetry = TRUE; + return EFI_DEVICE_ERROR; + } else if ((ReturnStatus == EFI_INVALID_PARAMETER) || (ReturnStatus == EFI_UNSUPPORTED)) { + *NeedRetry = FALSE; + return ReturnStatus; + } - if (ScsiVersion < SCSI_COMMAND_VERSION_3) { - // - // submit Read Capacity(10) Command. in this call,not request sense data - // - CommandStatus = ScsiReadCapacityCommand ( - ScsiDiskDevice->ScsiIo, - EFI_TIMER_PERIOD_SECONDS(1), - NULL, - &SenseDataLength, - &HostAdapterStatus, - &TargetStatus, - (VOID *) &CapacityData10, - &DataLength10, - FALSE - ); - } else { - // - // submit Read Capacity(16) Command to get parameter LogicalBlocksPerPhysicalBlock - // and LowestAlignedLba - // - CommandStatus = ScsiReadCapacity16Command ( - ScsiDiskDevice->ScsiIo, - EFI_TIMER_PERIOD_SECONDS (1), - NULL, - &SenseDataLength, - &HostAdapterStatus, - &TargetStatus, - (VOID *) &CapacityData16, - &DataLength16, - FALSE - ); - } - // - // no need to check HostAdapterStatus and TargetStatus - // - if (CommandStatus == EFI_SUCCESS) { - GetMediaInfo (ScsiDiskDevice, &CapacityData10,&CapacityData16); - return EFI_SUCCESS; - - } else 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) { + // + // 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 // @@ -1271,610 +3926,917 @@ ScsiDiskReadCapacity ( 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)) { + 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; - } - - if (!*NeedRetry) { + } 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; } } - // - // ScsiDiskRequestSenseKeys() failed after several rounds of retry. - // set *NeedRetry = FALSE to avoid the outside caller try again. - // - *NeedRetry = FALSE; - return EFI_DEVICE_ERROR; + + return ReturnStatus; } + /** - Check the HostAdapter status and re-interpret it in EFI_STATUS. + Submit Write(16) Command. - @param HostAdapterStatus Host Adapter status + @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_SUCCESS Host adapter is OK. - @retval EFI_TIMEOUT Timeout. - @retval EFI_NOT_READY Adapter NOT ready. - @retval EFI_DEVICE_ERROR Adapter device error. + @return EFI_STATUS is returned by calling ScsiWrite16Command(). **/ EFI_STATUS -CheckHostAdapterStatus ( - IN UINT8 HostAdapterStatus +ScsiDiskWrite16 ( + IN SCSI_DISK_DEV *ScsiDiskDevice, + OUT BOOLEAN *NeedRetry, + IN UINT64 Timeout, + IN UINT8 *DataBuffer, + IN OUT UINT32 *DataLength, + IN UINT64 StartLba, + IN UINT32 SectorCount ) { - 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; + EFI_STATUS Status; + EFI_STATUS ReturnStatus; + UINT8 SenseDataLength; + UINT8 HostAdapterStatus; + UINT8 TargetStatus; + UINTN Action; - case EFI_EXT_SCSI_STATUS_HOST_ADAPTER_BUS_FREE: - case EFI_EXT_SCSI_STATUS_HOST_ADAPTER_PHASE_ERROR: + // + // 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 || ReturnStatus == EFI_BAD_BUFFER_SIZE) { + *NeedRetry = TRUE; return EFI_DEVICE_ERROR; - - default: - return EFI_SUCCESS; + } else if ((ReturnStatus == EFI_INVALID_PARAMETER) || (ReturnStatus == EFI_UNSUPPORTED)) { + *NeedRetry = FALSE; + return ReturnStatus; } -} - - -/** - 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; + // + // 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; + } - case EFI_EXT_SCSI_STATUS_TARGET_RESERVATION_CONFLICT: + Status = CheckTargetStatus (TargetStatus); + if (Status == EFI_NOT_READY) { + // + // reset the scsi device + // + ScsiDiskDevice->ScsiIo->ResetDevice (ScsiDiskDevice->ScsiIo); + *NeedRetry = TRUE; return EFI_DEVICE_ERROR; - break; + } else if (Status == EFI_DEVICE_ERROR) { + *NeedRetry = FALSE; + return EFI_DEVICE_ERROR; + } - default: - return EFI_SUCCESS; + 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 ReturnStatus; } /** - 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 + 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. - @retval EFI_DEVICE_ERROR Indicates that error occurs - @retval EFI_SUCCESS Successfully to request sense key + @param Event The instance of EFI_EVENT. + @param Context The parameter passed in. **/ -EFI_STATUS -ScsiDiskRequestSenseKeys ( - IN OUT SCSI_DISK_DEV *ScsiDiskDevice, - OUT BOOLEAN *NeedRetry, - OUT EFI_SCSI_SENSE_DATA **SenseDataArray, - OUT UINTN *NumberOfSenseKeys, - IN BOOLEAN AskResetIfError +VOID +EFIAPI +ScsiDiskNotify ( + IN EFI_EVENT Event, + IN VOID *Context ) { - EFI_SCSI_SENSE_DATA *PtrSenseData; - UINT8 SenseDataLength; - BOOLEAN SenseReq; - EFI_STATUS Status; - EFI_STATUS FallStatus; - UINT8 HostAdapterStatus; - UINT8 TargetStatus; - - FallStatus = EFI_SUCCESS; - SenseDataLength = sizeof (EFI_SCSI_SENSE_DATA); + 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; - ZeroMem ( - ScsiDiskDevice->SenseData, - sizeof (EFI_SCSI_SENSE_DATA) * (ScsiDiskDevice->SenseDataNumber) - ); + // + // If previous sub-tasks already fails, no need to process this sub-task. + // + if (Token->TransactionStatus != EFI_SUCCESS) { + goto Exit; + } - *NumberOfSenseKeys = 0; - *SenseDataArray = ScsiDiskDevice->SenseData; - PtrSenseData = ScsiDiskDevice->SenseData; + // + // 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; + } - for (SenseReq = TRUE; SenseReq;) { - Status = ScsiRequestSenseCommand ( - ScsiDiskDevice->ScsiIo, - EFI_TIMER_PERIOD_SECONDS (2), - 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; + 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 (EFI_ERROR (FallStatus)) { - if (*NumberOfSenseKeys != 0) { - *NeedRetry = FALSE; - return EFI_SUCCESS; + 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 { - return EFI_DEVICE_ERROR; + 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; } + } - (*NumberOfSenseKeys) += 1; + // + // This sub-task succeeds, no need to retry. + // + goto Exit; +Retry: + if (Request->InBuffer != NULL) { // - // no more sense key or number of sense keys exceeds predefined, - // skip the loop. + // SCSI read command // - if ((PtrSenseData->Sense_Key == EFI_SCSI_SK_NO_SENSE) || - (*NumberOfSenseKeys == ScsiDiskDevice->SenseDataNumber)) { - SenseReq = FALSE; + 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 + ); } - PtrSenseData += 1; - } - return EFI_SUCCESS; -} - - -/** - Get information from media read capacity command. - - @param ScsiDiskDevice The pointer of SCSI_DISK_DEV - @param Capacity The pointer of EFI_SCSI_DISK_CAPACITY_DATA - -**/ -VOID -GetMediaInfo ( - IN OUT SCSI_DISK_DEV *ScsiDiskDevice, - EFI_SCSI_DISK_CAPACITY_DATA *Capacity10, - EFI_SCSI_DISK_CAPACITY_DATA16 *Capacity16 - ) -{ - UINT8 ScsiVersion; - UINT8 *Ptr; - - ScsiVersion = (UINT8)(ScsiDiskDevice->InquiryData.Version & 0x03); - ScsiDiskDevice->BlkIo.Media->LowestAlignedLba = 0; - ScsiDiskDevice->BlkIo.Media->LogicalBlocksPerPhysicalBlock = 1; - - if (ScsiVersion < SCSI_COMMAND_VERSION_3) { - 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.Revision = EFI_BLOCK_IO_PROTOCOL_REVISION; + 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 + ); + } - 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 = Capacity16->LogicPerPhysical; - ScsiDiskDevice->BlkIo.Revision = EFI_BLOCK_IO_PROTOCOL_REVISION2; + 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; + } + } } - - ScsiDiskDevice->BlkIo.Media->MediaPresent = TRUE; - - if (ScsiDiskDevice->DeviceType == EFI_SCSI_TYPE_DISK) { - ScsiDiskDevice->BlkIo.Media->BlockSize = 0x200; +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); } - if (ScsiDiskDevice->DeviceType == EFI_SCSI_TYPE_CDROM) { - ScsiDiskDevice->BlkIo.Media->BlockSize = 0x800; - } + FreePool (Request->SenseData); + FreePool (Request); } -/** - 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. + 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(). **/ EFI_STATUS -ScsiDiskReadSectors ( - IN SCSI_DISK_DEV *ScsiDiskDevice, - OUT VOID *Buffer, - IN EFI_LBA Lba, - IN UINTN NumberOfBlocks +ScsiDiskAsyncRead10 ( + IN SCSI_DISK_DEV *ScsiDiskDevice, + IN UINT64 Timeout, + IN UINT8 TimesRetry, + OUT UINT8 *DataBuffer, + IN UINT32 DataLength, + IN UINT32 StartLba, + IN UINT32 SectorCount, + IN OUT SCSI_BLKIO2_REQUEST *BlkIo2Req, + IN EFI_BLOCK_IO2_TOKEN *Token ) { - UINTN BlocksRemaining; - UINT32 Lba32; - UINT8 *PtrBuffer; - UINT32 BlockSize; - UINT32 ByteCount; - UINT32 MaxBlock; - UINT32 SectorCount; - UINT64 Timeout; - EFI_STATUS Status; - UINT8 Index; - UINT8 MaxRetry; - BOOLEAN NeedRetry; - EFI_SCSI_SENSE_DATA *SenseData; - UINTN NumberOfSenseKeys; - - SenseData = NULL; - NumberOfSenseKeys = 0; - - Status = EFI_SUCCESS; + EFI_STATUS Status; + SCSI_ASYNC_RW_REQUEST *Request; + EFI_EVENT AsyncIoEvent; + EFI_TPL OldTpl; - BlocksRemaining = NumberOfBlocks; - BlockSize = ScsiDiskDevice->BlkIo.Media->BlockSize; - // - // limit the data bytes that can be transferred by one Read(10) Command - // - MaxBlock = 65536; - - PtrBuffer = Buffer; - Lba32 = (UINT32) Lba; - - while (BlocksRemaining > 0) { + AsyncIoEvent = NULL; - if (BlocksRemaining <= MaxBlock) { + Request = AllocateZeroPool (sizeof (SCSI_ASYNC_RW_REQUEST)); + if (Request == NULL) { + return EFI_OUT_OF_RESOURCES; + } - SectorCount = (UINT16) BlocksRemaining; - } else { + OldTpl = gBS->RaiseTPL (TPL_NOTIFY); + InsertTailList (&BlkIo2Req->ScsiRWQueue, &Request->Link); + gBS->RestoreTPL (OldTpl); - SectorCount = MaxBlock; - } + 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; + } - ByteCount = SectorCount * BlockSize; - Timeout = EFI_TIMER_PERIOD_SECONDS (2); + Request->ScsiDiskDevice = ScsiDiskDevice; + Request->Timeout = Timeout; + Request->TimesRetry = TimesRetry; + Request->InBuffer = DataBuffer; + Request->DataLength = DataLength; + Request->StartLba = StartLba; + Request->SectorCount = SectorCount; + Request->BlkIo2Req = BlkIo2Req; - MaxRetry = 2; - for (Index = 0; Index < MaxRetry; Index++) { + // + // Create Event + // + Status = gBS->CreateEvent ( + EVT_NOTIFY_SIGNAL, + TPL_NOTIFY, + ScsiDiskNotify, + Request, + &AsyncIoEvent + ); + if (EFI_ERROR(Status)) { + goto ErrorExit; + } - Status = ScsiDiskRead10 ( - ScsiDiskDevice, - &NeedRetry, - &SenseData, - &NumberOfSenseKeys, - Timeout, - PtrBuffer, - &ByteCount, - Lba32, - SectorCount - ); - if (!EFI_ERROR (Status)) { - break; - } + 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; + } - if (!NeedRetry) { - return EFI_DEVICE_ERROR; - } + return EFI_SUCCESS; - } +ErrorExit: + if (AsyncIoEvent != NULL) { + gBS->CloseEvent (AsyncIoEvent); + } - if ((Index == MaxRetry) && (Status != EFI_SUCCESS)) { - return EFI_DEVICE_ERROR; + if (Request != NULL) { + if (Request->SenseData != NULL) { + FreePool (Request->SenseData); } - // - // actual transferred sectors - // - SectorCount = ByteCount / BlockSize; + OldTpl = gBS->RaiseTPL (TPL_NOTIFY); + RemoveEntryList (&Request->Link); + gBS->RestoreTPL (OldTpl); - Lba32 += SectorCount; - PtrBuffer = PtrBuffer + SectorCount * BlockSize; - BlocksRemaining -= SectorCount; + FreePool (Request); } - return EFI_SUCCESS; + return Status; } -/** - 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. +/** + 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 -ScsiDiskWriteSectors ( - IN SCSI_DISK_DEV *ScsiDiskDevice, - IN VOID *Buffer, - IN EFI_LBA Lba, - IN UINTN NumberOfBlocks +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 ) { - UINTN BlocksRemaining; - UINT32 Lba32; - UINT8 *PtrBuffer; - UINT32 BlockSize; - UINT32 ByteCount; - UINT32 MaxBlock; - UINT32 SectorCount; - UINT64 Timeout; - EFI_STATUS Status; - UINT8 Index; - UINT8 MaxRetry; - BOOLEAN NeedRetry; - EFI_SCSI_SENSE_DATA *SenseData; - UINTN NumberOfSenseKeys; + EFI_STATUS Status; + SCSI_ASYNC_RW_REQUEST *Request; + EFI_EVENT AsyncIoEvent; + EFI_TPL OldTpl; - SenseData = NULL; - NumberOfSenseKeys = 0; + AsyncIoEvent = NULL; - Status = EFI_SUCCESS; + Request = AllocateZeroPool (sizeof (SCSI_ASYNC_RW_REQUEST)); + if (Request == NULL) { + return EFI_OUT_OF_RESOURCES; + } - BlocksRemaining = NumberOfBlocks; - BlockSize = ScsiDiskDevice->BlkIo.Media->BlockSize; - // - // limit the data bytes that can be transferred by one Write(10) Command - // - MaxBlock = 65536; + OldTpl = gBS->RaiseTPL (TPL_NOTIFY); + InsertTailList (&BlkIo2Req->ScsiRWQueue, &Request->Link); + gBS->RestoreTPL (OldTpl); - PtrBuffer = Buffer; - Lba32 = (UINT32) Lba; + 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; + } - while (BlocksRemaining > 0) { + Request->ScsiDiskDevice = ScsiDiskDevice; + Request->Timeout = Timeout; + Request->TimesRetry = TimesRetry; + Request->OutBuffer = DataBuffer; + Request->DataLength = DataLength; + Request->StartLba = StartLba; + Request->SectorCount = SectorCount; + Request->BlkIo2Req = BlkIo2Req; - if (BlocksRemaining <= MaxBlock) { + // + // Create Event + // + Status = gBS->CreateEvent ( + EVT_NOTIFY_SIGNAL, + TPL_NOTIFY, + ScsiDiskNotify, + Request, + &AsyncIoEvent + ); + if (EFI_ERROR(Status)) { + goto ErrorExit; + } - SectorCount = (UINT16) BlocksRemaining; - } else { + 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; + } - SectorCount = MaxBlock; - } + return EFI_SUCCESS; - ByteCount = SectorCount * BlockSize; - Timeout = EFI_TIMER_PERIOD_SECONDS (2); - MaxRetry = 2; - for (Index = 0; Index < MaxRetry; Index++) { - Status = ScsiDiskWrite10 ( - ScsiDiskDevice, - &NeedRetry, - &SenseData, - &NumberOfSenseKeys, - Timeout, - PtrBuffer, - &ByteCount, - Lba32, - SectorCount - ); - if (!EFI_ERROR (Status)) { - break; - } +ErrorExit: + if (AsyncIoEvent != NULL) { + gBS->CloseEvent (AsyncIoEvent); + } - if (!NeedRetry) { - return EFI_DEVICE_ERROR; - } + if (Request != NULL) { + if (Request->SenseData != NULL) { + FreePool (Request->SenseData); } - if ((Index == MaxRetry) && (Status != EFI_SUCCESS)) { - return EFI_DEVICE_ERROR; - } - // - // actual transferred sectors - // - SectorCount = ByteCount / BlockSize; + OldTpl = gBS->RaiseTPL (TPL_NOTIFY); + RemoveEntryList (&Request->Link); + gBS->RestoreTPL (OldTpl); - Lba32 += SectorCount; - PtrBuffer = PtrBuffer + SectorCount * BlockSize; - BlocksRemaining -= SectorCount; + FreePool (Request); } - return EFI_SUCCESS; + return Status; } /** - Submit Read command. - - @param ScsiDiskDevice The pointer of ScsiDiskDevice - @param NeedRetry The pointer of flag indicates if needs retry if error happens - @param SenseDataArray NOT used yet in this function - @param NumberOfSenseKeys The number of sense key - @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 SectorSize The size of sector + 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(). - @return EFI_STATUS is returned by calling ScsiRead10Command(). **/ EFI_STATUS -ScsiDiskRead10 ( +ScsiDiskAsyncRead16 ( IN SCSI_DISK_DEV *ScsiDiskDevice, - OUT BOOLEAN *NeedRetry, - OUT EFI_SCSI_SENSE_DATA **SenseDataArray, OPTIONAL - OUT UINTN *NumberOfSenseKeys, IN UINT64 Timeout, + IN UINT8 TimesRetry, OUT UINT8 *DataBuffer, - IN OUT UINT32 *DataLength, - IN UINT32 StartLba, - IN UINT32 SectorSize + IN UINT32 DataLength, + IN UINT64 StartLba, + IN UINT32 SectorCount, + IN OUT SCSI_BLKIO2_REQUEST *BlkIo2Req, + IN EFI_BLOCK_IO2_TOKEN *Token ) { - UINT8 SenseDataLength; - EFI_STATUS Status; - UINT8 HostAdapterStatus; - UINT8 TargetStatus; + 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; + + // + // Create Event + // + Status = gBS->CreateEvent ( + EVT_NOTIFY_SIGNAL, + TPL_NOTIFY, + ScsiDiskNotify, + Request, + &AsyncIoEvent + ); + if (EFI_ERROR(Status)) { + goto ErrorExit; + } + + 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; + } + + 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); + } - *NeedRetry = FALSE; - *NumberOfSenseKeys = 0; - SenseDataLength = 0; - Status = ScsiRead10Command ( - ScsiDiskDevice->ScsiIo, - Timeout, - NULL, - &SenseDataLength, - &HostAdapterStatus, - &TargetStatus, - DataBuffer, - DataLength, - StartLba, - SectorSize - ); return Status; } /** - Submit Write Command. - - @param ScsiDiskDevice The pointer of ScsiDiskDevice - @param NeedRetry The pointer of flag indicates if needs retry if error happens - @param SenseDataArray NOT used yet in this function - @param NumberOfSenseKeys The number of sense key - @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 SectorSize The size of sector - - @return EFI_STATUS is returned by calling ScsiWrite10Command(). + 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 -ScsiDiskWrite10 ( +ScsiDiskAsyncWrite16 ( IN SCSI_DISK_DEV *ScsiDiskDevice, - OUT BOOLEAN *NeedRetry, - OUT EFI_SCSI_SENSE_DATA **SenseDataArray, OPTIONAL - OUT UINTN *NumberOfSenseKeys, IN UINT64 Timeout, + IN UINT8 TimesRetry, IN UINT8 *DataBuffer, - IN OUT UINT32 *DataLength, - IN UINT32 StartLba, - IN UINT32 SectorSize + IN UINT32 DataLength, + IN UINT64 StartLba, + IN UINT32 SectorCount, + IN OUT SCSI_BLKIO2_REQUEST *BlkIo2Req, + IN EFI_BLOCK_IO2_TOKEN *Token ) { - EFI_STATUS Status; - UINT8 SenseDataLength; - UINT8 HostAdapterStatus; - UINT8 TargetStatus; + 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 = 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; + } + + 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); + } - *NeedRetry = FALSE; - *NumberOfSenseKeys = 0; - SenseDataLength = 0; - Status = ScsiWrite10Command ( - ScsiDiskDevice->ScsiIo, - Timeout, - NULL, - &SenseDataLength, - &HostAdapterStatus, - &TargetStatus, - DataBuffer, - DataLength, - StartLba, - SectorSize - ); return Status; } @@ -2026,7 +4988,7 @@ ScsiDiskIsHardwareError ( SensePtr = SenseData; for (Index = 0; Index < SenseCounts; Index++) { - + // // Sense Key is EFI_SCSI_SK_HARDWARE_ERROR (0x4) // @@ -2103,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) @@ -2124,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. @@ -2219,7 +5181,7 @@ ScsiDiskHaveSenseKey ( SensePtr = SenseData; for (Index = 0; Index < SenseCounts; Index++) { - + // // Sense Key is SK_NO_SENSE (0x0) // @@ -2265,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; @@ -2303,7 +5265,7 @@ DetermineInstallBlockIo ( return TRUE; } } - + return FALSE; } @@ -2312,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; @@ -2348,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); @@ -2363,22 +5325,155 @@ 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. - @param[in] This Pointer to the EFI_DISK_INFO_PROTOCOL instance. - @param[in,out] InquiryData Pointer to a buffer for the inquiry data. - @param[in,out] InquiryDataSize Pointer to the value for the inquiry data size. + @param[in] This Pointer to the EFI_DISK_INFO_PROTOCOL instance. + @param[in, out] InquiryData Pointer to a buffer for the inquiry data. + @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 @@ -2410,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 + @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 @@ -2433,9 +5528,9 @@ ScsiDiskInfoIdentify ( EFI_STATUS Status; SCSI_DISK_DEV *ScsiDiskDevice; - if (CompareGuid (&This->Interface, &gEfiDiskInfoScsiInterfaceGuid)) { + 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; } @@ -2453,14 +5548,14 @@ 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. - @param[in,out] SenseData Pointer to the SenseData. - @param[in,out] SenseDataSize Size of SenseData in bytes. - @param[out] SenseDataNumber Pointer to the value for the sense data size. + @param[in] This Pointer to the EFI_DISK_INFO_PROTOCOL instance. + @param[in, out] SenseData Pointer to the SenseData. + @param[in, out] SenseDataSize Size of SenseData in bytes. + @param[out] SenseDataNumber Pointer to the value for the sense data size. @retval EFI_SUCCESS The command was accepted without any errors. @retval EFI_NOT_FOUND Device does not support this data class. @@ -2484,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. @@ -2502,7 +5597,7 @@ ScsiDiskInfoWhichIde ( { SCSI_DISK_DEV *ScsiDiskDevice; - if (CompareGuid (&This->Interface, &gEfiDiskInfoScsiInterfaceGuid)) { + if (CompareGuid (&This->Interface, &gEfiDiskInfoScsiInterfaceGuid) || CompareGuid (&This->Interface, &gEfiDiskInfoUfsInterfaceGuid)) { // // This is not an IDE physical device. // @@ -2525,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 @@ -2545,9 +5640,9 @@ AtapiIdentifyDevice ( ZeroMem (Cdb, sizeof (Cdb)); Cdb[0] = ATA_CMD_IDENTIFY_DEVICE; - CommandPacket.Timeout = EFI_TIMER_PERIOD_SECONDS (1); + CommandPacket.Timeout = SCSI_DISK_TIMEOUT; CommandPacket.Cdb = Cdb; - CommandPacket.CdbLength = sizeof (Cdb); + CommandPacket.CdbLength = (UINT8) sizeof (Cdb); CommandPacket.InDataBuffer = &ScsiDiskDevice->IdentifyData; CommandPacket.InTransferLength = sizeof (ScsiDiskDevice->IdentifyData); @@ -2565,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, @@ -2582,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); // @@ -2614,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 { @@ -2625,13 +5720,17 @@ 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); } return; } } while (--IdentifyRetry > 0); + } else if ((DevicePathType (ChildDevicePathNode) == MESSAGING_DEVICE_PATH) && + (DevicePathSubType (ChildDevicePathNode) == MSG_UFS_DP)) { + CopyGuid (&ScsiDiskDevice->DiskInfo.Interface, &gEfiDiskInfoUfsInterfaceGuid); + break; } DevicePathNode = ChildDevicePathNode; }