Sample Code

Windows Driver Samples/ Microsoft slate system virtual audio device driver sample/ C++/ common.cpp/

/*++

Copyright (c) Microsoft Corporation All Rights Reserved

Module Name:

    common.cpp

Abstract:

    Implementation of the AdapterCommon class. 

--*/

#pragma warning (disable : 4127)

#include <sysvad.h>
#include "hw.h"
#include "savedata.h"
#include "IHVPrivatePropertySet.h"
#include "simple.h"

#ifdef SYSVAD_BTH_BYPASS
#include <limits.h>
#include <initguid.h>
#include <bthhfpddi.h>
#include <wdmguid.h>    // guild-arrival/removal
#include <devpkey.h>
#include "bthhfpminipairs.h"

// # of sec before sync request is cancelled.
#define BTH_HFP_SYNC_REQ_TIMEOUT_IN_SEC         60 
#define BTH_HFP_NOTIFICATION_MAX_ERROR_COUNT    5

#endif // SYSVAD_BTH_BYPASS

//-----------------------------------------------------------------------------
// CSaveData statics
//-----------------------------------------------------------------------------

PSAVEWORKER_PARAM       CSaveData::m_pWorkItems = NULL;
PDEVICE_OBJECT          CSaveData::m_pDeviceObject = NULL;

//=============================================================================
// Classes
//=============================================================================
#ifdef SYSVAD_BTH_BYPASS
class BthHfpDevice;     // Forward declaration.
#endif // SYSVAD_BTH_BYPASS

///////////////////////////////////////////////////////////////////////////////
// CAdapterCommon
//   

class CAdapterCommon : 
    public IAdapterCommon,
    public IAdapterPowerManagement,
    public CUnknown    
{
    private:
        PSERVICEGROUP           m_pServiceGroupWave;
        PDEVICE_OBJECT          m_pDeviceObject;
        PDEVICE_OBJECT          m_pPhysicalDeviceObject;
        WDFDEVICE               m_WdfDevice;            // Wdf device. 
        DEVICE_POWER_STATE      m_PowerState;  

        PCSYSVADHW              m_pHW;                  // Virtual SYSVAD HW object
        PPORTCLSETWHELPER       m_pPortClsEtwHelper;

        static LONG             m_AdapterInstances;     // # of adapter objects.
       
    public:
        //=====================================================================
        // Default CUnknown
        DECLARE_STD_UNKNOWN();
        DEFINE_STD_CONSTRUCTOR(CAdapterCommon);
        ~CAdapterCommon();

        //=====================================================================
        // Default IAdapterPowerManagement
        IMP_IAdapterPowerManagement;

        //=====================================================================
        // IAdapterCommon methods      

        STDMETHODIMP_(NTSTATUS) Init
        (   
            _In_  PDEVICE_OBJECT  DeviceObject
        );

        STDMETHODIMP_(PDEVICE_OBJECT)   GetDeviceObject(void);
        
        STDMETHODIMP_(PDEVICE_OBJECT)   GetPhysicalDeviceObject(void);
        
        STDMETHODIMP_(WDFDEVICE)        GetWdfDevice(void);

        STDMETHODIMP_(void)     SetWaveServiceGroup
        (   
            _In_  PSERVICEGROUP   ServiceGroup
        );

        STDMETHODIMP_(BOOL)     bDevSpecificRead();

        STDMETHODIMP_(void)     bDevSpecificWrite
        (
            _In_  BOOL            bDevSpecific
        );
        STDMETHODIMP_(INT)      iDevSpecificRead();

        STDMETHODIMP_(void)     iDevSpecificWrite
        (
            _In_  INT             iDevSpecific
        );
        STDMETHODIMP_(UINT)     uiDevSpecificRead();

        STDMETHODIMP_(void)     uiDevSpecificWrite
        (
            _In_  UINT            uiDevSpecific
        );

        STDMETHODIMP_(BOOL)     MixerMuteRead
        (
            _In_  ULONG           Index,
            _In_  ULONG           Channel
        );

        STDMETHODIMP_(void)     MixerMuteWrite
        (
            _In_  ULONG           Index,
            _In_  ULONG           Channel,
            _In_  BOOL            Value
        );

        STDMETHODIMP_(ULONG)    MixerMuxRead(void);

        STDMETHODIMP_(void)     MixerMuxWrite
        (
            _In_  ULONG           Index
        );

        STDMETHODIMP_(void)     MixerReset(void);

        STDMETHODIMP_(LONG)     MixerVolumeRead
        ( 
            _In_  ULONG           Index,
            _In_  ULONG           Channel
        );

        STDMETHODIMP_(void)     MixerVolumeWrite
        ( 
            _In_  ULONG           Index,
            _In_  ULONG           Channel,
            _In_  LONG            Value 
        );

        STDMETHODIMP_(LONG)     MixerPeakMeterRead
        ( 
            _In_  ULONG           Index,
            _In_  ULONG           Channel
        );

        STDMETHODIMP_(NTSTATUS) WriteEtwEvent 
        ( 
            _In_ EPcMiniportEngineEvent    miniportEventType,
            _In_ ULONGLONG      ullData1,
            _In_ ULONGLONG      ullData2,
            _In_ ULONGLONG      ullData3,
            _In_ ULONGLONG      ullData4
        );

        STDMETHODIMP_(VOID)     SetEtwHelper 
        ( 
            PPORTCLSETWHELPER _pPortClsEtwHelper
        );
        
        STDMETHODIMP_(NTSTATUS) InstallSubdevice
        ( 
            _In_opt_        PIRP                                    Irp,
            _In_            PWSTR                                   Name,
            _In_            REFGUID                                 PortClassId,
            _In_            REFGUID                                 MiniportClassId,
            _In_opt_        PFNCREATEMINIPORT                       MiniportCreate,
            _In_opt_        PVOID                                   DeviceContext,
            _In_            PENDPOINT_MINIPAIR                      MiniportPair,
            _In_opt_        PRESOURCELIST                           ResourceList,
            _In_            REFGUID                                 PortInterfaceId,
            _Out_opt_       PUNKNOWN                              * OutPortInterface,
            _Out_opt_       PUNKNOWN                              * OutPortUnknown
        );
        
        STDMETHODIMP_(NTSTATUS) UnregisterSubdevice
        (
            _In_opt_ PUNKNOWN               UnknownPort
        );
        
        STDMETHODIMP_(NTSTATUS) ConnectTopologies
        (
            _In_ PUNKNOWN                   UnknownTopology,
            _In_ PUNKNOWN                   UnknownWave,
            _In_ PHYSICALCONNECTIONTABLE*   PhysicalConnections
        );
        
        STDMETHODIMP_(NTSTATUS) DisconnectTopologies
        (
            _In_ PUNKNOWN                   UnknownTopology,
            _In_ PUNKNOWN                   UnknownWave,
            _In_ PHYSICALCONNECTIONTABLE*   PhysicalConnections
        );
        
        STDMETHODIMP_(NTSTATUS) InstallEndpointFilters
        (
            _In_opt_    PIRP                Irp, 
            _In_        PENDPOINT_MINIPAIR  MiniportPair,
            _In_opt_    PVOID               DeviceContext,
            _Out_opt_   PUNKNOWN *          UnknownTopology,
            _Out_opt_   PUNKNOWN *          UnknownWave
        );
        
        STDMETHODIMP_(NTSTATUS) RemoveEndpointFilters
        (
            _In_        PENDPOINT_MINIPAIR  MiniportPair,
            _In_opt_    PUNKNOWN            UnknownTopology,
            _In_opt_    PUNKNOWN            UnknownWave
        );
        
#ifdef SYSVAD_BTH_BYPASS
        STDMETHODIMP_(NTSTATUS) InitBthScoBypass();
        
        STDMETHODIMP_(VOID)     CleanupBthScoBypass();
#endif // SYSVAD_BTH_BYPASS
        
        //=====================================================================
        // friends
        friend NTSTATUS         NewAdapterCommon
        ( 
            _Out_       PUNKNOWN *              Unknown,
            _In_        REFCLSID,
            _In_opt_    PUNKNOWN                UnknownOuter,
            _When_((PoolType & NonPagedPoolMustSucceed) != 0,
                __drv_reportError("Must succeed pool allocations are forbidden. "
                        "Allocation failures cause a system crash"))
            _In_        POOL_TYPE               PoolType 
        );

#ifdef SYSVAD_BTH_BYPASS
        //=====================================================================
        // Bluetooth Hands-free Profile SCO Bypass support.

    private:
        PVOID                   m_BthHfpScoNotificationHandle;
        FAST_MUTEX              m_BthHfpFastMutex;              // To serialize access.
        WDFWORKITEM             m_BthHfpWorkItem;               // Async work-item.
        LIST_ENTRY              m_BthHfpWorkTasks;              // Work-item's tasks.
        LIST_ENTRY              m_BthHfpDevices;                // Bth HFP devices.
        NPAGED_LOOKASIDE_LIST   m_BhtHfpWorkTaskPool;           // LookasideList
        size_t                  m_BhtHfpWorkTaskPoolElementSize;
        BOOL                    m_BthHfpEnableCleanup;          // Do cleanup if true.

    private:
        static 
        DRIVER_NOTIFICATION_CALLBACK_ROUTINE  EvtBthHfpScoBypassInterfaceChange;
        
        static 
        EVT_WDF_WORKITEM                      EvtBthHfpScoBypassInterfaceWorkItem;
    
    protected:
        BthHfpDevice * BthHfpDeviceFind
        (
            _In_ PUNICODE_STRING SymbolicLinkName
        );

        NTSTATUS BthHfpScoInterfaceArrival
        (
            _In_ PUNICODE_STRING SymbolicLinkName
        );

        NTSTATUS BthHfpScoInterfaceRemoval
        (
            _In_ PUNICODE_STRING SymbolicLinkName
        );
#endif // SYSVAD_BTH_BYPASS

};

//
// Used to implement the singleton pattern.
//
LONG  CAdapterCommon::m_AdapterInstances = 0;


#ifdef SYSVAD_BTH_BYPASS

//=====================================================================
//
// CAdapterCommon: Bluetooth Hands-Free Profile SCO Bypass definitions.
//

struct BthHfpWorkItemContext
{
    CAdapterCommon *    Adapter;
};

WDF_DECLARE_CONTEXT_TYPE_WITH_NAME
(
    BthHfpWorkItemContext,
    GetBthHfpWorkItemContext
)

// start/stop the device
enum eBthHfpTaskAction
{
    eBthHfpTaskStart    = 1,
    eBthHfpTaskStop     = 2,
};

struct BthHfpWorkTask
{
    LIST_ENTRY          ListEntry;
    BthHfpDevice      * Device;
    eBthHfpTaskAction   Action;
};


//=====================================================================
//
// Device: Bluetooth Hands-Free Profile SCO Bypass definitions.
//

// BTH HFP device's notification work-item context.
struct BthHfpDeviceNotificationWorkItemContext
{
    BthHfpDevice *  BthHfpDevice;
};

WDF_DECLARE_CONTEXT_TYPE_WITH_NAME
(
    BthHfpDeviceNotificationWorkItemContext,
    GetBthHfpDeviceNotificationWorkItemContext
)

// BTH HFP device's notification request context.
union BthHfpDeviceNotificationBuffer
{
    BOOL        bImmediate;
    LONG        Volume;
    BOOL        BoolStatus;
    NTSTATUS    NtStatus;
};

struct BthHfpDeviceNotificationReqContext
{
    BthHfpDevice                  * BthHfpDevice;
    LONG                            Errors;
    BthHfpDeviceNotificationBuffer  Buffer;
    WDFMEMORY                       MemIn;
    WDFMEMORY                       MemOut;
};

WDF_DECLARE_CONTEXT_TYPE_WITH_NAME
(
    BthHfpDeviceNotificationReqContext,
    GetBthHfpDeviceNotificationReqContext
)

enum eBthHfpState
{
    eBthHfpStateInvalid         = 0,
    eBthHfpStateInitializing    = 1,
    eBthHfpStateRunning         = 2,
    eBthHfpStateStopping        = 3,
    eBthHfpStateStopped         = 4,
    eBthHfpStateFailed          = 5,
};

// To support event notification.
struct BthHfpEventCallback
{
    PFNEVENTNOTIFICATION    Handler;
    PVOID                   Context;
};

//
// This class represents a the Bluetooth Hands-Free Profile SCO Bypass device.
// There is one class for each interface (id = symbolic-link-name).
//
class BthHfpDevice : 
    IBthHfpDeviceCommon,
    public CUnknown    
{
    private:
        eBthHfpState            m_State;
            
        CAdapterCommon        * m_Adapter;
        WDFIOTARGET             m_WdfIoTarget;
        
        LIST_ENTRY              m_ListEntry;
        UNICODE_STRING          m_SymbolicLinkName;
        UNICODE_STRING          m_SpeakerAudioSymbolicLinkName;
        UNICODE_STRING          m_MicAudioSymbolicLinkName;
        PENDPOINT_MINIPAIR      m_SpeakerMiniports;
        PENDPOINT_MINIPAIR      m_MicMiniports;
        PUNKNOWN                m_UnknownSpeakerTopology;
        PUNKNOWN                m_UnknownSpeakerWave;
        PUNKNOWN                m_UnknownMicTopology;
        PUNKNOWN                m_UnknownMicWave;        

        PBTHHFP_DESCRIPTOR2     m_Descriptor;
        PKSPROPERTY_VALUES      m_VolumePropValues;
        LONG                    m_SpeakerVolumeLevel;
        LONG                    m_MicVolumeLevel;
        union{
          BOOL                  m_ConnectionStatus;
          LONG                  m_ConnectionStatusLong;
        }; // unnamed.
        
        union{
          NTSTATUS              m_StreamStatus;
          LONG                  m_StreamStatusLong;
        }; // unnamed.

        KEVENT                  m_StreamStatusEvent;


        //
        // Set to TRUE when the HF (remote device) wants to disable the 
        // NR + EC of the AG (local system).
        //
        LONG                    m_NRECDisableStatusLong;
        
        WDFREQUEST              m_StreamReq;
        WDFREQUEST              m_SpeakerVolumeReq;
        WDFREQUEST              m_MicVolumeReq;
        WDFREQUEST              m_ConnectionReq;
        WDFREQUEST              m_NRECDisableStatusReq;
        WDFWORKITEM             m_WorkItem;
        WDFCOLLECTION           m_ReqCollection;
        KSPIN_LOCK              m_Lock;

        LONG                    m_nStreams; // # of open streams.

        BthHfpEventCallback     m_SpeakerVolumeCallback;
        BthHfpEventCallback     m_SpeakerConnectionStatusCallback;
        BthHfpEventCallback     m_MicVolumeCallback;
        BthHfpEventCallback     m_MicConnectionStatusCallback;

    public:
        //=====================================================================
        // Default CUnknown
        DECLARE_STD_UNKNOWN();
        DEFINE_STD_CONSTRUCTOR(BthHfpDevice);
        ~BthHfpDevice();

        NTSTATUS Init
        (
            _In_ CAdapterCommon     * Adapter, 
            _In_ PUNICODE_STRING      SymbolicLinkName
        );

    public:
        //=====================================================================
        //
        // Public functions used by CAdapterCommon object.
        //
        VOID Start();
        VOID Stop();

        PLIST_ENTRY GetListEntry()
        {
            return &m_ListEntry;
        }

        PUNICODE_STRING GetSymbolicLinkName()
        {
            return &m_SymbolicLinkName;
        }

        static
        BthHfpDevice * 
        GetBthHfpDevice
        (
            _In_ PLIST_ENTRY le
        )
        {
            return CONTAINING_RECORD(le, BthHfpDevice, m_ListEntry);
        }

    public:
        //=====================================================================
        //
        // IBthHfpDeviceCommon functions. 
        //
        STDMETHODIMP_(BOOL)                 IsVolumeSupported();
        
        STDMETHODIMP_(PKSPROPERTY_VALUES)   GetVolumeSettings
        (
            _Out_ PULONG    Size 
        );
    
        STDMETHODIMP_(LONG)                 GetSpeakerVolume();
     
        STDMETHODIMP_(NTSTATUS)             SetSpeakerVolume
        (
            _In_ ULONG      Volume
        );
        
        STDMETHODIMP_(LONG)                 GetMicVolume();
    
        STDMETHODIMP_(NTSTATUS)             SetMicVolume
        (
            _In_ ULONG      Volume
        );
    
        STDMETHODIMP_(BOOL)                 GetConnectionStatus();
        
        STDMETHODIMP_(NTSTATUS)             Connect();
    
        STDMETHODIMP_(NTSTATUS)             Disconnect();
        
        STDMETHODIMP_(BOOL)                 GetStreamStatus();
        
        STDMETHODIMP_(NTSTATUS)             StreamOpen();
    
        STDMETHODIMP_(NTSTATUS)             StreamClose();
        
        STDMETHODIMP_(GUID)                 GetContainerId();
        
        STDMETHODIMP_(VOID)                 SetSpeakerVolumeHandler
        (
            _In_opt_    PFNEVENTNOTIFICATION    EventHandler,
            _In_opt_    PVOID                   EventHandlerContext
        );
        
        STDMETHODIMP_(VOID)                 SetSpeakerConnectionStatusHandler
        (
            _In_opt_    PFNEVENTNOTIFICATION    EventHandler,
            _In_opt_    PVOID                   EventHandlerContext
        );
        
        STDMETHODIMP_(VOID)                 SetMicVolumeHandler
        (
            _In_opt_    PFNEVENTNOTIFICATION    EventHandler,
            _In_opt_    PVOID                   EventHandlerContext
        );
        
        STDMETHODIMP_(VOID)                 SetMicConnectionStatusHandler
        (
            _In_opt_    PFNEVENTNOTIFICATION    EventHandler,
            _In_opt_    PVOID                   EventHandlerContext
        );
        
        STDMETHODIMP_(BOOL)                 IsNRECSupported();
        
        STDMETHODIMP_(BOOL)                 GetNRECDisableStatus();

    private:
        //=====================================================================
        //
        // Helper functions.
        //
        NTSTATUS    SendIoCtrlSynchronously
        (
            _In_opt_    WDFREQUEST  Request,
            _In_        ULONG       IoControlCode,
            _In_        ULONG       InLength,
            _In_        ULONG       OutLength,
            _When_(InLength > 0 || OutLength > 0, _In_)
            _When_(InLength == 0 && OutLength == 0, _In_opt_)
                        PVOID       Buffer
        );
        
        NTSTATUS    SendIoCtrlAsynchronously
        (
            _In_        WDFREQUEST      Request,
            _In_        ULONG           IoControlCode,
            _In_opt_    WDFMEMORY       MemIn,
            _In_opt_    WDFMEMORY       MemOut,
            _In_        PFN_WDF_REQUEST_COMPLETION_ROUTINE CompletionRoutine,
            _In_        WDFCONTEXT      Context
        );
        
        NTSTATUS    GetBthHfpDescriptor
        (
            _Out_ PBTHHFP_DESCRIPTOR2 * Descriptor
        );
        
        NTSTATUS    EnableBthHfpNrecDisableStatusNotification();
        
        NTSTATUS    GetBthHfpVolumePropertyValues
        (
            _In_  ULONG                 Length,
            _Out_ PKSPROPERTY_VALUES  * PropValues
        );
        
        NTSTATUS    SetBthHfpSpeakerVolume
        (
            _In_ LONG  Volume  
        );
        
        NTSTATUS    GetBthHfpSpeakerVolume
        (
            _Out_ LONG  * Volume    
        );
        
        NTSTATUS    EnableBthHfpSpeakerVolumeStatusNotification();
        
        NTSTATUS    SetBthHfpMicVolume
        (
            _In_ LONG  Volume  
        );
        
        NTSTATUS    GetBthHfpMicVolume
        (
            _Out_ LONG  * Volume    
        );
        
        NTSTATUS    EnableBthHfpMicVolumeStatusNotification();
        
        NTSTATUS    GetBthHfpConnectionStatus
        (
            _Out_ BOOL * ConnectionStatus    
        );
        
        NTSTATUS    EnableBthHfpConnectionStatusNotification();
        
        NTSTATUS    CreateAudioInterface
        (
            _Inout_ PWSTR               TopologyName,
            _Out_   PUNICODE_STRING     AudioSymbolicLinkName
        );
        
        NTSTATUS    SetBthHfpConnect();
        
        NTSTATUS    SetBthHfpDisconnect();

        NTSTATUS    SetBthHfpStreamOpen();

        NTSTATUS    SetBthHfpStreamClose();
        
        NTSTATUS    EnableBthHfpStreamStatusNotification();
        
        NTSTATUS    StopBthHfpStreamStatusNotification();
        
        //
        // WDF I/O Target callback.
        //
        static
        EVT_WDF_IO_TARGET_QUERY_REMOVE    EvtBthHfpTargetQueryRemove;

        static
        EVT_WDF_IO_TARGET_REMOVE_CANCELED EvtBthHfpTargetRemoveCanceled;

        static
        EVT_WDF_IO_TARGET_REMOVE_COMPLETE EvtBthHfpTargetRemoveComplete;

        //
        // Status notifications callbacks.
        //
        static 
        EVT_WDF_REQUEST_COMPLETION_ROUTINE EvtBthHfpDeviceStreamStatusCompletion;

        static 
        EVT_WDF_REQUEST_COMPLETION_ROUTINE EvtBthHfpDeviceNotificationStatusCompletion;
        
        static
        EVT_WDF_WORKITEM                   EvtBthHfpDeviceNotificationStatusWorkItem;
};
#endif // SYSVAD_BTH_BYPASS

