/*
 * gptsync/os_efi.c
 * EFI glue for gptsync
 *
 * Copyright (c) 2006 Christoph Pfisterer
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are
 * met:
 *
 *  * Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 *
 *  * Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in the
 *    documentation and/or other materials provided with the
 *    distribution.
 *
 *  * Neither the name of Christoph Pfisterer nor the names of the
 *    contributors may be used to endorse or promote products derived
 *    from this software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
 * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */

#include "gptsync.h"

// variables

EFI_BLOCK_IO_PROTOCOL *BlockIO = NULL;
EFI_BLOCK_IO2_PROTOCOL *BlockIO2 = NULL;
EFI_BLOCK_IO2_TOKEN BlockIO2Token;

//
// sector I/O functions
//

UINTN read_sector(UINT64 lba, UINT8 *buffer)
{
    EFI_STATUS          Status;

    if (BlockIO2 != NULL)
    {
      Status = BlockIO2->ReadBlocksEx(BlockIO2, BlockIO2->Media->MediaId, lba, &BlockIO2Token, 512, buffer);
    } else {
      Status = BlockIO->ReadBlocks(BlockIO, BlockIO->Media->MediaId, lba, 512, buffer);
    }
    if (EFI_ERROR(Status)) {
        // TODO: report error
        return 1;
    }
    return 0;
}

UINTN write_sector(UINT64 lba, UINT8 *buffer)
{
    EFI_STATUS          Status;

    if (BlockIO2 != NULL)
    {
      Status = BlockIO2->WriteBlocksEx(BlockIO2, BlockIO2->Media->MediaId, lba, &BlockIO2Token, 512, buffer);
    } else {
      Status = BlockIO->WriteBlocks(BlockIO, BlockIO->Media->MediaId, lba, 512, buffer);
    }
    if (EFI_ERROR(Status)) {
        // TODO: report error
        return 1;
    }
    return 0;
}

//
// Keyboard input
//

static BOOLEAN ReadAllKeyStrokes(VOID)
{
    EFI_STATUS          Status;
    BOOLEAN             GotKeyStrokes;
    EFI_INPUT_KEY       Key;
    
    GotKeyStrokes = FALSE;
    for (;;) {
        Status = gST->ConIn->ReadKeyStroke(gST->ConIn, &Key);
        if (Status == EFI_SUCCESS) {
            GotKeyStrokes = TRUE;
            continue;
        }
        break;
    }
    return GotKeyStrokes;
}

static VOID PauseForKey(VOID)
{
    UINTN               Index;
    
    Print(L"\n* Hit any key to continue *");
    
    if (ReadAllKeyStrokes()) {  // remove buffered key strokes
        gBS->Stall(5000000);     // 5 seconds delay
        ReadAllKeyStrokes();    // empty the buffer again
    }
    
    gBS->WaitForEvent(1, &gST->ConIn->WaitForKey, &Index);
    ReadAllKeyStrokes();        // empty the buffer to protect the menu
    
    Print(L"\n");
}

UINTN input_boolean(CHARN *prompt, BOOLEAN *bool_out)
{
    EFI_STATUS          Status;
    UINTN               Index;
    EFI_INPUT_KEY       Key;
    
    Print(prompt);
    
    if (ReadAllKeyStrokes()) {  // remove buffered key strokes
        gBS->Stall(500000);      // 0.5 seconds delay
        ReadAllKeyStrokes();    // empty the buffer again
    }
    
    gBS->WaitForEvent(1, &gST->ConIn->WaitForKey, &Index);
    Status = gST->ConIn->ReadKeyStroke(gST->ConIn, &Key);
    if (EFI_ERROR(Status))
        return 1;
    
    if (Key.UnicodeChar == 'y' || Key.UnicodeChar == 'Y') {
        Print(L"Yes\n");
        *bool_out = TRUE;
    } else {
        Print(L"No\n");
        *bool_out = FALSE;
    }
    
    ReadAllKeyStrokes();
    return 0;
}

//
// main entry point
//

