Newer
Older
AMI-Aptio-BIOS-Reversed / MdeModulePkg / Bus / Scsi / ScsiDiskDxe / ScsiDisk.c
@Ajax Dong Ajax Dong 2 days ago 47 KB Full restructure
/** @file
  ScsiDisk.c - UEFI SCSI Disk Driver

  This driver manages SCSI hard disk devices. It produces:
    - EFI_BLOCK_IO_PROTOCOL
    - EFI_DISK_IO_PROTOCOL
    - EFI_ERASE_BLOCK_PROTOCOL

  on child handles created when a SCSI I/O protocol is detected on a
  controller handle.

  Derived from reverse engineering of ScsiDisk.efi (MD5: 5ca30228dfaec7f138f04c586d10b153).
  Source: MdeModulePkg\Bus\Scsi\ScsiDiskDxe\ScsiDisk.c
  Build: HR6N0XMLK DEBUG_VS2015 X64

  NOTE: This is a reconstructed source based on static analysis of the
  compiled binary. Function signatures and logic are inferred from
  observed behavior in the disassembly.
**/

#include "ScsiDisk.h"

//
// Global data (from .data section, 0x88A0-0x8AA0)
//
EFI_HANDLE              gImageHandle     = NULL;    // 0x8A70
EFI_SYSTEM_TABLE       *gST              = NULL;    // 0x8A60
EFI_BOOT_SERVICES      *gBS              = NULL;    // 0x8A68
EFI_RUNTIME_SERVICES   *gRT              = NULL;    // 0x8A78

//
// Component Name tables (in .data at 0x8A10-0x8A58)
//
STATIC EFI_UNICODE_STRING_TABLE  mScsiDiskDriverNameTable[] = {
  { "eng;en", L"Scsi Disk Driver" },
  { NULL, NULL }
};

STATIC EFI_UNICODE_STRING_TABLE  mScsiDiskControllerNameTable[] = {
  { "eng;en", L"SCSI Disk Device" },
  { NULL, NULL }
};

//
// EFI_DRIVER_BINDING_PROTOCOL instance (at 0x88C0 in .data)
//
STATIC EFI_DRIVER_BINDING_PROTOCOL  gScsiDiskDriverBinding = {
  ScsiDiskDriverBindingSupported,
  ScsiDiskDriverBindingStart,
  ScsiDiskDriverBindingStop,
  0x10,
  NULL,
  NULL
};

//
// EFI_COMPONENT_NAME2_PROTOCOL instance (at 0x88E0 in .data)
//
STATIC EFI_COMPONENT_NAME2_PROTOCOL  gScsiDiskComponentName2 = {
  ScsiDiskComponentNameGetDriverName,
  ScsiDiskComponentNameGetControllerName,
  "en"
};

//
// EFI_DRIVER_CONFIGURATION_PROTOCOL instance (at 0x8900 in .data)
//
STATIC EFI_DRIVER_CONFIGURATION_PROTOCOL  gScsiDiskDriverConfiguration = {
  NULL,
  NULL,
  NULL
};

//
// GUID definitions
//
EFI_GUID  gEfiScsiIoProtocolGuid              = SCSI_DISK_SCSI_IO_GUID;
EFI_GUID  gEfiBlockIoProtocolGuid             = SCSI_DISK_BLOCK_IO_GUID;
EFI_GUID  gEfiDiskIoProtocolGuid              = SCSI_DISK_DISK_IO_GUID;
EFI_GUID  gEfiDriverBindingProtocolGuid       = SCSI_DISK_DRIVER_BINDING_GUID;
EFI_GUID  gEfiStorageSecurityCommandProtocolGuid = STORAGE_SECURITY_COMMAND_PROTOCOL_GUID;

//
// Forward declarations of local helper functions
//
STATIC
EFI_STATUS
ScsiDiskReadSectors (
  IN  SCSI_DISK_DEVICE *Device,
  IN  UINT32            MediaId,
  IN  EFI_LBA           Lba,
  OUT VOID             *Buffer,
  IN  UINTN             BufferSize
  );

STATIC
EFI_STATUS
ScsiDiskWriteSectors (
  IN  SCSI_DISK_DEVICE *Device,
  IN  UINT32            MediaId,
  IN  EFI_LBA           Lba,
  IN  VOID             *Buffer,
  IN  UINTN             BufferSize
  );

STATIC
EFI_STATUS
ScsiDiskReadCapacity (
  IN SCSI_DISK_DEVICE *Device
  );

STATIC
VOID
ScsiDiskParseCapacityData (
  IN SCSI_DISK_DEVICE *Device,
  IN UINT8            *Data,
  IN BOOLEAN          IsCapacity16
  );

//
// Module Entry Point
//

EFI_STATUS
EFIAPI
ModuleEntryPoint (
  IN EFI_HANDLE  ImageHandle
  )
{
  EFI_STATUS  Status;

  gST  = (VOID *)0;  // Set by _ModuleEntryPoint thunk
  gBS  = (VOID *)0;  // Set by _ModuleEntryPoint thunk
  gRT  = (VOID *)0;  // Set by _ModuleEntryPoint thunk
  gImageHandle = ImageHandle;

  Status = ScsiDiskDriverEntry (ImageHandle);
  return Status;
}

//
// Driver Entry Point
//

EFI_STATUS
EFIAPI
ScsiDiskDriverEntry (
  IN EFI_HANDLE  ImageHandle
  )
{
  EFI_STATUS  Status;
  VOID        *Interface;

  gImageHandle = ImageHandle;

  Status = gBS->InstallMultipleProtocolInterfaces (
             &ImageHandle,
             &gEfiDriverBindingProtocolGuid,  &gScsiDiskDriverBinding,
             &gEfiComponentName2ProtocolGuid, &gScsiDiskComponentName2,
             &gEfiDriverConfigurationProtocolGuid, &Interface,
             NULL
             );
  return Status;
}

//
// Driver Binding Protocol
//

EFI_STATUS
EFIAPI
ScsiDiskDriverBindingSupported (
  IN EFI_DRIVER_BINDING_PROTOCOL  *This,
  IN EFI_HANDLE                   ControllerHandle,
  IN EFI_DEVICE_PATH_PROTOCOL     *RemainingDevicePath
  )
{
  EFI_STATUS  Status;
  VOID        *Interface;

  Status = gBS->OpenProtocol (
             ControllerHandle,
             &gEfiScsiIoProtocolGuid,
             &Interface,
             gImageHandle,
             ControllerHandle,
             EFI_OPEN_PROTOCOL_BY_DRIVER
             );
  if (EFI_ERROR (Status)) {
    return EFI_UNSUPPORTED;
  }

  gBS->CloseProtocol (
         ControllerHandle,
         &gEfiScsiIoProtocolGuid,
         gImageHandle,
         ControllerHandle
         );

  return EFI_SUCCESS;
}

