/*****************************************************************************/
/* Copyright (c) 2008 NXP B.V.                                               */
/*                                                                           */
/* 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, using version 2 of the License.             */
/*                                                                           */
/* 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307       */
/* USA.                                                                      */
/*                                                                           */
/*****************************************************************************/
/**
    @file   phStbDbg_Module.c
    @brief  Linux kernel module for phStbDbg component (tracing).

@b  Component:  phStbDbg.

*//*

Rev Date        Author      Comments
--------------------------------------------------------------------------------
  1 20050701    batelaan    Initial
  2 20050705    batelaan    Crosscheck fixes
  3 20050711    batelaan    Fix tmDbg_Print to check flags
  4 20050728    batelaan    Use shared memory for debug units.
  5 20050805    batelaan    Crosscheck fixes
  6 20050809    batelaan    Use separate UnitInfo struct to increase resilience against roque programs.
  7 20050824    batelaan    Crosscheck corrections. Ensure we don't overflow memory.
  8 20061023    batelaan    Add EXPORT_SYMBOL(gphStbDbg_DbgUnit)
  9 20061025    batelaan    Remove EXPORT_SYMBOL(gphStbDbg_DbgUnit), as it should be done by macro DBG_UNIT
 10 20061103    barnish     DBG_UNIT now requires a semicolon
 11 20061109    batelaan    Fix mmap.
 12 20070112    steel       Update for 2.6.19 kernel
 13 20071211    batelaan    Added phStbDbg_DbgUnit()
 14 20080215    batelaan    Update file copyright/license text
 15 20080408    steel       Add copy_from_user for virtual addresses
 16 20080409    batelaan    Use copy_from_user also for structure access
 17 20080421    amccurdy    Use GFP_ATOMIC for kmalloc calls with spinlocks held
 18 20081106    batelaan    Add key handling functionality.
 19 20090121    batelaan    Update author/license.
--------------------------------------------------------------------------------
    For consistency and standardisation retain the Section Separators.
*/

/* needed for versioned kernels (i.e. most kernels in current use) */
#include <linux/module.h>
#include <linux/kernel.h>       /* printk() */
#include <linux/fs.h>
#include <linux/mm.h>
#include <linux/cdev.h>
#include <linux/device.h>
#include <linux/spinlock.h>
#include <linux/string.h>

#include <tmNxCompId.h>
#include <phStbCommon.h>
#include <phStbDbg.h>
#include <phStbDbg_Internal.h>
#include <phStbDbg_Tree.h>

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

#define PH_STBDBG_MODULE_EXIT_IMPL 0

#define THIS_MODULE_DESCRIPTION "phStbDbg driver"
MODULE_LICENSE("GPL v2");
MODULE_DESCRIPTION(THIS_MODULE_DESCRIPTION);
MODULE_AUTHOR("NXP B.V.");

/* The number of devices we expose */
#define NR_DEVS 1

/** 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(...) do {printk(KERN_DEBUG __VA_ARGS__);} while (0)
#else
  #define TRACE(...) do {} while (0)
#endif

/** Print format for an unit:
    Nr: 12:<name> flags: <0x12345678>
*/
#define FORMAT "%02u) 0x%08x %s\n"

/* Size of print format, without the variable name length.
   this is:
   - sizeof(FORMAT)
   - minus the 3 %specs (4 + 4 + 2)
   - plus size of the integers printouts. */
#define FORMAT_SIZE (sizeof(FORMAT) - 10 + 2 + 8)

/******************************************************************
* FUNCTION PROTOTYPES                    [p]ph<Module>_<Word>+    *
*******************************************************************/

static int phStbDbg_open(struct inode *inode, struct file *file);
static ssize_t phStbDbg_read(struct file *file, char __user *buf, size_t count, loff_t *pos);
static ssize_t phStbDbg_release(struct inode *inode, struct file *file);
static int phStbDbg_ioctl(struct inode *inode, struct file *file, unsigned int cmd, unsigned long arg);
static ssize_t phStbDbg_mmap(struct file * file, struct vm_area_struct * vma);

/******************************************************************
* LOCAL TYPEDEFS                                                  *
* recommendation only:                   [p]ph<Module>_<Word>+_t  *
*******************************************************************/

