/*++

Copyright (c) 1996  Microsoft Corporation

Module Name:

   decinit.c

Abstract:

   This is the WDM decoder class driver.  This module contains code related
   to request processing.

Author:

    billpa

Environment:

   Kernel mode only


Revision History:

--*/

#include "codcls.h"

#if DBG

#if WIN95_BUILD
ULONG           StreamDebug = DebugLevelInfo;
#else
ULONG           StreamDebug = DebugLevelError;
#endif

#define STREAM_BUFFER_SIZE 256
UCHAR           StreamBuffer[STREAM_BUFFER_SIZE];
#endif

#ifdef POOL_TAGGING
#ifdef ExAllocatePool
#undef ExAllocatePool
#endif
#define ExAllocatePool(a,b) ExAllocatePoolWithTag(a,b,'PscS')
#endif


VOID
StreamClassStreamNotification(
             IN STREAM_MINIDRIVER_STREAM_NOTIFICATION_TYPE NotificationType,
                              IN PHW_STREAM_OBJECT HwStreamObject,
                              ...
)
/*++

Routine Description:

  stream notification routine for minidriver

Arguments:

  NotificationType - indicates what has happened
  HwStreamObject - address of minidriver's stream struct

Return Value:

  none

--*/

{
    va_list         Arguments;
    PSTREAM_REQUEST_BLOCK SRB;
    PSTREAM_OBJECT  StreamObject = CONTAINING_RECORD(
                                                     HwStreamObject,
                                                     STREAM_OBJECT,
                                                     HwStreamObject
                                                    );
    PDEVICE_EXTENSION DeviceExtension;
    KIRQL           Irql;

    #if DBG
    PMDL            CurrentMdl;
    #endif

    va_start(Arguments, HwStreamObject);

    ASSERT(HwStreamObject != NULL);

    DeviceExtension = StreamObject->DeviceExtension;

    ASSERT((DeviceExtension->BeginMinidriverCallin == SCBeginSynchronizedMinidriverCallin) ||
           (DeviceExtension->BeginMinidriverCallin == SCBeginUnsynchronizedMinidriverCallin));

    #if DBG
    if (DeviceExtension->NoSync) {

        ASSERT(KeGetCurrentIrql() <= DISPATCH_LEVEL);

    }                           // if nosync
    #endif

    //
    // optimization for async drivers - just directly call back the request
    // rather than queuing it on the DPC processed completed list.
    //

    if ((DeviceExtension->NoSync) && (NotificationType == StreamRequestComplete)) {

        SRB = CONTAINING_RECORD(va_arg(Arguments,
                                       PHW_STREAM_REQUEST_BLOCK),
                                STREAM_REQUEST_BLOCK,
                                HwSRB);

        KeAcquireSpinLock(&DeviceExtension->SpinLock, &Irql);

        //
        // Clear the active flag.
        //

        ASSERT(SRB->Flags & SRB_FLAGS_IS_ACTIVE);
        SRB->Flags &= ~SRB_FLAGS_IS_ACTIVE;

        #if DBG
        //
        // assert the MDL list.
        //

        if (SRB->HwSRB.Irp) {
            CurrentMdl = SRB->HwSRB.Irp->MdlAddress;

            while (CurrentMdl) {

                CurrentMdl = CurrentMdl->Next;
            }                   // while

        }                       // if IRP
        ASSERT(SRB->HwSRB.Flags & SRB_HW_FLAGS_STREAM_REQUEST);

        if ((SRB->HwSRB.Command == SRB_READ_DATA) ||
            (SRB->HwSRB.Command == SRB_WRITE_DATA)) {

            ASSERT(SRB->HwSRB.Flags & SRB_HW_FLAGS_DATA_TRANSFER);
        } else {

            ASSERT(!(SRB->HwSRB.Flags & SRB_HW_FLAGS_DATA_TRANSFER));
        }                       // if read/write
        #endif


        if (SRB->DoNotCallBack) {

            DebugPrint((DebugLevelError, "'ScNotify: NOT calling back request - Irp = %x, S# = %x\n",
                SRB->HwSRB.Irp, StreamObject->HwStreamObject.StreamNumber));
            KeReleaseSpinLock(&DeviceExtension->SpinLock, Irql);
            return;

        }                       // if NoCallback
        KeReleaseSpinLock(&DeviceExtension->SpinLock, Irql);

        DebugPrint((DebugLevelTrace, "'SCNotification: Completing async stream Irp %x, S# = %x, SRB = %x, Func = %x, Callback = %x, SRB->IRP = %x\n",
                  SRB->HwSRB.Irp, StreamObject->HwStreamObject.StreamNumber,
                    SRB, SRB->HwSRB.Command, SRB->Callback, SRB->HwSRB.Irp));
        (SRB->Callback) (SRB);

        return;

    }                           // if nosync & complete
    BEGIN_MINIDRIVER_STREAM_CALLIN(DeviceExtension, &Irql);

    switch (NotificationType) {

    case ReadyForNextStreamDataRequest:

        //
        // Start next data packet on adapter's stream queue.
        //

        DebugPrint((DebugLevelTrace, "'StreamClassStreamNotify: ready for next stream data request, S# = %x\n",
                    StreamObject->HwStreamObject.StreamNumber));

        ASSERT(!(StreamObject->ReadyForNextDataReq));
        ASSERT(!(DeviceExtension->NoSync));

        StreamObject->ReadyForNextDataReq = TRUE;
        break;

    case ReadyForNextStreamControlRequest:

        //
        // Start next data packet on adapter's stream queue.
        //

        DebugPrint((DebugLevelTrace, "'StreamClassStreamNotify: ready for next stream control request, S# = %x\n",
                    StreamObject->HwStreamObject.StreamNumber));

        ASSERT(!(StreamObject->ReadyForNextControlReq));
        ASSERT(!(DeviceExtension->NoSync));

        StreamObject->ReadyForNextControlReq = TRUE;
        break;

    case StreamRequestComplete:

        SRB = CONTAINING_RECORD(va_arg(Arguments,
                                       PHW_STREAM_REQUEST_BLOCK),
                                STREAM_REQUEST_BLOCK,
                                HwSRB);

        DebugPrint((DebugLevelTrace, "'SCStreamNot: completing Irp %x, S# = %x, SRB = %x, Command = %x\n",
                    SRB->HwSRB.Irp, StreamObject->HwStreamObject.StreamNumber, SRB, SRB->HwSRB.Command));
        ASSERT(SRB->HwSRB.Status != STATUS_PENDING);
        ASSERT(SRB->Flags & SRB_FLAGS_IS_ACTIVE);

        //
        // Clear the active flag.
        //

        SRB->Flags &= ~SRB_FLAGS_IS_ACTIVE;

        //
        // add the SRB to the list of completed SRB's.
        //

        SRB->HwSRB.NextSRB = StreamObject->ComObj.InterruptData.CompletedSRB;
        StreamObject->ComObj.InterruptData.CompletedSRB = &SRB->HwSRB;

        #if DBG
        //
        // assert the MDL list.
        //

        if (SRB->HwSRB.Irp) {
            CurrentMdl = SRB->HwSRB.Irp->MdlAddress;

            while (CurrentMdl) {

                CurrentMdl = CurrentMdl->Next;
            }                   // while

        }                       // if IRP
        ASSERT(SRB->HwSRB.Flags & SRB_HW_FLAGS_STREAM_REQUEST);

        if ((SRB->HwSRB.Command == SRB_READ_DATA) ||
            (SRB->HwSRB.Command == SRB_WRITE_DATA)) {

            ASSERT(SRB->HwSRB.Flags & SRB_HW_FLAGS_DATA_TRANSFER);
        } else {

            ASSERT(!(SRB->HwSRB.Flags & SRB_HW_FLAGS_DATA_TRANSFER));
        }                       // if read/write
        #endif

        break;

    case SignalMultipleStreamEvents:
        {

            GUID           *EventGuid = va_arg(Arguments, GUID *);
            ULONG           EventItem = va_arg(Arguments, ULONG);

            //
            // signal all events that match the criteria.  note that we are
            // already
            // at the level required for synchronizing the list, so no lock
            // type is specified.
            //

            KsGenerateEventList(EventGuid,
                                EventItem,
                                &StreamObject->NotifyList,
                                KSEVENTS_NONE,
                                NULL);


        }                       // case event

        break;

    case SignalStreamEvent:

        KsGenerateEvent(va_arg(Arguments, PKSEVENT_ENTRY));
        break;


    case DeleteStreamEvent:
        {

            PKSEVENT_ENTRY  EventEntry;

            //
            // remove the entry from the list, and add it to the dead list.
            // note
            // that we are already at the correct sync level to do this.
            //

            EventEntry = va_arg(Arguments, PKSEVENT_ENTRY);
            RemoveEntryList(&EventEntry->ListEntry);

            InsertTailList(&DeviceExtension->DeadEventList,
                           &EventEntry->ListEntry);

        }
        break;

    default:

        ASSERT(0);
    }

    va_end(Arguments);

    END_MINIDRIVER_STREAM_CALLIN(StreamObject, &Irql);

}                               // end StreamClassStreamNotification()