EFI_STATUS
EFIAPI
ScsiDiskDriverBindingStart (
  IN EFI_DRIVER_BINDING_PROTOCOL  *This,
  IN EFI_HANDLE                   ControllerHandle,
  IN EFI_DEVICE_PATH_PROTOCOL     *RemainingDevicePath
  )
{
  EFI_STATUS       Status;
  SCSI_DISK_DEVICE *Device;
  BOOLEAN          MediaChanged;
  UINT32           Retry;

  //
  // Allocate zero-initialized device structure (0x390 bytes)
  //
  Device = (SCSI_DISK_DEVICE *)gBS->AllocateZeroPool (SCSI_DISK_DEVICE_SIZE);
  if (Device == NULL) {
    return EFI_OUT_OF_RESOURCES;
  }

  Device->Signature = SCSI_DISK_DEVICE_SIGNATURE;
  ScsiDiskInitializeListHead (&Device->AsyncUnmapQueue);

  //
  // Open SCSI I/O protocol on the controller handle
  //
  Status = gBS->OpenProtocol (
             ControllerHandle,
             &gEfiScsiIoProtocolGuid,
             (VOID **)&Device->ScsiIo,
             gImageHandle,
             ControllerHandle,
             EFI_OPEN_PROTOCOL_BY_DRIVER
             );
  if (EFI_ERROR (Status)) {
    gBS->FreePool (Device);
    return Status;
  }

  Device->ControllerHandle = ControllerHandle;
  Device->MediaPresentFlag = 1;

  //
  // Retry loop for media detection
  //
  for (Retry = 0; Retry < 3; Retry++) {
    Status = ScsiDiskDetectMedia (Device, FALSE, &MediaChanged);
    if (!EFI_ERROR (Status)) {
      break;
    }

    if (Status == EFI_NO_MEDIA) {
      //
      // No media present -- still create device node, just with no media
      //
      break;
    }

    gBS->Stall (100000);
  }

  //
  // Set up Block I/O protocol function pointers (inlined in structure)
  //
  Device->BlkIoRevision        = 0x200031;
  Device->BlkIoMediaPtr        = &Device->Media;
  Device->BlkIoReset           = ScsiDiskReset;
  Device->BlkIoReadBlocks      = ScsiDiskReadBlocks;
  Device->BlkIoWriteBlocks     = ScsiDiskWriteBlocks;
  Device->BlkIoFlushBlocks     = ScsiDiskFlushBlocks;
  Device->BlkIoMediaPtr2       = &Device->Media;

  //
  // Set up Disk I/O protocol function pointers (inlined in structure)
  //
  Device->DiskIoReset          = ScsiDiskDiskIoReset;
  Device->DiskIoRead           = ScsiDiskDiskIoRead;
  Device->DiskIoWrite          = ScsiDiskDiskIoWrite;
  Device->DiskIoFlush          = ScsiDiskDiskIoFlush;

  //
  // Fill in media descriptor
  //
  Device->Media.RemovableMedia   = (UINT8)ScsiDiskIsDeviceRemovable (Device, ControllerHandle);
  Device->Media.MediaPresent     = Device->MediaPresentFlag ? 1 : 0;
  Device->Media.LogicalPartition = 0;
  Device->Media.ReadOnly         = 0;
  Device->Media.WriteCaching     = 0;
  Device->Media.BlockSize        = 0x200;
  Device->Media.IoAlign          = 0;
  Device->Media.LastBlock        = 0;
  Device->Media.LowestAlignedLBA = 0;
  Device->Media.LogicalUnitsPerPhysicalBlockExponent = 0;
  Device->Media.OptimalTransferLengthGranularity     = 0;

  //
  // Install Block I/O and Disk I/O protocols on the controller handle
  //
  Status = gBS->InstallMultipleProtocolInterfaces (
             &ControllerHandle,
             &gEfiBlockIoProtocolGuid,  &Device->BlockIo,
             &gEfiDiskIoProtocolGuid,   &Device->DiskIo,
             NULL
             );
  if (EFI_ERROR (Status)) {
    gBS->CloseProtocol (
           ControllerHandle,
           &gEfiScsiIoProtocolGuid,
           gImageHandle,
           ControllerHandle
           );
    gBS->FreePool (Device);
    return Status;
  }

  return EFI_SUCCESS;
}

EFI_STATUS
EFIAPI
ScsiDiskDriverBindingStop (
  IN EFI_DRIVER_BINDING_PROTOCOL  *This,
  IN EFI_HANDLE                   ControllerHandle,
  IN UINTN                        NumberOfChildren,
  IN EFI_HANDLE                  *ChildHandleBuffer
  )
{
  EFI_STATUS       Status;
  SCSI_DISK_DEVICE *Device;
  EFI_BLOCK_IO_PROTOCOL *BlockIo;
  UINTN            Index;

  for (Index = 0; Index < NumberOfChildren; Index++) {
    Status = gBS->OpenProtocol (
               ChildHandleBuffer[Index],
               &gEfiBlockIoProtocolGuid,
               (VOID **)&BlockIo,
               gImageHandle,
               ControllerHandle,
               EFI_OPEN_PROTOCOL_GET_PROTOCOL
               );
    if (EFI_ERROR (Status)) {
      continue;
    }

    Device = SCSI_DISK_DEVICE_FROM_BLOCK_IO (BlockIo);

    gBS->UninstallMultipleProtocolInterfaces (
           ChildHandleBuffer[Index],
           &gEfiBlockIoProtocolGuid, &Device->BlockIo,
           &gEfiDiskIoProtocolGuid,  &Device->DiskIo,
           NULL
           );

    gBS->CloseProtocol (
           Device->ControllerHandle,
           &gEfiScsiIoProtocolGuid,
           gImageHandle,
           Device->ControllerHandle
           );

    ScsiDiskFreeDeviceMemory (Device);
  }

  return EFI_SUCCESS;
}

//
// Block I/O Protocol
//

EFI_STATUS
EFIAPI
ScsiDiskReset (
  IN EFI_BLOCK_IO_PROTOCOL  *This,
  IN BOOLEAN                ExtendedVerification
  )
{
  SCSI_DISK_DEVICE *Device;

  Device = SCSI_DISK_DEVICE_FROM_BLOCK_IO (This);

  if (ExtendedVerification) {
    Device->ScsiIo->ResetBus (Device->ScsiIo);
  }

  return Device->ScsiIo->ResetDevice (Device->ScsiIo);
}

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_DEVICE *Device;
  EFI_STATUS       Status;
  BOOLEAN          MediaChanged;

  Device = SCSI_DISK_DEVICE_FROM_BLOCK_IO (This);

  Status = ScsiDiskDetectMedia (Device, TRUE, &MediaChanged);
  if (EFI_ERROR (Status)) {
    return Status;
  }

  if (MediaChanged) {
    return EFI_MEDIA_CHANGED;
  }

  if (!Device->Media.MediaPresent) {
    return EFI_NO_MEDIA;
  }

  if (MediaId != Device->Media.MediaId) {
    return EFI_MEDIA_CHANGED;
  }

  if ((BufferSize % Device->Media.BlockSize) != 0) {
    return EFI_BAD_BUFFER_SIZE;
  }

  if (LBA + (BufferSize / Device->Media.BlockSize) > Device->Media.LastBlock + 1) {
    return EFI_INVALID_PARAMETER;
  }

  return ScsiDiskReadSectors (Device, MediaId, LBA, Buffer, BufferSize);
}

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_DEVICE *Device;
  EFI_STATUS       Status;
  BOOLEAN          MediaChanged;

  Device = SCSI_DISK_DEVICE_FROM_BLOCK_IO (This);

  Status = ScsiDiskDetectMedia (Device, TRUE, &MediaChanged);
  if (EFI_ERROR (Status)) {
    return Status;
  }

  if (MediaChanged) {
    return EFI_MEDIA_CHANGED;
  }

  if (!Device->Media.MediaPresent) {
    return EFI_NO_MEDIA;
  }

  if (MediaId != Device->Media.MediaId) {
    return EFI_MEDIA_CHANGED;
  }

  if (Device->Media.ReadOnly) {
    return EFI_WRITE_PROTECTED;
  }

  if ((BufferSize % Device->Media.BlockSize) != 0) {
    return EFI_BAD_BUFFER_SIZE;
  }

  if (LBA + (BufferSize / Device->Media.BlockSize) > Device->Media.LastBlock + 1) {
    return EFI_INVALID_PARAMETER;
  }

  return ScsiDiskWriteSectors (Device, MediaId, LBA, Buffer, BufferSize);
}

EFI_STATUS
EFIAPI
ScsiDiskFlushBlocks (
  IN EFI_BLOCK_IO_PROTOCOL  *This
  )
{
  return EFI_SUCCESS;
}

//
// Disk I/O Protocol
//

EFI_STATUS
EFIAPI
ScsiDiskDiskIoReset (
  IN EFI_DISK_IO_PROTOCOL  *This,
  IN UINT64                Offset
  )
{
  EFI_STATUS  Status;
  SCSI_DISK_DEVICE *Device;

  Device = CR (This, SCSI_DISK_DEVICE, DiskIoReset, SCSI_DISK_DEVICE_SIGNATURE);
  Status = Device->BlkIoReset (&Device->BlockIo, TRUE);
  return Status;
}