//-----------------------------------------------------------------------------
// Functions
//-----------------------------------------------------------------------------

//=============================================================================
#pragma code_seg("PAGE")
NTSTATUS
NewAdapterCommon
( 
    _Out_       PUNKNOWN *              Unknown,
    _In_        REFCLSID,
    _In_opt_    PUNKNOWN                UnknownOuter,
    _When_((PoolType & NonPagedPoolMustSucceed) != 0,
        __drv_reportError("Must succeed pool allocations are forbidden. "
			    "Allocation failures cause a system crash"))
    _In_        POOL_TYPE               PoolType 
)
/*++

Routine Description:

  Creates a new CAdapterCommon

Arguments:

  Unknown - 

  UnknownOuter -

  PoolType

Return Value:

  NT status code.

--*/
{
    PAGED_CODE();

    ASSERT(Unknown);

    NTSTATUS ntStatus;

    //
    // This sample supports only one instance of this object.
    // (b/c of CSaveData's static members and Bluetooth HFP logic). 
    //
    if (CAdapterCommon::m_AdapterInstances != 0)
    {
        ntStatus = STATUS_DEVICE_BUSY;
        DPF(D_ERROR, ("NewAdapterCommon failed, only one instance is allowed"));
        goto Done;
    }
    
    CAdapterCommon::m_AdapterInstances++;

    //
    // Allocate an adapter object.
    //
    CAdapterCommon *p = new(PoolType, MINADAPTER_POOLTAG) CAdapterCommon(UnknownOuter);
    if (p == NULL)
    {
        ntStatus = STATUS_INSUFFICIENT_RESOURCES;
        DPF(D_ERROR, ("NewAdapterCommon failed, 0x%x", ntStatus));
        goto Done;
    }

    // 
    // Success.
    //
    *Unknown = PUNKNOWN((PADAPTERCOMMON)(p));
    (*Unknown)->AddRef(); 
    ntStatus = STATUS_SUCCESS; 

Done:    
    return ntStatus;
} // NewAdapterCommon

//=============================================================================
#pragma code_seg("PAGE")
CAdapterCommon::~CAdapterCommon
( 
    void 
)
/*++

Routine Description:

  Destructor for CAdapterCommon.

Arguments:

Return Value:

  void

--*/
{
    PAGED_CODE();
    DPF_ENTER(("[CAdapterCommon::~CAdapterCommon]"));

    if (m_pHW)
    {
        delete m_pHW;
        m_pHW = NULL;
    }
    
    CSaveData::DestroyWorkItems();

    SAFE_RELEASE(m_pPortClsEtwHelper);
    SAFE_RELEASE(m_pServiceGroupWave);
 
    if (m_WdfDevice)
    {
        WdfObjectDelete(m_WdfDevice);
        m_WdfDevice = NULL;
    }

    CAdapterCommon::m_AdapterInstances--;
    ASSERT(CAdapterCommon::m_AdapterInstances == 0);
} // ~CAdapterCommon  

//=============================================================================
#pragma code_seg("PAGE")
STDMETHODIMP_(PDEVICE_OBJECT)   
CAdapterCommon::GetDeviceObject
(
    void
)
/*++

Routine Description:

  Returns the deviceobject

Arguments:

Return Value:

  PDEVICE_OBJECT

--*/
{
    PAGED_CODE();
    
    return m_pDeviceObject;
} // GetDeviceObject

//=============================================================================
#pragma code_seg("PAGE")
STDMETHODIMP_(PDEVICE_OBJECT)   
CAdapterCommon::GetPhysicalDeviceObject
(
    void
)
/*++

Routine Description:

  Returns the PDO.

Arguments:

Return Value:

  PDEVICE_OBJECT

--*/
{
    PAGED_CODE();
    
    return m_pPhysicalDeviceObject;
} // GetPhysicalDeviceObject

//=============================================================================
#pragma code_seg("PAGE")
STDMETHODIMP_(WDFDEVICE)   
CAdapterCommon::GetWdfDevice
(
    void
)
/*++

Routine Description:

  Returns the associated WDF miniport device. Note that this is NOT an audio
  miniport. The WDF miniport device is the WDF device associated with the
  adapter.

Arguments:

Return Value:

  WDFDEVICE

--*/
{
    PAGED_CODE();
    
    return m_WdfDevice;
} // GetWdfDevice

//=============================================================================
#pragma code_seg("PAGE")
NTSTATUS
CAdapterCommon::Init
( 
    _In_  PDEVICE_OBJECT          DeviceObject 
)
/*++

Routine Description:

    Initialize adapter common object.

Arguments:

    DeviceObject - pointer to the device object

Return Value:

  NT status code.

--*/
{
    PAGED_CODE();
    DPF_ENTER(("[CAdapterCommon::Init]"));

    ASSERT(DeviceObject);

    NTSTATUS        ntStatus    = STATUS_SUCCESS;

#ifdef SYSVAD_BTH_BYPASS
    m_BthHfpEnableCleanup = FALSE;
#endif // SYSVAD_BTH_BYPASS

    m_pServiceGroupWave     = NULL;
    m_pDeviceObject         = DeviceObject;
    m_pPhysicalDeviceObject = NULL;
    m_WdfDevice             = NULL;
    m_PowerState            = PowerDeviceD0;
    m_pHW                   = NULL;
    m_pPortClsEtwHelper     = NULL;

    //
    // Get the PDO.
    //
    ntStatus = PcGetPhysicalDeviceObject(DeviceObject, &m_pPhysicalDeviceObject);
    IF_FAILED_ACTION_JUMP(
        ntStatus,
        DPF(D_ERROR, ("PcGetPhysicalDeviceObject failed, 0x%x", ntStatus)),
        Done);

    //
    // Create a WDF miniport to represent the adapter. Note that WDF miniports 
    // are NOT audio miniports. An audio adapter is associated with a single WDF
    // miniport. This driver uses WDF to simplify the handling of the Bluetooth
    // SCO HFP Bypass interface.
    //
    ntStatus = WdfDeviceMiniportCreate( WdfGetDriver(),
                                        WDF_NO_OBJECT_ATTRIBUTES,
                                        DeviceObject,           // FDO
                                        NULL,                   // Next device.
                                        NULL,                   // PDO
                                       &m_WdfDevice);
    IF_FAILED_ACTION_JUMP(
        ntStatus,
        DPF(D_ERROR, ("WdfDeviceMiniportCreate failed, 0x%x", ntStatus)),
        Done);

    // Initialize HW.
    // 
    m_pHW = new (NonPagedPoolNx, SYSVAD_POOLTAG)  CSYSVADHW;
    if (!m_pHW)
    {
        DPF(D_TERSE, ("Insufficient memory for SYSVAD HW"));
        ntStatus = STATUS_INSUFFICIENT_RESOURCES;
    }
    IF_FAILED_JUMP(ntStatus, Done);
    
    m_pHW->MixerReset();

    //
    // Initialize SaveData class.
    //
    CSaveData::SetDeviceObject(DeviceObject);   //device object is needed by CSaveData
    ntStatus = CSaveData::InitializeWorkItems(DeviceObject);
    IF_FAILED_JUMP(ntStatus, Done);

Done:

    return ntStatus;
} // Init

//=============================================================================
#pragma code_seg("PAGE")
STDMETHODIMP_(void)
CAdapterCommon::MixerReset
( 
    void 
)
/*++

Routine Description:

  Reset mixer registers from registry.

Arguments:

Return Value:

  void

--*/
{
    PAGED_CODE();
    
    if (m_pHW)
    {
        m_pHW->MixerReset();
    }
} // MixerReset

//=============================================================================
/* Here are the definitions of the standard miniport events.

Event type	: eMINIPORT_IHV_DEFINED 
Parameter 1	: Defined and used by IHVs
Parameter 2	: Defined and used by IHVs
Parameter 3	:Defined and used by IHVs
Parameter 4 :Defined and used by IHVs

Event type: eMINIPORT_BUFFER_COMPLETE
Parameter 1: Current linear buffer position	
Parameter 2: the previous WaveRtBufferWritePosition that the drive received	
Parameter 3: Data length completed
Parameter 4:0

Event type: eMINIPORT_PIN_STATE
Parameter 1: Current linear buffer position	
Parameter 2: the previous WaveRtBufferWritePosition that the drive received		
Parameter 3: Pin State 0->KS_STOP, 1->KS_ACQUIRE, 2->KS_PAUSE, 3->KS_RUN 
Parameter 4:0

Event type: eMINIPORT_GET_STREAM_POS
Parameter 1: Current linear buffer position	
Parameter 2: the previous WaveRtBufferWritePosition that the drive received	
Parameter 3: 0
Parameter 4:0


Event type: eMINIPORT_SET_WAVERT_BUFFER_WRITE_POS
Parameter 1: Current linear buffer position	
Parameter 2: the previous WaveRtBufferWritePosition that the drive received	
Parameter 3: the arget WaveRtBufferWritePosition received from portcls
Parameter 4:0

Event type: eMINIPORT_GET_PRESENTATION_POS
Parameter 1: Current linear buffer position	
Parameter 2: the previous WaveRtBufferWritePosition that the drive received	
Parameter 3: Presentation position
Parameter 4:0

Event type: eMINIPORT_PROGRAM_DMA 
Parameter 1: Current linear buffer position	
Parameter 2: the previous WaveRtBufferWritePosition that the drive received	
Parameter 3: Starting  WaveRt buffer offset
Parameter 4: Data length

Event type: eMINIPORT_GLITCH_REPORT
Parameter 1: Current linear buffer position	
Parameter 2: the previous WaveRtBufferWritePosition that the drive received	
Parameter 3: major glitch code: 1:WaveRT buffer is underrun, 
                                2:decoder errors, 
                                3:receive the same wavert buffer two in a row in event driven mode
Parameter 4: minor code for the glitch cause

Event type: eMINIPORT_LAST_BUFFER_RENDERED
Parameter 1: Current linear buffer position	
Parameter 2: the very last WaveRtBufferWritePosition that the driver received	
Parameter 3: 0
Parameter 4: 0

*/
#pragma code_seg()
STDMETHODIMP
CAdapterCommon::WriteEtwEvent
( 
    _In_ EPcMiniportEngineEvent    miniportEventType,
    _In_ ULONGLONG  ullData1,
    _In_ ULONGLONG  ullData2,
    _In_ ULONGLONG  ullData3,
    _In_ ULONGLONG  ullData4
)
{
    NTSTATUS ntStatus = STATUS_SUCCESS;

    if (m_pPortClsEtwHelper)
    {
        ntStatus = m_pPortClsEtwHelper->MiniportWriteEtwEvent( miniportEventType, ullData1, ullData2, ullData3, ullData4) ;
    }
    return ntStatus;
} // WriteEtwEvent

//=============================================================================
#pragma code_seg("PAGE")
STDMETHODIMP_(void)
CAdapterCommon::SetEtwHelper
( 
    PPORTCLSETWHELPER _pPortClsEtwHelper
)
{
    PAGED_CODE();
    
    SAFE_RELEASE(m_pPortClsEtwHelper);

    m_pPortClsEtwHelper = _pPortClsEtwHelper;

    if (m_pPortClsEtwHelper)
    {
        m_pPortClsEtwHelper->AddRef();
    }
} // SetEtwHelper

//=============================================================================
#pragma code_seg("PAGE")
STDMETHODIMP
CAdapterCommon::NonDelegatingQueryInterface
( 
    _In_ REFIID                      Interface,
    _COM_Outptr_ PVOID *        Object 
)
/*++

Routine Description:

  QueryInterface routine for AdapterCommon

Arguments:

  Interface - 

  Object -

Return Value:

  NT status code.

--*/
{
    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;
} // NonDelegatingQueryInterface

//=============================================================================
#pragma code_seg("PAGE")
STDMETHODIMP_(void)
CAdapterCommon::SetWaveServiceGroup
( 
    _In_ PSERVICEGROUP            ServiceGroup 
)
/*++

Routine Description:


Arguments:

Return Value:

  NT status code.

--*/
{
    PAGED_CODE();
    DPF_ENTER(("[CAdapterCommon::SetWaveServiceGroup]"));
    
    SAFE_RELEASE(m_pServiceGroupWave);

    m_pServiceGroupWave = ServiceGroup;

    if (m_pServiceGroupWave)
    {
        m_pServiceGroupWave->AddRef();
    }
} // SetWaveServiceGroup

//=============================================================================
#pragma code_seg()
STDMETHODIMP_(BOOL)
CAdapterCommon::bDevSpecificRead()
/*++

Routine Description:

  Fetch Device Specific information.

Arguments:

  N/A

Return Value:

    BOOL - Device Specific info

--*/
{
    if (m_pHW)
    {
        return m_pHW->bGetDevSpecific();
    }

    return FALSE;
} // bDevSpecificRead

//=============================================================================
#pragma code_seg()
STDMETHODIMP_(void)
CAdapterCommon::bDevSpecificWrite
(
    _In_  BOOL                    bDevSpecific
)
/*++

Routine Description:

  Store the new value in the Device Specific location.

Arguments:

  bDevSpecific - Value to store

Return Value:

  N/A.

--*/
{
    if (m_pHW)
    {
        m_pHW->bSetDevSpecific(bDevSpecific);
    }
} // DevSpecificWrite

//=============================================================================
#pragma code_seg()
STDMETHODIMP_(INT)
CAdapterCommon::iDevSpecificRead()
/*++

Routine Description:

  Fetch Device Specific information.

Arguments:

  N/A

Return Value:

    INT - Device Specific info

--*/
{
    if (m_pHW)
    {
        return m_pHW->iGetDevSpecific();
    }

    return 0;
} // iDevSpecificRead

//=============================================================================
#pragma code_seg()
STDMETHODIMP_(void)
CAdapterCommon::iDevSpecificWrite
(
    _In_  INT                    iDevSpecific
)
/*++

Routine Description:

  Store the new value in the Device Specific location.

Arguments:

  iDevSpecific - Value to store

Return Value:

  N/A.

--*/
{
    if (m_pHW)
    {
        m_pHW->iSetDevSpecific(iDevSpecific);
    }
} // iDevSpecificWrite

//=============================================================================
#pragma code_seg()
STDMETHODIMP_(UINT)
CAdapterCommon::uiDevSpecificRead()
/*++

Routine Description:

  Fetch Device Specific information.

Arguments:

  N/A

Return Value:

    UINT - Device Specific info

--*/
{
    if (m_pHW)
    {
        return m_pHW->uiGetDevSpecific();
    }

    return 0;
} // uiDevSpecificRead

//=============================================================================
#pragma code_seg()
STDMETHODIMP_(void)
CAdapterCommon::uiDevSpecificWrite
(
    _In_  UINT                    uiDevSpecific
)
/*++

Routine Description:

  Store the new value in the Device Specific location.

Arguments:

  uiDevSpecific - Value to store

Return Value:

  N/A.

--*/
{
    if (m_pHW)
    {
        m_pHW->uiSetDevSpecific(uiDevSpecific);
    }
} // uiDevSpecificWrite

//=============================================================================
#pragma code_seg()
STDMETHODIMP_(BOOL)
CAdapterCommon::MixerMuteRead
(
    _In_  ULONG               Index,
    _In_  ULONG               Channel
)
/*++

Routine Description:

  Store the new value in mixer register array.

Arguments:

  Index - node id

Return Value:

    BOOL - mixer mute setting for this node

--*/
{
    if (m_pHW)
    {
        return m_pHW->GetMixerMute(Index, Channel);
    }

    return 0;
} // MixerMuteRead

//=============================================================================
#pragma code_seg()
STDMETHODIMP_(void)
CAdapterCommon::MixerMuteWrite
(
    _In_  ULONG                   Index,
    _In_  ULONG                   Channel,
    _In_  BOOL                    Value
)
/*++

Routine Description:

  Store the new value in mixer register array.

Arguments:

  Index - node id

  Value - new mute settings

Return Value:

  NT status code.

--*/
{
    if (m_pHW)
    {
        m_pHW->SetMixerMute(Index, Channel, Value);
    }
} // MixerMuteWrite

//=============================================================================
#pragma code_seg()
STDMETHODIMP_(ULONG)
CAdapterCommon::MixerMuxRead() 
/*++

Routine Description:

  Return the mux selection

Arguments:

  Index - node id

  Value - new mute settings

Return Value:

  NT status code.

--*/
{
    if (m_pHW)
    {
        return m_pHW->GetMixerMux();
    }

    return 0;
} // MixerMuxRead

//=============================================================================
#pragma code_seg()
STDMETHODIMP_(void)
CAdapterCommon::MixerMuxWrite
(
    _In_  ULONG                   Index
)
/*++

Routine Description:

  Store the new mux selection

Arguments:

  Index - node id

  Value - new mute settings

Return Value:

  NT status code.

--*/
{
    if (m_pHW)
    {
        m_pHW->SetMixerMux(Index);
    }
} // MixerMuxWrite

//=============================================================================
#pragma code_seg()
STDMETHODIMP_(LONG)
CAdapterCommon::MixerVolumeRead
( 
    _In_  ULONG                   Index,
    _In_  ULONG                   Channel
)
/*++

Routine Description:

  Return the value in mixer register array.

Arguments:

  Index - node id

  Channel = which channel

Return Value:

    Byte - mixer volume settings for this line

--*/
{
    if (m_pHW)
    {
        return m_pHW->GetMixerVolume(Index, Channel);
    }

    return 0;
} // MixerVolumeRead

//=============================================================================
#pragma code_seg()
STDMETHODIMP_(void)
CAdapterCommon::MixerVolumeWrite
( 
    _In_  ULONG                   Index,
    _In_  ULONG                   Channel,
    _In_  LONG                    Value
)
/*++

Routine Description:

  Store the new value in mixer register array.

Arguments:

  Index - node id

  Channel - which channel

  Value - new volume level

Return Value:

    void

--*/
{
    if (m_pHW)
    {
        m_pHW->SetMixerVolume(Index, Channel, Value);
    }
} // MixerVolumeWrite

//=============================================================================
#pragma code_seg()
STDMETHODIMP_(LONG)
CAdapterCommon::MixerPeakMeterRead
( 
    _In_  ULONG                   Index,
    _In_  ULONG                   Channel
)
/*++

Routine Description:

  Return the value in mixer register array.

Arguments:

  Index - node id

  Channel = which channel

Return Value:

    Byte - mixer sample peak meter settings for this line

--*/
{
    if (m_pHW)
    {
        return m_pHW->GetMixerPeakMeter(Index, Channel);
    }

    return 0;
} // MixerVolumeRead

//=============================================================================
#pragma code_seg()
STDMETHODIMP_(void)
CAdapterCommon::PowerChangeState
( 
    _In_  POWER_STATE             NewState 
)
/*++

Routine Description:


Arguments:

  NewState - The requested, new power state for the device. 

Return Value:

    void

Note:
  From MSDN:

  To assist the driver, PortCls will pause any active audio streams prior to calling
  this method to place the device in a sleep state. After calling this method, PortCls
  will unpause active audio streams, to wake the device up. Miniports can opt for 
  additional notification by utilizing the IPowerNotify interface.

  The miniport driver must perform the requested change to the device's power state 
  before it returns from the PowerChangeState call. If the miniport driver needs to 
  save or restore any device state before a power-state change, the miniport driver 
  should support the IPowerNotify interface, which allows it to receive advance warning
  of any such change. Before returning from a successful PowerChangeState call, the 
  miniport driver should cache the new power state.

  While the miniport driver is in one of the sleep states (any state other than 
  PowerDeviceD0), it must avoid writing to the hardware. The miniport driver must cache
  any hardware accesses that need to be deferred until the device powers up again. If
  the power state is changing from one of the sleep states to PowerDeviceD0, the 
  miniport driver should perform any deferred hardware accesses after it has powered up
  the device. If the power state is changing from PowerDeviceD0 to a sleep state, the 
  miniport driver can perform any necessary hardware accesses during the PowerChangeState
  call before it powers down the device.

  While powered down, a miniport driver is never asked to create a miniport driver object
  or stream object. PortCls always places the device in the PowerDeviceD0 state before
  calling the miniport driver's NewStream method.
  
--*/
{
    DPF_ENTER(("[CAdapterCommon::PowerChangeState]"));

    // is this actually a state change??
    //
    if (NewState.DeviceState != m_PowerState)
    {
        // switch on new state
        //
        switch (NewState.DeviceState)
        {
            case PowerDeviceD0:
            case PowerDeviceD1:
            case PowerDeviceD2:
            case PowerDeviceD3:
                m_PowerState = NewState.DeviceState;

                DPF
                ( 
                    D_VERBOSE, 
                    ("Entering D%u", ULONG(m_PowerState) - ULONG(PowerDeviceD0)) 
                );

                break;
    
            default:
            
                DPF(D_VERBOSE, ("Unknown Device Power State"));
                break;
        }
    }
} // PowerStateChange