VOID
StreamClassDeviceNotification(
             IN STREAM_MINIDRIVER_DEVICE_NOTIFICATION_TYPE NotificationType,
                              IN PVOID HwDeviceExtension,
                              ...
)
/*++

Routine Description:

  device notification routine for minidriver

Arguments:

  NotificationType - indicates what has happened
  HwDeviceExtension - address of minidriver's device extension

Return Value:

  none

--*/

{
    va_list         Arguments;
    PSTREAM_REQUEST_BLOCK SRB;
    PDEVICE_EXTENSION DeviceExtension =
    (PDEVICE_EXTENSION) HwDeviceExtension - 1;

    KIRQL           Irql;

    va_start(Arguments, HwDeviceExtension);

    ASSERT(HwDeviceExtension != NULL);

    #if DBG
    if (DeviceExtension->NoSync) {

        ASSERT(KeGetCurrentIrql() <= DISPATCH_LEVEL);

    }                           // if nosync
    #endif

    BEGIN_MINIDRIVER_DEVICE_CALLIN(DeviceExtension, &Irql);

    switch (NotificationType) {

    case ReadyForNextDeviceRequest:

        //
        // Start next control packet on adapter's device queue.
        //

        DebugPrint((DebugLevelTrace, "'StreamClassDeviceNotify: ready for next stream.\n"));
        ASSERT(!(DeviceExtension->ReadyForNextReq));
        ASSERT(!(DeviceExtension->NoSync));
        DeviceExtension->ReadyForNextReq = TRUE;
        break;

    case DeviceRequestComplete:

        SRB = CONTAINING_RECORD(va_arg(Arguments, PHW_STREAM_REQUEST_BLOCK),
                                STREAM_REQUEST_BLOCK,
                                HwSRB);

        DebugPrint((DebugLevelTrace, "'StreamClassDeviceNotify: stream request complete.\n"));
        ASSERT(SRB->HwSRB.Status != STATUS_PENDING);
        ASSERT(SRB->Flags & SRB_FLAGS_IS_ACTIVE);
        ASSERT(!(SRB->HwSRB.Flags & SRB_HW_FLAGS_STREAM_REQUEST));
        ASSERT(!(SRB->HwSRB.Flags & SRB_HW_FLAGS_DATA_TRANSFER));

        //
        // Clear the active flag.
        //

        SRB->Flags &= ~SRB_FLAGS_IS_ACTIVE;

        //
        // add the SRB to the list of completed SRB's.
        //

        SRB->HwSRB.NextSRB = DeviceExtension->ComObj.InterruptData.CompletedSRB;
        DeviceExtension->ComObj.InterruptData.CompletedSRB = &SRB->HwSRB;

        break;

    case SignalMultipleDeviceEvents:
        {

            GUID           *EventGuid = va_arg(Arguments, GUID *);
            ULONG           EventItem = va_arg(Arguments, ULONG);

            //
            // signal all events that match the criteria.  note that we are
            // already
            // at the level required for synchronizing the list, so no lock
            // type is specified.
            //

            PFILTER_INSTANCE FilterInstance;
            
            ASSERT( 0 == DeviceExtension->MinidriverData->
                         HwInitData.FilterInstanceExtensionSize);
                         
            //
            // this is synced should not need to avoid race
            //

            FilterInstance = (PFILTER_INSTANCE)
                              DeviceExtension->FilterInstanceList.Flink;

            if ( (PLIST_ENTRY)FilterInstance == 
                    &DeviceExtension->FilterInstanceList ) {

                DebugPrint((DebugLevelWarning, "Filter Closed\n"));                    
                break;
            }
            
            FilterInstance = CONTAINING_RECORD(FilterInstance,
                                       FILTER_INSTANCE,
                                       NextFilterInstance);
                                       
            KsGenerateEventList(EventGuid,
                                EventItem,
                                &FilterInstance->NotifyList,
                                KSEVENTS_NONE,
                                NULL);
                                
        }
        
        break;
    #if ENABLE_MULTIPLE_FILTER_TYPES
    case SignalMultipleDeviceInstanceEvents:
        {            
            PFILTER_INSTANCE FilterInstance =
                (PFILTER_INSTANCE)va_arg( Arguments, PVOID) -1;
            GUID           *EventGuid = va_arg(Arguments, GUID *);
            ULONG           EventItem = va_arg(Arguments, ULONG);

            //
            // signal all events that match the criteria.  note that we are
            // already
            // at the level required for synchronizing the list, so no lock
            // type is specified.
            //
            
            KsGenerateEventList(EventGuid,
                                EventItem,
                                &FilterInstance->NotifyList,
                                KSEVENTS_NONE,
                                NULL);
        } 
        break;
    #endif // ENABLE_MULTIPLE_FILTER_TYPES

    case SignalDeviceEvent:

        KsGenerateEvent(va_arg(Arguments, PKSEVENT_ENTRY));
        break;


    case DeleteDeviceEvent:
        {

            PKSEVENT_ENTRY  EventEntry;

            //
            // remove the entry from the list, and add it to the dead list.
            // note
            // that we are already at the correct sync level to do this.
            //

            EventEntry = va_arg(Arguments, PKSEVENT_ENTRY);
            RemoveEntryList(&EventEntry->ListEntry);

            InsertTailList(&DeviceExtension->DeadEventList,
                           &EventEntry->ListEntry);

        }
        break;

    default:

        ASSERT(0);
    }

    va_end(Arguments);

    //
    // Request a DPC be queued after the interrupt completes.
    //

    END_MINIDRIVER_DEVICE_CALLIN(DeviceExtension, &Irql);

}                               // end StreamClassDeviceNotification()



VOID
StreamClassScheduleTimer(
                         IN OPTIONAL PHW_STREAM_OBJECT HwStreamObject,
                         IN PVOID HwDeviceExtension,
                         IN ULONG NumberOfMicroseconds,
                         IN PHW_TIMER_ROUTINE TimerRoutine,
                         IN PVOID Context
)
/*++

Routine Description:

  schedules a timer callback for the minidriver

Arguments:

  HwStreamObject - address of minidriver's stream struct
  HwDeviceExtension - address of minidriver's device extension
  NumberOfMicroseconds - # of microseconds that should elapse before calling
  TimerRoutine - routine to call when the time expires
  Context - value to pass into the timer routine

Return Value:

  none

--*/

{
    PSTREAM_OBJECT  StreamObject;
    KIRQL           Irql;
    PDEVICE_EXTENSION DeviceExtension = (PDEVICE_EXTENSION)
    (HwDeviceExtension) - 1;
    PCOMMON_OBJECT  ComObj;

    ASSERT(HwDeviceExtension != NULL);

    StreamObject = CONTAINING_RECORD(
                                     HwStreamObject,
                                     STREAM_OBJECT,
                                     HwStreamObject
        );

    #if DBG
    if (DeviceExtension->NoSync) {

        ASSERT(KeGetCurrentIrql() <= DISPATCH_LEVEL);

    }                           // if nosync
    #endif

    //
    // The driver wants to set the timer.
    // Save the timer parameters.
    //

    BEGIN_MINIDRIVER_STREAM_CALLIN(DeviceExtension, &Irql);

    if (HwStreamObject) {

        ComObj = &StreamObject->ComObj;
        //DebugPrint((DebugLevelVerbose, "'StreamClassScheduleTimer for stream.\n"));

    } else {

        StreamObject = NULL;
        ComObj = &DeviceExtension->ComObj;
        ComObj->InterruptData.Flags |= INTERRUPT_FLAGS_NOTIFICATION_REQUIRED;
        DebugPrint((DebugLevelVerbose, "'StreamClassScheduleTimer for device.\n"));

    }

    //
    // assert that a timer is not scheduled multiple times.
    //

    #if DBG
    if ((ComObj->InterruptData.Flags & INTERRUPT_FLAGS_TIMER_CALL_REQUEST) &&
        ((NumberOfMicroseconds != 0) && (ComObj->InterruptData.HwTimerValue
                                         != 0))) {

        DebugPrint((DebugLevelFatal, "Stream Minidriver scheduled same timer twice!\n"));
        DEBUG_BREAKPOINT();
        ASSERT(1 == 0);
    }                           // if scheduled twice
    #endif

    ComObj->InterruptData.Flags |= INTERRUPT_FLAGS_TIMER_CALL_REQUEST;
    ComObj->InterruptData.HwTimerRoutine = TimerRoutine;
    ComObj->InterruptData.HwTimerValue = NumberOfMicroseconds;
    ComObj->InterruptData.HwTimerContext = Context;

    if (StreamObject) {
        END_MINIDRIVER_STREAM_CALLIN(StreamObject, &Irql);

    } else {

        END_MINIDRIVER_DEVICE_CALLIN(DeviceExtension, &Irql);
    }                           // if streamobject
}