EFI_STATUS
EFIAPI
ScsiDiskDiskIoRead (
  IN EFI_DISK_IO_PROTOCOL  *This,
  IN UINT32                MediaId,
  IN UINT64                Offset,
  IN UINTN                 BufferSize,
  OUT VOID                *Buffer
  )
{
  SCSI_DISK_DEVICE *Device;
  EFI_BLOCK_IO_MEDIA *Media;
  UINT64            Lba;
  UINTN             BlockSize;
  UINT8            *DataBuffer;
  UINTN             DataSize;
  UINTN             OffsetInBlock;
  EFI_STATUS        Status;

  Device = CR (This, SCSI_DISK_DEVICE, DiskIoRead, SCSI_DISK_DEVICE_SIGNATURE);
  Media = &Device->Media;
  BlockSize = Media->BlockSize;

  Lba = Offset / BlockSize;
  OffsetInBlock = (UINTN)(Offset % BlockSize);

  //
  // Allocate intermediate buffer for block-aligned read
  //
  DataSize = ((OffsetInBlock + BufferSize + BlockSize - 1) / BlockSize) * BlockSize;
  DataBuffer = (UINT8 *)gBS->AllocatePool (DataSize);
  if (DataBuffer == NULL) {
    return EFI_OUT_OF_RESOURCES;
  }

  Status = Device->BlkIoReadBlocks (&Device->BlockIo, MediaId, Lba, DataSize, DataBuffer);
  if (EFI_ERROR (Status)) {
    gBS->FreePool (DataBuffer);
    return Status;
  }

  gBS->CopyMem (Buffer, DataBuffer + OffsetInBlock, BufferSize);
  gBS->FreePool (DataBuffer);

  return EFI_SUCCESS;
}

EFI_STATUS
EFIAPI
ScsiDiskDiskIoWrite (
  IN EFI_DISK_IO_PROTOCOL  *This,
  IN UINT32                MediaId,
  IN UINT64                Offset,
  IN UINTN                 BufferSize,
  IN VOID                 *Buffer
  )
{
  SCSI_DISK_DEVICE *Device;
  EFI_BLOCK_IO_MEDIA *Media;
  UINT64            Lba;
  UINTN             BlockSize;
  UINT8            *DataBuffer;
  UINTN             DataSize;
  UINTN             OffsetInBlock;
  EFI_STATUS        Status;
  BOOLEAN           NeedRead;

  Device = CR (This, SCSI_DISK_DEVICE, DiskIoWrite, SCSI_DISK_DEVICE_SIGNATURE);
  Media = &Device->Media;
  BlockSize = Media->BlockSize;

  Lba = Offset / BlockSize;
  OffsetInBlock = (UINTN)(Offset % BlockSize);

  DataSize = ((OffsetInBlock + BufferSize + BlockSize - 1) / BlockSize) * BlockSize;
  NeedRead = (OffsetInBlock != 0) || (((OffsetInBlock + BufferSize) % BlockSize) != 0);

  DataBuffer = (UINT8 *)gBS->AllocatePool (DataSize);
  if (DataBuffer == NULL) {
    return EFI_OUT_OF_RESOURCES;
  }

  if (NeedRead) {
    Status = Device->BlkIoReadBlocks (&Device->BlockIo, MediaId, Lba, DataSize, DataBuffer);
    if (EFI_ERROR (Status)) {
      gBS->FreePool (DataBuffer);
      return Status;
    }
  } else {
    gBS->ZeroMem (DataBuffer, DataSize);
  }

  gBS->CopyMem (DataBuffer + OffsetInBlock, Buffer, BufferSize);

  Status = Device->BlkIoWriteBlocks (&Device->BlockIo, MediaId, Lba, DataSize, DataBuffer);
  gBS->FreePool (DataBuffer);

  return Status;
}

EFI_STATUS
EFIAPI
ScsiDiskDiskIoFlush (
  IN EFI_DISK_IO_PROTOCOL  *This
  )
{
  return EFI_SUCCESS;
}

//
// Internal SCSI Command Functions
//

STATIC
EFI_STATUS
ScsiDiskReadSectors (
  IN  SCSI_DISK_DEVICE *Device,
  IN  UINT32            MediaId,
  IN  EFI_LBA           Lba,
  OUT VOID             *Buffer,
  IN  UINTN             BufferSize
  )
{
  EFI_STATUS  Status;
  UINT8       Cdb[16];
  UINT32      BlockSize;
  UINTN       Blocks;
  UINTN       TransferSize;
  UINTN       MaxTransfer;
  UINT8       SenseData[SCSI_DESC_SIZE];
  UINT8       HostAdapterStatus;
  UINT8       TargetStatus;
  BOOLEAN     MediaChanged;

  BlockSize = Device->Media.BlockSize;
  MaxTransfer = 0xFFFF * BlockSize;

  while (BufferSize > 0) {
    TransferSize = (BufferSize < MaxTransfer) ? BufferSize : MaxTransfer;
    Blocks = TransferSize / BlockSize;

    if (Lba > 0xFFFFFFFF) {
      //
      // READ16 (0x88): 16-byte CDB
      //
      Cdb[0] = SCSI_OP_READ16;
      Cdb[1]  = 0;
      Cdb[2]  = (UINT8)(Lba >> 56);
      Cdb[3]  = (UINT8)(Lba >> 48);
      Cdb[4]  = (UINT8)(Lba >> 40);
      Cdb[5]  = (UINT8)(Lba >> 32);
      Cdb[6]  = (UINT8)(Lba >> 24);
      Cdb[7]  = (UINT8)(Lba >> 16);
      Cdb[8]  = (UINT8)(Lba >> 8);
      Cdb[9]  = (UINT8)(Lba);
      Cdb[10] = (UINT8)(Blocks >> 24);
      Cdb[11] = (UINT8)(Blocks >> 16);
      Cdb[12] = (UINT8)(Blocks >> 8);
      Cdb[13] = (UINT8)(Blocks);
      Cdb[14] = 0;
      Cdb[15] = 0;

      Status = ScsiDiskExecuteScsiCommand (
                 Device->ScsiIo,
                 SCSI_TIMEOUT_1S,
                 Buffer,
                 SenseData,
                 &HostAdapterStatus,
                 &TargetStatus,
                 NULL,
                 (UINT32 *)&TransferSize,
                 Cdb,
                 16
                 );
    } else {
      //
      // READ10 (0x28): 10-byte CDB
      //
      Cdb[0] = SCSI_OP_READ10;
      Cdb[1] = 0;
      Cdb[2] = (UINT8)(Lba >> 24);
      Cdb[3] = (UINT8)(Lba >> 16);
      Cdb[4] = (UINT8)(Lba >> 8);
      Cdb[5] = (UINT8)(Lba);
      Cdb[6] = 0;
      Cdb[7] = (UINT8)(Blocks >> 8);
      Cdb[8] = (UINT8)(Blocks);
      Cdb[9] = 0;

      Status = ScsiDiskExecuteScsiCommand (
                 Device->ScsiIo,
                 SCSI_TIMEOUT_1S,
                 Buffer,
                 SenseData,
                 &HostAdapterStatus,
                 &TargetStatus,
                 NULL,
                 (UINT32 *)&TransferSize,
                 Cdb,
                 10
                 );
    }

    if (EFI_ERROR (Status) ||
        ScsiDiskIsHostAdapterError (HostAdapterStatus) ||
        ScsiDiskIsTargetError (TargetStatus)) {
      Status = ScsiDiskRetryCommand (
                 Device,
                 &MediaChanged,
                 &HostAdapterStatus,
                 SenseData,
                 SCSI_DISK_MAX_RETRY
                 );
      if (EFI_ERROR (Status)) {
        return Status;
      }
      continue;
    }

    Buffer = (UINT8 *)Buffer + TransferSize;
    Lba += Blocks;
    BufferSize -= TransferSize;
  }

  return EFI_SUCCESS;
}

