text: /** @file
    OVMF support for QEMU system firmware flash device
  
    Copyright (c) 2009 - 2013, Intel Corporation. All rights reserved.<BR>
  
    SPDX-License-Identifier: BSD-2-Clause-Patent
  
  **/
  
  #include <Library/BaseMemoryLib.h>
  #include <Library/DebugLib.h>
  #include <Library/MemEncryptSevLib.h>
  #include <Library/PcdLib.h>
  
  #include "QemuFlash.h"
  
  #define WRITE_BYTE_CMD           0x10
  #define BLOCK_ERASE_CMD          0x20
  #define CLEAR_STATUS_CMD         0x50
  #define READ_STATUS_CMD          0x70
  #define READ_DEVID_CMD           0x90
  #define BLOCK_ERASE_CONFIRM_CMD  0xd0
  #define READ_ARRAY_CMD           0xff
  
  #define CLEARED_ARRAY_STATUS  0x00
  
  
  UINT8 *mFlashBase;
  
  STATIC UINTN       mFdBlockSize = 0;
  STATIC UINTN       mFdBlockCount = 0;
  
  STATIC
  volatile UINT8*
  QemuFlashPtr (
    IN        EFI_LBA                             Lba,
    IN        UINTN                               Offset
    )
  {
    return mFlashBase + ((UINTN)Lba * mFdBlockSize) + Offset;
  }
  
  
  /**
    Determines if the QEMU flash memory device is present.
  
    @retval FALSE   The QEMU flash device is not present.
    @retval TRUE    The QEMU flash device is present.
  
  **/
  STATIC
  BOOLEAN
  QemuFlashDetected (
    VOID
    )
  {
    BOOLEAN  FlashDetected;
    volatile UINT8  *Ptr;
  
    UINTN Offset;
    UINT8 OriginalUint8;
    UINT8 ProbeUint8;
  
    FlashDetected = FALSE;
    Ptr = QemuFlashPtr (0, 0);
  
    for (Offset = 0; Offset < mFdBlockSize; Offset++) {
      Ptr = QemuFlashPtr (0, Offset);
      ProbeUint8 = *Ptr;
      if (ProbeUint8 != CLEAR_STATUS_CMD &&
          ProbeUint8 != READ_STATUS_CMD &&
          ProbeUint8 != CLEARED_ARRAY_STATUS) {
        break;
      }
    }
  
    if (Offset >= mFdBlockSize) {
      DEBUG ((DEBUG_INFO, "QEMU Flash: Failed to find probe location\n"));
      return FALSE;
    }
  
    DEBUG ((DEBUG_INFO, "QEMU Flash: Attempting flash detection at %p\n", Ptr));
  
    if (MemEncryptSevEsIsEnabled ()) {
      //
      // When SEV-ES is enabled, the check below can result in an infinite
      // loop with respect to a nested page fault. When the memslot is mapped
      // read-only, the nested page table entry is read-only. The check below
      // will cause a nested page fault that cannot be emulated, causing
      // the instruction to retried over and over. For SEV-ES, acknowledge that
      // the FD appears as ROM and not as FLASH, but report FLASH anyway because
      // FLASH behavior can be simulated using VMGEXIT.
      //
      DEBUG ((DEBUG_INFO,
        "QEMU Flash: SEV-ES enabled, assuming FD behaves as FLASH\n"));
      return TRUE;
    }
  
    OriginalUint8 = *Ptr;
    *Ptr = CLEAR_STATUS_CMD;
    ProbeUint8 = *Ptr;
    if (OriginalUint8 != CLEAR_STATUS_CMD &&
        ProbeUint8 == CLEAR_STATUS_CMD) {
      DEBUG ((DEBUG_INFO, "QemuFlashDetected => FD behaves as RAM\n"));
      *Ptr = OriginalUint8;
    } else {
      *Ptr = READ_STATUS_CMD;
      ProbeUint8 = *Ptr;
      if (ProbeUint8 == OriginalUint8) {
        DEBUG ((DEBUG_INFO, "QemuFlashDetected => FD behaves as ROM\n"));
      } else if (ProbeUint8 == READ_STATUS_CMD) {
        DEBUG ((DEBUG_INFO, "QemuFlashDetected => FD behaves as RAM\n"));
        *Ptr = OriginalUint8;
      } else if (ProbeUint8 == CLEARED_ARRAY_STATUS) {
        DEBUG ((DEBUG_INFO, "QemuFlashDetected => FD behaves as FLASH\n"));
        FlashDetected = TRUE;
        *Ptr = READ_ARRAY_CMD;
      }
    }
  
    DEBUG ((DEBUG_INFO, "QemuFlashDetected => %a\n",
                        FlashDetected ? "Yes" : "No"));
    return FlashDetected;
  }
  
  
  /**
    Read from QEMU Flash
  
    @param[in] Lba      The starting logical block index to read from.
    @param[in] Offset   Offset into the block at which to begin reading.
    @param[in] NumBytes On input, indicates the requested read size. On
                        output, indicates the actual number of bytes read
    @param[in] Buffer   Pointer to the buffer to read into.
  
  **/
  EFI_STATUS
  QemuFlashRead (
    IN        EFI_LBA                              Lba,
    IN        UINTN                                Offset,
    IN        UINTN                                *NumBytes,
    IN        UINT8                                *Buffer
    )
  {
    UINT8  *Ptr;
  
    //
    // Only write to the first 64k. We don't bother saving the FTW Spare
    // block into the flash memory.
    //
    if (Lba >= mFdBlockCount) {
      return EFI_INVALID_PARAMETER;
    }
  
    //
    // Get flash address
    //
    Ptr = (UINT8*) QemuFlashPtr (Lba, Offset);
  
    CopyMem (Buffer, Ptr, *NumBytes);
  
    return EFI_SUCCESS;
  }
  
  
  /**
    Write to QEMU Flash
  
    @param[in] Lba      The starting logical block index to write to.
    @param[in] Offset   Offset into the block at which to begin writing.
    @param[in] NumBytes On input, indicates the requested write size. On
                        output, indicates the actual number of bytes written
    @param[in] Buffer   Pointer to the data to write.
  
  **/
  EFI_STATUS
  QemuFlashWrite (
    IN        EFI_LBA                             Lba,
    IN        UINTN                               Offset,
    IN        UINTN                               *NumBytes,
    IN        UINT8                               *Buffer
    )
  {
    volatile UINT8  *Ptr;
    UINTN           Loop;
  
    //
    // Only write to the first 64k. We don't bother saving the FTW Spare
    // block into the flash memory.
    //
    if (Lba >= mFdBlockCount) {
      return EFI_INVALID_PARAMETER;
    }
  
    //
    // Program flash
    //
    Ptr = QemuFlashPtr (Lba, Offset);
    for (Loop = 0; Loop < *NumBytes; Loop++) {
      QemuFlashPtrWrite (Ptr, WRITE_BYTE_CMD);
      QemuFlashPtrWrite (Ptr, Buffer[Loop]);
  
      Ptr++;
    }
  
    //
    // Restore flash to read mode
    //
    if (*NumBytes > 0) {
      QemuFlashPtrWrite (Ptr - 1, READ_ARRAY_CMD);
    }
  
    return EFI_SUCCESS;
  }
  
  
  /**
    Erase a QEMU Flash block
  
    @param Lba    The logical block index to erase.
  
  **/
  EFI_STATUS
  QemuFlashEraseBlock (
    IN   EFI_LBA      Lba
    )
  {
    volatile UINT8  *Ptr;
  
    if (Lba >= mFdBlockCount) {
      return EFI_INVALID_PARAMETER;
    }
  
    Ptr = QemuFlashPtr (Lba, 0);
    QemuFlashPtrWrite (Ptr, BLOCK_ERASE_CMD);
    QemuFlashPtrWrite (Ptr, BLOCK_ERASE_CONFIRM_CMD);
    return EFI_SUCCESS;
  }
  
  
  /**
    Initializes QEMU flash memory support
  
    @retval EFI_WRITE_PROTECTED   The QEMU flash device is not present.
    @retval EFI_SUCCESS           The QEMU flash device is supported.
  
  **/
  EFI_STATUS
  QemuFlashInitialize (
    VOID
    )
  {
    mFlashBase = (UINT8*)(UINTN) PcdGet32 (PcdOvmfFdBaseAddress);
    mFdBlockSize = PcdGet32 (PcdOvmfFirmwareBlockSize);
    ASSERT(PcdGet32 (PcdOvmfFirmwareFdSize) % mFdBlockSize == 0);
    mFdBlockCount = PcdGet32 (PcdOvmfFirmwareFdSize) / mFdBlockSize;
  
    //
    // execute module specific hooks before probing the flash
    //
    QemuFlashBeforeProbe (
      (EFI_PHYSICAL_ADDRESS)(UINTN) mFlashBase,
      mFdBlockSize,
      mFdBlockCount
      );
  
    if (!QemuFlashDetected ()) {
      ASSERT (!FeaturePcdGet (PcdSmmSmramRequire));
      return EFI_WRITE_PROTECTED;
    }
  
    return EFI_SUCCESS;
  }
  
  