/****************************************************************************
*
*    Copyright (C) 2005 - 2013 by Vivante Corp.
*
*    This program is free software; you can redistribute it and/or modify
*    it under the terms of the GNU General Public License as published by
*    the Free Software Foundation; either version 2 of the license, or
*    (at your option) any later version.
*
*    This program is distributed in the hope that it will be useful,
*    but WITHOUT ANY WARRANTY; without even the implied warranty of
*    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
*    GNU General Public License for more details.
*
*    You should have received a copy of the GNU General Public License
*    along with this program; if not write to the Free Software
*    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
*
*****************************************************************************/


#include "gc_hal_kernel_precomp.h"
#include "gc_hal_kernel_debug.h"

/******************************************************************************\
******************************** Debug Variables *******************************
\******************************************************************************/

static gceSTATUS _lastError  = gcvSTATUS_OK;
static gctUINT32 _debugLevel = gcvLEVEL_ERROR;
/*
_debugZones config value
Please Reference define in gc_hal_base.h
*/
static gctUINT32 _debugZones = gcvZONE_NONE;

/******************************************************************************\
********************************* Debug Switches *******************************
\******************************************************************************/

/*
    gcdBUFFERED_OUTPUT

    When set to non-zero, all output is collected into a buffer with the
    specified size.  Once the buffer gets full, the debug buffer will be
    printed to the console. gcdBUFFERED_SIZE determines the size of the buffer.
*/
#define gcdBUFFERED_OUTPUT  0

/*
    gcdBUFFERED_SIZE

    When set to non-zero, all output is collected into a buffer with the
    specified size.  Once the buffer gets full, the debug buffer will be
    printed to the console.
*/
#define gcdBUFFERED_SIZE    (1024 * 1024 * 2)

/*
    gcdDMA_BUFFER_COUNT

    If greater then zero, the debugger will attempt to find the command buffer
    where DMA is currently executing and then print this buffer and
    (gcdDMA_BUFFER_COUNT - 1) buffers before the current one. If set to zero
    or the current buffer is not found, all buffers are printed.
*/
#define gcdDMA_BUFFER_COUNT 0

/*
    gcdTHREAD_BUFFERS

    When greater then one, will accumulate messages from the specified number
    of threads in separate output buffers.
*/
#define gcdTHREAD_BUFFERS   1

/*
    gcdENABLE_OVERFLOW

    When set to non-zero, and the output buffer gets full, instead of being
    printed, it will be allowed to overflow removing the oldest messages.
*/
#define gcdENABLE_OVERFLOW  1

/*
    gcdSHOW_LINE_NUMBER

    When enabledm each print statement will be preceeded with the current
    line number.
*/
#define gcdSHOW_LINE_NUMBER 0

/*
    gcdSHOW_PROCESS_ID

    When enabledm each print statement will be preceeded with the current
    process ID.
*/
#define gcdSHOW_PROCESS_ID  0

/*
    gcdSHOW_THREAD_ID

    When enabledm each print statement will be preceeded with the current
    thread ID.
*/
#define gcdSHOW_THREAD_ID   0

/*
    gcdSHOW_TIME

    When enabled each print statement will be preceeded with the current
    high-resolution time.
*/
#define gcdSHOW_TIME        0


/******************************************************************************\
****************************** Miscellaneous Macros ****************************
\******************************************************************************/

#if gcmIS_DEBUG(gcdDEBUG_TRACE)
#   define gcmDBGASSERT(Expression, Format, Value) \
        if (!(Expression)) \
        { \
            _DirectPrint( \
                "*** gcmDBGASSERT ***************************\n" \
                "    function     : %s\n" \
                "    line         : %d\n" \
                "    expression   : " #Expression "\n" \
                "    actual value : " Format "\n", \
                __FUNCTION__, __LINE__, Value \
                ); \
        }
#else
#   define gcmDBGASSERT(Expression, Format, Value)
#endif

#define gcmPTRALIGNMENT(Pointer, Alignemnt) \
( \
    gcmALIGN(gcmPTR2INT(Pointer), Alignemnt) - gcmPTR2INT(Pointer) \
)

#if gcdALIGNBYSIZE
#   define gcmISALIGNED(Offset, Alignment) \
        (((Offset) & ((Alignment) - 1)) == 0)

#   define gcmkALIGNPTR(Type, Pointer, Alignment) \
        Pointer = (Type) gcmINT2PTR(gcmALIGN(gcmPTR2INT(Pointer), Alignment))
#else
#   define gcmISALIGNED(Offset, Alignment) \
        gcvTRUE

#   define gcmkALIGNPTR(Type, Pointer, Alignment)
#endif

#define gcmALIGNSIZE(Offset, Size) \
    ((Size - Offset) + Size)

#define gcdHAVEPREFIX \
( \
       gcdSHOW_TIME \
    || gcdSHOW_LINE_NUMBER \
    || gcdSHOW_PROCESS_ID \
    || gcdSHOW_THREAD_ID \
)

#if gcdHAVEPREFIX

#   define gcdOFFSET                    0

#if gcdSHOW_TIME
#if gcmISALIGNED(gcdOFFSET, 8)
#           define gcdTIMESIZE          gcmSIZEOF(gctUINT64)
#       elif gcdOFFSET == 4
#           define gcdTIMESIZE          gcmALIGNSIZE(4, gcmSIZEOF(gctUINT64))
#       else
#           error "Unexpected offset value."
#       endif
#       undef  gcdOFFSET
#       define gcdOFFSET                8
#if !defined(gcdPREFIX_LEADER)
#           define gcdPREFIX_LEADER     gcmSIZEOF(gctUINT64)
#           define gcdTIMEFORMAT        "0x%016llX"
#       else
#           define gcdTIMEFORMAT        ", 0x%016llX"
#       endif
#   else
#       define gcdTIMESIZE              0
#       define gcdTIMEFORMAT
#   endif

#if gcdSHOW_LINE_NUMBER
#if gcmISALIGNED(gcdOFFSET, 8)
#           define gcdNUMSIZE           gcmSIZEOF(gctUINT64)
#       elif gcdOFFSET == 4
#           define gcdNUMSIZE           gcmALIGNSIZE(4, gcmSIZEOF(gctUINT64))
#       else
#           error "Unexpected offset value."
#       endif
#       undef  gcdOFFSET
#       define gcdOFFSET                8
#if !defined(gcdPREFIX_LEADER)
#           define gcdPREFIX_LEADER     gcmSIZEOF(gctUINT64)
#           define gcdNUMFORMAT         "%8llu"
#       else
#           define gcdNUMFORMAT         ", %8llu"
#       endif
#   else
#       define gcdNUMSIZE               0
#       define gcdNUMFORMAT
#   endif

#if gcdSHOW_PROCESS_ID
#if gcmISALIGNED(gcdOFFSET, 4)
#           define gcdPIDSIZE           gcmSIZEOF(gctUINT32)
#       else
#           error "Unexpected offset value."
#       endif
#       undef  gcdOFFSET
#       define gcdOFFSET                4
#if !defined(gcdPREFIX_LEADER)
#           define gcdPREFIX_LEADER     gcmSIZEOF(gctUINT32)
#           define gcdPIDFORMAT         "pid=%5d"
#       else
#           define gcdPIDFORMAT         ", pid=%5d"
#       endif
#   else
#       define gcdPIDSIZE               0
#       define gcdPIDFORMAT
#   endif

#if gcdSHOW_THREAD_ID
#if gcmISALIGNED(gcdOFFSET, 4)
#           define gcdTIDSIZE           gcmSIZEOF(gctUINT32)
#       else
#           error "Unexpected offset value."
#       endif
#       undef  gcdOFFSET
#       define gcdOFFSET                4
#if !defined(gcdPREFIX_LEADER)
#           define gcdPREFIX_LEADER     gcmSIZEOF(gctUINT32)
#           define gcdTIDFORMAT         "tid=%5d"
#       else
#           define gcdTIDFORMAT         ", tid=%5d"
#       endif
#   else
#       define gcdTIDSIZE               0
#       define gcdTIDFORMAT
#   endif

#   define gcdPREFIX_SIZE \
    ( \
          gcdTIMESIZE \
        + gcdNUMSIZE  \
        + gcdPIDSIZE  \
        + gcdTIDSIZE  \
    )

    static const char * _prefixFormat =
    "["
        gcdTIMEFORMAT
        gcdNUMFORMAT
        gcdPIDFORMAT
        gcdTIDFORMAT
    "] ";

#else

#   define gcdPREFIX_LEADER             gcmSIZEOF(gctUINT32)
#   define gcdPREFIX_SIZE               0

#endif

/* Assumed largest variable argument leader size. */
#define gcdVARARG_LEADER                gcmSIZEOF(gctUINT64)

/* Alignnments. */
#if gcdALIGNBYSIZE
#   define gcdPREFIX_ALIGNMENT gcdPREFIX_LEADER
#   define gcdVARARG_ALIGNMENT gcdVARARG_LEADER
#else
#   define gcdPREFIX_ALIGNMENT 0
#   define gcdVARARG_ALIGNMENT 0
#endif