//=============================================================================
#pragma code_seg()
STDMETHODIMP_(NTSTATUS)
CAdapterCommon::QueryDeviceCapabilities
( 
    _Inout_updates_bytes_(sizeof(DEVICE_CAPABILITIES)) PDEVICE_CAPABILITIES    PowerDeviceCaps 
)
/*++

Routine Description:

    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.         

Arguments:

  PowerDeviceCaps - The device's capabilities. 

Return Value:

  NT status code.

--*/
{
    UNREFERENCED_PARAMETER(PowerDeviceCaps);

    DPF_ENTER(("[CAdapterCommon::QueryDeviceCapabilities]"));

    return (STATUS_SUCCESS);
} // QueryDeviceCapabilities

//=============================================================================
#pragma code_seg()
STDMETHODIMP_(NTSTATUS)
CAdapterCommon::QueryPowerChangeState
( 
    _In_  POWER_STATE             NewStateQuery 
)
/*++

Routine Description:

  Query to see if the device can change to this power state 

Arguments:

  NewStateQuery - The requested, new power state for the device

Return Value:

  NT status code.

--*/
{
    UNREFERENCED_PARAMETER(NewStateQuery);

    DPF_ENTER(("[CAdapterCommon::QueryPowerChangeState]"));

    return STATUS_SUCCESS;
} // QueryPowerChangeState

//=============================================================================
#pragma code_seg("PAGE")
STDMETHODIMP_(NTSTATUS)
CAdapterCommon::InstallSubdevice
( 
    _In_opt_        PIRP                                    Irp,
    _In_            PWSTR                                   Name,
    _In_            REFGUID                                 PortClassId,
    _In_            REFGUID                                 MiniportClassId,
    _In_opt_        PFNCREATEMINIPORT                       MiniportCreate,
    _In_opt_        PVOID                                   DeviceContext,
    _In_            PENDPOINT_MINIPAIR                      MiniportPair,
    _In_opt_        PRESOURCELIST                           ResourceList,
    _In_            REFGUID                                 PortInterfaceId,
    _Out_opt_       PUNKNOWN                              * OutPortInterface,
    _Out_opt_       PUNKNOWN                              * OutPortUnknown
)
{
/*++

Routine Description:

    This function creates and registers a subdevice consisting of a port       
    driver, a minport driver and a set of resources bound together.  It will   
    also optionally place a pointer to an interface on the port driver in a    
    specified location before initializing the port driver.  This is done so   
    that a common ISR can have access to the port driver during 
    initialization, when the ISR might fire.                                   

Arguments:

    Irp - pointer to the irp object.

    Name - name of the miniport. Passes to PcRegisterSubDevice
 
    PortClassId - port class id. Passed to PcNewPort.

    MiniportClassId - miniport class id. Passed to PcNewMiniport.

    MiniportCreate - pointer to a miniport creation function. If NULL, 
                     PcNewMiniport is used.
                     
    DeviceContext - deviceType specific.

    MiniportPair - endpoint configuration info.    

    ResourceList - pointer to the resource list.

    PortInterfaceId - GUID that represents the port interface.
       
    OutPortInterface - pointer to store the port interface

    OutPortUnknown - pointer to store the unknown port interface.

Return Value:

    NT status code.

--*/
    PAGED_CODE();
    DPF_ENTER(("[InstallSubDevice %S]", Name));

    ASSERT(Name != NULL);
    ASSERT(m_pDeviceObject != NULL);

    NTSTATUS                    ntStatus;
    PPORT                       port            = NULL;
    PUNKNOWN                    miniport        = NULL;
    PADAPTERCOMMON              adapterCommon   = NULL;

    adapterCommon = PADAPTERCOMMON(this);

    // Create the port driver object
    //
    ntStatus = PcNewPort(&port, PortClassId);

    // Create the miniport object
    //
    if (NT_SUCCESS(ntStatus))
    {
        if (MiniportCreate)
        {
            ntStatus = 
                MiniportCreate
                ( 
                    &miniport,
                    MiniportClassId,
                    NULL,
                    NonPagedPoolNx,
                    adapterCommon,
                    DeviceContext,
                    MiniportPair
                );
        }
        else
        {
            ntStatus = 
                PcNewMiniport
                (
                    (PMINIPORT *) &miniport, 
                    MiniportClassId
                );
        }
    }

    // Init the port driver and miniport in one go.
    //
    if (NT_SUCCESS(ntStatus))
    {
#pragma warning(push)
        // IPort::Init's annotation on ResourceList requires it to be non-NULL.  However,
        // for dynamic devices, we may no longer have the resource list and this should
        // still succeed.
        //
#pragma warning(disable:6387)
        ntStatus = 
            port->Init
            ( 
                m_pDeviceObject,
                Irp,
                miniport,
                adapterCommon,
                ResourceList 
            );
#pragma warning (pop)

        if (NT_SUCCESS(ntStatus))
        {
            // Register the subdevice (port/miniport combination).
            //
            ntStatus = 
                PcRegisterSubdevice
                ( 
                    m_pDeviceObject,
                    Name,
                    port 
                );
        }

        // We don't need the miniport any more.  Either the port has it,
        // or we've failed, and it should be deleted.
        //
        miniport->Release();
    }

    // Deposit the port interfaces if it's needed.
    //
    if (NT_SUCCESS(ntStatus))
    {
        if (OutPortUnknown)
        {
            ntStatus = 
                port->QueryInterface
                ( 
                    IID_IUnknown,
                    (PVOID *)OutPortUnknown 
                );
        }

        if (OutPortInterface)
        {
            ntStatus = 
                port->QueryInterface
                ( 
                    PortInterfaceId,
                    (PVOID *) OutPortInterface 
                );
        }
    }

    if (port)
    {
        port->Release();
    }

    return ntStatus;
} // InstallSubDevice

//=============================================================================
#pragma code_seg("PAGE")
STDMETHODIMP_(NTSTATUS)
CAdapterCommon::UnregisterSubdevice
(
    _In_opt_   PUNKNOWN     UnknownPort
)
/*++

Routine Description:

  Unregisters and releases the specified subdevice.

Arguments:

  UnknownPort - Wave or topology port interface.

Return Value:

  NTSTATUS

--*/
{
    PAGED_CODE();
    DPF_ENTER(("[CAdapterCommon::UnregisterSubdevice]"));

    ASSERT(m_pDeviceObject != NULL);
    
    NTSTATUS                ntStatus            = STATUS_SUCCESS;
    PUNREGISTERSUBDEVICE    unregisterSubdevice = NULL;
    
    if (NULL == UnknownPort)
    {
        return ntStatus;
    }

    //
    // Get the IUnregisterSubdevice interface.
    //
    ntStatus = UnknownPort->QueryInterface( 
        IID_IUnregisterSubdevice,
        (PVOID *)&unregisterSubdevice);

    //
    // Unregister the port object.
    //
    if (NT_SUCCESS(ntStatus))
    {
        ntStatus = unregisterSubdevice->UnregisterSubdevice(
            m_pDeviceObject,
            UnknownPort);

        //
        // Release the IUnregisterSubdevice interface.
        //
        unregisterSubdevice->Release();
    }
    
    return ntStatus;
}

//=============================================================================
#pragma code_seg("PAGE")
STDMETHODIMP_(NTSTATUS)
CAdapterCommon::ConnectTopologies
(
    _In_ PUNKNOWN                   UnknownTopology,
    _In_ PUNKNOWN                   UnknownWave,
    _In_ PHYSICALCONNECTIONTABLE*   PhysicalConnections
)
/*++

Routine Description:

  Connects the bridge pins between the wave and mixer topologies.

Arguments:

Return Value:

  NTSTATUS

--*/
{
    PAGED_CODE();
    DPF_ENTER(("[CAdapterCommon::ConnectTopologies]"));
    
    ASSERT(m_pDeviceObject != NULL);
    
    NTSTATUS        ntStatus            = STATUS_SUCCESS;
    BOOLEAN         fPhyConnTopoToWave  = FALSE;
    BOOLEAN         fPhyConnWaveToTopo  = FALSE;

    //
    // register wave <=> topology connections
    // This will connect bridge pins of wave and topology
    // miniports.
    //
    if ((PhysicalConnections->ulTopologyOut != (ULONG)-1) &&
        (PhysicalConnections->ulWaveIn != (ULONG)-1))
    {
        ntStatus =
            PcRegisterPhysicalConnection
            ( 
                m_pDeviceObject,
                UnknownTopology,
                PhysicalConnections->ulTopologyOut,
                UnknownWave,
                PhysicalConnections->ulWaveIn
            );
    
        if (NT_SUCCESS(ntStatus))
        {
            fPhyConnTopoToWave = TRUE;
        }
    }
    
    if (NT_SUCCESS(ntStatus))
    {
        if ((PhysicalConnections->ulWaveOut != (ULONG)-1) &&
            (PhysicalConnections->ulTopologyIn != (ULONG)-1))
        {
            ntStatus =
                PcRegisterPhysicalConnection
                ( 
                    m_pDeviceObject,
                    UnknownWave,
                    PhysicalConnections->ulWaveOut,
                    UnknownTopology,
                    PhysicalConnections->ulTopologyIn
                );
            
            if (NT_SUCCESS(ntStatus))
            {
                // just in case more code is added into this function later on.
                fPhyConnWaveToTopo = TRUE; 
            }
        }
    }
    
    //
    // Cleanup in case of error.
    //
    if (!NT_SUCCESS(ntStatus) && (fPhyConnWaveToTopo || fPhyConnTopoToWave))
    {
        PUNREGISTERPHYSICALCONNECTION   unregisterPhysicalConnection = NULL;
        NTSTATUS                        ntStatus2;
        
        ASSERT(UnknownTopology != NULL);
    
        //
        // Get the IUnregisterPhysicalConnection interface
        //
        ntStatus2 = UnknownTopology->QueryInterface( 
            IID_IUnregisterPhysicalConnection,
            (PVOID *)&unregisterPhysicalConnection);
    
        if (NT_SUCCESS(ntStatus2))
        {
            //
            // Remove the render physical connection
            //
            if (fPhyConnWaveToTopo)
            {
                ntStatus2 = unregisterPhysicalConnection->UnregisterPhysicalConnection(
                    m_pDeviceObject,
                    UnknownWave,
                    PhysicalConnections->ulWaveOut,
                    UnknownTopology,
                    PhysicalConnections->ulTopologyIn);
            }
        
            //
            // Remove the capture physical connection
            //
            if (fPhyConnTopoToWave)
            {
                ntStatus2 = unregisterPhysicalConnection->UnregisterPhysicalConnection(
                    m_pDeviceObject,
                    UnknownTopology,
                    PhysicalConnections->ulTopologyOut,
                    UnknownWave,
                    PhysicalConnections->ulWaveIn);
            }
        }

        UNREFERENCED_VAR(ntStatus2);
        
        //
        // Release the IUnregisterPhysicalConnection interface.
        //
        SAFE_RELEASE(unregisterPhysicalConnection);
    }

    return ntStatus;
}

//=============================================================================
#pragma code_seg("PAGE")
STDMETHODIMP_(NTSTATUS)
CAdapterCommon::DisconnectTopologies
(
    _In_ PUNKNOWN                   UnknownTopology,
    _In_ PUNKNOWN                   UnknownWave,
    _In_ PHYSICALCONNECTIONTABLE*   PhysicalConnections
)
/*++

Routine Description:

  Disconnects the bridge pins between the wave and mixer topologies.

Arguments:

Return Value:

  NTSTATUS

--*/
{
    PAGED_CODE();
    DPF_ENTER(("[CAdapterCommon::DisconnectTopologies]"));
    
    ASSERT(m_pDeviceObject != NULL);
    
    NTSTATUS                        ntStatus                        = STATUS_SUCCESS;
    NTSTATUS                        ntStatus2                       = STATUS_SUCCESS;
    PUNREGISTERPHYSICALCONNECTION   unregisterPhysicalConnection    = NULL;

    //
    // Get the IUnregisterPhysicalConnection interface
    //
    ntStatus = UnknownTopology->QueryInterface( 
        IID_IUnregisterPhysicalConnection,
        (PVOID *)&unregisterPhysicalConnection);
    
    if (NT_SUCCESS(ntStatus))
    { 
        //
        // Remove the render physical connection
        //
        if ((PhysicalConnections->ulWaveOut != (ULONG)-1) &&
            (PhysicalConnections->ulTopologyIn != (ULONG)-1))
        {
            ntStatus = unregisterPhysicalConnection->UnregisterPhysicalConnection(
                m_pDeviceObject,
                UnknownWave,
                PhysicalConnections->ulWaveOut,
                UnknownTopology,
                PhysicalConnections->ulTopologyIn);
            
            if(!NT_SUCCESS(ntStatus))
            {
                DPF(D_TERSE, ("DisconnectTopologies: UnregisterPhysicalConnection(render) failed, 0x%x", ntStatus));
            }
        }
    
        //
        // Remove the capture physical connection
        //
        if ((PhysicalConnections->ulTopologyOut != (ULONG)-1) &&
            (PhysicalConnections->ulWaveIn != (ULONG)-1))
        {
            ntStatus = unregisterPhysicalConnection->UnregisterPhysicalConnection(
                m_pDeviceObject,
                UnknownTopology,
                PhysicalConnections->ulTopologyOut,
                UnknownWave,
                PhysicalConnections->ulWaveIn);
            
            if(!NT_SUCCESS(ntStatus2))
            {
                DPF(D_TERSE, ("DisconnectTopologies: UnregisterPhysicalConnection(capture) failed, 0x%x", ntStatus2));
                if (NT_SUCCESS(ntStatus))
                {
                    ntStatus = ntStatus2;
                }
            }
        }
    }
    
    //
    // Release the IUnregisterPhysicalConnection interface.
    //
    SAFE_RELEASE(unregisterPhysicalConnection);

    return ntStatus;
}

//=============================================================================
#pragma code_seg("PAGE")
STDMETHODIMP_(NTSTATUS)
CAdapterCommon::InstallEndpointFilters
(
    _In_opt_    PIRP                Irp, 
    _In_        PENDPOINT_MINIPAIR  MiniportPair,
    _In_opt_    PVOID               DeviceContext,
    _Out_opt_   PUNKNOWN *          UnknownTopology,
    _Out_opt_   PUNKNOWN *          UnknownWave
)
{
    PAGED_CODE();
    DPF_ENTER(("[CAdapterCommon::InstallEndpointFilters]"));
    
    NTSTATUS            ntStatus            = STATUS_SUCCESS;
    PUNKNOWN            unknownTopology     = NULL;
    PUNKNOWN            unknownWave         = NULL;
    
    if (UnknownTopology)
    {
        *UnknownTopology = NULL;
    }

    if (UnknownWave)
    {
        *UnknownWave = NULL;
    }
    
    // Install SYSVAD topology miniport for the render endpoint.
    //
    ntStatus = InstallSubdevice(Irp,
                                MiniportPair->TopoName, // make sure this name matches with SYSVAD.<TopoName>.szPname in the inf's [Strings] section
                                CLSID_PortTopology,
                                CLSID_PortTopology, 
                                MiniportPair->TopoCreateCallback,
                                DeviceContext,
                                MiniportPair,
                                NULL,
                                IID_IPortTopology,
                                NULL,
                                &unknownTopology
                                );
  
    // Install SYSVAD wave miniport for the render endpoint.
    //
    if (NT_SUCCESS(ntStatus))
    {
        ntStatus = InstallSubdevice(Irp,
                                    MiniportPair->WaveName, // make sure this name matches with SYSVAD.<WaveName>.szPname in the inf's [Strings] section
                                    CLSID_PortWaveRT,
                                    CLSID_PortWaveRT,   
                                    MiniportPair->WaveCreateCallback,
                                    DeviceContext,
                                    MiniportPair,
                                    NULL,
                                    IID_IPortWaveRT,
                                    NULL, 
                                    &unknownWave
                                    );
    }

    if (unknownTopology && unknownWave)
    {
        //
        // register wave <=> topology connections
        // This will connect bridge pins of wave and topology
        // miniports.
        //
        ntStatus = ConnectTopologies(
            unknownTopology,
            unknownWave,
            MiniportPair->PhysicalConnections);
    }

    if (NT_SUCCESS(ntStatus))
    {
        //
        // Set output parameters.
        //
        if (UnknownTopology != NULL && unknownTopology != NULL)
        {
            unknownTopology->AddRef();
            *UnknownTopology = unknownTopology;
        }
        
        if (UnknownWave != NULL && unknownWave != NULL)
        {
            unknownWave->AddRef();
            *UnknownWave = unknownWave;
        }
    }
    else
    {
        if (unknownTopology != NULL)
        {
            UnregisterSubdevice(unknownTopology);
        }
            
        if (unknownWave != NULL)
        {
            UnregisterSubdevice(unknownWave);
        }
    }
   
    SAFE_RELEASE(unknownTopology);
    SAFE_RELEASE(unknownWave);

    return ntStatus;
}

//=============================================================================
#pragma code_seg("PAGE")
STDMETHODIMP_(NTSTATUS)
CAdapterCommon::RemoveEndpointFilters
(
    _In_        PENDPOINT_MINIPAIR  MiniportPair,
    _In_opt_    PUNKNOWN            UnknownTopology,
    _In_opt_    PUNKNOWN            UnknownWave
)
{
    PAGED_CODE();
    DPF_ENTER(("[CAdapterCommon::RemoveEndpointFilters]"));
    
    NTSTATUS    ntStatus   = STATUS_SUCCESS;
    
    if (UnknownTopology != NULL && UnknownWave != NULL)
    {
        ntStatus = DisconnectTopologies(
            UnknownTopology,
            UnknownWave,
            MiniportPair->PhysicalConnections);

        if (!NT_SUCCESS(ntStatus))
        {
            DPF(D_VERBOSE, ("RemoveEndpointFilters: DisconnectTopologies failed: 0x%x", ntStatus));
        }
    }

    ntStatus = UnregisterSubdevice(UnknownWave);
    if (!NT_SUCCESS(ntStatus))
    {
        DPF(D_VERBOSE, ("RemoveEndpointFilters: UnregisterSubdevice(wave) failed: 0x%x", ntStatus));
    }
    
    ntStatus = UnregisterSubdevice(UnknownTopology);
    if (!NT_SUCCESS(ntStatus))
    {
        DPF(D_VERBOSE, ("RemoveEndpointFilters: UnregisterSubdevice(topology) failed: 0x%x", ntStatus));
    }

    //
    // All Done.
    //
    ntStatus = STATUS_SUCCESS;
    
    return ntStatus;
}

#ifdef SYSVAD_BTH_BYPASS
//
// CAdapterCommon Bluetooth Hands-Free Profile function implementation.
//

//=============================================================================
#pragma code_seg("PAGE")
VOID
CAdapterCommon::EvtBthHfpScoBypassInterfaceWorkItem
(
    _In_    WDFWORKITEM WorkItem
)
/*++

Routine Description:

  The function handles the arrival or removal of a HFP SCO Bypass interface.

Arguments:

    WorkItem    - WDF work-item object.
    
--*/
{
    PAGED_CODE();
    DPF_ENTER(("[EvtBthHfpScoBypassInterfaceWorkItem]"));

    CAdapterCommon        * This;
    
    if (WorkItem == NULL) 
    {
        return;
    }

    This = GetBthHfpWorkItemContext(WorkItem)->Adapter;
    ASSERT(This != NULL);

    for (;;)
    {
        PLIST_ENTRY         le          = NULL;
        BthHfpWorkTask    * task        = NULL;
        
        //
        // Retrieve a taask.
        //
        ExAcquireFastMutex(&This->m_BthHfpFastMutex);
        if (!IsListEmpty(&This->m_BthHfpWorkTasks))
        {
            le = RemoveHeadList(&This->m_BthHfpWorkTasks);
            task = CONTAINING_RECORD(le, BthHfpWorkTask, ListEntry);
            InitializeListHead(le);
        }
        ExReleaseFastMutex(&This->m_BthHfpFastMutex);
        
        if (task == NULL)
        {
            break;
        }

        ASSERT(task->Device != NULL);
        _Analysis_assume_(task->Device != NULL);
        
        //
        // Process the task.
        //
        switch(task->Action)
        {
        case eBthHfpTaskStart:
            task->Device->Start();
            break;

        case eBthHfpTaskStop:
            task->Device->Stop();
            break;

        default:
            DPF(D_ERROR, ("EvtBthHfpScoBypassInterfaceWorkItem: invalid action %d", task->Action));
            break;
        }

        //
        // Release the ref we took on the device when we inserted the task in the queue.
        // For a stop operation this may be the last reference.
        //
        SAFE_RELEASE(task->Device);

        //
        // Free the task.
        //
        ExFreeToNPagedLookasideList(&This->m_BhtHfpWorkTaskPool, task);
    }
}

