/*++

Copyright (c) 2014 Minoca Corp. All Rights Reserved

Module Name:

    devinfo.c

Abstract:

    This module implements support for device information requests.

Author:

    Evan Green 9-Apr-2014

Environment:

    Kernel

--*/

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

#include <minoca/kernel/kernel.h>
#include "iop.h"

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

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

/*++

Structure Description:

    This structure defines a device information entry.

Members:

    ListEntry - Stores pointers to the next and previous information entries in
        the global list.

    Uuid - Stores the universally unique identifier of the device information
        type.

    DeviceId - Stores the device ID of the device that registered the
        information.

    Device - Stores a pointer to the device that registered the information.

--*/

typedef struct _DEVICE_INFORMATION_ENTRY {
    LIST_ENTRY ListEntry;
    UUID Uuid;
    DEVICE_ID DeviceId;
    PDEVICE Device;
} DEVICE_INFORMATION_ENTRY, *PDEVICE_INFORMATION_ENTRY;

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

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

UINTN IoNextDeviceId;

LIST_ENTRY IoDeviceInformationList;
PSHARED_EXCLUSIVE_LOCK IoDeviceInformationLock;

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

KERNEL_API
KSTATUS
IoLocateDeviceInformation (
    PUUID Uuid,
    PDEVICE Device,
    PDEVICE_ID DeviceId,
    PDEVICE_INFORMATION_RESULT Results,
    PULONG ResultCount
    )

/*++

Routine Description:

    This routine returns instances of devices enumerating information. Callers
    can get all devices enumerating the given information type, or all
    information types enumerated by a given device. This routine must be called
    at low level.

Arguments:

    Uuid - Supplies an optional pointer to the information identifier to
        filter on. If NULL, any information type will match.

    Device - Supplies an optional pointer to the device to match against. If
        NULL (and the device ID parameter is NULL), then any device will match.

    DeviceId - Supplies an optional pointer to the device ID to match against.
        If NULL (and the device ID parameter is NULL), then any device will
        match.

    Results - Supplies a pointer to a caller allocated buffer where the
        results will be returned.

    ResultCount - Supplies a pointer that upon input contains the size of the
        buffer in information result elements. On output, returns the number
        of elements in the query, even if the provided buffer was too small.
        Do note however that the number of results can change between two
        successive searches.

Return Value:

    STATUS_SUCCESS on success.

    STATUS_BUFFER_TOO_SMALL if the provided buffer was not large enough to
    contain all the results. The result count will contain the required number
    of elements to contain the results.

--*/

{

    ULONG BufferSize;
    PLIST_ENTRY CurrentEntry;
    PDEVICE_INFORMATION_ENTRY Entry;
    ULONG MatchCount;
    KSTATUS Status;

    ASSERT(KeGetRunLevel() == RunLevelLow);

    BufferSize = *ResultCount;

    ASSERT((BufferSize == 0) || (Results != NULL));

    MatchCount = 0;

    //
    // Loop through and look for elements that match.
    //

    KeAcquireSharedExclusiveLockShared(IoDeviceInformationLock);
    CurrentEntry = IoDeviceInformationList.Next;
    while (CurrentEntry != &IoDeviceInformationList) {
        Entry = LIST_VALUE(CurrentEntry, DEVICE_INFORMATION_ENTRY, ListEntry);
        CurrentEntry = CurrentEntry->Next;

        //
        // Skip anything that doesn't match.
        //

        if (Device != NULL) {
            if (Entry->Device != Device) {
                continue;
            }
        }

        if (DeviceId != NULL) {
            if (Entry->DeviceId != *DeviceId) {
                continue;
            }
        }

        if (Uuid != NULL) {
            if (RtlAreUuidsEqual(Uuid, &(Entry->Uuid)) == FALSE) {
                continue;
            }
        }

        //
        // This matches. Copy it into the results if there's space.
        //

        if (MatchCount < BufferSize) {
            RtlCopyMemory(&(Results->Uuid), &(Entry->Uuid), sizeof(UUID));
            Results->DeviceId = Entry->DeviceId;
            Results += 1;
        }

        MatchCount += 1;
    }

    KeReleaseSharedExclusiveLockShared(IoDeviceInformationLock);
    Status = STATUS_SUCCESS;
    if (MatchCount > BufferSize) {
        Status = STATUS_BUFFER_TOO_SMALL;
    }

    *ResultCount = MatchCount;
    return Status;
}

KERNEL_API
KSTATUS
IoGetSetDeviceInformation (
    DEVICE_ID DeviceId,
    PUUID Uuid,
    PVOID Data,
    PUINTN DataSize,
    BOOL Set
    )

