/*++

Copyright (c) 2014 Minoca Corp. All Rights Reserved

Module Name:

    info.c

Abstract:

    This module implements support for the get and set system information
    calls.

Author:

    Evan Green 10-Apr-2014

Environment:

    Kernel

--*/

//
// ------------------------------------------------------------------- Includes
//

#include <minoca/kernel/kernel.h>
#include <minoca/fw/smbios.h>
#include "kep.h"

//
// ---------------------------------------------------------------- Definitions
//

//
// ------------------------------------------------------ Data Type Definitions
//

//
// ----------------------------------------------- Internal Function Prototypes
//

KSTATUS
KepGetSetSystemInformation (
    BOOL FromKernelMode,
    SYSTEM_INFORMATION_SUBSYSTEM Subsystem,
    UINTN InformationType,
    PVOID Data,
    PUINTN DataSize,
    BOOL Set
    );

KSTATUS
KepGetSetKeSystemInformation (
    BOOL FromKernelMode,
    KE_INFORMATION_TYPE InformationType,
    PVOID Data,
    PUINTN DataSize,
    BOOL Set
    );

KSTATUS
KepGetSystemVersion (
    PVOID Data,
    PUINTN DataSize,
    BOOL Set
    );

KSTATUS
KepGetFirmwareTable (
    PVOID Data,
    PUINTN DataSize,
    BOOL Set
    );

KSTATUS
KepGetFirmwareType (
    PVOID Data,
    PUINTN DataSize,
    BOOL Set
    );

KSTATUS
KepGetProcessorUsage (
    PVOID Data,
    PUINTN DataSize,
    BOOL Set
    );

KSTATUS
KepGetProcessorCount (
    PVOID Data,
    PUINTN DataSize,
    BOOL Set
    );

KSTATUS
KepGetKernelCommandLine (
    PVOID Data,
    PUINTN DataSize,
    BOOL Set
    );

//
// -------------------------------------------------------------------- Globals
//

SYSTEM_FIRMWARE_TYPE KeSystemFirmwareType = SystemFirmwareUnknown;
PKERNEL_COMMAND_LINE KeCommandLine = NULL;

//
// ------------------------------------------------------------------ Functions
//

KERNEL_API
KSTATUS
KeGetSetSystemInformation (
    SYSTEM_INFORMATION_SUBSYSTEM Subsystem,
    UINTN InformationType,
    PVOID Data,
    PUINTN DataSize,
    BOOL Set
    )

/*++

Routine Description:

    This routine gets or sets system information.

Arguments:

    Subsystem - Supplies the subsystem to query or set information of.

    InformationType - Supplies the information type, which is specific to
        the subsystem. The type of this value is generally
        <subsystem>_INFORMATION_TYPE (eg IO_INFORMATION_TYPE).

    Data - Supplies a pointer to the data buffer where the data is either
        returned for a get operation or given for a set operation.

    DataSize - Supplies a pointer that on input contains the size of the
        data buffer. On output, contains the required size of the data buffer.

    Set - Supplies a boolean indicating if this is a get operation (FALSE) or
        a set operation (TRUE).

Return Value:

    STATUS_SUCCESS if the information was successfully queried or set.

    STATUS_BUFFER_TOO_SMALL if the buffer size specified was too small. The
    required buffer size will be returned in the data size parameter.

    STATUS_DATA_LENGTH_MISMATCH if the buffer size was not correct. The
    correct buffer size will be returned in the data size parameter.

    STATUS_INVALID_PARAMETER if the given subsystem or information type is
    not known.

    Other status codes on other failures.

--*/

{

    KSTATUS Status;

    Status = KepGetSetSystemInformation(TRUE,
                                        Subsystem,
                                        InformationType,
                                        Data,
                                        DataSize,
                                        Set);

    return Status;
}

KERNEL_API
PKERNEL_ARGUMENT
KeGetKernelArgument (
    PKERNEL_ARGUMENT Start,
    PSTR Component,
    PSTR Name
    )

/*++

Routine Description:

    This routine looks up a kernel command line argument.

Arguments:

    Start - Supplies an optional pointer to the previous command line argument
        to start from. Supply NULL here initially.

    Component - Supplies a pointer to the component string to look up.

    Name - Supplies a pointer to the argument name to look up.

Return Value:

    Returns a pointer to a matching kernel argument on success.

    NULL if no argument could be found.

--*/

