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>
This commit is contained in:
Chris Fernald 2026-06-19 12:33:56 -07:00 committed by mergify[bot]
parent c3ba0416ce
commit 12c5ded287
5 changed files with 204 additions and 29 deletions

View file

@ -13,6 +13,7 @@ SPDX-License-Identifier: BSD-2-Clause-Patent
#include <Guid/FileInfo.h>
#include <Guid/FileSystemInfo.h>
#include <Guid/FileSystemVolumeLabelInfo.h>
#include <Guid/EventGroup.h>
#include <Protocol/BlockIo.h>
#include <Protocol/DiskIo.h>
#include <Protocol/DiskIo2.h>
@ -388,6 +389,16 @@ struct _FAT_VOLUME {
//
VOID *CacheBuffer;
DISK_CACHE DiskCache[CacheMaxType];
//
// Event signaled before ExitBootServices that flushes any dirty caches.
//
EFI_EVENT FlushEvent;
//
// A flag that disables caching on this volume.
//
BOOLEAN CachingDisabled;
};
//
@ -847,6 +858,24 @@ FatIFileClose (
FAT_IFILE *IFile
);
/**
Write back any dirty FAT metadata and disk-cache pages for the volume to
the underlying media.
@param Volume - The volume whose dirty cache should be flushed.
@param Task - Point to task instance, may be NULL.
@retval EFI_SUCCESS - Any dirty caches were flushed.
@return Others - An I/O error occurred while writing back.
**/
EFI_STATUS
FatFlushDirtyCache (
IN FAT_VOLUME *Volume,
IN FAT_TASK *Task
);
/**
Set error status for a specific OFile, reference checking the volume.
@ -2029,3 +2058,17 @@ extern EFI_COMPONENT_NAME2_PROTOCOL gFatComponentName2;
extern EFI_LOCK FatFsLock;
extern EFI_LOCK FatTaskLock;
extern EFI_FILE_PROTOCOL FatFileInterface;
/**
Notification callback for the pre-ExitBootServices event to flush any dirty caches.
@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
);

View file

@ -72,6 +72,7 @@
gEfiFileInfoGuid ## SOMETIMES_CONSUMES ## UNDEFINED
gEfiFileSystemInfoGuid ## SOMETIMES_CONSUMES ## UNDEFINED
gEfiFileSystemVolumeLabelInfoIdGuid ## SOMETIMES_CONSUMES ## UNDEFINED
gEfiEventBeforeExitBootServicesGuid ## CONSUMES ## Event
[Protocols]
gEfiDiskIoProtocolGuid ## TO_START

View file

@ -364,6 +364,57 @@ FatCheckVolumeRef (
/**
Write back any dirty FAT metadata and disk-cache pages for the volume to
the underlying media.
@param Volume - The volume whose dirty cache should be flushed.
@param Task - Point to task instance, may be NULL.
@retval EFI_SUCCESS - Any dirty caches were flushed.
@return Others - An I/O error occurred while writing back.
**/
EFI_STATUS
FatFlushDirtyCache (
IN FAT_VOLUME *Volume,
IN FAT_TASK *Task
)
{
EFI_STATUS Status;
if (!Volume->Valid) {
return EFI_SUCCESS;
}
//
// Update the free hint info. Volume->FreeInfoPos != 0
// indicates this a FAT32 volume
//
if (Volume->FreeInfoValid && Volume->FatDirty && Volume->FreeInfoPos) {
Status = FatDiskIo (Volume, WriteDisk, Volume->FreeInfoPos, sizeof (FAT_INFO_SECTOR), &Volume->FatInfoSector, Task);
if (EFI_ERROR (Status)) {
return Status;
}
}
//
// Update that the volume is not dirty
//
if (Volume->FatDirty && (Volume->FatType != Fat12)) {
Volume->FatDirty = FALSE;
Status = FatAccessVolumeDirty (Volume, WriteFat, &Volume->NotDirtyValue);
if (EFI_ERROR (Status)) {
return Status;
}
}
//
// Flush all dirty cache entries to disk
//
return FatVolumeFlushCache (Volume, Task);
}
/**
Set error status for a specific OFile, reference checking the volume.
If volume is already marked as invalid, and all resources are freed
after reference checking, the file system protocol is uninstalled and
@ -401,37 +452,15 @@ FatCleanupVolume (
// volume be cleaned up even the volume is invalid.
//
FatCheckVolumeRef (Volume);
if (Volume->Valid) {
//
// Update the free hint info. Volume->FreeInfoPos != 0
// indicates this a FAT32 volume
//
if (Volume->FreeInfoValid && Volume->FatDirty && Volume->FreeInfoPos) {
Status = FatDiskIo (Volume, WriteDisk, Volume->FreeInfoPos, sizeof (FAT_INFO_SECTOR), &Volume->FatInfoSector, Task);
if (EFI_ERROR (Status)) {
return Status;
}
}
//
// Update that the volume is not dirty
// Write back any dirty FAT metadata and disk-cache pages. No-op when the
// volume is no longer Valid.
//
if (Volume->FatDirty && (Volume->FatType != Fat12)) {
Volume->FatDirty = FALSE;
Status = FatAccessVolumeDirty (Volume, WriteFat, &Volume->NotDirtyValue);
Status = FatFlushDirtyCache (Volume, Task);
if (EFI_ERROR (Status)) {
return Status;
}
}
//
// Flush all dirty cache entries to disk
//
Status = FatVolumeFlushCache (Volume, Task);
if (EFI_ERROR (Status)) {
return Status;
}
}
//
// If the volume is cleared , remove it.

View file

@ -122,6 +122,30 @@ FatAllocateVolume (
DEBUG ((DEBUG_INIT, "Installed Fat filesystem on %p\n", Handle));
Volume->Valid = TRUE;
//
// Create a pre-ExitBootServices event for this volume to flush any dirty caches so
// they are not lost. before ExitBootServices is required because the underlying
// Block-io and device protocols may not be available later than this.
//
Status = gBS->CreateEventEx (
EVT_NOTIFY_SIGNAL,
TPL_CALLBACK,
FatOnBeforeExitBootServices,
Volume,
&gEfiEventBeforeExitBootServicesGuid,
&Volume->FlushEvent
);
if (EFI_ERROR (Status)) {
DEBUG ((
DEBUG_WARN,
"%a: CreateEventEx for pre-ExitBootServices failed (%r), dirty caches will not be flushed on exit!\n",
__func__,
Status
));
Volume->FlushEvent = NULL;
Status = EFI_SUCCESS;
}
Done:
if (EFI_ERROR (Status)) {
FatFreeVolume (Volume);

View file

@ -331,6 +331,14 @@ FatDiskIo (
//
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
@ -449,6 +457,68 @@ FatFreeDirEnt (
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).
@ -461,6 +531,14 @@ 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
//