/** @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) { | |
*Ptr = WRITE_BYTE_CMD; | |
*Ptr = OriginalUint8; | |
*Ptr = READ_STATUS_CMD; | |
ProbeUint8 = *Ptr; | |
*Ptr = READ_ARRAY_CMD; | |
if (ProbeUint8 & 0x10 /* programming error */) { | |
DEBUG ((DEBUG_INFO, "QemuFlashDetected => FD behaves as FLASH, write-protected\n")); | |
} else { | |
DEBUG ((DEBUG_INFO, "QemuFlashDetected => FD behaves as FLASH, writable\n")); | |
FlashDetected = TRUE; | |
} | |
} | |
} | |
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; | |
} |