STATIC
EFI_STATUS
ScsiDiskWriteSectors (
  IN  SCSI_DISK_DEVICE *Device,
  IN  UINT32            MediaId,
  IN  EFI_LBA           Lba,
  IN  VOID             *Buffer,
  IN  UINTN             BufferSize
  )
{
  EFI_STATUS  Status;
  UINT8       Cdb[16];
  UINT32      BlockSize;
  UINTN       Blocks;
  UINTN       TransferSize;
  UINTN       MaxTransfer;
  UINT8       SenseData[SCSI_DESC_SIZE];
  UINT8       HostAdapterStatus;
  UINT8       TargetStatus;
  BOOLEAN     MediaChanged;

  BlockSize = Device->Media.BlockSize;
  MaxTransfer = 0xFFFF * BlockSize;

  while (BufferSize > 0) {
    TransferSize = (BufferSize < MaxTransfer) ? BufferSize : MaxTransfer;
    Blocks = TransferSize / BlockSize;

    if (Lba > 0xFFFFFFFF) {
      //
      // WRITE16 (0x8A): 16-byte CDB
      //
      Cdb[0] = SCSI_OP_WRITE16;
      Cdb[1]  = 0;
      Cdb[2]  = (UINT8)(Lba >> 56);
      Cdb[3]  = (UINT8)(Lba >> 48);
      Cdb[4]  = (UINT8)(Lba >> 40);
      Cdb[5]  = (UINT8)(Lba >> 32);
      Cdb[6]  = (UINT8)(Lba >> 24);
      Cdb[7]  = (UINT8)(Lba >> 16);
      Cdb[8]  = (UINT8)(Lba >> 8);
      Cdb[9]  = (UINT8)(Lba);
      Cdb[10] = (UINT8)(Blocks >> 24);
      Cdb[11] = (UINT8)(Blocks >> 16);
      Cdb[12] = (UINT8)(Blocks >> 8);
      Cdb[13] = (UINT8)(Blocks);
      Cdb[14] = 0;
      Cdb[15] = 0;

      Status = ScsiDiskExecuteScsiCommand (
                 Device->ScsiIo,
                 SCSI_TIMEOUT_1S,
                 Buffer,
                 SenseData,
                 &HostAdapterStatus,
                 &TargetStatus,
                 NULL,
                 (UINT32 *)&TransferSize,
                 Cdb,
                 16
                 );
    } else {
      //
      // WRITE10 (0x2A): 10-byte CDB
      //
      Cdb[0] = SCSI_OP_WRITE10;
      Cdb[1] = 0;
      Cdb[2] = (UINT8)(Lba >> 24);
      Cdb[3] = (UINT8)(Lba >> 16);
      Cdb[4] = (UINT8)(Lba >> 8);
      Cdb[5] = (UINT8)(Lba);
      Cdb[6] = 0;
      Cdb[7] = (UINT8)(Blocks >> 8);
      Cdb[8] = (UINT8)(Blocks);
      Cdb[9] = 0;

      Status = ScsiDiskExecuteScsiCommand (
                 Device->ScsiIo,
                 SCSI_TIMEOUT_1S,
                 Buffer,
                 SenseData,
                 &HostAdapterStatus,
                 &TargetStatus,
                 NULL,
                 (UINT32 *)&TransferSize,
                 Cdb,
                 10
                 );
    }

    if (EFI_ERROR (Status) ||
        ScsiDiskIsHostAdapterError (HostAdapterStatus) ||
        ScsiDiskIsTargetError (TargetStatus)) {
      Status = ScsiDiskRetryCommand (
                 Device,
                 &MediaChanged,
                 &HostAdapterStatus,
                 SenseData,
                 SCSI_DISK_MAX_RETRY
                 );
      if (EFI_ERROR (Status)) {
        return Status;
      }
      continue;
    }

    Buffer = (UINT8 *)Buffer + TransferSize;
    Lba += Blocks;
    BufferSize -= TransferSize;
  }

  return EFI_SUCCESS;
}

//
// Media Detection and Sense Processing
//

EFI_STATUS
ScsiDiskDetectMedia (
  IN  SCSI_DISK_DEVICE *Device,
  IN  BOOLEAN           MustBeMediaPresent,
  OUT BOOLEAN           *MediaChanged
  )
{
  EFI_STATUS  Status;
  VOID       *SenseData;
  UINT8      SenseDataLength;
  UINTN      Result;
  BOOLEAN    RetrySupported;

  *MediaChanged = FALSE;

  //
  // Step 1: TEST UNIT READY
  //
  Status = ScsiDiskTestUnitReady (Device, &RetrySupported, &SenseData, &SenseDataLength);
  if (EFI_ERROR (Status)) {
    //
    // TUR failed -- parse sense to determine cause
    //
    if (ScsiDiskParseSenseData (Device, SenseData, SenseDataLength, &Result)) {
      switch (Result) {
        case SCSI_SENSE_IS_NO_MEDIA:
          Device->MediaPresentFlag = 0;
          Device->Media.MediaPresent = 0;
          return EFI_NO_MEDIA;

        case SCSI_SENSE_IS_MEDIA_CHANGE:
          *MediaChanged = TRUE;
          Device->MediaPresentFlag = 1;
          Device->Media.MediaPresent = 1;
          break;

        case SCSI_SENSE_IS_RESET_BEFORE:
          *MediaChanged = TRUE;
          Device->MediaPresentFlag = 1;
          Device->Media.MediaPresent = 1;
          break;

        default:
          return EFI_DEVICE_ERROR;
      }
    }
  }

  //
  // Step 2: If media appears present, read capacity
  //
  if (Device->MediaPresentFlag) {
    Status = ScsiDiskReadCapacity (Device);
  }

  return Status;
}

EFI_STATUS
ScsiDiskInquiryDevice (
  IN  SCSI_DISK_DEVICE *Device,
  OUT BOOLEAN           *RetrySupported
  )
{
  EFI_STATUS  Status;
  UINT8       SenseData[SCSI_DESC_SIZE];
  UINT8       HostAdapterStatus;
  UINT8       TargetStatus;
  UINT8       InquiryData[36];
  UINT32      DataLength;
  UINT8       VpdPage[VPD_MAX_PAGE_LENGTH];
  UINT32      VpdLength;
  UINTN       Index;

  //
  // Standard INQUIRY (36 bytes)
  //
  DataLength = 36;
  Status = ScsiDiskExecuteInquiry (
             Device->ScsiIo,
             SCSI_TIMEOUT_300MS,
             SenseData,
             &HostAdapterStatus,
             &TargetStatus,
             InquiryData,
             &DataLength
             );
  if (EFI_ERROR (Status)) {
    return Status;
  }

  Device->InquiryDataType = InquiryData[0];

  //
  // Check if VPD pages are supported (byte 3, bit 4)
  //
  if (InquiryData[3] & 0x10) {
    //
    // Request VPD page list (page 0x00)
    //
    VpdLength = VPD_MAX_PAGE_LENGTH;
    Status = ScsiDiskExecuteInquiry16 (
               Device->ScsiIo,
               SCSI_TIMEOUT_300MS,
               SenseData,
               &HostAdapterStatus,
               &TargetStatus,
               VpdPage,
               &VpdLength,
               VPD_SUPPORTED_PAGES
               );
    if (EFI_ERROR (Status)) {
      return Status;
    }

    //
    // Search for page 0xB0 (Block Limits / thin provisioning)
    //
    for (Index = 4; Index < VpdLength; Index++) {
      if (VpdPage[Index] == VPD_BLOCK_LIMITS) {
        //
        // Read VPD page 0xB0
        //
        VpdLength = VPD_BLOCK_LIMITS_LEN;
        Status = ScsiDiskExecuteInquiry16 (
                   Device->ScsiIo,
                   SCSI_TIMEOUT_300MS,
                   SenseData,
                   &HostAdapterStatus,
                   &TargetStatus,
                   VpdPage,
                   &VpdLength,
                   VPD_BLOCK_LIMITS
                   );
        if (!EFI_ERROR (Status) &&
            VpdPage[1] == VPD_BLOCK_LIMITS) {
          Device->UnmapEraseData   = 1;
          Device->UnmapEraseData2  = 1;
          Device->EraseBlockEnabled= 1;
        }
        break;
      }
    }

    Device->InquiryVpdProcessed = 1;
  }

  return EFI_SUCCESS;
}

