/*++

Copyright (c) 2014 Minoca Corp. All Rights Reserved

Module Name:

    partgpt.c

Abstract:

    This module implements UEFI GPT partition support.

Author:

    Evan Green 19-Mar-2014

Environment:

    Firmware

--*/

//
// ------------------------------------------------------------------- Includes
//

#include "ueficore.h"
#include "part.h"

//
// ---------------------------------------------------------------- Definitions
//

//
// ------------------------------------------------------ Data Type Definitions
//

//
// ----------------------------------------------- Internal Function Prototypes
//

BOOLEAN
EfipPartitionValidGptTable (
    EFI_BLOCK_IO_PROTOCOL *BlockIo,
    EFI_DISK_IO_PROTOCOL *DiskIo,
    EFI_LBA Lba,
    EFI_PARTITION_TABLE_HEADER *PartitionHeader
    );

BOOLEAN
EfipPartitionCheckCrc (
    UINTN MaxSize,
    EFI_TABLE_HEADER *Header
    );

BOOLEAN
EfipPartitionCheckPartitionEntriesCrc (
    EFI_BLOCK_IO_PROTOCOL *BlockIo,
    EFI_DISK_IO_PROTOCOL *DiskIo,
    EFI_PARTITION_TABLE_HEADER *PartitionHeader
    );

VOID
EfipPartitionCheckGptEntries (
    EFI_PARTITION_TABLE_HEADER *Header,
    EFI_PARTITION_ENTRY *Entries,
    EFI_PARTITION_ENTRY_STATUS *EntryStatus
    );

//
// -------------------------------------------------------------------- Globals
//

EFI_GUID EfiPartitionTypeUnusedGuid = EFI_PARTITION_TYPE_UNUSED_GUID;
EFI_GUID EfiPartitionTypeSystemPartitionGuid =
                                            EFI_PARTITION_TYPE_EFI_SYSTEM_GUID;

//
// ------------------------------------------------------------------ Functions
//

EFI_STATUS
EfiPartitionDetectGpt (
    EFI_DRIVER_BINDING_PROTOCOL *This,
    EFI_HANDLE Handle,
    EFI_DISK_IO_PROTOCOL *DiskIo,
    EFI_BLOCK_IO_PROTOCOL *BlockIo,
    EFI_DEVICE_PATH_PROTOCOL *DevicePath
    )

/*++

Routine Description:

    This routine attempts to detect a GPT partitioned disk, and exposes child
    block devices for each partition it finds.

Arguments:

    This - Supplies the driver binding protocol instance.

    Handle - Supplies the new controller handle.

    DiskIo - Supplies a pointer to the disk I/O protocol.

    BlockIo - Supplies a pointer to the block I/O protocol.

    DevicePath - Supplies a pointer to the device path.

Return Value:

    EFI status code.

--*/

