Flash can be write-protected in qemu (which is usually the case for code). In case the variable store flash block is configured read-only ovmf wouldn't be able to store EFI variables there, so not setting up fvb in that case (and fallhack to emulation) is the better option. It'll avoid problems later due to flash writes failing. The patch tries to write back the original value read earlier, so flash content doesn't change in case the write succeeds. But the status we read back after the attempt to write will tell us whenever flash is writable or not. Signed-off-by: Gerd Hoffmann <kraxel@redhat.com>
		
			
				
	
	
		
			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;
 | |
| }
 |