/***************************************************************************** * common.cpp - Common code used by all the sb16 miniports. ***************************************************************************** * Copyright (c) 1997-2000 Microsoft Corporation. All rights reserved. * * Implmentation of the common code object. This class deals with interrupts * for the device, and is a collection of common code used by all the * miniports. */ #include "common.h" #define STR_MODULENAME "sb16Adapter: " /***************************************************************************** * CAdapterCommon ***************************************************************************** * Adapter common object. */ class CAdapterCommon : public IAdapterCommon, public IAdapterPowerManagement, public CUnknown { private: PINTERRUPTSYNC m_pInterruptSync; PUCHAR m_pWaveBase; PWAVEMINIPORTSB16 m_WaveMiniportSB16; #ifdef EVENT_SUPPORT PTOPOMINIPORTSB16 m_TopoMiniportSB16; // Topology miniport of SB16. #endif PDEVICE_OBJECT m_pDeviceObject; DEVICE_POWER_STATE m_PowerState; BYTE MixerSettings[DSP_MIX_MAXREGS]; void AcknowledgeIRQ ( void ); public: DECLARE_STD_UNKNOWN(); DEFINE_STD_CONSTRUCTOR(CAdapterCommon); ~CAdapterCommon(); /***************************************************************************** * IAdapterCommon methods */ STDMETHODIMP_(NTSTATUS) Init ( IN PRESOURCELIST ResourceList, IN PDEVICE_OBJECT DeviceObject ); STDMETHODIMP_(PINTERRUPTSYNC) GetInterruptSync ( void ); STDMETHODIMP_(void) SetWaveMiniport (IN PWAVEMINIPORTSB16 Miniport) { m_WaveMiniportSB16 = Miniport; } STDMETHODIMP_(BYTE) ReadController ( void ); STDMETHODIMP_(BOOLEAN) WriteController ( IN BYTE Value ); STDMETHODIMP_(NTSTATUS) ResetController ( void ); STDMETHODIMP_(void) MixerRegWrite ( IN BYTE Index, IN BYTE Value ); STDMETHODIMP_(BYTE) MixerRegRead ( IN BYTE Index ); STDMETHODIMP_(void) MixerReset ( void ); STDMETHODIMP RestoreMixerSettingsFromRegistry ( void ); STDMETHODIMP SaveMixerSettingsToRegistry ( void ); #ifdef EVENT_SUPPORT // // The topology miniport needs to tell us the pointer to the Event-interface. // STDMETHODIMP_(void) SetTopologyMiniport (IN PTOPOMINIPORTSB16 Miniport) { m_TopoMiniportSB16 = Miniport; }; #endif /************************************************************************* * IAdapterPowerManagement implementation * * This macro is from PORTCLS.H. It lists all the interface's functions. */ IMP_IAdapterPowerManagement; friend NTSTATUS InterruptServiceRoutine ( IN PINTERRUPTSYNC InterruptSync, IN PVOID DynamicContext ); }; static MIXERSETTING DefaultMixerSettings[] = { { L"LeftMasterVol", DSP_MIX_MASTERVOLIDX_L, 0xD8 }, { L"RightMasterVol", DSP_MIX_MASTERVOLIDX_R, 0xD8 }, { L"LeftWaveVol", DSP_MIX_VOICEVOLIDX_L, 0xD8 }, { L"RightWaveVol", DSP_MIX_VOICEVOLIDX_R, 0xD8 }, { L"LeftMidiVol", DSP_MIX_FMVOLIDX_L, 0xD8 }, { L"RightMidiVol", DSP_MIX_FMVOLIDX_R, 0xD8 }, { L"LeftCDVol", DSP_MIX_CDVOLIDX_L, 0xD8 }, { L"RightCDVol", DSP_MIX_CDVOLIDX_R, 0xD8 }, { L"LeftLineInVol", DSP_MIX_LINEVOLIDX_L, 0xD8 }, { L"RightLineInVol", DSP_MIX_LINEVOLIDX_R, 0xD8 }, { L"MicVol", DSP_MIX_MICVOLIDX, 0xD8 }, { L"PcSpkrVol", DSP_MIX_SPKRVOLIDX, 0x00 }, { L"OutputMixer", DSP_MIX_OUTMIXIDX, 0x1E }, { L"LeftInputMixer", DSP_MIX_ADCMIXIDX_L, 0x55 }, { L"RightInputMixer", DSP_MIX_ADCMIXIDX_R, 0x2B }, { L"LeftInputGain", DSP_MIX_INGAINIDX_L, 0x00 }, { L"RightInputGain", DSP_MIX_INGAINIDX_R, 0x00 }, { L"LeftOutputGain", DSP_MIX_OUTGAINIDX_L, 0x80 }, { L"RightOutputGain", DSP_MIX_OUTGAINIDX_R, 0x80 }, { L"MicAGC", DSP_MIX_AGCIDX, 0x01 }, { L"LeftTreble", DSP_MIX_TREBLEIDX_L, 0x80 }, { L"RightTreble", DSP_MIX_TREBLEIDX_R, 0x80 }, { L"LeftBass", DSP_MIX_BASSIDX_L, 0x80 }, { L"RightBass", DSP_MIX_BASSIDX_R, 0x80 }, }; #pragma code_seg("PAGE") /***************************************************************************** * NewAdapterCommon() ***************************************************************************** * Create a new adapter common object. */ NTSTATUS NewAdapterCommon ( OUT PUNKNOWN * Unknown, IN REFCLSID, IN PUNKNOWN UnknownOuter OPTIONAL, IN POOL_TYPE PoolType ) { PAGED_CODE(); ASSERT(Unknown); STD_CREATE_BODY_ ( CAdapterCommon, Unknown, UnknownOuter, PoolType, PADAPTERCOMMON ); } /***************************************************************************** * CAdapterCommon::Init() ***************************************************************************** * Initialize an adapter common object. */ NTSTATUS CAdapterCommon:: Init ( IN PRESOURCELIST ResourceList, IN PDEVICE_OBJECT DeviceObject ) { PAGED_CODE(); ASSERT(ResourceList); ASSERT(DeviceObject); // // Make sure we have the resources we expect // if ((ResourceList->NumberOfPorts() < 1) || (ResourceList->NumberOfInterrupts() != 1)) { _DbgPrintF (DEBUGLVL_TERSE, ("unknown configuration; check your code!")); // Bail out. return STATUS_INSUFFICIENT_RESOURCES; } m_pDeviceObject = DeviceObject; m_WaveMiniportSB16 = NULL; #ifdef EVENT_SUPPORT m_TopoMiniportSB16 = NULL; #endif // // Get the base address for the wave device. // ASSERT(ResourceList->FindTranslatedPort(0)); m_pWaveBase = (PUCHAR)(ResourceList->FindTranslatedPort(0)->u.Port.Start.QuadPart); // // Set initial device power state // m_PowerState = PowerDeviceD0; // // Reset the hardware. // NTSTATUS ntStatus = ResetController(); if(NT_SUCCESS(ntStatus)) { _DbgPrintF(DEBUGLVL_VERBOSE,("ResetController Succeeded")); AcknowledgeIRQ(); // // Hook up the interrupt. // ntStatus = PcNewInterruptSync( // See portcls.h &m_pInterruptSync, // Save object ptr NULL, // OuterUnknown(optional). ResourceList, // He gets IRQ from ResourceList. 0, // Resource Index InterruptSyncModeNormal // Run ISRs once until we get SUCCESS ); if (NT_SUCCESS(ntStatus) && m_pInterruptSync) { // run this ISR first ntStatus = m_pInterruptSync->RegisterServiceRoutine(InterruptServiceRoutine,PVOID(this),FALSE); if (NT_SUCCESS(ntStatus)) { ntStatus = m_pInterruptSync->Connect(); } // if we could not connect or register the ISR, release the object. if (!NT_SUCCESS (ntStatus)) { m_pInterruptSync->Release(); m_pInterruptSync = NULL; } } } else { _DbgPrintF(DEBUGLVL_TERSE,("ResetController Failure")); } return ntStatus; } /***************************************************************************** * CAdapterCommon::~CAdapterCommon() ***************************************************************************** * Destructor. */ CAdapterCommon:: ~CAdapterCommon ( void ) { PAGED_CODE(); _DbgPrintF(DEBUGLVL_VERBOSE,("[CAdapterCommon::~CAdapterCommon]")); if (m_pInterruptSync) { m_pInterruptSync->Disconnect(); m_pInterruptSync->Release(); m_pInterruptSync = NULL; } } /***************************************************************************** * CAdapterCommon::NonDelegatingQueryInterface() ***************************************************************************** * Obtains an interface. */ STDMETHODIMP CAdapterCommon:: NonDelegatingQueryInterface ( REFIID Interface, PVOID * Object ) { PAGED_CODE(); ASSERT(Object); if (IsEqualGUIDAligned(Interface,IID_IUnknown)) { *Object = PVOID(PUNKNOWN(PADAPTERCOMMON(this))); } else if (IsEqualGUIDAligned(Interface,IID_IAdapterCommon)) { *Object = PVOID(PADAPTERCOMMON(this)); } else if (IsEqualGUIDAligned(Interface,IID_IAdapterPowerManagement)) { *Object = PVOID(PADAPTERPOWERMANAGEMENT(this)); } else { *Object = NULL; } if (*Object) { PUNKNOWN(*Object)->AddRef(); return STATUS_SUCCESS; } return STATUS_INVALID_PARAMETER; } /***************************************************************************** * CAdapterCommon::GetInterruptSync() ***************************************************************************** * Get a pointer to the interrupt synchronization object. */ STDMETHODIMP_(PINTERRUPTSYNC) CAdapterCommon:: GetInterruptSync ( void ) { PAGED_CODE(); return m_pInterruptSync; } #pragma code_seg() /***************************************************************************** * CAdapterCommon::ReadController() ***************************************************************************** * Read a byte from the controller. */ STDMETHODIMP_(BYTE) CAdapterCommon:: ReadController ( void ) { BYTE returnValue = BYTE(-1); ASSERT(m_pWaveBase); ULONGLONG startTime = PcGetTimeInterval(0); do { if (READ_PORT_UCHAR (m_pWaveBase + DSP_REG_DATAAVAIL) & 0x80) { returnValue = READ_PORT_UCHAR (m_pWaveBase + DSP_REG_READ); } } while ((PcGetTimeInterval(startTime) < GTI_MILLISECONDS(100)) && (BYTE(-1) == returnValue)); ASSERT((BYTE(-1) != returnValue) || !"ReadController timeout!"); return returnValue; } /***************************************************************************** * CAdapterCommon::WriteController() ***************************************************************************** * Write a byte to the controller. */ STDMETHODIMP_(BOOLEAN) CAdapterCommon:: WriteController ( IN BYTE Value ) { ASSERT(m_pWaveBase); BOOLEAN returnValue = FALSE; ULONGLONG startTime = PcGetTimeInterval(0); do { BYTE status = READ_PORT_UCHAR (m_pWaveBase + DSP_REG_WRITE); if ((status & 0x80) == 0) { WRITE_PORT_UCHAR (m_pWaveBase + DSP_REG_WRITE, Value); returnValue = TRUE; } } while ((PcGetTimeInterval(startTime) < GTI_MILLISECONDS(100)) && ! returnValue); ASSERT(returnValue || !"WriteController timeout"); return returnValue; } /***************************************************************************** * CAdapterCommon::MixerRegWrite() ***************************************************************************** * Writes a mixer register. */ STDMETHODIMP_(void) CAdapterCommon:: MixerRegWrite ( IN BYTE Index, IN BYTE Value ) { ASSERT( m_pWaveBase ); BYTE actualIndex; // only hit the hardware if we're in an acceptable power state if( m_PowerState <= PowerDeviceD1 ) { actualIndex = (BYTE) ((Index < 0x80) ? (Index + DSP_MIX_BASEIDX) : Index); WRITE_PORT_UCHAR (m_pWaveBase + DSP_REG_MIXREG, actualIndex); WRITE_PORT_UCHAR (m_pWaveBase + DSP_REG_MIXDATA, Value); } if(Index < DSP_MIX_MAXREGS) { MixerSettings[Index] = Value; } } /***************************************************************************** * CAdapterCommon::MixerRegRead() ***************************************************************************** * Reads a mixer register. */ STDMETHODIMP_(BYTE) CAdapterCommon:: MixerRegRead ( IN BYTE Index ) { if(Index < DSP_MIX_MAXREGS) { return MixerSettings[Index]; } // // Not in the cache? Read from HW directly. // // We need to make sure that we can access the HW directly for // the volumes that can change externally. // This is done here with passing an index outside of the cache. // Since the an index=0 is actually DSP_MIX_BASEIDX which is less // than the cache size (DSP_MIX_MAXREGS), you can access any volume // directly with passing DSP_MIX_BASEIDX + index. // You could also pass a flag - but we want to keep the changes // minimal - or create a new function like MixerRegReadDirect(). // WRITE_PORT_UCHAR (m_pWaveBase + DSP_REG_MIXREG, Index); return READ_PORT_UCHAR (m_pWaveBase + DSP_REG_MIXDATA); } /***************************************************************************** * CAdapterCommon::MixerReset() ***************************************************************************** * Resets the mixer */ STDMETHODIMP_(void) CAdapterCommon:: MixerReset ( void ) { ASSERT(m_pWaveBase); WRITE_PORT_UCHAR (m_pWaveBase + DSP_REG_MIXREG, DSP_MIX_DATARESETIDX); WRITE_PORT_UCHAR (m_pWaveBase + DSP_REG_MIXDATA, 0); RestoreMixerSettingsFromRegistry(); } /***************************************************************************** * CAdapterCommon::AcknowledgeIRQ() ***************************************************************************** * Acknowledge interrupt request. */ void CAdapterCommon:: AcknowledgeIRQ ( void ) { ASSERT(m_pWaveBase); READ_PORT_UCHAR (m_pWaveBase + DSP_REG_ACK16BIT); READ_PORT_UCHAR (m_pWaveBase + DSP_REG_ACK8BIT); } /***************************************************************************** * CAdapterCommon::ResetController() ***************************************************************************** * Resets the controller. */ STDMETHODIMP_(NTSTATUS) CAdapterCommon:: ResetController(void) { NTSTATUS ntStatus = STATUS_UNSUCCESSFUL; // write a 1 to the reset bit WRITE_PORT_UCHAR (m_pWaveBase + DSP_REG_RESET,1); // wait for at least 3 microseconds KeStallExecutionProcessor (5L); // okay, 5us // write a 0 to the reset bit WRITE_PORT_UCHAR (m_pWaveBase + DSP_REG_RESET,0); // hang out for 100us KeStallExecutionProcessor (100L); // read the controller BYTE ReadVal = ReadController (); // check return value if( ReadVal == BYTE(0xAA) ) { ntStatus = STATUS_SUCCESS; } return ntStatus; } /***************************************************************************** * CAdapterCommon::RestoreMixerSettingsFromRegistry() ***************************************************************************** * Restores the mixer settings based on settings stored in the registry. */ STDMETHODIMP CAdapterCommon:: RestoreMixerSettingsFromRegistry ( void ) { PREGISTRYKEY DriverKey; PREGISTRYKEY SettingsKey; _DbgPrintF(DEBUGLVL_VERBOSE,("[RestoreMixerSettingsFromRegistry]")); // open the driver registry key NTSTATUS ntStatus = PcNewRegistryKey( &DriverKey, // IRegistryKey NULL, // OuterUnknown DriverRegistryKey, // Registry key type KEY_ALL_ACCESS, // Access flags m_pDeviceObject, // Device object NULL, // Subdevice NULL, // ObjectAttributes 0, // Create options NULL ); // Disposition if(NT_SUCCESS(ntStatus)) { UNICODE_STRING KeyName; ULONG Disposition; // make a unicode strong for the subkey name RtlInitUnicodeString( &KeyName, L"Settings" ); // open the settings subkey ntStatus = DriverKey->NewSubKey( &SettingsKey, // Subkey NULL, // OuterUnknown KEY_ALL_ACCESS, // Access flags &KeyName, // Subkey name REG_OPTION_NON_VOLATILE, // Create options &Disposition ); if(NT_SUCCESS(ntStatus)) { ULONG ResultLength; if(Disposition == REG_CREATED_NEW_KEY) { // copy default settings for(ULONG i = 0; i < SIZEOF_ARRAY(DefaultMixerSettings); i++) { MixerRegWrite( DefaultMixerSettings[i].RegisterIndex, DefaultMixerSettings[i].RegisterSetting ); } } else { // allocate data to hold key info PVOID KeyInfo = ExAllocatePool(PagedPool, sizeof(KEY_VALUE_PARTIAL_INFORMATION) + sizeof(DWORD)); if(NULL != KeyInfo) { // loop through all mixer settings for(UINT i = 0; i < SIZEOF_ARRAY(DefaultMixerSettings); i++) { // init key name RtlInitUnicodeString( &KeyName, DefaultMixerSettings[i].KeyName ); // query the value key ntStatus = SettingsKey->QueryValueKey( &KeyName, KeyValuePartialInformation, KeyInfo, sizeof(KEY_VALUE_PARTIAL_INFORMATION) + sizeof(DWORD), &ResultLength ); if(NT_SUCCESS(ntStatus)) { PKEY_VALUE_PARTIAL_INFORMATION PartialInfo = PKEY_VALUE_PARTIAL_INFORMATION(KeyInfo); if(PartialInfo->DataLength == sizeof(DWORD)) { // set mixer register to registry value MixerRegWrite( DefaultMixerSettings[i].RegisterIndex, BYTE(*(PDWORD(PartialInfo->Data))) ); } } else { // if key access failed, set to default MixerRegWrite( DefaultMixerSettings[i].RegisterIndex, DefaultMixerSettings[i].RegisterSetting ); } } // free the key info ExFreePool(KeyInfo); } else { // copy default settings for(ULONG i = 0; i < SIZEOF_ARRAY(DefaultMixerSettings); i++) { MixerRegWrite( DefaultMixerSettings[i].RegisterIndex, DefaultMixerSettings[i].RegisterSetting ); } ntStatus = STATUS_INSUFFICIENT_RESOURCES; } } // release the settings key SettingsKey->Release(); } // release the driver key DriverKey->Release(); } return ntStatus; } /***************************************************************************** * CAdapterCommon::SaveMixerSettingsToRegistry() ***************************************************************************** * Saves the mixer settings to the registry. */ STDMETHODIMP CAdapterCommon:: SaveMixerSettingsToRegistry ( void ) { PREGISTRYKEY DriverKey; PREGISTRYKEY SettingsKey; _DbgPrintF(DEBUGLVL_VERBOSE,("[SaveMixerSettingsToRegistry]")); // open the driver registry key NTSTATUS ntStatus = PcNewRegistryKey( &DriverKey, // IRegistryKey NULL, // OuterUnknown DriverRegistryKey, // Registry key type KEY_ALL_ACCESS, // Access flags m_pDeviceObject, // Device object NULL, // Subdevice NULL, // ObjectAttributes 0, // Create options NULL ); // Disposition if(NT_SUCCESS(ntStatus)) { UNICODE_STRING KeyName; // make a unicode strong for the subkey name RtlInitUnicodeString( &KeyName, L"Settings" ); // open the settings subkey ntStatus = DriverKey->NewSubKey( &SettingsKey, // Subkey NULL, // OuterUnknown KEY_ALL_ACCESS, // Access flags &KeyName, // Subkey name REG_OPTION_NON_VOLATILE, // Create options NULL ); if(NT_SUCCESS(ntStatus)) { // loop through all mixer settings for(UINT i = 0; i < SIZEOF_ARRAY(MixerSettings); i++) { // init key name RtlInitUnicodeString( &KeyName, DefaultMixerSettings[i].KeyName ); // set the key DWORD KeyValue = DWORD(MixerSettings[DefaultMixerSettings[i].RegisterIndex]); ntStatus = SettingsKey->SetValueKey( &KeyName, // Key name REG_DWORD, // Key type PVOID(&KeyValue), sizeof(DWORD) ); if(!NT_SUCCESS(ntStatus)) { break; } } // release the settings key SettingsKey->Release(); } // release the driver key DriverKey->Release(); } return ntStatus; } /***************************************************************************** * CAdapterCommon::PowerChangeState() ***************************************************************************** * Change power state for the device. */ STDMETHODIMP_(void) CAdapterCommon:: PowerChangeState ( IN POWER_STATE NewState ) { UINT i; _DbgPrintF( DEBUGLVL_VERBOSE, ("[CAdapterCommon::PowerChangeState]")); // Is this actually a state change? if( NewState.DeviceState != m_PowerState ) { // switch on new state switch( NewState.DeviceState ) { case PowerDeviceD0: // Insert your code here for entering the full power state (D0). // This code may be a function of the current power state. Note that // property accesses such as volume and mute changes may occur when // the device is in a sleep state (D1-D3) and should be cached in the // driver to be restored upon entering D0. However, it should also be // noted that new miniport and new streams will only be attempted at // D0 -- PortCls will place the device in D0 prior to the NewStream call. // Save the new state. This local value is used to determine when to cache // property accesses and when to permit the driver from accessing the hardware. m_PowerState = NewState.DeviceState; // restore mixer settings for(i = 0; i < DSP_MIX_MAXREGS - 1; i++) { if( i != DSP_MIX_MICVOLIDX ) { MixerRegWrite( BYTE(i), MixerSettings[i] ); } } if (m_WaveMiniportSB16) { m_WaveMiniportSB16->RestoreSampleRate(); } break; case PowerDeviceD1: // This sleep state is the lowest latency sleep state with respect to the // latency time required to return to D0. The driver can still access // the hardware in this state if desired. If the driver is not being used // an inactivity timer in PortCls will place the driver in this state after // a timeout period controllable via the registry. case PowerDeviceD2: // This is a medium latency sleep state. In this state the device driver // cannot assume that it can touch the hardware so any accesses need to be // cached and the hardware restored upon entering D0 (or D1 conceivably). case PowerDeviceD3: // This is a full hibernation state and is the longest latency sleep state. // The driver cannot access the hardware in this state and must cache any // hardware accesses and restore the hardware upon returning to D0 (or D1). // Save the new state. m_PowerState = NewState.DeviceState; _DbgPrintF(DEBUGLVL_VERBOSE,(" Entering D%d",ULONG(m_PowerState)-ULONG(PowerDeviceD0))); break; default: _DbgPrintF(DEBUGLVL_VERBOSE,(" Unknown Device Power State")); break; } } } /***************************************************************************** * CAdapterCommon::QueryPowerChangeState() ***************************************************************************** * Query to see if the device can * change to this power state */ STDMETHODIMP_(NTSTATUS) CAdapterCommon:: QueryPowerChangeState ( IN POWER_STATE NewStateQuery ) { _DbgPrintF( DEBUGLVL_TERSE, ("[CAdapterCommon::QueryPowerChangeState]")); // Check here to see of a legitimate state is being requested // based on the device state and fail the call if the device/driver // cannot support the change requested. Otherwise, return STATUS_SUCCESS. // Note: A QueryPowerChangeState() call is not guaranteed to always preceed // a PowerChangeState() call. return STATUS_SUCCESS; } /***************************************************************************** * CAdapterCommon::QueryDeviceCapabilities() ***************************************************************************** * Called at startup to get the caps for the device. This structure provides * the system with the mappings between system power state and device power * state. This typically will not need modification by the driver. * */ STDMETHODIMP_(NTSTATUS) CAdapterCommon:: QueryDeviceCapabilities ( IN PDEVICE_CAPABILITIES PowerDeviceCaps ) { _DbgPrintF( DEBUGLVL_TERSE, ("[CAdapterCommon::QueryDeviceCapabilities]")); return STATUS_SUCCESS; } /***************************************************************************** * InterruptServiceRoutine() ***************************************************************************** * ISR. */ NTSTATUS InterruptServiceRoutine ( IN PINTERRUPTSYNC InterruptSync, IN PVOID DynamicContext ) { ASSERT(InterruptSync); ASSERT(DynamicContext); CAdapterCommon *that = (CAdapterCommon *) DynamicContext; // // We are here because the MPU tried and failed, so // must be a wave interrupt. // ASSERT(that->m_pWaveBase); // // Read the Interrupt status register. // BYTE IntrStatus = that->MixerRegRead (0x82); // // In case we really read the interrupt status register, we should // also USE it and make sure that we really have a wave interrupt // and not something else! // if (IntrStatus & 0x03) // Voice8 or Voice16 Interrupt { // // Make sure there is a wave miniport. // if (that->m_WaveMiniportSB16) { // // Tell it it needs to do some work. // that->m_WaveMiniportSB16->ServiceWaveISR (); } // // ACK the ISR. // that->AcknowledgeIRQ(); } #ifdef EVENT_SUPPORT // // This code will fire a volume event in case the HW volume has changed. // else if (IntrStatus & 0x10) // Volume interrupt on C16X-mixers { // // Ack vol interrupt // IntrStatus &= ~0x10; that->MixerRegWrite (0x82, IntrStatus); // // Generate an event for the master volume (as an example) // if (that->m_TopoMiniportSB16) { that->m_TopoMiniportSB16->ServiceEvent (); } } #endif return STATUS_SUCCESS; }