/*
 * COPYRIGHT:       See COPYING in the top level directory
 * PROJECT:         ReactOS Kernel
 * FILE:            ntoskrnl/ex/profile.c
 * PURPOSE:         Support for Executive Profile Objects
 * PROGRAMMERS:     Alex Ionescu (alex@relsoft.net)
 *                  Thomas Weidenmueller
 */

/* INCLUDES *****************************************************************/

#include <ntoskrnl.h>
#define NDEBUG
#include <debug.h>

#if defined (ALLOC_PRAGMA)
#pragma alloc_text(INIT, ExpInitializeProfileImplementation)
#endif

#define TAG_PROFILE 'forP'

/* GLOBALS *******************************************************************/

POBJECT_TYPE ExProfileObjectType = NULL;
KMUTEX ExpProfileMutex;

GENERIC_MAPPING ExpProfileMapping =
{
    STANDARD_RIGHTS_READ    | PROFILE_CONTROL,
    STANDARD_RIGHTS_WRITE   | PROFILE_CONTROL,
    STANDARD_RIGHTS_EXECUTE | PROFILE_CONTROL,
    PROFILE_ALL_ACCESS
};

/* FUNCTIONS *****************************************************************/

VOID
NTAPI
ExpDeleteProfile(PVOID ObjectBody)
{
    PEPROFILE Profile;
    ULONG State;

    /* Typecast the Object */
    Profile = (PEPROFILE)ObjectBody;

    /* Check if there if the Profile was started */
    if (Profile->LockedBufferAddress)
    {
        /* Stop the Profile */
        State = KeStopProfile(Profile->ProfileObject);
        ASSERT(State != FALSE);

        /* Unmap the Locked Buffer */
        MmUnmapLockedPages(Profile->LockedBufferAddress, Profile->Mdl);
        MmUnlockPages(Profile->Mdl);
        IoFreeMdl(Profile->Mdl);
    }

    /* Check if a Process is associated and reference it */
    if (Profile->Process) ObDereferenceObject(Profile->Process);
}

VOID
INIT_FUNCTION
NTAPI
ExpInitializeProfileImplementation(VOID)
{
    OBJECT_TYPE_INITIALIZER ObjectTypeInitializer;
    UNICODE_STRING Name;
    DPRINT("Creating Profile Object Type\n");

    /* Initialize the Mutex to lock the States */
    KeInitializeMutex(&ExpProfileMutex, 64);

    /* Create the Event Pair Object Type */
    RtlZeroMemory(&ObjectTypeInitializer, sizeof(ObjectTypeInitializer));
    RtlInitUnicodeString(&Name, L"Profile");
    ObjectTypeInitializer.Length = sizeof(ObjectTypeInitializer);
    ObjectTypeInitializer.DefaultNonPagedPoolCharge = sizeof(KPROFILE);
    ObjectTypeInitializer.GenericMapping = ExpProfileMapping;
    ObjectTypeInitializer.PoolType = NonPagedPool;
    ObjectTypeInitializer.DeleteProcedure = ExpDeleteProfile;
    ObjectTypeInitializer.ValidAccessMask = PROFILE_ALL_ACCESS;
    ObCreateObjectType(&Name, &ObjectTypeInitializer, NULL, &ExProfileObjectType);
}