/** Holds info (name and address of public tmDbgUnit struct) for a debug unit. */
typedef struct UnitInfo {
    struct UnitInfo *next;
    ptmDbgUnit_t pUnit;
    Char name[1];
} UnitInfo_t;
typedef UnitInfo_t *pUnitInfo_t;

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

#if TMDBG_PRINTS_ARE_ENABLED
DBG_UNIT(gphStbDbg_DbgUnit);
#endif

/** Public debug unit structures. These are memory-mapped into user application space. */
ptmDbgUnit_t gpphStbDbg_Entries;
static struct page *gpphStbDbg_SharedPage;

/*******************************************************************
* LOCAL DATA                                                       *
********************************************************************/

static dev_t gDev; /* The start of the reserved device number region */
static struct cdev gCdev;
static struct class *gSysClass; /* The GPIO class under /sys/class */
static unsigned int gMajor; /* Major number of our device, obtained from gDev and held for convenience */
static unsigned nEntries;
static size_t gTotalNamesSize;

static pUnitInfo_t gppStbDbg_UnitInfos;
static pUnitInfo_t gppStbDbg_UnitInfoLast;

static tmDbgUnit_t *gppStbDbg_EntriesLast;
static DEFINE_SPINLOCK(gLock);
static DECLARE_MUTEX(gKeyTreeLock);

/* The file operations for the GPIO character devices */
static struct file_operations phStbDbg_fops =
{
    .owner      = THIS_MODULE,
    .open       = phStbDbg_open,
    .release    = phStbDbg_release,
    .read       = phStbDbg_read,
    .mmap       = phStbDbg_mmap,
    .ioctl      = phStbDbg_ioctl
};

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

/******************************************************************************/
void *phStbDbg_Allocator(size_t size)
{
    return kmalloc(size, GFP_KERNEL);
}

/******************************************************************************/
void phStbDbg_Deallocator(void *addr)
{
    kfree(addr);
}

/******************************************************************************/
void phStbDbg_KeyTreeLockInit(void)
{
    /* Mutex is declared and initialised statically. */
}

/******************************************************************************/
void phStbDbg_KeyTreeLockEnter(void)
{
    down(&gKeyTreeLock);
}

/******************************************************************************/
void phStbDbg_KeyTreeLockExit(void)
{
    up(&gKeyTreeLock);
}

/******************************************************************************/
ptmDbgUnit_t phStbDbg_DbgUnit(void)
{
    return gphStbDbg_DbgUnit;
}

/******************************************************************************/
static Void StbDbg_Lock(Void)
{
    spin_lock(&gLock);
}

/******************************************************************************/
static Void StbDbg_Unlock(Void)
{
    spin_unlock(&gLock);
}

/******************************************************************************/
static UnitInfo_t *StbDbg_New(const char *name)
{
    UnitInfo_t *pUnitInfo;
    size_t nameLen = strlen(name);

    pUnitInfo = kmalloc(sizeof(UnitInfo_t) + nameLen, GFP_ATOMIC);
    if (pUnitInfo == NULL)
    {
        return NULL;
    }

    if (gppStbDbg_EntriesLast == NULL)
    {
        pUnitInfo->pUnit = gpphStbDbg_Entries;
    }
    else
    {
        /* Calculate offset from gppStbDbg_EntriesLast: */
        pUnitInfo->pUnit = (tmDbgUnit_t*)(((char*)gppStbDbg_EntriesLast) +
                                          sizeof(tmDbgUnit_t) +
                                          strlen(gppStbDbg_EntriesLast->name));
        pUnitInfo->pUnit = (tmDbgUnit_t*) ALIGN((UInt32)pUnitInfo->pUnit, sizeof(tmDbgUnit_t*));
        TRACE("StbDbg_New (%s) pUnit=%p\n", name, pUnitInfo->pUnit);

        /* Check that there is space in the shared memory page (a singe page): */
        if (((char*)pUnitInfo->pUnit) + sizeof(tmDbgUnit_t) + nameLen - ((char*)gpphStbDbg_Entries) >= PAGE_SIZE)
        {
            kfree(pUnitInfo);
            return NULL;
        }
    }
    gppStbDbg_EntriesLast = pUnitInfo->pUnit;
    strcpy(pUnitInfo->pUnit->name, name);
    pUnitInfo->pUnit->flgs = 0;

    /* Put into linked list: */
    if (gppStbDbg_UnitInfos == NULL)
    {
        gppStbDbg_UnitInfos = pUnitInfo;
    }
    else
    {
        gppStbDbg_UnitInfoLast->next = pUnitInfo;
    }
    gppStbDbg_UnitInfoLast = pUnitInfo;
    pUnitInfo->next = NULL;

    strcpy(pUnitInfo->name, name);

    gTotalNamesSize += nameLen;
    nEntries++;
    return pUnitInfo;
}