{

    PEFI_PARTITION_TABLE_HEADER BackupHeader;
    UINT32 BlockSize;
    HARDDRIVE_DEVICE_PATH DrivePath;
    UINT32 EntriesSize;
    PEFI_PARTITION_ENTRY Entry;
    EFI_STATUS GptValidStatus;
    UINTN Index;
    EFI_LBA LastBlock;
    BOOLEAN Match;
    UINT32 MediaId;
    PEFI_PARTITION_ENTRY PartitionEntry;
    PEFI_PARTITION_ENTRY_STATUS PartitionEntryStatus;
    UINTN PartitionEntryStatusSize;
    PEFI_PARTITION_TABLE_HEADER PrimaryHeader;
    PEFI_MASTER_BOOT_RECORD ProtectiveMbr;
    EFI_STATUS Status;
    BOOLEAN SystemPartition;
    BOOLEAN Valid;

    ProtectiveMbr = NULL;
    PrimaryHeader = NULL;
    BackupHeader = NULL;
    PartitionEntry = NULL;
    PartitionEntryStatus = NULL;
    BlockSize = BlockIo->Media->BlockSize;
    LastBlock = BlockIo->Media->LastBlock;
    MediaId = BlockIo->Media->MediaId;
    GptValidStatus = EFI_NOT_FOUND;
    ProtectiveMbr = EfiCoreAllocateBootPool(BlockSize);
    if (ProtectiveMbr == NULL) {
        return EFI_NOT_FOUND;
    }

    //
    // Read the protective MBR from LBA zero.
    //

    Status = DiskIo->ReadDisk(DiskIo,
                              MediaId,
                              0,
                              BlockSize,
                              ProtectiveMbr);

    if (EFI_ERROR(Status)) {
        GptValidStatus = Status;
        goto PartitionDetectGptEnd;
    }

    //
    // Verify that the protective MBR is valid.
    //

    for (Index = 0; Index < EFI_MAX_MBR_PARTITIONS; Index += 1) {
        if ((ProtectiveMbr->Partition[Index].BootIndicator == 0x00) &&
            (ProtectiveMbr->Partition[Index].OsIndicator ==
             EFI_PROTECTIVE_MBR_PARTITION) &&
            (EFI_UNPACK_UINT32(ProtectiveMbr->Partition[Index].StartingLba) ==
             1)) {

            break;
        }
    }

    if (Index == EFI_MAX_MBR_PARTITIONS) {
        goto PartitionDetectGptEnd;
    }

    //
    // Allocate the GPT structures.
    //

    PrimaryHeader = EfiCoreAllocateBootPool(sizeof(EFI_PARTITION_TABLE_HEADER));
    if (PrimaryHeader == NULL) {
        goto PartitionDetectGptEnd;
    }

    BackupHeader = EfiCoreAllocateBootPool(sizeof(EFI_PARTITION_TABLE_HEADER));
    if (BackupHeader == NULL) {
        goto PartitionDetectGptEnd;
    }

    //
    // Check the primary and backup headers.
    //

    Valid = EfipPartitionValidGptTable(BlockIo,
                                       DiskIo,
                                       EFI_PRIMARY_PARTITION_HEADER_LBA,
                                       PrimaryHeader);

    if (Valid == FALSE) {
        Valid = EfipPartitionValidGptTable(BlockIo,
                                           DiskIo,
                                           LastBlock,
                                           BackupHeader);

        //
        // End now if neither the primary nor the backup header was valid.
        //

        if (Valid == FALSE) {
            goto PartitionDetectGptEnd;

        //
        // The primary header was bad but the backup header is valid.
        //

        } else {
            RtlDebugPrint("Warning: Primary GPT header was bad, using backup "
                          "header.\n");

            EfiCopyMem(PrimaryHeader,
                       BackupHeader,
                       sizeof(EFI_PARTITION_TABLE_HEADER));
        }

    //
    // The primary partition header is valid. Check the backup header.
    //

    } else {
        Valid = EfipPartitionValidGptTable(BlockIo,
                                           DiskIo,
                                           PrimaryHeader->AlternateLba,
                                           BackupHeader);

        if (Valid == FALSE) {
            RtlDebugPrint("Warning: Backup GPT header is invalid!\n");
        }
    }

    //
    // Read the EFI partition entries.
    //

    EntriesSize = PrimaryHeader->NumberOfPartitionEntries *
                  PrimaryHeader->SizeOfPartitionEntry;

    PartitionEntry = EfiCoreAllocateBootPool(EntriesSize);
    if (PartitionEntry == NULL) {
        goto PartitionDetectGptEnd;
    }

    Status = DiskIo->ReadDisk(DiskIo,
                              MediaId,
                              PrimaryHeader->PartitionEntryLba * BlockSize,
                              EntriesSize,
                              PartitionEntry);

    if (EFI_ERROR(Status)) {
        GptValidStatus = Status;
        goto PartitionDetectGptEnd;
    }

    PartitionEntryStatusSize = PrimaryHeader->NumberOfPartitionEntries *
                               sizeof(EFI_PARTITION_ENTRY_STATUS);

    PartitionEntryStatus = EfiCoreAllocateBootPool(PartitionEntryStatusSize);
    if (PartitionEntryStatus == NULL) {
        goto PartitionDetectGptEnd;
    }

    EfiSetMem(PartitionEntryStatus, PartitionEntryStatusSize, 0);

    //
    // Check the integrity of the partition entries.
    //

    EfipPartitionCheckGptEntries(PrimaryHeader,
                                 PartitionEntry,
                                 PartitionEntryStatus);

    //
    // Everything looks pretty valid.
    //

    GptValidStatus = EFI_SUCCESS;

    //
    // Create child device handles.
    //

    for (Index = 0;
         Index < PrimaryHeader->NumberOfPartitionEntries;
         Index += 1) {

        Entry = (EFI_PARTITION_ENTRY *)((UINT8 *)PartitionEntry +
                                        (Index *
                                         PrimaryHeader->SizeOfPartitionEntry));

        Match = EfiCoreCompareGuids(&(Entry->PartitionTypeGuid),
                                    &EfiPartitionTypeUnusedGuid);

        if ((Match != FALSE) ||
            (PartitionEntryStatus[Index].OutOfRange != FALSE) ||
            (PartitionEntryStatus[Index].Overlap != FALSE) ||
            (PartitionEntryStatus[Index].OsSpecific != FALSE)) {

            //
            // Don't use null entries, invalid entries, or OS-specific entries.
            //

            continue;
        }

        EfiSetMem(&DrivePath, sizeof(DrivePath), 0);
        DrivePath.Header.Type = MEDIA_DEVICE_PATH;
        DrivePath.Header.SubType = MEDIA_HARDDRIVE_DP;
        EfiCoreSetDevicePathNodeLength(&(DrivePath.Header), sizeof(DrivePath));
        DrivePath.PartitionNumber = (UINT32)Index + 1;
        DrivePath.MBRType = MBR_TYPE_EFI_PARTITION_TABLE_HEADER;
        DrivePath.SignatureType = SIGNATURE_TYPE_GUID;
        DrivePath.PartitionStart = Entry->StartingLba;
        DrivePath.PartitionSize = Entry->EndingLba - Entry->StartingLba + 1;
        EfiCopyMem(&(DrivePath.Signature),
                   &(Entry->UniquePartitionGuid), sizeof(EFI_GUID));

        SystemPartition =
                     EfiCoreCompareGuids(&(Entry->PartitionTypeGuid),
                                         &EfiPartitionTypeSystemPartitionGuid);

        Status = EfiPartitionInstallChildHandle(
                                        This,
                                        Handle,
                                        DiskIo,
                                        BlockIo,
                                        DevicePath,
                                        (EFI_DEVICE_PATH_PROTOCOL *)&DrivePath,
                                        Entry->StartingLba,
                                        Entry->EndingLba,
                                        BlockSize,
                                        SystemPartition);
    }

PartitionDetectGptEnd:
    if (ProtectiveMbr != NULL) {
        EfiFreePool(ProtectiveMbr);
    }

    if (PrimaryHeader != NULL) {
        EfiFreePool(PrimaryHeader);
    }

    if (BackupHeader != NULL) {
        EfiFreePool(BackupHeader);
    }

    if (PartitionEntry != NULL) {
        EfiFreePool(PartitionEntry);
    }

    if (PartitionEntryStatus != NULL) {
        EfiFreePool(PartitionEntryStatus);
    }

    return GptValidStatus;
}

