3 * Copyright (c) 2012-2014, ARM Limited. All rights reserved.
5 * This program and the accompanying materials
6 * are licensed and made available under the terms and conditions of the BSD License
7 * which accompanies this distribution. The full text of the license may be found at
8 * http://opensource.org/licenses/bsd-license.php
10 * THE PROGRAM IS DISTRIBUTED UNDER THE BSD LICENSE ON AN "AS IS" BASIS,
11 * WITHOUT WARRANTIES OR REPRESENTATIONS OF ANY KIND, EITHER EXPRESS OR IMPLIED.
15 #include "BootMonFsInternal.h"
17 // Clear a file's image description on storage media:
18 // UEFI allows you to seek past the end of a file, a subsequent write will grow
19 // the file. It does not specify how space between the former end of the file
20 // and the beginning of the write should be filled. It's therefore possible that
21 // BootMonFs metadata, that comes after the end of a file, could be left there
22 // and wrongly detected by BootMonFsImageInBlock.
25 InvalidateImageDescription (
26 IN BOOTMON_FS_FILE
*File
29 EFI_DISK_IO_PROTOCOL
*DiskIo
;
30 EFI_BLOCK_IO_PROTOCOL
*BlockIo
;
35 UINT64 DescriptionAddress
;
37 DiskIo
= File
->Instance
->DiskIo
;
38 BlockIo
= File
->Instance
->BlockIo
;
39 MediaId
= BlockIo
->Media
->MediaId
;
40 BlockSize
= BlockIo
->Media
->BlockSize
;
42 DescriptionAddress
= (File
->HwDescription
.BlockEnd
* BlockSize
)
43 - sizeof (HW_IMAGE_DESCRIPTION
);
45 Buffer
= AllocateZeroPool (sizeof (HW_IMAGE_DESCRIPTION
));
47 Status
= DiskIo
->WriteDisk (DiskIo
,
50 sizeof (HW_IMAGE_DESCRIPTION
),
59 // Flush file data that will extend the file's length. Update and, if necessary,
60 // move the image description.
61 // We need to pass the file's starting position on media (FileStart), because
62 // if the file hasn't been flushed before its Description->BlockStart won't
63 // have been initialised.
64 // FileStart must be aligned to the media's block size.
65 // Note that this function uses DiskIo to flush, so call BlockIo->FlushBlocks()
70 IN BOOTMON_FS_FILE
*File
,
71 IN BOOTMON_FS_FILE_REGION
*Region
,
72 IN UINT64 NewFileSize
,
77 EFI_DISK_IO_PROTOCOL
*DiskIo
;
79 HW_IMAGE_DESCRIPTION
*Description
;
81 DiskIo
= File
->Instance
->DiskIo
;
83 BlockSize
= File
->Instance
->BlockIo
->Media
->BlockSize
;
85 ASSERT (FileStart
% BlockSize
== 0);
87 // Only invalidate the Image Description of files that have already been
89 if (File
->HwDescription
.RegionCount
> 0) {
90 Status
= InvalidateImageDescription (File
);
91 ASSERT_EFI_ERROR (Status
);
95 // Update File Description
97 Description
= &File
->HwDescription
;
98 Description
->Attributes
= 1;
99 Description
->BlockStart
= FileStart
/ BlockSize
;
100 Description
->BlockEnd
= Description
->BlockStart
+ (NewFileSize
/ BlockSize
);
101 Description
->Footer
.FooterSignature1
= HW_IMAGE_FOOTER_SIGNATURE_1
;
102 Description
->Footer
.FooterSignature2
= HW_IMAGE_FOOTER_SIGNATURE_2
;
104 Description
->Footer
.Version
= HW_IMAGE_FOOTER_VERSION
;
105 Description
->Footer
.Offset
= HW_IMAGE_FOOTER_OFFSET
;
107 Description
->Footer
.Version
= HW_IMAGE_FOOTER_VERSION2
;
108 Description
->Footer
.Offset
= HW_IMAGE_FOOTER_OFFSET2
;
110 Description
->RegionCount
= 1;
111 Description
->Region
[0].Checksum
= 0;
112 Description
->Region
[0].Offset
= Description
->BlockStart
* BlockSize
;
113 Description
->Region
[0].Size
= NewFileSize
- sizeof (HW_IMAGE_DESCRIPTION
);
115 Status
= BootMonFsComputeFooterChecksum (Description
);
116 if (EFI_ERROR (Status
)) {
120 // Write the new file data
121 Status
= DiskIo
->WriteDisk (
123 File
->Instance
->Media
->MediaId
,
124 FileStart
+ Region
->Offset
,
128 ASSERT_EFI_ERROR (Status
);
130 // Round the file size up to the nearest block size
131 if ((NewFileSize
% BlockSize
) > 0) {
132 NewFileSize
+= BlockSize
- (NewFileSize
% BlockSize
);
134 // Update the file description on the media
135 Status
= DiskIo
->WriteDisk (
137 File
->Instance
->Media
->MediaId
,
138 (FileStart
+ NewFileSize
) - sizeof (HW_IMAGE_DESCRIPTION
),
139 sizeof (HW_IMAGE_DESCRIPTION
),
142 ASSERT_EFI_ERROR (Status
);
148 BootMonFsFileNeedFlush (
149 IN BOOTMON_FS_FILE
*File
152 return !IsListEmpty (&File
->RegionToFlushLink
);
155 // Find a space on media for a file that has not yet been flushed to disk.
156 // Just returns the first space that's big enough.
157 // This function could easily be adapted to:
158 // - Find space for moving an existing file that has outgrown its space
159 // (We do not currently move files, just return EFI_VOLUME_FULL)
160 // - Find space for a fragment of a file that has outgrown its space
161 // (We do not currently fragment files - it's not clear whether fragmentation
162 // is actually part of BootMonFs as there is no spec)
163 // - Be more clever about finding space (choosing the largest or smallest
166 // File - the new (not yet flushed) file for which we need to find space.
167 // FileStart - the position on media of the file (in bytes).
170 BootMonFsFindSpaceForNewFile (
171 IN BOOTMON_FS_FILE
*File
,
172 OUT UINT64
*FileStart
175 LIST_ENTRY
*FileLink
;
176 BOOTMON_FS_FILE
*RootFile
;
177 BOOTMON_FS_FILE
*FileEntry
;
180 EFI_BLOCK_IO_MEDIA
*Media
;
182 Media
= File
->Instance
->BlockIo
->Media
;
183 BlockSize
= Media
->BlockSize
;
184 RootFile
= File
->Instance
->RootFile
;
186 if (IsListEmpty (&RootFile
->Link
)) {
190 // This function must only be called for file which has not been flushed into
192 ASSERT (File
->HwDescription
.RegionCount
== 0);
194 // Find out how big the file will be
195 FileSize
= BootMonFsGetImageLength (File
);
196 // Add the file header to the file
197 FileSize
+= sizeof (HW_IMAGE_DESCRIPTION
);
200 // Go through all the files in the list
201 for (FileLink
= GetFirstNode (&RootFile
->Link
);
202 !IsNull (&RootFile
->Link
, FileLink
);
203 FileLink
= GetNextNode (&RootFile
->Link
, FileLink
)
206 FileEntry
= BOOTMON_FS_FILE_FROM_LINK_THIS (FileLink
);
207 // If the free space preceding the file is big enough to contain the new
209 if (((FileEntry
->HwDescription
.BlockStart
* BlockSize
) - *FileStart
)
211 // The file list must be in disk-order
212 RemoveEntryList (&File
->Link
);
213 File
->Link
.BackLink
= FileLink
->BackLink
;
214 File
->Link
.ForwardLink
= FileLink
;
215 FileLink
->BackLink
->ForwardLink
= &File
->Link
;
216 FileLink
->BackLink
= &File
->Link
;
220 *FileStart
= (FileEntry
->HwDescription
.BlockEnd
+ 1) * BlockSize
;
223 // See if there's space after the last file
224 if ((((Media
->LastBlock
+ 1) * BlockSize
) - *FileStart
) >= FileSize
) {
227 return EFI_VOLUME_FULL
;
231 // Free the resources in the file's Region list.
235 IN BOOTMON_FS_FILE
*File
238 LIST_ENTRY
*RegionToFlushLink
;
239 BOOTMON_FS_FILE_REGION
*Region
;
241 RegionToFlushLink
= GetFirstNode (&File
->RegionToFlushLink
);
242 while (!IsNull (&File
->RegionToFlushLink
, RegionToFlushLink
)) {
243 // Repeatedly remove the first node from the list and free its resources.
244 Region
= (BOOTMON_FS_FILE_REGION
*) RegionToFlushLink
;
245 RemoveEntryList (RegionToFlushLink
);
246 FreePool (Region
->Buffer
);
249 RegionToFlushLink
= GetFirstNode (&File
->RegionToFlushLink
);
256 IN EFI_FILE_PROTOCOL
*This
260 BOOTMON_FS_INSTANCE
*Instance
;
261 LIST_ENTRY
*RegionToFlushLink
;
262 BOOTMON_FS_FILE
*File
;
263 BOOTMON_FS_FILE
*NextFile
;
264 BOOTMON_FS_FILE_REGION
*Region
;
265 LIST_ENTRY
*FileLink
;
266 UINTN CurrentPhysicalSize
;
273 UINT64 EndOfAppendSpace
;
275 EFI_DISK_IO_PROTOCOL
*DiskIo
;
276 EFI_BLOCK_IO_PROTOCOL
*BlockIo
;
278 Status
= EFI_SUCCESS
;
281 File
= BOOTMON_FS_FILE_FROM_FILE_THIS (This
);
283 return EFI_INVALID_PARAMETER
;
286 // Check if the file needs to be flushed
287 if (!BootMonFsFileNeedFlush (File
)) {
291 Instance
= File
->Instance
;
292 BlockIo
= Instance
->BlockIo
;
293 DiskIo
= Instance
->DiskIo
;
294 BlockSize
= BlockIo
->Media
->BlockSize
;
296 // If the file doesn't exist then find a space for it
297 if (File
->HwDescription
.RegionCount
== 0) {
298 Status
= BootMonFsFindSpaceForNewFile (File
, &FileStart
);
299 // FileStart has changed so we need to recompute RegionEnd
300 if (EFI_ERROR (Status
)) {
304 FileStart
= File
->HwDescription
.BlockStart
* BlockSize
;
307 // FileEnd is the NOR address of the end of the file's data
308 FileEnd
= FileStart
+ BootMonFsGetImageLength (File
);
310 for (RegionToFlushLink
= GetFirstNode (&File
->RegionToFlushLink
);
311 !IsNull (&File
->RegionToFlushLink
, RegionToFlushLink
);
312 RegionToFlushLink
= GetNextNode (&File
->RegionToFlushLink
, RegionToFlushLink
)
315 Region
= (BOOTMON_FS_FILE_REGION
*)RegionToFlushLink
;
317 // RegionStart and RegionEnd are the the intended NOR address of the
318 // start and end of the region
319 RegionStart
= FileStart
+ Region
->Offset
;
320 RegionEnd
= RegionStart
+ Region
->Size
;
322 if (RegionEnd
< FileEnd
) {
323 // Handle regions representing edits to existing portions of the file
324 // Write the region data straight into the file
325 Status
= DiskIo
->WriteDisk (DiskIo
,
326 BlockIo
->Media
->MediaId
,
331 if (EFI_ERROR (Status
)) {
335 // Handle regions representing appends to the file
337 // Note: Since seeking past the end of the file with SetPosition() is
338 // valid, it's possible there will be a gap between the current end of
339 // the file and the beginning of the new region. Since the UEFI spec
340 // says nothing about this case (except "a subsequent write would grow
341 // the file"), we just leave garbage in the gap.
343 // Check if there is space to append the new region
345 NewFileSize
= (RegionEnd
- FileStart
) + sizeof (HW_IMAGE_DESCRIPTION
);
346 CurrentPhysicalSize
= BootMonFsGetPhysicalSize (File
);
347 if (NewFileSize
<= CurrentPhysicalSize
) {
350 // Get the File Description for the next file
351 FileLink
= GetNextNode (&Instance
->RootFile
->Link
, &File
->Link
);
352 if (!IsNull (&Instance
->RootFile
->Link
, FileLink
)) {
353 NextFile
= BOOTMON_FS_FILE_FROM_LINK_THIS (FileLink
);
355 // If there is space between the beginning of the current file and the
356 // beginning of the next file then use it
357 EndOfAppendSpace
= NextFile
->HwDescription
.BlockStart
* BlockSize
;
359 // We are flushing the last file.
360 EndOfAppendSpace
= (BlockIo
->Media
->LastBlock
+ 1) * BlockSize
;
362 if (EndOfAppendSpace
- FileStart
>= NewFileSize
) {
367 if (HasSpace
== TRUE
) {
368 Status
= FlushAppendRegion (File
, Region
, NewFileSize
, FileStart
);
369 if (EFI_ERROR (Status
)) {
373 // There isn't a space for the file.
374 // Options here are to move the file or fragment it. However as files
375 // may represent boot images at fixed positions, these options will
376 // break booting if the bootloader doesn't use BootMonFs to find the
379 return EFI_VOLUME_FULL
;
384 FreeFileRegions (File
);
386 // Flush DiskIo Buffers (see UEFI Spec 12.7 - DiskIo buffers are flushed by
387 // calling FlushBlocks on the same device's BlockIo).
388 BlockIo
->FlushBlocks (BlockIo
);
394 Closes a file on the Nor Flash FS volume.
396 @param This The EFI_FILE_PROTOCOL to close.
398 @return Always returns EFI_SUCCESS.
404 IN EFI_FILE_PROTOCOL
*This
407 // Flush the file if needed
412 // Create a new instance of BOOTMON_FS_FILE.
413 // Uses BootMonFsCreateFile to
417 IN BOOTMON_FS_INSTANCE
*Instance
,
418 IN CHAR8
* AsciiFileName
,
419 OUT BOOTMON_FS_FILE
**NewHandle
423 BOOTMON_FS_FILE
*File
;
425 Status
= BootMonFsCreateFile (Instance
, &File
);
426 if (EFI_ERROR (Status
)) {
430 // Remove the leading '\\'
431 if (*AsciiFileName
== '\\') {
436 CopyMem (File
->HwDescription
.Footer
.Filename
, AsciiFileName
, MAX_NAME_LENGTH
);
438 // Add the file to list of files of the File System
439 InsertHeadList (&Instance
->RootFile
->Link
, &File
->Link
);
446 Opens a file on the Nor Flash FS volume
448 Calls BootMonFsGetFileFromAsciiFilename to search the list of tracked files.
450 @param This The EFI_FILE_PROTOCOL parent handle.
451 @param NewHandle Double-pointer to the newly created protocol.
452 @param FileName The name of the image/metadata on flash
453 @param OpenMode Read,write,append etc
458 Run out of space to keep track of the allocated structures
460 Unable to locate the volume associated with the parent file handle
462 Filename wasn't found on flash
469 IN EFI_FILE_PROTOCOL
*This
,
470 OUT EFI_FILE_PROTOCOL
**NewHandle
,
476 BOOTMON_FS_FILE
*Directory
;
477 BOOTMON_FS_FILE
*File
;
478 BOOTMON_FS_INSTANCE
*Instance
;
479 CHAR8
* AsciiFileName
;
482 if ((FileName
== NULL
) || (NewHandle
== NULL
)) {
483 return EFI_INVALID_PARAMETER
;
486 // The only valid modes are read, read/write, and read/write/create
487 if (!(OpenMode
& EFI_FILE_MODE_READ
) || ((OpenMode
& EFI_FILE_MODE_CREATE
) && !(OpenMode
& EFI_FILE_MODE_WRITE
))) {
488 return EFI_INVALID_PARAMETER
;
491 Directory
= BOOTMON_FS_FILE_FROM_FILE_THIS (This
);
492 if (Directory
== NULL
) {
493 return EFI_DEVICE_ERROR
;
496 Instance
= Directory
->Instance
;
498 // If the instance has not been initialized it yet then do it ...
499 if (!Instance
->Initialized
) {
500 Status
= BootMonFsInitialize (Instance
);
501 if (EFI_ERROR (Status
)) {
506 // BootMonFs interface requires ASCII filenames
507 AsciiFileName
= AllocatePool ((StrLen (FileName
) + 1) * sizeof (CHAR8
));
508 if (AsciiFileName
== NULL
) {
509 return EFI_OUT_OF_RESOURCES
;
511 UnicodeStrToAsciiStr (FileName
, AsciiFileName
);
513 if ((AsciiStrCmp (AsciiFileName
, "\\") == 0) ||
514 (AsciiStrCmp (AsciiFileName
, "/") == 0) ||
515 (AsciiStrCmp (AsciiFileName
, "") == 0) ||
516 (AsciiStrCmp (AsciiFileName
, ".") == 0))
519 // Opening '/', '\', '.', or the NULL pathname is trying to open the root directory
522 *NewHandle
= &Instance
->RootFile
->File
;
523 Instance
->RootFile
->Position
= 0;
524 Status
= EFI_SUCCESS
;
527 // Open or Create a regular file
530 // Check if the file already exists
531 Status
= BootMonGetFileFromAsciiFileName (Instance
, AsciiFileName
, &File
);
532 if (Status
== EFI_NOT_FOUND
) {
533 // The file doesn't exist.
534 if (OpenMode
& EFI_FILE_MODE_CREATE
) {
535 // If the file does not exist but is required then create it.
536 if (Attributes
& EFI_FILE_DIRECTORY
) {
537 // BootMonFS doesn't support subdirectories
538 Status
= EFI_UNSUPPORTED
;
541 Status
= CreateNewFile (Instance
, AsciiFileName
, &File
);
542 if (!EFI_ERROR (Status
)) {
543 File
->OpenMode
= OpenMode
;
544 *NewHandle
= &File
->File
;
549 } else if (Status
== EFI_SUCCESS
) {
551 File
->OpenMode
= OpenMode
;
552 *NewHandle
= &File
->File
;
557 FreePool (AsciiFileName
);
562 // Delete() for the root directory's EFI_FILE_PROTOCOL instance
565 BootMonFsDeleteFail (
566 IN EFI_FILE_PROTOCOL
*This
570 // You can't delete the root directory
571 return EFI_WARN_DELETE_FAILURE
;
576 IN EFI_FILE_PROTOCOL
*This
580 BOOTMON_FS_FILE
*File
;
581 LIST_ENTRY
*RegionToFlushLink
;
582 BOOTMON_FS_FILE_REGION
*Region
;
583 HW_IMAGE_DESCRIPTION
*Description
;
584 EFI_BLOCK_IO_PROTOCOL
*BlockIo
;
587 File
= BOOTMON_FS_FILE_FROM_FILE_THIS (This
);
589 return EFI_DEVICE_ERROR
;
592 Status
= EFI_SUCCESS
;
594 if (BootMonFsFileNeedFlush (File
)) {
595 // Free the entries from the Buffer List
596 RegionToFlushLink
= GetFirstNode (&File
->RegionToFlushLink
);
598 Region
= (BOOTMON_FS_FILE_REGION
*)RegionToFlushLink
;
601 RegionToFlushLink
= RemoveEntryList (RegionToFlushLink
);
604 FreePool (Region
->Buffer
);
606 } while (!IsListEmpty (&File
->RegionToFlushLink
));
609 // If (RegionCount is greater than 0) then the file already exists
610 if (File
->HwDescription
.RegionCount
> 0) {
611 Description
= &File
->HwDescription
;
612 BlockIo
= File
->Instance
->BlockIo
;
614 // Create an empty buffer
615 EmptyBuffer
= AllocateZeroPool (BlockIo
->Media
->BlockSize
);
616 if (EmptyBuffer
== NULL
) {
618 return EFI_OUT_OF_RESOURCES
;
621 // Invalidate the last Block
622 Status
= BlockIo
->WriteBlocks (BlockIo
, BlockIo
->Media
->MediaId
, Description
->BlockEnd
, BlockIo
->Media
->BlockSize
, EmptyBuffer
);
623 ASSERT_EFI_ERROR (Status
);
625 FreePool (EmptyBuffer
);
628 // Remove the entry from the list
629 RemoveEntryList (&File
->Link
);