EFI_STATUS
ScsiDiskTestUnitReady (
  IN  SCSI_DISK_DEVICE *Device,
  OUT BOOLEAN           *RetrySupported,
  OUT VOID             **SenseData,
  OUT UINT8             *SenseDataLength
  )
{
  EFI_STATUS  Status;
  UINT8       SenseBuf[SCSI_DESC_SIZE];
  UINT8       HostAdapterStatus;
  UINT8       TargetStatus;
  UINTN       RetryCount;
  UINTN       Result;

  *RetrySupported = TRUE;

  for (RetryCount = 0; RetryCount < SCSI_DISK_MAX_RETRY; RetryCount++) {
    Status = ScsiDiskExecuteTestUnitReady (
               Device->ScsiIo,
               SCSI_TIMEOUT_300MS,
               SenseBuf,
               &HostAdapterStatus,
               &TargetStatus
               );

    if (!EFI_ERROR (Status)) {
      *SenseData = NULL;
      *SenseDataLength = 0;
      return EFI_SUCCESS;
    }

    if (ScsiDiskIsHostAdapterError (HostAdapterStatus)) {
      return EFI_DEVICE_ERROR;
    }

    if (ScsiDiskIsTargetError (TargetStatus)) {
      *RetrySupported = FALSE;
      continue;
    }

    //
    // CHECK CONDITION -- request sense
    //
    Status = ScsiDiskRequestSense (Device, RetrySupported, SenseData, SenseDataLength);
    if (EFI_ERROR (Status)) {
      return Status;
    }

    if (ScsiDiskParseSenseData (Device, *SenseData, *SenseDataLength, &Result)) {
      switch (Result) {
        case SCSI_SENSE_IS_NO_MEDIA:
          return EFI_NO_MEDIA;

        case SCSI_SENSE_IS_MEDIA_CHANGE:
          return EFI_MEDIA_CHANGED;

        case SCSI_SENSE_IS_RESET_BEFORE:
          continue;

        default:
          if (RetryCount >= SCSI_DISK_MAX_RETRY - 1) {
            return EFI_DEVICE_ERROR;
          }
          continue;
      }
    }
  }

  return EFI_DEVICE_ERROR;
}

BOOLEAN
ScsiDiskParseSenseData (
  IN  SCSI_DISK_DEVICE *Device,
  IN  VOID            *SenseData,
  IN  UINT8            SenseDataLength,
  OUT UINTN            *Result
  )
{
  UINT8 *Sense;
  UINT8 SenseKey;
  UINT8  Asc;
  UINT8  Ascq;

  if (SenseData == NULL || SenseDataLength < 18) {
    return FALSE;
  }

  Sense = (UINT8 *)SenseData;

  //
  // Fixed format sense data:
  //   byte 0:  Valid (bit 7) | Response code
  //   byte 2:  Sense key (bits 0-3)
  //   byte 12: ASC
  //   byte 13: ASCQ
  //
  SenseKey = Sense[2] & 0x0F;
  Asc  = Sense[12];
  Ascq = Sense[13];

  //
  // NOT READY + MEDIUM NOT PRESENT = no media
  //
  if (SenseKey == SCSI_SENSE_NOT_READY && Asc == SCSI_ASC_MEDIUM_NOT_PRESENT) {
    *Result = SCSI_SENSE_IS_NO_MEDIA;
    return TRUE;
  }

  //
  // UNIT ATTENTION + MEDIUM CHANGED = media change
  //
  if (SenseKey == SCSI_SENSE_UNIT_ATTENTION && Asc == SCSI_ASC_MEDIUM_CHANGED) {
    *Result = SCSI_SENSE_IS_MEDIA_CHANGE;
    return TRUE;
  }

  //
  // UNIT ATTENTION + RESET = bus reset detected
  //
  if (SenseKey == SCSI_SENSE_UNIT_ATTENTION && Asc == SCSI_ASC_UNIT_ATTENTION_RESET) {
    *Result = SCSI_SENSE_IS_RESET_BEFORE;
    return TRUE;
  }

  //
  // Generic error
  //
  *Result = SCSI_SENSE_IS_ERROR;
  return TRUE;
}

EFI_STATUS
ScsiDiskRequestSense (
  IN  SCSI_DISK_DEVICE *Device,
  OUT BOOLEAN           *RetrySupported,
  OUT VOID             **SenseData,
  OUT UINT8             *SenseDataLength
  )
{
  EFI_STATUS  Status;
  UINT8       SenseBuf[SCSI_DESC_SIZE];
  UINT32      DataLength;
  UINT8       HostAdapterStatus;
  UINT8       TargetStatus;

  DataLength = SCSI_DESC_SIZE;
  Status = ScsiDiskExecuteRequestSense (
             Device->ScsiIo,
             SCSI_TIMEOUT_10MS,
             SenseBuf,
             &HostAdapterStatus,
             &TargetStatus
             );

  if (!EFI_ERROR (Status) &&
      HostAdapterStatus == 0 &&
      TargetStatus == 0) {
    *SenseData = SenseBuf;
    *SenseDataLength = SCSI_DESC_SIZE;
    return EFI_SUCCESS;
  }

  *SenseData = NULL;
  *SenseDataLength = 0;
  return EFI_DEVICE_ERROR;
}

STATIC
EFI_STATUS
ScsiDiskReadCapacity (
  IN SCSI_DISK_DEVICE *Device
  )
{
  EFI_STATUS  Status;
  UINT8       SenseData[SCSI_DESC_SIZE];
  UINT8       HostAdapterStatus;
  UINT8       TargetStatus;
  UINT8       CapacityData[8];
  UINT8       CapacityData16[32];
  UINT32      DataLength;

  //
  // Try READ CAPACITY (10) first
  //
  DataLength = 8;
  Status = ScsiDiskExecuteReadCapacity10 (
             Device->ScsiIo,
             SCSI_TIMEOUT_300MS,
             SenseData,
             &HostAdapterStatus,
             &TargetStatus,
             CapacityData,
             &DataLength
             );
  if (!EFI_ERROR (Status)) {
    ScsiDiskParseCapacityData (Device, CapacityData, FALSE);
    return EFI_SUCCESS;
  }

  //
  // Fall back to READ CAPACITY (16)
  //
  DataLength = 32;
  Status = ScsiDiskExecuteReadCapacity16 (
             Device->ScsiIo,
             SCSI_TIMEOUT_300MS,
             SenseData,
             &HostAdapterStatus,
             &TargetStatus,
             CapacityData16,
             &DataLength
             );
  if (!EFI_ERROR (Status)) {
    ScsiDiskParseCapacityData (Device, CapacityData16, TRUE);
    return EFI_SUCCESS;
  }

  return Status;
}

STATIC
VOID
ScsiDiskParseCapacityData (
  IN SCSI_DISK_DEVICE *Device,
  IN UINT8            *Data,
  IN BOOLEAN          IsCapacity16
  )
{
  UINT32  BlockSize;
  UINT64  LastBlock;

  if (IsCapacity16) {
    //
    // READ CAPACITY (16): bytes 0-7 = returned LBA, bytes 8-11 = block size
    //
    LastBlock = ((UINT64)Data[0] << 56) |
                ((UINT64)Data[1] << 48) |
                ((UINT64)Data[2] << 40) |
                ((UINT64)Data[3] << 32) |
                ((UINT64)Data[4] << 24) |
                ((UINT64)Data[5] << 16) |
                ((UINT64)Data[6] << 8)  |
                ((UINT64)Data[7]);
    BlockSize = ((UINT32)Data[8]  << 24) |
                ((UINT32)Data[9]  << 16) |
                ((UINT32)Data[10] << 8)  |
                ((UINT32)Data[11]);
  } else {
    //
    // READ CAPACITY (10): bytes 0-3 = returned LBA, bytes 4-7 = block size
    //
    LastBlock = ((UINT32)Data[0] << 24) |
                ((UINT32)Data[1] << 16) |
                ((UINT32)Data[2] << 8)  |
                ((UINT32)Data[3]);
    BlockSize = ((UINT32)Data[4] << 24) |
                ((UINT32)Data[5] << 16) |
                ((UINT32)Data[6] << 8)  |
                ((UINT32)Data[7]);
  }

  Device->Media.BlockSize = BlockSize;
  Device->Media.LastBlock = LastBlock;
  Device->Media.MediaId++;
}