EFI_STATUS
EFIAPI
GptSyncMain    (IN EFI_HANDLE           ImageHandle,
                IN EFI_SYSTEM_TABLE     *SystemTable)
{
    EFI_STATUS          Status;
    EFI_STATUS          Status2;
    UINTN               SyncStatus;
    UINTN               Index;
    UINTN               HandleCount;
    UINTN               HandleCount2;
    EFI_HANDLE          *HandleBuffer = NULL;
    EFI_HANDLE          *HandleBuffer2 = NULL;
    EFI_HANDLE          DeviceHandle;
    EFI_DEVICE_PATH     *DevicePath, *NextDevicePath;
    BOOLEAN             Usable;

    Status = gBS->LocateHandle(ByProtocol, &gEfiBlockIo2ProtocolGuid, NULL,
                               &HandleCount2, NULL);

    Status = gBS->LocateHandle(ByProtocol, &gEfiBlockIoProtocolGuid, NULL,
                               &HandleCount, NULL);

    if (!HandleCount && !HandleCount2)
    {
        Status = EFI_NOT_FOUND;
        return Status;
    }

    HandleBuffer = AllocateZeroPool(HandleCount * sizeof(EFI_HANDLE));

    HandleBuffer2 = AllocateZeroPool(HandleCount2 * sizeof(EFI_HANDLE));

    Status2 = gBS->LocateHandle(ByProtocol, &gEfiBlockIo2ProtocolGuid, NULL,
                             &HandleCount2, HandleBuffer2);

    Status = gBS->LocateHandle(ByProtocol, &gEfiBlockIoProtocolGuid, NULL,
                             &HandleCount, HandleBuffer);
    if (EFI_ERROR (Status) && EFI_ERROR (Status2)) {
        Status = EFI_NOT_FOUND;
        return Status;
    }

    for (Index = 0; Index < HandleCount2; Index++) {
        
        DeviceHandle = HandleBuffer2[Index];
        
        // check device path
        DevicePath = DevicePathFromHandle(DeviceHandle);
        Usable = TRUE;
        while (DevicePath != NULL && !IsDevicePathEndType(DevicePath)) {
            NextDevicePath = NextDevicePathNode(DevicePath);
            
            if (DevicePathType(DevicePath) == MESSAGING_DEVICE_PATH &&
                (DevicePathSubType(DevicePath) == MSG_USB_DP ||
                 DevicePathSubType(DevicePath) == MSG_USB_CLASS_DP ||
                 DevicePathSubType(DevicePath) == MSG_1394_DP ||
                 DevicePathSubType(DevicePath) == MSG_FIBRECHANNEL_DP))
                Usable = FALSE;         // USB/FireWire/FC device
            if (DevicePathType(DevicePath) == MEDIA_DEVICE_PATH)
                Usable = FALSE;         // partition, El Torito entry, legacy BIOS device
            
            DevicePath = NextDevicePath;
        }
        if (!Usable)
            continue;
        
        Status = gBS->HandleProtocol(DeviceHandle, &gEfiBlockIo2ProtocolGuid, (VOID **) &BlockIO2);
        if (EFI_ERROR(Status)) {
            // TODO: report error
            BlockIO = NULL;
        } else {
            if (BlockIO2->Media->BlockSize != 512)
                BlockIO2 = NULL;    // optical media
            else
                break;
        }
        
    }

    FreePool (HandleBuffer2);

    for (Index = 0; Index < HandleCount; Index++) {
        
        DeviceHandle = HandleBuffer[Index];
        
        // check device path
        DevicePath = DevicePathFromHandle(DeviceHandle);
        Usable = TRUE;
        while (DevicePath != NULL && !IsDevicePathEndType(DevicePath)) {
            NextDevicePath = NextDevicePathNode(DevicePath);
            
            if (DevicePathType(DevicePath) == MESSAGING_DEVICE_PATH &&
                (DevicePathSubType(DevicePath) == MSG_USB_DP ||
                 DevicePathSubType(DevicePath) == MSG_USB_CLASS_DP ||
                 DevicePathSubType(DevicePath) == MSG_1394_DP ||
                 DevicePathSubType(DevicePath) == MSG_FIBRECHANNEL_DP))
                Usable = FALSE;         // USB/FireWire/FC device
            if (DevicePathType(DevicePath) == MEDIA_DEVICE_PATH)
                Usable = FALSE;         // partition, El Torito entry, legacy BIOS device
            
            DevicePath = NextDevicePath;
        }
        if (!Usable)
            continue;
        
        Status = gBS->HandleProtocol(DeviceHandle, &gEfiBlockIoProtocolGuid, (VOID **) &BlockIO);
        if (EFI_ERROR(Status)) {
            // TODO: report error
            BlockIO = NULL;
        } else {
            if (BlockIO->Media->BlockSize != 512)
                BlockIO = NULL;    // optical media
            else
                break;
        }
        
    }

    FreePool (HandleBuffer);
    
    if ((BlockIO == NULL) && (BlockIO2 == NULL)) {
        Print(L"Internal hard disk device not found!\n");
        return EFI_NOT_FOUND;
    }

    SyncStatus = gptsync();
    
    if (SyncStatus == 0)
        PauseForKey();
    
    if (SyncStatus)
        return EFI_NOT_FOUND;

    return EFI_SUCCESS;
}