/*
 *************************************************************************
 *  File:       DISPATCH.C
 *
 *  Module:     USBCCGP.SYS
 *              USB Common Class Generic Parent driver.
 *
 *  Copyright (c) 1998  Microsoft Corporation
 *
 *
 *  Author:     ervinp
 *
 *************************************************************************
 */

#include <wdm.h>
#include <windef.h>
#include <unknown.h>
#ifdef DRM_SUPPORT
#include <ks.h>
#include <ksmedia.h>
#include <drmk.h>
#include <ksdrmhlp.h>
#endif
#include <usbdi.h>
#include <usbdlib.h>
#include <usbioctl.h>

#include "usbccgp.h"
#include "debug.h"

#ifdef ALLOC_PRAGMA
        #pragma alloc_text(PAGE, USBC_Create)
        #pragma alloc_text(PAGE, USBC_DeviceControl)
        #pragma alloc_text(PAGE, USBC_SystemControl)
        #pragma alloc_text(PAGE, USBC_Power)
#ifdef DRM_SUPPORT
        #pragma alloc_text(PAGE, USBC_SetContentId)
#endif
#endif


/*
 *  USBC_Dispatch
 *
 *      Note:  this function cannot be pageable because reads can
 *             come in at DISPATCH level.
 */
NTSTATUS USBC_Dispatch(IN PDEVICE_OBJECT devObj, IN PIRP irp)
{
    PIO_STACK_LOCATION irpSp;
    PDEVEXT devExt;
    PPARENT_FDO_EXT parentFdoExt;
    PFUNCTION_PDO_EXT functionPdoExt;
    ULONG majorFunction, minorFunction;
    BOOLEAN isParentFdo;
    NTSTATUS status;
    BOOLEAN abortIrp = FALSE;

    devExt = devObj->DeviceExtension;
    ASSERT(devExt);
    ASSERT(devExt->signature == USBCCGP_TAG);

    irpSp = IoGetCurrentIrpStackLocation(irp);

    /*
     *  Keep these privately so we still have it after the IRP completes
     *  or after the device extension is freed on a REMOVE_DEVICE
     */
    majorFunction = irpSp->MajorFunction;
    minorFunction = irpSp->MinorFunction;
    isParentFdo = devExt->isParentFdo;

    DBG_LOG_IRP_MAJOR(irp, majorFunction, isParentFdo, FALSE, 0);

    if (isParentFdo){
        parentFdoExt = &devExt->parentFdoExt;
        functionPdoExt = BAD_POINTER;
    }
    else {
        functionPdoExt = &devExt->functionPdoExt;
        parentFdoExt = functionPdoExt->parentFdoExt;
    }

    /*
     *  For all IRPs except REMOVE, we increment the PendingActionCount
     *  across the dispatch routine in order to prevent a race condition with
     *  the REMOVE_DEVICE IRP (without this increment, if REMOVE_DEVICE
     *  preempted another IRP, device object and extension might get
     *  freed while the second thread was still using it).
     */
    if (!((majorFunction == IRP_MJ_PNP) && (minorFunction == IRP_MN_REMOVE_DEVICE))){
        IncrementPendingActionCount(parentFdoExt);
    }


    /*
     *  Make sure we don't process any IRPs besides PNP and CLOSE
     *  while a device object is getting removed.
     *  Do this after we've incremented the pendingActionCount for this IRP.
     */
    if ((majorFunction != IRP_MJ_PNP) && (majorFunction != IRP_MJ_CLOSE)){
        enum deviceState state = (isParentFdo) ? parentFdoExt->state : functionPdoExt->state;
        if (!isParentFdo && majorFunction == IRP_MJ_POWER) {
            /*
             *  Don't abort power IRP's on child function PDO's, even if
             *  state is STATE_REMOVING or STATE_REMOVED as this will veto
             *  a suspend request if the child function PDO is disabled.
             */
            ;
        } else if ((state == STATE_REMOVING) || (state == STATE_REMOVED)){
            abortIrp = TRUE;
        }
    }

    if (abortIrp){
        /*
         *  Fail all irps after a remove irp.
         *  This should never happen except:
         *  we can get a power irp on a function pdo after a remove
         *  because (per splante) the power state machine is not synchronized
         *  with the pnp state machine.  We now handle this case above.
         */
        DBGWARN(("Aborting IRP %ph (function %xh/%xh) because delete pending", irp, majorFunction, minorFunction));
        ASSERT((majorFunction == IRP_MJ_POWER) && !isParentFdo);
        status = irp->IoStatus.Status = STATUS_DELETE_PENDING;
        if (majorFunction == IRP_MJ_POWER){
            PoStartNextPowerIrp(irp);
        }
        IoCompleteRequest(irp, IO_NO_INCREMENT);
    }
    else {
        switch (majorFunction){

            case IRP_MJ_CREATE:
                status = USBC_Create(devExt, irp);
                break;

            case IRP_MJ_CLOSE:
                status = USBC_Close(devExt, irp);
                break;

            case IRP_MJ_DEVICE_CONTROL:
                status = USBC_DeviceControl(devExt, irp);
                break;

            case IRP_MJ_SYSTEM_CONTROL:
                status = USBC_SystemControl(devExt, irp);
                break;

            case IRP_MJ_INTERNAL_DEVICE_CONTROL:
                status = USBC_InternalDeviceControl(devExt, irp);
                break;

            case IRP_MJ_PNP:
                status = USBC_PnP(devExt, irp);
                break;

            case IRP_MJ_POWER:
                status = USBC_Power(devExt, irp);
                break;

            default:
                DBGERR(("USBC_Dispatch: unsupported irp majorFunction %xh.", majorFunction));
                if (isParentFdo){
                    /*
                     *  Pass this IRP to the parent device.
                     */
                    IoSkipCurrentIrpStackLocation(irp);
                    status = IoCallDriver(parentFdoExt->topDevObj, irp);
                }
                else {
                    /*
                     *  This is not a pnp/power/syscntrl irp, so we fail unsupported irps
                     *  with an actual error code (not with the default status).
                     */
                    status = irp->IoStatus.Status = STATUS_NOT_SUPPORTED;
                    IoCompleteRequest(irp, IO_NO_INCREMENT);
                }
                break;
        }
    }


    DBG_LOG_IRP_MAJOR(irp, majorFunction, isParentFdo, TRUE, status);

    /*
     *  Balance the increment above
     */
    if (!((majorFunction == IRP_MJ_PNP) && (minorFunction == IRP_MN_REMOVE_DEVICE))){
        DecrementPendingActionCount(parentFdoExt);
    }

    return status;
}