NTSTATUS
NTAPI
NtCreateProfile(OUT PHANDLE ProfileHandle,
                IN HANDLE Process OPTIONAL,
                IN PVOID RangeBase,
                IN ULONG RangeSize,
                IN ULONG BucketSize,
                IN PVOID Buffer,
                IN ULONG BufferSize,
                IN KPROFILE_SOURCE ProfileSource,
                IN KAFFINITY Affinity)
{
    HANDLE hProfile;
    PEPROFILE Profile;
    PEPROCESS pProcess;
    KPROCESSOR_MODE PreviousMode = ExGetPreviousMode();
    OBJECT_ATTRIBUTES ObjectAttributes;
    NTSTATUS Status;
    ULONG Log2 = 0;
    ULONG_PTR Segment = 0;
    PAGED_CODE();

    /* Easy way out */
    if(!BufferSize) return STATUS_INVALID_PARAMETER_7;

    /* Check if this is a low-memory profile */
    if ((!BucketSize) && (RangeBase < (PVOID)(0x10000)))
    {
        /* Validate size */
        if (BufferSize < sizeof(ULONG)) return STATUS_INVALID_PARAMETER_7;

        /* This will become a segmented profile object */
        Segment = (ULONG_PTR)RangeBase;
        RangeBase = 0;

        /* Recalculate the bucket size */
        BucketSize = RangeSize / (BufferSize / sizeof(ULONG));

        /* Convert it to log2 */
        BucketSize--;
        while (BucketSize >>= 1) Log2++;
        BucketSize += Log2 + 1;
    }

    /* Validate bucket size */
    if ((BucketSize > 31) || (BucketSize < 2))
    {
        DPRINT1("Bucket size invalid\n");
        return STATUS_INVALID_PARAMETER;
    }

    /* Make sure that the buckets can map the range */
    if ((RangeSize >> (BucketSize - 2)) > BufferSize)
    {
        DPRINT1("Bucket size too small\n");
        return STATUS_BUFFER_TOO_SMALL;
    }

    /* Make sure that the range isn't too gigantic */
    if (((ULONG_PTR)RangeBase + RangeSize) < RangeSize)
    {
        DPRINT1("Range too big\n");
        return STATUS_BUFFER_OVERFLOW;
    }

    /* Check if we were called from user-mode */
    if(PreviousMode != KernelMode)
    {
        /* Entry SEH */
        _SEH2_TRY
        {
            /* Make sure that the handle pointer is valid */
            ProbeForWriteHandle(ProfileHandle);

            /* Check if the buffer is valid */
            ProbeForWrite(Buffer,
                          BufferSize,
                          sizeof(ULONG));
        }
        _SEH2_EXCEPT(EXCEPTION_EXECUTE_HANDLER)
        {
            /* Return the exception code */
            _SEH2_YIELD(return _SEH2_GetExceptionCode());
        }
        _SEH2_END;
    }

    /* Check if a process was specified */
    if (Process)
    {
        /* Reference it */
        Status = ObReferenceObjectByHandle(Process,
                                           PROCESS_QUERY_INFORMATION,
                                           PsProcessType,
                                           PreviousMode,
                                           (PVOID*)&pProcess,
                                           NULL);
        if (!NT_SUCCESS(Status)) return(Status);
    }
    else
    {
        /* Segmented profile objects cannot be used system-wide */
        if (Segment) return STATUS_INVALID_PARAMETER;

        /* No process was specified, which means a System-Wide Profile */
        pProcess = NULL;

        /* For this, we need to check the Privilege */
        if(!SeSinglePrivilegeCheck(SeSystemProfilePrivilege, PreviousMode))
        {
            DPRINT1("NtCreateProfile: Caller requires the SeSystemProfilePrivilege privilege!\n");
            return STATUS_PRIVILEGE_NOT_HELD;
        }
    }

    /* Create the object */
    InitializeObjectAttributes(&ObjectAttributes,
                               NULL,
                               0,
                               NULL,
                               NULL);
    Status = ObCreateObject(KernelMode,
                            ExProfileObjectType,
                            &ObjectAttributes,
                            PreviousMode,
                            NULL,
                            sizeof(EPROFILE),
                            0,
                            sizeof(EPROFILE) + sizeof(KPROFILE),
                            (PVOID*)&Profile);
    if (!NT_SUCCESS(Status))
    {
        /* Dereference the process object if it was specified */
        if (pProcess) ObDereferenceObject(pProcess);

        /* Return Status */
        return Status;
    }

    /* Initialize it */
    Profile->RangeBase = RangeBase;
    Profile->RangeSize = RangeSize;
    Profile->Buffer = Buffer;
    Profile->BufferSize = BufferSize;
    Profile->BucketSize = BucketSize;
    Profile->LockedBufferAddress = NULL;
    Profile->Segment = Segment;
    Profile->ProfileSource = ProfileSource;
    Profile->Affinity = Affinity;
    Profile->Process = pProcess;

    /* Insert into the Object Tree */
    Status = ObInsertObject ((PVOID)Profile,
                             NULL,
                             PROFILE_CONTROL,
                             0,
                             NULL,
                             &hProfile);
    ObDereferenceObject(Profile);

    /* Check for Success */
    if (!NT_SUCCESS(Status))
    {
        /* Dereference Process on failure */
        if (pProcess) ObDereferenceObject(pProcess);
        return Status;
    }

    /* Enter SEH */
    _SEH2_TRY
    {
        /* Copy the created handle back to the caller*/
        *ProfileHandle = hProfile;
    }
    _SEH2_EXCEPT(ExSystemExceptionFilter())
    {
        Status = _SEH2_GetExceptionCode();
    }
    _SEH2_END;

    /* Return Status */
    return Status;
}

