You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
676 lines
15 KiB
676 lines
15 KiB
/**************************************************************************
|
|
|
|
AVStream Simulated Hardware Sample
|
|
|
|
Copyright (c) 2001, Microsoft Corporation.
|
|
|
|
File:
|
|
|
|
hwsim.cpp
|
|
|
|
Abstract:
|
|
|
|
This file contains the hardware simulation. It fakes "DMA" transfers,
|
|
scatter gather mapping handling, ISR's, etc... The ISR routine in
|
|
here will be called when an ISR would be generated by the fake hardware
|
|
and it will directly call into the device level ISR for more accurate
|
|
simulation.
|
|
|
|
History:
|
|
|
|
created 3/9/2001
|
|
|
|
**************************************************************************/
|
|
|
|
#include "BDACap.h"
|
|
|
|
/**************************************************************************
|
|
|
|
PAGEABLE CODE
|
|
|
|
**************************************************************************/
|
|
|
|
#ifdef ALLOC_PRAGMA
|
|
#pragma code_seg("PAGE")
|
|
#endif // ALLOC_PRAGMA
|
|
|
|
|
|
CHardwareSimulation::
|
|
CHardwareSimulation (
|
|
IN IHardwareSink *HardwareSink
|
|
) :
|
|
m_HardwareSink (HardwareSink),
|
|
m_ScatterGatherMappingsMax (SCATTER_GATHER_MAPPINGS_MAX)
|
|
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
Construct a hardware simulation
|
|
|
|
Arguments:
|
|
|
|
HardwareSink -
|
|
The hardware sink interface. This is used to trigger
|
|
fake interrupt service routines from.
|
|
|
|
Return Value:
|
|
|
|
Success / Failure
|
|
|
|
--*/
|
|
|
|
{
|
|
|
|
PAGED_CODE();
|
|
|
|
//
|
|
// Initialize the DPC's, timer's, and locks necessary to simulate
|
|
// this capture hardware.
|
|
//
|
|
KeInitializeDpc (
|
|
&m_IsrFakeDpc,
|
|
reinterpret_cast <PKDEFERRED_ROUTINE>
|
|
(CHardwareSimulation::SimulatedInterrupt),
|
|
this
|
|
);
|
|
|
|
KeInitializeEvent (
|
|
&m_HardwareEvent,
|
|
SynchronizationEvent,
|
|
FALSE
|
|
);
|
|
|
|
KeInitializeTimer (&m_IsrTimer);
|
|
|
|
KeInitializeSpinLock (&m_ListLock);
|
|
|
|
}
|
|
|
|
/*************************************************/
|
|
|
|
|
|
NTSTATUS
|
|
CHardwareSimulation::
|
|
Start (
|
|
IN CTsSynthesizer *TsSynth,
|
|
IN LONGLONG TimePerFrame,
|
|
IN ULONG PacketSize,
|
|
IN ULONG PacketsPerSample
|
|
)
|
|
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
Start the hardware simulation. This will kick the interrupts on,
|
|
begin issuing DPC's, filling in capture information, etc...
|
|
We keep track of starvation starting at this point.
|
|
|
|
Arguments:
|
|
|
|
TsSynth -
|
|
The transport stream synthesizer to use to generate TS packets
|
|
on the capture buffer.
|
|
|
|
TimePerFrame -
|
|
The time per frame... we issue interrupts this often.
|
|
|
|
PacketSize -
|
|
The size of a single transport stream packet.
|
|
|
|
PacketsPerSample -
|
|
The number of packets in a single capture sample
|
|
|
|
Return Value:
|
|
|
|
Success / Failure (typical failure will be out of memory on the
|
|
scratch buffer, etc...)
|
|
|
|
--*/
|
|
|
|
{
|
|
|
|
PAGED_CODE();
|
|
|
|
NTSTATUS Status = STATUS_SUCCESS;
|
|
|
|
m_TsSynth = TsSynth;
|
|
m_TimePerFrame = TimePerFrame;
|
|
m_SampleSize = PacketSize * PacketsPerSample;
|
|
m_PacketSize = PacketSize;
|
|
m_PacketsPerSample = PacketsPerSample;
|
|
|
|
InitializeListHead (&m_ScatterGatherMappings);
|
|
m_NumMappingsCompleted = 0;
|
|
m_ScatterGatherMappingsQueued = 0;
|
|
m_NumFramesSkipped = 0;
|
|
m_InterruptTime = 0;
|
|
|
|
KeQuerySystemTime (&m_StartTime);
|
|
|
|
//
|
|
// Allocate a scratch buffer for the synthesizer.
|
|
//
|
|
m_SynthesisBuffer = reinterpret_cast <PUCHAR> (
|
|
ExAllocatePool (
|
|
NonPagedPool,
|
|
m_SampleSize
|
|
)
|
|
);
|
|
|
|
if (!m_SynthesisBuffer) {
|
|
Status = STATUS_INSUFFICIENT_RESOURCES;
|
|
}
|
|
|
|
//
|
|
// If everything is ok, start issuing interrupts.
|
|
//
|
|
if (NT_SUCCESS (Status)) {
|
|
|
|
//
|
|
// Initialize the entry lookaside.
|
|
//
|
|
ExInitializeNPagedLookasideList (
|
|
&m_ScatterGatherLookaside,
|
|
NULL,
|
|
NULL,
|
|
0,
|
|
sizeof (SCATTER_GATHER_ENTRY),
|
|
'nEGS',
|
|
0
|
|
);
|
|
|
|
//
|
|
// Set up the synthesizer with the width, height, and scratch buffer.
|
|
//
|
|
m_TsSynth -> SetSampleSize (m_PacketSize, m_PacketsPerSample);
|
|
m_TsSynth -> SetBuffer (m_SynthesisBuffer);
|
|
|
|
LARGE_INTEGER NextTime;
|
|
NextTime.QuadPart = m_StartTime.QuadPart + m_TimePerFrame;
|
|
|
|
m_HardwareState = HardwareRunning;
|
|
KeSetTimer (&m_IsrTimer, NextTime, &m_IsrFakeDpc);
|
|
|
|
}
|
|
|
|
return Status;
|
|
|
|
}
|
|
|
|
/*************************************************/
|
|
|
|
|
|
NTSTATUS
|
|
CHardwareSimulation::
|
|
Pause (
|
|
BOOLEAN Pausing
|
|
)
|
|
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
Pause the hardware simulation... When the hardware simulation is told
|
|
to pause, it stops issuing interrupts, etc... but it does not reset
|
|
the counters
|
|
|
|
Arguments:
|
|
|
|
Pausing -
|
|
Indicates whether the hardware is pausing or not.
|
|
|
|
TRUE -
|
|
Pause the hardware
|
|
|
|
FALSE -
|
|
Unpause the hardware from a previous pause
|
|
|
|
|
|
Return Value:
|
|
|
|
Success / Failure
|
|
|
|
--*/
|
|
|
|
{
|
|
|
|
PAGED_CODE();
|
|
|
|
if (Pausing && m_HardwareState == HardwareRunning) {
|
|
//
|
|
// If we were running, stop completing mappings, etc...
|
|
//
|
|
m_StopHardware = TRUE;
|
|
|
|
KeWaitForSingleObject (
|
|
&m_HardwareEvent,
|
|
Suspended,
|
|
KernelMode,
|
|
FALSE,
|
|
NULL
|
|
);
|
|
|
|
ASSERT (m_StopHardware == FALSE);
|
|
|
|
m_HardwareState = HardwarePaused;
|
|
|
|
} else if (!Pausing && m_HardwareState == HardwarePaused) {
|
|
|
|
//
|
|
// For unpausing the hardware, we need to compute the relative time
|
|
// and restart interrupts.
|
|
//
|
|
LARGE_INTEGER UnpauseTime;
|
|
|
|
KeQuerySystemTime (&UnpauseTime);
|
|
m_InterruptTime = (ULONG) (
|
|
(UnpauseTime.QuadPart - m_StartTime.QuadPart) /
|
|
m_TimePerFrame
|
|
);
|
|
|
|
UnpauseTime.QuadPart = m_StartTime.QuadPart +
|
|
(m_InterruptTime + 1) * m_TimePerFrame;
|
|
|
|
m_HardwareState = HardwareRunning;
|
|
KeSetTimer (&m_IsrTimer, UnpauseTime, &m_IsrFakeDpc);
|
|
|
|
}
|
|
|
|
return STATUS_SUCCESS;
|
|
|
|
}
|
|
|
|
/*************************************************/
|
|
|
|
|
|
NTSTATUS
|
|
CHardwareSimulation::
|
|
Stop (
|
|
)
|
|
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
Stop the hardware simulation.... Wait until the hardware simulation
|
|
has successfully stopped and then return.
|
|
|
|
Arguments:
|
|
|
|
None
|
|
|
|
Return Value:
|
|
|
|
Success / Failure
|
|
|
|
--*/
|
|
|
|
{
|
|
|
|
PAGED_CODE();
|
|
|
|
//
|
|
// If the hardware is told to stop while it's running, we need to
|
|
// halt the interrupts first. If we're already paused, this has
|
|
// already been done.
|
|
//
|
|
if (m_HardwareState == HardwareRunning) {
|
|
|
|
m_StopHardware = TRUE;
|
|
|
|
KeWaitForSingleObject (
|
|
&m_HardwareEvent,
|
|
Suspended,
|
|
KernelMode,
|
|
FALSE,
|
|
NULL
|
|
);
|
|
|
|
ASSERT (m_StopHardware == FALSE);
|
|
|
|
}
|
|
|
|
m_HardwareState = HardwareStopped;
|
|
|
|
//
|
|
// The image synthesizer may still be around. Just for safety's
|
|
// sake, NULL out the image synthesis buffer and toast it.
|
|
//
|
|
m_TsSynth -> SetBuffer (NULL);
|
|
|
|
if (m_SynthesisBuffer) {
|
|
ExFreePool (m_SynthesisBuffer);
|
|
m_SynthesisBuffer = NULL;
|
|
}
|
|
|
|
//
|
|
// Delete the scatter / gather lookaside for this run.
|
|
//
|
|
ExDeleteNPagedLookasideList (&m_ScatterGatherLookaside);
|
|
|
|
return STATUS_SUCCESS;
|
|
|
|
}
|
|
|
|
/**************************************************************************
|
|
|
|
LOCKED CODE
|
|
|
|
**************************************************************************/
|
|
|
|
#ifdef ALLOC_PRAGMA
|
|
#pragma code_seg()
|
|
#endif // ALLOC_PRAGMA
|
|
|
|
|
|
ULONG
|
|
CHardwareSimulation::
|
|
ReadNumberOfMappingsCompleted (
|
|
)
|
|
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
Read the number of scatter / gather mappings which have been
|
|
completed (TOTAL NUMBER) since the last reset of the simulated
|
|
hardware
|
|
|
|
Arguments:
|
|
|
|
None
|
|
|
|
Return Value:
|
|
|
|
Total number of completed mappings.
|
|
|
|
--*/
|
|
|
|
{
|
|
|
|
//
|
|
// Don't care if this is being updated this moment in the DPC... I only
|
|
// need a number to return which isn't too great (too small is ok).
|
|
// In real hardware, this wouldn't be done this way anyway.
|
|
//
|
|
return m_NumMappingsCompleted;
|
|
|
|
}
|
|
|
|
/*************************************************/
|
|
|
|
|
|
ULONG
|
|
CHardwareSimulation::
|
|
ProgramScatterGatherMappings (
|
|
IN PUCHAR *Buffer,
|
|
IN PKSMAPPING Mappings,
|
|
IN ULONG MappingsCount,
|
|
IN ULONG MappingStride
|
|
)
|
|
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
Program the scatter gather mapping list. This shoves a bunch of
|
|
entries on a list for access during the fake interrupt. Note that
|
|
we have physical addresses here only for simulation. We really
|
|
access via the virtual address.... although we chunk it into multiple
|
|
buffers to more realistically simulate S/G
|
|
|
|
Arguments:
|
|
|
|
Buffer -
|
|
The virtual address of the buffer mapped by the mapping list
|
|
|
|
Mappings -
|
|
The KSMAPPINGS array corresponding to the buffer
|
|
|
|
MappingsCount -
|
|
The number of mappings in the mappings array
|
|
|
|
MappingStride -
|
|
The mapping stride used in initialization of AVStream DMA
|
|
|
|
Return Value:
|
|
|
|
Number of mappings actually inserted.
|
|
|
|
--*/
|
|
|
|
{
|
|
|
|
KIRQL Irql;
|
|
|
|
ULONG MappingsInserted = 0;
|
|
|
|
//
|
|
// Protect our S/G list with a spinlock.
|
|
//
|
|
KeAcquireSpinLock (&m_ListLock, &Irql);
|
|
|
|
//
|
|
// Loop through the scatter / gather list and break the buffer up into
|
|
// chunks equal to the scatter / gather mappings. Stuff the virtual
|
|
// addresses of these chunks on a list somewhere. We update the buffer
|
|
// pointer the caller passes as a more convenient way of doing this.
|
|
//
|
|
// If I could just remap physical in the list to virtual easily here,
|
|
// I wouldn't need to do it.
|
|
//
|
|
for (ULONG MappingNum = 0;
|
|
MappingNum < MappingsCount &&
|
|
m_ScatterGatherMappingsQueued < m_ScatterGatherMappingsMax;
|
|
MappingNum++) {
|
|
|
|
PSCATTER_GATHER_ENTRY Entry =
|
|
reinterpret_cast <PSCATTER_GATHER_ENTRY> (
|
|
ExAllocateFromNPagedLookasideList (
|
|
&m_ScatterGatherLookaside
|
|
)
|
|
);
|
|
|
|
if (!Entry) {
|
|
break;
|
|
}
|
|
|
|
Entry -> Virtual = *Buffer;
|
|
Entry -> ByteCount = Mappings -> ByteCount;
|
|
|
|
//
|
|
// Move forward a specific number of bytes in chunking this into
|
|
// mapping sized va buffers.
|
|
//
|
|
*Buffer += Entry -> ByteCount;
|
|
Mappings = reinterpret_cast <PKSMAPPING> (
|
|
(reinterpret_cast <PUCHAR> (Mappings) + MappingStride)
|
|
);
|
|
|
|
InsertTailList (&m_ScatterGatherMappings, &(Entry -> ListEntry));
|
|
MappingsInserted++;
|
|
m_ScatterGatherMappingsQueued++;
|
|
m_ScatterGatherBytesQueued += Entry -> ByteCount;
|
|
|
|
}
|
|
|
|
KeReleaseSpinLock (&m_ListLock, Irql);
|
|
|
|
return MappingsInserted;
|
|
|
|
}
|
|
|
|
/*************************************************/
|
|
|
|
|
|
NTSTATUS
|
|
CHardwareSimulation::
|
|
FillScatterGatherBuffers (
|
|
)
|
|
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
The hardware has synthesized a buffer in scratch space and we're to
|
|
fill scatter / gather buffers.
|
|
|
|
Arguments:
|
|
|
|
None
|
|
|
|
Return Value:
|
|
|
|
Success / Failure
|
|
|
|
--*/
|
|
|
|
{
|
|
|
|
//
|
|
// We're using this list lock to protect our scatter / gather lists instead
|
|
// of some hardware mechanism / KeSynchronizeExecution / whatever.
|
|
//
|
|
KeAcquireSpinLockAtDpcLevel (&m_ListLock);
|
|
|
|
PUCHAR Buffer = reinterpret_cast <PUCHAR> (m_SynthesisBuffer);
|
|
ULONG BufferRemaining = m_SampleSize;
|
|
|
|
//
|
|
// For simplification, if there aren't enough scatter / gather buffers
|
|
// queued, we don't partially fill the ones that are available. We just
|
|
// skip the frame and consider it starvation.
|
|
//
|
|
// This could be enforced by only programming scatter / gather mappings
|
|
// for a buffer if all of them fit in the table also...
|
|
//
|
|
while (BufferRemaining &&
|
|
m_ScatterGatherMappingsQueued > 0 &&
|
|
m_ScatterGatherBytesQueued >= BufferRemaining) {
|
|
|
|
LIST_ENTRY *listEntry = RemoveHeadList (&m_ScatterGatherMappings);
|
|
m_ScatterGatherMappingsQueued--;
|
|
|
|
PSCATTER_GATHER_ENTRY SGEntry =
|
|
reinterpret_cast <PSCATTER_GATHER_ENTRY> (
|
|
CONTAINING_RECORD (
|
|
listEntry,
|
|
SCATTER_GATHER_ENTRY,
|
|
ListEntry
|
|
)
|
|
);
|
|
|
|
//
|
|
// Since we're software, we'll be accessing this by virtual address...
|
|
//
|
|
ULONG BytesToCopy =
|
|
(BufferRemaining < SGEntry -> ByteCount) ?
|
|
BufferRemaining :
|
|
SGEntry -> ByteCount;
|
|
|
|
RtlCopyMemory (
|
|
SGEntry -> Virtual,
|
|
Buffer,
|
|
BytesToCopy
|
|
);
|
|
|
|
BufferRemaining -= BytesToCopy;
|
|
Buffer += BytesToCopy;
|
|
m_NumMappingsCompleted++;
|
|
m_ScatterGatherBytesQueued -= SGEntry -> ByteCount;
|
|
|
|
//
|
|
// Release the scatter / gather entry back to our lookaside.
|
|
//
|
|
ExFreeToNPagedLookasideList (
|
|
&m_ScatterGatherLookaside,
|
|
reinterpret_cast <PVOID> (SGEntry)
|
|
);
|
|
|
|
}
|
|
|
|
KeReleaseSpinLockFromDpcLevel (&m_ListLock);
|
|
|
|
if (BufferRemaining) return STATUS_INSUFFICIENT_RESOURCES;
|
|
else return STATUS_SUCCESS;
|
|
|
|
}
|
|
|
|
/*************************************************/
|
|
|
|
|
|
void
|
|
CHardwareSimulation::
|
|
FakeHardware (
|
|
)
|
|
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
Simulate an interrupt and what the hardware would have done in the
|
|
time since the previous interrupt.
|
|
|
|
Arguments:
|
|
|
|
None
|
|
|
|
Return Value:
|
|
|
|
None
|
|
|
|
--*/
|
|
|
|
{
|
|
|
|
m_InterruptTime++;
|
|
|
|
//
|
|
// The hardware can be in a pause state in which case, it issues interrupts
|
|
// but does not complete mappings. In this case, don't bother synthesizing
|
|
// a frame and doing the work of looking through the mappings table.
|
|
//
|
|
if (m_HardwareState == HardwareRunning) {
|
|
|
|
//
|
|
// Fill scatter gather buffers
|
|
//
|
|
if (!NT_SUCCESS (FillScatterGatherBuffers ())) {
|
|
InterlockedIncrement (PLONG (&m_NumFramesSkipped));
|
|
}
|
|
|
|
}
|
|
|
|
//
|
|
// Issue an interrupt to our hardware sink. This is a "fake" interrupt.
|
|
// It will occur at DISPATCH_LEVEL.
|
|
//
|
|
m_HardwareSink -> Interrupt ();
|
|
|
|
//
|
|
// Reschedule the timer if the hardware isn't being stopped.
|
|
//
|
|
if (!m_StopHardware) {
|
|
|
|
//
|
|
// Reschedule the timer for the next interrupt time.
|
|
//
|
|
LARGE_INTEGER NextTime;
|
|
NextTime.QuadPart = m_StartTime.QuadPart +
|
|
(m_TimePerFrame * (m_InterruptTime + 1));
|
|
|
|
KeSetTimer (&m_IsrTimer, NextTime, &m_IsrFakeDpc);
|
|
|
|
} else {
|
|
//
|
|
// If someone is waiting on the hardware to stop, raise the stop
|
|
// event and clear the flag.
|
|
//
|
|
m_StopHardware = FALSE;
|
|
KeSetEvent (&m_HardwareEvent, IO_NO_INCREMENT, FALSE);
|
|
}
|
|
|
|
}
|
|
|