#if gcdBUFFERED_OUTPUT
#   define gcdOUTPUTPREFIX _AppendPrefix
#   define gcdOUTPUTSTRING _AppendString
#   define gcdOUTPUTCOPY   _AppendCopy
#   define gcdOUTPUTBUFFER _AppendBuffer
#else
#   define gcdOUTPUTPREFIX _PrintPrefix
#   define gcdOUTPUTSTRING _PrintString
#   define gcdOUTPUTCOPY   _PrintString
#   define gcdOUTPUTBUFFER _PrintBuffer
#endif

/******************************************************************************\
****************************** Private Structures ******************************
\******************************************************************************/

typedef enum _gceBUFITEM
{
    gceBUFITEM_NONE,
    gcvBUFITEM_PREFIX,
    gcvBUFITEM_STRING,
    gcvBUFITEM_COPY,
    gcvBUFITEM_BUFFER
}
gceBUFITEM;

/* Common item head/buffer terminator. */
typedef struct _gcsBUFITEM_HEAD * gcsBUFITEM_HEAD_PTR;
typedef struct _gcsBUFITEM_HEAD
{
    gceBUFITEM              type;
}
gcsBUFITEM_HEAD;

/* String prefix (for ex. [     1,tid=0x019A]) */
typedef struct _gcsBUFITEM_PREFIX * gcsBUFITEM_PREFIX_PTR;
typedef struct _gcsBUFITEM_PREFIX
{
    gceBUFITEM              type;
#if gcdHAVEPREFIX
    gctPOINTER              prefixData;
#endif
}
gcsBUFITEM_PREFIX;

/* Buffered string. */
typedef struct _gcsBUFITEM_STRING * gcsBUFITEM_STRING_PTR;
typedef struct _gcsBUFITEM_STRING
{
    gceBUFITEM              type;
    gctINT                  indent;
    gctCONST_STRING         message;
    gctPOINTER              messageData;
    gctUINT                 messageDataSize;
}
gcsBUFITEM_STRING;

/* Buffered string (copy of the string is included with the record). */
typedef struct _gcsBUFITEM_COPY * gcsBUFITEM_COPY_PTR;
typedef struct _gcsBUFITEM_COPY
{
    gceBUFITEM              type;
    gctINT                  indent;
    gctPOINTER              messageData;
    gctUINT                 messageDataSize;
}
gcsBUFITEM_COPY;

/* Memory buffer. */
typedef struct _gcsBUFITEM_BUFFER * gcsBUFITEM_BUFFER_PTR;
typedef struct _gcsBUFITEM_BUFFER
{
    gceBUFITEM              type;
    gctINT                  indent;
    gceDUMP_BUFFER          bufferType;

#if gcdDMA_BUFFER_COUNT && (gcdTHREAD_BUFFERS == 1)
    gctUINT32               dmaAddress;
#endif

    gctUINT                 dataSize;
    gctUINT32               address;
#if gcdHAVEPREFIX
    gctPOINTER              prefixData;
#endif
}
gcsBUFITEM_BUFFER;

typedef struct _gcsBUFFERED_OUTPUT * gcsBUFFERED_OUTPUT_PTR;
typedef struct _gcsBUFFERED_OUTPUT
{
#if gcdTHREAD_BUFFERS > 1
    gctUINT32               threadID;
#endif

#if gcdSHOW_LINE_NUMBER
    gctUINT64               lineNumber;
#endif

    gctINT                  indent;

#if gcdBUFFERED_OUTPUT
    gctINT                  start;
    gctINT                  index;
    gctINT                  count;
    gctUINT8                buffer[gcdBUFFERED_SIZE];
#endif

    gcsBUFFERED_OUTPUT_PTR  prev;
    gcsBUFFERED_OUTPUT_PTR  next;
}
gcsBUFFERED_OUTPUT;

typedef gctUINT (* gcfPRINTSTRING) (
    IN gcsBUFFERED_OUTPUT_PTR OutputBuffer,
    IN gcsBUFITEM_HEAD_PTR Item
    );

typedef gctINT (* gcfGETITEMSIZE) (
    IN gcsBUFITEM_HEAD_PTR Item
    );

/******************************************************************************\
******************************* Private Variables ******************************
\******************************************************************************/

static gcsBUFFERED_OUTPUT     _outputBuffer[gcdTHREAD_BUFFERS];
static gcsBUFFERED_OUTPUT_PTR _outputBufferHead = gcvNULL;
static gcsBUFFERED_OUTPUT_PTR _outputBufferTail = gcvNULL;

/******************************************************************************\
****************************** Item Size Functions *****************************
\******************************************************************************/

#if gcdBUFFERED_OUTPUT
static gctINT
_GetTerminatorItemSize(
    IN gcsBUFITEM_HEAD_PTR Item
    )
{
    return gcmSIZEOF(gcsBUFITEM_HEAD);
}

static gctINT
_GetPrefixItemSize(
    IN gcsBUFITEM_HEAD_PTR Item
    )
{
#if gcdHAVEPREFIX
    gcsBUFITEM_PREFIX_PTR item = (gcsBUFITEM_PREFIX_PTR) Item;
    gctUINT vlen = ((gctUINT8_PTR) item->prefixData) - ((gctUINT8_PTR) item);
    return vlen + gcdPREFIX_SIZE;
#else
    return gcmSIZEOF(gcsBUFITEM_PREFIX);
#endif
}

static gctINT
_GetStringItemSize(
    IN gcsBUFITEM_HEAD_PTR Item
    )
{
    gcsBUFITEM_STRING_PTR item = (gcsBUFITEM_STRING_PTR) Item;
    gctUINT vlen = ((gctUINT8_PTR) item->messageData) - ((gctUINT8_PTR) item);
    return vlen + item->messageDataSize;
}

static gctINT
_GetCopyItemSize(
    IN gcsBUFITEM_HEAD_PTR Item
    )
{
    gcsBUFITEM_COPY_PTR item = (gcsBUFITEM_COPY_PTR) Item;
    gctUINT vlen = ((gctUINT8_PTR) item->messageData) - ((gctUINT8_PTR) item);
    return vlen + item->messageDataSize;
}

static gctINT
_GetBufferItemSize(
    IN gcsBUFITEM_HEAD_PTR Item
    )
{
#if gcdHAVEPREFIX
    gcsBUFITEM_BUFFER_PTR item = (gcsBUFITEM_BUFFER_PTR) Item;
    gctUINT vlen = ((gctUINT8_PTR) item->prefixData) - ((gctUINT8_PTR) item);
    return vlen + gcdPREFIX_SIZE + item->dataSize;
#else
    gcsBUFITEM_BUFFER_PTR item = (gcsBUFITEM_BUFFER_PTR) Item;
    return gcmSIZEOF(gcsBUFITEM_BUFFER) + item->dataSize;
#endif
}

static gcfGETITEMSIZE _itemSize[] =
{
    _GetTerminatorItemSize,
    _GetPrefixItemSize,
    _GetStringItemSize,
    _GetCopyItemSize,
    _GetBufferItemSize
};
#endif

/******************************************************************************\
******************************* Printing Functions *****************************
\******************************************************************************/

#if gcdDEBUG || gcdBUFFERED_OUTPUT
static void
_DirectPrint(
    gctCONST_STRING Message,
    ...
    )
{
    gctINT len;
    char buffer[768];
    gctARGUMENTS arguments;

    gcmkARGUMENTS_START(arguments, Message);
    len = gcmkVSPRINTF(buffer, gcmSIZEOF(buffer), Message, arguments);
    gcmkARGUMENTS_END(arguments);

    buffer[len] = '\0';
    gcmkOUTPUT_STRING(buffer);
}
#endif

static int
_AppendIndent(
    IN gctINT Indent,
    IN char * Buffer,
    IN int BufferSize
    )
{
    gctINT i;

    gctINT len    = 0;
    gctINT indent = Indent % 40;

    for (i = 0; i < indent; i += 1)
    {
        Buffer[len++] = ' ';
    }

    if (indent != Indent)
    {
        len += gcmkSPRINTF(
            Buffer + len, BufferSize - len, " <%d> ", Indent
            );

        Buffer[len] = '\0';
    }

    return len;
}

#if gcdHAVEPREFIX
static void
_PrintPrefix(
    IN gcsBUFFERED_OUTPUT_PTR OutputBuffer,
    IN gctPOINTER Data
    )
{
    char buffer[768];
    gctINT len;

    /* Format the string. */
    len = gcmkVSPRINTF(buffer, gcmSIZEOF(buffer), _prefixFormat, Data);
    buffer[len] = '\0';

    /* Print the string. */
    gcmkOUTPUT_STRING(buffer);
}
#endif

static void
_PrintString(
    IN gcsBUFFERED_OUTPUT_PTR OutputBuffer,
    IN gctINT Indent,
    IN gctCONST_STRING Message,
    IN gctUINT ArgumentSize,
    IN gctPOINTER Data
    )
{
    char buffer[768];
    gctINT len;

    /* Append the indent string. */
    len = _AppendIndent(Indent, buffer, gcmSIZEOF(buffer));

    /* Format the string. */
    len += gcmkVSPRINTF(buffer + len, gcmSIZEOF(buffer) - len, Message, Data);
    buffer[len] = '\0';

    /* Add end-of-line if missing. */
    if (buffer[len - 1] != '\n')
    {
        buffer[len++] = '\n';
        buffer[len] = '\0';
    }

    /* Print the string. */
    gcmkOUTPUT_STRING(buffer);
}

