/*****************************************************************************/
/* Copyright (c) 2009 NXP B.V.                                               */
/*                                                                           */
/* This library is free software; you can redistribute it and/or             */
/* modify it under the terms of the GNU Lesser General Public                */
/* License as published by the Free Software Foundation, using               */
/* version 2.1 of the License.                                               */
/*                                                                           */
/* This library 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          */
/* Lesser General Public License for more details.                           */
/*                                                                           */
/* You should have received a copy of the GNU Lesser General Public          */
/* License along with this library; if not, write to the Free Software       */
/* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA   */
/*                                                                           */
/*****************************************************************************/
/**
    @file   phStbDbg_Linux.c
    @brief  Debug/Tracing macros for Linux userspace mode.

@b  Component:  phStbDbg

*//*

Rev Date        Author      Comments
--------------------------------------------------------------------------------
  1 20050628    batelaan    Original
  2 20050628    batelaan    Crosscheck fixes.
  3 20050711    batelaan    Fix tmDbg_Print to check flags; improve output.
  4 20050728    batelaan    Use shared memory for debug units.
  5 20050824    batelaan    Increase resilience.
  6 20050824    batelaan    Crosscheck corrections.
  7 20051028    batelaan    Don't crash if unit is null in tmDbg_Print()
  8 20060815    batelaan    Use LOG_NOTICE instead of LOG_DEBUG so messages
                            are more likely to appear via syslog.
  9 20070525    steel       fix so we can build in retail mode
 10 20071211    batelaan    DBG_ROOT is now a function.
 11 20080225    batelaan    tmDbg_Init: check gStbDbg_Initialised.
 12 20080227    batelaan    Update copyright header.
 13 20080408    steel       add copy_from_user for virtual addresses
 15 20080829    batelaan    Remove compilation warning
 16 20081106    batelaan    Add key handling functionality.
 17 20090120    batelaan    Update file copyright/license text
--------------------------------------------------------------------------------
    For consistency and standardisation retain the Section Separators.
*/

/***********************************************
* INCLUDE FILES                                *
************************************************/

#include <assert.h>
#include <string.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <sys/ioctl.h>
#include <sys/mman.h>
#include <sys/syslog.h>
#include <stdarg.h>
#include <errno.h>
#include <tmNxCompId.h>
#include <phStbDbg.h>
#include <phStbDbg_Tree.h>
#include <kmodule/phStbDbg_Internal.h>

/***********************************************
* LOCAL MACROS                                 *
* recommendation only: <MODULE><_WORD>+        *
************************************************/

/** Maximum length of a DBG_ASSERT2 message.
    See also: tmDbgConfig.h:37:#define DBG_MAX_MSG_SIZE        512 */
#define PH_STBDBG_MAX_MSG_SIZE 512U

#if defined(UNITTEST)
  #define TRACE(x...) do {fprintf(stderr, x);} while (0)
#else
  #define TRACE(x...) do {} while (0)
#endif

#define PH_STBDBG_DEVPATH "/dev/" PH_STBDBG_DEVNAME

/******************************************************************
* LOCAL TYPEDEFS                                                  *
* recommendation only:                     [p]phStbDbg_<Word>+_t  *
*******************************************************************/

/******************************************************************
* STATIC FUNCTION PROTOTYPES                  <Module>_<Word>+    *
*******************************************************************/

/******************************************************************
* STATIC DATA                  g[k|p|kp|pk|kpk]<Module>_<Word>+   *
*******************************************************************/

#if TMDBG_PRINTS_ARE_ENABLED || TMDBG_ASSERTS_ARE_ENABLED
static Bool gStbDbg_Initialised = 0;
static int gStbDbg_Fd = -1;
static int gStbDbg_Klog_Fd = -1;
#endif

/******************************************************************
* EXPORTED DATA                  g[k|p|kp|pk|kpk]phStbDbg_<Word>+ *
*******************************************************************/

#if TMDBG_PRINTS_ARE_ENABLED || TMDBG_ASSERTS_ARE_ENABLED
static Char *gphStbDbg_SyslogIdent = "STBDBG";
#endif

#if TMDBG_PRINTS_ARE_ENABLED

