static ssize_t _print_profiling_states(gckKERNEL Kernel,
                    gctUINT32 Count,
                    char *buf)
{
    gctUINT32 len   = 0;
    gctUINT32 i     = 0;
    gctUINT32 index = 0;
    gctUINT64 tick  = 0;
    gceSTATUS status = gcvSTATUS_OK;
    gctUINT32 preTick, curTick;
    gckProfNode_PTR profNode = gcvNULL;

    gcmkONERROR(gckKERNEL_QueryLastProfNode(Kernel, &index, &profNode));
    gcmkONERROR(gckOS_GetProfileTick(&tick));

    preTick = gckOS_ProfileToMS(tick);

    len += sprintf(buf+len, " [GPU%d] tick = %d\n", Kernel->core, preTick);
    len += sprintf(buf+len, " index  duration  idle_ticks  busy_ticks\n");
    len += sprintf(buf+len, "-------+-------+-----------+-----------+\n");

    for(i = 0; i < Count; i++)
    {
        gctUINT32 idx = (index + gcdPROFILE_NODES_NUM - i) % gcdPROFILE_NODES_NUM;

        curTick = profNode[idx].tick;

        len += sprintf(buf+len, "%2d(%3d):%8d%s%12d\n", i, idx,
                                preTick-curTick,
                                profNode[idx].idle ? "" : "\t    ",
                                curTick);
        preTick = curTick;
    }

    len += sprintf(buf+len, "\n");

    return len;

OnError:
    return sprintf(buf, "Failed to load stats for gpu %d\n", Kernel->core);
}
/*******************************************************************************
**  gckKERNEL_AddProcessDB
**
**  Add a record to a process database.
**
**  INPUT:
**
**      gckKERNEL Kernel
**          Pointer to a gckKERNEL object.
**
**      gctUINT32 ProcessID
**          Process ID used to identify the database.
**
**      gceDATABASE_TYPE TYPE
**          Type of the record to add.
**
**      gctPOINTER Pointer
**          Data of the record to add.
**
**      gctPHYS_ADDR Physical
**          Physical address of the record to add.
**
**      gctSIZE_T Size
**          Size of the record to add.
**
**  OUTPUT:
**
**      Nothing.
*/
gceSTATUS
gckKERNEL_AddProcessDB(
    IN gckKERNEL Kernel,
    IN gctUINT32 ProcessID,
    IN gceDATABASE_TYPE Type,
    IN gctPOINTER Pointer,
    IN gctPHYS_ADDR Physical,
    IN gctSIZE_T Size
    )
{
    gceSTATUS status;
    gcsDATABASE_PTR database;
    gcsDATABASE_RECORD_PTR record = gcvNULL;
    gcsDATABASE_COUNTERS * count;

    gcmkHEADER_ARG("Kernel=0x%x ProcessID=%d Type=%d Pointer=0x%x "
                   "Physical=0x%x Size=%lu",
                   Kernel, ProcessID, Type, Pointer, Physical, Size);

    /* Verify the arguments. */
    gcmkVERIFY_OBJECT(Kernel, gcvOBJ_KERNEL);

    /* Special case the idle record. */
    if (Type == gcvDB_IDLE)
    {
        gctUINT64 time;

        /* Get the current profile time. */
        gcmkONERROR(gckOS_GetProfileTick(&time));

        if ((ProcessID == 0) && (Kernel->db->lastIdle != 0))
        {
            /* Out of idle, adjust time it was idle. */
            Kernel->db->idleTime += time - Kernel->db->lastIdle;
            Kernel->db->lastIdle  = 0;
        }
        else if (ProcessID == 1)
        {
            /* Save current idle time. */
            Kernel->db->lastIdle = time;
        }

#if gcdDYNAMIC_SPEED
        {
            /* Test for first call. */
            if (Kernel->db->lastSlowdown == 0)
            {
                /* Save milliseconds. */
                Kernel->db->lastSlowdown     = time;
                Kernel->db->lastSlowdownIdle = Kernel->db->idleTime;
            }
            else
            {
                /* Compute ellapsed time in milliseconds. */
                gctUINT delta = gckOS_ProfileToMS(time - Kernel->db->lastSlowdown);

                /* Test for end of period. */
                if (delta >= gcdDYNAMIC_SPEED)
                {
                    /* Compute number of idle milliseconds. */
                    gctUINT idle = gckOS_ProfileToMS(
                        Kernel->db->idleTime  - Kernel->db->lastSlowdownIdle);

                    /* Broadcast to slow down the GPU. */
                    gcmkONERROR(gckOS_BroadcastCalibrateSpeed(Kernel->os,
                                                              Kernel->hardware,
                                                              idle,
                                                              delta));

                    /* Save current time. */
                    Kernel->db->lastSlowdown     = time;
                    Kernel->db->lastSlowdownIdle = Kernel->db->idleTime;
                }
            }
        }
#endif

        /* Success. */
        gcmkFOOTER_NO();
        return gcvSTATUS_OK;
    }

    /* Verify the arguments. */
    gcmkVERIFY_ARGUMENT(Pointer != gcvNULL);

    /* Find the database. */
    gcmkONERROR(gckKERNEL_FindDatabase(Kernel, ProcessID, gcvFALSE, &database));

    /* Create a new record in the database. */
    gcmkONERROR(gckKERNEL_NewRecord(Kernel, database, _GetSlot(database, Pointer), &record));

    /* Initialize the record. */
    record->kernel   = Kernel;
    record->type     = Type;
    record->data     = Pointer;
    record->physical = Physical;
    record->bytes    = Size;

    /* Get pointer to counters. */
    switch (Type)
    {
    case gcvDB_VIDEO_MEMORY:
        count = &database->vidMem;
        break;

    case gcvDB_NON_PAGED:
        count = &database->nonPaged;
        break;

    case gcvDB_CONTIGUOUS:
        count = &database->contiguous;
        break;

    case gcvDB_MAP_MEMORY:
        count = &database->mapMemory;
        break;

    case gcvDB_MAP_USER_MEMORY:
        count = &database->mapUserMemory;
        break;

    default:
        count = gcvNULL;
        break;
    }

    if (count != gcvNULL)
    {
        /* Adjust counters. */
        count->totalBytes += Size;
        count->bytes      += Size;

        if (count->bytes > count->maxBytes)
        {
            count->maxBytes = count->bytes;
        }
    }

    /* Success. */
    gcmkFOOTER_NO();
    return gcvSTATUS_OK;

OnError:
    /* Return the status. */
    gcmkFOOTER();
    return status;
}
static ssize_t gc_proc_write(
    struct file *file,
    const char *buff,
    size_t len,
    loff_t *off)
{
    char messages[256];

    if(len > 256)
        len = 256;

    if(copy_from_user(messages, buff, len))
        return -EFAULT;

    printk("\n");
    if(strncmp(messages, "dutycycle", 9) == 0)
    {
        gctINT32            option;
        gcuDATABASE_INFO    gpuIdle;
        static gctUINT64    startTime = 0;
        static gctUINT64    endTime = 0;

        gcmkVERIFY_OK(gckOS_ZeroMemory((gctPOINTER)&gpuIdle, gcmSIZEOF(gcuDATABASE_INFO)));

        sscanf(messages+9, "%d", &option);

        switch(option) {
        case 0:
            gcmkVERIFY_OK(gckOS_GetProfileTick(&startTime));
            gcmkVERIFY_OK(gckKERNEL_QueryDutyCycleDB(galDevice->kernels[gcvCORE_MAJOR], gcvTRUE, &gpuIdle));
            gpuIdle.time = 0;
            endTime = 0;
            break;
        case 1:
            gcmkVERIFY_OK(gckKERNEL_QueryDutyCycleDB(galDevice->kernels[gcvCORE_MAJOR], gcvFALSE, &gpuIdle));
            gcmkVERIFY_OK(gckOS_GetProfileTick(&endTime));
            break;
        default:
            printk(KERN_INFO "usage: echo dutycycle [0|1] > /proc/driver/gc\n");
        }

        if(startTime != 0 && endTime != 0)
        {
            gctUINT64   delta = endTime - startTime;
            gctUINT32   per   = 100 - 100 * gckOS_ProfileToMS(gpuIdle.time) / gckOS_ProfileToMS(delta);
            printk(KERN_INFO "\n  %%GPU     START       END     DELTA      IDLE\n");
            printk(KERN_INFO "%5u%%  %8u  %8u  %8u  %8u\n\n",
                per,
                gckOS_ProfileToMS(startTime),
                gckOS_ProfileToMS(endTime),
                gckOS_ProfileToMS(delta),
                gckOS_ProfileToMS(gpuIdle.time));
        }
    }
    else if(strncmp(messages, "reg", 3) == 0)
    {
        gctINT32 option;
        gctUINT32 idle, address, clockControl;

        sscanf(messages+3, "%d", &option);

        switch(option) {
        case 1:
            /* Read the current FE address. */
            gckOS_ReadRegisterEx(galDevice->os,
                                 galDevice->kernels[0]->hardware->core,
                                 0x00664,
                                 &address);
            gcmkPRINT("address: 0x%2x\n", address);
            break;
        case 2:
            /* Read idle register. */
            gckOS_ReadRegisterEx(galDevice->os,
                                 galDevice->kernels[0]->hardware->core,
                                 0x00004,
                                 &idle);
            gcmkPRINT("idle: 0x%2x\n", idle);
            break;
        case 3:
            gckOS_ReadRegisterEx(galDevice->os,
                                 gcvCORE_MAJOR,
                                 0x00000,
                                 &clockControl);
            gcmkPRINT("clockControl: 0x%2x\n", clockControl);
            break;
        default:
            printk(KERN_INFO "usage: echo reg [1|2|3] > /proc/driver/gc\n");
        }
    }
    else if(strncmp(messages, "help", 4) == 0)
    {
        printk("Supported options:\n"
                "dutycycle       measure dutycycle in a period\n"
                "reg             enable debugging for register reading\n"
                "help            show this help page\n"
                "\n");
    }
    else
    {
        gcmkPRINT("unknown echo\n");
    }
    printk("\n");

    return len;
}