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 // Skip files that aren't on disk yet
208 if (FileEntry
->HwDescription
.RegionCount
== 0) {
212 // If the free space preceding the file is big enough to contain the new
214 if (((FileEntry
->HwDescription
.BlockStart
* BlockSize
) - *FileStart
)
216 // The file list must be in disk-order
217 RemoveEntryList (&File
->Link
);
218 File
->Link
.BackLink
= FileLink
->BackLink
;
219 File
->Link
.ForwardLink
= FileLink
;
220 FileLink
->BackLink
->ForwardLink
= &File
->Link
;
221 FileLink
->BackLink
= &File
->Link
;
225 *FileStart
= (FileEntry
->HwDescription
.BlockEnd
+ 1) * BlockSize
;
228 // See if there's space after the last file
229 if ((((Media
->LastBlock
+ 1) * BlockSize
) - *FileStart
) >= FileSize
) {
232 return EFI_VOLUME_FULL
;
236 // Free the resources in the file's Region list.
240 IN BOOTMON_FS_FILE
*File
243 LIST_ENTRY
*RegionToFlushLink
;
244 BOOTMON_FS_FILE_REGION
*Region
;
246 RegionToFlushLink
= GetFirstNode (&File
->RegionToFlushLink
);
247 while (!IsNull (&File
->RegionToFlushLink
, RegionToFlushLink
)) {
248 // Repeatedly remove the first node from the list and free its resources.
249 Region
= (BOOTMON_FS_FILE_REGION
*) RegionToFlushLink
;
250 RemoveEntryList (RegionToFlushLink
);
251 FreePool (Region
->Buffer
);
254 RegionToFlushLink
= GetFirstNode (&File
->RegionToFlushLink
);
261 IN EFI_FILE_PROTOCOL
*This
265 BOOTMON_FS_INSTANCE
*Instance
;
266 LIST_ENTRY
*RegionToFlushLink
;
267 BOOTMON_FS_FILE
*File
;
268 BOOTMON_FS_FILE
*NextFile
;
269 BOOTMON_FS_FILE_REGION
*Region
;
270 LIST_ENTRY
*FileLink
;
271 UINTN CurrentPhysicalSize
;
278 UINT64 EndOfAppendSpace
;
280 EFI_DISK_IO_PROTOCOL
*DiskIo
;
281 EFI_BLOCK_IO_PROTOCOL
*BlockIo
;
283 Status
= EFI_SUCCESS
;
286 File
= BOOTMON_FS_FILE_FROM_FILE_THIS (This
);
288 return EFI_INVALID_PARAMETER
;
291 // Check if the file needs to be flushed
292 if (!BootMonFsFileNeedFlush (File
)) {
296 Instance
= File
->Instance
;
297 BlockIo
= Instance
->BlockIo
;
298 DiskIo
= Instance
->DiskIo
;
299 BlockSize
= BlockIo
->Media
->BlockSize
;
301 // If the file doesn't exist then find a space for it
302 if (File
->HwDescription
.RegionCount
== 0) {
303 Status
= BootMonFsFindSpaceForNewFile (File
, &FileStart
);
304 // FileStart has changed so we need to recompute RegionEnd
305 if (EFI_ERROR (Status
)) {
309 FileStart
= File
->HwDescription
.BlockStart
* BlockSize
;
312 // FileEnd is the current NOR address of the end of the file's data
313 FileEnd
= FileStart
+ File
->HwDescription
.Region
[0].Size
;
315 for (RegionToFlushLink
= GetFirstNode (&File
->RegionToFlushLink
);
316 !IsNull (&File
->RegionToFlushLink
, RegionToFlushLink
);
317 RegionToFlushLink
= GetNextNode (&File
->RegionToFlushLink
, RegionToFlushLink
)
320 Region
= (BOOTMON_FS_FILE_REGION
*)RegionToFlushLink
;
322 // RegionStart and RegionEnd are the the intended NOR address of the
323 // start and end of the region
324 RegionStart
= FileStart
+ Region
->Offset
;
325 RegionEnd
= RegionStart
+ Region
->Size
;
327 if (RegionEnd
< FileEnd
) {
328 // Handle regions representing edits to existing portions of the file
329 // Write the region data straight into the file
330 Status
= DiskIo
->WriteDisk (DiskIo
,
331 BlockIo
->Media
->MediaId
,
336 if (EFI_ERROR (Status
)) {
340 // Handle regions representing appends to the file
342 // Note: Since seeking past the end of the file with SetPosition() is
343 // valid, it's possible there will be a gap between the current end of
344 // the file and the beginning of the new region. Since the UEFI spec
345 // says nothing about this case (except "a subsequent write would grow
346 // the file"), we just leave garbage in the gap.
348 // Check if there is space to append the new region
350 NewFileSize
= (RegionEnd
- FileStart
) + sizeof (HW_IMAGE_DESCRIPTION
);
351 CurrentPhysicalSize
= BootMonFsGetPhysicalSize (File
);
352 if (NewFileSize
<= CurrentPhysicalSize
) {
355 // Get the File Description for the next file
356 FileLink
= GetNextNode (&Instance
->RootFile
->Link
, &File
->Link
);
357 if (!IsNull (&Instance
->RootFile
->Link
, FileLink
)) {
358 NextFile
= BOOTMON_FS_FILE_FROM_LINK_THIS (FileLink
);
360 // If there is space between the beginning of the current file and the
361 // beginning of the next file then use it
362 EndOfAppendSpace
= NextFile
->HwDescription
.BlockStart
* BlockSize
;
364 // We are flushing the last file.
365 EndOfAppendSpace
= (BlockIo
->Media
->LastBlock
+ 1) * BlockSize
;
367 if (EndOfAppendSpace
- FileStart
>= NewFileSize
) {
372 if (HasSpace
== TRUE
) {
373 Status
= FlushAppendRegion (File
, Region
, NewFileSize
, FileStart
);
374 if (EFI_ERROR (Status
)) {
378 // There isn't a space for the file.
379 // Options here are to move the file or fragment it. However as files
380 // may represent boot images at fixed positions, these options will
381 // break booting if the bootloader doesn't use BootMonFs to find the
384 return EFI_VOLUME_FULL
;
389 FreeFileRegions (File
);
391 // Flush DiskIo Buffers (see UEFI Spec 12.7 - DiskIo buffers are flushed by
392 // calling FlushBlocks on the same device's BlockIo).
393 BlockIo
->FlushBlocks (BlockIo
);
399 Closes a file on the Nor Flash FS volume.
401 @param This The EFI_FILE_PROTOCOL to close.
403 @return Always returns EFI_SUCCESS.
409 IN EFI_FILE_PROTOCOL
*This
412 // Flush the file if needed
417 // Create a new instance of BOOTMON_FS_FILE.
418 // Uses BootMonFsCreateFile to
422 IN BOOTMON_FS_INSTANCE
*Instance
,
423 IN CHAR8
* AsciiFileName
,
424 OUT BOOTMON_FS_FILE
**NewHandle
428 BOOTMON_FS_FILE
*File
;
430 Status
= BootMonFsCreateFile (Instance
, &File
);
431 if (EFI_ERROR (Status
)) {
435 // Remove the leading '\\'
436 if (*AsciiFileName
== '\\') {
441 CopyMem (File
->HwDescription
.Footer
.Filename
, AsciiFileName
, MAX_NAME_LENGTH
);
443 // Add the file to list of files of the File System
444 InsertHeadList (&Instance
->RootFile
->Link
, &File
->Link
);
451 Opens a file on the Nor Flash FS volume
453 Calls BootMonFsGetFileFromAsciiFilename to search the list of tracked files.
455 @param This The EFI_FILE_PROTOCOL parent handle.
456 @param NewHandle Double-pointer to the newly created protocol.
457 @param FileName The name of the image/metadata on flash
458 @param OpenMode Read,write,append etc
463 Run out of space to keep track of the allocated structures
465 Unable to locate the volume associated with the parent file handle
467 Filename wasn't found on flash
474 IN EFI_FILE_PROTOCOL
*This
,
475 OUT EFI_FILE_PROTOCOL
**NewHandle
,
481 BOOTMON_FS_FILE
*Directory
;
482 BOOTMON_FS_FILE
*File
;
483 BOOTMON_FS_INSTANCE
*Instance
;
484 CHAR8
* AsciiFileName
;
487 if ((FileName
== NULL
) || (NewHandle
== NULL
)) {
488 return EFI_INVALID_PARAMETER
;
491 // The only valid modes are read, read/write, and read/write/create
492 if (!(OpenMode
& EFI_FILE_MODE_READ
) || ((OpenMode
& EFI_FILE_MODE_CREATE
) && !(OpenMode
& EFI_FILE_MODE_WRITE
))) {
493 return EFI_INVALID_PARAMETER
;
496 Directory
= BOOTMON_FS_FILE_FROM_FILE_THIS (This
);
497 if (Directory
== NULL
) {
498 return EFI_DEVICE_ERROR
;
501 Instance
= Directory
->Instance
;
503 // If the instance has not been initialized it yet then do it ...
504 if (!Instance
->Initialized
) {
505 Status
= BootMonFsInitialize (Instance
);
506 if (EFI_ERROR (Status
)) {
511 // BootMonFs interface requires ASCII filenames
512 AsciiFileName
= AllocatePool ((StrLen (FileName
) + 1) * sizeof (CHAR8
));
513 if (AsciiFileName
== NULL
) {
514 return EFI_OUT_OF_RESOURCES
;
516 UnicodeStrToAsciiStr (FileName
, AsciiFileName
);
518 if ((AsciiStrCmp (AsciiFileName
, "\\") == 0) ||
519 (AsciiStrCmp (AsciiFileName
, "/") == 0) ||
520 (AsciiStrCmp (AsciiFileName
, "") == 0) ||
521 (AsciiStrCmp (AsciiFileName
, ".") == 0))
524 // Opening '/', '\', '.', or the NULL pathname is trying to open the root directory
527 *NewHandle
= &Instance
->RootFile
->File
;
528 Instance
->RootFile
->Position
= 0;
529 Status
= EFI_SUCCESS
;
532 // Open or Create a regular file
535 // Check if the file already exists
536 Status
= BootMonGetFileFromAsciiFileName (Instance
, AsciiFileName
, &File
);
537 if (Status
== EFI_NOT_FOUND
) {
538 // The file doesn't exist.
539 if (OpenMode
& EFI_FILE_MODE_CREATE
) {
540 // If the file does not exist but is required then create it.
541 if (Attributes
& EFI_FILE_DIRECTORY
) {
542 // BootMonFS doesn't support subdirectories
543 Status
= EFI_UNSUPPORTED
;
546 Status
= CreateNewFile (Instance
, AsciiFileName
, &File
);
547 if (!EFI_ERROR (Status
)) {
548 File
->OpenMode
= OpenMode
;
549 *NewHandle
= &File
->File
;
554 } else if (Status
== EFI_SUCCESS
) {
556 File
->OpenMode
= OpenMode
;
557 *NewHandle
= &File
->File
;
562 FreePool (AsciiFileName
);
567 // Delete() for the root directory's EFI_FILE_PROTOCOL instance
570 BootMonFsDeleteFail (
571 IN EFI_FILE_PROTOCOL
*This
575 // You can't delete the root directory
576 return EFI_WARN_DELETE_FAILURE
;
581 IN EFI_FILE_PROTOCOL
*This
585 BOOTMON_FS_FILE
*File
;
586 LIST_ENTRY
*RegionToFlushLink
;
587 BOOTMON_FS_FILE_REGION
*Region
;
588 HW_IMAGE_DESCRIPTION
*Description
;
589 EFI_BLOCK_IO_PROTOCOL
*BlockIo
;
592 File
= BOOTMON_FS_FILE_FROM_FILE_THIS (This
);
594 return EFI_DEVICE_ERROR
;
597 Status
= EFI_SUCCESS
;
599 if (BootMonFsFileNeedFlush (File
)) {
600 // Free the entries from the Buffer List
601 RegionToFlushLink
= GetFirstNode (&File
->RegionToFlushLink
);
603 Region
= (BOOTMON_FS_FILE_REGION
*)RegionToFlushLink
;
606 RegionToFlushLink
= RemoveEntryList (RegionToFlushLink
);
609 FreePool (Region
->Buffer
);
611 } while (!IsListEmpty (&File
->RegionToFlushLink
));
614 // If (RegionCount is greater than 0) then the file already exists
615 if (File
->HwDescription
.RegionCount
> 0) {
616 Description
= &File
->HwDescription
;
617 BlockIo
= File
->Instance
->BlockIo
;
619 // Create an empty buffer
620 EmptyBuffer
= AllocateZeroPool (BlockIo
->Media
->BlockSize
);
621 if (EmptyBuffer
== NULL
) {
623 return EFI_OUT_OF_RESOURCES
;
626 // Invalidate the last Block
627 Status
= BlockIo
->WriteBlocks (BlockIo
, BlockIo
->Media
->MediaId
, Description
->BlockEnd
, BlockIo
->Media
->BlockSize
, EmptyBuffer
);
628 ASSERT_EFI_ERROR (Status
);
630 FreePool (EmptyBuffer
);
633 // Remove the entry from the list
634 RemoveEntryList (&File
->Link
);