/**
 * PROJECT:         ExectOS
 * COPYRIGHT:       See COPYING.md in the top level directory
 * FILE:            xtldr/efiutil.c
 * DESCRIPTION:     EFI utilities
 * DEVELOPERS:      Rafal Kupiec <belliash@codingworkshop.eu.org>
 */

#include <xtbl.h>


/**
 * This routine initializes the COM port debug console.
 *
 * @return This routine returns a status code.
 *
 * @since XT 1.0
 */
EFI_STATUS
BlComPortInitialize()
{
    EFI_GUID LIPGuid = EFI_LOADED_IMAGE_PROTOCOL_GUID;
    PEFI_LOADED_IMAGE_PROTOCOL LoadedImage;
    ULONG PortNumber, BaudRate;
    PWCHAR Argument, CommandLine, LastArg;
    EFI_STATUS EfiStatus;
    XTSTATUS Status;

    /* Set default serial port options */
    PortNumber = 0;
    BaudRate = 0;

    /* Handle loaded image protocol */
    EfiStatus = EfiSystemTable->BootServices->HandleProtocol(EfiImageHandle, &LIPGuid, (PVOID *)&LoadedImage);
    if(EfiStatus == STATUS_EFI_SUCCESS)
    {

        /* Check if launched from UEFI shell */
        if(LoadedImage && LoadedImage->LoadOptions)
        {
            /* Store arguments passed from UEFI shell */
            CommandLine = (PWCHAR)LoadedImage->LoadOptions;

            /* Find command in command line */
            Argument = RtlWideStringTokenize(CommandLine, L" ", &LastArg);

            /* Iterate over all arguments passed to boot loader */
            while(Argument != NULL)
            {
                /* Check if this is DEBUG parameter */
                if(RtlWideStringCompare(Argument, L"DEBUG=", 6) == 0)
                {
                    /* Skip to the argument value */
                    Argument += 6;

                    /* Make sure COM port is being used */
                    if(RtlWideStringCompare(Argument, L"COM", 3))
                    {
                        /* Invalid debug port specified */
                        BlEfiPrint(L"ERROR: Invalid debug port specified, falling back to defaults\n");
                        break;
                    }

                    /* Read COM port number */
                    Argument += 3;
                    while(*Argument >= '0' && *Argument <= '9')
                    {
                        /* Get port number */
                        PortNumber *= 10;
                        PortNumber += *Argument - '0';
                        Argument++;
                    }

                    /* Look for additional COM port parameters */
                    if(*Argument == ',')
                    {
                        /* Baud rate provided */
                        Argument++;
                        while(*Argument >= '0' && *Argument <= '9')
                        {
                            /* Get baud rate */
                            BaudRate *= 10;
                            BaudRate += *Argument - '0';
                            Argument++;
                        }
                    }

                    /* No need to check next arguments */
                    break;
                }

                /* Take next argument */
                Argument = RtlWideStringTokenize(NULL, L" ", &LastArg);
            }
        }
    }

    /* Initialize COM port */
    Status = HlInitializeComPort(&EfiSerialPort, PortNumber, BaudRate);
    if(Status != STATUS_SUCCESS)
    {
        /* Serial port initialization failed, mark as not ready */
        return STATUS_EFI_NOT_READY;
    }

    /* Return success */
    return STATUS_EFI_SUCCESS;
}

/**
 * Writes a character to the serial console.
 *
 * @param Character
 *        The integer promotion of the character to be written.
 *
 * @return This routine does not return any value.
 *
 * @since XT 1.0
 */
VOID
BlComPortPutChar(IN USHORT Character)
{
    USHORT Buffer[2];

    /* Write character to the serial console */
    Buffer[0] = Character;
    Buffer[1] = 0;

    HlComPortPutByte(&EfiSerialPort, Buffer[0]);
}

/**
 * This routine formats the input string and prints it out to the serial console.
 *
 * @param Format
 *        The formatted string that is to be written to the output.
 *
 * @param ...
 *        Depending on the format string, this routine might expect a sequence of additional arguments.
 *
 * @return This routine does not return any value.
 *
 * @since XT 1.0
 */
VOID
BlDbgPrint(IN PUINT16 Format,
           IN ...)
{
    VA_LIST Arguments;

    /* Check if EFI serial port is fully initialized */
    if(EfiSerialPort.Flags & COMPORT_FLAG_INIT)
    {
        /* Initialise the va_list */
        VA_START(Arguments, Format);

        /* Format and print the string to the serial console */
        BlStringPrint(BlComPortPutChar, Format, Arguments);

        /* Clean up the va_list */
        VA_END(Arguments);
    }
}

/**
 * Replaces slashes (/) with backslashes (\) in the string containing on-disk path.
 *
 * @param Path
 *        A pointer to the string containing an original system path.
 *
 * @return A pointer to converted string with EFI supported path separators.
 *
 * @since XT 1.0
 */
PWCHAR
BlEfiDirectorySeparator(IN PWCHAR Path)
{
    PWCHAR EfiPath = NULL;

    while(*Path)
    {
        if(*Path == '/')
        {
            *EfiPath = '\\';
        }
        else
        {
            *EfiPath = *Path;
        }
        Path++;
        EfiPath++;
    }

    return EfiPath;
}

/**
 * This routine allocates a pool memory.
 *
 * @param Size
 *        The number of bytes to allocate from the pool.
 *
 * @param Memory
 *        The pointer to a physical address.
 *
 * @return This routine returns a status code.
 *
 * @since XT 1.0
 */
EFI_STATUS
BlEfiMemoryAllocatePool(IN UINT_PTR Size,
                        OUT PVOID *Memory)
{
    /* Allocate pool */
    return EfiSystemTable->BootServices->AllocatePool(EfiLoaderData, Size, Memory);
}

/**
 * Returns pool memory to the system.
 *
 * @param Memory
 *        The pointer to the buffer to free.
 *
 * @return This routine returns a status code.
 *
 * @since XT 1.0
 */
EFI_STATUS
BlEfiMemoryFreePool(IN PVOID Memory)
{
    /* Free pool */
    return EfiSystemTable->BootServices->FreePool(Memory);
}

/**
 * This routine formats the input string and prints it out to the stdout and serial console.
 *
 * @param Format
 *        The formatted string that is to be written to the output.
 *
 * @param ...
 *        Depending on the format string, this routine might expect a sequence of additional arguments.
 *
 * @return This routine does not return any value.
 *
 * @since XT 1.0
 *
 * @todo Check if GOP is active and use it instead of default conout protocol
 */
VOID
BlEfiPrint(IN PUINT16 Format,
           IN ...)
{
    VA_LIST Arguments;

    /* Initialise the va_list */
    VA_START(Arguments, Format);

    /* Format and print the string to the stdout */
    BlStringPrint(BlConsolePutChar, Format, Arguments);

    /* Print to serial console only if not running under OVMF */
    if(RtlWideStringCompare(EfiSystemTable->FirmwareVendor, L"EDK II", 6) != 0)
    {
        /* Check if EFI serial port is fully initialized */
        if(EfiSerialPort.Flags & COMPORT_FLAG_INIT)
        {
            /* Format and print the string to the serial console */
            BlStringPrint(BlComPortPutChar, Format, Arguments);
        }
    }

    /* Clean up the va_list */
    VA_END(Arguments);
}