/*++

Routine Description:

    This routine gets or sets device information.

Arguments:

    DeviceId - Supplies the device ID of the device to get or set information
        for.

    Uuid - Supplies a pointer to the identifier of the device information type
        to get or set.

    Data - Supplies a pointer to the data buffer that either contains the
        information to set or will contain the information to get on success.

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

    Set - Supplies a boolean indicating whether to get information (FALSE) or
        set information (TRUE).

Return Value:

    Status code.

--*/

{

    PLIST_ENTRY CurrentEntry;
    PDEVICE Device;
    PDEVICE_INFORMATION_ENTRY Entry;
    SYSTEM_CONTROL_DEVICE_INFORMATION Request;
    KSTATUS Status;

    ASSERT(KeGetRunLevel() == RunLevelLow);

    //
    // Loop through and find the information entry to convert from a device ID
    // back to a device.
    //

    Device = NULL;
    KeAcquireSharedExclusiveLockShared(IoDeviceInformationLock);
    CurrentEntry = IoDeviceInformationList.Next;
    while (CurrentEntry != &IoDeviceInformationList) {
        Entry = LIST_VALUE(CurrentEntry, DEVICE_INFORMATION_ENTRY, ListEntry);
        CurrentEntry = CurrentEntry->Next;
        if (Entry->DeviceId == DeviceId) {
            Device = Entry->Device;
            break;
        }
    }

    KeReleaseSharedExclusiveLockShared(IoDeviceInformationLock);
    if (Device == NULL) {
        *DataSize = 0;
        return STATUS_NO_INTERFACE;
    }

    RtlZeroMemory(&Request, sizeof(SYSTEM_CONTROL_DEVICE_INFORMATION));
    RtlCopyMemory(&(Request.Uuid), Uuid, sizeof(UUID));
    Request.Data = Data;
    Request.DataSize = *DataSize;
    Request.Set = Set;
    Status = IopSendSystemControlIrp(Device,
                                     IrpMinorSystemControlDeviceInformation,
                                     &Request);

    *DataSize = Request.DataSize;
    return Status;
}

KERNEL_API
KSTATUS
IoRegisterDeviceInformation (
    PDEVICE Device,
    PUUID Uuid,
    BOOL Register
    )

/*++

Routine Description:

    This routine registers or deregisters a device to respond to information
    requests of the given universally unique identifier. This routine must be
    called at low level.

Arguments:

    Device - Supplies a pointer to the device.

    Uuid - Supplies a pointer to the device information identifier.

    Register - Supplies a boolean indcating if the device is registering (TRUE)
        or de-registering (FALSE) for the information type.

Return Value:

    STATUS_SUCCESS on success.

    STATUS_INSUFFICIENT_RESOURCES on allocation failure.

--*/

{

    PLIST_ENTRY CurrentEntry;
    PDEVICE_INFORMATION_ENTRY Entry;
    PDEVICE_INFORMATION_ENTRY ExistingEntry;
    PDEVICE_INFORMATION_ENTRY NewEntry;
    KSTATUS Status;

    ASSERT(KeGetRunLevel() == RunLevelLow);

    ExistingEntry = NULL;
    NewEntry = NULL;
    Status = STATUS_SUCCESS;

    //
    // Allocate and initialize the entry before acquiring the lock.
    //

    if (Register != FALSE) {
        NewEntry = MmAllocatePagedPool(sizeof(DEVICE_INFORMATION_ENTRY),
                                       DEVICE_INFORMATION_ALLOCATION_TAG);

        if (NewEntry == NULL) {
            Status = STATUS_INSUFFICIENT_RESOURCES;
            goto RegisterDeviceInformationEnd;
        }

        NewEntry->ListEntry.Next = NULL;
        RtlCopyMemory(&(NewEntry->Uuid), Uuid, sizeof(UUID));
        NewEntry->Device = Device;
        NewEntry->DeviceId = Device->DeviceId;
    }

    //
    // Look for an existing entry matching the device and UUID.
    //

    KeAcquireSharedExclusiveLockExclusive(IoDeviceInformationLock);
    CurrentEntry = IoDeviceInformationList.Next;
    while (CurrentEntry != &IoDeviceInformationList) {
        Entry = LIST_VALUE(CurrentEntry, DEVICE_INFORMATION_ENTRY, ListEntry);
        CurrentEntry = CurrentEntry->Next;
        if ((Entry->Device == Device) &&
            (RtlAreUuidsEqual(&(Entry->Uuid), Uuid) != FALSE)) {

            ExistingEntry = Entry;
            break;
        }
    }

    //
    // If there was an existing entry, remove and free it if necessary.
    //

    if (ExistingEntry != NULL) {
        if (Register == FALSE) {
            LIST_REMOVE(&(ExistingEntry->ListEntry));
            ExistingEntry->ListEntry.Next = NULL;

        //
        // The entry exists and the caller is trying to register it again.
        // Clear out the pointer so it's not freed at the end of this routine.
        //

        } else {
            ExistingEntry = NULL;
        }

    //
    // There was no previous entry, add the new one if needed.
    //

    } else {
        if (Register != FALSE) {
            INSERT_AFTER(&(NewEntry->ListEntry), &IoDeviceInformationList);
            NewEntry = NULL;
        }
    }

RegisterDeviceInformationEnd:
    KeReleaseSharedExclusiveLockExclusive(IoDeviceInformationLock);
    if (ExistingEntry != NULL) {

        ASSERT(ExistingEntry->ListEntry.Next == NULL);

        MmFreePagedPool(ExistingEntry);
    }

    if (NewEntry != NULL) {

        ASSERT(NewEntry->ListEntry.Next == NULL);

        MmFreePagedPool(NewEntry);
    }

    return Status;
}

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

