|
|
/****************************************************************************
* * midi.c * * Midi routines for wdmaud.sys * * Copyright (C) Microsoft Corporation, 1997 - 1999 All Rights Reserved. * * History * S.Mohanraj (MohanS) * M.McLaughlin (MikeM) * 5-19-97 - Noel Cross (NoelC) * ***************************************************************************/
#include "wdmsys.h"
#define IRP_LATENCY_100NS 3000
//
// This is just a scratch location that is never used for anything
// but a parameter to core functions.
//
IO_STATUS_BLOCK gIoStatusBlock ;
#pragma PAGEABLE_CODE
#pragma PAGEABLE_DATA
ULONGLONG GetCurrentMidiTime() { LARGE_INTEGER liFrequency,liTime;
PAGED_CODE(); // total ticks since system booted
liTime = KeQueryPerformanceCounter(&liFrequency);
// Convert ticks to 100ns units using Ks macro
//
return (KSCONVERT_PERFORMANCE_TIME(liFrequency.QuadPart,liTime)); }
//
// This routine gives us a pMidiPin to play with.
//
NTSTATUS OpenMidiPin( PWDMACONTEXT pWdmaContext, ULONG DeviceNumber, ULONG DataFlow //DataFlow is either in or out.
) { PMIDI_PIN_INSTANCE pMidiPin = NULL; NTSTATUS Status; PKSPIN_CONNECT pConnect = NULL; PKSDATARANGE pDataRange; PCONTROLS_LIST pControlList = NULL; ULONG Device; ULONG PinId;
PAGED_CODE(); //
// Because of the ZERO_FILL_MEMORY flag, are pMidiPin structure will come
// back zero'd out.
//
Status = AudioAllocateMemory_Fixed(sizeof(MIDI_PIN_INSTANCE), TAG_Audi_PIN, ZERO_FILL_MEMORY, &pMidiPin); if(!NT_SUCCESS(Status)) { goto exit; }
pMidiPin->dwSig = MIDI_PIN_INSTANCE_SIGNATURE; pMidiPin->DataFlow = DataFlow; pMidiPin->DeviceNumber = DeviceNumber; pMidiPin->PinState = KSSTATE_STOP;
KeInitializeSpinLock( &pMidiPin->MidiPinSpinLock );
KeInitializeEvent ( &pMidiPin->StopEvent,SynchronizationEvent,FALSE ) ;
if( KSPIN_DATAFLOW_IN == DataFlow ) { pMidiPin->pMidiDevice = &pWdmaContext->MidiOutDevs[DeviceNumber];
if (NULL == pWdmaContext->MidiOutDevs[DeviceNumber].pMidiPin) { pWdmaContext->MidiOutDevs[DeviceNumber].pMidiPin = pMidiPin; } else { DPF(DL_TRACE|FA_MIDI, ("Midi device in use") );
AudioFreeMemory( sizeof(MIDI_PIN_INSTANCE),&pMidiPin ); Status = STATUS_DEVICE_BUSY; goto exit; } } else { //
// KSPIN_DATAFLOW_OUT
//
pMidiPin->pMidiDevice = &pWdmaContext->MidiInDevs[DeviceNumber];
InitializeListHead(&pMidiPin->MidiInQueueListHead);
KeInitializeSpinLock(&pMidiPin->MidiInQueueSpinLock);
if (NULL == pWdmaContext->MidiInDevs[DeviceNumber].pMidiPin) { pWdmaContext->MidiInDevs[DeviceNumber].pMidiPin = pMidiPin; } else { AudioFreeMemory( sizeof(MIDI_PIN_INSTANCE),&pMidiPin ); Status = STATUS_DEVICE_BUSY; goto exit; } }
//
// We only support one midi client at a time, the check above will
// only add this structure if there is not already one there. If there
// was something there already, we skip all the following code and
// go directly to the exit lable. Thus, fGraphRunning must not be
// set when we are here.
//
ASSERT( !pMidiPin->fGraphRunning );
pMidiPin->fGraphRunning++;
//
// Because of the ZERO_FILL_MEMORY flag our pConnect structure will
// come back all zero'd out.
//
Status = AudioAllocateMemory_Fixed(sizeof(KSPIN_CONNECT) + sizeof(KSDATARANGE), TAG_Audt_CONNECT, ZERO_FILL_MEMORY, &pConnect); if(!NT_SUCCESS(Status)) { pMidiPin->fGraphRunning--; goto exit ; }
pDataRange = (PKSDATARANGE)(pConnect + 1);
PinId = pMidiPin->pMidiDevice->PinId; Device = pMidiPin->pMidiDevice->Device;
pConnect->Interface.Set = KSINTERFACESETID_Standard ; pConnect->Interface.Id = KSINTERFACE_STANDARD_STREAMING; pConnect->Medium.Set = KSMEDIUMSETID_Standard; pConnect->Medium.Id = KSMEDIUM_STANDARD_DEVIO; pConnect->Priority.PriorityClass = KSPRIORITY_NORMAL; pConnect->Priority.PrioritySubClass = 1; pDataRange->MajorFormat = KSDATAFORMAT_TYPE_MUSIC; pDataRange->SubFormat = KSDATAFORMAT_SUBTYPE_MIDI; pDataRange->Specifier = KSDATAFORMAT_SPECIFIER_NONE; pDataRange->FormatSize = sizeof( KSDATARANGE ); pDataRange->Reserved = 0 ;
Status = AudioAllocateMemory_Fixed((sizeof(CONTROLS_LIST) + ( (MAX_MIDI_CONTROLS - 1) * sizeof(CONTROL_NODE) ) ), TAG_AudC_CONTROL, ZERO_FILL_MEMORY, &pControlList) ; if(!NT_SUCCESS(Status)) { pMidiPin->fGraphRunning--; AudioFreeMemory( sizeof(KSPIN_CONNECT) + sizeof(KSDATARANGE),&pConnect ); goto exit ; }
pControlList->Count = MAX_MIDI_CONTROLS ; pControlList->Controls[MIDI_CONTROL_VOLUME].Control = KSNODETYPE_VOLUME ; pMidiPin->pControlList = pControlList ;
// Open a pin
Status = OpenSysAudioPin(Device, PinId, pMidiPin->DataFlow, pConnect, &pMidiPin->pFileObject, &pMidiPin->pDeviceObject, pMidiPin->pControlList);
AudioFreeMemory( sizeof(KSPIN_CONNECT) + sizeof(KSDATARANGE),&pConnect );
if (!NT_SUCCESS(Status)) { CloseMidiDevicePin(pMidiPin->pMidiDevice); goto exit ; }
//
// OpenSysAudioPin sets the file object in the pin. Now that we have
// successfully returned from the call, validate that we have non-NULL
// items.
//
ASSERT(pMidiPin->pFileObject); ASSERT(pMidiPin->pDeviceObject);
//
// For output we put the device in a RUN state on open
// For input we have to wait until the device gets told
// to start
//
if ( KSPIN_DATAFLOW_IN == pMidiPin->DataFlow ) { Status = AttachVirtualSource(pMidiPin->pFileObject, pMidiPin->pMidiDevice->pWdmaContext->VirtualMidiPinId);
if (NT_SUCCESS(Status)) { Status = StateMidiOutPin(pMidiPin, KSSTATE_RUN); }
if (!NT_SUCCESS(Status)) { CloseMidiDevicePin(pMidiPin->pMidiDevice); } } else { //
// Pause will queue a bunch of IRPs
//
Status = StateMidiInPin(pMidiPin, KSSTATE_PAUSE); if (!NT_SUCCESS(Status)) { CloseMidiDevicePin(pMidiPin->pMidiDevice); } }
exit:
RETURN( Status ); }
//
// This routine is called from multiple places. As long as it's not reentrant, it should be
// ok. Should check for that.
//
// This routine gets called from RemoveDevNode. RemoveDevNode gets called from user mode
// or from the ContextCleanup routine. Both routines are in the global mutex.
//
VOID CloseMidiDevicePin( PMIDIDEVICE pMidiDevice ) { PAGED_CODE(); if (NULL != pMidiDevice->pMidiPin ) { //
// CloseMidiPin must not fail.
//
CloseMidiPin ( pMidiDevice->pMidiPin ) ; //
// AudioFreeMemory Nulls out this memory location.
//
AudioFreeMemory( sizeof(MIDI_PIN_INSTANCE),&pMidiDevice->pMidiPin ) ; } }
#pragma LOCKED_CODE
#pragma LOCKED_DATA
//
// The idea behind this SpinLock is that we want to protect the NumPendingIos
// value in the Irp completion routine. There, there is a preemption issue
// that we can't have an InterlockedIncrement or InterlockedDecrement interfer
// with.
//
void LockedMidiIoCount( PMIDI_PIN_INSTANCE pCurMidiPin, BOOL bIncrease ) { KIRQL OldIrql;
KeAcquireSpinLock(&pCurMidiPin->MidiPinSpinLock,&OldIrql);
if( bIncrease ) pCurMidiPin->NumPendingIos++; else pCurMidiPin->NumPendingIos--; KeReleaseSpinLock(&pCurMidiPin->MidiPinSpinLock, OldIrql); }
VOID FreeIrpMdls( PIRP pIrp ) { if (pIrp->MdlAddress != NULL) { PMDL Mdl, nextMdl;
for (Mdl = pIrp->MdlAddress; Mdl != (PMDL) NULL; Mdl = nextMdl) { nextMdl = Mdl->Next; MmUnlockPages( Mdl ); AudioFreeMemory_Unknown( &Mdl ); }
pIrp->MdlAddress = NULL; } }
#pragma PAGEABLE_CODE
#pragma PAGEABLE_DATA
//
// This routine can not fail. When it returns, pMidiPin will be freed.
//
VOID CloseMidiPin( PMIDI_PIN_INSTANCE pMidiPin ) { PMIDIINHDR pHdr; PMIDIINHDR pTemp; KSSTATE State;
PAGED_CODE();
// This is designed to bring us back to square one, even
// if we were not completely opened
if( !pMidiPin->fGraphRunning ) { ASSERT(pMidiPin->fGraphRunning == 1); return ; }
pMidiPin->fGraphRunning--;
// Close the file object (pMidiPin->pFileObject, if it exists)
if(pMidiPin->pFileObject) { //
// For Midi Input we need to flush the queued up scratch IRPs by
// issuing a STOP command.
//
// We don't want to do that for Midi Output because we might loose
// the "all notes off" sequence that needs to get to the device.
//
// Regardless, in both cases we need to wait until we have
// compeletely flushed the device before we can close it.
//
if ( KSPIN_DATAFLOW_OUT == pMidiPin->DataFlow ) { PLIST_ENTRY ple;
//
// This is kind of a catch-22. We need to release
// the mutex which was grabbed when we entered the
// ioctl dispatch routine to allow the midi input
// irps which are queued up in a work item waiting
// until the mutex is free in order to be send
// down to portcls.
//
WdmaReleaseMutex(pMidiPin->pMidiDevice->pWdmaContext); //
// This loop removes an entry and frees it until the list is empty.
//
while((ple = ExInterlockedRemoveHeadList(&pMidiPin->MidiInQueueListHead, &pMidiPin->MidiInQueueSpinLock)) != NULL) { LPMIDIDATA pMidiData; PIRP UserIrp; PWDMAPENDINGIRP_CONTEXT pPendingIrpContext;
pHdr = CONTAINING_RECORD(ple,MIDIINHDR,Next); //
// Get into locals and zero out midi data
//
UserIrp = pHdr->pIrp; pMidiData = pHdr->pMidiData; pPendingIrpContext = pHdr->pPendingIrpContext; ASSERT(pPendingIrpContext); RtlZeroMemory(pMidiData, sizeof(MIDIDATA));
//
// unlock memory before completing the Irp
//
wdmaudUnmapBuffer(pHdr->pMdl); AudioFreeMemory_Unknown(&pHdr);
//
// Now complete the Irp for wdmaud.drv to process
//
DPF(DL_TRACE|FA_MIDI, ("CloseMidiPin: Freeing pending UserIrp: 0x%08lx",UserIrp)); wdmaudUnprepareIrp ( UserIrp, STATUS_CANCELLED, sizeof(DEVICEINFO), pPendingIrpContext ); } }
//
// At this point we know that the list is empty, but there
// still might be an Irp in the completion process. We have
// to call the standard wait routine to make sure it gets completed.
//
pMidiPin->StoppingSource = TRUE ;
if ( KSPIN_DATAFLOW_OUT == pMidiPin->DataFlow ) { StatePin ( pMidiPin->pFileObject, KSSTATE_STOP, &pMidiPin->PinState ) ; }
//
// Need to wait for all in and out data to complete.
//
MidiCompleteIo( pMidiPin, FALSE );
if ( KSPIN_DATAFLOW_OUT == pMidiPin->DataFlow ) { //
// Grab back the mutex which was freed before we started
// waiting on the I/O to complete.
//
WdmaGrabMutex(pMidiPin->pMidiDevice->pWdmaContext); }
CloseSysAudio(pMidiPin->pMidiDevice->pWdmaContext, pMidiPin->pFileObject); pMidiPin->pFileObject = NULL; }
//
// AudioFreeMemory_Unknown Nulls out this location
//
AudioFreeMemory_Unknown ( &pMidiPin->pControlList ) ; }
#pragma LOCKED_CODE
#pragma LOCKED_DATA
//
// This is the IRP completion routine.
//
NTSTATUS WriteMidiEventCallBack( PDEVICE_OBJECT pDeviceObject, PIRP pIrp, IN PSTREAM_HEADER_EX pStreamHeader ) { KIRQL OldIrql; PMIDI_PIN_INSTANCE pMidiOutPin;
pMidiOutPin = pStreamHeader->pMidiPin;
if (pMidiOutPin) { KeAcquireSpinLock(&pMidiOutPin->MidiPinSpinLock,&OldIrql); //
// One less Io packet outstanding, thus we always decrement the
// outstanding count. Then, we compare to see if we're the last
// packet and we're stopping then we signal the saiting thead.
//
if( ( 0 == --pMidiOutPin->NumPendingIos ) && pMidiOutPin->StoppingSource ) { KeSetEvent ( &pMidiOutPin->StopEvent, 0, FALSE ) ; }
//
// Upon releasing this spin lock pMidiOutPin will no longer be valid.
// Thus we must not touch it.
//
KeReleaseSpinLock(&pMidiOutPin->MidiPinSpinLock,OldIrql); }
//
// If there are any Mdls, free them here otherwise IoCompleteRequest will do it after the
// freeing of our data buffer below.
//
FreeIrpMdls(pIrp); AudioFreeMemory(sizeof(STREAM_HEADER_EX),&pStreamHeader); return STATUS_SUCCESS; }
#pragma PAGEABLE_CODE
#pragma PAGEABLE_DATA
NTSTATUS WriteMidiEventPin( PMIDIDEVICE pMidiOutDevice, ULONG ulEvent ) { PKSMUSICFORMAT pMusicFormat; PSTREAM_HEADER_EX pStreamHeader = NULL; PMIDI_PIN_INSTANCE pMidiPin; NTSTATUS Status = STATUS_SUCCESS; BYTE bEvent; ULONG TheEqualizer; ULONGLONG nsPlayTime; KEVENT keEventObject; PWDMACONTEXT pWdmaContext;
PAGED_CODE(); pMidiPin = pMidiOutDevice->pMidiPin;
if (!pMidiPin ||!pMidiPin->fGraphRunning || !pMidiPin->pFileObject ) { DPF(DL_WARNING|FA_MIDI,("Not ready pMidiPin=%X",pMidiPin) ); RETURN( STATUS_DEVICE_NOT_READY ); }
//
// allocate enough memory for the stream header
// the midi/music header, the data, the work item,
// and the DeviceNumber. The memroy allocation
// is zero'd with the ZERO_FILL_MEMORY flag
//
Status = AudioAllocateMemory_Fixed(sizeof(STREAM_HEADER_EX) + sizeof(KSMUSICFORMAT) + sizeof(ULONG), TAG_Audh_STREAMHEADER, ZERO_FILL_MEMORY, &pStreamHeader); // ulEvent
if(!NT_SUCCESS(Status)) { return Status; }
// Get a pointer to the music header
pMusicFormat = (PKSMUSICFORMAT)(pStreamHeader + 1);
// Play 0 ms from the time-stamp in the KSSTREAM_HEADER
pMusicFormat->TimeDeltaMs = 0; RtlCopyMemory((BYTE *)(pMusicFormat + 1), // the actual data
&ulEvent, sizeof(ulEvent));
// setup the stream header
pStreamHeader->Header.Data = pMusicFormat;
pStreamHeader->Header.FrameExtent = sizeof(KSMUSICFORMAT) + sizeof(ULONG); pStreamHeader->Header.Size = sizeof( KSSTREAM_HEADER ); pStreamHeader->Header.DataUsed = pStreamHeader->Header.FrameExtent;
nsPlayTime = GetCurrentMidiTime() - pMidiPin->LastTimeNs + IRP_LATENCY_100NS; pStreamHeader->Header.PresentationTime.Time = nsPlayTime; pStreamHeader->Header.PresentationTime.Numerator = 1; pStreamHeader->Header.PresentationTime.Denominator = 1;
pStreamHeader->pMidiPin = pMidiPin;
//
// Figure out how many bytes in this
// event are valid.
//
bEvent = (BYTE)ulEvent; TheEqualizer = 0; if(!IS_STATUS(bEvent)) { if (pMidiPin->bCurrentStatus) { bEvent = pMidiPin->bCurrentStatus; TheEqualizer = 1; } else { // Bad MIDI Stream didn't have a running status
DPF(DL_WARNING|FA_MIDI,("No running status") ); AudioFreeMemory(sizeof(STREAM_HEADER_EX),&pStreamHeader); RETURN( STATUS_UNSUCCESSFUL ); } }
if(IS_SYSTEM(bEvent)) { if( IS_REALTIME(bEvent) || bEvent == MIDI_TUNEREQ || bEvent == MIDI_SYSX || bEvent == MIDI_EOX ) { pMusicFormat->ByteCount = 1; } else if(bEvent == MIDI_SONGPP) { pMusicFormat->ByteCount = 3; } else { pMusicFormat->ByteCount = 2; } } // Check for three byte messages
else if((bEvent < MIDI_PCHANGE) || (bEvent >= MIDI_PBEND)) { pMusicFormat->ByteCount = 3 - TheEqualizer; } else { pMusicFormat->ByteCount = 2 - TheEqualizer; }
//
// Cache the running status
//
if ( (bEvent >= MIDI_NOTEOFF) && (bEvent < MIDI_CLOCK) ) { pMidiPin->bCurrentStatus = (BYTE)((bEvent < MIDI_SYSX) ? bEvent : 0); }
//
// Initialize our wait event, in case we need to wait.
//
KeInitializeEvent(&keEventObject, SynchronizationEvent, FALSE);
LockedMidiIoCount(pMidiPin,INCREASE);
//
// Need to release the mutex so that during full-duplex
// situations, we can get the midi input buffers down
// to the device without blocking.
//
pWdmaContext = pMidiPin->pMidiDevice->pWdmaContext; WdmaReleaseMutex(pWdmaContext);
// Set the packet to the device.
Status = KsStreamIo( pMidiPin->pFileObject, &keEventObject, // Event
NULL, // PortContext
WriteMidiEventCallBack, pStreamHeader, // CompletionContext
KsInvokeOnSuccess | KsInvokeOnCancel | KsInvokeOnError, &gIoStatusBlock, pStreamHeader, sizeof( KSSTREAM_HEADER ), KSSTREAM_WRITE | KSSTREAM_SYNCHRONOUS, KernelMode );
if ( (Status != STATUS_PENDING) && (Status != STATUS_SUCCESS) ) { DPF(DL_WARNING|FA_MIDI, ("KsStreamIO failed: 0x%08lx",Status)); }
//
// Wait a minute here!!! If the Irp comes back pending, we
// can NOT complete our user mode Irp! But, there is no
// infastructure for storing the Irp in this call stack. The
// other routines use wdmaudPrepareIrp to complete that user
// Irp. Also, pPendingIrpContext is stored in the Irp context
// so that the completion routine has the list to get the
// user mode Irp from.
//
// .... Or, we need to make this routine synchronous and
// wait like WriteMidiOutPin. I believe that this is bug
// #551052. It should be fixed eventually.
//
//
// Here is the fix. Wait if it is pending.
//
if ( STATUS_PENDING == Status ) { //
// Wait for the completion.
//
Status = KeWaitForSingleObject( &keEventObject, Executive, KernelMode, FALSE, (PLARGE_INTEGER) NULL ); } //
// Now grab the mutex again
//
WdmaGrabMutex(pWdmaContext);
RETURN( Status ); }
#pragma LOCKED_CODE
#pragma LOCKED_DATA
//
// This is an IRP completion routine.
//
NTSTATUS WriteMidiCallBack( PDEVICE_OBJECT pDeviceObject, PIRP pIrp, IN PSTREAM_HEADER_EX pStreamHeader ) { //
// If there are any Mdls, free them here otherwise IoCompleteRequest will do it after the
// freeing of our data buffer below.
//
FreeIrpMdls(pIrp); //
// Cleanup after this synchronous write
//
AudioFreeMemory_Unknown(&pStreamHeader->Header.Data); // Music data
wdmaudUnmapBuffer(pStreamHeader->pBufferMdl); AudioFreeMemory_Unknown(&pStreamHeader->pMidiHdr);
AudioFreeMemory(sizeof(STREAM_HEADER_EX),&pStreamHeader); return STATUS_SUCCESS; }
#pragma PAGEABLE_CODE
#pragma PAGEABLE_DATA
NTSTATUS WriteMidiOutPin( LPMIDIHDR pMidiHdr, PSTREAM_HEADER_EX pStreamHeader, BOOL *pCompletedIrp ) { NTSTATUS Status = STATUS_INVALID_DEVICE_REQUEST; PKSMUSICFORMAT pMusicFormat = NULL; KEVENT keEventObject; ULONG AlignedLength; ULONGLONG nsPlayTime; PMIDI_PIN_INSTANCE pMidiPin; PWDMACONTEXT pWdmaContext;
PAGED_CODE();
pMidiPin = pStreamHeader->pMidiPin;
if (!pMidiPin ||!pMidiPin->fGraphRunning || !pMidiPin->pFileObject ) { DPF(DL_WARNING|FA_MIDI,("Not Ready") ); wdmaudUnmapBuffer(pStreamHeader->pBufferMdl); AudioFreeMemory_Unknown( &pMidiHdr ); AudioFreeMemory( sizeof(STREAM_HEADER_EX),&pStreamHeader ); RETURN( STATUS_DEVICE_NOT_READY ); }
//
// FrameExtent contains dwBufferLength right now
//
AlignedLength = ((pStreamHeader->Header.FrameExtent + 3) & ~3);
Status = AudioAllocateMemory_Fixed(sizeof(KSMUSICFORMAT) + AlignedLength, TAG_Audm_MUSIC, ZERO_FILL_MEMORY, &pMusicFormat);
if(!NT_SUCCESS(Status)) { wdmaudUnmapBuffer(pStreamHeader->pBufferMdl); AudioFreeMemory_Unknown( &pMidiHdr ); AudioFreeMemory( sizeof(STREAM_HEADER_EX),&pStreamHeader ); return Status; }
// Play 0 ms from the time-stamp in the KSSTREAM_HEADER
pMusicFormat->TimeDeltaMs = 0;
//
// the system mapped data was stored in the data field
// of the stream header
//
RtlCopyMemory((BYTE *)(pMusicFormat + 1), // the actual data
pStreamHeader->Header.Data, pStreamHeader->Header.FrameExtent);
//
// Setup the number of bytes of midi data we're sending
//
pMusicFormat->ByteCount = pStreamHeader->Header.FrameExtent;
// setup the stream header
pStreamHeader->Header.Data = pMusicFormat;
// Now overwrite FrameExtent with the correct rounded up dword aligned value
pStreamHeader->Header.FrameExtent = sizeof(KSMUSICFORMAT) + AlignedLength; pStreamHeader->Header.OptionsFlags= 0; pStreamHeader->Header.Size = sizeof( KSSTREAM_HEADER ); pStreamHeader->Header.TypeSpecificFlags = 0; pStreamHeader->Header.DataUsed = pStreamHeader->Header.FrameExtent; pStreamHeader->pMidiHdr = pMidiHdr;
nsPlayTime = GetCurrentMidiTime() - pStreamHeader->pMidiPin->LastTimeNs + IRP_LATENCY_100NS; pStreamHeader->Header.PresentationTime.Time = nsPlayTime; pStreamHeader->Header.PresentationTime.Numerator = 1; pStreamHeader->Header.PresentationTime.Denominator = 1;
//
// Initialize our wait event, in case we need to wait.
//
KeInitializeEvent(&keEventObject, SynchronizationEvent, FALSE);
//
// Need to release the mutex so that during full-duplex
// situations, we can get the midi input buffers down
// to the device without blocking.
//
pWdmaContext = pMidiPin->pMidiDevice->pWdmaContext; WdmaReleaseMutex(pWdmaContext);
// Send the packet to the device.
Status = KsStreamIo( pMidiPin->pFileObject, &keEventObject, // Event
NULL, // PortContext
WriteMidiCallBack, pStreamHeader, // CompletionContext
KsInvokeOnSuccess | KsInvokeOnCancel | KsInvokeOnError, &gIoStatusBlock, &pStreamHeader->Header, sizeof( KSSTREAM_HEADER ), KSSTREAM_WRITE | KSSTREAM_SYNCHRONOUS, KernelMode );
//
// Wait if it is pending.
//
if ( STATUS_PENDING == Status ) {
//
// Wait for the completion.
//
Status = KeWaitForSingleObject( &keEventObject, Executive, KernelMode, FALSE, (PLARGE_INTEGER) NULL ); } //
// From the Wait above, we can see that this routine is
// always synchronous. Thus, any Irp that we passed down
// in the KsStreamIo call will have been completed and KS
// will have signaled keEventObject. Thus, we can
// now complete our Irp.
//
// ... Thus we leave pCompletedIrp set to FALSE.
//
//
// Now grab the mutex again
//
WdmaGrabMutex(pWdmaContext);
RETURN( Status ); }
NTSTATUS ResetMidiInPin( PMIDI_PIN_INSTANCE pMidiPin ) { NTSTATUS Status;
PAGED_CODE();
if (!pMidiPin || !pMidiPin->fGraphRunning) { DPF(DL_WARNING|FA_MIDI,("Not Ready") ); RETURN( STATUS_DEVICE_NOT_READY ); }
Status = StateMidiInPin ( pMidiPin, KSSTATE_PAUSE );
RETURN( Status ); }
NTSTATUS StateMidiOutPin( PMIDI_PIN_INSTANCE pMidiPin, KSSTATE State ) { NTSTATUS Status;
PAGED_CODE();
if (!pMidiPin || !pMidiPin->fGraphRunning) { DPF(DL_WARNING|FA_MIDI,("Not Ready") ); RETURN( STATUS_DEVICE_NOT_READY ); }
if (State == KSSTATE_RUN) { pMidiPin->LastTimeNs = GetCurrentMidiTime(); } else if (State == KSSTATE_STOP) { pMidiPin->LastTimeNs = 0; }
Status = StatePin ( pMidiPin->pFileObject, State, &pMidiPin->PinState ) ;
RETURN( Status ); }
//
// Waits for all the Irps to complete.
//
void MidiCompleteIo( PMIDI_PIN_INSTANCE pMidiPin, BOOL Yield ) { PAGED_CODE();
if ( pMidiPin->NumPendingIos ) { DPF(DL_TRACE|FA_MIDI, ("Waiting on %d I/Os to flush Midi device", pMidiPin->NumPendingIos )); if( Yield ) { //
// This is kind of a catch-22. We need to release
// the mutex which was grabbed when we entered the
// ioctl dispatch routine to allow the midi input
// irps which are queued up in a work item waiting
// until the mutex is free in order to be send
// down to portcls.
//
WdmaReleaseMutex(pMidiPin->pMidiDevice->pWdmaContext);
} //
// Wait for all the Irps to complete. The last one will
// signal us to wake.
//
KeWaitForSingleObject ( &pMidiPin->StopEvent, Executive, KernelMode, FALSE, NULL ) ;
if( Yield ) { WdmaGrabMutex(pMidiPin->pMidiDevice->pWdmaContext); }
DPF(DL_TRACE|FA_MIDI, ("Done waiting to flush Midi device")); }
//
// Why do we have this?
//
KeClearEvent ( &pMidiPin->StopEvent );
//
// All the IRPs have completed. We now restore the StoppingSource
// variable so that we can recycle the pMidiPin.
//
pMidiPin->StoppingSource = FALSE;
} //
// If the driver failed the KSSTATE_STOP request, we return that error
// code to the caller.
//
NTSTATUS StopMidiPinAndCompleteIo( PMIDI_PIN_INSTANCE pMidiPin, BOOL Yield ) { NTSTATUS Status;
PAGED_CODE(); //
// Indicate to the completion routine that we are stopping now.
//
pMidiPin->StoppingSource = TRUE;
//
// Tell the driver to stop. Regardless, we will wait for the
// IRPs to complete if there are any outstanding.
//
Status = StatePin( pMidiPin->pFileObject, KSSTATE_STOP, &pMidiPin->PinState ) ; //
// NOTE: On success, the pMidiPin->PinState value will be
// KSSTATE_STOP. On Error it will be the old state.
//
// This raises the question - Do we hang on failure?
//
MidiCompleteIo( pMidiPin,Yield );
return Status; }
NTSTATUS StateMidiInPin( PMIDI_PIN_INSTANCE pMidiPin, KSSTATE State ) { NTSTATUS Status;
PAGED_CODE();
if (!pMidiPin || !pMidiPin->fGraphRunning) { DPF(DL_WARNING|FA_MIDI,("Not Ready") ); RETURN( STATUS_DEVICE_NOT_READY ); }
//
// We need to complete any pending SysEx buffers on a midiInStop
//
//
// Here, if we're asked to go to the paused state and we're not
// already in the paused state, we have to go through a stop.
// Thus we stop the driver, wait for it to complete all the outstanding
// IRPs and then place the driver in pause and place buffers
// down on it again.
//
if( (KSSTATE_PAUSE == State) && (KSSTATE_PAUSE != pMidiPin->PinState) ) { Status = StopMidiPinAndCompleteIo(pMidiPin,TRUE);
//
// If we were successful at stopping the driver, we set
// the pin back up in the pause state.
//
if (NT_SUCCESS(Status)) { ULONG BufferCount;
//
// Put the driver back in the pause state.
//
Status = StatePin ( pMidiPin->pFileObject, State, &pMidiPin->PinState ) ;
if (NT_SUCCESS(Status)) { //
// This loop places STREAM_BUFFERS (128) of them down on the
// device. NumPendingIos should be 128 when this is done.
//
for (BufferCount = 0; BufferCount < STREAM_BUFFERS; BufferCount++) { Status = ReadMidiPin( pMidiPin ); if (!NT_SUCCESS(Status)) { CloseMidiPin( pMidiPin ); //
// Appears that this error path is not correct. If we
// call CloseMidiPin fGraphRunning will get reduced to 0.
// Then, on the next close call CloseMidiPin will assert
// because the pin is not running. We need to be able to
// error out of this path without messing up the fGraphRunning
// state.
//
break; } } } }
} else {
//
// Else we're not going to the pause state, so just make the state
// change.
//
Status = StatePin ( pMidiPin->pFileObject, State, &pMidiPin->PinState ) ; }
RETURN( Status ); }
#pragma LOCKED_CODE
#pragma LOCKED_DATA
NTSTATUS ReadMidiCallBack( PDEVICE_OBJECT pDeviceObject, PIRP pIrp, IN PSTREAM_HEADER_EX pStreamHeader ) { WRITE_CONTEXT *pwc; PMIDI_PIN_INSTANCE pMidiInPin; PMIDIINHDR pMidiInHdr; PKSMUSICFORMAT IrpMusicHdr; ULONG IrpDataLeft; LPBYTE IrpData; ULONG RunningTimeMs; BOOL bResubmit = TRUE; BOOL bDataError = FALSE; NTSTATUS Status = STATUS_SUCCESS; KIRQL OldIrql; PLIST_ENTRY ple;
DPF(DL_TRACE|FA_MIDI, ("Irp.Status = 0x%08lx",pIrp->IoStatus.Status));
pMidiInPin = pStreamHeader->pMidiPin;
//
// No pin should ever be closed before all the Io comes back. So
// we'll sanity check that here.
//
ASSERT(pMidiInPin);
if( pMidiInPin ) { DPF(DL_TRACE|FA_MIDI, ("R%d: 0x%08x", pMidiInPin->NumPendingIos, pStreamHeader));
//
// This routine should do an ExInterlockedRemoveHeadList to get the
// head of the list.
//
if((ple = ExInterlockedRemoveHeadList(&pMidiInPin->MidiInQueueListHead, &pMidiInPin->MidiInQueueSpinLock)) != NULL) { PWDMAPENDINGIRP_CONTEXT pPendingIrpContext; LPMIDIDATA pMidiData; PIRP UserIrp;
//
// We have something to do.
//
pMidiInHdr = CONTAINING_RECORD(ple, MIDIINHDR, Next);
//
// Pull some information into locals
//
IrpData = (LPBYTE)((PKSMUSICFORMAT)(pStreamHeader->Header.Data) + 1); UserIrp = pMidiInHdr->pIrp; pMidiData = pMidiInHdr->pMidiData; pPendingIrpContext = pMidiInHdr->pPendingIrpContext; ASSERT(pPendingIrpContext);
//
// Let's see what we have here
//
DPF(DL_TRACE|FA_MIDI, ("IrpData = 0x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x", *(LPBYTE)IrpData,*(LPBYTE)IrpData+1,*(LPBYTE)IrpData+2, *(LPBYTE)IrpData+3,*(LPBYTE)IrpData+4,*(LPBYTE)IrpData+5, *(LPBYTE)IrpData+6,*(LPBYTE)IrpData+7,*(LPBYTE)IrpData+8, *(LPBYTE)IrpData+9,*(LPBYTE)IrpData+10,*(LPBYTE)IrpData+11) ); //
// Copy over the good stuff...
//
RtlCopyMemory(&pMidiData->StreamHeader, &pStreamHeader->Header, sizeof(KSSTREAM_HEADER)); RtlCopyMemory(&pMidiData->MusicFormat, pStreamHeader->Header.Data, sizeof(KSMUSICFORMAT)); RtlCopyMemory(&pMidiData->MusicData, ((PKSMUSICFORMAT)(pStreamHeader->Header.Data) + 1), 3 * sizeof( DWORD )); // cheesy
//
// unlock memory before completing the Irp
//
wdmaudUnmapBuffer(pMidiInHdr->pMdl); AudioFreeMemory_Unknown(&pMidiInHdr);
//
// Now complete the Irp for wdmaud.drv to process
//
wdmaudUnprepareIrp( UserIrp, pIrp->IoStatus.Status, sizeof(MIDIDATA), pPendingIrpContext ); } else { // !!! Break here to catch underflow !!!
if (pIrp->IoStatus.Status == STATUS_SUCCESS) { DPF(DL_TRACE|FA_MIDI, ("!!! Underflowing MIDI Input !!!")); //_asm { int 3 };
} } } //
// If there are any Mdls, free them here otherwise IoCompleteRequest will do it after the
// freeing of our data buffer below.
//
FreeIrpMdls(pIrp);
AudioFreeMemory(sizeof(STREAM_HEADER_EX),&pStreamHeader);
if(pMidiInPin) { KeAcquireSpinLock(&pMidiInPin->MidiPinSpinLock,&OldIrql);
pMidiInPin->NumPendingIos--;
if ( pMidiInPin->StoppingSource || (pIrp->IoStatus.Status == STATUS_CANCELLED) || (pIrp->IoStatus.Status == STATUS_NO_SUCH_DEVICE) || (pIrp->Cancel) ) { bResubmit = FALSE;
if ( 0 == pMidiInPin->NumPendingIos ) { KeSetEvent ( &pMidiInPin->StopEvent, 0, FALSE ) ; } } //
// We need to be careful about using pMidiPin after releasing the spinlock.
// if we are closing down and the NumPendingIos goes to zero the pMidiPin
// can be freed. In that case we must not touch pMidiPin. bResubmit
// protects us below.
//
KeReleaseSpinLock(&pMidiInPin->MidiPinSpinLock, OldIrql);
//
// Resubmit to keep the cycle going...and going. Note that bResubmit
// must be first in this comparison. If bResubmit is FALSE, then pMidiInPin
// could be freed.
//
if (bResubmit && pMidiInPin->fGraphRunning ) { //
// This call to ReadMidiPin causes wdmaud.sys to place another
// buffer down on the device. One call, one buffer.
//
ReadMidiPin(pMidiInPin); } }
return STATUS_SUCCESS; }
//
// Called from Irp completion routine, thus this code must be locked.
//
NTSTATUS ReadMidiPin( PMIDI_PIN_INSTANCE pMidiPin ) { PKSMUSICFORMAT pMusicFormat; PSTREAM_HEADER_EX pStreamHeader = NULL; PWORK_QUEUE_ITEM pWorkItem; NTSTATUS Status = STATUS_SUCCESS;
DPF(DL_TRACE|FA_MIDI, ("Entered"));
if (!pMidiPin->fGraphRunning) { DPF(DL_WARNING|FA_MIDI,("Bad fGraphRunning") ); RETURN( STATUS_DEVICE_NOT_READY ); }
Status = AudioAllocateMemory_Fixed(sizeof(STREAM_HEADER_EX) + sizeof(WORK_QUEUE_ITEM) + MUSICBUFFERSIZE, TAG_Audh_STREAMHEADER, ZERO_FILL_MEMORY, &pStreamHeader);
if(!NT_SUCCESS(Status)) { RETURN( Status ); }
pWorkItem = (PWORK_QUEUE_ITEM)(pStreamHeader + 1);
pStreamHeader->Header.Size = sizeof( KSSTREAM_HEADER ); pStreamHeader->Header.PresentationTime.Numerator = 10000; pStreamHeader->Header.PresentationTime.Denominator = 1;
pMusicFormat = (PKSMUSICFORMAT)((BYTE *)pWorkItem + sizeof(WORK_QUEUE_ITEM)); pStreamHeader->Header.Data = pMusicFormat; pStreamHeader->Header.FrameExtent = MUSICBUFFERSIZE;
pStreamHeader->pMidiPin = pMidiPin;
ASSERT( pMidiPin->pFileObject );
//
// Increase the number of outstanding IRPs as we get ready to add
// this one to the list.
//
LockedMidiIoCount( pMidiPin,INCREASE ); ObReferenceObject( pMidiPin->pFileObject );
Status = QueueWorkList( pMidiPin->pMidiDevice->pWdmaContext, ReadMidiEventWorkItem, pStreamHeader, 0 ); if (!NT_SUCCESS(Status)) { //
// If the memory allocation fails in QueueWorkItem then it can fail. We
// will need to free our memory and unlock things.
//
LockedMidiIoCount(pMidiPin,DECREASE); ObDereferenceObject(pMidiPin->pFileObject); AudioFreeMemory( sizeof(STREAM_HEADER_EX),&pStreamHeader ); }
RETURN( Status ); }
#pragma PAGEABLE_CODE
#pragma PAGEABLE_DATA
//
// This is a work item that midi schedules. Notice that the caller did a reference
// on the file object so that it would still be valid when we're here. We should never
// get called and find that the file object is invalid. Same holds for the StreamHeader
// and the corresponding pMidiPin.
//
VOID ReadMidiEventWorkItem( PSTREAM_HEADER_EX pStreamHeader, PVOID NotUsed ) { NTSTATUS Status = STATUS_UNSUCCESSFUL; PFILE_OBJECT MidiFileObject;
PAGED_CODE();
ASSERT( pStreamHeader->pMidiPin->pFileObject );
DPF(DL_TRACE|FA_MIDI, ("A%d: 0x%08x", pStreamHeader->pMidiPin->NumPendingIos, pStreamHeader));
//
// We need to store the MidiFileObject here because the pStreamHeader
// will/may get freed during the KsStreamIo call. Basically, when
// you call KsStreamIo the Irp may get completed and the pStreamHeader
// will get freed. But, it's safe to store the file object because of
// this reference count.
//
MidiFileObject = pStreamHeader->pMidiPin->pFileObject;
Status = KsStreamIo( pStreamHeader->pMidiPin->pFileObject, NULL, // Event
NULL, // PortContext
ReadMidiCallBack, pStreamHeader, // CompletionContext
KsInvokeOnSuccess | KsInvokeOnCancel | KsInvokeOnError, &gIoStatusBlock, &pStreamHeader->Header, sizeof( KSSTREAM_HEADER ), KSSTREAM_READ, KernelMode );
//
// We are done with the file object.
//
ObDereferenceObject( MidiFileObject );
// WorkItem: shouldn't this be if( !NTSUCCESS(Status) )?
if ( STATUS_UNSUCCESSFUL == Status ) DPF(DL_WARNING|FA_MIDI, ("KsStreamIo failed2: Status = 0x%08lx", Status));
//
// Warning: If, for any reason, the completion routine is not called
// for this Irp, wdmaud.sys will hang. It's been discovered that
// KsStreamIo may error out in low memory conditions. There is an
// outstanding bug to address this.
//
return; }
//
// pNewMidiHdr will always be valid. The caller just allocated it!
//
NTSTATUS AddBufferToMidiInQueue( PMIDI_PIN_INSTANCE pMidiPin, PMIDIINHDR pNewMidiInHdr ) { NTSTATUS Status = STATUS_SUCCESS; PMIDIINHDR pTemp;
PAGED_CODE();
if (!pMidiPin || !pMidiPin->fGraphRunning) { DPF(DL_WARNING|FA_MIDI,("Bad fGraphRunning") ); RETURN( STATUS_DEVICE_NOT_READY ); }
DPF(DL_TRACE|FA_MIDI, ("received sysex buffer"));
ExInterlockedInsertTailList(&pMidiPin->MidiInQueueListHead, &pNewMidiInHdr->Next, &pMidiPin->MidiInQueueSpinLock);
Status = STATUS_PENDING;
RETURN( Status ); }
VOID CleanupMidiDevices( IN PWDMACONTEXT pWdmaContext ) { DWORD DeviceNumber; DWORD DeviceType; PMIDI_PIN_INSTANCE pMidiPin=NULL;
PAGED_CODE(); for (DeviceNumber = 0; DeviceNumber < MAXNUMDEVS; DeviceNumber++) { for (DeviceType = MidiInDevice; DeviceType < MixerDevice; DeviceType++) { if (DeviceType == MidiInDevice) { pMidiPin = pWdmaContext->MidiInDevs[DeviceNumber].pMidiPin; } else if (DeviceType == MidiOutDevice) { pMidiPin = pWdmaContext->MidiOutDevs[DeviceNumber].pMidiPin; } else { ASSERT(!"CleanupMidiDevices: Out of range!"); }
if (pWdmaContext->apCommonDevice[DeviceType][DeviceNumber]->Device != UNUSED_DEVICE) { if (pMidiPin != NULL) { NTSTATUS Status; KSSTATE State;
StopMidiPinAndCompleteIo( pMidiPin, FALSE );
//
// Probably redundant, but this frees memory associated
// with the MIDI device.
//
if( DeviceType == MidiInDevice ) { CloseMidiDevicePin(&pWdmaContext->MidiInDevs[DeviceNumber]); } if( DeviceType == MidiOutDevice ) { CloseMidiDevicePin(&pWdmaContext->MidiOutDevs[DeviceNumber]); }
} // end for active pins
} // end for valid Device
} // end for DeviceTypes
} // end for DeviceNumber
} // CleanupMidiDevices
|