/** @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; VOID *Buffer; EFI_STATUS Status; DiskIo = File->Instance->DiskIo; BlockIo = File->Instance->BlockIo; MediaId = BlockIo->Media->MediaId; Buffer = AllocateZeroPool (sizeof (HW_IMAGE_DESCRIPTION)); Status = DiskIo->WriteDisk (DiskIo, MediaId, File->HwDescAddress, 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->HwDescAddress != 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; #ifdef MDE_CPU_ARM Description->Footer.Version = HW_IMAGE_FOOTER_VERSION; Description->Footer.Offset = HW_IMAGE_FOOTER_OFFSET; #else Description->Footer.Version = HW_IMAGE_FOOTER_VERSION2; Description->Footer.Offset = HW_IMAGE_FOOTER_OFFSET2; #endif 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); } File->HwDescAddress = (FileStart + NewFileSize) - sizeof (HW_IMAGE_DESCRIPTION); // Update the file description on the media Status = DiskIo->WriteDisk ( DiskIo, File->Instance->Media->MediaId, File->HwDescAddress, 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); // Skip files that aren't on disk yet if (FileEntry->HwDescription.RegionCount == 0) { continue; } // 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 current NOR address of the end of the file's data FileEnd = FileStart + File->HwDescription.Region[0].Size; 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; 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) { 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 = InvalidateImageDescription (File); ASSERT_EFI_ERROR (Status); FreePool (EmptyBuffer); } // Remove the entry from the list RemoveEntryList (&File->Link); FreePool (File); return Status; }