/**************************************************************************** * * 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