VOID
StreamClassCallAtNewPriority(
                             IN OPTIONAL PHW_STREAM_OBJECT HwStreamObject,
                             IN PVOID HwDeviceExtension,
                             IN STREAM_PRIORITY Priority,
                             IN PHW_PRIORITY_ROUTINE PriorityRoutine,
                             IN PVOID Context
)
/*++

Routine Description:

  schedules a callback at the specified priority

Arguments:

  HwStreamObject - address of minidriver's stream struct
  HwDeviceExtension - address of minidriver's device extension
  Priority - priority at which to call minidriver
  PriorityRoutine - routine to call at specified priority
  Context - value to pass into the priority routine

Return Value:

  none

--*/

{
    PSTREAM_OBJECT  StreamObject;
    KIRQL           Irql;
    PDEVICE_EXTENSION DeviceExtension = (PDEVICE_EXTENSION)
    (HwDeviceExtension) - 1;
    PCOMMON_OBJECT  ComObj;

    ASSERT(HwDeviceExtension != NULL);

    StreamObject = CONTAINING_RECORD(
                                     HwStreamObject,
                                     STREAM_OBJECT,
                                     HwStreamObject
        );

    //
    // The driver wants to get called back at a different priority.
    // Save the priority parameters.
    //

    if (Priority == LowToHigh) {

        //
        // the minidriver wishes to be called from low priority to high
        // we must call it directly from this routine as we cannot use
        // the interruptcontext structure due to the possibility of
        // reentrancy.
        //


        DebugPrint((DebugLevelVerbose, "'StreamClassChangePriority LowToHigh.\n"));
        ASSERT(KeGetCurrentIrql() < DISPATCH_LEVEL);

        KeAcquireSpinLock(&DeviceExtension->SpinLock, &Irql);

        DeviceExtension->SynchronizeExecution(
                                           DeviceExtension->InterruptObject,
                                              (PVOID) PriorityRoutine,
                                              Context);

        KeReleaseSpinLockFromDpcLevel(&DeviceExtension->SpinLock);


        //
        // Call the DPC directly to check for work.
        //

        StreamClassDpc(NULL,
                       DeviceExtension->DeviceObject,
                       NULL,
                       NULL);

        KeLowerIrql(Irql);

    } else {

        if (HwStreamObject) {

            DebugPrint((DebugLevelVerbose, "'StreamClassChangePriority to %x for stream %x\n",
                        StreamObject->ComObj.InterruptData.HwPriorityLevel, StreamObject->HwStreamObject.StreamNumber));
            ComObj = &StreamObject->ComObj;
            SCRequestDpcForStream(StreamObject);

        } else {

            DebugPrint((DebugLevelVerbose, "'StreamClassChangePriority for device.\n"));
            ComObj = &DeviceExtension->ComObj;
            ComObj->InterruptData.Flags |= INTERRUPT_FLAGS_NOTIFICATION_REQUIRED;

        }                       // if streamobject

        #if DBG
        if ((ComObj->InterruptData.Flags &
            INTERRUPT_FLAGS_PRIORITY_CHANGE_REQUEST) || 
             ((ComObj->PriorityWorkItemScheduled) && (Priority == Low))) {

            DebugPrint((DebugLevelFatal, "Stream Minidriver scheduled priority twice!\n"));
            DEBUG_BREAKPOINT();
            ASSERT(1 == 0);
        }                       // if scheduled twice

        ComObj->PriorityWorkItemScheduled = TRUE;

        #endif

        ComObj->InterruptData.Flags |= INTERRUPT_FLAGS_PRIORITY_CHANGE_REQUEST;
        ComObj->InterruptData.HwPriorityLevel = Priority;
        ComObj->InterruptData.HwPriorityRoutine = PriorityRoutine;
        ComObj->InterruptData.HwPriorityContext = Context;
    }                           // if lowtohigh

}

VOID
StreamClassLogError(
                    IN PVOID HwDeviceExtension,
                    IN PHW_STREAM_REQUEST_BLOCK hwSRB OPTIONAL,
                    IN ULONG ErrorCode,
                    IN ULONG UniqueId
)
/*++

Routine Description:

    This routine saves the error log information, and queues a DPC if necessary.

Arguments:

    HwDeviceExtension - Supplies the HBA miniport driver's adapter data storage.

    SRB - Supplies an optional pointer to SRB if there is one.

    ErrorCode - Supplies an error code indicating the type of error.

    UniqueId - Supplies a unique identifier for the error.

Return Value:

    None.

--*/

{
    PDEVICE_EXTENSION deviceExtension =
    ((PDEVICE_EXTENSION) HwDeviceExtension) - 1;
    PDEVICE_OBJECT  DeviceObject = deviceExtension->DeviceObject;
    PERROR_LOG_ENTRY errorLogEntry;
    PSTREAM_REQUEST_BLOCK SRB;
    KIRQL           Irql;

    //
    // If the error log entry is already full, then dump the error.
    //

    DEBUG_BREAKPOINT();
    ASSERT(HwDeviceExtension != NULL);
    BEGIN_MINIDRIVER_DEVICE_CALLIN(deviceExtension, &Irql);

    DebugPrint((DebugLevelError, "StreamClassLogError.\n"));
    if (deviceExtension->ComObj.InterruptData.Flags & INTERRUPT_FLAGS_LOG_ERROR) {
        DEBUG_BREAKPOINT();
        DebugPrint((1, "'StreamClassLogError: Ignoring error log packet.\n"));
        return;
    }
    //
    // Save the error log data in the log entry.
    //

    errorLogEntry = &deviceExtension->ComObj.InterruptData.LogEntry;
    errorLogEntry->ErrorCode = ErrorCode;
    errorLogEntry->UniqueId = UniqueId;

    //
    // Get the sequence number from the SRB.
    //

    if (hwSRB != NULL) {

        DEBUG_BREAKPOINT();
        SRB = CONTAINING_RECORD(hwSRB,
                                STREAM_REQUEST_BLOCK,
                                HwSRB);
        errorLogEntry->SequenceNumber = SRB->SequenceNumber;
    } else {

        DEBUG_BREAKPOINT();
        errorLogEntry->SequenceNumber = 0;
    }

    //
    // Indicate that the error log entry is in use and that a
    // notification
    // is required.
    //

    deviceExtension->ComObj.InterruptData.Flags |= INTERRUPT_FLAGS_LOG_ERROR;

    END_MINIDRIVER_DEVICE_CALLIN(deviceExtension, &Irql);

    return;

}                               // end StreamClassLogError()


#if DBG


VOID
StreamClassDebugPrint(
                      STREAM_DEBUG_LEVEL DebugPrintLevel,
                      PSCHAR DebugMessage,
                      ...
)
/*++

Routine Description:

    Debug print routine

Arguments:

    DebugPrintLevel - Debug print level
    DebugMessage - message to print


Return Value:

    None

--*/

{
    va_list         ap;

    va_start(ap, DebugMessage);

    if (DebugPrintLevel <= (INT) StreamDebug) {

        _vsnprintf(StreamBuffer, STREAM_BUFFER_SIZE-1, DebugMessage, ap);

        DbgPrint(StreamBuffer);
    }
    va_end(ap);

}                               // end StreamClassDebugPrint()

#else

//
// StreamClassDebugPrint stub
//

VOID
StreamClassDebugPrint(
                      STREAM_DEBUG_LEVEL DebugPrintLevel,
                      PSCHAR DebugMessage,
                      ...
)
{
}

#endif