//
// --------------------------------------------------------- Internal Functions
//

BOOLEAN
EfipPartitionValidGptTable (
    EFI_BLOCK_IO_PROTOCOL *BlockIo,
    EFI_DISK_IO_PROTOCOL *DiskIo,
    EFI_LBA Lba,
    EFI_PARTITION_TABLE_HEADER *PartitionHeader
    )

/*++

Routine Description:

    This routine determines if the given partition table header is valid.

Arguments:

    BlockIo - Supplies a pointer to the block I/O protocol.

    DiskIo - Supplies a pointer to the disk I/O protocol.

    Lba - Supplies the LBA to read.

    PartitionHeader - Supplies a pointer where the partition table will be read
        and returned.

Return Value:

    TRUE if the header is valid.

    FALSE if the header is invalid.

--*/

{

    UINT32 BlockSize;
    EFI_PARTITION_TABLE_HEADER *Header;
    UINT32 MediaId;
    EFI_STATUS Status;
    BOOLEAN Valid;

    BlockSize = BlockIo->Media->BlockSize;
    MediaId = BlockIo->Media->MediaId;
    Header = EfiCoreAllocateBootPool(BlockSize);
    if (Header == NULL) {
        return FALSE;
    }

    EfiSetMem(Header, BlockSize, 0);
    Status = DiskIo->ReadDisk(DiskIo,
                              MediaId,
                              Lba * BlockSize,
                              BlockSize,
                              Header);

    if (EFI_ERROR(Status)) {
        EfiFreePool(Header);
        return FALSE;
    }

    if ((Header->Header.Signature != EFI_GPT_HEADER_SIGNATURE) ||
        (EfipPartitionCheckCrc(BlockSize, &(Header->Header)) == FALSE) ||
        (Header->MyLba != Lba) ||
        (Header->SizeOfPartitionEntry < sizeof(EFI_PARTITION_ENTRY))) {

        EfiFreePool(Header);
        return FALSE;
    }

    EfiCopyMem(PartitionHeader, Header, sizeof(EFI_PARTITION_TABLE_HEADER));
    Valid = EfipPartitionCheckPartitionEntriesCrc(BlockIo,
                                                  DiskIo,
                                                  PartitionHeader);

    EfiFreePool(Header);
    return Valid;
}

