mirror of https://github.com/acidanthera/audk.git
5727 lines
178 KiB
C
5727 lines
178 KiB
C
/** @file
|
|
SCSI disk driver that layers on every SCSI IO protocol in the system.
|
|
|
|
Copyright (c) 2006 - 2017, Intel Corporation. All rights reserved.<BR>
|
|
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.
|
|
|
|
**/
|
|
|
|
|
|
#include "ScsiDisk.h"
|
|
|
|
EFI_DRIVER_BINDING_PROTOCOL gScsiDiskDriverBinding = {
|
|
ScsiDiskDriverBindingSupported,
|
|
ScsiDiskDriverBindingStart,
|
|
ScsiDiskDriverBindingStop,
|
|
0xa,
|
|
NULL,
|
|
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));
|
|
}
|
|
}
|
|
|
|
/**
|
|
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 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
|
|
)
|
|
{
|
|
EFI_STATUS Status;
|
|
|
|
//
|
|
// Install driver model protocol(s).
|
|
//
|
|
Status = EfiLibInstallDriverBindingComponentName2 (
|
|
ImageHandle,
|
|
SystemTable,
|
|
&gScsiDiskDriverBinding,
|
|
ImageHandle,
|
|
&gScsiDiskComponentName,
|
|
&gScsiDiskComponentName2
|
|
);
|
|
ASSERT_EFI_ERROR (Status);
|
|
|
|
|
|
return Status;
|
|
}
|
|
|
|
/**
|
|
Test to see if this driver supports ControllerHandle.
|
|
|
|
This service is called by the EFI boot service ConnectController(). In order
|
|
to make drivers as small as possible, there are a few calling restrictions for
|
|
this service. ConnectController() must follow these calling restrictions.
|
|
If any other agent wishes to call Supported() it must also follow these
|
|
calling restrictions.
|
|
|
|
@param This Protocol instance pointer.
|
|
@param ControllerHandle Handle of device to test
|
|
@param RemainingDevicePath Optional parameter use to pick a specific child
|
|
device to start.
|
|
|
|
@retval EFI_SUCCESS This driver supports this device
|
|
@retval EFI_ALREADY_STARTED This driver is already running on this device
|
|
@retval other This driver does not support this device
|
|
|
|
**/
|
|
EFI_STATUS
|
|
EFIAPI
|
|
ScsiDiskDriverBindingSupported (
|
|
IN EFI_DRIVER_BINDING_PROTOCOL *This,
|
|
IN EFI_HANDLE Controller,
|
|
IN EFI_DEVICE_PATH_PROTOCOL *RemainingDevicePath OPTIONAL
|
|
)
|
|
{
|
|
EFI_STATUS Status;
|
|
EFI_SCSI_IO_PROTOCOL *ScsiIo;
|
|
UINT8 DeviceType;
|
|
|
|
Status = gBS->OpenProtocol (
|
|
Controller,
|
|
&gEfiScsiIoProtocolGuid,
|
|
(VOID **) &ScsiIo,
|
|
This->DriverBindingHandle,
|
|
Controller,
|
|
EFI_OPEN_PROTOCOL_BY_DRIVER
|
|
);
|
|
if (EFI_ERROR (Status)) {
|
|
return Status;
|
|
}
|
|
|
|
Status = ScsiIo->GetDeviceType (ScsiIo, &DeviceType);
|
|
if (!EFI_ERROR (Status)) {
|
|
if ((DeviceType == EFI_SCSI_TYPE_DISK) || (DeviceType == EFI_SCSI_TYPE_CDROM)) {
|
|
Status = EFI_SUCCESS;
|
|
} else {
|
|
Status = EFI_UNSUPPORTED;
|
|
}
|
|
}
|
|
|
|
gBS->CloseProtocol (
|
|
Controller,
|
|
&gEfiScsiIoProtocolGuid,
|
|
This->DriverBindingHandle,
|
|
Controller
|
|
);
|
|
return Status;
|
|
}
|
|
|
|
|
|
/**
|
|
Start this driver on ControllerHandle.
|
|
|
|
This service is called by the EFI boot service ConnectController(). In order
|
|
to make drivers as small as possible, there are a few calling restrictions for
|
|
this service. ConnectController() must follow these calling restrictions. If
|
|
any other agent wishes to call Start() it must also follow these calling
|
|
restrictions.
|
|
|
|
@param This Protocol instance pointer.
|
|
@param ControllerHandle Handle of device to bind driver to
|
|
@param RemainingDevicePath Optional parameter use to pick a specific child
|
|
device to start.
|
|
|
|
@retval EFI_SUCCESS This driver is added to ControllerHandle
|
|
@retval EFI_ALREADY_STARTED This driver is already running on ControllerHandle
|
|
@retval other This driver does not support this device
|
|
|
|
**/
|
|
EFI_STATUS
|
|
EFIAPI
|
|
ScsiDiskDriverBindingStart (
|
|
IN EFI_DRIVER_BINDING_PROTOCOL *This,
|
|
IN EFI_HANDLE Controller,
|
|
IN EFI_DEVICE_PATH_PROTOCOL *RemainingDevicePath OPTIONAL
|
|
)
|
|
{
|
|
EFI_STATUS Status;
|
|
EFI_SCSI_IO_PROTOCOL *ScsiIo;
|
|
SCSI_DISK_DEV *ScsiDiskDevice;
|
|
BOOLEAN Temp;
|
|
UINT8 Index;
|
|
UINT8 MaxRetry;
|
|
BOOLEAN NeedRetry;
|
|
BOOLEAN MustReadCapacity;
|
|
|
|
MustReadCapacity = TRUE;
|
|
|
|
ScsiDiskDevice = (SCSI_DISK_DEV *) AllocateZeroPool (sizeof (SCSI_DISK_DEV));
|
|
if (ScsiDiskDevice == NULL) {
|
|
return EFI_OUT_OF_RESOURCES;
|
|
}
|
|
|
|
Status = gBS->OpenProtocol (
|
|
Controller,
|
|
&gEfiScsiIoProtocolGuid,
|
|
(VOID **) &ScsiIo,
|
|
This->DriverBindingHandle,
|
|
Controller,
|
|
EFI_OPEN_PROTOCOL_BY_DRIVER
|
|
);
|
|
if (EFI_ERROR (Status)) {
|
|
FreePool (ScsiDiskDevice);
|
|
return Status;
|
|
}
|
|
|
|
ScsiDiskDevice->Signature = SCSI_DISK_DEV_SIGNATURE;
|
|
ScsiDiskDevice->ScsiIo = ScsiIo;
|
|
ScsiDiskDevice->BlkIo.Revision = EFI_BLOCK_IO_PROTOCOL_REVISION3;
|
|
ScsiDiskDevice->BlkIo.Media = &ScsiDiskDevice->BlkIoMedia;
|
|
ScsiDiskDevice->BlkIo.Media->IoAlign = ScsiIo->IoAlign;
|
|
ScsiDiskDevice->BlkIo.Reset = ScsiDiskReset;
|
|
ScsiDiskDevice->BlkIo.ReadBlocks = ScsiDiskReadBlocks;
|
|
ScsiDiskDevice->BlkIo.WriteBlocks = ScsiDiskWriteBlocks;
|
|
ScsiDiskDevice->BlkIo.FlushBlocks = ScsiDiskFlushBlocks;
|
|
ScsiDiskDevice->BlkIo2.Media = &ScsiDiskDevice->BlkIoMedia;
|
|
ScsiDiskDevice->BlkIo2.Reset = ScsiDiskResetEx;
|
|
ScsiDiskDevice->BlkIo2.ReadBlocksEx = ScsiDiskReadBlocksEx;
|
|
ScsiDiskDevice->BlkIo2.WriteBlocksEx = ScsiDiskWriteBlocksEx;
|
|
ScsiDiskDevice->BlkIo2.FlushBlocksEx = ScsiDiskFlushBlocksEx;
|
|
ScsiDiskDevice->EraseBlock.Revision = EFI_ERASE_BLOCK_PROTOCOL_REVISION;
|
|
ScsiDiskDevice->EraseBlock.EraseLengthGranularity = 1;
|
|
ScsiDiskDevice->EraseBlock.EraseBlocks = ScsiDiskEraseBlocks;
|
|
ScsiDiskDevice->UnmapInfo.MaxBlkDespCnt = 1;
|
|
ScsiDiskDevice->BlockLimitsVpdSupported = FALSE;
|
|
ScsiDiskDevice->Handle = Controller;
|
|
InitializeListHead (&ScsiDiskDevice->AsyncTaskQueue);
|
|
|
|
ScsiIo->GetDeviceType (ScsiIo, &(ScsiDiskDevice->DeviceType));
|
|
switch (ScsiDiskDevice->DeviceType) {
|
|
case EFI_SCSI_TYPE_DISK:
|
|
ScsiDiskDevice->BlkIo.Media->BlockSize = 0x200;
|
|
MustReadCapacity = TRUE;
|
|
break;
|
|
|
|
case EFI_SCSI_TYPE_CDROM:
|
|
ScsiDiskDevice->BlkIo.Media->BlockSize = 0x800;
|
|
ScsiDiskDevice->BlkIo.Media->ReadOnly = TRUE;
|
|
MustReadCapacity = FALSE;
|
|
break;
|
|
}
|
|
//
|
|
// The Sense Data Array's initial size is 6
|
|
//
|
|
ScsiDiskDevice->SenseDataNumber = 6;
|
|
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
|
|
);
|
|
FreePool (ScsiDiskDevice);
|
|
return EFI_OUT_OF_RESOURCES;
|
|
}
|
|
|
|
//
|
|
// Retrieve device information
|
|
//
|
|
MaxRetry = 2;
|
|
for (Index = 0; Index < MaxRetry; Index++) {
|
|
Status = ScsiDiskInquiryDevice (ScsiDiskDevice, &NeedRetry);
|
|
if (!EFI_ERROR (Status)) {
|
|
break;
|
|
}
|
|
|
|
if (!NeedRetry) {
|
|
FreePool (ScsiDiskDevice->SenseData);
|
|
gBS->CloseProtocol (
|
|
Controller,
|
|
&gEfiScsiIoProtocolGuid,
|
|
This->DriverBindingHandle,
|
|
Controller
|
|
);
|
|
FreePool (ScsiDiskDevice);
|
|
return EFI_DEVICE_ERROR;
|
|
}
|
|
}
|
|
//
|
|
// The second parameter "TRUE" means must
|
|
// retrieve media capacity
|
|
//
|
|
Status = ScsiDiskDetectMedia (ScsiDiskDevice, MustReadCapacity, &Temp);
|
|
if (!EFI_ERROR (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 ((EFI_D_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
|
|
);
|
|
return EFI_SUCCESS;
|
|
}
|
|
}
|
|
}
|
|
|
|
gBS->FreePool (ScsiDiskDevice->SenseData);
|
|
gBS->FreePool (ScsiDiskDevice);
|
|
gBS->CloseProtocol (
|
|
Controller,
|
|
&gEfiScsiIoProtocolGuid,
|
|
This->DriverBindingHandle,
|
|
Controller
|
|
);
|
|
return Status;
|
|
|
|
}
|
|
|
|
|
|
/**
|
|
Stop this driver on ControllerHandle.
|
|
|
|
This service is called by the EFI boot service DisconnectController().
|
|
In order to make drivers as small as possible, there are a few calling
|
|
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
|
|
children is zero stop the entire bus driver.
|
|
@param ChildHandleBuffer List of Child Handles to Stop.
|
|
|
|
@retval EFI_SUCCESS This driver is removed ControllerHandle
|
|
@retval other This driver was not removed from this device
|
|
|
|
**/
|
|
EFI_STATUS
|
|
EFIAPI
|
|
ScsiDiskDriverBindingStop (
|
|
IN EFI_DRIVER_BINDING_PROTOCOL *This,
|
|
IN EFI_HANDLE Controller,
|
|
IN UINTN NumberOfChildren,
|
|
IN EFI_HANDLE *ChildHandleBuffer OPTIONAL
|
|
)
|
|
{
|
|
EFI_BLOCK_IO_PROTOCOL *BlkIo;
|
|
EFI_ERASE_BLOCK_PROTOCOL *EraseBlock;
|
|
SCSI_DISK_DEV *ScsiDiskDevice;
|
|
EFI_STATUS Status;
|
|
|
|
Status = gBS->OpenProtocol (
|
|
Controller,
|
|
&gEfiBlockIoProtocolGuid,
|
|
(VOID **) &BlkIo,
|
|
This->DriverBindingHandle,
|
|
Controller,
|
|
EFI_OPEN_PROTOCOL_GET_PROTOCOL
|
|
);
|
|
if (EFI_ERROR (Status)) {
|
|
return Status;
|
|
}
|
|
|
|
ScsiDiskDevice = SCSI_DISK_DEV_FROM_BLKIO (BlkIo);
|
|
|
|
//
|
|
// Wait for the BlockIo2 requests queue to become empty
|
|
//
|
|
while (!IsListEmpty (&ScsiDiskDevice->AsyncTaskQueue));
|
|
|
|
//
|
|
// If Erase Block Protocol is installed, then uninstall this protocol.
|
|
//
|
|
Status = gBS->OpenProtocol (
|
|
Controller,
|
|
&gEfiEraseBlockProtocolGuid,
|
|
(VOID **) &EraseBlock,
|
|
This->DriverBindingHandle,
|
|
Controller,
|
|
EFI_OPEN_PROTOCOL_GET_PROTOCOL
|
|
);
|
|
|
|
if (!EFI_ERROR (Status)) {
|
|
Status = gBS->UninstallProtocolInterface (
|
|
Controller,
|
|
&gEfiEraseBlockProtocolGuid,
|
|
&ScsiDiskDevice->EraseBlock
|
|
);
|
|
if (EFI_ERROR (Status)) {
|
|
return Status;
|
|
}
|
|
}
|
|
|
|
Status = gBS->UninstallMultipleProtocolInterfaces (
|
|
Controller,
|
|
&gEfiBlockIoProtocolGuid,
|
|
&ScsiDiskDevice->BlkIo,
|
|
&gEfiBlockIo2ProtocolGuid,
|
|
&ScsiDiskDevice->BlkIo2,
|
|
&gEfiDiskInfoProtocolGuid,
|
|
&ScsiDiskDevice->DiskInfo,
|
|
NULL
|
|
);
|
|
if (!EFI_ERROR (Status)) {
|
|
gBS->CloseProtocol (
|
|
Controller,
|
|
&gEfiScsiIoProtocolGuid,
|
|
This->DriverBindingHandle,
|
|
Controller
|
|
);
|
|
|
|
ReleaseScsiDiskDeviceResources (ScsiDiskDevice);
|
|
|
|
return EFI_SUCCESS;
|
|
}
|
|
//
|
|
// errors met
|
|
//
|
|
return Status;
|
|
}
|
|
|
|
/**
|
|
Reset SCSI Disk.
|
|
|
|
|
|
@param This The pointer of EFI_BLOCK_IO_PROTOCOL
|
|
@param ExtendedVerification The flag about if extend verificate
|
|
|
|
@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
|
|
EFIAPI
|
|
ScsiDiskReset (
|
|
IN EFI_BLOCK_IO_PROTOCOL *This,
|
|
IN BOOLEAN ExtendedVerification
|
|
)
|
|
{
|
|
EFI_TPL OldTpl;
|
|
SCSI_DISK_DEV *ScsiDiskDevice;
|
|
EFI_STATUS Status;
|
|
|
|
OldTpl = gBS->RaiseTPL (TPL_CALLBACK);
|
|
|
|
ScsiDiskDevice = SCSI_DISK_DEV_FROM_BLKIO (This);
|
|
|
|
Status = ScsiDiskDevice->ScsiIo->ResetDevice (ScsiDiskDevice->ScsiIo);
|
|
|
|
if (EFI_ERROR (Status)) {
|
|
if (Status == EFI_UNSUPPORTED) {
|
|
Status = EFI_SUCCESS;
|
|
} else {
|
|
Status = EFI_DEVICE_ERROR;
|
|
goto Done;
|
|
}
|
|
}
|
|
|
|
if (!ExtendedVerification) {
|
|
goto Done;
|
|
}
|
|
|
|
Status = ScsiDiskDevice->ScsiIo->ResetBus (ScsiDiskDevice->ScsiIo);
|
|
|
|
if (EFI_ERROR (Status)) {
|
|
Status = EFI_DEVICE_ERROR;
|
|
goto Done;
|
|
}
|
|
|
|
Done:
|
|
gBS->RestoreTPL (OldTpl);
|
|
return Status;
|
|
}
|
|
|
|
/**
|
|
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 BufferSize The size of Buffer
|
|
@param Buffer The buffer to fill the read out data
|
|
|
|
@retval EFI_SUCCESS Successfully to read out block.
|
|
@retval EFI_DEVICE_ERROR Fail to detect media.
|
|
@retval EFI_NO_MEDIA Media is not present.
|
|
@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.
|
|
|
|
**/
|
|
EFI_STATUS
|
|
EFIAPI
|
|
ScsiDiskReadBlocks (
|
|
IN EFI_BLOCK_IO_PROTOCOL *This,
|
|
IN UINT32 MediaId,
|
|
IN EFI_LBA Lba,
|
|
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_BLKIO (This);
|
|
|
|
if (!IS_DEVICE_FIXED(ScsiDiskDevice)) {
|
|
|
|
Status = ScsiDiskDetectMedia (ScsiDiskDevice, FALSE, &MediaChange);
|
|
if (EFI_ERROR (Status)) {
|
|
Status = EFI_DEVICE_ERROR;
|
|
goto Done;
|
|
}
|
|
|
|
if (MediaChange) {
|
|
gBS->ReinstallProtocolInterface (
|
|
ScsiDiskDevice->Handle,
|
|
&gEfiBlockIoProtocolGuid,
|
|
&ScsiDiskDevice->BlkIo,
|
|
&ScsiDiskDevice->BlkIo
|
|
);
|
|
gBS->ReinstallProtocolInterface (
|
|
ScsiDiskDevice->Handle,
|
|
&gEfiBlockIo2ProtocolGuid,
|
|
&ScsiDiskDevice->BlkIo2,
|
|
&ScsiDiskDevice->BlkIo2
|
|
);
|
|
if (DetermineInstallEraseBlock(ScsiDiskDevice, ScsiDiskDevice->Handle)) {
|
|
gBS->ReinstallProtocolInterface (
|
|
ScsiDiskDevice->Handle,
|
|
&gEfiEraseBlockProtocolGuid,
|
|
&ScsiDiskDevice->EraseBlock,
|
|
&ScsiDiskDevice->EraseBlock
|
|
);
|
|
}
|
|
Status = EFI_MEDIA_CHANGED;
|
|
goto Done;
|
|
}
|
|
}
|
|
//
|
|
// Get the intrinsic block size
|
|
//
|
|
Media = ScsiDiskDevice->BlkIo.Media;
|
|
BlockSize = Media->BlockSize;
|
|
|
|
NumberOfBlocks = BufferSize / BlockSize;
|
|
|
|
if (!(Media->MediaPresent)) {
|
|
Status = EFI_NO_MEDIA;
|
|
goto Done;
|
|
}
|
|
|
|
if (MediaId != Media->MediaId) {
|
|
Status = EFI_MEDIA_CHANGED;
|
|
goto Done;
|
|
}
|
|
|
|
if (Buffer == NULL) {
|
|
Status = EFI_INVALID_PARAMETER;
|
|
goto Done;
|
|
}
|
|
|
|
if (BufferSize == 0) {
|
|
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.
|
|
//
|
|
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 BufferSize The size of Buffer
|
|
@param Buffer The buffer to fill the read out data
|
|
|
|
@retval EFI_SUCCESS Successfully to read out block.
|
|
@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_BAD_BUFFER_SIZE The Buffer was not a multiple of the block size of the device.
|
|
@retval EFI_INVALID_PARAMETER Invalid parameter passed in.
|
|
|
|
**/
|
|
EFI_STATUS
|
|
EFIAPI
|
|
ScsiDiskWriteBlocks (
|
|
IN EFI_BLOCK_IO_PROTOCOL *This,
|
|
IN UINT32 MediaId,
|
|
IN EFI_LBA Lba,
|
|
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_BLKIO (This);
|
|
|
|
if (!IS_DEVICE_FIXED(ScsiDiskDevice)) {
|
|
|
|
Status = ScsiDiskDetectMedia (ScsiDiskDevice, FALSE, &MediaChange);
|
|
if (EFI_ERROR (Status)) {
|
|
Status = EFI_DEVICE_ERROR;
|
|
goto Done;
|
|
}
|
|
|
|
if (MediaChange) {
|
|
gBS->ReinstallProtocolInterface (
|
|
ScsiDiskDevice->Handle,
|
|
&gEfiBlockIoProtocolGuid,
|
|
&ScsiDiskDevice->BlkIo,
|
|
&ScsiDiskDevice->BlkIo
|
|
);
|
|
gBS->ReinstallProtocolInterface (
|
|
ScsiDiskDevice->Handle,
|
|
&gEfiBlockIo2ProtocolGuid,
|
|
&ScsiDiskDevice->BlkIo2,
|
|
&ScsiDiskDevice->BlkIo2
|
|
);
|
|
if (DetermineInstallEraseBlock(ScsiDiskDevice, ScsiDiskDevice->Handle)) {
|
|
gBS->ReinstallProtocolInterface (
|
|
ScsiDiskDevice->Handle,
|
|
&gEfiEraseBlockProtocolGuid,
|
|
&ScsiDiskDevice->EraseBlock,
|
|
&ScsiDiskDevice->EraseBlock
|
|
);
|
|
}
|
|
Status = EFI_MEDIA_CHANGED;
|
|
goto Done;
|
|
}
|
|
}
|
|
//
|
|
// Get the intrinsic block size
|
|
//
|
|
Media = ScsiDiskDevice->BlkIo.Media;
|
|
BlockSize = Media->BlockSize;
|
|
|
|
NumberOfBlocks = BufferSize / BlockSize;
|
|
|
|
if (!(Media->MediaPresent)) {
|
|
Status = EFI_NO_MEDIA;
|
|
goto Done;
|
|
}
|
|
|
|
if (MediaId != Media->MediaId) {
|
|
Status = EFI_MEDIA_CHANGED;
|
|
goto Done;
|
|
}
|
|
|
|
if (Media->ReadOnly) {
|
|
Status = EFI_WRITE_PROTECTED;
|
|
goto Done;
|
|
}
|
|
|
|
if (BufferSize == 0) {
|
|
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 read sectors command
|
|
// to transfer data from device to host.
|
|
//
|
|
Status = ScsiDiskWriteSectors (ScsiDiskDevice, Buffer, Lba, NumberOfBlocks);
|
|
|
|
Done:
|
|
gBS->RestoreTPL (OldTpl);
|
|
return Status;
|
|
}
|
|
|
|
/**
|
|
Flush Block to Disk.
|
|
|
|
EFI_SUCCESS is returned directly.
|
|
|
|
@param This The pointer of EFI_BLOCK_IO_PROTOCOL
|
|
|
|
@retval EFI_SUCCESS All outstanding data was written to the device
|
|
|
|
**/
|
|
EFI_STATUS
|
|
EFIAPI
|
|
ScsiDiskFlushBlocks (
|
|
IN EFI_BLOCK_IO_PROTOCOL *This
|
|
)
|
|
{
|
|
//
|
|
// return directly
|
|
//
|
|
return EFI_SUCCESS;
|
|
}
|
|
|
|
|
|
/**
|
|
Reset SCSI Disk.
|
|
|
|
@param This The pointer of EFI_BLOCK_IO2_PROTOCOL.
|
|
@param ExtendedVerification The flag about if extend verificate.
|
|
|
|
@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
|
|
EFIAPI
|
|
ScsiDiskResetEx (
|
|
IN EFI_BLOCK_IO2_PROTOCOL *This,
|
|
IN BOOLEAN ExtendedVerification
|
|
)
|
|
{
|
|
EFI_TPL OldTpl;
|
|
SCSI_DISK_DEV *ScsiDiskDevice;
|
|
EFI_STATUS Status;
|
|
|
|
OldTpl = gBS->RaiseTPL (TPL_CALLBACK);
|
|
|
|
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 (!ExtendedVerification) {
|
|
goto Done;
|
|
}
|
|
|
|
Status = ScsiDiskDevice->ScsiIo->ResetBus (ScsiDiskDevice->ScsiIo);
|
|
|
|
if (EFI_ERROR (Status)) {
|
|
Status = EFI_DEVICE_ERROR;
|
|
goto Done;
|
|
}
|
|
|
|
Done:
|
|
gBS->RestoreTPL (OldTpl);
|
|
return Status;
|
|
}
|
|
|
|
/**
|
|
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);
|
|
|
|
if (!IS_DEVICE_FIXED(ScsiDiskDevice)) {
|
|
|
|
Status = ScsiDiskDetectMedia (ScsiDiskDevice, FALSE, &MediaChange);
|
|
if (EFI_ERROR (Status)) {
|
|
Status = EFI_DEVICE_ERROR;
|
|
goto Done;
|
|
}
|
|
|
|
if (MediaChange) {
|
|
gBS->ReinstallProtocolInterface (
|
|
ScsiDiskDevice->Handle,
|
|
&gEfiBlockIoProtocolGuid,
|
|
&ScsiDiskDevice->BlkIo,
|
|
&ScsiDiskDevice->BlkIo
|
|
);
|
|
gBS->ReinstallProtocolInterface (
|
|
ScsiDiskDevice->Handle,
|
|
&gEfiBlockIo2ProtocolGuid,
|
|
&ScsiDiskDevice->BlkIo2,
|
|
&ScsiDiskDevice->BlkIo2
|
|
);
|
|
if (DetermineInstallEraseBlock(ScsiDiskDevice, ScsiDiskDevice->Handle)) {
|
|
gBS->ReinstallProtocolInterface (
|
|
ScsiDiskDevice->Handle,
|
|
&gEfiEraseBlockProtocolGuid,
|
|
&ScsiDiskDevice->EraseBlock,
|
|
&ScsiDiskDevice->EraseBlock
|
|
);
|
|
}
|
|
Status = EFI_MEDIA_CHANGED;
|
|
goto Done;
|
|
}
|
|
}
|
|
//
|
|
// Get the intrinsic block size
|
|
//
|
|
Media = ScsiDiskDevice->BlkIo2.Media;
|
|
BlockSize = Media->BlockSize;
|
|
|
|
NumberOfBlocks = BufferSize / BlockSize;
|
|
|
|
if (!(Media->MediaPresent)) {
|
|
Status = EFI_NO_MEDIA;
|
|
goto Done;
|
|
}
|
|
|
|
if (MediaId != Media->MediaId) {
|
|
Status = EFI_MEDIA_CHANGED;
|
|
goto Done;
|
|
}
|
|
|
|
if (Buffer == NULL) {
|
|
Status = EFI_INVALID_PARAMETER;
|
|
goto Done;
|
|
}
|
|
|
|
if (BufferSize == 0) {
|
|
if ((Token != NULL) && (Token->Event != NULL)) {
|
|
Token->TransactionStatus = EFI_SUCCESS;
|
|
gBS->SignalEvent (Token->Event);
|
|
}
|
|
|
|
Status = EFI_SUCCESS;
|
|
goto Done;
|
|
}
|
|
|
|
if (BufferSize % BlockSize != 0) {
|
|
Status = EFI_BAD_BUFFER_SIZE;
|
|
goto Done;
|
|
}
|
|
|
|
if (Lba > Media->LastBlock) {
|
|
Status = EFI_INVALID_PARAMETER;
|
|
goto Done;
|
|
}
|
|
|
|
if ((Lba + NumberOfBlocks - 1) > Media->LastBlock) {
|
|
Status = EFI_INVALID_PARAMETER;
|
|
goto Done;
|
|
}
|
|
|
|
if ((Media->IoAlign > 1) && (((UINTN) Buffer & (Media->IoAlign - 1)) != 0)) {
|
|
Status = EFI_INVALID_PARAMETER;
|
|
goto Done;
|
|
}
|
|
|
|
//
|
|
// If all the parameters are valid, then perform read sectors command
|
|
// to transfer data from device to host.
|
|
//
|
|
if ((Token != NULL) && (Token->Event != NULL)) {
|
|
Token->TransactionStatus = EFI_SUCCESS;
|
|
Status = ScsiDiskAsyncReadSectors (
|
|
ScsiDiskDevice,
|
|
Buffer,
|
|
Lba,
|
|
NumberOfBlocks,
|
|
Token
|
|
);
|
|
} else {
|
|
Status = ScsiDiskReadSectors (
|
|
ScsiDiskDevice,
|
|
Buffer,
|
|
Lba,
|
|
NumberOfBlocks
|
|
);
|
|
}
|
|
|
|
Done:
|
|
gBS->RestoreTPL (OldTpl);
|
|
return Status;
|
|
}
|
|
|
|
/**
|
|
The function is to Write Block to SCSI Disk.
|
|
|
|
@param This The pointer of EFI_BLOCK_IO_PROTOCOL.
|
|
@param MediaId The Id of Media detected.
|
|
@param Lba The logic block address.
|
|
@param Token A pointer to the token associated with the transaction.
|
|
@param BufferSize The size of Buffer.
|
|
@param Buffer The buffer to fill the read out data.
|
|
|
|
@retval EFI_SUCCESS The data were written correctly to the device.
|
|
@retval EFI_WRITE_PROTECTED The device cannot be written to.
|
|
@retval EFI_NO_MEDIA There is no media in the device.
|
|
@retval EFI_MEDIA_CHANGED The MediaId is not for the current media.
|
|
@retval EFI_DEVICE_ERROR The device reported an error while attempting
|
|
to perform the write operation.
|
|
@retval EFI_BAD_BUFFER_SIZE The BufferSize parameter is not a multiple of
|
|
the intrinsic block size of the device.
|
|
@retval EFI_INVALID_PARAMETER The write request contains LBAs that are not
|
|
valid, or the buffer is not on proper
|
|
alignment.
|
|
|
|
**/
|
|
EFI_STATUS
|
|
EFIAPI
|
|
ScsiDiskWriteBlocksEx (
|
|
IN EFI_BLOCK_IO2_PROTOCOL *This,
|
|
IN UINT32 MediaId,
|
|
IN EFI_LBA Lba,
|
|
IN OUT EFI_BLOCK_IO2_TOKEN *Token,
|
|
IN UINTN BufferSize,
|
|
IN VOID *Buffer
|
|
)
|
|
{
|
|
SCSI_DISK_DEV *ScsiDiskDevice;
|
|
EFI_BLOCK_IO_MEDIA *Media;
|
|
EFI_STATUS Status;
|
|
UINTN BlockSize;
|
|
UINTN NumberOfBlocks;
|
|
BOOLEAN MediaChange;
|
|
EFI_TPL OldTpl;
|
|
|
|
MediaChange = FALSE;
|
|
OldTpl = gBS->RaiseTPL (TPL_CALLBACK);
|
|
ScsiDiskDevice = SCSI_DISK_DEV_FROM_BLKIO2 (This);
|
|
|
|
if (!IS_DEVICE_FIXED(ScsiDiskDevice)) {
|
|
|
|
Status = ScsiDiskDetectMedia (ScsiDiskDevice, FALSE, &MediaChange);
|
|
if (EFI_ERROR (Status)) {
|
|
Status = EFI_DEVICE_ERROR;
|
|
goto Done;
|
|
}
|
|
|
|
if (MediaChange) {
|
|
gBS->ReinstallProtocolInterface (
|
|
ScsiDiskDevice->Handle,
|
|
&gEfiBlockIoProtocolGuid,
|
|
&ScsiDiskDevice->BlkIo,
|
|
&ScsiDiskDevice->BlkIo
|
|
);
|
|
gBS->ReinstallProtocolInterface (
|
|
ScsiDiskDevice->Handle,
|
|
&gEfiBlockIo2ProtocolGuid,
|
|
&ScsiDiskDevice->BlkIo2,
|
|
&ScsiDiskDevice->BlkIo2
|
|
);
|
|
if (DetermineInstallEraseBlock(ScsiDiskDevice, ScsiDiskDevice->Handle)) {
|
|
gBS->ReinstallProtocolInterface (
|
|
ScsiDiskDevice->Handle,
|
|
&gEfiEraseBlockProtocolGuid,
|
|
&ScsiDiskDevice->EraseBlock,
|
|
&ScsiDiskDevice->EraseBlock
|
|
);
|
|
}
|
|
Status = EFI_MEDIA_CHANGED;
|
|
goto Done;
|
|
}
|
|
}
|
|
//
|
|
// Get the intrinsic block size
|
|
//
|
|
Media = ScsiDiskDevice->BlkIo2.Media;
|
|
BlockSize = Media->BlockSize;
|
|
|
|
NumberOfBlocks = BufferSize / BlockSize;
|
|
|
|
if (!(Media->MediaPresent)) {
|
|
Status = EFI_NO_MEDIA;
|
|
goto Done;
|
|
}
|
|
|
|
if (MediaId != Media->MediaId) {
|
|
Status = EFI_MEDIA_CHANGED;
|
|
goto Done;
|
|
}
|
|
|
|
if (Media->ReadOnly) {
|
|
Status = EFI_WRITE_PROTECTED;
|
|
goto Done;
|
|
}
|
|
|
|
if (BufferSize == 0) {
|
|
if ((Token != NULL) && (Token->Event != NULL)) {
|
|
Token->TransactionStatus = EFI_SUCCESS;
|
|
gBS->SignalEvent (Token->Event);
|
|
}
|
|
|
|
Status = EFI_SUCCESS;
|
|
goto Done;
|
|
}
|
|
|
|
if (Buffer == NULL) {
|
|
Status = EFI_INVALID_PARAMETER;
|
|
goto Done;
|
|
}
|
|
|
|
if (BufferSize % BlockSize != 0) {
|
|
Status = EFI_BAD_BUFFER_SIZE;
|
|
goto Done;
|
|
}
|
|
|
|
if (Lba > Media->LastBlock) {
|
|
Status = EFI_INVALID_PARAMETER;
|
|
goto Done;
|
|
}
|
|
|
|
if ((Lba + NumberOfBlocks - 1) > Media->LastBlock) {
|
|
Status = EFI_INVALID_PARAMETER;
|
|
goto Done;
|
|
}
|
|
|
|
if ((Media->IoAlign > 1) && (((UINTN) Buffer & (Media->IoAlign - 1)) != 0)) {
|
|
Status = EFI_INVALID_PARAMETER;
|
|
goto Done;
|
|
}
|
|
|
|
//
|
|
// if all the parameters are valid, then perform write sectors command
|
|
// to transfer data from device to host.
|
|
//
|
|
if ((Token != NULL) && (Token->Event != NULL)) {
|
|
Token->TransactionStatus = EFI_SUCCESS;
|
|
Status = ScsiDiskAsyncWriteSectors (
|
|
ScsiDiskDevice,
|
|
Buffer,
|
|
Lba,
|
|
NumberOfBlocks,
|
|
Token
|
|
);
|
|
} else {
|
|
Status = ScsiDiskWriteSectors (
|
|
ScsiDiskDevice,
|
|
Buffer,
|
|
Lba,
|
|
NumberOfBlocks
|
|
);
|
|
}
|
|
|
|
Done:
|
|
gBS->RestoreTPL (OldTpl);
|
|
return Status;
|
|
}
|
|
|
|
/**
|
|
Flush the Block Device.
|
|
|
|
@param This Indicates a pointer to the calling context.
|
|
@param Token A pointer to the token associated with the transaction.
|
|
|
|
@retval EFI_SUCCESS All outstanding data was written to the device.
|
|
@retval EFI_DEVICE_ERROR The device reported an error while attempting to
|
|
write data.
|
|
@retval EFI_WRITE_PROTECTED The device cannot be written to.
|
|
@retval EFI_NO_MEDIA There is no media in the device.
|
|
@retval EFI_MEDIA_CHANGED The MediaId is not for the current media.
|
|
|
|
**/
|
|
EFI_STATUS
|
|
EFIAPI
|
|
ScsiDiskFlushBlocksEx (
|
|
IN EFI_BLOCK_IO2_PROTOCOL *This,
|
|
IN OUT EFI_BLOCK_IO2_TOKEN *Token
|
|
)
|
|
{
|
|
SCSI_DISK_DEV *ScsiDiskDevice;
|
|
EFI_BLOCK_IO_MEDIA *Media;
|
|
EFI_STATUS Status;
|
|
BOOLEAN MediaChange;
|
|
EFI_TPL OldTpl;
|
|
|
|
MediaChange = FALSE;
|
|
OldTpl = gBS->RaiseTPL (TPL_CALLBACK);
|
|
ScsiDiskDevice = SCSI_DISK_DEV_FROM_BLKIO2 (This);
|
|
|
|
if (!IS_DEVICE_FIXED(ScsiDiskDevice)) {
|
|
|
|
Status = ScsiDiskDetectMedia (ScsiDiskDevice, FALSE, &MediaChange);
|
|
if (EFI_ERROR (Status)) {
|
|
Status = EFI_DEVICE_ERROR;
|
|
goto Done;
|
|
}
|
|
|
|
if (MediaChange) {
|
|
gBS->ReinstallProtocolInterface (
|
|
ScsiDiskDevice->Handle,
|
|
&gEfiBlockIoProtocolGuid,
|
|
&ScsiDiskDevice->BlkIo,
|
|
&ScsiDiskDevice->BlkIo
|
|
);
|
|
gBS->ReinstallProtocolInterface (
|
|
ScsiDiskDevice->Handle,
|
|
&gEfiBlockIo2ProtocolGuid,
|
|
&ScsiDiskDevice->BlkIo2,
|
|
&ScsiDiskDevice->BlkIo2
|
|
);
|
|
if (DetermineInstallEraseBlock(ScsiDiskDevice, ScsiDiskDevice->Handle)) {
|
|
gBS->ReinstallProtocolInterface (
|
|
ScsiDiskDevice->Handle,
|
|
&gEfiEraseBlockProtocolGuid,
|
|
&ScsiDiskDevice->EraseBlock,
|
|
&ScsiDiskDevice->EraseBlock
|
|
);
|
|
}
|
|
Status = EFI_MEDIA_CHANGED;
|
|
goto Done;
|
|
}
|
|
}
|
|
|
|
Media = ScsiDiskDevice->BlkIo2.Media;
|
|
|
|
if (!(Media->MediaPresent)) {
|
|
Status = EFI_NO_MEDIA;
|
|
goto Done;
|
|
}
|
|
|
|
if (Media->ReadOnly) {
|
|
Status = EFI_WRITE_PROTECTED;
|
|
goto Done;
|
|
}
|
|
|
|
//
|
|
// Wait for the BlockIo2 requests queue to become empty
|
|
//
|
|
while (!IsListEmpty (&ScsiDiskDevice->AsyncTaskQueue));
|
|
|
|
Status = EFI_SUCCESS;
|
|
|
|
//
|
|
// Signal caller event
|
|
//
|
|
if ((Token != NULL) && (Token->Event != NULL)) {
|
|
Token->TransactionStatus = EFI_SUCCESS;
|
|
gBS->SignalEvent (Token->Event);
|
|
}
|
|
|
|
Done:
|
|
gBS->RestoreTPL (OldTpl);
|
|
return Status;
|
|
}
|
|
|
|
|
|
/**
|
|
Internal helper notify function which process the result of an asynchronous
|
|
SCSI UNMAP Command and signal the event passed from EraseBlocks.
|
|
|
|
@param Event The instance of EFI_EVENT.
|
|
@param Context The parameter passed in.
|
|
|
|
**/
|
|
VOID
|
|
EFIAPI
|
|
ScsiDiskAsyncUnmapNotify (
|
|
IN EFI_EVENT Event,
|
|
IN VOID *Context
|
|
)
|
|
{
|
|
SCSI_ERASEBLK_REQUEST *EraseBlkReq;
|
|
EFI_SCSI_IO_SCSI_REQUEST_PACKET *CommandPacket;
|
|
EFI_ERASE_BLOCK_TOKEN *Token;
|
|
EFI_STATUS Status;
|
|
|
|
gBS->CloseEvent (Event);
|
|
|
|
EraseBlkReq = (SCSI_ERASEBLK_REQUEST *) Context;
|
|
CommandPacket = &EraseBlkReq->CommandPacket;
|
|
Token = EraseBlkReq->Token;
|
|
Token->TransactionStatus = EFI_SUCCESS;
|
|
|
|
Status = CheckHostAdapterStatus (CommandPacket->HostAdapterStatus);
|
|
if (EFI_ERROR(Status)) {
|
|
DEBUG ((
|
|
EFI_D_ERROR,
|
|
"ScsiDiskAsyncUnmapNotify: Host adapter indicating error status 0x%x.\n",
|
|
CommandPacket->HostAdapterStatus
|
|
));
|
|
|
|
Token->TransactionStatus = Status;
|
|
goto Done;
|
|
}
|
|
|
|
Status = CheckTargetStatus (CommandPacket->TargetStatus);
|
|
if (EFI_ERROR(Status)) {
|
|
DEBUG ((
|
|
EFI_D_ERROR,
|
|
"ScsiDiskAsyncUnmapNotify: Target indicating error status 0x%x.\n",
|
|
CommandPacket->HostAdapterStatus
|
|
));
|
|
|
|
Token->TransactionStatus = Status;
|
|
goto Done;
|
|
}
|
|
|
|
Done:
|
|
RemoveEntryList (&EraseBlkReq->Link);
|
|
FreePool (CommandPacket->OutDataBuffer);
|
|
FreePool (EraseBlkReq->CommandPacket.Cdb);
|
|
FreePool (EraseBlkReq);
|
|
|
|
gBS->SignalEvent (Token->Event);
|
|
}
|
|
|
|
/**
|
|
Require the device server to cause one or more LBAs to be unmapped.
|
|
|
|
@param ScsiDiskDevice The pointer of ScsiDiskDevice.
|
|
@param Lba The start block number.
|
|
@param Blocks Total block number to be unmapped.
|
|
@param Token The pointer to the token associated with the
|
|
non-blocking erase block request.
|
|
|
|
@retval EFI_SUCCESS Target blocks have been successfully unmapped.
|
|
@retval EFI_DEVICE_ERROR Fail to unmap the target blocks.
|
|
|
|
**/
|
|
EFI_STATUS
|
|
ScsiDiskUnmap (
|
|
IN SCSI_DISK_DEV *ScsiDiskDevice,
|
|
IN UINT64 Lba,
|
|
IN UINTN Blocks,
|
|
IN EFI_ERASE_BLOCK_TOKEN *Token OPTIONAL
|
|
)
|
|
{
|
|
EFI_SCSI_IO_PROTOCOL *ScsiIo;
|
|
SCSI_ERASEBLK_REQUEST *EraseBlkReq;
|
|
EFI_SCSI_IO_SCSI_REQUEST_PACKET *CommandPacket;
|
|
EFI_SCSI_DISK_UNMAP_BLOCK_DESP *BlkDespPtr;
|
|
EFI_STATUS Status;
|
|
EFI_STATUS ReturnStatus;
|
|
UINT8 *Cdb;
|
|
UINT32 MaxLbaCnt;
|
|
UINT32 MaxBlkDespCnt;
|
|
UINT32 BlkDespCnt;
|
|
UINT16 UnmapParamListLen;
|
|
VOID *UnmapParamList;
|
|
EFI_EVENT AsyncUnmapEvent;
|
|
EFI_TPL OldTpl;
|
|
|
|
ScsiIo = ScsiDiskDevice->ScsiIo;
|
|
MaxLbaCnt = ScsiDiskDevice->UnmapInfo.MaxLbaCnt;
|
|
MaxBlkDespCnt = ScsiDiskDevice->UnmapInfo.MaxBlkDespCnt;
|
|
EraseBlkReq = NULL;
|
|
UnmapParamList = NULL;
|
|
AsyncUnmapEvent = NULL;
|
|
ReturnStatus = EFI_SUCCESS;
|
|
|
|
if (Blocks / (UINTN) MaxLbaCnt > MaxBlkDespCnt) {
|
|
ReturnStatus = EFI_DEVICE_ERROR;
|
|
goto Done;
|
|
}
|
|
|
|
EraseBlkReq = AllocateZeroPool (sizeof (SCSI_ERASEBLK_REQUEST));
|
|
if (EraseBlkReq == NULL) {
|
|
ReturnStatus = EFI_DEVICE_ERROR;
|
|
goto Done;
|
|
}
|
|
|
|
EraseBlkReq->CommandPacket.Cdb = AllocateZeroPool (0xA);
|
|
if (EraseBlkReq->CommandPacket.Cdb == NULL) {
|
|
ReturnStatus = EFI_DEVICE_ERROR;
|
|
goto Done;
|
|
}
|
|
|
|
BlkDespCnt = (UINT32) ((Blocks - 1) / MaxLbaCnt + 1);
|
|
UnmapParamListLen = (UINT16) (sizeof (EFI_SCSI_DISK_UNMAP_PARAM_LIST_HEADER)
|
|
+ BlkDespCnt * sizeof (EFI_SCSI_DISK_UNMAP_BLOCK_DESP));
|
|
UnmapParamList = AllocateZeroPool (UnmapParamListLen);
|
|
if (UnmapParamList == NULL) {
|
|
ReturnStatus = EFI_DEVICE_ERROR;
|
|
goto Done;
|
|
}
|
|
|
|
*((UINT16 *)UnmapParamList) = SwapBytes16 (UnmapParamListLen - 2);
|
|
*((UINT16 *)UnmapParamList + 1) = SwapBytes16 (UnmapParamListLen - sizeof (EFI_SCSI_DISK_UNMAP_PARAM_LIST_HEADER));
|
|
|
|
BlkDespPtr = (EFI_SCSI_DISK_UNMAP_BLOCK_DESP *)((UINT8 *)UnmapParamList + sizeof (EFI_SCSI_DISK_UNMAP_PARAM_LIST_HEADER));
|
|
while (Blocks > 0) {
|
|
if (Blocks > MaxLbaCnt) {
|
|
*(UINT64 *)(&BlkDespPtr->Lba) = SwapBytes64 (Lba);
|
|
*(UINT32 *)(&BlkDespPtr->BlockNum) = SwapBytes32 (MaxLbaCnt);
|
|
Blocks -= MaxLbaCnt;
|
|
Lba += MaxLbaCnt;
|
|
} else {
|
|
*(UINT64 *)(&BlkDespPtr->Lba) = SwapBytes64 (Lba);
|
|
*(UINT32 *)(&BlkDespPtr->BlockNum) = SwapBytes32 ((UINT32) Blocks);
|
|
Blocks = 0;
|
|
}
|
|
|
|
BlkDespPtr++;
|
|
}
|
|
|
|
CommandPacket = &EraseBlkReq->CommandPacket;
|
|
CommandPacket->Timeout = SCSI_DISK_TIMEOUT;
|
|
CommandPacket->OutDataBuffer = UnmapParamList;
|
|
CommandPacket->OutTransferLength = UnmapParamListLen;
|
|
CommandPacket->CdbLength = 0xA;
|
|
CommandPacket->DataDirection = EFI_SCSI_DATA_OUT;
|
|
//
|
|
// Fill Cdb for UNMAP Command
|
|
//
|
|
Cdb = CommandPacket->Cdb;
|
|
Cdb[0] = EFI_SCSI_OP_UNMAP;
|
|
WriteUnaligned16 ((UINT16 *)&Cdb[7], SwapBytes16 (UnmapParamListLen));
|
|
|
|
if ((Token != NULL) && (Token->Event != NULL)) {
|
|
//
|
|
// Non-blocking UNMAP request
|
|
//
|
|
Status = gBS->CreateEvent (
|
|
EVT_NOTIFY_SIGNAL,
|
|
TPL_NOTIFY,
|
|
ScsiDiskAsyncUnmapNotify,
|
|
EraseBlkReq,
|
|
&AsyncUnmapEvent
|
|
);
|
|
if (EFI_ERROR(Status)) {
|
|
ReturnStatus = EFI_DEVICE_ERROR;
|
|
goto Done;
|
|
}
|
|
|
|
OldTpl = gBS->RaiseTPL (TPL_NOTIFY);
|
|
InsertTailList (&ScsiDiskDevice->AsyncTaskQueue, &EraseBlkReq->Link);
|
|
gBS->RestoreTPL (OldTpl);
|
|
|
|
EraseBlkReq->Token = Token;
|
|
|
|
Status = ScsiIo->ExecuteScsiCommand (
|
|
ScsiIo,
|
|
CommandPacket,
|
|
AsyncUnmapEvent
|
|
);
|
|
if (EFI_ERROR(Status)) {
|
|
ReturnStatus = EFI_DEVICE_ERROR;
|
|
|
|
OldTpl = gBS->RaiseTPL (TPL_NOTIFY);
|
|
RemoveEntryList (&EraseBlkReq->Link);
|
|
gBS->RestoreTPL (OldTpl);
|
|
|
|
goto Done;
|
|
} else {
|
|
//
|
|
// Directly return if the non-blocking UNMAP request is queued.
|
|
//
|
|
return EFI_SUCCESS;
|
|
}
|
|
} else {
|
|
//
|
|
// Blocking UNMAP request
|
|
//
|
|
Status = ScsiIo->ExecuteScsiCommand (
|
|
ScsiIo,
|
|
CommandPacket,
|
|
NULL
|
|
);
|
|
if (EFI_ERROR(Status)) {
|
|
ReturnStatus = EFI_DEVICE_ERROR;
|
|
goto Done;
|
|
}
|
|
}
|
|
|
|
//
|
|
// Only blocking UNMAP request will reach here.
|
|
//
|
|
Status = CheckHostAdapterStatus (CommandPacket->HostAdapterStatus);
|
|
if (EFI_ERROR(Status)) {
|
|
DEBUG ((
|
|
EFI_D_ERROR,
|
|
"ScsiDiskUnmap: Host adapter indicating error status 0x%x.\n",
|
|
CommandPacket->HostAdapterStatus
|
|
));
|
|
|
|
ReturnStatus = EFI_DEVICE_ERROR;
|
|
goto Done;
|
|
}
|
|
|
|
Status = CheckTargetStatus (CommandPacket->TargetStatus);
|
|
if (EFI_ERROR(Status)) {
|
|
DEBUG ((
|
|
EFI_D_ERROR,
|
|
"ScsiDiskUnmap: Target indicating error status 0x%x.\n",
|
|
CommandPacket->HostAdapterStatus
|
|
));
|
|
|
|
ReturnStatus = EFI_DEVICE_ERROR;
|
|
goto Done;
|
|
}
|
|
|
|
Done:
|
|
if (EraseBlkReq != NULL) {
|
|
if (EraseBlkReq->CommandPacket.Cdb != NULL) {
|
|
FreePool (EraseBlkReq->CommandPacket.Cdb);
|
|
}
|
|
FreePool (EraseBlkReq);
|
|
}
|
|
|
|
if (UnmapParamList != NULL) {
|
|
FreePool (UnmapParamList);
|
|
}
|
|
|
|
if (AsyncUnmapEvent != NULL) {
|
|
gBS->CloseEvent (AsyncUnmapEvent);
|
|
}
|
|
|
|
return ReturnStatus;
|
|
}
|
|
|
|
/**
|
|
Erase a specified number of device blocks.
|
|
|
|
@param[in] This Indicates a pointer to the calling context.
|
|
@param[in] MediaId The media ID that the erase request is for.
|
|
@param[in] Lba The starting logical block address to be
|
|
erased. The caller is responsible for erasing
|
|
only legitimate locations.
|
|
@param[in, out] Token A pointer to the token associated with the
|
|
transaction.
|
|
@param[in] Size The size in bytes to be erased. This must be
|
|
a multiple of the physical block size of the
|
|
device.
|
|
|
|
@retval EFI_SUCCESS The erase request was queued if Event is not
|
|
NULL. The data was erased correctly to the
|
|
device if the Event is NULL.to the device.
|
|
@retval EFI_WRITE_PROTECTED The device cannot be erased due to write
|
|
protection.
|
|
@retval EFI_DEVICE_ERROR The device reported an error while attempting
|
|
to perform the erase operation.
|
|
@retval EFI_INVALID_PARAMETER The erase request contains LBAs that are not
|
|
valid.
|
|
@retval EFI_NO_MEDIA There is no media in the device.
|
|
@retval EFI_MEDIA_CHANGED The MediaId is not for the current media.
|
|
|
|
**/
|
|
EFI_STATUS
|
|
EFIAPI
|
|
ScsiDiskEraseBlocks (
|
|
IN EFI_ERASE_BLOCK_PROTOCOL *This,
|
|
IN UINT32 MediaId,
|
|
IN EFI_LBA Lba,
|
|
IN OUT EFI_ERASE_BLOCK_TOKEN *Token,
|
|
IN UINTN Size
|
|
)
|
|
{
|
|
SCSI_DISK_DEV *ScsiDiskDevice;
|
|
EFI_BLOCK_IO_MEDIA *Media;
|
|
EFI_STATUS Status;
|
|
UINTN BlockSize;
|
|
UINTN NumberOfBlocks;
|
|
BOOLEAN MediaChange;
|
|
EFI_TPL OldTpl;
|
|
|
|
MediaChange = FALSE;
|
|
OldTpl = gBS->RaiseTPL (TPL_CALLBACK);
|
|
ScsiDiskDevice = SCSI_DISK_DEV_FROM_ERASEBLK (This);
|
|
|
|
if (!IS_DEVICE_FIXED(ScsiDiskDevice)) {
|
|
Status = ScsiDiskDetectMedia (ScsiDiskDevice, FALSE, &MediaChange);
|
|
if (EFI_ERROR (Status)) {
|
|
Status = EFI_DEVICE_ERROR;
|
|
goto Done;
|
|
}
|
|
|
|
if (MediaChange) {
|
|
gBS->ReinstallProtocolInterface (
|
|
ScsiDiskDevice->Handle,
|
|
&gEfiBlockIoProtocolGuid,
|
|
&ScsiDiskDevice->BlkIo,
|
|
&ScsiDiskDevice->BlkIo
|
|
);
|
|
gBS->ReinstallProtocolInterface (
|
|
ScsiDiskDevice->Handle,
|
|
&gEfiBlockIo2ProtocolGuid,
|
|
&ScsiDiskDevice->BlkIo2,
|
|
&ScsiDiskDevice->BlkIo2
|
|
);
|
|
if (DetermineInstallEraseBlock(ScsiDiskDevice, ScsiDiskDevice->Handle)) {
|
|
gBS->ReinstallProtocolInterface (
|
|
ScsiDiskDevice->Handle,
|
|
&gEfiEraseBlockProtocolGuid,
|
|
&ScsiDiskDevice->EraseBlock,
|
|
&ScsiDiskDevice->EraseBlock
|
|
);
|
|
}
|
|
Status = EFI_MEDIA_CHANGED;
|
|
goto Done;
|
|
}
|
|
}
|
|
//
|
|
// Get the intrinsic block size
|
|
//
|
|
Media = ScsiDiskDevice->BlkIo.Media;
|
|
|
|
if (!(Media->MediaPresent)) {
|
|
Status = EFI_NO_MEDIA;
|
|
goto Done;
|
|
}
|
|
|
|
if (MediaId != Media->MediaId) {
|
|
Status = EFI_MEDIA_CHANGED;
|
|
goto Done;
|
|
}
|
|
|
|
if (Media->ReadOnly) {
|
|
Status = EFI_WRITE_PROTECTED;
|
|
goto Done;
|
|
}
|
|
|
|
if (Size == 0) {
|
|
if ((Token != NULL) && (Token->Event != NULL)) {
|
|
Token->TransactionStatus = EFI_SUCCESS;
|
|
gBS->SignalEvent (Token->Event);
|
|
}
|
|
Status = EFI_SUCCESS;
|
|
goto Done;
|
|
}
|
|
|
|
BlockSize = Media->BlockSize;
|
|
if ((Size % BlockSize) != 0) {
|
|
Status = EFI_INVALID_PARAMETER;
|
|
goto Done;
|
|
}
|
|
|
|
NumberOfBlocks = Size / BlockSize;
|
|
if ((Lba + NumberOfBlocks - 1) > Media->LastBlock) {
|
|
Status = EFI_INVALID_PARAMETER;
|
|
goto Done;
|
|
}
|
|
|
|
if ((Token != NULL) && (Token->Event != NULL)) {
|
|
Status = ScsiDiskUnmap (ScsiDiskDevice, Lba, NumberOfBlocks, Token);
|
|
} else {
|
|
Status = ScsiDiskUnmap (ScsiDiskDevice, Lba, NumberOfBlocks, NULL);
|
|
}
|
|
|
|
Done:
|
|
gBS->RestoreTPL (OldTpl);
|
|
return Status;
|
|
}
|
|
|
|
|
|
/**
|
|
Detect Device and read out capacity ,if error occurs, parse the sense key.
|
|
|
|
@param ScsiDiskDevice The pointer of SCSI_DISK_DEV
|
|
@param MustReadCapacity The flag about reading device capacity
|
|
@param MediaChange The pointer of flag indicates if media has changed
|
|
|
|
@retval EFI_DEVICE_ERROR Indicates that error occurs
|
|
@retval EFI_SUCCESS Successfully to detect media
|
|
|
|
**/
|
|
EFI_STATUS
|
|
ScsiDiskDetectMedia (
|
|
IN SCSI_DISK_DEV *ScsiDiskDevice,
|
|
IN BOOLEAN MustReadCapacity,
|
|
OUT BOOLEAN *MediaChange
|
|
)
|
|
{
|
|
EFI_STATUS Status;
|
|
EFI_SCSI_SENSE_DATA *SenseData;
|
|
UINTN NumberOfSenseKeys;
|
|
BOOLEAN NeedRetry;
|
|
BOOLEAN NeedReadCapacity;
|
|
UINT8 Retry;
|
|
UINT8 MaxRetry;
|
|
EFI_BLOCK_IO_MEDIA OldMedia;
|
|
UINTN Action;
|
|
EFI_EVENT TimeoutEvt;
|
|
|
|
Status = EFI_SUCCESS;
|
|
SenseData = NULL;
|
|
NumberOfSenseKeys = 0;
|
|
Retry = 0;
|
|
MaxRetry = 3;
|
|
Action = ACTION_NO_ACTION;
|
|
NeedReadCapacity = FALSE;
|
|
*MediaChange = FALSE;
|
|
TimeoutEvt = NULL;
|
|
|
|
CopyMem (&OldMedia, ScsiDiskDevice->BlkIo.Media, sizeof (OldMedia));
|
|
|
|
Status = gBS->CreateEvent (
|
|
EVT_TIMER,
|
|
TPL_CALLBACK,
|
|
NULL,
|
|
NULL,
|
|
&TimeoutEvt
|
|
);
|
|
if (EFI_ERROR (Status)) {
|
|
return Status;
|
|
}
|
|
|
|
Status = gBS->SetTimer (TimeoutEvt, TimerRelative, EFI_TIMER_PERIOD_SECONDS(120));
|
|
if (EFI_ERROR (Status)) {
|
|
goto EXIT;
|
|
}
|
|
|
|
//
|
|
// Sending Test_Unit cmd to poll device status.
|
|
// If the sense data shows the drive is not ready or reset before, we need poll the device status again.
|
|
// We limit the upper boundary to 120 seconds.
|
|
//
|
|
while (EFI_ERROR (gBS->CheckEvent (TimeoutEvt))) {
|
|
Status = ScsiDiskTestUnitReady (
|
|
ScsiDiskDevice,
|
|
&NeedRetry,
|
|
&SenseData,
|
|
&NumberOfSenseKeys
|
|
);
|
|
if (!EFI_ERROR (Status)) {
|
|
Status = DetectMediaParsingSenseKeys (
|
|
ScsiDiskDevice,
|
|
SenseData,
|
|
NumberOfSenseKeys,
|
|
&Action
|
|
);
|
|
if (EFI_ERROR (Status)) {
|
|
goto EXIT;
|
|
} else if (Action == ACTION_RETRY_COMMAND_LATER) {
|
|
continue;
|
|
} else {
|
|
break;
|
|
}
|
|
} else {
|
|
Retry++;
|
|
if (!NeedRetry || (Retry >= MaxRetry)) {
|
|
goto EXIT;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (EFI_ERROR (Status)) {
|
|
goto EXIT;
|
|
}
|
|
|
|
//
|
|
// ACTION_NO_ACTION: need not read capacity
|
|
// other action code: need read capacity
|
|
//
|
|
if (Action == ACTION_READ_CAPACITY) {
|
|
NeedReadCapacity = TRUE;
|
|
}
|
|
|
|
//
|
|
// either NeedReadCapacity is TRUE, or MustReadCapacity is TRUE,
|
|
// retrieve capacity via Read Capacity command
|
|
//
|
|
if (NeedReadCapacity || MustReadCapacity) {
|
|
//
|
|
// retrieve media information
|
|
//
|
|
for (Retry = 0; Retry < MaxRetry; Retry++) {
|
|
Status = ScsiDiskReadCapacity (
|
|
ScsiDiskDevice,
|
|
&NeedRetry,
|
|
&SenseData,
|
|
&NumberOfSenseKeys
|
|
);
|
|
if (!EFI_ERROR (Status)) {
|
|
//
|
|
// analyze sense key to action
|
|
//
|
|
Status = DetectMediaParsingSenseKeys (
|
|
ScsiDiskDevice,
|
|
SenseData,
|
|
NumberOfSenseKeys,
|
|
&Action
|
|
);
|
|
if (EFI_ERROR (Status)) {
|
|
//
|
|
// if Status is error, it may indicate crisis error,
|
|
// so return without retry.
|
|
//
|
|
goto EXIT;
|
|
} else if (Action == ACTION_RETRY_COMMAND_LATER) {
|
|
Retry = 0;
|
|
continue;
|
|
} else {
|
|
break;
|
|
}
|
|
} else {
|
|
Retry++;
|
|
if (!NeedRetry || (Retry >= MaxRetry)) {
|
|
goto EXIT;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (EFI_ERROR (Status)) {
|
|
goto EXIT;
|
|
}
|
|
}
|
|
|
|
if (ScsiDiskDevice->BlkIo.Media->MediaId != OldMedia.MediaId) {
|
|
//
|
|
// Media change information got from the device
|
|
//
|
|
*MediaChange = TRUE;
|
|
}
|
|
|
|
if (ScsiDiskDevice->BlkIo.Media->ReadOnly != OldMedia.ReadOnly) {
|
|
*MediaChange = TRUE;
|
|
ScsiDiskDevice->BlkIo.Media->MediaId += 1;
|
|
}
|
|
|
|
if (ScsiDiskDevice->BlkIo.Media->BlockSize != OldMedia.BlockSize) {
|
|
*MediaChange = TRUE;
|
|
ScsiDiskDevice->BlkIo.Media->MediaId += 1;
|
|
}
|
|
|
|
if (ScsiDiskDevice->BlkIo.Media->LastBlock != OldMedia.LastBlock) {
|
|
*MediaChange = TRUE;
|
|
ScsiDiskDevice->BlkIo.Media->MediaId += 1;
|
|
}
|
|
|
|
if (ScsiDiskDevice->BlkIo.Media->MediaPresent != OldMedia.MediaPresent) {
|
|
if (ScsiDiskDevice->BlkIo.Media->MediaPresent) {
|
|
//
|
|
// when change from no media to media present, reset the MediaId to 1.
|
|
//
|
|
ScsiDiskDevice->BlkIo.Media->MediaId = 1;
|
|
} else {
|
|
//
|
|
// when no media, reset the MediaId to zero.
|
|
//
|
|
ScsiDiskDevice->BlkIo.Media->MediaId = 0;
|
|
}
|
|
|
|
*MediaChange = TRUE;
|
|
}
|
|
|
|
EXIT:
|
|
if (TimeoutEvt != NULL) {
|
|
gBS->CloseEvent (TimeoutEvt);
|
|
}
|
|
return Status;
|
|
}
|
|
|
|
|
|
/**
|
|
Send out Inquiry command to Device.
|
|
|
|
@param ScsiDiskDevice The pointer of SCSI_DISK_DEV
|
|
@param NeedRetry Indicates if needs try again when error happens
|
|
|
|
@retval EFI_DEVICE_ERROR Indicates that error occurs
|
|
@retval EFI_SUCCESS Successfully to detect media
|
|
|
|
**/
|
|
EFI_STATUS
|
|
ScsiDiskInquiryDevice (
|
|
IN OUT SCSI_DISK_DEV *ScsiDiskDevice,
|
|
OUT BOOLEAN *NeedRetry
|
|
)
|
|
{
|
|
UINT32 InquiryDataLength;
|
|
UINT8 SenseDataLength;
|
|
UINT8 HostAdapterStatus;
|
|
UINT8 TargetStatus;
|
|
EFI_SCSI_SENSE_DATA *SenseDataArray;
|
|
UINTN NumberOfSenseKeys;
|
|
EFI_STATUS Status;
|
|
UINT8 MaxRetry;
|
|
UINT8 Index;
|
|
EFI_SCSI_SUPPORTED_VPD_PAGES_VPD_PAGE *SupportedVpdPages;
|
|
EFI_SCSI_BLOCK_LIMITS_VPD_PAGE *BlockLimits;
|
|
UINTN PageLength;
|
|
|
|
InquiryDataLength = sizeof (EFI_SCSI_INQUIRY_DATA);
|
|
SenseDataLength = 0;
|
|
|
|
Status = ScsiInquiryCommand (
|
|
ScsiDiskDevice->ScsiIo,
|
|
SCSI_DISK_TIMEOUT,
|
|
NULL,
|
|
&SenseDataLength,
|
|
&HostAdapterStatus,
|
|
&TargetStatus,
|
|
(VOID *) &(ScsiDiskDevice->InquiryData),
|
|
&InquiryDataLength,
|
|
FALSE
|
|
);
|
|
//
|
|
// no need to check HostAdapterStatus and TargetStatus
|
|
//
|
|
if ((Status == EFI_SUCCESS) || (Status == EFI_WARN_BUFFER_TOO_SMALL)) {
|
|
ParseInquiryData (ScsiDiskDevice);
|
|
|
|
if (ScsiDiskDevice->DeviceType == EFI_SCSI_TYPE_DISK) {
|
|
//
|
|
// Check whether the device supports Block Limits VPD page (0xB0)
|
|
//
|
|
SupportedVpdPages = AllocateAlignedBuffer (ScsiDiskDevice, sizeof (EFI_SCSI_SUPPORTED_VPD_PAGES_VPD_PAGE));
|
|
if (SupportedVpdPages == NULL) {
|
|
*NeedRetry = FALSE;
|
|
return EFI_DEVICE_ERROR;
|
|
}
|
|
ZeroMem (SupportedVpdPages, sizeof (EFI_SCSI_SUPPORTED_VPD_PAGES_VPD_PAGE));
|
|
InquiryDataLength = sizeof (EFI_SCSI_SUPPORTED_VPD_PAGES_VPD_PAGE);
|
|
SenseDataLength = 0;
|
|
Status = ScsiInquiryCommandEx (
|
|
ScsiDiskDevice->ScsiIo,
|
|
SCSI_DISK_TIMEOUT,
|
|
NULL,
|
|
&SenseDataLength,
|
|
&HostAdapterStatus,
|
|
&TargetStatus,
|
|
(VOID *) SupportedVpdPages,
|
|
&InquiryDataLength,
|
|
TRUE,
|
|
EFI_SCSI_PAGE_CODE_SUPPORTED_VPD
|
|
);
|
|
if (!EFI_ERROR (Status)) {
|
|
PageLength = (SupportedVpdPages->PageLength2 << 8)
|
|
| SupportedVpdPages->PageLength1;
|
|
|
|
//
|
|
// Sanity checks for coping with broken devices
|
|
//
|
|
if (PageLength > sizeof SupportedVpdPages->SupportedVpdPageList) {
|
|
DEBUG ((EFI_D_WARN,
|
|
"%a: invalid PageLength (%u) in Supported VPD Pages page\n",
|
|
__FUNCTION__, (UINT32)PageLength));
|
|
PageLength = 0;
|
|
}
|
|
|
|
if ((PageLength > 0) &&
|
|
(SupportedVpdPages->SupportedVpdPageList[0] !=
|
|
EFI_SCSI_PAGE_CODE_SUPPORTED_VPD)) {
|
|
DEBUG ((EFI_D_WARN,
|
|
"%a: Supported VPD Pages page doesn't start with code 0x%02x\n",
|
|
__FUNCTION__, EFI_SCSI_PAGE_CODE_SUPPORTED_VPD));
|
|
PageLength = 0;
|
|
}
|
|
|
|
//
|
|
// Locate the code for the Block Limits VPD page
|
|
//
|
|
for (Index = 0; Index < PageLength; Index++) {
|
|
//
|
|
// Sanity check
|
|
//
|
|
if ((Index > 0) &&
|
|
(SupportedVpdPages->SupportedVpdPageList[Index] <=
|
|
SupportedVpdPages->SupportedVpdPageList[Index - 1])) {
|
|
DEBUG ((EFI_D_WARN,
|
|
"%a: non-ascending code in Supported VPD Pages page @ %u\n",
|
|
__FUNCTION__, Index));
|
|
Index = 0;
|
|
PageLength = 0;
|
|
break;
|
|
}
|
|
|
|
if (SupportedVpdPages->SupportedVpdPageList[Index] == EFI_SCSI_PAGE_CODE_BLOCK_LIMITS_VPD) {
|
|
break;
|
|
}
|
|
}
|
|
|
|
//
|
|
// Query the Block Limits VPD page
|
|
//
|
|
if (Index < PageLength) {
|
|
BlockLimits = AllocateAlignedBuffer (ScsiDiskDevice, sizeof (EFI_SCSI_BLOCK_LIMITS_VPD_PAGE));
|
|
if (BlockLimits == NULL) {
|
|
FreeAlignedBuffer (SupportedVpdPages, sizeof (EFI_SCSI_SUPPORTED_VPD_PAGES_VPD_PAGE));
|
|
*NeedRetry = FALSE;
|
|
return EFI_DEVICE_ERROR;
|
|
}
|
|
ZeroMem (BlockLimits, sizeof (EFI_SCSI_BLOCK_LIMITS_VPD_PAGE));
|
|
InquiryDataLength = sizeof (EFI_SCSI_BLOCK_LIMITS_VPD_PAGE);
|
|
SenseDataLength = 0;
|
|
Status = ScsiInquiryCommandEx (
|
|
ScsiDiskDevice->ScsiIo,
|
|
SCSI_DISK_TIMEOUT,
|
|
NULL,
|
|
&SenseDataLength,
|
|
&HostAdapterStatus,
|
|
&TargetStatus,
|
|
(VOID *) BlockLimits,
|
|
&InquiryDataLength,
|
|
TRUE,
|
|
EFI_SCSI_PAGE_CODE_BLOCK_LIMITS_VPD
|
|
);
|
|
if (!EFI_ERROR (Status)) {
|
|
ScsiDiskDevice->BlkIo.Media->OptimalTransferLengthGranularity =
|
|
(BlockLimits->OptimalTransferLengthGranularity2 << 8) |
|
|
BlockLimits->OptimalTransferLengthGranularity1;
|
|
|
|
ScsiDiskDevice->UnmapInfo.MaxLbaCnt =
|
|
(BlockLimits->MaximumUnmapLbaCount4 << 24) |
|
|
(BlockLimits->MaximumUnmapLbaCount3 << 16) |
|
|
(BlockLimits->MaximumUnmapLbaCount2 << 8) |
|
|
BlockLimits->MaximumUnmapLbaCount1;
|
|
ScsiDiskDevice->UnmapInfo.MaxBlkDespCnt =
|
|
(BlockLimits->MaximumUnmapBlockDescriptorCount4 << 24) |
|
|
(BlockLimits->MaximumUnmapBlockDescriptorCount3 << 16) |
|
|
(BlockLimits->MaximumUnmapBlockDescriptorCount2 << 8) |
|
|
BlockLimits->MaximumUnmapBlockDescriptorCount1;
|
|
ScsiDiskDevice->EraseBlock.EraseLengthGranularity =
|
|
(BlockLimits->OptimalUnmapGranularity4 << 24) |
|
|
(BlockLimits->OptimalUnmapGranularity3 << 16) |
|
|
(BlockLimits->OptimalUnmapGranularity2 << 8) |
|
|
BlockLimits->OptimalUnmapGranularity1;
|
|
if (BlockLimits->UnmapGranularityAlignmentValid != 0) {
|
|
ScsiDiskDevice->UnmapInfo.GranularityAlignment =
|
|
(BlockLimits->UnmapGranularityAlignment4 << 24) |
|
|
(BlockLimits->UnmapGranularityAlignment3 << 16) |
|
|
(BlockLimits->UnmapGranularityAlignment2 << 8) |
|
|
BlockLimits->UnmapGranularityAlignment1;
|
|
}
|
|
|
|
if (ScsiDiskDevice->EraseBlock.EraseLengthGranularity == 0) {
|
|
//
|
|
// A value of 0 indicates that the optimal unmap granularity is
|
|
// not reported.
|
|
//
|
|
ScsiDiskDevice->EraseBlock.EraseLengthGranularity = 1;
|
|
}
|
|
|
|
ScsiDiskDevice->BlockLimitsVpdSupported = TRUE;
|
|
}
|
|
|
|
FreeAlignedBuffer (BlockLimits, sizeof (EFI_SCSI_BLOCK_LIMITS_VPD_PAGE));
|
|
}
|
|
}
|
|
|
|
FreeAlignedBuffer (SupportedVpdPages, sizeof (EFI_SCSI_SUPPORTED_VPD_PAGES_VPD_PAGE));
|
|
}
|
|
}
|
|
|
|
if (!EFI_ERROR (Status)) {
|
|
return EFI_SUCCESS;
|
|
|
|
} else if (Status == EFI_NOT_READY) {
|
|
*NeedRetry = TRUE;
|
|
return EFI_DEVICE_ERROR;
|
|
|
|
} else if ((Status == EFI_INVALID_PARAMETER) || (Status == EFI_UNSUPPORTED)) {
|
|
*NeedRetry = FALSE;
|
|
return EFI_DEVICE_ERROR;
|
|
}
|
|
//
|
|
// go ahead to check HostAdapterStatus and TargetStatus
|
|
// (EFI_TIMEOUT, EFI_DEVICE_ERROR)
|
|
//
|
|
|
|
Status = CheckHostAdapterStatus (HostAdapterStatus);
|
|
if ((Status == EFI_TIMEOUT) || (Status == EFI_NOT_READY)) {
|
|
*NeedRetry = TRUE;
|
|
return EFI_DEVICE_ERROR;
|
|
} else if (Status == EFI_DEVICE_ERROR) {
|
|
//
|
|
// reset the scsi channel
|
|
//
|
|
ScsiDiskDevice->ScsiIo->ResetBus (ScsiDiskDevice->ScsiIo);
|
|
*NeedRetry = FALSE;
|
|
return EFI_DEVICE_ERROR;
|
|
}
|
|
|
|
Status = CheckTargetStatus (TargetStatus);
|
|
if (Status == EFI_NOT_READY) {
|
|
//
|
|
// reset the scsi device
|
|
//
|
|
ScsiDiskDevice->ScsiIo->ResetDevice (ScsiDiskDevice->ScsiIo);
|
|
*NeedRetry = TRUE;
|
|
return EFI_DEVICE_ERROR;
|
|
|
|
} else if (Status == EFI_DEVICE_ERROR) {
|
|
*NeedRetry = FALSE;
|
|
return EFI_DEVICE_ERROR;
|
|
}
|
|
|
|
//
|
|
// if goes here, meant ScsiInquiryCommand() failed.
|
|
// if ScsiDiskRequestSenseKeys() succeeds at last,
|
|
// better retry ScsiInquiryCommand(). (by setting *NeedRetry = TRUE)
|
|
//
|
|
MaxRetry = 3;
|
|
for (Index = 0; Index < MaxRetry; Index++) {
|
|
Status = ScsiDiskRequestSenseKeys (
|
|
ScsiDiskDevice,
|
|
NeedRetry,
|
|
&SenseDataArray,
|
|
&NumberOfSenseKeys,
|
|
TRUE
|
|
);
|
|
if (!EFI_ERROR (Status)) {
|
|
*NeedRetry = TRUE;
|
|
return EFI_DEVICE_ERROR;
|
|
}
|
|
|
|
if (!*NeedRetry) {
|
|
return EFI_DEVICE_ERROR;
|
|
}
|
|
}
|
|
//
|
|
// ScsiDiskRequestSenseKeys() failed after several rounds of retry.
|
|
// set *NeedRetry = FALSE to avoid the outside caller try again.
|
|
//
|
|
*NeedRetry = FALSE;
|
|
return EFI_DEVICE_ERROR;
|
|
}
|
|
|
|
/**
|
|
To test device.
|
|
|
|
When Test Unit Ready command succeeds, retrieve Sense Keys via Request Sense;
|
|
When Test Unit Ready command encounters any error caused by host adapter or
|
|
target, return error without retrieving Sense Keys.
|
|
|
|
@param ScsiDiskDevice The pointer of SCSI_DISK_DEV
|
|
@param NeedRetry The pointer of flag indicates try again
|
|
@param SenseDataArray The pointer of an array of sense data
|
|
@param NumberOfSenseKeys The pointer of the number of sense data array
|
|
|
|
@retval EFI_DEVICE_ERROR Indicates that error occurs
|
|
@retval EFI_SUCCESS Successfully to test unit
|
|
|
|
**/
|
|
EFI_STATUS
|
|
ScsiDiskTestUnitReady (
|
|
IN SCSI_DISK_DEV *ScsiDiskDevice,
|
|
OUT BOOLEAN *NeedRetry,
|
|
OUT EFI_SCSI_SENSE_DATA **SenseDataArray,
|
|
OUT UINTN *NumberOfSenseKeys
|
|
)
|
|
{
|
|
EFI_STATUS Status;
|
|
UINT8 SenseDataLength;
|
|
UINT8 HostAdapterStatus;
|
|
UINT8 TargetStatus;
|
|
UINT8 Index;
|
|
UINT8 MaxRetry;
|
|
|
|
SenseDataLength = (UINT8) (ScsiDiskDevice->SenseDataNumber * sizeof (EFI_SCSI_SENSE_DATA));
|
|
*NumberOfSenseKeys = 0;
|
|
|
|
//
|
|
// Parameter 3 and 4: do not require sense data, retrieve it when needed.
|
|
//
|
|
Status = ScsiTestUnitReadyCommand (
|
|
ScsiDiskDevice->ScsiIo,
|
|
SCSI_DISK_TIMEOUT,
|
|
ScsiDiskDevice->SenseData,
|
|
&SenseDataLength,
|
|
&HostAdapterStatus,
|
|
&TargetStatus
|
|
);
|
|
//
|
|
// no need to check HostAdapterStatus and TargetStatus
|
|
//
|
|
if (Status == EFI_NOT_READY) {
|
|
*NeedRetry = TRUE;
|
|
return EFI_DEVICE_ERROR;
|
|
|
|
} else if ((Status == EFI_INVALID_PARAMETER) || (Status == EFI_UNSUPPORTED)) {
|
|
*NeedRetry = FALSE;
|
|
return EFI_DEVICE_ERROR;
|
|
}
|
|
//
|
|
// go ahead to check HostAdapterStatus and TargetStatus(in case of EFI_DEVICE_ERROR)
|
|
//
|
|
|
|
Status = CheckHostAdapterStatus (HostAdapterStatus);
|
|
if ((Status == EFI_TIMEOUT) || (Status == EFI_NOT_READY)) {
|
|
*NeedRetry = TRUE;
|
|
return EFI_DEVICE_ERROR;
|
|
|
|
} else if (Status == EFI_DEVICE_ERROR) {
|
|
//
|
|
// reset the scsi channel
|
|
//
|
|
ScsiDiskDevice->ScsiIo->ResetBus (ScsiDiskDevice->ScsiIo);
|
|
*NeedRetry = FALSE;
|
|
return EFI_DEVICE_ERROR;
|
|
}
|
|
|
|
Status = CheckTargetStatus (TargetStatus);
|
|
if (Status == EFI_NOT_READY) {
|
|
//
|
|
// reset the scsi device
|
|
//
|
|
ScsiDiskDevice->ScsiIo->ResetDevice (ScsiDiskDevice->ScsiIo);
|
|
*NeedRetry = TRUE;
|
|
return EFI_DEVICE_ERROR;
|
|
|
|
} else if (Status == EFI_DEVICE_ERROR) {
|
|
*NeedRetry = FALSE;
|
|
return EFI_DEVICE_ERROR;
|
|
}
|
|
|
|
if (SenseDataLength != 0) {
|
|
*NumberOfSenseKeys = SenseDataLength / sizeof (EFI_SCSI_SENSE_DATA);
|
|
*SenseDataArray = ScsiDiskDevice->SenseData;
|
|
return EFI_SUCCESS;
|
|
}
|
|
|
|
MaxRetry = 3;
|
|
for (Index = 0; Index < MaxRetry; Index++) {
|
|
Status = ScsiDiskRequestSenseKeys (
|
|
ScsiDiskDevice,
|
|
NeedRetry,
|
|
SenseDataArray,
|
|
NumberOfSenseKeys,
|
|
FALSE
|
|
);
|
|
if (!EFI_ERROR (Status)) {
|
|
return EFI_SUCCESS;
|
|
}
|
|
|
|
if (!*NeedRetry) {
|
|
return EFI_DEVICE_ERROR;
|
|
}
|
|
}
|
|
//
|
|
// ScsiDiskRequestSenseKeys() failed after several rounds of retry.
|
|
// set *NeedRetry = FALSE to avoid the outside caller try again.
|
|
//
|
|
*NeedRetry = FALSE;
|
|
return EFI_DEVICE_ERROR;
|
|
}
|
|
|
|
/**
|
|
Parsing Sense Keys which got from request sense command.
|
|
|
|
@param ScsiDiskDevice The pointer of SCSI_DISK_DEV
|
|
@param SenseData The pointer of EFI_SCSI_SENSE_DATA
|
|
@param NumberOfSenseKeys The number of sense key
|
|
@param Action The pointer of action which indicates what is need to do next
|
|
|
|
@retval EFI_DEVICE_ERROR Indicates that error occurs
|
|
@retval EFI_SUCCESS Successfully to complete the parsing
|
|
|
|
**/
|
|
EFI_STATUS
|
|
DetectMediaParsingSenseKeys (
|
|
OUT SCSI_DISK_DEV *ScsiDiskDevice,
|
|
IN EFI_SCSI_SENSE_DATA *SenseData,
|
|
IN UINTN NumberOfSenseKeys,
|
|
OUT UINTN *Action
|
|
)
|
|
{
|
|
BOOLEAN RetryLater;
|
|
|
|
//
|
|
// Default is to read capacity, unless..
|
|
//
|
|
*Action = ACTION_READ_CAPACITY;
|
|
|
|
if (NumberOfSenseKeys == 0) {
|
|
if (ScsiDiskDevice->BlkIo.Media->MediaPresent == TRUE) {
|
|
*Action = ACTION_NO_ACTION;
|
|
}
|
|
return EFI_SUCCESS;
|
|
}
|
|
|
|
if (!ScsiDiskHaveSenseKey (SenseData, NumberOfSenseKeys)) {
|
|
//
|
|
// No Sense Key returned from last submitted command
|
|
//
|
|
if (ScsiDiskDevice->BlkIo.Media->MediaPresent == TRUE) {
|
|
*Action = ACTION_NO_ACTION;
|
|
}
|
|
return EFI_SUCCESS;
|
|
}
|
|
|
|
if (ScsiDiskIsNoMedia (SenseData, NumberOfSenseKeys)) {
|
|
ScsiDiskDevice->BlkIo.Media->MediaPresent = FALSE;
|
|
ScsiDiskDevice->BlkIo.Media->LastBlock = 0;
|
|
*Action = ACTION_NO_ACTION;
|
|
DEBUG ((EFI_D_VERBOSE, "ScsiDisk: ScsiDiskIsNoMedia\n"));
|
|
return EFI_SUCCESS;
|
|
}
|
|
|
|
if (ScsiDiskIsMediaChange (SenseData, NumberOfSenseKeys)) {
|
|
ScsiDiskDevice->BlkIo.Media->MediaId++;
|
|
DEBUG ((EFI_D_VERBOSE, "ScsiDisk: ScsiDiskIsMediaChange!\n"));
|
|
return EFI_SUCCESS;
|
|
}
|
|
|
|
if (ScsiDiskIsResetBefore (SenseData, NumberOfSenseKeys)) {
|
|
*Action = ACTION_RETRY_COMMAND_LATER;
|
|
DEBUG ((EFI_D_VERBOSE, "ScsiDisk: ScsiDiskIsResetBefore!\n"));
|
|
return EFI_SUCCESS;
|
|
}
|
|
|
|
if (ScsiDiskIsMediaError (SenseData, NumberOfSenseKeys)) {
|
|
DEBUG ((EFI_D_VERBOSE, "ScsiDisk: ScsiDiskIsMediaError\n"));
|
|
*Action = ACTION_RETRY_WITH_BACKOFF_ALGO;
|
|
return EFI_DEVICE_ERROR;
|
|
}
|
|
|
|
if (ScsiDiskIsHardwareError (SenseData, NumberOfSenseKeys)) {
|
|
DEBUG ((EFI_D_VERBOSE, "ScsiDisk: ScsiDiskIsHardwareError\n"));
|
|
*Action = ACTION_RETRY_WITH_BACKOFF_ALGO;
|
|
return EFI_DEVICE_ERROR;
|
|
}
|
|
|
|
if (!ScsiDiskIsDriveReady (SenseData, NumberOfSenseKeys, &RetryLater)) {
|
|
if (RetryLater) {
|
|
*Action = ACTION_RETRY_COMMAND_LATER;
|
|
DEBUG ((EFI_D_VERBOSE, "ScsiDisk: ScsiDiskDriveNotReady!\n"));
|
|
return EFI_SUCCESS;
|
|
}
|
|
*Action = ACTION_NO_ACTION;
|
|
return EFI_DEVICE_ERROR;
|
|
}
|
|
|
|
*Action = ACTION_RETRY_WITH_BACKOFF_ALGO;
|
|
DEBUG ((EFI_D_VERBOSE, "ScsiDisk: Sense Key = 0x%x ASC = 0x%x!\n", SenseData->Sense_Key, SenseData->Addnl_Sense_Code));
|
|
return EFI_SUCCESS;
|
|
}
|
|
|
|
|
|
/**
|
|
Send read capacity command to device and get the device parameter.
|
|
|
|
@param ScsiDiskDevice The pointer of SCSI_DISK_DEV
|
|
@param NeedRetry The pointer of flag indicates if need a retry
|
|
@param SenseDataArray The pointer of an array of sense data
|
|
@param NumberOfSenseKeys The number of sense key
|
|
|
|
@retval EFI_DEVICE_ERROR Indicates that error occurs
|
|
@retval EFI_SUCCESS Successfully to read capacity or sense data is received.
|
|
|
|
**/
|
|
EFI_STATUS
|
|
ScsiDiskReadCapacity (
|
|
IN OUT SCSI_DISK_DEV *ScsiDiskDevice,
|
|
OUT BOOLEAN *NeedRetry,
|
|
OUT EFI_SCSI_SENSE_DATA **SenseDataArray,
|
|
OUT UINTN *NumberOfSenseKeys
|
|
)
|
|
{
|
|
UINT8 HostAdapterStatus;
|
|
UINT8 TargetStatus;
|
|
EFI_STATUS CommandStatus;
|
|
EFI_STATUS Status;
|
|
UINT8 Index;
|
|
UINT8 MaxRetry;
|
|
UINT8 SenseDataLength;
|
|
UINT32 DataLength10;
|
|
UINT32 DataLength16;
|
|
EFI_SCSI_DISK_CAPACITY_DATA *CapacityData10;
|
|
EFI_SCSI_DISK_CAPACITY_DATA16 *CapacityData16;
|
|
|
|
CapacityData10 = AllocateAlignedBuffer (ScsiDiskDevice, sizeof (EFI_SCSI_DISK_CAPACITY_DATA));
|
|
if (CapacityData10 == NULL) {
|
|
*NeedRetry = FALSE;
|
|
return EFI_DEVICE_ERROR;
|
|
}
|
|
CapacityData16 = AllocateAlignedBuffer (ScsiDiskDevice, sizeof (EFI_SCSI_DISK_CAPACITY_DATA16));
|
|
if (CapacityData16 == NULL) {
|
|
FreeAlignedBuffer (CapacityData10, sizeof (EFI_SCSI_DISK_CAPACITY_DATA));
|
|
*NeedRetry = FALSE;
|
|
return EFI_DEVICE_ERROR;
|
|
}
|
|
|
|
SenseDataLength = 0;
|
|
DataLength10 = sizeof (EFI_SCSI_DISK_CAPACITY_DATA);
|
|
DataLength16 = sizeof (EFI_SCSI_DISK_CAPACITY_DATA16);
|
|
ZeroMem (CapacityData10, sizeof (EFI_SCSI_DISK_CAPACITY_DATA));
|
|
ZeroMem (CapacityData16, sizeof (EFI_SCSI_DISK_CAPACITY_DATA16));
|
|
|
|
*NumberOfSenseKeys = 0;
|
|
*NeedRetry = FALSE;
|
|
|
|
//
|
|
// submit Read Capacity(10) Command. If it returns capacity of FFFFFFFFh,
|
|
// 16 byte command should be used to access large hard disk >2TB
|
|
//
|
|
CommandStatus = ScsiReadCapacityCommand (
|
|
ScsiDiskDevice->ScsiIo,
|
|
SCSI_DISK_TIMEOUT,
|
|
NULL,
|
|
&SenseDataLength,
|
|
&HostAdapterStatus,
|
|
&TargetStatus,
|
|
(VOID *) CapacityData10,
|
|
&DataLength10,
|
|
FALSE
|
|
);
|
|
|
|
ScsiDiskDevice->Cdb16Byte = FALSE;
|
|
if ((!EFI_ERROR (CommandStatus)) && (CapacityData10->LastLba3 == 0xff) && (CapacityData10->LastLba2 == 0xff) &&
|
|
(CapacityData10->LastLba1 == 0xff) && (CapacityData10->LastLba0 == 0xff)) {
|
|
//
|
|
// use Read Capacity (16), Read (16) and Write (16) next when hard disk size > 2TB
|
|
//
|
|
ScsiDiskDevice->Cdb16Byte = TRUE;
|
|
//
|
|
// submit Read Capacity(16) Command to get parameter LogicalBlocksPerPhysicalBlock
|
|
// and LowestAlignedLba
|
|
//
|
|
CommandStatus = ScsiReadCapacity16Command (
|
|
ScsiDiskDevice->ScsiIo,
|
|
SCSI_DISK_TIMEOUT,
|
|
NULL,
|
|
&SenseDataLength,
|
|
&HostAdapterStatus,
|
|
&TargetStatus,
|
|
(VOID *) CapacityData16,
|
|
&DataLength16,
|
|
FALSE
|
|
);
|
|
}
|
|
|
|
//
|
|
// no need to check HostAdapterStatus and TargetStatus
|
|
//
|
|
if (CommandStatus == EFI_SUCCESS) {
|
|
GetMediaInfo (ScsiDiskDevice, CapacityData10, CapacityData16);
|
|
FreeAlignedBuffer (CapacityData10, sizeof (EFI_SCSI_DISK_CAPACITY_DATA));
|
|
FreeAlignedBuffer (CapacityData16, sizeof (EFI_SCSI_DISK_CAPACITY_DATA16));
|
|
return EFI_SUCCESS;
|
|
}
|
|
|
|
FreeAlignedBuffer (CapacityData10, sizeof (EFI_SCSI_DISK_CAPACITY_DATA));
|
|
FreeAlignedBuffer (CapacityData16, sizeof (EFI_SCSI_DISK_CAPACITY_DATA16));
|
|
|
|
if (CommandStatus == EFI_NOT_READY) {
|
|
*NeedRetry = TRUE;
|
|
return EFI_DEVICE_ERROR;
|
|
} else if ((CommandStatus == EFI_INVALID_PARAMETER) || (CommandStatus == EFI_UNSUPPORTED)) {
|
|
*NeedRetry = FALSE;
|
|
return EFI_DEVICE_ERROR;
|
|
}
|
|
|
|
//
|
|
// go ahead to check HostAdapterStatus and TargetStatus
|
|
// (EFI_TIMEOUT, EFI_DEVICE_ERROR, EFI_WARN_BUFFER_TOO_SMALL)
|
|
//
|
|
|
|
Status = CheckHostAdapterStatus (HostAdapterStatus);
|
|
if ((Status == EFI_TIMEOUT) || (Status == EFI_NOT_READY)) {
|
|
*NeedRetry = TRUE;
|
|
return EFI_DEVICE_ERROR;
|
|
|
|
} else if (Status == EFI_DEVICE_ERROR) {
|
|
//
|
|
// reset the scsi channel
|
|
//
|
|
ScsiDiskDevice->ScsiIo->ResetBus (ScsiDiskDevice->ScsiIo);
|
|
*NeedRetry = FALSE;
|
|
return EFI_DEVICE_ERROR;
|
|
}
|
|
|
|
Status = CheckTargetStatus (TargetStatus);
|
|
if (Status == EFI_NOT_READY) {
|
|
//
|
|
// reset the scsi device
|
|
//
|
|
ScsiDiskDevice->ScsiIo->ResetDevice (ScsiDiskDevice->ScsiIo);
|
|
*NeedRetry = TRUE;
|
|
return EFI_DEVICE_ERROR;
|
|
|
|
} else if (Status == EFI_DEVICE_ERROR) {
|
|
*NeedRetry = FALSE;
|
|
return EFI_DEVICE_ERROR;
|
|
}
|
|
|
|
//
|
|
// if goes here, meant ScsiReadCapacityCommand() failed.
|
|
// if ScsiDiskRequestSenseKeys() succeeds at last,
|
|
// better retry ScsiReadCapacityCommand(). (by setting *NeedRetry = TRUE)
|
|
//
|
|
MaxRetry = 3;
|
|
for (Index = 0; Index < MaxRetry; Index++) {
|
|
|
|
Status = ScsiDiskRequestSenseKeys (
|
|
ScsiDiskDevice,
|
|
NeedRetry,
|
|
SenseDataArray,
|
|
NumberOfSenseKeys,
|
|
TRUE
|
|
);
|
|
if (!EFI_ERROR (Status)) {
|
|
return EFI_SUCCESS;
|
|
}
|
|
|
|
if (!*NeedRetry) {
|
|
return EFI_DEVICE_ERROR;
|
|
}
|
|
}
|
|
//
|
|
// ScsiDiskRequestSenseKeys() failed after several rounds of retry.
|
|
// set *NeedRetry = FALSE to avoid the outside caller try again.
|
|
//
|
|
*NeedRetry = FALSE;
|
|
return EFI_DEVICE_ERROR;
|
|
}
|
|
|
|
/**
|
|
Check the HostAdapter status and re-interpret it in EFI_STATUS.
|
|
|
|
@param HostAdapterStatus Host Adapter status
|
|
|
|
@retval EFI_SUCCESS Host adapter is OK.
|
|
@retval EFI_TIMEOUT Timeout.
|
|
@retval EFI_NOT_READY Adapter NOT ready.
|
|
@retval EFI_DEVICE_ERROR Adapter device error.
|
|
|
|
**/
|
|
EFI_STATUS
|
|
CheckHostAdapterStatus (
|
|
IN UINT8 HostAdapterStatus
|
|
)
|
|
{
|
|
switch (HostAdapterStatus) {
|
|
case EFI_EXT_SCSI_STATUS_HOST_ADAPTER_OK:
|
|
return EFI_SUCCESS;
|
|
|
|
case EFI_EXT_SCSI_STATUS_HOST_ADAPTER_SELECTION_TIMEOUT:
|
|
case EFI_EXT_SCSI_STATUS_HOST_ADAPTER_TIMEOUT:
|
|
case EFI_EXT_SCSI_STATUS_HOST_ADAPTER_TIMEOUT_COMMAND:
|
|
return EFI_TIMEOUT;
|
|
|
|
case EFI_EXT_SCSI_STATUS_HOST_ADAPTER_MESSAGE_REJECT:
|
|
case EFI_EXT_SCSI_STATUS_HOST_ADAPTER_PARITY_ERROR:
|
|
case EFI_EXT_SCSI_STATUS_HOST_ADAPTER_REQUEST_SENSE_FAILED:
|
|
case EFI_EXT_SCSI_STATUS_HOST_ADAPTER_DATA_OVERRUN_UNDERRUN:
|
|
case EFI_EXT_SCSI_STATUS_HOST_ADAPTER_BUS_RESET:
|
|
return EFI_NOT_READY;
|
|
|
|
case EFI_EXT_SCSI_STATUS_HOST_ADAPTER_BUS_FREE:
|
|
case EFI_EXT_SCSI_STATUS_HOST_ADAPTER_PHASE_ERROR:
|
|
return EFI_DEVICE_ERROR;
|
|
|
|
default:
|
|
return EFI_SUCCESS;
|
|
}
|
|
}
|
|
|
|
|
|
/**
|
|
Check the target status and re-interpret it in EFI_STATUS.
|
|
|
|
@param TargetStatus Target status
|
|
|
|
@retval EFI_NOT_READY Device is NOT ready.
|
|
@retval EFI_DEVICE_ERROR
|
|
@retval EFI_SUCCESS
|
|
|
|
**/
|
|
EFI_STATUS
|
|
CheckTargetStatus (
|
|
IN UINT8 TargetStatus
|
|
)
|
|
{
|
|
switch (TargetStatus) {
|
|
case EFI_EXT_SCSI_STATUS_TARGET_GOOD:
|
|
case EFI_EXT_SCSI_STATUS_TARGET_CHECK_CONDITION:
|
|
case EFI_EXT_SCSI_STATUS_TARGET_CONDITION_MET:
|
|
return EFI_SUCCESS;
|
|
|
|
case EFI_EXT_SCSI_STATUS_TARGET_INTERMEDIATE:
|
|
case EFI_EXT_SCSI_STATUS_TARGET_INTERMEDIATE_CONDITION_MET:
|
|
case EFI_EXT_SCSI_STATUS_TARGET_BUSY:
|
|
case EFI_EXT_SCSI_STATUS_TARGET_TASK_SET_FULL:
|
|
return EFI_NOT_READY;
|
|
|
|
case EFI_EXT_SCSI_STATUS_TARGET_RESERVATION_CONFLICT:
|
|
return EFI_DEVICE_ERROR;
|
|
|
|
default:
|
|
return EFI_SUCCESS;
|
|
}
|
|
}
|
|
|
|
|
|
/**
|
|
Retrieve all sense keys from the device.
|
|
|
|
When encountering error during the process, if retrieve sense keys before
|
|
error encountered, it returns the sense keys with return status set to EFI_SUCCESS,
|
|
and NeedRetry set to FALSE; otherwize, return the proper return status.
|
|
|
|
@param ScsiDiskDevice The pointer of SCSI_DISK_DEV
|
|
@param NeedRetry The pointer of flag indicates if need a retry
|
|
@param SenseDataArray The pointer of an array of sense data
|
|
@param NumberOfSenseKeys The number of sense key
|
|
@param AskResetIfError The flag indicates if need reset when error occurs
|
|
|
|
@retval EFI_DEVICE_ERROR Indicates that error occurs
|
|
@retval EFI_SUCCESS Successfully to request sense key
|
|
|
|
**/
|
|
EFI_STATUS
|
|
ScsiDiskRequestSenseKeys (
|
|
IN OUT SCSI_DISK_DEV *ScsiDiskDevice,
|
|
OUT BOOLEAN *NeedRetry,
|
|
OUT EFI_SCSI_SENSE_DATA **SenseDataArray,
|
|
OUT UINTN *NumberOfSenseKeys,
|
|
IN BOOLEAN AskResetIfError
|
|
)
|
|
{
|
|
EFI_SCSI_SENSE_DATA *PtrSenseData;
|
|
UINT8 SenseDataLength;
|
|
BOOLEAN SenseReq;
|
|
EFI_STATUS Status;
|
|
EFI_STATUS FallStatus;
|
|
UINT8 HostAdapterStatus;
|
|
UINT8 TargetStatus;
|
|
|
|
FallStatus = EFI_SUCCESS;
|
|
SenseDataLength = (UINT8) sizeof (EFI_SCSI_SENSE_DATA);
|
|
|
|
ZeroMem (
|
|
ScsiDiskDevice->SenseData,
|
|
sizeof (EFI_SCSI_SENSE_DATA) * (ScsiDiskDevice->SenseDataNumber)
|
|
);
|
|
|
|
*NumberOfSenseKeys = 0;
|
|
*SenseDataArray = ScsiDiskDevice->SenseData;
|
|
Status = EFI_SUCCESS;
|
|
PtrSenseData = AllocateAlignedBuffer (ScsiDiskDevice, sizeof (EFI_SCSI_SENSE_DATA));
|
|
if (PtrSenseData == NULL) {
|
|
return EFI_DEVICE_ERROR;
|
|
}
|
|
|
|
for (SenseReq = TRUE; SenseReq;) {
|
|
ZeroMem (PtrSenseData, sizeof (EFI_SCSI_SENSE_DATA));
|
|
Status = ScsiRequestSenseCommand (
|
|
ScsiDiskDevice->ScsiIo,
|
|
SCSI_DISK_TIMEOUT,
|
|
PtrSenseData,
|
|
&SenseDataLength,
|
|
&HostAdapterStatus,
|
|
&TargetStatus
|
|
);
|
|
if ((Status == EFI_SUCCESS) || (Status == EFI_WARN_BUFFER_TOO_SMALL)) {
|
|
FallStatus = EFI_SUCCESS;
|
|
|
|
} else if ((Status == EFI_TIMEOUT) || (Status == EFI_NOT_READY)) {
|
|
*NeedRetry = TRUE;
|
|
FallStatus = EFI_DEVICE_ERROR;
|
|
|
|
} else if ((Status == EFI_INVALID_PARAMETER) || (Status == EFI_UNSUPPORTED)) {
|
|
*NeedRetry = FALSE;
|
|
FallStatus = EFI_DEVICE_ERROR;
|
|
|
|
} else if (Status == EFI_DEVICE_ERROR) {
|
|
if (AskResetIfError) {
|
|
ScsiDiskDevice->ScsiIo->ResetDevice (ScsiDiskDevice->ScsiIo);
|
|
}
|
|
|
|
FallStatus = EFI_DEVICE_ERROR;
|
|
}
|
|
|
|
if (EFI_ERROR (FallStatus)) {
|
|
if (*NumberOfSenseKeys != 0) {
|
|
*NeedRetry = FALSE;
|
|
Status = EFI_SUCCESS;
|
|
goto EXIT;
|
|
} else {
|
|
Status = EFI_DEVICE_ERROR;
|
|
goto EXIT;
|
|
}
|
|
}
|
|
|
|
CopyMem (ScsiDiskDevice->SenseData + *NumberOfSenseKeys, PtrSenseData, SenseDataLength);
|
|
(*NumberOfSenseKeys) += 1;
|
|
|
|
//
|
|
// no more sense key or number of sense keys exceeds predefined,
|
|
// skip the loop.
|
|
//
|
|
if ((PtrSenseData->Sense_Key == EFI_SCSI_SK_NO_SENSE) ||
|
|
(*NumberOfSenseKeys == ScsiDiskDevice->SenseDataNumber)) {
|
|
SenseReq = FALSE;
|
|
}
|
|
}
|
|
|
|
EXIT:
|
|
FreeAlignedBuffer (PtrSenseData, sizeof (EFI_SCSI_SENSE_DATA));
|
|
return Status;
|
|
}
|
|
|
|
|
|
/**
|
|
Get information from media read capacity command.
|
|
|
|
@param ScsiDiskDevice The pointer of SCSI_DISK_DEV
|
|
@param Capacity10 The pointer of EFI_SCSI_DISK_CAPACITY_DATA
|
|
@param Capacity16 The pointer of EFI_SCSI_DISK_CAPACITY_DATA16
|
|
|
|
**/
|
|
VOID
|
|
GetMediaInfo (
|
|
IN OUT SCSI_DISK_DEV *ScsiDiskDevice,
|
|
IN EFI_SCSI_DISK_CAPACITY_DATA *Capacity10,
|
|
IN EFI_SCSI_DISK_CAPACITY_DATA16 *Capacity16
|
|
)
|
|
{
|
|
UINT8 *Ptr;
|
|
|
|
if (!ScsiDiskDevice->Cdb16Byte) {
|
|
ScsiDiskDevice->BlkIo.Media->LastBlock = ((UINT32) Capacity10->LastLba3 << 24) |
|
|
(Capacity10->LastLba2 << 16) |
|
|
(Capacity10->LastLba1 << 8) |
|
|
Capacity10->LastLba0;
|
|
|
|
ScsiDiskDevice->BlkIo.Media->BlockSize = (Capacity10->BlockSize3 << 24) |
|
|
(Capacity10->BlockSize2 << 16) |
|
|
(Capacity10->BlockSize1 << 8) |
|
|
Capacity10->BlockSize0;
|
|
ScsiDiskDevice->BlkIo.Media->LowestAlignedLba = 0;
|
|
ScsiDiskDevice->BlkIo.Media->LogicalBlocksPerPhysicalBlock = 0;
|
|
if (!ScsiDiskDevice->BlockLimitsVpdSupported) {
|
|
ScsiDiskDevice->UnmapInfo.MaxLbaCnt = (UINT32) ScsiDiskDevice->BlkIo.Media->LastBlock;
|
|
}
|
|
} else {
|
|
Ptr = (UINT8*)&ScsiDiskDevice->BlkIo.Media->LastBlock;
|
|
*Ptr++ = Capacity16->LastLba0;
|
|
*Ptr++ = Capacity16->LastLba1;
|
|
*Ptr++ = Capacity16->LastLba2;
|
|
*Ptr++ = Capacity16->LastLba3;
|
|
*Ptr++ = Capacity16->LastLba4;
|
|
*Ptr++ = Capacity16->LastLba5;
|
|
*Ptr++ = Capacity16->LastLba6;
|
|
*Ptr = Capacity16->LastLba7;
|
|
|
|
ScsiDiskDevice->BlkIo.Media->BlockSize = (Capacity16->BlockSize3 << 24) |
|
|
(Capacity16->BlockSize2 << 16) |
|
|
(Capacity16->BlockSize1 << 8) |
|
|
Capacity16->BlockSize0;
|
|
|
|
ScsiDiskDevice->BlkIo.Media->LowestAlignedLba = (Capacity16->LowestAlignLogic2 << 8) |
|
|
Capacity16->LowestAlignLogic1;
|
|
ScsiDiskDevice->BlkIo.Media->LogicalBlocksPerPhysicalBlock = (1 << Capacity16->LogicPerPhysical);
|
|
if (!ScsiDiskDevice->BlockLimitsVpdSupported) {
|
|
if (ScsiDiskDevice->BlkIo.Media->LastBlock > (UINT32) -1) {
|
|
ScsiDiskDevice->UnmapInfo.MaxLbaCnt = (UINT32) -1;
|
|
} else {
|
|
ScsiDiskDevice->UnmapInfo.MaxLbaCnt = (UINT32) ScsiDiskDevice->BlkIo.Media->LastBlock;
|
|
}
|
|
}
|
|
}
|
|
|
|
ScsiDiskDevice->BlkIo.Media->MediaPresent = TRUE;
|
|
}
|
|
|
|
/**
|
|
Parse Inquiry data.
|
|
|
|
@param ScsiDiskDevice The pointer of SCSI_DISK_DEV
|
|
|
|
**/
|
|
VOID
|
|
ParseInquiryData (
|
|
IN OUT SCSI_DISK_DEV *ScsiDiskDevice
|
|
)
|
|
{
|
|
ScsiDiskDevice->FixedDevice = (BOOLEAN) ((ScsiDiskDevice->InquiryData.Rmb == 1) ? 0 : 1);
|
|
ScsiDiskDevice->BlkIoMedia.RemovableMedia = (BOOLEAN) (!ScsiDiskDevice->FixedDevice);
|
|
}
|
|
|
|
/**
|
|
Read sector from SCSI Disk.
|
|
|
|
@param ScsiDiskDevice The pointer of SCSI_DISK_DEV
|
|
@param Buffer The buffer to fill in the read out data
|
|
@param Lba Logic block address
|
|
@param NumberOfBlocks The number of blocks to read
|
|
|
|
@retval EFI_DEVICE_ERROR Indicates a device error.
|
|
@retval EFI_SUCCESS Operation is successful.
|
|
|
|
**/
|
|
EFI_STATUS
|
|
ScsiDiskReadSectors (
|
|
IN SCSI_DISK_DEV *ScsiDiskDevice,
|
|
OUT VOID *Buffer,
|
|
IN EFI_LBA Lba,
|
|
IN UINTN NumberOfBlocks
|
|
)
|
|
{
|
|
UINTN BlocksRemaining;
|
|
UINT8 *PtrBuffer;
|
|
UINT32 BlockSize;
|
|
UINT32 ByteCount;
|
|
UINT32 MaxBlock;
|
|
UINT32 SectorCount;
|
|
UINT32 NextSectorCount;
|
|
UINT64 Timeout;
|
|
EFI_STATUS Status;
|
|
UINT8 Index;
|
|
UINT8 MaxRetry;
|
|
BOOLEAN NeedRetry;
|
|
|
|
Status = EFI_SUCCESS;
|
|
|
|
BlocksRemaining = NumberOfBlocks;
|
|
BlockSize = ScsiDiskDevice->BlkIo.Media->BlockSize;
|
|
|
|
//
|
|
// limit the data bytes that can be transferred by one Read(10) or Read(16) Command
|
|
//
|
|
if (!ScsiDiskDevice->Cdb16Byte) {
|
|
MaxBlock = 0xFFFF;
|
|
} else {
|
|
MaxBlock = 0xFFFFFFFF;
|
|
}
|
|
|
|
PtrBuffer = Buffer;
|
|
|
|
while (BlocksRemaining > 0) {
|
|
|
|
if (BlocksRemaining <= MaxBlock) {
|
|
if (!ScsiDiskDevice->Cdb16Byte) {
|
|
SectorCount = (UINT16) BlocksRemaining;
|
|
} else {
|
|
SectorCount = (UINT32) BlocksRemaining;
|
|
}
|
|
} else {
|
|
SectorCount = MaxBlock;
|
|
}
|
|
|
|
ByteCount = SectorCount * BlockSize;
|
|
//
|
|
// |------------------------|-----------------|------------------|-----------------|
|
|
// | ATA Transfer Mode | Transfer Rate | SCSI Interface | Transfer Rate |
|
|
// |------------------------|-----------------|------------------|-----------------|
|
|
// | PIO Mode 0 | 3.3Mbytes/sec | SCSI-1 | 5Mbytes/sec |
|
|
// |------------------------|-----------------|------------------|-----------------|
|
|
// | PIO Mode 1 | 5.2Mbytes/sec | Fast SCSI | 10Mbytes/sec |
|
|
// |------------------------|-----------------|------------------|-----------------|
|
|
// | PIO Mode 2 | 8.3Mbytes/sec | Fast-Wide SCSI | 20Mbytes/sec |
|
|
// |------------------------|-----------------|------------------|-----------------|
|
|
// | PIO Mode 3 | 11.1Mbytes/sec | Ultra SCSI | 20Mbytes/sec |
|
|
// |------------------------|-----------------|------------------|-----------------|
|
|
// | PIO Mode 4 | 16.6Mbytes/sec | Ultra Wide SCSI | 40Mbytes/sec |
|
|
// |------------------------|-----------------|------------------|-----------------|
|
|
// | Single-word DMA Mode 0 | 2.1Mbytes/sec | Ultra2 SCSI | 40Mbytes/sec |
|
|
// |------------------------|-----------------|------------------|-----------------|
|
|
// | Single-word DMA Mode 1 | 4.2Mbytes/sec | Ultra2 Wide SCSI | 80Mbytes/sec |
|
|
// |------------------------|-----------------|------------------|-----------------|
|
|
// | Single-word DMA Mode 2 | 8.4Mbytes/sec | Ultra3 SCSI | 160Mbytes/sec |
|
|
// |------------------------|-----------------|------------------|-----------------|
|
|
// | Multi-word DMA Mode 0 | 4.2Mbytes/sec | Ultra-320 SCSI | 320Mbytes/sec |
|
|
// |------------------------|-----------------|------------------|-----------------|
|
|
// | Multi-word DMA Mode 1 | 13.3Mbytes/sec | Ultra-640 SCSI | 640Mbytes/sec |
|
|
// |------------------------|-----------------|------------------|-----------------|
|
|
//
|
|
// As ScsiDisk and ScsiBus driver are used to manage SCSI or ATAPI devices, we have to use
|
|
// the lowest transfer rate to calculate the possible maximum timeout value for each operation.
|
|
// From the above table, we could know 2.1Mbytes per second is lowest one.
|
|
// The timout value is rounded up to nearest integar and here an additional 30s is added
|
|
// to follow ATA spec in which it mentioned that the device may take up to 30s to respond
|
|
// commands in the Standby/Idle mode.
|
|
//
|
|
Timeout = EFI_TIMER_PERIOD_SECONDS (ByteCount / 2100000 + 31);
|
|
|
|
MaxRetry = 2;
|
|
for (Index = 0; Index < MaxRetry; Index++) {
|
|
if (!ScsiDiskDevice->Cdb16Byte) {
|
|
Status = ScsiDiskRead10 (
|
|
ScsiDiskDevice,
|
|
&NeedRetry,
|
|
Timeout,
|
|
PtrBuffer,
|
|
&ByteCount,
|
|
(UINT32) Lba,
|
|
SectorCount
|
|
);
|
|
} else {
|
|
Status = ScsiDiskRead16 (
|
|
ScsiDiskDevice,
|
|
&NeedRetry,
|
|
Timeout,
|
|
PtrBuffer,
|
|
&ByteCount,
|
|
Lba,
|
|
SectorCount
|
|
);
|
|
}
|
|
if (!EFI_ERROR (Status)) {
|
|
break;
|
|
}
|
|
|
|
if (!NeedRetry) {
|
|
return EFI_DEVICE_ERROR;
|
|
}
|
|
|
|
//
|
|
// We need to retry. However, if ScsiDiskRead10() or ScsiDiskRead16() has
|
|
// lowered ByteCount on output, we must make sure that we lower
|
|
// SectorCount accordingly. SectorCount will be encoded in the CDB, and
|
|
// it is invalid to request more sectors in the CDB than the entire
|
|
// transfer (ie. ByteCount) can carry.
|
|
//
|
|
// In addition, ByteCount is only expected to go down, or stay unchaged.
|
|
// Therefore we don't need to update Timeout: the original timeout should
|
|
// accommodate shorter transfers too.
|
|
//
|
|
NextSectorCount = ByteCount / BlockSize;
|
|
if (NextSectorCount < SectorCount) {
|
|
SectorCount = NextSectorCount;
|
|
//
|
|
// Account for any rounding down.
|
|
//
|
|
ByteCount = SectorCount * BlockSize;
|
|
}
|
|
}
|
|
|
|
if ((Index == MaxRetry) && (Status != EFI_SUCCESS)) {
|
|
return EFI_DEVICE_ERROR;
|
|
}
|
|
|
|
//
|
|
// actual transferred sectors
|
|
//
|
|
SectorCount = ByteCount / BlockSize;
|
|
|
|
Lba += SectorCount;
|
|
PtrBuffer = PtrBuffer + SectorCount * BlockSize;
|
|
BlocksRemaining -= SectorCount;
|
|
}
|
|
|
|
return EFI_SUCCESS;
|
|
}
|
|
|
|
/**
|
|
Write sector to SCSI Disk.
|
|
|
|
@param ScsiDiskDevice The pointer of SCSI_DISK_DEV
|
|
@param Buffer The buffer of data to be written into SCSI Disk
|
|
@param Lba Logic block address
|
|
@param NumberOfBlocks The number of blocks to read
|
|
|
|
@retval EFI_DEVICE_ERROR Indicates a device error.
|
|
@retval EFI_SUCCESS Operation is successful.
|
|
|
|
**/
|
|
EFI_STATUS
|
|
ScsiDiskWriteSectors (
|
|
IN SCSI_DISK_DEV *ScsiDiskDevice,
|
|
IN VOID *Buffer,
|
|
IN EFI_LBA Lba,
|
|
IN UINTN NumberOfBlocks
|
|
)
|
|
{
|
|
UINTN BlocksRemaining;
|
|
UINT8 *PtrBuffer;
|
|
UINT32 BlockSize;
|
|
UINT32 ByteCount;
|
|
UINT32 MaxBlock;
|
|
UINT32 SectorCount;
|
|
UINT32 NextSectorCount;
|
|
UINT64 Timeout;
|
|
EFI_STATUS Status;
|
|
UINT8 Index;
|
|
UINT8 MaxRetry;
|
|
BOOLEAN NeedRetry;
|
|
|
|
Status = EFI_SUCCESS;
|
|
|
|
BlocksRemaining = NumberOfBlocks;
|
|
BlockSize = ScsiDiskDevice->BlkIo.Media->BlockSize;
|
|
|
|
//
|
|
// limit the data bytes that can be transferred by one Read(10) or Read(16) Command
|
|
//
|
|
if (!ScsiDiskDevice->Cdb16Byte) {
|
|
MaxBlock = 0xFFFF;
|
|
} else {
|
|
MaxBlock = 0xFFFFFFFF;
|
|
}
|
|
|
|
PtrBuffer = Buffer;
|
|
|
|
while (BlocksRemaining > 0) {
|
|
|
|
if (BlocksRemaining <= MaxBlock) {
|
|
if (!ScsiDiskDevice->Cdb16Byte) {
|
|
SectorCount = (UINT16) BlocksRemaining;
|
|
} else {
|
|
SectorCount = (UINT32) BlocksRemaining;
|
|
}
|
|
} else {
|
|
SectorCount = MaxBlock;
|
|
}
|
|
|
|
ByteCount = SectorCount * BlockSize;
|
|
//
|
|
// |------------------------|-----------------|------------------|-----------------|
|
|
// | ATA Transfer Mode | Transfer Rate | SCSI Interface | Transfer Rate |
|
|
// |------------------------|-----------------|------------------|-----------------|
|
|
// | PIO Mode 0 | 3.3Mbytes/sec | SCSI-1 | 5Mbytes/sec |
|
|
// |------------------------|-----------------|------------------|-----------------|
|
|
// | PIO Mode 1 | 5.2Mbytes/sec | Fast SCSI | 10Mbytes/sec |
|
|
// |------------------------|-----------------|------------------|-----------------|
|
|
// | PIO Mode 2 | 8.3Mbytes/sec | Fast-Wide SCSI | 20Mbytes/sec |
|
|
// |------------------------|-----------------|------------------|-----------------|
|
|
// | PIO Mode 3 | 11.1Mbytes/sec | Ultra SCSI | 20Mbytes/sec |
|
|
// |------------------------|-----------------|------------------|-----------------|
|
|
// | PIO Mode 4 | 16.6Mbytes/sec | Ultra Wide SCSI | 40Mbytes/sec |
|
|
// |------------------------|-----------------|------------------|-----------------|
|
|
// | Single-word DMA Mode 0 | 2.1Mbytes/sec | Ultra2 SCSI | 40Mbytes/sec |
|
|
// |------------------------|-----------------|------------------|-----------------|
|
|
// | Single-word DMA Mode 1 | 4.2Mbytes/sec | Ultra2 Wide SCSI | 80Mbytes/sec |
|
|
// |------------------------|-----------------|------------------|-----------------|
|
|
// | Single-word DMA Mode 2 | 8.4Mbytes/sec | Ultra3 SCSI | 160Mbytes/sec |
|
|
// |------------------------|-----------------|------------------|-----------------|
|
|
// | Multi-word DMA Mode 0 | 4.2Mbytes/sec | Ultra-320 SCSI | 320Mbytes/sec |
|
|
// |------------------------|-----------------|------------------|-----------------|
|
|
// | Multi-word DMA Mode 1 | 13.3Mbytes/sec | Ultra-640 SCSI | 640Mbytes/sec |
|
|
// |------------------------|-----------------|------------------|-----------------|
|
|
//
|
|
// As ScsiDisk and ScsiBus driver are used to manage SCSI or ATAPI devices, we have to use
|
|
// the lowest transfer rate to calculate the possible maximum timeout value for each operation.
|
|
// From the above table, we could know 2.1Mbytes per second is lowest one.
|
|
// The timout value is rounded up to nearest integar and here an additional 30s is added
|
|
// to follow ATA spec in which it mentioned that the device may take up to 30s to respond
|
|
// commands in the Standby/Idle mode.
|
|
//
|
|
Timeout = EFI_TIMER_PERIOD_SECONDS (ByteCount / 2100000 + 31);
|
|
MaxRetry = 2;
|
|
for (Index = 0; Index < MaxRetry; Index++) {
|
|
if (!ScsiDiskDevice->Cdb16Byte) {
|
|
Status = ScsiDiskWrite10 (
|
|
ScsiDiskDevice,
|
|
&NeedRetry,
|
|
Timeout,
|
|
PtrBuffer,
|
|
&ByteCount,
|
|
(UINT32) Lba,
|
|
SectorCount
|
|
);
|
|
} else {
|
|
Status = ScsiDiskWrite16 (
|
|
ScsiDiskDevice,
|
|
&NeedRetry,
|
|
Timeout,
|
|
PtrBuffer,
|
|
&ByteCount,
|
|
Lba,
|
|
SectorCount
|
|
);
|
|
}
|
|
if (!EFI_ERROR (Status)) {
|
|
break;
|
|
}
|
|
|
|
if (!NeedRetry) {
|
|
return EFI_DEVICE_ERROR;
|
|
}
|
|
|
|
//
|
|
// We need to retry. However, if ScsiDiskWrite10() or ScsiDiskWrite16()
|
|
// has lowered ByteCount on output, we must make sure that we lower
|
|
// SectorCount accordingly. SectorCount will be encoded in the CDB, and
|
|
// it is invalid to request more sectors in the CDB than the entire
|
|
// transfer (ie. ByteCount) can carry.
|
|
//
|
|
// In addition, ByteCount is only expected to go down, or stay unchaged.
|
|
// Therefore we don't need to update Timeout: the original timeout should
|
|
// accommodate shorter transfers too.
|
|
//
|
|
NextSectorCount = ByteCount / BlockSize;
|
|
if (NextSectorCount < SectorCount) {
|
|
SectorCount = NextSectorCount;
|
|
//
|
|
// Account for any rounding down.
|
|
//
|
|
ByteCount = SectorCount * BlockSize;
|
|
}
|
|
}
|
|
|
|
if ((Index == MaxRetry) && (Status != EFI_SUCCESS)) {
|
|
return EFI_DEVICE_ERROR;
|
|
}
|
|
//
|
|
// actual transferred sectors
|
|
//
|
|
SectorCount = ByteCount / BlockSize;
|
|
|
|
Lba += SectorCount;
|
|
PtrBuffer = PtrBuffer + SectorCount * BlockSize;
|
|
BlocksRemaining -= SectorCount;
|
|
}
|
|
|
|
return EFI_SUCCESS;
|
|
}
|
|
|
|
/**
|
|
Asynchronously read sector from SCSI Disk.
|
|
|
|
@param ScsiDiskDevice The pointer of SCSI_DISK_DEV.
|
|
@param Buffer The buffer to fill in the read out data.
|
|
@param Lba Logic block address.
|
|
@param NumberOfBlocks The number of blocks to read.
|
|
@param Token A pointer to the token associated with the
|
|
non-blocking read request.
|
|
|
|
@retval EFI_INVALID_PARAMETER Token is NULL or Token->Event is NULL.
|
|
@retval EFI_DEVICE_ERROR Indicates a device error.
|
|
@retval EFI_SUCCESS Operation is successful.
|
|
|
|
**/
|
|
EFI_STATUS
|
|
ScsiDiskAsyncReadSectors (
|
|
IN SCSI_DISK_DEV *ScsiDiskDevice,
|
|
OUT VOID *Buffer,
|
|
IN EFI_LBA Lba,
|
|
IN UINTN NumberOfBlocks,
|
|
IN EFI_BLOCK_IO2_TOKEN *Token
|
|
)
|
|
{
|
|
UINTN BlocksRemaining;
|
|
UINT8 *PtrBuffer;
|
|
UINT32 BlockSize;
|
|
UINT32 ByteCount;
|
|
UINT32 MaxBlock;
|
|
UINT32 SectorCount;
|
|
UINT64 Timeout;
|
|
SCSI_BLKIO2_REQUEST *BlkIo2Req;
|
|
EFI_STATUS Status;
|
|
EFI_TPL OldTpl;
|
|
|
|
if ((Token == NULL) || (Token->Event == NULL)) {
|
|
return EFI_INVALID_PARAMETER;
|
|
}
|
|
|
|
BlkIo2Req = AllocateZeroPool (sizeof (SCSI_BLKIO2_REQUEST));
|
|
if (BlkIo2Req == NULL) {
|
|
return EFI_OUT_OF_RESOURCES;
|
|
}
|
|
|
|
BlkIo2Req->Token = Token;
|
|
|
|
OldTpl = gBS->RaiseTPL (TPL_NOTIFY);
|
|
InsertTailList (&ScsiDiskDevice->AsyncTaskQueue, &BlkIo2Req->Link);
|
|
gBS->RestoreTPL (OldTpl);
|
|
|
|
InitializeListHead (&BlkIo2Req->ScsiRWQueue);
|
|
|
|
Status = EFI_SUCCESS;
|
|
|
|
BlocksRemaining = NumberOfBlocks;
|
|
BlockSize = ScsiDiskDevice->BlkIo.Media->BlockSize;
|
|
|
|
//
|
|
// Limit the data bytes that can be transferred by one Read(10) or Read(16)
|
|
// Command
|
|
//
|
|
if (!ScsiDiskDevice->Cdb16Byte) {
|
|
MaxBlock = 0xFFFF;
|
|
} else {
|
|
MaxBlock = 0xFFFFFFFF;
|
|
}
|
|
|
|
PtrBuffer = Buffer;
|
|
|
|
while (BlocksRemaining > 0) {
|
|
|
|
if (BlocksRemaining <= MaxBlock) {
|
|
if (!ScsiDiskDevice->Cdb16Byte) {
|
|
SectorCount = (UINT16) BlocksRemaining;
|
|
} else {
|
|
SectorCount = (UINT32) BlocksRemaining;
|
|
}
|
|
} else {
|
|
SectorCount = MaxBlock;
|
|
}
|
|
|
|
ByteCount = SectorCount * BlockSize;
|
|
//
|
|
// |------------------------|-----------------|------------------|-----------------|
|
|
// | ATA Transfer Mode | Transfer Rate | SCSI Interface | Transfer Rate |
|
|
// |------------------------|-----------------|------------------|-----------------|
|
|
// | PIO Mode 0 | 3.3Mbytes/sec | SCSI-1 | 5Mbytes/sec |
|
|
// |------------------------|-----------------|------------------|-----------------|
|
|
// | PIO Mode 1 | 5.2Mbytes/sec | Fast SCSI | 10Mbytes/sec |
|
|
// |------------------------|-----------------|------------------|-----------------|
|
|
// | PIO Mode 2 | 8.3Mbytes/sec | Fast-Wide SCSI | 20Mbytes/sec |
|
|
// |------------------------|-----------------|------------------|-----------------|
|
|
// | PIO Mode 3 | 11.1Mbytes/sec | Ultra SCSI | 20Mbytes/sec |
|
|
// |------------------------|-----------------|------------------|-----------------|
|
|
// | PIO Mode 4 | 16.6Mbytes/sec | Ultra Wide SCSI | 40Mbytes/sec |
|
|
// |------------------------|-----------------|------------------|-----------------|
|
|
// | Single-word DMA Mode 0 | 2.1Mbytes/sec | Ultra2 SCSI | 40Mbytes/sec |
|
|
// |------------------------|-----------------|------------------|-----------------|
|
|
// | Single-word DMA Mode 1 | 4.2Mbytes/sec | Ultra2 Wide SCSI | 80Mbytes/sec |
|
|
// |------------------------|-----------------|------------------|-----------------|
|
|
// | Single-word DMA Mode 2 | 8.4Mbytes/sec | Ultra3 SCSI | 160Mbytes/sec |
|
|
// |------------------------|-----------------|------------------|-----------------|
|
|
// | Multi-word DMA Mode 0 | 4.2Mbytes/sec | Ultra-320 SCSI | 320Mbytes/sec |
|
|
// |------------------------|-----------------|------------------|-----------------|
|
|
// | Multi-word DMA Mode 1 | 13.3Mbytes/sec | Ultra-640 SCSI | 640Mbytes/sec |
|
|
// |------------------------|-----------------|------------------|-----------------|
|
|
//
|
|
// As ScsiDisk and ScsiBus driver are used to manage SCSI or ATAPI devices,
|
|
// we have to use the lowest transfer rate to calculate the possible
|
|
// maximum timeout value for each operation.
|
|
// From the above table, we could know 2.1Mbytes per second is lowest one.
|
|
// The timout value is rounded up to nearest integar and here an additional
|
|
// 30s is added to follow ATA spec in which it mentioned that the device
|
|
// may take up to 30s to respond commands in the Standby/Idle mode.
|
|
//
|
|
Timeout = EFI_TIMER_PERIOD_SECONDS (ByteCount / 2100000 + 31);
|
|
|
|
if (!ScsiDiskDevice->Cdb16Byte) {
|
|
Status = ScsiDiskAsyncRead10 (
|
|
ScsiDiskDevice,
|
|
Timeout,
|
|
0,
|
|
PtrBuffer,
|
|
ByteCount,
|
|
(UINT32) Lba,
|
|
SectorCount,
|
|
BlkIo2Req,
|
|
Token
|
|
);
|
|
} else {
|
|
Status = ScsiDiskAsyncRead16 (
|
|
ScsiDiskDevice,
|
|
Timeout,
|
|
0,
|
|
PtrBuffer,
|
|
ByteCount,
|
|
Lba,
|
|
SectorCount,
|
|
BlkIo2Req,
|
|
Token
|
|
);
|
|
}
|
|
if (EFI_ERROR (Status)) {
|
|
//
|
|
// Some devices will return EFI_DEVICE_ERROR or EFI_TIMEOUT when the data
|
|
// length of a SCSI I/O command is too large.
|
|
// In this case, we retry sending the SCSI command with a data length
|
|
// half of its previous value.
|
|
//
|
|
if ((Status == EFI_DEVICE_ERROR) || (Status == EFI_TIMEOUT)) {
|
|
if ((MaxBlock > 1) && (SectorCount > 1)) {
|
|
MaxBlock = MIN (MaxBlock, SectorCount) >> 1;
|
|
continue;
|
|
}
|
|
}
|
|
|
|
OldTpl = gBS->RaiseTPL (TPL_NOTIFY);
|
|
if (IsListEmpty (&BlkIo2Req->ScsiRWQueue)) {
|
|
//
|
|
// Free the SCSI_BLKIO2_REQUEST structure only when there is no other
|
|
// SCSI sub-task running. Otherwise, it will be freed in the callback
|
|
// function ScsiDiskNotify().
|
|
//
|
|
RemoveEntryList (&BlkIo2Req->Link);
|
|
FreePool (BlkIo2Req);
|
|
BlkIo2Req = NULL;
|
|
gBS->RestoreTPL (OldTpl);
|
|
|
|
//
|
|
// It is safe to return error status to the caller, since there is no
|
|
// previous SCSI sub-task executing.
|
|
//
|
|
Status = EFI_DEVICE_ERROR;
|
|
goto Done;
|
|
} else {
|
|
gBS->RestoreTPL (OldTpl);
|
|
|
|
//
|
|
// There are previous SCSI commands still running, EFI_SUCCESS should
|
|
// be returned to make sure that the caller does not free resources
|
|
// still using by these SCSI commands.
|
|
//
|
|
Status = EFI_SUCCESS;
|
|
goto Done;
|
|
}
|
|
}
|
|
|
|
//
|
|
// Sectors submitted for transfer
|
|
//
|
|
SectorCount = ByteCount / BlockSize;
|
|
|
|
Lba += SectorCount;
|
|
PtrBuffer = PtrBuffer + SectorCount * BlockSize;
|
|
BlocksRemaining -= SectorCount;
|
|
}
|
|
|
|
Status = EFI_SUCCESS;
|
|
|
|
Done:
|
|
if (BlkIo2Req != NULL) {
|
|
BlkIo2Req->LastScsiRW = TRUE;
|
|
|
|
OldTpl = gBS->RaiseTPL (TPL_NOTIFY);
|
|
if (IsListEmpty (&BlkIo2Req->ScsiRWQueue)) {
|
|
RemoveEntryList (&BlkIo2Req->Link);
|
|
FreePool (BlkIo2Req);
|
|
BlkIo2Req = NULL;
|
|
|
|
gBS->SignalEvent (Token->Event);
|
|
}
|
|
gBS->RestoreTPL (OldTpl);
|
|
}
|
|
|
|
return Status;
|
|
}
|
|
|
|
/**
|
|
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 timout value is rounded up to nearest integar and here an additional
|
|
// 30s is added to follow ATA spec in which it mentioned that the device
|
|
// may take up to 30s to respond commands in the Standby/Idle mode.
|
|
//
|
|
Timeout = EFI_TIMER_PERIOD_SECONDS (ByteCount / 2100000 + 31);
|
|
|
|
if (!ScsiDiskDevice->Cdb16Byte) {
|
|
Status = ScsiDiskAsyncWrite10 (
|
|
ScsiDiskDevice,
|
|
Timeout,
|
|
0,
|
|
PtrBuffer,
|
|
ByteCount,
|
|
(UINT32) Lba,
|
|
SectorCount,
|
|
BlkIo2Req,
|
|
Token
|
|
);
|
|
} else {
|
|
Status = ScsiDiskAsyncWrite16 (
|
|
ScsiDiskDevice,
|
|
Timeout,
|
|
0,
|
|
PtrBuffer,
|
|
ByteCount,
|
|
Lba,
|
|
SectorCount,
|
|
BlkIo2Req,
|
|
Token
|
|
);
|
|
}
|
|
if (EFI_ERROR (Status)) {
|
|
//
|
|
// 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 algorithem to resolve some compatibility issues that
|
|
// some SCSI targets or ATAPI devices couldn't correctly response reading/writing
|
|
// big data in a single operation.
|
|
// This algorithem will at first try to execute original request. If the request fails
|
|
// with media error sense data or else, it will reduce the transfer length to half and
|
|
// try again till the operation succeeds or fails with one sector transfer length.
|
|
//
|
|
BackOff:
|
|
*NeedRetry = FALSE;
|
|
Action = ACTION_NO_ACTION;
|
|
SenseDataLength = (UINT8) (ScsiDiskDevice->SenseDataNumber * sizeof (EFI_SCSI_SENSE_DATA));
|
|
ReturnStatus = ScsiRead10Command (
|
|
ScsiDiskDevice->ScsiIo,
|
|
Timeout,
|
|
ScsiDiskDevice->SenseData,
|
|
&SenseDataLength,
|
|
&HostAdapterStatus,
|
|
&TargetStatus,
|
|
DataBuffer,
|
|
DataLength,
|
|
StartLba,
|
|
SectorCount
|
|
);
|
|
|
|
if (ReturnStatus == EFI_NOT_READY || ReturnStatus == EFI_BAD_BUFFER_SIZE) {
|
|
*NeedRetry = TRUE;
|
|
return EFI_DEVICE_ERROR;
|
|
} else if ((ReturnStatus == EFI_INVALID_PARAMETER) || (ReturnStatus == EFI_UNSUPPORTED)) {
|
|
*NeedRetry = FALSE;
|
|
return ReturnStatus;
|
|
}
|
|
|
|
//
|
|
// go ahead to check HostAdapterStatus and TargetStatus
|
|
// (EFI_TIMEOUT, EFI_DEVICE_ERROR, EFI_WARN_BUFFER_TOO_SMALL)
|
|
//
|
|
Status = CheckHostAdapterStatus (HostAdapterStatus);
|
|
if ((Status == EFI_TIMEOUT) || (Status == EFI_NOT_READY)) {
|
|
*NeedRetry = TRUE;
|
|
return EFI_DEVICE_ERROR;
|
|
} else if (Status == EFI_DEVICE_ERROR) {
|
|
//
|
|
// reset the scsi channel
|
|
//
|
|
ScsiDiskDevice->ScsiIo->ResetBus (ScsiDiskDevice->ScsiIo);
|
|
*NeedRetry = FALSE;
|
|
return EFI_DEVICE_ERROR;
|
|
}
|
|
|
|
Status = CheckTargetStatus (TargetStatus);
|
|
if (Status == EFI_NOT_READY) {
|
|
//
|
|
// reset the scsi device
|
|
//
|
|
ScsiDiskDevice->ScsiIo->ResetDevice (ScsiDiskDevice->ScsiIo);
|
|
*NeedRetry = TRUE;
|
|
return EFI_DEVICE_ERROR;
|
|
} else if (Status == EFI_DEVICE_ERROR) {
|
|
*NeedRetry = FALSE;
|
|
return EFI_DEVICE_ERROR;
|
|
}
|
|
|
|
if ((TargetStatus == EFI_EXT_SCSI_STATUS_TARGET_CHECK_CONDITION) || (EFI_ERROR (ReturnStatus))) {
|
|
DEBUG ((EFI_D_ERROR, "ScsiDiskRead10: Check Condition happened!\n"));
|
|
Status = DetectMediaParsingSenseKeys (ScsiDiskDevice, ScsiDiskDevice->SenseData, SenseDataLength / sizeof (EFI_SCSI_SENSE_DATA), &Action);
|
|
if (Action == ACTION_RETRY_COMMAND_LATER) {
|
|
*NeedRetry = TRUE;
|
|
return EFI_DEVICE_ERROR;
|
|
} else if (Action == ACTION_RETRY_WITH_BACKOFF_ALGO) {
|
|
if (SectorCount <= 1) {
|
|
//
|
|
// Jump out if the operation still fails with one sector transfer length.
|
|
//
|
|
*NeedRetry = FALSE;
|
|
return EFI_DEVICE_ERROR;
|
|
}
|
|
//
|
|
// Try again with half length if the sense data shows we need to retry.
|
|
//
|
|
SectorCount >>= 1;
|
|
*DataLength = SectorCount * ScsiDiskDevice->BlkIo.Media->BlockSize;
|
|
goto BackOff;
|
|
} else {
|
|
*NeedRetry = FALSE;
|
|
return EFI_DEVICE_ERROR;
|
|
}
|
|
}
|
|
|
|
return ReturnStatus;
|
|
}
|
|
|
|
|
|
/**
|
|
Submit Write(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 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 algorithem to resolve some compatibility issues that
|
|
// some SCSI targets or ATAPI devices couldn't correctly response reading/writing
|
|
// big data in a single operation.
|
|
// This algorithem will at first try to execute original request. If the request fails
|
|
// with media error sense data or else, it will reduce the transfer length to half and
|
|
// try again till the operation succeeds or fails with one sector transfer length.
|
|
//
|
|
BackOff:
|
|
*NeedRetry = FALSE;
|
|
Action = ACTION_NO_ACTION;
|
|
SenseDataLength = (UINT8) (ScsiDiskDevice->SenseDataNumber * sizeof (EFI_SCSI_SENSE_DATA));
|
|
ReturnStatus = 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 ((EFI_D_ERROR, "ScsiDiskWrite10: Check Condition happened!\n"));
|
|
Status = DetectMediaParsingSenseKeys (ScsiDiskDevice, ScsiDiskDevice->SenseData, SenseDataLength / sizeof (EFI_SCSI_SENSE_DATA), &Action);
|
|
if (Action == ACTION_RETRY_COMMAND_LATER) {
|
|
*NeedRetry = TRUE;
|
|
return EFI_DEVICE_ERROR;
|
|
} else if (Action == ACTION_RETRY_WITH_BACKOFF_ALGO) {
|
|
if (SectorCount <= 1) {
|
|
//
|
|
// Jump out if the operation still fails with one sector transfer length.
|
|
//
|
|
*NeedRetry = FALSE;
|
|
return EFI_DEVICE_ERROR;
|
|
}
|
|
//
|
|
// Try again with half length if the sense data shows we need to retry.
|
|
//
|
|
SectorCount >>= 1;
|
|
*DataLength = SectorCount * ScsiDiskDevice->BlkIo.Media->BlockSize;
|
|
goto BackOff;
|
|
} else {
|
|
*NeedRetry = FALSE;
|
|
return EFI_DEVICE_ERROR;
|
|
}
|
|
}
|
|
|
|
return ReturnStatus;
|
|
}
|
|
|
|
|
|
/**
|
|
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 algorithem to resolve some compatibility issues that
|
|
// some SCSI targets or ATAPI devices couldn't correctly response reading/writing
|
|
// big data in a single operation.
|
|
// This algorithem will at first try to execute original request. If the request fails
|
|
// with media error sense data or else, it will reduce the transfer length to half and
|
|
// try again till the operation succeeds or fails with one sector transfer length.
|
|
//
|
|
BackOff:
|
|
*NeedRetry = FALSE;
|
|
Action = ACTION_NO_ACTION;
|
|
SenseDataLength = (UINT8) (ScsiDiskDevice->SenseDataNumber * sizeof (EFI_SCSI_SENSE_DATA));
|
|
ReturnStatus = 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;
|
|
}
|
|
|
|
//
|
|
// go ahead to check HostAdapterStatus and TargetStatus
|
|
// (EFI_TIMEOUT, EFI_DEVICE_ERROR, EFI_WARN_BUFFER_TOO_SMALL)
|
|
//
|
|
Status = CheckHostAdapterStatus (HostAdapterStatus);
|
|
if ((Status == EFI_TIMEOUT) || (Status == EFI_NOT_READY)) {
|
|
*NeedRetry = TRUE;
|
|
return EFI_DEVICE_ERROR;
|
|
} else if (Status == EFI_DEVICE_ERROR) {
|
|
//
|
|
// reset the scsi channel
|
|
//
|
|
ScsiDiskDevice->ScsiIo->ResetBus (ScsiDiskDevice->ScsiIo);
|
|
*NeedRetry = FALSE;
|
|
return EFI_DEVICE_ERROR;
|
|
}
|
|
|
|
Status = CheckTargetStatus (TargetStatus);
|
|
if (Status == EFI_NOT_READY) {
|
|
//
|
|
// reset the scsi device
|
|
//
|
|
ScsiDiskDevice->ScsiIo->ResetDevice (ScsiDiskDevice->ScsiIo);
|
|
*NeedRetry = TRUE;
|
|
return EFI_DEVICE_ERROR;
|
|
} else if (Status == EFI_DEVICE_ERROR) {
|
|
*NeedRetry = FALSE;
|
|
return EFI_DEVICE_ERROR;
|
|
}
|
|
|
|
if ((TargetStatus == EFI_EXT_SCSI_STATUS_TARGET_CHECK_CONDITION) || (EFI_ERROR (ReturnStatus))) {
|
|
DEBUG ((EFI_D_ERROR, "ScsiDiskRead16: Check Condition happened!\n"));
|
|
Status = DetectMediaParsingSenseKeys (ScsiDiskDevice, ScsiDiskDevice->SenseData, SenseDataLength / sizeof (EFI_SCSI_SENSE_DATA), &Action);
|
|
if (Action == ACTION_RETRY_COMMAND_LATER) {
|
|
*NeedRetry = TRUE;
|
|
return EFI_DEVICE_ERROR;
|
|
} else if (Action == ACTION_RETRY_WITH_BACKOFF_ALGO) {
|
|
if (SectorCount <= 1) {
|
|
//
|
|
// Jump out if the operation still fails with one sector transfer length.
|
|
//
|
|
*NeedRetry = FALSE;
|
|
return EFI_DEVICE_ERROR;
|
|
}
|
|
//
|
|
// Try again with half length if the sense data shows we need to retry.
|
|
//
|
|
SectorCount >>= 1;
|
|
*DataLength = SectorCount * ScsiDiskDevice->BlkIo.Media->BlockSize;
|
|
goto BackOff;
|
|
} else {
|
|
*NeedRetry = FALSE;
|
|
return EFI_DEVICE_ERROR;
|
|
}
|
|
}
|
|
|
|
return ReturnStatus;
|
|
}
|
|
|
|
|
|
/**
|
|
Submit Write(16) Command.
|
|
|
|
@param ScsiDiskDevice The pointer of ScsiDiskDevice
|
|
@param NeedRetry The pointer of flag indicates if needs retry if error happens
|
|
@param Timeout The time to complete the command
|
|
@param DataBuffer The buffer to fill with the read out data
|
|
@param DataLength The length of buffer
|
|
@param StartLba The start logic block address
|
|
@param SectorCount The number of blocks to write
|
|
|
|
@return EFI_STATUS is returned by calling ScsiWrite16Command().
|
|
|
|
**/
|
|
EFI_STATUS
|
|
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
|
|
)
|
|
{
|
|
EFI_STATUS Status;
|
|
EFI_STATUS ReturnStatus;
|
|
UINT8 SenseDataLength;
|
|
UINT8 HostAdapterStatus;
|
|
UINT8 TargetStatus;
|
|
UINTN Action;
|
|
|
|
//
|
|
// Implement a backoff algorithem to resolve some compatibility issues that
|
|
// some SCSI targets or ATAPI devices couldn't correctly response reading/writing
|
|
// big data in a single operation.
|
|
// This algorithem will at first try to execute original request. If the request fails
|
|
// with media error sense data or else, it will reduce the transfer length to half and
|
|
// try again till the operation succeeds or fails with one sector transfer length.
|
|
//
|
|
BackOff:
|
|
*NeedRetry = FALSE;
|
|
Action = ACTION_NO_ACTION;
|
|
SenseDataLength = (UINT8) (ScsiDiskDevice->SenseDataNumber * sizeof (EFI_SCSI_SENSE_DATA));
|
|
ReturnStatus = 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;
|
|
}
|
|
|
|
//
|
|
// go ahead to check HostAdapterStatus and TargetStatus
|
|
// (EFI_TIMEOUT, EFI_DEVICE_ERROR, EFI_WARN_BUFFER_TOO_SMALL)
|
|
//
|
|
Status = CheckHostAdapterStatus (HostAdapterStatus);
|
|
if ((Status == EFI_TIMEOUT) || (Status == EFI_NOT_READY)) {
|
|
*NeedRetry = TRUE;
|
|
return EFI_DEVICE_ERROR;
|
|
} else if (Status == EFI_DEVICE_ERROR) {
|
|
//
|
|
// reset the scsi channel
|
|
//
|
|
ScsiDiskDevice->ScsiIo->ResetBus (ScsiDiskDevice->ScsiIo);
|
|
*NeedRetry = FALSE;
|
|
return EFI_DEVICE_ERROR;
|
|
}
|
|
|
|
Status = CheckTargetStatus (TargetStatus);
|
|
if (Status == EFI_NOT_READY) {
|
|
//
|
|
// reset the scsi device
|
|
//
|
|
ScsiDiskDevice->ScsiIo->ResetDevice (ScsiDiskDevice->ScsiIo);
|
|
*NeedRetry = TRUE;
|
|
return EFI_DEVICE_ERROR;
|
|
} else if (Status == EFI_DEVICE_ERROR) {
|
|
*NeedRetry = FALSE;
|
|
return EFI_DEVICE_ERROR;
|
|
}
|
|
|
|
if ((TargetStatus == EFI_EXT_SCSI_STATUS_TARGET_CHECK_CONDITION) || (EFI_ERROR (ReturnStatus))) {
|
|
DEBUG ((EFI_D_ERROR, "ScsiDiskWrite16: Check Condition happened!\n"));
|
|
Status = DetectMediaParsingSenseKeys (ScsiDiskDevice, ScsiDiskDevice->SenseData, SenseDataLength / sizeof (EFI_SCSI_SENSE_DATA), &Action);
|
|
if (Action == ACTION_RETRY_COMMAND_LATER) {
|
|
*NeedRetry = TRUE;
|
|
return EFI_DEVICE_ERROR;
|
|
} else if (Action == ACTION_RETRY_WITH_BACKOFF_ALGO) {
|
|
if (SectorCount <= 1) {
|
|
//
|
|
// Jump out if the operation still fails with one sector transfer length.
|
|
//
|
|
*NeedRetry = FALSE;
|
|
return EFI_DEVICE_ERROR;
|
|
}
|
|
//
|
|
// Try again with half length if the sense data shows we need to retry.
|
|
//
|
|
SectorCount >>= 1;
|
|
*DataLength = SectorCount * ScsiDiskDevice->BlkIo.Media->BlockSize;
|
|
goto BackOff;
|
|
} else {
|
|
*NeedRetry = FALSE;
|
|
return EFI_DEVICE_ERROR;
|
|
}
|
|
}
|
|
|
|
return ReturnStatus;
|
|
}
|
|
|
|
|
|
/**
|
|
Internal helper notify function in which determine whether retry of a SCSI
|
|
Read/Write command is needed and signal the event passed from Block I/O(2) if
|
|
the SCSI I/O operation completes.
|
|
|
|
@param Event The instance of EFI_EVENT.
|
|
@param Context The parameter passed in.
|
|
|
|
**/
|
|
VOID
|
|
EFIAPI
|
|
ScsiDiskNotify (
|
|
IN EFI_EVENT Event,
|
|
IN VOID *Context
|
|
)
|
|
{
|
|
EFI_STATUS Status;
|
|
SCSI_ASYNC_RW_REQUEST *Request;
|
|
SCSI_DISK_DEV *ScsiDiskDevice;
|
|
EFI_BLOCK_IO2_TOKEN *Token;
|
|
UINTN Action;
|
|
UINT32 OldDataLength;
|
|
UINT32 OldSectorCount;
|
|
UINT8 MaxRetry;
|
|
|
|
gBS->CloseEvent (Event);
|
|
|
|
Request = (SCSI_ASYNC_RW_REQUEST *) Context;
|
|
ScsiDiskDevice = Request->ScsiDiskDevice;
|
|
Token = Request->BlkIo2Req->Token;
|
|
OldDataLength = Request->DataLength;
|
|
OldSectorCount = Request->SectorCount;
|
|
MaxRetry = 2;
|
|
|
|
//
|
|
// If previous sub-tasks already fails, no need to process this sub-task.
|
|
//
|
|
if (Token->TransactionStatus != EFI_SUCCESS) {
|
|
goto Exit;
|
|
}
|
|
|
|
//
|
|
// Check HostAdapterStatus and TargetStatus
|
|
// (EFI_TIMEOUT, EFI_DEVICE_ERROR, EFI_WARN_BUFFER_TOO_SMALL)
|
|
//
|
|
Status = CheckHostAdapterStatus (Request->HostAdapterStatus);
|
|
if ((Status == EFI_TIMEOUT) || (Status == EFI_NOT_READY)) {
|
|
if (++Request->TimesRetry > MaxRetry) {
|
|
Token->TransactionStatus = EFI_DEVICE_ERROR;
|
|
goto Exit;
|
|
} else {
|
|
goto Retry;
|
|
}
|
|
} else if (Status == EFI_DEVICE_ERROR) {
|
|
//
|
|
// reset the scsi channel
|
|
//
|
|
ScsiDiskDevice->ScsiIo->ResetBus (ScsiDiskDevice->ScsiIo);
|
|
Token->TransactionStatus = EFI_DEVICE_ERROR;
|
|
goto Exit;
|
|
}
|
|
|
|
Status = CheckTargetStatus (Request->TargetStatus);
|
|
if (Status == EFI_NOT_READY) {
|
|
//
|
|
// reset the scsi device
|
|
//
|
|
ScsiDiskDevice->ScsiIo->ResetDevice (ScsiDiskDevice->ScsiIo);
|
|
if (++Request->TimesRetry > MaxRetry) {
|
|
Token->TransactionStatus = EFI_DEVICE_ERROR;
|
|
goto Exit;
|
|
} else {
|
|
goto Retry;
|
|
}
|
|
} else if (Status == EFI_DEVICE_ERROR) {
|
|
Token->TransactionStatus = EFI_DEVICE_ERROR;
|
|
goto Exit;
|
|
}
|
|
|
|
if (Request->TargetStatus == EFI_EXT_SCSI_STATUS_TARGET_CHECK_CONDITION) {
|
|
DEBUG ((EFI_D_ERROR, "ScsiDiskNotify: Check Condition happened!\n"));
|
|
|
|
Status = DetectMediaParsingSenseKeys (
|
|
ScsiDiskDevice,
|
|
Request->SenseData,
|
|
Request->SenseDataLength / sizeof (EFI_SCSI_SENSE_DATA),
|
|
&Action
|
|
);
|
|
if (Action == ACTION_RETRY_COMMAND_LATER) {
|
|
if (++Request->TimesRetry > MaxRetry) {
|
|
Token->TransactionStatus = EFI_DEVICE_ERROR;
|
|
goto Exit;
|
|
} else {
|
|
goto Retry;
|
|
}
|
|
} else if (Action == ACTION_RETRY_WITH_BACKOFF_ALGO) {
|
|
if (Request->SectorCount <= 1) {
|
|
//
|
|
// Jump out if the operation still fails with one sector transfer
|
|
// length.
|
|
//
|
|
Token->TransactionStatus = EFI_DEVICE_ERROR;
|
|
goto Exit;
|
|
}
|
|
//
|
|
// Try again with two half length request if the sense data shows we need
|
|
// to retry.
|
|
//
|
|
Request->SectorCount >>= 1;
|
|
Request->DataLength = Request->SectorCount * ScsiDiskDevice->BlkIo.Media->BlockSize;
|
|
Request->TimesRetry = 0;
|
|
|
|
goto Retry;
|
|
} else {
|
|
Token->TransactionStatus = EFI_DEVICE_ERROR;
|
|
goto Exit;
|
|
}
|
|
}
|
|
|
|
//
|
|
// This sub-task succeeds, no need to retry.
|
|
//
|
|
goto Exit;
|
|
|
|
Retry:
|
|
if (Request->InBuffer != NULL) {
|
|
//
|
|
// SCSI read command
|
|
//
|
|
if (!ScsiDiskDevice->Cdb16Byte) {
|
|
Status = ScsiDiskAsyncRead10 (
|
|
ScsiDiskDevice,
|
|
Request->Timeout,
|
|
Request->TimesRetry,
|
|
Request->InBuffer,
|
|
Request->DataLength,
|
|
(UINT32) Request->StartLba,
|
|
Request->SectorCount,
|
|
Request->BlkIo2Req,
|
|
Token
|
|
);
|
|
} else {
|
|
Status = ScsiDiskAsyncRead16 (
|
|
ScsiDiskDevice,
|
|
Request->Timeout,
|
|
Request->TimesRetry,
|
|
Request->InBuffer,
|
|
Request->DataLength,
|
|
Request->StartLba,
|
|
Request->SectorCount,
|
|
Request->BlkIo2Req,
|
|
Token
|
|
);
|
|
}
|
|
|
|
if (EFI_ERROR (Status)) {
|
|
Token->TransactionStatus = EFI_DEVICE_ERROR;
|
|
goto Exit;
|
|
} else if (OldSectorCount != Request->SectorCount) {
|
|
//
|
|
// Original sub-task will be split into two new sub-tasks with smaller
|
|
// DataLength
|
|
//
|
|
if (!ScsiDiskDevice->Cdb16Byte) {
|
|
Status = ScsiDiskAsyncRead10 (
|
|
ScsiDiskDevice,
|
|
Request->Timeout,
|
|
0,
|
|
Request->InBuffer + Request->SectorCount * ScsiDiskDevice->BlkIo.Media->BlockSize,
|
|
OldDataLength - Request->DataLength,
|
|
(UINT32) Request->StartLba + Request->SectorCount,
|
|
OldSectorCount - Request->SectorCount,
|
|
Request->BlkIo2Req,
|
|
Token
|
|
);
|
|
} else {
|
|
Status = ScsiDiskAsyncRead16 (
|
|
ScsiDiskDevice,
|
|
Request->Timeout,
|
|
0,
|
|
Request->InBuffer + Request->SectorCount * ScsiDiskDevice->BlkIo.Media->BlockSize,
|
|
OldDataLength - Request->DataLength,
|
|
Request->StartLba + Request->SectorCount,
|
|
OldSectorCount - Request->SectorCount,
|
|
Request->BlkIo2Req,
|
|
Token
|
|
);
|
|
}
|
|
if (EFI_ERROR (Status)) {
|
|
Token->TransactionStatus = EFI_DEVICE_ERROR;
|
|
goto Exit;
|
|
}
|
|
}
|
|
} else {
|
|
//
|
|
// SCSI write command
|
|
//
|
|
if (!ScsiDiskDevice->Cdb16Byte) {
|
|
Status = ScsiDiskAsyncWrite10 (
|
|
ScsiDiskDevice,
|
|
Request->Timeout,
|
|
Request->TimesRetry,
|
|
Request->OutBuffer,
|
|
Request->DataLength,
|
|
(UINT32) Request->StartLba,
|
|
Request->SectorCount,
|
|
Request->BlkIo2Req,
|
|
Token
|
|
);
|
|
} else {
|
|
Status = ScsiDiskAsyncWrite16 (
|
|
ScsiDiskDevice,
|
|
Request->Timeout,
|
|
Request->TimesRetry,
|
|
Request->OutBuffer,
|
|
Request->DataLength,
|
|
Request->StartLba,
|
|
Request->SectorCount,
|
|
Request->BlkIo2Req,
|
|
Token
|
|
);
|
|
}
|
|
|
|
if (EFI_ERROR (Status)) {
|
|
Token->TransactionStatus = EFI_DEVICE_ERROR;
|
|
goto Exit;
|
|
} else if (OldSectorCount != Request->SectorCount) {
|
|
//
|
|
// Original sub-task will be split into two new sub-tasks with smaller
|
|
// DataLength
|
|
//
|
|
if (!ScsiDiskDevice->Cdb16Byte) {
|
|
Status = ScsiDiskAsyncWrite10 (
|
|
ScsiDiskDevice,
|
|
Request->Timeout,
|
|
0,
|
|
Request->OutBuffer + Request->SectorCount * ScsiDiskDevice->BlkIo.Media->BlockSize,
|
|
OldDataLength - Request->DataLength,
|
|
(UINT32) Request->StartLba + Request->SectorCount,
|
|
OldSectorCount - Request->SectorCount,
|
|
Request->BlkIo2Req,
|
|
Token
|
|
);
|
|
} else {
|
|
Status = ScsiDiskAsyncWrite16 (
|
|
ScsiDiskDevice,
|
|
Request->Timeout,
|
|
0,
|
|
Request->OutBuffer + Request->SectorCount * ScsiDiskDevice->BlkIo.Media->BlockSize,
|
|
OldDataLength - Request->DataLength,
|
|
Request->StartLba + Request->SectorCount,
|
|
OldSectorCount - Request->SectorCount,
|
|
Request->BlkIo2Req,
|
|
Token
|
|
);
|
|
}
|
|
if (EFI_ERROR (Status)) {
|
|
Token->TransactionStatus = EFI_DEVICE_ERROR;
|
|
goto Exit;
|
|
}
|
|
}
|
|
}
|
|
|
|
Exit:
|
|
RemoveEntryList (&Request->Link);
|
|
if ((IsListEmpty (&Request->BlkIo2Req->ScsiRWQueue)) &&
|
|
(Request->BlkIo2Req->LastScsiRW)) {
|
|
//
|
|
// The last SCSI R/W command of a BlockIo2 request completes
|
|
//
|
|
RemoveEntryList (&Request->BlkIo2Req->Link);
|
|
FreePool (Request->BlkIo2Req); // Should be freed only once
|
|
gBS->SignalEvent (Token->Event);
|
|
}
|
|
|
|
FreePool (Request->SenseData);
|
|
FreePool (Request);
|
|
}
|
|
|
|
|
|
/**
|
|
Submit Async Read(10) command.
|
|
|
|
@param ScsiDiskDevice The pointer of ScsiDiskDevice.
|
|
@param Timeout The time to complete the command.
|
|
@param TimesRetry The number of times the command has been retried.
|
|
@param DataBuffer The buffer to fill with the read out data.
|
|
@param DataLength The length of buffer.
|
|
@param StartLba The start logic block address.
|
|
@param SectorCount The number of blocks to read.
|
|
@param BlkIo2Req The upstream BlockIo2 request.
|
|
@param Token The pointer to the token associated with the
|
|
non-blocking read request.
|
|
|
|
@retval EFI_OUT_OF_RESOURCES The request could not be completed due to a
|
|
lack of resources.
|
|
@return others Status returned by calling
|
|
ScsiRead10CommandEx().
|
|
|
|
**/
|
|
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;
|
|
|
|
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
|
|
ScsiDiskAsyncWrite10 (
|
|
IN SCSI_DISK_DEV *ScsiDiskDevice,
|
|
IN UINT64 Timeout,
|
|
IN UINT8 TimesRetry,
|
|
IN UINT8 *DataBuffer,
|
|
IN UINT32 DataLength,
|
|
IN UINT32 StartLba,
|
|
IN UINT32 SectorCount,
|
|
IN OUT SCSI_BLKIO2_REQUEST *BlkIo2Req,
|
|
IN EFI_BLOCK_IO2_TOKEN *Token
|
|
)
|
|
{
|
|
EFI_STATUS Status;
|
|
SCSI_ASYNC_RW_REQUEST *Request;
|
|
EFI_EVENT AsyncIoEvent;
|
|
EFI_TPL OldTpl;
|
|
|
|
AsyncIoEvent = NULL;
|
|
|
|
Request = AllocateZeroPool (sizeof (SCSI_ASYNC_RW_REQUEST));
|
|
if (Request == NULL) {
|
|
return EFI_OUT_OF_RESOURCES;
|
|
}
|
|
|
|
OldTpl = gBS->RaiseTPL (TPL_NOTIFY);
|
|
InsertTailList (&BlkIo2Req->ScsiRWQueue, &Request->Link);
|
|
gBS->RestoreTPL (OldTpl);
|
|
|
|
Request->SenseDataLength = (UINT8) (6 * sizeof (EFI_SCSI_SENSE_DATA));
|
|
Request->SenseData = AllocateZeroPool (Request->SenseDataLength);
|
|
if (Request->SenseData == NULL) {
|
|
Status = EFI_OUT_OF_RESOURCES;
|
|
goto ErrorExit;
|
|
}
|
|
|
|
Request->ScsiDiskDevice = ScsiDiskDevice;
|
|
Request->Timeout = Timeout;
|
|
Request->TimesRetry = TimesRetry;
|
|
Request->OutBuffer = DataBuffer;
|
|
Request->DataLength = DataLength;
|
|
Request->StartLba = StartLba;
|
|
Request->SectorCount = SectorCount;
|
|
Request->BlkIo2Req = BlkIo2Req;
|
|
|
|
//
|
|
// Create Event
|
|
//
|
|
Status = gBS->CreateEvent (
|
|
EVT_NOTIFY_SIGNAL,
|
|
TPL_NOTIFY,
|
|
ScsiDiskNotify,
|
|
Request,
|
|
&AsyncIoEvent
|
|
);
|
|
if (EFI_ERROR(Status)) {
|
|
goto ErrorExit;
|
|
}
|
|
|
|
Status = ScsiWrite10CommandEx (
|
|
ScsiDiskDevice->ScsiIo,
|
|
Request->Timeout,
|
|
Request->SenseData,
|
|
&Request->SenseDataLength,
|
|
&Request->HostAdapterStatus,
|
|
&Request->TargetStatus,
|
|
Request->OutBuffer,
|
|
&Request->DataLength,
|
|
(UINT32) Request->StartLba,
|
|
Request->SectorCount,
|
|
AsyncIoEvent
|
|
);
|
|
if (EFI_ERROR(Status)) {
|
|
goto ErrorExit;
|
|
}
|
|
|
|
return EFI_SUCCESS;
|
|
|
|
ErrorExit:
|
|
if (AsyncIoEvent != NULL) {
|
|
gBS->CloseEvent (AsyncIoEvent);
|
|
}
|
|
|
|
if (Request != NULL) {
|
|
if (Request->SenseData != NULL) {
|
|
FreePool (Request->SenseData);
|
|
}
|
|
|
|
OldTpl = gBS->RaiseTPL (TPL_NOTIFY);
|
|
RemoveEntryList (&Request->Link);
|
|
gBS->RestoreTPL (OldTpl);
|
|
|
|
FreePool (Request);
|
|
}
|
|
|
|
return Status;
|
|
}
|
|
|
|
|
|
/**
|
|
Submit Async Read(16) command.
|
|
|
|
@param ScsiDiskDevice The pointer of ScsiDiskDevice.
|
|
@param Timeout The time to complete the command.
|
|
@param TimesRetry The number of times the command has been retried.
|
|
@param DataBuffer The buffer to fill with the read out data.
|
|
@param DataLength The length of buffer.
|
|
@param StartLba The start logic block address.
|
|
@param SectorCount The number of blocks to read.
|
|
@param BlkIo2Req The upstream BlockIo2 request.
|
|
@param Token The pointer to the token associated with the
|
|
non-blocking read request.
|
|
|
|
@retval EFI_OUT_OF_RESOURCES The request could not be completed due to a
|
|
lack of resources.
|
|
@return others Status returned by calling
|
|
ScsiRead16CommandEx().
|
|
|
|
**/
|
|
EFI_STATUS
|
|
ScsiDiskAsyncRead16 (
|
|
IN SCSI_DISK_DEV *ScsiDiskDevice,
|
|
IN UINT64 Timeout,
|
|
IN UINT8 TimesRetry,
|
|
OUT UINT8 *DataBuffer,
|
|
IN UINT32 DataLength,
|
|
IN UINT64 StartLba,
|
|
IN UINT32 SectorCount,
|
|
IN OUT SCSI_BLKIO2_REQUEST *BlkIo2Req,
|
|
IN EFI_BLOCK_IO2_TOKEN *Token
|
|
)
|
|
{
|
|
EFI_STATUS Status;
|
|
SCSI_ASYNC_RW_REQUEST *Request;
|
|
EFI_EVENT AsyncIoEvent;
|
|
EFI_TPL OldTpl;
|
|
|
|
AsyncIoEvent = NULL;
|
|
|
|
Request = AllocateZeroPool (sizeof (SCSI_ASYNC_RW_REQUEST));
|
|
if (Request == NULL) {
|
|
return EFI_OUT_OF_RESOURCES;
|
|
}
|
|
|
|
OldTpl = gBS->RaiseTPL (TPL_NOTIFY);
|
|
InsertTailList (&BlkIo2Req->ScsiRWQueue, &Request->Link);
|
|
gBS->RestoreTPL (OldTpl);
|
|
|
|
Request->SenseDataLength = (UINT8) (6 * sizeof (EFI_SCSI_SENSE_DATA));
|
|
Request->SenseData = AllocateZeroPool (Request->SenseDataLength);
|
|
if (Request->SenseData == NULL) {
|
|
Status = EFI_OUT_OF_RESOURCES;
|
|
goto ErrorExit;
|
|
}
|
|
|
|
Request->ScsiDiskDevice = ScsiDiskDevice;
|
|
Request->Timeout = Timeout;
|
|
Request->TimesRetry = TimesRetry;
|
|
Request->InBuffer = DataBuffer;
|
|
Request->DataLength = DataLength;
|
|
Request->StartLba = StartLba;
|
|
Request->SectorCount = SectorCount;
|
|
Request->BlkIo2Req = BlkIo2Req;
|
|
|
|
//
|
|
// Create Event
|
|
//
|
|
Status = gBS->CreateEvent (
|
|
EVT_NOTIFY_SIGNAL,
|
|
TPL_NOTIFY,
|
|
ScsiDiskNotify,
|
|
Request,
|
|
&AsyncIoEvent
|
|
);
|
|
if (EFI_ERROR(Status)) {
|
|
goto ErrorExit;
|
|
}
|
|
|
|
Status = ScsiRead16CommandEx (
|
|
ScsiDiskDevice->ScsiIo,
|
|
Request->Timeout,
|
|
Request->SenseData,
|
|
&Request->SenseDataLength,
|
|
&Request->HostAdapterStatus,
|
|
&Request->TargetStatus,
|
|
Request->InBuffer,
|
|
&Request->DataLength,
|
|
Request->StartLba,
|
|
Request->SectorCount,
|
|
AsyncIoEvent
|
|
);
|
|
if (EFI_ERROR(Status)) {
|
|
goto ErrorExit;
|
|
}
|
|
|
|
return EFI_SUCCESS;
|
|
|
|
ErrorExit:
|
|
if (AsyncIoEvent != NULL) {
|
|
gBS->CloseEvent (AsyncIoEvent);
|
|
}
|
|
|
|
if (Request != NULL) {
|
|
if (Request->SenseData != NULL) {
|
|
FreePool (Request->SenseData);
|
|
}
|
|
|
|
OldTpl = gBS->RaiseTPL (TPL_NOTIFY);
|
|
RemoveEntryList (&Request->Link);
|
|
gBS->RestoreTPL (OldTpl);
|
|
|
|
FreePool (Request);
|
|
}
|
|
|
|
return Status;
|
|
}
|
|
|
|
|
|
/**
|
|
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
|
|
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_STATUS Status;
|
|
SCSI_ASYNC_RW_REQUEST *Request;
|
|
EFI_EVENT AsyncIoEvent;
|
|
EFI_TPL OldTpl;
|
|
|
|
AsyncIoEvent = NULL;
|
|
|
|
Request = AllocateZeroPool (sizeof (SCSI_ASYNC_RW_REQUEST));
|
|
if (Request == NULL) {
|
|
return EFI_OUT_OF_RESOURCES;
|
|
}
|
|
|
|
OldTpl = gBS->RaiseTPL (TPL_NOTIFY);
|
|
InsertTailList (&BlkIo2Req->ScsiRWQueue, &Request->Link);
|
|
gBS->RestoreTPL (OldTpl);
|
|
|
|
Request->SenseDataLength = (UINT8) (6 * sizeof (EFI_SCSI_SENSE_DATA));
|
|
Request->SenseData = AllocateZeroPool (Request->SenseDataLength);
|
|
if (Request->SenseData == NULL) {
|
|
Status = EFI_OUT_OF_RESOURCES;
|
|
goto ErrorExit;
|
|
}
|
|
|
|
Request->ScsiDiskDevice = ScsiDiskDevice;
|
|
Request->Timeout = Timeout;
|
|
Request->TimesRetry = TimesRetry;
|
|
Request->OutBuffer = DataBuffer;
|
|
Request->DataLength = DataLength;
|
|
Request->StartLba = StartLba;
|
|
Request->SectorCount = SectorCount;
|
|
Request->BlkIo2Req = BlkIo2Req;
|
|
|
|
//
|
|
// Create Event
|
|
//
|
|
Status = gBS->CreateEvent (
|
|
EVT_NOTIFY_SIGNAL,
|
|
TPL_NOTIFY,
|
|
ScsiDiskNotify,
|
|
Request,
|
|
&AsyncIoEvent
|
|
);
|
|
if (EFI_ERROR(Status)) {
|
|
goto ErrorExit;
|
|
}
|
|
|
|
Status = ScsiWrite16CommandEx (
|
|
ScsiDiskDevice->ScsiIo,
|
|
Request->Timeout,
|
|
Request->SenseData,
|
|
&Request->SenseDataLength,
|
|
&Request->HostAdapterStatus,
|
|
&Request->TargetStatus,
|
|
Request->OutBuffer,
|
|
&Request->DataLength,
|
|
Request->StartLba,
|
|
Request->SectorCount,
|
|
AsyncIoEvent
|
|
);
|
|
if (EFI_ERROR(Status)) {
|
|
goto ErrorExit;
|
|
}
|
|
|
|
return EFI_SUCCESS;
|
|
|
|
ErrorExit:
|
|
if (AsyncIoEvent != NULL) {
|
|
gBS->CloseEvent (AsyncIoEvent);
|
|
}
|
|
|
|
if (Request != NULL) {
|
|
if (Request->SenseData != NULL) {
|
|
FreePool (Request->SenseData);
|
|
}
|
|
|
|
OldTpl = gBS->RaiseTPL (TPL_NOTIFY);
|
|
RemoveEntryList (&Request->Link);
|
|
gBS->RestoreTPL (OldTpl);
|
|
|
|
FreePool (Request);
|
|
}
|
|
|
|
return Status;
|
|
}
|
|
|
|
|
|
/**
|
|
Check sense key to find if media presents.
|
|
|
|
@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
|
|
**/
|
|
BOOLEAN
|
|
ScsiDiskIsNoMedia (
|
|
IN EFI_SCSI_SENSE_DATA *SenseData,
|
|
IN UINTN SenseCounts
|
|
)
|
|
{
|
|
EFI_SCSI_SENSE_DATA *SensePtr;
|
|
UINTN Index;
|
|
BOOLEAN IsNoMedia;
|
|
|
|
IsNoMedia = FALSE;
|
|
SensePtr = SenseData;
|
|
|
|
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;
|
|
}
|
|
SensePtr++;
|
|
}
|
|
|
|
return IsNoMedia;
|
|
}
|
|
|
|
|
|
/**
|
|
Parse sense key.
|
|
|
|
@param SenseData The pointer of EFI_SCSI_SENSE_DATA
|
|
@param SenseCounts The number of sense key
|
|
|
|
@retval TRUE Error
|
|
@retval FALSE NOT error
|
|
|
|
**/
|
|
BOOLEAN
|
|
ScsiDiskIsMediaError (
|
|
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++) {
|
|
|
|
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:
|
|
|
|
//
|
|
// 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;
|
|
|
|
default:
|
|
break;
|
|
}
|
|
|
|
break;
|
|
|
|
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;
|
|
|
|
default:
|
|
break;
|
|
}
|
|
break;
|
|
|
|
default:
|
|
break;
|
|
}
|
|
|
|
SensePtr++;
|
|
}
|
|
|
|
return IsError;
|
|
}
|
|
|
|
|
|
/**
|
|
Check sense key to find if hardware error happens.
|
|
|
|
@param SenseData The pointer of EFI_SCSI_SENSE_DATA
|
|
@param SenseCounts The number of sense key
|
|
|
|
@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++) {
|
|
|
|
//
|
|
// Sense Key is EFI_SCSI_SK_HARDWARE_ERROR (0x4)
|
|
//
|
|
if (SensePtr->Sense_Key == EFI_SCSI_SK_HARDWARE_ERROR) {
|
|
IsError = TRUE;
|
|
}
|
|
|
|
SensePtr++;
|
|
}
|
|
|
|
return IsError;
|
|
}
|
|
|
|
|
|
/**
|
|
Check sense key to find if media has changed.
|
|
|
|
@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.
|
|
**/
|
|
BOOLEAN
|
|
ScsiDiskIsMediaChange (
|
|
IN EFI_SCSI_SENSE_DATA *SenseData,
|
|
IN UINTN SenseCounts
|
|
)
|
|
{
|
|
EFI_SCSI_SENSE_DATA *SensePtr;
|
|
UINTN Index;
|
|
BOOLEAN IsMediaChanged;
|
|
|
|
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;
|
|
}
|
|
|
|
SensePtr++;
|
|
}
|
|
|
|
return IsMediaChanged;
|
|
}
|
|
|
|
/**
|
|
Check sense key to find if reset happens.
|
|
|
|
@param SenseData The pointer of EFI_SCSI_SENSE_DATA
|
|
@param SenseCounts The number of sense key
|
|
|
|
@retval TRUE It is reset before.
|
|
@retval FALSE It is NOT reset before.
|
|
|
|
**/
|
|
BOOLEAN
|
|
ScsiDiskIsResetBefore (
|
|
IN EFI_SCSI_SENSE_DATA *SenseData,
|
|
IN UINTN SenseCounts
|
|
)
|
|
{
|
|
EFI_SCSI_SENSE_DATA *SensePtr;
|
|
UINTN Index;
|
|
BOOLEAN IsResetBefore;
|
|
|
|
IsResetBefore = 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_RESET (0x29)
|
|
//
|
|
if ((SensePtr->Sense_Key == EFI_SCSI_SK_UNIT_ATTENTION) &&
|
|
(SensePtr->Addnl_Sense_Code == EFI_SCSI_ASC_RESET)) {
|
|
IsResetBefore = TRUE;
|
|
}
|
|
|
|
SensePtr++;
|
|
}
|
|
|
|
return IsResetBefore;
|
|
}
|
|
|
|
/**
|
|
Check sense key to find if the drive is ready.
|
|
|
|
@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
|
|
|
|
@retval TRUE Drive is ready.
|
|
@retval FALSE Drive is NOT ready.
|
|
|
|
**/
|
|
BOOLEAN
|
|
ScsiDiskIsDriveReady (
|
|
IN EFI_SCSI_SENSE_DATA *SenseData,
|
|
IN UINTN SenseCounts,
|
|
OUT BOOLEAN *RetryLater
|
|
)
|
|
{
|
|
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) {
|
|
|
|
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;
|
|
}
|
|
break;
|
|
|
|
default:
|
|
break;
|
|
}
|
|
|
|
SensePtr++;
|
|
}
|
|
|
|
return IsReady;
|
|
}
|
|
|
|
/**
|
|
Check sense key to find if it has sense key.
|
|
|
|
@param SenseData - The pointer of EFI_SCSI_SENSE_DATA
|
|
@param SenseCounts - The number of sense key
|
|
|
|
@retval TRUE It has sense key.
|
|
@retval FALSE It has NOT any sense key.
|
|
|
|
**/
|
|
BOOLEAN
|
|
ScsiDiskHaveSenseKey (
|
|
IN EFI_SCSI_SENSE_DATA *SenseData,
|
|
IN UINTN SenseCounts
|
|
)
|
|
{
|
|
EFI_SCSI_SENSE_DATA *SensePtr;
|
|
UINTN Index;
|
|
BOOLEAN HaveSenseKey;
|
|
|
|
if (SenseCounts == 0) {
|
|
HaveSenseKey = FALSE;
|
|
} else {
|
|
HaveSenseKey = TRUE;
|
|
}
|
|
|
|
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;
|
|
}
|
|
|
|
SensePtr++;
|
|
}
|
|
|
|
return HaveSenseKey;
|
|
}
|
|
|
|
/**
|
|
Release resource about disk device.
|
|
|
|
@param ScsiDiskDevice The pointer of SCSI_DISK_DEV
|
|
|
|
**/
|
|
VOID
|
|
ReleaseScsiDiskDeviceResources (
|
|
IN SCSI_DISK_DEV *ScsiDiskDevice
|
|
)
|
|
{
|
|
if (ScsiDiskDevice == NULL) {
|
|
return ;
|
|
}
|
|
|
|
if (ScsiDiskDevice->SenseData != NULL) {
|
|
FreePool (ScsiDiskDevice->SenseData);
|
|
ScsiDiskDevice->SenseData = NULL;
|
|
}
|
|
|
|
if (ScsiDiskDevice->ControllerNameTable != NULL) {
|
|
FreeUnicodeStringTable (ScsiDiskDevice->ControllerNameTable);
|
|
ScsiDiskDevice->ControllerNameTable = NULL;
|
|
}
|
|
|
|
FreePool (ScsiDiskDevice);
|
|
|
|
ScsiDiskDevice = NULL;
|
|
}
|
|
|
|
/**
|
|
Determine if Block Io & Block Io2 should be produced.
|
|
|
|
|
|
@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.
|
|
|
|
**/
|
|
BOOLEAN
|
|
DetermineInstallBlockIo (
|
|
IN EFI_HANDLE ChildHandle
|
|
)
|
|
{
|
|
EFI_SCSI_PASS_THRU_PROTOCOL *ScsiPassThru;
|
|
EFI_EXT_SCSI_PASS_THRU_PROTOCOL *ExtScsiPassThru;
|
|
|
|
//
|
|
// 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;
|
|
}
|
|
|
|
/**
|
|
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 ProtocolGuid ProtocolGuid pointer.
|
|
@param ChildHandle Child Handle to retrieve Parent information.
|
|
|
|
**/
|
|
VOID *
|
|
EFIAPI
|
|
GetParentProtocol (
|
|
IN EFI_GUID *ProtocolGuid,
|
|
IN EFI_HANDLE ChildHandle
|
|
)
|
|
{
|
|
UINTN Index;
|
|
UINTN HandleCount;
|
|
VOID *Interface;
|
|
EFI_STATUS Status;
|
|
EFI_HANDLE *HandleBuffer;
|
|
|
|
//
|
|
// Retrieve the list of all handles from the handle database
|
|
//
|
|
Status = gBS->LocateHandleBuffer (
|
|
ByProtocol,
|
|
ProtocolGuid,
|
|
NULL,
|
|
&HandleCount,
|
|
&HandleBuffer
|
|
);
|
|
|
|
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;
|
|
}
|
|
}
|
|
}
|
|
|
|
gBS->FreePool (HandleBuffer);
|
|
return NULL;
|
|
}
|
|
|
|
/**
|
|
Determine if EFI Erase Block Protocol should be produced.
|
|
|
|
@param ScsiDiskDevice The pointer of SCSI_DISK_DEV.
|
|
@param ChildHandle Handle of device.
|
|
|
|
@retval TRUE Should produce EFI Erase Block Protocol.
|
|
@retval FALSE Should not produce EFI Erase Block Protocol.
|
|
|
|
**/
|
|
BOOLEAN
|
|
DetermineInstallEraseBlock (
|
|
IN SCSI_DISK_DEV *ScsiDiskDevice,
|
|
IN EFI_HANDLE ChildHandle
|
|
)
|
|
{
|
|
UINT8 HostAdapterStatus;
|
|
UINT8 TargetStatus;
|
|
EFI_STATUS CommandStatus;
|
|
EFI_STATUS Status;
|
|
BOOLEAN UfsDevice;
|
|
BOOLEAN RetVal;
|
|
EFI_DEVICE_PATH_PROTOCOL *DevicePathNode;
|
|
UINT8 SenseDataLength;
|
|
UINT32 DataLength16;
|
|
EFI_SCSI_DISK_CAPACITY_DATA16 *CapacityData16;
|
|
|
|
UfsDevice = FALSE;
|
|
RetVal = TRUE;
|
|
CapacityData16 = NULL;
|
|
|
|
Status = gBS->HandleProtocol (
|
|
ChildHandle,
|
|
&gEfiDevicePathProtocolGuid,
|
|
(VOID **) &DevicePathNode
|
|
);
|
|
//
|
|
// Device Path protocol must be installed on the device handle.
|
|
//
|
|
ASSERT_EFI_ERROR (Status);
|
|
|
|
while (!IsDevicePathEndType (DevicePathNode)) {
|
|
//
|
|
// For now, only support Erase Block Protocol on UFS devices.
|
|
//
|
|
if ((DevicePathNode->Type == MESSAGING_DEVICE_PATH) &&
|
|
(DevicePathNode->SubType == MSG_UFS_DP)) {
|
|
UfsDevice = TRUE;
|
|
break;
|
|
}
|
|
|
|
DevicePathNode = NextDevicePathNode (DevicePathNode);
|
|
}
|
|
if (!UfsDevice) {
|
|
RetVal = FALSE;
|
|
goto Done;
|
|
}
|
|
|
|
//
|
|
// Check whether the erase functionality is enabled on the UFS device.
|
|
//
|
|
CapacityData16 = AllocateAlignedBuffer (ScsiDiskDevice, sizeof (EFI_SCSI_DISK_CAPACITY_DATA16));
|
|
if (CapacityData16 == NULL) {
|
|
RetVal = FALSE;
|
|
goto Done;
|
|
}
|
|
|
|
SenseDataLength = 0;
|
|
DataLength16 = sizeof (EFI_SCSI_DISK_CAPACITY_DATA16);
|
|
ZeroMem (CapacityData16, sizeof (EFI_SCSI_DISK_CAPACITY_DATA16));
|
|
|
|
CommandStatus = ScsiReadCapacity16Command (
|
|
ScsiDiskDevice->ScsiIo,
|
|
SCSI_DISK_TIMEOUT,
|
|
NULL,
|
|
&SenseDataLength,
|
|
&HostAdapterStatus,
|
|
&TargetStatus,
|
|
(VOID *) CapacityData16,
|
|
&DataLength16,
|
|
FALSE
|
|
);
|
|
|
|
if (CommandStatus == EFI_SUCCESS) {
|
|
//
|
|
// Universal Flash Storage (UFS) Version 2.0
|
|
// Section 11.3.9.2
|
|
// Bits TPE and TPRZ should both be set to enable the erase feature on UFS.
|
|
//
|
|
if (((CapacityData16->LowestAlignLogic2 & BIT7) == 0) ||
|
|
((CapacityData16->LowestAlignLogic2 & BIT6) == 0)) {
|
|
DEBUG ((
|
|
EFI_D_VERBOSE,
|
|
"ScsiDisk EraseBlock: Either TPE or TPRZ is not set: 0x%x.\n",
|
|
CapacityData16->LowestAlignLogic2
|
|
));
|
|
|
|
RetVal = FALSE;
|
|
goto Done;
|
|
}
|
|
} else {
|
|
DEBUG ((
|
|
EFI_D_VERBOSE,
|
|
"ScsiDisk EraseBlock: ReadCapacity16 failed with status %r.\n",
|
|
CommandStatus
|
|
));
|
|
|
|
RetVal = FALSE;
|
|
goto Done;
|
|
}
|
|
|
|
//
|
|
// Check whether the UFS device server implements the UNMAP command.
|
|
//
|
|
if ((ScsiDiskDevice->UnmapInfo.MaxLbaCnt == 0) ||
|
|
(ScsiDiskDevice->UnmapInfo.MaxBlkDespCnt == 0)) {
|
|
DEBUG ((
|
|
EFI_D_VERBOSE,
|
|
"ScsiDisk EraseBlock: The device server does not implement the UNMAP command.\n"
|
|
));
|
|
|
|
RetVal = FALSE;
|
|
goto Done;
|
|
}
|
|
|
|
Done:
|
|
if (CapacityData16 != NULL) {
|
|
FreeAlignedBuffer (CapacityData16, sizeof (EFI_SCSI_DISK_CAPACITY_DATA16));
|
|
}
|
|
|
|
return RetVal;
|
|
}
|
|
|
|
/**
|
|
Provides inquiry information for the controller type.
|
|
|
|
This function is used by the IDE bus driver to get inquiry data. Data format
|
|
of Identify data is defined by the Interface GUID.
|
|
|
|
@param[in] This Pointer to the EFI_DISK_INFO_PROTOCOL instance.
|
|
@param[in, out] InquiryData Pointer to a buffer for the inquiry data.
|
|
@param[in, out] InquiryDataSize Pointer to the value for the inquiry data size.
|
|
|
|
@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
|
|
|
|
**/
|
|
EFI_STATUS
|
|
EFIAPI
|
|
ScsiDiskInfoInquiry (
|
|
IN EFI_DISK_INFO_PROTOCOL *This,
|
|
IN OUT VOID *InquiryData,
|
|
IN OUT UINT32 *InquiryDataSize
|
|
)
|
|
{
|
|
EFI_STATUS Status;
|
|
SCSI_DISK_DEV *ScsiDiskDevice;
|
|
|
|
ScsiDiskDevice = SCSI_DISK_DEV_FROM_DISKINFO (This);
|
|
|
|
Status = EFI_BUFFER_TOO_SMALL;
|
|
if (*InquiryDataSize >= sizeof (ScsiDiskDevice->InquiryData)) {
|
|
Status = EFI_SUCCESS;
|
|
CopyMem (InquiryData, &ScsiDiskDevice->InquiryData, sizeof (ScsiDiskDevice->InquiryData));
|
|
}
|
|
*InquiryDataSize = sizeof (ScsiDiskDevice->InquiryData);
|
|
return Status;
|
|
}
|
|
|
|
|
|
/**
|
|
Provides identify information for the controller type.
|
|
|
|
This function is used by the IDE bus driver to get identify data. Data format
|
|
of Identify data is defined by the Interface GUID.
|
|
|
|
@param[in] This Pointer to the EFI_DISK_INFO_PROTOCOL
|
|
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
|
|
|
|
**/
|
|
EFI_STATUS
|
|
EFIAPI
|
|
ScsiDiskInfoIdentify (
|
|
IN EFI_DISK_INFO_PROTOCOL *This,
|
|
IN OUT VOID *IdentifyData,
|
|
IN OUT UINT32 *IdentifyDataSize
|
|
)
|
|
{
|
|
EFI_STATUS Status;
|
|
SCSI_DISK_DEV *ScsiDiskDevice;
|
|
|
|
if (CompareGuid (&This->Interface, &gEfiDiskInfoScsiInterfaceGuid) || CompareGuid (&This->Interface, &gEfiDiskInfoUfsInterfaceGuid)) {
|
|
//
|
|
// Physical SCSI bus does not support this data class.
|
|
//
|
|
return EFI_NOT_FOUND;
|
|
}
|
|
|
|
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));
|
|
}
|
|
*IdentifyDataSize = sizeof (ScsiDiskDevice->IdentifyData);
|
|
return Status;
|
|
}
|
|
|
|
/**
|
|
Provides sense data information for the controller type.
|
|
|
|
This function is used by the IDE bus driver to get sense data.
|
|
Data format of Sense data is defined by the Interface GUID.
|
|
|
|
@param[in] This Pointer to the EFI_DISK_INFO_PROTOCOL instance.
|
|
@param[in, out] SenseData Pointer to the SenseData.
|
|
@param[in, out] SenseDataSize Size of SenseData in bytes.
|
|
@param[out] SenseDataNumber Pointer to the value for the sense data size.
|
|
|
|
@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.
|
|
|
|
**/
|
|
EFI_STATUS
|
|
EFIAPI
|
|
ScsiDiskInfoSenseData (
|
|
IN EFI_DISK_INFO_PROTOCOL *This,
|
|
IN OUT VOID *SenseData,
|
|
IN OUT UINT32 *SenseDataSize,
|
|
OUT UINT8 *SenseDataNumber
|
|
)
|
|
{
|
|
return EFI_NOT_FOUND;
|
|
}
|
|
|
|
|
|
/**
|
|
This function is used by the IDE bus driver to get controller information.
|
|
|
|
@param[in] This Pointer to the EFI_DISK_INFO_PROTOCOL instance.
|
|
@param[out] IdeChannel Pointer to the Ide Channel number. Primary or secondary.
|
|
@param[out] IdeDevice Pointer to the Ide Device number. Master or slave.
|
|
|
|
@retval EFI_SUCCESS IdeChannel and IdeDevice are valid.
|
|
@retval EFI_UNSUPPORTED This is not an IDE device.
|
|
|
|
**/
|
|
EFI_STATUS
|
|
EFIAPI
|
|
ScsiDiskInfoWhichIde (
|
|
IN EFI_DISK_INFO_PROTOCOL *This,
|
|
OUT UINT32 *IdeChannel,
|
|
OUT UINT32 *IdeDevice
|
|
)
|
|
{
|
|
SCSI_DISK_DEV *ScsiDiskDevice;
|
|
|
|
if (CompareGuid (&This->Interface, &gEfiDiskInfoScsiInterfaceGuid) || CompareGuid (&This->Interface, &gEfiDiskInfoUfsInterfaceGuid)) {
|
|
//
|
|
// This is not an IDE physical device.
|
|
//
|
|
return EFI_UNSUPPORTED;
|
|
}
|
|
|
|
ScsiDiskDevice = SCSI_DISK_DEV_FROM_DISKINFO (This);
|
|
*IdeChannel = ScsiDiskDevice->Channel;
|
|
*IdeDevice = ScsiDiskDevice->Device;
|
|
|
|
return EFI_SUCCESS;
|
|
}
|
|
|
|
|
|
/**
|
|
Issues ATA IDENTIFY DEVICE command to identify ATAPI device.
|
|
|
|
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.
|
|
|
|
@param ScsiDiskDevice The pointer of SCSI_DISK_DEV
|
|
|
|
@retval EFI_SUCCESS The ATAPI device identify data were retrieved successfully.
|
|
@retval others Some error occurred during the identification that ATAPI device.
|
|
|
|
**/
|
|
EFI_STATUS
|
|
AtapiIdentifyDevice (
|
|
IN OUT SCSI_DISK_DEV *ScsiDiskDevice
|
|
)
|
|
{
|
|
EFI_SCSI_IO_SCSI_REQUEST_PACKET CommandPacket;
|
|
UINT8 Cdb[6];
|
|
|
|
//
|
|
// Initialize SCSI REQUEST_PACKET and 6-byte Cdb
|
|
//
|
|
ZeroMem (&CommandPacket, sizeof (CommandPacket));
|
|
ZeroMem (Cdb, sizeof (Cdb));
|
|
|
|
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 ScsiDiskDevice->ScsiIo->ExecuteScsiCommand (ScsiDiskDevice->ScsiIo, &CommandPacket, NULL);
|
|
}
|
|
|
|
|
|
/**
|
|
Initialize the installation of DiskInfo protocol.
|
|
|
|
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
|
|
InitializeInstallDiskInfo (
|
|
IN SCSI_DISK_DEV *ScsiDiskDevice,
|
|
IN EFI_HANDLE ChildHandle
|
|
)
|
|
{
|
|
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;
|
|
}
|
|
DevicePathNode = ChildDevicePathNode;
|
|
}
|
|
|
|
return;
|
|
}
|