static tmDbgUnit_t gpStbDbg_RootUnit = {DBG_WARNING, ""};
ptmDbgUnit_t gphStbDbg_DbgUnit = &gpStbDbg_RootUnit;

ptmDbgUnit_t gpphStbDbg_Entries = NULL;

#endif

/*******************************************************************************
* FUNCTION IMPLEMENTATION  <Module>[_<Word>+] for static functions             *
*                          tm[<layer>]<Module>[_<Word>+] for exported functions*
********************************************************************************/

ptmDbgUnit_t phStbDbg_DbgUnit(void)
{
    return gphStbDbg_DbgUnit;
}

/******************************************************************************/
#if TMDBG_PRINTS_ARE_ENABLED || TMDBG_ASSERTS_ARE_ENABLED
Void tmDbg_Init(Void)
{
    TRACE(">>tmDbg_Init\n");
    if (!gStbDbg_Initialised)
    {
        gStbDbg_Initialised = 1;
        openlog(gphStbDbg_SyslogIdent, LOG_CONS|LOG_PID, LOG_USER);
        gStbDbg_Fd = open(PH_STBDBG_DEVPATH, O_RDWR);
        if (gStbDbg_Fd < 0)
        {
            fprintf(stderr, "tmDbg_Init: open \"" PH_STBDBG_DEVPATH "\"=> error %s\n",
                   strerror(errno));
        }
        else
        {
            gpphStbDbg_Entries = mmap(0, PH_STBDBG_DEVLEN, PROT_READ|PROT_WRITE, MAP_SHARED,
                                      gStbDbg_Fd, 0);
            if (gpphStbDbg_Entries == NULL)
            {
                fprintf(stderr, "tmDbg_Init: mmap => error %s\n",
                       strerror(errno));
            }
            DBG_REGISTER_MODULE(gphStbDbg_DbgUnit, "DBG_ROOT");
        }

		gStbDbg_Klog_Fd = open("/dev/kmsg", O_WRONLY);
		if (gStbDbg_Klog_Fd < 0)
		{
			fprintf(stderr, "TmDbg_Init: open \"/dev/kmsg\" failed: %m\n");
		}
    }
    TRACE("<<tmDbg_Init\n");
}
#endif // TMDBG_PRINTS_ARE_ENABLED

/******************************************************************************/
#if TMDBG_PRINTS_ARE_ENABLED
Void
tmDbg_RegisterModule(
    ptmDbgUnit_t          * ppUnit,     // O: The registered debug unit will be
                                        //    stored here.
    const Char*             pUnitName   // I: A pointer to the name or GUID. 
    )
{
    int st;
    phStbDbg_RegisterModule_t reg;

    if (!gStbDbg_Initialised)
    {
        tmDbg_Init();
    }

    reg.name = (char*)pUnitName;
    reg.length = strlen(pUnitName);
    if (reg.length >= PHSTBDBG_MAX_MODULE_NAME_LENGTH)
    {
        reg.length = PHSTBDBG_MAX_MODULE_NAME_LENGTH - 1;
    }

    st = ioctl(gStbDbg_Fd, PH_STBDBG_IOCTL_REGISTER_MODULE, &reg);
    /*TRACE("tmDbg_RegisterModule: ioctl PH_STBDBG_IOCTL_REGISTER_MODULE \"%s\" => %d\n",
          pUnitName, st);*/
    if (st >= 0)
    {
        *ppUnit = (ptmDbgUnit_t)(((pChar)gpphStbDbg_Entries) + st);
        /*TRACE("tmDbg_RegisterModule: \"%s\" => after adding base: (addr %p)\n",
              pUnitName, *ppUnit);*/
        TRACE("tmDbg_RegisterModule: \"%s\" flags=0x%x name=\"%s\"\n",
              pUnitName, (*ppUnit)->flgs, (*ppUnit)->name);
    }
    else
    {
        *ppUnit = NULL;
    }
}
#endif