BOOLEAN
EfipPartitionCheckCrc (
    UINTN MaxSize,
    EFI_TABLE_HEADER *Header
    )

/*++

Routine Description:

    This routine validates the CRC of the partition header.

Arguments:

    MaxSize - Supplies the maximum size of the buffer.

    Header - Supplies a pointer to the header to validate.

Return Value:

    TRUE if the CRC is valid.

    FALSE if the CRC is invalid.

--*/

{

    UINT32 Crc;
    UINT32 OriginalCrc;
    UINTN Size;

    Size = Header->HeaderSize;
    Crc = 0;
    if (Size == 0) {
        return FALSE;
    }

    if ((MaxSize != 0) && (Size > MaxSize)) {
        return FALSE;
    }

    OriginalCrc = Header->CRC32;
    Header->CRC32 = 0;
    EfiCalculateCrc32((UINT8 *)Header, Size, &Crc);
    Header->CRC32 = Crc;
    if (Crc != OriginalCrc) {
        return FALSE;
    }

    return TRUE;
}

BOOLEAN
EfipPartitionCheckPartitionEntriesCrc (
    EFI_BLOCK_IO_PROTOCOL *BlockIo,
    EFI_DISK_IO_PROTOCOL *DiskIo,
    EFI_PARTITION_TABLE_HEADER *PartitionHeader
    )

/*++

Routine Description:

    This routine validates the CRC of the partition entries.

Arguments:

    BlockIo - Supplies a pointer to the block I/O protocol.

    DiskIo - Supplies a pointer to the disk I/O protocol.

    PartitionHeader - Supplies a pointer to the GPT header.

Return Value:

    TRUE if the CRC is valid.

    FALSE if the CRC is invalid.

--*/

