aboutsummaryrefslogtreecommitdiffstats
path: root/Lessons_uncategorized/Lesson_FDF_FV_9/README.md
blob: 9bbaf3b158409751046ed1a060c51aaf30d6fcc3 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
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 <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.