{

    PKERNEL_ARGUMENT Argument;
    UINTN ArgumentIndex;
    PKERNEL_COMMAND_LINE Line;
    BOOL Match;

    Line = KeCommandLine;
    if (Line == NULL) {
        return NULL;
    }

    ArgumentIndex = 0;
    Argument = &(Line->Arguments[ArgumentIndex]);
    if (Start != NULL) {
        while ((ArgumentIndex < Line->ArgumentCount) &&
               (Argument != Start)) {

            Argument += 1;
            ArgumentIndex += 1;
        }

        //
        // If the argument was never found or is the last argument, nothing
        // new will be found.
        //

        if (ArgumentIndex >= Line->ArgumentCount - 1) {
            return NULL;
        }

        Argument += 1;
        ArgumentIndex += 1;
    }

    while (ArgumentIndex < Line->ArgumentCount) {
        Match = RtlAreStringsEqual(Component,
                                   Argument->Component,
                                   KERNEL_MAX_COMMAND_LINE);

        if (Match != FALSE) {
            Match = RtlAreStringsEqual(Name,
                                       Argument->Name,
                                       KERNEL_MAX_COMMAND_LINE);

            if (Match != FALSE) {
                return Argument;
            }
        }

        ArgumentIndex += 1;
        Argument += 1;
    }

    return NULL;
}

VOID
KeSysGetSetSystemInformation (
    ULONG SystemCallNumber,
    PVOID SystemCallParameter,
    PTRAP_FRAME TrapFrame,
    PULONG ResultSize
    )

/*++

Routine Description:

    This routine implements the user mode system call for getting and setting
    system information.

Arguments:

    SystemCallNumber - Supplies the system call number that was requested.

    SystemCallParameter - Supplies a pointer to the parameters supplied with
        the system call. This structure will be a stack-local copy of the
        actual parameters passed from user-mode.

    TrapFrame - Supplies a pointer to the trap frame generated by this jump
        from user mode to kernel mode.

    ResultSize - Supplies a pointer where the system call routine returns the
        size of the parameter structure to be copied back to user mode. The
        value returned here must be no larger than the original parameter
        structure size. The default is the original size of the parameters.

Return Value:

    None.

--*/

{

    PVOID Buffer;
    UINTN CopySize;
    KSTATUS CopyStatus;
    PSYSTEM_CALL_GET_SET_SYSTEM_INFORMATION Request;
    KSTATUS Status;

    ASSERT(SystemCallNumber == SystemCallGetSetSystemInformation);

    Buffer = NULL;
    Request = SystemCallParameter;

    //
    // Create a paged pool buffer to hold the data.
    //

    CopySize = 0;
    if (Request->DataSize != 0) {
        Buffer = MmAllocatePagedPool(Request->DataSize,
                                     KE_INFORMATION_ALLOCATION_TAG);

        if (Buffer == NULL) {
            Status = STATUS_INSUFFICIENT_RESOURCES;
            goto SysGetSetSystemInformationEnd;
        }

        CopySize = Request->DataSize;

        //
        // Copy the data into the kernel mode buffer.
        //

        Status = MmCopyFromUserMode(Buffer,
                                    Request->Data,
                                    Request->DataSize);

        if (!KSUCCESS(Status)) {
            goto SysGetSetSystemInformationEnd;
        }
    }

    Status = KepGetSetSystemInformation(FALSE,
                                        Request->Subsystem,
                                        Request->InformationType,
                                        Buffer,
                                        &(Request->DataSize),
                                        Request->Set);

    //
    // Copy the data back into user mode, even on set operations.
    //

    if (CopySize > Request->DataSize) {
        CopySize = Request->DataSize;
    }

    if (CopySize != 0) {
        CopyStatus = MmCopyToUserMode(Request->Data, Buffer, CopySize);
        if ((KSUCCESS(Status)) && (!KSUCCESS(CopyStatus))) {
            Status = CopyStatus;
        }
    }

SysGetSetSystemInformationEnd:
    if (Buffer != NULL) {
        MmFreePagedPool(Buffer);
    }

    Request->Status = Status;
    return;
}

//
// --------------------------------------------------------- Internal Functions
//

KSTATUS
KepGetSetSystemInformation (
    BOOL FromKernelMode,
    SYSTEM_INFORMATION_SUBSYSTEM Subsystem,
    UINTN InformationType,
    PVOID Data,
    PUINTN DataSize,
    BOOL Set
    )