static void
_PrintBuffer(
    IN gcsBUFFERED_OUTPUT_PTR OutputBuffer,
    IN gctINT Indent,
    IN gctPOINTER PrefixData,
    IN gctPOINTER Data,
    IN gctUINT Address,
    IN gctUINT DataSize,
    IN gceDUMP_BUFFER Type,
    IN gctUINT32 DmaAddress
    )
{
    static gctCONST_STRING _titleString[] =
    {
        "CONTEXT BUFFER",
        "USER COMMAND BUFFER",
        "KERNEL COMMAND BUFFER",
        "LINK BUFFER",
        "WAIT LINK BUFFER",
        ""
    };

    static const gctINT COLUMN_COUNT = 8;

    gctUINT i, count, column, address;
    gctUINT32_PTR data;
    gctCHAR buffer[768];
    gctUINT indent, len;
    gctBOOL command;

    /* Append space for the prefix. */
#if gcdHAVEPREFIX
    indent = gcmkVSPRINTF(buffer, gcmSIZEOF(buffer), _prefixFormat, PrefixData);
    buffer[indent] = '\0';
#else
    indent = 0;
#endif

    /* Append the indent string. */
    indent += _AppendIndent(
        Indent, buffer + indent, gcmSIZEOF(buffer) - indent
        );

    switch (Type)
    {
    case gceDUMP_BUFFER_CONTEXT:
    case gceDUMP_BUFFER_USER:
    case gceDUMP_BUFFER_KERNEL:
    case gceDUMP_BUFFER_LINK:
    case gceDUMP_BUFFER_WAITLINK:
        /* Form and print the title string. */
        gcmkSPRINTF2(
            buffer + indent, gcmSIZEOF(buffer) - indent,
            "%s%s\n", _titleString[Type],
            ((DmaAddress >= Address) && (DmaAddress < Address + DataSize))
                ? " (CURRENT)" : ""
            );

        gcmkOUTPUT_STRING(buffer);

        /* Terminate the string. */
        buffer[indent] = '\0';

        /* This is a command buffer. */
        command = gcvTRUE;
        break;

    case gceDUMP_BUFFER_FROM_USER:
        /* This is not a command buffer. */
        command = gcvFALSE;

        /* No title. */
        break;

    default:
        gcmDBGASSERT(gcvFALSE, "%s", "invalid buffer type");

        /* This is not a command buffer. */
        command = gcvFALSE;
    }

    /* Overwrite the prefix with spaces. */
    for (i = 0; i < indent; i += 1)
    {
        buffer[i] = ' ';
    }

    /* Form and print the opening string. */
    if (command)
    {
        gcmkSPRINTF2(
            buffer + indent, gcmSIZEOF(buffer) - indent,
            "@[kernel.command %08X %08X\n", Address, DataSize
            );

        gcmkOUTPUT_STRING(buffer);

        /* Terminate the string. */
        buffer[indent] = '\0';
    }

    /* Get initial address. */
    address = Address;

    /* Cast the data pointer. */
    data = (gctUINT32_PTR) Data;

    /* Compute the number of double words. */
    count = DataSize / gcmSIZEOF(gctUINT32);

    /* Print the buffer. */
    for (i = 0, len = indent, column = 0; i < count; i += 1)
    {
        /* Append the address. */
        if (column == 0)
        {
            len += gcmkSPRINTF(
                buffer + len, gcmSIZEOF(buffer) - len, "0x%08X:", address
                );
        }

        /* Append the data value. */
        len += gcmkSPRINTF2(
            buffer + len, gcmSIZEOF(buffer) - len, "%c%08X",
            (address == DmaAddress)? '>' : ' ', data[i]
            );

        buffer[len] = '\0';

        /* Update the address. */
        address += gcmSIZEOF(gctUINT32);

        /* Advance column count. */
        column += 1;

        /* End of line? */
        if ((column % COLUMN_COUNT) == 0)
        {
            /* Append EOL. */
            gcmkSTRCAT(buffer + len, gcmSIZEOF(buffer) - len, "\n");

            /* Print the string. */
            gcmkOUTPUT_STRING(buffer);

            /* Reset. */
            len    = indent;
            column = 0;
        }
    }

    /* Print the last partial string. */
    if (column != 0)
    {
        /* Append EOL. */
        gcmkSTRCAT(buffer + len, gcmSIZEOF(buffer) - len, "\n");

        /* Print the string. */
        gcmkOUTPUT_STRING(buffer);
    }

    /* Form and print the opening string. */
    if (command)
    {
        buffer[indent] = '\0';
        gcmkSTRCAT(buffer, gcmSIZEOF(buffer), "] -- command\n");
        gcmkOUTPUT_STRING(buffer);
    }
}

#if gcdBUFFERED_OUTPUT
static gctUINT
_PrintNone(
    IN gcsBUFFERED_OUTPUT_PTR OutputBuffer,
    IN gcsBUFITEM_HEAD_PTR Item
    )
{
    /* Return the size of the node. */
    return gcmSIZEOF(gcsBUFITEM_HEAD);
}

static gctUINT
_PrintPrefixWrapper(
    IN gcsBUFFERED_OUTPUT_PTR OutputBuffer,
    IN gcsBUFITEM_HEAD_PTR Item
    )
{
#if gcdHAVEPREFIX
    gcsBUFITEM_PREFIX_PTR item;
    gctUINT vlen;

    /* Get access to the data. */
    item = (gcsBUFITEM_PREFIX_PTR) Item;

    /* Print the message. */
    _PrintPrefix(OutputBuffer, item->prefixData);

    /* Compute the size of the variable portion of the structure. */
    vlen = ((gctUINT8_PTR) item->prefixData) - ((gctUINT8_PTR) item);

    /* Return the size of the node. */
    return vlen + gcdPREFIX_SIZE;
#else
    return gcmSIZEOF(gcsBUFITEM_PREFIX);
#endif
}

static gctUINT
_PrintStringWrapper(
    IN gcsBUFFERED_OUTPUT_PTR OutputBuffer,
    IN gcsBUFITEM_HEAD_PTR Item
    )
{
    gcsBUFITEM_STRING_PTR item;
    gctUINT vlen;

    /* Get access to the data. */
    item = (gcsBUFITEM_STRING_PTR) Item;

    /* Print the message. */
    _PrintString(
        OutputBuffer,
        item->indent, item->message, item->messageDataSize, item->messageData
        );

    /* Compute the size of the variable portion of the structure. */
    vlen = ((gctUINT8_PTR) item->messageData) - ((gctUINT8_PTR) item);

    /* Return the size of the node. */
    return vlen + item->messageDataSize;
}

static gctUINT
_PrintCopyWrapper(
    IN gcsBUFFERED_OUTPUT_PTR OutputBuffer,
    IN gcsBUFITEM_HEAD_PTR Item
    )
{
    gcsBUFITEM_COPY_PTR item;
    gctCONST_STRING message;
    gctUINT vlen;

    /* Get access to the data. */
    item = (gcsBUFITEM_COPY_PTR) Item;

    /* Determine the string pointer. */
    message = (gctCONST_STRING) (item + 1);

    /* Print the message. */
    _PrintString(
        OutputBuffer,
        item->indent, message, item->messageDataSize, item->messageData
        );

    /* Compute the size of the variable portion of the structure. */
    vlen = ((gctUINT8_PTR) item->messageData) - ((gctUINT8_PTR) item);

    /* Return the size of the node. */
    return vlen + item->messageDataSize;
}

static gctUINT
_PrintBufferWrapper(
    IN gcsBUFFERED_OUTPUT_PTR OutputBuffer,
    IN gcsBUFITEM_HEAD_PTR Item
    )
{
#if gcdHAVEPREFIX
    gctUINT32 dmaAddress;
    gcsBUFITEM_BUFFER_PTR item;
    gctPOINTER data;
    gctUINT vlen;

    /* Get access to the data. */
    item = (gcsBUFITEM_BUFFER_PTR) Item;

#if gcdDMA_BUFFER_COUNT && (gcdTHREAD_BUFFERS == 1)
    dmaAddress = item->dmaAddress;
#else
    dmaAddress = 0xFFFFFFFF;
#endif

    if (dmaAddress != 0)
    {
        /* Compute the data address. */
        data = ((gctUINT8_PTR) item->prefixData) + gcdPREFIX_SIZE;

        /* Print buffer. */
        _PrintBuffer(
            OutputBuffer,
            item->indent, item->prefixData,
            data, item->address, item->dataSize,
            item->bufferType, dmaAddress
            );
    }

    /* Compute the size of the variable portion of the structure. */
    vlen = ((gctUINT8_PTR) item->prefixData) - ((gctUINT8_PTR) item);

    /* Return the size of the node. */
    return vlen + gcdPREFIX_SIZE + item->dataSize;
#else
    gctUINT32 dmaAddress;
    gcsBUFITEM_BUFFER_PTR item;

    /* Get access to the data. */
    item = (gcsBUFITEM_BUFFER_PTR) Item;

#if gcdDMA_BUFFER_COUNT && (gcdTHREAD_BUFFERS == 1)
    dmaAddress = item->dmaAddress;
#else
    dmaAddress = 0xFFFFFFFF;
#endif

    if (dmaAddress != 0)
    {
        /* Print buffer. */
        _PrintBuffer(
            OutputBuffer,
            item->indent, gcvNULL,
            item + 1, item->address, item->dataSize,
            item->bufferType, dmaAddress
            );
    }

    /* Return the size of the node. */
    return gcmSIZEOF(gcsBUFITEM_BUFFER) + item->dataSize;
#endif
}

