]>
Commit | Line | Data |
---|---|---|
1e57a462 | 1 | /** @file\r |
2 | Implementation of the 6 PEI Ffs (FV) APIs in library form.\r | |
3402aac7 | 3 | \r |
1e57a462 | 4 | This code only knows about a FV if it has a EFI_HOB_TYPE_FV entry in the HOB list\r |
5 | \r | |
6 | Copyright (c) 2008 - 2009, Apple Inc. All rights reserved.<BR>\r | |
3402aac7 | 7 | \r |
878b807a | 8 | SPDX-License-Identifier: BSD-2-Clause-Patent\r |
1e57a462 | 9 | \r |
10 | **/\r | |
11 | \r | |
12 | #include <PrePi.h>\r | |
13 | #include <Library/ExtractGuidedSectionLib.h>\r | |
14 | \r | |
1e57a462 | 15 | #define GET_OCCUPIED_SIZE(ActualSize, Alignment) \\r |
16 | (ActualSize) + (((Alignment) - ((ActualSize) & ((Alignment) - 1))) & ((Alignment) - 1))\r | |
17 | \r | |
1e57a462 | 18 | /**\r |
19 | Returns the highest bit set of the State field\r | |
3402aac7 | 20 | \r |
1e57a462 | 21 | @param ErasePolarity Erase Polarity as defined by EFI_FVB2_ERASE_POLARITY\r |
22 | in the Attributes field.\r | |
23 | @param FfsHeader Pointer to FFS File Header\r | |
3402aac7 | 24 | \r |
1e57a462 | 25 | \r |
26 | @retval the highest bit in the State field\r | |
27 | \r | |
28 | **/\r | |
29 | STATIC\r | |
30 | EFI_FFS_FILE_STATE\r | |
e7108d0e | 31 | GetFileState (\r |
1e57a462 | 32 | IN UINT8 ErasePolarity,\r |
33 | IN EFI_FFS_FILE_HEADER *FfsHeader\r | |
34 | )\r | |
35 | {\r | |
36 | EFI_FFS_FILE_STATE FileState;\r | |
37 | EFI_FFS_FILE_STATE HighestBit;\r | |
38 | \r | |
39 | FileState = FfsHeader->State;\r | |
40 | \r | |
41 | if (ErasePolarity != 0) {\r | |
e7108d0e | 42 | FileState = (EFI_FFS_FILE_STATE) ~FileState;\r |
1e57a462 | 43 | }\r |
44 | \r | |
45 | HighestBit = 0x80;\r | |
46 | while (HighestBit != 0 && (HighestBit & FileState) == 0) {\r | |
47 | HighestBit >>= 1;\r | |
48 | }\r | |
49 | \r | |
50 | return HighestBit;\r | |
3402aac7 | 51 | }\r |
1e57a462 | 52 | \r |
1e57a462 | 53 | /**\r |
54 | Calculates the checksum of the header of a file.\r | |
55 | The header is a zero byte checksum, so zero means header is good\r | |
3402aac7 | 56 | \r |
1e57a462 | 57 | @param FfsHeader Pointer to FFS File Header\r |
3402aac7 | 58 | \r |
1e57a462 | 59 | @retval Checksum of the header\r |
60 | \r | |
61 | **/\r | |
62 | STATIC\r | |
63 | UINT8\r | |
64 | CalculateHeaderChecksum (\r | |
65 | IN EFI_FFS_FILE_HEADER *FileHeader\r | |
66 | )\r | |
67 | {\r | |
e7108d0e MK |
68 | UINT8 *Ptr;\r |
69 | UINTN Index;\r | |
70 | UINT8 Sum;\r | |
3402aac7 | 71 | \r |
1e57a462 | 72 | Sum = 0;\r |
73 | Ptr = (UINT8 *)FileHeader;\r | |
74 | \r | |
e7108d0e | 75 | for (Index = 0; Index < sizeof (EFI_FFS_FILE_HEADER) - 3; Index += 4) {\r |
1e57a462 | 76 | Sum = (UINT8)(Sum + Ptr[Index]);\r |
77 | Sum = (UINT8)(Sum + Ptr[Index+1]);\r | |
78 | Sum = (UINT8)(Sum + Ptr[Index+2]);\r | |
79 | Sum = (UINT8)(Sum + Ptr[Index+3]);\r | |
80 | }\r | |
81 | \r | |
e7108d0e | 82 | for ( ; Index < sizeof (EFI_FFS_FILE_HEADER); Index++) {\r |
1e57a462 | 83 | Sum = (UINT8)(Sum + Ptr[Index]);\r |
84 | }\r | |
3402aac7 | 85 | \r |
1e57a462 | 86 | //\r |
3402aac7 | 87 | // State field (since this indicates the different state of file).\r |
1e57a462 | 88 | //\r |
89 | Sum = (UINT8)(Sum - FileHeader->State);\r | |
90 | //\r | |
91 | // Checksum field of the file is not part of the header checksum.\r | |
92 | //\r | |
93 | Sum = (UINT8)(Sum - FileHeader->IntegrityCheck.Checksum.File);\r | |
94 | \r | |
95 | return Sum;\r | |
96 | }\r | |
97 | \r | |
1e57a462 | 98 | /**\r |
99 | Given a FileHandle return the VolumeHandle\r | |
3402aac7 | 100 | \r |
1e57a462 | 101 | @param FileHandle File handle to look up\r |
102 | @param VolumeHandle Match for FileHandle\r | |
3402aac7 | 103 | \r |
1e57a462 | 104 | @retval TRUE VolumeHandle is valid\r |
105 | \r | |
106 | **/\r | |
107 | STATIC\r | |
108 | BOOLEAN\r | |
109 | EFIAPI\r | |
110 | FileHandleToVolume (\r | |
e7108d0e MK |
111 | IN EFI_PEI_FILE_HANDLE FileHandle,\r |
112 | OUT EFI_PEI_FV_HANDLE *VolumeHandle\r | |
1e57a462 | 113 | )\r |
114 | {\r | |
115 | EFI_FIRMWARE_VOLUME_HEADER *FwVolHeader;\r | |
116 | EFI_PEI_HOB_POINTERS Hob;\r | |
117 | \r | |
118 | Hob.Raw = GetHobList ();\r | |
119 | if (Hob.Raw == NULL) {\r | |
120 | return FALSE;\r | |
121 | }\r | |
3402aac7 | 122 | \r |
1e57a462 | 123 | do {\r |
124 | Hob.Raw = GetNextHob (EFI_HOB_TYPE_FV, Hob.Raw);\r | |
125 | if (Hob.Raw != NULL) {\r | |
126 | FwVolHeader = (EFI_FIRMWARE_VOLUME_HEADER *)(UINTN)(Hob.FirmwareVolume->BaseAddress);\r | |
e7108d0e MK |
127 | if (((UINT64)(UINTN)FileHandle > (UINT64)(UINTN)FwVolHeader) && \\r |
128 | ((UINT64)(UINTN)FileHandle <= ((UINT64)(UINTN)FwVolHeader + FwVolHeader->FvLength - 1)))\r | |
129 | {\r | |
1e57a462 | 130 | *VolumeHandle = (EFI_PEI_FV_HANDLE)FwVolHeader;\r |
131 | return TRUE;\r | |
132 | }\r | |
133 | \r | |
134 | Hob.Raw = GetNextHob (EFI_HOB_TYPE_FV, GET_NEXT_HOB (Hob));\r | |
135 | }\r | |
136 | } while (Hob.Raw != NULL);\r | |
3402aac7 | 137 | \r |
1e57a462 | 138 | return FALSE;\r |
139 | }\r | |
140 | \r | |
1e57a462 | 141 | /**\r |
142 | Given the input file pointer, search for the next matching file in the\r | |
143 | FFS volume as defined by SearchType. The search starts from FileHeader inside\r | |
144 | the Firmware Volume defined by FwVolHeader.\r | |
3402aac7 | 145 | \r |
1e57a462 | 146 | @param FileHandle File handle to look up\r |
147 | @param VolumeHandle Match for FileHandle\r | |
3402aac7 | 148 | \r |
1e57a462 | 149 | \r |
150 | **/\r | |
151 | EFI_STATUS\r | |
152 | FindFileEx (\r | |
e7108d0e MK |
153 | IN CONST EFI_PEI_FV_HANDLE FvHandle,\r |
154 | IN CONST EFI_GUID *FileName OPTIONAL,\r | |
155 | IN EFI_FV_FILETYPE SearchType,\r | |
156 | IN OUT EFI_PEI_FILE_HANDLE *FileHandle\r | |
1e57a462 | 157 | )\r |
158 | {\r | |
e7108d0e MK |
159 | EFI_FIRMWARE_VOLUME_HEADER *FwVolHeader;\r |
160 | EFI_FFS_FILE_HEADER **FileHeader;\r | |
161 | EFI_FFS_FILE_HEADER *FfsFileHeader;\r | |
162 | EFI_FIRMWARE_VOLUME_EXT_HEADER *FwVolExHeaderInfo;\r | |
163 | UINT32 FileLength;\r | |
164 | UINT32 FileOccupiedSize;\r | |
165 | UINT32 FileOffset;\r | |
166 | UINT64 FvLength;\r | |
167 | UINT8 ErasePolarity;\r | |
168 | UINT8 FileState;\r | |
1e57a462 | 169 | \r |
170 | FwVolHeader = (EFI_FIRMWARE_VOLUME_HEADER *)FvHandle;\r | |
171 | FileHeader = (EFI_FFS_FILE_HEADER **)FileHandle;\r | |
172 | \r | |
173 | FvLength = FwVolHeader->FvLength;\r | |
174 | if (FwVolHeader->Attributes & EFI_FVB2_ERASE_POLARITY) {\r | |
175 | ErasePolarity = 1;\r | |
176 | } else {\r | |
177 | ErasePolarity = 0;\r | |
178 | }\r | |
179 | \r | |
180 | //\r | |
181 | // If FileHeader is not specified (NULL) or FileName is not NULL,\r | |
182 | // start with the first file in the firmware volume. Otherwise,\r | |
183 | // start from the FileHeader.\r | |
184 | //\r | |
185 | if ((*FileHeader == NULL) || (FileName != NULL)) {\r | |
186 | FfsFileHeader = (EFI_FFS_FILE_HEADER *)((UINT8 *)FwVolHeader + FwVolHeader->HeaderLength);\r | |
187 | if (FwVolHeader->ExtHeaderOffset != 0) {\r | |
188 | FwVolExHeaderInfo = (EFI_FIRMWARE_VOLUME_EXT_HEADER *)(((UINT8 *)FwVolHeader) + FwVolHeader->ExtHeaderOffset);\r | |
e7108d0e | 189 | FfsFileHeader = (EFI_FFS_FILE_HEADER *)(((UINT8 *)FwVolExHeaderInfo) + FwVolExHeaderInfo->ExtHeaderSize);\r |
1e57a462 | 190 | }\r |
191 | } else {\r | |
192 | //\r | |
193 | // Length is 24 bits wide so mask upper 8 bits\r | |
194 | // FileLength is adjusted to FileOccupiedSize as it is 8 byte aligned.\r | |
195 | //\r | |
e7108d0e | 196 | FileLength = *(UINT32 *)(*FileHeader)->Size & 0x00FFFFFF;\r |
1e57a462 | 197 | FileOccupiedSize = GET_OCCUPIED_SIZE (FileLength, 8);\r |
e7108d0e | 198 | FfsFileHeader = (EFI_FFS_FILE_HEADER *)((UINT8 *)*FileHeader + FileOccupiedSize);\r |
1e57a462 | 199 | }\r |
3402aac7 | 200 | \r |
5a44a766 OM |
201 | // FFS files begin with a header that is aligned on an 8-byte boundary\r |
202 | FfsFileHeader = ALIGN_POINTER (FfsFileHeader, 8);\r | |
203 | \r | |
e7108d0e | 204 | FileOffset = (UINT32)((UINT8 *)FfsFileHeader - (UINT8 *)FwVolHeader);\r |
1e57a462 | 205 | ASSERT (FileOffset <= 0xFFFFFFFF);\r |
206 | \r | |
207 | while (FileOffset < (FvLength - sizeof (EFI_FFS_FILE_HEADER))) {\r | |
208 | //\r | |
3402aac7 | 209 | // Get FileState which is the highest bit of the State\r |
1e57a462 | 210 | //\r |
211 | FileState = GetFileState (ErasePolarity, FfsFileHeader);\r | |
212 | \r | |
213 | switch (FileState) {\r | |
e7108d0e MK |
214 | case EFI_FILE_HEADER_INVALID:\r |
215 | FileOffset += sizeof (EFI_FFS_FILE_HEADER);\r | |
216 | FfsFileHeader = (EFI_FFS_FILE_HEADER *)((UINT8 *)FfsFileHeader + sizeof (EFI_FFS_FILE_HEADER));\r | |
217 | break;\r | |
218 | \r | |
219 | case EFI_FILE_DATA_VALID:\r | |
220 | case EFI_FILE_MARKED_FOR_UPDATE:\r | |
221 | if (CalculateHeaderChecksum (FfsFileHeader) != 0) {\r | |
222 | ASSERT (FALSE);\r | |
223 | *FileHeader = NULL;\r | |
224 | return EFI_NOT_FOUND;\r | |
225 | }\r | |
1e57a462 | 226 | \r |
e7108d0e MK |
227 | FileLength = *(UINT32 *)(FfsFileHeader->Size) & 0x00FFFFFF;\r |
228 | FileOccupiedSize = GET_OCCUPIED_SIZE (FileLength, 8);\r | |
1e57a462 | 229 | \r |
e7108d0e MK |
230 | if (FileName != NULL) {\r |
231 | if (CompareGuid (&FfsFileHeader->Name, (EFI_GUID *)FileName)) {\r | |
232 | *FileHeader = FfsFileHeader;\r | |
233 | return EFI_SUCCESS;\r | |
234 | }\r | |
235 | } else if (((SearchType == FfsFileHeader->Type) || (SearchType == EFI_FV_FILETYPE_ALL)) &&\r | |
236 | (FfsFileHeader->Type != EFI_FV_FILETYPE_FFS_PAD))\r | |
237 | {\r | |
1e57a462 | 238 | *FileHeader = FfsFileHeader;\r |
239 | return EFI_SUCCESS;\r | |
240 | }\r | |
1e57a462 | 241 | \r |
e7108d0e MK |
242 | FileOffset += FileOccupiedSize;\r |
243 | FfsFileHeader = (EFI_FFS_FILE_HEADER *)((UINT8 *)FfsFileHeader + FileOccupiedSize);\r | |
244 | break;\r | |
3402aac7 | 245 | \r |
e7108d0e MK |
246 | case EFI_FILE_DELETED:\r |
247 | FileLength = *(UINT32 *)(FfsFileHeader->Size) & 0x00FFFFFF;\r | |
248 | FileOccupiedSize = GET_OCCUPIED_SIZE (FileLength, 8);\r | |
249 | FileOffset += FileOccupiedSize;\r | |
250 | FfsFileHeader = (EFI_FFS_FILE_HEADER *)((UINT8 *)FfsFileHeader + FileOccupiedSize);\r | |
251 | break;\r | |
1e57a462 | 252 | \r |
e7108d0e MK |
253 | default:\r |
254 | *FileHeader = NULL;\r | |
255 | return EFI_NOT_FOUND;\r | |
3402aac7 | 256 | }\r |
1e57a462 | 257 | }\r |
258 | \r | |
1e57a462 | 259 | *FileHeader = NULL;\r |
3402aac7 | 260 | return EFI_NOT_FOUND;\r |
1e57a462 | 261 | }\r |
262 | \r | |
1e57a462 | 263 | /**\r |
264 | Go through the file to search SectionType section,\r | |
3402aac7 RC |
265 | when meeting an encapsuled section.\r |
266 | \r | |
c673216f MX |
267 | @param SectionType - Filter to find only section of this type.\r |
268 | @param SectionCheckHook - A hook which can check if the section is the target one.\r | |
269 | @param Section - From where to search.\r | |
270 | @param SectionSize - The file size to search.\r | |
271 | @param OutputBuffer - Pointer to the section to search.\r | |
3402aac7 | 272 | \r |
1e57a462 | 273 | @retval EFI_SUCCESS\r |
274 | **/\r | |
275 | EFI_STATUS\r | |
276 | FfsProcessSection (\r | |
277 | IN EFI_SECTION_TYPE SectionType,\r | |
c673216f | 278 | IN FFS_CHECK_SECTION_HOOK SectionCheckHook,\r |
1e57a462 | 279 | IN EFI_COMMON_SECTION_HEADER *Section,\r |
280 | IN UINTN SectionSize,\r | |
281 | OUT VOID **OutputBuffer\r | |
282 | )\r | |
283 | {\r | |
e7108d0e MK |
284 | EFI_STATUS Status;\r |
285 | UINT32 SectionLength;\r | |
286 | UINT32 ParsedLength;\r | |
287 | EFI_COMPRESSION_SECTION *CompressionSection;\r | |
288 | EFI_COMPRESSION_SECTION2 *CompressionSection2;\r | |
289 | UINT32 DstBufferSize;\r | |
290 | VOID *ScratchBuffer;\r | |
291 | UINT32 ScratchBufferSize;\r | |
292 | VOID *DstBuffer;\r | |
293 | UINT16 SectionAttribute;\r | |
294 | UINT32 AuthenticationStatus;\r | |
295 | CHAR8 *CompressedData;\r | |
6777e673 | 296 | UINT32 CompressedDataLength;\r |
c673216f | 297 | BOOLEAN Found;\r |
1e57a462 | 298 | \r |
c673216f | 299 | Found = FALSE;\r |
1e57a462 | 300 | *OutputBuffer = NULL;\r |
301 | ParsedLength = 0;\r | |
302 | Status = EFI_NOT_FOUND;\r | |
303 | while (ParsedLength < SectionSize) {\r | |
4efc8a40 MZ |
304 | if (IS_SECTION2 (Section)) {\r |
305 | ASSERT (SECTION2_SIZE (Section) > 0x00FFFFFF);\r | |
306 | }\r | |
307 | \r | |
1e57a462 | 308 | if (Section->Type == SectionType) {\r |
c673216f MX |
309 | if (SectionCheckHook != NULL) {\r |
310 | Found = SectionCheckHook (Section) == EFI_SUCCESS;\r | |
4efc8a40 | 311 | } else {\r |
c673216f | 312 | Found = TRUE;\r |
4efc8a40 | 313 | }\r |
1e57a462 | 314 | \r |
c673216f MX |
315 | if (Found) {\r |
316 | if (IS_SECTION2 (Section)) {\r | |
317 | *OutputBuffer = (VOID *)((UINT8 *)Section + sizeof (EFI_COMMON_SECTION_HEADER2));\r | |
318 | } else {\r | |
319 | *OutputBuffer = (VOID *)((UINT8 *)Section + sizeof (EFI_COMMON_SECTION_HEADER));\r | |
320 | }\r | |
321 | \r | |
322 | return EFI_SUCCESS;\r | |
323 | } else {\r | |
324 | goto CheckNextSection;\r | |
325 | }\r | |
1e57a462 | 326 | } else if ((Section->Type == EFI_SECTION_COMPRESSION) || (Section->Type == EFI_SECTION_GUID_DEFINED)) {\r |
1e57a462 | 327 | if (Section->Type == EFI_SECTION_COMPRESSION) {\r |
4efc8a40 | 328 | if (IS_SECTION2 (Section)) {\r |
e7108d0e | 329 | CompressionSection2 = (EFI_COMPRESSION_SECTION2 *)Section;\r |
4efc8a40 MZ |
330 | SectionLength = SECTION2_SIZE (Section);\r |
331 | \r | |
332 | if (CompressionSection2->CompressionType != EFI_STANDARD_COMPRESSION) {\r | |
333 | return EFI_UNSUPPORTED;\r | |
334 | }\r | |
335 | \r | |
e7108d0e | 336 | CompressedData = (CHAR8 *)((EFI_COMPRESSION_SECTION2 *)Section + 1);\r |
6777e673 | 337 | CompressedDataLength = SectionLength - sizeof (EFI_COMPRESSION_SECTION2);\r |
4efc8a40 | 338 | } else {\r |
e7108d0e MK |
339 | CompressionSection = (EFI_COMPRESSION_SECTION *)Section;\r |
340 | SectionLength = SECTION_SIZE (Section);\r | |
4efc8a40 MZ |
341 | \r |
342 | if (CompressionSection->CompressionType != EFI_STANDARD_COMPRESSION) {\r | |
343 | return EFI_UNSUPPORTED;\r | |
344 | }\r | |
345 | \r | |
e7108d0e | 346 | CompressedData = (CHAR8 *)((EFI_COMPRESSION_SECTION *)Section + 1);\r |
6777e673 | 347 | CompressedDataLength = SectionLength - sizeof (EFI_COMPRESSION_SECTION);\r |
1e57a462 | 348 | }\r |
349 | \r | |
350 | Status = UefiDecompressGetInfo (\r | |
4efc8a40 MZ |
351 | CompressedData,\r |
352 | CompressedDataLength,\r | |
c1cdcab9 | 353 | &DstBufferSize,\r |
1e57a462 | 354 | &ScratchBufferSize\r |
355 | );\r | |
356 | } else if (Section->Type == EFI_SECTION_GUID_DEFINED) {\r | |
357 | Status = ExtractGuidedSectionGetInfo (\r | |
358 | Section,\r | |
c1cdcab9 | 359 | &DstBufferSize,\r |
1e57a462 | 360 | &ScratchBufferSize,\r |
361 | &SectionAttribute\r | |
362 | );\r | |
363 | }\r | |
3402aac7 | 364 | \r |
1e57a462 | 365 | if (EFI_ERROR (Status)) {\r |
366 | //\r | |
367 | // GetInfo failed\r | |
368 | //\r | |
a1878955 | 369 | DEBUG ((DEBUG_ERROR, "Decompress GetInfo Failed - %r\n", Status));\r |
1e57a462 | 370 | return EFI_NOT_FOUND;\r |
371 | }\r | |
e7108d0e | 372 | \r |
1e57a462 | 373 | //\r |
374 | // Allocate scratch buffer\r | |
375 | //\r | |
376 | ScratchBuffer = (VOID *)(UINTN)AllocatePages (EFI_SIZE_TO_PAGES (ScratchBufferSize));\r | |
377 | if (ScratchBuffer == NULL) {\r | |
378 | return EFI_OUT_OF_RESOURCES;\r | |
379 | }\r | |
e7108d0e | 380 | \r |
1e57a462 | 381 | //\r |
3402aac7 | 382 | // Allocate destination buffer, extra one page for adjustment\r |
1e57a462 | 383 | //\r |
384 | DstBuffer = (VOID *)(UINTN)AllocatePages (EFI_SIZE_TO_PAGES (DstBufferSize) + 1);\r | |
385 | if (DstBuffer == NULL) {\r | |
386 | return EFI_OUT_OF_RESOURCES;\r | |
387 | }\r | |
e7108d0e | 388 | \r |
1e57a462 | 389 | //\r |
390 | // DstBuffer still is one section. Adjust DstBuffer offset, skip EFI section header\r | |
391 | // to make section data at page alignment.\r | |
392 | //\r | |
e7108d0e | 393 | if (IS_SECTION2 (Section)) {\r |
4efc8a40 | 394 | DstBuffer = (UINT8 *)DstBuffer + EFI_PAGE_SIZE - sizeof (EFI_COMMON_SECTION_HEADER2);\r |
e7108d0e | 395 | } else {\r |
4efc8a40 | 396 | DstBuffer = (UINT8 *)DstBuffer + EFI_PAGE_SIZE - sizeof (EFI_COMMON_SECTION_HEADER);\r |
e7108d0e MK |
397 | }\r |
398 | \r | |
1e57a462 | 399 | //\r |
400 | // Call decompress function\r | |
401 | //\r | |
402 | if (Section->Type == EFI_SECTION_COMPRESSION) {\r | |
4efc8a40 | 403 | if (IS_SECTION2 (Section)) {\r |
e7108d0e MK |
404 | CompressedData = (CHAR8 *)((EFI_COMPRESSION_SECTION2 *)Section + 1);\r |
405 | } else {\r | |
406 | CompressedData = (CHAR8 *)((EFI_COMPRESSION_SECTION *)Section + 1);\r | |
4efc8a40 MZ |
407 | }\r |
408 | \r | |
1e57a462 | 409 | Status = UefiDecompress (\r |
e7108d0e MK |
410 | CompressedData,\r |
411 | DstBuffer,\r | |
412 | ScratchBuffer\r | |
413 | );\r | |
1e57a462 | 414 | } else if (Section->Type == EFI_SECTION_GUID_DEFINED) {\r |
415 | Status = ExtractGuidedSectionDecode (\r | |
e7108d0e MK |
416 | Section,\r |
417 | &DstBuffer,\r | |
418 | ScratchBuffer,\r | |
419 | &AuthenticationStatus\r | |
420 | );\r | |
1e57a462 | 421 | }\r |
3402aac7 | 422 | \r |
1e57a462 | 423 | if (EFI_ERROR (Status)) {\r |
424 | //\r | |
425 | // Decompress failed\r | |
426 | //\r | |
a1878955 | 427 | DEBUG ((DEBUG_ERROR, "Decompress Failed - %r\n", Status));\r |
1e57a462 | 428 | return EFI_NOT_FOUND;\r |
429 | } else {\r | |
430 | return FfsProcessSection (\r | |
e7108d0e | 431 | SectionType,\r |
c673216f | 432 | SectionCheckHook,\r |
e7108d0e MK |
433 | DstBuffer,\r |
434 | DstBufferSize,\r | |
435 | OutputBuffer\r | |
436 | );\r | |
437 | }\r | |
1e57a462 | 438 | }\r |
439 | \r | |
c673216f | 440 | CheckNextSection:\r |
4efc8a40 MZ |
441 | if (IS_SECTION2 (Section)) {\r |
442 | SectionLength = SECTION2_SIZE (Section);\r | |
443 | } else {\r | |
444 | SectionLength = SECTION_SIZE (Section);\r | |
445 | }\r | |
e7108d0e | 446 | \r |
1e57a462 | 447 | //\r |
1e57a462 | 448 | // SectionLength is adjusted it is 4 byte aligned.\r |
449 | // Go to the next section\r | |
450 | //\r | |
1e57a462 | 451 | SectionLength = GET_OCCUPIED_SIZE (SectionLength, 4);\r |
452 | ASSERT (SectionLength != 0);\r | |
453 | ParsedLength += SectionLength;\r | |
e7108d0e | 454 | Section = (EFI_COMMON_SECTION_HEADER *)((UINT8 *)Section + SectionLength);\r |
1e57a462 | 455 | }\r |
3402aac7 | 456 | \r |
1e57a462 | 457 | return EFI_NOT_FOUND;\r |
458 | }\r | |
459 | \r | |
1e57a462 | 460 | /**\r |
461 | This service enables discovery sections of a given type within a valid FFS file.\r | |
0826808d | 462 | Caller also can provide a SectionCheckHook to do additional checking.\r |
1e57a462 | 463 | \r |
0826808d MX |
464 | @param SectionType The value of the section type to find.\r |
465 | @param SectionCheckHook A hook which can check if the section is the target one.\r | |
466 | @param FileHandle A pointer to the file header that contains the set of sections to\r | |
1e57a462 | 467 | be searched.\r |
468 | @param SectionData A pointer to the discovered section, if successful.\r | |
469 | \r | |
470 | @retval EFI_SUCCESS The section was found.\r | |
471 | @retval EFI_NOT_FOUND The section was not found.\r | |
472 | \r | |
473 | **/\r | |
474 | EFI_STATUS\r | |
475 | EFIAPI\r | |
0826808d | 476 | FfsFindSectionDataWithHook (\r |
c673216f MX |
477 | IN EFI_SECTION_TYPE SectionType,\r |
478 | IN FFS_CHECK_SECTION_HOOK SectionCheckHook,\r | |
479 | IN EFI_PEI_FILE_HANDLE FileHandle,\r | |
480 | OUT VOID **SectionData\r | |
1e57a462 | 481 | )\r |
482 | {\r | |
e7108d0e MK |
483 | EFI_FFS_FILE_HEADER *FfsFileHeader;\r |
484 | UINT32 FileSize;\r | |
485 | EFI_COMMON_SECTION_HEADER *Section;\r | |
1e57a462 | 486 | \r |
487 | FfsFileHeader = (EFI_FFS_FILE_HEADER *)(FileHandle);\r | |
488 | \r | |
489 | //\r | |
3402aac7 | 490 | // Size is 24 bits wide so mask upper 8 bits.\r |
1e57a462 | 491 | // Does not include FfsFileHeader header size\r |
492 | // FileSize is adjusted to FileOccupiedSize as it is 8 byte aligned.\r | |
493 | //\r | |
e7108d0e MK |
494 | Section = (EFI_COMMON_SECTION_HEADER *)(FfsFileHeader + 1);\r |
495 | FileSize = *(UINT32 *)(FfsFileHeader->Size) & 0x00FFFFFF;\r | |
1e57a462 | 496 | FileSize -= sizeof (EFI_FFS_FILE_HEADER);\r |
497 | \r | |
498 | return FfsProcessSection (\r | |
e7108d0e | 499 | SectionType,\r |
c673216f | 500 | SectionCheckHook,\r |
e7108d0e MK |
501 | Section,\r |
502 | FileSize,\r | |
503 | SectionData\r | |
504 | );\r | |
1e57a462 | 505 | }\r |
506 | \r | |
1e57a462 | 507 | /**\r |
508 | This service enables discovery of additional firmware files.\r | |
509 | \r | |
510 | @param SearchType A filter to find files only of this type.\r | |
511 | @param FwVolHeader Pointer to the firmware volume header of the volume to search.\r | |
512 | This parameter must point to a valid FFS volume.\r | |
513 | @param FileHeader Pointer to the current file from which to begin searching.\r | |
514 | \r | |
515 | @retval EFI_SUCCESS The file was found.\r | |
516 | @retval EFI_NOT_FOUND The file was not found.\r | |
517 | @retval EFI_NOT_FOUND The header checksum was not zero.\r | |
518 | \r | |
519 | **/\r | |
520 | EFI_STATUS\r | |
521 | EFIAPI\r | |
522 | FfsFindNextFile (\r | |
e7108d0e MK |
523 | IN UINT8 SearchType,\r |
524 | IN EFI_PEI_FV_HANDLE VolumeHandle,\r | |
525 | IN OUT EFI_PEI_FILE_HANDLE *FileHandle\r | |
1e57a462 | 526 | )\r |
527 | {\r | |
528 | return FindFileEx (VolumeHandle, NULL, SearchType, FileHandle);\r | |
529 | }\r | |
530 | \r | |
1e57a462 | 531 | /**\r |
532 | This service enables discovery of additional firmware volumes.\r | |
533 | \r | |
534 | @param Instance This instance of the firmware volume to find. The value 0 is the\r | |
535 | Boot Firmware Volume (BFV).\r | |
536 | @param FwVolHeader Pointer to the firmware volume header of the volume to return.\r | |
537 | \r | |
538 | @retval EFI_SUCCESS The volume was found.\r | |
539 | @retval EFI_NOT_FOUND The volume was not found.\r | |
540 | \r | |
541 | **/\r | |
542 | EFI_STATUS\r | |
543 | EFIAPI\r | |
544 | FfsFindNextVolume (\r | |
e7108d0e MK |
545 | IN UINTN Instance,\r |
546 | IN OUT EFI_PEI_FV_HANDLE *VolumeHandle\r | |
1e57a462 | 547 | )\r |
548 | {\r | |
e7108d0e | 549 | EFI_PEI_HOB_POINTERS Hob;\r |
1e57a462 | 550 | \r |
551 | Hob.Raw = GetHobList ();\r | |
552 | if (Hob.Raw == NULL) {\r | |
553 | return EFI_NOT_FOUND;\r | |
554 | }\r | |
3402aac7 | 555 | \r |
1e57a462 | 556 | do {\r |
557 | Hob.Raw = GetNextHob (EFI_HOB_TYPE_FV, Hob.Raw);\r | |
558 | if (Hob.Raw != NULL) {\r | |
559 | if (Instance-- == 0) {\r | |
560 | *VolumeHandle = (EFI_PEI_FV_HANDLE)(UINTN)(Hob.FirmwareVolume->BaseAddress);\r | |
561 | return EFI_SUCCESS;\r | |
562 | }\r | |
563 | \r | |
564 | Hob.Raw = GetNextHob (EFI_HOB_TYPE_FV, GET_NEXT_HOB (Hob));\r | |
565 | }\r | |
566 | } while (Hob.Raw != NULL);\r | |
3402aac7 | 567 | \r |
1e57a462 | 568 | return EFI_NOT_FOUND;\r |
1e57a462 | 569 | }\r |
570 | \r | |
1e57a462 | 571 | /**\r |
572 | Find a file in the volume by name\r | |
3402aac7 | 573 | \r |
1e57a462 | 574 | @param FileName A pointer to the name of the file to\r |
575 | find within the firmware volume.\r | |
576 | \r | |
577 | @param VolumeHandle The firmware volume to search FileHandle\r | |
578 | Upon exit, points to the found file's\r | |
579 | handle or NULL if it could not be found.\r | |
580 | \r | |
581 | @retval EFI_SUCCESS File was found.\r | |
582 | \r | |
583 | @retval EFI_NOT_FOUND File was not found.\r | |
584 | \r | |
585 | @retval EFI_INVALID_PARAMETER VolumeHandle or FileHandle or\r | |
586 | FileName was NULL.\r | |
587 | \r | |
588 | **/\r | |
589 | EFI_STATUS\r | |
3402aac7 | 590 | EFIAPI\r |
1e57a462 | 591 | FfsFindFileByName (\r |
e7108d0e MK |
592 | IN CONST EFI_GUID *FileName,\r |
593 | IN EFI_PEI_FV_HANDLE VolumeHandle,\r | |
594 | OUT EFI_PEI_FILE_HANDLE *FileHandle\r | |
1e57a462 | 595 | )\r |
596 | {\r | |
597 | EFI_STATUS Status;\r | |
e7108d0e | 598 | \r |
1e57a462 | 599 | if ((VolumeHandle == NULL) || (FileName == NULL) || (FileHandle == NULL)) {\r |
600 | return EFI_INVALID_PARAMETER;\r | |
601 | }\r | |
e7108d0e | 602 | \r |
1e57a462 | 603 | Status = FindFileEx (VolumeHandle, FileName, 0, FileHandle);\r |
604 | if (Status == EFI_NOT_FOUND) {\r | |
605 | *FileHandle = NULL;\r | |
606 | }\r | |
e7108d0e | 607 | \r |
1e57a462 | 608 | return Status;\r |
609 | }\r | |
610 | \r | |
1e57a462 | 611 | /**\r |
612 | Get information about the file by name.\r | |
613 | \r | |
614 | @param FileHandle Handle of the file.\r | |
615 | \r | |
616 | @param FileInfo Upon exit, points to the file's\r | |
617 | information.\r | |
618 | \r | |
619 | @retval EFI_SUCCESS File information returned.\r | |
3402aac7 | 620 | \r |
1e57a462 | 621 | @retval EFI_INVALID_PARAMETER If FileHandle does not\r |
622 | represent a valid file.\r | |
3402aac7 | 623 | \r |
1e57a462 | 624 | @retval EFI_INVALID_PARAMETER If FileInfo is NULL.\r |
3402aac7 | 625 | \r |
1e57a462 | 626 | **/\r |
627 | EFI_STATUS\r | |
3402aac7 | 628 | EFIAPI\r |
1e57a462 | 629 | FfsGetFileInfo (\r |
630 | IN EFI_PEI_FILE_HANDLE FileHandle,\r | |
631 | OUT EFI_FV_FILE_INFO *FileInfo\r | |
632 | )\r | |
633 | {\r | |
e7108d0e MK |
634 | UINT8 FileState;\r |
635 | UINT8 ErasePolarity;\r | |
636 | EFI_FFS_FILE_HEADER *FileHeader;\r | |
637 | EFI_PEI_FV_HANDLE VolumeHandle;\r | |
1e57a462 | 638 | \r |
639 | if ((FileHandle == NULL) || (FileInfo == NULL)) {\r | |
640 | return EFI_INVALID_PARAMETER;\r | |
641 | }\r | |
642 | \r | |
643 | VolumeHandle = 0;\r | |
644 | //\r | |
645 | // Retrieve the FirmwareVolume which the file resides in.\r | |
646 | //\r | |
e7108d0e | 647 | if (!FileHandleToVolume (FileHandle, &VolumeHandle)) {\r |
1e57a462 | 648 | return EFI_INVALID_PARAMETER;\r |
649 | }\r | |
650 | \r | |
e7108d0e | 651 | if (((EFI_FIRMWARE_VOLUME_HEADER *)VolumeHandle)->Attributes & EFI_FVB2_ERASE_POLARITY) {\r |
1e57a462 | 652 | ErasePolarity = 1;\r |
653 | } else {\r | |
654 | ErasePolarity = 0;\r | |
655 | }\r | |
656 | \r | |
657 | //\r | |
3402aac7 | 658 | // Get FileState which is the highest bit of the State\r |
1e57a462 | 659 | //\r |
e7108d0e | 660 | FileState = GetFileState (ErasePolarity, (EFI_FFS_FILE_HEADER *)FileHandle);\r |
1e57a462 | 661 | \r |
662 | switch (FileState) {\r | |
663 | case EFI_FILE_DATA_VALID:\r | |
664 | case EFI_FILE_MARKED_FOR_UPDATE:\r | |
3402aac7 | 665 | break;\r |
1e57a462 | 666 | default:\r |
667 | return EFI_INVALID_PARAMETER;\r | |
e7108d0e | 668 | }\r |
1e57a462 | 669 | \r |
670 | FileHeader = (EFI_FFS_FILE_HEADER *)FileHandle;\r | |
e7108d0e MK |
671 | CopyMem (&FileInfo->FileName, &FileHeader->Name, sizeof (EFI_GUID));\r |
672 | FileInfo->FileType = FileHeader->Type;\r | |
1e57a462 | 673 | FileInfo->FileAttributes = FileHeader->Attributes;\r |
e7108d0e MK |
674 | FileInfo->BufferSize = ((*(UINT32 *)FileHeader->Size) & 0x00FFFFFF) - sizeof (EFI_FFS_FILE_HEADER);\r |
675 | FileInfo->Buffer = (FileHeader + 1);\r | |
1e57a462 | 676 | return EFI_SUCCESS;\r |
677 | }\r | |
678 | \r | |
1e57a462 | 679 | /**\r |
680 | Get Information about the volume by name\r | |
681 | \r | |
682 | @param VolumeHandle Handle of the volume.\r | |
683 | \r | |
684 | @param VolumeInfo Upon exit, points to the volume's\r | |
685 | information.\r | |
686 | \r | |
687 | @retval EFI_SUCCESS File information returned.\r | |
3402aac7 | 688 | \r |
1e57a462 | 689 | @retval EFI_INVALID_PARAMETER If FileHandle does not\r |
690 | represent a valid file.\r | |
3402aac7 | 691 | \r |
1e57a462 | 692 | @retval EFI_INVALID_PARAMETER If FileInfo is NULL.\r |
693 | \r | |
694 | **/\r | |
695 | EFI_STATUS\r | |
3402aac7 | 696 | EFIAPI\r |
1e57a462 | 697 | FfsGetVolumeInfo (\r |
698 | IN EFI_PEI_FV_HANDLE VolumeHandle,\r | |
699 | OUT EFI_FV_INFO *VolumeInfo\r | |
700 | )\r | |
701 | {\r | |
e7108d0e MK |
702 | EFI_FIRMWARE_VOLUME_HEADER FwVolHeader;\r |
703 | EFI_FIRMWARE_VOLUME_EXT_HEADER *FwVolExHeaderInfo;\r | |
1e57a462 | 704 | \r |
705 | if (VolumeInfo == NULL) {\r | |
706 | return EFI_INVALID_PARAMETER;\r | |
707 | }\r | |
3402aac7 | 708 | \r |
1e57a462 | 709 | //\r |
3402aac7 RC |
710 | // VolumeHandle may not align at 8 byte,\r |
711 | // but FvLength is UINT64 type, which requires FvHeader align at least 8 byte.\r | |
1e57a462 | 712 | // So, Copy FvHeader into the local FvHeader structure.\r |
713 | //\r | |
714 | CopyMem (&FwVolHeader, VolumeHandle, sizeof (EFI_FIRMWARE_VOLUME_HEADER));\r | |
715 | //\r | |
716 | // Check Fv Image Signature\r | |
717 | //\r | |
718 | if (FwVolHeader.Signature != EFI_FVH_SIGNATURE) {\r | |
719 | return EFI_INVALID_PARAMETER;\r | |
720 | }\r | |
e7108d0e | 721 | \r |
1e57a462 | 722 | VolumeInfo->FvAttributes = FwVolHeader.Attributes;\r |
e7108d0e MK |
723 | VolumeInfo->FvStart = (VOID *)VolumeHandle;\r |
724 | VolumeInfo->FvSize = FwVolHeader.FvLength;\r | |
725 | CopyMem (&VolumeInfo->FvFormat, &FwVolHeader.FileSystemGuid, sizeof (EFI_GUID));\r | |
1e57a462 | 726 | \r |
727 | if (FwVolHeader.ExtHeaderOffset != 0) {\r | |
e7108d0e MK |
728 | FwVolExHeaderInfo = (EFI_FIRMWARE_VOLUME_EXT_HEADER *)(((UINT8 *)VolumeHandle) + FwVolHeader.ExtHeaderOffset);\r |
729 | CopyMem (&VolumeInfo->FvName, &FwVolExHeaderInfo->FvName, sizeof (EFI_GUID));\r | |
1e57a462 | 730 | }\r |
e7108d0e | 731 | \r |
1e57a462 | 732 | return EFI_SUCCESS;\r |
733 | }\r | |
734 | \r | |
1e57a462 | 735 | /**\r |
736 | Search through every FV until you find a file of type FileType\r | |
737 | \r | |
91c38d4e | 738 | @param FileType File handle of a Fv type file.\r |
c6a72cd7 | 739 | @param Volumehandle On success Volume Handle of the match\r |
91c38d4e | 740 | @param FileHandle On success File Handle of the match\r |
3402aac7 | 741 | \r |
91c38d4e RC |
742 | @retval EFI_NOT_FOUND FV image can't be found.\r |
743 | @retval EFI_SUCCESS Successfully found FileType\r | |
1e57a462 | 744 | \r |
745 | **/\r | |
746 | EFI_STATUS\r | |
747 | EFIAPI\r | |
748 | FfsAnyFvFindFirstFile (\r | |
e7108d0e MK |
749 | IN EFI_FV_FILETYPE FileType,\r |
750 | OUT EFI_PEI_FV_HANDLE *VolumeHandle,\r | |
751 | OUT EFI_PEI_FILE_HANDLE *FileHandle\r | |
1e57a462 | 752 | )\r |
753 | {\r | |
e7108d0e MK |
754 | EFI_STATUS Status;\r |
755 | UINTN Instance;\r | |
1e57a462 | 756 | \r |
757 | //\r | |
758 | // Search every FV for the DXE Core\r | |
759 | //\r | |
760 | Instance = 0;\r | |
761 | *FileHandle = NULL;\r | |
762 | \r | |
e7108d0e | 763 | while (1) {\r |
1e57a462 | 764 | Status = FfsFindNextVolume (Instance++, VolumeHandle);\r |
e7108d0e | 765 | if (EFI_ERROR (Status)) {\r |
1e57a462 | 766 | break;\r |
767 | }\r | |
768 | \r | |
769 | Status = FfsFindNextFile (FileType, *VolumeHandle, FileHandle);\r | |
e7108d0e | 770 | if (!EFI_ERROR (Status)) {\r |
1e57a462 | 771 | break;\r |
772 | }\r | |
773 | }\r | |
3402aac7 | 774 | \r |
1e57a462 | 775 | return Status;\r |
776 | }\r | |
777 | \r | |
1e57a462 | 778 | /**\r |
779 | Get Fv image from the FV type file, then add FV & FV2 Hob.\r | |
780 | \r | |
91c38d4e | 781 | @param FileHandle File handle of a Fv type file.\r |
1e57a462 | 782 | \r |
783 | \r | |
91c38d4e RC |
784 | @retval EFI_NOT_FOUND FV image can't be found.\r |
785 | @retval EFI_SUCCESS Successfully to process it.\r | |
1e57a462 | 786 | \r |
787 | **/\r | |
788 | EFI_STATUS\r | |
789 | EFIAPI\r | |
790 | FfsProcessFvFile (\r | |
e7108d0e | 791 | IN EFI_PEI_FILE_HANDLE FvFileHandle\r |
1e57a462 | 792 | )\r |
793 | {\r | |
794 | EFI_STATUS Status;\r | |
795 | EFI_PEI_FV_HANDLE FvImageHandle;\r | |
796 | EFI_FV_INFO FvImageInfo;\r | |
797 | UINT32 FvAlignment;\r | |
798 | VOID *FvBuffer;\r | |
799 | EFI_PEI_HOB_POINTERS HobFv2;\r | |
800 | \r | |
e7108d0e | 801 | FvBuffer = NULL;\r |
1e57a462 | 802 | \r |
803 | //\r | |
804 | // Check if this EFI_FV_FILETYPE_FIRMWARE_VOLUME_IMAGE file has already\r | |
805 | // been extracted.\r | |
806 | //\r | |
807 | HobFv2.Raw = GetHobList ();\r | |
808 | while ((HobFv2.Raw = GetNextHob (EFI_HOB_TYPE_FV2, HobFv2.Raw)) != NULL) {\r | |
809 | if (CompareGuid (&(((EFI_FFS_FILE_HEADER *)FvFileHandle)->Name), &HobFv2.FirmwareVolume2->FileName)) {\r | |
810 | //\r | |
811 | // this FILE has been dispatched, it will not be dispatched again.\r | |
812 | //\r | |
813 | return EFI_SUCCESS;\r | |
814 | }\r | |
e7108d0e | 815 | \r |
1e57a462 | 816 | HobFv2.Raw = GET_NEXT_HOB (HobFv2);\r |
817 | }\r | |
818 | \r | |
819 | //\r | |
820 | // Find FvImage in FvFile\r | |
821 | //\r | |
0826808d | 822 | Status = FfsFindSectionDataWithHook (EFI_SECTION_FIRMWARE_VOLUME_IMAGE, NULL, FvFileHandle, (VOID **)&FvImageHandle);\r |
1e57a462 | 823 | if (EFI_ERROR (Status)) {\r |
824 | return Status;\r | |
825 | }\r | |
3402aac7 | 826 | \r |
1e57a462 | 827 | //\r |
828 | // Collect FvImage Info.\r | |
829 | //\r | |
830 | ZeroMem (&FvImageInfo, sizeof (FvImageInfo));\r | |
831 | Status = FfsGetVolumeInfo (FvImageHandle, &FvImageInfo);\r | |
832 | ASSERT_EFI_ERROR (Status);\r | |
3402aac7 | 833 | \r |
1e57a462 | 834 | //\r |
835 | // FvAlignment must be more than 8 bytes required by FvHeader structure.\r | |
836 | //\r | |
837 | FvAlignment = 1 << ((FvImageInfo.FvAttributes & EFI_FVB2_ALIGNMENT) >> 16);\r | |
838 | if (FvAlignment < 8) {\r | |
839 | FvAlignment = 8;\r | |
840 | }\r | |
3402aac7 | 841 | \r |
1e57a462 | 842 | //\r |
843 | // Check FvImage\r | |
844 | //\r | |
e7108d0e MK |
845 | if ((UINTN)FvImageInfo.FvStart % FvAlignment != 0) {\r |
846 | FvBuffer = AllocateAlignedPages (EFI_SIZE_TO_PAGES ((UINT32)FvImageInfo.FvSize), FvAlignment);\r | |
1e57a462 | 847 | if (FvBuffer == NULL) {\r |
848 | return EFI_OUT_OF_RESOURCES;\r | |
849 | }\r | |
e7108d0e MK |
850 | \r |
851 | CopyMem (FvBuffer, FvImageInfo.FvStart, (UINTN)FvImageInfo.FvSize);\r | |
1e57a462 | 852 | //\r |
853 | // Update FvImageInfo after reload FvImage to new aligned memory\r | |
854 | //\r | |
e7108d0e | 855 | FfsGetVolumeInfo ((EFI_PEI_FV_HANDLE)FvBuffer, &FvImageInfo);\r |
1e57a462 | 856 | }\r |
857 | \r | |
1e57a462 | 858 | //\r |
c6a72cd7 | 859 | // Inform HOB consumer phase, i.e. DXE core, the existence of this FV\r |
1e57a462 | 860 | //\r |
e7108d0e | 861 | BuildFvHob ((EFI_PHYSICAL_ADDRESS)(UINTN)FvImageInfo.FvStart, FvImageInfo.FvSize);\r |
3402aac7 | 862 | \r |
1e57a462 | 863 | //\r |
864 | // Makes the encapsulated volume show up in DXE phase to skip processing of\r | |
865 | // encapsulated file again.\r | |
866 | //\r | |
867 | BuildFv2Hob (\r | |
e7108d0e | 868 | (EFI_PHYSICAL_ADDRESS)(UINTN)FvImageInfo.FvStart,\r |
1e57a462 | 869 | FvImageInfo.FvSize,\r |
870 | &FvImageInfo.FvName,\r | |
871 | &(((EFI_FFS_FILE_HEADER *)FvFileHandle)->Name)\r | |
872 | );\r | |
873 | \r | |
874 | return EFI_SUCCESS;\r | |
875 | }\r |