| /** @file | |
| FAT file system access routines for FAT recovery PEIM | |
| Copyright (c) 2006 - 2018, Intel Corporation. All rights reserved.<BR> | |
| SPDX-License-Identifier: BSD-2-Clause-Patent | |
| **/ | |
| #include "FatLitePeim.h" | |
| /** | |
| Check if there is a valid FAT in the corresponding Block device | |
| of the volume and if yes, fill in the relevant fields for the | |
| volume structure. Note there should be a valid Block device number | |
| already set. | |
| @param PrivateData Global memory map for accessing global | |
| variables. | |
| @param Volume On input, the BlockDeviceNumber field of the | |
| Volume should be a valid value. On successful | |
| output, all fields except the VolumeNumber | |
| field is initialized. | |
| @retval EFI_SUCCESS A FAT is found and the volume structure is | |
| initialized. | |
| @retval EFI_NOT_FOUND There is no FAT on the corresponding device. | |
| @retval EFI_DEVICE_ERROR There is something error while accessing device. | |
| **/ | |
| EFI_STATUS | |
| FatGetBpbInfo ( | |
| IN PEI_FAT_PRIVATE_DATA *PrivateData, | |
| IN OUT PEI_FAT_VOLUME *Volume | |
| ) | |
| { | |
| EFI_STATUS Status; | |
| PEI_FAT_BOOT_SECTOR Bpb; | |
| PEI_FAT_BOOT_SECTOR_EX BpbEx; | |
| UINT32 Sectors; | |
| UINT32 SectorsPerFat; | |
| UINT32 RootDirSectors; | |
| UINT64 FatLba; | |
| UINT64 RootLba; | |
| UINT64 FirstClusterLba; | |
| // | |
| // Read in the BPB | |
| // | |
| Status = FatReadDisk ( | |
| PrivateData, | |
| Volume->BlockDeviceNo, | |
| 0, | |
| sizeof (PEI_FAT_BOOT_SECTOR_EX), | |
| &BpbEx | |
| ); | |
| if (EFI_ERROR (Status)) { | |
| return Status; | |
| } | |
| CopyMem ( | |
| (UINT8 *)(&Bpb), | |
| (UINT8 *)(&BpbEx), | |
| sizeof (PEI_FAT_BOOT_SECTOR) | |
| ); | |
| Volume->FatType = FatUnknown; | |
| Sectors = Bpb.Sectors; | |
| if (Sectors == 0) { | |
| Sectors = Bpb.LargeSectors; | |
| } | |
| SectorsPerFat = Bpb.SectorsPerFat; | |
| if (SectorsPerFat == 0) { | |
| SectorsPerFat = BpbEx.LargeSectorsPerFat; | |
| Volume->FatType = Fat32; | |
| } | |
| // | |
| // Filter out those not a FAT | |
| // | |
| if ((Bpb.Ia32Jump[0] != 0xe9) && (Bpb.Ia32Jump[0] != 0xeb) && (Bpb.Ia32Jump[0] != 0x49)) { | |
| return EFI_NOT_FOUND; | |
| } | |
| if ((Bpb.ReservedSectors == 0) || (Bpb.NoFats == 0) || (Sectors == 0)) { | |
| return EFI_NOT_FOUND; | |
| } | |
| if ((Bpb.SectorsPerCluster != 1) && | |
| (Bpb.SectorsPerCluster != 2) && | |
| (Bpb.SectorsPerCluster != 4) && | |
| (Bpb.SectorsPerCluster != 8) && | |
| (Bpb.SectorsPerCluster != 16) && | |
| (Bpb.SectorsPerCluster != 32) && | |
| (Bpb.SectorsPerCluster != 64) && | |
| (Bpb.SectorsPerCluster != 128) | |
| ) | |
| { | |
| return EFI_NOT_FOUND; | |
| } | |
| if ((Volume->FatType == Fat32) && ((SectorsPerFat == 0) || (BpbEx.FsVersion != 0))) { | |
| return EFI_NOT_FOUND; | |
| } | |
| if ((Bpb.Media != 0xf0) && | |
| (Bpb.Media != 0xf8) && | |
| (Bpb.Media != 0xf9) && | |
| (Bpb.Media != 0xfb) && | |
| (Bpb.Media != 0xfc) && | |
| (Bpb.Media != 0xfd) && | |
| (Bpb.Media != 0xfe) && | |
| (Bpb.Media != 0xff) && | |
| // | |
| // FujitsuFMR | |
| // | |
| (Bpb.Media != 0x00) && | |
| (Bpb.Media != 0x01) && | |
| (Bpb.Media != 0xfa) | |
| ) | |
| { | |
| return EFI_NOT_FOUND; | |
| } | |
| if ((Volume->FatType != Fat32) && (Bpb.RootEntries == 0)) { | |
| return EFI_NOT_FOUND; | |
| } | |
| // | |
| // If this is fat32, refuse to mount mirror-disabled volumes | |
| // | |
| if ((Volume->FatType == Fat32) && ((BpbEx.ExtendedFlags & 0x80) != 0)) { | |
| return EFI_NOT_FOUND; | |
| } | |
| // | |
| // Fill in the volume structure fields | |
| // (Sectors & SectorsPerFat is computed earlier already) | |
| // | |
| Volume->ClusterSize = Bpb.SectorSize * Bpb.SectorsPerCluster; | |
| Volume->RootEntries = Bpb.RootEntries; | |
| Volume->SectorSize = Bpb.SectorSize; | |
| RootDirSectors = ((Volume->RootEntries * sizeof (FAT_DIRECTORY_ENTRY)) + (Volume->SectorSize - 1)) / Volume->SectorSize; | |
| FatLba = Bpb.ReservedSectors; | |
| RootLba = Bpb.NoFats * SectorsPerFat + FatLba; | |
| FirstClusterLba = RootLba + RootDirSectors; | |
| Volume->VolumeSize = MultU64x32 (Sectors, Volume->SectorSize); | |
| Volume->FatPos = MultU64x32 (FatLba, Volume->SectorSize); | |
| Volume->RootDirPos = MultU64x32 (RootLba, Volume->SectorSize); | |
| Volume->FirstClusterPos = MultU64x32 (FirstClusterLba, Volume->SectorSize); | |
| Volume->MaxCluster = (UINT32)(Sectors - FirstClusterLba) / Bpb.SectorsPerCluster; | |
| Volume->RootDirCluster = BpbEx.RootDirFirstCluster; | |
| // | |
| // If this is not a fat32, determine if it's a fat16 or fat12 | |
| // | |
| if (Volume->FatType != Fat32) { | |
| if (Volume->MaxCluster >= 65525) { | |
| return EFI_NOT_FOUND; | |
| } | |
| Volume->FatType = Volume->MaxCluster < 4085 ? Fat12 : Fat16; | |
| } | |
| return EFI_SUCCESS; | |
| } | |
| /** | |
| Gets the next cluster in the cluster chain | |
| @param PrivateData Global memory map for accessing global variables | |
| @param Volume The volume | |
| @param Cluster The cluster | |
| @param NextCluster The cluster number of the next cluster | |
| @retval EFI_SUCCESS The address is got | |
| @retval EFI_INVALID_PARAMETER ClusterNo exceeds the MaxCluster of the volume. | |
| @retval EFI_DEVICE_ERROR Read disk error | |
| **/ | |
| EFI_STATUS | |
| FatGetNextCluster ( | |
| IN PEI_FAT_PRIVATE_DATA *PrivateData, | |
| IN PEI_FAT_VOLUME *Volume, | |
| IN UINT32 Cluster, | |
| OUT UINT32 *NextCluster | |
| ) | |
| { | |
| EFI_STATUS Status; | |
| UINT64 FatEntryPos; | |
| UINT32 Dummy; | |
| *NextCluster = 0; | |
| if (Volume->FatType == Fat32) { | |
| FatEntryPos = Volume->FatPos + MultU64x32 (4, Cluster); | |
| Status = FatReadDisk (PrivateData, Volume->BlockDeviceNo, FatEntryPos, 4, NextCluster); | |
| *NextCluster &= 0x0fffffff; | |
| // | |
| // Pad high bits for our FAT_CLUSTER_... macro definitions to work | |
| // | |
| if ((*NextCluster) >= 0x0ffffff7) { | |
| *NextCluster |= (-1 &~0xf); | |
| } | |
| } else if (Volume->FatType == Fat16) { | |
| FatEntryPos = Volume->FatPos + MultU64x32 (2, Cluster); | |
| Status = FatReadDisk (PrivateData, Volume->BlockDeviceNo, FatEntryPos, 2, NextCluster); | |
| // | |
| // Pad high bits for our FAT_CLUSTER_... macro definitions to work | |
| // | |
| if ((*NextCluster) >= 0xfff7) { | |
| *NextCluster |= (-1 &~0xf); | |
| } | |
| } else { | |
| FatEntryPos = Volume->FatPos + DivU64x32Remainder (MultU64x32 (3, Cluster), 2, &Dummy); | |
| Status = FatReadDisk (PrivateData, Volume->BlockDeviceNo, FatEntryPos, 2, NextCluster); | |
| if ((Cluster & 0x01) != 0) { | |
| *NextCluster = (*NextCluster) >> 4; | |
| } else { | |
| *NextCluster = (*NextCluster) & 0x0fff; | |
| } | |
| // | |
| // Pad high bits for our FAT_CLUSTER_... macro definitions to work | |
| // | |
| if ((*NextCluster) >= 0x0ff7) { | |
| *NextCluster |= (-1 &~0xf); | |
| } | |
| } | |
| if (EFI_ERROR (Status)) { | |
| return EFI_DEVICE_ERROR; | |
| } | |
| return EFI_SUCCESS; | |
| } | |
| /** | |
| Set a file's CurrentPos and CurrentCluster, then compute StraightReadAmount. | |
| @param PrivateData the global memory map | |
| @param File the file | |
| @param Pos the Position which is offset from the file's | |
| CurrentPos | |
| @retval EFI_SUCCESS Success. | |
| @retval EFI_INVALID_PARAMETER Pos is beyond file's size. | |
| @retval EFI_DEVICE_ERROR Something error while accessing media. | |
| **/ | |
| EFI_STATUS | |
| FatSetFilePos ( | |
| IN PEI_FAT_PRIVATE_DATA *PrivateData, | |
| IN PEI_FAT_FILE *File, | |
| IN UINT32 Pos | |
| ) | |
| { | |
| EFI_STATUS Status; | |
| UINT32 AlignedPos; | |
| UINT32 Offset; | |
| UINT32 Cluster; | |
| UINT32 PrevCluster; | |
| if (File->IsFixedRootDir) { | |
| if (Pos >= MultU64x32 (File->Volume->RootEntries, 32) - File->CurrentPos) { | |
| return EFI_INVALID_PARAMETER; | |
| } | |
| File->CurrentPos += Pos; | |
| File->StraightReadAmount = (UINT32)(MultU64x32 (File->Volume->RootEntries, 32) - File->CurrentPos); | |
| } else { | |
| DivU64x32Remainder (File->CurrentPos, File->Volume->ClusterSize, &Offset); | |
| AlignedPos = (UINT32)File->CurrentPos - (UINT32)Offset; | |
| while | |
| ( | |
| !FAT_CLUSTER_FUNCTIONAL (File->CurrentCluster) && | |
| AlignedPos + File->Volume->ClusterSize <= File->CurrentPos + Pos | |
| ) | |
| { | |
| AlignedPos += File->Volume->ClusterSize; | |
| Status = FatGetNextCluster ( | |
| PrivateData, | |
| File->Volume, | |
| File->CurrentCluster, | |
| &File->CurrentCluster | |
| ); | |
| if (EFI_ERROR (Status)) { | |
| return EFI_DEVICE_ERROR; | |
| } | |
| } | |
| if (FAT_CLUSTER_FUNCTIONAL (File->CurrentCluster)) { | |
| return EFI_INVALID_PARAMETER; | |
| } | |
| File->CurrentPos += Pos; | |
| // | |
| // Calculate the amount of consecutive cluster occupied by the file. | |
| // FatReadFile() will use it to read these blocks once. | |
| // | |
| File->StraightReadAmount = 0; | |
| Cluster = File->CurrentCluster; | |
| while (!FAT_CLUSTER_FUNCTIONAL (Cluster)) { | |
| File->StraightReadAmount += File->Volume->ClusterSize; | |
| PrevCluster = Cluster; | |
| Status = FatGetNextCluster (PrivateData, File->Volume, Cluster, &Cluster); | |
| if (EFI_ERROR (Status)) { | |
| return EFI_DEVICE_ERROR; | |
| } | |
| if (Cluster != PrevCluster + 1) { | |
| break; | |
| } | |
| } | |
| DivU64x32Remainder (File->CurrentPos, File->Volume->ClusterSize, &Offset); | |
| File->StraightReadAmount -= (UINT32)Offset; | |
| } | |
| return EFI_SUCCESS; | |
| } | |
| /** | |
| Reads file data. Updates the file's CurrentPos. | |
| @param PrivateData Global memory map for accessing global variables | |
| @param File The file. | |
| @param Size The amount of data to read. | |
| @param Buffer The buffer storing the data. | |
| @retval EFI_SUCCESS The data is read. | |
| @retval EFI_INVALID_PARAMETER File is invalid. | |
| @retval EFI_DEVICE_ERROR Something error while accessing media. | |
| **/ | |
| EFI_STATUS | |
| FatReadFile ( | |
| IN PEI_FAT_PRIVATE_DATA *PrivateData, | |
| IN PEI_FAT_FILE *File, | |
| IN UINTN Size, | |
| OUT VOID *Buffer | |
| ) | |
| { | |
| EFI_STATUS Status; | |
| CHAR8 *BufferPtr; | |
| UINT32 Offset; | |
| UINT64 PhysicalAddr; | |
| UINTN Amount; | |
| BufferPtr = Buffer; | |
| if (File->IsFixedRootDir) { | |
| // | |
| // This is the fixed root dir in FAT12 and FAT16 | |
| // | |
| if (File->CurrentPos + Size > File->Volume->RootEntries * sizeof (FAT_DIRECTORY_ENTRY)) { | |
| return EFI_INVALID_PARAMETER; | |
| } | |
| Status = FatReadDisk ( | |
| PrivateData, | |
| File->Volume->BlockDeviceNo, | |
| File->Volume->RootDirPos + File->CurrentPos, | |
| Size, | |
| Buffer | |
| ); | |
| File->CurrentPos += (UINT32)Size; | |
| return Status; | |
| } else { | |
| if ((File->Attributes & FAT_ATTR_DIRECTORY) == 0) { | |
| Size = Size < (File->FileSize - File->CurrentPos) ? Size : (File->FileSize - File->CurrentPos); | |
| } | |
| // | |
| // This is a normal cluster based file | |
| // | |
| while (Size != 0) { | |
| DivU64x32Remainder (File->CurrentPos, File->Volume->ClusterSize, &Offset); | |
| PhysicalAddr = File->Volume->FirstClusterPos + MultU64x32 (File->Volume->ClusterSize, File->CurrentCluster - 2); | |
| Amount = File->StraightReadAmount; | |
| Amount = Size > Amount ? Amount : Size; | |
| Status = FatReadDisk ( | |
| PrivateData, | |
| File->Volume->BlockDeviceNo, | |
| PhysicalAddr + Offset, | |
| Amount, | |
| BufferPtr | |
| ); | |
| if (EFI_ERROR (Status)) { | |
| return EFI_DEVICE_ERROR; | |
| } | |
| // | |
| // Advance the file's current pos and current cluster | |
| // | |
| FatSetFilePos (PrivateData, File, (UINT32)Amount); | |
| BufferPtr += Amount; | |
| Size -= Amount; | |
| } | |
| return EFI_SUCCESS; | |
| } | |
| } | |
| /** | |
| This function reads the next item in the parent directory and | |
| initializes the output parameter SubFile (CurrentPos is initialized to 0). | |
| The function updates the CurrentPos of the parent dir to after the item read. | |
| If no more items were found, the function returns EFI_NOT_FOUND. | |
| @param PrivateData Global memory map for accessing global variables | |
| @param ParentDir The parent directory. | |
| @param SubFile The File structure containing the sub file that | |
| is caught. | |
| @retval EFI_SUCCESS The next sub file is obtained. | |
| @retval EFI_INVALID_PARAMETER The ParentDir is not a directory. | |
| @retval EFI_NOT_FOUND No more sub file exists. | |
| @retval EFI_DEVICE_ERROR Something error while accessing media. | |
| **/ | |
| EFI_STATUS | |
| FatReadNextDirectoryEntry ( | |
| IN PEI_FAT_PRIVATE_DATA *PrivateData, | |
| IN PEI_FAT_FILE *ParentDir, | |
| OUT PEI_FAT_FILE *SubFile | |
| ) | |
| { | |
| EFI_STATUS Status; | |
| FAT_DIRECTORY_ENTRY DirEntry; | |
| CHAR16 *Pos; | |
| CHAR16 BaseName[9]; | |
| CHAR16 Ext[4]; | |
| ZeroMem ((UINT8 *)SubFile, sizeof (PEI_FAT_FILE)); | |
| // | |
| // Pick a valid directory entry | |
| // | |
| while (1) { | |
| // | |
| // Read one entry | |
| // | |
| Status = FatReadFile (PrivateData, ParentDir, 32, &DirEntry); | |
| if (EFI_ERROR (Status)) { | |
| return EFI_DEVICE_ERROR; | |
| } | |
| // | |
| // We only search for *FILE* in root directory | |
| // Long file name entry is *NOT* supported | |
| // | |
| if (((DirEntry.Attributes & FAT_ATTR_DIRECTORY) == FAT_ATTR_DIRECTORY) || (DirEntry.Attributes == FAT_ATTR_LFN)) { | |
| continue; | |
| } | |
| // | |
| // if this is a terminator dir entry, just return EFI_NOT_FOUND | |
| // | |
| if (DirEntry.FileName[0] == EMPTY_ENTRY_MARK) { | |
| return EFI_NOT_FOUND; | |
| } | |
| // | |
| // If this not an invalid entry neither an empty entry, this is what we want. | |
| // otherwise we will start a new loop to continue to find something meaningful | |
| // | |
| if ((UINT8)DirEntry.FileName[0] != DELETE_ENTRY_MARK) { | |
| break; | |
| } | |
| } | |
| // | |
| // fill in the output parameter | |
| // | |
| EngFatToStr (8, DirEntry.FileName, BaseName); | |
| EngFatToStr (3, DirEntry.FileName + 8, Ext); | |
| Pos = (UINT16 *)SubFile->FileName; | |
| SetMem ((UINT8 *)Pos, FAT_MAX_FILE_NAME_LENGTH, 0); | |
| CopyMem ((UINT8 *)Pos, (UINT8 *)BaseName, 2 * (StrLen (BaseName) + 1)); | |
| if (Ext[0] != 0) { | |
| Pos += StrLen (BaseName); | |
| *Pos = '.'; | |
| Pos++; | |
| CopyMem ((UINT8 *)Pos, (UINT8 *)Ext, 2 * (StrLen (Ext) + 1)); | |
| } | |
| SubFile->Attributes = DirEntry.Attributes; | |
| SubFile->CurrentCluster = DirEntry.FileCluster; | |
| if (ParentDir->Volume->FatType == Fat32) { | |
| SubFile->CurrentCluster |= DirEntry.FileClusterHigh << 16; | |
| } | |
| SubFile->CurrentPos = 0; | |
| SubFile->FileSize = DirEntry.FileSize; | |
| SubFile->StartingCluster = SubFile->CurrentCluster; | |
| SubFile->Volume = ParentDir->Volume; | |
| // | |
| // in Pei phase, time parameters do not need to be filled for minimum use. | |
| // | |
| return Status; | |
| } |