STREAM_PHYSICAL_ADDRESS
StreamClassGetPhysicalAddress(
                              IN PVOID HwDeviceExtension,
                              IN PHW_STREAM_REQUEST_BLOCK HwSRB OPTIONAL,
                              IN PVOID VirtualAddress,
                              IN STREAM_BUFFER_TYPE Type,
                              OUT ULONG * Length
)
/*++

Routine Description:

    Convert virtual address to physical address for DMA.

Arguments:

    HwDeviceExtension - Supplies the HBA miniport driver's adapter data storage.
    HwSRB - Supplies an optional pointer to SRB if there is one.
    VirtualAddress - pointer to address for which to retrieve physical address
    Type - type of buffer in VirtualAddress

Return Value:

    Returns phys address and length or NULL if invalid address

--*/

{
    PDEVICE_EXTENSION deviceExtension = ((PDEVICE_EXTENSION) HwDeviceExtension) - 1;
    PKSSTREAM_HEADER CurrentHeader;
    PKSSCATTER_GATHER ScatterList;
    PSTREAM_REQUEST_BLOCK SRB;
    ULONG           VirtualOffset;
    PHYSICAL_ADDRESS address;
    ULONG           NumberOfBuffers,
                    i,
                    SizeSoFar = 0,
                    ListSize = 0;
    ULONG           DataBytes;
    PHW_STREAM_OBJECT HwStreamObject;

    ASSERT(HwDeviceExtension != NULL);

    switch (Type) {

    case PerRequestExtension:

        ASSERT(HwSRB);
        SRB = CONTAINING_RECORD((PHW_STREAM_REQUEST_BLOCK) HwSRB,
                                STREAM_REQUEST_BLOCK,
                                HwSRB);

        VirtualOffset = (ULONG) ((ULONG_PTR) VirtualAddress - (ULONG_PTR) (SRB + 1));
        *Length = SRB->ExtensionLength - VirtualOffset;
        address.QuadPart = SRB->PhysicalAddress.QuadPart +
            sizeof(STREAM_REQUEST_BLOCK) +
            VirtualOffset;

        return (address);

    case DmaBuffer:
        VirtualOffset = (ULONG) ((ULONG_PTR) VirtualAddress - (ULONG_PTR) deviceExtension->DmaBuffer);
        *Length = deviceExtension->DmaBufferLength - VirtualOffset;
        address.QuadPart = deviceExtension->DmaBufferPhysical.QuadPart
            + VirtualOffset;

        return (address);

    case SRBDataBuffer:
        ASSERT(HwSRB);

        SRB = CONTAINING_RECORD((PHW_STREAM_REQUEST_BLOCK) HwSRB,
                                STREAM_REQUEST_BLOCK,
                                HwSRB);

        HwStreamObject = SRB->HwSRB.StreamObject;
        ASSERT(HwStreamObject);

        CurrentHeader = SRB->HwSRB.CommandData.DataBufferArray;

        NumberOfBuffers = SRB->HwSRB.NumberOfBuffers;

        for (i = 0; i < NumberOfBuffers; i++) {

            if (SRB->HwSRB.Command == SRB_WRITE_DATA) {

                DataBytes = CurrentHeader->DataUsed;

            } else {            // if write

                DataBytes = CurrentHeader->FrameExtent;

            }                   // if write


            //
            // see if the buffer is within the range of this element
            //

            VirtualOffset = (ULONG) ((ULONG_PTR) VirtualAddress - (ULONG_PTR) CurrentHeader->Data + 1);
            if (VirtualOffset > DataBytes) {

                //
                // buffer not within this element.  add the size of this one
                // to our total.
                //

                SizeSoFar += DataBytes;

            } else {

                //
                // we've found the element.  Now calculate the phys
                // address from the phys list.
                //
                // GUBGUB - This function is seldom called. n is most ofen small
                // <=3. The O(n^2) performance concern is insignificant.
                // - this algorithm gets n^2 expensive for long lists
                // an alternative is to build a separate array which holds
                // the mapping between the stream headers and the s/g
                // elements
                // for each header.  We currently don't get that many
                // elements
                // so the below is more efficient now.
                //

                ScatterList = SRB->HwSRB.ScatterGatherBuffer;

                while (SizeSoFar > ListSize) {

                    ListSize += ScatterList++->Length;
                }

                //
                // Now ScatterList points to the correct scatter/gather
                // element.
                //


                while (VirtualOffset > ScatterList->Length) {
                    VirtualOffset -= ScatterList->Length;
                    ScatterList++;
                }

                *Length = ScatterList->Length - VirtualOffset + 1;
                address.QuadPart = ScatterList->PhysicalAddress.QuadPart
                    + VirtualOffset - 1;
                return (address);
            }                   // if buffer

            CurrentHeader = ((PKSSTREAM_HEADER) ((PBYTE) CurrentHeader +
                                 HwStreamObject->StreamHeaderMediaSpecific +
                                    HwStreamObject->StreamHeaderWorkspace));

        }                       // for # buffers

        DebugPrint((DebugLevelFatal, "StreamClassGetPhysicalAddress: address not in SRB!\n"));

    default:
        DEBUG_BREAKPOINT();
        *Length = 0;
        address.QuadPart = (LONGLONG) 0;
        return (address);

    }                           // switch

}                               // end StreamClassGetPhysicalAddress()

VOID
StreamClassDebugAssert(
                       IN PCHAR File,
                       IN ULONG Line,
                       IN PCHAR AssertText,
                       IN ULONG AssertValue
)
/*++

Routine Description:

    This is the minidriver debug assert call.  When running a checked version
    of the class driver, asserts are recognized resulting in a debug
    message and breakpoint.  When running a free version of the port driver,
    asserts are ignored.

Arguments:
    File - file name where assert occurred
    Line - line number of assert
    AssertText - Text to be printed
    AssertValue - value to be printed

Return Value:

    none

--*/
{
    DebugPrint((DebugLevelError, "(%s:%d) Assert failed (%s)=0x%x\n", File, Line, AssertText, AssertValue));
    DbgBreakPoint();
}



VOID
SCRequestDpcForStream(
                      IN PSTREAM_OBJECT StreamObject

)
/*++

Routine Description:

    This routine places a stream object on the NeedyStream queue if it is
    not already there

Arguments:

    StreamObject - pointer to stream object

Return Value:

    none

--*/
{
    PDEVICE_EXTENSION DeviceExtension = StreamObject->DeviceExtension;

    //
    // add the stream to the queue of needy streams unless it is already
    // there.
    //

    #if DBG
    if (DeviceExtension->NeedyStream) {

        ASSERT(DeviceExtension->NeedyStream->OnNeedyQueue);
    }
    #endif

    ASSERT(StreamObject->NextNeedyStream != StreamObject);

    if (!(StreamObject->OnNeedyQueue)) {

        ASSERT(!StreamObject->NextNeedyStream);

        DebugPrint((DebugLevelVerbose, "'SCRequestDpc: Stream %x added to needy queue, Next = %x\n",
                    StreamObject, StreamObject->NextNeedyStream));

        StreamObject->OnNeedyQueue = TRUE;
        StreamObject->NextNeedyStream = DeviceExtension->NeedyStream;
        DeviceExtension->NeedyStream = StreamObject;

        ASSERT(StreamObject->NextNeedyStream != StreamObject);

    } else {

        DebugPrint((DebugLevelVerbose, "'SCRequestDpc: Stream %x already on needy queue\n",
                    StreamObject));
    }                           // if on needy queue

    StreamObject->ComObj.InterruptData.Flags |= INTERRUPT_FLAGS_NOTIFICATION_REQUIRED;

}



VOID
StreamClassAbortOutstandingRequests(
                                    IN PVOID HwDeviceExtension,
                                    IN PHW_STREAM_OBJECT HwStreamObject,
                                    IN NTSTATUS Status
)
/*++

Routine Description:

  aborts outstanding requests on the specified device or stream

Arguments:

  HwStreamObject - address of minidriver's stream struct
  HwDeviceExtension - device extension
  Status - NT Status to use for aborting

Return Value:

  none

--*/