NTSTATUS USBC_Create(PDEVEXT devExt, PIRP irp)
{
    NTSTATUS status;

    PAGED_CODE();

    if (devExt->isParentFdo){
        PPARENT_FDO_EXT parentFdoExt = &devExt->parentFdoExt;
        IoSkipCurrentIrpStackLocation(irp);
        status = IoCallDriver(parentFdoExt->topDevObj, irp);
    }
    else {
        /*
         *  This is not a pnp/power/syscntrl irp, so we fail unsupported irps
         *  with an actual error code (not with the default status).
         */
        status = irp->IoStatus.Status = STATUS_NOT_SUPPORTED;
        IoCompleteRequest(irp, IO_NO_INCREMENT);
    }

    return status;
}

NTSTATUS USBC_Close(PDEVEXT devExt, PIRP irp)
{
    NTSTATUS status;

    if (devExt->isParentFdo){
        PPARENT_FDO_EXT parentFdoExt = &devExt->parentFdoExt;
        IoSkipCurrentIrpStackLocation(irp);
        status = IoCallDriver(parentFdoExt->topDevObj, irp);
    }
    else {
        /*
         *  This is not a pnp/power/syscntrl irp, so we fail unsupported irps
         *  with an actual error code (not with the default status).
         */
        status = irp->IoStatus.Status = STATUS_NOT_SUPPORTED;
        IoCompleteRequest(irp, IO_NO_INCREMENT);
    }

    return status;
}


#ifdef DRM_SUPPORT

/*****************************************************************************
 * USBC_SetContentId
 *****************************************************************************
 *
 */
NTSTATUS
USBC_SetContentId
(
    IN PIRP                          irp,
    IN PKSP_DRMAUDIOSTREAM_CONTENTID pKsProperty,
    IN PKSDRMAUDIOSTREAM_CONTENTID   pvData
)
{
    ULONG ContentId;
    PIO_STACK_LOCATION iostack;
    PDEVEXT devExt;
    USBD_PIPE_HANDLE hPipe;
    NTSTATUS status;

    PAGED_CODE();

    ASSERT(irp);
    ASSERT(pKsProperty);
    ASSERT(pvData);

    iostack = IoGetCurrentIrpStackLocation(irp);
    devExt = iostack->DeviceObject->DeviceExtension;
    hPipe = pKsProperty->Context;
    ContentId = pvData->ContentId;

    if (devExt->isParentFdo){
        // IOCTL sent to parent FDO.  Forward to down the stack.
        PPARENT_FDO_EXT parentFdoExt = &devExt->parentFdoExt;
        status = pKsProperty->DrmForwardContentToDeviceObject(ContentId, parentFdoExt->topDevObj, hPipe);
    }
    else {
        // IOCTL send to function PDO.  Forward to parent FDO on other stack.
        PFUNCTION_PDO_EXT functionPdoExt = &devExt->functionPdoExt;
        PPARENT_FDO_EXT parentFdoExt = functionPdoExt->parentFdoExt;
        status = pKsProperty->DrmForwardContentToDeviceObject(ContentId, parentFdoExt->fdo, hPipe);
    }

    return status;
}

