audk/ArmPlatformPkg/FileSystem/BootMonFs/BootMonFsOpenClose.c

629 lines
19 KiB
C

/** @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;
}