/**
 * PROJECT:         ExectOS
 * COPYRIGHT:       See COPYING.md in the top level directory
 * FILE:            xtldr/modules/beep/beep.c
 * DESCRIPTION:     XTLDR Beep Module
 * DEVELOPERS:      Rafal Kupiec <belliash@codingworkshop.eu.org>
 */

#include <beep.h>


/* Beep module information */
MODULE_AUTHOR(L"Rafal Kupiec <belliash@codingworkshop.eu.org>");
MODULE_DESCRIPTION(L"Plays a GRUB compatible tune via PC speaker");
MODULE_LICENSE(L"GPLv3");
MODULE_VERSION(L"0.1");

/**
 * Disables the PC speaker.
 *
 * @return This routine does not return any value.
 *
 * @since XT 1.0
 */
XTCDECL
VOID
BpDisableToneBeep()
{
    UCHAR Status;

    /* Stop the PC speaker */
    Status = HlIoPortInByte(0x61);
    HlIoPortOutByte(0x61, Status & 0xFC);
}

/**
 * Enables the PC speaker and plays a sound.
 *
 * @param Pitch
 *        Specifies a pitch (in Hz) of the sound.
 *
 * @return This routine does not return any value.
 *
 * @since XT 1.0
 */
XTCDECL
VOID
BpEnableToneBeep(IN UINT Pitch)
{
    UINT Counter;
    UCHAR Status;

    /* Pitch only in range of 20..20000 */
    if(Pitch < 20)
    {
        Pitch = 20;
    }
    else if(Pitch > 20000)
    {
        Pitch = 20000;
    }

    /* Set the desired frequency of the PIT clock */
    Counter = 0x1234DD / Pitch;
    HlIoPortOutByte(0x43, 0xB6);
    HlIoPortOutByte(0x43, 0xB6);
    HlIoPortOutByte(0x42, (UCHAR) Counter & 0xFF);
    HlIoPortOutByte(0x42, (UCHAR) (Counter >> 8) & 0xFF);

    /* Start the PC speaker */
    Status = HlIoPortInByte(0x61);
    HlIoPortOutByte(0x61, Status | 0x03);
}

/**
 * This routine plays a tune.
 *
 * @param Arguments
 *        Optional list of parameters provided with the command.
 *
 * @return This routine does not return any value.
 *
 * @since XT 1.0
 */
XTCDECL
VOID
BpPlayTune(IN PWCHAR Arguments)
{
    LONG Pitch, Duration, Tempo;
    PWCHAR Argument, LastArgument;

    /* Reset pitch and duration */
    Duration = -1;
    Pitch = -1;
    Tempo = -1;

    /* Tokenize provided list of arguments */
    Argument = RtlTokenizeWideString(Arguments, L" ", &LastArgument);

    /* Iterate over all arguments */
    while(Argument != NULL)
    {
        /* Check if tempo, pitch and duration are set */
        if(Tempo < 0)
        {
            /* Set the tempo */
            Tempo = BpWideStringToNumber(Argument);
        }
        else if(Pitch < 0)
        {
            /* Set the pitch */
            Pitch = BpWideStringToNumber(Argument);
        }
        else
        {
            /* Set the duration */
            Duration = BpWideStringToNumber(Argument);

            /* Check pitch value */
            if(Pitch > 0)
            {
                /* Emit the beep tone */
                BpEnableToneBeep(Pitch);
            }
            else
            {
                /* Stop emitting beep tone */
                BpDisableToneBeep();
            }

            /* Wait for duration time */
            XtLdrProtocol->Util.SleepExecution(60000 * Duration / Tempo);

            /* Reset pitch and duration */
            Pitch = -1;
            Duration = -1;
        }

        /* Get next argument */
        Argument = RtlTokenizeWideString(NULL, L" ", &LastArgument);
    }

    /* Stop emitting beep tone */
    BpDisableToneBeep();
}

/**
 * Converts a wide string into a number.
 *
 * @param String
 *        Supplies an input wide string.
 *
 * @return This routine returns the number that was converted from the wide string.
 *
 * @since XT 1.0
 */
XTCDECL
UINT
BpWideStringToNumber(IN PWCHAR String)
{
    ULONG Number = 0;

    /* Iterate over all characters until '\0' found */
    do
    {
        /* Check if this is a digit */
        if(*String - '0' < 10)
        {
            /* Add another digit to the number */
            Number *= 10;
            Number += *String - '0';
        }
    }
    while(*++String != L'\0');

    /* Return number */
    return Number;
}

/**
 * This routine is the entry point of the XT EFI boot loader module.
 *
 * @param ImageHandle
 *        Firmware-allocated handle that identifies the image.
 *
 * @param SystemTable
 *        Provides the EFI system table.
 *
 * @return This routine returns status code.
 *
 * @since XT 1.0
 */
XTCDECL
EFI_STATUS
XtLdrModuleMain(IN EFI_HANDLE ImageHandle,
                IN PEFI_SYSTEM_TABLE SystemTable)
{
    EFI_STATUS Status;

    /* Open the XTLDR protocol */
    Status = BlGetXtLdrProtocol(SystemTable, ImageHandle, &XtLdrProtocol);
    if(Status != STATUS_EFI_SUCCESS)
    {
        /* Failed to open the protocol, return error */
        return STATUS_EFI_PROTOCOL_ERROR;
    }

    /* Play the tune set in the configuration */
    BpPlayTune(XtLdrProtocol->Config.GetValue(L"TUNE"));

    /* Return success */
    return STATUS_EFI_SUCCESS;
}