/*++

Routine Description:

    This routine gets or sets system information.

Arguments:

    FromKernelMode - Supplies a boolean indicating whether or not this request
        (and the buffer associated with it) originates from user mode (FALSE)
        or kernel mode (TRUE).

    Subsystem - Supplies the subsystem to query or set information of.

    InformationType - Supplies the information type, which is specific to
        the subsystem. The type of this value is generally
        <subsystem>_INFORMATION_TYPE (eg IO_INFORMATION_TYPE).

    Data - Supplies a pointer to the data buffer where the data is either
        returned for a get operation or given for a set operation.

    DataSize - Supplies a pointer that on input contains the size of the
        data buffer. On output, contains the required size of the data buffer.

    Set - Supplies a boolean indicating if this is a get operation (FALSE) or
        a set operation (TRUE).

Return Value:

    STATUS_SUCCESS if the information was successfully queried or set.

    STATUS_BUFFER_TOO_SMALL if the buffer size specified was too small. The
    required buffer size will be returned in the data size parameter.

    STATUS_DATA_LENGTH_MISMATCH if the buffer size was not correct. The
    correct buffer size will be returned in the data size parameter.

    STATUS_INVALID_PARAMETER if the given subsystem or information type is
    not known.

    Other status codes on other failures.

--*/

{

    KSTATUS Status;

    switch (Subsystem) {
    case SystemInformationKe:
        Status = KepGetSetKeSystemInformation(FromKernelMode,
                                              InformationType,
                                              Data,
                                              DataSize,
                                              Set);

        break;

    case SystemInformationIo:
        Status = IoGetSetSystemInformation(FromKernelMode,
                                           InformationType,
                                           Data,
                                           DataSize,
                                           Set);

        break;

    case SystemInformationMm:
        Status = MmGetSetSystemInformation(FromKernelMode,
                                           InformationType,
                                           Data,
                                           DataSize,
                                           Set);

        break;

    case SystemInformationPs:
        Status = PsGetSetSystemInformation(FromKernelMode,
                                           InformationType,
                                           Data,
                                           DataSize,
                                           Set);

        break;

    case SystemInformationHl:
        Status = HlGetSetSystemInformation(FromKernelMode,
                                           InformationType,
                                           Data,
                                           DataSize,
                                           Set);

        break;

    case SystemInformationSp:
        Status = SpGetSetSystemInformation(FromKernelMode,
                                           InformationType,
                                           Data,
                                           DataSize,
                                           Set);

        break;

    case SystemInformationPm:
        Status = PmGetSetSystemInformation(FromKernelMode,
                                           InformationType,
                                           Data,
                                           DataSize,
                                           Set);

        break;

    default:
        Status = STATUS_INVALID_PARAMETER;
        *DataSize = 0;
        break;
    }

    return Status;
}

KSTATUS
KepGetSetKeSystemInformation (
    BOOL FromKernelMode,
    KE_INFORMATION_TYPE InformationType,
    PVOID Data,
    PUINTN DataSize,
    BOOL Set
    )

/*++

Routine Description:

    This routine gets or sets system information.

Arguments:

    FromKernelMode - Supplies a boolean indicating whether or not this request
        (and the buffer associated with it) originates from user mode (FALSE)
        or kernel mode (TRUE).

    InformationType - Supplies the information type.

    Data - Supplies a pointer to the data buffer where the data is either
        returned for a get operation or given for a set operation.

    DataSize - Supplies a pointer that on input contains the size of the
        data buffer. On output, contains the required size of the data buffer.

    Set - Supplies a boolean indicating if this is a get operation (FALSE) or
        a set operation (TRUE).

Return Value:

    Status code.

--*/

{

    KSTATUS Status;

    switch (InformationType) {
    case KeInformationSystemVersion:
        Status = KepGetSystemVersion(Data, DataSize, Set);
        break;

    case KeInformationFirmwareTable:
        Status = KepGetFirmwareTable(Data, DataSize, Set);
        break;

    case KeInformationFirmwareType:
        Status = KepGetFirmwareType(Data, DataSize, Set);
        break;

    case KeInformationProcessorUsage:
        Status = KepGetProcessorUsage(Data, DataSize, Set);
        break;

    case KeInformationProcessorCount:
        Status = KepGetProcessorCount(Data, DataSize, Set);
        break;

    case KeInformationKernelCommandLine:
        Status = KepGetKernelCommandLine(Data, DataSize, Set);
        break;

    default:
        Status = STATUS_INVALID_PARAMETER;
        *DataSize = 0;
        break;
    }

    return Status;
}

KSTATUS
KepGetSystemVersion (
    PVOID Data,
    PUINTN DataSize,
    BOOL Set
    )

/*++

Routine Description:

    This routine gets OS version information.

Arguments:

    Data - Supplies a pointer to the data buffer where the data is either
        returned for a get operation or given for a set operation.

    DataSize - Supplies a pointer that on input contains the size of the
        data buffer. On output, contains the required size of the data buffer.

    Set - Supplies a boolean indicating if this is a get operation (FALSE) or
        a set operation (TRUE).

Return Value:

    Status code.

--*/