//
// Error Handling
//

BOOLEAN
ScsiDiskIsHostAdapterError (
  IN UINT8  Status
  )
{
  switch (Status) {
    case 0x00:  // EFI_EXT_SCSI_STATUS_HOST_ADAPTER_OK
    case 0x02:  // EFI_EXT_SCSI_STATUS_HOST_ADAPTER_SELECTION_FAIL
      return FALSE;
    default:
      return TRUE;
  }
}

BOOLEAN
ScsiDiskIsTargetError (
  IN UINT8  Status
  )
{
  switch (Status) {
    case 0x00:  // EFI_EXT_SCSI_STATUS_TARGET_GOOD
    case 0x02:  // EFI_EXT_SCSI_STATUS_TARGET_CHECK_CONDITION
      return FALSE;
    default:
      return TRUE;
  }
}

EFI_STATUS
ScsiDiskRetryCommand (
  IN  SCSI_DISK_DEVICE  *Device,
  OUT BOOLEAN           *MediaChanged,
  IN  UINT8             *HostAdapterStatus,
  IN  UINT8             *SenseData,
  IN  UINT8             RetryCount
  )
{
  UINTN   Result;
  UINTN   RetryIndex;

  for (RetryIndex = 0; RetryIndex < RetryCount; RetryIndex++) {
    if (ScsiDiskIsHostAdapterError (*HostAdapterStatus)) {
      Device->ScsiIo->ResetBus (Device->ScsiIo);
    }

    if (ScsiDiskIsTargetError (*HostAdapterStatus)) {
      Device->ScsiIo->ResetDevice (Device->ScsiIo);
      *MediaChanged = TRUE;
    }

    if (ScsiDiskParseSenseData (Device, SenseData, SCSI_DESC_SIZE, &Result)) {
      if (Result == SCSI_SENSE_IS_MEDIA_CHANGE) {
        *MediaChanged = TRUE;
        return EFI_MEDIA_CHANGED;
      }
      if (Result == SCSI_SENSE_IS_NO_MEDIA) {
        return EFI_NO_MEDIA;
      }
    }

    return EFI_SUCCESS;
  }

  return EFI_DEVICE_ERROR;
}

//
// UNMAP / Erase Block
//

EFI_STATUS
ScsiDiskUnmap (
  IN SCSI_DISK_DEVICE  *Device,
  IN EFI_LBA           LBA,
  IN UINTN             RemainingBytes
  )
{
  EFI_STATUS  Status;
  UINT8       Cdb[10];
  UINT8       SenseData[SCSI_DESC_SIZE];
  UINT8       HostAdapterStatus;
  UINT8       TargetStatus;
  UINT8       ParamList[24];
  UINT32      ParamListSize;
  UINT32      BlockSize;
  UINT32      BlockCount;
  BOOLEAN     MediaChanged;

  BlockSize  = Device->Media.BlockSize;
  BlockCount = (UINT32)(RemainingBytes / BlockSize);

  //
  // Build UNMAP parameter list
  //
  ParamListSize = 24;

  //
  // Header (8 bytes)
  //
  ParamList[0]  = 0;
  ParamList[1]  = 0;
  ParamList[2]  = 0;
  ParamList[3]  = 16;                       // Data length
  ParamList[4]  = 0;
  ParamList[5]  = 0;
  ParamList[6]  = 0;
  ParamList[7]  = 12;                       // Block descriptor data length

  //
  // Reserved
  //
  ParamList[8]  = 0;
  ParamList[9]  = 0;
  ParamList[10] = 0;
  ParamList[11] = 0;

  //
  // Block descriptor (16 bytes): LBA + block count
  //
  ParamList[12] = (UINT8)(LBA >> 56);
  ParamList[13] = (UINT8)(LBA >> 48);
  ParamList[14] = (UINT8)(LBA >> 40);
  ParamList[15] = (UINT8)(LBA >> 32);
  ParamList[16] = (UINT8)(LBA >> 24);
  ParamList[17] = (UINT8)(LBA >> 16);
  ParamList[18] = (UINT8)(LBA >> 8);
  ParamList[19] = (UINT8)(LBA);

  ParamList[20] = (UINT8)(BlockCount >> 24);
  ParamList[21] = (UINT8)(BlockCount >> 16);
  ParamList[22] = (UINT8)(BlockCount >> 8);
  ParamList[23] = (UINT8)(BlockCount);

  //
  // Build UNMAP CDB (10 bytes)
  //
  Cdb[0] = SCSI_OP_UNMAP;
  Cdb[1] = 0;
  Cdb[2] = 0;
  Cdb[3] = 0;
  Cdb[4] = 0;
  Cdb[5] = 0;
  Cdb[6] = 0;
  Cdb[7] = 0;
  Cdb[8] = ParamListSize;
  Cdb[9] = 0;

  Status = ScsiDiskExecuteScsiCommand (
             Device->ScsiIo,
             SCSI_TIMEOUT_1S,
             NULL,
             SenseData,
             &HostAdapterStatus,
             &TargetStatus,
             ParamList,
             &ParamListSize,
             Cdb,
             10
             );

  if (EFI_ERROR (Status) ||
      ScsiDiskIsHostAdapterError (HostAdapterStatus) ||
      ScsiDiskIsTargetError (TargetStatus)) {
    Status = ScsiDiskRetryCommand (Device, &MediaChanged, &HostAdapterStatus, SenseData, SCSI_DISK_MAX_RETRY);
  }

  return Status;
}

EFI_STATUS
EFIAPI
ScsiDiskEraseBlocks (
  IN EFI_ERASE_BLOCK_PROTOCOL  *This,
  IN UINT32                    MediaId,
  IN EFI_LBA                   LBA,
  IN UINTN                    *RemainingBytes
  )
{
  SCSI_DISK_DEVICE *Device;
  EFI_STATUS       Status;

  Device = CR (This, SCSI_DISK_DEVICE, EraseBlock, SCSI_DISK_DEVICE_SIGNATURE);

  if (RemainingBytes == NULL || *RemainingBytes == 0) {
    return EFI_INVALID_PARAMETER;
  }

  if (!Device->EraseBlockEnabled) {
    return EFI_UNSUPPORTED;
  }

  Status = ScsiDiskUnmap (Device, LBA, *RemainingBytes);
  if (!EFI_ERROR (Status)) {
    *RemainingBytes = 0;
  }

  return Status;
}

//
// Async UNMAP Notification
//

VOID
EFIAPI
ScsiDiskAsyncUnmapNotification (
  IN EFI_EVENT  Event,
  IN VOID       *Context
  )
{
  SCSI_DISK_ASYNC_UNMAP_NOTIFICATION  *Notification;

  Notification = (SCSI_DISK_ASYNC_UNMAP_NOTIFICATION *)Context;
  if (Notification == NULL) {
    return;
  }

  //
  // Signal completion by signaling the event
  //
  gBS->SignalEvent (Notification->Event);
}

VOID
EFIAPI
ScsiDiskAsyncUnmapTimer (
  IN EFI_EVENT  Event,
  IN VOID       *Context
  )
{
  SCSI_DISK_DEVICE *Device;
  LIST_ENTRY       *Entry;
  SCSI_DISK_ASYNC_UNMAP_NOTIFICATION  *Notification;

  Device = (SCSI_DISK_DEVICE *)Context;
  if (Device == NULL) {
    return;
  }

  //
  // Process pending notifications
  //
  while (!ScsiDiskIsListEmpty (&Device->AsyncUnmapQueue)) {
    Entry = Device->AsyncUnmapQueue.ForwardLink;
    Notification = BASE_CR (Entry, SCSI_DISK_ASYNC_UNMAP_NOTIFICATION, Link);

    ScsiDiskRemoveEntryList (&Notification->Link);

    if (Notification->Buffer != NULL) {
      gBS->FreePool (Notification->Buffer);
    }

    gBS->CloseEvent (Notification->Event);
    gBS->FreePool (Notification);
  }
}

