X-Git-Url: https://git.proxmox.com/?p=mirror_edk2.git;a=blobdiff_plain;f=MdeModulePkg%2FBus%2FScsi%2FScsiDiskDxe%2FScsiDisk.c;h=98e84b4ea8aae04ff02f004cc08155975c83aa2b;hp=d2a751a8ed5d316b33790d275e5fe1aada36e9db;hb=HEAD;hpb=e72a3b3ea82c6520fc195ebc36e198a9ca9ca457 diff --git a/MdeModulePkg/Bus/Scsi/ScsiDiskDxe/ScsiDisk.c b/MdeModulePkg/Bus/Scsi/ScsiDiskDxe/ScsiDisk.c index d2a751a8ed..fbc236cb46 100644 --- a/MdeModulePkg/Bus/Scsi/ScsiDiskDxe/ScsiDisk.c +++ b/MdeModulePkg/Bus/Scsi/ScsiDiskDxe/ScsiDisk.c @@ -1,22 +1,15 @@ /** @file SCSI disk driver that layers on every SCSI IO protocol in the system. -Copyright (c) 2006 - 2008, 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 - 2019, Intel Corporation. All rights reserved.
+Copyright (c) 1985 - 2022, American Megatrends International LLC.
+SPDX-License-Identifier: BSD-2-Clause-Patent **/ - #include "ScsiDisk.h" - -EFI_DRIVER_BINDING_PROTOCOL gScsiDiskDriverBinding = { +EFI_DRIVER_BINDING_PROTOCOL gScsiDiskDriverBinding = { ScsiDiskDriverBindingSupported, ScsiDiskDriverBindingStart, ScsiDiskDriverBindingStop, @@ -25,26 +18,103 @@ EFI_DRIVER_BINDING_PROTOCOL gScsiDiskDriverBinding = { NULL }; +EFI_DISK_INFO_PROTOCOL gScsiDiskInfoProtocolTemplate = { + EFI_DISK_INFO_SCSI_INTERFACE_GUID, + ScsiDiskInfoInquiry, + ScsiDiskInfoIdentify, + ScsiDiskInfoSenseData, + 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)); + } +} + +/** + Remove trailing spaces from the string. + + @param String The ASCII string to remove the trailing spaces. + + @retval the new length of the string. +**/ +UINTN +RemoveTrailingSpaces ( + IN OUT CHAR8 *String + ) +{ + UINTN Length; + + Length = AsciiStrLen (String); + if (Length == 0) { + return 0; + } + + while ((Length > 0) && (String[Length-1] == ' ')) { + Length--; + } + + String[Length] = '\0'; + return Length; +} + /** 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. **/ EFI_STATUS EFIAPI -InitializeScsiDisk( - IN EFI_HANDLE ImageHandle, - IN EFI_SYSTEM_TABLE *SystemTable +InitializeScsiDisk ( + IN EFI_HANDLE ImageHandle, + IN EFI_SYSTEM_TABLE *SystemTable ) { - EFI_STATUS Status; + EFI_STATUS Status; // // Install driver model protocol(s). @@ -59,7 +129,6 @@ InitializeScsiDisk( ); ASSERT_EFI_ERROR (Status); - return Status; } @@ -97,7 +166,7 @@ ScsiDiskDriverBindingSupported ( Status = gBS->OpenProtocol ( Controller, &gEfiScsiIoProtocolGuid, - (VOID **) &ScsiIo, + (VOID **)&ScsiIo, This->DriverBindingHandle, Controller, EFI_OPEN_PROTOCOL_BY_DRIVER @@ -108,7 +177,10 @@ ScsiDiskDriverBindingSupported ( Status = ScsiIo->GetDeviceType (ScsiIo, &DeviceType); if (!EFI_ERROR (Status)) { - if ((DeviceType == EFI_SCSI_TYPE_DISK) || (DeviceType == EFI_SCSI_TYPE_CDROM)) { + if ((DeviceType == EFI_SCSI_TYPE_DISK) || + (DeviceType == EFI_SCSI_TYPE_CDROM) || + (DeviceType == EFI_SCSI_TYPE_WLUN)) + { Status = EFI_SUCCESS; } else { Status = EFI_UNSUPPORTED; @@ -124,7 +196,6 @@ ScsiDiskDriverBindingSupported ( return Status; } - /** Start this driver on ControllerHandle. @@ -159,77 +230,93 @@ ScsiDiskDriverBindingStart ( UINT8 Index; UINT8 MaxRetry; BOOLEAN NeedRetry; + BOOLEAN MustReadCapacity; + CHAR8 VendorStr[VENDOR_IDENTIFICATION_LENGTH + 1]; + CHAR8 ProductStr[PRODUCT_IDENTIFICATION_LENGTH + 1]; + CHAR16 DeviceStr[VENDOR_IDENTIFICATION_LENGTH + PRODUCT_IDENTIFICATION_LENGTH + 2]; - Status = gBS->AllocatePool ( - EfiBootServicesData, - sizeof (SCSI_DISK_DEV), - (VOID **) &ScsiDiskDevice - ); - if (EFI_ERROR (Status)) { - return Status; - } + MustReadCapacity = TRUE; - ZeroMem (ScsiDiskDevice, sizeof (SCSI_DISK_DEV)); + ScsiDiskDevice = (SCSI_DISK_DEV *)AllocateZeroPool (sizeof (SCSI_DISK_DEV)); + if (ScsiDiskDevice == NULL) { + return EFI_OUT_OF_RESOURCES; + } Status = gBS->OpenProtocol ( Controller, &gEfiScsiIoProtocolGuid, - (VOID **) &ScsiIo, + (VOID **)&ScsiIo, This->DriverBindingHandle, Controller, EFI_OPEN_PROTOCOL_BY_DRIVER ); if (EFI_ERROR (Status)) { - gBS->FreePool (ScsiDiskDevice); + FreePool (ScsiDiskDevice); 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->StorageSecurity.ReceiveData = ScsiDiskReceiveData; + ScsiDiskDevice->StorageSecurity.SendData = ScsiDiskSendData; + 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; - break; + 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; - case EFI_SCSI_TYPE_CDROM: - ScsiDiskDevice->BlkIo.Media->BlockSize = 0x800; - break; + case EFI_SCSI_TYPE_WLUN: + MustReadCapacity = FALSE; + break; } + // // The Sense Data Array's initial size is 6 // ScsiDiskDevice->SenseDataNumber = 6; - Status = gBS->AllocatePool ( - EfiBootServicesData, - sizeof (EFI_SCSI_SENSE_DATA) * ScsiDiskDevice->SenseDataNumber, - (VOID **) &(ScsiDiskDevice->SenseData) - ); - if (EFI_ERROR (Status)) { + ScsiDiskDevice->SenseData = (EFI_SCSI_SENSE_DATA *)AllocateZeroPool ( + sizeof (EFI_SCSI_SENSE_DATA) * ScsiDiskDevice->SenseDataNumber + ); + if (ScsiDiskDevice->SenseData == NULL) { gBS->CloseProtocol ( - Controller, - &gEfiScsiIoProtocolGuid, - This->DriverBindingHandle, - Controller - ); - gBS->FreePool (ScsiDiskDevice); - return Status; + Controller, + &gEfiScsiIoProtocolGuid, + This->DriverBindingHandle, + Controller + ); + FreePool (ScsiDiskDevice); + return EFI_OUT_OF_RESOURCES; } - ZeroMem ( - ScsiDiskDevice->SenseData, - sizeof (EFI_SCSI_SENSE_DATA) * ScsiDiskDevice->SenseDataNumber - ); - // - // Retrive device information + // Retrieve device information // MaxRetry = 2; for (Index = 0; Index < MaxRetry; Index++) { @@ -239,65 +326,114 @@ ScsiDiskDriverBindingStart ( } if (!NeedRetry) { - gBS->FreePool (ScsiDiskDevice->SenseData); + FreePool (ScsiDiskDevice->SenseData); gBS->CloseProtocol ( Controller, &gEfiScsiIoProtocolGuid, This->DriverBindingHandle, Controller ); - gBS->FreePool (ScsiDiskDevice); + FreePool (ScsiDiskDevice); return EFI_DEVICE_ERROR; } } + // // The second parameter "TRUE" means must // retrieve media capacity // - Status = ScsiDiskDetectMedia (ScsiDiskDevice, TRUE, &Temp); + Status = ScsiDiskDetectMedia (ScsiDiskDevice, MustReadCapacity, &Temp); if (!EFI_ERROR (Status)) { - Status = gBS->InstallMultipleProtocolInterfaces ( - &Controller, - &gEfiBlockIoProtocolGuid, - &ScsiDiskDevice->BlkIo, - NULL - ); - } - - if (EFI_ERROR (Status)) { - gBS->FreePool (ScsiDiskDevice->SenseData); - gBS->CloseProtocol ( - Controller, - &gEfiScsiIoProtocolGuid, - This->DriverBindingHandle, - Controller - ); - gBS->FreePool (ScsiDiskDevice); - return Status; - } + // + // Determine if Block IO & Block IO2 should be produced on this controller + // handle + // + if (DetermineInstallBlockIo (Controller)) { + InitializeInstallDiskInfo (ScsiDiskDevice, Controller); + Status = gBS->InstallMultipleProtocolInterfaces ( + &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 ((DEBUG_ERROR, "ScsiDisk: Failed to install the Erase Block Protocol! Status = %r\n", Status)); + } + } - ScsiDiskDevice->ControllerNameTable = NULL; - AddUnicodeString2 ( - "eng", - gScsiDiskComponentName.SupportedLanguages, - &ScsiDiskDevice->ControllerNameTable, - L"SCSI Disk Device", - TRUE - ); - AddUnicodeString2 ( - "en", - gScsiDiskComponentName2.SupportedLanguages, - &ScsiDiskDevice->ControllerNameTable, - L"SCSI Disk Device", - FALSE - ); + if (DetermineInstallStorageSecurity (ScsiDiskDevice, Controller)) { + Status = gBS->InstallProtocolInterface ( + &Controller, + &gEfiStorageSecurityCommandProtocolGuid, + EFI_NATIVE_INTERFACE, + &ScsiDiskDevice->StorageSecurity + ); + if (EFI_ERROR (Status)) { + DEBUG ((DEBUG_ERROR, "ScsiDisk: Failed to install the Storage Security Command Protocol! Status = %r\n", Status)); + } + } + CopyMem ( + VendorStr, + &ScsiDiskDevice->InquiryData.Reserved_5_95[VENDOR_IDENTIFICATION_OFFSET], + VENDOR_IDENTIFICATION_LENGTH + ); + VendorStr[VENDOR_IDENTIFICATION_LENGTH] = 0; + RemoveTrailingSpaces (VendorStr); - return EFI_SUCCESS; + CopyMem ( + ProductStr, + &ScsiDiskDevice->InquiryData.Reserved_5_95[PRODUCT_IDENTIFICATION_OFFSET], + PRODUCT_IDENTIFICATION_LENGTH + ); + ProductStr[PRODUCT_IDENTIFICATION_LENGTH] = 0; + RemoveTrailingSpaces (ProductStr); + + UnicodeSPrint (DeviceStr, sizeof (DeviceStr), L"%a %a", VendorStr, ProductStr); + + ScsiDiskDevice->ControllerNameTable = NULL; + AddUnicodeString2 ( + "eng", + gScsiDiskComponentName.SupportedLanguages, + &ScsiDiskDevice->ControllerNameTable, + DeviceStr, + TRUE + ); + AddUnicodeString2 ( + "en", + gScsiDiskComponentName2.SupportedLanguages, + &ScsiDiskDevice->ControllerNameTable, + DeviceStr, + FALSE + ); + return EFI_SUCCESS; + } + } + } + gBS->FreePool (ScsiDiskDevice->SenseData); + gBS->FreePool (ScsiDiskDevice); + gBS->CloseProtocol ( + Controller, + &gEfiScsiIoProtocolGuid, + This->DriverBindingHandle, + Controller + ); + return Status; } - /** Stop this driver on ControllerHandle. @@ -306,7 +442,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 @@ -320,20 +456,21 @@ ScsiDiskDriverBindingStart ( EFI_STATUS EFIAPI ScsiDiskDriverBindingStop ( - IN EFI_DRIVER_BINDING_PROTOCOL *This, - IN EFI_HANDLE Controller, - IN UINTN NumberOfChildren, - IN EFI_HANDLE *ChildHandleBuffer OPTIONAL + IN EFI_DRIVER_BINDING_PROTOCOL *This, + IN EFI_HANDLE Controller, + IN UINTN NumberOfChildren, + 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, &gEfiBlockIoProtocolGuid, - (VOID **) &BlkIo, + (VOID **)&BlkIo, This->DriverBindingHandle, Controller, EFI_OPEN_PROTOCOL_GET_PROTOCOL @@ -342,11 +479,46 @@ ScsiDiskDriverBindingStop ( return Status; } - ScsiDiskDevice = SCSI_DISK_DEV_FROM_THIS (BlkIo); - Status = gBS->UninstallProtocolInterface ( + 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 + &ScsiDiskDevice->BlkIo, + &gEfiBlockIo2ProtocolGuid, + &ScsiDiskDevice->BlkIo2, + &gEfiDiskInfoProtocolGuid, + &ScsiDiskDevice->DiskInfo, + NULL ); if (!EFI_ERROR (Status)) { gBS->CloseProtocol ( @@ -360,6 +532,7 @@ ScsiDiskDriverBindingStop ( return EFI_SUCCESS; } + // // errors met // @@ -376,25 +549,34 @@ ScsiDiskDriverBindingStop ( @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 retured from EFI_SCSI_IO_PROTOCOL.ResetDevice(). + @return EFI_STATUS is returned from EFI_SCSI_IO_PROTOCOL.ResetDevice(). **/ EFI_STATUS EFIAPI ScsiDiskReset ( - IN EFI_BLOCK_IO_PROTOCOL *This, - IN BOOLEAN ExtendedVerification + IN EFI_BLOCK_IO_PROTOCOL *This, + IN BOOLEAN ExtendedVerification ) { - EFI_TPL OldTpl; - SCSI_DISK_DEV *ScsiDiskDevice; - EFI_STATUS Status; + EFI_TPL OldTpl; + SCSI_DISK_DEV *ScsiDiskDevice; + EFI_STATUS Status; 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); - 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; @@ -402,6 +584,11 @@ ScsiDiskReset ( Status = ScsiDiskDevice->ScsiIo->ResetBus (ScsiDiskDevice->ScsiIo); + if (EFI_ERROR (Status)) { + Status = EFI_DEVICE_ERROR; + goto Done; + } + Done: gBS->RestoreTPL (OldTpl); return Status; @@ -427,11 +614,11 @@ Done: EFI_STATUS EFIAPI ScsiDiskReadBlocks ( - IN EFI_BLOCK_IO_PROTOCOL *This, - IN UINT32 MediaId, - IN EFI_LBA Lba, - IN UINTN BufferSize, - OUT VOID *Buffer + IN EFI_BLOCK_IO_PROTOCOL *This, + IN UINT32 MediaId, + IN EFI_LBA Lba, + IN UINTN BufferSize, + OUT VOID *Buffer ) { SCSI_DISK_DEV *ScsiDiskDevice; @@ -442,21 +629,12 @@ 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); - - if (!IS_DEVICE_FIXED(ScsiDiskDevice)) { + MediaChange = FALSE; + OldTpl = gBS->RaiseTPL (TPL_CALLBACK); + ScsiDiskDevice = SCSI_DISK_DEV_FROM_BLKIO (This); + Media = ScsiDiskDevice->BlkIo.Media; + if (!IS_DEVICE_FIXED (ScsiDiskDevice)) { Status = ScsiDiskDetectMedia (ScsiDiskDevice, FALSE, &MediaChange); if (EFI_ERROR (Status)) { Status = EFI_DEVICE_ERROR; @@ -465,20 +643,56 @@ ScsiDiskReadBlocks ( if (MediaChange) { gBS->ReinstallProtocolInterface ( - ScsiDiskDevice->Handle, - &gEfiBlockIoProtocolGuid, - &ScsiDiskDevice->BlkIo, - &ScsiDiskDevice->BlkIo - ); + 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 (DetermineInstallStorageSecurity (ScsiDiskDevice, ScsiDiskDevice->Handle)) { + gBS->ReinstallProtocolInterface ( + ScsiDiskDevice->Handle, + &gEfiStorageSecurityCommandProtocolGuid, + &ScsiDiskDevice->StorageSecurity, + &ScsiDiskDevice->StorageSecurity + ); + } + + 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; + BlockSize = Media->BlockSize; + + if (BlockSize == 0) { + Status = EFI_DEVICE_ERROR; + goto Done; + } - NumberOfBlocks = BufferSize / BlockSize; + NumberOfBlocks = BufferSize / BlockSize; if (!(Media->MediaPresent)) { Status = EFI_NO_MEDIA; @@ -490,6 +704,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; @@ -505,7 +729,7 @@ ScsiDiskReadBlocks ( goto Done; } - if ((Media->IoAlign > 1) && (((UINTN) Buffer & (Media->IoAlign - 1)) != 0)) { + if ((Media->IoAlign > 1) && (((UINTN)Buffer & (Media->IoAlign - 1)) != 0)) { Status = EFI_INVALID_PARAMETER; goto Done; } @@ -534,7 +758,7 @@ Done: @retval EFI_WRITE_PROTECTED The device can not be written to. @retval EFI_DEVICE_ERROR Fail to detect media. @retval EFI_NO_MEDIA Media is not present. - @retval EFI_MEDIA_CHNAGED Media has changed. + @retval EFI_MEDIA_CHANGED Media has changed. @retval EFI_BAD_BUFFER_SIZE The Buffer was not a multiple of the block size of the device. @retval EFI_INVALID_PARAMETER Invalid parameter passed in. @@ -542,11 +766,11 @@ Done: EFI_STATUS EFIAPI ScsiDiskWriteBlocks ( - IN EFI_BLOCK_IO_PROTOCOL *This, - IN UINT32 MediaId, - IN EFI_LBA Lba, - IN UINTN BufferSize, - IN VOID *Buffer + IN EFI_BLOCK_IO_PROTOCOL *This, + IN UINT32 MediaId, + IN EFI_LBA Lba, + IN UINTN BufferSize, + IN VOID *Buffer ) { SCSI_DISK_DEV *ScsiDiskDevice; @@ -557,21 +781,12 @@ 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); - - if (!IS_DEVICE_FIXED(ScsiDiskDevice)) { + MediaChange = FALSE; + OldTpl = gBS->RaiseTPL (TPL_CALLBACK); + ScsiDiskDevice = SCSI_DISK_DEV_FROM_BLKIO (This); + Media = ScsiDiskDevice->BlkIo.Media; + if (!IS_DEVICE_FIXED (ScsiDiskDevice)) { Status = ScsiDiskDetectMedia (ScsiDiskDevice, FALSE, &MediaChange); if (EFI_ERROR (Status)) { Status = EFI_DEVICE_ERROR; @@ -580,20 +795,56 @@ ScsiDiskWriteBlocks ( if (MediaChange) { gBS->ReinstallProtocolInterface ( - ScsiDiskDevice->Handle, - &gEfiBlockIoProtocolGuid, - &ScsiDiskDevice->BlkIo, - &ScsiDiskDevice->BlkIo - ); + 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 (DetermineInstallStorageSecurity (ScsiDiskDevice, ScsiDiskDevice->Handle)) { + gBS->ReinstallProtocolInterface ( + ScsiDiskDevice->Handle, + &gEfiStorageSecurityCommandProtocolGuid, + &ScsiDiskDevice->StorageSecurity, + &ScsiDiskDevice->StorageSecurity + ); + } + + 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; + BlockSize = Media->BlockSize; + + if (BlockSize == 0) { + Status = EFI_DEVICE_ERROR; + goto Done; + } - NumberOfBlocks = BufferSize / BlockSize; + NumberOfBlocks = BufferSize / BlockSize; if (!(Media->MediaPresent)) { Status = EFI_NO_MEDIA; @@ -605,6 +856,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; @@ -620,10 +886,11 @@ ScsiDiskWriteBlocks ( goto Done; } - if ((Media->IoAlign > 1) && (((UINTN) Buffer & (Media->IoAlign - 1)) != 0)) { + 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. @@ -648,7 +915,7 @@ Done: EFI_STATUS EFIAPI ScsiDiskFlushBlocks ( - IN EFI_BLOCK_IO_PROTOCOL *This + IN EFI_BLOCK_IO_PROTOCOL *This ) { // @@ -657,262 +924,3729 @@ ScsiDiskFlushBlocks ( return EFI_SUCCESS; } - /** - Dectect 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; - - Status = EFI_SUCCESS; - ReadCapacityStatus = EFI_SUCCESS; - SenseData = NULL; - NumberOfSenseKeys = 0; - NeedReadCapacity = FALSE; - CopyMem (&OldMedia, ScsiDiskDevice->BlkIo.Media, sizeof (OldMedia)); - *MediaChange = FALSE; - MaxRetry = 3; + EFI_TPL OldTpl; + SCSI_DISK_DEV *ScsiDiskDevice; + EFI_STATUS Status; - for (Index = 0; Index < MaxRetry; Index++) { - Status = ScsiDiskTestUnitReady ( - ScsiDiskDevice, - &NeedRetry, - &SenseData, - &NumberOfSenseKeys - ); - if (!EFI_ERROR (Status)) { - break; - } + OldTpl = gBS->RaiseTPL (TPL_CALLBACK); - if (!NeedRetry) { - return Status; + ScsiDiskDevice = SCSI_DISK_DEV_FROM_BLKIO2 (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 ((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; + Status = EFI_DEVICE_ERROR; + goto Done; } - // - // ACTION_NO_ACTION: need not read capacity + +Done: + gBS->RestoreTPL (OldTpl); + return Status; +} + +/** + The function is to Read Block from 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 read request was queued if Token-> Event is + not NULL. The data was read correctly from the + device if theToken-> Event is NULL. + @retval EFI_DEVICE_ERROR The device reported an error while attempting + to perform the read operation. + @retval EFI_NO_MEDIA There is no media in the device. + @retval EFI_MEDIA_CHANGED The MediaId is not for the current media. + @retval EFI_BAD_BUFFER_SIZE The BufferSize parameter is not a multiple of + the intrinsic block size of the device. + @retval EFI_INVALID_PARAMETER The read request contains LBAs that are not + valid, or the buffer is not on proper + alignment. + @retval EFI_OUT_OF_RESOURCES The request could not be completed due to a + lack of resources. + +**/ +EFI_STATUS +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; + + 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 (DetermineInstallStorageSecurity (ScsiDiskDevice, ScsiDiskDevice->Handle)) { + gBS->ReinstallProtocolInterface ( + ScsiDiskDevice->Handle, + &gEfiStorageSecurityCommandProtocolGuid, + &ScsiDiskDevice->StorageSecurity, + &ScsiDiskDevice->StorageSecurity + ); + } + + if (Media->MediaPresent) { + Status = EFI_MEDIA_CHANGED; + } else { + Status = EFI_NO_MEDIA; + } + + goto Done; + } + } + + // + // Get the intrinsic block size + // + BlockSize = Media->BlockSize; + + if (BlockSize == 0) { + Status = EFI_DEVICE_ERROR; + goto Done; + } + + 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 (DetermineInstallStorageSecurity (ScsiDiskDevice, ScsiDiskDevice->Handle)) { + gBS->ReinstallProtocolInterface ( + ScsiDiskDevice->Handle, + &gEfiStorageSecurityCommandProtocolGuid, + &ScsiDiskDevice->StorageSecurity, + &ScsiDiskDevice->StorageSecurity + ); + } + + if (Media->MediaPresent) { + Status = EFI_MEDIA_CHANGED; + } else { + Status = EFI_NO_MEDIA; + } + + goto Done; + } + } + + // + // Get the intrinsic block size + // + BlockSize = Media->BlockSize; + + if (BlockSize == 0) { + Status = EFI_DEVICE_ERROR; + goto Done; + } + + 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 (DetermineInstallStorageSecurity (ScsiDiskDevice, ScsiDiskDevice->Handle)) { + gBS->ReinstallProtocolInterface ( + ScsiDiskDevice->Handle, + &gEfiStorageSecurityCommandProtocolGuid, + &ScsiDiskDevice->StorageSecurity, + &ScsiDiskDevice->StorageSecurity + ); + } + + 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 (( + DEBUG_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 (( + DEBUG_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 (( + DEBUG_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 (( + DEBUG_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 + ); + } + + if (DetermineInstallStorageSecurity (ScsiDiskDevice, ScsiDiskDevice->Handle)) { + gBS->ReinstallProtocolInterface ( + ScsiDiskDevice->Handle, + &gEfiStorageSecurityCommandProtocolGuid, + &ScsiDiskDevice->StorageSecurity, + &ScsiDiskDevice->StorageSecurity + ); + } + + 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; +} + +/** + Send a security protocol command to a device that receives data and/or the result + of one or more commands sent by SendData. + + The ReceiveData function sends a security protocol command to the given MediaId. + The security protocol command sent is defined by SecurityProtocolId and contains + the security protocol specific data SecurityProtocolSpecificData. The function + returns the data from the security protocol command in PayloadBuffer. + + For devices supporting the SCSI command set, the security protocol command is sent + using the SECURITY PROTOCOL IN command defined in SPC-4. + + If PayloadBufferSize is too small to store the available data from the security + protocol command, the function shall copy PayloadBufferSize bytes into the + PayloadBuffer and return EFI_WARN_BUFFER_TOO_SMALL. + + If PayloadBuffer or PayloadTransferSize is NULL and PayloadBufferSize is non-zero, + the function shall return EFI_INVALID_PARAMETER. + + If the given MediaId does not support security protocol commands, the function shall + return EFI_UNSUPPORTED. If there is no media in the device, the function returns + EFI_NO_MEDIA. If the MediaId is not the ID for the current media in the device, + the function returns EFI_MEDIA_CHANGED. + + If the security protocol fails to complete within the Timeout period, the function + shall return EFI_TIMEOUT. + + If the security protocol command completes without an error, the function shall + return EFI_SUCCESS. If the security protocol command completes with an error, the + function shall return EFI_DEVICE_ERROR. + + @param This Indicates a pointer to the calling context. + @param MediaId ID of the medium to receive data from. + @param Timeout The timeout, in 100ns units, to use for the execution + of the security protocol command. A Timeout value of 0 + means that this function will wait indefinitely for the + security protocol command to execute. If Timeout is greater + than zero, then this function will return EFI_TIMEOUT if the + time required to execute the receive data command is greater than Timeout. + @param SecurityProtocolId The value of the "Security Protocol" parameter of + the security protocol command to be sent. + @param SecurityProtocolSpecificData The value of the "Security Protocol Specific" parameter + of the security protocol command to be sent. + @param PayloadBufferSize Size in bytes of the payload data buffer. + @param PayloadBuffer A pointer to a destination buffer to store the security + protocol command specific payload data for the security + protocol command. The caller is responsible for having + either implicit or explicit ownership of the buffer. + @param PayloadTransferSize A pointer to a buffer to store the size in bytes of the + data written to the payload data buffer. + + @retval EFI_SUCCESS The security protocol command completed successfully. + @retval EFI_WARN_BUFFER_TOO_SMALL The PayloadBufferSize was too small to store the available + data from the device. The PayloadBuffer contains the truncated data. + @retval EFI_UNSUPPORTED The given MediaId does not support security protocol commands. + @retval EFI_DEVICE_ERROR The security protocol command completed with an error. + @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_INVALID_PARAMETER The PayloadBuffer or PayloadTransferSize is NULL and + PayloadBufferSize is non-zero. + @retval EFI_TIMEOUT A timeout occurred while waiting for the security + protocol command to execute. + +**/ +EFI_STATUS +EFIAPI +ScsiDiskReceiveData ( + IN EFI_STORAGE_SECURITY_COMMAND_PROTOCOL *This, + IN UINT32 MediaId OPTIONAL, + IN UINT64 Timeout, + IN UINT8 SecurityProtocolId, + IN UINT16 SecurityProtocolSpecificData, + IN UINTN PayloadBufferSize, + OUT VOID *PayloadBuffer, + OUT UINTN *PayloadTransferSize + ) +{ + SCSI_DISK_DEV *ScsiDiskDevice; + EFI_BLOCK_IO_MEDIA *Media; + EFI_STATUS Status; + BOOLEAN MediaChange; + EFI_TPL OldTpl; + UINT8 SenseDataLength; + UINT8 HostAdapterStatus; + UINT8 TargetStatus; + VOID *AlignedBuffer; + BOOLEAN AlignedBufferAllocated; + + AlignedBuffer = NULL; + MediaChange = FALSE; + AlignedBufferAllocated = FALSE; + OldTpl = gBS->RaiseTPL (TPL_CALLBACK); + ScsiDiskDevice = SCSI_DISK_DEV_FROM_STORSEC (This); + Media = ScsiDiskDevice->BlkIo.Media; + + SenseDataLength = (UINT8)(ScsiDiskDevice->SenseDataNumber * sizeof (EFI_SCSI_SENSE_DATA)); + + 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 (DetermineInstallStorageSecurity (ScsiDiskDevice, ScsiDiskDevice->Handle)) { + gBS->ReinstallProtocolInterface ( + ScsiDiskDevice->Handle, + &gEfiStorageSecurityCommandProtocolGuid, + &ScsiDiskDevice->StorageSecurity, + &ScsiDiskDevice->StorageSecurity + ); + } + + if (Media->MediaPresent) { + Status = EFI_MEDIA_CHANGED; + } else { + Status = EFI_NO_MEDIA; + } + + goto Done; + } + } + + // + // Validate Media + // + if (!(Media->MediaPresent)) { + Status = EFI_NO_MEDIA; + goto Done; + } + + if ((MediaId != 0) && (MediaId != Media->MediaId)) { + Status = EFI_MEDIA_CHANGED; + goto Done; + } + + if (PayloadBufferSize != 0) { + if ((PayloadBuffer == NULL) || (PayloadTransferSize == NULL)) { + Status = EFI_INVALID_PARAMETER; + goto Done; + } + + if ((ScsiDiskDevice->ScsiIo->IoAlign > 1) && !IS_ALIGNED (PayloadBuffer, ScsiDiskDevice->ScsiIo->IoAlign)) { + AlignedBuffer = AllocateAlignedBuffer (ScsiDiskDevice, PayloadBufferSize); + if (AlignedBuffer == NULL) { + Status = EFI_OUT_OF_RESOURCES; + goto Done; + } + + ZeroMem (AlignedBuffer, PayloadBufferSize); + AlignedBufferAllocated = TRUE; + } else { + AlignedBuffer = PayloadBuffer; + } + } + + Status = ScsiSecurityProtocolInCommand ( + ScsiDiskDevice->ScsiIo, + Timeout, + ScsiDiskDevice->SenseData, + &SenseDataLength, + &HostAdapterStatus, + &TargetStatus, + SecurityProtocolId, + SecurityProtocolSpecificData, + FALSE, + PayloadBufferSize, + AlignedBuffer, + PayloadTransferSize + ); + if (EFI_ERROR (Status)) { + goto Done; + } + + if (AlignedBufferAllocated) { + CopyMem (PayloadBuffer, AlignedBuffer, PayloadBufferSize); + } + + if (PayloadBufferSize < *PayloadTransferSize) { + Status = EFI_WARN_BUFFER_TOO_SMALL; + goto Done; + } + + Status = CheckHostAdapterStatus (HostAdapterStatus); + if (EFI_ERROR (Status)) { + goto Done; + } + + Status = CheckTargetStatus (TargetStatus); + if (EFI_ERROR (Status)) { + goto Done; + } + +Done: + if (AlignedBufferAllocated) { + ZeroMem (AlignedBuffer, PayloadBufferSize); + FreeAlignedBuffer (AlignedBuffer, PayloadBufferSize); + } + + gBS->RestoreTPL (OldTpl); + return Status; +} + +/** + Send a security protocol command to a device. + + The SendData function sends a security protocol command containing the payload + PayloadBuffer to the given MediaId. The security protocol command sent is + defined by SecurityProtocolId and contains the security protocol specific data + SecurityProtocolSpecificData. If the underlying protocol command requires a + specific padding for the command payload, the SendData function shall add padding + bytes to the command payload to satisfy the padding requirements. + + For devices supporting the SCSI command set, the security protocol command is sent + using the SECURITY PROTOCOL OUT command defined in SPC-4. + + If PayloadBuffer is NULL and PayloadBufferSize is non-zero, the function shall + return EFI_INVALID_PARAMETER. + + If the given MediaId does not support security protocol commands, the function + shall return EFI_UNSUPPORTED. If there is no media in the device, the function + returns EFI_NO_MEDIA. If the MediaId is not the ID for the current media in the + device, the function returns EFI_MEDIA_CHANGED. + + If the security protocol fails to complete within the Timeout period, the function + shall return EFI_TIMEOUT. + + If the security protocol command completes without an error, the function shall return + EFI_SUCCESS. If the security protocol command completes with an error, the function + shall return EFI_DEVICE_ERROR. + + @param This Indicates a pointer to the calling context. + @param MediaId ID of the medium to receive data from. + @param Timeout The timeout, in 100ns units, to use for the execution + of the security protocol command. A Timeout value of 0 + means that this function will wait indefinitely for the + security protocol command to execute. If Timeout is greater + than zero, then this function will return EFI_TIMEOUT if the + time required to execute the receive data command is greater than Timeout. + @param SecurityProtocolId The value of the "Security Protocol" parameter of + the security protocol command to be sent. + @param SecurityProtocolSpecificData The value of the "Security Protocol Specific" parameter + of the security protocol command to be sent. + @param PayloadBufferSize Size in bytes of the payload data buffer. + @param PayloadBuffer A pointer to a destination buffer to store the security + protocol command specific payload data for the security + protocol command. + + @retval EFI_SUCCESS The security protocol command completed successfully. + @retval EFI_UNSUPPORTED The given MediaId does not support security protocol commands. + @retval EFI_DEVICE_ERROR The security protocol command completed with an error. + @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_INVALID_PARAMETER The PayloadBuffer is NULL and PayloadBufferSize is non-zero. + @retval EFI_TIMEOUT A timeout occurred while waiting for the security + protocol command to execute. + +**/ +EFI_STATUS +EFIAPI +ScsiDiskSendData ( + IN EFI_STORAGE_SECURITY_COMMAND_PROTOCOL *This, + IN UINT32 MediaId OPTIONAL, + IN UINT64 Timeout, + IN UINT8 SecurityProtocolId, + IN UINT16 SecurityProtocolSpecificData, + IN UINTN PayloadBufferSize, + OUT VOID *PayloadBuffer + ) +{ + SCSI_DISK_DEV *ScsiDiskDevice; + EFI_BLOCK_IO_MEDIA *Media; + EFI_STATUS Status; + BOOLEAN MediaChange; + EFI_TPL OldTpl; + UINT8 SenseDataLength; + UINT8 HostAdapterStatus; + UINT8 TargetStatus; + VOID *AlignedBuffer; + BOOLEAN AlignedBufferAllocated; + + AlignedBuffer = NULL; + MediaChange = FALSE; + AlignedBufferAllocated = FALSE; + OldTpl = gBS->RaiseTPL (TPL_CALLBACK); + ScsiDiskDevice = SCSI_DISK_DEV_FROM_STORSEC (This); + Media = ScsiDiskDevice->BlkIo.Media; + + SenseDataLength = (UINT8)(ScsiDiskDevice->SenseDataNumber * sizeof (EFI_SCSI_SENSE_DATA)); + + 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 (DetermineInstallStorageSecurity (ScsiDiskDevice, ScsiDiskDevice->Handle)) { + gBS->ReinstallProtocolInterface ( + ScsiDiskDevice->Handle, + &gEfiStorageSecurityCommandProtocolGuid, + &ScsiDiskDevice->StorageSecurity, + &ScsiDiskDevice->StorageSecurity + ); + } + + if (Media->MediaPresent) { + Status = EFI_MEDIA_CHANGED; + } else { + Status = EFI_NO_MEDIA; + } + + goto Done; + } + } + + // + // Validate Media + // + if (!(Media->MediaPresent)) { + Status = EFI_NO_MEDIA; + goto Done; + } + + if ((MediaId != 0) && (MediaId != Media->MediaId)) { + Status = EFI_MEDIA_CHANGED; + goto Done; + } + + if (Media->ReadOnly) { + Status = EFI_WRITE_PROTECTED; + goto Done; + } + + if (PayloadBufferSize != 0) { + if (PayloadBuffer == NULL) { + Status = EFI_INVALID_PARAMETER; + goto Done; + } + + if ((ScsiDiskDevice->ScsiIo->IoAlign > 1) && !IS_ALIGNED (PayloadBuffer, ScsiDiskDevice->ScsiIo->IoAlign)) { + AlignedBuffer = AllocateAlignedBuffer (ScsiDiskDevice, PayloadBufferSize); + if (AlignedBuffer == NULL) { + Status = EFI_OUT_OF_RESOURCES; + goto Done; + } + + CopyMem (AlignedBuffer, PayloadBuffer, PayloadBufferSize); + AlignedBufferAllocated = TRUE; + } else { + AlignedBuffer = PayloadBuffer; + } + } + + Status = ScsiSecurityProtocolOutCommand ( + ScsiDiskDevice->ScsiIo, + Timeout, + ScsiDiskDevice->SenseData, + &SenseDataLength, + &HostAdapterStatus, + &TargetStatus, + SecurityProtocolId, + SecurityProtocolSpecificData, + FALSE, + PayloadBufferSize, + AlignedBuffer + ); + if (EFI_ERROR (Status)) { + goto Done; + } + + Status = CheckHostAdapterStatus (HostAdapterStatus); + if (EFI_ERROR (Status)) { + goto Done; + } + + Status = CheckTargetStatus (TargetStatus); + if (EFI_ERROR (Status)) { + goto Done; + } + +Done: + if (AlignedBufferAllocated) { + ZeroMem (AlignedBuffer, PayloadBufferSize); + FreeAlignedBuffer (AlignedBuffer, PayloadBufferSize); + } + + 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_NO_ACTION) { - NeedReadCapacity = FALSE; - } else { - NeedReadCapacity = TRUE; + if (Action == ACTION_READ_CAPACITY) { + NeedReadCapacity = TRUE; + } + + // + // READ_CAPACITY command is not supported by any of the UFS WLUNs. + // + if (ScsiDiskDevice->DeviceType == EFI_SCSI_TYPE_WLUN) { + NeedReadCapacity = FALSE; + MustReadCapacity = FALSE; + ScsiDiskDevice->BlkIo.Media->MediaPresent = 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 (( + DEBUG_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 (( + DEBUG_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 (( + DEBUG_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 ((DEBUG_VERBOSE, "ScsiDisk: ScsiDiskIsNoMedia\n")); + return EFI_SUCCESS; + } + + if (ScsiDiskIsMediaChange (SenseData, NumberOfSenseKeys)) { + ScsiDiskDevice->BlkIo.Media->MediaId++; + DEBUG ((DEBUG_VERBOSE, "ScsiDisk: ScsiDiskIsMediaChange!\n")); + return EFI_SUCCESS; + } + + if (ScsiDiskIsResetBefore (SenseData, NumberOfSenseKeys)) { + *Action = ACTION_RETRY_COMMAND_LATER; + DEBUG ((DEBUG_VERBOSE, "ScsiDisk: ScsiDiskIsResetBefore!\n")); + return EFI_SUCCESS; + } + + if (ScsiDiskIsMediaError (SenseData, NumberOfSenseKeys)) { + DEBUG ((DEBUG_VERBOSE, "ScsiDisk: ScsiDiskIsMediaError\n")); + *Action = ACTION_RETRY_WITH_BACKOFF_ALGO; + return EFI_DEVICE_ERROR; + } + + if (ScsiDiskIsHardwareError (SenseData, NumberOfSenseKeys)) { + DEBUG ((DEBUG_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 ((DEBUG_VERBOSE, "ScsiDisk: ScsiDiskDriveNotReady!\n")); + return EFI_SUCCESS; + } + + *Action = ACTION_NO_ACTION; + return EFI_DEVICE_ERROR; + } + + *Action = ACTION_RETRY_WITH_BACKOFF_ALGO; + DEBUG ((DEBUG_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; otherwise, 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 timeout value is rounded up to nearest integer and here an additional 30s is added + // to follow ATA spec in which it mentioned that the device may take up to 30s to respond + // commands in the Standby/Idle mode. + // + Timeout = EFI_TIMER_PERIOD_SECONDS (ByteCount / 2100000 + 31); + + MaxRetry = 2; + for (Index = 0; Index < MaxRetry; Index++) { + if (!ScsiDiskDevice->Cdb16Byte) { + Status = ScsiDiskRead10 ( + ScsiDiskDevice, + &NeedRetry, + Timeout, + PtrBuffer, + &ByteCount, + (UINT32)Lba, + SectorCount + ); + } else { + Status = ScsiDiskRead16 ( + ScsiDiskDevice, + &NeedRetry, + Timeout, + PtrBuffer, + &ByteCount, + Lba, + SectorCount + ); + } + + if (!EFI_ERROR (Status)) { + break; + } + + if (!NeedRetry) { + return EFI_DEVICE_ERROR; + } + + // + // We need to retry. However, if ScsiDiskRead10() or ScsiDiskRead16() has + // lowered ByteCount on output, we must make sure that we lower + // SectorCount accordingly. SectorCount will be encoded in the CDB, and + // it is invalid to request more sectors in the CDB than the entire + // transfer (ie. ByteCount) can carry. + // + // In addition, ByteCount is only expected to go down, or stay unchanged. + // Therefore we don't need to update Timeout: the original timeout should + // accommodate shorter transfers too. + // + NextSectorCount = ByteCount / BlockSize; + if (NextSectorCount < SectorCount) { + SectorCount = NextSectorCount; + // + // Account for any rounding down. + // + ByteCount = SectorCount * BlockSize; + } + } + + if ((Index == MaxRetry) && (Status != EFI_SUCCESS)) { + return EFI_DEVICE_ERROR; + } + + // + // actual transferred sectors + // + SectorCount = ByteCount / BlockSize; + + Lba += SectorCount; + PtrBuffer = PtrBuffer + SectorCount * BlockSize; + BlocksRemaining -= SectorCount; + } + + return EFI_SUCCESS; +} + +/** + Write sector to SCSI Disk. + + @param ScsiDiskDevice The pointer of SCSI_DISK_DEV + @param Buffer The buffer of data to be written into SCSI Disk + @param Lba Logic block address + @param NumberOfBlocks The number of blocks to read + + @retval EFI_DEVICE_ERROR Indicates a device error. + @retval EFI_SUCCESS Operation is successful. + +**/ +EFI_STATUS +ScsiDiskWriteSectors ( + IN SCSI_DISK_DEV *ScsiDiskDevice, + IN VOID *Buffer, + IN EFI_LBA Lba, + IN UINTN NumberOfBlocks + ) +{ + UINTN BlocksRemaining; + UINT8 *PtrBuffer; + UINT32 BlockSize; + UINT32 ByteCount; + UINT32 MaxBlock; + UINT32 SectorCount; + UINT32 NextSectorCount; + UINT64 Timeout; + EFI_STATUS Status; + UINT8 Index; + UINT8 MaxRetry; + BOOLEAN NeedRetry; + + Status = EFI_SUCCESS; + + BlocksRemaining = NumberOfBlocks; + BlockSize = ScsiDiskDevice->BlkIo.Media->BlockSize; + + // + // limit the data bytes that can be transferred by one Read(10) or Read(16) Command + // + if (!ScsiDiskDevice->Cdb16Byte) { + MaxBlock = 0xFFFF; + } else { + MaxBlock = 0xFFFFFFFF; + } + + PtrBuffer = Buffer; + + while (BlocksRemaining > 0) { + if (BlocksRemaining <= MaxBlock) { + if (!ScsiDiskDevice->Cdb16Byte) { + SectorCount = (UINT16)BlocksRemaining; + } else { + SectorCount = (UINT32)BlocksRemaining; + } + } else { + SectorCount = MaxBlock; + } + + ByteCount = SectorCount * BlockSize; + // + // |------------------------|-----------------|------------------|-----------------| + // | ATA Transfer Mode | Transfer Rate | SCSI Interface | Transfer Rate | + // |------------------------|-----------------|------------------|-----------------| + // | PIO Mode 0 | 3.3Mbytes/sec | SCSI-1 | 5Mbytes/sec | + // |------------------------|-----------------|------------------|-----------------| + // | PIO Mode 1 | 5.2Mbytes/sec | Fast SCSI | 10Mbytes/sec | + // |------------------------|-----------------|------------------|-----------------| + // | PIO Mode 2 | 8.3Mbytes/sec | Fast-Wide SCSI | 20Mbytes/sec | + // |------------------------|-----------------|------------------|-----------------| + // | PIO Mode 3 | 11.1Mbytes/sec | Ultra SCSI | 20Mbytes/sec | + // |------------------------|-----------------|------------------|-----------------| + // | PIO Mode 4 | 16.6Mbytes/sec | Ultra Wide SCSI | 40Mbytes/sec | + // |------------------------|-----------------|------------------|-----------------| + // | Single-word DMA Mode 0 | 2.1Mbytes/sec | Ultra2 SCSI | 40Mbytes/sec | + // |------------------------|-----------------|------------------|-----------------| + // | Single-word DMA Mode 1 | 4.2Mbytes/sec | Ultra2 Wide SCSI | 80Mbytes/sec | + // |------------------------|-----------------|------------------|-----------------| + // | Single-word DMA Mode 2 | 8.4Mbytes/sec | Ultra3 SCSI | 160Mbytes/sec | + // |------------------------|-----------------|------------------|-----------------| + // | Multi-word DMA Mode 0 | 4.2Mbytes/sec | Ultra-320 SCSI | 320Mbytes/sec | + // |------------------------|-----------------|------------------|-----------------| + // | Multi-word DMA Mode 1 | 13.3Mbytes/sec | Ultra-640 SCSI | 640Mbytes/sec | + // |------------------------|-----------------|------------------|-----------------| + // + // As ScsiDisk and ScsiBus driver are used to manage SCSI or ATAPI devices, we have to use + // the lowest transfer rate to calculate the possible maximum timeout value for each operation. + // From the above table, we could know 2.1Mbytes per second is lowest one. + // The timeout value is rounded up to nearest integer and here an additional 30s is added + // to follow ATA spec in which it mentioned that the device may take up to 30s to respond + // commands in the Standby/Idle mode. + // + Timeout = EFI_TIMER_PERIOD_SECONDS (ByteCount / 2100000 + 31); + MaxRetry = 2; + for (Index = 0; Index < MaxRetry; Index++) { + if (!ScsiDiskDevice->Cdb16Byte) { + Status = ScsiDiskWrite10 ( + ScsiDiskDevice, + &NeedRetry, + Timeout, + PtrBuffer, + &ByteCount, + (UINT32)Lba, + SectorCount + ); + } else { + Status = ScsiDiskWrite16 ( + ScsiDiskDevice, + &NeedRetry, + Timeout, + PtrBuffer, + &ByteCount, + Lba, + SectorCount + ); + } + + if (!EFI_ERROR (Status)) { + break; + } + + if (!NeedRetry) { + return EFI_DEVICE_ERROR; + } + + // + // We need to retry. However, if ScsiDiskWrite10() or ScsiDiskWrite16() + // has lowered ByteCount on output, we must make sure that we lower + // SectorCount accordingly. SectorCount will be encoded in the CDB, and + // it is invalid to request more sectors in the CDB than the entire + // transfer (ie. ByteCount) can carry. + // + // In addition, ByteCount is only expected to go down, or stay unchanged. + // Therefore we don't need to update Timeout: the original timeout should + // accommodate shorter transfers too. + // + NextSectorCount = ByteCount / BlockSize; + if (NextSectorCount < SectorCount) { + SectorCount = NextSectorCount; + // + // Account for any rounding down. + // + ByteCount = SectorCount * BlockSize; + } + } + + if ((Index == MaxRetry) && (Status != EFI_SUCCESS)) { + return EFI_DEVICE_ERROR; + } + + // + // actual transferred sectors + // + SectorCount = ByteCount / BlockSize; + + Lba += SectorCount; + PtrBuffer = PtrBuffer + SectorCount * BlockSize; + BlocksRemaining -= SectorCount; + } + + return EFI_SUCCESS; +} + +/** + 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 timeout value is rounded up to nearest integer 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; +} + +/** + Asynchronously 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. + @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 +ScsiDiskAsyncWriteSectors ( + IN SCSI_DISK_DEV *ScsiDiskDevice, + IN VOID *Buffer, + IN EFI_LBA Lba, + IN UINTN NumberOfBlocks, + IN EFI_BLOCK_IO2_TOKEN *Token + ) +{ + UINTN BlocksRemaining; + UINT8 *PtrBuffer; + UINT32 BlockSize; + UINT32 ByteCount; + UINT32 MaxBlock; + UINT32 SectorCount; + UINT64 Timeout; + 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 timeout value is rounded up to nearest integer 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)) { + // + // 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; +} + +/** + Submit Read(10) command. + + @param ScsiDiskDevice The pointer of ScsiDiskDevice + @param NeedRetry The pointer of flag indicates if needs retry if error happens + @param Timeout The time to complete the command + @param DataBuffer The buffer to fill with the read out data + @param DataLength The length of buffer + @param StartLba The start logic block address + @param SectorCount The number of blocks to read + + @return EFI_STATUS is returned by calling ScsiRead10Command(). +**/ +EFI_STATUS +ScsiDiskRead10 ( + IN SCSI_DISK_DEV *ScsiDiskDevice, + OUT BOOLEAN *NeedRetry, + IN UINT64 Timeout, + OUT UINT8 *DataBuffer, + IN OUT UINT32 *DataLength, + IN UINT32 StartLba, + IN UINT32 SectorCount + ) +{ + UINT8 SenseDataLength; + EFI_STATUS Status; + EFI_STATUS ReturnStatus; + UINT8 HostAdapterStatus; + UINT8 TargetStatus; + UINTN Action; + + // + // Implement a backoff algorithm 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 algorithm will at first try to execute original request. If the request fails + // with media error sense data or else, it will reduce the transfer length to half and + // try again till the operation succeeds or fails with one sector transfer length. + // +BackOff: + *NeedRetry = FALSE; + Action = ACTION_NO_ACTION; + SenseDataLength = (UINT8)(ScsiDiskDevice->SenseDataNumber * sizeof (EFI_SCSI_SENSE_DATA)); + ReturnStatus = ScsiRead10Command ( + ScsiDiskDevice->ScsiIo, + Timeout, + ScsiDiskDevice->SenseData, + &SenseDataLength, + &HostAdapterStatus, + &TargetStatus, + DataBuffer, + DataLength, + StartLba, + SectorCount + ); + + if ((ReturnStatus == EFI_NOT_READY) || (ReturnStatus == EFI_BAD_BUFFER_SIZE)) { + *NeedRetry = TRUE; + return EFI_DEVICE_ERROR; + } else if ((ReturnStatus == EFI_INVALID_PARAMETER) || (ReturnStatus == EFI_UNSUPPORTED)) { + *NeedRetry = FALSE; + return ReturnStatus; } // - // either NeedReadCapacity is TRUE, or MustReadCapacity is TRUE, - // retrieve capacity via Read Capacity command + // go ahead to check HostAdapterStatus and TargetStatus + // (EFI_TIMEOUT, EFI_DEVICE_ERROR, EFI_WARN_BUFFER_TOO_SMALL) // - if (NeedReadCapacity || MustReadCapacity) { + Status = CheckHostAdapterStatus (HostAdapterStatus); + if ((Status == EFI_TIMEOUT) || (Status == EFI_NOT_READY)) { + *NeedRetry = TRUE; + return EFI_DEVICE_ERROR; + } else if (Status == EFI_DEVICE_ERROR) { // - // retrieve media information + // reset the scsi channel // - MaxRetry = 3; - for (Index = 0; Index < MaxRetry; Index++) { + ScsiDiskDevice->ScsiIo->ResetBus (ScsiDiskDevice->ScsiIo); + *NeedRetry = FALSE; + return EFI_DEVICE_ERROR; + } - ReadCapacityStatus = ScsiDiskReadCapacity ( - ScsiDiskDevice, - &NeedRetry, - &SenseData, - &NumberOfSenseKeys - ); - if (EFI_ERROR (ReadCapacityStatus) && !NeedRetry) { + Status = CheckTargetStatus (TargetStatus); + if (Status == EFI_NOT_READY) { + // + // reset the scsi device + // + ScsiDiskDevice->ScsiIo->ResetDevice (ScsiDiskDevice->ScsiIo); + *NeedRetry = TRUE; + return EFI_DEVICE_ERROR; + } else if (Status == EFI_DEVICE_ERROR) { + *NeedRetry = FALSE; + return EFI_DEVICE_ERROR; + } + + if ((TargetStatus == EFI_EXT_SCSI_STATUS_TARGET_CHECK_CONDITION) || (EFI_ERROR (ReturnStatus))) { + DEBUG ((DEBUG_ERROR, "ScsiDiskRead10: Check Condition happened!\n")); + 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; } + // - // analyze sense key to action - // - Status = DetectMediaParsingSenseKeys ( - ScsiDiskDevice, - SenseData, - NumberOfSenseKeys, - &Action - ); - // - // if Status is error, it may indicate crisis error, - // so return without retry. + // Try again with half length if the sense data shows we need to retry. // - if (EFI_ERROR (Status)) { - return Status; - } + SectorCount >>= 1; + *DataLength = SectorCount * ScsiDiskDevice->BlkIo.Media->BlockSize; + goto BackOff; + } else { + *NeedRetry = FALSE; + return EFI_DEVICE_ERROR; + } + } - switch (Action) { - case ACTION_NO_ACTION: - // - // no retry - // - Index = MaxRetry; - break; + return ReturnStatus; +} - 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; +/** + Submit Write(10) Command. - default: + @param ScsiDiskDevice The pointer of ScsiDiskDevice + @param NeedRetry The pointer of flag indicates if needs retry if error happens + @param Timeout The time to complete the command + @param DataBuffer The buffer to fill with the read out data + @param DataLength The length of buffer + @param StartLba The start logic block address + @param SectorCount The number of blocks to write + + @return EFI_STATUS is returned by calling ScsiWrite10Command(). + +**/ +EFI_STATUS +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 + ) +{ + EFI_STATUS Status; + EFI_STATUS ReturnStatus; + UINT8 SenseDataLength; + UINT8 HostAdapterStatus; + UINT8 TargetStatus; + UINTN Action; + + // + // Implement a backoff algorithm 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 algorithm 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 = 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; + } + + // + // go ahead to check HostAdapterStatus and TargetStatus + // (EFI_TIMEOUT, EFI_DEVICE_ERROR, EFI_WARN_BUFFER_TOO_SMALL) + // + Status = CheckHostAdapterStatus (HostAdapterStatus); + if ((Status == EFI_TIMEOUT) || (Status == EFI_NOT_READY)) { + *NeedRetry = TRUE; + return EFI_DEVICE_ERROR; + } else if (Status == EFI_DEVICE_ERROR) { + // + // reset the scsi channel + // + ScsiDiskDevice->ScsiIo->ResetBus (ScsiDiskDevice->ScsiIo); + *NeedRetry = FALSE; + return EFI_DEVICE_ERROR; + } + + Status = CheckTargetStatus (TargetStatus); + if (Status == EFI_NOT_READY) { + // + // reset the scsi device + // + ScsiDiskDevice->ScsiIo->ResetDevice (ScsiDiskDevice->ScsiIo); + *NeedRetry = TRUE; + return EFI_DEVICE_ERROR; + } else if (Status == EFI_DEVICE_ERROR) { + *NeedRetry = FALSE; + return EFI_DEVICE_ERROR; + } + + if ((TargetStatus == EFI_EXT_SCSI_STATUS_TARGET_CHECK_CONDITION) || (EFI_ERROR (ReturnStatus))) { + DEBUG ((DEBUG_ERROR, "ScsiDiskWrite10: Check Condition happened!\n")); + 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) { // - // other cases, just retry the command + // Jump out if the operation still fails with one sector transfer length. // - break; + *NeedRetry = FALSE; + return EFI_DEVICE_ERROR; } - } - if ((Index == MaxRetry) && EFI_ERROR (ReadCapacityStatus)) { + // + // 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; } } - if (ScsiDiskDevice->BlkIo.Media->MediaId != OldMedia.MediaId) { - // - // Media change information got from the device - // - *MediaChange = TRUE; - } + return ReturnStatus; +} - if (ScsiDiskDevice->BlkIo.Media->ReadOnly != OldMedia.ReadOnly) { - *MediaChange = TRUE; - ScsiDiskDevice->BlkIo.Media->MediaId += 1; +/** + Submit Read(16) command. + + @param ScsiDiskDevice The pointer of ScsiDiskDevice + @param NeedRetry The pointer of flag indicates if needs retry if error happens + @param Timeout The time to complete the command + @param DataBuffer The buffer to fill with the read out data + @param DataLength The length of buffer + @param StartLba The start logic block address + @param SectorCount The number of blocks to read + + @return EFI_STATUS is returned by calling ScsiRead16Command(). +**/ +EFI_STATUS +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 SenseDataLength; + EFI_STATUS Status; + EFI_STATUS ReturnStatus; + UINT8 HostAdapterStatus; + UINT8 TargetStatus; + UINTN Action; + + // + // Implement a backoff algorithm 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 algorithm 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 = 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 (ScsiDiskDevice->BlkIo.Media->BlockSize != OldMedia.BlockSize) { - *MediaChange = TRUE; - ScsiDiskDevice->BlkIo.Media->MediaId += 1; + // + // 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; } - if (ScsiDiskDevice->BlkIo.Media->LastBlock != OldMedia.LastBlock) { - *MediaChange = TRUE; - ScsiDiskDevice->BlkIo.Media->MediaId += 1; + 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 (ScsiDiskDevice->BlkIo.Media->MediaPresent != OldMedia.MediaPresent) { - if (ScsiDiskDevice->BlkIo.Media->MediaPresent) { + if ((TargetStatus == EFI_EXT_SCSI_STATUS_TARGET_CHECK_CONDITION) || (EFI_ERROR (ReturnStatus))) { + DEBUG ((DEBUG_ERROR, "ScsiDiskRead16: Check Condition happened!\n")); + 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; + } + // - // when change from no media to media present, reset the MediaId to 1. + // Try again with half length if the sense data shows we need to retry. // - ScsiDiskDevice->BlkIo.Media->MediaId = 1; + SectorCount >>= 1; + *DataLength = SectorCount * ScsiDiskDevice->BlkIo.Media->BlockSize; + goto BackOff; } else { - // - // when no media, reset the MediaId to zero. - // - ScsiDiskDevice->BlkIo.Media->MediaId = 0; + *NeedRetry = FALSE; + return EFI_DEVICE_ERROR; } - - *MediaChange = TRUE; } - return EFI_SUCCESS; + return ReturnStatus; } - /** - Send out Inquiry command to Device. + Submit Write(16) Command. - @param ScsiDiskDevice The pointer of SCSI_DISK_DEV - @param NeedRetry Indicates if needs try again when error happens + @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 detect media + @return EFI_STATUS is returned by calling ScsiWrite16Command(). **/ EFI_STATUS -ScsiDiskInquiryDevice ( - IN OUT SCSI_DISK_DEV *ScsiDiskDevice, - OUT BOOLEAN *NeedRetry +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 ) { - UINT32 InquiryDataLength; - UINT8 SenseDataLength; - UINT8 HostAdapterStatus; - UINT8 TargetStatus; - EFI_SCSI_SENSE_DATA *SenseDataArray; - UINTN NumberOfSenseKeys; - EFI_STATUS Status; - UINT8 MaxRetry; - UINT8 Index; + EFI_STATUS Status; + EFI_STATUS ReturnStatus; + UINT8 SenseDataLength; + UINT8 HostAdapterStatus; + UINT8 TargetStatus; + UINTN Action; - InquiryDataLength = sizeof (EFI_SCSI_INQUIRY_DATA); - SenseDataLength = 0; + // + // Implement a backoff algorithm 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 algorithm 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; + } else if ((ReturnStatus == EFI_INVALID_PARAMETER) || (ReturnStatus == EFI_UNSUPPORTED)) { + *NeedRetry = FALSE; + return ReturnStatus; + } - Status = ScsiInquiryCommand ( - ScsiDiskDevice->ScsiIo, - EFI_TIMER_PERIOD_SECONDS (1), - NULL, - &SenseDataLength, - &HostAdapterStatus, - &TargetStatus, - (VOID *) &(ScsiDiskDevice->InquiryData), - &InquiryDataLength, - FALSE - ); - // - // no need to check HostAdapterStatus and TargetStatus + // + // 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 // - 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) { - // - // reset the scsi channel - // ScsiDiskDevice->ScsiIo->ResetBus (ScsiDiskDevice->ScsiIo); *NeedRetry = FALSE; return EFI_DEVICE_ERROR; @@ -926,1277 +4660,1760 @@ ScsiDiskInquiryDevice ( 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 SubmitInquiryCommand() failed. - // if ScsiDiskRequestSenseKeys() succeeds at last, - // better retry SubmitInquiryCommand(). (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 ((DEBUG_ERROR, "ScsiDiskWrite16: Check Condition happened!\n")); + DetectMediaParsingSenseKeys (ScsiDiskDevice, ScsiDiskDevice->SenseData, SenseDataLength / sizeof (EFI_SCSI_SENSE_DATA), &Action); + if (Action == ACTION_RETRY_COMMAND_LATER) { *NeedRetry = TRUE; return EFI_DEVICE_ERROR; - } + } else if (Action == ACTION_RETRY_WITH_BACKOFF_ALGO) { + if (SectorCount <= 1) { + // + // Jump out if the operation still fails with one sector transfer length. + // + *NeedRetry = FALSE; + return EFI_DEVICE_ERROR; + } - if (!*NeedRetry) { + // + // 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; } /** - To test deivice. - - 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 + 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 test unit + @param Event The instance of EFI_EVENT. + @param Context The parameter passed in. **/ -EFI_STATUS -ScsiDiskTestUnitReady ( - IN SCSI_DISK_DEV *ScsiDiskDevice, - OUT BOOLEAN *NeedRetry, - OUT EFI_SCSI_SENSE_DATA **SenseDataArray, - OUT UINTN *NumberOfSenseKeys +VOID +EFIAPI +ScsiDiskNotify ( + IN EFI_EVENT Event, + IN VOID *Context ) { - EFI_STATUS Status; - UINT8 SenseDataLength; - UINT8 HostAdapterStatus; - UINT8 TargetStatus; - UINT8 Index; - UINT8 MaxRetry; + 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; - SenseDataLength = 0; - *NumberOfSenseKeys = 0; - - // - // 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 + // If previous sub-tasks already fails, no need to process this sub-task. // - if (Status == EFI_NOT_READY) { - *NeedRetry = TRUE; - return EFI_DEVICE_ERROR; - - } else if ((Status == EFI_INVALID_PARAMETER) || (Status == EFI_UNSUPPORTED)) { - *NeedRetry = FALSE; - return EFI_DEVICE_ERROR; + if (Token->TransactionStatus != EFI_SUCCESS) { + goto Exit; } + // - // go ahead to check HostAdapterStatus and TargetStatus(in case of EFI_DEVICE_ERROR) + // Check HostAdapterStatus and TargetStatus + // (EFI_TIMEOUT, EFI_DEVICE_ERROR, EFI_WARN_BUFFER_TOO_SMALL) // - - Status = CheckHostAdapterStatus (HostAdapterStatus); + Status = CheckHostAdapterStatus (Request->HostAdapterStatus); if ((Status == EFI_TIMEOUT) || (Status == EFI_NOT_READY)) { - *NeedRetry = TRUE; - return EFI_DEVICE_ERROR; - + 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); - *NeedRetry = FALSE; - return EFI_DEVICE_ERROR; + Token->TransactionStatus = EFI_DEVICE_ERROR; + goto Exit; } - Status = CheckTargetStatus (TargetStatus); + Status = CheckTargetStatus (Request->TargetStatus); if (Status == EFI_NOT_READY) { // // reset the scsi device // ScsiDiskDevice->ScsiIo->ResetDevice (ScsiDiskDevice->ScsiIo); - *NeedRetry = TRUE; - return EFI_DEVICE_ERROR; - + if (++Request->TimesRetry > MaxRetry) { + Token->TransactionStatus = EFI_DEVICE_ERROR; + goto Exit; + } else { + goto Retry; + } } else if (Status == EFI_DEVICE_ERROR) { - *NeedRetry = FALSE; - return EFI_DEVICE_ERROR; + Token->TransactionStatus = EFI_DEVICE_ERROR; + goto Exit; } - MaxRetry = 3; - for (Index = 0; Index < MaxRetry; Index++) { - Status = ScsiDiskRequestSenseKeys ( - ScsiDiskDevice, - NeedRetry, - SenseDataArray, - NumberOfSenseKeys, - FALSE - ); - if (!EFI_ERROR (Status)) { - return EFI_SUCCESS; - } + if (Request->TargetStatus == EFI_EXT_SCSI_STATUS_TARGET_CHECK_CONDITION) { + DEBUG ((DEBUG_ERROR, "ScsiDiskNotify: Check Condition happened!\n")); + + DetectMediaParsingSenseKeys ( + ScsiDiskDevice, + Request->SenseData, + Request->SenseDataLength / sizeof (EFI_SCSI_SENSE_DATA), + &Action + ); + if (Action == ACTION_RETRY_COMMAND_LATER) { + if (++Request->TimesRetry > MaxRetry) { + Token->TransactionStatus = EFI_DEVICE_ERROR; + goto Exit; + } else { + goto Retry; + } + } else if (Action == ACTION_RETRY_WITH_BACKOFF_ALGO) { + if (Request->SectorCount <= 1) { + // + // Jump out if the operation still fails with one sector transfer + // length. + // + Token->TransactionStatus = EFI_DEVICE_ERROR; + goto Exit; + } - if (!*NeedRetry) { - return EFI_DEVICE_ERROR; + // + // 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; } } + // - // ScsiDiskRequestSenseKeys() failed after several rounds of retry. - // set *NeedRetry = FALSE to avoid the outside caller try again. + // This sub-task succeeds, no need to retry. // - *NeedRetry = FALSE; - return EFI_DEVICE_ERROR; + goto Exit; + +Retry: + if (Request->InBuffer != NULL) { + // + // SCSI read command + // + if (!ScsiDiskDevice->Cdb16Byte) { + Status = ScsiDiskAsyncRead10 ( + ScsiDiskDevice, + Request->Timeout, + Request->TimesRetry, + Request->InBuffer, + Request->DataLength, + (UINT32)Request->StartLba, + Request->SectorCount, + Request->BlkIo2Req, + Token + ); + } else { + Status = ScsiDiskAsyncRead16 ( + ScsiDiskDevice, + Request->Timeout, + Request->TimesRetry, + Request->InBuffer, + Request->DataLength, + Request->StartLba, + Request->SectorCount, + Request->BlkIo2Req, + Token + ); + } + + if (EFI_ERROR (Status)) { + Token->TransactionStatus = EFI_DEVICE_ERROR; + goto Exit; + } else if (OldSectorCount != Request->SectorCount) { + // + // Original sub-task will be split into two new sub-tasks with smaller + // DataLength + // + if (!ScsiDiskDevice->Cdb16Byte) { + Status = ScsiDiskAsyncRead10 ( + ScsiDiskDevice, + Request->Timeout, + 0, + Request->InBuffer + Request->SectorCount * ScsiDiskDevice->BlkIo.Media->BlockSize, + OldDataLength - Request->DataLength, + (UINT32)Request->StartLba + Request->SectorCount, + OldSectorCount - Request->SectorCount, + Request->BlkIo2Req, + Token + ); + } else { + Status = ScsiDiskAsyncRead16 ( + ScsiDiskDevice, + Request->Timeout, + 0, + Request->InBuffer + Request->SectorCount * ScsiDiskDevice->BlkIo.Media->BlockSize, + OldDataLength - Request->DataLength, + Request->StartLba + Request->SectorCount, + OldSectorCount - Request->SectorCount, + Request->BlkIo2Req, + Token + ); + } + + if (EFI_ERROR (Status)) { + Token->TransactionStatus = EFI_DEVICE_ERROR; + goto Exit; + } + } + } else { + // + // SCSI write command + // + if (!ScsiDiskDevice->Cdb16Byte) { + Status = ScsiDiskAsyncWrite10 ( + ScsiDiskDevice, + Request->Timeout, + Request->TimesRetry, + Request->OutBuffer, + Request->DataLength, + (UINT32)Request->StartLba, + Request->SectorCount, + Request->BlkIo2Req, + Token + ); + } else { + Status = ScsiDiskAsyncWrite16 ( + ScsiDiskDevice, + Request->Timeout, + Request->TimesRetry, + Request->OutBuffer, + Request->DataLength, + Request->StartLba, + Request->SectorCount, + Request->BlkIo2Req, + Token + ); + } + + if (EFI_ERROR (Status)) { + Token->TransactionStatus = EFI_DEVICE_ERROR; + goto Exit; + } else if (OldSectorCount != Request->SectorCount) { + // + // Original sub-task will be split into two new sub-tasks with smaller + // DataLength + // + if (!ScsiDiskDevice->Cdb16Byte) { + Status = ScsiDiskAsyncWrite10 ( + ScsiDiskDevice, + Request->Timeout, + 0, + Request->OutBuffer + Request->SectorCount * ScsiDiskDevice->BlkIo.Media->BlockSize, + OldDataLength - Request->DataLength, + (UINT32)Request->StartLba + Request->SectorCount, + OldSectorCount - Request->SectorCount, + Request->BlkIo2Req, + Token + ); + } else { + Status = ScsiDiskAsyncWrite16 ( + ScsiDiskDevice, + Request->Timeout, + 0, + Request->OutBuffer + Request->SectorCount * ScsiDiskDevice->BlkIo.Media->BlockSize, + OldDataLength - Request->DataLength, + Request->StartLba + Request->SectorCount, + OldSectorCount - Request->SectorCount, + Request->BlkIo2Req, + Token + ); + } + + if (EFI_ERROR (Status)) { + Token->TransactionStatus = EFI_DEVICE_ERROR; + goto Exit; + } + } + } + +Exit: + RemoveEntryList (&Request->Link); + if ((IsListEmpty (&Request->BlkIo2Req->ScsiRWQueue)) && + (Request->BlkIo2Req->LastScsiRW)) + { + // + // The last SCSI R/W command of a BlockIo2 request completes + // + RemoveEntryList (&Request->BlkIo2Req->Link); + FreePool (Request->BlkIo2Req); // Should be freed only once + gBS->SignalEvent (Token->Event); + } + + FreePool (Request->SenseData); + FreePool (Request); } /** - Parsing Sense Keys which got from request sense command. + 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(). - @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 +**/ +EFI_STATUS +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 + ) +{ + EFI_STATUS Status; + SCSI_ASYNC_RW_REQUEST *Request; + EFI_EVENT AsyncIoEvent; + EFI_TPL OldTpl; - @retval EFI_DEVICE_ERROR Indicates that error occurs - @retval EFI_SUCCESS Successfully to complete the parsing + 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 = ScsiRead10CommandEx ( + ScsiDiskDevice->ScsiIo, + Request->Timeout, + Request->SenseData, + &Request->SenseDataLength, + &Request->HostAdapterStatus, + &Request->TargetStatus, + Request->InBuffer, + &Request->DataLength, + (UINT32)Request->StartLba, + Request->SectorCount, + AsyncIoEvent + ); + if (EFI_ERROR (Status)) { + goto ErrorExit; + } + + return EFI_SUCCESS; + +ErrorExit: + if (AsyncIoEvent != NULL) { + gBS->CloseEvent (AsyncIoEvent); + } + + if (Request != NULL) { + if (Request->SenseData != NULL) { + FreePool (Request->SenseData); + } + + OldTpl = gBS->RaiseTPL (TPL_NOTIFY); + RemoveEntryList (&Request->Link); + gBS->RestoreTPL (OldTpl); + + FreePool (Request); + } + + return Status; +} + +/** + Submit Async Write(10) command. + + @param ScsiDiskDevice The pointer of ScsiDiskDevice. + @param Timeout The time to complete the command. + @param TimesRetry The number of times the command has been retried. + @param DataBuffer The buffer contains the data to write. + @param DataLength The length of buffer. + @param StartLba The start logic block address. + @param SectorCount The number of blocks to write. + @param BlkIo2Req The upstream BlockIo2 request. + @param Token The pointer to the token associated with the + non-blocking read request. + + @retval EFI_OUT_OF_RESOURCES The request could not be completed due to a + lack of resources. + @return others Status returned by calling + ScsiWrite10CommandEx(). **/ EFI_STATUS -DetectMediaParsingSenseKeys ( - OUT SCSI_DISK_DEV *ScsiDiskDevice, - IN EFI_SCSI_SENSE_DATA *SenseData, - IN UINTN NumberOfSenseKeys, - OUT UINTN *Action +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 ) { - BOOLEAN RetryLater; + 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; // - // Default is to read capacity, unless.. + // Create Event // - *Action = ACTION_READ_CAPACITY; + Status = gBS->CreateEvent ( + EVT_NOTIFY_SIGNAL, + TPL_NOTIFY, + ScsiDiskNotify, + Request, + &AsyncIoEvent + ); + if (EFI_ERROR (Status)) { + goto ErrorExit; + } - if (NumberOfSenseKeys == 0) { - *Action = ACTION_NO_ACTION; - return EFI_SUCCESS; + 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; } - if (!ScsiDiskHaveSenseKey (SenseData, NumberOfSenseKeys)) { - // - // No Sense Key returned from last submitted command - // - *Action = ACTION_NO_ACTION; - return EFI_SUCCESS; + return EFI_SUCCESS; + +ErrorExit: + if (AsyncIoEvent != NULL) { + gBS->CloseEvent (AsyncIoEvent); } - if (ScsiDiskIsNoMedia (SenseData, NumberOfSenseKeys)) { - ScsiDiskDevice->BlkIo.Media->MediaPresent = FALSE; - ScsiDiskDevice->BlkIo.Media->LastBlock = 0; - *Action = ACTION_NO_ACTION; - return EFI_SUCCESS; + if (Request != NULL) { + if (Request->SenseData != NULL) { + FreePool (Request->SenseData); + } + + OldTpl = gBS->RaiseTPL (TPL_NOTIFY); + RemoveEntryList (&Request->Link); + gBS->RestoreTPL (OldTpl); + + FreePool (Request); + } + + return Status; +} + +/** + Submit Async Read(16) command. + + @param ScsiDiskDevice The pointer of ScsiDiskDevice. + @param Timeout The time to complete the command. + @param TimesRetry The number of times the command has been retried. + @param DataBuffer The buffer to fill with the read out data. + @param DataLength The length of buffer. + @param StartLba The start logic block address. + @param SectorCount The number of blocks to read. + @param BlkIo2Req The upstream BlockIo2 request. + @param Token The pointer to the token associated with the + non-blocking read request. + + @retval EFI_OUT_OF_RESOURCES The request could not be completed due to a + lack of resources. + @return others Status returned by calling + ScsiRead16CommandEx(). + +**/ +EFI_STATUS +ScsiDiskAsyncRead16 ( + IN SCSI_DISK_DEV *ScsiDiskDevice, + IN UINT64 Timeout, + IN UINT8 TimesRetry, + OUT UINT8 *DataBuffer, + IN UINT32 DataLength, + IN UINT64 StartLba, + IN UINT32 SectorCount, + IN OUT SCSI_BLKIO2_REQUEST *BlkIo2Req, + IN EFI_BLOCK_IO2_TOKEN *Token + ) +{ + EFI_STATUS Status; + SCSI_ASYNC_RW_REQUEST *Request; + EFI_EVENT AsyncIoEvent; + EFI_TPL OldTpl; + + AsyncIoEvent = NULL; + + Request = AllocateZeroPool (sizeof (SCSI_ASYNC_RW_REQUEST)); + if (Request == NULL) { + return EFI_OUT_OF_RESOURCES; + } + + OldTpl = gBS->RaiseTPL (TPL_NOTIFY); + InsertTailList (&BlkIo2Req->ScsiRWQueue, &Request->Link); + gBS->RestoreTPL (OldTpl); + + Request->SenseDataLength = (UINT8)(6 * sizeof (EFI_SCSI_SENSE_DATA)); + Request->SenseData = AllocateZeroPool (Request->SenseDataLength); + if (Request->SenseData == NULL) { + Status = EFI_OUT_OF_RESOURCES; + goto ErrorExit; + } + + Request->ScsiDiskDevice = ScsiDiskDevice; + Request->Timeout = Timeout; + Request->TimesRetry = TimesRetry; + Request->InBuffer = DataBuffer; + Request->DataLength = DataLength; + Request->StartLba = StartLba; + Request->SectorCount = SectorCount; + Request->BlkIo2Req = BlkIo2Req; + + // + // Create Event + // + Status = gBS->CreateEvent ( + EVT_NOTIFY_SIGNAL, + TPL_NOTIFY, + ScsiDiskNotify, + Request, + &AsyncIoEvent + ); + if (EFI_ERROR (Status)) { + goto ErrorExit; } - if (ScsiDiskIsMediaChange (SenseData, NumberOfSenseKeys)) { - ScsiDiskDevice->BlkIo.Media->MediaId++; - return EFI_SUCCESS; + Status = ScsiRead16CommandEx ( + ScsiDiskDevice->ScsiIo, + Request->Timeout, + Request->SenseData, + &Request->SenseDataLength, + &Request->HostAdapterStatus, + &Request->TargetStatus, + Request->InBuffer, + &Request->DataLength, + Request->StartLba, + Request->SectorCount, + AsyncIoEvent + ); + if (EFI_ERROR (Status)) { + goto ErrorExit; } - if (ScsiDiskIsMediaError (SenseData, NumberOfSenseKeys)) { - ScsiDiskDevice->BlkIo.Media->MediaPresent = FALSE; - ScsiDiskDevice->BlkIo.Media->LastBlock = 0; - return EFI_DEVICE_ERROR; - } + return EFI_SUCCESS; - if (ScsiDiskIsHardwareError (SenseData, NumberOfSenseKeys)) { - return EFI_DEVICE_ERROR; +ErrorExit: + if (AsyncIoEvent != NULL) { + gBS->CloseEvent (AsyncIoEvent); } - if (!ScsiDiskIsDriveReady (SenseData, NumberOfSenseKeys, &RetryLater)) { - if (RetryLater) { - *Action = ACTION_RETRY_COMMAND_LATER; - return EFI_SUCCESS; + if (Request != NULL) { + if (Request->SenseData != NULL) { + FreePool (Request->SenseData); } - return EFI_DEVICE_ERROR; + OldTpl = gBS->RaiseTPL (TPL_NOTIFY); + RemoveEntryList (&Request->Link); + gBS->RestoreTPL (OldTpl); + + FreePool (Request); } - return EFI_SUCCESS; + return Status; } - /** - Send read capacity command to device and get the device parameter. - - @param ScsiDiskDevice The pointer of SCSI_DISK_DEV - @param NeedRetry The pointer of flag indicates if need a retry - @param SenseDataArray The pointer of an array of sense data - @param NumberOfSenseKeys The number of sense key - - @retval EFI_DEVICE_ERROR Indicates that error occurs - @retval EFI_SUCCESS Successfully to read capacity + 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 -ScsiDiskReadCapacity ( - IN OUT SCSI_DISK_DEV *ScsiDiskDevice, - OUT BOOLEAN *NeedRetry, - OUT EFI_SCSI_SENSE_DATA **SenseDataArray, - OUT UINTN *NumberOfSenseKeys +ScsiDiskAsyncWrite16 ( + IN SCSI_DISK_DEV *ScsiDiskDevice, + IN UINT64 Timeout, + IN UINT8 TimesRetry, + IN UINT8 *DataBuffer, + IN UINT32 DataLength, + IN UINT64 StartLba, + IN UINT32 SectorCount, + IN OUT SCSI_BLKIO2_REQUEST *BlkIo2Req, + IN EFI_BLOCK_IO2_TOKEN *Token ) { - EFI_SCSI_DISK_CAPACITY_DATA CapacityData; - UINT32 DataLength; - UINT8 HostAdapterStatus; - UINT8 TargetStatus; - EFI_STATUS CommandStatus; - EFI_STATUS Status; - UINT8 Index; - UINT8 MaxRetry; - UINT8 SenseDataLength; + EFI_STATUS Status; + SCSI_ASYNC_RW_REQUEST *Request; + EFI_EVENT AsyncIoEvent; + EFI_TPL OldTpl; - SenseDataLength = 0; - ZeroMem (&CapacityData, sizeof (EFI_SCSI_DISK_CAPACITY_DATA)); - DataLength = sizeof (EFI_SCSI_DISK_CAPACITY_DATA); + AsyncIoEvent = NULL; - *NumberOfSenseKeys = 0; - *NeedRetry = FALSE; - // - // submit Read Capacity Command. in this call,not request sense data - // - CommandStatus = ScsiReadCapacityCommand ( - ScsiDiskDevice->ScsiIo, - EFI_TIMER_PERIOD_SECONDS (1), - NULL, - &SenseDataLength, - &HostAdapterStatus, - &TargetStatus, - (VOID *) &CapacityData, - &DataLength, - FALSE - ); - // - // no need to check HostAdapterStatus and TargetStatus - // - if (CommandStatus == EFI_SUCCESS) { - GetMediaInfo (ScsiDiskDevice, &CapacityData); - 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) { - // - // reset the scsi channel - // - ScsiDiskDevice->ScsiIo->ResetBus (ScsiDiskDevice->ScsiIo); - *NeedRetry = FALSE; - return EFI_DEVICE_ERROR; + Request = AllocateZeroPool (sizeof (SCSI_ASYNC_RW_REQUEST)); + if (Request == NULL) { + return EFI_OUT_OF_RESOURCES; } - Status = CheckTargetStatus (TargetStatus); - if (Status == EFI_NOT_READY) { - // - // reset the scsi device - // - ScsiDiskDevice->ScsiIo->ResetDevice (ScsiDiskDevice->ScsiIo); - *NeedRetry = TRUE; - return EFI_DEVICE_ERROR; + OldTpl = gBS->RaiseTPL (TPL_NOTIFY); + InsertTailList (&BlkIo2Req->ScsiRWQueue, &Request->Link); + gBS->RestoreTPL (OldTpl); - } else if (Status == EFI_DEVICE_ERROR) { - *NeedRetry = FALSE; - return EFI_DEVICE_ERROR; + 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; + // - // if goes here, meant SubmitReadCapacityCommand() failed. - // if ScsiDiskRequestSenseKeys() succeeds at last, - // better retry SubmitReadCapacityCommand(). (by setting *NeedRetry = TRUE) + // Create Event // - MaxRetry = 3; - for (Index = 0; Index < MaxRetry; Index++) { + Status = gBS->CreateEvent ( + EVT_NOTIFY_SIGNAL, + TPL_NOTIFY, + ScsiDiskNotify, + Request, + &AsyncIoEvent + ); + if (EFI_ERROR (Status)) { + goto ErrorExit; + } - Status = ScsiDiskRequestSenseKeys ( - ScsiDiskDevice, - NeedRetry, - SenseDataArray, - NumberOfSenseKeys, - TRUE - ); - if (!EFI_ERROR (Status)) { - *NeedRetry = TRUE; - return EFI_DEVICE_ERROR; - } + Status = ScsiWrite16CommandEx ( + ScsiDiskDevice->ScsiIo, + Request->Timeout, + Request->SenseData, + &Request->SenseDataLength, + &Request->HostAdapterStatus, + &Request->TargetStatus, + Request->OutBuffer, + &Request->DataLength, + Request->StartLba, + Request->SectorCount, + AsyncIoEvent + ); + if (EFI_ERROR (Status)) { + goto ErrorExit; + } - if (!*NeedRetry) { - return EFI_DEVICE_ERROR; + return EFI_SUCCESS; + +ErrorExit: + if (AsyncIoEvent != NULL) { + gBS->CloseEvent (AsyncIoEvent); + } + + if (Request != NULL) { + if (Request->SenseData != NULL) { + FreePool (Request->SenseData); } + + OldTpl = gBS->RaiseTPL (TPL_NOTIFY); + RemoveEntryList (&Request->Link); + gBS->RestoreTPL (OldTpl); + + FreePool (Request); } - // - // ScsiDiskRequestSenseKeys() failed after several rounds of retry. - // set *NeedRetry = FALSE to avoid the outside caller try again. - // - *NeedRetry = FALSE; - return EFI_DEVICE_ERROR; + + return Status; } /** - Check the HostAdapter status and re-interpret it in EFI_STATUS. - - @param HostAdapterStatus Host Adapter status + Check sense key to find if media presents. - @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. + @param SenseData The pointer of EFI_SCSI_SENSE_DATA + @param SenseCounts The number of sense key + @retval TRUE NOT any media + @retval FALSE Media presents **/ -EFI_STATUS -CheckHostAdapterStatus ( - IN UINT8 HostAdapterStatus +BOOLEAN +ScsiDiskIsNoMedia ( + IN EFI_SCSI_SENSE_DATA *SenseData, + IN UINTN SenseCounts ) { - 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; + EFI_SCSI_SENSE_DATA *SensePtr; + UINTN Index; + BOOLEAN IsNoMedia; - 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; + IsNoMedia = FALSE; + SensePtr = SenseData; - case EFI_EXT_SCSI_STATUS_HOST_ADAPTER_BUS_FREE: - case EFI_EXT_SCSI_STATUS_HOST_ADAPTER_PHASE_ERROR: - return EFI_DEVICE_ERROR; + for (Index = 0; Index < SenseCounts; Index++) { + // + // Sense Key is EFI_SCSI_SK_NOT_READY (0x2), + // Additional Sense Code is ASC_NO_MEDIA (0x3A) + // + if ((SensePtr->Sense_Key == EFI_SCSI_SK_NOT_READY) && + (SensePtr->Addnl_Sense_Code == EFI_SCSI_ASC_NO_MEDIA)) + { + IsNoMedia = TRUE; + } - default: - return EFI_SUCCESS; + SensePtr++; } -} + return IsNoMedia; +} /** - Check the target status and re-interpret it in EFI_STATUS. + Parse sense key. - @param TargetStatus Target status + @param SenseData The pointer of EFI_SCSI_SENSE_DATA + @param SenseCounts The number of sense key - @retval EFI_NOT_READY Device is NOT ready. - @retval EFI_DEVICE_ERROR - @retval EFI_SUCCESS + @retval TRUE Error + @retval FALSE NOT error **/ -EFI_STATUS -CheckTargetStatus ( - IN UINT8 TargetStatus +BOOLEAN +ScsiDiskIsMediaError ( + IN EFI_SCSI_SENSE_DATA *SenseData, + IN UINTN SenseCounts ) { - 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; + EFI_SCSI_SENSE_DATA *SensePtr; + UINTN Index; + BOOLEAN IsError; - 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; + IsError = FALSE; + SensePtr = SenseData; - case EFI_EXT_SCSI_STATUS_TARGET_RESERVATION_CONFLICT: - return EFI_DEVICE_ERROR; - break; + for (Index = 0; Index < SenseCounts; Index++) { + switch (SensePtr->Sense_Key) { + case EFI_SCSI_SK_MEDIUM_ERROR: + // + // Sense Key is EFI_SCSI_SK_MEDIUM_ERROR (0x3) + // + switch (SensePtr->Addnl_Sense_Code) { + // + // fall through + // + case EFI_SCSI_ASC_MEDIA_ERR1: - default: - return EFI_SUCCESS; - } -} + // + // fall through + // + case EFI_SCSI_ASC_MEDIA_ERR2: + // + // fall through + // + case EFI_SCSI_ASC_MEDIA_ERR3: + case EFI_SCSI_ASC_MEDIA_ERR4: + IsError = TRUE; + break; -/** - Retrieve all sense keys from the device. + default: + break; + } - When encountering error during the process, if retrieve sense keys before - error encounterred, it returns the sense keys with return status set to EFI_SUCCESS, - and NeedRetry set to FALSE; otherwize, return the proper return status. + break; - @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 + case EFI_SCSI_SK_NOT_READY: + // + // Sense Key is EFI_SCSI_SK_NOT_READY (0x2) + // + switch (SensePtr->Addnl_Sense_Code) { + // + // Additional Sense Code is ASC_MEDIA_UPSIDE_DOWN (0x6) + // + case EFI_SCSI_ASC_MEDIA_UPSIDE_DOWN: + IsError = TRUE; + break; - @retval EFI_DEVICE_ERROR Indicates that error occurs - @retval EFI_SUCCESS Successfully to request sense key + default: + break; + } -**/ -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; + break; - FallStatus = EFI_SUCCESS; - SenseDataLength = sizeof (EFI_SCSI_SENSE_DATA); + default: + break; + } - ZeroMem ( - ScsiDiskDevice->SenseData, - sizeof (EFI_SCSI_SENSE_DATA) * (ScsiDiskDevice->SenseDataNumber) - ); + SensePtr++; + } - *NumberOfSenseKeys = 0; - *SenseDataArray = ScsiDiskDevice->SenseData; - PtrSenseData = ScsiDiskDevice->SenseData; + return IsError; +} - 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; - } +/** + Check sense key to find if hardware error happens. - if (EFI_ERROR (FallStatus)) { - if (*NumberOfSenseKeys != 0) { - *NeedRetry = FALSE; - return EFI_SUCCESS; - } else { - return EFI_DEVICE_ERROR; - } - } + @param SenseData The pointer of EFI_SCSI_SENSE_DATA + @param SenseCounts The number of sense key - (*NumberOfSenseKeys) += 1; + @retval TRUE Hardware error exits. + @retval FALSE NO error. + +**/ +BOOLEAN +ScsiDiskIsHardwareError ( + IN EFI_SCSI_SENSE_DATA *SenseData, + IN UINTN SenseCounts + ) +{ + EFI_SCSI_SENSE_DATA *SensePtr; + UINTN Index; + BOOLEAN IsError; + IsError = FALSE; + SensePtr = SenseData; + + for (Index = 0; Index < SenseCounts; Index++) { // - // no more sense key or number of sense keys exceeds predefined, - // skip the loop. + // Sense Key is EFI_SCSI_SK_HARDWARE_ERROR (0x4) // - if ((PtrSenseData->Sense_Key == EFI_SCSI_SK_NO_SENSE) || - (*NumberOfSenseKeys == ScsiDiskDevice->SenseDataNumber)) { - SenseReq = FALSE; + if (SensePtr->Sense_Key == EFI_SCSI_SK_HARDWARE_ERROR) { + IsError = TRUE; } - PtrSenseData += 1; + + SensePtr++; } - return EFI_SUCCESS; -} + return IsError; +} /** - Get information from media read capacity command. + Check sense key to find if media has changed. - @param ScsiDiskDevice The pointer of SCSI_DISK_DEV - @param Capacity The pointer of EFI_SCSI_DISK_CAPACITY_DATA + @param SenseData The pointer of EFI_SCSI_SENSE_DATA + @param SenseCounts The number of sense key + @retval TRUE Media is changed. + @retval FALSE Media is NOT changed. **/ -VOID -GetMediaInfo ( - IN OUT SCSI_DISK_DEV *ScsiDiskDevice, - IN EFI_SCSI_DISK_CAPACITY_DATA *Capacity +BOOLEAN +ScsiDiskIsMediaChange ( + IN EFI_SCSI_SENSE_DATA *SenseData, + IN UINTN SenseCounts ) { - ScsiDiskDevice->BlkIo.Media->LastBlock = (Capacity->LastLba3 << 24) | - (Capacity->LastLba2 << 16) | - (Capacity->LastLba1 << 8) | - Capacity->LastLba0; + EFI_SCSI_SENSE_DATA *SensePtr; + UINTN Index; + BOOLEAN IsMediaChanged; - ScsiDiskDevice->BlkIo.Media->MediaPresent = TRUE; - ScsiDiskDevice->BlkIo.Media->BlockSize = (Capacity->BlockSize3 << 24) | - (Capacity->BlockSize2 << 16) | - (Capacity->BlockSize1 << 8) | - Capacity->BlockSize0; - if (ScsiDiskDevice->DeviceType == EFI_SCSI_TYPE_DISK) { - ScsiDiskDevice->BlkIo.Media->BlockSize = 0x200; - } + IsMediaChanged = FALSE; + SensePtr = SenseData; + + for (Index = 0; Index < SenseCounts; Index++) { + // + // Sense Key is EFI_SCSI_SK_UNIT_ATTENTION (0x6), + // Additional sense code is EFI_SCSI_ASC_MEDIA_CHANGE (0x28) + // + if ((SensePtr->Sense_Key == EFI_SCSI_SK_UNIT_ATTENTION) && + (SensePtr->Addnl_Sense_Code == EFI_SCSI_ASC_MEDIA_CHANGE)) + { + IsMediaChanged = TRUE; + } - if (ScsiDiskDevice->DeviceType == EFI_SCSI_TYPE_CDROM) { - ScsiDiskDevice->BlkIo.Media->BlockSize = 0x800; + SensePtr++; } -} - -/** - 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 ? 0 : 1); - ScsiDiskDevice->BlkIoMedia.RemovableMedia = (BOOLEAN) (!ScsiDiskDevice->FixedDevice); + return IsMediaChanged; } /** - Read sector from SCSI Disk. + Check sense key to find if reset happens. - @param ScsiDiskDevice The poiniter 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 SenseData The pointer of EFI_SCSI_SENSE_DATA + @param SenseCounts The number of sense key - @retval EFI_DEVICE_ERROR Indicates a device error. - @retval EFI_SUCCESS Operation is successful. + @retval TRUE It is reset before. + @retval FALSE It is NOT reset before. **/ -EFI_STATUS -ScsiDiskReadSectors ( - IN SCSI_DISK_DEV *ScsiDiskDevice, - OUT VOID *Buffer, - IN EFI_LBA Lba, - IN UINTN NumberOfBlocks +BOOLEAN +ScsiDiskIsResetBefore ( + IN EFI_SCSI_SENSE_DATA *SenseData, + IN UINTN SenseCounts ) { - 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_SCSI_SENSE_DATA *SensePtr; + UINTN Index; + BOOLEAN IsResetBefore; - SenseData = NULL; - NumberOfSenseKeys = 0; + IsResetBefore = FALSE; + SensePtr = SenseData; - Status = EFI_SUCCESS; + for (Index = 0; Index < SenseCounts; Index++) { + // + // Sense Key is EFI_SCSI_SK_UNIT_ATTENTION (0x6) + // Additional Sense Code is EFI_SCSI_ASC_RESET (0x29) + // + if ((SensePtr->Sense_Key == EFI_SCSI_SK_UNIT_ATTENTION) && + (SensePtr->Addnl_Sense_Code == EFI_SCSI_ASC_RESET)) + { + IsResetBefore = TRUE; + } - BlocksRemaining = NumberOfBlocks; - BlockSize = ScsiDiskDevice->BlkIo.Media->BlockSize; - // - // limit the data bytes that can be transferred by one Read(10) Command - // - MaxBlock = 65536; + SensePtr++; + } - PtrBuffer = Buffer; - Lba32 = (UINT32) Lba; + return IsResetBefore; +} - while (BlocksRemaining > 0) { +/** + Check sense key to find if the drive is ready. - if (BlocksRemaining <= MaxBlock) { + @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 - SectorCount = (UINT16) BlocksRemaining; - } else { + @retval TRUE Drive is ready. + @retval FALSE Drive is NOT ready. - SectorCount = MaxBlock; - } +**/ +BOOLEAN +ScsiDiskIsDriveReady ( + IN EFI_SCSI_SENSE_DATA *SenseData, + IN UINTN SenseCounts, + OUT BOOLEAN *RetryLater + ) +{ + EFI_SCSI_SENSE_DATA *SensePtr; + UINTN Index; + BOOLEAN IsReady; - ByteCount = SectorCount * BlockSize; - Timeout = EFI_TIMER_PERIOD_SECONDS (2); + IsReady = TRUE; + *RetryLater = FALSE; + SensePtr = SenseData; - MaxRetry = 2; - for (Index = 0; Index < MaxRetry; Index++) { + for (Index = 0; Index < SenseCounts; Index++) { + switch (SensePtr->Sense_Key) { + case EFI_SCSI_SK_NOT_READY: + // + // Sense Key is EFI_SCSI_SK_NOT_READY (0x2) + // + switch (SensePtr->Addnl_Sense_Code) { + case EFI_SCSI_ASC_NOT_READY: + // + // Additional Sense Code is EFI_SCSI_ASC_NOT_READY (0x4) + // + switch (SensePtr->Addnl_Sense_Code_Qualifier) { + case EFI_SCSI_ASCQ_IN_PROGRESS: + // + // Additional Sense Code Qualifier is + // EFI_SCSI_ASCQ_IN_PROGRESS (0x1) + // + IsReady = FALSE; + *RetryLater = TRUE; + break; + + default: + IsReady = FALSE; + *RetryLater = FALSE; + break; + } + + break; + + default: + break; + } - Status = ScsiDiskRead10 ( - ScsiDiskDevice, - &NeedRetry, - &SenseData, - &NumberOfSenseKeys, - Timeout, - PtrBuffer, - &ByteCount, - Lba32, - SectorCount - ); - if (!EFI_ERROR (Status)) { break; - } - - if (!NeedRetry) { - return EFI_DEVICE_ERROR; - } - - } - if ((Index == MaxRetry) && (Status != EFI_SUCCESS)) { - return EFI_DEVICE_ERROR; + default: + break; } - // - // actual transferred sectors - // - SectorCount = ByteCount / BlockSize; - - Lba32 += SectorCount; - PtrBuffer = PtrBuffer + SectorCount * BlockSize; - BlocksRemaining -= SectorCount; + SensePtr++; } - return EFI_SUCCESS; + return IsReady; } /** - Write sector to SCSI Disk. + Check sense key to find if it has sense key. - @param ScsiDiskDevice The poiniter 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 SenseData - The pointer of EFI_SCSI_SENSE_DATA + @param SenseCounts - The number of sense key - @retval EFI_DEVICE_ERROR Indicates a device error. - @retval EFI_SUCCESS Operation is successful. + @retval TRUE It has sense key. + @retval FALSE It has NOT any sense key. **/ -EFI_STATUS -ScsiDiskWriteSectors ( - IN SCSI_DISK_DEV *ScsiDiskDevice, - IN VOID *Buffer, - IN EFI_LBA Lba, - IN UINTN NumberOfBlocks +BOOLEAN +ScsiDiskHaveSenseKey ( + IN EFI_SCSI_SENSE_DATA *SenseData, + IN UINTN SenseCounts ) { - 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; - - BlocksRemaining = NumberOfBlocks; - BlockSize = ScsiDiskDevice->BlkIo.Media->BlockSize; - // - // limit the data bytes that can be transferred by one Write(10) Command - // - MaxBlock = 65536; - - PtrBuffer = Buffer; - Lba32 = (UINT32) Lba; - - while (BlocksRemaining > 0) { + EFI_SCSI_SENSE_DATA *SensePtr; + UINTN Index; + BOOLEAN HaveSenseKey; - if (BlocksRemaining <= MaxBlock) { - - SectorCount = (UINT16) BlocksRemaining; - } else { - - SectorCount = MaxBlock; - } - - 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; - } + if (SenseCounts == 0) { + HaveSenseKey = FALSE; + } else { + HaveSenseKey = TRUE; + } - if (!NeedRetry) { - return EFI_DEVICE_ERROR; - } - } + SensePtr = SenseData; - if ((Index == MaxRetry) && (Status != EFI_SUCCESS)) { - return EFI_DEVICE_ERROR; - } + for (Index = 0; Index < SenseCounts; Index++) { // - // actual transferred sectors + // Sense Key is SK_NO_SENSE (0x0) // - SectorCount = ByteCount / BlockSize; + if ((SensePtr->Sense_Key == EFI_SCSI_SK_NO_SENSE) && + (Index == 0)) + { + HaveSenseKey = FALSE; + } - Lba32 += SectorCount; - PtrBuffer = PtrBuffer + SectorCount * BlockSize; - BlocksRemaining -= SectorCount; + SensePtr++; } - return EFI_SUCCESS; + return HaveSenseKey; } - /** - Sumbmit Read command. + Release resource about disk device. - @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 + @param ScsiDiskDevice The pointer of SCSI_DISK_DEV - @return EFI_STATUS is returned by calling ScsiRead10Command(). **/ -EFI_STATUS -ScsiDiskRead10 ( - IN SCSI_DISK_DEV *ScsiDiskDevice, - OUT BOOLEAN *NeedRetry, - OUT EFI_SCSI_SENSE_DATA **SenseDataArray, OPTIONAL - OUT UINTN *NumberOfSenseKeys, - IN UINT64 Timeout, - OUT UINT8 *DataBuffer, - IN OUT UINT32 *DataLength, - IN UINT32 StartLba, - IN UINT32 SectorSize +VOID +ReleaseScsiDiskDeviceResources ( + IN SCSI_DISK_DEV *ScsiDiskDevice ) { - UINT8 SenseDataLength; - EFI_STATUS Status; - UINT8 HostAdapterStatus; - UINT8 TargetStatus; + if (ScsiDiskDevice == NULL) { + return; + } - *NeedRetry = FALSE; - *NumberOfSenseKeys = 0; - SenseDataLength = 0; - Status = ScsiRead10Command ( - ScsiDiskDevice->ScsiIo, - Timeout, - NULL, - &SenseDataLength, - &HostAdapterStatus, - &TargetStatus, - DataBuffer, - DataLength, - StartLba, - SectorSize - ); - return Status; -} + if (ScsiDiskDevice->SenseData != NULL) { + FreePool (ScsiDiskDevice->SenseData); + ScsiDiskDevice->SenseData = NULL; + } + + if (ScsiDiskDevice->ControllerNameTable != NULL) { + FreeUnicodeStringTable (ScsiDiskDevice->ControllerNameTable); + ScsiDiskDevice->ControllerNameTable = NULL; + } + + FreePool (ScsiDiskDevice); + ScsiDiskDevice = NULL; +} /** - Submit Write Command. + Determine if Block Io & Block Io2 should be produced. - @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(). + @param ChildHandle Child Handle to retrieve Parent information. + + @retval TRUE Should produce Block Io & Block Io2. + @retval FALSE Should not produce Block Io & Block Io2. **/ -EFI_STATUS -ScsiDiskWrite10 ( - IN SCSI_DISK_DEV *ScsiDiskDevice, - OUT BOOLEAN *NeedRetry, - OUT EFI_SCSI_SENSE_DATA **SenseDataArray, OPTIONAL - OUT UINTN *NumberOfSenseKeys, - IN UINT64 Timeout, - IN UINT8 *DataBuffer, - IN OUT UINT32 *DataLength, - IN UINT32 StartLba, - IN UINT32 SectorSize +BOOLEAN +DetermineInstallBlockIo ( + IN EFI_HANDLE ChildHandle ) { - EFI_STATUS Status; - UINT8 SenseDataLength; - UINT8 HostAdapterStatus; - UINT8 TargetStatus; + EFI_SCSI_PASS_THRU_PROTOCOL *ScsiPassThru; + EFI_EXT_SCSI_PASS_THRU_PROTOCOL *ExtScsiPassThru; - *NeedRetry = FALSE; - *NumberOfSenseKeys = 0; - SenseDataLength = 0; - Status = ScsiWrite10Command ( - ScsiDiskDevice->ScsiIo, - Timeout, - NULL, - &SenseDataLength, - &HostAdapterStatus, - &TargetStatus, - DataBuffer, - DataLength, - StartLba, - SectorSize - ); - return Status; -} + // + // Firstly, check if ExtScsiPassThru Protocol parent handle exists. If existence, + // check its attribute, logic or physical. + // + ExtScsiPassThru = (EFI_EXT_SCSI_PASS_THRU_PROTOCOL *)GetParentProtocol (&gEfiExtScsiPassThruProtocolGuid, ChildHandle); + if (ExtScsiPassThru != NULL) { + if ((ExtScsiPassThru->Mode->Attributes & EFI_SCSI_PASS_THRU_ATTRIBUTES_LOGICAL) != 0) { + return TRUE; + } + } + + // + // Secondly, check if ScsiPassThru Protocol parent handle exists. If existence, + // check its attribute, logic or physical. + // + ScsiPassThru = (EFI_SCSI_PASS_THRU_PROTOCOL *)GetParentProtocol (&gEfiScsiPassThruProtocolGuid, ChildHandle); + if (ScsiPassThru != NULL) { + if ((ScsiPassThru->Mode->Attributes & EFI_SCSI_PASS_THRU_ATTRIBUTES_LOGICAL) != 0) { + return TRUE; + } + } + return FALSE; +} /** - Check sense key to find if media presents. + Search protocol database and check to see if the protocol + 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. - @param SenseData The pointer of EFI_SCSI_SENSE_DATA - @param SenseCounts The number of sense key - @retval TRUE NOT any media - @retval FALSE Media presents + @param ProtocolGuid ProtocolGuid pointer. + @param ChildHandle Child Handle to retrieve Parent information. + **/ -BOOLEAN -ScsiDiskIsNoMedia ( - IN EFI_SCSI_SENSE_DATA *SenseData, - IN UINTN SenseCounts +VOID * +EFIAPI +GetParentProtocol ( + IN EFI_GUID *ProtocolGuid, + IN EFI_HANDLE ChildHandle ) { - EFI_SCSI_SENSE_DATA *SensePtr; - UINTN Index; - BOOLEAN IsNoMedia; + UINTN Index; + UINTN HandleCount; + VOID *Interface; + EFI_STATUS Status; + EFI_HANDLE *HandleBuffer; - IsNoMedia = FALSE; - SensePtr = SenseData; + // + // Retrieve the list of all handles from the handle database + // + Status = gBS->LocateHandleBuffer ( + ByProtocol, + ProtocolGuid, + NULL, + &HandleCount, + &HandleBuffer + ); - for (Index = 0; Index < SenseCounts; Index++) { - // - // Sense Key is EFI_SCSI_SK_NOT_READY (0x2), - // Additional Sense Code is ASC_NO_MEDIA (0x3A) - // - if ((SensePtr->Sense_Key == EFI_SCSI_SK_NOT_READY) && - (SensePtr->Addnl_Sense_Code == EFI_SCSI_ASC_NO_MEDIA)) { - IsNoMedia = TRUE; + if (EFI_ERROR (Status)) { + return NULL; + } + + // + // 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); + if (!EFI_ERROR (Status)) { + Status = gBS->HandleProtocol (HandleBuffer[Index], ProtocolGuid, (VOID **)&Interface); + if (!EFI_ERROR (Status)) { + gBS->FreePool (HandleBuffer); + return Interface; + } } - SensePtr++; } - return IsNoMedia; + gBS->FreePool (HandleBuffer); + return NULL; } - /** - Parse sense key. + Determine if EFI Erase Block Protocol should be produced. - @param SenseData The pointer of EFI_SCSI_SENSE_DATA - @param SenseCounts The number of sense key + @param ScsiDiskDevice The pointer of SCSI_DISK_DEV. + @param ChildHandle Handle of device. - @retval TRUE Error - @retval FALSE NOT error + @retval TRUE Should produce EFI Erase Block Protocol. + @retval FALSE Should not produce EFI Erase Block Protocol. **/ BOOLEAN -ScsiDiskIsMediaError ( - IN EFI_SCSI_SENSE_DATA *SenseData, - IN UINTN SenseCounts +DetermineInstallEraseBlock ( + IN SCSI_DISK_DEV *ScsiDiskDevice, + IN EFI_HANDLE ChildHandle ) { - EFI_SCSI_SENSE_DATA *SensePtr; - UINTN Index; - BOOLEAN IsError; - - IsError = FALSE; - SensePtr = SenseData; + 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; - for (Index = 0; Index < SenseCounts; Index++) { + // + // UNMAP command is not supported by any of the UFS WLUNs. + // + if (ScsiDiskDevice->DeviceType == EFI_SCSI_TYPE_WLUN) { + RetVal = FALSE; + goto Done; + } - switch (SensePtr->Sense_Key) { + Status = gBS->HandleProtocol ( + ChildHandle, + &gEfiDevicePathProtocolGuid, + (VOID **)&DevicePathNode + ); + // + // Device Path protocol must be installed on the device handle. + // + ASSERT_EFI_ERROR (Status); - case EFI_SCSI_SK_MEDIUM_ERROR: - // - // Sense Key is EFI_SCSI_SK_MEDIUM_ERROR (0x3) - // - switch (SensePtr->Addnl_Sense_Code) { + 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; + } - // - // fall through - // - case EFI_SCSI_ASC_MEDIA_ERR1: + DevicePathNode = NextDevicePathNode (DevicePathNode); + } - // - // fall through - // - case EFI_SCSI_ASC_MEDIA_ERR2: + if (!UfsDevice) { + RetVal = FALSE; + goto Done; + } - // - // fall through - // - case EFI_SCSI_ASC_MEDIA_ERR3: - case EFI_SCSI_ASC_MEDIA_ERR4: - IsError = TRUE; - break; + // + // 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; + } - default: - break; - } + SenseDataLength = 0; + DataLength16 = sizeof (EFI_SCSI_DISK_CAPACITY_DATA16); + ZeroMem (CapacityData16, sizeof (EFI_SCSI_DISK_CAPACITY_DATA16)); - break; + CommandStatus = ScsiReadCapacity16Command ( + ScsiDiskDevice->ScsiIo, + SCSI_DISK_TIMEOUT, + NULL, + &SenseDataLength, + &HostAdapterStatus, + &TargetStatus, + (VOID *)CapacityData16, + &DataLength16, + FALSE + ); - case EFI_SCSI_SK_NOT_READY: - // - // Sense Key is EFI_SCSI_SK_NOT_READY (0x2) - // - switch (SensePtr->Addnl_Sense_Code) { - // - // Additional Sense Code is ASC_MEDIA_UPSIDE_DOWN (0x6) - // - case EFI_SCSI_ASC_MEDIA_UPSIDE_DOWN: - IsError = TRUE; - break; + 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 (( + DEBUG_VERBOSE, + "ScsiDisk EraseBlock: Either TPE or TPRZ is not set: 0x%x.\n", + CapacityData16->LowestAlignLogic2 + )); + + RetVal = FALSE; + goto Done; + } + } else { + DEBUG (( + DEBUG_VERBOSE, + "ScsiDisk EraseBlock: ReadCapacity16 failed with status %r.\n", + CommandStatus + )); - default: - break; - } - break; + RetVal = FALSE; + goto Done; + } - default: - break; - } + // + // Check whether the UFS device server implements the UNMAP command. + // + if ((ScsiDiskDevice->UnmapInfo.MaxLbaCnt == 0) || + (ScsiDiskDevice->UnmapInfo.MaxBlkDespCnt == 0)) + { + DEBUG (( + DEBUG_VERBOSE, + "ScsiDisk EraseBlock: The device server does not implement the UNMAP command.\n" + )); + + RetVal = FALSE; + goto Done; + } - SensePtr++; +Done: + if (CapacityData16 != NULL) { + FreeAlignedBuffer (CapacityData16, sizeof (EFI_SCSI_DISK_CAPACITY_DATA16)); } - return IsError; + return RetVal; } - /** - Check sense key to find if hardware error happens. + Determine if EFI Storage Security Command Protocol should be produced. - @param SenseData The pointer of EFI_SCSI_SENSE_DATA - @param SenseCounts The number of sense key + @param ScsiDiskDevice The pointer of SCSI_DISK_DEV. + @param ChildHandle Handle of device. - @retval TRUE Hardware error exits. - @retval FALSE NO error. + @retval TRUE Should produce EFI Storage Security Command Protocol. + @retval FALSE Should not produce EFI Storage Security Command Protocol. **/ BOOLEAN -ScsiDiskIsHardwareError ( - IN EFI_SCSI_SENSE_DATA *SenseData, - IN UINTN SenseCounts +DetermineInstallStorageSecurity ( + IN SCSI_DISK_DEV *ScsiDiskDevice, + IN EFI_HANDLE ChildHandle ) { - EFI_SCSI_SENSE_DATA *SensePtr; - UINTN Index; - BOOLEAN IsError; - - IsError = FALSE; - SensePtr = SenseData; + EFI_STATUS Status; + UFS_DEVICE_PATH *UfsDevice; + BOOLEAN RetVal; + EFI_DEVICE_PATH_PROTOCOL *DevicePathNode; + + UfsDevice = NULL; + RetVal = TRUE; + + Status = gBS->HandleProtocol ( + ChildHandle, + &gEfiDevicePathProtocolGuid, + (VOID **)&DevicePathNode + ); + // + // Device Path protocol must be installed on the device handle. + // + ASSERT_EFI_ERROR (Status); - for (Index = 0; Index < SenseCounts; Index++) { - + while (!IsDevicePathEndType (DevicePathNode)) { // - // Sense Key is EFI_SCSI_SK_HARDWARE_ERROR (0x4) + // For now, only support Storage Security Command Protocol on UFS devices. // - if (SensePtr->Sense_Key == EFI_SCSI_SK_HARDWARE_ERROR) { - IsError = TRUE; + if ((DevicePathNode->Type == MESSAGING_DEVICE_PATH) && + (DevicePathNode->SubType == MSG_UFS_DP)) + { + UfsDevice = (UFS_DEVICE_PATH *)DevicePathNode; + break; } - SensePtr++; + DevicePathNode = NextDevicePathNode (DevicePathNode); } - return IsError; -} + if (UfsDevice == NULL) { + RetVal = FALSE; + goto Done; + } + + if (UfsDevice->Lun != UFS_WLUN_RPMB) { + RetVal = FALSE; + } +Done: + return RetVal; +} /** - Check sense key to find if media has changed. + Provides inquiry information for the controller type. - @param SenseData The pointer of EFI_SCSI_SENSE_DATA - @param SenseCounts The number of sense key + 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. + + @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 TRUE Media is changed. - @retval FALSE Medit is NOT changed. **/ -BOOLEAN -ScsiDiskIsMediaChange ( - IN EFI_SCSI_SENSE_DATA *SenseData, - IN UINTN SenseCounts +EFI_STATUS +EFIAPI +ScsiDiskInfoInquiry ( + IN EFI_DISK_INFO_PROTOCOL *This, + IN OUT VOID *InquiryData, + IN OUT UINT32 *InquiryDataSize ) { - EFI_SCSI_SENSE_DATA *SensePtr; - UINTN Index; - BOOLEAN IsMediaChanged; + EFI_STATUS Status; + SCSI_DISK_DEV *ScsiDiskDevice; - IsMediaChanged = FALSE; - SensePtr = SenseData; - - for (Index = 0; Index < SenseCounts; Index++) { - // - // Sense Key is EFI_SCSI_SK_UNIT_ATTENTION (0x6), - // Additional sense code is EFI_SCSI_ASC_MEDIA_CHANGE (0x28) - // - if ((SensePtr->Sense_Key == EFI_SCSI_SK_UNIT_ATTENTION) && - (SensePtr->Addnl_Sense_Code == EFI_SCSI_ASC_MEDIA_CHANGE)) { - IsMediaChanged = TRUE; - } + ScsiDiskDevice = SCSI_DISK_DEV_FROM_DISKINFO (This); - SensePtr++; + Status = EFI_BUFFER_TOO_SMALL; + if (*InquiryDataSize >= sizeof (ScsiDiskDevice->InquiryData)) { + Status = EFI_SUCCESS; + CopyMem (InquiryData, &ScsiDiskDevice->InquiryData, sizeof (ScsiDiskDevice->InquiryData)); } - return IsMediaChanged; + *InquiryDataSize = sizeof (ScsiDiskDevice->InquiryData); + return Status; } /** - Check sense key to find if reset happens. + Provides identify information for the controller type. - @param SenseData The pointer of EFI_SCSI_SENSE_DATA - @param SenseCounts The number of sense key + This function is used by the IDE bus driver to get identify data. Data format + of Identify data is defined by the Interface GUID. - @retval TRUE It is reset before. - @retval FALSE It is NOT reset before. + @param[in] This Pointer to the EFI_DISK_INFO_PROTOCOL + instance. + @param[in, out] IdentifyData Pointer to a buffer for the identify data. + @param[in, out] IdentifyDataSize Pointer to the value for the identify data + size. + + @retval EFI_SUCCESS The command was accepted without any errors. + @retval EFI_NOT_FOUND Device does not support this data class + @retval EFI_DEVICE_ERROR Error reading IdentifyData from device + @retval EFI_BUFFER_TOO_SMALL IdentifyDataSize not big enough **/ -BOOLEAN -ScsiDiskIsResetBefore ( - IN EFI_SCSI_SENSE_DATA *SenseData, - IN UINTN SenseCounts +EFI_STATUS +EFIAPI +ScsiDiskInfoIdentify ( + IN EFI_DISK_INFO_PROTOCOL *This, + IN OUT VOID *IdentifyData, + IN OUT UINT32 *IdentifyDataSize ) { - EFI_SCSI_SENSE_DATA *SensePtr; - UINTN Index; - BOOLEAN IsResetBefore; - - IsResetBefore = FALSE; - SensePtr = SenseData; + EFI_STATUS Status; + SCSI_DISK_DEV *ScsiDiskDevice; - for (Index = 0; Index < SenseCounts; Index++) { - + if (CompareGuid (&This->Interface, &gEfiDiskInfoScsiInterfaceGuid) || CompareGuid (&This->Interface, &gEfiDiskInfoUfsInterfaceGuid)) { // - // Sense Key is EFI_SCSI_SK_UNIT_ATTENTION (0x6) - // Additional Sense Code is EFI_SCSI_ASC_RESET (0x29) + // Physical SCSI bus does not support this data class. // - if ((SensePtr->Sense_Key == EFI_SCSI_SK_UNIT_ATTENTION) && - (SensePtr->Addnl_Sense_Code == EFI_SCSI_ASC_RESET)) { - IsResetBefore = TRUE; - } + return EFI_NOT_FOUND; + } - SensePtr++; + ScsiDiskDevice = SCSI_DISK_DEV_FROM_DISKINFO (This); + + Status = EFI_BUFFER_TOO_SMALL; + if (*IdentifyDataSize >= sizeof (ScsiDiskDevice->IdentifyData)) { + Status = EFI_SUCCESS; + CopyMem (IdentifyData, &ScsiDiskDevice->IdentifyData, sizeof (ScsiDiskDevice->IdentifyData)); } - return IsResetBefore; + *IdentifyDataSize = sizeof (ScsiDiskDevice->IdentifyData); + return Status; } /** - Check sense key to find if the drive is ready. + Provides sense data information for the controller type. - @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 + This function is used by the IDE bus driver to get sense data. + Data format of Sense data is defined by the Interface GUID. - @retval TRUE Drive is ready. - @retval FALSE Drive is NOT ready. + @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. + @retval EFI_DEVICE_ERROR Error reading SenseData from device. + @retval EFI_BUFFER_TOO_SMALL SenseDataSize not big enough. **/ -BOOLEAN -ScsiDiskIsDriveReady ( - IN EFI_SCSI_SENSE_DATA *SenseData, - IN UINTN SenseCounts, - OUT BOOLEAN *RetryLater +EFI_STATUS +EFIAPI +ScsiDiskInfoSenseData ( + IN EFI_DISK_INFO_PROTOCOL *This, + IN OUT VOID *SenseData, + IN OUT UINT32 *SenseDataSize, + OUT UINT8 *SenseDataNumber ) { - EFI_SCSI_SENSE_DATA *SensePtr; - UINTN Index; - BOOLEAN IsReady; - - IsReady = TRUE; - *RetryLater = FALSE; - SensePtr = SenseData; - - for (Index = 0; Index < SenseCounts; Index++) { - - switch (SensePtr->Sense_Key) { + return EFI_NOT_FOUND; +} - case EFI_SCSI_SK_NOT_READY: - // - // Sense Key is EFI_SCSI_SK_NOT_READY (0x2) - // - switch (SensePtr->Addnl_Sense_Code) { - case EFI_SCSI_ASC_NOT_READY: - // - // Additional Sense Code is EFI_SCSI_ASC_NOT_READY (0x4) - // - switch (SensePtr->Addnl_Sense_Code_Qualifier) { - case EFI_SCSI_ASCQ_IN_PROGRESS: - // - // Additional Sense Code Qualifier is - // EFI_SCSI_ASCQ_IN_PROGRESS (0x1) - // - IsReady = FALSE; - *RetryLater = TRUE; - break; +/** + This function is used by the IDE bus driver to get controller information. - default: - IsReady = FALSE; - *RetryLater = FALSE; - break; - } - break; + @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. - default: - break; - } - break; + @retval EFI_SUCCESS IdeChannel and IdeDevice are valid. + @retval EFI_UNSUPPORTED This is not an IDE device. - default: - break; - } +**/ +EFI_STATUS +EFIAPI +ScsiDiskInfoWhichIde ( + IN EFI_DISK_INFO_PROTOCOL *This, + OUT UINT32 *IdeChannel, + OUT UINT32 *IdeDevice + ) +{ + SCSI_DISK_DEV *ScsiDiskDevice; - SensePtr++; + if (CompareGuid (&This->Interface, &gEfiDiskInfoScsiInterfaceGuid) || CompareGuid (&This->Interface, &gEfiDiskInfoUfsInterfaceGuid)) { + // + // This is not an IDE physical device. + // + return EFI_UNSUPPORTED; } - return IsReady; + ScsiDiskDevice = SCSI_DISK_DEV_FROM_DISKINFO (This); + *IdeChannel = ScsiDiskDevice->Channel; + *IdeDevice = ScsiDiskDevice->Device; + + return EFI_SUCCESS; } /** - Check sense key to find if it has sense key. + Issues ATA IDENTIFY DEVICE command to identify ATAPI device. - @param SenseData - The pointer of EFI_SCSI_SENSE_DATA - @param SenseCounts - The number of sense key + This function tries to fill 512-byte ATAPI_IDENTIFY_DATA for ATAPI device to + implement Identify() interface for DiskInfo protocol. The ATA command is sent + via SCSI Request Packet. - @retval TRUE It has sense key. - @retval FALSE It has NOT any sense key. + @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. **/ -BOOLEAN -ScsiDiskHaveSenseKey ( - IN EFI_SCSI_SENSE_DATA *SenseData, - IN UINTN SenseCounts +EFI_STATUS +AtapiIdentifyDevice ( + IN OUT SCSI_DISK_DEV *ScsiDiskDevice ) { - EFI_SCSI_SENSE_DATA *SensePtr; - UINTN Index; - BOOLEAN HaveSenseKey; - - if (SenseCounts == 0) { - HaveSenseKey = FALSE; - } else { - HaveSenseKey = TRUE; - } + EFI_SCSI_IO_SCSI_REQUEST_PACKET CommandPacket; + UINT8 Cdb[6]; - SensePtr = SenseData; - - for (Index = 0; Index < SenseCounts; Index++) { - - // - // Sense Key is SK_NO_SENSE (0x0) - // - if ((SensePtr->Sense_Key == EFI_SCSI_SK_NO_SENSE) && - (Index == 0)) { - HaveSenseKey = FALSE; - } + // + // Initialize SCSI REQUEST_PACKET and 6-byte Cdb + // + ZeroMem (&CommandPacket, sizeof (CommandPacket)); + ZeroMem (Cdb, sizeof (Cdb)); - SensePtr++; - } + Cdb[0] = ATA_CMD_IDENTIFY_DEVICE; + CommandPacket.Timeout = SCSI_DISK_TIMEOUT; + CommandPacket.Cdb = Cdb; + CommandPacket.CdbLength = (UINT8)sizeof (Cdb); + CommandPacket.InDataBuffer = &ScsiDiskDevice->IdentifyData; + CommandPacket.InTransferLength = sizeof (ScsiDiskDevice->IdentifyData); - return HaveSenseKey; + return ScsiDiskDevice->ScsiIo->ExecuteScsiCommand (ScsiDiskDevice->ScsiIo, &CommandPacket, NULL); } /** - Release resource about disk device. + Initialize the installation of DiskInfo protocol. - @param ScsiDiskDevice The pointer of SCSI_DISK_DEV + This function prepares for the installation of DiskInfo protocol on the child handle. + By default, it installs DiskInfo protocol with SCSI interface GUID. If it further + detects that the physical device is an ATAPI/AHCI device, it then updates interface GUID + to be IDE/AHCI interface GUID. + + @param ScsiDiskDevice The pointer of SCSI_DISK_DEV. + @param ChildHandle Child handle to install DiskInfo protocol. **/ VOID -ReleaseScsiDiskDeviceResources ( - IN SCSI_DISK_DEV *ScsiDiskDevice +InitializeInstallDiskInfo ( + IN SCSI_DISK_DEV *ScsiDiskDevice, + IN EFI_HANDLE ChildHandle ) { - if (ScsiDiskDevice == NULL) { - return ; - } - - if (ScsiDiskDevice->SenseData != NULL) { - gBS->FreePool (ScsiDiskDevice->SenseData); - ScsiDiskDevice->SenseData = NULL; - } + EFI_STATUS Status; + EFI_DEVICE_PATH_PROTOCOL *DevicePathNode; + EFI_DEVICE_PATH_PROTOCOL *ChildDevicePathNode; + ATAPI_DEVICE_PATH *AtapiDevicePath; + SATA_DEVICE_PATH *SataDevicePath; + UINTN IdentifyRetry; + + Status = gBS->HandleProtocol (ChildHandle, &gEfiDevicePathProtocolGuid, (VOID **)&DevicePathNode); + // + // Device Path protocol must be installed on the device handle. + // + ASSERT_EFI_ERROR (Status); + // + // Copy the DiskInfo protocol template. + // + CopyMem (&ScsiDiskDevice->DiskInfo, &gScsiDiskInfoProtocolTemplate, sizeof (gScsiDiskInfoProtocolTemplate)); + + while (!IsDevicePathEnd (DevicePathNode)) { + ChildDevicePathNode = NextDevicePathNode (DevicePathNode); + if ((DevicePathType (DevicePathNode) == HARDWARE_DEVICE_PATH) && + (DevicePathSubType (DevicePathNode) == HW_PCI_DP) && + (DevicePathType (ChildDevicePathNode) == MESSAGING_DEVICE_PATH) && + ((DevicePathSubType (ChildDevicePathNode) == MSG_ATAPI_DP) || + (DevicePathSubType (ChildDevicePathNode) == MSG_SATA_DP))) + { + IdentifyRetry = 3; + do { + // + // Issue ATA Identify Device Command via SCSI command, which is required to publish DiskInfo protocol + // with IDE/AHCI interface GUID. + // + Status = AtapiIdentifyDevice (ScsiDiskDevice); + if (!EFI_ERROR (Status)) { + if (DevicePathSubType (ChildDevicePathNode) == MSG_ATAPI_DP) { + // + // We find the valid ATAPI device path + // + AtapiDevicePath = (ATAPI_DEVICE_PATH *)ChildDevicePathNode; + ScsiDiskDevice->Channel = AtapiDevicePath->PrimarySecondary; + ScsiDiskDevice->Device = AtapiDevicePath->SlaveMaster; + // + // Update the DiskInfo.Interface to IDE interface GUID for the physical ATAPI device. + // + CopyGuid (&ScsiDiskDevice->DiskInfo.Interface, &gEfiDiskInfoIdeInterfaceGuid); + } else { + // + // We find the valid SATA device path + // + SataDevicePath = (SATA_DEVICE_PATH *)ChildDevicePathNode; + ScsiDiskDevice->Channel = SataDevicePath->HBAPortNumber; + ScsiDiskDevice->Device = SataDevicePath->PortMultiplierPortNumber; + // + // 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; + } - if (ScsiDiskDevice->ControllerNameTable != NULL) { - FreeUnicodeStringTable (ScsiDiskDevice->ControllerNameTable); - ScsiDiskDevice->ControllerNameTable = NULL; + DevicePathNode = ChildDevicePathNode; } - gBS->FreePool (ScsiDiskDevice); - - ScsiDiskDevice = NULL; + return; }