Hi, Laszlo

Thanks for root cause this issue.

We ever met the same issue when installing windows 8 32bit through cdrom and we 
found:

1) Why 32bit UEFI win8 OS installation fails with some DVD-Only devices is 
because OS would invoke a single read operation to read 65535 sector one time. 
The CDRoms couldn't handle this request and the sense data shows here is 
HARDWARE_ERROR.
2) Why 64bit UEFI win8 OS installation could succeed with 
Combo/Blue-ray/DVD-only devices is because the maximum transfer sector number 
in a single read operation is less than or equal to 512 sector.
3) Combo/BlueRay DVDs we tested are all ok to send 65535 sectors each time.
4) DVD-Only CDRoms we tested all have problem to send 65535 sectors each time 
and cause win8 32 bit installation failure.

That's why r15491 was introduced. The fix depends on real h/w response, that is 
sense data, to do action.

In your findings, it looks like the ScsiDisk driver forgets to handle 
EFI_BAD_BUFFER_SIZE and EFI_EXT_SCSI_STATUS_HOST_ADAPTER_DATA_OVERRUN_UNDERRUN 
status (point (2) in the your commit log) and it causes SectorCount and 
ByteCount mismatch and brings this BSOD.

As the UEFI spec doesn't clearly speak how to combine the return status and 
host status, whether is it possible for 3rd scsi pass thru driver to only 
return EFI_BAD_BUFFER_SIZE and not set the host status like VirtioScsi driver. 
If there is such case, the "Retry" variable may be FALSE rather TRUE and causes 
the new SectorCount adjustment logic bypass.

So could we update the patch as attached? 

Thanks
Feng

-----Original Message-----
From: edk2-devel [mailto:edk2-devel-boun...@lists.01.org] On Behalf Of Laszlo 
Ersek
Sent: Saturday, September 05, 2015 02:22
To: edk2-de...@ml01.01.org
Cc: Tian, Feng
Subject: [edk2] [PATCH] MdeModulePkg: ScsiDiskDxe: adapt SectorCount when 
shortening transfers

The specification of the EFI_EXT_SCSI_PASS_THRU_PROTOCOL.PassThru()
function documents the EFI_BAD_BUFFER_SIZE return status, and the 
EFI_EXT_SCSI_STATUS_HOST_ADAPTER_DATA_OVERRUN_UNDERRUN host adapter status.

These allow an EFI_EXT_SCSI_PASS_THRU_PROTOCOL implementation to request higher 
layers in the stack (in this instance, UefiScsiLib and ScsiDiskDxe) to break up 
the transfer into smaller pieces.

These conditions percolate up the stack correctly: the retry loops in 
ScsiDiskDxe's ScsiDiskReadSectors() and ScsiDiskWriteSectors() functions 
correctly and transparently update the transfer size (ByteCount), accommodating 
any shortening requested by lower levels of the stack. After the loop -- if the 
request ultimately succeeds -- SectorCount is even recalculated from the final 
ByteCount, to see how many sectors the outer loop should advance.

However, the inner (ie. retry) loops both have the same error: when the 
underlying protocols request the transfer to be shortened, the decrease in 
transfer size (ie. ByteCount) should immediately be reflected in SectorCount. 
Otherwise the sector count encoded in the CDB will exceed the transfer size, 
which is a permanent error.

This issue has been witnessed while booting

  en_windows_8.1_pro_n_vl_with_update_x86_dvd_6051127.iso

on the 32-bit build of OVMF, from a virtio-scsi CD-ROM:

(1) "cdboot.efi" correctly requested (from far atop) a long read:

    Timeout=940000000
    CdbLength=10
    DataDir=Read
    InTransferLength=134215680
    OutTransferLength=0
    SenseDataLength=108

    Cdb: 28 00 00 00 25 DD 00 FF FF 00
         ^     ^^^^^^^^^^^    ^^^^^
         |          |            |
         |          |     number of 2KB sectors to read,
         |          |     corresponding to 2048 * 65535 = 134215680 bytes
         |          |     (see InTransferLength above)
         |          |
         |   LBA to read from
         |
      READ (10)

