| /** @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; | |
| } |