//=============================================================================
#pragma code_seg("PAGE")
BthHfpDevice *
CAdapterCommon::BthHfpDeviceFind
(
    _In_ PUNICODE_STRING SymbolicLinkName
)
/*++

Routine Description:

  The function looks for the specified device in the adapter's list.

Arguments:

  SymbolicLinkName - interface's symbolic link.
  
Return Value:

  BthHfpDevice pointer or NULL.

--*/
{
    PAGED_CODE();
    DPF_ENTER(("[CAdapterCommon::BthHfpDeviceFind]"));
    
    PLIST_ENTRY     le          = NULL;
    BthHfpDevice  * bthDevice   = NULL;

    ExAcquireFastMutex(&m_BthHfpFastMutex);
    
    for (le = m_BthHfpDevices.Flink; le != &m_BthHfpDevices; le = le->Flink)
    {
        BthHfpDevice  *     tmpBthDevice    = BthHfpDevice::GetBthHfpDevice(le);
        ASSERT(tmpBthDevice != NULL);
        
        PUNICODE_STRING     unicodeStr      = tmpBthDevice->GetSymbolicLinkName();
        ASSERT(unicodeStr != NULL);

        if (unicodeStr->Length == SymbolicLinkName->Length &&
            0 == wcsncmp(unicodeStr->Buffer, SymbolicLinkName->Buffer, unicodeStr->Length/sizeof(WCHAR)))
        {
            // Found it!
            bthDevice = tmpBthDevice;
            bthDevice->AddRef();
            break;
        }
    }
    
    ExReleaseFastMutex(&m_BthHfpFastMutex);

    return bthDevice;
}

//=============================================================================
#pragma code_seg("PAGE")
NTSTATUS
CAdapterCommon::BthHfpScoInterfaceArrival
(
    _In_ PUNICODE_STRING SymbolicLinkName
)
/*++

Routine Description:

  The function handles the arrival of a new HFP SCO Bypass interface.

Arguments:

  SymbolicLinkName - new interface's symbolic link.
  
Return Value:

  NT status code.

--*/
{
    PAGED_CODE();
    DPF_ENTER(("[CAdapterCommon::BthHfpScoInterfaceArrival]"));

    NTSTATUS            ntStatus        = STATUS_SUCCESS;
    BthHfpDevice      * bthDevice       = NULL;
    BthHfpWorkTask    * bthWorkTask     = NULL;
    
    DPF(D_VERBOSE, ("BthHfpScoInterfaceArrival: SymbolicLinkName %wZ", SymbolicLinkName));

    //
    // Check if the Bluetooth device is already present.
    // According to the docs it is possible to receive two notifications for the same
    // interface.
    //
    bthDevice = BthHfpDeviceFind(SymbolicLinkName);
    if (bthDevice != NULL)
    {
        DPF(D_VERBOSE, ("BthHfpScoInterfaceArrival: Bluetooth HFP device already present"));
        SAFE_RELEASE(bthDevice);
        ntStatus = STATUS_SUCCESS;
        goto Done;
    }
    
    //
    // Alloc a new structure for this Bluetooth hands-free device.
    //
    bthDevice = new (NonPagedPoolNx, MINADAPTER_POOLTAG) BthHfpDevice(NULL); // NULL -> OuterUnknown
    if (NULL == bthDevice)
    {
        DPF(D_ERROR, ("BthHfpScoInterfaceArrival: unable to allocate BthHfpDevice, out of memory"));
        ntStatus = STATUS_INSUFFICIENT_RESOURCES;
        goto Done;
    }
    
    DPF(D_VERBOSE, ("BthHfpScoInterfaceArrival: created BthHfpDevice 0x%p ", bthDevice));
    
    //
    // Basic initialization of the Bluetooth Hands-Free Profile interface.
    // The audio miniport creation is done later by the BthHfpDevice.Start()
    // which is invoked asynchronously by a worker thread.
    // BthHfpDevice->Init() must be invoked just after the creation of the object.
    //
    ntStatus = bthDevice->Init(this, SymbolicLinkName);
    IF_FAILED_JUMP(ntStatus, Done);

    //
    // Get and init a work task.
    //
    bthWorkTask = (BthHfpWorkTask*)ExAllocateFromNPagedLookasideList(&m_BhtHfpWorkTaskPool);
    if (NULL == bthWorkTask)
    {
        DPF(D_ERROR, ("BthHfpScoInterfaceArrival: unable to allocate BthHfpWorkTask, out of memory"));
        ntStatus = STATUS_INSUFFICIENT_RESOURCES;
        goto Done;
    }
    
    RtlZeroMemory(bthWorkTask, sizeof(*bthWorkTask));
    bthWorkTask->Action = eBthHfpTaskStart;
    InitializeListHead(&bthWorkTask->ListEntry);
    // Note that bthDevice has one reference at this point.
    bthWorkTask->Device = bthDevice;
    
    ExAcquireFastMutex(&m_BthHfpFastMutex);
    
    //
    // Insert this new Bluetooth HFP device in our list.
    //
    InsertTailList(&m_BthHfpDevices, bthDevice->GetListEntry());

    //
    // Add a new task for the worker thread.
    //
    InsertTailList(&m_BthHfpWorkTasks, &bthWorkTask->ListEntry);
    bthDevice->AddRef();    // released when task runs.

    //
    // Schedule a work-item if not already running.
    //
    WdfWorkItemEnqueue(m_BthHfpWorkItem);
    
    ExReleaseFastMutex(&m_BthHfpFastMutex);

Done:
    if (!NT_SUCCESS(ntStatus))
    {
        // Release the last ref, this will delete the BthHfpDevice 
        SAFE_RELEASE(bthDevice);

        if (bthWorkTask != NULL)
        {
            ExFreeToNPagedLookasideList(&m_BhtHfpWorkTaskPool, bthWorkTask);
            bthWorkTask = NULL;
        }
    }
    
    return ntStatus;
}

//=============================================================================
#pragma code_seg("PAGE")
NTSTATUS
CAdapterCommon::BthHfpScoInterfaceRemoval
(
    _In_ PUNICODE_STRING SymbolicLinkName
)
/*++

Routine Description:

  The function handles the removal of a HFP SCO Bypass interface.

Arguments:

  SymbolicLinkName - interface's symbolic link to remove.
  
Return Value:

  NT status code.

--*/
{
    PAGED_CODE();
    DPF_ENTER(("[CAdapterCommon::BthHfpScoInterfaceRemoval]"));

    NTSTATUS            ntStatus        = STATUS_SUCCESS;
    BthHfpDevice      * bthDevice       = NULL;
    BthHfpWorkTask    * bthWorkTask     = NULL;
    
    DPF(D_VERBOSE, ("BthHfpScoInterfaceRemoval: SymbolicLinkName %wZ", SymbolicLinkName));

    //
    // Check if the Bluetooth device is present.
    //
    bthDevice = BthHfpDeviceFind(SymbolicLinkName);
    if (bthDevice == NULL)
    {
        // This can happen if the init/start of the BthHfpDevice failed.
        DPF(D_VERBOSE, ("BthHfpScoInterfaceRemoval: Bluetooth HFP device not found"));
        ntStatus = STATUS_SUCCESS;
        goto Done;
    }
    
    //
    // Init a work task.
    //
    bthWorkTask = (BthHfpWorkTask*)ExAllocateFromNPagedLookasideList(&m_BhtHfpWorkTaskPool);
    if (NULL == bthWorkTask)
    {
        DPF(D_ERROR, ("BthHfpScoInterfaceRemoval: unable to allocate BthHfpWorkTask, out of memory"));
        ntStatus = STATUS_INSUFFICIENT_RESOURCES;
        goto Done;
    }
    
    RtlZeroMemory(bthWorkTask, sizeof(*bthWorkTask));
    bthWorkTask->Action = eBthHfpTaskStop;
    InitializeListHead(&bthWorkTask->ListEntry);
    // Work-item callback will release the reference we got above from BthHfpDeviceFind.
    bthWorkTask->Device = bthDevice;

    ExAcquireFastMutex(&m_BthHfpFastMutex);
    
    //
    // Remove this Bluetooth device from our list and release the associated reference.
    //
    RemoveEntryList(bthDevice->GetListEntry());
    InitializeListHead(bthDevice->GetListEntry());
    bthDevice->Release();   // This is not the last ref.

    //
    // Add a new task for the worker thread.
    //
    InsertTailList(&m_BthHfpWorkTasks, &bthWorkTask->ListEntry);

    //
    // Schedule a work-item if not already running.
    //
    WdfWorkItemEnqueue(m_BthHfpWorkItem);
    
    ExReleaseFastMutex(&m_BthHfpFastMutex);

    //
    // All done.
    //
    ntStatus = STATUS_SUCCESS;

Done:

    if (!NT_SUCCESS(ntStatus))
    {
        // Release the ref we got in find.
        SAFE_RELEASE(bthDevice);
    }
    
    return ntStatus;
}

//=============================================================================
#pragma code_seg("PAGE")
NTSTATUS
CAdapterCommon::EvtBthHfpScoBypassInterfaceChange(
  _In_          PVOID   NotificationPointer,
  _Inout_opt_   PVOID   Context
  )
/*++

Routine Description:

    This callback is invoked when a new HFP SCO Bypass interface is added or removed.

Arguments:
    NotificationPointer - Interface change notification 
    Context - CAdapterCommon ptr.
    
Return Value:

    NT status code.

--*/
{
    PAGED_CODE();
    DPF_ENTER(("[EvtBthHfpScoBypassInterfaceChange]"));
    
    NTSTATUS                              ntStatus      = STATUS_SUCCESS;
    CAdapterCommon                      * This          = NULL;
    PDEVICE_INTERFACE_CHANGE_NOTIFICATION Notification  = (PDEVICE_INTERFACE_CHANGE_NOTIFICATION) NotificationPointer;
    
    //
    // Make sure this is the interface class we extect. Any other class guid
    // is an error, but let it go since it is not fatal to the machine.
    //
    if (!IsEqualGUID(Notification->InterfaceClassGuid, GUID_DEVINTERFACE_BLUETOOTH_HFP_SCO_HCIBYPASS)) 
    {
        DPF(D_VERBOSE, ("EvtBthHfpScoBypassInterfaceChange: bad interface ClassGuid"));
        ASSERTMSG("EvtBthHfpScoBypassInterfaceChange: bad interface ClassGuid ", FALSE);
        
        goto Done;
    }

    This = (CAdapterCommon *)Context;
    ASSERT(This != NULL);
    _Analysis_assume_(This != NULL);
    
    //
    // Take action based on the event. Any other event type is an error, 
    // but let it go since it is not fatal to the machine.
    //
    if (IsEqualGUID(Notification->Event, GUID_DEVICE_INTERFACE_ARRIVAL)) 
    {
        ntStatus = This->BthHfpScoInterfaceArrival(Notification->SymbolicLinkName);
    }
    else if (IsEqualGUID(Notification->Event, GUID_DEVICE_INTERFACE_REMOVAL))
    {
        ntStatus = This->BthHfpScoInterfaceRemoval(Notification->SymbolicLinkName);
    }
    else 
    {
        DPF(D_VERBOSE, ("EvtBthHfpScoBypassInterfaceChange: bad "
            "GUID_DEVINTERFACE_BLUETOOTH_HFP_SCO_HCIBYPASS event"));
        ASSERTMSG("EvtBthHfpScoBypassInterfaceChange: bad "
            "GUID_DEVINTERFACE_BLUETOOTH_HFP_SCO_HCIBYPASS event ", FALSE);
        
        goto Done;
    }

Done:
    return ntStatus;
}

//=============================================================================
#pragma code_seg("PAGE")
NTSTATUS
CAdapterCommon::InitBthScoBypass()
/*++

Routine Description:

  Initialize the bluetooth bypass environment.

Return Value:

  NT status code.

--*/
{
    PAGED_CODE();
    DPF_ENTER(("[CAdapterCommon::InitBluetoothBypass]"));

    NTSTATUS                ntStatus = STATUS_SUCCESS;
    WDF_WORKITEM_CONFIG     wiConfig;
    WDF_OBJECT_ATTRIBUTES   attributes;
    BthHfpWorkItemContext * wiContext;
    
    //
    // Init spin-lock, linked lists, work-item, event, etc.
    // Init all members to default values. This basic init should not fail.
    //
    m_BthHfpWorkItem = NULL;
    m_BthHfpScoNotificationHandle = NULL;
    ExInitializeFastMutex(&m_BthHfpFastMutex);
    InitializeListHead(&m_BthHfpWorkTasks);
    InitializeListHead(&m_BthHfpDevices);
    m_BhtHfpWorkTaskPoolElementSize = sizeof(BthHfpWorkTask);
    ExInitializeNPagedLookasideList(&m_BhtHfpWorkTaskPool,
                                    NULL,
                                    NULL,
                                    0,
                                    m_BhtHfpWorkTaskPoolElementSize,
                                    MINADAPTER_POOLTAG,
                                    0); 
    //
    // Enable Bluetooth HFP SCO-Bypass Cleanup.
    // Do any allocation/initialization that can fail after this point.
    //
    m_BthHfpEnableCleanup = TRUE;

    //
    // Allocate a WDF work-item.
    //
    WDF_WORKITEM_CONFIG_INIT(&wiConfig, EvtBthHfpScoBypassInterfaceWorkItem);
    wiConfig.AutomaticSerialization = FALSE;

    WDF_OBJECT_ATTRIBUTES_INIT_CONTEXT_TYPE(&attributes, BthHfpWorkItemContext);
    attributes.ParentObject = GetWdfDevice();
    ntStatus = WdfWorkItemCreate( &wiConfig,
                                  &attributes,
                                  &m_BthHfpWorkItem);
    IF_FAILED_ACTION_JUMP(
        ntStatus,
        DPF(D_ERROR, ("InitBthScoBypass: WdfWorkItemCreate failed: 0x%x", ntStatus)),
        Done);

    wiContext = GetBthHfpWorkItemContext(m_BthHfpWorkItem);
    wiContext->Adapter = this; // weak ref.
 
    //
    // Register for bluetooth heandsfree profile interface changes.
    //
    ntStatus = IoRegisterPlugPlayNotification (
                    EventCategoryDeviceInterfaceChange,
                    PNPNOTIFY_DEVICE_INTERFACE_INCLUDE_EXISTING_INTERFACES,
                    (PVOID)&GUID_DEVINTERFACE_BLUETOOTH_HFP_SCO_HCIBYPASS,
                    m_pDeviceObject->DriverObject,
                    EvtBthHfpScoBypassInterfaceChange,
                    (PVOID)this,
                    &m_BthHfpScoNotificationHandle);

    IF_FAILED_ACTION_JUMP(
        ntStatus,
        DPF(D_ERROR, ("InitBthScoBypass: IoRegisterPlugPlayNotification(GUID_DEVINTERFACE_BLUETOOTH_HFP_SCO_HCIBYPASS) failed: 0x%x", ntStatus)),
        Done);

    //
    // Initialization completed.
    //
    ntStatus = STATUS_SUCCESS;

Done:
    return ntStatus;
}

//=============================================================================
#pragma code_seg("PAGE")
VOID
CAdapterCommon::CleanupBthScoBypass()
/*++

Routine Description:

  Cleanup the bluetooth bypass environment.

--*/
{
    PAGED_CODE();
    DPF_ENTER(("[CAdapterCommon::CleanupBthScoBypass]"));

    //
    // Do nothing if Bluetooth HFP environment was not correctly initialized.
    //
    if (m_BthHfpEnableCleanup == FALSE)
    {
        return;
    }
    
    //
    // Unregister for bluetooth heandsfree profile interface changes.
    //
    if (m_BthHfpScoNotificationHandle != NULL)
    {
        (void)IoUnregisterPlugPlayNotificationEx(m_BthHfpScoNotificationHandle);
        m_BthHfpScoNotificationHandle = NULL;
    }

    //
    // Wait for the Bluetooth hands-free profile worker thread to be done.
    //
    if (m_BthHfpWorkItem != NULL)
    {
        WdfWorkItemFlush(m_BthHfpWorkItem);
        WdfObjectDelete(m_BthHfpWorkItem);
        m_BthHfpWorkItem = NULL;
    }
    
    ASSERT(IsListEmpty(&m_BthHfpWorkTasks));
    
    //
    // Stop and delete all BthHfpDevices. We are the only thread accessing this list, 
    // so there is no need to acquire the mutex.
    //
    while (!IsListEmpty(&m_BthHfpDevices))
    {
        BthHfpDevice  * bthDevice   = NULL;
        PLIST_ENTRY     le          = NULL;
        
        le = RemoveHeadList(&m_BthHfpDevices);
        
        bthDevice = BthHfpDevice::GetBthHfpDevice(le);
        InitializeListHead(le);

        // bthDevice is invalid after this call.
        bthDevice->Stop();

        // This should be the last reference.
        bthDevice->Release();
    }
    
    ASSERT(IsListEmpty(&m_BthHfpDevices));
    
    //
    // General cleanup.
    //
    ExDeleteNPagedLookasideList(&m_BhtHfpWorkTaskPool);
}
#endif  // SYSVAD_BTH_BYPASS

#ifdef SYSVAD_BTH_BYPASS
//
// BthHfpDevice implementation.
//

//=============================================================================
#pragma code_seg("PAGE")
STDMETHODIMP
BthHfpDevice::NonDelegatingQueryInterface
( 
    _In_ REFIID                 Interface,
    _COM_Outptr_ PVOID *        Object 
)
/*++

Routine Description:

  QueryInterface routine for BthHfpDevice

Arguments:

  Interface - 

  Object -

Return Value:

  NT status code.

--*/
{
    PAGED_CODE();

    ASSERT(Object);

    if (IsEqualGUIDAligned(Interface, IID_IUnknown))
    {
        *Object = PVOID(PUNKNOWN(PBTHHFPDEVICECOMMON(this)));
    }
    else if (IsEqualGUIDAligned(Interface, IID_IBthHfpDeviceCommon))
    {
        *Object = PVOID(PBTHHFPDEVICECOMMON(this));
    }
    else
    {
        *Object = NULL;
    }

    if (*Object)
    {
        PUNKNOWN(*Object)->AddRef();
        return STATUS_SUCCESS;
    }

    return STATUS_INVALID_PARAMETER;
} // NonDelegatingQueryInterface

//=============================================================================
// Dummy stubs to override the default WDF behavior of closing the target 
// on query remove. This driver closes and deletes the supporting objects
// when the target removes the BTH HFP SCO Bypass interface.
//

#pragma code_seg("PAGE")
NTSTATUS
BthHfpDevice::EvtBthHfpTargetQueryRemove
( 
    _In_    WDFIOTARGET     IoTarget
)
{
    PAGED_CODE();
    DPF_ENTER(("[BthHfpDevice::EvtBthHfpTargetQueryRemove]"));
    
    UNREFERENCED_PARAMETER(IoTarget);
    return STATUS_SUCCESS;
}

#pragma code_seg("PAGE")
VOID
BthHfpDevice::EvtBthHfpTargetRemoveCanceled
( 
    _In_    WDFIOTARGET     IoTarget
)
{
    PAGED_CODE();
    DPF_ENTER(("[BthHfpDevice::EvtBthHfpTargetRemoveCanceled]"));
    
    UNREFERENCED_PARAMETER(IoTarget);
}
#pragma code_seg("PAGE")
VOID
BthHfpDevice::EvtBthHfpTargetRemoveComplete
( 
    _In_    WDFIOTARGET     IoTarget
)
{
    PAGED_CODE();
    DPF_ENTER(("[BthHfpDevice::EvtBthHfpTargetRemoveComplete]"));
    
    UNREFERENCED_PARAMETER(IoTarget);
}

