summaryrefslogtreecommitdiff
path: root/ArmPlatformPkg/FileSystem/BootMonFs/BootMonFsOpenClose.c
diff options
context:
space:
mode:
Diffstat (limited to 'ArmPlatformPkg/FileSystem/BootMonFs/BootMonFsOpenClose.c')
-rw-r--r--ArmPlatformPkg/FileSystem/BootMonFs/BootMonFsOpenClose.c628
1 files changed, 628 insertions, 0 deletions
diff --git a/ArmPlatformPkg/FileSystem/BootMonFs/BootMonFsOpenClose.c b/ArmPlatformPkg/FileSystem/BootMonFs/BootMonFsOpenClose.c
new file mode 100644
index 0000000000..908393fa53
--- /dev/null
+++ b/ArmPlatformPkg/FileSystem/BootMonFs/BootMonFsOpenClose.c
@@ -0,0 +1,628 @@
+/** @file
+*
+* Copyright (c) 2012-2014, ARM Limited. All rights reserved.
+*
+* This program and the accompanying materials
+* are licensed and made available under the terms and conditions of the BSD License
+* which accompanies this distribution. The full text of the license may be found at
+* http://opensource.org/licenses/bsd-license.php
+*
+* THE PROGRAM IS DISTRIBUTED UNDER THE BSD LICENSE ON AN "AS IS" BASIS,
+* WITHOUT WARRANTIES OR REPRESENTATIONS OF ANY KIND, EITHER EXPRESS OR IMPLIED.
+*
+**/
+
+#include "BootMonFsInternal.h"
+
+// Clear a file's image description on storage media:
+// UEFI allows you to seek past the end of a file, a subsequent write will grow
+// the file. It does not specify how space between the former end of the file
+// and the beginning of the write should be filled. It's therefore possible that
+// BootMonFs metadata, that comes after the end of a file, could be left there
+// and wrongly detected by BootMonFsImageInBlock.
+STATIC
+EFI_STATUS
+InvalidateImageDescription (
+ IN BOOTMON_FS_FILE *File
+ )
+{
+ EFI_DISK_IO_PROTOCOL *DiskIo;
+ EFI_BLOCK_IO_PROTOCOL *BlockIo;
+ UINT32 MediaId;
+ UINT32 BlockSize;
+ VOID *Buffer;
+ EFI_STATUS Status;
+ UINT64 DescriptionAddress;
+
+ DiskIo = File->Instance->DiskIo;
+ BlockIo = File->Instance->BlockIo;
+ MediaId = BlockIo->Media->MediaId;
+ BlockSize = BlockIo->Media->BlockSize;
+
+ DescriptionAddress = (File->HwDescription.BlockEnd * BlockSize)
+ - sizeof (HW_IMAGE_DESCRIPTION);
+
+ Buffer = AllocateZeroPool (sizeof (HW_IMAGE_DESCRIPTION));
+
+ Status = DiskIo->WriteDisk (DiskIo,
+ MediaId,
+ DescriptionAddress,
+ sizeof (HW_IMAGE_DESCRIPTION),
+ Buffer
+ );
+
+ FreePool(Buffer);
+
+ return Status;
+}
+
+// Flush file data that will extend the file's length. Update and, if necessary,
+// move the image description.
+// We need to pass the file's starting position on media (FileStart), because
+// if the file hasn't been flushed before its Description->BlockStart won't
+// have been initialised.
+// FileStart must be aligned to the media's block size.
+// Note that this function uses DiskIo to flush, so call BlockIo->FlushBlocks()
+// after calling it.
+STATIC
+EFI_STATUS
+FlushAppendRegion (
+ IN BOOTMON_FS_FILE *File,
+ IN BOOTMON_FS_FILE_REGION *Region,
+ IN UINT64 NewFileSize,
+ IN UINT64 FileStart
+ )
+{
+ EFI_STATUS Status;
+ EFI_DISK_IO_PROTOCOL *DiskIo;
+ UINTN BlockSize;
+ HW_IMAGE_DESCRIPTION *Description;
+
+ DiskIo = File->Instance->DiskIo;
+
+ BlockSize = File->Instance->BlockIo->Media->BlockSize;
+
+ ASSERT (FileStart % BlockSize == 0);
+
+ // Only invalidate the Image Description of files that have already been
+ // written in Flash
+ if (File->HwDescription.RegionCount > 0) {
+ Status = InvalidateImageDescription (File);
+ ASSERT_EFI_ERROR (Status);
+ }
+
+ //
+ // Update File Description
+ //
+ Description = &File->HwDescription;
+ Description->Attributes = 1;
+ Description->BlockStart = FileStart / BlockSize;
+ Description->BlockEnd = Description->BlockStart + (NewFileSize / BlockSize);
+ Description->Footer.FooterSignature1 = HW_IMAGE_FOOTER_SIGNATURE_1;
+ Description->Footer.FooterSignature2 = HW_IMAGE_FOOTER_SIGNATURE_2;
+ Description->Footer.Version = HW_IMAGE_FOOTER_VERSION;
+ Description->Footer.Offset = HW_IMAGE_FOOTER_OFFSET;
+ Description->RegionCount = 1;
+ Description->Region[0].Checksum = 0;
+ Description->Region[0].Offset = Description->BlockStart * BlockSize;
+ Description->Region[0].Size = NewFileSize - sizeof (HW_IMAGE_DESCRIPTION);
+
+ Status = BootMonFsComputeFooterChecksum (Description);
+ if (EFI_ERROR (Status)) {
+ return Status;
+ }
+
+ // Write the new file data
+ Status = DiskIo->WriteDisk (
+ DiskIo,
+ File->Instance->Media->MediaId,
+ FileStart + Region->Offset,
+ Region->Size,
+ Region->Buffer
+ );
+ ASSERT_EFI_ERROR (Status);
+
+ // Round the file size up to the nearest block size
+ if ((NewFileSize % BlockSize) > 0) {
+ NewFileSize += BlockSize - (NewFileSize % BlockSize);
+ }
+ // Update the file description on the media
+ Status = DiskIo->WriteDisk (
+ DiskIo,
+ File->Instance->Media->MediaId,
+ (FileStart + NewFileSize) - sizeof (HW_IMAGE_DESCRIPTION),
+ sizeof (HW_IMAGE_DESCRIPTION),
+ Description
+ );
+ ASSERT_EFI_ERROR (Status);
+
+ return Status;
+}
+
+BOOLEAN
+BootMonFsFileNeedFlush (
+ IN BOOTMON_FS_FILE *File
+ )
+{
+ return !IsListEmpty (&File->RegionToFlushLink);
+}
+
+// Find a space on media for a file that has not yet been flushed to disk.
+// Just returns the first space that's big enough.
+// This function could easily be adapted to:
+// - Find space for moving an existing file that has outgrown its space
+// (We do not currently move files, just return EFI_VOLUME_FULL)
+// - Find space for a fragment of a file that has outgrown its space
+// (We do not currently fragment files - it's not clear whether fragmentation
+// is actually part of BootMonFs as there is no spec)
+// - Be more clever about finding space (choosing the largest or smallest
+// suitable space)
+// Parameters:
+// File - the new (not yet flushed) file for which we need to find space.
+// FileStart - the position on media of the file (in bytes).
+STATIC
+EFI_STATUS
+BootMonFsFindSpaceForNewFile (
+ IN BOOTMON_FS_FILE *File,
+ OUT UINT64 *FileStart
+ )
+{
+ LIST_ENTRY *FileLink;
+ BOOTMON_FS_FILE *RootFile;
+ BOOTMON_FS_FILE *FileEntry;
+ UINTN BlockSize;
+ UINT64 FileSize;
+ EFI_BLOCK_IO_MEDIA *Media;
+
+ Media = File->Instance->BlockIo->Media;
+ BlockSize = Media->BlockSize;
+ RootFile = File->Instance->RootFile;
+
+ if (IsListEmpty (&RootFile->Link)) {
+ return EFI_SUCCESS;
+ }
+
+ // This function must only be called for file which has not been flushed into
+ // Flash yet
+ ASSERT (File->HwDescription.RegionCount == 0);
+
+ // Find out how big the file will be
+ FileSize = BootMonFsGetImageLength (File);
+ // Add the file header to the file
+ FileSize += sizeof (HW_IMAGE_DESCRIPTION);
+
+ *FileStart = 0;
+ // Go through all the files in the list
+ for (FileLink = GetFirstNode (&RootFile->Link);
+ !IsNull (&RootFile->Link, FileLink);
+ FileLink = GetNextNode (&RootFile->Link, FileLink)
+ )
+ {
+ FileEntry = BOOTMON_FS_FILE_FROM_LINK_THIS (FileLink);
+ // If the free space preceding the file is big enough to contain the new
+ // file then use it!
+ if (((FileEntry->HwDescription.BlockStart * BlockSize) - *FileStart)
+ >= FileSize) {
+ // The file list must be in disk-order
+ RemoveEntryList (&File->Link);
+ File->Link.BackLink = FileLink->BackLink;
+ File->Link.ForwardLink = FileLink;
+ FileLink->BackLink->ForwardLink = &File->Link;
+ FileLink->BackLink = &File->Link;
+
+ return EFI_SUCCESS;
+ } else {
+ *FileStart = (FileEntry->HwDescription.BlockEnd + 1) * BlockSize;
+ }
+ }
+ // See if there's space after the last file
+ if ((((Media->LastBlock + 1) * BlockSize) - *FileStart) >= FileSize) {
+ return EFI_SUCCESS;
+ } else {
+ return EFI_VOLUME_FULL;
+ }
+}
+
+// Free the resources in the file's Region list.
+STATIC
+VOID
+FreeFileRegions (
+ IN BOOTMON_FS_FILE *File
+ )
+{
+ LIST_ENTRY *RegionToFlushLink;
+ BOOTMON_FS_FILE_REGION *Region;
+
+ RegionToFlushLink = GetFirstNode (&File->RegionToFlushLink);
+ while (!IsNull (&File->RegionToFlushLink, RegionToFlushLink)) {
+ // Repeatedly remove the first node from the list and free its resources.
+ Region = (BOOTMON_FS_FILE_REGION *) RegionToFlushLink;
+ RemoveEntryList (RegionToFlushLink);
+ FreePool (Region->Buffer);
+ FreePool (Region);
+
+ RegionToFlushLink = GetFirstNode (&File->RegionToFlushLink);
+ }
+}
+
+EFIAPI
+EFI_STATUS
+BootMonFsFlushFile (
+ IN EFI_FILE_PROTOCOL *This
+ )
+{
+ EFI_STATUS Status;
+ BOOTMON_FS_INSTANCE *Instance;
+ LIST_ENTRY *RegionToFlushLink;
+ BOOTMON_FS_FILE *File;
+ BOOTMON_FS_FILE *NextFile;
+ BOOTMON_FS_FILE_REGION *Region;
+ LIST_ENTRY *FileLink;
+ UINTN CurrentPhysicalSize;
+ UINTN BlockSize;
+ UINT64 FileStart;
+ UINT64 FileEnd;
+ UINT64 RegionStart;
+ UINT64 RegionEnd;
+ UINT64 NewFileSize;
+ UINT64 EndOfAppendSpace;
+ BOOLEAN HasSpace;
+ EFI_DISK_IO_PROTOCOL *DiskIo;
+ EFI_BLOCK_IO_PROTOCOL *BlockIo;
+
+ Status = EFI_SUCCESS;
+ FileStart = 0;
+
+ File = BOOTMON_FS_FILE_FROM_FILE_THIS (This);
+ if (File == NULL) {
+ return EFI_INVALID_PARAMETER;
+ }
+
+ // Check if the file needs to be flushed
+ if (!BootMonFsFileNeedFlush (File)) {
+ return Status;
+ }
+
+ Instance = File->Instance;
+ BlockIo = Instance->BlockIo;
+ DiskIo = Instance->DiskIo;
+ BlockSize = BlockIo->Media->BlockSize;
+
+ // If the file doesn't exist then find a space for it
+ if (File->HwDescription.RegionCount == 0) {
+ Status = BootMonFsFindSpaceForNewFile (File, &FileStart);
+ // FileStart has changed so we need to recompute RegionEnd
+ if (EFI_ERROR (Status)) {
+ return Status;
+ }
+ } else {
+ FileStart = File->HwDescription.BlockStart * BlockSize;
+ }
+
+ // FileEnd is the NOR address of the end of the file's data
+ FileEnd = FileStart + BootMonFsGetImageLength (File);
+
+ for (RegionToFlushLink = GetFirstNode (&File->RegionToFlushLink);
+ !IsNull (&File->RegionToFlushLink, RegionToFlushLink);
+ RegionToFlushLink = GetNextNode (&File->RegionToFlushLink, RegionToFlushLink)
+ )
+ {
+ Region = (BOOTMON_FS_FILE_REGION*)RegionToFlushLink;
+
+ // RegionStart and RegionEnd are the the intended NOR address of the
+ // start and end of the region
+ RegionStart = FileStart + Region->Offset;
+ RegionEnd = RegionStart + Region->Size;
+
+ if (RegionEnd < FileEnd) {
+ // Handle regions representing edits to existing portions of the file
+ // Write the region data straight into the file
+ Status = DiskIo->WriteDisk (DiskIo,
+ BlockIo->Media->MediaId,
+ RegionStart,
+ Region->Size,
+ Region->Buffer
+ );
+ if (EFI_ERROR (Status)) {
+ return Status;
+ }
+ } else {
+ // Handle regions representing appends to the file
+ //
+ // Note: Since seeking past the end of the file with SetPosition() is
+ // valid, it's possible there will be a gap between the current end of
+ // the file and the beginning of the new region. Since the UEFI spec
+ // says nothing about this case (except "a subsequent write would grow
+ // the file"), we just leave garbage in the gap.
+
+ // Check if there is space to append the new region
+ HasSpace = FALSE;
+ NewFileSize = (RegionEnd - FileStart) + sizeof (HW_IMAGE_DESCRIPTION);
+ CurrentPhysicalSize = BootMonFsGetPhysicalSize (File);
+ if (NewFileSize <= CurrentPhysicalSize) {
+ HasSpace = TRUE;
+ } else {
+ // Get the File Description for the next file
+ FileLink = GetNextNode (&Instance->RootFile->Link, &File->Link);
+ if (!IsNull (&Instance->RootFile->Link, FileLink)) {
+ NextFile = BOOTMON_FS_FILE_FROM_LINK_THIS (FileLink);
+
+ // If there is space between the beginning of the current file and the
+ // beginning of the next file then use it
+ EndOfAppendSpace = NextFile->HwDescription.BlockStart * BlockSize;
+ } else {
+ // We are flushing the last file.
+ EndOfAppendSpace = (BlockIo->Media->LastBlock + 1) * BlockSize;
+ }
+ if (EndOfAppendSpace - FileStart >= NewFileSize) {
+ HasSpace = TRUE;
+ }
+ }
+
+ if (HasSpace == TRUE) {
+ Status = FlushAppendRegion (File, Region, NewFileSize, FileStart);
+ if (EFI_ERROR (Status)) {
+ return Status;
+ }
+ } else {
+ // There isn't a space for the file.
+ // Options here are to move the file or fragment it. However as files
+ // may represent boot images at fixed positions, these options will
+ // break booting if the bootloader doesn't use BootMonFs to find the
+ // image.
+
+ return EFI_VOLUME_FULL;
+ }
+ }
+ }
+
+ FreeFileRegions (File);
+
+ // Flush DiskIo Buffers (see UEFI Spec 12.7 - DiskIo buffers are flushed by
+ // calling FlushBlocks on the same device's BlockIo).
+ BlockIo->FlushBlocks (BlockIo);
+
+ return Status;
+}
+
+/**
+ Closes a file on the Nor Flash FS volume.
+
+ @param This The EFI_FILE_PROTOCOL to close.
+
+ @return Always returns EFI_SUCCESS.
+
+**/
+EFIAPI
+EFI_STATUS
+BootMonFsCloseFile (
+ IN EFI_FILE_PROTOCOL *This
+ )
+{
+ // Flush the file if needed
+ This->Flush (This);
+ return EFI_SUCCESS;
+}
+
+// Create a new instance of BOOTMON_FS_FILE.
+// Uses BootMonFsCreateFile to
+STATIC
+EFI_STATUS
+CreateNewFile (
+ IN BOOTMON_FS_INSTANCE *Instance,
+ IN CHAR8* AsciiFileName,
+ OUT BOOTMON_FS_FILE **NewHandle
+ )
+{
+ EFI_STATUS Status;
+ BOOTMON_FS_FILE *File;
+
+ Status = BootMonFsCreateFile (Instance, &File);
+ if (EFI_ERROR (Status)) {
+ return Status;
+ }
+
+ // Remove the leading '\\'
+ if (*AsciiFileName == '\\') {
+ AsciiFileName++;
+ }
+
+ // Set the file name
+ CopyMem (File->HwDescription.Footer.Filename, AsciiFileName, MAX_NAME_LENGTH);
+
+ // Add the file to list of files of the File System
+ InsertHeadList (&Instance->RootFile->Link, &File->Link);
+
+ *NewHandle = File;
+ return Status;
+}
+
+/**
+ Opens a file on the Nor Flash FS volume
+
+ Calls BootMonFsGetFileFromAsciiFilename to search the list of tracked files.
+
+ @param This The EFI_FILE_PROTOCOL parent handle.
+ @param NewHandle Double-pointer to the newly created protocol.
+ @param FileName The name of the image/metadata on flash
+ @param OpenMode Read,write,append etc
+ @param Attributes ?
+
+ @return EFI_STATUS
+ OUT_OF_RESOURCES
+ Run out of space to keep track of the allocated structures
+ DEVICE_ERROR
+ Unable to locate the volume associated with the parent file handle
+ NOT_FOUND
+ Filename wasn't found on flash
+ SUCCESS
+
+**/
+EFIAPI
+EFI_STATUS
+BootMonFsOpenFile (
+ IN EFI_FILE_PROTOCOL *This,
+ OUT EFI_FILE_PROTOCOL **NewHandle,
+ IN CHAR16 *FileName,
+ IN UINT64 OpenMode,
+ IN UINT64 Attributes
+ )
+{
+ BOOTMON_FS_FILE *Directory;
+ BOOTMON_FS_FILE *File;
+ BOOTMON_FS_INSTANCE *Instance;
+ CHAR8* AsciiFileName;
+ EFI_STATUS Status;
+
+ if ((FileName == NULL) || (NewHandle == NULL)) {
+ return EFI_INVALID_PARAMETER;
+ }
+
+ // The only valid modes are read, read/write, and read/write/create
+ if (!(OpenMode & EFI_FILE_MODE_READ) || ((OpenMode & EFI_FILE_MODE_CREATE) && !(OpenMode & EFI_FILE_MODE_WRITE))) {
+ return EFI_INVALID_PARAMETER;
+ }
+
+ Directory = BOOTMON_FS_FILE_FROM_FILE_THIS (This);
+ if (Directory == NULL) {
+ return EFI_DEVICE_ERROR;
+ }
+
+ Instance = Directory->Instance;
+
+ // If the instance has not been initialized it yet then do it ...
+ if (!Instance->Initialized) {
+ Status = BootMonFsInitialize (Instance);
+ if (EFI_ERROR (Status)) {
+ return Status;
+ }
+ }
+
+ // BootMonFs interface requires ASCII filenames
+ AsciiFileName = AllocatePool ((StrLen (FileName) + 1) * sizeof (CHAR8));
+ if (AsciiFileName == NULL) {
+ return EFI_OUT_OF_RESOURCES;
+ }
+ UnicodeStrToAsciiStr (FileName, AsciiFileName);
+
+ if ((AsciiStrCmp (AsciiFileName, "\\") == 0) ||
+ (AsciiStrCmp (AsciiFileName, "/") == 0) ||
+ (AsciiStrCmp (AsciiFileName, "") == 0) ||
+ (AsciiStrCmp (AsciiFileName, ".") == 0))
+ {
+ //
+ // Opening '/', '\', '.', or the NULL pathname is trying to open the root directory
+ //
+
+ *NewHandle = &Instance->RootFile->File;
+ Instance->RootFile->Position = 0;
+ Status = EFI_SUCCESS;
+ } else {
+ //
+ // Open or Create a regular file
+ //
+
+ // Check if the file already exists
+ Status = BootMonGetFileFromAsciiFileName (Instance, AsciiFileName, &File);
+ if (Status == EFI_NOT_FOUND) {
+ // The file doesn't exist.
+ if (OpenMode & EFI_FILE_MODE_CREATE) {
+ // If the file does not exist but is required then create it.
+ if (Attributes & EFI_FILE_DIRECTORY) {
+ // BootMonFS doesn't support subdirectories
+ Status = EFI_UNSUPPORTED;
+ } else {
+ // Create a new file
+ Status = CreateNewFile (Instance, AsciiFileName, &File);
+ if (!EFI_ERROR (Status)) {
+ File->OpenMode = OpenMode;
+ *NewHandle = &File->File;
+ File->Position = 0;
+ }
+ }
+ }
+ } else if (Status == EFI_SUCCESS) {
+ // The file exists
+ File->OpenMode = OpenMode;
+ *NewHandle = &File->File;
+ File->Position = 0;
+ }
+ }
+
+ FreePool (AsciiFileName);
+
+ return Status;
+}
+
+// Delete() for the root directory's EFI_FILE_PROTOCOL instance
+EFIAPI
+EFI_STATUS
+BootMonFsDeleteFail (
+ IN EFI_FILE_PROTOCOL *This
+ )
+{
+ This->Close(This);
+ // You can't delete the root directory
+ return EFI_WARN_DELETE_FAILURE;
+}
+EFIAPI
+EFI_STATUS
+BootMonFsDelete (
+ IN EFI_FILE_PROTOCOL *This
+ )
+{
+ EFI_STATUS Status;
+ BOOTMON_FS_FILE *File;
+ LIST_ENTRY *RegionToFlushLink;
+ BOOTMON_FS_FILE_REGION *Region;
+ HW_IMAGE_DESCRIPTION *Description;
+ EFI_BLOCK_IO_PROTOCOL *BlockIo;
+ UINT8 *EmptyBuffer;
+
+ File = BOOTMON_FS_FILE_FROM_FILE_THIS (This);
+ if (File == NULL) {
+ return EFI_DEVICE_ERROR;
+ }
+
+ Status = EFI_SUCCESS;
+
+ if (BootMonFsFileNeedFlush (File)) {
+ // Free the entries from the Buffer List
+ RegionToFlushLink = GetFirstNode (&File->RegionToFlushLink);
+ do {
+ Region = (BOOTMON_FS_FILE_REGION*)RegionToFlushLink;
+
+ // Get Next entry
+ RegionToFlushLink = RemoveEntryList (RegionToFlushLink);
+
+ // Free the buffers
+ FreePool (Region->Buffer);
+ FreePool (Region);
+ } while (!IsListEmpty (&File->RegionToFlushLink));
+ }
+
+ // If (RegionCount is greater than 0) then the file already exists
+ if (File->HwDescription.RegionCount > 0) {
+ Description = &File->HwDescription;
+ BlockIo = File->Instance->BlockIo;
+
+ // Create an empty buffer
+ EmptyBuffer = AllocateZeroPool (BlockIo->Media->BlockSize);
+ if (EmptyBuffer == NULL) {
+ FreePool (File);
+ return EFI_OUT_OF_RESOURCES;
+ }
+
+ // Invalidate the last Block
+ Status = BlockIo->WriteBlocks (BlockIo, BlockIo->Media->MediaId, Description->BlockEnd, BlockIo->Media->BlockSize, EmptyBuffer);
+ ASSERT_EFI_ERROR (Status);
+
+ FreePool (EmptyBuffer);
+ }
+
+ // Remove the entry from the list
+ RemoveEntryList (&File->Link);
+ FreePool (File);
+ return Status;
+}
+