//
// Device Node Lifecycle
//

EFI_STATUS
ScsiDiskCreateDeviceNode (
  OUT SCSI_DISK_DEVICE          **Device,
  IN  EFI_SCSI_IO_SCSI_REQUEST  *Request
  )
{
  SCSI_DISK_DEVICE *NewDevice;

  NewDevice = (SCSI_DISK_DEVICE *)gBS->AllocateZeroPool (SCSI_DISK_DEVICE_SIZE);
  if (NewDevice == NULL) {
    return EFI_OUT_OF_RESOURCES;
  }

  NewDevice->Signature = SCSI_DISK_DEVICE_SIGNATURE;
  ScsiDiskInitializeListHead (&NewDevice->AsyncUnmapQueue);

  *Device = NewDevice;
  return EFI_SUCCESS;
}

VOID
ScsiDiskFreeDeviceMemory (
  IN SCSI_DISK_DEVICE  *Device
  )
{
  LIST_ENTRY  *Entry;
  LIST_ENTRY  *NextEntry;
  SCSI_DISK_ASYNC_UNMAP_NOTIFICATION *Notification;

  if (Device == NULL) {
    return;
  }

  //
  // Cancel pending async UNMAP notifications
  //
  Entry = Device->AsyncUnmapQueue.ForwardLink;
  while (Entry != &Device->AsyncUnmapQueue) {
    NextEntry = Entry->ForwardLink;
    Notification = BASE_CR (Entry, SCSI_DISK_ASYNC_UNMAP_NOTIFICATION, Link);

    gBS->CloseEvent (Notification->Event);
    if (Notification->Buffer != NULL) {
      gBS->FreePool (Notification->Buffer);
    }
    gBS->FreePool (Notification);

    Entry = NextEntry;
  }

  gBS->FreePool (Device);
}

EFI_STATUS
ScsiDiskRegisterExitBootServices (
  IN SCSI_DISK_DEVICE  *Device,
  IN EFI_HANDLE        ControllerHandle
  )
{
  EFI_STATUS  Status;

  Status = gBS->CreateEvent (
             EVT_SIGNAL_EXIT_BOOT_SERVICES,
             TPL_CALLBACK,
             ScsiDiskExitBootServicesCallback,
             Device,
             &Device->ControllerHandle
             );
  return Status;
}

VOID
EFIAPI
ScsiDiskExitBootServicesCallback (
  IN EFI_EVENT  Event,
  IN VOID       *Context
  )
{
  SCSI_DISK_DEVICE *Device;

  Device = (SCSI_DISK_DEVICE *)Context;
  if (Device == NULL) {
    return;
  }

  //
  // Process pending async queue before OS takes over
  //
  while (!ScsiDiskIsListEmpty (&Device->AsyncUnmapQueue)) {
    ScsiDiskAsyncUnmapTimer (Event, Device);
  }
}

//
// Device Capability Checks
//

BOOLEAN
ScsiDiskIsDeviceRemovable (
  IN SCSI_DISK_DEVICE *Device,
  IN EFI_HANDLE        ControllerHandle
  )
{
  //
  // Check removable bit in INQUIRY data byte 1, bit 7
  //
  return (Device->InquiryDataType & 0x80) ? TRUE : FALSE;
}

BOOLEAN
ScsiDiskIsNeedEraseBlock (
  IN SCSI_DISK_DEVICE  *Device,
  IN EFI_LBA           LBA,
  IN UINT32           *RemainingBytes
  )
{
  return Device->EraseBlockEnabled ? TRUE : FALSE;
}

BOOLEAN
ScsiDiskIsEraseBlockProtocolInstalled (
  IN EFI_HANDLE  ControllerHandle
  )
{
  EFI_STATUS  Status;
  VOID        *Interface;

  Status = gBS->OpenProtocol (
             ControllerHandle,
             &gEfiBlockIoProtocolGuid,
             &Interface,
             gImageHandle,
             ControllerHandle,
             EFI_OPEN_PROTOCOL_GET_PROTOCOL
             );
  return (BOOLEAN)(!EFI_ERROR (Status));
}

//
// Component Name Protocol
//

EFI_STATUS
EFIAPI
ScsiDiskComponentNameGetDriverName (
  IN  EFI_COMPONENT_NAME_PROTOCOL  *This,
  IN  CHAR8                        *Language,
  OUT CHAR16                       **DriverName
  )
{
  if (Language == NULL || DriverName == NULL) {
    return EFI_INVALID_PARAMETER;
  }

  *DriverName = L"Scsi Disk Driver";
  return EFI_SUCCESS;
}

EFI_STATUS
EFIAPI
ScsiDiskComponentNameGetControllerName (
  IN  EFI_COMPONENT_NAME_PROTOCOL  *This,
  IN  EFI_HANDLE                   ControllerHandle,
  IN  EFI_HANDLE                   ChildHandle        OPTIONAL,
  IN  CHAR8                        *Language,
  OUT CHAR16                       **ControllerName
  )
{
  if (Language == NULL || ControllerName == NULL) {
    return EFI_INVALID_PARAMETER;
  }

  *ControllerName = L"SCSI Disk Device";
  return EFI_SUCCESS;
}

//
// SCSI Command Execution Helpers
//

EFI_STATUS
ScsiDiskExecuteScsiCommand (
  IN  EFI_SCSI_IO_PROTOCOL  *ScsiIo,
  IN  UINT64                Timeout,
  IN  VOID                  *DataBuffer,
  OUT UINT8                 *SenseData,
  OUT UINT8                 *HostAdapterStatus,
  OUT UINT8                 *TargetStatus,
  IN  VOID                  *Data,
  IN  OUT UINT32            *DataLength,
  IN  UINT8                 *Cdb,
  IN  UINT8                 CdbLength
  )
{
  return ScsiIo->ExecScsiCommand (
              ScsiIo,
              Cdb,
              CdbLength,
              NULL,
              0,
              DataLength,
              DataBuffer,
              *DataLength,
              Timeout,
              HostAdapterStatus,
              TargetStatus
              );
}

EFI_STATUS
ScsiDiskExecuteTestUnitReady (
  IN  EFI_SCSI_IO_PROTOCOL  *ScsiIo,
  IN  UINT64                Timeout,
  OUT UINT8                 *SenseData,
  OUT UINT8                 *HostAdapterStatus,
  OUT UINT8                 *TargetStatus
  )
{
  UINT8   Cdb[6];
  UINT32  DataLength;

  Cdb[0] = SCSI_OP_TEST_UNIT_READY;
  Cdb[1] = 0;
  Cdb[2] = 0;
  Cdb[3] = 0;
  Cdb[4] = 0;
  Cdb[5] = 0;

  DataLength = 0;
  return ScsiIo->ExecScsiCommand (
              ScsiIo,
              Cdb,
              6,
              NULL,
              0,
              &DataLength,
              NULL,
              0,
              Timeout,
              HostAdapterStatus,
              TargetStatus
              );
}

EFI_STATUS
ScsiDiskExecuteRequestSense (
  IN  EFI_SCSI_IO_PROTOCOL  *ScsiIo,
  IN  UINT64                Timeout,
  OUT UINT8                 *SenseData,
  OUT UINT8                 *HostAdapterStatus,
  OUT UINT8                 *TargetStatus
  )
{
  UINT8   Cdb[6];
  UINT32  DataLength;

  Cdb[0] = SCSI_OP_REQUEST_SENSE;
  Cdb[1] = 0;
  Cdb[2] = 0;
  Cdb[3] = 0;
  Cdb[4] = SCSI_DESC_SIZE;
  Cdb[5] = 0;

  DataLength = SCSI_DESC_SIZE;
  return ScsiIo->ExecScsiCommand (
              ScsiIo,
              Cdb,
              6,
              NULL,
              0,
              &DataLength,
              SenseData,
              SCSI_DESC_SIZE,
              Timeout,
              HostAdapterStatus,
              TargetStatus
              );
}