{

    ULONG BufferSize;
    KSTATUS Status;
    SYSTEM_VERSION_INFORMATION VersionInformation;
    PSYSTEM_VERSION_INFORMATION VersionInformationPointer;

    if (Set != FALSE) {
        Status = STATUS_ACCESS_DENIED;
        return Status;
    }

    //
    // If the data is at least big enough to hold the version information
    // structure, then try to get everything.
    //

    if (*DataSize >= sizeof(SYSTEM_VERSION_INFORMATION)) {
        BufferSize = *DataSize - sizeof(SYSTEM_VERSION_INFORMATION);
        VersionInformationPointer = Data;
        Status = KeGetSystemVersion(VersionInformationPointer,
                                    Data + sizeof(SYSTEM_VERSION_INFORMATION),
                                    &BufferSize);

        if (KSUCCESS(Status)) {

            //
            // Make the string pointers into offsets for user mode.
            //

            if (VersionInformationPointer->ProductName != NULL) {
                VersionInformationPointer->ProductName =
                    (PVOID)((UINTN)(VersionInformationPointer->ProductName) -
                            (UINTN)Data);
            }

            if (VersionInformationPointer->BuildString != NULL) {
                VersionInformationPointer->BuildString =
                    (PVOID)((UINTN)(VersionInformationPointer->BuildString) -
                            (UINTN)Data);
            }
        }

    //
    // The data isn't even big enough for the version information structure.
    // Call out to get the true size.
    //

    } else {
        VersionInformationPointer = &VersionInformation;
        BufferSize = 0;
        KeGetSystemVersion(VersionInformationPointer,
                           NULL,
                           &BufferSize);

        Status = STATUS_BUFFER_TOO_SMALL;
    }

    BufferSize += sizeof(SYSTEM_VERSION_INFORMATION);
    *DataSize = BufferSize;
    return Status;
}

KSTATUS
KepGetFirmwareTable (
    PVOID Data,
    PUINTN DataSize,
    BOOL Set
    )

/*++

Routine Description:

    This routine gets a system firmware table.

Arguments:

    Data - Supplies a pointer to the data buffer where the data is either
        returned for a get operation or given for a set operation.

    DataSize - Supplies a pointer that on input contains the size of the
        data buffer. On output, contains the required size of the data buffer.

    Set - Supplies a boolean indicating if this is a get operation (FALSE) or
        a set operation (TRUE).

Return Value:

    Status code.

--*/

{

    PDESCRIPTION_HEADER AcpiTable;
    ULONG Length;
    PSMBIOS_ENTRY_POINT SmbiosTable;
    KSTATUS Status;

    if (Set != FALSE) {
        return STATUS_ACCESS_DENIED;
    }

    Status = PsCheckPermission(PERMISSION_SYSTEM_ADMINISTRATOR);
    if (!KSUCCESS(Status)) {
        return Status;
    }

    if (*DataSize < sizeof(DESCRIPTION_HEADER)) {
        *DataSize = sizeof(DESCRIPTION_HEADER);
        return STATUS_BUFFER_TOO_SMALL;
    }

    AcpiTable = AcpiFindTable(*((PULONG)Data), NULL);
    if (AcpiTable == NULL) {
        *DataSize = 0;
        return STATUS_NOT_FOUND;
    }

    if (*((PULONG)Data) == SMBIOS_ANCHOR_STRING_VALUE) {
        SmbiosTable = (PSMBIOS_ENTRY_POINT)AcpiTable;
        Length = sizeof(SMBIOS_ENTRY_POINT) + SmbiosTable->StructureTableLength;

    } else {
        Length = AcpiTable->Length;
    }

    if (*DataSize < Length) {
        *DataSize = Length;
        return STATUS_BUFFER_TOO_SMALL;
    }

    RtlCopyMemory(Data, AcpiTable, Length);
    *DataSize = Length;
    return STATUS_SUCCESS;
}

KSTATUS
KepGetFirmwareType (
    PVOID Data,
    PUINTN DataSize,
    BOOL Set
    )

/*++

Routine Description:

    This routine gets the platform firmware type.

Arguments:

    Data - Supplies a pointer to the data buffer where the data is either
        returned for a get operation or given for a set operation.

    DataSize - Supplies a pointer that on input contains the size of the
        data buffer. On output, contains the required size of the data buffer.

    Set - Supplies a boolean indicating if this is a get operation (FALSE) or
        a set operation (TRUE).

Return Value:

    Status code.

--*/