static gcfPRINTSTRING _printArray[] =
{
    _PrintNone,
    _PrintPrefixWrapper,
    _PrintStringWrapper,
    _PrintCopyWrapper,
    _PrintBufferWrapper
};
#endif

/******************************************************************************\
******************************* Private Functions ******************************
\******************************************************************************/

#if gcdBUFFERED_OUTPUT

#if gcdDMA_BUFFER_COUNT && (gcdTHREAD_BUFFERS == 1)
static gcsBUFITEM_BUFFER_PTR
_FindCurrentDMABuffer(
    gctUINT32 DmaAddress
    )
{
    gctINT i, skip;
    gcsBUFITEM_HEAD_PTR item;
    gcsBUFITEM_BUFFER_PTR dmaCurrent;

    /* Reset the current buffer. */
    dmaCurrent = gcvNULL;

    /* Get the first stored item. */
    item = (gcsBUFITEM_HEAD_PTR) &_outputBufferHead->buffer[_outputBufferHead->start];

    /* Run through all items. */
    for (i = 0; i < _outputBufferHead->count; i += 1)
    {
        /* Buffer item? */
        if (item->type == gcvBUFITEM_BUFFER)
        {
            gcsBUFITEM_BUFFER_PTR buffer = (gcsBUFITEM_BUFFER_PTR) item;

            if ((DmaAddress >= buffer->address) &&
                (DmaAddress <  buffer->address + buffer->dataSize))
            {
                dmaCurrent = buffer;
            }
        }

        /* Get the item size and skip it. */
        skip = (* _itemSize[item->type]) (item);
        item = (gcsBUFITEM_HEAD_PTR) ((gctUINT8_PTR) item + skip);

        /* End of the buffer? Wrap around. */
        if (item->type == gceBUFITEM_NONE)
        {
            item = (gcsBUFITEM_HEAD_PTR) _outputBufferHead->buffer;
        }
    }

    /* Return result. */
    return dmaCurrent;
}

static void
_EnableAllDMABuffers(
    void
    )
{
    gctINT i, skip;
    gcsBUFITEM_HEAD_PTR item;

    /* Get the first stored item. */
    item = (gcsBUFITEM_HEAD_PTR) &_outputBufferHead->buffer[_outputBufferHead->start];

    /* Run through all items. */
    for (i = 0; i < _outputBufferHead->count; i += 1)
    {
        /* Buffer item? */
        if (item->type == gcvBUFITEM_BUFFER)
        {
            gcsBUFITEM_BUFFER_PTR buffer = (gcsBUFITEM_BUFFER_PTR) item;

            /* Enable the buffer. */
            buffer->dmaAddress = ~0U;
        }

        /* Get the item size and skip it. */
        skip = (* _itemSize[item->type]) (item);
        item = (gcsBUFITEM_HEAD_PTR) ((gctUINT8_PTR) item + skip);

        /* End of the buffer? Wrap around. */
        if (item->type == gceBUFITEM_NONE)
        {
            item = (gcsBUFITEM_HEAD_PTR) _outputBufferHead->buffer;
        }
    }
}

static void
_EnableDMABuffers(
    gctUINT32 DmaAddress,
    gcsBUFITEM_BUFFER_PTR CurrentDMABuffer
    )
{
    gctINT i, skip, index;
    gcsBUFITEM_HEAD_PTR item;
    gcsBUFITEM_BUFFER_PTR buffers[gcdDMA_BUFFER_COUNT];

    /* Reset buffer pointers. */
    gckOS_ZeroMemory(buffers, gcmSIZEOF(buffers));

    /* Set the current buffer index. */
    index = -1;

    /* Get the first stored item. */
    item = (gcsBUFITEM_HEAD_PTR) &_outputBufferHead->buffer[_outputBufferHead->start];

    /* Run through all items until the current DMA buffer is found. */
    for (i = 0; i < _outputBufferHead->count; i += 1)
    {
        /* Buffer item? */
        if (item->type == gcvBUFITEM_BUFFER)
        {
            /* Advance the index. */
            index = (index + 1) % gcdDMA_BUFFER_COUNT;

            /* Add to the buffer array. */
            buffers[index] = (gcsBUFITEM_BUFFER_PTR) item;

            /* Stop if this is the current DMA buffer. */
            if ((gcsBUFITEM_BUFFER_PTR) item == CurrentDMABuffer)
            {
                break;
            }
        }

        /* Get the item size and skip it. */
        skip = (* _itemSize[item->type]) (item);
        item = (gcsBUFITEM_HEAD_PTR) ((gctUINT8_PTR) item + skip);

        /* End of the buffer? Wrap around. */
        if (item->type == gceBUFITEM_NONE)
        {
            item = (gcsBUFITEM_HEAD_PTR) _outputBufferHead->buffer;
        }
    }

    /* Enable the found buffers. */
    gcmDBGASSERT(index != -1, "%d", index);

    for (i = 0; i < gcdDMA_BUFFER_COUNT; i += 1)
    {
        if (buffers[index] == gcvNULL)
        {
            break;
        }

        buffers[index]->dmaAddress = DmaAddress;

        index -= 1;

        if (index == -1)
        {
            index = gcdDMA_BUFFER_COUNT - 1;
        }
    }
}
#endif

static void
_Flush(
    gctUINT32 DmaAddress
    )
{
    gctINT i, skip;
    gcsBUFITEM_HEAD_PTR item;

    gcsBUFFERED_OUTPUT_PTR outputBuffer = _outputBufferHead;

#if gcdDMA_BUFFER_COUNT && (gcdTHREAD_BUFFERS == 1)
    if ((outputBuffer != gcvNULL) && (outputBuffer->count != 0))
    {
        /* Find the current DMA buffer. */
        gcsBUFITEM_BUFFER_PTR dmaCurrent = _FindCurrentDMABuffer(DmaAddress);

        /* Was the current buffer found? */
        if (dmaCurrent == gcvNULL)
        {
            /* No, print all buffers. */
            _EnableAllDMABuffers();
        }
        else
        {
            /* Yes, enable only specified number of buffers. */
            _EnableDMABuffers(DmaAddress, dmaCurrent);
        }
    }
#endif

    while (outputBuffer != gcvNULL)
    {
        if (outputBuffer->count != 0)
        {
            _DirectPrint("********************************************************************************\n");
            _DirectPrint("FLUSHING DEBUG OUTPUT BUFFER (%d elements).\n", outputBuffer->count);
            _DirectPrint("********************************************************************************\n");

            item = (gcsBUFITEM_HEAD_PTR) &outputBuffer->buffer[outputBuffer->start];

            for (i = 0; i < outputBuffer->count; i += 1)
            {
                skip = (* _printArray[item->type]) (outputBuffer, item);

                item = (gcsBUFITEM_HEAD_PTR) ((gctUINT8_PTR) item + skip);

                if (item->type == gceBUFITEM_NONE)
                {
                    item = (gcsBUFITEM_HEAD_PTR) outputBuffer->buffer;
                }
            }

            outputBuffer->start = 0;
            outputBuffer->index = 0;
            outputBuffer->count = 0;
        }

        outputBuffer = outputBuffer->next;
    }
}

static gcsBUFITEM_HEAD_PTR
_AllocateItem(
    IN gcsBUFFERED_OUTPUT_PTR OutputBuffer,
    IN gctINT Size
    )
{
    gctINT skip;
    gcsBUFITEM_HEAD_PTR item, next;

#if gcdENABLE_OVERFLOW
    if (
            (OutputBuffer->index + Size >= gcdBUFFERED_SIZE - gcmSIZEOF(gcsBUFITEM_HEAD))
            ||
            (
                (OutputBuffer->index        <  OutputBuffer->start) &&
                (OutputBuffer->index + Size >= OutputBuffer->start)
            )
    )
    {
        if (OutputBuffer->index + Size >= gcdBUFFERED_SIZE - gcmSIZEOF(gcsBUFITEM_HEAD))
        {
            if (OutputBuffer->index < OutputBuffer->start)
            {
                item = (gcsBUFITEM_HEAD_PTR) &OutputBuffer->buffer[OutputBuffer->start];

                while (item->type != gceBUFITEM_NONE)
                {
                    skip = (* _itemSize[item->type]) (item);

                    OutputBuffer->start += skip;
                    OutputBuffer->count -= 1;

                    item->type = gceBUFITEM_NONE;
                    item = (gcsBUFITEM_HEAD_PTR) ((gctUINT8_PTR) item + skip);
                }

                OutputBuffer->start = 0;
            }

            OutputBuffer->index = 0;
        }

        item = (gcsBUFITEM_HEAD_PTR) &OutputBuffer->buffer[OutputBuffer->start];

        while (OutputBuffer->start - OutputBuffer->index <= Size)
        {
            skip = (* _itemSize[item->type]) (item);

            OutputBuffer->start += skip;
            OutputBuffer->count -= 1;

            item->type = gceBUFITEM_NONE;
            item = (gcsBUFITEM_HEAD_PTR) ((gctUINT8_PTR) item + skip);

            if (item->type == gceBUFITEM_NONE)
            {
                OutputBuffer->start = 0;
                break;
            }
        }
    }
#else
    if (OutputBuffer->index + Size > gcdBUFFERED_SIZE - gcmSIZEOF(gcsBUFITEM_HEAD))
    {
        _DirectPrint("\nMessage buffer full; forcing message flush.\n\n");
        _Flush(~0U);
    }
#endif

    item = (gcsBUFITEM_HEAD_PTR) &OutputBuffer->buffer[OutputBuffer->index];

    OutputBuffer->index += Size;
    OutputBuffer->count += 1;

    next = (gcsBUFITEM_HEAD_PTR) ((gctUINT8_PTR) item + Size);
    next->type = gceBUFITEM_NONE;

    return item;
}