//=============================================================================
#pragma code_seg("PAGE")
NTSTATUS
BthHfpDevice::Init
(
    _In_ CAdapterCommon     * Adapter, 
    _In_ PUNICODE_STRING      SymbolicLinkName
)
{
    PAGED_CODE();
    DPF_ENTER(("[BthHfpDevice::Init]"));

    NTSTATUS                                ntStatus        = STATUS_SUCCESS;
    BthHfpDeviceNotificationWorkItemContext *wiCtx          = NULL;
    BthHfpDeviceNotificationReqContext      *reqCtx         = NULL;
    WDF_OBJECT_ATTRIBUTES                   attributes;
    WDF_IO_TARGET_OPEN_PARAMS               openParams;
    WDF_WORKITEM_CONFIG                     wiConfig;

    AddRef(); // first ref.
    
    //
    // Basic init of all the class' members.
    //
    m_State                 = eBthHfpStateInitializing;
    m_Adapter               = Adapter;

    // Static config.
    m_WdfIoTarget           = NULL;
    m_SpeakerMiniports      = NULL;
    m_MicMiniports          = NULL;
    m_UnknownSpeakerTopology = NULL;
    m_UnknownSpeakerWave    = NULL;
    m_UnknownMicTopology    = NULL;
    m_UnknownMicWave        = NULL;
    m_Descriptor            = NULL;
    m_VolumePropValues      = NULL;

    // Notification updates. 
    m_SpeakerVolumeLevel    = 0;
    m_MicVolumeLevel        = 0;
    m_ConnectionStatusLong  = FALSE;
    m_StreamStatusLong      = STATUS_INVALID_DEVICE_STATE; // Sco stream is not open.
    m_NRECDisableStatusLong = FALSE;
        
    m_StreamReq             = NULL;
    m_SpeakerVolumeReq      = NULL;
    m_MicVolumeReq          = NULL;
    m_ConnectionReq         = NULL;
    m_NRECDisableStatusReq  = NULL;

    m_WorkItem              = NULL;
    m_ReqCollection         = NULL;
    
    m_nStreams              = 0;

    KeInitializeEvent(&m_StreamStatusEvent, NotificationEvent, TRUE);

    InitializeListHead(&m_ListEntry);
    KeInitializeSpinLock(&m_Lock);
    
    RtlZeroMemory(&m_SymbolicLinkName, sizeof(m_SymbolicLinkName));
    RtlZeroMemory(&m_SpeakerAudioSymbolicLinkName, sizeof(m_SpeakerAudioSymbolicLinkName));
    RtlZeroMemory(&m_MicAudioSymbolicLinkName, sizeof(m_MicAudioSymbolicLinkName));
    
    RtlZeroMemory(&m_SpeakerVolumeCallback, sizeof(m_SpeakerVolumeCallback));
    RtlZeroMemory(&m_SpeakerConnectionStatusCallback, sizeof(m_SpeakerConnectionStatusCallback));
    RtlZeroMemory(&m_MicVolumeCallback, sizeof(m_MicVolumeCallback));
    RtlZeroMemory(&m_MicConnectionStatusCallback, sizeof(m_MicConnectionStatusCallback));
    
    //
    // Allocate a notification WDF work-item.
    //
    WDF_WORKITEM_CONFIG_INIT(&wiConfig, EvtBthHfpDeviceNotificationStatusWorkItem);
    wiConfig.AutomaticSerialization = FALSE;

    WDF_OBJECT_ATTRIBUTES_INIT_CONTEXT_TYPE(&attributes, BthHfpDeviceNotificationWorkItemContext);
    attributes.ParentObject = Adapter->GetWdfDevice();
    ntStatus = WdfWorkItemCreate( &wiConfig,
                                  &attributes,
                                  &m_WorkItem);
    IF_FAILED_ACTION_JUMP(
        ntStatus,
        DPF(D_ERROR, ("Init: WdfWorkItemCreate failed: 0x%x", ntStatus)),
        Done);

    wiCtx = GetBthHfpDeviceNotificationWorkItemContext(m_WorkItem);
    wiCtx->BthHfpDevice = this; // weak ref.

    //
    // Allocate a collection to hold notification requests for the notification work-item.
    //
    WDF_OBJECT_ATTRIBUTES_INIT(&attributes);
    attributes.ParentObject = m_Adapter->GetWdfDevice();
    ntStatus = WdfCollectionCreate(
        &attributes,
        &m_ReqCollection);
    
    IF_FAILED_ACTION_JUMP(
        ntStatus,
        DPF(D_ERROR, ("Init: WdfCollectionCreate failed: 0x%x", ntStatus)),
        Done);
    
    //
    // Open the target interface.
    //
    WDF_OBJECT_ATTRIBUTES_INIT(&attributes);
    attributes.ParentObject = m_Adapter->GetWdfDevice();
    ntStatus = WdfIoTargetCreate(m_Adapter->GetWdfDevice(),
                                 &attributes,
                                 &m_WdfIoTarget);
    IF_FAILED_ACTION_JUMP(
        ntStatus,
        DPF(D_ERROR, ("Init: WdfIoTargetCreate failed: 0x%x", ntStatus)),
        Done);

    WDF_IO_TARGET_OPEN_PARAMS_INIT_OPEN_BY_NAME(
        &openParams,
        SymbolicLinkName,
        STANDARD_RIGHTS_ALL);     
    
    openParams.EvtIoTargetQueryRemove = EvtBthHfpTargetQueryRemove;
    openParams.EvtIoTargetRemoveCanceled = EvtBthHfpTargetRemoveCanceled;
    openParams.EvtIoTargetRemoveComplete = EvtBthHfpTargetRemoveComplete;
    
    ntStatus = WdfIoTargetOpen(m_WdfIoTarget, &openParams);
    IF_FAILED_ACTION_JUMP(
        ntStatus,
        DPF(D_ERROR, ("Init: WdfIoTargetOpen(%wZ) failed: 0x%x", SymbolicLinkName, ntStatus)),
        Done);
    
    //
    // Make a copy of the symbolic link list.
    //
    m_SymbolicLinkName.MaximumLength = SymbolicLinkName->MaximumLength;
    m_SymbolicLinkName.Length = SymbolicLinkName->Length;
    m_SymbolicLinkName.Buffer = (PWSTR) ExAllocatePoolWithTag(NonPagedPoolNx,
                                                              SymbolicLinkName->MaximumLength,
                                                              MINADAPTER_POOLTAG);
    if (m_SymbolicLinkName.Buffer == NULL)
    {
        ntStatus = STATUS_INSUFFICIENT_RESOURCES;
    }
    
    IF_FAILED_ACTION_JUMP(
        ntStatus,
        DPF(D_ERROR, ("Init: ExAllocatePoolWithTag failed, out of memory")),
        Done);
    
    RtlCopyUnicodeString(&m_SymbolicLinkName, SymbolicLinkName);

    //
    // Allocate the WDF requests for status notifications.
    //
    
    //
    // IOCTL_BTHHFP_DEVICE_GET_NRECDISABLE_STATUS_UPDATE 
    //
    WDF_OBJECT_ATTRIBUTES_INIT_CONTEXT_TYPE(&attributes, BthHfpDeviceNotificationReqContext);
    attributes.ParentObject = m_Adapter->GetWdfDevice();
    ntStatus = WdfRequestCreate(
        &attributes,
        m_WdfIoTarget,
        &m_NRECDisableStatusReq);
    IF_FAILED_ACTION_JUMP(
        ntStatus,
        DPF(D_ERROR, ("Init: WdfRequestCreate(Nrec-disable status) failed, 0x%x", ntStatus)),
        Done);

    // Init context.
    reqCtx = GetBthHfpDeviceNotificationReqContext(m_NRECDisableStatusReq);
    reqCtx->BthHfpDevice = this; // weak ref.
    
    ntStatus = WdfMemoryCreatePreallocated(
        WDF_NO_OBJECT_ATTRIBUTES,
        &reqCtx->Buffer,
        sizeof(reqCtx->Buffer.bImmediate),
        &reqCtx->MemIn);
    IF_FAILED_ACTION_JUMP(
        ntStatus,
        DPF(D_ERROR, ("Init: WdfMemoryCreatePreallocated failed, 0x%x", ntStatus)),
        Done);
    
    ntStatus = WdfMemoryCreatePreallocated(
        WDF_NO_OBJECT_ATTRIBUTES,
        &reqCtx->Buffer,
        sizeof(reqCtx->Buffer.BoolStatus),
        &reqCtx->MemOut);
    IF_FAILED_ACTION_JUMP(
        ntStatus,
        DPF(D_ERROR, ("Init: WdfMemoryCreatePreallocated failed, 0x%x", ntStatus)),
        Done);
    
    //
    // IOCTL_BTHHFP_SPEAKER_GET_VOLUME_STATUS_UPDATE 
    //
    WDF_OBJECT_ATTRIBUTES_INIT_CONTEXT_TYPE(&attributes, BthHfpDeviceNotificationReqContext);
    attributes.ParentObject = m_Adapter->GetWdfDevice();
    ntStatus = WdfRequestCreate(
        &attributes,
        m_WdfIoTarget,
        &m_SpeakerVolumeReq);
    IF_FAILED_ACTION_JUMP(
        ntStatus,
        DPF(D_ERROR, ("Init: WdfRequestCreate(Speaker-Volume) failed, 0x%x", ntStatus)),
        Done);

    // Init context.
    reqCtx = GetBthHfpDeviceNotificationReqContext(m_SpeakerVolumeReq);
    reqCtx->BthHfpDevice = this; // weak ref.
    
    ntStatus = WdfMemoryCreatePreallocated(
        WDF_NO_OBJECT_ATTRIBUTES,
        &reqCtx->Buffer,
        sizeof(reqCtx->Buffer.bImmediate),
        &reqCtx->MemIn);
    IF_FAILED_ACTION_JUMP(
        ntStatus,
        DPF(D_ERROR, ("Init: WdfMemoryCreatePreallocated failed, 0x%x", ntStatus)),
        Done);
    
    ntStatus = WdfMemoryCreatePreallocated(
        WDF_NO_OBJECT_ATTRIBUTES,
        &reqCtx->Buffer,
        sizeof(reqCtx->Buffer.Volume),
        &reqCtx->MemOut);
    IF_FAILED_ACTION_JUMP(
        ntStatus,
        DPF(D_ERROR, ("Init: WdfMemoryCreatePreallocated failed, 0x%x", ntStatus)),
        Done);
    
    //
    // IOCTL_BTHHFP_MIC_GET_VOLUME_STATUS_UPDATE
    //
    WDF_OBJECT_ATTRIBUTES_INIT_CONTEXT_TYPE(&attributes, BthHfpDeviceNotificationReqContext);
    attributes.ParentObject = m_Adapter->GetWdfDevice();
    ntStatus = WdfRequestCreate(
        &attributes,
        m_WdfIoTarget,
        &m_MicVolumeReq);
    IF_FAILED_ACTION_JUMP(
        ntStatus,
        DPF(D_ERROR, ("Init: WdfRequestCreate(Mic-Volume) failed, 0x%x", ntStatus)),
        Done);
    
    // Init context.
    reqCtx = GetBthHfpDeviceNotificationReqContext(m_MicVolumeReq);
    reqCtx->BthHfpDevice = this; // weak ref.
    
    ntStatus = WdfMemoryCreatePreallocated(
        WDF_NO_OBJECT_ATTRIBUTES,
        &reqCtx->Buffer,
        sizeof(reqCtx->Buffer.bImmediate),
        &reqCtx->MemIn);
    IF_FAILED_ACTION_JUMP(
        ntStatus,
        DPF(D_ERROR, ("Init: WdfMemoryCreatePreallocated failed, 0x%x", ntStatus)),
        Done);
    
    ntStatus = WdfMemoryCreatePreallocated(
        WDF_NO_OBJECT_ATTRIBUTES,
        &reqCtx->Buffer,
        sizeof(reqCtx->Buffer.Volume),
        &reqCtx->MemOut);
    IF_FAILED_ACTION_JUMP(
        ntStatus,
        DPF(D_ERROR, ("Init: WdfMemoryCreatePreallocated failed, 0x%x", ntStatus)),
        Done);
    
    //
    // IOCTL_BTHHFP_DEVICE_GET_CONNECTION_STATUS_UPDATE
    //
    WDF_OBJECT_ATTRIBUTES_INIT_CONTEXT_TYPE(&attributes, BthHfpDeviceNotificationReqContext);
    attributes.ParentObject = m_Adapter->GetWdfDevice();
    ntStatus = WdfRequestCreate(
        &attributes,
        m_WdfIoTarget,
        &m_ConnectionReq);
    IF_FAILED_ACTION_JUMP(
        ntStatus,
        DPF(D_ERROR, ("Init: WdfRequestCreate(Connection-Status) failed, 0x%x", ntStatus)),
        Done);
    
    // Init context.
    reqCtx = GetBthHfpDeviceNotificationReqContext(m_ConnectionReq);
    reqCtx->BthHfpDevice = this; // weak ref.
    
    ntStatus = WdfMemoryCreatePreallocated(
        WDF_NO_OBJECT_ATTRIBUTES,
        &reqCtx->Buffer,
        sizeof(reqCtx->Buffer.bImmediate),
        &reqCtx->MemIn);
    IF_FAILED_ACTION_JUMP(
        ntStatus,
        DPF(D_ERROR, ("Init: WdfMemoryCreatePreallocated failed, 0x%x", ntStatus)),
        Done);
    
    ntStatus = WdfMemoryCreatePreallocated(
        WDF_NO_OBJECT_ATTRIBUTES,
        &reqCtx->Buffer,
        sizeof(reqCtx->Buffer.BoolStatus),
        &reqCtx->MemOut);
    IF_FAILED_ACTION_JUMP(
        ntStatus,
        DPF(D_ERROR, ("Init: WdfMemoryCreatePreallocated failed, 0x%x", ntStatus)),
        Done);
        
    //
    // IOCTL_BTHHFP_STREAM_GET_STATUS_UPDATE
    //
    WDF_OBJECT_ATTRIBUTES_INIT_CONTEXT_TYPE(&attributes, BthHfpDeviceNotificationReqContext);
    attributes.ParentObject = m_Adapter->GetWdfDevice();
    ntStatus = WdfRequestCreate(
        &attributes,
        m_WdfIoTarget,
        &m_StreamReq);
    IF_FAILED_ACTION_JUMP(
        ntStatus,
        DPF(D_ERROR, ("Init: WdfRequestCreate(Stream-Status) failed, 0x%x", ntStatus)),
        Done);
    
    // Init context.
    reqCtx = GetBthHfpDeviceNotificationReqContext(m_StreamReq);
    reqCtx->BthHfpDevice = this; // weak ref.
    
    ntStatus = WdfMemoryCreatePreallocated(
        WDF_NO_OBJECT_ATTRIBUTES,
        &reqCtx->Buffer,
        sizeof(reqCtx->Buffer.bImmediate),
        &reqCtx->MemIn);
    IF_FAILED_ACTION_JUMP(
        ntStatus,
        DPF(D_ERROR, ("Init: WdfMemoryCreatePreallocated failed, 0x%x", ntStatus)),
        Done);
    
    ntStatus = WdfMemoryCreatePreallocated(
        WDF_NO_OBJECT_ATTRIBUTES,
        &reqCtx->Buffer,
        sizeof(reqCtx->Buffer.NtStatus),
        &reqCtx->MemOut);
    IF_FAILED_ACTION_JUMP(
        ntStatus,
        DPF(D_ERROR, ("Init: WdfMemoryCreatePreallocated failed, 0x%x", ntStatus)),
        Done);        
    
   //
   // This remote device is now in running state. No need to use interlock operations
   // b/c at this time this is the only thread accessing this info.
   //
   m_State = eBthHfpStateRunning;
   
   //
   // Init successful.
   //
   ntStatus = STATUS_SUCCESS;

Done:
    return ntStatus;
}

//=============================================================================
#pragma code_seg("PAGE")
BthHfpDevice::~BthHfpDevice
( 
    void 
)
/*++

Routine Description:

  Destructor for BthHfpDevice.

Arguments:

Return Value:

  void

--*/
{
    PAGED_CODE();
    DPF_ENTER(("[BthHfpDevice::~BthHfpDevice]"));

    ASSERT(m_State != eBthHfpStateRunning);
    ASSERT(IsListEmpty(&m_ListEntry));
    
    //
    // Release ref to remote stack.
    //
    if (m_WdfIoTarget != NULL)
    {
        WdfObjectDelete(m_WdfIoTarget);
        m_WdfIoTarget = NULL;
    }

    //
    // Free symbolic links.
    //
    if (m_SymbolicLinkName.Buffer != NULL)
    {
        ExFreePoolWithTag(m_SymbolicLinkName.Buffer, MINADAPTER_POOLTAG);
        RtlZeroMemory(&m_SymbolicLinkName, sizeof(m_SymbolicLinkName));
    }

    if (m_SpeakerAudioSymbolicLinkName.Buffer != NULL)
    {
        RtlFreeUnicodeString(&m_SpeakerAudioSymbolicLinkName); // yes, this must use RtlFreeUnicodeString.
        RtlZeroMemory(&m_SpeakerAudioSymbolicLinkName, sizeof(m_SpeakerAudioSymbolicLinkName));
    }
    
    if (m_MicAudioSymbolicLinkName.Buffer != NULL)
    {
        RtlFreeUnicodeString(&m_MicAudioSymbolicLinkName); // yes, this must use RtlFreeUnicodeString.
        RtlZeroMemory(&m_MicAudioSymbolicLinkName, sizeof(m_MicAudioSymbolicLinkName));
    }
    
    if (m_Descriptor != NULL)
    {
        ExFreePoolWithTag(m_Descriptor, MINADAPTER_POOLTAG);
        m_Descriptor = NULL;
    }

    if (m_VolumePropValues != NULL)
    {
        ExFreePoolWithTag(m_VolumePropValues, MINADAPTER_POOLTAG);
        m_VolumePropValues = NULL;
    }

    //
    // Free Irps.
    //
    if (m_SpeakerVolumeReq != NULL)
    {
        BthHfpDeviceNotificationReqContext * ctx;

        // Delete the associated memory objects.
        ctx = GetBthHfpDeviceNotificationReqContext(m_SpeakerVolumeReq);
        if (ctx->MemIn != NULL)
        {
            WdfObjectDelete(ctx->MemIn);
            ctx->MemIn = NULL;
        }
        
        if (ctx->MemOut != NULL)
        {
            WdfObjectDelete(ctx->MemOut);
            ctx->MemOut = NULL;
        }

        // Delete the request.
        WdfObjectDelete(m_SpeakerVolumeReq);
        m_SpeakerVolumeReq = NULL;
    }
    
    if (m_MicVolumeReq != NULL)
    {
        BthHfpDeviceNotificationReqContext * ctx;

        // Delete the associated memory objects.
        ctx = GetBthHfpDeviceNotificationReqContext(m_MicVolumeReq);
        if (ctx->MemIn != NULL)
        {
            WdfObjectDelete(ctx->MemIn);
            ctx->MemIn = NULL;
        }
        
        if (ctx->MemOut != NULL)
        {
            WdfObjectDelete(ctx->MemOut);
            ctx->MemOut = NULL;
        }

        // Delete the request.
        WdfObjectDelete(m_MicVolumeReq);
        m_MicVolumeReq = NULL;
    }
    
    if (m_ConnectionReq != NULL)
    {
        BthHfpDeviceNotificationReqContext * ctx;

        // Delete the associated memory objects.
        ctx = GetBthHfpDeviceNotificationReqContext(m_ConnectionReq);
        if (ctx->MemIn != NULL)
        {
            WdfObjectDelete(ctx->MemIn);
            ctx->MemIn = NULL;
        }
        
        if (ctx->MemOut != NULL)
        {
            WdfObjectDelete(ctx->MemOut);
            ctx->MemOut = NULL;
        }

        // Delete the request.
        WdfObjectDelete(m_ConnectionReq);
        m_ConnectionReq = NULL;
    }
    
    if (m_StreamReq != NULL)
    {
        BthHfpDeviceNotificationReqContext * ctx;

        // Delete the associated memory objects.
        ctx = GetBthHfpDeviceNotificationReqContext(m_StreamReq);
        if (ctx->MemIn != NULL)
        {
            WdfObjectDelete(ctx->MemIn);
            ctx->MemIn = NULL;
        }
        
        if (ctx->MemOut != NULL)
        {
            WdfObjectDelete(ctx->MemOut);
            ctx->MemOut = NULL;
        }

        // Delete the request.
        WdfObjectDelete(m_StreamReq);
        m_StreamReq = NULL;
    }

    //
    // Notification work-item.
    //
    if (m_WorkItem != NULL)
    {
        WdfObjectDelete(m_WorkItem);
        m_WorkItem = NULL;
    }

    //
    // Notification req. collection.
    //
    if (m_ReqCollection != NULL)
    {
        WdfObjectDelete(m_ReqCollection);
        m_ReqCollection = NULL;
    }

    ASSERT(m_UnknownSpeakerTopology == NULL);
    SAFE_RELEASE(m_UnknownSpeakerTopology);
    
    ASSERT(m_UnknownSpeakerWave == NULL);
    SAFE_RELEASE(m_UnknownSpeakerWave);
    
    ASSERT(m_UnknownMicTopology == NULL);
    SAFE_RELEASE(m_UnknownMicTopology);
    
    ASSERT(m_UnknownMicWave == NULL);
    SAFE_RELEASE(m_UnknownMicWave);

    ASSERT(m_nStreams == 0);

    ASSERT(m_SpeakerVolumeCallback.Handler == NULL);
    ASSERT(m_SpeakerConnectionStatusCallback.Handler == NULL);
    ASSERT(m_MicVolumeCallback.Handler == NULL);
    ASSERT(m_MicConnectionStatusCallback.Handler == NULL);
} // ~CAdapterCommon  

