/** @file | |
FAT file system access routines for FAT recovery PEIM | |
Copyright (c) 2006 - 2018, Intel Corporation. All rights reserved.<BR> | |
This program and the accompanying materials are licensed and made available | |
under the terms and conditions of the BSD License which accompanies this | |
distribution. The full text of the license may be found at | |
http://opensource.org/licenses/bsd-license.php | |
THE PROGRAM IS DISTRIBUTED UNDER THE BSD LICENSE ON AN "AS IS" BASIS, | |
WITHOUT WARRANTIES OR REPRESENTATIONS OF ANY KIND, EITHER EXPRESS OR IMPLIED. | |
**/ | |
#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; | |
} |