#if gcdALIGNBYSIZE
static void
_FreeExtraSpace(
    IN gcsBUFFERED_OUTPUT_PTR OutputBuffer,
    IN gctPOINTER Item,
    IN gctINT ItemSize,
    IN gctINT FreeSize
    )
{
    gcsBUFITEM_HEAD_PTR next;

    OutputBuffer->index -= FreeSize;

    next = (gcsBUFITEM_HEAD_PTR) ((gctUINT8_PTR) Item + ItemSize);
    next->type = gceBUFITEM_NONE;
}
#endif

#if gcdHAVEPREFIX
static void
_AppendPrefix(
    IN gcsBUFFERED_OUTPUT_PTR OutputBuffer,
    IN gctPOINTER Data
    )
{
    gctUINT8_PTR prefixData;
    gcsBUFITEM_PREFIX_PTR item;
    gctINT allocSize;

#if gcdALIGNBYSIZE
    gctUINT alignment;
    gctINT size, freeSize;
#endif

    gcmDBGASSERT(Data != gcvNULL, "%p", Data);

    /* Determine the maximum item size. */
    allocSize
        = gcmSIZEOF(gcsBUFITEM_PREFIX)
        + gcdPREFIX_SIZE
        + gcdPREFIX_ALIGNMENT;

    /* Allocate prefix item. */
    item = (gcsBUFITEM_PREFIX_PTR) _AllocateItem(OutputBuffer, allocSize);

    /* Compute the initial prefix data pointer. */
    prefixData = (gctUINT8_PTR) (item + 1);

    /* Align the data pointer as necessary. */
#if gcdALIGNBYSIZE
    alignment = gcmPTRALIGNMENT(prefixData, gcdPREFIX_ALIGNMENT);
    prefixData += alignment;
#endif

    /* Set item data. */
    item->type       = gcvBUFITEM_PREFIX;
    item->prefixData = prefixData;

    /* Copy argument value. */
    memcpy(prefixData, Data, gcdPREFIX_SIZE);

#if gcdALIGNBYSIZE
    /* Compute the actual node size. */
    size = gcmSIZEOF(gcsBUFITEM_PREFIX) + gcdPREFIX_SIZE + alignment;

    /* Free extra memory if any. */
    freeSize = allocSize - size;
    if (freeSize != 0)
    {
        _FreeExtraSpace(OutputBuffer, item, size, freeSize);
    }
#endif
}
#endif

static void
_AppendString(
    IN gcsBUFFERED_OUTPUT_PTR OutputBuffer,
    IN gctINT Indent,
    IN gctCONST_STRING Message,
    IN gctUINT ArgumentSize,
    IN gctPOINTER Data
    )
{
    gctUINT8_PTR messageData;
    gcsBUFITEM_STRING_PTR item;
    gctINT allocSize;

#if gcdALIGNBYSIZE
    gctUINT alignment;
    gctINT size, freeSize;
#endif

    /* Determine the maximum item size. */
    allocSize
        = gcmSIZEOF(gcsBUFITEM_STRING)
        + ArgumentSize
        + gcdVARARG_ALIGNMENT;

    /* Allocate prefix item. */
    item = (gcsBUFITEM_STRING_PTR) _AllocateItem(OutputBuffer, allocSize);

    /* Compute the initial message data pointer. */
    messageData = (gctUINT8_PTR) (item + 1);

    /* Align the data pointer as necessary. */
#if gcdALIGNBYSIZE
    alignment = gcmPTRALIGNMENT(messageData, gcdVARARG_ALIGNMENT);
    messageData += alignment;
#endif

    /* Set item data. */
    item->type            = gcvBUFITEM_STRING;
    item->indent          = Indent;
    item->message         = Message;
    item->messageData     = messageData;
    item->messageDataSize = ArgumentSize;

    /* Copy argument value. */
    if (ArgumentSize != 0)
    {
        memcpy(messageData, Data, ArgumentSize);
    }

#if gcdALIGNBYSIZE
    /* Compute the actual node size. */
    size = gcmSIZEOF(gcsBUFITEM_STRING) + ArgumentSize + alignment;

    /* Free extra memory if any. */
    freeSize = allocSize - size;
    if (freeSize != 0)
    {
        _FreeExtraSpace(OutputBuffer, item, size, freeSize);
    }
#endif
}

static void
_AppendCopy(
    IN gcsBUFFERED_OUTPUT_PTR OutputBuffer,
    IN gctINT Indent,
    IN gctCONST_STRING Message,
    IN gctUINT ArgumentSize,
    IN gctPOINTER Data
    )
{
    gctUINT8_PTR messageData;
    gcsBUFITEM_COPY_PTR item;
    gctINT allocSize;
    gctINT messageLength;
    gctCONST_STRING message;

#if gcdALIGNBYSIZE
    gctUINT alignment;
    gctINT size, freeSize;
#endif

    /* Get the length of the string. */
    messageLength = strlen(Message) + 1;

    /* Determine the maximum item size. */
    allocSize
        = gcmSIZEOF(gcsBUFITEM_COPY)
        + messageLength
        + ArgumentSize
        + gcdVARARG_ALIGNMENT;

    /* Allocate prefix item. */
    item = (gcsBUFITEM_COPY_PTR) _AllocateItem(OutputBuffer, allocSize);

    /* Determine the message placement. */
    message = (gctCONST_STRING) (item + 1);

    /* Compute the initial message data pointer. */
    messageData = (gctUINT8_PTR) message + messageLength;

    /* Align the data pointer as necessary. */
#if gcdALIGNBYSIZE
    if (ArgumentSize == 0)
    {
        alignment = 0;
    }
    else
    {
        alignment = gcmPTRALIGNMENT(messageData, gcdVARARG_ALIGNMENT);
        messageData += alignment;
    }
#endif

    /* Set item data. */
    item->type            = gcvBUFITEM_COPY;
    item->indent          = Indent;
    item->messageData     = messageData;
    item->messageDataSize = ArgumentSize;

    /* Copy the message. */
    memcpy((gctPOINTER) message, Message, messageLength);

    /* Copy argument value. */
    if (ArgumentSize != 0)
    {
        memcpy(messageData, Data, ArgumentSize);
    }

#if gcdALIGNBYSIZE
    /* Compute the actual node size. */
    size
        = gcmSIZEOF(gcsBUFITEM_COPY)
        + messageLength
        + ArgumentSize
        + alignment;

    /* Free extra memory if any. */
    freeSize = allocSize - size;
    if (freeSize != 0)
    {
        _FreeExtraSpace(OutputBuffer, item, size, freeSize);
    }
#endif
}

static void
_AppendBuffer(
    IN gcsBUFFERED_OUTPUT_PTR OutputBuffer,
    IN gctINT Indent,
    IN gctPOINTER PrefixData,
    IN gctPOINTER Data,
    IN gctUINT Address,
    IN gctUINT DataSize,
    IN gceDUMP_BUFFER Type,
    IN gctUINT32 DmaAddress
    )
{
#if gcdHAVEPREFIX
    gctUINT8_PTR prefixData;
    gcsBUFITEM_BUFFER_PTR item;
    gctINT allocSize;
    gctPOINTER data;

#if gcdALIGNBYSIZE
    gctUINT alignment;
    gctINT size, freeSize;
#endif

    gcmDBGASSERT(DataSize != 0, "%d", DataSize);
    gcmDBGASSERT(Data != gcvNULL, "%p", Data);

    /* Determine the maximum item size. */
    allocSize
        = gcmSIZEOF(gcsBUFITEM_BUFFER)
        + gcdPREFIX_SIZE
        + gcdPREFIX_ALIGNMENT
        + DataSize;

    /* Allocate prefix item. */
    item = (gcsBUFITEM_BUFFER_PTR) _AllocateItem(OutputBuffer, allocSize);

    /* Compute the initial prefix data pointer. */
    prefixData = (gctUINT8_PTR) (item + 1);

#if gcdALIGNBYSIZE
    /* Align the data pointer as necessary. */
    alignment = gcmPTRALIGNMENT(prefixData, gcdPREFIX_ALIGNMENT);
    prefixData += alignment;
#endif

    /* Set item data. */
    item->type       = gcvBUFITEM_BUFFER;
    item->indent     = Indent;
    item->bufferType = Type;
    item->dataSize   = DataSize;
    item->address    = Address;
    item->prefixData = prefixData;

#if gcdDMA_BUFFER_COUNT && (gcdTHREAD_BUFFERS == 1)
    item->dmaAddress = DmaAddress;
#endif

    /* Copy prefix data. */
    memcpy(prefixData, PrefixData, gcdPREFIX_SIZE);

    /* Compute the data pointer. */
    data = prefixData + gcdPREFIX_SIZE;

    /* Copy argument value. */
    memcpy(data, Data, DataSize);

#if gcdALIGNBYSIZE
    /* Compute the actual node size. */
    size
        = gcmSIZEOF(gcsBUFITEM_BUFFER)
        + gcdPREFIX_SIZE
        + alignment
        + DataSize;

    /* Free extra memory if any. */
    freeSize = allocSize - size;
    if (freeSize != 0)
    {
        _FreeExtraSpace(OutputBuffer, item, size, freeSize);
    }
#endif
#else
    gcsBUFITEM_BUFFER_PTR item;
    gctINT size;

    gcmDBGASSERT(DataSize != 0, "%d", DataSize);
    gcmDBGASSERT(Data != gcvNULL, "%p", Data);

    /* Determine the maximum item size. */
    size = gcmSIZEOF(gcsBUFITEM_BUFFER) + DataSize;

    /* Allocate prefix item. */
    item = (gcsBUFITEM_BUFFER_PTR) _AllocateItem(OutputBuffer, size);

    /* Set item data. */
    item->type     = gcvBUFITEM_BUFFER;
    item->indent   = Indent;
    item->dataSize = DataSize;
    item->address  = Address;

    /* Copy argument value. */
    memcpy(item + 1, Data, DataSize);
#endif
}
#endif