/******************************************************************************/
static UnitInfo_t *StbDbg_Lookup(const char *name)
{
    UnitInfo_t *p = NULL;
    StbDbg_Lock();
    for (p = gppStbDbg_UnitInfos; p != NULL; p = p->next)
    {
        /*TRACE("StbDbg_Lookup (for: %s) looking at: (%s)(pUnit=%p: %s)\n",
              name, p->name, p->pUnit, p->pUnit->name);*/
        if (strcmp(p->name, name) == 0)
        {
            break;
        }
    }
    if (p == NULL)
    {
        p = StbDbg_New(name);
    }
    StbDbg_Unlock();
    return p;
}

/******************************************************************************/
/** Open the device.
 */
static int phStbDbg_open(struct inode *inode, struct file *file)
{
    file->private_data = NULL;

    return 0;
}

/******************************************************************************/
static ssize_t phStbDbg_release(struct inode *inode, struct file *file)
{
    if (file->private_data != NULL)
    {
        kfree(file->private_data);
    }
    return 0;
}

/******************************************************************************/
/* Return the debug flag setting. */
static char * StbDbg_GetInfo(Void)
{
    UnitInfo_t *p;
    size_t infoLen;
    size_t used = 0;
    unsigned n = 0;
    char *info = NULL;

    StbDbg_Lock();

    infoLen = (nEntries * FORMAT_SIZE) + gTotalNamesSize;
    info = kmalloc(infoLen, GFP_ATOMIC);
    if (info == NULL)
    {
        printk(KERN_ERR "phStbDbg: kmalloc %u failed\n", infoLen);
        goto RETURN;
    }

    for (p = gppStbDbg_UnitInfos; p != NULL; p = p->next)
    {
        size_t printed;
        TRACE("phStbDbg: entry %u (%s)(%s) flags 0x%08x\n",
              n, p->name, p->pUnit->name, p->pUnit->flgs);
        printed = snprintf(
            info + used, infoLen - used,
            FORMAT,
            n, p->pUnit->flgs, p->name);
        used += printed;
        n++;
    }

  RETURN:
    StbDbg_Unlock();
    return info;
}

/******************************************************************************/
/* Read debug flag settings. */
static ssize_t phStbDbg_read(struct file *file, char __user *buf, size_t count, loff_t *pos)
{
    ssize_t retval = 0;
    char *info;
    ssize_t len;

    if (*pos == 0)
    {
        /* Beginning of the read, so we setup the string in private_data
           to contain the debug flag setting. */
        file->private_data = StbDbg_GetInfo();
    }
    info = (char*)file->private_data;
    if (info == NULL)
    {
        return -1;
    }
    len = strlen(info);

    if (*pos < len)
    {
        if (count > len - *pos)
        {
            count = len - *pos;
        }

        if (copy_to_user(buf, info + *pos, count))
        {
            printk(KERN_ERR "phStbDbg: copy_to_user %d bytes offset %d failed\n",
                   (int)count, (int)*pos);
            retval = -EFAULT;
        }
        else
        {
            *pos += count;
            retval = count;
        }
    }

    return retval;
}