/******************************************************************************/
#if TMDBG_PRINTS_ARE_ENABLED
Void tmDbg_Print(ptmDbgUnit_t uid, UInt32 flags, Char *fmt, ...)
{
    va_list args;
    char *name = (uid == NULL) ? "(NULL-DbgUnit)" : uid->name;
    /*TRACE(">>tmDbg_Print(\"%s\", 0x%x, \"%s\"...)\n", name, flags, fmt);*/
    if (DBG_PRINT_CHECK(uid, flags))
    {
		const char *priority;
        Char buf[PH_STBDBG_MAX_MSG_SIZE];
        int len;
        Char *type;
        va_start(args, fmt);
        if (!gStbDbg_Initialised)
        {
            tmDbg_Init();
        }
        if (flags & DBG_WARNING)
        {
            priority = "<4>"; // KERN_WARNING
        }
        else
        {
            priority = "<6>"; // KERN_INFO
        }
        /* Add the text in front of the message, but only if one of the predefined
         * flags is enabled (and any number of custom flags). DBG_CUSTOM_FLAG - 1 is
         * exactly "all predefined flags". The switch will only find exact matches,
         * which is that only the flag is defined. */
        switch (flags & (DBG_CUSTOM_START - 1))
        {
            case DBG_STATE:
                type = "State Change ";
                break;
            case DBG_EVENTIN:
                type = "Event in: ";
                break;
            case DBG_EVENTOUT:
                type = "Event out: ";
                break;
            case DBG_INTERFACE_ENTER:
                type = "Interface enter: ";
                break;
            case DBG_INTERFACE_LEAVE:
                type = "Interface leave: ";
                break;
            case DBG_INTERNAL_ENTER:
                type = "Internal enter: ";
                break;
            case DBG_INTERNAL_LEAVE:
                type = "Internal leave: ";
                break;
            case DBG_EXTERNAL_CALL:
                type = "External call: ";
                break;
            case DBG_EXTERNAL_RETURN:
                type = "External return: ";
                break;
            case DBG_WARNING:
                type = "Warning: ";
                    break;
            default:
                type = "";
        }

		len = snprintf(buf, (size_t)(PH_STBDBG_MAX_MSG_SIZE - 1), "%s%s %s", priority, name, type);
		if (len > PH_STBDBG_MAX_MSG_SIZE - 2)
			len = PH_STBDBG_MAX_MSG_SIZE - 2;
        len += vsnprintf(buf + len, (size_t)(PH_STBDBG_MAX_MSG_SIZE - len - 1), fmt, args);
		if (len > PH_STBDBG_MAX_MSG_SIZE - 2)
			len = PH_STBDBG_MAX_MSG_SIZE - 2;

        if (buf[len - 1] != '\n')
        {
            buf[len++] = '\n';
            buf[len] = '\0';
        }

		if (gStbDbg_Klog_Fd >= 0)
			write(gStbDbg_Klog_Fd, buf, len);
    }
    /*TRACE("<<tmDbg_Print(\"%s\", 0x%x, \"%s\"...)\n", name, flags, fmt);*/
}
#endif


/******************************************************************************/
#if TMDBG_ASSERTS_ARE_ENABLED
Char *tmDbg_StrAssertFormat(Char *fmt, ...)
{
    static char msg[PH_STBDBG_MAX_MSG_SIZE];
    va_list args;
    TRACE(">>tmDbg_StrAssertFormat(\"%s\"...)\n", fmt);
    va_start(args, fmt);
    vsnprintf(msg, PH_STBDBG_MAX_MSG_SIZE, fmt, args);
    /* Really try hard to ensure everything gets printed. */
    fprintf(stderr, "\ntmDbg_StrAssertFormat returns: %s\n", msg);
    TRACE("<<tmDbg_StrAssertFormat(\"%s\"...) => %s\n", fmt, msg);
    return msg;
}

#endif

