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