From 1e77f067e6db3ef7c5045e973ee9f3d1761fb11f Mon Sep 17 00:00:00 2001 From: Konstantin Aladyshev Date: Wed, 27 Jul 2022 16:26:07 +0300 Subject: Add lesson about memory-mapped flash Signed-off-by: Konstantin Aladyshev --- Lessons_uncategorized/Lesson_FDF_FV_9/README.md | 363 ++++++++++++++++++++++++ 1 file changed, 363 insertions(+) create mode 100644 Lessons_uncategorized/Lesson_FDF_FV_9/README.md (limited to 'Lessons_uncategorized/Lesson_FDF_FV_9/README.md') diff --git a/Lessons_uncategorized/Lesson_FDF_FV_9/README.md b/Lessons_uncategorized/Lesson_FDF_FV_9/README.md new file mode 100644 index 0000000..9bbaf3b --- /dev/null +++ b/Lessons_uncategorized/Lesson_FDF_FV_9/README.md @@ -0,0 +1,363 @@ +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](https://github.com/tianocore/edk2/blob/master/OvmfPkg/OvmfPkgX64.fdf): +``` ++--------------------------------------- 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 +#include + +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. + -- cgit v1.2.3-18-g5258