/******************************************************************************/
#if TMDBG_ASSERTS_ARE_ENABLED
Void tmDbg_AssertError(
    const Char *assertion,
    const Char *file,
    UInt line,
    const Char *msg)
{
    TRACE(">>tmDbg_AssertError(\"%s\", \"%s\", %u, \"%s\"\n",
          assertion, file, line, msg);
    if (!gStbDbg_Initialised)
    {
        tmDbg_Init();
    }
    if (msg == NULL)
    {
        msg = "(no message)";
    }

	if (gStbDbg_Klog_Fd >= 0)
	{
		char buf[PH_STBDBG_MAX_MSG_SIZE];
		int len;

		len = snprintf(buf, sizeof(buf) - 1, "<3>Assertion! %s %s:%u\n", assertion, file, line);
		if (len > sizeof(buf) - 2)
			len = sizeof(buf) - 2;
		if (buf[len - 1] != '\n')
		{
			buf[len++] = '\n';
			buf[len] = '\0';
		}
		write(gStbDbg_Klog_Fd, buf, len);

		len = snprintf(buf, sizeof(buf) - 1, "<3>Assertion msg: %s\n", msg);
		if (len > sizeof(buf) - 2)
			len = sizeof(buf) - 2;
		if (buf[len - 1] != '\n')
		{
			buf[len++] = '\n';
			buf[len] = '\0';
		}
		write(gStbDbg_Klog_Fd, buf, len);
	}
	else
	{
		/* Really try hard to ensure everything gets printed. */
		syslog(LOG_CRIT, "Assertion! %s: %s:%u\n",
			   assertion, file, line);
		syslog(LOG_CRIT, "Assertion msg: %s\n",
			   msg);
	}
	fprintf(stderr, "\nAssertion! %s: %s:%u: %s\n",
			assertion, file, line, msg);
	fflush(NULL);
#ifndef NDEBUG
    closelog();
#ifdef __UCLIBC__
    __assert(assertion, file, line, msg);
#else
    __assert_fail(assertion, file, line, msg);
#endif
#endif
    TRACE("<<tmDbg_AssertError\n");
}

#endif //TMDBG_ASSERTS_ARE_ENABLED

/******************************************************************************/
tmErrorCode_t
tmDbg_KeySet(
    const char *key,
    const char *value)
{
    int st;
    phStbDbg_KeyAccess_t data;
    data.key = (char*)key;
    data.value = (char*)value;
    data.err = TM_ERR_NO_RESOURCES;

    if (!gStbDbg_Initialised)
    {
        tmDbg_Init();
    }

    st = ioctl(gStbDbg_Fd, PH_STBDBG_IOCTL_KEY_SET, &data);
    if (st != 0)
    {
        perror("DBG_KEY_SET");
    }
    switch (data.err)
    {
        case TM_OK:
            break;
        case PH_STBDBG_TREE_ERR_NODE_LEAF:
            fprintf(stderr, "tmDbg_KeySet: \"%s\" => parent node has a value\n", key);
            break;
        case PH_STBDBG_TREE_ERR_NODE_NOT_LEAF:
            fprintf(stderr, "tmDbg_KeySet: \"%s\" => node is non-leaf\n", key);
            break;
        default:
            fprintf(stderr, "tmDbg_KeySet: \"%s\" => err 0x%x\n", key, data.err);
    }
    return data.err;
}

/******************************************************************************/
tmErrorCode_t
tmDbg_KeyGet(
    const char *key,
    char *valueStorage)
{
    int st;
    phStbDbg_KeyAccess_t data;
    data.key = (char*)key;
    data.value = valueStorage;
    data.err = TM_ERR_NO_RESOURCES;

    if (!gStbDbg_Initialised)
    {
        tmDbg_Init();
    }

    st = ioctl(gStbDbg_Fd, PH_STBDBG_IOCTL_KEY_GET, &data);
    if (st != 0)
    {
        perror("DBG_KEY_GET");
    }
    return data.err;
}

/******************************************************************************/
tmErrorCode_t
tmDbg_KeyNext(
    char *keyStorage,
    char *valueStorage)
{
    int st;
    phStbDbg_KeyAccess_t data;
    data.key = (char*)keyStorage;
    data.value = (char*)valueStorage;
    data.err = TM_ERR_NO_RESOURCES;

    if (!gStbDbg_Initialised)
    {
        tmDbg_Init();
    }

    st = ioctl(gStbDbg_Fd, PH_STBDBG_IOCTL_KEY_NEXT, &data);
    if (st != 0)
    {
        perror("DBG_KEY_NEXT");
    }
    return data.err;
}

/******************************************************************************/