//
// IBthHfpDeviceCommon implementation. 
//

//=============================================================================
#pragma code_seg("PAGE")
BOOL
BthHfpDevice::IsVolumeSupported()
{
    PAGED_CODE();
    DPF_ENTER(("[BthHfpDevice::IsVolumeSupported]"));
    
    return m_Descriptor->SupportsVolume;
}

//=============================================================================
#pragma code_seg("PAGE")
PKSPROPERTY_VALUES
BthHfpDevice::GetVolumeSettings
(
    _Out_ PULONG    Size 
)
{
    PAGED_CODE();
    DPF_ENTER(("[BthHfpDevice::GetVolumeSettings]"));

    ASSERT(Size != NULL);
    
    *Size = m_Descriptor->VolumePropertyValuesSize;
    
    return m_VolumePropValues;
}

//=============================================================================
#pragma code_seg("PAGE")
LONG
BthHfpDevice::GetSpeakerVolume()
{
    PAGED_CODE();
    DPF_ENTER(("[BthHfpDevice::GetSpeakerVolume]"));
    
    return InterlockedCompareExchange(&m_SpeakerVolumeLevel, 0, 0);
}

//=============================================================================
#pragma code_seg("PAGE")
NTSTATUS
BthHfpDevice::SetSpeakerVolume
(
    _In_ ULONG      Volume
)
{
    PAGED_CODE();
    DPF_ENTER(("[BthHfpDevice::SetSpeakerVolume]"));
    
    return SetBthHfpSpeakerVolume(Volume);
}

//=============================================================================
#pragma code_seg("PAGE")
LONG
BthHfpDevice::GetMicVolume()
{
    PAGED_CODE();
    DPF_ENTER(("[BthHfpDevice::GetMicVolume]"));
    
    return InterlockedCompareExchange(&m_MicVolumeLevel, 0, 0);
}

//=============================================================================
#pragma code_seg("PAGE")
NTSTATUS
BthHfpDevice::SetMicVolume
(
    _In_ ULONG      Volume
)
{
    PAGED_CODE();
    DPF_ENTER(("[BthHfpDevice::SetMicVolume]"));
    
    return SetBthHfpMicVolume(Volume);
}

//=============================================================================
#pragma code_seg("PAGE")
BOOL
BthHfpDevice::GetConnectionStatus()
{
    PAGED_CODE();
    DPF_ENTER(("[BthHfpDevice::GetConnectionStatus]"));
    
    return (BOOL)InterlockedCompareExchange(&m_ConnectionStatusLong, 0, 0);
}

//=============================================================================
#pragma code_seg("PAGE")
NTSTATUS
BthHfpDevice::Connect()
{
    PAGED_CODE();
    DPF_ENTER(("[BthHfpDevice::Connect]"));
    
    return SetBthHfpConnect();
}

//=============================================================================
#pragma code_seg("PAGE")
NTSTATUS
BthHfpDevice::Disconnect()
{
    PAGED_CODE();
    DPF_ENTER(("[BthHfpDevice::Disconnect]"));
    
    return SetBthHfpDisconnect();
}

//=============================================================================
#pragma code_seg()
BOOL
BthHfpDevice::GetStreamStatus()
{
    DPF_ENTER(("[BthHfpDevice::GetStreamStatus]"));

    NTSTATUS ntStatus;

    ntStatus = (NTSTATUS)InterlockedCompareExchange(&m_StreamStatusLong, 0, 0);

    return NT_SUCCESS(ntStatus) ? TRUE : FALSE;
}

//=============================================================================
#pragma code_seg("PAGE")
NTSTATUS
BthHfpDevice::StreamOpen()
{
    PAGED_CODE();
    DPF_ENTER(("[BthHfpDevice::StreamOpen]"));

    NTSTATUS    ntStatus    = STATUS_SUCCESS;
    LONG        nStreams    = 0;

    ASSERT(m_nStreams >= 0);
    
    nStreams = InterlockedIncrement(&m_nStreams);
    if (nStreams == 1)
    {
        BOOLEAN  streamOpen = FALSE;
        
        ntStatus = SetBthHfpStreamOpen();
        if (NT_SUCCESS(ntStatus))
        {
            streamOpen = TRUE;
            m_StreamStatus = STATUS_SUCCESS;
            ntStatus = EnableBthHfpStreamStatusNotification();
        }

        //
        // Cleanup if any error.
        //
        if (!NT_SUCCESS(ntStatus))
        {
            nStreams = InterlockedDecrement(&m_nStreams);
            ASSERT(nStreams == 0);
            UNREFERENCED_VAR(nStreams);

            if (streamOpen)
            {
                SetBthHfpStreamClose();
            }

            m_StreamStatus = STATUS_INVALID_DEVICE_STATE;
        }
    }

    return ntStatus;
}

//=============================================================================
#pragma code_seg("PAGE")
NTSTATUS
BthHfpDevice::StreamClose()
{
    PAGED_CODE();
    DPF_ENTER(("[BthHfpDevice::StreamClose]"));
    
    NTSTATUS    ntStatus    = STATUS_SUCCESS;
    LONG        nStreams    = 0;

    ASSERT(m_nStreams > 0);
    
    nStreams = InterlockedDecrement(&m_nStreams);
    if (nStreams == 0)
    {
        ntStatus = SetBthHfpStreamClose();
        
        StopBthHfpStreamStatusNotification();
        
        m_StreamStatus = STATUS_INVALID_DEVICE_STATE;
    }

    return ntStatus;
}

//=============================================================================
#pragma code_seg("PAGE")
GUID
BthHfpDevice::GetContainerId()
{
    PAGED_CODE();
    DPF_ENTER(("[BthHfpDevice::GetContainerId]"));
    
    return m_Descriptor->ContainerId;
}

//=============================================================================
#pragma code_seg("PAGE")
VOID
BthHfpDevice::SetSpeakerVolumeHandler
(
    _In_opt_    PFNEVENTNOTIFICATION    EventHandler,
    _In_opt_    PVOID                   EventHandlerContext
)
    {
        PAGED_CODE();
        DPF_ENTER(("[BthHfpDevice::SetSpeakerVolumeHandler]"));

        ASSERT(EventHandler == NULL || m_SpeakerVolumeCallback.Handler == NULL);
        
        m_SpeakerVolumeCallback.Handler = EventHandler; // weak ref.
        m_SpeakerVolumeCallback.Context = EventHandlerContext;
    }

//=============================================================================
#pragma code_seg("PAGE")
VOID
BthHfpDevice::SetSpeakerConnectionStatusHandler
(
    _In_opt_    PFNEVENTNOTIFICATION    EventHandler,
    _In_opt_    PVOID                   EventHandlerContext
)
{
    PAGED_CODE();
    DPF_ENTER(("[BthHfpDevice::SetSpeakerConnectionStatusHandler]"));
    
    ASSERT(EventHandler == NULL || m_SpeakerConnectionStatusCallback.Handler == NULL);
    
    m_SpeakerConnectionStatusCallback.Handler = EventHandler; // weak ref.
    m_SpeakerConnectionStatusCallback.Context = EventHandlerContext;
}

//=============================================================================
#pragma code_seg("PAGE")
VOID
BthHfpDevice::SetMicVolumeHandler
(
    _In_opt_    PFNEVENTNOTIFICATION    EventHandler,
    _In_opt_    PVOID                   EventHandlerContext
)
{
    PAGED_CODE();
    DPF_ENTER(("[BthHfpDevice::SetMicVolumeHandler]"));
    
    ASSERT(EventHandler == NULL || m_MicVolumeCallback.Handler == NULL);
    
    m_MicVolumeCallback.Handler = EventHandler; // weak ref.
    m_MicVolumeCallback.Context = EventHandlerContext;
}

//=============================================================================
#pragma code_seg("PAGE")
VOID
BthHfpDevice::SetMicConnectionStatusHandler
(
    _In_opt_    PFNEVENTNOTIFICATION    EventHandler,
    _In_opt_    PVOID                   EventHandlerContext
)
{
    PAGED_CODE();
    DPF_ENTER(("[BthHfpDevice::SetMicConnectionStatusHandler]"));
    
    ASSERT(EventHandler == NULL || m_MicConnectionStatusCallback.Handler == NULL);
    
    m_MicConnectionStatusCallback.Handler = EventHandler; // weak ref.
    m_MicConnectionStatusCallback.Context = EventHandlerContext;
}

//=============================================================================
#pragma code_seg("PAGE")
BOOL
BthHfpDevice::IsNRECSupported()
{
    PAGED_CODE();
    DPF_ENTER(("[BthHfpDevice::IsNRECSupported]"));
    
    return m_Descriptor->SupportsNREC;
}

//=============================================================================
#pragma code_seg("PAGE")
BOOL
BthHfpDevice::GetNRECDisableStatus()
{
    PAGED_CODE();
    DPF_ENTER(("[BthHfpDevice::GetNRECDisableStatus]"));

    // Return TRUE if HF wants to disable the NREC on the AG.
    return (BOOL)InterlockedCompareExchange(&m_NRECDisableStatusLong, 0, 0);
}

//
// Helper functions.
//

//=============================================================================
#pragma code_seg("PAGE")
NTSTATUS
BthHfpDevice::CreateAudioInterface
(
    _Inout_ PWSTR               TopologyName,
    _Out_   PUNICODE_STRING     AudioSymbolicLinkName
)
/*++

Routine Description:

  Create the audio interface (in disabled mode).

--*/
{
    PAGED_CODE();
    DPF_ENTER(("[BthHfpDevice::CreateAudioInterface]"));
    
    NTSTATUS        ntStatus    = STATUS_SUCCESS;
    UNICODE_STRING  topoName;

    RtlInitUnicodeString(&topoName, TopologyName);

    //
    // Reset output value.
    //
    RtlInitUnicodeString(AudioSymbolicLinkName, NULL);

    //
    // Register an audio interface if not already present.
    //
    ntStatus = IoRegisterDeviceInterface(
        m_Adapter->GetPhysicalDeviceObject(),
        &KSCATEGORY_AUDIO,
        &topoName,
        AudioSymbolicLinkName);
    IF_FAILED_ACTION_JUMP(
        ntStatus,
        DPF(D_ERROR, ("CreateAudioInterface: IoRegisterDeviceInterface(KSCATEGORY_AUDIO): failed, 0x%x", ntStatus)),
        Done);

    //
    // Add the friendly name to the interface.
    //
    ntStatus = IoSetDeviceInterfacePropertyData(
        AudioSymbolicLinkName,
        &DEVPKEY_DeviceInterface_FriendlyName,
        LOCALE_NEUTRAL,
        PLUGPLAY_PROPERTY_PERSISTENT,
        DEVPROP_TYPE_STRING_INDIRECT,
        m_Descriptor->FriendlyName.Length + sizeof(UNICODE_NULL),
        m_Descriptor->FriendlyName.Buffer);
    IF_FAILED_ACTION_JUMP(
        ntStatus,
        DPF(D_ERROR, ("CreateAudioInterface: IoSetDeviceInterfacePropertyData(DEVPKEY_DeviceInterface_FriendlyName): failed, 0x%x", ntStatus)),
        Done);

    //
    // All done.
    //
    ntStatus = STATUS_SUCCESS;
    
Done:
    return ntStatus;
}

//=============================================================================
#pragma code_seg()
NTSTATUS 
BthHfpDevice::SendIoCtrlAsynchronously
(
    _In_        WDFREQUEST      Request,
    _In_        ULONG           IoControlCode,
    _In_opt_    WDFMEMORY       MemIn,
    _In_opt_    WDFMEMORY       MemOut,
    _In_        PFN_WDF_REQUEST_COMPLETION_ROUTINE CompletionRoutine,
    _In_        WDFCONTEXT      Context
)
/*++

Routine Description:

  This function aynchronously sends an I/O Ctrl request to the BTH HFP
  SCO Bypass device.

--*/
{
    DPF_ENTER(("[BthHfpDevice::SendIoCtrlAsynchronously]"));
    
    NTSTATUS    ntStatus    = STATUS_SUCCESS;
    BOOLEAN     fSent       = FALSE;

    //
    // Format and send the request.
    //
    ntStatus = WdfIoTargetFormatRequestForIoctl(
        m_WdfIoTarget,
        Request,
        IoControlCode,
        MemIn,
        NULL,
        MemOut,
        NULL);

    IF_FAILED_ACTION_JUMP(
        ntStatus,
        DPF(D_ERROR, ("SendIoCtrlAsynchronously: WdfIoTargetFormatRequestForIoctl(0x%x) failed, 0x%x", IoControlCode, ntStatus)),
        Done);

    WdfRequestSetCompletionRoutine(
        Request,
        CompletionRoutine,
        Context);

    fSent = WdfRequestSend(Request, m_WdfIoTarget, NULL);  // no options.
    if (fSent == FALSE)
    {
        ntStatus = WdfRequestGetStatus(Request);
        if (NT_SUCCESS(ntStatus))
        {
            ntStatus = STATUS_INVALID_DEVICE_STATE;
        }
        
        DPF(D_ERROR, ("SendIoCtrlAsynchronously: WdfRequestSend(0x%x) failed, 0x%x", IoControlCode, ntStatus));
        goto Done;
    }

    //
    // All Done.
    //
    ntStatus = STATUS_SUCCESS;

Done:

    return ntStatus;
}

//=============================================================================
#pragma code_seg("PAGE")
NTSTATUS 
BthHfpDevice::SendIoCtrlSynchronously
(
    _In_opt_    WDFREQUEST  Request,
    _In_        ULONG       IoControlCode,
    _In_        ULONG       InLength,
    _In_        ULONG       OutLength,
    _When_(InLength > 0 || OutLength > 0, _In_)
    _When_(InLength == 0 && OutLength == 0, _In_opt_)
                PVOID       Buffer
)
/*++

Routine Description:

  This function inits and synchronously sends an I/O Ctrl request to the BTH HFP
  SCO Bypass device.

--*/
{
    PAGED_CODE();
    DPF_ENTER(("[BthHfpDevice::SendIoCtrlSynchronously]"));
    
    NTSTATUS                    ntStatus    = STATUS_SUCCESS;
    PWDF_MEMORY_DESCRIPTOR      memInPtr    = NULL;
    PWDF_MEMORY_DESCRIPTOR      memOutPtr   = NULL;
    WDF_MEMORY_DESCRIPTOR       memIn;
    WDF_MEMORY_DESCRIPTOR       memOut;
    WDF_REQUEST_SEND_OPTIONS    reqOpts;

    //
    // Format and send the request.
    //
    if (InLength)
    {
        WDF_MEMORY_DESCRIPTOR_INIT_BUFFER(&memIn,  Buffer, InLength);
        memInPtr = &memIn;
    }

    if (OutLength)
    {
        WDF_MEMORY_DESCRIPTOR_INIT_BUFFER(&memOut, Buffer, OutLength);
        memOutPtr = &memOut;
    }
    
    WDF_REQUEST_SEND_OPTIONS_INIT(
        &reqOpts, 
        WDF_REQUEST_SEND_OPTION_TIMEOUT | 
         WDF_REQUEST_SEND_OPTION_SYNCHRONOUS);

    reqOpts.Timeout = WDF_REL_TIMEOUT_IN_SEC(BTH_HFP_SYNC_REQ_TIMEOUT_IN_SEC);
        
    ntStatus = WdfIoTargetSendIoctlSynchronously(
        m_WdfIoTarget,
        Request,
        IoControlCode,
        memInPtr,
        memOutPtr,
        &reqOpts,
        NULL);      // bytes returned.
    
    IF_FAILED_ACTION_JUMP(
        ntStatus,
        DPF(D_VERBOSE, ("SendIoCtrlSynchronously: WdfIoTargetSendIoctlSynchronously(0x%x) failed, 0x%x", IoControlCode, ntStatus)),
        Done);

Done:

    return ntStatus;
}

//=============================================================================
#pragma code_seg()
VOID
BthHfpDevice::EvtBthHfpDeviceNotificationStatusWorkItem
(
    _In_    WDFWORKITEM WorkItem
)
/*++

Routine Description:

  The function processes status notification updates.

Arguments:

  WorkItem    - WDF work-item object.
  
--*/
{
    DPF_ENTER(("[BthHfpDevice::EvtBthHfpDeviceNotificationStatusWorkItem]"));

    NTSTATUS            ntStatus = STATUS_SUCCESS;
    BthHfpDevice      * This;
    KIRQL               oldIrql;
    
    if (WorkItem == NULL) 
    {
        return;
    }

    This = GetBthHfpDeviceNotificationWorkItemContext(WorkItem)->BthHfpDevice;
    ASSERT(This != NULL);

    for (;;)
    {
        BOOL                                    resend  = TRUE; 
        WDFREQUEST                              req     = NULL;
        BthHfpDeviceNotificationReqContext    * reqCtx;
        WDF_REQUEST_COMPLETION_PARAMS           params;
            
        //
        // Retrieve a task.
        //
        KeAcquireSpinLock(&This->m_Lock, &oldIrql);
        req = (WDFREQUEST) WdfCollectionGetFirstItem(This->m_ReqCollection);
        if (req != NULL)
        {
            WdfCollectionRemove(This->m_ReqCollection, req);
        }
        KeReleaseSpinLock(&This->m_Lock, oldIrql);

        if (req == NULL)
        {
            break;
        }

        //
        // Get request parameters and context.
        //
        WDF_REQUEST_COMPLETION_PARAMS_INIT(&params);
        WdfRequestGetCompletionParams(req, &params);
        
        reqCtx = GetBthHfpDeviceNotificationReqContext(req);
        ASSERT(reqCtx != NULL);
        
        //
        // Handle this notification.
        //
        if (NT_SUCCESS(params.IoStatus.Status))
        {
            switch(params.Parameters.Ioctl.IoControlCode)
            {
            case IOCTL_BTHHFP_DEVICE_GET_NRECDISABLE_STATUS_UPDATE:
                {
                    InterlockedExchange(&This->m_NRECDisableStatusLong, (LONG)reqCtx->Buffer.BoolStatus);
                }
                break;
                
            case IOCTL_BTHHFP_SPEAKER_GET_VOLUME_STATUS_UPDATE:
                {
                    LONG oldVolume;

                    oldVolume = InterlockedExchange(&This->m_SpeakerVolumeLevel, reqCtx->Buffer.Volume);
                    if (reqCtx->Buffer.Volume != oldVolume)
                    {
                        // Notify audio miniport about this change.
                        if (This->m_SpeakerVolumeCallback.Handler != NULL)
                        {
                            This->m_SpeakerVolumeCallback.Handler(
                                This->m_SpeakerVolumeCallback.Context);
                        }
                    }
                }
                break;
                    
            case IOCTL_BTHHFP_MIC_GET_VOLUME_STATUS_UPDATE:
                {
                    LONG oldVolume;

                    oldVolume = InterlockedExchange(&This->m_MicVolumeLevel, reqCtx->Buffer.Volume);
                    if (reqCtx->Buffer.Volume != oldVolume)
                    {
                        // Notify audio miniport about this change.
                        if (This->m_MicVolumeCallback.Handler != NULL)
                        {
                            This->m_MicVolumeCallback.Handler(
                                This->m_MicVolumeCallback.Context);
                        }
                    }
                }
                break;

            case IOCTL_BTHHFP_DEVICE_GET_CONNECTION_STATUS_UPDATE:
                {
                    BOOL oldStatus;

                    oldStatus = (BOOL)InterlockedExchange(&This->m_ConnectionStatusLong, (LONG)reqCtx->Buffer.BoolStatus);
                    if (reqCtx->Buffer.BoolStatus != oldStatus)
                    {
                        // Notify audio miniport about this change.
                        if (This->m_SpeakerConnectionStatusCallback.Handler != NULL)
                        {
                            This->m_SpeakerConnectionStatusCallback.Handler(
                                This->m_SpeakerConnectionStatusCallback.Context);
                        }
                        
                        if (This->m_MicConnectionStatusCallback.Handler != NULL)
                        {
                            This->m_MicConnectionStatusCallback.Handler(
                                This->m_MicConnectionStatusCallback.Context);
                        }
                    }
                }
                break;

            default:
                // This should never happen.
                resend = FALSE;
                DPF(D_ERROR, ("EvtBthHfpDeviceNotificationStatusWorkItem: invalid request ctrl 0x%x", 
                    params.Parameters.Ioctl.IoControlCode));
                break;
            }
        }

        if (resend)
        {
            WDF_REQUEST_REUSE_PARAMS    reuseParams;
            
            WDF_REQUEST_REUSE_PARAMS_INIT(
                &reuseParams,   
                WDF_REQUEST_REUSE_NO_FLAGS,
                STATUS_SUCCESS);
            
            ntStatus = WdfRequestReuse(req, &reuseParams);
            if (!NT_SUCCESS(ntStatus))
            {
                DPF(D_ERROR, ("GetBthHfpDescriptor: WdfRequestReuse failed, 0x%x", ntStatus));
                break;
            }
        
            // Resend status notification request.
            reqCtx->Buffer.bImmediate = FALSE;

            ntStatus = This->SendIoCtrlAsynchronously(
                req,
                params.Parameters.Ioctl.IoControlCode,
                reqCtx->MemIn,
                reqCtx->MemOut,
                EvtBthHfpDeviceNotificationStatusCompletion,
                This);
            
            if (!NT_SUCCESS(ntStatus))
            {
                DPF(D_ERROR, ("EvtBthHfpDeviceNotificationStatusWorkItem: SendIoCtrlAsynchronously"
                              "(0x%x) failed, 0x%x", 
                              params.Parameters.Ioctl.IoControlCode, ntStatus));
                break;
            }
        }
    }
}