{

    UINT8 *Buffer;
    UINT32 Crc;
    UINTN EntriesSize;
    UINT64 Offset;
    EFI_STATUS Status;
    BOOLEAN Valid;

    EntriesSize = PartitionHeader->NumberOfPartitionEntries *
                  PartitionHeader->SizeOfPartitionEntry;

    Buffer = EfiCoreAllocateBootPool(EntriesSize);
    if (Buffer == NULL) {
        return FALSE;
    }

    Offset = PartitionHeader->PartitionEntryLba *
             BlockIo->Media->BlockSize;

    Status = DiskIo->ReadDisk(DiskIo,
                              BlockIo->Media->MediaId,
                              Offset,
                              EntriesSize,
                              Buffer);

    if (EFI_ERROR(Status)) {
        EfiFreePool(Buffer);
        return FALSE;
    }

    Valid = FALSE;
    Status = EfiCalculateCrc32(Buffer, EntriesSize, &Crc);
    if (EFI_ERROR(Status)) {
        RtlDebugPrint("GPT: Needed CRC and it wasn't there!\n");

    } else if (PartitionHeader->PartitionEntryArrayCrc32 == Crc) {
        Valid = TRUE;
    }

    EfiFreePool(Buffer);
    return Valid;
}

VOID
EfipPartitionCheckGptEntries (
    EFI_PARTITION_TABLE_HEADER *Header,
    EFI_PARTITION_ENTRY *Entries,
    EFI_PARTITION_ENTRY_STATUS *EntryStatus
    )

/*++

Routine Description:

    This routine checks the validity of the partition entry array.

Arguments:

    Header - Supplies a pointer to the partition entry header.

    Entries - Supplies a pointer to the partition entries to validate.

    EntryStatus - Supplies a pointer where the validity information of each
        partition will be returned.

Return Value:

    None.

--*/

{

    UINTN CompareIndex;
    EFI_LBA EndingLba;
    EFI_PARTITION_ENTRY *Entry;
    UINTN EntryIndex;
    BOOLEAN Match;
    EFI_LBA StartingLba;

    for (EntryIndex = 0;
         EntryIndex < Header->NumberOfPartitionEntries;
         EntryIndex += 1) {

        Entry = (EFI_PARTITION_ENTRY *)((UINT8 *)Entries +
                                        (EntryIndex *
                                         Header->SizeOfPartitionEntry));

        Match = EfiCoreCompareGuids(&(Entry->PartitionTypeGuid),
                                    &EfiPartitionTypeUnusedGuid);

        if (Match != FALSE) {
            continue;
        }

        StartingLba = Entry->StartingLba;
        EndingLba = Entry->EndingLba;
        if ((StartingLba > EndingLba) ||
            (StartingLba < Header->FirstUsableLba) ||
            (EndingLba < Header->FirstUsableLba) ||
            (EndingLba > Header->LastUsableLba)) {

            EntryStatus[EntryIndex].OutOfRange = TRUE;
            continue;
        }

        if ((Entry->Attributes & EFI_GPT_ATTRIBUTE_OS_SPECIFIC) != 0) {
            EntryStatus[EntryIndex].OsSpecific = TRUE;
        }

        for (CompareIndex = EntryIndex + 1;
             CompareIndex < Header->NumberOfPartitionEntries;
             CompareIndex += 1) {

            Entry = (EFI_PARTITION_ENTRY *)((UINT8 *)Entries +
                                            (CompareIndex *
                                             Header->SizeOfPartitionEntry));

            Match = EfiCoreCompareGuids(&(Entry->PartitionTypeGuid),
                                        &EfiPartitionTypeUnusedGuid);

            if (Match != FALSE) {
                continue;
            }

            if ((Entry->EndingLba >= StartingLba) &&
                (Entry->StartingLba <= EndingLba)) {

                EntryStatus[CompareIndex].Overlap = TRUE;
                EntryStatus[EntryIndex].Overlap = TRUE;
            }
        }
    }

    return;
}