static gcmINLINE void
_InitBuffers(
    void
    )
{
    int i;

    if (_outputBufferHead == gcvNULL)
    {
        for (i = 0; i < gcdTHREAD_BUFFERS; i += 1)
        {
            if (_outputBufferTail == gcvNULL)
            {
                _outputBufferHead = &_outputBuffer[i];
            }
            else
            {
                _outputBufferTail->next = &_outputBuffer[i];
            }

#if gcdTHREAD_BUFFERS > 1
            _outputBuffer[i].threadID = ~0U;
#endif

            _outputBuffer[i].prev = _outputBufferTail;
            _outputBuffer[i].next =  gcvNULL;

            _outputBufferTail = &_outputBuffer[i];
        }
    }
}

static gcmINLINE gcsBUFFERED_OUTPUT_PTR
_GetOutputBuffer(
    void
    )
{
    gcsBUFFERED_OUTPUT_PTR outputBuffer;

#if gcdTHREAD_BUFFERS > 1
    /* Get the current thread ID. */
    gctUINT32 ThreadID = gcmkGETTHREADID();

    /* Locate the output buffer for the thread. */
    outputBuffer = _outputBufferHead;

    while (outputBuffer != gcvNULL)
    {
        if (outputBuffer->threadID == ThreadID)
        {
            break;
        }

        outputBuffer = outputBuffer->next;
    }

    /* No matching buffer found? */
    if (outputBuffer == gcvNULL)
    {
        /* Get the tail for the buffer. */
        outputBuffer = _outputBufferTail;

        /* Move it to the head. */
        _outputBufferTail       = _outputBufferTail->prev;
        _outputBufferTail->next = gcvNULL;

        outputBuffer->prev = gcvNULL;
        outputBuffer->next = _outputBufferHead;

        _outputBufferHead->prev = outputBuffer;
        _outputBufferHead       = outputBuffer;

        /* Reset the buffer. */
        outputBuffer->threadID   = ThreadID;
#if gcdBUFFERED_OUTPUT
        outputBuffer->start      = 0;
        outputBuffer->index      = 0;
        outputBuffer->count      = 0;
#endif
#if gcdSHOW_LINE_NUMBER
        outputBuffer->lineNumber = 0;
#endif
    }
#else
    outputBuffer = _outputBufferHead;
#endif

    return outputBuffer;
}

static gcmINLINE int _GetArgumentSize(
    IN gctCONST_STRING Message
    )
{
    int i, count;

    gcmDBGASSERT(Message != gcvNULL, "%p", Message);

    for (i = 0, count = 0; Message[i]; i += 1)
    {
        if (Message[i] == '%')
        {
            count += 1;
        }
    }

    return count * gcmSIZEOF(gctUINT32);
}

#if gcdHAVEPREFIX
static void
_InitPrefixData(
    IN gcsBUFFERED_OUTPUT_PTR OutputBuffer,
    IN gctPOINTER Data
    )
{
    gctUINT8_PTR data  = (gctUINT8_PTR) Data;

#if gcdSHOW_TIME
    {
        gctUINT64 time;
        gckOS_GetProfileTick(&time);
        gcmkALIGNPTR(gctUINT8_PTR, data, gcmSIZEOF(gctUINT64));
        * ((gctUINT64_PTR) data) = time;
        data += gcmSIZEOF(gctUINT64);
    }
#endif

#if gcdSHOW_LINE_NUMBER
    {
        gcmkALIGNPTR(gctUINT8_PTR, data, gcmSIZEOF(gctUINT64));
        * ((gctUINT64_PTR) data) = OutputBuffer->lineNumber;
        data += gcmSIZEOF(gctUINT64);
    }
#endif

#if gcdSHOW_PROCESS_ID
    {
        gcmkALIGNPTR(gctUINT8_PTR, data, gcmSIZEOF(gctUINT32));
        * ((gctUINT32_PTR) data) = gcmkGETPROCESSID();
        data += gcmSIZEOF(gctUINT32);
    }
#endif

#if gcdSHOW_THREAD_ID
    {
        gcmkALIGNPTR(gctUINT8_PTR, data, gcmSIZEOF(gctUINT32));
        * ((gctUINT32_PTR) data) = gcmkGETTHREADID();
    }
#endif
}
#endif

static void
_Print(
    IN gctUINT ArgumentSize,
    IN gctBOOL CopyMessage,
    IN gctCONST_STRING Message,
    IN gctARGUMENTS Arguments
    )
{
    gcsBUFFERED_OUTPUT_PTR outputBuffer;
    gcmkDECLARE_LOCK(lockHandle);

    gcmkLOCKSECTION(lockHandle);

    /* Initialize output buffer list. */
    _InitBuffers();

    /* Locate the proper output buffer. */
    outputBuffer = _GetOutputBuffer();

    /* Update the line number. */
#if gcdSHOW_LINE_NUMBER
    outputBuffer->lineNumber += 1;
#endif

    /* Print prefix. */
#if gcdHAVEPREFIX
    {
        gctUINT8_PTR alignedPrefixData;
        gctUINT8 prefixData[gcdPREFIX_SIZE + gcdPREFIX_ALIGNMENT];

        /* Compute aligned pointer. */
        alignedPrefixData = prefixData;
        gcmkALIGNPTR(gctUINT8_PTR, alignedPrefixData, gcdPREFIX_ALIGNMENT);

        /* Initialize the prefix data. */
        _InitPrefixData(outputBuffer, alignedPrefixData);

        /* Print the prefix. */
        gcdOUTPUTPREFIX(outputBuffer, alignedPrefixData);
    }
#endif

    /* Form the indent string. */
    if (strncmp(Message, "--", 2) == 0)
    {
        outputBuffer->indent -= 2;
    }

    /* Print the message. */
    if (CopyMessage)
    {
        gcdOUTPUTCOPY(
            outputBuffer, outputBuffer->indent,
            Message, ArgumentSize, * (gctPOINTER *) &Arguments
            );
    }
    else
    {
        gcdOUTPUTSTRING(
            outputBuffer, outputBuffer->indent,
            Message, ArgumentSize, * (gctPOINTER *) &Arguments
            );
    }

    /* Check increasing indent. */
    if (strncmp(Message, "++", 2) == 0)
    {
        outputBuffer->indent += 2;
    }

    gcmkUNLOCKSECTION(lockHandle);
}


/******************************************************************************\
********************************* Debug Macros *********************************
\******************************************************************************/

#ifdef __QNXNTO__

extern volatile unsigned g_nQnxInIsrs;

#define gcmDEBUGPRINT(ArgumentSize, CopyMessage, Message) \
{ \
    if (atomic_add_value(&g_nQnxInIsrs, 1) == 0) \
    { \
        gctARGUMENTS __arguments__; \
        gcmkARGUMENTS_START(__arguments__, Message); \
        _Print(ArgumentSize, CopyMessage, Message, __arguments__); \
        gcmkARGUMENTS_END(__arguments__); \
    } \
    atomic_sub(&g_nQnxInIsrs, 1); \
}

#else

#define gcmDEBUGPRINT(ArgumentSize, CopyMessage, Message) \
{ \
    gctARGUMENTS __arguments__; \
    gcmkARGUMENTS_START(__arguments__, Message); \
    _Print(ArgumentSize, CopyMessage, Message, __arguments__); \
    gcmkARGUMENTS_END(__arguments__); \
}

#endif

/******************************************************************************\
********************************** Debug Code **********************************
\******************************************************************************/

/*******************************************************************************
**
**  gckOS_Print
**
**  Send a message to the debugger.
**
**  INPUT:
**
**      gctCONST_STRING Message
**          Pointer to message.
**
**      ...
**          Optional arguments.
**
**  OUTPUT:
**
**      Nothing.
*/

void
gckOS_Print(
    IN gctCONST_STRING Message,
    ...
    )
{
    gcmDEBUGPRINT(_GetArgumentSize(Message), gcvFALSE, Message);
}

