/** @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) { 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); *Ptr = BLOCK_ERASE_CMD; *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; }