(2) In turn, the EFI_EXT_SCSI_PASS_THRU_PROTOCOL.PassThru() function
    provided by "OvmfPkg/VirtioScsiDxe/VirtioScsi.c" asked for the request
    to be shortened:

    InTransferLength=16776704
    OutTransferLength=16776704
    SenseDataLength=0
    HostAdapterStatus=EFI_EXT_SCSI_STATUS_HOST_ADAPTER_DATA_OVERRUN_UNDERRUN
    TargetStatus=0
    Status=EFI_BAD_BUFFER_SIZE

(3) Then ScsiDiskReadSectors() in
    "MdeModulePkg/Bus/Scsi/ScsiDiskDxe/ScsiDisk.c" retried the request
    with correctly shortened transfer length, but incorrectly unchanged
    sector count:

    Timeout=940000000
    CdbLength=10
    DataDir=Read
    InTransferLength=16776704 <--- updated as requested
    OutTransferLength=0
    SenseDataLength=108

    Cdb: 28 00 00 00 25 DD 00 FF FF 00
         ^     ^^^^^^^^^^^    ^^^^^
         |          |            |
         |          |       not changed!
         |          |
         |   LBA to read from
         |
      READ (10)

(4) Since 65535 sectors of 2KB each wouldn't fit in a buffer of approx.
    16MB, QEMU's virtio-scsi controller unconditionally rejected this
    request with VIRTIO_SCSI_S_OVERRUN, which VirtioScsiDxe then mapped
    to:

    InTransferLength=16776704
    OutTransferLength=0
    SenseDataLength=0
    HostAdapterStatus=EFI_EXT_SCSI_STATUS_HOST_ADAPTER_DATA_OVERRUN_UNDERRUN
    TargetStatus=0
    Status=EFI_DEVICE_ERROR

(5) After two more tries of the same, ScsiDiskDxe passed up the error,
    which ultimately caused "cdboot.efi" to BSOD.

Many thanks to Larry Cleeton from Microsoft for helping debug "cdboot.efi".

Cc: Larry Cleeton <larry.clee...@microsoft.com>
Cc: Feng Tian <feng.t...@intel.com>
Contributed-under: TianoCore Contribution Agreement 1.0
Signed-off-by: Laszlo Ersek <ler...@redhat.com>
---

Notes:
    For ease of review, this patch has been formatted with the
    "--function-context" git option. Although including extra context can
    lead to unnecessary conflicts during patch application, I expect zero
    churn in this file in the meantime, so that aspect of
    "--function-context" should not be an issue. (In any case I intend to
    commit the patch myself, if Feng approves the patch.)

 MdeModulePkg/Bus/Scsi/ScsiDiskDxe/ScsiDisk.c | 41 ++++++++++++++++++++
 1 file changed, 41 insertions(+)

diff --git a/MdeModulePkg/Bus/Scsi/ScsiDiskDxe/ScsiDisk.c 
b/MdeModulePkg/Bus/Scsi/ScsiDiskDxe/ScsiDisk.c
index e7abe54..8070ccc 100644
--- a/MdeModulePkg/Bus/Scsi/ScsiDiskDxe/ScsiDisk.c
+++ b/MdeModulePkg/Bus/Scsi/ScsiDiskDxe/ScsiDisk.c
@@ -1779,144 +1779,164 @@ 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.
 
 **/
@@ -1924,143 +1944,164 @@ 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;
 }
 
 
 /**
   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().
 **/
--
1.8.3.1

_______________________________________________
edk2-devel mailing list
edk2-devel@lists.01.org
https://lists.01.org/mailman/listinfo/edk2-devel
_______________________________________________
edk2-devel mailing list
edk2-devel@lists.01.org
https://lists.01.org/mailman/listinfo/edk2-devel

Reply via email to