/*******************************************************************************
**
**  gckOS_PrintN
**
**  Send a message to the debugger.
**
**  INPUT:
**
**      gctUINT ArgumentSize
**          The size of the optional arguments in bytes.
**
**      gctCONST_STRING Message
**          Pointer to message.
**
**      ...
**          Optional arguments.
**
**  OUTPUT:
**
**      Nothing.
*/

void
gckOS_PrintN(
    IN gctUINT ArgumentSize,
    IN gctCONST_STRING Message,
    ...
    )
{
    gcmDEBUGPRINT(ArgumentSize, gcvFALSE, Message);
}

/*******************************************************************************
**
**  gckOS_CopyPrint
**
**  Send a message to the debugger. If in buffered output mode, the entire
**  message will be copied into the buffer instead of using the pointer to
**  the string.
**
**  INPUT:
**
**      gctCONST_STRING Message
**          Pointer to message.
**
**      ...
**          Optional arguments.
**
**  OUTPUT:
**
**      Nothing.
*/

void
gckOS_CopyPrint(
    IN gctCONST_STRING Message,
    ...
    )
{
    gcmDEBUGPRINT(_GetArgumentSize(Message), gcvTRUE, Message);
}

/*******************************************************************************
**
**  gckOS_DumpBuffer
**
**  Print the contents of the specified buffer.
**
**  INPUT:
**
**      gckOS Os
**          Pointer to gckOS object.
**
**      gctPOINTER Buffer
**          Pointer to the buffer to print.
**
**      gctUINT Size
**          Size of the buffer.
**
**      gceDUMP_BUFFER Type
**          Buffer type.
**
**  OUTPUT:
**
**      Nothing.
*/

void
gckOS_DumpBuffer(
    IN gckOS Os,
    IN gctPOINTER Buffer,
    IN gctUINT Size,
    IN gceDUMP_BUFFER Type,
    IN gctBOOL CopyMessage
    )
{
    gctUINT32 address;
    gcsBUFFERED_OUTPUT_PTR outputBuffer;
    static gctBOOL userLocked;
    gctCHAR *buffer = (gctCHAR*)Buffer;

    gcmkDECLARE_LOCK(lockHandle);

    /* Request lock when not coming from user,
       or coming from user and not yet locked
          and message is starting with @[. */
    if (Type == gceDUMP_BUFFER_FROM_USER)
    {
        if ((Size > 2)
        && (buffer[0] == '@')
        && (buffer[1] == '['))
        {
            /* Beginning of a user dump. */
            gcmkLOCKSECTION(lockHandle);
            userLocked = gcvTRUE;
        }
        /* Else, let it pass through. */
    }
    else
    {
        gcmkLOCKSECTION(lockHandle);
        userLocked = gcvFALSE;
    }

    if (Buffer != gcvNULL)
    {
        /* Initialize output buffer list. */
        _InitBuffers();

        /* Locate the proper output buffer. */
        outputBuffer = _GetOutputBuffer();

        /* Update the line number. */
#if gcdSHOW_LINE_NUMBER
        outputBuffer->lineNumber += 1;
#endif

        /* Get the physical address of the buffer. */
        if (Type != gceDUMP_BUFFER_FROM_USER)
        {
            gcmkVERIFY_OK(gckOS_GetPhysicalAddress(Os, Buffer, &address));
        }
        else
        {
            address = 0;
        }

#if gcdHAVEPREFIX
        {
            gctUINT8_PTR alignedPrefixData;
            gctUINT8 prefixData[gcdPREFIX_SIZE + gcdPREFIX_ALIGNMENT];

            /* Compute aligned pointer. */
            alignedPrefixData = prefixData;
            gcmkALIGNPTR(gctUINT8_PTR, alignedPrefixData, gcdPREFIX_ALIGNMENT);

            /* Initialize the prefix data. */
            _InitPrefixData(outputBuffer, alignedPrefixData);

            /* Print/schedule the buffer. */
            gcdOUTPUTBUFFER(
                outputBuffer, outputBuffer->indent,
                alignedPrefixData, Buffer, address, Size, Type, 0
                );
        }
#else
        /* Print/schedule the buffer. */
        if (Type == gceDUMP_BUFFER_FROM_USER)
        {
            gcdOUTPUTSTRING(
                outputBuffer, outputBuffer->indent,
                Buffer, 0, gcvNULL
                );
        }
        else
        {
            gcdOUTPUTBUFFER(
                outputBuffer, outputBuffer->indent,
                gcvNULL, Buffer, address, Size, Type, 0
                );
        }
#endif
    }

    /* Unlock when not coming from user,
       or coming from user and not yet locked. */
    if (userLocked)
    {
        if ((Size > 4)
        && (buffer[0] == ']')
        && (buffer[1] == ' ')
        && (buffer[2] == '-')
        && (buffer[3] == '-'))
        {
            /* End of a user dump. */
            gcmkUNLOCKSECTION(lockHandle);
            userLocked = gcvFALSE;
        }
        /* Else, let it pass through, don't unlock. */
    }
    else
    {
        gcmkUNLOCKSECTION(lockHandle);
    }
}

/*******************************************************************************
**
**  gckOS_DebugTrace
**
**  Send a leveled message to the debugger.
**
**  INPUT:
**
**      gctUINT32 Level
**          Debug level of message.
**
**      gctCONST_STRING Message
**          Pointer to message.
**
**      ...
**          Optional arguments.
**
**  OUTPUT:
**
**      Nothing.
*/

void
gckOS_DebugTrace(
    IN gctUINT32 Level,
    IN gctCONST_STRING Message,
    ...
    )
{
    if (Level > _debugLevel)
    {
//        return;
    }

    gcmDEBUGPRINT(_GetArgumentSize(Message), gcvFALSE, Message);
}

/*******************************************************************************
**
**  gckOS_DebugTraceN
**
**  Send a leveled message to the debugger.
**
**  INPUT:
**
**      gctUINT32 Level
**          Debug level of message.
**
**      gctUINT ArgumentSize
**          The size of the optional arguments in bytes.
**
**      gctCONST_STRING Message
**          Pointer to message.
**
**      ...
**          Optional arguments.
**
**  OUTPUT:
**
**      Nothing.
*/

void
gckOS_DebugTraceN(
    IN gctUINT32 Level,
    IN gctUINT ArgumentSize,
    IN gctCONST_STRING Message,
    ...
    )
{
    if (Level > _debugLevel)
    {
//        return;
    }

    gcmDEBUGPRINT(ArgumentSize, gcvFALSE, Message);
}

/*******************************************************************************
**
**  gckOS_DebugTraceZone
**
**  Send a leveled and zoned message to the debugger.
**
**  INPUT:
**
**      gctUINT32 Level
**          Debug level for message.
**
**      gctUINT32 Zone
**          Debug zone for message.
**
**      gctCONST_STRING Message
**          Pointer to message.
**
**      ...
**          Optional arguments.
**
**  OUTPUT:
**
**      Nothing.
*/

void
gckOS_DebugTraceZone(
    IN gctUINT32 Level,
    IN gctUINT32 Zone,
    IN gctCONST_STRING Message,
    ...
    )
{
    if ((Level > _debugLevel) || !(Zone & _debugZones))
    {
//        return;
    }

    gcmDEBUGPRINT(_GetArgumentSize(Message), gcvFALSE, Message);
}

/*******************************************************************************
**
**  gckOS_DebugTraceZoneN
**
**  Send a leveled and zoned message to the debugger.
**
**  INPUT:
**
**      gctUINT32 Level
**          Debug level for message.
**
**      gctUINT32 Zone
**          Debug zone for message.
**
**      gctUINT ArgumentSize
**          The size of the optional arguments in bytes.
**
**      gctCONST_STRING Message
**          Pointer to message.
**
**      ...
**          Optional arguments.
**
**  OUTPUT:
**
**      Nothing.
*/

void
gckOS_DebugTraceZoneN(
    IN gctUINT32 Level,
    IN gctUINT32 Zone,
    IN gctUINT ArgumentSize,
    IN gctCONST_STRING Message,
    ...
    )
{
    if ((Level > _debugLevel) || !(Zone & _debugZones))
    {
//        return;
    }

    gcmDEBUGPRINT(ArgumentSize, gcvFALSE, Message);
}

/*******************************************************************************
**
**  gckOS_DebugBreak
**
**  Break into the debugger.
**
**  INPUT:
**
**      Nothing.
**
**  OUTPUT:
**
**      Nothing.
*/
void
gckOS_DebugBreak(
    void
    )
{
    gckOS_DebugTrace(gcvLEVEL_ERROR, "%s(%d)", __FUNCTION__, __LINE__);
}

/*******************************************************************************
**
**  gckOS_DebugFatal
**
**  Send a message to the debugger and break into the debugger.
**
**  INPUT:
**
**      gctCONST_STRING Message
**          Pointer to message.
**
**      ...
**          Optional arguments.
**
**  OUTPUT:
**
**      Nothing.
*/
void
gckOS_DebugFatal(
    IN gctCONST_STRING Message,
    ...
    )
{
    gcmkPRINT_VERSION();
    gcmDEBUGPRINT(_GetArgumentSize(Message), gcvFALSE, Message);

    /* Break into the debugger. */
    gckOS_DebugBreak();
}

/*******************************************************************************
**
**  gckOS_SetDebugLevel
**
**  Set the debug level.
**
**  INPUT:
**
**      gctUINT32 Level
**          New debug level.
**
**  OUTPUT:
**
**      Nothing.
*/

