/** @file Miscellaneous functions. Copyright (c) 2005 - 2018, Intel Corporation. All rights reserved.
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 sucessfully. @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 sucessfully. @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 accesss 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 (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 ((EFI_D_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); } /** 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 ) { // // 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; }