edk2/FatPkg/EnhancedFatDxe/Misc.c
Chris Fernald 12c5ded287 FatPkg: Clean any volume caches during exit boot services
The current implementation assumes the the caller will perform the
necessary cleanup before exiting boot services. This has been observed
to drop some cached file writes that occur even before the application
calling exit boot services is launched.

This commit adds a per-volume pre-ExitBootServices event to flush any
dirty caches and perform other cleanup the volume to ensure all write
data is persisted and consistent. After the flush, caching will be
disabled for the volume in the future to ensure that all subsequent
access persists.

Signed-off-by: Chris Fernald <chfernal@microsoft.com>
2026-06-22 22:06:08 +00:00

694 lines
17 KiB
C

/** @file
Miscellaneous functions.
Copyright (c) 2005 - 2018, Intel Corporation. All rights reserved.<BR>
SPDX-License-Identifier: BSD-2-Clause-Patent
**/
#include "Fat.h"
UINT8 mMonthDays[] = { 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 };
/**
Create the task
@param IFile - The instance of the open file.
@param Token - A pointer to the token associated with the transaction.
@return FAT_TASK * - Return the task instance.
**/
FAT_TASK *
FatCreateTask (
FAT_IFILE *IFile,
EFI_FILE_IO_TOKEN *Token
)
{
FAT_TASK *Task;
Task = AllocateZeroPool (sizeof (*Task));
if (Task != NULL) {
Task->Signature = FAT_TASK_SIGNATURE;
Task->IFile = IFile;
Task->FileIoToken = Token;
InitializeListHead (&Task->Subtasks);
InitializeListHead (&Task->Link);
}
return Task;
}
/**
Destroy the task.
@param Task - The task to be destroyed.
**/
VOID
FatDestroyTask (
FAT_TASK *Task
)
{
LIST_ENTRY *Link;
FAT_SUBTASK *Subtask;
Link = GetFirstNode (&Task->Subtasks);
while (!IsNull (&Task->Subtasks, Link)) {
Subtask = CR (Link, FAT_SUBTASK, Link, FAT_SUBTASK_SIGNATURE);
Link = FatDestroySubtask (Subtask);
}
FreePool (Task);
}
/**
Wait all non-blocking requests complete.
@param IFile - The instance of the open file.
**/
VOID
FatWaitNonblockingTask (
FAT_IFILE *IFile
)
{
BOOLEAN TaskQueueEmpty;
do {
EfiAcquireLock (&FatTaskLock);
TaskQueueEmpty = IsListEmpty (&IFile->Tasks);
EfiReleaseLock (&FatTaskLock);
} while (!TaskQueueEmpty);
}
/**
Remove the subtask from subtask list.
@param Subtask - The subtask to be removed.
@return LIST_ENTRY * - The next node in the list.
**/
LIST_ENTRY *
FatDestroySubtask (
FAT_SUBTASK *Subtask
)
{
LIST_ENTRY *Link;
gBS->CloseEvent (Subtask->DiskIo2Token.Event);
Link = RemoveEntryList (&Subtask->Link);
FreePool (Subtask);
return Link;
}
/**
Execute the task.
@param IFile - The instance of the open file.
@param Task - The task to be executed.
@retval EFI_SUCCESS - The task was executed successfully.
@return other - An error occurred when executing the task.
**/
EFI_STATUS
FatQueueTask (
IN FAT_IFILE *IFile,
IN FAT_TASK *Task
)
{
EFI_STATUS Status;
LIST_ENTRY *Link;
LIST_ENTRY *NextLink;
FAT_SUBTASK *Subtask;
//
// Sometimes the Task doesn't contain any subtasks, signal the event directly.
//
if (IsListEmpty (&Task->Subtasks)) {
Task->FileIoToken->Status = EFI_SUCCESS;
gBS->SignalEvent (Task->FileIoToken->Event);
FreePool (Task);
return EFI_SUCCESS;
}
EfiAcquireLock (&FatTaskLock);
InsertTailList (&IFile->Tasks, &Task->Link);
EfiReleaseLock (&FatTaskLock);
Status = EFI_SUCCESS;
//
// Use NextLink to store the next link of the list, because Link might be remove from the
// doubly-linked list and get freed in the end of current loop.
//
// Also, list operation APIs like IsNull() and GetNextNode() are avoided during the loop, since
// they may check the validity of doubly-linked lists by traversing them. These APIs cannot
// handle list elements being removed during the traverse.
//
for ( Link = GetFirstNode (&Task->Subtasks), NextLink = GetNextNode (&Task->Subtasks, Link)
; Link != &Task->Subtasks
; Link = NextLink, NextLink = Link->ForwardLink
)
{
Subtask = CR (Link, FAT_SUBTASK, Link, FAT_SUBTASK_SIGNATURE);
if (Subtask->Write) {
Status = IFile->OFile->Volume->DiskIo2->WriteDiskEx (
IFile->OFile->Volume->DiskIo2,
IFile->OFile->Volume->MediaId,
Subtask->Offset,
&Subtask->DiskIo2Token,
Subtask->BufferSize,
Subtask->Buffer
);
} else {
Status = IFile->OFile->Volume->DiskIo2->ReadDiskEx (
IFile->OFile->Volume->DiskIo2,
IFile->OFile->Volume->MediaId,
Subtask->Offset,
&Subtask->DiskIo2Token,
Subtask->BufferSize,
Subtask->Buffer
);
}
if (EFI_ERROR (Status)) {
break;
}
}
if (EFI_ERROR (Status)) {
EfiAcquireLock (&FatTaskLock);
//
// Remove all the remaining subtasks when failure.
// We shouldn't remove all the tasks because the non-blocking requests have
// been submitted and cannot be canceled.
//
while (!IsNull (&Task->Subtasks, Link)) {
Subtask = CR (Link, FAT_SUBTASK, Link, FAT_SUBTASK_SIGNATURE);
Link = FatDestroySubtask (Subtask);
}
if (IsListEmpty (&Task->Subtasks)) {
RemoveEntryList (&Task->Link);
FreePool (Task);
} else {
//
// If one or more subtasks have been already submitted, set FileIoToken
// to NULL so that the callback won't signal the event.
//
Task->FileIoToken = NULL;
}
EfiReleaseLock (&FatTaskLock);
}
return Status;
}
/**
Set the volume as dirty or not.
@param Volume - FAT file system volume.
@param IoMode - The access mode.
@param DirtyValue - Set the volume as dirty or not.
@retval EFI_SUCCESS - Set the new FAT entry value successfully.
@return other - An error occurred when operation the FAT entries.
**/
EFI_STATUS
FatAccessVolumeDirty (
IN FAT_VOLUME *Volume,
IN IO_MODE IoMode,
IN VOID *DirtyValue
)
{
UINTN WriteCount;
WriteCount = Volume->FatEntrySize;
return FatDiskIo (Volume, IoMode, Volume->FatPos + WriteCount, WriteCount, DirtyValue, NULL);
}
/**
Invoke a notification event.
@param Event Event whose notification function is being invoked.
@param Context The pointer to the notification function's context,
which is implementation-dependent.
**/
VOID
EFIAPI
FatOnAccessComplete (
IN EFI_EVENT Event,
IN VOID *Context
)
{
EFI_STATUS Status;
FAT_SUBTASK *Subtask;
FAT_TASK *Task;
//
// Avoid someone in future breaks the below assumption.
//
ASSERT (EfiGetCurrentTpl () == FatTaskLock.Tpl);
Subtask = (FAT_SUBTASK *)Context;
Task = Subtask->Task;
Status = Subtask->DiskIo2Token.TransactionStatus;
ASSERT (Task->Signature == FAT_TASK_SIGNATURE);
ASSERT (Subtask->Signature == FAT_SUBTASK_SIGNATURE);
//
// Remove the task unconditionally
//
FatDestroySubtask (Subtask);
//
// Task->FileIoToken is NULL which means the task will be ignored (just recycle the subtask and task memory).
//
if (Task->FileIoToken != NULL) {
if (IsListEmpty (&Task->Subtasks) || EFI_ERROR (Status)) {
Task->FileIoToken->Status = Status;
gBS->SignalEvent (Task->FileIoToken->Event);
//
// Mark Task->FileIoToken to NULL so that the subtasks belonging to the task will be ignored.
//
Task->FileIoToken = NULL;
}
}
if (IsListEmpty (&Task->Subtasks)) {
RemoveEntryList (&Task->Link);
FreePool (Task);
}
}
/**
General disk access function.
@param Volume - FAT file system volume.
@param IoMode - The access mode (disk read/write or cache access).
@param Offset - The starting byte offset to read from.
@param BufferSize - Size of Buffer.
@param Buffer - Buffer containing read data.
@param Task point to task instance.
@retval EFI_SUCCESS - The operation is performed successfully.
@retval EFI_VOLUME_CORRUPTED - The access is
@return Others - The status of read/write the disk
**/
EFI_STATUS
FatDiskIo (
IN FAT_VOLUME *Volume,
IN IO_MODE IoMode,
IN UINT64 Offset,
IN UINTN BufferSize,
IN OUT VOID *Buffer,
IN FAT_TASK *Task
)
{
EFI_STATUS Status;
EFI_DISK_IO_PROTOCOL *DiskIo;
EFI_DISK_READ IoFunction;
FAT_SUBTASK *Subtask;
//
// Verify the IO is in devices range
//
Status = EFI_VOLUME_CORRUPTED;
if (Offset + BufferSize <= Volume->VolumeSize) {
if (Volume->CachingDisabled) {
//
// Caching has been turned off for this volume. Convert
// IO mode to raw disk access equivalent.
//
IoMode = (IO_MODE)RAW_ACCESS (IoMode);
}
if (CACHE_ENABLED (IoMode)) {
//
// Access cache
//
Status = FatAccessCache (Volume, CACHE_TYPE (IoMode), RAW_ACCESS (IoMode), Offset, BufferSize, Buffer, Task);
} else {
//
// Access disk directly
//
if (Task == NULL) {
//
// Blocking access
//
DiskIo = Volume->DiskIo;
IoFunction = (IoMode == ReadDisk) ? DiskIo->ReadDisk : DiskIo->WriteDisk;
Status = IoFunction (DiskIo, Volume->MediaId, Offset, BufferSize, Buffer);
} else {
//
// Non-blocking access
//
Subtask = AllocateZeroPool (sizeof (*Subtask));
if (Subtask == NULL) {
Status = EFI_OUT_OF_RESOURCES;
} else {
Subtask->Signature = FAT_SUBTASK_SIGNATURE;
Subtask->Task = Task;
Subtask->Write = (BOOLEAN)(IoMode == WriteDisk);
Subtask->Offset = Offset;
Subtask->Buffer = Buffer;
Subtask->BufferSize = BufferSize;
Status = gBS->CreateEvent (
EVT_NOTIFY_SIGNAL,
TPL_NOTIFY,
FatOnAccessComplete,
Subtask,
&Subtask->DiskIo2Token.Event
);
if (!EFI_ERROR (Status)) {
InsertTailList (&Task->Subtasks, &Subtask->Link);
} else {
FreePool (Subtask);
}
}
}
}
}
if (EFI_ERROR (Status)) {
Volume->DiskError = TRUE;
DEBUG ((DEBUG_ERROR, "FatDiskIo: error %r\n", Status));
}
return Status;
}
/**
Lock the volume.
**/
VOID
FatAcquireLock (
VOID
)
{
EfiAcquireLock (&FatFsLock);
}
/**
Lock the volume.
If the lock is already in the acquired state, then EFI_ACCESS_DENIED is returned.
Otherwise, EFI_SUCCESS is returned.
@retval EFI_SUCCESS - The volume is locked.
@retval EFI_ACCESS_DENIED - The volume could not be locked because it is already locked.
**/
EFI_STATUS
FatAcquireLockOrFail (
VOID
)
{
return EfiAcquireLockOrFail (&FatFsLock);
}
/**
Unlock the volume.
**/
VOID
FatReleaseLock (
VOID
)
{
EfiReleaseLock (&FatFsLock);
}
/**
Free directory entry.
@param DirEnt - The directory entry to be freed.
**/
VOID
FatFreeDirEnt (
IN FAT_DIRENT *DirEnt
)
{
if (DirEnt->FileString != NULL) {
FreePool (DirEnt->FileString);
}
FreePool (DirEnt);
}
/**
Pre-ExitBootServices notification, signaled once per FAT volume. Context
is the FAT_VOLUME this event was created for. This routine will flush any
dirty caches for the volume.
@param Event - The event that was signaled.
@param Context - The context of the event, which is the FAT_VOLUME for which to flush caches.
**/
VOID
EFIAPI
FatOnBeforeExitBootServices (
IN EFI_EVENT Event,
IN VOID *Context
)
{
FAT_VOLUME *Volume;
EFI_STATUS Status;
Volume = (FAT_VOLUME *)Context;
if ((Volume == NULL) || (Volume->Signature != FAT_VOLUME_SIGNATURE)) {
return;
}
if (!Volume->Valid || Volume->ReadOnly || Volume->DiskError) {
return;
}
Status = FatAcquireLockOrFail ();
if (EFI_ERROR (Status)) {
DEBUG ((DEBUG_WARN, "%a: FAT lock busy, skipping flush of %p\n", __func__, Volume->Handle));
return;
}
//
// Flush any dirty caches. This will still leave all handles valid in case
// other callback intend on using the file system protocol to flush high
// level data in pre-ExitBootServices. Those callers will just have to
// explicitly flush/close the handles.
//
Status = FatFlushDirtyCache (Volume, NULL);
if (EFI_ERROR (Status)) {
DEBUG ((
DEBUG_ERROR,
"%a: FatFlushDirtyCache on %p returned %r\n",
__func__,
Volume->Handle,
Status
));
}
//
// Disable caching from this point forward to ensure that any further writes
// don't get dropped.
//
Volume->CachingDisabled = TRUE;
FatReleaseLock ();
}
/**
Free volume structure (including the contents of directory cache and disk cache).
@param Volume - The volume structure to be freed.
**/
VOID
FatFreeVolume (
IN FAT_VOLUME *Volume
)
{
//
// Close the per-volume pre-ExitBootServices event.
//
if (Volume->FlushEvent != NULL) {
gBS->CloseEvent (Volume->FlushEvent);
Volume->FlushEvent = NULL;
}
//
// Free disk cache
//
if (Volume->CacheBuffer != NULL) {
FreePool (Volume->CacheBuffer);
}
//
// Free directory cache
//
FatCleanupODirCache (Volume);
FreePool (Volume);
}
/**
Translate EFI time to FAT time.
@param ETime - The time of EFI_TIME.
@param FTime - The time of FAT_DATE_TIME.
**/
VOID
FatEfiTimeToFatTime (
IN EFI_TIME *ETime,
OUT FAT_DATE_TIME *FTime
)
{
//
// ignores timezone info in source ETime
//
if (ETime->Year > 1980) {
FTime->Date.Year = (UINT16)(ETime->Year - 1980);
}
if (ETime->Year >= 1980 + FAT_MAX_YEAR_FROM_1980) {
FTime->Date.Year = FAT_MAX_YEAR_FROM_1980;
}
FTime->Date.Month = ETime->Month;
FTime->Date.Day = ETime->Day;
FTime->Time.Hour = ETime->Hour;
FTime->Time.Minute = ETime->Minute;
FTime->Time.DoubleSecond = (UINT16)(ETime->Second / 2);
}
/**
Translate Fat time to EFI time.
@param FTime - The time of FAT_DATE_TIME.
@param ETime - The time of EFI_TIME..
**/
VOID
FatFatTimeToEfiTime (
IN FAT_DATE_TIME *FTime,
OUT EFI_TIME *ETime
)
{
ETime->Year = (UINT16)(FTime->Date.Year + 1980);
ETime->Month = (UINT8)FTime->Date.Month;
ETime->Day = (UINT8)FTime->Date.Day;
ETime->Hour = (UINT8)FTime->Time.Hour;
ETime->Minute = (UINT8)FTime->Time.Minute;
ETime->Second = (UINT8)(FTime->Time.DoubleSecond * 2);
ETime->Nanosecond = 0;
ETime->TimeZone = EFI_UNSPECIFIED_TIMEZONE;
ETime->Daylight = 0;
}
/**
Get Current FAT time.
@param FatNow - Current FAT time.
**/
VOID
FatGetCurrentFatTime (
OUT FAT_DATE_TIME *FatNow
)
{
EFI_STATUS Status;
EFI_TIME Now;
Status = gRT->GetTime (&Now, NULL);
if (!EFI_ERROR (Status)) {
FatEfiTimeToFatTime (&Now, FatNow);
} else {
ZeroMem (&Now, sizeof (EFI_TIME));
Now.Year = 1980;
Now.Month = 1;
Now.Day = 1;
FatEfiTimeToFatTime (&Now, FatNow);
}
}
/**
Check whether a time is valid.
@param Time - The time of EFI_TIME.
@retval TRUE - The time is valid.
@retval FALSE - The time is not valid.
**/
BOOLEAN
FatIsValidTime (
IN EFI_TIME *Time
)
{
UINTN Day;
BOOLEAN ValidTime;
ValidTime = TRUE;
//
// Check the fields for range problems
// Fat can only support from 1980
//
if ((Time->Year < 1980) ||
(Time->Month < 1) ||
(Time->Month > 12) ||
(Time->Day < 1) ||
(Time->Day > 31) ||
(Time->Hour > 23) ||
(Time->Minute > 59) ||
(Time->Second > 59) ||
(Time->Nanosecond > 999999999)
)
{
ValidTime = FALSE;
} else {
//
// Perform a more specific check of the day of the month
//
Day = mMonthDays[Time->Month - 1];
if ((Time->Month == 2) && IS_LEAP_YEAR (Time->Year)) {
Day += 1;
//
// 1 extra day this month
//
}
if (Time->Day > Day) {
ValidTime = FALSE;
}
}
return ValidTime;
}