{
    PSTREAM_OBJECT  StreamObject = NULL;
    PDEVICE_EXTENSION DeviceExtension =
    (PDEVICE_EXTENSION) HwDeviceExtension - 1;
    KIRQL           Irql;
    PLIST_ENTRY     SrbEntry,
                    ListEntry;
    PSTREAM_REQUEST_BLOCK CurrentSrb;
    PHW_STREAM_OBJECT CurrentHwStreamObject;
    PSTREAM_OBJECT  CurrentStreamObject;

    ASSERT(HwDeviceExtension != NULL);

    #if DBG
    if (DeviceExtension->NoSync) {

        ASSERT(KeGetCurrentIrql() <= DISPATCH_LEVEL);

    }                           // if nosync
    #endif

    if (HwStreamObject) {

        DEBUG_BREAKPOINT();
        StreamObject = CONTAINING_RECORD(HwStreamObject,
                                         STREAM_OBJECT,
                                         HwStreamObject);
    }
    BEGIN_MINIDRIVER_DEVICE_CALLIN(DeviceExtension, &Irql);

    DebugPrint((DebugLevelError, "StreamClassAbortOutstandingRequests.\n"));

    //
    // walk the outstanding queue and abort all requests on it.
    //

    SrbEntry = ListEntry = &DeviceExtension->OutstandingQueue;

    while (SrbEntry->Flink != ListEntry) {

        SrbEntry = SrbEntry->Flink;

        //
        // follow the link to the Srb
        //

        CurrentSrb = CONTAINING_RECORD(SrbEntry,
                                       STREAM_REQUEST_BLOCK,
                                       SRBListEntry);

        CurrentHwStreamObject = CurrentSrb->HwSRB.StreamObject;

        if ((!HwStreamObject) || (CurrentHwStreamObject ==
                                  HwStreamObject)) {


            //
            // abort this one and show that it's ready for a next request,
            // assuming it's active.  it might not be active if the
            // minidriver
            // just called it back.
            //

            if (CurrentSrb->Flags & SRB_FLAGS_IS_ACTIVE) {

                //
                // Clear the active flag.
                //

                CurrentSrb->Flags &= ~SRB_FLAGS_IS_ACTIVE;

                CurrentSrb->HwSRB.Status = Status;

                if (CurrentSrb->HwSRB.Flags & SRB_HW_FLAGS_STREAM_REQUEST) {

                    CurrentStreamObject = CONTAINING_RECORD(
                                                      CurrentHwStreamObject,
                                                            STREAM_OBJECT,
                                                            HwStreamObject
                        );
                    //
                    // indicate that the appropriate queue is ready for a
                    // next
                    // request.
                    //

                    if (CurrentSrb->HwSRB.Flags & SRB_HW_FLAGS_DATA_TRANSFER) {

                        CurrentStreamObject->ReadyForNextDataReq = TRUE;

                    } else {    // if data

                        CurrentStreamObject->ReadyForNextControlReq = TRUE;
                    }           // if data

                    DebugPrint((DebugLevelTrace, "'SCAbort: aborting stream IRP %x\n",
                                CurrentSrb->HwSRB.Irp));

                    //
                    // add the SRB to the list of completed stream SRB's.
                    //

                    CurrentSrb->HwSRB.NextSRB = CurrentStreamObject->ComObj.InterruptData.CompletedSRB;
                    CurrentStreamObject->ComObj.InterruptData.CompletedSRB = &CurrentSrb->HwSRB;

                    //
                    // add this stream to the queue of needy streams
                    //

                    SCRequestDpcForStream(CurrentStreamObject);

                } else {        // if stream

                    DebugPrint((DebugLevelTrace, "'SCAbort: aborting device IRP %x\n",
                                CurrentSrb->HwSRB.Irp));

                    //
                    // add the SRB to the list of completed device SRB's.
                    //

                    DEBUG_BREAKPOINT();
                    CurrentSrb->HwSRB.NextSRB = DeviceExtension->ComObj.InterruptData.CompletedSRB;
                    DeviceExtension->ComObj.InterruptData.CompletedSRB = &CurrentSrb->HwSRB;

                    DeviceExtension->ReadyForNextReq = TRUE;

                }               // if stream

            }                   // if active
        }                       // if aborting this one
    }                           // while list entry

    //
    // all necessary requests have been aborted.  exit.
    //

    END_MINIDRIVER_DEVICE_CALLIN(DeviceExtension, &Irql);
}


PKSEVENT_ENTRY
StreamClassGetNextEvent(
                        IN PVOID HwInstanceExtension_OR_HwDeviceExtension,
                        IN OPTIONAL PHW_STREAM_OBJECT HwStreamObject,
                        IN OPTIONAL GUID * EventGuid,
                        IN OPTIONAL ULONG EventItem,
                        IN OPTIONAL PKSEVENT_ENTRY CurrentEvent
)
/*++

Routine Description:

Arguments:
    HwInstanceExtenion: was HwDeviceExtension. But we now support multiinstances.
    Therefore, we need the HwInstanceExtension instead for MF.

    CurrentEvent - event (if any) to get the next from

Return Value:

  next event, if any

--*/

{

    PSTREAM_OBJECT  StreamObject = CONTAINING_RECORD(HwStreamObject,
                                                     STREAM_OBJECT,
                                                     HwStreamObject);

    PFILTER_INSTANCE FilterInstance;    
    PDEVICE_EXTENSION DeviceExtension;
    
    //(PDEVICE_EXTENSION) HwDeviceExtension - 1;
    PLIST_ENTRY     EventListEntry,
                    EventEntry;
    PKSEVENT_ENTRY  NextEvent,
                    ReturnEvent = NULL;
    KIRQL           Irql;

    //
    // see which is HwInstanceExtension_OR_HwDeviceExtension
    // need to try HwInstanceExtension first because is has a smaller
    // offset backward so we don't touch invalid memory.
    //
    // try
    FilterInstance = (PFILTER_INSTANCE) 
                     HwInstanceExtension_OR_HwDeviceExtension-1;
                     
    if ( SIGN_FILTER_INSTANCE != FilterInstance->Signature ) {
        //
        // single instance legacy driver
        //    
        DeviceExtension = (PDEVICE_EXTENSION)
                          HwInstanceExtension_OR_HwDeviceExtension -1;
                          
        ASSERT( 0 == DeviceExtension->MinidriverData->
                     HwInitData.FilterInstanceExtensionSize);

        if (DeviceExtension->NoSync) {
            KeAcquireSpinLock(&DeviceExtension->SpinLock, &Irql);
        }

        if ( IsListEmpty( &DeviceExtension->FilterInstanceList ) ) {
			//
			// filter has been closed. but we are called. 
			// Single instance drivers do not receive open/close
			// they don't know when to sotp calling this. 
			// We need to check.
			//
			DebugPrint((DebugLevelWarning, "GetNextEvent no open filters\n"));
			
            if (DeviceExtension->NoSync) {
                KeReleaseSpinLock(&DeviceExtension->SpinLock, Irql);
            }
            
			return NULL;
		}
		

        FilterInstance = (PFILTER_INSTANCE)
                         DeviceExtension->FilterInstanceList.Flink;

        if (DeviceExtension->NoSync) {
            KeReleaseSpinLock(&DeviceExtension->SpinLock, Irql);
        }
                                           
        FilterInstance = CONTAINING_RECORD(FilterInstance,
                                           FILTER_INSTANCE,
                                           NextFilterInstance);
    }
    
    else {
        DeviceExtension = FilterInstance ->DeviceExtension;        
    }
    
    #if DBG
    if (DeviceExtension->NoSync) {

        ASSERT(KeGetCurrentIrql() <= DISPATCH_LEVEL);

    }
    
    #endif
    //
    // take the spinlock if we are unsynchronized.
    //

    BEGIN_MINIDRIVER_DEVICE_CALLIN(DeviceExtension, &Irql);

    //
    // loop thru the events, trying to find the requested one.
    //

    if (HwStreamObject) {

        EventListEntry = EventEntry = &StreamObject->NotifyList;

    } else { 
    
        EventListEntry = EventEntry = &FilterInstance->NotifyList;
    }

    while (EventEntry->Flink != EventListEntry) {

        EventEntry = EventEntry->Flink;
        NextEvent = CONTAINING_RECORD(EventEntry,
                                      KSEVENT_ENTRY,
                                      ListEntry);


        if ((EventItem == NextEvent->EventItem->EventId) &&
            (!EventGuid || IsEqualGUIDAligned(EventGuid, NextEvent->EventSet->Set))) {

            //
            // if we are to return the 1st event which matches, break.
            //

            if (!CurrentEvent) {

                ReturnEvent = NextEvent;
                break;

            }                   // if !current
            //
            // if we are to return the next event after the specified one,
            // check
            // to see if these match.   If they do, zero the specified event
            // so
            // that we will return the next event of the specified type.
            //

            if (CurrentEvent == NextEvent) {
                CurrentEvent = NULL;

            }                   // if cur=next
        }                       // if guid & id match
    }                           // while events

    //
    // if we are unsynchronized, release the spinlock acquired in the macro
    // above.
    //

    ASSERT(--DeviceExtension->LowerApiThreads == 0); // typo barfs. but this is truely ok

    if (DeviceExtension->NoSync) {

        KeReleaseSpinLock(&DeviceExtension->SpinLock, Irql);
    }
    //
    // return the next event, if any.
    //

    return (ReturnEvent);
}