//=============================================================================
#pragma code_seg()
void 
BthHfpDevice::EvtBthHfpDeviceNotificationStatusCompletion
(
  _In_  WDFREQUEST Request,
  _In_  WDFIOTARGET Target,
  _In_  PWDF_REQUEST_COMPLETION_PARAMS Params,
  _In_  WDFCONTEXT Context
)
{
    DPF_ENTER(("[BthHfpDevice::EvtBthHfpDeviceNotificationStatusCompletion]"));
   
    NTSTATUS                                ntStatus    = STATUS_SUCCESS;
    BthHfpDeviceNotificationReqContext    * ctx         = NULL;
    BthHfpDevice                          * This        = NULL;
    KIRQL                                   oldIrql;
    
    UNREFERENCED_PARAMETER(Target);
    UNREFERENCED_PARAMETER(Context);
    
    ctx = GetBthHfpDeviceNotificationReqContext(Request);
    This = ctx->BthHfpDevice;
    ASSERT(This != NULL);

    ntStatus = Params->IoStatus.Status;
    if (ntStatus == STATUS_CANCELLED)
    {
        // BTH HFP device is shutting down. Do not re-send this request.
        goto Done;
    }

    //
    // If something is wrong with the HFP interface, do not loop forever.
    //
    if (!NT_SUCCESS(ntStatus))
    {
        if (++ctx->Errors > BTH_HFP_NOTIFICATION_MAX_ERROR_COUNT)
        {
            // Too many errors. Do not re-send this request.
            goto Done;
        }
    }
    else
    {
        // reset the # of errors.
        ctx->Errors = 0;
    }

    // 
    // Let the work-item thread process this request.
    //
    KeAcquireSpinLock(&This->m_Lock, &oldIrql);

    ntStatus = WdfCollectionAdd(This->m_ReqCollection, Request);
    if (NT_SUCCESS(ntStatus))
    {
        WdfWorkItemEnqueue(This->m_WorkItem);
    }

    KeReleaseSpinLock(&This->m_Lock, oldIrql);

Done:;
}

//=============================================================================
#pragma code_seg("PAGE")
NTSTATUS 
BthHfpDevice::GetBthHfpDescriptor
(
    _Out_ PBTHHFP_DESCRIPTOR2 * Descriptor
)
/*++

Routine Description:

  This function synchronously gets the remote Bluetooth Hands-Free Profile SCO
  Bypass descriptor.

--*/
{
    PAGED_CODE();
    DPF_ENTER(("[BthHfpDevice::GetBthHfpDescriptor]"));
    
    NTSTATUS                    ntStatus    = STATUS_SUCCESS;
    WDFREQUEST                  req         = NULL;
    PBTHHFP_DESCRIPTOR2         descriptor  = NULL;
    ULONG                       length      = 0;
    ULONG_PTR                   information = 0;
    WDF_REQUEST_REUSE_PARAMS    reuseParams;   
    WDF_OBJECT_ATTRIBUTES       attributes;
    
    *Descriptor = NULL;

    //
    // Allocate and format a WDF request.
    //
    WDF_OBJECT_ATTRIBUTES_INIT(&attributes);
    attributes.ParentObject = m_Adapter->GetWdfDevice();
    ntStatus = WdfRequestCreate(
        &attributes,
        m_WdfIoTarget,
        &req);
    IF_FAILED_ACTION_JUMP(
        ntStatus,
        DPF(D_ERROR, ("GetBthHfpDescriptor: WdfRequestCreate failed, 0x%x", ntStatus)),
        Done);

    //
    // Get the size of the buffer.
    //
    ntStatus = SendIoCtrlSynchronously(
        req,
        IOCTL_BTHHFP_DEVICE_GET_DESCRIPTOR2,
        NULL,
        NULL,
        NULL);
        
    if (ntStatus != STATUS_BUFFER_TOO_SMALL)
    {
        if (NT_SUCCESS(ntStatus))
        {
            ntStatus = STATUS_INVALID_DEVICE_STATE;
        }
        
        DPF(D_ERROR, ("GetBthHfpDescriptor: SendIoCtrlSynchronously(IOCTL_BTHHFP_DEVICE_GET_DESCRIPTOR): failed, 0x%x", ntStatus));
        goto Done;
    }
    
    ntStatus = STATUS_SUCCESS;

    information = WdfRequestGetInformation(req);
    if (information == 0 || information > ULONG_MAX)
    {
        ntStatus = STATUS_INVALID_DEVICE_STATE;        
        DPF(D_ERROR, ("GetBthHfpDescriptor: IOCTL_BTHHFP_DEVICE_GET_DESCRIPTOR buffer too big (%Id): 0x%x", information, ntStatus));
        goto Done;
    }

    length = (ULONG)information;
        
    //
    // Allocate memory needed to hold the info.
    //
    descriptor  = (PBTHHFP_DESCRIPTOR2) ExAllocatePoolWithTag(NonPagedPoolNx, length, MINADAPTER_POOLTAG);
    if (descriptor == NULL)
    {
        ntStatus = STATUS_INSUFFICIENT_RESOURCES;
    }
    IF_FAILED_ACTION_JUMP(
        ntStatus,
        DPF(D_ERROR, ("GetBthHfpDescriptor: ExAllocatePoolWithTag failed, out of memory")),
        Done);

    //
    // Get the Bth HFP SCO Bypass descriptor.
    //
    WDF_REQUEST_REUSE_PARAMS_INIT(
        &reuseParams,   
        WDF_REQUEST_REUSE_NO_FLAGS,
        STATUS_SUCCESS);
    
    ntStatus = WdfRequestReuse(req, &reuseParams);
    IF_FAILED_ACTION_JUMP(
        ntStatus,
        DPF(D_ERROR, ("GetBthHfpDescriptor: WdfRequestReuse failed, 0x%x", ntStatus)),
        Done);
    
    ntStatus = SendIoCtrlSynchronously(
        req,
        IOCTL_BTHHFP_DEVICE_GET_DESCRIPTOR,
        NULL,
        length,
        descriptor);
    IF_FAILED_ACTION_JUMP(
        ntStatus,
        DPF(D_ERROR, ("GetBthHfpDescriptor: SendIoCtrlSynchronously(IOCTL_BTHHFP_DEVICE_GET_DESCRIPTOR) failed, 0x%x", ntStatus)),
        Done);

    //
    // All done.
    //
    *Descriptor = descriptor;
    ntStatus = STATUS_SUCCESS;
    
Done:
    if (!NT_SUCCESS(ntStatus))
    {
        if (descriptor != NULL)
        {
            ExFreePoolWithTag(descriptor, MINADAPTER_POOLTAG);
        }   
    }

    if (req != NULL)
    {
        WdfObjectDelete(req);
        req = NULL;
    }
    
    return ntStatus;
}

//=============================================================================
#pragma code_seg()
NTSTATUS 
BthHfpDevice::EnableBthHfpNrecDisableStatusNotification()
/*++

Routine Description:

  This function registers for Bluetooth Hands-Free Profile SCO Bypass NREC-Disable 
  status change notification. 

--*/
{
    DPF_ENTER(("[BthHfpDevice::EnableBthHfpNrecDisableStatusNotification]"));
    
    NTSTATUS                                ntStatus    = STATUS_SUCCESS;
    BthHfpDeviceNotificationReqContext    * ctx         = NULL;

    ctx = GetBthHfpDeviceNotificationReqContext(m_NRECDisableStatusReq);

    //
    // This is a notification request.
    //
    ctx->Buffer.bImmediate = FALSE; 
    
    //
    // Get the Bth HFP SCO Bypass NREC-Disable status (async).
    //
    ntStatus = SendIoCtrlAsynchronously(
        m_NRECDisableStatusReq,
        IOCTL_BTHHFP_DEVICE_GET_NRECDISABLE_STATUS_UPDATE,
        ctx->MemIn,
        ctx->MemOut,
        EvtBthHfpDeviceNotificationStatusCompletion,
        this);
    
    IF_FAILED_ACTION_JUMP(
        ntStatus,
        DPF(D_ERROR, ("EnableBthHfpNrecDisableStatusNotification: SendIoCtrlAsynchronously(IOCTL_BTHHFP_DEVICE_GET_NRECDISABLE_STATUS_UPDATE) failed, 0x%x", ntStatus)),
        Done);

    //
    // All done.
    //
    ntStatus = STATUS_SUCCESS;
    
Done:

    return ntStatus;
}

//=============================================================================
#pragma code_seg("PAGE")
NTSTATUS 
BthHfpDevice::GetBthHfpVolumePropertyValues
(
    _In_  ULONG                 Length,
    _Out_ PKSPROPERTY_VALUES  * PropValues
)
/*++

Routine Description:

  This function synchronously gets the remote Bluetooth Hands-Free Profile SCO
  Bypass volume values.

--*/
{
    PAGED_CODE();
    DPF_ENTER(("[BthHfpDevice::GetBthHfpVolumePropertyValues]"));
    
    NTSTATUS            ntStatus    = STATUS_SUCCESS;
    PKSPROPERTY_VALUES  propValues  = NULL;
    
    *PropValues = NULL;

    //
    // Allocate memory.
    //
    propValues  = (PKSPROPERTY_VALUES) ExAllocatePoolWithTag(NonPagedPoolNx, Length, MINADAPTER_POOLTAG);
    if (propValues == NULL)
    {
        ntStatus = STATUS_INSUFFICIENT_RESOURCES;
    }
    IF_FAILED_ACTION_JUMP(
        ntStatus,
        DPF(D_ERROR, ("GetBthHfpVolumePropertyValues: ExAllocatePoolWithTag failed, out of memory")),
        Done);

    //
    // Get the Bth HFP SCO Bypass descriptor.
    //
    ntStatus = SendIoCtrlSynchronously(
        NULL,
        IOCTL_BTHHFP_DEVICE_GET_VOLUMEPROPERTYVALUES, 
        0, 
        Length, 
        propValues);
    
    IF_FAILED_ACTION_JUMP(
        ntStatus,
        DPF(D_ERROR, ("GetBthHfpVolumePropertyValues: SendIoCtrlSynchronously(IOCTL_BTHHFP_DEVICE_GET_VOLUMEPROPERTYVALUES) failed, 0x%x", ntStatus)),
        Done);

    //
    // All done.
    //
    *PropValues = propValues;
    ntStatus = STATUS_SUCCESS;
    
Done:
    if (!NT_SUCCESS(ntStatus))
    {
        if (propValues != NULL)
        {
            ExFreePoolWithTag(propValues, MINADAPTER_POOLTAG);
        }   
    }
    
    return ntStatus;
}

//=============================================================================
#pragma code_seg("PAGE")
NTSTATUS 
BthHfpDevice::SetBthHfpSpeakerVolume
(
    _In_ LONG  Volume  
)
/*++

Routine Description:

  This function synchronously sets the remote Bluetooth Hands-Free Profile SCO
  Bypass speaker volume.

--*/
{
    PAGED_CODE();
    DPF_ENTER(("[BthHfpDevice::SetBthHfpSpeakerVolume]"));
    
    NTSTATUS        ntStatus    = STATUS_SUCCESS;
    
    //
    // Get the Bth HFP SCO Bypass speaker volume.
    //
    ntStatus = SendIoCtrlSynchronously(
        NULL,
        IOCTL_BTHHFP_SPEAKER_SET_VOLUME,
        sizeof(Volume),
        0,
        &Volume);
    
    IF_FAILED_ACTION_JUMP(
        ntStatus,
        DPF(D_ERROR, ("SetBthHfpSpeakerVolume: SendIoCtrlSynchronously(IOCTL_BTHHFP_SPEAKER_SET_VOLUME) failed, 0x%x", ntStatus)),
        Done);

    //
    // All done.
    //
    ntStatus = STATUS_SUCCESS;
    
Done:

    return ntStatus;
}

//=============================================================================
#pragma code_seg("PAGE")
NTSTATUS 
BthHfpDevice::GetBthHfpSpeakerVolume
(
    _Out_ LONG  * Volume    
)
/*++

Routine Description:

  This function synchronously gets the remote Bluetooth Hands-Free Profile SCO
  Bypass speaker volume.

--*/
{
    PAGED_CODE();
    DPF_ENTER(("[BthHfpDevice::GetBthHfpSpeakerVolume]"));
    
    NTSTATUS                        ntStatus    = STATUS_SUCCESS;
    BthHfpDeviceNotificationBuffer  buffer      = {0};

    *Volume = 0;
    
    buffer.bImmediate = TRUE;
    
    //
    // Get the Bth HFP SCO Bypass speaker volume.
    //
    ntStatus = SendIoCtrlSynchronously(
        NULL,
        IOCTL_BTHHFP_SPEAKER_GET_VOLUME_STATUS_UPDATE,
        sizeof(buffer.bImmediate),
        sizeof(buffer.Volume),
        &buffer);
    
    IF_FAILED_ACTION_JUMP(
        ntStatus,
        DPF(D_ERROR, ("GetBthHfpSpeakerVolume: SendIoCtrlSynchronously(IOCTL_BTHHFP_SPEAKER_GET_VOLUME_STATUS_UPDATE) failed, 0x%x", ntStatus)),
        Done);

    //
    // All done.
    //
    *Volume = buffer.Volume;
    ntStatus = STATUS_SUCCESS;
    
Done:

    return ntStatus;
}

//=============================================================================
#pragma code_seg()
NTSTATUS 
BthHfpDevice::EnableBthHfpSpeakerVolumeStatusNotification()
/*++

Routine Description:

  This function registers for Bluetooth Hands-Free Profile SCO Bypass speaker 
  volume change notification. 

--*/
{
    DPF_ENTER(("[BthHfpDevice::EnableBthHfpSpeakerVolumeStatusNotification]"));
    
    NTSTATUS                                ntStatus    = STATUS_SUCCESS;
    BthHfpDeviceNotificationReqContext    * ctx         = NULL;

    ctx = GetBthHfpDeviceNotificationReqContext(m_SpeakerVolumeReq);

    //
    // This is a notification request.
    //
    ctx->Buffer.bImmediate = FALSE; 
    
    //
    // Register for speaker volume updates.
    //
    ntStatus = SendIoCtrlAsynchronously(
        m_SpeakerVolumeReq,
        IOCTL_BTHHFP_SPEAKER_GET_VOLUME_STATUS_UPDATE,
        ctx->MemIn,
        ctx->MemOut,
        EvtBthHfpDeviceNotificationStatusCompletion,
        this);
    
    IF_FAILED_ACTION_JUMP(
        ntStatus,
        DPF(D_ERROR, ("EnableBthHfpSpeakerVolumeStatusNotification: SendIoCtrlAsynchronously(IOCTL_BTHHFP_SPEAKER_GET_VOLUME_STATUS_UPDATE) failed, 0x%x", ntStatus)),
        Done);

    //
    // All done.
    //
    ntStatus = STATUS_SUCCESS;
    
Done:

    return ntStatus;
}

//=============================================================================
#pragma code_seg("PAGE")
NTSTATUS 
BthHfpDevice::SetBthHfpMicVolume
(
    _In_ LONG  Volume  
)
/*++

Routine Description:

  This function synchronously sets the remote Bluetooth Hands-Free Profile SCO
  Bypass mic volume.

--*/
{
    PAGED_CODE();
    DPF_ENTER(("[BthHfpDevice::SetBthHfpMicVolume]"));
    
    NTSTATUS        ntStatus    = STATUS_SUCCESS;
    
    //
    // Get the Bth HFP SCO Bypass mic volume.
    //
    ntStatus = SendIoCtrlSynchronously(
        NULL,
        IOCTL_BTHHFP_MIC_SET_VOLUME,
        sizeof(Volume),
        0,
        &Volume);
    
    IF_FAILED_ACTION_JUMP(
        ntStatus,
        DPF(D_ERROR, ("SetBthHfpMicVolume: SendIoCtrlSynchronously(IOCTL_BTHHFP_MIC_SET_VOLUME) failed, 0x%x", ntStatus)),
        Done);

    //
    // All done.
    //
    ntStatus = STATUS_SUCCESS;
    
Done:

    return ntStatus;
}

//=============================================================================
#pragma code_seg("PAGE")
NTSTATUS 
BthHfpDevice::GetBthHfpMicVolume
(
    _Out_ LONG  * Volume    
)
/*++

Routine Description:

  This function synchronously gets the remote Bluetooth Hands-Free Profile SCO
  Bypass mic volume.

--*/
{
    PAGED_CODE();
    DPF_ENTER(("[BthHfpDevice::GetBthHfpMicVolume]"));
    
    NTSTATUS                        ntStatus    = STATUS_SUCCESS;
    BthHfpDeviceNotificationBuffer  buffer      = {0};
    
    *Volume = 0;
    
    buffer.bImmediate = TRUE;
    
    //
    // Get the Bth HFP SCO Bypass mic volume.
    //
    ntStatus = SendIoCtrlSynchronously(
        NULL,
        IOCTL_BTHHFP_MIC_GET_VOLUME_STATUS_UPDATE,
        sizeof(buffer.bImmediate),
        sizeof(buffer.Volume),
        &buffer);
    
    IF_FAILED_ACTION_JUMP(
        ntStatus,
        DPF(D_ERROR, ("GetBthHfpMicVolume: SendIoCtrlSynchronously(IOCTL_BTHHFP_MIC_GET_VOLUME_STATUS_UPDATE) failed, 0x%x", ntStatus)),
        Done);

    //
    // All done.
    //
    *Volume = buffer.Volume;
    ntStatus = STATUS_SUCCESS;
    
Done:

    return ntStatus;
}

//=============================================================================
#pragma code_seg()
NTSTATUS 
BthHfpDevice::EnableBthHfpMicVolumeStatusNotification()
/*++

Routine Description:

  This function registers for Bluetooth Hands-Free Profile SCO Bypass mic 
  volume change notification. 

--*/
{
    DPF_ENTER(("[BthHfpDevice::EnableBthHfpMicVolumeStatusNotification]"));
    
    NTSTATUS                               ntStatus    = STATUS_SUCCESS;
    BthHfpDeviceNotificationReqContext   * ctx         = NULL;

    ctx = GetBthHfpDeviceNotificationReqContext(m_MicVolumeReq);
    
    //
    // This is a notification request.
    //
    ctx->Buffer.bImmediate = FALSE; 
    
    //
    // Register for mic volume updates.
    //
    ntStatus = SendIoCtrlAsynchronously(
        m_MicVolumeReq,
        IOCTL_BTHHFP_MIC_GET_VOLUME_STATUS_UPDATE,
        ctx->MemIn,
        ctx->MemOut,
        EvtBthHfpDeviceNotificationStatusCompletion,
        this);
    
    IF_FAILED_ACTION_JUMP(
        ntStatus,
        DPF(D_ERROR, ("EnableBthHfpMicVolumeStatusNotification: SendIoCtrlAsynchronously(IOCTL_BTHHFP_MIC_GET_VOLUME_STATUS_UPDATE) failed, 0x%x", ntStatus)),
        Done);

    //
    // All done.
    //
    ntStatus = STATUS_SUCCESS;
    
Done:

    return ntStatus;
}

//=============================================================================
#pragma code_seg("PAGE")
NTSTATUS 
BthHfpDevice::GetBthHfpConnectionStatus
(
    _Out_ BOOL * ConnectionStatus    
)
/*++

Routine Description:

  This function synchronously gets the remote Bluetooth Hands-Free Profile SCO
  Bypass connection status.

--*/
{
    PAGED_CODE();
    DPF_ENTER(("[BthHfpDevice::GetBthHfpConnectionStatus]"));
    
    NTSTATUS            ntStatus    = STATUS_SUCCESS;
    BOOL                bValue      = TRUE; // In: bImmediate, Out: value.
    
    *ConnectionStatus = 0;
    
    //
    // Get the Bth HFP SCO Bypass connection status.
    //
    ntStatus = SendIoCtrlSynchronously(
        NULL,
        IOCTL_BTHHFP_DEVICE_GET_CONNECTION_STATUS_UPDATE,
        sizeof(bValue),
        sizeof(bValue),
        &bValue);
    
    IF_FAILED_ACTION_JUMP(
        ntStatus,
        DPF(D_ERROR, ("GetBthHfpConnectionStatus: SendIoCtrlSynchronously(IOCTL_BTHHFP_DEVICE_GET_CONNECTION_STATUS_UPDATE) failed, 0x%x", ntStatus)),
        Done);

    //
    // All done.
    //
    *ConnectionStatus = bValue;
    ntStatus = STATUS_SUCCESS;
    
Done:

    return ntStatus;
}

