|
|
/*++
Copyright (c) 1998-2000 Microsoft Corporation. All rights reserved.
Module Name:
shreq.cpp
Abstract:
This module contains the implementation of the kernel streaming shell requestor object.
Author:
Dale Sather (DaleSat) 31-Jul-1998
--*/
#include "private.h"
#include <kcom.h>
#include "stdio.h"
#define POOLTAG_REQUESTOR 'gbcP'
#define POOLTAG_STREAMHEADER 'hscP'
//
// CKsShellQueue is the implementation of the kernel shell requestor object.
//
class CKsShellRequestor: public IKsShellTransport, public IKsWorkSink, public CBaseUnknown { private: PIKSSHELLTRANSPORT m_TransportSource; PIKSSHELLTRANSPORT m_TransportSink; PDEVICE_OBJECT m_NextDeviceObject; PFILE_OBJECT m_AllocatorFileObject; KSSTREAMALLOCATOR_FUNCTIONTABLE m_AllocatorFunctionTable; KSSTREAMALLOCATOR_STATUS m_AllocatorStatus; ULONG m_StackSize; ULONG m_ProbeFlags; ULONG m_StreamHeaderSize; ULONG m_FrameSize; ULONG m_FrameCount; ULONG m_ActiveIrpCountPlusOne; BOOLEAN m_Flushing; BOOLEAN m_EndOfStream; KSSTATE m_State; INTERLOCKEDLIST_HEAD m_IrpsToFree; WORK_QUEUE_ITEM m_WorkItem; PKSWORKER m_Worker; KEVENT m_StopEvent;
public: DEFINE_STD_UNKNOWN();
CKsShellRequestor(PUNKNOWN OuterUnknown): CBaseUnknown(OuterUnknown) { } ~CKsShellRequestor();
IMP_IKsShellTransport; IMP_IKsWorkSink;
NTSTATUS Init( IN ULONG ProbeFlags, IN ULONG StreamHeaderSize OPTIONAL, IN ULONG FrameSize, IN ULONG FrameCount, IN PDEVICE_OBJECT NextDeviceObject, IN PFILE_OBJECT AllocatorFileObject OPTIONAL );
private: NTSTATUS Prime( void ); NTSTATUS Unprime( void ); PVOID AllocateFrame( void ); void FreeFrame( IN PVOID Frame ); };
IMPLEMENT_STD_UNKNOWN(CKsShellRequestor)
#pragma code_seg("PAGE")
NTSTATUS KspShellCreateRequestor( OUT PIKSSHELLTRANSPORT* RequestorTransport, IN ULONG ProbeFlags, IN ULONG StreamHeaderSize OPTIONAL, IN ULONG FrameSize, IN ULONG FrameCount, IN PDEVICE_OBJECT NextDeviceObject, IN PFILE_OBJECT AllocatorFileObject OPTIONAL )
/*++
Routine Description:
This routine creates a new requestor.
Arguments:
Return Value:
--*/
{ _DbgPrintF(DEBUGLVL_BLAB,("KspShellCreateRequestor"));
PAGED_CODE();
ASSERT(RequestorTransport);
NTSTATUS status;
CKsShellRequestor *requestor = new(NonPagedPool,POOLTAG_REQUESTOR) CKsShellRequestor(NULL);
if (requestor) { requestor->AddRef();
status = requestor->Init( ProbeFlags, StreamHeaderSize, FrameSize, FrameCount, NextDeviceObject, AllocatorFileObject);
if (NT_SUCCESS(status)) { *RequestorTransport = PIKSSHELLTRANSPORT(requestor); } else { requestor->Release(); } } else { status = STATUS_INSUFFICIENT_RESOURCES; }
return status; }
NTSTATUS CKsShellRequestor:: Init( IN ULONG ProbeFlags, IN ULONG StreamHeaderSize OPTIONAL, IN ULONG FrameSize, IN ULONG FrameCount, IN PDEVICE_OBJECT NextDeviceObject, IN PFILE_OBJECT AllocatorFileObject OPTIONAL )
/*++
Routine Description:
This routine initializes a requestor object.
Arguments:
Return Value:
--*/
{ _DbgPrintF(DEBUGLVL_VERBOSE,("CKsShellRequestor::Init"));
PAGED_CODE();
ASSERT(((StreamHeaderSize == 0) || (StreamHeaderSize >= sizeof(KSSTREAM_HEADER))) && ((StreamHeaderSize & FILE_QUAD_ALIGNMENT) == 0));
if (StreamHeaderSize == 0) { StreamHeaderSize = sizeof(KSSTREAM_HEADER); }
m_NextDeviceObject = NextDeviceObject; m_AllocatorFileObject = AllocatorFileObject;
m_ProbeFlags = ProbeFlags; m_StreamHeaderSize = StreamHeaderSize; m_FrameSize = FrameSize; m_FrameCount = FrameCount;
m_State = KSSTATE_STOP; m_Flushing = FALSE; m_EndOfStream = FALSE;
//
// This is a one-based count of IRPs in circulation. We decrement it when
// we go to stop state and block until it hits zero.
//
m_ActiveIrpCountPlusOne = 1; KeInitializeEvent(&m_StopEvent,SynchronizationEvent,FALSE);
//
// Initialize IRP-freeing work item stuff.
//
InitializeInterlockedListHead(&m_IrpsToFree); KsInitializeWorkSinkItem(&m_WorkItem,this);
NTSTATUS status = KsRegisterCountedWorker(DelayedWorkQueue,&m_WorkItem,&m_Worker); if (!NT_SUCCESS(status)) { return status; }
//
// Get the function table and status from the allocator if there is an
// allocator.
//
if (m_AllocatorFileObject) { KSPROPERTY property; property.Set = KSPROPSETID_StreamAllocator; property.Id = KSPROPERTY_STREAMALLOCATOR_FUNCTIONTABLE; property.Flags = KSPROPERTY_TYPE_GET;
ULONG bytesReturned; status = KsSynchronousIoControlDevice( m_AllocatorFileObject, KernelMode, IOCTL_KS_PROPERTY, PVOID(&property), sizeof(property), PVOID(&m_AllocatorFunctionTable), sizeof(m_AllocatorFunctionTable), &bytesReturned);
if (NT_SUCCESS(status) && (bytesReturned != sizeof(m_AllocatorFunctionTable))) { status = STATUS_INVALID_BUFFER_SIZE; }
if (NT_SUCCESS(status)) { property.Id = KSPROPERTY_STREAMALLOCATOR_STATUS;
status = KsSynchronousIoControlDevice( m_AllocatorFileObject, KernelMode, IOCTL_KS_PROPERTY, PVOID(&property), sizeof(property), PVOID(&m_AllocatorStatus), sizeof(m_AllocatorStatus), &bytesReturned);
if (NT_SUCCESS(status) && (bytesReturned != sizeof(m_AllocatorStatus))) { status = STATUS_INVALID_BUFFER_SIZE; }
if (NT_SUCCESS(status)) { m_FrameSize = m_AllocatorStatus.Framing.FrameSize; m_FrameCount = m_AllocatorStatus.Framing.Frames;
_DbgPrintF(DEBUGLVL_VERBOSE,("#### Req%p.Init: using allocator 0x%08x, size %d, count %d",this,m_AllocatorFileObject,m_FrameSize,m_FrameCount)); } else { _DbgPrintF(DEBUGLVL_TERSE,("#### Req%p.Init: allocator failed status query: 0x%08x",this,status)); } } else { _DbgPrintF(DEBUGLVL_TERSE,("#### Req%p.Init: allocator failed function table query: 0x%08x",this,status)); } } else { _DbgPrintF(DEBUGLVL_VERBOSE,("#### Req%p.Init: not using an allocator, size %d, count %d",this,m_FrameSize,m_FrameCount)); }
return status; }
CKsShellRequestor:: ~CKsShellRequestor( void )
/*++
Routine Description:
This routine destructs a requestor object.
Arguments:
Return Value:
--*/
{ _DbgPrintF(DEBUGLVL_BLAB,("CKsShellRequestor::~CKsShellRequestor")); _DbgPrintF(DEBUGLVL_VERBOSE,("#### Req%p.~",this));
PAGED_CODE();
ASSERT(! m_TransportSink); ASSERT(! m_TransportSource); if (m_Worker) { KsUnregisterWorker(m_Worker); m_Worker = NULL; } }
STDMETHODIMP_(NTSTATUS) CKsShellRequestor:: NonDelegatedQueryInterface( IN REFIID InterfaceId, OUT PVOID* InterfacePointer )
/*++
Routine Description:
This routine obtains an interface to a requestor object.
Arguments:
Return Value:
--*/
{ _DbgPrintF(DEBUGLVL_BLAB,("CKsShellRequestor::NonDelegatedQueryInterface"));
PAGED_CODE();
ASSERT(InterfacePointer);
NTSTATUS status = STATUS_SUCCESS;
if (IsEqualGUIDAligned(InterfaceId,__uuidof(IKsShellTransport))) { *InterfacePointer = PVOID(PIKSSHELLTRANSPORT(this)); AddRef(); } else { status = CBaseUnknown::NonDelegatedQueryInterface( InterfaceId,InterfacePointer); }
return status; }
STDMETHODIMP_(void) CKsShellRequestor:: Work( void )
/*++
Routine Description:
This routine performs work in a worker thread. In particular, it frees IRPs.
Arguments:
Return Value:
--*/
{ _DbgPrintF(DEBUGLVL_BLAB,("CKsShellRequestor::Work"));
PAGED_CODE();
//
// Send all IRPs in the queue.
//
do { if (! IsListEmpty(&m_IrpsToFree.ListEntry)) { PIRP irp;
PLIST_ENTRY pListHead = ExInterlockedRemoveHeadList(&m_IrpsToFree.ListEntry,&m_IrpsToFree.SpinLock); if (pListHead) { irp = CONTAINING_RECORD(pListHead,IRP,Tail.Overlay.ListEntry); } else { return; } _DbgPrintF(DEBUGLVL_VERBOSE,("#### Req%p.Work: freeing IRP 0x%08x",this,irp));
//
// Free MDL(s).
//
PMDL nextMdl; for (PMDL mdl = irp->MdlAddress; mdl != NULL; mdl = nextMdl) { nextMdl = mdl->Next;
if (mdl->MdlFlags & MDL_PAGES_LOCKED) { MmUnlockPages(mdl); } IoFreeMdl(mdl); }
//
// Free header and frame.
//
PKSSTREAM_HEADER streamHeader = PKSSTREAM_HEADER(irp->UserBuffer);
if (streamHeader) { if (streamHeader->Data) { FreeFrame(streamHeader->Data); } ExFreePool(streamHeader); }
IoFreeIrp(irp);
//
// Count the active IRPs. If we have hit zero, this means that
// another thread is waiting to finish a transition to stop state.
//
if (! InterlockedDecrement(PLONG(&m_ActiveIrpCountPlusOne))) { KeSetEvent(&m_StopEvent,IO_NO_INCREMENT,FALSE); } } } while (KsDecrementCountedWorker(m_Worker)); }
#pragma code_seg()
STDMETHODIMP_(NTSTATUS) CKsShellRequestor:: TransferKsIrp( IN PIRP Irp, IN PIKSSHELLTRANSPORT* NextTransport )
/*++
Routine Description:
This routine handles the arrival of a streaming IRP.
Arguments:
Return Value:
--*/
{ _DbgPrintF(DEBUGLVL_BLAB,("CKsShellRequestor::TransferKsIrp"));
ASSERT(Irp); ASSERT(NextTransport);
ASSERT(m_TransportSink);
if (m_State != KSSTATE_RUN) { _DbgPrintF(DEBUGLVL_VERBOSE,("#### Req%p.TransferKsIrp: got IRP %p in state %d",this,Irp,m_State)); }
NTSTATUS status; PKSSTREAM_HEADER streamHeader = PKSSTREAM_HEADER(Irp->UserBuffer);
//
// Check for end of stream.
//
if (streamHeader->OptionsFlags & KSSTREAM_HEADER_OPTIONSF_ENDOFSTREAM) { m_EndOfStream = TRUE; _DbgPrintF(DEBUGLVL_VERBOSE,("#### Req%p.TransferKsIrp: IRP %p is marked end-of-stream",this,Irp)); }
if (m_Flushing || m_EndOfStream || (m_State == KSSTATE_STOP)) { //
// Stopping...destroy the IRP.
//
ExInterlockedInsertTailList( &m_IrpsToFree.ListEntry, &Irp->Tail.Overlay.ListEntry, &m_IrpsToFree.SpinLock);
*NextTransport = NULL; KsIncrementCountedWorker(m_Worker); status = STATUS_PENDING; } else { //
// Recondition and forward it.
//
PVOID frame = streamHeader->Data;
RtlZeroMemory(streamHeader,m_StreamHeaderSize); streamHeader->Size = m_StreamHeaderSize; streamHeader->Data = frame; streamHeader->FrameExtent = m_FrameSize;
Irp->IoStatus.Status = STATUS_SUCCESS; Irp->IoStatus.Information = 0; Irp->PendingReturned = 0; Irp->Cancel = 0;
*NextTransport = m_TransportSink; status = STATUS_SUCCESS; }
return status; }
#pragma code_seg("PAGE")
STDMETHODIMP_(void) CKsShellRequestor:: Connect( IN PIKSSHELLTRANSPORT NewTransport OPTIONAL, OUT PIKSSHELLTRANSPORT *OldTransport OPTIONAL, IN KSPIN_DATAFLOW DataFlow )
/*++
Routine Description:
This routine establishes a transport connection.
Arguments:
Return Value:
--*/
{ _DbgPrintF(DEBUGLVL_BLAB,("CKsShellRequestor::Connect"));
PAGED_CODE();
KsShellStandardConnect( NewTransport, OldTransport, DataFlow, PIKSSHELLTRANSPORT(this), &m_TransportSource, &m_TransportSink); }
STDMETHODIMP_(NTSTATUS) CKsShellRequestor:: SetDeviceState( IN KSSTATE ksStateTo, IN KSSTATE ksStateFrom, IN PIKSSHELLTRANSPORT* NextTransport )
/*++
Routine Description:
This routine handles notification that the device state has changed.
Arguments:
Return Value:
--*/
{ _DbgPrintF(DEBUGLVL_VERBOSE,("#### Req%p.SetDeviceState: set from %d to %d",this,ksStateFrom,ksStateTo));
PAGED_CODE();
ASSERT(NextTransport);
NTSTATUS status;
//
// If this is a change of state, note the new state and indicate the next
// recipient.
//
if (m_State != ksStateTo) { //
// The state has changed. Just note the new state, indicate the next
// recipient, and get out. We will get the same state change again
// when it has gone all the way around the circuit.
//
m_State = ksStateTo;
if (ksStateTo > ksStateFrom){ *NextTransport = m_TransportSink; } else { *NextTransport = m_TransportSource; }
status = STATUS_SUCCESS; } else { //
// The state change has gone all the way around the circuit and come
// back. All the other components are in the new state now. For
// transitions out of acquire state, there is work to be done.
//
*NextTransport = NULL;
if (ksStateFrom == KSSTATE_ACQUIRE) { if (ksStateTo == KSSTATE_PAUSE) { //
// Acquire-to-pause requires us to prime.
//
status = Prime(); } else { //
// Acquire-to-stop requires us to wait until all IRPs are home to
// roost.
//
if (InterlockedDecrement(PLONG(&m_ActiveIrpCountPlusOne))) { _DbgPrintF(DEBUGLVL_VERBOSE,("#### Req%p.SetDeviceState: waiting for %d active IRPs to return",this,m_ActiveIrpCountPlusOne)); KeWaitForSingleObject( &m_StopEvent, Suspended, KernelMode, FALSE, NULL ); _DbgPrintF(DEBUGLVL_VERBOSE,("#### Req%p.SetDeviceState: done waiting",this)); } status = STATUS_SUCCESS; } } else { #if 1
//
// Nothing to do.
//
#else
// Take a configuration pass through the circuit.
// The transport interface for each element in the circuit exposes GetTransportConfig
// and SetTransportConfig.
// The requestor's stack depth starts at 1
// Each element that reports in GetTransportConfig returns it's stack depth
// Queues, Intra-Pins report 1
// Extra-Pins report the depth of the connected device object plus one
// Splitters are handled somewhat differently (irrelevant to portcls?)
// After each GetTransportConfig, the requestor's stack depth is adjusted to be the
// highest depth seen thus far in configuration
// At the end, SetTransportConfig is used to set the requestors stack depth
//
// This mechanism is also used to decide probe flags.
// If you're interested in looking at the source, ksfilter\ks\shpipe.cpp
// ConfigureCompleteCircuit and sh*.cpp GetTransportConfig / SetTransportConfig
// are the ones to look at.
//
#endif
status = STATUS_SUCCESS; } }
return status; }
NTSTATUS CKsShellRequestor:: Prime( void )
/*++
Routine Description:
This routine primes the requestor.
Arguments:
None.
Return Value:
Status.
--*/
{ PAGED_CODE();
//
// Cache the stack size.
//
m_StackSize = m_NextDeviceObject->StackSize; // HACK
//
// ADRIAO ISSUE 06/29/1999
// Arghhhh!!!!!!!!
//
// ISSUE MARTINP 2000/12/18 This is fixed with AVStream, so it isn't worth
// making large changes to address this issue for Windows XP. This code is
// no longer present in PortCls2. We should make sure this is fixed in for
// sure in Blackcomb.
//
_DbgPrintF(DEBUGLVL_VERBOSE,("#### Req%p.Prime: stack size is %d",this,m_StackSize)); m_StackSize = 6;
//
// Reset the end of stream indicator.
//
m_EndOfStream = FALSE;
NTSTATUS status = STATUS_SUCCESS;
//
// Call the allocator or create some synthetic frames. Then wrap the
// frames in IRPs.
//
for (ULONG count = m_FrameCount; count--;) { //
// Allocate the frame.
//
PVOID frame = AllocateFrame();
if (! frame) { status = STATUS_INSUFFICIENT_RESOURCES; _DbgPrintF(DEBUGLVL_TERSE,("#### Req%p.Prime: failed to allocate frame",this)); break; }
//
// Allocate and initialize the stream header.
//
PKSSTREAM_HEADER streamHeader = (PKSSTREAM_HEADER) ExAllocatePoolWithTag( NonPagedPool,m_StreamHeaderSize,POOLTAG_STREAMHEADER);
if (! streamHeader) { status = STATUS_INSUFFICIENT_RESOURCES; _DbgPrintF(DEBUGLVL_TERSE,("#### Req%p.Prime: failed to allocate stream header",this)); FreeFrame(frame); break; }
RtlZeroMemory(streamHeader,m_StreamHeaderSize); streamHeader->Size = m_StreamHeaderSize; streamHeader->Data = frame; streamHeader->FrameExtent = m_FrameSize;
//
// Count the active IRPs.
//
InterlockedIncrement(PLONG(&m_ActiveIrpCountPlusOne));
//
// Allocate an IRP.
//
ASSERT(m_StackSize); PIRP irp = IoAllocateIrp(CCHAR(m_StackSize),FALSE);
if (! irp) { status = STATUS_INSUFFICIENT_RESOURCES; _DbgPrintF(DEBUGLVL_TERSE,("#### Req%p.Prime: failed to allocate IRP",this)); ExFreePool(streamHeader); FreeFrame(frame); break; }
irp->UserBuffer = streamHeader; irp->RequestorMode = KernelMode; irp->Flags = IRP_NOCACHE;
//
// Set the stack pointer to the first location and fill it in.
//
IoSetNextIrpStackLocation(irp);
PIO_STACK_LOCATION irpSp = IoGetCurrentIrpStackLocation(irp);
irpSp->MajorFunction = IRP_MJ_DEVICE_CONTROL; irpSp->Parameters.DeviceIoControl.IoControlCode = IOCTL_KS_READ_STREAM; irpSp->Parameters.DeviceIoControl.OutputBufferLength = m_StreamHeaderSize;
//
// Let KsProbeStreamIrp() prepare the IRP as specified by the caller.
//
status = KsProbeStreamIrp(irp,m_ProbeFlags,sizeof(KSSTREAM_HEADER));
if (! NT_SUCCESS(status)) { _DbgPrintF(DEBUGLVL_TERSE,("#### Req%p.Prime: KsProbeStreamIrp failed: 0x%08x",this,status)); IoFreeIrp(irp); ExFreePool(streamHeader); FreeFrame(frame); break; }
//
// Send the IRP to the next component.
//
//mgp
_DbgPrintF(DEBUGLVL_VERBOSE,("#### Req%p.SetDeviceState: transferring new IRP 0x%08x",this,irp)); status = KsShellTransferKsIrp(m_TransportSink,irp);
if (NT_SUCCESS(status) || (status == STATUS_MORE_PROCESSING_REQUIRED)) { status = STATUS_SUCCESS; } else { _DbgPrintF(DEBUGLVL_TERSE,("#### Req%p.Prime: receiver failed transfer call: 0x%08x",this,status)); IoFreeIrp(irp); ExFreePool(streamHeader); FreeFrame(frame); break; } }
return status; }
STDMETHODIMP_(void) CKsShellRequestor:: SetResetState( IN KSRESET ksReset, IN PIKSSHELLTRANSPORT* NextTransport )
/*++
Routine Description:
This routine handles notification that the reset state has changed.
Arguments:
Return Value:
--*/
{ _DbgPrintF(DEBUGLVL_VERBOSE,("CKsShellRequestor::SetResetState] to %d",ksReset));
PAGED_CODE();
ASSERT(NextTransport);
if (m_Flushing != (ksReset == KSRESET_BEGIN)) { *NextTransport = m_TransportSink; m_Flushing = (ksReset == KSRESET_BEGIN); } else { *NextTransport = NULL; } }
PVOID CKsShellRequestor:: AllocateFrame( void )
/*++
Routine Description:
This routine allocates a frame.
Arguments:
None.
Return Value:
The allocated frame or NULL if no frame could be allocated.
--*/
{ _DbgPrintF(DEBUGLVL_BLAB,("CKsShellRequestor::AllocateFrame"));
PAGED_CODE();
PVOID frame; if (m_AllocatorFileObject) { m_AllocatorFunctionTable.AllocateFrame(m_AllocatorFileObject,&frame); } else { frame = ExAllocatePoolWithTag(NonPagedPool,m_FrameSize,'kHcP'); }
return frame; }
void CKsShellRequestor:: FreeFrame( IN PVOID Frame )
/*++
Routine Description:
This routine frees a frame.
Arguments:
Frame - The frame to free.
Return Value:
None.
--*/
{ _DbgPrintF(DEBUGLVL_VERBOSE,("CKsShellRequestor::FreeFrame"));
PAGED_CODE();
if (m_AllocatorFileObject) { m_AllocatorFunctionTable.FreeFrame(m_AllocatorFileObject,Frame); } else { ExFreePool(Frame); } }
#if DBG
STDMETHODIMP_(void) CKsShellRequestor:: DbgRollCall( IN ULONG MaxNameSize, OUT PCHAR Name, OUT PIKSSHELLTRANSPORT* NextTransport, OUT PIKSSHELLTRANSPORT* PrevTransport )
/*++
Routine Description:
This routine produces a component name and the transport pointers.
Arguments:
Return Value:
--*/
{ _DbgPrintF(DEBUGLVL_BLAB,("CKsShellRequestor::DbgRollCall"));
PAGED_CODE();
ASSERT(Name); ASSERT(NextTransport); ASSERT(PrevTransport);
ULONG references = AddRef() - 1; Release();
_snprintf(Name,MaxNameSize,"Req%p refs=%d\n",this,references); *NextTransport = m_TransportSink; *PrevTransport = m_TransportSource; } #endif
|