/******************************************************************************/
/* Memory map the device. This maps ::gpphStbDbg_Entries. */
static ssize_t phStbDbg_mmap(struct file * file, struct vm_area_struct * vma)
{
    unsigned long vsize = vma->vm_end - vma->vm_start;

    if (vsize != PH_STBDBG_DEVLEN)
    {
        printk(KERN_ERR "phStbDbg_mmap: vsize=%lu (!=%u)\n", vsize, PH_STBDBG_DEVLEN);
        return -EINVAL;
    }

    if (vma->vm_pgoff != 0)
    {
        printk(KERN_ERR "phStbDbg_mmap: vma->vm_pgoff=%lu\n", vma->vm_pgoff);
        return -EINVAL;
    }

    /* Prevent the swapper from considering these pages for swap and touching them */
    vma->vm_flags |= VM_RESERVED | VM_DONTEXPAND;

    /*!!!! Must be mapped uncached to prevent caching problems between user and kernel space !!!!!*/
    vma->vm_page_prot = pgprot_noncached( vma->vm_page_prot );

    if (remap_pfn_range(vma,
                        vma->vm_start,
                        page_to_pfn(gpphStbDbg_SharedPage),
                        vsize,
                        vma->vm_page_prot))
        return -EAGAIN;
    return 0;
}

/******************************************************************************/
static int StbDbg_IoctlRegisterModule(
    unsigned long arg)
{
    int result = 0;
    phStbDbg_RegisterModule_t reg;
    char name[PHSTBDBG_MAX_MODULE_NAME_LENGTH];
    UnitInfo_t *pUnitInfo;
    copy_from_user(&reg, (void *)arg, sizeof(phStbDbg_RegisterModule_t));
    copy_from_user(name, reg.name, reg.length + 1);
    TRACE("phStbDbg_ioctl PH_STBDBG_IOCTL_REGISTER_MODULE \"%s\"\n", name);
    pUnitInfo = StbDbg_Lookup(name);
    if (pUnitInfo != NULL)
    {
        UInt32 offset;
        /* Convert to offset from gpphStbDbg_Entries address: */
        offset = ((char*)pUnitInfo->pUnit) - ((char*)gpphStbDbg_Entries);
        result = (int)offset;
        TRACE("phStbDbg_ioctl PH_STBDBG_IOCTL_REGISTER_MODULE \"%s\" => (%s)(%s) offset %d\n",
              name, pUnitInfo->name, pUnitInfo->pUnit->name, result);
    }
    else
    {
        result = -ENOMEM;
    }
    return result;
}

/******************************************************************************/
static int StbDbg_IoctlKeySet(
    unsigned long arg)
{
    int result = 0;
    phStbDbg_KeyAccess_t data;
    char key[DBG_KEY_MAX_LEN];
    char value[DBG_KEY_MAX_LEN];
    long slen;
    (void)copy_from_user(&data, (void *)arg, sizeof(data));
    slen = strncpy_from_user(key, data.key, DBG_KEY_MAX_LEN - 1);
    if (slen < 0) {
        return slen;
    }
    key[slen] = 0;
    slen = strncpy_from_user(value, data.value, DBG_KEY_MAX_LEN - 1);
    if (slen < 0) {
        return slen;
    }
    value[slen] = 0;
    data.err = tmDbg_KeySet(key, value);
    copy_to_user((void *)arg, &data, sizeof(data));
    return result;
}

/******************************************************************************/
static int StbDbg_IoctlKeyGet(
    unsigned long arg)
{
    int result = 0;
    phStbDbg_KeyAccess_t data;
    char key[DBG_KEY_MAX_LEN];
    char value[DBG_KEY_MAX_LEN];
    tmErrorCode_t err;
    long slen;
    copy_from_user(&data, (void *)arg, sizeof(data));
    slen = strncpy_from_user(key, data.key, DBG_KEY_MAX_LEN - 1);
    if (slen < 0) {
        return slen;
    }
    key[slen] = 0;
    err = tmDbg_KeyGet(key, value);
    if (err == TM_OK)
    {
        copy_to_user((void*)data.value, value, strlen(value) + 1);
    }
    data.err = err;
    copy_to_user((void *)arg, &data, sizeof(data));
    return result;
}