//=============================================================================
#pragma code_seg()
NTSTATUS 
BthHfpDevice::EnableBthHfpConnectionStatusNotification()
/*++

Routine Description:

  This function registers for Bluetooth Hands-Free Profile SCO Bypass  
  connection status notification. 

--*/
{
    DPF_ENTER(("[BthHfpDevice::EnableBthHfpConnectionStatusNotification]"));
    
    NTSTATUS                               ntStatus    = STATUS_SUCCESS;
    BthHfpDeviceNotificationReqContext   * ctx         = NULL;

    ctx = GetBthHfpDeviceNotificationReqContext(m_ConnectionReq);
    
    //
    // Make sure this obj is alive while the IRP is active.
    //
    ctx->Buffer.bImmediate = FALSE; 
    
    //
    // Get the Bth HFP SCO Bypass connection status.
    //
    ntStatus = SendIoCtrlAsynchronously(
        m_ConnectionReq,
        IOCTL_BTHHFP_DEVICE_GET_CONNECTION_STATUS_UPDATE,
        ctx->MemIn,
        ctx->MemOut,
        EvtBthHfpDeviceNotificationStatusCompletion,
        this);
    
    IF_FAILED_ACTION_JUMP(
        ntStatus,
        DPF(D_ERROR, ("EnableBthHfpConnectionStatusNotification: SendIoCtrlAsynchronously(IOCTL_BTHHFP_DEVICE_GET_CONNECTION_STATUS_UPDATE) failed, 0x%x", ntStatus)),
        Done);

    //
    // All done.
    //
    ntStatus = STATUS_SUCCESS;
    
Done:

    return ntStatus;
}

//=============================================================================
#pragma code_seg("PAGE")
NTSTATUS 
BthHfpDevice::SetBthHfpConnect()
/*++

Routine Description:

  This function synchronously requests a Bluetooth Hands-Free Profile level
  connection to the paired Bluetooth device.
  
  This request initiates the Service Level Connection establishment procedure 
  and completes without waiting for the connection procedure to complete. 
  Connection status can be determined using IOCTL_BTHHFP_GET_CONNECTION_STATUS_UPDATE.

--*/
{
    PAGED_CODE();
    DPF_ENTER(("[BthHfpDevice::SetBthHfpConnect]"));
    
    NTSTATUS        ntStatus    = STATUS_SUCCESS;
    
    ntStatus = SendIoCtrlSynchronously(
        NULL,
        IOCTL_BTHHFP_DEVICE_REQUEST_CONNECT,
        0,
        0,
        NULL);

    IF_FAILED_ACTION_JUMP(
        ntStatus,
        DPF(D_ERROR, ("SetBthHfpConnect: SendIoCtrlSynchronously(IOCTL_BTHHFP_DEVICE_REQUEST_CONNECT) failed, 0x%x", ntStatus)),
        Done);

    //
    // All done.
    //
    ntStatus = STATUS_SUCCESS;
    
Done:

    return ntStatus;
}

//=============================================================================
#pragma code_seg("PAGE")
NTSTATUS 
BthHfpDevice::SetBthHfpDisconnect()
/*++

Routine Description:

  This function synchronously requests a Bluetooth Hands-Free Profile level
  connection to the paired Bluetooth device.

  This request initiates disconnection of the Service Level Connection and 
  completes without waiting for the disconnection to complete. Connection 
  status can be determined using IOCTL_BTHHFP_GET_CONNECTION_STATUS_UPDATE.

--*/
{
    PAGED_CODE();
    DPF_ENTER(("[BthHfpDevice::SetBthHfpDisconnect]"));
    
    NTSTATUS        ntStatus    = STATUS_SUCCESS;
    
    ntStatus = SendIoCtrlSynchronously(
        NULL,
        IOCTL_BTHHFP_DEVICE_REQUEST_DISCONNECT,
        0,
        0,
        NULL);

    IF_FAILED_ACTION_JUMP(
        ntStatus,
        DPF(D_ERROR, ("SetBthHfpDisconnect: SendIoCtrlSynchronously(IOCTL_BTHHFP_DEVICE_REQUEST_DISCONNECT) failed, 0x%x", ntStatus)),
        Done);

    //
    // All done.
    //
    ntStatus = STATUS_SUCCESS;
    
Done:

    return ntStatus;
}

//=============================================================================
#pragma code_seg()
void 
BthHfpDevice::EvtBthHfpDeviceStreamStatusCompletion
(
  _In_  WDFREQUEST Request,
  _In_  WDFIOTARGET Target,
  _In_  PWDF_REQUEST_COMPLETION_PARAMS Params,
  _In_  WDFCONTEXT Context
)
/*++

Routine Description:

  Completion callback for the Bluetooth Hands-Free Profile SCO Bypass  
  stream status notification. 

--*/
{
    DPF_ENTER(("[BthHfpDevice::EvtBthHfpDeviceStreamStatusCompletion]"));
   
    NTSTATUS                                ntStatus    = STATUS_SUCCESS;
    BthHfpDeviceNotificationReqContext    * reqCtx      = NULL;
    BthHfpDevice                          * This        = NULL;
    NTSTATUS                                ntResult    = STATUS_SUCCESS;
    
    UNREFERENCED_PARAMETER(Target);
    UNREFERENCED_PARAMETER(Context);

    //
    // Get the SCO stream status.
    //
    reqCtx = GetBthHfpDeviceNotificationReqContext(Request);
    This = reqCtx->BthHfpDevice;
    ASSERT(This != NULL);

    ntStatus = Params->IoStatus.Status;
    if (!NT_SUCCESS(ntStatus))
    {
        ntResult = STATUS_INVALID_DEVICE_STATE;
    }
    else 
    {
        ntResult = reqCtx->Buffer.NtStatus;
    }

    InterlockedExchange(&This->m_StreamStatusLong, (LONG)ntResult);
    
    //
    // Let the stop routine know we are done. Stop routine will 
    // re-init the request.
    //
    KeSetEvent(&This->m_StreamStatusEvent, IO_NO_INCREMENT, FALSE);
}

//=============================================================================
#pragma code_seg()
NTSTATUS 
BthHfpDevice::EnableBthHfpStreamStatusNotification()
/*++

Routine Description:

  This function registers for Bluetooth Hands-Free Profile SCO Bypass  
  stream status notification. 

--*/
{
    DPF_ENTER(("[BthHfpDevice::EnableBthHfpStreamStatusNotification]"));
    
    NTSTATUS                               ntStatus    = STATUS_SUCCESS;
    BthHfpDeviceNotificationReqContext   * ctx         = NULL;

    ASSERT(m_nStreams > 0);
    
    ctx = GetBthHfpDeviceNotificationReqContext(m_StreamReq);
    ctx->Buffer.bImmediate = FALSE; 

    KeClearEvent(&m_StreamStatusEvent);
    
    //
    // Get the Bth HFP SCO Bypass connection status.
    //
    ntStatus = SendIoCtrlAsynchronously(
        m_StreamReq,
        IOCTL_BTHHFP_STREAM_GET_STATUS_UPDATE,
        ctx->MemIn,
        ctx->MemOut,
        EvtBthHfpDeviceStreamStatusCompletion,
        this);
    
    if (!NT_SUCCESS(ntStatus))
    {
        KeSetEvent(&m_StreamStatusEvent, IO_NO_INCREMENT, FALSE);
        DPF(D_ERROR, ("EnableBthHfpStreamStatusNotification: SendIoCtrlAsynchronously(IOCTL_BTHHFP_STREAM_GET_STATUS_UPDATE) failed, 0x%x", ntStatus));
        goto Done;
    }

    //
    // All done.
    //
    ntStatus = STATUS_SUCCESS;
    
Done:

    return ntStatus;
}

//=============================================================================
#pragma code_seg("PAGE")
NTSTATUS 
BthHfpDevice::StopBthHfpStreamStatusNotification()
/*++

Routine Description:

  This function stops the Bluetooth Hands-Free Profile SCO Bypass  
  connection status notification.
  The function waits for the request to be done before returning.

--*/
{
    PAGED_CODE();
    DPF_ENTER(("[BthHfpDevice::StopBthHfpStreamStatusNotification]"));
    
    NTSTATUS                                ntStatus    = STATUS_SUCCESS;
    BthHfpDeviceNotificationReqContext    * reqCtx      = NULL;
    WDF_REQUEST_REUSE_PARAMS                reuseParams;       
   
    WdfRequestCancelSentRequest(m_StreamReq);
    KeWaitForSingleObject(&m_StreamStatusEvent, Executive, KernelMode, FALSE, NULL);

    reqCtx = GetBthHfpDeviceNotificationReqContext(m_StreamReq);
    ASSERT(reqCtx != NULL);
    UNREFERENCED_VAR(reqCtx);
    
    // 
    // Re-init the request for later.
    //
    WDF_REQUEST_REUSE_PARAMS_INIT(
        &reuseParams,   
        WDF_REQUEST_REUSE_NO_FLAGS,
        STATUS_SUCCESS);
    
    ntStatus = WdfRequestReuse(m_StreamReq, &reuseParams);
    if (!NT_SUCCESS(ntStatus))
    {
        DPF(D_ERROR, ("StopBthHfpStreamStatusNotification: WdfRequestReuse failed, 0x%x", ntStatus));
    }

    return STATUS_SUCCESS;
}

//=============================================================================
#pragma code_seg("PAGE")
NTSTATUS 
BthHfpDevice::SetBthHfpStreamOpen()
/*++

Routine Description:

  This function synchronously requests an open SCO channel to transmit audio
  data over the air.

--*/
{
    PAGED_CODE();
    DPF_ENTER(("[BthHfpDevice::SetBthHfpStreamOpen]"));
    
    NTSTATUS        ntStatus    = STATUS_SUCCESS;
    
    ntStatus = SendIoCtrlSynchronously(
        NULL,
        IOCTL_BTHHFP_STREAM_OPEN,
        0,
        0,
        NULL);

    if (ntStatus == STATUS_DEVICE_BUSY)
    {
        // The stream channel is already open.
        DPF(D_VERBOSE, ("SetBthHfpStreamOpen: the stream channel is already open"));
        ntStatus = STATUS_SUCCESS;
    }
    
    IF_FAILED_ACTION_JUMP(
        ntStatus,
        DPF(D_ERROR, ("SetBthHfpStreamOpen: SendIoCtrlSynchronously(IOCTL_BTHHFP_STREAM_OPEN) failed, 0x%x", ntStatus)),
        Done);

    //
    // All done.
    //
    ntStatus = STATUS_SUCCESS;
    
Done:

    return ntStatus;
}

//=============================================================================
#pragma code_seg("PAGE")
NTSTATUS 
BthHfpDevice::SetBthHfpStreamClose()
/*++

Routine Description:

  This function synchronously requests to close the SCO channel.

--*/
{
    PAGED_CODE();
    DPF_ENTER(("[BthHfpDevice::SetBthHfpStreamClose]"));
    
    NTSTATUS        ntStatus    = STATUS_SUCCESS;
    
    ntStatus = SendIoCtrlSynchronously(
        NULL,
        IOCTL_BTHHFP_STREAM_CLOSE,
        0,
        0,
        NULL);

    IF_FAILED_ACTION_JUMP(
        ntStatus,
        DPF(D_ERROR, ("SetBthHfpStreamClose: SendIoCtrlSynchronously(IOCTL_BTHHFP_STREAM_CLOSE) failed, 0x%x", ntStatus)),
        Done);

    //
    // All done.
    //
    ntStatus = STATUS_SUCCESS;
    
Done:

    return ntStatus;
}

//=============================================================================
#pragma code_seg("PAGE")
VOID 
BthHfpDevice::Start()
/*++

Routine Description:

  Asynchronously called to start the audio device.

--*/
{
    PAGED_CODE();
    DPF_ENTER(("[BthHfpDevice::Start]"));

    NTSTATUS            ntStatus            = STATUS_SUCCESS;
    BOOL                connStatus          = FALSE;

    //
    // Get bth hfp descriptor
    //
    ntStatus = GetBthHfpDescriptor(&m_Descriptor);
    IF_FAILED_ACTION_JUMP(
        ntStatus,
        DPF(D_ERROR, ("Start: GetBthHfpDescriptor: failed to retrieve BTHHFP_DESCRIPTOR2, 0x%x", ntStatus)),
        Done);

    //
    // Get valume settings.
    //
    if (m_Descriptor->SupportsVolume)
    {
        PKSPROPERTY_VALUES  volumePropValues    = NULL;
        LONG                volume              = 0;

        // Volume settings.
        ntStatus = GetBthHfpVolumePropertyValues(
            m_Descriptor->VolumePropertyValuesSize, 
            &volumePropValues);
        
        IF_FAILED_ACTION_JUMP(
            ntStatus,
            DPF(D_ERROR, ("Start: GetBthHfpVolumePropertyValues: failed to retrieve KSPROPERTY_VALUES, 0x%x", ntStatus)),
            Done);

        m_VolumePropValues = volumePropValues;

        // Speaker volume.
        ntStatus = GetBthHfpSpeakerVolume(&volume);
        IF_FAILED_ACTION_JUMP(
            ntStatus,
            DPF(D_ERROR, ("Start: GetBthHfpSpeakerVolume: failed, 0x%x", ntStatus)),
            Done);

        m_SpeakerVolumeLevel = volume;

        // Mic volume.
        ntStatus = GetBthHfpMicVolume(&volume);
        IF_FAILED_ACTION_JUMP(
            ntStatus,
            DPF(D_ERROR, ("Start: GetBthHfpMicVolume: failed, 0x%x", ntStatus)),
            Done);
        
        m_MicVolumeLevel = volume;
    } 

    //
    // Get connection status.
    //
    ntStatus = GetBthHfpConnectionStatus(&connStatus);
    IF_FAILED_ACTION_JUMP(
        ntStatus,
        DPF(D_ERROR, ("Start: GetBthHfpConnectionStatus: failed, 0x%x", ntStatus)),
        Done);

    m_ConnectionStatus = connStatus;

    //
    // Get the topology/wave descriptors.
    //
    m_SpeakerMiniports = g_BthHfpRenderEndpoints[0];
    m_MicMiniports = g_BthHfpCaptureEndpoints[0];

    ASSERT(m_SpeakerMiniports != NULL);
    ASSERT(m_MicMiniports != NULL);
    _Analysis_assume_(m_SpeakerMiniports != NULL);
    _Analysis_assume_(m_MicMiniports != NULL);
    
    //
    // Create the audio interface (in disabled mode).
    //
    ntStatus = CreateAudioInterface(m_SpeakerMiniports->TopoName, &m_SpeakerAudioSymbolicLinkName);
    IF_FAILED_ACTION_JUMP(
        ntStatus,
        DPF(D_ERROR, ("Start: CreateAudioInterface(KSCATEGORY_AUDIO) for speaker: failed, 0x%x", ntStatus)),
        Done);
    
    ntStatus = CreateAudioInterface(m_MicMiniports->TopoName, &m_MicAudioSymbolicLinkName);
    IF_FAILED_ACTION_JUMP(
        ntStatus,
        DPF(D_ERROR, ("Start: CreateAudioInterface(KSCATEGORY_AUDIO) for mic: failed, 0x%x", ntStatus)),
        Done);

    //
    // Register topology and wave filters.
    //
    ntStatus = m_Adapter->InstallEndpointFilters(
        NULL,
        m_SpeakerMiniports,
        PBTHHFPDEVICECOMMON(this),
        &m_UnknownSpeakerTopology,
        &m_UnknownSpeakerWave
        );
    
    IF_FAILED_ACTION_JUMP(
        ntStatus,
        DPF(D_ERROR, ("Start: InstallEndpointRenderFilters (Bth HFP SCO-Bypass): failed, 0x%x", ntStatus)),
        Done);

    ntStatus = m_Adapter->InstallEndpointFilters(
        NULL,
        m_MicMiniports,
        PBTHHFPDEVICECOMMON(this),
        &m_UnknownMicTopology,
        &m_UnknownMicWave
        );
    
    IF_FAILED_ACTION_JUMP(
        ntStatus,
        DPF(D_ERROR, ("Start: InstallEndpointCaptureFilters (Bth HFP SCO-Bypass): failed, 0x%x", ntStatus)),
        Done);

    //
    // Pend status notifications.
    //
    
    // NREC disable AudioGateway (AG) status.
    ntStatus = EnableBthHfpNrecDisableStatusNotification();
    IF_FAILED_ACTION_JUMP(
        ntStatus,
        DPF(D_ERROR, ("Start: EnableBthHfpNrecDisableStatusNotification: failed, 0x%x", ntStatus)),
        Done);
    
    // Volume speaker status.
    ntStatus = EnableBthHfpSpeakerVolumeStatusNotification();
    IF_FAILED_ACTION_JUMP(
        ntStatus,
        DPF(D_ERROR, ("Start: EnableBthHfpSpeakerVolumeStatusNotification: failed, 0x%x", ntStatus)),
        Done);
    
    // Volume mic status.
    ntStatus = EnableBthHfpMicVolumeStatusNotification();
    IF_FAILED_ACTION_JUMP(
        ntStatus,
        DPF(D_ERROR, ("Start: EnableBthHfpMicVolumeStatusNotification: failed, 0x%x", ntStatus)),
        Done);
    
    // Connection status.
    ntStatus = EnableBthHfpConnectionStatusNotification();
    IF_FAILED_ACTION_JUMP(
        ntStatus,
        DPF(D_ERROR, ("Start: EnableBthHfpConnectionStatusNotification: failed, 0x%x", ntStatus)),
        Done);

    //
    // All done.
    //
    ntStatus = STATUS_SUCCESS;
    
Done:;
    if (!NT_SUCCESS(ntStatus))
    {
        InterlockedExchange((PLONG)&m_State, eBthHfpStateFailed);
    }
}

//=============================================================================
#pragma code_seg("PAGE")
VOID 
BthHfpDevice::Stop()
/*++

Routine Description:

  Asynchronously called to stop the audio device.
  After returning from this function, there are no more async notifications
  pending (volume, connection, etc.).
  
--*/
{
    PAGED_CODE();
    DPF_ENTER(("[BthHfpDevice::Stop]"));

    NTSTATUS        ntStatus    = STATUS_SUCCESS;
    eBthHfpState    state       = eBthHfpStateInvalid;

    state = (eBthHfpState) InterlockedExchange((PLONG)&m_State, eBthHfpStateStopping);
    ASSERT(state == eBthHfpStateRunning || state == eBthHfpStateFailed);
    UNREFERENCED_VAR(state);
    
    //
    // Stop async notifications.
    //
    WdfIoTargetPurge(m_WdfIoTarget, WdfIoTargetPurgeIoAndWait);

    //
    // Wait for work-item.
    //
    WdfWorkItemFlush(m_WorkItem);
    
    //
    // Remove the topology and wave render filters.
    //
    if (m_UnknownSpeakerTopology || m_UnknownSpeakerWave)
    {
        ntStatus = m_Adapter->RemoveEndpointFilters(
            m_SpeakerMiniports,
            m_UnknownSpeakerTopology,
            m_UnknownSpeakerWave);
        
        if (!NT_SUCCESS(ntStatus))
        {
            DPF(D_ERROR, ("RemoveEndpointFilters (Bth HFP SCO-Bypass Speaker): failed, 0x%x", ntStatus));
        }
    }
    
    //
    // Remove the topology and wave capture filters.
    //
    if (m_UnknownMicTopology || m_UnknownMicWave)
    {
        ntStatus = m_Adapter->RemoveEndpointFilters(
            m_MicMiniports,
            m_UnknownMicTopology,
            m_UnknownMicWave);
        
        if (!NT_SUCCESS(ntStatus))
        {
            DPF(D_ERROR, ("RemoveEndpointFilters (Bth HFP SCO-Bypass Capture): failed, 0x%x", ntStatus));
        }
    }

    //
    // Release port/miniport pointers.
    //
    SAFE_RELEASE(m_UnknownSpeakerTopology);
    SAFE_RELEASE(m_UnknownSpeakerWave);
    SAFE_RELEASE(m_UnknownMicTopology);
    SAFE_RELEASE(m_UnknownMicWave);

    //
    // The device is in the stopped state.
    //
    InterlockedExchange((PLONG)&m_State, eBthHfpStateStopped);
}

#endif // SYSVAD_BTH_BYPASS


Our Services

  • What our customers say about us?

© 2011-2024 All Rights Reserved. Joya Systems. 4425 South Mopac Building II Suite 101 Austin, TX 78735 Tel: 800-DEV-KERNEL

Privacy Policy. Terms of use. Valid XHTML & CSS