VOID
StreamClassQueryMasterClock(
                            IN PHW_STREAM_OBJECT HwStreamObject,
                            IN HANDLE MasterClockHandle,
                            IN TIME_FUNCTION TimeFunction,
                            IN PHW_QUERY_CLOCK_ROUTINE ClockCallbackRoutine
)
/*++

Routine Description:

Arguments:

  HwStreamObject - address of minidriver's stream struct
  Context - value to pass into the time callback routine

Return Value:

  none

--*/

{

    PSTREAM_OBJECT  StreamObject = CONTAINING_RECORD(HwStreamObject,
                                                     STREAM_OBJECT,
                                                     HwStreamObject);

    PDEVICE_EXTENSION DeviceExtension =
    (PDEVICE_EXTENSION) StreamObject->DeviceExtension;
    KIRQL           Irql;

    #if DBG
    if (DeviceExtension->NoSync) {

        ASSERT(KeGetCurrentIrql() <= DISPATCH_LEVEL);

    }                           // if nosync
    #endif

    BEGIN_MINIDRIVER_STREAM_CALLIN(DeviceExtension, &Irql);

    //
    // save away the parameters for the clock query.  The DPC will do the
    // actual processing.
    //

    StreamObject->ComObj.InterruptData.HwQueryClockRoutine = ClockCallbackRoutine;
    StreamObject->ComObj.InterruptData.HwQueryClockFunction = TimeFunction;

    StreamObject->ComObj.InterruptData.Flags |= INTERRUPT_FLAGS_CLOCK_QUERY_REQUEST;


    END_MINIDRIVER_STREAM_CALLIN(StreamObject, &Irql);
}

#if ENABLE_MULTIPLE_FILTER_TYPES
VOID
StreamClassFilterReenumerateStreams(
    IN PVOID HwInstanceExtension,
    IN ULONG StreamDescriptorSize )
/*++

    Description:

        Reenumerates all streams on the filter instance.
        This is used to increase the number of pins exposed to
        the world so that application can make connections on
        new streams exposed. It's caller's responsibility
        not to change the order of the streams that have been
        open ( connected ). If there is no reduction of the streams
        This won't be an issue.

    Arguments;

        HwInstanceExtension:
            The instanc extension pointer we gave to the mini driver

        StreamDecriptorSize:
            # of bytes to contain the new stream descriptor for the filter

    Return Valuse:

        None    
--*/
{
    PFILTER_INSTANCE    FilterInstance;
    PDEVICE_EXTENSION   DeviceExtension; 
    KIRQL               Irql;

    FilterInstance = ( PFILTER_INSTANCE ) HwInstanceExtension -1;
    DeviceExtension = FilterInstance->DeviceExtension;
    
    //
    // take the spinlock if we are unsynchronized.
    //

    #if DBG
    if (DeviceExtension->NoSync) {

        ASSERT(KeGetCurrentIrql() <= DISPATCH_LEVEL);

    }
    #   endif

    BEGIN_MINIDRIVER_DEVICE_CALLIN(DeviceExtension, &Irql);

    //
    // show that we need to rescan the stream info, and set the new size in
    // the config info structure.
    //

    DeviceExtension->ComObj.InterruptData.Flags |=
        INTERRUPT_FLAGS_NEED_STREAM_RESCAN;

    InterlockedExchange( &FilterInstance->NeedReenumeration, 1 );
    FilterInstance->StreamDescriptorSize = StreamDescriptorSize;

    //
    // queue a DPC to service the request.
    //

    END_MINIDRIVER_DEVICE_CALLIN(DeviceExtension, &Irql);
    return;
}
#endif // ENABLE_MULTIPLE_FILTER_TYPES

VOID
StreamClassReenumerateStreams(
                              IN PVOID HwDeviceExtension,
                              IN ULONG StreamDescriptorSize
)
/*++

Routine Description:

    Reenumerates all streams on the device

Arguments:

    HwDeviceExtension - pointer to minidriver's device extension
    StreamDescriptorSize - size of the buffer needed by the minidriver to
     hold the stream info.

Return Value:

    none

--*/

{

    PDEVICE_EXTENSION DeviceExtension =
    (PDEVICE_EXTENSION) HwDeviceExtension - 1;
    KIRQL           Irql;

    //
    // take the spinlock if we are unsynchronized.
    //

    TRAP;
    #if DBG
    if (DeviceExtension->NoSync) {

        ASSERT(KeGetCurrentIrql() <= DISPATCH_LEVEL);

    }                           // if nosync
    #endif

    BEGIN_MINIDRIVER_DEVICE_CALLIN(DeviceExtension, &Irql);

    //
    // show that we need to rescan the stream info, and set the new size in
    // the config info structure.
    //

    ASSERT(!DeviceExtension->ComObj.InterruptData.Flags &
           INTERRUPT_FLAGS_NEED_STREAM_RESCAN);

    DeviceExtension->ComObj.InterruptData.Flags |=
        INTERRUPT_FLAGS_NEED_STREAM_RESCAN;
    DeviceExtension->ConfigurationInformation->StreamDescriptorSize =
        StreamDescriptorSize;

    //
    // queue a DPC to service the request.
    //

    END_MINIDRIVER_DEVICE_CALLIN(DeviceExtension, &Irql);
    return;
}



#define FCC(ch4) ((((DWORD)(ch4) & 0xFF) << 24) |     \
                  (((DWORD)(ch4) & 0xFF00) << 8) |    \
                  (((DWORD)(ch4) & 0xFF0000) >> 8) |  \
                  (((DWORD)(ch4) & 0xFF000000) >> 24))

// OK to have zero instances of pin In this case you will have to
// Create a pin to have even one instance
#define REG_PIN_B_ZERO 0x1

// The filter renders this input
#define REG_PIN_B_RENDERER 0x2

// OK to create many instance of  pin
#define REG_PIN_B_MANY 0x4

// This is an Output pin
#define REG_PIN_B_OUTPUT 0x8

typedef struct {
    ULONG           Version;
    ULONG           Merit;
    ULONG           Pins;
    ULONG           Reserved;
}               REGFILTER_REG;

typedef struct {
    ULONG           Signature;
    ULONG           Flags;
    ULONG           PossibleInstances;
    ULONG           MediaTypes;
    ULONG           MediumTypes;
    ULONG           CategoryOffset;
    ULONG           MediumOffset;   // By definition, we always have a Medium
    //#ifdef _WIN64
    //This method create filterdata that upset ring3 code.
    //ULONG           ulPad;        // align to quadword to make ia64 happy
    //#endif
}               REGFILTERPINS_REG2;