NTSTATUS
NTAPI
NtQueryPerformanceCounter(OUT PLARGE_INTEGER PerformanceCounter,
                          OUT PLARGE_INTEGER PerformanceFrequency OPTIONAL)
{
    KPROCESSOR_MODE PreviousMode = ExGetPreviousMode();
    LARGE_INTEGER PerfFrequency;
    NTSTATUS Status = STATUS_SUCCESS;

    /* Check if we were called from user-mode */
    if (PreviousMode != KernelMode)
    {
        /* Entry SEH Block */
        _SEH2_TRY
        {
            /* Make sure the counter and frequency are valid */
            ProbeForWriteLargeInteger(PerformanceCounter);
            if (PerformanceFrequency)
            {
                ProbeForWriteLargeInteger(PerformanceFrequency);
            }
        }
        _SEH2_EXCEPT(EXCEPTION_EXECUTE_HANDLER)
        {
            /* Return the exception code */
            _SEH2_YIELD(return _SEH2_GetExceptionCode());
        }
        _SEH2_END;
    }

    /* Enter a new SEH Block */
    _SEH2_TRY
    {
        /* Query the Kernel */
        *PerformanceCounter = KeQueryPerformanceCounter(&PerfFrequency);

        /* Return Frequency if requested */
        if (PerformanceFrequency) *PerformanceFrequency = PerfFrequency;
    }
    _SEH2_EXCEPT(ExSystemExceptionFilter())
    {
        /* Get the exception code */
        Status = _SEH2_GetExceptionCode();
    }
    _SEH2_END;

    /* Return status to caller */
    return Status;
}

NTSTATUS
NTAPI
NtStartProfile(IN HANDLE ProfileHandle)
{
    PEPROFILE Profile;
    PKPROFILE ProfileObject;
    KPROCESSOR_MODE PreviousMode = ExGetPreviousMode();
    PVOID TempLockedBufferAddress;
    NTSTATUS Status;
    PAGED_CODE();

    /* Get the Object */
    Status = ObReferenceObjectByHandle(ProfileHandle,
                                       PROFILE_CONTROL,
                                       ExProfileObjectType,
                                       PreviousMode,
                                       (PVOID*)&Profile,
                                       NULL);
    if (!NT_SUCCESS(Status)) return(Status);

    /* To avoid a Race, wait on the Mutex */
    KeWaitForSingleObject(&ExpProfileMutex,
                          Executive,
                          KernelMode,
                          FALSE,
                          NULL);

    /* The Profile can still be enabled though, so handle that */
    if (Profile->LockedBufferAddress)
    {
        /* Release our lock, dereference and return */
        KeReleaseMutex(&ExpProfileMutex, FALSE);
        ObDereferenceObject(Profile);
        return STATUS_PROFILING_NOT_STOPPED;
    }

    /* Allocate a Kernel Profile Object. */
    ProfileObject = ExAllocatePoolWithTag(NonPagedPool,
                                          sizeof(EPROFILE),
                                          TAG_PROFILE);
    if (!ProfileObject)
    {
        /* Out of memory, fail */
        KeReleaseMutex(&ExpProfileMutex, FALSE);
        ObDereferenceObject(Profile);
        return STATUS_INSUFFICIENT_RESOURCES;
    }

    /* Allocate the Mdl Structure */
    Profile->Mdl = IoAllocateMdl(Profile->Buffer, Profile->BufferSize, FALSE, FALSE, NULL);

    /* Protect this in SEH as we might raise an exception */
    _SEH2_TRY
    {
        /* Probe and Lock for Write Access */
        MmProbeAndLockPages(Profile->Mdl, PreviousMode, IoWriteAccess);
    }
    _SEH2_EXCEPT(ExSystemExceptionFilter())
    {
        /* Release our lock, free the buffer, dereference and return */
        KeReleaseMutex(&ExpProfileMutex, FALSE);
        ObDereferenceObject(Profile);
        ExFreePoolWithTag(ProfileObject, TAG_PROFILE);
        _SEH2_YIELD(return _SEH2_GetExceptionCode());
    }
    _SEH2_END;

    /* Map the pages */
    TempLockedBufferAddress = MmMapLockedPages(Profile->Mdl, KernelMode);

    /* Initialize the Kernel Profile Object */
    Profile->ProfileObject = ProfileObject;
    KeInitializeProfile(ProfileObject,
                        (PKPROCESS)Profile->Process,
                        Profile->RangeBase,
                        Profile->RangeSize,
                        Profile->BucketSize,
                        Profile->ProfileSource,
                        Profile->Affinity);

    /* Start the Profiling */
    KeStartProfile(ProfileObject, TempLockedBufferAddress);

    /* Now it's safe to save this */
    Profile->LockedBufferAddress = TempLockedBufferAddress;

    /* Release mutex, dereference and return */
    KeReleaseMutex(&ExpProfileMutex, FALSE);
    ObDereferenceObject(Profile);
    return STATUS_SUCCESS;
}