/*++

Routine Description:

    This routine implements the user mode system call for locating device
    information registrations by UUID or device ID.

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.

--*/

{

    UINTN AllocationSize;
    ULONG CopyCount;
    KSTATUS CopyStatus;
    PDEVICE_ID DeviceIdPointer;
    PSYSTEM_CALL_LOCATE_DEVICE_INFORMATION Request;
    PDEVICE_INFORMATION_RESULT Results;
    KSTATUS Status;
    PUUID UuidPointer;

    ASSERT(SystemCallNumber == SystemCallLocateDeviceInformation);

    CopyCount = 0;
    Request = SystemCallParameter;

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

    Results = NULL;
    if (Request->ResultCount != 0) {
        CopyCount = Request->ResultCount;
        AllocationSize = sizeof(DEVICE_INFORMATION_RESULT) * CopyCount;
        Results = MmAllocatePagedPool(
                                    AllocationSize,
                                    DEVICE_INFORMATION_REQUEST_ALLOCATION_TAG);

        if (Results == NULL) {
            Status = STATUS_INSUFFICIENT_RESOURCES;
            goto SysLocateDeviceInformationEnd;
        }
    }

    DeviceIdPointer = NULL;
    UuidPointer = NULL;
    if (Request->ByDeviceId != FALSE) {
        DeviceIdPointer = &(Request->DeviceId);
    }

    if (Request->ByUuid != FALSE) {
        UuidPointer = &(Request->Uuid);
    }

    Status = IoLocateDeviceInformation(UuidPointer,
                                       NULL,
                                       DeviceIdPointer,
                                       Results,
                                       &(Request->ResultCount));

    //
    // Copy the results back into user mode.
    //

    if (Request->ResultCount < CopyCount) {
        CopyCount = Request->ResultCount;
    }

    if (CopyCount != 0) {
        CopyStatus = MmCopyToUserMode(
                                Request->Results,
                                Results,
                                CopyCount * sizeof(DEVICE_INFORMATION_RESULT));

        if ((KSUCCESS(Status)) && (!KSUCCESS(CopyStatus))) {
            Status = CopyStatus;
        }
    }

SysLocateDeviceInformationEnd:
    Request->Status = Status;
    if (Results != NULL) {
        MmFreePagedPool(Results);
    }

    return;
}

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

/*++

Routine Description:

    This routine implements the user mode system call for getting and setting
    device 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_DEVICE_INFORMATION Request;
    KSTATUS Status;

    ASSERT(SystemCallNumber == SystemCallGetSetDeviceInformation);

    Buffer = NULL;
    Request = SystemCallParameter;

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

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

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

        CopySize = Request->DataSize;

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

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

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

    Status = IoGetSetDeviceInformation(Request->DeviceId,
                                       &(Request->Uuid),
                                       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;
        }
    }

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

    Request->Status = Status;
    return;
}

DEVICE_ID
IopGetNextDeviceId (
    VOID
    )

/*++

Routine Description:

    This routine allocates and returns a device ID.

Arguments:

    None.

Return Value:

    Returns a unique device ID number.

--*/

{

    DEVICE_ID DeviceId;

    DeviceId = RtlAtomicAdd(&IoNextDeviceId, 1);
    return DeviceId;
}

KSTATUS
IopInitializeDeviceInformationSupport (
    VOID
    )

/*++

Routine Description:

    This routine initializes device information support.

Arguments:

    None.

Return Value:

    Status code.

--*/

{

    KSTATUS Status;

    IoNextDeviceId = OBJECT_MANAGER_DEVICE_ID + 1;
    INITIALIZE_LIST_HEAD(&IoDeviceInformationList);
    IoDeviceInformationLock = KeCreateSharedExclusiveLock();
    if (IoDeviceInformationLock == NULL) {
        Status = STATUS_INSUFFICIENT_RESOURCES;
        goto InitializeDeviceInformationSupportEnd;
    }

    Status = STATUS_SUCCESS;

InitializeDeviceInformationSupportEnd:
    return Status;
}

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