NTSTATUS
StreamClassRegisterFilterWithNoKSPins(
                                      IN PDEVICE_OBJECT DeviceObject,
                                      IN const GUID * InterfaceClassGUID,
                                      IN ULONG PinCount,
                                      IN BOOL * PinDirection,
                                      IN KSPIN_MEDIUM * MediumList,
                                      IN OPTIONAL GUID * CategoryList
)
/*++

Routine Description:

    This routine is used to register filters with DShow which have no
    KS pins and therefore do not stream in kernel mode.  This is typically
    used for TvTuners, Crossbars, and the like.  On exit, a new binary
    registry key, "FilterData" is created which contains the Mediums and
    optionally the Categories for each pin on the filter.

Arguments:

    DeviceObject -
           Device object

    InterfaceClassGUID
           GUID representing the class to register

    PinCount -
           Count of the number of pins on this filter

    PinDirection -
           Array of BOOLS indicating pin direction for each pin (length PinCount)
           If TRUE, this pin is an output pin

    MediumList -
           Array of PKSMEDIUM_DATA (length PinCount)

    CategoryList -
           Array of GUIDs indicating pin categories (length PinCount) OPTIONAL


Return Value:

    NTSTATUS SUCCESS if the Blob was created

--*/
{
    NTSTATUS        Status;
    ULONG           CurrentPin;
    ULONG           TotalCategories;
    REGFILTER_REG  *RegFilter;
    REGFILTERPINS_REG2 UNALIGNED * RegPin;
    GUID            UNALIGNED * CategoryCache;
    KSPIN_MEDIUM    UNALIGNED * MediumCache;
    ULONG           FilterDataLength;
    PUCHAR          FilterData;
    PWSTR           SymbolicLinkList;

    ASSERT(KeGetCurrentIrql() < DISPATCH_LEVEL);

    if ((PinCount == 0) || (!InterfaceClassGUID) || (!PinDirection) || (!MediumList)) {
        return STATUS_INVALID_DEVICE_REQUEST;
    }
    //
    // Calculate the maximum amount of space which could be taken up by
    // this cache data.
    //
    
    TotalCategories = (CategoryList ? PinCount : 0);

    FilterDataLength = sizeof(REGFILTER_REG) +
        PinCount * sizeof(REGFILTERPINS_REG2) +
        PinCount * sizeof(KSPIN_MEDIUM) +
        TotalCategories * sizeof(GUID);
    //
    // Allocate space to create the BLOB
    //

    FilterData = ExAllocatePool(PagedPool, FilterDataLength);
    if (!FilterData) {
        return STATUS_INSUFFICIENT_RESOURCES;
    }
    //
    // Place the header in the data, defaulting the Merit to "unused".
    //

    DebugPrint((DebugLevelTrace,
                "FilterData:%p\n",
                FilterData ));

    RegFilter = (REGFILTER_REG *) FilterData;
    RegFilter->Version = 2;
    RegFilter->Merit = 0x200000;
    RegFilter->Pins = PinCount;
    RegFilter->Reserved = 0;

    //
    // Calculate the offset to the list of pins, and to the
    // MediumList and CategoryList
    //

    RegPin = (REGFILTERPINS_REG2 *) (RegFilter + 1);
    MediumCache = (PKSPIN_MEDIUM) ((PUCHAR) (RegPin + PinCount));
    CategoryCache = (GUID *) (MediumCache + PinCount);

    //
    // Create each pin header, followed by the list of Mediums
    // followed by the list of optional categories.
    //

    for (CurrentPin = 0; CurrentPin < PinCount; CurrentPin++, RegPin++) {

        //
        // Initialize the pin header.
        //
        
        DebugPrint((DebugLevelTrace,
                    "CurrentPin:%d RegPin:%p MediumCache:%p CategoryCache:%p\n",
                    CurrentPin, RegPin, MediumCache, CategoryCache ));
                    
        RegPin->Signature = FCC('0pi3');
        (*(PUCHAR) & RegPin->Signature) += (BYTE) CurrentPin;
        RegPin->Flags = (PinDirection[CurrentPin] ? REG_PIN_B_OUTPUT : 0);
        RegPin->PossibleInstances = 1;
        RegPin->MediaTypes = 0;
        RegPin->MediumTypes = 1;
        RegPin->MediumOffset = (ULONG) ((PUCHAR) MediumCache - (PUCHAR) FilterData);

        *MediumCache++ = MediumList[CurrentPin];

        if (CategoryList) {
            RegPin->CategoryOffset = (ULONG) ((PUCHAR) CategoryCache - (PUCHAR) FilterData);
            *CategoryCache++ = CategoryList[CurrentPin];
        } else {
            RegPin->CategoryOffset = 0;
        }

    }

    //
    // Now create the BLOB in the registry
    //

	//
	// Note for using the flag DEVICE_INTERFACE_INCLUDE_NONACTIVE following:
	// PnP change circa 3/30/99 made the funtion IoSetDeviceInterfaceState() become
	// asynchronous. It returns SUCCESS even when the enabling is deferred. Now when
	// we arrive here, the DeviceInterface is still not enabled, we receive empty 
	// Symbolic link if the flag is not set. Here we only try to write relevent
	// FilterData to the registry. I argue this should be fine for 
	// 1. Currently, if a device is removed, the registry key for the DeviceClass
	//	  remains and with FilterData.Whatever components use the FilterData should
	//	  be able to handle if the device is removed by either check Control\Linked
	//	  or handling the failure in attempt to make connection to the non-exiting device.
	// 2. I have found that if a device is moved between slots ( PCI, USB ports ) the
	//	  DeviceInterface at DeviceClass is reused or at lease become the first entry in 
	//    the registry. Therefore, we will be updating the right entry with the proposed flag.
	//
    if (NT_SUCCESS(Status = IoGetDeviceInterfaces(
                       InterfaceClassGUID,   // ie.&KSCATEGORY_TVTUNER,etc.
                       DeviceObject, // IN PDEVICE_OBJECT PhysicalDeviceObject,OPTIONAL,
                       DEVICE_INTERFACE_INCLUDE_NONACTIVE,    // IN ULONG Flags,
                       &SymbolicLinkList // OUT PWSTR *SymbolicLinkList
                       ))) {
        UNICODE_STRING  SymbolicLinkListU;
        HANDLE          DeviceInterfaceKey;

        RtlInitUnicodeString(&SymbolicLinkListU, SymbolicLinkList);

        DebugPrint((DebugLevelVerbose,
                    "NoKSPin for SymbolicLink %S\n",
                    SymbolicLinkList ));
                    
        if (NT_SUCCESS(Status = IoOpenDeviceInterfaceRegistryKey(
                           &SymbolicLinkListU,    // IN PUNICODE_STRING SymbolicLinkName,
                           STANDARD_RIGHTS_ALL,   // IN ACCESS_MASK DesiredAccess,
                           &DeviceInterfaceKey    // OUT PHANDLE DeviceInterfaceKey
                           ))) {

            UNICODE_STRING  FilterDataString;

            RtlInitUnicodeString(&FilterDataString, L"FilterData");

            Status = ZwSetValueKey(DeviceInterfaceKey,
                                   &FilterDataString,
                                   0,
                                   REG_BINARY,
                                   FilterData,
                                   FilterDataLength);

            ZwClose(DeviceInterfaceKey);
        }
        
        // START NEW MEDIUM CACHING CODE
        for (CurrentPin = 0; CurrentPin < PinCount; CurrentPin++) {
            NTSTATUS LocalStatus;

            LocalStatus = KsCacheMedium(&SymbolicLinkListU, 
                                        &MediumList[CurrentPin],
                                        (DWORD) ((PinDirection[CurrentPin] ? 1 : 0))   // 1 == output
                                        );
            #if DBG
            if (LocalStatus != STATUS_SUCCESS) {
                DebugPrint((DebugLevelError,
                           "KsCacheMedium: SymbolicLink = %S, Status = %x\n",
                           SymbolicLinkListU.Buffer, LocalStatus));
            }
            #endif
        }
        // END NEW MEDIUM CACHING CODE
        
        ExFreePool(SymbolicLinkList);
    }
    ExFreePool(RegFilter);

    return Status;
}

BOOLEAN
StreamClassReadWriteConfig(
                           IN PVOID HwDeviceExtension,
                           IN BOOLEAN Read,
                           IN PVOID Buffer,
                           IN ULONG Offset,
                           IN ULONG Length
)
/*++

Routine Description:

    Sends down a config space read/write.   MUST BE CALLED AT PASSIVE LEVEL!

Arguments:

    HwDeviceExtension - device extension

    Read - TRUE if read, FALSE if write.

    Buffer - The info to read or write.

    Offset - The offset in config space to read or write.

    Length - The length to transfer.

Return Value:

    None.

--*/

{
    PIO_STACK_LOCATION nextStack;
    PIRP            irp;
    NTSTATUS        ntStatus;
    KEVENT          event;
    PDEVICE_EXTENSION DeviceExtension =
    (PDEVICE_EXTENSION) HwDeviceExtension - 1;
    PDEVICE_OBJECT  DeviceObject = DeviceExtension->DeviceObject;

    PAGED_CODE();

    ASSERT(KeGetCurrentIrql() < DISPATCH_LEVEL);

    if (Read) {
        memset(Buffer, '\0', Length);
    }
    irp = IoAllocateIrp(DeviceObject->StackSize, FALSE);

    if (!irp) {
        DebugPrint((DebugLevelError, "StreamClassRWConfig: no IRP.\n"));
        TRAP;
        return (FALSE);
    }

    //
    // new rule says all PnP Irp must be initialized to this
    //
    irp->IoStatus.Status = STATUS_NOT_SUPPORTED;
    
    KeInitializeEvent(&event, NotificationEvent, FALSE);

    IoSetCompletionRoutine(irp,
                           SCSynchCompletionRoutine,
                           &event,
                           TRUE,
                           TRUE,
                           TRUE);


    nextStack = IoGetNextIrpStackLocation(irp);
    ASSERT(nextStack != NULL);
    nextStack->MajorFunction = IRP_MJ_PNP;
    nextStack->MinorFunction = Read ? IRP_MN_READ_CONFIG : IRP_MN_WRITE_CONFIG;
    nextStack->Parameters.ReadWriteConfig.WhichSpace = 0;
    nextStack->Parameters.ReadWriteConfig.Buffer = Buffer;
    nextStack->Parameters.ReadWriteConfig.Offset = Offset;
    nextStack->Parameters.ReadWriteConfig.Length = Length;

    ASSERT( DeviceExtension->HwDeviceExtension == HwDeviceExtension );
    ntStatus = IoCallDriver(DeviceExtension->PhysicalDeviceObject,
                            irp);

    if (ntStatus == STATUS_PENDING) {
        // wait for irp to complete

        TRAP;
        KeWaitForSingleObject(
                              &event,
                              Suspended,
                              KernelMode,
                              FALSE,
                              NULL);
    }
    if (!NT_SUCCESS(ntStatus)) {
        DebugPrint((DebugLevelError, "StreamClassRWConfig: bad status!.\n"));
        TRAP;
    }
    IoFreeIrp(irp);
    return (TRUE);

}