NTSTATUS
NTAPI
NtStopProfile(IN HANDLE ProfileHandle)
{
    PEPROFILE Profile;
    KPROCESSOR_MODE PreviousMode = ExGetPreviousMode();
    NTSTATUS Status;
    PAGED_CODE();

    /* Get the Object */
    Status = ObReferenceObjectByHandle(ProfileHandle,
                                       PROFILE_CONTROL,
                                       ExProfileObjectType,
                                       PreviousMode,
                                       (PVOID*)&Profile,
                                       NULL);
    if (!NT_SUCCESS(Status)) return(Status);

    /* Get the Mutex */
    KeWaitForSingleObject(&ExpProfileMutex,
                          Executive,
                          KernelMode,
                          FALSE,
                          NULL);

    /* Make sure the Profile Object is really Started */
    if (!Profile->LockedBufferAddress)
    {
        Status = STATUS_PROFILING_NOT_STARTED;
        goto Exit;
    }

    /* Stop the Profile */
    KeStopProfile(Profile->ProfileObject);

    /* Unlock the Buffer */
    MmUnmapLockedPages(Profile->LockedBufferAddress, Profile->Mdl);
    MmUnlockPages(Profile->Mdl);
    ExFreePoolWithTag(Profile->ProfileObject, TAG_PROFILE);

    /* Clear the Locked Buffer pointer, meaning the Object is Stopped */
    Profile->LockedBufferAddress = NULL;

Exit:
    /* Release Mutex, Dereference and Return */
    KeReleaseMutex(&ExpProfileMutex, FALSE);
    ObDereferenceObject(Profile);
    return Status;
}

NTSTATUS
NTAPI
NtQueryIntervalProfile(IN KPROFILE_SOURCE ProfileSource,
                       OUT PULONG Interval)
{
    KPROCESSOR_MODE PreviousMode = ExGetPreviousMode();
    ULONG ReturnInterval;
    NTSTATUS Status = STATUS_SUCCESS;
    PAGED_CODE();

    /* Check if we were called from user-mode */
    if (PreviousMode != KernelMode)
    {
        /* Enter SEH Block */
        _SEH2_TRY
        {
            /* Validate interval */
            ProbeForWriteUlong(Interval);
        }
        _SEH2_EXCEPT(EXCEPTION_EXECUTE_HANDLER)
        {
            /* Return the exception code */
            _SEH2_YIELD(return _SEH2_GetExceptionCode());
        }
        _SEH2_END;
    }

    /* Query the Interval */
    ReturnInterval = (ULONG)KeQueryIntervalProfile(ProfileSource);

    /* Enter SEH block for return */
    _SEH2_TRY
    {
        /* Return the data */
        *Interval = ReturnInterval;
    }
    _SEH2_EXCEPT(ExSystemExceptionFilter())
    {
        /* Get the exception code */
        Status = _SEH2_GetExceptionCode();
    }
    _SEH2_END;

    /* Return Success */
    return Status;
}

NTSTATUS
NTAPI
NtSetIntervalProfile(IN ULONG Interval,
                     IN KPROFILE_SOURCE Source)
{
    /* Let the Kernel do the job */
    KeSetIntervalProfile(Interval, Source);

    /* Nothing can go wrong */
    return STATUS_SUCCESS;
}