/******************************************************************************/
static int StbDbg_IoctlKeyNext(
    unsigned long arg)
{
    int result = 0;
    phStbDbg_KeyAccess_t data;
    char key[DBG_KEY_MAX_LEN];
    char value[DBG_KEY_MAX_LEN];
    tmErrorCode_t err;
    long slen;
    copy_from_user(&data, (void *)arg, sizeof(data));
    slen = strncpy_from_user(key, data.key, DBG_KEY_MAX_LEN - 1);
    if (slen < 0) {
        return slen;
    }
    key[slen] = 0;
    err = tmDbg_KeyNext(key, value);
    if (err == TM_OK)
    {
        copy_to_user((void*)data.key, key, strlen(key) + 1);
        copy_to_user((void*)data.value, value, strlen(value) + 1);
    }
    data.err = err;
    copy_to_user((void *)arg, &data, sizeof(data));
    return result;
}

/******************************************************************************/
/* Handle all ioctls supported by phStbDbg */

static int phStbDbg_ioctl(struct inode *inode, struct file *file, unsigned int cmd, unsigned long arg)
{
    int result;

    switch (cmd)
    {
        case PH_STBDBG_IOCTL_REGISTER_MODULE:
            result = StbDbg_IoctlRegisterModule(arg);
            break;

        case PH_STBDBG_IOCTL_KEY_SET:
            result = StbDbg_IoctlKeySet(arg);
            break;

        case PH_STBDBG_IOCTL_KEY_GET:
            result = StbDbg_IoctlKeyGet(arg);
            break;

        case PH_STBDBG_IOCTL_KEY_NEXT:
            result = StbDbg_IoctlKeyNext(arg);
            break;

        default:
            result = -ENOTTY;
            break;
    }

    return result;
}

/******************************************************************************/
/** Module Creation function.
    - creates the device.

    @retval int Module Initialised successfully.
*/
static int __init phStbDbg_ModuleInit(void)
{
    int result = -1;

    /** To track module initialisation progress, so we can undo correctly on error. */
    enum {
        cdevAdded = 1<<0,
        classSimpleDeviceAdded = 1<<1
    } initialised = 0;

    spin_lock_init(&gLock);
    gpphStbDbg_Entries = (tmDbgUnit_t*)get_zeroed_page(GFP_KERNEL);
    if (gpphStbDbg_Entries == NULL)
    {
        printk(KERN_ERR "phStbDbg: no mem\n");
        goto ERROR_RETURN;
    }
    gpphStbDbg_SharedPage = virt_to_page(gpphStbDbg_Entries);

    /* Set the page as reserved so that it can be re-mapped using remap_pfn_range */
    SetPageReserved(gpphStbDbg_SharedPage);

    /* Make sure we use the UNCACHED address in the driver! */
    gpphStbDbg_Entries = (tmDbgUnit_t*) UNCAC_ADDR((unsigned long)gpphStbDbg_Entries);

    /* Allocate a region of minor numbers (1) under a major number */
    result = alloc_chrdev_region(&gDev, 0, NR_DEVS, PH_STBDBG_DEVNAME);
    if (result < 0)
    {
        printk(KERN_ERR "phStbDbg: alloc_chrdev_region => %d\n", result);
        goto ERROR_RETURN;
    }

    gMajor = MAJOR(gDev);

    /* Set up /sys entry */
    gSysClass = class_create(THIS_MODULE, PH_STBDBG_DEVNAME);
    if (gSysClass == NULL)
    {
        printk(KERN_ERR "phStbDbg: class_create => %d\n", result);
        result = -ENOMEM;
        goto ERROR_RETURN;
    }

    cdev_init(&gCdev, &phStbDbg_fops);
    gCdev.owner = THIS_MODULE;

    result = cdev_add(&gCdev, gDev, 1);
    if (result)
    {
        printk(KERN_INFO "phStbDbg: cdev_add => %d\n", result);
        goto ERROR_RETURN;
    }
    initialised |= cdevAdded;

    class_device_create(gSysClass, NULL, gDev, NULL, PH_STBDBG_DEVNAME);
    initialised |= classSimpleDeviceAdded;

    tmDbg_RegisterModule(&gphStbDbg_DbgUnit, "DBG_ROOT");
    gphStbDbg_DbgUnit->flgs = DBG_WARNING;

    phStbDbg_KeyInit();

#if (!defined(MODULE)) && defined(PH_STBDBG_CPU_NAME)
    {
        /* linux_banner is not exported, so this does not work in loadable kernel modules. */
        extern const char linux_banner[];
        tmDbg_KeySet(__stringify(PH_STBDBG_CPU_NAME) "/linux_banner", linux_banner);
    }
#endif

    printk("%s (%s-%s)\n", THIS_MODULE_DESCRIPTION, __DATE__, __TIME__);

    return 0;

  ERROR_RETURN:
    printk(KERN_ERR "%s (%s-%s) => %d\n", THIS_MODULE_DESCRIPTION, __DATE__, __TIME__, result);
    if (gpphStbDbg_Entries != NULL)
    {
        ClearPageReserved(gpphStbDbg_SharedPage);
        free_page(CAC_ADDR((unsigned long)gpphStbDbg_Entries));
    }

    if (initialised & classSimpleDeviceAdded)
    {
        class_device_destroy(gSysClass, gDev);
    }

    if (initialised & cdevAdded)
    {
        cdev_del(&gCdev);
    }
    return result;
}

