mirror of https://github.com/acidanthera/audk.git
283 lines
7.0 KiB
C
283 lines
7.0 KiB
C
/** @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;
|
|
}
|