#endif


NTSTATUS USBC_DeviceControl(PDEVEXT devExt, PIRP irp)
{
    PIO_STACK_LOCATION irpSp = IoGetCurrentIrpStackLocation(irp);
    ULONG ioControlCode = irpSp->Parameters.DeviceIoControl.IoControlCode;
    NTSTATUS status;

    PAGED_CODE();

#ifdef DRM_SUPPORT

    if (IOCTL_KS_PROPERTY == ioControlCode) {
        status = KsPropertyHandleDrmSetContentId(irp, USBC_SetContentId);
        irp->IoStatus.Status = status;
        IoCompleteRequest(irp, IO_NO_INCREMENT);
    } else {
#endif
        if (devExt->isParentFdo){
            PPARENT_FDO_EXT parentFdoExt = &devExt->parentFdoExt;
            status = ParentDeviceControl(parentFdoExt, irp);
        }
        else {
            /*
             *  Pass the IOCTL IRP sent to our child PDO to our own parent FDO.
             */
            PFUNCTION_PDO_EXT functionPdoExt = &devExt->functionPdoExt;
            PPARENT_FDO_EXT parentFdoExt = functionPdoExt->parentFdoExt;
            IoCopyCurrentIrpStackLocationToNext(irp);
            status = IoCallDriver(parentFdoExt->fdo, irp);
        }
#ifdef DRM_SUPPORT
    }
#endif

    DBG_LOG_IOCTL(ioControlCode, status);

    return status;
}

NTSTATUS USBC_SystemControl(PDEVEXT devExt, PIRP irp)
{
    NTSTATUS status;

    PAGED_CODE();

    if (devExt->isParentFdo){
        PPARENT_FDO_EXT parentFdoExt = &devExt->parentFdoExt;
        IoSkipCurrentIrpStackLocation(irp);
        status = IoCallDriver(parentFdoExt->topDevObj, irp);
    }
    else {
        /*
         *  Pass the IOCTL IRP sent to our child PDO to our own parent FDO.
         */
        PFUNCTION_PDO_EXT functionPdoExt = &devExt->functionPdoExt;
        PPARENT_FDO_EXT parentFdoExt = functionPdoExt->parentFdoExt;
        IoCopyCurrentIrpStackLocationToNext(irp);
        status = IoCallDriver(parentFdoExt->fdo, irp);
    }

    return status;
}


/*
 *  USBC_InternalDeviceControl
 *
 *
 *      Note:  this function cannot be pageable because internal
 *             ioctls may be sent at IRQL==DISPATCH_LEVEL.
 */
NTSTATUS USBC_InternalDeviceControl(PDEVEXT devExt, PIRP irp)
{
    PIO_STACK_LOCATION irpSp = IoGetCurrentIrpStackLocation(irp);
    ULONG ioControlCode = irpSp->Parameters.DeviceIoControl.IoControlCode;
    NTSTATUS status;

    if (devExt->isParentFdo){
        PPARENT_FDO_EXT parentFdoExt = &devExt->parentFdoExt;
        status = ParentInternalDeviceControl(parentFdoExt, irp);
    }
    else {
        PFUNCTION_PDO_EXT functionPdoExt = &devExt->functionPdoExt;
        status = FunctionInternalDeviceControl(functionPdoExt, irp);
    }

    DBG_LOG_IOCTL(ioControlCode, status);

    return status;
}


NTSTATUS USBC_Power(PDEVEXT devExt, PIRP irp)
{
    NTSTATUS status;

    PAGED_CODE();

    if (devExt->isParentFdo){
        PPARENT_FDO_EXT parentFdoExt = &devExt->parentFdoExt;
        status = HandleParentFdoPower(parentFdoExt, irp);
    }
    else {
        PFUNCTION_PDO_EXT functionPdoExt = &devExt->functionPdoExt;
        status = HandleFunctionPdoPower(functionPdoExt, irp);
    }

    return status;
}