From 4f9caecb9454f3856e76f4960979be19602590ca Mon Sep 17 00:00:00 2001 From: Saurin Shah Date: Mon, 24 Nov 2025 09:54:14 -0800 Subject: [PATCH] SDCA sample driver. --- .../Documentation/IntroductionToSdca.docx | Bin 0 -> 152708 bytes audio/Soundwire/LICENSE | 23 + audio/Soundwire/README.md | 2 + .../Samples/SdcaVad/Apo/inc/CommonMacros.h | 337 ++ .../Samples/SdcaVad/Apo/inc/CustomPropKeys.h | 39 + .../Samples/SdcaVad/Apo/kws/KWSApo.cpp | 62 + .../Samples/SdcaVad/Apo/kws/KWSApo.h | 134 + .../Samples/SdcaVad/Apo/kws/KWSApo.png | Bin 0 -> 1191 bytes .../Samples/SdcaVad/Apo/kws/KWSApo.vcxproj | 468 +++ .../SdcaVad/Apo/kws/KWSApo.vcxproj.Filters | 21 + .../Samples/SdcaVad/Apo/kws/KWSApoDll.cpp | 71 + .../Samples/SdcaVad/Apo/kws/KWSApoDll.def | 4 + .../Samples/SdcaVad/Apo/kws/KWSApoDll.idl | 40 + .../Samples/SdcaVad/Apo/kws/KWSApoDll.rc | 20 + .../Samples/SdcaVad/Apo/kws/KWSApoDll.rgs | 11 + .../Samples/SdcaVad/Apo/kws/KWSApoEfx.cpp | 599 ++++ .../Samples/SdcaVad/Apo/kws/KWSApoEfx.rgs | 13 + .../SdcaVad/Apo/kws/KWSApoInterface.idl | 21 + .../Samples/SdcaVad/Apo/kws/Resource.h | 19 + .../Samples/SdcaVad/Common/NewDelete.cpp | 149 + .../EventDetectorContosoAdapter.cpp | 211 ++ .../EventDetectorContosoAdapter.def | 6 + .../EventDetectorContosoAdapter.idl | 20 + .../EventDetectorContosoAdapter.rc | 13 + .../EventDetectorContosoAdapter.vcxproj | 433 +++ ...ventDetectorContosoAdapter.vcxproj.Filters | 17 + .../SdcaVad/EventDetectorAdapter/dllmain.cpp | 35 + .../SdcaVad/EventDetectorAdapter/stdafx.cpp | 6 + .../SdcaVad/EventDetectorAdapter/stdafx.h | 13 + .../SdcaVad/EventDetectorAdapter/targetver.h | 10 + .../Samples/SdcaVad/Inc/AudioFormats.h | 623 ++++ .../SdcaVad/Inc/ContosoEventDetector.h | 51 + .../Soundwire/Samples/SdcaVad/Inc/NewDelete.h | 121 + .../SdcaVad/Inc/SdcaVXuTestInterface.h | 55 + .../Samples/SdcaVad/Inc/TestProperties.h | 60 + .../Soundwire/Samples/SdcaVad/Inc/cpp_utils.h | 71 + .../Samples/SdcaVad/Inc/trace_macros.h | 460 +++ .../Samples/SdcaVad/Package/package.VcxProj | 166 + .../SdcaVad/Package/package.VcxProj.Filters | 21 + audio/Soundwire/Samples/SdcaVad/README.md | 145 + audio/Soundwire/Samples/SdcaVad/SDCAVad.sln | 185 ++ .../SdcaVad/SdcaVCodec/CircuitHelper.cpp | 286 ++ .../SdcaVad/SdcaVCodec/CircuitHelper.h | 84 + .../Samples/SdcaVad/SdcaVCodec/Extension.cpp | 262 ++ .../SdcaVad/SdcaVCodec/SDCAVCodec.vcxproj | 364 +++ .../SdcaVCodec/SDCAVCodec.vcxproj.Filters | 21 + .../Samples/SdcaVad/SdcaVCodec/SdcaVCodec.inx | 146 + .../Samples/SdcaVad/SdcaVCodec/Trace.h | 34 + .../Samples/SdcaVad/SdcaVCodec/capture.cpp | 1150 +++++++ .../Samples/SdcaVad/SdcaVCodec/device.cpp | 808 +++++ .../Samples/SdcaVad/SdcaVCodec/driver.cpp | 218 ++ .../Samples/SdcaVad/SdcaVCodec/private.h | 522 ++++ .../Samples/SdcaVad/SdcaVCodec/render.cpp | 1310 ++++++++ .../Samples/SdcaVad/SdcaVCodec/resources.rc | 12 + .../SdcaVad/SdcaVCodec/streamengine.cpp | 270 ++ .../Samples/SdcaVad/SdcaVCodec/streamengine.h | 131 + .../Samples/SdcaVad/SdcaVDsp/AcpiReader.cpp | 1715 +++++++++++ .../Samples/SdcaVad/SdcaVDsp/AcpiReader.h | 297 ++ .../Samples/SdcaVad/SdcaVDsp/AudioModule.cpp | 384 +++ .../Samples/SdcaVad/SdcaVDsp/AudioModule.h | 216 ++ .../SdcaVad/SdcaVDsp/CircuitHelper.cpp | 1467 +++++++++ .../Samples/SdcaVad/SdcaVDsp/CircuitHelper.h | 135 + .../SdcaVad/SdcaVDsp/KeywordDetector.cpp | 1053 +++++++ .../SdcaVad/SdcaVDsp/KeywordDetector.h | 237 ++ .../Samples/SdcaVad/SdcaVDsp/PositionClock.h | 38 + .../SdcaVad/SdcaVDsp/PositionSimClock.cpp | 140 + .../SdcaVad/SdcaVDsp/PositionSimClock.h | 76 + .../Samples/SdcaVad/SdcaVDsp/SDCAVDsp.vcxproj | 385 +++ .../SdcaVad/SdcaVDsp/SDCAVDsp.vcxproj.Filters | 24 + .../Samples/SdcaVad/SdcaVDsp/SdcaVApo.inx | 79 + .../Samples/SdcaVad/SdcaVDsp/SdcaVDsp.inx | 159 + .../Samples/SdcaVad/SdcaVDsp/SimPeakMeter.cpp | 106 + .../Samples/SdcaVad/SdcaVDsp/SimPeakMeter.h | 51 + .../SdcaVad/SdcaVDsp/ToneGenerator.cpp | 329 ++ .../Samples/SdcaVad/SdcaVDsp/ToneGenerator.h | 93 + .../Samples/SdcaVad/SdcaVDsp/Trace.h | 34 + .../Samples/SdcaVad/SdcaVDsp/WaveReader.cpp | 772 +++++ .../Samples/SdcaVad/SdcaVDsp/WaveReader.h | 196 ++ .../Samples/SdcaVad/SdcaVDsp/capture.cpp | 1465 +++++++++ .../SdcaVad/SdcaVDsp/circuitstream.cpp | 820 +++++ .../Samples/SdcaVad/SdcaVDsp/device.cpp | 1631 ++++++++++ .../Samples/SdcaVad/SdcaVDsp/driver.cpp | 172 ++ .../SdcaVad/SdcaVDsp/offloadStreamEngine.cpp | 606 ++++ .../SdcaVad/SdcaVDsp/offloadStreamEngine.h | 172 ++ .../Samples/SdcaVad/SdcaVDsp/packages.config | 4 + .../Samples/SdcaVad/SdcaVDsp/private.h | 803 +++++ .../Samples/SdcaVad/SdcaVDsp/render.cpp | 2730 +++++++++++++++++ .../SdcaVad/SdcaVDsp/renderAudioEngine.cpp | 502 +++ .../Samples/SdcaVad/SdcaVDsp/resources.rc | 12 + .../Samples/SdcaVad/SdcaVDsp/savedata.cpp | 1043 +++++++ .../Samples/SdcaVad/SdcaVDsp/savedata.h | 257 ++ .../Samples/SdcaVad/SdcaVDsp/streamengine.cpp | 1053 +++++++ .../Samples/SdcaVad/SdcaVDsp/streamengine.h | 391 +++ .../Samples/SdcaVad/SdcaVXu/AudioModule.cpp | 405 +++ .../Samples/SdcaVad/SdcaVXu/AudioModule.h | 246 ++ .../Samples/SdcaVad/SdcaVXu/CircuitDevice.cpp | 464 +++ .../Samples/SdcaVad/SdcaVXu/CircuitDevice.h | 57 + .../Samples/SdcaVad/SdcaVXu/Device.h | 37 + .../Samples/SdcaVad/SdcaVXu/ModuleCircuit.cpp | 376 +++ .../Samples/SdcaVad/SdcaVXu/ModuleCircuit.h | 32 + .../Samples/SdcaVad/SdcaVXu/SDCAVXu.vcxproj | 355 +++ .../SdcaVad/SdcaVXu/SDCAVXu.vcxproj.Filters | 21 + .../Samples/SdcaVad/SdcaVXu/SdcaVXu.inx | 144 + .../Soundwire/Samples/SdcaVad/SdcaVXu/Trace.h | 34 + .../Samples/SdcaVad/SdcaVXu/capture.cpp | 1059 +++++++ .../Samples/SdcaVad/SdcaVXu/capture.h | 84 + .../Samples/SdcaVad/SdcaVXu/device.cpp | 1426 +++++++++ .../Samples/SdcaVad/SdcaVXu/driver.cpp | 178 ++ .../Samples/SdcaVad/SdcaVXu/driver.h | 35 + .../Samples/SdcaVad/SdcaVXu/private.h | 422 +++ .../Samples/SdcaVad/SdcaVXu/render.cpp | 2093 +++++++++++++ .../Samples/SdcaVad/SdcaVXu/render.h | 84 + .../Samples/SdcaVad/SdcaVXu/resources.rc | 12 + .../Samples/SdcaVad/SdcaVXu/streamengine.cpp | 264 ++ .../Samples/SdcaVad/SdcaVXu/streamengine.h | 131 + 115 files changed, 37979 insertions(+) create mode 100644 audio/Soundwire/Documentation/IntroductionToSdca.docx create mode 100644 audio/Soundwire/LICENSE create mode 100644 audio/Soundwire/README.md create mode 100644 audio/Soundwire/Samples/SdcaVad/Apo/inc/CommonMacros.h create mode 100644 audio/Soundwire/Samples/SdcaVad/Apo/inc/CustomPropKeys.h create mode 100644 audio/Soundwire/Samples/SdcaVad/Apo/kws/KWSApo.cpp create mode 100644 audio/Soundwire/Samples/SdcaVad/Apo/kws/KWSApo.h create mode 100644 audio/Soundwire/Samples/SdcaVad/Apo/kws/KWSApo.png create mode 100644 audio/Soundwire/Samples/SdcaVad/Apo/kws/KWSApo.vcxproj create mode 100644 audio/Soundwire/Samples/SdcaVad/Apo/kws/KWSApo.vcxproj.Filters create mode 100644 audio/Soundwire/Samples/SdcaVad/Apo/kws/KWSApoDll.cpp create mode 100644 audio/Soundwire/Samples/SdcaVad/Apo/kws/KWSApoDll.def create mode 100644 audio/Soundwire/Samples/SdcaVad/Apo/kws/KWSApoDll.idl create mode 100644 audio/Soundwire/Samples/SdcaVad/Apo/kws/KWSApoDll.rc create mode 100644 audio/Soundwire/Samples/SdcaVad/Apo/kws/KWSApoDll.rgs create mode 100644 audio/Soundwire/Samples/SdcaVad/Apo/kws/KWSApoEfx.cpp create mode 100644 audio/Soundwire/Samples/SdcaVad/Apo/kws/KWSApoEfx.rgs create mode 100644 audio/Soundwire/Samples/SdcaVad/Apo/kws/KWSApoInterface.idl create mode 100644 audio/Soundwire/Samples/SdcaVad/Apo/kws/Resource.h create mode 100644 audio/Soundwire/Samples/SdcaVad/Common/NewDelete.cpp create mode 100644 audio/Soundwire/Samples/SdcaVad/EventDetectorAdapter/EventDetectorContosoAdapter.cpp create mode 100644 audio/Soundwire/Samples/SdcaVad/EventDetectorAdapter/EventDetectorContosoAdapter.def create mode 100644 audio/Soundwire/Samples/SdcaVad/EventDetectorAdapter/EventDetectorContosoAdapter.idl create mode 100644 audio/Soundwire/Samples/SdcaVad/EventDetectorAdapter/EventDetectorContosoAdapter.rc create mode 100644 audio/Soundwire/Samples/SdcaVad/EventDetectorAdapter/EventDetectorContosoAdapter.vcxproj create mode 100644 audio/Soundwire/Samples/SdcaVad/EventDetectorAdapter/EventDetectorContosoAdapter.vcxproj.Filters create mode 100644 audio/Soundwire/Samples/SdcaVad/EventDetectorAdapter/dllmain.cpp create mode 100644 audio/Soundwire/Samples/SdcaVad/EventDetectorAdapter/stdafx.cpp create mode 100644 audio/Soundwire/Samples/SdcaVad/EventDetectorAdapter/stdafx.h create mode 100644 audio/Soundwire/Samples/SdcaVad/EventDetectorAdapter/targetver.h create mode 100644 audio/Soundwire/Samples/SdcaVad/Inc/AudioFormats.h create mode 100644 audio/Soundwire/Samples/SdcaVad/Inc/ContosoEventDetector.h create mode 100644 audio/Soundwire/Samples/SdcaVad/Inc/NewDelete.h create mode 100644 audio/Soundwire/Samples/SdcaVad/Inc/SdcaVXuTestInterface.h create mode 100644 audio/Soundwire/Samples/SdcaVad/Inc/TestProperties.h create mode 100644 audio/Soundwire/Samples/SdcaVad/Inc/cpp_utils.h create mode 100644 audio/Soundwire/Samples/SdcaVad/Inc/trace_macros.h create mode 100644 audio/Soundwire/Samples/SdcaVad/Package/package.VcxProj create mode 100644 audio/Soundwire/Samples/SdcaVad/Package/package.VcxProj.Filters create mode 100644 audio/Soundwire/Samples/SdcaVad/README.md create mode 100644 audio/Soundwire/Samples/SdcaVad/SDCAVad.sln create mode 100644 audio/Soundwire/Samples/SdcaVad/SdcaVCodec/CircuitHelper.cpp create mode 100644 audio/Soundwire/Samples/SdcaVad/SdcaVCodec/CircuitHelper.h create mode 100644 audio/Soundwire/Samples/SdcaVad/SdcaVCodec/Extension.cpp create mode 100644 audio/Soundwire/Samples/SdcaVad/SdcaVCodec/SDCAVCodec.vcxproj create mode 100644 audio/Soundwire/Samples/SdcaVad/SdcaVCodec/SDCAVCodec.vcxproj.Filters create mode 100644 audio/Soundwire/Samples/SdcaVad/SdcaVCodec/SdcaVCodec.inx create mode 100644 audio/Soundwire/Samples/SdcaVad/SdcaVCodec/Trace.h create mode 100644 audio/Soundwire/Samples/SdcaVad/SdcaVCodec/capture.cpp create mode 100644 audio/Soundwire/Samples/SdcaVad/SdcaVCodec/device.cpp create mode 100644 audio/Soundwire/Samples/SdcaVad/SdcaVCodec/driver.cpp create mode 100644 audio/Soundwire/Samples/SdcaVad/SdcaVCodec/private.h create mode 100644 audio/Soundwire/Samples/SdcaVad/SdcaVCodec/render.cpp create mode 100644 audio/Soundwire/Samples/SdcaVad/SdcaVCodec/resources.rc create mode 100644 audio/Soundwire/Samples/SdcaVad/SdcaVCodec/streamengine.cpp create mode 100644 audio/Soundwire/Samples/SdcaVad/SdcaVCodec/streamengine.h create mode 100644 audio/Soundwire/Samples/SdcaVad/SdcaVDsp/AcpiReader.cpp create mode 100644 audio/Soundwire/Samples/SdcaVad/SdcaVDsp/AcpiReader.h create mode 100644 audio/Soundwire/Samples/SdcaVad/SdcaVDsp/AudioModule.cpp create mode 100644 audio/Soundwire/Samples/SdcaVad/SdcaVDsp/AudioModule.h create mode 100644 audio/Soundwire/Samples/SdcaVad/SdcaVDsp/CircuitHelper.cpp create mode 100644 audio/Soundwire/Samples/SdcaVad/SdcaVDsp/CircuitHelper.h create mode 100644 audio/Soundwire/Samples/SdcaVad/SdcaVDsp/KeywordDetector.cpp create mode 100644 audio/Soundwire/Samples/SdcaVad/SdcaVDsp/KeywordDetector.h create mode 100644 audio/Soundwire/Samples/SdcaVad/SdcaVDsp/PositionClock.h create mode 100644 audio/Soundwire/Samples/SdcaVad/SdcaVDsp/PositionSimClock.cpp create mode 100644 audio/Soundwire/Samples/SdcaVad/SdcaVDsp/PositionSimClock.h create mode 100644 audio/Soundwire/Samples/SdcaVad/SdcaVDsp/SDCAVDsp.vcxproj create mode 100644 audio/Soundwire/Samples/SdcaVad/SdcaVDsp/SDCAVDsp.vcxproj.Filters create mode 100644 audio/Soundwire/Samples/SdcaVad/SdcaVDsp/SdcaVApo.inx create mode 100644 audio/Soundwire/Samples/SdcaVad/SdcaVDsp/SdcaVDsp.inx create mode 100644 audio/Soundwire/Samples/SdcaVad/SdcaVDsp/SimPeakMeter.cpp create mode 100644 audio/Soundwire/Samples/SdcaVad/SdcaVDsp/SimPeakMeter.h create mode 100644 audio/Soundwire/Samples/SdcaVad/SdcaVDsp/ToneGenerator.cpp create mode 100644 audio/Soundwire/Samples/SdcaVad/SdcaVDsp/ToneGenerator.h create mode 100644 audio/Soundwire/Samples/SdcaVad/SdcaVDsp/Trace.h create mode 100644 audio/Soundwire/Samples/SdcaVad/SdcaVDsp/WaveReader.cpp create mode 100644 audio/Soundwire/Samples/SdcaVad/SdcaVDsp/WaveReader.h create mode 100644 audio/Soundwire/Samples/SdcaVad/SdcaVDsp/capture.cpp create mode 100644 audio/Soundwire/Samples/SdcaVad/SdcaVDsp/circuitstream.cpp create mode 100644 audio/Soundwire/Samples/SdcaVad/SdcaVDsp/device.cpp create mode 100644 audio/Soundwire/Samples/SdcaVad/SdcaVDsp/driver.cpp create mode 100644 audio/Soundwire/Samples/SdcaVad/SdcaVDsp/offloadStreamEngine.cpp create mode 100644 audio/Soundwire/Samples/SdcaVad/SdcaVDsp/offloadStreamEngine.h create mode 100644 audio/Soundwire/Samples/SdcaVad/SdcaVDsp/packages.config create mode 100644 audio/Soundwire/Samples/SdcaVad/SdcaVDsp/private.h create mode 100644 audio/Soundwire/Samples/SdcaVad/SdcaVDsp/render.cpp create mode 100644 audio/Soundwire/Samples/SdcaVad/SdcaVDsp/renderAudioEngine.cpp create mode 100644 audio/Soundwire/Samples/SdcaVad/SdcaVDsp/resources.rc create mode 100644 audio/Soundwire/Samples/SdcaVad/SdcaVDsp/savedata.cpp create mode 100644 audio/Soundwire/Samples/SdcaVad/SdcaVDsp/savedata.h create mode 100644 audio/Soundwire/Samples/SdcaVad/SdcaVDsp/streamengine.cpp create mode 100644 audio/Soundwire/Samples/SdcaVad/SdcaVDsp/streamengine.h create mode 100644 audio/Soundwire/Samples/SdcaVad/SdcaVXu/AudioModule.cpp create mode 100644 audio/Soundwire/Samples/SdcaVad/SdcaVXu/AudioModule.h create mode 100644 audio/Soundwire/Samples/SdcaVad/SdcaVXu/CircuitDevice.cpp create mode 100644 audio/Soundwire/Samples/SdcaVad/SdcaVXu/CircuitDevice.h create mode 100644 audio/Soundwire/Samples/SdcaVad/SdcaVXu/Device.h create mode 100644 audio/Soundwire/Samples/SdcaVad/SdcaVXu/ModuleCircuit.cpp create mode 100644 audio/Soundwire/Samples/SdcaVad/SdcaVXu/ModuleCircuit.h create mode 100644 audio/Soundwire/Samples/SdcaVad/SdcaVXu/SDCAVXu.vcxproj create mode 100644 audio/Soundwire/Samples/SdcaVad/SdcaVXu/SDCAVXu.vcxproj.Filters create mode 100644 audio/Soundwire/Samples/SdcaVad/SdcaVXu/SdcaVXu.inx create mode 100644 audio/Soundwire/Samples/SdcaVad/SdcaVXu/Trace.h create mode 100644 audio/Soundwire/Samples/SdcaVad/SdcaVXu/capture.cpp create mode 100644 audio/Soundwire/Samples/SdcaVad/SdcaVXu/capture.h create mode 100644 audio/Soundwire/Samples/SdcaVad/SdcaVXu/device.cpp create mode 100644 audio/Soundwire/Samples/SdcaVad/SdcaVXu/driver.cpp create mode 100644 audio/Soundwire/Samples/SdcaVad/SdcaVXu/driver.h create mode 100644 audio/Soundwire/Samples/SdcaVad/SdcaVXu/private.h create mode 100644 audio/Soundwire/Samples/SdcaVad/SdcaVXu/render.cpp create mode 100644 audio/Soundwire/Samples/SdcaVad/SdcaVXu/render.h create mode 100644 audio/Soundwire/Samples/SdcaVad/SdcaVXu/resources.rc create mode 100644 audio/Soundwire/Samples/SdcaVad/SdcaVXu/streamengine.cpp create mode 100644 audio/Soundwire/Samples/SdcaVad/SdcaVXu/streamengine.h diff --git a/audio/Soundwire/Documentation/IntroductionToSdca.docx b/audio/Soundwire/Documentation/IntroductionToSdca.docx new file mode 100644 index 0000000000000000000000000000000000000000..1bf9d7dec6a4346336b762780332ec3d19cae9fc GIT binary patch literal 152708 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 SIz#wyA-qjPv +#include + + +//------------------------------------------------------------------------- +// Description: +// +// If the condition evaluates to TRUE, jump to the given label. +// +// Parameters: +// +// condition - [in] code that fits in if statement +// label - [in] label to jump if condition is met +// +#define IF_TRUE_JUMP(condition, label) \ + if (condition) \ + { \ + goto label; \ + } + +//------------------------------------------------------------------------- +// Description: +// +// If the condition evaluates to FALSE, jump to the given label. +// +// Parameters: +// +// condition - [in] code that fits in if statement +// label - [in] label to jump if condition is met +// +#define IF_FALSE_JUMP(condition, label) \ + if (!condition) \ + { \ + goto label; \ + } + +//------------------------------------------------------------------------- +// Description: +// +// If the hresult passed FAILED, jump to the given label. +// +// Parameters: +// +// _hresult - [in] Value to check +// label - [in] label to jump if condition is met +// +#define IF_FAILED_JUMP(_hresult, label) \ + if (FAILED(_hresult)) \ + { \ + goto label; \ + } + +//------------------------------------------------------------------------- +// Description: +// +// If the hresult passed SUCCEEDED, jump to the given label. +// +// Parameters: +// +// _hresult - [in] Value to check +// label - [in] label to jump if condition is met +// +#define IF_SUCCEEDED_JUMP(_hresult, label) \ + if (SUCCEEDED(_hresult)) \ + { \ + goto label; \ + } + +//------------------------------------------------------------------------- +// Description: +// +// If the condition evaluates to TRUE, perform the given statement +// then jump to the given label. +// +// Parameters: +// +// condition - [in] Code that fits in if statement +// action - [in] action to perform in body of if statement +// label - [in] label to jump if condition is met +// +#define IF_TRUE_ACTION_JUMP(condition, action, label) \ + if (condition) \ + { \ + action; \ + goto label; \ + } + +//------------------------------------------------------------------------- +// Description: +// +// If the hresult FAILED, perform the given statement then jump to +// the given label. +// +// Parameters: +// +// _hresult - [in] Value to check +// action - [in] action to perform in body of if statement +// label - [in] label to jump if condition is met +// +#define IF_FAILED_ACTION_JUMP(_hresult, action, label) \ + if (FAILED(_hresult)) \ + { \ + action; \ + goto label; \ + } + +//------------------------------------------------------------------------- +// Description: +// +// Closes a handle and assigns NULL. +// +// Parameters: +// +// h - [in] handle to close +// +#define SAFE_CLOSE_HANDLE(h) \ + if (NULL != h) \ + { \ + CloseHandle(h); \ + h = NULL; \ + } + +//------------------------------------------------------------------------- +// Description: +// +// Addref an interface pointer +// +// Parameters: +// +// p - [in] object to addref +// +#define SAFE_ADDREF(p) \ + if (NULL != p) \ + { \ + (p)->AddRef();; \ + } + +//------------------------------------------------------------------------- +// Description: +// +// Releases an interface pointer and assigns NULL. +// +// Parameters: +// +// p - [in] object to release +// +#define SAFE_RELEASE(p) \ + if (NULL != p) \ + { \ + (p)->Release(); \ + (p) = NULL; \ + } + +//------------------------------------------------------------------------- +// Description: +// +// Deletes a pointer and assigns NULL. Do not check for NULL because +// the default delete operator checks for it. +// +// Parameters: +// +// p - [in] object to delete +// +#define SAFE_DELETE(p) \ + delete p; \ + p = NULL; + +//------------------------------------------------------------------------- +// Description: +// +// Deletes an array pointer and assigns NULL. Do not check for NULL because +// the default delete operator checks for it. +// +// Parameters: +// +// p - [in] Array to delete +// +#define SAFE_DELETE_ARRAY(p) \ + delete [] p; \ + p = NULL; + +//------------------------------------------------------------------------- +// Description: +// +// Frees a block of memory allocated by CoTaskMemAlloc and assigns NULL to +// the pointer +// +// Parameters: +// +// p - [in] Pointer to memory to free +// +#define SAFE_COTASKMEMFREE(p) \ + if (NULL != p) \ + { \ + CoTaskMemFree(p); \ + (p) = NULL; \ + } + +//------------------------------------------------------------------------- +// Description: +// +// Frees a DLL loaded with LoadLibrary and assigns NULL to the handle +// +// Parameters: +// +// h - [in] Handle to DLL to free +// +#define SAFE_FREELIBRARY(h) \ + if (NULL != h) \ + { \ + FreeLibrary(h); \ + (h) = NULL; \ + } + +//------------------------------------------------------------------------- +// Description: +// +// Used to validate a read pointer +// +// Parameters: +// +// p - [in] read pointer. +// s - [in] size of memory in bytes pointed to by p. +// +#define IS_VALID_READ_POINTER(p, s) ((NULL != p) || (0 == s)) + +//------------------------------------------------------------------------- +// Description: +// +// Used to validate a write pointer +// +// Parameters: +// +// p - [in] write pointer. +// s - [in] size of memory in bytes pointed to by p. +// +#define IS_VALID_WRITE_POINTER(p, s) ((NULL != p) || (0 == s)) + +//------------------------------------------------------------------------- +// Description: +// +// Used to validate a read pointer of a particular type +// +// Parameters: +// +// p - [in] typed read pointer +// +#define IS_VALID_TYPED_READ_POINTER(p) IS_VALID_READ_POINTER((p), sizeof *(p)) + +//------------------------------------------------------------------------- +// Description: +// +// Used to validate a write pointer of a particular type +// +// Parameters: +// +// p - [in] typed write pointer +// +#define IS_VALID_TYPED_WRITE_POINTER(p) IS_VALID_WRITE_POINTER((p), sizeof *(p)) + +// --------------------------------------------------------------------------- +// Macros that wrap windows messages. Similar to those in windowsX.h and +// commctrl.h +// +#if !defined Static_SetIcon +#define Static_SetIcon(hwnd, hi) \ + (BOOL)SNDMSG((hwnd), STM_SETIMAGE, (WPARAM)IMAGE_ICON, (LPARAM)(hi)) +#endif + +#define TrackBar_SetTickFrequency(hwnd, f) \ + (BOOL)SNDMSG((hwnd), TBM_SETTICFREQ, (WPARAM)(f), 0) + +#define TrackBar_SetBuddy(hwnd, f, hbud) \ + (HWND)SNDMSG((hwnd), TBM_SETBUDDY, (WPARAM)(f), (LPARAM)hbud) + +#define TrackBar_GetPos(hwnd) \ + (int)SNDMSG((hwnd), TBM_GETPOS, 0, 0) + +#define TrackBar_SetPos(hwnd, pos) \ + SNDMSG((hwnd), TBM_SETPOS, (WPARAM)TRUE, (LPARAM)pos) + +#define TrackBar_SetRange(hwnd, min, max) \ + SNDMSG((hwnd), TBM_SETRANGE , (WPARAM)TRUE, (LPARAM) MAKELONG(min, max)) + +#define TrackBar_SetThumbLength(hwnd, l) \ + SNDMSG((hwnd), TBM_SETTHUMBLENGTH, (WPARAM)l, 0); + +#define TrackBar_SetPageSize(hwnd, n) \ + SNDMSG((hwnd), TBM_SETPAGESIZE, 0, (LPARAM)n) + +#define Window_GetFont(hwnd) \ + (HFONT)SNDMSG((hwnd), WM_GETFONT, 0, 0) + +#define Window_SetFont(hwnd, font) \ + SNDMSG((hwnd), WM_SETFONT, (WPARAM)font, FALSE) + + +// ---------------------------------------------------------------------- +// A struct for holding a rect in easier terms than a RECT struct +// +struct SRECT +{ + int x, y, w, h; + SRECT() + { + x = y = w = h = 0; + } + SRECT(int X, int Y, int W, int H) + { + x = X; y = Y; w = W; h = H; + } + SRECT(RECT* prc) + { + x = prc->left; + y = prc->top; + w = prc->right - prc->left; + h = prc->bottom - prc->top; + } +}; + +#define HNS_PER_SECOND (10ull * 1000ull * 1000ull) diff --git a/audio/Soundwire/Samples/SdcaVad/Apo/inc/CustomPropKeys.h b/audio/Soundwire/Samples/SdcaVad/Apo/inc/CustomPropKeys.h new file mode 100644 index 000000000..dab286a53 --- /dev/null +++ b/audio/Soundwire/Samples/SdcaVad/Apo/inc/CustomPropKeys.h @@ -0,0 +1,39 @@ +// Microsoft Windows +// Copyright (C) Microsoft Corporation. All rights reserved. +// +#pragma once + +// header files for imported files +#include "propidl.h" + +#ifdef DEFINE_PROPERTYKEY +#undef DEFINE_PROPERTYKEY +#endif + +#ifdef INITGUID +#define DEFINE_PROPERTYKEY(name, l, w1, w2, b1, b2, b3, b4, b5, b6, b7, b8, pid) EXTERN_C const PROPERTYKEY name = { { l, w1, w2, { b1, b2, b3, b4, b5, b6, b7, b8 } }, pid } +#else +#define DEFINE_PROPERTYKEY(name, l, w1, w2, b1, b2, b3, b4, b5, b6, b7, b8, pid) EXTERN_C const PROPERTYKEY name +#endif // INITGUID + +// ---------------------------------------------------------------------- +// +// PKEY_Endpoint_Enable_Channel_Swap_SFX: When value is 0x00000001, Channel Swap local effect is enabled +// {A44531EF-5377-4944-AE15-53789A9629C7},2 +// vartype = VT_UI4 +DEFINE_PROPERTYKEY(PKEY_Endpoint_Enable_Channel_Swap_SFX, 0xa44531ef, 0x5377, 0x4944, 0xae, 0x15, 0x53, 0x78, 0x9a, 0x96, 0x29, 0xc7, 2); + +// PKEY_Endpoint_Enable_Channel_Swap_MFX: When value is 0x00000001, Channel Swap global effect is enabled +// {A44531EF-5377-4944-AE15-53789A9629C7},3 +// vartype = VT_UI4 +DEFINE_PROPERTYKEY(PKEY_Endpoint_Enable_Channel_Swap_MFX, 0xa44531ef, 0x5377, 0x4944, 0xae, 0x15, 0x53, 0x78, 0x9a, 0x96, 0x29, 0xc7, 3); + +// PKEY_Endpoint_Enable_Delay_SFX: When value is 0x00000001, Delay local effect is enabled +// {A44531EF-5377-4944-AE15-53789A9629C7},4 +// vartype = VT_UI4 +DEFINE_PROPERTYKEY(PKEY_Endpoint_Enable_Delay_SFX, 0xa44531ef, 0x5377, 0x4944, 0xae, 0x15, 0x53, 0x78, 0x9a, 0x96, 0x29, 0xc7, 4); + +// PKEY_Endpoint_Enable_Delay_MFX: When value is 0x00000001, Delay global effect is enabled +// {A44531EF-5377-4944-AE15-53789A9629C7},5 +// vartype = VT_UI4 +DEFINE_PROPERTYKEY(PKEY_Endpoint_Enable_Delay_MFX, 0xa44531ef, 0x5377, 0x4944, 0xae, 0x15, 0x53, 0x78, 0x9a, 0x96, 0x29, 0xc7, 5); diff --git a/audio/Soundwire/Samples/SdcaVad/Apo/kws/KWSApo.cpp b/audio/Soundwire/Samples/SdcaVad/Apo/kws/KWSApo.cpp new file mode 100644 index 000000000..d67eaca04 --- /dev/null +++ b/audio/Soundwire/Samples/SdcaVad/Apo/kws/KWSApo.cpp @@ -0,0 +1,62 @@ +// +// KWSApo.cpp -- Copyright (c) Microsoft Corporation. All rights reserved. +// +// Description: +// +// Implementation of ProcessBuffer +// +#include +#include +#include +#include +#include + +#include +#include +#include + +#include + +#include "KWSApo.h" + +#pragma AVRT_CODE_BEGIN +void WriteSilence( + _Out_writes_(u32FrameCount * u32SamplesPerFrame) + FLOAT32 *pf32Frames, + UINT32 u32FrameCount, + UINT32 u32SamplesPerFrame ) +{ + ZeroMemory(pf32Frames, sizeof(FLOAT32) * u32FrameCount * u32SamplesPerFrame); +} +#pragma AVRT_CODE_END + +#pragma AVRT_CODE_BEGIN +void ProcessBuffer( + FLOAT32 *pf32OutputFrames, + const FLOAT32 *pf32InputFrames, + UINT32 u32ValidFrameCount, + INTERLEAVED_AUDIO_FORMAT_INFORMATION *formatInfo) +{ + UINT32 totalChannelCount = (formatInfo->PrimaryChannelCount + formatInfo->InterleavedChannelCount); + + ASSERT_REALTIME(); + ATLASSERT( IS_VALID_TYPED_READ_POINTER(pf32InputFrames) ); + ATLASSERT( IS_VALID_TYPED_WRITE_POINTER(pf32OutputFrames) ); + + // loop through samples + while (u32ValidFrameCount--) + { + // copy over the Primary channel data + for (UINT32 i = formatInfo->PrimaryChannelStartPosition; i < (formatInfo->PrimaryChannelStartPosition + formatInfo->PrimaryChannelCount); i++) + { + *pf32OutputFrames = *(pf32InputFrames + i); + pf32OutputFrames++; + } + + // step forward to the next frame, ignoring interleaved data + pf32InputFrames += (totalChannelCount); + } +} + +#pragma AVRT_CODE_END + diff --git a/audio/Soundwire/Samples/SdcaVad/Apo/kws/KWSApo.h b/audio/Soundwire/Samples/SdcaVad/Apo/kws/KWSApo.h new file mode 100644 index 000000000..066d5c5b2 --- /dev/null +++ b/audio/Soundwire/Samples/SdcaVad/Apo/kws/KWSApo.h @@ -0,0 +1,134 @@ +// +// KWSApo.h -- Copyright (c) Microsoft Corporation. All rights reserved. +// +// Description: +// +// Declaration of the CKWSApoEFX class. +// + +#pragma once + +#include +#include +#include +#include + +#include +#include + +_Analysis_mode_(_Analysis_code_type_user_driver_) + +#pragma AVRT_VTABLES_BEGIN +// KWS APO class - EFX +class CKWSApoEFX : + public CComObjectRootEx, + public CComCoClass, + public CBaseAudioProcessingObject, + public IMMNotificationClient, + public IAudioSystemEffects2, + public IKWSApoEFX +{ +public: + // constructor + CKWSApoEFX() + : CBaseAudioProcessingObject(sm_RegProperties) + { + } + + virtual ~CKWSApoEFX(); // destructor + +DECLARE_REGISTRY_RESOURCEID(IDR_KWSAPOEFX) + +BEGIN_COM_MAP(CKWSApoEFX) + COM_INTERFACE_ENTRY(IKWSApoEFX) + COM_INTERFACE_ENTRY(IAudioSystemEffects) + COM_INTERFACE_ENTRY(IAudioSystemEffects2) + COM_INTERFACE_ENTRY(IMMNotificationClient) + COM_INTERFACE_ENTRY(IAudioProcessingObjectRT) + COM_INTERFACE_ENTRY(IAudioProcessingObject) + COM_INTERFACE_ENTRY(IAudioProcessingObjectConfiguration) +END_COM_MAP() + +DECLARE_PROTECT_FINAL_CONSTRUCT() + +public: + STDMETHOD_(void, APOProcess)(UINT32 u32NumInputConnections, + APO_CONNECTION_PROPERTY** ppInputConnections, UINT32 u32NumOutputConnections, + APO_CONNECTION_PROPERTY** ppOutputConnections); + + STDMETHOD(GetLatency)(HNSTIME* pTime); + + STDMETHOD(LockForProcess)(UINT32 u32NumInputConnections, + APO_CONNECTION_DESCRIPTOR** ppInputConnections, + UINT32 u32NumOutputConnections, APO_CONNECTION_DESCRIPTOR** ppOutputConnections); + + STDMETHOD(Initialize)(UINT32 cbDataSize, BYTE* pbyData); + + // IAudioSystemEffects2 + STDMETHOD(GetEffectsList)(_Outptr_result_buffer_maybenull_(*pcEffects) LPGUID *ppEffectsIds, _Out_ UINT *pcEffects, _In_ HANDLE Event); + + // IAudioProcessingObject + STDMETHOD(IsInputFormatSupported)(IAudioMediaType *pOutputFormat, IAudioMediaType *pRequestedInputFormat, IAudioMediaType **ppSupportedInputFormat); + STDMETHOD(IsOutputFormatSupported)(IAudioMediaType *pInputFormat, IAudioMediaType *pRequestedOutputFormat, IAudioMediaType **ppSupportedOutputFormat); + STDMETHOD(GetInputChannelCount)(UINT32 *pu32ChannelCount); + + // IMMNotificationClient + STDMETHODIMP OnDeviceStateChanged(LPCWSTR pwstrDeviceId, DWORD dwNewState) + { + UNREFERENCED_PARAMETER(pwstrDeviceId); + UNREFERENCED_PARAMETER(dwNewState); + return S_OK; + } + STDMETHODIMP OnDeviceAdded(LPCWSTR pwstrDeviceId) + { + UNREFERENCED_PARAMETER(pwstrDeviceId); + return S_OK; + } + STDMETHODIMP OnDeviceRemoved(LPCWSTR pwstrDeviceId) + { + UNREFERENCED_PARAMETER(pwstrDeviceId); + return S_OK; + } + STDMETHODIMP OnDefaultDeviceChanged(EDataFlow flow, ERole role, LPCWSTR pwstrDefaultDeviceId) + { + UNREFERENCED_PARAMETER(flow); + UNREFERENCED_PARAMETER(role); + UNREFERENCED_PARAMETER(pwstrDefaultDeviceId); + return S_OK; + } + STDMETHODIMP OnPropertyValueChanged(LPCWSTR pwstrDeviceId, const PROPERTYKEY key) + { + UNREFERENCED_PARAMETER(pwstrDeviceId); + UNREFERENCED_PARAMETER(key); + return S_OK; + } + +public: + CComPtr m_spAPOSystemEffectsProperties; + CComPtr m_spEnumerator; + static const CRegAPOProperties<1> sm_RegProperties; // registration properties + INTERLEAVED_AUDIO_FORMAT_INFORMATION m_FormatInfo{ 0 }; +}; +#pragma AVRT_VTABLES_END + +OBJECT_ENTRY_AUTO(__uuidof(KWSApoEFX), CKWSApoEFX) + +// +// Declaration of the ProcessBuffer routine. +// +void ProcessBuffer( + FLOAT32 *pf32OutputFrames, + const FLOAT32 *pf32InputFrames, + UINT32 u32ValidFrameCount, + INTERLEAVED_AUDIO_FORMAT_INFORMATION *formatInfo); + +// +// Convenience methods +// + +void WriteSilence( + _Out_writes_(u32FrameCount * u32SamplesPerFrame) + FLOAT32 *pf32Frames, + UINT32 u32FrameCount, + UINT32 u32SamplesPerFrame ); + diff --git a/audio/Soundwire/Samples/SdcaVad/Apo/kws/KWSApo.png b/audio/Soundwire/Samples/SdcaVad/Apo/kws/KWSApo.png new file mode 100644 index 0000000000000000000000000000000000000000..3a6e102a3754e168a90004881a0cb5ef927c95a7 GIT binary patch literal 1191 zcmeAS@N?(olHy`uVBq!ia0vp^NkE*y!3HFsNd!r97I;J!GcfQS0b$0e+I-SLH8!3ujv*CsZ|CeST5KcI*8e@~gV@X%#+^*Z zeOGO{{qR!ezP!`xy_-*8$epd#`&K(5{$O8sYjtA(geM#VY@24@++HzX(vpo+@!Vp2 zuJk}{r+=35cj7$j!cX++aX#Hgm$+Igyhs=7dx@YC% z2{ZSs;VZ6@t}fkKR`R-YtNph*|9<5e=SU~z%0Ew5TE=41#h)gz!Flf1rbnli?5yK1 zeCqS+>CWs!CwRC@U795G>$lgQYT$4Vxx@0TyMSNf_0-2oQ~HkFFuhn+BmHTC?mUkR z$;wN7J=o!Pzh+~@oAo=kZbv~SIwoio?OxiqURU!FeKzyH&pl1mk}({mqv zn)>AR+PZm9Vs0fnfBilE*o(kxucbXr-lG~3i7Nr_LjlFb!!*1zG zYiq81h`L^s=AAaxCS#vSex7*Hshd1=*e`1fN7}DC<~{H2-T-*Hd;i)0s{7CKl+*emY8)t-_oiSbY0 z#&P)7(*^x?la@b?-*cd6;+msp9F~8M`*!Q;?cn~oyZqMAN`JoQp7U)%Q<(!<-}gWB zi)fkqC23J-)_31oe~%yVkau~0sPNAsOmb@?HjAyC$a-EtZ{FRa>z{1O4CVHf zy{Zl<50uaP9k4g}#}1C8_ivbM@c5a3Z8CLi3Cp-2nPk1y`(8)I{lqBEeV?U;9=_he zViC1`+06HRihuai6lyYh73TB1HaG|>1h8<3Ff!4V*?2-eaHhV2y}U&%uyA4UboFyt I=akR{09UmUB>(^b literal 0 HcmV?d00001 diff --git a/audio/Soundwire/Samples/SdcaVad/Apo/kws/KWSApo.vcxproj b/audio/Soundwire/Samples/SdcaVad/Apo/kws/KWSApo.vcxproj new file mode 100644 index 000000000..109280479 --- /dev/null +++ b/audio/Soundwire/Samples/SdcaVad/Apo/kws/KWSApo.vcxproj @@ -0,0 +1,468 @@ + + + + + Debug + Win32 + + + Release + Win32 + + + Debug + x64 + + + Release + x64 + + + Debug + ARM + + + Release + ARM + + + Debug + ARM64 + + + Release + ARM64 + + + + {47358AD6-A48A-465B-965F-0A66F8BDFE23} + $(MSBuildProjectName) + false + true + Debug + Win32 + {D5CCDCB1-348E-414E-BB0F-33C773760034} + $(LatestTargetPlatformVersion) + + + + Windows10 + True + Universal + false + + WindowsApplicationForDrivers10.0 + DynamicLibrary + + + Windows10 + False + Universal + false + + WindowsApplicationForDrivers10.0 + DynamicLibrary + + + Windows10 + True + Universal + false + + WindowsApplicationForDrivers10.0 + DynamicLibrary + + + Windows10 + False + Universal + false + + WindowsApplicationForDrivers10.0 + DynamicLibrary + + + Windows10 + True + Universal + false + + WindowsApplicationForDrivers10.0 + DynamicLibrary + + + Windows10 + False + Universal + false + + WindowsApplicationForDrivers10.0 + DynamicLibrary + + + Windows10 + True + Universal + false + + WindowsApplicationForDrivers10.0 + DynamicLibrary + + + Windows10 + False + Universal + false + + WindowsApplicationForDrivers10.0 + DynamicLibrary + + + + $(IntDir) + + + + + + + + + + + + + + + + + + + + + + + + + + + + SDCAVKwsAPO + + + SDCAVKwsAPO + + + SDCAVKwsAPO + + + SDCAVKwsAPO + + + SDCAVKwsAPO + + + SDCAVKwsAPO + + + SDCAVKwsAPO + + + SDCAVKwsAPO + + + + _DllMainCRTStartup@12 + _DllMainCRTStartup + + + + + _DllMainCRTStartup@12 + _DllMainCRTStartup + + + + + _DllMainCRTStartup@12 + _DllMainCRTStartup + + + + + _DllMainCRTStartup@12 + _DllMainCRTStartup + + + + + _DllMainCRTStartup@12 + _DllMainCRTStartup + + + + + _DllMainCRTStartup@12 + _DllMainCRTStartup + + + + + _DllMainCRTStartup@12 + _DllMainCRTStartup + + + + + _DllMainCRTStartup@12 + _DllMainCRTStartup + + + + Dynamic + + + Dynamic + + + Dynamic + + + Dynamic + + + Dynamic + + + Dynamic + + + Dynamic + + + Dynamic + + + + true + Level4 + %(PreprocessorDefinitions);_WINDLL;_USRDLL;UNICODE;_UNICODE + %(AdditionalIncludeDirectories);$(DDK_INC_PATH);..\inc;..\..\;. + + + + true + + + %(PreprocessorDefinitions);_WINDLL;_USRDLL;UNICODE;_UNICODE + %(AdditionalIncludeDirectories);$(DDK_INC_PATH);..\inc;..\..\ + + + %(PreprocessorDefinitions);_WINDLL;_USRDLL;UNICODE;_UNICODE + %(AdditionalIncludeDirectories);$(DDK_INC_PATH);..\inc;..\..\ + + + %(AdditionalDependencies);Kernel32.lib;ole32.lib;oleaut32.lib;advapi32.lib;user32.lib;uuid.lib;AudioBaseProcessingObjectV140.lib;audiomediatypecrt.lib;AudioEng.lib + KWSApoDll.def + /ignore:4217,4049 %(AdditionalOptions) + + + + + true + Level4 + %(PreprocessorDefinitions);_WINDLL;_USRDLL;UNICODE;_UNICODE + %(AdditionalIncludeDirectories);$(DDK_INC_PATH);..\inc;..\..\;. + + + + + true + + + %(PreprocessorDefinitions);_WINDLL;_USRDLL;UNICODE;_UNICODE + %(AdditionalIncludeDirectories);$(DDK_INC_PATH);..\inc;..\..\ + + + %(PreprocessorDefinitions);_WINDLL;_USRDLL;UNICODE;_UNICODE + %(AdditionalIncludeDirectories);$(DDK_INC_PATH);..\inc;..\..\ + + + %(AdditionalDependencies);Kernel32.lib;ole32.lib;oleaut32.lib;advapi32.lib;user32.lib;uuid.lib;AudioBaseProcessingObjectV140.lib;audiomediatypecrt.lib;AudioEng.lib + KWSApoDll.def + /ignore:4217,4049 %(AdditionalOptions) + + + + + true + Level4 + %(PreprocessorDefinitions);_WINDLL;_USRDLL;UNICODE;_UNICODE + %(AdditionalIncludeDirectories);$(DDK_INC_PATH);..\inc;..\..\;. + + + + true + + + %(PreprocessorDefinitions);_WINDLL;_USRDLL;UNICODE;_UNICODE + %(AdditionalIncludeDirectories);$(DDK_INC_PATH);..\inc;..\..\ + + + %(PreprocessorDefinitions);_WINDLL;_USRDLL;UNICODE;_UNICODE + %(AdditionalIncludeDirectories);$(DDK_INC_PATH);..\inc;..\..\ + + + %(AdditionalDependencies);Kernel32.lib;ole32.lib;oleaut32.lib;advapi32.lib;user32.lib;uuid.lib;AudioBaseProcessingObjectV140.lib;audiomediatypecrt.lib;AudioEng.lib + KWSApoDll.def + /ignore:4217,4049 %(AdditionalOptions) + + + + + true + Level4 + %(PreprocessorDefinitions);_WINDLL;_USRDLL;UNICODE;_UNICODE + %(AdditionalIncludeDirectories);$(DDK_INC_PATH);..\inc;..\..\;. + + + + + true + + + %(PreprocessorDefinitions);_WINDLL;_USRDLL;UNICODE;_UNICODE + %(AdditionalIncludeDirectories);$(DDK_INC_PATH);..\inc;..\..\ + + + %(PreprocessorDefinitions);_WINDLL;_USRDLL;UNICODE;_UNICODE + %(AdditionalIncludeDirectories);$(DDK_INC_PATH);..\inc;..\..\ + + + %(AdditionalDependencies);Kernel32.lib;ole32.lib;oleaut32.lib;advapi32.lib;user32.lib;uuid.lib;AudioBaseProcessingObjectV140.lib;audiomediatypecrt.lib;AudioEng.lib + KWSApoDll.def + /ignore:4217,4049 %(AdditionalOptions) + + + + + true + Level4 + %(PreprocessorDefinitions);_WINDLL;_USRDLL;UNICODE;_UNICODE + %(AdditionalIncludeDirectories);$(DDK_INC_PATH);..\inc;..\..\;. + + + + true + + + %(PreprocessorDefinitions);_WINDLL;_USRDLL;UNICODE;_UNICODE + %(AdditionalIncludeDirectories);$(DDK_INC_PATH);..\inc;..\..\ + + + %(PreprocessorDefinitions);_WINDLL;_USRDLL;UNICODE;_UNICODE + %(AdditionalIncludeDirectories);$(DDK_INC_PATH);..\inc;..\..\ + + + %(AdditionalDependencies);Kernel32.lib;ole32.lib;oleaut32.lib;advapi32.lib;user32.lib;uuid.lib;AudioBaseProcessingObjectV140.lib;audiomediatypecrt.lib;AudioEng.lib + KWSApoDll.def + /ignore:4217,4049 %(AdditionalOptions) + + + + + true + Level4 + %(PreprocessorDefinitions);_WINDLL;_USRDLL;UNICODE;_UNICODE + %(AdditionalIncludeDirectories);$(DDK_INC_PATH);..\inc;..\..\;. + + + + true + + + %(PreprocessorDefinitions);_WINDLL;_USRDLL;UNICODE;_UNICODE + %(AdditionalIncludeDirectories);$(DDK_INC_PATH);..\inc;..\..\ + + + %(PreprocessorDefinitions);_WINDLL;_USRDLL;UNICODE;_UNICODE + %(AdditionalIncludeDirectories);$(DDK_INC_PATH);..\inc;..\..\ + + + %(AdditionalDependencies);Kernel32.lib;ole32.lib;oleaut32.lib;advapi32.lib;user32.lib;uuid.lib;AudioBaseProcessingObjectV140.lib;audiomediatypecrt.lib;AudioEng.lib + KWSApoDll.def + /ignore:4217,4049 %(AdditionalOptions) + + + + + true + Level4 + %(PreprocessorDefinitions);_WINDLL;_USRDLL;UNICODE;_UNICODE + %(AdditionalIncludeDirectories);$(DDK_INC_PATH);..\inc;..\..\;. + + + + true + + + %(PreprocessorDefinitions);_WINDLL;_USRDLL;UNICODE;_UNICODE + %(AdditionalIncludeDirectories);$(DDK_INC_PATH);..\inc;..\..\ + + + %(PreprocessorDefinitions);_WINDLL;_USRDLL;UNICODE;_UNICODE + %(AdditionalIncludeDirectories);$(DDK_INC_PATH);..\inc;..\..\ + + + %(AdditionalDependencies);Kernel32.lib;ole32.lib;oleaut32.lib;advapi32.lib;user32.lib;uuid.lib;AudioBaseProcessingObjectV140.lib;audiomediatypecrt.lib;AudioEng.lib + KWSApoDll.def + /ignore:4217,4049 %(AdditionalOptions) + + + + + true + Level4 + %(PreprocessorDefinitions);_WINDLL;_USRDLL;UNICODE;_UNICODE + %(AdditionalIncludeDirectories);$(DDK_INC_PATH);..\inc;..\..\;. + + + + true + + + %(PreprocessorDefinitions);_WINDLL;_USRDLL;UNICODE;_UNICODE + %(AdditionalIncludeDirectories);$(DDK_INC_PATH);..\inc;..\..\ + + + %(PreprocessorDefinitions);_WINDLL;_USRDLL;UNICODE;_UNICODE + %(AdditionalIncludeDirectories);$(DDK_INC_PATH);..\inc;..\..\ + + + %(AdditionalDependencies);Kernel32.lib;ole32.lib;oleaut32.lib;advapi32.lib;user32.lib;uuid.lib;AudioBaseProcessingObjectV140.lib;audiomediatypecrt.lib;AudioEng.lib + KWSApoDll.def + /ignore:4217,4049 %(AdditionalOptions) + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/audio/Soundwire/Samples/SdcaVad/Apo/kws/KWSApo.vcxproj.Filters b/audio/Soundwire/Samples/SdcaVad/Apo/kws/KWSApo.vcxproj.Filters new file mode 100644 index 000000000..a5710c73d --- /dev/null +++ b/audio/Soundwire/Samples/SdcaVad/Apo/kws/KWSApo.vcxproj.Filters @@ -0,0 +1,21 @@ + + + + + cpp;c;cc;cxx;def;odl;idl;hpj;bat;asm;asmx; + {5f4ff11d-7a0b-4f75-9d79-744ae028cc53} + + + h;hpp;hxx;hm;inl;inc;xsd + {bd103b81-05b2-440e-84e7-0723d7fe4109} + + + rc;ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe;resx;tiff;tif;png;wav;mfcribbon-ms;man;xml + {a57b6b78-38ae-4348-ade3-0200e81a3dd7} + + + idl + {c6318700-1bce-4812-92f1-160881e4c976} + + + diff --git a/audio/Soundwire/Samples/SdcaVad/Apo/kws/KWSApoDll.cpp b/audio/Soundwire/Samples/SdcaVad/Apo/kws/KWSApoDll.cpp new file mode 100644 index 000000000..286b7869f --- /dev/null +++ b/audio/Soundwire/Samples/SdcaVad/Apo/kws/KWSApoDll.cpp @@ -0,0 +1,71 @@ +// +// KWSApoDll.cpp -- Copyright (c) Microsoft Corporation. All rights reserved. +// +// Author: +// +// Description: +// +// KWSApoDll.cpp : Implementation of DLL Exports. + +#include +#include +#include +#include +#include + +#include "resource.h" +#include "KWSApoDll.h" +#include + +#include + + +//------------------------------------------------------------------------- +// Array of APO_REG_PROPERTIES structures implemented in this module. +// Each new APO implementation will be added to this array. +// +APO_REG_PROPERTIES const *gCoreAPOs[] = +{ + &CKWSApoEFX::sm_RegProperties.m_Properties +}; + +// {secret} +class CKWSApoDllModule : public CAtlDllModuleT< CKWSApoDllModule > +{ +public : + DECLARE_LIBID(LIBID_KWSApoDlllib) + DECLARE_REGISTRY_APPID_RESOURCEID(IDR_KWSAPODLL, "{0A21D954-674A-4C09-806E-DB4FBE8F199C}") + +}; + +// {secret} +CKWSApoDllModule _AtlModule; + + +// {secret} +extern "C" BOOL WINAPI DllMain(HINSTANCE /* hInstance */, DWORD dwReason, LPVOID lpReserved) +{ + if (DLL_PROCESS_ATTACH == dwReason) + { + } + // do necessary cleanup only if the DLL is being unloaded dynamically + else if ((DLL_PROCESS_DETACH == dwReason) && (NULL == lpReserved)) + { + } + + return _AtlModule.DllMain(dwReason, lpReserved); +} + + +// {secret} +STDAPI DllCanUnloadNow(void) +{ + return _AtlModule.DllCanUnloadNow(); +} + + +// {secret} +STDAPI DllGetClassObject(_In_ REFCLSID rclsid, _In_ REFIID riid, _Outptr_ LPVOID FAR* ppv) +{ + return _AtlModule.DllGetClassObject(rclsid, riid, ppv); +} diff --git a/audio/Soundwire/Samples/SdcaVad/Apo/kws/KWSApoDll.def b/audio/Soundwire/Samples/SdcaVad/Apo/kws/KWSApoDll.def new file mode 100644 index 000000000..3f3b0c5ab --- /dev/null +++ b/audio/Soundwire/Samples/SdcaVad/Apo/kws/KWSApoDll.def @@ -0,0 +1,4 @@ + +EXPORTS + DllCanUnloadNow PRIVATE + DllGetClassObject PRIVATE \ No newline at end of file diff --git a/audio/Soundwire/Samples/SdcaVad/Apo/kws/KWSApoDll.idl b/audio/Soundwire/Samples/SdcaVad/Apo/kws/KWSApoDll.idl new file mode 100644 index 000000000..086643d7c --- /dev/null +++ b/audio/Soundwire/Samples/SdcaVad/Apo/kws/KWSApoDll.idl @@ -0,0 +1,40 @@ +// +// KWSAPODll.idl -- Copyright (c) Microsoft Corporation. All rights reserved. +// +// Author: +// +// Description: +// +// KWSAPODll.idl : Definition of COM interfaces and coclasses for the DLL. + +import "oaidl.idl"; +import "ocidl.idl"; +import "KWSApoInterface.idl"; + +//------------------------------------------------------------------------- +// KWSApoDlllib +// +[ + uuid(E928E566-CBA7-4181-9B8B-8822E2BD28AB), + version(1.0) +] +library KWSApoDlllib +{ + importlib("stdole2.tlb"); + + // for KWS APO - EFX + [ + uuid(9D89F614-F9D6-40DD-9F21-5E69FA3981ED) + ] + coclass KWSApoEFX + { + interface IAudioProcessingObject; + interface IAudioProcessingObjectRT; + interface IAudioProcessingObjectConfiguration; + interface IMMNotificationClient; + interface IAudioSystemEffects; + [default] interface IKWSApoEFX; + }; + + +} diff --git a/audio/Soundwire/Samples/SdcaVad/Apo/kws/KWSApoDll.rc b/audio/Soundwire/Samples/SdcaVad/Apo/kws/KWSApoDll.rc new file mode 100644 index 000000000..9add84527 --- /dev/null +++ b/audio/Soundwire/Samples/SdcaVad/Apo/kws/KWSApoDll.rc @@ -0,0 +1,20 @@ +#if 0 +Copyright (c) Microsoft Corporation. All Rights Reserved +#endif + +#include "resource.h" +#include "winres.h" +#include +#define VER_FILETYPE VFT_DLL +#define VER_FILESUBTYPE VFT_UNKNOWN +#define VER_FILEDESCRIPTION_STR "KWS APO" +#define VER_INTERNALNAME_STR "KWSApo" +#define VER_ORIGINALFILENAME_STR "KWSApo.Dll" +#include + +IDR_KWSAPODLL REGISTRY "KWSApoDll.rgs" +IDR_KWSAPOEFX REGISTRY "KWSApoEFX.rgs" + +// ICON +IDI_EFFECT_ICON RCDATA "KWSApo.png" + diff --git a/audio/Soundwire/Samples/SdcaVad/Apo/kws/KWSApoDll.rgs b/audio/Soundwire/Samples/SdcaVad/Apo/kws/KWSApoDll.rgs new file mode 100644 index 000000000..b89d2a413 --- /dev/null +++ b/audio/Soundwire/Samples/SdcaVad/Apo/kws/KWSApoDll.rgs @@ -0,0 +1,11 @@ +HKCR +{ + NoRemove AppID + { + '%APPID%' = s 'KWSApoDll' + 'KWSApoDll.DLL' + { + val AppID = s '%APPID%' + } + } +} diff --git a/audio/Soundwire/Samples/SdcaVad/Apo/kws/KWSApoEfx.cpp b/audio/Soundwire/Samples/SdcaVad/Apo/kws/KWSApoEfx.cpp new file mode 100644 index 000000000..5924cd369 --- /dev/null +++ b/audio/Soundwire/Samples/SdcaVad/Apo/kws/KWSApoEfx.cpp @@ -0,0 +1,599 @@ +// +// KWSApoEFX.cpp -- Copyright (c) Microsoft Corporation. All rights reserved. +// +// Description: +// +// Implementation of CKWSApoEFX +// + +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +#include + +#include "KWSApo.h" +#include +#include + +// Static declaration of the APO_REG_PROPERTIES structure +// associated with this APO. The number in <> brackets is the +// number of IIDs supported by this APO. If more than one, then additional +// IIDs are added at the end +#pragma warning (disable : 4815) +const AVRT_DATA CRegAPOProperties<1> CKWSApoEFX::sm_RegProperties( + __uuidof(KWSApoEFX), // clsid of this APO + L"CKWSApoEFX", // friendly name of this APO + L"Copyright (c) Microsoft Corporation", // copyright info + 1, // major version # + 0, // minor version # + __uuidof(IKWSApoEFX), // iid of primary interface + (APO_FLAG) (APO_FLAG_BITSPERSAMPLE_MUST_MATCH | APO_FLAG_FRAMESPERSECOND_MUST_MATCH), + DEFAULT_APOREG_MININPUTCONNECTIONS, + DEFAULT_APOREG_MAXINPUTCONNECTIONS, + DEFAULT_APOREG_MINOUTPUTCONNECTIONS, + DEFAULT_APOREG_MAXOUTPUTCONNECTIONS, + DEFAULT_APOREG_MAXINSTANCES + ); + + +#pragma AVRT_CODE_BEGIN +//------------------------------------------------------------------------- +// Description: +// +// Do the actual processing of data. +// +// Parameters: +// +// u32NumInputConnections - [in] number of input connections +// ppInputConnections - [in] pointer to list of input APO_CONNECTION_PROPERTY pointers +// u32NumOutputConnections - [in] number of output connections +// ppOutputConnections - [in] pointer to list of output APO_CONNECTION_PROPERTY pointers +// +// Return values: +// +// void +// +// Remarks: +// +// This function processes data in a manner dependent on the implementing +// object. This routine can not fail and can not block, or call any other +// routine that blocks, or touch pagable memory. +// +STDMETHODIMP_(void) CKWSApoEFX::APOProcess( + UINT32 u32NumInputConnections, + APO_CONNECTION_PROPERTY** ppInputConnections, + UINT32 u32NumOutputConnections, + APO_CONNECTION_PROPERTY** ppOutputConnections) +{ + UNREFERENCED_PARAMETER(u32NumInputConnections); + UNREFERENCED_PARAMETER(u32NumOutputConnections); + + FLOAT32 *pf32InputFrames, *pf32OutputFrames; + + ATLASSERT(m_bIsLocked); + + // assert that the number of input and output connectins fits our registration properties + ATLASSERT(m_pRegProperties->u32MinInputConnections <= u32NumInputConnections); + ATLASSERT(m_pRegProperties->u32MaxInputConnections >= u32NumInputConnections); + ATLASSERT(m_pRegProperties->u32MinOutputConnections <= u32NumOutputConnections); + ATLASSERT(m_pRegProperties->u32MaxOutputConnections >= u32NumOutputConnections); + + // check APO_BUFFER_FLAGS. + switch( ppInputConnections[0]->u32BufferFlags ) + { + case BUFFER_INVALID: + { + ATLASSERT(false); // invalid flag - should never occur. don't do anything. + break; + } + case BUFFER_VALID: + case BUFFER_SILENT: + { + // get input pointer to connection buffer + pf32InputFrames = reinterpret_cast(ppInputConnections[0]->pBuffer); + ATLASSERT( IS_VALID_TYPED_READ_POINTER(pf32InputFrames) ); + + // get output pointer to connection buffer + pf32OutputFrames = reinterpret_cast(ppOutputConnections[0]->pBuffer); + ATLASSERT( IS_VALID_TYPED_WRITE_POINTER(pf32OutputFrames) ); + + if (BUFFER_SILENT == ppInputConnections[0]->u32BufferFlags) + { + WriteSilence( pf32OutputFrames, + ppInputConnections[0]->u32ValidFrameCount, + GetSamplesPerFrame() ); + } + else + { + ProcessBuffer(pf32OutputFrames, pf32InputFrames, + ppInputConnections[0]->u32ValidFrameCount, + &m_FormatInfo); + + // we don't try to remember silence + ppOutputConnections[0]->u32BufferFlags = BUFFER_VALID; + } + + // Set the valid frame count. + ppOutputConnections[0]->u32ValidFrameCount = ppInputConnections[0]->u32ValidFrameCount; + + break; + } + default: + { + ATLASSERT(false); // invalid flag - should never occur + break; + } + } // switch + +} // APOProcess +#pragma AVRT_CODE_END + +//------------------------------------------------------------------------- +// Description: +// +// Parameters: +// +// pTime - [out] hundreds-of-nanoseconds +// +// Return values: +// +// S_OK on success, a failure code on failure +STDMETHODIMP CKWSApoEFX::GetLatency(HNSTIME* pTime) +{ + ASSERT_NONREALTIME(); + HRESULT hr = S_OK; + + IF_TRUE_ACTION_JUMP(NULL == pTime, hr = E_POINTER, Exit); + + *pTime = 0; + +Exit: + return hr; +} + + +//------------------------------------------------------------------------- +// Description: +// +// Verifies that the APO is ready to process and locks its state if so. +// +// Parameters: +// +// u32NumInputConnections - [in] number of input connections attached to this APO +// ppInputConnections - [in] connection descriptor of each input connection attached to this APO +// u32NumOutputConnections - [in] number of output connections attached to this APO +// ppOutputConnections - [in] connection descriptor of each output connection attached to this APO +// +// Return values: +// +// S_OK Object is locked and ready to process. +// E_POINTER Invalid pointer passed to function. +// APOERR_INVALID_CONNECTION_FORMAT Invalid connection format. +// APOERR_NUM_CONNECTIONS_INVALID Number of input or output connections is not valid on +// this APO. +STDMETHODIMP CKWSApoEFX::LockForProcess(UINT32 u32NumInputConnections, + APO_CONNECTION_DESCRIPTOR** ppInputConnections, + UINT32 u32NumOutputConnections, APO_CONNECTION_DESCRIPTOR** ppOutputConnections) +{ + ASSERT_NONREALTIME(); + HRESULT hr = S_OK; + + UNCOMPRESSEDAUDIOFORMAT uncompAudioFormat; + + // fill in the samples per frame for the output (since APO_FLAG_SAMPLESPERFRAME_MUST_MATCH is not selected) + // There are two potentially different samples per frame values here. The input, which will be interleaved + primary. + // And the output, which is just the primary. Because this is used for clearing the zeroing the output buffer, we're going + // to fill it in with the output samples per frame. ProcessBuffer has both. + hr = ppOutputConnections[0]->pFormat->GetUncompressedAudioFormat(&uncompAudioFormat); + IF_FAILED_JUMP(hr, Exit); + + m_u32SamplesPerFrame = uncompAudioFormat.dwSamplesPerFrame; + + hr = CBaseAudioProcessingObject::LockForProcess(u32NumInputConnections, + ppInputConnections, u32NumOutputConnections, ppOutputConnections); + IF_FAILED_JUMP(hr, Exit); + +Exit: + return hr; +} + +// The method that this long comment refers to is "Initialize()" +//------------------------------------------------------------------------- +// Description: +// +// Generic initialization routine for APOs. +// +// Parameters: +// +// cbDataSize - [in] the size in bytes of the initialization data. +// pbyData - [in] initialization data specific to this APO +// +// Return values: +// +// S_OK Successful completion. +// E_POINTER Invalid pointer passed to this function. +// E_INVALIDARG Invalid argument +// AEERR_ALREADY_INITIALIZED APO is already initialized +// +// Remarks: +// +// This method initializes the APO. The data is variable length and +// should have the form of: +// +// struct MyAPOInitializationData +// { +// APOInitBaseStruct APOInit; +// ... // add additional fields here +// }; +// +// If the APO needs no initialization or needs no data to initialize +// itself, it is valid to pass NULL as the pbyData parameter and 0 as +// the cbDataSize parameter. +// +// As part of designing an APO, decide which parameters should be +// immutable (set once during initialization) and which mutable (changeable +// during the lifetime of the APO instance). Immutable parameters must +// only be specifiable in the Initialize call; mutable parameters must be +// settable via methods on whichever parameter control interface(s) your +// APO provides. Mutable values should either be set in the initialize +// method (if they are required for proper operation of the APO prior to +// LockForProcess) or default to reasonable values upon initialize and not +// be required to be set before LockForProcess. +// +// Within the mutable parameters, you must also decide which can be changed +// while the APO is locked for processing and which cannot. +// +// All parameters should be considered immutable as a first choice, unless +// there is a specific scenario which requires them to be mutable; similarly, +// no mutable parameters should be changeable while the APO is locked, unless +// a specific scenario requires them to be. Following this guideline will +// simplify the APO's state diagram and implementation and prevent certain +// types of bug. +// +// If a parameter changes the APOs latency or MaxXXXFrames values, it must be +// immutable. +// +// The default version of this function uses no initialization data, but does verify +// the passed parameters and set the m_bIsInitialized member to true. +// +// Note: This method may not be called from a real-time processing thread. +// + +HRESULT CKWSApoEFX::Initialize(UINT32 cbDataSize, BYTE* pbyData) +{ + HRESULT hr = S_OK; + CComPtr spMyDevice; + CComPtr spMyDeviceTopology; + CComPtr spMyConnector; + CComPtr spPart; + UINT myPartId; + CComPtr spKsControl; + ULONG cbReturned = 0; + + IF_TRUE_ACTION_JUMP( ((NULL == pbyData) && (0 != cbDataSize)), hr = E_INVALIDARG, Exit); + IF_TRUE_ACTION_JUMP( ((NULL != pbyData) && (0 == cbDataSize)), hr = E_INVALIDARG, Exit); + + if (cbDataSize == sizeof(APOInitSystemEffects2)) + { + // + // Initialize for mode-specific signal processing + // + APOInitSystemEffects2* papoSysFxInit2 = (APOInitSystemEffects2*)pbyData; + KSP_PIN ksPinProperty; + + // Save reference to the effects property store. This saves effects settings + // and is the communication medium between this APO and any associated UI. + m_spAPOSystemEffectsProperties = papoSysFxInit2->pAPOSystemEffectsProperties; + + // Windows should pass a valid collection. + ATLASSERT(papoSysFxInit2->pDeviceCollection != nullptr); + IF_TRUE_ACTION_JUMP(papoSysFxInit2->pDeviceCollection == nullptr, hr = E_INVALIDARG, Exit); + + // Get the IDeviceTopology and IConnector interfaces to communicate with this + // APO's counterpart audio driver. This can be used for any proprietary + // communication. + hr = papoSysFxInit2->pDeviceCollection->Item(papoSysFxInit2->nSoftwareIoDeviceInCollection, &spMyDevice); + IF_FAILED_JUMP(hr, Exit); + + hr = spMyDevice->Activate(__uuidof(IKsControl), CLSCTX_ALL, NULL, (void**)&spKsControl); + IF_FAILED_JUMP(hr, Exit); + + hr = spMyDevice->Activate(__uuidof(IDeviceTopology), CLSCTX_ALL, NULL, (void**)&spMyDeviceTopology); + IF_FAILED_JUMP(hr, Exit); + + hr = spMyDeviceTopology->GetConnector(papoSysFxInit2->nSoftwareIoConnectorIndex, &spMyConnector); + IF_FAILED_JUMP(hr, Exit); + + spPart = spMyConnector; + + hr = spPart->GetLocalId(&myPartId); + IF_FAILED_JUMP(hr, Exit); + + ::ZeroMemory(&ksPinProperty, sizeof(ksPinProperty)); + ksPinProperty.Property.Set = KSPROPSETID_InterleavedAudio; + ksPinProperty.Property.Id = KSPROPERTY_INTERLEAVEDAUDIO_FORMATINFORMATION; + ksPinProperty.Property.Flags = KSPROPERTY_TYPE_GET; + ksPinProperty.PinId = myPartId & 0x0000ffff; + + ::ZeroMemory(&m_FormatInfo, sizeof(m_FormatInfo)); + + hr = spKsControl->KsProperty(&(ksPinProperty.Property), sizeof(ksPinProperty), &m_FormatInfo, sizeof(m_FormatInfo), &cbReturned); + IF_FAILED_JUMP(hr, Exit); + + IF_TRUE_ACTION_JUMP( m_FormatInfo.Size != sizeof(m_FormatInfo), hr = E_INVALIDARG, Exit); + } + else + { + // Invalid initialization size + hr = E_INVALIDARG; + goto Exit; + } + + // + // Register for notification of registry updates + // + hr = m_spEnumerator.CoCreateInstance(__uuidof(MMDeviceEnumerator)); + IF_FAILED_JUMP(hr, Exit); + + hr = m_spEnumerator->RegisterEndpointNotificationCallback(this); + IF_FAILED_JUMP(hr, Exit); + + m_bIsInitialized = true; + + +Exit: + return hr; +} + +//------------------------------------------------------------------------- +// Description: +// +// +// +// Parameters: +// +// +// +// Return values: +// +// +// +// Remarks: +// +// +STDMETHODIMP CKWSApoEFX::GetEffectsList(_Outptr_result_buffer_maybenull_(*pcEffects) LPGUID *ppEffectsIds, _Out_ UINT *pcEffects, _In_ HANDLE Event) +{ + UNREFERENCED_PARAMETER(Event); + + *ppEffectsIds = NULL; + *pcEffects = 0; + + return S_OK; +} + +//------------------------------------------------------------------------- +// Description: +// +// +// +// Parameters: +// +// +// +// Return values: +// +// +// +// Remarks: +// +// +STDMETHODIMP CKWSApoEFX::IsInputFormatSupported(IAudioMediaType *pOutputFormat, IAudioMediaType *pRequestedInputFormat, IAudioMediaType **ppSupportedInputFormat) +{ + ASSERT_NONREALTIME(); + bool formatChanged = false; + HRESULT hResult; + UNCOMPRESSEDAUDIOFORMAT uncompInputFormat; + IAudioMediaType *recommendedFormat = NULL; + UINT totalChannelCount = (m_FormatInfo.PrimaryChannelCount + m_FormatInfo.InterleavedChannelCount); + + IF_TRUE_ACTION_JUMP((NULL == pRequestedInputFormat) || (NULL == ppSupportedInputFormat), hResult = E_POINTER, Exit); + *ppSupportedInputFormat = NULL; + + // Initial comparison to make sure the requested format is valid and consistent with the output + // format. Because of the APO flags specified during creation, the samples per frame value will + // not be validated. + hResult = IsFormatTypeSupported( pOutputFormat, pRequestedInputFormat, &recommendedFormat, true ); + IF_FAILED_JUMP(hResult, Exit); + + // If the input format is changed, make sure we track it for our return code. + if (S_FALSE == hResult) + { + formatChanged = true; + } + + // now retrieve the format that IsFormatTypeSupported decided on, building upon that by adding + // our channel count constraint. + hResult = recommendedFormat->GetUncompressedAudioFormat(&uncompInputFormat); + IF_FAILED_JUMP(hResult, Exit); + + // the expected input channel count, for interleaved audio, is the total number of channels + // reported in the interleaved format information. Fail any request for a format that doesn't + // meet that requirement. + if (uncompInputFormat.dwSamplesPerFrame != totalChannelCount) + { + hResult = APOERR_FORMAT_NOT_SUPPORTED; + goto Exit; + } + + // If the requested format exactly matched our requirements, + // just return it. + if(!formatChanged) + { + *ppSupportedInputFormat = pRequestedInputFormat; + (*ppSupportedInputFormat)->AddRef(); + hResult = S_OK; + } + else // we're proposing something different than the input, copy it and return S_FALSE; + { + hResult = CreateAudioMediaTypeFromUncompressedAudioFormat(&uncompInputFormat, ppSupportedInputFormat); + IF_FAILED_JUMP(hResult, Exit); + + hResult = S_FALSE; + } + +Exit: + + if (recommendedFormat) + { + recommendedFormat->Release(); + } + + return hResult; +} + +//------------------------------------------------------------------------- +// Description: +// +// +// +// Parameters: +// +// +// +// Return values: +// +// +// +// Remarks: +// +// +STDMETHODIMP CKWSApoEFX::IsOutputFormatSupported(IAudioMediaType *pInputFormat, IAudioMediaType *pRequestedOutputFormat, IAudioMediaType **ppSupportedOutputFormat) +{ + ASSERT_NONREALTIME(); + bool formatChanged = false; + HRESULT hResult; + UNCOMPRESSEDAUDIOFORMAT uncompOutputFormat; + IAudioMediaType *recommendedFormat = NULL; + + IF_TRUE_ACTION_JUMP((NULL == pRequestedOutputFormat) || (NULL == ppSupportedOutputFormat), hResult = E_POINTER, Exit); + *ppSupportedOutputFormat = NULL; + + // Initial comparison to make sure the requested format is valid and consistent with the input + // format. Because of the APO flags specified during creation, the samples per frame value will + // not be validated. + hResult = IsFormatTypeSupported( pInputFormat, pRequestedOutputFormat, &recommendedFormat, true ); + IF_FAILED_JUMP(hResult, Exit); + + // If the output format is changed, make sure we track it for our return code. + if (S_FALSE == hResult) + { + formatChanged = true; + } + + // now retrieve the format that IsFormatTypeSupported decided on, building upon that by adding + // our channel count constraint. + hResult = recommendedFormat->GetUncompressedAudioFormat(&uncompOutputFormat); + IF_FAILED_JUMP(hResult, Exit); + + // The expected output channel count is the number of primary channels in the interleaved data. + // We're removing the interleaved data. + if (uncompOutputFormat.dwSamplesPerFrame != m_FormatInfo.PrimaryChannelCount) + { + uncompOutputFormat.dwSamplesPerFrame = m_FormatInfo.PrimaryChannelCount; + uncompOutputFormat.dwChannelMask = m_FormatInfo.PrimaryChannelMask; + formatChanged = true; + } + + // If the requested format exactly matched our requirements, + // just return it. + if(!formatChanged) + { + *ppSupportedOutputFormat = pRequestedOutputFormat; + (*ppSupportedOutputFormat)->AddRef(); + hResult = S_OK; + } + else // we're proposing something different, copy it and return S_FALSE; + { + hResult = CreateAudioMediaTypeFromUncompressedAudioFormat(&uncompOutputFormat, ppSupportedOutputFormat); + IF_FAILED_JUMP(hResult, Exit); + hResult = S_FALSE; + } + +Exit: + + if (recommendedFormat) + { + recommendedFormat->Release(); + } + + return hResult; +} + +//------------------------------------------------------------------------- +// Description: +// +// +// +// Parameters: +// +// +// +// Return values: +// +// +// +// Remarks: +// +// +STDMETHODIMP CKWSApoEFX::GetInputChannelCount(UINT32 *pu32ChannelCount) +{ + ASSERT_NONREALTIME(); + HRESULT hResult = S_OK; + + IF_TRUE_ACTION_JUMP(!m_bIsInitialized, hResult = APOERR_NOT_INITIALIZED, Exit); + IF_TRUE_ACTION_JUMP(NULL == pu32ChannelCount, hResult = E_POINTER, Exit); + + // the input channel count is always the sum of the primary and interleaved + *pu32ChannelCount = (m_FormatInfo.PrimaryChannelCount + m_FormatInfo.InterleavedChannelCount); + +Exit: + return hResult; +} // GetChannelCount + +//------------------------------------------------------------------------- +// Description: +// +// Destructor. +// +// Parameters: +// +// void +// +// Return values: +// +// void +// +// Remarks: +// +// This method deletes whatever was allocated. +// +// This method may not be called from a real-time processing thread. +// +CKWSApoEFX::~CKWSApoEFX(void) +{ + // + // unregister for callbacks + // + if (m_bIsInitialized) + { + m_spEnumerator->UnregisterEndpointNotificationCallback(this); + } +} // ~CKWSApoEFX diff --git a/audio/Soundwire/Samples/SdcaVad/Apo/kws/KWSApoEfx.rgs b/audio/Soundwire/Samples/SdcaVad/Apo/kws/KWSApoEfx.rgs new file mode 100644 index 000000000..dc5bd41a6 --- /dev/null +++ b/audio/Soundwire/Samples/SdcaVad/Apo/kws/KWSApoEfx.rgs @@ -0,0 +1,13 @@ +HKCR +{ + NoRemove CLSID + { + ForceRemove {9D89F614-F9D6-40DD-9F21-5E69FA3981ED} = s 'KWSApoEFX Class' + { + InprocServer32 = s '%MODULE%' + { + val ThreadingModel = s 'Both' + } + } + } +} diff --git a/audio/Soundwire/Samples/SdcaVad/Apo/kws/KWSApoInterface.idl b/audio/Soundwire/Samples/SdcaVad/Apo/kws/KWSApoInterface.idl new file mode 100644 index 000000000..6c0c98ad1 --- /dev/null +++ b/audio/Soundwire/Samples/SdcaVad/Apo/kws/KWSApoInterface.idl @@ -0,0 +1,21 @@ +// +// KWSApoInterface.idl -- Copyright (c) Microsoft Corporation. All rights reserved. +// +// Description: +// +// The interface and type definitions for KWS APO functionality. +// +import "oaidl.idl"; +import "ocidl.idl"; +import "audioenginebaseapo.idl"; + + +[ + object, + uuid(CF5C2AA7-68A8-4FD1-B86F-EBC008AD1B6F), + pointer_default(unique) +] +interface IKWSApoEFX : IUnknown +{ +}; + diff --git a/audio/Soundwire/Samples/SdcaVad/Apo/kws/Resource.h b/audio/Soundwire/Samples/SdcaVad/Apo/kws/Resource.h new file mode 100644 index 000000000..df1260e19 --- /dev/null +++ b/audio/Soundwire/Samples/SdcaVad/Apo/kws/Resource.h @@ -0,0 +1,19 @@ +//{{NO_DEPENDENCIES}} +// Microsoft Visual C++ generated include file. +// Used by KWSApoDll.rc +// +#define IDS_PROJNAME 100 +#define IDR_KWSAPODLL 101 +#define IDR_KWSAPOEFX 110 +#define IDI_EFFECT_ICON 200 + +// Next default values for new objects +// +#ifdef APSTUDIO_INVOKED +#ifndef APSTUDIO_READONLY_SYMBOLS +#define _APS_NEXT_RESOURCE_VALUE 201 +#define _APS_NEXT_COMMAND_VALUE 32768 +#define _APS_NEXT_CONTROL_VALUE 201 +#define _APS_NEXT_SYMED_VALUE 132 +#endif +#endif diff --git a/audio/Soundwire/Samples/SdcaVad/Common/NewDelete.cpp b/audio/Soundwire/Samples/SdcaVad/Common/NewDelete.cpp new file mode 100644 index 000000000..a94733640 --- /dev/null +++ b/audio/Soundwire/Samples/SdcaVad/Common/NewDelete.cpp @@ -0,0 +1,149 @@ +/***************************************************************************** +* NewDelete.cpp - CPP placement new and delete operators implementation +***************************************************************************** +* Copyright (c) Microsoft Corporation All Rights Reserved +* +* Module Name: +* +* NewDelete.cpp +* +* Abstract: +* +* Definition of placement new and delete operators. +* +*/ + +#ifdef _NEW_DELETE_OPERATORS_ + +#include "NewDelete.h" + +#pragma code_seg() +/***************************************************************************** +* Functions +*/ + +/***************************************************************************** +* ::new() +***************************************************************************** +* New function for creating objects with a specified allocation tag. +*/ +PVOID operator new +( + size_t iSize, + POOL_FLAGS poolFlags, + ULONG tag +) +{ + PVOID result = ExAllocatePool2(poolFlags, iSize, tag); + + return result; +} + + +/***************************************************************************** +* ::new() +***************************************************************************** +* New function for creating objects with a specified allocation tag. +*/ +PVOID operator new +( + size_t iSize, + POOL_FLAGS poolFlags +) +{ + PVOID result = ExAllocatePool2(poolFlags, iSize, DEFAULT_POOLTAG); + + return result; +} + + +/***************************************************************************** +* ::delete() +***************************************************************************** +* Delete with tag function. +*/ +void __cdecl operator delete +( + PVOID pVoid, + ULONG tag +) +{ + if (pVoid) + { + ExFreePoolWithTag(pVoid, tag); + } +} + + +/***************************************************************************** +* ::delete() +***************************************************************************** +* Sized Delete function. +*/ +void __cdecl operator delete +( + _Pre_maybenull_ __drv_freesMem(Mem) PVOID pVoid, + _In_ size_t cbSize +) +{ + UNREFERENCED_PARAMETER(cbSize); + + if (pVoid) + { + ExFreePool(pVoid); + } +} + +/***************************************************************************** +* ::delete() +***************************************************************************** +* Sized Delete function. +*/ +void __cdecl operator delete +( + PVOID pVoid +) +{ + if (pVoid) + { + ExFreePool(pVoid); + } +} + + +/***************************************************************************** +* ::delete() +***************************************************************************** +* Sized Array Delete function. +*/ +void __cdecl operator delete[] +( + _Pre_maybenull_ __drv_freesMem(Mem) PVOID pVoid, + _In_ size_t cbSize +) +{ + UNREFERENCED_PARAMETER(cbSize); + + if (pVoid) + { + ExFreePool(pVoid); + } +} + + +/***************************************************************************** +* ::delete() +***************************************************************************** +* Array Delete function. +*/ +void __cdecl operator delete[] +( + _Pre_maybenull_ __drv_freesMem(Mem) PVOID pVoid +) +{ + if (pVoid) + { + ExFreePool(pVoid); + } +} +#endif//_NEW_DELETE_OPERATORS_ diff --git a/audio/Soundwire/Samples/SdcaVad/EventDetectorAdapter/EventDetectorContosoAdapter.cpp b/audio/Soundwire/Samples/SdcaVad/EventDetectorAdapter/EventDetectorContosoAdapter.cpp new file mode 100644 index 000000000..8a8ec90db --- /dev/null +++ b/audio/Soundwire/Samples/SdcaVad/EventDetectorAdapter/EventDetectorContosoAdapter.cpp @@ -0,0 +1,211 @@ +// Copyright (C) Microsoft Corporation. All rights reserved. +#include "stdafx.h" + +#include +#include + +#include +#include "EventDetectorContosoAdapter.h" +#include "ContosoEventDetector.h" +#include + +using namespace Microsoft::WRL; + +class EventDetectorContosoAdapter : public RuntimeClass, IEventDetectorOemAdapter> +{ +public: + EventDetectorContosoAdapter() + { + } + + STDMETHODIMP GetCapabilities( + _Out_ EVENTFEATURES* GlobalFeatureSupport, + _Outptr_ LANGID** LangIds, + _Out_ ULONG* NumLanguages, + _Out_ ULONG* NumUserRecordings, + _Outptr_ WAVEFORMATEX** ppFormat) + { + const WAVEFORMATEX waveFormat = { WAVE_FORMAT_PCM, 1, 16000, 32000, 2, 16, 0 }; + + *ppFormat = (WAVEFORMATEX *)CoTaskMemAlloc(sizeof(WAVEFORMATEX)); + if (*ppFormat == nullptr) + { + return E_OUTOFMEMORY; + } + + memcpy(*ppFormat, &waveFormat, sizeof(WAVEFORMATEX)); + + *GlobalFeatureSupport = EVENTFEATURES_NoEventFeatures; + + *LangIds = (LANGID *)CoTaskMemAlloc(sizeof(LANGID)); + + if (*LangIds == nullptr) + { + return E_OUTOFMEMORY; + } + + **LangIds = 0x0409; + *NumLanguages = 1; + *NumUserRecordings = 0; + + return S_OK; + } + + STDMETHODIMP GetCapabilitiesForLanguage( + _In_ LANGID LangId, + _Outptr_ DETECTIONEVENT** EventIds, + _Out_ ULONG* NumEvents) + { + if (LangId == 0x0409) + { + DETECTIONEVENT events[] = { { CONTOSO_KEYWORD1, EVENTFEATURES_NoEventFeatures, {0}, L"Contoso 1", TRUE }, + { CONTOSO_KEYWORD2, EVENTFEATURES_NoEventFeatures, {0}, L"Contoso 2", TRUE } }; + + *EventIds = (DETECTIONEVENT *)CoTaskMemAlloc(sizeof(events)); + if (*EventIds == nullptr) + { + return E_OUTOFMEMORY; + } + + memcpy(*EventIds, &events, sizeof(events)); + *NumEvents = 2; + } + else + { + return E_INVALIDARG; + } + + return S_OK; + } + + STDMETHODIMP VerifyUserEventData( + _In_ IStream* ModelData, + _In_ WAVEFORMATEX* UserRecording, + _In_ DETECTIONEVENTSELECTOR EventSelector, + _In_ LONG EventEndBytePos) + { + UNREFERENCED_PARAMETER(ModelData); + UNREFERENCED_PARAMETER(UserRecording); + UNREFERENCED_PARAMETER(EventSelector); + UNREFERENCED_PARAMETER(EventEndBytePos); + + return E_NOTIMPL; + } + + STDMETHODIMP ComputeAndAddUserModelData( + _Inout_ IStream* ModelData, + _In_ DETECTIONEVENTSELECTOR EventSelector, + _In_ LONG* EventEndBytePos, + _In_ WAVEFORMATEX** UserRecordings, + _In_ ULONG NumUserRecordings) + { + UNREFERENCED_PARAMETER(ModelData); + UNREFERENCED_PARAMETER(EventSelector); + UNREFERENCED_PARAMETER(EventEndBytePos); + UNREFERENCED_PARAMETER(UserRecordings); + UNREFERENCED_PARAMETER(NumUserRecordings); + + return E_NOTIMPL; + } + + STDMETHODIMP BuildArmingPatternData( + _In_ IStream* UserModelData, + _In_ DETECTIONEVENTSELECTOR* EventSelectors, + _In_ ULONG NumEventSelectors, + _Outptr_ SOUNDDETECTOR_PATTERNHEADER** ppPatternData) + { + CONTOSO_KEYWORDCONFIGURATION *pPatternData = nullptr; + + UNREFERENCED_PARAMETER(UserModelData); + + if (NumEventSelectors > 2) + { + return E_INVALIDARG; + } + + if ((EventSelectors[0].Event.EventId != CONTOSO_KEYWORD1 && EventSelectors[0].Event.EventId != CONTOSO_KEYWORD2) || + (EventSelectors[0].UserId != 0) || (EventSelectors[0].LangId != 0x0409)) + { + return E_INVALIDARG; + } + + pPatternData = (CONTOSO_KEYWORDCONFIGURATION*)CoTaskMemAlloc(sizeof(CONTOSO_KEYWORDCONFIGURATION)); + if (pPatternData == nullptr) + { + return E_OUTOFMEMORY; + } + + pPatternData->Header.Size = sizeof(*pPatternData); + pPatternData->Header.PatternType = CONTOSO_KEYWORDCONFIGURATION_IDENTIFIER2; + pPatternData->ContosoDetectorConfigurationData = 0x12345678; + + *ppPatternData = &pPatternData->Header; + pPatternData = nullptr; + + return S_OK; + } + + STDMETHODIMP ParseDetectionResultData( + _In_ IStream* UserModelData, + _In_ SOUNDDETECTOR_PATTERNHEADER* Result, + _Outptr_ SOUNDDETECTOR_PATTERNHEADER** AssistantContext, + _Out_ DETECTIONEVENTSELECTOR* EventSelector, + _Out_ EVENTACTION* EventAction, + _Out_ ULONG64* EventStartPerformanceCounterValue, + _Out_ ULONG64* EventEndPerformanceCounterValue, + _Outptr_ WCHAR** DebugOutput) + { + const CONTOSO_KEYWORDDETECTIONRESULT *contosoResult; + + UNREFERENCED_PARAMETER(UserModelData); + + if (Result->PatternType != CONTOSO_KEYWORDCONFIGURATION_IDENTIFIER2 || Result->Size < sizeof(CONTOSO_KEYWORDDETECTIONRESULT)) + { + return E_INVALIDARG; + } + + contosoResult = (CONTOSO_KEYWORDDETECTIONRESULT*)Result; + + if (CONTOSO_KEYWORD1 == contosoResult->EventId) + { + wcscpy_s(EventSelector->Event.DisplayName, L"Contoso 1"); + } + else if (CONTOSO_KEYWORD2 == contosoResult->EventId) + { + wcscpy_s(EventSelector->Event.DisplayName, L"Contoso 2"); + } + else + { + return E_INVALIDARG; + } + + // Fill in event action information for the actual detection, based on what has been armed. + EventSelector->Event.EventId = contosoResult->EventId; + EventSelector->Armed = TRUE; + EventSelector->UserId = 0; + EventSelector->LangId = 0x0409; + + EventAction->EventdActionType = EVENTACTIONTYPE_Accept; + EventAction->EventActionContextType = EVENTACTIONCONTEXTTYPE_None; + + // Retrieve the event start/stop times for the payload + *EventStartPerformanceCounterValue = contosoResult->KeywordStartTimestamp; + *EventEndPerformanceCounterValue = contosoResult->KeywordStopTimestamp; + + *AssistantContext = nullptr; + *DebugOutput = nullptr; + + return S_OK; + } + + STDMETHODIMP_(void) ReportOSDetectionResult( + _In_ DETECTIONEVENTSELECTOR EventSelector, + _In_ EVENTACTION EventAction) + { + UNREFERENCED_PARAMETER(EventSelector); + UNREFERENCED_PARAMETER(EventAction); + } +}; + +CoCreatableClass(EventDetectorContosoAdapter); + diff --git a/audio/Soundwire/Samples/SdcaVad/EventDetectorAdapter/EventDetectorContosoAdapter.def b/audio/Soundwire/Samples/SdcaVad/EventDetectorAdapter/EventDetectorContosoAdapter.def new file mode 100644 index 000000000..ddeb3eac1 --- /dev/null +++ b/audio/Soundwire/Samples/SdcaVad/EventDetectorAdapter/EventDetectorContosoAdapter.def @@ -0,0 +1,6 @@ +LIBRARY + +EXPORTS + DllGetActivationFactory PRIVATE + DllGetClassObject PRIVATE + DllCanUnloadNow PRIVATE diff --git a/audio/Soundwire/Samples/SdcaVad/EventDetectorAdapter/EventDetectorContosoAdapter.idl b/audio/Soundwire/Samples/SdcaVad/EventDetectorAdapter/EventDetectorContosoAdapter.idl new file mode 100644 index 000000000..9289fba5e --- /dev/null +++ b/audio/Soundwire/Samples/SdcaVad/EventDetectorAdapter/EventDetectorContosoAdapter.idl @@ -0,0 +1,20 @@ +// Copyright (C) Microsoft Corporation. All rights reserved. + +import "oaidl.idl"; +import "ocidl.idl"; + +import "EventDetectorOemAdapter.idl"; + +[uuid(33C3430A-6199-4C55-831F-30EDF9D644EB), version(1.0)] +library EventDetectorContosoAdapterLib +{ + // The class's uuid (i.e. the COM CLSID) must match that of the pattern + // type GUID returned by the audio driver + // 0x207f3d0c, 0x5c79, 0x496f, 0xa9, 0x4c, 0xd3, 0xd2, 0x93, 0x4d, 0xbf, 0xa9 + [uuid(207F3D0C-5C79-496F-A94C-D3D2934DBFA9), version(1.0)] + coclass EventDetectorContosoAdapter + { + [default] interface IEventDetectorOemAdapter; + } +}; + diff --git a/audio/Soundwire/Samples/SdcaVad/EventDetectorAdapter/EventDetectorContosoAdapter.rc b/audio/Soundwire/Samples/SdcaVad/EventDetectorAdapter/EventDetectorContosoAdapter.rc new file mode 100644 index 000000000..d871dd723 --- /dev/null +++ b/audio/Soundwire/Samples/SdcaVad/EventDetectorAdapter/EventDetectorContosoAdapter.rc @@ -0,0 +1,13 @@ +#if 0 +Copyright (c) Microsoft Corporation. All Rights Reserved +#endif + +#include "winres.h" +#include +#define VER_FILETYPE VFT_DLL +#define VER_FILESUBTYPE VFT_UNKNOWN +#define VER_FILEDESCRIPTION_STR "Contoso event detector adapter" +#define VER_INTERNALNAME_STR "EventDetectorContosoAdapter" +#define VER_ORIGINALFILENAME_STR "EventDetectorContosoAdapter.dll" +#include + \ No newline at end of file diff --git a/audio/Soundwire/Samples/SdcaVad/EventDetectorAdapter/EventDetectorContosoAdapter.vcxproj b/audio/Soundwire/Samples/SdcaVad/EventDetectorAdapter/EventDetectorContosoAdapter.vcxproj new file mode 100644 index 000000000..102311008 --- /dev/null +++ b/audio/Soundwire/Samples/SdcaVad/EventDetectorAdapter/EventDetectorContosoAdapter.vcxproj @@ -0,0 +1,433 @@ + + + + + Debug + Win32 + + + Release + Win32 + + + Debug + x64 + + + Release + x64 + + + Debug + ARM + + + Release + ARM + + + Debug + ARM64 + + + Release + ARM64 + + + + {E0F02048-78A4-4FE8-B863-66E6CB6A2C37} + $(MSBuildProjectName) + false + true + Debug + Win32 + {C1851F27-BDAA-4DF1-8CA5-9D0176FD2B33} + $(LatestTargetPlatformVersion) + + + + Windows10 + True + Universal + + WindowsApplicationForDrivers10.0 + DynamicLibrary + + + Windows10 + False + Universal + + WindowsApplicationForDrivers10.0 + DynamicLibrary + + + Windows10 + True + Universal + + WindowsApplicationForDrivers10.0 + DynamicLibrary + + + Windows10 + False + Universal + + WindowsApplicationForDrivers10.0 + DynamicLibrary + + + Windows10 + True + Universal + + WindowsApplicationForDrivers10.0 + DynamicLibrary + + + Windows10 + False + Universal + + WindowsApplicationForDrivers10.0 + DynamicLibrary + + + Windows10 + True + Universal + + WindowsApplicationForDrivers10.0 + DynamicLibrary + + + Windows10 + False + Universal + + WindowsApplicationForDrivers10.0 + DynamicLibrary + + + + $(IntDir) + + + + + + + + + + + + + + + + + + + + + + + + + + + + EventDetectorContosoAdapter + + + EventDetectorContosoAdapter + + + EventDetectorContosoAdapter + + + EventDetectorContosoAdapter + + + EventDetectorContosoAdapter + + + EventDetectorContosoAdapter + + + EventDetectorContosoAdapter + + + EventDetectorContosoAdapter + + + + _DllMainCRTStartup@12 + _DllMainCRTStartup + + + + + _DllMainCRTStartup@12 + _DllMainCRTStartup + + + + + _DllMainCRTStartup@12 + _DllMainCRTStartup + + + + + _DllMainCRTStartup@12 + _DllMainCRTStartup + + + + + _DllMainCRTStartup@12 + _DllMainCRTStartup + + + + + _DllMainCRTStartup@12 + _DllMainCRTStartup + + + + + _DllMainCRTStartup@12 + _DllMainCRTStartup + + + + + _DllMainCRTStartup@12 + _DllMainCRTStartup + + + + + MultiThreaded + MultiThreadedDebug + true + Level4 + %(PreprocessorDefinitions);_WINDLL;_USRDLL;UNICODE;_UNICODE + %(AdditionalIncludeDirectories);$(DDK_INC_PATH);..\inc;..\ + + + true + + + %(PreprocessorDefinitions);_WINDLL;_USRDLL;UNICODE;_UNICODE + %(AdditionalIncludeDirectories);$(DDK_INC_PATH);..\inc;..\ + + + %(PreprocessorDefinitions);_WINDLL;_USRDLL;UNICODE;_UNICODE + %(AdditionalIncludeDirectories);$(DDK_INC_PATH);..\inc;..\ + + + %(AdditionalDependencies);Kernel32.lib;ole32.lib;oleaut32.lib;advapi32.lib;user32.lib;uuid.lib;mfplat.lib;runtimeobject.lib + EventDetectorContosoAdapter.def + + + + + MultiThreaded + MultiThreadedDebug + true + Level4 + %(PreprocessorDefinitions);_WINDLL;_USRDLL;UNICODE;_UNICODE + %(AdditionalIncludeDirectories);$(DDK_INC_PATH);..\inc;..\ + + + true + + + %(PreprocessorDefinitions);_WINDLL;_USRDLL;UNICODE;_UNICODE + %(AdditionalIncludeDirectories);$(DDK_INC_PATH);..\inc;..\ + + + %(PreprocessorDefinitions);_WINDLL;_USRDLL;UNICODE;_UNICODE + %(AdditionalIncludeDirectories);$(DDK_INC_PATH);..\inc;..\ + + + %(AdditionalDependencies);Kernel32.lib;ole32.lib;oleaut32.lib;advapi32.lib;user32.lib;uuid.lib;mfplat.lib;runtimeobject.lib + EventDetectorContosoAdapter.def + + + + + MultiThreaded + MultiThreadedDebug + true + Level4 + %(PreprocessorDefinitions);_WINDLL;_USRDLL;UNICODE;_UNICODE + %(AdditionalIncludeDirectories);$(DDK_INC_PATH);..\inc;..\ + + + true + + + %(PreprocessorDefinitions);_WINDLL;_USRDLL;UNICODE;_UNICODE + %(AdditionalIncludeDirectories);$(DDK_INC_PATH);..\inc;..\ + + + %(PreprocessorDefinitions);_WINDLL;_USRDLL;UNICODE;_UNICODE + %(AdditionalIncludeDirectories);$(DDK_INC_PATH);..\inc;..\ + + + %(AdditionalDependencies);Kernel32.lib;ole32.lib;oleaut32.lib;advapi32.lib;user32.lib;uuid.lib;mfplat.lib;runtimeobject.lib + EventDetectorContosoAdapter.def + + + + + MultiThreaded + MultiThreadedDebug + true + Level4 + %(PreprocessorDefinitions);_WINDLL;_USRDLL;UNICODE;_UNICODE + %(AdditionalIncludeDirectories);$(DDK_INC_PATH);..\inc;..\ + + + true + + + %(PreprocessorDefinitions);_WINDLL;_USRDLL;UNICODE;_UNICODE + %(AdditionalIncludeDirectories);$(DDK_INC_PATH);..\inc;..\ + + + %(PreprocessorDefinitions);_WINDLL;_USRDLL;UNICODE;_UNICODE + %(AdditionalIncludeDirectories);$(DDK_INC_PATH);..\inc;..\ + + + %(AdditionalDependencies);Kernel32.lib;ole32.lib;oleaut32.lib;advapi32.lib;user32.lib;uuid.lib;mfplat.lib;runtimeobject.lib + EventDetectorContosoAdapter.def + + + + + MultiThreaded + MultiThreadedDebug + true + Level4 + %(PreprocessorDefinitions);_WINDLL;_USRDLL;UNICODE;_UNICODE + %(AdditionalIncludeDirectories);$(DDK_INC_PATH);..\inc;..\ + + + true + + + %(PreprocessorDefinitions);_WINDLL;_USRDLL;UNICODE;_UNICODE + %(AdditionalIncludeDirectories);$(DDK_INC_PATH);..\inc;..\ + + + %(PreprocessorDefinitions);_WINDLL;_USRDLL;UNICODE;_UNICODE + %(AdditionalIncludeDirectories);$(DDK_INC_PATH);..\inc;..\ + + + %(AdditionalDependencies);Kernel32.lib;ole32.lib;oleaut32.lib;advapi32.lib;user32.lib;uuid.lib;mfplat.lib;runtimeobject.lib + EventDetectorContosoAdapter.def + + + + + MultiThreaded + MultiThreadedDebug + true + Level4 + %(PreprocessorDefinitions);_WINDLL;_USRDLL;UNICODE;_UNICODE + %(AdditionalIncludeDirectories);$(DDK_INC_PATH);..\inc;..\ + + + true + + + %(PreprocessorDefinitions);_WINDLL;_USRDLL;UNICODE;_UNICODE + %(AdditionalIncludeDirectories);$(DDK_INC_PATH);..\inc;..\ + + + %(PreprocessorDefinitions);_WINDLL;_USRDLL;UNICODE;_UNICODE + %(AdditionalIncludeDirectories);$(DDK_INC_PATH);..\inc;..\ + + + %(AdditionalDependencies);Kernel32.lib;ole32.lib;oleaut32.lib;advapi32.lib;user32.lib;uuid.lib;mfplat.lib;runtimeobject.lib + EventDetectorContosoAdapter.def + + + + + MultiThreaded + MultiThreadedDebug + true + Level4 + %(PreprocessorDefinitions);_WINDLL;_USRDLL;UNICODE;_UNICODE + %(AdditionalIncludeDirectories);$(DDK_INC_PATH);..\inc;..\ + + + true + + + %(PreprocessorDefinitions);_WINDLL;_USRDLL;UNICODE;_UNICODE + %(AdditionalIncludeDirectories);$(DDK_INC_PATH);..\inc;..\ + + + %(PreprocessorDefinitions);_WINDLL;_USRDLL;UNICODE;_UNICODE + %(AdditionalIncludeDirectories);$(DDK_INC_PATH);..\inc;..\ + + + %(AdditionalDependencies);Kernel32.lib;ole32.lib;oleaut32.lib;advapi32.lib;user32.lib;uuid.lib;mfplat.lib;runtimeobject.lib + EventDetectorContosoAdapter.def + + + + + MultiThreaded + MultiThreadedDebug + true + Level4 + %(PreprocessorDefinitions);_WINDLL;_USRDLL;UNICODE;_UNICODE + %(AdditionalIncludeDirectories);$(DDK_INC_PATH);..\inc;..\ + + + true + + + %(PreprocessorDefinitions);_WINDLL;_USRDLL;UNICODE;_UNICODE + %(AdditionalIncludeDirectories);$(DDK_INC_PATH);..\inc;..\ + + + %(PreprocessorDefinitions);_WINDLL;_USRDLL;UNICODE;_UNICODE + %(AdditionalIncludeDirectories);$(DDK_INC_PATH);..\inc;..\ + + + %(AdditionalDependencies);Kernel32.lib;ole32.lib;oleaut32.lib;advapi32.lib;user32.lib;uuid.lib;mfplat.lib;runtimeobject.lib + EventDetectorContosoAdapter.def + + + + + + + + + + + + + + + + + + + + + + + diff --git a/audio/Soundwire/Samples/SdcaVad/EventDetectorAdapter/EventDetectorContosoAdapter.vcxproj.Filters b/audio/Soundwire/Samples/SdcaVad/EventDetectorAdapter/EventDetectorContosoAdapter.vcxproj.Filters new file mode 100644 index 000000000..6637ef91b --- /dev/null +++ b/audio/Soundwire/Samples/SdcaVad/EventDetectorAdapter/EventDetectorContosoAdapter.vcxproj.Filters @@ -0,0 +1,17 @@ + + + + + cpp;c;cc;cxx;def;odl;idl;hpj;bat;asm;asmx;* + {75C3E63D-04EA-4EB3-B9F1-505497C51F71} + + + h;hpp;hxx;hm;inl;inc;xsd + {5E8F8D4C-2886-4109-9E78-CE5EA6DB4AC4} + + + rc;ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe;resx;tiff;tif;png;wav;mfcribbon-ms;man;xml + {75D3016A-4ED8-4FAF-9817-394BE1112534} + + + diff --git a/audio/Soundwire/Samples/SdcaVad/EventDetectorAdapter/dllmain.cpp b/audio/Soundwire/Samples/SdcaVad/EventDetectorAdapter/dllmain.cpp new file mode 100644 index 000000000..39c15c7b5 --- /dev/null +++ b/audio/Soundwire/Samples/SdcaVad/EventDetectorAdapter/dllmain.cpp @@ -0,0 +1,35 @@ +// Copyright (C) Microsoft Corporation. All rights reserved. +#include "stdafx.h" +#include + +using namespace Microsoft::WRL; + +#if !defined(__WRL_CLASSIC_COM__) +STDAPI DllGetActivationFactory(_In_ HSTRING activatibleClassId, _COM_Outptr_ IActivationFactory** factory) +{ + return Module::GetModule().GetActivationFactory(activatibleClassId, factory); +} +#endif + +#if !defined(__WRL_WINRT_STRICT__) +_Check_return_ +STDAPI DllGetClassObject(_In_ REFCLSID rclsid, _In_ REFIID riid, _Outptr_ LPVOID FAR* ppv) +{ + return Module::GetModule().GetClassObject(rclsid, riid, ppv); +} +#endif + +__control_entrypoint(DllExport) +STDAPI DllCanUnloadNow() +{ + return Module::GetModule().Terminate() ? S_OK : S_FALSE; +} + +STDAPI_(BOOL) DllMain(_In_ HINSTANCE hinst, DWORD reason, _In_opt_ void*) +{ + if (reason == DLL_PROCESS_ATTACH) + { + DisableThreadLibraryCalls(hinst); + } + return TRUE; +} diff --git a/audio/Soundwire/Samples/SdcaVad/EventDetectorAdapter/stdafx.cpp b/audio/Soundwire/Samples/SdcaVad/EventDetectorAdapter/stdafx.cpp new file mode 100644 index 000000000..cf76439a3 --- /dev/null +++ b/audio/Soundwire/Samples/SdcaVad/EventDetectorAdapter/stdafx.cpp @@ -0,0 +1,6 @@ +// Copyright (C) Microsoft Corporation. All rights reserved. + +#include "stdafx.h" + +// TODO: reference any additional headers you need in STDAFX.H +// and not in this file diff --git a/audio/Soundwire/Samples/SdcaVad/EventDetectorAdapter/stdafx.h b/audio/Soundwire/Samples/SdcaVad/EventDetectorAdapter/stdafx.h new file mode 100644 index 000000000..6ed512c84 --- /dev/null +++ b/audio/Soundwire/Samples/SdcaVad/EventDetectorAdapter/stdafx.h @@ -0,0 +1,13 @@ +// Copyright (C) Microsoft Corporation. All rights reserved. + +#pragma once + +#include "targetver.h" + +//#define WIN32_LEAN_AND_MEAN // Exclude rarely-used stuff from Windows headers +// Windows Header Files: +#include + + + +// TODO: reference additional headers your program requires here diff --git a/audio/Soundwire/Samples/SdcaVad/EventDetectorAdapter/targetver.h b/audio/Soundwire/Samples/SdcaVad/EventDetectorAdapter/targetver.h new file mode 100644 index 000000000..847309f28 --- /dev/null +++ b/audio/Soundwire/Samples/SdcaVad/EventDetectorAdapter/targetver.h @@ -0,0 +1,10 @@ +// Copyright (C) Microsoft Corporation. All rights reserved. + +#pragma once + +// Including SDKDDKVer.h defines the highest available Windows platform. + +// If you wish to build your application for a previous Windows platform, include WinSDKVer.h and +// set the _WIN32_WINNT macro to the platform you wish to support before including SDKDDKVer.h. + +#include diff --git a/audio/Soundwire/Samples/SdcaVad/Inc/AudioFormats.h b/audio/Soundwire/Samples/SdcaVad/Inc/AudioFormats.h new file mode 100644 index 000000000..d93dcf19b --- /dev/null +++ b/audio/Soundwire/Samples/SdcaVad/Inc/AudioFormats.h @@ -0,0 +1,623 @@ +/*++ + +Copyright (c) Microsoft Corporation. All rights reserved. + + THIS CODE AND INFORMATION IS PROVIDED "AS IS" WITHOUT WARRANTY OF ANY + KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE + IMPLIED WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A PARTICULAR + PURPOSE. + +Module Name: + + AudioFormats.h + +Abstract: + + Contains Audio formats supported for the SDCAVad Device + +Environment: + + Kernel mode + +--*/ + +#pragma once + +// +// Basic-testing formats. +// +static +KSDATAFORMAT_WAVEFORMATEXTENSIBLE Pcm44100c2 = +{ + { + sizeof(KSDATAFORMAT_WAVEFORMATEXTENSIBLE), + 0, + 0, + 0, + STATICGUIDOF(KSDATAFORMAT_TYPE_AUDIO), + STATICGUIDOF(KSDATAFORMAT_SUBTYPE_PCM), + STATICGUIDOF(KSDATAFORMAT_SPECIFIER_WAVEFORMATEX) + }, + { + { + WAVE_FORMAT_EXTENSIBLE, + 2, + 44100, + 176400, + 4, + 16, + sizeof(WAVEFORMATEXTENSIBLE)-sizeof(WAVEFORMATEX) + }, + 16, + KSAUDIO_SPEAKER_STEREO, + STATICGUIDOF(KSDATAFORMAT_SUBTYPE_PCM) + } +}; + +static +KSDATAFORMAT_WAVEFORMATEXTENSIBLE Pcm44100c2nomask = +{ + { + sizeof(KSDATAFORMAT_WAVEFORMATEXTENSIBLE), + 0, + 0, + 0, + STATICGUIDOF(KSDATAFORMAT_TYPE_AUDIO), + STATICGUIDOF(KSDATAFORMAT_SUBTYPE_PCM), + STATICGUIDOF(KSDATAFORMAT_SPECIFIER_WAVEFORMATEX) + }, + { + { + WAVE_FORMAT_EXTENSIBLE, + 2, + 44100, + 176400, + 4, + 16, + sizeof(WAVEFORMATEXTENSIBLE)-sizeof(WAVEFORMATEX) + }, + 16, + 0, + STATICGUIDOF(KSDATAFORMAT_SUBTYPE_PCM) + } +}; + +static +KSDATAFORMAT_WAVEFORMATEXTENSIBLE Pcm48000c2 = +{ + { + sizeof(KSDATAFORMAT_WAVEFORMATEXTENSIBLE), + 0, + 0, + 0, + STATICGUIDOF(KSDATAFORMAT_TYPE_AUDIO), + STATICGUIDOF(KSDATAFORMAT_SUBTYPE_PCM), + STATICGUIDOF(KSDATAFORMAT_SPECIFIER_WAVEFORMATEX) + }, + { + { + WAVE_FORMAT_EXTENSIBLE, + 2, + 48000, + 192000, + 4, + 16, + sizeof(WAVEFORMATEXTENSIBLE) - sizeof(WAVEFORMATEX) + }, + 16, + KSAUDIO_SPEAKER_STEREO, + STATICGUIDOF(KSDATAFORMAT_SUBTYPE_PCM) + } +}; + +static +KSDATAFORMAT_WAVEFORMATEXTENSIBLE Pcm44100c2_24in32 = +{ + { + sizeof(KSDATAFORMAT_WAVEFORMATEXTENSIBLE), + 0, + 0, + 0, + STATICGUIDOF(KSDATAFORMAT_TYPE_AUDIO), + STATICGUIDOF(KSDATAFORMAT_SUBTYPE_PCM), + STATICGUIDOF(KSDATAFORMAT_SPECIFIER_WAVEFORMATEX) + }, + { + { + WAVE_FORMAT_EXTENSIBLE, + 2, + 44100, + 352800, + 8, + 32, + sizeof(WAVEFORMATEXTENSIBLE) - sizeof(WAVEFORMATEX) + }, + 24, + KSAUDIO_SPEAKER_STEREO, + STATICGUIDOF(KSDATAFORMAT_SUBTYPE_PCM) + } +}; + +static +KSDATAFORMAT_WAVEFORMATEXTENSIBLE Pcm44100c2_24in32_nomask = +{ + { + sizeof(KSDATAFORMAT_WAVEFORMATEXTENSIBLE), + 0, + 0, + 0, + STATICGUIDOF(KSDATAFORMAT_TYPE_AUDIO), + STATICGUIDOF(KSDATAFORMAT_SUBTYPE_PCM), + STATICGUIDOF(KSDATAFORMAT_SPECIFIER_WAVEFORMATEX) + }, + { + { + WAVE_FORMAT_EXTENSIBLE, + 2, + 44100, + 352800, + 8, + 32, + sizeof(WAVEFORMATEXTENSIBLE) - sizeof(WAVEFORMATEX) + }, + 24, + 0, + STATICGUIDOF(KSDATAFORMAT_SUBTYPE_PCM) + } +}; + +static +KSDATAFORMAT_WAVEFORMATEXTENSIBLE Pcm48000c2_24in32 = +{ + { + sizeof(KSDATAFORMAT_WAVEFORMATEXTENSIBLE), + 0, + 0, + 0, + STATICGUIDOF(KSDATAFORMAT_TYPE_AUDIO), + STATICGUIDOF(KSDATAFORMAT_SUBTYPE_PCM), + STATICGUIDOF(KSDATAFORMAT_SPECIFIER_WAVEFORMATEX) + }, + { + { + WAVE_FORMAT_EXTENSIBLE, + 2, + 48000, + 384000, + 8, + 32, + sizeof(WAVEFORMATEXTENSIBLE) - sizeof(WAVEFORMATEX) + }, + 24, + KSAUDIO_SPEAKER_STEREO, + STATICGUIDOF(KSDATAFORMAT_SUBTYPE_PCM) + } +}; + +static +KSDATAFORMAT_WAVEFORMATEXTENSIBLE Pcm48000c2_24in32_nomask = +{ + { + sizeof(KSDATAFORMAT_WAVEFORMATEXTENSIBLE), + 0, + 0, + 0, + STATICGUIDOF(KSDATAFORMAT_TYPE_AUDIO), + STATICGUIDOF(KSDATAFORMAT_SUBTYPE_PCM), + STATICGUIDOF(KSDATAFORMAT_SPECIFIER_WAVEFORMATEX) + }, + { + { + WAVE_FORMAT_EXTENSIBLE, + 2, + 48000, + 384000, + 8, + 32, + sizeof(WAVEFORMATEXTENSIBLE) - sizeof(WAVEFORMATEX) + }, + 24, + 0, + STATICGUIDOF(KSDATAFORMAT_SUBTYPE_PCM) + } +}; + +// No Mask version is used for 2ch Capture, where the mask is not meaningful +static +KSDATAFORMAT_WAVEFORMATEXTENSIBLE Pcm48000c2nomask = +{ + { + sizeof(KSDATAFORMAT_WAVEFORMATEXTENSIBLE), + 0, + 0, + 0, + STATICGUIDOF(KSDATAFORMAT_TYPE_AUDIO), + STATICGUIDOF(KSDATAFORMAT_SUBTYPE_PCM), + STATICGUIDOF(KSDATAFORMAT_SPECIFIER_WAVEFORMATEX) + }, + { + { + WAVE_FORMAT_EXTENSIBLE, + 2, + 48000, + 192000, + 4, + 16, + sizeof(WAVEFORMATEXTENSIBLE) - sizeof(WAVEFORMATEX) + }, + 16, + 0, + STATICGUIDOF(KSDATAFORMAT_SUBTYPE_PCM) + } +}; + + +static +KSDATAFORMAT_WAVEFORMATEXTENSIBLE Pcm96000c2_24in32 = +{ + { + sizeof(KSDATAFORMAT_WAVEFORMATEXTENSIBLE), + 0, + 0, + 0, + STATICGUIDOF(KSDATAFORMAT_TYPE_AUDIO), + STATICGUIDOF(KSDATAFORMAT_SUBTYPE_PCM), + STATICGUIDOF(KSDATAFORMAT_SPECIFIER_WAVEFORMATEX) + }, + { + { + WAVE_FORMAT_EXTENSIBLE, + 2, + 96000, + 768000, + 8, + 32, + sizeof(WAVEFORMATEXTENSIBLE) - sizeof(WAVEFORMATEX) + }, + 24, + KSAUDIO_SPEAKER_STEREO, + STATICGUIDOF(KSDATAFORMAT_SUBTYPE_PCM) + } +}; + +static +KSDATAFORMAT_WAVEFORMATEXTENSIBLE Pcm96000c2 = +{ + { + sizeof(KSDATAFORMAT_WAVEFORMATEXTENSIBLE), + 0, + 0, + 0, + STATICGUIDOF(KSDATAFORMAT_TYPE_AUDIO), + STATICGUIDOF(KSDATAFORMAT_SUBTYPE_PCM), + STATICGUIDOF(KSDATAFORMAT_SPECIFIER_WAVEFORMATEX) + }, + { + { + WAVE_FORMAT_EXTENSIBLE, + 2, + 96000, + 384000, + 4, + 16, + sizeof(WAVEFORMATEXTENSIBLE) - sizeof(WAVEFORMATEX) + }, + 16, + KSAUDIO_SPEAKER_STEREO, + STATICGUIDOF(KSDATAFORMAT_SUBTYPE_PCM) + } +}; + +static +KSDATAFORMAT_WAVEFORMATEXTENSIBLE Pcm192000c2_24in32 = +{ + { + sizeof(KSDATAFORMAT_WAVEFORMATEXTENSIBLE), + 0, + 0, + 0, + STATICGUIDOF(KSDATAFORMAT_TYPE_AUDIO), + STATICGUIDOF(KSDATAFORMAT_SUBTYPE_PCM), + STATICGUIDOF(KSDATAFORMAT_SPECIFIER_WAVEFORMATEX) + }, + { + { + WAVE_FORMAT_EXTENSIBLE, + 2, + 192000, + 1536000, + 8, + 32, + sizeof(WAVEFORMATEXTENSIBLE) - sizeof(WAVEFORMATEX) + }, + 24, + KSAUDIO_SPEAKER_STEREO, + STATICGUIDOF(KSDATAFORMAT_SUBTYPE_PCM) + } +}; + +static +KSDATAFORMAT_WAVEFORMATEXTENSIBLE Pcm192000c2 = +{ + { + sizeof(KSDATAFORMAT_WAVEFORMATEXTENSIBLE), + 0, + 0, + 0, + STATICGUIDOF(KSDATAFORMAT_TYPE_AUDIO), + STATICGUIDOF(KSDATAFORMAT_SUBTYPE_PCM), + STATICGUIDOF(KSDATAFORMAT_SPECIFIER_WAVEFORMATEX) + }, + { + { + WAVE_FORMAT_EXTENSIBLE, + 2, + 192000, + 768000, + 4, + 16, + sizeof(WAVEFORMATEXTENSIBLE) - sizeof(WAVEFORMATEX) + }, + 16, + KSAUDIO_SPEAKER_STEREO, + STATICGUIDOF(KSDATAFORMAT_SUBTYPE_PCM) + } +}; + + + +static +KSDATAFORMAT_WAVEFORMATEXTENSIBLE Pcm44100c1 = +{ + { + sizeof(KSDATAFORMAT_WAVEFORMATEXTENSIBLE), + 0, + 0, + 0, + STATICGUIDOF(KSDATAFORMAT_TYPE_AUDIO), + STATICGUIDOF(KSDATAFORMAT_SUBTYPE_PCM), + STATICGUIDOF(KSDATAFORMAT_SPECIFIER_WAVEFORMATEX) + }, + { + { + WAVE_FORMAT_EXTENSIBLE, + 1, + 44100, + 88200, + 2, + 16, + sizeof(WAVEFORMATEXTENSIBLE) - sizeof(WAVEFORMATEX) + }, + 16, + KSAUDIO_SPEAKER_MONO, + STATICGUIDOF(KSDATAFORMAT_SUBTYPE_PCM) + } +}; + +static +KSDATAFORMAT_WAVEFORMATEXTENSIBLE Pcm48000c1 = +{ + { + sizeof(KSDATAFORMAT_WAVEFORMATEXTENSIBLE), + 0, + 0, + 0, + STATICGUIDOF(KSDATAFORMAT_TYPE_AUDIO), + STATICGUIDOF(KSDATAFORMAT_SUBTYPE_PCM), + STATICGUIDOF(KSDATAFORMAT_SPECIFIER_WAVEFORMATEX) + }, + { + { + WAVE_FORMAT_EXTENSIBLE, + 1, + 48000, + 96000, + 2, + 16, + sizeof(WAVEFORMATEXTENSIBLE) - sizeof(WAVEFORMATEX) + }, + 16, + KSAUDIO_SPEAKER_MONO, + STATICGUIDOF(KSDATAFORMAT_SUBTYPE_PCM) + } +}; + +static +KSDATAFORMAT_WAVEFORMATEXTENSIBLE Pcm48000c4nomask = +{ + { + sizeof(KSDATAFORMAT_WAVEFORMATEXTENSIBLE), + 0, + 0, + 0, + STATICGUIDOF(KSDATAFORMAT_TYPE_AUDIO), + STATICGUIDOF(KSDATAFORMAT_SUBTYPE_PCM), + STATICGUIDOF(KSDATAFORMAT_SPECIFIER_WAVEFORMATEX) + }, + { + { + WAVE_FORMAT_EXTENSIBLE, + 4, + 48000, + 384000, + 8, + 16, + sizeof(WAVEFORMATEXTENSIBLE) - sizeof(WAVEFORMATEX) + }, + 16, + 0, + STATICGUIDOF(KSDATAFORMAT_SUBTYPE_PCM) + } +}; + +static +KSDATAFORMAT_WAVEFORMATEXTENSIBLE Pcm16000c2nomask = +{ + { + sizeof(KSDATAFORMAT_WAVEFORMATEXTENSIBLE), + 0, + 0, + 0, + STATICGUIDOF(KSDATAFORMAT_TYPE_AUDIO), + STATICGUIDOF(KSDATAFORMAT_SUBTYPE_PCM), + STATICGUIDOF(KSDATAFORMAT_SPECIFIER_WAVEFORMATEX) + }, + { + { + WAVE_FORMAT_EXTENSIBLE, + 2, + 16000, + 64000, + 4, + 16, + sizeof(WAVEFORMATEXTENSIBLE) - sizeof(WAVEFORMATEX) + }, + 16, + 0, + STATICGUIDOF(KSDATAFORMAT_SUBTYPE_PCM) + } +}; + +static +KSDATAFORMAT_WAVEFORMATEXTENSIBLE Pcm16000c4nomask = +{ + { + sizeof(KSDATAFORMAT_WAVEFORMATEXTENSIBLE), + 0, + 0, + 0, + STATICGUIDOF(KSDATAFORMAT_TYPE_AUDIO), + STATICGUIDOF(KSDATAFORMAT_SUBTYPE_PCM), + STATICGUIDOF(KSDATAFORMAT_SPECIFIER_WAVEFORMATEX) + }, + { + { + WAVE_FORMAT_EXTENSIBLE, + 4, + 16000, + 128000, + 8, + 16, + sizeof(WAVEFORMATEXTENSIBLE) - sizeof(WAVEFORMATEX) + }, + 16, + 0, + STATICGUIDOF(KSDATAFORMAT_SUBTYPE_PCM) + } +}; + +PAGED_CODE_SEG +inline +NTSTATUS +SdcaVad_RetrieveOrCreateDataFormatList( + _In_ ACXPIN Pin, + _In_ PGUID Mode, + _Out_ ACXDATAFORMATLIST * FormatList +) +{ + PAGED_CODE(); + + // Note: AcxPinGetRawDataFormatList will do the same thing as AcxPinRetrieveModeDataFormatList(RAW) + NTSTATUS status = AcxPinRetrieveModeDataFormatList(Pin, Mode, FormatList); + if (!NT_SUCCESS(status)) + { + WDF_OBJECT_ATTRIBUTES attributes; + WDF_OBJECT_ATTRIBUTES_INIT(&attributes); + attributes.ParentObject = Pin; + + ACX_DATAFORMAT_LIST_CONFIG config; + ACX_DATAFORMAT_LIST_CONFIG_INIT(&config); + + status = AcxDataFormatListCreate(AcxCircuitGetWdfDevice(AcxPinGetCircuit(Pin)), &attributes, &config, FormatList); + + if (NT_SUCCESS(status)) + { + status = AcxPinAssignModeDataFormatList(Pin, Mode, *FormatList); + } + } + return status; +} + +PAGED_CODE_SEG +inline +NTSTATUS +SdcaVad_ClearDataFormatList( + _In_ ACXDATAFORMATLIST FormatList +) +{ + PAGED_CODE(); + + ACX_DATAFORMAT_LIST_ITERATOR formatIter; + ACXDATAFORMAT format; + ACXDATAFORMAT formatToDelete = nullptr; + NTSTATUS status = STATUS_SUCCESS; + + // The AcxDataFormatListRemoveDataFormats API is not available in ACX 1.0 + ACX_DATAFORMAT_LIST_ITERATOR_INIT(&formatIter); + AcxDataFormatListBeginIteration(FormatList, &formatIter); + status = AcxDataFormatListRetrieveNextFormat(FormatList, &formatIter, &format); + while (NT_SUCCESS(status) && format != nullptr) + { + // We can delete this format after we've retrieved the next format + formatToDelete = format; + status = AcxDataFormatListRetrieveNextFormat(FormatList, &formatIter, &format); + if (!NT_SUCCESS(status)) + { + format = nullptr; + } + + status = AcxDataFormatListRemoveDataFormat(FormatList, formatToDelete); + if (!NT_SUCCESS(status)) + { + break; + } + + } + AcxDataFormatListEndIteration(FormatList, &formatIter); + + if (status == STATUS_NO_MORE_ENTRIES) + { + status = STATUS_SUCCESS; + } + + return status; +} + +PAGED_CODE_SEG +inline +NTSTATUS +SdcaVad_CopyFormats( + _In_ ACXDATAFORMATLIST SourceList, + _In_ ACXDATAFORMATLIST DestinationList, + _Out_ PULONG FormatCount +) +{ + PAGED_CODE(); + + ACX_DATAFORMAT_LIST_ITERATOR formatIter; + ACXDATAFORMAT format; + NTSTATUS status = STATUS_SUCCESS; + + *FormatCount = 0; + + // Now copy over all formats from the target pin + ACX_DATAFORMAT_LIST_ITERATOR_INIT(&formatIter); + AcxDataFormatListBeginIteration(SourceList, &formatIter); + while (NT_SUCCESS(status) && NT_SUCCESS(AcxDataFormatListRetrieveNextFormat(SourceList, &formatIter, &format))) + { + ++*FormatCount; + + // The DataFormatList adds a reference to the format object + status = AcxDataFormatListAddDataFormat(DestinationList, format); + } + AcxDataFormatListEndIteration(SourceList, &formatIter); + + // Then finally assign the default format + ACXDATAFORMAT defaultFormat; + if (NT_SUCCESS(status) && NT_SUCCESS(AcxDataFormatListRetrieveDefaultDataFormat(SourceList, &defaultFormat))) + { + status = AcxDataFormatListAssignDefaultDataFormat(DestinationList, defaultFormat); + } + + return status; +} diff --git a/audio/Soundwire/Samples/SdcaVad/Inc/ContosoEventDetector.h b/audio/Soundwire/Samples/SdcaVad/Inc/ContosoEventDetector.h new file mode 100644 index 000000000..ca73daa46 --- /dev/null +++ b/audio/Soundwire/Samples/SdcaVad/Inc/ContosoEventDetector.h @@ -0,0 +1,51 @@ +/*++ + +Copyright (c) Microsoft Corporation. All rights reserved. + + THIS CODE AND INFORMATION IS PROVIDED "AS IS" WITHOUT WARRANTY OF ANY + KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE + IMPLIED WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A PARTICULAR + PURPOSE. + +Module Name: + + ContosoEventDetector.h + +Abstract: + + Sample event detector definitions + +Environment: + + Kernel mode + +--*/ + + +#pragma once + +typedef struct +{ + SOUNDDETECTOR_PATTERNHEADER Header; + LONGLONG ContosoDetectorConfigurationData; +} CONTOSO_KEYWORDCONFIGURATION; + +typedef struct +{ + SOUNDDETECTOR_PATTERNHEADER Header; + LONGLONG ContosoDetectorResultData; + ULONGLONG KeywordStartTimestamp; + ULONGLONG KeywordStopTimestamp; + GUID EventId; +} CONTOSO_KEYWORDDETECTIONRESULT; + +DEFINE_GUID(CONTOSO_KEYWORDCONFIGURATION_IDENTIFIER2, +0x207f3d0c, 0x5c79, 0x496f, 0xa9, 0x4c, 0xd3, 0xd2, 0x93, 0x4d, 0xbf, 0xa9); + +// {A537F559-2D67-463B-B10E-BEB750A21F31} +DEFINE_GUID(CONTOSO_KEYWORD1, +0xa537f559, 0x2d67, 0x463b, 0xb1, 0xe, 0xbe, 0xb7, 0x50, 0xa2, 0x1f, 0x31); +// {655E417A-80A5-4A77-B3F1-512EAF67ABCF} +DEFINE_GUID(CONTOSO_KEYWORD2, +0x655e417a, 0x80a5, 0x4a77, 0xb3, 0xf1, 0x51, 0x2e, 0xaf, 0x67, 0xab, 0xcf); + diff --git a/audio/Soundwire/Samples/SdcaVad/Inc/NewDelete.h b/audio/Soundwire/Samples/SdcaVad/Inc/NewDelete.h new file mode 100644 index 000000000..94e6f1e9b --- /dev/null +++ b/audio/Soundwire/Samples/SdcaVad/Inc/NewDelete.h @@ -0,0 +1,121 @@ +/*++ + +Copyright (c) Microsoft Corporation All Rights Reserved + +Module Name: + +NewDelete.h + +Abstract: + +Declaration of placement new and delete operators. + + +--*/ +#pragma once + +#ifdef _NEW_DELETE_OPERATORS_ + +/* make prototypes usable from C++ */ +#ifdef __cplusplus +extern "C" { +#endif + +#include + +#ifdef __cplusplus +} +#endif + +// Pool tag used for SDCA sample allocations +#define DEFAULT_POOLTAG 'wNwS' + +/***************************************************************************** +* Functions +*/ + +/***************************************************************************** +* ::new() +***************************************************************************** +* New function for creating objects with a specified allocation tag and +* pool type +*/ +PVOID operator new +( + size_t iSize, + POOL_FLAGS poolFlags, + ULONG tag +); + + +/***************************************************************************** +* ::new() +***************************************************************************** +* New function for creating objects with a specified pool type. +*/ +PVOID operator new +( + size_t iSize, + POOL_FLAGS poolFlags +); + + +/***************************************************************************** +* ::delete() +***************************************************************************** +* Delete with tag function. +*/ +void __cdecl operator delete +( + PVOID pVoid, + ULONG tag +); + + +/***************************************************************************** +* ::delete() +***************************************************************************** +* Sized Delete function. +*/ +void __cdecl operator delete +( + _Pre_maybenull_ __drv_freesMem(Mem) PVOID pVoid, + _In_ size_t cbSize +); + + +/***************************************************************************** +* ::delete() +***************************************************************************** +* Basic Delete function. +*/ +void __cdecl operator delete +( + PVOID pVoid +); + + +/***************************************************************************** +* ::delete() +***************************************************************************** +* Sized Array Delete function. +*/ +void __cdecl operator delete[] +( + _Pre_maybenull_ __drv_freesMem(Mem) PVOID pVoid, + _In_ size_t cbSize +); + + +/***************************************************************************** +* ::delete() +***************************************************************************** +* Array Delete function. +*/ +void __cdecl operator delete[] +( + _Pre_maybenull_ __drv_freesMem(Mem) PVOID pVoid +); + +#endif//_NEW_DELETE_OPERATORS_ + diff --git a/audio/Soundwire/Samples/SdcaVad/Inc/SdcaVXuTestInterface.h b/audio/Soundwire/Samples/SdcaVad/Inc/SdcaVXuTestInterface.h new file mode 100644 index 000000000..eac37c3ed --- /dev/null +++ b/audio/Soundwire/Samples/SdcaVad/Inc/SdcaVXuTestInterface.h @@ -0,0 +1,55 @@ +/*++ + +Copyright (c) Microsoft Corporation. All rights reserved. + + THIS CODE AND INFORMATION IS PROVIDED "AS IS" WITHOUT WARRANTY OF ANY + KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE + IMPLIED WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A PARTICULAR + PURPOSE. + +Module Name: + + SdcaVXuTestInterface.h + +Abstract: + + Contains definitions to allow user mode applications to communicate + directly with private interface of SdcaVXu driver + +Environment: + + Kernel mode, User mode + +--*/ + +#pragma once + +// The SDCA XU Driver can add a device interface to any of the raw PDOs it creates. +// +// We'll add the GUID_DEVINTERFACE_SDCAVXU_TEST_RAWCONTROL to the initial raw PDO +// the XU driver creates (which currently isn't used for any other purpose). +// +// User-mode applications can then use this device interface to find the target +// for IOCTL requests that are intended for the XU driver. +// +// A real XU driver could add a device interface to the initial raw PDO or to +// any of the Circuit Devices it creates to support the XU driver circuits, or +// it could use WdfControlDeviceInitAllocate to create a Control Device Object +// to accept IOCTL requests from user mode (though a Control Device Object +// cannot be used with WdfDeviceCreateDeviceInterface and would instead be +// used with WdfDeviceCreateSymbolicLink) +// +// A real XU driver could also use WdfDeviceInitAssignName to enable user-mode +// applications to call CreateFile for the device. + +// {48124666-FA50-47DE-A72D-0833510DBF96} +DEFINE_GUID(GUID_DEVINTERFACE_SDCAVXU_TEST_RAWCONTROL, +0x48124666, 0xfa50, 0x47de, 0xa7, 0x2d, 0x8, 0x33, 0x51, 0xd, 0xbf, 0x96); + +typedef struct _SDCAVXU_TEST_DATA +{ + ULONG Data; +} SDCAVXU_TEST_DATA, *PSDCAVXU_TEST_DATA; + +#define IOCTL_SDCAVXU_INTERFACE_TEST \ + CTL_CODE(FILE_DEVICE_UNKNOWN, 0x1, METHOD_BUFFERED, FILE_ANY_ACCESS) diff --git a/audio/Soundwire/Samples/SdcaVad/Inc/TestProperties.h b/audio/Soundwire/Samples/SdcaVad/Inc/TestProperties.h new file mode 100644 index 000000000..1963ca44d --- /dev/null +++ b/audio/Soundwire/Samples/SdcaVad/Inc/TestProperties.h @@ -0,0 +1,60 @@ +/*++ + +Copyright (c) Microsoft Corporation. All rights reserved. + + THIS CODE AND INFORMATION IS PROVIDED "AS IS" WITHOUT WARRANTY OF ANY + KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE + IMPLIED WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A PARTICULAR + PURPOSE. + +Module Name: + + TestProperties.h + +Abstract: + + Contains Test property values + +Environment: + + Kernel mode + +--*/ + +#pragma once + +const ULONG _DSP_STREAM_PROPERTY_UI4_VALUE = 1; + +// +// VENDOR SPECIFIC DATA +// These are made up placeholders to demonstrate how the KSPROPERTY_SDCA_VENDOR_SPECIFIC works +typedef struct _VIRTUAL_STACK_VENDOR_SPECIFIC_CONTROL +{ + ULONG VendorSpecificId; + ULONG VendorSpecificSize; + union + { + struct TestData + { + ULONG EndpointId; + ULONG DataPort; + } Data; + struct TestConfig + { + BOOLEAN IsScatterGather; + } Config; + }; +} VIRTUAL_STACK_VENDOR_SPECIFIC_CONTROL, * PVIRTUAL_STACK_VENDOR_SPECIFIC_CONTROL; + +typedef struct _VIRTUAL_STACK_VENDOR_SPECIFIC_VALUE_TEST_DATA +{ + ULONG Test1; + ULONG Test2; +} VIRTUAL_STACK_VENDOR_SPECIFIC_VALUE_TEST_DATA, * PVIRTUAL_STACK_VENDOR_SPECIFIC_VALUE_TEST_DATA; + +enum VIRTUAL_STACK_VENDOR_SPECIFIC_REQUEST +{ + VirtualStackVendorSpecificRequestGetTestData, + VirtualStackVendorSpecificRequestSetTestConfig +}; + diff --git a/audio/Soundwire/Samples/SdcaVad/Inc/cpp_utils.h b/audio/Soundwire/Samples/SdcaVad/Inc/cpp_utils.h new file mode 100644 index 000000000..f4ed62207 --- /dev/null +++ b/audio/Soundwire/Samples/SdcaVad/Inc/cpp_utils.h @@ -0,0 +1,71 @@ +/*++ + +Copyright (c) Microsoft Corporation. All rights reserved. + + THIS CODE AND INFORMATION IS PROVIDED "AS IS" WITHOUT WARRANTY OF ANY + KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE + IMPLIED WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A PARTICULAR + PURPOSE. + +Module Name: + + cpp_utils.h + +Abstract: + + Contains CPP utilities + +Environment: + + Kernel mode + +--*/ + +#pragma once + +// Function scope_exit instantiates scope_exit object +// Constructor accepts lamda as parameter. +// Assign tasks in lamdba to be executed on scope exit. +template +auto scope_exit(F f) +{ + class scope_exit + { + public: + scope_exit(F f) : + _f{ f } + { + } + + ~scope_exit() + { + if (_call) + { + _f(); + } + } + + // Ensures the scope_exit lambda will not be called + void release() + { + _call = false; + } + + // Executes the scope_exit lambda immediately if not yet run; ensures it will not run again + void reset() + { + if (_call) + { + _f(); + _call = false; + } + } + + private: + F _f; + bool _call = true; + }; + + return scope_exit{ f }; +}; + diff --git a/audio/Soundwire/Samples/SdcaVad/Inc/trace_macros.h b/audio/Soundwire/Samples/SdcaVad/Inc/trace_macros.h new file mode 100644 index 000000000..c2188dc67 --- /dev/null +++ b/audio/Soundwire/Samples/SdcaVad/Inc/trace_macros.h @@ -0,0 +1,460 @@ +#pragma once + +#include // for va_start, etc. + +#pragma region Tracing level definitions + +#if !defined(FAILED_NTSTATUS) +#define FAILED_NTSTATUS(status) (((NTSTATUS)(status)) < 0) +#endif + +#if !defined(SUCCEEDED_NTSTATUS) +#define SUCCEEDED_NTSTATUS(status) (((NTSTATUS)(status)) >= 0) +#endif + +//! Define shorter versions of the ETW trace levels +#define LEVEL_CRITICAL TRACE_LEVEL_CRITICAL +#define LEVEL_ERROR TRACE_LEVEL_ERROR +#define LEVEL_WARNING TRACE_LEVEL_WARNING +#define LEVEL_INFO TRACE_LEVEL_INFORMATION +#define LEVEL_VERBOSE TRACE_LEVEL_VERBOSE + +//! This is a special LEVEL that changes the trace macro level from ERROR to VERBOSE +//! depending on whether the return value passed to the macro was non-zero or zero, +//! respectively. +#define LEVEL_COND 0xFF +#pragma endregion + +//! Logger and Enabled that supports both level and flag. +//! \link https://msdn.microsoft.com/en-us/library/windows/hardware/ff542492(v=vs.85).aspx +#define WPP_LEVEL_FLAGS_LOGGER(LEVEL, FLAGS) WPP_LEVEL_LOGGER(FLAGS) +#define WPP_LEVEL_FLAGS_ENABLED(LEVEL, FLAGS) (WPP_LEVEL_ENABLED(FLAGS) && (WPP_CONTROL(WPP_BIT_ ## FLAGS).Level >= LEVEL)) + +//! This macro is to be used by the WPP custom macros below that want to do conditional +//! logging based on return value. If LEVEL_VERBOSE is specified when calling a macro that +//! uses this, the level will be set to LEVEL_INFO if return code is 0 or +//! LEVEL_ERROR if the return code is not 0. This can be called in any PRE macro. +//! +//! The "LEVEL == LEVEL_COND" check generates a compiler warning that the "conditional +//! expression is constant" so we explicitly disable that. +#define WPP_CONDITIONAL_LEVEL_FLAGS_OVERRIDE(LEVEL, FLAGS, HR) \ + BOOL bEnabled = WPP_LEVEL_FLAGS_ENABLED(LEVEL, FLAGS); \ + __pragma(warning(push)) \ + __pragma(warning(disable: 4127)) \ + if (LEVEL == LEVEL_COND) \ + { \ + if (SUCCEEDED(HR)) \ + { \ + bEnabled = WPP_LEVEL_FLAGS_ENABLED(LEVEL_VERBOSE, FLAGS); \ + } \ + else \ + { \ + bEnabled = WPP_LEVEL_FLAGS_ENABLED(LEVEL_ERROR, FLAGS); \ + } \ + } \ + __pragma(warning(pop)) + +#define WPP_CONDITIONAL_LEVEL_FLAGS_OVERRIDE_NTSTATUS(LEVEL, FLAGS, STATUS) \ + BOOLEAN bEnabled = WPP_LEVEL_FLAGS_ENABLED(LEVEL, FLAGS); \ + __pragma(warning(push)) \ + __pragma(warning(disable: 4127)) \ + if (LEVEL == LEVEL_COND) \ + { \ + if (SUCCEEDED_NTSTATUS(STATUS)) \ + { \ + bEnabled = WPP_LEVEL_FLAGS_ENABLED(LEVEL_VERBOSE, FLAGS); \ + } \ + else \ + { \ + bEnabled = WPP_LEVEL_FLAGS_ENABLED(LEVEL_ERROR, FLAGS); \ + } \ + } \ + __pragma(warning(pop)) + + +#define WPP_LEVEL_FLAGS_IFRLOG_ENABLED(LEVEL, FLAGS, IFRLOG) WPP_LEVEL_FLAGS_ENABLED(LEVEL, FLAGS) +#define WPP_LEVEL_FLAGS_IFRLOG_LOGGER(LEVEL, FLAGS, IFRLOG) WPP_LEVEL_FLAGS_LOGGER(LEVEL, FLAGS) +#define WPP_LEVEL_IFRLOG_FLAGS_ENABLED(LEVEL, IFRLOG, FLAGS) WPP_LEVEL_FLAGS_ENABLED(LEVEL, FLAGS) +#define WPP_LEVEL_IFRLOG_FLAGS_LOGGER(LEVEL, IFRLOG, FLAGS) WPP_LEVEL_FLAGS_LOGGER(LEVEL, FLAGS) + +#define WPP_LEVEL_FLAGS_HR_PRE(LEVEL, FLAGS, HR) { WPP_CONDITIONAL_LEVEL_FLAGS_OVERRIDE(LEVEL, FLAGS, HR) +#define WPP_LEVEL_FLAGS_HR_POST(LEVEL, FLAGS, HR) ;} +#define WPP_LEVEL_FLAGS_HR_ENABLED(LEVEL, FLAGS, HR) bEnabled +#define WPP_LEVEL_FLAGS_HR_LOGGER(LEVEL, FLAGS, HR) WPP_LEVEL_FLAGS_LOGGER(LEVEL, FLAGS) + +#define WPP_LEVEL_FLAGS_RETVAL_ENABLED(LEVEL, FLAGS, RETVAL) WPP_LEVEL_FLAGS_ENABLED(LEVEL, FLAGS) +#define WPP_LEVEL_FLAGS_RETVAL_LOGGER(LEVEL, FLAGS, RETVAL) WPP_LEVEL_FLAGS_LOGGER(LEVEL, FLAGS) + +#define WPP_LEVEL_FLAGS_FI_ENABLED(LEVEL, FLAGS, FI) WPP_LEVEL_FLAGS_ENABLED(LEVEL, FLAGS) +#define WPP_LEVEL_FLAGS_FI_LOGGER(LEVEL, FLAGS, FI) WPP_LEVEL_FLAGS_LOGGER(LEVEL, FLAGS) + +#define WPP_LEVEL_FLAGS_STATUS_PRE(LEVEL, FLAGS, STATUS) { WPP_CONDITIONAL_LEVEL_FLAGS_OVERRIDE_NTSTATUS(LEVEL, FLAGS, STATUS) +#define WPP_LEVEL_FLAGS_STATUS_POST(LEVEL, FLAGS, STATUS) ;} +#define WPP_LEVEL_FLAGS_STATUS_ENABLED(LEVEL, FLAGS, STATUS) bEnabled +#define WPP_LEVEL_FLAGS_STATUS_LOGGER(LEVEL, FLAGS, STATUS) WPP_LEVEL_FLAGS_LOGGER(LEVEL, FLAGS) + +#define WPP_LEVEL_FLAGS_RETSTATUS_PRE(LEVEL, FLAGS, RETSTATUS) do { NTSTATUS __statusRet = (RETSTATUS); if (FAILED_NTSTATUS(__statusRet)) { +#define WPP_LEVEL_FLAGS_RETSTATUS_POST(LEVEL, FLAGS, RETSTATUS) ; return __statusRet; } } while (0, 0) +#define WPP_LEVEL_FLAGS_RETSTATUS_ENABLED(LEVEL, FLAGS, RETSTATUS) WPP_LEVEL_FLAGS_ENABLED(LEVEL, FLAGS) +#define WPP_LEVEL_FLAGS_RETSTATUS_LOGGER(LEVEL, FLAGS, RETSTATUS) WPP_LEVEL_FLAGS_LOGGER(LEVEL, FLAGS) + +#define WPP_LEVEL_FLAGS_IFRLOG_RETSTATUS_PRE(LEVEL, FLAGS, IFRLOG, RETSTATUS) do { NTSTATUS __statusRet = (RETSTATUS); if (FAILED_NTSTATUS(__statusRet)) { +#define WPP_LEVEL_FLAGS_IFRLOG_RETSTATUS_POST(LEVEL, FLAGS, IFRLOG, RETSTATUS) ; return __statusRet; } } while (0, 0) +#define WPP_LEVEL_FLAGS_IFRLOG_RETSTATUS_ENABLED(LEVEL, FLAGS, IFRLOG, RETSTATUS) WPP_LEVEL_FLAGS_ENABLED(LEVEL, FLAGS) +#define WPP_LEVEL_FLAGS_IFRLOG_RETSTATUS_LOGGER(LEVEL, FLAGS, IFRLOG, RETSTATUS) WPP_LEVEL_FLAGS_LOGGER(LEVEL, FLAGS) + +#define WPP_LEVEL_FLAGS_IFRLOG_RETSTATUS_ALLOWEDSTATUS_PRE(LEVEL, FLAGS, IFRLOG, RETSTATUS, ALLOWEDSTATUS) do {\ +NTSTATUS __statusRet = (RETSTATUS);\ +if(__statusRet == ALLOWEDSTATUS)\ +{\ + __statusRet = STATUS_SUCCESS;\ +}\ +if (FAILED_NTSTATUS(__statusRet)) { +#define WPP_LEVEL_FLAGS_IFRLOG_RETSTATUS_ALLOWEDSTATUS_POST(LEVEL, FLAGS, IFRLOG, RETSTATUS, ALLOWEDSTATUS) ; return __statusRet; } } while (0, 0) +#define WPP_LEVEL_FLAGS_IFRLOG_RETSTATUS_ALLOWEDSTATUS_ENABLED(LEVEL, FLAGS, IFRLOG, RETSTATUS, ALLOWEDSTATUS) WPP_LEVEL_FLAGS_ENABLED(LEVEL, FLAGS) +#define WPP_LEVEL_FLAGS_IFRLOG_RETSTATUS_ALLOWEDSTATUS_LOGGER(LEVEL, FLAGS, IFRLOG, RETSTATUS, ALLOWEDSTATUS) WPP_LEVEL_FLAGS_LOGGER(LEVEL, FLAGS) + +#define WPP_LEVEL_FLAGS_RETPTR_PRE(LEVEL, FLAGS, RETPTR) do { if ((RETPTR) == nullptr) { +#define WPP_LEVEL_FLAGS_RETPTR_POST(LEVEL, FLAGS, RETPTR) ; return STATUS_INSUFFICIENT_RESOURCES; } } while (0, 0) +#define WPP_LEVEL_FLAGS_RETPTR_ENABLED(LEVEL, FLAGS, RETPTR) WPP_LEVEL_FLAGS_ENABLED(LEVEL, FLAGS) +#define WPP_LEVEL_FLAGS_RETPTR_LOGGER(LEVEL, FLAGS, RETPTR) WPP_LEVEL_FLAGS_LOGGER(LEVEL, FLAGS) + +#define WPP_LEVEL_FLAGS_RETSTATUS_RETPTR_PRE(LEVEL, FLAGS, RETSTATUS, RETPTR) do { NTSTATUS __statusRet = (RETSTATUS); if ((RETPTR) == nullptr) { +#define WPP_LEVEL_FLAGS_RETSTATUS_RETPTR_POST(LEVEL, FLAGS, RETSTATUS, RETPTR) ; return __statusRet; } } while (0, 0) +#define WPP_LEVEL_FLAGS_RETSTATUS_RETPTR_ENABLED(LEVEL, FLAGS, RETSTATUS, RETPTR) WPP_LEVEL_FLAGS_ENABLED(LEVEL, FLAGS) +#define WPP_LEVEL_FLAGS_RETSTATUS_RETPTR_LOGGER(LEVEL, FLAGS, RETSTATUS, RETPTR) WPP_LEVEL_FLAGS_LOGGER(LEVEL, FLAGS) + +#define WPP_LEVEL_FLAGS_RETSTATUS_POSCOND_PRE(LEVEL, FLAGS, RETSTATUS, POSCOND) do { NTSTATUS __statusRet = (RETSTATUS); if ((POSCOND)) { +#define WPP_LEVEL_FLAGS_RETSTATUS_POSCOND_POST(LEVEL, FLAGS, RETSTATUS, POSCOND) ; return __statusRet; } } while (0, 0) +#define WPP_LEVEL_FLAGS_RETSTATUS_POSCOND_ENABLED(LEVEL, FLAGS, RETSTATUS, POSCOND) WPP_LEVEL_FLAGS_ENABLED(LEVEL, FLAGS) +#define WPP_LEVEL_FLAGS_RETSTATUS_POSCOND_LOGGER(LEVEL, FLAGS, RETSTATUS, POSCOND) WPP_LEVEL_FLAGS_LOGGER(LEVEL, FLAGS) + +#define WPP_LEVEL_FLAGS_IFRLOG_POSCOND_RETSTATUS_PRE(LEVEL, FLAGS, IFRLOG, POSCOND, RETSTATUS) do { NTSTATUS __statusRet = (RETSTATUS); if ((POSCOND)) { +#define WPP_LEVEL_FLAGS_IFRLOG_POSCOND_RETSTATUS_POST(LEVEL, FLAGS, IFRLOG, POSCOND, RETSTATUS) ; return __statusRet; } } while (0, 0) +#define WPP_LEVEL_FLAGS_IFRLOG_POSCOND_RETSTATUS_ENABLED(LEVEL, FLAGS, IFRLOG, POSCOND, RETSTATUS) WPP_LEVEL_FLAGS_ENABLED(LEVEL, FLAGS) +#define WPP_LEVEL_FLAGS_IFRLOG_POSCOND_RETSTATUS_LOGGER(LEVEL, FLAGS, IFRLOG, POSCOND, RETSTATUS) WPP_LEVEL_FLAGS_LOGGER(LEVEL, FLAGS) + +#define WPP_LEVEL_FLAGS_RETSTATUS_NEGCOND_PRE(LEVEL, FLAGS, RETSTATUS, NEGCOND) do { NTSTATUS __statusRet = (RETSTATUS); if (!(NEGCOND)) { +#define WPP_LEVEL_FLAGS_RETSTATUS_NEGCOND_POST(LEVEL, FLAGS, RETSTATUS, NEGCOND) ; return __statusRet; } } while (0, 0) +#define WPP_LEVEL_FLAGS_RETSTATUS_NEGCOND_ENABLED(LEVEL, FLAGS, RETSTATUS, NEGCOND) WPP_LEVEL_FLAGS_ENABLED(LEVEL, FLAGS) +#define WPP_LEVEL_FLAGS_RETSTATUS_NEGCOND_LOGGER(LEVEL, FLAGS, RETSTATUS, NEGCOND) WPP_LEVEL_FLAGS_LOGGER(LEVEL, FLAGS) + +#pragma region IFR Enablement Macros + +// Opt-in to a WPP recorder feature that enables independent evaluation of conditions to decide if a +// message needs to be sent to the recorder, an enabled session, or both. +#define ENABLE_WPP_TRACE_FILTERING_WITH_WPP_RECORDER 1 + +// Logger/Enabled macros used to decide if a message that is being sent to a custom recorder should +// also go to an enabled session. These do not depend on the custom recorder itself, so just +// delegate to the default. +#define WPP_IFRLOG_LEVEL_FLAGS_LOGGER(IFRLOG, LEVEL, FLAGS) WPP_LEVEL_FLAGS_LOGGER(LEVEL, FLAGS) +#define WPP_IFRLOG_LEVEL_FLAGS_ENABLED(IFRLOG, LEVEL, FLAGS) WPP_LEVEL_FLAGS_ENABLED(LEVEL, FLAGS) + +#define WPP_RECORDER_CONDITIONAL_LEVEL_FLAGS_OVERRIDE(LEVEL, FLAGS, HR) \ + ((LEVEL == LEVEL_COND) ? \ + (FAILED(HR) ? \ + WPP_RECORDER_LEVEL_FLAGS_FILTER(LEVEL_ERROR, FLAGS) : WPP_RECORDER_LEVEL_FLAGS_FILTER(LEVEL_VERBOSE, FLAGS)) : \ + WPP_RECORDER_LEVEL_FLAGS_FILTER(LEVEL, FLAGS)) + +#define WPP_RECORDER_CONDITIONAL_LEVEL_FLAGS_OVERRIDE_NTSTATUS(LEVEL, FLAGS, STATUS) \ + ((LEVEL == LEVEL_COND) ? \ + (FAILED_NTSTATUS(STATUS) ? \ + WPP_RECORDER_LEVEL_FLAGS_FILTER(LEVEL_ERROR, FLAGS) : WPP_RECORDER_LEVEL_FLAGS_FILTER(LEVEL_VERBOSE, FLAGS)) : \ + WPP_RECORDER_LEVEL_FLAGS_FILTER(LEVEL, FLAGS)) + +#define WPP_RECORDER_LEVEL_FLAGS_HR_ARGS(LEVEL, FLAGS, RETVAL) WPP_RECORDER_LEVEL_FLAGS_ARGS(LEVEL, FLAGS) +#define WPP_RECORDER_LEVEL_FLAGS_HR_FILTER(LEVEL, FLAGS, RETVAL) WPP_RECORDER_LEVEL_FLAGS_FILTER(LEVEL, FLAGS) + +#define WPP_RECORDER_LEVEL_FLAGS_RETVAL_ARGS(LEVEL, FLAGS, RETVAL) WPP_RECORDER_LEVEL_FLAGS_ARGS(LEVEL, FLAGS) +#define WPP_RECORDER_LEVEL_FLAGS_RETVAL_FILTER(LEVEL, FLAGS, RETVAL) WPP_RECORDER_LEVEL_FLAGS_FILTER(LEVEL, FLAGS) + +#define WPP_RECORDER_LEVEL_FLAGS_FI_ARGS(LEVEL, FLAGS, RETVAL) WPP_RECORDER_LEVEL_FLAGS_ARGS(LEVEL, FLAGS) +#define WPP_RECORDER_LEVEL_FLAGS_FI_FILTER(LEVEL, FLAGS, RETVAL) WPP_RECORDER_LEVEL_FLAGS_FILTER(LEVEL, FLAGS) + +#define WPP_RECORDER_LEVEL_FLAGS_STATUS_ARGS(LEVEL, FLAGS, STATUS) WPP_RECORDER_LEVEL_FLAGS_ARGS(LEVEL, FLAGS) +#define WPP_RECORDER_LEVEL_FLAGS_STATUS_FILTER(LEVEL, FLAGS, STATUS) WPP_RECORDER_LEVEL_FLAGS_FILTER(LEVEL, FLAGS) + +#define WPP_RECORDER_LEVEL_FLAGS_RETSTATUS_ARGS(LEVEL, FLAGS, RETSTATUS) WPP_RECORDER_LEVEL_FLAGS_ARGS(LEVEL, FLAGS) +#define WPP_RECORDER_LEVEL_FLAGS_RETSTATUS_FILTER(LEVEL, FLAGS, RETSTATUS) WPP_RECORDER_LEVEL_FLAGS_FILTER(LEVEL, FLAGS) + +#define WPP_RECORDER_LEVEL_FLAGS_IFRLOG_RETSTATUS_ARGS(LEVEL, FLAGS, IFRLOG, RETSTATUS) WPP_RECORDER_LEVEL_FLAGS_ARGS(LEVEL, FLAGS) +#define WPP_RECORDER_LEVEL_FLAGS_IFRLOG_RETSTATUS_FILTER(LEVEL, FLAGS, IFRLOG, RETSTATUS) WPP_RECORDER_LEVEL_FLAGS_FILTER(LEVEL, FLAGS) + +#define WPP_RECORDER_LEVEL_FLAGS_IFRLOG_RETSTATUS_ALLOWEDSTATUS_ARGS(LEVEL, FLAGS, IFRLOG, RETSTATUS, ALLOWEDSTATUS) WPP_RECORDER_LEVEL_FLAGS_ARGS(LEVEL, FLAGS) +#define WPP_RECORDER_LEVEL_FLAGS_IFRLOG_RETSTATUS_ALLOWEDSTATUS_FILTER(LEVEL, FLAGS, IFRLOG, RETSTATUS, ALLOWEDSTATUS) WPP_RECORDER_LEVEL_FLAGS_FILTER(LEVEL, FLAGS) + +#define WPP_RECORDER_LEVEL_FLAGS_RETPTR_ARGS(LEVEL, FLAGS, RETPTR) WPP_RECORDER_LEVEL_FLAGS_ARGS(LEVEL, FLAGS) +#define WPP_RECORDER_LEVEL_FLAGS_RETPTR_FILTER(LEVEL, FLAGS, RETPTR) WPP_RECORDER_LEVEL_FLAGS_FILTER(LEVEL, FLAGS) + +#define WPP_RECORDER_LEVEL_FLAGS_RETSTATUS_RETPTR_ARGS(LEVEL, FLAGS, RETSTATUS, RETPTR) WPP_RECORDER_LEVEL_FLAGS_ARGS(LEVEL, FLAGS) +#define WPP_RECORDER_LEVEL_FLAGS_RETSTATUS_RETPTR_FILTER(LEVEL, FLAGS, RETSTATUS, RETPTR) WPP_RECORDER_LEVEL_FLAGS_FILTER(LEVEL, FLAGS) + +#define WPP_RECORDER_LEVEL_FLAGS_RETSTATUS_POSCOND_ARGS(LEVEL, FLAGS, RETSTATUS, POSCOND) WPP_RECORDER_LEVEL_FLAGS_ARGS(LEVEL, FLAGS) +#define WPP_RECORDER_LEVEL_FLAGS_RETSTATUS_POSCOND_FILTER(LEVEL, FLAGS, RETSTATUS, POSCOND) WPP_RECORDER_LEVEL_FLAGS_FILTER(LEVEL, FLAGS) + +#define WPP_RECORDER_LEVEL_FLAGS_IFRLOG_POSCOND_RETSTATUS_ARGS(LEVEL, FLAGS, IFRLOG, RETSTATUS, POSCOND) WPP_RECORDER_LEVEL_FLAGS_ARGS(LEVEL, FLAGS) +#define WPP_RECORDER_LEVEL_FLAGS_IFRLOG_POSCOND_RETSTATUS_FILTER(LEVEL, FLAGS, IFRLOG, RETSTATUS, POSCOND) WPP_RECORDER_LEVEL_FLAGS_FILTER(LEVEL, FLAGS) + +#define WPP_RECORDER_LEVEL_FLAGS_RETSTATUS_NEGCOND_ARGS(LEVEL, FLAGS, RETSTATUS, NEGCOND) WPP_RECORDER_LEVEL_FLAGS_ARGS(LEVEL, FLAGS) +#define WPP_RECORDER_LEVEL_FLAGS_RETSTATUS_NEGCOND_FILTER(LEVEL, FLAGS, RETSTATUS, NEGCOND) WPP_RECORDER_LEVEL_FLAGS_FILTER(LEVEL, FLAGS) +#pragma endregion + +#pragma region Custom tracing macros + +// begin_wpp config +// USEPREFIX(DrvLogCritical, "%!STDPREFIX!CRIT: "); +// USEPREFIX(DrvLogError, "%!STDPREFIX!ERROR: "); +// USEPREFIX(DrvLogWarning, "%!STDPREFIX!WARN: "); +// USEPREFIX(DrvLogInfo, "%!STDPREFIX!INFO: "); +// USEPREFIX(DrvLogVerbose, "%!STDPREFIX!VERB: "); +// USEPREFIX(DrvLogEnter, "%!STDPREFIX!ENTER"); +// USEPREFIX(DrvLogExit, "%!STDPREFIX!EXIT"); +// end_wpp + +// begin_wpp config +// FUNC DrvLogCritical{LEVEL=TRACE_LEVEL_CRITICAL}(IFRLOG,FLAGS,MSG,...); +// FUNC DrvLogError{LEVEL=TRACE_LEVEL_ERROR}(IFRLOG,FLAGS,MSG,...); +// FUNC DrvLogWarning{LEVEL=TRACE_LEVEL_WARNING}(IFRLOG,FLAGS,MSG,...); +// FUNC DrvLogInfo{LEVEL=TRACE_LEVEL_INFORMATION}(IFRLOG,FLAGS,MSG,...); +// FUNC DrvLogEnter{LEVEL=TRACE_LEVEL_VERBOSE,FLAGS=FLAG_FUNCTION}(IFRLOG,...); +// FUNC DrvLogVerbose{LEVEL=TRACE_LEVEL_VERBOSE}(IFRLOG,FLAGS,MSG,...); +// FUNC DrvLogExit{LEVEL=TRACE_LEVEL_VERBOSE,FLAGS=FLAG_FUNCTION}(IFRLOG,...); +// end_wpp + + +#ifdef __INTELLISENSE__ +#define FLAG_DEVICE_ALL 0x01 +#define FLAG_FUNCTION 0x02 +#define FLAG_INFO 0x04 +#define FLAG_PNP 0x08 +#define FLAG_POWER 0x10 +#define FLAG_STREAM 0x20 +#define FLAG_INIT 0x40 +#define FLAG_DDI 0x80 +#define FLAG_GENERIC 0x100 +void DrvLogCritical(void* log, int flags, const WCHAR* fmt, ...); +void DrvLogError(void* log, int flags, const WCHAR* fmt, ...); +void DrvLogWarning(void* log, int flags, const WCHAR* fmt, ...); +void DrvLogInfo(void* log, int flags, const WCHAR* fmt, ...); +void DrvLogEnter(void* log, ...); +void DrvLogVerbose(void* log, int flags, const WCHAR* fmt, ...); +void DrvLogExit(void* log, ...); + +void RETURN_NTSTATUS_IF_FAILED(NTSTATUS status); +void RETURN_NTSTATUS_IF_FAILED_MSG(NTSTATUS status, const WCHAR *fmt, ...); +void RETURN_NTSTATUS_IF_FAILED_UNLESS_ALLOWED(NTSTATUS returnStatus, NTSTATUS allowedStatus); +void RETURN_NTSTATUS_IF_NULL_ALLOC(PVOID ptr); +void RETURN_NTSTATUS_IF_NULL(PVOID ptr); +void RETURN_NTSTATUS_IF_TRUE(BOOL condition, NTSTATUS status); +void RETURN_NTSTATUS_IF_TRUE_MSG(BOOL condition, NTSTATUS status, const WCHAR *fmt, ...); +void RETURN_NTSTATUS_IF_FALSE(BOOL condition, NTSTATUS status); +void RETURN_NTSTATUS(NTSTATUS status); +void RETURN_NTSTATUS_MSG(NTSTATUS status, const WCHAR* fmt, ...); +#endif// __INTELLISENSE__ + +//********************************************************* +// MACRO: TRACE_METHOD_LINE +// +// begin_wpp config +// FUNC TRACE_METHOD_LINE(LEVEL, FLAGS, MSG, ...); +// USESUFFIX (TRACE_METHOD_LINE, ", this=0x%p", this); +// end_wpp + +//********************************************************* +// MACRO: TRACE_METHOD_ENTRY +// +// begin_wpp config +// FUNC TRACE_METHOD_ENTRY(LEVEL, FLAGS); +// USESUFFIX (TRACE_METHOD_ENTRY, "Enter, this=0x%p", this); +// end_wpp + +//********************************************************* +// MACRO: TRACE_METHOD_EXIT +// +// begin_wpp config +// FUNC TRACE_METHOD_EXIT(LEVEL, FLAGS); +// USESUFFIX (TRACE_METHOD_EXIT, "Exit, this=0x%p", this); +// end_wpp + +//********************************************************* +// MACRO: TRACE_METHOD_EXIT_HR +// +// begin_wpp config +// FUNC TRACE_METHOD_EXIT_HR(LEVEL, FLAGS, HR); +// USESUFFIX (TRACE_METHOD_EXIT_HR, "Exit, this=0x%p, hr=%!HRESULT!", this, HR); +// end_wpp + +//********************************************************* +// MACRO: TRACE_METHOD_EXIT_DWORD +// +// begin_wpp config +// FUNC TRACE_METHOD_EXIT_DWORD(LEVEL, FLAGS, RETVAL); +// USESUFFIX (TRACE_METHOD_EXIT_DWORD, "Exit, this=0x%p, ret=0x%08Ix ", this, RETVAL); +// end_wpp + +//********************************************************* +// MACRO: TRACE_METHOD_EXIT_PTR +// +// begin_wpp config +// FUNC TRACE_METHOD_EXIT_PTR(LEVEL, FLAGS, RETVAL); +// USESUFFIX (TRACE_METHOD_EXIT_PTR,"Exit, this=0x%p, retptr=0x%p", this, RETVAL); +// end_wpp + +//********************************************************* +// MACRO: TRACE_METHOD_EXIT_STATUS +// +// begin_wpp config +// FUNC TRACE_METHOD_EXIT_STATUS(LEVEL, FLAGS, STATUS); +// USESUFFIX (TRACE_METHOD_EXIT_STATUS, "Exit, this=0x%p, status=%!STATUS!", this, STATUS); +// end_wpp + +//********************************************************* +// MACRO: TRACE_FUNCTION_ENTRY +// +// begin_wpp config +// FUNC TRACE_FUNCTION_ENTRY(LEVEL, FLAGS); +// USESUFFIX (TRACE_FUNCTION_ENTRY, "Enter"); +// end_wpp + +//********************************************************* +// MACRO: TRACE_FUNCTION_EXIT +// +// begin_wpp config +// FUNC TRACE_FUNCTION_EXIT(LEVEL, FLAGS); +// USESUFFIX (TRACE_FUNCTION_EXIT, "Exit"); +// end_wpp + +//********************************************************* +// MACRO: TRACE_FUNCTION_EXIT_HR +// +// begin_wpp config +// FUNC TRACE_FUNCTION_EXIT_HR(LEVEL, FLAGS, HR); +// USESUFFIX (TRACE_FUNCTION_EXIT_HR, "Exit, hr=%!HRESULT!", HR); +// end_wpp + +//********************************************************* +// MACRO: TRACE_FUNCTION_EXIT_DWORD +// +// begin_wpp config +// FUNC TRACE_FUNCTION_EXIT_DWORD(LEVEL, FLAGS, RETVAL); +// USESUFFIX (TRACE_FUNCTION_EXIT_DWORD, "Exit, ret=0x%08Ix", RETVAL); +// end_wpp + +//********************************************************* +// MACRO: TRACE_FUNCTION_EXIT_PTR +// +// begin_wpp config +// FUNC TRACE_FUNCTION_EXIT_PTR(LEVEL, FLAGS, RETVAL); +// USESUFFIX (TRACE_FUNCTION_EXIT_PTR, "Exit, retptr=0x%p", RETVAL); +// end_wpp + +//********************************************************* +// MACRO: TRACE_FUNCTION_EXIT_STATUS +// +// begin_wpp config +// FUNC TRACE_FUNCTION_EXIT_STATUS(LEVEL, FLAGS, STATUS); +// USESUFFIX (TRACE_FUNCTION_EXIT_STATUS, "Exit, status=%!STATUS!", STATUS); +// end_wpp + +//********************************************************* +// MACRO: TRACE_LINE +// +// begin_wpp config +// FUNC TRACE_LINE(LEVEL, FLAGS, MSG, ...); +// end_wpp + +//********************************************************* +// MACRO: TRACE_HRESULT +// +// begin_wpp config +// FUNC TRACE_HRESULT(LEVEL, FLAGS, HR, MSG, ...); +// USESUFFIX (TRACE_HRESULT, ", ret=%!HRESULT!", HR); +// end_wpp + +//********************************************************* +// MACRO: TRACE_FAILURE_INFO (WIL FailureInfo logging) +// see: https://github.com/microsoft/wil/blob/master/include/wil/result_macros.h +// +// begin_wpp config +// FUNC TRACE_FAILURE_INFO(LEVEL, FLAGS, FI); +// USESUFFIX(TRACE_FAILURE_INFO, " [%04X] '%ws', hr=%!HRESULT! ['%s' (%u)]", FI.threadId, FI.pszMessage, FI.hr, FI.pszFile, FI.uLineNumber); +// end_wpp + +// MACRO: RETURN_NTSTATUS_IF_FAILED +// +// begin_wpp config +// FUNC RETURN_NTSTATUS_IF_FAILED{LEVEL=LEVEL_ERROR,FLAGS=FLAG_DEVICE_ALL,IFRLOG=g_SDCAVDspLog}(RETSTATUS); +// USEPREFIX(RETURN_NTSTATUS_IF_FAILED, "%!STDPREFIX!ERROR:"); +// USESUFFIX(RETURN_NTSTATUS_IF_FAILED, " File:%s, Line:%d - status=%!STATUS!", __FILE__, __LINE__, __statusRet); +// end_wpp + +// MACRO: RETURN_NTSTATUS_IF_FAILED_MSG +// +// begin_wpp config +// FUNC RETURN_NTSTATUS_IF_FAILED_MSG{LEVEL=LEVEL_ERROR,FLAGS=FLAG_DEVICE_ALL,IFRLOG=g_SDCAVDspLog}(RETSTATUS, MSG, ...); +// USEPREFIX(RETURN_NTSTATUS_IF_FAILED_MSG, "%!STDPREFIX!ERROR:"); +// USESUFFIX(RETURN_NTSTATUS_IF_FAILED_MSG, " - status=%!STATUS!",__statusRet); +// end_wpp + +// MACRO: RETURN_NTSTATUS_IF_FAILED_UNLESS_ALLOWED +// +// begin_wpp config +// FUNC RETURN_NTSTATUS_IF_FAILED_UNLESS_ALLOWED{LEVEL=LEVEL_ERROR,FLAGS=FLAG_DEVICE_ALL,IFRLOG=g_SDCAVDspLog}(RETSTATUS, ALLOWEDSTATUS); +// USEPREFIX(RETURN_NTSTATUS_IF_FAILED_UNLESS_ALLOWED, "%!STDPREFIX!ERROR:"); +// USESUFFIX(RETURN_NTSTATUS_IF_FAILED_UNLESS_ALLOWED, " File:%s, Line:%d - status=%!STATUS!", __FILE__, __LINE__, __statusRet); +// end_wpp + +// MACRO: RETURN_NTSTATUS_IF_NULL_ALLOC +// +// begin_wpp config +// FUNC RETURN_NTSTATUS_IF_NULL_ALLOC{LEVEL=LEVEL_ERROR,FLAGS=DUMMY}(RETPTR); +// USESUFFIX(RETURN_NTSTATUS_IF_NULL, "status=STATUS_INSUFFICIENT_RESOURCES"); +// end_wpp + +// MACRO: RETURN_NTSTATUS_IF_NULL +// +// begin_wpp config +// FUNC RETURN_NTSTATUS_IF_NULL{LEVEL=LEVEL_ERROR,FLAGS=DUMMY}(RETSTATUS, RETPTR); +// USESUFFIX(RETURN_NTSTATUS_IF_NULL, "status=%!STATUS!", __statusRet); +// end_wpp + +// MACRO: RETURN_NTSTATUS_IF_TRUE +// +// begin_wpp config +// FUNC RETURN_NTSTATUS_IF_TRUE{LEVEL=LEVEL_ERROR,FLAGS=FLAG_DEVICE_ALL,IFRLOG=g_SDCAVDspLog}(POSCOND, RETSTATUS); +// USESUFFIX(RETURN_NTSTATUS_IF_TRUE, " File:%s, Line:%d - status=%!STATUS!", __FILE__, __LINE__, __statusRet); +// end_wpp + +// MACRO: RETURN_NTSTATUS_IF_TRUE_MSG +// +// begin_wpp config +// FUNC RETURN_NTSTATUS_IF_TRUE_MSG{LEVEL=LEVEL_ERROR,FLAGS=FLAG_DEVICE_ALL,IFRLOG=g_SDCAVDspLog}(POSCOND, RETSTATUS, MSG, ...); +// USESUFFIX(RETURN_NTSTATUS_IF_TRUE_MSG, " - status=%!STATUS!", __statusRet); +// end_wpp + +// MACRO: RETURN_NTSTATUS_IF_FALSE +// +// begin_wpp config +// FUNC RETURN_NTSTATUS_IF_FALSE{LEVEL=LEVEL_ERROR,FLAGS=DUMMY}(RETSTATUS, NEGCOND); +// USESUFFIX(RETURN_NTSTATUS_IF_FALSE, " File:%s, Line:%d - status=%!STATUS!", __FILE__, __LINE__, __statusRet); +// end_wpp + +// MACRO: RETURN_NTSTATUS +// +// begin_wpp config +// FUNC RETURN_NTSTATUS{LEVEL=LEVEL_ERROR,FLAGS=FLAG_DEVICE_ALL,IFRLOG=g_SDCAVDspLog}(RETSTATUS); +// USESUFFIX(RETURN_NTSTATUS, " File:%s, Line:%d - status=%!STATUS!", __FILE__, __LINE__, __statusRet); +// end_wpp + +// MACRO: RETURN_NTSTATUS_MSG +// +// begin_wpp config +// FUNC RETURN_NTSTATUS_MSG{LEVEL=LEVEL_ERROR,FLAGS=FLAG_DEVICE_ALL,IFRLOG=g_SDCAVDspLog}(RETSTATUS, MSG, ...); +// USESUFFIX(RETURN_NTSTATUS_MSG, " - status=%!STATUS!", __statusRet); +// end_wpp + +#define W32 +#define WPP_CHECK_FOR_NULL_STRING //to prevent exceptions due to NULL strings + +#pragma endregion diff --git a/audio/Soundwire/Samples/SdcaVad/Package/package.VcxProj b/audio/Soundwire/Samples/SdcaVad/Package/package.VcxProj new file mode 100644 index 000000000..3125e599a --- /dev/null +++ b/audio/Soundwire/Samples/SdcaVad/Package/package.VcxProj @@ -0,0 +1,166 @@ + + + + + Debug + Win32 + + + Release + Win32 + + + Debug + x64 + + + Release + x64 + + + Debug + ARM + + + Release + ARM + + + Debug + ARM64 + + + Release + ARM64 + + + + + {E0F02048-78A4-4FE8-B863-66E6CB6A2C37} + + + {47358AD6-A48A-465B-965F-0A66F8BDFE23} + + + {98C9E1FB-3F06-4B5C-BA88-545AD0A80F94} + + + {5FDD3888-48B5-496C-83B0-E107CFFF46BC} + + + {B1B6FD46-A26E-4D07-BE2E-FD87725500DC} + + + + WindowsKernelModeDriver10.0 + Utility + Package + true + Debug + + + + {830B14D5-0E32-4F9E-AEFA-4C9F6FC13C2A} + {E9D167E6-E633-4284-8F02-FF86DF3B6D7F} + $(MSBuildProjectName) + + + Windows10 + true + + + Windows10 + false + + + Windows10 + true + + + Windows10 + false + + + Windows10 + true + + + Windows10 + false + + + Windows10 + true + + + Windows10 + false + + + + + + + + + + + DbgengKernelDebugger + False + None + + + + + + %PathToInf% + False + False + True + + 133563 + + + + sha256 + + + + + sha256 + + + + + sha256 + + + + + sha256 + + + + + sha256 + + + + + sha256 + + + + + sha256 + + + + + sha256 + + + + + + diff --git a/audio/Soundwire/Samples/SdcaVad/Package/package.VcxProj.Filters b/audio/Soundwire/Samples/SdcaVad/Package/package.VcxProj.Filters new file mode 100644 index 000000000..63b755487 --- /dev/null +++ b/audio/Soundwire/Samples/SdcaVad/Package/package.VcxProj.Filters @@ -0,0 +1,21 @@ + + + + + cpp;c;cc;cxx;def;odl;idl;hpj;bat;asm;asmx;* + {0924612D-FBA7-477E-BBB9-B3B6EA7C6B03} + + + h;hpp;hxx;hm;inl;inc;xsd + {97311738-0AD8-4F7E-AC88-9C9BEAF53AEE} + + + rc;ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe;resx;tiff;tif;png;wav;mfcribbon-ms;man;xml + {8DC1C4F0-C285-400E-A983-77A339891354} + + + inf;inv;inx;mof;mc; + {9F086F34-79D2-4FAA-87FD-C2DBEFCED0BA} + + + \ No newline at end of file diff --git a/audio/Soundwire/Samples/SdcaVad/README.md b/audio/Soundwire/Samples/SdcaVad/README.md new file mode 100644 index 000000000..e440aadf6 --- /dev/null +++ b/audio/Soundwire/Samples/SdcaVad/README.md @@ -0,0 +1,145 @@ +--- +page_type: sample +description: "The Microsoft SDCA Virtual Audio Device Driver (SdcaVad) shows how to develop ACX/SDCA audio drivers that expose support for SDCA audio devices." +languages: +- cpp +products: +- windows +- windows-wdk +urlFragment: sdcavad +--- + +# SDCA Virtual Audio Device Driver Sample + +## Introduction + +The Microsoft SDCA Virtual Audio Device Driver (SdcaVad) shows how to develop ACX/SDCA audio drivers that expose support for SDCA audio devices. + +The following table shows the features that are implemented in the various subdirectories of this sample. + +| Directory | Description | +| --- | --- | +| SdcaVCodec | SDCA Virual Codec Driver. | +| SdcaVDsp | SDCA Virual DSP Driver. | +| SdcaVXu | SDCA Virtual XU Driver. | +| Apo\kws | Sample APO that uses KSPROPERTY_INTERLEAVEDAUDIO_FORMATINFORMATION to determine if the keyword spotter pin is interleaving loopback audio with the microphone audio and identify which channels contain loopback audio. If it is interleaved the APO will strip out the loopback audio and deliver only the microphone audio upstream. Because channel data is removed, the APO negotiates an output format which is different than the input format. | +| EventDetectorAdapter | Sample Event Detector Adapter. | + +## Build the sample + +If you simply want to build this sample driver and don't intend to run or test it, then you do not need a target computer (also called a test computer). If, however, you would like to deploy, run and test this sample driver, then you need a second computer that will serve as your target computer. Instructions are provided in the **Run the sample** section to show you how to set up the target computer - also referred to as *provisioning* a target computer. + +Perform the following steps to build this sample driver. + +### 1. Open the driver solution in Visual Studio + +In Microsoft Visual Studio, Click **File** \> **Open** \> **Project/Solution...** and navigate to the folder that contains the sample files (for example, *C:\Windows-driver-samples\audio\sdcavad*). Double-click the *sdcavad* solution file. + +In Visual Studio locate the Solution Explorer. (If this is not already open, choose **Solution Explorer** from the **View** menu.) In Solution Explorer, you can see one solution that has six projects. + +### 2. Set the sample's configuration and platform + +In Solution Explorer, right-click **Solution 'sdcavad' (6 of 6 projects)**, and choose **Configuration Manager**. Make sure that the configuration and platform settings are the same for the six projects. Set the configuration to **Debug**, and the platform to **x64** for all the projects. If you make any configuration and/or platform changes for one project, you must make the same changes for all the remaining projects. + +### 3. Build the sample using Visual Studio + +In Visual Studio, click **Build** \> **Build Solution**. + +### 4. Locate the built driver package + +In File Explorer, navigate to the folder that contains the sample files. For example, you would navigate to *C:\\Windows-driver-samples\\audio\\sdcavad*, if that's the folder you specified in the preceding Step 1. + +In the folder, the location of the driver package varies depending on the configuration and platform settings that you selected in the **Configuration Manager**. For example, if you set **Debug** and **x64**, then the built driver package will be saved to a folder named *Debug* inside a folder named *x64*. Double-click the folder for the built driver package, and then double-click the folder named *package*. + +The package should contain these files: + +| File | Description | +| --- | --- | +| SdcaVCodec.sys | The SDCA Virtual Codec Driver file. | +| SdcaVCodec.inf | A information(INF) file that contians information needed to install the SDCA Virtual Codec Driver. | +| SdcaVDsp.sys | The SDCA Virtual DSP Driver file. | +| SdcaVDsp.inf | A information(INF) file that contians information needed to install the SDCA Virtual DSP Driver. | +| SdcaVXu.sys | The SDCA Virtual XU Driver file. | +| SdcaVXu.inf | A information(INF) file that contians information needed to install the SDCA Virtual XU Driver. | +| EventDetectorContosoAdapter.dll | Sample Event detector adapter. | +| SdcaVKwsApo.dll | The KWS APO. | +| SdcaVApo.inf | A information (INF) file that installs an APO device. | +| sdcavad.cat | A signed catalog file, which serves as the signature for the entire package. | + +## Run the sample + +The computer where you install the driver is called the *target computer* or the *test computer*. Typically this is a separate computer from the computer on which you develop and build the driver package. The computer where you develop and build the driver is called the *host computer*. + +The process of moving the driver package to the target computer and installing the driver is called *deploying* the driver. You can deploy the SDCA sample driver automatically or manually. + +### Prepare the target computer + +First of all, install the latest [Windows Driver Kit](https://docs.microsoft.com/windows-hardware/drivers/download-the-wdk) (WDK) on the target computer, minimum version required for the WDK is 25926, which corresponds to the canary channel. + +Before you manually deploy a driver, you must prepare the target computer by turning on test signing and by installing a certificate. You also need to locate the DevCon tool in your WDK installation. After that you're ready to run the built driver sample. + +Open a Command Prompt window as Administrator. Then enter the following command: + +`bcdedit /set TESTSIGNING ON` + +and reboot the target computer. + +> [!IMPORTANT] +> Before using BCDEdit to change boot information you may need to temporarily suspend Windows security features such as BitLocker and Secure Boot on the test PC. + +Re-enable these security features when testing is complete and appropriately manage the test PC, when the security features are disabled. + +After rebooting, navigate to the Tools folder in your WDK installation and locate the DevCon tool. For example, look in the following folder: + +C:\\Program Files (x86)\\Windows Kits\\10\\Tools\\x64\\devcon.exe + +Copy *devcon.exe* to a folder on the target computer where it is easier to find. For example, create a *C:\\Tools* folder and copy *devcon.exe* to that folder. + +Create a folder on the target for the built driver package (for example, *C:\\SdcaVad*). Copy all the files from the built driver package on the host computer to the folder that you created on the target computer. + +Create a folder on the target computer for the certificate created by the build process. For example, you could create a folder named *C:\\Certificates* on the target computer, and then copy *package.cer* to it from the host computer. You can find this certificate in the same folder on the host computer, as the *package* folder that contains the built driver files. On the target computer, right-click the certificate file, and click **Install**, then follow the prompts to install the test certificate. + +If you need more detailed instructions for setting up the target computer, see [Preparing a Computer for Manual Driver Deployment](https://docs.microsoft.com/windows-hardware/drivers/develop/preparing-a-computer-for-manual-driver-deployment). + +#### A note on signatures + +Since most of these binary files are executed in kernel mode, it is important that they are signed and, optionally, to have a kernel debugger attached. + +Without any signature or kernel debugger, the driver will not be installed in the target computer. With a kernel debugger attached, the driver can be installed and the driver files (.sys extension) would be loaded, but any user mode files (.dll files) will not be loaded. + +The only way of installing and executing the whole driver sample is to have all the files (.sys, .dll and .cat) signed with a trusted certificate. This will allow the entire driver to be loaded even without a kernel debugger attached. + +For more information on the subject, see [Driver signing](https://docs.microsoft.com/windows-hardware/drivers/install/driver-signing). + +### Install the driver + +#### Single INF files + +Each sample driver contains an INF file, which will install the sample driver. + +On the target computer, open a Command Prompt window as Administrator. Navigate to your driver package folder, and enter the following command: + +`devcon install SdcaVDsp.inf SOUNDWIRETEST\DSP` +`devcon install SdcaVCodec.inf Root\SDCAVCodec` + +Then, the XU INF (*SdcaVXu.inf*) and the APO INF (*SdcaVApo.inf*) can be installed - right-click the INF file and select **Install** to install it. + +After successfully installing the sample drivers, you're now ready to test it. + +### Test the driver + +On the target computer, in a Command Prompt window, enter **devmgmt.msc** to open Device Manager. In Device Manager, on the **View** menu, choose **Devices by type**. In the device tree, locate *SDCA Virtual Dsp Audio Driver*. This is typically under the **Sound, video and game controllers** node. + +On the target computer, open Control Panel and navigate to **Hardware and Sound** \> **Manage audio devices**. In the Sound dialog box, select the speaker icon labeled as *SDCA Virtual Codec Audio Driver*, then click **Set Default**, but do not click **OK**. This will keep the Sound dialog box open. + +Locate an MP3 or other audio file on the target computer and double-click to play it. Then in the Sound dialog box, verify that there is activity in the volume level indicator associated with the *SDCA Virtual Codec Audio Driver* driver. + +## HLK testing + +The sample uploaded here is tested using the latest HLK version available to make sure it passes all audio tests in the current playlist. However, since it is a virtual audio driver it does not implement audio mixing and simulates capture and loopback by generating a tone. Given these limitations, there are some HLK tests that are expected to fail because they rely on the described functionality. + +In the case of audio tests, one of these exceptions is the Hardware Offload of Audio Processing Test. This test is aimed at devices that support offload capabilities and performs checks to make sure that the device complies with the appropiate requirements. In the particular case of SdcaVad, this test will fail for endpoints with offload and loopback. + +For endpoints with offload, the test will fail because the driver includes offload pins but it does not implement a mixer with volume, mute and peak meter nodes, etc. For the case of endpoints with loopback, the test will fail because the driver simulates loopback by returning a sine tone instead of performing real mixing of streams in host and/or offload pins. + +Besides, the current version of SdcaVad also failed the General Audio Test and the Device Power State Transition Test and we're investigating the failures. diff --git a/audio/Soundwire/Samples/SdcaVad/SDCAVad.sln b/audio/Soundwire/Samples/SdcaVad/SDCAVad.sln new file mode 100644 index 000000000..468a8e131 --- /dev/null +++ b/audio/Soundwire/Samples/SdcaVad/SDCAVad.sln @@ -0,0 +1,185 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio Version 16 +VisualStudioVersion = 16.0.29613.14 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "EventDetectorAdapter", "EventDetectorAdapter", "{A54839B3-5655-40FE-A908-3D4541341F3D}" +EndProject +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "EventDetectorContosoAdapter", "EventDetectorAdapter\EventDetectorContosoAdapter.vcxproj", "{E0F02048-78A4-4FE8-B863-66E6CB6A2C37}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Apo", "Apo", "{439D009C-33A7-49D5-991B-DC90856A4556}" +EndProject +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "KWSApo", "Apo\kws\KWSApo.vcxproj", "{47358AD6-A48A-465B-965F-0A66F8BDFE23}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Package", "Package", "{87A26F2D-E1DB-43B1-9DF8-84535829D72F}" +EndProject +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "package", "Package\package.VcxProj", "{830B14D5-0E32-4F9E-AEFA-4C9F6FC13C2A}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "SdcaVCodec", "SdcaVCodec", "{604DD19C-D187-49E3-B99C-40EF15BE99AB}" +EndProject +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "SDCAVCodec", "SdcaVCodec\SDCAVCodec.vcxproj", "{98C9E1FB-3F06-4B5C-BA88-545AD0A80F94}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "SdcaVDsp", "SdcaVDsp", "{E9F3F705-E937-4B89-B23B-F0DC1AB32AE2}" +EndProject +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "SDCAVDsp", "SdcaVDsp\SDCAVDsp.vcxproj", "{5FDD3888-48B5-496C-83B0-E107CFFF46BC}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "SdcaVXu", "SdcaVXu", "{ECBA917D-ECAD-48BF-AA29-EEB0D1DA728F}" +EndProject +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "SDCAVXu", "SdcaVXu\SDCAVXu.vcxproj", "{B1B6FD46-A26E-4D07-BE2E-FD87725500DC}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|ARM = Debug|ARM + Debug|ARM64 = Debug|ARM64 + Debug|x64 = Debug|x64 + Debug|Win32 = Debug|Win32 + Release|ARM = Release|ARM + Release|ARM64 = Release|ARM64 + Release|x64 = Release|x64 + Release|Win32 = Release|Win32 + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {E0F02048-78A4-4FE8-B863-66E6CB6A2C37}.Debug|ARM.ActiveCfg = Debug|ARM + {E0F02048-78A4-4FE8-B863-66E6CB6A2C37}.Debug|ARM.Build.0 = Debug|ARM + {E0F02048-78A4-4FE8-B863-66E6CB6A2C37}.Debug|ARM64.ActiveCfg = Debug|ARM64 + {E0F02048-78A4-4FE8-B863-66E6CB6A2C37}.Debug|ARM64.Build.0 = Debug|ARM64 + {E0F02048-78A4-4FE8-B863-66E6CB6A2C37}.Debug|x64.ActiveCfg = Debug|x64 + {E0F02048-78A4-4FE8-B863-66E6CB6A2C37}.Debug|x64.Build.0 = Debug|x64 + {E0F02048-78A4-4FE8-B863-66E6CB6A2C37}.Debug|Win32.ActiveCfg = Debug|Win32 + {E0F02048-78A4-4FE8-B863-66E6CB6A2C37}.Debug|Win32.Build.0 = Debug|Win32 + {E0F02048-78A4-4FE8-B863-66E6CB6A2C37}.Release|ARM.ActiveCfg = Release|ARM + {E0F02048-78A4-4FE8-B863-66E6CB6A2C37}.Release|ARM.Build.0 = Release|ARM + {E0F02048-78A4-4FE8-B863-66E6CB6A2C37}.Release|ARM64.ActiveCfg = Release|ARM64 + {E0F02048-78A4-4FE8-B863-66E6CB6A2C37}.Release|ARM64.Build.0 = Release|ARM64 + {E0F02048-78A4-4FE8-B863-66E6CB6A2C37}.Release|x64.ActiveCfg = Release|x64 + {E0F02048-78A4-4FE8-B863-66E6CB6A2C37}.Release|x64.Build.0 = Release|x64 + {E0F02048-78A4-4FE8-B863-66E6CB6A2C37}.Release|Win32.ActiveCfg = Release|Win32 + {E0F02048-78A4-4FE8-B863-66E6CB6A2C37}.Release|Win32.Build.0 = Release|Win32 + {47358AD6-A48A-465B-965F-0A66F8BDFE23}.Debug|ARM.ActiveCfg = Debug|ARM + {47358AD6-A48A-465B-965F-0A66F8BDFE23}.Debug|ARM.Build.0 = Debug|ARM + {47358AD6-A48A-465B-965F-0A66F8BDFE23}.Debug|ARM64.ActiveCfg = Debug|ARM64 + {47358AD6-A48A-465B-965F-0A66F8BDFE23}.Debug|ARM64.Build.0 = Debug|ARM64 + {47358AD6-A48A-465B-965F-0A66F8BDFE23}.Debug|x64.ActiveCfg = Debug|x64 + {47358AD6-A48A-465B-965F-0A66F8BDFE23}.Debug|x64.Build.0 = Debug|x64 + {47358AD6-A48A-465B-965F-0A66F8BDFE23}.Debug|Win32.ActiveCfg = Debug|Win32 + {47358AD6-A48A-465B-965F-0A66F8BDFE23}.Debug|Win32.Build.0 = Debug|Win32 + {47358AD6-A48A-465B-965F-0A66F8BDFE23}.Release|ARM.ActiveCfg = Release|ARM + {47358AD6-A48A-465B-965F-0A66F8BDFE23}.Release|ARM.Build.0 = Release|ARM + {47358AD6-A48A-465B-965F-0A66F8BDFE23}.Release|ARM64.ActiveCfg = Release|ARM64 + {47358AD6-A48A-465B-965F-0A66F8BDFE23}.Release|ARM64.Build.0 = Release|ARM64 + {47358AD6-A48A-465B-965F-0A66F8BDFE23}.Release|x64.ActiveCfg = Release|x64 + {47358AD6-A48A-465B-965F-0A66F8BDFE23}.Release|x64.Build.0 = Release|x64 + {47358AD6-A48A-465B-965F-0A66F8BDFE23}.Release|Win32.ActiveCfg = Release|Win32 + {47358AD6-A48A-465B-965F-0A66F8BDFE23}.Release|Win32.Build.0 = Release|Win32 + {830B14D5-0E32-4F9E-AEFA-4C9F6FC13C2A}.Debug|ARM.ActiveCfg = Debug|ARM + {830B14D5-0E32-4F9E-AEFA-4C9F6FC13C2A}.Debug|ARM.Build.0 = Debug|ARM + {830B14D5-0E32-4F9E-AEFA-4C9F6FC13C2A}.Debug|ARM.Deploy.0 = Debug|ARM + {830B14D5-0E32-4F9E-AEFA-4C9F6FC13C2A}.Debug|ARM64.ActiveCfg = Debug|ARM64 + {830B14D5-0E32-4F9E-AEFA-4C9F6FC13C2A}.Debug|ARM64.Build.0 = Debug|ARM64 + {830B14D5-0E32-4F9E-AEFA-4C9F6FC13C2A}.Debug|ARM64.Deploy.0 = Debug|ARM64 + {830B14D5-0E32-4F9E-AEFA-4C9F6FC13C2A}.Debug|x64.ActiveCfg = Debug|x64 + {830B14D5-0E32-4F9E-AEFA-4C9F6FC13C2A}.Debug|x64.Build.0 = Debug|x64 + {830B14D5-0E32-4F9E-AEFA-4C9F6FC13C2A}.Debug|x64.Deploy.0 = Debug|x64 + {830B14D5-0E32-4F9E-AEFA-4C9F6FC13C2A}.Debug|Win32.ActiveCfg = Debug|Win32 + {830B14D5-0E32-4F9E-AEFA-4C9F6FC13C2A}.Debug|Win32.Build.0 = Debug|Win32 + {830B14D5-0E32-4F9E-AEFA-4C9F6FC13C2A}.Debug|Win32.Deploy.0 = Debug|Win32 + {830B14D5-0E32-4F9E-AEFA-4C9F6FC13C2A}.Release|ARM.ActiveCfg = Release|ARM + {830B14D5-0E32-4F9E-AEFA-4C9F6FC13C2A}.Release|ARM.Build.0 = Release|ARM + {830B14D5-0E32-4F9E-AEFA-4C9F6FC13C2A}.Release|ARM.Deploy.0 = Release|ARM + {830B14D5-0E32-4F9E-AEFA-4C9F6FC13C2A}.Release|ARM64.ActiveCfg = Release|ARM64 + {830B14D5-0E32-4F9E-AEFA-4C9F6FC13C2A}.Release|ARM64.Build.0 = Release|ARM64 + {830B14D5-0E32-4F9E-AEFA-4C9F6FC13C2A}.Release|ARM64.Deploy.0 = Release|ARM64 + {830B14D5-0E32-4F9E-AEFA-4C9F6FC13C2A}.Release|x64.ActiveCfg = Release|x64 + {830B14D5-0E32-4F9E-AEFA-4C9F6FC13C2A}.Release|x64.Build.0 = Release|x64 + {830B14D5-0E32-4F9E-AEFA-4C9F6FC13C2A}.Release|x64.Deploy.0 = Release|x64 + {830B14D5-0E32-4F9E-AEFA-4C9F6FC13C2A}.Release|Win32.ActiveCfg = Release|Win32 + {830B14D5-0E32-4F9E-AEFA-4C9F6FC13C2A}.Release|Win32.Build.0 = Release|Win32 + {830B14D5-0E32-4F9E-AEFA-4C9F6FC13C2A}.Release|Win32.Deploy.0 = Release|Win32 + {98C9E1FB-3F06-4B5C-BA88-545AD0A80F94}.Debug|ARM.ActiveCfg = Debug|ARM + {98C9E1FB-3F06-4B5C-BA88-545AD0A80F94}.Debug|ARM.Build.0 = Debug|ARM + {98C9E1FB-3F06-4B5C-BA88-545AD0A80F94}.Debug|ARM.Deploy.0 = Debug|ARM + {98C9E1FB-3F06-4B5C-BA88-545AD0A80F94}.Debug|ARM64.ActiveCfg = Debug|ARM64 + {98C9E1FB-3F06-4B5C-BA88-545AD0A80F94}.Debug|ARM64.Build.0 = Debug|ARM64 + {98C9E1FB-3F06-4B5C-BA88-545AD0A80F94}.Debug|ARM64.Deploy.0 = Debug|ARM64 + {98C9E1FB-3F06-4B5C-BA88-545AD0A80F94}.Debug|x64.ActiveCfg = Debug|x64 + {98C9E1FB-3F06-4B5C-BA88-545AD0A80F94}.Debug|x64.Build.0 = Debug|x64 + {98C9E1FB-3F06-4B5C-BA88-545AD0A80F94}.Debug|x64.Deploy.0 = Debug|x64 + {98C9E1FB-3F06-4B5C-BA88-545AD0A80F94}.Debug|Win32.ActiveCfg = Debug|Win32 + {98C9E1FB-3F06-4B5C-BA88-545AD0A80F94}.Debug|Win32.Build.0 = Debug|Win32 + {98C9E1FB-3F06-4B5C-BA88-545AD0A80F94}.Debug|Win32.Deploy.0 = Debug|Win32 + {98C9E1FB-3F06-4B5C-BA88-545AD0A80F94}.Release|ARM.ActiveCfg = Release|ARM + {98C9E1FB-3F06-4B5C-BA88-545AD0A80F94}.Release|ARM.Build.0 = Release|ARM + {98C9E1FB-3F06-4B5C-BA88-545AD0A80F94}.Release|ARM.Deploy.0 = Release|ARM + {98C9E1FB-3F06-4B5C-BA88-545AD0A80F94}.Release|ARM64.ActiveCfg = Release|ARM64 + {98C9E1FB-3F06-4B5C-BA88-545AD0A80F94}.Release|ARM64.Build.0 = Release|ARM64 + {98C9E1FB-3F06-4B5C-BA88-545AD0A80F94}.Release|ARM64.Deploy.0 = Release|ARM64 + {98C9E1FB-3F06-4B5C-BA88-545AD0A80F94}.Release|x64.ActiveCfg = Release|x64 + {98C9E1FB-3F06-4B5C-BA88-545AD0A80F94}.Release|x64.Build.0 = Release|x64 + {98C9E1FB-3F06-4B5C-BA88-545AD0A80F94}.Release|x64.Deploy.0 = Release|x64 + {98C9E1FB-3F06-4B5C-BA88-545AD0A80F94}.Release|Win32.ActiveCfg = Release|Win32 + {98C9E1FB-3F06-4B5C-BA88-545AD0A80F94}.Release|Win32.Build.0 = Release|Win32 + {98C9E1FB-3F06-4B5C-BA88-545AD0A80F94}.Release|Win32.Deploy.0 = Release|Win32 + {5FDD3888-48B5-496C-83B0-E107CFFF46BC}.Debug|ARM.ActiveCfg = Debug|ARM + {5FDD3888-48B5-496C-83B0-E107CFFF46BC}.Debug|ARM.Build.0 = Debug|ARM + {5FDD3888-48B5-496C-83B0-E107CFFF46BC}.Debug|ARM.Deploy.0 = Debug|ARM + {5FDD3888-48B5-496C-83B0-E107CFFF46BC}.Debug|ARM64.ActiveCfg = Debug|ARM64 + {5FDD3888-48B5-496C-83B0-E107CFFF46BC}.Debug|ARM64.Build.0 = Debug|ARM64 + {5FDD3888-48B5-496C-83B0-E107CFFF46BC}.Debug|ARM64.Deploy.0 = Debug|ARM64 + {5FDD3888-48B5-496C-83B0-E107CFFF46BC}.Debug|x64.ActiveCfg = Debug|x64 + {5FDD3888-48B5-496C-83B0-E107CFFF46BC}.Debug|x64.Build.0 = Debug|x64 + {5FDD3888-48B5-496C-83B0-E107CFFF46BC}.Debug|x64.Deploy.0 = Debug|x64 + {5FDD3888-48B5-496C-83B0-E107CFFF46BC}.Debug|Win32.ActiveCfg = Debug|Win32 + {5FDD3888-48B5-496C-83B0-E107CFFF46BC}.Debug|Win32.Build.0 = Debug|Win32 + {5FDD3888-48B5-496C-83B0-E107CFFF46BC}.Debug|Win32.Deploy.0 = Debug|Win32 + {5FDD3888-48B5-496C-83B0-E107CFFF46BC}.Release|ARM.ActiveCfg = Release|ARM + {5FDD3888-48B5-496C-83B0-E107CFFF46BC}.Release|ARM.Build.0 = Release|ARM + {5FDD3888-48B5-496C-83B0-E107CFFF46BC}.Release|ARM.Deploy.0 = Release|ARM + {5FDD3888-48B5-496C-83B0-E107CFFF46BC}.Release|ARM64.ActiveCfg = Release|ARM64 + {5FDD3888-48B5-496C-83B0-E107CFFF46BC}.Release|ARM64.Build.0 = Release|ARM64 + {5FDD3888-48B5-496C-83B0-E107CFFF46BC}.Release|ARM64.Deploy.0 = Release|ARM64 + {5FDD3888-48B5-496C-83B0-E107CFFF46BC}.Release|x64.ActiveCfg = Release|x64 + {5FDD3888-48B5-496C-83B0-E107CFFF46BC}.Release|x64.Build.0 = Release|x64 + {5FDD3888-48B5-496C-83B0-E107CFFF46BC}.Release|x64.Deploy.0 = Release|x64 + {5FDD3888-48B5-496C-83B0-E107CFFF46BC}.Release|Win32.ActiveCfg = Release|Win32 + {5FDD3888-48B5-496C-83B0-E107CFFF46BC}.Release|Win32.Build.0 = Release|Win32 + {5FDD3888-48B5-496C-83B0-E107CFFF46BC}.Release|Win32.Deploy.0 = Release|Win32 + {B1B6FD46-A26E-4D07-BE2E-FD87725500DC}.Debug|ARM.ActiveCfg = Debug|ARM + {B1B6FD46-A26E-4D07-BE2E-FD87725500DC}.Debug|ARM.Build.0 = Debug|ARM + {B1B6FD46-A26E-4D07-BE2E-FD87725500DC}.Debug|ARM.Deploy.0 = Debug|ARM + {B1B6FD46-A26E-4D07-BE2E-FD87725500DC}.Debug|ARM64.ActiveCfg = Debug|ARM64 + {B1B6FD46-A26E-4D07-BE2E-FD87725500DC}.Debug|ARM64.Build.0 = Debug|ARM64 + {B1B6FD46-A26E-4D07-BE2E-FD87725500DC}.Debug|ARM64.Deploy.0 = Debug|ARM64 + {B1B6FD46-A26E-4D07-BE2E-FD87725500DC}.Debug|x64.ActiveCfg = Debug|x64 + {B1B6FD46-A26E-4D07-BE2E-FD87725500DC}.Debug|x64.Build.0 = Debug|x64 + {B1B6FD46-A26E-4D07-BE2E-FD87725500DC}.Debug|x64.Deploy.0 = Debug|x64 + {B1B6FD46-A26E-4D07-BE2E-FD87725500DC}.Debug|Win32.ActiveCfg = Debug|Win32 + {B1B6FD46-A26E-4D07-BE2E-FD87725500DC}.Debug|Win32.Build.0 = Debug|Win32 + {B1B6FD46-A26E-4D07-BE2E-FD87725500DC}.Debug|Win32.Deploy.0 = Debug|Win32 + {B1B6FD46-A26E-4D07-BE2E-FD87725500DC}.Release|ARM.ActiveCfg = Release|ARM + {B1B6FD46-A26E-4D07-BE2E-FD87725500DC}.Release|ARM.Build.0 = Release|ARM + {B1B6FD46-A26E-4D07-BE2E-FD87725500DC}.Release|ARM.Deploy.0 = Release|ARM + {B1B6FD46-A26E-4D07-BE2E-FD87725500DC}.Release|ARM64.ActiveCfg = Release|ARM64 + {B1B6FD46-A26E-4D07-BE2E-FD87725500DC}.Release|ARM64.Build.0 = Release|ARM64 + {B1B6FD46-A26E-4D07-BE2E-FD87725500DC}.Release|ARM64.Deploy.0 = Release|ARM64 + {B1B6FD46-A26E-4D07-BE2E-FD87725500DC}.Release|x64.ActiveCfg = Release|x64 + {B1B6FD46-A26E-4D07-BE2E-FD87725500DC}.Release|x64.Build.0 = Release|x64 + {B1B6FD46-A26E-4D07-BE2E-FD87725500DC}.Release|x64.Deploy.0 = Release|x64 + {B1B6FD46-A26E-4D07-BE2E-FD87725500DC}.Release|Win32.ActiveCfg = Release|Win32 + {B1B6FD46-A26E-4D07-BE2E-FD87725500DC}.Release|Win32.Build.0 = Release|Win32 + {B1B6FD46-A26E-4D07-BE2E-FD87725500DC}.Release|Win32.Deploy.0 = Release|Win32 + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection + GlobalSection(NestedProjects) = preSolution + {E0F02048-78A4-4FE8-B863-66E6CB6A2C37} = {A54839B3-5655-40FE-A908-3D4541341F3D} + {47358AD6-A48A-465B-965F-0A66F8BDFE23} = {439D009C-33A7-49D5-991B-DC90856A4556} + {830B14D5-0E32-4F9E-AEFA-4C9F6FC13C2A} = {87A26F2D-E1DB-43B1-9DF8-84535829D72F} + {98C9E1FB-3F06-4B5C-BA88-545AD0A80F94} = {604DD19C-D187-49E3-B99C-40EF15BE99AB} + {5FDD3888-48B5-496C-83B0-E107CFFF46BC} = {E9F3F705-E937-4B89-B23B-F0DC1AB32AE2} + {B1B6FD46-A26E-4D07-BE2E-FD87725500DC} = {ECBA917D-ECAD-48BF-AA29-EEB0D1DA728F} + EndGlobalSection + GlobalSection(ExtensibilityGlobals) = postSolution + SolutionGuid = {33CFA437-4CF0-4818-810F-4D78CF082C35} + EndGlobalSection +EndGlobal diff --git a/audio/Soundwire/Samples/SdcaVad/SdcaVCodec/CircuitHelper.cpp b/audio/Soundwire/Samples/SdcaVad/SdcaVCodec/CircuitHelper.cpp new file mode 100644 index 000000000..e7b36b1f9 --- /dev/null +++ b/audio/Soundwire/Samples/SdcaVad/SdcaVCodec/CircuitHelper.cpp @@ -0,0 +1,286 @@ +/*++ + + THIS CODE AND INFORMATION IS PROVIDED "AS IS" WITHOUT WARRANTY OF ANY + KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE + IMPLIED WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A PARTICULAR + PURPOSE. + +Module Name: + + CircuitHelper.cpp + +Abstract: + + This module contains helper functions for device.cpp and render.cpp files. + +Environment: + + Kernel mode + +--*/ + +#include "private.h" +#include "CircuitHelper.h" + +#ifndef __INTELLISENSE__ +#include "CircuitHelper.tmh" +#endif + +PAGED_CODE_SEG +NTSTATUS CreateRenderCircuit( + _In_ PACXCIRCUIT_INIT CircuitInit, + _In_ UNICODE_STRING CircuitName, + _In_ WDFDEVICE Device, + _Out_ ACXCIRCUIT* Circuit +) +{ + + PAGED_CODE(); + + NTSTATUS status = STATUS_SUCCESS; + + WDF_OBJECT_ATTRIBUTES attributes; + WDF_OBJECT_ATTRIBUTES_INIT(&attributes); + WDF_OBJECT_ATTRIBUTES_INIT_CONTEXT_TYPE(&attributes, CODEC_RENDER_CIRCUIT_CONTEXT); + + RETURN_NTSTATUS_IF_FAILED(AcxCircuitInitAssignName(CircuitInit, &CircuitName)); + + // + // Add circuit type. + // + AcxCircuitInitSetCircuitType(CircuitInit, AcxCircuitTypeRender); + + // + // Assign the circuit's pnp-power callbacks. + // + { + ACX_CIRCUIT_PNPPOWER_CALLBACKS powerCallbacks; + ACX_CIRCUIT_PNPPOWER_CALLBACKS_INIT(&powerCallbacks); + powerCallbacks.EvtAcxCircuitPowerUp = CodecR_EvtCircuitPowerUp; + powerCallbacks.EvtAcxCircuitPowerDown = CodecR_EvtCircuitPowerDown; + AcxCircuitInitSetAcxCircuitPnpPowerCallbacks(CircuitInit, &powerCallbacks); + } + + // + // Assign the circuit's composite callbacks. + // + { + ACX_CIRCUIT_COMPOSITE_CALLBACKS compositeCallbacks; + ACX_CIRCUIT_COMPOSITE_CALLBACKS_INIT(&compositeCallbacks); + compositeCallbacks.EvtAcxCircuitCompositeCircuitInitialize = CodecR_EvtCircuitCompositeCircuitInitialize; + compositeCallbacks.EvtAcxCircuitCompositeInitialize = CodecR_EvtCircuitCompositeInitialize; + AcxCircuitInitSetAcxCircuitCompositeCallbacks(CircuitInit, &compositeCallbacks); + } + + // + // Set circuit-callbacks. + // + RETURN_NTSTATUS_IF_FAILED(AcxCircuitInitAssignAcxRequestPreprocessCallback( + CircuitInit, + CodecR_EvtCircuitRequestPreprocess, + (ACXCONTEXT)AcxRequestTypeAny, // dbg only + AcxRequestTypeAny, + NULL, + AcxItemIdNone)); + + RETURN_NTSTATUS_IF_FAILED(AcxCircuitInitAssignAcxCreateStreamCallback( + CircuitInit, + CodecR_EvtCircuitCreateStream)); + + // + // Create the circuit. + // + WDF_OBJECT_ATTRIBUTES_INIT_CONTEXT_TYPE(&attributes, CODEC_RENDER_CIRCUIT_CONTEXT); + RETURN_NTSTATUS_IF_FAILED(AcxCircuitCreate(Device, &attributes, &CircuitInit, Circuit)); + + return status; +} + +PAGED_CODE_SEG +NTSTATUS ConnectRenderCircuitElements( + _In_ ULONG ElementCount, + _In_reads_(ElementCount) ACXELEMENT* Elements, + _In_ ACXCIRCUIT Circuit +) +{ + PAGED_CODE(); + + NTSTATUS status = STATUS_SUCCESS; + + // + // Explicitly connect the circuit/elements. Note that driver doens't + // need to perform this step when circuit/elements are connected in the + // same order as they were added to the circuit. By default ACX connects + // the elements starting from the sink circuit pin and ending with the + // source circuit pin for both render and capture devices. + // + // circuit.pin[default_sink] -> 1st element.pin[default_in] + // 1st element.pin[default_out] -> 2nd element.pin[default_in] + // 2nd element.pin[default_out] -> circuit.pin[default_source] + // + const int numElements = 2; + const int numConnections = numElements + 1; + + ACX_CONNECTION connections[numConnections]; + ACX_CONNECTION_INIT(&connections[0], Circuit, Elements[ElementCount - 2]); + ACX_CONNECTION_INIT(&connections[1], Elements[ElementCount - 2], Elements[ElementCount - 1]); + ACX_CONNECTION_INIT(&connections[2], Elements[ElementCount - 1], Circuit); + + // + // Add the connections linking circuit to elements. + // + RETURN_NTSTATUS_IF_FAILED(AcxCircuitAddConnections(Circuit, connections, SIZEOF_ARRAY(connections))); + + return status; +} + +PAGED_CODE_SEG +NTSTATUS ObjBagAddBlob( + _In_ ACXOBJECTBAG ObjBag, + _In_z_ const char* Blob +) +{ + PAGED_CODE(); + + NTSTATUS status = STATUS_SUCCESS; + + DECLARE_CONST_ACXOBJECTBAG_SYSTEM_PROPERTY_NAME(VendorPropertiesBlock); + STRING vendorBlob; + RtlInitString(&vendorBlob, Blob); + WDFMEMORY vendorBlobMem; + RETURN_NTSTATUS_IF_FAILED(WdfMemoryCreatePreallocated(NULL, vendorBlob.Buffer, vendorBlob.MaximumLength, &vendorBlobMem)); + RETURN_NTSTATUS_IF_FAILED(AcxObjectBagAddBlob(ObjBag, &VendorPropertiesBlock, vendorBlobMem)); + WdfObjectDelete(vendorBlobMem); + + return status; +} + +PAGED_CODE_SEG +NTSTATUS ObjBagAddEndpointId( + _In_ ACXOBJECTBAG ObjBag, + _In_ UINT Value +) +{ + PAGED_CODE(); + + NTSTATUS status = STATUS_SUCCESS; + + DECLARE_CONST_ACXOBJECTBAG_SOUNDWIRE_PROPERTY_NAME(EndpointId); + RETURN_NTSTATUS_IF_FAILED(AcxObjectBagAddUI4(ObjBag, &EndpointId, Value)); + + return status; +} + +PAGED_CODE_SEG +NTSTATUS ObjBagAddDataPortNumber( + _In_ ACXOBJECTBAG ObjBag, + _In_ UINT Value +) +{ + PAGED_CODE(); + + NTSTATUS status = STATUS_SUCCESS; + + DECLARE_CONST_ACXOBJECTBAG_SOUNDWIRE_PROPERTY_NAME(DataPortNumber); + RETURN_NTSTATUS_IF_FAILED(AcxObjectBagAddUI4(ObjBag, &DataPortNumber, Value)); + + return status; +} + +PAGED_CODE_SEG +NTSTATUS ObjBagAddTestUI4( + _In_ ACXOBJECTBAG ObjBag, + _In_ UINT Value +) +{ + PAGED_CODE(); + + NTSTATUS status = STATUS_SUCCESS; + + DECLARE_CONST_ACXOBJECTBAG_DRIVER_PROPERTY_NAME(msft, TestUI4); + RETURN_NTSTATUS_IF_FAILED(AcxObjectBagAddUI4(ObjBag, &TestUI4, Value)); + + return status; +} + + +PAGED_CODE_SEG +NTSTATUS ObjBagAddCircuitId( + _In_ ACXOBJECTBAG ObjBag, + _In_ GUID Guid +) +{ + PAGED_CODE(); + + NTSTATUS status = STATUS_SUCCESS; + + DECLARE_CONST_ACXOBJECTBAG_DRIVER_PROPERTY_NAME(msft, CircuitId); + RETURN_NTSTATUS_IF_FAILED(AcxObjectBagAddGuid(ObjBag, &CircuitId, Guid)); + + return status; +} + +PAGED_CODE_SEG +NTSTATUS ObjBagAddUnicodeStrings( + _In_ ACXOBJECTBAG ObjBag, + _In_ UNICODE_STRING FriendlyNameStr, + _In_ UNICODE_STRING NameStr +) +{ + PAGED_CODE(); + + NTSTATUS status = STATUS_SUCCESS; + + DECLARE_CONST_ACXOBJECTBAG_SYSTEM_PROPERTY_NAME(FriendlyName); + RETURN_NTSTATUS_IF_FAILED(AcxObjectBagAddUnicodeString(ObjBag, &FriendlyName, &FriendlyNameStr)); + + DECLARE_CONST_ACXOBJECTBAG_SYSTEM_PROPERTY_NAME(Name); + RETURN_NTSTATUS_IF_FAILED(AcxObjectBagAddUnicodeString(ObjBag, &Name, &NameStr)); + + return status; +} + +PAGED_CODE_SEG +NTSTATUS AddJack( + _In_ WDF_OBJECT_ATTRIBUTES Attributes, + _In_ ACXPIN Pin, + _In_ ULONG ChannelMapping, + _In_ ULONG Color, + _In_ ACX_JACK_CONNECTION_TYPE ConnectionType, + _In_ ACX_JACK_GEO_LOCATION GeoLocation, + _In_ ACX_JACK_GEN_LOCATION GenLocation, + _In_ ACX_JACK_PORT_CONNECTION PortConnection +) +{ + PAGED_CODE(); + + NTSTATUS status = STATUS_SUCCESS; + + ACX_JACK_CONFIG jackCfg; + ACX_JACK_CONFIG_INIT(&jackCfg); + jackCfg.Description.ChannelMapping = ChannelMapping; + jackCfg.Description.Color = Color; + jackCfg.Description.ConnectionType = ConnectionType; + jackCfg.Description.GeoLocation = GeoLocation; + jackCfg.Description.GenLocation = GenLocation; + jackCfg.Description.PortConnection = PortConnection; + + WDF_OBJECT_ATTRIBUTES_INIT_CONTEXT_TYPE(&Attributes, CODEC_JACK_CONTEXT); + Attributes.ParentObject = Pin; + + ACXJACK jack; + RETURN_NTSTATUS_IF_FAILED(AcxJackCreate(Pin, &Attributes, &jackCfg, &jack)); + + ASSERT(jack != NULL); + + PCODEC_JACK_CONTEXT jackCtx; + jackCtx = GetCodecJackContext(jack); + ASSERT(jackCtx); + jackCtx->Dummy = 0; + + RETURN_NTSTATUS_IF_FAILED(AcxPinAddJacks(Pin, &jack, 1)); + + return status; +} + + diff --git a/audio/Soundwire/Samples/SdcaVad/SdcaVCodec/CircuitHelper.h b/audio/Soundwire/Samples/SdcaVad/SdcaVCodec/CircuitHelper.h new file mode 100644 index 000000000..c3776c1cc --- /dev/null +++ b/audio/Soundwire/Samples/SdcaVad/SdcaVCodec/CircuitHelper.h @@ -0,0 +1,84 @@ +/*++ + + THIS CODE AND INFORMATION IS PROVIDED "AS IS" WITHOUT WARRANTY OF ANY + KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE + IMPLIED WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A PARTICULAR + PURPOSE. + +Module Name: + + CircuitHelper.h + +Abstract: + + This module contains helper functions for device.cpp and render.cpp files. + +Environment: + + Kernel mode + +--*/ + +PAGED_CODE_SEG +NTSTATUS CreateRenderCircuit( + _In_ PACXCIRCUIT_INIT CircuitInit, + _In_ UNICODE_STRING CircuitName, + _In_ WDFDEVICE Device, + _Out_ ACXCIRCUIT* Circuit +); + +PAGED_CODE_SEG +NTSTATUS ConnectRenderCircuitElements( + _In_ ULONG ElementCount, + _In_reads_(ElementCount) ACXELEMENT* Elements, + _In_ ACXCIRCUIT Circuit +); + +PAGED_CODE_SEG +NTSTATUS ObjBagAddBlob( + _In_ ACXOBJECTBAG ObjBag, + _In_z_ const char* Blob +); + +PAGED_CODE_SEG +NTSTATUS ObjBagAddEndpointId( + _In_ ACXOBJECTBAG ObjBag, + _In_ UINT Value +); + +PAGED_CODE_SEG +NTSTATUS ObjBagAddDataPortNumber( + _In_ ACXOBJECTBAG ObjBag, + _In_ UINT Value +); + +PAGED_CODE_SEG +NTSTATUS ObjBagAddTestUI4( + _In_ ACXOBJECTBAG ObjBag, + _In_ UINT Value +); + +PAGED_CODE_SEG +NTSTATUS ObjBagAddCircuitId( + _In_ ACXOBJECTBAG ObjBag, + _In_ GUID Guid +); + +PAGED_CODE_SEG +NTSTATUS ObjBagAddUnicodeStrings( + _In_ ACXOBJECTBAG ObjBag, + _In_ UNICODE_STRING FriendlyNameStr, + _In_ UNICODE_STRING NameStr +); + +PAGED_CODE_SEG +NTSTATUS AddJack( + _In_ WDF_OBJECT_ATTRIBUTES Attributes, + _In_ ACXPIN Pin, + _In_ ULONG ChannelMapping, + _In_ ULONG Color, + _In_ ACX_JACK_CONNECTION_TYPE ConnectionType, + _In_ ACX_JACK_GEO_LOCATION GeoLocation, + _In_ ACX_JACK_GEN_LOCATION GenLocation, + _In_ ACX_JACK_PORT_CONNECTION PortConnection +); diff --git a/audio/Soundwire/Samples/SdcaVad/SdcaVCodec/Extension.cpp b/audio/Soundwire/Samples/SdcaVad/SdcaVCodec/Extension.cpp new file mode 100644 index 000000000..d56a172b2 --- /dev/null +++ b/audio/Soundwire/Samples/SdcaVad/SdcaVCodec/Extension.cpp @@ -0,0 +1,262 @@ +/*++ + + THIS CODE AND INFORMATION IS PROVIDED "AS IS" WITHOUT WARRANTY OF ANY + KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE + IMPLIED WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A PARTICULAR + PURPOSE. + +Module Name: + + Extension.cpp + +Abstract: + + SDCA XU functions + +Environment: + + Kernel mode + +--*/ + +#include "private.h" + +#ifndef __INTELLISENSE__ +#include "Extension.tmh" +#endif + +PAGED_CODE_SEG +NTSTATUS Codec_SdcaXuSetJackOverride +( + _In_ PVOID Context, // SDCA Context + _In_ BOOLEAN Override // TRUE: Override + // FALSE: Default SDCA behavior +) +{ + PAGED_CODE(); + UNREFERENCED_PARAMETER(Context); + UNREFERENCED_PARAMETER(Override); + NTSTATUS status = STATUS_SUCCESS; + + PCODEC_DEVICE_CONTEXT devCtx; + + devCtx = GetCodecDeviceContext(Context); + ASSERT(devCtx != NULL); + + devCtx->SdcaXuData.bExtensionJackOVerride = Override; + + return status; +} + +PAGED_CODE_SEG +NTSTATUS Codec_SdcaXuSetJackSelectedMode +( + _In_ PVOID Context, // SDCA Context + _In_ ULONG GroupEntityId, // SDCA Group Entity ID for Jack(s) + _In_ ULONG SelectedMode // Type of jack type overriden by XU +) +{ + PAGED_CODE(); + UNREFERENCED_PARAMETER(Context); + UNREFERENCED_PARAMETER(GroupEntityId); + UNREFERENCED_PARAMETER(SelectedMode); + NTSTATUS status = STATUS_SUCCESS; + + return status; +} + +#pragma code_seg() +NTSTATUS Codec_SdcaXuPDEPowerReferenceAcquire +( + _In_ PVOID Context, // SDCA Context + _In_ ULONG PowerDomainEntityId, // SDCA Entity ID for entity + _In_ SDCAXU_POWER_STATE RequiredState // Power state the PowerDomain needs to be in +) +{ + UNREFERENCED_PARAMETER(Context); + UNREFERENCED_PARAMETER(PowerDomainEntityId); + UNREFERENCED_PARAMETER(RequiredState); + NTSTATUS status = STATUS_SUCCESS; + + return status; +} + +#pragma code_seg() +NTSTATUS Codec_SdcaXuPDEPowerReferenceRelease +( + _In_ PVOID Context, // SDCA Context + _In_ ULONG PowerDomainEntityId, // SDCA Entity ID for entity + _In_ SDCAXU_POWER_STATE ReleasedState // Power state the PowerDomain no longer needs to be in +) +{ + UNREFERENCED_PARAMETER(Context); + UNREFERENCED_PARAMETER(PowerDomainEntityId); + UNREFERENCED_PARAMETER(ReleasedState); + NTSTATUS status = STATUS_SUCCESS; + + return status; +} + +#pragma code_seg() +NTSTATUS Codec_SdcaXuReadDeferredAudioControls +( + _In_ PVOID Context, // SDCA Context + _Inout_ PSDCA_AUDIO_CONTROLS Controls // Array of SDCA Audio Controls +) +{ + UNREFERENCED_PARAMETER(Context); + UNREFERENCED_PARAMETER(Controls); + NTSTATUS status = STATUS_SUCCESS; + + return status; +} + +#pragma code_seg() +NTSTATUS Codec_SdcaXuWriteDeferredAudioControls +( + _In_ PVOID Context, // SDCA Context + _Inout_ PSDCA_AUDIO_CONTROLS Controls // Array of SDCA Audio Controls +) +{ + UNREFERENCED_PARAMETER(Context); + UNREFERENCED_PARAMETER(Controls); + NTSTATUS status = STATUS_SUCCESS; + + return status; +} + +PAGED_CODE_SEG +NTSTATUS Codec_SdcaXuSetXUEntities +( + _In_ PVOID Context, + _In_ ULONG NumEntities, + _In_reads_(NumEntities) + ULONG EntityIDs[] + ) +{ + PAGED_CODE(); + + DrvLogEnter(g_SDCAVCodecLog); + + NTSTATUS status = STATUS_SUCCESS; + + PCODEC_DEVICE_CONTEXT devCtx; + devCtx = GetCodecDeviceContext((WDFDEVICE)Context); + + if (NumEntities) + { + PULONG pXUEntities = (PULONG)ExAllocatePool2(POOL_FLAG_NON_PAGED, sizeof(ULONG) * NumEntities, DRIVER_TAG); + RETURN_NTSTATUS_IF_TRUE(NULL == pXUEntities, STATUS_INSUFFICIENT_RESOURCES); + + for (ULONG i = 0; i < NumEntities; i++) + { + pXUEntities[i] = EntityIDs[i]; + } + + devCtx->SdcaXuData.numXUEntities = NumEntities; + devCtx->SdcaXuData.XUEntities = pXUEntities; + } + + return status; +} + +PAGED_CODE_SEG +NTSTATUS Codec_SdcaXuRegisterForInterrupts +( + _In_ PVOID Context, + _In_ PSDCAXU_INTERRUPT_INFO InterruptInfo +) +{ + PAGED_CODE(); + + DrvLogEnter(g_SDCAVCodecLog); + + NTSTATUS status = STATUS_SUCCESS; + + PCODEC_DEVICE_CONTEXT devCtx; + devCtx = GetCodecDeviceContext((WDFDEVICE)Context); + + RETURN_NTSTATUS_IF_TRUE(InterruptInfo->Size != sizeof(SDCAXU_INTERRUPT_INFO), STATUS_INVALID_PARAMETER_1); + + PSDCAXU_INTERRUPT_INFO pInterruptInfo = (PSDCAXU_INTERRUPT_INFO)ExAllocatePool2( + POOL_FLAG_NON_PAGED, + InterruptInfo->Size, + DRIVER_TAG); + RETURN_NTSTATUS_IF_TRUE(NULL == pInterruptInfo, STATUS_MEMORY_NOT_ALLOCATED); + + RtlCopyMemory(pInterruptInfo, InterruptInfo, InterruptInfo->Size); + + devCtx->SdcaXuData.InterruptInfo = pInterruptInfo; + + return status; +} + +PAGED_CODE_SEG +NTSTATUS Codec_GetSdcaXu(_In_ WDFDEVICE Device) +{ + PAGED_CODE(); + NTSTATUS status = STATUS_SUCCESS; + PCODEC_DEVICE_CONTEXT devCtx; + + devCtx = GetCodecDeviceContext(Device); + ASSERT(devCtx != NULL); + + RtlZeroMemory(&devCtx->SdcaXuData, sizeof(devCtx->SdcaXuData)); + + // Initialize Interface for requesting correct version + devCtx->SdcaXuData.ExtensionInterface.InterfaceHeader.Size = sizeof(SDCAXU_INTERFACE_V0101); + devCtx->SdcaXuData.ExtensionInterface.InterfaceHeader.Version = SDCAXU_INTERFACE_VERSION_0101; + + // + // Provide SDCA Interface that XU driver can call into + // XU driver will copy these function addresses while + // handling Query Interface + // + devCtx->SdcaXuData.ExtensionInterface.EvtSetXUEntities = Codec_SdcaXuSetXUEntities; + devCtx->SdcaXuData.ExtensionInterface.EvtRegisterForInterrupts = Codec_SdcaXuRegisterForInterrupts; + devCtx->SdcaXuData.ExtensionInterface.EvtSetJackOverride = Codec_SdcaXuSetJackOverride; + devCtx->SdcaXuData.ExtensionInterface.EvtSetJackSelectedMode = Codec_SdcaXuSetJackSelectedMode; + devCtx->SdcaXuData.ExtensionInterface.EvtPDEPowerReferenceAcquire = Codec_SdcaXuPDEPowerReferenceAcquire; + devCtx->SdcaXuData.ExtensionInterface.EvtPDEPowerReferenceRelease = Codec_SdcaXuPDEPowerReferenceRelease; + devCtx->SdcaXuData.ExtensionInterface.EvtReadDeferredAudioControls = Codec_SdcaXuReadDeferredAudioControls; + devCtx->SdcaXuData.ExtensionInterface.EvtWriteDeferredAudioControls = Codec_SdcaXuWriteDeferredAudioControls; + + status = WdfFdoQueryForInterface( + Device, + &SDCAXU_INTERFACE, + (PINTERFACE)&(devCtx->SdcaXuData.ExtensionInterface), + sizeof(SDCAXU_INTERFACE_V0101), + SDCAXU_INTERFACE_VERSION_0101, + Device + ); + + if (NT_SUCCESS(status)) + { + devCtx->SdcaXuData.bSdcaXu = TRUE; + } + + return status; +} + +PAGED_CODE_SEG +NTSTATUS Codec_SetSdcaXuHwConfig(_In_ WDFDEVICE Device) +{ + PAGED_CODE(); + + NTSTATUS status = STATUS_SUCCESS; + + DrvLogEnter(g_SDCAVCodecLog); + + PCODEC_DEVICE_CONTEXT devCtx; + devCtx = GetCodecDeviceContext(Device); + ASSERT(devCtx != NULL); + PSDCAXU_INTERFACE_V0101 exInterface = &devCtx->SdcaXuData.ExtensionInterface; + PVOID exContext = devCtx->SdcaXuData.ExtensionInterface.InterfaceHeader.Context; + + SdcaXuAcpiBlob acpiBlob; + acpiBlob.NumEndpoints = 2; + RETURN_NTSTATUS_IF_FAILED(exInterface->EvtSetHwConfig(exContext, SdcaXuHwConfigTypeAcpiBlob, &acpiBlob, sizeof(acpiBlob))); + + return status; +} + diff --git a/audio/Soundwire/Samples/SdcaVad/SdcaVCodec/SDCAVCodec.vcxproj b/audio/Soundwire/Samples/SdcaVad/SdcaVCodec/SDCAVCodec.vcxproj new file mode 100644 index 000000000..c855cfc05 --- /dev/null +++ b/audio/Soundwire/Samples/SdcaVad/SdcaVCodec/SDCAVCodec.vcxproj @@ -0,0 +1,364 @@ + + + + + Debug + x64 + + + Release + x64 + + + Debug + Win32 + + + Release + Win32 + + + Debug + ARM + + + Release + ARM + + + Debug + ARM64 + + + Release + ARM64 + + + + {98C9E1FB-3F06-4B5C-BA88-545AD0A80F94} + $(MSBuildProjectName) + 1 + 31 + 1 + 0 + v4.5 + 12.0 + false + true + Debug + Win32 + $(LatestTargetPlatformVersion) + + + + Windows10 + true + WindowsKernelModeDriver10.0 + Driver + KMDF + Universal + + + Windows10 + false + WindowsKernelModeDriver10.0 + Driver + KMDF + Universal + + + Windows10 + true + WindowsKernelModeDriver10.0 + Driver + KMDF + Universal + + + Windows10 + false + WindowsKernelModeDriver10.0 + Driver + KMDF + Universal + + + Windows10 + true + WindowsKernelModeDriver10.0 + Driver + KMDF + Universal + + + Windows10 + false + WindowsKernelModeDriver10.0 + Driver + KMDF + Universal + + + Windows10 + true + WindowsKernelModeDriver10.0 + Driver + KMDF + Universal + + + Windows10 + false + WindowsKernelModeDriver10.0 + Driver + KMDF + Universal + + + + $(IntDir) + + + + + + + + + + DbgengKernelDebugger + + + DbgengKernelDebugger + + + DbgengKernelDebugger + + + DbgengKernelDebugger + + + DbgengKernelDebugger + + + DbgengKernelDebugger + + + DbgengKernelDebugger + + + DbgengKernelDebugger + + + + %(AdditionalDependencies);$(DDK_LIB_PATH)\libcntpr.lib;wpprecorder.lib;$(DDK_LIB_PATH)\acx\km\$(ACX_VERSION_MAJOR).$(ACX_VERSION_MINOR)\acxstub.lib + + + %(AdditionalIncludeDirectories);$(SDK_INC_PATH);$(DDK_INC_PATH)\acx\km\$(ACX_VERSION_MAJOR).$(ACX_VERSION_MINOR);..\inc;. + %(PreprocessorDefinitions);UNICODE;_UNICODE + %(PreprocessorDefinitions);ACX_VERSION_MAJOR=1;ACX_VERSION_MINOR=0;_NEW_DELETE_OPERATORS_ + true + true + + + ..\inc\trace_macros.h + -km \ +-DENABLE_WPP_RECORDER=1 \ +-DENABLE_WPP_TRACE_FILTERING_WITH_WPP_RECORDER=1 \ +-func:DoTraceLevelMessage(LEVEL,FLAGS,MSG,...) \ +-p:SDCAVCodec + + + sha256 + + + + + %(AdditionalDependencies);$(DDK_LIB_PATH)\libcntpr.lib;wpprecorder.lib;$(DDK_LIB_PATH)\acx\km\$(ACX_VERSION_MAJOR).$(ACX_VERSION_MINOR)\acxstub.lib + + + %(AdditionalIncludeDirectories);$(SDK_INC_PATH);$(DDK_INC_PATH)\acx\km\$(ACX_VERSION_MAJOR).$(ACX_VERSION_MINOR);..\inc;..\common\. + %(PreprocessorDefinitions);UNICODE;_UNICODE + %(PreprocessorDefinitions);ACX_VERSION_MAJOR=1;ACX_VERSION_MINOR=0;_NEW_DELETE_OPERATORS_ + true + true + + + ..\inc\trace_macros.h + -km \ +-DENABLE_WPP_RECORDER=1 \ +-DENABLE_WPP_TRACE_FILTERING_WITH_WPP_RECORDER=1 \ +-func:DoTraceLevelMessage(LEVEL,FLAGS,MSG,...) \ +-p:SDCAVCodec + + + sha256 + + + + + %(AdditionalDependencies);$(DDK_LIB_PATH)\libcntpr.lib;wpprecorder.lib;$(DDK_LIB_PATH)\acx\km\$(ACX_VERSION_MAJOR).$(ACX_VERSION_MINOR)\acxstub.lib + + + %(AdditionalIncludeDirectories);$(SDK_INC_PATH);$(DDK_INC_PATH)\acx\km\$(ACX_VERSION_MAJOR).$(ACX_VERSION_MINOR);..\inc;..\common\. + %(PreprocessorDefinitions);UNICODE;_UNICODE + %(PreprocessorDefinitions);ACX_VERSION_MAJOR=1;ACX_VERSION_MINOR=0;_NEW_DELETE_OPERATORS_ + true + true + + + ..\inc\trace_macros.h + -km \ +-DENABLE_WPP_RECORDER=1 \ +-DENABLE_WPP_TRACE_FILTERING_WITH_WPP_RECORDER=1 \ +-func:DoTraceLevelMessage(LEVEL,FLAGS,MSG,...) \ +-p:SDCAVCodec + + + sha256 + + + + + %(AdditionalDependencies);$(DDK_LIB_PATH)\libcntpr.lib;wpprecorder.lib;$(DDK_LIB_PATH)\acx\km\$(ACX_VERSION_MAJOR).$(ACX_VERSION_MINOR)\acxstub.lib + + + %(AdditionalIncludeDirectories);$(SDK_INC_PATH);$(DDK_INC_PATH)\acx\km\$(ACX_VERSION_MAJOR).$(ACX_VERSION_MINOR);..\inc;..\common\. + %(PreprocessorDefinitions);UNICODE;_UNICODE + %(PreprocessorDefinitions);ACX_VERSION_MAJOR=1;ACX_VERSION_MINOR=0;_NEW_DELETE_OPERATORS_ + true + true + + + ..\inc\trace_macros.h + -km \ +-DENABLE_WPP_RECORDER=1 \ +-DENABLE_WPP_TRACE_FILTERING_WITH_WPP_RECORDER=1 \ +-func:DoTraceLevelMessage(LEVEL,FLAGS,MSG,...) \ +-p:SDCAVCodec + + + sha256 + + + + + %(AdditionalDependencies);$(DDK_LIB_PATH)\libcntpr.lib;wpprecorder.lib;$(DDK_LIB_PATH)\acx\km\$(ACX_VERSION_MAJOR).$(ACX_VERSION_MINOR)\acxstub.lib + + + %(AdditionalIncludeDirectories);$(SDK_INC_PATH);$(DDK_INC_PATH)\acx\km\$(ACX_VERSION_MAJOR).$(ACX_VERSION_MINOR);..\inc;..\common\. + %(PreprocessorDefinitions);UNICODE;_UNICODE + %(PreprocessorDefinitions);ACX_VERSION_MAJOR=1;ACX_VERSION_MINOR=0;_NEW_DELETE_OPERATORS_ + true + true + + + ..\inc\trace_macros.h + -km \ +-DENABLE_WPP_RECORDER=1 \ +-DENABLE_WPP_TRACE_FILTERING_WITH_WPP_RECORDER=1 \ +-func:DoTraceLevelMessage(LEVEL,FLAGS,MSG,...) \ +-p:SDCAVCodec + + + sha256 + + + + + %(AdditionalDependencies);$(DDK_LIB_PATH)\libcntpr.lib;wpprecorder.lib;$(DDK_LIB_PATH)\acx\km\$(ACX_VERSION_MAJOR).$(ACX_VERSION_MINOR)\acxstub.lib + + + %(AdditionalIncludeDirectories);$(SDK_INC_PATH);$(DDK_INC_PATH)\acx\km\$(ACX_VERSION_MAJOR).$(ACX_VERSION_MINOR);..\inc;..\common\. + %(PreprocessorDefinitions);UNICODE;_UNICODE + %(PreprocessorDefinitions);ACX_VERSION_MAJOR=1;ACX_VERSION_MINOR=0;_NEW_DELETE_OPERATORS_ + true + true + + + ..\inc\trace_macros.h + -km \ +-DENABLE_WPP_RECORDER=1 \ +-DENABLE_WPP_TRACE_FILTERING_WITH_WPP_RECORDER=1 \ +-func:DoTraceLevelMessage(LEVEL,FLAGS,MSG,...) \ +-p:SDCAVCodec + + + sha256 + + + + + %(AdditionalDependencies);$(DDK_LIB_PATH)\libcntpr.lib;wpprecorder.lib;$(DDK_LIB_PATH)\acx\km\$(ACX_VERSION_MAJOR).$(ACX_VERSION_MINOR)\acxstub.lib + + + %(AdditionalIncludeDirectories);$(SDK_INC_PATH);$(DDK_INC_PATH)\acx\km\$(ACX_VERSION_MAJOR).$(ACX_VERSION_MINOR);..\inc;..\common\. + %(PreprocessorDefinitions);UNICODE;_UNICODE + %(PreprocessorDefinitions);ACX_VERSION_MAJOR=1;ACX_VERSION_MINOR=0;_NEW_DELETE_OPERATORS_ + true + true + + + ..\inc\trace_macros.h + -km \ +-DENABLE_WPP_RECORDER=1 \ +-DENABLE_WPP_TRACE_FILTERING_WITH_WPP_RECORDER=1 \ +-func:DoTraceLevelMessage(LEVEL,FLAGS,MSG,...) \ +-p:SDCAVCodec + + + sha256 + + + + + %(AdditionalDependencies);$(DDK_LIB_PATH)\libcntpr.lib;wpprecorder.lib;$(DDK_LIB_PATH)\acx\km\$(ACX_VERSION_MAJOR).$(ACX_VERSION_MINOR)\acxstub.lib + + + %(AdditionalIncludeDirectories);$(SDK_INC_PATH);$(DDK_INC_PATH)\acx\km\$(ACX_VERSION_MAJOR).$(ACX_VERSION_MINOR);..\inc;..\common\. + %(PreprocessorDefinitions);UNICODE;_UNICODE + %(PreprocessorDefinitions);ACX_VERSION_MAJOR=1;ACX_VERSION_MINOR=0;_NEW_DELETE_OPERATORS_ + true + true + + + ..\inc\trace_macros.h + -km \ +-DENABLE_WPP_RECORDER=1 \ +-DENABLE_WPP_TRACE_FILTERING_WITH_WPP_RECORDER=1 \ +-func:DoTraceLevelMessage(LEVEL,FLAGS,MSG,...) \ +-p:SDCAVCodec + + + sha256 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/audio/Soundwire/Samples/SdcaVad/SdcaVCodec/SDCAVCodec.vcxproj.Filters b/audio/Soundwire/Samples/SdcaVad/SdcaVCodec/SDCAVCodec.vcxproj.Filters new file mode 100644 index 000000000..44bc3f924 --- /dev/null +++ b/audio/Soundwire/Samples/SdcaVad/SdcaVCodec/SDCAVCodec.vcxproj.Filters @@ -0,0 +1,21 @@ + + + + + {4FC737F1-C7A5-4376-A066-2A32D752A2FF} + cpp;c;cc;cxx;def;odl;idl;hpj;bat;asm;asmx + + + {93995380-89BD-4b04-88EB-625FBE52EBFB} + h;hpp;hxx;hm;inl;inc;xsd + + + {67DA6AB6-F800-4c08-8B7A-83BB121AAD01} + rc;ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe;resx;tiff;tif;png;wav;mfcribbon-ms + + + {8E41214B-6785-4CFE-B992-037D68949A14} + inf;inv;inx;mof;mc; + + + diff --git a/audio/Soundwire/Samples/SdcaVad/SdcaVCodec/SdcaVCodec.inx b/audio/Soundwire/Samples/SdcaVad/SdcaVCodec/SdcaVCodec.inx new file mode 100644 index 000000000..88c9eba41 --- /dev/null +++ b/audio/Soundwire/Samples/SdcaVad/SdcaVCodec/SdcaVCodec.inx @@ -0,0 +1,146 @@ +;/*++ +; +;Copyright (c) Microsoft Corporation. All rights reserved. +; +;Module Name: +; +; SDCAVCodec.INF +; +;--*/ + +[Version] +Signature="$WINDOWS NT$" +Class=SYSTEM +ClassGuid={4d36e97d-e325-11ce-bfc1-08002be10318} +Provider=%ProviderName% +DriverVer=06/13/2016, 1.0.0.1 +CatalogFile=SDCAVad.cat +PnpLockdown=1 + +[DestinationDirs] +DefaultDestDir = 13 + +;***************************************** +; Audio Device Install Section +;***************************************** +[Manufacturer] +%StdMfg%=Standard,NT$ARCH$.10.0...19041 + +[Standard.NT$ARCH$.10.0...19041] +%WdfCodecDevice.DeviceDesc%=Audio_Device, ROOT\SDCAVCodec + +[Audio_Device.NT] +CopyFiles=Audio_Device.NT.Copy + +[Audio_Device.NT.Copy] +SDCAVCodec.sys + + +[Audio_Device.NT.HW] +AddReg=FilterLevelReg + +;**************************************************** +; SDCAXu filters are installed in filter level +; SDCAXu +;**************************************************** +[FilterLevelReg] +HKR,,LowerFilterLevels,%REG_MULTI_SZ%,"SDCAXu","DefaultLowerFilter" +HKR,,LowerFilterDefaultLevel,,"DefaultLowerFilter" + +;-------------- Service installation + +[Audio_Device.NT.Services] +AddService = SDCAVCodec, %SPSVCINST_ASSOCSERVICE%, Audio_Service_Inst + +[Audio_Service_Inst] +DisplayName = %WdfCodecDevice.DeviceDesc% +ServiceType = 1 ; SERVICE_KERNEL_DRIVER +StartType = 3 ; SERVICE_DEMAND_START +ErrorControl = 1 ; SERVICE_ERROR_NORMAL +ServiceBinary = %13%\SDCAVCodec.sys + +[SourceDisksNames] +1 = %DiskId1%,,,"" + +[SourceDisksFiles] +SDCAVCodec.sys = 1,, + + +[Audio_Device.NT.Wdf] +KmdfService = SDCAVCodec, Audio_wdfsect +[Audio_wdfsect] +KmdfLibraryVersion = $KMDFVERSION$ + +; +; render interfaces: speaker +; +[Audio_Device.I.Speaker] +AddReg=Audio_Device.I.Speaker.AddReg +[Audio_Device.I.Speaker.AddReg] +HKR,,CLSID,,%Proxy.CLSID% +HKR,,FriendlyName,,%Audio_Device.Speaker.szPname% +; The following lines opt-in to pull mode. +HKR,EP\0,%PKEY_AudioEndpoint_Association%,,%KSNODETYPE_ANY% +HKR,EP\0,%PKEY_AudioEndpoint_Supports_EventDriven_Mode%,0x00010001,0x1 + +; +; capture interfaces: microphone +; +[Audio_Device.I.Microphone] +AddReg=Audio_Device.I.Microphone.AddReg +[Audio_Device.I.Microphone.AddReg] +HKR,,CLSID,,%Proxy.CLSID% +HKR,,FriendlyName,,%Audio_Device.Microphone.szPname% +; The following lines opt-in to pull mode. +HKR,EP\0,%PKEY_AudioEndpoint_Association%,,%KSNODETYPE_ANY% +HKR,EP\0,%PKEY_AudioEndpoint_Supports_EventDriven_Mode%,0x00010001,0x1 + +; +; PnP add interface directives for static enumerated audio endpoints. +; +[Audio_Device.NT.Interfaces] +; Interfaces for render endpoint. +AddInterface=%KSCATEGORY_AUDIO%, %KSNAME_Speaker%, Audio_Device.I.Speaker +AddInterface=%KSCATEGORY_TOPOLOGY%, %KSNAME_Speaker%, Audio_Device.I.Speaker + +; Interfaces for mic capture endpoint +AddInterface=%KSCATEGORY_AUDIO%, %KSNAME_Microphone%, Audio_Device.I.Microphone +AddInterface=%KSCATEGORY_TOPOLOGY%, %KSNAME_Microphone%, Audio_Device.I.Microphone + +[Strings] +; +;Non-localizable +; +KSNAME_Speaker="Speaker0" +KSNAME_Microphone="Microphone0" + +SPSVCINST_ASSOCSERVICE = 0x00000002 +ProviderName = "VS_Microsoft" + +Proxy.CLSID = "{17CCA71B-ECD7-11D0-B908-00A0C9223196}" +KSCATEGORY_AUDIO = "{6994AD04-93EF-11D0-A3CC-00A0C9223196}" +KSCATEGORY_RENDER = "{65E8773E-8F56-11D0-A3B9-00A0C9223196}" +KSCATEGORY_CAPTURE = "{65E8773D-8F56-11D0-A3B9-00A0C9223196}" +KSCATEGORY_REALTIME = "{EB115FFC-10C8-4964-831D-6DCB02E6F23F}" +KSCATEGORY_TOPOLOGY = "{DDA54A40-1E4C-11D1-A050-405705C10000}" + +MediaCategories="SYSTEM\CurrentControlSet\Control\MediaCategories" +KSNODETYPE_ANY = "{00000000-0000-0000-0000-000000000000}" + +PKEY_AudioEndpoint_ControlPanelPageProvider = "{1DA5D803-D492-4EDD-8C23-E0C0FFEE7F0E},1" +PKEY_AudioEndpoint_Association = "{1DA5D803-D492-4EDD-8C23-E0C0FFEE7F0E},2" +PKEY_AudioEndpoint_Supports_EventDriven_Mode = "{1DA5D803-D492-4EDD-8C23-E0C0FFEE7F0E},7" +PKEY_AudioEndpoint_Default_VolumeInDb = "{1DA5D803-D492-4EDD-8C23-E0C0FFEE7F0E},9" +REG_MULTI_SZ = 0x00010000 +; +;Localizable +; +StdMfg = "SDCA Virtual Codec Audio Device" +DiskId1 = "SDCA Virtual Codec Audio Driver Installation Disk" +WdfCodecDevice.DeviceDesc = "SDCA Virtual Codec Audio Driver" + +;; friendly names +Audio_Device.Speaker.szPname="SDCA Virtual Codec Speaker" +Audio_Device.Microphone.szPname="SDCA Virtual Codec Microphone" + + diff --git a/audio/Soundwire/Samples/SdcaVad/SdcaVCodec/Trace.h b/audio/Soundwire/Samples/SdcaVad/SdcaVCodec/Trace.h new file mode 100644 index 000000000..4b2929307 --- /dev/null +++ b/audio/Soundwire/Samples/SdcaVad/SdcaVCodec/Trace.h @@ -0,0 +1,34 @@ +/*++ + +Copyright (c) Microsoft Corporation + +Module Name: + +Trace.h + +--*/ + +#pragma once + +#include +#include // For TRACE_LEVEL definitions + +#define WPP_TOTAL_BUFFER_SIZE (PAGE_SIZE) +#define WPP_ERROR_PARTITION_SIZE (WPP_TOTAL_BUFFER_SIZE/4) + +// {C456FD64-2AC1-4A72-8280-B4163555CD03} +#define WPP_CONTROL_GUIDS \ +WPP_DEFINE_CONTROL_GUID(DrvLogger,(c456fd64,2ac1,4a72,8280,b4163555cd03), \ + WPP_DEFINE_BIT(FLAG_DEVICE_ALL) /* bit 0 = 0x00000001 */ \ + WPP_DEFINE_BIT(FLAG_FUNCTION) /* bit 1 = 0x00000002 */ \ + WPP_DEFINE_BIT(FLAG_INFO) /* bit 2 = 0x00000004 */ \ + WPP_DEFINE_BIT(FLAG_PNP) /* bit 3 = 0x00000008 */ \ + WPP_DEFINE_BIT(FLAG_POWER) /* bit 4 = 0x00000010 */ \ + WPP_DEFINE_BIT(FLAG_STREAM) /* bit 5 = 0x00000020 */ \ + WPP_DEFINE_BIT(FLAG_INIT) /* bit 6 = 0x00000040 */ \ + WPP_DEFINE_BIT(FLAG_DDI) /* bit 7 = 0x00000080 */ \ + WPP_DEFINE_BIT(FLAG_GENERIC) /* bit 8 = 0x00000100 */ \ + ) + +#include "trace_macros.h" + diff --git a/audio/Soundwire/Samples/SdcaVad/SdcaVCodec/capture.cpp b/audio/Soundwire/Samples/SdcaVad/SdcaVCodec/capture.cpp new file mode 100644 index 000000000..df6de34c6 --- /dev/null +++ b/audio/Soundwire/Samples/SdcaVad/SdcaVCodec/capture.cpp @@ -0,0 +1,1150 @@ +/*++ + + THIS CODE AND INFORMATION IS PROVIDED "AS IS" WITHOUT WARRANTY OF ANY + KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE + IMPLIED WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A PARTICULAR + PURPOSE. + +Module Name: + + Capture.cpp + +Abstract: + + Contains ACX Capture factory and circuit + +Environment: + + Kernel mode + +--*/ + +#include "private.h" +#include +#include "stdunk.h" +#include +#include +#include +#include "streamengine.h" +#include "soundwirecontroller.h" +#include "sdcastreaming.h" +#include "CircuitHelper.h" + +#include "AudioFormats.h" + +#ifndef __INTELLISENSE__ +#include "capture.tmh" +#endif + +ACX_PROPERTY_ITEM KwsProperties[] = +{ + { + &KSPROPERTYSETID_SdcaKws, + KSPROPERTY_SDCAKWS_DEVICE_CAPABILITY, + ACX_PROPERTY_ITEM_FLAG_GET, + &CodecC_EvtCircuitDeviceKwsCapability, // Event to call + NULL, // Reserved + 0, // ControlCb + sizeof(DEVICE_KWS_CAPABILITY_DESCRIPTOR) // ValueCb + }, + { + &KSPROPERTYSETID_SdcaKws, + KSPROPERTY_SDCAKWS_VAD_CAPABILITY, + ACX_PROPERTY_ITEM_FLAG_GET, + &CodecC_EvtCircuitVadCapability, // Event to call + NULL, // Reserved + 0, // ControlCb + sizeof(VAD_DESCRIPTOR) // ValueCb + }, + { + &KSPROPERTYSETID_SdcaKws, + KSPROPERTY_SDCAKWS_VAD_ENTITIES, + ACX_PROPERTY_ITEM_FLAG_GET, + &CodecC_EvtCircuitVadEntities, // Event to call + NULL, // Reserved + 0, // ControlCb + sizeof(VAD_ENTITIES) // ValueCb + }, + { + &KSPROPERTYSETID_SdcaKws, + KSPROPERTY_SDCAKWS_ACCESS_EVENTS, + ACX_PROPERTY_ITEM_FLAG_SET, + &CodecC_EvtCircuitSetKwsAccessEvents, // Event to call + NULL, // Reserved + 0, // ControlCb + sizeof(SDCA_KWS_NOTIFICATIONS) // ValueCb + }, + { + &KSPROPERTYSETID_SdcaKws, + KSPROPERTY_SDCAKWS_CONFIGURE_VAD_PORT, + ACX_PROPERTY_ITEM_FLAG_SET, + &CodecC_EvtCircuitConfigureVadPort, // Event to call + NULL, // Reserved + 0, // ControlCb + sizeof(SDCA_KWS_PREPARE_PARAMS) // ValueCb + }, + { + &KSPROPERTYSETID_SdcaKws, + KSPROPERTY_SDCAKWS_CLEANUP_VAD_PORT, + ACX_PROPERTY_ITEM_FLAG_SET, + &CodecC_EvtCircuitCleanupVadPort, // Event to call + // No parameters - can only have one + }, +}; + +_Use_decl_annotations_ +PAGED_CODE_SEG +VOID +CodecC_EvtCircuitVadCapability( + _In_ WDFOBJECT Object, + _In_ WDFREQUEST Request +) +{ + NTSTATUS status = STATUS_NOT_SUPPORTED; + ACX_REQUEST_PARAMETERS params; + ULONG_PTR outDataCb = 0; + PVAD_DESCRIPTOR value; + ULONG valueCb; + ULONG_PTR minSize; + PCODEC_CAPTURE_CIRCUIT_CONTEXT circuitCtx; + ULONG formatCount = 0; + + PAGED_CODE(); + + circuitCtx = GetCaptureCircuitContext((ACXCIRCUIT)Object); + + ACX_REQUEST_PARAMETERS_INIT(¶ms); + AcxRequestGetParameters(Request, ¶ms); + + ASSERT(params.Type == AcxRequestTypeProperty); + ASSERT(params.Parameters.Property.Verb == AcxPropertyVerbGet); + + value = (PVAD_DESCRIPTOR)params.Parameters.Property.Value; + valueCb = params.Parameters.Property.ValueCb; + + // + // Compute min size. + // + minSize = sizeof(VAD_DESCRIPTOR); + + // + // Sample only supports 1 format + // + formatCount = 1; + + // Note the VAD_DESCRIPTOR already has room for 1, hence subtracting that here + minSize += (formatCount - ANYSIZE_ARRAY) * sizeof(WAVEFORMATEXTENSIBLE); + + if (valueCb == 0) + { + outDataCb = minSize; + status = STATUS_BUFFER_OVERFLOW; + } + else if (valueCb < minSize) + { + outDataCb = 0; + status = STATUS_BUFFER_TOO_SMALL; + } + else + { + // + // Reset buffer. + // + RtlZeroMemory(value, valueCb); + + // It's safe for us to use the KwsDataFormat directly in AcxDataFormatGetWaveFormatExtensible + // because we control it and know it will have a proper WAVEFORMATEXTENSIBLE value. + RtlCopyMemory(value->Format, AcxDataFormatGetWaveFormatExtensible(circuitCtx->KwsDataFormat), sizeof(WAVEFORMATEXTENSIBLE)); + + value->FormatCount = formatCount; + + // + // All done. + // + outDataCb = minSize; + status = STATUS_SUCCESS; + } + + WdfRequestCompleteWithInformation(Request, status, outDataCb); +} + + +_Use_decl_annotations_ +PAGED_CODE_SEG +VOID +CodecC_EvtCircuitVadEntities( + _In_ WDFOBJECT Object, + _In_ WDFREQUEST Request +) +{ + NTSTATUS status = STATUS_NOT_SUPPORTED; + ACX_REQUEST_PARAMETERS params; + ULONG_PTR outDataCb = 0; + PVAD_DESCRIPTOR value; + ULONG valueCb; + ULONG_PTR minSize; + PCODEC_CAPTURE_CIRCUIT_CONTEXT circuitCtx; + + PAGED_CODE(); + + circuitCtx = GetCaptureCircuitContext((ACXCIRCUIT)Object); + + ACX_REQUEST_PARAMETERS_INIT(¶ms); + AcxRequestGetParameters(Request, ¶ms); + + ASSERT(params.Type == AcxRequestTypeProperty); + ASSERT(params.Parameters.Property.Verb == AcxPropertyVerbGet); + + value = (PVAD_DESCRIPTOR)params.Parameters.Property.Value; + valueCb = params.Parameters.Property.ValueCb; + + // we're going to return 0 entities, so only the base structure + // is needed. + minSize = sizeof(VAD_ENTITIES); + + if (valueCb == 0) + { + outDataCb = minSize; + status = STATUS_BUFFER_OVERFLOW; + } + else if (valueCb < minSize) + { + outDataCb = 0; + status = STATUS_BUFFER_TOO_SMALL; + } + else + { + // + // Reset buffer. + // + RtlZeroMemory(value, valueCb); + + // we do not have disco info for this sample driver, so + // we have no entities to copy, but an empty list is sufficient + // for testing. + + outDataCb = minSize; + status = STATUS_SUCCESS; + } + + WdfRequestCompleteWithInformation(Request, status, outDataCb); +} + +_Use_decl_annotations_ +PAGED_CODE_SEG +VOID +CodecC_EvtCircuitDeviceKwsCapability( + _In_ WDFOBJECT Object, + _In_ WDFREQUEST Request +) +{ + NTSTATUS status = STATUS_NOT_SUPPORTED; + ACX_REQUEST_PARAMETERS params; + ULONG_PTR outDataCb = 0; + PDEVICE_KWS_CAPABILITY_DESCRIPTOR value; + ULONG valueCb; + ULONG_PTR minSize; + PCODEC_CAPTURE_CIRCUIT_CONTEXT circuitCtx; + + PAGED_CODE(); + + circuitCtx = GetCaptureCircuitContext((ACXCIRCUIT)Object); + + ACX_REQUEST_PARAMETERS_INIT(¶ms); + AcxRequestGetParameters(Request, ¶ms); + + ASSERT(params.Type == AcxRequestTypeProperty); + ASSERT(params.Parameters.Property.Verb == AcxPropertyVerbGet); + + value = (PDEVICE_KWS_CAPABILITY_DESCRIPTOR)params.Parameters.Property.Value; + valueCb = params.Parameters.Property.ValueCb; + + // + // Compute min size. + // + + minSize = sizeof(DEVICE_KWS_CAPABILITY_DESCRIPTOR); + + if (valueCb == 0) + { + outDataCb = minSize; + status = STATUS_BUFFER_OVERFLOW; + } + else if (valueCb < minSize) + { + outDataCb = 0; + status = STATUS_BUFFER_TOO_SMALL; + } + else + { + // + // Reset buffer. + // + RtlZeroMemory(value, valueCb); + + // Get the Device KWS Capabilities + // In this sample, just return that Buffered is supported + value->DataPathsSupported = SupportedDataPathsBufferedRaw; + + // + // All done. + // + outDataCb = minSize; + status = STATUS_SUCCESS; + } + + WdfRequestCompleteWithInformation(Request, status, outDataCb); +} + +_Use_decl_annotations_ +PAGED_CODE_SEG +VOID +CodecC_EvtCircuitSetKwsAccessEvents( + _In_ WDFOBJECT Object, + _In_ WDFREQUEST Request +) +{ + NTSTATUS status = STATUS_NOT_SUPPORTED; + ACX_REQUEST_PARAMETERS params; + ULONG_PTR outDataCb = 0; // default no size info + PSDCA_KWS_NOTIFICATIONS value; + ULONG valueCb; + ULONG minSize; + PCODEC_CAPTURE_CIRCUIT_CONTEXT circuitCtx; + + PAGED_CODE(); + + circuitCtx = GetCaptureCircuitContext((ACXCIRCUIT)Object); + + ACX_REQUEST_PARAMETERS_INIT(¶ms); + AcxRequestGetParameters(Request, ¶ms); + + ASSERT(params.Type == AcxRequestTypeProperty); + ASSERT(params.Parameters.Property.Verb == AcxPropertyVerbSet); + + minSize = sizeof(SDCA_KWS_NOTIFICATIONS); + + value = (PSDCA_KWS_NOTIFICATIONS)params.Parameters.Property.Value; + valueCb = params.Parameters.Property.ValueCb; + + if (valueCb == 0) + { + outDataCb = minSize; + status = STATUS_BUFFER_OVERFLOW; + goto exit; + } + + if (valueCb < minSize) + { + status = STATUS_BUFFER_TOO_SMALL; + goto exit; + } + + circuitCtx->KwsSuspendEvent = value->Suspend; + circuitCtx->KwsResumeEvent = value->Resume; + + status = STATUS_SUCCESS; + +exit: + + WdfRequestCompleteWithInformation(Request, status, outDataCb); +} + +_Use_decl_annotations_ +PAGED_CODE_SEG +VOID +CodecC_EvtCircuitConfigureVadPort( + _In_ WDFOBJECT Object, + _In_ WDFREQUEST Request +) +{ + NTSTATUS status = STATUS_NOT_SUPPORTED; + ACX_REQUEST_PARAMETERS params; + ULONG_PTR outDataCb = 0; // default no size info + PSDCA_KWS_PREPARE_PARAMS value; + ULONG valueCb; + ULONG minSize; + PCODEC_CAPTURE_CIRCUIT_CONTEXT circuitCtx; + + PAGED_CODE(); + + circuitCtx = GetCaptureCircuitContext((ACXCIRCUIT)Object); + + ACX_REQUEST_PARAMETERS_INIT(¶ms); + AcxRequestGetParameters(Request, ¶ms); + + ASSERT(params.Type == AcxRequestTypeProperty); + ASSERT(params.Parameters.Property.Verb == AcxPropertyVerbSet); + + if (circuitCtx->KwsActiveVadStream) + { + status = STATUS_INVALID_DEVICE_REQUEST; + goto exit; + } + + minSize = sizeof(SDCA_KWS_PREPARE_PARAMS); + + value = (PSDCA_KWS_PREPARE_PARAMS)params.Parameters.Property.Value; + valueCb = params.Parameters.Property.ValueCb; + + if (valueCb == 0) + { + outDataCb = minSize; + status = STATUS_BUFFER_OVERFLOW; + goto exit; + } + + if (valueCb < minSize) + { + status = STATUS_BUFFER_TOO_SMALL; + goto exit; + } + + if (value->DetectionFormat.Format.cbSize > sizeof(WAVEFORMATEXTENSIBLE) - sizeof(WAVEFORMATEX)) + { + status = STATUS_INVALID_PARAMETER; + goto exit; + } + + // Set up the hardware for KWS + circuitCtx->KwsActiveVadStream = TRUE; + status = STATUS_SUCCESS; + +exit: + + WdfRequestCompleteWithInformation(Request, status, outDataCb); +} + +_Use_decl_annotations_ +PAGED_CODE_SEG +VOID +CodecC_EvtCircuitCleanupVadPort( + _In_ WDFOBJECT Object, + _In_ WDFREQUEST Request +) +{ + NTSTATUS status = STATUS_NOT_SUPPORTED; + ACX_REQUEST_PARAMETERS params; + ULONG_PTR outDataCb = 0; // default no size info + PCODEC_CAPTURE_CIRCUIT_CONTEXT circuitCtx; + + PAGED_CODE(); + + circuitCtx = GetCaptureCircuitContext((ACXCIRCUIT)Object); + + ACX_REQUEST_PARAMETERS_INIT(¶ms); + AcxRequestGetParameters(Request, ¶ms); + + ASSERT(params.Type == AcxRequestTypeProperty); + ASSERT(params.Parameters.Property.Verb == AcxPropertyVerbSet); + + if (!circuitCtx->KwsActiveVadStream) + { + status = STATUS_INVALID_DEVICE_REQUEST; + goto exit; + } + + // Deconfigure hardware + circuitCtx->KwsActiveVadStream = FALSE; + status = STATUS_SUCCESS; + +exit: + + WdfRequestCompleteWithInformation(Request, status, outDataCb); +} + +PAGED_CODE_SEG +NTSTATUS +CodecC_EvtAcxPinSetDataFormat( + _In_ ACXPIN Pin, + _In_ ACXDATAFORMAT DataFormat +) +{ + PAGED_CODE(); + + UNREFERENCED_PARAMETER(Pin); + UNREFERENCED_PARAMETER(DataFormat); + + + return STATUS_NOT_SUPPORTED; +} + +#pragma code_seg() +VOID +CodecC_EvtPinContextCleanup( + _In_ WDFOBJECT WdfPin +) +/*++ + +Routine Description: + + In this callback, it cleans up pin context. + +Arguments: + + WdfDevice - WDF device object + +Return Value: + + NULL + +--*/ +{ + UNREFERENCED_PARAMETER(WdfPin); +} + +PAGED_CODE_SEG +VOID +CodecC_EvtCircuitRequestPreprocess( + _In_ ACXOBJECT Object, + _In_ ACXCONTEXT DriverContext, + _In_ WDFREQUEST Request +) +/*++ + +Routine Description: + + This function is an example of a preprocess routine. + +--*/ +{ + PAGED_CODE(); + + UNREFERENCED_PARAMETER(DriverContext); + + ASSERT(Object != NULL); + ASSERT(DriverContext); + ASSERT(Request); + + + // + // Just give the request back to ACX. + // + (VOID)AcxCircuitDispatchAcxRequest((ACXCIRCUIT)Object, Request); +} + +PAGED_CODE_SEG +VOID +CodecC_EvtStreamRequestPreprocess( + _In_ ACXOBJECT Object, + _In_ ACXCONTEXT DriverContext, + _In_ WDFREQUEST Request +) +/*++ + +Routine Description: + + This function is an example of a preprocess routine. + +--*/ +{ + PAGED_CODE(); + + UNREFERENCED_PARAMETER(DriverContext); + + ASSERT(Object != NULL); + ASSERT(DriverContext); + ASSERT(Request); + + + // + // Just give the request back to ACX. + // + (VOID)AcxStreamDispatchAcxRequest((ACXSTREAM)Object, Request); +} + +PAGED_CODE_SEG +NTSTATUS +CodecC_AddCaptures( + _In_ WDFDRIVER Driver, + _In_ WDFDEVICE Device +) +{ + NTSTATUS status = STATUS_SUCCESS; + + UNREFERENCED_PARAMETER(Driver); + + PAGED_CODE(); + + // + // Add a static capture device. + // + RETURN_NTSTATUS_IF_FAILED(CodecC_AddStaticCapture(Device)); + + return status; +} + +PAGED_CODE_SEG +NTSTATUS +CodecC_AddStaticCapture( + _In_ WDFDEVICE Device +) +{ + NTSTATUS status = STATUS_SUCCESS; + + PAGED_CODE(); + + PCODEC_DEVICE_CONTEXT devCtx; + devCtx = GetCodecDeviceContext(Device); + ASSERT(devCtx != NULL); + + // + // Alloc audio context to current device. + // + WDF_OBJECT_ATTRIBUTES attributes; + WDF_OBJECT_ATTRIBUTES_INIT_CONTEXT_TYPE(&attributes, CODEC_CAPTURE_DEVICE_CONTEXT); + PCODEC_CAPTURE_DEVICE_CONTEXT captureDevCtx; + RETURN_NTSTATUS_IF_FAILED(WdfObjectAllocateContext(Device, &attributes, (PVOID*)&captureDevCtx)); + + ASSERT(captureDevCtx); + + // + // Create a capture circuit associated with this device. + // + ACXCIRCUIT captureCircuit = NULL; + RETURN_NTSTATUS_IF_FAILED(CodecC_CreateCaptureCircuit(Device, &captureCircuit)); + + RETURN_NTSTATUS_IF_FAILED(Codec_SdcaXuSetCaptureEndpointConfig(Device, captureCircuit)); + + devCtx->Capture = captureCircuit; + + return status; +} + +EXTERN_C const GUID DECLSPEC_SELECTANY CODEC_CIRCUIT_CAPTURE_GUID; +EXTERN_C const GUID DECLSPEC_SELECTANY EXTENSION_CIRCUIT_CAPTURE_GUID; +EXTERN_C const GUID DECLSPEC_SELECTANY SYSTEM_CONTAINER_GUID; + +PAGED_CODE_SEG +NTSTATUS Codec_SdcaXuSetCaptureEndpointConfig( + _In_ WDFDEVICE Device, + _In_ ACXCIRCUIT Circuit +) +{ + PAGED_CODE(); + + DrvLogEnter(g_SDCAVCodecLog); + + NTSTATUS status = STATUS_SUCCESS; + + PCODEC_DEVICE_CONTEXT devCtx; + devCtx = GetCodecDeviceContext(Device); + ASSERT(devCtx != NULL); + + DECLARE_CONST_UNICODE_STRING(circuitName, L"ExtensionMicrophone0"); + DECLARE_CONST_UNICODE_STRING(circuitUri, EXT_CAPTURE_CIRCUIT_URI); + +#pragma prefast(suppress:__WARNING_ALIASED_MEMORY_LEAK, "memory is freed by scope_exit") + PSDCAXU_ACX_CIRCUIT_CONFIG exCircuitConfig = (PSDCAXU_ACX_CIRCUIT_CONFIG)ExAllocatePool2( + POOL_FLAG_NON_PAGED, + sizeof(SDCAXU_ACX_CIRCUIT_CONFIG) + circuitName.MaximumLength, + DRIVER_TAG); + RETURN_NTSTATUS_IF_TRUE(NULL == exCircuitConfig, STATUS_INSUFFICIENT_RESOURCES); + auto exConfigFree = scope_exit([&exCircuitConfig]() { + ExFreePoolWithTag(exCircuitConfig, DRIVER_TAG); + }); + + // + // Provide circuit configuration to SDCA XU driver + // SDCA XU driver will generate circuits to match this configuration + // + if (devCtx->SdcaXuData.bSdcaXu) + { + exCircuitConfig->cbSize = sizeof(SDCAXU_ACX_CIRCUIT_CONFIG) + circuitName.MaximumLength; + + exCircuitConfig->CircuitName = circuitName; + exCircuitConfig->CircuitName.Buffer = (PWCH)(exCircuitConfig + 1); + RtlCopyMemory(exCircuitConfig->CircuitName.Buffer, circuitName.Buffer, circuitName.MaximumLength); + + exCircuitConfig->CircuitContext = Circuit; + exCircuitConfig->CircuitType = AcxCircuitTypeCapture; + exCircuitConfig->ContainerID = SYSTEM_CONTAINER_GUID; + exCircuitConfig->ComponentID = EXTENSION_CIRCUIT_CAPTURE_GUID; + exCircuitConfig->ComponentUri = circuitUri; + + PSDCAXU_INTERFACE_V0101 exInterface = &devCtx->SdcaXuData.ExtensionInterface; + PVOID exContext = devCtx->SdcaXuData.ExtensionInterface.InterfaceHeader.Context; + + RETURN_NTSTATUS_IF_FAILED(exInterface->EvtSetEndpointConfig(exContext, SdcaXuEndpointConfigTypeAcxCircuitConfig, exCircuitConfig, exCircuitConfig->cbSize)); + } + + return status; +} + +PAGED_CODE_SEG +NTSTATUS +CodecC_CreateCaptureCircuit( + _In_ WDFDEVICE Device, + _Out_ ACXCIRCUIT * Circuit +) +/*++ + +Routine Description: + + This routine builds the CODEC capture circuit. + +Return Value: + + NT status value + +--*/ +{ + NTSTATUS status = STATUS_SUCCESS; + + PAGED_CODE(); + + // + // Init output value. + // + *Circuit = NULL; + + /////////////////////////////////////////////////////////// + // + // Create a circuit. + // + + // + // Get a CircuitInit structure. + // + PACXCIRCUIT_INIT circuitInit = NULL; + circuitInit = AcxCircuitInitAllocate(Device); + RETURN_NTSTATUS_IF_TRUE(NULL == circuitInit, STATUS_NO_MEMORY); + auto circuitInitScope = scope_exit([&circuitInit]() { + AcxCircuitInitFree(circuitInit); + }); + + // + // Add circuit identifiers. + // + AcxCircuitInitSetComponentId(circuitInit, &CODEC_CIRCUIT_CAPTURE_GUID); + + DECLARE_CONST_UNICODE_STRING(circuitUri, CAPTURE_CIRCUIT_URI); + RETURN_NTSTATUS_IF_FAILED(AcxCircuitInitAssignComponentUri(circuitInit, &circuitUri)); + + DECLARE_CONST_UNICODE_STRING(circuitName, L"Microphone0"); + RETURN_NTSTATUS_IF_FAILED(AcxCircuitInitAssignName(circuitInit, &circuitName)); + + // + // Add circuit type. + // + AcxCircuitInitSetCircuitType(circuitInit, AcxCircuitTypeCapture); + + // + // Assign the circuit's pnp-power callbacks. + // + ACX_CIRCUIT_PNPPOWER_CALLBACKS powerCallbacks; + ACX_CIRCUIT_PNPPOWER_CALLBACKS_INIT(&powerCallbacks); + powerCallbacks.EvtAcxCircuitPowerUp = CodecC_EvtCircuitPowerUp; + powerCallbacks.EvtAcxCircuitPowerDown = CodecC_EvtCircuitPowerDown; + AcxCircuitInitSetAcxCircuitPnpPowerCallbacks(circuitInit, &powerCallbacks); + + // + // Set circuit-callbacks. + // + RETURN_NTSTATUS_IF_FAILED(AcxCircuitInitAssignAcxRequestPreprocessCallback( + circuitInit, + CodecC_EvtCircuitRequestPreprocess, + (ACXCONTEXT)AcxRequestTypeAny, // dbg only + AcxRequestTypeAny, + NULL, + AcxItemIdNone)); + + RETURN_NTSTATUS_IF_FAILED(AcxCircuitInitAssignAcxCreateStreamCallback( + circuitInit, + CodecC_EvtCircuitCreateStream)); + + // + // Add properties, events and methods. + // + RETURN_NTSTATUS_IF_FAILED(AcxCircuitInitAssignProperties(circuitInit, + KwsProperties, + ARRAYSIZE(KwsProperties))); + + // + // Create the circuit. + // + WDF_OBJECT_ATTRIBUTES attributes; + WDF_OBJECT_ATTRIBUTES_INIT_CONTEXT_TYPE(&attributes, CODEC_CAPTURE_CIRCUIT_CONTEXT); + ACXCIRCUIT circuit; + RETURN_NTSTATUS_IF_FAILED(AcxCircuitCreate(Device, &attributes, &circuitInit, &circuit)); + circuitInitScope.release(); + + ASSERT(circuit != NULL); + CODEC_CAPTURE_CIRCUIT_CONTEXT *circuitCtx; + circuitCtx = GetCaptureCircuitContext(circuit); + ASSERT(circuitCtx); + + // + // Post circuit creation initialization. + // + + /////////////////////////////////////////////////////////// + // + // Add two custom circuit elements. Note that driver doesn't need to + // perform this step if it doesn't want to expose any circuit elements. + // + + // + // Create 1st custom circuit-element. + // + ACX_ELEMENT_CONFIG elementCfg; + ACX_ELEMENT_CONFIG_INIT(&elementCfg); + WDF_OBJECT_ATTRIBUTES_INIT_CONTEXT_TYPE(&attributes, CODEC_ELEMENT_CONTEXT); + attributes.ParentObject = circuit; + + const int numElements = 2; + ACXELEMENT elements[numElements] = {0}; + RETURN_NTSTATUS_IF_FAILED(AcxElementCreate(circuit, &attributes, &elementCfg, &elements[0])); + + ASSERT(elements[0] != NULL); + CODEC_ELEMENT_CONTEXT *elementCtx; + elementCtx = GetCodecElementContext(elements[0]); + ASSERT(elementCtx); + UNREFERENCED_PARAMETER(elementCtx); + + // + // Create 2nd custom circuit-element. + // + ACX_ELEMENT_CONFIG_INIT(&elementCfg); + WDF_OBJECT_ATTRIBUTES_INIT_CONTEXT_TYPE(&attributes, CODEC_ELEMENT_CONTEXT); + attributes.ParentObject = circuit; + + RETURN_NTSTATUS_IF_FAILED(AcxElementCreate(circuit, &attributes, &elementCfg, &elements[1])); + + ASSERT(elements[1] != NULL); + elementCtx = GetCodecElementContext(elements[1]); + ASSERT(elementCtx); + UNREFERENCED_PARAMETER(elementCtx); + + // + // Add the circuit elements + // + RETURN_NTSTATUS_IF_FAILED(AcxCircuitAddElements(circuit, elements, SIZEOF_ARRAY(elements))); + + /////////////////////////////////////////////////////////// + // Create Capture Pin, using default pin id. + // Acx Circuit will create other pin by default. + // + // Allocate the formats this circuit supports. Use formats without + // channel mask for capture. + // + // PCM:44100 channel:2 24in32 + ACX_DATAFORMAT_CONFIG formatCfg; + ACX_DATAFORMAT_CONFIG_INIT_KS(&formatCfg, &Pcm44100c2_24in32_nomask); + WDF_OBJECT_ATTRIBUTES_INIT_CONTEXT_TYPE(&attributes, CODEC_FORMAT_CONTEXT); + attributes.ParentObject = circuit; + + ACXDATAFORMAT formatPcm44100c2_24in32nomask; + RETURN_NTSTATUS_IF_FAILED(AcxDataFormatCreate(Device, &attributes, &formatCfg, &formatPcm44100c2_24in32nomask)); + + CODEC_FORMAT_CONTEXT *formatCtx; + formatCtx = GetCodecFormatContext(formatPcm44100c2_24in32nomask); + ASSERT(formatCtx); + UNREFERENCED_PARAMETER(formatCtx); + + // PCM:48000 channel:2 24in32 + ACX_DATAFORMAT_CONFIG_INIT_KS(&formatCfg, &Pcm48000c2_24in32_nomask); + WDF_OBJECT_ATTRIBUTES_INIT_CONTEXT_TYPE(&attributes, CODEC_FORMAT_CONTEXT); + attributes.ParentObject = circuit; + + ACXDATAFORMAT formatPcm48000c2_24in32nomask; + RETURN_NTSTATUS_IF_FAILED(AcxDataFormatCreate(Device, &attributes, &formatCfg, &formatPcm48000c2_24in32nomask)); + + formatCtx = GetCodecFormatContext(formatPcm48000c2_24in32nomask); + ASSERT(formatCtx); + UNREFERENCED_PARAMETER(formatCtx); + + // This is the format we'll report support for with KWS. Note that DSP uses 4ch; that includes + // 2ch from the hardware + 2ch reference + ACX_DATAFORMAT_CONFIG_INIT_KS(&formatCfg, &Pcm16000c2nomask); + WDF_OBJECT_ATTRIBUTES_INIT_CONTEXT_TYPE(&attributes, CODEC_FORMAT_CONTEXT); + attributes.ParentObject = circuit; + + ACXDATAFORMAT formatPcm16000c2nomask; + RETURN_NTSTATUS_IF_FAILED(AcxDataFormatCreate(Device, &attributes, &formatCfg, &formatPcm16000c2nomask)); + + formatCtx = GetCodecFormatContext(formatPcm16000c2nomask); + ASSERT(formatCtx); + UNREFERENCED_PARAMETER(formatCtx); + + /////////////////////////////////////////////////////////// + // + // Create Capture Pin. AcxCircuit creates the other pin by default. + // + + ACX_PIN_CALLBACKS pinCallbacks; + ACX_PIN_CALLBACKS_INIT(&pinCallbacks); + pinCallbacks.EvtAcxPinSetDataFormat = CodecC_EvtAcxPinSetDataFormat; + + ACX_PIN_CONFIG pinCfg; + ACX_PIN_CONFIG_INIT(&pinCfg); + pinCfg.Type = AcxPinTypeSource; + pinCfg.Communication = AcxPinCommunicationNone; + pinCfg.Category = &KSCATEGORY_AUDIO; + pinCfg.PinCallbacks = &pinCallbacks; + + WDF_OBJECT_ATTRIBUTES_INIT_CONTEXT_TYPE(&attributes, CODEC_PIN_CONTEXT); + attributes.EvtCleanupCallback = CodecC_EvtPinContextCleanup; + attributes.ParentObject = circuit; + + ACXPIN pin; + RETURN_NTSTATUS_IF_FAILED(AcxPinCreate(circuit, &attributes, &pinCfg, &pin)); + + ASSERT(pin != NULL); + CODEC_PIN_CONTEXT *pinCtx; + pinCtx = GetCodecPinContext(pin); + ASSERT(pinCtx); + UNREFERENCED_PARAMETER(pinCtx); + + // + // Add our supported formats to the Default mode for the circuit + // + ACXDATAFORMATLIST formatList; + formatList = AcxPinGetRawDataFormatList(pin); + RETURN_NTSTATUS_IF_TRUE(NULL == formatList, STATUS_INSUFFICIENT_RESOURCES); + + RETURN_NTSTATUS_IF_FAILED(AcxDataFormatListAddDataFormat(formatList, formatPcm44100c2_24in32nomask)); + + RETURN_NTSTATUS_IF_FAILED(AcxDataFormatListAddDataFormat(formatList, formatPcm48000c2_24in32nomask)); + + circuitCtx->KwsDataFormat = formatPcm16000c2nomask; + + // Add Capture Pin, using default pin id. + RETURN_NTSTATUS_IF_FAILED(AcxCircuitAddPins(circuit, &pin, 1)); + + /////////////////////////////////////////////////////////// + // + // Create Bridge Pin. + // + ACX_PIN_CONFIG_INIT(&pinCfg); + pinCfg.Type = AcxPinTypeSink; + pinCfg.Communication = AcxPinCommunicationNone; + pinCfg.Category = &KSNODETYPE_MICROPHONE; + + WDF_OBJECT_ATTRIBUTES_INIT_CONTEXT_TYPE(&attributes, CODEC_PIN_CONTEXT); + attributes.EvtCleanupCallback = CodecR_EvtPinContextCleanup; + attributes.ParentObject = circuit; + RETURN_NTSTATUS_IF_FAILED(AcxPinCreate(circuit, &attributes, &pinCfg, &pin)); + + ASSERT(pin != NULL); + + RETURN_NTSTATUS_IF_FAILED(AddJack(attributes, pin, SPEAKER_FRONT_LEFT | SPEAKER_FRONT_RIGHT, RGB(0, 0, 0), AcxConnTypeAtapiInternal, AcxGeoLocFront, AcxGenLocPrimaryBox, AcxPortConnIntegratedDevice)); + + // Add capture bridge pin + RETURN_NTSTATUS_IF_FAILED(AcxCircuitAddPins(circuit, &pin, 1)); + + + + // + // Explicitly connect the circuit/elements. Note that driver doens't + // need to perform this step when circuit/elements are connected in the + // same order as they were added to the circuit. By default ACX connects + // the elements starting from the sink circuit pin and ending on the + // source circuit pin on both render and capture devices. + // + // circuit.pin[default_sink] -> 1st element.pin[default_in] + // 1st element.pin[default_out] -> 2nd element.pin[default_in] + // 2nd element.pin[default_out] -> circuit.pin[default_source] + // + const int numConnections = numElements + 1; + ACX_CONNECTION connections[numConnections]; + ACX_CONNECTION_INIT(&connections[0], circuit, elements[0]); + ACX_CONNECTION_INIT(&connections[1], elements[0], elements[1]); + ACX_CONNECTION_INIT(&connections[2], elements[1], circuit); + + // + // Add the connections linking circuit to elements. + // + RETURN_NTSTATUS_IF_FAILED(AcxCircuitAddConnections(circuit, connections, SIZEOF_ARRAY(connections))); + + // + // Set output value. + // + *Circuit = circuit; + + return status; +} + +_Use_decl_annotations_ +PAGED_CODE_SEG +NTSTATUS +CodecC_EvtCircuitPowerUp( + _In_ WDFDEVICE Device, + _In_ ACXCIRCUIT Circuit, + _In_ WDF_POWER_DEVICE_STATE PreviousState +) +{ + PAGED_CODE(); + + UNREFERENCED_PARAMETER(Device); + UNREFERENCED_PARAMETER(Circuit); + UNREFERENCED_PARAMETER(PreviousState); + + return STATUS_SUCCESS; +} + +_Use_decl_annotations_ +PAGED_CODE_SEG +NTSTATUS +CodecC_EvtCircuitPowerDown( + _In_ WDFDEVICE Device, + _In_ ACXCIRCUIT Circuit, + _In_ WDF_POWER_DEVICE_STATE TargetState +) +{ + PAGED_CODE(); + + UNREFERENCED_PARAMETER(Device); + UNREFERENCED_PARAMETER(Circuit); + UNREFERENCED_PARAMETER(TargetState); + + return STATUS_SUCCESS; +} + +PAGED_CODE_SEG +NTSTATUS +CodecC_EvtCircuitCreateStream( + _In_ WDFDEVICE Device, + _In_ ACXCIRCUIT Circuit, + _In_ ACXPIN Pin, + _In_ PACXSTREAM_INIT StreamInit, + _In_ ACXDATAFORMAT StreamFormat, + _In_ const GUID * SignalProcessingMode, + _In_ ACXOBJECTBAG VarArguments +) +/*++ + +Routine Description: + + This routine create a stream for the specified circuit. + +Return Value: + + NT status value + +--*/ +{ + PAGED_CODE(); + + NTSTATUS status = STATUS_SUCCESS; + + UNREFERENCED_PARAMETER(Pin); + UNREFERENCED_PARAMETER(SignalProcessingMode); + UNREFERENCED_PARAMETER(VarArguments); + + ASSERT(IsEqualGUID(*SignalProcessingMode, AUDIO_SIGNALPROCESSINGMODE_RAW)); + + PCODEC_CAPTURE_DEVICE_CONTEXT devCtx; + devCtx = GetCaptureDeviceContext(Device); + ASSERT(devCtx != NULL); + + DECLARE_CONST_ACXOBJECTBAG_DRIVER_PROPERTY_NAME(msft, TestUI4); + if (VarArguments) + { + // Get the variable arguments parameter and retrive the values set by the DSP object. + ULONG ui4Value = 0; + RETURN_NTSTATUS_IF_FAILED(AcxObjectBagRetrieveUI4(VarArguments, &TestUI4, &ui4Value)); + + RETURN_NTSTATUS_IF_TRUE(ui4Value == 0, STATUS_UNSUCCESSFUL); + + ui4Value++; + + // Add the modified value back to object bag. + RETURN_NTSTATUS_IF_FAILED(AcxObjectBagAddUI4(VarArguments, &TestUI4, ui4Value)); + } + + // + // Set circuit-callbacks. + // + RETURN_NTSTATUS_IF_FAILED(AcxStreamInitAssignAcxRequestPreprocessCallback( + StreamInit, + CodecC_EvtStreamRequestPreprocess, + (ACXCONTEXT)AcxRequestTypeAny, // dbg only + AcxRequestTypeAny, + NULL, + AcxItemIdNone)); + + /* + // + // Add properties, events and methods. + // + RETURN_NTSTATUS_IF_FAILED(AcxStreamInitAssignProperties(StreamInit, + StreamProperties, + StreamPropertiesCount)); + */ + + // + // Init streaming callbacks. + // + ACX_STREAM_CALLBACKS streamCallbacks; + ACX_STREAM_CALLBACKS_INIT(&streamCallbacks); + streamCallbacks.EvtAcxStreamPrepareHardware = Codec_EvtStreamPrepareHardware; + streamCallbacks.EvtAcxStreamReleaseHardware = Codec_EvtStreamReleaseHardware; + streamCallbacks.EvtAcxStreamRun = Codec_EvtStreamRun; + streamCallbacks.EvtAcxStreamPause = Codec_EvtStreamPause; + + RETURN_NTSTATUS_IF_FAILED(AcxStreamInitAssignAcxStreamCallbacks(StreamInit, &streamCallbacks)); + + // + // Create the stream. + // + WDF_OBJECT_ATTRIBUTES attributes; + WDF_OBJECT_ATTRIBUTES_INIT_CONTEXT_TYPE(&attributes, CODEC_STREAM_CONTEXT); + attributes.EvtDestroyCallback = Codec_EvtStreamDestroy; + ACXSTREAM stream; + RETURN_NTSTATUS_IF_FAILED(AcxStreamCreate(Device, Circuit, &attributes, &StreamInit, &stream)); + + CCaptureStreamEngine *streamEngine = NULL; + streamEngine = new(POOL_FLAG_NON_PAGED, DRIVER_TAG) CCaptureStreamEngine(stream, StreamFormat); + RETURN_NTSTATUS_IF_TRUE(NULL == streamEngine, STATUS_INSUFFICIENT_RESOURCES); + + CODEC_STREAM_CONTEXT *streamCtx; + streamCtx = GetCodecStreamContext(stream); + ASSERT(streamCtx); + streamCtx->StreamEngine = (PVOID)streamEngine; + streamEngine = NULL; + + // + // Post stream creation initialization. + // + + // + // Create 1st custom stream-elements. + // + ACX_ELEMENT_CONFIG elementCfg; + ACX_ELEMENT_CONFIG_INIT(&elementCfg); + WDF_OBJECT_ATTRIBUTES_INIT_CONTEXT_TYPE(&attributes, CODEC_ELEMENT_CONTEXT); + attributes.ParentObject = stream; + + ACXELEMENT elements[2] = {0}; + RETURN_NTSTATUS_IF_FAILED(AcxElementCreate(stream, &attributes, &elementCfg, &elements[0])); + + ASSERT(elements[0] != NULL); + CODEC_ELEMENT_CONTEXT *elementCtx; + elementCtx = GetCodecElementContext(elements[0]); + ASSERT(elementCtx); + UNREFERENCED_PARAMETER(elementCtx); + + // + // Create 2nd custom stream-elements. + // + ACX_ELEMENT_CONFIG_INIT(&elementCfg); + WDF_OBJECT_ATTRIBUTES_INIT_CONTEXT_TYPE(&attributes, CODEC_ELEMENT_CONTEXT); + attributes.ParentObject = stream; + + RETURN_NTSTATUS_IF_FAILED(AcxElementCreate(stream, &attributes, &elementCfg, &elements[1])); + + ASSERT(elements[1] != NULL); + elementCtx = GetCodecElementContext(elements[1]); + ASSERT(elementCtx); + UNREFERENCED_PARAMETER(elementCtx); + + // + // Add stream elements + // + RETURN_NTSTATUS_IF_FAILED(AcxStreamAddElements(stream, elements, SIZEOF_ARRAY(elements))); + + return status; +} + + diff --git a/audio/Soundwire/Samples/SdcaVad/SdcaVCodec/device.cpp b/audio/Soundwire/Samples/SdcaVad/SdcaVCodec/device.cpp new file mode 100644 index 000000000..e4805669a --- /dev/null +++ b/audio/Soundwire/Samples/SdcaVad/SdcaVCodec/device.cpp @@ -0,0 +1,808 @@ +/*++ + + THIS CODE AND INFORMATION IS PROVIDED "AS IS" WITHOUT WARRANTY OF ANY + KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE + IMPLIED WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A PARTICULAR + PURPOSE. + +Module Name: + + Device.cpp + +Abstract: + + Plug and Play module. This file contains routines to handle pnp requests. + +Environment: + + Kernel mode + +--*/ + +#include "private.h" +#include +#include "stdunk.h" +#include +#include +#include +#include "streamengine.h" +#include "CircuitHelper.h" + +#ifndef __INTELLISENSE__ +#include "device.tmh" +#endif + +UNICODE_STRING g_RegistryPath = {0}; // This is used to store the registry settings path for the driver + +__drv_requiresIRQL(PASSIVE_LEVEL) +PAGED_CODE_SEG +NTSTATUS CopyRegistrySettingsPath +( + _In_ PUNICODE_STRING RegistryPath +) +/*++ + +Routine Description: + +Copies the following registry path to a global variable. + +\REGISTRY\MACHINE\SYSTEM\ControlSetxxx\Services\\Parameters + +Arguments: + +RegistryPath - Registry path passed to DriverEntry + +Returns: + +NTSTATUS - SUCCESS if able to configure the framework + +--*/ + +{ + PAGED_CODE(); + + // Initializing the unicode string, so that if it is not allocated it will not be deallocated too. + RtlInitUnicodeString(&g_RegistryPath, NULL); + + g_RegistryPath.MaximumLength = RegistryPath->Length + sizeof(WCHAR); + + g_RegistryPath.Buffer = (PWCH)ExAllocatePool2(POOL_FLAG_PAGED, g_RegistryPath.MaximumLength, DRIVER_TAG); + + if (g_RegistryPath.Buffer == NULL) + { + return STATUS_INSUFFICIENT_RESOURCES; + } + + // ExAllocatePool2 zeros memory. + + RtlAppendUnicodeToString(&g_RegistryPath, RegistryPath->Buffer); + + return STATUS_SUCCESS; +} + +PAGED_CODE_SEG +NTSTATUS +Codec_EvtBusDeviceAdd( + _In_ WDFDRIVER Driver, + _Inout_ PWDFDEVICE_INIT DeviceInit + ) +/*++ +Routine Description: + + EvtDeviceAdd is called by the framework in response to AddDevice + call from the PnP manager. We create and initialize a device object to + represent a new instance of the device. All the software resources + should be allocated in this callback. + +Arguments: + + Driver - Handle to a framework driver object created in DriverEntry + + DeviceInit - Pointer to a framework-allocated WDFDEVICE_INIT structure. + +Return Value: + + NTSTATUS + +--*/ +{ + PAGED_CODE(); + + NTSTATUS status = STATUS_SUCCESS; + + // + // Allow ACX to add any pre-requirement it needs on this device. + // + ACX_DEVICEINIT_CONFIG devInitCfg; + ACX_DEVICEINIT_CONFIG_INIT(&devInitCfg); + RETURN_NTSTATUS_IF_FAILED(AcxDeviceInitInitialize(DeviceInit, &devInitCfg)); + + // + // Initialize the pnpPowerCallbacks structure. Callback events for PNP + // and Power are specified here. If you don't supply any callbacks, + // the Framework will take appropriate default actions based on whether + // DeviceInit is initialized to be an FDO, a PDO or a filter device + // object. + // + WDF_PNPPOWER_EVENT_CALLBACKS pnpPowerCallbacks; + WDF_PNPPOWER_EVENT_CALLBACKS_INIT(&pnpPowerCallbacks); + pnpPowerCallbacks.EvtDevicePrepareHardware = Codec_EvtDevicePrepareHardware; + pnpPowerCallbacks.EvtDeviceReleaseHardware = Codec_EvtDeviceReleaseHardware; + WdfDeviceInitSetPnpPowerEventCallbacks(DeviceInit, &pnpPowerCallbacks); + + // + // Specify the type of context needed. + // Use default locking, i.e., none. + // + WDF_OBJECT_ATTRIBUTES attributes; + WDF_OBJECT_ATTRIBUTES_INIT_CONTEXT_TYPE(&attributes, CODEC_DEVICE_CONTEXT); + attributes.EvtCleanupCallback = Codec_EvtDeviceContextCleanup; + + // + // Create the device. + // + WDFDEVICE device = NULL; + RETURN_NTSTATUS_IF_FAILED(WdfDeviceCreate(&DeviceInit, &attributes, &device)); + + // + // Init Codec's device context. + // + PCODEC_DEVICE_CONTEXT devCtx; + devCtx = GetCodecDeviceContext(device); + ASSERT(devCtx != NULL); + devCtx->Render = NULL; + devCtx->Capture = NULL; + + // + // Assume XU lower filter driver is not present + // + devCtx->SdcaXuData.bSdcaXu = FALSE; + + // + // Allow ACX to add any post-requirement it needs on this device. + // + ACX_DEVICE_CONFIG devCfg; + ACX_DEVICE_CONFIG_INIT(&devCfg); + RETURN_NTSTATUS_IF_FAILED(AcxDeviceInitialize(device, &devCfg)); + + // + // Tell the framework to set the SurpriseRemovalOK in the DeviceCaps so + // that you don't get the popup in usermode (on Win2K) when you surprise + // remove the device. + // + WDF_DEVICE_PNP_CAPABILITIES pnpCaps; + WDF_DEVICE_PNP_CAPABILITIES_INIT(&pnpCaps); + pnpCaps.SurpriseRemovalOK = WdfTrue; + WdfDeviceSetPnpCapabilities(device, &pnpCaps); + + // + // Get SDCA XU filter interface + // + RETURN_NTSTATUS_IF_FAILED_UNLESS_ALLOWED(Codec_GetSdcaXu(device), STATUS_NOT_SUPPORTED); + + if (devCtx->SdcaXuData.bSdcaXu) + { + RETURN_NTSTATUS_IF_FAILED(Codec_SetSdcaXuHwConfig(device)); + } + + RETURN_NTSTATUS_IF_FAILED(Codec_AddRenderComposites(device)); + + RETURN_NTSTATUS_IF_FAILED(Codec_AddCaptureComposites(device)); + + // + // Add a render device and a capture device. + // + RETURN_NTSTATUS_IF_FAILED(CodecR_AddRenders(Driver, device)); + + // + // Add a render device and a capture device. + // + RETURN_NTSTATUS_IF_FAILED(CodecC_AddCaptures(Driver, device)); + + return status; +} + +PAGED_CODE_SEG +NTSTATUS +Codec_EvtDevicePrepareHardware( + _In_ WDFDEVICE Device, + _In_ WDFCMRESLIST ResourceList, + _In_ WDFCMRESLIST ResourceListTranslated +) +/*++ + +Routine Description: + + In this callback, the driver does whatever is necessary to make the + hardware ready to use. + +Arguments: + + Device - handle to a device + +Return Value: + + NT status value + +--*/ +{ + + UNREFERENCED_PARAMETER(ResourceList); + UNREFERENCED_PARAMETER(ResourceListTranslated); + + PAGED_CODE(); + + DrvLogEnter(g_SDCAVCodecLog); + + NTSTATUS status = STATUS_SUCCESS; + + PCODEC_DEVICE_CONTEXT devCtx; + devCtx = GetCodecDeviceContext(Device); + ASSERT(devCtx != NULL); + + + RETURN_NTSTATUS_IF_FAILED(Codec_SetPowerPolicy(Device)); + + // + // Add static circuit to device's list. + // + ASSERT(devCtx->Render); + RETURN_NTSTATUS_IF_FAILED(AcxDeviceAddCircuit(Device, devCtx->Render)); + + ASSERT(devCtx->Capture); + RETURN_NTSTATUS_IF_FAILED(AcxDeviceAddCircuit(Device, devCtx->Capture)); + + return status; +} + +PAGED_CODE_SEG +NTSTATUS +Codec_EvtDeviceReleaseHardware( + _In_ WDFDEVICE Device, + _In_ WDFCMRESLIST ResourceListTranslated +) +/*++ + +Routine Description: + + In this callback, the driver releases the h/w resources allocated in the + prepare h/w callback. + +Arguments: + + Device - handle to a device + +Return Value: + + NT status value + +--*/ +{ + PAGED_CODE(); + + NTSTATUS status = STATUS_SUCCESS; + + UNREFERENCED_PARAMETER(Device); + UNREFERENCED_PARAMETER(ResourceListTranslated); + + PCODEC_DEVICE_CONTEXT devCtx; + devCtx = GetCodecDeviceContext(Device); + ASSERT(devCtx != NULL); + + + return status; +} + +PAGED_CODE_SEG +NTSTATUS +Codec_SetPowerPolicy( + _In_ WDFDEVICE Device +) +{ + PAGED_CODE(); + + NTSTATUS status = STATUS_SUCCESS; + + DrvLogEnter(g_SDCAVCodecLog); + + WDF_DEVICE_POWER_POLICY_IDLE_SETTINGS idleSettings; + //WDF_DEVICE_POWER_POLICY_WAKE_SETTINGS wakeSettings; + + // + // Init the idle policy structure. + // + //WDF_DEVICE_POWER_POLICY_IDLE_SETTINGS_INIT(&idleSettings, IdleCanWakeFromS0); + WDF_DEVICE_POWER_POLICY_IDLE_SETTINGS_INIT(&idleSettings, IdleCannotWakeFromS0); + idleSettings.IdleTimeout = 10000; // 10-sec + + RETURN_NTSTATUS_IF_FAILED(WdfDeviceAssignS0IdleSettings(Device, &idleSettings)); + + return status; +} + +#pragma code_seg() + +DEFINE_GUID(CODEC_CIRCUIT_RENDER_GUID, +0xfd4b6e78, 0x51e0, 0x4aa6, 0x90, 0x98, 0xbb, 0xcb, 0x70, 0x89, 0xcb, 0x6a); + +DEFINE_GUID(EXTENSION_CIRCUIT_RENDER_GUID, +0x656ab905, 0x55fb, 0x4b08, 0xb6, 0x01, 0xd7, 0xf0, 0xc1, 0xce, 0x36, 0x2c); + +// {17F5B19F-C2C7-4B53-AFB9-49A0283D0DCE} +DEFINE_GUID(DSP_CIRCUIT_SPEAKER_GUID, + 0x17f5b19f, 0xc2c7, 0x4b53, 0xaf, 0xb9, 0x49, 0xa0, 0x28, 0x3d, 0xd, 0xce); + +DEFINE_GUID(CODEC_CIRCUIT_CAPTURE_GUID, +0x67ec5936, 0xa395, 0x4e93, 0xbe, 0x8a, 0xfc, 0xed, 0xe3, 0x1b, 0xad, 0x40); + +DEFINE_GUID(EXTENSION_CIRCUIT_CAPTURE_GUID, +0x44c69385, 0xa012, 0x405f, 0x8a, 0x9a, 0x7b, 0x44, 0x29, 0x71, 0xc8, 0x50); + +// {6F9EACF7-CD2D-4030-9E49-7CC4ADEFF192} +DEFINE_GUID(DSP_CIRCUIT_MICROPHONE_GUID, + 0x6f9eacf7, 0xcd2d, 0x4030, 0x9e, 0x49, 0x7c, 0xc4, 0xad, 0xef, 0xf1, 0x92); + +DEFINE_GUID(SYSTEM_CONTAINER_GUID, +0x00000000, 0x0000, 0x0000, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF); + + +#define DSP_FACTORY_URI L"acpi:obj-path:\\_SB.PC00.HDAS" + +#define RENDER_CIRCUIT_UNIQUE_ID L"{613fd364-64bb-4b69-99bc-5b075ea9756b}" +#define RENDER_CIRCUIT_FRIENDLY_NAME L"Speaker-360" +#define RENDER_CIRCUIT_NAME L"Speaker" +#define CAPTURE_CIRCUIT_UNIQUE_ID L"{3A509246-5902-4AA2-9E06-C7C8D10461C3}" +#define CAPTURE_CIRCUIT_FRIENDLY_NAME L"Microphone-360" +#define CAPTURE_CIRCUIT_NAME L"Microphone" + +#define CIRCUIT_RENDER_VENDOR_BLOB "Streaming_Speaker" +#define CIRCUIT_CAPTURE_VENDOR_BLOB "Streaming_MicrophoneArray" + +__drv_requiresIRQL(PASSIVE_LEVEL) +PAGED_CODE_SEG +NTSTATUS +Codec_AddRenderComposites(_In_ WDFDEVICE Device) +{ + NTSTATUS status = STATUS_SUCCESS; + + UNREFERENCED_PARAMETER(Device); + + PAGED_CODE(); + + RETURN_NTSTATUS_IF_FAILED(Codec_AddComposites(Device, CompositeType_RENDER)); + + return status; +} + +__drv_requiresIRQL(PASSIVE_LEVEL) +PAGED_CODE_SEG +NTSTATUS +Codec_AddCaptureComposites(_In_ WDFDEVICE Device) +{ + NTSTATUS status = STATUS_SUCCESS; + + UNREFERENCED_PARAMETER(Device); + + PAGED_CODE(); + + RETURN_NTSTATUS_IF_FAILED(Codec_AddComposites(Device, CompositeType_CAPTURE)); + + return status; +} + +__drv_requiresIRQL(PASSIVE_LEVEL) +PAGED_CODE_SEG +NTSTATUS +Codec_AddComposites(_In_ WDFDEVICE Device, _In_ CompositeType compositeType) +{ + PAGED_CODE(); + + NTSTATUS status = STATUS_SUCCESS; + + DrvLogEnter(g_SDCAVCodecLog); + + UNICODE_STRING circuit_IDs[] = { + { sizeof(RENDER_CIRCUIT_UNIQUE_ID) - sizeof(WCHAR), sizeof(RENDER_CIRCUIT_UNIQUE_ID), RENDER_CIRCUIT_UNIQUE_ID }, + { sizeof(CAPTURE_CIRCUIT_UNIQUE_ID) - sizeof(WCHAR), sizeof(CAPTURE_CIRCUIT_UNIQUE_ID), CAPTURE_CIRCUIT_UNIQUE_ID} + }; + + UNICODE_STRING circuit_friendly_names[] = { + { sizeof(RENDER_CIRCUIT_FRIENDLY_NAME) - sizeof(WCHAR), sizeof(RENDER_CIRCUIT_FRIENDLY_NAME), RENDER_CIRCUIT_FRIENDLY_NAME }, + { sizeof(CAPTURE_CIRCUIT_FRIENDLY_NAME) - sizeof(WCHAR), sizeof(CAPTURE_CIRCUIT_FRIENDLY_NAME), CAPTURE_CIRCUIT_FRIENDLY_NAME} + }; + + UNICODE_STRING circuit_names[] = { + { sizeof(RENDER_CIRCUIT_NAME) - sizeof(WCHAR), sizeof(RENDER_CIRCUIT_NAME), RENDER_CIRCUIT_NAME }, + { sizeof(CAPTURE_CIRCUIT_NAME) - sizeof(WCHAR), sizeof(CAPTURE_CIRCUIT_NAME), CAPTURE_CIRCUIT_NAME} + }; + + UNICODE_STRING codec_circuit_uris[] = { + { sizeof(RENDER_CIRCUIT_URI) - sizeof(WCHAR), sizeof(RENDER_CIRCUIT_URI), RENDER_CIRCUIT_URI }, + { sizeof(CAPTURE_CIRCUIT_URI) - sizeof(WCHAR), sizeof(CAPTURE_CIRCUIT_URI), CAPTURE_CIRCUIT_URI} + }; + + UNICODE_STRING extension_circuit_uris[] = { + { sizeof(EXT_RENDER_CIRCUIT_URI) - sizeof(WCHAR), sizeof(EXT_RENDER_CIRCUIT_URI), EXT_RENDER_CIRCUIT_URI }, + { sizeof(EXT_CAPTURE_CIRCUIT_URI) - sizeof(WCHAR), sizeof(EXT_CAPTURE_CIRCUIT_URI), EXT_CAPTURE_CIRCUIT_URI} + }; + + GUID dsp_circuit_guids[] = { + DSP_CIRCUIT_SPEAKER_GUID, + DSP_CIRCUIT_MICROPHONE_GUID + }; + + UNICODE_STRING dsp_factory_uris[] = { + { sizeof(DSP_FACTORY_URI) - sizeof(WCHAR), sizeof(DSP_FACTORY_URI), DSP_FACTORY_URI }, + { sizeof(DSP_FACTORY_URI) - sizeof(WCHAR), sizeof(DSP_FACTORY_URI), DSP_FACTORY_URI} + }; + + const char* dsp_factory_vendor_blobs[] = { + CIRCUIT_RENDER_VENDOR_BLOB, + CIRCUIT_CAPTURE_VENDOR_BLOB + }; + + PCODEC_DEVICE_CONTEXT deviceCtx = NULL; + deviceCtx = GetCodecDeviceContext(Device); + ASSERT(deviceCtx); + + // + // May be called again for rebalance + // Add composites only once + // + RETURN_NTSTATUS_IF_TRUE(0 != deviceCtx->refComposite[compositeType], STATUS_SUCCESS); + + // + // Object bag + // + // This obj-bag config setting is shared by all composite/circuit templates. + ACX_OBJECTBAG_CONFIG objBagCfg; + ACX_OBJECTBAG_CONFIG_INIT(&objBagCfg); + + WDF_OBJECT_ATTRIBUTES attributes; + WDF_OBJECT_ATTRIBUTES_INIT(&attributes); + attributes.ParentObject = AcxGetManager(NULL); + + ACXOBJECTBAG objBag = NULL; + RETURN_NTSTATUS_IF_FAILED(AcxObjectBagCreate(&attributes, &objBagCfg, &objBag)); + auto objBag_scope = scope_exit([&objBag]() { + if (objBag != NULL) + { + WdfObjectDelete(objBag); + } + }); + + // + // Add a test unsigned int 4 bytes to the object bag + // + RETURN_NTSTATUS_IF_FAILED(ObjBagAddTestUI4(objBag, 0)); + + // + // Add unique circuit ID to the object bag + // This unique Id will be picked up by DSP circuit + // + DECLARE_CONST_ACXOBJECTBAG_SYSTEM_PROPERTY_NAME(UniqueID); + GUID uniqueID = { 0 }; + RETURN_NTSTATUS_IF_FAILED(RtlGUIDFromString(&circuit_IDs[compositeType], &uniqueID)); + + RETURN_NTSTATUS_IF_FAILED(AcxObjectBagAddGuid(objBag, &UniqueID, uniqueID)); + + RETURN_NTSTATUS_IF_FAILED(ObjBagAddUnicodeStrings(objBag, circuit_friendly_names[compositeType], circuit_names[compositeType])); + + RETURN_NTSTATUS_IF_FAILED(ObjBagAddEndpointId(objBag, 9)); + + RETURN_NTSTATUS_IF_FAILED(ObjBagAddDataPortNumber(objBag, 9)); + + // + // Composite template. + // + ULONG circuitsInTemplate = 0; + ACXCIRCUITTEMPLATE circuits[3] = { 0 }; + ACX_COMPOSITE_TEMPLATE_CONFIG compositeCfg; + ACX_COMPOSITE_TEMPLATE_CONFIG_INIT(&compositeCfg); + compositeCfg.Properties = objBag; + compositeCfg.Flags |= AcxCompositeTemplateConfigSingleton; + + ACXCOMPOSITETEMPLATE composite = NULL; + RETURN_NTSTATUS_IF_FAILED(AcxCompositeTemplateCreate(WdfGetDriver(), + &attributes, + &compositeCfg, + &composite)); + + auto composite_scope = scope_exit([&composite]() { + WdfObjectDelete(composite); + composite = NULL; + }); + + objBag = NULL; + + // This attribute setting is shared by all the circuit templates. + WDF_OBJECT_ATTRIBUTES_INIT(&attributes); + attributes.ParentObject = composite; + + // Codec template. + RETURN_NTSTATUS_IF_FAILED(AcxObjectBagCreate(&attributes, &objBagCfg, &objBag)); + + RETURN_NTSTATUS_IF_FAILED(ObjBagAddTestUI4(objBag, 2)); + + ACX_CIRCUIT_TEMPLATE_CONFIG circuitCfg1; + ACX_CIRCUIT_TEMPLATE_CONFIG_INIT(&circuitCfg1); + circuitCfg1.CircuitProperties = objBag; + circuitCfg1.CircuitUri = &codec_circuit_uris[compositeType]; + + ULONG codecIndex = circuitsInTemplate; + RETURN_NTSTATUS_IF_FAILED(AcxCircuitTemplateCreate(WdfGetDriver(), + &attributes, + &circuitCfg1, + &circuits[circuitsInTemplate++])); + + objBag = NULL; + + // XU template. + // + // Check if XU is present + // and compose with Xu circuit + // + if (deviceCtx->SdcaXuData.bSdcaXu) + { + RETURN_NTSTATUS_IF_FAILED(AcxObjectBagCreate(&attributes, &objBagCfg, &objBag)); + + RETURN_NTSTATUS_IF_FAILED(ObjBagAddTestUI4(objBag, 2)); + + ACX_CIRCUIT_TEMPLATE_CONFIG circuitCfg2; + ACX_CIRCUIT_TEMPLATE_CONFIG_INIT(&circuitCfg2); + circuitCfg2.CircuitProperties = objBag; + circuitCfg2.CircuitUri = &extension_circuit_uris[compositeType]; + + RETURN_NTSTATUS_IF_FAILED(AcxCircuitTemplateCreate(WdfGetDriver(), + &attributes, + &circuitCfg2, + &circuits[circuitsInTemplate++])); + + objBag = NULL; + } + + // Dsp template. + RETURN_NTSTATUS_IF_FAILED(AcxObjectBagCreate(&attributes, &objBagCfg, &objBag)); + + RETURN_NTSTATUS_IF_FAILED(ObjBagAddTestUI4(objBag, 3)); + + RETURN_NTSTATUS_IF_FAILED(ObjBagAddCircuitId(objBag, dsp_circuit_guids[compositeType])); + + RETURN_NTSTATUS_IF_FAILED(ObjBagAddBlob(objBag, dsp_factory_vendor_blobs[compositeType])); + + ACX_CIRCUIT_TEMPLATE_CONFIG circuitCfg3; + ACX_CIRCUIT_TEMPLATE_CONFIG_INIT(&circuitCfg3); + circuitCfg3.CircuitProperties = objBag; + circuitCfg3.FactoryUri = &dsp_factory_uris[compositeType]; + circuitCfg3.Flags |= AcxCircuitTemplateCircuitOnDemand; + + RETURN_NTSTATUS_IF_FAILED(AcxCircuitTemplateCreate(WdfGetDriver(), + &attributes, + &circuitCfg3, + &circuits[circuitsInTemplate++])); + + objBag = NULL; + objBag_scope.release(); + + RETURN_NTSTATUS_IF_FAILED(AcxCompositeTemplateAssignCircuits(composite, circuits, circuitsInTemplate)); + + // Select the core circuit. + AcxCompositeTemplateSetCoreCircuit(composite, circuits[codecIndex]); + + // Final step. + RETURN_NTSTATUS_IF_FAILED(AcxManagerAddCompositeTemplate(AcxGetManager(NULL), composite)); + + deviceCtx->Composite[compositeType] = composite; + composite_scope.release(); + + deviceCtx->refComposite[compositeType]++; + + return status; +} + +#pragma code_seg() +NTSTATUS +Codec_RemoveComposites(_In_ WDFDEVICE Device) +{ + NTSTATUS status = STATUS_SUCCESS; + + PCODEC_DEVICE_CONTEXT deviceCtx = NULL; + deviceCtx = GetCodecDeviceContext(Device); + ASSERT(deviceCtx); + + for (ULONG compositeType = CompositeType_RENDER; compositeType <= CompositeType_CAPTURE; ) + { + if (deviceCtx->refComposite[compositeType]) + { + deviceCtx->refComposite[compositeType]--; + if (deviceCtx->refComposite[compositeType] == 0) + { + if (deviceCtx->Composite[compositeType] != NULL) + { + RETURN_NTSTATUS_IF_FAILED(AcxManagerRemoveCompositeTemplate(AcxGetManager(NULL), deviceCtx->Composite[compositeType])); + + WdfObjectDelete(deviceCtx->Composite[compositeType]); + deviceCtx->Composite[compositeType] = NULL; + } + } + } + + compositeType++; + } + + return status; +} + +#pragma code_seg() +VOID +Codec_EvtDeviceContextCleanup( + _In_ WDFOBJECT WdfDevice + ) +/*++ + +Routine Description: + + In this callback, it cleans up device context. + +Arguments: + + WdfDevice - WDF device object + +Return Value: + + NULL + +--*/ +{ + WDFDEVICE device; + PCODEC_DEVICE_CONTEXT devCtx; + + + device = (WDFDEVICE)WdfDevice; + devCtx = GetCodecDeviceContext(device); + ASSERT(devCtx != NULL); + + Codec_RemoveComposites(device); + + if (devCtx->SdcaXuData.XUEntities) + { + ExFreePoolWithTag(devCtx->SdcaXuData.XUEntities, DRIVER_TAG); + devCtx->SdcaXuData.numXUEntities = 0; + } + if (devCtx->SdcaXuData.InterruptInfo) + { + ExFreePoolWithTag(devCtx->SdcaXuData.InterruptInfo, DRIVER_TAG); + devCtx->SdcaXuData.InterruptInfo = NULL; + } +} + +#pragma code_seg() +VOID +Codec_EvtStreamDestroy( + _In_ WDFOBJECT Object + ) +{ + PCODEC_STREAM_CONTEXT ctx; + CStreamEngine * streamEngine = NULL; + + ctx = GetCodecStreamContext((ACXSTREAM)Object); + + streamEngine = (CStreamEngine*)ctx->StreamEngine; + ctx->StreamEngine = NULL; + delete streamEngine; +} + +PAGED_CODE_SEG +NTSTATUS +Codec_EvtStreamGetHwLatency( + _In_ ACXSTREAM Stream, + _Out_ ULONG * FifoSize, + _Out_ ULONG * Delay +) +{ + PCODEC_STREAM_CONTEXT ctx; + CStreamEngine * streamEngine = NULL; + + PAGED_CODE(); + + ctx = GetCodecStreamContext(Stream); + + streamEngine = (CStreamEngine*)ctx->StreamEngine; + + return streamEngine->GetHWLatency(FifoSize, Delay); +} + +PAGED_CODE_SEG +NTSTATUS +Codec_EvtStreamPrepareHardware( + _In_ ACXSTREAM Stream + ) +{ + PCODEC_STREAM_CONTEXT ctx; + CStreamEngine * streamEngine = NULL; + + PAGED_CODE(); + + ctx = GetCodecStreamContext(Stream); + + streamEngine = (CStreamEngine*)ctx->StreamEngine; + + return streamEngine->PrepareHardware(); +} + +PAGED_CODE_SEG +NTSTATUS +Codec_EvtStreamReleaseHardware( + _In_ ACXSTREAM Stream + ) +{ + PCODEC_STREAM_CONTEXT ctx; + CStreamEngine * streamEngine = NULL; + + PAGED_CODE(); + + ctx = GetCodecStreamContext(Stream); + + streamEngine = (CStreamEngine*)ctx->StreamEngine; + + return streamEngine->ReleaseHardware(); +} + +PAGED_CODE_SEG +NTSTATUS +Codec_EvtStreamRun( + _In_ ACXSTREAM Stream + ) +{ + PCODEC_STREAM_CONTEXT ctx; + CStreamEngine * streamEngine = NULL; + + PAGED_CODE(); + + ctx = GetCodecStreamContext(Stream); + + streamEngine = (CStreamEngine*)ctx->StreamEngine; + + return streamEngine->Run(); +} + + +PAGED_CODE_SEG +NTSTATUS +Codec_EvtStreamPause( + _In_ ACXSTREAM Stream + ) +{ + PCODEC_STREAM_CONTEXT ctx; + CStreamEngine * streamEngine = NULL; + + PAGED_CODE(); + + ctx = GetCodecStreamContext(Stream); + + streamEngine = (CStreamEngine*)ctx->StreamEngine; + + return streamEngine->Pause(); +} + +PAGED_CODE_SEG +NTSTATUS +Codec_EvtStreamAssignDrmContentId( + _In_ ACXSTREAM Stream, + _In_ ULONG DrmContentId, + _In_ PACXDRMRIGHTS DrmRights + ) +{ + PCODEC_STREAM_CONTEXT ctx; + CStreamEngine * streamEngine = NULL; + + PAGED_CODE(); + + ctx = GetCodecStreamContext(Stream); + + streamEngine = (CStreamEngine*)ctx->StreamEngine; + + return streamEngine->AssignDrmContentId(DrmContentId, DrmRights); +} + + diff --git a/audio/Soundwire/Samples/SdcaVad/SdcaVCodec/driver.cpp b/audio/Soundwire/Samples/SdcaVad/SdcaVCodec/driver.cpp new file mode 100644 index 000000000..4c9f937e9 --- /dev/null +++ b/audio/Soundwire/Samples/SdcaVad/SdcaVCodec/driver.cpp @@ -0,0 +1,218 @@ +/*++ + +Copyright (c) Microsoft Corporation. All rights reserved. + +Module Name: + + Driver.cpp + +Abstract: + + Sample soundwire Codec driver + +Environment: + + Kernel mode only + +--*/ + +#include "private.h" + +#ifndef __INTELLISENSE__ +#include "driver.tmh" +#endif + +RECORDER_LOG g_SDCAVCodecLog{ nullptr }; + +INIT_CODE_SEG +void +Test_ClientVersionHigherThanFramework() +{ + PAGED_CODE(); + + // example on how to check if a function is available. + /* + if (ACX_IS_FUNCTION_AVAILABLE(AcxCircuitCreate)) { + DbgPrint("Available: AcxCircuitCreate\n"); + } + else + { + DbgPrint("Not available: AcxCircuitCreate\n"); + ASSERT(FALSE); + } + */ + + if (ACX_IS_FIELD_AVAILABLE(ACX_DEVICEINIT_CONFIG, SynchronizationScope)) { + ACX_DEVICEINIT_CONFIG config; + ACX_DEVICEINIT_CONFIG_INIT(&config); + DbgPrint("Available: ACX_DEVICEINIT_CONFIG.SynchronizationScope\n"); + } + else + { + DbgPrint("Not available: ACX_DEVICEINIT_CONFIG.SynchronizationScope\n"); + ASSERT(FALSE); + } +} + +PAGED_CODE_SEG +VOID Codec_DriverUnload(_In_ WDFDRIVER Driver) +{ + PAGED_CODE(); + + if (!Driver) + { + ASSERT(FALSE); + return; + } + + WPP_CLEANUP(WdfDriverWdmGetDriverObject(Driver)); + + if (g_RegistryPath.Buffer != NULL) + { + ExFreePool(g_RegistryPath.Buffer); + RtlZeroMemory(&g_RegistryPath, sizeof(g_RegistryPath)); + } + + return; +} + +INIT_CODE_SEG +NTSTATUS +DriverEntry( + _In_ PDRIVER_OBJECT DriverObject, + _In_ PUNICODE_STRING RegistryPath + ) +/*++ + +Routine Description: + DriverEntry initializes the driver and is the first routine called by the + system after the driver is loaded. + +Parameters Description: + + DriverObject - represents the instance of the function driver that is loaded + into memory. DriverEntry must initialize members of DriverObject before it + returns to the caller. DriverObject is allocated by the system before the + driver is loaded, and it is released by the system after the system unloads + the function driver from memory. + + RegistryPath - represents the driver specific path in the Registry. + The function driver can use the path to store driver related data between + reboots. The path does not store hardware instance specific data. + +Return Value: + + STATUS_SUCCESS if successful, + STATUS_UNSUCCESSFUL otherwise. + +--*/ +{ + PAGED_CODE(); + + NTSTATUS status = STATUS_SUCCESS; + + WPP_INIT_TRACING(DriverObject, RegistryPath); + + auto exit = scope_exit([&status, &DriverObject]() { + if (!NT_SUCCESS(status)) + { + if (g_RegistryPath.Buffer != NULL) + { + ExFreePool(g_RegistryPath.Buffer); + RtlZeroMemory(&g_RegistryPath, sizeof(g_RegistryPath)); + } + + WPP_CLEANUP(DriverObject); + } + else + { + DrvLogInfo(g_SDCAVCodecLog, FLAG_INIT, "ACX SDCA Virtual Codec Driver Init complete, %!STATUS!", status); + } + }); + + RETURN_NTSTATUS_IF_FAILED(CopyRegistrySettingsPath(RegistryPath)); + + // + // Initiialize driver config to control the attributes that + // are global to the driver. Note that framework by default + // provides a driver unload routine. If you create any resources + // in the DriverEntry and want to be cleaned in driver unload, + // you can override that by manually setting the EvtDriverUnload in the + // config structure. In general xxx_CONFIG_INIT macros are provided to + // initialize most commonly used members. + // + + WDF_DRIVER_CONFIG wdfCfg; + WDF_DRIVER_CONFIG_INIT(&wdfCfg, Codec_EvtBusDeviceAdd); + wdfCfg.EvtDriverUnload = Codec_DriverUnload; + + // + // Add a driver context. (for illustration purposes only). + // + WDF_OBJECT_ATTRIBUTES attributes; + WDF_OBJECT_ATTRIBUTES_INIT_CONTEXT_TYPE(&attributes, CODEC_DRIVER_CONTEXT); + + // + // Create a framework driver object to represent our driver. + // + WDFDRIVER driver; + RETURN_NTSTATUS_IF_FAILED(WdfDriverCreate( + DriverObject, + RegistryPath, + &attributes, // Driver Attributes + &wdfCfg, // Driver Config Info + &driver // hDriver + )); + + RECORDER_CONFIGURE_PARAMS recorderConfig; + RECORDER_CONFIGURE_PARAMS_INIT(&recorderConfig); + recorderConfig.CreateDefaultLog = FALSE; + WppRecorderConfigure(&recorderConfig); + + RECORDER_LOG_CREATE_PARAMS recorderLogCreateParams; + RECORDER_LOG_CREATE_PARAMS_INIT(&recorderLogCreateParams, NULL); + recorderLogCreateParams.TotalBufferSize = WPP_TOTAL_BUFFER_SIZE; + recorderLogCreateParams.ErrorPartitionSize = WPP_ERROR_PARTITION_SIZE; + + RtlStringCbPrintfA(recorderLogCreateParams.LogIdentifier, + RECORDER_LOG_IDENTIFIER_MAX_CHARS, + "SDCAVCodec"); + + RECORDER_LOG logHandle = NULL; + status = WppRecorderLogCreate(&recorderLogCreateParams, &logHandle); + if (!NT_SUCCESS(status)) + { + logHandle = NULL; + + // Non fatal failure + status = STATUS_SUCCESS; + } + + g_SDCAVCodecLog = logHandle; + + // + // Post init. + // + ACX_DRIVER_CONFIG acxCfg; + ACX_DRIVER_CONFIG_INIT(&acxCfg); + + RETURN_NTSTATUS_IF_FAILED(AcxDriverInitialize(driver, &acxCfg)); + + // + // Test ACX bindings. + // + ACX_DRIVER_VERSION_AVAILABLE_PARAMS ver; + ACX_DRIVER_VERSION_AVAILABLE_PARAMS_INIT(&ver, 1, 0); + if (!AcxDriverIsVersionAvailable(driver, &ver)) { + status = STATUS_DRIVER_INTERNAL_ERROR; + DbgPrint("Unexpected ACX library version.\n"); + ASSERT(FALSE); + } + RETURN_NTSTATUS_IF_FAILED(status); + + Test_ClientVersionHigherThanFramework(); + + return status; +} + + diff --git a/audio/Soundwire/Samples/SdcaVad/SdcaVCodec/private.h b/audio/Soundwire/Samples/SdcaVad/SdcaVCodec/private.h new file mode 100644 index 000000000..602133bae --- /dev/null +++ b/audio/Soundwire/Samples/SdcaVad/SdcaVCodec/private.h @@ -0,0 +1,522 @@ +/*++ + +Copyright (c) Microsoft Corporation. All rights reserved. + + THIS CODE AND INFORMATION IS PROVIDED "AS IS" WITHOUT WARRANTY OF ANY + KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE + IMPLIED WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A PARTICULAR + PURPOSE. + +Module Name: + + private.h + +Abstract: + + Contains structure definitions and function prototypes private to + the driver. + +Environment: + + Kernel mode + +--*/ + +#ifndef _PRIVATE_H_ +#define _PRIVATE_H_ + +#include "cpp_utils.h" + +#include "NewDelete.h" + +/* make prototypes usable from C++ */ +#ifdef __cplusplus +extern "C" { +#endif + +#pragma warning(disable:4200) // +#pragma warning(disable:4201) // nameless struct/union +#pragma warning(disable:4214) // bit field types other than int + +#include +#include +#include +#include + +#pragma warning(default:4200) +#pragma warning(default:4201) +#pragma warning(default:4214) + +#include +#include + +#include "SoundWireController.h" +#include "SdcaXu.h" + +#include "trace.h" + +#include + +#define PAGED_CODE_SEG __declspec(code_seg("PAGE")) +#define INIT_CODE_SEG __declspec(code_seg("INIT")) + +extern RECORDER_LOG g_SDCAVCodecLog; + +// Simple ACX driver +#define DRIVER_TAG (ULONG) 'Ccds' + +// Number of millisecs per sec. +#define MS_PER_SEC 1000 + +// Number of hundred nanosecs per sec. +#define HNS_PER_SEC 10000000 + +// Compatible ID for render/capture +#define ACX_CODEC_TEST_COMPATIBLE_ID L"{99a0ee05-7167-4b63-843d-19d6d285942e}" + +// Container ID for render/capture +#define ACX_CODEC_TEST_CONTAINER_ID L"{00000000-0000-0000-ffff-ffffffffffff}" + +#define RENDER_CIRCUIT_URI L"test:obj-path:\\SDCAVCODEC\\RENDER" +#define EXT_RENDER_CIRCUIT_URI L"test:obj-path:\\SDCAVCODEC\\RENDER_xu" +#define CAPTURE_CIRCUIT_URI L"test:obj-path:\\SDCAVCODEC\\CAPTURE" +#define EXT_CAPTURE_CIRCUIT_URI L"test:obj-path:\\SDCAVCODEC\\CAPTURE_xu" + +#undef MIN +#undef MAX +#define MIN(a,b) ((a) > (b) ? (b) : (a)) +#define MAX(a,b) ((a) > (b) ? (a) : (b)) + +#ifndef BOOL +typedef int BOOL; +#endif + +#ifndef SIZEOF_ARRAY +#define SIZEOF_ARRAY(ar) (sizeof(ar)/sizeof((ar)[0])) +#endif // !defined(SIZEOF_ARRAY) + +#ifndef RGB +#define RGB(r, g, b) (DWORD)(r << 16 | g << 8 | b) +#endif + + +// +// Example Acpi blob for Hardware configuration +// +typedef struct _SdcaXuAcpiBlob +{ + // Number of endpoints + ULONG NumEndpoints; + +}SdcaXuAcpiBlob, *PSdcaXuAcpiBlob; + +#define ALL_CHANNELS_ID UINT32_MAX +#define MAX_CHANNELS 2 + +// +// Ks support. +// +#define KSPROPERTY_TYPE_ALL KSPROPERTY_TYPE_BASICSUPPORT | \ + KSPROPERTY_TYPE_GET | \ + KSPROPERTY_TYPE_SET + +// +// Define CODEC driver context. +// +typedef struct _CODEC_DRIVER_CONTEXT { + ULONG reserved; +} CODEC_DRIVER_CONTEXT, *PCODEC_DRIVER_CONTEXT; + +WDF_DECLARE_CONTEXT_TYPE_WITH_NAME(CODEC_DRIVER_CONTEXT, GetCodecDriverContext) + +// +// Extension Unit specific data +// +typedef struct _SDCAXU_DATA +{ + BOOLEAN bSdcaXu; + BOOLEAN bExtensionJackOVerride; + SDCAXU_INTERFACE_V0101 ExtensionInterface; + ULONG numXUEntities; + ULONG *XUEntities; + PSDCAXU_INTERRUPT_INFO InterruptInfo; +}SDCAXU_DATA, *PSDCAXU_DATA; + +// +// Define CODEC device context. +// +typedef struct _CODEC_DEVICE_CONTEXT { + ACXCIRCUIT Render; + ACXCIRCUIT Capture; + ACXCOMPOSITETEMPLATE Composite[2]; + ULONG refComposite[2]; + SDCAXU_DATA SdcaXuData; + +} CODEC_DEVICE_CONTEXT, *PCODEC_DEVICE_CONTEXT; + +WDF_DECLARE_CONTEXT_TYPE_WITH_NAME(CODEC_DEVICE_CONTEXT, GetCodecDeviceContext) + +// +// Define RENDER device context. +// +typedef struct _CODEC_RENDER_DEVICE_CONTEXT { + ACXCIRCUIT Circuit; + BOOLEAN FirstTimePrepareHardware; +} CODEC_RENDER_DEVICE_CONTEXT, *PCODEC_RENDER_DEVICE_CONTEXT; + +WDF_DECLARE_CONTEXT_TYPE_WITH_NAME(CODEC_RENDER_DEVICE_CONTEXT, GetRenderDeviceContext) + +// +// Define RENDER circuit context. +// +typedef struct _CODEC_RENDER_CIRCUIT_CONTEXT { + ACXMUTE MuteElement; + ACXVOLUME VolumeElement; +} CODEC_RENDER_CIRCUIT_CONTEXT, *PCODEC_RENDER_CIRCUIT_CONTEXT; + +WDF_DECLARE_CONTEXT_TYPE_WITH_NAME(CODEC_RENDER_CIRCUIT_CONTEXT, GetRenderCircuitContext) + +// +// Define CAPTURE device context. +// +typedef struct _CODEC_CAPTURE_DEVICE_CONTEXT { + ACXCIRCUIT Circuit; + BOOLEAN FirstTimePrepareHardware; +} CODEC_CAPTURE_DEVICE_CONTEXT, *PCODEC_CAPTURE_DEVICE_CONTEXT; + +WDF_DECLARE_CONTEXT_TYPE_WITH_NAME(CODEC_CAPTURE_DEVICE_CONTEXT, GetCaptureDeviceContext) + +// +// Define CAPTURE circuit context. +// +typedef struct _CODEC_CAPTURE_CIRCUIT_CONTEXT { + BOOLEAN KwsActiveVadStream; + KEVENT KwsSuspendEvent; + KEVENT KwsResumeEvent; + ACXDATAFORMAT KwsDataFormat; +} CODEC_CAPTURE_CIRCUIT_CONTEXT, *PCODEC_CAPTURE_CIRCUIT_CONTEXT; + +WDF_DECLARE_CONTEXT_TYPE_WITH_NAME(CODEC_CAPTURE_CIRCUIT_CONTEXT, GetCaptureCircuitContext) + +// +// Define CODEC render/capture stream context. +// +typedef struct _CODEC_STREAM_CONTEXT { + PVOID StreamEngine; +} CODEC_STREAM_CONTEXT, *PCODEC_STREAM_CONTEXT; + +WDF_DECLARE_CONTEXT_TYPE_WITH_NAME(CODEC_STREAM_CONTEXT, GetCodecStreamContext) + +// +// Define CODEC circuit/stream element context. +// +typedef struct _CODEC_ELEMENT_CONTEXT { + BOOLEAN Dummy; +} CODEC_ELEMENT_CONTEXT, *PCODEC_ELEMENT_CONTEXT; + +WDF_DECLARE_CONTEXT_TYPE_WITH_NAME(CODEC_ELEMENT_CONTEXT, GetCodecElementContext) + +// +// Define CODEC circuit/stream element context. +// +typedef struct _CODEC_MUTE_ELEMENT_CONTEXT { + BOOL MuteState[MAX_CHANNELS]; + WDFTIMER Timer; // for testing only. +} CODEC_MUTE_ELEMENT_CONTEXT, *PCODEC_MUTE_ELEMENT_CONTEXT; + +WDF_DECLARE_CONTEXT_TYPE_WITH_NAME(CODEC_MUTE_ELEMENT_CONTEXT, GetCodecMuteElementContext) + +// +// Define CODEC mute timer context. +// +typedef struct _CODEC_MUTE_TIMER_CONTEXT { + ACXMUTE MuteElement; +} CODEC_MUTE_TIMER_CONTEXT, *PCODEC_MUTE_TIMER_CONTEXT; + +WDF_DECLARE_CONTEXT_TYPE_WITH_NAME(CODEC_MUTE_TIMER_CONTEXT, GetCodecMuteTimerContext) + +// +// Define CODEC circuit/stream element context. +// +typedef struct _CODEC_VOLUME_ELEMENT_CONTEXT { + LONG VolumeLevel[MAX_CHANNELS]; + WDFTIMER Timer; // for testing only. +} CODEC_VOLUME_ELEMENT_CONTEXT, *PCODEC_VOLUME_ELEMENT_CONTEXT; + +WDF_DECLARE_CONTEXT_TYPE_WITH_NAME(CODEC_VOLUME_ELEMENT_CONTEXT, GetCodecVolumeElementContext) + + +#define VOLUME_STEPPING 0x8000 +#define VOLUME_LEVEL_MAXIMUM 0x00000000 +#define VOLUME_LEVEL_MINIMUM (-96 * 0x10000) + +// +// Define CODEC mute timer context. +// +typedef struct _CODEC_VOLUME_TIMER_CONTEXT { + ACXVOLUME VolumeElement; +} CODEC_VOLUME_TIMER_CONTEXT, *PCODEC_VOLUME_TIMER_CONTEXT; + +WDF_DECLARE_CONTEXT_TYPE_WITH_NAME(CODEC_VOLUME_TIMER_CONTEXT, GetCodecVolumeTimerContext) + + +// +// Define CODEC format context. +// +typedef struct _CODEC_FORMAT_CONTEXT { + BOOLEAN Dummy; +} CODEC_FORMAT_CONTEXT, *PCODEC_FORMAT_CONTEXT; + +WDF_DECLARE_CONTEXT_TYPE_WITH_NAME(CODEC_FORMAT_CONTEXT, GetCodecFormatContext) + +typedef struct _CODEC_PIN_CONTEXT { + BOOLEAN Dummy; +} CODEC_PIN_CONTEXT, *PCODEC_PIN_CONTEXT; + +WDF_DECLARE_CONTEXT_TYPE_WITH_NAME(CODEC_PIN_CONTEXT, GetCodecPinContext) + +typedef struct _CODEC_JACK_CONTEXT +{ + ULONG Dummy; +} CODEC_JACK_CONTEXT, *PCODEC_JACK_CONTEXT; + +WDF_DECLARE_CONTEXT_TYPE_WITH_NAME(CODEC_JACK_CONTEXT, GetCodecJackContext) + +typedef enum { + CompositeType_RENDER, + CompositeType_CAPTURE +}CompositeType; + +// +// Driver prototypes. +// +DRIVER_INITIALIZE DriverEntry; +EVT_WDF_DRIVER_UNLOAD Codec_DriverUnload; +EVT_WDF_DRIVER_DEVICE_ADD Codec_EvtBusDeviceAdd; + +// Device callbacks. + +EVT_WDF_DEVICE_PREPARE_HARDWARE Codec_EvtDevicePrepareHardware; +EVT_WDF_DEVICE_RELEASE_HARDWARE Codec_EvtDeviceReleaseHardware; +EVT_WDF_DEVICE_CONTEXT_CLEANUP Codec_EvtDeviceContextCleanup; + +// Stream callbacks shared between Capture and Render + +EVT_WDF_OBJECT_CONTEXT_DESTROY Codec_EvtStreamDestroy; +EVT_ACX_STREAM_GET_HW_LATENCY Codec_EvtStreamGetHwLatency; +EVT_ACX_STREAM_PREPARE_HARDWARE Codec_EvtStreamPrepareHardware; +EVT_ACX_STREAM_RELEASE_HARDWARE Codec_EvtStreamReleaseHardware; +EVT_ACX_STREAM_RUN Codec_EvtStreamRun; +EVT_ACX_STREAM_PAUSE Codec_EvtStreamPause; +EVT_ACX_STREAM_ASSIGN_DRM_CONTENT_ID Codec_EvtStreamAssignDrmContentId; + +// Render callbacks. + +EVT_ACX_OBJECT_PREPROCESS_REQUEST CodecR_EvtCircuitRequestPreprocess; +EVT_ACX_CIRCUIT_CREATE_STREAM CodecR_EvtCircuitCreateStream; +EVT_ACX_CIRCUIT_POWER_UP CodecR_EvtCircuitPowerUp; +EVT_ACX_CIRCUIT_POWER_DOWN CodecR_EvtCircuitPowerDown; +EVT_ACX_STREAM_SET_RENDER_PACKET CodecR_EvtStreamSetRenderPacket; +EVT_ACX_PIN_SET_DATAFORMAT CodecR_EvtAcxPinSetDataFormat; +EVT_WDF_DEVICE_CONTEXT_CLEANUP CodecR_EvtPinContextCleanup; + +EVT_ACX_CIRCUIT_COMPOSITE_CIRCUIT_INITIALIZE CodecR_EvtCircuitCompositeCircuitInitialize; +EVT_ACX_CIRCUIT_COMPOSITE_INITIALIZE CodecR_EvtCircuitCompositeInitialize; + +// Capture callbacks. + +EVT_ACX_OBJECT_PREPROCESS_REQUEST CodecC_EvtCircuitRequestPreprocess; +EVT_ACX_CIRCUIT_CREATE_STREAM CodecC_EvtCircuitCreateStream; +EVT_ACX_CIRCUIT_POWER_UP CodecC_EvtCircuitPowerUp; +EVT_ACX_CIRCUIT_POWER_DOWN CodecC_EvtCircuitPowerDown; +EVT_ACX_STREAM_GET_CAPTURE_PACKET CodecC_EvtStreamGetCapturePacket; +EVT_ACX_PIN_SET_DATAFORMAT CodecC_EvtAcxPinSetDataFormat; +EVT_WDF_DEVICE_CONTEXT_CLEANUP CodecC_EvtPinContextCleanup; + +EVT_ACX_OBJECT_PREPROCESS_REQUEST CodecC_EvtStreamRequestPreprocess; + +EVT_ACX_OBJECT_PROCESS_REQUEST CodecC_EvtCircuitDeviceKwsCapability; +EVT_ACX_OBJECT_PROCESS_REQUEST CodecC_EvtCircuitVadCapability; +EVT_ACX_OBJECT_PROCESS_REQUEST CodecC_EvtCircuitVadEntities; +EVT_ACX_OBJECT_PROCESS_REQUEST CodecC_EvtCircuitSetKwsAccessEvents; +EVT_ACX_OBJECT_PROCESS_REQUEST CodecC_EvtCircuitConfigureVadPort; +EVT_ACX_OBJECT_PROCESS_REQUEST CodecC_EvtCircuitCleanupVadPort; + +EVT_ACX_MUTE_ASSIGN_STATE CodecR_EvtMuteAssignStateCallback; +EVT_ACX_MUTE_RETRIEVE_STATE CodecR_EvtMuteRetrieveStateCallback; +EVT_WDF_TIMER CodecR_EvtMuteTimerFunc; +EVT_ACX_VOLUME_ASSIGN_LEVEL CodecR_EvtVolumeAssignLevelCallback; +EVT_ACX_VOLUME_RETRIEVE_LEVEL CodecR_EvtVolumeRetrieveLevelCallback; +EVT_WDF_TIMER CodecR_EvtVolumeTimerFunc; +EVT_ACX_OBJECT_PREPROCESS_REQUEST CodecR_EvtStreamRequestPreprocess; + +/* make internal prototypes usable from C++ */ +#ifdef __cplusplus +} +#endif + +// +// Used to store the registry settings path for the driver +// +extern UNICODE_STRING g_RegistryPath; + +__drv_requiresIRQL(PASSIVE_LEVEL) +PAGED_CODE_SEG +NTSTATUS +CopyRegistrySettingsPath( + _In_ PUNICODE_STRING RegistryPath + ); + +__drv_requiresIRQL(PASSIVE_LEVEL) +PAGED_CODE_SEG +NTSTATUS +Codec_AddComposites(_In_ WDFDEVICE Device, _In_ CompositeType compositeType); + +__drv_requiresIRQL(PASSIVE_LEVEL) +PAGED_CODE_SEG +NTSTATUS +Codec_AddRenderComposites(_In_ WDFDEVICE Device); + +__drv_requiresIRQL(PASSIVE_LEVEL) +PAGED_CODE_SEG +NTSTATUS +Codec_AddCaptureComposites(_In_ WDFDEVICE Device); + +#pragma code_seg() +NTSTATUS +Codec_RemoveComposites(_In_ WDFDEVICE Device); + +PAGED_CODE_SEG +NTSTATUS +Codec_SetPowerPolicy( + _In_ WDFDEVICE Device + ); + +PAGED_CODE_SEG +NTSTATUS +CodecR_AddRenders( + _In_ WDFDRIVER Driver, + _In_ WDFDEVICE Device + ); + +PAGED_CODE_SEG +NTSTATUS +CodecR_AddStaticRender( + _In_ WDFDEVICE Device + ); + +PAGED_CODE_SEG +NTSTATUS +CodecR_CreateRenderCircuit( + _In_ WDFDEVICE Device, + _Out_ ACXCIRCUIT * Circuit + ); + +PAGED_CODE_SEG +NTSTATUS +CodecC_AddCaptures( + _In_ WDFDRIVER Driver, + _In_ WDFDEVICE Device + ); + +PAGED_CODE_SEG +NTSTATUS +CodecC_AddStaticCapture( + _In_ WDFDEVICE Device + ); + +PAGED_CODE_SEG +NTSTATUS +CodecC_CreateCaptureCircuit( + _In_ WDFDEVICE Device, + _Out_ ACXCIRCUIT * Circuit + ); + +// +// Extension Unit +// +PAGED_CODE_SEG +NTSTATUS Codec_SdcaXuSetJackOverride +( + _In_ PVOID Context, // SDCA Context + _In_ BOOLEAN Override // TRUE: Override + // FALSE: Default SDCA behavior +); + +PAGED_CODE_SEG +NTSTATUS Codec_SdcaXuSetJackSelectedMode +( + _In_ PVOID Context, // SDCA Context + _In_ ULONG GroupEntityId, // SDCA Group Entity ID for Jack(s) + _In_ ULONG SelectedMode // Type of jack type overriden by XU +); + +#pragma code_seg() +NTSTATUS Codec_SdcaXuPDEPowerReferenceAcquire +( + _In_ PVOID Context, + _In_ ULONG PowerDomainEntityId, + _In_ SDCAXU_POWER_STATE RequiredState +); + +#pragma code_seg() +NTSTATUS Codec_SdcaXuPDEPowerReferenceRelease +( + _In_ PVOID Context, + _In_ ULONG PowerDomainEntityId, + _In_ SDCAXU_POWER_STATE ReleasedState +); + +#pragma code_seg() +NTSTATUS Codec_SdcaXuReadDeferredAudioControls +( + _In_ PVOID Context, + _Inout_ PSDCA_AUDIO_CONTROLS Controls +); + +#pragma code_seg() +NTSTATUS Codec_SdcaXuWriteDeferredAudioControls +( + _In_ PVOID Context, + _Inout_ PSDCA_AUDIO_CONTROLS Controls +); + +PAGED_CODE_SEG +NTSTATUS Codec_SdcaXuSetXUEntities +( + _In_ PVOID Context, + _In_ ULONG NumEntities, + _In_reads_(NumEntities) + ULONG EntityIDs[] +); + +PAGED_CODE_SEG +NTSTATUS Codec_SdcaXuRegisterForInterrupts +( + _In_ PVOID Context, + _In_ PSDCAXU_INTERRUPT_INFO InterruptInfo +); + +PAGED_CODE_SEG +NTSTATUS Codec_SdcaXuSetRenderEndpointConfig +( + _In_ WDFDEVICE Device, + _In_ ACXCIRCUIT Circuit +); + +PAGED_CODE_SEG +NTSTATUS Codec_SdcaXuSetCaptureEndpointConfig +( + _In_ WDFDEVICE Device, + _In_ ACXCIRCUIT Circuit +); + +PAGED_CODE_SEG +NTSTATUS Codec_GetSdcaXu(_In_ WDFDEVICE Device); + +PAGED_CODE_SEG +NTSTATUS Codec_SetSdcaXuHwConfig(_In_ WDFDEVICE Device); + +#pragma code_seg() + +#endif // _PRIVATE_H_ diff --git a/audio/Soundwire/Samples/SdcaVad/SdcaVCodec/render.cpp b/audio/Soundwire/Samples/SdcaVad/SdcaVCodec/render.cpp new file mode 100644 index 000000000..901398dc4 --- /dev/null +++ b/audio/Soundwire/Samples/SdcaVad/SdcaVCodec/render.cpp @@ -0,0 +1,1310 @@ +/*++ + + THIS CODE AND INFORMATION IS PROVIDED "AS IS" WITHOUT WARRANTY OF ANY + KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE + IMPLIED WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A PARTICULAR + PURPOSE. + +Module Name: + + Render.cpp + +Abstract: + + Contains ACX Capture factory and circuit + +Environment: + + Kernel mode + +--*/ + +#include "private.h" +#include +#include "stdunk.h" +#include +#include +#include +#include "streamengine.h" +#include "soundwirecontroller.h" +#include "sdcastreaming.h" +#include "CircuitHelper.h" + +#include "AudioFormats.h" + +#ifndef __INTELLISENSE__ +#include "render.tmh" +#endif + +//#define CODEC_NEXT_CIRCUIT_STR L"\\??\\ROOT#AcxAmpTestDriver#0000#{2c6bb644-e1ae-47f8-9a2b-1d1fa750f2fa}\\Speaker0" +//#define CODEC_NEXT_CIRCUIT_STR L"\\??\\ROOT#AcxAmpTestDriver#0000#{6994AD04-93EF-11D0-A3CC-00A0C9223196}\\Speaker0" + +//#define CODEC_PREVIOUS_CIRCUIT_STR L"\\??\\AcxDspTestDriver#DynamicEnumSpeaker0#1&6244bc4&d&00#{2c6bb644-e1ae-47f8-9a2b-1d1fa750f2fa}\\Speaker0" +//#define CODEC_PREVIOUS_CIRCUIT_STR L"\\??\\AcxDspTestDriver#DynamicEnumSpeaker0#1&6244bc4&0&00#{6994AD04-93EF-11D0-A3CC-00A0C9223196}\\Speaker0" + +PAGED_CODE_SEG +VOID +CodecR_EvtPinCInstancesCallback( + _In_ WDFOBJECT Object, + _In_ WDFREQUEST Request + ) +{ + PAGED_CODE(); + + // TEMP: for testing only. + UNREFERENCED_PARAMETER(Object); + WdfRequestComplete(Request, STATUS_UNSUCCESSFUL); +} + +PAGED_CODE_SEG +VOID +CodecR_EvtPinCTypesCallback( + _In_ WDFOBJECT Object, + _In_ WDFREQUEST Request + ) +{ + PAGED_CODE(); + + // TEMP: for testing only. + UNREFERENCED_PARAMETER(Object); + WdfRequestComplete(Request, STATUS_UNSUCCESSFUL); +} + +PAGED_CODE_SEG +VOID +CodecR_EvtPinDataFlowCallback( + _In_ WDFOBJECT Object, + _In_ WDFREQUEST Request + ) +{ + PAGED_CODE(); + + // TEMP: for testing only. + UNREFERENCED_PARAMETER(Object); + WdfRequestComplete(Request, STATUS_UNSUCCESSFUL); +} + +PAGED_CODE_SEG +VOID +CodecR_EvtPinDataRangesCallback( + _In_ WDFOBJECT Object, + _In_ WDFREQUEST Request + ) +{ + PAGED_CODE(); + + // TEMP: for testing only. + UNREFERENCED_PARAMETER(Object); + WdfRequestComplete(Request, STATUS_UNSUCCESSFUL); +} + +PAGED_CODE_SEG +VOID +CodecR_EvtPinDataIntersectionCallback( + _In_ WDFOBJECT Object, + _In_ WDFREQUEST Request + ) +{ + PAGED_CODE(); + + // TEMP: for testing only. + UNREFERENCED_PARAMETER(Object); + WdfRequestComplete(Request, STATUS_UNSUCCESSFUL); +} + +PAGED_CODE_SEG +NTSTATUS +CodecR_EvtAcxPinSetDataFormat ( + _In_ ACXPIN Pin, + _In_ ACXDATAFORMAT DataFormat + ) +{ + PAGED_CODE(); + + UNREFERENCED_PARAMETER(Pin); + UNREFERENCED_PARAMETER(DataFormat); + + + return STATUS_NOT_SUPPORTED; +} + +PAGED_CODE_SEG +NTSTATUS +CodecR_EvtMuteAssignStateCallback( + _In_ ACXMUTE Mute, + _In_ ULONG Channel, + _In_ ULONG State + ) +{ + PAGED_CODE(); + + ASSERT(Mute); + PCODEC_MUTE_ELEMENT_CONTEXT muteCtx = GetCodecMuteElementContext(Mute); + ASSERT(muteCtx); + + if (Channel != ALL_CHANNELS_ID) + { + muteCtx->MuteState[Channel] = State; + } + else + { + for (ULONG i = 0; i < MAX_CHANNELS; ++i) + { + muteCtx->MuteState[i] = State; + } + } + + return STATUS_SUCCESS; +} + +PAGED_CODE_SEG +NTSTATUS +NTAPI +CodecR_EvtMuteRetrieveStateCallback( + _In_ ACXMUTE Mute, + _In_ ULONG Channel, + _Out_ ULONG *State + ) +{ + PAGED_CODE(); + + ASSERT(Mute); + PCODEC_MUTE_ELEMENT_CONTEXT muteCtx = GetCodecMuteElementContext(Mute); + ASSERT(muteCtx); + + if (Channel == ALL_CHANNELS_ID) + { + Channel = 0; + } + + *State = muteCtx->MuteState[Channel]; + + return STATUS_SUCCESS; +} + +// +// Testing mute element. +// +#pragma code_seg() +VOID +CodecR_EvtMuteTimerFunc( + _In_ WDFTIMER Timer + ) +{ + PCODEC_MUTE_TIMER_CONTEXT timerCtx = GetCodecMuteTimerContext(Timer); + + ASSERT(timerCtx != NULL); + ASSERT(timerCtx->MuteElement != NULL); + + PCODEC_MUTE_ELEMENT_CONTEXT muteCtx = GetCodecMuteElementContext(timerCtx->MuteElement); + ASSERT(muteCtx != NULL); + + // update settings 0 <-> 1 + for (ULONG i = 0; i < MAX_CHANNELS; ++i) + { + muteCtx->MuteState[i] = !muteCtx->MuteState[i]; + } + + AcxMuteChangeStateNotification(timerCtx->MuteElement); +} + +PAGED_CODE_SEG +NTSTATUS +CodecR_EvtVolumeAssignLevelCallback( + _In_ ACXVOLUME Volume, + _In_ ULONG Channel, + _In_ LONG VolumeLevel + ) +{ + PAGED_CODE(); + + ASSERT(Volume); + PCODEC_VOLUME_ELEMENT_CONTEXT volumeCtx = GetCodecVolumeElementContext(Volume); + ASSERT(volumeCtx); + + if (Channel != ALL_CHANNELS_ID) + { + volumeCtx->VolumeLevel[Channel] = VolumeLevel; + } + else + { + for (ULONG i = 0; i < MAX_CHANNELS; ++i) + { + volumeCtx->VolumeLevel[i] = VolumeLevel; + } + } + + return STATUS_SUCCESS; +} + +PAGED_CODE_SEG +NTSTATUS +NTAPI +CodecR_EvtVolumeRetrieveLevelCallback( + _In_ ACXVOLUME Volume, + _In_ ULONG Channel, + _Out_ LONG *VolumeLevel + ) +{ + PAGED_CODE(); + + ASSERT(Volume); + PCODEC_VOLUME_ELEMENT_CONTEXT volumeCtx = GetCodecVolumeElementContext(Volume); + ASSERT(volumeCtx); + + if (Channel == ALL_CHANNELS_ID) + { + Channel = 0; + } + + *VolumeLevel = volumeCtx->VolumeLevel[Channel]; + + return STATUS_SUCCESS; +} + +// +// Testing volume element. +// +#pragma code_seg() +VOID +CodecR_EvtVolumeTimerFunc( + _In_ WDFTIMER Timer + ) +{ + PCODEC_VOLUME_TIMER_CONTEXT timerCtx = GetCodecVolumeTimerContext(Timer); + + ASSERT(timerCtx != NULL); + ASSERT(timerCtx->VolumeElement != NULL); + + PCODEC_VOLUME_ELEMENT_CONTEXT volumeCtx = GetCodecVolumeElementContext(timerCtx->VolumeElement); + ASSERT(volumeCtx != NULL); + + // Toggle volume between max and min + for (ULONG i = 0; i < MAX_CHANNELS; ++i) + { + volumeCtx->VolumeLevel[i] = volumeCtx->VolumeLevel[i] == VOLUME_LEVEL_MAXIMUM ? VOLUME_LEVEL_MINIMUM : VOLUME_LEVEL_MAXIMUM; + } + + AcxVolumeChangeLevelNotification(timerCtx->VolumeElement); +} + +#pragma code_seg() +VOID +CodecR_EvtPinContextCleanup( + _In_ WDFOBJECT WdfPin + ) +/*++ + +Routine Description: + + In this callback, it cleans up pin context. + +Arguments: + + WdfDevice - WDF device object + +Return Value: + + NULL + +--*/ +{ + UNREFERENCED_PARAMETER(WdfPin); +} + +PAGED_CODE_SEG +VOID +CodecR_EvtCircuitRequestPreprocess( + _In_ ACXOBJECT Object, + _In_ ACXCONTEXT DriverContext, + _In_ WDFREQUEST Request + ) +/*++ + +Routine Description: + + This function is an example of a preprocess routine. + +--*/ +{ + PAGED_CODE(); + + UNREFERENCED_PARAMETER(DriverContext); + + ASSERT(Object != NULL); + ASSERT(DriverContext); + ASSERT(Request); + + + // + // Just give the request back to ACX. + // + (VOID)AcxCircuitDispatchAcxRequest((ACXCIRCUIT)Object, Request); +} + +PAGED_CODE_SEG +VOID +CodecR_EvtStreamRequestPreprocess( + _In_ ACXOBJECT Object, + _In_ ACXCONTEXT DriverContext, + _In_ WDFREQUEST Request + ) +/*++ + +Routine Description: + + This function is an example of a preprocess routine. + +--*/ +{ + PAGED_CODE(); + + UNREFERENCED_PARAMETER(DriverContext); + + ASSERT(Object != NULL); + ASSERT(DriverContext); + ASSERT(Request); + + + // + // Just give the request back to ACX. + // + (VOID)AcxStreamDispatchAcxRequest((ACXSTREAM)Object, Request); +} + +PAGED_CODE_SEG +NTSTATUS +CodecR_AddRenders( + _In_ WDFDRIVER Driver, + _In_ WDFDEVICE Device + ) +{ + PAGED_CODE(); + + UNREFERENCED_PARAMETER(Driver); + + NTSTATUS status = STATUS_SUCCESS; + + // + // Add a static render device. + // + status = CodecR_AddStaticRender(Device); + + return status; +} + +PAGED_CODE_SEG +NTSTATUS +CodecR_AddStaticRender( + _In_ WDFDEVICE Device +) +{ + PAGED_CODE(); + + DrvLogEnter(g_SDCAVCodecLog); + + NTSTATUS status = STATUS_SUCCESS; + + PCODEC_DEVICE_CONTEXT devCtx; + devCtx = GetCodecDeviceContext(Device); + ASSERT(devCtx != NULL); + + // + // Alloc audio context to current device. + // + WDF_OBJECT_ATTRIBUTES attributes; + WDF_OBJECT_ATTRIBUTES_INIT_CONTEXT_TYPE(&attributes, CODEC_RENDER_DEVICE_CONTEXT); + PCODEC_RENDER_DEVICE_CONTEXT renderDevCtx; + RETURN_NTSTATUS_IF_FAILED(WdfObjectAllocateContext(Device, &attributes, (PVOID*)&renderDevCtx)); + ASSERT(renderDevCtx); + + // + // Create a render circuit associated with this device. + // + ACXCIRCUIT renderCircuit = NULL; + RETURN_NTSTATUS_IF_FAILED(CodecR_CreateRenderCircuit(Device, &renderCircuit)); + + RETURN_NTSTATUS_IF_FAILED(Codec_SdcaXuSetRenderEndpointConfig(Device, renderCircuit)); + + devCtx->Render = renderCircuit; + + return status; +} + +EXTERN_C const GUID DECLSPEC_SELECTANY CODEC_CIRCUIT_RENDER_GUID; +EXTERN_C const GUID DECLSPEC_SELECTANY EXTENSION_CIRCUIT_RENDER_GUID; +EXTERN_C const GUID DECLSPEC_SELECTANY SYSTEM_CONTAINER_GUID; + +PAGED_CODE_SEG +NTSTATUS Codec_SdcaXuSetRenderEndpointConfig +( + _In_ WDFDEVICE Device, + _In_ ACXCIRCUIT Circuit +) +{ + PAGED_CODE(); + + DrvLogEnter(g_SDCAVCodecLog); + + NTSTATUS status = STATUS_SUCCESS; + + PCODEC_DEVICE_CONTEXT devCtx; + devCtx = GetCodecDeviceContext(Device); + ASSERT(devCtx != NULL); + + DECLARE_CONST_UNICODE_STRING(circuitName, L"ExtensionSpeaker0"); + DECLARE_CONST_UNICODE_STRING(circuitUri, EXT_RENDER_CIRCUIT_URI); + +#pragma prefast(suppress:__WARNING_ALIASED_MEMORY_LEAK, "memory is freed by scope_exit") + PSDCAXU_ACX_CIRCUIT_CONFIG exCircuitConfig = (PSDCAXU_ACX_CIRCUIT_CONFIG)ExAllocatePool2( + POOL_FLAG_NON_PAGED, + sizeof(SDCAXU_ACX_CIRCUIT_CONFIG) + circuitName.MaximumLength, + DRIVER_TAG); + RETURN_NTSTATUS_IF_TRUE(NULL == exCircuitConfig, STATUS_INSUFFICIENT_RESOURCES); + auto exConfigFree = scope_exit([&exCircuitConfig]() { + ExFreePoolWithTag(exCircuitConfig, DRIVER_TAG); + }); + + // + // Provide circuit configuration to SDCA XU driver + // SDCA XU driver will generate circuits to match this configuration + // + if (devCtx->SdcaXuData.bSdcaXu) + { + exCircuitConfig->cbSize = sizeof(SDCAXU_ACX_CIRCUIT_CONFIG) + circuitName.MaximumLength; + + exCircuitConfig->CircuitName = circuitName; + exCircuitConfig->CircuitName.Buffer = (PWCH)(exCircuitConfig + 1); + RtlCopyMemory(exCircuitConfig->CircuitName.Buffer, circuitName.Buffer, circuitName.MaximumLength); + + exCircuitConfig->CircuitContext = Circuit; + exCircuitConfig->CircuitType = AcxCircuitTypeRender; + exCircuitConfig->ContainerID = SYSTEM_CONTAINER_GUID; + exCircuitConfig->ComponentID = EXTENSION_CIRCUIT_RENDER_GUID; + exCircuitConfig->ComponentUri = circuitUri; + + PSDCAXU_INTERFACE_V0101 exInterface = &devCtx->SdcaXuData.ExtensionInterface; + PVOID exContext = devCtx->SdcaXuData.ExtensionInterface.InterfaceHeader.Context; + + RETURN_NTSTATUS_IF_FAILED(exInterface->EvtSetEndpointConfig(exContext, SdcaXuEndpointConfigTypeAcxCircuitConfig, exCircuitConfig, exCircuitConfig->cbSize)); + } + + return status; +} + +// {3CE41646-9BF2-4A9E-B851-D711CAE9AEA8} +DEFINE_GUID(SDCAVADPropsetId, + 0x3ce41646, 0x9bf2, 0x4a9e, 0xb8, 0x51, 0xd7, 0x11, 0xca, 0xe9, 0xae, 0xa8); + +typedef enum { + SDCAVAD_PROPERTY_TEST1, + SDCAVAD_PROPERTY_TEST2, + SDCAVAD_PROPERTY_TEST3, + SDCAVAD_PROPERTY_TEST4, + SDCAVAD_PROPERTY_TEST5, + SDCAVAD_PROPERTY_TEST6, +} SDCAVAD_Properties; + +PAGED_CODE_SEG +NTSTATUS +CodecR_SDCAVADPropertyTest1( + _Inout_ PVOID pValue, + _In_ ULONG ValueCb, + _Out_ PULONG ValueCbOut +) +{ + PAGED_CODE(); + + UNREFERENCED_PARAMETER(pValue); + UNREFERENCED_PARAMETER(ValueCb); + + NTSTATUS status = STATUS_SUCCESS; + + DrvLogInfo(g_SDCAVCodecLog, FLAG_STREAM, L"SDCAVCodec: SDCAVAD_PROPERTY_TEST1"); + + *ValueCbOut = 0; + + return status; +} + +PAGED_CODE_SEG +NTSTATUS +CodecR_SDCAVADPropertyTest2( + _Inout_ PVOID pValue, + _In_ ULONG ValueCb, + _Out_ PULONG ValueCbOut +) +{ + PAGED_CODE(); + + UNREFERENCED_PARAMETER(ValueCb); + + NTSTATUS status = STATUS_SUCCESS; + + DrvLogInfo(g_SDCAVCodecLog, FLAG_STREAM, L"SDCAVCodec: SDCAVAD_PROPERTY_TEST2"); + + *((PULONG)pValue) = 10; + *ValueCbOut = sizeof(ULONG); + + return status; +} + +PAGED_CODE_SEG +NTSTATUS +CodecR_SDCAVADPropertyTest5( + _Inout_ PVOID pValue, + _In_ ULONG ValueCb, + _Out_ PULONG ValueCbOut +) +{ + PAGED_CODE(); + + UNREFERENCED_PARAMETER(pValue); + UNREFERENCED_PARAMETER(ValueCb); + + NTSTATUS status = STATUS_SUCCESS; + + DrvLogInfo(g_SDCAVCodecLog, FLAG_STREAM, L"SDCAVCodec: SDCAVAD_PROPERTY_TEST5"); + + *ValueCbOut = 0; + + return status; +} + +PAGED_CODE_SEG +NTSTATUS +CodecR_SDCAVADPropertyTest6( + _Inout_ PVOID pValue, + _In_ ULONG ValueCb, + _Out_ PULONG ValueCbOut +) +{ + PAGED_CODE(); + + UNREFERENCED_PARAMETER(ValueCb); + + NTSTATUS status = STATUS_SUCCESS; + + DrvLogInfo(g_SDCAVCodecLog, FLAG_STREAM, L"SDCAVCodec: SDCAVAD_PROPERTY_TEST6"); + + *((PULONG)pValue) = 12; + *ValueCbOut = sizeof(ULONG); + + return status; +} + +PAGED_CODE_SEG +VOID +CodecR_EvtPropertyCallback( + _In_ WDFOBJECT Object, + _In_ WDFREQUEST Request +) +{ + PAGED_CODE(); + + UNREFERENCED_PARAMETER(Object); + + ACX_REQUEST_PARAMETERS params; + ACX_REQUEST_PARAMETERS_INIT(¶ms); + + AcxRequestGetParameters(Request, ¶ms); + + NTSTATUS status = STATUS_SUCCESS; + PVOID Value = params.Parameters.Property.Value; + ULONG ValueCb = params.Parameters.Property.ValueCb; + ULONG ValueCbOut = 0; + + switch (params.Parameters.Property.Id) + { + case SDCAVAD_PROPERTY_TEST1: + status = CodecR_SDCAVADPropertyTest1(Value, ValueCb, &ValueCbOut); + break; + case SDCAVAD_PROPERTY_TEST2: + status = CodecR_SDCAVADPropertyTest2(Value, ValueCb, &ValueCbOut); + break; + case SDCAVAD_PROPERTY_TEST5: + status = CodecR_SDCAVADPropertyTest5(Value, ValueCb, &ValueCbOut); + break; + case SDCAVAD_PROPERTY_TEST6: + status = CodecR_SDCAVADPropertyTest6(Value, ValueCb, &ValueCbOut); + break; + default: + break; + } + + WdfRequestCompleteWithInformation(Request, status, ValueCbOut); +} + +PAGED_CODE_SEG +VOID +CodecR_EvtPropertyVendorSpecificCallback( + _In_ WDFOBJECT Object, + _In_ WDFREQUEST Request +) +{ + PAGED_CODE(); + + UNREFERENCED_PARAMETER(Object); + + ACX_REQUEST_PARAMETERS params; + ACX_REQUEST_PARAMETERS_INIT(¶ms); + + AcxRequestGetParameters(Request, ¶ms); + + NTSTATUS status = STATUS_SUCCESS; + + // The Class Driver will send IOCTL_SOUNDWIRE_VENDOR_SPECIFIC with Control/Value to the SoundWire Controller. + + PVIRTUAL_STACK_VENDOR_SPECIFIC_CONTROL control = (PVIRTUAL_STACK_VENDOR_SPECIFIC_CONTROL)params.Parameters.Property.Control; + ULONG controlCb = params.Parameters.Property.ControlCb; + + PVIRTUAL_STACK_VENDOR_SPECIFIC_VALUE_TEST_DATA value = (PVIRTUAL_STACK_VENDOR_SPECIFIC_VALUE_TEST_DATA)params.Parameters.Property.Value; + ULONG valueCb = params.Parameters.Property.ValueCb; + + ULONG_PTR information = 0; + + // Validate we have enough control data + if (controlCb < sizeof(VIRTUAL_STACK_VENDOR_SPECIFIC_CONTROL)) + { + status = STATUS_INVALID_PARAMETER; + } + else if (control->VendorSpecificSize != sizeof(VIRTUAL_STACK_VENDOR_SPECIFIC_CONTROL)) + { + status = STATUS_INVALID_PARAMETER; + } + else if (control->VendorSpecificId == VirtualStackVendorSpecificRequestGetTestData) + { + if (valueCb == 0 && value == nullptr) + { + status = STATUS_BUFFER_OVERFLOW; + information = sizeof(VIRTUAL_STACK_VENDOR_SPECIFIC_VALUE_TEST_DATA); + } + else if (valueCb < sizeof(VIRTUAL_STACK_VENDOR_SPECIFIC_VALUE_TEST_DATA)) + { + status = STATUS_BUFFER_TOO_SMALL; + } + else + { + value->Test1 = 0x12345678; + value->Test2 = 0x87654321; + information = sizeof(VIRTUAL_STACK_VENDOR_SPECIFIC_VALUE_TEST_DATA); + } + } + else if (control->VendorSpecificId == VirtualStackVendorSpecificRequestSetTestConfig) + { + DrvLogInfo(g_SDCAVCodecLog, FLAG_STREAM, L"SDCAVCodec: VENDOR SPECIFIC Set Test Config %d", control->Config.IsScatterGather); + } + else + { + status = STATUS_INVALID_PARAMETER; + } + + WdfRequestCompleteWithInformation(Request, status, information); +} + +static ACX_PROPERTY_ITEM g_CircuitProperties[] = +{ + { + &SDCAVADPropsetId, + SDCAVAD_PROPERTY_TEST1, + ACX_PROPERTY_ITEM_FLAG_SET, + CodecR_EvtPropertyCallback + }, + { + &SDCAVADPropsetId, + SDCAVAD_PROPERTY_TEST2, + ACX_PROPERTY_ITEM_FLAG_GET, + CodecR_EvtPropertyCallback + }, + { + &SDCAVADPropsetId, + SDCAVAD_PROPERTY_TEST5, + ACX_PROPERTY_ITEM_FLAG_SET, + CodecR_EvtPropertyCallback + }, + { + &SDCAVADPropsetId, + SDCAVAD_PROPERTY_TEST6, + ACX_PROPERTY_ITEM_FLAG_GET, + CodecR_EvtPropertyCallback + }, + { + &KSPROPERTYSETID_Sdca, + KSPROPERTY_SDCA_VENDOR_SPECIFIC, + ACX_PROPERTY_ITEM_FLAG_GET | ACX_PROPERTY_ITEM_FLAG_SET, + CodecR_EvtPropertyVendorSpecificCallback + }, +}; + +PAGED_CODE_SEG +NTSTATUS +CodecR_CreateRenderCircuit( + _In_ WDFDEVICE Device, + _Out_ ACXCIRCUIT * Circuit +) +/*++ + +Routine Description: + + This routine builds the CODEC render circuit. + +Return Value: + + NT status value + +--*/ +{ + PAGED_CODE(); + + NTSTATUS status = STATUS_SUCCESS; + + DrvLogEnter(g_SDCAVCodecLog); + + // + // Get a CircuitInit structure. + // + PACXCIRCUIT_INIT circuitInit = NULL; + circuitInit = AcxCircuitInitAllocate(Device); + RETURN_NTSTATUS_IF_TRUE(NULL == circuitInit, STATUS_NO_MEMORY); + auto circuitInitScope = scope_exit([&circuitInit]() { + AcxCircuitInitFree(circuitInit); + }); + + // + // Init output value. + // + *Circuit = NULL; + + /////////////////////////////////////////////////////////// + // + // Create a circuit. + // + + // + // Add circuit identifiers. + // + AcxCircuitInitSetComponentId(circuitInit, &CODEC_CIRCUIT_RENDER_GUID); + + DECLARE_CONST_UNICODE_STRING(circuitUri, RENDER_CIRCUIT_URI); + RETURN_NTSTATUS_IF_FAILED(AcxCircuitInitAssignComponentUri(circuitInit, &circuitUri)); + + WDF_OBJECT_ATTRIBUTES attributes; + ACXCIRCUIT circuit; + WDF_OBJECT_ATTRIBUTES_INIT_CONTEXT_TYPE(&attributes, CODEC_RENDER_CIRCUIT_CONTEXT); + DECLARE_CONST_UNICODE_STRING(circuitName, L"Speaker0"); + + // + // Add properties, events and methods. + // + RETURN_NTSTATUS_IF_FAILED(AcxCircuitInitAssignProperties(circuitInit, + g_CircuitProperties, + SIZEOF_ARRAY(g_CircuitProperties))); + + + RETURN_NTSTATUS_IF_FAILED(CreateRenderCircuit(circuitInit, circuitName, Device, &circuit)); + circuitInitScope.release(); + + CODEC_RENDER_CIRCUIT_CONTEXT *circuitCtx; + ASSERT(circuit != NULL); + circuitCtx = GetRenderCircuitContext(circuit); + ASSERT(circuitCtx); + + // + // Post circuit creation initialization. + // + + /////////////////////////////////////////////////////////// + // + // Add two custom circuit elements. Note that driver doesn't need to + // perform this step if it doesn't want to expose any circuit elements. + // + + // + // Create 1st custom circuit-element (mute element). + // + ACX_MUTE_CALLBACKS muteCallbacks; + ACX_MUTE_CALLBACKS_INIT(&muteCallbacks); + muteCallbacks.EvtAcxMuteAssignState = CodecR_EvtMuteAssignStateCallback; + muteCallbacks.EvtAcxMuteRetrieveState = CodecR_EvtMuteRetrieveStateCallback; + + ACX_MUTE_CONFIG muteCfg; + ACX_MUTE_CONFIG_INIT(&muteCfg); + muteCfg.ChannelsCount = MAX_CHANNELS; + muteCfg.Callbacks = &muteCallbacks; + + WDF_OBJECT_ATTRIBUTES_INIT_CONTEXT_TYPE(&attributes, CODEC_MUTE_ELEMENT_CONTEXT); + attributes.ParentObject = circuit; + + const int numElements = 2; + ACXELEMENT elements[numElements] = {0}; + RETURN_NTSTATUS_IF_FAILED(AcxMuteCreate(circuit, &attributes, &muteCfg, (ACXMUTE *)&elements[0])); + + ASSERT(elements[0] != NULL); + CODEC_MUTE_ELEMENT_CONTEXT *muteCtx; + muteCtx = GetCodecMuteElementContext(elements[0]); + ASSERT(muteCtx); + UNREFERENCED_PARAMETER(muteCtx); + + circuitCtx->MuteElement = (ACXMUTE)elements[0]; + + // + // Testing async mute state change. + // + { + WDF_TIMER_CONFIG timerCfg; + PCODEC_MUTE_TIMER_CONTEXT timerCtx; + + WDF_OBJECT_ATTRIBUTES_INIT_CONTEXT_TYPE(&attributes, CODEC_MUTE_TIMER_CONTEXT); + attributes.ParentObject = circuitCtx->MuteElement; + + WDF_TIMER_CONFIG_INIT_PERIODIC(&timerCfg, CodecR_EvtMuteTimerFunc, 4000 /* 4sec in msec */); + + RETURN_NTSTATUS_IF_FAILED(WdfTimerCreate(&timerCfg, &attributes, &muteCtx->Timer)); + + ASSERT(muteCtx->Timer); + + timerCtx = GetCodecMuteTimerContext(muteCtx->Timer); + ASSERT(timerCtx); + + timerCtx->MuteElement = circuitCtx->MuteElement; + } + + // + // Create 2nd custom circuit-element (volume element). + // + ACX_VOLUME_CALLBACKS volumeCallbacks; + ACX_VOLUME_CALLBACKS_INIT(&volumeCallbacks); + volumeCallbacks.EvtAcxVolumeAssignLevel = CodecR_EvtVolumeAssignLevelCallback; + volumeCallbacks.EvtAcxVolumeRetrieveLevel = CodecR_EvtVolumeRetrieveLevelCallback; + + ACX_VOLUME_CONFIG volumeCfg; + ACX_VOLUME_CONFIG_INIT(&volumeCfg); + volumeCfg.ChannelsCount = MAX_CHANNELS; + volumeCfg.Minimum = VOLUME_LEVEL_MINIMUM; + volumeCfg.Maximum = VOLUME_LEVEL_MAXIMUM; + volumeCfg.SteppingDelta = VOLUME_STEPPING; + volumeCfg.Callbacks = &volumeCallbacks; + + WDF_OBJECT_ATTRIBUTES_INIT_CONTEXT_TYPE(&attributes, CODEC_VOLUME_ELEMENT_CONTEXT); + attributes.ParentObject = circuit; + + RETURN_NTSTATUS_IF_FAILED(AcxVolumeCreate(circuit, &attributes, &volumeCfg, (ACXVOLUME *)&elements[1])); + + ASSERT(elements[1] != NULL); + CODEC_VOLUME_ELEMENT_CONTEXT *volumeCtx; + volumeCtx = GetCodecVolumeElementContext(elements[1]); + ASSERT(volumeCtx); + volumeCtx->VolumeLevel[0] = (VOLUME_LEVEL_MAXIMUM + VOLUME_LEVEL_MINIMUM) / 2 / VOLUME_STEPPING * VOLUME_STEPPING; + volumeCtx->VolumeLevel[1] = (VOLUME_LEVEL_MAXIMUM + VOLUME_LEVEL_MINIMUM) / 2 / VOLUME_STEPPING * VOLUME_STEPPING; + + circuitCtx->VolumeElement = (ACXVOLUME)elements[1]; + + // + // Testing async volume state change. + // + { + WDF_TIMER_CONFIG timerCfg; + PCODEC_VOLUME_TIMER_CONTEXT timerCtx; + + WDF_OBJECT_ATTRIBUTES_INIT_CONTEXT_TYPE(&attributes, CODEC_VOLUME_TIMER_CONTEXT); + attributes.ParentObject = circuitCtx->VolumeElement; + + WDF_TIMER_CONFIG_INIT_PERIODIC(&timerCfg, CodecR_EvtVolumeTimerFunc, 4500 /* 4.5sec in msec */); + + RETURN_NTSTATUS_IF_FAILED(WdfTimerCreate(&timerCfg, &attributes, &volumeCtx->Timer)); + + ASSERT(volumeCtx->Timer); + + timerCtx = GetCodecVolumeTimerContext(volumeCtx->Timer); + ASSERT(timerCtx); + + timerCtx->VolumeElement = circuitCtx->VolumeElement; + } + + // + // Add the circuit elements + // + RETURN_NTSTATUS_IF_FAILED(AcxCircuitAddElements(circuit, elements, SIZEOF_ARRAY(elements))); + + /////////////////////////////////////////////////////////// + // + // Allocate the formats this circuit supports. + // + // PCM:44100 channel:2 24in32 + ACX_DATAFORMAT_CONFIG formatCfg; + ACX_DATAFORMAT_CONFIG_INIT_KS(&formatCfg, &Pcm44100c2_24in32); + WDF_OBJECT_ATTRIBUTES_INIT_CONTEXT_TYPE(&attributes, CODEC_FORMAT_CONTEXT); + attributes.ParentObject = circuit; + + ACXDATAFORMAT formatPcm44100c2_24in32; + RETURN_NTSTATUS_IF_FAILED(AcxDataFormatCreate(Device, &attributes, &formatCfg, &formatPcm44100c2_24in32)); + + CODEC_FORMAT_CONTEXT *formatCtx; + formatCtx = GetCodecFormatContext(formatPcm44100c2_24in32); + ASSERT(formatCtx); + + UNREFERENCED_PARAMETER(formatCtx); + + // PCM:48000 channel:2 24in32 + ACX_DATAFORMAT_CONFIG_INIT_KS(&formatCfg, &Pcm48000c2_24in32); + WDF_OBJECT_ATTRIBUTES_INIT_CONTEXT_TYPE(&attributes, CODEC_FORMAT_CONTEXT); + attributes.ParentObject = circuit; + + ACXDATAFORMAT formatPcm48000c2_24in32; + RETURN_NTSTATUS_IF_FAILED(AcxDataFormatCreate(Device, &attributes, &formatCfg, &formatPcm48000c2_24in32)); + + formatCtx = GetCodecFormatContext(formatPcm48000c2_24in32); + ASSERT(formatCtx); + UNREFERENCED_PARAMETER(formatCtx); + + /////////////////////////////////////////////////////////// + // + // Create render pin. AcxCircuit creates the other pin by default. + // + + ACX_PIN_CALLBACKS pinCallbacks; + ACX_PIN_CALLBACKS_INIT(&pinCallbacks); + pinCallbacks.EvtAcxPinSetDataFormat = CodecR_EvtAcxPinSetDataFormat; + + ACX_PIN_CONFIG pinCfg; + ACX_PIN_CONFIG_INIT(&pinCfg); + pinCfg.Type = AcxPinTypeSink; + pinCfg.Communication = AcxPinCommunicationNone; + pinCfg.Category = &KSCATEGORY_AUDIO; + pinCfg.PinCallbacks = &pinCallbacks; + + WDF_OBJECT_ATTRIBUTES_INIT_CONTEXT_TYPE(&attributes, CODEC_PIN_CONTEXT); + attributes.EvtCleanupCallback = CodecR_EvtPinContextCleanup; + attributes.ParentObject = circuit; + + ACXPIN pin; + RETURN_NTSTATUS_IF_FAILED(AcxPinCreate(circuit, &attributes, &pinCfg, &pin)); + + ASSERT(pin != NULL); + CODEC_PIN_CONTEXT *pinCtx; + pinCtx = GetCodecPinContext(pin); + ASSERT(pinCtx); + + // + // Add our supported formats to the Default mode for the circuit + // + ACXDATAFORMATLIST formatList; + formatList = AcxPinGetRawDataFormatList(pin); + if (formatList == NULL) + { + status = STATUS_INSUFFICIENT_RESOURCES; + } + RETURN_NTSTATUS_IF_FAILED(status); + + RETURN_NTSTATUS_IF_FAILED(AcxDataFormatListAssignDefaultDataFormat(formatList, formatPcm48000c2_24in32)); + RETURN_NTSTATUS_IF_FAILED(AcxDataFormatListAddDataFormat(formatList, formatPcm44100c2_24in32)); + + // Add render pin, using default pin id (0) + RETURN_NTSTATUS_IF_FAILED(AcxCircuitAddPins(circuit, &pin, 1)); + + /////////////////////////////////////////////////////////// + // + // Create Bridge Pin. + // + + ACX_PIN_CONFIG_INIT(&pinCfg); + pinCfg.Type = AcxPinTypeSource; + pinCfg.Communication = AcxPinCommunicationNone; + pinCfg.Category = &KSNODETYPE_SPEAKER; + + WDF_OBJECT_ATTRIBUTES_INIT_CONTEXT_TYPE(&attributes, CODEC_PIN_CONTEXT); + attributes.EvtCleanupCallback = CodecR_EvtPinContextCleanup; + attributes.ParentObject = circuit; + + RETURN_NTSTATUS_IF_FAILED(AcxPinCreate(circuit, &attributes, &pinCfg, &pin)); + + ASSERT(pin != NULL); + pinCtx = GetCodecPinContext(pin); + ASSERT(pinCtx); + + RETURN_NTSTATUS_IF_FAILED(AddJack(attributes, pin, SPEAKER_FRONT_LEFT | SPEAKER_FRONT_RIGHT, RGB(0, 0, 0), AcxConnTypeAtapiInternal, AcxGeoLocFront, AcxGenLocPrimaryBox, AcxPortConnIntegratedDevice)); + + // Add render bridge pin + RETURN_NTSTATUS_IF_FAILED(AcxCircuitAddPins(circuit, &pin, 1)); + + ConnectRenderCircuitElements(numElements, elements, circuit); + + // + // Set output value. + // + *Circuit = circuit; + + // + // Done. + // + status = STATUS_SUCCESS; + + + return status; +} + +_Use_decl_annotations_ +#pragma code_seg() +NTSTATUS +CodecR_EvtCircuitPowerUp ( + _In_ WDFDEVICE Device, + _In_ ACXCIRCUIT Circuit, + _In_ WDF_POWER_DEVICE_STATE PreviousState + ) +{ + UNREFERENCED_PARAMETER(Device); + UNREFERENCED_PARAMETER(PreviousState); + + CODEC_RENDER_CIRCUIT_CONTEXT * circuitCtx; + CODEC_MUTE_ELEMENT_CONTEXT * muteCtx; + CODEC_VOLUME_ELEMENT_CONTEXT * volumeCtx; + + // for testing. + circuitCtx = GetRenderCircuitContext(Circuit); + ASSERT(circuitCtx); + + ASSERT(circuitCtx->MuteElement); + muteCtx = GetCodecMuteElementContext(circuitCtx->MuteElement); + ASSERT(muteCtx); + + ASSERT(circuitCtx->VolumeElement); + volumeCtx = GetCodecVolumeElementContext(circuitCtx->VolumeElement); + ASSERT(volumeCtx); + + return STATUS_SUCCESS; +} + +_Use_decl_annotations_ +PAGED_CODE_SEG +NTSTATUS +CodecR_EvtCircuitPowerDown ( + _In_ WDFDEVICE Device, + _In_ ACXCIRCUIT Circuit, + _In_ WDF_POWER_DEVICE_STATE TargetState + ) +{ + UNREFERENCED_PARAMETER(Device); + UNREFERENCED_PARAMETER(TargetState); + + CODEC_RENDER_CIRCUIT_CONTEXT * circuitCtx; + CODEC_MUTE_ELEMENT_CONTEXT * muteCtx; + CODEC_VOLUME_ELEMENT_CONTEXT * volumeCtx; + + PAGED_CODE(); + + // for testing. + circuitCtx = GetRenderCircuitContext(Circuit); + ASSERT(circuitCtx); + + ASSERT(circuitCtx->MuteElement); + muteCtx = GetCodecMuteElementContext(circuitCtx->MuteElement); + ASSERT(muteCtx); + + ASSERT(circuitCtx->VolumeElement); + volumeCtx = GetCodecVolumeElementContext(circuitCtx->VolumeElement); + ASSERT(volumeCtx); + + return STATUS_SUCCESS; +} + +_Use_decl_annotations_ +PAGED_CODE_SEG +NTSTATUS +CodecR_EvtCircuitCompositeCircuitInitialize( + _In_ WDFDEVICE Device, + _In_ ACXCIRCUIT Circuit, + _In_opt_ ACXOBJECTBAG CircuitProperties +) +{ + PAGED_CODE(); + + UNREFERENCED_PARAMETER(Device); + UNREFERENCED_PARAMETER(Circuit); + + NTSTATUS status = STATUS_SUCCESS; + + if (CircuitProperties != NULL) + { + DECLARE_CONST_ACXOBJECTBAG_DRIVER_PROPERTY_NAME(msft, TestUI4); + ULONG testUI4 = 0; + + status = AcxObjectBagRetrieveUI4(CircuitProperties, &TestUI4, &testUI4); + } + + return status; +} + +_Use_decl_annotations_ +PAGED_CODE_SEG +NTSTATUS +CodecR_EvtCircuitCompositeInitialize( + _In_ WDFDEVICE Device, + _In_ ACXCIRCUIT Circuit, + _In_ ACXOBJECTBAG CompositeProperties + ) +{ + PAGED_CODE(); + + NTSTATUS status = STATUS_SUCCESS; + + UNREFERENCED_PARAMETER(Device); + UNREFERENCED_PARAMETER(Circuit); + + ASSERT(CompositeProperties); + + DECLARE_CONST_ACXOBJECTBAG_SYSTEM_PROPERTY_NAME(UniqueID); + GUID uniqueId = {0}; + status = AcxObjectBagRetrieveGuid(CompositeProperties, &UniqueID, &uniqueId); + + return status; +} + +PAGED_CODE_SEG +NTSTATUS +CodecR_EvtCircuitCreateStream( + _In_ WDFDEVICE Device, + _In_ ACXCIRCUIT Circuit, + _In_ ACXPIN Pin, + _In_ PACXSTREAM_INIT StreamInit, + _In_ ACXDATAFORMAT StreamFormat, + _In_ const GUID * SignalProcessingMode, + _In_ ACXOBJECTBAG VarArguments +) +/*++ + +Routine Description: + + This routine create a stream for the specified circuit. + +Return Value: + + NT status value + +--*/ +{ + PAGED_CODE(); + + NTSTATUS status = STATUS_SUCCESS; + + DrvLogEnter(g_SDCAVCodecLog); + + UNREFERENCED_PARAMETER(Pin); + UNREFERENCED_PARAMETER(SignalProcessingMode); + + ASSERT(IsEqualGUID(*SignalProcessingMode, AUDIO_SIGNALPROCESSINGMODE_RAW)); + + PCODEC_RENDER_DEVICE_CONTEXT devCtx; + devCtx = GetRenderDeviceContext(Device); + ASSERT(devCtx != NULL); + + DECLARE_CONST_ACXOBJECTBAG_DRIVER_PROPERTY_NAME(msft, TestUI4); + if (VarArguments) + { + // Get the variable arguments parameter and retrive the values set by the DSP object. + ULONG ui4Value = 0; + RETURN_NTSTATUS_IF_FAILED(AcxObjectBagRetrieveUI4(VarArguments, &TestUI4, &ui4Value)); + + RETURN_NTSTATUS_IF_TRUE(ui4Value == 0, STATUS_UNSUCCESSFUL); + + ui4Value++; + + // Add the modified value back to object bag. + RETURN_NTSTATUS_IF_FAILED(AcxObjectBagAddUI4(VarArguments, &TestUI4, ui4Value)); + } + + // + // Set circuit-callbacks. + // + RETURN_NTSTATUS_IF_FAILED(AcxStreamInitAssignAcxRequestPreprocessCallback( + StreamInit, + CodecR_EvtStreamRequestPreprocess, + (ACXCONTEXT)AcxRequestTypeAny, // dbg only + AcxRequestTypeAny, + NULL, + AcxItemIdNone)); + + /* + // + // Add properties, events and methods. + // + RETURN_NTSTATUS_IF_FAILED(AcxStreamInitAssignProperties(StreamInit, + StreamProperties, + StreamPropertiesCount)); + */ + + // + // Init streaming callbacks. + // + ACX_STREAM_CALLBACKS streamCallbacks; + ACX_STREAM_CALLBACKS_INIT(&streamCallbacks); + streamCallbacks.EvtAcxStreamPrepareHardware = Codec_EvtStreamPrepareHardware; + streamCallbacks.EvtAcxStreamReleaseHardware = Codec_EvtStreamReleaseHardware; + streamCallbacks.EvtAcxStreamRun = Codec_EvtStreamRun; + streamCallbacks.EvtAcxStreamPause = Codec_EvtStreamPause; + streamCallbacks.EvtAcxStreamAssignDrmContentId = Codec_EvtStreamAssignDrmContentId; + + RETURN_NTSTATUS_IF_FAILED(AcxStreamInitAssignAcxStreamCallbacks(StreamInit, &streamCallbacks)); + + // + // Create the stream. + // + WDF_OBJECT_ATTRIBUTES attributes; + WDF_OBJECT_ATTRIBUTES_INIT_CONTEXT_TYPE(&attributes, CODEC_STREAM_CONTEXT); + attributes.EvtDestroyCallback = Codec_EvtStreamDestroy; + ACXSTREAM stream; + RETURN_NTSTATUS_IF_FAILED(AcxStreamCreate(Device, Circuit, &attributes, &StreamInit, &stream)); + + CRenderStreamEngine *streamEngine = NULL; + streamEngine = new(POOL_FLAG_NON_PAGED, DRIVER_TAG) CRenderStreamEngine(stream, StreamFormat); + RETURN_NTSTATUS_IF_TRUE(NULL == streamEngine, STATUS_INSUFFICIENT_RESOURCES); + + CODEC_STREAM_CONTEXT *streamCtx; + streamCtx = GetCodecStreamContext(stream); + ASSERT(streamCtx); + streamCtx->StreamEngine = (PVOID)streamEngine; + streamEngine = NULL; + + // + // Post stream creation initialization. + // + + // + // Create 1st custom stream-elements. + // + ACX_ELEMENT_CONFIG elementCfg; + ACX_ELEMENT_CONFIG_INIT(&elementCfg); + WDF_OBJECT_ATTRIBUTES_INIT_CONTEXT_TYPE(&attributes, CODEC_ELEMENT_CONTEXT); + attributes.ParentObject = stream; + + ACXELEMENT elements[2] = {0}; + RETURN_NTSTATUS_IF_FAILED(AcxElementCreate(stream, &attributes, &elementCfg, &elements[0])); + + ASSERT(elements[0] != NULL); + CODEC_ELEMENT_CONTEXT *elementCtx; + elementCtx = GetCodecElementContext(elements[0]); + ASSERT(elementCtx); + UNREFERENCED_PARAMETER(elementCtx); + + // + // Create 2nd custom stream-elements. + // + ACX_ELEMENT_CONFIG_INIT(&elementCfg); + WDF_OBJECT_ATTRIBUTES_INIT_CONTEXT_TYPE(&attributes, CODEC_ELEMENT_CONTEXT); + attributes.ParentObject = stream; + + RETURN_NTSTATUS_IF_FAILED(AcxElementCreate(stream, &attributes, &elementCfg, &elements[1])); + + ASSERT(elements[1] != NULL); + elementCtx = GetCodecElementContext(elements[1]); + ASSERT(elementCtx); + UNREFERENCED_PARAMETER(elementCtx); + + // + // Add stream elements + // + RETURN_NTSTATUS_IF_FAILED(AcxStreamAddElements(stream, elements, SIZEOF_ARRAY(elements))); + + return status; +} + + diff --git a/audio/Soundwire/Samples/SdcaVad/SdcaVCodec/resources.rc b/audio/Soundwire/Samples/SdcaVad/SdcaVCodec/resources.rc new file mode 100644 index 000000000..0b1f97490 --- /dev/null +++ b/audio/Soundwire/Samples/SdcaVad/SdcaVCodec/resources.rc @@ -0,0 +1,12 @@ +#include + +#include + +#define VER_FILETYPE VFT_DRV +#define VER_FILESUBTYPE VFT2_DRV_SYSTEM +#define VER_FILEDESCRIPTION_STR "ACX v1.0 Codec Audio Driver" +#define VER_INTERNALNAME_STR "SDCAVCodec.sys" +#define VER_ORIGINALFILENAME_STR "SDCAVCodec.sys" + +#include "common.ver" + diff --git a/audio/Soundwire/Samples/SdcaVad/SdcaVCodec/streamengine.cpp b/audio/Soundwire/Samples/SdcaVad/SdcaVCodec/streamengine.cpp new file mode 100644 index 000000000..730f05535 --- /dev/null +++ b/audio/Soundwire/Samples/SdcaVad/SdcaVCodec/streamengine.cpp @@ -0,0 +1,270 @@ +/*++ + + THIS CODE AND INFORMATION IS PROVIDED "AS IS" WITHOUT WARRANTY OF ANY + KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE + IMPLIED WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A PARTICULAR + PURPOSE. + +Module Name: + + StreamEngine.cpp + +Abstract: + + Virtual Streaming Engine - this module controls streaming logic for + the device. + +Environment: + + Kernel mode + +--*/ + +#include "private.h" +#include +#include "stdunk.h" +#include +#include +#include +#include "streamengine.h" + +#ifndef __INTELLISENSE__ +#include "streamengine.tmh" +#endif + +_Use_decl_annotations_ +PAGED_CODE_SEG +CStreamEngine::CStreamEngine( + _In_ ACXSTREAM Stream, + _In_ ACXDATAFORMAT StreamFormat + ) + : m_CurrentState(AcxStreamStateStop), + m_Stream(Stream), + m_StreamFormat(StreamFormat) +{ + PAGED_CODE(); + + KeQueryPerformanceCounter(&m_PerformanceCounterFrequency); +} + +#pragma code_seg() +CStreamEngine::~CStreamEngine() +{ +} + +_Use_decl_annotations_ +PAGED_CODE_SEG +NTSTATUS +CStreamEngine::PrepareHardware() +{ + PAGED_CODE(); + + m_CurrentState = AcxStreamStatePause; + + return STATUS_SUCCESS; +} + +_Use_decl_annotations_ +PAGED_CODE_SEG +NTSTATUS +CStreamEngine::ReleaseHardware() +{ + PAGED_CODE(); + + m_CurrentState = AcxStreamStateStop; + + return STATUS_SUCCESS; +} + +_Use_decl_annotations_ +PAGED_CODE_SEG +NTSTATUS +CStreamEngine::Pause() +{ + PAGED_CODE(); + + m_CurrentState = AcxStreamStatePause; + + return STATUS_SUCCESS; +} + +_Use_decl_annotations_ +PAGED_CODE_SEG +NTSTATUS +CStreamEngine::Run() +{ + PAGED_CODE(); + + NTSTATUS status = STATUS_SUCCESS; + + if (m_CurrentState != AcxStreamStatePause) + { + status = STATUS_INVALID_STATE_TRANSITION; + return status; + } + + m_CurrentState = AcxStreamStateRun; + + return status; +} + +_Use_decl_annotations_ +PAGED_CODE_SEG +NTSTATUS +CStreamEngine::AssignDrmContentId( + _In_ ULONG DrmContentId, + _In_ PACXDRMRIGHTS DrmRights +) +{ + PAGED_CODE(); + + UNREFERENCED_PARAMETER(DrmContentId); + UNREFERENCED_PARAMETER(DrmRights); + + // + // At this point the driver should enforce the new DrmRights. + // + // HDMI render: if DigitalOutputDisable or CopyProtect is true, enable HDCP. + // + // From MSDN: + // + // This sample doesn't forward protected content, but if your driver uses + // lower layer drivers or a different stack to properly work, please see the + // following info from MSDN: + // + // "Before allowing protected content to flow through a data path, the system + // verifies that the data path is secure. To do so, the system authenticates + // each module in the data path beginning at the upstream end of the data path + // and moving downstream. As each module is authenticated, that module gives + // the system information about the next module in the data path so that it + // can also be authenticated. To be successfully authenticated, a module's + // binary file must be signed as DRM-compliant. + // + // Two adjacent modules in the data path can communicate with each other in + // one of several ways. If the upstream module calls the downstream module + // through IoCallDriver, the downstream module is part of a WDM driver. In + // this case, the upstream module calls the AcxDrmForwardContentToDeviceObject + // function to provide the system with the device object representing the + // downstream module. (If the two modules communicate through the downstream + // module's content handlers, the upstream module calls AcxDrmAddContentHandlers + // instead.) + // + // For more information, see MSDN's DRM Functions and Interfaces. + // + + return STATUS_SUCCESS; +} + +_Use_decl_annotations_ +PAGED_CODE_SEG +NTSTATUS +CStreamEngine::GetHWLatency( + _Out_ ULONG * FifoSize, + _Out_ ULONG * Delay +) +{ + PAGED_CODE(); + + *FifoSize = 128; + *Delay = 0; + + return STATUS_SUCCESS; +} + +_Use_decl_annotations_ +PAGED_CODE_SEG +CRenderStreamEngine::CRenderStreamEngine( + _In_ ACXSTREAM Stream, + _In_ ACXDATAFORMAT StreamFormat +) + : CStreamEngine(Stream, StreamFormat) +{ + PAGED_CODE(); +} + +_Use_decl_annotations_ +PAGED_CODE_SEG +CRenderStreamEngine::~CRenderStreamEngine() +{ + PAGED_CODE(); +} + +_Use_decl_annotations_ +PAGED_CODE_SEG +NTSTATUS +CRenderStreamEngine::PrepareHardware() +{ + NTSTATUS status = STATUS_SUCCESS; + + PAGED_CODE(); + + status = CStreamEngine::PrepareHardware(); + + // Add other init here. + + return status; +} + +_Use_decl_annotations_ +PAGED_CODE_SEG +NTSTATUS +CRenderStreamEngine::ReleaseHardware() +{ + PAGED_CODE(); + + return CStreamEngine::ReleaseHardware(); +} + +_Use_decl_annotations_ +PAGED_CODE_SEG +CCaptureStreamEngine::CCaptureStreamEngine( + _In_ ACXSTREAM Stream, + _In_ ACXDATAFORMAT StreamFormat +) + : CStreamEngine(Stream, StreamFormat) +{ + PAGED_CODE(); +} + +_Use_decl_annotations_ +PAGED_CODE_SEG +CCaptureStreamEngine::~CCaptureStreamEngine() +{ + PAGED_CODE(); +} + +_Use_decl_annotations_ +PAGED_CODE_SEG +NTSTATUS +CCaptureStreamEngine::PrepareHardware() +{ + PAGED_CODE(); + + NTSTATUS status = STATUS_SUCCESS; + + RETURN_NTSTATUS_IF_FAILED(CStreamEngine::PrepareHardware()); + + RETURN_NTSTATUS_IF_FAILED(ReadRegistrySettings()); + + return status; +} + +_Use_decl_annotations_ +PAGED_CODE_SEG +NTSTATUS +CCaptureStreamEngine::ReleaseHardware() +{ + PAGED_CODE(); + + return CStreamEngine::ReleaseHardware(); +} + +_Use_decl_annotations_ +PAGED_CODE_SEG +NTSTATUS +CCaptureStreamEngine::ReadRegistrySettings() +{ + PAGED_CODE(); + + return STATUS_SUCCESS; +} diff --git a/audio/Soundwire/Samples/SdcaVad/SdcaVCodec/streamengine.h b/audio/Soundwire/Samples/SdcaVad/SdcaVCodec/streamengine.h new file mode 100644 index 000000000..906e21f3a --- /dev/null +++ b/audio/Soundwire/Samples/SdcaVad/SdcaVCodec/streamengine.h @@ -0,0 +1,131 @@ +#pragma once + +#define HNSTIME_PER_MILLISECOND 10000 + +class CStreamEngine +{ +public: + virtual + __drv_maxIRQL(PASSIVE_LEVEL) + PAGED_CODE_SEG + NTSTATUS + PrepareHardware(); + + virtual + __drv_maxIRQL(PASSIVE_LEVEL) + PAGED_CODE_SEG + NTSTATUS + ReleaseHardware(); + + virtual + __drv_maxIRQL(PASSIVE_LEVEL) + PAGED_CODE_SEG + NTSTATUS + Run(); + + virtual + __drv_maxIRQL(PASSIVE_LEVEL) + PAGED_CODE_SEG + NTSTATUS + Pause(); + + virtual + __drv_maxIRQL(PASSIVE_LEVEL) + PAGED_CODE_SEG + NTSTATUS + AssignDrmContentId( + _In_ ULONG DrmContentId, + _In_ PACXDRMRIGHTS DrmRights + ); + + virtual + __drv_maxIRQL(PASSIVE_LEVEL) + PAGED_CODE_SEG + NTSTATUS + GetHWLatency( + _Out_ ULONG * FifoSize, + _Out_ ULONG * Delay + ); + + __drv_maxIRQL(PASSIVE_LEVEL) + PAGED_CODE_SEG + CStreamEngine( + _In_ ACXSTREAM Stream, + _In_ ACXDATAFORMAT StreamFormat + ); + + __drv_maxIRQL(PASSIVE_LEVEL) + virtual + #pragma code_seg() + ~CStreamEngine(); + +protected: + ACX_STREAM_STATE m_CurrentState; + ACXSTREAM m_Stream; + ACXDATAFORMAT m_StreamFormat; + LARGE_INTEGER m_PerformanceCounterFrequency; +}; + +class CRenderStreamEngine : public CStreamEngine +{ +public: + __drv_maxIRQL(PASSIVE_LEVEL) + PAGED_CODE_SEG + CRenderStreamEngine( + _In_ ACXSTREAM Stream, + _In_ ACXDATAFORMAT StreamFormat + ); + + __drv_maxIRQL(PASSIVE_LEVEL) + PAGED_CODE_SEG + ~CRenderStreamEngine(); + + virtual + __drv_maxIRQL(PASSIVE_LEVEL) + PAGED_CODE_SEG + NTSTATUS + PrepareHardware(); + + virtual + __drv_maxIRQL(PASSIVE_LEVEL) + PAGED_CODE_SEG + NTSTATUS + ReleaseHardware(); + +protected: + // data section. +}; + +class CCaptureStreamEngine : public CStreamEngine +{ +public: + __drv_maxIRQL(PASSIVE_LEVEL) + PAGED_CODE_SEG + CCaptureStreamEngine( + _In_ ACXSTREAM Stream, + _In_ ACXDATAFORMAT StreamFormat + ); + + __drv_maxIRQL(PASSIVE_LEVEL) + PAGED_CODE_SEG + ~CCaptureStreamEngine(); + + virtual + __drv_maxIRQL(PASSIVE_LEVEL) + PAGED_CODE_SEG + NTSTATUS + PrepareHardware(); + + virtual + __drv_maxIRQL(PASSIVE_LEVEL) + PAGED_CODE_SEG + NTSTATUS + ReleaseHardware(); + +protected: + __drv_maxIRQL(PASSIVE_LEVEL) + PAGED_CODE_SEG + NTSTATUS + ReadRegistrySettings(); +}; + diff --git a/audio/Soundwire/Samples/SdcaVad/SdcaVDsp/AcpiReader.cpp b/audio/Soundwire/Samples/SdcaVad/SdcaVDsp/AcpiReader.cpp new file mode 100644 index 000000000..6a223c30c --- /dev/null +++ b/audio/Soundwire/Samples/SdcaVad/SdcaVDsp/AcpiReader.cpp @@ -0,0 +1,1715 @@ +/*++ + +Copyright (c) Microsoft Corporation. All rights reserved. + + THIS CODE AND INFORMATION IS PROVIDED "AS IS" WITHOUT WARRANTY OF ANY + KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE + IMPLIED WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A PARTICULAR + PURPOSE. + +Module Name: + + AcpiReader.cpp + +Abstract: + + Implements Acpi reader module. + +Environment: + + Kernel mode + +--*/ + +#include "private.h" + +#include + +#include "AcpiReader.h" + +#ifndef __INTELLISENSE__ +#include "AcpiReader.tmh" +#endif + +namespace ACPIREADER +{ + RECORDER_LOG AcpiReader::s_AcpiReaderLog { nullptr }; + ULONG AcpiReader::s_MemoryTag { 0 }; + + _Use_decl_annotations_ + PAGED_CODE_SEG + NTSTATUS + AcpiReader::_CreateAndInitialize(_In_ WDFDEVICE Device, _In_ RECORDER_LOG Log, _In_ ULONG MemoryTag) + { + NTSTATUS status = STATUS_SUCCESS; + WDF_OBJECT_ATTRIBUTES attributes; + AcpiReader * This{ nullptr }; + VOID * contextAddress; + + PAGED_CODE(); + + WDF_OBJECT_ATTRIBUTES_INIT_CONTEXT_TYPE(&attributes, AcpiReader); + attributes.EvtDestroyCallback = EvtContextDestroy; + + status = WdfObjectAllocateContext(Device, &attributes, &contextAddress); + if (!NT_SUCCESS(status)) + { + goto exit; + } + + s_AcpiReaderLog = Log; + s_MemoryTag = MemoryTag; + + This = new (contextAddress) AcpiReader(Device); + + exit: + return status; + } + + _Use_decl_annotations_ + PAGED_CODE_SEG + NTSTATUS + AcpiReader::ParseGuid( + _In_ PACPI_METHOD_ARGUMENT Argument, + _Out_writes_bytes_(BufferLength) PVOID Buffer, + _In_ ULONG BufferLength + ) + /*++ + + Routine Description: + + This function parses the content of an ACPI method argument into a GUID. + + Arguments: + + Argument - Supplies the ACPI argument to parse. + + Buffer - Supplies a pointer to the buffer to store the GUID. + + BufferLength - Supplies the buffer size in bytes. + + Return Value: + + NTSTATUS + + --*/ + { + + NTSTATUS status = STATUS_SUCCESS; + + PAGED_CODE(); + + DrvLogEnter(s_AcpiReaderLog); + + if (Argument->Type != ACPI_METHOD_ARGUMENT_BUFFER) + { + status = STATUS_INVALID_PARAMETER; + DrvLogError(s_AcpiReaderLog, FLAG_INIT, "%!FUNC! Unexpected Argument Type. Expected: %lu, Actual: %lu, %!STATUS!", ACPI_METHOD_ARGUMENT_BUFFER, Argument->Type, status); + ASSERT(FALSE); + goto exit; + } + + if (BufferLength < sizeof(GUID)) + { + status = STATUS_BUFFER_TOO_SMALL; + DrvLogError(s_AcpiReaderLog, FLAG_INIT, "%!FUNC! Buffer too small. Expected: %lu, Actual: %lu, %!STATUS!", sizeof(GUID), BufferLength, status); + ASSERT(FALSE); + goto exit; + } + + if (Argument->DataLength != sizeof(GUID)) + { + status = STATUS_ACPI_INVALID_ARGTYPE; + DrvLogError(s_AcpiReaderLog, FLAG_INIT, "%!FUNC! Unexpected Argument DataLength. Expected: %lu, Actual: %lu, %!STATUS!", sizeof(GUID), Argument->DataLength, status); + ASSERT(FALSE); + goto exit; + } + + RtlCopyMemory((PUCHAR)Buffer, Argument->Data, Argument->DataLength); + + exit: + DrvLogExit(s_AcpiReaderLog); + return status; + } + + _Use_decl_annotations_ + PAGED_CODE_SEG + NTSTATUS + AcpiReader::ParseULongLong( + _In_ PACPI_METHOD_ARGUMENT Argument, + _Out_ PULONGLONG Value + ) + /*++ + Routine Description: + + This function parses the content of an ACPI method argument into a ULONGLONG. + + Arguments: + + Argument - Supplies the ACPI argument to parse. + + Value - Supplies a pointer to the buffer to store the ULONGLONG value. + + Return Value: + + NTSTATUS + + --*/ + { + NTSTATUS status = STATUS_SUCCESS; + + DrvLogEnter(s_AcpiReaderLog); + + PAGED_CODE(); + + if (Argument->Type != ACPI_METHOD_ARGUMENT_INTEGER) + { + status = STATUS_INVALID_PARAMETER; + DrvLogError(s_AcpiReaderLog, FLAG_INIT, "%!FUNC! Unexpected Argument Type. Expected: %lu, Actual: %lu, %!STATUS!", ACPI_METHOD_ARGUMENT_INTEGER, Argument->Type, status); + ASSERT(FALSE); + goto exit; + } + + if (Argument->DataLength != sizeof(ULONGLONG)) + { + status = STATUS_ACPI_INVALID_ARGTYPE; + DrvLogError(s_AcpiReaderLog, FLAG_INIT, "%!FUNC! Unexpected Argument DataLength. Expected: %lu, Actual: %lu, %!STATUS!", sizeof(ULONGLONG), Argument->DataLength, status); + ASSERT(FALSE); + goto exit; + } + + RtlCopyMemory(Value, Argument->Data, Argument->DataLength); + + exit: + DrvLogExit(s_AcpiReaderLog); + return status; + } + + _Use_decl_annotations_ + PAGED_CODE_SEG + NTSTATUS + AcpiReader::ParseULong( + _In_ PACPI_METHOD_ARGUMENT Argument, + _Out_ PULONG Value + ) + /*++ + Routine Description: + + This function parses the content of an ACPI method argument into a ULONG. + + Arguments: + + Argument - Supplies the ACPI argument to parse. + + Value - Supplies a pointer to the buffer to store the ULONG value. + + Return Value: + + NTSTATUS + + --*/ + { + NTSTATUS status = STATUS_SUCCESS; + + DrvLogEnter(s_AcpiReaderLog); + + PAGED_CODE(); + + if (Argument->Type != ACPI_METHOD_ARGUMENT_INTEGER) + { + status = STATUS_INVALID_PARAMETER; + DrvLogError(s_AcpiReaderLog, FLAG_INIT, "%!FUNC! Unexpected Argument Type. Expected: %lu, Actual: %lu, %!STATUS!", ACPI_METHOD_ARGUMENT_INTEGER, Argument->Type, status); + ASSERT(FALSE); + goto exit; + } + + // Even though we are looking for a ULONG value, the DataLength will be set to ULONGLONG since we always use IOCTL_ACPI_EVAL_METHOD_EX + if (Argument->DataLength != sizeof(ULONGLONG)) + { + status = STATUS_ACPI_INVALID_ARGTYPE; + DrvLogError(s_AcpiReaderLog, FLAG_INIT, "%!FUNC! Unexpected Argument DataLength. Expected: %lu, Actual: %lu, %!STATUS!", sizeof(ULONGLONG), Argument->DataLength, status); + ASSERT(FALSE); + goto exit; + } + + *Value = (ULONG)Argument->Argument; + + exit: + DrvLogExit(s_AcpiReaderLog); + return status; + } + + __drv_maxIRQL(PASSIVE_LEVEL) + PAGED_CODE_SEG + NTSTATUS + AcpiReader::ParseString( + _In_ PACPI_METHOD_ARGUMENT Argument, + _Out_writes_opt_z_(ValueStringSize) char * ValueString, + _In_ ULONG ValueStringSize, + _Out_ PULONG PropertyValueSize + ) + /*++ + Routine Description: + + This function parses the content of an ACPI method argument into a string. + + Arguments: + + Argument - Supplies the ACPI argument to parse. + + ValueString - Buffer that will hold property value if found. + + ValueStringSize - Size of the output buffer. + + PropertyValueSize - Actual length of the property value. + + Return Value: + + NTSTATUS + + --*/ + { + NTSTATUS status = STATUS_SUCCESS; + + DrvLogEnter(s_AcpiReaderLog); + + PAGED_CODE(); + + if (Argument->Type != ACPI_METHOD_ARGUMENT_STRING) + { + status = STATUS_INVALID_PARAMETER; + DrvLogError(s_AcpiReaderLog, FLAG_INIT, "%!FUNC! Unexpected Argument Type. Expected %lu, Actual %lu, %!STATUS!", ACPI_METHOD_ARGUMENT_STRING, Argument->Type, status); + ASSERT(FALSE); + goto exit; + } + + *PropertyValueSize = Argument->DataLength; + + if (ValueStringSize == 0) + { + status = STATUS_BUFFER_TOO_SMALL; + goto exit; + } + else if (ValueStringSize < Argument->DataLength) + { + status = STATUS_BUFFER_TOO_SMALL; + DrvLogError(s_AcpiReaderLog, FLAG_INIT, "%!FUNC! Output buffer too small. Required %lu, Actual %lu, %!STATUS!", Argument->DataLength, ValueStringSize, status); + goto exit; + } +#pragma prefast(suppress:__WARNING_PRECONDITION_NULLTERMINATION_VIOLATION, "ACPI driver returns a NULL-terminated string.") + status = RtlStringCbCopyA(ValueString, ValueStringSize, (char *)Argument->Data); + + exit: + DrvLogExit(s_AcpiReaderLog); + return status; + } + + __drv_maxIRQL(PASSIVE_LEVEL) + PAGED_CODE_SEG + NTSTATUS + AcpiReader::ParseBuffer( + _In_ PACPI_METHOD_ARGUMENT Argument, + _Out_writes_bytes_(ValueBufferSize) PVOID ValueBuffer, + _In_ ULONG ValueBufferSize, + _Out_ PULONG PropertyValueSize + ) + /*++ + Routine Description: + + This function parses the content of an ACPI method argument into a buffer. + + Arguments: + + Argument - Supplies the ACPI argument to parse. + + ValueBuffer - Buffer that will hold property value if found. + + ValueBufferSize - Size of the output buffer. + + PropertyValueSize - Actual length of the property value. + + Return Value: + + NTSTATUS + + --*/ + { + NTSTATUS status = STATUS_SUCCESS; + + DrvLogEnter(s_AcpiReaderLog); + + PAGED_CODE(); + + if (Argument->Type != ACPI_METHOD_ARGUMENT_BUFFER) + { + status = STATUS_INVALID_PARAMETER; + DrvLogError(s_AcpiReaderLog, FLAG_INIT, "%!FUNC! Unexpected Argument Type. Expected %lu, Actual %lu, %!STATUS!", ACPI_METHOD_ARGUMENT_BUFFER, Argument->Type, status); + ASSERT(FALSE); + goto exit; + } + + *PropertyValueSize = Argument->DataLength; + + if (ValueBufferSize < Argument->DataLength) + { + status = STATUS_BUFFER_TOO_SMALL; + + // DrvLogVerbose if ValueBufferSize is 0, which means it's being called to determine size. Otherwise, DrvLogError. + if (ValueBufferSize == 0) + { + DrvLogVerbose(s_AcpiReaderLog, FLAG_INIT, "%!FUNC! Output buffer too small. Required %lu, Actual %lu, %!STATUS!", Argument->DataLength, ValueBufferSize, status); + } + else + { + DrvLogError(s_AcpiReaderLog, FLAG_INIT, "%!FUNC! Output buffer too small. Required %lu, Actual %lu, %!STATUS!", Argument->DataLength, ValueBufferSize, status); + } + + goto exit; + } + + RtlCopyMemory(ValueBuffer, Argument->Data, ValueBufferSize); + + exit: + DrvLogExit(s_AcpiReaderLog); + return status; + } + + __drv_maxIRQL(PASSIVE_LEVEL) + PAGED_CODE_SEG + NTSTATUS + AcpiReader::ParseULongArray( + _In_ PACPI_METHOD_ARGUMENT Argument, + _Out_writes_(ValueArrayCount) ULONG * ValueArray, + _In_ ULONG ValueArrayCount, + _Out_ PULONG PropertyValueArrayCount + ) + { + NTSTATUS status = STATUS_SUCCESS; + PACPI_METHOD_ARGUMENT currentArgument; + ULONG argumentIndex; + + DrvLogEnter(s_AcpiReaderLog); + + PAGED_CODE(); + + if (Argument->Type != ACPI_METHOD_ARGUMENT_PACKAGE) + { + status = STATUS_INVALID_PARAMETER; + DrvLogError(s_AcpiReaderLog, FLAG_INIT, "%!FUNC! Unexpected Argument Type. Expected %lu, Actual %lu, %!STATUS!", ACPI_METHOD_ARGUMENT_PACKAGE, Argument->Type, status); + ASSERT(FALSE); + goto exit; + } + + // Initialize everything to 0 + for (ULONG i = 0; i < ValueArrayCount; ++i) + { + ValueArray[i] = 0; + } + + *PropertyValueArrayCount = 0; + currentArgument = (PACPI_METHOD_ARGUMENT)Argument->Data; + + for (argumentIndex = 0; (PUCHAR)currentArgument < (PUCHAR)Argument->Data + Argument->DataLength; argumentIndex++) + { +#pragma prefast(suppress:26014, "Incorrect Validation: ACPI driver returns well-formed data that doesn't extend past known length.") + if (currentArgument->Type != ACPI_METHOD_ARGUMENT_INTEGER) + { + status = STATUS_INVALID_PARAMETER; + DrvLogError(s_AcpiReaderLog, FLAG_INIT, "%!FUNC! Unexpected argument in an array, %!STATUS!", status); + ASSERT(FALSE); + goto exit; + } + + (*PropertyValueArrayCount)++; + currentArgument = ACPI_METHOD_NEXT_ARGUMENT(currentArgument); + } + + if (ValueArrayCount == 0) + { + status = STATUS_BUFFER_TOO_SMALL; + goto exit; + } + else if (ValueArrayCount < *PropertyValueArrayCount) + { + status = STATUS_BUFFER_TOO_SMALL; + DrvLogError(s_AcpiReaderLog, FLAG_INIT, "%!FUNC! Output array too small. Required elements %lu, Actual %lu, %!STATUS!", *PropertyValueArrayCount, ValueArrayCount, status); + goto exit; + } + + currentArgument = (PACPI_METHOD_ARGUMENT)Argument->Data; + for (argumentIndex = 0; (PUCHAR)currentArgument < (PUCHAR)Argument->Data + Argument->DataLength && argumentIndex < ValueArrayCount; argumentIndex++) + { + status = ParseULong(currentArgument, &ValueArray[argumentIndex]); + if (!NT_SUCCESS(status)) + { + DrvLogError(s_AcpiReaderLog, FLAG_INIT, "%!FUNC! Unexpected argument in an array, %!STATUS!", status); + ASSERT(FALSE); + goto exit; + } + + currentArgument = ACPI_METHOD_NEXT_ARGUMENT(currentArgument); + } + + exit: + DrvLogExit(s_AcpiReaderLog); + return status; + } + + _Use_decl_annotations_ + PAGED_CODE_SEG + NTSTATUS + AcpiReader::EnumChildren( + _Out_ WDFMEMORY * EnumChildrenOutput + ) + /*++ + Routine Description: + + This function sends IOCTL_ACPI_ENUM_CHILDREN to ACPI to enumerate child devices. + + Arguments: + + EnumChildrenOutput - Supplies a resulting memory object. + + Return Value: + + NTSTATUS code. + + --*/ + { + NTSTATUS status; + WDFMEMORY inputMem{ WDF_NO_HANDLE }; + PACPI_ENUM_CHILDREN_INPUT_BUFFER inputBuf; + size_t inputBufSize; + WDF_MEMORY_DESCRIPTOR inputMemDesc; + WDFMEMORY outputMem{ WDF_NO_HANDLE }; + PACPI_ENUM_CHILDREN_OUTPUT_BUFFER outputBuf; + size_t outputBufSize; + WDF_MEMORY_DESCRIPTOR outputMemDesc; + WDF_OBJECT_ATTRIBUTES attr; + ULONG attempts; + WDFIOTARGET acpiIoTarget; + WDF_REQUEST_SEND_OPTIONS sendOptions; + + PAGED_CODE(); + + DrvLogEnter(s_AcpiReaderLog); + + ASSERT(m_AcpiDevice); + + acpiIoTarget = WdfDeviceGetIoTarget(m_AcpiDevice); + + WDF_OBJECT_ATTRIBUTES_INIT(&attr); + attr.ParentObject = m_AcpiDevice; + + inputBufSize = sizeof(*inputBuf); + status = WdfMemoryCreate( + &attr, + NonPagedPoolNx, + s_MemoryTag, + inputBufSize, + &inputMem, + (PVOID*)&inputBuf); + + if (!NT_SUCCESS(status)) + { + DrvLogError(s_AcpiReaderLog, FLAG_INIT, "%!FUNC! WdfMemoryCreate failed for inputBuf, %!STATUS!", status); + ASSERT(FALSE); + goto exit; + } + + RtlZeroMemory(inputBuf, inputBufSize); + inputBuf->Signature = ACPI_ENUM_CHILDREN_INPUT_BUFFER_SIGNATURE; + inputBuf->Flags = ENUM_CHILDREN_IMMEDIATE_ONLY; + + WDF_MEMORY_DESCRIPTOR_INIT_HANDLE(&inputMemDesc, inputMem, nullptr); + + // + // The initial output buffer allows one child only. It will be re-allocated + // with the returning "NumberOfChildren" bytes when IOCTL_ACPI_ENUM_CHILDREN + // fails with STATUS_BUFFER_OVERFLOW. The returning "NumberOfChildren" is + // not the number of children, but the required size in bytes. + // + outputBufSize = sizeof(*outputBuf); + attempts = 0; + + do + { + WDF_OBJECT_ATTRIBUTES_INIT(&attr); + attr.ParentObject = m_AcpiDevice; + + status = WdfMemoryCreate( + &attr, + NonPagedPoolNx, + s_MemoryTag, + outputBufSize, + &outputMem, + (PVOID*)&outputBuf); + + if (!NT_SUCCESS(status)) + { + DrvLogError(s_AcpiReaderLog, FLAG_INIT, "%!FUNC! WdfMemoryCreate failed for outputBuf, %!STATUS!", status); + ASSERT(FALSE); + goto exit; + } + + WDF_MEMORY_DESCRIPTOR_INIT_HANDLE(&outputMemDesc, outputMem, nullptr); + + WDF_REQUEST_SEND_OPTIONS_INIT(&sendOptions, 0); + WDF_REQUEST_SEND_OPTIONS_SET_TIMEOUT(&sendOptions, WDF_REL_TIMEOUT_IN_SEC(ACPI_REQUEST_TIMEOUT_SEC)); + + status = WdfIoTargetSendIoctlSynchronously( + acpiIoTarget, + NULL, + IOCTL_ACPI_ENUM_CHILDREN, + &inputMemDesc, + &outputMemDesc, + &sendOptions, + nullptr); + + if (NT_SUCCESS(status)) + { + if (outputBuf->Signature != ACPI_ENUM_CHILDREN_OUTPUT_BUFFER_SIGNATURE) + { + status = STATUS_ACPI_INVALID_DATA; + DrvLogError(s_AcpiReaderLog, FLAG_INIT, "%!FUNC! Invalid data in ACPI_ENUM_CHILDREN_OUTPUT_BUFFER, %!STATUS!", status); + ASSERT(FALSE); + goto exit; + } + + // + // There must be at least one, because this device is included in the list. + // When IOCTL_ACPI_ENUM_CHILDREN succeeds, "NumberOfChildren" does have + // the number of children. (When the IOCTL fails, it's the required size + // in bytes.) + // + if (outputBuf->NumberOfChildren < 1) + { + status = STATUS_ACPI_INVALID_DATA; + DrvLogError(s_AcpiReaderLog, FLAG_INIT, "%!FUNC! No child devices in ACPI_ENUM_CHILDREN_OUTPUT_BUFFER, %!STATUS!", status); + ASSERT(FALSE); + goto exit; + } + + // + // Return the output memory object. + // + *EnumChildrenOutput = outputMem; + outputMem = WDF_NO_HANDLE; + + break; + } + + if (status != STATUS_BUFFER_OVERFLOW) + { + DrvLogError(s_AcpiReaderLog, FLAG_INIT, "%!FUNC! IOCTL_ACPI_ENUM_CHILDREN _BUFFER, %!STATUS!", status); + // No assert since this is common in sdca bringup + goto exit; + } + + if (outputBuf->Signature != ACPI_ENUM_CHILDREN_OUTPUT_BUFFER_SIGNATURE) + { + status = STATUS_ACPI_INVALID_DATA; + DrvLogError(s_AcpiReaderLog, FLAG_INIT, "%!FUNC! Invalid data in ACPI_ENUM_CHILDREN_OUTPUT_BUFFER, %!STATUS!", status); + ASSERT(FALSE); + goto exit; + } + + // + // When IOCTL_ACPI_ENUM_CHILDREN fails with STATUS_BUFFER_OVERFLOW, + // "NumberOfChildren" is not the number of children, but the required + // size in bytes. + // + outputBufSize = outputBuf->NumberOfChildren; + WdfObjectDelete(outputMem); + outputMem = WDF_NO_HANDLE; + attempts++; + } while (attempts < 2); + + exit: + + if (inputMem != WDF_NO_HANDLE) + { + WdfObjectDelete(inputMem); + inputMem = WDF_NO_HANDLE; + } + + if (outputMem != WDF_NO_HANDLE) + { + WdfObjectDelete(outputMem); + outputMem = WDF_NO_HANDLE; + } + + DrvLogExit(s_AcpiReaderLog); + return status; + } + + _Use_decl_annotations_ + PAGED_CODE_SEG + NTSTATUS + AcpiReader::EvaluateMethod( + _In_ LPCSTR MethodName, + _Out_ WDFMEMORY * ReturnMemory + ) + /*++ + Routine Description: + + This function sends IOCTL_ACPI_EVAL_METHOD_EX to ACPI to evaluate a method. + + Arguments: + + MethodName - Supplies a packed string identifying the method. + + ReturnMemory - Supplies the resulting memory object. + + Return Value: + + NTSTATUS code. + + --*/ + { + const ULONG InitialControlMethodOutputSize = 0x200; // 512 bytes + UCHAR attempts; + WDF_MEMORY_DESCRIPTOR inputDesc; + WDFMEMORY outputMem{ WDF_NO_HANDLE }; + PACPI_EVAL_OUTPUT_BUFFER outputBuf; + ULONG outputBufLength; + WDF_MEMORY_DESCRIPTOR outputDesc; + ULONG_PTR sizeReturned; + ACPI_EVAL_INPUT_BUFFER_EX inputBuf; + WDF_OBJECT_ATTRIBUTES attr; + WDFIOTARGET acpiIoTarget; + NTSTATUS status; + WDF_REQUEST_SEND_OPTIONS sendOptions; + + DrvLogEnter(s_AcpiReaderLog); + + PAGED_CODE(); + + ASSERT(m_AcpiDevice); + + acpiIoTarget = WdfDeviceGetIoTarget(m_AcpiDevice); + + // + // Prepare an input buffer. + // + inputBuf.Signature = ACPI_EVAL_INPUT_BUFFER_SIGNATURE_EX; + + status = RtlStringCchCopyA( + inputBuf.MethodName, + sizeof(inputBuf.MethodName), + MethodName); + + if (!NT_SUCCESS(status)) + { + DrvLogError(s_AcpiReaderLog, FLAG_INIT, "%!FUNC! RtlStringCchCopyA failed to copy ACPI method name, %!STATUS!", status); + ASSERT(FALSE); + goto exit; + } + + WDF_MEMORY_DESCRIPTOR_INIT_BUFFER( + &inputDesc, + (PVOID)&inputBuf, + sizeof(ACPI_EVAL_INPUT_BUFFER_EX)); + + // + // Set the initial size for the output buffer to be allocated. + // + outputBuf = NULL; + outputBufLength = InitialControlMethodOutputSize; + attempts = 0; + + do + { + WDF_OBJECT_ATTRIBUTES_INIT(&attr); + attr.ParentObject = m_AcpiDevice; + + status = WdfMemoryCreate( + &attr, + NonPagedPoolNx, + s_MemoryTag, + outputBufLength, + &outputMem, + (PVOID*)&outputBuf); + + if (!NT_SUCCESS(status)) + { + DrvLogError(s_AcpiReaderLog, FLAG_INIT, "%!FUNC! WdfMemoryCreate failed for %Iu bytes, %!STATUS!", outputBufLength, status); + ASSERT(FALSE); + goto exit; + } + + RtlZeroMemory(outputBuf, outputBufLength); + + WDF_MEMORY_DESCRIPTOR_INIT_BUFFER( + &outputDesc, + (PVOID)outputBuf, + outputBufLength); + + WDF_REQUEST_SEND_OPTIONS_INIT(&sendOptions, 0); + WDF_REQUEST_SEND_OPTIONS_SET_TIMEOUT(&sendOptions, WDF_REL_TIMEOUT_IN_SEC(ACPI_REQUEST_TIMEOUT_SEC)); + + status = WdfIoTargetSendIoctlSynchronously( + acpiIoTarget, + NULL, + IOCTL_ACPI_EVAL_METHOD_EX, + &inputDesc, + &outputDesc, + &sendOptions, + &sizeReturned); + + if (NT_SUCCESS(status)) + { + // + // IOCTL_ACPI_EVAL_METHOD_EX succeeded. + // + if (sizeReturned == 0) + { + status = STATUS_UNSUCCESSFUL; + DrvLogError(s_AcpiReaderLog, FLAG_INIT, "%!FUNC! IOCTL_ACPI_EVAL_METHOD_EX returned 0 byte, %!STATUS!", status); + ASSERT(FALSE); + goto exit; + } + + if (outputBuf->Signature != ACPI_EVAL_OUTPUT_BUFFER_SIGNATURE) + { + status = STATUS_ACPI_INVALID_DATA; + DrvLogError(s_AcpiReaderLog, FLAG_INIT, "%!FUNC! ACPI_EVAL_OUTPUT_BUFFER signature (0x%x) is incorrect, %!STATUS!", outputBuf->Signature, status); + ASSERT(FALSE); + goto exit; + } + + // + // Return the output memory object. + // + *ReturnMemory = outputMem; + outputMem = WDF_NO_HANDLE; + + break; + } + + if (status != STATUS_BUFFER_OVERFLOW) + { + DrvLogError(s_AcpiReaderLog, FLAG_INIT, "!FUNC! IOCTL_ACPI_EVAL_METHOD_EX failed , %!STATUS!", status); + // Failure is common when used alongside virtual stack + goto exit; + } + + // + // If the output buffer was insufficient, then re-allocate one with + // appropriate size and retry. + // + outputBufLength = outputBuf->Length; + WdfObjectDelete(outputMem); + outputMem = WDF_NO_HANDLE; + attempts++; + + if (attempts == 2) + { + DrvLogError(s_AcpiReaderLog, FLAG_INIT, "%!FUNC! IOCTL_ACPI_EVAL_METHOD_EX has failed for %u times. Stopped retrying., %!STATUS!", attempts, status); + ASSERT(FALSE); + } + } while (attempts < 2); + + exit: + + if (outputMem != WDF_NO_HANDLE) + { + WdfObjectDelete(outputMem); + outputMem = WDF_NO_HANDLE; + } + + DrvLogExit(s_AcpiReaderLog); + + return status; + } + + _Use_decl_annotations_ + PAGED_CODE_SEG + NTSTATUS + AcpiReader::EvaluateAdr( + _In_opt_ LPCSTR ChildDeviceName, + _Out_ PULONGLONG Address + ) + /*++ + Routine Description: + + This function evaluates a _ADR method. + + Arguments: + + ChildDeviceName - Supplies a child device name. If Null, evaluate + the _ADR for the current device instead. + + Address - Returning the device address from _ADR. + + Return Value: + + NTSTATUS code. + + --*/ + { + NTSTATUS status; + CHAR fullMethodName[MAX_PATH]; + WDFMEMORY outputMem{ WDF_NO_HANDLE }; + PACPI_EVAL_OUTPUT_BUFFER outputBuf; + + DrvLogEnter(s_AcpiReaderLog); + + PAGED_CODE(); + + if (!Address) + { + status = STATUS_INVALID_PARAMETER; + goto exit; + } + + if (ChildDeviceName != nullptr) + { + status = RtlStringCchPrintfA( + fullMethodName, + sizeof(fullMethodName), + "%s._ADR", + ChildDeviceName); + } + else + { + status = RtlStringCchCopyA( + fullMethodName, + sizeof(fullMethodName), + "_ADR"); + } + + if (!NT_SUCCESS(status)) + { + DrvLogError(s_AcpiReaderLog, FLAG_INIT, "%!FUNC! RtlStringCchPrintfA for creating method name _BUFFER, %!STATUS!", status); + ASSERT(FALSE); + goto exit; + } + + status = EvaluateMethod( + fullMethodName, + &outputMem); + + if (!NT_SUCCESS(status)) + { + DrvLogError(s_AcpiReaderLog, FLAG_INIT, "%!FUNC! EvaluateMethod failed on [%s], %!STATUS!", fullMethodName, status); + ASSERT(FALSE); + goto exit; + } + + outputBuf = (PACPI_EVAL_OUTPUT_BUFFER)WdfMemoryGetBuffer(outputMem, NULL); + + if (outputBuf->Count < 1) + { + status = STATUS_ACPI_INVALID_DATA; + DrvLogError(s_AcpiReaderLog, FLAG_INIT, "%!FUNC! _ADR of [%s] didn't return anything, %!STATUS!", fullMethodName, status); + ASSERT(FALSE); + goto exit; + } + + if (outputBuf->Argument[0].Type != ACPI_METHOD_ARGUMENT_INTEGER) + { + status = STATUS_ACPI_INVALID_DATA; + DrvLogError(s_AcpiReaderLog, FLAG_INIT, "%!FUNC! _ADR of [%s] returned an unexpected argument of type %hu, %!STATUS!", fullMethodName, outputBuf->Argument[0].Type, status); + ASSERT(FALSE); + goto exit; + } + + status = ParseULongLong(outputBuf->Argument, Address); + + if (!NT_SUCCESS(status)) + { + DrvLogError(s_AcpiReaderLog, FLAG_INIT, "%!FUNC! Unexpected argument from _ADR of [%s], %!STATUS!", fullMethodName, status); + ASSERT(FALSE); + goto exit; + } + + exit: + if (outputMem != WDF_NO_HANDLE) + { + WdfObjectDelete(outputMem); + outputMem = WDF_NO_HANDLE; + } + + DrvLogExit(s_AcpiReaderLog); + return status; + } + + _Use_decl_annotations_ + PAGED_CODE_SEG + NTSTATUS + AcpiReader::EvaluateAdr( + _Out_ PULONGLONG Address + ) + /*++ + Routine Description: + + This function evaluates the _ADR method for the current device. + + Arguments: + + Address - Returning the device address from _ADR. + + Return Value: + + NTSTATUS code. + + --*/ + + { + NTSTATUS status; + + DrvLogEnter(s_AcpiReaderLog); + + PAGED_CODE(); + + status = EvaluateAdr(nullptr, Address); + + DrvLogExit(s_AcpiReaderLog); + return status; + } + + + __drv_maxIRQL(PASSIVE_LEVEL) + PAGED_CODE_SEG + NTSTATUS + AcpiReader::GetPropertyString( + _In_ LPCSTR PropertyName, + _In_ ACPI_METHOD_SECTION PropertySection, + _In_ WDFMEMORY AcpiEvalOutputBuf, + _Out_writes_opt_z_(ValueStringSize) char * ValueString, + _In_ ULONG ValueStringSize, + _Out_ PULONG PropertyValueSize + ) + /*++ + Routine Description: + + This function searches for PropertyName in ACPI_EVAL_OUTPUT_BUFFER and returns + string value for the property if found. + + Arguments: + + PropertyName - Property name to search for. + + PropertySection - Specifies if property is under device property or hierarchical + data extension section. + + AcpiEvalOutputBuf - WDFMEMORY containing ACPI_EVAL_OUTPUT_BUFFER in + which property needs to be searched. + + ValueString - Buffer that will hold property value if found. + + ValueStringSize - Size of the output buffer. + + PropertyValueSize - Actual length of the property value. + + Return Value: + + NTSTATUS code. + + --*/ + { + NTSTATUS status = STATUS_NOT_FOUND; + PACPI_METHOD_ARGUMENT propValueArg = NULL; + + DrvLogEnter(s_AcpiReaderLog); + + PAGED_CODE(); + + if (!PropertyName || !PropertyValueSize || (AcpiEvalOutputBuf == WDF_NO_HANDLE)) + { + status = STATUS_INVALID_PARAMETER; + DrvLogError(s_AcpiReaderLog, FLAG_INIT, "%!FUNC! Invalid PropertyName, PropertyValueSize or AcpiEvalOutputBuf, %!STATUS!", status); + goto exit; + } + if (ValueStringSize > 0 && !ValueString) + { + status = STATUS_INVALID_PARAMETER; + DrvLogError(s_AcpiReaderLog, FLAG_INIT, "%!FUNC! Null ValueString with non-zero ValueStringSize, %!STATUS!", status); + goto exit; + } + + status = GetProperty(PropertyName, PropertySection, AcpiEvalOutputBuf, &propValueArg); + + if (NT_SUCCESS(status)) + { + status = ParseString(propValueArg, ValueString, ValueStringSize, PropertyValueSize); + } + + exit: + return status; + } + + __drv_maxIRQL(PASSIVE_LEVEL) + PAGED_CODE_SEG + NTSTATUS + AcpiReader::GetPropertyULongLong( + _In_ LPCSTR PropertyName, + _In_ ACPI_METHOD_SECTION PropertySection, + _In_ WDFMEMORY AcpiEvalOutputBuf, + _Out_ PULONGLONG PropertyValue + ) + /*++ + Routine Description: + + This function searches for PropertyName in ACPI_EVAL_OUTPUT_BUFFER and returns + ULONGLONG value for the property if found. + + Arguments: + + PropertyName - Property name to search for. + + PropertySection - Specifies if property is under device property or hierarchical + data extension section. + + AcpiEvalOutputBuf - WDFMEMORY containing ACPI_EVAL_OUTPUT_BUFFER in + which property needs to be searched. + + PropertyValue - Value of the property. + + Return Value: + + NTSTATUS code. + + --*/ + { + NTSTATUS status = STATUS_NOT_FOUND; + PACPI_METHOD_ARGUMENT propValueArg = NULL; + + DrvLogEnter(s_AcpiReaderLog); + + PAGED_CODE(); + + if (!PropertyName || !PropertyValue || (AcpiEvalOutputBuf == WDF_NO_HANDLE)) + { + status = STATUS_INVALID_PARAMETER; + DrvLogError(s_AcpiReaderLog, FLAG_INIT, "%!FUNC! Invalid PropertyName, PropertyValue or AcpiEvalOutputBuf, %!STATUS!", status); + goto exit; + } + + status = GetProperty(PropertyName, PropertySection, AcpiEvalOutputBuf, &propValueArg); + + if (NT_SUCCESS(status)) + { + status = ParseULongLong(propValueArg, PropertyValue); + } + + exit: + return status; + } + + __drv_maxIRQL(PASSIVE_LEVEL) + PAGED_CODE_SEG + NTSTATUS + AcpiReader::GetPropertyULong( + _In_ LPCSTR PropertyName, + _In_ ACPI_METHOD_SECTION PropertySection, + _In_ WDFMEMORY AcpiEvalOutputBuf, + _Out_ PULONG PropertyValue + ) + /*++ + Routine Description: + + This function searches for PropertyName in ACPI_EVAL_OUTPUT_BUFFER and returns + ULONG value for the property if found. + + Arguments: + + PropertyName - Property name to search for. + + PropertySection - Specifies if property is under device property or hierarchical + data extension section. + + AcpiEvalOutputBuf - WDFMEMORY containing ACPI_EVAL_OUTPUT_BUFFER in + which property needs to be searched. + + PropertyValue - Value of the property. + + Return Value: + + NTSTATUS code. + + --*/ + { + NTSTATUS status = STATUS_NOT_FOUND; + PACPI_METHOD_ARGUMENT propValueArg = NULL; + + DrvLogEnter(s_AcpiReaderLog); + + PAGED_CODE(); + + if (!PropertyName || !PropertyValue || (AcpiEvalOutputBuf == WDF_NO_HANDLE)) + { + status = STATUS_INVALID_PARAMETER; + DrvLogError(s_AcpiReaderLog, FLAG_INIT, "%!FUNC! Invalid PropertyName, PropertyValue or AcpiEvalOutputBuf, %!STATUS!", status); + goto exit; + } + + status = GetProperty(PropertyName, PropertySection, AcpiEvalOutputBuf, &propValueArg); + + if (NT_SUCCESS(status)) + { + status = ParseULong(propValueArg, PropertyValue); + } + + exit: + DrvLogExit(s_AcpiReaderLog); + + return status; + } + + __drv_maxIRQL(PASSIVE_LEVEL) + PAGED_CODE_SEG + NTSTATUS + AcpiReader::GetPropertyBuffer( + _In_ LPCSTR PropertyName, + _In_ WDFMEMORY AcpiEvalOutputBuf, + _Out_writes_bytes_(ValueBufferSize) PVOID ValueBuffer, + _In_ ULONG ValueBufferSize, + _Out_ PULONG PropertyValueSize + ) + /*++ + Routine Description: + + This function searches for PropertyName in ACPI_EVAL_OUTPUT_BUFFER and returns + buffer value for the property if found. + + Arguments: + + PropertyName - Property name to search for. + + AcpiEvalOutputBuf - WDFMEMORY containing ACPI_EVAL_OUTPUT_BUFFER in + which property needs to be searched. + + ValueBuffer - Buffer that will hold property value if found. + + ValueBufferSize - Size of the output buffer. + + PropertyValueSize - Actual length of the property value. + + Return Value: + + NTSTATUS code. + + --*/ + { + NTSTATUS status = STATUS_NOT_FOUND; + PACPI_METHOD_ARGUMENT propValueArg = NULL; + char methodName[MAX_PATH]; + ULONG valueSize = 0; + WDFMEMORY bufferBlock = nullptr; + PACPI_EVAL_OUTPUT_BUFFER buffer; + + DrvLogEnter(s_AcpiReaderLog); + + PAGED_CODE(); + + if (!PropertyName || !PropertyValueSize || (AcpiEvalOutputBuf == WDF_NO_HANDLE)) + { + status = STATUS_INVALID_PARAMETER; + DrvLogError(s_AcpiReaderLog, FLAG_INIT, "%!FUNC! Invalid PropertyName, PropertyValueSize or AcpiEvalOutputBuf, %!STATUS!", status); + goto exit; + } + // Property we are searching is expected to return a buffer, + // so this property will be under Buffer UUID section + // ToUUID("EDB12DD0-363D-4085-A3D2-49522CA160C4"), + // Package() { + // Package { Property, "BUF0"} + // } + status = GetProperty(PropertyName, ACPI_METHOD_SECTION_BUFFER, AcpiEvalOutputBuf, &propValueArg); + + if (!NT_SUCCESS(status)) + { + // No need to log an error as it may be an optional property and not expected to be present all the time. + goto exit; + } + + // Value of the property will be method name + status = ParseString(propValueArg, methodName, sizeof(methodName), &valueSize); + if (!NT_SUCCESS(status)) + { + DrvLogError(s_AcpiReaderLog, FLAG_INIT, "%!FUNC! Failed to retrieve method name for %hs, %!STATUS!", PropertyName, status); + goto exit; + } + + // Evaluate method which will return contents of the buffer + // e.g. Evaluate method "BUF0" + status = EvaluateMethod(methodName, &bufferBlock); + if (!NT_SUCCESS(status)) + { + DrvLogError(s_AcpiReaderLog, FLAG_INIT, "%!FUNC! Failed to evaluate method %hs, %!STATUS!", + methodName, + status); + goto exit; + } + + buffer = (PACPI_EVAL_OUTPUT_BUFFER)WdfMemoryGetBuffer(bufferBlock, NULL); + // This method must contain only one ACPI argument + if (buffer->Count != 1) + { + status = STATUS_INVALID_PARAMETER; + DrvLogError(s_AcpiReaderLog, FLAG_INIT, "%!FUNC! Method %hs has argument count %d, expected 1, %!STATUS!", + methodName, + buffer->Count, + status); + goto exit; + } + + status = ParseBuffer(buffer->Argument, ValueBuffer, ValueBufferSize, PropertyValueSize); + + exit: + if (bufferBlock != nullptr) + { + WdfObjectDelete(bufferBlock); + bufferBlock = nullptr; + } + + DrvLogExit(s_AcpiReaderLog); + + return status; + } + + __drv_maxIRQL(PASSIVE_LEVEL) + PAGED_CODE_SEG + NTSTATUS + AcpiReader::GetPropertyULongArray( + _In_ LPCSTR PropertyName, + _In_ ACPI_METHOD_SECTION PropertySection, + _In_ WDFMEMORY AcpiEvalOutputBuf, + _Out_writes_(ValueArrayCount) ULONG * ValueArray, + _In_ ULONG ValueArrayCount, + _Out_ PULONG PropertyValueArrayCount + ) + /*++ + Routine Description: + + This function searches for PropertyName in ACPI_EVAL_OUTPUT_BUFFER and returns + an array of ULONGs for the property if found. + + Arguments: + + PropertyName - Property name to search for. + + PropertySection - Specifies if property is under device property or hierarchical + data extension section. + + AcpiEvalOutputBuf - WDFMEMORY containing ACPI_EVAL_OUTPUT_BUFFER in + which property needs to be searched. + + ValueArray - ULONG array that will hold property values if found. + + ValueArrayCount - Total count of elements in ValueArray. + + PropertyValueArrayCount - Valid count of elements in ValueArray. + + Return Value: + + NTSTATUS code. + + --*/ + { + NTSTATUS status = STATUS_NOT_FOUND; + PACPI_METHOD_ARGUMENT PropValueArg = NULL; + + DrvLogEnter(s_AcpiReaderLog); + + PAGED_CODE(); + + if (!PropertyName || !PropertyValueArrayCount || (AcpiEvalOutputBuf == WDF_NO_HANDLE)) + { + status = STATUS_INVALID_PARAMETER; + DrvLogError(s_AcpiReaderLog, FLAG_INIT, "%!FUNC! Invalid PropertyName, PropertyValueArrayCount or AcpiEvalOutputBuf, %!STATUS!", status); + goto exit; + } + + status = GetProperty(PropertyName, PropertySection, AcpiEvalOutputBuf, &PropValueArg); + + if (NT_SUCCESS(status)) + { + status = ParseULongArray(PropValueArg, ValueArray, ValueArrayCount, PropertyValueArrayCount); + } + + exit: + DrvLogExit(s_AcpiReaderLog); + + return status; + } + + _Use_decl_annotations_ + PAGED_CODE_SEG + NTSTATUS + AcpiReader::GetProperty( + _In_ LPCSTR PropertyName, + _In_ ACPI_METHOD_SECTION PropertySection, + _In_ WDFMEMORY AcpiEvalOutputBuf, + _Outptr_result_maybenull_ PACPI_METHOD_ARGUMENT * PropertyValue + ) + /*++ + Routine Description: + + This function searches for PropertyName in ACPI_EVAL_OUTPUT_BUFFER and returns + ACPI_METHOD_ARGUMENT for the property if found. + + Arguments: + + PropertyName - Property name to search for. + + PropertySection - Specifies if property is under device property, hierarchical + data extension or buffer section. + + AcpiEvalOutputBuf - WDFMEMORY containing ACPI_EVAL_OUTPUT_BUFFER in + which property needs to be searched. + + PropertyValue - ACPI_MEDHOD_ARGUMENT pointer to property value if the property was found. + + Return Value: + + NTSTATUS code. + + --*/ + { + NTSTATUS status = STATUS_NOT_FOUND; + PACPI_EVAL_OUTPUT_BUFFER Buffer; + PACPI_METHOD_ARGUMENT currentArgument; + ULONG argumentIndex; + GUID guid; + ACPI_METHOD_SECTION section = ACPI_METHOD_SECTION_UNKNOWN; + BOOL found = FALSE; + size_t PropertyLength; + size_t BufferLength; + + DrvLogEnter(s_AcpiReaderLog); + + PAGED_CODE(); + + Buffer = (PACPI_EVAL_OUTPUT_BUFFER)WdfMemoryGetBuffer(AcpiEvalOutputBuf, &BufferLength); + + if (BufferLength < FIELD_OFFSET(ACPI_EVAL_OUTPUT_BUFFER, Argument)) + { + status = STATUS_INVALID_PARAMETER; + DrvLogError(s_AcpiReaderLog, FLAG_INIT, "%!FUNC! WdfMemoryBuffer length %llu too short, %!STATUS!", BufferLength, status); + ASSERT(FALSE); + return status; + } + + if (Buffer->Length > BufferLength) + { + status = STATUS_INVALID_PARAMETER; + DrvLogError(s_AcpiReaderLog, FLAG_INIT, "%!FUNC! ACPI_EVAL_OUTPUT_BUFFER length %lu exceeds WdfMemoryBuffer length %llu, %!STATUS!", Buffer->Length, BufferLength, status); + ASSERT(FALSE); + return status; + } + + // Method structure + // Name (Method, Package() { + // ToUUID("DAFFD814-6EBA-4D8C-8A91-BC9BBF4AA301"), + // Package () { + // Package (2) { Property, Value } + // : + // Package (2) { Property, Value } + // }, + // ToUUID("DBB8E3E6-5886-4BA6-8795-1319F52A966B"), + // Package() { + // Package { Property, Sub-Package} + // : + // Package { Property, Sub-Package} + // } + // ToUUID("EDB12DD0-363D-4085-A3D2-49522CA160C4"), + // Package() { + // Package { Property, Sub-Package} + // : + // Package { Property, Sub-Package} + // } + // } + + PropertyLength = strlen(PropertyName) + 1; // Add one for NULL terminator as ACPI_METHOD_ARGUMENT Datalength includes it. + + currentArgument = ACPI_EVAL_OUTPUT_BUFFER_ARGUMENTS_BEGIN(Buffer); + for (argumentIndex = 0; argumentIndex < Buffer->Count && !found; argumentIndex++) + { + if (((PUCHAR)currentArgument + ACPI_METHOD_ARGUMENT_LENGTH(0) > (PUCHAR)ACPI_EVAL_OUTPUT_BUFFER_ARGUMENTS_END(Buffer)) || + ((PUCHAR)currentArgument + ACPI_METHOD_ARGUMENT_LENGTH_FROM_ARGUMENT(currentArgument) > (PUCHAR)ACPI_EVAL_OUTPUT_BUFFER_ARGUMENTS_END(Buffer))) + { + status = STATUS_INVALID_PARAMETER; + DrvLogError(s_AcpiReaderLog, FLAG_INIT, "%!FUNC! ACPI_METHOD_ARGUMENT outside of ACPI_EVAL_OUTPUT_BUFFER length, %!STATUS!", status); + ASSERT(FALSE); + return status; + } + + switch (currentArgument->Type) + { + case ACPI_METHOD_ARGUMENT_BUFFER: + + status = ParseGuid(currentArgument, &guid, sizeof(GUID)); + if (NT_SUCCESS(status) && guid == DSD_DEVICE_PROPERTIES_GUID) + { + section = ACPI_METHOD_SECTION_DEVICE_PROPERTIES; + } + else if (NT_SUCCESS(status) && guid == DSD_HIERARCHICAL_DATA_EXTENSION_GUID) + { + section = ACPI_METHOD_SECTION_HIERARCHICAL_DATA_EXTENSION; + } + else if (NT_SUCCESS(status) && guid == DSD_BUFFER_GUID) + { + section = ACPI_METHOD_SECTION_BUFFER; + } + else + { + DrvLogError(s_AcpiReaderLog, FLAG_INIT, "%!FUNC! Skipping unexpected ACPI_METHOD_ARGUMENT_BUFFER argument, %!STATUS!", status); + ASSERT(FALSE); + section = ACPI_METHOD_SECTION_UNKNOWN; + } + break; + + case ACPI_METHOD_ARGUMENT_PACKAGE: + + // Caller specified the section in which to search property + // so further search only if this package is under that section + if (section == PropertySection) + { + // Parse sub-packages + status = ParsePropertiesPackage(PropertyName, PropertyLength, currentArgument, PropertyValue); + if (NT_SUCCESS(status)) + { + found = TRUE; + } + } + break; + + default: + DrvLogError(s_AcpiReaderLog, FLAG_INIT, "%!FUNC! Skipping unexpected argument[%d] of type[%d]", argumentIndex, currentArgument->Type); + break; + } + currentArgument = ACPI_METHOD_NEXT_ARGUMENT(currentArgument); + } + + if (!found) + { + status = STATUS_NOT_FOUND; + } + + DrvLogExit(s_AcpiReaderLog); + return status; + } + + _Use_decl_annotations_ + PAGED_CODE_SEG + NTSTATUS + AcpiReader::ParsePropertiesPackage( + _In_ LPCSTR PropertyName, + _In_ size_t PropertyLength, + _In_ PACPI_METHOD_ARGUMENT Package, + _Outptr_result_maybenull_ PACPI_METHOD_ARGUMENT * PropertyValue + ) + /*++ + Routine Description: + + This function searches for PropertyName in a package that contains + property packages. + + Arguments: + + PropertyName - Property name to search for. + + Package - Pointer to ACPI_METHOD_ARGUMENT containing package under + device property or hierarchical data extension section. + + PropertyValue - ACPI_MEDHOD_ARGUMENT pointer to property value if the property was found. + + Return Value: + + NTSTATUS code. + --*/ + { + NTSTATUS status = STATUS_NOT_FOUND; + PACPI_METHOD_ARGUMENT currentArgument; + ULONG argumentIndex; + BOOL found = FALSE; + + DrvLogEnter(s_AcpiReaderLog); + + PAGED_CODE(); + + // Package structure + // Package () { + // Package (2) { Property, Value } + // : + // Package (2) { Property, Value } + // } + + if (Package->DataLength < ACPI_METHOD_ARGUMENT_LENGTH(0)) + { + status = STATUS_INVALID_PARAMETER; + DrvLogError(s_AcpiReaderLog, FLAG_INIT, "%!FUNC! Package ACPI_METHOD_ARGUMENT too small, %!STATUS!", status); + ASSERT(FALSE); + return status; + } + + currentArgument = (PACPI_METHOD_ARGUMENT)Package->Data; + for (argumentIndex = 0 ; ((PUCHAR)currentArgument < (PUCHAR)Package->Data + Package->DataLength) && !found; argumentIndex++) + { + if (((PUCHAR)currentArgument + ACPI_METHOD_ARGUMENT_LENGTH(0) > (PUCHAR)Package->Data + Package->DataLength) || + ((PUCHAR)currentArgument + ACPI_METHOD_ARGUMENT_LENGTH_FROM_ARGUMENT(currentArgument) > (PUCHAR)Package->Data + Package->DataLength)) + { + status = STATUS_INVALID_PARAMETER; + DrvLogError(s_AcpiReaderLog, FLAG_INIT, "%!FUNC! ACPI_METHOD_ARGUMENT outside of package length, %!STATUS!", status); + ASSERT(FALSE); + return status; + } + + switch (currentArgument->Type) + { + case ACPI_METHOD_ARGUMENT_PACKAGE: + status = FindProperty(PropertyName, PropertyLength, currentArgument, PropertyValue); + if (NT_SUCCESS(status)) + { + found = TRUE; + } + break; + + default: + DrvLogError(s_AcpiReaderLog, FLAG_INIT, "%!FUNC! Skipping unexpected argument[%d] of type[%d]", argumentIndex, currentArgument->Type); + break; + } + currentArgument = ACPI_METHOD_NEXT_ARGUMENT(currentArgument); + } + + if (!found) + { + status = STATUS_NOT_FOUND; + } + + DrvLogExit(s_AcpiReaderLog); + return status; + } + + _Use_decl_annotations_ + PAGED_CODE_SEG + NTSTATUS + AcpiReader::FindProperty( + _In_ LPCSTR PropertyName, + _In_ size_t PropertyLength, + _In_ PACPI_METHOD_ARGUMENT Package, + _Outptr_result_maybenull_ PACPI_METHOD_ARGUMENT * PropertyValue + ) + /*++ + Routine Description: + + This function searches for PropertyName in a package that contains + a property name and value. + + Arguments: + + PropertyName - Property name to search for. + + Package - Pointer to ACPI_METHOD_ARGUMENT containing package that has + property name and value. + + PropertyValue - ACPI_MEDHOD_ARGUMENT pointer to property value if the property was found. + + Return Value: + + NTSTATUS code. + --*/ + { + NTSTATUS status = STATUS_NOT_FOUND; + PACPI_METHOD_ARGUMENT propNameArgument; + PACPI_METHOD_ARGUMENT propValArgument; + + DrvLogEnter(s_AcpiReaderLog); + + PAGED_CODE(); + + // Property package structure + // Package (2) { Property, Value } + + propNameArgument = (PACPI_METHOD_ARGUMENT)Package->Data; + propValArgument = ACPI_METHOD_NEXT_ARGUMENT(propNameArgument); + + if (Package->DataLength < ACPI_METHOD_ARGUMENT_LENGTH(0)) + { + status = STATUS_INVALID_PARAMETER; + DrvLogError(s_AcpiReaderLog, FLAG_INIT, "%!FUNC! Package ACPI_METHOD_ARGUMENT too small, %!STATUS!", status); + ASSERT(FALSE); + return status; + } + + if ((PUCHAR)propNameArgument + ACPI_METHOD_ARGUMENT_LENGTH_FROM_ARGUMENT(propNameArgument) > + (PUCHAR)Package + ACPI_METHOD_ARGUMENT_LENGTH_FROM_ARGUMENT(Package)) + { + status = STATUS_INVALID_PARAMETER; + DrvLogError(s_AcpiReaderLog, FLAG_INIT, "%!FUNC! prop name ACPI_METHOD_ARGUMENT outside of package length, %!STATUS!", status); + ASSERT(FALSE); + return status; + } + + if (propNameArgument->Type == ACPI_METHOD_ARGUMENT_STRING) + { + if ((PropertyLength == propNameArgument->DataLength) && + !_strnicmp(PropertyName, (char*)propNameArgument->Data, propNameArgument->DataLength)) + { + if ((PUCHAR)propValArgument + ACPI_METHOD_ARGUMENT_LENGTH_FROM_ARGUMENT(propValArgument) > + (PUCHAR)Package + ACPI_METHOD_ARGUMENT_LENGTH_FROM_ARGUMENT(Package)) + { + status = STATUS_INVALID_PARAMETER; + DrvLogError(s_AcpiReaderLog, FLAG_INIT, "%!FUNC! prop val ACPI_METHOD_ARGUMENT outside of package length, %!STATUS!", status); + ASSERT(FALSE); + return status; + } + + *PropertyValue = propValArgument; + status = STATUS_SUCCESS; + } + } + + DrvLogExit(s_AcpiReaderLog); + return status; + } + + _Use_decl_annotations_ + PAGED_CODE_SEG + VOID + AcpiReader::FreeBuffer( + _Inout_ WDFMEMORY * AcpiEvalOutputBuf + ) + /*++ + Routine Description: + + This function frees memory object. + + Arguments: + + AcpiEvalOutputBuf - Memory object to be freed. + + Return Value: + + VOID + + --*/ + { + DrvLogEnter(s_AcpiReaderLog); + + PAGED_CODE(); + + ASSERT(*AcpiEvalOutputBuf); + if ((*AcpiEvalOutputBuf) != WDF_NO_HANDLE) + { + WdfObjectDelete(*AcpiEvalOutputBuf); + *AcpiEvalOutputBuf = WDF_NO_HANDLE; + } + + DrvLogExit(s_AcpiReaderLog); + return; + } + + _Use_decl_annotations_ + PAGED_CODE_SEG + VOID + AcpiReader::EvtContextDestroy(WDFOBJECT Object) + { + PAGED_CODE(); + + AcpiReader * context = GetAcpiReaderDeviceContext(Object); + context->~AcpiReader(); + } +} // namespace ACPIREADER diff --git a/audio/Soundwire/Samples/SdcaVad/SdcaVDsp/AcpiReader.h b/audio/Soundwire/Samples/SdcaVad/SdcaVDsp/AcpiReader.h new file mode 100644 index 000000000..5c9ceee81 --- /dev/null +++ b/audio/Soundwire/Samples/SdcaVad/SdcaVDsp/AcpiReader.h @@ -0,0 +1,297 @@ +/*++ + +Copyright (c) Microsoft Corporation. All rights reserved. + + THIS CODE AND INFORMATION IS PROVIDED "AS IS" WITHOUT WARRANTY OF ANY + KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE + IMPLIED WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A PARTICULAR + PURPOSE. + +Module Name: + + AcpiReader.h + +Abstract: + + Contains ACPI reader module. + +Environment: + + Kernel mode + +--*/ + +#pragma once + +#ifndef _ACPIREADER_H_ +#define _ACPIREADER_H_ + +/* make prototypes usable from C++ */ +#ifdef __cplusplus +extern "C" { +#endif + +#include + +// Number of seconds for ACPI request timeout. +#define ACPI_REQUEST_TIMEOUT_SEC 5 + +// +// Device properties UUID in the ACPI methods. +// {DAFFD814-6EBA-4D8C-8A91-BC9BBF4AA301} +// +DEFINE_GUID(DSD_DEVICE_PROPERTIES_GUID, + 0xDAFFD814, 0x6EBA, 0x4D8C, 0x8A, 0x91, 0xBC, 0x9B, 0xBF, 0x4A, 0xA3, 0x01); + +// +// Hierarchical data extension UUID in the ACPI methods. +// {DBB8E3E6-5886-4BA6-8795-1319F52A966B} +// + +DEFINE_GUID(DSD_HIERARCHICAL_DATA_EXTENSION_GUID, + 0xDBB8E3E6, 0x5886, 0x4BA6, 0x87, 0x95, 0x13, 0x19, 0xF5, 0x2A, 0x96, 0x6B); + +// +// Buffer UUID in ACPI methods. +// {EDB12DD0-363D-4085-A3D2-49522CA160C4} +// + +DEFINE_GUID(DSD_BUFFER_GUID, + 0xEDB12DD0, 0x363D, 0x4085, 0xA3, 0xD2, 0x49, 0x52, 0x2C, 0xA1, 0x60, 0xC4); + +namespace ACPIREADER +{ + typedef enum + { + ACPI_METHOD_SECTION_UNKNOWN = 0, + ACPI_METHOD_SECTION_DEVICE_PROPERTIES = 1, + ACPI_METHOD_SECTION_HIERARCHICAL_DATA_EXTENSION = 2, + ACPI_METHOD_SECTION_BUFFER = 3 + } ACPI_METHOD_SECTION; + + class AcpiReader + { + private: + WDFDEVICE m_AcpiDevice{ nullptr }; + + __drv_maxIRQL(PASSIVE_LEVEL) + PAGED_CODE_SEG + NTSTATUS + ParseULongLong( + _In_ PACPI_METHOD_ARGUMENT Argument, + _Out_ PULONGLONG Value); + + __drv_maxIRQL(PASSIVE_LEVEL) + PAGED_CODE_SEG + NTSTATUS + ParseULong( + _In_ PACPI_METHOD_ARGUMENT Argument, + _Out_ PULONG Value); + + __drv_maxIRQL(PASSIVE_LEVEL) + PAGED_CODE_SEG + NTSTATUS + ParseString( + _In_ PACPI_METHOD_ARGUMENT Argument, + _Out_writes_opt_z_(ValueStringSize) char * ValueString, + _In_ ULONG ValueStringSize, + _Out_ PULONG PropertyValueSize); + + __drv_maxIRQL(PASSIVE_LEVEL) + PAGED_CODE_SEG + NTSTATUS + ParseBuffer( + _In_ PACPI_METHOD_ARGUMENT Argument, + _Out_writes_bytes_(ValueBufferSize) PVOID ValueBuffer, + _In_ ULONG ValueBufferSize, + _Out_ PULONG PropertyValueSize); + + __drv_maxIRQL(PASSIVE_LEVEL) + PAGED_CODE_SEG + NTSTATUS + ParseGuid( + _In_ PACPI_METHOD_ARGUMENT Argument, + _Out_writes_bytes_(BufferLength) PVOID Buffer, + _In_ ULONG BufferLength); + + __drv_maxIRQL(PASSIVE_LEVEL) + PAGED_CODE_SEG + NTSTATUS + GetProperty( + _In_ LPCSTR PropertyName, + _In_ ACPI_METHOD_SECTION PropertySection, + _In_ WDFMEMORY AcpiEvalOutputBuf, + _Outptr_result_maybenull_ PACPI_METHOD_ARGUMENT * PropertyValue); + + __drv_maxIRQL(PASSIVE_LEVEL) + PAGED_CODE_SEG + NTSTATUS + ParsePropertiesPackage( + _In_ LPCSTR PropertyName, + _In_ size_t PropertyLength, + _In_ PACPI_METHOD_ARGUMENT Package, + _Outptr_result_maybenull_ PACPI_METHOD_ARGUMENT * PropertyValue); + + __drv_maxIRQL(PASSIVE_LEVEL) + PAGED_CODE_SEG + NTSTATUS + FindProperty( + _In_ LPCSTR PropertyName, + _In_ size_t PropertyLength, + _In_ PACPI_METHOD_ARGUMENT Package, + _Outptr_result_maybenull_ PACPI_METHOD_ARGUMENT * PropertyValue); + + __drv_maxIRQL(PASSIVE_LEVEL) + PAGED_CODE_SEG + NTSTATUS + ParseULongArray( + _In_ PACPI_METHOD_ARGUMENT Argument, + _Out_writes_(ValueArrayCount) ULONG * ValueArray, + _In_ ULONG ValueArrayCount, + _Out_ PULONG PropertyValueArrayCount); + + public: + static + _Must_inspect_result_ + __drv_maxIRQL(PASSIVE_LEVEL) + PAGED_CODE_SEG + NTSTATUS + _CreateAndInitialize(_In_ WDFDEVICE Device, _In_ RECORDER_LOG Log, _In_ ULONG MemoryTag); + + __drv_maxIRQL(PASSIVE_LEVEL) + PAGED_CODE_SEG + NTSTATUS + EnumChildren( + _Out_ WDFMEMORY * EnumChildrenOutput); + + __drv_maxIRQL(PASSIVE_LEVEL) + PAGED_CODE_SEG + NTSTATUS + EvaluateMethod( + _In_ LPCSTR MethodName, + _Out_ WDFMEMORY * ReturnMemory); + + __drv_maxIRQL(PASSIVE_LEVEL) + PAGED_CODE_SEG + NTSTATUS + EvaluateAdr( + _In_opt_ LPCSTR ChildDeviceName, + _Out_ PULONGLONG Address); + + __drv_maxIRQL(PASSIVE_LEVEL) + PAGED_CODE_SEG + NTSTATUS + EvaluateAdr( + _Out_ PULONGLONG Address); + + __drv_maxIRQL(PASSIVE_LEVEL) + PAGED_CODE_SEG + NTSTATUS + GetPropertyULongLong( + _In_ LPCSTR PropertyName, + _In_ ACPI_METHOD_SECTION PropertySection, + _In_ WDFMEMORY AcpiEvalOutputBuf, + _Out_ PULONGLONG PropertyValue); + + __drv_maxIRQL(PASSIVE_LEVEL) + PAGED_CODE_SEG + NTSTATUS + GetPropertyULong( + _In_ LPCSTR PropertyName, + _In_ ACPI_METHOD_SECTION PropertySection, + _In_ WDFMEMORY AcpiEvalOutputBuf, + _Out_ PULONG PropertyValue); + + __drv_maxIRQL(PASSIVE_LEVEL) + PAGED_CODE_SEG + NTSTATUS + GetPropertyString( + _In_ LPCSTR PropertyName, + _In_ ACPI_METHOD_SECTION PropertySection, + _In_ WDFMEMORY AcpiEvalOutputBuf, + _Out_writes_opt_z_(ValueStringSize) char * ValueString, + _In_ ULONG ValueStringSize, + _Out_ PULONG PropertyValueSize); + + __drv_maxIRQL(PASSIVE_LEVEL) + PAGED_CODE_SEG + NTSTATUS + GetPropertyBuffer( + _In_ LPCSTR PropertyName, + _In_ WDFMEMORY AcpiEvalOutputBuf, + _Out_writes_bytes_(ValueBufferSize) PVOID ValueBuffer, + _In_ ULONG ValueBufferSize, + _Out_ PULONG PropertyValueSize); + + __drv_maxIRQL(PASSIVE_LEVEL) + PAGED_CODE_SEG + NTSTATUS + GetPropertyULongArray( + _In_ LPCSTR PropertyName, + _In_ ACPI_METHOD_SECTION PropertySection, + _In_ WDFMEMORY AcpiEvalOutputBuf, + _Out_writes_(ValueArrayCount) ULONG * ValueArray, + _In_ ULONG ValueArrayCount, + _Out_ PULONG PropertyValueArrayCount); + + __drv_maxIRQL(PASSIVE_LEVEL) + PAGED_CODE_SEG + VOID + FreeBuffer( + _Inout_ WDFMEMORY* AcpiEvalOutputBuf); + + protected: + + static + RECORDER_LOG s_AcpiReaderLog; + + static + ULONG s_MemoryTag; + + static + __drv_maxIRQL(PASSIVE_LEVEL) + PAGED_CODE_SEG + EVT_WDF_OBJECT_CONTEXT_DESTROY + EvtContextDestroy; + + __drv_maxIRQL(PASSIVE_LEVEL) + PAGED_CODE_SEG + AcpiReader(_In_ WDFDEVICE Device) : m_AcpiDevice(Device) { PAGED_CODE(); } + + // Placement-new to construct the object inside the WDF context space. + static + __drv_maxIRQL(PASSIVE_LEVEL) + PAGED_CODE_SEG + void* + operator new ( + _In_ size_t /* SizeInBytes */, + _In_ void* ContextMemory + ) + { + PAGED_CODE(); + // We already have the memory courtesy of WDF so we don't have to allocate anything. + return ContextMemory; + } + + static + __drv_maxIRQL(PASSIVE_LEVEL) + PAGED_CODE_SEG + void + operator delete ( + _In_ void* /* ContextMemory */ + ) + { + PAGED_CODE(); + // Since we didn't allocate the memory, don't try to deallocate it. + } + + }; + + WDF_DECLARE_CONTEXT_TYPE_WITH_NAME(AcpiReader, GetAcpiReaderDeviceContext) +} +/* make internal prototypes usable from C++ */ +#ifdef __cplusplus +} +#endif + +#endif // _ACPIREADER_H_ diff --git a/audio/Soundwire/Samples/SdcaVad/SdcaVDsp/AudioModule.cpp b/audio/Soundwire/Samples/SdcaVad/SdcaVDsp/AudioModule.cpp new file mode 100644 index 000000000..79764cdcb --- /dev/null +++ b/audio/Soundwire/Samples/SdcaVad/SdcaVDsp/AudioModule.cpp @@ -0,0 +1,384 @@ +/*++ + + THIS CODE AND INFORMATION IS PROVIDED "AS IS" WITHOUT WARRANTY OF ANY + KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE + IMPLIED WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A PARTICULAR + PURPOSE. + +Module Name: + + AudioModule.cpp + +Abstract: + + Implementation of general purpose audio module property handlers + +Environment: + + Kernel mode + +--*/ + +#include "private.h" +#include "audiomodule.h" +#include "stdunk.h" +#include + +AUDIOMODULE_PARAMETER_INFO AudioModule0_ParameterInfo[] = +{ + { + ACX_PROPERTY_ITEM_FLAG_GET | ACX_PROPERTY_ITEM_FLAG_SET | ACX_PROPERTY_ITEM_FLAG_BASICSUPPORT, + AUDIOMODULE_PARAMETER_FLAG_CHANGE_NOTIFICATION, + (ULONG)RTL_FIELD_SIZE(DSP_AUDIOMODULE0_CONTEXT, Parameter1), + VT_UI4, + AudioModule0_ValidParameterList, + SIZEOF_ARRAY(AudioModule0_ValidParameterList) + }, + { + ACX_PROPERTY_ITEM_FLAG_GET | ACX_PROPERTY_ITEM_FLAG_BASICSUPPORT, + 0, + (ULONG)RTL_FIELD_SIZE(DSP_AUDIOMODULE0_CONTEXT, Parameter2), + VT_UI1, + NULL, + 0 + }, +}; + +AUDIOMODULE_PARAMETER_INFO AudioModule1_ParameterInfo[] = +{ + { + KSPROPERTY_TYPE_GET | KSPROPERTY_TYPE_SET | KSPROPERTY_TYPE_BASICSUPPORT, + AUDIOMODULE_PARAMETER_FLAG_CHANGE_NOTIFICATION, + (ULONG)RTL_FIELD_SIZE(DSP_AUDIOMODULE1_CONTEXT, Parameter1), + VT_UI1, + AudioModule1_ValidParameterList, + SIZEOF_ARRAY(AudioModule1_ValidParameterList) + }, + { + KSPROPERTY_TYPE_GET | KSPROPERTY_TYPE_BASICSUPPORT, + 0, + (ULONG)RTL_FIELD_SIZE(DSP_AUDIOMODULE1_CONTEXT, Parameter2), + VT_UI8, + NULL, + 0 + }, + { + KSPROPERTY_TYPE_GET | KSPROPERTY_TYPE_SET | KSPROPERTY_TYPE_BASICSUPPORT, + AUDIOMODULE_PARAMETER_FLAG_CHANGE_NOTIFICATION, + (ULONG)RTL_FIELD_SIZE(DSP_AUDIOMODULE1_CONTEXT, Parameter3), + VT_UI4, + NULL, + 0 + }, +}; + +AUDIOMODULE_PARAMETER_INFO AudioModule2_ParameterInfo[] = +{ + { + KSPROPERTY_TYPE_GET | KSPROPERTY_TYPE_SET | KSPROPERTY_TYPE_BASICSUPPORT, + AUDIOMODULE_PARAMETER_FLAG_CHANGE_NOTIFICATION, + (ULONG)RTL_FIELD_SIZE(DSP_AUDIOMODULE2_CONTEXT, Parameter1), + VT_UI4, + AudioModule2_ValidParameterList, + SIZEOF_ARRAY(AudioModule2_ValidParameterList) + }, + { + KSPROPERTY_TYPE_GET | KSPROPERTY_TYPE_BASICSUPPORT, + 0, + (ULONG)RTL_FIELD_SIZE(DSP_AUDIOMODULE2_CONTEXT, Parameter2), + VT_UI2, + NULL, + 0 + }, +}; + +#pragma code_seg("PAGE") +NTSTATUS +AudioModule_GenericHandler_BasicSupport( + _In_ PAUDIOMODULE_PARAMETER_INFO ParameterInfo, + _Out_writes_bytes_opt_(*BufferCb) PVOID Buffer, + _Inout_ ULONG * BufferCb + ) +{ + NTSTATUS ntStatus = STATUS_SUCCESS; + ULONG cbFullProperty = 0; + ULONG cbDataListSize = 0; + + PAGED_CODE(); + + ASSERT(ParameterInfo); + ASSERT(BufferCb); + + // + // Compute total size of property. + // + ntStatus = RtlULongMult(ParameterInfo->Size, + ParameterInfo->ValidSetCount, + &cbDataListSize); + if (!NT_SUCCESS(ntStatus)) + { + ASSERT(FALSE); + *BufferCb = 0; + return ntStatus; + } + + ntStatus = RtlULongAdd(cbDataListSize, + (ULONG)(sizeof(KSPROPERTY_DESCRIPTION) + + sizeof(KSPROPERTY_MEMBERSHEADER)), + &cbFullProperty); + + if (!NT_SUCCESS(ntStatus)) + { + ASSERT(FALSE); + *BufferCb = 0; + return ntStatus; + } + + // + // Return the info the caller is asking for. + // + if (*BufferCb == 0) + { + // caller wants to know the size of the buffer. + *BufferCb = cbFullProperty; + ntStatus = STATUS_BUFFER_OVERFLOW; + } + else if (*BufferCb >= (sizeof(KSPROPERTY_DESCRIPTION))) + { + PKSPROPERTY_DESCRIPTION propDesc = PKSPROPERTY_DESCRIPTION(Buffer); + + propDesc->AccessFlags = ParameterInfo->AccessFlags; + propDesc->DescriptionSize = cbFullProperty; + propDesc->PropTypeSet.Set = KSPROPTYPESETID_General; + propDesc->PropTypeSet.Id = ParameterInfo->VtType; + propDesc->PropTypeSet.Flags = 0; + propDesc->MembersListCount = 1; + propDesc->Reserved = 0; + + // if return buffer can also hold a list description, return it too + if(*BufferCb >= cbFullProperty) + { + // fill in the members header + PKSPROPERTY_MEMBERSHEADER members = + PKSPROPERTY_MEMBERSHEADER(propDesc + 1); + + members->MembersFlags = KSPROPERTY_MEMBER_VALUES; + members->MembersSize = ParameterInfo->Size; + members->MembersCount = ParameterInfo->ValidSetCount; + members->Flags = KSPROPERTY_MEMBER_FLAG_DEFAULT; + + // fill in valid array. + BYTE* array = (BYTE*)(members + 1); + + RtlCopyMemory(array, ParameterInfo->ValidSet, cbDataListSize); + + // set the return value size + *BufferCb = cbFullProperty; + } + else + { + *BufferCb = sizeof(KSPROPERTY_DESCRIPTION); + } + } + else if(*BufferCb >= sizeof(ULONG)) + { + // if return buffer can hold a ULONG, return the access flags + PULONG accessFlags = PULONG(Buffer); + + *BufferCb = sizeof(ULONG); + *accessFlags = ParameterInfo->AccessFlags; + } + else + { + *BufferCb = 0; + ntStatus = STATUS_BUFFER_TOO_SMALL; + } + + return ntStatus; +} + +#pragma code_seg("PAGE") +BOOLEAN +IsAudioModuleParameterValid( + _In_ PAUDIOMODULE_PARAMETER_INFO ParameterInfo, + _In_reads_bytes_opt_(BufferCb) PVOID Buffer, + _In_ ULONG BufferCb + ) +{ + PAGED_CODE(); + + ULONG i = 0; + ULONG j = 0; + BOOLEAN validParam = FALSE; + + // + // Validate buffer ptr and size. + // + if (Buffer == NULL || BufferCb == 0) + { + validParam = FALSE; + goto exit; + } + + // + // Check its size. + // + if (BufferCb < ParameterInfo->Size) + { + validParam = FALSE; + goto exit; + } + + // + // Check the valid list. + // + if (ParameterInfo->ValidSet && ParameterInfo->ValidSetCount) + { + BYTE* buffer = (BYTE*)ParameterInfo->ValidSet; + BYTE* pattern = (BYTE*)Buffer; + + // + // Scan the valid list. + // + for (i = 0; i < ParameterInfo->ValidSetCount; ++i) + { + for (j=0; j < ParameterInfo->Size; ++j) + { + if (buffer[j] != pattern[j]) + { + break; + } + } + + if (j == ParameterInfo->Size) + { + // got a match. + break; + } + + buffer += ParameterInfo->Size; + } + + // + // If end of list, we didn't find the value. + // + if (i == ParameterInfo->ValidSetCount) + { + validParam = FALSE; + goto exit; + } + } + else + { + // + // Negative-testing support. Fail request if value is -1. + // + BYTE* buffer = (BYTE*)Buffer; + + for (i = 0; i < ParameterInfo->Size; ++i) + { + if (buffer[i] != 0xFF) + { + break; + } + } + + // + // If value is -1, return error. + // + if (i == ParameterInfo->Size) + { + validParam = FALSE; + goto exit; + } + } + + validParam = TRUE; + +exit: + return validParam; +} + +#pragma code_seg("PAGE") +NTSTATUS +AudioModule_GenericHandler( + _In_ ULONG Verb, + _In_ ULONG ParameterId, + _In_ PAUDIOMODULE_PARAMETER_INFO ParameterInfo, + _Inout_updates_bytes_(ParameterInfo->Size) PVOID CurrentValue, + _In_reads_bytes_opt_(InBufferCb) PVOID InBuffer, + _In_ ULONG InBufferCb, + _Out_writes_bytes_opt_(*OutBufferCb) PVOID OutBuffer, + _Inout_ ULONG * OutBufferCb, + _In_ BOOL * ParameterChanged + ) +{ + PAGED_CODE(); + + UNREFERENCED_PARAMETER(ParameterId); + + *ParameterChanged = FALSE; + + // Handle KSPROPERTY_TYPE_BASICSUPPORT query + if (Verb & KSPROPERTY_TYPE_BASICSUPPORT) + { + return AudioModule_GenericHandler_BasicSupport(ParameterInfo, OutBuffer, OutBufferCb); + } + + ULONG cbMinSize = ParameterInfo->Size; + + if (Verb & KSPROPERTY_TYPE_GET) + { + // Verify module parameter supports 'get'. + if (!(ParameterInfo->AccessFlags & KSPROPERTY_TYPE_GET)) + { + *OutBufferCb = 0; + return STATUS_INVALID_DEVICE_REQUEST; + } + + // Verify value size + if (*OutBufferCb == 0) + { + *OutBufferCb = cbMinSize; + return STATUS_BUFFER_OVERFLOW; + } + if (*OutBufferCb < cbMinSize) + { + *OutBufferCb = 0; + return STATUS_BUFFER_TOO_SMALL; + } + else + { + RtlCopyMemory(OutBuffer, CurrentValue, ParameterInfo->Size); + *OutBufferCb = cbMinSize; + return STATUS_SUCCESS; + } + } + else if (Verb & KSPROPERTY_TYPE_SET) + { + *OutBufferCb = 0; + + // Verify it is a write prop. + if (!(ParameterInfo->AccessFlags & KSPROPERTY_TYPE_SET)) + { + return STATUS_INVALID_DEVICE_REQUEST; + } + + // Validate parameter. + if (!IsAudioModuleParameterValid(ParameterInfo, InBuffer, InBufferCb)) + { + return STATUS_INVALID_PARAMETER; + } + + if (ParameterInfo->Size != + RtlCompareMemory(CurrentValue, InBuffer, ParameterInfo->Size)) + { + RtlCopyMemory(CurrentValue, InBuffer, ParameterInfo->Size); + *ParameterChanged = TRUE; + } + + return STATUS_SUCCESS; + } + + return STATUS_INVALID_DEVICE_REQUEST; +} diff --git a/audio/Soundwire/Samples/SdcaVad/SdcaVDsp/AudioModule.h b/audio/Soundwire/Samples/SdcaVad/SdcaVDsp/AudioModule.h new file mode 100644 index 000000000..9369f8356 --- /dev/null +++ b/audio/Soundwire/Samples/SdcaVad/SdcaVDsp/AudioModule.h @@ -0,0 +1,216 @@ +/*++ + +Copyright (c) Microsoft Corporation. All rights reserved. + + THIS CODE AND INFORMATION IS PROVIDED "AS IS" WITHOUT WARRANTY OF ANY + KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE + IMPLIED WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A PARTICULAR + PURPOSE. + +Module Name: + + AudioModule.h + +Abstract: + + Contains audio modules definitions and function prototypes private to + the driver. + +Environment: + + Kernel mode + +--*/ + +#ifndef _AUDIOMODULE_H_ +#define _AUDIOMODULE_H_ + +/* make prototypes usable from C++ */ +#ifdef __cplusplus +extern "C" { +#endif + +// Audio module definitions + +// +// Audio module instance defintion. +// This sample driver generates an instance id by combinding the +// configuration set # for a class module id with the instance of that +// configuration. Real driver should use a more robust scheme, such as +// an indirect mapping from/to an instance id to/from a configuration set +// + location in the pipeline + any other info the driver needs. +// +// top 8 bits reserved for use by aggregation +// next 12 bits are the config id mask +// bottom 12 bits instance id +#define AUDIOMODULE_CLASS_CFG_ID_MASK 0xFFF +#define AUDIOMODULE_CLASS_CFG_INSTANCE_ID_MASK 0xFFF + +#define AUDIOMODULE_INSTANCE_ID(ClassCfgId, ClassCfgInstanceId) \ + ((ULONG(ClassCfgId & AUDIOMODULE_CLASS_CFG_ID_MASK) << 12) | \ + (ULONG(ClassCfgInstanceId & AUDIOMODULE_CLASS_CFG_INSTANCE_ID_MASK))) + +#define AUDIOMODULE_GET_CLASSCFGID(InstanceId) \ + (ULONG(InstanceId) >> 12 & AUDIOMODULE_CLASS_CFG_ID_MASK) + +enum AudioModule_Parameter { + AudioModuleParameter1 = 0, + AudioModuleParameter2, + AudioModuleParameter3 +}; + +typedef struct _AUDIOMODULE_CUSTOM_COMMAND { + ULONG Verb; // get, set and support + AudioModule_Parameter ParameterId; +} AUDIOMODULE_CUSTOM_COMMAND, *PAUDIOMODULE_CUSTOM_COMMAND; + +enum AudioModule_Notification_Type { + AudioModuleParameterChanged = 0, +}; + +typedef struct _AUDIOMODULE_CUSTOM_NOTIFICATION { + ULONG Type; + union { + struct { + ULONG ParameterId; + } ParameterChanged; + }; +} AUDIOMODULE_CUSTOM_NOTIFICATION, *PAUDIOMODULE_CUSTOM_NOTIFICATION; + +#define AUDIOMODULE_PARAMETER_FLAG_CHANGE_NOTIFICATION 0x00000001 + +typedef struct _DSP_AUDIOMODULE0_CONTEXT { + ACXPNPEVENT Event; + ULONG Parameter1; + BYTE Parameter2; + ULONG InstanceId; +} DSP_AUDIOMODULE0_CONTEXT, *PDSP_AUDIOMODULE0_CONTEXT; + +typedef struct _DSP_AUDIOMODULE1_CONTEXT { + ACXPNPEVENT Event; + BYTE Parameter1; + ULONGLONG Parameter2; + DWORD Parameter3; + ULONG InstanceId; +} DSP_AUDIOMODULE1_CONTEXT, *PDSP_AUDIOMODULE1_CONTEXT; + +typedef struct _DSP_AUDIOMODULE2_CONTEXT { + ACXPNPEVENT Event; + ULONG Parameter1; + USHORT Parameter2; + ULONG InstanceId; +} DSP_AUDIOMODULE2_CONTEXT, *PDSP_AUDIOMODULE2_CONTEXT; + +WDF_DECLARE_CONTEXT_TYPE_WITH_NAME(DSP_AUDIOMODULE0_CONTEXT, GetDspAudioModule0Context); +WDF_DECLARE_CONTEXT_TYPE_WITH_NAME(DSP_AUDIOMODULE1_CONTEXT, GetDspAudioModule1Context); +WDF_DECLARE_CONTEXT_TYPE_WITH_NAME(DSP_AUDIOMODULE2_CONTEXT, GetDspAudioModule2Context); + +typedef struct _AUDIOMODULE_PARAMETER_INFO +{ + USHORT AccessFlags; // get/set/basic-support attributes. + USHORT Flags; + ULONG Size; + DWORD VtType; + PVOID ValidSet; + ULONG ValidSetCount; +} AUDIOMODULE_PARAMETER_INFO, *PAUDIOMODULE_PARAMETER_INFO; + +// +// Module 0 definitions +// +#define AUDIOMODULE0DESCRIPTION L"Generic system module" +#define AUDIOMODULE0_MAJOR 0x1 +#define AUDIOMODULE0_MINOR 0X0 + +// {BD7CDC7F-F52E-4A95-B026-586926056128} +static const GUID AudioModule0Id = +{ 0xbd7cdc7f, 0xf52e, 0x4a95, { 0xb0, 0x26, 0x58, 0x69, 0x26, 0x5, 0x61, 0x28 } }; + +EVT_ACX_AUDIOMODULE_PROCESSCOMMAND DspR_EvtProcessCommand0; + +static +ULONG AudioModule0_ValidParameterList[] = +{ + 1, 2, 5 +}; + +extern AUDIOMODULE_PARAMETER_INFO AudioModule0_ParameterInfo[2]; + +// +// Module 1 definitions +// +static +BYTE AudioModule1_ValidParameterList[] = +{ + 0, 1, 2 +}; + +extern AUDIOMODULE_PARAMETER_INFO AudioModule1_ParameterInfo[3]; + +#define AUDIOMODULE1DESCRIPTION L"Module 1" +#define AUDIOMODULE1_MAJOR 0x2 +#define AUDIOMODULE1_MINOR 0X1 + +// {2803D255-6175-40A4-A572-ECF9FF6F07A9} +static const GUID AudioModule1Id = +{ 0x2803d255, 0x6175, 0x40a4, { 0xa5, 0x72, 0xec, 0xf9, 0xff, 0x6f, 0x7, 0xa9 } }; + +EVT_ACX_AUDIOMODULE_PROCESSCOMMAND DspR_EvtProcessCommand1; + +// +// Module 2 definitions +// +static +ULONG AudioModule2_ValidParameterList[] = +{ + 1, 0xfffffffe +}; + +extern AUDIOMODULE_PARAMETER_INFO AudioModule2_ParameterInfo[2]; + +#define AUDIOMODULE2DESCRIPTION L"Module 2" +#define AUDIOMODULE2_MAJOR 0x2 +#define AUDIOMODULE2_MINOR 0X0 + +// {2225578F-DF3B-40D8-BE80-031E1649DCC4} +static const GUID AudioModule2Id = +{ 0x2225578f, 0xdf3b, 0x40d8, { 0xbe, 0x80, 0x3, 0x1e, 0x16, 0x49, 0xdc, 0xc4 } }; + + +EVT_ACX_AUDIOMODULE_PROCESSCOMMAND DspR_EvtProcessCommand2; + +// General purpose helper functions + +NTSTATUS +AudioModule_GenericHandler_BasicSupport( + _In_ PAUDIOMODULE_PARAMETER_INFO ParameterInfo, + _Out_writes_bytes_opt_(*BufferCb) PVOID Buffer, + _Inout_ ULONG * BufferCb + ); + +BOOLEAN +IsAudioModuleParameterValid( + _In_ PAUDIOMODULE_PARAMETER_INFO ParameterInfo, + _In_reads_bytes_opt_(BufferCb) PVOID Buffer, + _In_ ULONG BufferCb + ); + +NTSTATUS +AudioModule_GenericHandler( + _In_ ULONG Verb, + _In_ ULONG ParameterId, + _In_ PAUDIOMODULE_PARAMETER_INFO ParameterInfo, + _Inout_updates_bytes_(ParameterInfo->Size) PVOID CurrentValue, + _In_reads_bytes_opt_(InBufferCb) PVOID InBuffer, + _In_ ULONG InBufferCb, + _Out_writes_bytes_opt_(*OutBufferCb) PVOID OutBuffer, + _Inout_ ULONG * OutBufferCb, + _In_ BOOL * ParameterChanged + ); + +/* make internal prototypes usable from C++ */ +#ifdef __cplusplus +} +#endif + +#endif // _AUDIOMODULE_H_ diff --git a/audio/Soundwire/Samples/SdcaVad/SdcaVDsp/CircuitHelper.cpp b/audio/Soundwire/Samples/SdcaVad/SdcaVDsp/CircuitHelper.cpp new file mode 100644 index 000000000..94d94843c --- /dev/null +++ b/audio/Soundwire/Samples/SdcaVad/SdcaVDsp/CircuitHelper.cpp @@ -0,0 +1,1467 @@ +/*++ + + THIS CODE AND INFORMATION IS PROVIDED "AS IS" WITHOUT WARRANTY OF ANY + KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE + IMPLIED WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A PARTICULAR + PURPOSE. + +Module Name: + + CircuitHelper.cpp + +Abstract: + + This module contains helper functions for render.cpp and capture.cpp files. + +Environment: + + Kernel mode + +--*/ + +#include "private.h" +#include "CircuitHelper.h" +#include "TestProperties.h" +#include "AudioFormats.h" + +#ifndef __INTELLISENSE__ +#include "CircuitHelper.tmh" +#endif + +PAGED_CODE_SEG +NTSTATUS CreateCaptureCircuit( + _In_ PACXCIRCUIT_INIT CircuitInit, + _In_ UNICODE_STRING CircuitName, + _In_ WDFDEVICE Device, + _Out_ ACXCIRCUIT* Circuit +) +{ + + PAGED_CODE(); + + NTSTATUS status = STATUS_SUCCESS; + + WDF_OBJECT_ATTRIBUTES attributes; + WDF_OBJECT_ATTRIBUTES_INIT(&attributes); + + // Circuit Component ID already assigned by the device handler + + RETURN_NTSTATUS_IF_FAILED(AcxCircuitInitAssignName(CircuitInit, &CircuitName)); + + // + // Add circuit type. + // + AcxCircuitInitSetCircuitType(CircuitInit, AcxCircuitTypeCapture); + + // + // Assign the circuit's pnp-power callbacks. + // + { + ACX_CIRCUIT_PNPPOWER_CALLBACKS powerCallbacks; + ACX_CIRCUIT_PNPPOWER_CALLBACKS_INIT(&powerCallbacks); + powerCallbacks.EvtAcxCircuitPowerUp = DspC_EvtCircuitPowerUp; + powerCallbacks.EvtAcxCircuitPowerDown = DspC_EvtCircuitPowerDown; + AcxCircuitInitSetAcxCircuitPnpPowerCallbacks(CircuitInit, &powerCallbacks); + } + + // + // Assign the circuit's composite callbacks. + // + { + ACX_CIRCUIT_COMPOSITE_CALLBACKS compositeCallbacks; + ACX_CIRCUIT_COMPOSITE_CALLBACKS_INIT(&compositeCallbacks); + compositeCallbacks.EvtAcxCircuitCompositeCircuitInitialize = DspC_EvtCircuitCompositeCircuitInitialize; + compositeCallbacks.EvtAcxCircuitCompositeInitialize = DspC_EvtCircuitCompositeInitialize; + AcxCircuitInitSetAcxCircuitCompositeCallbacks(CircuitInit, &compositeCallbacks); + } + + + // + // Add pre-process callbacks. + // +// See description in private.h +#ifdef ACX_WORKAROUND_ACXPIN_01 + RETURN_NTSTATUS_IF_FAILED(AcxCircuitInitAssignAcxRequestPreprocessCallback( + CircuitInit, + Dsp_EvtStreamGetStreamCountRequestPreprocess, + (ACXCONTEXT)Device, + AcxRequestTypeProperty, + &KSPROPSETID_Pin, + KSPROPERTY_PIN_CINSTANCES)); +#endif // ACX_WORKAROUND_ACXPIN_01 + + RETURN_NTSTATUS_IF_FAILED(AcxCircuitInitAssignAcxRequestPreprocessCallback( + CircuitInit, + DspC_EvtCircuitRequestPreprocess, + (ACXCONTEXT)AcxRequestTypeAny, // dbg only + AcxRequestTypeAny, + NULL, + AcxItemIdNone)); + + RETURN_NTSTATUS_IF_FAILED(AcxCircuitInitAssignAcxCreateStreamCallback( + CircuitInit, + DspC_EvtCircuitCreateStream)); + + /* + // + // Add properties, events and methods. + // + RETURN_NTSTATUS_IF_FAILED(AcxCircuitInitAssignProperties(CircuitInit, + CircuitProperties, + CircuitPropertiesCount)); + */ + + // + // Create the circuit. + // + WDF_OBJECT_ATTRIBUTES_INIT_CONTEXT_TYPE(&attributes, DSP_CIRCUIT_CONTEXT); + attributes.EvtCleanupCallback = DspC_EvtCircuitContextCleanup; + RETURN_NTSTATUS_IF_FAILED(AcxCircuitCreate(Device, &attributes, &CircuitInit, Circuit)); + + return status; +} + +PAGED_CODE_SEG +VOID Dsp_EvtPropertyResourceGroup( + _In_ ACXOBJECT Circuit, + _In_ WDFREQUEST Request +) +{ + PAGED_CODE(); + + ACX_REQUEST_PARAMETERS params; + ACX_REQUEST_PARAMETERS_INIT(¶ms); + AcxRequestGetParameters(Request, ¶ms); + + PAUDIORESOURCEMANAGEMENT_RESOURCEGROUP resourceGroup = + (PAUDIORESOURCEMANAGEMENT_RESOURCEGROUP)params.Parameters.Property.Value; + + DrvLogInfo(g_SDCAVDspLog, FLAG_STREAM, L"SDCA VDSP Circuit %p received KSPROPERTY_AUDIORESOURCEMANAGEMENT_RESOURCEGROUP with group \"%ls\" %ls", + Circuit, resourceGroup->ResourceGroupName, resourceGroup->ResourceGroupAcquired ? L"Acquired" : L"Released"); + + WdfRequestComplete(Request, STATUS_SUCCESS); +} + + +PAGED_CODE_SEG +NTSTATUS CreateRenderCircuit( + _In_ PACXCIRCUIT_INIT CircuitInit, + _In_ UNICODE_STRING CircuitName, + _In_ WDFDEVICE Device, + _Out_ ACXCIRCUIT* Circuit +) +{ + + PAGED_CODE(); + + NTSTATUS status = STATUS_SUCCESS; + + WDF_OBJECT_ATTRIBUTES attributes; + WDF_OBJECT_ATTRIBUTES_INIT(&attributes); + + // Circuit Component ID already assigned by the device handler + + RETURN_NTSTATUS_IF_FAILED(AcxCircuitInitAssignName(CircuitInit, &CircuitName)); + + // + // Add circuit type. + // + AcxCircuitInitSetCircuitType(CircuitInit, AcxCircuitTypeRender); + + // + // Assign the circuit's pnp-power callbacks. + // + { + ACX_CIRCUIT_PNPPOWER_CALLBACKS powerCallbacks; + ACX_CIRCUIT_PNPPOWER_CALLBACKS_INIT(&powerCallbacks); + powerCallbacks.EvtAcxCircuitPowerUp = DspR_EvtCircuitPowerUp; + powerCallbacks.EvtAcxCircuitPowerDown = DspR_EvtCircuitPowerDown; + AcxCircuitInitSetAcxCircuitPnpPowerCallbacks(CircuitInit, &powerCallbacks); + } + + // + // Assign the circuit's composite callbacks. + // + { + ACX_CIRCUIT_COMPOSITE_CALLBACKS compositeCallbacks; + ACX_CIRCUIT_COMPOSITE_CALLBACKS_INIT(&compositeCallbacks); + compositeCallbacks.EvtAcxCircuitCompositeCircuitInitialize = DspR_EvtCircuitCompositeCircuitInitialize; + compositeCallbacks.EvtAcxCircuitCompositeInitialize = DspR_EvtCircuitCompositeInitialize; + AcxCircuitInitSetAcxCircuitCompositeCallbacks(CircuitInit, &compositeCallbacks); + } + + // + // Assign properties handled by the circuit. + // + { + ACX_PROPERTY_ITEM RenderCircuitProperties[] = + { + { + &KSPROPSETID_AudioResourceManagement, + KSPROPERTY_AUDIORESOURCEMANAGEMENT_RESOURCEGROUP, + ACX_PROPERTY_ITEM_FLAG_SET, + Dsp_EvtPropertyResourceGroup, + nullptr, + 0, + sizeof(AUDIORESOURCEMANAGEMENT_RESOURCEGROUP), + 0 + }, + }; + + RETURN_NTSTATUS_IF_FAILED(AcxCircuitInitAssignProperties(CircuitInit, RenderCircuitProperties, ARRAYSIZE(RenderCircuitProperties))); + } + // + // Add pre-process callbacks. + // +// See description in private.h +#ifdef ACX_WORKAROUND_ACXPIN_01 + RETURN_NTSTATUS_IF_FAILED(AcxCircuitInitAssignAcxRequestPreprocessCallback( + CircuitInit, + Dsp_EvtStreamGetStreamCountRequestPreprocess, + (ACXCONTEXT)Device, + AcxRequestTypeProperty, + &KSPROPSETID_Pin, + KSPROPERTY_PIN_CINSTANCES)); +#endif + +// See description in private.h +#ifdef ACX_WORKAROUND_ACXPIN_02 + RETURN_NTSTATUS_IF_FAILED(AcxCircuitInitAssignAcxRequestPreprocessCallback( + CircuitInit, + Dsp_EvtStreamProposeDataFormatRequestPreprocess, + (ACXCONTEXT)Device, + AcxRequestTypeProperty, + &KSPROPSETID_Pin, + KSPROPERTY_PIN_PROPOSEDATAFORMAT)); +#endif // ACX_WORKAROUND_ACXPIN_02 + + RETURN_NTSTATUS_IF_FAILED(AcxCircuitInitAssignAcxRequestPreprocessCallback( + CircuitInit, + DspR_EvtCircuitRequestPreprocess, + (ACXCONTEXT)AcxRequestTypeAny, // dbg only + AcxRequestTypeAny, + NULL, + AcxItemIdNone)); + + RETURN_NTSTATUS_IF_FAILED(AcxCircuitInitAssignAcxCreateStreamCallback( + CircuitInit, + DspR_EvtCircuitCreateStream)); + + /* + // + // Add properties, events and methods. + // + RETURN_NTSTATUS_IF_FAILED(AcxCircuitInitAssignProperties(CircuitInit, + CircuitProperties, + CircuitPropertiesCount)); + */ + + // + // Disable ACX remote stream handling. + // This is for testing only b/c by creating an explicit stream-bridge below, + // the default ACX behavior for stream-bridge is automatically disabled. + // + AcxCircuitInitDisableDefaultStreamBridgeHandling(CircuitInit); + + // + // Create the circuit. + // + WDF_OBJECT_ATTRIBUTES_INIT_CONTEXT_TYPE(&attributes, DSP_CIRCUIT_CONTEXT); + attributes.EvtCleanupCallback = DspR_EvtCircuitContextCleanup; + RETURN_NTSTATUS_IF_FAILED(AcxCircuitCreate(Device, &attributes, &CircuitInit, Circuit)); + + return status; +} + +PAGED_CODE_SEG +NTSTATUS AllocateFormat( + _In_ KSDATAFORMAT_WAVEFORMATEXTENSIBLE WaveFormat, + _In_ ACXCIRCUIT Circuit, + _In_ WDFDEVICE Device, + _Out_ ACXDATAFORMAT* Format +) +{ + PAGED_CODE(); + + NTSTATUS status = STATUS_SUCCESS; + + WDF_OBJECT_ATTRIBUTES attributes; + WDF_OBJECT_ATTRIBUTES_INIT(&attributes); + + ACX_DATAFORMAT_CONFIG formatCfg; + ACX_DATAFORMAT_CONFIG_INIT_KS(&formatCfg, &WaveFormat); + WDF_OBJECT_ATTRIBUTES_INIT_CONTEXT_TYPE(&attributes, DSP_FORMAT_CONTEXT); + attributes.ParentObject = Circuit; + + RETURN_NTSTATUS_IF_FAILED(AcxDataFormatCreate(Device, &attributes, &formatCfg, Format)); + + ASSERT((*Format) != NULL); + DSP_FORMAT_CONTEXT* formatCtx; + formatCtx = GetDspFormatContext(*Format); + ASSERT(formatCtx); + UNREFERENCED_PARAMETER(formatCtx); + + return status; +} + +PAGED_CODE_SEG +NTSTATUS CreatePin( + _In_ ACX_PIN_TYPE PinType, + _In_ ACXCIRCUIT Circuit, + _In_ ACX_PIN_COMMUNICATION Communication, + _In_ const GUID* Category, + _In_ ACX_PIN_CALLBACKS* PinCallbacks, + _In_ ULONG PinStreamCount, + _In_ bool Mic, + _Out_ ACXPIN* Pin +) +{ + PAGED_CODE(); + + NTSTATUS status = STATUS_SUCCESS; + + WDF_OBJECT_ATTRIBUTES attributes; + WDF_OBJECT_ATTRIBUTES_INIT(&attributes); + + ACX_PIN_CONFIG pinCfg; + ACX_PIN_CONFIG_INIT(&pinCfg); + pinCfg.Type = PinType; + pinCfg.Communication = Communication; + pinCfg.Category = Category; + pinCfg.PinCallbacks = PinCallbacks; + +// See description in private.h +#ifndef ACX_WORKAROUND_ACXPIN_01 + pinCfg->MaxStreams = PinStreamCount; +#endif + + ACX_MICROPHONE_CONFIG micCfg; + ACX_INTERLEAVED_AUDIO_FORMAT_INFORMATION InterleavedFormat; + + if (Mic) + { + ACX_MICROPHONE_CONFIG_INIT(&micCfg); + ACX_INTERLEAVED_AUDIO_FORMAT_INFORMATION_INIT(&InterleavedFormat); + + InterleavedFormat.PrimaryChannelCount = 2; + InterleavedFormat.PrimaryChannelStartPosition = 0; + InterleavedFormat.PrimaryChannelMask = 0; + InterleavedFormat.InterleavedChannelCount = 2; + InterleavedFormat.InterleavedChannelStartPosition = 2; + InterleavedFormat.InterleavedChannelMask = KSAUDIO_SPEAKER_STEREO; + + micCfg.InterleavedFormat = &InterleavedFormat; + + pinCfg.Flags |= AcxPinConfigMicrophoneConfigSpecified; + pinCfg.u.MicrophoneConfig = &micCfg; + } + + WDF_OBJECT_ATTRIBUTES_INIT_CONTEXT_TYPE(&attributes, DSP_PIN_CONTEXT); + attributes.EvtCleanupCallback = DspR_EvtPinContextCleanup; + attributes.ParentObject = Circuit; + + RETURN_NTSTATUS_IF_FAILED(AcxPinCreate(Circuit, &attributes, &pinCfg, Pin)); + ASSERT(Pin != NULL); + +// See description in private.h +#ifdef ACX_WORKAROUND_ACXPIN_01 + { + PDSP_PIN_CONTEXT pinCtx = GetDspPinContext(*Pin); + pinCtx->MaxStreams = PinStreamCount; + pinCtx->CurrentStreamsCount = 0; + } +#endif + + return status; +} + +PAGED_CODE_SEG +NTSTATUS RetrieveProperties( + _In_ PACX_FACTORY_CIRCUIT_ADD_CIRCUIT CircuitConfig, + _Out_ PULONG EndpointID +) +{ + PAGED_CODE(); + + NTSTATUS status = STATUS_SUCCESS; + + DECLARE_CONST_ACXOBJECTBAG_SOUNDWIRE_PROPERTY_NAME(EndpointId); + DECLARE_CONST_ACXOBJECTBAG_SOUNDWIRE_PROPERTY_NAME(DataPortNumber); + + WDF_OBJECT_ATTRIBUTES attributes; + WDF_OBJECT_ATTRIBUTES_INIT(&attributes); + + // Create object bag from the CompositeProperties + ACXOBJECTBAG compositeProperties; + ACX_OBJECTBAG_CONFIG propConfig; + ACX_OBJECTBAG_CONFIG_INIT(&propConfig); + propConfig.Handle = CircuitConfig->CompositeProperties; + propConfig.Flags |= AcxObjectBagConfigOpenWithHandle; + + RETURN_NTSTATUS_IF_FAILED(AcxObjectBagOpen(&attributes, &propConfig, &compositeProperties)); + + auto cleanupCompositeProperties = scope_exit([=]() { + WdfObjectDelete(compositeProperties); + } + ); + + RETURN_NTSTATUS_IF_FAILED(AcxObjectBagRetrieveUI4(compositeProperties, &EndpointId, EndpointID)); + + return status; +} + +PAGED_CODE_SEG +NTSTATUS +DetermineSpecialStreamDetailsFromVendorProperties( + _In_ ACXCIRCUIT Circuit, + _In_ AcpiReader * Acpi, + _In_ HANDLE CircuitPropertiesHandle + ) +{ + DECLARE_CONST_ACXOBJECTBAG_SYSTEM_PROPERTY_NAME(VendorPropertiesBlock); + WDFMEMORY vendorPropertiesBlock = NULL; + DSP_CIRCUIT_CONTEXT* circuitCtx; + NTSTATUS status = STATUS_SUCCESS; + PSDCA_PATH_DESCRIPTORS2 pPathDesc2 = nullptr; + + PAGED_CODE(); + + ACX_OBJECTBAG_CONFIG propConfig; + ACXOBJECTBAG circuitProperties; + ACX_OBJECTBAG_CONFIG_INIT(&propConfig); + propConfig.Handle = CircuitPropertiesHandle; + propConfig.Flags |= AcxObjectBagConfigOpenWithHandle; + + WDF_OBJECT_ATTRIBUTES attributes; + WDF_OBJECT_ATTRIBUTES_INIT(&attributes); + + RETURN_NTSTATUS_IF_FAILED(AcxObjectBagOpen(&attributes, &propConfig, &circuitProperties)); + + auto cleanupPropConfig = scope_exit([=]() + { + WdfObjectDelete(circuitProperties); + }); + + RETURN_NTSTATUS_IF_FAILED(AcxObjectBagRetrieveBlob(circuitProperties, &VendorPropertiesBlock, NULL, &vendorPropertiesBlock)); + + auto cleanup1 = scope_exit([&vendorPropertiesBlock] () + { + if (vendorPropertiesBlock != NULL) + { + WdfObjectDelete(vendorPropertiesBlock); + vendorPropertiesBlock = NULL; + } + }); + + // + // The below code would be replaced in a real DSP driver (or modified to use vendor-specific properties) + // + circuitCtx = GetDspCircuitContext(Circuit); + ASSERT(circuitCtx); + + for (ULONG i = (UINT)SpecialStreamTypeUltrasoundRender; i < (UINT)SpecialStreamType_Count; i++) + { + SDCA_PATH path = SdcaPathFromSpecialStreamType((SDCA_SPECIALSTREAM_TYPE)i); + ULONG propertyValue = 0; + char propertyName[256]; + + RETURN_NTSTATUS_IF_FAILED(RtlStringCbPrintfA(propertyName, sizeof(propertyName), "acpi-vendor-mstest-specialstream-0x%x-size", path)); + + // Sample driver uses this proeprty to determine whether to use PathDescriptor2 or PathDescriptor + NTSTATUS tempStatus = Acpi->GetPropertyULong(propertyName, ACPI_METHOD_SECTION_DEVICE_PROPERTIES, vendorPropertiesBlock, &propertyValue); + if (!NT_SUCCESS(tempStatus)) + { + // This special stream is either not supported or does not use PathDescriptor2 + continue; + } + + pPathDesc2 = (PSDCA_PATH_DESCRIPTORS2)ExAllocatePool2( + POOL_FLAG_NON_PAGED, + propertyValue, + DRIVER_TAG); + if (pPathDesc2 == nullptr) + { + status = STATUS_INSUFFICIENT_RESOURCES; + goto exit; + } + + auto cleanup2 = scope_exit([&pPathDesc2]() + { + if (pPathDesc2 != NULL) + { + ExFreePool(pPathDesc2); + pPathDesc2 = NULL; + } + }); + + pPathDesc2->Size = propertyValue; + pPathDesc2->Version = SDCA_PATH_DESCRIPTOR2_VERSION_1; + pPathDesc2->SdcaPath = path; + + // Since we found one specialstream property, all others are required to be present + RETURN_NTSTATUS_IF_FAILED(RtlStringCbPrintfA(propertyName, sizeof(propertyName), "acpi-vendor-mstest-specialstream-0x%x-endpoint-id", path)); + RETURN_NTSTATUS_IF_FAILED(Acpi->GetPropertyULong(propertyName, ACPI_METHOD_SECTION_DEVICE_PROPERTIES, vendorPropertiesBlock, &propertyValue)); + pPathDesc2->EndpointId = propertyValue; + + pPathDesc2->SpecialPathFormat.Format.wFormatTag = WAVE_FORMAT_EXTENSIBLE; + RETURN_NTSTATUS_IF_FAILED(RtlStringCbPrintfA(propertyName, sizeof(propertyName), "acpi-vendor-mstest-specialstream-0x%x-specialpathformat-channels", path)); + RETURN_NTSTATUS_IF_FAILED(Acpi->GetPropertyULong(propertyName, ACPI_METHOD_SECTION_DEVICE_PROPERTIES, vendorPropertiesBlock, &propertyValue)); + pPathDesc2->SpecialPathFormat.Format.nChannels = (WORD)propertyValue; + + RETURN_NTSTATUS_IF_FAILED(RtlStringCbPrintfA(propertyName, sizeof(propertyName), "acpi-vendor-mstest-specialstream-0x%x-specialpathformat-bits-per-sample", path)); + RETURN_NTSTATUS_IF_FAILED(Acpi->GetPropertyULong(propertyName, ACPI_METHOD_SECTION_DEVICE_PROPERTIES, vendorPropertiesBlock, &propertyValue)); + pPathDesc2->SpecialPathFormat.Format.wBitsPerSample = (WORD)propertyValue; + + RETURN_NTSTATUS_IF_FAILED(RtlStringCbPrintfA(propertyName, sizeof(propertyName), "acpi-vendor-mstest-specialstream-0x%x-specialpathformat-samples-per-sec", path)); + RETURN_NTSTATUS_IF_FAILED(Acpi->GetPropertyULong(propertyName, ACPI_METHOD_SECTION_DEVICE_PROPERTIES, vendorPropertiesBlock, &propertyValue)); + pPathDesc2->SpecialPathFormat.Format.nSamplesPerSec = propertyValue; + pPathDesc2->SpecialPathFormat.Format.nBlockAlign = pPathDesc2->SpecialPathFormat.Format.nChannels * pPathDesc2->SpecialPathFormat.Format.wBitsPerSample; + pPathDesc2->SpecialPathFormat.Format.nAvgBytesPerSec = pPathDesc2->SpecialPathFormat.Format.nSamplesPerSec * pPathDesc2->SpecialPathFormat.Format.nBlockAlign; + pPathDesc2->SpecialPathFormat.Format.cbSize = sizeof(WAVEFORMATEXTENSIBLE) - sizeof(WAVEFORMATEX); + + RETURN_NTSTATUS_IF_FAILED(RtlStringCbPrintfA(propertyName, sizeof(propertyName), "acpi-vendor-mstest-specialstream-0x%x-specialpathformat-valid-bits-per-sample", path)); + RETURN_NTSTATUS_IF_FAILED(Acpi->GetPropertyULong(propertyName, ACPI_METHOD_SECTION_DEVICE_PROPERTIES, vendorPropertiesBlock, &propertyValue)); + pPathDesc2->SpecialPathFormat.Samples.wValidBitsPerSample = (WORD)propertyValue; + + RETURN_NTSTATUS_IF_FAILED(RtlStringCbPrintfA(propertyName, sizeof(propertyName), "acpi-vendor-mstest-specialstream-0x%x-specialpathformat-channel-mask", path)); + RETURN_NTSTATUS_IF_FAILED(Acpi->GetPropertyULong(propertyName, ACPI_METHOD_SECTION_DEVICE_PROPERTIES, vendorPropertiesBlock, &propertyValue)); + pPathDesc2->SpecialPathFormat.dwChannelMask = propertyValue; + pPathDesc2->SpecialPathFormat.SubFormat = KSDATAFORMAT_SUBTYPE_PCM; + + RETURN_NTSTATUS_IF_FAILED(RtlStringCbPrintfA(propertyName, sizeof(propertyName), "acpi-vendor-mstest-specialstream-0x%x-desc-count", path)); + RETURN_NTSTATUS_IF_FAILED(Acpi->GetPropertyULong(propertyName, ACPI_METHOD_SECTION_DEVICE_PROPERTIES, vendorPropertiesBlock, &propertyValue)); + pPathDesc2->DescriptorCount = propertyValue; + + for (ULONG j = 0; j < pPathDesc2->DescriptorCount; j++) + { + pPathDesc2->Descriptor[j].Size = sizeof(pPathDesc2->Descriptor[0]); + pPathDesc2->Descriptor[j].Version = SDCA_PATH_DESCRIPTOR2_VERSION_1; + + + // In this sample, we are getting the function informaiton id from audio composition data, however, this id is + // generated at runtime so the real drivers would have information like function number, peripheral id etc. in + // its composition data and then use that to map it to a function information id by querying down stream circuit. + RETURN_NTSTATUS_IF_FAILED(RtlStringCbPrintfA(propertyName, sizeof(propertyName), "acpi-vendor-mstest-specialstream-0x%x-desc-0x%x-func-info-id", path, j)); + RETURN_NTSTATUS_IF_FAILED(Acpi->GetPropertyULong(propertyName, ACPI_METHOD_SECTION_DEVICE_PROPERTIES, vendorPropertiesBlock, &propertyValue)); + pPathDesc2->Descriptor[j].FunctionInformationId = propertyValue; + + RETURN_NTSTATUS_IF_FAILED(RtlStringCbPrintfA(propertyName, sizeof(propertyName), "acpi-vendor-mstest-specialstream-0x%x-desc-0x%x-terminal-id", path, j)); + RETURN_NTSTATUS_IF_FAILED(Acpi->GetPropertyULong(propertyName, ACPI_METHOD_SECTION_DEVICE_PROPERTIES, vendorPropertiesBlock, &propertyValue)); + pPathDesc2->Descriptor[j].TerminalEntityId = propertyValue; + + RETURN_NTSTATUS_IF_FAILED(RtlStringCbPrintfA(propertyName, sizeof(propertyName), "acpi-vendor-mstest-specialstream-0x%x-desc-0x%x-dp-map", path, j)); + RETURN_NTSTATUS_IF_FAILED(Acpi->GetPropertyULong(propertyName, ACPI_METHOD_SECTION_DEVICE_PROPERTIES, vendorPropertiesBlock, &propertyValue)); + pPathDesc2->Descriptor[j].DataPortMap = propertyValue; + + // DataPortMap indicates which DPIndex entries are used, in this sample we'll only use + // a single data port and that will be DPIndex_A. + pPathDesc2->Descriptor[j].DataPortConfig[0].Size = sizeof(pPathDesc2->Descriptor[0].DataPortConfig); + pPathDesc2->Descriptor[j].DataPortConfig[0].EndpointId = pPathDesc2->EndpointId; + RETURN_NTSTATUS_IF_FAILED(RtlStringCbPrintfA(propertyName, sizeof(propertyName), "acpi-vendor-mstest-specialstream-0x%x-desc-0x%x-dp-index-0x0-dp-number", path, j)); + RETURN_NTSTATUS_IF_FAILED(Acpi->GetPropertyULong(propertyName, ACPI_METHOD_SECTION_DEVICE_PROPERTIES, vendorPropertiesBlock, &propertyValue)); + pPathDesc2->Descriptor[j].DataPortConfig[0].DataPortNumber = propertyValue; + + RETURN_NTSTATUS_IF_FAILED(RtlStringCbPrintfA(propertyName, sizeof(propertyName), "acpi-vendor-mstest-specialstream-0x%x-desc-0x%x-dp-index-0x0-dp-modes", path, j)); + RETURN_NTSTATUS_IF_FAILED(Acpi->GetPropertyULong(propertyName, ACPI_METHOD_SECTION_DEVICE_PROPERTIES, vendorPropertiesBlock, &propertyValue)); + pPathDesc2->Descriptor[j].DataPortConfig[0].Modes = propertyValue; + + RETURN_NTSTATUS_IF_FAILED(RtlStringCbPrintfA(propertyName, sizeof(propertyName), "acpi-vendor-mstest-specialstream-0x%x-desc-0x%x-dp-index-0x0-dp-channel-mask", path, j)); + RETURN_NTSTATUS_IF_FAILED(Acpi->GetPropertyULong(propertyName, ACPI_METHOD_SECTION_DEVICE_PROPERTIES, vendorPropertiesBlock, &propertyValue)); + pPathDesc2->Descriptor[j].DataPortConfig[0].ChannelMask = propertyValue; + } + + // Now save it to circuitCtx + circuitCtx->SpecialStreamPathDescriptors2[i] = pPathDesc2; + cleanup2.release(); + } + +exit: + return status; +} + +PAGED_CODE_SEG +NTSTATUS CreateStreamBridge( + _In_ ACX_STREAM_BRIDGE_CONFIG StreamCfg, + _In_ ACXCIRCUIT Circuit, + _In_ ACXPIN Pin, + _In_ DSP_PIN_CONTEXT* PinCtx, + _In_ ULONG BridgeDataPortNumber, + _In_ ULONG BridgeEndpointId, + _In_opt_ PSDCA_PATH_DESCRIPTORS2 PathDescriptors, + _In_ BOOL Render +) +{ + PAGED_CODE(); + + NTSTATUS status = STATUS_SUCCESS; + + DECLARE_CONST_ACXOBJECTBAG_SOUNDWIRE_PROPERTY_NAME(EndpointId); + DECLARE_CONST_ACXOBJECTBAG_SOUNDWIRE_PROPERTY_NAME(DataPortNumber); + + WDF_OBJECT_ATTRIBUTES attributes; + WDF_OBJECT_ATTRIBUTES_INIT(&attributes); + + attributes.ParentObject = Pin; + + ACX_OBJECTBAG_CONFIG objBagCfg; + ACXOBJECTBAG objBag = NULL; + ACX_OBJECTBAG_CONFIG_INIT(&objBagCfg); + WDF_OBJECT_ATTRIBUTES_INIT(&attributes); + attributes.ParentObject = Circuit; + RETURN_NTSTATUS_IF_FAILED(AcxObjectBagCreate(&attributes, &objBagCfg, &objBag)); + + DECLARE_CONST_ACXOBJECTBAG_DRIVER_PROPERTY_NAME(msft, TestUI4); + RETURN_NTSTATUS_IF_FAILED(AcxObjectBagAddUI4(objBag, &TestUI4, _DSP_STREAM_PROPERTY_UI4_VALUE)); + + // EndpointId, DataPortNumber, and DPNo included for backwards compatibility. + // If SdcaPropertyPathDescriptors2 is included in the object bag, these will be ignored. + RETURN_NTSTATUS_IF_FAILED(AcxObjectBagAddUI4(objBag, &EndpointId, BridgeEndpointId)); + RETURN_NTSTATUS_IF_FAILED(AcxObjectBagAddUI4(objBag, &DataPortNumber, BridgeDataPortNumber)); + + DECLARE_CONST_ACXOBJECTBAG_SOUNDWIRE_PROPERTY_NAME(DPNo); + RETURN_NTSTATUS_IF_FAILED(AcxObjectBagAddUI4(objBag, &DPNo, BridgeDataPortNumber)); + + if (PathDescriptors && PathDescriptors->Size >= sizeof(SDCA_PATH_DESCRIPTORS2)) + { + // For uniform aggregated devices and non-aggregated devices, we can save the SdcaPropertyPathDescriptors2 + // now to the stream bridge. + + // If the aggregated devices have different configurations (such as a different Channel Mask) the + // SdcaPropertyPathDescriptors2 should be added to the stream bridge when the pin is connected, since the + // FunctionInformationId is determined at run time based on the order that the aggregated devices are discovered. + + // Apply the EndpointID to the PathDescriptors structures + PathDescriptors->EndpointId = BridgeEndpointId; + + // The PathDescriptors->Descriptor[n].DataPortConfig[m].EndpointId value is ignored + + WDFMEMORY pathDescriptorsMemory; + RETURN_NTSTATUS_IF_FAILED(WdfMemoryCreatePreallocated(WDF_NO_OBJECT_ATTRIBUTES, PathDescriptors, PathDescriptors->Size, &pathDescriptorsMemory)); + auto memory_free = scope_exit([&pathDescriptorsMemory]() + { + WdfObjectDelete(pathDescriptorsMemory); + pathDescriptorsMemory = nullptr; + }); + + // For sample simplicity we always add the path descriptors here. + // If the EvtPinConnected discovers connected aggregated audio functions it will overwrite this. + RETURN_NTSTATUS_IF_FAILED(AcxObjectBagAddBlob(objBag, &SdcaPropertyPathDescriptors2, pathDescriptorsMemory)); + } + + // Save the Object Bag that's being assigned to the stream bridge + // This will be updated at Pin Connect time if the connected endpoint is aggregated and uses + // different data ports for each of the aggregated audio functions + // The AcxObjectBag's lifetime is tied to the Circuit, so the Pin will be able to access it + // for the Pin's entire lifetime. + PinCtx->HostStreamObjBag = objBag; + + // + // Add a stream BRIDGE. + // + PCGUID inModes[] = + { + &AUDIO_SIGNALPROCESSINGMODE_RAW, + &AUDIO_SIGNALPROCESSINGMODE_DEFAULT, + }; + + if (Render) { + StreamCfg.InModesCount = SIZEOF_ARRAY(inModes); + StreamCfg.InModes = inModes; + } + + // Do not specify InModes for capture - this will prevent the ACX framework from adding created streams to this stream + // bridge automatically. We want to add the stream bridges manually since we don't want KWS streams added. + StreamCfg.OutMode = &AUDIO_SIGNALPROCESSINGMODE_RAW; + StreamCfg.OutStreamVarArguments = objBag; + + // Uncomment this line to reverse the change-state sequence notifications. + //streamCfg.Flags |= AcxStreamBridgeInvertChangeStateSequence; + + ACXSTREAMBRIDGE streamBridge = NULL; + RETURN_NTSTATUS_IF_FAILED(AcxStreamBridgeCreate(Circuit, &attributes, &StreamCfg, &streamBridge)); + + if (!Render) { + PinCtx->HostStreamBridge = streamBridge; + } + + RETURN_NTSTATUS_IF_FAILED(AcxPinAddStreamBridges(Pin, &streamBridge, 1)); + + return status; +} + +PAGED_CODE_SEG +NTSTATUS ConnectCaptureCircuitElements( + _In_ ULONG ElementCount, + _In_reads_(ElementCount) ACXELEMENT* Elements, + _In_ ACXCIRCUIT Circuit +) +{ + PAGED_CODE(); + + NTSTATUS status = STATUS_SUCCESS; + + // connection between each element, plus the connection to the circuit, + // and an extra connection for the kws pin. + const int numElements = 3; + const int numConnections = numElements + 2; + + // + // Explicitly connect the circuit/elements. Note that driver doens't + // need to perform this step when circuit/elements are connected in the + // same order as they were added to the circuit. By default ACX connects + // the elements starting from the sink circuit pin and ending with the + // source circuit pin for devices. + // + // circuit.pin[default_sink] -> 1st element.pin[default_in] + // 1st element.pin[default_out] -> 2nd element.pin[default_in] + // 2nd element.pin[default_out] -> circuit.pin[default_source] + // + + ACX_CONNECTION connections[numConnections]; + ACX_CONNECTION_INIT(&connections[0], Circuit, Elements[0]); + + ACX_CONNECTION_INIT(&connections[1], Elements[0], Elements[ElementCount-2]); + ACX_CONNECTION_INIT(&connections[2], Elements[ElementCount-2], Elements[ElementCount-1]); + ACX_CONNECTION_INIT(&connections[3], Elements[ElementCount-1], Circuit); + ACX_CONNECTION_INIT(&connections[4], Elements[ElementCount-1], Circuit); + connections[4].ToPin.Id = 1; + + // + // Add the connections linking circuit to elements. + // + RETURN_NTSTATUS_IF_FAILED(AcxCircuitAddConnections(Circuit, connections, SIZEOF_ARRAY(connections))); + + return status; +} + +PAGED_CODE_SEG +NTSTATUS ConnectRenderCircuitElements( + _In_ ACXAUDIOENGINE AudioEngineElement, + _In_ ACXCIRCUIT Circuit +) +{ + PAGED_CODE(); + + NTSTATUS status = STATUS_SUCCESS; + + // + // Explicitly connect the circuit/elements. Note that driver doesn't + // need to perform this step when circuit/elements are connected in the + // same order as they were added to the circuit. By default ACX connects + // the elements starting from the sink circuit pin and ending with the + // source circuit pin for both render and capture devices. + // + // Circuit layout + // ----------------------------------------- + // | | + // | -------------------- | + // Host -0->|-----1->| |-0-------->|-3-> Bridge Pin + // | | Audio Engine | | + // Offload -1->|-----2->| Node |-3--| | + // | |------------------| | | + // | | | + // Loopback <-2-|<------------------------------ | | + // | | + // | | + // |---------------------------------------| + // + + ACX_CONNECTION connections[4]; + + ACX_CONNECTION_INIT(&connections[0], Circuit, AudioEngineElement); + connections[0].FromPin.Id = DspPinTypeHost; + connections[0].ToPin.Id = 1; + + ACX_CONNECTION_INIT(&connections[1], Circuit, AudioEngineElement); + connections[1].FromPin.Id = DspPinTypeOffload; + connections[1].ToPin.Id = 2; + + ACX_CONNECTION_INIT(&connections[2], AudioEngineElement, Circuit); + connections[2].ToPin.Id = DspPinTypeLoopback; + connections[2].FromPin.Id = 3; + + ACX_CONNECTION_INIT(&connections[3], AudioEngineElement, Circuit); + connections[3].ToPin.Id = DspPinTypeBridge; + connections[3].FromPin.Id = 0; + + // + // Add the connections linking circuit to elements. + // + RETURN_NTSTATUS_IF_FAILED(AcxCircuitAddConnections(Circuit, connections, SIZEOF_ARRAY(connections))); + + return status; + +} + +PAGED_CODE_SEG +NTSTATUS CreateAudioEngine( + _In_ ACXCIRCUIT Circuit, + _In_reads_(DspPinType_Count) ACXPIN* Pins, + _Out_ ACXAUDIOENGINE* AudioEngineElement +) +{ + PAGED_CODE(); + + NTSTATUS status = STATUS_SUCCESS; + + WDF_OBJECT_ATTRIBUTES attributes; + WDF_OBJECT_ATTRIBUTES_INIT(&attributes); + + ///////////////////////////////////////////////////////// + // + // Create two elements to handle volume and mute for the audioengine + // element + + // Mute + ACX_MUTE_CALLBACKS muteCallbacks; + ACX_MUTE_CALLBACKS_INIT(&muteCallbacks); + muteCallbacks.EvtAcxMuteAssignState = DspR_EvtMuteAssignState; + muteCallbacks.EvtAcxMuteRetrieveState = DspR_EvtMuteRetrieveState; + + ACX_MUTE_CONFIG muteCfg; + ACX_MUTE_CONFIG_INIT(&muteCfg); + muteCfg.ChannelsCount = MAX_CHANNELS; + muteCfg.Name = &KSAUDFNAME_WAVE_MUTE; + muteCfg.Callbacks = &muteCallbacks; + + WDF_OBJECT_ATTRIBUTES_INIT_CONTEXT_TYPE(&attributes, DSP_MUTE_ELEMENT_CONTEXT); + attributes.ParentObject = Circuit; + + ACXMUTE muteElement; + RETURN_NTSTATUS_IF_FAILED(AcxMuteCreate(Circuit, &attributes, &muteCfg, &muteElement)); + + // Volume + ACX_VOLUME_CALLBACKS volumeCallbacks; + ACX_VOLUME_CALLBACKS_INIT(&volumeCallbacks); + volumeCallbacks.EvtAcxRampedVolumeAssignLevel = DspR_EvtRampedVolumeAssignLevel; + volumeCallbacks.EvtAcxVolumeRetrieveLevel = DspR_EvtVolumeRetrieveLevel; + + ACX_VOLUME_CONFIG volumeCfg; + ACX_VOLUME_CONFIG_INIT(&volumeCfg); + volumeCfg.ChannelsCount = MAX_CHANNELS; + volumeCfg.Minimum = VOLUME_LEVEL_MINIMUM; + volumeCfg.Maximum = VOLUME_LEVEL_MAXIMUM; + volumeCfg.SteppingDelta = VOLUME_STEPPING; + volumeCfg.Name = &KSAUDFNAME_VOLUME_CONTROL; + volumeCfg.Callbacks = &volumeCallbacks; + + WDF_OBJECT_ATTRIBUTES_INIT_CONTEXT_TYPE(&attributes, DSP_VOLUME_ELEMENT_CONTEXT); + attributes.ParentObject = Circuit; + + ACXVOLUME volumeElement; + RETURN_NTSTATUS_IF_FAILED(AcxVolumeCreate(Circuit, &attributes, &volumeCfg, &volumeElement)); + + // + // Create peakmeter element for Audio engine + // + ACX_PEAKMETER_CALLBACKS peakmeterCallbacks; + ACX_PEAKMETER_CALLBACKS_INIT(&peakmeterCallbacks); + peakmeterCallbacks.EvtAcxPeakMeterRetrieveLevel = DspR_EvtPeakMeterRetrieveLevelCallback; + + ACX_PEAKMETER_CONFIG peakmeterCfg; + ACX_PEAKMETER_CONFIG_INIT(&peakmeterCfg); + peakmeterCfg.ChannelsCount = MAX_CHANNELS; + peakmeterCfg.Minimum = PEAKMETER_MINIMUM; + peakmeterCfg.Maximum = PEAKMETER_MAXIMUM; + peakmeterCfg.SteppingDelta = PEAKMETER_STEPPING_DELTA; + peakmeterCfg.Callbacks = &peakmeterCallbacks; + + WDF_OBJECT_ATTRIBUTES_INIT_CONTEXT_TYPE(&attributes, DSP_PEAKMETER_ELEMENT_CONTEXT); + attributes.ParentObject = Circuit; + + ACXPEAKMETER peakmeterElement; + RETURN_NTSTATUS_IF_FAILED(AcxPeakMeterCreate(Circuit, &attributes, &peakmeterCfg, &peakmeterElement)); + ASSERT(peakmeterElement != NULL); + + PDSP_PEAKMETER_ELEMENT_CONTEXT peakmeterCtx; + peakmeterCtx = GetDspPeakMeterElementContext(peakmeterElement); + ASSERT(peakmeterCtx); + peakmeterCtx->peakMeter = GetDspCircuitContext(Circuit)->peakMeter; + + GetDspCircuitContext(Circuit)->PeakMeterElement = peakmeterElement; + + // + // Create Audio Engine + // + ACX_AUDIOENGINE_CALLBACKS audioEngineCallbacks; + ACX_AUDIOENGINE_CALLBACKS_INIT(&audioEngineCallbacks); + audioEngineCallbacks.EvtAcxAudioEngineRetrieveBufferSizeLimits = DspR_EvtAcxAudioEngineRetrieveBufferSizeLimits; + audioEngineCallbacks.EvtAcxAudioEngineAssignEffectsState = DspR_EvtAcxAudioEngineAssignEffectsState; + audioEngineCallbacks.EvtAcxAudioEngineRetrieveEffectsState = DspR_EvtAcxAudioEngineRetrieveEffectsState; + audioEngineCallbacks.EvtAcxAudioEngineRetrieveEngineMixFormat = DspR_EvtAcxAudioEngineRetrieveEngineMixFormat; + audioEngineCallbacks.EvtAcxAudioEngineAssignEngineDeviceFormat = DspR_EvtAcxAudioEngineAssignEngineDeviceFormat; + + ACX_AUDIOENGINE_CONFIG audioEngineCfg; + ACX_AUDIOENGINE_CONFIG_INIT(&audioEngineCfg); + audioEngineCfg.HostPin = Pins[DspPinTypeHost]; + audioEngineCfg.OffloadPin = Pins[DspPinTypeOffload]; + audioEngineCfg.LoopbackPin = Pins[DspPinTypeLoopback]; + audioEngineCfg.VolumeElement = volumeElement; + audioEngineCfg.MuteElement = muteElement; + audioEngineCfg.PeakMeterElement = peakmeterElement; + audioEngineCfg.Callbacks = &audioEngineCallbacks; + + WDF_OBJECT_ATTRIBUTES_INIT_CONTEXT_TYPE(&attributes, DSP_ENGINE_CONTEXT); + attributes.ParentObject = Circuit; + + RETURN_NTSTATUS_IF_FAILED(AcxAudioEngineCreate(Circuit, &attributes, &audioEngineCfg, AudioEngineElement)); + + return status; +} + +PAGED_CODE_SEG +NTSTATUS SendProperty( + _In_ WDFOBJECT AcxTarget, + _Inout_ PACX_REQUEST_PARAMETERS PropertyParameters, + _Out_opt_ PULONG_PTR Information +) +{ + PAGED_CODE(); + + if (Information) + { + *Information = 0; + } + + // + // First step: Determine the WDFIOTARGET to which the property request will be sent + // + WDFIOTARGET ioTarget = nullptr; + if (PropertyParameters->Parameters.Property.ItemType == AcxItemTypePin) + { + ioTarget = AcxTargetPinGetWdfIoTarget((ACXTARGETPIN)AcxTarget); + } + else if (PropertyParameters->Parameters.Property.ItemType == AcxItemTypeElement) + { + ioTarget = AcxTargetElementGetWdfIoTarget((ACXTARGETELEMENT)AcxTarget); + } + else if (PropertyParameters->Parameters.Property.ItemType == AcxItemTypeCircuit) + { + ioTarget = AcxTargetCircuitGetWdfIoTarget((ACXTARGETCIRCUIT)AcxTarget); + } + else + { + RETURN_NTSTATUS(STATUS_INVALID_PARAMETER); + } + + // + // Create the request + // + WDF_OBJECT_ATTRIBUTES attributes; + WDF_OBJECT_ATTRIBUTES_INIT(&attributes); + attributes.ParentObject = AcxTarget; + + WDFREQUEST request; + RETURN_NTSTATUS_IF_FAILED(WdfRequestCreate(&attributes, ioTarget, &request)); + auto request_free = scope_exit([&request]() + { + WdfObjectDelete(request); + request = nullptr; + }); + + // + // ACX framework will format the request properly depending on the type of the target + // + if (PropertyParameters->Parameters.Property.ItemType == AcxItemTypePin) + { + RETURN_NTSTATUS_IF_FAILED(AcxTargetPinFormatRequestForProperty((ACXTARGETPIN)AcxTarget, request, PropertyParameters)); + } + else if (PropertyParameters->Parameters.Property.ItemType == AcxItemTypeElement) + { + RETURN_NTSTATUS_IF_FAILED(AcxTargetElementFormatRequestForProperty((ACXTARGETELEMENT)AcxTarget, request, PropertyParameters)); + } + else if (PropertyParameters->Parameters.Property.ItemType == AcxItemTypeCircuit) + { + RETURN_NTSTATUS_IF_FAILED(AcxTargetCircuitFormatRequestForProperty((ACXTARGETCIRCUIT)AcxTarget, request, PropertyParameters)); + } + + // + // Send the request synchronously, with a timeout + // + WDF_REQUEST_SEND_OPTIONS sendOptions; + WDF_REQUEST_SEND_OPTIONS_INIT(&sendOptions, WDF_REQUEST_SEND_OPTION_SYNCHRONOUS); + WDF_REQUEST_SEND_OPTIONS_SET_TIMEOUT(&sendOptions, WDF_REL_TIMEOUT_IN_SEC(REQUEST_TIMEOUT_SECONDS)); + + if (!WdfRequestSend(request, ioTarget, &sendOptions)) + { + // + // The framework failed to send the request. + // + RETURN_NTSTATUS_IF_FAILED(WdfRequestGetStatus(request)); + } + + // + // The request was successfully delivered and handled. The status will be based on the target's handling + // + if (Information) + { + *Information = WdfRequestGetInformation(request); + } + + return WdfRequestGetStatus(request); +} + +// Nonpaged, since this will be called in power up situations +#pragma code_seg() +VOID CircuitRequestPreprocess( + _In_ ACXOBJECT Object, + _In_ ACXCONTEXT DriverContext, + _In_ WDFREQUEST Request + ) +/*++ + +Routine Description: + + This Circuit Request Preprocess routine will forward any Volume + or Mute requests to the appropriate downstream circuit, if we've + discovered a downstream circuit that handles Volume and Mute + +--*/ +{ + NTSTATUS status = STATUS_NOT_SUPPORTED; + ACX_REQUEST_PARAMETERS params; + ACX_REQUEST_PARAMETERS targetParams; + PDSP_CIRCUIT_CONTEXT circuitCtx; + ACXELEMENT element; + ULONG_PTR information = 0; + ACXTARGETELEMENT targetElement = nullptr; + GUID propertySet; + ULONG propertyId; + BOOLEAN isMute = FALSE; + BOOLEAN isVolume = FALSE; + + // Preprocess will be called very frequently. Don't trace enter/exit. + //DrvLogEnter(g_SDCAVDspLog); + + UNREFERENCED_PARAMETER(DriverContext); + + ASSERT(Object != NULL); + ASSERT(DriverContext); + ASSERT(Request); + + ACX_REQUEST_PARAMETERS_INIT(¶ms); + AcxRequestGetParameters(Request, ¶ms); + + propertySet = params.Parameters.Property.Set; + propertyId = params.Parameters.Property.Id; + circuitCtx = GetDspCircuitContext(Object); + + if (circuitCtx == nullptr || + params.Parameters.Property.ItemType != AcxItemTypeElement) + { + // We only handle requests for our render circuit (which must have our context) + // We only forward element requests to the child paths + (VOID)AcxCircuitDispatchAcxRequest((ACXCIRCUIT)Object, Request); + return; + } + + if (IsEqualGUID(propertySet, KSPROPSETID_Audio) && propertyId == KSPROPERTY_AUDIO_VOLUMELEVEL) + { + isVolume = TRUE; + } + else if (IsEqualGUID(propertySet, KSPROPSETID_Audio) && propertyId == KSPROPERTY_AUDIO_MUTE) + { + isMute = TRUE; + } + // Do not forward KSPROPERTY_AUDIOENGINE_VOLUMELEVEL - that is only valid for a stream property. + + if (!isVolume && !isMute) + { + // Only handle Volume and Mute requests + (VOID)AcxCircuitDispatchAcxRequest((ACXCIRCUIT)Object, Request); + return; + } + + element = AcxCircuitGetElementById((ACXCIRCUIT)Object, params.Parameters.Property.ItemId); + if (!element) + { + // We only handle requests for the volume or mute elements, and this isn't an element + (VOID)AcxCircuitDispatchAcxRequest((ACXCIRCUIT)Object, Request); + return; + } + + if (isVolume) + { + targetElement = circuitCtx->TargetVolumeHandler; + } + else if (isMute) + { + targetElement = circuitCtx->TargetMuteHandler; + } + + if (targetElement == nullptr) + { + // We only handle requests for the volume or mute elements if we have a target. + (VOID)AcxCircuitDispatchAcxRequest((ACXCIRCUIT)Object, Request); + return; + } + + if (isVolume && (GetDspVolumeElementContext(element) == nullptr && GetDspEngineContext(element) == nullptr)) + { + // Volume request that isn't for our volume or audioengine element? + (VOID)AcxCircuitDispatchAcxRequest((ACXCIRCUIT)Object, Request); + return; + } + + if (isMute && (GetDspMuteElementContext(element) == nullptr && GetDspEngineContext(element) == nullptr)) + { + // Mute request that isn't for our mute or audioengine element? + (VOID)AcxCircuitDispatchAcxRequest((ACXCIRCUIT)Object, Request); + return; + } + + propertySet = params.Parameters.Property.Set; + propertyId = params.Parameters.Property.Id; + + ACX_REQUEST_PARAMETERS_INIT_PROPERTY(&targetParams, + propertySet, + propertyId, + params.Parameters.Property.Verb, + params.Parameters.Property.ItemType, + AcxTargetElementGetId(targetElement), + params.Parameters.Property.Control, + params.Parameters.Property.ControlCb, + params.Parameters.Property.Value, + params.Parameters.Property.ValueCb); + + status = SendProperty(targetElement, &targetParams, &information); + + WdfRequestCompleteWithInformation(Request, status, information); +} + +PAGED_CODE_SEG +NTSTATUS CreateTargetCircuit( + _In_ ACXCIRCUIT Circuit, + _In_ PKSPIN_PHYSICALCONNECTION Connection, + _In_ ULONG ConnectionSize, + _Out_ ACXTARGETCIRCUIT * TargetCircuit +) +{ + PAGED_CODE(); + + // We have the physical connection. Create a target circuit for it. + size_t symbolicLinkSize; + // Size of the string is no more than the total size of the value returned, less the size of the physicalconnection struct, + // plus the first character of the link (which is included in the physicalconnection struct) + symbolicLinkSize = ConnectionSize - sizeof(KSPIN_PHYSICALCONNECTION) + sizeof(WCHAR); + if (symbolicLinkSize > USHORT_MAX) + { + // Symbolic Link has to fit in UNICODE_STRING which uses USHORT to hold Length/MaximumLength + RETURN_NTSTATUS_MSG(STATUS_UNSUCCESSFUL, L"Physical connection too large for unicode_string %lld", symbolicLinkSize); + } + + UNICODE_STRING symbolicLink{ 0 }; + symbolicLink.MaximumLength = (USHORT)symbolicLinkSize; + symbolicLink.Buffer = Connection->SymbolicLinkName; + // preload the length + (void)RtlStringCbLengthW(symbolicLink.Buffer, symbolicLink.MaximumLength, &symbolicLinkSize); + symbolicLink.Length = (USHORT)symbolicLinkSize; + + WDF_OBJECT_ATTRIBUTES attributes; + WDF_OBJECT_ATTRIBUTES_INIT(&attributes); + attributes.ParentObject = Circuit; + + WDFSTRING link; + RETURN_NTSTATUS_IF_FAILED(WdfStringCreate(&symbolicLink, &attributes, &link)); + auto link_free = scope_exit([&link]() + { + if (link) + { + WdfObjectDelete(link); + link = nullptr; + } + }); + + ACX_TARGET_CIRCUIT_CONFIG targetCktCfg; + ACX_TARGET_CIRCUIT_CONFIG_INIT(&targetCktCfg); + targetCktCfg.SymbolicLinkName = link; + + WDF_OBJECT_ATTRIBUTES_INIT(&attributes); + attributes.ParentObject = Circuit; + + RETURN_NTSTATUS_IF_FAILED(AcxTargetCircuitCreate(AcxCircuitGetWdfDevice(Circuit), &attributes, &targetCktCfg, TargetCircuit)); + + return STATUS_SUCCESS; +} + +PAGED_CODE_SEG +NTSTATUS FindDownstreamVolumeMute( + _In_ ACXCIRCUIT Circuit, + _In_ ACXTARGETCIRCUIT TargetCircuit +) +{ + NTSTATUS status; + PDSP_CIRCUIT_CONTEXT circuitCtx; + ACX_REQUEST_PARAMETERS params; + + PAGED_CODE(); + + circuitCtx = GetDspCircuitContext(Circuit); + + // + // Note on behavior: This search algorithm will select the last Volume and Mute elements that are both + // present in the same circuit in the Endpoint Path. + // This logic could be updated to select the last Volume and Mute elements, or the first or last + // Volume or the first or last Mute element. + // + + // + // First look through target's pins to determine if there's another circuit downstream. + // If there is, we'll look at that circuit for volume/mute. + // + for (ULONG pinIndex = 0; pinIndex < AcxTargetCircuitGetPinsCount(TargetCircuit); ++pinIndex) + { + ACXTARGETPIN targetPin = AcxTargetCircuitGetTargetPin(TargetCircuit, pinIndex); + ULONG targetPinFlow = 0; + ACX_REQUEST_PARAMETERS_INIT_PROPERTY(¶ms, + KSPROPSETID_Pin, + KSPROPERTY_PIN_DATAFLOW, + AcxPropertyVerbGet, + AcxItemTypePin, + AcxTargetPinGetId(targetPin), + nullptr, 0, + &targetPinFlow, + sizeof(targetPinFlow)); + + RETURN_NTSTATUS_IF_FAILED(SendProperty(targetPin, ¶ms, nullptr)); + + // + // Searching for the downstream pins. For Render, these are the dataflow out pins + // + if (circuitCtx->IsRenderCircuit && targetPinFlow != KSPIN_DATAFLOW_OUT) + { + continue; + } + else if (!circuitCtx->IsRenderCircuit && targetPinFlow != KSPIN_DATAFLOW_IN) + { + continue; + } + + // Get the target pin's physical connection. We'll do this twice: first to get size and allocate, second to get the connection + PKSPIN_PHYSICALCONNECTION pinConnection = nullptr; + auto connection_free = scope_exit([&pinConnection]() + { + if (pinConnection) + { + ExFreePool(pinConnection); + pinConnection = nullptr; + } + }); + + ULONG pinConnectionSize = 0; + ULONG_PTR info = 0; + for (ULONG i = 0; i < 2; ++i) + { + ACX_REQUEST_PARAMETERS_INIT_PROPERTY(¶ms, + KSPROPSETID_Pin, + KSPROPERTY_PIN_PHYSICALCONNECTION, + AcxPropertyVerbGet, + AcxItemTypePin, + AcxTargetPinGetId(targetPin), + nullptr, 0, + pinConnection, + pinConnectionSize); + + status = SendProperty(targetPin, ¶ms, &info); + + if (status == STATUS_BUFFER_OVERFLOW) + { + // Pin connection already allocated, so how did this fail? + RETURN_NTSTATUS_IF_TRUE(pinConnection != nullptr, status); + + pinConnectionSize = (ULONG)info; + pinConnection = (PKSPIN_PHYSICALCONNECTION)ExAllocatePool2(POOL_FLAG_NON_PAGED, pinConnectionSize, DRIVER_TAG); + // RETURN_NTSTATUS_IF_NULL_ALLOC causes compile errors + RETURN_NTSTATUS_IF_TRUE(pinConnection == nullptr, STATUS_INSUFFICIENT_RESOURCES); + } + else if (!NT_SUCCESS(status)) + { + // There are no more connected circuits. Continue with processing this circuit. + break; + } + } + + if (!NT_SUCCESS(status)) + { + // There are no more connected circuits. Continue handling this circuit. + break; + } + + ACXTARGETCIRCUIT nextTargetCircuit; + RETURN_NTSTATUS_IF_FAILED(CreateTargetCircuit(Circuit, pinConnection, pinConnectionSize, &nextTargetCircuit)); + auto circuit_free = scope_exit([&nextTargetCircuit]() + { + if (nextTargetCircuit) + { + WdfObjectDelete(nextTargetCircuit); + nextTargetCircuit = nullptr; + } + }); + + RETURN_NTSTATUS_IF_FAILED_UNLESS_ALLOWED(FindDownstreamVolumeMute(Circuit, nextTargetCircuit), STATUS_NOT_FOUND); + if (circuitCtx->TargetVolumeMuteCircuit == nextTargetCircuit) + { + circuitCtx->TargetCircuitToDelete = nextTargetCircuit; + + // The nextTargetCircuit is the owner of the volume/mute target elements. + // We will delete it when the pin is disconnected. + circuit_free.release(); + + // We found volume/mute. Return. + return STATUS_SUCCESS; + } + + // There's only one downstream pin on the current targetcircuit, and we just processed it. + break; + } + + // + // Search the target circuit for a volume or mute element. + // This sample code doesn't support downstream audioengine elements. + // + for (ULONG elementIndex = 0; elementIndex < AcxTargetCircuitGetElementsCount(TargetCircuit); ++elementIndex) + { + ACXTARGETELEMENT targetElement = AcxTargetCircuitGetTargetElement(TargetCircuit, elementIndex); + GUID elementType = AcxTargetElementGetType(targetElement); + + if (IsEqualGUID(elementType, KSNODETYPE_VOLUME) && + circuitCtx->TargetVolumeHandler == nullptr) + { + // Found Volume + circuitCtx->TargetVolumeHandler = targetElement; + } + if (IsEqualGUID(elementType, KSNODETYPE_MUTE) && + circuitCtx->TargetMuteHandler == nullptr) + { + // Found Mute + circuitCtx->TargetMuteHandler = targetElement; + } + } + + if (circuitCtx->TargetVolumeHandler && circuitCtx->TargetMuteHandler) + { + circuitCtx->TargetVolumeMuteCircuit = TargetCircuit; + return STATUS_SUCCESS; + } + + // + // If we only found one of volume or mute, keep searching for both + // + if (circuitCtx->TargetVolumeHandler || circuitCtx->TargetMuteHandler) + { + circuitCtx->TargetMuteHandler = circuitCtx->TargetVolumeHandler = nullptr; + } + + return STATUS_NOT_FOUND; +} + +PAGED_CODE_SEG +NTSTATUS +ReplicateFormatsForAudioEngine( + _In_ ACXAUDIOENGINE AudioEngine, + _In_ ACXTARGETCIRCUIT TargetCircuit, + _In_ ULONG TargetPinId +) +{ + PAGED_CODE(); + + ACXTARGETPIN targetPin; + targetPin = AcxTargetCircuitGetTargetPin(TargetCircuit, TargetPinId); + if (!targetPin) + { + RETURN_NTSTATUS(STATUS_UNSUCCESSFUL); + } + + ACXDATAFORMATLIST targetFormatList; + // We expect at least Raw format in SDCA downstream circuits + RETURN_NTSTATUS_IF_FAILED(AcxTargetPinRetrieveModeDataFormatList(targetPin, &AUDIO_SIGNALPROCESSINGMODE_RAW, &targetFormatList)); + + ACXDATAFORMATLIST localFormatList = AcxAudioEngineGetDeviceFormatList(AudioEngine); + + RETURN_NTSTATUS_IF_FAILED(SdcaVad_ClearDataFormatList(localFormatList)); + + ULONG formatCount = 0; + RETURN_NTSTATUS_IF_FAILED(SdcaVad_CopyFormats(targetFormatList, localFormatList, &formatCount)); + + if (formatCount == 0) + { + RETURN_NTSTATUS(STATUS_NO_MATCH); + } + + return STATUS_SUCCESS; +} + +PAGED_CODE_SEG +NTSTATUS +ReplicateFormatsForPin( + _In_ ACXPIN Pin, + _In_ ACXTARGETCIRCUIT TargetCircuit, + _In_ ULONG TargetPinId +) +{ + PAGED_CODE(); + + ACXTARGETPIN targetPin; + targetPin = AcxTargetCircuitGetTargetPin(TargetCircuit, TargetPinId); + if (!targetPin) + { + RETURN_NTSTATUS(STATUS_UNSUCCESSFUL); + } + + // Don't delete the target pin - it will be cleaned up when the target circuit is cleaned up by ACX + + GUID targetModes[] = + { + AUDIO_SIGNALPROCESSINGMODE_RAW, + AUDIO_SIGNALPROCESSINGMODE_DEFAULT, + AUDIO_SIGNALPROCESSINGMODE_COMMUNICATIONS, + AUDIO_SIGNALPROCESSINGMODE_SPEECH + }; + + ULONG totalFormats = 0; + + for (ULONG modeIdx = 0; modeIdx < ARRAYSIZE(targetModes); ++modeIdx) + { + ACXDATAFORMATLIST targetFormatList; + ACXDATAFORMATLIST localFormatList = nullptr; + + NTSTATUS status = AcxTargetPinRetrieveModeDataFormatList(targetPin, targetModes + modeIdx, &targetFormatList); + if (!NT_SUCCESS(status)) + { + // If the downstream pin doesn't support any formats for this mode, make sure we clear out our pin's + // formats for this mode as well. + if (modeIdx == 0) + { + localFormatList = AcxPinGetRawDataFormatList(Pin); + } + else + { + // Ignore the status + AcxPinRetrieveModeDataFormatList(Pin, targetModes + modeIdx, &localFormatList); + } + if (localFormatList) + { + RETURN_NTSTATUS_IF_FAILED(SdcaVad_ClearDataFormatList(localFormatList)); + } + continue; + } + + RETURN_NTSTATUS_IF_FAILED(SdcaVad_RetrieveOrCreateDataFormatList(Pin, targetModes + modeIdx, &localFormatList)); + + RETURN_NTSTATUS_IF_FAILED(SdcaVad_ClearDataFormatList(localFormatList)); + + ULONG formatCount = 0; + RETURN_NTSTATUS_IF_FAILED(SdcaVad_CopyFormats(targetFormatList, localFormatList, &formatCount)); + + totalFormats += formatCount; + } + + if (totalFormats == 0) + { + return STATUS_NO_MATCH; + } + + return STATUS_SUCCESS; +} + + diff --git a/audio/Soundwire/Samples/SdcaVad/SdcaVDsp/CircuitHelper.h b/audio/Soundwire/Samples/SdcaVad/SdcaVDsp/CircuitHelper.h new file mode 100644 index 000000000..9324e7eb1 --- /dev/null +++ b/audio/Soundwire/Samples/SdcaVad/SdcaVDsp/CircuitHelper.h @@ -0,0 +1,135 @@ +/*++ + + THIS CODE AND INFORMATION IS PROVIDED "AS IS" WITHOUT WARRANTY OF ANY + KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE + IMPLIED WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A PARTICULAR + PURPOSE. + +Module Name: + + CircuitHelper.h + +Abstract: + + This module contains helper functions for render.cpp and capture.cpp files. + +Environment: + + Kernel mode + +--*/ + +#include "AcpiReader.h" + +using namespace ACPIREADER; + +PAGED_CODE_SEG +NTSTATUS CreateCaptureCircuit( + _In_ PACXCIRCUIT_INIT CircuitInit, + _In_ UNICODE_STRING CircuitName, + _In_ WDFDEVICE Device, + _Out_ ACXCIRCUIT* Circuit +); + +PAGED_CODE_SEG +NTSTATUS CreateRenderCircuit( + _In_ PACXCIRCUIT_INIT CircuitInit, + _In_ UNICODE_STRING CircuitName, + _In_ WDFDEVICE Device, + _Out_ ACXCIRCUIT* Circuit +); + +PAGED_CODE_SEG +NTSTATUS AllocateFormat( + _In_ KSDATAFORMAT_WAVEFORMATEXTENSIBLE WaveFormat, + _In_ ACXCIRCUIT Circuit, + _In_ WDFDEVICE Device, + _Out_ ACXDATAFORMAT* Format +); + +PAGED_CODE_SEG +NTSTATUS CreatePin( + _In_ ACX_PIN_TYPE PinType, + _In_ ACXCIRCUIT Circuit, + _In_ ACX_PIN_COMMUNICATION Communication, + _In_ const GUID* Category, + _In_ ACX_PIN_CALLBACKS* PinCallbacks, + _In_ ULONG PinStreamCount, + _In_ bool Mic, + _Out_ ACXPIN* Pin +); + +PAGED_CODE_SEG +NTSTATUS RetrieveProperties( + _In_ PACX_FACTORY_CIRCUIT_ADD_CIRCUIT CircuitConfig, + _In_ PULONG EndpointID +); + +PAGED_CODE_SEG +NTSTATUS +DetermineSpecialStreamDetailsFromVendorProperties( + _In_ ACXCIRCUIT Circuit, + _In_ AcpiReader * Acpi, + _In_ HANDLE CircuitPropertiesHandle +); + +PAGED_CODE_SEG +NTSTATUS CreateStreamBridge( + _In_ ACX_STREAM_BRIDGE_CONFIG StreamCfg, + _In_ ACXCIRCUIT Circuit, + _In_ ACXPIN Pin, + _In_ DSP_PIN_CONTEXT* PinCtx, + _In_ ULONG BridgeDataPortNumber, + _In_ ULONG BridgeEndpointId, + _In_opt_ PSDCA_PATH_DESCRIPTORS2 PathDescriptors, + _In_ BOOL Render +); + +PAGED_CODE_SEG +NTSTATUS ConnectCaptureCircuitElements( + _In_ ULONG ElementCount, + _In_reads_(ElementCount) ACXELEMENT* Elements, + _In_ ACXCIRCUIT Circuit +); + +PAGED_CODE_SEG +NTSTATUS ConnectRenderCircuitElements( + _In_ ACXAUDIOENGINE AudioEngineElement, + _In_ ACXCIRCUIT Circuit +); + +PAGED_CODE_SEG +NTSTATUS CreateAudioEngine( + _In_ ACXCIRCUIT Circuit, + _In_reads_(DspPinType_Count) ACXPIN* Pins, + _Out_ ACXAUDIOENGINE* AudioEngineElement +); + +#pragma code_seg() +VOID CircuitRequestPreprocess( + _In_ ACXOBJECT Object, + _In_ ACXCONTEXT DriverContext, + _In_ WDFREQUEST Request +); + +PAGED_CODE_SEG +NTSTATUS FindDownstreamVolumeMute( + _In_ ACXCIRCUIT Circuit, + _In_ ACXTARGETCIRCUIT TargetCircuit +); + +PAGED_CODE_SEG +NTSTATUS +ReplicateFormatsForAudioEngine( + _In_ ACXAUDIOENGINE AudioEngine, + _In_ ACXTARGETCIRCUIT TargetCircuit, + _In_ ULONG TargetPinId +); + +PAGED_CODE_SEG +NTSTATUS +ReplicateFormatsForPin( + _In_ ACXPIN Pin, + _In_ ACXTARGETCIRCUIT TargetCircuit, + _In_ ULONG TargetPinId +); diff --git a/audio/Soundwire/Samples/SdcaVad/SdcaVDsp/KeywordDetector.cpp b/audio/Soundwire/Samples/SdcaVad/SdcaVDsp/KeywordDetector.cpp new file mode 100644 index 000000000..3611a2c18 --- /dev/null +++ b/audio/Soundwire/Samples/SdcaVad/SdcaVDsp/KeywordDetector.cpp @@ -0,0 +1,1053 @@ +/*++ + +Copyright (c) Microsoft Corporation. All rights reserved. + + THIS CODE AND INFORMATION IS PROVIDED "AS IS" WITHOUT WARRANTY OF ANY + KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE + IMPLIED WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A PARTICULAR + PURPOSE. + +Module Name: + + KeywordDetector.cpp + +Abstract: + + Sample keyword detector management. + +Environment: + + Kernel mode + +--*/ + +#include "private.h" +#include "stdunk.h" +#include +#include +#include +#include "streamengine.h" + +#include "KeywordDetector.h" + +#ifndef __INTELLISENSE__ +#include "KeywordDetector.tmh" +#endif + + +#pragma code_seg("PAGE") +CKeywordDetector::CKeywordDetector( + _In_ WDFDEVICE Device, + _In_ ACXCIRCUIT Circuit, + _In_ WAVEFORMATEXTENSIBLE *DetectionFormat +) + : + m_streamRunning(FALSE), + m_qpcStartCapture(0), + m_nLastQueuedPacket(-1), + m_SoundDetectorArmed1(FALSE), + m_SoundDetectorArmed2(FALSE), + m_SoundDetectorData1(0), + m_SoundDetectorData2(0), + m_ullKeywordStartTimestamp(0), + m_ullKeywordStopTimestamp(0), + m_Device(Device), + m_Circuit(Circuit), + m_Prepared(FALSE), + m_Suspended(FALSE), + m_dispatchThread(nullptr), + m_FunctionInformation(nullptr), + m_Initialized(FALSE) +{ + PAGED_CODE(); + DSP_CIRCUIT_CONTEXT *circuitCtx; + + memcpy(&(m_PrepareParams.DetectionFormat), DetectionFormat, sizeof(WAVEFORMATEXTENSIBLE)); + circuitCtx = GetDspCircuitContext(m_Circuit); + m_PrepareParams.EndpointId = circuitCtx->EndpointId; + + // Assume streaming (bypass) mode + m_PrepareParams.VadMode = VadModeStreaming; + + KeInitializeEvent(&(m_Events.Suspend), SynchronizationEvent, FALSE); + KeInitializeEvent(&(m_Events.Resume), SynchronizationEvent, FALSE); + + // Initialize our pool of packets and the list structures + // The packet spin locks protect the producer/consumer relationship + // between the dpc routine and GetReadPacket + KeInitializeSpinLock(&m_PacketPoolSpinLock); + KeInitializeSpinLock(&m_PacketFifoSpinLock); + + // The buffering state spin lock protects the state variables + // shared between the arm/disarm and the dpc routine + KeInitializeSpinLock(&m_BufferingStateSpinLock); + + // current state is disarmed + // reset fifo and buffering state + UpdateBufferingState(); +} + +#pragma code_seg("PAGE") +CKeywordDetector::~CKeywordDetector() +{ + PAGED_CODE(); + + m_threadExitEvent.set(); + m_threadExitedEvent.wait(); + + if (m_FunctionInformation) + { + ExFreePool(m_FunctionInformation); + } + + m_Initialized = FALSE; +} + + +#pragma code_seg("PAGE") +_IRQL_requires_max_(PASSIVE_LEVEL) +NTSTATUS CKeywordDetector::Initialize() +{ + PAGED_CODE(); + HANDLE handle; + LARGE_INTEGER qpcFrequency; + + KeQueryPerformanceCounter(&qpcFrequency); + m_qpcFrequency = qpcFrequency.QuadPart; + + // TODO: currently ignoring the results of these calls as to + // not break anything (since they all currently fail) + + // Retrieve capabilities to know ivad/evad, and + // entity id's + RETURN_NTSTATUS_IF_FAILED(GetDeviceKwsCapabilityDescriptor(&m_CapabilityDescriptor)); + RETURN_NTSTATUS_IF_FAILED(GetDeviceFunctionInformation(&m_FunctionInformation)); + RETURN_NTSTATUS_IF_TRUE(0 == m_CapabilityDescriptor.DataPathsSupported, STATUS_NOT_SUPPORTED); + + RETURN_NTSTATUS_IF_FAILED(GetVadDescriptor(&m_VadDescriptor)); + RETURN_NTSTATUS_IF_FAILED(GetVadEntities(&m_VadEntities)); + + // create worker thread to handle suspended access + RETURN_NTSTATUS_IF_FAILED(PsCreateSystemThread(&handle, THREAD_ALL_ACCESS, 0, 0, 0, CKeywordDetector::s_HandleNotifications, this)); + + auto scope_exit([&handle]() { + ZwClose(handle); + }); + + RETURN_NTSTATUS_IF_FAILED(ObReferenceObjectByHandleWithTag(handle, THREAD_ALL_ACCESS, nullptr, KernelMode, KEYWORDDETECTOR_POOLTAG, (PVOID*)&m_dispatchThread, nullptr)); + + // set notification events + RETURN_NTSTATUS_IF_FAILED(SetSuspendAccessEvent(&m_Events)); + + + return STATUS_SUCCESS; +} + +_Use_decl_annotations_ +void CKeywordDetector::s_HandleNotifications(PVOID context) +{ + PAGED_CODE(); + auto kws = static_cast(context); + kws->HandleNotifications(); +} + +_IRQL_requires_(PASSIVE_LEVEL) +void +CKeywordDetector::HandleNotifications() +{ + PAGED_CODE(); + NTSTATUS status{ STATUS_SUCCESS }; + PVOID waitObjects[] = { &m_Events.Suspend, &m_Events.Resume, m_threadExitEvent.get() }; + + // start with the even reset to indicate that the thread is running + m_threadExitedEvent.clear(); + while (true) + { + status = KeWaitForMultipleObjects(3, waitObjects, WaitAny, Executive, KernelMode, FALSE, nullptr, nullptr); + if (STATUS_WAIT_0 == status) + { + auto lock = m_csLock.acquire(); +#pragma prefast(suppress:__WARNING_NEED_NO_COMPETING_THREAD, "wil::fast_mutex lacks required SAL annotation, lock is held") + m_Suspended = TRUE; +#pragma prefast(suppress:__WARNING_CALLER_FAILING_TO_HOLD, "wil::fast_mutex lacks required SAL annotation, lock is held") + UpdateVadStreamState(); + continue; + } + if (STATUS_WAIT_1 == status) + { + auto lock = m_csLock.acquire(); +#pragma prefast(suppress:__WARNING_NEED_NO_COMPETING_THREAD, "wil::fast_mutex lacks required SAL annotation, lock is held") + m_Suspended = FALSE; +#pragma prefast(suppress:__WARNING_CALLER_FAILING_TO_HOLD, "wil::fast_mutex lacks required SAL annotation, lock is held") + UpdateVadStreamState(); + continue; + } + + else // consider as exit event + { + break; + } + } + m_threadExitedEvent.set(); + PsTerminateSystemThread(status); +} + + +#pragma code_seg("PAGE") +_IRQL_requires_max_(PASSIVE_LEVEL) +NTSTATUS CKeywordDetector::ReadKeywordTimestampRegistry() +{ + PAGED_CODE(); + + UNICODE_STRING parametersPath; + + RTL_QUERY_REGISTRY_TABLE paramTable[] = { + // QueryRoutine Flags Name EntryContext DefaultType DefaultData DefaultLength + { NULL, RTL_QUERY_REGISTRY_DIRECT | RTL_QUERY_REGISTRY_TYPECHECK, L"KeywordDetectorStartTimestamp", &m_ullKeywordStartTimestamp, (REG_QWORD << RTL_QUERY_REGISTRY_TYPECHECK_SHIFT) | REG_QWORD, &m_ullKeywordStartTimestamp, sizeof(ULONGLONG) }, + { NULL, RTL_QUERY_REGISTRY_DIRECT | RTL_QUERY_REGISTRY_TYPECHECK, L"KeywordDetectorStopTimestamp", &m_ullKeywordStopTimestamp, (REG_QWORD << RTL_QUERY_REGISTRY_TYPECHECK_SHIFT) | REG_QWORD, &m_ullKeywordStopTimestamp, sizeof(ULONGLONG) }, + { NULL, 0, NULL, NULL, 0, NULL, 0 } + }; + + RtlInitUnicodeString(¶metersPath, NULL); + + // The sizeof(WCHAR) is added to the maximum length, for allowing a space for null termination of the string. + parametersPath.MaximumLength = + g_RegistryPath.Length + sizeof(L"\\Parameters") + sizeof(WCHAR); + +#pragma prefast(suppress:__WARNING_ALIASED_MEMORY_LEAK, "memory is freed by scope_exit") + parametersPath.Buffer = (PWCH)ExAllocatePool2(PagedPool, parametersPath.MaximumLength, KEYWORDDETECTOR_POOLTAG); + RETURN_NTSTATUS_IF_TRUE(parametersPath.Buffer == NULL, STATUS_INSUFFICIENT_RESOURCES); + auto parametersPath_free = scope_exit([¶metersPath]() { + PAGED_CODE(); + ExFreePool(parametersPath.Buffer); + }); + + RtlAppendUnicodeToString(¶metersPath, g_RegistryPath.Buffer); + RtlAppendUnicodeToString(¶metersPath, L"\\Parameters"); + + RETURN_NTSTATUS_IF_FAILED(RtlQueryRegistryValues( + RTL_REGISTRY_ABSOLUTE | RTL_REGISTRY_OPTIONAL, + parametersPath.Buffer, + ¶mTable[0], + NULL, + NULL + )); + + return STATUS_SUCCESS; +} + +#pragma code_seg("PAGE") +_IRQL_requires_max_(PASSIVE_LEVEL) +NTSTATUS CKeywordDetector::ResetDetector(_In_ GUID eventId) +{ + PAGED_CODE(); + + RETURN_NTSTATUS_IF_TRUE(eventId != CONTOSO_KEYWORD1 && + eventId != CONTOSO_KEYWORD2 && + eventId != GUID_NULL, + STATUS_INVALID_PARAMETER); + + // Initialize detector on first use, which is going to be the + // initial reset of the detector. + if(!m_Initialized) + { + RETURN_NTSTATUS_IF_FAILED(Initialize()); + m_Initialized = TRUE; + } + + auto lock = m_csLock.acquire(); + + if (eventId == CONTOSO_KEYWORD1) + { + m_SoundDetectorData1 = 0; +#pragma prefast(suppress:__WARNING_NEED_NO_COMPETING_THREAD, "wil::fast_mutex lacks required SAL annotation, lock is held") + m_SoundDetectorArmed1 = FALSE; + } + else if(eventId == CONTOSO_KEYWORD2) + { + m_SoundDetectorData2 = 0; +#pragma prefast(suppress:__WARNING_NEED_NO_COMPETING_THREAD, "wil::fast_mutex lacks required SAL annotation, lock is held") + m_SoundDetectorArmed2 = FALSE; + } + else if(eventId == GUID_NULL) + { + // When DownloadDetectorData is called to set the pattern for multiple keywords + // at once, all keyword detectors must be reset. Also used during keyword detector + // initialization and cleanup to restore it back to initial state and power down. + m_SoundDetectorData1 = 0; +#pragma prefast(suppress:__WARNING_NEED_NO_COMPETING_THREAD, "wil::fast_mutex lacks required SAL annotation, lock is held") + m_SoundDetectorArmed1 = FALSE; + m_SoundDetectorData2 = 0; +#pragma prefast(suppress:__WARNING_NEED_NO_COMPETING_THREAD, "wil::fast_mutex lacks required SAL annotation, lock is held") + m_SoundDetectorArmed2 = FALSE; + } + +#pragma prefast(suppress:__WARNING_CALLER_FAILING_TO_HOLD, "wil::fast_mutex lacks required SAL annotation, lock is held") + RETURN_NTSTATUS_IF_FAILED(UpdateVadStreamState()); + + return STATUS_SUCCESS; +} + +#pragma code_seg("PAGE") +_IRQL_requires_max_(PASSIVE_LEVEL) +NTSTATUS CKeywordDetector::DownloadDetectorData(_In_ GUID eventId, _In_ LONGLONG Data) +{ + PAGED_CODE(); + + RETURN_NTSTATUS_IF_TRUE(eventId != CONTOSO_KEYWORD1 && + eventId != CONTOSO_KEYWORD2 && + eventId != GUID_NULL, + STATUS_INVALID_PARAMETER); + + + // reset the detector for this event Id + ResetDetector(eventId); + + // In this example, the driver supports detection data + // set with a single call for both detectors, or each + // detector set individually. + if (eventId == CONTOSO_KEYWORD1) + { + m_SoundDetectorData1 = Data; + } + else if(eventId == CONTOSO_KEYWORD2) + { + m_SoundDetectorData2 = Data; + } + else if(eventId == GUID_NULL) + { + // in this simplified example "Data" is set on both detectors, + // however in a real system "Data" could be a data structure which + // contains different values for each detector. + m_SoundDetectorData1 = m_SoundDetectorData2 = Data; + } + + return STATUS_SUCCESS; +} + +// The following function is only applicable to single keyword detection systems, +// and assumes keyword detector #1. +#pragma code_seg("PAGE") +_IRQL_requires_max_(PASSIVE_LEVEL) +NTSTATUS CKeywordDetector::GetDetectorData(_In_ GUID eventId, _Out_ LONGLONG *Data) +{ + PAGED_CODE(); + + RETURN_NTSTATUS_IF_TRUE(eventId != CONTOSO_KEYWORD1 && + eventId != CONTOSO_KEYWORD2 && + eventId != GUID_NULL, + STATUS_INVALID_PARAMETER); + + *Data = 0; + + if (eventId == CONTOSO_KEYWORD1) + { + *Data = m_SoundDetectorData1; + } + else if(eventId == CONTOSO_KEYWORD2) + { + *Data = m_SoundDetectorData2; + } + + return STATUS_SUCCESS; +} + +#pragma code_seg("PAGE") +_IRQL_requires_max_(PASSIVE_LEVEL) +ULONGLONG CKeywordDetector::GetStartTimestamp() +{ + PAGED_CODE(); + + return m_ullKeywordStartTimestamp; +} + +#pragma code_seg("PAGE") +_IRQL_requires_max_(PASSIVE_LEVEL) +ULONGLONG CKeywordDetector::GetStopTimestamp() +{ + PAGED_CODE(); + + return m_ullKeywordStopTimestamp; +} + +#pragma code_seg("PAGE") +_IRQL_requires_max_(PASSIVE_LEVEL) +NTSTATUS CKeywordDetector::SetArmed(_In_ GUID eventId, _In_ BOOLEAN Arm) +{ + PAGED_CODE(); + + BOOLEAN previousDetector1State = FALSE; + BOOLEAN previousDetector2State = FALSE; + + RETURN_NTSTATUS_IF_TRUE(eventId != CONTOSO_KEYWORD1 && + eventId != CONTOSO_KEYWORD2 && + eventId != GUID_NULL, + STATUS_INVALID_PARAMETER); + + // lock scope enter + { + auto lock = m_csLock.acquire(); + + // the previous state is "armed" if either detector is armed. + // this reflects the fact that both detectors are sharing the + // same stream. +#pragma prefast(suppress:__WARNING_NEED_NO_COMPETING_THREAD, "wil::fast_mutex lacks required SAL annotation, lock is held") + previousDetector1State = m_SoundDetectorArmed1; +#pragma prefast(suppress:__WARNING_NEED_NO_COMPETING_THREAD, "wil::fast_mutex lacks required SAL annotation, lock is held") + previousDetector2State = m_SoundDetectorArmed2; + + auto revertOnFailure = scope_exit([&]() { + PAGED_CODE(); +#pragma prefast(suppress:__WARNING_NEED_NO_COMPETING_THREAD, "wil::fast_mutex lacks required SAL annotation, lock is held") + m_SoundDetectorArmed1 = previousDetector1State; +#pragma prefast(suppress:__WARNING_NEED_NO_COMPETING_THREAD, "wil::fast_mutex lacks required SAL annotation, lock is held") + m_SoundDetectorArmed2 = previousDetector2State; +#pragma prefast(suppress:__WARNING_CALLER_FAILING_TO_HOLD, "wil::fast_mutex lacks required SAL annotation, lock is held") + UpdateVadStreamState(); + }); + + if (eventId == CONTOSO_KEYWORD1) + { +#pragma prefast(suppress:__WARNING_NEED_NO_COMPETING_THREAD, "wil::fast_mutex lacks required SAL annotation, lock is held") + m_SoundDetectorArmed1 = Arm; + } + else if(eventId == CONTOSO_KEYWORD2) + { +#pragma prefast(suppress:__WARNING_NEED_NO_COMPETING_THREAD, "wil::fast_mutex lacks required SAL annotation, lock is held") + m_SoundDetectorArmed2 = Arm; + } + +#pragma prefast(suppress:__WARNING_CALLER_FAILING_TO_HOLD, "wil::fast_mutex lacks required SAL annotation, lock is held") + RETURN_NTSTATUS_IF_FAILED(UpdateVadStreamState()); + + revertOnFailure.release(); + } + + // Change buffering state if needed + UpdateBufferingState(); + + return STATUS_SUCCESS; +} + +#pragma code_seg("PAGE") +_IRQL_requires_max_(PASSIVE_LEVEL) +NTSTATUS CKeywordDetector::GetArmed(_In_ GUID eventId, _Out_ BOOLEAN *Arm) +{ + PAGED_CODE(); + + RETURN_NTSTATUS_IF_TRUE(eventId != CONTOSO_KEYWORD1 && + eventId != CONTOSO_KEYWORD2 && + eventId != GUID_NULL, + STATUS_INVALID_PARAMETER); + + auto lock = m_csLock.acquire(); + + *Arm = FALSE; + + if (eventId == CONTOSO_KEYWORD1) + { +#pragma prefast(suppress:__WARNING_NEED_NO_COMPETING_THREAD, "wil::fast_mutex lacks required SAL annotation, lock is held") + *Arm = m_SoundDetectorArmed1; + } + else if(eventId == CONTOSO_KEYWORD2) + { +#pragma prefast(suppress:__WARNING_NEED_NO_COMPETING_THREAD, "wil::fast_mutex lacks required SAL annotation, lock is held") + *Arm = m_SoundDetectorArmed2; + } + + return STATUS_SUCCESS; +} + +#pragma code_seg("PAGE") +_IRQL_requires_max_(PASSIVE_LEVEL) +VOID CKeywordDetector::Run() +{ + PAGED_CODE(); + m_streamRunning = TRUE; + UpdateBufferingState(); +} + +#pragma code_seg("PAGE") +_IRQL_requires_max_(PASSIVE_LEVEL) +VOID CKeywordDetector::Stop() +{ + PAGED_CODE(); + m_streamRunning = FALSE; + UpdateBufferingState(); +} + +#pragma code_seg() +_IRQL_requires_max_(PASSIVE_LEVEL) +VOID CKeywordDetector::UpdateBufferingState() +{ + BOOL armed = FALSE; + KIRQL irql = PASSIVE_LEVEL; + + { + auto lock = m_csLock.acquire(); +#pragma prefast(suppress:__WARNING_RACE_CONDITION, "wil::fast_mutex lacks required SAL annotation, lock is held") + armed = m_SoundDetectorArmed1 | m_SoundDetectorArmed2; + } + + // acquire buffering state spin lock to synchronize state changes with the running dpc routine + KeAcquireSpinLock(&m_BufferingStateSpinLock, &irql); + + if (armed || m_streamRunning) + { + // if we're armed or stream running, and not buffering, start buffering + // if m_qpcStartCapture is not 0, then it's already buffering + if (m_qpcStartCapture == 0) + { + m_qpcStartCapture = KeQueryPerformanceCounter(NULL).QuadPart; + } + } + else + { + // if we're disarmed and no stream running, reset buffering + m_qpcStartCapture = 0; + m_nLastQueuedPacket = (-1); + InitializeListHead(&m_PacketPoolHead); + InitializeListHead(&m_PacketFifoHead); + + for (int i = 0; i < ARRAYSIZE(m_PacketPool); i++) + { + InsertTailList(&m_PacketPoolHead, &m_PacketPool[i].ListEntry); + } + } + + KeReleaseSpinLock(&m_BufferingStateSpinLock, irql); + + return; +} + +#pragma code_seg() +_IRQL_requires_max_(PASSIVE_LEVEL) +VOID CKeywordDetector::NotifyDetection() +{ + KIRQL irql = PASSIVE_LEVEL; + + // Because we are modifying shared buffer state to simulate a notification, + // we need to acquire the spin lock to synchronize this with the dpc routine + KeAcquireSpinLock(&m_BufferingStateSpinLock, &irql); + + // A detection will only happen if armed and the + // stream is already running. If there isn't a client + // running, then set the stream start time to align + // with this detection. + if (!m_streamRunning) + { + // the start capture time is now. + m_qpcStartCapture = KeQueryPerformanceCounter(NULL).QuadPart; + + // The following code is for testing purposes only. + // m_qpcFrequency is defined to be the number of ticks in 1 second. + // Use the stream start time (the current time retrieved in StartBufferStream) to + // mark when the keyword ended, and the start time minus 1 second worth of ticks + // to mark when the keyword started. Also, adjust the stream start time to align + // to this new keyword start time, so that the simulated stream contains the full keyword. + + m_ullKeywordStopTimestamp = m_qpcStartCapture; // stop time is the current time + m_qpcStartCapture = m_qpcStartCapture - m_qpcFrequency; // buffer start time is 1 second ago + m_ullKeywordStartTimestamp = m_qpcStartCapture; // buffer start time = keyword start time + + } + else + { + // The following code is for testing purposes only. + // If the stream is running, we cannot modify qpcStartCapture to be in + // the past, so instead make the keyword start & stop times fit within the + // time period that the keyword has been running. If it has been running + // for more than 1 second, then set the keyword start time to be 1 second back + // into the stream, as though we just figured out there was a keyword there. + // If it has been running less than one second, then the keyword size ends + // up being however long the stream has been running. + + LARGE_INTEGER qpc; + qpc = KeQueryPerformanceCounter(NULL); + + m_ullKeywordStopTimestamp = qpc.QuadPart; // stop time is the current time + + if (m_qpcStartCapture < (qpc.QuadPart - m_qpcFrequency)) + { + m_ullKeywordStartTimestamp = (qpc.QuadPart - m_qpcFrequency); + } + else + { + m_ullKeywordStartTimestamp = m_qpcStartCapture; + } + } + + KeReleaseSpinLock(&m_BufferingStateSpinLock, irql); + + return; +} + +#pragma code_seg() +_IRQL_requires_min_(DISPATCH_LEVEL) +VOID CKeywordDetector::DpcRoutine( + _In_ LONGLONG PerformanceCounter, + _In_ LONGLONG PerformanceFrequency, + _Out_ BOOLEAN *isRealtime, + _Out_ LONGLONG *NewPacketNumber, + _Out_ ULONGLONG *NewPerformanceCount) +{ + LONGLONG currentPacket; + LONGLONG packetsToQueue; + + KIRQL irql = PASSIVE_LEVEL; + + // used to synchronize buffering state variables with stream state changes, + // arming changes, etc. + KeAcquireSpinLock(&m_BufferingStateSpinLock, &irql); + + *isRealtime = FALSE; + *NewPacketNumber = 0; + *NewPerformanceCount = 0; + + // TODO: the timer only runs when the stream is open, but really for KWS it should be building up a collection of burst data + // in the queue from 1.5 sec before the trigger happens. Is there some way to simulate that behavior here? Without doing that, + // there isn't really a burst that happens, just a trickle because while the timestamps will be right, the queue won't contain + // anything until the timer fires at the normal rate. + + if (m_qpcStartCapture > 0) + { + currentPacket = (PerformanceCounter - m_qpcStartCapture) * (SamplesPerSecond / SamplesPerPacket) / PerformanceFrequency; + packetsToQueue = currentPacket - m_nLastQueuedPacket; + + // If the fifo is empty, and we're going to add something, then we are realtime + *isRealtime = IsListEmpty(&m_PacketFifoHead) && packetsToQueue > 0; + + *NewPacketNumber = m_nLastQueuedPacket+1; + *NewPerformanceCount = m_qpcStartCapture + (*NewPacketNumber * m_qpcFrequency * SamplesPerPacket / SamplesPerSecond); + + while (packetsToQueue > 0) + { + LIST_ENTRY* packetListEntry; + PACKET_ENTRY* packetEntry; + + do + { + packetListEntry = ExInterlockedRemoveHeadList(&m_PacketPoolHead, &m_PacketPoolSpinLock); + if (packetListEntry != NULL) break; + + // Pool is empty, no room to buffer more, an overrun is occurring. Drop and reuse the + // oldest packet from head of fifo. + + // Since the pool is empty, the fifo should be full. However, although unlikely, the + // driver might empty the fifo before this routine removes a packet. In that case, the + // pool should have packets available again. Therefore this is a retry loop. + packetListEntry = ExInterlockedRemoveHeadList(&m_PacketFifoHead, &m_PacketFifoSpinLock); + if (packetListEntry != NULL) break; + } while (TRUE); + + packetEntry = CONTAINING_RECORD(packetListEntry, PACKET_ENTRY, ListEntry); + + packetEntry->PacketNumber = ++m_nLastQueuedPacket; + packetEntry->QpcWhenSampled = m_qpcStartCapture + (packetEntry->PacketNumber * PerformanceFrequency * SamplesPerPacket / SamplesPerSecond); + + // TODO: this should really put something real in the buffer. Use the sine tone generator maybe? + RtlZeroMemory(&packetEntry->Samples[0], sizeof(packetEntry->Samples)); + + ExInterlockedInsertTailList(&m_PacketFifoHead, packetListEntry, &m_PacketFifoSpinLock); + + packetsToQueue -= 1; + } + } + + KeReleaseSpinLock(&m_BufferingStateSpinLock, irql); +} + +#pragma code_seg() +_IRQL_requires_max_(PASSIVE_LEVEL) +NTSTATUS CKeywordDetector::GetFifoStart(_Out_ ULONG *PacketNumber, _Out_ ULONGLONG *PerformanceCount) +{ + NTSTATUS status = STATUS_DEVICE_NOT_READY; + KIRQL irql = PASSIVE_LEVEL; + + // acquire the fifo spin lock in order to safely inspect the head of the fifo + KeAcquireSpinLock(&m_PacketFifoSpinLock, &irql); + + // peek at the first entry in the list, and retrieve the required packet number and qpc for it + if (!IsListEmpty(m_PacketFifoHead.Flink)) + { + PACKET_ENTRY *packetEntry; + + packetEntry = CONTAINING_RECORD(m_PacketFifoHead.Flink, PACKET_ENTRY, ListEntry); + + status = RtlLongLongToULong(packetEntry->PacketNumber, PacketNumber); + if (NT_SUCCESS(status)) + { + *PerformanceCount = packetEntry->QpcWhenSampled; + status = STATUS_SUCCESS; + } + } + + KeReleaseSpinLock(&m_PacketFifoSpinLock, irql); + + return status; +} + +#pragma code_seg() +_IRQL_requires_max_(PASSIVE_LEVEL) +NTSTATUS CKeywordDetector::GetReadPacket +( + _In_ ULONG PacketCount, + _In_ ULONG PacketSize, + _In_reads_(PacketSize) PVOID *Packets, + _Out_ ULONG *PacketNumber, + _Out_ ULONG64 *PerformanceCounterValue, + _Out_ BOOLEAN *MoreData, + _Out_ ULONG *NextPacketNumber, + _Out_ ULONGLONG *NextPerformanceCount +) +{ + NTSTATUS status = STATUS_DEVICE_NOT_READY; + LIST_ENTRY *packetListEntry = NULL; + + // This call is synchronized with the dpc routine through the packet list + // spin locks, as a producer consumer relationship. + // buffering state variables are not available, and taking the buffering state + // lock here would introduce lock contention between the producer and the consumer. + *PacketNumber = 0; + *PerformanceCounterValue = 0; + *MoreData = FALSE; + *NextPacketNumber = 0; + *NextPerformanceCount = 0; + + packetListEntry = ExInterlockedRemoveHeadList(&m_PacketFifoHead, &m_PacketFifoSpinLock); + if (packetListEntry != NULL) + { + BYTE *packetData; + PACKET_ENTRY *packetEntry; + + packetEntry = CONTAINING_RECORD(packetListEntry, PACKET_ENTRY, ListEntry); + + status = RtlLongLongToULong(packetEntry->PacketNumber, PacketNumber); + if (NT_SUCCESS(status)) + { + packetData = (PBYTE) Packets[(*PacketNumber) % PacketCount]; + + *PerformanceCounterValue = packetEntry->QpcWhenSampled; + + if (NT_SUCCESS(GetFifoStart(NextPacketNumber, NextPerformanceCount))) + { + *MoreData = TRUE; + } + + // TODO: the packet size here needs to line up to the packet size allocated. + // Also, handle the first packet offset + RtlCopyMemory(packetData, packetEntry->Samples, min(sizeof(packetEntry->Samples), PacketSize)); + } + + ExInterlockedInsertTailList(&m_PacketPoolHead, packetListEntry, &m_PacketPoolSpinLock); + } + + return STATUS_SUCCESS; +} + +PAGED_CODE_SEG +NTSTATUS +CKeywordDetector::SendPropertyTo +( + _In_ GUID PropertySet, + _In_ ULONG PropertyId, + _In_ ACX_PROPERTY_VERB Verb, + _In_ PVOID Control, + _In_ ULONG ControlCb, + _Inout_ PVOID Value, + _In_ ULONG ValueCb, + _Out_ ULONG_PTR* Information +) +{ + PAGED_CODE(); + + ACXPIN pin; + pin = AcxCircuitGetPinById(m_Circuit, DspCapturePinTypeBridge); + RETURN_NTSTATUS_IF_TRUE(pin == NULL, STATUS_INVALID_PARAMETER); + + DSP_PIN_CONTEXT* pinCtx = GetDspPinContext(pin); + RETURN_NTSTATUS_IF_TRUE(pinCtx == NULL, STATUS_INVALID_PARAMETER); + + RETURN_NTSTATUS_IF_TRUE(pinCtx->TargetCircuit == NULL, STATUS_INVALID_DEVICE_STATE); + + ACX_REQUEST_PARAMETERS requestParams; + ACX_REQUEST_PARAMETERS_INIT_PROPERTY( + &requestParams, + PropertySet, + PropertyId, + Verb, + AcxItemTypeCircuit, + 0, + Control, ControlCb, + Value, ValueCb + ); + + WDFREQUEST request; + WDF_OBJECT_ATTRIBUTES attributes; + WDF_OBJECT_ATTRIBUTES_INIT(&attributes); + attributes.ParentObject = m_Device; + RETURN_NTSTATUS_IF_FAILED(WdfRequestCreate(&attributes, AcxTargetCircuitGetWdfIoTarget(pinCtx->TargetCircuit), &request)); + + auto request_free = scope_exit([&request]() { + WdfObjectDelete(request); + }); + + RETURN_NTSTATUS_IF_FAILED(AcxTargetCircuitFormatRequestForProperty(pinCtx->TargetCircuit, request, &requestParams)); + + WDF_REQUEST_SEND_OPTIONS sendOptions; + WDF_REQUEST_SEND_OPTIONS_INIT(&sendOptions, WDF_REQUEST_SEND_OPTION_SYNCHRONOUS); + WDF_REQUEST_SEND_OPTIONS_SET_TIMEOUT(&sendOptions, WDF_REL_TIMEOUT_IN_SEC(5)); + + RETURN_NTSTATUS_IF_TRUE(!WdfRequestSend(request, AcxTargetCircuitGetWdfIoTarget(pinCtx->TargetCircuit), &sendOptions), STATUS_INVALID_DEVICE_REQUEST); + + NTSTATUS status = (WdfRequestGetStatus(request)); + + if (Information) + { + *Information = WdfRequestGetInformation(request); + } + if (status == STATUS_BUFFER_OVERFLOW && ValueCb == 0) + { + // Don't trace this error, it's normal + return status; + } + + RETURN_NTSTATUS_IF_FAILED(status); + + return STATUS_SUCCESS; +} + +PAGED_CODE_SEG +NTSTATUS +CKeywordDetector::GetDeviceFunctionInformation +( + _Out_ PSDCA_FUNCTION_INFORMATION_LIST *FunctionInfo +) +{ + PAGED_CODE(); + NTSTATUS status = STATUS_SUCCESS; + ULONG_PTR requiredBufferSize = 0; + + RETURN_NTSTATUS_IF_TRUE(nullptr == FunctionInfo, STATUS_INVALID_PARAMETER); + + status = SendPropertyTo(KSPROPERTYSETID_Sdca, + KSPROPERTY_SDCA_FUNCTION_INFORMATION, + AcxPropertyVerbGet, + nullptr, 0, + nullptr, 0, + &requiredBufferSize); + + if (status == STATUS_BUFFER_OVERFLOW) + { + // expect a buffer overflow error, confirm size is valid + if (requiredBufferSize >= sizeof(SDCA_FUNCTION_INFORMATION_LIST)) + { + // size is valid, allocate and retrieve + *FunctionInfo = (PSDCA_FUNCTION_INFORMATION_LIST) ExAllocatePool2(POOL_FLAG_NON_PAGED, requiredBufferSize, DRIVER_TAG); + RETURN_NTSTATUS_IF_TRUE(nullptr == *FunctionInfo, STATUS_INSUFFICIENT_RESOURCES); + status = SendPropertyTo(KSPROPERTYSETID_Sdca, + KSPROPERTY_SDCA_FUNCTION_INFORMATION, + AcxPropertyVerbGet, + nullptr, 0, + *FunctionInfo, sizeof(SDCA_FUNCTION_INFORMATION_LIST), + nullptr); + } + else + { + // correct buffer overflow error, but size is wrong + RETURN_NTSTATUS_IF_FAILED(STATUS_UNSUCCESSFUL); + } + } + else if (NT_SUCCESS(status)) + { + // call should not succeeded with a null buffer pointer + RETURN_NTSTATUS_IF_FAILED(STATUS_INVALID_DEVICE_REQUEST); + } + + RETURN_NTSTATUS_IF_FAILED(status); + + return STATUS_SUCCESS; +} + +PAGED_CODE_SEG +NTSTATUS +CKeywordDetector::GetDeviceKwsCapabilityDescriptor +( + _Out_ PDEVICE_KWS_CAPABILITY_DESCRIPTOR Descriptor +) +{ + PAGED_CODE(); + + memset(Descriptor, 0, sizeof(DEVICE_KWS_CAPABILITY_DESCRIPTOR)); + RETURN_NTSTATUS_IF_FAILED(SendPropertyTo(KSPROPERTYSETID_SdcaKws, + KSPROPERTY_SDCAKWS_DEVICE_CAPABILITY, + AcxPropertyVerbGet, + nullptr, 0, + Descriptor, sizeof(DEVICE_KWS_CAPABILITY_DESCRIPTOR), + nullptr)); + + return STATUS_SUCCESS; +} + +PAGED_CODE_SEG +NTSTATUS +CKeywordDetector::GetVadDescriptor( + _Out_ PVAD_DESCRIPTOR_FORMAT Descriptor + ) +{ + PAGED_CODE(); + + // For simplicity, this sample code uses a static-sized VAD_DESCRIPTOR + // with room for 11 total formats. This still has the potential to + // fail if the target device supports more than that many formats for VAD + memset(Descriptor, 0, sizeof(VAD_DESCRIPTOR_FORMAT)); + RETURN_NTSTATUS_IF_FAILED(SendPropertyTo(KSPROPERTYSETID_SdcaKws, + KSPROPERTY_SDCAKWS_VAD_CAPABILITY, + AcxPropertyVerbGet, + nullptr, 0, + Descriptor, sizeof(VAD_DESCRIPTOR_FORMAT), + nullptr)); + + return STATUS_SUCCESS; +} + +PAGED_CODE_SEG +NTSTATUS +CKeywordDetector::GetVadEntities( + _Out_ PVAD_ENTITIES_EXTRA Entities + ) +{ + PAGED_CODE(); + + // For simplicity, this sample code uses a static-sized VAD_ENTITIES + // with room for 25 total elements. This still has the potential to + // fail if the target device has more than 25 elements. + memset(Entities, 0, sizeof(VAD_ENTITIES_EXTRA)); + RETURN_NTSTATUS_IF_FAILED(SendPropertyTo(KSPROPERTYSETID_SdcaKws, + KSPROPERTY_SDCAKWS_VAD_ENTITIES, + AcxPropertyVerbGet, + nullptr, 0, + Entities, sizeof(VAD_ENTITIES_EXTRA), + nullptr)); + + return STATUS_SUCCESS; +} + +PAGED_CODE_SEG +NTSTATUS +CKeywordDetector::SetSuspendAccessEvent +( + _In_ PSDCA_KWS_NOTIFICATIONS Events +) +{ + PAGED_CODE(); + + RETURN_NTSTATUS_IF_FAILED(SendPropertyTo(KSPROPERTYSETID_SdcaKws, + KSPROPERTY_SDCAKWS_ACCESS_EVENTS, + AcxPropertyVerbSet, + nullptr, 0, + Events, sizeof(SDCA_KWS_NOTIFICATIONS), + nullptr)); + + return STATUS_SUCCESS; +} + +// There are three states. Disarmed, Armed and Suspended, +// and Armed and Prepared. + +// If we're Disarmed, we need to clean up the vad stream, suspend/resume +// state doesn't matter. + +// if we're Armed and Suspended, we should be detecting but are experiencing +// a period of deafness due to the codec driver needing to access the hardware. +// So, we need to clean up the vad stream and wait for the resume notification +// to recreate the vad stream + +// if we're Armed and Prepared, then we're actively detecting, so we +// need the stream prepared. +PAGED_CODE_SEG +_Requires_lock_held_(m_csLock) +NTSTATUS +CKeywordDetector::UpdateVadStreamState() +{ + PAGED_CODE(); + + if (m_SoundDetectorArmed1 || m_SoundDetectorArmed2) + { + // if we're armed, not prepared, and not suspended, then + // we need to move to the armed and prepared state. + if (!m_Prepared && !m_Suspended) + { + // To move into the Armed and Prepared state + // we need to prepare the vad stream + RETURN_NTSTATUS_IF_FAILED(ConfigureVadPort(&m_PrepareParams)); + } + // if we are armed, prepared, and suspended, then + // we need to move to the armed and suspended state + else if (m_Prepared && m_Suspended) + { + // To move into the Armed and Suspended state + // we need to cleanup the VAD stream + RETURN_NTSTATUS_IF_FAILED(CleanupVadPort()); + } + // else + // if we are armed, prepared, and not suspended, then we are in + // the armed and prepared state, nothing else to do. + + // Or, if we are armed, not prepared, and suspended, then we are in + // the armed and suspended state, nothing else to do. + } + else + { + if (m_Prepared) + { + // moving into the disarmed state + RETURN_NTSTATUS_IF_FAILED(CleanupVadPort()); + } + } + + return STATUS_SUCCESS; +} + + +PAGED_CODE_SEG +_Requires_lock_held_(m_csLock) +NTSTATUS +CKeywordDetector::ConfigureVadPort +( + _In_ PSDCA_KWS_PREPARE_PARAMS PrepareParams +) +{ + PAGED_CODE(); + + RETURN_NTSTATUS_IF_FAILED(SendPropertyTo(KSPROPERTYSETID_SdcaKws, + KSPROPERTY_SDCAKWS_CONFIGURE_VAD_PORT, + AcxPropertyVerbSet, + nullptr, 0, + PrepareParams, sizeof(SDCA_KWS_PREPARE_PARAMS), + nullptr)); + + m_Prepared = TRUE; + + return STATUS_SUCCESS; +} + +PAGED_CODE_SEG +_Requires_lock_held_(m_csLock) +NTSTATUS +CKeywordDetector::CleanupVadPort( ) +{ + PAGED_CODE(); + + RETURN_NTSTATUS_IF_FAILED(SendPropertyTo(KSPROPERTYSETID_SdcaKws, + KSPROPERTY_SDCAKWS_CLEANUP_VAD_PORT, + AcxPropertyVerbSet, + nullptr, 0, + NULL, 0, + nullptr)); + + m_Prepared = FALSE; + + return STATUS_SUCCESS; +} + diff --git a/audio/Soundwire/Samples/SdcaVad/SdcaVDsp/KeywordDetector.h b/audio/Soundwire/Samples/SdcaVad/SdcaVDsp/KeywordDetector.h new file mode 100644 index 000000000..9ebc3a5e7 --- /dev/null +++ b/audio/Soundwire/Samples/SdcaVad/SdcaVDsp/KeywordDetector.h @@ -0,0 +1,237 @@ +/*++ + +Copyright (c) Microsoft Corporation. All rights reserved. + + THIS CODE AND INFORMATION IS PROVIDED "AS IS" WITHOUT WARRANTY OF ANY + KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE + IMPLIED WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A PARTICULAR + PURPOSE. + +Module Name: + + KeywordDetector.h + +Abstract: + + Sample Keyword Detector. + + +--*/ + +#pragma once + +#include +#include "ContosoEventDetector.h" +#include "soundwirecontroller.h" +#include "sdcastreaming.h" + +#define KEYWORDDETECTOR_POOLTAG 'KWS0' + +typedef struct +{ + VAD_DESCRIPTOR Descriptor; + WAVEFORMATEXTENSIBLE ExtraFormats[10]; +} VAD_DESCRIPTOR_FORMAT, * PVAD_DESCRIPTOR_FORMAT; + +typedef struct +{ + ULONG EntitiesCount; + ENTITY_INFO ExtraEntities[25]; +} VAD_ENTITIES_EXTRA, * PVAD_ENTITIES_EXTRA; + +class CKeywordDetector +{ +public: + CKeywordDetector(_In_ WDFDEVICE Device, _In_ ACXCIRCUIT Circuit, _In_ PWAVEFORMATEXTENSIBLE Format); + + ~CKeywordDetector(); + + _IRQL_requires_max_(PASSIVE_LEVEL) + NTSTATUS Initialize(); + + _IRQL_requires_max_(PASSIVE_LEVEL) + NTSTATUS ResetDetector(_In_ GUID eventId); + + _IRQL_requires_max_(PASSIVE_LEVEL) + NTSTATUS DownloadDetectorData(_In_ GUID eventId, _In_ LONGLONG Data); + + _IRQL_requires_max_(PASSIVE_LEVEL) + NTSTATUS GetDetectorData(_In_ GUID eventId, _Out_ LONGLONG *Data); + + _IRQL_requires_max_(PASSIVE_LEVEL) + ULONGLONG GetStartTimestamp(); + + _IRQL_requires_max_(PASSIVE_LEVEL) + ULONGLONG GetStopTimestamp(); + + _IRQL_requires_max_(PASSIVE_LEVEL) + NTSTATUS SetArmed(_In_ GUID eventId, _In_ BOOLEAN Arm); + + _IRQL_requires_max_(PASSIVE_LEVEL) + VOID NotifyDetection(); + + _IRQL_requires_max_(PASSIVE_LEVEL) + NTSTATUS GetArmed(_In_ GUID eventId, _Out_ BOOLEAN *Arm); + + _IRQL_requires_max_(PASSIVE_LEVEL) + VOID Run(); + + _IRQL_requires_max_(PASSIVE_LEVEL) + VOID Stop(); + + _IRQL_requires_min_(DISPATCH_LEVEL) + VOID DpcRoutine(_In_ LONGLONG PerformanceCounter, _In_ LONGLONG PerformanceFrequency, _Out_ BOOLEAN *isRealtime, _Out_ LONGLONG *NewPacketNumber, _Out_ ULONGLONG *NewPerformanceCount); + + _IRQL_requires_max_(PASSIVE_LEVEL) + NTSTATUS GetReadPacket(_In_ ULONG PacketCount, _In_ ULONG PacketSize, _In_reads_(PacketSize) PVOID *Packets, _Out_ ULONG *PacketNumber, + _Out_ ULONGLONG *PerformanceCount, _Out_ BOOLEAN *MoreData, _Out_ ULONG *NextPacketNumber, _Out_ ULONGLONG *NextPerformanceCount); + + _IRQL_requires_max_(PASSIVE_LEVEL) + NTSTATUS GetFifoStart(_Out_ ULONG *PacketNumber, _Out_ ULONGLONG *PerformanceCount); + +private: + + _IRQL_requires_max_(PASSIVE_LEVEL) + VOID UpdateBufferingState(); + + _IRQL_requires_max_(PASSIVE_LEVEL) + NTSTATUS ReadKeywordTimestampRegistry(); + + PAGED_CODE_SEG + NTSTATUS + SendPropertyTo + ( + _In_ GUID PropertySet, + _In_ ULONG PropertyId, + _In_ ACX_PROPERTY_VERB Verb, + _In_ PVOID Control, + _In_ ULONG ControlCb, + _Inout_ PVOID Value, + _In_ ULONG ValueCb, + _Out_ ULONG_PTR* Information + ); + + + PAGED_CODE_SEG + NTSTATUS + GetDeviceFunctionInformation( + _Out_ PSDCA_FUNCTION_INFORMATION_LIST *FunctionInfo + ); + + PAGED_CODE_SEG + NTSTATUS + GetDeviceKwsCapabilityDescriptor( + _Out_ PDEVICE_KWS_CAPABILITY_DESCRIPTOR Descriptor + ); + + PAGED_CODE_SEG + NTSTATUS + GetVadDescriptor( + _Out_ PVAD_DESCRIPTOR_FORMAT Descriptor + ); + + PAGED_CODE_SEG + NTSTATUS + GetVadEntities ( + _Out_ PVAD_ENTITIES_EXTRA Entities + ); + + PAGED_CODE_SEG + NTSTATUS + SetSuspendAccessEvent( + _In_ PSDCA_KWS_NOTIFICATIONS Events + ); + + PAGED_CODE_SEG + _Requires_lock_held_(m_csLock) + NTSTATUS + UpdateVadStreamState(); + + PAGED_CODE_SEG + _Requires_lock_held_(m_csLock) + NTSTATUS + ConfigureVadPort( + _In_ PSDCA_KWS_PREPARE_PARAMS PrepareParams + ); + + PAGED_CODE_SEG + _Requires_lock_held_(m_csLock) + NTSTATUS + CleanupVadPort( + ); + + static KSTART_ROUTINE s_HandleNotifications; + + PAGED_CODE_SEG + void + HandleNotifications(); + + // The Contoso keyword detector processes 10ms packets of 16KHz 16-bit PCM + // audio samples + static const int SamplesPerSecond = 16000; + static const int SamplesPerPacket = (10 * SamplesPerSecond / 1000); + + typedef struct + { + LIST_ENTRY ListEntry; + LONGLONG PacketNumber; + LONGLONG QpcWhenSampled; + UINT16 Samples[SamplesPerPacket]; + } PACKET_ENTRY; + + // set at initialization, safe to use in all threads + WDFDEVICE m_Device; + ACXCIRCUIT m_Circuit; + LONGLONG m_qpcFrequency; + BOOLEAN m_Initialized; + SDCA_KWS_PREPARE_PARAMS m_PrepareParams; + PSDCA_FUNCTION_INFORMATION_LIST m_FunctionInformation; + DEVICE_KWS_CAPABILITY_DESCRIPTOR m_CapabilityDescriptor; + VAD_DESCRIPTOR_FORMAT m_VadDescriptor; + SDCA_KWS_NOTIFICATIONS m_Events; + PACKET_ENTRY m_PacketPool[1 * SamplesPerSecond / SamplesPerPacket]; // Enough storage for 1 second of audio data + VAD_ENTITIES_EXTRA m_VadEntities; + + // single thread access, no lock necessary + LONGLONG m_SoundDetectorData1; + LONGLONG m_SoundDetectorData2; + ULONGLONG m_ullKeywordStartTimestamp; + ULONGLONG m_ullKeywordStopTimestamp; + BOOLEAN m_streamRunning; + + // the following state variables are shared between dpc and stream state + KSPIN_LOCK m_BufferingStateSpinLock; + _Guarded_by_(m_BufferingStateSpinLock) + LONGLONG m_qpcStartCapture; + _Guarded_by_(m_BufferingStateSpinLock) + LONGLONG m_nLastQueuedPacket; + + // protected through interlocked access to the packet pool + KSPIN_LOCK m_PacketPoolSpinLock; + LIST_ENTRY m_PacketPoolHead; + + // protected through interlocked access to the packet pool + KSPIN_LOCK m_PacketFifoSpinLock; + LIST_ENTRY m_PacketFifoHead; + + + // the following variables are shared with the sdca notification event + // handler thread, and are protected by m_csLock + mutable wil::fast_mutex_with_critical_region m_csLock; + PETHREAD m_dispatchThread; + mutable wil::kernel_event_auto_reset m_threadExitEvent; + mutable wil::kernel_event_manual_reset m_threadExitedEvent{ true }; + + _Guarded_by_(m_csLock) + BOOLEAN m_Prepared; + + _Guarded_by_(m_csLock) + BOOLEAN m_Suspended; + + _Guarded_by_(m_csLock) + BOOLEAN m_SoundDetectorArmed1; + + _Guarded_by_(m_csLock) + BOOLEAN m_SoundDetectorArmed2; +}; + diff --git a/audio/Soundwire/Samples/SdcaVad/SdcaVDsp/PositionClock.h b/audio/Soundwire/Samples/SdcaVad/SdcaVDsp/PositionClock.h new file mode 100644 index 000000000..d57bb522a --- /dev/null +++ b/audio/Soundwire/Samples/SdcaVad/SdcaVDsp/PositionClock.h @@ -0,0 +1,38 @@ +/*++ + + THIS CODE AND INFORMATION IS PROVIDED "AS IS" WITHOUT WARRANTY OF ANY + KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE + IMPLIED WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A PARTICULAR + PURPOSE. + +Module Name: + + PositionClock.h + +Abstract: + + Simulated clock Interface for keeping track of stream position. + +Environment: + + Kernel mode + +--*/ +#pragma once + +class IPositionClock +{ +public: + __drv_maxIRQL(PASSIVE_LEVEL) + virtual void Pause() = 0; + + __drv_maxIRQL(PASSIVE_LEVEL) + virtual void Run() = 0; + + __drv_maxIRQL(PASSIVE_LEVEL) + virtual void Stop() = 0; + + __drv_maxIRQL(PASSIVE_LEVEL) + virtual ULONGLONG GetElapsedTime(_Out_ PULONGLONG pQpcTimeStamp) = 0; +}; + diff --git a/audio/Soundwire/Samples/SdcaVad/SdcaVDsp/PositionSimClock.cpp b/audio/Soundwire/Samples/SdcaVad/SdcaVDsp/PositionSimClock.cpp new file mode 100644 index 000000000..db0f2aee6 --- /dev/null +++ b/audio/Soundwire/Samples/SdcaVad/SdcaVDsp/PositionSimClock.cpp @@ -0,0 +1,140 @@ +/*++ + + THIS CODE AND INFORMATION IS PROVIDED "AS IS" WITHOUT WARRANTY OF ANY + KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE + IMPLIED WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A PARTICULAR + PURPOSE. + +Module Name: + + PositionSimClock.cpp + +Abstract: + + Simulated clock for keeping track of stream position. + +Environment: + + Kernel mode + +--*/ + +#include "private.h" +#include "PositionSimClock.h" + +#ifndef __INTELLISENSE__ +#include "positionsimclock.tmh" +#endif + +_Use_decl_annotations_ +PAGED_CODE_SEG +CPositionSimClock::CPositionSimClock() +{ + PAGED_CODE(); + + m_State = SIM_CLOCK_STATE_STOP; + m_ElapsedTimeWhenPaused = 0; + m_StartTime = 0; + m_QpcTimeStamp = 0; + + KeInitializeSpinLock(&m_Lock); +} + +#pragma code_seg() +_Use_decl_annotations_ +CPositionSimClock::~CPositionSimClock() +{ +} + +#pragma code_seg() +_Use_decl_annotations_ +void CPositionSimClock::Run() +{ + KIRQL irql = PASSIVE_LEVEL; + KeAcquireSpinLock(&m_Lock, &irql); + + if (m_State == SIM_CLOCK_STATE_STOP || + m_State == SIM_CLOCK_STATE_PAUSE) + { + m_StartTime = KeQueryInterruptTimePrecise(&m_QpcTimeStamp); + } + + m_State = SIM_CLOCK_STATE_RUN; + + DrvLogInfo(g_SDCAVDspLog, FLAG_STREAM, L"CPositionSimClock::Run SIM_CLOCK_STATE_RUN : %lld", m_StartTime); + + KeReleaseSpinLock(&m_Lock, irql); +} + +#pragma code_seg() +_Use_decl_annotations_ +void CPositionSimClock::Pause() +{ + KIRQL irql = PASSIVE_LEVEL; + KeAcquireSpinLock(&m_Lock, &irql); + + if (m_State == SIM_CLOCK_STATE_RUN) + { + m_ElapsedTimeWhenPaused = GetElapsedTimeUnlocked(); + DrvLogInfo(g_SDCAVDspLog, FLAG_STREAM, L"CPositionSimClock::Pause ElapsedTimeWhenPaused : %lld", m_ElapsedTimeWhenPaused); + } + + m_State = SIM_CLOCK_STATE_PAUSE; + + KeReleaseSpinLock(&m_Lock, irql); +} + +#pragma code_seg() +_Use_decl_annotations_ +void CPositionSimClock::Stop() +{ + KIRQL irql = PASSIVE_LEVEL; + KeAcquireSpinLock(&m_Lock, &irql); + + m_State = SIM_CLOCK_STATE_STOP; + m_StartTime = 0; + m_ElapsedTimeWhenPaused = 0; + + DrvLogInfo(g_SDCAVDspLog, FLAG_STREAM, L"CPositionSimClock::Stop"); + + KeReleaseSpinLock(&m_Lock, irql); +} + +#pragma code_seg() +_Use_decl_annotations_ +ULONGLONG CPositionSimClock::GetElapsedTimeUnlocked() +{ + ULONGLONG current_time = KeQueryInterruptTimePrecise(&m_QpcTimeStamp); + + ULONGLONG elapsedTime = (current_time - m_StartTime) + m_ElapsedTimeWhenPaused; + + return elapsedTime; +} + +#pragma code_seg() +_Use_decl_annotations_ +ULONGLONG CPositionSimClock::GetElapsedTime(PULONGLONG pQpcTimeStamp) +{ + KIRQL irql = PASSIVE_LEVEL; + KeAcquireSpinLock(&m_Lock, &irql); + + ULONGLONG elapsedTime = 0; + if (m_State == SIM_CLOCK_STATE_RUN) + { + elapsedTime = GetElapsedTimeUnlocked(); + } + else + { + elapsedTime = m_ElapsedTimeWhenPaused; + } + + if (pQpcTimeStamp) + { + *pQpcTimeStamp = m_QpcTimeStamp; + } + + KeReleaseSpinLock(&m_Lock, irql); + + return elapsedTime; +} + diff --git a/audio/Soundwire/Samples/SdcaVad/SdcaVDsp/PositionSimClock.h b/audio/Soundwire/Samples/SdcaVad/SdcaVDsp/PositionSimClock.h new file mode 100644 index 000000000..2d1844faa --- /dev/null +++ b/audio/Soundwire/Samples/SdcaVad/SdcaVDsp/PositionSimClock.h @@ -0,0 +1,76 @@ +/*++ + + THIS CODE AND INFORMATION IS PROVIDED "AS IS" WITHOUT WARRANTY OF ANY + KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE + IMPLIED WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A PARTICULAR + PURPOSE. + +Module Name: + + PositionSimClock.h + +Abstract: + + Simulated clock for keeping track of stream position. + +Environment: + + Kernel mode + +--*/ + +#pragma once + +#include "PositionClock.h" + +#define HNSTIME_PER_MILLISECOND 10000 + +class CPositionSimClock : public IPositionClock +{ +public: + __drv_maxIRQL(PASSIVE_LEVEL) + PAGED_CODE_SEG + CPositionSimClock(); + + virtual + __drv_maxIRQL(PASSIVE_LEVEL) + ~CPositionSimClock(); + + virtual + __drv_maxIRQL(PASSIVE_LEVEL) + void Pause(); + + virtual + __drv_maxIRQL(PASSIVE_LEVEL) + void Run(); + + virtual + __drv_maxIRQL(PASSIVE_LEVEL) + void Stop(); + + virtual + __drv_maxIRQL(PASSIVE_LEVEL) + ULONGLONG GetElapsedTime(_Out_ PULONGLONG pQpcTimeStamp); + +protected: + ULONGLONG m_StartTime; + ULONGLONG m_ElapsedTimeWhenPaused; + ULONGLONG m_QpcTimeStamp; + + KSPIN_LOCK m_Lock; + + typedef enum _SimClockState_t + { + SIM_CLOCK_STATE_STOP, + SIM_CLOCK_STATE_PAUSE, + SIM_CLOCK_STATE_RUN, + + SIM_CLOCK_STATE_Count + }SimClockState; + + SimClockState m_State; + + __drv_maxIRQL(DISPATCH_LEVEL) + ULONGLONG GetElapsedTimeUnlocked(); +}; + diff --git a/audio/Soundwire/Samples/SdcaVad/SdcaVDsp/SDCAVDsp.vcxproj b/audio/Soundwire/Samples/SdcaVad/SdcaVDsp/SDCAVDsp.vcxproj new file mode 100644 index 000000000..20e87f0c9 --- /dev/null +++ b/audio/Soundwire/Samples/SdcaVad/SdcaVDsp/SDCAVDsp.vcxproj @@ -0,0 +1,385 @@ + + + + + Debug + Win32 + + + Release + Win32 + + + Debug + x64 + + + Release + x64 + + + Debug + ARM + + + Release + ARM + + + Debug + ARM64 + + + Release + ARM64 + + + + {5FDD3888-48B5-496C-83B0-E107CFFF46BC} + $(MSBuildProjectName) + 1 + 31 + 1 + 0 + v4.5 + 12.0 + false + true + Debug + Win32 + $(LatestTargetPlatformVersion) + + + + Windows10 + true + WindowsKernelModeDriver10.0 + Driver + KMDF + Universal + + + Windows10 + false + WindowsKernelModeDriver10.0 + Driver + KMDF + Universal + + + Windows10 + true + WindowsKernelModeDriver10.0 + Driver + KMDF + Universal + + + Windows10 + false + WindowsKernelModeDriver10.0 + Driver + KMDF + Universal + + + Windows10 + true + WindowsKernelModeDriver10.0 + Driver + KMDF + Universal + + + Windows10 + false + WindowsKernelModeDriver10.0 + Driver + KMDF + Universal + + + Windows10 + true + WindowsKernelModeDriver10.0 + Driver + KMDF + Universal + + + Windows10 + false + WindowsKernelModeDriver10.0 + Driver + KMDF + Universal + + + + $(IntDir) + + + + + + + + + + DbgengKernelDebugger + + + DbgengKernelDebugger + + + DbgengKernelDebugger + + + DbgengKernelDebugger + + + DbgengKernelDebugger + + + DbgengKernelDebugger + + + DbgengKernelDebugger + + + DbgengKernelDebugger + + + + %(AdditionalDependencies);$(DDK_LIB_PATH)\libcntpr.lib;wpprecorder.lib;$(DDK_LIB_PATH)\acx\km\$(ACX_VERSION_MAJOR).$(ACX_VERSION_MINOR)\acxstub.lib + + + %(AdditionalIncludeDirectories);$(SDK_INC_PATH);$(DDK_INC_PATH)\acx\km\$(ACX_VERSION_MAJOR).$(ACX_VERSION_MINOR);..\inc;. + %(PreprocessorDefinitions);UNICODE;_UNICODE + %(PreprocessorDefinitions);ACX_VERSION_MAJOR=1;ACX_VERSION_MINOR=0 + %(PreprocessorDefinitions);ACX_WORKAROUND_ACXFACTORYCIRCUIT_01;ACX_WORKAROUND_ACXPIN_01;_NEW_DELETE_OPERATORS_ + true + ..\inc\trace_macros.h + -km \ +-DENABLE_WPP_RECORDER=1 \ +-DENABLE_WPP_TRACE_FILTERING_WITH_WPP_RECORDER=1 \ +-func:DoTraceLevelMessage(LEVEL,FLAGS,MSG,...) \ +-p:SDCAVCodec + true + + + sha256 + + + + + %(AdditionalDependencies);$(DDK_LIB_PATH)\libcntpr.lib;wpprecorder.lib;$(DDK_LIB_PATH)\acx\km\$(ACX_VERSION_MAJOR).$(ACX_VERSION_MINOR)\acxstub.lib + + + %(AdditionalIncludeDirectories);$(SDK_INC_PATH);$(DDK_INC_PATH)\acx\km\$(ACX_VERSION_MAJOR).$(ACX_VERSION_MINOR);..\inc;. + %(PreprocessorDefinitions);UNICODE;_UNICODE + %(PreprocessorDefinitions);ACX_VERSION_MAJOR=1;ACX_VERSION_MINOR=0 + %(PreprocessorDefinitions);ACX_WORKAROUND_ACXFACTORYCIRCUIT_01;ACX_WORKAROUND_ACXPIN_01;_NEW_DELETE_OPERATORS_ + true + ..\inc\trace_macros.h + -km \ +-DENABLE_WPP_RECORDER=1 \ +-DENABLE_WPP_TRACE_FILTERING_WITH_WPP_RECORDER=1 \ +-func:DoTraceLevelMessage(LEVEL,FLAGS,MSG,...) \ +-p:SDCAVCodec + true + + + sha256 + + + + + %(AdditionalDependencies);$(DDK_LIB_PATH)\libcntpr.lib;wpprecorder.lib;$(DDK_LIB_PATH)\acx\km\$(ACX_VERSION_MAJOR).$(ACX_VERSION_MINOR)\acxstub.lib + + + %(AdditionalIncludeDirectories);$(SDK_INC_PATH);$(DDK_INC_PATH)\acx\km\$(ACX_VERSION_MAJOR).$(ACX_VERSION_MINOR);..\inc;. + %(PreprocessorDefinitions);UNICODE;_UNICODE + %(PreprocessorDefinitions);ACX_VERSION_MAJOR=1;ACX_VERSION_MINOR=0 + %(PreprocessorDefinitions);ACX_WORKAROUND_ACXFACTORYCIRCUIT_01;ACX_WORKAROUND_ACXPIN_01;_NEW_DELETE_OPERATORS_ + true + ..\inc\trace_macros.h + -km \ +-DENABLE_WPP_RECORDER=1 \ +-DENABLE_WPP_TRACE_FILTERING_WITH_WPP_RECORDER=1 \ +-func:DoTraceLevelMessage(LEVEL,FLAGS,MSG,...) \ +-p:SDCAVCodec + true + + + sha256 + + + + + %(AdditionalDependencies);$(DDK_LIB_PATH)\libcntpr.lib;wpprecorder.lib;$(DDK_LIB_PATH)\acx\km\$(ACX_VERSION_MAJOR).$(ACX_VERSION_MINOR)\acxstub.lib + + + %(AdditionalIncludeDirectories);$(SDK_INC_PATH);$(DDK_INC_PATH)\acx\km\$(ACX_VERSION_MAJOR).$(ACX_VERSION_MINOR);..\inc;. + %(PreprocessorDefinitions);UNICODE;_UNICODE + %(PreprocessorDefinitions);ACX_VERSION_MAJOR=1;ACX_VERSION_MINOR=0 + %(PreprocessorDefinitions);ACX_WORKAROUND_ACXFACTORYCIRCUIT_01;ACX_WORKAROUND_ACXPIN_01;_NEW_DELETE_OPERATORS_ + true + ..\inc\trace_macros.h + -km \ +-DENABLE_WPP_RECORDER=1 \ +-DENABLE_WPP_TRACE_FILTERING_WITH_WPP_RECORDER=1 \ +-func:DoTraceLevelMessage(LEVEL,FLAGS,MSG,...) \ +-p:SDCAVCodec + true + + + sha256 + + + + + %(AdditionalDependencies);$(DDK_LIB_PATH)\libcntpr.lib;wpprecorder.lib;$(DDK_LIB_PATH)\acx\km\$(ACX_VERSION_MAJOR).$(ACX_VERSION_MINOR)\acxstub.lib + + + %(AdditionalIncludeDirectories);$(SDK_INC_PATH);$(DDK_INC_PATH)\acx\km\$(ACX_VERSION_MAJOR).$(ACX_VERSION_MINOR);..\inc;. + %(PreprocessorDefinitions);UNICODE;_UNICODE + %(PreprocessorDefinitions);ACX_VERSION_MAJOR=1;ACX_VERSION_MINOR=0 + %(PreprocessorDefinitions);ACX_WORKAROUND_ACXFACTORYCIRCUIT_01;ACX_WORKAROUND_ACXPIN_01;_NEW_DELETE_OPERATORS_ + true + ..\inc\trace_macros.h + -km \ +-DENABLE_WPP_RECORDER=1 \ +-DENABLE_WPP_TRACE_FILTERING_WITH_WPP_RECORDER=1 \ +-func:DoTraceLevelMessage(LEVEL,FLAGS,MSG,...) \ +-p:SDCAVCodec + true + + + sha256 + + + + + %(AdditionalDependencies);$(DDK_LIB_PATH)\libcntpr.lib;wpprecorder.lib;$(DDK_LIB_PATH)\acx\km\$(ACX_VERSION_MAJOR).$(ACX_VERSION_MINOR)\acxstub.lib + + + %(AdditionalIncludeDirectories);$(SDK_INC_PATH);$(DDK_INC_PATH)\acx\km\$(ACX_VERSION_MAJOR).$(ACX_VERSION_MINOR);..\inc;. + %(PreprocessorDefinitions);UNICODE;_UNICODE + %(PreprocessorDefinitions);ACX_VERSION_MAJOR=1;ACX_VERSION_MINOR=0 + %(PreprocessorDefinitions);ACX_WORKAROUND_ACXFACTORYCIRCUIT_01;ACX_WORKAROUND_ACXPIN_01;_NEW_DELETE_OPERATORS_ + true + ..\inc\trace_macros.h + -km \ +-DENABLE_WPP_RECORDER=1 \ +-DENABLE_WPP_TRACE_FILTERING_WITH_WPP_RECORDER=1 \ +-func:DoTraceLevelMessage(LEVEL,FLAGS,MSG,...) \ +-p:SDCAVCodec + true + + + sha256 + + + + + %(AdditionalDependencies);$(DDK_LIB_PATH)\libcntpr.lib;wpprecorder.lib;$(DDK_LIB_PATH)\acx\km\$(ACX_VERSION_MAJOR).$(ACX_VERSION_MINOR)\acxstub.lib + + + %(AdditionalIncludeDirectories);$(SDK_INC_PATH);$(DDK_INC_PATH)\acx\km\$(ACX_VERSION_MAJOR).$(ACX_VERSION_MINOR);..\inc;. + %(PreprocessorDefinitions);UNICODE;_UNICODE + %(PreprocessorDefinitions);ACX_VERSION_MAJOR=1;ACX_VERSION_MINOR=0 + %(PreprocessorDefinitions);ACX_WORKAROUND_ACXFACTORYCIRCUIT_01;ACX_WORKAROUND_ACXPIN_01;_NEW_DELETE_OPERATORS_ + true + ..\inc\trace_macros.h + -km \ +-DENABLE_WPP_RECORDER=1 \ +-DENABLE_WPP_TRACE_FILTERING_WITH_WPP_RECORDER=1 \ +-func:DoTraceLevelMessage(LEVEL,FLAGS,MSG,...) \ +-p:SDCAVCodec + true + + + sha256 + + + + + %(AdditionalDependencies);$(DDK_LIB_PATH)\libcntpr.lib;wpprecorder.lib;$(DDK_LIB_PATH)\acx\km\$(ACX_VERSION_MAJOR).$(ACX_VERSION_MINOR)\acxstub.lib + + + %(AdditionalIncludeDirectories);$(SDK_INC_PATH);$(DDK_INC_PATH)\acx\km\$(ACX_VERSION_MAJOR).$(ACX_VERSION_MINOR);..\inc;. + %(PreprocessorDefinitions);UNICODE;_UNICODE + %(PreprocessorDefinitions);ACX_VERSION_MAJOR=1;ACX_VERSION_MINOR=0 + %(PreprocessorDefinitions);ACX_WORKAROUND_ACXFACTORYCIRCUIT_01;ACX_WORKAROUND_ACXPIN_01;_NEW_DELETE_OPERATORS_ + true + ..\inc\trace_macros.h + -km \ +-DENABLE_WPP_RECORDER=1 \ +-DENABLE_WPP_TRACE_FILTERING_WITH_WPP_RECORDER=1 \ +-func:DoTraceLevelMessage(LEVEL,FLAGS,MSG,...) \ +-p:SDCAVCodec + true + + + sha256 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + This project references NuGet package(s) that are missing on this computer. Use NuGet Package Restore to download them. For more information, see http://go.microsoft.com/fwlink/?LinkID=322105. The missing file is {0}. + + + + diff --git a/audio/Soundwire/Samples/SdcaVad/SdcaVDsp/SDCAVDsp.vcxproj.Filters b/audio/Soundwire/Samples/SdcaVad/SdcaVDsp/SDCAVDsp.vcxproj.Filters new file mode 100644 index 000000000..e1d939163 --- /dev/null +++ b/audio/Soundwire/Samples/SdcaVad/SdcaVDsp/SDCAVDsp.vcxproj.Filters @@ -0,0 +1,24 @@ + + + + + {4FC737F1-C7A5-4376-A066-2A32D752A2FF} + cpp;c;cc;cxx;def;odl;idl;hpj;bat;asm;asmx + + + {93995380-89BD-4b04-88EB-625FBE52EBFB} + h;hpp;hxx;hm;inl;inc;xsd + + + {67DA6AB6-F800-4c08-8B7A-83BB121AAD01} + rc;ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe;resx;tiff;tif;png;wav;mfcribbon-ms + + + {8E41214B-6785-4CFE-B992-037D68949A14} + inf;inv;inx;mof;mc; + + + + + + diff --git a/audio/Soundwire/Samples/SdcaVad/SdcaVDsp/SdcaVApo.inx b/audio/Soundwire/Samples/SdcaVad/SdcaVDsp/SdcaVApo.inx new file mode 100644 index 000000000..5b1b39d56 --- /dev/null +++ b/audio/Soundwire/Samples/SdcaVad/SdcaVDsp/SdcaVApo.inx @@ -0,0 +1,79 @@ +[Version] +Signature = "$WINDOWS NT$" +Class = AudioProcessingObject +ClassGuid = {5989fce8-9cd0-467d-8a6a-5419e31529d4} +Provider = %ProviderName% +DriverVer = 02/22/2016,1.0.0.1 +CatalogFile = sdcavad.cat +PnpLockDown = 1 + +[Manufacturer] +%MfgName% = ApoComponents,NT$ARCH$.10.0...19041 + +[ApoComponents.NT$ARCH$.10.0...19041] +%Apo.ComponentDesc% = ApoComponent_Install,SWC\VEN_SDCAV_SMPL&CID_APO + +[ApoComponent_Install] +CopyFiles = Apo_CopyFiles +AddReg = Apo_AddReg + +[Apo_CopyFiles] +sdcavkwsapo.dll + +[Apo_AddReg] +; Keyword Spotter Endpoint effect APO COM registration +HKR,Classes\CLSID\%KWS_FX_ENDPOINT_CLSID%,,,%KWS_FriendlyName% +HKR,Classes\CLSID\%KWS_FX_ENDPOINT_CLSID%\InProcServer32,,0x00020000,%13%\sdcavKWSApo.dll +HKR,Classes\CLSID\%KWS_FX_ENDPOINT_CLSID%\InProcServer32,ThreadingModel,,"Both" + +; Keyword Spotter APO registration +HKR,AudioEngine\AudioProcessingObjects\%KWS_FX_ENDPOINT_CLSID%,"FriendlyName", ,%KWS_FriendlyName% +HKR,AudioEngine\AudioProcessingObjects\%KWS_FX_ENDPOINT_CLSID%,"Copyright", ,%Copyright% +HKR,AudioEngine\AudioProcessingObjects\%KWS_FX_ENDPOINT_CLSID%,"MajorVersion", 0x00010001, 1 +HKR,AudioEngine\AudioProcessingObjects\%KWS_FX_ENDPOINT_CLSID%,"MinorVersion", 0x00010001, 1 +HKR,AudioEngine\AudioProcessingObjects\%KWS_FX_ENDPOINT_CLSID%,"Flags", 0x00010001, 0xC +HKR,AudioEngine\AudioProcessingObjects\%KWS_FX_ENDPOINT_CLSID%,"MinInputConnections", 0x00010001, 1 +HKR,AudioEngine\AudioProcessingObjects\%KWS_FX_ENDPOINT_CLSID%,"MaxInputConnections", 0x00010001, 1 +HKR,AudioEngine\AudioProcessingObjects\%KWS_FX_ENDPOINT_CLSID%,"MinOutputConnections", 0x00010001, 1 +HKR,AudioEngine\AudioProcessingObjects\%KWS_FX_ENDPOINT_CLSID%,"MaxOutputConnections", 0x00010001, 1 +HKR,AudioEngine\AudioProcessingObjects\%KWS_FX_ENDPOINT_CLSID%,"MaxInstances", 0x00010001, 0xffffffff +HKR,AudioEngine\AudioProcessingObjects\%KWS_FX_ENDPOINT_CLSID%,"NumAPOInterfaces", 0x00010001, 1 +HKR,AudioEngine\AudioProcessingObjects\%KWS_FX_ENDPOINT_CLSID%,"APOInterface0", ,"{FD7F2B29-24D0-4B5C-B177-592C39F9CA10}" + +[ApoComponent_Install.HW] +AddReg = FriendlyName_AddReg + +[FriendlyName_AddReg] +HKR,,FriendlyName,,%Apo.ComponentDesc% + +[ApoComponent_Install.Services] +AddService=,2 ; no function driver, install a null driver. + +[SourceDisksNames] +1 = Disk + +[SourceDisksFiles] +sdcavkwsapo.dll = 1 + +[DestinationDirs] +Apo_CopyFiles = 13 ; 13=Package's DriverStore directory + +[SignatureAttributes] +sdcavkwsapo.dll = SignatureAttributes.PETrust + +[SignatureAttributes.PETrust] +PETrust = true + +[Strings] +MfgName = "TODO-Set-Manufacturer" +ProviderName = "TODO-Set-Provider" +Apo.ComponentDesc = "Audio SDCAV APO Sample" + +; Driver developers would replace these CLSIDs with those of their own APOs +KWS_FX_ENDPOINT_CLSID = "{9D89F614-F9D6-40DD-9F21-5E69FA3981ED}" + +; see audioenginebaseapo.idl for APO_FLAG enum values +APO_FLAG_DEFAULT = 0x0000000e + +KWS_FriendlyName = "Keyword Spotter APO Sample (endpoint effect)" +Copyright = "Sample" diff --git a/audio/Soundwire/Samples/SdcaVad/SdcaVDsp/SdcaVDsp.inx b/audio/Soundwire/Samples/SdcaVad/SdcaVDsp/SdcaVDsp.inx new file mode 100644 index 000000000..a0e0a6543 --- /dev/null +++ b/audio/Soundwire/Samples/SdcaVad/SdcaVDsp/SdcaVDsp.inx @@ -0,0 +1,159 @@ +;/*++ +; +;Copyright (c) Microsoft Corporation. All rights reserved. +; +;Module Name: +; +; SDCAVDsp.INF +; +;--*/ + +[Version] +Signature="$WINDOWS NT$" +Class=MEDIA +ClassGuid={4d36e96c-e325-11ce-bfc1-08002be10318} +Provider=%ProviderName% +DriverVer=06/13/2016, 1.0.0.1 +CatalogFile=SDCAVad.cat +PnpLockdown=1 + +[DestinationDirs] +DefaultDestDir = 13 + +;***************************************** +; Audio Device Install Section +;***************************************** +[ControlFlags] +ExcludeFromSelect = {4DCB0606-6415-4A36-BDC5-9B1792117DC9}\Render +ExcludeFromSelect = {4DCB0606-6415-4A36-BDC5-9B1792117DC9}\Capture + +[Manufacturer] +%StdMfg%=Standard,NT$ARCH$.10.0...19041 + +[Standard.NT$ARCH$.10.0...19041] +%WdfDspDevice.DeviceDesc%=Audio_Device, SOUNDWIRETEST\DSP +%WdfDspDevice.DeviceDesc%=Audio_Child_Device, {4DCB0606-6415-4A36-BDC5-9B1792117DC9}\Render +%WdfDspDevice.DeviceDesc%=Audio_Child_Device, {4DCB0606-6415-4A36-BDC5-9B1792117DC9}\Capture + +[Audio_Device.NT] +CopyFiles=Audio_Device.NT.Copy +AddReg=EVENTDETECTORCONTOSOADAPTER.AddReg + +[Audio_Child_Device.NT] +CopyFiles=Audio_Device.NT.Copy + +[Audio_Device.NT.Copy] +SDCAVDsp.sys +EventDetectorContosoAdapter.dll + +;-------------- Service installation + +[Audio_Device.NT.Services] +AddService = SDCAVDsp, %SPSVCINST_ASSOCSERVICE%, Audio_Service_Inst + +[Audio_Child_Device.NT.Services] +;NULL Driver +AddService = , %SPSVCINST_ASSOCSERVICE% + +[Audio_Service_Inst] +DisplayName = %WdfDspDevice.DeviceDesc% +ServiceType = 1 ; SERVICE_KERNEL_DRIVER +StartType = 3 ; SERVICE_DEMAND_START +ErrorControl = 1 ; SERVICE_ERROR_NORMAL +ServiceBinary = %13%\SDCAVDsp.sys + +[SourceDisksNames] +1 = %DiskId1%,,,"" + +[SourceDisksFiles] +SDCAVDsp.sys = 1,, +EventDetectorContosoAdapter.dll = 1,, + +[Audio_Device.NT.Wdf] +KmdfService = SDCAVDsp, Audio_wdfsect +[Audio_wdfsect] +KmdfLibraryVersion = $KMDFVERSION$ + +[EVENTDETECTORCONTOSOADAPTER.AddReg] +HKCR,CLSID\%EVENTDETECTORCONTOSOADAPTER_CLSID2%,,,"EventDetectorContosoAdapter2 Class" +HKCR,CLSID\%EVENTDETECTORCONTOSOADAPTER_CLSID2%\InProcServer32,,0x00020000,%13%\eventdetectorcontosoadapter.dll +HKCR,CLSID\%EVENTDETECTORCONTOSOADAPTER_CLSID2%\InProcServer32,ThreadingModel,,"Apartment" +HKCR,CLSID\%EVENTDETECTORCONTOSOADAPTER_CLSID2%\Version,,,"1.0" + +; +; render interfaces: speaker +; +[Audio_Device.I.Speaker] +AddReg=Audio_Device.I.Speaker.AddReg +[Audio_Device.I.Speaker.AddReg] +HKR,,CLSID,,%Proxy.CLSID% +HKR,,FriendlyName,,%Audio_Device.Speaker.szPname% +; The following lines opt-in to pull mode. +HKR,EP\0,%PKEY_AudioEndpoint_Association%,,%KSNODETYPE_ANY% +HKR,EP\0,%PKEY_AudioEndpoint_Supports_EventDriven_Mode%,0x00010001,0x1 + +; +; capture interfaces: microphone +; +[Audio_Device.I.Microphone] +AddReg=Audio_Device.I.Microphone.AddReg +[Audio_Device.I.Microphone.AddReg] +HKR,,CLSID,,%Proxy.CLSID% +HKR,,FriendlyName,,%Audio_Device.Microphone.szPname% +; The following lines opt-in to pull mode. +HKR,EP\0,%PKEY_AudioEndpoint_Association%,,%KSNODETYPE_ANY% +HKR,EP\0,%PKEY_AudioEndpoint_Supports_EventDriven_Mode%,0x00010001,0x1 + +; +; PnP add interface directives for dynamic enumerated audio endpoints. +; +[Audio_Child_Device.NT.Interfaces] +; Interfaces for render endpoint. capture is used for loopback. +AddInterface=%KSCATEGORY_AUDIO%, %KSNAME_Speaker%, Audio_Device.I.Speaker +AddInterface=%KSCATEGORY_RENDER%, %KSNAME_Speaker%, Audio_Device.I.Speaker +AddInterface=%KSCATEGORY_REALTIME%, %KSNAME_Speaker%, Audio_Device.I.Speaker +;AddInterface=%KSCATEGORY_CAPTURE%, %KSNAME_Speaker%, Audio_Device.I.Speaker + +; Interfaces for mic capture endpoint +AddInterface=%KSCATEGORY_AUDIO%, %KSNAME_Microphone%, Audio_Device.I.Microphone +AddInterface=%KSCATEGORY_CAPTURE%, %KSNAME_Microphone%, Audio_Device.I.Microphone +AddInterface=%KSCATEGORY_REALTIME%, %KSNAME_Microphone%, Audio_Device.I.Microphone + +[Strings] +; +;Non-localizable +; +KSNAME_Speaker="Speaker0" +KSNAME_Microphone="Microphone0" + +SPSVCINST_ASSOCSERVICE = 0x00000002 +ProviderName = "VS_Microsoft" + +Proxy.CLSID = "{17CCA71B-ECD7-11D0-B908-00A0C9223196}" +KSCATEGORY_AUDIO = "{6994AD04-93EF-11D0-A3CC-00A0C9223196}" +KSCATEGORY_RENDER = "{65E8773E-8F56-11D0-A3B9-00A0C9223196}" +KSCATEGORY_CAPTURE = "{65E8773D-8F56-11D0-A3B9-00A0C9223196}" +KSCATEGORY_REALTIME = "{EB115FFC-10C8-4964-831D-6DCB02E6F23F}" + +MediaCategories="SYSTEM\CurrentControlSet\Control\MediaCategories" +KSNODETYPE_ANY = "{00000000-0000-0000-0000-000000000000}" + +PKEY_AudioEndpoint_ControlPanelPageProvider = "{1DA5D803-D492-4EDD-8C23-E0C0FFEE7F0E},1" +PKEY_AudioEndpoint_Association = "{1DA5D803-D492-4EDD-8C23-E0C0FFEE7F0E},2" +PKEY_AudioEndpoint_Supports_EventDriven_Mode = "{1DA5D803-D492-4EDD-8C23-E0C0FFEE7F0E},7" +PKEY_AudioEndpoint_Default_VolumeInDb = "{1DA5D803-D492-4EDD-8C23-E0C0FFEE7F0E},9" + +; Driver developers would replace this CLSID with their own keyword detector OEM adapter +EVENTDETECTORCONTOSOADAPTER_CLSID2 = {207F3D0C-5C79-496F-A94C-D3D2934DBFA9} + +; +;Localizable +; +StdMfg = "SDCA Virtual Dsp Audio Device" +DiskId1 = "SDCA Virtual Dsp Audio Driver Installation Disk" +WdfDspDevice.DeviceDesc = "SDCA Virtual Dsp Audio Driver" + +;; friendly names +Audio_Device.Speaker.szPname="SDCA Virtual DSP Speaker" +Audio_Device.Microphone.szPname="SDCA Virtual DSP Microphone" + diff --git a/audio/Soundwire/Samples/SdcaVad/SdcaVDsp/SimPeakMeter.cpp b/audio/Soundwire/Samples/SdcaVad/SdcaVDsp/SimPeakMeter.cpp new file mode 100644 index 000000000..0dfbd4e5c --- /dev/null +++ b/audio/Soundwire/Samples/SdcaVad/SdcaVDsp/SimPeakMeter.cpp @@ -0,0 +1,106 @@ +/*++ + + THIS CODE AND INFORMATION IS PROVIDED "AS IS" WITHOUT WARRANTY OF ANY + KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE + IMPLIED WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A PARTICULAR + PURPOSE. + +Module Name: + + SimPeakMeter.cpp + +Abstract: + + Virtual Peakmeter - aggregates all streams + +Environment: + + Kernel mode + +--*/ + +#include "private.h" +#include "SimPeakMeter.h" + +#ifndef __INTELLISENSE__ +#include "SimPeakMeter.tmh" +#endif + +_Use_decl_annotations_ +PAGED_CODE_SEG +CSimPeakMeter::CSimPeakMeter() +{ + PAGED_CODE(); + m_NumStreams = 0; + m_PeakMeterIndex = 0; +} + +_Use_decl_annotations_ +PAGED_CODE_SEG +CSimPeakMeter::~CSimPeakMeter() +{ + PAGED_CODE(); +} + +_Use_decl_annotations_ +PAGED_CODE_SEG +LONG CSimPeakMeter::GetValue(ULONG Channel) +{ + PAGED_CODE(); + + // Ignore channel + UNREFERENCED_PARAMETER(Channel); + +#define PEAKMETER_VALUE_FULL (PEAKMETER_MAXIMUM / PEAKMETER_STEPPING_DELTA * PEAKMETER_STEPPING_DELTA) +#define PEAKMETER_VALUE_HALF (PEAKMETER_MAXIMUM / 2 / PEAKMETER_STEPPING_DELTA * PEAKMETER_STEPPING_DELTA) +#define PEAKMETER_VALUE_QUARTER (PEAKMETER_MAXIMUM / 4 / PEAKMETER_STEPPING_DELTA * PEAKMETER_STEPPING_DELTA) +#define PEAKMETER_VALUE_ONE_EIGTH (PEAKMETER_MAXIMUM / 8 / PEAKMETER_STEPPING_DELTA * PEAKMETER_STEPPING_DELTA) + + LONG PeakMeterValues[] = { + PEAKMETER_VALUE_ONE_EIGTH, + PEAKMETER_VALUE_QUARTER, + PEAKMETER_VALUE_HALF, + PEAKMETER_VALUE_FULL, + PEAKMETER_VALUE_HALF, + PEAKMETER_VALUE_QUARTER + }; + + if (m_NumStreams) + { + LONG pmi = InterlockedIncrement(&m_PeakMeterIndex); + if (pmi == ARRAYSIZE(PeakMeterValues)) + { + pmi = 0; + InterlockedExchange(&m_PeakMeterIndex, 0); + } + + return PeakMeterValues[pmi]; + } + + // + // No active streams. Peak meter = 0 + // + return 0; +} + +_Use_decl_annotations_ +PAGED_CODE_SEG +NTSTATUS CSimPeakMeter::StartStream() +{ + PAGED_CODE(); + InterlockedIncrement(&m_NumStreams); + + return STATUS_SUCCESS; +} + +_Use_decl_annotations_ +PAGED_CODE_SEG +NTSTATUS CSimPeakMeter::StopStream() +{ + PAGED_CODE(); + + ASSERT(m_NumStreams); + InterlockedDecrement(&m_NumStreams); + + return STATUS_SUCCESS; +} diff --git a/audio/Soundwire/Samples/SdcaVad/SdcaVDsp/SimPeakMeter.h b/audio/Soundwire/Samples/SdcaVad/SdcaVDsp/SimPeakMeter.h new file mode 100644 index 000000000..7203f299c --- /dev/null +++ b/audio/Soundwire/Samples/SdcaVad/SdcaVDsp/SimPeakMeter.h @@ -0,0 +1,51 @@ +/*++ + + THIS CODE AND INFORMATION IS PROVIDED "AS IS" WITHOUT WARRANTY OF ANY + KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE + IMPLIED WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A PARTICULAR + PURPOSE. + +Module Name: + + SimPeakMeter.h + +Abstract: + + Virtual Peakmeter - aggregates all streams + +Environment: + + Kernel mode + +--*/ + +#pragma once + +class CSimPeakMeter +{ +public: + __drv_maxIRQL(PASSIVE_LEVEL) + PAGED_CODE_SEG + CSimPeakMeter(); + + __drv_maxIRQL(PASSIVE_LEVEL) + PAGED_CODE_SEG + ~CSimPeakMeter(); + + __drv_maxIRQL(PASSIVE_LEVEL) + PAGED_CODE_SEG + LONG GetValue(_In_ ULONG Channel); + + __drv_maxIRQL(PASSIVE_LEVEL) + PAGED_CODE_SEG + NTSTATUS StartStream(); + + __drv_maxIRQL(PASSIVE_LEVEL) + PAGED_CODE_SEG + NTSTATUS StopStream(); + +private: + LONG m_NumStreams; + LONG m_PeakMeterIndex; +}; + diff --git a/audio/Soundwire/Samples/SdcaVad/SdcaVDsp/ToneGenerator.cpp b/audio/Soundwire/Samples/SdcaVad/SdcaVDsp/ToneGenerator.cpp new file mode 100644 index 000000000..f98067c22 --- /dev/null +++ b/audio/Soundwire/Samples/SdcaVad/SdcaVDsp/ToneGenerator.cpp @@ -0,0 +1,329 @@ +/*++ + +Copyright (c) Microsoft Corporation All Rights Reserved + +Module Name: + + ToneGenerator + +Abstract: + + Implementation of a generic sine wave generator. + +--*/ + +#include +#include +#include +#define NOBITMAP +#include +#undef NOBITMAP + +#define _USE_MATH_DEFINES +#include +#include + +#include + +#define TONEGENERATOR_POOLTAG 'TGMP' + +const double TWO_PI = M_PI * 2; + +#define MIN(x, y) ((x) < (y) ? (x) : (y)) +#define IF_FAILED_JUMP(result, tag) do {if (!NT_SUCCESS(result)) {goto tag;}} while(false) +#define IF_TRUE_JUMP(result, tag) do {if (result) {goto tag;}} while(false) +#define IF_TRUE_ACTION_JUMP(result, action, tag) do {if (result) {action; goto tag;}} while(false) + +// +// Double to long conversion. +// +__drv_maxIRQL(DISPATCH_LEVEL) +#pragma code_seg() +long ConvertToLong(double Value) +{ + return (long)(Value * _I32_MAX); +}; + +// +// Double to short conversion. +// +__drv_maxIRQL(DISPATCH_LEVEL) +#pragma code_seg() +short ConvertToShort(double Value) +{ + return (short)(Value * _I16_MAX); +}; + +// +// Double to char conversion. +// +__drv_maxIRQL(DISPATCH_LEVEL) +#pragma code_seg() +unsigned char ConvertToUChar(double Value) +{ + const double F_127_5 = 127.5; + return (unsigned char)(Value * F_127_5 + F_127_5); +}; + + +// +// Ctor: basic init. +// +_Use_decl_annotations_ +__declspec(code_seg("PAGE")) +ToneGenerator::ToneGenerator() +: m_Frequency(0), + m_ChannelCount(0), + m_BitsPerSample(0), + m_SamplesPerSecond(0), + m_Mute(false), + m_PartialFrame(NULL), + m_PartialFrameBytes(0), + m_FrameSize(0) +{ + // Theta (double) and SampleIncrement (double) are init in the Init() method + // after saving the floating point state. +} + +// +// Dtor: free resources. +// +_Use_decl_annotations_ +__declspec(code_seg("PAGE")) +ToneGenerator::~ToneGenerator() +{ + if (m_PartialFrame) + { + ExFreePoolWithTag(m_PartialFrame, TONEGENERATOR_POOLTAG); + m_PartialFrame = NULL; + m_PartialFrameBytes = 0; + } +} + +// +// Init a new frame. +// Note: caller will save and restore the floatingpoint state. +// +#pragma warning(push) +// Caller wraps this routine between KeSaveFloatingPointState/KeRestoreFloatingPointState calls. +#pragma warning(disable: 28110) + +_Use_decl_annotations_ +#pragma code_seg() +VOID ToneGenerator::InitNewFrame +( + _Out_writes_bytes_(FrameSize) BYTE* Frame, + _In_ DWORD FrameSize +) +{ + double sinValue = m_ToneDCOffset + m_ToneAmplitude * sin( m_Theta ); + + if (FrameSize != (DWORD)m_ChannelCount * m_BitsPerSample/8) + { + ASSERT(FALSE); + RtlZeroMemory(Frame, FrameSize); + return; + } + + for(ULONG i = 0; i < m_ChannelCount; ++i) + { + if (m_BitsPerSample == 8) + { + unsigned char *dataBuffer = reinterpret_cast(Frame); + dataBuffer[i] = ConvertToUChar(sinValue); + } + else if (m_BitsPerSample == 16) + { + short *dataBuffer = reinterpret_cast(Frame); + dataBuffer[i] = ConvertToShort(sinValue); + } + else if (m_BitsPerSample == 24) + { + BYTE *dataBuffer = Frame; + long val = ConvertToLong(sinValue); + val = val >> 8; + RtlCopyMemory(dataBuffer, &val, 3); + } + else if (m_BitsPerSample == 32) + { + long *dataBuffer = reinterpret_cast(Frame); + dataBuffer[i] = ConvertToLong(sinValue); + } + } + + m_Theta += m_SampleIncrement; + if (m_Theta >= TWO_PI) + { + m_Theta -= TWO_PI; + } +} +#pragma warning(pop) + +// +// GenerateSamples() +// +// Generate a sine wave that fits into the specified buffer. +// +// Buffer - Buffer to hold the samples +// BufferLength - Length of the buffer. +// +// +_Use_decl_annotations_ +#pragma code_seg() +void ToneGenerator::GenerateSine +( + _Out_writes_bytes_(BufferLength) BYTE *Buffer, + _In_ size_t BufferLength +) +{ + NTSTATUS status; + KFLOATING_SAVE saveData; + BYTE * buffer; + size_t length; + size_t copyBytes; + + // if muted, or tone generator disabled via registry, + // we deliver silence. + if (m_Mute) + { + goto ZeroBuffer; + } + + status = KeSaveFloatingPointState(&saveData); + if (!NT_SUCCESS(status)) + { + goto ZeroBuffer; + } + + buffer = Buffer; + length = BufferLength; + + // + // Check if we have any residual frame bytes from the last time. + // + if (m_PartialFrameBytes) + { + ASSERT(m_FrameSize > m_PartialFrameBytes); + DWORD offset = m_FrameSize - m_PartialFrameBytes; + copyBytes = MIN(m_PartialFrameBytes, length); + RtlCopyMemory(buffer, m_PartialFrame + offset, copyBytes); + RtlZeroMemory(m_PartialFrame + offset, copyBytes); + length -= copyBytes; + buffer += copyBytes; + m_PartialFrameBytes = 0; + } + + IF_TRUE_JUMP(length == 0, Done); + + // + // Copy all the aligned frames. + // + + size_t frames = length/m_FrameSize; + + for (size_t i = 0; i < frames; ++i) + { + InitNewFrame(buffer, m_FrameSize); + buffer += m_FrameSize; + length -= m_FrameSize; + } + + IF_TRUE_JUMP(length == 0, Done); + + // + // Copy any partial frame at the end. + // + ASSERT(m_FrameSize > length); + InitNewFrame(m_PartialFrame, m_FrameSize); + RtlCopyMemory(buffer, m_PartialFrame, length); + RtlZeroMemory(m_PartialFrame, length); + m_PartialFrameBytes = m_FrameSize - (DWORD)length; + +Done: + KeRestoreFloatingPointState(&saveData); + return; + +ZeroBuffer: + RtlZeroMemory(Buffer, BufferLength); + return; +} + +_Use_decl_annotations_ +__declspec(code_seg("PAGE")) +NTSTATUS ToneGenerator::Init +( + _In_ DWORD ToneFrequency, + _In_ double ToneAmplitude, + _In_ double ToneDCOffset, + _In_ double ToneInitialPhase, + _In_ PWAVEFORMATEXTENSIBLE WfExt +) +{ + NTSTATUS status = STATUS_SUCCESS; + KFLOATING_SAVE saveData; + + // + // This sample supports PCM formats only. + // + if ((WfExt->Format.wFormatTag != WAVE_FORMAT_PCM && + !(WfExt->Format.wFormatTag == WAVE_FORMAT_EXTENSIBLE && + IsEqualGUIDAligned(WfExt->SubFormat, KSDATAFORMAT_SUBTYPE_PCM)))) + { + status = STATUS_NOT_SUPPORTED; + } + IF_FAILED_JUMP(status, Done); + + // + // Save floating state (just in case). + // + status = KeSaveFloatingPointState(&saveData); + IF_FAILED_JUMP(status, Done); + + // + // Basic init. + // + m_Theta = ToneInitialPhase; + m_Frequency = ToneFrequency; + m_ToneAmplitude = ToneAmplitude; + m_ToneDCOffset = ToneDCOffset; + + m_ChannelCount = WfExt->Format.nChannels; // # channels. + m_BitsPerSample = WfExt->Format.wBitsPerSample; // bits per sample. + m_SamplesPerSecond = WfExt->Format.nSamplesPerSec; // samples per sec. + m_Mute = false; + m_SampleIncrement = (m_Frequency * TWO_PI) / (double)m_SamplesPerSecond; + m_FrameSize = (DWORD)m_ChannelCount * m_BitsPerSample/8; + ASSERT(m_FrameSize == WfExt->Format.nBlockAlign); + + // + // Restore floating state. + // + KeRestoreFloatingPointState(&saveData); + + // + // Allocate a buffer to hold a partial frame. + // + m_PartialFrame = (BYTE*)ExAllocatePool2( + POOL_FLAG_NON_PAGED, + m_FrameSize, + TONEGENERATOR_POOLTAG); + + IF_TRUE_ACTION_JUMP(m_PartialFrame == NULL, status = STATUS_INSUFFICIENT_RESOURCES, Done); + + status = STATUS_SUCCESS; + +Done: + return status; +} + +_Use_decl_annotations_ +__declspec(code_seg("PAGE")) +NTSTATUS ToneGenerator::Init +( + _In_ DWORD ToneFrequency, + _In_ PWAVEFORMATEXTENSIBLE WfExt +) +{ + return Init(ToneFrequency, 0.5, 0, 0, WfExt); +} + diff --git a/audio/Soundwire/Samples/SdcaVad/SdcaVDsp/ToneGenerator.h b/audio/Soundwire/Samples/SdcaVad/SdcaVDsp/ToneGenerator.h new file mode 100644 index 000000000..c9fd08fc2 --- /dev/null +++ b/audio/Soundwire/Samples/SdcaVad/SdcaVDsp/ToneGenerator.h @@ -0,0 +1,93 @@ +/*++ + +Copyright (c) Microsoft Corporation All Rights Reserved + +Module Name: + + ToneGenerator.h + +Abstract: + + Declaration of a generic sine wave generator. + +--*/ +#ifndef _SAMPLE_TONEGENERATOR_H +#define _SAMPLE_TONEGENERATOR_H + +class ToneGenerator +{ +public: + DWORD m_Frequency; + WORD m_ChannelCount; + WORD m_BitsPerSample; + DWORD m_SamplesPerSecond; + double m_Theta; + double m_SampleIncrement; + bool m_Mute; + BYTE* m_PartialFrame; + DWORD m_PartialFrameBytes; + DWORD m_FrameSize; + double m_ToneAmplitude; + double m_ToneDCOffset; + +public: + __drv_maxIRQL(PASSIVE_LEVEL) + __declspec(code_seg("PAGE")) + ToneGenerator(); + + __drv_maxIRQL(PASSIVE_LEVEL) + __declspec(code_seg("PAGE")) + ~ToneGenerator(); + + __drv_maxIRQL(PASSIVE_LEVEL) + __declspec(code_seg("PAGE")) + NTSTATUS + Init + ( + _In_ DWORD ToneFrequency, + _In_ double ToneAmplitude, + _In_ double ToneDCOffset, + _In_ double ToneInitialPhase, + _In_ PWAVEFORMATEXTENSIBLE WfExt + ); + + __drv_maxIRQL(PASSIVE_LEVEL) + __declspec(code_seg("PAGE")) + NTSTATUS + Init + ( + _In_ DWORD ToneFrequency, + _In_ PWAVEFORMATEXTENSIBLE WfExt + ); + + __drv_maxIRQL(DISPATCH_LEVEL) + #pragma code_seg() + VOID + GenerateSine + ( + _Out_writes_bytes_(BufferLength) BYTE *Buffer, + _In_ size_t BufferLength + ); + + __drv_maxIRQL(PASSIVE_LEVEL) + __declspec(code_seg("PAGE")) + VOID + SetMute + ( + _In_ bool Value + ) + { + m_Mute = Value; + } + +private: + __drv_maxIRQL(DISPATCH_LEVEL) + #pragma code_seg() + VOID InitNewFrame + ( + _Out_writes_bytes_(FrameSize) BYTE* Frame, + _In_ DWORD FrameSize + ); +}; + +#endif // _SAMPLE_TONEGENERATOR_H diff --git a/audio/Soundwire/Samples/SdcaVad/SdcaVDsp/Trace.h b/audio/Soundwire/Samples/SdcaVad/SdcaVDsp/Trace.h new file mode 100644 index 000000000..e029aad05 --- /dev/null +++ b/audio/Soundwire/Samples/SdcaVad/SdcaVDsp/Trace.h @@ -0,0 +1,34 @@ +/*++ + +Copyright (c) Microsoft Corporation + +Module Name: + +Trace.h + +--*/ + +#pragma once + +#include +#include // For TRACE_LEVEL definitions + +#define WPP_TOTAL_BUFFER_SIZE (PAGE_SIZE) +#define WPP_ERROR_PARTITION_SIZE (WPP_TOTAL_BUFFER_SIZE/4) + +// {CDB67DAC-8621-4E28-97A9-9C6EAFAA4A64} +#define WPP_CONTROL_GUIDS \ +WPP_DEFINE_CONTROL_GUID(DrvLogger,(cdb67dac,8621,4e28,97a9,9c6eafaa4a64), \ + WPP_DEFINE_BIT(FLAG_DEVICE_ALL) /* bit 0 = 0x00000001 */ \ + WPP_DEFINE_BIT(FLAG_FUNCTION) /* bit 1 = 0x00000002 */ \ + WPP_DEFINE_BIT(FLAG_INFO) /* bit 2 = 0x00000004 */ \ + WPP_DEFINE_BIT(FLAG_PNP) /* bit 3 = 0x00000008 */ \ + WPP_DEFINE_BIT(FLAG_POWER) /* bit 4 = 0x00000010 */ \ + WPP_DEFINE_BIT(FLAG_STREAM) /* bit 5 = 0x00000020 */ \ + WPP_DEFINE_BIT(FLAG_INIT) /* bit 6 = 0x00000040 */ \ + WPP_DEFINE_BIT(FLAG_DDI) /* bit 7 = 0x00000080 */ \ + WPP_DEFINE_BIT(FLAG_GENERIC) /* bit 8 = 0x00000100 */ \ + ) + +#include "trace_macros.h" + diff --git a/audio/Soundwire/Samples/SdcaVad/SdcaVDsp/WaveReader.cpp b/audio/Soundwire/Samples/SdcaVad/SdcaVDsp/WaveReader.cpp new file mode 100644 index 000000000..d7b048929 --- /dev/null +++ b/audio/Soundwire/Samples/SdcaVad/SdcaVDsp/WaveReader.cpp @@ -0,0 +1,772 @@ +/*++ + +Copyright (c) Microsoft Corporation All Rights Reserved + +Module Name: + WaveReader.cpp + +Abstract: + Implementation of ACX DSP Test Driver wave file reader + + To read data from disk, this class maintains a circular data buffer. This buffer is segmented into multiple chunks of + big buffer (Though we only need two, so it is set to two now). Initially we fill first two chunks and once a chunk gets emptied + by OS, we schedule a workitem to fill the next available chunk. + + +--*/ + +#pragma warning (disable : 4127) +#pragma warning (disable : 26165) + +#include "private.h" +#include +#include "stdunk.h" +#include +#include +#include +#include "WaveReader.h" + +#define FILE_NAME_BUFFER_TAG 'WRT1' +#define WAVE_DATA_BUFFER_TAG 'WRT2' +#define WORK_ITEM_BUFFER_TAG 'WRT3' + +#define MAX_READ_WORKER_ITEM_COUNT 15 + +#define IF_FAILED_JUMP(result, tag) do {if (!NT_SUCCESS(result)) {goto tag;}} while(false) +#define IF_TRUE_JUMP(result, tag) do {if (result) {goto tag;}} while(false) +#define IF_TRUE_ACTION_JUMP(result, action, tag) do {if (result) {action; goto tag;}} while(false) + +PREADWORKER_PARAM CWaveReader::m_pWorkItems = NULL; +PDEVICE_OBJECT CWaveReader::m_pDeviceObject = NULL; + + +/*++ + +Routine Description: + Ctor: basic init. + +--*/ + +_Use_decl_annotations_ +PAGED_CODE_SEG +CWaveReader::CWaveReader() +: m_ChannelCount(0), + m_BitsPerSample(0), + m_SamplesPerSecond(0), + m_Mute(false), + m_FileHandle(NULL) +{ + PAGED_CODE(); + m_WaveDataQueue.pWavData = NULL; + KeInitializeMutex(&m_FileSync, 0); +} + +/*++ + +Routine Description: + Dtor: free resources. + +--*/ +_Use_decl_annotations_ +PAGED_CODE_SEG +CWaveReader::~CWaveReader() +{ + PAGED_CODE(); + if (STATUS_SUCCESS == KeWaitForSingleObject + ( + &m_FileSync, + Executive, + KernelMode, + FALSE, + NULL + )) + { + if (m_WaveDataQueue.pWavData != NULL) + { + ExFreePoolWithTag(m_WaveDataQueue.pWavData, WAVE_DATA_BUFFER_TAG); + m_WaveDataQueue.pWavData = NULL; + } + + FileClose(); + KeReleaseMutex(&m_FileSync, FALSE); + } + +} + +/*++ + +Routine Description: + - Initializing the workitems. These workitems will be scheduled asynchronously by the OS. + - When these work items will be scheduled the wave file will be read and the data + - will be put inside the big chunks. + +Arguments: + Device object + +Return Value: + NT status code. + +--*/ + +_Use_decl_annotations_ +PAGED_CODE_SEG +NTSTATUS CWaveReader::InitializeWorkItems(PDEVICE_OBJECT DeviceObject) +{ + PAGED_CODE(); + + ASSERT(DeviceObject); + + NTSTATUS ntStatus = STATUS_SUCCESS; + + if (m_pWorkItems != NULL) + { + return ntStatus; + } + + m_pWorkItems = (PREADWORKER_PARAM) + ExAllocatePool2 + ( + POOL_FLAG_NON_PAGED, + sizeof(READWORKER_PARAM) * MAX_READ_WORKER_ITEM_COUNT, + 'RDPT' + ); + if (m_pWorkItems) + { + for (int i = 0; i < MAX_READ_WORKER_ITEM_COUNT; i++) + { + + m_pWorkItems[i].WorkItem = IoAllocateWorkItem(DeviceObject); + if (m_pWorkItems[i].WorkItem == NULL) + { + return STATUS_INSUFFICIENT_RESOURCES; + } + KeInitializeEvent + ( + &m_pWorkItems[i].EventDone, + NotificationEvent, + TRUE + ); + } + } + else + { + ntStatus = STATUS_INSUFFICIENT_RESOURCES; + } + + return ntStatus; +} + +/*++ + +Routine Description: +- Wait for all the scheduled workitems to finish. + +--*/ + + +//============================================================================= +_Use_decl_annotations_ +PAGED_CODE_SEG +void CWaveReader::WaitAllWorkItems() +{ + PAGED_CODE(); + + for (int i = 0; i < MAX_READ_WORKER_ITEM_COUNT; i++) + { + KeWaitForSingleObject + ( + &(m_pWorkItems[i].EventDone), + Executive, + KernelMode, + FALSE, + NULL + ); + } +} + +/*++ + +Routine Description: + - Deallocating the workitems. + +--*/ + + +_Use_decl_annotations_ +PAGED_CODE_SEG +VOID CWaveReader::DestroyWorkItems() +{ + PAGED_CODE(); + + if (m_pWorkItems) + { + for (int i = 0; i < MAX_READ_WORKER_ITEM_COUNT; i++) + { + if (m_pWorkItems[i].WorkItem != NULL) + { + IoFreeWorkItem(m_pWorkItems[i].WorkItem); + m_pWorkItems[i].WorkItem = NULL; + } + } + ExFreePoolWithTag(m_pWorkItems, WORK_ITEM_BUFFER_TAG); + m_pWorkItems = NULL; + } +} + +/*++ + +Routine Description: + - Get a free work item to schedule a file read operation. + +--*/ +_Use_decl_annotations_ +#pragma code_seg() +PREADWORKER_PARAM CWaveReader::GetNewWorkItem() +{ + LARGE_INTEGER timeOut = { 0 }; + NTSTATUS ntStatus; + + for (int i = 0; i < MAX_READ_WORKER_ITEM_COUNT; i++) + { + ntStatus = + KeWaitForSingleObject + ( + &m_pWorkItems[i].EventDone, + Executive, + KernelMode, + FALSE, + &timeOut + ); + if (STATUS_SUCCESS == ntStatus) + { + if (m_pWorkItems[i].WorkItem) + return &(m_pWorkItems[i]); + else + return NULL; + } + } + + return NULL; +} + +/*++ +Routine Description: +- This routine will enqueue a workitem for reading wave file and putting +- the data into the chunk buffer. + +Arguments: + Chunk descriptor for the chunk to be filled. +--*/ + +_Use_decl_annotations_ +#pragma code_seg() +VOID CWaveReader::ReadWavChunk(PCHUNKDESCRIPTOR pChunkDescriptor) +{ + PREADWORKER_PARAM pParam = NULL; + + pParam = GetNewWorkItem(); + if (pParam) + { + pParam->PtrWaveReader = this; + pParam->PtrChunkDescriptor = pChunkDescriptor; + KeResetEvent(&pParam->EventDone); + IoQueueWorkItem(pParam->WorkItem, ReadFrameWorkerCallback, + DelayedWorkQueue, (PVOID)pParam); + } +} + +_Use_decl_annotations_ +PAGED_CODE_SEG +IO_WORKITEM_ROUTINE ReadFrameWorkerCallback; +/* +Routine Description: +- This routine will be called by the OS. It will fill the chunk buffer, defined by the chunk descriptor +- If end of file is reached it will mark the end of file as true. + +Arguments: + pDeviceObject - Device object + Context - pointer to reader worker params +*/ + +_Use_decl_annotations_ +PAGED_CODE_SEG +VOID ReadFrameWorkerCallback +( + PDEVICE_OBJECT pDeviceObject, + PVOID Context +) +{ + PAGED_CODE(); + UNREFERENCED_PARAMETER(pDeviceObject); + pWaveReader pWavRd; + PREADWORKER_PARAM pParam = (PREADWORKER_PARAM)Context; + + if (NULL == pParam) + { + // This is completely unexpected, assert here. + // + ASSERT(pParam); + goto exit; + } + + pWavRd = pParam->PtrWaveReader; + + if (pWavRd == NULL) + { + goto exit; + } + if (STATUS_SUCCESS == KeWaitForSingleObject + ( + &pWavRd->m_FileSync, + Executive, + KernelMode, + FALSE, + NULL + )) + { + + NTSTATUS ntStatus = STATUS_SUCCESS; + + ASSERT(Context); + + IO_STATUS_BLOCK ioStatusBlock; + + if (pParam->WorkItem) + { + if (pWavRd->m_WaveDataQueue.bEofReached || pWavRd->m_WaveDataQueue.pWavData == NULL) + { + KeReleaseMutex(&pWavRd->m_FileSync, FALSE); + goto exit; + } + + if (pParam->PtrChunkDescriptor->pStartAddress != NULL) + { + ntStatus = ZwReadFile(pWavRd->m_FileHandle, + NULL, + NULL, + NULL, + &ioStatusBlock, + pParam->PtrChunkDescriptor->pStartAddress, + pParam->PtrChunkDescriptor->ulChunkLength, + NULL, + NULL); + + pParam->PtrChunkDescriptor->bIsChunkEmpty = false; + + if (ioStatusBlock.Information != pParam->PtrChunkDescriptor->ulChunkLength) + { + pWavRd->m_WaveDataQueue.bEofReached = true; + } + } + } + + KeReleaseMutex(&pWavRd->m_FileSync, FALSE); + } + +exit: + KeSetEvent(&pParam->EventDone, 0, FALSE); +} + +/*++ + +Routine Description: +- If all the chunks are empty this resturn true. + +--*/ + +_Use_decl_annotations_ +#pragma code_seg() +bool CWaveReader::IsAllChunkEmpty() +{ + for (int i = 0; i < NUM_OF_CHUNK_FOR_FILE_READ; i++) + { + if (!m_WaveDataQueue.sChunkDescriptor[i].bIsChunkEmpty) + { + return false; + } + } + return true; +} + +/*++ +Routine Description: + - This routine does the actual copy of data from the chunk buffer to the buffer provided by OS. + - If it empties the current chunk buffer, then it sets it state to empty and then enqueue a workitem + - to read data from the wave file and put it to the next available chunk buffer. + +Arguments: + Buffer - Pointer to the OS buffer + BufferLength - Length of the data to be filled (in bytes) + +--*/ +_Use_decl_annotations_ +#pragma code_seg() +VOID CWaveReader::CopyDataFromRingBuffer +( + BYTE *Buffer, + ULONG BufferLength +) +{ + if (IsAllChunkEmpty()) + { + RtlZeroMemory(Buffer, BufferLength); + } + else + { + ULONG prevChunk = (m_WaveDataQueue.ulReadPtr*NUM_OF_CHUNK_FOR_FILE_READ )/ m_WaveDataQueue.ulLength; + + ///////////////// + BYTE *currentBuf = Buffer; + ULONG length = BufferLength; + while (length > 0) + { + ULONG runWrite = min(length, m_WaveDataQueue.ulLength - m_WaveDataQueue.ulReadPtr); + + // Copy the wave buffer data to OS buffer + RtlCopyMemory(currentBuf, m_WaveDataQueue.pWavData + m_WaveDataQueue.ulReadPtr, runWrite); + // Zero out the wave buffer, so that if wave end of file is reached we should copy only zeros + RtlZeroMemory(m_WaveDataQueue.pWavData + m_WaveDataQueue.ulReadPtr, runWrite); + // Update the read pointer + m_WaveDataQueue.ulReadPtr = (m_WaveDataQueue.ulReadPtr + runWrite) % m_WaveDataQueue.ulLength; + currentBuf += runWrite; + length = length - runWrite; + } + + ULONG curChunk = (m_WaveDataQueue.ulReadPtr*NUM_OF_CHUNK_FOR_FILE_READ) / m_WaveDataQueue.ulLength; + + if (curChunk != prevChunk) + { + m_WaveDataQueue.currentExecutedChunk++; + // Schedule a workitem to read data from the wave file + ULONG chunkNo = m_WaveDataQueue.currentExecutedChunk % NUM_OF_CHUNK_FOR_FILE_READ; + m_WaveDataQueue.sChunkDescriptor[chunkNo].bIsChunkEmpty = true; + if (!m_WaveDataQueue.bEofReached) + { + ReadWavChunk(&m_WaveDataQueue.sChunkDescriptor[chunkNo]); + } + } + } +} + +/*++ +Routine Description: + - Just a high level read buffer call. + + Arguments: + Buffer - Pointer to the OS buffer + BufferLength - Length of the data to be filled (in bytes) +--*/ + +_Use_decl_annotations_ +#pragma code_seg() +VOID CWaveReader::ReadWaveData +( + BYTE *Buffer, + ULONG BufferLength +) +{ + if (m_Mute) + { + RtlZeroMemory(Buffer, BufferLength); + } + else + { + CopyDataFromRingBuffer(Buffer, BufferLength); + } +} + +/*++ +Routine Description: +- initialization for the wavereader member variables, +- Allocating memory for the 1 second buffer +- Preread the one second buffer data, so that when OS comes to read the data we have it available in the memory. + +Arguments: + WfExt - Format which should be used for capture + fileNameString - name of the file to be read + +Return: + NTStatus +--*/ +_Use_decl_annotations_ +PAGED_CODE_SEG +NTSTATUS CWaveReader::Init +( + PWAVEFORMATEXTENSIBLE WfExt, + PUNICODE_STRING puiFileName +) +{ + PAGED_CODE(); + NTSTATUS ntStatus = STATUS_SUCCESS; + KFLOATING_SAVE saveData; + + // Save floating state (just in case). + ntStatus = KeSaveFloatingPointState(&saveData); + if (!NT_SUCCESS(ntStatus)) + { + return ntStatus; + } + + // + // This sample supports PCM 16bit formats only. + // + if ((WfExt->Format.wFormatTag != WAVE_FORMAT_PCM && + !(WfExt->Format.wFormatTag == WAVE_FORMAT_EXTENSIBLE && + IsEqualGUIDAligned(WfExt->SubFormat, KSDATAFORMAT_SUBTYPE_PCM))) || + (WfExt->Format.wBitsPerSample != 16 && + WfExt->Format.wBitsPerSample != 8)) + { + ntStatus = STATUS_NOT_SUPPORTED; + } + IF_FAILED_JUMP(ntStatus, Done); + + // Basic init. + m_ChannelCount = WfExt->Format.nChannels; // # channels. + m_BitsPerSample = WfExt->Format.wBitsPerSample; // bits per sample. + m_SamplesPerSecond = WfExt->Format.nSamplesPerSec; // samples per sec. + m_Mute = false; + + // Wave data queue initialization + m_WaveDataQueue.ulLength = WfExt->Format.nAvgBytesPerSec; + m_WaveDataQueue.bEofReached = false; + m_WaveDataQueue.ulReadPtr = 0; + + // Mark all the chunk empty + for (int i = 0; i < NUM_OF_CHUNK_FOR_FILE_READ; i++) + { + m_WaveDataQueue.sChunkDescriptor[i].bIsChunkEmpty = true; + } + + ntStatus = OpenWaveFile(puiFileName); + IF_FAILED_JUMP(ntStatus, Done); + + ntStatus = AllocateBigBuffer(); + IF_FAILED_JUMP(ntStatus, Done); + + ntStatus = ReadHeaderAndFillBuffer(); + +Done: + (void)KeRestoreFloatingPointState(&saveData); + return ntStatus; +} + +/*++ +Routine Description: + This function read the wave header file and compare the header info with the + stream info. Currently we are using only number of channel, sampling frequency + and bits per sample as the primary parameters for the wave file to compare against + stream params. If the params don't match we return success but streams zeros. + +Return: + NTStatus +--*/ + +_Use_decl_annotations_ +PAGED_CODE_SEG +NTSTATUS CWaveReader::ReadHeaderAndFillBuffer() +{ + PAGED_CODE(); + NTSTATUS ntStatus = STATUS_SUCCESS; + ntStatus = FileReadHeader(); + + if(NT_SUCCESS(ntStatus)) + { + if (m_WaveHeader.numChannels != m_ChannelCount || + m_WaveHeader.bitsPerSample != m_BitsPerSample || + m_WaveHeader.sampleRate != m_SamplesPerSecond) + { + // If the wave file format don't match we wont treat this as error + // and we will stream zeros. So we return from here and will not read the + // wave file and wont fill the buffers. + return STATUS_SUCCESS; + } + } + + if (NT_SUCCESS(ntStatus)) + { + // If the wave file format is same as the stream format we will stream the data + // else we will just stream zeros. + ReadWavChunk(&m_WaveDataQueue.sChunkDescriptor[0]); // Fill the first chunk + ReadWavChunk(&m_WaveDataQueue.sChunkDescriptor[1]); // Fill the second chunk + // Set the current executed chunk to 1. Once OS finishs the data for the first chunk + // use the currentExecutedChunk to find the next chunk and schedule a workitem to fill the + // data into the next chunk + m_WaveDataQueue.currentExecutedChunk = 1; + } + + return ntStatus; +} + +/*++ +Routine Description: + This function allocates 1 second buffer. + Segments the buffer into multiple (currently two) chunks. Assigns the start pointer and length + for each chunk. + +Return: + NTStatus +--*/ + +_Use_decl_annotations_ +PAGED_CODE_SEG +NTSTATUS CWaveReader::AllocateBigBuffer() +{ + PAGED_CODE(); + NTSTATUS ntStatus = STATUS_SUCCESS; + + m_WaveDataQueue.pWavData = (PBYTE) + ExAllocatePool2 + ( + POOL_FLAG_NON_PAGED, + m_WaveDataQueue.ulLength, + WAVE_DATA_BUFFER_TAG + ); + if (!m_WaveDataQueue.pWavData) + { + ntStatus = STATUS_INSUFFICIENT_RESOURCES; + } + else + { + // ExAllocatePool2 zeros memory. + + ULONG chunklLength = m_WaveDataQueue.ulLength / NUM_OF_CHUNK_FOR_FILE_READ; + for (int i = 0; i < NUM_OF_CHUNK_FOR_FILE_READ; i++) + { + m_WaveDataQueue.sChunkDescriptor[i].pStartAddress = m_WaveDataQueue.pWavData + chunklLength*i; + m_WaveDataQueue.sChunkDescriptor[i].ulChunkLength = chunklLength; + } + } + return ntStatus; +} + +/*++ +Routine Description: + This function opens wave file. + +Arguments: + fileNameString - Name of the wave file + +Return: + NTStatus +--*/ + +_Use_decl_annotations_ +PAGED_CODE_SEG +NTSTATUS CWaveReader::OpenWaveFile(PUNICODE_STRING puiFileName) +{ + PAGED_CODE(); + NTSTATUS ntStatus = STATUS_SUCCESS; + + if (NT_SUCCESS(ntStatus) && puiFileName->Buffer != NULL) + { + // Create data file. + InitializeObjectAttributes + ( + &m_objectAttributes, + puiFileName, + OBJ_CASE_INSENSITIVE | OBJ_KERNEL_HANDLE, + NULL, + NULL + ); + + // Open Wave File + ntStatus = FileOpen(); + } + + return ntStatus; +} + +/*++ +Routine Description: + This function closes wave file handle. + +Return: + NTStatus +--*/ + +_Use_decl_annotations_ +PAGED_CODE_SEG +NTSTATUS CWaveReader::FileClose() +{ + PAGED_CODE(); + + NTSTATUS ntStatus = STATUS_SUCCESS; + + if (m_FileHandle) + { + ntStatus = ZwClose(m_FileHandle); + m_FileHandle = NULL; + } + + return ntStatus; +} + +/*++ +Routine Description: + Reads the wave file file header information + +Return: + NTStatus +--*/ + +_Use_decl_annotations_ +PAGED_CODE_SEG +NTSTATUS CWaveReader::FileReadHeader() +{ + PAGED_CODE(); + NTSTATUS ntStatus = STATUS_SUCCESS; + IO_STATUS_BLOCK ioStatusBlock; + + + ntStatus = ZwReadFile(m_FileHandle, + NULL, + NULL, + NULL, + &ioStatusBlock, + &m_WaveHeader, + sizeof(WAVEHEADER), + NULL, + NULL); + + return ntStatus; +} + +/*++ +Routine Description: + This function opens wave file. + +Return: + NTStatus +--*/ + +_Use_decl_annotations_ +PAGED_CODE_SEG +NTSTATUS CWaveReader::FileOpen() +{ + + PAGED_CODE(); + NTSTATUS ntStatus = STATUS_SUCCESS; + IO_STATUS_BLOCK ioStatusBlock; + + if (!m_FileHandle) + { + ntStatus = + ZwCreateFile + ( + &m_FileHandle, + GENERIC_READ, + &m_objectAttributes, + &ioStatusBlock, + NULL, + FILE_ATTRIBUTE_NORMAL, + FILE_SHARE_READ, + FILE_OPEN, + FILE_SYNCHRONOUS_IO_NONALERT, + NULL, + 0 + ); + } + + return ntStatus; +} + diff --git a/audio/Soundwire/Samples/SdcaVad/SdcaVDsp/WaveReader.h b/audio/Soundwire/Samples/SdcaVad/SdcaVDsp/WaveReader.h new file mode 100644 index 000000000..2d5c537f9 --- /dev/null +++ b/audio/Soundwire/Samples/SdcaVad/SdcaVDsp/WaveReader.h @@ -0,0 +1,196 @@ +/*++ + +Copyright (c) Microsoft Corporation All Rights Reserved + +Module Name: + + WaveReader.h + +Abstract: + + Declaration of ACX DSP Test Driver wave reader. + + +--*/ +#pragma once + +#define _USE_MATH_DEFINES +#include +#include + +#define NUM_OF_CHUNK_FOR_FILE_READ 2 + +class CWaveReader; + +// Wave header structure decleration +typedef CWaveReader *pWaveReader; +typedef struct _WAVEHEADER +{ + BYTE chunkId[4]; + ULONG chunkSize; + BYTE format[4]; + BYTE subChunkId[4]; + ULONG subChunkSize; + WORD audioFormat; + WORD numChannels; + ULONG sampleRate; + ULONG bytesPerSecond; + WORD blockAlign; + WORD bitsPerSample; + BYTE dataChunkId[4]; + ULONG dataSize; +}WAVEHEADER; + +typedef struct _CHUNKDESCRIPTOR +{ + PBYTE pStartAddress; // Starting address of the chunk + ULONG ulChunkLength; // Length of the chunk + bool bIsChunkEmpty; // If the chunk is empty +}CHUNKDESCRIPTOR; +typedef CHUNKDESCRIPTOR *PCHUNKDESCRIPTOR; + +/* + The idea here is to allocate one second long worth of buffer and divide it into NUM_OF_CHUNK_FOR_FILE_READ chunks. + In one file read operation we read and fill one chunk data . The chunk will be emptied every 10 ms by OS. + Once the OS empties one chunk data we schedule a workitem to read and fill next available chunk. +*/ + +typedef struct _WAVEDATAQUEUE +{ + PBYTE pWavData; // Pointer to the temporary buffer for reading one second worth of data from wave file + ULONG ulLength; // length of pWavData in bytes + ULONG ulReadPtr; // current reading position in pWavData in bytes + bool bEofReached; // This will be set once the eof is reached. + WORD currentExecutedChunk; + CHUNKDESCRIPTOR sChunkDescriptor[NUM_OF_CHUNK_FOR_FILE_READ]; +}WAVEDATAQUEUE; +typedef WAVEDATAQUEUE *PWAVEDATAQUEUE; + +// Parameter to workitem. +#include +typedef struct _READWORKER_PARAM { + PIO_WORKITEM WorkItem; // Pointer to the workitem + KEVENT EventDone; // Used for synchronizing a workitem for scheduling. + pWaveReader PtrWaveReader; // pointer to the wavereader class. + PCHUNKDESCRIPTOR PtrChunkDescriptor; // chunk descriptor for the chunk, which needs to be filled after file read +} READWORKER_PARAM; +typedef READWORKER_PARAM *PREADWORKER_PARAM; +#include + +__drv_maxIRQL(PASSIVE_LEVEL) +PAGED_CODE_SEG +IO_WORKITEM_ROUTINE ReadFrameWorkerCallback; + +// Wave Reader class + +class CWaveReader +{ + +public: + HANDLE m_FileHandle; // Wave File handle. + WORD m_ChannelCount; // Number of Channels for the stream during stream init + WORD m_BitsPerSample; // Number of Bits per sample for the stream during stream init + DWORD m_SamplesPerSecond; // Number of Sample per second for the stream during stream init + bool m_Mute; // Capture Zero buffer if mute + OBJECT_ATTRIBUTES m_objectAttributes; // Used for opening file. + WAVEDATAQUEUE m_WaveDataQueue; // Big buffer data object and its current state + KMUTEX m_FileSync; // Synchronizes file access + WAVEHEADER m_WaveHeader; + +public: + __drv_maxIRQL(PASSIVE_LEVEL) + PAGED_CODE_SEG + CWaveReader(); + + __drv_maxIRQL(PASSIVE_LEVEL) + PAGED_CODE_SEG + ~CWaveReader(); + + __drv_maxIRQL(PASSIVE_LEVEL) + PAGED_CODE_SEG + NTSTATUS Init + ( + _In_ PWAVEFORMATEXTENSIBLE WfExt, + _In_ PUNICODE_STRING puiFileName + ); + + __drv_maxIRQL(DISPATCH_LEVEL) + #pragma code_seg() + VOID ReadWaveData + ( + _Out_writes_bytes_(BufferLength) BYTE *Buffer, + _In_ ULONG BufferLength + ); + + __drv_maxIRQL(PASSIVE_LEVEL) + PAGED_CODE_SEG + VOID SetMute(_In_ bool Value) + { + PAGED_CODE(); + + m_Mute = Value; + } + + __drv_maxIRQL(PASSIVE_LEVEL) + PAGED_CODE_SEG + void WaitAllWorkItems(); + + // Static allocation totally related to the workitems for reading data from wavefile and putting it to chunk buffer + static PDEVICE_OBJECT m_pDeviceObject; + static PREADWORKER_PARAM m_pWorkItems; + PAGED_CODE_SEG + static NTSTATUS InitializeWorkItems(_In_ PDEVICE_OBJECT DeviceObject); + + __drv_maxIRQL(DISPATCH_LEVEL) + #pragma code_seg() + static PREADWORKER_PARAM GetNewWorkItem(); + + __drv_maxIRQL(PASSIVE_LEVEL) + PAGED_CODE_SEG + static VOID DestroyWorkItems(); + +private: + __drv_maxIRQL(DISPATCH_LEVEL) + #pragma code_seg() + VOID ReadWavChunk(PCHUNKDESCRIPTOR PtrChunkDescriptor); + + __drv_maxIRQL(DISPATCH_LEVEL) + #pragma code_seg() + bool IsAllChunkEmpty(); + + __drv_maxIRQL(PASSIVE_LEVEL) + PAGED_CODE_SEG + NTSTATUS OpenWaveFile(PUNICODE_STRING puiFileName); + + __drv_maxIRQL(PASSIVE_LEVEL) + PAGED_CODE_SEG + NTSTATUS FileClose(); + + __drv_maxIRQL(PASSIVE_LEVEL) + PAGED_CODE_SEG + NTSTATUS FileReadHeader(); + + __drv_maxIRQL(PASSIVE_LEVEL) + PAGED_CODE_SEG + NTSTATUS FileOpen(); + + __drv_maxIRQL(DISPATCH_LEVEL) + #pragma code_seg() + VOID CopyDataFromRingBuffer + ( + _Out_writes_bytes_(BufferLength) BYTE *Buffer, + _In_ ULONG BufferLength + ); + + __drv_maxIRQL(PASSIVE_LEVEL) + PAGED_CODE_SEG + NTSTATUS AllocateBigBuffer(); + + __drv_maxIRQL(PASSIVE_LEVEL) + PAGED_CODE_SEG + NTSTATUS ReadHeaderAndFillBuffer(); + + friend IO_WORKITEM_ROUTINE ReadFrameWorkerCallback; +}; + + diff --git a/audio/Soundwire/Samples/SdcaVad/SdcaVDsp/capture.cpp b/audio/Soundwire/Samples/SdcaVad/SdcaVDsp/capture.cpp new file mode 100644 index 000000000..eccd57385 --- /dev/null +++ b/audio/Soundwire/Samples/SdcaVad/SdcaVDsp/capture.cpp @@ -0,0 +1,1465 @@ +/*++ + + THIS CODE AND INFORMATION IS PROVIDED "AS IS" WITHOUT WARRANTY OF ANY + KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE + IMPLIED WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A PARTICULAR + PURPOSE. + +Module Name: + + capture.cpp + +Abstract: + + capture factory and circuit + +Environment: + + Kernel mode + +--*/ + +#include "private.h" +#include +#include "stdunk.h" +#include +#include +#include +#include "streamengine.h" +#include "CircuitHelper.h" + +#include "TestProperties.h" +#include "KeywordDetector.h" +#include "sdcastreaming.h" + +#include "AudioFormats.h" + +#ifndef __INTELLISENSE__ +#include "capture.tmh" +#endif + +// +// max # of streams. +// +#define DSPC_MAX_OUTPUT_SYSTEM_STREAMS 1 +#define DSPC_MAX_OUTPUT_KEYWORDDETECTOR_STREAMS 1 + +// +// Factory circuit IDs. +// +#define CAPTURE_DEVICE_ID_STR L"{4DCB0606-6415-4A36-BDC5-9B1792117DC9}\\Capture&CP_%wZ" +DECLARE_CONST_UNICODE_STRING(CaptureHardwareId, L"{4DCB0606-6415-4A36-BDC5-9B1792117DC9}\\Capture"); + +PAGED_CODE_SEG +NTSTATUS DspC_EvtAcxKeywordSpotterRetrieveArm( + _In_ ACXKEYWORDSPOTTER KeywordSpotter, + _In_ GUID * EventId, + _Out_ BOOLEAN * Arm +) +{ + PAGED_CODE(); + PDSP_KEYWORDSPOTTER_CONTEXT keywordSpotterCtx; + CKeywordDetector * keywordDetector = NULL; + + keywordSpotterCtx = GetDspKeywordSpotterContext(KeywordSpotter); + + keywordDetector = (CKeywordDetector*)keywordSpotterCtx->KeywordDetector; + + RETURN_NTSTATUS_IF_FAILED(keywordDetector->GetArmed(*EventId, Arm)); + + return STATUS_SUCCESS; +} + +PAGED_CODE_SEG +NTSTATUS DspC_EvtAcxKeywordSpotterAssignArm( + _In_ ACXKEYWORDSPOTTER KeywordSpotter, + _In_ GUID * EventId, + _In_ BOOLEAN Arm +) +{ + PAGED_CODE(); + PDSP_KEYWORDSPOTTER_CONTEXT keywordSpotterCtx; + CKeywordDetector * keywordDetector = NULL; + + keywordSpotterCtx = GetDspKeywordSpotterContext(KeywordSpotter); + + keywordDetector = (CKeywordDetector*)keywordSpotterCtx->KeywordDetector; + + RETURN_NTSTATUS_IF_FAILED(keywordDetector->SetArmed(*EventId, Arm)); + + // the following code is for example only, after arming the + // requested keyword we immediately trigger a detection + // so that the automated tests do not block. + if (Arm) + { + CONTOSO_KEYWORDDETECTIONRESULT detectionResult; + + // notify the keyword detector that we have a notification, to populate + // timestamp information for this detection. + keywordDetector->NotifyDetection(); + + // fill in the detection specific information + detectionResult.EventId = *EventId; + detectionResult.Header.Size = sizeof(CONTOSO_KEYWORDDETECTIONRESULT); + detectionResult.Header.PatternType = CONTOSO_KEYWORDCONFIGURATION_IDENTIFIER2; + detectionResult.KeywordStartTimestamp = keywordDetector->GetStartTimestamp(); + detectionResult.KeywordStopTimestamp = keywordDetector->GetStopTimestamp(); + keywordDetector->GetDetectorData(*EventId, &(detectionResult.ContosoDetectorResultData)); + + RETURN_NTSTATUS_IF_FAILED(AcxPnpEventGenerateEvent(keywordSpotterCtx->Event, &detectionResult, sizeof(CONTOSO_KEYWORDDETECTIONRESULT))); + } + + return STATUS_SUCCESS; +} + +PAGED_CODE_SEG +NTSTATUS DspC_EvtAcxKeywordSpotterAssignPatterns( + _In_ ACXKEYWORDSPOTTER KeywordSpotter, + _In_ GUID * EventId, + _In_ PVOID Pattern, + _In_ ULONG PatternSize + ) +{ + KSMULTIPLE_ITEM * itemsHeader = nullptr; + SOUNDDETECTOR_PATTERNHEADER * patternHeader; + CONTOSO_KEYWORDCONFIGURATION * pattern; + ULONG cbRemaining = 0; + PDSP_KEYWORDSPOTTER_CONTEXT keywordSpotterCtx; + CKeywordDetector * keywordDetector = NULL; + + PAGED_CODE(); + + keywordSpotterCtx = GetDspKeywordSpotterContext(KeywordSpotter); + + keywordDetector = (CKeywordDetector*)keywordSpotterCtx->KeywordDetector; + + cbRemaining = PatternSize; + + // The SYSVADPROPERTY_ITEM for this property ensures the value size is at + // least sizeof KSMULTIPLE_ITEM. + RETURN_NTSTATUS_IF_TRUE(cbRemaining < sizeof(KSMULTIPLE_ITEM), STATUS_INVALID_PARAMETER); + + itemsHeader = (KSMULTIPLE_ITEM*)Pattern; + + // Verify property value is large enough to include the items + RETURN_NTSTATUS_IF_TRUE(itemsHeader->Size > cbRemaining, STATUS_INVALID_PARAMETER); + + // No items so clear the configuration. + if (itemsHeader->Count == 0) + { + keywordDetector->ResetDetector(*EventId); + } + else + { + // This sample supports only 1 pattern type. + RETURN_NTSTATUS_IF_TRUE(itemsHeader->Count > 1, STATUS_NOT_SUPPORTED); + + // Bytes remaining after the items header + cbRemaining = itemsHeader->Size - sizeof(*itemsHeader); + + // Verify the property value is large enough to include the pattern header. + RETURN_NTSTATUS_IF_TRUE(cbRemaining < sizeof(SOUNDDETECTOR_PATTERNHEADER), STATUS_INVALID_PARAMETER); + + patternHeader = (SOUNDDETECTOR_PATTERNHEADER*)(itemsHeader + 1); + + // Verify the pattern type is supported. + RETURN_NTSTATUS_IF_TRUE(patternHeader->PatternType != CONTOSO_KEYWORDCONFIGURATION_IDENTIFIER2, STATUS_NOT_SUPPORTED); + + // Verify the property value is large enough for the pattern. + RETURN_NTSTATUS_IF_TRUE(cbRemaining < patternHeader->Size, STATUS_INVALID_PARAMETER); + + // Verify the pattern is large enough. + RETURN_NTSTATUS_IF_TRUE(patternHeader->Size != sizeof(CONTOSO_KEYWORDCONFIGURATION), STATUS_INVALID_PARAMETER); + + pattern = (CONTOSO_KEYWORDCONFIGURATION*)(patternHeader); + + RETURN_NTSTATUS_IF_FAILED(keywordDetector->DownloadDetectorData(*EventId, pattern->ContosoDetectorConfigurationData)); + } + + return STATUS_SUCCESS; +} + +PAGED_CODE_SEG +NTSTATUS DspC_EvtAcxKeywordSpotterAssignReset( + _In_ ACXKEYWORDSPOTTER KeywordSpotter, + _In_ GUID * EventId + ) +{ + PAGED_CODE(); + PDSP_KEYWORDSPOTTER_CONTEXT keywordSpotterCtx; + CKeywordDetector * keywordDetector = NULL; + + keywordSpotterCtx = GetDspKeywordSpotterContext(KeywordSpotter); + + keywordDetector = (CKeywordDetector*)keywordSpotterCtx->KeywordDetector; + + RETURN_NTSTATUS_IF_FAILED(keywordDetector->ResetDetector(*EventId)); + + return STATUS_SUCCESS; +} + +PAGED_CODE_SEG +NTSTATUS DspC_EvtAcxFactoryCircuitCreateCircuitDevice( + _In_ WDFDEVICE Parent, + _In_ ACXFACTORYCIRCUIT Factory, + _In_ PACX_FACTORY_CIRCUIT_ADD_CIRCUIT CircuitConfig, + _Out_ WDFDEVICE * Device +) +{ + PAGED_CODE(); + + DrvLogEnter(g_SDCAVDspLog); + + NTSTATUS status = STATUS_SUCCESS; + WDF_OBJECT_ATTRIBUTES attributes; + + UNREFERENCED_PARAMETER(Factory); + + *Device = NULL; + + // Allocate a generic buffer to hold a PnP ID of this device. + // MAX_DEVICE_ID_LEN is the count of wchar in the device ID name. + C_ASSERT(NTSTRSAFE_UNICODE_STRING_MAX_CCH >= MAX_DEVICE_ID_LEN); + C_ASSERT(USHORT_MAX >= MAX_DEVICE_ID_LEN * sizeof(WCHAR)); + WCHAR *wstrBuffer = NULL; + const USHORT wstrBufferCch = MAX_DEVICE_ID_LEN; + wstrBuffer = new(POOL_FLAG_NON_PAGED, DRIVER_TAG) WCHAR[wstrBufferCch]; + RETURN_NTSTATUS_IF_TRUE(wstrBuffer == NULL, STATUS_INSUFFICIENT_RESOURCES); + auto wstrBufferFree = scope_exit([&wstrBuffer]() { + delete[] wstrBuffer; + wstrBuffer = NULL; + }); + + RtlZeroMemory(wstrBuffer, sizeof(WCHAR) * wstrBufferCch); + + // + // Create a child audio device for this circuit. + // + PWDFDEVICE_INIT devInit = NULL; + devInit = WdfPdoInitAllocate(Parent); + RETURN_NTSTATUS_IF_TRUE(NULL == devInit, STATUS_INSUFFICIENT_RESOURCES); + auto devInitFree = scope_exit([&devInit]() { + WdfDeviceInitFree(devInit); + devInit = NULL; + }); + + // + // Provide DeviceID, HardwareIDs, CompatibleIDs and InstanceId + // + + // + // Create the PnP Device ID. + // + // Retrieve the unique id of this composite. This logic uses this unique id to + // make the device id unique. Using a deterministic value for the pnp device id, guarantees + // that the KS properties associated with this audio device interface stay the same across + // reboots, even when the circuit factory is used in several ACX composites. + // + { + DECLARE_CONST_ACXOBJECTBAG_SYSTEM_PROPERTY_NAME(UniqueID); + + ACX_OBJECTBAG_CONFIG objBagCfg; + ACX_OBJECTBAG_CONFIG_INIT(&objBagCfg); + objBagCfg.Handle = CircuitConfig->CompositeProperties; + objBagCfg.Flags |= AcxObjectBagConfigOpenWithHandle; + + WDF_OBJECT_ATTRIBUTES_INIT(&attributes); + + ACXOBJECTBAG objBag = NULL; + RETURN_NTSTATUS_IF_FAILED(AcxObjectBagOpen(&attributes, &objBagCfg, &objBag)); + auto objBagFree = scope_exit([&objBag]() { + WdfObjectDelete(objBag); + objBag = NULL; + }); + + GUID uniqueId = { 0 }; + RETURN_NTSTATUS_IF_FAILED(AcxObjectBagRetrieveGuid(objBag, &UniqueID, &uniqueId)); + + UNICODE_STRING uniqueIdStr = { 0 }; + RETURN_NTSTATUS_IF_FAILED(RtlStringFromGUID(uniqueId, &uniqueIdStr)); + + // Init the deviceId unicode string. + UNICODE_STRING pnpDeviceId = {0}; + pnpDeviceId.Buffer = wstrBuffer; + pnpDeviceId.Length = 0; + pnpDeviceId.MaximumLength = (USHORT)(sizeof(WCHAR) * wstrBufferCch); + + status = RtlUnicodeStringPrintf(&pnpDeviceId, CAPTURE_DEVICE_ID_STR, &uniqueIdStr); + + RtlFreeUnicodeString(&uniqueIdStr); + + RETURN_NTSTATUS_IF_FAILED(status); + + // This is the device ID and the first H/W ID. + // This ID is used to create a unique audio device interface. + // Note that this ID is NOT the match with this driver's INF. + RETURN_NTSTATUS_IF_FAILED(WdfPdoInitAssignDeviceID(devInit, &pnpDeviceId)); + + RETURN_NTSTATUS_IF_FAILED(WdfPdoInitAddHardwareID(devInit, &pnpDeviceId)); + } + + // This H/W ID is the match with this driver's INF. + RETURN_NTSTATUS_IF_FAILED(WdfPdoInitAddHardwareID(devInit, &CaptureHardwareId)); + + /* + RETURN_NTSTATUS_IF_FAILED(WdfPdoInitAddCompatibleID(devInit, &CaptureCompatibleId)); + + RETURN_NTSTATUS_IF_FAILED(WdfPdoInitAssignInstanceID(devInit, &CaptureInstanceId)); + + RETURN_NTSTATUS_IF_FAILED(WdfPdoInitAssignContainerID(devInit, &CaptureContainerId)); + + // + // You can call WdfPdoInitAddDeviceText multiple times, adding device + // text for multiple locales. When the system displays the text, it + // chooses the text that matches the current locale, if available. + // Otherwise it will use the string for the default locale. + // The driver can specify the driver's default locale by calling + // WdfPdoInitSetDefaultLocale. + // + RETURN_NTSTATUS_IF_FAILED(WdfPdoInitAddDeviceText(devInit, + &CaptureDeviceLocation, + &CaptureDeviceLocation, + 0x409)); + */ + + WdfPdoInitSetDefaultLocale(devInit, 0x409); + + // + // Allow ACX to add any pre-requirement it needs on this device. + // + ACX_DEVICEINIT_CONFIG devInitCfg; + ACX_DEVICEINIT_CONFIG_INIT(&devInitCfg); + devInitCfg.Flags |= AcxDeviceInitConfigRawDevice; + RETURN_NTSTATUS_IF_FAILED(AcxDeviceInitInitialize(devInit, &devInitCfg)); + + // + // Initialize the pnpPowerCallbacks structure. Callback events for PNP + // and Power are specified here. If you don't supply any callbacks, + // the Framework will take appropriate default actions based on whether + // DeviceInit is initialized to be an FDO, a PDO or a filter device + // object. + // + WDF_PNPPOWER_EVENT_CALLBACKS pnpPowerCallbacks; + WDF_PNPPOWER_EVENT_CALLBACKS_INIT(&pnpPowerCallbacks); + pnpPowerCallbacks.EvtDevicePrepareHardware = DspC_EvtDevicePrepareHardware; + pnpPowerCallbacks.EvtDeviceReleaseHardware = DspC_EvtDeviceReleaseHardware; + pnpPowerCallbacks.EvtDeviceSelfManagedIoInit = DspC_EvtDeviceSelfManagedIoInit; + WdfDeviceInitSetPnpPowerEventCallbacks(devInit, &pnpPowerCallbacks); + + // + // Specify a context for this capture device. + // + WDF_OBJECT_ATTRIBUTES_INIT_CONTEXT_TYPE(&attributes, DSP_CAPTURE_DEVICE_CONTEXT); + attributes.EvtCleanupCallback = DspC_EvtDeviceContextCleanup; + attributes.ExecutionLevel = WdfExecutionLevelPassive; + + WDFDEVICE device; + RETURN_NTSTATUS_IF_FAILED(WdfDeviceCreate(&devInit, &attributes, &device)); + devInitFree.release(); + + // + // Init capture's device context. + // + PDSP_CAPTURE_DEVICE_CONTEXT devCtx; + devCtx = GetCaptureDeviceContext(device); + ASSERT(devCtx != NULL); + + // + // Set device capabilities. + // + { + WDF_DEVICE_PNP_CAPABILITIES pnpCaps; + WDF_DEVICE_PNP_CAPABILITIES_INIT(&pnpCaps); + + pnpCaps.SurpriseRemovalOK = WdfTrue; + pnpCaps.UniqueID = WdfFalse; + + WdfDeviceSetPnpCapabilities(device, &pnpCaps); + } + + // + // Allow ACX to add any post-requirement it needs on this device. + // + ACX_DEVICE_CONFIG devCfg; + ACX_DEVICE_CONFIG_INIT(&devCfg); + RETURN_NTSTATUS_IF_FAILED(AcxDeviceInitialize(device, &devCfg)); + + *Device = device; + + return status; +} + +PAGED_CODE_SEG +NTSTATUS DspC_CreateKeywordSpotterElement( + _In_ WDFDEVICE Device, + _In_ ACXCIRCUIT Circuit, + _Out_ ACXKEYWORDSPOTTER * Element +) +{ + WDF_OBJECT_ATTRIBUTES attributes; + ACX_KEYWORDSPOTTER_CALLBACKS keywordSpotterCallbacks; + ACX_KEYWORDSPOTTER_CONFIG keywordSpotterCfg; + PDSP_KEYWORDSPOTTER_CONTEXT keywordSpotterCtx; + ACX_PNPEVENT_CONFIG keywordEventCfg; + ACXPNPEVENT keywordEvent; + + PAGED_CODE(); + + ACX_KEYWORDSPOTTER_CALLBACKS_INIT(&keywordSpotterCallbacks); + keywordSpotterCallbacks.EvtAcxKeywordSpotterRetrieveArm = DspC_EvtAcxKeywordSpotterRetrieveArm; + keywordSpotterCallbacks.EvtAcxKeywordSpotterAssignArm = DspC_EvtAcxKeywordSpotterAssignArm; + keywordSpotterCallbacks.EvtAcxKeywordSpotterAssignPatterns = DspC_EvtAcxKeywordSpotterAssignPatterns; + keywordSpotterCallbacks.EvtAcxKeywordSpotterAssignReset = DspC_EvtAcxKeywordSpotterAssignReset; + + ACX_KEYWORDSPOTTER_CONFIG_INIT(&keywordSpotterCfg); + keywordSpotterCfg.Pattern = &CONTOSO_KEYWORDCONFIGURATION_IDENTIFIER2; + keywordSpotterCfg.Callbacks = &keywordSpotterCallbacks; + + WDF_OBJECT_ATTRIBUTES_INIT_CONTEXT_TYPE(&attributes, DSP_KEYWORDSPOTTER_CONTEXT); + attributes.ParentObject = Circuit; + + RETURN_NTSTATUS_IF_FAILED(AcxKeywordSpotterCreate(Circuit, &attributes, &keywordSpotterCfg, Element)); + + keywordSpotterCtx = GetDspKeywordSpotterContext(*Element); + ASSERT(keywordSpotterCtx); + + keywordSpotterCtx->KeywordDetector = (PVOID) new(POOL_FLAG_NON_PAGED, DRIVER_TAG) CKeywordDetector(Device, Circuit, &(Pcm44100c1.WaveFormatExt)); + RETURN_NTSTATUS_IF_TRUE(keywordSpotterCtx->KeywordDetector == NULL, STATUS_INSUFFICIENT_RESOURCES); + + ACX_PNPEVENT_CONFIG_INIT(&keywordEventCfg); + + WDF_OBJECT_ATTRIBUTES_INIT_CONTEXT_TYPE(&attributes, DSP_PNPEVENT_CONTEXT); + attributes.ParentObject = *Element; + RETURN_NTSTATUS_IF_FAILED(AcxPnpEventCreate(Device, *Element, &attributes, &keywordEventCfg, &keywordEvent)); + + keywordSpotterCtx->Event = keywordEvent; + + return STATUS_SUCCESS; +} + +PAGED_CODE_SEG +NTSTATUS DspC_EvtAcxFactoryCircuitCreateCircuit( + _In_ WDFDEVICE Parent, + _In_ WDFDEVICE Device, + _In_ ACXFACTORYCIRCUIT Factory, + _In_ PACX_FACTORY_CIRCUIT_ADD_CIRCUIT CircuitConfig, + _In_ PACXCIRCUIT_INIT CircuitInit, + _In_ ULONG DataPortNumber, + _In_ PSDCA_PATH_DESCRIPTORS2 PathDescriptors +) +{ + PAGED_CODE(); + + UNREFERENCED_PARAMETER(Factory); + + DrvLogEnter(g_SDCAVDspLog); + + NTSTATUS status = STATUS_SUCCESS; + WDF_OBJECT_ATTRIBUTES attributes; + + DECLARE_CONST_UNICODE_STRING(circuitName, L"Microphone0"); + + // + // Init output value. + // + ASSERT(Device); + + DECLARE_CONST_ACXOBJECTBAG_SOUNDWIRE_PROPERTY_NAME(EndpointId); + ULONG endpointId = 0; + + WDF_OBJECT_ATTRIBUTES_INIT(&attributes); + + RETURN_NTSTATUS_IF_FAILED(RetrieveProperties(CircuitConfig, &endpointId)); + + /////////////////////////////////////////////////////////// + // + // Create a circuit. + // + + ACXCIRCUIT circuit; + RETURN_NTSTATUS_IF_FAILED(CreateCaptureCircuit(CircuitInit, circuitName, Device, &circuit)); + + AcpiReader * acpiReader = GetAcpiReaderDeviceContext(Parent); + RETURN_NTSTATUS_IF_FAILED(DetermineSpecialStreamDetailsFromVendorProperties(circuit, acpiReader, CircuitConfig->CircuitProperties)); + + ASSERT(circuit != NULL); + DSP_CIRCUIT_CONTEXT *circuitCtx; + circuitCtx = GetDspCircuitContext(circuit); + ASSERT(circuitCtx); + + circuitCtx->EndpointId = endpointId; + circuitCtx->DataPortNumber = DataPortNumber; + + // + // Post circuit creation initialization. + // + + /////////////////////////////////////////////////////////// + // + // Add two custom circuit elements. Note that driver doesn't need to + // perform this step if it doesn't want to expose any circuit elements. + // + + // + // Create 1st custom circuit-element. + // + ACX_ELEMENT_CONFIG elementCfg; + ACX_ELEMENT_CONFIG_INIT(&elementCfg); + WDF_OBJECT_ATTRIBUTES_INIT_CONTEXT_TYPE(&attributes, DSP_ELEMENT_CONTEXT); + attributes.ParentObject = circuit; + + const int numElements = 3; + ACXELEMENT elements[numElements] = {0}; + + RETURN_NTSTATUS_IF_FAILED(AcxElementCreate(circuit, &attributes, &elementCfg, &elements[0])); + + ASSERT(elements[0] != NULL); + DSP_ELEMENT_CONTEXT *elementCtx; + elementCtx = GetDspElementContext(elements[0]); + ASSERT(elementCtx); + UNREFERENCED_PARAMETER(elementCtx); + + // + // Create 2nd custom circuit-element. + // + ACX_ELEMENT_CONFIG_INIT(&elementCfg); + WDF_OBJECT_ATTRIBUTES_INIT_CONTEXT_TYPE(&attributes, DSP_ELEMENT_CONTEXT); + attributes.ParentObject = circuit; + + RETURN_NTSTATUS_IF_FAILED(AcxElementCreate(circuit, &attributes, &elementCfg, &elements[1])); + + ASSERT(elements[1] != NULL); + elementCtx = GetDspElementContext(elements[1]); + ASSERT(elementCtx); + UNREFERENCED_PARAMETER(elementCtx); + + // + // Create 3rd circuit-element, keyword spotter + // + RETURN_NTSTATUS_IF_FAILED(DspC_CreateKeywordSpotterElement(Device, circuit, (ACXKEYWORDSPOTTER *) &elements[2])); + circuitCtx->KeywordSpotter = (ACXKEYWORDSPOTTER)elements[2]; + + // + // Add the circuit elements + // + RETURN_NTSTATUS_IF_FAILED(AcxCircuitAddElements(circuit, elements, SIZEOF_ARRAY(elements))); + + /////////////////////////////////////////////////////////// + // + // Allocate the formats this circuit supports. + // + // PCM:44100 channel:2 24in32 + ACXDATAFORMAT formatPcm44100c2nomask; + RETURN_NTSTATUS_IF_FAILED(AllocateFormat(Pcm44100c2_24in32_nomask, circuit, Device, &formatPcm44100c2nomask)); + + // PCM:48000 channel:2 24in32 (Needed for SDCA class driver bring up) + // The No-Mask version matches what real drivers use for multi-channel capture + ACXDATAFORMAT formatPcm48000c2nomask; + RETURN_NTSTATUS_IF_FAILED(AllocateFormat(Pcm48000c2_24in32_nomask, circuit, Device, &formatPcm48000c2nomask)); + + // PCM:16000 channel:4 - this is used solely for the KeywordSpotterPin + // The No-Mask version matches what real drivers use for multi-channel capture + ACXDATAFORMAT formatPcm16000c4nomask; + RETURN_NTSTATUS_IF_FAILED(AllocateFormat(Pcm16000c4nomask, circuit, Device, &formatPcm16000c4nomask)); + + /////////////////////////////////////////////////////////// + // + // Create capture pin. AcxCircuit creates the other pin by default. + // + ACXPIN pin; + ACX_PIN_CALLBACKS pinCallbacks; + + ACX_PIN_CALLBACKS_INIT(&pinCallbacks); + pinCallbacks.EvtAcxPinSetDataFormat = DspC_EvtAcxPinSetDataFormat; + + RETURN_NTSTATUS_IF_FAILED(CreatePin(AcxPinTypeSource, + circuit, + AcxPinCommunicationSink, + &KSCATEGORY_AUDIO, + &pinCallbacks, + DSPC_MAX_OUTPUT_SYSTEM_STREAMS, + false, + &pin)); + ASSERT(pin != NULL); + + DSP_PIN_CONTEXT* pinCtx; + pinCtx = GetDspPinContext(pin); + ASSERT(pinCtx); + pinCtx->CapturePinType = DspCapturePinTypeHost; + + // + // Don't add any supported formats here, those will be added when this circuit + // connects to the downstream circuit + // + + // + // Add capture pin, using default pin id (0) + // + RETURN_NTSTATUS_IF_FAILED(AcxCircuitAddPins(circuit, &pin, 1)); + + /////////////////////////////////////////////////////////// + // + // Create keyword streaming pin. + // + ACX_PIN_CALLBACKS_INIT(&pinCallbacks); + pinCallbacks.EvtAcxPinSetDataFormat = DspC_EvtAcxPinSetDataFormat; + + + pin = NULL; + RETURN_NTSTATUS_IF_FAILED(CreatePin(AcxPinTypeSource, + circuit, + AcxPinCommunicationSink, + &KSNODETYPE_AUDIO_KEYWORDDETECTOR, + &pinCallbacks, + DSPC_MAX_OUTPUT_KEYWORDDETECTOR_STREAMS, + true, + &pin)); + + ASSERT(pin != NULL); + pinCtx = GetDspPinContext(pin); + ASSERT(pinCtx); + pinCtx->CapturePinType = DspCapturePinTypeKeyword; + + // + // Add our supported formats to the raw mode for the circuit + // + ACXDATAFORMATLIST formatList = AcxPinGetRawDataFormatList(pin); + RETURN_NTSTATUS_IF_TRUE(NULL == formatList, STATUS_INSUFFICIENT_RESOURCES); + + RETURN_NTSTATUS_IF_FAILED(AcxDataFormatListAssignDefaultDataFormat(formatList, formatPcm16000c4nomask)); + + RETURN_NTSTATUS_IF_FAILED(AcxCircuitAddPins(circuit, &pin, 1)); + + /////////////////////////////////////////////////////////// + // + // Create bridge pin. + // + ACX_PIN_CALLBACKS_INIT(&pinCallbacks); + pinCallbacks.EvtAcxPinConnected = DspC_EvtPinConnected; + pinCallbacks.EvtAcxPinDisconnected = DspC_EvtPinDisconnected; + + pin = NULL; + RETURN_NTSTATUS_IF_FAILED(CreatePin(AcxPinTypeSink, + circuit, + AcxPinCommunicationNone, + &KSCATEGORY_AUDIO, + &pinCallbacks, + 0, // max streams. + false, + &pin)); + + ASSERT(pin != NULL); + pinCtx = GetDspPinContext(pin); + ASSERT(pinCtx); + pinCtx->CapturePinType = DspCapturePinTypeBridge; + + // + // Add a stream BRIDGE. + // + + ACX_STREAM_BRIDGE_CONFIG streamCfg; + ACX_STREAM_BRIDGE_CONFIG_INIT(&streamCfg); + RETURN_NTSTATUS_IF_FAILED(CreateStreamBridge(streamCfg, circuit, pin, pinCtx, DataPortNumber, endpointId, PathDescriptors, false)); + + RETURN_NTSTATUS_IF_FAILED(AcxCircuitAddPins(circuit, &pin, 1)); + + RETURN_NTSTATUS_IF_FAILED(ConnectCaptureCircuitElements(3, elements, circuit)); + + // + // Store the circuit handle in the capture device context. + // + PDSP_CAPTURE_DEVICE_CONTEXT captureDevCtx = NULL; + captureDevCtx = GetCaptureDeviceContext(Device); + ASSERT(captureDevCtx); + captureDevCtx->Circuit = circuit; + captureDevCtx->FirstTimePrepareHardware = TRUE; + + return status; +} + +// +// This callback is called when the Circuit bridge pin is connected to +// bridge pin of another circuit. +// +// This will happen when the composite circuit is fully initialized. +// From this point onwards the TargetCircuit can be used to send +// KSPROPERTY requests +// +// params: +// TargetCircuit - ACX wrapper for WDFIOTARGET for the connected circuit +// TargetPinId - The pin on the connected circuit. This can be used to +// send pin specific KSPROPERTY requests. +// +PAGED_CODE_SEG +VOID +DspC_EvtPinConnected ( + _In_ ACXPIN Pin, + _In_ ACXTARGETCIRCUIT TargetCircuit, + _In_ ULONG TargetPinId + ) +{ + PAGED_CODE(); + + DSP_PIN_CONTEXT *pinCtx; + pinCtx = GetDspPinContext(Pin); + pinCtx->TargetCircuit = TargetCircuit; + pinCtx->TargetPinId = TargetPinId; + + // For this sample driver, we're only adding formats to the host pin that the downstream + // pin supports. For a real DSP driver, the AUDIO_SIGNALPROCESSINGMODE_RAW data format list + // would probably include all the downstream formats, but the _DEFAULT and possibly _SPEECH + // or _COMMUNICATIONS modes would contain different formats. + // As an example, a Microphone Array's _RAW mode formats should match the channel count of the + // number of microphones in the array, whereas the _DEFAULT mode formats would be the processed + // stream in Stereo. + NTSTATUS status; + ACXPIN hostPin = AcxCircuitGetPinById(AcxPinGetCircuit(Pin), DspCapturePinTypeHost); + status = ReplicateFormatsForPin(hostPin, TargetCircuit, TargetPinId); + if (!NT_SUCCESS(status)) + { + DrvLogError(g_SDCAVDspLog, FLAG_STREAM, L"Failed to replicate downstream formats to host pin, %!STATUS!", + status); + } + + // The ACX Framework will maintain the TargetCircuit until after it's called EvtPinDisconnect. +} + +// +// This callback is called when the Circuit bridge pin is disconnected +// from the bridge pin of another circuit. +// +// This will happen when the composite circuit is deinitialized. +// From this point onwards the TargetCircuit cannnot be used to send +// KSPROPERTY requests. +// TargetCircuit should only be used to access the attached context. +// +// params: +// TargetCircuit - ACX wrapper for WDFIOTARGET for the connected circuit +// TargetPinId - The pin on the connected circuit. +// +PAGED_CODE_SEG +VOID +DspC_EvtPinDisconnected ( + _In_ ACXPIN Pin, + _In_ ACXTARGETCIRCUIT TargetCircuit, + _In_ ULONG TargetPinId + ) +{ + PAGED_CODE(); + + UNREFERENCED_PARAMETER(TargetPinId); + UNREFERENCED_PARAMETER(TargetCircuit); + + DSP_PIN_CONTEXT *pinCtx; + pinCtx = GetDspPinContext(Pin); + + if (pinCtx->TargetCircuit) + { + // After calling EvtPinDisconnected, the ACX framework will clean up + // the TargetCircuit. + pinCtx->TargetCircuit = NULL; + pinCtx->TargetPinId = (ULONG)(-1); + } +} + +PAGED_CODE_SEG +NTSTATUS DspC_EvtDevicePrepareHardware( + _In_ WDFDEVICE Device, + _In_ WDFCMRESLIST ResourceList, + _In_ WDFCMRESLIST ResourceListTranslated +) +/*++ + +Routine Description: + + In this callback, the driver does whatever is necessary to make the + hardware ready to use. + +Arguments: + + Device - handle to a device + +Return Value: + + NT status value + +--*/ +{ + NTSTATUS status = STATUS_SUCCESS; + + DrvLogEnter(g_SDCAVDspLog); + + UNREFERENCED_PARAMETER(ResourceList); + UNREFERENCED_PARAMETER(ResourceListTranslated); + + PAGED_CODE(); + + PDSP_CAPTURE_DEVICE_CONTEXT devCtx; + devCtx = GetCaptureDeviceContext(Device); + ASSERT(devCtx != NULL); + + if (!devCtx->FirstTimePrepareHardware) + { + // + // This is a rebalance. Validate the circuit resources and + // if needed, delete and re-create the circuit. + // The sample driver doens't use resources, thus the existing + // circuits are kept. + // + status = STATUS_SUCCESS; + return status; + } + + // + // Set child's power policy. + // + RETURN_NTSTATUS_IF_FAILED(DspC_SetPowerPolicy(Device)); + + // + // Add circuit to child's list. + // + RETURN_NTSTATUS_IF_FAILED(AcxDeviceAddCircuit(Device, devCtx->Circuit)); + + // + // Keep track this is not the first time this callback was called. + // + devCtx->FirstTimePrepareHardware = FALSE; + + DrvLogExit(g_SDCAVDspLog); + + return status; +} + +PAGED_CODE_SEG +NTSTATUS DspC_EvtDeviceReleaseHardware( + _In_ WDFDEVICE Device, + _In_ WDFCMRESLIST ResourceListTranslated +) +/*++ + +Routine Description: + + In this callback, the driver releases the h/w resources allocated in the + prepare h/w callback. + +Arguments: + + Device - handle to a device + +Return Value: + + NT status value + +--*/ +{ + NTSTATUS status = STATUS_SUCCESS; + DrvLogEnter(g_SDCAVDspLog); + + UNREFERENCED_PARAMETER(Device); + UNREFERENCED_PARAMETER(ResourceListTranslated); + + PAGED_CODE(); + + PDSP_CAPTURE_DEVICE_CONTEXT devCtx; + devCtx = GetCaptureDeviceContext(Device); + ASSERT(devCtx != NULL); + + DrvLogExit(g_SDCAVDspLog); + + return status; +} + +PAGED_CODE_SEG +NTSTATUS DspC_EvtDeviceSelfManagedIoInit( + _In_ WDFDEVICE Device +) +/*++ + +Routine Description: + + In this callback, the driver does one-time init of self-managed I/O data. + +Arguments: + + Device - handle to a device + +Return Value: + + NT status value + +--*/ +{ + PAGED_CODE(); + + PDSP_CAPTURE_DEVICE_CONTEXT devCtx; + devCtx = GetCaptureDeviceContext(Device); + ASSERT(devCtx != NULL); + + return STATUS_SUCCESS; +} + +#pragma code_seg() +VOID DspC_EvtDeviceContextCleanup( + _In_ WDFOBJECT WdfDevice +) +/*++ + +Routine Description: + + In this callback, it cleans up device context. + +Arguments: + + WdfDevice - WDF device object + +Return Value: + + NULL + +--*/ +{ + WDFDEVICE device; + PDSP_CAPTURE_DEVICE_CONTEXT devCtx; + + device = (WDFDEVICE)WdfDevice; + devCtx = GetCaptureDeviceContext(device); + ASSERT(devCtx != NULL); + + // only clean up the circuit if it was + // successfully created, else it'll crash + if (devCtx->Circuit != NULL) + { + DspC_CircuitCleanup(devCtx->Circuit); + devCtx->Circuit = NULL; + } +} + +#pragma code_seg() +VOID +DspC_EvtCircuitContextCleanup( + _In_ WDFOBJECT Circuit + ) +/*++ + +Routine Description: + + In this callback, it cleans up circuit context. + +Arguments: + + WdfDevice - WDF device object + +Return Value: + + NULL + +--*/ +{ + PDSP_CIRCUIT_CONTEXT circuitCtx; + + circuitCtx = GetDspCircuitContext(Circuit); + ASSERT(circuitCtx != NULL); + + // clean up the path context information in case it wasn't cleaned up + // by pin disconnection. + circuitCtx->SpecialStreamAvailablePaths = 0; + + for(ULONG i = (UINT) SpecialStreamTypeUltrasoundRender; i < (UINT) SpecialStreamType_Count; i++) + { + if (circuitCtx->SpecialStreamPathDescriptors[i]) + { + ExFreePool(circuitCtx->SpecialStreamPathDescriptors[i]); + circuitCtx->SpecialStreamPathDescriptors[i] = nullptr; + } + } + + for (ULONG i = (UINT)SpecialStreamTypeUltrasoundRender; i < (UINT)SpecialStreamType_Count; i++) + { + if (circuitCtx->SpecialStreamPathDescriptors2[i]) + { + ExFreePool(circuitCtx->SpecialStreamPathDescriptors2[i]); + circuitCtx->SpecialStreamPathDescriptors2[i] = nullptr; + } + } + + if (circuitCtx->SpecialStreamTargetCircuit) + { + WdfObjectDereferenceWithTag(circuitCtx->SpecialStreamTargetCircuit, (PVOID)DRIVER_TAG); + circuitCtx->SpecialStreamTargetCircuit = nullptr; + } + + DrvLogInfo(g_SDCAVDspLog, FLAG_STREAM, L"SDCA VDSP Circuit Cleanup %p", Circuit); +} + +#pragma code_seg() +_Use_decl_annotations_ +NTSTATUS DspC_EvtCircuitPowerUp ( + WDFDEVICE, + ACXCIRCUIT, + WDF_POWER_DEVICE_STATE +) +{ + return STATUS_SUCCESS; +} + +PAGED_CODE_SEG +_Use_decl_annotations_ +NTSTATUS DspC_EvtCircuitPowerDown ( + WDFDEVICE, + ACXCIRCUIT, + WDF_POWER_DEVICE_STATE +) +{ + PAGED_CODE(); + + return STATUS_SUCCESS; +} + +_Use_decl_annotations_ +PAGED_CODE_SEG +NTSTATUS DspC_EvtCircuitCompositeCircuitInitialize( + WDFDEVICE, + ACXCIRCUIT, + ACXOBJECTBAG +) +{ + NTSTATUS status = STATUS_SUCCESS; + + PAGED_CODE(); + + return status; +} + +_Use_decl_annotations_ +PAGED_CODE_SEG +NTSTATUS DspC_EvtCircuitCompositeInitialize( + WDFDEVICE, + ACXCIRCUIT, + ACXOBJECTBAG +) +{ + NTSTATUS status = STATUS_SUCCESS; + + PAGED_CODE(); + + return status; +} + +PAGED_CODE_SEG +VOID DspC_EvtCircuitRequestPreprocess( + _In_ ACXOBJECT Object, + _In_ ACXCONTEXT DriverContext, + _In_ WDFREQUEST Request +) +/*++ + +Routine Description: + + This function is an example of a preprocess routine. + +--*/ +{ + PAGED_CODE(); + + UNREFERENCED_PARAMETER(DriverContext); + + ASSERT(Object != NULL); + ASSERT(DriverContext); + ASSERT(Request); + + // + // Just give the request back to ACX. + // + (VOID)AcxCircuitDispatchAcxRequest((ACXCIRCUIT)Object, Request); +} + +PAGED_CODE_SEG +NTSTATUS +DspC_EvtCircuitCreateStream( + _In_ WDFDEVICE Device, + _In_ ACXCIRCUIT Circuit, + _In_ ACXPIN Pin, + _In_ PACXSTREAM_INIT StreamInit, + _In_ ACXDATAFORMAT StreamFormat, + _In_ const GUID * SignalProcessingMode, + _In_ ACXOBJECTBAG VarArguments +) +/*++ + +Routine Description: + + This routine create a stream for the specified circuit. + +Return Value: + + NT status value + +--*/ +{ + PAGED_CODE(); + + UNREFERENCED_PARAMETER(SignalProcessingMode); + UNREFERENCED_PARAMETER(VarArguments); + + DrvLogEnter(g_SDCAVDspLog); + + NTSTATUS status = STATUS_SUCCESS; + BOOLEAN kwsStream = FALSE; + + DSP_PIN_CONTEXT * pinCtx; + pinCtx = GetDspPinContext(Pin); + ASSERT(pinCtx != NULL); + +// See description in private.h +#ifdef ACX_WORKAROUND_ACXPIN_01 + { + ASSERT(pinCtx->CurrentStreamsCount != (ULONG)-1); + RETURN_NTSTATUS_IF_TRUE_MSG( + pinCtx->CurrentStreamsCount >= pinCtx->MaxStreams, + STATUS_INSUFFICIENT_RESOURCES, + L"ACXCIRCUIT %p ACXPIN %p cannot create another ACXSTREAM, max count is %d, %!STATUS!", + Circuit, Pin, pinCtx->MaxStreams, status); + } +#endif + + // + // Request a Vendor-Specific property from the Controller + // + Dsp_SendVendorSpecificProperties( + Device, + Circuit, + FALSE); + + // + // Set circuit-callbacks. + // + RETURN_NTSTATUS_IF_FAILED(AcxStreamInitAssignAcxRequestPreprocessCallback( + StreamInit, + DspC_EvtStreamRequestPreprocess, + (ACXCONTEXT)AcxRequestTypeAny, // dbg only + AcxRequestTypeAny, + NULL, + AcxItemIdNone)); + + /* + // + // Add properties, events and methods. + // + RETURN_NTSTATUS_IF_FAILED(AcxStreamInitAssignProperties(StreamInit, + StreamProperties, + StreamPropertiesCount)); + */ + + // + // Init streaming callbacks. + // + ACX_STREAM_CALLBACKS streamCallbacks; + ACX_STREAM_CALLBACKS_INIT(&streamCallbacks); + streamCallbacks.EvtAcxStreamPrepareHardware = Dsp_EvtStreamPrepareHardware; + streamCallbacks.EvtAcxStreamReleaseHardware = Dsp_EvtStreamReleaseHardware; + streamCallbacks.EvtAcxStreamRun = Dsp_EvtStreamRun; + streamCallbacks.EvtAcxStreamPause = Dsp_EvtStreamPause; + + RETURN_NTSTATUS_IF_FAILED(AcxStreamInitAssignAcxStreamCallbacks(StreamInit, &streamCallbacks)); + + // + // Init RT streaming callbacks. + // + ACX_RT_STREAM_CALLBACKS rtCallbacks; + ACX_RT_STREAM_CALLBACKS_INIT(&rtCallbacks); + rtCallbacks.EvtAcxStreamGetHwLatency = Dsp_EvtStreamGetHwLatency; + rtCallbacks.EvtAcxStreamAllocateRtPackets = Dsp_EvtStreamAllocateRtPackets; + rtCallbacks.EvtAcxStreamFreeRtPackets = Dsp_EvtStreamFreeRtPackets; + rtCallbacks.EvtAcxStreamGetCapturePacket = DspC_EvtStreamGetCapturePacket; + rtCallbacks.EvtAcxStreamGetCurrentPacket = Dsp_EvtStreamGetCurrentPacket; + rtCallbacks.EvtAcxStreamGetPresentationPosition = Dsp_EvtStreamGetPresentationPosition; + + RETURN_NTSTATUS_IF_FAILED(AcxStreamInitAssignAcxRtStreamCallbacks(StreamInit, &rtCallbacks)); + + // + // Buffer notifications are supported. + // + AcxStreamInitSetAcxRtStreamSupportsNotifications(StreamInit); + + // + // Create the stream. + // + WDF_OBJECT_ATTRIBUTES attributes; + ACXSTREAM stream; + WDF_OBJECT_ATTRIBUTES_INIT_CONTEXT_TYPE(&attributes, DSP_STREAM_CONTEXT); + attributes.EvtDestroyCallback = Dsp_EvtStreamContextDestroy; + attributes.EvtCleanupCallback = Dsp_EvtStreamContextCleanup; + + RETURN_NTSTATUS_IF_FAILED(AcxRtStreamCreate(Device, Circuit, &attributes, &StreamInit, &stream)); + + DSP_STREAM_CONTEXT* streamCtx; + streamCtx = GetDspStreamContext(stream); + ASSERT(streamCtx); + + streamCtx->CapturePinType = pinCtx->CapturePinType; + + DSP_CIRCUIT_CONTEXT * circuitCtx; + circuitCtx = GetDspCircuitContext(Circuit); + ASSERT(circuitCtx != NULL); + + CCaptureStreamEngine *streamEngine = NULL; + + if (pinCtx->CapturePinType == DspCapturePinTypeKeyword) + { + PDSP_KEYWORDSPOTTER_CONTEXT keywordSpotterCtx; + keywordSpotterCtx = GetDspKeywordSpotterContext(circuitCtx->KeywordSpotter); + ASSERT(keywordSpotterCtx); + + streamEngine = new(POOL_FLAG_NON_PAGED, DRIVER_TAG) CBufferedCaptureStreamEngine(stream, StreamFormat, (CKeywordDetector *) keywordSpotterCtx->KeywordDetector); + kwsStream = TRUE; + } + else + { + streamEngine = new(POOL_FLAG_NON_PAGED, DRIVER_TAG) CCaptureStreamEngine(stream, StreamFormat); + } + + RETURN_NTSTATUS_IF_TRUE(NULL == streamEngine, STATUS_INSUFFICIENT_RESOURCES); + + streamCtx->StreamEngine = (PVOID)streamEngine; + streamEngine = NULL; + + // + // Post stream creation initialization. + // + + // + // Create 1st custom stream-elements. + // + ACX_ELEMENT_CONFIG elementCfg; + ACX_ELEMENT_CONFIG_INIT(&elementCfg); + WDF_OBJECT_ATTRIBUTES_INIT_CONTEXT_TYPE(&attributes, DSP_ELEMENT_CONTEXT); + attributes.ParentObject = stream; + + ACXELEMENT elements[2] = { 0 }; + RETURN_NTSTATUS_IF_FAILED(AcxElementCreate(stream, &attributes, &elementCfg, &elements[0])); + + ASSERT(elements[0] != NULL); + DSP_ELEMENT_CONTEXT *elementCtx; + elementCtx = GetDspElementContext(elements[0]); + ASSERT(elementCtx); + UNREFERENCED_PARAMETER(elementCtx); + + // + // Create 2nd custom stream-elements. + // + ACX_ELEMENT_CONFIG_INIT(&elementCfg); + WDF_OBJECT_ATTRIBUTES_INIT_CONTEXT_TYPE(&attributes, DSP_ELEMENT_CONTEXT); + attributes.ParentObject = stream; + + RETURN_NTSTATUS_IF_FAILED(AcxElementCreate(stream, &attributes, &elementCfg, &elements[1])); + + ASSERT(elements[1] != NULL); + elementCtx = GetDspElementContext(elements[1]); + ASSERT(elementCtx); + UNREFERENCED_PARAMETER(elementCtx); + + // + // Add stream elements + // + RETURN_NTSTATUS_IF_FAILED(AcxStreamAddElements(stream, elements, SIZEOF_ARRAY(elements))); + + ACXPIN bridgePin = AcxCircuitGetPinById(Circuit, (ULONG)DspCapturePinTypeBridge); + RETURN_NTSTATUS_IF_TRUE(bridgePin == NULL, STATUS_UNSUCCESSFUL); + PDSP_PIN_CONTEXT bridgePinCtx = GetDspPinContext(bridgePin); + if (!kwsStream) + { + // KWS Streams are handled in the DSP. Only add non-KWS streams to the StreamBridge, which + // will forward the stream creation to downlevel circuits (i.e. Xu and Codec drivers) + RETURN_NTSTATUS_IF_FAILED(AcxStreamBridgeAddStream(bridgePinCtx->HostStreamBridge, stream)); + } + +// See description in private.h +#ifdef ACX_WORKAROUND_ACXPIN_01 + { + ASSERT(pinCtx->CurrentStreamsCount != (ULONG)-1); + InterlockedIncrement(PLONG(&pinCtx->CurrentStreamsCount)); + streamCtx->StreamIsCounted = TRUE; + } +#endif + + streamCtx->Pin = Pin; + WdfObjectReferenceWithTag(Pin, (PVOID)DRIVER_TAG); + + return status; +} + +PAGED_CODE_SEG +VOID +DspC_EvtStreamRequestPreprocess( + _In_ ACXOBJECT Object, + _In_ ACXCONTEXT DriverContext, + _In_ WDFREQUEST Request +) +/*++ + +Routine Description: + + This function is an example of a preprocess routine. + +--*/ +{ + PAGED_CODE(); + + NTSTATUS status = STATUS_NOT_SUPPORTED; + ACX_REQUEST_PARAMETERS params; + ULONG_PTR outDataCb = 0; + PDSP_STREAM_CONTEXT streamCtx; + + UNREFERENCED_PARAMETER(DriverContext); + + ASSERT(Object != NULL); + ASSERT(DriverContext); + ASSERT(Request); + + ACX_REQUEST_PARAMETERS_INIT(¶ms); + AcxRequestGetParameters(Request, ¶ms); + + streamCtx = GetDspStreamContext(Object); + if (streamCtx && streamCtx->CapturePinType == DspCapturePinTypeKeyword) + { + if (IsEqualGUID(params.Parameters.Property.Set, KSPROPSETID_RtAudio) && + params.Parameters.Property.Id == KSPROPERTY_RTAUDIO_PACKETVREGISTER) + { + status = STATUS_NOT_SUPPORTED; + outDataCb = 0; + + DrvLogInfo(g_SDCAVDspLog, FLAG_STREAM, L"DSP Capture Stream for Keyword Overriding PACKETVREGISTER request, %!STATUS!", + status); + + WdfRequestCompleteWithInformation(Request, status, outDataCb); + return; + } + } + + // + // Just give the request back to ACX. + // + (VOID)AcxStreamDispatchAcxRequest((ACXSTREAM)Object, Request); +} + +PAGED_CODE_SEG +NTSTATUS +DspC_SetPowerPolicy( + _In_ WDFDEVICE Device +) +{ + NTSTATUS status = STATUS_SUCCESS; + WDF_DEVICE_POWER_POLICY_IDLE_SETTINGS idleSettings; + //WDF_DEVICE_POWER_POLICY_WAKE_SETTINGS wakeSettings; + + PAGED_CODE(); + + // + // Init the idle policy structure. + // + //WDF_DEVICE_POWER_POLICY_IDLE_SETTINGS_INIT(&idleSettings, IdleCanWakeFromS0); + WDF_DEVICE_POWER_POLICY_IDLE_SETTINGS_INIT(&idleSettings, IdleCannotWakeFromS0); + idleSettings.IdleTimeout = 10000; // 10-sec + + status = WdfDeviceAssignS0IdleSettings(Device, &idleSettings); + + return status; +} + +PAGED_CODE_SEG +NTSTATUS +DspC_CircuitCleanup( + _In_ ACXCIRCUIT Circuit + ) +{ + PDSP_CIRCUIT_CONTEXT circuitCtx; + PDSP_KEYWORDSPOTTER_CONTEXT keywordSpotterCtx; + CKeywordDetector * keywordDetector = NULL; + + PAGED_CODE(); + + // Remove the static capture circuit + circuitCtx = GetDspCircuitContext(Circuit); + ASSERT(circuitCtx != NULL); + + keywordSpotterCtx = GetDspKeywordSpotterContext(circuitCtx->KeywordSpotter); + ASSERT(keywordSpotterCtx != NULL); + + keywordDetector = (CKeywordDetector*)keywordSpotterCtx->KeywordDetector; + keywordSpotterCtx->KeywordDetector = NULL; + delete keywordDetector; + + return STATUS_SUCCESS; +} + +PAGED_CODE_SEG +NTSTATUS +DspC_EvtAcxPinSetDataFormat( + _In_ ACXPIN Pin, + _In_ ACXDATAFORMAT DataFormat +) +{ + PAGED_CODE(); + + UNREFERENCED_PARAMETER(Pin); + UNREFERENCED_PARAMETER(DataFormat); + + + return STATUS_NOT_SUPPORTED; +} + +#pragma code_seg() +VOID +DspC_EvtPinContextCleanup( + _In_ WDFOBJECT WdfPin +) +/*++ + +Routine Description: + + In this callback, it cleans up pin context. + +Arguments: + + WdfDevice - WDF device object + +Return Value: + + NULL + +--*/ +{ + DSP_PIN_CONTEXT *pinCtx; + pinCtx = GetDspPinContext(WdfPin); + + if (pinCtx->TargetCircuit) + { + WdfObjectDereferenceWithTag(pinCtx->TargetCircuit, (PVOID)DRIVER_TAG); + + pinCtx->TargetCircuit = NULL; + pinCtx->TargetPinId = (ULONG)(-1); + } +} + +PAGED_CODE_SEG +NTSTATUS +DspC_EvtStreamGetCapturePacket( + _In_ ACXSTREAM Stream, + _Out_ ULONG* LastCapturePacket, + _Out_ ULONGLONG* QPCPacketStart, + _Out_ BOOLEAN* MoreData +) +{ + PDSP_STREAM_CONTEXT ctx; + CCaptureStreamEngine* streamEngine = NULL; + + PAGED_CODE(); + + ctx = GetDspStreamContext(Stream); + + streamEngine = static_cast(ctx->StreamEngine); + + return streamEngine->GetCapturePacket(LastCapturePacket, QPCPacketStart, MoreData); +} + + diff --git a/audio/Soundwire/Samples/SdcaVad/SdcaVDsp/circuitstream.cpp b/audio/Soundwire/Samples/SdcaVad/SdcaVDsp/circuitstream.cpp new file mode 100644 index 000000000..d7d500c2e --- /dev/null +++ b/audio/Soundwire/Samples/SdcaVad/SdcaVDsp/circuitstream.cpp @@ -0,0 +1,820 @@ +/*++ + + THIS CODE AND INFORMATION IS PROVIDED "AS IS" WITHOUT WARRANTY OF ANY + KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE + IMPLIED WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A PARTICULAR + PURPOSE. + +Module Name: + + circuitstream.cpp + +Abstract: + + Circuit Stream callbacks + +Environment: + + Kernel mode + +--*/ + +#include "private.h" +#include +#include "stdunk.h" +#include +#include +#include +#include "streamengine.h" + +#pragma code_seg() +VOID +Dsp_EvtStreamContextDestroy( + _In_ WDFOBJECT Object +) +{ + PDSP_STREAM_CONTEXT ctx; + CStreamEngine * streamEngine = NULL; + + ctx = GetDspStreamContext((ACXSTREAM)Object); + + streamEngine = (CStreamEngine*)ctx->StreamEngine; + ctx->StreamEngine = NULL; + delete streamEngine; +} + +PAGED_CODE_SEG +VOID +Dsp_EvtStreamContextCleanup( + _In_ WDFOBJECT Object +) +{ + PDSP_STREAM_CONTEXT streamCtx = GetDspStreamContext((ACXSTREAM)Object); + + PAGED_CODE(); + + if (streamCtx->Pin != NULL) + { +#ifdef ACX_WORKAROUND_ACXPIN_01 + + PDSP_PIN_CONTEXT pinCtx = GetDspPinContext(streamCtx->Pin); + + if (streamCtx->StreamIsCounted) + { + ASSERT(pinCtx->CurrentStreamsCount > 0); + InterlockedDecrement(PLONG(&pinCtx->CurrentStreamsCount)); + streamCtx->StreamIsCounted = FALSE; + } +#endif // ACX_WORKAROUND_ACXPIN_01 + + WdfObjectDereferenceWithTag(streamCtx->Pin, (PVOID)DRIVER_TAG); + streamCtx->Pin = NULL; + } + + if (streamCtx->SpecialStreamTargetCircuit) + { + WdfObjectDereferenceWithTag(streamCtx->SpecialStreamTargetCircuit, (PVOID)DRIVER_TAG); + streamCtx->SpecialStreamTargetCircuit = nullptr; + } +} + +#ifdef ACX_WORKAROUND_ACXPIN_01 +PAGED_CODE_SEG +VOID +Dsp_EvtStreamGetStreamCountRequestPreprocess( + _In_ ACXOBJECT Object, + _In_ ACXCONTEXT DriverContext, + _In_ WDFREQUEST Request +) +/*++ + +Routine Description: + + This function is a preprocess routine. + +--*/ +{ + NTSTATUS status = STATUS_NOT_SUPPORTED; + ACXCIRCUIT circuit = (ACXCIRCUIT)Object; + ULONG_PTR outDataCb = 0; + ACX_REQUEST_PARAMETERS params; + + UNREFERENCED_PARAMETER(DriverContext); + + PAGED_CODE(); + + ACX_REQUEST_PARAMETERS_INIT(¶ms); + AcxRequestGetParameters(Request, ¶ms); + + // + // Make sure this is a pin property request. + // + if ((params.Type != AcxRequestTypeProperty) || + (params.Parameters.Property.ItemType != AcxItemTypePin)) + { + status = STATUS_INVALID_DEVICE_REQUEST; + goto exit; + } + + // + // Handle only the 'get' verb. + // + if (params.Parameters.Property.Verb == AcxPropertyVerbGet) + { + ACXPIN pin = NULL; + KSPIN_CINSTANCES * value = NULL; + ULONG valueCb = 0; + ULONG minSize = sizeof(KSPIN_CINSTANCES); + + value = (KSPIN_CINSTANCES*)params.Parameters.Property.Value; + valueCb = params.Parameters.Property.ValueCb; + + // + // Get the associated pin object. + // + pin = AcxCircuitGetPinById(circuit, params.Parameters.Property.ItemId); + if (pin == NULL) + { + status = STATUS_INVALID_DEVICE_REQUEST; + goto exit; + } + + if (valueCb == 0) + { + outDataCb = minSize; + status = STATUS_BUFFER_OVERFLOW; + goto exit; + } + else if (valueCb < minSize) + { + status = STATUS_BUFFER_TOO_SMALL; + goto exit; + } + else + { + PDSP_PIN_CONTEXT pinCtx = GetDspPinContext(pin); + value->PossibleCount = pinCtx->MaxStreams; + value->CurrentCount = pinCtx->CurrentStreamsCount; // Aligned dword reads are atomic. + outDataCb = minSize; + } + } + else + { + // + // Just give it back to ACX. After this call the request is gone. + // + (VOID)AcxCircuitDispatchAcxRequest((ACXCIRCUIT)Object, Request); + Request = NULL; + goto exit; + } + + status = STATUS_SUCCESS; + +exit: + if (Request != NULL) + { + WdfRequestCompleteWithInformation(Request, status, outDataCb); + } +} +#endif // ACX_WORKAROUND_ACXPIN_01 + +// See description in private.h +#ifdef ACX_WORKAROUND_ACXPIN_02 +PAGED_CODE_SEG +VOID +Dsp_EvtStreamProposeDataFormatRequestPreprocess( + _In_ ACXOBJECT Object, + _In_ ACXCONTEXT DriverContext, + _In_ WDFREQUEST Request +) +/*++ + +Routine Description: + + This function is a preprocess routine. + +--*/ +{ + PAGED_CODE(); + + UNREFERENCED_PARAMETER(DriverContext); + + { + ACXCIRCUIT circuit = (ACXCIRCUIT)Object; + ACXPIN pin = NULL; + PDSP_PIN_CONTEXT pinCtx = NULL; + + ACX_REQUEST_PARAMETERS params; + + ACX_REQUEST_PARAMETERS_INIT(¶ms); + AcxRequestGetParameters(Request, ¶ms); + + if ((params.Type != AcxRequestTypeProperty) || + (params.Parameters.Property.ItemType != AcxItemTypePin) || + (params.Parameters.Property.Verb != AcxPropertyVerbSet)) + { + goto forward_request; + } + + // + // Get the associated pin object. + // + pin = AcxCircuitGetPinById(circuit, params.Parameters.Property.ItemId); + if (pin == NULL) + { + goto forward_request; + } + + // + // Check if this is the offload pin. + // + pinCtx = GetDspPinContext(pin); + if (!pinCtx || (pinCtx->PinType != DspPinTypeOffload)) + { + goto forward_request; + } + + // + // This is an offload pin, check # of streams. + // + if (pinCtx->CurrentStreamsCount >= pinCtx->MaxStreams) + { + // Cannot create any more streams, error out. + WdfRequestComplete(Request, STATUS_INSUFFICIENT_RESOURCES); + return; + } + } + + // + // Just give it back to ACX. After this call the request is gone. + // +forward_request: + (VOID)AcxCircuitDispatchAcxRequest((ACXCIRCUIT)Object, Request); +} +#endif // ACX_WORKAROUND_ACXPIN_02 + +PAGED_CODE_SEG +NTSTATUS +Dsp_EvtStreamGetHwLatency( + _In_ ACXSTREAM Stream, + _Out_ ULONG * FifoSize, + _Out_ ULONG * Delay +) +{ + PDSP_STREAM_CONTEXT ctx; + CStreamEngine * streamEngine = NULL; + + PAGED_CODE(); + + ctx = GetDspStreamContext(Stream); + + streamEngine = (CStreamEngine*)ctx->StreamEngine; + + return streamEngine->GetHWLatency(FifoSize, Delay); +} + +PAGED_CODE_SEG +NTSTATUS +Dsp_EvtStreamAllocateRtPackets( + _In_ ACXSTREAM Stream, + _In_ ULONG PacketCount, + _In_ ULONG PacketSize, + _Out_ PACX_RTPACKET *Packets +) +{ + PDSP_STREAM_CONTEXT ctx; + CStreamEngine * streamEngine = NULL; + + PAGED_CODE(); + + ctx = GetDspStreamContext(Stream); + + streamEngine = (CStreamEngine*)ctx->StreamEngine; + + return streamEngine->AllocateRtPackets(PacketCount, PacketSize, Packets); +} + +PAGED_CODE_SEG +VOID +Dsp_EvtStreamFreeRtPackets( + _In_ ACXSTREAM Stream, + _In_ PACX_RTPACKET Packets, + _In_ ULONG PacketCount +) +{ + PDSP_STREAM_CONTEXT ctx; + CStreamEngine * streamEngine = NULL; + + PAGED_CODE(); + + ctx = GetDspStreamContext(Stream); + + streamEngine = (CStreamEngine*)ctx->StreamEngine; + + return streamEngine->FreeRtPackets(Packets, PacketCount); +} + +PAGED_CODE_SEG +NTSTATUS +Dsp_PrepareSpecialStreamForStream( + _In_ ACXSTREAM Stream, + _In_ SDCA_SPECIALSTREAM_TYPE SpecialStreamType, + _In_ ULONG FunctionBitMask + ) +{ + NTSTATUS status = STATUS_SUCCESS; + SDCA_PATH specialStreamPath = SdcaPathFromSpecialStreamType(SpecialStreamType); + PDSP_STREAM_CONTEXT ctx = GetDspStreamContext(Stream); + ACXCIRCUIT circuit = AcxPinGetCircuit(ctx->Pin); + PDSP_CIRCUIT_CONTEXT circuitCtx = GetDspCircuitContext(circuit); + PSDCA_PATH_DESCRIPTORS pathDescriptors = nullptr; + BOOLEAN activeStreamCountIncremented = FALSE; + + PAGED_CODE(); + + if (circuitCtx->SpecialStreamAvailablePaths & specialStreamPath && + !ctx->SpecialStreamInUse[SpecialStreamType]) + { + ULONG streamCount = InterlockedIncrement(PLONG(&(circuitCtx->SpecialStreamActive[SpecialStreamType]))); + activeStreamCountIncremented = TRUE; + + if (1 == streamCount) + { + // TODO: The above ensures that the global special stream usage counts are protected, however if a special stream + // were destroyed at near the same time as another one created, then there could be a timing issue between the + // timing of this call to create the path and the timing of the ReleaseHardware call destroying the path. + // i.e. ReleaseHardware performs an interlocked decrement to 0 and then a context switch. PrepareHardware runs and does an interlocked increment + // back to 1, and performs the CreatePath call as it appears to be the first and the path is not created. + // Then, ReleaseHardware resumes and calls DestroyPath. + // So, can a PrepareHardware and a ReleaseHardware for two different streams on the same pin, happen at the same time? + + // Sample driver uses audio composition data to determine if it is going to use pathdescriptor2 or pathdescriptor + // to prepare special stream. Audio composition will also provide the entire pathdescriptor2 to be used. + if (circuitCtx->SpecialStreamPathDescriptors2[SpecialStreamType]) + { + status = DSP_SendPropertyTo( + AcxCircuitGetWdfDevice(circuit), + ctx->SpecialStreamTargetCircuit, + KSPROPERTYSETID_Sdca, + KSPROPERTY_SDCA_CREATE_PATH2, + AcxPropertyVerbSet, + nullptr, 0, + circuitCtx->SpecialStreamPathDescriptors2[SpecialStreamType], + circuitCtx->SpecialStreamPathDescriptors2[SpecialStreamType]->Size, + nullptr); + if (!NT_SUCCESS(status)) + { + goto exit; + } + } + else + { + // for simplicity, we take a copy of the entire path descriptors returned from downstream, and then adjust that copy + // to have the requested data port & format, leaving the remaining, if there are any, unused. + pathDescriptors = (PSDCA_PATH_DESCRIPTORS)ExAllocatePool2(POOL_FLAG_NON_PAGED, circuitCtx->SpecialStreamPathDescriptors[SpecialStreamType]->Size, DRIVER_TAG); + if (pathDescriptors == nullptr) + { + status = STATUS_INSUFFICIENT_RESOURCES; + goto exit; + } + RtlCopyMemory(pathDescriptors, circuitCtx->SpecialStreamPathDescriptors[SpecialStreamType], circuitCtx->SpecialStreamPathDescriptors[SpecialStreamType]->Size); + + // walk the descriptors and adjust each entry to have 1 format, preferred one if available, + // and a single data port. The remaining entries beyond the first are there and accounted for + // by the size, but are unused. + PSDCA_PATH_DESCRIPTOR currentDescriptor = (PSDCA_PATH_DESCRIPTOR)(pathDescriptors + 1); + // In some cases we will only use some of the functions; targetDescriptor will receive the next descriptor if we skip any + PSDCA_PATH_DESCRIPTOR targetDescriptor = currentDescriptor; + ULONG descriptorCount = 0; + for (ULONG j = 0; j < pathDescriptors->DescriptorCount; j++) + { + PSDCA_PATH_DESCRIPTOR nextDescriptor = (PSDCA_PATH_DESCRIPTOR)(((BYTE*)currentDescriptor) + currentDescriptor->Size); + + if (((1 << currentDescriptor->FunctionInformationId) & FunctionBitMask) == 0) + { + // This audio function isn't included in what should be started + currentDescriptor = nextDescriptor; + continue; + } + + if (currentDescriptor != targetDescriptor) + { + RtlCopyMemory(targetDescriptor, currentDescriptor, currentDescriptor->Size); + } + + PSDCA_PATH_DESCRIPTOR nextTargetDescriptor = (PSDCA_PATH_DESCRIPTOR)(((BYTE*)targetDescriptor) + targetDescriptor->Size); + + for (ULONG i = 0; i < targetDescriptor->FormatCount; i++) + { + if (48000 == targetDescriptor->Formats[i].Format.nSamplesPerSec) + { + RtlCopyMemory(&(targetDescriptor->Formats[0]), &(targetDescriptor->Formats[i]), sizeof(targetDescriptor->Formats[0])); + break; + } + } + + targetDescriptor->FormatCount = min(targetDescriptor->FormatCount, 1); + targetDescriptor->DataPortCount = min(targetDescriptor->DataPortCount, 1); + + targetDescriptor = nextTargetDescriptor; + currentDescriptor = nextDescriptor; + + ++descriptorCount; + } + + if (descriptorCount == 0) + { + // No target functions were chosen. + status = STATUS_INVALID_PARAMETER; + goto exit; + } + + pathDescriptors->DescriptorCount = descriptorCount; + + // A real DSP driver would choose a suitable EndpointID. This is a placeholder. + pathDescriptors->EndpointId = 0xaa; + + status = DSP_SendPropertyTo( + AcxCircuitGetWdfDevice(circuit), + ctx->SpecialStreamTargetCircuit, + KSPROPERTYSETID_Sdca, + KSPROPERTY_SDCA_CREATE_PATH, + AcxPropertyVerbSet, + nullptr, 0, + pathDescriptors, pathDescriptors->Size, + nullptr); + if (!NT_SUCCESS(status)) + { + goto exit; + } + } + } + + // If we succeeded in creating the path, set the tracking variable for the stream type + ctx->SpecialStreamInUse[SpecialStreamType] = TRUE; + } + +exit: + if (!NT_SUCCESS(status) && activeStreamCountIncremented) + { + // if we failed to create it, this call is going to fail, undo the circuit context tracking + InterlockedDecrement(PLONG(&(circuitCtx->SpecialStreamActive[SpecialStreamType]))); + } + + if (pathDescriptors) + { + ExFreePool(pathDescriptors); + pathDescriptors = nullptr; + } + + return status; +} + +PAGED_CODE_SEG +NTSTATUS +Dsp_ReleaseSpecialStreamsForStream( + _In_ ACXSTREAM Stream +) +{ + NTSTATUS status = STATUS_SUCCESS; + PDSP_STREAM_CONTEXT ctx = GetDspStreamContext(Stream); + ACXCIRCUIT circuit = AcxPinGetCircuit(ctx->Pin); + PDSP_CIRCUIT_CONTEXT circuitCtx = GetDspCircuitContext(circuit); + + PAGED_CODE(); + + for (ULONG streamType = 0; streamType < ARRAYSIZE(ctx->SpecialStreamInUse); ++streamType) + { + if (!ctx->SpecialStreamInUse[streamType]) + { + continue; + } + + // As the special stream hardware is potentially shared across multiple streams, + // special stream state is tracked in the circuit context. + // Decrement shared circuit context tracking to indicate that this stream is no longer using this special stream path + ULONG streamCount = InterlockedDecrement(PLONG(&(circuitCtx->SpecialStreamActive[streamType]))); + + // if this was the last user of it, destroy the special stream path + if (0 == streamCount) + { + SDCA_PATH path = SdcaPathFromSpecialStreamType((SDCA_SPECIALSTREAM_TYPE)streamType); + NTSTATUS sendStatus; + + sendStatus = DSP_SendPropertyTo( + AcxCircuitGetWdfDevice(circuit), + ctx->SpecialStreamTargetCircuit, + KSPROPERTYSETID_Sdca, + KSPROPERTY_SDCA_DESTROY_PATH, + AcxPropertyVerbSet, + nullptr, 0, + &path, sizeof(path), + nullptr); + + status = !NT_SUCCESS(status) ? status : sendStatus; + } + + // if the path has been destroyed, it also cannot be running, + // so update the special stream state for both. + ctx->SpecialStreamInUse[streamType] = FALSE; + ctx->SpecialStreamRunning[streamType] = FALSE; + } + + return status; +} + +PAGED_CODE_SEG +NTSTATUS +Dsp_EvtStreamPrepareHardware( + _In_ ACXSTREAM Stream +) +{ + NTSTATUS status = STATUS_SUCCESS; + PDSP_STREAM_CONTEXT ctx; + CStreamEngine * streamEngine = NULL; + + PAGED_CODE(); + + ctx = GetDspStreamContext(Stream); + + streamEngine = (CStreamEngine*)ctx->StreamEngine; + + // prepare the stream engine hardware + status = streamEngine->PrepareHardware(); + + // For a host or offload pin, start the Sense stream + // if it isn't already running + if (NT_SUCCESS(status) && + (DspPinTypeHost == ctx->PinType || DspPinTypeOffload == ctx->PinType)) + { + status = Dsp_PrepareSpecialStreamForStream(Stream, SpecialStreamTypeIvSense); + } + + // If this is loopback, we may be able to use reference + // stream hardware, check + if (NT_SUCCESS(status) && + DspPinTypeLoopback == ctx->PinType) + { + // for sample purposes, we're using the same stream engine for loopback with + // reference stream as without. The only difference is whether the special stream + // properties are being used to create, destroy, start, and stop the reference stream + // hardware when the loopback stream is used. + + status = Dsp_PrepareSpecialStreamForStream(Stream, SpecialStreamTypeReferenceStream); + } + + if (!NT_SUCCESS(status)) + { + (void)Dsp_ReleaseSpecialStreamsForStream(Stream); + } + + return status; +} + +PAGED_CODE_SEG +NTSTATUS +Dsp_EvtStreamReleaseHardware( + _In_ ACXSTREAM Stream +) +{ + NTSTATUS status = STATUS_SUCCESS; + PDSP_STREAM_CONTEXT ctx; + CStreamEngine * streamEngine = NULL; + + PAGED_CODE(); + + ctx = GetDspStreamContext(Stream); + + streamEngine = (CStreamEngine*)ctx->StreamEngine; + + status = Dsp_ReleaseSpecialStreamsForStream(Stream); + + NTSTATUS engineStatus = streamEngine->ReleaseHardware(); + + return NT_SUCCESS(engineStatus)?status:engineStatus; +} + +PAGED_CODE_SEG +NTSTATUS +Dsp_StopSpecialStreamsForStream( + _In_ ACXSTREAM Stream +) +{ + NTSTATUS status = STATUS_SUCCESS; + PDSP_STREAM_CONTEXT ctx = GetDspStreamContext(Stream); + ACXCIRCUIT circuit = AcxPinGetCircuit(ctx->Pin); + PDSP_CIRCUIT_CONTEXT circuitCtx = GetDspCircuitContext(circuit); + + PAGED_CODE(); + + for (ULONG streamType = 0; streamType < ARRAYSIZE(ctx->SpecialStreamInUse); ++streamType) + { + if (ctx->SpecialStreamRunning[streamType]) + { + // As the special stream hardware is potentially shared across multiple streams, + // special stream state is tracked in the circuit context. + // Decrement shared circuit context tracking to indicate that this stream is no longer running + ULONG streamCount = InterlockedDecrement(PLONG(&(circuitCtx->SpecialStreamRunning[streamType]))); + + // if this was the last stream using it, stop the path + if (0 == streamCount) + { + SDCA_PATH path = SdcaPathFromSpecialStreamType((SDCA_SPECIALSTREAM_TYPE)streamType); + NTSTATUS sendStatus; + + sendStatus = DSP_SendPropertyTo( + AcxCircuitGetWdfDevice(circuit), + ctx->SpecialStreamTargetCircuit, + KSPROPERTYSETID_Sdca, + KSPROPERTY_SDCA_STOP_PATH, + AcxPropertyVerbSet, + nullptr, 0, + &path, sizeof(path), + nullptr); + + status = !NT_SUCCESS(status) ? status : sendStatus; + } + + // update special stream state + ctx->SpecialStreamRunning[streamType] = FALSE; + } + } + + return status; +} + +PAGED_CODE_SEG +NTSTATUS +Dsp_StartSpecialStreamsForStream( + _In_ ACXSTREAM Stream + ) +{ + NTSTATUS status = STATUS_SUCCESS; + PDSP_STREAM_CONTEXT ctx = GetDspStreamContext(Stream); + ACXCIRCUIT circuit = AcxPinGetCircuit(ctx->Pin); + PDSP_CIRCUIT_CONTEXT circuitCtx = GetDspCircuitContext(circuit); + + PAGED_CODE(); + + for (ULONG streamType = 0; streamType < ARRAYSIZE(ctx->SpecialStreamInUse); ++streamType) + { + if (ctx->SpecialStreamInUse[streamType] && + !ctx->SpecialStreamRunning[streamType]) + { + // As the special stream hardware is potentially shared across multiple streams, + // special stream state is tracked in the circuit context. + // Increment shared circuit context tracking to indicate that this stream is running + ULONG streamCount = InterlockedIncrement(PLONG(&(circuitCtx->SpecialStreamRunning[streamType]))); + + // if we are the first to use it, start the path + if (1 == streamCount) + { + SDCA_PATH path = SdcaPathFromSpecialStreamType((SDCA_SPECIALSTREAM_TYPE)streamType); + status = DSP_SendPropertyTo( + AcxCircuitGetWdfDevice(circuit), + ctx->SpecialStreamTargetCircuit, + KSPROPERTYSETID_Sdca, + KSPROPERTY_SDCA_START_PATH, + AcxPropertyVerbSet, + nullptr, 0, + &path, sizeof(path), + nullptr); + } + + // if we succeeded in starting the path, update set our tracking + if (NT_SUCCESS(status)) + { + ctx->SpecialStreamRunning[streamType] = TRUE; + } + else + { + // if we failed to set the state, so clear the state tracking + InterlockedDecrement(PLONG(&(circuitCtx->SpecialStreamRunning[streamType]))); + + // If we failed, exit early so we can clean up + break; + } + } + } + + if (!NT_SUCCESS(status)) + { + // If this stream has more than one special stream, it's possible we failed after starting + // one or more special streams. As such, make sure all streams are stopped. + (void)Dsp_StopSpecialStreamsForStream(Stream); + } + return status; +} + +PAGED_CODE_SEG +NTSTATUS +Dsp_EvtStreamRun( + _In_ ACXSTREAM Stream +) +{ + NTSTATUS status = STATUS_SUCCESS; + PDSP_STREAM_CONTEXT ctx; + CStreamEngine * streamEngine = NULL; + + PAGED_CODE(); + + ctx = GetDspStreamContext(Stream); + + streamEngine = (CStreamEngine*)ctx->StreamEngine; + + status = streamEngine->Run(); + + // if we're using reference stream and aren't already running, + // set our state to running. + if (NT_SUCCESS(status)) + { + status = Dsp_StartSpecialStreamsForStream(Stream); + } + + return status; +} + +PAGED_CODE_SEG +NTSTATUS +Dsp_EvtStreamPause( + _In_ ACXSTREAM Stream +) +{ + NTSTATUS status = STATUS_SUCCESS; + PDSP_STREAM_CONTEXT ctx; + CStreamEngine * streamEngine = NULL; + + PAGED_CODE(); + + ctx = GetDspStreamContext(Stream); + + streamEngine = (CStreamEngine*)ctx->StreamEngine; + + // if any special streams are running (which can only happen if they're being used) + // and we're pausing, update tracking + status = Dsp_StopSpecialStreamsForStream(Stream); + + NTSTATUS engineStatus = streamEngine->Pause(); + + return NT_SUCCESS(engineStatus)?status:engineStatus; +} + +PAGED_CODE_SEG +NTSTATUS +Dsp_EvtStreamAssignDrmContentId( + _In_ ACXSTREAM Stream, + _In_ ULONG DrmContentId, + _In_ PACXDRMRIGHTS DrmRights +) +{ + PDSP_STREAM_CONTEXT ctx; + CStreamEngine * streamEngine = NULL; + + PAGED_CODE(); + + ctx = GetDspStreamContext(Stream); + + streamEngine = (CStreamEngine*)ctx->StreamEngine; + + return streamEngine->AssignDrmContentId(DrmContentId, DrmRights); +} + +PAGED_CODE_SEG +NTSTATUS +Dsp_EvtStreamGetCurrentPacket( + _In_ ACXSTREAM Stream, + _Out_ PULONG CurrentPacket +) +{ + PDSP_STREAM_CONTEXT ctx; + CStreamEngine * streamEngine = NULL; + + PAGED_CODE(); + + ctx = GetDspStreamContext(Stream); + + streamEngine = static_cast(ctx->StreamEngine); + + return streamEngine->GetCurrentPacket(CurrentPacket); +} + +PAGED_CODE_SEG +NTSTATUS +Dsp_EvtStreamGetPresentationPosition( + _In_ ACXSTREAM Stream, + _Out_ PULONGLONG PositionInBlocks, + _Out_ PULONGLONG QPCPosition +) +{ + PDSP_STREAM_CONTEXT ctx; + CStreamEngine * streamEngine = NULL; + + PAGED_CODE(); + + ctx = GetDspStreamContext(Stream); + + streamEngine = static_cast(ctx->StreamEngine); + + return streamEngine->GetPresentationPosition(PositionInBlocks, QPCPosition); +} + diff --git a/audio/Soundwire/Samples/SdcaVad/SdcaVDsp/device.cpp b/audio/Soundwire/Samples/SdcaVad/SdcaVDsp/device.cpp new file mode 100644 index 000000000..36c0e95a1 --- /dev/null +++ b/audio/Soundwire/Samples/SdcaVad/SdcaVDsp/device.cpp @@ -0,0 +1,1631 @@ +/*++ + + THIS CODE AND INFORMATION IS PROVIDED "AS IS" WITHOUT WARRANTY OF ANY + KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE + IMPLIED WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A PARTICULAR + PURPOSE. + +Module Name: + + Device.cpp + +Abstract: + + Plug and Play module. This file contains routines to handle pnp requests. + +Environment: + + Kernel mode + +--*/ + +#include "private.h" +#include "streamengine.h" +#include "AcpiReader.h" +#include + + +#ifndef __INTELLISENSE__ +#include "device.tmh" +#endif + +using namespace ACPIREADER; + +UNICODE_STRING g_RegistryPath = {0}; // This is used to store the registry settings path for the driver + +DEFINE_GUID(DSP_CIRCUIT_RENDER_GUID, +0x9e4f4968, 0x4dd0, 0x4aaa, 0x93, 0x0e, 0xcd, 0xc4, 0xe2, 0x8f, 0xf5, 0xb1); + +DEFINE_GUID(DSP_CIRCUIT_CAPTURE_GUID, +0xe813215a, 0xfb5e, 0x4c9d, 0xb8, 0x99, 0x91, 0x18, 0x56, 0xb6, 0xde, 0x81); + +// {17F5B19F-C2C7-4B53-AFB9-49A0283D0DCE} +DEFINE_GUID(DSP_CIRCUIT_SPEAKER_GUID, + 0x17f5b19f, 0xc2c7, 0x4b53, 0xaf, 0xb9, 0x49, 0xa0, 0x28, 0x3d, 0xd, 0xce); + +// {6F9EACF7-CD2D-4030-9E49-7CC4ADEFF192} +DEFINE_GUID(DSP_CIRCUIT_MICROPHONE_GUID, + 0x6f9eacf7, 0xcd2d, 0x4030, 0x9e, 0x49, 0x7c, 0xc4, 0xad, 0xef, 0xf1, 0x92); + +// {9B5AEA69-F6E5-4BA3-9968-37FA548F5503} +DEFINE_GUID(DSP_CIRCUIT_UNIVERSALJACK_RENDER_GUID, + 0x9b5aea69, 0xf6e5, 0x4ba3, 0x99, 0x68, 0x37, 0xfa, 0x54, 0x8f, 0x55, 0x3); + +// {3D405590-9368-4706-88E1-B69AD80C8969} +DEFINE_GUID(DSP_CIRCUIT_UNIVERSALJACK_CAPTURE_GUID, + 0x3d405590, 0x9368, 0x4706, 0x88, 0xe1, 0xb6, 0x9a, 0xd8, 0xc, 0x89, 0x69); + +// {4DCB0606-6415-4A36-BDC5-9B1792117DC9} +DEFINE_GUID(DSP_FACTORY_GUID, + 0x4dcb0606, 0x6415, 0x4a36, 0xbd, 0xc5, 0x9b, 0x17, 0x92, 0x11, 0x7d, 0xc9); + +DEFINE_GUID(SYSTEM_CONTAINER_GUID, +0x00000000, 0x0000, 0x0000, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF); + +// See description in private.h +#ifdef ACX_WORKAROUND_ACXFACTORYCIRCUIT_01 +// +// Factory class method: KSMETHODSETID_AcxFactoryCircuit +// +#define STATIC_KSMETHODSETID_AcxFactoryCircuit\ + 0xc09a3089L, 0x3eee, 0x47e0, 0xb9, 0x37, 0x4a, 0x74, 0x66, 0xae, 0xed, 0x6b +DEFINE_GUIDSTRUCT("c09a3089-3eee-47e0-b937-4a7466aeed6b", KSMETHODSETID_AcxFactoryCircuit); +#define KSMETHODSETID_AcxFactoryCircuit DEFINE_GUIDNAMED(KSMETHODSETID_AcxFactoryCircuit) + +typedef enum { + KSMETHOD_ACXFACTORYCIRCUIT_ADDCIRCUIT = 1, + KSMETHOD_ACXFACTORYCIRCUIT_REMOVECIRCUIT = 2, +} KSMETHOD_ACXFACTORYCIRCUIT; +#endif // ACX_WORKAROUND_ACXFACTORYCIRCUIT_01 + +#pragma code_seg() + +__drv_requiresIRQL(PASSIVE_LEVEL) +PAGED_CODE_SEG +NTSTATUS +CopyRegistrySettingsPath( + _In_ PUNICODE_STRING RegistryPath + ) +/*++ + +Routine Description: + +Copies the following registry path to a global variable. + +\REGISTRY\MACHINE\SYSTEM\ControlSetxxx\Services\\Parameters + +Arguments: + +RegistryPath - Registry path passed to DriverEntry + +Returns: + +NTSTATUS - SUCCESS if able to configure the framework + +--*/ + +{ + PAGED_CODE(); + + // Initializing the unicode string, so that if it is not allocated it will not be deallocated too. + RtlInitUnicodeString(&g_RegistryPath, NULL); + + g_RegistryPath.MaximumLength = RegistryPath->Length + sizeof(WCHAR); + + g_RegistryPath.Buffer = (PWCH)ExAllocatePool2(POOL_FLAG_PAGED, g_RegistryPath.MaximumLength, DRIVER_TAG); + + if (g_RegistryPath.Buffer == NULL) + { + return STATUS_INSUFFICIENT_RESOURCES; + } + + // ExAllocatePool2 zeros memory. + + RtlAppendUnicodeToString(&g_RegistryPath, RegistryPath->Buffer); + + return STATUS_SUCCESS; +} + +PAGED_CODE_SEG +NTSTATUS +Dsp_AddAudioSensorsDevice( + _In_ WDFCHILDLIST DeviceList, + _In_ PWDF_CHILD_IDENTIFICATION_DESCRIPTION_HEADER IdentificationDescription, + _In_ PWDFDEVICE_INIT ChildInit + ) +{ + NTSTATUS status = STATUS_SUCCESS; + WDF_OBJECT_ATTRIBUTES attributes; + PAUDIO_SENSORS_DEVICE_CONTEXT audioSensorsDevCtx; + PDSP_DEVICE_CONTEXT dspDevCtx; + WDFDEVICE sensorsDevice = nullptr; + + WDFDEVICE Device = WdfChildListGetDevice(DeviceList); + + DECLARE_CONST_UNICODE_STRING(buffer, L"SOUNDWIRE\\AUDIOSENSORS"); + DECLARE_UNICODE_STRING_SIZE(buffer2, 128); + DECLARE_CONST_UNICODE_STRING(AudioSensorsDeviceText, L"Audio Sensors Device"); + + PAGED_CODE(); + + UNREFERENCED_PARAMETER(IdentificationDescription); + + // + // Provide DeviceID, HardwareIDs, CompatibleIDs and InstanceId + // + RETURN_NTSTATUS_IF_FAILED(WdfPdoInitAddHardwareID(ChildInit, &buffer)); + + RETURN_NTSTATUS_IF_FAILED(WdfPdoInitAssignDeviceID(ChildInit, &buffer)); + + RETURN_NTSTATUS_IF_FAILED(RtlUnicodeStringPrintf(&buffer2, L"%08x", 12345)); + + RETURN_NTSTATUS_IF_FAILED(WdfPdoInitAssignInstanceID(ChildInit, &buffer2)); + + RETURN_NTSTATUS_IF_FAILED(WdfPdoInitAddDeviceText(ChildInit, &AudioSensorsDeviceText, &AudioSensorsDeviceText, 0x409)); + + WdfPdoInitSetDefaultLocale(ChildInit, 0x409); + + WDF_OBJECT_ATTRIBUTES_INIT_CONTEXT_TYPE(&attributes, AUDIO_SENSORS_DEVICE_CONTEXT); + RETURN_NTSTATUS_IF_FAILED(WdfDeviceCreate(&ChildInit, &attributes, &sensorsDevice)); + + dspDevCtx = GetDspDeviceContext(Device); + ASSERT(dspDevCtx!=NULL); + + dspDevCtx->AudioSensorsDevice = sensorsDevice; + + audioSensorsDevCtx = GetAudioSensorsDeviceContext(sensorsDevice); + ASSERT(audioSensorsDevCtx != NULL); + + // + // Set device capabilities. + // + { + WDF_DEVICE_PNP_CAPABILITIES pnpCaps; + WDF_DEVICE_PNP_CAPABILITIES_INIT(&pnpCaps); + + pnpCaps.SurpriseRemovalOK = WdfTrue; + pnpCaps.UniqueID = WdfFalse; + + WdfDeviceSetPnpCapabilities(sensorsDevice, &pnpCaps); + } + + DrvLogInfo(g_SDCAVDspLog, FLAG_INIT, "Successfully Created Audio Sensors Device."); + + return status; +} + +_Use_decl_annotations_ +PAGED_CODE_SEG +NTSTATUS +Dsp_CreateChildList( + _In_ WDFDEVICE Device +) +{ + NTSTATUS status = STATUS_SUCCESS; + WDF_CHILD_LIST_CONFIG config; + WDF_CHILD_IDENTIFICATION_DESCRIPTION_HEADER description; + PDSP_DEVICE_CONTEXT dspDevCtx; + + PAGED_CODE(); + + dspDevCtx = GetDspDeviceContext(Device); + ASSERT(dspDevCtx != NULL); + + // + // Init a new child list so that we can enumerate Audio Sensors PDO + // + WDF_CHILD_LIST_CONFIG_INIT( + &config, + sizeof(WDF_CHILD_IDENTIFICATION_DESCRIPTION_HEADER), + Dsp_AddAudioSensorsDevice // callback to create a child device. + ); + + RETURN_NTSTATUS_IF_FAILED(WdfChildListCreate( + Device, + &config, + WDF_NO_OBJECT_ATTRIBUTES, + &dspDevCtx->ChildList)); + + WDF_CHILD_IDENTIFICATION_DESCRIPTION_HEADER_INIT(&description, sizeof(description)); + RETURN_NTSTATUS_IF_FAILED(WdfChildListAddOrUpdateChildDescriptionAsPresent( + dspDevCtx->ChildList, + &description, + NULL)); + + DrvLogInfo(g_SDCAVDspLog, FLAG_INIT, "Successfully created new child list"); + + return status; +} + +PAGED_CODE_SEG +NTSTATUS +Dsp_EvtBusDeviceAdd( + _In_ WDFDRIVER Driver, + _Inout_ PWDFDEVICE_INIT DeviceInit + ) +/*++ +Routine Description: + + EvtDeviceAdd is called by the framework in response to AddDevice + call from the PnP manager. We create and initialize a device object to + represent a new instance of the device. All the software resources + should be allocated in this callback. + +Arguments: + + Driver - Handle to a framework driver object created in DriverEntry + + DeviceInit - Pointer to a framework-allocated WDFDEVICE_INIT structure. + +Return Value: + + NTSTATUS + +--*/ +{ + PAGED_CODE(); + + NTSTATUS status = STATUS_SUCCESS; + + UNREFERENCED_PARAMETER(Driver); + + // + // Initialize the pnpPowerCallbacks structure. Callback events for PNP + // and Power are specified here. If you don't supply any callbacks, + // the Framework will take appropriate default actions based on whether + // DeviceInit is initialized to be an FDO, a PDO or a filter device + // object. + // + WDF_PNPPOWER_EVENT_CALLBACKS pnpPowerCallbacks; + WDF_PNPPOWER_EVENT_CALLBACKS_INIT(&pnpPowerCallbacks); + pnpPowerCallbacks.EvtDevicePrepareHardware = Dsp_EvtDevicePrepareHardware; + pnpPowerCallbacks.EvtDeviceReleaseHardware = Dsp_EvtDeviceReleaseHardware; + WdfDeviceInitSetPnpPowerEventCallbacks(DeviceInit, &pnpPowerCallbacks); + + // + // Specify the type of context needed. + // Use default locking, i.e., none. + // + WDF_OBJECT_ATTRIBUTES attributes; + WDF_OBJECT_ATTRIBUTES_INIT_CONTEXT_TYPE(&attributes, DSP_DEVICE_CONTEXT); + attributes.EvtCleanupCallback = Dsp_EvtDeviceContextCleanup; + + // + // Allow ACX to add any pre-requirement it needs on this device. + // + ACX_DEVICEINIT_CONFIG devInitCfg; + ACX_DEVICEINIT_CONFIG_INIT(&devInitCfg); + + RETURN_NTSTATUS_IF_FAILED(AcxDeviceInitInitialize(DeviceInit, &devInitCfg)); + + // + // Create the device. + // + WDFDEVICE device = NULL; + RETURN_NTSTATUS_IF_FAILED(WdfDeviceCreate(&DeviceInit, &attributes, &device)); + + // + // Init Dsp's device context. + // + PDSP_DEVICE_CONTEXT devCtx; + devCtx = GetDspDeviceContext(device); + ASSERT(devCtx != NULL); + devCtx->Render = NULL; + devCtx->Capture = NULL; + // + // Allow ACX to add any post-requirement it needs on this device. + // + ACX_DEVICE_CONFIG devCfg; + ACX_DEVICE_CONFIG_INIT(&devCfg); + + RETURN_NTSTATUS_IF_FAILED(AcxDeviceInitialize(device, &devCfg)); + + // + // Tell the framework to set the SurpriseRemovalOK in the DeviceCaps so + // that you don't get the popup in usermode (on Win2K) when you surprise + // remove the device. + // + WDF_DEVICE_PNP_CAPABILITIES pnpCaps; + WDF_DEVICE_PNP_CAPABILITIES_INIT(&pnpCaps); + pnpCaps.SurpriseRemovalOK = WdfTrue; + WdfDeviceSetPnpCapabilities(device, &pnpCaps); + + // + // Default child list is owned by ACX and can only contain PDOs that + // ACX is aware of so create new child list that will contain Audio Sensors PDO. + // + RETURN_NTSTATUS_IF_FAILED(Dsp_CreateChildList(device)); + + return status; +} + +PAGED_CODE_SEG +NTSTATUS +Dsp_EvtDevicePrepareHardware( + _In_ WDFDEVICE Device, + _In_ WDFCMRESLIST ResourceList, + _In_ WDFCMRESLIST ResourceListTranslated +) +/*++ + +Routine Description: + + In this callback, the driver does whatever is necessary to make the + hardware ready to use. + +Arguments: + + Device - handle to a device + +Return Value: + + NT status value + +--*/ +{ + PAGED_CODE(); + + NTSTATUS status = STATUS_SUCCESS; + + UNREFERENCED_PARAMETER(ResourceList); + UNREFERENCED_PARAMETER(ResourceListTranslated); + + PDSP_DEVICE_CONTEXT devCtx; + devCtx = GetDspDeviceContext(Device); + ASSERT(devCtx != NULL); + + + RETURN_NTSTATUS_IF_FAILED(Dsp_SetPowerPolicy(Device)); + + RETURN_NTSTATUS_IF_FAILED(CSaveData::SetDeviceObject(WdfDeviceWdmGetDeviceObject(Device))); + + RETURN_NTSTATUS_IF_FAILED(CSaveData::InitializeWorkItems(WdfDeviceWdmGetDeviceObject(Device))); + + RETURN_NTSTATUS_IF_FAILED(CWaveReader::InitializeWorkItems(WdfDeviceWdmGetDeviceObject(Device))); + + RETURN_NTSTATUS_IF_FAILED(AcpiReader::_CreateAndInitialize(Device, g_SDCAVDspLog, DRIVER_TAG)); + + // + // Add a circuit factory that will handle all different devices + // + if (!devCtx->Factory) + { + RETURN_NTSTATUS_IF_FAILED(Dsp_AddFactoryCircuit(Device)); + } + + return status; +} + +PAGED_CODE_SEG +NTSTATUS +Dsp_EvtDeviceReleaseHardware( + _In_ WDFDEVICE Device, + _In_ WDFCMRESLIST ResourceListTranslated + ) +/*++ + +Routine Description: + + In this callback, the driver releases the h/w resources allocated in the + prepare h/w callback. + +Arguments: + + Device - handle to a device + +Return Value: + + NT status value + +--*/ +{ + PAGED_CODE(); + + NTSTATUS status = STATUS_SUCCESS; + + UNREFERENCED_PARAMETER(Device); + UNREFERENCED_PARAMETER(ResourceListTranslated); + + PDSP_DEVICE_CONTEXT devCtx; + devCtx = GetDspDeviceContext(Device); + ASSERT(devCtx != NULL); + + // + // Note that we don't remove the factory circuit here (AcxDeviceRemoveFactoryCircuit). + // If the factory circuit is removed here, any circuit devices created through it could + // be destroyed without ACX knowledge resulting in a Duplicate PDO bugcheck. + // + + + CSaveData::DestroyWorkItems(); + CWaveReader::DestroyWorkItems(); + + return status; +} + +PAGED_CODE_SEG +NTSTATUS +Dsp_SetPowerPolicy( + _In_ WDFDEVICE Device + ) +{ + PAGED_CODE(); + + NTSTATUS status = STATUS_SUCCESS; + + // + // Init the idle policy structure. + // + WDF_DEVICE_POWER_POLICY_IDLE_SETTINGS idleSettings; + WDF_DEVICE_POWER_POLICY_IDLE_SETTINGS_INIT(&idleSettings, IdleCannotWakeFromS0); + idleSettings.IdleTimeout = 10000; // 10-sec + + status = WdfDeviceAssignS0IdleSettings(Device, &idleSettings); + + return status; +} + +#pragma code_seg() +VOID +Dsp_EvtDeviceContextCleanup( + _In_ WDFOBJECT WdfDevice +) +/*++ + +Routine Description: + + In this callback, it cleans up device context. + +Arguments: + + WdfDevice - WDF device object + +Return Value: + + NULL + +--*/ +{ + WDFDEVICE device; + PDSP_DEVICE_CONTEXT devCtx; + + device = (WDFDEVICE)WdfDevice; + devCtx = GetDspDeviceContext(device); + ASSERT(devCtx != NULL); + + if (devCtx->Capture) + { + DspC_CircuitCleanup(devCtx->Capture); + devCtx->Capture = NULL; + } + + if (devCtx->AudioSensorsDevice) + { + devCtx->AudioSensorsDevice = nullptr; + } +} + +PAGED_CODE_SEG +NTSTATUS +Dsp_DetermineCircuitDetailsFromVendorProperties( + _In_ AcpiReader * Acpi, + _In_ ACXOBJECTBAG CircuitProperties, + _Out_ PGUID CircuitId, + _Out_opt_ ULONG * DataPortNumber = nullptr, + _In_ ULONG MaxPathDescriptors = 0, + _Out_opt_ PSDCA_PATH_DESCRIPTORS2 PathDescriptors = nullptr +) +{ + DECLARE_CONST_ACXOBJECTBAG_SYSTEM_PROPERTY_NAME(VendorPropertiesBlock); + WDFMEMORY vendorPropertiesBlock = NULL; + char* vendorPropertiesBuffer = NULL; + ULONG vendorPropertiesSize; + NTSTATUS status = STATUS_NOT_FOUND; + + PAGED_CODE(); + + if (PathDescriptors != nullptr && MaxPathDescriptors == 0) + { + RETURN_NTSTATUS(STATUS_INVALID_PARAMETER); + } + + RETURN_NTSTATUS_IF_FAILED(AcxObjectBagRetrieveBlob(CircuitProperties, &VendorPropertiesBlock, NULL, &vendorPropertiesBlock)); + + auto cleanup1 = scope_exit([&vendorPropertiesBlock] () + { + if (vendorPropertiesBlock != NULL) + { + WdfObjectDelete(vendorPropertiesBlock); + vendorPropertiesBlock = NULL; + } + }); + + RETURN_NTSTATUS_IF_FAILED_UNLESS_ALLOWED( + Acpi->GetPropertyString("acpi-vendor-config-type", ACPI_METHOD_SECTION_DEVICE_PROPERTIES, + vendorPropertiesBlock, NULL, 0, &vendorPropertiesSize), + STATUS_BUFFER_TOO_SMALL); + + vendorPropertiesBuffer = (char*)ExAllocatePool2(POOL_FLAG_PAGED, vendorPropertiesSize, DRIVER_TAG); + + auto cleanup2 = scope_exit([&vendorPropertiesBuffer] () + { + if (vendorPropertiesBuffer != NULL) + { + ExFreePool(vendorPropertiesBuffer); + vendorPropertiesBuffer = NULL; + } + }); + + RETURN_NTSTATUS_IF_FAILED( + Acpi->GetPropertyString("acpi-vendor-config-type", ACPI_METHOD_SECTION_DEVICE_PROPERTIES, + vendorPropertiesBlock, vendorPropertiesBuffer, vendorPropertiesSize, &vendorPropertiesSize)); + + // use ACPI methods to parse for DataPortNumber + if (DataPortNumber) + { + *DataPortNumber = 0; + } + if (PathDescriptors) + { + RtlZeroMemory(PathDescriptors, sizeof(*PathDescriptors) + (MaxPathDescriptors - 1) * sizeof(PathDescriptors->Descriptor[0])); + } + + *CircuitId = NULL_GUID; + + // This code also assumes Data Port number based on type of endpoint, which is not correct + // for real hardware. Data Port number should come from ACPI. + if (sizeof("Streaming_Speaker") <= vendorPropertiesSize && sizeof("Streaming_Speaker") == RtlCompareMemory((PBYTE)vendorPropertiesBuffer, "Streaming_Speaker", sizeof("Streaming_Speaker"))) + { + *CircuitId = DSP_CIRCUIT_SPEAKER_GUID; + if (DataPortNumber) + { + // Speaker connects to DP 1 + *DataPortNumber = 1; + } + status = STATUS_SUCCESS; + } + else if (sizeof("Streaming_MicrophoneArray") <= vendorPropertiesSize && sizeof("Streaming_MicrophoneArray") == RtlCompareMemory((PBYTE)vendorPropertiesBuffer, "Streaming_MicrophoneArray", sizeof("Streaming_MicrophoneArray"))) + { + *CircuitId = DSP_CIRCUIT_MICROPHONE_GUID; + if (DataPortNumber) + { + // Raw capture path connects to DP 6 + *DataPortNumber = 6; + } + status = STATUS_SUCCESS; + } + else if (sizeof("Streaming_Headphones") <= vendorPropertiesSize && sizeof("Streaming_Headphones") == RtlCompareMemory((PBYTE)vendorPropertiesBuffer, "Streaming_Headphones", sizeof("Streaming_Headphones"))) + { + *CircuitId = DSP_CIRCUIT_UNIVERSALJACK_RENDER_GUID; + if (DataPortNumber) + { + // UAJ Output uses DP 3 + *DataPortNumber = 3; + } + status = STATUS_SUCCESS; + } + else if (sizeof("Streaming_LineOut") <= vendorPropertiesSize && sizeof("Streaming_LineOut") == RtlCompareMemory((PBYTE)vendorPropertiesBuffer, "Streaming_LineOut", sizeof("Streaming_LineOut"))) + { + *CircuitId = DSP_CIRCUIT_UNIVERSALJACK_RENDER_GUID; + if (DataPortNumber) + { + // UAJ Output uses DP 3 + *DataPortNumber = 3; + } + status = STATUS_SUCCESS; + } + else if (sizeof("Streaming_HeadsetOutput") <= vendorPropertiesSize && sizeof("Streaming_HeadsetOutput") == RtlCompareMemory((PBYTE)vendorPropertiesBuffer, "Streaming_HeadsetOutput", sizeof("Streaming_HeadsetOutput"))) + { + *CircuitId = DSP_CIRCUIT_UNIVERSALJACK_RENDER_GUID; + if (DataPortNumber) + { + // UAJ Output uses DP 3 + *DataPortNumber = 3; + } + status = STATUS_SUCCESS; + } + else if (sizeof("Streaming_Microphone") <= vendorPropertiesSize && sizeof("Streaming_Microphone") == RtlCompareMemory((PBYTE)vendorPropertiesBuffer, "Streaming_Microphone", sizeof("Streaming_Microphone"))) + { + *CircuitId = DSP_CIRCUIT_UNIVERSALJACK_CAPTURE_GUID; + if (DataPortNumber) + { + // UAJ Input uses DP 2 + *DataPortNumber = 2; + } + status = STATUS_SUCCESS; + } + else if (sizeof("Streaming_LineIn") <= vendorPropertiesSize && sizeof("Streaming_LineIn") == RtlCompareMemory((PBYTE)vendorPropertiesBuffer, "Streaming_LineIn", sizeof("Streaming_LineIn"))) + { + *CircuitId = DSP_CIRCUIT_UNIVERSALJACK_CAPTURE_GUID; + if (DataPortNumber) + { + // UAJ Input uses DP 2 + *DataPortNumber = 2; + } + status = STATUS_SUCCESS; + } + else if (sizeof("Streaming_HeadsetMic") <= vendorPropertiesSize && sizeof("Streaming_HeadsetMic") == RtlCompareMemory((PBYTE)vendorPropertiesBuffer, "Streaming_HeadsetMic", sizeof("Streaming_HeadsetMic"))) + { + *CircuitId = DSP_CIRCUIT_UNIVERSALJACK_CAPTURE_GUID; + if (DataPortNumber) + { + // UAJ Input uses DP 2 + *DataPortNumber = 2; + } + status = STATUS_SUCCESS; + } + + // + // The below code would be replaced in a real DSP driver (or modified to use vendor-specific properties) + // + ULONG vendorAggCount = 0; + NTSTATUS aggCountStatus = Acpi->GetPropertyULong("acpi-vendor-mstest-aggregateddevice-count", ACPI_METHOD_SECTION_DEVICE_PROPERTIES, vendorPropertiesBlock, &vendorAggCount); + if (NT_SUCCESS(aggCountStatus) && vendorAggCount > 0 && vendorAggCount <= MaxPathDescriptors && PathDescriptors != nullptr) + { + // + // This endpoint supports aggregation. If we find the necessary properties for SDCA_PATH_DESCRIPTORS2 for each aggregated device + // we'll use the PathDescriptors for the endpoint. The PathDescriptors allows each aggregated device to use different channel masks. + // + const ULONG MAX_PROPERTY_SIZE = ARRAYSIZE("acpi-vendor-mstest-aggregateddevice-%d-dp-channel-mask"); + ULONG peripheralSuccessCount = 0; + size_t descriptorsSize = sizeof(*PathDescriptors) + sizeof(PathDescriptors->Descriptor[0]) * (vendorAggCount - 1); + + for (ULONG i = 0; i < vendorAggCount && i < MAX_AGGREGATED_DEVICES; ++i) + { + char propertyName[MAX_PROPERTY_SIZE]; + + PathDescriptors->Descriptor[i].Size = sizeof(PathDescriptors->Descriptor[1]); + PathDescriptors->Descriptor[i].Version = SDCA_PATH_DESCRIPTOR2_VERSION_1; + PathDescriptors->Descriptor[i].FunctionInformationId = i; + PathDescriptors->Descriptor[i].DataPortMap = SdcaDataPortMapIndexA; + PathDescriptors->Descriptor[i].DataPortConfig[0].Size = sizeof(SOUNDWIRE_DATAPORT_CONFIGURATION); + // EndpointId will be supplied during CreateStreamBridge + PathDescriptors->Descriptor[i].DataPortConfig[0].EndpointId = 0; + PathDescriptors->Descriptor[i].DataPortConfig[0].Modes = SoundWireDataPortModeIsochronous; + + // Values from the vendor blob of a partner's DSP driver + status = RtlStringCbPrintfA(propertyName, sizeof(propertyName), "acpi-vendor-mstest-aggregateddevice-%d-unique-id", i); + if (!NT_SUCCESS(status)) + { + break; + } + + // We need to be able to match each Descriptor we find with a specific aggregated device. The aggregated device ordering at runtime + // can be different, so we need to save the UniqueID for the audio function now. + // At pin connection, we will discover the aggregated devices and replace the Uniquie ID with the appropriate FunctionInformationId + status = Acpi->GetPropertyULong(propertyName, ACPI_METHOD_SECTION_DEVICE_PROPERTIES, vendorPropertiesBlock, &PathDescriptors->Descriptor[i].FunctionInformationId); + if (!NT_SUCCESS(status)) + { + break; + } + + status = RtlStringCbPrintfA(propertyName, sizeof(propertyName), "acpi-vendor-mstest-aggregateddevice-%d-terminal-id", i); + if (!NT_SUCCESS(status)) + { + break; + } + + status = Acpi->GetPropertyULong(propertyName, ACPI_METHOD_SECTION_DEVICE_PROPERTIES, vendorPropertiesBlock, &PathDescriptors->Descriptor[i].TerminalEntityId); + if (!NT_SUCCESS(status)) + { + break; + } + + status = RtlStringCbPrintfA(propertyName, sizeof(propertyName), "acpi-vendor-mstest-aggregateddevice-%d-dp-number", i); + if (!NT_SUCCESS(status)) + { + break; + } + + status = Acpi->GetPropertyULong(propertyName, ACPI_METHOD_SECTION_DEVICE_PROPERTIES, vendorPropertiesBlock, &PathDescriptors->Descriptor[i].DataPortConfig[0].DataPortNumber); + if (!NT_SUCCESS(status)) + { + break; + } + + status = RtlStringCbPrintfA(propertyName, sizeof(propertyName), "acpi-vendor-mstest-aggregateddevice-%d-dp-channel-mask", i); + if (!NT_SUCCESS(status)) + { + break; + } + + status = Acpi->GetPropertyULong(propertyName, ACPI_METHOD_SECTION_DEVICE_PROPERTIES, vendorPropertiesBlock, &PathDescriptors->Descriptor[i].DataPortConfig[0].ChannelMask); + if (!NT_SUCCESS(status)) + { + break; + } + + ++peripheralSuccessCount; + } + + if (peripheralSuccessCount == vendorAggCount) + { + // Found all the data we wanted for each of the aggregated devices + PathDescriptors->Size = (ULONG)descriptorsSize; + PathDescriptors->Version = SDCA_PATH_DESCRIPTOR2_VERSION_1; + PathDescriptors->DescriptorCount = vendorAggCount; + PathDescriptors->SdcaPath = SdcaPathDefault; + } + + // Ignore failures retrieving optional properties + status = STATUS_SUCCESS; + } + + ULONG vendorDataPortNumber = ULONG_MAX; + ULONG vendorChannelMask = ULONG_MAX; + ULONG vendorTerminalId = ULONG_MAX; + + + // GetPropertyULong will leave the value as is (ULONG_MAX) if it isn't found + Acpi->GetPropertyULong("acpi-vendor-mstest-device-terminal-id", ACPI_METHOD_SECTION_DEVICE_PROPERTIES, vendorPropertiesBlock, &vendorTerminalId); + Acpi->GetPropertyULong("acpi-vendor-mstest-device-dp-number", ACPI_METHOD_SECTION_DEVICE_PROPERTIES, vendorPropertiesBlock, &vendorDataPortNumber); + Acpi->GetPropertyULong("acpi-vendor-mstest-device-dp-channel-mask", ACPI_METHOD_SECTION_DEVICE_PROPERTIES, vendorPropertiesBlock, &vendorChannelMask); + + // DataPortNumber by itself is retained to validate back compat with systems that don't support the + // new PathDescriptors2 structure + if (DataPortNumber) + { + // Example vendor property for a streaming device + if (vendorDataPortNumber != ULONG_MAX) + { + *DataPortNumber = vendorDataPortNumber; + } + } + + // Only fill out the PathDescriptors here if we didn't already fill it out with aggregated information + if (PathDescriptors && PathDescriptors->Size == 0) + { + // Example code if the vendor values have been discovered for a single non-aggregated endpoint + if ((vendorTerminalId != ULONG_MAX) && (vendorDataPortNumber != ULONG_MAX) && (vendorChannelMask != ULONG_MAX)) + { + // We have enough information to fill out the PathDescriptors structure + PathDescriptors->Size = sizeof(*PathDescriptors); + PathDescriptors->Version = SDCA_PATH_DESCRIPTOR2_VERSION_1; + PathDescriptors->SdcaPath = SdcaPathDefault; + // EndpointId will be filled in later + PathDescriptors->EndpointId = 0; + PathDescriptors->DescriptorCount = 1; + PathDescriptors->Descriptor[0].Size = sizeof(PathDescriptors->Descriptor[0]); + PathDescriptors->Descriptor[0].Version = SDCA_PATH_DESCRIPTOR2_VERSION_1; + PathDescriptors->Descriptor[0].FunctionInformationId = 0; + PathDescriptors->Descriptor[0].TerminalEntityId = vendorTerminalId; + // DataPortMap indicates which DPIndex entries are used, in this sample we'll only use + // a single data port and that will be DPIndex_A. + PathDescriptors->Descriptor[0].DataPortMap = SdcaDataPortMapIndexA; + PathDescriptors->Descriptor[0].DataPortConfig[0].Size = sizeof(PathDescriptors->Descriptor[0].DataPortConfig[0]); + PathDescriptors->Descriptor[0].DataPortConfig[0].DataPortNumber = vendorDataPortNumber; + // The Descriptor-specific EndpointId is ignored + PathDescriptors->Descriptor[0].DataPortConfig[0].EndpointId = 0; + // Mode may be specified as something other than Isochronous depending on hardware and configuration + PathDescriptors->Descriptor[0].DataPortConfig[0].Modes = SoundWireDataPortModeIsochronous; + PathDescriptors->Descriptor[0].DataPortConfig[0].ChannelMask = vendorChannelMask; + } + } + + return status; +} + +PAGED_CODE_SEG +NTSTATUS +Dsp_EvtAcxFactoryCircuitCreateCircuitDevice( + _In_ WDFDEVICE Parent, + _In_ ACXFACTORYCIRCUIT Factory, + _In_ PACX_FACTORY_CIRCUIT_ADD_CIRCUIT CircuitConfig, + _Out_ WDFDEVICE * Device +) +{ + ACXOBJECTBAG circuitProperties; + + DECLARE_CONST_ACXOBJECTBAG_DRIVER_PROPERTY_NAME(msft, CircuitId); + + PAGED_CODE(); + + NTSTATUS status = STATUS_SUCCESS; + WDF_OBJECT_ATTRIBUTES attributes; + + *Device = NULL; + + // Create object bag from the CircuitProperties + ACX_OBJECTBAG_CONFIG propConfig; + ACX_OBJECTBAG_CONFIG_INIT(&propConfig); + propConfig.Handle = CircuitConfig->CircuitProperties; + propConfig.Flags |= AcxObjectBagConfigOpenWithHandle; + + WDF_OBJECT_ATTRIBUTES_INIT(&attributes); + + RETURN_NTSTATUS_IF_FAILED(AcxObjectBagOpen(&attributes, &propConfig, &circuitProperties)); + + auto cleanupPropConfig = scope_exit([=]() { + WdfObjectDelete(circuitProperties); + } + ); + + // Retrieve the intended Circuit ID from the object bag + GUID circuitId; + AcpiReader * acpiReader = GetAcpiReaderDeviceContext(Parent); + + RETURN_NTSTATUS_IF_TRUE(acpiReader == NULL, STATUS_INVALID_PARAMETER); + + RETURN_NTSTATUS_IF_FAILED(Dsp_DetermineCircuitDetailsFromVendorProperties(acpiReader, circuitProperties, &circuitId)); + + // Call the appropriate CreateCircuitDevice based on the Circuit ID + if (IsEqualGUID(circuitId, DSP_CIRCUIT_MICROPHONE_GUID) || IsEqualGUID(circuitId, DSP_CIRCUIT_UNIVERSALJACK_CAPTURE_GUID)) + { + status = DspC_EvtAcxFactoryCircuitCreateCircuitDevice(Parent, Factory, CircuitConfig, Device); + } + else if (IsEqualGUID(circuitId, DSP_CIRCUIT_SPEAKER_GUID) || IsEqualGUID(circuitId, DSP_CIRCUIT_UNIVERSALJACK_RENDER_GUID)) + { + status = DspR_EvtAcxFactoryCircuitCreateCircuitDevice(Parent, Factory, CircuitConfig, Device); + } + else + { + status = STATUS_NOT_SUPPORTED; + DrvLogError(g_SDCAVDspLog, FLAG_INIT, L"Unexpected CircuitId %!GUID!, %!STATUS!", &circuitId, status); + } + +// See description in private.h +#ifdef ACX_WORKAROUND_ACXFACTORYCIRCUIT_01 + // + // On success, cache this device info. + // + if (NT_SUCCESS(status)) + { + status = Dsp_AddChildDeviceToCache(Factory, &CircuitConfig->CircuitUniqueId, *Device); + if (!NT_SUCCESS(status)) + { + DrvLogError(g_SDCAVDspLog, FLAG_INIT, + L"Dsp_AddChildDeviceToCache(Factory=%p, ID=%!GUID!, WDFDEVICE=%p) failed, %!STATUS!", + Factory, &CircuitConfig->CircuitUniqueId, *Device, status); + + WdfObjectDelete(*Device); + *Device = NULL; + } + } +#endif + + return status; +} + +PAGED_CODE_SEG +NTSTATUS +Dsp_EvtAcxFactoryCircuitCreateCircuit( + _In_ WDFDEVICE Parent, + _In_ WDFDEVICE Device, + _In_ ACXFACTORYCIRCUIT Factory, + _In_ PACX_FACTORY_CIRCUIT_ADD_CIRCUIT CircuitConfig, + _In_ PACXCIRCUIT_INIT CircuitInit +) +{ + ACXOBJECTBAG circuitProperties; + + DECLARE_CONST_ACXOBJECTBAG_DRIVER_PROPERTY_NAME(msft, CircuitId); + + PAGED_CODE(); + + NTSTATUS status = STATUS_SUCCESS; + + DrvLogEnter(g_SDCAVDspLog); + + // Create object bag from the CompositeProperties + ACX_OBJECTBAG_CONFIG propConfig; + ACX_OBJECTBAG_CONFIG_INIT(&propConfig); + propConfig.Handle = CircuitConfig->CircuitProperties; + propConfig.Flags |= AcxObjectBagConfigOpenWithHandle; + + WDF_OBJECT_ATTRIBUTES attributes; + WDF_OBJECT_ATTRIBUTES_INIT(&attributes); + + RETURN_NTSTATUS_IF_FAILED(AcxObjectBagOpen(&attributes, &propConfig, &circuitProperties)); + + auto cleanupPropConfig = scope_exit([=]() { + WdfObjectDelete(circuitProperties); + } + ); + + // Retrieve the intended Circuit ID from the object bag + GUID circuitId; + ULONG dataPortNumber = 0; + + PSDCA_PATH_DESCRIPTORS2 pathDescriptors = (PSDCA_PATH_DESCRIPTORS2)ExAllocatePool2( + POOL_FLAG_NON_PAGED, + sizeof(SDCA_PATH_DESCRIPTORS2) + sizeof(SDCA_PATH_DESCRIPTOR2)*(MAX_AGGREGATED_DEVICES-1), + DRIVER_TAG); + if (pathDescriptors == nullptr) + { + RETURN_NTSTATUS_IF_FAILED(STATUS_INSUFFICIENT_RESOURCES); + } + auto descriptors_free = scope_exit([&pathDescriptors]() + { + ExFreePool(pathDescriptors); + }); + + AcpiReader * acpiReader = GetAcpiReaderDeviceContext(Parent); + + RETURN_NTSTATUS_IF_TRUE(acpiReader == NULL, STATUS_INVALID_PARAMETER); + + RETURN_NTSTATUS_IF_FAILED(Dsp_DetermineCircuitDetailsFromVendorProperties( + acpiReader, + circuitProperties, + &circuitId, + &dataPortNumber, + MAX_AGGREGATED_DEVICES, + pathDescriptors)); + + AcxCircuitInitSetComponentId(CircuitInit, &circuitId); + + // Call the appropriate CreateCircuitDevice based on the Circuit ID + if (IsEqualGUID(circuitId, DSP_CIRCUIT_MICROPHONE_GUID) || IsEqualGUID(circuitId, DSP_CIRCUIT_UNIVERSALJACK_CAPTURE_GUID)) + { + return DspC_EvtAcxFactoryCircuitCreateCircuit(Parent, Device, Factory, CircuitConfig, CircuitInit, dataPortNumber, pathDescriptors); + } + else if (IsEqualGUID(circuitId, DSP_CIRCUIT_SPEAKER_GUID) || IsEqualGUID(circuitId, DSP_CIRCUIT_UNIVERSALJACK_RENDER_GUID)) + { + return DspR_EvtAcxFactoryCircuitCreateCircuit(Parent, Device, Factory, CircuitConfig, CircuitInit, dataPortNumber, pathDescriptors); + } + + status = STATUS_NOT_SUPPORTED; + DrvLogError(g_SDCAVDspLog, FLAG_INIT, L"Unexpected CircuitId %!GUID!, %!STATUS!", &circuitId, status); + return status; +} + +PAGED_CODE_SEG +NTSTATUS +Dsp_AddFactoryCircuit( + _In_ WDFDEVICE Device +) +{ + PAGED_CODE(); + + NTSTATUS status = STATUS_SUCCESS; + PDSP_DEVICE_CONTEXT devCtx = GetDspDeviceContext(Device); + PDSP_FACTORY_CONTEXT factoryCtx = NULL; + + ASSERT(devCtx != NULL); + + DECLARE_CONST_UNICODE_STRING(dspFactoryName, L"VirtualDspFactoryCircuit"); + DECLARE_CONST_UNICODE_STRING(dspFactoryUri, L"acpi:obj-path:\\_SB.PC00.HDAS"); + + // + // Get a FactoryCircuitInit structure. + // + PACXFACTORYCIRCUIT_INIT factoryInit = NULL; + factoryInit = AcxFactoryCircuitInitAllocate(Device); + + // + // Add factory identifiers. + // + RETURN_NTSTATUS_IF_FAILED(AcxFactoryCircuitInitAssignComponentUri(factoryInit, &dspFactoryUri)); + RETURN_NTSTATUS_IF_FAILED(AcxFactoryCircuitInitAssignName(factoryInit, &dspFactoryName)); + + // + // Add properties, events and methods. + // +// See description in private.h +#ifdef ACX_WORKAROUND_ACXFACTORYCIRCUIT_01 + // + // Set circuit-callbacks. + // + RETURN_NTSTATUS_IF_FAILED(AcxFactoryCircuitInitAssignAcxRequestPreprocessCallback( + factoryInit, + Dsp_EvtFactoryRemoveCircuitRequestPreprocess, + (ACXCONTEXT)Device, + AcxRequestTypeMethod, + &KSMETHODSETID_AcxFactoryCircuit, + KSMETHOD_ACXFACTORYCIRCUIT_REMOVECIRCUIT)); +#endif + + // + // Assign the circuit's operation-callbacks. + // + ACX_FACTORY_CIRCUIT_OPERATION_CALLBACKS operationCallbacks; + ACX_FACTORY_CIRCUIT_OPERATION_CALLBACKS_INIT(&operationCallbacks); + operationCallbacks.EvtAcxFactoryCircuitCreateCircuitDevice = Dsp_EvtAcxFactoryCircuitCreateCircuitDevice; + operationCallbacks.EvtAcxFactoryCircuitCreateCircuit = Dsp_EvtAcxFactoryCircuitCreateCircuit; + AcxFactoryCircuitInitSetOperationCallbacks(factoryInit, &operationCallbacks); + + // + // Create the factory circuit. + // + WDF_OBJECT_ATTRIBUTES attributes; + ACXFACTORYCIRCUIT factory; + + WDF_OBJECT_ATTRIBUTES_INIT_CONTEXT_TYPE(&attributes, DSP_FACTORY_CONTEXT); + attributes.ParentObject = Device; + attributes.EvtCleanupCallback = Dsp_EvtFactoryContextCleanup; + attributes.EvtDestroyCallback = Dsp_EvtFactoryContextDestroy; + + ASSERT(devCtx->Factory == NULL); + RETURN_NTSTATUS_IF_FAILED(AcxFactoryCircuitCreate(Device, &attributes, &factoryInit, &factory)); + ASSERT(factory != NULL); + + factoryCtx = GetDspFactoryContext(factory); + factoryCtx->Device = Device; + +// See description in private.h +#ifdef ACX_WORKAROUND_ACXFACTORYCIRCUIT_01 + RETURN_NTSTATUS_IF_FAILED(Dsp_InitializeChildDevicesCache(factory)); +#endif // ACX_WORKAROUND_ACXFACTORYCIRCUIT_01 + + // + // Add circuit factory to device. + // It will remain added until the Device is cleaned up by WDF due to removal. + // + RETURN_NTSTATUS_IF_FAILED(AcxDeviceAddFactoryCircuit(Device, factory)); + devCtx->Factory = factory; + + return status; +} + +PAGED_CODE_SEG +NTSTATUS +Dsp_SendTestPropertyTo( + _In_ WDFDEVICE Device, + _In_ ACXCIRCUIT Circuit, + _In_ GUID PropertySet, + _In_ ULONG PropertyId, + _In_ ACX_PROPERTY_VERB Verb, + _In_ PVOID Control, + _In_ ULONG ControlCb, + _Inout_ PVOID Value, + _In_ ULONG ValueCb, + _Out_ ULONG_PTR* Information +) +{ + PAGED_CODE(); + + NTSTATUS status = STATUS_SUCCESS; + + if (Information) + { + *Information = 0; + } + + PDSP_CIRCUIT_CONTEXT circuitCtx = GetDspCircuitContext(Circuit); + + ACXPIN pin; + if (circuitCtx->IsRenderCircuit) + { + pin = AcxCircuitGetPinById(Circuit, DspPinTypeBridge); + } + else + { + pin = AcxCircuitGetPinById(Circuit, DspCapturePinTypeBridge); + } + ASSERT(pin); + + DSP_PIN_CONTEXT* pinCtx = GetDspPinContext(pin); + ASSERT(pinCtx); + + RETURN_NTSTATUS_IF_TRUE(pinCtx->TargetCircuit == NULL, STATUS_INVALID_DEVICE_STATE); + + ACX_REQUEST_PARAMETERS requestParams; + ACX_REQUEST_PARAMETERS_INIT_PROPERTY( + &requestParams, + PropertySet, + PropertyId, + Verb, + AcxItemTypeCircuit, + 0, + Control, ControlCb, + Value, ValueCb + ); + + WDFREQUEST request; + WDF_OBJECT_ATTRIBUTES attributes; + WDF_OBJECT_ATTRIBUTES_INIT(&attributes); + attributes.ParentObject = Device; + RETURN_NTSTATUS_IF_FAILED(WdfRequestCreate(&attributes, AcxTargetCircuitGetWdfIoTarget(pinCtx->TargetCircuit), &request)); + auto request_free = scope_exit([&request]() + { + WdfObjectDelete(request); + }); + + RETURN_NTSTATUS_IF_FAILED(AcxTargetCircuitFormatRequestForProperty(pinCtx->TargetCircuit, request, &requestParams)); + + WDF_REQUEST_SEND_OPTIONS sendOptions; + WDF_REQUEST_SEND_OPTIONS_INIT(&sendOptions, WDF_REQUEST_SEND_OPTION_SYNCHRONOUS); + WDF_REQUEST_SEND_OPTIONS_SET_TIMEOUT(&sendOptions, WDF_REL_TIMEOUT_IN_SEC(5)); + + RETURN_NTSTATUS_IF_TRUE(!WdfRequestSend(request, AcxTargetCircuitGetWdfIoTarget(pinCtx->TargetCircuit), &sendOptions), STATUS_INVALID_DEVICE_REQUEST); + status = WdfRequestGetStatus(request); + if (Information) + { + *Information = WdfRequestGetInformation(request); + } + if (status == STATUS_BUFFER_OVERFLOW && ValueCb == 0) + { + // Don't trace this error, it's normal + return status; + } + RETURN_NTSTATUS_IF_FAILED(status); + + return status; +} + +PAGED_CODE_SEG +VOID +Dsp_SendVendorSpecificProperties( + _In_ WDFDEVICE Device, + _In_ ACXCIRCUIT Circuit, + _In_ BOOLEAN SetValue +) +{ + VIRTUAL_STACK_VENDOR_SPECIFIC_CONTROL control = { 0 }; + VIRTUAL_STACK_VENDOR_SPECIFIC_VALUE_TEST_DATA data = { 0 }; + ULONG_PTR info; + + PAGED_CODE(); + + control.VendorSpecificId = VirtualStackVendorSpecificRequestGetTestData; + control.VendorSpecificSize = sizeof(control); + control.Data.DataPort = 0; + control.Data.EndpointId = 0; + + NTSTATUS status = Dsp_SendTestPropertyTo( + Device, + Circuit, + KSPROPERTYSETID_Sdca, + KSPROPERTY_SDCA_VENDOR_SPECIFIC, + AcxPropertyVerbGet, + &control, + sizeof(control), + nullptr, + 0, + &info); + + DrvLogInfo(g_SDCAVDspLog, FLAG_STREAM, L"KSPROPERTY_SDCA_VENDOR_SPECIFIC GetTestData for size request returns %!STATUS! (%p)", status, (void*)info); + + status = Dsp_SendTestPropertyTo( + Device, + Circuit, + KSPROPERTYSETID_Sdca, + KSPROPERTY_SDCA_VENDOR_SPECIFIC, + AcxPropertyVerbGet, + &control, + sizeof(control), + &data, + sizeof(data), + &info); + + DrvLogInfo(g_SDCAVDspLog, FLAG_STREAM, L"KSPROPERTY_SDCA_VENDOR_SPECIFIC GetTestData returns %#x : %#x, %!STATUS!", data.Test1, data.Test2, status); + + RtlZeroMemory(&control, sizeof(control)); + control.VendorSpecificId = VirtualStackVendorSpecificRequestSetTestConfig; + control.VendorSpecificSize = sizeof(control); + control.Config.IsScatterGather = SetValue; + status = Dsp_SendTestPropertyTo( + Device, + Circuit, + KSPROPERTYSETID_Sdca, + KSPROPERTY_SDCA_VENDOR_SPECIFIC, + AcxPropertyVerbSet, + &control, + sizeof(control), + &data, + sizeof(data), + &info); + + DrvLogInfo(g_SDCAVDspLog, FLAG_STREAM, L"KSPROPERTY_SDCA_VENDOR_SPECIFIC SetTestParam returned %!STATUS!", status); +} + +PAGED_CODE_SEG +VOID +Dsp_EvtFactoryContextCleanup( + _In_ WDFOBJECT Factory + ) +{ + PAGED_CODE(); + +// See description in private.h +#ifdef ACX_WORKAROUND_ACXFACTORYCIRCUIT_01 + Dsp_CleanupChildDevicesCache((ACXFACTORYCIRCUIT)Factory); +#else + UNREFERENCED_PARAMETER(Factory); +#endif +} + +PAGED_CODE_SEG +VOID +Dsp_EvtFactoryContextDestroy( + _In_ WDFOBJECT Factory + ) +{ + PAGED_CODE(); + +// See description in private.h +#ifdef ACX_WORKAROUND_ACXFACTORYCIRCUIT_01 + Dsp_DeleteChildDevicesCache((ACXFACTORYCIRCUIT)Factory); +#else + UNREFERENCED_PARAMETER(Factory); +#endif +} + +// See description in private.h +#ifdef ACX_WORKAROUND_ACXFACTORYCIRCUIT_01 +PAGED_CODE_SEG +NTSTATUS +Dsp_InitializeChildDevicesCache( + _In_ ACXFACTORYCIRCUIT Factory + ) +{ + PDSP_FACTORY_CONTEXT factoryCtx = GetDspFactoryContext(Factory); + + PAGED_CODE(); + + RETURN_NTSTATUS_IF_FAILED(WdfWaitLockCreate(WDF_NO_OBJECT_ATTRIBUTES, &factoryCtx->CacheLock)); + RETURN_NTSTATUS_IF_FAILED(WdfCollectionCreate(WDF_NO_OBJECT_ATTRIBUTES, &factoryCtx->Cache)); + + return STATUS_SUCCESS; +} + +PAGED_CODE_SEG +VOID +Dsp_CleanupChildDevicesCache( + _In_ ACXFACTORYCIRCUIT Factory + ) +{ + PDSP_FACTORY_CONTEXT factoryCtx = GetDspFactoryContext(Factory); + WDFOBJECT child = NULL; + + PAGED_CODE(); + + // + // Factory is going away, cleanup child devices cache. + // + if (factoryCtx->Cache == NULL || factoryCtx->CacheLock == NULL) + { + return; // Nothing to do. + } + + WdfWaitLockAcquire(factoryCtx->CacheLock, NULL); + + while ((child = WdfCollectionGetFirstItem(factoryCtx->Cache)) != NULL) + { + PDSP_DEVICEID_CONTEXT idCtx = GetDspDeviceIdContext(child); + + // + // - zero out ID. + // - remove the item from the cache. + // + idCtx->UniqueID = NULL_GUID; + WdfCollectionRemoveItem(factoryCtx->Cache, 0); + } + + WdfWaitLockRelease(factoryCtx->CacheLock); +} + +PAGED_CODE_SEG +VOID +Dsp_DeleteChildDevicesCache( + _In_ ACXFACTORYCIRCUIT Factory + ) +{ + PDSP_FACTORY_CONTEXT factoryCtx = GetDspFactoryContext(Factory); + + PAGED_CODE(); + + if (factoryCtx->Cache != NULL) + { + WdfObjectDelete(factoryCtx->Cache); + factoryCtx->Cache = NULL; + } + + if (factoryCtx->CacheLock != NULL) + { + WdfObjectDelete(factoryCtx->CacheLock); + factoryCtx->CacheLock = NULL; + } +} + +PAGED_CODE_SEG +bool +Dsp_IsChildDeviceInCacheLocked( + _In_ ACXFACTORYCIRCUIT Factory, + _In_ const GUID * UniqueId + ) +{ + PDSP_FACTORY_CONTEXT factoryCtx = GetDspFactoryContext(Factory); + ULONG count = WdfCollectionGetCount(factoryCtx->Cache); + bool isPresent = false; + + PAGED_CODE(); + + for (ULONG i = 0; i < count; i++) + { + WDFDEVICE child = NULL; + PDSP_DEVICEID_CONTEXT idCtx = NULL; + + child = (WDFDEVICE)WdfCollectionGetItem(factoryCtx->Cache, i); + idCtx = GetDspDeviceIdContext(child); + + if ((idCtx != 0) && IsEqualGUID(idCtx->UniqueID, *UniqueId)) + { + // Found it. + isPresent = true; + break; + } + } + + return isPresent; +} + +PAGED_CODE_SEG +NTSTATUS +Dsp_AddChildDeviceToCache( + _In_ ACXFACTORYCIRCUIT Factory, + _In_ const GUID * UniqueId, + _In_ WDFDEVICE Device + ) +{ + NTSTATUS status = STATUS_UNSUCCESSFUL; + PDSP_FACTORY_CONTEXT factoryCtx = GetDspFactoryContext(Factory); + + PAGED_CODE(); + + WdfWaitLockAcquire(factoryCtx->CacheLock, NULL); + + // + // Make sure there is not another device with the same ID. + // + if (Dsp_IsChildDeviceInCacheLocked(Factory, UniqueId)) + { + status = STATUS_DEVICE_ALREADY_ATTACHED; + } + else + { + // + // Attach a device ID context if not already present. + // + PDSP_DEVICEID_CONTEXT idCtx = GetDspDeviceIdContext(Device); + if (idCtx == NULL) + { + // Add the device ID context. + WDF_OBJECT_ATTRIBUTES attributes; + WDF_OBJECT_ATTRIBUTES_INIT_CONTEXT_TYPE(&attributes, DSP_DEVICEID_CONTEXT); + attributes.EvtCleanupCallback = Dsp_EvtDeviceIdContextCleanup; + + status = WdfObjectAllocateContext(Device, &attributes, (PVOID*)&idCtx); + if (!NT_SUCCESS(status)) + { + idCtx = NULL; // just in case. + DrvLogError(g_SDCAVDspLog, FLAG_INIT, + "Failed to allocate a DSP_DEVICEID_CONTEXT on WDFDEVICE %p, %!STATUS!", + Device, status); + } + } + else + { + // This should not happen, but just in case, cleanup the context. + ASSERT(FALSE); + idCtx->UniqueID = NULL_GUID; + if (idCtx->Factory != NULL) + { + WdfObjectDereferenceWithTag(idCtx->Factory, (PVOID)DRIVER_TAG); + idCtx->Factory = NULL; + } + } + + if (idCtx != NULL) + { + // + // Store the unique ID of this device. + // + idCtx->UniqueID = *UniqueId; + + // + // Take a strong ref on the factory object. + // Ref is removed on context cleanup. + // + idCtx->Factory = Factory; + WdfObjectReferenceWithTag(Factory, (PVOID)DRIVER_TAG); + + // + // Add the device to our cache. + // + status = WdfCollectionAdd(factoryCtx->Cache, Device); + } + } + + WdfWaitLockRelease(factoryCtx->CacheLock); + + return status; +} + +PAGED_CODE_SEG +WDFDEVICE +Dsp_RemoveChildDeviceFromCache( + _In_ ACXFACTORYCIRCUIT Factory, + _In_ const GUID * UniqueId + ) +{ + PDSP_FACTORY_CONTEXT factoryCtx = GetDspFactoryContext(Factory); + WDFDEVICE child = NULL; + ULONG count; + + PAGED_CODE(); + + WdfWaitLockAcquire(factoryCtx->CacheLock, NULL); + + count = WdfCollectionGetCount(factoryCtx->Cache); + + for (ULONG i = 0; i < count; i++) + { + PDSP_DEVICEID_CONTEXT idCtx = NULL; + WDFDEVICE device = NULL; + + device = (WDFDEVICE)WdfCollectionGetItem(factoryCtx->Cache, i); + idCtx = GetDspDeviceIdContext(device); + + if ((idCtx != 0) && IsEqualGUID(idCtx->UniqueID, *UniqueId)) + { + // Found it. + // - zero out ID. + // - add a ref for the caller. + // - remove the item from the cache. + idCtx->UniqueID = NULL_GUID; + WdfObjectReferenceWithTag(device, (PVOID)DRIVER_TAG); + WdfCollectionRemoveItem(factoryCtx->Cache, i); + child = device; + break; + } + } + + WdfWaitLockRelease(factoryCtx->CacheLock); + + return child; +} + +PAGED_CODE_SEG +VOID +Dsp_PurgeChildDeviceFromCache( + _In_ ACXFACTORYCIRCUIT Factory, + _In_ WDFDEVICE Device + ) +{ + PDSP_FACTORY_CONTEXT factoryCtx = GetDspFactoryContext(Factory); + PDSP_DEVICEID_CONTEXT idCtx = GetDspDeviceIdContext(Device); + WDFDEVICE child = NULL; + + PAGED_CODE(); + + WdfWaitLockAcquire(factoryCtx->CacheLock, NULL); + + // + // Scan the cache only if the device's unique-id is not null. + // + if (idCtx != NULL && !IsEqualGUID(NULL_GUID, idCtx->UniqueID)) + { + ULONG count = WdfCollectionGetCount(factoryCtx->Cache); + + for (ULONG i = 0; i < count; i++) + { + child = (WDFDEVICE)WdfCollectionGetItem(factoryCtx->Cache, i); + if (child == Device) + { + // + // Found it. + // - zero out ID. + // - remove the item from the cache. + // + idCtx->UniqueID = NULL_GUID; + WdfCollectionRemoveItem(factoryCtx->Cache, i); + break; + } + } + } + + WdfWaitLockRelease(factoryCtx->CacheLock); +} + +PAGED_CODE_SEG +VOID +Dsp_EvtDeviceIdContextCleanup( + _In_ WDFOBJECT Device + ) +{ + PDSP_DEVICEID_CONTEXT idCtx = GetDspDeviceIdContext(Device); + + PAGED_CODE(); + + Dsp_PurgeChildDeviceFromCache(idCtx->Factory, (WDFDEVICE)Device); + WdfObjectDereferenceWithTag(idCtx->Factory, (PVOID)DRIVER_TAG); +} + +PAGED_CODE_SEG +VOID +Dsp_EvtFactoryCircuitRemoveCircuitCallback +( + _In_ WDFOBJECT Object, + _In_ WDFREQUEST Request + ) +{ + NTSTATUS status = STATUS_NOT_SUPPORTED; + ACXFACTORYCIRCUIT factory = (ACXFACTORYCIRCUIT)Object; + PDSP_FACTORY_CONTEXT factoryCtx = GetDspFactoryContext(factory); + WDFDEVICE child = NULL; + PACX_FACTORY_CIRCUIT_REMOVE_CIRCUIT args; + ULONG argsCb = sizeof(ACX_FACTORY_CIRCUIT_REMOVE_CIRCUIT); + ACX_REQUEST_PARAMETERS params; + + PAGED_CODE(); + + ACX_REQUEST_PARAMETERS_INIT(¶ms); + AcxRequestGetParameters(Request, ¶ms); + + ASSERT(params.Type == AcxRequestTypeMethod); + ASSERT(params.Parameters.Method.Verb == AcxMethodVerbSend); + ASSERT(params.Parameters.Method.ArgsCb >= argsCb); + + args = (PACX_FACTORY_CIRCUIT_REMOVE_CIRCUIT)params.Parameters.Method.Args; + argsCb = params.Parameters.Method.ArgsCb; // use real value. + + if (args->Size < argsCb) + { + status = STATUS_INVALID_PARAMETER; + DrvLogError(g_SDCAVDspLog, FLAG_GENERIC, + "ACX_FACTORY_CIRCUIT_REMOVE_CIRCUIT.Size %d is invalid, it should be >= %d, %!STATUS!", + args->Size, argsCb, status); + goto exit; + } + + // + // Remove the circut/circuit-device. + // If found, there is a pending WDF ref on the object. + // + child = Dsp_RemoveChildDeviceFromCache(factory, &args->CircuitUniqueId); + if (child == NULL) + { + // Device is gone. Nothing to do. + status = STATUS_SUCCESS; + goto exit; + } + + // + // Tell ACX not to enum this child device anymore. + // + status = AcxDeviceRemoveCircuitDevice(factoryCtx->Device, child); + if (!NT_SUCCESS(status)) + { + DrvLogError(g_SDCAVDspLog, FLAG_GENERIC, + "Parent %p, ACXFACTORYCIRCUIT %p, Child %p, AcxDeviceRemoveCircuitDevice failed, %!STATUS!", + factoryCtx->Device, factory, child, status); + goto exit; + } + + status = STATUS_SUCCESS; + +exit: + if (child != NULL) + { + WdfObjectDereferenceWithTag(child, (PVOID)DRIVER_TAG); + } + + WdfRequestComplete(Request, status); +} + +PAGED_CODE_SEG +VOID +Dsp_EvtFactoryRemoveCircuitRequestPreprocess( + _In_ ACXOBJECT Object, + _In_ ACXCONTEXT DriverContext, + _In_ WDFREQUEST Request +) +/*++ + +Routine Description: + + This function is an example of a preprocess routine. + +--*/ +{ + PAGED_CODE(); + + UNREFERENCED_PARAMETER(DriverContext); + ASSERT(Object); + ASSERT(Request); + + Dsp_EvtFactoryCircuitRemoveCircuitCallback(Object, Request); +} +#endif //ACX_WORKAROUND_ACXFACTORYCIRCUIT_01 + + diff --git a/audio/Soundwire/Samples/SdcaVad/SdcaVDsp/driver.cpp b/audio/Soundwire/Samples/SdcaVad/SdcaVDsp/driver.cpp new file mode 100644 index 000000000..98cc6d73a --- /dev/null +++ b/audio/Soundwire/Samples/SdcaVad/SdcaVDsp/driver.cpp @@ -0,0 +1,172 @@ +/*++ + +Copyright (c) Microsoft Corporation. All rights reserved. + +Module Name: + + Driver.cpp + +Abstract: + + Sample Soundwire DSP Driver. + +Environment: + + Kernel mode only + +--*/ + +#include "private.h" +#include "trace.h" + +#ifndef __INTELLISENSE__ +#include "driver.tmh" +#endif + +RECORDER_LOG g_SDCAVDspLog{ nullptr }; + +PAGED_CODE_SEG +void Dsp_DriverUnload (_In_ WDFDRIVER Driver) +{ + PAGED_CODE(); + + if (!Driver) + { + return; + } + + if (g_RegistryPath.Buffer != NULL) + { + ExFreePool(g_RegistryPath.Buffer); + RtlZeroMemory(&g_RegistryPath, sizeof(g_RegistryPath)); + } + + WPP_CLEANUP(WdfDriverWdmGetDriverObject(Driver)); + + return; +} + +INIT_CODE_SEG +NTSTATUS +DriverEntry( + _In_ PDRIVER_OBJECT DriverObject, + _In_ PUNICODE_STRING RegistryPath + ) +/*++ + +Routine Description: + DriverEntry initializes the driver and is the first routine called by the + system after the driver is loaded. + +Parameters Description: + + DriverObject - represents the instance of the function driver that is loaded + into memory. DriverEntry must initialize members of DriverObject before it + returns to the caller. DriverObject is allocated by the system before the + driver is loaded, and it is released by the system after the system unloads + the function driver from memory. + + RegistryPath - represents the driver specific path in the Registry. + The function driver can use the path to store driver related data between + reboots. The path does not store hardware instance specific data. + +Return Value: + + STATUS_SUCCESS if successful, + STATUS_UNSUCCESSFUL otherwise. + +--*/ +{ + PAGED_CODE(); + + NTSTATUS status = STATUS_SUCCESS; + + WPP_INIT_TRACING(DriverObject, RegistryPath); + + auto exit = scope_exit([&status, &DriverObject]() { + if (!NT_SUCCESS(status)) + { + if (g_RegistryPath.Buffer != NULL) + { + ExFreePool(g_RegistryPath.Buffer); + RtlZeroMemory(&g_RegistryPath, sizeof(g_RegistryPath)); + } + + WPP_CLEANUP(DriverObject); + } + else + { + DrvLogInfo(g_SDCAVDspLog, FLAG_INIT, "ACX SDCA Virtual DSP Driver Init complete, %!STATUS!", status); + } + }); + + RETURN_NTSTATUS_IF_FAILED(CopyRegistrySettingsPath(RegistryPath)); + + // + // Initiialize driver config to control the attributes that + // are global to the driver. Note that framework by default + // provides a driver unload routine. If you create any resources + // in the DriverEntry and want to be cleaned in driver unload, + // you can override that by manually setting the EvtDriverUnload in the + // config structure. In general xxx_CONFIG_INIT macros are provided to + // initialize most commonly used members. + // + + WDF_DRIVER_CONFIG wdfCfg; + WDF_DRIVER_CONFIG_INIT(&wdfCfg, Dsp_EvtBusDeviceAdd); + wdfCfg.EvtDriverUnload = Dsp_DriverUnload; + + // + // Add a driver context. (for illustration purposes only). + // + WDF_OBJECT_ATTRIBUTES attributes; + WDF_OBJECT_ATTRIBUTES_INIT_CONTEXT_TYPE(&attributes, DSP_DRIVER_CONTEXT); + + // + // Create a framework driver object to represent our driver. + // + WDFDRIVER driver; + RETURN_NTSTATUS_IF_FAILED(WdfDriverCreate( + DriverObject, + RegistryPath, + &attributes, // Driver Attributes + &wdfCfg, // Driver Config Info + &driver // hDriver + )); + + RECORDER_CONFIGURE_PARAMS recorderConfig; + RECORDER_CONFIGURE_PARAMS_INIT(&recorderConfig); + recorderConfig.CreateDefaultLog = FALSE; + WppRecorderConfigure(&recorderConfig); + + RECORDER_LOG_CREATE_PARAMS recorderLogCreateParams; + RECORDER_LOG_CREATE_PARAMS_INIT(&recorderLogCreateParams, NULL); + recorderLogCreateParams.TotalBufferSize = WPP_TOTAL_BUFFER_SIZE; + recorderLogCreateParams.ErrorPartitionSize = WPP_ERROR_PARTITION_SIZE; + + RtlStringCbPrintfA(recorderLogCreateParams.LogIdentifier, + RECORDER_LOG_IDENTIFIER_MAX_CHARS, + "SDCAVDsp"); + + RECORDER_LOG logHandle = NULL; + status = WppRecorderLogCreate(&recorderLogCreateParams, &logHandle); + if (!NT_SUCCESS(status)) + { + logHandle = NULL; + + // Non fatal failure + status = STATUS_SUCCESS; + } + + g_SDCAVDspLog = logHandle; + + // + // Post init. + // + ACX_DRIVER_CONFIG acxCfg; + ACX_DRIVER_CONFIG_INIT(&acxCfg); + + RETURN_NTSTATUS_IF_FAILED(AcxDriverInitialize(driver, &acxCfg)); + + return status; +} diff --git a/audio/Soundwire/Samples/SdcaVad/SdcaVDsp/offloadStreamEngine.cpp b/audio/Soundwire/Samples/SdcaVad/SdcaVDsp/offloadStreamEngine.cpp new file mode 100644 index 000000000..c86af13a4 --- /dev/null +++ b/audio/Soundwire/Samples/SdcaVad/SdcaVDsp/offloadStreamEngine.cpp @@ -0,0 +1,606 @@ +/*++ + + THIS CODE AND INFORMATION IS PROVIDED "AS IS" WITHOUT WARRANTY OF ANY + KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE + IMPLIED WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A PARTICULAR + PURPOSE. + +Module Name: + + offloadStreamEngine.cpp + +Abstract: + + Virtual Streaming Engine - this module controls offload streaming logic for + the device. + +Environment: + + Kernel mode + +--*/ + +#include "private.h" +#include +#include +#include +#include "offloadStreamEngine.h" + +#ifndef __INTELLISENSE__ +#include "offloadStreamEngine.tmh" +#endif + +_Use_decl_annotations_ +PAGED_CODE_SEG +COffloadStreamEngine::COffloadStreamEngine( + ACXSTREAM Stream, + ACXDATAFORMAT StreamFormat, + CSimPeakMeter *circuitPeakmeter +) :CStreamEngine(Stream, StreamFormat, circuitPeakmeter) +{ + PAGED_CODE(); + + m_BufferReadTimer = NULL; + m_LastBufferTimer = NULL; + m_PacketsWritten = 0; + m_PacketsRead = 0; + m_SinglePacketPosition = 0; +} + +_Use_decl_annotations_ +#pragma code_seg() +COffloadStreamEngine::~COffloadStreamEngine() +{ +} + +_Use_decl_annotations_ +PAGED_CODE_SEG +NTSTATUS +COffloadStreamEngine::PrepareHardware() +{ + PAGED_CODE(); + + NTSTATUS status = STATUS_SUCCESS; + RETURN_NTSTATUS_IF_FAILED(CStreamEngine::PrepareHardware()); + // CStreamEngine::PrepareHardware will update state to Pause, but we don't + // want to be in Pause state if any of the below actions fail. + m_CurrentState = AcxStreamStateStop; + + // + // Buffer read callbacks + // + WDF_TIMER_CONFIG timerConfig; + LONG period = (LONG)((ULONGLONG)m_PacketSize * HNS_PER_SEC / (ULONGLONG)GetBytesPerSecond()); + WDF_TIMER_CONFIG_INIT_PERIODIC( + &timerConfig, + COffloadStreamEngine::s_EvtBufferReadTimerCallback, + period / HNSTIME_PER_MILLISECOND + ); + timerConfig.UseHighResolutionTimer = WdfTrue; + + WDF_OBJECT_ATTRIBUTES timerAttributes; + WDF_OBJECT_ATTRIBUTES_INIT(&timerAttributes); + WDF_OBJECT_ATTRIBUTES_INIT_CONTEXT_TYPE(&timerAttributes, STREAM_TIMER_CONTEXT); + timerAttributes.ParentObject = m_Stream; + + RETURN_NTSTATUS_IF_FAILED(WdfTimerCreate( + &timerConfig, + &timerAttributes, + &m_BufferReadTimer + )); + + auto bt_free = scope_exit([this]() { + WdfObjectDelete(m_BufferReadTimer); + m_BufferReadTimer = NULL; + }); + + PSTREAM_TIMER_CONTEXT timerCtx; + timerCtx = GetStreamTimerContext(m_BufferReadTimer); + timerCtx->StreamEngine = this; + + // + // Last Buffer read callback + // + WDF_TIMER_CONFIG_INIT( + &timerConfig, + COffloadStreamEngine::s_EvtLastBufferTimerCallback + ); + timerConfig.UseHighResolutionTimer = WdfTrue; + + WDF_OBJECT_ATTRIBUTES_INIT(&timerAttributes); + WDF_OBJECT_ATTRIBUTES_INIT_CONTEXT_TYPE(&timerAttributes, STREAM_TIMER_CONTEXT); + timerAttributes.ParentObject = m_Stream; + + RETURN_NTSTATUS_IF_FAILED(WdfTimerCreate( + &timerConfig, + &timerAttributes, + &m_LastBufferTimer + )); + + auto lbt_free = scope_exit([this]() { + WdfObjectDelete(m_LastBufferTimer); + m_LastBufferTimer = NULL; + }); + + timerCtx = GetStreamTimerContext(m_LastBufferTimer); + timerCtx->StreamEngine = this; + + RETURN_NTSTATUS_IF_FAILED(m_SaveData.SetDataFormat((PKSDATAFORMAT)AcxDataFormatGetKsDataFormat(m_StreamFormat))); + + RETURN_NTSTATUS_IF_FAILED(m_SaveData.Initialize(TRUE)); + + RETURN_NTSTATUS_IF_FAILED(m_SaveData.SetMaxWriteSize(m_PacketSize * m_PacketsCount * MAX_FILE_WRITE_FRAMES)); + + m_CurrentState = AcxStreamStatePause; + + bt_free.release(); + lbt_free.release(); + + return status; +} + +_Use_decl_annotations_ +PAGED_CODE_SEG +NTSTATUS +COffloadStreamEngine::ReleaseHardware() +{ + PAGED_CODE(); + + m_SaveData.WaitAllWorkItems(); + m_SaveData.Cleanup(); + + if (m_BufferReadTimer) + { + WdfTimerStop(m_BufferReadTimer, TRUE); + WdfObjectDelete(m_BufferReadTimer); + m_BufferReadTimer = NULL; + } + + if (m_LastBufferTimer) + { + WdfTimerStop(m_LastBufferTimer, TRUE); + WdfObjectDelete(m_LastBufferTimer); + m_LastBufferTimer = NULL; + } + + m_LinearBufferClock.Stop(); + + m_PacketsWritten = 0; + m_PacketsRead = 0; + + CStreamEngine::ReleaseHardware(); + + return STATUS_SUCCESS; +} + +_Use_decl_annotations_ +PAGED_CODE_SEG +NTSTATUS +COffloadStreamEngine::Run() +{ + NTSTATUS status = STATUS_SUCCESS; + + PAGED_CODE(); + + DrvLogInfo(g_SDCAVDspLog, FLAG_STREAM, L"COffloadStreamEngine::Run"); + + if (m_CurrentState != AcxStreamStatePause) + { + status = STATUS_INVALID_STATE_TRANSITION; + return status; + } + + ULONGLONG bytesPerSec = GetBytesPerSecond(); + + ULONGLONG elapsedTimeWhenPaused = m_LinearBufferClock.GetElapsedTime(NULL); + if (elapsedTimeWhenPaused) + { + // Stream has resumed from pause + // Calculate remaining buffer for next notification + + // Remaining buffer from when stream was paused + // Hardware might have cycled more than the bytes written + // This can happen if there was a glitch and hardware was + // starved + ULONGLONG packetTime = (ULONGLONG)m_PacketSize * HNS_PER_SEC / bytesPerSec; + LONG remainingTime = (LONG)((ULONGLONG)elapsedTimeWhenPaused % packetTime); + WdfTimerStart(m_BufferReadTimer, WDF_REL_TIMEOUT_IN_MS(remainingTime / HNSTIME_PER_MILLISECOND)); + DrvLogInfo(g_SDCAVDspLog, FLAG_STREAM, L"COffloadStreamEngine::Run - Notification Timer Started - first timeout :%d ms", (ULONG)(remainingTime / HNSTIME_PER_MILLISECOND)); + } + else + { + // Run has been called first time on this stream + LONG period = (LONG)((ULONGLONG)m_PacketSize * HNS_PER_SEC / (ULONGLONG)bytesPerSec); + WdfTimerStart(m_BufferReadTimer, WDF_REL_TIMEOUT_IN_MS(period / HNSTIME_PER_MILLISECOND)); + DrvLogInfo(g_SDCAVDspLog, FLAG_STREAM, L"COffloadStreamEngine::Run - Notification Timer Started - first timeout :%d ms", (ULONG)(period / HNSTIME_PER_MILLISECOND)); + } + + m_LinearBufferClock.Run(); + m_CurrentState = AcxStreamStateRun; + + m_PeakMeter.StartStream(); + m_pCircuitPeakmeter->StartStream(); + + return status; +} + +_Use_decl_annotations_ +PAGED_CODE_SEG +NTSTATUS +COffloadStreamEngine::Pause() +{ + PAGED_CODE(); + + NTSTATUS status = STATUS_SUCCESS; + + DrvLogInfo(g_SDCAVDspLog, FLAG_STREAM, L"COffloadStreamEngine::Pause - from %d", m_CurrentState); + + RETURN_NTSTATUS_IF_TRUE(m_CurrentState != AcxStreamStateRun, STATUS_INVALID_STATE_TRANSITION); + + WdfTimerStop(m_BufferReadTimer, TRUE); + + m_LinearBufferClock.Pause(); + + m_PeakMeter.StopStream(); + m_pCircuitPeakmeter->StopStream(); + + m_CurrentState = AcxStreamStatePause; + + return status; +} + +_Use_decl_annotations_ +#pragma code_seg() +NTSTATUS +COffloadStreamEngine::GetPresentationPosition( + PULONGLONG PositionInBlocks, + PULONGLONG QPCPosition +) +{ + DrvLogVerbose(g_SDCAVDspLog, FLAG_STREAM, L"COffloadStreamEngine::GetPresentationPosition"); + + ULONG blockAlign; + LARGE_INTEGER qpc; + + blockAlign = AcxDataFormatGetBlockAlign(m_StreamFormat); + qpc = KeQueryPerformanceCounter(NULL); + + ULONGLONG streamPosition = m_LinearBufferClock.GetElapsedTime(NULL); + + // Simulate Presentation position lag by 20 ms + if (streamPosition > (OFFLOAD_PRESENTATION_POSITION_LAG_IN_MS * HNSTIME_PER_MILLISECOND)) + { + streamPosition -= (OFFLOAD_PRESENTATION_POSITION_LAG_IN_MS * HNSTIME_PER_MILLISECOND); + } + else + { + streamPosition = 0; + } + + *PositionInBlocks = (streamPosition * GetBytesPerSecond() / HNS_PER_SEC) / blockAlign; + + *QPCPosition = (ULONGLONG)qpc.QuadPart; + + return STATUS_SUCCESS; +} + +_Use_decl_annotations_ +#pragma code_seg() +NTSTATUS +COffloadStreamEngine::GetLinearBufferPosition( + PULONGLONG Position +) +{ + DrvLogVerbose(g_SDCAVDspLog, FLAG_STREAM, L"COffloadStreamEngine::GetLinearBufferPosition"); + + ULONGLONG bytesPerSecond = GetBytesPerSecond(); + ULONGLONG currentTime = m_LinearBufferClock.GetElapsedTime(NULL); + + // Update position + *Position = currentTime * bytesPerSecond / HNS_PER_SEC; + + return STATUS_SUCCESS; +} + +_Use_decl_annotations_ +PAGED_CODE_SEG +NTSTATUS +COffloadStreamEngine::SetCurrentWritePosition( + ULONG Position +) +{ + PAGED_CODE(); + + if (m_PacketsCount == 1) + { + return SetCurrentWritePositionSinglePacket(Position); + } + + // Determine buffer + // Designed for ping-pong + // Position == m_PacketSize, ping buffer was just filled + // Position == m_PacketSize * 2, pong buffer was just filled + ULONG packetIndex = Position == m_PacketSize ? 0 : 1; + + DrvLogVerbose(g_SDCAVDspLog, FLAG_STREAM, L"COffloadStreamEngine::SetCurrentWritePosition, Position = 0x%08x, PacketIndex :%d", Position, packetIndex); + + // + // Detect if the packet just written is incorrect + // + ULONG packetsRead = (ULONG)InterlockedCompareExchange((LONG *)&m_PacketsRead, -1, -1); + + if (m_CurrentState == AcxStreamStateRun) + { + ULONG expectedPacketIndex = (packetsRead % 2) ? 0 : 1; + if (packetIndex != expectedPacketIndex) + { + DrvLogError(g_SDCAVDspLog, FLAG_STREAM, L"COffloadStreamEngine incorrect packet write: %d, Packets Read: %08d", packetIndex, packetsRead); + // \TODO: ACX doesn't recover from this error + // Continuing automatically recovers with next call + //return STATUS_DATA_OVERRUN; + } + } + + // + // Catch up to packets read + 1 + // This is to recover from condition when OS was not writing enough data + // + (ULONG)InterlockedExchange((LONG *)&m_PacketsWritten, packetsRead + 1); + + PBYTE packetBuffer = NULL; + packetBuffer = (PBYTE)m_Packets[packetIndex]; + // Packet 0 starts at an offset if the size isn't a multiple of page_size + if (packetIndex == 0) + { + packetBuffer += m_FirstPacketOffset; + } + + m_SaveData.WriteData(packetBuffer, m_PacketSize); + + return STATUS_SUCCESS; +} + +_Use_decl_annotations_ +PAGED_CODE_SEG +NTSTATUS +COffloadStreamEngine::SetCurrentWritePositionSinglePacket( + ULONG Position +) +{ + PAGED_CODE(); + + // Offload streams are almost always 2-packet. However, it is possible to create an offload stream + // as a timer-driven (single packet) stream. This code will ensure correct behavior in this case. + ULONG packetIndex = 0; + + DrvLogVerbose(g_SDCAVDspLog, FLAG_STREAM, L"COffloadStreamEngine::SetCurrentWritePositionSinglePacket, Position = 0x%08x, PacketIndex :%d", Position, packetIndex); + + // + // Detect if the packet just written is incorrect + // + ULONG packetsRead = (ULONG)InterlockedCompareExchange((LONG *)&m_PacketsRead, -1, -1); + + // Position has wrapped, can increment the written count. + if (Position < m_SinglePacketPosition) + { + // + // Catch up to packets read + 1 + // This is to recover from condition when OS was not writing enough data + // + (ULONG)InterlockedExchange((LONG*)&m_PacketsWritten, packetsRead + 1); + } + + PBYTE packetBuffer = NULL; + packetBuffer = (PBYTE)m_Packets[packetIndex]; + // Packet 0 starts at an offset if the size isn't a multiple of page_size + // For single-packet the offset should be 0. + packetBuffer += m_FirstPacketOffset; + + // AudioKSE adds 1 to the position + Position -= 1; + Position %= m_PacketSize; + + if (Position <= m_SinglePacketPosition) + { + // Handle the case of wraparound by copying from the last position to the end of the buffer + m_SaveData.WriteData(packetBuffer + m_SinglePacketPosition, m_PacketSize - m_SinglePacketPosition); + m_SinglePacketPosition = 0; + } + // Write from the last position (0 in the case of wraparound) to the new Position + m_SaveData.WriteData(packetBuffer + m_SinglePacketPosition, Position); + m_SinglePacketPosition = Position; + + return STATUS_SUCCESS; +} + +_Use_decl_annotations_ +PAGED_CODE_SEG +NTSTATUS +COffloadStreamEngine::SetLastBufferPosition( + ULONG Position +) +{ + PAGED_CODE(); + + ULONG bytesPerSec = GetBytesPerSecond(); + + // Determine buffer + ULONG packetIndex = Position < m_PacketSize ? 0 : 1; + ULONG lastBufferSize = Position <= m_PacketSize ? Position : Position - m_PacketSize; + + // time for rendering last buffer + ULONGLONG lastBufferTime = (ULONGLONG)lastBufferSize * HNS_PER_SEC / (ULONGLONG)bytesPerSec; + + ULONGLONG totalStreamTime = (ULONGLONG)m_PacketsWritten* (ULONGLONG)m_PacketSize* HNS_PER_SEC / (ULONGLONG)bytesPerSec; + totalStreamTime += (ULONGLONG)lastBufferTime; + + // Simulate Presentation position lag by 20 ms + ULONGLONG presentationTime = m_LinearBufferClock.GetElapsedTime(NULL) - (OFFLOAD_PRESENTATION_POSITION_LAG_IN_MS * HNSTIME_PER_MILLISECOND); + + lastBufferTime = totalStreamTime - presentationTime; + + // + // Start last buffer timer + // + RETURN_NTSTATUS_IF_TRUE_MSG(NULL == m_LastBufferTimer, STATUS_INVALID_PARAMETER, L"Set Last Buffer Position called out of sequence - without calling prepare hardware"); + WdfTimerStart(m_LastBufferTimer, WDF_REL_TIMEOUT_IN_MS(lastBufferTime / HNSTIME_PER_MILLISECOND)); + + PBYTE packetBuffer = NULL; + packetBuffer = (PBYTE)m_Packets[packetIndex]; + // Packet 0 starts at an offset if the size isn't a multiple of page_size + if (packetIndex == 0) + { + packetBuffer += m_FirstPacketOffset; + } + + m_SaveData.WriteData(packetBuffer, lastBufferSize); + + return STATUS_SUCCESS; +} + +_Use_decl_annotations_ +PAGED_CODE_SEG +NTSTATUS +COffloadStreamEngine::AssignDrmContentId( + ULONG, + PACXDRMRIGHTS DrmRights +) +{ + PAGED_CODE(); + + // + // At this point the driver should enforce the new DrmRights. + // The sample driver handles DrmRights per stream basis, and + // stops writing the stream to disk, if CopyProtect = TRUE. + // + // HDMI render: if DigitalOutputDisable or CopyProtect is true, enable HDCP. + // Loopback: if CopyProtect is true, disable loopback stream. + // + + // + // Sample writes each stream seperately to disk. If the rights for this + // stream indicates that the stream is CopyProtected, stop writing to disk. + // + m_SaveData.Disable(DrmRights->CopyProtect); + + // + // From MSDN: + // + // This sample doesn't forward protected content, but if your driver uses + // lower layer drivers or a different stack to properly work, please see the + // following info from MSDN: + // + // "Before allowing protected content to flow through a data path, the system + // verifies that the data path is secure. To do so, the system authenticates + // each module in the data path beginning at the upstream end of the data path + // and moving downstream. As each module is authenticated, that module gives + // the system information about the next module in the data path so that it + // can also be authenticated. To be successfully authenticated, a module's + // binary file must be signed as DRM-compliant. + // + // Two adjacent modules in the data path can communicate with each other in + // one of several ways. If the upstream module calls the downstream module + // through IoCallDriver, the downstream module is part of a WDM driver. In + // this case, the upstream module calls the AcxDrmForwardContentToDeviceObject + // function to provide the system with the device object representing the + // downstream module. (If the two modules communicate through the downstream + // module's content handlers, the upstream module calls AcxDrmAddContentHandlers + // instead.) + // + // For more information, see MSDN's DRM Functions and Interfaces. + // + + return STATUS_SUCCESS; +} + +_Use_decl_annotations_ +#pragma code_seg() +VOID +COffloadStreamEngine::s_EvtBufferReadTimerCallback( + WDFTIMER Timer +) +{ + COffloadStreamEngine * This; + PSTREAM_TIMER_CONTEXT timerCtx; + + // Get our stream engine pointer from the timer context + timerCtx = GetStreamTimerContext(Timer); + This = (COffloadStreamEngine *)timerCtx->StreamEngine; + + // Call the BufferReadCallback for the engine + This->BufferReadCallback(); +} + +// Callback indicating buffer read complete +_Use_decl_annotations_ +#pragma code_seg() +VOID +COffloadStreamEngine::BufferReadCallback() +{ + // Save the time at which we moved to the next packet + ULONGLONG qpcCompleted; + qpcCompleted = (ULONGLONG)KeQueryPerformanceCounter(NULL).QuadPart; + + ULONG packetsWritten = (ULONG)InterlockedCompareExchange((LONG*)&m_PacketsWritten, -1, -1); + + // We've completed a packet! Increment our currently active packet + ULONG packetsRead = (ULONG)InterlockedIncrement((LONG *)&m_PacketsRead) - 1; + + // + // \TODO + // Detect if hardware has cycled more than the OS. + // Can happen if application doesn't write data on time + // + if(packetsRead > packetsWritten) + { + DrvLogError(g_SDCAVDspLog, FLAG_STREAM, L"COffloadStreamEngine starved PacketsWritten: %08d, Packets Read: %08d", packetsWritten, packetsRead); + } + + // Tell ACX we've completed the packet. + // 0 based packet count + (void)AcxRtStreamNotifyPacketComplete(m_Stream, (ULONGLONG)packetsRead, qpcCompleted); + DrvLogVerbose(g_SDCAVDspLog, FLAG_STREAM, L"COffloadStreamEngine::BufferReadCallback packet complete - %d", packetsRead); +} + +_Use_decl_annotations_ +#pragma code_seg() +VOID +COffloadStreamEngine::s_EvtLastBufferTimerCallback( + WDFTIMER Timer +) +{ + COffloadStreamEngine * This; + PSTREAM_TIMER_CONTEXT timerCtx; + + // Get our stream engine pointer from the timer context + timerCtx = GetStreamTimerContext(Timer); + This = (COffloadStreamEngine *)timerCtx->StreamEngine; + + // Call the LastBufferRenderComplete for the engine + This->LastBufferRenderComplete(); +} + +_Use_decl_annotations_ +#pragma code_seg() +VOID +COffloadStreamEngine::LastBufferRenderComplete() +{ + // Save the time at which we moved to the next packet + ULONGLONG qpcCompleted; + qpcCompleted = (ULONGLONG)KeQueryPerformanceCounter(NULL).QuadPart; + + ULONGLONG completedPacket; + completedPacket = (ULONG)InterlockedIncrement((LONG*)&m_PacketsRead) - 1; + + // Tell ACX we've completed the packet. + (void)AcxRtStreamNotifyPacketComplete(m_Stream, completedPacket, qpcCompleted); + DrvLogVerbose(g_SDCAVDspLog, FLAG_STREAM, L"COffloadStreamEngine::LastBufferRenderComplete packet complete - %d", (ULONG)completedPacket); +} + +_Use_decl_annotations_ +#pragma code_seg() +VOID +COffloadStreamEngine::ProcessPacket() +{ +} + diff --git a/audio/Soundwire/Samples/SdcaVad/SdcaVDsp/offloadStreamEngine.h b/audio/Soundwire/Samples/SdcaVad/SdcaVDsp/offloadStreamEngine.h new file mode 100644 index 000000000..4c325dda1 --- /dev/null +++ b/audio/Soundwire/Samples/SdcaVad/SdcaVDsp/offloadStreamEngine.h @@ -0,0 +1,172 @@ +/*++ + + THIS CODE AND INFORMATION IS PROVIDED "AS IS" WITHOUT WARRANTY OF ANY + KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE + IMPLIED WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A PARTICULAR + PURPOSE. + +Module Name: + + offloadStreamEngine.h + +Abstract: + + Virtual Streaming Engine - this module controls offload streaming logic for + the device. + +Environment: + + Kernel mode + +--*/ + +#pragma once + +#include "streamengine.h" +#include "PositionSimClock.h" + +#define MAX_FILE_WRITE_FRAMES (16) +#define OFFLOAD_PRESENTATION_POSITION_LAG_IN_MS (20) + +class COffloadStreamEngine : public CStreamEngine +{ +public: + __drv_maxIRQL(PASSIVE_LEVEL) + PAGED_CODE_SEG + COffloadStreamEngine( + _In_ ACXSTREAM Stream, + _In_ ACXDATAFORMAT StreamFormat, + _In_ CSimPeakMeter *circuitPeakmeter + ); + + virtual + __drv_maxIRQL(PASSIVE_LEVEL) + #pragma code_seg() + ~COffloadStreamEngine(); + + virtual + __drv_maxIRQL(PASSIVE_LEVEL) + PAGED_CODE_SEG + NTSTATUS + PrepareHardware(); + + virtual + __drv_maxIRQL(PASSIVE_LEVEL) + PAGED_CODE_SEG + NTSTATUS + ReleaseHardware(); + + virtual + __drv_maxIRQL(PASSIVE_LEVEL) + PAGED_CODE_SEG + NTSTATUS + Run(); + + virtual + __drv_maxIRQL(PASSIVE_LEVEL) + PAGED_CODE_SEG + NTSTATUS + Pause(); + + virtual + __drv_maxIRQL(PASSIVE_LEVEL) + #pragma code_seg() + NTSTATUS + GetPresentationPosition( + _Out_ PULONGLONG PositionInBlocks, + _Out_ PULONGLONG QPCPosition + ); + + virtual + __drv_maxIRQL(PASSIVE_LEVEL) + PAGED_CODE_SEG + NTSTATUS + AssignDrmContentId( + _In_ ULONG DrmContentId, + _In_ PACXDRMRIGHTS DrmRights + ); + + virtual + __drv_maxIRQL(PASSIVE_LEVEL) + #pragma code_seg() + NTSTATUS + GetLinearBufferPosition( + _Out_ PULONGLONG Position + ); + + virtual + __drv_maxIRQL(PASSIVE_LEVEL) + PAGED_CODE_SEG + NTSTATUS + SetCurrentWritePosition( + _In_ ULONG Position + ); + + virtual + __drv_maxIRQL(PASSIVE_LEVEL) + PAGED_CODE_SEG + NTSTATUS + SetLastBufferPosition( + _In_ ULONG Position + ); + +protected: + WDFTIMER m_BufferReadTimer; + WDFTIMER m_LastBufferTimer; + + CPositionSimClock m_LinearBufferClock; + + CSaveData m_SaveData; + + // Number of packets written by OS + ULONG m_PacketsWritten; + + // Number of packets read by hardware + ULONG m_PacketsRead; + + ULONG m_SinglePacketPosition; + + virtual + __drv_maxIRQL(DISPATCH_LEVEL) + #pragma code_seg() + VOID + ProcessPacket(); + + virtual + __drv_maxIRQL(PASSIVE_LEVEL) + PAGED_CODE_SEG + NTSTATUS + SetCurrentWritePositionSinglePacket( + _In_ ULONG Position + ); + + static + __drv_maxIRQL(DISPATCH_LEVEL) + _Function_class_(EVT_WDF_TIMER) + #pragma code_seg() + VOID s_EvtBufferReadTimerCallback( + _In_ WDFTIMER Timer + ); + + static + __drv_maxIRQL(DISPATCH_LEVEL) + _Function_class_(EVT_WDF_TIMER) + #pragma code_seg() + VOID s_EvtLastBufferTimerCallback( + _In_ WDFTIMER Timer + ); + + // Callback indicating buffer read complete + virtual + __drv_maxIRQL(DISPATCH_LEVEL) + #pragma code_seg() + VOID + BufferReadCallback(); + + virtual + __drv_maxIRQL(DISPATCH_LEVEL) + #pragma code_seg() + VOID + LastBufferRenderComplete(); +}; + diff --git a/audio/Soundwire/Samples/SdcaVad/SdcaVDsp/packages.config b/audio/Soundwire/Samples/SdcaVad/SdcaVDsp/packages.config new file mode 100644 index 000000000..26065b144 --- /dev/null +++ b/audio/Soundwire/Samples/SdcaVad/SdcaVDsp/packages.config @@ -0,0 +1,4 @@ + + + + \ No newline at end of file diff --git a/audio/Soundwire/Samples/SdcaVad/SdcaVDsp/private.h b/audio/Soundwire/Samples/SdcaVad/SdcaVDsp/private.h new file mode 100644 index 000000000..065b33758 --- /dev/null +++ b/audio/Soundwire/Samples/SdcaVad/SdcaVDsp/private.h @@ -0,0 +1,803 @@ +/*++ + +Copyright (c) Microsoft Corporation. All rights reserved. + + THIS CODE AND INFORMATION IS PROVIDED "AS IS" WITHOUT WARRANTY OF ANY + KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE + IMPLIED WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A PARTICULAR + PURPOSE. + +Module Name: + + private.h + +Abstract: + + Contains structure definitions and function prototypes private to + the driver. + +Environment: + + Kernel mode + +Notes: + + Workarounds: + + ACX_WORKAROUND_ACXFACTORYCIRCUIT_01 + enables the logic to workaround an ACX v1.0 issue related to ACXFACTORYCIRCUIT race with Add/Remove child WDFDEVICE. + this issue has been fixed in ACX v1.1. + + ACX_WORKAROUND_ACXPIN_01 + enables the logic to workaround an ACX v1.0 issue related to ACXPIN's KSPROPERTY_PIN_CINSTANCES to return the pin's + stream instances vs. returning the total # of streams on a circuit. This # is not the same when circuit supports an + audio engine node, and client has instantiated streams on several pins (host/loopback/offload). + this issue has been fixed in ACX v1.1. + + ACX_WORKAROUND_ACXPIN_02 + enables the logic to workaround an ACX v1.1 issue related to ACXPIN's KSPROPERTY_PIN_PROPOSEDATAFORMAT set requests + directed to an 'offload' pin of an audio engine. The workaround fails the request if there are no enough resources + (streams). ACX will be enhanced in the future to automatically check this when the pin is tagged as 'offload' pin. + ACX_WORKAROUND_ACXPIN_01 must be enabled as well for ACX_WORKAROUND_ACXPIN_02 to work. + +--*/ + +#ifndef _PRIVATE_H_ +#define _PRIVATE_H_ + +#include "cpp_utils.h" + +#include "stdunk.h" +#include +#include +#include + +#include "NewDelete.h" + +/* make prototypes usable from C++ */ +#ifdef __cplusplus +extern "C" { +#endif + +#pragma warning(disable:4200) // +#pragma warning(disable:4201) // nameless struct/union +#pragma warning(disable:4214) // bit field types other than int + +#include +#include +#include +#include +#include + +#pragma warning(default:4200) +#pragma warning(default:4201) +#pragma warning(default:4214) + +#include +#include + +#include "AudioAggregation.h" +#include "soundwirecontroller.h" +#include "sdcastreaming.h" + +#include "trace.h" + +#define PAGED_CODE_SEG __declspec(code_seg("PAGE")) +#define INIT_CODE_SEG __declspec(code_seg("INIT")) + +extern RECORDER_LOG g_SDCAVDspLog; + +// Check for workaround dependencies. +#ifdef ACX_WORKAROUND_ACXPIN_02 + #ifndef ACX_WORKAROUND_ACXPIN_01 + #error ACX_WORKAROUND_ACXPIN_02 requires ACX_WORKAROUND_ACXPIN_01. + #endif +#endif + +// Copied from cfgmgr32.h +#if !defined(MAX_DEVICE_ID_LEN) +#define MAX_DEVICE_ID_LEN 200 +#endif + +// Define a NULL GUID if not already defined. +#if !defined(NULL_GUID) +#define NULL_GUID { 0, 0, 0, { 0, 0, 0, 0, 0, 0, 0, 0 } } +#endif + +// SDCA Sample driver + +#define DRIVER_TAG (ULONG) 'Dcds' + +// Number of millisecs per sec. +#define MS_PER_SEC 1000 + +// Number of hundred nanosecs per sec. +#define HNS_PER_SEC 10000000 + +// Compatible ID for render/capture +#define ACX_DSP_RENDER_COMPATIBLE_ID L"{ad164f4d-4149-41ed-82e8-99732ed7371a}" + +// Container ID for render/capture +#define ACX_DSP_SYSTEM_CONTAINER_ID L"{00000000-0000-0000-ffff-ffffffffffff}" + + +// Compatible ID for render/capture +#define ACX_DSP_TEST_COMPATIBLE_ID L"{ad164f4d-4149-41ed-82e8-99732ed7371a}" +// Container ID for render/capture +#define ACX_DSP_TEST_CONTAINER_ID L"{00000000-0000-0000-ffff-ffffffffffff}" + +extern const GUID DSP_CIRCUIT_SPEAKER_GUID; +extern const GUID DSP_CIRCUIT_MICROPHONE_GUID; +extern const GUID DSP_CIRCUIT_UNIVERSALJACK_RENDER_GUID; +extern const GUID DSP_CIRCUIT_UNIVERSALJACK_CAPTURE_GUID; + +extern const GUID SYSTEM_CONTAINER_GUID; + +#undef MIN +#undef MAX +#define MIN(a,b) ((a) > (b) ? (b) : (a)) +#define MAX(a,b) ((a) > (b) ? (a) : (b)) + +#define REQUEST_TIMEOUT_SECONDS 5 + +#ifndef SIZEOF_ARRAY +#define SIZEOF_ARRAY(ar) (sizeof(ar)/sizeof((ar)[0])) +#endif // !defined(SIZEOF_ARRAY) + +// +// Define DSP driver context. +// +typedef struct _DSP_DRIVER_CONTEXT { + BOOLEAN Dummy; +} DSP_DRIVER_CONTEXT, *PDSP_DRIVER_CONTEXT; + +WDF_DECLARE_CONTEXT_TYPE_WITH_NAME(DSP_DRIVER_CONTEXT, GetDspDriverContext) + +#define ALL_CHANNELS_ID UINT32_MAX +#define MAX_CHANNELS 2 +#define CHANNEL_MASK_INVALID UINT32_MAX + +// +// Define DSP device context. +// +typedef struct _DSP_DEVICE_CONTEXT { + ACXCIRCUIT Render; + ACXCIRCUIT Capture; + ACXFACTORYCIRCUIT Factory; + WDFDEVICE AudioSensorsDevice; + WDFCHILDLIST ChildList; +} DSP_DEVICE_CONTEXT, *PDSP_DEVICE_CONTEXT; + +WDF_DECLARE_CONTEXT_TYPE_WITH_NAME(DSP_DEVICE_CONTEXT, GetDspDeviceContext) + +// +// Define Audio Sensors device context. +// +typedef struct _AUDIO_SENSORS_DEVICE_CONTEXT +{ + WDFDEVICE Device; +} AUDIO_SENSORS_DEVICE_CONTEXT, *PAUDIO_SENSORS_DEVICE_CONTEXT; + +WDF_DECLARE_CONTEXT_TYPE_WITH_NAME(AUDIO_SENSORS_DEVICE_CONTEXT, GetAudioSensorsDeviceContext) + +// +// Define DSP factory context. +// +typedef struct _DSP_FACTORY_CONTEXT { + WDFDEVICE Device; +#ifdef ACX_WORKAROUND_ACXFACTORYCIRCUIT_01 + WDFWAITLOCK CacheLock; + WDFCOLLECTION Cache; +#endif +} DSP_FACTORY_CONTEXT, *PDSP_FACTORY_CONTEXT; + +WDF_DECLARE_CONTEXT_TYPE_WITH_NAME(DSP_FACTORY_CONTEXT, GetDspFactoryContext) + +#ifdef ACX_WORKAROUND_ACXFACTORYCIRCUIT_01 +// +// Define DSP device ID context. +// +typedef struct _DSP_DEVICEID_CONTEXT { + ACXFACTORYCIRCUIT Factory; + GUID UniqueID; +} DSP_DEVICEID_CONTEXT, *PDSP_DEVICEID_CONTEXT; + +WDF_DECLARE_CONTEXT_TYPE_WITH_NAME(DSP_DEVICEID_CONTEXT, GetDspDeviceIdContext) +#endif // ACX_WORKAROUND_ACXFACTORYCIRCUIT_01 + +// +// Define RENDER device context. +// +typedef struct _DSP_RENDER_DEVICE_CONTEXT { + ACXCIRCUIT Circuit; + BOOLEAN FirstTimePrepareHardware; +} DSP_RENDER_DEVICE_CONTEXT, *PDSP_RENDER_DEVICE_CONTEXT; + +WDF_DECLARE_CONTEXT_TYPE_WITH_NAME(DSP_RENDER_DEVICE_CONTEXT, GetRenderDeviceContext) + +// Special stream definitions +typedef enum _SDCA_SPECIALSTREAM_TYPE +{ + SpecialStreamTypeNotSupported = 0, + SpecialStreamTypeUltrasoundRender = 1, + SpecialStreamTypeUltrasoundCapture = 2, + SpecialStreamTypeReferenceStream = 3, + SpecialStreamTypeIvSense = 4, + SpecialStreamType_Count = 5, +} SDCA_SPECIALSTREAM_TYPE, *PSDCA_SPECIALSTREAM_TYPE; + +inline const SDCA_SPECIALSTREAM_TYPE SpecialStreamTypeFromSdcaPath(SDCA_PATH path) +{ + switch(path) + { + case SdcaPathUltrasoundRender: + return SpecialStreamTypeUltrasoundRender; + case SdcaPathUltrasoundCapture: + return SpecialStreamTypeUltrasoundCapture; + case SdcaPathReferenceStream: + return SpecialStreamTypeReferenceStream; + case SdcaPathIvSense: + return SpecialStreamTypeIvSense; + } + return SpecialStreamTypeNotSupported; +} + +inline const SDCA_PATH SdcaPathFromSpecialStreamType(SDCA_SPECIALSTREAM_TYPE type) +{ + switch(type) + { + case SpecialStreamTypeUltrasoundRender: + return SdcaPathUltrasoundRender; + case SpecialStreamTypeUltrasoundCapture: + return SdcaPathUltrasoundCapture; + case SpecialStreamTypeReferenceStream: + return SdcaPathReferenceStream; + case SpecialStreamTypeIvSense: + return SdcaPathIvSense; + } + return (SDCA_PATH) 0; +} + +// Maximum of 8 devices chosen for the purpose of making this sample simpler +#define MAX_AGGREGATED_DEVICES (8) + +// +// Define circuit context. +// +typedef struct _DSP_CIRCUIT_CONTEXT { + ULONG EndpointId; + ULONG DataPortNumber; + ACXAUDIOENGINE AudioEngineElement; + ACXPEAKMETER PeakMeterElement; + PVOID peakMeter; + ACXKEYWORDSPOTTER KeywordSpotter; + + // If the VolumeMuteHandler is set, we will forward any + // Volume/Mute requests for the current circuit to this + // target circuit. If the target circuit was allocated + // by this driver, it will also be copied to + // TargetCircuitToDelete + ACXTARGETCIRCUIT TargetCircuitToDelete; + ACXTARGETCIRCUIT TargetVolumeMuteCircuit; + ACXTARGETELEMENT TargetVolumeHandler; + ACXTARGETELEMENT TargetMuteHandler; + + BOOLEAN IsRenderCircuit; + + // This will contain information on the aggregated devices we're connected to + BOOLEAN Aggregated; + ULONG AggregatedDeviceCount; + SDCA_AGGREGATION_DEVICE AggregatedDevices[MAX_AGGREGATED_DEVICES]; + PSDCA_PATH_DESCRIPTORS2 AggregatedPathDescriptors; + + ULONG SpecialStreamAvailablePaths; + PSDCA_PATH_DESCRIPTORS SpecialStreamPathDescriptors[SpecialStreamType_Count]; + PSDCA_PATH_DESCRIPTORS2 SpecialStreamPathDescriptors2[SpecialStreamType_Count]; + ULONG SpecialStreamActive[SpecialStreamType_Count]; + ULONG SpecialStreamRunning[SpecialStreamType_Count]; + ACXTARGETCIRCUIT SpecialStreamTargetCircuit; + // The ConnectedFunctionInformation will be used with SpecialStream logic and also + // for determining appropriate data ports to be used with each connected audio function + PSDCA_FUNCTION_INFORMATION_LIST ConnectedFunctionInformation; + +} DSP_CIRCUIT_CONTEXT, * PDSP_CIRCUIT_CONTEXT; + +WDF_DECLARE_CONTEXT_TYPE_WITH_NAME(DSP_CIRCUIT_CONTEXT, GetDspCircuitContext) + +// +// Define CAPTURE device context. +// +typedef struct _DSP_CAPTURE_DEVICE_CONTEXT { + ACXCIRCUIT Circuit; + BOOLEAN FirstTimePrepareHardware; +} DSP_CAPTURE_DEVICE_CONTEXT, *PDSP_CAPTURE_DEVICE_CONTEXT; + +WDF_DECLARE_CONTEXT_TYPE_WITH_NAME(DSP_CAPTURE_DEVICE_CONTEXT, GetCaptureDeviceContext) + +// +// Define DSP circuit/stream element context. +// +typedef struct _DSP_ELEMENT_CONTEXT { + BOOLEAN Dummy; +} DSP_ELEMENT_CONTEXT, *PDSP_ELEMENT_CONTEXT; + +WDF_DECLARE_CONTEXT_TYPE_WITH_NAME(DSP_ELEMENT_CONTEXT, GetDspElementContext) + +// +// Define DSP format context. +// +typedef struct _DSP_FORMAT_CONTEXT { + BOOLEAN Dummy; +} DSP_FORMAT_CONTEXT, *PDSP_FORMAT_CONTEXT; + +WDF_DECLARE_CONTEXT_TYPE_WITH_NAME(DSP_FORMAT_CONTEXT, GetDspFormatContext) + +typedef enum _DSP_PIN_TYPE { + DspPinTypeHost, + DspPinTypeOffload, + DspPinTypeLoopback, + DspPinTypeBridge, + DspPinType_Count +} DSP_PIN_TYPE, * PDSP_PIN_TYPE; + +typedef enum _DSP_CAPTURE_PIN_TYPE { + DspCapturePinTypeHost, + DspCapturePinTypeKeyword, + DspCapturePinTypeBridge, + DspCapturePinType_Count +} DSP_CAPTURE_PIN_TYPE, * PDSP_CAPTURE_PIN_TYPE; + +typedef struct _DSP_PIN_CONTEXT { + ACXTARGETCIRCUIT TargetCircuit; + ULONG TargetPinId; + DSP_PIN_TYPE PinType; + DSP_CAPTURE_PIN_TYPE CapturePinType; + + // The stream bridge below will only be valid for the Capture circuit Bridge Pin + + // Host stream bridge will be used to ensure host stream creations are passed + // to the downlevel circuits. Since the HostStreamBridge won't have InModes set, + // the ACX framework will not add streams automatically. We will add streams for + // non KWS pin. + ACXSTREAMBRIDGE HostStreamBridge; + ACXOBJECTBAG HostStreamObjBag; + +#ifdef ACX_WORKAROUND_ACXPIN_01 + ULONG MaxStreams; + ULONG CurrentStreamsCount; +#endif +} DSP_PIN_CONTEXT, *PDSP_PIN_CONTEXT; + +WDF_DECLARE_CONTEXT_TYPE_WITH_NAME(DSP_PIN_CONTEXT, GetDspPinContext) + +// +// Define DSP render/capture stream context. +// +typedef struct _DSP_STREAM_CONTEXT { + PVOID StreamEngine; + DSP_PIN_TYPE PinType; + DSP_CAPTURE_PIN_TYPE CapturePinType; + ACXPIN Pin; // used by acx workaround, and reference streams + +#ifdef ACX_WORKAROUND_ACXPIN_01 + BOOLEAN StreamIsCounted; // TRUE = stream is counted on the pin. +#endif + + ACXTARGETCIRCUIT SpecialStreamTargetCircuit; + BOOLEAN SpecialStreamInUse[SpecialStreamType_Count]; + BOOLEAN SpecialStreamRunning[SpecialStreamType_Count]; +} DSP_STREAM_CONTEXT, *PDSP_STREAM_CONTEXT; + +WDF_DECLARE_CONTEXT_TYPE_WITH_NAME(DSP_STREAM_CONTEXT, GetDspStreamContext) + +typedef struct _DSP_ENGINE_CONTEXT { + ACXDATAFORMAT MixFormat; + BOOLEAN GFxEnabled; +} DSP_ENGINE_CONTEXT, * PDSP_ENGINE_CONTEXT; + +WDF_DECLARE_CONTEXT_TYPE_WITH_NAME(DSP_ENGINE_CONTEXT, GetDspEngineContext) + +typedef struct _DSP_STREAMAUDIOENGINE_CONTEXT { + BOOLEAN LFxEnabled; +} DSP_STREAMAUDIOENGINE_CONTEXT, * PDSP_STREAMAUDIOENGINE_CONTEXT; + +WDF_DECLARE_CONTEXT_TYPE_WITH_NAME(DSP_STREAMAUDIOENGINE_CONTEXT, GetDspStreamAudioEngineContext) + +// +// Define DSP keyword spotter context +// +typedef struct _DSP_KEYWORDSPOTTER_CONTEXT { + ACXPNPEVENT Event; + PVOID KeywordDetector; +} DSP_KEYWORDSPOTTER_CONTEXT, *PDSP_KEYWORDSPOTTER_CONTEXT; + +WDF_DECLARE_CONTEXT_TYPE_WITH_NAME(DSP_KEYWORDSPOTTER_CONTEXT, GetDspKeywordSpotterContext) + +typedef struct _DSP_PNPEVENT_CONTEXT { + BOOLEAN Dummy; +} DSP_PNPEVENT_CONTEXT, *PDSP_PNPEVENT_CONTEXT; + +WDF_DECLARE_CONTEXT_TYPE_WITH_NAME(DSP_PNPEVENT_CONTEXT, GetDspPnpEventContext) + +// +// Define DSP peakmeter element context. +// +typedef struct _DSP_PEAKMETER_ELEMENT_CONTEXT { + PVOID peakMeter; +} DSP_PEAKMETER_ELEMENT_CONTEXT, * PDSP_PEAKMETER_ELEMENT_CONTEXT; + +WDF_DECLARE_CONTEXT_TYPE_WITH_NAME(DSP_PEAKMETER_ELEMENT_CONTEXT, GetDspPeakMeterElementContext) + +#define PEAKMETER_STEPPING_DELTA 0x1000 +#define PEAKMETER_MAXIMUM LONG_MAX +#define PEAKMETER_MINIMUM LONG_MIN + +// +// Define DSP circuit/stream element context. +// +typedef struct _DSP_MUTE_ELEMENT_CONTEXT { + BOOL MuteState[MAX_CHANNELS]; +} DSP_MUTE_ELEMENT_CONTEXT, * PDSP_MUTE_ELEMENT_CONTEXT; + +WDF_DECLARE_CONTEXT_TYPE_WITH_NAME(DSP_MUTE_ELEMENT_CONTEXT, GetDspMuteElementContext) + +// +// Define DSP circuit/stream element context. +// +typedef struct _DSP_VOLUME_ELEMENT_CONTEXT { + LONG VolumeLevel[MAX_CHANNELS]; +} DSP_VOLUME_ELEMENT_CONTEXT, * PDSP_VOLUME_ELEMENT_CONTEXT; + +WDF_DECLARE_CONTEXT_TYPE_WITH_NAME(DSP_VOLUME_ELEMENT_CONTEXT, GetDspVolumeElementContext) + +#define VOLUME_STEPPING 0x8000 +#define VOLUME_LEVEL_MAXIMUM 0x00000000 +#define VOLUME_LEVEL_MINIMUM (-96 * 0x10000) + +// +// Driver prototypes. +// +DRIVER_INITIALIZE DriverEntry; +EVT_WDF_DRIVER_UNLOAD Dsp_DriverUnload; +EVT_WDF_DRIVER_DEVICE_ADD Dsp_EvtBusDeviceAdd; +EVT_WDF_CHILD_LIST_CREATE_DEVICE Dsp_AddAudioSensorsDevice; + +// Device callbacks. + +EVT_WDF_DEVICE_PREPARE_HARDWARE Dsp_EvtDevicePrepareHardware; +EVT_WDF_DEVICE_RELEASE_HARDWARE Dsp_EvtDeviceReleaseHardware; +EVT_WDF_DEVICE_CONTEXT_CLEANUP Dsp_EvtDeviceContextCleanup; + +EVT_ACX_FACTORY_CIRCUIT_CREATE_CIRCUITDEVICE Dsp_EvtAcxFactoryCircuitCreateCircuitDevice; +EVT_ACX_FACTORY_CIRCUIT_CREATE_CIRCUIT Dsp_EvtAcxFactoryCircuitCreateCircuit; +EVT_WDF_DEVICE_CONTEXT_CLEANUP Dsp_EvtFactoryContextCleanup; +EVT_WDF_DEVICE_CONTEXT_DESTROY Dsp_EvtFactoryContextDestroy; + +// Stream callbacks shared between Capture and Render + +EVT_WDF_OBJECT_CONTEXT_DESTROY Dsp_EvtStreamContextDestroy; +EVT_ACX_STREAM_GET_HW_LATENCY Dsp_EvtStreamGetHwLatency; +EVT_ACX_STREAM_ALLOCATE_RTPACKETS Dsp_EvtStreamAllocateRtPackets; +EVT_ACX_STREAM_FREE_RTPACKETS Dsp_EvtStreamFreeRtPackets; +EVT_ACX_STREAM_PREPARE_HARDWARE Dsp_EvtStreamPrepareHardware; +EVT_ACX_STREAM_RELEASE_HARDWARE Dsp_EvtStreamReleaseHardware; +EVT_ACX_STREAM_RUN Dsp_EvtStreamRun; +EVT_ACX_STREAM_PAUSE Dsp_EvtStreamPause; +EVT_ACX_STREAM_GET_CURRENT_PACKET Dsp_EvtStreamGetCurrentPacket; +EVT_ACX_STREAM_ASSIGN_DRM_CONTENT_ID Dsp_EvtStreamAssignDrmContentId; +EVT_ACX_STREAM_GET_PRESENTATION_POSITION Dsp_EvtStreamGetPresentationPosition; +EVT_ACX_OBJECT_PREPROCESS_REQUEST DspC_EvtStreamRequestPreprocess; + + +// Render callbacks. +EVT_ACX_FACTORY_CIRCUIT_CREATE_CIRCUITDEVICE DspR_EvtAcxFactoryCircuitCreateCircuitDevice; + +_IRQL_requires_max_(PASSIVE_LEVEL) +NTSTATUS +NTAPI +DspR_EvtAcxFactoryCircuitCreateCircuit( + _In_ + WDFDEVICE Parent, + _In_ + WDFDEVICE Device, + _In_ + ACXFACTORYCIRCUIT Factory, + _In_ + PACX_FACTORY_CIRCUIT_ADD_CIRCUIT Config, + _In_ + PACXCIRCUIT_INIT CircuitInit, + _In_ + ULONG DataPortNumber, + _In_opt_ + PSDCA_PATH_DESCRIPTORS2 PathDescriptors +); + +EVT_ACX_CIRCUIT_COMPOSITE_CIRCUIT_INITIALIZE DspR_EvtCircuitCompositeCircuitInitialize; +EVT_ACX_CIRCUIT_COMPOSITE_INITIALIZE DspR_EvtCircuitCompositeInitialize; + +EVT_WDF_DEVICE_CONTEXT_CLEANUP DspR_EvtCircuitContextCleanup; +EVT_WDF_DEVICE_PREPARE_HARDWARE DspR_EvtDevicePrepareHardware; +EVT_WDF_DEVICE_RELEASE_HARDWARE DspR_EvtDeviceReleaseHardware; +EVT_WDF_DEVICE_SELF_MANAGED_IO_INIT DspR_EvtDeviceSelfManagedIoInit; +EVT_WDF_DEVICE_CONTEXT_CLEANUP DspR_EvtDeviceContextCleanup; +EVT_ACX_OBJECT_PREPROCESS_REQUEST DspR_EvtCircuitRequestPreprocess; +EVT_ACX_CIRCUIT_CREATE_STREAM DspR_EvtCircuitCreateStream; +EVT_ACX_CIRCUIT_POWER_UP DspR_EvtCircuitPowerUp; +EVT_ACX_CIRCUIT_POWER_DOWN DspR_EvtCircuitPowerDown; +EVT_ACX_STREAM_SET_RENDER_PACKET DspR_EvtStreamSetRenderPacket; +EVT_ACX_PIN_SET_DATAFORMAT DspR_EvtAcxPinSetDataFormat; +EVT_WDF_DEVICE_CONTEXT_CLEANUP DspR_EvtPinContextCleanup; +EVT_ACX_PIN_CONNECTED DspR_EvtPinConnected; +EVT_ACX_PIN_DISCONNECTED DspR_EvtPinDisconnected; + +//Render Audio Engine +EVT_ACX_MUTE_ASSIGN_STATE DspR_EvtMuteAssignState; +EVT_ACX_MUTE_RETRIEVE_STATE DspR_EvtMuteRetrieveState; +EVT_ACX_VOLUME_ASSIGN_LEVEL DspR_EvtVolumeAssignLevel; +EVT_ACX_VOLUME_RETRIEVE_LEVEL DspR_EvtVolumeRetrieveLevel; +EVT_ACX_PEAKMETER_RETRIEVE_LEVEL DspR_EvtPeakMeterRetrieveLevelCallback; +EVT_ACX_RAMPED_VOLUME_ASSIGN_LEVEL DspR_EvtRampedVolumeAssignLevel; +EVT_ACX_AUDIOENGINE_RETRIEVE_BUFFER_SIZE_LIMITS DspR_EvtAcxAudioEngineRetrieveBufferSizeLimits; +EVT_ACX_AUDIOENGINE_RETRIEVE_EFFECTS_STATE DspR_EvtAcxAudioEngineRetrieveEffectsState; +EVT_ACX_AUDIOENGINE_ASSIGN_EFFECTS_STATE DspR_EvtAcxAudioEngineAssignEffectsState; +EVT_ACX_AUDIOENGINE_RETRIEVE_ENGINE_FORMAT DspR_EvtAcxAudioEngineRetrieveEngineMixFormat; +EVT_ACX_AUDIOENGINE_ASSIGN_ENGINE_FORMAT DspR_EvtAcxAudioEngineAssignEngineDeviceFormat; +EVT_ACX_STREAMAUDIOENGINE_RETRIEVE_EFFECTS_STATE DspR_EvtAcxStreamAudioEngineRetrieveEffectsState; +EVT_ACX_STREAMAUDIOENGINE_ASSIGN_EFFECTS_STATE DspR_EvtAcxStreamAudioEngineAssignEffectsState; +EVT_ACX_STREAMAUDIOENGINE_RETRIEVE_PRESENTATION_POSITION DspR_EvtAcxStreamAudioEngineRetrievePresentationPosition; +EVT_ACX_STREAMAUDIOENGINE_ASSIGN_CURRENT_WRITE_POSITION DspR_EvtAcxStreamAudioEngineAssignCurrentWritePosition; +EVT_ACX_STREAMAUDIOENGINE_RETRIEVE_LINEAR_BUFFER_POSITION DspR_EvtAcxStreamAudioEngineRetrieveLinearBufferPosition; +EVT_ACX_STREAMAUDIOENGINE_ASSIGN_LAST_BUFFER_POSITION DspR_EvtAcxStreamAudioEngineAssignLastBufferPosition; +EVT_ACX_STREAMAUDIOENGINE_ASSIGN_LOOPBACK_PROTECTION DspR_EvtAcxStreamAudioEngineAssignLoopbackProtection; + +// Capture callbacks. +EVT_ACX_FACTORY_CIRCUIT_CREATE_CIRCUITDEVICE DspC_EvtAcxFactoryCircuitCreateCircuitDevice; + +_IRQL_requires_max_(PASSIVE_LEVEL) +NTSTATUS +NTAPI +DspC_EvtAcxFactoryCircuitCreateCircuit( + _In_ + WDFDEVICE Parent, + _In_ + WDFDEVICE Device, + _In_ + ACXFACTORYCIRCUIT Factory, + _In_ + PACX_FACTORY_CIRCUIT_ADD_CIRCUIT Config, + _In_ + PACXCIRCUIT_INIT CircuitInit, + _In_ + ULONG DataPortNumber, + _In_ + PSDCA_PATH_DESCRIPTORS2 PathDescriptors +); + +EVT_ACX_CIRCUIT_COMPOSITE_CIRCUIT_INITIALIZE DspC_EvtCircuitCompositeCircuitInitialize; +EVT_ACX_CIRCUIT_COMPOSITE_INITIALIZE DspC_EvtCircuitCompositeInitialize; + +EVT_WDF_DEVICE_CONTEXT_CLEANUP DspC_EvtCircuitContextCleanup; +EVT_WDF_DEVICE_PREPARE_HARDWARE DspC_EvtDevicePrepareHardware; +EVT_WDF_DEVICE_RELEASE_HARDWARE DspC_EvtDeviceReleaseHardware; +EVT_WDF_DEVICE_SELF_MANAGED_IO_INIT DspC_EvtDeviceSelfManagedIoInit; +EVT_WDF_DEVICE_CONTEXT_CLEANUP DspC_EvtDeviceContextCleanup; +EVT_ACX_OBJECT_PREPROCESS_REQUEST DspC_EvtCircuitRequestPreprocess; +EVT_ACX_CIRCUIT_CREATE_STREAM DspC_EvtCircuitCreateStream; +EVT_ACX_CIRCUIT_POWER_UP DspC_EvtCircuitPowerUp; +EVT_ACX_CIRCUIT_POWER_DOWN DspC_EvtCircuitPowerDown; +EVT_ACX_STREAM_GET_CAPTURE_PACKET DspC_EvtStreamGetCapturePacket; +EVT_ACX_PIN_SET_DATAFORMAT DspC_EvtAcxPinSetDataFormat; +EVT_WDF_DEVICE_CONTEXT_CLEANUP DspC_EvtPinContextCleanup; +EVT_ACX_PIN_CONNECTED DspC_EvtPinConnected; +EVT_ACX_PIN_DISCONNECTED DspC_EvtPinDisconnected; +EVT_ACX_KEYWORDSPOTTER_RETRIEVE_ARM DspC_EvtAcxKeywordSpotterRetrieveArm; +EVT_ACX_KEYWORDSPOTTER_ASSIGN_ARM DspC_EvtAcxKeywordSpotterAssignArm; +EVT_ACX_KEYWORDSPOTTER_ASSIGN_PATTERNS DspC_EvtAcxKeywordSpotterAssignPatterns; +EVT_ACX_KEYWORDSPOTTER_ASSIGN_RESET DspC_EvtAcxKeywordSpotterAssignReset; + +// Property testing, todo: remove them. + +EVT_ACX_OBJECT_PROCESS_REQUEST DspR_EvtPinCInstancesCallback; +EVT_ACX_OBJECT_PROCESS_REQUEST DspR_EvtPinCTypesCallback; +EVT_ACX_OBJECT_PROCESS_REQUEST DspR_EvtPinDataFlowCallback; +EVT_ACX_OBJECT_PROCESS_REQUEST DspR_EvtPinDataRangesCallback; +EVT_ACX_OBJECT_PROCESS_REQUEST DspR_EvtPinDataIntersectionCallback; +EVT_ACX_OBJECT_PROCESS_REQUEST DspR_EvtPinPhysicalConnectionCallback; + + +EVT_ACX_OBJECT_PREPROCESS_REQUEST DspR_EvtStreamRequestPreprocess; +EVT_WDF_OBJECT_CONTEXT_CLEANUP Dsp_EvtStreamContextCleanup; + +#ifdef ACX_WORKAROUND_ACXPIN_01 +EVT_ACX_OBJECT_PREPROCESS_REQUEST Dsp_EvtStreamGetStreamCountRequestPreprocess; +#endif // ACX_WORKAROUND_ACXPIN_01 + +#ifdef ACX_WORKAROUND_ACXPIN_02 +EVT_ACX_OBJECT_PREPROCESS_REQUEST Dsp_EvtStreamProposeDataFormatRequestPreprocess; +#endif // ACX_WORKAROUND_ACXPIN_02 + +/* make internal prototypes usable from C++ */ +#ifdef __cplusplus +} +#endif + +// +// Used to store the registry settings path for the driver +// +extern UNICODE_STRING g_RegistryPath; + +__drv_requiresIRQL(PASSIVE_LEVEL) +PAGED_CODE_SEG +NTSTATUS +CopyRegistrySettingsPath( + _In_ PUNICODE_STRING RegistryPath + ); + +PAGED_CODE_SEG +NTSTATUS +Dsp_CreateChildList( + _In_ WDFDEVICE Device +); + +PAGED_CODE_SEG +NTSTATUS +Dsp_AddFactoryCircuit( + _In_ WDFDEVICE Device +); + +PAGED_CODE_SEG +VOID +Dsp_RemoveFactoryCircuit( + _In_ WDFDEVICE Device +); + +#ifdef ACX_WORKAROUND_ACXFACTORYCIRCUIT_01 +PAGED_CODE_SEG +NTSTATUS +Dsp_InitializeChildDevicesCache( + _In_ ACXFACTORYCIRCUIT Factory + ); + +PAGED_CODE_SEG +VOID +Dsp_CleanupChildDevicesCache( + _In_ ACXFACTORYCIRCUIT Factory + ); + +PAGED_CODE_SEG +VOID +Dsp_DeleteChildDevicesCache( + _In_ ACXFACTORYCIRCUIT Factory + ); + +PAGED_CODE_SEG +bool +Dsp_IsChildDeviceInCacheLocked( + _In_ ACXFACTORYCIRCUIT Factory, + _In_ const GUID * UniqueId + ); + +PAGED_CODE_SEG +NTSTATUS +Dsp_AddChildDeviceToCache( + _In_ ACXFACTORYCIRCUIT Factory, + _In_ const GUID * UniqueId, + _In_ WDFDEVICE Device + ); + +PAGED_CODE_SEG +WDFDEVICE +Dsp_RemoveChildDeviceFromCache( + _In_ ACXFACTORYCIRCUIT Factory, + _In_ const GUID * UniqueId + ); + +PAGED_CODE_SEG +VOID +Dsp_PurgeChildDeviceFromCache( + _In_ ACXFACTORYCIRCUIT Factory, + _In_ WDFDEVICE Device + ); + +EVT_ACX_OBJECT_PREPROCESS_REQUEST Dsp_EvtFactoryRemoveCircuitRequestPreprocess; +EVT_WDF_DEVICE_CONTEXT_CLEANUP Dsp_EvtDeviceIdContextCleanup; +EVT_ACX_OBJECT_PROCESS_REQUEST Dsp_EvtFactoryCircuitRemoveCircuitCallback; +#endif // ACX_WORKAROUND_ACXFACTORYCIRCUIT_01 + +PAGED_CODE_SEG +NTSTATUS +DspC_CircuitCleanup( + _In_ ACXCIRCUIT Device + ); + +PAGED_CODE_SEG +NTSTATUS +Dsp_SetPowerPolicy( + _In_ WDFDEVICE Device + ); + +PAGED_CODE_SEG +NTSTATUS +DspR_SetPowerPolicy( + _In_ WDFDEVICE Device + ); + +PAGED_CODE_SEG +NTSTATUS +DspC_SetPowerPolicy( + _In_ WDFDEVICE Device + ); + +PAGED_CODE_SEG +NTSTATUS +DSP_SendPropertyTo +( + _In_ WDFDEVICE Device, + _In_ ACXTARGETCIRCUIT TargetCircuit, + _In_ GUID PropertySet, + _In_ ULONG PropertyId, + _In_ ACX_PROPERTY_VERB Verb, + _In_ PVOID Control, + _In_ ULONG ControlCb, + _Inout_ PVOID Value, + _In_ ULONG ValueCb, + _Out_ ULONG_PTR* Information +); + +PAGED_CODE_SEG +NTSTATUS +Dsp_SendTestPropertyTo( + _In_ WDFDEVICE Device, + _In_ ACXCIRCUIT Circuit, + _In_ GUID PropertySet, + _In_ ULONG PropertyId, + _In_ ACX_PROPERTY_VERB Verb, + _In_ PVOID Control, + _In_ ULONG ControlCb, + _Inout_ PVOID Value, + _In_ ULONG ValueCb, + _Out_ ULONG_PTR* Information + ); + +PAGED_CODE_SEG +VOID +Dsp_SendVendorSpecificProperties( + _In_ WDFDEVICE Device, + _In_ ACXCIRCUIT Circuit, + _In_ BOOLEAN SetValue + ); + + +// Create a single SDCA_PATH for special streaming associated +// with the given ACXSTREAM +PAGED_CODE_SEG +NTSTATUS +Dsp_PrepareSpecialStreamForStream( + _In_ ACXSTREAM Stream, + _In_ SDCA_SPECIALSTREAM_TYPE SpecialStreamType, + _In_ ULONG FunctionBitMask = 0xffffffff + ); + +// Destroy all SDCA_PATHs associated with the given ACXSTREAM +PAGED_CODE_SEG +NTSTATUS +Dsp_ReleaseSpecialStreamsForStream( + _In_ ACXSTREAM Stream + ); + +// Start all SDCA_PATHs associated with the given ACXSTREAM +PAGED_CODE_SEG +NTSTATUS +Dsp_StartSpecialStreamsForStream( + _In_ ACXSTREAM Stream + ); + +// Stop all SDCA_PATHs associated with the given ACXSTREAM +PAGED_CODE_SEG +NTSTATUS +Dsp_StopSpecialStreamsForStream( + _In_ ACXSTREAM Stream + ); + +#endif // _PRIVATE_H_ + diff --git a/audio/Soundwire/Samples/SdcaVad/SdcaVDsp/render.cpp b/audio/Soundwire/Samples/SdcaVad/SdcaVDsp/render.cpp new file mode 100644 index 000000000..fe16d5c45 --- /dev/null +++ b/audio/Soundwire/Samples/SdcaVad/SdcaVDsp/render.cpp @@ -0,0 +1,2730 @@ +/*++ + + THIS CODE AND INFORMATION IS PROVIDED "AS IS" WITHOUT WARRANTY OF ANY + KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE + IMPLIED WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A PARTICULAR + PURPOSE. + +Module Name: + + render.cpp + +Abstract: + + Render factory and circuit + +Environment: + + Kernel mode + +--*/ + +#include "private.h" +#include +#include "stdunk.h" +#include +#include +#include +#include "streamengine.h" +#include "offloadStreamEngine.h" +#include "SimPeakMeter.h" +#include "CircuitHelper.h" +#include "AcpiReader.h" + +#include "TestProperties.h" +#include "AudioFormats.h" + +#ifndef __INTELLISENSE__ +#include "render.tmh" +#endif + +#include "audiomodule.h" + +using namespace ACPIREADER; + +// +// max # of streams for each pin type. +// +#define DSPR_MAX_INPUT_HOST_STREAMS 2 +#define DSPR_MAX_INPUT_OFFLOAD_STREAMS 3 +#define DSPR_MAX_OUTPUT_LOOPBACK_STREAMS 1 + +// +// Factory circuit IDs. +// +#define RENDER_DEVICE_ID_STR L"{4DCB0606-6415-4A36-BDC5-9B1792117DC9}\\Render&CP_%wZ" +DECLARE_CONST_UNICODE_STRING(RenderHardwareId, L"{4DCB0606-6415-4A36-BDC5-9B1792117DC9}\\Render"); + +DECLARE_CONST_UNICODE_STRING(RenderCompatibleId, ACX_DSP_TEST_COMPATIBLE_ID); +DECLARE_CONST_UNICODE_STRING(RenderContainerId, ACX_DSP_TEST_CONTAINER_ID); +DECLARE_CONST_UNICODE_STRING(RenderDeviceLocation, L"SDCAVDsp Dynamic Enum Speaker"); + +PAGED_CODE_SEG +VOID +DspR_EvtPinCInstancesCallback( + _In_ WDFOBJECT Object, + _In_ WDFREQUEST Request + ) +{ + PAGED_CODE(); + + // TEMP: for testing only. + UNREFERENCED_PARAMETER(Object); + WdfRequestComplete(Request, STATUS_UNSUCCESSFUL); +} + +PAGED_CODE_SEG +VOID +DspR_EvtPinCTypesCallback( + _In_ WDFOBJECT Object, + _In_ WDFREQUEST Request + ) +{ + PAGED_CODE(); + + // TEMP: for testing only. + UNREFERENCED_PARAMETER(Object); + WdfRequestComplete(Request, STATUS_UNSUCCESSFUL); +} + +PAGED_CODE_SEG +VOID +DspR_EvtPinDataFlowCallback( + _In_ WDFOBJECT Object, + _In_ WDFREQUEST Request + ) +{ + PAGED_CODE(); + + // TEMP: for testing only. + UNREFERENCED_PARAMETER(Object); + WdfRequestComplete(Request, STATUS_UNSUCCESSFUL); +} + +PAGED_CODE_SEG +VOID +DspR_EvtPinDataRangesCallback( + _In_ WDFOBJECT Object, + _In_ WDFREQUEST Request + ) +{ + PAGED_CODE(); + + // TEMP: for testing only. + UNREFERENCED_PARAMETER(Object); + WdfRequestComplete(Request, STATUS_UNSUCCESSFUL); +} + +PAGED_CODE_SEG +VOID +DspR_EvtPinDataIntersectionCallback( + _In_ WDFOBJECT Object, + _In_ WDFREQUEST Request + ) +{ + PAGED_CODE(); + + // TEMP: for testing only. + UNREFERENCED_PARAMETER(Object); + WdfRequestComplete(Request, STATUS_UNSUCCESSFUL); +} + +PAGED_CODE_SEG +NTSTATUS +DspR_EvtAcxPinSetDataFormat ( + _In_ ACXPIN Pin, + _In_ ACXDATAFORMAT DataFormat + ) +{ + PAGED_CODE(); + + UNREFERENCED_PARAMETER(Pin); + UNREFERENCED_PARAMETER(DataFormat); + + + return STATUS_NOT_SUPPORTED; +} + +PAGED_CODE_SEG +NTSTATUS +DSP_SendPropertyTo +( + _In_ WDFDEVICE Device, + _In_ ACXTARGETCIRCUIT TargetCircuit, + _In_ GUID PropertySet, + _In_ ULONG PropertyId, + _In_ ACX_PROPERTY_VERB Verb, + _In_ PVOID Control, + _In_ ULONG ControlCb, + _Inout_ PVOID Value, + _In_ ULONG ValueCb, + _Out_ ULONG_PTR* Information +) +{ + PAGED_CODE(); + + ACX_REQUEST_PARAMETERS requestParams; + ACX_REQUEST_PARAMETERS_INIT_PROPERTY( + &requestParams, + PropertySet, + PropertyId, + Verb, + AcxItemTypeCircuit, + 0, + Control, ControlCb, + Value, ValueCb + ); + + WDFREQUEST request; + WDF_OBJECT_ATTRIBUTES attributes; + WDF_OBJECT_ATTRIBUTES_INIT(&attributes); + attributes.ParentObject = Device; + RETURN_NTSTATUS_IF_FAILED(WdfRequestCreate(&attributes, AcxTargetCircuitGetWdfIoTarget(TargetCircuit), &request)); + + auto request_free = scope_exit([&request]() { + WdfObjectDelete(request); + }); + + RETURN_NTSTATUS_IF_FAILED(AcxTargetCircuitFormatRequestForProperty(TargetCircuit, request, &requestParams)); + + WDF_REQUEST_SEND_OPTIONS sendOptions; + WDF_REQUEST_SEND_OPTIONS_INIT(&sendOptions, WDF_REQUEST_SEND_OPTION_SYNCHRONOUS); + WDF_REQUEST_SEND_OPTIONS_SET_TIMEOUT(&sendOptions, WDF_REL_TIMEOUT_IN_SEC(5)); + + RETURN_NTSTATUS_IF_TRUE(!WdfRequestSend(request, AcxTargetCircuitGetWdfIoTarget(TargetCircuit), &sendOptions), STATUS_INVALID_DEVICE_REQUEST); + + NTSTATUS status = WdfRequestGetStatus(request); + if (Information) + { + *Information = WdfRequestGetInformation(request); + } + if (status == STATUS_BUFFER_OVERFLOW && ValueCb == 0) + { + // Don't trace this error, it's normal + return status; + } + + RETURN_NTSTATUS_IF_FAILED(status); + + return STATUS_SUCCESS; +} + +PAGED_CODE_SEG +NTSTATUS +DspR_AssignAggregatedDataPorts( + _In_ ACXCIRCUIT Circuit, + _In_ ACXPIN Pin + ) +{ + NTSTATUS status = STATUS_SUCCESS; + const ULONG MAX_EXPECTED_AGGREGATED_DEVICES = 16; + // There will be one data port in this array for each aggregated device. + ULONG dataPortPerFunction[MAX_EXPECTED_AGGREGATED_DEVICES]; + ULONG dataPortPerFunctionCount = 0; + struct DataPortMap + { + ULONG FunctionId; + ULONG DataPortNumber; + }; + + // This is an example of one way the Function ID could be used to determine which data port should be used + // Note that the order of the Audio Functions is not determistic. We will recalculate the data port array + // each time our circuit's pin is connected to the aggregator's pin. + // Note that until the pin connection is made there is no way to determine what order the audio functions + // will be indexed by. + // + // In most or all cases for real-world drivers, this information should be loaded from the ACPI audio composition + // tables as an array of mappings between Function ID and Data Port. In the case of conflicting Function IDs the + // streaming driver could also include FunctionManufacturerId when determining which data port to use. + DataPortMap dataPortMapping[] = + { + {0x6798, 0x1}, // Example Function ID of 6798 + {0x5037, 0x3}, // Example Function ID of 5037 + }; + + DSP_PIN_CONTEXT* pinCtx = GetDspPinContext(Pin); + PDSP_CIRCUIT_CONTEXT circuitCtx = GetDspCircuitContext(Circuit); + + PAGED_CODE(); + + if (circuitCtx->ConnectedFunctionInformation && + circuitCtx->ConnectedFunctionInformation->FunctionCount >= 1) + { + // The DataPortNumbers entry is required for aggregated systems that use different data port numbers for each + // connected audio function. + // The DataPortNumbers entry can also be used for non-aggregated systems, where the single value will be used + // instead of DPNo. + // For aggregated systems that use the same data port number for each connected audio function, DataPortNumbers + // must still have one entry for each audio function if it is used. + for (ULONG i = 0; i < circuitCtx->ConnectedFunctionInformation->FunctionCount; ++i) + { + // For each of the devices that's being aggregated, we will determine if we have a data port for the device + // in the mapping. If so, we will assign that data port to the device's index in the array of data ports we + // will add to the VarArguments for the stream bridge. + + for (ULONG mapIdx = 0; mapIdx < ARRAYSIZE(dataPortMapping); ++mapIdx) + { + if (dataPortMapping[mapIdx].FunctionId == circuitCtx->ConnectedFunctionInformation->FunctionInfoList[i].FunctionId) + { + // The Audio Function at index 'i' has the same Function Id as this mapping entry. + dataPortPerFunction[i] = dataPortMapping[mapIdx].DataPortNumber; + + // We want to ensure we have a data port in our map for each audio function + ++dataPortPerFunctionCount; + break; + } + } + } + } + + ASSERT((dataPortPerFunctionCount == 0) || (dataPortPerFunctionCount == circuitCtx->ConnectedFunctionInformation->FunctionCount)); + + if ((dataPortPerFunctionCount > 0) && (dataPortPerFunctionCount == circuitCtx->ConnectedFunctionInformation->FunctionCount)) + { + // The SdcaAggregator driver will override DPNo for each aggregated device with the value in that device's index in the + // DataPortNumbers array. + DECLARE_CONST_ACXOBJECTBAG_SOUNDWIRE_PROPERTY_NAME(DataPortNumbers); + WDFMEMORY dataPortMemory = nullptr; + + RETURN_NTSTATUS_IF_FAILED(WdfMemoryCreatePreallocated(nullptr, dataPortPerFunction, sizeof(ULONG)* dataPortPerFunctionCount, &dataPortMemory)); + auto dataPortMemory_free = scope_exit([&dataPortMemory]() + { + WdfObjectDelete(dataPortMemory); + }); + + // Add the DataPortNumbers to the AcxObjectBag that was assigned to the Stream Bridge during circuit creation. + RETURN_NTSTATUS_IF_FAILED(AcxObjectBagAddBlob(pinCtx->HostStreamObjBag, &DataPortNumbers, dataPortMemory)); + } + else if (dataPortPerFunctionCount > 0) + { + status = STATUS_DEVICE_CONFIGURATION_ERROR; + DrvLogError(g_SDCAVDspLog, FLAG_INFO, L"Found aggregated data port entry, but not for every audio function, %!STATUS!", status); + } + // If dataPortPerFunctionCount is 0, there aren't specific data ports per audio function and SdcaAggregator can leave DPNo as is for + // each of the different audio functions. + + return status; +} + +PAGED_CODE_SEG +NTSTATUS +DspR_AssignAggregatedPathDescriptors( + _In_ ACXCIRCUIT Circuit, + _In_ ACXPIN Pin +) +{ + WDFMEMORY descriptorsMemory = nullptr; + PSDCA_PATH_DESCRIPTORS2 descriptorsBuffer = nullptr; + + DSP_PIN_CONTEXT* pinCtx = GetDspPinContext(Pin); + PDSP_CIRCUIT_CONTEXT circuitCtx = GetDspCircuitContext(Circuit); + + PAGED_CODE(); + + if (circuitCtx->AggregatedPathDescriptors == nullptr) + { + return STATUS_SUCCESS; + } + + // Note that in the companion amp scenario some DSP drivers may not include aggregated path descriptor information + // for the companion amps. In that case, the connected function information count (which includes companions) + // will be more than the desciptor count. + if (!circuitCtx->ConnectedFunctionInformation || + circuitCtx->ConnectedFunctionInformation->FunctionCount < circuitCtx->AggregatedPathDescriptors->DescriptorCount) + { + return STATUS_SUCCESS; + } + + RETURN_NTSTATUS_IF_FAILED(WdfMemoryCreate(WDF_NO_OBJECT_ATTRIBUTES, NonPagedPoolNx, DRIVER_TAG, circuitCtx->AggregatedPathDescriptors->Size, &descriptorsMemory, (PVOID*)&descriptorsBuffer)); + auto free_memory = scope_exit([&descriptorsMemory]() + { + WdfObjectDelete(descriptorsMemory); + }); + + // Copy over entirely; we'll fix up the Function Information Id inplace + RtlCopyMemory(descriptorsBuffer, circuitCtx->AggregatedPathDescriptors, circuitCtx->AggregatedPathDescriptors->Size); + + ULONG fixedUpDescriptors = 0; + for (ULONG i = 0; i < circuitCtx->AggregatedPathDescriptors->DescriptorCount; ++i) + { + // For each of the aggregated devices, we need look it up by the UniqueID in the list of path descriptors + // we have. + for (ULONG connected = 0; connected < circuitCtx->ConnectedFunctionInformation->FunctionCount; ++connected) + { + // When saving the aggregated path descriptors, we stored the Function Info Unique ID in the descriptor's FunctionInformationId + // We use the Function Info Unique ID here to determine the correct FunctionInformationId for the aggregated device. + // The order of the aggregated devices can change depending on a lot of factors, so we need to use the Unique ID to get the right + // FunctionInformationId for each device. + if (circuitCtx->AggregatedPathDescriptors->Descriptor[i].FunctionInformationId == circuitCtx->ConnectedFunctionInformation->FunctionInfoList[connected].UniqueId) + { + descriptorsBuffer->Descriptor[i].FunctionInformationId = circuitCtx->ConnectedFunctionInformation->FunctionInfoList[connected].FunctionInformationId; + ++fixedUpDescriptors; + break; + } + } + } + + if (fixedUpDescriptors != descriptorsBuffer->DescriptorCount) + { + RETURN_NTSTATUS_IF_FAILED(STATUS_DEVICE_CONFIGURATION_ERROR); + } + + descriptorsBuffer->EndpointId = circuitCtx->EndpointId; + + // Add the DataPortNumbers to the AcxObjectBag that was assigned to the Stream Bridge during circuit creation. + RETURN_NTSTATUS_IF_FAILED(AcxObjectBagAddBlob(pinCtx->HostStreamObjBag, &SdcaPropertyPathDescriptors2, descriptorsMemory)); + + return STATUS_SUCCESS; +} + + + +// +// This callback is called when the Circuit bridge pin is connected to +// bridge pin of another circuit. +// +// This will happen when the composite circuit is fully initialized. +// From this point onwards the TargetCircuit can be used to send +// KSPROPERTY requests +// +// params: +// TargetCircuit - ACX wrapper for WDFIOTARGET for the connected circuit +// TargetPinId - The pin on the connected circuit. This can be used to +// send pin specific KSPROPERTY requests. +// +PAGED_CODE_SEG +VOID +DspR_EvtPinConnected ( + _In_ ACXPIN Pin, + _In_ ACXTARGETCIRCUIT TargetCircuit, + _In_ ULONG TargetPinId + ) +{ + PAGED_CODE(); + + DSP_PIN_CONTEXT *pinCtx; + pinCtx = GetDspPinContext(Pin); + pinCtx->TargetCircuit = TargetCircuit; + pinCtx->TargetPinId = TargetPinId; + + // The bridge pin should support the same formats that are supported by the downstream circuit + // We could also change the formats supported by the host pin here, but a DSP will typically determine + // those formats and do appropriate processing. + ACXPIN bridgePin = AcxCircuitGetPinById(AcxPinGetCircuit(Pin), DspPinTypeBridge); + NTSTATUS status = ReplicateFormatsForPin(bridgePin, TargetCircuit, TargetPinId); + if (!NT_SUCCESS(status)) + { + DrvLogError(g_SDCAVDspLog, FLAG_STREAM, L"Failed to replicate downstream formats to bridge pin, %!STATUS!", + status); + } + + ACXAUDIOENGINE audioEngine = GetDspCircuitContext(AcxPinGetCircuit(Pin))->AudioEngineElement; + status = ReplicateFormatsForAudioEngine(audioEngine, TargetCircuit, TargetPinId); + if (!NT_SUCCESS(status)) + { + DrvLogError(g_SDCAVDspLog, FLAG_STREAM, L"Failed to replicate downstream formats to audio engine, %!STATUS!", + status); + } + + // The ACX framework will maintain the TargetCircuit until after it's called EvtPinDisconnected + + ACXCIRCUIT circuit = AcxPinGetCircuit(Pin); + status = FindDownstreamVolumeMute(circuit, TargetCircuit); + if (!NT_SUCCESS(status)) + { + DrvLogWarning(g_SDCAVDspLog, FLAG_INIT, L"Unable to find downstream volume/mute elements. Volume and Mute forwarding will be disabled. %!STATUS!", status); + } + + PDSP_CIRCUIT_CONTEXT circuitCtx = GetDspCircuitContext(circuit); + + // Preallocate enough room to hold information for maximum expected aggregated devices. + struct AggregationDevices + { + SDCA_AGGREGATION_DEVICES Devices; + SDCA_AGGREGATION_DEVICE DeviceExtra[MAX_AGGREGATED_DEVICES-1]; + }; + AggregationDevices aggDevices{ 0 }; + aggDevices.Devices.Size = sizeof(aggDevices); + // The DSP driver should know from the ACPI composition tables whether + // this circuit is connected to an aggregated endpoint. However, in the + // meantime, we will just ask the target circuit. + status = DSP_SendPropertyTo( + AcxCircuitGetWdfDevice(circuit), + TargetCircuit, + KSPROPERTYSETID_SdcaAgg, + KSPROPERTY_SDCAAGG_AGGREGATED_DEVICES, + AcxPropertyVerbGet, + nullptr, 0, + &aggDevices, + sizeof(aggDevices), + nullptr + ); + + if (NT_SUCCESS(status)) + { + circuitCtx->Aggregated = TRUE; + circuitCtx->AggregatedDeviceCount = aggDevices.Devices.FunctionCount; + for (ULONG i = 0; i < aggDevices.Devices.FunctionCount; ++i) + { + RtlCopyMemory(&circuitCtx->AggregatedDevices[i], &aggDevices.Devices.FunctionIds[i], sizeof(SDCA_AGGREGATION_DEVICE)); + } + } + + // Delete previous ConnectedFunctionInformation if any is already allocated + if (circuitCtx->ConnectedFunctionInformation) + { + ExFreePool(circuitCtx->ConnectedFunctionInformation); + circuitCtx->ConnectedFunctionInformation = nullptr; + } + + ULONG_PTR requiredBufferSize; + + // retrieve the function information for this device. We'll use this information if the device + // has special stream capabilities. + // We'll also use this information if this is an aggregated device that has different Data Port requirements for the audio functions. + status = DSP_SendPropertyTo( + AcxCircuitGetWdfDevice(circuit), + TargetCircuit, + KSPROPERTYSETID_Sdca, + KSPROPERTY_SDCA_FUNCTION_INFORMATION, + AcxPropertyVerbGet, + nullptr, 0, + nullptr, 0, + &requiredBufferSize); + + if (status == STATUS_BUFFER_OVERFLOW && + requiredBufferSize >= sizeof(SDCA_FUNCTION_INFORMATION_LIST)) + { + circuitCtx->ConnectedFunctionInformation = (PSDCA_FUNCTION_INFORMATION_LIST)ExAllocatePool2(POOL_FLAG_NON_PAGED, requiredBufferSize, DRIVER_TAG); + if (!circuitCtx->ConnectedFunctionInformation) + { + return; + } + + status = DSP_SendPropertyTo( + AcxCircuitGetWdfDevice(circuit), + TargetCircuit, + KSPROPERTYSETID_Sdca, + KSPROPERTY_SDCA_FUNCTION_INFORMATION, + AcxPropertyVerbGet, + nullptr, 0, + circuitCtx->ConnectedFunctionInformation, (ULONG)requiredBufferSize, + nullptr); + } + + if (circuitCtx->ConnectedFunctionInformation && circuitCtx->ConnectedFunctionInformation->FunctionCount > 1) + { + // Since FunctionCount is > 1, this is an aggregated system. As such we should update the stream bridge's VarArguments Bag + // to include a list of data ports based on the devices being aggregated. + // This is necessary if the aggregated audio functions are not uniform and use different data ports for their inputs. + status = DspR_AssignAggregatedDataPorts(circuit, Pin); + if (!NT_SUCCESS(status)) + { + DrvLogWarning(g_SDCAVDspLog, FLAG_INIT, L"Unable to assign data ports for aggregated connection. %!STATUS!", status); + } + + // To specify channel mask or more information, the path descriptors structure needs to be used + status = DspR_AssignAggregatedPathDescriptors(circuit, Pin); + if (!NT_SUCCESS(status)) + { + DrvLogWarning(g_SDCAVDspLog, FLAG_INIT, L"Unable to assign path descriptors for aggregated connection. %!STATUS!", status); + } + } + + // retrieve the special stream capabilities for the downstream device + status = DSP_SendPropertyTo( + AcxCircuitGetWdfDevice(circuit), + TargetCircuit, + KSPROPERTYSETID_Sdca, + KSPROPERTY_SDCA_FUNCTION_CAPABILITY, + AcxPropertyVerbGet, + NULL, 0, + &circuitCtx->SpecialStreamAvailablePaths, sizeof(SDCA_PATH), + nullptr); + + if (NT_SUCCESS(status)) + { + // we have path information for a target circuit which supports + // special paths. collect/refresh our cached information. + if (circuitCtx->SpecialStreamTargetCircuit) + { + // ACX will not call EvtPinConnected more than once without + // calling EvtPinDisconnected between, so SpecialStreamTargetCircuit + // should be NULL here. + ASSERT(FALSE); + } + + // Since we'll clean this up in EvtPinDisconnected we do not + // need to perform WdfObjectReference on the TargetCircuit here. + circuitCtx->SpecialStreamTargetCircuit = TargetCircuit; + + for(ULONG i = (UINT) SpecialStreamTypeUltrasoundRender; i < (UINT) SpecialStreamType_Count; i++) + { + if (circuitCtx->SpecialStreamPathDescriptors[i]) + { + ExFreePool(circuitCtx->SpecialStreamPathDescriptors[i]); + circuitCtx->SpecialStreamPathDescriptors[i] = nullptr; + } + } + } + + // go through the capabilities and query each that is supported for the descriptors + for(ULONG i = (UINT) SpecialStreamTypeUltrasoundRender; i < (UINT) SpecialStreamType_Count; i++) + { + SDCA_PATH currentPath = SdcaPathFromSpecialStreamType((SDCA_SPECIALSTREAM_TYPE) i); + + if ((circuitCtx->SpecialStreamAvailablePaths & currentPath) != 0) + { + // The descriptor is a variable length structure, so + // we need to first determine the size required + status = DSP_SendPropertyTo( + AcxCircuitGetWdfDevice(circuit), + TargetCircuit, + KSPROPERTYSETID_Sdca, + KSPROPERTY_SDCA_PATH_DESCRIPTORS, + AcxPropertyVerbGet, + ¤tPath, sizeof(SDCA_PATH), + nullptr, 0, + &requiredBufferSize); + + // buffer overflow indicates that the descriptorSize has been filled in with + // the required buffer size. It should be at least a SDCA_PATH_DESCRIPTORS worth + // of data, more depending on formats supported. + if (status == STATUS_BUFFER_OVERFLOW && + requiredBufferSize >= sizeof(SDCA_PATH_DESCRIPTORS)) + { + // now that we know the size, allocate and retrieve it. + circuitCtx->SpecialStreamPathDescriptors[i] = (PSDCA_PATH_DESCRIPTORS) ExAllocatePool2(POOL_FLAG_NON_PAGED, requiredBufferSize, DRIVER_TAG); + if (circuitCtx->SpecialStreamPathDescriptors[i]) + { + status = DSP_SendPropertyTo( + AcxCircuitGetWdfDevice(circuit), + TargetCircuit, + KSPROPERTYSETID_Sdca, + KSPROPERTY_SDCA_PATH_DESCRIPTORS, + AcxPropertyVerbGet, + ¤tPath, sizeof(SDCA_PATH), + circuitCtx->SpecialStreamPathDescriptors[i], (ULONG) requiredBufferSize, + nullptr); + } + } + } + } +} + +// +// This callback is called when the Circuit bridge pin is disconnected +// from the bridge pin of another circuit. +// +// This will happen when the composite circuit is deinitialized. +// From this point onwards the TargetCircuit cannnot be used to send +// KSPROPERTY requests. +// TargetCircuit should only be used to access the attached context. +// +// params: +// TargetCircuit - ACX wrapper for WDFIOTARGET for the connected circuit +// TargetPinId - The pin on the connected circuit. +// +PAGED_CODE_SEG +VOID +DspR_EvtPinDisconnected ( + _In_ ACXPIN Pin, + _In_ ACXTARGETCIRCUIT TargetCircuit, + _In_ ULONG TargetPinId + ) +{ + PAGED_CODE(); + + UNREFERENCED_PARAMETER(TargetPinId); + UNREFERENCED_PARAMETER(TargetCircuit); + + DSP_PIN_CONTEXT *pinCtx; + pinCtx = GetDspPinContext(Pin); + + // We cannot use the TargetCircuit after returning from EvtPinDisconnected + if (pinCtx->TargetCircuit) + { + pinCtx->TargetCircuit = NULL; + pinCtx->TargetPinId = (ULONG)(-1); + } + + ACXCIRCUIT circuit = AcxPinGetCircuit(Pin); + PDSP_CIRCUIT_CONTEXT circuitCtx = GetDspCircuitContext(circuit); + if (circuitCtx->TargetVolumeMuteCircuit) + { + circuitCtx->TargetMuteHandler = nullptr; + circuitCtx->TargetVolumeHandler = nullptr; + circuitCtx->TargetVolumeMuteCircuit = nullptr; + } + if (circuitCtx->TargetCircuitToDelete) + { + WdfObjectDelete(circuitCtx->TargetCircuitToDelete); + circuitCtx->TargetCircuitToDelete = nullptr; + } + + circuitCtx->SpecialStreamAvailablePaths = 0; + + for(ULONG i = (UINT) SpecialStreamTypeUltrasoundRender; i < (UINT) SpecialStreamType_Count; i++) + { + if (circuitCtx->SpecialStreamPathDescriptors[i]) + { + ExFreePool(circuitCtx->SpecialStreamPathDescriptors[i]); + circuitCtx->SpecialStreamPathDescriptors[i] = nullptr; + } + } + + if (circuitCtx->SpecialStreamTargetCircuit) + { + circuitCtx->SpecialStreamTargetCircuit = nullptr; + } + + if (circuitCtx->ConnectedFunctionInformation) + { + ExFreePool(circuitCtx->ConnectedFunctionInformation); + circuitCtx->ConnectedFunctionInformation = nullptr; + } +} + +PAGED_CODE_SEG +NTSTATUS +DspR_EvtDevicePrepareHardware( + _In_ WDFDEVICE Device, + _In_ WDFCMRESLIST ResourceList, + _In_ WDFCMRESLIST ResourceListTranslated + ) +/*++ + +Routine Description: + + In this callback, the driver does whatever is necessary to make the + hardware ready to use. + +Arguments: + + Device - handle to a device + +Return Value: + + NT status value + +--*/ +{ + NTSTATUS status = STATUS_SUCCESS; + PDSP_RENDER_DEVICE_CONTEXT devCtx; + + UNREFERENCED_PARAMETER(ResourceList); + UNREFERENCED_PARAMETER(ResourceListTranslated); + + PAGED_CODE(); + + devCtx = GetRenderDeviceContext(Device); + ASSERT(devCtx != NULL); + + DrvLogInfo(g_SDCAVDspLog, FLAG_STREAM, L"SDCA VDSP %p Prepare Hardware, First Time %d", Device, devCtx->FirstTimePrepareHardware); + + if (!devCtx->FirstTimePrepareHardware) + { + // + // This is a rebalance. Validate the circuit resources and + // if needed, delete and re-create the circuit. + // The sample driver doens't use resources, thus the existing + // circuits are kept. + // + status = STATUS_SUCCESS; + return status; + } + + // + // Set child's power policy. + // + RETURN_NTSTATUS_IF_FAILED(DspR_SetPowerPolicy(Device)); + + // + // Add circuit to child's list. + // + RETURN_NTSTATUS_IF_FAILED(AcxDeviceAddCircuit(Device, devCtx->Circuit)); + + // + // Keep track this is not the first time this callback was called. + // + devCtx->FirstTimePrepareHardware = FALSE; + + return status; +} + +PAGED_CODE_SEG +NTSTATUS +DspR_EvtDeviceReleaseHardware( + _In_ WDFDEVICE Device, + _In_ WDFCMRESLIST ResourceListTranslated + ) +/*++ + +Routine Description: + + In this callback, the driver releases the h/w resources allocated in the + prepare h/w callback. + +Arguments: + + Device - handle to a device + +Return Value: + + NT status value + +--*/ +{ + NTSTATUS status; + PDSP_RENDER_DEVICE_CONTEXT devCtx; + + UNREFERENCED_PARAMETER(ResourceListTranslated); + + PAGED_CODE(); + + devCtx = GetRenderDeviceContext(Device); + ASSERT(devCtx != NULL); + + DrvLogInfo(g_SDCAVDspLog, FLAG_STREAM, L"SDCA VDSP %p Release Hardware", Device); + + status = STATUS_SUCCESS; + + return status; +} + +PAGED_CODE_SEG +NTSTATUS +DspR_EvtDeviceSelfManagedIoInit( + _In_ WDFDEVICE Device + ) +/*++ + +Routine Description: + + In this callback, the driver does one-time init of self-managed I/O data. + +Arguments: + + Device - handle to a device + +Return Value: + + NT status value + +--*/ +{ + PDSP_RENDER_DEVICE_CONTEXT devCtx; + + PAGED_CODE(); + + devCtx = GetRenderDeviceContext(Device); + ASSERT(devCtx != NULL); + + return STATUS_SUCCESS; +} + +#pragma code_seg() +VOID +DspR_EvtDeviceContextCleanup( + _In_ WDFOBJECT WdfDevice + ) +/*++ + +Routine Description: + + In this callback, it cleans up device context. + +Arguments: + + WdfDevice - WDF device object + +Return Value: + + NULL + +--*/ +{ + WDFDEVICE device; + PDSP_RENDER_DEVICE_CONTEXT devCtx; + + device = (WDFDEVICE)WdfDevice; + devCtx = GetRenderDeviceContext(device); + ASSERT(devCtx != NULL); + + DrvLogInfo(g_SDCAVDspLog, FLAG_STREAM, L"SDCA VDSP Device Cleanup %p", WdfDevice); +} + +#pragma code_seg() +VOID +DspR_EvtCircuitContextCleanup( + _In_ WDFOBJECT Circuit + ) +/*++ + +Routine Description: + + In this callback, it cleans up circuit context. + +Arguments: + + WdfDevice - WDF device object + +Return Value: + + NULL + +--*/ +{ + PDSP_CIRCUIT_CONTEXT circuitCtx; + + circuitCtx = GetDspCircuitContext(Circuit); + ASSERT(circuitCtx != NULL); + + if (circuitCtx->peakMeter) + { + CSimPeakMeter* peakMeter = (CSimPeakMeter *)circuitCtx->peakMeter; + delete peakMeter; + circuitCtx->peakMeter = NULL; + } + + // clean up the path context information in case it wasn't cleaned up + // by pin disconnection. + circuitCtx->SpecialStreamAvailablePaths = 0; + + for(ULONG i = (UINT) SpecialStreamTypeUltrasoundRender; i < (UINT) SpecialStreamType_Count; i++) + { + if (circuitCtx->SpecialStreamPathDescriptors[i]) + { + ExFreePool(circuitCtx->SpecialStreamPathDescriptors[i]); + circuitCtx->SpecialStreamPathDescriptors[i] = nullptr; + } + } + + for (ULONG i = (UINT)SpecialStreamTypeUltrasoundRender; i < (UINT)SpecialStreamType_Count; i++) + { + if (circuitCtx->SpecialStreamPathDescriptors2[i]) + { + ExFreePool(circuitCtx->SpecialStreamPathDescriptors2[i]); + circuitCtx->SpecialStreamPathDescriptors2[i] = nullptr; + } + } + + if (circuitCtx->SpecialStreamTargetCircuit) + { + circuitCtx->SpecialStreamTargetCircuit = nullptr; + } + + if (circuitCtx->ConnectedFunctionInformation) + { + ExFreePool(circuitCtx->ConnectedFunctionInformation); + circuitCtx->ConnectedFunctionInformation = nullptr; + } + + if (circuitCtx->AggregatedPathDescriptors) + { + ExFreePool(circuitCtx->AggregatedPathDescriptors); + circuitCtx->AggregatedPathDescriptors = nullptr; + } + + DrvLogInfo(g_SDCAVDspLog, FLAG_STREAM, L"SDCA VDSP Circuit Cleanup %p", Circuit); +} + +#pragma code_seg() +VOID +DspR_EvtPinContextCleanup( + _In_ WDFOBJECT WdfPin + ) +/*++ + +Routine Description: + + In this callback, it cleans up pin context. + +Arguments: + + WdfDevice - WDF device object + +Return Value: + + NULL + +--*/ +{ + DSP_PIN_CONTEXT *pinCtx; + pinCtx = GetDspPinContext(WdfPin); + + if (pinCtx->TargetCircuit) + { + pinCtx->TargetCircuit = NULL; + pinCtx->TargetPinId = (ULONG)(-1); + } +} + +#pragma code_seg() +VOID +DspR_EvtCircuitRequestPreprocess( + _In_ ACXOBJECT Object, + _In_ ACXCONTEXT DriverContext, + _In_ WDFREQUEST Request +) +/*++ + +Routine Description: + + This function is an example of a preprocess routine. + +--*/ +{ + CircuitRequestPreprocess(Object, DriverContext, Request); +} + +PAGED_CODE_SEG +VOID +DspR_EvtStreamRequestPreprocess( + _In_ ACXOBJECT Object, + _In_ ACXCONTEXT DriverContext, + _In_ WDFREQUEST Request + ) +/*++ + +Routine Description: + + This function is an example of a preprocess routine. + +--*/ +{ + PAGED_CODE(); + + UNREFERENCED_PARAMETER(DriverContext); + + ASSERT(Object != NULL); + ASSERT(DriverContext); + ASSERT(Request); + + + // + // Just give the request back to ACX. + // + (VOID)AcxStreamDispatchAcxRequest((ACXSTREAM)Object, Request); +} + +PAGED_CODE_SEG +NTSTATUS +DspR_SetPowerPolicy( + _In_ WDFDEVICE Device +) +{ + PAGED_CODE(); + + NTSTATUS status = STATUS_SUCCESS; + + // + // Init the idle policy structure. + // + WDF_DEVICE_POWER_POLICY_IDLE_SETTINGS idleSettings; + WDF_DEVICE_POWER_POLICY_IDLE_SETTINGS_INIT(&idleSettings, IdleCannotWakeFromS0); + idleSettings.IdleTimeout = 10000; // 10-sec + + RETURN_NTSTATUS_IF_FAILED(WdfDeviceAssignS0IdleSettings(Device, &idleSettings)); + + return status; +} + +PAGED_CODE_SEG +NTSTATUS DspR_EvtAcxFactoryCircuitCreateCircuitDevice( + _In_ WDFDEVICE Parent, + _In_ ACXFACTORYCIRCUIT Factory, + _In_ PACX_FACTORY_CIRCUIT_ADD_CIRCUIT CircuitConfig, + _Out_ WDFDEVICE * Device +) +{ + PAGED_CODE(); + + NTSTATUS status = STATUS_SUCCESS; + + WDF_OBJECT_ATTRIBUTES attributes; + + UNREFERENCED_PARAMETER(Factory); + + *Device = NULL; + + // Allocate a generic buffer to hold a PnP ID of this device. + // MAX_DEVICE_ID_LEN is the count of wchar in the device ID name. + C_ASSERT(NTSTRSAFE_UNICODE_STRING_MAX_CCH >= MAX_DEVICE_ID_LEN); + C_ASSERT(USHORT_MAX >= MAX_DEVICE_ID_LEN * sizeof(WCHAR)); + WCHAR *wstrBuffer = NULL; + const USHORT wstrBufferCch = MAX_DEVICE_ID_LEN; + wstrBuffer = new(POOL_FLAG_NON_PAGED, DRIVER_TAG) WCHAR[wstrBufferCch]; + RETURN_NTSTATUS_IF_TRUE(NULL == wstrBuffer, STATUS_INSUFFICIENT_RESOURCES); + auto wstrBuffer_free = scope_exit([&wstrBuffer](){ + delete [] wstrBuffer; + }); + + RtlZeroMemory(wstrBuffer, sizeof(WCHAR) * wstrBufferCch); + + // + // Create a child audio device for this circuit. + // + PWDFDEVICE_INIT devInit = NULL; + devInit = WdfPdoInitAllocate(Parent); + RETURN_NTSTATUS_IF_TRUE(NULL == devInit, STATUS_INSUFFICIENT_RESOURCES); + auto devInit_free = scope_exit([&devInit]() { + WdfDeviceInitFree(devInit); + }); + + // + // Provide DeviceID, HardwareIDs, CompatibleIDs and InstanceId + // + + // + // Create the PnP Device ID. + // + // Retrieve the unique id of this composite. This logic uses this unique id to + // make the device id unique. Using a deterministic value for the pnp device id, guarantees + // that the KS properties associated with this audio device interface stay the same across + // reboots, even when the circuit factory is used in several ACX composites. + // + { + GUID uniqueId = { 0 }; + UNICODE_STRING uniqueIdStr = { 0 }; + UNICODE_STRING pnpDeviceId = { 0 }; + ACX_OBJECTBAG_CONFIG objBagCfg; + + DECLARE_CONST_ACXOBJECTBAG_SYSTEM_PROPERTY_NAME(UniqueID); + + ACX_OBJECTBAG_CONFIG_INIT(&objBagCfg); + objBagCfg.Handle = CircuitConfig->CompositeProperties; + objBagCfg.Flags |= AcxObjectBagConfigOpenWithHandle; + + WDF_OBJECT_ATTRIBUTES_INIT(&attributes); + ACXOBJECTBAG objBag = NULL; + + RETURN_NTSTATUS_IF_FAILED(AcxObjectBagOpen(&attributes, &objBagCfg, &objBag)); + auto objBag_free = scope_exit([&objBag]() { + WdfObjectDelete(objBag); + }); + + RETURN_NTSTATUS_IF_FAILED(AcxObjectBagRetrieveGuid(objBag, &UniqueID, &uniqueId)); + + RETURN_NTSTATUS_IF_FAILED(RtlStringFromGUID(uniqueId, &uniqueIdStr)); + + // Init the deviceId unicode string. + pnpDeviceId.Buffer = wstrBuffer; + pnpDeviceId.Length = 0; + pnpDeviceId.MaximumLength = (USHORT)(sizeof(WCHAR) * wstrBufferCch); + + status = RtlUnicodeStringPrintf(&pnpDeviceId, RENDER_DEVICE_ID_STR, &uniqueIdStr); + + RtlFreeUnicodeString(&uniqueIdStr); + + RETURN_NTSTATUS_IF_FAILED(status); + + // This is the device ID and the first H/W ID. + // This ID is used to create a unique audio device interface. + // Note that this ID is NOT the match with this driver's INF. + RETURN_NTSTATUS_IF_FAILED(WdfPdoInitAssignDeviceID(devInit, &pnpDeviceId)); + + RETURN_NTSTATUS_IF_FAILED(WdfPdoInitAddHardwareID(devInit, &pnpDeviceId)); + } + + // This H/W ID is the match with this driver's INF. + RETURN_NTSTATUS_IF_FAILED(WdfPdoInitAddHardwareID(devInit, &RenderHardwareId)); + + /* + RETURN_NTSTATUS_IF_FAILED(WdfPdoInitAddCompatibleID(devInit, &RenderCompatibleId)); + + RETURN_NTSTATUS_IF_FAILED(WdfPdoInitAssignInstanceID(devInit, &RenderInstanceId)); + + RETURN_NTSTATUS_IF_FAILED(WdfPdoInitAssignContainerID(devInit, &RenderContainerId)); + + // + // You can call WdfPdoInitAddDeviceText multiple times, adding device + // text for multiple locales. When the system displays the text, it + // chooses the text that matches the current locale, if available. + // Otherwise it will use the string for the default locale. + // The driver can specify the driver's default locale by calling + // WdfPdoInitSetDefaultLocale. + // + RETURN_NTSTATUS_IF_FAILED(WdfPdoInitAddDeviceText(devInit, + &RenderDeviceLocation, + &RenderDeviceLocation, + 0x409)); + */ + + WdfPdoInitSetDefaultLocale(devInit, 0x409); + + // + // Allow ACX to add any pre-requirement it needs on this device. + // + ACX_DEVICEINIT_CONFIG devInitCfg; + ACX_DEVICEINIT_CONFIG_INIT(&devInitCfg); + devInitCfg.Flags |= AcxDeviceInitConfigRawDevice; + RETURN_NTSTATUS_IF_FAILED(AcxDeviceInitInitialize(devInit, &devInitCfg)); + + // + // Initialize the pnpPowerCallbacks structure. Callback events for PNP + // and Power are specified here. If you don't supply any callbacks, + // the Framework will take appropriate default actions based on whether + // DeviceInit is initialized to be an FDO, a PDO or a filter device + // object. + // + WDF_PNPPOWER_EVENT_CALLBACKS pnpPowerCallbacks; + WDF_PNPPOWER_EVENT_CALLBACKS_INIT(&pnpPowerCallbacks); + pnpPowerCallbacks.EvtDevicePrepareHardware = DspR_EvtDevicePrepareHardware; + pnpPowerCallbacks.EvtDeviceReleaseHardware = DspR_EvtDeviceReleaseHardware; + pnpPowerCallbacks.EvtDeviceSelfManagedIoInit = DspR_EvtDeviceSelfManagedIoInit; + WdfDeviceInitSetPnpPowerEventCallbacks(devInit, &pnpPowerCallbacks); + + // + // Specify a context for this render device. + // + WDF_OBJECT_ATTRIBUTES_INIT_CONTEXT_TYPE(&attributes, DSP_RENDER_DEVICE_CONTEXT); + attributes.EvtCleanupCallback = DspR_EvtDeviceContextCleanup; + attributes.ExecutionLevel = WdfExecutionLevelPassive; + + WDFDEVICE device = NULL; + RETURN_NTSTATUS_IF_FAILED(WdfDeviceCreate(&devInit, &attributes, &device)); + + devInit_free.release(); + + // + // Init render's device context. + // + PDSP_RENDER_DEVICE_CONTEXT devCtx; + devCtx = GetRenderDeviceContext(device); + ASSERT(devCtx != NULL); + + // + // Set device capabilities. + // + { + WDF_DEVICE_PNP_CAPABILITIES pnpCaps; + WDF_DEVICE_PNP_CAPABILITIES_INIT(&pnpCaps); + + pnpCaps.SurpriseRemovalOK = WdfTrue; + pnpCaps.UniqueID = WdfFalse; + + WdfDeviceSetPnpCapabilities(device, &pnpCaps); + } + + // + // Allow ACX to add any post-requirement it needs on this device. + // + ACX_DEVICE_CONFIG devCfg; + ACX_DEVICE_CONFIG_INIT(&devCfg); + RETURN_NTSTATUS_IF_FAILED(AcxDeviceInitialize(device, &devCfg)); + + DrvLogInfo(g_SDCAVDspLog, FLAG_STREAM, L"SDCA VDSP Parent %p Create Circuit Device %p", Parent, device); + + *Device = device; + + return status; +} + + +// {3CE41646-9BF2-4A9E-B851-D711CAE9AEA8} +DEFINE_GUID(SDCAVADPropsetId, + 0x3ce41646, 0x9bf2, 0x4a9e, 0xb8, 0x51, 0xd7, 0x11, 0xca, 0xe9, 0xae, 0xa8); + +typedef enum { + SDCAVAD_PROPERTY_TEST1, + SDCAVAD_PROPERTY_TEST2, + SDCAVAD_PROPERTY_TEST3, + SDCAVAD_PROPERTY_TEST4, + SDCAVAD_PROPERTY_TEST5, + SDCAVAD_PROPERTY_TEST6, +} SDCAVAD_Properties; + + +#pragma code_seg("PAGE") +NTSTATUS +DspR_EvtProcessCommand0( + _In_ ACXAUDIOMODULE AudioModule, + _In_ PVOID InBuffer, + _In_ ULONG InBufferCb, + _In_ PVOID OutBuffer, + _Inout_ PULONG OutBufferCb + ) +{ + BOOL fNewValue = FALSE; + PVOID currentValue = nullptr; + PVOID inBuffer = nullptr; + ULONG inBufferCb = 0; + PDSP_AUDIOMODULE0_CONTEXT audioModuleCtx; + AUDIOMODULE_PARAMETER_INFO * parameterInfo = nullptr; + AUDIOMODULE_CUSTOM_COMMAND * command = nullptr; + + PAGED_CODE(); + + audioModuleCtx = GetDspAudioModule0Context(AudioModule); + RETURN_NTSTATUS_IF_TRUE(nullptr == audioModuleCtx, STATUS_INTERNAL_ERROR); + + // + // Basic parameter validation (module specific). + // + RETURN_NTSTATUS_IF_TRUE(InBuffer == nullptr || InBufferCb == 0, STATUS_INVALID_PARAMETER); + RETURN_NTSTATUS_IF_TRUE(InBufferCb < sizeof(AUDIOMODULE_CUSTOM_COMMAND), STATUS_INVALID_PARAMETER); + + command = (AUDIOMODULE_CUSTOM_COMMAND*)InBuffer; + + RETURN_NTSTATUS_IF_TRUE(command->ParameterId >= SIZEOF_ARRAY(AudioModule0_ParameterInfo), STATUS_INVALID_PARAMETER); + + // + // Validate the parameter referenced in the command. + // + switch (command->ParameterId) + { + case AudioModuleParameter1: + currentValue = &audioModuleCtx->Parameter1; + parameterInfo = &AudioModule0_ParameterInfo[AudioModuleParameter1]; + break; + case AudioModuleParameter2: + currentValue = &audioModuleCtx->Parameter2; + parameterInfo = &AudioModule0_ParameterInfo[AudioModuleParameter2]; + break; + default: + RETURN_NTSTATUS(STATUS_INVALID_PARAMETER); + } + + // + // Update input buffer ptr/size. + // + inBuffer = (PVOID)((ULONG_PTR)InBuffer + sizeof(AUDIOMODULE_CUSTOM_COMMAND)); + inBufferCb = InBufferCb - sizeof(AUDIOMODULE_CUSTOM_COMMAND); + + if (inBufferCb == 0) + { + inBuffer = nullptr; + } + + RETURN_NTSTATUS_IF_FAILED(AudioModule_GenericHandler( + command->Verb, + command->ParameterId, + parameterInfo, + currentValue, + inBuffer, + inBufferCb, + OutBuffer, + OutBufferCb, + &fNewValue)); + + if (fNewValue && + (parameterInfo->Flags & AUDIOMODULE_PARAMETER_FLAG_CHANGE_NOTIFICATION)) + { + AUDIOMODULE_CUSTOM_NOTIFICATION customNotification = {0}; + + customNotification.Type = AudioModuleParameterChanged; + customNotification.ParameterChanged.ParameterId = command->ParameterId; + + RETURN_NTSTATUS_IF_FAILED(AcxPnpEventGenerateEvent(audioModuleCtx->Event, &customNotification, (USHORT)sizeof(customNotification))); + } + + return STATUS_SUCCESS; +} + +#pragma code_seg("PAGE") +NTSTATUS +DspR_EvtProcessCommand1( + _In_ ACXAUDIOMODULE AudioModule, + _In_ PVOID InBuffer, + _In_ ULONG InBufferCb, + _In_ PVOID OutBuffer, + _Inout_ PULONG OutBufferCb + ) +{ + BOOL fNewValue = FALSE; + PVOID currentValue = nullptr; + PVOID inBuffer = nullptr; + ULONG inBufferCb = 0; + PDSP_AUDIOMODULE1_CONTEXT audioModuleCtx; + AUDIOMODULE_PARAMETER_INFO * parameterInfo = nullptr; + AUDIOMODULE_CUSTOM_COMMAND * command = nullptr; + + PAGED_CODE(); + + audioModuleCtx = GetDspAudioModule1Context(AudioModule); + RETURN_NTSTATUS_IF_TRUE(nullptr == audioModuleCtx, STATUS_INTERNAL_ERROR); + + // + // Basic parameter validation (module specific). + // + RETURN_NTSTATUS_IF_TRUE(InBuffer == nullptr || InBufferCb == 0, STATUS_INVALID_PARAMETER); + RETURN_NTSTATUS_IF_TRUE(InBufferCb < sizeof(AUDIOMODULE_CUSTOM_COMMAND), STATUS_INVALID_PARAMETER); + + command = (AUDIOMODULE_CUSTOM_COMMAND*)InBuffer; + + RETURN_NTSTATUS_IF_TRUE(command->ParameterId >= SIZEOF_ARRAY(AudioModule1_ParameterInfo), STATUS_INVALID_PARAMETER); + + // + // Validate the parameter referenced in the command. + // + switch (command->ParameterId) + { + case AudioModuleParameter1: + currentValue = &audioModuleCtx->Parameter1; + parameterInfo = &AudioModule1_ParameterInfo[AudioModuleParameter1]; + break; + case AudioModuleParameter2: + currentValue = &audioModuleCtx->Parameter2; + parameterInfo = &AudioModule1_ParameterInfo[AudioModuleParameter2]; + break; + case AudioModuleParameter3: + currentValue = &audioModuleCtx->Parameter3; + parameterInfo = &AudioModule1_ParameterInfo[AudioModuleParameter3]; + break; + default: + RETURN_NTSTATUS(STATUS_INVALID_PARAMETER); + } + + // + // Update input buffer ptr/size. + // + inBuffer = (PVOID)((ULONG_PTR)InBuffer + sizeof(AUDIOMODULE_CUSTOM_COMMAND)); + inBufferCb = InBufferCb - sizeof(AUDIOMODULE_CUSTOM_COMMAND); + + if (inBufferCb == 0) + { + inBuffer = nullptr; + } + + RETURN_NTSTATUS_IF_FAILED(AudioModule_GenericHandler( + command->Verb, + command->ParameterId, + parameterInfo, + currentValue, + inBuffer, + inBufferCb, + OutBuffer, + OutBufferCb, + &fNewValue)); + + if (fNewValue && + (parameterInfo->Flags & AUDIOMODULE_PARAMETER_FLAG_CHANGE_NOTIFICATION)) + { + AUDIOMODULE_CUSTOM_NOTIFICATION customNotification = {0}; + + customNotification.Type = AudioModuleParameterChanged; + customNotification.ParameterChanged.ParameterId = command->ParameterId; + + RETURN_NTSTATUS_IF_FAILED(AcxPnpEventGenerateEvent(audioModuleCtx->Event, &customNotification, (USHORT)sizeof(customNotification))); + } + + return STATUS_SUCCESS; +} + +#pragma code_seg("PAGE") +NTSTATUS +DspR_EvtProcessCommand2( + _In_ ACXAUDIOMODULE AudioModule, + _In_ PVOID InBuffer, + _In_ ULONG InBufferCb, + _In_ PVOID OutBuffer, + _Inout_ PULONG OutBufferCb + ) +{ + BOOL fNewValue = FALSE; + PVOID currentValue = nullptr; + PVOID inBuffer = nullptr; + ULONG inBufferCb = 0; + PDSP_AUDIOMODULE2_CONTEXT audioModuleCtx; + AUDIOMODULE_PARAMETER_INFO * parameterInfo = nullptr; + AUDIOMODULE_CUSTOM_COMMAND * command = nullptr; + + PAGED_CODE(); + + audioModuleCtx = GetDspAudioModule2Context(AudioModule); + RETURN_NTSTATUS_IF_TRUE(nullptr == audioModuleCtx, STATUS_INTERNAL_ERROR); + + // + // Basic parameter validation (module specific). + // + RETURN_NTSTATUS_IF_TRUE(InBuffer == nullptr || InBufferCb == 0, STATUS_INVALID_PARAMETER); + RETURN_NTSTATUS_IF_TRUE(InBufferCb < sizeof(AUDIOMODULE_CUSTOM_COMMAND), STATUS_INVALID_PARAMETER); + + command = (AUDIOMODULE_CUSTOM_COMMAND*)InBuffer; + + RETURN_NTSTATUS_IF_TRUE(command->ParameterId >= SIZEOF_ARRAY(AudioModule2_ParameterInfo), STATUS_INVALID_PARAMETER); + + // + // Validate the parameter referenced in the command. + // + switch (command->ParameterId) + { + case AudioModuleParameter1: + currentValue = &audioModuleCtx->Parameter1; + parameterInfo = &AudioModule2_ParameterInfo[AudioModuleParameter1]; + break; + case AudioModuleParameter2: + currentValue = &audioModuleCtx->Parameter2; + parameterInfo = &AudioModule2_ParameterInfo[AudioModuleParameter2]; + break; + default: + RETURN_NTSTATUS(STATUS_INVALID_PARAMETER); + } + + // + // Update input buffer ptr/size. + // + inBuffer = (PVOID)((ULONG_PTR)InBuffer + sizeof(AUDIOMODULE_CUSTOM_COMMAND)); + inBufferCb = InBufferCb - sizeof(AUDIOMODULE_CUSTOM_COMMAND); + + if (inBufferCb == 0) + { + inBuffer = nullptr; + } + + RETURN_NTSTATUS_IF_FAILED(AudioModule_GenericHandler( + command->Verb, + command->ParameterId, + parameterInfo, + currentValue, + inBuffer, + inBufferCb, + OutBuffer, + OutBufferCb, + &fNewValue)); + + if (fNewValue && + (parameterInfo->Flags & AUDIOMODULE_PARAMETER_FLAG_CHANGE_NOTIFICATION)) + { + AUDIOMODULE_CUSTOM_NOTIFICATION customNotification = {0}; + + customNotification.Type = AudioModuleParameterChanged; + customNotification.ParameterChanged.ParameterId = command->ParameterId; + + RETURN_NTSTATUS_IF_FAILED(AcxPnpEventGenerateEvent(audioModuleCtx->Event, &customNotification, (USHORT)sizeof(customNotification))); + } + + return STATUS_SUCCESS; +} + +#pragma code_seg("PAGE") +NTSTATUS +DspR_CreateCircuitModules( + _In_ WDFDEVICE Device, + _In_ ACXCIRCUIT Circuit + ) +/*++ + +Routine Description: + + This routine creates all of the audio module elements and adds them to the circuit + +Return Value: + + NT status value + +--*/ +{ + WDF_OBJECT_ATTRIBUTES attributes; + ACX_AUDIOMODULE_CALLBACKS audioModuleCallbacks; + ACX_AUDIOMODULE_CONFIG audioModuleCfg; + ACXAUDIOMODULE audioModuleElement; + PDSP_AUDIOMODULE0_CONTEXT audioModule0Ctx; + PDSP_AUDIOMODULE1_CONTEXT audioModule1Ctx; + PDSP_AUDIOMODULE2_CONTEXT audioModule2Ctx; + ACX_PNPEVENT_CONFIG audioModuleEventCfg; + ACXPNPEVENT audioModuleEvent; + + PAGED_CODE(); + + // Now add audio modules to the circuit + // module 0 + + ACX_AUDIOMODULE_CALLBACKS_INIT(&audioModuleCallbacks); + audioModuleCallbacks.EvtAcxAudioModuleProcessCommand = DspR_EvtProcessCommand0; + + ACX_AUDIOMODULE_CONFIG_INIT(&audioModuleCfg); + audioModuleCfg.Name = &AudioModule0Id; + audioModuleCfg.Descriptor.ClassId = AudioModule0Id; + audioModuleCfg.Descriptor.InstanceId = AUDIOMODULE_INSTANCE_ID(0,0); + audioModuleCfg.Descriptor.VersionMajor = AUDIOMODULE0_MAJOR; + audioModuleCfg.Descriptor.VersionMinor = AUDIOMODULE0_MINOR; + RETURN_NTSTATUS_IF_FAILED(RtlStringCchCopyNW(audioModuleCfg.Descriptor.Name, + ACX_AUDIOMODULE_MAX_NAME_CCH_SIZE, + AUDIOMODULE0DESCRIPTION, + wcslen(AUDIOMODULE0DESCRIPTION))); + + audioModuleCfg.Callbacks = &audioModuleCallbacks; + + WDF_OBJECT_ATTRIBUTES_INIT_CONTEXT_TYPE(&attributes, DSP_AUDIOMODULE0_CONTEXT); + attributes.ParentObject = Circuit; + + RETURN_NTSTATUS_IF_FAILED(AcxAudioModuleCreate(Circuit, &attributes, &audioModuleCfg, &audioModuleElement)); + + audioModule0Ctx = GetDspAudioModule0Context(audioModuleElement); + ASSERT(audioModule0Ctx); + + ACX_PNPEVENT_CONFIG_INIT(&audioModuleEventCfg); + + WDF_OBJECT_ATTRIBUTES_INIT_CONTEXT_TYPE(&attributes, DSP_PNPEVENT_CONTEXT); + attributes.ParentObject = audioModuleElement; + RETURN_NTSTATUS_IF_FAILED(AcxPnpEventCreate(Device, audioModuleElement, &attributes, &audioModuleEventCfg, &audioModuleEvent)); + + audioModule0Ctx->Event = audioModuleEvent; + + RETURN_NTSTATUS_IF_FAILED(AcxCircuitAddElements(Circuit, (ACXELEMENT *) &audioModuleElement, 1)); + + // module 1 + + ACX_AUDIOMODULE_CALLBACKS_INIT(&audioModuleCallbacks); + audioModuleCallbacks.EvtAcxAudioModuleProcessCommand = DspR_EvtProcessCommand1; + + ACX_AUDIOMODULE_CONFIG_INIT(&audioModuleCfg); + audioModuleCfg.Name = &AudioModule1Id; + audioModuleCfg.Descriptor.ClassId = AudioModule1Id; + audioModuleCfg.Descriptor.InstanceId = AUDIOMODULE_INSTANCE_ID(0,0); + audioModuleCfg.Descriptor.VersionMajor = AUDIOMODULE1_MAJOR; + audioModuleCfg.Descriptor.VersionMinor = AUDIOMODULE1_MINOR; + RETURN_NTSTATUS_IF_FAILED(RtlStringCchCopyNW(audioModuleCfg.Descriptor.Name, + ACX_AUDIOMODULE_MAX_NAME_CCH_SIZE, + AUDIOMODULE1DESCRIPTION, + wcslen(AUDIOMODULE1DESCRIPTION))); + + audioModuleCfg.Callbacks = &audioModuleCallbacks; + + + WDF_OBJECT_ATTRIBUTES_INIT_CONTEXT_TYPE(&attributes, DSP_AUDIOMODULE1_CONTEXT); + attributes.ParentObject = Circuit; + + RETURN_NTSTATUS_IF_FAILED(AcxAudioModuleCreate(Circuit, &attributes, &audioModuleCfg, &audioModuleElement)); + + audioModule1Ctx = GetDspAudioModule1Context(audioModuleElement); + ASSERT(audioModule1Ctx); + + ACX_PNPEVENT_CONFIG_INIT(&audioModuleEventCfg); + + WDF_OBJECT_ATTRIBUTES_INIT_CONTEXT_TYPE(&attributes, DSP_PNPEVENT_CONTEXT); + attributes.ParentObject = audioModuleElement; + RETURN_NTSTATUS_IF_FAILED(AcxPnpEventCreate(Device, audioModuleElement, &attributes, &audioModuleEventCfg, &audioModuleEvent)); + + audioModule1Ctx->Event = audioModuleEvent; + + RETURN_NTSTATUS_IF_FAILED(AcxCircuitAddElements(Circuit, (ACXELEMENT *) &audioModuleElement, 1)); + + // module 2 + + ACX_AUDIOMODULE_CALLBACKS_INIT(&audioModuleCallbacks); + audioModuleCallbacks.EvtAcxAudioModuleProcessCommand = DspR_EvtProcessCommand2; + + ACX_AUDIOMODULE_CONFIG_INIT(&audioModuleCfg); + audioModuleCfg.Name = &AudioModule2Id; + audioModuleCfg.Descriptor.ClassId = AudioModule2Id; + audioModuleCfg.Descriptor.InstanceId = AUDIOMODULE_INSTANCE_ID(1,0); + audioModuleCfg.Descriptor.VersionMajor = AUDIOMODULE2_MAJOR; + audioModuleCfg.Descriptor.VersionMinor = AUDIOMODULE2_MINOR; + RETURN_NTSTATUS_IF_FAILED(RtlStringCchCopyNW(audioModuleCfg.Descriptor.Name, + ACX_AUDIOMODULE_MAX_NAME_CCH_SIZE, + AUDIOMODULE2DESCRIPTION, + wcslen(AUDIOMODULE2DESCRIPTION))); + + audioModuleCfg.Callbacks = &audioModuleCallbacks; + + WDF_OBJECT_ATTRIBUTES_INIT_CONTEXT_TYPE(&attributes, DSP_AUDIOMODULE2_CONTEXT); + attributes.ParentObject = Circuit; + + RETURN_NTSTATUS_IF_FAILED(AcxAudioModuleCreate(Circuit, &attributes, &audioModuleCfg, &audioModuleElement)); + + audioModule2Ctx = GetDspAudioModule2Context(audioModuleElement); + ASSERT(audioModule2Ctx); + + ACX_PNPEVENT_CONFIG_INIT(&audioModuleEventCfg); + + WDF_OBJECT_ATTRIBUTES_INIT_CONTEXT_TYPE(&attributes, DSP_PNPEVENT_CONTEXT); + attributes.ParentObject = audioModuleElement; + RETURN_NTSTATUS_IF_FAILED(AcxPnpEventCreate(Device, audioModuleElement, &attributes, &audioModuleEventCfg, &audioModuleEvent)); + + audioModule2Ctx->Event = audioModuleEvent; + + RETURN_NTSTATUS_IF_FAILED(AcxCircuitAddElements(Circuit, (ACXELEMENT *) &audioModuleElement, 1)); + + return STATUS_SUCCESS; +} + +PAGED_CODE_SEG +NTSTATUS +DspR_AddOffloadFormats( + _In_ ACXPIN Pin +) +{ + PAGED_CODE(); + + ACXCIRCUIT circuit = AcxPinGetCircuit(Pin); + WDFDEVICE device = AcxCircuitGetWdfDevice(circuit); + // PCM:44100 channel:2 24in32 + ACXDATAFORMAT formatPcm44100c2_24in32; + RETURN_NTSTATUS_IF_FAILED(AllocateFormat(Pcm44100c2_24in32, circuit, device, &formatPcm44100c2_24in32)); + + // PCM:48000 channel:2 24in32 + ACXDATAFORMAT formatPcm48000c2_24in32; + RETURN_NTSTATUS_IF_FAILED(AllocateFormat(Pcm48000c2_24in32, circuit, device, &formatPcm48000c2_24in32)); + + // PCM:96000 channel:2 24in32 + ACXDATAFORMAT formatPcm96000c2_24in32; + RETURN_NTSTATUS_IF_FAILED(AllocateFormat(Pcm96000c2_24in32, circuit, device, &formatPcm96000c2_24in32)); + + // PCM:192000 channel:2 24in32 + ACXDATAFORMAT formatPcm192000c2_24in32; + RETURN_NTSTATUS_IF_FAILED(AllocateFormat(Pcm192000c2_24in32, circuit, device, &formatPcm192000c2_24in32)); + + // PCM:44100 channel:2 16 + ACXDATAFORMAT formatPcm44100c2; + RETURN_NTSTATUS_IF_FAILED(AllocateFormat(Pcm44100c2, circuit, device, &formatPcm44100c2)); + + // PCM:48000 channel:2 16 + ACXDATAFORMAT formatPcm48000c2; + RETURN_NTSTATUS_IF_FAILED(AllocateFormat(Pcm48000c2, circuit, device, &formatPcm48000c2)); + + // PCM:96000 channel:2 16 + ACXDATAFORMAT formatPcm96000c2; + RETURN_NTSTATUS_IF_FAILED(AllocateFormat(Pcm96000c2, circuit, device, &formatPcm96000c2)); + + // PCM:192000 channel:2 16 + ACXDATAFORMAT formatPcm192000c2; + RETURN_NTSTATUS_IF_FAILED(AllocateFormat(Pcm192000c2, circuit, device, &formatPcm192000c2)); + + // + // Add our supported formats to the raw mode for the circuit + // + ACXDATAFORMATLIST formatList = AcxPinGetRawDataFormatList(Pin); + RETURN_NTSTATUS_IF_TRUE(formatList == NULL, STATUS_INSUFFICIENT_RESOURCES); + + // + // For Offload scenarios, Windows will use 16 bit per sample offload only + // + RETURN_NTSTATUS_IF_FAILED(AcxDataFormatListAssignDefaultDataFormat(formatList, formatPcm48000c2)); + + RETURN_NTSTATUS_IF_FAILED(AcxDataFormatListAddDataFormat(formatList, formatPcm44100c2)); + RETURN_NTSTATUS_IF_FAILED(AcxDataFormatListAddDataFormat(formatList, formatPcm96000c2)); + RETURN_NTSTATUS_IF_FAILED(AcxDataFormatListAddDataFormat(formatList, formatPcm192000c2)); + + // Include the formats supported by the host pin as well. + RETURN_NTSTATUS_IF_FAILED(AcxDataFormatListAddDataFormat(formatList, formatPcm44100c2_24in32)); + RETURN_NTSTATUS_IF_FAILED(AcxDataFormatListAddDataFormat(formatList, formatPcm48000c2_24in32)); + RETURN_NTSTATUS_IF_FAILED(AcxDataFormatListAddDataFormat(formatList, formatPcm96000c2_24in32)); + RETURN_NTSTATUS_IF_FAILED(AcxDataFormatListAddDataFormat(formatList, formatPcm192000c2_24in32)); + + // + // Set up supported Default Mode formats + // + WDF_OBJECT_ATTRIBUTES attributes; + WDF_OBJECT_ATTRIBUTES_INIT(&attributes); + attributes.ParentObject = circuit; + + ACX_DATAFORMAT_LIST_CONFIG dflCfg; + ACX_DATAFORMAT_LIST_CONFIG_INIT(&dflCfg); + + RETURN_NTSTATUS_IF_FAILED(AcxDataFormatListCreate(device, &attributes, &dflCfg, &formatList)); + + // + // For Offload scenarios, Windows will use 16 bit per sample offload only + // + RETURN_NTSTATUS_IF_FAILED(AcxDataFormatListAssignDefaultDataFormat(formatList, formatPcm48000c2)); + + RETURN_NTSTATUS_IF_FAILED(AcxDataFormatListAddDataFormat(formatList, formatPcm44100c2)); + RETURN_NTSTATUS_IF_FAILED(AcxDataFormatListAddDataFormat(formatList, formatPcm96000c2)); + RETURN_NTSTATUS_IF_FAILED(AcxDataFormatListAddDataFormat(formatList, formatPcm192000c2)); + + // Include the formats supported by the host pin as well. + RETURN_NTSTATUS_IF_FAILED(AcxDataFormatListAddDataFormat(formatList, formatPcm44100c2_24in32)); + RETURN_NTSTATUS_IF_FAILED(AcxDataFormatListAddDataFormat(formatList, formatPcm48000c2_24in32)); + RETURN_NTSTATUS_IF_FAILED(AcxDataFormatListAddDataFormat(formatList, formatPcm96000c2_24in32)); + RETURN_NTSTATUS_IF_FAILED(AcxDataFormatListAddDataFormat(formatList, formatPcm192000c2_24in32)); + + RETURN_NTSTATUS_IF_FAILED(AcxPinAssignModeDataFormatList(Pin, &AUDIO_SIGNALPROCESSINGMODE_DEFAULT, formatList)); + + return STATUS_SUCCESS; +} + +PAGED_CODE_SEG +NTSTATUS +DspR_EvtAcxFactoryCircuitCreateCircuit( + _In_ WDFDEVICE Parent, + _In_ WDFDEVICE Device, + _In_ ACXFACTORYCIRCUIT Factory, + _In_ PACX_FACTORY_CIRCUIT_ADD_CIRCUIT CircuitConfig, + _In_ PACXCIRCUIT_INIT CircuitInit, + _In_ ULONG DataPortNumber, + _In_opt_ PSDCA_PATH_DESCRIPTORS2 PathDescriptors +) +{ + + PAGED_CODE(); + + UNREFERENCED_PARAMETER(Parent); + UNREFERENCED_PARAMETER(Factory); + + NTSTATUS status = STATUS_SUCCESS; + + DrvLogEnter(g_SDCAVDspLog); + + DECLARE_CONST_UNICODE_STRING(circuitName, L"Speaker0"); + + WDF_OBJECT_ATTRIBUTES attributes; + + // + // Init output value. + // + ASSERT(Device); + + DECLARE_CONST_ACXOBJECTBAG_SOUNDWIRE_PROPERTY_NAME(EndpointId); + ULONG endpointId = 0; + + WDF_OBJECT_ATTRIBUTES_INIT(&attributes); + + RETURN_NTSTATUS_IF_FAILED(RetrieveProperties(CircuitConfig, &endpointId)); + + /////////////////////////////////////////////////////////// + // + // Create a circuit. + // + + ACXCIRCUIT circuit; + RETURN_NTSTATUS_IF_FAILED(CreateRenderCircuit(CircuitInit, circuitName, Device, &circuit)); + AcpiReader * acpiReader = GetAcpiReaderDeviceContext(Parent); + + RETURN_NTSTATUS_IF_FAILED(DetermineSpecialStreamDetailsFromVendorProperties(circuit, acpiReader, CircuitConfig->CircuitProperties)); + + ASSERT(circuit != NULL); + DSP_CIRCUIT_CONTEXT *circuitCtx; + circuitCtx = GetDspCircuitContext(circuit); + ASSERT(circuitCtx); + + circuitCtx->EndpointId = endpointId; + circuitCtx->DataPortNumber = DataPortNumber; + circuitCtx->IsRenderCircuit = TRUE; + + // + // Sim Peakmeter + // + circuitCtx->peakMeter = (PVOID)new(POOL_FLAG_NON_PAGED, DRIVER_TAG) CSimPeakMeter(); + RETURN_NTSTATUS_IF_TRUE(NULL == circuitCtx->peakMeter, STATUS_INSUFFICIENT_RESOURCES); + + // + // Post circuit creation initialization. + // + + /////////////////////////////////////////////////////////// + // + // Allocate the formats this circuit supports. + // + // PCM:44100 channel:2 24in32 + ACXDATAFORMAT formatPcm44100c2_24in32; + RETURN_NTSTATUS_IF_FAILED(AllocateFormat(Pcm44100c2_24in32, circuit, Device, &formatPcm44100c2_24in32)); + + // PCM:48000 channel:2 24in32 + ACXDATAFORMAT formatPcm48000c2_24in32; + RETURN_NTSTATUS_IF_FAILED(AllocateFormat(Pcm48000c2_24in32, circuit, Device, &formatPcm48000c2_24in32)); + + // PCM:96000 channel:2 24in32 + ACXDATAFORMAT formatPcm96000c2_24in32; + RETURN_NTSTATUS_IF_FAILED(AllocateFormat(Pcm96000c2_24in32, circuit, Device, &formatPcm96000c2_24in32)); + + // PCM:192000 channel:2 24in32 + ACXDATAFORMAT formatPcm192000c2_24in32; + RETURN_NTSTATUS_IF_FAILED(AllocateFormat(Pcm192000c2_24in32, circuit, Device, &formatPcm192000c2_24in32)); + + /////////////////////////////////////////////////////////// + // + // Create Pins + // + ACXPIN pins[DspPinType_Count]; + + // + // Create host render pin. + // + + ACX_PIN_CALLBACKS pinCallbacks; + ACX_PIN_CALLBACKS_INIT(&pinCallbacks); + pinCallbacks.EvtAcxPinSetDataFormat = DspR_EvtAcxPinSetDataFormat; + + RETURN_NTSTATUS_IF_FAILED(CreatePin(AcxPinTypeSink, + circuit, + AcxPinCommunicationSink, + &KSCATEGORY_AUDIO, + &pinCallbacks, + DSPR_MAX_INPUT_HOST_STREAMS, + false, + &pins[DspPinTypeHost])); + ASSERT(pins[DspPinTypeHost] != NULL); + + PDSP_PIN_CONTEXT pinCtx; + pinCtx = GetDspPinContext(pins[DspPinTypeHost]); + ASSERT(pinCtx); + pinCtx->PinType = DspPinTypeHost; + + // + // A DSP driver could add the formats it supports here, or it could wait until + // the downstream pin is connected and discover the supported formats to use + // formats supported by the SdcaClass driver for this endpoint based on the + // DisCo data for the endpoint (e.g. supported data port widths, supported clock + // sample rates, etc.) + // + ACXDATAFORMATLIST formatList; + formatList = AcxPinGetRawDataFormatList(pins[DspPinTypeHost]); + RETURN_NTSTATUS_IF_TRUE(formatList == NULL, STATUS_INSUFFICIENT_RESOURCES); + + RETURN_NTSTATUS_IF_FAILED(AcxDataFormatListAssignDefaultDataFormat(formatList, formatPcm48000c2_24in32)); + + RETURN_NTSTATUS_IF_FAILED(AcxDataFormatListAddDataFormat(formatList, formatPcm44100c2_24in32)); + RETURN_NTSTATUS_IF_FAILED(AcxDataFormatListAddDataFormat(formatList, formatPcm192000c2_24in32)); + + // + // Set up supported Default Mode formats + // + WDF_OBJECT_ATTRIBUTES_INIT(&attributes); + attributes.ParentObject = circuit; + ACX_DATAFORMAT_LIST_CONFIG dflCfg; + ACX_DATAFORMAT_LIST_CONFIG_INIT(&dflCfg); + AcxDataFormatListCreate(Device, &attributes, &dflCfg, &formatList); + + RETURN_NTSTATUS_IF_FAILED(AcxDataFormatListAssignDefaultDataFormat(formatList, formatPcm48000c2_24in32)); + + RETURN_NTSTATUS_IF_FAILED(AcxDataFormatListAddDataFormat(formatList, formatPcm44100c2_24in32)); + RETURN_NTSTATUS_IF_FAILED(AcxDataFormatListAddDataFormat(formatList, formatPcm192000c2_24in32)); + + RETURN_NTSTATUS_IF_FAILED(AcxPinAssignModeDataFormatList(pins[DspPinTypeHost], &AUDIO_SIGNALPROCESSINGMODE_DEFAULT, formatList)); + + /////////////////////////////////////////////////////////// + // + // Create Offload Render Pin. + // + + ACX_PIN_CALLBACKS_INIT(&pinCallbacks); + pinCallbacks.EvtAcxPinSetDataFormat = DspR_EvtAcxPinSetDataFormat; + + RETURN_NTSTATUS_IF_FAILED(CreatePin(AcxPinTypeSink, + circuit, + AcxPinCommunicationSink, + &KSCATEGORY_AUDIO, + &pinCallbacks, + DSPR_MAX_INPUT_OFFLOAD_STREAMS, + false, + &pins[DspPinTypeOffload])); + ASSERT(pins[DspPinTypeOffload] != NULL); + + pinCtx = GetDspPinContext(pins[DspPinTypeOffload]); + ASSERT(pinCtx); + pinCtx->PinType = DspPinTypeOffload; + + RETURN_NTSTATUS_IF_FAILED(DspR_AddOffloadFormats(pins[DspPinTypeOffload])); + + /////////////////////////////////////////////////////////// + // + // Create loopback Pin. + // + + ACX_PIN_CALLBACKS_INIT(&pinCallbacks); + pinCallbacks.EvtAcxPinSetDataFormat = DspR_EvtAcxPinSetDataFormat; + + RETURN_NTSTATUS_IF_FAILED(CreatePin(AcxPinTypeSource, + circuit, + AcxPinCommunicationSink, + &KSNODETYPE_AUDIO_LOOPBACK, + &pinCallbacks, + DSPR_MAX_OUTPUT_LOOPBACK_STREAMS, + false, + &pins[DspPinTypeLoopback])); + ASSERT(pins[DspPinTypeLoopback] != NULL); + + pinCtx = GetDspPinContext(pins[DspPinTypeLoopback]); + ASSERT(pinCtx); + pinCtx->PinType = DspPinTypeLoopback; + + // + // Add our supported formats to the raw mode for the circuit + // + formatList = AcxPinGetRawDataFormatList(pins[DspPinTypeLoopback]); + RETURN_NTSTATUS_IF_TRUE(formatList == NULL, STATUS_INSUFFICIENT_RESOURCES); + + RETURN_NTSTATUS_IF_FAILED(AcxDataFormatListAssignDefaultDataFormat(formatList, formatPcm48000c2_24in32)); + + RETURN_NTSTATUS_IF_FAILED(AcxDataFormatListAddDataFormat(formatList, formatPcm44100c2_24in32)); + RETURN_NTSTATUS_IF_FAILED(AcxDataFormatListAddDataFormat(formatList, formatPcm192000c2_24in32)); + + // + // Create Audio Engine + // + ACXAUDIOENGINE audioEngineElement; + RETURN_NTSTATUS_IF_FAILED(CreateAudioEngine(circuit, pins, &audioEngineElement)); + circuitCtx->AudioEngineElement = audioEngineElement; + + PDSP_ENGINE_CONTEXT audioEngineCtx; + audioEngineCtx = GetDspEngineContext(audioEngineElement); + + // + // Add our supported formats to the audio engine device format list + // + formatList = AcxAudioEngineGetDeviceFormatList(audioEngineElement); + RETURN_NTSTATUS_IF_TRUE(formatList == NULL, STATUS_INSUFFICIENT_RESOURCES); + + RETURN_NTSTATUS_IF_FAILED(AcxDataFormatListAssignDefaultDataFormat(formatList, formatPcm48000c2_24in32)); + + RETURN_NTSTATUS_IF_FAILED(AcxDataFormatListAddDataFormat(formatList, formatPcm44100c2_24in32)); + RETURN_NTSTATUS_IF_FAILED(AcxDataFormatListAddDataFormat(formatList, formatPcm192000c2_24in32)); + + // Create a new format to use for Engine Mix format + AllocateFormat(Pcm48000c2_24in32, circuit, Device, &formatPcm48000c2_24in32); + audioEngineCtx->MixFormat = formatPcm48000c2_24in32; + + // Set the global efects as disabled + audioEngineCtx->GFxEnabled = FALSE; + + // + // Add AudioEngine to the circuit + // + RETURN_NTSTATUS_IF_FAILED(AcxCircuitAddElements(circuit, (ACXELEMENT*)&audioEngineElement, 1)); + + // + // Create and add the audio modules + // + RETURN_NTSTATUS_IF_FAILED(DspR_CreateCircuitModules(Device, circuit)); + + /////////////////////////////////////////////////////////// + // + // Create bridge pin. + // + ACX_PIN_CALLBACKS_INIT(&pinCallbacks); + pinCallbacks.EvtAcxPinConnected = DspR_EvtPinConnected; + pinCallbacks.EvtAcxPinDisconnected = DspR_EvtPinDisconnected; + + RETURN_NTSTATUS_IF_FAILED(CreatePin(AcxPinTypeSource, + circuit, + AcxPinCommunicationNone, + &KSCATEGORY_AUDIO, + &pinCallbacks, + 0, + false, + &pins[DspPinTypeBridge])); + ASSERT(pins[DspPinTypeBridge] != NULL); + + pinCtx = GetDspPinContext(pins[DspPinTypeBridge]); + ASSERT(pinCtx); + pinCtx->PinType = DspPinTypeBridge; + + // + // Add our supported formats to the raw mode for the bridge pin. + // This is required for ACX to retrieve Device format + // + formatList = AcxPinGetRawDataFormatList(pins[DspPinTypeBridge]); + RETURN_NTSTATUS_IF_TRUE(formatList == NULL, STATUS_INSUFFICIENT_RESOURCES); + + RETURN_NTSTATUS_IF_FAILED(AcxDataFormatListAssignDefaultDataFormat(formatList, formatPcm48000c2_24in32)); + + RETURN_NTSTATUS_IF_FAILED(AcxDataFormatListAddDataFormat(formatList, formatPcm44100c2_24in32)); + RETURN_NTSTATUS_IF_FAILED(AcxDataFormatListAddDataFormat(formatList, formatPcm192000c2_24in32)); + + if (PathDescriptors != nullptr && PathDescriptors->Size > 0) + { + circuitCtx->AggregatedPathDescriptors = (PSDCA_PATH_DESCRIPTORS2)ExAllocatePool2(POOL_FLAG_NON_PAGED, PathDescriptors->Size, DRIVER_TAG); + if (circuitCtx->AggregatedPathDescriptors == nullptr) + { + RETURN_NTSTATUS_IF_FAILED(STATUS_INSUFFICIENT_RESOURCES); + } + + RtlCopyMemory(circuitCtx->AggregatedPathDescriptors, PathDescriptors, PathDescriptors->Size); + } + + // + // Add a stream BRIDGE. + // + + ACX_STREAM_BRIDGE_CONFIG streamCfg; + ACX_STREAM_BRIDGE_CONFIG_INIT(&streamCfg); + + RETURN_NTSTATUS_IF_FAILED(CreateStreamBridge(streamCfg, circuit, pins[DspPinTypeBridge], pinCtx, DataPortNumber, endpointId, PathDescriptors, true)); + + // + // Add bridge pin + // + RETURN_NTSTATUS_IF_FAILED(AcxCircuitAddPins(circuit, pins, DspPinType_Count)); + + RETURN_NTSTATUS_IF_FAILED(ConnectRenderCircuitElements(audioEngineElement, circuit)); + + // + // Store the circuit handle in the render device context. + // + PDSP_RENDER_DEVICE_CONTEXT renderDevCtx = NULL; + renderDevCtx = GetRenderDeviceContext(Device); + ASSERT(renderDevCtx); + renderDevCtx->Circuit = circuit; + renderDevCtx->FirstTimePrepareHardware = TRUE; + + DrvLogInfo(g_SDCAVDspLog, FLAG_STREAM, L"SDCA VDSP Circuit Device %p Create Circuit %p", Device, circuit); + + return status; +} + +#pragma code_seg() +_Use_decl_annotations_ +NTSTATUS +DspR_EvtCircuitPowerUp ( + WDFDEVICE, + ACXCIRCUIT, + WDF_POWER_DEVICE_STATE + ) +{ + return STATUS_SUCCESS; +} + +PAGED_CODE_SEG +_Use_decl_annotations_ +NTSTATUS +DspR_EvtCircuitPowerDown ( + WDFDEVICE Device, + ACXCIRCUIT Circuit, + WDF_POWER_DEVICE_STATE TargetState + ) +{ + PAGED_CODE(); + + UNREFERENCED_PARAMETER(Device); + UNREFERENCED_PARAMETER(Circuit); + UNREFERENCED_PARAMETER(TargetState); + + return STATUS_SUCCESS; +} + +_Use_decl_annotations_ +PAGED_CODE_SEG +NTSTATUS +DspR_EvtCircuitCompositeCircuitInitialize( + WDFDEVICE Device, + ACXCIRCUIT Circuit, + ACXOBJECTBAG CircuitProperties + ) +{ + NTSTATUS status = STATUS_SUCCESS; + + PAGED_CODE(); + + UNREFERENCED_PARAMETER(Device); + UNREFERENCED_PARAMETER(Circuit); + UNREFERENCED_PARAMETER(CircuitProperties); + + return status; +} + +_Use_decl_annotations_ +PAGED_CODE_SEG +NTSTATUS +DspR_EvtCircuitCompositeInitialize( + WDFDEVICE Device, + ACXCIRCUIT Circuit, + ACXOBJECTBAG CompositeProperties + ) +{ + NTSTATUS status = STATUS_SUCCESS; + + PAGED_CODE(); + + UNREFERENCED_PARAMETER(Device); + UNREFERENCED_PARAMETER(Circuit); + UNREFERENCED_PARAMETER(CompositeProperties); + + return status; +} + +#pragma code_seg("PAGE") +NTSTATUS +DspR_CreateStreamModules( + _In_ WDFDEVICE Device, + _In_ ACXSTREAM Stream + ) +/*++ + +Routine Description: + + This routine creates all of the audio module elements and adds them to the stream + +Return Value: + + NT status value + +--*/ +{ + WDF_OBJECT_ATTRIBUTES attributes; + ACX_AUDIOMODULE_CALLBACKS audioModuleCallbacks; + ACX_AUDIOMODULE_CONFIG audioModuleCfg; + ACXAUDIOMODULE audioModuleElement; + PDSP_AUDIOMODULE0_CONTEXT audioModule0Ctx; + PDSP_AUDIOMODULE1_CONTEXT audioModule1Ctx; + PDSP_AUDIOMODULE2_CONTEXT audioModule2Ctx; + ACX_PNPEVENT_CONFIG audioModuleEventCfg; + ACXPNPEVENT audioModuleEvent; + + PAGED_CODE(); + + // Now add audio modules to the stream + // module 0 + // for simplicity of the example, we implement the same modules on the stream as is + // on the circuit + ACX_AUDIOMODULE_CALLBACKS_INIT(&audioModuleCallbacks); + audioModuleCallbacks.EvtAcxAudioModuleProcessCommand = DspR_EvtProcessCommand0; + + ACX_AUDIOMODULE_CONFIG_INIT(&audioModuleCfg); + audioModuleCfg.Name = &AudioModule0Id; + audioModuleCfg.Descriptor.ClassId = AudioModule0Id; + audioModuleCfg.Descriptor.InstanceId = AUDIOMODULE_INSTANCE_ID(1,0); + audioModuleCfg.Descriptor.VersionMajor = AUDIOMODULE0_MAJOR; + audioModuleCfg.Descriptor.VersionMinor = AUDIOMODULE0_MINOR; + RETURN_NTSTATUS_IF_FAILED(RtlStringCchCopyNW(audioModuleCfg.Descriptor.Name, + ACX_AUDIOMODULE_MAX_NAME_CCH_SIZE, + AUDIOMODULE0DESCRIPTION, + wcslen(AUDIOMODULE0DESCRIPTION))); + + audioModuleCfg.Callbacks = &audioModuleCallbacks; + + + WDF_OBJECT_ATTRIBUTES_INIT_CONTEXT_TYPE(&attributes, DSP_AUDIOMODULE0_CONTEXT); + attributes.ParentObject = Stream; + + RETURN_NTSTATUS_IF_FAILED(AcxAudioModuleCreate(Stream, &attributes, &audioModuleCfg, &audioModuleElement)); + + audioModule0Ctx = GetDspAudioModule0Context(audioModuleElement); + ASSERT(audioModule0Ctx); + + ACX_PNPEVENT_CONFIG_INIT(&audioModuleEventCfg); + + WDF_OBJECT_ATTRIBUTES_INIT_CONTEXT_TYPE(&attributes, DSP_PNPEVENT_CONTEXT); + attributes.ParentObject = audioModuleElement; + RETURN_NTSTATUS_IF_FAILED(AcxPnpEventCreate(Device, audioModuleElement, &attributes, &audioModuleEventCfg, &audioModuleEvent)); + + audioModule0Ctx->Event = audioModuleEvent; + + RETURN_NTSTATUS_IF_FAILED(AcxStreamAddElements(Stream, (ACXELEMENT *) &audioModuleElement, 1)); + + // module 1 + + ACX_AUDIOMODULE_CALLBACKS_INIT(&audioModuleCallbacks); + audioModuleCallbacks.EvtAcxAudioModuleProcessCommand = DspR_EvtProcessCommand1; + + ACX_AUDIOMODULE_CONFIG_INIT(&audioModuleCfg); + audioModuleCfg.Name = &AudioModule1Id; + audioModuleCfg.Descriptor.ClassId = AudioModule1Id; + audioModuleCfg.Descriptor.InstanceId = AUDIOMODULE_INSTANCE_ID(1,0); + audioModuleCfg.Descriptor.VersionMajor = AUDIOMODULE1_MAJOR; + audioModuleCfg.Descriptor.VersionMinor = AUDIOMODULE1_MINOR; + RETURN_NTSTATUS_IF_FAILED(RtlStringCchCopyNW(audioModuleCfg.Descriptor.Name, + ACX_AUDIOMODULE_MAX_NAME_CCH_SIZE, + AUDIOMODULE1DESCRIPTION, + wcslen(AUDIOMODULE1DESCRIPTION))); + + audioModuleCfg.Callbacks = &audioModuleCallbacks; + + + WDF_OBJECT_ATTRIBUTES_INIT_CONTEXT_TYPE(&attributes, DSP_AUDIOMODULE1_CONTEXT); + attributes.ParentObject = Stream; + + RETURN_NTSTATUS_IF_FAILED(AcxAudioModuleCreate(Stream, &attributes, &audioModuleCfg, &audioModuleElement)); + + audioModule1Ctx = GetDspAudioModule1Context(audioModuleElement); + ASSERT(audioModule1Ctx); + + ACX_PNPEVENT_CONFIG_INIT(&audioModuleEventCfg); + + WDF_OBJECT_ATTRIBUTES_INIT_CONTEXT_TYPE(&attributes, DSP_PNPEVENT_CONTEXT); + attributes.ParentObject = audioModuleElement; + RETURN_NTSTATUS_IF_FAILED(AcxPnpEventCreate(Device, audioModuleElement, &attributes, &audioModuleEventCfg, &audioModuleEvent)); + + audioModule1Ctx->Event = audioModuleEvent; + + RETURN_NTSTATUS_IF_FAILED(AcxStreamAddElements(Stream, (ACXELEMENT *) &audioModuleElement, 1)); + + // module 2 + + ACX_AUDIOMODULE_CALLBACKS_INIT(&audioModuleCallbacks); + audioModuleCallbacks.EvtAcxAudioModuleProcessCommand = DspR_EvtProcessCommand2; + + ACX_AUDIOMODULE_CONFIG_INIT(&audioModuleCfg); + audioModuleCfg.Name = &AudioModule2Id; + audioModuleCfg.Descriptor.ClassId = AudioModule2Id; + audioModuleCfg.Descriptor.InstanceId = AUDIOMODULE_INSTANCE_ID(2,0); + audioModuleCfg.Descriptor.VersionMajor = AUDIOMODULE2_MAJOR; + audioModuleCfg.Descriptor.VersionMinor = AUDIOMODULE2_MINOR; + RETURN_NTSTATUS_IF_FAILED(RtlStringCchCopyNW(audioModuleCfg.Descriptor.Name, + ACX_AUDIOMODULE_MAX_NAME_CCH_SIZE, + AUDIOMODULE2DESCRIPTION, + wcslen(AUDIOMODULE2DESCRIPTION))); + + audioModuleCfg.Callbacks = &audioModuleCallbacks; + + WDF_OBJECT_ATTRIBUTES_INIT_CONTEXT_TYPE(&attributes, DSP_AUDIOMODULE2_CONTEXT); + attributes.ParentObject = Stream; + + RETURN_NTSTATUS_IF_FAILED(AcxAudioModuleCreate(Stream, &attributes, &audioModuleCfg, &audioModuleElement)); + + audioModule2Ctx = GetDspAudioModule2Context(audioModuleElement); + ASSERT(audioModule2Ctx); + + ACX_PNPEVENT_CONFIG_INIT(&audioModuleEventCfg); + + WDF_OBJECT_ATTRIBUTES_INIT_CONTEXT_TYPE(&attributes, DSP_PNPEVENT_CONTEXT); + attributes.ParentObject = audioModuleElement; + RETURN_NTSTATUS_IF_FAILED(AcxPnpEventCreate(Device, audioModuleElement, &attributes, &audioModuleEventCfg, &audioModuleEvent)); + + audioModule2Ctx->Event = audioModuleEvent; + + RETURN_NTSTATUS_IF_FAILED(AcxStreamAddElements(Stream, (ACXELEMENT *) &audioModuleElement, 1)); + + return STATUS_SUCCESS; +} + +PAGED_CODE_SEG +NTSTATUS +DspR_EvtCircuitCreateStream( + _In_ WDFDEVICE Device, + _In_ ACXCIRCUIT Circuit, + _In_ ACXPIN Pin, + _In_ PACXSTREAM_INIT StreamInit, + _In_ ACXDATAFORMAT DataFormat, + _In_ const GUID* SignalProcessingMode, + _In_ ACXOBJECTBAG VarArguments +) +/*++ + +Routine Description: + + This routine create a stream for the specified circuit. + +Return Value: + + NT status value + +--*/ +{ + PAGED_CODE(); + + UNREFERENCED_PARAMETER(SignalProcessingMode); + UNREFERENCED_PARAMETER(VarArguments); + + DrvLogEnter(g_SDCAVDspLog); + + NTSTATUS status = STATUS_SUCCESS; + + PDSP_PIN_CONTEXT pinCtx = GetDspPinContext(Pin); + ASSERT(pinCtx); + +// See description in private.h +#ifdef ACX_WORKAROUND_ACXPIN_01 + { + ASSERT(pinCtx->CurrentStreamsCount != (ULONG)-1); + RETURN_NTSTATUS_IF_TRUE_MSG( + pinCtx->CurrentStreamsCount >= pinCtx->MaxStreams, + STATUS_INSUFFICIENT_RESOURCES, + L"ACXCIRCUIT %p ACXPIN %p cannot create another ACXSTREAM, max count is %d, %!STATUS!", + Circuit, Pin, pinCtx->MaxStreams, status); + } +#endif + + // Check incorrect pin instantiation. + RETURN_NTSTATUS_IF_TRUE_MSG(NULL == pinCtx, STATUS_INVALID_PARAMETER, L"Incorrect pin is being instantiated"); + RETURN_NTSTATUS_IF_TRUE_MSG( + NULL == pinCtx || + (pinCtx->PinType != DspPinTypeHost && + pinCtx->PinType != DspPinTypeOffload && + pinCtx->PinType != DspPinTypeLoopback), + STATUS_INVALID_PARAMETER, L"Incorrect pin is being instantiated"); + + // + // TEST sending KS Property to connected circuits + // + ULONG testValue = 7; + status = Dsp_SendTestPropertyTo( + Device, + Circuit, + SDCAVADPropsetId, + SDCAVAD_PROPERTY_TEST1, + AcxPropertyVerbSet, + nullptr, 0, + &testValue, sizeof(ULONG), + nullptr); + DrvLogInfo(g_SDCAVDspLog, FLAG_STREAM, L"SDCAVAD_PROPERTY_TEST1 SET :%!STATUS!, Value = %d", status, testValue); + + status = Dsp_SendTestPropertyTo( + Device, + Circuit, + SDCAVADPropsetId, + SDCAVAD_PROPERTY_TEST2, + AcxPropertyVerbGet, + nullptr, 0, + &testValue, sizeof(ULONG), + nullptr); + DrvLogInfo(g_SDCAVDspLog, FLAG_STREAM, L"SDCAVAD_PROPERTY_TEST2 GET :%!STATUS!, Value = %d", status, testValue); + + testValue = 8; + status = Dsp_SendTestPropertyTo( + Device, + Circuit, + SDCAVADPropsetId, + SDCAVAD_PROPERTY_TEST3, + AcxPropertyVerbSet, + nullptr, 0, + &testValue, sizeof(ULONG), + nullptr); + DrvLogInfo(g_SDCAVDspLog, FLAG_STREAM, L"SDCAVAD_PROPERTY_TEST3 SET :%!STATUS!, Value = %d", status, testValue); + + status = Dsp_SendTestPropertyTo( + Device, + Circuit, + SDCAVADPropsetId, + SDCAVAD_PROPERTY_TEST4, + AcxPropertyVerbGet, + nullptr, 0, + &testValue, sizeof(ULONG), + nullptr); + DrvLogInfo(g_SDCAVDspLog, FLAG_STREAM, L"SDCAVAD_PROPERTY_TEST4 GET :%!STATUS!, Value = %d", status, testValue); + + testValue = 9; + status = Dsp_SendTestPropertyTo( + Device, + Circuit, + SDCAVADPropsetId, + SDCAVAD_PROPERTY_TEST5, + AcxPropertyVerbSet, + nullptr, 0, + &testValue, sizeof(ULONG), + nullptr); + DrvLogInfo(g_SDCAVDspLog, FLAG_STREAM, L"SDCAVAD_PROPERTY_TEST5 SET :%!STATUS!, Value = %d", status, testValue); + + status = Dsp_SendTestPropertyTo( + Device, + Circuit, + SDCAVADPropsetId, + SDCAVAD_PROPERTY_TEST6, + AcxPropertyVerbGet, + nullptr, 0, + &testValue, sizeof(ULONG), + nullptr); + DrvLogInfo(g_SDCAVDspLog, FLAG_STREAM, L"SDCAVAD_PROPERTY_TEST6 GET :%!STATUS!, Value = %d", status, testValue); + + status = STATUS_SUCCESS; + + if (pinCtx->PinType != DspPinTypeOffload) + { + // + // Set circuit-callbacks. + // + RETURN_NTSTATUS_IF_FAILED(AcxStreamInitAssignAcxRequestPreprocessCallback( + StreamInit, + DspR_EvtStreamRequestPreprocess, + (ACXCONTEXT)AcxRequestTypeAny, // dbg only + AcxRequestTypeAny, + NULL, + AcxItemIdNone)); + } + + // + // Request a Vendor-Specific property from the Controller + // + Dsp_SendVendorSpecificProperties( + Device, + Circuit, + TRUE); + + /* + // + // Add properties, events and methods. + // + RETURN_NTSTATUS_IF_FAILED(AcxStreamInitAssignProperties(StreamInit, + StreamProperties, + StreamPropertiesCount)); + */ + + // + // Init streaming callbacks. + // + ACX_STREAM_CALLBACKS streamCallbacks; + ACX_STREAM_CALLBACKS_INIT(&streamCallbacks); + streamCallbacks.EvtAcxStreamPrepareHardware = Dsp_EvtStreamPrepareHardware; + streamCallbacks.EvtAcxStreamReleaseHardware = Dsp_EvtStreamReleaseHardware; + streamCallbacks.EvtAcxStreamRun = Dsp_EvtStreamRun; + streamCallbacks.EvtAcxStreamPause = Dsp_EvtStreamPause; + streamCallbacks.EvtAcxStreamAssignDrmContentId = Dsp_EvtStreamAssignDrmContentId; + + RETURN_NTSTATUS_IF_FAILED(AcxStreamInitAssignAcxStreamCallbacks(StreamInit, &streamCallbacks)); + + // + // Init RT streaming callbacks. + // + ACX_RT_STREAM_CALLBACKS rtCallbacks; + ACX_RT_STREAM_CALLBACKS_INIT(&rtCallbacks); + rtCallbacks.EvtAcxStreamGetHwLatency = Dsp_EvtStreamGetHwLatency; + rtCallbacks.EvtAcxStreamAllocateRtPackets = Dsp_EvtStreamAllocateRtPackets; + rtCallbacks.EvtAcxStreamFreeRtPackets = Dsp_EvtStreamFreeRtPackets; + rtCallbacks.EvtAcxStreamSetRenderPacket = DspR_EvtStreamSetRenderPacket; + rtCallbacks.EvtAcxStreamGetCurrentPacket = Dsp_EvtStreamGetCurrentPacket; + rtCallbacks.EvtAcxStreamGetPresentationPosition = Dsp_EvtStreamGetPresentationPosition; + + RETURN_NTSTATUS_IF_FAILED(AcxStreamInitAssignAcxRtStreamCallbacks(StreamInit, &rtCallbacks)); + + // + // Buffer notifications are supported. + // + AcxStreamInitSetAcxRtStreamSupportsNotifications(StreamInit); + + // + // Create the stream. + // + WDF_OBJECT_ATTRIBUTES attributes; + ACXSTREAM stream; + WDF_OBJECT_ATTRIBUTES_INIT_CONTEXT_TYPE(&attributes, DSP_STREAM_CONTEXT); + attributes.EvtDestroyCallback = Dsp_EvtStreamContextDestroy; + attributes.EvtCleanupCallback = Dsp_EvtStreamContextCleanup; + + + RETURN_NTSTATUS_IF_FAILED(AcxRtStreamCreate(Device, Circuit, &attributes, &StreamInit, &stream)); + + PDSP_CIRCUIT_CONTEXT circuitCtx = GetDspCircuitContext(Circuit); + ASSERT(circuitCtx); + + CStreamEngine* streamEngine = NULL; + if (pinCtx->PinType == DspPinTypeOffload) + { + streamEngine = new(POOL_FLAG_NON_PAGED, DRIVER_TAG) COffloadStreamEngine(stream, DataFormat, (CSimPeakMeter *)circuitCtx->peakMeter); + } + else + { + streamEngine = new(POOL_FLAG_NON_PAGED, DRIVER_TAG) CRenderStreamEngine(stream, DataFormat, (CSimPeakMeter *)circuitCtx->peakMeter); + } + RETURN_NTSTATUS_IF_TRUE(NULL == streamEngine, STATUS_INSUFFICIENT_RESOURCES); + + DSP_STREAM_CONTEXT* streamCtx; + streamCtx = GetDspStreamContext(stream); + ASSERT(streamCtx); + streamCtx->StreamEngine = (PVOID)streamEngine; + streamEngine = NULL; + streamCtx->PinType = pinCtx->PinType; + + if (DspPinTypeLoopback == pinCtx->PinType && + circuitCtx->SpecialStreamAvailablePaths & SdcaPathReferenceStream) + { + WdfObjectReferenceWithTag(circuitCtx->SpecialStreamTargetCircuit, (PVOID)DRIVER_TAG); + streamCtx->SpecialStreamTargetCircuit = circuitCtx->SpecialStreamTargetCircuit; + } + + if ((DspPinTypeHost == pinCtx->PinType || DspPinTypeOffload == pinCtx->PinType) && + circuitCtx->SpecialStreamAvailablePaths & SdcaPathIvSense) + { + WdfObjectReferenceWithTag(circuitCtx->SpecialStreamTargetCircuit, (PVOID)DRIVER_TAG); + streamCtx->SpecialStreamTargetCircuit = circuitCtx->SpecialStreamTargetCircuit; + } + + // + // Post stream creation initialization. + // + + if (circuitCtx->AudioEngineElement != nullptr) + { + // + // The circuit has an Audio Engine element, so all streams created for the circuit + // also require an Audio Engine element to allow the OS to + // * Adjust per-stream volume and mute + // * Monitor per-stream peakmeter values + // * Retrieve stream position + // * Set stream effects state + // + + // + // Volume Element + // + ACX_VOLUME_CALLBACKS volumeCallbacks; + ACX_VOLUME_CALLBACKS_INIT(&volumeCallbacks); + volumeCallbacks.EvtAcxRampedVolumeAssignLevel = DspR_EvtRampedVolumeAssignLevel; + volumeCallbacks.EvtAcxVolumeRetrieveLevel = DspR_EvtVolumeRetrieveLevel; + + // Create Volume element for the audio engine to use + ACX_VOLUME_CONFIG volumeCfg; + ACX_VOLUME_CONFIG_INIT(&volumeCfg); + volumeCfg.ChannelsCount = MAX_CHANNELS; + volumeCfg.Minimum = VOLUME_LEVEL_MINIMUM; + volumeCfg.Maximum = VOLUME_LEVEL_MAXIMUM; + volumeCfg.SteppingDelta = VOLUME_STEPPING; + volumeCfg.Name = &KSAUDFNAME_VOLUME_CONTROL; + volumeCfg.Callbacks = &volumeCallbacks; + + WDF_OBJECT_ATTRIBUTES_INIT_CONTEXT_TYPE(&attributes, DSP_VOLUME_ELEMENT_CONTEXT); + attributes.ParentObject = stream; + + ACXVOLUME volumeElement; + RETURN_NTSTATUS_IF_FAILED(AcxVolumeCreate(stream, &attributes, &volumeCfg, &volumeElement)); + + // + // Mute Element + // + ACX_MUTE_CALLBACKS muteCallbacks; + ACX_MUTE_CALLBACKS_INIT(&muteCallbacks); + muteCallbacks.EvtAcxMuteAssignState = DspR_EvtMuteAssignState; + muteCallbacks.EvtAcxMuteRetrieveState = DspR_EvtMuteRetrieveState; + + ACX_MUTE_CONFIG muteCfg; + ACX_MUTE_CONFIG_INIT(&muteCfg); + muteCfg.ChannelsCount = MAX_CHANNELS; + muteCfg.Name = &KSAUDFNAME_WAVE_MUTE; + muteCfg.Callbacks = &muteCallbacks; + + WDF_OBJECT_ATTRIBUTES_INIT_CONTEXT_TYPE(&attributes, DSP_MUTE_ELEMENT_CONTEXT); + attributes.ParentObject = stream; + + ACXMUTE muteElement; + RETURN_NTSTATUS_IF_FAILED(AcxMuteCreate(stream, &attributes, &muteCfg, &muteElement)); + + // + // Peakmeter Element + // + ACX_PEAKMETER_CALLBACKS peakmeterCallbacks; + ACX_PEAKMETER_CALLBACKS_INIT(&peakmeterCallbacks); + peakmeterCallbacks.EvtAcxPeakMeterRetrieveLevel = DspR_EvtPeakMeterRetrieveLevelCallback; + + ACX_PEAKMETER_CONFIG peakmeterCfg; + ACX_PEAKMETER_CONFIG_INIT(&peakmeterCfg); + peakmeterCfg.ChannelsCount = MAX_CHANNELS; + peakmeterCfg.Minimum = PEAKMETER_MINIMUM; + peakmeterCfg.Maximum = PEAKMETER_MAXIMUM; + peakmeterCfg.SteppingDelta = PEAKMETER_STEPPING_DELTA; + peakmeterCfg.Callbacks = &peakmeterCallbacks; + + WDF_OBJECT_ATTRIBUTES_INIT_CONTEXT_TYPE(&attributes, DSP_PEAKMETER_ELEMENT_CONTEXT); + attributes.ParentObject = stream; + + ACXPEAKMETER peakmeterElement; + RETURN_NTSTATUS_IF_FAILED(AcxPeakMeterCreate(stream, &attributes, &peakmeterCfg, &peakmeterElement)); + + PDSP_PEAKMETER_ELEMENT_CONTEXT peakmeterCtx; + ASSERT(peakmeterElement != NULL); + peakmeterCtx = GetDspPeakMeterElementContext(peakmeterElement); + ASSERT(peakmeterCtx); + peakmeterCtx->peakMeter = ((CStreamEngine*)streamCtx->StreamEngine)->GetPeakMeter(); + + // + // Stream Audio Engine Node + // + ACX_STREAMAUDIOENGINE_CALLBACKS streamAudioEngineCallbacks; + // Create the AudioEngine element to control offloaded streaming. + ACX_STREAMAUDIOENGINE_CALLBACKS_INIT(&streamAudioEngineCallbacks); + streamAudioEngineCallbacks.EvtAcxStreamAudioEngineAssignEffectsState = DspR_EvtAcxStreamAudioEngineAssignEffectsState; + streamAudioEngineCallbacks.EvtAcxStreamAudioEngineRetrieveEffectsState = DspR_EvtAcxStreamAudioEngineRetrieveEffectsState; + streamAudioEngineCallbacks.EvtAcxStreamAudioEngineRetrievePresentationPosition = DspR_EvtAcxStreamAudioEngineRetrievePresentationPosition; + streamAudioEngineCallbacks.EvtAcxStreamAudioEngineAssignCurrentWritePosition = DspR_EvtAcxStreamAudioEngineAssignCurrentWritePosition; + streamAudioEngineCallbacks.EvtAcxStreamAudioEngineRetrieveLinearBufferPosition = DspR_EvtAcxStreamAudioEngineRetrieveLinearBufferPosition; + streamAudioEngineCallbacks.EvtAcxStreamAudioEngineAssignLastBufferPosition = DspR_EvtAcxStreamAudioEngineAssignLastBufferPosition; + streamAudioEngineCallbacks.EvtAcxStreamAudioEngineAssignLoopbackProtection = DspR_EvtAcxStreamAudioEngineAssignLoopbackProtection; + + ACX_STREAMAUDIOENGINE_CONFIG audioEngineCfg; + ACX_STREAMAUDIOENGINE_CONFIG_INIT(&audioEngineCfg); + audioEngineCfg.VolumeElement = volumeElement; + audioEngineCfg.MuteElement = muteElement; + audioEngineCfg.PeakMeterElement = peakmeterElement; + audioEngineCfg.Callbacks = &streamAudioEngineCallbacks; + + WDF_OBJECT_ATTRIBUTES_INIT_CONTEXT_TYPE(&attributes, DSP_STREAMAUDIOENGINE_CONTEXT); + attributes.ParentObject = stream; + + ACXELEMENT streamAudioEngine; + RETURN_NTSTATUS_IF_FAILED(AcxStreamAudioEngineCreate(stream, circuitCtx->AudioEngineElement, &attributes, &audioEngineCfg, (ACXSTREAMAUDIOENGINE*)&streamAudioEngine)); + + // Set local effects as disabled + PDSP_STREAMAUDIOENGINE_CONTEXT pStreamAudioEngineCtx; + pStreamAudioEngineCtx = GetDspStreamAudioEngineContext(streamAudioEngine); + pStreamAudioEngineCtx->LFxEnabled = FALSE; + + + RETURN_NTSTATUS_IF_FAILED(AcxStreamAddElements(stream, &streamAudioEngine, 1)); + + // Add our stream audio modules + RETURN_NTSTATUS_IF_FAILED(DspR_CreateStreamModules(Device, stream)); + } + else + { + // + // Create 1st custom stream-elements. + // + ACX_ELEMENT_CONFIG elementCfg; + ACX_ELEMENT_CONFIG_INIT(&elementCfg); + WDF_OBJECT_ATTRIBUTES_INIT_CONTEXT_TYPE(&attributes, DSP_ELEMENT_CONTEXT); + attributes.ParentObject = stream; + + ACXELEMENT elements[2] = { 0 }; + RETURN_NTSTATUS_IF_FAILED(AcxElementCreate(stream, &attributes, &elementCfg, &elements[0])); + + ASSERT(elements[0] != NULL); + DSP_ELEMENT_CONTEXT* elementCtx; + elementCtx = GetDspElementContext(elements[0]); + ASSERT(elementCtx); + UNREFERENCED_PARAMETER(elementCtx); + + // + // Create 2nd custom stream-elements. + // + ACX_ELEMENT_CONFIG_INIT(&elementCfg); + WDF_OBJECT_ATTRIBUTES_INIT_CONTEXT_TYPE(&attributes, DSP_ELEMENT_CONTEXT); + attributes.ParentObject = stream; + + RETURN_NTSTATUS_IF_FAILED(AcxElementCreate(stream, &attributes, &elementCfg, &elements[1])); + + ASSERT(elements[1] != NULL); + elementCtx = GetDspElementContext(elements[1]); + ASSERT(elementCtx); + UNREFERENCED_PARAMETER(elementCtx); + + // + // Add stream elements + // + RETURN_NTSTATUS_IF_FAILED(AcxStreamAddElements(stream, elements, SIZEOF_ARRAY(elements))); + + // Add our stream audio modules + RETURN_NTSTATUS_IF_FAILED(DspR_CreateStreamModules(Device, stream)); + } + +// See description in private.h +#ifdef ACX_WORKAROUND_ACXPIN_01 + { + ASSERT(pinCtx->CurrentStreamsCount != (ULONG)-1); + InterlockedIncrement(PLONG(&pinCtx->CurrentStreamsCount)); + streamCtx->StreamIsCounted = TRUE; + } +#endif + + streamCtx->Pin = Pin; + WdfObjectReferenceWithTag(Pin, (PVOID)DRIVER_TAG); + + return status; +} + +PAGED_CODE_SEG +NTSTATUS +DspR_EvtStreamSetRenderPacket( + _In_ ACXSTREAM Stream, + _In_ ULONG Packet, + _In_ ULONG Flags, + _In_ ULONG EosPacketLength + ) +{ + PDSP_STREAM_CONTEXT ctx; + CRenderStreamEngine * streamEngine = NULL; + + PAGED_CODE(); + + ctx = GetDspStreamContext(Stream); + + streamEngine = static_cast(ctx->StreamEngine); + + return streamEngine->SetRenderPacket(Packet, Flags, EosPacketLength); +} + +// +//#pragma code_seg() +//NTSTATUS +//DspR_EvtAcxCircuitProcess( +// _In_ ACXCIRCUIT Circuit, +// _In_ ACXSTREAMIO Stream +// ) +//{ +// UNREFERENCED_PARAMETER(Circuit); +// UNREFERENCED_PARAMETER(Stream); +// +// return STATUS_SUCCESS; +//} +// + + diff --git a/audio/Soundwire/Samples/SdcaVad/SdcaVDsp/renderAudioEngine.cpp b/audio/Soundwire/Samples/SdcaVad/SdcaVDsp/renderAudioEngine.cpp new file mode 100644 index 000000000..e9bb94967 --- /dev/null +++ b/audio/Soundwire/Samples/SdcaVad/SdcaVDsp/renderAudioEngine.cpp @@ -0,0 +1,502 @@ +/*++ + + THIS CODE AND INFORMATION IS PROVIDED "AS IS" WITHOUT WARRANTY OF ANY + KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE + IMPLIED WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A PARTICULAR + PURPOSE. + +Module Name: + + renderaudioengine.cpp + +Abstract: + + Render Audio Engine - callbacks for Audio Engine Node + +Environment: + + Kernel mode + +--*/ + +#include "private.h" +#include +#include "stdunk.h" +#include +#include +#include +#include "offloadStreamEngine.h" +#include "SimPeakMeter.h" + +#include "TestProperties.h" +#include "AudioFormats.h" + +#ifndef __INTELLISENSE__ +#include "renderaudioengine.tmh" +#endif + +// Sizes for min/max for audioengine buffers +// Buffer duration is for both ping and pong buffers combined +// so multiply it by 2 +#define MIN_AUDIOENGINE_BUFFER_DURATION_IN_MS (10 * 2) +#define MAX_AUDIOENGINE_BUFFER_DURATION_IN_MS (2000 * 2) + +_Use_decl_annotations_ +PAGED_CODE_SEG +NTSTATUS +DspR_EvtAcxAudioEngineRetrieveBufferSizeLimits( + ACXAUDIOENGINE, + ACXDATAFORMAT DataFormat, + PULONG MinBufferBytes, + PULONG MaxBufferBytes + ) +{ + PAGED_CODE(); + + ULONG bytesPerSecond = AcxDataFormatGetAverageBytesPerSec(DataFormat); + + *MinBufferBytes = (ULONG) (MIN_AUDIOENGINE_BUFFER_DURATION_IN_MS * bytesPerSecond / 1000); + *MaxBufferBytes = (ULONG) (MAX_AUDIOENGINE_BUFFER_DURATION_IN_MS * bytesPerSecond / 1000); + + return STATUS_SUCCESS; +} + +_Use_decl_annotations_ +PAGED_CODE_SEG +NTSTATUS +DspR_EvtAcxAudioEngineRetrieveEffectsState( + ACXAUDIOENGINE AudioEngine, + PULONG State +) +{ + PAGED_CODE(); + + PDSP_ENGINE_CONTEXT pAudioEngineCtx; + pAudioEngineCtx = GetDspEngineContext(AudioEngine); + + *State = pAudioEngineCtx->GFxEnabled; + + return STATUS_SUCCESS; +} + +_Use_decl_annotations_ +PAGED_CODE_SEG +NTSTATUS +DspR_EvtAcxAudioEngineAssignEffectsState( + ACXAUDIOENGINE AudioEngine, + ULONG State +) +{ + PAGED_CODE(); + + PDSP_ENGINE_CONTEXT pAudioEngineCtx; + pAudioEngineCtx = GetDspEngineContext(AudioEngine); + + pAudioEngineCtx->GFxEnabled = (BOOLEAN)State; + + return STATUS_SUCCESS; +} + +_Use_decl_annotations_ +PAGED_CODE_SEG +NTSTATUS +DspR_EvtAcxStreamAudioEngineRetrieveEffectsState( + ACXSTREAMAUDIOENGINE StreamAudioEngine, + PULONG State +) +{ + PAGED_CODE(); + + PDSP_STREAMAUDIOENGINE_CONTEXT pStreamAudioEngineCtx; + pStreamAudioEngineCtx = GetDspStreamAudioEngineContext(StreamAudioEngine); + + *State = pStreamAudioEngineCtx->LFxEnabled; + + return STATUS_SUCCESS; +} + +_Use_decl_annotations_ +PAGED_CODE_SEG +NTSTATUS +DspR_EvtAcxStreamAudioEngineAssignEffectsState( + ACXSTREAMAUDIOENGINE StreamAudioEngine, + ULONG State +) +{ + PAGED_CODE(); + + PDSP_STREAMAUDIOENGINE_CONTEXT pStreamAudioEngineCtx; + pStreamAudioEngineCtx = GetDspStreamAudioEngineContext(StreamAudioEngine); + + pStreamAudioEngineCtx->LFxEnabled = (BOOLEAN)State; + + return STATUS_SUCCESS; +} + +_Use_decl_annotations_ +PAGED_CODE_SEG +NTSTATUS +DspR_EvtAcxAudioEngineRetrieveEngineMixFormat( + ACXAUDIOENGINE AudioEngine, + ACXDATAFORMAT * Format + ) +{ + PDSP_ENGINE_CONTEXT audioEngineCtx; + PAGED_CODE(); + + audioEngineCtx = GetDspEngineContext(AudioEngine); + + if (!audioEngineCtx->MixFormat) + { + return STATUS_INVALID_DEVICE_STATE; + } + + *Format = audioEngineCtx->MixFormat; + + return STATUS_SUCCESS; +} + +_Use_decl_annotations_ +PAGED_CODE_SEG +NTSTATUS +DspR_EvtAcxAudioEngineAssignEngineDeviceFormat( + _In_ ACXAUDIOENGINE AudioEngine, + _In_ ACXDATAFORMAT Format + ) +{ + PAGED_CODE(); + + // Get the downstream pin + ACXCIRCUIT parentCircuit = (ACXCIRCUIT)AcxElementGetContainer((ACXELEMENT)AudioEngine); + + ACXPIN downstreamPin = AcxCircuitGetPinById(parentCircuit, DspPinTypeBridge); + if (!downstreamPin) + { + RETURN_NTSTATUS(STATUS_INTERNAL_ERROR); + } + + // Start by getting the list of formats for the raw mode + ACXDATAFORMATLIST formatList; + RETURN_NTSTATUS_IF_FAILED(AcxPinRetrieveModeDataFormatList(downstreamPin, &AUDIO_SIGNALPROCESSINGMODE_RAW, &formatList)); + + // Find the format we were given in that list. + NTSTATUS status = STATUS_NO_MATCH; + + ACX_DATAFORMAT_LIST_ITERATOR formatListIter; + ACX_DATAFORMAT_LIST_ITERATOR_INIT(&formatListIter); + AcxDataFormatListBeginIteration(formatList, &formatListIter); + + ACXDATAFORMAT listFormat; + while (NT_SUCCESS(AcxDataFormatListRetrieveNextFormat(formatList, &formatListIter, &listFormat))) + { + if (AcxDataFormatIsEqual(listFormat, Format)) + { + // Assign the format as the default format. + // Note there is an existing ACX issue with default format assignment - assigning the default + // will only work if the format is already in the list (or is the first format added to the list). + AcxDataFormatListAssignDefaultDataFormat(formatList, listFormat); + + // Use the format we pulled out of our list since it will have an appropriate lifetime + PDSP_ENGINE_CONTEXT audioEngineCtx; + audioEngineCtx = GetDspEngineContext(AudioEngine); + audioEngineCtx->MixFormat = listFormat; + + status = STATUS_SUCCESS; + + break; + } + } + AcxDataFormatListEndIteration(formatList, &formatListIter); + + return status; +} + +_Use_decl_annotations_ +PAGED_CODE_SEG +NTSTATUS +DspR_EvtPeakMeterRetrieveLevelCallback( + ACXPEAKMETER PeakMeter, + ULONG Channel, + LONG * PeakMeterLevel + ) +{ + PAGED_CODE(); + + ASSERT(PeakMeter); + + if (Channel == ALL_CHANNELS_ID) + { + Channel = 0; + } + + PDSP_PEAKMETER_ELEMENT_CONTEXT peakmeterCtx = GetDspPeakMeterElementContext(PeakMeter); + ASSERT(peakmeterCtx); + CSimPeakMeter* peakMeter = (CSimPeakMeter *)peakmeterCtx->peakMeter; + *PeakMeterLevel = peakMeter->GetValue(Channel); + + return STATUS_SUCCESS; +} + +_Use_decl_annotations_ +PAGED_CODE_SEG +NTSTATUS +DspR_EvtMuteAssignState( + ACXMUTE Mute, + ULONG Channel, + ULONG State + ) +{ + PDSP_MUTE_ELEMENT_CONTEXT muteCtx; + ULONG i; + + PAGED_CODE(); + + muteCtx = GetDspMuteElementContext(Mute); + ASSERT(muteCtx); + + if (Channel != ALL_CHANNELS_ID) + { + muteCtx->MuteState[Channel] = State; + } + else + { + for (i = 0; i < MAX_CHANNELS; ++i) + { + muteCtx->MuteState[i] = State; + } + } + + return STATUS_SUCCESS; +} + +_Use_decl_annotations_ +PAGED_CODE_SEG +NTSTATUS +DspR_EvtMuteRetrieveState( + ACXMUTE Mute, + ULONG Channel, + ULONG * State + ) +{ + PDSP_MUTE_ELEMENT_CONTEXT muteCtx; + + PAGED_CODE(); + + muteCtx = GetDspMuteElementContext(Mute); + ASSERT(muteCtx); + + // use first channel for all channels setting. + if (Channel != ALL_CHANNELS_ID) + { + *State = muteCtx->MuteState[Channel]; + } + else + { + *State = muteCtx->MuteState[0]; + } + + return STATUS_SUCCESS; +} + +_Use_decl_annotations_ +PAGED_CODE_SEG +NTSTATUS +DspR_EvtRampedVolumeAssignLevel( + ACXVOLUME Volume, + ULONG Channel, + LONG VolumeLevel, + ACX_VOLUME_CURVE_TYPE, + ULONGLONG + ) +{ + PDSP_VOLUME_ELEMENT_CONTEXT volumeCtx; + ULONG i; + + PAGED_CODE(); + + volumeCtx = GetDspVolumeElementContext(Volume); + ASSERT(volumeCtx); + + if (Channel != ALL_CHANNELS_ID) + { + volumeCtx->VolumeLevel[Channel] = VolumeLevel; + } + else + { + for (i = 0; i < MAX_CHANNELS; ++i) + { + volumeCtx->VolumeLevel[i] = VolumeLevel; + } + } + + return STATUS_SUCCESS; +} + +_Use_decl_annotations_ +PAGED_CODE_SEG +NTSTATUS +DspR_EvtVolumeRetrieveLevel( + ACXVOLUME Volume, + ULONG Channel, + LONG * VolumeLevel +) +{ + PDSP_VOLUME_ELEMENT_CONTEXT volumeCtx; + + PAGED_CODE(); + + volumeCtx = GetDspVolumeElementContext(Volume); + ASSERT(volumeCtx); + + if (Channel != ALL_CHANNELS_ID) + { + *VolumeLevel = volumeCtx->VolumeLevel[Channel]; + } + else + { + *VolumeLevel = volumeCtx->VolumeLevel[0]; + } + + return STATUS_SUCCESS; +} + +_Use_decl_annotations_ +PAGED_CODE_SEG +NTSTATUS +DspR_EvtAcxStreamAudioEngineRetrievePresentationPosition( + _In_ ACXSTREAMAUDIOENGINE StreamAudioEngine, + _Out_ PULONGLONG PositionInBlocks, + _Out_ PULONGLONG QPCPosition +) +{ + NTSTATUS status = STATUS_INVALID_PARAMETER; + ACXSTREAM stream; + PDSP_STREAM_CONTEXT ctx; + CStreamEngine* streamEngine = NULL; + + PAGED_CODE(); + + stream = AcxStreamAudioEngineGetStream(StreamAudioEngine); + if (stream) + { + ctx = GetDspStreamContext(stream); + + streamEngine = static_cast(ctx->StreamEngine); + + status = streamEngine->GetPresentationPosition(PositionInBlocks, QPCPosition); + } + + return status; +} + +_Use_decl_annotations_ +PAGED_CODE_SEG +NTSTATUS +DspR_EvtAcxStreamAudioEngineAssignCurrentWritePosition( + _In_ ACXSTREAMAUDIOENGINE StreamAudioEngine, + _In_ ULONG Position +) +{ + NTSTATUS status = STATUS_INVALID_PARAMETER; + ACXSTREAM stream; + PDSP_STREAM_CONTEXT ctx; + COffloadStreamEngine* streamEngine = NULL; + + PAGED_CODE(); + + stream = AcxStreamAudioEngineGetStream(StreamAudioEngine); + if (stream) + { + ctx = GetDspStreamContext(stream); + + if (ctx->PinType == DspPinTypeOffload) + { + streamEngine = static_cast(ctx->StreamEngine); + + status = streamEngine->SetCurrentWritePosition(Position); + } + else + { + status = STATUS_NOT_SUPPORTED; + } + } + + return status; +} + +_Use_decl_annotations_ +PAGED_CODE_SEG +NTSTATUS +DspR_EvtAcxStreamAudioEngineRetrieveLinearBufferPosition( + _In_ ACXSTREAMAUDIOENGINE StreamAudioEngine, + _Out_ PULONGLONG Position +) +{ + NTSTATUS status = STATUS_INVALID_PARAMETER; + ACXSTREAM stream; + PDSP_STREAM_CONTEXT ctx; + CStreamEngine* streamEngine = NULL; + + PAGED_CODE(); + + stream = AcxStreamAudioEngineGetStream(StreamAudioEngine); + if (stream) + { + ctx = GetDspStreamContext(stream); + + streamEngine = static_cast(ctx->StreamEngine); + + status = streamEngine->GetLinearBufferPosition(Position); + } + + return status; +} + +_Use_decl_annotations_ +PAGED_CODE_SEG +NTSTATUS +DspR_EvtAcxStreamAudioEngineAssignLastBufferPosition( + _In_ ACXSTREAMAUDIOENGINE StreamAudioEngine, + _In_ ULONG Position +) +{ + NTSTATUS status = STATUS_INVALID_PARAMETER; + ACXSTREAM stream; + PDSP_STREAM_CONTEXT ctx; + COffloadStreamEngine* streamEngine = NULL; + + PAGED_CODE(); + + stream = AcxStreamAudioEngineGetStream(StreamAudioEngine); + if (stream) + { + ctx = GetDspStreamContext(stream); + + if (ctx->PinType == DspPinTypeOffload) + { + streamEngine = static_cast(ctx->StreamEngine); + + status = streamEngine->SetLastBufferPosition(Position); + } + else + { + status = STATUS_NOT_SUPPORTED; + } + } + + return status; +} + +_Use_decl_annotations_ +PAGED_CODE_SEG +NTSTATUS +DspR_EvtAcxStreamAudioEngineAssignLoopbackProtection( + _In_ ACXSTREAMAUDIOENGINE, + _In_ ACX_CONSTRICTOR_OPTION +) +{ + PAGED_CODE(); + + return STATUS_SUCCESS; +} + diff --git a/audio/Soundwire/Samples/SdcaVad/SdcaVDsp/resources.rc b/audio/Soundwire/Samples/SdcaVad/SdcaVDsp/resources.rc new file mode 100644 index 000000000..a2cc093a2 --- /dev/null +++ b/audio/Soundwire/Samples/SdcaVad/SdcaVDsp/resources.rc @@ -0,0 +1,12 @@ +#include + +#include + +#define VER_FILETYPE VFT_DRV +#define VER_FILESUBTYPE VFT2_DRV_SYSTEM +#define VER_FILEDESCRIPTION_STR "ACX v1.0 DSP Audio Driver" +#define VER_INTERNALNAME_STR "SDCAVDsp.sys" +#define VER_ORIGINALFILENAME_STR "SDCAVDsp.sys" + +#include "common.ver" + diff --git a/audio/Soundwire/Samples/SdcaVad/SdcaVDsp/savedata.cpp b/audio/Soundwire/Samples/SdcaVad/SdcaVDsp/savedata.cpp new file mode 100644 index 000000000..4d70ad099 --- /dev/null +++ b/audio/Soundwire/Samples/SdcaVad/SdcaVDsp/savedata.cpp @@ -0,0 +1,1043 @@ +/*++ + +Copyright (c) Microsoft Corporation All Rights Reserved + +Module Name: + + savedata.cpp + +Abstract: + + Implementation of ACX DSP Test Driver data saving class. + + To save the playback data to disk, this class maintains a circular data + buffer, associated frame structures and worker items to save frames to + disk. + Each frame structure represents a portion of buffer. When that portion + of frame is full, a workitem is scheduled to save it to disk. + + + +--*/ +#pragma warning (disable : 4127) +#pragma warning (disable : 26165) + + +#include "private.h" +#include +#include "stdunk.h" +#include +#include +#include +#include "savedata.h" +#include // This is for using RtlStringCbPrintf + +#define SAVEDATA_POOLTAG 'TDVS' +#define SAVEDATA_POOLTAG1 '1DVS' +#define SAVEDATA_POOLTAG2 '2DVS' +#define SAVEDATA_POOLTAG3 '3DVS' +#define SAVEDATA_POOLTAG4 '4DVS' +#define SAVEDATA_POOLTAG5 '5DVS' +#define SAVEDATA_POOLTAG6 '6DVS' +#define SAVEDATA_POOLTAG7 '7DVS' + +//============================================================================= +// Defines +//============================================================================= +#define RIFF_TAG 0x46464952; +#define WAVE_TAG 0x45564157; +#define FMT__TAG 0x20746D66; +#define DATA_TAG 0x61746164; + +#define DEFAULT_FRAME_COUNT 4 +#define DEFAULT_FRAME_SIZE PAGE_SIZE * 4 +#define DEFAULT_BUFFER_SIZE DEFAULT_FRAME_SIZE * DEFAULT_FRAME_COUNT + +#define DEFAULT_FILE_NAME L"\\DosDevices\\C:\\STREAM" +#define OFFLOAD_FILE_NAME L"OFFLOAD" +#define HOST_FILE_NAME L"HOST" + +#define MAX_WORKER_ITEM_COUNT 15 + + +PSAVEWORKER_PARAM CSaveData::m_pWorkItems = NULL; +PDEVICE_OBJECT CSaveData::m_pDeviceObject = NULL; + +//============================================================================= +// Statics +//============================================================================= +ULONG CSaveData::m_ulStreamId = 0; +ULONG CSaveData::m_ulOffloadStreamId = 0; + +//============================================================================= +// CSaveData +//============================================================================= + +//============================================================================= +_Use_decl_annotations_ +PAGED_CODE_SEG +CSaveData::CSaveData() +: m_pDataBuffer(NULL), + m_FileHandle(NULL), + m_ulFrameCount(DEFAULT_FRAME_COUNT), + m_ulBufferSize(DEFAULT_BUFFER_SIZE), + m_ulFrameSize(DEFAULT_FRAME_SIZE), + m_ulBufferOffset(0), + m_ulFrameIndex(0), + m_fFrameUsed(NULL), + m_waveFormat(NULL), + m_pFilePtr(NULL), + m_fWriteDisabled(FALSE), + m_bInitialized(FALSE) +{ + PAGED_CODE(); + + m_FileHeader.dwRiff = RIFF_TAG; + m_FileHeader.dwFileSize = 0; + m_FileHeader.dwWave = WAVE_TAG; + m_FileHeader.dwFormat = FMT__TAG; + m_FileHeader.dwFormatLength = sizeof(WAVEFORMATEX); + + m_DataHeader.dwData = DATA_TAG; + m_DataHeader.dwDataLength = 0; + + RtlZeroMemory(&m_objectAttributes, sizeof(m_objectAttributes)); +} // CSaveData + +//============================================================================= +_Use_decl_annotations_ +PAGED_CODE_SEG +CSaveData::~CSaveData() +{ + PAGED_CODE(); + Cleanup(); +} // CSaveData + +void +_Use_decl_annotations_ +PAGED_CODE_SEG +CSaveData::Cleanup +( + void +) +{ + PAGED_CODE(); + + // Update the wave header in data file with real file size. + // + if(m_pFilePtr) + { + // RIFF header, whose size is the whole file size minus RIFF header. + m_FileHeader.dwFileSize = + (DWORD)m_pFilePtr->QuadPart - 2 * sizeof(DWORD); + // The data length is the size of all the audio that was written. + // It gets calculated by taking: + m_DataHeader.dwDataLength = (DWORD)m_pFilePtr->QuadPart - // the whole file size, + sizeof(m_FileHeader) - // minus the file header, + m_FileHeader.dwFormatLength - // minus the format, + sizeof(m_DataHeader); // minus the data header itself. + + if (STATUS_SUCCESS == KeWaitForSingleObject + ( + &m_FileSync, + Executive, + KernelMode, + FALSE, + NULL + )) + { + if (NT_SUCCESS(FileOpen(FALSE))) + { + FileWriteHeader(); + + FileClose(); + } + + KeReleaseMutex(&m_FileSync, FALSE); + } + + m_FileHeader.dwRiff = RIFF_TAG; + m_FileHeader.dwFileSize = 0; + m_FileHeader.dwWave = WAVE_TAG; + m_FileHeader.dwFormat = FMT__TAG; + m_FileHeader.dwFormatLength = sizeof(WAVEFORMATEX); + + m_DataHeader.dwData = DATA_TAG; + m_DataHeader.dwDataLength = 0; + m_pFilePtr = NULL; + } + + if (m_waveFormat) + { + ExFreePoolWithTag(m_waveFormat, SAVEDATA_POOLTAG1); + m_waveFormat = NULL; + } + + if (m_fFrameUsed) + { + ExFreePoolWithTag(m_fFrameUsed, SAVEDATA_POOLTAG2); + m_fFrameUsed = NULL; + } + + if (m_FileName.Buffer) + { + ExFreePoolWithTag(m_FileName.Buffer, SAVEDATA_POOLTAG3); + m_FileName.Buffer = NULL; + } + + if (m_pDataBuffer) + { + ExFreePoolWithTag(m_pDataBuffer, SAVEDATA_POOLTAG4); + m_pDataBuffer = NULL; + } +} + +//============================================================================= +_Use_decl_annotations_ +PAGED_CODE_SEG +void +CSaveData::DestroyWorkItems +( + void +) +{ + PAGED_CODE(); + + if (m_pWorkItems) + { + for (int i = 0; i < MAX_WORKER_ITEM_COUNT; i++) + { + if (m_pWorkItems[i].WorkItem!=NULL) + { + IoFreeWorkItem(m_pWorkItems[i].WorkItem); + m_pWorkItems[i].WorkItem = NULL; + } + } + ExFreePoolWithTag(m_pWorkItems, SAVEDATA_POOLTAG); + m_pWorkItems = NULL; + } + +} // DestroyWorkItems + +//============================================================================= +_Use_decl_annotations_ +PAGED_CODE_SEG +void +CSaveData::Disable +( + _In_ BOOL fDisable +) +{ + PAGED_CODE(); + + m_fWriteDisabled = fDisable; +} // Disable + +//============================================================================= +_Use_decl_annotations_ +PAGED_CODE_SEG +NTSTATUS +CSaveData::FileClose(void) +{ + PAGED_CODE(); + + NTSTATUS ntStatus = STATUS_SUCCESS; + + if (m_FileHandle) + { + ntStatus = ZwClose(m_FileHandle); + m_FileHandle = NULL; + } + + return ntStatus; +} // FileClose + +//============================================================================= +_Use_decl_annotations_ +PAGED_CODE_SEG +NTSTATUS +CSaveData::FileOpen +( + BOOL fOverWrite +) +{ + PAGED_CODE(); + + NTSTATUS ntStatus = STATUS_SUCCESS; + IO_STATUS_BLOCK ioStatusBlock; + + if( FALSE == m_bInitialized ) + { + return STATUS_UNSUCCESSFUL; + } + + if(!m_FileHandle) + { + ntStatus = + ZwCreateFile + ( + &m_FileHandle, + GENERIC_WRITE | SYNCHRONIZE, + &m_objectAttributes, + &ioStatusBlock, + NULL, + FILE_ATTRIBUTE_NORMAL, + 0, + fOverWrite ? FILE_OVERWRITE_IF : FILE_OPEN_IF, + FILE_NON_DIRECTORY_FILE | FILE_SYNCHRONOUS_IO_NONALERT, + NULL, + 0 + ); + } + + return ntStatus; +} // FileOpen + +//============================================================================= +_Use_decl_annotations_ +PAGED_CODE_SEG +NTSTATUS +CSaveData::FileWrite +( + PBYTE pData, + ULONG ulDataSize +) +{ + PAGED_CODE(); + + ASSERT(pData); + ASSERT(m_pFilePtr); + + NTSTATUS ntStatus; + + if (m_FileHandle) + { + IO_STATUS_BLOCK ioStatusBlock; + + ntStatus = ZwWriteFile( m_FileHandle, + NULL, + NULL, + NULL, + &ioStatusBlock, + pData, + ulDataSize, + m_pFilePtr, + NULL); + + if (NT_SUCCESS(ntStatus)) + { + ASSERT(ioStatusBlock.Information == ulDataSize); + + m_pFilePtr->QuadPart += ulDataSize; + } + } + else + { + ntStatus = STATUS_INVALID_HANDLE; + } + + return ntStatus; +} // FileWrite + +//============================================================================= +_Use_decl_annotations_ +PAGED_CODE_SEG +NTSTATUS +CSaveData::FileWriteHeader(void) +{ + PAGED_CODE(); + + NTSTATUS ntStatus; + + if (m_FileHandle && m_waveFormat) + { + IO_STATUS_BLOCK ioStatusBlock; + + m_pFilePtr->QuadPart = 0; + + m_FileHeader.dwFormatLength = (m_waveFormat->wFormatTag == WAVE_FORMAT_PCM) ? + sizeof( PCMWAVEFORMAT ) : + sizeof( WAVEFORMATEX ) + m_waveFormat->cbSize; + + ntStatus = ZwWriteFile( m_FileHandle, + NULL, + NULL, + NULL, + &ioStatusBlock, + &m_FileHeader, + sizeof(m_FileHeader), + m_pFilePtr, + NULL); + + if (NT_SUCCESS(ntStatus)) + { + m_pFilePtr->QuadPart += sizeof(m_FileHeader); + + ntStatus = ZwWriteFile( m_FileHandle, + NULL, + NULL, + NULL, + &ioStatusBlock, + m_waveFormat, + m_FileHeader.dwFormatLength, + m_pFilePtr, + NULL); + } + + if (NT_SUCCESS(ntStatus)) + { + m_pFilePtr->QuadPart += m_FileHeader.dwFormatLength; + + ntStatus = ZwWriteFile( m_FileHandle, + NULL, + NULL, + NULL, + &ioStatusBlock, + &m_DataHeader, + sizeof(m_DataHeader), + m_pFilePtr, + NULL); + } + + if (NT_SUCCESS(ntStatus)) + { + m_pFilePtr->QuadPart += sizeof(m_DataHeader); + } + } + else + { + ntStatus = STATUS_INVALID_HANDLE; + } + + + return ntStatus; +} // FileWriteHeader + +_Use_decl_annotations_ +PAGED_CODE_SEG +NTSTATUS +CSaveData::SetDeviceObject +( + PDEVICE_OBJECT DeviceObject +) +{ + PAGED_CODE(); + + ASSERT(DeviceObject); + + NTSTATUS ntStatus = STATUS_SUCCESS; + + m_pDeviceObject = DeviceObject; + return ntStatus; +} + +_Use_decl_annotations_ +PAGED_CODE_SEG +PDEVICE_OBJECT +CSaveData::GetDeviceObject +( + void +) +{ + PAGED_CODE(); + + return m_pDeviceObject; +} + +//============================================================================= +_Use_decl_annotations_ +#pragma code_seg() +PSAVEWORKER_PARAM +CSaveData::GetNewWorkItem +( + void +) +{ + LARGE_INTEGER timeOut = { 0 }; + NTSTATUS ntStatus; + + for (int i = 0; i < MAX_WORKER_ITEM_COUNT; i++) + { + ntStatus = + KeWaitForSingleObject + ( + &m_pWorkItems[i].EventDone, + Executive, + KernelMode, + FALSE, + &timeOut + ); + if (STATUS_SUCCESS == ntStatus) + { + if (m_pWorkItems[i].WorkItem) + return &(m_pWorkItems[i]); + else + return NULL; + } + } + + return NULL; +} // GetNewWorkItem + +//============================================================================= +_Use_decl_annotations_ +PAGED_CODE_SEG +NTSTATUS +CSaveData::Initialize +( + BOOL _bOffloaded +) +{ + PAGED_CODE(); + + NTSTATUS ntStatus = STATUS_SUCCESS; + WCHAR szTemp[MAX_PATH]; + size_t cLen; + + if (_bOffloaded) + { + m_ulOffloadStreamId++; + } + else + { + m_ulStreamId++; + } + + // Allocate data file name. + // + RtlStringCchPrintfW(szTemp, MAX_PATH, L"%s_%s_%d.wav", DEFAULT_FILE_NAME, _bOffloaded ? OFFLOAD_FILE_NAME : HOST_FILE_NAME, _bOffloaded ? m_ulOffloadStreamId : m_ulStreamId); + m_FileName.Length = 0; + ntStatus = RtlStringCchLengthW (szTemp, sizeof(szTemp)/sizeof(szTemp[0]), &cLen); + if (NT_SUCCESS(ntStatus)) + { + m_FileName.MaximumLength = (USHORT)((cLen * sizeof(WCHAR)) + sizeof(WCHAR));//convert to wchar and add room for NULL + m_FileName.Buffer = (PWSTR) + ExAllocatePool2 + ( + POOL_FLAG_PAGED, + m_FileName.MaximumLength, + SAVEDATA_POOLTAG3 + ); + if (!m_FileName.Buffer) + { + ntStatus = STATUS_INSUFFICIENT_RESOURCES; + } + } + + // Allocate memory for data buffer. + // + if (NT_SUCCESS(ntStatus)) + { + RtlStringCbCopyW(m_FileName.Buffer, m_FileName.MaximumLength, szTemp); + m_FileName.Length = (USHORT)wcslen(m_FileName.Buffer) * sizeof(WCHAR); + + m_pDataBuffer = (PBYTE) + ExAllocatePool2 + ( + POOL_FLAG_NON_PAGED, + m_ulBufferSize, + SAVEDATA_POOLTAG4 + ); + if (!m_pDataBuffer) + { + ntStatus = STATUS_INSUFFICIENT_RESOURCES; + } + else + { + // ExAllocatePool2 zeros memory. + } + } + + // Allocate memory for frame usage flags and m_pFilePtr. + // + if (NT_SUCCESS(ntStatus)) + { + m_fFrameUsed = (PBOOL) + ExAllocatePool2 + ( + POOL_FLAG_NON_PAGED, + m_ulFrameCount * sizeof(BOOL) + + sizeof(LARGE_INTEGER), + SAVEDATA_POOLTAG2 + ); + if (!m_fFrameUsed) + { + ntStatus = STATUS_INSUFFICIENT_RESOURCES; + } + } + + // Initialize the spinlock to synchronize access to the frames + // + KeInitializeSpinLock ( &m_FrameInUseSpinLock ) ; + + // Initialize the file mutex + // + KeInitializeMutex( &m_FileSync, 1 ) ; + + // Open the data file. + // + if (NT_SUCCESS(ntStatus)) + { + // m_fFrameUsed has additional memory to hold m_pFilePtr + // + m_pFilePtr = (PLARGE_INTEGER) + (((PBYTE) m_fFrameUsed) + m_ulFrameCount * sizeof(BOOL)); + RtlZeroMemory(m_fFrameUsed, m_ulFrameCount * sizeof(BOOL) + sizeof(LARGE_INTEGER)); + + // Create data file. + InitializeObjectAttributes + ( + &m_objectAttributes, + &m_FileName, + OBJ_CASE_INSENSITIVE|OBJ_KERNEL_HANDLE, + NULL, + NULL + ); + + m_bInitialized = TRUE; + + // Write wave header information to data file. + ntStatus = KeWaitForSingleObject + ( + &m_FileSync, + Executive, + KernelMode, + FALSE, + NULL + ); + + if (STATUS_SUCCESS == ntStatus) + { + ntStatus = FileOpen(TRUE); + if (NT_SUCCESS(ntStatus)) + { + ntStatus = FileWriteHeader(); + + FileClose(); + } + + KeReleaseMutex( &m_FileSync, FALSE ); + } + } + + return ntStatus; +} // Initialize + +//============================================================================= +_Use_decl_annotations_ +PAGED_CODE_SEG +NTSTATUS +CSaveData::InitializeWorkItems +( + PDEVICE_OBJECT DeviceObject +) +{ + PAGED_CODE(); + + ASSERT(DeviceObject); + + NTSTATUS ntStatus = STATUS_SUCCESS; + + if (m_pWorkItems != NULL) + { + return ntStatus; + } + + m_pWorkItems = (PSAVEWORKER_PARAM) + ExAllocatePool2 + ( + POOL_FLAG_NON_PAGED, + sizeof(SAVEWORKER_PARAM) * MAX_WORKER_ITEM_COUNT, + SAVEDATA_POOLTAG + ); + if (m_pWorkItems) + { + for (int i = 0; i < MAX_WORKER_ITEM_COUNT; i++) + { + + m_pWorkItems[i].WorkItem = IoAllocateWorkItem(DeviceObject); + if(m_pWorkItems[i].WorkItem == NULL) + { + return STATUS_INSUFFICIENT_RESOURCES; + } + KeInitializeEvent + ( + &m_pWorkItems[i].EventDone, + NotificationEvent, + TRUE + ); + } + } + else + { + ntStatus = STATUS_INSUFFICIENT_RESOURCES; + } + + return ntStatus; +} // InitializeWorkItems + +//============================================================================= +_Use_decl_annotations_ +PAGED_CODE_SEG +VOID +SaveFrameWorkerCallback +( + PDEVICE_OBJECT pDeviceObject, + PVOID Context +) +{ + UNREFERENCED_PARAMETER(pDeviceObject); + + PAGED_CODE(); + + ASSERT(Context); + + PSAVEWORKER_PARAM pParam = (PSAVEWORKER_PARAM) Context; + PCSaveData pSaveData; + + if (NULL == pParam) + { + // This is completely unexpected, assert here. + // + ASSERT(pParam); + return; + } + + ASSERT(pParam->pSaveData); + ASSERT(pParam->pSaveData->m_fFrameUsed); + + if (pParam->WorkItem) + { + pSaveData = pParam->pSaveData; + + if (STATUS_SUCCESS == KeWaitForSingleObject + ( + &pSaveData->m_FileSync, + Executive, + KernelMode, + FALSE, + NULL + )) + { + if (NT_SUCCESS(pSaveData->FileOpen(FALSE))) + { + pSaveData->FileWrite(pParam->pData, pParam->ulDataSize); + pSaveData->FileClose(); + } + InterlockedExchange( (LONG *)&(pSaveData->m_fFrameUsed[pParam->ulFrameNo]), FALSE ); + + KeReleaseMutex( &pSaveData->m_FileSync, FALSE ); + } + } + + KeSetEvent(&pParam->EventDone, 0, FALSE); +} // SaveFrameWorkerCallback + +//============================================================================= +_Use_decl_annotations_ +PAGED_CODE_SEG +NTSTATUS +CSaveData::SetDataFormat +( + PKSDATAFORMAT pDataFormat +) +{ + PAGED_CODE(); + NTSTATUS ntStatus = STATUS_SUCCESS; + + ASSERT(pDataFormat); + + PWAVEFORMATEX pwfx = NULL; + + if (IsEqualGUIDAligned(pDataFormat->Specifier, + KSDATAFORMAT_SPECIFIER_DSOUND)) + { + pwfx = + &(((PKSDATAFORMAT_DSOUND) pDataFormat)->BufferDesc.WaveFormatEx); + } + else if (IsEqualGUIDAligned(pDataFormat->Specifier, + KSDATAFORMAT_SPECIFIER_WAVEFORMATEX)) + { + pwfx = &((PKSDATAFORMAT_WAVEFORMATEX) pDataFormat)->WaveFormatEx; + } + + if (pwfx) + { + // Free the previously allocated waveformat + if (m_waveFormat) + { + ExFreePoolWithTag(m_waveFormat, SAVEDATA_POOLTAG1); + } + + m_waveFormat = (PWAVEFORMATEX) + ExAllocatePool2 + ( + POOL_FLAG_NON_PAGED, + (pwfx->wFormatTag == WAVE_FORMAT_PCM) ? + sizeof( PCMWAVEFORMAT ) : + sizeof( WAVEFORMATEX ) + pwfx->cbSize, + SAVEDATA_POOLTAG1 + ); + + if(m_waveFormat) + { + RtlCopyMemory( m_waveFormat, + pwfx, + (pwfx->wFormatTag == WAVE_FORMAT_PCM) ? + sizeof( PCMWAVEFORMAT ) : + sizeof( WAVEFORMATEX ) + pwfx->cbSize); + } + else + { + ntStatus = STATUS_INSUFFICIENT_RESOURCES; + } + } + return ntStatus; +} // SetDataFormat + +//============================================================================= +_Use_decl_annotations_ +PAGED_CODE_SEG +NTSTATUS +CSaveData::SetMaxWriteSize +( + ULONG ulMaxWriteSize +) +{ + PAGED_CODE(); + + NTSTATUS ntStatus = STATUS_SUCCESS; + ULONG bufferSize = 0; + PBYTE buffer = NULL; + + // + // Compute new buffer size. + // + ntStatus = RtlULongMult(ulMaxWriteSize, DEFAULT_FRAME_COUNT, &bufferSize); + if (!NT_SUCCESS(ntStatus)) + { + ntStatus = STATUS_INSUFFICIENT_RESOURCES; + goto Done; + } + + // + // Alloc memory for buffer. + // + buffer = (PBYTE) + ExAllocatePool2 + ( + POOL_FLAG_NON_PAGED, + bufferSize, + SAVEDATA_POOLTAG4 + ); + if (!buffer) + { + ntStatus = STATUS_INSUFFICIENT_RESOURCES; + goto Done; + } + + // ExAllocatePool2 zeros memory. + + // + // Free old one. + // + if (m_pDataBuffer) + { + ExFreePoolWithTag(m_pDataBuffer, SAVEDATA_POOLTAG4); + m_pDataBuffer = NULL; + } + + // + // Init new buffer settings. + // + m_pDataBuffer = buffer; + m_ulBufferSize = bufferSize; + m_ulFrameSize = ulMaxWriteSize; + + ntStatus = STATUS_SUCCESS; + +Done: + return ntStatus; +} // SetDataFormat + +//============================================================================= +_Use_decl_annotations_ +PAGED_CODE_SEG +void +CSaveData::ReadData +( + PBYTE pBuffer, + ULONG ulByteCount +) +{ + UNREFERENCED_PARAMETER(pBuffer); + UNREFERENCED_PARAMETER(ulByteCount); + + PAGED_CODE(); + + // Not implemented yet. +} // ReadData + +//============================================================================= +_Use_decl_annotations_ +#pragma code_seg() +void +CSaveData::SaveFrame +( + ULONG ulFrameNo, + ULONG ulDataSize +) +{ + PSAVEWORKER_PARAM pParam = NULL; + + pParam = GetNewWorkItem(); + if (pParam) + { + pParam->pSaveData = this; + pParam->ulFrameNo = ulFrameNo; + pParam->ulDataSize = ulDataSize; + pParam->pData = m_pDataBuffer + ulFrameNo * m_ulFrameSize; + KeResetEvent(&pParam->EventDone); + IoQueueWorkItem(pParam->WorkItem, SaveFrameWorkerCallback, + CriticalWorkQueue, (PVOID)pParam); + } +} // SaveFrame + +//============================================================================= +void +_Use_decl_annotations_ +PAGED_CODE_SEG +CSaveData::WaitAllWorkItems +( + void +) +{ + PAGED_CODE(); + + // Save the last partially-filled frame + if (m_ulBufferOffset > m_ulFrameIndex * m_ulFrameSize) + { + ULONG size; + + size = m_ulBufferOffset - m_ulFrameIndex * m_ulFrameSize; + SaveFrame(m_ulFrameIndex, size); + } + + for (int i = 0; i < MAX_WORKER_ITEM_COUNT; i++) + { + KeWaitForSingleObject + ( + &(m_pWorkItems[i].EventDone), + Executive, + KernelMode, + FALSE, + NULL + ); + } +} // WaitAllWorkItems + +//============================================================================= +_Use_decl_annotations_ +#pragma code_seg() +void +CSaveData::WriteData +( + PBYTE pBuffer, + ULONG ulByteCount +) +{ + ASSERT(pBuffer); + + BOOL fSaveFrame = FALSE; + ULONG ulSaveFrameIndex = 0; + KIRQL oldIrql; + + // If stream writing is disabled, then exit. + // + if (m_fWriteDisabled) + { + return; + } + + if( 0 == ulByteCount ) + { + return; + } + + // The logic below assumes that write size is <= than frame size. + if (ulByteCount > m_ulFrameSize) + { + ulByteCount = m_ulFrameSize; + } + + // Check to see if this frame is available. + KeAcquireSpinLock(&m_FrameInUseSpinLock, &oldIrql); + if (!m_fFrameUsed[m_ulFrameIndex]) + { + KeReleaseSpinLock(&m_FrameInUseSpinLock, oldIrql ); + + ULONG ulWriteBytes = ulByteCount; + + if( (m_ulBufferSize - m_ulBufferOffset) < ulWriteBytes ) + { + ulWriteBytes = m_ulBufferSize - m_ulBufferOffset; + } + + RtlCopyMemory(m_pDataBuffer + m_ulBufferOffset, pBuffer, ulWriteBytes); + m_ulBufferOffset += ulWriteBytes; + + // Check to see if we need to save this frame + if (m_ulBufferOffset >= ((m_ulFrameIndex + 1) * m_ulFrameSize)) + { + fSaveFrame = TRUE; + } + + // Loop the buffer, if we reached the end. + if (m_ulBufferOffset == m_ulBufferSize) + { + fSaveFrame = TRUE; + m_ulBufferOffset = 0; + } + + if (fSaveFrame) + { + InterlockedExchange( (LONG *)&(m_fFrameUsed[m_ulFrameIndex]), TRUE ); + ulSaveFrameIndex = m_ulFrameIndex; + m_ulFrameIndex = (m_ulFrameIndex + 1) % m_ulFrameCount; + } + + // Write the left over if the next frame is available. + if (ulWriteBytes != ulByteCount) + { + KeAcquireSpinLock(&m_FrameInUseSpinLock, &oldIrql ); + if (!m_fFrameUsed[m_ulFrameIndex]) + { + KeReleaseSpinLock(&m_FrameInUseSpinLock, oldIrql ); + RtlCopyMemory + ( + m_pDataBuffer + m_ulBufferOffset, + pBuffer + ulWriteBytes, + ulByteCount - ulWriteBytes + ); + + m_ulBufferOffset += ulByteCount - ulWriteBytes; + } + else + { + KeReleaseSpinLock(&m_FrameInUseSpinLock, oldIrql); + } + } + + if (fSaveFrame) + { + SaveFrame(ulSaveFrameIndex, m_ulFrameSize); + } + } + else + { + KeReleaseSpinLock(&m_FrameInUseSpinLock, oldIrql ); + } + +} // WriteData + + diff --git a/audio/Soundwire/Samples/SdcaVad/SdcaVDsp/savedata.h b/audio/Soundwire/Samples/SdcaVad/SdcaVDsp/savedata.h new file mode 100644 index 000000000..2cc1eb47e --- /dev/null +++ b/audio/Soundwire/Samples/SdcaVad/SdcaVDsp/savedata.h @@ -0,0 +1,257 @@ +/*++ + +Copyright (c) Microsoft Corporation All Rights Reserved + +Module Name: + + savedata.h + +Abstract: + + Declaration of ACX DSP Test Driver data saving class. This class supplies services +to save data to disk. + + +--*/ + +#pragma once + +//----------------------------------------------------------------------------- +// Forward declaration +//----------------------------------------------------------------------------- +class CSaveData; +typedef CSaveData *PCSaveData; + + +//----------------------------------------------------------------------------- +// Structs +//----------------------------------------------------------------------------- + +// Parameter to workitem. +#include +typedef struct _SAVEWORKER_PARAM { + PIO_WORKITEM WorkItem; + ULONG ulFrameNo; + ULONG ulDataSize; + PBYTE pData; + PCSaveData pSaveData; + KEVENT EventDone; +} SAVEWORKER_PARAM; +typedef SAVEWORKER_PARAM *PSAVEWORKER_PARAM; +#include + +// wave file header. +#include +typedef struct _OUTPUT_FILE_HEADER +{ + DWORD dwRiff; + DWORD dwFileSize; + DWORD dwWave; + DWORD dwFormat; + DWORD dwFormatLength; +} OUTPUT_FILE_HEADER; +typedef OUTPUT_FILE_HEADER *POUTPUT_FILE_HEADER; + +typedef struct _OUTPUT_DATA_HEADER +{ + DWORD dwData; + DWORD dwDataLength; +} OUTPUT_DATA_HEADER; +typedef OUTPUT_DATA_HEADER *POUTPUT_DATA_HEADER; + +#include + +//----------------------------------------------------------------------------- +// Classes +//----------------------------------------------------------------------------- + +/////////////////////////////////////////////////////////////////////////////// +// CSaveData +// Saves the wave data to disk. +// +__drv_maxIRQL(PASSIVE_LEVEL) +PAGED_CODE_SEG +IO_WORKITEM_ROUTINE SaveFrameWorkerCallback; + +class CSaveData +{ +protected: + UNICODE_STRING m_FileName; // DataFile name. + HANDLE m_FileHandle; // DataFile handle. + PBYTE m_pDataBuffer; // Data buffer. + ULONG m_ulBufferSize; // Total buffer size. + + ULONG m_ulFrameIndex; // Current Frame. + ULONG m_ulFrameCount; // Frame count. + ULONG m_ulFrameSize; + ULONG m_ulBufferOffset; // index in buffer. + PBOOL m_fFrameUsed; // Frame usage table. + KSPIN_LOCK m_FrameInUseSpinLock; // Spinlock for synch. + KMUTEX m_FileSync; // Synchronizes file access + + OBJECT_ATTRIBUTES m_objectAttributes; // Used for opening file. + + OUTPUT_FILE_HEADER m_FileHeader; + PWAVEFORMATEX m_waveFormat; + OUTPUT_DATA_HEADER m_DataHeader; + PLARGE_INTEGER m_pFilePtr; + + static PDEVICE_OBJECT m_pDeviceObject; + static ULONG m_ulStreamId; + static ULONG m_ulOffloadStreamId; + static PSAVEWORKER_PARAM m_pWorkItems; + + BOOL m_fWriteDisabled; + + BOOL m_bInitialized; + +public: + __drv_maxIRQL(PASSIVE_LEVEL) + PAGED_CODE_SEG + CSaveData(); + + __drv_maxIRQL(PASSIVE_LEVEL) + PAGED_CODE_SEG + ~CSaveData(); + + __drv_maxIRQL(PASSIVE_LEVEL) + PAGED_CODE_SEG + void + Cleanup( + void + ); + + static + __drv_maxIRQL(PASSIVE_LEVEL) + PAGED_CODE_SEG + NTSTATUS + InitializeWorkItems( + _In_ PDEVICE_OBJECT DeviceObject + ); + + static + __drv_maxIRQL(PASSIVE_LEVEL) + PAGED_CODE_SEG + void + DestroyWorkItems( + void + ); + + __drv_maxIRQL(PASSIVE_LEVEL) + PAGED_CODE_SEG + void + Disable( + _In_ BOOL fDisable + ); + + static + __drv_maxIRQL(DISPATCH_LEVEL) + #pragma code_seg() + PSAVEWORKER_PARAM + GetNewWorkItem( + void + ); + + __drv_maxIRQL(PASSIVE_LEVEL) + PAGED_CODE_SEG + NTSTATUS + Initialize( + _In_ BOOL _bOffloaded + ); + + static + __drv_maxIRQL(PASSIVE_LEVEL) + PAGED_CODE_SEG + NTSTATUS + SetDeviceObject( + _In_ PDEVICE_OBJECT DeviceObject + ); + + static + __drv_maxIRQL(PASSIVE_LEVEL) + PAGED_CODE_SEG + PDEVICE_OBJECT + GetDeviceObject( + void + ); + + __drv_maxIRQL(PASSIVE_LEVEL) + PAGED_CODE_SEG + void + ReadData( + _Inout_updates_bytes_all_(ulByteCount) PBYTE pBuffer, + _In_ ULONG ulByteCount + ); + + __drv_maxIRQL(PASSIVE_LEVEL) + PAGED_CODE_SEG + NTSTATUS + SetDataFormat( + _In_ PKSDATAFORMAT pDataFormat + ); + + __drv_maxIRQL(PASSIVE_LEVEL) + PAGED_CODE_SEG + NTSTATUS + SetMaxWriteSize( + _In_ ULONG ulMaxWriteSize + ); + + __drv_maxIRQL(PASSIVE_LEVEL) + PAGED_CODE_SEG + void + WaitAllWorkItems( + void + ); + + __drv_maxIRQL(DISPATCH_LEVEL) + #pragma code_seg() + void + WriteData( + _In_reads_bytes_(ulByteCount) PBYTE pBuffer, + _In_ ULONG ulByteCount + ); + +private: + __drv_maxIRQL(PASSIVE_LEVEL) + PAGED_CODE_SEG + NTSTATUS + FileClose( + void + ); + + __drv_maxIRQL(PASSIVE_LEVEL) + PAGED_CODE_SEG + NTSTATUS + FileOpen( + _In_ BOOL fOverWrite + ); + + __drv_maxIRQL(PASSIVE_LEVEL) + PAGED_CODE_SEG + NTSTATUS + FileWrite( + _In_reads_bytes_(ulDataSize) PBYTE pData, + _In_ ULONG ulDataSize + ); + + __drv_maxIRQL(PASSIVE_LEVEL) + PAGED_CODE_SEG + NTSTATUS + FileWriteHeader( + void + ); + + __drv_maxIRQL(DISPATCH_LEVEL) + #pragma code_seg() + void + SaveFrame( + _In_ ULONG ulFrameNo, + _In_ ULONG ulDataSize + ); + + friend + IO_WORKITEM_ROUTINE SaveFrameWorkerCallback; +}; +typedef CSaveData *PCSaveData; + diff --git a/audio/Soundwire/Samples/SdcaVad/SdcaVDsp/streamengine.cpp b/audio/Soundwire/Samples/SdcaVad/SdcaVDsp/streamengine.cpp new file mode 100644 index 000000000..a979a1297 --- /dev/null +++ b/audio/Soundwire/Samples/SdcaVad/SdcaVDsp/streamengine.cpp @@ -0,0 +1,1053 @@ +/*++ + + THIS CODE AND INFORMATION IS PROVIDED "AS IS" WITHOUT WARRANTY OF ANY + KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE + IMPLIED WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A PARTICULAR + PURPOSE. + +Module Name: + + StreamEngine.cpp + +Abstract: + + Virtual Streaming Engine - this module controls streaming logic for + the device. + +Environment: + + Kernel mode + +--*/ + +#include "private.h" +#include +#include "stdunk.h" +#include +#include +#include +#include "streamengine.h" + +#ifndef __INTELLISENSE__ +#include "streamengine.tmh" +#endif + +_Use_decl_annotations_ +PAGED_CODE_SEG +CStreamEngine::CStreamEngine( + ACXSTREAM Stream, + ACXDATAFORMAT StreamFormat, + CSimPeakMeter *circuitPeakmeter + ) + : m_PacketsCount(0), + m_PacketSize(0), + m_FirstPacketOffset(0), + m_NotificationTimer(NULL), + m_CurrentState(AcxStreamStateStop), + m_CurrentPacket(0), + m_Position(0), + m_Stream(Stream), + m_StreamFormat(StreamFormat), + m_StartTime(0), + m_StartPosition(0), + m_GlitchAdjust(0), + m_pCircuitPeakmeter(circuitPeakmeter) +{ + PAGED_CODE(); + + KeQueryPerformanceCounter(&m_PerformanceCounterFrequency); + RtlZeroMemory(m_Packets, sizeof(m_Packets)); +} + +_Use_decl_annotations_ +#pragma code_seg() +CStreamEngine::~CStreamEngine() +{ +} + +_Use_decl_annotations_ +PAGED_CODE_SEG +NTSTATUS +CStreamEngine::AllocateRtPackets( + ULONG PacketCount, + ULONG PacketSize, + PACX_RTPACKET * Packets + ) +{ + PAGED_CODE(); + + NTSTATUS status = STATUS_SUCCESS; + + PVOID packetBuffer = NULL; + PACX_RTPACKET packets = NULL; + + auto exit = scope_exit([&]() { + if (packetBuffer) + { + ExFreePoolWithTag(packetBuffer, DRIVER_TAG); + } + if (packets) + { + FreeRtPackets(packets, PacketCount); + } + }); + + RETURN_NTSTATUS_IF_TRUE(PacketCount > MAX_PACKET_COUNT, STATUS_INVALID_PARAMETER); + + size_t packetsSize = 0; + RETURN_NTSTATUS_IF_FAILED(RtlSizeTMult(PacketCount, sizeof(ACX_RTPACKET), &packetsSize)); + +#pragma prefast(suppress:__WARNING_MEMORY_LEAK, "On error packets gets freed inside scope_exit.") + packets = (PACX_RTPACKET)ExAllocatePool2(POOL_FLAG_NON_PAGED, packetsSize, DRIVER_TAG); + RETURN_NTSTATUS_IF_TRUE(!packets, STATUS_NO_MEMORY); + + // ExAllocatePool2 zeros memory. + + // We need to allocate page-aligned buffers, to ensure no kernel memory leaks + // to user space. Round up the packet size to page aligned, then calculate + // the first packet's buffer offset so packet 0 ends on a page boundary and + // packet 1 begins on a page boundary. + ULONG packetAllocSizeInPages = 0; + ULONG packetAllocSizeInBytes = 0; + ULONG firstPacketOffset = 0; + RETURN_NTSTATUS_IF_FAILED(RtlULongAdd(PacketSize, PAGE_SIZE - 1, &packetAllocSizeInPages)); + + packetAllocSizeInPages = packetAllocSizeInPages / PAGE_SIZE; + packetAllocSizeInBytes = PAGE_SIZE * packetAllocSizeInPages; + firstPacketOffset = packetAllocSizeInBytes - PacketSize; + + ULONG i; + for (i = 0; i < PacketCount; ++i) + { + PMDL pMdl = NULL; + + ACX_RTPACKET_INIT(&packets[i]); + + packetBuffer = ExAllocatePool2(POOL_FLAG_NON_PAGED, packetAllocSizeInBytes, DRIVER_TAG); + RETURN_NTSTATUS_IF_TRUE(packetBuffer == NULL, STATUS_NO_MEMORY); + + // ExAllocatePool2 zeros memory. + + pMdl = IoAllocateMdl(packetBuffer, packetAllocSizeInBytes, FALSE, FALSE, NULL); + RETURN_NTSTATUS_IF_TRUE(pMdl == NULL, STATUS_NO_MEMORY); + + MmBuildMdlForNonPagedPool(pMdl); + + WDF_MEMORY_DESCRIPTOR_INIT_MDL( + &((packets)[i].RtPacketBuffer), + pMdl, + packetAllocSizeInBytes); + + packets[i].RtPacketSize = PacketSize; + if (i == 0) + { + packets[i].RtPacketOffset = firstPacketOffset; + } + else + { + packets[i].RtPacketOffset = 0; + } + m_Packets[i] = packetBuffer; + + packetBuffer = NULL; + } + + *Packets = packets; + packets = NULL; + m_PacketsCount = PacketCount; + m_PacketSize = PacketSize; + m_FirstPacketOffset = firstPacketOffset; + + return status; +} + +_Use_decl_annotations_ +PAGED_CODE_SEG +VOID +CStreamEngine::FreeRtPackets( + PACX_RTPACKET Packets, + ULONG PacketCount +) +{ + ULONG i; + PVOID buffer; + + PAGED_CODE(); + + for (i = 0; i < PacketCount; ++i) + { + if (Packets[i].RtPacketBuffer.u.MdlType.Mdl) + { + buffer = MmGetMdlVirtualAddress(Packets[i].RtPacketBuffer.u.MdlType.Mdl); + IoFreeMdl(Packets[i].RtPacketBuffer.u.MdlType.Mdl); + ExFreePool(buffer); + } + } + + ExFreePool(Packets); +} + +_Use_decl_annotations_ +PAGED_CODE_SEG +NTSTATUS +CStreamEngine::PrepareHardware() +{ + PAGED_CODE(); + + NTSTATUS status = STATUS_SUCCESS; + + WDF_TIMER_CONFIG timerConfig; + WDF_OBJECT_ATTRIBUTES timerAttributes; + WDF_TIMER_CONFIG_INIT(&timerConfig, CStreamEngine::s_EvtStreamPassCallback); + timerConfig.AutomaticSerialization = TRUE; + timerConfig.UseHighResolutionTimer = WdfTrue; + timerConfig.Period = 0; + + WDF_OBJECT_ATTRIBUTES_INIT(&timerAttributes); + WDF_OBJECT_ATTRIBUTES_INIT_CONTEXT_TYPE(&timerAttributes, STREAM_TIMER_CONTEXT); + timerAttributes.ParentObject = m_Stream; + + RETURN_NTSTATUS_IF_FAILED(WdfTimerCreate( + &timerConfig, + &timerAttributes, + &m_NotificationTimer + )); + + PSTREAM_TIMER_CONTEXT timerCtx; + timerCtx = GetStreamTimerContext(m_NotificationTimer); + timerCtx->StreamEngine = this; + + m_CurrentState = AcxStreamStatePause; + + return status; +} + +_Use_decl_annotations_ +PAGED_CODE_SEG +NTSTATUS +CStreamEngine::ReleaseHardware() +{ + PAGED_CODE(); + + if (m_NotificationTimer) + { + WdfTimerStop(m_NotificationTimer, TRUE); + WdfObjectDelete(m_NotificationTimer); + m_NotificationTimer = NULL; + } + + KeFlushQueuedDpcs(); + + m_Position = 0; + m_GlitchAdjust = 0; + m_CurrentPacket = 0; + + m_CurrentState = AcxStreamStateStop; + + return STATUS_SUCCESS; +} + +_Use_decl_annotations_ +PAGED_CODE_SEG +NTSTATUS +CStreamEngine::Pause() +{ + PAGED_CODE(); + + NTSTATUS status = STATUS_SUCCESS; + + DrvLogInfo(g_SDCAVDspLog, FLAG_STREAM, L"CStreamEngine::Pause - from %d", m_CurrentState); + + RETURN_NTSTATUS_IF_TRUE(m_CurrentState != AcxStreamStateRun, STATUS_INVALID_STATE_TRANSITION); + + m_PeakMeter.StopStream(); + if (m_pCircuitPeakmeter) + { + m_pCircuitPeakmeter->StopStream(); + } + + WdfTimerStop(m_NotificationTimer, TRUE); + + // Save the position we paused at. + UpdatePosition(); + + m_CurrentState = AcxStreamStatePause; + + return status; +} + +_Use_decl_annotations_ +PAGED_CODE_SEG +NTSTATUS +CStreamEngine::Run() +{ + NTSTATUS status = STATUS_SUCCESS; + + PAGED_CODE(); + + DrvLogInfo(g_SDCAVDspLog, FLAG_STREAM, L"CStreamEngine::Run"); + + if (m_CurrentState != AcxStreamStatePause) + { + status = STATUS_INVALID_STATE_TRANSITION; + return status; + } + + m_PeakMeter.StartStream(); + if (m_pCircuitPeakmeter) + { + m_pCircuitPeakmeter->StartStream(); + } + + // Save the time and position - if we ran and paused previously, the StartTime and StartPosition will allow + // us to continue scheduling packet completions correctly, while still reporting absolute position from the + // start of the stream. + m_StartTime = KSCONVERT_PERFORMANCE_TIME(m_PerformanceCounterFrequency.QuadPart, KeQueryPerformanceCounter(NULL)); + m_StartPosition = m_Position; + + // Reset time we've lost to glitches + m_GlitchAdjust = 0; + + ScheduleNextPass(); + + m_CurrentState = AcxStreamStateRun; + + return status; +} + +_Use_decl_annotations_ +PAGED_CODE_SEG +NTSTATUS +CStreamEngine::GetPresentationPosition( + PULONGLONG PositionInBlocks, + PULONGLONG QPCPosition +) +{ + PAGED_CODE(); + + DrvLogVerbose(g_SDCAVDspLog, FLAG_STREAM, L"CStreamEngine::GetPresentationPosition"); + + ULONG blockAlign; + LARGE_INTEGER qpc; + + blockAlign = AcxDataFormatGetBlockAlign(m_StreamFormat); + qpc = KeQueryPerformanceCounter(NULL); + + // Update the position based on the current time + UpdatePosition(); + + *PositionInBlocks = m_Position / blockAlign; + + *QPCPosition = (ULONGLONG)qpc.QuadPart; + + return STATUS_SUCCESS; +} + +_Use_decl_annotations_ +PAGED_CODE_SEG +NTSTATUS +CStreamEngine::AssignDrmContentId( + ULONG DrmContentId, + PACXDRMRIGHTS DrmRights +) +{ + PAGED_CODE(); + + UNREFERENCED_PARAMETER(DrmContentId); + UNREFERENCED_PARAMETER(DrmRights); + + // + // At this point the driver should enforce the new DrmRights. + // + // HDMI render: if DigitalOutputDisable or CopyProtect is true, enable HDCP. + // + // From MSDN: + // + // This sample doesn't forward protected content, but if your driver uses + // lower layer drivers or a different stack to properly work, please see the + // following info from MSDN: + // + // "Before allowing protected content to flow through a data path, the system + // verifies that the data path is secure. To do so, the system authenticates + // each module in the data path beginning at the upstream end of the data path + // and moving downstream. As each module is authenticated, that module gives + // the system information about the next module in the data path so that it + // can also be authenticated. To be successfully authenticated, a module's + // binary file must be signed as DRM-compliant. + // + // Two adjacent modules in the data path can communicate with each other in + // one of several ways. If the upstream module calls the downstream module + // through IoCallDriver, the downstream module is part of a WDM driver. In + // this case, the upstream module calls the AcxDrmForwardContentToDeviceObject + // function to provide the system with the device object representing the + // downstream module. (If the two modules communicate through the downstream + // module's content handlers, the upstream module calls AcxDrmAddContentHandlers + // instead.) + // + // For more information, see MSDN's DRM Functions and Interfaces. + // + + return STATUS_SUCCESS; +} + +_Use_decl_annotations_ +PAGED_CODE_SEG +NTSTATUS +CStreamEngine::GetHWLatency( + ULONG * FifoSize, + ULONG * Delay +) +{ + PAGED_CODE(); + + *FifoSize = 128; + *Delay = 0; + + return STATUS_SUCCESS; +} + +_Use_decl_annotations_ +PAGED_CODE_SEG +CSimPeakMeter * +CStreamEngine::GetPeakMeter() +{ + PAGED_CODE(); + + return &m_PeakMeter; +} + +_Use_decl_annotations_ +#pragma code_seg() +VOID +CStreamEngine::s_EvtStreamPassCallback( + WDFTIMER Timer +) +{ + CStreamEngine * This; + PSTREAM_TIMER_CONTEXT timerCtx; + + // Get our stream engine pointer from the timer context + timerCtx = GetStreamTimerContext(Timer); + This = timerCtx->StreamEngine; + + // Call the StreamPassCallback for the engine + This->StreamPassCallback(); +} + +// This is run every time the stream timer fires +_Use_decl_annotations_ +#pragma code_seg() +VOID +CStreamEngine::StreamPassCallback() +{ + ULONGLONG completedPacket; + ULONGLONG qpcCompleted; + + // Save the time at which we moved to the next packet + qpcCompleted = (ULONGLONG)KeQueryPerformanceCounter(NULL).QuadPart; + + // Process the packet (e.g. save render to file/generate capture data) + ProcessPacket(); + + // We've completed a packet! Increment our currently active packet + completedPacket = (ULONG)InterlockedIncrement((LONG*)&m_CurrentPacket) - 1; + + InterlockedExchange64(&m_LastPacketStart.QuadPart, m_CurrentPacketStart.QuadPart); + InterlockedExchange64(&m_CurrentPacketStart.QuadPart, qpcCompleted); + + // Tell ACX we've completed the packet. + (void)AcxRtStreamNotifyPacketComplete(m_Stream, completedPacket, qpcCompleted); + + // Schedule when our new current packet will finish + ScheduleNextPass(); +} + +_Use_decl_annotations_ +#pragma code_seg() +VOID +CStreamEngine::ScheduleNextPass() +{ + LONGLONG delay = 0; + ULONG bytesPerSecond; + ULONGLONG nextPacket = 0; + ULONGLONG nextPacketStartPosition = 0; + ULONGLONG nextPacketPositionFromLastPause = 0; + ULONGLONG nextPacketTimeFromLastPauseHns = 0; + ULONGLONG nextPacketTime = 0; + ULONGLONG currentTime; + BOOLEAN inTimerQueue = FALSE; + + // Get the number of bytes per second from our stored stream format + bytesPerSecond = GetBytesPerSecond(); + + // Calculate the absolute position of the beginning of the next packet from the beginning of the stream + nextPacket = m_CurrentPacket + 1; + nextPacketStartPosition = nextPacket * m_PacketSize; + + // Adjust next packet position to account for the last time we resumed from Pause + nextPacketPositionFromLastPause = nextPacketStartPosition - m_StartPosition; + + // Convert from bytes to HNS (to prevent truncation, multiply first then divide) + nextPacketTimeFromLastPauseHns = nextPacketPositionFromLastPause * HNS_PER_SEC / bytesPerSecond; + + // Next packet time is Time @ resume from Pause, offset for lost time due to glitch, with next packet time added + nextPacketTime = m_StartTime + m_GlitchAdjust + nextPacketTimeFromLastPauseHns; + + currentTime = KSCONVERT_PERFORMANCE_TIME(m_PerformanceCounterFrequency.QuadPart, KeQueryPerformanceCounter(NULL)); + + // Determine how long we want to wait, in HNS. Negative since it's a relative wait + delay = -(LONGLONG)(nextPacketTime - currentTime); + + // If the delay isn't negative, this means we lost some time (e.g. broken into kernel debugger). Update + // our glitch adjust to account for that lost time, and attempt to schedule again + if (delay >= 0) + { + // Glitch!!! + // Update the glitch adjustment and set the new delay. + m_GlitchAdjust += delay; + + StreamPassCallback(); + + return; + } + + // Start the timer for our next pass! Note the timer isn't periodic. + inTimerQueue = WdfTimerStart(m_NotificationTimer, delay); + + // We shouldn't be scheduling our next pass if the timer was previously still pending + ASSERT(inTimerQueue == FALSE); +} + +_Use_decl_annotations_ +#pragma code_seg() +VOID +CStreamEngine::UpdatePosition() +{ + ULONGLONG currentTime; + ULONG bytesPerSecond; + + if (m_CurrentState != AcxStreamStateRun) + { + return; + } + bytesPerSecond = GetBytesPerSecond(); + currentTime = KSCONVERT_PERFORMANCE_TIME(m_PerformanceCounterFrequency.QuadPart, KeQueryPerformanceCounter(NULL)); + + // Update position + m_Position = m_StartPosition - m_GlitchAdjust + (currentTime - m_StartTime) * bytesPerSecond / HNS_PER_SEC; +} + +_Use_decl_annotations_ +#pragma code_seg() +ULONG +CStreamEngine::GetBytesPerSecond() +{ + ULONG bytesPerSecond; + + bytesPerSecond = AcxDataFormatGetAverageBytesPerSec(m_StreamFormat); + + return bytesPerSecond; +} + +_Use_decl_annotations_ +PAGED_CODE_SEG +NTSTATUS +CStreamEngine::GetCurrentPacket( + PULONG CurrentPacket + ) +{ + ULONG currentPacket; + PAGED_CODE(); + + currentPacket = (ULONG)InterlockedCompareExchange((LONG*)&m_CurrentPacket, -1, -1); + + *CurrentPacket = currentPacket; + + return STATUS_SUCCESS; +} + +_Use_decl_annotations_ +PAGED_CODE_SEG +CRenderStreamEngine::CRenderStreamEngine( + ACXSTREAM Stream, + ACXDATAFORMAT StreamFormat, + CSimPeakMeter *circuitPeakmeter + ) + : CStreamEngine(Stream, StreamFormat, circuitPeakmeter) +{ + PAGED_CODE(); +} + +_Use_decl_annotations_ +PAGED_CODE_SEG +CRenderStreamEngine::~CRenderStreamEngine() +{ + PAGED_CODE(); +} + +_Use_decl_annotations_ +PAGED_CODE_SEG +NTSTATUS +CRenderStreamEngine::PrepareHardware() +{ + PAGED_CODE(); + + NTSTATUS status = STATUS_SUCCESS; + + RETURN_NTSTATUS_IF_FAILED(CStreamEngine::PrepareHardware()); + + // ignore failure + RETURN_NTSTATUS_IF_FAILED(m_SaveData.SetDataFormat((PKSDATAFORMAT)AcxDataFormatGetKsDataFormat(m_StreamFormat))); + + // ignore failure + RETURN_NTSTATUS_IF_FAILED(m_SaveData.Initialize(FALSE)); + + // ignore failure + RETURN_NTSTATUS_IF_FAILED(m_SaveData.SetMaxWriteSize(m_PacketSize * m_PacketsCount * 16)); + + return status; +} + +_Use_decl_annotations_ +PAGED_CODE_SEG +NTSTATUS +CRenderStreamEngine::ReleaseHardware() +{ + PAGED_CODE(); + + m_SaveData.WaitAllWorkItems(); + m_SaveData.Cleanup(); + + return CStreamEngine::ReleaseHardware(); +} + +_Use_decl_annotations_ +PAGED_CODE_SEG +NTSTATUS +CRenderStreamEngine::AssignDrmContentId( + ULONG DrmContentId, + PACXDRMRIGHTS DrmRights + ) +{ + PAGED_CODE(); + + UNREFERENCED_PARAMETER(DrmContentId); + + // + // At this point the driver should enforce the new DrmRights. + // The sample driver handles DrmRights per stream basis, and + // stops writing the stream to disk, if CopyProtect = TRUE. + // + // HDMI render: if DigitalOutputDisable or CopyProtect is true, enable HDCP. + // Loopback: if CopyProtect is true, disable loopback stream. + // + + // + // Sample writes each stream seperately to disk. If the rights for this + // stream indicates that the stream is CopyProtected, stop writing to disk. + // + m_SaveData.Disable(DrmRights->CopyProtect); + + // + // From MSDN: + // + // This sample doesn't forward protected content, but if your driver uses + // lower layer drivers or a different stack to properly work, please see the + // following info from MSDN: + // + // "Before allowing protected content to flow through a data path, the system + // verifies that the data path is secure. To do so, the system authenticates + // each module in the data path beginning at the upstream end of the data path + // and moving downstream. As each module is authenticated, that module gives + // the system information about the next module in the data path so that it + // can also be authenticated. To be successfully authenticated, a module's + // binary file must be signed as DRM-compliant. + // + // Two adjacent modules in the data path can communicate with each other in + // one of several ways. If the upstream module calls the downstream module + // through IoCallDriver, the downstream module is part of a WDM driver. In + // this case, the upstream module calls the AcxDrmForwardContentToDeviceObject + // function to provide the system with the device object representing the + // downstream module. (If the two modules communicate through the downstream + // module's content handlers, the upstream module calls AcxDrmAddContentHandlers + // instead.) + // + // For more information, see MSDN's DRM Functions and Interfaces. + // + + return STATUS_SUCCESS; +} + +_Use_decl_annotations_ +PAGED_CODE_SEG +NTSTATUS +CRenderStreamEngine::SetRenderPacket( + ULONG Packet, + ULONG Flags, + ULONG EosPacketLength + ) +{ + NTSTATUS status = STATUS_SUCCESS; + ULONG currentPacket; + + UNREFERENCED_PARAMETER(Flags); + UNREFERENCED_PARAMETER(EosPacketLength); + + PAGED_CODE(); + + currentPacket = (ULONG)InterlockedCompareExchange((LONG*)&m_CurrentPacket, -1, -1); + + if (Packet <= currentPacket) + { + //ASSERT(FALSE); + status = STATUS_DATA_LATE_ERROR; + } + else if (Packet > currentPacket + 1) + { + //ASSERT(FALSE); + status = STATUS_DATA_OVERRUN; + } + + return status; +} + +_Use_decl_annotations_ +#pragma code_seg() +NTSTATUS +CRenderStreamEngine::GetLinearBufferPosition( + _Out_ PULONGLONG Position +) +{ + NTSTATUS status; + ULONGLONG qpcIgnored = 0; + + // For this sample, we're borrowing the Presentation Position. + // An actual device would return the position of the last byte + // read from the audio buffer, not the last byte presented to the user + status = GetPresentationPosition(Position, &qpcIgnored); + if (!NT_SUCCESS(status)) + { + return status; + } + + *Position *= AcxDataFormatGetBlockAlign(m_StreamFormat); + + return STATUS_SUCCESS; + +} + +_Use_decl_annotations_ +#pragma code_seg() +VOID +CRenderStreamEngine::ProcessPacket() +{ + ULONG currentPacket; + ULONG packetIndex; + PBYTE packetBuffer; + + currentPacket = (ULONG)InterlockedCompareExchange((LONG*)&m_CurrentPacket, -1, -1); + + packetIndex = currentPacket % m_PacketsCount; + packetBuffer = (PBYTE)m_Packets[packetIndex]; + // Packet 0 starts at an offset if the size isn't a multiple of page_size + if (packetIndex == 0) + { + packetBuffer += m_FirstPacketOffset; + } + + m_SaveData.WriteData(packetBuffer, m_PacketSize); +} + +_Use_decl_annotations_ +PAGED_CODE_SEG +CCaptureStreamEngine::CCaptureStreamEngine( + ACXSTREAM Stream, + ACXDATAFORMAT StreamFormat + ) + : CStreamEngine(Stream, StreamFormat, nullptr), + m_EnableWaveCapture(0) +{ + PAGED_CODE(); + + m_CurrentPacketStart.QuadPart = 0; + m_LastPacketStart.QuadPart = 0; + + RtlInitUnicodeString(&m_HostCaptureFileName, NULL); + RtlInitUnicodeString(&m_LoopbackCaptureFileName, NULL); +} + +_Use_decl_annotations_ +PAGED_CODE_SEG +CCaptureStreamEngine::~CCaptureStreamEngine() +{ + PAGED_CODE(); + + RtlFreeUnicodeString(&m_HostCaptureFileName); + RtlFreeUnicodeString(&m_LoopbackCaptureFileName); +} + +_Use_decl_annotations_ +PAGED_CODE_SEG +NTSTATUS +CCaptureStreamEngine::PrepareHardware() +{ + PAGED_CODE(); + + NTSTATUS status = STATUS_SUCCESS; + + RETURN_NTSTATUS_IF_FAILED(CStreamEngine::PrepareHardware()); + + RETURN_NTSTATUS_IF_FAILED(ReadRegistrySettings()); + + if (m_EnableWaveCapture) + { + status = m_WaveReader.Init((PWAVEFORMATEXTENSIBLE)AcxDataFormatGetWaveFormatExtensible(m_StreamFormat), + &m_HostCaptureFileName); + if (!NT_SUCCESS(status)) + { + m_EnableWaveCapture = FALSE; + } + } + + if (!m_EnableWaveCapture) + { + status = m_ToneGenerator.Init(DEFAULT_FREQUENCY, (PWAVEFORMATEXTENSIBLE)AcxDataFormatGetWaveFormatExtensible(m_StreamFormat)); + } + + return status; +} + +_Use_decl_annotations_ +PAGED_CODE_SEG +NTSTATUS +CCaptureStreamEngine::ReleaseHardware() +{ + PAGED_CODE(); + + if (m_EnableWaveCapture) + { + m_WaveReader.WaitAllWorkItems(); + } + + return CStreamEngine::ReleaseHardware(); +} + +_Use_decl_annotations_ +PAGED_CODE_SEG +NTSTATUS +CCaptureStreamEngine::GetCapturePacket( + ULONG * LastCapturePacket, + ULONGLONG * QPCPacketStart, + BOOLEAN * MoreData + ) +{ + NTSTATUS status = STATUS_SUCCESS; + ULONG currentPacket; + LONGLONG qpcPacketStart; + + PAGED_CODE(); + + currentPacket = (ULONG)InterlockedCompareExchange((LONG*)&m_CurrentPacket, -1, -1); + qpcPacketStart = InterlockedCompareExchange64(&m_LastPacketStart.QuadPart, -1, -1); + + *LastCapturePacket = currentPacket - 1; + *QPCPacketStart = (ULONGLONG)qpcPacketStart; + *MoreData = FALSE; + + return status; +} + +_Use_decl_annotations_ +#pragma code_seg() +VOID +CCaptureStreamEngine::ProcessPacket() +{ + ULONG currentPacket; + ULONG packetIndex; + PBYTE packetBuffer; + + currentPacket = (ULONG)InterlockedCompareExchange((LONG*)&m_CurrentPacket, -1, -1); + + packetIndex = currentPacket % m_PacketsCount; + packetBuffer = (PBYTE)m_Packets[packetIndex]; + + // Packet 0 starts at an offset if the size isn't a multiple of page_size + if (packetIndex == 0) + { + packetBuffer += m_FirstPacketOffset; + } + + if (m_EnableWaveCapture) + { + m_WaveReader.ReadWaveData(packetBuffer, m_PacketSize); + } + else + { + m_ToneGenerator.GenerateSine(packetBuffer, m_PacketSize); + } +} + +_Use_decl_annotations_ +PAGED_CODE_SEG +NTSTATUS +CCaptureStreamEngine::ReadRegistrySettings() +{ + PAGED_CODE(); + + NTSTATUS status = STATUS_SUCCESS; + + // TRUE only on SUCCESS + m_EnableWaveCapture = FALSE; + + RTL_QUERY_REGISTRY_TABLE paramTable[] = { + // QueryRoutine Flags Name EntryContext DefaultType DefaultData DefaultLength + { NULL, RTL_QUERY_REGISTRY_DIRECT | RTL_QUERY_REGISTRY_TYPECHECK, L"EnableWaveCapture", &m_EnableWaveCapture, (REG_DWORD << RTL_QUERY_REGISTRY_TYPECHECK_SHIFT) | REG_DWORD, &m_EnableWaveCapture, sizeof(DWORD) }, + { NULL, RTL_QUERY_REGISTRY_DIRECT | RTL_QUERY_REGISTRY_TYPECHECK, L"HostCaptureFileName", &m_HostCaptureFileName, (REG_SZ << RTL_QUERY_REGISTRY_TYPECHECK_SHIFT) | REG_SZ, &m_HostCaptureFileName, sizeof(UNICODE_STRING) }, + { NULL, RTL_QUERY_REGISTRY_DIRECT | RTL_QUERY_REGISTRY_TYPECHECK, L"LoopbackCaptureFileName", &m_LoopbackCaptureFileName, (REG_SZ << RTL_QUERY_REGISTRY_TYPECHECK_SHIFT) | REG_SZ, &m_LoopbackCaptureFileName, sizeof(UNICODE_STRING) }, + { NULL, 0, NULL, NULL, 0, NULL, 0 } + }; + + UNICODE_STRING parametersPath; + RtlInitUnicodeString(¶metersPath, NULL); + + // The sizeof(WCHAR) is added to the maximum length, for allowing a space for null termination of the string. + parametersPath.MaximumLength = g_RegistryPath.Length + sizeof(L"\\Parameters") + sizeof(WCHAR); + +#pragma prefast(suppress:__WARNING_ALIASED_MEMORY_LEAK, "memory is freed by scope_exit") + parametersPath.Buffer = (PWCH)ExAllocatePool2(POOL_FLAG_PAGED, parametersPath.MaximumLength, DRIVER_TAG); + RETURN_NTSTATUS_IF_TRUE(parametersPath.Buffer == NULL, STATUS_INSUFFICIENT_RESOURCES); + auto parametersPath_free = scope_exit([¶metersPath]() { + ExFreePool(parametersPath.Buffer); + }); + + // ExAllocatePool2 zeros memory. + + RtlAppendUnicodeToString(¶metersPath, g_RegistryPath.Buffer); + RtlAppendUnicodeToString(¶metersPath, L"\\Parameters"); + + RETURN_NTSTATUS_IF_FAILED(RtlQueryRegistryValues(RTL_REGISTRY_ABSOLUTE | RTL_REGISTRY_OPTIONAL, + parametersPath.Buffer, + ¶mTable[0], + NULL, + NULL)); + + m_EnableWaveCapture = TRUE; + + return status; +} + +_Use_decl_annotations_ +PAGED_CODE_SEG +CBufferedCaptureStreamEngine::CBufferedCaptureStreamEngine( + _In_ ACXSTREAM Stream, + _In_ ACXDATAFORMAT StreamFormat, + _In_ CKeywordDetector * KeywordDetector + + ) + : CCaptureStreamEngine(Stream, StreamFormat), + m_KeywordDetector(KeywordDetector) +{ + PAGED_CODE(); +} + +_Use_decl_annotations_ +PAGED_CODE_SEG +CBufferedCaptureStreamEngine::~CBufferedCaptureStreamEngine() +{ + PAGED_CODE(); +} + +// This is run every time the stream timer fires +_Use_decl_annotations_ +#pragma code_seg() +VOID +CBufferedCaptureStreamEngine::StreamPassCallback() +{ + LARGE_INTEGER qpc; + LARGE_INTEGER qpcFrequency; + BOOLEAN isRealtime = FALSE; + ULONGLONG completedPacket; + LONGLONG NewPacketNumber; + ULONGLONG NewPerformanceCount; + + qpc = KeQueryPerformanceCounter(&qpcFrequency); + + // As this is a simulation, we still want the ScheduleNextPass to + // keep producing data. To that end, update the current packet + // information used for production. + completedPacket = (ULONG)InterlockedIncrement((LONG*)&m_CurrentPacket) - 1; + InterlockedExchange64(&m_LastPacketStart.QuadPart, m_CurrentPacketStart.QuadPart); + InterlockedExchange64(&m_CurrentPacketStart.QuadPart, qpc.QuadPart); + + + // Add the next packet to the fifo queue + m_KeywordDetector->DpcRoutine(qpc.QuadPart, qpcFrequency.QuadPart, &isRealtime, &NewPacketNumber, &NewPerformanceCount); + + if (isRealtime && (m_CurrentState == AcxStreamStateRun)) + { + // We are running real time and just completed a packet, so notify. + (void)AcxRtStreamNotifyPacketComplete(m_Stream, NewPacketNumber, NewPerformanceCount); + } + + // Schedule when our new current packet will finish + ScheduleNextPass(); +} + +_Use_decl_annotations_ +PAGED_CODE_SEG +NTSTATUS +CBufferedCaptureStreamEngine::Pause() +{ + PAGED_CODE(); + + m_KeywordDetector->Stop(); + return CCaptureStreamEngine::Pause(); +} + +_Use_decl_annotations_ +PAGED_CODE_SEG +NTSTATUS +CBufferedCaptureStreamEngine::Run() +{ + PAGED_CODE(); + ULONG FrontCapturePacket; + ULONGLONG QPCFrontPacket; + + m_KeywordDetector->Run(); + NTSTATUS status = CCaptureStreamEngine::Run(); + + NTSTATUS fifoStatus = m_KeywordDetector->GetFifoStart(&FrontCapturePacket, &QPCFrontPacket); + if (NT_SUCCESS(fifoStatus)) + { + // We just entered the run state, so we need to trigger the packet completion for the first + // buffer in the fifo + (void)AcxRtStreamNotifyPacketComplete(m_Stream, FrontCapturePacket, QPCFrontPacket); + } + + return status; +} + +_Use_decl_annotations_ +PAGED_CODE_SEG +NTSTATUS +CBufferedCaptureStreamEngine::GetCapturePacket( + _Out_ ULONG * LastCapturePacket, + _Out_ ULONGLONG * QPCPacketStart, + _Out_ BOOLEAN * MoreData + ) +{ + PAGED_CODE(); + ULONG nextPacketNumber; + ULONGLONG nextQPCCount; + + // retrieve the packet from the fifo queue + NTSTATUS status = m_KeywordDetector->GetReadPacket(m_PacketsCount, m_PacketSize, m_Packets, LastCapturePacket, QPCPacketStart, MoreData, &nextPacketNumber, &nextQPCCount); + + if (NT_SUCCESS(status) && MoreData) + { + (void)AcxRtStreamNotifyPacketComplete(m_Stream, nextPacketNumber, nextQPCCount); + } + + return status; +} + diff --git a/audio/Soundwire/Samples/SdcaVad/SdcaVDsp/streamengine.h b/audio/Soundwire/Samples/SdcaVad/SdcaVDsp/streamengine.h new file mode 100644 index 000000000..902bb007a --- /dev/null +++ b/audio/Soundwire/Samples/SdcaVad/SdcaVDsp/streamengine.h @@ -0,0 +1,391 @@ +/*++ + + THIS CODE AND INFORMATION IS PROVIDED "AS IS" WITHOUT WARRANTY OF ANY + KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE + IMPLIED WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A PARTICULAR + PURPOSE. + +Module Name: + + streamengine.h + +Abstract: + + Virtual Streaming Engine - this module controls streaming logic for + the device. + +Environment: + + Kernel mode + +--*/ +#pragma once + +#include "savedata.h" +#include "tonegenerator.h" +#include "WaveReader.h" +#include "SimPeakMeter.h" +#include "KeywordDetector.h" + +#define HNSTIME_PER_MILLISECOND 10000 + +#define MAX_PACKET_COUNT 2 + +#define DEFAULT_FREQUENCY (220) +#define LOOPBACK_FREQUENCY (500) +#define DEFAULT_FREQUENCY (220) + +class CStreamEngine +{ +public: + virtual + __drv_maxIRQL(PASSIVE_LEVEL) + PAGED_CODE_SEG + NTSTATUS + AllocateRtPackets( + _In_ ULONG PacketCount, + _In_ ULONG PacketSize, + _Out_ PACX_RTPACKET * Packets + ); + + virtual + __drv_maxIRQL(PASSIVE_LEVEL) + PAGED_CODE_SEG + VOID + FreeRtPackets( + _Frees_ptr_ PACX_RTPACKET Packets, + _In_ ULONG PacketCount + ); + + virtual + __drv_maxIRQL(PASSIVE_LEVEL) + PAGED_CODE_SEG + NTSTATUS + PrepareHardware(); + + virtual + __drv_maxIRQL(PASSIVE_LEVEL) + PAGED_CODE_SEG + NTSTATUS + ReleaseHardware(); + + virtual + __drv_maxIRQL(PASSIVE_LEVEL) + PAGED_CODE_SEG + NTSTATUS + Run(); + + virtual + __drv_maxIRQL(PASSIVE_LEVEL) + PAGED_CODE_SEG + NTSTATUS + Pause(); + + virtual + __drv_maxIRQL(PASSIVE_LEVEL) + PAGED_CODE_SEG + NTSTATUS + GetPresentationPosition( + _Out_ PULONGLONG PositionInBlocks, + _Out_ PULONGLONG QPCPosition + ); + + virtual + __drv_maxIRQL(PASSIVE_LEVEL) + PAGED_CODE_SEG + NTSTATUS + GetCurrentPacket( + _Out_ PULONG CurrentPacket + ); + + virtual + __drv_maxIRQL(PASSIVE_LEVEL) + PAGED_CODE_SEG + NTSTATUS + AssignDrmContentId( + _In_ ULONG DrmContentId, + _In_ PACXDRMRIGHTS DrmRights + ); + + virtual + __drv_maxIRQL(PASSIVE_LEVEL) + PAGED_CODE_SEG + NTSTATUS + GetHWLatency( + _Out_ ULONG * FifoSize, + _Out_ ULONG * Delay + ); + + virtual + __drv_maxIRQL(PASSIVE_LEVEL) + #pragma code_seg() + NTSTATUS + GetLinearBufferPosition( + _Out_ PULONGLONG Position + ) + { + UNREFERENCED_PARAMETER(Position); + return STATUS_NOT_SUPPORTED; + } + + virtual + __drv_maxIRQL(PASSIVE_LEVEL) + PAGED_CODE_SEG + CSimPeakMeter * + GetPeakMeter(); + + __drv_maxIRQL(PASSIVE_LEVEL) + PAGED_CODE_SEG + CStreamEngine( + _In_ ACXSTREAM Stream, + _In_ ACXDATAFORMAT StreamFormat, + _In_opt_ CSimPeakMeter *circuitPeakmeter + ); + + virtual + __drv_maxIRQL(PASSIVE_LEVEL) + #pragma code_seg() + ~CStreamEngine(); + +protected: + PVOID m_Packets[MAX_PACKET_COUNT]{ nullptr }; + ULONG m_PacketsCount{ 0 }; + ULONG m_PacketSize{ 0 }; + ULONG m_FirstPacketOffset{ 0 }; + WDFTIMER m_NotificationTimer{ nullptr }; + ACX_STREAM_STATE m_CurrentState{ AcxStreamStateStop }; + ULONG m_CurrentPacket{ 0 }; + ULONGLONG m_Position{ 0 }; + ACXSTREAM m_Stream{ nullptr }; + ACXDATAFORMAT m_StreamFormat{ nullptr }; + ULONGLONG m_StartTime{ 0 }; + ULONGLONG m_StartPosition{ 0 }; + ULONGLONG m_GlitchAdjust{ 0 }; + LARGE_INTEGER m_PerformanceCounterFrequency{ 0 }; + LARGE_INTEGER m_CurrentPacketStart{ 0 }; + LARGE_INTEGER m_LastPacketStart{ 0 }; + CSimPeakMeter m_PeakMeter; + CSimPeakMeter* m_pCircuitPeakmeter{ nullptr }; + + static + __drv_maxIRQL(DISPATCH_LEVEL) + _Function_class_(EVT_WDF_TIMER) + #pragma code_seg() + VOID s_EvtStreamPassCallback( + _In_ WDFTIMER Timer + ); + + // This is run every time the stream timer fires + virtual + __drv_maxIRQL(DISPATCH_LEVEL) + #pragma code_seg() + VOID + StreamPassCallback(); + + virtual + __drv_maxIRQL(DISPATCH_LEVEL) + #pragma code_seg() + VOID + ScheduleNextPass(); + + virtual + __drv_maxIRQL(DISPATCH_LEVEL) + #pragma code_seg() + VOID + UpdatePosition(); + + virtual + __drv_maxIRQL(DISPATCH_LEVEL) + #pragma code_seg() + ULONG + GetBytesPerSecond(); + + virtual + __drv_maxIRQL(DISPATCH_LEVEL) + #pragma code_seg() + VOID + ProcessPacket() = 0; +}; + +class CRenderStreamEngine : public CStreamEngine +{ +public: + __drv_maxIRQL(PASSIVE_LEVEL) + PAGED_CODE_SEG + CRenderStreamEngine( + _In_ ACXSTREAM Stream, + _In_ ACXDATAFORMAT StreamFormat, + _In_ CSimPeakMeter *circuitPeakmeter + ); + + __drv_maxIRQL(PASSIVE_LEVEL) + PAGED_CODE_SEG + ~CRenderStreamEngine(); + + virtual + __drv_maxIRQL(PASSIVE_LEVEL) + PAGED_CODE_SEG + NTSTATUS + PrepareHardware(); + + virtual + __drv_maxIRQL(PASSIVE_LEVEL) + PAGED_CODE_SEG + NTSTATUS + ReleaseHardware(); + + virtual + __drv_maxIRQL(PASSIVE_LEVEL) + PAGED_CODE_SEG + NTSTATUS + AssignDrmContentId( + _In_ ULONG DrmContentId, + _In_ PACXDRMRIGHTS DrmRights + ); + + __drv_maxIRQL(PASSIVE_LEVEL) + PAGED_CODE_SEG + NTSTATUS + SetRenderPacket( + _In_ ULONG Packet, + _In_ ULONG Flags, + _In_ ULONG EosPacketLength + ); + + virtual + __drv_maxIRQL(PASSIVE_LEVEL) + #pragma code_seg() + NTSTATUS + GetLinearBufferPosition( + _Out_ PULONGLONG Position + ); + +protected: + CSaveData m_SaveData; + + virtual + __drv_maxIRQL(DISPATCH_LEVEL) + #pragma code_seg() + VOID + ProcessPacket(); + +}; + +class CCaptureStreamEngine : public CStreamEngine +{ +public: + __drv_maxIRQL(PASSIVE_LEVEL) + PAGED_CODE_SEG + CCaptureStreamEngine( + _In_ ACXSTREAM Stream, + _In_ ACXDATAFORMAT StreamFormat + ); + + __drv_maxIRQL(PASSIVE_LEVEL) + PAGED_CODE_SEG + ~CCaptureStreamEngine(); + + virtual + __drv_maxIRQL(PASSIVE_LEVEL) + PAGED_CODE_SEG + NTSTATUS + PrepareHardware(); + + virtual + __drv_maxIRQL(PASSIVE_LEVEL) + PAGED_CODE_SEG + NTSTATUS + ReleaseHardware(); + + virtual + __drv_maxIRQL(PASSIVE_LEVEL) + PAGED_CODE_SEG + NTSTATUS + GetCapturePacket( + _Out_ ULONG * LastCapturePacket, + _Out_ ULONGLONG * QPCPacketStart, + _Out_ BOOLEAN * MoreData + ); + +protected: + ToneGenerator m_ToneGenerator; + CWaveReader m_WaveReader; + DWORD m_EnableWaveCapture{ 0 }; + UNICODE_STRING m_HostCaptureFileName{ 0 }; + UNICODE_STRING m_LoopbackCaptureFileName{ 0 }; + + virtual + __drv_maxIRQL(DISPATCH_LEVEL) + #pragma code_seg() + VOID + ProcessPacket(); + + __drv_maxIRQL(PASSIVE_LEVEL) + PAGED_CODE_SEG + NTSTATUS + ReadRegistrySettings(); +}; + +class CBufferedCaptureStreamEngine : public CCaptureStreamEngine +{ +public: + __drv_maxIRQL(PASSIVE_LEVEL) + PAGED_CODE_SEG + CBufferedCaptureStreamEngine( + _In_ ACXSTREAM Stream, + _In_ ACXDATAFORMAT StreamFormat, + _In_ CKeywordDetector * KeywordDetector + ); + + __drv_maxIRQL(PASSIVE_LEVEL) + PAGED_CODE_SEG + ~CBufferedCaptureStreamEngine(); + + virtual + __drv_maxIRQL(PASSIVE_LEVEL) + PAGED_CODE_SEG + NTSTATUS + Run(); + + virtual + __drv_maxIRQL(PASSIVE_LEVEL) + PAGED_CODE_SEG + NTSTATUS + Pause(); + + virtual + __drv_maxIRQL(PASSIVE_LEVEL) + PAGED_CODE_SEG + NTSTATUS + GetCapturePacket( + _Out_ ULONG * LastCapturePacket, + _Out_ ULONGLONG * QPCPacketStart, + _Out_ BOOLEAN * MoreData + ); + +protected: + virtual + __drv_maxIRQL(DISPATCH_LEVEL) + #pragma code_seg() + VOID + ProcessPacket() {} + + // This is run every time the stream timer fires + virtual + __drv_maxIRQL(DISPATCH_LEVEL) + #pragma code_seg() + VOID + StreamPassCallback(); + + CKeywordDetector * m_KeywordDetector{ nullptr }; +}; + + +// Define DSP circuit/stream pin context. +// +typedef struct _STREAM_TIMER_CONTEXT { + CStreamEngine * StreamEngine; +} STREAM_TIMER_CONTEXT, *PSTREAM_TIMER_CONTEXT; + +#pragma code_seg() +WDF_DECLARE_CONTEXT_TYPE_WITH_NAME(STREAM_TIMER_CONTEXT, GetStreamTimerContext) diff --git a/audio/Soundwire/Samples/SdcaVad/SdcaVXu/AudioModule.cpp b/audio/Soundwire/Samples/SdcaVad/SdcaVXu/AudioModule.cpp new file mode 100644 index 000000000..73f15aaf0 --- /dev/null +++ b/audio/Soundwire/Samples/SdcaVad/SdcaVXu/AudioModule.cpp @@ -0,0 +1,405 @@ +/*++ + + THIS CODE AND INFORMATION IS PROVIDED "AS IS" WITHOUT WARRANTY OF ANY + KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE + IMPLIED WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A PARTICULAR + PURPOSE. + +Module Name: + + AudioModule.cpp + +Abstract: + + Implementation of general purpose audio module property handlers + +Environment: + + Kernel mode + +--*/ + +#include "private.h" +#include "audiomodule.h" +#include "stdunk.h" +#include + +AUDIOMODULE_PARAMETER_INFO AudioModule_ParameterInfo[] = +{ + { + ACX_PROPERTY_ITEM_FLAG_GET | ACX_PROPERTY_ITEM_FLAG_SET | ACX_PROPERTY_ITEM_FLAG_BASICSUPPORT, + AUDIOMODULE_PARAMETER_FLAG_CHANGE_NOTIFICATION, + (ULONG)RTL_FIELD_SIZE(SDCAXU_AUDIOMODULE_CONTEXT, Parameter1), + VT_UI4, + AudioModule_ValidParameterList, + SIZEOF_ARRAY(AudioModule_ValidParameterList) + }, + { + ACX_PROPERTY_ITEM_FLAG_GET | ACX_PROPERTY_ITEM_FLAG_BASICSUPPORT, + 0, + (ULONG)RTL_FIELD_SIZE(SDCAXU_AUDIOMODULE_CONTEXT, Parameter2), + VT_UI1, + NULL, + 0 + }, +}; + +AUDIOMODULE_PARAMETER_INFO AudioModule0_ParameterInfo[] = +{ + { + ACX_PROPERTY_ITEM_FLAG_GET | ACX_PROPERTY_ITEM_FLAG_SET | ACX_PROPERTY_ITEM_FLAG_BASICSUPPORT, + AUDIOMODULE_PARAMETER_FLAG_CHANGE_NOTIFICATION, + (ULONG)RTL_FIELD_SIZE(SDCAXU_AUDIOMODULE0_CONTEXT, Parameter1), + VT_UI4, + AudioModule0_ValidParameterList, + SIZEOF_ARRAY(AudioModule0_ValidParameterList) + }, + { + ACX_PROPERTY_ITEM_FLAG_GET | ACX_PROPERTY_ITEM_FLAG_BASICSUPPORT, + 0, + (ULONG)RTL_FIELD_SIZE(SDCAXU_AUDIOMODULE0_CONTEXT, Parameter2), + VT_UI1, + NULL, + 0 + }, +}; + +AUDIOMODULE_PARAMETER_INFO AudioModule1_ParameterInfo[] = +{ + { + KSPROPERTY_TYPE_GET | KSPROPERTY_TYPE_SET | KSPROPERTY_TYPE_BASICSUPPORT, + AUDIOMODULE_PARAMETER_FLAG_CHANGE_NOTIFICATION, + (ULONG)RTL_FIELD_SIZE(SDCAXU_AUDIOMODULE1_CONTEXT, Parameter1), + VT_UI1, + AudioModule1_ValidParameterList, + SIZEOF_ARRAY(AudioModule1_ValidParameterList) + }, + { + KSPROPERTY_TYPE_GET | KSPROPERTY_TYPE_BASICSUPPORT, + 0, + (ULONG)RTL_FIELD_SIZE(SDCAXU_AUDIOMODULE1_CONTEXT, Parameter2), + VT_UI8, + NULL, + 0 + }, + { + KSPROPERTY_TYPE_GET | KSPROPERTY_TYPE_SET | KSPROPERTY_TYPE_BASICSUPPORT, + AUDIOMODULE_PARAMETER_FLAG_CHANGE_NOTIFICATION, + (ULONG)RTL_FIELD_SIZE(SDCAXU_AUDIOMODULE1_CONTEXT, Parameter3), + VT_UI4, + NULL, + 0 + }, +}; + +AUDIOMODULE_PARAMETER_INFO AudioModule2_ParameterInfo[] = +{ + { + KSPROPERTY_TYPE_GET | KSPROPERTY_TYPE_SET | KSPROPERTY_TYPE_BASICSUPPORT, + AUDIOMODULE_PARAMETER_FLAG_CHANGE_NOTIFICATION, + (ULONG)RTL_FIELD_SIZE(SDCAXU_AUDIOMODULE2_CONTEXT, Parameter1), + VT_UI4, + AudioModule2_ValidParameterList, + SIZEOF_ARRAY(AudioModule2_ValidParameterList) + }, + { + KSPROPERTY_TYPE_GET | KSPROPERTY_TYPE_BASICSUPPORT, + 0, + (ULONG)RTL_FIELD_SIZE(SDCAXU_AUDIOMODULE2_CONTEXT, Parameter2), + VT_UI2, + NULL, + 0 + }, +}; + + +#pragma code_seg("PAGE") +NTSTATUS +AudioModule_GenericHandler_BasicSupport( + _In_ PAUDIOMODULE_PARAMETER_INFO ParameterInfo, + _Out_writes_bytes_opt_(*BufferCb) PVOID Buffer, + _Inout_ ULONG * BufferCb + ) +{ + NTSTATUS ntStatus = STATUS_SUCCESS; + ULONG cbFullProperty = 0; + ULONG cbDataListSize = 0; + + PAGED_CODE(); + + ASSERT(ParameterInfo); + ASSERT(BufferCb); + + // + // Compute total size of property. + // + ntStatus = RtlULongMult(ParameterInfo->Size, + ParameterInfo->ValidSetCount, + &cbDataListSize); + if (!NT_SUCCESS(ntStatus)) + { + ASSERT(FALSE); + *BufferCb = 0; + return ntStatus; + } + + ntStatus = RtlULongAdd(cbDataListSize, + (ULONG)(sizeof(KSPROPERTY_DESCRIPTION) + + sizeof(KSPROPERTY_MEMBERSHEADER)), + &cbFullProperty); + + if (!NT_SUCCESS(ntStatus)) + { + ASSERT(FALSE); + *BufferCb = 0; + return ntStatus; + } + + // + // Return the info the caller is asking for. + // + if (*BufferCb == 0) + { + // caller wants to know the size of the buffer. + *BufferCb = cbFullProperty; + ntStatus = STATUS_BUFFER_OVERFLOW; + } + else if (*BufferCb >= (sizeof(KSPROPERTY_DESCRIPTION))) + { + PKSPROPERTY_DESCRIPTION propDesc = PKSPROPERTY_DESCRIPTION(Buffer); + + propDesc->AccessFlags = ParameterInfo->AccessFlags; + propDesc->DescriptionSize = cbFullProperty; + propDesc->PropTypeSet.Set = KSPROPTYPESETID_General; + propDesc->PropTypeSet.Id = ParameterInfo->VtType; + propDesc->PropTypeSet.Flags = 0; + propDesc->MembersListCount = 1; + propDesc->Reserved = 0; + + // if return buffer can also hold a list description, return it too + if(*BufferCb >= cbFullProperty) + { + // fill in the members header + PKSPROPERTY_MEMBERSHEADER members = + PKSPROPERTY_MEMBERSHEADER(propDesc + 1); + + members->MembersFlags = KSPROPERTY_MEMBER_VALUES; + members->MembersSize = ParameterInfo->Size; + members->MembersCount = ParameterInfo->ValidSetCount; + members->Flags = KSPROPERTY_MEMBER_FLAG_DEFAULT; + + // fill in valid array. + BYTE* array = (BYTE*)(members + 1); + + RtlCopyMemory(array, ParameterInfo->ValidSet, cbDataListSize); + + // set the return value size + *BufferCb = cbFullProperty; + } + else + { + *BufferCb = sizeof(KSPROPERTY_DESCRIPTION); + } + } + else if(*BufferCb >= sizeof(ULONG)) + { + // if return buffer can hold a ULONG, return the access flags + PULONG accessFlags = PULONG(Buffer); + + *BufferCb = sizeof(ULONG); + *accessFlags = ParameterInfo->AccessFlags; + } + else + { + *BufferCb = 0; + ntStatus = STATUS_BUFFER_TOO_SMALL; + } + + return ntStatus; +} + +#pragma code_seg("PAGE") +BOOLEAN +IsAudioModuleParameterValid( + _In_ PAUDIOMODULE_PARAMETER_INFO ParameterInfo, + _In_reads_bytes_opt_(BufferCb) PVOID Buffer, + _In_ ULONG BufferCb + ) +{ + PAGED_CODE(); + + ULONG i = 0; + ULONG j = 0; + BOOLEAN validParam = FALSE; + + // + // Validate buffer ptr and size. + // + if (Buffer == NULL || BufferCb == 0) + { + validParam = FALSE; + goto exit; + } + + // + // Check its size. + // + if (BufferCb < ParameterInfo->Size) + { + validParam = FALSE; + goto exit; + } + + // + // Check the valid list. + // + if (ParameterInfo->ValidSet && ParameterInfo->ValidSetCount) + { + BYTE* buffer = (BYTE*)ParameterInfo->ValidSet; + BYTE* pattern = (BYTE*)Buffer; + + // + // Scan the valid list. + // + for (i = 0; i < ParameterInfo->ValidSetCount; ++i) + { + for (j=0; j < ParameterInfo->Size; ++j) + { + if (buffer[j] != pattern[j]) + { + break; + } + } + + if (j == ParameterInfo->Size) + { + // got a match. + break; + } + + buffer += ParameterInfo->Size; + } + + // + // If end of list, we didn't find the value. + // + if (i == ParameterInfo->ValidSetCount) + { + validParam = FALSE; + goto exit; + } + } + else + { + // + // Negative-testing support. Fail request if value is -1. + // + BYTE* buffer = (BYTE*)Buffer; + + for (i = 0; i < ParameterInfo->Size; ++i) + { + if (buffer[i] != 0xFF) + { + break; + } + } + + // + // If value is -1, return error. + // + if (i == ParameterInfo->Size) + { + validParam = FALSE; + goto exit; + } + } + + validParam = TRUE; + +exit: + return validParam; +} + +#pragma code_seg("PAGE") +NTSTATUS +AudioModule_GenericHandler( + _In_ ULONG Verb, + _In_ ULONG ParameterId, + _In_ PAUDIOMODULE_PARAMETER_INFO ParameterInfo, + _Inout_updates_bytes_(ParameterInfo->Size) PVOID CurrentValue, + _In_reads_bytes_opt_(InBufferCb) PVOID InBuffer, + _In_ ULONG InBufferCb, + _Out_writes_bytes_opt_(*OutBufferCb) PVOID OutBuffer, + _Inout_ ULONG * OutBufferCb, + _In_ BOOL * ParameterChanged + ) +{ + PAGED_CODE(); + + UNREFERENCED_PARAMETER(ParameterId); + + *ParameterChanged = FALSE; + + // Handle KSPROPERTY_TYPE_BASICSUPPORT query + if (Verb & KSPROPERTY_TYPE_BASICSUPPORT) + { + return AudioModule_GenericHandler_BasicSupport(ParameterInfo, OutBuffer, OutBufferCb); + } + + ULONG cbMinSize = ParameterInfo->Size; + + if (Verb & KSPROPERTY_TYPE_GET) + { + // Verify module parameter supports 'get'. + if (!(ParameterInfo->AccessFlags & KSPROPERTY_TYPE_GET)) + { + *OutBufferCb = 0; + return STATUS_INVALID_DEVICE_REQUEST; + } + + // Verify value size + if (*OutBufferCb == 0) + { + *OutBufferCb = cbMinSize; + return STATUS_BUFFER_OVERFLOW; + } + if (*OutBufferCb < cbMinSize) + { + *OutBufferCb = 0; + return STATUS_BUFFER_TOO_SMALL; + } + else + { + RtlCopyMemory(OutBuffer, CurrentValue, ParameterInfo->Size); + *OutBufferCb = cbMinSize; + return STATUS_SUCCESS; + } + } + else if (Verb & KSPROPERTY_TYPE_SET) + { + *OutBufferCb = 0; + + // Verify it is a write prop. + if (!(ParameterInfo->AccessFlags & KSPROPERTY_TYPE_SET)) + { + return STATUS_INVALID_DEVICE_REQUEST; + } + + // Validate parameter. + if (!IsAudioModuleParameterValid(ParameterInfo, InBuffer, InBufferCb)) + { + return STATUS_INVALID_PARAMETER; + } + + if (ParameterInfo->Size != + RtlCompareMemory(CurrentValue, InBuffer, ParameterInfo->Size)) + { + RtlCopyMemory(CurrentValue, InBuffer, ParameterInfo->Size); + *ParameterChanged = TRUE; + } + + return STATUS_SUCCESS; + } + + return STATUS_INVALID_DEVICE_REQUEST; +} diff --git a/audio/Soundwire/Samples/SdcaVad/SdcaVXu/AudioModule.h b/audio/Soundwire/Samples/SdcaVad/SdcaVXu/AudioModule.h new file mode 100644 index 000000000..11fd7c3b2 --- /dev/null +++ b/audio/Soundwire/Samples/SdcaVad/SdcaVXu/AudioModule.h @@ -0,0 +1,246 @@ +/*++ + +Copyright (c) Microsoft Corporation. All rights reserved. + + THIS CODE AND INFORMATION IS PROVIDED "AS IS" WITHOUT WARRANTY OF ANY + KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE + IMPLIED WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A PARTICULAR + PURPOSE. + +Module Name: + + AudioModule.h + +Abstract: + + Contains audio modules definitions and function prototypes private to + the driver. + +Environment: + + Kernel mode + +--*/ + +#ifndef _AUDIOMODULE_H_ +#define _AUDIOMODULE_H_ + +/* make prototypes usable from C++ */ +#ifdef __cplusplus +extern "C" { +#endif + +// Audio module definitions + +// +// Audio module instance defintion. +// + +// top 8 bits reserved for use by aggregation +// next 12 bits are the config id mask +// bottom 12 bits instance id +#define AUDIOMODULE_CLASS_CFG_ID_MASK 0xFFF +#define AUDIOMODULE_CLASS_CFG_INSTANCE_ID_MASK 0xFFF + +#define AUDIOMODULE_INSTANCE_ID(ClassCfgId, ClassCfgInstanceId) \ + ((ULONG(ClassCfgId & AUDIOMODULE_CLASS_CFG_ID_MASK) << 12) | \ + (ULONG(ClassCfgInstanceId & AUDIOMODULE_CLASS_CFG_INSTANCE_ID_MASK))) + +#define AUDIOMODULE_GET_CLASSCFGID(InstanceId) \ + (ULONG(InstanceId) >> 12 & AUDIOMODULE_CLASS_CFG_ID_MASK) + +enum AudioModule_Parameter { + AudioModuleParameter1 = 0, + AudioModuleParameter2, + AudioModuleParameter3 +}; + +typedef struct _AUDIOMODULE_CUSTOM_COMMAND { + ULONG Verb; // get, set and support + AudioModule_Parameter ParameterId; +} AUDIOMODULE_CUSTOM_COMMAND, *PAUDIOMODULE_CUSTOM_COMMAND; + +enum AudioModule_Notification_Type { + AudioModuleParameterChanged = 0, +}; + +typedef struct _AUDIOMODULE_CUSTOM_NOTIFICATION { + ULONG Type; + union + { + struct + { + ULONG ParameterId; + } ParameterChanged; + }; +} AUDIOMODULE_CUSTOM_NOTIFICATION, *PAUDIOMODULE_CUSTOM_NOTIFICATION; + +#define AUDIOMODULE_PARAMETER_FLAG_CHANGE_NOTIFICATION 0x00000001 + +typedef struct _SDCAXU_AUDIOMODULE_CONTEXT { + ACXPNPEVENT Event; + ULONG Parameter1; + BYTE Parameter2; + ULONG InstanceId; + ACXCIRCUIT Circuit; +} SDCAXU_AUDIOMODULE_CONTEXT, *PSDCAXU_AUDIOMODULE_CONTEXT; + +WDF_DECLARE_CONTEXT_TYPE_WITH_NAME(SDCAXU_AUDIOMODULE_CONTEXT, GetSdcaXuAudioModuleContext); + +typedef struct _SDCAXU_AUDIOMODULE0_CONTEXT { + ACXPNPEVENT Event; + ULONG Parameter1; + BYTE Parameter2; + ULONG InstanceId; + ACXCIRCUIT Circuit; +} SDCAXU_AUDIOMODULE0_CONTEXT, *PSDCAXU_AUDIOMODULE0_CONTEXT; + +typedef struct _SDCAXU_AUDIOMODULE1_CONTEXT { + ACXPNPEVENT Event; + BYTE Parameter1; + ULONGLONG Parameter2; + DWORD Parameter3; + ULONG InstanceId; + ACXCIRCUIT Circuit; +} SDCAXU_AUDIOMODULE1_CONTEXT, *PSDCAXU_AUDIOMODULE1_CONTEXT; + +typedef struct _SDCAXU_AUDIOMODULE2_CONTEXT { + ACXPNPEVENT Event; + ULONG Parameter1; + USHORT Parameter2; + ULONG InstanceId; + ACXCIRCUIT Circuit; +} SDCAXU_AUDIOMODULE2_CONTEXT, *PSDCAXU_AUDIOMODULE2_CONTEXT; + +WDF_DECLARE_CONTEXT_TYPE_WITH_NAME(SDCAXU_AUDIOMODULE0_CONTEXT, GetSdcaXuAudioModule0Context); +WDF_DECLARE_CONTEXT_TYPE_WITH_NAME(SDCAXU_AUDIOMODULE1_CONTEXT, GetSdcaXuAudioModule1Context); +WDF_DECLARE_CONTEXT_TYPE_WITH_NAME(SDCAXU_AUDIOMODULE2_CONTEXT, GetSdcaXuAudioModule2Context); + +typedef struct _AUDIOMODULE_PARAMETER_INFO +{ + USHORT AccessFlags; // get/set/basic-support attributes. + USHORT Flags; + ULONG Size; + DWORD VtType; + PVOID ValidSet; + ULONG ValidSetCount; +} AUDIOMODULE_PARAMETER_INFO, *PAUDIOMODULE_PARAMETER_INFO; + +// +// factory Module definitions +// +#define AUDIOMODULEDESCRIPTION L"Generic system module" +#define AUDIOMODULE_MAJOR 0x1 +#define AUDIOMODULE_MINOR 0X0 +static const GUID AudioModuleId = +{ 0xe24c8b6f, 0xede7, 0x4255, 0x8b, 0x39, 0x77, 0x97, 0x60, 0x15, 0xd5, 0x93 }; + +EVT_ACX_AUDIOMODULE_PROCESSCOMMAND SdcaXu_EvtProcessCommand; + +static +ULONG AudioModule_ValidParameterList[] = +{ + 1, 2, 5 +}; + +extern AUDIOMODULE_PARAMETER_INFO AudioModule_ParameterInfo[2]; + + +// +// render Module 0 definitions +// +#define AUDIOMODULE0DESCRIPTION L"Generic system module" +#define AUDIOMODULE0_MAJOR 0x1 +#define AUDIOMODULE0_MINOR 0X0 + +// {D96F901A-BFDD-46FD-9B1E-4FCD9D693360} +static const GUID AudioModule0Id = +{ 0xd96f901a, 0xbfdd, 0x46fd, { 0x9b, 0x1e, 0x4f, 0xcd, 0x9d, 0x69, 0x33, 0x60 } }; + +EVT_ACX_AUDIOMODULE_PROCESSCOMMAND SdcaXu_EvtProcessCommand0; + +static +ULONG AudioModule0_ValidParameterList[] = +{ + 1, 2, 5 +}; + +extern AUDIOMODULE_PARAMETER_INFO AudioModule0_ParameterInfo[2]; + +// +// render Module 1 definitions +// +static +BYTE AudioModule1_ValidParameterList[] = +{ + 0, 1, 2 +}; + +extern AUDIOMODULE_PARAMETER_INFO AudioModule1_ParameterInfo[3]; + +#define AUDIOMODULE1DESCRIPTION L"Module 1" +#define AUDIOMODULE1_MAJOR 0x2 +#define AUDIOMODULE1_MINOR 0X1 + +// {631A7961-DED6-4405-9A37-E4C4380918E4} +static const GUID AudioModule1Id = +{ 0x631a7961, 0xded6, 0x4405, { 0x9a, 0x37, 0xe4, 0xc4, 0x38, 0x9, 0x18, 0xe4 } }; + +EVT_ACX_AUDIOMODULE_PROCESSCOMMAND SdcaXu_EvtProcessCommand1; + +// +// render Module 2 definitions +// +static +ULONG AudioModule2_ValidParameterList[] = +{ + 1, 0xfffffffe +}; + +extern AUDIOMODULE_PARAMETER_INFO AudioModule2_ParameterInfo[2]; + +#define AUDIOMODULE2DESCRIPTION L"Module 2" +#define AUDIOMODULE2_MAJOR 0x2 +#define AUDIOMODULE2_MINOR 0X0 + +// {3471D6C5-6322-4730-8FD3-177061B52BB5} +static const GUID AudioModule2Id = +{ 0x3471d6c5, 0x6322, 0x4730, { 0x8f, 0xd3, 0x17, 0x70, 0x61, 0xb5, 0x2b, 0xb5 } }; + +EVT_ACX_AUDIOMODULE_PROCESSCOMMAND SdcaXu_EvtProcessCommand2; + +// General purpose helper functions + +NTSTATUS +AudioModule_GenericHandler_BasicSupport( + _In_ PAUDIOMODULE_PARAMETER_INFO ParameterInfo, + _Out_writes_bytes_opt_(*BufferCb) PVOID Buffer, + _Inout_ ULONG * BufferCb + ); + +BOOLEAN +IsAudioModuleParameterValid( + _In_ PAUDIOMODULE_PARAMETER_INFO ParameterInfo, + _In_reads_bytes_opt_(BufferCb) PVOID Buffer, + _In_ ULONG BufferCb + ); + +NTSTATUS +AudioModule_GenericHandler( + _In_ ULONG Verb, + _In_ ULONG ParameterId, + _In_ PAUDIOMODULE_PARAMETER_INFO ParameterInfo, + _Inout_updates_bytes_(ParameterInfo->Size) PVOID CurrentValue, + _In_reads_bytes_opt_(InBufferCb) PVOID InBuffer, + _In_ ULONG InBufferCb, + _Out_writes_bytes_opt_(*OutBufferCb) PVOID OutBuffer, + _Inout_ ULONG * OutBufferCb, + _In_ BOOL * ParameterChanged + ); + +/* make internal prototypes usable from C++ */ +#ifdef __cplusplus +} +#endif + +#endif // _AUDIOMODULE_H_ diff --git a/audio/Soundwire/Samples/SdcaVad/SdcaVXu/CircuitDevice.cpp b/audio/Soundwire/Samples/SdcaVad/SdcaVXu/CircuitDevice.cpp new file mode 100644 index 000000000..df0d2c90c --- /dev/null +++ b/audio/Soundwire/Samples/SdcaVad/SdcaVXu/CircuitDevice.cpp @@ -0,0 +1,464 @@ +/*++ + + THIS CODE AND INFORMATION IS PROVIDED "AS IS" WITHOUT WARRANTY OF ANY + KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE + IMPLIED WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A PARTICULAR + PURPOSE. + +Module Name: + + CircuitDevice.cpp + +Abstract: + + Raw PDO for ACX circuits. This file contains routines to create Device + and handle pnp requests + +Environment: + + Kernel mode + +--*/ + +#include "private.h" +#include +#include "stdunk.h" +#include "modulecircuit.h" + +#include "CircuitDevice.h" + +#include "SdcaVXuTestInterface.h" + +#ifndef __INTELLISENSE__ +#include "CircuitDevice.tmh" +#endif + +PAGED_CODE_SEG +NTSTATUS +SDCAVXu_CreateCircuitDevice( + _In_ WDFDEVICE Device +) +{ + PAGED_CODE(); + + NTSTATUS status = STATUS_SUCCESS; + WDFDEVICE circuitDevice = NULL; + + auto exit = scope_exit([&status, &circuitDevice]() { + if (!NT_SUCCESS(status)) + { + if (circuitDevice != NULL) + { + WdfObjectDelete(circuitDevice); + } + } + }); + + // + // Create a child audio device for this circuit. + // + PWDFDEVICE_INIT devInit = NULL; + devInit = WdfPdoInitAllocate(Device); + RETURN_NTSTATUS_IF_TRUE(NULL == devInit, STATUS_INSUFFICIENT_RESOURCES); + + auto devInit_free = scope_exit([&devInit, &status]() { + WdfDeviceInitFree(devInit); + }); + + // + // Provide DeviceID, HardwareIDs, CompatibleIDs and InstanceId + // + RETURN_NTSTATUS_IF_FAILED(WdfPdoInitAddHardwareID(devInit, &CircuitHardwareId)); + + RETURN_NTSTATUS_IF_FAILED(WdfPdoInitAssignDeviceID(devInit, &CircuitDeviceId)); + + RETURN_NTSTATUS_IF_FAILED(WdfPdoInitAddCompatibleID(devInit, &CircuitCompatibleId)); + + RETURN_NTSTATUS_IF_FAILED(WdfPdoInitAssignInstanceID(devInit, &CircuitInstanceId)); + + RETURN_NTSTATUS_IF_FAILED(WdfPdoInitAssignContainerID(devInit, &CircuitContainerId)); + + + // + // You can call WdfPdoInitAddDeviceText multiple times, adding device + // text for multiple locales. When the system displays the text, it + // chooses the text that matches the current locale, if available. + // Otherwise it will use the string for the default locale. + // The driver can specify the driver's default locale by calling + // WdfPdoInitSetDefaultLocale. + // + RETURN_NTSTATUS_IF_FAILED(WdfPdoInitAddDeviceText(devInit, + &CircuitDeviceDescription, + &CircuitDeviceLocation, + 0x409)); + + WdfPdoInitSetDefaultLocale(devInit, 0x409); + + // + // Allow ACX to add any pre-requirement it needs on this device. + // + ACX_DEVICEINIT_CONFIG acxDevInitCfg; + ACX_DEVICEINIT_CONFIG_INIT(&acxDevInitCfg); + acxDevInitCfg.Flags |= AcxDeviceInitConfigRawDevice; + RETURN_NTSTATUS_IF_FAILED(AcxDeviceInitInitialize(devInit, &acxDevInitCfg)); + + // + // Initialize the pnpPowerCallbacks structure. Callback events for PNP + // and Power are specified here. If you don't supply any callbacks, + // the Framework will take appropriate default actions based on whether + // DeviceInit is initialized to be an FDO, a PDO or a filter device + // object. + // + WDF_PNPPOWER_EVENT_CALLBACKS pnpPowerCallbacks; + WDF_PNPPOWER_EVENT_CALLBACKS_INIT(&pnpPowerCallbacks); + pnpPowerCallbacks.EvtDevicePrepareHardware = SdcaXuCircuit_EvtDevicePrepareHardware; + pnpPowerCallbacks.EvtDeviceReleaseHardware = SdcaXuCircuit_EvtDeviceReleaseHardware; + pnpPowerCallbacks.EvtDeviceSelfManagedIoInit = SdcaXuCircuit_EvtDeviceSelfManagedIoInit; + WdfDeviceInitSetPnpPowerEventCallbacks(devInit, &pnpPowerCallbacks); + + // + // Specify a context for this circuit device. + // + WDF_OBJECT_ATTRIBUTES attributes; + attributes.ParentObject = Device; + WDF_OBJECT_ATTRIBUTES_INIT_CONTEXT_TYPE(&attributes, SDCAXU_CIRCUIT_DEVICE_CONTEXT); + attributes.EvtCleanupCallback = SdcaXuCircuit_EvtDeviceContextCleanup; + attributes.ExecutionLevel = WdfExecutionLevelPassive; + RETURN_NTSTATUS_IF_FAILED(WdfDeviceCreate(&devInit, &attributes, &circuitDevice)); + + // + // devInit attached to device, no need to free + // + devInit_free.release(); + + // + // Tell the framework to set the NoDisplayInUI in the DeviceCaps so + // that the device does not show up in Device Manager. + // + WDF_DEVICE_PNP_CAPABILITIES pnpCaps; + WDF_DEVICE_PNP_CAPABILITIES_INIT(&pnpCaps); + pnpCaps.NoDisplayInUI = WdfTrue; + WdfDeviceSetPnpCapabilities(circuitDevice, &pnpCaps); + + // + // Init circuit's device context. + // + PSDCAXU_CIRCUIT_DEVICE_CONTEXT circuitDeviceContext; + circuitDeviceContext = GetCircuitDeviceContext(circuitDevice); + ASSERT(circuitDeviceContext != NULL); + circuitDeviceContext->FirstTimePrepareHardware = TRUE; + + // + // Allow ACX to add any post-requirement it needs on this device. + // + ACX_DEVICE_CONFIG devCfg; + ACX_DEVICE_CONFIG_INIT(&devCfg); + RETURN_NTSTATUS_IF_FAILED(AcxDeviceInitialize(circuitDevice, &devCfg)); + + // + // Add circuitDevice to Device's dynamic circuit device list. + // + RETURN_NTSTATUS_IF_FAILED(AcxDeviceAddCircuitDevice(Device, circuitDevice)); + + PSDCAXU_DEVICE_CONTEXT deviceContext; + deviceContext = GetSdcaXuDeviceContext(Device); + deviceContext->CircuitDevice = circuitDevice; + + // + // Add the RAWCONTROL interface to this device + // + RETURN_NTSTATUS_IF_FAILED(SdcaXu_ConfigureTestInterface(circuitDevice, &GUID_DEVINTERFACE_SDCAVXU_TEST_RAWCONTROL)); + + return status; +} + +PAGED_CODE_SEG +NTSTATUS +SdcaXu_EvtIoctlInterfaceTest( + _In_ WDFDEVICE Device, + _In_ WDFREQUEST Request +) +{ + PAGED_CODE(); + + size_t cbOutputBuffer = 0; + ULONG * input = nullptr; + ULONG * output = nullptr; + ULONG temp = 0; + + DrvLogInfo(g_SDCAVXuLog, FLAG_INFO, L"Received test IOCTL for device %p", Device); + + RETURN_NTSTATUS_IF_FAILED(WdfRequestRetrieveInputBuffer(Request, sizeof(ULONG), (PVOID*)&input, nullptr)); + + RETURN_NTSTATUS_IF_FAILED(WdfRequestRetrieveOutputBuffer(Request, 0, (PVOID*)&output, &cbOutputBuffer)); + if (cbOutputBuffer == 0) + { + WdfRequestSetInformation(Request, sizeof(ULONG)); + return STATUS_BUFFER_OVERFLOW; + } + else if (cbOutputBuffer < sizeof(ULONG)) + { + return STATUS_BUFFER_TOO_SMALL; + } + + temp = ~(*input); + + DrvLogInfo(g_SDCAVXuLog, FLAG_INFO, L"Input received: %x; output being sent back: %x", *input, temp); + + *output = temp; + + WdfRequestSetInformation(Request, sizeof(ULONG)); + return STATUS_SUCCESS; +} + +_Use_decl_annotations_ +PAGED_CODE_SEG +VOID +SdcaXu_EvtIoDeviceControl( + _In_ WDFQUEUE Queue, + _In_ WDFREQUEST Request, + _In_ size_t /*OutputBufferLength*/, + _In_ size_t /*InputBufferLength*/, + _In_ ULONG IoControlCode +) +{ + // The IO Queue for this driver is created with + // attributes.ExecutionLevel = WdfExecutionLevelPassive; + _Analysis_assume_(KeGetCurrentIrql() == PASSIVE_LEVEL); + PAGED_CODE(); + + WDFDEVICE device = WdfIoQueueGetDevice(Queue); + NTSTATUS status = STATUS_INVALID_DEVICE_REQUEST; + + switch (IoControlCode) + { + case IOCTL_SDCAVXU_INTERFACE_TEST: + status = SdcaXu_EvtIoctlInterfaceTest(device, Request); + break; + } + + WdfRequestComplete(Request, status); +} + +// +// SdcaXu_ConfigureTestInterface +// +// This function will: +// 1. Add a queue to the given device that will handle the test IOCTL +// 2. Create a device interface so the device can be located through SetupApi methods +// An alternative to using WdfDeviceCreateDeviceInterface would be to use WdfDeviceCreateSymbolicLink +// with a well-formed name +// +PAGED_CODE_SEG +NTSTATUS +SdcaXu_ConfigureTestInterface( + _In_ WDFDEVICE Device, + _In_ PCGUID Interface +) +{ + PAGED_CODE(); + + WDF_OBJECT_ATTRIBUTES attributes; + WDF_OBJECT_ATTRIBUTES_INIT(&attributes); + attributes.ExecutionLevel = WdfExecutionLevelPassive; + + WDF_IO_QUEUE_CONFIG queueConfig; + WDF_IO_QUEUE_CONFIG_INIT_DEFAULT_QUEUE(&queueConfig, WdfIoQueueDispatchSequential); + queueConfig.EvtIoDeviceControl = SdcaXu_EvtIoDeviceControl; + + RETURN_NTSTATUS_IF_FAILED(WdfIoQueueCreate(Device, &queueConfig, &attributes, nullptr)); + + RETURN_NTSTATUS_IF_FAILED(WdfDeviceCreateDeviceInterface(Device, + Interface, + NULL)); + return STATUS_SUCCESS; +} + +PAGED_CODE_SEG +NTSTATUS +SdcaXuCircuit_EvtDevicePrepareHardware( + _In_ WDFDEVICE Device, + _In_ WDFCMRESLIST ResourceList, + _In_ WDFCMRESLIST ResourceListTranslated +) +/*++ + +Routine Description: + + In this callback, the driver does whatever is necessary to make the + hardware ready to use. + +Arguments: + + Device - handle to a device + +Return Value: + + NT status value + +--*/ +{ + NTSTATUS status = STATUS_SUCCESS; + + UNREFERENCED_PARAMETER(ResourceList); + UNREFERENCED_PARAMETER(ResourceListTranslated); + + PAGED_CODE(); + + PSDCAXU_CIRCUIT_DEVICE_CONTEXT devCtx; + devCtx = GetCircuitDeviceContext(Device); + ASSERT(devCtx != NULL); + + if (!devCtx->FirstTimePrepareHardware) + { + // + // This is a rebalance. Validate the circuit resources and + // if needed, delete and re-create the circuit. + // The sample driver doesn't use resources, thus the existing + // circuits are kept. + // + return STATUS_SUCCESS; + } + + + // + // Set child's power policy. + // + RETURN_NTSTATUS_IF_FAILED(SdcaXuCircuit_SetPowerPolicy(Device)); + + + RETURN_NTSTATUS_IF_FAILED(SdcaXu_CreateModuleCircuit(Device)); + + // + // Keep track this is not the first time this callback was called. + // + devCtx->FirstTimePrepareHardware = FALSE; + + return status; +} + +PAGED_CODE_SEG +NTSTATUS +SdcaXuCircuit_SetPowerPolicy( + _In_ WDFDEVICE Device +) +{ + NTSTATUS status = STATUS_SUCCESS; + WDF_DEVICE_POWER_POLICY_IDLE_SETTINGS idleSettings; + //WDF_DEVICE_POWER_POLICY_WAKE_SETTINGS wakeSettings; + + PAGED_CODE(); + + // + // Init the idle policy structure. + // + //WDF_DEVICE_POWER_POLICY_IDLE_SETTINGS_INIT(&idleSettings, IdleCanWakeFromS0); + WDF_DEVICE_POWER_POLICY_IDLE_SETTINGS_INIT(&idleSettings, IdleCannotWakeFromS0); + idleSettings.IdleTimeout = IDLE_POWER_TIMEOUT; + idleSettings.IdleTimeoutType = SystemManagedIdleTimeoutWithHint; + + status = WdfDeviceAssignS0IdleSettings(Device, &idleSettings); + + return status; +} + +PAGED_CODE_SEG +NTSTATUS +SdcaXuCircuit_EvtDeviceReleaseHardware( + _In_ WDFDEVICE Device, + _In_ WDFCMRESLIST ResourceListTranslated +) +/*++ + +Routine Description: + + In this callback, the driver releases the h/w resources allocated in the + prepare h/w callback. + +Arguments: + + Device - handle to a device + +Return Value: + + NT status value + +--*/ +{ + NTSTATUS status = STATUS_SUCCESS; + + UNREFERENCED_PARAMETER(Device); + UNREFERENCED_PARAMETER(ResourceListTranslated); + + PAGED_CODE(); + + PSDCAXU_CIRCUIT_DEVICE_CONTEXT devCtx; + devCtx = GetCircuitDeviceContext(Device); + ASSERT(devCtx != NULL); + UNREFERENCED_PARAMETER(devCtx); + + + return status; +} + +PAGED_CODE_SEG +NTSTATUS +SdcaXuCircuit_EvtDeviceSelfManagedIoInit( + _In_ WDFDEVICE Device +) +/*++ + +Routine Description: + + In this callback, the driver does one-time init of self-managed I/O data. + +Arguments: + + Device - handle to a device + +Return Value: + + NT status value + +--*/ +{ + PAGED_CODE(); + + PSDCAXU_CIRCUIT_DEVICE_CONTEXT devCtx; + devCtx = GetCircuitDeviceContext(Device); + ASSERT(devCtx != NULL); + UNREFERENCED_PARAMETER(devCtx); + + return STATUS_SUCCESS; +} + +#pragma code_seg() +VOID +SdcaXuCircuit_EvtDeviceContextCleanup( + _In_ WDFOBJECT WdfDevice +) +/*++ + +Routine Description: + + In this callback, it cleans up device context. + +Arguments: + + WdfDevice - WDF device object + +Return Value: + + NULL + +--*/ +{ + WDFDEVICE device; + PSDCAXU_CIRCUIT_DEVICE_CONTEXT devCtx; + + device = (WDFDEVICE)WdfDevice; + devCtx = GetCircuitDeviceContext(device); + ASSERT(devCtx != NULL); +} + + diff --git a/audio/Soundwire/Samples/SdcaVad/SdcaVXu/CircuitDevice.h b/audio/Soundwire/Samples/SdcaVad/SdcaVXu/CircuitDevice.h new file mode 100644 index 000000000..f537be045 --- /dev/null +++ b/audio/Soundwire/Samples/SdcaVad/SdcaVXu/CircuitDevice.h @@ -0,0 +1,57 @@ +/*++ + +Copyright (c) Microsoft Corporation. All rights reserved. + + THIS CODE AND INFORMATION IS PROVIDED "AS IS" WITHOUT WARRANTY OF ANY + KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE + IMPLIED WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A PARTICULAR + PURPOSE. + +Module Name: + + CircuitDevice.h + +Abstract: + + Raw PDO for ACX circuits. This file contains routines to create Device + and handle pnp requests + +Environment: + + Kernel mode + +--*/ + +#pragma once + +// +// Circuit's settings for raw PDO. +// +DECLARE_CONST_UNICODE_STRING(CircuitDeviceId, L"SDCAVad\\ExtensionDevice"); +DECLARE_CONST_UNICODE_STRING(CircuitHardwareId, L"SDCAVad\\ExtensionDevice"); +DECLARE_CONST_UNICODE_STRING(CircuitInstanceId, L"00"); +DECLARE_CONST_UNICODE_STRING(CircuitCompatibleId, SDCAVAD_COMPATIBLE_ID); +DECLARE_CONST_UNICODE_STRING(CircuitContainerId, SDCAVAD_CONTAINER_ID); +DECLARE_CONST_UNICODE_STRING(CircuitDeviceDescription, L"SDCAVad Device (Ext)"); +DECLARE_CONST_UNICODE_STRING(CircuitDeviceLocation, L"SDCAVad Device"); + +PAGED_CODE_SEG +NTSTATUS +SDCAVXu_CreateCircuitDevice( + _In_ WDFDEVICE Device +); + +PAGED_CODE_SEG +NTSTATUS +SdcaXuCircuit_SetPowerPolicy( + _In_ WDFDEVICE Device +); + +// Render Device callbacks. +EVT_WDF_DEVICE_PREPARE_HARDWARE SdcaXuCircuit_EvtDevicePrepareHardware; +EVT_WDF_DEVICE_RELEASE_HARDWARE SdcaXuCircuit_EvtDeviceReleaseHardware; +EVT_WDF_DEVICE_SELF_MANAGED_IO_INIT SdcaXuCircuit_EvtDeviceSelfManagedIoInit; +EVT_WDF_DEVICE_CONTEXT_CLEANUP SdcaXuCircuit_EvtDeviceContextCleanup; + +#pragma code_seg() + diff --git a/audio/Soundwire/Samples/SdcaVad/SdcaVXu/Device.h b/audio/Soundwire/Samples/SdcaVad/SdcaVXu/Device.h new file mode 100644 index 000000000..eaf6b3067 --- /dev/null +++ b/audio/Soundwire/Samples/SdcaVad/SdcaVXu/Device.h @@ -0,0 +1,37 @@ +/*++ + +Copyright (c) Microsoft Corporation. All rights reserved. + + THIS CODE AND INFORMATION IS PROVIDED "AS IS" WITHOUT WARRANTY OF ANY + KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE + IMPLIED WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A PARTICULAR + PURPOSE. + +Module Name: + + render.h + +Abstract: + + Contains structure definitions and function prototypes private to + the driver. + +Environment: + + Kernel mode + +--*/ + +#pragma once + +// +// Device settings for raw PDO. +// +DECLARE_CONST_UNICODE_STRING(DeviceId, L"SDCAVad\\ExtensionDevice"); +DECLARE_CONST_UNICODE_STRING(HardwareId, L"SDCAVad\\ExtensionDevice"); +DECLARE_CONST_UNICODE_STRING(InstanceId, L"00"); +DECLARE_CONST_UNICODE_STRING(CompatibleId, SDCAVAD_COMPATIBLE_ID); +DECLARE_CONST_UNICODE_STRING(ContainerId, SDCAVAD_CONTAINER_ID); +DECLARE_CONST_UNICODE_STRING(DeviceDescription, L"SDCAVad Extension Device"); +DECLARE_CONST_UNICODE_STRING(DeviceLocation, L"SDCAVad"); + diff --git a/audio/Soundwire/Samples/SdcaVad/SdcaVXu/ModuleCircuit.cpp b/audio/Soundwire/Samples/SdcaVad/SdcaVXu/ModuleCircuit.cpp new file mode 100644 index 000000000..85926c070 --- /dev/null +++ b/audio/Soundwire/Samples/SdcaVad/SdcaVXu/ModuleCircuit.cpp @@ -0,0 +1,376 @@ +/*++ + + THIS CODE AND INFORMATION IS PROVIDED "AS IS" WITHOUT WARRANTY OF ANY + KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE + IMPLIED WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A PARTICULAR + PURPOSE. + +Module Name: + + ModuleCircuit.cpp + +Abstract: + + Circuit implementation that hosts an AudioModule + +Environment: + + Kernel mode + +--*/ + +#include "private.h" +#include "audiomodule.h" +#include +#include "stdunk.h" +#include +#include +#include + +#include "ModuleCircuit.h" + +#ifndef __INTELLISENSE__ +#include "ModuleCircuit.tmh" +#endif + +DEFINE_GUID(SDCAXU_MODULECIRCUIT_GUID, +0x63434534, 0xBD84, 0x8DFE, 0x7A, 0xAA, 0xFF, 0x84, 0xD8, 0x23, 0xAB, 0xBD); + +DEFINE_GUID(KSCATEGORY_ACXCIRCUIT, +0x2c6bb644L, 0xe1ae, 0x47f8, 0x9a, 0x2b, 0x1d, 0x1f, 0xa7, 0x50, 0xf2, 0xfa); + +DEFINE_GUID(SDCAXU_FACTORY_CATEGORY, +0x1983badd, 0x5cd, 0x4dc8, 0x83, 0xe5, 0x84, 0xaf, 0x83, 0xdf, 0xb0, 0xc3); + +// +// Name of circuit hosting an XU module. +// +DECLARE_CONST_UNICODE_STRING(s_ModuleCircuitName, L"ExtensionModuleCircuit"); + + +PAGED_CODE_SEG +NTSTATUS +SdcaXu_EvtProcessCommand( + _In_ ACXAUDIOMODULE AudioModule, + _In_ PVOID InBuffer, + _In_ ULONG InBufferCb, + _In_ PVOID OutBuffer, + _Inout_ PULONG OutBufferCb + ) +{ + PAGED_CODE(); + + NTSTATUS status = STATUS_SUCCESS; + BOOL fNewValue = FALSE; + PVOID currentValue; + PVOID inBuffer = NULL; + ULONG inBufferCb = 0; + PSDCAXU_AUDIOMODULE_CONTEXT audioModuleCtx; + AUDIOMODULE_PARAMETER_INFO * parameterInfo = NULL; + AUDIOMODULE_CUSTOM_COMMAND * command = NULL; + + audioModuleCtx = GetSdcaXuAudioModuleContext(AudioModule); + if (audioModuleCtx == NULL) + { + ASSERT(FALSE); // this should not happen. + status = STATUS_INTERNAL_ERROR; + goto exit; + } + + // + // Basic parameter validation (module specific). + // + if (InBuffer == NULL || InBufferCb == 0) + { + return STATUS_INVALID_PARAMETER; + } + + if (InBufferCb < sizeof(AUDIOMODULE_CUSTOM_COMMAND)) + { + return STATUS_INVALID_PARAMETER; + } + + command = (AUDIOMODULE_CUSTOM_COMMAND*)InBuffer; + + if (command->ParameterId >= SIZEOF_ARRAY(AudioModule_ParameterInfo)) + { + return STATUS_INVALID_PARAMETER; + } + + // + // Validate the parameter referenced in the command. + // + switch (command->ParameterId) + { + case AudioModuleParameter1: + currentValue = &audioModuleCtx->Parameter1; + parameterInfo = &AudioModule_ParameterInfo[AudioModuleParameter1]; + break; + case AudioModuleParameter2: + currentValue = &audioModuleCtx->Parameter2; + parameterInfo = &AudioModule_ParameterInfo[AudioModuleParameter2]; + break; + default: + status = STATUS_INVALID_PARAMETER; + goto exit; + } + + // + // Update input buffer ptr/size. + // + inBuffer = (PVOID)((ULONG_PTR)InBuffer + sizeof(AUDIOMODULE_CUSTOM_COMMAND)); + inBufferCb = InBufferCb - sizeof(AUDIOMODULE_CUSTOM_COMMAND); + + if (inBufferCb == 0) + { + inBuffer = NULL; + } + + status = AudioModule_GenericHandler( + command->Verb, + command->ParameterId, + parameterInfo, + currentValue, + inBuffer, + inBufferCb, + OutBuffer, + OutBufferCb, + &fNewValue); + + if (!NT_SUCCESS(status)) + { + goto exit; + } + + if (fNewValue && + (parameterInfo->Flags & AUDIOMODULE_PARAMETER_FLAG_CHANGE_NOTIFICATION)) + { + AUDIOMODULE_CUSTOM_NOTIFICATION customNotification = {0}; + + customNotification.Type = AudioModuleParameterChanged; + customNotification.ParameterChanged.ParameterId = command->ParameterId; + + status = AcxPnpEventGenerateEvent(audioModuleCtx->Event, &customNotification, (USHORT)sizeof(customNotification)); + if (!NT_SUCCESS(status)) + { + goto exit; + } + } + + // Normalize error code. + status = STATUS_SUCCESS; + +exit: + return status; +} + + +PAGED_CODE_SEG +NTSTATUS +SdcaXu_CreateModuleCircuitModules( + _In_ WDFDEVICE Device, + _In_ ACXCIRCUIT Circuit + ) +/*++ + +Routine Description: + + This routine creates all of the audio module elements and adds them to the circuit + +Return Value: + + NT status value + +--*/ +{ + PAGED_CODE(); + + NTSTATUS status; + WDF_OBJECT_ATTRIBUTES attributes; + ACX_AUDIOMODULE_CALLBACKS audioModuleCallbacks; + ACX_AUDIOMODULE_CONFIG audioModuleCfg; + ACXAUDIOMODULE audioModuleElement; + PSDCAXU_AUDIOMODULE_CONTEXT audioModuleCtx; + ACX_PNPEVENT_CONFIG audioModuleEventCfg; + ACXPNPEVENT audioModuleEvent; + + ACX_AUDIOMODULE_CALLBACKS_INIT(&audioModuleCallbacks); + audioModuleCallbacks.EvtAcxAudioModuleProcessCommand = SdcaXu_EvtProcessCommand; + + ACX_AUDIOMODULE_CONFIG_INIT(&audioModuleCfg); + audioModuleCfg.Name = &AudioModuleId; + audioModuleCfg.Descriptor.ClassId = AudioModuleId; + audioModuleCfg.Descriptor.InstanceId = AUDIOMODULE_INSTANCE_ID(0,0); + audioModuleCfg.Descriptor.VersionMajor = AUDIOMODULE_MAJOR; + audioModuleCfg.Descriptor.VersionMinor = AUDIOMODULE_MINOR; + status = RtlStringCchCopyNW(audioModuleCfg.Descriptor.Name, + ACX_AUDIOMODULE_MAX_NAME_CCH_SIZE, + AUDIOMODULEDESCRIPTION, + wcslen(AUDIOMODULEDESCRIPTION)); + if (!NT_SUCCESS(status)) + { + ASSERT(FALSE); + goto exit; + } + + audioModuleCfg.Callbacks = &audioModuleCallbacks; + + + WDF_OBJECT_ATTRIBUTES_INIT_CONTEXT_TYPE(&attributes, SDCAXU_AUDIOMODULE_CONTEXT); + attributes.ParentObject = Circuit; + + status = AcxAudioModuleCreate(Circuit, &attributes, &audioModuleCfg, &audioModuleElement); + if (!NT_SUCCESS(status)) + { + ASSERT(FALSE); + goto exit; + } + + audioModuleCtx = GetSdcaXuAudioModuleContext(audioModuleElement); + ASSERT(audioModuleCtx); + + ACX_PNPEVENT_CONFIG_INIT(&audioModuleEventCfg); + + WDF_OBJECT_ATTRIBUTES_INIT_CONTEXT_TYPE(&attributes, SDCAXU_PNPEVENT_CONTEXT); + attributes.ParentObject = audioModuleElement; + status = AcxPnpEventCreate(Device, audioModuleElement, &attributes, &audioModuleEventCfg, &audioModuleEvent); + if (!NT_SUCCESS(status)) + { + ASSERT(FALSE); + goto exit; + } + + audioModuleCtx->Event = audioModuleEvent; + + status = AcxCircuitAddElements(Circuit, (ACXELEMENT *) &audioModuleElement, 1); + if (!NT_SUCCESS(status)) + { + ASSERT(FALSE); + goto exit; + } + + // + // Done. + // + status = STATUS_SUCCESS; + +exit: + return status; +} + +_Function_class_(EVT_ACX_CIRCUIT_CREATE_STREAM) +PAGED_CODE_SEG +NTSTATUS +SdcaXu_EvtAcxCircuitCreateStream( + _In_ WDFDEVICE Device, + _In_ ACXCIRCUIT Circuit, + _In_ ACXPIN Pin, + _In_ PACXSTREAM_INIT StreamInit, + _In_ ACXDATAFORMAT StreamFormat, + _In_ const GUID * SignalProcessingMode, + _In_ ACXOBJECTBAG VarArguments +) +{ + PAGED_CODE(); + + UNREFERENCED_PARAMETER(Device); + UNREFERENCED_PARAMETER(Circuit); + UNREFERENCED_PARAMETER(Pin); + UNREFERENCED_PARAMETER(StreamInit); + UNREFERENCED_PARAMETER(StreamFormat); + UNREFERENCED_PARAMETER(SignalProcessingMode); + UNREFERENCED_PARAMETER(VarArguments); + + return STATUS_NOT_SUPPORTED; +} + + +PAGED_CODE_SEG +NTSTATUS +SdcaXu_CreateModuleCircuit( + _In_ WDFDEVICE Device + ) +{ + PAGED_CODE(); + + NTSTATUS status; + WDF_OBJECT_ATTRIBUTES attributes; + ACXCIRCUIT circuit; + PACXCIRCUIT_INIT circuitInit = NULL; + SDCAXU_MODULECIRCUIT_CONTEXT * circuitCtx; + + // + // ACX expects an 'other' circuit to start with KSCATEGORY_ACXCIRCUIT and also have + // KSCATEGORY_AUDIO. The XU driver can add other categories after these. + // + GUID categories[] = { + KSCATEGORY_ACXCIRCUIT, + KSCATEGORY_AUDIO, + SDCAXU_FACTORY_CATEGORY + }; + + // + // Get a CircuitInit structure. + // + circuitInit = AcxCircuitInitAllocate(Device); + + // + // Add circuit identifiers. + // + AcxCircuitInitSetComponentId(circuitInit, &SDCAXU_MODULECIRCUIT_GUID); + AcxCircuitInitAssignCategories(circuitInit, categories, ARRAYSIZE(categories)); + AcxCircuitInitSetCircuitType(circuitInit, AcxCircuitTypeOther); + AcxCircuitInitAssignName(circuitInit, &s_ModuleCircuitName); + AcxCircuitInitAssignAcxCreateStreamCallback(circuitInit, SdcaXu_EvtAcxCircuitCreateStream); + + // + // Create the circuit. + // + WDF_OBJECT_ATTRIBUTES_INIT_CONTEXT_TYPE(&attributes, SDCAXU_MODULECIRCUIT_CONTEXT); + attributes.ParentObject = Device; + status = AcxCircuitCreate(Device, &attributes, &circuitInit, &circuit); + if (!NT_SUCCESS(status)) + { + ASSERT(FALSE); + goto exit; + } + + // circuitInit has been freed by AcxCircuitCreate at this point + ASSERT(circuitInit == NULL); + + ASSERT(circuit != NULL); + circuitCtx = GetModuleCircuitContext(circuit); + ASSERT(circuitCtx); + UNREFERENCED_PARAMETER(circuitCtx); + + // + // Create and add the audio modules to the circuit + // + status = SdcaXu_CreateModuleCircuitModules(Device, circuit); + if (!NT_SUCCESS(status)) + { + ASSERT(FALSE); + goto exit; + } + + // + // Add circuit to device. + // + status = AcxDeviceAddCircuit(Device, circuit); + if (!NT_SUCCESS(status)) + { + ASSERT(FALSE); + goto exit; + } + + // Done + status = STATUS_SUCCESS; + +exit: + if (circuitInit) + { + AcxCircuitInitFree(circuitInit); + } + return status; +} + diff --git a/audio/Soundwire/Samples/SdcaVad/SdcaVXu/ModuleCircuit.h b/audio/Soundwire/Samples/SdcaVad/SdcaVXu/ModuleCircuit.h new file mode 100644 index 000000000..17fbfc4de --- /dev/null +++ b/audio/Soundwire/Samples/SdcaVad/SdcaVXu/ModuleCircuit.h @@ -0,0 +1,32 @@ +/*++ + +Copyright (c) Microsoft Corporation. All rights reserved. + + THIS CODE AND INFORMATION IS PROVIDED "AS IS" WITHOUT WARRANTY OF ANY + KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE + IMPLIED WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A PARTICULAR + PURPOSE. + +Module Name: + + ModuleCircuit.h + +Abstract: + + Raw PDO for ACX circuits. This file contains routines to create Device + and handle pnp requests + +Environment: + + Kernel mode + +--*/ + +#pragma once + +PAGED_CODE_SEG +NTSTATUS +SdcaXu_CreateModuleCircuit( + _In_ WDFDEVICE Device +); + diff --git a/audio/Soundwire/Samples/SdcaVad/SdcaVXu/SDCAVXu.vcxproj b/audio/Soundwire/Samples/SdcaVad/SdcaVXu/SDCAVXu.vcxproj new file mode 100644 index 000000000..c3cfaadc4 --- /dev/null +++ b/audio/Soundwire/Samples/SdcaVad/SdcaVXu/SDCAVXu.vcxproj @@ -0,0 +1,355 @@ + + + + + Debug + x64 + + + Release + x64 + + + Debug + Win32 + + + Release + Win32 + + + Debug + ARM + + + Release + ARM + + + Debug + ARM64 + + + Release + ARM64 + + + + {B1B6FD46-A26E-4D07-BE2E-FD87725500DC} + $(MSBuildProjectName) + 1 + 31 + 1 + 0 + v4.5 + 12.0 + false + true + Debug + Win32 + $(LatestTargetPlatformVersion) + + + + Windows10 + true + WindowsKernelModeDriver10.0 + Driver + KMDF + Universal + + + Windows10 + false + WindowsKernelModeDriver10.0 + Driver + KMDF + Universal + + + Windows10 + true + WindowsKernelModeDriver10.0 + Driver + KMDF + Universal + + + Windows10 + false + WindowsKernelModeDriver10.0 + Driver + KMDF + Universal + + + Windows10 + true + WindowsKernelModeDriver10.0 + Driver + KMDF + Universal + + + Windows10 + false + WindowsKernelModeDriver10.0 + Driver + KMDF + Universal + + + Windows10 + true + WindowsKernelModeDriver10.0 + Driver + KMDF + Universal + + + Windows10 + false + WindowsKernelModeDriver10.0 + Driver + KMDF + Universal + + + + $(IntDir) + + + + + + + + + + DbgengKernelDebugger + + + DbgengKernelDebugger + + + DbgengKernelDebugger + + + DbgengKernelDebugger + + + DbgengKernelDebugger + + + DbgengKernelDebugger + + + DbgengKernelDebugger + + + DbgengKernelDebugger + + + + %(AdditionalDependencies);$(DDK_LIB_PATH)\libcntpr.lib;wpprecorder.lib;$(DDK_LIB_PATH)\acx\km\$(ACX_VERSION_MAJOR).$(ACX_VERSION_MINOR)\acxstub.lib + + + %(AdditionalIncludeDirectories);$(SDK_INC_PATH);$(DDK_INC_PATH)\acx\km\$(ACX_VERSION_MAJOR).$(ACX_VERSION_MINOR);..\inc;. + %(PreprocessorDefinitions);UNICODE;_UNICODE + %(PreprocessorDefinitions);ACX_VERSION_MAJOR=1;ACX_VERSION_MINOR=0;_NEW_DELETE_OPERATORS_ + true + -km \ +-DENABLE_WPP_RECORDER=1 \ +-DENABLE_WPP_TRACE_FILTERING_WITH_WPP_RECORDER=1 \ +-func:DoTraceLevelMessage(LEVEL,FLAGS,MSG,...) \ +-p:SDCAVCodec + ..\inc\trace_macros.h + true + + + sha256 + + + + + %(AdditionalDependencies);$(DDK_LIB_PATH)\libcntpr.lib;wpprecorder.lib;$(DDK_LIB_PATH)\acx\km\$(ACX_VERSION_MAJOR).$(ACX_VERSION_MINOR)\acxstub.lib + + + %(AdditionalIncludeDirectories);$(SDK_INC_PATH);$(DDK_INC_PATH)\acx\km\$(ACX_VERSION_MAJOR).$(ACX_VERSION_MINOR);..\inc;. + %(PreprocessorDefinitions);UNICODE;_UNICODE + %(PreprocessorDefinitions);ACX_VERSION_MAJOR=1;ACX_VERSION_MINOR=0;_NEW_DELETE_OPERATORS_ + true + -km \ +-DENABLE_WPP_RECORDER=1 \ +-DENABLE_WPP_TRACE_FILTERING_WITH_WPP_RECORDER=1 \ +-func:DoTraceLevelMessage(LEVEL,FLAGS,MSG,...) \ +-p:SDCAVCodec + ..\inc\trace_macros.h + true + + + sha256 + + + + + %(AdditionalDependencies);$(DDK_LIB_PATH)\libcntpr.lib;wpprecorder.lib;$(DDK_LIB_PATH)\acx\km\$(ACX_VERSION_MAJOR).$(ACX_VERSION_MINOR)\acxstub.lib + + + %(AdditionalIncludeDirectories);$(SDK_INC_PATH);$(DDK_INC_PATH)\acx\km\$(ACX_VERSION_MAJOR).$(ACX_VERSION_MINOR);..\inc;. + %(PreprocessorDefinitions);UNICODE;_UNICODE + %(PreprocessorDefinitions);ACX_VERSION_MAJOR=1;ACX_VERSION_MINOR=0;_NEW_DELETE_OPERATORS_ + true + -km \ +-DENABLE_WPP_RECORDER=1 \ +-DENABLE_WPP_TRACE_FILTERING_WITH_WPP_RECORDER=1 \ +-func:DoTraceLevelMessage(LEVEL,FLAGS,MSG,...) \ +-p:SDCAVCodec + ..\inc\trace_macros.h + true + + + sha256 + + + + + %(AdditionalDependencies);$(DDK_LIB_PATH)\libcntpr.lib;wpprecorder.lib;$(DDK_LIB_PATH)\acx\km\$(ACX_VERSION_MAJOR).$(ACX_VERSION_MINOR)\acxstub.lib + + + %(AdditionalIncludeDirectories);$(SDK_INC_PATH);$(DDK_INC_PATH)\acx\km\$(ACX_VERSION_MAJOR).$(ACX_VERSION_MINOR);..\inc;. + %(PreprocessorDefinitions);UNICODE;_UNICODE + %(PreprocessorDefinitions);ACX_VERSION_MAJOR=1;ACX_VERSION_MINOR=0;_NEW_DELETE_OPERATORS_ + true + -km \ +-DENABLE_WPP_RECORDER=1 \ +-DENABLE_WPP_TRACE_FILTERING_WITH_WPP_RECORDER=1 \ +-func:DoTraceLevelMessage(LEVEL,FLAGS,MSG,...) \ +-p:SDCAVCodec + ..\inc\trace_macros.h + true + + + sha256 + + + + + %(AdditionalDependencies);$(DDK_LIB_PATH)\libcntpr.lib;wpprecorder.lib;$(DDK_LIB_PATH)\acx\km\$(ACX_VERSION_MAJOR).$(ACX_VERSION_MINOR)\acxstub.lib + + + %(AdditionalIncludeDirectories);$(SDK_INC_PATH);$(DDK_INC_PATH)\acx\km\$(ACX_VERSION_MAJOR).$(ACX_VERSION_MINOR);..\inc;. + %(PreprocessorDefinitions);UNICODE;_UNICODE + %(PreprocessorDefinitions);ACX_VERSION_MAJOR=1;ACX_VERSION_MINOR=0;_NEW_DELETE_OPERATORS_ + true + -km \ +-DENABLE_WPP_RECORDER=1 \ +-DENABLE_WPP_TRACE_FILTERING_WITH_WPP_RECORDER=1 \ +-func:DoTraceLevelMessage(LEVEL,FLAGS,MSG,...) \ +-p:SDCAVCodec + ..\inc\trace_macros.h + true + + + sha256 + + + + + %(AdditionalDependencies);$(DDK_LIB_PATH)\libcntpr.lib;wpprecorder.lib;$(DDK_LIB_PATH)\acx\km\$(ACX_VERSION_MAJOR).$(ACX_VERSION_MINOR)\acxstub.lib + + + %(AdditionalIncludeDirectories);$(SDK_INC_PATH);$(DDK_INC_PATH)\acx\km\$(ACX_VERSION_MAJOR).$(ACX_VERSION_MINOR);..\inc;. + %(PreprocessorDefinitions);UNICODE;_UNICODE + %(PreprocessorDefinitions);ACX_VERSION_MAJOR=1;ACX_VERSION_MINOR=0;_NEW_DELETE_OPERATORS_ + true + -km \ +-DENABLE_WPP_RECORDER=1 \ +-DENABLE_WPP_TRACE_FILTERING_WITH_WPP_RECORDER=1 \ +-func:DoTraceLevelMessage(LEVEL,FLAGS,MSG,...) \ +-p:SDCAVCodec + ..\inc\trace_macros.h + true + + + sha256 + + + + + %(AdditionalDependencies);$(DDK_LIB_PATH)\libcntpr.lib;wpprecorder.lib;$(DDK_LIB_PATH)\acx\km\$(ACX_VERSION_MAJOR).$(ACX_VERSION_MINOR)\acxstub.lib + + + %(AdditionalIncludeDirectories);$(SDK_INC_PATH);$(DDK_INC_PATH)\acx\km\$(ACX_VERSION_MAJOR).$(ACX_VERSION_MINOR);..\inc;. + %(PreprocessorDefinitions);UNICODE;_UNICODE + %(PreprocessorDefinitions);ACX_VERSION_MAJOR=1;ACX_VERSION_MINOR=0;_NEW_DELETE_OPERATORS_ + true + -km \ +-DENABLE_WPP_RECORDER=1 \ +-DENABLE_WPP_TRACE_FILTERING_WITH_WPP_RECORDER=1 \ +-func:DoTraceLevelMessage(LEVEL,FLAGS,MSG,...) \ +-p:SDCAVCodec + ..\inc\trace_macros.h + true + + + sha256 + + + + + %(AdditionalDependencies);$(DDK_LIB_PATH)\libcntpr.lib;wpprecorder.lib;$(DDK_LIB_PATH)\acx\km\$(ACX_VERSION_MAJOR).$(ACX_VERSION_MINOR)\acxstub.lib + + + %(AdditionalIncludeDirectories);$(SDK_INC_PATH);$(DDK_INC_PATH)\acx\km\$(ACX_VERSION_MAJOR).$(ACX_VERSION_MINOR);..\inc;. + %(PreprocessorDefinitions);UNICODE;_UNICODE + %(PreprocessorDefinitions);ACX_VERSION_MAJOR=1;ACX_VERSION_MINOR=0;_NEW_DELETE_OPERATORS_ + true + -km \ +-DENABLE_WPP_RECORDER=1 \ +-DENABLE_WPP_TRACE_FILTERING_WITH_WPP_RECORDER=1 \ +-func:DoTraceLevelMessage(LEVEL,FLAGS,MSG,...) \ +-p:SDCAVCodec + ..\inc\trace_macros.h + true + + + sha256 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/audio/Soundwire/Samples/SdcaVad/SdcaVXu/SDCAVXu.vcxproj.Filters b/audio/Soundwire/Samples/SdcaVad/SdcaVXu/SDCAVXu.vcxproj.Filters new file mode 100644 index 000000000..8d058e73a --- /dev/null +++ b/audio/Soundwire/Samples/SdcaVad/SdcaVXu/SDCAVXu.vcxproj.Filters @@ -0,0 +1,21 @@ + + + + + {4FC737F1-C7A5-4376-A066-2A32D752A2FF} + cpp;c;cc;cxx;def;odl;idl;hpj;bat;asm;asmx + + + {93995380-89BD-4b04-88EB-625FBE52EBFB} + h;hpp;hxx;hm;inl;inc;xsd + + + {67DA6AB6-F800-4c08-8B7A-83BB121AAD01} + rc;ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe;resx;tiff;tif;png;wav;mfcribbon-ms + + + {8E41214B-6785-4CFE-B992-037D68949A14} + inf;inv;inx;mof;mc; + + + diff --git a/audio/Soundwire/Samples/SdcaVad/SdcaVXu/SdcaVXu.inx b/audio/Soundwire/Samples/SdcaVad/SdcaVXu/SdcaVXu.inx new file mode 100644 index 000000000..28c86344b --- /dev/null +++ b/audio/Soundwire/Samples/SdcaVad/SdcaVXu/SdcaVXu.inx @@ -0,0 +1,144 @@ +;/*++ +; +;Copyright (c) Microsoft Corporation. All rights reserved. +; +;Module Name: +; +; SDCAVXu.INF +; +;--*/ + +[Version] +Signature="$WINDOWS NT$" +Class=Extension +ClassGuid={e2f84ce7-8efa-411c-aa69-97454ca4cb57} +Provider=%ProviderName% +DriverVer=06/13/2016, 1.0.0.1 +CatalogFile=SDCAVad.cat +ExtensionId={790C1DE0-AA33-4CB8-BB0C-F523C73B4AA1} +PnpLockdown=1 + +[DestinationDirs] +DefaultDestDir = 13 + +;***************************************** +; Audio Device Install Section +;***************************************** +[Manufacturer] +%StdMfg%=Standard,NT$ARCH$.10.0...19041 + +;*************************************************************** +; XU filter is installed as Lower filter to SDCAVCodec +; Match hw-id +;*************************************************************** +[Standard.NT$ARCH$.10.0...19041] +; Replace following with real Hardware Ids +%WdfExtensionDevice.DeviceDesc%=Audio_Device, ROOT\SDCAVCodec +%WdfExtensionDevice.DeviceDesc%=Audio_Device, SOUNDWIRE\AUDIOFUNCTION +%WdfExtensionDevice.DeviceDesc%=Audio_Device, SDCA_10\AUDIOFUNCTION +%WdfExtensionDevice.DeviceDesc%=Audio_Device, SDCA_11\AUDIOFUNCTION + +%WdfExtensionDevice.DeviceDesc%=Audio_APO_Device, SOUNDWIRE\DynamicEnumChild + +[Audio_Device.NT] +CopyFiles=Audio_Device.NT.Copy + +[Audio_Device.NT.Copy] +SDCAVXu.sys + +[Audio_Device.NT.Filters] +AddFilter=SDCAVXu,,SDCAVXuInstall + +[Audio_APO_Device.NT] + +[Audio_APO_Device.NT.HW] +; The FriendlyName_AddReg will change the name of the SdcaClass child device +;AddReg = FriendlyName_AddReg + +;[FriendlyName_AddReg] +;HKR,,FriendlyName,,%ExtendedFriendlyName% + +[Audio_APO_Device.NT.Components] +AddComponent = SdcaVKwsApo,,Apo_AddComponent + +[Apo_AddComponent] +ComponentIDs = VEN_SDCAV_SMPL&CID_APO +Description = "Audio SDCAV APO Sample" + +[SDCAVXuInstall] +FilterLevel=SDCAXu + +[DeviceExtensions.I.Microphone] +AddReg=DeviceExtensions.I.Microphone.AddReg + +[DeviceExtensions.I.Microphone.AddReg] +HKR,FX\0,%PKEY_FX_Association%,,%KSNODETYPE_ANY% +HKR,FX\0,%PKEY_FX_KeywordDetector_ModeEffectClsid%,,%FX_DISCOVER_EFFECTS_APO_CLSID% +HKR,FX\0,%PKEY_FX_KeywordDetector_EndpointEffectClsid%,,%KWS_FX_ENDPOINT_CLSID% + +; An EFX APO must support default mode to be loaded. This does not mean the keyword burst pin must support default mode, it +; may still run in some other mode like speech mode. This simply means that the KWS EFX APO supports default mode. +HKR,FX\0,%PKEY_EFX_KeywordDetector_ProcessingModes_Supported_For_Streaming%,%REG_MULTI_SZ%,%AUDIO_SIGNALPROCESSINGMODE_DEFAULT% + +[Audio_APO_Device.NT.Interfaces] +AddInterface = %KSCATEGORY_AUDIO%, %KSNAME_Microphone%, DeviceExtensions.I.Microphone +AddInterface = %KSCATEGORY_CAPTURE%, %KSNAME_Microphone%, DeviceExtensions.I.Microphone +AddInterface = %KSCATEGORY_REALTIME%, %KSNAME_Microphone%, DeviceExtensions.I.Microphone + + +;-------------- Service installation + +[Audio_Device.NT.Services] +AddService = SDCAVXu,,Audio_Service_Inst + +[Audio_Service_Inst] +DisplayName = %WdfExtensionDevice.DeviceDesc% +ServiceType = 1 ; SERVICE_KERNEL_DRIVER +StartType = 3 ; SERVICE_DEMAND_START +ErrorControl = 1 ; SERVICE_ERROR_NORMAL +ServiceBinary = %13%\SDCAVXu.sys + +[SourceDisksNames] +1 = %DiskId1%,,,"" + +[SourceDisksFiles] +SDCAVXu.sys = 1,, + +[Audio_Device.NT.Wdf] +KmdfService = SDCAVXu, Audio_wdfsect +[Audio_wdfsect] +KmdfLibraryVersion = $KMDFVERSION$ + +[Strings] + +ProviderName = "VS_Microsoft" + +; +;Localizable +; +StdMfg = "SDCA Virtual XU Audio Device" +DiskId1 = "SDCA Virtual XU Audio Driver Installation Disk" +WdfExtensionDevice.DeviceDesc = "SDCA Virtual XU Audio Driver" + +ExtendedFriendlyName = "SDCAV (with APO Extensions)" +PKEY_FX_Association = "{D04E05A6-594B-4FB6-A80D-01AF5EED7D1D},0" +PKEY_FX_KeywordDetector_EndpointEffectClsid = "{D04E05A6-594B-4fb6-A80D-01AF5EED7D1D},10" +PKEY_FX_KeywordDetector_ModeEffectClsid = "{D04E05A6-594B-4fb6-A80D-01AF5EED7D1D},9" +PKEY_EFX_KeywordDetector_ProcessingModes_Supported_For_Streaming = "{D3993A3F-99C2-4402-B5EC-A92A0367664B},10" + +FX_DISCOVER_EFFECTS_APO_CLSID = "{CABC2F7B-4AF5-47F8-A95D-3A6F23F53DD4}" + +KWS_FX_ENDPOINT_CLSID = "{9D89F614-F9D6-40DD-9F21-5E69FA3981ED}" + +KSNODETYPE_ANY = "{00000000-0000-0000-0000-000000000000}" + +KSNAME_Microphone="Microphone0" + +KSCATEGORY_AUDIO = "{6994AD04-93EF-11D0-A3CC-00A0C9223196}" +KSCATEGORY_REALTIME = "{EB115FFC-10C8-4964-831D-6DCB02E6F23F}" +KSCATEGORY_CAPTURE = "{65E8773D-8F56-11D0-A3B9-00A0C9223196}" + +REG_MULTI_SZ = 0x00010000 ; FLG_ADDREG_TYPE_MULTI_SZ + +AUDIO_SIGNALPROCESSINGMODE_DEFAULT = "{C18E2F7E-933D-4965-B7D1-1EEF228D2AF3}" + diff --git a/audio/Soundwire/Samples/SdcaVad/SdcaVXu/Trace.h b/audio/Soundwire/Samples/SdcaVad/SdcaVXu/Trace.h new file mode 100644 index 000000000..245248a7f --- /dev/null +++ b/audio/Soundwire/Samples/SdcaVad/SdcaVXu/Trace.h @@ -0,0 +1,34 @@ +/*++ + +Copyright (c) Microsoft Corporation + +Module Name: + +Trace.h + +--*/ + +#pragma once + +#include +#include // For TRACE_LEVEL definitions + +#define WPP_TOTAL_BUFFER_SIZE (PAGE_SIZE) +#define WPP_ERROR_PARTITION_SIZE (WPP_TOTAL_BUFFER_SIZE/4) + +// {47BE3522-EAE5-47F4-9EE0-BC4260C39937} +#define WPP_CONTROL_GUIDS \ +WPP_DEFINE_CONTROL_GUID(DrvLogger,(47BE3522,EAE5,47F4,9EE0,BC4260C39937), \ + WPP_DEFINE_BIT(FLAG_DEVICE_ALL) /* bit 0 = 0x00000001 */ \ + WPP_DEFINE_BIT(FLAG_FUNCTION) /* bit 1 = 0x00000002 */ \ + WPP_DEFINE_BIT(FLAG_INFO) /* bit 2 = 0x00000004 */ \ + WPP_DEFINE_BIT(FLAG_PNP) /* bit 3 = 0x00000008 */ \ + WPP_DEFINE_BIT(FLAG_POWER) /* bit 4 = 0x00000010 */ \ + WPP_DEFINE_BIT(FLAG_STREAM) /* bit 5 = 0x00000020 */ \ + WPP_DEFINE_BIT(FLAG_INIT) /* bit 6 = 0x00000040 */ \ + WPP_DEFINE_BIT(FLAG_DDI) /* bit 7 = 0x00000080 */ \ + WPP_DEFINE_BIT(FLAG_GENERIC) /* bit 8 = 0x00000100 */ \ + ) + +#include "trace_macros.h" + diff --git a/audio/Soundwire/Samples/SdcaVad/SdcaVXu/capture.cpp b/audio/Soundwire/Samples/SdcaVad/SdcaVXu/capture.cpp new file mode 100644 index 000000000..150336b11 --- /dev/null +++ b/audio/Soundwire/Samples/SdcaVad/SdcaVXu/capture.cpp @@ -0,0 +1,1059 @@ +/*++ + + THIS CODE AND INFORMATION IS PROVIDED "AS IS" WITHOUT WARRANTY OF ANY + KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE + IMPLIED WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A PARTICULAR + PURPOSE. + +Module Name: + + Capture.cpp + +Abstract: + + Plug and Play module. This file contains routines to handle pnp requests. + +Environment: + + Kernel mode + +--*/ + +#include "private.h" +#include +#include "stdunk.h" +#include +#include +#include + +#include "capture.h" + +#include "streamengine.h" + +#include "AudioFormats.h" + +#ifndef __INTELLISENSE__ +#include "capture.tmh" +#endif + +PAGED_CODE_SEG +NTSTATUS +SdcaXuC_EvtAcxPinSetDataFormat ( + _In_ ACXPIN Pin, + _In_ ACXDATAFORMAT DataFormat + ) +{ + PAGED_CODE(); + + UNREFERENCED_PARAMETER(Pin); + UNREFERENCED_PARAMETER(DataFormat); + + + return STATUS_NOT_SUPPORTED; +} + +#pragma code_seg() +VOID +SdcaXuC_EvtPinContextCleanup( + _In_ WDFOBJECT WdfPin + ) +/*++ + +Routine Description: + + In this callback, it cleans up pin context. + +Arguments: + + WdfDevice - WDF device object + +Return Value: + + NULL + +--*/ +{ + UNREFERENCED_PARAMETER(WdfPin); +} + +VOID SdcaXuC_EvtCircuitContextCleanup(_In_ WDFOBJECT Object) +{ + ACXCIRCUIT circuit = (ACXCIRCUIT)Object; + PSDCAXU_CAPTURE_CIRCUIT_CONTEXT cirCtx = GetCaptureCircuitContext(circuit); + + if (cirCtx->CircuitConfig) + { + ExFreePoolWithTag(cirCtx->CircuitConfig, DRIVER_TAG); + cirCtx->CircuitConfig = NULL; + } + + return; +} + +PAGED_CODE_SEG +VOID +SdcaXuC_EvtCircuitRequestPreprocess( + _In_ ACXOBJECT Object, + _In_ ACXCONTEXT DriverContext, + _In_ WDFREQUEST Request + ) +/*++ + +Routine Description: + + This function is an example of a preprocess routine. + +--*/ +{ + PAGED_CODE(); + + UNREFERENCED_PARAMETER(DriverContext); + + ASSERT(Object != NULL); + ASSERT(DriverContext); + ASSERT(Request); + + + // + // Just give the request back to ACX. + // + (VOID)AcxCircuitDispatchAcxRequest((ACXCIRCUIT)Object, Request); +} + +PAGED_CODE_SEG +_Use_decl_annotations_ +VOID +SdcaXuC_EvtStreamRequestPreprocess( + _In_ ACXOBJECT Object, + _In_ ACXCONTEXT DriverContext, + _In_ WDFREQUEST Request +) +/*++ + +Routine Description: + + This function is an example of a preprocess routine. + +--*/ +{ + PAGED_CODE(); + + UNREFERENCED_PARAMETER(DriverContext); + + ASSERT(Object != NULL); + ASSERT(DriverContext); + ASSERT(Request); + + + // + // Just give the request back to ACX. + // + (VOID)AcxStreamDispatchAcxRequest((ACXSTREAM)Object, Request); +} + +PAGED_CODE_SEG +NTSTATUS +SdcaXuC_SetPowerPolicy( + _In_ WDFDEVICE Device +) +{ + NTSTATUS status = STATUS_SUCCESS; + WDF_DEVICE_POWER_POLICY_IDLE_SETTINGS idleSettings; + //WDF_DEVICE_POWER_POLICY_WAKE_SETTINGS wakeSettings; + + PAGED_CODE(); + + // + // Init the idle policy structure. + // + //WDF_DEVICE_POWER_POLICY_IDLE_SETTINGS_INIT(&idleSettings, IdleCanWakeFromS0); + WDF_DEVICE_POWER_POLICY_IDLE_SETTINGS_INIT(&idleSettings, IdleCannotWakeFromS0); + idleSettings.IdleTimeout = IDLE_POWER_TIMEOUT; + idleSettings.IdleTimeoutType = SystemManagedIdleTimeoutWithHint; + + status = WdfDeviceAssignS0IdleSettings(Device, &idleSettings); + + return status; +} + +PAGED_CODE_SEG +NTSTATUS +SdcaXuC_EvtDevicePrepareHardware( + _In_ WDFDEVICE Device, + _In_ WDFCMRESLIST ResourceList, + _In_ WDFCMRESLIST ResourceListTranslated +) +/*++ + +Routine Description: + + In this callback, the driver does whatever is necessary to make the + hardware ready to use. + +Arguments: + + Device - handle to a device + +Return Value: + + NT status value + +--*/ +{ + NTSTATUS status = STATUS_SUCCESS; + + UNREFERENCED_PARAMETER(ResourceList); + UNREFERENCED_PARAMETER(ResourceListTranslated); + + PAGED_CODE(); + + PSDCAXU_CAPTURE_DEVICE_CONTEXT devCtx; + devCtx = GetCaptureDeviceContext(Device); + ASSERT(devCtx != NULL); + + if (!devCtx->FirstTimePrepareHardware) + { + // + // This is a rebalance. Validate the circuit resources and + // if needed, delete and re-create the circuit. + // The sample driver doens't use resources, thus the existing + // circuits are kept. + // + return STATUS_SUCCESS; + } + + // + // Set child's power policy. + // + RETURN_NTSTATUS_IF_FAILED(SdcaXuC_SetPowerPolicy(Device)); + + // + // Add circuit to child's list. + // + RETURN_NTSTATUS_IF_FAILED(AcxDeviceAddCircuit(Device, devCtx->Circuit)); + + // + // Keep track this is not the first time this callback was called. + // + devCtx->FirstTimePrepareHardware = FALSE; + + return status; +} + +PAGED_CODE_SEG +NTSTATUS +SdcaXuC_EvtDeviceReleaseHardware( + _In_ WDFDEVICE Device, + _In_ WDFCMRESLIST ResourceListTranslated +) +/*++ + +Routine Description: + + In this callback, the driver releases the h/w resources allocated in the + prepare h/w callback. + +Arguments: + + Device - handle to a device + +Return Value: + + NT status value + +--*/ +{ + NTSTATUS status = STATUS_SUCCESS; + + UNREFERENCED_PARAMETER(Device); + UNREFERENCED_PARAMETER(ResourceListTranslated); + + PAGED_CODE(); + + PSDCAXU_CAPTURE_DEVICE_CONTEXT devCtx; + devCtx = GetCaptureDeviceContext(Device); + ASSERT(devCtx != NULL); + UNREFERENCED_PARAMETER(devCtx); + + + return status; +} + +PAGED_CODE_SEG +VOID +SdcaXuC_EvtDeviceContextCleanup( + _In_ WDFOBJECT WdfDevice +) +/*++ + +Routine Description: + + In this callback, it cleans up capture device context. + +Arguments: + + WdfDevice - WDF device object + +Return Value: + + NULL + +--*/ +{ + PAGED_CODE(); + UNREFERENCED_PARAMETER(WdfDevice); +} + +PAGED_CODE_SEG +NTSTATUS +SdcaXuC_EvtDeviceSelfManagedIoInit( + _In_ WDFDEVICE Device +) +/*++ + +Routine Description: + + In this callback, the driver does one-time init of self-managed I/O data. + +Arguments: + + Device - handle to a device + +Return Value: + + NT status value + +--*/ +{ + PAGED_CODE(); + + PSDCAXU_CAPTURE_DEVICE_CONTEXT devCtx; + devCtx = GetCaptureDeviceContext(Device); + ASSERT(devCtx != NULL); + UNREFERENCED_PARAMETER(devCtx); + + return STATUS_SUCCESS; +} + +PAGED_CODE_SEG +NTSTATUS +SdcaXu_CreateCaptureDevice( + _In_ WDFDEVICE Device, + _Out_ WDFDEVICE* CaptureDevice +) +{ + NTSTATUS status = STATUS_SUCCESS; + WDFDEVICE captureDevice = NULL; + + PAGED_CODE(); + + auto exit = scope_exit([&status, &captureDevice]() { + if (!NT_SUCCESS(status)) + { + if (captureDevice != NULL) + { + WdfObjectDelete(captureDevice); + } + } + }); + + *CaptureDevice = NULL; + + // + // Create a child audio device for this circuit. + // + PWDFDEVICE_INIT devInit = NULL; + devInit = WdfPdoInitAllocate(Device); + RETURN_NTSTATUS_IF_TRUE(NULL == devInit, STATUS_MEMORY_NOT_ALLOCATED); + + auto devInit_free = scope_exit([&devInit, &status]() { + WdfDeviceInitFree(devInit); + }); + + // + // Provide DeviceID, HardwareIDs, CompatibleIDs and InstanceId + // + RETURN_NTSTATUS_IF_FAILED(WdfPdoInitAddHardwareID(devInit, &CaptureHardwareId)); + + RETURN_NTSTATUS_IF_FAILED(WdfPdoInitAssignDeviceID(devInit, &CaptureDeviceId)); + + RETURN_NTSTATUS_IF_FAILED(WdfPdoInitAddCompatibleID(devInit, &CaptureCompatibleId)); + + RETURN_NTSTATUS_IF_FAILED(WdfPdoInitAssignInstanceID(devInit, &CaptureInstanceId)); + + RETURN_NTSTATUS_IF_FAILED(WdfPdoInitAssignContainerID(devInit, &CaptureContainerId)); + + + // + // You can call WdfPdoInitAddDeviceText multiple times, adding device + // text for multiple locales. When the system displays the text, it + // chooses the text that matches the current locale, if available. + // Otherwise it will use the string for the default locale. + // The driver can specify the driver's default locale by calling + // WdfPdoInitSetDefaultLocale. + // + RETURN_NTSTATUS_IF_FAILED(WdfPdoInitAddDeviceText(devInit, + &CaptureDeviceDescription, + &CaptureDeviceLocation, + 0x409)); + + WdfPdoInitSetDefaultLocale(devInit, 0x409); + + // + // Allow ACX to add any pre-requirement it needs on this device. + // + ACX_DEVICEINIT_CONFIG acxDevInitCfg; + ACX_DEVICEINIT_CONFIG_INIT(&acxDevInitCfg); + acxDevInitCfg.Flags |= AcxDeviceInitConfigRawDevice; + RETURN_NTSTATUS_IF_FAILED(AcxDeviceInitInitialize(devInit, &acxDevInitCfg)); + + // + // Initialize the pnpPowerCallbacks structure. Callback events for PNP + // and Power are specified here. If you don't supply any callbacks, + // the Framework will take appropriate default actions based on whether + // DeviceInit is initialized to be an FDO, a PDO or a filter device + // object. + // + WDF_PNPPOWER_EVENT_CALLBACKS pnpPowerCallbacks; + WDF_PNPPOWER_EVENT_CALLBACKS_INIT(&pnpPowerCallbacks); + pnpPowerCallbacks.EvtDevicePrepareHardware = SdcaXuC_EvtDevicePrepareHardware; + pnpPowerCallbacks.EvtDeviceReleaseHardware = SdcaXuC_EvtDeviceReleaseHardware; + pnpPowerCallbacks.EvtDeviceSelfManagedIoInit = SdcaXuC_EvtDeviceSelfManagedIoInit; + WdfDeviceInitSetPnpPowerEventCallbacks(devInit, &pnpPowerCallbacks); + + // + // Specify a context for this capture device. + // + WDF_OBJECT_ATTRIBUTES attributes; + WDF_OBJECT_ATTRIBUTES_INIT_CONTEXT_TYPE(&attributes, SDCAXU_CAPTURE_DEVICE_CONTEXT); + attributes.EvtCleanupCallback = SdcaXuC_EvtDeviceContextCleanup; + attributes.ExecutionLevel = WdfExecutionLevelPassive; + RETURN_NTSTATUS_IF_FAILED(WdfDeviceCreate(&devInit, &attributes, &captureDevice)); + + // + // devInit attached to device, no need to free + // + devInit_free.release(); + + // + // Tell the framework to set the NoDisplayInUI in the DeviceCaps so + // that the device does not show up in Device Manager. + // + WDF_DEVICE_PNP_CAPABILITIES pnpCaps; + WDF_DEVICE_PNP_CAPABILITIES_INIT(&pnpCaps); + pnpCaps.NoDisplayInUI = WdfTrue; + WdfDeviceSetPnpCapabilities(captureDevice, &pnpCaps); + + // + // Init capture's device context. + // + PSDCAXU_CAPTURE_DEVICE_CONTEXT devCtx; + devCtx = GetCaptureDeviceContext(captureDevice); + ASSERT(devCtx != NULL); + UNREFERENCED_PARAMETER(devCtx); + + // + // Allow ACX to add any post-requirement it needs on this device. + // + ACX_DEVICE_CONFIG devCfg; + ACX_DEVICE_CONFIG_INIT(&devCfg); + RETURN_NTSTATUS_IF_FAILED(AcxDeviceInitialize(captureDevice, &devCfg)); + + // + // Set output value. + // + *CaptureDevice = captureDevice; + + return status; +} + +PAGED_CODE_SEG +NTSTATUS +SdcaXu_AddDynamicCapture( + _In_ WDFDEVICE Device, + _In_ PSDCAXU_ACX_CIRCUIT_CONFIG CircuitConfig +) +{ + NTSTATUS status = STATUS_SUCCESS; + + PAGED_CODE(); + + // + // Create a device to associated with this circuit. + // + WDFDEVICE captureDevice = NULL; + RETURN_NTSTATUS_IF_FAILED(SdcaXu_CreateCaptureDevice(Device, &captureDevice)); + auto deviceFree = scope_exit([&captureDevice]() { + WdfObjectDelete(captureDevice); + }); + + ASSERT(captureDevice); + PSDCAXU_CAPTURE_DEVICE_CONTEXT captureDevCtx; + captureDevCtx = GetCaptureDeviceContext(captureDevice); + ASSERT(captureDevCtx); + + // + // Create a capture circuit associated with this child device. + // + ACXCIRCUIT captureCircuit = NULL; + RETURN_NTSTATUS_IF_FAILED(SdcaXu_CreateCaptureCircuit(captureDevice, CircuitConfig, &captureCircuit)); + + captureDevCtx->Circuit = captureCircuit; + captureDevCtx->FirstTimePrepareHardware = TRUE; + + // + // Add circuit to device's dynamic circuit device list. + // + RETURN_NTSTATUS_IF_FAILED(AcxDeviceAddCircuitDevice(Device, captureDevice)); + + // Successfully created circuit for dynamic deivce + // Do not delete + deviceFree.release(); + + PSDCAXU_DEVICE_CONTEXT devCtx = GetSdcaXuDeviceContext(Device); + for (ULONG i = 0; i < ARRAYSIZE(devCtx->EndpointDevices); ++i) + { + if (devCtx->EndpointDevices[i].CircuitDevice == nullptr) + { + DrvLogInfo(g_SDCAVXuLog, FLAG_DDI, L"XU Device %p adding capture circuit device %p with component ID %!GUID! Uri %ls", + Device, captureDevice, &CircuitConfig->ComponentID, + CircuitConfig->ComponentUri.Buffer ? CircuitConfig->ComponentUri.Buffer : L""); + devCtx->EndpointDevices[i].CircuitDevice = captureDevice; + devCtx->EndpointDevices[i].CircuitId = CircuitConfig->ComponentID; + if (CircuitConfig->ComponentUri.Length > 0) + { + USHORT cbAlloc = CircuitConfig->ComponentUri.Length + sizeof(WCHAR); + // protect against overflow + if (CircuitConfig->ComponentUri.Length % 2 != 0 || + cbAlloc < CircuitConfig->ComponentUri.Length) + { + RETURN_NTSTATUS_IF_FAILED(STATUS_INVALID_PARAMETER); + } + + PWCHAR circuitUri = (PWCHAR)ExAllocatePool2(POOL_FLAG_NON_PAGED, cbAlloc, DRIVER_TAG); + if (!circuitUri) + { + RETURN_NTSTATUS_IF_FAILED(STATUS_INSUFFICIENT_RESOURCES); + } + + devCtx->EndpointDevices[i].CircuitUri.Buffer = circuitUri; + devCtx->EndpointDevices[i].CircuitUri.MaximumLength = cbAlloc; + devCtx->EndpointDevices[i].CircuitUri.Length = 0; + RtlCopyUnicodeString(&devCtx->EndpointDevices[i].CircuitUri, &CircuitConfig->ComponentUri); + } + break; + } + } + + return status; +} + +PAGED_CODE_SEG +NTSTATUS +SdcaXu_CreateCaptureCircuit( + _In_ WDFDEVICE Device, + _In_ PSDCAXU_ACX_CIRCUIT_CONFIG CircuitConfig, + _Out_ ACXCIRCUIT *Circuit +) +/*++ + +Routine Description: + + This routine builds the SdcaXu capture circuit. + +Return Value: + + NT status value + +--*/ +{ + PAGED_CODE(); + + NTSTATUS status = STATUS_SUCCESS; + + // + // Get a CircuitInit structure. + // + PACXCIRCUIT_INIT circuitInit = NULL; + circuitInit = AcxCircuitInitAllocate(Device); + RETURN_NTSTATUS_IF_TRUE(NULL == circuitInit, STATUS_MEMORY_NOT_ALLOCATED); + auto circuitInit_free = scope_exit([&circuitInit]() { + AcxCircuitInitFree(circuitInit); + }); + + // + // Init output value. + // + *Circuit = NULL; + + // + // Copy Circuit configuration + // + PSDCAXU_ACX_CIRCUIT_CONFIG pCircuitConfig = NULL; + pCircuitConfig = (PSDCAXU_ACX_CIRCUIT_CONFIG)ExAllocatePool2(POOL_FLAG_NON_PAGED, + CircuitConfig->cbSize, + DRIVER_TAG); + RETURN_NTSTATUS_IF_TRUE(NULL == pCircuitConfig, STATUS_MEMORY_NOT_ALLOCATED); + auto circuitConfig_free = scope_exit([&pCircuitConfig]() { + ExFreePoolWithTag(pCircuitConfig, DRIVER_TAG); + }); + + RtlCopyMemory(pCircuitConfig, CircuitConfig, CircuitConfig->cbSize); + + // Remap UNICODE_STRING.Buffer + // buffer for unicode string begins immediately after SdcaXuAcxCircuitConfig + RETURN_NTSTATUS_IF_TRUE_MSG(pCircuitConfig->cbSize < (sizeof(SDCAXU_ACX_CIRCUIT_CONFIG) + pCircuitConfig->CircuitName.MaximumLength), + STATUS_INVALID_PARAMETER, L"CircuitConfig->cbSize = %d Required = %d", + pCircuitConfig->cbSize, + (int)(sizeof(SDCAXU_ACX_CIRCUIT_CONFIG) + pCircuitConfig->CircuitName.MaximumLength)); + + pCircuitConfig->CircuitName.Buffer = (PWCH)(pCircuitConfig + 1); + + // + // Create a circuit. + // + + // + // Add circuit identifiers. + // + if (!IsEqualGUID(pCircuitConfig->ComponentID, GUID_NULL)) + { + AcxCircuitInitSetComponentId(circuitInit, &pCircuitConfig->ComponentID); + } + + RETURN_NTSTATUS_IF_FAILED(AcxCircuitInitAssignComponentUri(circuitInit, &pCircuitConfig->ComponentUri)); + + RETURN_NTSTATUS_IF_FAILED(AcxCircuitInitAssignName(circuitInit, &pCircuitConfig->CircuitName)); + + // + // Add circuit type. + // + AcxCircuitInitSetCircuitType(circuitInit, AcxCircuitTypeCapture); + + // + // Assign the circuit's pnp-power callbacks. + // + { + ACX_CIRCUIT_PNPPOWER_CALLBACKS powerCallbacks; + ACX_CIRCUIT_PNPPOWER_CALLBACKS_INIT(&powerCallbacks); + powerCallbacks.EvtAcxCircuitPowerUp = SdcaXuC_EvtCircuitPowerUp; + powerCallbacks.EvtAcxCircuitPowerDown = SdcaXuC_EvtCircuitPowerDown; + AcxCircuitInitSetAcxCircuitPnpPowerCallbacks(circuitInit, &powerCallbacks); + } + + // + // Set circuit-callbacks. + // + RETURN_NTSTATUS_IF_FAILED(AcxCircuitInitAssignAcxRequestPreprocessCallback( + circuitInit, + SdcaXuC_EvtCircuitRequestPreprocess, + (ACXCONTEXT)AcxRequestTypeAny, // dbg only + AcxRequestTypeAny, + NULL, + AcxItemIdNone)); + + RETURN_NTSTATUS_IF_FAILED(AcxCircuitInitAssignAcxCreateStreamCallback( + circuitInit, + SdcaXuC_EvtCircuitCreateStream)); + + /* + // + // Add properties, events and methods. + // + RETURN_NTSTATUS_IF_FAILED(AcxCircuitInitAssignProperties(circuitInit, + CircuitProperties, + CircuitPropertiesCount)); + */ + + // + // Disable default Stream Bridge handling in ACX + // Create stream handler will add Stream Bridge + // to support Object-bag forwarding + // + AcxCircuitInitDisableDefaultStreamBridgeHandling(circuitInit); + + // + // Create the circuit. + // + WDF_OBJECT_ATTRIBUTES attributes; + ACXCIRCUIT circuit; + WDF_OBJECT_ATTRIBUTES_INIT_CONTEXT_TYPE(&attributes, SDCAXU_CAPTURE_CIRCUIT_CONTEXT); + attributes.EvtCleanupCallback = SdcaXuC_EvtCircuitContextCleanup; + RETURN_NTSTATUS_IF_FAILED(AcxCircuitCreate(Device, &attributes, &circuitInit, &circuit)); + + // circuitInit is now associated with circuit and will be managed with + // circuit lifetime. + circuitInit_free.release(); + + SDCAXU_CAPTURE_CIRCUIT_CONTEXT* circuitCtx; + ASSERT(circuit != NULL); + circuitCtx = GetCaptureCircuitContext(circuit); + ASSERT(circuitCtx); + + circuitCtx->CircuitConfig = pCircuitConfig; + circuitConfig_free.release(); + + // + // Post circuit creation initialization. + // + + // + // Add two custom circuit elements. Note that driver doesn't need to + // perform this step if it doesn't want to expose any circuit elements. + // + + // + // Create 1st custom circuit-element. + // + ACX_ELEMENT_CONFIG elementCfg; + ACX_ELEMENT_CONFIG_INIT(&elementCfg); + WDF_OBJECT_ATTRIBUTES_INIT_CONTEXT_TYPE(&attributes, SDCAXU_ELEMENT_CONTEXT); + attributes.ParentObject = circuit; + + const int numElements = 2; + ACXELEMENT elements[numElements] = { 0 }; + RETURN_NTSTATUS_IF_FAILED(AcxElementCreate(circuit, &attributes, &elementCfg, &elements[0])); + + ASSERT(elements[0] != NULL); + SDCAXU_ELEMENT_CONTEXT* elementCtx; + elementCtx = GetSdcaXuElementContext(elements[0]); + ASSERT(elementCtx); + UNREFERENCED_PARAMETER(elementCtx); + + // + // Create 2nd custom circuit-element. + // + ACX_ELEMENT_CONFIG_INIT(&elementCfg); + WDF_OBJECT_ATTRIBUTES_INIT_CONTEXT_TYPE(&attributes, SDCAXU_ELEMENT_CONTEXT); + attributes.ParentObject = circuit; + + RETURN_NTSTATUS_IF_FAILED(AcxElementCreate(circuit, &attributes, &elementCfg, &elements[1])); + + ASSERT(elements[1] != NULL); + elementCtx = GetSdcaXuElementContext(elements[1]); + ASSERT(elementCtx); + UNREFERENCED_PARAMETER(elementCtx); + + // + // Add the circuit elements + // + RETURN_NTSTATUS_IF_FAILED(AcxCircuitAddElements(circuit, elements, SIZEOF_ARRAY(elements))); + + // + // Create capture pin. AcxCircuit creates the other pin by default. + // + ACX_PIN_CALLBACKS pinCallbacks; + ACX_PIN_CALLBACKS_INIT(&pinCallbacks); + pinCallbacks.EvtAcxPinSetDataFormat = SdcaXuC_EvtAcxPinSetDataFormat; + + ACX_PIN_CONFIG pinCfg; + ACX_PIN_CONFIG_INIT(&pinCfg); + pinCfg.Type = AcxPinTypeSource; + pinCfg.Communication = AcxPinCommunicationNone; + pinCfg.Category = &KSCATEGORY_AUDIO; + pinCfg.PinCallbacks = &pinCallbacks; + + WDF_OBJECT_ATTRIBUTES_INIT_CONTEXT_TYPE(&attributes, SDCAXU_PIN_CONTEXT); + attributes.EvtCleanupCallback = SdcaXuC_EvtPinContextCleanup; + attributes.ParentObject = circuit; + + ACXPIN pin; + RETURN_NTSTATUS_IF_FAILED(AcxPinCreate(circuit, &attributes, &pinCfg, &pin)); + + ASSERT(pin != NULL); + SDCAXU_PIN_CONTEXT* pinCtx; + pinCtx = GetSdcaXuPinContext(pin); + ASSERT(pinCtx); + + // When the downstream pin connects to the Class driver, we'll + // copy formats from the Class driver (instead of hardcoding + // formats here) + + // + // Add capture pin, using default pin id (0) + // + RETURN_NTSTATUS_IF_FAILED(AcxCircuitAddPins(circuit, &pin, 1)); + + /////////////////////////////////////////////////////////// + // + // Create bridge pin. AcxCircuit creates the other pin by default. + // + ACX_PIN_CALLBACKS_INIT(&pinCallbacks); + pinCallbacks.EvtAcxPinConnected = SdcaXu_EvtPinConnected; + + ACX_PIN_CONFIG_INIT(&pinCfg); + pinCfg.Type = AcxPinTypeSink; + pinCfg.Communication = AcxPinCommunicationNone; + pinCfg.Category = &KSCATEGORY_AUDIO; + pinCfg.PinCallbacks = &pinCallbacks; + + WDF_OBJECT_ATTRIBUTES_INIT_CONTEXT_TYPE(&attributes, SDCAXU_PIN_CONTEXT); + attributes.EvtCleanupCallback = SdcaXuC_EvtPinContextCleanup; + attributes.ParentObject = circuit; + + pin = NULL; + RETURN_NTSTATUS_IF_FAILED(AcxPinCreate(circuit, &attributes, &pinCfg, &pin)); + + ASSERT(pin != NULL); + pinCtx = GetSdcaXuPinContext(pin); + ASSERT(pinCtx); + + // + // Add brige pin, using default pin id (1) + // + RETURN_NTSTATUS_IF_FAILED(AcxCircuitAddPins(circuit, &pin, 1)); + + // + // Add a stream bridge to the bridge pin to propagate the stream obj-bags. + // + { + PCGUID inModes[] = + { + &NULL_GUID, // Match every mode. + }; + + WDF_OBJECT_ATTRIBUTES_INIT(&attributes); + attributes.ParentObject = pin; + + ACX_STREAM_BRIDGE_CONFIG streamBridgeConfig; + ACX_STREAM_BRIDGE_CONFIG_INIT(&streamBridgeConfig); + + streamBridgeConfig.Flags |= AcxStreamBridgeForwardInStreamVarArguments; + streamBridgeConfig.InModesCount = ARRAYSIZE(inModes); + streamBridgeConfig.InModes = inModes; + streamBridgeConfig.OutMode = &NULL_GUID; // Use the MODE associated the in-stream. + + ACXSTREAMBRIDGE streamBridge = NULL; + RETURN_NTSTATUS_IF_FAILED(AcxStreamBridgeCreate(circuit, &attributes, &streamBridgeConfig, &streamBridge)); + + RETURN_NTSTATUS_IF_FAILED(AcxPinAddStreamBridges(pin, &streamBridge, 1)); + } + + // + // Explicitly connect the circuit/elements. Note that driver doens't + // need to perform this step when circuit/elements are connected in the + // same order as they were added to the circuit. By default ACX connects + // the elements starting from the sink circuit pin and ending with the + // source circuit pin for both capture and capture devices. + // + // circuit.pin[default_sink] -> 1st element.pin[default_in] + // 1st element.pin[default_out] -> 2nd element.pin[default_in] + // 2nd element.pin[default_out] -> circuit.pin[default_source] + // + const int numConnections = numElements + 1; + ACX_CONNECTION connections[numConnections]; + ACX_CONNECTION_INIT(&connections[0], circuit, elements[0]); + ACX_CONNECTION_INIT(&connections[1], elements[0], elements[1]); + ACX_CONNECTION_INIT(&connections[2], elements[1], circuit); + + // + // Add the connections linking circuit to elements. + // + RETURN_NTSTATUS_IF_FAILED(AcxCircuitAddConnections(circuit, connections, SIZEOF_ARRAY(connections))); + + // + // Set output value. + // + *Circuit = circuit; + + // + // Done. + // + + return status; +} + +#pragma code_seg() +_Use_decl_annotations_ +NTSTATUS +SdcaXuC_EvtCircuitPowerUp ( + _In_ WDFDEVICE Device, + _In_ ACXCIRCUIT Circuit, + _In_ WDF_POWER_DEVICE_STATE PreviousState + ) +{ + // Do not page out. + + UNREFERENCED_PARAMETER(Device); + UNREFERENCED_PARAMETER(Circuit); + UNREFERENCED_PARAMETER(PreviousState); + + return STATUS_SUCCESS; +} + +PAGED_CODE_SEG +_Use_decl_annotations_ +NTSTATUS +SdcaXuC_EvtCircuitPowerDown ( + _In_ WDFDEVICE Device, + _In_ ACXCIRCUIT Circuit, + _In_ WDF_POWER_DEVICE_STATE TargetState + ) +{ + PAGED_CODE(); + + UNREFERENCED_PARAMETER(Device); + UNREFERENCED_PARAMETER(Circuit); + UNREFERENCED_PARAMETER(TargetState); + + return STATUS_SUCCESS; +} + +PAGED_CODE_SEG +NTSTATUS +SdcaXuC_EvtCircuitCreateStream( + _In_ WDFDEVICE Device, + _In_ ACXCIRCUIT Circuit, + _In_ ACXPIN Pin, + _In_ PACXSTREAM_INIT StreamInit, + _In_ ACXDATAFORMAT StreamFormat, + _In_ const GUID * SignalProcessingMode, + _In_ ACXOBJECTBAG VarArguments + ) +/*++ + +Routine Description: + + This routine create a stream for the specified circuit. + +Return Value: + + NT status value + +--*/ +{ + NTSTATUS status = STATUS_SUCCESS; + + PAGED_CODE(); + + UNREFERENCED_PARAMETER(Pin); + UNREFERENCED_PARAMETER(SignalProcessingMode); + UNREFERENCED_PARAMETER(VarArguments); + + ASSERT(IsEqualGUID(*SignalProcessingMode, AUDIO_SIGNALPROCESSINGMODE_RAW)); + + PSDCAXU_CAPTURE_DEVICE_CONTEXT devCtx; + devCtx = GetCaptureDeviceContext(Device); + ASSERT(devCtx != NULL); + + // + // Set circuit-callbacks. + // + RETURN_NTSTATUS_IF_FAILED(AcxStreamInitAssignAcxRequestPreprocessCallback( + StreamInit, + SdcaXuC_EvtStreamRequestPreprocess, + (ACXCONTEXT)AcxRequestTypeAny, // dbg only + AcxRequestTypeAny, + NULL, + AcxItemIdNone)); + + /* + // + // Add properties, events and methods. + // + RETURN_NTSTATUS_IF_FAILED(AcxStreamInitAssignProperties(StreamInit, + StreamProperties, + StreamPropertiesCount)); + */ + + // + // Init streaming callbacks. + // + ACX_STREAM_CALLBACKS streamCallbacks; + ACX_STREAM_CALLBACKS_INIT(&streamCallbacks); + streamCallbacks.EvtAcxStreamPrepareHardware = SdcaXu_EvtStreamPrepareHardware; + streamCallbacks.EvtAcxStreamReleaseHardware = SdcaXu_EvtStreamReleaseHardware; + streamCallbacks.EvtAcxStreamRun = SdcaXu_EvtStreamRun; + streamCallbacks.EvtAcxStreamPause = SdcaXu_EvtStreamPause; + streamCallbacks.EvtAcxStreamAssignDrmContentId = SdcaXu_EvtStreamAssignDrmContentId; + + RETURN_NTSTATUS_IF_FAILED(AcxStreamInitAssignAcxStreamCallbacks(StreamInit, &streamCallbacks)); + + // + // Create the stream. + // + WDF_OBJECT_ATTRIBUTES attributes; + WDF_OBJECT_ATTRIBUTES_INIT_CONTEXT_TYPE(&attributes, SDCAXU_STREAM_CONTEXT); + attributes.EvtDestroyCallback = SdcaXu_EvtStreamDestroy; + ACXSTREAM stream; + RETURN_NTSTATUS_IF_FAILED(AcxStreamCreate(Device, Circuit, &attributes, &StreamInit, &stream)); + + CCaptureStreamEngine *streamEngine = NULL; + streamEngine = new(POOL_FLAG_NON_PAGED, DRIVER_TAG) CCaptureStreamEngine(stream, StreamFormat); + RETURN_NTSTATUS_IF_TRUE(NULL == streamEngine, STATUS_MEMORY_NOT_ALLOCATED); + auto stream_scope = scope_exit([&streamEngine]() { + delete streamEngine; + }); + + SDCAXU_STREAM_CONTEXT *streamCtx; + streamCtx = GetSdcaXuStreamContext(stream); + ASSERT(streamCtx); + streamCtx->StreamEngine = (PVOID)streamEngine; + stream_scope.release(); + + // + // Post stream creation initialization. + // + + ACXELEMENT elements[2] = {0}; + ACX_ELEMENT_CONFIG elementCfg; + // + // Create 1st custom stream-elements. + // + ACX_ELEMENT_CONFIG_INIT(&elementCfg); + WDF_OBJECT_ATTRIBUTES_INIT_CONTEXT_TYPE(&attributes, SDCAXU_ELEMENT_CONTEXT); + attributes.ParentObject = stream; + + RETURN_NTSTATUS_IF_FAILED(AcxElementCreate(stream, &attributes, &elementCfg, &elements[0])); + + ASSERT(elements[0] != NULL); + SDCAXU_ELEMENT_CONTEXT *elementCtx; + elementCtx = GetSdcaXuElementContext(elements[0]); + ASSERT(elementCtx); + UNREFERENCED_PARAMETER(elementCtx); + + // + // Create 2nd custom stream-elements. + // + ACX_ELEMENT_CONFIG_INIT(&elementCfg); + WDF_OBJECT_ATTRIBUTES_INIT_CONTEXT_TYPE(&attributes, SDCAXU_ELEMENT_CONTEXT); + attributes.ParentObject = stream; + + RETURN_NTSTATUS_IF_FAILED(AcxElementCreate(stream, &attributes, &elementCfg, &elements[1])); + + ASSERT(elements[1] != NULL); + elementCtx = GetSdcaXuElementContext(elements[1]); + ASSERT(elementCtx); + UNREFERENCED_PARAMETER(elementCtx); + + // + // Add stream elements + // + RETURN_NTSTATUS_IF_FAILED(AcxStreamAddElements(stream, elements, SIZEOF_ARRAY(elements))); + + // + // Done. + // + return status; +} + +PAGED_CODE_SEG +NTSTATUS +SdcaXu_AddCaptures( + _In_ WDFDEVICE Device, + _In_ PSDCAXU_ACX_CIRCUIT_CONFIG CircuitConfig +) +{ + NTSTATUS status = STATUS_SUCCESS; + + PAGED_CODE(); + + // + // Add dynamic capture circuit using raw PDO + // + status = SdcaXu_AddDynamicCapture(Device, CircuitConfig); + + return status; +} + + diff --git a/audio/Soundwire/Samples/SdcaVad/SdcaVXu/capture.h b/audio/Soundwire/Samples/SdcaVad/SdcaVXu/capture.h new file mode 100644 index 000000000..16f3218de --- /dev/null +++ b/audio/Soundwire/Samples/SdcaVad/SdcaVXu/capture.h @@ -0,0 +1,84 @@ +/*++ + +Copyright (c) Microsoft Corporation. All rights reserved. + + THIS CODE AND INFORMATION IS PROVIDED "AS IS" WITHOUT WARRANTY OF ANY + KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE + IMPLIED WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A PARTICULAR + PURPOSE. + +Module Name: + + capture.h + +Abstract: + + Contains structure definitions and function prototypes private to + the driver. + +Environment: + + Kernel mode + +--*/ + +#pragma once + +// +// Circuit's settings for raw PDO. +// +DECLARE_CONST_UNICODE_STRING(CaptureDeviceId, L"SDCAVad\\ExtensionMicrophone"); +DECLARE_CONST_UNICODE_STRING(CaptureHardwareId, L"SDCAVad\\ExtensionMicrophone"); +DECLARE_CONST_UNICODE_STRING(CaptureInstanceId, L"00"); +DECLARE_CONST_UNICODE_STRING(CaptureCompatibleId, SDCAVAD_COMPATIBLE_ID); +DECLARE_CONST_UNICODE_STRING(CaptureContainerId, SDCAVAD_CONTAINER_ID); +DECLARE_CONST_UNICODE_STRING(CaptureDeviceDescription, L"SDCAVad Microphone(Ext)"); +DECLARE_CONST_UNICODE_STRING(CaptureDeviceLocation, L"SDCAVad Microphone"); + +PAGED_CODE_SEG +NTSTATUS +SdcaXuC_SetPowerPolicy( + _In_ WDFDEVICE Device +); + +PAGED_CODE_SEG +NTSTATUS +SdcaXu_CreateCaptureDevice( + _In_ WDFDEVICE Device, + _Out_ WDFDEVICE *CaptureDevice +); + +PAGED_CODE_SEG +NTSTATUS +SdcaXu_AddDynamicCapture( + _In_ WDFDEVICE Device +); + +PAGED_CODE_SEG +NTSTATUS +SdcaXu_CreateCaptureCircuit( + _In_ WDFDEVICE Device, + _In_ PSDCAXU_ACX_CIRCUIT_CONFIG CircuitConfig, + _Out_ ACXCIRCUIT *Circuit +); + +// Capture Device callbacks. + +EVT_WDF_DEVICE_PREPARE_HARDWARE SdcaXuC_EvtDevicePrepareHardware; +EVT_WDF_DEVICE_RELEASE_HARDWARE SdcaXuC_EvtDeviceReleaseHardware; +EVT_WDF_DEVICE_SELF_MANAGED_IO_INIT SdcaXuC_EvtDeviceSelfManagedIoInit; +EVT_WDF_DEVICE_CONTEXT_CLEANUP SdcaXuC_EvtDeviceContextCleanup; + +// Capture callbacks. + +EVT_WDF_OBJECT_CONTEXT_CLEANUP SdcaXuC_EvtCircuitContextCleanup; +EVT_ACX_OBJECT_PREPROCESS_REQUEST SdcaXuC_EvtCircuitRequestPreprocess; +EVT_ACX_CIRCUIT_CREATE_STREAM SdcaXuC_EvtCircuitCreateStream; +EVT_ACX_CIRCUIT_POWER_UP SdcaXuC_EvtCircuitPowerUp; +EVT_ACX_CIRCUIT_POWER_DOWN SdcaXuC_EvtCircuitPowerDown; +EVT_ACX_PIN_SET_DATAFORMAT SdcaXuC_EvtAcxPinSetDataFormat; +EVT_WDF_DEVICE_CONTEXT_CLEANUP SdcaXuC_EvtPinContextCleanup; +EVT_ACX_OBJECT_PREPROCESS_REQUEST SdcaXuC_EvtStreamRequestPreprocess; + +#pragma code_seg() + diff --git a/audio/Soundwire/Samples/SdcaVad/SdcaVXu/device.cpp b/audio/Soundwire/Samples/SdcaVad/SdcaVXu/device.cpp new file mode 100644 index 000000000..766e6b8c5 --- /dev/null +++ b/audio/Soundwire/Samples/SdcaVad/SdcaVXu/device.cpp @@ -0,0 +1,1426 @@ +/*++ + + THIS CODE AND INFORMATION IS PROVIDED "AS IS" WITHOUT WARRANTY OF ANY + KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE + IMPLIED WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A PARTICULAR + PURPOSE. + +Module Name: + + Device.cpp + +Abstract: + + Plug and Play module. This file contains routines to handle pnp requests. + +Environment: + + Kernel mode + +--*/ + +#include "private.h" +#include +#include "stdunk.h" +#include +#include +#include + +#include "streamengine.h" +#include "device.h" +#include "CircuitDevice.h" +#include "ModuleCircuit.h" + +#include "AudioFormats.h" + +#ifndef __INTELLISENSE__ +#include "device.tmh" +#endif + +UNICODE_STRING g_RegistryPath = {0}; // This is used to store the registry settings path for the driver + +__drv_requiresIRQL(PASSIVE_LEVEL) +PAGED_CODE_SEG +NTSTATUS +CopyRegistrySettingsPath( + _In_ PUNICODE_STRING RegistryPath +) +/*++ + +Routine Description: + +Copies the following registry path to a global variable. + +\REGISTRY\MACHINE\SYSTEM\ControlSetxxx\Services\\Parameters + +Arguments: + +RegistryPath - Registry path passed to DriverEntry + +Returns: + +NTSTATUS - SUCCESS if able to configure the framework + +--*/ + +{ + PAGED_CODE(); + + // Initializing the unicode string, so that if it is not allocated it will not be deallocated too. + RtlInitUnicodeString(&g_RegistryPath, NULL); + + g_RegistryPath.MaximumLength = RegistryPath->Length + sizeof(WCHAR); + + g_RegistryPath.Buffer = (PWCH)ExAllocatePool2(POOL_FLAG_PAGED, g_RegistryPath.MaximumLength, DRIVER_TAG); + + if (g_RegistryPath.Buffer == NULL) + { + return STATUS_INSUFFICIENT_RESOURCES; + } + + // ExAllocatePool2 zeros memory. + + RtlAppendUnicodeToString(&g_RegistryPath, RegistryPath->Buffer); + + return STATUS_SUCCESS; +} + +PAGED_CODE_SEG +NTSTATUS SdcaXu_SetHwConfig +( + _In_ PVOID Context, + _In_ SDCAXU_HW_CONFIG_TYPE HwConfigType, + _In_opt_ PVOID HwConfigData, + _In_ ULONG HwConfigDataSize +) +{ + PAGED_CODE(); + NTSTATUS status = STATUS_SUCCESS; + + DrvLogEnter(g_SDCAVXuLog); + + PSDCAXU_DEVICE_CONTEXT devCtx; + devCtx = GetSdcaXuDeviceContext((WDFDEVICE)Context); + + if (HwConfigType == SdcaXuHwConfigTypeAcpiBlob && + NULL != HwConfigData && + sizeof(devCtx->SDCADeviceData.HwData) <= HwConfigDataSize) + { + RtlCopyMemory(&devCtx->SDCADeviceData.HwData, HwConfigData, sizeof(devCtx->SDCADeviceData.HwData)); + } + else if (HwConfigType == SdcaXuHwConfigTypeAcpiBlob && + NULL != HwConfigData && + sizeof(SdcaXuAcpiBlob) <= HwConfigDataSize) + { + devCtx->SDCADeviceData.NumEndpoints = ((PSdcaXuAcpiBlob)HwConfigData)->NumEndpoints; + } + + RETURN_NTSTATUS_IF_FAILED(SdcaXu_SetXUEntities((WDFDEVICE)Context)); + + RETURN_NTSTATUS_IF_FAILED(SdcaXu_RegisterForInterrupts((WDFDEVICE)Context)); + + RETURN_NTSTATUS_IF_FAILED(SdcaXu_SetJackOverride((WDFDEVICE)Context)); + + return status; +} + +PAGED_CODE_SEG +NTSTATUS SdcaXu_SetXUEntities(_In_ WDFDEVICE Device) +{ + PAGED_CODE(); + NTSTATUS status = STATUS_SUCCESS; + + DrvLogEnter(g_SDCAVXuLog); + + PSDCAXU_DEVICE_CONTEXT devCtx; + devCtx = GetSdcaXuDeviceContext(Device); + + if (devCtx->SDCADeviceData.bSDCAInterface) + { + ULONG xuEntities[ARRAYSIZE(devCtx->SDCADeviceData.HwData.AvailableXUEntities)] = { 0 }; + ULONG xuCount = 0; + + // A real driver would select the XU Entites to turn on + //for (ULONG i = 0; i < ARRAYSIZE(devCtx->SDCADeviceData.HwData.AvailableXUEntities); ++i) + //{ + // if (devCtx->SDCADeviceData.HwData.AvailableXUEntities[i] != 0) + // { + // xuEntities[xuCount] = devCtx->SDCADeviceData.HwData.AvailableXUEntities[i]; + // ++xuCount; + // } + //} + + RETURN_NTSTATUS_IF_FAILED(devCtx->SDCADeviceData.SDCAInterface.EvtSetXUEntities(devCtx->SDCADeviceData.SDCAContext, xuCount, xuEntities)); + DrvLogInfo(g_SDCAVXuLog, FLAG_INIT, "SdcaXu_SetXUEntities - Configured %d XU Entities", xuCount); + } + else + { + DrvLogInfo(g_SDCAVXuLog, FLAG_INIT, "SdcaXu_SetXUEntities - No SDCA Interface available"); + } + + return status; +} + +PAGED_CODE_SEG +NTSTATUS SdcaXu_RegisterForInterrupts(_In_ WDFDEVICE Device) +{ + PAGED_CODE(); + NTSTATUS status = STATUS_SUCCESS; + + DrvLogEnter(g_SDCAVXuLog); + + PSDCAXU_DEVICE_CONTEXT devCtx; + devCtx = GetSdcaXuDeviceContext(Device); + + if (devCtx->SDCADeviceData.bSDCAInterface) + { + + SDCAXU_INTERRUPT_INFO interruptInfo{ 0 }; + + interruptInfo.Size = sizeof(SDCAXU_INTERRUPT_INFO); + SDCA_INTERRUPT_DEFINE_MASK(interruptInfo.SDCAInterruptMask, 0); + //SDCA_INTERRUPT_DEFINE_MASK(interruptInfo.SCPInterruptMask, 1, 2); + //SDCA_INTERRUPT_DEFINE_MASK(interruptInfo.DataPortInterrupts[0], 1, 2); + //SDCA_INTERRUPT_DEFINE_MASK(interruptInfo.DataPortInterrupts[1], 1, 5); + + RETURN_NTSTATUS_IF_FAILED(devCtx->SDCADeviceData.SDCAInterface.EvtRegisterForInterrupts( + devCtx->SDCADeviceData.SDCAContext, + &interruptInfo)); + + DrvLogInfo(g_SDCAVXuLog, FLAG_INIT, "SdcaXu_RegisterForInterrupts - Registered for Interrupts"); + } + else + { + RETURN_NTSTATUS(STATUS_NOINTERFACE); + } + + return status; +} + +PAGED_CODE_SEG +NTSTATUS SdcaXu_SetJackOverride(_In_ WDFDEVICE Device) +{ + PAGED_CODE(); + NTSTATUS status = STATUS_SUCCESS; + + DrvLogEnter(g_SDCAVXuLog); + + PSDCAXU_DEVICE_CONTEXT devCtx; + devCtx = GetSdcaXuDeviceContext(Device); + + if (devCtx->SDCADeviceData.bSDCAInterface) + { + //RETURN_NTSTATUS_IF_FAILED(devCtx->SDCADeviceData.SDCAInterface.EvtSetJackOverride( + // devCtx->SDCADeviceData.SDCAContext, + // TRUE)); + + DrvLogInfo(g_SDCAVXuLog, FLAG_INIT, "SdcaXu_SetJackOverride - Skipping enabling Jack Override"); + } + else + { + RETURN_NTSTATUS(STATUS_NOINTERFACE); + } + + return status; +} + +PAGED_CODE_SEG +NTSTATUS SdcaXu_SetEndpointConfig +( + _In_ PVOID Context, + _In_ SDCAXU_ENDPOINT_CONFIG_TYPE EndpointConfigType, + _In_opt_ PVOID EndpointConfigData, + _In_ ULONG EndpointConfigDataSize +) +{ + PAGED_CODE(); + + NTSTATUS status = STATUS_SUCCESS; + + DrvLogEnter(g_SDCAVXuLog); + + RETURN_NTSTATUS_IF_TRUE(NULL == EndpointConfigData, STATUS_INVALID_PARAMETER); + + WDFDEVICE Device = (WDFDEVICE)Context; + + switch (EndpointConfigType) + { + case SdcaXuEndpointConfigTypeAcxCircuitConfig: + { + RETURN_NTSTATUS_IF_TRUE_MSG((sizeof(SDCAXU_ACX_CIRCUIT_CONFIG) > EndpointConfigDataSize || + sizeof(SDCAXU_ACX_CIRCUIT_CONFIG) > ((PSDCAXU_ACX_CIRCUIT_CONFIG)EndpointConfigData)->cbSize), + STATUS_INVALID_PARAMETER_1, L"%d %d", EndpointConfigDataSize, ((PSDCAXU_ACX_CIRCUIT_CONFIG)EndpointConfigData)->cbSize); + + PSDCAXU_ACX_CIRCUIT_CONFIG circuitConfig = (PSDCAXU_ACX_CIRCUIT_CONFIG)EndpointConfigData; + // + // Add Xu circuit. + // + if (AcxCircuitTypeRender == circuitConfig->CircuitType) + { + RETURN_NTSTATUS_IF_FAILED_MSG(SdcaXu_AddRenders(Device, circuitConfig), L"Device %p", Device); + } + else if (AcxCircuitTypeCapture == circuitConfig->CircuitType) + { + RETURN_NTSTATUS_IF_FAILED_MSG(SdcaXu_AddCaptures(Device, circuitConfig), L"Device %p", Device); + } + } + break; + + default: + RETURN_NTSTATUS_MSG(STATUS_INVALID_PARAMETER_2, L"%d", EndpointConfigType); + } + + return status; +} + +PAGED_CODE_SEG +NTSTATUS SdcaXu_RemoveEndpointConfig +( + _In_ PVOID Context, + _In_ SDCAXU_ENDPOINT_CONFIG_TYPE EndpointConfigType, + _In_opt_ PVOID EndpointConfigData, + _In_ ULONG EndpointConfigDataSize +) +{ + PAGED_CODE(); + + NTSTATUS status = STATUS_SUCCESS; + + DrvLogEnter(g_SDCAVXuLog); + + RETURN_NTSTATUS_IF_TRUE(NULL == EndpointConfigData, STATUS_INVALID_PARAMETER); + + WDFDEVICE Device = (WDFDEVICE)Context; + + switch (EndpointConfigType) + { + case SdcaXuEndpointConfigTypeAcxCircuitConfig: + { + RETURN_NTSTATUS_IF_TRUE_MSG((sizeof(SDCAXU_ACX_CIRCUIT_CONFIG) > EndpointConfigDataSize || + sizeof(SDCAXU_ACX_CIRCUIT_CONFIG) > ((PSDCAXU_ACX_CIRCUIT_CONFIG)EndpointConfigData)->cbSize), + STATUS_INVALID_PARAMETER_1, L"%d %d", EndpointConfigDataSize, ((PSDCAXU_ACX_CIRCUIT_CONFIG)EndpointConfigData)->cbSize); + + PSDCAXU_ACX_CIRCUIT_CONFIG circuitConfig = (PSDCAXU_ACX_CIRCUIT_CONFIG)EndpointConfigData; + + PSDCAXU_DEVICE_CONTEXT devCtx = GetSdcaXuDeviceContext(Device); + for (ULONG i = 0; i < ARRAYSIZE(devCtx->EndpointDevices); ++i) + { + if (circuitConfig->ComponentUri.Length > 0) + { + if (devCtx->EndpointDevices[i].CircuitUri.Buffer && + RtlEqualUnicodeString(&circuitConfig->ComponentUri, &devCtx->EndpointDevices[i].CircuitUri, TRUE /* case insensitive */)) + { + DrvLogInfo(g_SDCAVXuLog, FLAG_DDI, L"Xu Device %p removing circuit device %p with component URI %ls", + Device, devCtx->EndpointDevices[i].CircuitDevice, circuitConfig->ComponentUri.Buffer); + AcxDeviceRemoveCircuitDevice(Device, devCtx->EndpointDevices[i].CircuitDevice); + ExFreePool(devCtx->EndpointDevices[i].CircuitUri.Buffer); + RtlZeroMemory(&devCtx->EndpointDevices[i], sizeof(ENDPOINT_DEVICE_PAIR)); + break; + } + } + else if (IsEqualGUID(devCtx->EndpointDevices[i].CircuitId, circuitConfig->ComponentID)) + { + DrvLogInfo(g_SDCAVXuLog, FLAG_DDI, L"Xu Device %p removing circuit device %p with component ID %!GUID!", + Device, devCtx->EndpointDevices[i].CircuitDevice, &circuitConfig->ComponentID); + AcxDeviceRemoveCircuitDevice(Device, devCtx->EndpointDevices[i].CircuitDevice); + if (devCtx->EndpointDevices[i].CircuitUri.Buffer) + { + ExFreePool(devCtx->EndpointDevices[i].CircuitUri.Buffer); + } + RtlZeroMemory(&devCtx->EndpointDevices[i], sizeof(ENDPOINT_DEVICE_PAIR)); + break; + } + } + } + break; + + default: + RETURN_NTSTATUS_MSG(STATUS_INVALID_PARAMETER_2, L"%d", EndpointConfigType); + } + + return status; +} + +PAGED_CODE_SEG +NTSTATUS SdcaXu_InterruptHandler +( + _In_ PVOID Context, + _Inout_ PSDCAXU_INTERRUPT_INFO Interrupt +) +{ + PAGED_CODE(); + NTSTATUS status = STATUS_SUCCESS; + + WDFDEVICE Device = (WDFDEVICE)Context; + + DrvLogInfo(g_SDCAVXuLog, FLAG_INFO, + L"Device %p Handling Interrupt SCP %08x " + L"DP %02x %02x %02x %02x %02x %02x %02x %02x %02x %02x %02x %02x %02x %02x %02x " + L"SDCA %08x", + Device, + Interrupt->SCPInterruptMask, + Interrupt->DataPortInterrupts[0], + Interrupt->DataPortInterrupts[1], + Interrupt->DataPortInterrupts[2], + Interrupt->DataPortInterrupts[3], + Interrupt->DataPortInterrupts[4], + Interrupt->DataPortInterrupts[5], + Interrupt->DataPortInterrupts[6], + Interrupt->DataPortInterrupts[7], + Interrupt->DataPortInterrupts[8], + Interrupt->DataPortInterrupts[9], + Interrupt->DataPortInterrupts[10], + Interrupt->DataPortInterrupts[11], + Interrupt->DataPortInterrupts[12], + Interrupt->DataPortInterrupts[13], + Interrupt->DataPortInterrupts[14], + Interrupt->SDCAInterruptMask); + return status; +} + +PAGED_CODE_SEG +NTSTATUS SdcaXuPowerStateChangeHandlerPre(_In_ PVOID Context, _In_ ULONG PDE_EntityId, _In_ SDCAXU_POWER_STATE OldState, _In_ SDCAXU_POWER_STATE NewState) +{ + PAGED_CODE(); + NTSTATUS status = STATUS_SUCCESS; + + WDFDEVICE Device = (WDFDEVICE)Context; + + DrvLogInfo(g_SDCAVXuLog, FLAG_INFO, + L"Device %p Handling Power State Change Pre for Entity 0x%x, Current State %d, New State %d", + Device, + PDE_EntityId, + OldState, + NewState); + + return status; +} + +PAGED_CODE_SEG +NTSTATUS SdcaXuPowerStateChangeHandlerPost(_In_ PVOID Context, _In_ ULONG PDE_EntityId, _In_ SDCAXU_POWER_STATE OldState, _In_ SDCAXU_POWER_STATE NewState) +{ + PAGED_CODE(); + NTSTATUS status = STATUS_SUCCESS; + + WDFDEVICE Device = (WDFDEVICE)Context; + + DrvLogInfo(g_SDCAVXuLog, FLAG_INFO, + L"Device %p Handling Power State Change Post for Entity 0x%x, Previous State %d, New State %d", + Device, + PDE_EntityId, + OldState, + NewState); + + return status; +} + +PAGED_CODE_SEG +NTSTATUS SdcaXuJackStateChangeHandler(_In_ PVOID Context, _In_ ULONG GroupEntityId, _In_ ULONG DetectedMode, _In_ SDCAXU_JACK_EVENT JackEvent) +{ + PAGED_CODE(); + NTSTATUS status = STATUS_SUCCESS; + + WDFDEVICE Device = (WDFDEVICE)Context; + + PSDCAXU_DEVICE_CONTEXT devCtx = GetSdcaXuDeviceContext(Device); + + DrvLogInfo(g_SDCAVXuLog, FLAG_INFO, + L"Device %p Handling Jack State Change for Entity 0x%x, Detected Mode %d, Event %d, will use %d as Selected Mode", + Device, + GroupEntityId, + DetectedMode, + JackEvent, + DetectedMode); + + // A real XU driver would probably do more with this. + status = devCtx->SDCADeviceData.SDCAInterface.EvtSetJackSelectedMode( + devCtx->SDCADeviceData.SDCAContext, + GroupEntityId, + DetectedMode); + + return status; +} + +PAGED_CODE_SEG +NTSTATUS SdcaXuFunctionHasBeenResetHandler(_In_ PVOID Context) +{ + PAGED_CODE(); + NTSTATUS status = STATUS_SUCCESS; + + WDFDEVICE Device = (WDFDEVICE)Context; + + DrvLogInfo(g_SDCAVXuLog, FLAG_INFO, L"Device %p Handling Function_Has_Been_Reset", Device); + + return status; +} + +PAGED_CODE_SEG +NTSTATUS SdcaXuFunctionNeedsInitializationHandler(_In_ PVOID Context) +{ + PAGED_CODE(); + NTSTATUS status = STATUS_SUCCESS; + + WDFDEVICE Device = (WDFDEVICE)Context; + + DrvLogInfo(g_SDCAVXuLog, FLAG_INFO, L"Device %p Handling Function_Needs_Initialization", Device); + + return status; +} + +PAGED_CODE_SEG +NTSTATUS SdcaXuFunctionFaultHandler(_In_ PVOID Context) +{ + PAGED_CODE(); + NTSTATUS status = STATUS_SUCCESS; + + WDFDEVICE Device = (WDFDEVICE)Context; + + DrvLogInfo(g_SDCAVXuLog, FLAG_INFO, L"Device %p Handling Function_Fault", Device); + + return status; +} + +PAGED_CODE_SEG +NTSTATUS SdcaXuUMPSequenceFaultHandler(_In_ PVOID Context) +{ + PAGED_CODE(); + NTSTATUS status = STATUS_SUCCESS; + + WDFDEVICE Device = (WDFDEVICE)Context; + + DrvLogInfo(g_SDCAVXuLog, FLAG_INFO, L"Device %p Handling Function_UMP_Sequence_Fault", Device); + + return status; +} + +PAGED_CODE_SEG +NTSTATUS SdcaXuStreamingStoppedAbnormallyHandler(_In_ PVOID Context) +{ + PAGED_CODE(); + NTSTATUS status = STATUS_SUCCESS; + + WDFDEVICE Device = (WDFDEVICE)Context; + + DrvLogInfo(g_SDCAVXuLog, FLAG_INFO, L"Device %p Handling Streaming_Stopped_Abnormally", Device); + + return status; +} + +PAGED_CODE_SEG +NTSTATUS SdcaXuCommitGroupHandler(_In_ PVOID Context, _In_ PSDCAXU_NOTIFICATION_COMMIT_GROUP CommitGroup) +{ + PAGED_CODE(); + NTSTATUS status = STATUS_SUCCESS; + + WDFDEVICE Device = (WDFDEVICE)Context; + + DrvLogInfo(g_SDCAVXuLog, FLAG_INFO, + L"Device %p Handling Commit Group notification with Commit Group %#x", + Device, + CommitGroup->CommitGroupHandle); + + return status; +} + +PAGED_CODE_SEG +NTSTATUS SdcaXuPostureHandler(_In_ PVOID Context, _In_ PSDCAXU_NOTIFICATION_POSTURE Posture) +{ + PAGED_CODE(); + NTSTATUS status = STATUS_SUCCESS; + + WDFDEVICE Device = (WDFDEVICE)Context; + + DrvLogInfo(g_SDCAVXuLog, FLAG_INFO, + L"Device %p Handling Posture notification with Posture %d", + Device, + Posture->Posture); + + return status; +} + +PAGED_CODE_SEG +NTSTATUS SdcaXuFdlBeginHandler(_In_ PVOID Context, _In_ PSDCAXU_NOTIFICATION_FDL_BEGIN FdlBegin) +{ + PAGED_CODE(); + NTSTATUS status = STATUS_SUCCESS; + + WDFDEVICE Device = (WDFDEVICE)Context; + + DrvLogInfo(g_SDCAVXuLog, FLAG_INFO, + L"Device %p Handling FDL Begin notification for entity %#x", + Device, + FdlBegin->FdlEntityId); + + return status; +} + +PAGED_CODE_SEG +NTSTATUS SdcaXuFdlEndHandler(_In_ PVOID Context, _In_ PSDCAXU_NOTIFICATION_FDL_END FdlEnd) +{ + PAGED_CODE(); + NTSTATUS status = STATUS_SUCCESS; + + WDFDEVICE Device = (WDFDEVICE)Context; + + DrvLogInfo(g_SDCAVXuLog, FLAG_INFO, + L"Device %p Handling FDL End notification for entity %#x with status %!STATUS!", + Device, + FdlEnd->FdlEntityId, + FdlEnd->FdlStatus); + + return status; +} + +PAGED_CODE_SEG +NTSTATUS SdcaXu_ChangeNotification( + _In_ PVOID Context, + _In_ SDCAXU_NOTIFICATION_TYPE NotificationType, + _In_opt_ PVOID NotificationData, + _In_ ULONG NotificationDataSize +) +{ + PAGED_CODE(); + + switch (NotificationType) + { + case SDCAXU_NOTIFICATION_TYPE::SdcaXuNotificationTypeJackDetect: + if (sizeof(SDCAXU_NOTIFICATION_JACK_DETECT) <= NotificationDataSize) + { + PSDCAXU_NOTIFICATION_JACK_DETECT pJackNotification = PSDCAXU_NOTIFICATION_JACK_DETECT(NotificationData); + return SdcaXuJackStateChangeHandler(Context, pJackNotification->GroupEntityId, pJackNotification->DetectedMode, pJackNotification->JackEvent); + } + else + { + return STATUS_INVALID_PARAMETER; + } + break; + + case SDCAXU_NOTIFICATION_TYPE::SdcaXuNotificationTypePowerPre: + if (sizeof(SDCAXU_NOTIFICATION_POWER) <= NotificationDataSize) + { + PSDCAXU_NOTIFICATION_POWER pPowerNotification = PSDCAXU_NOTIFICATION_POWER(NotificationData); + return SdcaXuPowerStateChangeHandlerPre(Context, pPowerNotification->PowerDomainEntityId, pPowerNotification->OldState, pPowerNotification->NewState); + } + else + { + return STATUS_INVALID_PARAMETER; + } + break; + + case SDCAXU_NOTIFICATION_TYPE::SdcaXuNotificationTypePowerPost: + if (sizeof(SDCAXU_NOTIFICATION_POWER) <= NotificationDataSize) + { + PSDCAXU_NOTIFICATION_POWER pPowerNotification = PSDCAXU_NOTIFICATION_POWER(NotificationData); + return SdcaXuPowerStateChangeHandlerPost(Context, pPowerNotification->PowerDomainEntityId, pPowerNotification->OldState, pPowerNotification->NewState); + } + else + { + return STATUS_INVALID_PARAMETER; + } + break; + + case SDCAXU_NOTIFICATION_TYPE::SdcaXuNotificationTypeHardwareReset: + if (0 == NotificationDataSize) + { + return SdcaXuFunctionHasBeenResetHandler(Context); + } + else + { + return STATUS_INVALID_PARAMETER; + } + break; + + case SDCAXU_NOTIFICATION_TYPE::SdcaXuNotificationTypeFunctionNeedsInitialization: + if (0 == NotificationDataSize) + { + return SdcaXuFunctionNeedsInitializationHandler(Context); + } + else + { + return STATUS_INVALID_PARAMETER; + } + break; + + case SDCAXU_NOTIFICATION_TYPE::SdcaXuNotificationTypeFunctionFault: + if (0 == NotificationDataSize) + { + return SdcaXuFunctionFaultHandler(Context); + } + else + { + return STATUS_INVALID_PARAMETER; + } + break; + + case SDCAXU_NOTIFICATION_TYPE::SdcaXuNotificationTypeUMPSequenceFault: + if (0 == NotificationDataSize) + { + return SdcaXuUMPSequenceFaultHandler(Context); + } + else + { + return STATUS_INVALID_PARAMETER; + } + break; + + case SDCAXU_NOTIFICATION_TYPE::SdcaXuNotificationTypeStreamingStoppedAbnormally: + if (0 == NotificationDataSize) + { + return SdcaXuStreamingStoppedAbnormallyHandler(Context); + } + else + { + return STATUS_INVALID_PARAMETER; + } + break; + + case SDCAXU_NOTIFICATION_TYPE::SdcaXuNotificationTypeCommitGroup: + if (sizeof(SDCAXU_NOTIFICATION_COMMIT_GROUP) <= NotificationDataSize) + { + return SdcaXuCommitGroupHandler(Context, (PSDCAXU_NOTIFICATION_COMMIT_GROUP)NotificationData); + } + else + { + return STATUS_INVALID_PARAMETER; + } + break; + + case SDCAXU_NOTIFICATION_TYPE::SdcaXuNotificationTypePosture: + if (sizeof(SDCAXU_NOTIFICATION_POSTURE) <= NotificationDataSize) + { + return SdcaXuPostureHandler(Context, (PSDCAXU_NOTIFICATION_POSTURE)NotificationData); + } + else + { + return STATUS_INVALID_PARAMETER; + } + break; + case SDCAXU_NOTIFICATION_TYPE::SdcaXuNotificationTypeFdlBegin: + if (sizeof(SDCAXU_NOTIFICATION_FDL_BEGIN) <= NotificationDataSize) + { + return SdcaXuFdlBeginHandler(Context, (PSDCAXU_NOTIFICATION_FDL_BEGIN)NotificationData); + } + else + { + return STATUS_INVALID_PARAMETER; + } + break; + case SDCAXU_NOTIFICATION_TYPE::SdcaXuNotificationTypeFdlEnd: + if (sizeof(SDCAXU_NOTIFICATION_FDL_END) <= NotificationDataSize) + { + return SdcaXuFdlEndHandler(Context, (PSDCAXU_NOTIFICATION_FDL_END)NotificationData); + } + else + { + return STATUS_INVALID_PARAMETER; + } + break; + } + + return STATUS_SUCCESS; +} + +PAGED_CODE_SEG +NTSTATUS +SdcaXu_RetrieveSwftFileOverride( + _In_ PVOID Context, + _In_ USHORT VendorID, + _In_ ULONG FileID, + _In_ USHORT SwftFileVersion, + _In_ ULONG SwftFileLength, + _Out_ PUSHORT NewFileVersion, + _Out_ PULONG NewFileLength, + _In_ ULONG NewFileBufferLength, + _Out_writes_bytes_opt_(NewFileBufferLength) + PVOID NewFileBuffer +) +{ + PAGED_CODE(); + const ULONG replaceFiles[][2] = { + // Using the Microsoft Vendor ID for testing and demonstration purposes - XU developer + // must use a different appropriate vendor ID + { 0x02cb, 0x00000001} + }; + const USHORT newFileVersion = 0x1010; + + BYTE newFileData[] = { + 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, + 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, + 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, + 0x18, 0x19, 0x1a, 0x1b, 0x1c, 0x1d, 0x1e, 0x1f, + }; + + WDFDEVICE device = (WDFDEVICE)Context; + + DrvLogInfo(g_SDCAVXuLog, FLAG_INFO, + L"Device %p Handling SWFT File Override for VendorID %#x FileID %#x Swft Version %#x Length %d", + device, + VendorID, + FileID, + SwftFileVersion, + SwftFileLength); + + if (NewFileBufferLength > 0 && + (!NewFileVersion || !NewFileLength || !NewFileBuffer)) + { + RETURN_NTSTATUS(STATUS_INVALID_PARAMETER); + } + else if (NewFileBufferLength == 0 && !NewFileLength) + { + RETURN_NTSTATUS(STATUS_INVALID_PARAMETER); + } + + // An XU developer may choose to override a SWFT file only if the version matches certain criteria + // or they may choose to override based on other criteria or always. + for (ULONG file = 0; file < ARRAYSIZE(replaceFiles); ++file) + { + if (replaceFiles[file][0] == VendorID && replaceFiles[file][1] == FileID && SwftFileVersion < newFileVersion) + { + if (NewFileBufferLength >= sizeof(newFileData)) + { + DrvLogInfo(g_SDCAVXuLog, FLAG_INFO, + L"Device %p Overriding SWFT file for VendorID %#x FileID %#x, new Version %#x Length %d", + device, + VendorID, + FileID, + newFileVersion, + sizeof(newFileData)); + + RtlCopyMemory(NewFileBuffer, newFileData, sizeof(newFileData)); + *NewFileVersion = newFileVersion; + *NewFileLength = sizeof(newFileData); + return STATUS_SUCCESS; + } + else + { + *NewFileLength = sizeof(newFileData); + RETURN_NTSTATUS(STATUS_BUFFER_OVERFLOW); + } + } + } + + return STATUS_NOT_FOUND; +} + +PAGED_CODE_SEG +_Use_decl_annotations_ +NTSTATUS +EvtSDCAVXuProcessQueryInterfaceRequest( + _In_ WDFDEVICE Device, + _In_ LPGUID InterfaceType, + _Inout_ PINTERFACE ExposedInterface, + _Inout_opt_ PVOID ExposedInterfaceSpecificData +) +{ + PAGED_CODE(); + + DrvLogEnter(g_SDCAVXuLog); + + NTSTATUS status = STATUS_SUCCESS; + + if (IsEqualGUID(*InterfaceType, SDCAXU_INTERFACE) && + ExposedInterface->Size >= sizeof(SDCAXU_INTERFACE_V0102)&& + ExposedInterface->Version == SDCAXU_INTERFACE_VERSION_0102) + { + PSDCAXU_INTERFACE_V0102 pSdcaXuInterface = PSDCAXU_INTERFACE_V0102(ExposedInterface); + + pSdcaXuInterface->InterfaceHeader.Context = Device; + pSdcaXuInterface->InterfaceHeader.InterfaceReference = WdfDeviceInterfaceReferenceNoOp; + pSdcaXuInterface->InterfaceHeader.InterfaceDereference = WdfDeviceInterfaceDereferenceNoOp; + + pSdcaXuInterface->EvtSetHwConfig = SdcaXu_SetHwConfig; + pSdcaXuInterface->EvtSetEndpointConfig = SdcaXu_SetEndpointConfig; + pSdcaXuInterface->EvtRemoveEndpointConfig = SdcaXu_RemoveEndpointConfig; + pSdcaXuInterface->EvtInterruptHandler = SdcaXu_InterruptHandler; + pSdcaXuInterface->EvtChangeNotification = SdcaXu_ChangeNotification; + pSdcaXuInterface->EvtRetrieveSwftFileOverride = SdcaXu_RetrieveSwftFileOverride; + + // + // Check if SDCA has provided its own functions for 2-way communication + // + if (pSdcaXuInterface->EvtSetXUEntities && + pSdcaXuInterface->EvtRegisterForInterrupts && + pSdcaXuInterface->EvtSetJackOverride && + pSdcaXuInterface->EvtSetJackSelectedMode && + pSdcaXuInterface->EvtPDEPowerReferenceAcquire && + pSdcaXuInterface->EvtPDEPowerReferenceRelease && + pSdcaXuInterface->EvtReadDeferredAudioControls && + pSdcaXuInterface->EvtWriteDeferredAudioControls) + { + PSDCAXU_DEVICE_CONTEXT devCtx; + devCtx = GetSdcaXuDeviceContext(Device); + + RtlZeroMemory(&devCtx->SDCADeviceData.SDCAInterface, sizeof(SDCAXU_INTERFACE_V0102)); + devCtx->SDCADeviceData.SDCAInterface.EvtSetXUEntities = pSdcaXuInterface->EvtSetXUEntities; + devCtx->SDCADeviceData.SDCAInterface.EvtRegisterForInterrupts = pSdcaXuInterface->EvtRegisterForInterrupts; + devCtx->SDCADeviceData.SDCAInterface.EvtSetJackOverride = pSdcaXuInterface->EvtSetJackOverride; + devCtx->SDCADeviceData.SDCAInterface.EvtSetJackSelectedMode = pSdcaXuInterface->EvtSetJackSelectedMode; + devCtx->SDCADeviceData.SDCAInterface.EvtPDEPowerReferenceAcquire = pSdcaXuInterface->EvtPDEPowerReferenceAcquire; + devCtx->SDCADeviceData.SDCAInterface.EvtPDEPowerReferenceRelease = pSdcaXuInterface->EvtPDEPowerReferenceRelease; + devCtx->SDCADeviceData.SDCAInterface.EvtReadDeferredAudioControls = pSdcaXuInterface->EvtReadDeferredAudioControls; + devCtx->SDCADeviceData.SDCAInterface.EvtWriteDeferredAudioControls = pSdcaXuInterface->EvtWriteDeferredAudioControls; + + devCtx->SDCADeviceData.SDCAContext = ExposedInterfaceSpecificData; + devCtx->SDCADeviceData.bSDCAInterface = WdfTrue; + } + } + else if (IsEqualGUID(*InterfaceType, SDCAXU_INTERFACE) && + ExposedInterface->Size >= sizeof(SDCAXU_INTERFACE_V0101)&& + ExposedInterface->Version == SDCAXU_INTERFACE_VERSION_0101) + { + PSDCAXU_INTERFACE_V0101 pSdcaXuInterface = PSDCAXU_INTERFACE_V0101(ExposedInterface); + + pSdcaXuInterface->InterfaceHeader.Context = Device; + pSdcaXuInterface->InterfaceHeader.InterfaceReference = WdfDeviceInterfaceReferenceNoOp; + pSdcaXuInterface->InterfaceHeader.InterfaceDereference = WdfDeviceInterfaceDereferenceNoOp; + + pSdcaXuInterface->EvtSetHwConfig = SdcaXu_SetHwConfig; + pSdcaXuInterface->EvtSetEndpointConfig = SdcaXu_SetEndpointConfig; + pSdcaXuInterface->EvtRemoveEndpointConfig = SdcaXu_RemoveEndpointConfig; + pSdcaXuInterface->EvtInterruptHandler = SdcaXu_InterruptHandler; + pSdcaXuInterface->EvtChangeNotification = SdcaXu_ChangeNotification; + + // + // Check if SDCA has provided its own functions for 2-way communication + // + if (pSdcaXuInterface->EvtSetXUEntities && + pSdcaXuInterface->EvtRegisterForInterrupts && + pSdcaXuInterface->EvtSetJackOverride && + pSdcaXuInterface->EvtSetJackSelectedMode && + pSdcaXuInterface->EvtPDEPowerReferenceAcquire && + pSdcaXuInterface->EvtPDEPowerReferenceRelease && + pSdcaXuInterface->EvtReadDeferredAudioControls && + pSdcaXuInterface->EvtWriteDeferredAudioControls) + { + PSDCAXU_DEVICE_CONTEXT devCtx; + devCtx = GetSdcaXuDeviceContext(Device); + + RtlZeroMemory(&devCtx->SDCADeviceData.SDCAInterface, sizeof(SDCAXU_INTERFACE_V0101)); + devCtx->SDCADeviceData.SDCAInterface.EvtSetXUEntities = pSdcaXuInterface->EvtSetXUEntities; + devCtx->SDCADeviceData.SDCAInterface.EvtRegisterForInterrupts = pSdcaXuInterface->EvtRegisterForInterrupts; + devCtx->SDCADeviceData.SDCAInterface.EvtSetJackOverride = pSdcaXuInterface->EvtSetJackOverride; + devCtx->SDCADeviceData.SDCAInterface.EvtSetJackSelectedMode = pSdcaXuInterface->EvtSetJackSelectedMode; + devCtx->SDCADeviceData.SDCAInterface.EvtPDEPowerReferenceAcquire = pSdcaXuInterface->EvtPDEPowerReferenceAcquire; + devCtx->SDCADeviceData.SDCAInterface.EvtPDEPowerReferenceRelease = pSdcaXuInterface->EvtPDEPowerReferenceRelease; + devCtx->SDCADeviceData.SDCAInterface.EvtReadDeferredAudioControls = pSdcaXuInterface->EvtReadDeferredAudioControls; + devCtx->SDCADeviceData.SDCAInterface.EvtWriteDeferredAudioControls = pSdcaXuInterface->EvtWriteDeferredAudioControls; + + devCtx->SDCADeviceData.SDCAContext = ExposedInterfaceSpecificData; + devCtx->SDCADeviceData.bSDCAInterface = WdfTrue; + } + } + else + { + status = STATUS_NOT_SUPPORTED; + } + + RETURN_NTSTATUS_IF_FAILED(status); + + return status; +} + +PAGED_CODE_SEG +NTSTATUS +SDCAVXuAddDDI( + _In_ WDFDEVICE device +) +{ + PAGED_CODE(); + NTSTATUS status = STATUS_SUCCESS; + WDF_QUERY_INTERFACE_CONFIG qiConfig; + + // + // Initialize the qiConfig structure + // + WDF_QUERY_INTERFACE_CONFIG_INIT( + &qiConfig, + NULL, + &SDCAXU_INTERFACE, + EvtSDCAVXuProcessQueryInterfaceRequest + ); + + qiConfig.ImportInterface = WdfTrue; + + // + // Create the interface + // + RETURN_NTSTATUS_IF_FAILED(WdfDeviceAddQueryInterface(device, &qiConfig)); + + return status; +} + +PAGED_CODE_SEG +NTSTATUS +SdcaXu_EvtBusDeviceAdd( + _In_ WDFDRIVER Driver, + _Inout_ PWDFDEVICE_INIT DeviceInit +) +/*++ +Routine Description: + + EvtDeviceAdd is called by the framework in response to AddDevice + call from the PnP manager. We create and initialize a device object to + represent a new instance of the device. All the software resources + should be allocated in this callback. + +Arguments: + + Driver - Handle to a framework driver object created in DriverEntry + + DeviceInit - Pointer to a framework-allocated WDFDEVICE_INIT structure. + +Return Value: + + NTSTATUS + +--*/ +{ + PAGED_CODE(); + + UNREFERENCED_PARAMETER(Driver); + + NTSTATUS status = STATUS_SUCCESS; + + // + // Initialize the pnpPowerCallbacks structure. Callback events for PNP + // and Power are specified here. If you don't supply any callbacks, + // the Framework will take appropriate default actions based on whether + // DeviceInit is initialized to be an FDO, a PDO or a filter device + // object. + // + WDF_PNPPOWER_EVENT_CALLBACKS pnpPowerCallbacks; + WDF_PNPPOWER_EVENT_CALLBACKS_INIT(&pnpPowerCallbacks); + pnpPowerCallbacks.EvtDevicePrepareHardware = SdcaXu_EvtDevicePrepareHardware; + pnpPowerCallbacks.EvtDeviceReleaseHardware = SdcaXu_EvtDeviceReleaseHardware; + WdfDeviceInitSetPnpPowerEventCallbacks(DeviceInit, &pnpPowerCallbacks); + + // + // Specify the type of context needed. + // Use default locking, i.e., none. + // + WDF_OBJECT_ATTRIBUTES attributes; + WDF_OBJECT_ATTRIBUTES_INIT_CONTEXT_TYPE(&attributes, SDCAXU_DEVICE_CONTEXT); + attributes.EvtCleanupCallback = SdcaXu_EvtDeviceContextCleanup; + + // + // Allow ACX to add any pre-requirement it needs on this device. + // + ACX_DEVICEINIT_CONFIG devInitCfg; + ACX_DEVICEINIT_CONFIG_INIT(&devInitCfg); + RETURN_NTSTATUS_IF_FAILED(AcxDeviceInitInitialize(DeviceInit, &devInitCfg)); + + WdfDeviceInitSetPowerPolicyOwnership(DeviceInit, FALSE); + + WdfFdoInitSetFilter(DeviceInit); + + // + // Create the device. + // + WDFDEVICE device = NULL; + RETURN_NTSTATUS_IF_FAILED(WdfDeviceCreate(&DeviceInit, &attributes, &device)); + + // + // Init SdcaXu's device context. + // + PSDCAXU_DEVICE_CONTEXT devCtx; + devCtx = GetSdcaXuDeviceContext(device); + ASSERT(devCtx != NULL); + devCtx->Render = NULL; + + // + // Allow ACX to add any post-requirement it needs on this device. + // + ACX_DEVICE_CONFIG devCfg; + ACX_DEVICE_CONFIG_INIT(&devCfg); + + RETURN_NTSTATUS_IF_FAILED(AcxDeviceInitialize(device, &devCfg)); + + // + // Tell the framework to set the SurpriseRemovalOK in the DeviceCaps so + // that you don't get the popup in usermode (on Win2K) when you surprise + // remove the device. + // + WDF_DEVICE_PNP_CAPABILITIES pnpCaps; + WDF_DEVICE_PNP_CAPABILITIES_INIT(&pnpCaps); + pnpCaps.SurpriseRemovalOK = WdfTrue; + WdfDeviceSetPnpCapabilities(device, &pnpCaps); + + // + // Add QI based Direct Device Interface + // + RETURN_NTSTATUS_IF_FAILED(SDCAVXuAddDDI(device)); + + // + // Create Raw PDO for ACX circuits + // + RETURN_NTSTATUS_IF_FAILED(SDCAVXu_CreateCircuitDevice(device)); + + return status; +} + +PAGED_CODE_SEG +NTSTATUS +SdcaXu_EvtDevicePrepareHardware( + _In_ WDFDEVICE Device, + _In_ WDFCMRESLIST ResourceList, + _In_ WDFCMRESLIST ResourceListTranslated +) +/*++ + +Routine Description: + + In this callback, the driver does whatever is necessary to make the + hardware ready to use. + +Arguments: + + Device - handle to a device + +Return Value: + + NT status value + +--*/ +{ + PAGED_CODE(); + + UNREFERENCED_PARAMETER(Device); + UNREFERENCED_PARAMETER(ResourceList); + UNREFERENCED_PARAMETER(ResourceListTranslated); + + return STATUS_SUCCESS; +} + +PAGED_CODE_SEG +NTSTATUS +SdcaXu_EvtDeviceReleaseHardware( + _In_ WDFDEVICE Device, + _In_ WDFCMRESLIST ResourceListTranslated +) +/*++ + +Routine Description: + + In this callback, the driver releases the h/w resources allocated in the + prepare h/w callback. + +Arguments: + + Device - handle to a device + +Return Value: + + NT status value + +--*/ +{ + NTSTATUS status; + PSDCAXU_DEVICE_CONTEXT devCtx; + + UNREFERENCED_PARAMETER(Device); + UNREFERENCED_PARAMETER(ResourceListTranslated); + + PAGED_CODE(); + + devCtx = GetSdcaXuDeviceContext(Device); + ASSERT(devCtx != NULL); + + + status = STATUS_SUCCESS; + + return status; +} + +#pragma code_seg() +VOID +SdcaXu_EvtDeviceContextCleanup( + _In_ WDFOBJECT WdfDevice +) +/*++ + +Routine Description: + + In this callback, it cleans up device context. + +Arguments: + + WdfDevice - WDF device object + +Return Value: + + NULL + +--*/ +{ + PSDCAXU_DEVICE_CONTEXT devCtx; + + devCtx = GetSdcaXuDeviceContext(WdfDevice); + ASSERT(devCtx != NULL); + + if (devCtx) + { + for (ULONG i = 0; i < ARRAYSIZE(devCtx->EndpointDevices); ++i) + { + if (devCtx->EndpointDevices[i].CircuitUri.Buffer) + { + // Since the Buffer item is part of the actual context memory block, it's guaranteed to be 0-valued until we use it +#pragma prefast(suppress: 6001 , "C6001 Using uninitialized memor: Using uninitialized memory '*devCtx.EndpointDevices.CircuitUri.Buffer" ) + ExFreePool(devCtx->EndpointDevices[i].CircuitUri.Buffer); + RtlZeroMemory(&devCtx->EndpointDevices[i], sizeof(devCtx->EndpointDevices[i])); + } + } + } +} + +PAGED_CODE_SEG +NTSTATUS +SdcaXu_ReplicateFormats( + _In_ ACXPIN Pin, + _In_ ACXTARGETCIRCUIT TargetCircuit, + _In_ ULONG TargetPinId +) +{ + PAGED_CODE(); + + // Using the opposite Pin Id to find the upstream pin only works here because + // we only register for EvtAcxPinConnected on the downstream pin and the XU circuit + // has only two pins (upstream and downstream). If the XU driver exposes more than + // two pins, the following logic would need to be updated to find correct upstream pin. + + ACXPIN upstreamPin; + if (AcxPinGetId(Pin) == 0) + { + upstreamPin = AcxCircuitGetPinById(AcxPinGetCircuit(Pin), 1); + } + else + { + upstreamPin = AcxCircuitGetPinById(AcxPinGetCircuit(Pin), 0); + } + + if (!upstreamPin) + { + RETURN_NTSTATUS(STATUS_UNSUCCESSFUL); + } + + ACXTARGETPIN targetPin = AcxTargetCircuitGetTargetPin(TargetCircuit, TargetPinId); + if (!targetPin) + { + RETURN_NTSTATUS(STATUS_UNSUCCESSFUL); + } + + // Don't delete the target pin - it will be cleaned up when the target circuit is cleaned up by ACX + + GUID targetModes[] = + { + AUDIO_SIGNALPROCESSINGMODE_RAW, + AUDIO_SIGNALPROCESSINGMODE_DEFAULT, + AUDIO_SIGNALPROCESSINGMODE_COMMUNICATIONS, + AUDIO_SIGNALPROCESSINGMODE_SPEECH + }; + + ULONG totalFormats = 0; + for (ULONG modeIdx = 0; modeIdx < ARRAYSIZE(targetModes); ++modeIdx) + { + ACXDATAFORMATLIST targetFormatList; + ACXDATAFORMATLIST localFormatList = nullptr; + NTSTATUS status = AcxTargetPinRetrieveModeDataFormatList(targetPin, targetModes+modeIdx, &targetFormatList); + if (!NT_SUCCESS(status)) + { + // If the downstream pin doesn't support any formats for this mode, make sure we clear out our host pin + // formats for this mode as well. + if (modeIdx == 0) + { + localFormatList = AcxPinGetRawDataFormatList(upstreamPin); + } + else + { + // Ignore the status + AcxPinRetrieveModeDataFormatList(upstreamPin, targetModes + modeIdx, &localFormatList); + } + if (localFormatList) + { + RETURN_NTSTATUS_IF_FAILED(SdcaVad_ClearDataFormatList(localFormatList)); + } + continue; + } + + RETURN_NTSTATUS_IF_FAILED(SdcaVad_RetrieveOrCreateDataFormatList(upstreamPin, targetModes + modeIdx, &localFormatList)); + + RETURN_NTSTATUS_IF_FAILED(SdcaVad_ClearDataFormatList(localFormatList)); + + ULONG formatCount = 0; + RETURN_NTSTATUS_IF_FAILED(SdcaVad_CopyFormats(targetFormatList, localFormatList, &formatCount)); + + totalFormats += formatCount; + } + + if (totalFormats == 0) + { + RETURN_NTSTATUS(STATUS_NO_MATCH); + } + + return STATUS_SUCCESS; +} + +PAGED_CODE_SEG +VOID +SdcaXu_EvtPinConnected( + _In_ ACXPIN Pin, + _In_ ACXTARGETCIRCUIT TargetCircuit, + _In_ ULONG TargetPinId +) +{ + NTSTATUS status; + + PAGED_CODE(); + + // Call the worker so we can trace the return of failures + status = SdcaXu_ReplicateFormats(Pin, TargetCircuit, TargetPinId); + + // If we found no formats, there's not much we can do about it. + if (!NT_SUCCESS(status)) + { + DrvLogError(g_SDCAVXuLog, FLAG_INIT, L"Failed to replicate downstream formats to upstream pin, %!STATUS!", status); + } +} + +#pragma code_seg() +VOID +SdcaXu_EvtStreamDestroy( + _In_ WDFOBJECT Object +) +{ + PSDCAXU_STREAM_CONTEXT ctx; + CStreamEngine * streamEngine = NULL; + + NTSTATUS status = STATUS_SUCCESS; + auto exit = scope_exit([&status]() { + if (!NT_SUCCESS(status)) + { + DrvLogError(g_SDCAVXuLog, FLAG_INIT, L"SdcaXu_EvtStreamDestroy - failed, %!STATUS!", status); + } + }); + + ctx = GetSdcaXuStreamContext((ACXSTREAM)Object); + + streamEngine = (CStreamEngine*)ctx->StreamEngine; + ctx->StreamEngine = NULL; + if (streamEngine) + { + delete streamEngine; + } +} + +PAGED_CODE_SEG +NTSTATUS +SdcaXu_EvtStreamGetHwLatency( + _In_ ACXSTREAM Stream, + _Out_ ULONG * FifoSize, + _Out_ ULONG * Delay +) +{ + PSDCAXU_STREAM_CONTEXT ctx; + CStreamEngine * streamEngine = NULL; + + PAGED_CODE(); + + ctx = GetSdcaXuStreamContext(Stream); + + streamEngine = (CStreamEngine*)ctx->StreamEngine; + + return streamEngine->GetHWLatency(FifoSize, Delay); +} + +PAGED_CODE_SEG +NTSTATUS +SdcaXu_EvtStreamPrepareHardware( + _In_ ACXSTREAM Stream +) +{ + PSDCAXU_STREAM_CONTEXT ctx; + CStreamEngine * streamEngine = NULL; + + PAGED_CODE(); + + ctx = GetSdcaXuStreamContext(Stream); + + streamEngine = (CStreamEngine*)ctx->StreamEngine; + + return streamEngine->PrepareHardware(); +} + +PAGED_CODE_SEG +NTSTATUS +SdcaXu_EvtStreamReleaseHardware( + _In_ ACXSTREAM Stream +) +{ + PSDCAXU_STREAM_CONTEXT ctx; + CStreamEngine * streamEngine = NULL; + + PAGED_CODE(); + + ctx = GetSdcaXuStreamContext(Stream); + + streamEngine = (CStreamEngine*)ctx->StreamEngine; + + return streamEngine->ReleaseHardware(); +} + +PAGED_CODE_SEG +NTSTATUS +SdcaXu_EvtStreamRun( + _In_ ACXSTREAM Stream +) +{ + PSDCAXU_STREAM_CONTEXT ctx; + CStreamEngine * streamEngine = NULL; + + PAGED_CODE(); + + ctx = GetSdcaXuStreamContext(Stream); + + streamEngine = (CStreamEngine*)ctx->StreamEngine; + + return streamEngine->Run(); +} + +PAGED_CODE_SEG +NTSTATUS +SdcaXu_EvtStreamPause( + _In_ ACXSTREAM Stream +) +{ + PSDCAXU_STREAM_CONTEXT ctx; + CStreamEngine * streamEngine = NULL; + + PAGED_CODE(); + + ctx = GetSdcaXuStreamContext(Stream); + + streamEngine = (CStreamEngine*)ctx->StreamEngine; + + return streamEngine->Pause(); +} + +PAGED_CODE_SEG +NTSTATUS +SdcaXu_EvtStreamAssignDrmContentId( + _In_ ACXSTREAM Stream, + _In_ ULONG DrmContentId, + _In_ PACXDRMRIGHTS DrmRights +) +{ + PSDCAXU_STREAM_CONTEXT ctx; + CStreamEngine * streamEngine = NULL; + + PAGED_CODE(); + + ctx = GetSdcaXuStreamContext(Stream); + + streamEngine = (CStreamEngine*)ctx->StreamEngine; + + return streamEngine->AssignDrmContentId(DrmContentId, DrmRights); +} + + diff --git a/audio/Soundwire/Samples/SdcaVad/SdcaVXu/driver.cpp b/audio/Soundwire/Samples/SdcaVad/SdcaVXu/driver.cpp new file mode 100644 index 000000000..775094ee0 --- /dev/null +++ b/audio/Soundwire/Samples/SdcaVad/SdcaVXu/driver.cpp @@ -0,0 +1,178 @@ +/*++ + +Copyright (c) Microsoft Corporation. All rights reserved. + +Module Name: + + Driver.cpp + +Abstract: + + Sample Soundwire Extension Unit Driver. + +Environment: + + Kernel mode only + +--*/ + +#include "private.h" +#include "trace.h" +#include "driver.h" + +#ifndef __INTELLISENSE__ +#include "driver.tmh" +#endif + +RECORDER_LOG g_SDCAVXuLog; + +// When creating private drops, use set C_DEFINES=-DDROP_STAMP=nnnn to add the drop timestamp to the initial trace +#ifndef DROP_STAMP +#define DROP_STAMP 0 +#endif + +PAGED_CODE_SEG +void SdcaXu_DriverUnload (_In_ WDFDRIVER Driver) +{ + PAGED_CODE(); + + if (!Driver) + { + ASSERT(FALSE); + return; + } + + if (g_RegistryPath.Buffer != NULL) + { + ExFreePool(g_RegistryPath.Buffer); + RtlZeroMemory(&g_RegistryPath, sizeof(g_RegistryPath)); + } + + WPP_CLEANUP(WdfDriverWdmGetDriverObject(Driver)); + + return; +} + +INIT_CODE_SEG +NTSTATUS +DriverEntry( + _In_ PDRIVER_OBJECT DriverObject, + _In_ PUNICODE_STRING RegistryPath + ) +/*++ + +Routine Description: + DriverEntry initializes the driver and is the first routine called by the + system after the driver is loaded. + +Parameters Description: + + DriverObject - represents the instance of the function driver that is loaded + into memory. DriverEntry must initialize members of DriverObject before it + returns to the caller. DriverObject is allocated by the system before the + driver is loaded, and it is released by the system after the system unloads + the function driver from memory. + + RegistryPath - represents the driver specific path in the Registry. + The function driver can use the path to store driver related data between + reboots. The path does not store hardware instance specific data. + +Return Value: + + STATUS_SUCCESS if successful, + STATUS_UNSUCCESSFUL otherwise. + +--*/ +{ + PAGED_CODE(); + + NTSTATUS status = STATUS_SUCCESS; + + WPP_INIT_TRACING(DriverObject, RegistryPath); + + auto exit = scope_exit([&status, &DriverObject]() { + if (!NT_SUCCESS(status)) + { + if (g_RegistryPath.Buffer != NULL) + { + ExFreePool(g_RegistryPath.Buffer); + RtlZeroMemory(&g_RegistryPath, sizeof(g_RegistryPath)); + } + + WPP_CLEANUP(DriverObject); + } + }); + + RETURN_NTSTATUS_IF_FAILED(CopyRegistrySettingsPath(RegistryPath)); + + // + // Initiialize driver config to control the attributes that + // are global to the driver. Note that framework by default + // provides a driver unload routine. If you create any resources + // in the DriverEntry and want to be cleaned in driver unload, + // you can override that by manually setting the EvtDriverUnload in the + // config structure. In general xxx_CONFIG_INIT macros are provided to + // initialize most commonly used members. + // + + WDF_DRIVER_CONFIG wdfCfg; + WDF_DRIVER_CONFIG_INIT(&wdfCfg, SdcaXu_EvtBusDeviceAdd); + wdfCfg.EvtDriverUnload = SdcaXu_DriverUnload; + + // + // Add a driver context. (for illustration purposes only). + // + WDF_OBJECT_ATTRIBUTES attributes; + WDF_OBJECT_ATTRIBUTES_INIT_CONTEXT_TYPE(&attributes, SDCAXU_DRIVER_CONTEXT); + + // + // Create a framework driver object to represent our driver. + // + WDFDRIVER driver; + RETURN_NTSTATUS_IF_FAILED(WdfDriverCreate( + DriverObject, + RegistryPath, + &attributes, // Driver Attributes + &wdfCfg, // Driver Config Info + &driver // hDriver + )); + + RECORDER_CONFIGURE_PARAMS recorderConfig; + RECORDER_CONFIGURE_PARAMS_INIT(&recorderConfig); + recorderConfig.CreateDefaultLog = FALSE; + WppRecorderConfigure(&recorderConfig); + + RECORDER_LOG_CREATE_PARAMS recorderLogCreateParams; + RECORDER_LOG_CREATE_PARAMS_INIT(&recorderLogCreateParams, NULL); + recorderLogCreateParams.TotalBufferSize = WPP_TOTAL_BUFFER_SIZE; + recorderLogCreateParams.ErrorPartitionSize = WPP_ERROR_PARTITION_SIZE; + + RtlStringCbPrintfA(recorderLogCreateParams.LogIdentifier, + RECORDER_LOG_IDENTIFIER_MAX_CHARS, + "SDCAVXu"); + + RECORDER_LOG logHandle = NULL; + status = WppRecorderLogCreate(&recorderLogCreateParams, &logHandle); + if (!NT_SUCCESS(status)) + { + logHandle = NULL; + + // Non fatal failure + status = STATUS_SUCCESS; + } + + g_SDCAVXuLog = logHandle; + + DrvLogInfo(g_SDCAVXuLog, FLAG_INIT, "SdcaVXu Driver loaded, %lld", DROP_STAMP); + + // + // Post init. + // + ACX_DRIVER_CONFIG acxCfg; + ACX_DRIVER_CONFIG_INIT(&acxCfg); + + RETURN_NTSTATUS_IF_FAILED(AcxDriverInitialize(driver, &acxCfg)); + + return status; +} + diff --git a/audio/Soundwire/Samples/SdcaVad/SdcaVXu/driver.h b/audio/Soundwire/Samples/SdcaVad/SdcaVXu/driver.h new file mode 100644 index 000000000..0666a3d73 --- /dev/null +++ b/audio/Soundwire/Samples/SdcaVad/SdcaVXu/driver.h @@ -0,0 +1,35 @@ +/*++ + +Copyright (c) Microsoft Corporation. All rights reserved. + + THIS CODE AND INFORMATION IS PROVIDED "AS IS" WITHOUT WARRANTY OF ANY + KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE + IMPLIED WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A PARTICULAR + PURPOSE. + +Module Name: + + driver.h + +Abstract: + + Contains structure definitions and function prototypes private to + the driver. + +Environment: + + Kernel mode + +--*/ + +#pragma once + +EVT_WDF_DRIVER_UNLOAD SdcaXu_DriverUnload; + +NTSTATUS +DriverEntry( + _In_ PDRIVER_OBJECT DriverObject, + _In_ PUNICODE_STRING RegistryPath +); + +#pragma code_seg() diff --git a/audio/Soundwire/Samples/SdcaVad/SdcaVXu/private.h b/audio/Soundwire/Samples/SdcaVad/SdcaVXu/private.h new file mode 100644 index 000000000..e39c857d4 --- /dev/null +++ b/audio/Soundwire/Samples/SdcaVad/SdcaVXu/private.h @@ -0,0 +1,422 @@ +/*++ + +Copyright (c) Microsoft Corporation. All rights reserved. + + THIS CODE AND INFORMATION IS PROVIDED "AS IS" WITHOUT WARRANTY OF ANY + KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE + IMPLIED WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A PARTICULAR + PURPOSE. + +Module Name: + + private.h + +Abstract: + + Contains structure definitions and function prototypes private to + the driver. + +Environment: + + Kernel mode + +--*/ + +#ifndef _PRIVATE_H_ +#define _PRIVATE_H_ + +#include "cpp_utils.h" + +#include "stdunk.h" +#include +#include + +#include "NewDelete.h" + +/* make prototypes usable from C++ */ +#ifdef __cplusplus +extern "C" { +#endif + +#pragma warning(disable:4200) // +#pragma warning(disable:4201) // nameless struct/union +#pragma warning(disable:4214) // bit field types other than int + +#include +#include +#include +#include + +#pragma warning(default:4200) +#pragma warning(default:4201) +#pragma warning(default:4214) + +#include +#include + +#include "SoundWireController.h" +#include "SdcaXu.h" + +#include "Trace.h" + +#define PAGED_CODE_SEG __declspec(code_seg("PAGE")) +#define INIT_CODE_SEG __declspec(code_seg("INIT")) + +extern RECORDER_LOG g_SDCAVXuLog; + +// Simple ACX driver +#define DRIVER_TAG (ULONG) 'Ecds' + +// Number of msec for idle timeout. +#define IDLE_POWER_TIMEOUT 5000 + +// Number of millisecs per sec. +#define MS_PER_SEC 1000 + +// Number of hundred nanosecs per sec. +#define HNS_PER_SEC 10000000 + +// Compatible ID for render/capture +#define SDCAVAD_COMPATIBLE_ID L"{2172A9B3-0690-4BB6-88FF-B778E999E04A}" + +// Container ID for render/capture +#define SDCAVAD_CONTAINER_ID L"{00000000-0000-0000-ffff-ffffffffffff}" + +// Bridge Pin Number for Circuit +const ULONG _BRIDGE_PIN_ID = 1; + +#undef MIN +#undef MAX +#define MIN(a,b) ((a) > (b) ? (b) : (a)) +#define MAX(a,b) ((a) > (b) ? (a) : (b)) + +#ifndef BOOL +typedef int BOOL; +#endif + +#ifndef SIZEOF_ARRAY +#define SIZEOF_ARRAY(ar) (sizeof(ar)/sizeof((ar)[0])) +#endif // !defined(SIZEOF_ARRAY) + +#define SDCA_INTERRUPT_DEFINE_MASK(Mask, ...)\ + {\ + unsigned char parameters[] = {__VA_ARGS__};\ + for(unsigned char iParameter = 0; iParameter < SIZEOF_ARRAY(parameters); iParameter ++) \ + {\ + Mask = Mask | (0x1<<(unsigned long)parameters[iParameter]); \ + }\ + }\ + +#ifdef ACX_WORKAROUND_AGGREGATED_MODULE_NOTIFICATIONS +#define STATIC_KSPROPERTYSETID_AcxCircuit\ + 0x4d12807eL, 0x55db, 0x48b8, 0xa4, 0x66, 0xf1, 0x5a, 0x51, 0x0f, 0x58, 0x17 +DEFINE_GUIDSTRUCT("4d12807e-55db-48b8-a466-f15a510f5817", KSPROPERTYSETID_AcxCircuit); +#define KSPROPERTYSETID_AcxCircuit DEFINE_GUIDNAMED(KSPROPERTYSETID_AcxCircuit) + +typedef enum { + KSPROPERTY_ACXCIRCUIT_INFORMATION = 1, // get + KSPROPERTY_ACXCIRCUIT_SETNOTIFICATIONDEVICE, + KSPROPERTY_ACXCIRCUIT_SETNOTIFICATIONID, + KSPROPERTY_ACXCIRCUIT_SETINSTANCEID, +} KSPROPERTY_ACXCIRCUIT; +#endif + +// NULL GUID +static const GUID NULL_GUID = +{0x00000000,0x0000,0x0000,{0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00}}; + +// +// Example Acpi blob for Hardware configuration +// +typedef struct _SdcaXuAcpiBlob +{ + // Number of endpoints + ULONG NumEndpoints; + +}SdcaXuAcpiBlob, *PSdcaXuAcpiBlob; + +// Test Structure so the Extension Unit Driver can do the right things. +typedef struct _EXTENSION_UNIT_HW_DATA +{ + UINT8 AvailableXUEntities[8]; + UINT8 AvailableGroupEntities[8]; +} EXTENSION_UNIT_HW_DATA, * PEXTENSION_UNIT_HW_DATA; + +// +// Define XU driver context. +// +typedef struct _SDCAXU_DRIVER_CONTEXT { + BOOLEAN Dummy; +} SDCAXU_DRIVER_CONTEXT, *PSDCAXU_DRIVER_CONTEXT; + +WDF_DECLARE_CONTEXT_TYPE_WITH_NAME(SDCAXU_DRIVER_CONTEXT, GetSdcaXuDriverContext) + +typedef struct _SDCA_DEVICE_DATA +{ + BOOLEAN bSDCAInterface; + PVOID SDCAContext; + SDCAXU_INTERFACE_V0101 SDCAInterface; + + ULONG NumEndpoints; + EXTENSION_UNIT_HW_DATA HwData; +}SDCA_DEVICE_DATA, * PSDCA_DEVICE_DATA; + +#define MAX_ENDPOINT_COUNT 6 +typedef struct _ENDPOINT_DEVICE_PAIR +{ + GUID CircuitId; + UNICODE_STRING CircuitUri; + WDFDEVICE CircuitDevice; +}ENDPOINT_DEVICE_PAIR, *PENDPOINT_DEVICE_PAIR; +// +// Define XU device context. +// +typedef struct _SDCAXU_DEVICE_CONTEXT { + ACXCIRCUIT Render; + ACXCIRCUIT Capture; + SDCA_DEVICE_DATA SDCADeviceData; + WDFDEVICE CircuitDevice; + + ENDPOINT_DEVICE_PAIR EndpointDevices[MAX_ENDPOINT_COUNT]; +} SDCAXU_DEVICE_CONTEXT, *PSDCAXU_DEVICE_CONTEXT; + +WDF_DECLARE_CONTEXT_TYPE_WITH_NAME(SDCAXU_DEVICE_CONTEXT, GetSdcaXuDeviceContext) + +// +// Define Circuit Device context. +// +typedef struct _SDCAXU_CIRCUIT_DEVICE_CONTEXT { + BOOLEAN FirstTimePrepareHardware; +} SDCAXU_CIRCUIT_DEVICE_CONTEXT, *PSDCAXU_CIRCUIT_DEVICE_CONTEXT; + +WDF_DECLARE_CONTEXT_TYPE_WITH_NAME(SDCAXU_CIRCUIT_DEVICE_CONTEXT, GetCircuitDeviceContext) + +// +// Define circuit context for module-hosting circuit. +// +typedef struct _SdcaXuMODULECIRCUIT_CONTEXT { + BOOLEAN Dummy; +} SDCAXU_MODULECIRCUIT_CONTEXT, *PSDCAXU_MODULECIRCUIT_CONTEXT; + +WDF_DECLARE_CONTEXT_TYPE_WITH_NAME(SDCAXU_MODULECIRCUIT_CONTEXT, GetModuleCircuitContext) + +// +// Define RENDER device context. +// +typedef struct _SDCAXU_RENDER_DEVICE_CONTEXT { + ACXCIRCUIT Circuit; + BOOLEAN FirstTimePrepareHardware; +} SDCAXU_RENDER_DEVICE_CONTEXT, *PSDCAXU_RENDER_DEVICE_CONTEXT; + +WDF_DECLARE_CONTEXT_TYPE_WITH_NAME(SDCAXU_RENDER_DEVICE_CONTEXT, GetRenderDeviceContext) + +// +// Define RENDER circuit context. +// +typedef struct _SDCAXU_RENDER_CIRCUIT_CONTEXT { + PSDCAXU_ACX_CIRCUIT_CONFIG CircuitConfig; + ULONG CommitGroupHandle; +#ifdef ACX_WORKAROUND_AGGREGATED_MODULE_NOTIFICATIONS + WDFDEVICE EndpointDevice; + WDFIOTARGET WdfIoNotificationTarget; + GUID PnpNotificationId; + DWORD InstanceId; +#endif +} SDCAXU_RENDER_CIRCUIT_CONTEXT, *PSDCAXU_RENDER_CIRCUIT_CONTEXT; + +WDF_DECLARE_CONTEXT_TYPE_WITH_NAME(SDCAXU_RENDER_CIRCUIT_CONTEXT, GetRenderCircuitContext) + +// +// Define CAPTURE device context. +// +typedef struct _SDCAXU_CAPTURE_DEVICE_CONTEXT { + ACXCIRCUIT Circuit; + BOOLEAN FirstTimePrepareHardware; +} SDCAXU_CAPTURE_DEVICE_CONTEXT, *PSDCAXU_CAPTURE_DEVICE_CONTEXT; + +WDF_DECLARE_CONTEXT_TYPE_WITH_NAME(SDCAXU_CAPTURE_DEVICE_CONTEXT, GetCaptureDeviceContext) + +// +// Define CAPTURE circuit context. +// +typedef struct _SDCAXU_CAPTURE_CIRCUIT_CONTEXT { + PSDCAXU_ACX_CIRCUIT_CONFIG CircuitConfig; +} SDCAXU_CAPTURE_CIRCUIT_CONTEXT, *PSDCAXU_CAPTURE_CIRCUIT_CONTEXT; + +WDF_DECLARE_CONTEXT_TYPE_WITH_NAME(SDCAXU_CAPTURE_CIRCUIT_CONTEXT, GetCaptureCircuitContext) + +// +// Define XU render/capture stream context. +// +typedef struct _SDCAXU_STREAM_CONTEXT { + PVOID StreamEngine; +} SDCAXU_STREAM_CONTEXT, *PSDCAXU_STREAM_CONTEXT; + +WDF_DECLARE_CONTEXT_TYPE_WITH_NAME(SDCAXU_STREAM_CONTEXT, GetSdcaXuStreamContext) + +// +// Define XU circuit/stream element context. +// +typedef struct _SDCAXU_ELEMENT_CONTEXT { + BOOLEAN Dummy; +} SDCAXU_ELEMENT_CONTEXT, *PSDCAXU_ELEMENT_CONTEXT; + +WDF_DECLARE_CONTEXT_TYPE_WITH_NAME(SDCAXU_ELEMENT_CONTEXT, GetSdcaXuElementContext) + +// +// Define XU format context. +// +typedef struct _SDCAXU_FORMAT_CONTEXT { + BOOLEAN Dummy; +} SDCAXU_FORMAT_CONTEXT, *PSDCAXU_FORMAT_CONTEXT; + +WDF_DECLARE_CONTEXT_TYPE_WITH_NAME(SDCAXU_FORMAT_CONTEXT, GetSdcaXuFormatContext) + +typedef struct _SDCAXU_PIN_CONTEXT { + BOOLEAN Dummy; +} SDCAXU_PIN_CONTEXT, *PSDCAXU_PIN_CONTEXT; + +WDF_DECLARE_CONTEXT_TYPE_WITH_NAME(SDCAXU_PIN_CONTEXT, GetSdcaXuPinContext) + +typedef struct _SDCAXU_PNPEVENT_CONTEXT { + BOOLEAN Dummy; +} SDCAXU_PNPEVENT_CONTEXT, *PSDCAXU_PNPEVENT_CONTEXT; + +WDF_DECLARE_CONTEXT_TYPE_WITH_NAME(SDCAXU_PNPEVENT_CONTEXT, GetSdcaXuPnpEventContext) + + +// +// Driver prototypes. +// +DRIVER_INITIALIZE DriverEntry; +DRIVER_UNLOAD SdcaXu_DriverUnload; +EVT_WDF_DRIVER_DEVICE_ADD SdcaXu_EvtBusDeviceAdd; + +// Device callbacks. + +EVT_WDF_DEVICE_PREPARE_HARDWARE SdcaXu_EvtDevicePrepareHardware; +EVT_WDF_DEVICE_RELEASE_HARDWARE SdcaXu_EvtDeviceReleaseHardware; +EVT_WDF_DEVICE_CONTEXT_CLEANUP SdcaXu_EvtDeviceContextCleanup; + +EVT_WDF_IO_QUEUE_IO_DEVICE_CONTROL SdcaXu_EvtIoDeviceControl; + +EVT_ACX_PIN_CONNECTED SdcaXu_EvtPinConnected; + +// Stream callbacks shared between Capture and Render + +EVT_WDF_OBJECT_CONTEXT_DESTROY SdcaXu_EvtStreamDestroy; +EVT_ACX_STREAM_GET_HW_LATENCY SdcaXu_EvtStreamGetHwLatency; +EVT_ACX_STREAM_PREPARE_HARDWARE SdcaXu_EvtStreamPrepareHardware; +EVT_ACX_STREAM_RELEASE_HARDWARE SdcaXu_EvtStreamReleaseHardware; +EVT_ACX_STREAM_RUN SdcaXu_EvtStreamRun; +EVT_ACX_STREAM_PAUSE SdcaXu_EvtStreamPause; +EVT_ACX_STREAM_ASSIGN_DRM_CONTENT_ID SdcaXu_EvtStreamAssignDrmContentId; + +EVT_ACX_FACTORY_CIRCUIT_CREATE_CIRCUITDEVICE SdcaXu_EvtAcxFactoryCircuitCreateCircuitDevice; +EVT_ACX_FACTORY_CIRCUIT_CREATE_CIRCUIT SdcaXu_EvtAcxFactoryCircuitCreateCircuit; + +/* make internal prototypes usable from C++ */ +#ifdef __cplusplus +} +#endif + +// +// Used to store the registry settings path for the driver +// +extern UNICODE_STRING g_RegistryPath; + +__drv_requiresIRQL(PASSIVE_LEVEL) +PAGED_CODE_SEG +NTSTATUS +CopyRegistrySettingsPath( + _In_ PUNICODE_STRING RegistryPath +); + +PAGED_CODE_SEG +NTSTATUS +SdcaXu_SetPowerPolicy( + _In_ WDFDEVICE Device +); + +PAGED_CODE_SEG +NTSTATUS +SdcaXu_AddRenders( + _In_ WDFDEVICE Device, + _In_ PSDCAXU_ACX_CIRCUIT_CONFIG CircuitConfig +); + +PAGED_CODE_SEG +NTSTATUS +SdcaXu_AddCaptures( + _In_ WDFDEVICE Device, + _In_ PSDCAXU_ACX_CIRCUIT_CONFIG CircuitConfig +); + +PAGED_CODE_SEG +NTSTATUS SdcaXu_SetHwConfig +( + _In_ PVOID Context, + _In_ SDCAXU_HW_CONFIG_TYPE HwConfigType, + _In_opt_ PVOID HwConfigData, + _In_ ULONG HwConfigDataSize +); + +PAGED_CODE_SEG +NTSTATUS SdcaXu_SetEndpointConfig +( + _In_ PVOID Context, + _In_ SDCAXU_ENDPOINT_CONFIG_TYPE EndpointConfigType, + _In_opt_ PVOID EndpointConfigData, + _In_ ULONG EndpointConfigDataSize +); + +PAGED_CODE_SEG +NTSTATUS SdcaXu_SDCAInterruptHandler +( + _In_ PVOID Context, + _In_ ULONG SDCAInterruptBit +); + +PAGED_CODE_SEG +NTSTATUS SdcaXu_SCPInterruptHandler +( + _In_ PVOID Context, + _In_ ULONG SCPInterruptBit +); + +PAGED_CODE_SEG +NTSTATUS SdcaXu_DataPortInterruptHandler +( + _In_ PVOID Context, + _In_ ULONG DataPort, + _In_ ULONG DataPortInterruptBit +); + +PAGED_CODE_SEG +NTSTATUS SdcaXu_ChangeNotification +( + _In_ PVOID Context, + _In_ SDCAXU_NOTIFICATION_TYPE NotificationType, + _In_opt_ PVOID NotificationData, + _In_ ULONG NotificationDataSize +); + +PAGED_CODE_SEG +NTSTATUS SdcaXu_SetXUEntities(_In_ WDFDEVICE Device); + +PAGED_CODE_SEG +NTSTATUS SdcaXu_RegisterForInterrupts(_In_ WDFDEVICE Device); + +PAGED_CODE_SEG +NTSTATUS SdcaXu_SetJackOverride(_In_ WDFDEVICE Device); + +EVT_WDF_DEVICE_PROCESS_QUERY_INTERFACE_REQUEST EvtSDCAVXuProcessQueryInterfaceRequest; + +PAGED_CODE_SEG +NTSTATUS +SdcaXu_ConfigureTestInterface( + _In_ WDFDEVICE Device, + _In_ PCGUID Interface +); + +#endif // _PRIVATE_H_ diff --git a/audio/Soundwire/Samples/SdcaVad/SdcaVXu/render.cpp b/audio/Soundwire/Samples/SdcaVad/SdcaVXu/render.cpp new file mode 100644 index 000000000..10d2e7051 --- /dev/null +++ b/audio/Soundwire/Samples/SdcaVad/SdcaVXu/render.cpp @@ -0,0 +1,2093 @@ +/*++ + + THIS CODE AND INFORMATION IS PROVIDED "AS IS" WITHOUT WARRANTY OF ANY + KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE + IMPLIED WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A PARTICULAR + PURPOSE. + +Module Name: + + Render.cpp + +Abstract: + + Plug and Play module. This file contains routines to handle pnp requests. + +Environment: + + Kernel mode + +--*/ + +#include "private.h" +#include +#include "stdunk.h" +#include +#include +#include + +#include "render.h" + +#include "streamengine.h" + +#include "AudioFormats.h" + +#include "audiomodule.h" + +#include "audioaggregation.h" + +#ifndef __INTELLISENSE__ +#include "render.tmh" +#endif + +PAGED_CODE_SEG +NTSTATUS +SdcaXuR_EvtAcxPinSetDataFormat ( + _In_ ACXPIN Pin, + _In_ ACXDATAFORMAT DataFormat + ) +{ + PAGED_CODE(); + + UNREFERENCED_PARAMETER(Pin); + UNREFERENCED_PARAMETER(DataFormat); + + + return STATUS_NOT_SUPPORTED; +} + +#pragma code_seg() +VOID +SdcaXuR_EvtPinContextCleanup( + _In_ WDFOBJECT WdfPin + ) +/*++ + +Routine Description: + + In this callback, it cleans up pin context. + +Arguments: + + WdfDevice - WDF device object + +Return Value: + + NULL + +--*/ +{ + UNREFERENCED_PARAMETER(WdfPin); +} + +VOID SdcaXuR_EvtCircuitContextCleanup(_In_ WDFOBJECT Object) +{ + ACXCIRCUIT circuit = (ACXCIRCUIT)Object; + PSDCAXU_RENDER_CIRCUIT_CONTEXT cirCtx = GetRenderCircuitContext(circuit); + + if (cirCtx->CircuitConfig) + { + ExFreePoolWithTag(cirCtx->CircuitConfig, DRIVER_TAG); + cirCtx->CircuitConfig = NULL; + } + +#ifdef ACX_WORKAROUND_AGGREGATED_MODULE_NOTIFICATIONS + if (cirCtx->WdfIoNotificationTarget) + { + WdfObjectDelete(cirCtx->WdfIoNotificationTarget); + cirCtx->WdfIoNotificationTarget = nullptr; + } + + cirCtx->PnpNotificationId = GUID_NULL; + cirCtx->InstanceId = 0; + cirCtx->EndpointDevice = nullptr; +#endif + + return; +} + +PAGED_CODE_SEG +VOID +SdcaXuR_EvtCircuitRequestPreprocess( + _In_ ACXOBJECT Object, + _In_ ACXCONTEXT DriverContext, + _In_ WDFREQUEST Request + ) +/*++ + +Routine Description: + + This function is an example of a preprocess routine. + +--*/ +{ + PAGED_CODE(); + + UNREFERENCED_PARAMETER(DriverContext); + + ASSERT(Object != NULL); + ASSERT(DriverContext); + ASSERT(Request); + + +// to handle module notifications directly, the following needs to be intercepted +// with the values cached, so that the XU has them available to compose the stream +// module notifications. +#ifdef ACX_WORKAROUND_AGGREGATED_MODULE_NOTIFICATIONS + if (Object != nullptr) + { + NTSTATUS status = STATUS_SUCCESS; + ACX_REQUEST_PARAMETERS params; + PSDCAXU_RENDER_CIRCUIT_CONTEXT cirCtx; + + cirCtx = GetRenderCircuitContext(Object); + + ACX_REQUEST_PARAMETERS_INIT(¶ms); + AcxRequestGetParameters(Request, ¶ms); + + if(params.Type == AcxRequestTypeProperty && + params.Parameters.Property.Set == KSPROPERTYSETID_AcxCircuit) + { + switch(params.Parameters.Property.Id) + { + case KSPROPERTY_ACXCIRCUIT_SETNOTIFICATIONDEVICE: + { + WDF_OBJECT_ATTRIBUTES ioTargetAttrib; + WDFIOTARGET ioTarget = nullptr; + WDF_IO_TARGET_OPEN_PARAMS openParams; + UNICODE_STRING symbolicLinkName = {0}; + + if (params.Parameters.Property.Verb != AcxPropertyVerbSet) + { + status = STATUS_INVALID_DEVICE_REQUEST; + goto exit; + } + + if (params.Parameters.Property.ValueCb == 0) + { + status = STATUS_INVALID_PARAMETER; + goto exit; + } + + status = RtlStringCbLengthW( + (LPCWSTR) params.Parameters.Property.Value, + params.Parameters.Property.ValueCb, // maximum buffer size, including NULL termination + NULL // optional returned lenght, not used. + ); + if (!NT_SUCCESS(status)) + { + goto exit; + } + + status = RtlUnicodeStringInitEx(&symbolicLinkName, (LPCWSTR) params.Parameters.Property.Value, 0); + if (!NT_SUCCESS(status)) + { + goto exit; + } + + WDF_OBJECT_ATTRIBUTES_INIT(&ioTargetAttrib); + ioTargetAttrib.ParentObject = cirCtx->EndpointDevice; + status = WdfIoTargetCreate( + cirCtx->EndpointDevice, + &ioTargetAttrib, + &ioTarget + ); + if (!NT_SUCCESS(status)) + { + goto exit; + } + + WDF_IO_TARGET_OPEN_PARAMS_INIT_OPEN_BY_NAME( + &openParams, + &symbolicLinkName, + STANDARD_RIGHTS_ALL + ); + status = WdfIoTargetOpen( + ioTarget, + &openParams + ); + if (!NT_SUCCESS(status)) + { + WdfObjectDelete(ioTarget); + goto exit; + } + + if (cirCtx->WdfIoNotificationTarget) + { + WdfObjectDelete(cirCtx->WdfIoNotificationTarget); + cirCtx->WdfIoNotificationTarget = nullptr; + } + + cirCtx->WdfIoNotificationTarget = ioTarget; + ioTarget = nullptr; + } + break; + case KSPROPERTY_ACXCIRCUIT_SETNOTIFICATIONID: + { + if (params.Parameters.Property.Verb != AcxPropertyVerbSet) + { + status = STATUS_INVALID_DEVICE_REQUEST; + goto exit; + } + + if (params.Parameters.Property.ValueCb != sizeof(GUID)) + { + status = STATUS_INVALID_PARAMETER; + goto exit; + } + + cirCtx->PnpNotificationId = *((LPGUID) params.Parameters.Property.Value); + } + break; + case KSPROPERTY_ACXCIRCUIT_SETINSTANCEID: + { + if (params.Parameters.Property.Verb != AcxPropertyVerbSet) + { + status = STATUS_INVALID_DEVICE_REQUEST; + goto exit; + } + + if (params.Parameters.Property.ValueCb != sizeof(DWORD)) + { + status = STATUS_INVALID_PARAMETER; + goto exit; + } + + cirCtx->InstanceId = *((PDWORD) params.Parameters.Property.Value); + } + break; + } + } + + if (!NT_SUCCESS(status)) + { + DrvLogWarning(g_SDCAVXuLog, FLAG_INIT, L"SdcaXuR_EvtCircuitRequestPreprocess - failure, %!STATUS!", status); + } + } +exit: +#endif + + // + // Just give the request back to ACX. + // + (VOID)AcxCircuitDispatchAcxRequest((ACXCIRCUIT)Object, Request); +} + +PAGED_CODE_SEG +_Use_decl_annotations_ +VOID +SdcaXuR_EvtStreamRequestPreprocess( + _In_ ACXOBJECT Object, + _In_ ACXCONTEXT DriverContext, + _In_ WDFREQUEST Request + ) +/*++ + +Routine Description: + + This function is an example of a preprocess routine. + +--*/ +{ + PAGED_CODE(); + + UNREFERENCED_PARAMETER(DriverContext); + + ASSERT(Object != NULL); + ASSERT(DriverContext); + ASSERT(Request); + + + // + // Just give the request back to ACX. + // + (VOID)AcxStreamDispatchAcxRequest((ACXSTREAM)Object, Request); +} + +PAGED_CODE_SEG +NTSTATUS +SdcaXuR_SetPowerPolicy( + _In_ WDFDEVICE Device +) +{ + NTSTATUS status = STATUS_SUCCESS; + WDF_DEVICE_POWER_POLICY_IDLE_SETTINGS idleSettings; + //WDF_DEVICE_POWER_POLICY_WAKE_SETTINGS wakeSettings; + + PAGED_CODE(); + + // + // Init the idle policy structure. + // + //WDF_DEVICE_POWER_POLICY_IDLE_SETTINGS_INIT(&idleSettings, IdleCanWakeFromS0); + WDF_DEVICE_POWER_POLICY_IDLE_SETTINGS_INIT(&idleSettings, IdleCannotWakeFromS0); + idleSettings.IdleTimeout = IDLE_POWER_TIMEOUT; + idleSettings.IdleTimeoutType = SystemManagedIdleTimeoutWithHint; + + status = WdfDeviceAssignS0IdleSettings(Device, &idleSettings); + + return status; +} + +PAGED_CODE_SEG +NTSTATUS +SdcaXuR_EvtDevicePrepareHardware( + _In_ WDFDEVICE Device, + _In_ WDFCMRESLIST ResourceList, + _In_ WDFCMRESLIST ResourceListTranslated +) +/*++ + +Routine Description: + + In this callback, the driver does whatever is necessary to make the + hardware ready to use. + +Arguments: + + Device - handle to a device + +Return Value: + + NT status value + +--*/ +{ + NTSTATUS status = STATUS_SUCCESS; + + UNREFERENCED_PARAMETER(ResourceList); + UNREFERENCED_PARAMETER(ResourceListTranslated); + + PAGED_CODE(); + + PSDCAXU_RENDER_DEVICE_CONTEXT devCtx; + devCtx = GetRenderDeviceContext(Device); + ASSERT(devCtx != NULL); + + if (!devCtx->FirstTimePrepareHardware) + { + // + // This is a rebalance. Validate the circuit resources and + // if needed, delete and re-create the circuit. + // The sample driver doens't use resources, thus the existing + // circuits are kept. + // + return STATUS_SUCCESS; + } + + // + // Set child's power policy. + // + RETURN_NTSTATUS_IF_FAILED(SdcaXuR_SetPowerPolicy(Device)); + + // + // Add circuit to child's list. + // + RETURN_NTSTATUS_IF_FAILED(AcxDeviceAddCircuit(Device, devCtx->Circuit)); + + // + // Keep track this is not the first time this callback was called. + // + devCtx->FirstTimePrepareHardware = FALSE; + + return status; +} + +PAGED_CODE_SEG +NTSTATUS +SdcaXuR_EvtDeviceReleaseHardware( + _In_ WDFDEVICE Device, + _In_ WDFCMRESLIST ResourceListTranslated +) +/*++ + +Routine Description: + + In this callback, the driver releases the h/w resources allocated in the + prepare h/w callback. + +Arguments: + + Device - handle to a device + +Return Value: + + NT status value + +--*/ +{ + NTSTATUS status = STATUS_SUCCESS; + + UNREFERENCED_PARAMETER(Device); + UNREFERENCED_PARAMETER(ResourceListTranslated); + + PAGED_CODE(); + + PSDCAXU_RENDER_DEVICE_CONTEXT devCtx; + devCtx = GetRenderDeviceContext(Device); + ASSERT(devCtx != NULL); + UNREFERENCED_PARAMETER(devCtx); + + + return status; +} + +PAGED_CODE_SEG +VOID +SdcaXuR_EvtDeviceContextCleanup( + _In_ WDFOBJECT WdfDevice +) +/*++ + +Routine Description: + + In this callback, it cleans up render device context. + +Arguments: + + WdfDevice - WDF device object + +Return Value: + + NULL + +--*/ +{ + PAGED_CODE(); + UNREFERENCED_PARAMETER(WdfDevice); +} + +PAGED_CODE_SEG +NTSTATUS +SdcaXuR_EvtDeviceSelfManagedIoInit( + _In_ WDFDEVICE Device +) +/*++ + +Routine Description: + + In this callback, the driver does one-time init of self-managed I/O data. + +Arguments: + + Device - handle to a device + +Return Value: + + NT status value + +--*/ +{ + PAGED_CODE(); + + PSDCAXU_RENDER_DEVICE_CONTEXT devCtx; + devCtx = GetRenderDeviceContext(Device); + ASSERT(devCtx != NULL); + UNREFERENCED_PARAMETER(devCtx); + + return STATUS_SUCCESS; +} + +_Use_decl_annotations_ +#pragma code_seg("PAGE") +NTSTATUS +SdcaXu_EvtProcessCommand0( + _In_ ACXAUDIOMODULE AudioModule, + _In_ PVOID InBuffer, + _In_ ULONG InBufferCb, + _In_ PVOID OutBuffer, + _Inout_ PULONG OutBufferCb + ) +{ + BOOL fNewValue = FALSE; + PVOID currentValue = nullptr; + PVOID inBuffer = nullptr; + ULONG inBufferCb = 0; + PSDCAXU_AUDIOMODULE0_CONTEXT audioModuleCtx; + AUDIOMODULE_PARAMETER_INFO * parameterInfo = nullptr; + AUDIOMODULE_CUSTOM_COMMAND * command = nullptr; + + PAGED_CODE(); + + audioModuleCtx = GetSdcaXuAudioModule0Context(AudioModule); + RETURN_NTSTATUS_IF_TRUE(nullptr == audioModuleCtx, STATUS_INTERNAL_ERROR); + + // + // Basic parameter validation (module specific). + // + RETURN_NTSTATUS_IF_TRUE(InBuffer == nullptr || InBufferCb == 0, STATUS_INVALID_PARAMETER); + RETURN_NTSTATUS_IF_TRUE(InBufferCb < sizeof(AUDIOMODULE_CUSTOM_COMMAND), STATUS_INVALID_PARAMETER); + + command = (AUDIOMODULE_CUSTOM_COMMAND*)InBuffer; + + RETURN_NTSTATUS_IF_TRUE(command->ParameterId >= SIZEOF_ARRAY(AudioModule0_ParameterInfo), STATUS_INVALID_PARAMETER); + + // + // Validate the parameter referenced in the command. + // + switch (command->ParameterId) + { + case AudioModuleParameter1: + currentValue = &audioModuleCtx->Parameter1; + parameterInfo = &AudioModule0_ParameterInfo[AudioModuleParameter1]; + break; + case AudioModuleParameter2: + currentValue = &audioModuleCtx->Parameter2; + parameterInfo = &AudioModule0_ParameterInfo[AudioModuleParameter2]; + break; + default: + RETURN_NTSTATUS(STATUS_INVALID_PARAMETER); + } + + // + // Update input buffer ptr/size. + // + inBuffer = (PVOID)((ULONG_PTR)InBuffer + sizeof(AUDIOMODULE_CUSTOM_COMMAND)); + inBufferCb = InBufferCb - sizeof(AUDIOMODULE_CUSTOM_COMMAND); + + if (inBufferCb == 0) + { + inBuffer = NULL; + } + + RETURN_NTSTATUS_IF_FAILED(AudioModule_GenericHandler( + command->Verb, + command->ParameterId, + parameterInfo, + currentValue, + inBuffer, + inBufferCb, + OutBuffer, + OutBufferCb, + &fNewValue)); + + if (fNewValue && + (parameterInfo->Flags & AUDIOMODULE_PARAMETER_FLAG_CHANGE_NOTIFICATION)) + { + AUDIOMODULE_CUSTOM_NOTIFICATION customNotification = {0}; + + customNotification.Type = AudioModuleParameterChanged; + customNotification.ParameterChanged.ParameterId = command->ParameterId; + +#ifndef ACX_WORKAROUND_AGGREGATED_MODULE_NOTIFICATIONS + RETURN_NTSTATUS_IF_FAILED(AcxPnpEventGenerateEvent(audioModuleCtx->Event, &customNotification, (USHORT)sizeof(customNotification))); +#else + if (audioModuleCtx->Circuit == nullptr) + { + RETURN_NTSTATUS_IF_FAILED(AcxPnpEventGenerateEvent(audioModuleCtx->Event, &customNotification, (USHORT)sizeof(customNotification))); + } + else + { + PSDCAXU_RENDER_CIRCUIT_CONTEXT cirCtx; + + cirCtx = GetRenderCircuitContext(audioModuleCtx->Circuit); + + // AcxPnpEventGenerateEvent will target the wrong PnpNotificationId, InstanceId, and IoTarget due to a lack + // of acx framework support. Compose the PNP notification manually and send it. + USHORT sizeRequired = FIELD_OFFSET(TARGET_DEVICE_CUSTOM_NOTIFICATION, CustomDataBuffer) + sizeof(KSAUDIOMODULE_NOTIFICATION) + sizeof(customNotification); + PTARGET_DEVICE_CUSTOM_NOTIFICATION pCustomNotify = (PTARGET_DEVICE_CUSTOM_NOTIFICATION) new(POOL_FLAG_NON_PAGED, DRIVER_TAG) BYTE[sizeRequired]; + if (pCustomNotify != nullptr) + { + RtlZeroMemory(pCustomNotify, sizeRequired); + + pCustomNotify->NameBufferOffset = -1; + pCustomNotify->Version = 1; + pCustomNotify->Size = sizeRequired; + pCustomNotify->Event = KSNOTIFICATIONID_AudioModule; + + PKSAUDIOMODULE_NOTIFICATION pModuleNotify = (PKSAUDIOMODULE_NOTIFICATION) &(pCustomNotify->CustomDataBuffer[0]); + pModuleNotify->ProviderId.DeviceId = cirCtx->PnpNotificationId; + pModuleNotify->ProviderId.ClassId = AudioModule0Id; + pModuleNotify->ProviderId.InstanceId = (AUDIOMODULE_INSTANCE_ID(0,0) | cirCtx->InstanceId); + RtlCopyMemory(pModuleNotify + 1, &customNotification, sizeof(customNotification)); + + if (cirCtx->WdfIoNotificationTarget) + { + PDEVICE_OBJECT devObj = WdfIoTargetWdmGetTargetPhysicalDevice(cirCtx->WdfIoNotificationTarget); + + // if the notification target is invalidated, retrieving the physical device will fail and we can't send the event. + if (devObj) + { + IoReportTargetDeviceChangeAsynchronous(devObj, + pCustomNotify, + NULL, + NULL); + } + } + + delete[] pCustomNotify; + } + } +#endif + } + + return STATUS_SUCCESS; +} + +_Use_decl_annotations_ +#pragma code_seg("PAGE") +NTSTATUS +SdcaXu_EvtProcessCommand1( + _In_ ACXAUDIOMODULE AudioModule, + _In_ PVOID InBuffer, + _In_ ULONG InBufferCb, + _In_ PVOID OutBuffer, + _Inout_ PULONG OutBufferCb + ) +{ + BOOL fNewValue = FALSE; + PVOID currentValue = nullptr; + PVOID inBuffer = nullptr; + ULONG inBufferCb = 0; + PSDCAXU_AUDIOMODULE1_CONTEXT audioModuleCtx; + AUDIOMODULE_PARAMETER_INFO * parameterInfo = nullptr; + AUDIOMODULE_CUSTOM_COMMAND * command = nullptr; + + PAGED_CODE(); + + audioModuleCtx = GetSdcaXuAudioModule1Context(AudioModule); + RETURN_NTSTATUS_IF_TRUE(nullptr == audioModuleCtx, STATUS_INTERNAL_ERROR); + + // + // Basic parameter validation (module specific). + // + RETURN_NTSTATUS_IF_TRUE(InBuffer == nullptr || InBufferCb == 0, STATUS_INVALID_PARAMETER); + RETURN_NTSTATUS_IF_TRUE(InBufferCb < sizeof(AUDIOMODULE_CUSTOM_COMMAND), STATUS_INVALID_PARAMETER); + + command = (AUDIOMODULE_CUSTOM_COMMAND*)InBuffer; + + RETURN_NTSTATUS_IF_TRUE(command->ParameterId >= SIZEOF_ARRAY(AudioModule1_ParameterInfo), STATUS_INVALID_PARAMETER); + + // + // Validate the parameter referenced in the command. + // + switch (command->ParameterId) + { + case AudioModuleParameter1: + currentValue = &audioModuleCtx->Parameter1; + parameterInfo = &AudioModule1_ParameterInfo[AudioModuleParameter1]; + break; + case AudioModuleParameter2: + currentValue = &audioModuleCtx->Parameter2; + parameterInfo = &AudioModule1_ParameterInfo[AudioModuleParameter2]; + break; + case AudioModuleParameter3: + currentValue = &audioModuleCtx->Parameter3; + parameterInfo = &AudioModule1_ParameterInfo[AudioModuleParameter3]; + break; + default: + RETURN_NTSTATUS(STATUS_INVALID_PARAMETER); + } + + // + // Update input buffer ptr/size. + // + inBuffer = (PVOID)((ULONG_PTR)InBuffer + sizeof(AUDIOMODULE_CUSTOM_COMMAND)); + inBufferCb = InBufferCb - sizeof(AUDIOMODULE_CUSTOM_COMMAND); + + if (inBufferCb == 0) + { + inBuffer = nullptr; + } + + RETURN_NTSTATUS_IF_FAILED(AudioModule_GenericHandler( + command->Verb, + command->ParameterId, + parameterInfo, + currentValue, + inBuffer, + inBufferCb, + OutBuffer, + OutBufferCb, + &fNewValue)); + + if (fNewValue && + (parameterInfo->Flags & AUDIOMODULE_PARAMETER_FLAG_CHANGE_NOTIFICATION)) + { + AUDIOMODULE_CUSTOM_NOTIFICATION customNotification = {0}; + + customNotification.Type = AudioModuleParameterChanged; + customNotification.ParameterChanged.ParameterId = command->ParameterId; + +#ifndef ACX_WORKAROUND_AGGREGATED_MODULE_NOTIFICATIONS + RETURN_NTSTATUS_IF_FAILED(AcxPnpEventGenerateEvent(audioModuleCtx->Event, &customNotification, (USHORT)sizeof(customNotification))); +#else + if (audioModuleCtx->Circuit == nullptr) + { + RETURN_NTSTATUS_IF_FAILED(AcxPnpEventGenerateEvent(audioModuleCtx->Event, &customNotification, (USHORT)sizeof(customNotification))); + } + else + { + PSDCAXU_RENDER_CIRCUIT_CONTEXT cirCtx; + + cirCtx = GetRenderCircuitContext(audioModuleCtx->Circuit); + + // AcxPnpEventGenerateEvent will target the wrong PnpNotificationId, InstanceId, and IoTarget due to a lack + // of acx framework support. Compose the PNP notification manually and send it. + USHORT sizeRequired = FIELD_OFFSET(TARGET_DEVICE_CUSTOM_NOTIFICATION, CustomDataBuffer) + sizeof(KSAUDIOMODULE_NOTIFICATION) + sizeof(customNotification); + PTARGET_DEVICE_CUSTOM_NOTIFICATION pCustomNotify = (PTARGET_DEVICE_CUSTOM_NOTIFICATION) new(POOL_FLAG_NON_PAGED, DRIVER_TAG) BYTE[sizeRequired]; + if (pCustomNotify != nullptr) + { + RtlZeroMemory(pCustomNotify, sizeRequired); + + pCustomNotify->NameBufferOffset = -1; + pCustomNotify->Version = 1; + pCustomNotify->Size = sizeRequired; + pCustomNotify->Event = KSNOTIFICATIONID_AudioModule; + + PKSAUDIOMODULE_NOTIFICATION pModuleNotify = (PKSAUDIOMODULE_NOTIFICATION) &(pCustomNotify->CustomDataBuffer[0]); + pModuleNotify->ProviderId.DeviceId = cirCtx->PnpNotificationId; + pModuleNotify->ProviderId.ClassId = AudioModule1Id; + pModuleNotify->ProviderId.InstanceId = (AUDIOMODULE_INSTANCE_ID(0,0) | cirCtx->InstanceId); + RtlCopyMemory(pModuleNotify + 1, &customNotification, sizeof(customNotification)); + + if (cirCtx->WdfIoNotificationTarget) + { + PDEVICE_OBJECT devObj = WdfIoTargetWdmGetTargetPhysicalDevice(cirCtx->WdfIoNotificationTarget); + + // if the notification target is invalidated, retrieving the physical device will fail and we can't send the event. + if (devObj) + { + IoReportTargetDeviceChangeAsynchronous(devObj, + pCustomNotify, + NULL, + NULL); + } + } + + delete[] pCustomNotify; + } + } +#endif + } + + return STATUS_SUCCESS; +} + +_Use_decl_annotations_ +#pragma code_seg("PAGE") +NTSTATUS +SdcaXu_EvtProcessCommand2( + _In_ ACXAUDIOMODULE AudioModule, + _In_ PVOID InBuffer, + _In_ ULONG InBufferCb, + _In_ PVOID OutBuffer, + _Inout_ PULONG OutBufferCb + ) +{ + BOOL fNewValue = FALSE; + PVOID currentValue = nullptr; + PVOID inBuffer = nullptr; + ULONG inBufferCb = 0; + PSDCAXU_AUDIOMODULE2_CONTEXT audioModuleCtx; + AUDIOMODULE_PARAMETER_INFO * parameterInfo = nullptr; + AUDIOMODULE_CUSTOM_COMMAND * command = nullptr; + + PAGED_CODE(); + + audioModuleCtx = GetSdcaXuAudioModule2Context(AudioModule); + RETURN_NTSTATUS_IF_TRUE(nullptr == audioModuleCtx, STATUS_INTERNAL_ERROR); + + // + // Basic parameter validation (module specific). + // + RETURN_NTSTATUS_IF_TRUE(InBuffer == nullptr || InBufferCb == 0, STATUS_INVALID_PARAMETER); + RETURN_NTSTATUS_IF_TRUE(InBufferCb < sizeof(AUDIOMODULE_CUSTOM_COMMAND), STATUS_INVALID_PARAMETER); + + command = (AUDIOMODULE_CUSTOM_COMMAND*)InBuffer; + + RETURN_NTSTATUS_IF_TRUE(command->ParameterId >= SIZEOF_ARRAY(AudioModule2_ParameterInfo), STATUS_INVALID_PARAMETER); + + // + // Validate the parameter referenced in the command. + // + switch (command->ParameterId) + { + case AudioModuleParameter1: + currentValue = &audioModuleCtx->Parameter1; + parameterInfo = &AudioModule2_ParameterInfo[AudioModuleParameter1]; + break; + case AudioModuleParameter2: + currentValue = &audioModuleCtx->Parameter2; + parameterInfo = &AudioModule2_ParameterInfo[AudioModuleParameter2]; + break; + default: + RETURN_NTSTATUS(STATUS_INVALID_PARAMETER); + } + + // + // Update input buffer ptr/size. + // + inBuffer = (PVOID)((ULONG_PTR)InBuffer + sizeof(AUDIOMODULE_CUSTOM_COMMAND)); + inBufferCb = InBufferCb - sizeof(AUDIOMODULE_CUSTOM_COMMAND); + + if (inBufferCb == 0) + { + inBuffer = nullptr; + } + + RETURN_NTSTATUS_IF_FAILED(AudioModule_GenericHandler( + command->Verb, + command->ParameterId, + parameterInfo, + currentValue, + inBuffer, + inBufferCb, + OutBuffer, + OutBufferCb, + &fNewValue)); + + if (fNewValue && + (parameterInfo->Flags & AUDIOMODULE_PARAMETER_FLAG_CHANGE_NOTIFICATION)) + { + AUDIOMODULE_CUSTOM_NOTIFICATION customNotification = {0}; + + customNotification.Type = AudioModuleParameterChanged; + customNotification.ParameterChanged.ParameterId = command->ParameterId; + +#ifndef ACX_WORKAROUND_AGGREGATED_MODULE_NOTIFICATIONS + RETURN_NTSTATUS_IF_FAILED(AcxPnpEventGenerateEvent(audioModuleCtx->Event, &customNotification, (USHORT)sizeof(customNotification))); +#else + if (audioModuleCtx->Circuit == nullptr) + { + RETURN_NTSTATUS_IF_FAILED(AcxPnpEventGenerateEvent(audioModuleCtx->Event, &customNotification, (USHORT)sizeof(customNotification))); + } + else + { + PSDCAXU_RENDER_CIRCUIT_CONTEXT cirCtx; + + cirCtx = GetRenderCircuitContext(audioModuleCtx->Circuit); + + // AcxPnpEventGenerateEvent will target the wrong PnpNotificationId, InstanceId, and IoTarget due to a lack + // of acx framework support. Compose the PNP notification manually and send it. + USHORT sizeRequired = FIELD_OFFSET(TARGET_DEVICE_CUSTOM_NOTIFICATION, CustomDataBuffer) + sizeof(KSAUDIOMODULE_NOTIFICATION) + sizeof(customNotification); + PTARGET_DEVICE_CUSTOM_NOTIFICATION pCustomNotify = (PTARGET_DEVICE_CUSTOM_NOTIFICATION) new(POOL_FLAG_NON_PAGED, DRIVER_TAG) BYTE[sizeRequired]; + if (pCustomNotify != nullptr) + { + RtlZeroMemory(pCustomNotify, sizeRequired); + + pCustomNotify->NameBufferOffset = -1; + pCustomNotify->Version = 1; + pCustomNotify->Size = sizeRequired; + pCustomNotify->Event = KSNOTIFICATIONID_AudioModule; + + PKSAUDIOMODULE_NOTIFICATION pModuleNotify = (PKSAUDIOMODULE_NOTIFICATION) &(pCustomNotify->CustomDataBuffer[0]); + pModuleNotify->ProviderId.DeviceId = cirCtx->PnpNotificationId; + pModuleNotify->ProviderId.ClassId = AudioModule2Id; + pModuleNotify->ProviderId.InstanceId = (AUDIOMODULE_INSTANCE_ID(1,0) | cirCtx->InstanceId); + RtlCopyMemory(pModuleNotify + 1, &customNotification, sizeof(customNotification)); + + if (cirCtx->WdfIoNotificationTarget) + { + PDEVICE_OBJECT devObj = WdfIoTargetWdmGetTargetPhysicalDevice(cirCtx->WdfIoNotificationTarget); + + // if the notification target is invalidated, retrieving the physical device will fail and we can't send the event. + if (devObj) + { + IoReportTargetDeviceChangeAsynchronous(devObj, + pCustomNotify, + NULL, + NULL); + } + } + + delete[] pCustomNotify; + } + } +#endif + } + + return STATUS_SUCCESS; +} + +#pragma code_seg("PAGE") +NTSTATUS +SdcaXu_CreateCircuitModules( + _In_ WDFDEVICE Device, + _In_ ACXCIRCUIT Circuit + ) +/*++ + +Routine Description: + + This routine creates all of the audio module elements and adds them to the circuit + +Return Value: + + NT status value + +--*/ +{ + WDF_OBJECT_ATTRIBUTES attributes; + ACX_AUDIOMODULE_CALLBACKS audioModuleCallbacks; + ACX_AUDIOMODULE_CONFIG audioModuleCfg; + ACXAUDIOMODULE audioModuleElement; + PSDCAXU_AUDIOMODULE0_CONTEXT audioModule0Ctx; + PSDCAXU_AUDIOMODULE1_CONTEXT audioModule1Ctx; + PSDCAXU_AUDIOMODULE2_CONTEXT audioModule2Ctx; + ACX_PNPEVENT_CONFIG audioModuleEventCfg; + ACXPNPEVENT audioModuleEvent; + + PAGED_CODE(); + + // Now add audio modules to the circuit + // module 0 + + ACX_AUDIOMODULE_CALLBACKS_INIT(&audioModuleCallbacks); + audioModuleCallbacks.EvtAcxAudioModuleProcessCommand = SdcaXu_EvtProcessCommand0; + + ACX_AUDIOMODULE_CONFIG_INIT(&audioModuleCfg); + audioModuleCfg.Name = &AudioModule0Id; + audioModuleCfg.Descriptor.ClassId = AudioModule0Id; + audioModuleCfg.Descriptor.InstanceId = AUDIOMODULE_INSTANCE_ID(0,0); + audioModuleCfg.Descriptor.VersionMajor = AUDIOMODULE0_MAJOR; + audioModuleCfg.Descriptor.VersionMinor = AUDIOMODULE0_MINOR; + RETURN_NTSTATUS_IF_FAILED(RtlStringCchCopyNW(audioModuleCfg.Descriptor.Name, + ACX_AUDIOMODULE_MAX_NAME_CCH_SIZE, + AUDIOMODULE0DESCRIPTION, + wcslen(AUDIOMODULE0DESCRIPTION))); + + audioModuleCfg.Callbacks = &audioModuleCallbacks; + + + WDF_OBJECT_ATTRIBUTES_INIT_CONTEXT_TYPE(&attributes, SDCAXU_AUDIOMODULE0_CONTEXT); + attributes.ParentObject = Circuit; + + RETURN_NTSTATUS_IF_FAILED(AcxAudioModuleCreate(Circuit, &attributes, &audioModuleCfg, &audioModuleElement)); + + audioModule0Ctx = GetSdcaXuAudioModule0Context(audioModuleElement); + ASSERT(audioModule0Ctx); + + ACX_PNPEVENT_CONFIG_INIT(&audioModuleEventCfg); + + WDF_OBJECT_ATTRIBUTES_INIT_CONTEXT_TYPE(&attributes, SDCAXU_PNPEVENT_CONTEXT); + attributes.ParentObject = audioModuleElement; + RETURN_NTSTATUS_IF_FAILED(AcxPnpEventCreate(Device, audioModuleElement, &attributes, &audioModuleEventCfg, &audioModuleEvent)); + + audioModule0Ctx->Event = audioModuleEvent; + audioModule0Ctx->Circuit = Circuit; + + RETURN_NTSTATUS_IF_FAILED(AcxCircuitAddElements(Circuit, (ACXELEMENT *) &audioModuleElement, 1)); + + // module 1 + + ACX_AUDIOMODULE_CALLBACKS_INIT(&audioModuleCallbacks); + audioModuleCallbacks.EvtAcxAudioModuleProcessCommand = SdcaXu_EvtProcessCommand1; + + ACX_AUDIOMODULE_CONFIG_INIT(&audioModuleCfg); + audioModuleCfg.Name = &AudioModule1Id; + audioModuleCfg.Descriptor.ClassId = AudioModule1Id; + audioModuleCfg.Descriptor.InstanceId = AUDIOMODULE_INSTANCE_ID(0,0); + audioModuleCfg.Descriptor.VersionMajor = AUDIOMODULE1_MAJOR; + audioModuleCfg.Descriptor.VersionMinor = AUDIOMODULE1_MINOR; + RETURN_NTSTATUS_IF_FAILED(RtlStringCchCopyNW(audioModuleCfg.Descriptor.Name, + ACX_AUDIOMODULE_MAX_NAME_CCH_SIZE, + AUDIOMODULE1DESCRIPTION, + wcslen(AUDIOMODULE1DESCRIPTION))); + + audioModuleCfg.Callbacks = &audioModuleCallbacks; + + + WDF_OBJECT_ATTRIBUTES_INIT_CONTEXT_TYPE(&attributes, SDCAXU_AUDIOMODULE1_CONTEXT); + attributes.ParentObject = Circuit; + + RETURN_NTSTATUS_IF_FAILED(AcxAudioModuleCreate(Circuit, &attributes, &audioModuleCfg, &audioModuleElement)); + + audioModule1Ctx = GetSdcaXuAudioModule1Context(audioModuleElement); + ASSERT(audioModule1Ctx); + + ACX_PNPEVENT_CONFIG_INIT(&audioModuleEventCfg); + + WDF_OBJECT_ATTRIBUTES_INIT_CONTEXT_TYPE(&attributes, SDCAXU_PNPEVENT_CONTEXT); + attributes.ParentObject = audioModuleElement; + RETURN_NTSTATUS_IF_FAILED(AcxPnpEventCreate(Device, audioModuleElement, &attributes, &audioModuleEventCfg, &audioModuleEvent)); + + audioModule1Ctx->Event = audioModuleEvent; + audioModule1Ctx->Circuit = Circuit; + + RETURN_NTSTATUS_IF_FAILED(AcxCircuitAddElements(Circuit, (ACXELEMENT *) &audioModuleElement, 1)); + + // module 2 + + ACX_AUDIOMODULE_CALLBACKS_INIT(&audioModuleCallbacks); + audioModuleCallbacks.EvtAcxAudioModuleProcessCommand = SdcaXu_EvtProcessCommand2; + + ACX_AUDIOMODULE_CONFIG_INIT(&audioModuleCfg); + audioModuleCfg.Name = &AudioModule2Id; + audioModuleCfg.Descriptor.ClassId = AudioModule2Id; + audioModuleCfg.Descriptor.InstanceId = AUDIOMODULE_INSTANCE_ID(1,0); + audioModuleCfg.Descriptor.VersionMajor = AUDIOMODULE2_MAJOR; + audioModuleCfg.Descriptor.VersionMinor = AUDIOMODULE2_MINOR; + RETURN_NTSTATUS_IF_FAILED(RtlStringCchCopyNW(audioModuleCfg.Descriptor.Name, + ACX_AUDIOMODULE_MAX_NAME_CCH_SIZE, + AUDIOMODULE2DESCRIPTION, + wcslen(AUDIOMODULE2DESCRIPTION))); + + audioModuleCfg.Callbacks = &audioModuleCallbacks; + + WDF_OBJECT_ATTRIBUTES_INIT_CONTEXT_TYPE(&attributes, SDCAXU_AUDIOMODULE2_CONTEXT); + attributes.ParentObject = Circuit; + + RETURN_NTSTATUS_IF_FAILED(AcxAudioModuleCreate(Circuit, &attributes, &audioModuleCfg, &audioModuleElement)); + + audioModule2Ctx = GetSdcaXuAudioModule2Context(audioModuleElement); + ASSERT(audioModule2Ctx); + + ACX_PNPEVENT_CONFIG_INIT(&audioModuleEventCfg); + + WDF_OBJECT_ATTRIBUTES_INIT_CONTEXT_TYPE(&attributes, SDCAXU_PNPEVENT_CONTEXT); + attributes.ParentObject = audioModuleElement; + RETURN_NTSTATUS_IF_FAILED(AcxPnpEventCreate(Device, audioModuleElement, &attributes, &audioModuleEventCfg, &audioModuleEvent)); + + audioModule2Ctx->Event = audioModuleEvent; + audioModule2Ctx->Circuit = Circuit; + + RETURN_NTSTATUS_IF_FAILED(AcxCircuitAddElements(Circuit, (ACXELEMENT *) &audioModuleElement, 1)); + + return STATUS_SUCCESS; +} + +PAGED_CODE_SEG +NTSTATUS +SdcaXu_CreateRenderDevice( + _In_ WDFDEVICE Device, + _Out_ WDFDEVICE* RenderDevice +) +{ + NTSTATUS status = STATUS_SUCCESS; + WDFDEVICE renderDevice = NULL; + + PAGED_CODE(); + + auto exit = scope_exit([&status, &renderDevice]() { + if (!NT_SUCCESS(status)) + { + if (renderDevice != NULL) + { + WdfObjectDelete(renderDevice); + } + } + }); + + *RenderDevice = NULL; + + // + // Create a child audio device for this circuit. + // + PWDFDEVICE_INIT devInit = NULL; + devInit = WdfPdoInitAllocate(Device); + RETURN_NTSTATUS_IF_TRUE(NULL == devInit, STATUS_INSUFFICIENT_RESOURCES); + + auto devInit_free = scope_exit([&devInit, &status]() { + WdfDeviceInitFree(devInit); + }); + + // + // Provide DeviceID, HardwareIDs, CompatibleIDs and InstanceId + // + RETURN_NTSTATUS_IF_FAILED(WdfPdoInitAddHardwareID(devInit, &RenderHardwareId)); + + RETURN_NTSTATUS_IF_FAILED(WdfPdoInitAssignDeviceID(devInit, &RenderDeviceId)); + + RETURN_NTSTATUS_IF_FAILED(WdfPdoInitAddCompatibleID(devInit, &RenderCompatibleId)); + + RETURN_NTSTATUS_IF_FAILED(WdfPdoInitAssignInstanceID(devInit, &RenderInstanceId)); + + RETURN_NTSTATUS_IF_FAILED(WdfPdoInitAssignContainerID(devInit, &RenderContainerId)); + + + // + // You can call WdfPdoInitAddDeviceText multiple times, adding device + // text for multiple locales. When the system displays the text, it + // chooses the text that matches the current locale, if available. + // Otherwise it will use the string for the default locale. + // The driver can specify the driver's default locale by calling + // WdfPdoInitSetDefaultLocale. + // + RETURN_NTSTATUS_IF_FAILED(WdfPdoInitAddDeviceText(devInit, + &RenderDeviceDescription, + &RenderDeviceLocation, + 0x409)); + + WdfPdoInitSetDefaultLocale(devInit, 0x409); + + // + // Allow ACX to add any pre-requirement it needs on this device. + // + ACX_DEVICEINIT_CONFIG acxDevInitCfg; + ACX_DEVICEINIT_CONFIG_INIT(&acxDevInitCfg); + acxDevInitCfg.Flags |= AcxDeviceInitConfigRawDevice; + RETURN_NTSTATUS_IF_FAILED(AcxDeviceInitInitialize(devInit, &acxDevInitCfg)); + + // + // Initialize the pnpPowerCallbacks structure. Callback events for PNP + // and Power are specified here. If you don't supply any callbacks, + // the Framework will take appropriate default actions based on whether + // DeviceInit is initialized to be an FDO, a PDO or a filter device + // object. + // + WDF_PNPPOWER_EVENT_CALLBACKS pnpPowerCallbacks; + WDF_PNPPOWER_EVENT_CALLBACKS_INIT(&pnpPowerCallbacks); + pnpPowerCallbacks.EvtDevicePrepareHardware = SdcaXuR_EvtDevicePrepareHardware; + pnpPowerCallbacks.EvtDeviceReleaseHardware = SdcaXuR_EvtDeviceReleaseHardware; + pnpPowerCallbacks.EvtDeviceSelfManagedIoInit = SdcaXuR_EvtDeviceSelfManagedIoInit; + WdfDeviceInitSetPnpPowerEventCallbacks(devInit, &pnpPowerCallbacks); + + // + // Specify a context for this render device. + // + WDF_OBJECT_ATTRIBUTES attributes; + WDF_OBJECT_ATTRIBUTES_INIT_CONTEXT_TYPE(&attributes, SDCAXU_RENDER_DEVICE_CONTEXT); + attributes.EvtCleanupCallback = SdcaXuR_EvtDeviceContextCleanup; + attributes.ExecutionLevel = WdfExecutionLevelPassive; + RETURN_NTSTATUS_IF_FAILED(WdfDeviceCreate(&devInit, &attributes, &renderDevice)); + + // + // devInit attached to device, no need to free + // + devInit_free.release(); + + // + // Tell the framework to set the NoDisplayInUI in the DeviceCaps so + // that the device does not show up in Device Manager. + // + WDF_DEVICE_PNP_CAPABILITIES pnpCaps; + WDF_DEVICE_PNP_CAPABILITIES_INIT(&pnpCaps); + pnpCaps.NoDisplayInUI = WdfTrue; + WdfDeviceSetPnpCapabilities(renderDevice, &pnpCaps); + + // + // Init render's device context. + // + PSDCAXU_RENDER_DEVICE_CONTEXT devCtx; + devCtx = GetRenderDeviceContext(renderDevice); + ASSERT(devCtx != NULL); + UNREFERENCED_PARAMETER(devCtx); + + // + // Allow ACX to add any post-requirement it needs on this device. + // + ACX_DEVICE_CONFIG devCfg; + ACX_DEVICE_CONFIG_INIT(&devCfg); + RETURN_NTSTATUS_IF_FAILED(AcxDeviceInitialize(renderDevice, &devCfg)); + + // + // Set output value. + // + *RenderDevice = renderDevice; + + return status; +} + +PAGED_CODE_SEG +NTSTATUS +SdcaXu_AddDynamicRender( + _In_ WDFDEVICE Device, + _In_ PSDCAXU_ACX_CIRCUIT_CONFIG CircuitConfig +) +{ + NTSTATUS status = STATUS_SUCCESS; + + PAGED_CODE(); + + // + // Create a device to associated with this circuit. + // + WDFDEVICE renderDevice = NULL; + RETURN_NTSTATUS_IF_FAILED(SdcaXu_CreateRenderDevice(Device, &renderDevice)); + auto deviceFree = scope_exit([&renderDevice]() { + WdfObjectDelete(renderDevice); + }); + + ASSERT(renderDevice); + PSDCAXU_RENDER_DEVICE_CONTEXT renderDevCtx; + renderDevCtx = GetRenderDeviceContext(renderDevice); + ASSERT(renderDevCtx); + + // + // Create a render circuit associated with this child device. + // + ACXCIRCUIT renderCircuit = NULL; + RETURN_NTSTATUS_IF_FAILED(SdcaXu_CreateRenderCircuit(renderDevice, CircuitConfig, &renderCircuit)); + + renderDevCtx->Circuit = renderCircuit; + renderDevCtx->FirstTimePrepareHardware = TRUE; + + RETURN_NTSTATUS_IF_FAILED(SdcaXu_CreateCircuitModules(Device, renderCircuit)); + + // + // Add circuit to device's dynamic circuit device list. + // + RETURN_NTSTATUS_IF_FAILED(AcxDeviceAddCircuitDevice(Device, renderDevice)); + + // Successfully created circuit for dynamic deivce + // Do not delete + deviceFree.release(); + + PSDCAXU_DEVICE_CONTEXT devCtx = GetSdcaXuDeviceContext(Device); + for (ULONG i = 0; i < ARRAYSIZE(devCtx->EndpointDevices); ++i) + { + if (devCtx->EndpointDevices[i].CircuitDevice == nullptr) + { + DrvLogInfo(g_SDCAVXuLog, FLAG_DDI, L"XU Device %p adding render circuit device %p with component ID %!GUID! and Uri %ls", + Device, renderDevice, &CircuitConfig->ComponentID, + CircuitConfig->ComponentUri.Buffer ? CircuitConfig->ComponentUri.Buffer : L""); + devCtx->EndpointDevices[i].CircuitDevice = renderDevice; + devCtx->EndpointDevices[i].CircuitId = CircuitConfig->ComponentID; + if (CircuitConfig->ComponentUri.Length > 0) + { + USHORT cbAlloc = CircuitConfig->ComponentUri.Length + sizeof(WCHAR); + // protect against overflow + if (CircuitConfig->ComponentUri.Length % 2 != 0 || + cbAlloc < CircuitConfig->ComponentUri.Length) + { + RETURN_NTSTATUS_IF_FAILED(STATUS_INVALID_PARAMETER); + } + + PWCHAR circuitUri = (PWCHAR)ExAllocatePool2(POOL_FLAG_NON_PAGED, cbAlloc, DRIVER_TAG); + if (!circuitUri) + { + RETURN_NTSTATUS_IF_FAILED(STATUS_INSUFFICIENT_RESOURCES); + } + + devCtx->EndpointDevices[i].CircuitUri.Buffer = circuitUri; + devCtx->EndpointDevices[i].CircuitUri.MaximumLength = cbAlloc; + devCtx->EndpointDevices[i].CircuitUri.Length = 0; + RtlCopyUnicodeString(&devCtx->EndpointDevices[i].CircuitUri, &CircuitConfig->ComponentUri); + } + break; + } + } + + return status; +} + +// {3CE41646-9BF2-4A9E-B851-D711CAE9AEA8} +DEFINE_GUID(SDCAVADPropsetId, + 0x3ce41646, 0x9bf2, 0x4a9e, 0xb8, 0x51, 0xd7, 0x11, 0xca, 0xe9, 0xae, 0xa8); + +typedef enum { + SDCAVAD_PROPERTY_TEST1, + SDCAVAD_PROPERTY_TEST2, + SDCAVAD_PROPERTY_TEST3, + SDCAVAD_PROPERTY_TEST4, + SDCAVAD_PROPERTY_TEST5, + SDCAVAD_PROPERTY_TEST6, +} SDCAVAD_Properties; + +PAGED_CODE_SEG +NTSTATUS +SdcaXu_SDCAVADPropertyTest3( + _Inout_ PVOID pValue, + _In_ ULONG ValueCb, + _Out_ PULONG ValueCbOut +) +{ + PAGED_CODE(); + + UNREFERENCED_PARAMETER(pValue); + UNREFERENCED_PARAMETER(ValueCb); + + NTSTATUS status = STATUS_SUCCESS; + + DrvLogInfo(g_SDCAVXuLog, FLAG_STREAM, L"SDCAVXu: SDCAVAD_PROPERTY_TEST3"); + + *ValueCbOut = 0; + + return status; +} + +PAGED_CODE_SEG +NTSTATUS +SdcaXu_SDCAVADPropertyTest4( + _Inout_ PVOID pValue, + _In_ ULONG ValueCb, + _Out_ PULONG ValueCbOut +) +{ + PAGED_CODE(); + + UNREFERENCED_PARAMETER(ValueCb); + + NTSTATUS status = STATUS_SUCCESS; + + DrvLogInfo(g_SDCAVXuLog, FLAG_STREAM, L"SDCAVXu: SDCAVAD_PROPERTY_TEST4"); + + *((PULONG)pValue) = 11; + *ValueCbOut = sizeof(ULONG); + + return status; +} + +PAGED_CODE_SEG +NTSTATUS +SdcaXu_SDCAVADPropertyTest5( + _Inout_ PVOID pValue, + _In_ ULONG ValueCb, + _Out_ PULONG ValueCbOut +) +{ + PAGED_CODE(); + + UNREFERENCED_PARAMETER(pValue); + UNREFERENCED_PARAMETER(ValueCb); + + NTSTATUS status = STATUS_SUCCESS; + + DrvLogInfo(g_SDCAVXuLog, FLAG_STREAM, L"SDCAVXu: SDCAVAD_PROPERTY_TEST5"); + + *ValueCbOut = 0; + + return status; +} + +PAGED_CODE_SEG +NTSTATUS +SdcaXu_SDCAVADPropertyTest6( + _Inout_ PVOID pValue, + _In_ ULONG ValueCb, + _Out_ PULONG ValueCbOut +) +{ + PAGED_CODE(); + + UNREFERENCED_PARAMETER(ValueCb); + + NTSTATUS status = STATUS_SUCCESS; + + DrvLogInfo(g_SDCAVXuLog, FLAG_STREAM, L"SDCAVXu: SDCAVAD_PROPERTY_TEST6"); + + *((PULONG)pValue) = 13; + *ValueCbOut = sizeof(ULONG); + + return status; +} + +PAGED_CODE_SEG +VOID +SdcaXu_EvtPropertyCallback( + _In_ WDFOBJECT Object, + _In_ WDFREQUEST Request +) +{ + UNREFERENCED_PARAMETER(Object); + + PAGED_CODE(); + + ACX_REQUEST_PARAMETERS params; + ACX_REQUEST_PARAMETERS_INIT(¶ms); + + AcxRequestGetParameters(Request, ¶ms); + + NTSTATUS status = STATUS_SUCCESS; + PVOID Value = params.Parameters.Property.Value; + ULONG ValueCb = params.Parameters.Property.ValueCb; + ULONG ValueCbOut = 0; + + if (IsEqualGUID(params.Parameters.Property.Set, SDCAVADPropsetId)) + { + switch (params.Parameters.Property.Id) + { + case SDCAVAD_PROPERTY_TEST3: + status = SdcaXu_SDCAVADPropertyTest3(Value, ValueCb, &ValueCbOut); + break; + case SDCAVAD_PROPERTY_TEST4: + status = SdcaXu_SDCAVADPropertyTest4(Value, ValueCb, &ValueCbOut); + break; + case SDCAVAD_PROPERTY_TEST5: + status = SdcaXu_SDCAVADPropertyTest5(Value, ValueCb, &ValueCbOut); + break; + case SDCAVAD_PROPERTY_TEST6: + status = SdcaXu_SDCAVADPropertyTest6(Value, ValueCb, &ValueCbOut); + break; + default: + break; + } + } + + WdfRequestCompleteWithInformation(Request, status, ValueCbOut); +} + +static ACX_PROPERTY_ITEM CircuitProperties[] = +{ + { + &SDCAVADPropsetId, + SDCAVAD_PROPERTY_TEST3, + ACX_PROPERTY_ITEM_FLAG_SET, + SdcaXu_EvtPropertyCallback + }, + { + &SDCAVADPropsetId, + SDCAVAD_PROPERTY_TEST4, + ACX_PROPERTY_ITEM_FLAG_GET, + SdcaXu_EvtPropertyCallback + }, + { + &SDCAVADPropsetId, + SDCAVAD_PROPERTY_TEST5, + ACX_PROPERTY_ITEM_FLAG_SET, + SdcaXu_EvtPropertyCallback + }, + { + &SDCAVADPropsetId, + SDCAVAD_PROPERTY_TEST6, + ACX_PROPERTY_ITEM_FLAG_GET, + SdcaXu_EvtPropertyCallback + }, +}; + +PAGED_CODE_SEG +NTSTATUS +SdcaXu_CreateRenderCircuit( + _In_ WDFDEVICE Device, + _In_ PSDCAXU_ACX_CIRCUIT_CONFIG CircuitConfig, + _Out_ ACXCIRCUIT *Circuit +) +/*++ + +Routine Description: + + This routine builds the SdcaXu render circuit. + +Return Value: + + NT status value + +--*/ +{ + PAGED_CODE(); + + // + // Get a CircuitInit structure. + // + PACXCIRCUIT_INIT circuitInit = NULL; + circuitInit = AcxCircuitInitAllocate(Device); + RETURN_NTSTATUS_IF_TRUE(NULL == circuitInit, STATUS_MEMORY_NOT_ALLOCATED); + auto circuitInit_free = scope_exit([&circuitInit]() { + AcxCircuitInitFree(circuitInit); + }); + + // + // Init output value. + // + *Circuit = NULL; + + // + // Copy Circuit configuration + // + PSDCAXU_ACX_CIRCUIT_CONFIG pCircuitConfig = NULL; + pCircuitConfig = (PSDCAXU_ACX_CIRCUIT_CONFIG)ExAllocatePool2(POOL_FLAG_NON_PAGED, + CircuitConfig->cbSize, + DRIVER_TAG); + RETURN_NTSTATUS_IF_TRUE(NULL == pCircuitConfig, STATUS_MEMORY_NOT_ALLOCATED); + auto circuitConfig_free = scope_exit([&pCircuitConfig]() { + ExFreePoolWithTag(pCircuitConfig, DRIVER_TAG); + }); + + RtlCopyMemory(pCircuitConfig, CircuitConfig, CircuitConfig->cbSize); + + // Remap UNICODE_STRING.Buffer + // buffer for unicode string begins immediately after SdcaXuAcxCircuitConfig + RETURN_NTSTATUS_IF_TRUE_MSG(pCircuitConfig->cbSize < (sizeof(SDCAXU_ACX_CIRCUIT_CONFIG) + pCircuitConfig->CircuitName.MaximumLength), + STATUS_INVALID_PARAMETER, L"CircuitConfig->cbSize = %d Required = %d", + pCircuitConfig->cbSize, + (int)(sizeof(SDCAXU_ACX_CIRCUIT_CONFIG) + pCircuitConfig->CircuitName.MaximumLength)); + + pCircuitConfig->CircuitName.Buffer = (PWCH)(pCircuitConfig + 1); + + // + // Create a circuit. + // + + // + // Add circuit identifiers. + // + if (!IsEqualGUID(pCircuitConfig->ComponentID, GUID_NULL)) + { + AcxCircuitInitSetComponentId(circuitInit, &pCircuitConfig->ComponentID); + } + + RETURN_NTSTATUS_IF_FAILED(AcxCircuitInitAssignComponentUri(circuitInit, &pCircuitConfig->ComponentUri)); + + RETURN_NTSTATUS_IF_FAILED(AcxCircuitInitAssignName(circuitInit, &pCircuitConfig->CircuitName)); + + // + // Add circuit type. + // + AcxCircuitInitSetCircuitType(circuitInit, AcxCircuitTypeRender); + + // + // Assign the circuit's pnp-power callbacks. + // + { + ACX_CIRCUIT_PNPPOWER_CALLBACKS powerCallbacks; + ACX_CIRCUIT_PNPPOWER_CALLBACKS_INIT(&powerCallbacks); + powerCallbacks.EvtAcxCircuitPowerUp = SdcaXuR_EvtCircuitPowerUp; + powerCallbacks.EvtAcxCircuitPowerDown = SdcaXuR_EvtCircuitPowerDown; + AcxCircuitInitSetAcxCircuitPnpPowerCallbacks(circuitInit, &powerCallbacks); + } + + // + // Set circuit-callbacks. + // + RETURN_NTSTATUS_IF_FAILED(AcxCircuitInitAssignAcxRequestPreprocessCallback( + circuitInit, + SdcaXuR_EvtCircuitRequestPreprocess, + (ACXCONTEXT)AcxRequestTypeAny, // dbg only + AcxRequestTypeAny, + NULL, + AcxItemIdNone)); + + RETURN_NTSTATUS_IF_FAILED(AcxCircuitInitAssignAcxCreateStreamCallback( + circuitInit, + SdcaXuR_EvtCircuitCreateStream)); + + // + // Disable default Stream Bridge handling in ACX + // Create stream handler will add Stream Bridge + // to support Object-bag forwarding + // + AcxCircuitInitDisableDefaultStreamBridgeHandling(circuitInit); + + // + // Add properties, events and methods. + // + RETURN_NTSTATUS_IF_FAILED(AcxCircuitInitAssignProperties(circuitInit, + CircuitProperties, + SIZEOF_ARRAY(CircuitProperties))); + + // + // Create the circuit. + // + WDF_OBJECT_ATTRIBUTES attributes; + ACXCIRCUIT circuit; + WDF_OBJECT_ATTRIBUTES_INIT_CONTEXT_TYPE(&attributes, SDCAXU_RENDER_CIRCUIT_CONTEXT); + attributes.EvtCleanupCallback = SdcaXuR_EvtCircuitContextCleanup; + RETURN_NTSTATUS_IF_FAILED(AcxCircuitCreate(Device, &attributes, &circuitInit, &circuit)); + + // circuitInit is now associated with circuit and will be managed with + // circuit lifetime. + circuitInit_free.release(); + + SDCAXU_RENDER_CIRCUIT_CONTEXT *circuitCtx; + ASSERT(circuit != NULL); + circuitCtx = GetRenderCircuitContext(circuit); + ASSERT(circuitCtx); + +#ifdef ACX_WORKAROUND_AGGREGATED_MODULE_NOTIFICATIONS + // cache the parent device for later, unreferenced since + // this is the parent + circuitCtx->EndpointDevice = Device; +#endif + + circuitCtx->CircuitConfig = pCircuitConfig; + circuitConfig_free.release(); + + // + // Post circuit creation initialization. + // + + // + // Add two custom circuit elements. Note that driver doesn't need to + // perform this step if it doesn't want to expose any circuit elements. + // + + // + // Create 1st custom circuit-element. + // + ACX_ELEMENT_CONFIG elementCfg; + ACX_ELEMENT_CONFIG_INIT(&elementCfg); + WDF_OBJECT_ATTRIBUTES_INIT_CONTEXT_TYPE(&attributes, SDCAXU_ELEMENT_CONTEXT); + attributes.ParentObject = circuit; + + const int numElements = 2; + ACXELEMENT elements[numElements] = { 0 }; + RETURN_NTSTATUS_IF_FAILED(AcxElementCreate(circuit, &attributes, &elementCfg, &elements[0])); + + ASSERT(elements[0] != NULL); + SDCAXU_ELEMENT_CONTEXT* elementCtx; + elementCtx = GetSdcaXuElementContext(elements[0]); + ASSERT(elementCtx); + UNREFERENCED_PARAMETER(elementCtx); + + // + // Create 2nd custom circuit-element. + // + ACX_ELEMENT_CONFIG_INIT(&elementCfg); + WDF_OBJECT_ATTRIBUTES_INIT_CONTEXT_TYPE(&attributes, SDCAXU_ELEMENT_CONTEXT); + attributes.ParentObject = circuit; + + RETURN_NTSTATUS_IF_FAILED(AcxElementCreate(circuit, &attributes, &elementCfg, &elements[1])); + + ASSERT(elements[1] != NULL); + elementCtx = GetSdcaXuElementContext(elements[1]); + ASSERT(elementCtx); + UNREFERENCED_PARAMETER(elementCtx); + + // + // Add the circuit elements + // + RETURN_NTSTATUS_IF_FAILED(AcxCircuitAddElements(circuit, elements, SIZEOF_ARRAY(elements))); + + // + // Create render pin. AcxCircuit creates the other pin by default. + // + + ACX_PIN_CALLBACKS pinCallbacks; + ACX_PIN_CALLBACKS_INIT(&pinCallbacks); + pinCallbacks.EvtAcxPinSetDataFormat = SdcaXuR_EvtAcxPinSetDataFormat; + + ACX_PIN_CONFIG pinCfg; + ACX_PIN_CONFIG_INIT(&pinCfg); + pinCfg.Type = AcxPinTypeSink; + pinCfg.Communication = AcxPinCommunicationNone; + pinCfg.Category = &KSCATEGORY_AUDIO; + pinCfg.PinCallbacks = &pinCallbacks; + + WDF_OBJECT_ATTRIBUTES_INIT_CONTEXT_TYPE(&attributes, SDCAXU_PIN_CONTEXT); + attributes.EvtCleanupCallback = SdcaXuR_EvtPinContextCleanup; + attributes.ParentObject = circuit; + + ACXPIN pin; + RETURN_NTSTATUS_IF_FAILED(AcxPinCreate(circuit, &attributes, &pinCfg, &pin)); + + ASSERT(pin != NULL); + SDCAXU_PIN_CONTEXT* pinCtx; + pinCtx = GetSdcaXuPinContext(pin); + ASSERT(pinCtx); + + // When the downstream pin connects to the Class driver, we'll + // copy formats from the Class driver (instead of hardcoding + // formats here) + + // + // Add render pin, using default pin id (0) + // + RETURN_NTSTATUS_IF_FAILED(AcxCircuitAddPins(circuit, &pin, 1)); + + /////////////////////////////////////////////////////////// + // + // Create bridge pin. AcxCircuit creates the other pin by default. + // + ACX_PIN_CALLBACKS_INIT(&pinCallbacks); + pinCallbacks.EvtAcxPinConnected = SdcaXu_EvtPinConnected; + + ACX_PIN_CONFIG_INIT(&pinCfg); + pinCfg.Type = AcxPinTypeSource; + pinCfg.Communication = AcxPinCommunicationNone; + pinCfg.Category = &KSCATEGORY_AUDIO; + pinCfg.PinCallbacks = &pinCallbacks; + + WDF_OBJECT_ATTRIBUTES_INIT_CONTEXT_TYPE(&attributes, SDCAXU_PIN_CONTEXT); + attributes.EvtCleanupCallback = SdcaXuR_EvtPinContextCleanup; + attributes.ParentObject = circuit; + + pin = NULL; + RETURN_NTSTATUS_IF_FAILED(AcxPinCreate(circuit, &attributes, &pinCfg, &pin)); + + ASSERT(pin != NULL); + pinCtx = GetSdcaXuPinContext(pin); + ASSERT(pinCtx); + + // + // Add brige pin, using default pin id (1) + // + RETURN_NTSTATUS_IF_FAILED(AcxCircuitAddPins(circuit, &pin, 1)); + + // + // Add a stream bridge to the bridge pin to propagate the stream obj-bags. + // + { + PCGUID inModes[] = + { + &NULL_GUID, // Match every mode. + }; + + WDF_OBJECT_ATTRIBUTES_INIT(&attributes); + attributes.ParentObject = pin; + + ACX_STREAM_BRIDGE_CONFIG streamBridgeConfig; + ACX_STREAM_BRIDGE_CONFIG_INIT(&streamBridgeConfig); + + streamBridgeConfig.Flags |= AcxStreamBridgeForwardInStreamVarArguments; + streamBridgeConfig.InModesCount = ARRAYSIZE(inModes); + streamBridgeConfig.InModes = inModes; + streamBridgeConfig.OutMode = &NULL_GUID; // Use the MODE associated the in-stream. + + ACXSTREAMBRIDGE streamBridge = NULL; + RETURN_NTSTATUS_IF_FAILED(AcxStreamBridgeCreate(circuit, &attributes, &streamBridgeConfig, &streamBridge)); + + RETURN_NTSTATUS_IF_FAILED(AcxPinAddStreamBridges(pin, &streamBridge, 1)); + } + + // + // Explicitly connect the circuit/elements. Note that driver doens't + // need to perform this step when circuit/elements are connected in the + // same order as they were added to the circuit. By default ACX connects + // the elements starting from the sink circuit pin and ending with the + // source circuit pin for both render and capture devices. + // + // circuit.pin[default_sink] -> 1st element.pin[default_in] + // 1st element.pin[default_out] -> 2nd element.pin[default_in] + // 2nd element.pin[default_out] -> circuit.pin[default_source] + // + const int numConnections = numElements + 1; + ACX_CONNECTION connections[numConnections]; + ACX_CONNECTION_INIT(&connections[0], circuit, elements[0]); + ACX_CONNECTION_INIT(&connections[1], elements[0], elements[1]); + ACX_CONNECTION_INIT(&connections[2], elements[1], circuit); + + // + // Add the connections linking circuit to elements. + // + RETURN_NTSTATUS_IF_FAILED(AcxCircuitAddConnections(circuit, connections, SIZEOF_ARRAY(connections))); + + // + // Set output value. + // + *Circuit = circuit; + + return STATUS_SUCCESS; +} + +#pragma code_seg() +_Use_decl_annotations_ +NTSTATUS +SdcaXuR_EvtCircuitPowerUp ( + _In_ WDFDEVICE Device, + _In_ ACXCIRCUIT Circuit, + _In_ WDF_POWER_DEVICE_STATE PreviousState + ) +{ + // Do not page out. + + UNREFERENCED_PARAMETER(Device); + UNREFERENCED_PARAMETER(Circuit); + UNREFERENCED_PARAMETER(PreviousState); + + return STATUS_SUCCESS; +} + +PAGED_CODE_SEG +_Use_decl_annotations_ +NTSTATUS +SdcaXuR_EvtCircuitPowerDown ( + _In_ WDFDEVICE Device, + _In_ ACXCIRCUIT Circuit, + _In_ WDF_POWER_DEVICE_STATE TargetState + ) +{ + PAGED_CODE(); + + UNREFERENCED_PARAMETER(Device); + UNREFERENCED_PARAMETER(Circuit); + UNREFERENCED_PARAMETER(TargetState); + + return STATUS_SUCCESS; +} + + +#pragma code_seg("PAGE") +NTSTATUS +SdcaXu_CreateStreamModules( + _In_ WDFDEVICE Device, + _In_ ACXSTREAM Stream + ) +/*++ + +Routine Description: + + This routine creates all of the audio module elements and adds them to the stream + +Return Value: + + NT status value + +--*/ +{ + WDF_OBJECT_ATTRIBUTES attributes; + ACX_AUDIOMODULE_CALLBACKS audioModuleCallbacks; + ACX_AUDIOMODULE_CONFIG audioModuleCfg; + ACXAUDIOMODULE audioModuleElement; + PSDCAXU_AUDIOMODULE0_CONTEXT audioModule0Ctx; + PSDCAXU_AUDIOMODULE1_CONTEXT audioModule1Ctx; + PSDCAXU_AUDIOMODULE2_CONTEXT audioModule2Ctx; + ACX_PNPEVENT_CONFIG audioModuleEventCfg; + ACXPNPEVENT audioModuleEvent; + + PAGED_CODE(); + + // Now add audio modules to the circuit + // module 0 + // for simplicity of the example, we implement the same modules on the stream as is + // on the circuit + ACX_AUDIOMODULE_CALLBACKS_INIT(&audioModuleCallbacks); + audioModuleCallbacks.EvtAcxAudioModuleProcessCommand = SdcaXu_EvtProcessCommand0; + + ACX_AUDIOMODULE_CONFIG_INIT(&audioModuleCfg); + audioModuleCfg.Name = &AudioModule0Id; + audioModuleCfg.Descriptor.ClassId = AudioModule0Id; + audioModuleCfg.Descriptor.InstanceId = AUDIOMODULE_INSTANCE_ID(1,0); + audioModuleCfg.Descriptor.VersionMajor = AUDIOMODULE0_MAJOR; + audioModuleCfg.Descriptor.VersionMinor = AUDIOMODULE0_MINOR; + RETURN_NTSTATUS_IF_FAILED(RtlStringCchCopyNW(audioModuleCfg.Descriptor.Name, + ACX_AUDIOMODULE_MAX_NAME_CCH_SIZE, + AUDIOMODULE0DESCRIPTION, + wcslen(AUDIOMODULE0DESCRIPTION))); + + audioModuleCfg.Callbacks = &audioModuleCallbacks; + + + WDF_OBJECT_ATTRIBUTES_INIT_CONTEXT_TYPE(&attributes, SDCAXU_AUDIOMODULE0_CONTEXT); + attributes.ParentObject = Stream; + + RETURN_NTSTATUS_IF_FAILED(AcxAudioModuleCreate(Stream, &attributes, &audioModuleCfg, &audioModuleElement)); + + audioModule0Ctx = GetSdcaXuAudioModule0Context(audioModuleElement); + ASSERT(audioModule0Ctx); + + ACX_PNPEVENT_CONFIG_INIT(&audioModuleEventCfg); + + WDF_OBJECT_ATTRIBUTES_INIT_CONTEXT_TYPE(&attributes, SDCAXU_PNPEVENT_CONTEXT); + attributes.ParentObject = audioModuleElement; + RETURN_NTSTATUS_IF_FAILED(AcxPnpEventCreate(Device, audioModuleElement, &attributes, &audioModuleEventCfg, &audioModuleEvent)); + + audioModule0Ctx->Event = audioModuleEvent; + + RETURN_NTSTATUS_IF_FAILED(AcxStreamAddElements(Stream, (ACXELEMENT *) &audioModuleElement, 1)); + + // module 1 + + ACX_AUDIOMODULE_CALLBACKS_INIT(&audioModuleCallbacks); + audioModuleCallbacks.EvtAcxAudioModuleProcessCommand = SdcaXu_EvtProcessCommand1; + + ACX_AUDIOMODULE_CONFIG_INIT(&audioModuleCfg); + audioModuleCfg.Name = &AudioModule1Id; + audioModuleCfg.Descriptor.ClassId = AudioModule1Id; + audioModuleCfg.Descriptor.InstanceId = AUDIOMODULE_INSTANCE_ID(1,0); + audioModuleCfg.Descriptor.VersionMajor = AUDIOMODULE1_MAJOR; + audioModuleCfg.Descriptor.VersionMinor = AUDIOMODULE1_MINOR; + RETURN_NTSTATUS_IF_FAILED(RtlStringCchCopyNW(audioModuleCfg.Descriptor.Name, + ACX_AUDIOMODULE_MAX_NAME_CCH_SIZE, + AUDIOMODULE1DESCRIPTION, + wcslen(AUDIOMODULE1DESCRIPTION))); + + audioModuleCfg.Callbacks = &audioModuleCallbacks; + + + WDF_OBJECT_ATTRIBUTES_INIT_CONTEXT_TYPE(&attributes, SDCAXU_AUDIOMODULE1_CONTEXT); + attributes.ParentObject = Stream; + + RETURN_NTSTATUS_IF_FAILED(AcxAudioModuleCreate(Stream, &attributes, &audioModuleCfg, &audioModuleElement)); + + audioModule1Ctx = GetSdcaXuAudioModule1Context(audioModuleElement); + ASSERT(audioModule1Ctx); + + ACX_PNPEVENT_CONFIG_INIT(&audioModuleEventCfg); + + WDF_OBJECT_ATTRIBUTES_INIT_CONTEXT_TYPE(&attributes, SDCAXU_PNPEVENT_CONTEXT); + attributes.ParentObject = audioModuleElement; + RETURN_NTSTATUS_IF_FAILED(AcxPnpEventCreate(Device, audioModuleElement, &attributes, &audioModuleEventCfg, &audioModuleEvent)); + + audioModule1Ctx->Event = audioModuleEvent; + + RETURN_NTSTATUS_IF_FAILED(AcxStreamAddElements(Stream, (ACXELEMENT *) &audioModuleElement, 1)); + + // module 2 + + ACX_AUDIOMODULE_CALLBACKS_INIT(&audioModuleCallbacks); + audioModuleCallbacks.EvtAcxAudioModuleProcessCommand = SdcaXu_EvtProcessCommand2; + + ACX_AUDIOMODULE_CONFIG_INIT(&audioModuleCfg); + audioModuleCfg.Name = &AudioModule2Id; + audioModuleCfg.Descriptor.ClassId = AudioModule2Id; + audioModuleCfg.Descriptor.InstanceId = AUDIOMODULE_INSTANCE_ID(1,0); + audioModuleCfg.Descriptor.VersionMajor = AUDIOMODULE2_MAJOR; + audioModuleCfg.Descriptor.VersionMinor = AUDIOMODULE2_MINOR; + RETURN_NTSTATUS_IF_FAILED(RtlStringCchCopyNW(audioModuleCfg.Descriptor.Name, + ACX_AUDIOMODULE_MAX_NAME_CCH_SIZE, + AUDIOMODULE2DESCRIPTION, + wcslen(AUDIOMODULE2DESCRIPTION))); + + audioModuleCfg.Callbacks = &audioModuleCallbacks; + + WDF_OBJECT_ATTRIBUTES_INIT_CONTEXT_TYPE(&attributes, SDCAXU_AUDIOMODULE2_CONTEXT); + attributes.ParentObject = Stream; + + RETURN_NTSTATUS_IF_FAILED(AcxAudioModuleCreate(Stream, &attributes, &audioModuleCfg, &audioModuleElement)); + + audioModule2Ctx = GetSdcaXuAudioModule2Context(audioModuleElement); + ASSERT(audioModule2Ctx); + + ACX_PNPEVENT_CONFIG_INIT(&audioModuleEventCfg); + + WDF_OBJECT_ATTRIBUTES_INIT_CONTEXT_TYPE(&attributes, SDCAXU_PNPEVENT_CONTEXT); + attributes.ParentObject = audioModuleElement; + RETURN_NTSTATUS_IF_FAILED(AcxPnpEventCreate(Device, audioModuleElement, &attributes, &audioModuleEventCfg, &audioModuleEvent)); + + audioModule2Ctx->Event = audioModuleEvent; + + RETURN_NTSTATUS_IF_FAILED(AcxStreamAddElements(Stream, (ACXELEMENT *) &audioModuleElement, 1)); + + return STATUS_SUCCESS; +} + + +PAGED_CODE_SEG +NTSTATUS +SdcaXuR_EvtCircuitCreateStream( + _In_ WDFDEVICE Device, + _In_ ACXCIRCUIT Circuit, + _In_ ACXPIN Pin, + _In_ PACXSTREAM_INIT StreamInit, + _In_ ACXDATAFORMAT StreamFormat, + _In_ const GUID * SignalProcessingMode, + _In_ ACXOBJECTBAG VarArguments +) +/*++ + +Routine Description: + + This routine create a stream for the specified circuit. + +Return Value: + + NT status value + +--*/ +{ + PAGED_CODE(); + + UNREFERENCED_PARAMETER(Pin); + UNREFERENCED_PARAMETER(SignalProcessingMode); + UNREFERENCED_PARAMETER(VarArguments); + + ASSERT(IsEqualGUID(*SignalProcessingMode, AUDIO_SIGNALPROCESSINGMODE_RAW)); + + PSDCAXU_RENDER_DEVICE_CONTEXT devCtx; + devCtx = GetRenderDeviceContext(Device); + ASSERT(devCtx != NULL); + + // + // Set circuit-callbacks. + // + RETURN_NTSTATUS_IF_FAILED(AcxStreamInitAssignAcxRequestPreprocessCallback( + StreamInit, + SdcaXuR_EvtStreamRequestPreprocess, + (ACXCONTEXT)AcxRequestTypeAny, // dbg only + AcxRequestTypeAny, + NULL, + AcxItemIdNone)); + + /* + // + // Add properties, events and methods. + // + RETURN_NTSTATUS_IF_FAILED(AcxStreamInitAssignProperties(StreamInit, + StreamProperties, + StreamPropertiesCount)); + */ + + // + // Init streaming callbacks. + // + ACX_STREAM_CALLBACKS streamCallbacks; + ACX_STREAM_CALLBACKS_INIT(&streamCallbacks); + streamCallbacks.EvtAcxStreamPrepareHardware = SdcaXu_EvtStreamPrepareHardware; + streamCallbacks.EvtAcxStreamReleaseHardware = SdcaXu_EvtStreamReleaseHardware; + streamCallbacks.EvtAcxStreamRun = SdcaXu_EvtStreamRun; + streamCallbacks.EvtAcxStreamPause = SdcaXu_EvtStreamPause; + streamCallbacks.EvtAcxStreamAssignDrmContentId = SdcaXu_EvtStreamAssignDrmContentId; + + RETURN_NTSTATUS_IF_FAILED(AcxStreamInitAssignAcxStreamCallbacks(StreamInit, &streamCallbacks)); + + // + // Create the stream. + // + WDF_OBJECT_ATTRIBUTES attributes; + WDF_OBJECT_ATTRIBUTES_INIT_CONTEXT_TYPE(&attributes, SDCAXU_STREAM_CONTEXT); + attributes.EvtDestroyCallback = SdcaXu_EvtStreamDestroy; + ACXSTREAM stream; + RETURN_NTSTATUS_IF_FAILED(AcxStreamCreate(Device, Circuit, &attributes, &StreamInit, &stream)); + + CRenderStreamEngine *streamEngine = NULL; + streamEngine = new(POOL_FLAG_NON_PAGED, DRIVER_TAG) CRenderStreamEngine(stream, StreamFormat); + RETURN_NTSTATUS_IF_TRUE(NULL == streamEngine, STATUS_MEMORY_NOT_ALLOCATED); + auto stream_scope = scope_exit([&streamEngine]() { + delete streamEngine; + }); + + SDCAXU_STREAM_CONTEXT *streamCtx; + streamCtx = GetSdcaXuStreamContext(stream); + ASSERT(streamCtx); + streamCtx->StreamEngine = (PVOID)streamEngine; + stream_scope.release(); + + // + // Post stream creation initialization. + // + + ACXELEMENT elements[2] = {0}; + ACX_ELEMENT_CONFIG elementCfg; + // + // Create 1st custom stream-elements. + // + ACX_ELEMENT_CONFIG_INIT(&elementCfg); + WDF_OBJECT_ATTRIBUTES_INIT_CONTEXT_TYPE(&attributes, SDCAXU_ELEMENT_CONTEXT); + attributes.ParentObject = stream; + + RETURN_NTSTATUS_IF_FAILED(AcxElementCreate(stream, &attributes, &elementCfg, &elements[0])); + + ASSERT(elements[0] != NULL); + SDCAXU_ELEMENT_CONTEXT *elementCtx; + elementCtx = GetSdcaXuElementContext(elements[0]); + ASSERT(elementCtx); + UNREFERENCED_PARAMETER(elementCtx); + + // + // Create 2nd custom stream-elements. + // + ACX_ELEMENT_CONFIG_INIT(&elementCfg); + WDF_OBJECT_ATTRIBUTES_INIT_CONTEXT_TYPE(&attributes, SDCAXU_ELEMENT_CONTEXT); + attributes.ParentObject = stream; + + RETURN_NTSTATUS_IF_FAILED(AcxElementCreate(stream, &attributes, &elementCfg, &elements[1])); + + ASSERT(elements[1] != NULL); + elementCtx = GetSdcaXuElementContext(elements[1]); + ASSERT(elementCtx); + UNREFERENCED_PARAMETER(elementCtx); + + // + // Add stream elements + // + RETURN_NTSTATUS_IF_FAILED(AcxStreamAddElements(stream, elements, SIZEOF_ARRAY(elements))); + + // + // Add stream modules + // + RETURN_NTSTATUS_IF_FAILED(SdcaXu_CreateStreamModules(Device, stream)); + + return STATUS_SUCCESS; +} + +PAGED_CODE_SEG +NTSTATUS +SdcaXu_AddRenders( + _In_ WDFDEVICE Device, + _In_ PSDCAXU_ACX_CIRCUIT_CONFIG CircuitConfig +) +{ + NTSTATUS status = STATUS_SUCCESS; + + PAGED_CODE(); + + // + // Add dynamic render circuit using raw PDO + // + status = SdcaXu_AddDynamicRender(Device, CircuitConfig); + + return status; +} + + diff --git a/audio/Soundwire/Samples/SdcaVad/SdcaVXu/render.h b/audio/Soundwire/Samples/SdcaVad/SdcaVXu/render.h new file mode 100644 index 000000000..72fd17cb5 --- /dev/null +++ b/audio/Soundwire/Samples/SdcaVad/SdcaVXu/render.h @@ -0,0 +1,84 @@ +/*++ + +Copyright (c) Microsoft Corporation. All rights reserved. + + THIS CODE AND INFORMATION IS PROVIDED "AS IS" WITHOUT WARRANTY OF ANY + KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE + IMPLIED WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A PARTICULAR + PURPOSE. + +Module Name: + + render.h + +Abstract: + + Contains structure definitions and function prototypes private to + the driver. + +Environment: + + Kernel mode + +--*/ + +#pragma once + +// +// Circuit's settings for raw PDO. +// +DECLARE_CONST_UNICODE_STRING(RenderDeviceId, L"SDCAVad\\ExtensionSpeaker"); +DECLARE_CONST_UNICODE_STRING(RenderHardwareId, L"SDCAVad\\ExtensionSpeaker"); +DECLARE_CONST_UNICODE_STRING(RenderInstanceId, L"00"); +DECLARE_CONST_UNICODE_STRING(RenderCompatibleId, SDCAVAD_COMPATIBLE_ID); +DECLARE_CONST_UNICODE_STRING(RenderContainerId, SDCAVAD_CONTAINER_ID); +DECLARE_CONST_UNICODE_STRING(RenderDeviceDescription, L"SDCAVad Speaker(Ext)"); +DECLARE_CONST_UNICODE_STRING(RenderDeviceLocation, L"SDCAVad Speaker"); + +PAGED_CODE_SEG +NTSTATUS +SdcaXuR_SetPowerPolicy( + _In_ WDFDEVICE Device +); + +PAGED_CODE_SEG +NTSTATUS +SdcaXu_CreateRenderDevice( + _In_ WDFDEVICE Device, + _Out_ WDFDEVICE *RenderDevice +); + +PAGED_CODE_SEG +NTSTATUS +SdcaXu_AddDynamicRender( + _In_ WDFDEVICE Device +); + +PAGED_CODE_SEG +NTSTATUS +SdcaXu_CreateRenderCircuit( + _In_ WDFDEVICE Device, + _In_ PSDCAXU_ACX_CIRCUIT_CONFIG CircuitConfig, + _Out_ ACXCIRCUIT *Circuit +); + +// Render Device callbacks. + +EVT_WDF_DEVICE_PREPARE_HARDWARE SdcaXuR_EvtDevicePrepareHardware; +EVT_WDF_DEVICE_RELEASE_HARDWARE SdcaXuR_EvtDeviceReleaseHardware; +EVT_WDF_DEVICE_SELF_MANAGED_IO_INIT SdcaXuR_EvtDeviceSelfManagedIoInit; +EVT_WDF_DEVICE_CONTEXT_CLEANUP SdcaXuR_EvtDeviceContextCleanup; + +// Render callbacks. + +EVT_WDF_OBJECT_CONTEXT_CLEANUP SdcaXuR_EvtCircuitContextCleanup; +EVT_ACX_OBJECT_PREPROCESS_REQUEST SdcaXuR_EvtCircuitRequestPreprocess; +EVT_ACX_CIRCUIT_CREATE_STREAM SdcaXuR_EvtCircuitCreateStream; +EVT_ACX_CIRCUIT_POWER_UP SdcaXuR_EvtCircuitPowerUp; +EVT_ACX_CIRCUIT_POWER_DOWN SdcaXuR_EvtCircuitPowerDown; +EVT_ACX_PIN_SET_DATAFORMAT SdcaXuR_EvtAcxPinSetDataFormat; +EVT_WDF_DEVICE_CONTEXT_CLEANUP SdcaXuR_EvtPinContextCleanup; +EVT_ACX_OBJECT_PREPROCESS_REQUEST SdcaXuR_EvtStreamRequestPreprocess; + +#pragma code_seg() + diff --git a/audio/Soundwire/Samples/SdcaVad/SdcaVXu/resources.rc b/audio/Soundwire/Samples/SdcaVad/SdcaVXu/resources.rc new file mode 100644 index 000000000..e2c18dd9c --- /dev/null +++ b/audio/Soundwire/Samples/SdcaVad/SdcaVXu/resources.rc @@ -0,0 +1,12 @@ +#include + +#include + +#define VER_FILETYPE VFT_DRV +#define VER_FILESUBTYPE VFT2_DRV_SYSTEM +#define VER_FILEDESCRIPTION_STR "ACX v1.0 SDCAXu Audio Driver" +#define VER_INTERNALNAME_STR "SDCAVXu.sys" +#define VER_ORIGINALFILENAME_STR "SDCAVXu.sys" + +#include "common.ver" + diff --git a/audio/Soundwire/Samples/SdcaVad/SdcaVXu/streamengine.cpp b/audio/Soundwire/Samples/SdcaVad/SdcaVXu/streamengine.cpp new file mode 100644 index 000000000..1396480d3 --- /dev/null +++ b/audio/Soundwire/Samples/SdcaVad/SdcaVXu/streamengine.cpp @@ -0,0 +1,264 @@ +/*++ + + THIS CODE AND INFORMATION IS PROVIDED "AS IS" WITHOUT WARRANTY OF ANY + KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE + IMPLIED WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A PARTICULAR + PURPOSE. + +Module Name: + + StreamEngine.cpp + +Abstract: + + Virtual Streaming Engine - this module controls streaming logic for + the device. + +Environment: + + Kernel mode + +--*/ + +#include "private.h" +#include +#include "stdunk.h" +#include +#include +#include +#include "trace.h" +#include "streamengine.h" + +#ifndef __INTELLISENSE__ +#include "streamengine.tmh" +#endif + +_Use_decl_annotations_ +PAGED_CODE_SEG +CStreamEngine::CStreamEngine( + _In_ ACXSTREAM Stream, + _In_ ACXDATAFORMAT StreamFormat + ) + : m_CurrentState(AcxStreamStateStop), + m_Stream(Stream), + m_StreamFormat(StreamFormat) +{ + PAGED_CODE(); + + KeQueryPerformanceCounter(&m_PerformanceCounterFrequency); +} + +PAGED_CODE_SEG +CStreamEngine::~CStreamEngine() +{ + PAGED_CODE(); +} + +_Use_decl_annotations_ +PAGED_CODE_SEG +NTSTATUS +CStreamEngine::PrepareHardware() +{ + PAGED_CODE(); + + m_CurrentState = AcxStreamStatePause; + + return STATUS_SUCCESS; +} + +_Use_decl_annotations_ +PAGED_CODE_SEG +NTSTATUS +CStreamEngine::ReleaseHardware() +{ + PAGED_CODE(); + + m_CurrentState = AcxStreamStateStop; + + return STATUS_SUCCESS; +} + +_Use_decl_annotations_ +PAGED_CODE_SEG +NTSTATUS +CStreamEngine::Pause() +{ + PAGED_CODE(); + + m_CurrentState = AcxStreamStatePause; + + return STATUS_SUCCESS; +} + +_Use_decl_annotations_ +PAGED_CODE_SEG +NTSTATUS +CStreamEngine::Run() +{ + PAGED_CODE(); + + if (m_CurrentState != AcxStreamStatePause) + { + return STATUS_INVALID_STATE_TRANSITION; + } + + m_CurrentState = AcxStreamStateRun; + + return STATUS_SUCCESS; +} + +_Use_decl_annotations_ +PAGED_CODE_SEG +NTSTATUS +CStreamEngine::AssignDrmContentId( + _In_ ULONG DrmContentId, + _In_ PACXDRMRIGHTS DrmRights + ) +{ + PAGED_CODE(); + + UNREFERENCED_PARAMETER(DrmContentId); + UNREFERENCED_PARAMETER(DrmRights); + + // + // At this point the driver should enforce the new DrmRights. + // + // HDMI render: if DigitalOutputDisable or CopyProtect is true, enable HDCP. + // + // From MSDN: + // + // This sample doesn't forward protected content, but if your driver uses + // lower layer drivers or a different stack to properly work, please see the + // following info from MSDN: + // + // "Before allowing protected content to flow through a data path, the system + // verifies that the data path is secure. To do so, the system authenticates + // each module in the data path beginning at the upstream end of the data path + // and moving downstream. As each module is authenticated, that module gives + // the system information about the next module in the data path so that it + // can also be authenticated. To be successfully authenticated, a module's + // binary file must be signed as DRM-compliant. + // + // Two adjacent modules in the data path can communicate with each other in + // one of several ways. If the upstream module calls the downstream module + // through IoCallDriver, the downstream module is part of a WDM driver. In + // this case, the upstream module calls the AcxDrmForwardContentToDeviceObject + // function to provide the system with the device object representing the + // downstream module. (If the two modules communicate through the downstream + // module's content handlers, the upstream module calls AcxDrmAddContentHandlers + // instead.) + // + // For more information, see MSDN's DRM Functions and Interfaces. + // + + return STATUS_SUCCESS; +} + +_Use_decl_annotations_ +PAGED_CODE_SEG +NTSTATUS +CStreamEngine::GetHWLatency( + _Out_ ULONG * FifoSize, + _Out_ ULONG * Delay + ) +{ + PAGED_CODE(); + + *FifoSize = 128; + *Delay = 0; + + return STATUS_SUCCESS; +} + +_Use_decl_annotations_ +PAGED_CODE_SEG +CRenderStreamEngine::CRenderStreamEngine( + _In_ ACXSTREAM Stream, + _In_ ACXDATAFORMAT StreamFormat + ) + : CStreamEngine(Stream, StreamFormat) +{ + PAGED_CODE(); +} + +_Use_decl_annotations_ +PAGED_CODE_SEG +CRenderStreamEngine::~CRenderStreamEngine() +{ + PAGED_CODE(); +} + +_Use_decl_annotations_ +PAGED_CODE_SEG +NTSTATUS +CRenderStreamEngine::PrepareHardware() +{ + PAGED_CODE(); + + RETURN_NTSTATUS_IF_FAILED(CStreamEngine::PrepareHardware()); + + // Add other init here. + + return STATUS_SUCCESS; +} + +_Use_decl_annotations_ +PAGED_CODE_SEG +NTSTATUS +CRenderStreamEngine::ReleaseHardware() +{ + PAGED_CODE(); + + return CStreamEngine::ReleaseHardware(); +} + +_Use_decl_annotations_ +PAGED_CODE_SEG +CCaptureStreamEngine::CCaptureStreamEngine( + _In_ ACXSTREAM Stream, + _In_ ACXDATAFORMAT StreamFormat + ) + : CStreamEngine(Stream, StreamFormat) +{ + PAGED_CODE(); +} + +_Use_decl_annotations_ +PAGED_CODE_SEG +CCaptureStreamEngine::~CCaptureStreamEngine() +{ + PAGED_CODE(); +} + +_Use_decl_annotations_ +PAGED_CODE_SEG +NTSTATUS +CCaptureStreamEngine::PrepareHardware() +{ + PAGED_CODE(); + + RETURN_NTSTATUS_IF_FAILED(CStreamEngine::PrepareHardware()); + + RETURN_NTSTATUS_IF_FAILED(ReadRegistrySettings()); + + return STATUS_SUCCESS; +} + +_Use_decl_annotations_ +PAGED_CODE_SEG +NTSTATUS +CCaptureStreamEngine::ReleaseHardware() +{ + PAGED_CODE(); + + return CStreamEngine::ReleaseHardware(); +} + +_Use_decl_annotations_ +PAGED_CODE_SEG +NTSTATUS +CCaptureStreamEngine::ReadRegistrySettings() +{ + PAGED_CODE(); + return STATUS_SUCCESS; +} diff --git a/audio/Soundwire/Samples/SdcaVad/SdcaVXu/streamengine.h b/audio/Soundwire/Samples/SdcaVad/SdcaVXu/streamengine.h new file mode 100644 index 000000000..61046693a --- /dev/null +++ b/audio/Soundwire/Samples/SdcaVad/SdcaVXu/streamengine.h @@ -0,0 +1,131 @@ +#pragma once + +#define HNSTIME_PER_MILLISECOND 10000 + +class CStreamEngine +{ +public: + virtual + __drv_maxIRQL(PASSIVE_LEVEL) + PAGED_CODE_SEG + NTSTATUS + PrepareHardware(); + + virtual + __drv_maxIRQL(PASSIVE_LEVEL) + PAGED_CODE_SEG + NTSTATUS + ReleaseHardware(); + + virtual + __drv_maxIRQL(PASSIVE_LEVEL) + PAGED_CODE_SEG + NTSTATUS + Run(); + + virtual + __drv_maxIRQL(PASSIVE_LEVEL) + PAGED_CODE_SEG + NTSTATUS + Pause(); + + virtual + __drv_maxIRQL(PASSIVE_LEVEL) + PAGED_CODE_SEG + NTSTATUS + AssignDrmContentId( + _In_ ULONG DrmContentId, + _In_ PACXDRMRIGHTS DrmRights + ); + + virtual + __drv_maxIRQL(PASSIVE_LEVEL) + PAGED_CODE_SEG + NTSTATUS + GetHWLatency( + _Out_ ULONG * FifoSize, + _Out_ ULONG * Delay + ); + + __drv_maxIRQL(PASSIVE_LEVEL) + PAGED_CODE_SEG + CStreamEngine( + _In_ ACXSTREAM Stream, + _In_ ACXDATAFORMAT StreamFormat + ); + + __drv_maxIRQL(PASSIVE_LEVEL) + PAGED_CODE_SEG + virtual + ~CStreamEngine(); + +protected: + ACX_STREAM_STATE m_CurrentState; + ACXSTREAM m_Stream; + ACXDATAFORMAT m_StreamFormat; + LARGE_INTEGER m_PerformanceCounterFrequency; +}; + +class CRenderStreamEngine : public CStreamEngine +{ +public: + __drv_maxIRQL(PASSIVE_LEVEL) + PAGED_CODE_SEG + CRenderStreamEngine( + _In_ ACXSTREAM Stream, + _In_ ACXDATAFORMAT StreamFormat + ); + + __drv_maxIRQL(PASSIVE_LEVEL) + PAGED_CODE_SEG + ~CRenderStreamEngine(); + + virtual + __drv_maxIRQL(PASSIVE_LEVEL) + PAGED_CODE_SEG + NTSTATUS + PrepareHardware(); + + virtual + __drv_maxIRQL(PASSIVE_LEVEL) + PAGED_CODE_SEG + NTSTATUS + ReleaseHardware(); + +protected: + // data section. +}; + +class CCaptureStreamEngine : public CStreamEngine +{ +public: + __drv_maxIRQL(PASSIVE_LEVEL) + PAGED_CODE_SEG + CCaptureStreamEngine( + _In_ ACXSTREAM Stream, + _In_ ACXDATAFORMAT StreamFormat + ); + + __drv_maxIRQL(PASSIVE_LEVEL) + PAGED_CODE_SEG + ~CCaptureStreamEngine(); + + virtual + __drv_maxIRQL(PASSIVE_LEVEL) + PAGED_CODE_SEG + NTSTATUS + PrepareHardware(); + + virtual + __drv_maxIRQL(PASSIVE_LEVEL) + PAGED_CODE_SEG + NTSTATUS + ReleaseHardware(); + +protected: + __drv_maxIRQL(PASSIVE_LEVEL) + PAGED_CODE_SEG + NTSTATUS + ReadRegistrySettings(); +}; +