diff options
Diffstat (limited to 'audio/SoundWire/Samples/SdcaVad/SdcaVDsp')
36 files changed, 19628 insertions, 0 deletions
diff --git a/audio/SoundWire/Samples/SdcaVad/SdcaVDsp/AcpiReader.cpp b/audio/SoundWire/Samples/SdcaVad/SdcaVDsp/AcpiReader.cpp new file mode 100644 index 00000000..6a223c30 --- /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 <stdunk.h> + +#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 00000000..5c9ceee8 --- /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 <acpiioct.h> + +// 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 00000000..79764cdc --- /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 <ks.h> + +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 00000000..9369f835 --- /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 00000000..94d94843 --- /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 00000000..9324e7eb --- /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 00000000..3611a2c1 --- /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 <ks.h> +#include <mmsystem.h> +#include <ksmedia.h> +#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<CKeywordDetector*>(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 00000000..9ebc3a5e --- /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 <wil\resource.h> +#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 00000000..d57bb522 --- /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 00000000..db0f2aee --- /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 00000000..2d1844fa --- /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 00000000..5661777a --- /dev/null +++ b/audio/SoundWire/Samples/SdcaVad/SdcaVDsp/SDCAVDsp.vcxproj @@ -0,0 +1,373 @@ +<?xml version="1.0" encoding="utf-8"?> +<Project DefaultTargets="Build" ToolsVersion="12.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003"> + <ItemGroup Label="ProjectConfigurations"> + <ProjectConfiguration Include="Debug|Win32"> + <Configuration>Debug</Configuration> + <Platform>Win32</Platform> + </ProjectConfiguration> + <ProjectConfiguration Include="Release|Win32"> + <Configuration>Release</Configuration> + <Platform>Win32</Platform> + </ProjectConfiguration> + <ProjectConfiguration Include="Debug|x64"> + <Configuration>Debug</Configuration> + <Platform>x64</Platform> + </ProjectConfiguration> + <ProjectConfiguration Include="Release|x64"> + <Configuration>Release</Configuration> + <Platform>x64</Platform> + </ProjectConfiguration> + <ProjectConfiguration Include="Debug|ARM"> + <Configuration>Debug</Configuration> + <Platform>ARM</Platform> + </ProjectConfiguration> + <ProjectConfiguration Include="Release|ARM"> + <Configuration>Release</Configuration> + <Platform>ARM</Platform> + </ProjectConfiguration> + <ProjectConfiguration Include="Debug|ARM64"> + <Configuration>Debug</Configuration> + <Platform>ARM64</Platform> + </ProjectConfiguration> + <ProjectConfiguration Include="Release|ARM64"> + <Configuration>Release</Configuration> + <Platform>ARM64</Platform> + </ProjectConfiguration> + </ItemGroup> + <PropertyGroup Label="Globals"> + <ProjectGuid>{5FDD3888-48B5-496C-83B0-E107CFFF46BC}</ProjectGuid> + <RootNamespace>$(MSBuildProjectName)</RootNamespace> + <KMDF_VERSION_MAJOR>1</KMDF_VERSION_MAJOR> + <KMDF_VERSION_MINOR>31</KMDF_VERSION_MINOR> + <ACX_VERSION_MAJOR>1</ACX_VERSION_MAJOR> + <ACX_VERSION_MINOR>0</ACX_VERSION_MINOR> + <TargetFrameworkVersion>v4.5</TargetFrameworkVersion> + <MinimumVisualStudioVersion>12.0</MinimumVisualStudioVersion> + <SupportsPackaging>false</SupportsPackaging> + <RequiresPackageProject>true</RequiresPackageProject> + <Configuration Condition="'$(Configuration)' == ''">Debug</Configuration> + <Platform Condition="'$(Platform)' == ''">Win32</Platform> + <WindowsTargetPlatformVersion>$(LatestTargetPlatformVersion)</WindowsTargetPlatformVersion> + </PropertyGroup> + <Import Project="$(VCTargetsPath)\Microsoft.Cpp.Default.props" /> + <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'" Label="Configuration"> + <TargetVersion>Windows10</TargetVersion> + <UseDebugLibraries>true</UseDebugLibraries> + <PlatformToolset>WindowsKernelModeDriver10.0</PlatformToolset> + <ConfigurationType>Driver</ConfigurationType> + <DriverType>KMDF</DriverType> + <DriverTargetPlatform>Universal</DriverTargetPlatform> + </PropertyGroup> + <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'" Label="Configuration"> + <TargetVersion>Windows10</TargetVersion> + <UseDebugLibraries>false</UseDebugLibraries> + <PlatformToolset>WindowsKernelModeDriver10.0</PlatformToolset> + <ConfigurationType>Driver</ConfigurationType> + <DriverType>KMDF</DriverType> + <DriverTargetPlatform>Universal</DriverTargetPlatform> + </PropertyGroup> + <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'" Label="Configuration"> + <TargetVersion>Windows10</TargetVersion> + <UseDebugLibraries>true</UseDebugLibraries> + <PlatformToolset>WindowsKernelModeDriver10.0</PlatformToolset> + <ConfigurationType>Driver</ConfigurationType> + <DriverType>KMDF</DriverType> + <DriverTargetPlatform>Universal</DriverTargetPlatform> + </PropertyGroup> + <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'" Label="Configuration"> + <TargetVersion>Windows10</TargetVersion> + <UseDebugLibraries>false</UseDebugLibraries> + <PlatformToolset>WindowsKernelModeDriver10.0</PlatformToolset> + <ConfigurationType>Driver</ConfigurationType> + <DriverType>KMDF</DriverType> + <DriverTargetPlatform>Universal</DriverTargetPlatform> + </PropertyGroup> + <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|ARM'" Label="Configuration"> + <TargetVersion>Windows10</TargetVersion> + <UseDebugLibraries>true</UseDebugLibraries> + <PlatformToolset>WindowsKernelModeDriver10.0</PlatformToolset> + <ConfigurationType>Driver</ConfigurationType> + <DriverType>KMDF</DriverType> + <DriverTargetPlatform>Universal</DriverTargetPlatform> + </PropertyGroup> + <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|ARM'" Label="Configuration"> + <TargetVersion>Windows10</TargetVersion> + <UseDebugLibraries>false</UseDebugLibraries> + <PlatformToolset>WindowsKernelModeDriver10.0</PlatformToolset> + <ConfigurationType>Driver</ConfigurationType> + <DriverType>KMDF</DriverType> + <DriverTargetPlatform>Universal</DriverTargetPlatform> + </PropertyGroup> + <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|ARM64'" Label="Configuration"> + <TargetVersion>Windows10</TargetVersion> + <UseDebugLibraries>true</UseDebugLibraries> + <PlatformToolset>WindowsKernelModeDriver10.0</PlatformToolset> + <ConfigurationType>Driver</ConfigurationType> + <DriverType>KMDF</DriverType> + <DriverTargetPlatform>Universal</DriverTargetPlatform> + </PropertyGroup> + <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|ARM64'" Label="Configuration"> + <TargetVersion>Windows10</TargetVersion> + <UseDebugLibraries>false</UseDebugLibraries> + <PlatformToolset>WindowsKernelModeDriver10.0</PlatformToolset> + <ConfigurationType>Driver</ConfigurationType> + <DriverType>KMDF</DriverType> + <DriverTargetPlatform>Universal</DriverTargetPlatform> + </PropertyGroup> + <Import Project="$(VCTargetsPath)\Microsoft.Cpp.props" /> + <PropertyGroup> + <OutDir>$(IntDir)</OutDir> + </PropertyGroup> + <ImportGroup Label="ExtensionSettings"> + </ImportGroup> + <ImportGroup Label="PropertySheets"> + <Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" /> + </ImportGroup> + <PropertyGroup Label="UserMacros" /> + <PropertyGroup /> + <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'"> + <DebuggerFlavor>DbgengKernelDebugger</DebuggerFlavor> + </PropertyGroup> + <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'"> + <DebuggerFlavor>DbgengKernelDebugger</DebuggerFlavor> + </PropertyGroup> + <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'"> + <DebuggerFlavor>DbgengKernelDebugger</DebuggerFlavor> + </PropertyGroup> + <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'"> + <DebuggerFlavor>DbgengKernelDebugger</DebuggerFlavor> + </PropertyGroup> + <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|ARM'"> + <DebuggerFlavor>DbgengKernelDebugger</DebuggerFlavor> + </PropertyGroup> + <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|ARM'"> + <DebuggerFlavor>DbgengKernelDebugger</DebuggerFlavor> + </PropertyGroup> + <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|ARM64'"> + <DebuggerFlavor>DbgengKernelDebugger</DebuggerFlavor> + </PropertyGroup> + <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|ARM64'"> + <DebuggerFlavor>DbgengKernelDebugger</DebuggerFlavor> + </PropertyGroup> + <ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'"> + <Link> + <AdditionalDependencies>%(AdditionalDependencies);$(DDK_LIB_PATH)\libcntpr.lib;wpprecorder.lib;$(DDK_LIB_PATH)\acx\km\$(ACX_VERSION_MAJOR).$(ACX_VERSION_MINOR)\acxstub.lib</AdditionalDependencies> + </Link> + <ClCompile> + <AdditionalIncludeDirectories>%(AdditionalIncludeDirectories);$(SDK_INC_PATH);$(DDK_INC_PATH)\acx\km\$(ACX_VERSION_MAJOR).$(ACX_VERSION_MINOR);..\inc;.;..\..\..\..\..\wil\include</AdditionalIncludeDirectories> + <PreprocessorDefinitions>%(PreprocessorDefinitions);UNICODE;_UNICODE</PreprocessorDefinitions> + <PreprocessorDefinitions>%(PreprocessorDefinitions);ACX_VERSION_MAJOR=1;ACX_VERSION_MINOR=0</PreprocessorDefinitions> + <PreprocessorDefinitions>%(PreprocessorDefinitions);ACX_WORKAROUND_ACXFACTORYCIRCUIT_01;ACX_WORKAROUND_ACXPIN_01;_NEW_DELETE_OPERATORS_</PreprocessorDefinitions> + <WppEnabled>true</WppEnabled> + <WppScanConfigurationData>..\inc\trace_macros.h</WppScanConfigurationData> + <WppAdditionalOptions>-km \ +-DENABLE_WPP_RECORDER=1 \ +-DENABLE_WPP_TRACE_FILTERING_WITH_WPP_RECORDER=1 \ +-func:DoTraceLevelMessage(LEVEL,FLAGS,MSG,...) \ +-p:SDCAVCodec</WppAdditionalOptions> + <WppRecorderEnabled>true</WppRecorderEnabled> + </ClCompile> + <DriverSign> + <FileDigestAlgorithm>sha256</FileDigestAlgorithm> + </DriverSign> + </ItemDefinitionGroup> + <ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'"> + <Link> + <AdditionalDependencies>%(AdditionalDependencies);$(DDK_LIB_PATH)\libcntpr.lib;wpprecorder.lib;$(DDK_LIB_PATH)\acx\km\$(ACX_VERSION_MAJOR).$(ACX_VERSION_MINOR)\acxstub.lib</AdditionalDependencies> + </Link> + <ClCompile> + <AdditionalIncludeDirectories>%(AdditionalIncludeDirectories);$(SDK_INC_PATH);$(DDK_INC_PATH)\acx\km\$(ACX_VERSION_MAJOR).$(ACX_VERSION_MINOR);..\inc;.;..\..\..\..\..\wil\include</AdditionalIncludeDirectories> + <PreprocessorDefinitions>%(PreprocessorDefinitions);UNICODE;_UNICODE</PreprocessorDefinitions> + <PreprocessorDefinitions>%(PreprocessorDefinitions);ACX_VERSION_MAJOR=1;ACX_VERSION_MINOR=0</PreprocessorDefinitions> + <PreprocessorDefinitions>%(PreprocessorDefinitions);ACX_WORKAROUND_ACXFACTORYCIRCUIT_01;ACX_WORKAROUND_ACXPIN_01;_NEW_DELETE_OPERATORS_</PreprocessorDefinitions> + <WppEnabled>true</WppEnabled> + <WppScanConfigurationData>..\inc\trace_macros.h</WppScanConfigurationData> + <WppAdditionalOptions>-km \ +-DENABLE_WPP_RECORDER=1 \ +-DENABLE_WPP_TRACE_FILTERING_WITH_WPP_RECORDER=1 \ +-func:DoTraceLevelMessage(LEVEL,FLAGS,MSG,...) \ +-p:SDCAVCodec</WppAdditionalOptions> + <WppRecorderEnabled>true</WppRecorderEnabled> + </ClCompile> + <DriverSign> + <FileDigestAlgorithm>sha256</FileDigestAlgorithm> + </DriverSign> + </ItemDefinitionGroup> + <ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'"> + <Link> + <AdditionalDependencies>%(AdditionalDependencies);$(DDK_LIB_PATH)\libcntpr.lib;wpprecorder.lib;$(DDK_LIB_PATH)\acx\km\$(ACX_VERSION_MAJOR).$(ACX_VERSION_MINOR)\acxstub.lib</AdditionalDependencies> + </Link> + <ClCompile> + <AdditionalIncludeDirectories>%(AdditionalIncludeDirectories);$(SDK_INC_PATH);$(DDK_INC_PATH)\acx\km\$(ACX_VERSION_MAJOR).$(ACX_VERSION_MINOR);..\inc;.;..\..\..\..\..\wil\include</AdditionalIncludeDirectories> + <PreprocessorDefinitions>%(PreprocessorDefinitions);UNICODE;_UNICODE</PreprocessorDefinitions> + <PreprocessorDefinitions>%(PreprocessorDefinitions);ACX_VERSION_MAJOR=1;ACX_VERSION_MINOR=0</PreprocessorDefinitions> + <PreprocessorDefinitions>%(PreprocessorDefinitions);ACX_WORKAROUND_ACXFACTORYCIRCUIT_01;ACX_WORKAROUND_ACXPIN_01;_NEW_DELETE_OPERATORS_</PreprocessorDefinitions> + <WppEnabled>true</WppEnabled> + <WppScanConfigurationData>..\inc\trace_macros.h</WppScanConfigurationData> + <WppAdditionalOptions>-km \ +-DENABLE_WPP_RECORDER=1 \ +-DENABLE_WPP_TRACE_FILTERING_WITH_WPP_RECORDER=1 \ +-func:DoTraceLevelMessage(LEVEL,FLAGS,MSG,...) \ +-p:SDCAVCodec</WppAdditionalOptions> + <WppRecorderEnabled>true</WppRecorderEnabled> + </ClCompile> + <DriverSign> + <FileDigestAlgorithm>sha256</FileDigestAlgorithm> + </DriverSign> + </ItemDefinitionGroup> + <ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'"> + <Link> + <AdditionalDependencies>%(AdditionalDependencies);$(DDK_LIB_PATH)\libcntpr.lib;wpprecorder.lib;$(DDK_LIB_PATH)\acx\km\$(ACX_VERSION_MAJOR).$(ACX_VERSION_MINOR)\acxstub.lib</AdditionalDependencies> + </Link> + <ClCompile> + <AdditionalIncludeDirectories>%(AdditionalIncludeDirectories);$(SDK_INC_PATH);$(DDK_INC_PATH)\acx\km\$(ACX_VERSION_MAJOR).$(ACX_VERSION_MINOR);..\inc;.;..\..\..\..\..\wil\include</AdditionalIncludeDirectories> + <PreprocessorDefinitions>%(PreprocessorDefinitions);UNICODE;_UNICODE</PreprocessorDefinitions> + <PreprocessorDefinitions>%(PreprocessorDefinitions);ACX_VERSION_MAJOR=1;ACX_VERSION_MINOR=0</PreprocessorDefinitions> + <PreprocessorDefinitions>%(PreprocessorDefinitions);ACX_WORKAROUND_ACXFACTORYCIRCUIT_01;ACX_WORKAROUND_ACXPIN_01;_NEW_DELETE_OPERATORS_</PreprocessorDefinitions> + <WppEnabled>true</WppEnabled> + <WppScanConfigurationData>..\inc\trace_macros.h</WppScanConfigurationData> + <WppAdditionalOptions>-km \ +-DENABLE_WPP_RECORDER=1 \ +-DENABLE_WPP_TRACE_FILTERING_WITH_WPP_RECORDER=1 \ +-func:DoTraceLevelMessage(LEVEL,FLAGS,MSG,...) \ +-p:SDCAVCodec</WppAdditionalOptions> + <WppRecorderEnabled>true</WppRecorderEnabled> + </ClCompile> + <DriverSign> + <FileDigestAlgorithm>sha256</FileDigestAlgorithm> + </DriverSign> + </ItemDefinitionGroup> + <ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Debug|ARM'"> + <Link> + <AdditionalDependencies>%(AdditionalDependencies);$(DDK_LIB_PATH)\libcntpr.lib;wpprecorder.lib;$(DDK_LIB_PATH)\acx\km\$(ACX_VERSION_MAJOR).$(ACX_VERSION_MINOR)\acxstub.lib</AdditionalDependencies> + </Link> + <ClCompile> + <AdditionalIncludeDirectories>%(AdditionalIncludeDirectories);$(SDK_INC_PATH);$(DDK_INC_PATH)\acx\km\$(ACX_VERSION_MAJOR).$(ACX_VERSION_MINOR);..\inc;.;..\..\..\..\..\wil\include</AdditionalIncludeDirectories> + <PreprocessorDefinitions>%(PreprocessorDefinitions);UNICODE;_UNICODE</PreprocessorDefinitions> + <PreprocessorDefinitions>%(PreprocessorDefinitions);ACX_VERSION_MAJOR=1;ACX_VERSION_MINOR=0</PreprocessorDefinitions> + <PreprocessorDefinitions>%(PreprocessorDefinitions);ACX_WORKAROUND_ACXFACTORYCIRCUIT_01;ACX_WORKAROUND_ACXPIN_01;_NEW_DELETE_OPERATORS_</PreprocessorDefinitions> + <WppEnabled>true</WppEnabled> + <WppScanConfigurationData>..\inc\trace_macros.h</WppScanConfigurationData> + <WppAdditionalOptions>-km \ +-DENABLE_WPP_RECORDER=1 \ +-DENABLE_WPP_TRACE_FILTERING_WITH_WPP_RECORDER=1 \ +-func:DoTraceLevelMessage(LEVEL,FLAGS,MSG,...) \ +-p:SDCAVCodec</WppAdditionalOptions> + <WppRecorderEnabled>true</WppRecorderEnabled> + </ClCompile> + <DriverSign> + <FileDigestAlgorithm>sha256</FileDigestAlgorithm> + </DriverSign> + </ItemDefinitionGroup> + <ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Release|ARM'"> + <Link> + <AdditionalDependencies>%(AdditionalDependencies);$(DDK_LIB_PATH)\libcntpr.lib;wpprecorder.lib;$(DDK_LIB_PATH)\acx\km\$(ACX_VERSION_MAJOR).$(ACX_VERSION_MINOR)\acxstub.lib</AdditionalDependencies> + </Link> + <ClCompile> + <AdditionalIncludeDirectories>%(AdditionalIncludeDirectories);$(SDK_INC_PATH);$(DDK_INC_PATH)\acx\km\$(ACX_VERSION_MAJOR).$(ACX_VERSION_MINOR);..\inc;.;..\..\..\..\..\wil\include</AdditionalIncludeDirectories> + <PreprocessorDefinitions>%(PreprocessorDefinitions);UNICODE;_UNICODE</PreprocessorDefinitions> + <PreprocessorDefinitions>%(PreprocessorDefinitions);ACX_VERSION_MAJOR=1;ACX_VERSION_MINOR=0</PreprocessorDefinitions> + <PreprocessorDefinitions>%(PreprocessorDefinitions);ACX_WORKAROUND_ACXFACTORYCIRCUIT_01;ACX_WORKAROUND_ACXPIN_01;_NEW_DELETE_OPERATORS_</PreprocessorDefinitions> + <WppEnabled>true</WppEnabled> + <WppScanConfigurationData>..\inc\trace_macros.h</WppScanConfigurationData> + <WppAdditionalOptions>-km \ +-DENABLE_WPP_RECORDER=1 \ +-DENABLE_WPP_TRACE_FILTERING_WITH_WPP_RECORDER=1 \ +-func:DoTraceLevelMessage(LEVEL,FLAGS,MSG,...) \ +-p:SDCAVCodec</WppAdditionalOptions> + <WppRecorderEnabled>true</WppRecorderEnabled> + </ClCompile> + <DriverSign> + <FileDigestAlgorithm>sha256</FileDigestAlgorithm> + </DriverSign> + </ItemDefinitionGroup> + <ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Release|ARM64'"> + <Link> + <AdditionalDependencies>%(AdditionalDependencies);$(DDK_LIB_PATH)\libcntpr.lib;wpprecorder.lib;$(DDK_LIB_PATH)\acx\km\$(ACX_VERSION_MAJOR).$(ACX_VERSION_MINOR)\acxstub.lib</AdditionalDependencies> + </Link> + <ClCompile> + <AdditionalIncludeDirectories>%(AdditionalIncludeDirectories);$(SDK_INC_PATH);$(DDK_INC_PATH)\acx\km\$(ACX_VERSION_MAJOR).$(ACX_VERSION_MINOR);..\inc;.;..\..\..\..\..\wil\include</AdditionalIncludeDirectories> + <PreprocessorDefinitions>%(PreprocessorDefinitions);UNICODE;_UNICODE</PreprocessorDefinitions> + <PreprocessorDefinitions>%(PreprocessorDefinitions);ACX_VERSION_MAJOR=1;ACX_VERSION_MINOR=0</PreprocessorDefinitions> + <PreprocessorDefinitions>%(PreprocessorDefinitions);ACX_WORKAROUND_ACXFACTORYCIRCUIT_01;ACX_WORKAROUND_ACXPIN_01;_NEW_DELETE_OPERATORS_</PreprocessorDefinitions> + <WppEnabled>true</WppEnabled> + <WppScanConfigurationData>..\inc\trace_macros.h</WppScanConfigurationData> + <WppAdditionalOptions>-km \ +-DENABLE_WPP_RECORDER=1 \ +-DENABLE_WPP_TRACE_FILTERING_WITH_WPP_RECORDER=1 \ +-func:DoTraceLevelMessage(LEVEL,FLAGS,MSG,...) \ +-p:SDCAVCodec</WppAdditionalOptions> + <WppRecorderEnabled>true</WppRecorderEnabled> + </ClCompile> + <DriverSign> + <FileDigestAlgorithm>sha256</FileDigestAlgorithm> + </DriverSign> + </ItemDefinitionGroup> + <ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Debug|ARM64'"> + <Link> + <AdditionalDependencies>%(AdditionalDependencies);$(DDK_LIB_PATH)\libcntpr.lib;wpprecorder.lib;$(DDK_LIB_PATH)\acx\km\$(ACX_VERSION_MAJOR).$(ACX_VERSION_MINOR)\acxstub.lib</AdditionalDependencies> + </Link> + <ClCompile> + <AdditionalIncludeDirectories>%(AdditionalIncludeDirectories);$(SDK_INC_PATH);$(DDK_INC_PATH)\acx\km\$(ACX_VERSION_MAJOR).$(ACX_VERSION_MINOR);..\inc;.;..\..\..\..\..\wil\include</AdditionalIncludeDirectories> + <PreprocessorDefinitions>%(PreprocessorDefinitions);UNICODE;_UNICODE</PreprocessorDefinitions> + <PreprocessorDefinitions>%(PreprocessorDefinitions);ACX_VERSION_MAJOR=1;ACX_VERSION_MINOR=0</PreprocessorDefinitions> + <PreprocessorDefinitions>%(PreprocessorDefinitions);ACX_WORKAROUND_ACXFACTORYCIRCUIT_01;ACX_WORKAROUND_ACXPIN_01;_NEW_DELETE_OPERATORS_</PreprocessorDefinitions> + <WppEnabled>true</WppEnabled> + <WppScanConfigurationData>..\inc\trace_macros.h</WppScanConfigurationData> + <WppAdditionalOptions>-km \ +-DENABLE_WPP_RECORDER=1 \ +-DENABLE_WPP_TRACE_FILTERING_WITH_WPP_RECORDER=1 \ +-func:DoTraceLevelMessage(LEVEL,FLAGS,MSG,...) \ +-p:SDCAVCodec</WppAdditionalOptions> + <WppRecorderEnabled>true</WppRecorderEnabled> + </ClCompile> + <DriverSign> + <FileDigestAlgorithm>sha256</FileDigestAlgorithm> + </DriverSign> + </ItemDefinitionGroup> + <ItemGroup> + <FilesToPackage Include="$(TargetPath)" /> + </ItemGroup> + <ItemGroup> + <Inf Exclude="@(Inx)" Include="*.inx" /> + <FilesToPackage Include="$(TargetPath)" Condition="'$(ConfigurationType)'=='Driver' or '$(ConfigurationType)'=='DynamicLibrary'" /> + </ItemGroup> + <ItemGroup> + <ClInclude Include="AcpiReader.h" /> + <ClInclude Include="CircuitHelper.h" /> + <ClInclude Include="KeywordDetector.h" /> + <ClInclude Include="..\inc\NewDelete.h" /> + <ClInclude Include="offloadStreamEngine.h" /> + <ClInclude Include="PositionClock.h" /> + <ClInclude Include="PositionSimClock.h" /> + <ClInclude Include="private.h" /> + <ClInclude Include="savedata.h" /> + <ClInclude Include="SimPeakMeter.h" /> + <ClInclude Include="streamengine.h" /> + <ClInclude Include="ToneGenerator.h" /> + <ClInclude Include="Trace.h" /> + <ClInclude Include="WaveReader.h" /> + </ItemGroup> + <ItemGroup> + <ClCompile Include="AcpiReader.cpp" /> + <ClCompile Include="AudioModule.cpp" /> + <ClCompile Include="capture.cpp" /> + <ClCompile Include="CircuitHelper.cpp" /> + <ClCompile Include="circuitstream.cpp" /> + <ClCompile Include="device.cpp" /> + <ClCompile Include="driver.cpp" /> + <ClCompile Include="KeywordDetector.cpp" /> + <ClCompile Include="..\common\NewDelete.cpp" /> + <ClCompile Include="offloadStreamEngine.cpp" /> + <ClCompile Include="PositionSimClock.cpp" /> + <ClCompile Include="render.cpp" /> + <ClCompile Include="renderAudioEngine.cpp" /> + <ClCompile Include="savedata.cpp" /> + <ClCompile Include="SimPeakMeter.cpp" /> + <ClCompile Include="streamengine.cpp" /> + <ClCompile Include="ToneGenerator.cpp" /> + <ClCompile Include="WaveReader.cpp" /> + <ResourceCompile Include="resources.rc" /> + </ItemGroup> + <Import Project="$(VCTargetsPath)\Microsoft.Cpp.targets" /> +</Project> 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 00000000..44bc3f92 --- /dev/null +++ b/audio/SoundWire/Samples/SdcaVad/SdcaVDsp/SDCAVDsp.vcxproj.Filters @@ -0,0 +1,21 @@ +<?xml version="1.0" encoding="utf-8"?> +<Project ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003"> + <ItemGroup> + <Filter Include="Source Files"> + <UniqueIdentifier>{4FC737F1-C7A5-4376-A066-2A32D752A2FF}</UniqueIdentifier> + <Extensions>cpp;c;cc;cxx;def;odl;idl;hpj;bat;asm;asmx</Extensions> + </Filter> + <Filter Include="Header Files"> + <UniqueIdentifier>{93995380-89BD-4b04-88EB-625FBE52EBFB}</UniqueIdentifier> + <Extensions>h;hpp;hxx;hm;inl;inc;xsd</Extensions> + </Filter> + <Filter Include="Resource Files"> + <UniqueIdentifier>{67DA6AB6-F800-4c08-8B7A-83BB121AAD01}</UniqueIdentifier> + <Extensions>rc;ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe;resx;tiff;tif;png;wav;mfcribbon-ms</Extensions> + </Filter> + <Filter Include="Driver Files"> + <UniqueIdentifier>{8E41214B-6785-4CFE-B992-037D68949A14}</UniqueIdentifier> + <Extensions>inf;inv;inx;mof;mc;</Extensions> + </Filter> + </ItemGroup> +</Project> diff --git a/audio/SoundWire/Samples/SdcaVad/SdcaVDsp/SdcaVApo.inx b/audio/SoundWire/Samples/SdcaVad/SdcaVDsp/SdcaVApo.inx new file mode 100644 index 00000000..5b1b39d5 --- /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 00000000..a0e0a654 --- /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 00000000..0dfbd4e5 --- /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 00000000..7203f299 --- /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 00000000..f98067c2 --- /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 <ntdef.h> +#include <wdm.h> +#include <minwindef.h> +#define NOBITMAP +#include <mmreg.h> +#undef NOBITMAP + +#define _USE_MATH_DEFINES +#include <math.h> +#include <limits.h> + +#include <ToneGenerator.h> + +#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<unsigned char *>(Frame); + dataBuffer[i] = ConvertToUChar(sinValue); + } + else if (m_BitsPerSample == 16) + { + short *dataBuffer = reinterpret_cast<short *>(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<long *>(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 00000000..c9fd08fc --- /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 00000000..e029aad0 --- /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 <WppRecorder.h> +#include <evntrace.h> // 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 00000000..d7b04892 --- /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 <devguid.h> +#include "stdunk.h" +#include <ks.h> +#include <mmsystem.h> +#include <ksmedia.h> +#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 00000000..2d5c537f --- /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 <math.h> +#include <limits.h> + +#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 <pshpack1.h> +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 <poppack.h> + +__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 00000000..eccd5738 --- /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 <devguid.h> +#include "stdunk.h" +#include <ks.h> +#include <mmsystem.h> +#include <ksmedia.h> +#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<CCaptureStreamEngine*>(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 00000000..d7d500c2 --- /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 <devguid.h> +#include "stdunk.h" +#include <ks.h> +#include <mmsystem.h> +#include <ksmedia.h> +#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<CStreamEngine*>(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<CStreamEngine*>(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 00000000..36c0e95a --- /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 <devguid.h> + + +#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\<driver>\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 00000000..98cc6d73 --- /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 00000000..c86af13a --- /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 <ks.h> +#include <mmsystem.h> +#include <ksmedia.h> +#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 00000000..4c325dda --- /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/private.h b/audio/SoundWire/Samples/SdcaVad/SdcaVDsp/private.h new file mode 100644 index 00000000..065b3375 --- /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 <mmsystem.h> +#include <ks.h> +#include <ksmedia.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 <initguid.h> +#include <ntddk.h> +#include <ntstrsafe.h> +#include <ntintsafe.h> +#include <TestProperties.h> + +#pragma warning(default:4200) +#pragma warning(default:4201) +#pragma warning(default:4214) + +#include <wdf.h> +#include <acx.h> + +#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 00000000..fe16d5c4 --- /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 <devguid.h> +#include "stdunk.h" +#include <ks.h> +#include <mmsystem.h> +#include <ksmedia.h> +#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<CRenderStreamEngine*>(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 00000000..e9bb9496 --- /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 <devguid.h> +#include "stdunk.h" +#include <ks.h> +#include <mmsystem.h> +#include <ksmedia.h> +#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<CStreamEngine*>(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<COffloadStreamEngine*>(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<CStreamEngine*>(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<COffloadStreamEngine*>(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 00000000..a2cc093a --- /dev/null +++ b/audio/SoundWire/Samples/SdcaVad/SdcaVDsp/resources.rc @@ -0,0 +1,12 @@ +#include <windows.h> + +#include <ntverp.h> + +#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 00000000..4d70ad09 --- /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 <devguid.h> +#include "stdunk.h" +#include <ks.h> +#include <mmsystem.h> +#include <ksmedia.h> +#include "savedata.h" +#include <ntstrsafe.h> // 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 00000000..2cc1eb47 --- /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 <pshpack1.h> +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 <poppack.h> + +// wave file header. +#include <pshpack1.h> +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 <poppack.h> + +//----------------------------------------------------------------------------- +// 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 00000000..a979a129 --- /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 <devguid.h> +#include "stdunk.h" +#include <ks.h> +#include <mmsystem.h> +#include <ksmedia.h> +#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 00000000..902bb007 --- /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) |
