/** @file
OVMF support for QEMU system firmware flash device
Copyright (c) 2009 - 2013, Intel Corporation. All rights reserved.
SPDX-License-Identifier: BSD-2-Clause-Patent
**/
#include
#include
#include
#include
#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;
}