{

    if (Set != FALSE) {
        return STATUS_ACCESS_DENIED;
    }

    if (*DataSize < sizeof(ULONG)) {
        *DataSize = sizeof(ULONG);
        return STATUS_BUFFER_TOO_SMALL;
    }

    *((PULONG)Data) = KeSystemFirmwareType;
    *DataSize = sizeof(ULONG);
    return STATUS_SUCCESS;
}

KSTATUS
KepGetProcessorUsage (
    PVOID Data,
    PUINTN DataSize,
    BOOL Set
    )

/*++

Routine Description:

    This routine gets processor usage information.

Arguments:

    Data - Supplies a pointer to the data buffer where the data is either
        returned for a get operation or given for a set operation.

    DataSize - Supplies a pointer that on input contains the size of the
        data buffer. On output, contains the required size of the data buffer.

    Set - Supplies a boolean indicating if this is a get operation (FALSE) or
        a set operation (TRUE).

Return Value:

    Status code.

--*/

{

    PPROCESSOR_USAGE_INFORMATION Information;
    UINTN ProcessorCount;
    KSTATUS Status;

    if (Set != FALSE) {
        return STATUS_ACCESS_DENIED;
    }

    Status = PsCheckPermission(PERMISSION_RESOURCES);
    if (!KSUCCESS(Status)) {
        return Status;
    }

    if (*DataSize != sizeof(PROCESSOR_USAGE_INFORMATION)) {
        *DataSize = sizeof(PROCESSOR_USAGE_INFORMATION);
        return STATUS_DATA_LENGTH_MISMATCH;
    }

    Information = Data;
    Information->CycleCounterFrequency = HlQueryProcessorCounterFrequency();
    if (Information->ProcessorNumber == (UINTN)-1) {
        KeGetTotalProcessorCycleAccounting(&(Information->Usage));

    } else {
        ProcessorCount = KeGetActiveProcessorCount();
        if (Information->ProcessorNumber > ProcessorCount) {
            Information->ProcessorNumber = ProcessorCount;
            return STATUS_OUT_OF_BOUNDS;
        }

        Status = KeGetProcessorCycleAccounting(Information->ProcessorNumber,
                                               &(Information->Usage));

        return Status;
    }

    return STATUS_SUCCESS;
}

KSTATUS
KepGetProcessorCount (
    PVOID Data,
    PUINTN DataSize,
    BOOL Set
    )

/*++

Routine Description:

    This routine gets processor count information.

Arguments:

    Data - Supplies a pointer to the data buffer where the data is either
        returned for a get operation or given for a set operation.

    DataSize - Supplies a pointer that on input contains the size of the
        data buffer. On output, contains the required size of the data buffer.

    Set - Supplies a boolean indicating if this is a get operation (FALSE) or
        a set operation (TRUE).

Return Value:

    Status code.

--*/

{

    PPROCESSOR_COUNT_INFORMATION Information;
    KSTATUS Status;

    if (Set != FALSE) {
        return STATUS_ACCESS_DENIED;
    }

    Status = PsCheckPermission(PERMISSION_RESOURCES);
    if (!KSUCCESS(Status)) {
        return Status;
    }

    if (*DataSize != sizeof(PROCESSOR_COUNT_INFORMATION)) {
        *DataSize = sizeof(PROCESSOR_COUNT_INFORMATION);
        return STATUS_DATA_LENGTH_MISMATCH;
    }

    Information = Data;
    Information->MaxProcessorCount = HlGetMaximumProcessorCount();
    Information->ActiveProcessorCount = KeGetActiveProcessorCount();
    return STATUS_SUCCESS;
}

KSTATUS
KepGetKernelCommandLine (
    PVOID Data,
    PUINTN DataSize,
    BOOL Set
    )

/*++

Routine Description:

    This routine gets the kernel command line information.

Arguments:

    Data - Supplies a pointer to the data buffer where the data is either
        returned for a get operation or given for a set operation.

    DataSize - Supplies a pointer that on input contains the size of the
        data buffer. On output, contains the required size of the data buffer.

    Set - Supplies a boolean indicating if this is a get operation (FALSE) or
        a set operation (TRUE).

Return Value:

    Status code.

--*/

{

    if (Set != FALSE) {
        return STATUS_ACCESS_DENIED;
    }

    if (KeCommandLine == NULL) {
        return STATUS_NOT_FOUND;
    }

    if (*DataSize < KeCommandLine->LineSize) {
        *DataSize = KeCommandLine->LineSize;
        return STATUS_BUFFER_TOO_SMALL;
    }

    RtlCopyMemory(Data, KeCommandLine->Line, KeCommandLine->LineSize);
    return STATUS_SUCCESS;
}