void
gckOS_SetDebugLevel(
    IN gctUINT32 Level
    )
{
    _debugLevel = Level;
}

/*******************************************************************************
**
**  gckOS_SetDebugZone
**
**  Set the debug zone.
**
**  INPUT:
**
**      gctUINT32 Zone
**          New debug zone.
**
**  OUTPUT:
**
**      Nothing.
*/
void
gckOS_SetDebugZone(
    IN gctUINT32 Zone
    )
{
    _debugZones = Zone;
}

/*******************************************************************************
**
**  gckOS_SetDebugLevelZone
**
**  Set the debug level and zone.
**
**  INPUT:
**
**      gctUINT32 Level
**          New debug level.
**
**      gctUINT32 Zone
**          New debug zone.
**
**  OUTPUT:
**
**      Nothing.
*/

void
gckOS_SetDebugLevelZone(
    IN gctUINT32 Level,
    IN gctUINT32 Zone
    )
{
    _debugLevel = Level;
    _debugZones = Zone;
}

/*******************************************************************************
**
**  gckOS_SetDebugZones
**
**  Enable or disable debug zones.
**
**  INPUT:
**
**      gctUINT32 Zones
**          Debug zones to enable or disable.
**
**      gctBOOL Enable
**          Set to gcvTRUE to enable the zones (or the Zones with the current
**          zones) or gcvFALSE to disable the specified Zones.
**
**  OUTPUT:
**
**      Nothing.
*/

void
gckOS_SetDebugZones(
    IN gctUINT32 Zones,
    IN gctBOOL Enable
    )
{
    if (Enable)
    {
        /* Enable the zones. */
        _debugZones |= Zones;
    }
    else
    {
        /* Disable the zones. */
        _debugZones &= ~Zones;
    }
}

/*******************************************************************************
**
**  gckOS_Verify
**
**  Called to verify the result of a function call.
**
**  INPUT:
**
**      gceSTATUS Status
**          Function call result.
**
**  OUTPUT:
**
**      Nothing.
*/

void
gckOS_Verify(
    IN gceSTATUS Status
    )
{
    _lastError = Status;
}

/*******************************************************************************
**
**  gckOS_DebugFlush
**
**  Force messages to be flushed out.
**
**  INPUT:
**
**      gctCONST_STRING CallerName
**          Name of the caller function.
**
**      gctUINT LineNumber
**          Line number of the caller.
**
**      gctUINT32 DmaAddress
**          The current DMA address or ~0U to ignore.
**
**  OUTPUT:
**
**      Nothing.
*/

void
gckOS_DebugFlush(
    gctCONST_STRING CallerName,
    gctUINT LineNumber,
    gctUINT32 DmaAddress
    )
{
#if gcdBUFFERED_OUTPUT
    _DirectPrint("\nFlush requested by %s(%d).\n\n", CallerName, LineNumber);
    _Flush(DmaAddress);
#endif
}
gctCONST_STRING
gckOS_DebugStatus2Name(
	gceSTATUS status
	)
{
	switch (status)
	{
	case gcvSTATUS_OK:
		return "gcvSTATUS_OK";
	case gcvSTATUS_TRUE:
		return "gcvSTATUS_TRUE";
	case gcvSTATUS_NO_MORE_DATA:
		return "gcvSTATUS_NO_MORE_DATA";
	case gcvSTATUS_CACHED:
		return "gcvSTATUS_CACHED";
	case gcvSTATUS_MIPMAP_TOO_LARGE:
		return "gcvSTATUS_MIPMAP_TOO_LARGE";
	case gcvSTATUS_NAME_NOT_FOUND:
		return "gcvSTATUS_NAME_NOT_FOUND";
	case gcvSTATUS_NOT_OUR_INTERRUPT:
		return "gcvSTATUS_NOT_OUR_INTERRUPT";
	case gcvSTATUS_MISMATCH:
		return "gcvSTATUS_MISMATCH";
	case gcvSTATUS_MIPMAP_TOO_SMALL:
		return "gcvSTATUS_MIPMAP_TOO_SMALL";
	case gcvSTATUS_LARGER:
		return "gcvSTATUS_LARGER";
	case gcvSTATUS_SMALLER:
		return "gcvSTATUS_SMALLER";
	case gcvSTATUS_CHIP_NOT_READY:
		return "gcvSTATUS_CHIP_NOT_READY";
	case gcvSTATUS_NEED_CONVERSION:
		return "gcvSTATUS_NEED_CONVERSION";
	case gcvSTATUS_SKIP:
		return "gcvSTATUS_SKIP";
	case gcvSTATUS_DATA_TOO_LARGE:
		return "gcvSTATUS_DATA_TOO_LARGE";
	case gcvSTATUS_INVALID_CONFIG:
		return "gcvSTATUS_INVALID_CONFIG";
	case gcvSTATUS_CHANGED:
		return "gcvSTATUS_CHANGED";
	case gcvSTATUS_NOT_SUPPORT_DITHER:
		return "gcvSTATUS_NOT_SUPPORT_DITHER";

	case gcvSTATUS_INVALID_ARGUMENT:
		return "gcvSTATUS_INVALID_ARGUMENT";
	case gcvSTATUS_INVALID_OBJECT:
		return "gcvSTATUS_INVALID_OBJECT";
	case gcvSTATUS_OUT_OF_MEMORY:
		return "gcvSTATUS_OUT_OF_MEMORY";
	case gcvSTATUS_MEMORY_LOCKED:
		return "gcvSTATUS_MEMORY_LOCKED";
	case gcvSTATUS_MEMORY_UNLOCKED:
		return "gcvSTATUS_MEMORY_UNLOCKED";
	case gcvSTATUS_HEAP_CORRUPTED:
		return "gcvSTATUS_HEAP_CORRUPTED";
	case gcvSTATUS_GENERIC_IO:
		return "gcvSTATUS_GENERIC_IO";
	case gcvSTATUS_INVALID_ADDRESS:
		return "gcvSTATUS_INVALID_ADDRESS";
	case gcvSTATUS_CONTEXT_LOSSED:
		return "gcvSTATUS_CONTEXT_LOSSED";
	case gcvSTATUS_TOO_COMPLEX:
		return "gcvSTATUS_TOO_COMPLEX";
	case gcvSTATUS_BUFFER_TOO_SMALL:
		return "gcvSTATUS_BUFFER_TOO_SMALL";
	case gcvSTATUS_INTERFACE_ERROR:
		return "gcvSTATUS_INTERFACE_ERROR";
	case gcvSTATUS_NOT_SUPPORTED:
		return "gcvSTATUS_NOT_SUPPORTED";
	case gcvSTATUS_MORE_DATA:
		return "gcvSTATUS_MORE_DATA";
	case gcvSTATUS_TIMEOUT:
		return "gcvSTATUS_TIMEOUT";
	case gcvSTATUS_OUT_OF_RESOURCES:
		return "gcvSTATUS_OUT_OF_RESOURCES";
	case gcvSTATUS_INVALID_DATA:
		return "gcvSTATUS_INVALID_DATA";
	case gcvSTATUS_INVALID_MIPMAP:
		return "gcvSTATUS_INVALID_MIPMAP";
	case gcvSTATUS_NOT_FOUND:
		return "gcvSTATUS_NOT_FOUND";
	case gcvSTATUS_NOT_ALIGNED:
		return "gcvSTATUS_NOT_ALIGNED";
	case gcvSTATUS_INVALID_REQUEST:
		return "gcvSTATUS_INVALID_REQUEST";
	case gcvSTATUS_GPU_NOT_RESPONDING:
		return "gcvSTATUS_GPU_NOT_RESPONDING";
	case gcvSTATUS_TIMER_OVERFLOW:
		return "gcvSTATUS_TIMER_OVERFLOW";
	case gcvSTATUS_VERSION_MISMATCH:
		return "gcvSTATUS_VERSION_MISMATCH";
	case gcvSTATUS_LOCKED:
		return "gcvSTATUS_LOCKED";

    /* Linker errors. */
	case gcvSTATUS_GLOBAL_TYPE_MISMATCH:
		return "gcvSTATUS_GLOBAL_TYPE_MISMATCH";
	case gcvSTATUS_TOO_MANY_ATTRIBUTES:
		return "gcvSTATUS_TOO_MANY_ATTRIBUTES";
	case gcvSTATUS_TOO_MANY_UNIFORMS:
		return "gcvSTATUS_TOO_MANY_UNIFORMS";
	case gcvSTATUS_TOO_MANY_VARYINGS:
		return "gcvSTATUS_TOO_MANY_VARYINGS";
	case gcvSTATUS_UNDECLARED_VARYING:
		return "gcvSTATUS_UNDECLARED_VARYING";
	case gcvSTATUS_VARYING_TYPE_MISMATCH:
		return "gcvSTATUS_VARYING_TYPE_MISMATCH";
	case gcvSTATUS_MISSING_MAIN:
		return "gcvSTATUS_MISSING_MAIN";
	case gcvSTATUS_NAME_MISMATCH:
		return "gcvSTATUS_NAME_MISMATCH";
	case gcvSTATUS_INVALID_INDEX:
		return "gcvSTATUS_INVALID_INDEX";
	default:
		return "nil";
	}
}
