aboutsummaryrefslogtreecommitdiffstats

We've investigated many methods how we can store data in flash. Now let's look at how we can access this data in our applications.

First we try to do it in a most simple way. The flash image is mapped to the processor memory. So let's just try to work with this memory region via pointers.

Base address of the flash in memory

Final OVMF image has a size of 4MB:

$ du -sh Build/OvmfX64/RELEASE_GCC5/FV/OVMF.fd
4.0M    Build/OvmfX64/RELEASE_GCC5/FV/OVMF.fd

In case of qemu-system-x86_64 it is mapped to the end of 32-bit address space. In this case it means that is mapped to the 0xFFC00000 address:

2^32 = 4Gb = 0x100000000
4MB = 4*1024*1024 = 0x400000

0x100000000 - 0x400000 = 0xFFC00000

If you look in the OvmfPkg/OvmfPkgDefines.fdf.inc file, you'll see:

!if $(FD_SIZE_IN_KB) == 4096
...
DEFINE FW_BASE_ADDRESS   = 0xFFC00000
...
!endif

This is the value that is used for the FD BaseAddress in the OvmfPkg/OvmfPkgX64.fdf:

[FD.OVMF]
BaseAddress   = $(FW_BASE_ADDRESS)
...

You can check dmem output at this address in UEFI shell:

Shell> dmem 0xFFC00000 0x100
Memory Address 00000000FFC00000 100 Bytes
  FFC00000: 00 00 00 00 00 00 00 00-00 00 00 00 00 00 00 00  *................*
  FFC00010: 8D 2B F1 FF 96 76 8B 4C-A9 85 27 47 07 5B 4F 50  *.+...v.L..'G.[OP*
  FFC00020: 00 40 08 00 00 00 00 00-5F 46 56 48 FF FE 04 00  *.@......_FVH....*
  FFC00030: 48 00 AF B8 00 00 00 02-84 00 00 00 00 10 00 00  *H...............*
  FFC00040: 00 00 00 00 00 00 00 00-78 2C F3 AA 7B 94 9A 43  *........x,..{..C*
  FFC00050: A1 80 2E 14 4E C3 77 92-B8 FF 03 00 5A FE 00 00  *....N.w.....Z...*
  FFC00060: 00 00 00 00 AA 55 3C 00-07 00 00 00 00 00 00 00  *.....U<.........*
  FFC00070: 00 00 00 00 00 00 00 00-00 00 00 00 00 00 00 00  *................*
  FFC00080: 00 00 00 00 00 00 00 00-08 00 00 00 04 00 00 00  *................*
  FFC00090: 11 40 70 EB 02 14 D3 11-8E 77 00 A0 C9 69 72 3B  *.@p......w...ir;*
  FFC000A0: 4D 00 54 00 43 00 00 00-01 00 00 00 AA 55 3C 00  *M.T.C........U<.*
  FFC000B0: 03 00 00 00 00 00 00 00-00 00 00 00 00 00 00 00  *................*
  FFC000C0: 00 00 00 00 00 00 00 00-00 00 00 00 00 00 00 00  *................*
  FFC000D0: 28 00 00 00 01 00 00 00-16 D6 47 4B D6 A8 52 45  *(.........GK..RE*
  FFC000E0: 9D 44 CC AD 2E 0F 4C F9-49 00 6E 00 69 00 74 00  *.D....L.I.n.i.t.*
  FFC000F0: 69 00 61 00 6C 00 41 00-74 00 74 00 65 00 6D 00  *i.a.l.A.t.t.e.m.*

And verify that it is indeed OVMF.fd image:

$ hexdump -n 256 Build/OvmfX64/RELEASE_GCC5/FV/OVMF.fd -C
00000000  00 00 00 00 00 00 00 00  00 00 00 00 00 00 00 00  |................|
00000010  8d 2b f1 ff 96 76 8b 4c  a9 85 27 47 07 5b 4f 50  |.+...v.L..'G.[OP|
00000020  00 40 08 00 00 00 00 00  5f 46 56 48 ff fe 04 00  |.@......_FVH....|
00000030  48 00 af b8 00 00 00 02  84 00 00 00 00 10 00 00  |H...............|
00000040  00 00 00 00 00 00 00 00  78 2c f3 aa 7b 94 9a 43  |........x,..{..C|
00000050  a1 80 2e 14 4e c3 77 92  b8 ff 03 00 5a fe 00 00  |....N.w.....Z...|
00000060  00 00 00 00 aa 55 3c 00  07 00 00 00 00 00 00 00  |.....U<.........|
00000070  00 00 00 00 00 00 00 00  00 00 00 00 00 00 00 00  |................|
00000080  00 00 00 00 00 00 00 00  08 00 00 00 04 00 00 00  |................|
00000090  11 40 70 eb 02 14 d3 11  8e 77 00 a0 c9 69 72 3b  |.@p......w...ir;|
000000a0  4d 00 54 00 43 00 00 00  01 00 00 00 aa 55 3c 00  |M.T.C........U<.|
000000b0  03 00 00 00 00 00 00 00  00 00 00 00 00 00 00 00  |................|
000000c0  00 00 00 00 00 00 00 00  00 00 00 00 00 00 00 00  |................|
000000d0  28 00 00 00 01 00 00 00  16 d6 47 4b d6 a8 52 45  |(.........GK..RE|
000000e0  9d 44 cc ad 2e 0f 4c f9  49 00 6e 00 69 00 74 00  |.D....L.I.n.i.t.|
000000f0  69 00 61 00 6c 00 41 00  74 00 74 00 65 00 6d 00  |i.a.l.A.t.t.e.m.|
00000100

OVMF image structure

Let's investigate OVMF image structure. We've already know that is basically consists of OVMF_VARS and OVMF_CODE images concatenated together:

[FD.OVMF]
BaseAddress   = $(FW_BASE_ADDRESS)
Size          = $(FW_SIZE)
ErasePolarity = 1
BlockSize     = $(BLOCK_SIZE)
NumBlocks     = $(FW_BLOCKS)

!include VarStore.fdf.inc          #  = [FD.OVMF_VARS]

$(VARS_SIZE)|$(FVMAIN_SIZE)        #
FV = FVMAIN_COMPACT                #
                                   #  = [FD.OVMF_CODE]
$(SECFV_OFFSET)|$(SECFV_SIZE)      #
FV = SECFV                         #

OVMF_CODE part consists of two Firmware Volumes: SECFV and FVMAIN_COMPACT. SECFV FV is pretty simple and consists only of two modules:

[FV.SECFV]
...
#
# SEC Phase modules
#
# The code in this FV handles the initial firmware startup, and
# decompresses the PEI and DXE FVs which handles the rest of the boot sequence.
#
INF  OvmfPkg/Sec/SecMain.inf
INF  RuleOverride=RESET_VECTOR OvmfPkg/ResetVector/ResetVector.in

While the FVMAIN_COMPACT volume is a Firmware Volume file, that has one Lzma compressed section (*_*_*_LZMA_GUID = EE4E5898-3914-4259-9D6E-DC7BD79403CF), which has 2 Firmware Volume subsections - images for PEI and DXE stages:

[FV.FVMAIN_COMPACT]
...
FILE FV_IMAGE = 9E21FD93-9C72-4c15-8C4B-E77F1DB2D792 {
   SECTION GUIDED EE4E5898-3914-4259-9D6E-DC7BD79403CF PROCESSING_REQUIRED = TRUE {
     #
     # These firmware volumes will have files placed in them uncompressed,
     # and then both firmware volumes will be compressed in a single
     # compression operation in order to achieve better overall compression.
     #
     SECTION FV_IMAGE = PEIFV
     SECTION FV_IMAGE = DXEFV
   }
 }

Here is a picture of an image structure from the OVMF package README.md:

+--------------------------------------- base + 0x400000 (4GB/0x100000000)
| VTF0 (16-bit reset code) and OVMF SEC
| (SECFV, 208KB/0x34000)
+--------------------------------------- base + 0x3cc000
|
| Compressed main firmware image
| (FVMAIN_COMPACT, 3360KB/0x348000)
|
+--------------------------------------- base + 0x84000
| Fault-tolerant write (FTW)
| Spare blocks (264KB/0x42000)
+--------------------------------------- base + 0x42000
| FTW Work block (4KB/0x1000)
+--------------------------------------- base + 0x41000
| Event log area (4KB/0x1000)
+--------------------------------------- base + 0x40000
| Non-volatile variable storage
| area (256KB/0x40000)
+--------------------------------------- base address (0xffc00000)

In case you wonder how the OVMF firmware works with the Lzma compressed FV: the code in SECFV locates FVMAIN_COMPACT Firmware Volume, and decompresses its content into RAM memory. The addresses of PEIFV and DXEFV Firmware Volumes after the decompression are defined by the following PCDs:

[FD.MEMFD]
BaseAddress   = $(MEMFD_BASE_ADDRESS)       # =0x800000 (OvmfPkg/OvmfPkgDefines.fdf.inc)
...

0x020000|0x0E0000
gUefiOvmfPkgTokenSpaceGuid.PcdOvmfPeiMemFvBase|gUefiOvmfPkgTokenSpaceGuid.PcdOvmfPeiMemFvSize
FV = PEIFV

0x100000|0xC00000
gUefiOvmfPkgTokenSpaceGuid.PcdOvmfDxeMemFvBase|gUefiOvmfPkgTokenSpaceGuid.PcdOvmfDxeMemFvSize
FV = DXEFV

If you'll calculate PCD values, you'll get that PEIFV would be placed at addresses 0x820000..0x900000 and DXEFV would be placed at addresses 0x900000..0x1500000.

To verify this, check FV headers with hexdump:

$ hexdump Build/OvmfX64/RELEASE_GCC5/FV/PEIFV.Fv -C | head
00000000  00 00 00 00 00 00 00 00  00 00 00 00 00 00 00 00  |................|
00000010  78 e5 8c 8c 3d 8a 1c 4f  99 35 89 61 85 c3 2d d3  |x...=..O.5.a..-.|
00000020  00 00 0e 00 00 00 00 00  5f 46 56 48 ff fe 07 00  |........_FVH....|
00000030  48 00 4f f6 60 00 00 02  0e 00 00 00 00 00 01 00  |H.O.`...........|
00000040  00 00 00 00 00 00 00 00  ff ff ff ff ff ff ff ff  |................|
00000050  ff ff ff ff ff ff ff ff  f4 aa f0 00 2c 00 00 f8  |............,...|
00000060  9b 07 38 69 03 b5 3d 4e  9d 24 b2 83 37 a2 58 06  |..8i..=N.$..7.X.|
00000070  14 00 00 00 ff ff ff ff  0a cc 45 1b 6a 15 8a 42  |..........E.j..B|
00000080  af 62 49 86 4d a0 e6 e6  b8 aa 02 00 2c 00 00 f8  |.bI.M.......,...|
00000090  14 00 00 19 4f da 3a 9b  56 ae 24 4c 8d ea f0 3b  |....O.:.V.$L...;|
$ hexdump Build/OvmfX64/RELEASE_GCC5/FV/DXEFV.Fv -C | head
00000000  00 00 00 00 00 00 00 00  00 00 00 00 00 00 00 00  |................|
00000010  78 e5 8c 8c 3d 8a 1c 4f  99 35 89 61 85 c3 2d d3  |x...=..O.5.a..-.|
00000020  00 00 c0 00 00 00 00 00  5f 46 56 48 ff fe 04 00  |........_FVH....|
00000030  48 00 ee f4 60 00 00 02  c0 00 00 00 00 00 01 00  |H...`...........|
00000040  00 00 00 00 00 00 00 00  ff ff ff ff ff ff ff ff  |................|
00000050  ff ff ff ff ff ff ff ff  f4 aa f0 00 2c 00 00 f8  |............,...|
00000060  c9 bd b8 7c eb f8 34 4f  aa ea 3e e4 af 65 16 a1  |...|..4O..>..e..|
00000070  14 00 00 00 ff ff ff ff  e7 0e 51 fc dc ff d4 11  |..........Q.....|
00000080  bd 41 00 80 c7 3c 88 81  16 aa 02 00 5c 00 00 f8  |.A...<......\...|
00000090  44 00 00 19 ce 0f 68 9b  6b ad 3a 4f b6 0b f5 98  |D.....h.k.:O....|

And dump OVMF memory with dmem from the UEFI shell:

Shell> dmem 820000 a0
Memory Address 0000000000820000 A0 Bytes
  00820000: 00 00 00 00 00 00 00 00-00 00 00 00 00 00 00 00  *................*
  00820010: 78 E5 8C 8C 3D 8A 1C 4F-99 35 89 61 85 C3 2D D3  *x...=..O.5.a..-.*
  00820020: 00 00 0E 00 00 00 00 00-5F 46 56 48 FF FE 07 00  *........_FVH....*
  00820030: 48 00 4F F6 60 00 00 02-0E 00 00 00 00 00 01 00  *H.O.`...........*
  00820040: 00 00 00 00 00 00 00 00-FF FF FF FF FF FF FF FF  *................*
  00820050: FF FF FF FF FF FF FF FF-F4 AA F0 00 2C 00 00 F8  *............,...*
  00820060: 9B 07 38 69 03 B5 3D 4E-9D 24 B2 83 37 A2 58 06  *..8i..=N.$..7.X.*
  00820070: 14 00 00 00 FF FF FF FF-0A CC 45 1B 6A 15 8A 42  *..........E.j..B*
  00820080: AF 62 49 86 4D A0 E6 E6-B8 AA 02 00 2C 00 00 F8  *.bI.M.......,...*
  00820090: 14 00 00 19 4F DA 3A 9B-56 AE 24 4C 8D EA F0 3B  *....O.:.V.$L...;*
Shell> Address 0000000000900000 A0 Bytes
  00900000: 00 00 00 00 00 00 00 00-00 00 00 00 00 00 00 00  *................*
  00900010: 78 E5 8C 8C 3D 8A 1C 4F-99 35 89 61 85 C3 2D D3  *x...=..O.5.a..-.*
  00900020: 00 00 C0 00 00 00 00 00-5F 46 56 48 FF FE 04 00  *........_FVH....*
  00900030: 48 00 EE F4 60 00 00 02-C0 00 00 00 00 00 01 00  *H...`...........*
  00900040: 00 00 00 00 00 00 00 00-FF FF FF FF FF FF FF FF  *................*
  00900050: FF FF FF FF FF FF FF FF-F4 AA F0 00 2C 00 00 F8  *............,...*
  00900060: C9 BD B8 7C EB F8 34 4F-AA EA 3E E4 AF 65 16 A1  *...|..4O..>..e..*
  00900070: 14 00 00 00 FF FF FF FF-E7 0E 51 FC DC FF D4 11  *..........Q.....*
  00900080: BD 41 00 80 C7 3C 88 81-16 AA 02 00 5C 00 00 F8  *.A...<......\...*
  00900090: 44 00 00 19 CE 0F 68 9B-6B AD 3A 4F B6 0B F5 98  *D.....h.k.:O....*

Create a custom region in OVMF image

Now let's try to add a custom region to the OVMF flash image and manipuate it with our custom application.

The biggest part in the OVMF image is FVMAIN_COMPACT Firmware Volume. In case you forgot the overall structure of the OVMF.fd image is this:

[FD.OVMF]
BaseAddress   = $(FW_BASE_ADDRESS)
Size          = $(FW_SIZE)
ErasePolarity = 1
BlockSize     = $(BLOCK_SIZE)
NumBlocks     = $(FW_BLOCKS)

!include VarStore.fdf.inc

$(VARS_SIZE)|$(FVMAIN_SIZE)
FV = FVMAIN_COMPACT

$(SECFV_OFFSET)|$(SECFV_SIZE)
FV = SECFV

Let's create a DATA region of size 0x1000 with a predefined array. We move the start of FVMAIN_COMPACT a 0x1000 further and put our region in this place:

[FD.OVMF]
BaseAddress   = $(FW_BASE_ADDRESS)
Size          = $(FW_SIZE)
ErasePolarity = 1
BlockSize     = $(BLOCK_SIZE)
NumBlocks     = $(FW_BLOCKS)

!include VarStore.fdf.inc

$(VARS_SIZE)|0x1000
gUefiOvmfPkgTokenSpaceGuid.PcdMyRegionBase
DATA = {
  0xDE, 0xAD, 0xBE, 0xEF
}

($(VARS_SIZE)+0x1000)|($(FVMAIN_SIZE)-0x1000)
FV = FVMAIN_COMPACT

$(SECFV_OFFSET)|$(SECFV_SIZE)
FV = SECFV

Here I've also added a PCD for the base of our region. It would be equal to $(FW_BASE_ADDRESS)+$(VARS_SIZE). I didn't define a PCD for the region size, as we wouldn't need it. Also you can see that it is possible to use mathematical expressions in region parameters definition.

Don't forget to add this new PCD to the OvmfPkg/OvmfPkg.dec file:

[PcdsFixedAtBuild]
...
gUefiOvmfPkgTokenSpaceGuid.PcdMyRegionBase|0x55|UINT32|0xa5a5a5a5

Here I've used a random token 0xa5a5a5a5 and 0x55 as a default value for the PCD.

Create application to manipulate custom region data

Now let's construct our application. It would try to read and write a value at the PcdMyRegionBase address.

UefiLessonsPkg/FlashAccessRaw/FlashAccessRaw.c:

#include <Library/UefiBootServicesTableLib.h>
#include <Library/UefiLib.h>

EFI_STATUS
EFIAPI
UefiMain (
  IN EFI_HANDLE        ImageHandle,
  IN EFI_SYSTEM_TABLE  *SystemTable
  )
{
  volatile UINT32* Val = (UINT32*)(FixedPcdGet32(PcdMyRegionBase));
  Print(L"Val = 0x%08x\n", *Val);
  *Val = 0xCAFECAFE;
  Print(L"Val = 0x%08x\n", *Val);
  return EFI_SUCCESS;
}

UefiLessonsPkg/FlashAccessRaw/FlashAccessRaw.inf:

[Defines]
  INF_VERSION                    = 1.25
  BASE_NAME                      = FlashAccessRaw
  FILE_GUID                      = 475028f8-4219-4615-9a24-c1ccc66f8fee
  MODULE_TYPE                    = UEFI_APPLICATION
  VERSION_STRING                 = 1.0
  ENTRY_POINT                    = UefiMain

[Sources]
  FlashAccessRaw.c

[Packages]
  MdePkg/MdePkg.dec
  OvmfPkg/OvmfPkg.dec                           # need to include this to get access to the PCD

[LibraryClasses]
  UefiApplicationEntryPoint
  UefiLib

[Pcd]
  gUefiOvmfPkgTokenSpaceGuid.PcdMyRegionBase   # necessary PCD

The important thing is that you should't build this app as a part of UefiLessonsPkg/UefiLessonsPkg.dsc via standard build command like we've used to:

[Components]
...
UefiLessonsPkg/FlashAccessRaw/FlashAccessRaw.inf

If you do it, the gUefiOvmfPkgTokenSpaceGuid.PcdMyRegionBase wouldn't get its value from the FDF file. You can verify this if you look at the created AutoGen file (Build/UefiLessonsPkg/RELEASE_GCC5/X64/UefiLessonsPkg/FlashAccessRaw/FlashAccessRaw/DEBUG/AutoGen.h). The PCD value in this case is getting assigned to its default value:

#define _PCD_VALUE_PcdMyRegionBase  0x55U

This is why the FlashAccessRaw application should be compiled as a part of OvmfPkg/OvmfPkgX64.dsc:

[Components]
  ...
  UefiLessonsPkg/FlashAccessRaw/FlashAccessRaw.inf

via OVMF build command:

build --platform=OvmfPkg/OvmfPkgX64.dsc --arch=X64 --buildtarget=RELEASE --tagname=GCC5

In this case PCD would get correct value Build/OvmfX64/RELEASE_GCC5/X64/UefiLessonsPkg/FlashAccessRaw/FlashAccessRaw/DEBUG/AutoGen.h:

#define _PCD_VALUE_PcdMyRegionBase  0xFFC84000U

Now copy correct version to the shared folder:

cp Build/OvmfX64/RELEASE_GCC5/X64/FlashAccessRaw.efi ~/UEFI_disk/

And check it's output:

FS0:\> FlashAccessRaw.efi
Val = 0xEFBEADDE
Val = 0xEFBEADDE

You can see that we've correctly read our value from the flash. The 0xEFBEADDE is just the 0xDEADBEEF backwards. This is just how little-endian architecture interprets UINT32 numbers in memory.

The second important observation from the output it that the memory-mapped flash region is read-only. It is not possible to rewrite it via pointers.