EFI_STATUS
ScsiDiskExecuteInquiry (
  IN  EFI_SCSI_IO_PROTOCOL  *ScsiIo,
  IN  UINT64                Timeout,
  OUT UINT8                 *SenseData,
  OUT UINT8                 *HostAdapterStatus,
  OUT UINT8                 *TargetStatus,
  OUT VOID                  *DataBuffer,
  IN  OUT UINT32            *DataLength
  )
{
  UINT8  Cdb[6];

  Cdb[0] = SCSI_OP_INQUIRY;
  Cdb[1] = 0;
  Cdb[2] = 0;
  Cdb[3] = 0;
  Cdb[4] = 36;
  Cdb[5] = 0;

  return ScsiIo->ExecScsiCommand (
              ScsiIo,
              Cdb,
              6,
              NULL,
              0,
              DataLength,
              DataBuffer,
              *DataLength,
              Timeout,
              HostAdapterStatus,
              TargetStatus
              );
}

EFI_STATUS
ScsiDiskExecuteInquiry16 (
  IN  EFI_SCSI_IO_PROTOCOL  *ScsiIo,
  IN  UINT64                Timeout,
  OUT UINT8                 *SenseData,
  OUT UINT8                 *HostAdapterStatus,
  OUT UINT8                 *TargetStatus,
  OUT VOID                  *DataBuffer,
  IN  OUT UINT32            *DataLength,
  IN  UINT8                 PageCode
  )
{
  UINT8  Cdb[16];

  Cdb[0]  = SCSI_OP_INQUIRY;
  Cdb[1]  = 0x01;           // EVPD = 1
  Cdb[2]  = PageCode;
  Cdb[3]  = 0;
  Cdb[4]  = 0;
  Cdb[5]  = 0;
  Cdb[6]  = 0;
  Cdb[7]  = 0;
  Cdb[8]  = 0;
  Cdb[9]  = 0;
  Cdb[10] = 0;
  Cdb[11] = 0;
  Cdb[12] = 0;
  Cdb[13] = 0;
  Cdb[14] = 0;
  Cdb[15] = 0;

  return ScsiIo->ExecScsiCommand (
              ScsiIo,
              Cdb,
              16,
              NULL,
              0,
              DataLength,
              DataBuffer,
              *DataLength,
              Timeout,
              HostAdapterStatus,
              TargetStatus
              );
}

EFI_STATUS
ScsiDiskExecuteReadCapacity10 (
  IN  EFI_SCSI_IO_PROTOCOL  *ScsiIo,
  IN  UINT64                Timeout,
  OUT UINT8                 *SenseData,
  OUT UINT8                 *HostAdapterStatus,
  OUT UINT8                 *TargetStatus,
  OUT VOID                  *DataBuffer,
  IN  OUT UINT32            *DataLength
  )
{
  UINT8  Cdb[10];

  Cdb[0] = SCSI_OP_READ_CAPACITY10;
  Cdb[1] = 0;
  Cdb[2] = 0;
  Cdb[3] = 0;
  Cdb[4] = 0;
  Cdb[5] = 0;
  Cdb[6] = 0;
  Cdb[7] = 0;
  Cdb[8] = 0;
  Cdb[9] = 0;

  return ScsiIo->ExecScsiCommand (
              ScsiIo,
              Cdb,
              10,
              NULL,
              0,
              DataLength,
              DataBuffer,
              *DataLength,
              Timeout,
              HostAdapterStatus,
              TargetStatus
              );
}

EFI_STATUS
ScsiDiskExecuteReadCapacity16 (
  IN  EFI_SCSI_IO_PROTOCOL  *ScsiIo,
  IN  UINT64                Timeout,
  OUT UINT8                 *SenseData,
  OUT UINT8                 *HostAdapterStatus,
  OUT UINT8                 *TargetStatus,
  OUT VOID                  *DataBuffer,
  IN  OUT UINT32            *DataLength
  )
{
  UINT8  Cdb[16];

  Cdb[0]  = SCSI_OP_READ_CAPACITY16;
  Cdb[1]  = 0;
  Cdb[2]  = 0;
  Cdb[3]  = 0;
  Cdb[4]  = 0;
  Cdb[5]  = 0;
  Cdb[6]  = 0;
  Cdb[7]  = 0;
  Cdb[8]  = 0;
  Cdb[9]  = 0;
  Cdb[10] = 0;
  Cdb[11] = 0;
  Cdb[12] = 0;
  Cdb[13] = 0;
  Cdb[14] = 0;
  Cdb[15] = 0;

  return ScsiIo->ExecScsiCommand (
              ScsiIo,
              Cdb,
              16,
              NULL,
              0,
              DataLength,
              DataBuffer,
              *DataLength,
              Timeout,
              HostAdapterStatus,
              TargetStatus
              );
}

//
// Library Import Wrappers (statically linked replacements)
//

UINTN
ScsiDiskIsListEmpty (
  IN LIST_ENTRY  *ListHead
  )
{
  return (BOOLEAN)(ListHead->ForwardLink == ListHead);
}

VOID
ScsiDiskInitializeListHead (
  IN LIST_ENTRY  *ListHead
  )
{
  ListHead->ForwardLink = ListHead;
  ListHead->BackLink    = ListHead;
}

VOID
ScsiDiskInsertTailList (
  IN LIST_ENTRY  *ListHead,
  IN LIST_ENTRY  *Entry
  )
{
  Entry->ForwardLink       = ListHead;
  Entry->BackLink          = ListHead->BackLink;
  ListHead->BackLink->ForwardLink = Entry;
  ListHead->BackLink       = Entry;
}

VOID
ScsiDiskRemoveEntryList (
  IN LIST_ENTRY  *Entry
  )
{
  Entry->BackLink->ForwardLink = Entry->ForwardLink;
  Entry->ForwardLink->BackLink = Entry->BackLink;
}

UINT16
ScsiDiskSwapBytes16 (
  IN UINT16  Value
  )
{
  return (UINT16)((Value >> 8) | (Value << 8));
}

UINT32
ScsiDiskSwapBytes32 (
  IN UINT32  Value
  )
{
  return ((Value >> 24) & 0x000000FF) |
         ((Value >> 8)  & 0x0000FF00) |
         ((Value << 8)  & 0x00FF0000) |
         ((Value << 24) & 0xFF000000);
}

UINT64
ScsiDiskSwapBytes64 (
  IN UINT64  Value
  )
{
  return ((UINT64)ScsiDiskSwapBytes32 ((UINT32)Value) << 32) |
          (UINT64)ScsiDiskSwapBytes32 ((UINT32)(Value >> 32));
}

UINT64
ScsiDiskReadUnaligned64 (
  IN VOID  *Buffer
  )
{
  UINT64  Value;
  gBS->CopyMem (&Value, Buffer, sizeof (Value));
  return Value;
}

UINTN
ScsiDiskGetAlignmentFromMask (
  IN UINTN  AlignmentMask
  )
{
  UINTN  Alignment;

  if (AlignmentMask == 0) {
    return 0;
  }

  for (Alignment = 1; !(AlignmentMask & Alignment); Alignment <<= 1) {
  }

  return Alignment;
}

BOOLEAN
ScsiDiskIsAlignedPointer (
  IN VOID   *Buffer,
  IN UINTN  Alignment
  )
{
  return (((UINTN)Buffer & (Alignment - 1)) == 0) ? TRUE : FALSE;
}

VOID *
ScsiDiskAllocatePool (
  IN UINTN  AllocationSize
  )
{
  VOID  *Buffer;
  gBS->AllocatePool (EfiBootServicesData, AllocationSize, &Buffer);
  return Buffer;
}

VOID *
ScsiDiskAllocateZeroPool (
  IN UINTN  AllocationSize
  )
{
  VOID  *Buffer;

  Buffer = ScsiDiskAllocatePool (AllocationSize);
  if (Buffer != NULL) {
    gBS->SetMem (Buffer, AllocationSize, 0);
  }

  return Buffer;
}

VOID
ScsiDiskFreePool (
  IN VOID  *Buffer
  )
{
  if (Buffer != NULL) {
    gBS->FreePool (Buffer);
  }
}

VOID
ScsiDiskSafeFreePoolWithCheck (
  VOID
  )
{
  //
  // Placeholder for safe-free logic
  //
}