/** @file Copyright (c) 2024, Arm Limited. All rights reserved.
SPDX-License-Identifier: BSD-2-Clause-Patent @par Reference(s): - GUID Partition Table (GPT) Disk Layout (https://uefi.org/specs/UEFI/2.10/05_GUID_Partition_Table_Format.html) **/ #include #include #include #include #include #include #include #include #include "DiskGpt.h" /** Calculate the extra size required to align a total size to a block boundary. @param[in] TotalSize The current total size in bytes. @param[in] BlockSize The block size to align to (must be non-zero). @retval The number of extra bytes required for alignment. **/ #define GET_EXTRA_BLOCK_ALIGN(TotalSize, BlockSize) \ (BlockSize - (TotalSize % BlockSize)) /** Retrieve the GPT partition header. @param[in] GptHandle GptHandle. @param[out] Header A pointer to the buffer where the GPT header is written to. @retval EFI_SUCCESS The GPT header was successfully written to Header. @retval Error The GPT header was not correctly read. **/ STATIC EFI_STATUS EFIAPI GetGptPartitionHeader ( IN GPT_PARTITION_HANDLE *GptHandle, OUT EFI_PARTITION_TABLE_HEADER *Header ) { EFI_STATUS Status; if ((GptHandle == NULL) || (GptHandle->Instance == NULL) || (Header == NULL)) { DEBUG ((DEBUG_ERROR, "GetGptPartitionHeader: Invalid parameters\n")); return EFI_INVALID_PARAMETER; } Status = GptReadPartition ( GptHandle, Header, sizeof (EFI_PARTITION_TABLE_HEADER), GPT_PROTECTIVE_MBR_SIZE, 0 ); if (EFI_ERROR (Status)) { DEBUG ((DEBUG_ERROR, "GetGptPartitionHeader: Failed to read GPT partition info\n")); } return Status; } /** Print the contents of the GPT header. @param[in] Header The GPT header to be printed. **/ STATIC VOID EFIAPI PrintGptHeader ( EFI_PARTITION_TABLE_HEADER *Header ) { DEBUG ((DEBUG_BLKIO, "--GPT Header--------------------------\n")); DEBUG ((DEBUG_BLKIO, "Signature %x\n", Header->Header.Signature)); DEBUG ((DEBUG_BLKIO, "Revision %x\n", Header->Header.Revision)); DEBUG ((DEBUG_BLKIO, "Header Lba %x\n", Header->MyLBA)); DEBUG ((DEBUG_BLKIO, "Disk GUID %g\n", &Header->DiskGUID)); DEBUG ((DEBUG_BLKIO, "Partition entry Lba %x\n", Header->PartitionEntryLBA)); DEBUG ((DEBUG_BLKIO, "Num partition entries %x\n", Header->NumberOfPartitionEntries)); DEBUG ((DEBUG_BLKIO, "Size of partition entry %x\n", Header->SizeOfPartitionEntry)); DEBUG ((DEBUG_BLKIO, "--------------------------------------\n")); } /** Read "ReadSize" bytes from a partition at "ReadOffset" bytes from its start. @param[in] GptHandle GptHandle. @param[out] DestBuffer Pointer to the buffer that will store the data read. @param[in] ReadSize The number of bytes to read. @param[in] ReadOffset The offset to read from within the partition. @param[in] BaseLba The start Lba of the partition. @retval EFI_SUCCESS ReadSize bytes read successfully. @retval Error No bytes read. **/ EFI_STATUS EFIAPI GptReadPartition ( IN GPT_PARTITION_HANDLE *GptHandle, OUT VOID *DestBuffer, IN UINTN ReadSize, IN UINTN ReadOffset, IN UINTN BaseLba ) { EFI_STATUS Status; EFI_BLOCK_IO_PROTOCOL *Instance; EFI_BLOCK_IO_MEDIA *Media; UINTN ReadAlignment; UINTN AuxBufferSize; VOID *AuxBuffer; UINTN Lba; UINTN GptOffset; // Read data offset based on GPT block size UINTN DeviceBlockOffset; // Read data offset in the device block if ((GptHandle == NULL) || (GptHandle->Instance == NULL) || (DestBuffer == NULL) || (ReadSize == 0)) { DEBUG ((DEBUG_ERROR, "GptReadPartition: Invalid parameters\n")); return EFI_INVALID_PARAMETER; } Instance = GptHandle->Instance; Media = Instance->Media; GptOffset = ((BaseLba * GPT_PARTITION_LBA_SIZE) + ReadOffset); Lba = GptOffset / Media->BlockSize; DeviceBlockOffset = GptOffset - (Lba * Media->BlockSize); ReadAlignment = GET_EXTRA_BLOCK_ALIGN ((DeviceBlockOffset + ReadSize), Media->BlockSize); AuxBufferSize = DeviceBlockOffset + ReadSize + ReadAlignment; Status = gMmst->MmAllocatePool (EfiRuntimeServicesData, AuxBufferSize, (VOID **)&AuxBuffer); if (EFI_ERROR (Status)) { DEBUG ((DEBUG_ERROR, "Not enough resources to complete read request\n")); return Status; } // read from flash Status = Instance->ReadBlocks ( Instance, Media->MediaId, Lba, AuxBufferSize, AuxBuffer ); if (EFI_ERROR (Status)) { DEBUG (( DEBUG_ERROR, "GptReadPartition: Flash read failed Lba %x writeSize %x\n", Lba, AuxBufferSize )); goto ErrorHandler; } CopyMem (DestBuffer, AuxBuffer + DeviceBlockOffset, ReadSize); ErrorHandler: gMmst->MmFreePool (AuxBuffer); return Status; } /** Write "BufferSize" bytes to a partition at "WriteOffset" bytes from its start. @param[in] GptHandle GptHandle. @param[in] SrcBuffer Pointer to the buffer with the data to be written. @param[in] BufferSize The number of bytes to write. @param[in] WriteOffset The offset to write to within the partition. @param[in] BaseLba The start Lba of the partition. @retval EFI_SUCCESS BufferSize bytes written successfully. @retval Error No bytes written. **/ EFI_STATUS EFIAPI GptWritePartition ( IN GPT_PARTITION_HANDLE *GptHandle, IN VOID *SrcBuffer, IN UINTN BufferSize, IN UINTN WriteOffset, IN UINTN BaseLba ) { EFI_STATUS Status; EFI_BLOCK_IO_PROTOCOL *Instance; EFI_BLOCK_IO_MEDIA *Media; UINTN WriteAlignment; UINTN AuxBufferSize; VOID *AuxBuffer; UINTN Lba; UINTN GptOffset; // Write data offset based on GPT block size UINTN DeviceBlockOffset; // Write data offset in the device block if ((GptHandle == NULL) || (GptHandle->Instance == NULL) || (SrcBuffer == NULL) || (BufferSize == 0)) { DEBUG ((DEBUG_INFO, "GptWritePartition: Invalid parameters\n")); return EFI_INVALID_PARAMETER; } Instance = GptHandle->Instance; Media = Instance->Media; GptOffset = ((BaseLba * GPT_PARTITION_LBA_SIZE) + WriteOffset); Lba = GptOffset / Media->BlockSize; DeviceBlockOffset = GptOffset - (Lba * Media->BlockSize); WriteAlignment = GET_EXTRA_BLOCK_ALIGN ((DeviceBlockOffset + BufferSize), Media->BlockSize); AuxBufferSize = DeviceBlockOffset + BufferSize + WriteAlignment; Status = gMmst->MmAllocatePool (EfiRuntimeServicesData, AuxBufferSize, (VOID **)&AuxBuffer); if (EFI_ERROR (Status)) { DEBUG ((DEBUG_ERROR, "Not enough resources to complete write request\n")); return Status; } // read from flash Status = Instance->ReadBlocks ( Instance, Media->MediaId, Lba, AuxBufferSize, AuxBuffer ); if (EFI_ERROR (Status)) { DEBUG (( DEBUG_ERROR, "GptWritePartition: Flash read failed Lba %x writeSize %x\n", Lba, AuxBufferSize )); goto ErrorHandler; } // Apply the changes to the aux buffer, commit these to flash. CopyMem (AuxBuffer + DeviceBlockOffset, SrcBuffer, BufferSize); // Write to flash Status = Instance->WriteBlocks ( Instance, Media->MediaId, Lba, AuxBufferSize, AuxBuffer ); if (EFI_ERROR (Status)) { DEBUG ((DEBUG_ERROR, "GptWritePartition: Flash write failed Lba %x writeSize %x\n", Lba, BufferSize)); } ErrorHandler: gMmst->MmFreePool (AuxBuffer); return Status; } /** Retrieve first matched GPT partition entry information. @param[in] GptHandle GptHandle. @param[in] Match Matching function to find out specific GPT partition. @param[in] Data Data used in @Match to find out specific GPT partition. @param[out] StartLBA The StartLBA of matched partition. @param[out] PartSize Size of matched partition in bytes. @retval EFI_SUCCESS The GPT partition array was correctly retrieved. @retval Error No information was correctly retrieved. **/ EFI_STATUS EFIAPI GptGetMatchedPartitionStats ( IN GPT_PARTITION_HANDLE *GptHandle, IN MATCH_FUNC_T Match, IN VOID *Data, OUT EFI_LBA *StartLBA, OUT UINTN *PartSize ) { EFI_STATUS Status; EFI_BLOCK_IO_PROTOCOL *Instance; UINTN Idx; CONST EFI_PARTITION_ENTRY *Entry; GPT_PARTITION_TABLE *GptTable; if ((GptHandle == NULL) || (GptHandle->Instance == NULL) || (GptHandle->GptTable == NULL) || (Match == NULL) || (Data == NULL)) { return EFI_INVALID_PARAMETER; } Status = EFI_NOT_FOUND; Instance = GptHandle->Instance; GptTable = GptHandle->GptTable; for (Idx = 0; Idx < GptTable->Header.NumberOfPartitionEntries; Idx++) { if (Match (&GptTable->PartitionTable[Idx], Data)) { Status = EFI_SUCCESS; Entry = &GptTable->PartitionTable[Idx]; if (StartLBA != NULL) { *StartLBA = Entry->StartingLBA; } if (PartSize != NULL) { *PartSize = (Entry->EndingLBA - Entry->StartingLBA + 1) * Instance->Media->BlockSize; } break; } } return Status; } /** Release GptHandle. @param[in] GptHandle Gpt Handle. **/ VOID EFIAPI GptReleasePartition ( IN GPT_PARTITION_HANDLE *GptHandle ) { if (GptHandle != NULL) { if (GptHandle->GptTable != NULL) { gMmst->MmFreePool (GptHandle->GptTable); GptHandle->GptTable = NULL; } GptHandle->Instance = NULL; gMmst->MmFreePool (GptHandle); } } /** Open GptHandle. @param[in] Instance The block IO protocol instance containing a GPT. @param[out] GptHandle Gpt Handle. @retval EFI_SUCCESS Success. @retval EFI_INVALID_PARAMETER Invalid Parameter. @retval EFI_ABORT Fail to read Gpt Table Header or Invalid Gpt Table. @retval EFI_OUT_OF_RESOURCES Out of memory. **/ EFI_STATUS EFIAPI GptOpenPartition ( IN EFI_BLOCK_IO_PROTOCOL *Instance, OUT GPT_PARTITION_HANDLE **GptHandle ) { EFI_STATUS Status; EFI_PARTITION_TABLE_HEADER Header; UINTN GptTableSize; UINTN GptPartitionArraySize; GPT_PARTITION_HANDLE *TmpGptHandle; if ((Instance == NULL) || (GptHandle == NULL)) { return EFI_INVALID_PARAMETER; } Status = gMmst->MmAllocatePool ( EfiRuntimeServicesData, sizeof (GPT_PARTITION_HANDLE), (VOID **)&TmpGptHandle ); if (EFI_ERROR (Status)) { return Status; } TmpGptHandle->Instance = Instance; TmpGptHandle->GptTable = NULL; // Retrieve partition header Status = GetGptPartitionHeader (TmpGptHandle, &Header); if (EFI_ERROR (Status)) { DEBUG ((DEBUG_ERROR, "Init fail, cannot retrieve GPT header\n")); return EFI_ABORTED; } PrintGptHeader (&Header); // The copy in-place of the GPT partition entry below assumes that the size of each // Partition entry == sizeof(EFI_PARTITION_ENTRY). // Abort if that assumption does not hold. if (Header.SizeOfPartitionEntry != sizeof (EFI_PARTITION_ENTRY)) { Status = EFI_ABORTED; DEBUG ((DEBUG_ERROR, "Init fail, partition size mismatch (%x)\n", Header.SizeOfPartitionEntry)); goto ErrorHandler; } GptPartitionArraySize = Header.NumberOfPartitionEntries * Header.SizeOfPartitionEntry; GptTableSize = GptPartitionArraySize + sizeof (EFI_PARTITION_TABLE_HEADER); Status = gMmst->MmAllocatePool ( EfiRuntimeServicesData, GptTableSize, (VOID **)&TmpGptHandle->GptTable ); if (EFI_ERROR (Status)) { DEBUG ((DEBUG_ERROR, "Init fail, cannot allocate memory for cached GPT header\n")); goto ErrorHandler; } CopyMem (TmpGptHandle->GptTable, &Header, sizeof (Header)); // Read The GPT partition array from flash (the read size must be a block size multiple) Status = GptReadPartition ( TmpGptHandle, TmpGptHandle->GptTable->PartitionTable, GptPartitionArraySize, 0, Header.PartitionEntryLBA ); if (Header.SizeOfPartitionEntry != sizeof (EFI_PARTITION_ENTRY)) { Status = EFI_ABORTED; DEBUG ((DEBUG_ERROR, "GptInit: Failed to read GPT partition table\n")); goto ErrorHandler; } *GptHandle = TmpGptHandle; return EFI_SUCCESS; ErrorHandler: if (TmpGptHandle) { if (TmpGptHandle->GptTable) { gMmst->MmFreePool (TmpGptHandle->GptTable); } gMmst->MmFreePool (TmpGptHandle); } *GptHandle = NULL; return Status; }