VOID
StreamClassQueryMasterClockSync(
                                IN HANDLE MasterClockHandle,
                                IN OUT PHW_TIME_CONTEXT TimeContext
)
/*++

Routine Description:

  synchronously returns the current time requested, based on the TimeContext
  parameter.

Arguments:

Return Value:

  none

--*/

{

    PHW_STREAM_OBJECT HwStreamObject = TimeContext->HwStreamObject;
    PSTREAM_OBJECT  StreamObject = CONTAINING_RECORD(HwStreamObject,
                                                     STREAM_OBJECT,
                                                     HwStreamObject);

    LARGE_INTEGER       ticks;
    ULONGLONG       rate;
    KIRQL           SavedIrql;

    ASSERT(MasterClockHandle);
    ASSERT(TimeContext->HwDeviceExtension);
    ASSERT(HwStreamObject);
    ASSERT(KeGetCurrentIrql() <= DISPATCH_LEVEL);

    //
    // Lock the use of MasterClock, so it won't dispear under us
    // 
    KeAcquireSpinLock( &StreamObject->LockUseMasterClock, &SavedIrql );

    if ( NULL == StreamObject->MasterClockInfo ) {
        //
        // If we are called when MasterClockInfo is NULL,
        // the mini driver has screwed up. We don't want to fault.
        //    
        ASSERT(0 && "Mini driver queries clock while there is no master clock" );
        //
        // give a hint that something is wrong via Time, since we return void.
        //
        TimeContext->Time = (ULONGLONG)-1;
        goto Exit;
    }

    //
    // process the requested time function
    //

    switch (TimeContext->Function) {

    case TIME_GET_STREAM_TIME:

        TimeContext->Time = StreamObject->MasterClockInfo->
            FunctionTable.GetCorrelatedTime(
                             StreamObject->MasterClockInfo->ClockFileObject,
                                            &TimeContext->SystemTime);
        break;


    case TIME_READ_ONBOARD_CLOCK:

        TRAP;

        TimeContext->Time = StreamObject->MasterClockInfo->
            FunctionTable.GetTime(
                            StreamObject->MasterClockInfo->ClockFileObject);

        //
        // timestamp the value as close as possible
        //

        ticks = KeQueryPerformanceCounter((PLARGE_INTEGER) & rate);

        TimeContext->SystemTime = KSCONVERT_PERFORMANCE_TIME( rate, ticks );
            

        break;

    default:
        DebugPrint((DebugLevelFatal, "SCQueryClockSync: unknown type!"));
        TRAP;
    }

Exit:
    KeReleaseSpinLock( &StreamObject->LockUseMasterClock, SavedIrql );
    return;
}

VOID
StreamClassCompleteRequestAndMarkQueueReady(
                                            IN PHW_STREAM_REQUEST_BLOCK Srb
)
/*++

Routine Description:

  completes a stream request and marks the appropriate queue as ready for next

Arguments:

Return Value:

  none

--*/

{
    PDEVICE_EXTENSION DeviceExtension =
    (PDEVICE_EXTENSION) Srb->HwDeviceExtension - 1;

    ASSERT(!(DeviceExtension->NoSync));

    ASSERT(Srb->Status != STATUS_PENDING);

    DebugPrint((DebugLevelTrace, "'StreamClassComplete&Mark:SRB = %p\n",
                Srb));

    switch (Srb->Flags & (SRB_HW_FLAGS_DATA_TRANSFER |
                          SRB_HW_FLAGS_STREAM_REQUEST)) {

    case SRB_HW_FLAGS_STREAM_REQUEST | SRB_HW_FLAGS_DATA_TRANSFER:

        StreamClassStreamNotification(StreamRequestComplete,
                                      Srb->StreamObject,
                                      Srb);

        StreamClassStreamNotification(ReadyForNextStreamDataRequest,
                                      Srb->StreamObject);

        break;

    case SRB_HW_FLAGS_STREAM_REQUEST:


        StreamClassStreamNotification(StreamRequestComplete,
                                      Srb->StreamObject,
                                      Srb);

        StreamClassStreamNotification(ReadyForNextStreamControlRequest,
                                      Srb->StreamObject);

        break;

    default:


        StreamClassDeviceNotification(DeviceRequestComplete,
                                      Srb->HwDeviceExtension,
                                      Srb);

        StreamClassDeviceNotification(ReadyForNextDeviceRequest,
                                      Srb->HwDeviceExtension);

        break;

    }                           // switch

}

#if ENABLE_MULTIPLE_FILTER_TYPES

VOID STREAMAPI
StreamClassFilterNotification(
	IN STREAM_MINIDRIVER_DEVICE_NOTIFICATION_TYPE NotificationType,
    IN PVOID HwInstanceExtension,
    ...
);

VOID STREAMAPI
StreamClassFilterScheduleTimer(
    IN PVOID HwInstanceExtension,
    IN ULONG NumberOfMicroseconds,
    IN PHW_TIMER_ROUTINE TimerRoutine,
    IN PVOID Context
);


PKSEVENT_ENTRY
StreamClassDeviceInstanceGetNextEvent(
    IN PVOID HwInstanceExtension,
    IN OPTIONAL GUID * EventGuid,
	IN OPTIONAL ULONG EventItem,
    IN OPTIONAL PKSEVENT_ENTRY CurrentEvent
)
/*++

Routine Description:

Arguments:

    CurrentEvent - event (if any) to get the next from

Return Value:

  next event, if any

--*/
{
	PFILTER_INSTANCE FilterInstance= (PFILTER_INSTANCE)
										HwInstanceExtension - 1;
    PDEVICE_EXTENSION DeviceExtension =
					    FilterInstance->DeviceObject->DeviceExtension;
    PLIST_ENTRY     EventListEntry, EventEntry;
    PKSEVENT_ENTRY  NextEvent, ReturnEvent = NULL;
    KIRQL           Irql;

	#if DBG
    if (DeviceExtension->NoSync) {

        ASSERT(KeGetCurrentIrql() <= DISPATCH_LEVEL);

    }                           // if nosync
	#endif

    //
    // take the spinlock if we are unsynchronized.
    //

    BEGIN_MINIDRIVER_DEVICE_CALLIN(DeviceExtension, &Irql);

    //
    // loop thru the events, trying to find the requested one.
    //

    EventListEntry = EventEntry = &FilterInstance->NotifyList;

    while (EventEntry->Flink != EventListEntry) {

        EventEntry = EventEntry->Flink;
        NextEvent = CONTAINING_RECORD(EventEntry,
                                      KSEVENT_ENTRY,
                                      ListEntry);


        if ((EventItem == NextEvent->EventItem->EventId) &&
            (!EventGuid || IsEqualGUIDAligned(EventGuid, NextEvent->EventSet->Set))) {

            //
            // if we are to return the 1st event which matches, break.
            //

            if (!CurrentEvent) {

                ReturnEvent = NextEvent;
                break;

            }                   // if !current
            //
            // if we are to return the next event after the specified one,
            // check
            // to see if these match.   If they do, zero the specified event
            // so
            // that we will return the next event of the specified type.
            //

            if (CurrentEvent == NextEvent) {
                CurrentEvent = NULL;

            }                   // if cur=next
        }                       // if guid & id match
    }                           // while events

    //
    // if we are unsynchronized, release the spinlock acquired in the macro
    // above.
    //

    ASSERT(--DeviceExtension->LowerApiThreads == 0); // typo barfs. but this is truely ok.

    if (DeviceExtension->NoSync) {

        KeReleaseSpinLock(&DeviceExtension->SpinLock, Irql);
    }
    //
    // return the next event, if any.
    //

    return (ReturnEvent);
}


#endif // ENABLE_MULTIPLE_FILTER_TYPES