/*
 *
 * dmprofil.c
 *
 * Profile objects
 *
 */

#include "dmp.h"

// Profile object
typedef struct _POB {
    LIST_ENTRY le;
    ULONG ulNext;
    int iulInterval;
    PDMPROFILE_HANDLER pfn;
} POB, *PPOB;

void DeletePob(PVOID);
extern KEVENT kevtNull;
OBJECT_TYPE obtPob = { DmAllocatePoolWithTag, DmFreePool, NULL, DeletePob, NULL, &kevtNull, 'opmd' };
LIST_ENTRY lePobHead = { &lePobHead, &lePobHead };
BOOL g_fResetInterval;
ULONG ulNextProfInt;
ULONG ulCurProfBase;
ULONG rgulIntervalTable[] =
{
        1221,
        2441,
        4883,
        9766,
       19531,
       39063,
       78125,
      156250,
      312500,
      625000,
     1250000,
     2500000,
     5000000,
     5000000 | 0x80000000
};

void QueuePob(PPOB ppob)
{
    ULONG ulNext;
    PLIST_ENTRY ple;
    PPOB ppobBefore;
    BOOL fStart;

    /* Dequeue if already on the list */
    _asm cli
    if(ppob->le.Flink) {
        RemoveEntryList(&ppob->le);
    }
    _asm sti

    /* Start the profile interrupt or reset the profile interval if
     * necessary */
    ulNext = 1 << ppob->iulInterval;
    fStart = ulCurProfBase == 0;
    if(fStart || ulNext < ulCurProfBase) {
        g_dmi.HalProfileIntervalRoutine(rgulIntervalTable[ppob->iulInterval]);
        ulCurProfBase = ulNext;
        if(fStart)
            g_dmi.HalStartProfileRoutine(0);
    }

    /* Now set the time to fire and queue */
    _asm cli
    ppob->ulNext = ulNextProfInt + ulNext;
    ple = lePobHead.Flink;
    while(ple != &lePobHead) {
        ppobBefore = CONTAINING_RECORD(ple, POB, le);
        if((int)(ppob->ulNext - ppobBefore->ulNext) < 0)
            /* Here's where we insert */
            break;
        ple = ple->Flink;
    }
    /* ple now points to the following entry or to the list head if we go at
     * the end */
    ppob->le.Flink = ple;
    ppob->le.Blink = ple->Blink;
    ple->Blink->Flink = &ppob->le;
    ple->Blink = &ppob->le;
    _asm sti
}

void DeletePob(PVOID pv)
{
    PPOB ppob = (PPOB)pv;
    KIRQL irqlSav = KeRaiseIrqlToDpcLevel();

    /* Dequeue this guy */
    _asm {
        pushfd
        cli
    }
    RemoveEntryList(&ppob->le);
    _asm popfd

    /* If the list is now empty, end the interrupt */
    if(lePobHead.Flink == &lePobHead) {
        g_dmi.HalStopProfileRoutine(0);
        ulCurProfBase = 0;
    } else if(1 << ppob->iulInterval == ulCurProfBase) {
        /* May need to change the interval, but we need to wait until the next
         * scheduled interrupt is sent so we keep the interrupt chain timed
         * correctly */
        PLIST_ENTRY ple;
        _asm {
            pushfd
            cli
        }
        g_fResetInterval = 32;
        for(ple = lePobHead.Flink; ple != &lePobHead; ple = ple->Flink) {
            ppob = CONTAINING_RECORD(ple, POB, le);
            if(ppob->iulInterval < g_fResetInterval)
                g_fResetInterval = ppob->iulInterval + 1;
        }
        _asm popfd
    }
    KeLowerIrql(irqlSav);
}

HRESULT DmStartProfile(PHANDLE ph, ULONG ulInterval, PDMPROFILE_HANDLER pfn)
{
    PPOB ppob;
    NTSTATUS st;
    int iul;

    st = ObCreateObject(&obtPob, NULL, sizeof(POB), &ppob);
    if(!NT_SUCCESS(st))
        return HrFromStatus(st, E_FAIL);
    RtlZeroMemory(ppob, sizeof *ppob);
    
    /* Make sure we can get a handle for this guy */
    st = ObOpenObjectByPointer(ppob, &obtPob, ph);
    if(!NT_SUCCESS(st)) {
        ObDereferenceObject(ppob);
        return HrFromStatus(st, E_FAIL);
    }
    
    /* Find an interval to fire on */
    ulInterval &= 0x7fffffff;
    for(iul = 0; (int)rgulIntervalTable[iul] >= 0; ++iul) {
        if(ulInterval <= rgulIntervalTable[iul])
            break;
    }
    if(iul) {
        /* See whether greater or smaller interval is closest */
        if(rgulIntervalTable[iul] - ulInterval >
                ulInterval - rgulIntervalTable[iul - 1])
            --iul;
    }
    if((int)rgulIntervalTable[iul] < 0)
        --iul;
    ppob->iulInterval = iul;
    ppob->pfn = pfn;

    /* Queue the object and return */
    ObDereferenceObject(ppob);
    QueuePob(ppob);
    return XBDM_NOERR;
}

HRESULT DmStopProfile(HANDLE h)
{
    PPOB ppob;
    NTSTATUS st;

    /* If we're at raised irql (like inside the ISR), this isn't going to
     * work */
    if(KeGetCurrentIrql() > DISPATCH_LEVEL)
        return E_FAIL;
    st = NtClose(h);
    return NT_SUCCESS(st) ? XBDM_NOERR : HrFromStatus(st, E_FAIL);
}

void ProfInt(PKTRAP_FRAME ptf)
{
	DMN_PROFINT dmpi;
    PLIST_ENTRY ple;
    BOOL fReset;

	dmpi.SegCs = (USHORT)ptf->SegCs;
    dmpi.EFlags = ptf->EFlags;
	dmpi.Eip = ptf->Eip;
    
    /* Walk the list of installed interrupt handlers and call anybody who's
     * due */
    ple = lePobHead.Flink;
    while(ple != &lePobHead) {
        PPOB ppob = CONTAINING_RECORD(ple, POB, le);
        if((int)(ppob->ulNext - ulNextProfInt) > 0)
            /* Nobody else due */
            break;
        ple = ple->Flink;
        if(ppob->ulNext == ulNextProfInt) {
            if(g_fResetInterval) {
                g_dmi.HalProfileIntervalRoutine(rgulIntervalTable[g_fResetInterval -
                    1]);
                ulCurProfBase = 1 << (g_fResetInterval - 1);
                g_fResetInterval = 0;
            }
            ppob->pfn(&dmpi);
            QueuePob(ppob);
        }
    }

    /* Update our interrupt time */
    ulNextProfInt += ulCurProfBase;
}