module_init(phStbDbg_ModuleInit);

/******************************************************************************/
#if PH_STBDBG_MODULE_EXIT_IMPL
/** Module Exit function.
    - free memory
    - delete the device.
*/
static void __exit phStbDbg_ModuleExit(void)
{
    if (gpphStbDbg_Entries != NULL)
    {
        free_page((unsigned long)gpphStbDbg_Entries);
    }

    class_device_destroy(gSysClass, gDev);

    cdev_del(&gCdev);
}

module_exit(phStbDbg_ModuleExit);
#endif /* PH_STBDBG_MODULE_EXIT_IMPL */

/******************************************************************************/
#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.
    )
{
    UnitInfo_t *pUnitInfo = StbDbg_Lookup(pUnitName);
    *ppUnit = pUnitInfo->pUnit;
}
#endif

/******************************************************************************/
#if TMDBG_PRINTS_ARE_ENABLED
Void tmDbg_Print(ptmDbgUnit_t uid, UInt32 flags, Char *fmt, ...)
{
    va_list args;

    /*TRACE(">>tmDbg_Print(\"%s\", 0x%x, \"%s\"...)\n", uid->name, flags, fmt);*/
    if (DBG_PRINT_CHECK(uid, flags))
    {
        Char buf[PH_STBDBG_MAX_MSG_SIZE];
        size_t len;
        char *type;
        char *priority;

        va_start(args, fmt);
        if (flags & DBG_WARNING)
        {
            priority = KERN_WARNING;
        }
        else
        {
            priority = KERN_DEBUG;
        }
        /* 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, PH_STBDBG_MAX_MSG_SIZE - 1, "%s %s", uid->name, type);
		if (len > PH_STBDBG_MAX_MSG_SIZE - 2)
			len = PH_STBDBG_MAX_MSG_SIZE - 2;
        len += vsnprintf(buf + len, 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 + 1] = '\0';
        }
        printk("%s%s", priority, buf);
    }
    /*TRACE("<<tmDbg_Print(\"%s\", 0x%x, \"%s\"...)\n", uid->name, flags, fmt);*/
}

#endif


/******************************************************************************/
#if TMDBG_ASSERTS_ARE_ENABLED
Char *tmDbg_StrAssertFormat(Char *fmt, ...)
{
    static char *buf = NULL;
    va_list args;

    if (buf == NULL)
    {
        buf = kmalloc(PH_STBDBG_MAX_MSG_SIZE, GFP_KERNEL);
    }

    TRACE(">>tmDbg_StrAssertFormat(\"%s\"...)\n", fmt);
    va_start(args, fmt);
    vsnprintf(buf, PH_STBDBG_MAX_MSG_SIZE, fmt, args);
    TRACE("<<tmDbg_StrAssertFormat(\"%s\"...) => %s\n", fmt, buf);
    return buf;
}

#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 (msg == NULL)
    {
        msg = "(no message)";
    }
    printk(KERN_ERR "Assertion! %s: %s:%u: %s\n",
           assertion, file, line, msg);
    BUG();
    TRACE("<<tmDbg_AssertError\n");
}

#endif //TMDBG_ASSERTS_ARE_ENABLED
/******************************************************************************/
