Files
LookingGlass/idd/LGAudio/BrokerDevice.cpp
Geoffrey McRae efc9952e74 [idd] audio: support 32-bit and common channel layouts
Advertise signed and floating-point 32-bit formats across mono,
stereo, quad, surround, 5.1, and 7.1 at every supported rate.

Carry sample encoding through the broker and LGMP protocol so integer
and floating-point streams remain distinct.

Expand packet, shared-memory, and ring capacity for eight-channel
32-bit audio.
2026-11-06 14:34:25 +11:00

1935 lines
58 KiB
C++

/**
* Looking Glass
* Copyright © 2017-2026 The Looking Glass Authors
* https://looking-glass.io
*
* This program is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License as published by the Free
* Software Foundation; either version 2 of the License, or (at your option)
* any later version.
*
* This program is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
* more details.
*
* You should have received a copy of the GNU General Public License along
* with this program; if not, write to the Free Software Foundation, Inc., 59
* Temple Place, Suite 330, Boston, MA 02111-1307 USA
*/
#include "BrokerDevice.h"
#include "Adapter.h"
#include "LGAudioBroker.h"
extern "C"
{
#include <wdmsec.h>
}
namespace
{
constexpr ULONG kBrokerFileSignature = 'fBGL';
constexpr ULONG kBrokerReadPoolTag = 'qBGL';
constexpr LONG kMaximumPendingReads = 32;
constexpr ULONG kReadServiceBatch = 8;
struct BrokerFileContext
{
ULONG signature;
bool bound;
UCHAR reserved[3];
ULONGLONG adapterGeneration;
ULONGLONG bindingGeneration;
ULONGLONG captureStreamGeneration;
ULONGLONG nextCaptureSequence;
CAudioAdapter *adapter;
};
struct BrokerControlExtension
{
KSPIN_LOCK stateLock;
FAST_MUTEX bindingLock;
FAST_MUTEX captureWriteLock;
EX_RUNDOWN_REF bindingRundown;
bool bindingRundownCompleted;
PFILE_OBJECT owner;
BrokerFileContext file;
IO_CSQ readCsq;
KSPIN_LOCK readQueueLock;
LIST_ENTRY readQueue;
KSPIN_LOCK pendingReadLock;
LONG pendingReadCount;
KEVENT readsDrainedEvent;
};
struct BrokerReadContext
{
ULONGLONG adapterGeneration;
ULONGLONG bindingGeneration;
ULONGLONG streamGeneration;
};
struct AdapterSnapshot
{
bool present;
ULONGLONG generation;
ULONGLONG eventSequence;
AudioStreamSnapshot render;
AudioStreamSnapshot capture;
};
struct BindingSnapshot
{
CAudioAdapter *adapter;
ULONGLONG adapterGeneration;
ULONGLONG bindingGeneration;
};
FAST_MUTEX g_controlLifecycleLock;
FAST_MUTEX g_adapterLifecycleLock;
KSPIN_LOCK g_adapterStateLock;
EX_RUNDOWN_REF g_adapterRundown;
EX_RUNDOWN_REF g_controlServiceRundown;
KDPC g_readServiceDpc;
PDRIVER_OBJECT g_driverObject;
PDEVICE_OBJECT g_controlDevice;
CAudioAdapter *g_adapter;
PDEVICE_OBJECT g_adapterDevice;
CAudioAdapter *g_boundAdapter;
volatile LONG g_initialized;
volatile LONG g_retiring;
volatile LONG g_pnpDeviceCount;
bool g_linkCreated;
bool g_rundownCompleted;
bool g_controlServiceRundownCompleted;
volatile LONG64 g_adapterGeneration;
volatile LONG64 g_eventSequence;
volatile LONG64 g_bindingGeneration;
volatile LONG64 g_bindCount;
volatile LONG64 g_unbindCount;
volatile LONG64 g_cleanupCount;
volatile LONG64 g_rejectedRequestCount;
volatile LONG64 g_renderPacketCount;
volatile LONG64 g_renderByteCount;
volatile LONG64 g_renderDropBytes;
volatile LONG64 g_capturePacketCount;
volatile LONG64 g_captureByteCount;
volatile LONG64 g_captureDropBytes;
volatile LONG64 g_captureUnderrunBytes;
volatile LONG64 g_cancelledReadCount;
volatile LONG64 g_renderSequence;
void ReadServiceDpc(
_In_ PKDPC dpc,
_In_opt_ PVOID deferredContext,
_In_opt_ PVOID systemArgument1,
_In_opt_ PVOID systemArgument2);
BrokerControlExtension *GetControlExtension(
_In_ PDEVICE_OBJECT deviceObject)
{
return static_cast<BrokerControlExtension *>(
deviceObject->DeviceExtension);
}
ULONGLONG ReadCounter(_In_ volatile LONG64 *counter)
{
return static_cast<ULONGLONG>(
InterlockedCompareExchange64(counter, 0, 0));
}
void IncrementEventSequence()
{
InterlockedIncrement64(&g_eventSequence);
}
void AddCounter(
_Inout_ volatile LONG64 *counter,
_In_ ULONGLONG value)
{
InterlockedAdd64(counter, static_cast<LONG64>(value));
}
PDEVICE_OBJECT LoadControlDevice()
{
return static_cast<PDEVICE_OBJECT>(
InterlockedCompareExchangePointer(
reinterpret_cast<PVOID volatile *>(&g_controlDevice),
nullptr, nullptr));
}
PDEVICE_OBJECT ExchangeControlDevice(
_In_opt_ PDEVICE_OBJECT deviceObject)
{
return static_cast<PDEVICE_OBJECT>(InterlockedExchangePointer(
reinterpret_cast<PVOID volatile *>(&g_controlDevice),
deviceObject));
}
CAudioAdapter *LoadBoundAdapter()
{
return static_cast<CAudioAdapter *>(
InterlockedCompareExchangePointer(
reinterpret_cast<PVOID volatile *>(&g_boundAdapter),
nullptr, nullptr));
}
NTSTATUS CompleteIrp(
_Inout_ PIRP irp,
_In_ NTSTATUS status,
_In_ ULONG_PTR information = 0)
{
irp->IoStatus.Status = status;
irp->IoStatus.Information = information;
IoCompleteRequest(irp, IO_NO_INCREMENT);
return status;
}
LONG AddPendingRead(_In_ BrokerControlExtension *extension)
{
KIRQL oldIrql;
KeAcquireSpinLock(&extension->pendingReadLock, &oldIrql);
const LONG count = ++extension->pendingReadCount;
if (count == 1)
KeClearEvent(&extension->readsDrainedEvent);
KeReleaseSpinLock(&extension->pendingReadLock, oldIrql);
return count;
}
void ReleasePendingRead(_In_ BrokerControlExtension *extension)
{
KIRQL oldIrql;
KeAcquireSpinLock(&extension->pendingReadLock, &oldIrql);
NT_ASSERT(extension->pendingReadCount > 0);
const LONG count = --extension->pendingReadCount;
if (count == 0)
KeSetEvent(&extension->readsDrainedEvent, IO_NO_INCREMENT, FALSE);
KeReleaseSpinLock(&extension->pendingReadLock, oldIrql);
}
bool HasPendingReads(_In_ BrokerControlExtension *extension)
{
KIRQL oldIrql;
KeAcquireSpinLock(&extension->pendingReadLock, &oldIrql);
const bool pending = extension->pendingReadCount != 0;
KeReleaseSpinLock(&extension->pendingReadLock, oldIrql);
return pending;
}
BrokerControlExtension *GetCsqExtension(_In_ PIO_CSQ csq)
{
return CONTAINING_RECORD(csq, BrokerControlExtension, readCsq);
}
void CsqInsertIrp(_In_ PIO_CSQ csq, _In_ PIRP irp)
{
BrokerControlExtension *extension = GetCsqExtension(csq);
InsertTailList(&extension->readQueue,
&irp->Tail.Overlay.ListEntry);
}
void CsqRemoveIrp(_In_ PIO_CSQ csq, _In_ PIRP irp)
{
UNREFERENCED_PARAMETER(csq);
RemoveEntryList(&irp->Tail.Overlay.ListEntry);
}
PIRP CsqPeekNextIrp(
_In_ PIO_CSQ csq,
_In_opt_ PIRP irp,
_In_opt_ PVOID peekContext)
{
BrokerControlExtension *extension = GetCsqExtension(csq);
PLIST_ENTRY entry = irp ?
irp->Tail.Overlay.ListEntry.Flink :
extension->readQueue.Flink;
while (entry != &extension->readQueue)
{
PIRP candidate = CONTAINING_RECORD(entry, IRP,
Tail.Overlay.ListEntry);
if (!peekContext ||
IoGetCurrentIrpStackLocation(candidate)->FileObject ==
peekContext)
{
return candidate;
}
entry = entry->Flink;
}
return nullptr;
}
void CsqAcquireLock(_In_ PIO_CSQ csq, _Out_ PKIRQL irql)
{
BrokerControlExtension *extension = GetCsqExtension(csq);
KeAcquireSpinLock(&extension->readQueueLock, irql);
}
void CsqReleaseLock(_In_ PIO_CSQ csq, _In_ KIRQL irql)
{
BrokerControlExtension *extension = GetCsqExtension(csq);
KeReleaseSpinLock(&extension->readQueueLock, irql);
}
void CsqCompleteCanceledIrp(_In_ PIO_CSQ csq, _In_ PIRP irp)
{
BrokerControlExtension *extension = GetCsqExtension(csq);
auto *context = static_cast<BrokerReadContext *>(
irp->Tail.Overlay.DriverContext[0]);
irp->Tail.Overlay.DriverContext[0] = nullptr;
if (context)
ExFreePoolWithTag(context, kBrokerReadPoolTag);
InterlockedIncrement64(&g_cancelledReadCount);
CompleteIrp(irp, STATUS_CANCELLED);
ReleasePendingRead(extension);
}
void SetReadContext(
_Inout_ PIRP irp,
_In_ BrokerReadContext *context)
{
irp->Tail.Overlay.DriverContext[0] = context;
}
BrokerReadContext *GetReadContext(_In_ PIRP irp)
{
return static_cast<BrokerReadContext *>(
irp->Tail.Overlay.DriverContext[0]);
}
void FreeReadContext(_Inout_ PIRP irp)
{
BrokerReadContext *context = GetReadContext(irp);
irp->Tail.Overlay.DriverContext[0] = nullptr;
if (context)
ExFreePoolWithTag(context, kBrokerReadPoolTag);
}
void CancelQueuedReads(
_In_ BrokerControlExtension *extension,
_In_opt_ PFILE_OBJECT fileObject,
_In_ NTSTATUS status)
{
PIRP irp;
while ((irp = IoCsqRemoveNextIrp(&extension->readCsq,
fileObject)) != nullptr)
{
FreeReadContext(irp);
InterlockedIncrement64(&g_cancelledReadCount);
CompleteIrp(irp, status);
ReleasePendingRead(extension);
}
}
void DrainQueuedReads(
_In_ BrokerControlExtension *extension,
_In_opt_ PFILE_OBJECT fileObject,
_In_ NTSTATUS status)
{
CancelQueuedReads(extension, fileObject, status);
while (HasPendingReads(extension))
{
KeWaitForSingleObject(&extension->readsDrainedEvent,
Executive, KernelMode, FALSE, nullptr);
}
}
void QueueReadService()
{
if (!ExAcquireRundownProtection(&g_controlServiceRundown))
return;
if (LoadControlDevice() &&
InterlockedCompareExchange(&g_retiring, 0, 0) == 0)
{
KeInsertQueueDpc(&g_readServiceDpc, nullptr, nullptr);
}
ExReleaseRundownProtection(&g_controlServiceRundown);
}
AdapterSnapshot GetAdapterSnapshot()
{
AdapterSnapshot snapshot{};
if (!ExAcquireRundownProtection(&g_adapterRundown))
{
snapshot.generation = ReadCounter(&g_adapterGeneration);
snapshot.eventSequence = ReadCounter(&g_eventSequence);
return snapshot;
}
CAudioAdapter *adapter;
KIRQL oldIrql;
KeAcquireSpinLock(&g_adapterStateLock, &oldIrql);
adapter = g_adapter;
snapshot.present = adapter != nullptr;
snapshot.generation = ReadCounter(&g_adapterGeneration);
snapshot.eventSequence = ReadCounter(&g_eventSequence);
KeReleaseSpinLock(&g_adapterStateLock, oldIrql);
if (adapter)
{
adapter->GetStreamSnapshot(EndpointKind::Render,
&snapshot.render);
adapter->GetStreamSnapshot(EndpointKind::Capture,
&snapshot.capture);
}
ExReleaseRundownProtection(&g_adapterRundown);
return snapshot;
}
bool IsOwnedFile(
_In_ BrokerControlExtension *extension,
_In_opt_ PFILE_OBJECT fileObject)
{
return fileObject && extension->owner == fileObject &&
fileObject->FsContext == &extension->file &&
extension->file.signature == kBrokerFileSignature;
}
NTSTATUS AcquireBinding(
_In_ BrokerControlExtension *extension,
_In_opt_ PFILE_OBJECT fileObject,
_Out_ BindingSnapshot *snapshot)
{
RtlZeroMemory(snapshot, sizeof(*snapshot));
if (!ExAcquireRundownProtection(&extension->bindingRundown))
return STATUS_DEVICE_NOT_READY;
NTSTATUS status = STATUS_SUCCESS;
KIRQL oldIrql;
KeAcquireSpinLock(&extension->stateLock, &oldIrql);
if (!IsOwnedFile(extension, fileObject))
status = STATUS_INVALID_HANDLE;
else if (!extension->file.bound || !extension->file.adapter)
status = STATUS_DEVICE_NOT_READY;
else
{
snapshot->adapter = extension->file.adapter;
snapshot->adapterGeneration =
extension->file.adapterGeneration;
snapshot->bindingGeneration =
extension->file.bindingGeneration;
}
KeReleaseSpinLock(&extension->stateLock, oldIrql);
if (!NT_SUCCESS(status))
ExReleaseRundownProtection(&extension->bindingRundown);
return status;
}
bool IsCurrentAdapter(_In_ const BindingSnapshot &binding)
{
bool current;
KIRQL oldIrql;
KeAcquireSpinLock(&g_adapterStateLock, &oldIrql);
current = g_adapter == binding.adapter &&
ReadCounter(&g_adapterGeneration) ==
binding.adapterGeneration;
KeReleaseSpinLock(&g_adapterStateLock, oldIrql);
return current;
}
CAudioAdapter *UnbindFileLocked(
_In_ BrokerControlExtension *extension)
{
if (!extension->file.bound)
return nullptr;
CAudioAdapter *adapter = extension->file.adapter;
extension->file.bound = false;
extension->file.adapterGeneration = 0;
extension->file.bindingGeneration = 0;
extension->file.captureStreamGeneration = 0;
extension->file.nextCaptureSequence = 0;
extension->file.adapter = nullptr;
InterlockedCompareExchangePointer(
reinterpret_cast<PVOID volatile *>(&g_boundAdapter),
nullptr, adapter);
InterlockedIncrement64(&g_bindingGeneration);
return adapter;
}
void FinishUnbind(
_In_ BrokerControlExtension *extension,
_In_ CAudioAdapter *adapter)
{
if (!adapter)
return;
adapter->SetBrokerActive(false);
ExWaitForRundownProtectionRelease(&extension->bindingRundown);
extension->bindingRundownCompleted = true;
adapter->Release();
InterlockedIncrement64(&g_unbindCount);
IncrementEventSequence();
}
_IRQL_requires_max_(PASSIVE_LEVEL)
void InvalidateControlBinding()
{
ExAcquireFastMutex(&g_controlLifecycleLock);
PDEVICE_OBJECT controlDevice = LoadControlDevice();
if (!controlDevice)
{
ExReleaseFastMutex(&g_controlLifecycleLock);
return;
}
BrokerControlExtension *extension =
GetControlExtension(controlDevice);
CAudioAdapter *adapter;
ExAcquireFastMutex(&extension->bindingLock);
KIRQL oldIrql;
KeAcquireSpinLock(&extension->stateLock, &oldIrql);
adapter = UnbindFileLocked(extension);
KeReleaseSpinLock(&extension->stateLock, oldIrql);
FinishUnbind(extension, adapter);
DrainQueuedReads(extension, nullptr, STATUS_DEVICE_NOT_CONNECTED);
ExReleaseFastMutex(&extension->bindingLock);
ExReleaseFastMutex(&g_controlLifecycleLock);
}
void DeleteSymbolicLinkLocked()
{
if (!g_linkCreated)
return;
UNICODE_STRING dosName =
RTL_CONSTANT_STRING(LGAUDIO_BROKER_DOS_DEVICE_NAME);
IoDeleteSymbolicLink(&dosName);
g_linkCreated = false;
}
void StartControlServiceLocked()
{
if (!g_controlServiceRundownCompleted)
return;
KeRemoveQueueDpc(&g_readServiceDpc);
ExReInitializeRundownProtection(&g_controlServiceRundown);
g_controlServiceRundownCompleted = false;
}
void StopControlServiceLocked()
{
if (g_controlServiceRundownCompleted)
return;
KeRemoveQueueDpc(&g_readServiceDpc);
ExWaitForRundownProtectionRelease(&g_controlServiceRundown);
KeRemoveQueueDpc(&g_readServiceDpc);
g_controlServiceRundownCompleted = true;
}
NTSTATUS CreateControlDeviceLocked()
{
if (LoadControlDevice())
return STATUS_SUCCESS;
if (!g_driverObject)
return STATUS_INVALID_DEVICE_STATE;
UNICODE_STRING ntName =
RTL_CONSTANT_STRING(LGAUDIO_BROKER_NT_DEVICE_NAME);
UNICODE_STRING dosName =
RTL_CONSTANT_STRING(LGAUDIO_BROKER_DOS_DEVICE_NAME);
UNICODE_STRING security = RTL_CONSTANT_STRING(
L"D:P(A;;GA;;;SY)(A;;GA;;;UD)");
PDEVICE_OBJECT deviceObject = nullptr;
NTSTATUS status = IoCreateDeviceSecure(
g_driverObject,
sizeof(BrokerControlExtension),
&ntName,
FILE_DEVICE_UNKNOWN,
FILE_DEVICE_SECURE_OPEN,
FALSE,
&security,
&LGAUDIO_BROKER_DEVICE_CLASS_GUID,
&deviceObject);
if (!NT_SUCCESS(status))
return status;
BrokerControlExtension *extension =
GetControlExtension(deviceObject);
RtlZeroMemory(extension, sizeof(*extension));
KeInitializeSpinLock(&extension->stateLock);
ExInitializeFastMutex(&extension->bindingLock);
ExInitializeFastMutex(&extension->captureWriteLock);
ExInitializeRundownProtection(&extension->bindingRundown);
KeInitializeSpinLock(&extension->readQueueLock);
InitializeListHead(&extension->readQueue);
KeInitializeSpinLock(&extension->pendingReadLock);
KeInitializeEvent(&extension->readsDrainedEvent,
NotificationEvent, TRUE);
status = IoCsqInitialize(&extension->readCsq,
CsqInsertIrp, CsqRemoveIrp, CsqPeekNextIrp,
CsqAcquireLock, CsqReleaseLock, CsqCompleteCanceledIrp);
if (!NT_SUCCESS(status))
{
IoDeleteDevice(deviceObject);
return status;
}
deviceObject->Flags |= DO_DIRECT_IO;
StartControlServiceLocked();
InterlockedExchange(&g_retiring, 0);
const PDEVICE_OBJECT previous = static_cast<PDEVICE_OBJECT>(
InterlockedCompareExchangePointer(
reinterpret_cast<PVOID volatile *>(&g_controlDevice),
deviceObject, nullptr));
if (previous)
{
IoDeleteDevice(deviceObject);
return STATUS_DEVICE_BUSY;
}
status = IoCreateSymbolicLink(&dosName, &ntName);
if (!NT_SUCCESS(status))
{
const PDEVICE_OBJECT removed = ExchangeControlDevice(nullptr);
NT_ASSERT(removed == deviceObject);
UNREFERENCED_PARAMETER(removed);
IoDeleteDevice(deviceObject);
return status;
}
g_linkCreated = true;
deviceObject->Flags &= ~DO_DEVICE_INITIALIZING;
return STATUS_SUCCESS;
}
void SetControlRetiringLocked(_In_ bool retiring)
{
PDEVICE_OBJECT controlDevice = LoadControlDevice();
if (!controlDevice)
return;
BrokerControlExtension *extension =
GetControlExtension(controlDevice);
KIRQL oldIrql;
KeAcquireSpinLock(&extension->stateLock, &oldIrql);
InterlockedExchange(&g_retiring, retiring ? 1 : 0);
KeReleaseSpinLock(&extension->stateLock, oldIrql);
}
PDEVICE_OBJECT TakeRetiredControlDeviceLocked()
{
PDEVICE_OBJECT controlDevice = LoadControlDevice();
if (!controlDevice)
return nullptr;
BrokerControlExtension *extension =
GetControlExtension(controlDevice);
bool takeDevice = false;
KIRQL oldIrql;
KeAcquireSpinLock(&extension->stateLock, &oldIrql);
if (InterlockedCompareExchange(&g_retiring, 0, 0) != 0 &&
!extension->owner && !HasPendingReads(extension))
{
const PDEVICE_OBJECT removed = ExchangeControlDevice(nullptr);
NT_ASSERT(removed == controlDevice);
takeDevice = removed == controlDevice;
}
KeReleaseSpinLock(&extension->stateLock, oldIrql);
return takeDevice ? controlDevice : nullptr;
}
NTSTATUS DispatchCreate(
_In_ PDEVICE_OBJECT deviceObject,
_Inout_ PIRP irp)
{
PIO_STACK_LOCATION stack = IoGetCurrentIrpStackLocation(irp);
PFILE_OBJECT fileObject = stack->FileObject;
if (!fileObject || fileObject->RelatedFileObject ||
fileObject->FileName.Length != 0)
{
InterlockedIncrement64(&g_rejectedRequestCount);
return CompleteIrp(irp, STATUS_INVALID_PARAMETER);
}
BrokerControlExtension *extension =
GetControlExtension(deviceObject);
NTSTATUS status = STATUS_SUCCESS;
KIRQL oldIrql;
KeAcquireSpinLock(&extension->stateLock, &oldIrql);
if (InterlockedCompareExchange(&g_retiring, 0, 0) != 0)
status = STATUS_DELETE_PENDING;
else if (extension->owner)
status = STATUS_SHARING_VIOLATION;
else
{
extension->owner = fileObject;
extension->file.signature = kBrokerFileSignature;
extension->file.bound = false;
extension->file.adapterGeneration = 0;
extension->file.bindingGeneration = 0;
extension->file.captureStreamGeneration = 0;
extension->file.nextCaptureSequence = 0;
extension->file.adapter = nullptr;
fileObject->FsContext = &extension->file;
}
KeReleaseSpinLock(&extension->stateLock, oldIrql);
if (!NT_SUCCESS(status))
InterlockedIncrement64(&g_rejectedRequestCount);
return CompleteIrp(irp, status);
}
NTSTATUS DispatchCleanup(
_In_ PDEVICE_OBJECT deviceObject,
_Inout_ PIRP irp)
{
PIO_STACK_LOCATION stack = IoGetCurrentIrpStackLocation(irp);
BrokerControlExtension *extension =
GetControlExtension(deviceObject);
CAudioAdapter *adapter = nullptr;
bool owned;
ExAcquireFastMutex(&extension->bindingLock);
KIRQL oldIrql;
KeAcquireSpinLock(&extension->stateLock, &oldIrql);
owned = IsOwnedFile(extension, stack->FileObject);
if (owned)
adapter = UnbindFileLocked(extension);
KeReleaseSpinLock(&extension->stateLock, oldIrql);
FinishUnbind(extension, adapter);
if (owned)
DrainQueuedReads(extension, stack->FileObject, STATUS_CANCELLED);
ExReleaseFastMutex(&extension->bindingLock);
InterlockedIncrement64(&g_cleanupCount);
return CompleteIrp(irp, STATUS_SUCCESS);
}
NTSTATUS DispatchClose(
_In_ PDEVICE_OBJECT deviceObject,
_Inout_ PIRP irp)
{
PIO_STACK_LOCATION stack = IoGetCurrentIrpStackLocation(irp);
PFILE_OBJECT fileObject = stack->FileObject;
BrokerControlExtension *extension =
GetControlExtension(deviceObject);
CAudioAdapter *adapter = nullptr;
bool owned;
ExAcquireFastMutex(&extension->bindingLock);
KIRQL oldIrql;
KeAcquireSpinLock(&extension->stateLock, &oldIrql);
owned = IsOwnedFile(extension, fileObject);
if (owned)
adapter = UnbindFileLocked(extension);
KeReleaseSpinLock(&extension->stateLock, oldIrql);
FinishUnbind(extension, adapter);
if (owned)
DrainQueuedReads(extension, fileObject, STATUS_CANCELLED);
KeAcquireSpinLock(&extension->stateLock, &oldIrql);
if (IsOwnedFile(extension, fileObject))
{
extension->owner = nullptr;
extension->file.signature = 0;
if (fileObject)
fileObject->FsContext = nullptr;
}
KeReleaseSpinLock(&extension->stateLock, oldIrql);
ExReleaseFastMutex(&extension->bindingLock);
const NTSTATUS status = CompleteIrp(irp, STATUS_SUCCESS);
PDEVICE_OBJECT deleteDevice = nullptr;
ExAcquireFastMutex(&g_controlLifecycleLock);
deleteDevice = TakeRetiredControlDeviceLocked();
ExReleaseFastMutex(&g_controlLifecycleLock);
if (deleteDevice)
IoDeleteDevice(deleteDevice);
return status;
}
bool ValidateVersionedInput(
_In_reads_bytes_(inputLength) const void *input,
_In_ ULONG inputLength,
_In_ ULONG requiredSize)
{
if (!input || inputLength < sizeof(LGAudioBrokerHeader))
return false;
const auto *header =
static_cast<const LGAudioBrokerHeader *>(input);
return header->size >= requiredSize &&
header->size <= inputLength &&
header->version == LGAUDIO_BROKER_ABI_VERSION;
}
bool ValidatePacketFormat(
_In_ const LGAudioBrokerFormat &format,
_Out_opt_ FormatFingerprint *fingerprint)
{
if (format.reserved)
return false;
SampleFormat sampleFormat = SampleFormat::Unknown;
switch (format.sampleFormat)
{
case LGAUDIO_BROKER_SAMPLE_S16_LE:
if (format.containerBits != 16 || format.validBits != 16)
return false;
sampleFormat = SampleFormat::S16LE;
break;
case LGAUDIO_BROKER_SAMPLE_S24_LE:
if (format.containerBits != 24 || format.validBits != 24)
return false;
sampleFormat = SampleFormat::S24LE;
break;
case LGAUDIO_BROKER_SAMPLE_S32_LE:
if (format.containerBits != 32 || format.validBits != 32)
return false;
sampleFormat = SampleFormat::S32LE;
break;
case LGAUDIO_BROKER_SAMPLE_F32_LE:
if (format.containerBits != 32 || format.validBits != 32)
return false;
sampleFormat = SampleFormat::F32LE;
break;
default:
return false;
}
if (!format.sampleRate ||
format.sampleRate > kMaximumSampleRate ||
!format.channels || format.channels > 8 ||
format.blockAlign !=
format.channels * (format.containerBits / 8))
{
return false;
}
if (fingerprint)
{
fingerprint->sampleFormat = sampleFormat;
fingerprint->sampleRate = format.sampleRate;
fingerprint->channelMask = format.channelMask;
fingerprint->channels = format.channels;
fingerprint->containerBits = format.containerBits;
fingerprint->validBits = format.validBits;
fingerprint->blockAlign = format.blockAlign;
}
return true;
}
void CopyBrokerFormat(
_In_ const FormatFingerprint &source,
_Out_ LGAudioBrokerFormat *destination)
{
switch (source.sampleFormat)
{
case SampleFormat::S16LE:
destination->sampleFormat = LGAUDIO_BROKER_SAMPLE_S16_LE;
break;
case SampleFormat::S24LE:
destination->sampleFormat = LGAUDIO_BROKER_SAMPLE_S24_LE;
break;
case SampleFormat::S32LE:
destination->sampleFormat = LGAUDIO_BROKER_SAMPLE_S32_LE;
break;
case SampleFormat::F32LE:
destination->sampleFormat = LGAUDIO_BROKER_SAMPLE_F32_LE;
break;
default:
destination->sampleFormat = 0;
break;
}
destination->sampleRate = source.sampleRate;
destination->channelMask = source.channelMask;
destination->channels = source.channels;
destination->containerBits = source.containerBits;
destination->validBits = source.validBits;
destination->blockAlign = source.blockAlign;
destination->reserved = 0;
}
UINT32 GetBrokerStreamState(_In_ KSSTATE state)
{
switch (state)
{
case KSSTATE_STOP:
return LGAUDIO_BROKER_STREAM_STOPPED;
case KSSTATE_ACQUIRE:
return LGAUDIO_BROKER_STREAM_ACQUIRED;
case KSSTATE_PAUSE:
return LGAUDIO_BROKER_STREAM_PAUSED;
case KSSTATE_RUN:
return LGAUDIO_BROKER_STREAM_RUNNING;
default:
return LGAUDIO_BROKER_STREAM_UNAVAILABLE;
}
}
void FillBrokerStreamState(
_In_ const AudioStreamSnapshot &source,
_Out_ LGAudioBrokerStreamState *destination)
{
destination->generation = source.generation;
destination->state = GetBrokerStreamState(source.state);
if (source.formatValid)
{
destination->flags |= LGAUDIO_BROKER_STREAM_FORMAT_VALID;
CopyBrokerFormat(source.format, &destination->format);
}
}
NTSTATUS QueryCaps(
_Inout_ PIRP irp,
_In_ ULONG outputLength)
{
if (outputLength < sizeof(LGAudioBrokerCaps))
return CompleteIrp(irp, STATUS_BUFFER_TOO_SMALL,
sizeof(LGAudioBrokerCaps));
auto *caps = static_cast<LGAudioBrokerCaps *>(
irp->AssociatedIrp.SystemBuffer);
RtlZeroMemory(caps, sizeof(*caps));
caps->size = sizeof(*caps);
caps->version = LGAUDIO_BROKER_ABI_VERSION;
caps->minimumVersion = LGAUDIO_BROKER_ABI_VERSION;
caps->maximumVersion = LGAUDIO_BROKER_ABI_VERSION;
caps->capabilities = LGAUDIO_BROKER_CAP_CONTROL |
LGAUDIO_BROKER_CAP_RENDER_READ |
LGAUDIO_BROKER_CAP_CAPTURE_WRITE |
LGAUDIO_BROKER_CAP_CLOCK_CONTROL;
caps->maximumRenderTransfer = LGAUDIO_BROKER_MAX_PAYLOAD_BYTES;
caps->maximumCaptureTransfer = LGAUDIO_BROKER_MAX_PAYLOAD_BYTES;
return CompleteIrp(irp, STATUS_SUCCESS, sizeof(*caps));
}
NTSTATUS QueryState(
_In_ BrokerControlExtension *extension,
_In_ PFILE_OBJECT fileObject,
_Inout_ PIRP irp,
_In_ ULONG outputLength)
{
if (outputLength < sizeof(LGAudioBrokerState))
return CompleteIrp(irp, STATUS_BUFFER_TOO_SMALL,
sizeof(LGAudioBrokerState));
const AdapterSnapshot snapshot = GetAdapterSnapshot();
bool bound = false;
ULONGLONG bindingGeneration;
KIRQL oldIrql;
KeAcquireSpinLock(&extension->stateLock, &oldIrql);
bindingGeneration = ReadCounter(&g_bindingGeneration);
if (IsOwnedFile(extension, fileObject))
{
bound = extension->file.bound && snapshot.present &&
extension->file.adapterGeneration == snapshot.generation &&
extension->file.adapter == LoadBoundAdapter();
}
KeReleaseSpinLock(&extension->stateLock, oldIrql);
auto *state = static_cast<LGAudioBrokerState *>(
irp->AssociatedIrp.SystemBuffer);
RtlZeroMemory(state, sizeof(*state));
state->size = sizeof(*state);
state->version = LGAUDIO_BROKER_ABI_VERSION;
state->adapterGeneration = snapshot.generation;
state->eventSequence = snapshot.eventSequence;
state->bindingGeneration = bindingGeneration;
if (snapshot.present)
{
state->flags |= LGAUDIO_BROKER_STATE_ADAPTER_PRESENT;
FillBrokerStreamState(snapshot.render, &state->render);
FillBrokerStreamState(snapshot.capture, &state->capture);
}
else
{
state->render.state = LGAUDIO_BROKER_STREAM_UNAVAILABLE;
state->capture.state = LGAUDIO_BROKER_STREAM_UNAVAILABLE;
}
if (bound)
{
state->flags |= LGAUDIO_BROKER_STATE_BOUND |
LGAUDIO_BROKER_STATE_DATA_PLANE_ACTIVE;
}
return CompleteIrp(irp, STATUS_SUCCESS, sizeof(*state));
}
NTSTATUS Bind(
_In_ BrokerControlExtension *extension,
_In_ PFILE_OBJECT fileObject,
_Inout_ PIRP irp,
_In_ ULONG inputLength,
_In_ ULONG outputLength)
{
const void *input = irp->AssociatedIrp.SystemBuffer;
if (!ValidateVersionedInput(input, inputLength,
sizeof(LGAudioBrokerBind)))
return CompleteIrp(irp, STATUS_INVALID_PARAMETER);
if (outputLength < sizeof(LGAudioBrokerBind))
return CompleteIrp(irp, STATUS_BUFFER_TOO_SMALL,
sizeof(LGAudioBrokerBind));
auto *bind = static_cast<LGAudioBrokerBind *>(
irp->AssociatedIrp.SystemBuffer);
if (bind->flags || bind->reserved0 || bind->reserved[0] ||
bind->reserved[1] || bind->bindingGeneration)
return CompleteIrp(irp, STATUS_INVALID_PARAMETER);
ULONGLONG generation = 0;
CAudioAdapter *adapter = BrokerDeviceAcquireAdapter(&generation);
if (!adapter)
return CompleteIrp(irp, STATUS_DEVICE_NOT_READY);
NTSTATUS status = STATUS_SUCCESS;
if (bind->adapterGeneration != generation)
status = STATUS_RETRY;
else
{
ExAcquireFastMutex(&extension->bindingLock);
if (extension->bindingRundownCompleted)
{
ExReInitializeRundownProtection(&extension->bindingRundown);
extension->bindingRundownCompleted = false;
}
KIRQL oldIrql;
KeAcquireSpinLock(&extension->stateLock, &oldIrql);
if (!IsOwnedFile(extension, fileObject))
status = STATUS_INVALID_HANDLE;
else if (extension->file.bound)
status = STATUS_INVALID_DEVICE_STATE;
else if (LoadBoundAdapter())
status = STATUS_DEVICE_BUSY;
KeReleaseSpinLock(&extension->stateLock, oldIrql);
if (NT_SUCCESS(status))
{
const ULONGLONG bindingGeneration = static_cast<ULONGLONG>(
InterlockedIncrement64(&g_bindingGeneration));
adapter->AddRef();
adapter->SetBrokerActive(true);
InterlockedExchangePointer(
reinterpret_cast<PVOID volatile *>(&g_boundAdapter), adapter);
KeAcquireSpinLock(&extension->stateLock, &oldIrql);
extension->file.bound = true;
extension->file.adapterGeneration = generation;
extension->file.bindingGeneration = bindingGeneration;
extension->file.captureStreamGeneration = 0;
extension->file.nextCaptureSequence = 0;
extension->file.adapter = adapter;
bind->bindingGeneration = bindingGeneration;
KeReleaseSpinLock(&extension->stateLock, oldIrql);
}
ExReleaseFastMutex(&extension->bindingLock);
}
BrokerDeviceReleaseAdapter();
if (NT_SUCCESS(status))
{
InterlockedIncrement64(&g_bindCount);
IncrementEventSequence();
}
return CompleteIrp(irp, status,
NT_SUCCESS(status) ? sizeof(*bind) : 0);
}
NTSTATUS Unbind(
_In_ BrokerControlExtension *extension,
_In_ PFILE_OBJECT fileObject,
_Inout_ PIRP irp,
_In_ ULONG inputLength)
{
const void *input = irp->AssociatedIrp.SystemBuffer;
if (!ValidateVersionedInput(input, inputLength,
sizeof(LGAudioBrokerHeader)))
return CompleteIrp(irp, STATUS_INVALID_PARAMETER);
CAudioAdapter *adapter = nullptr;
NTSTATUS status = STATUS_SUCCESS;
ExAcquireFastMutex(&extension->bindingLock);
KIRQL oldIrql;
KeAcquireSpinLock(&extension->stateLock, &oldIrql);
if (!IsOwnedFile(extension, fileObject))
status = STATUS_INVALID_HANDLE;
else
adapter = UnbindFileLocked(extension);
KeReleaseSpinLock(&extension->stateLock, oldIrql);
FinishUnbind(extension, adapter);
DrainQueuedReads(extension, fileObject, STATUS_CANCELLED);
ExReleaseFastMutex(&extension->bindingLock);
return CompleteIrp(irp, status);
}
NTSTATUS SetClockControl(
_In_ BrokerControlExtension *extension,
_In_ PFILE_OBJECT fileObject,
_Inout_ PIRP irp,
_In_ ULONG inputLength)
{
const void *input = irp->AssociatedIrp.SystemBuffer;
if (!ValidateVersionedInput(input, inputLength,
sizeof(LGAudioBrokerClockControl)))
return CompleteIrp(irp, STATUS_INVALID_PARAMETER);
const auto *control =
static_cast<const LGAudioBrokerClockControl *>(input);
if (control->flags || control->reserved0 ||
control->reserved[0] || control->reserved[1] ||
!control->adapterGeneration || !control->bindingGeneration ||
!control->streamGeneration || !control->targetRateQ32)
{
return CompleteIrp(irp, STATUS_INVALID_PARAMETER);
}
BindingSnapshot binding{};
NTSTATUS status = AcquireBinding(extension, fileObject, &binding);
if (NT_SUCCESS(status) &&
(control->adapterGeneration != binding.adapterGeneration ||
control->bindingGeneration != binding.bindingGeneration))
{
status = STATUS_RETRY;
}
if (NT_SUCCESS(status) && !IsCurrentAdapter(binding))
status = STATUS_DEVICE_NOT_CONNECTED;
if (NT_SUCCESS(status))
{
status = binding.adapter->SetRenderClockRate(
control->streamGeneration, control->targetRateQ32);
}
if (binding.adapter)
ExReleaseRundownProtection(&extension->bindingRundown);
return CompleteIrp(irp, status);
}
NTSTATUS QueryStats(
_Inout_ PIRP irp,
_In_ ULONG outputLength)
{
if (outputLength < sizeof(LGAudioBrokerStats))
return CompleteIrp(irp, STATUS_BUFFER_TOO_SMALL,
sizeof(LGAudioBrokerStats));
auto *stats = static_cast<LGAudioBrokerStats *>(
irp->AssociatedIrp.SystemBuffer);
RtlZeroMemory(stats, sizeof(*stats));
stats->size = sizeof(*stats);
stats->version = LGAUDIO_BROKER_ABI_VERSION;
stats->adapterGeneration = ReadCounter(&g_adapterGeneration);
stats->eventSequence = ReadCounter(&g_eventSequence);
stats->bindingGeneration = ReadCounter(&g_bindingGeneration);
stats->bindCount = ReadCounter(&g_bindCount);
stats->unbindCount = ReadCounter(&g_unbindCount);
stats->cleanupCount = ReadCounter(&g_cleanupCount);
stats->rejectedRequestCount =
ReadCounter(&g_rejectedRequestCount);
stats->renderPacketCount = ReadCounter(&g_renderPacketCount);
stats->renderByteCount = ReadCounter(&g_renderByteCount);
stats->renderDropBytes = ReadCounter(&g_renderDropBytes);
stats->capturePacketCount = ReadCounter(&g_capturePacketCount);
stats->captureByteCount = ReadCounter(&g_captureByteCount);
stats->captureDropBytes = ReadCounter(&g_captureDropBytes);
stats->captureUnderrunBytes =
ReadCounter(&g_captureUnderrunBytes);
stats->cancelledReadCount = ReadCounter(&g_cancelledReadCount);
return CompleteIrp(irp, STATUS_SUCCESS, sizeof(*stats));
}
void *MapDirectBuffer(
_In_ PIRP irp,
_In_ ULONG requiredBytes)
{
if (!irp->MdlAddress ||
MmGetMdlByteCount(irp->MdlAddress) < requiredBytes)
{
return nullptr;
}
return MmGetSystemAddressForMdlSafe(irp->MdlAddress,
NormalPagePriority | MdlMappingNoExecute);
}
void CompletePendingRead(
_In_ BrokerControlExtension *extension,
_Inout_ PIRP irp,
_In_ NTSTATUS status,
_In_ ULONG_PTR information = 0)
{
FreeReadContext(irp);
if (status == STATUS_CANCELLED ||
status == STATUS_DEVICE_NOT_READY ||
status == STATUS_DEVICE_NOT_CONNECTED ||
status == STATUS_RETRY)
{
InterlockedIncrement64(&g_cancelledReadCount);
}
CompleteIrp(irp, status, information);
ReleasePendingRead(extension);
}
NTSTATUS DispatchRead(
_In_ PDEVICE_OBJECT deviceObject,
_Inout_ PIRP irp)
{
PIO_STACK_LOCATION stack = IoGetCurrentIrpStackLocation(irp);
const ULONG length = stack->Parameters.Read.Length;
if (length <= sizeof(LGAudioBrokerPacketHeader) ||
length > sizeof(LGAudioBrokerPacketHeader) +
LGAUDIO_BROKER_MAX_PAYLOAD_BYTES ||
!MapDirectBuffer(irp, length))
{
return CompleteIrp(irp, STATUS_INVALID_PARAMETER);
}
BrokerControlExtension *extension =
GetControlExtension(deviceObject);
BindingSnapshot binding{};
NTSTATUS status = AcquireBinding(extension, stack->FileObject,
&binding);
if (!NT_SUCCESS(status))
return CompleteIrp(irp, status);
AudioStreamSnapshot render{};
binding.adapter->GetStreamSnapshot(EndpointKind::Render, &render);
const ULONG payloadCapacity =
length - sizeof(LGAudioBrokerPacketHeader);
if (!IsCurrentAdapter(binding))
status = STATUS_DEVICE_NOT_CONNECTED;
else if (!render.formatValid || render.state != KSSTATE_RUN)
status = STATUS_DEVICE_NOT_READY;
else if (!render.format.blockAlign ||
payloadCapacity < render.format.blockAlign)
status = STATUS_INVALID_PARAMETER;
if (!NT_SUCCESS(status))
{
ExReleaseRundownProtection(&extension->bindingRundown);
return CompleteIrp(irp, status);
}
#pragma warning(push)
#pragma warning(disable:4996) // Required for pre-2004 Windows 10 releases.
auto *context = static_cast<BrokerReadContext *>(
ExAllocatePoolWithTag(NonPagedPoolNx, sizeof(BrokerReadContext),
kBrokerReadPoolTag));
#pragma warning(pop)
if (!context)
{
ExReleaseRundownProtection(&extension->bindingRundown);
return CompleteIrp(irp, STATUS_INSUFFICIENT_RESOURCES);
}
context->adapterGeneration = binding.adapterGeneration;
context->bindingGeneration = binding.bindingGeneration;
context->streamGeneration = render.generation;
SetReadContext(irp, context);
const LONG pending = AddPendingRead(extension);
if (pending > kMaximumPendingReads)
{
ReleasePendingRead(extension);
FreeReadContext(irp);
ExReleaseRundownProtection(&extension->bindingRundown);
return CompleteIrp(irp, STATUS_DEVICE_BUSY);
}
IoMarkIrpPending(irp);
IoCsqInsertIrp(&extension->readCsq, irp, nullptr);
ExReleaseRundownProtection(&extension->bindingRundown);
QueueReadService();
return STATUS_PENDING;
}
bool ServiceReadIrp(
_In_ BrokerControlExtension *extension,
_Inout_ PIRP irp)
{
BrokerReadContext *context = GetReadContext(irp);
PIO_STACK_LOCATION stack = IoGetCurrentIrpStackLocation(irp);
if (!context ||
!ExAcquireRundownProtection(&extension->bindingRundown))
{
CompletePendingRead(extension, irp, STATUS_DEVICE_NOT_READY);
return true;
}
BindingSnapshot binding{};
NTSTATUS status = STATUS_SUCCESS;
KIRQL oldIrql;
KeAcquireSpinLock(&extension->stateLock, &oldIrql);
if (!IsOwnedFile(extension, stack->FileObject) ||
!extension->file.bound || !extension->file.adapter)
{
status = STATUS_DEVICE_NOT_READY;
}
else
{
binding.adapter = extension->file.adapter;
binding.adapterGeneration = extension->file.adapterGeneration;
binding.bindingGeneration = extension->file.bindingGeneration;
}
KeReleaseSpinLock(&extension->stateLock, oldIrql);
if (NT_SUCCESS(status) &&
(binding.adapterGeneration != context->adapterGeneration ||
binding.bindingGeneration != context->bindingGeneration))
{
status = STATUS_RETRY;
}
if (NT_SUCCESS(status) && !IsCurrentAdapter(binding))
status = STATUS_DEVICE_NOT_CONNECTED;
AudioStreamSnapshot render{};
if (NT_SUCCESS(status))
{
binding.adapter->GetStreamSnapshot(EndpointKind::Render, &render);
if (!render.formatValid || render.state != KSSTATE_RUN)
status = STATUS_DEVICE_NOT_READY;
else if (render.generation != context->streamGeneration)
status = STATUS_RETRY;
}
const ULONG length = stack->Parameters.Read.Length;
auto *buffer = static_cast<BYTE *>(MapDirectBuffer(irp, length));
if (NT_SUCCESS(status) && !buffer)
status = STATUS_INSUFFICIENT_RESOURCES;
ULONG bytesRead = 0;
bool discontinuity = false;
if (NT_SUCCESS(status))
{
const ULONG capacity =
length - sizeof(LGAudioBrokerPacketHeader);
BYTE *payload = buffer + sizeof(LGAudioBrokerPacketHeader);
status = binding.adapter->ReadRenderFrames(render.format,
render.generation, payload, capacity, &bytesRead,
&discontinuity);
}
if (NT_SUCCESS(status) && !bytesRead)
{
IoCsqInsertIrp(&extension->readCsq, irp, nullptr);
ExReleaseRundownProtection(&extension->bindingRundown);
return false;
}
if (NT_SUCCESS(status))
{
LGAudioBrokerPacketHeader packet{};
packet.size = sizeof(packet);
packet.version = LGAUDIO_BROKER_ABI_VERSION;
packet.flags = discontinuity ?
LGAUDIO_BROKER_PACKET_DISCONTINUITY : 0;
packet.direction = LGAUDIO_BROKER_PACKET_RENDER;
packet.adapterGeneration = binding.adapterGeneration;
packet.bindingGeneration = binding.bindingGeneration;
packet.streamGeneration = render.generation;
packet.sequence = static_cast<ULONGLONG>(
InterlockedIncrement64(&g_renderSequence) - 1);
CopyBrokerFormat(render.format, &packet.format);
packet.frameCount = bytesRead / render.format.blockAlign;
packet.payloadBytes = bytesRead;
RtlCopyMemory(buffer, &packet, sizeof(packet));
InterlockedIncrement64(&g_renderPacketCount);
AddCounter(&g_renderByteCount, bytesRead);
}
const ULONG_PTR information = NT_SUCCESS(status) ?
sizeof(LGAudioBrokerPacketHeader) + bytesRead : 0;
CompletePendingRead(extension, irp, status, information);
ExReleaseRundownProtection(&extension->bindingRundown);
return true;
}
void ReadServiceDpc(
_In_ PKDPC dpc,
_In_opt_ PVOID deferredContext,
_In_opt_ PVOID systemArgument1,
_In_opt_ PVOID systemArgument2)
{
UNREFERENCED_PARAMETER(dpc);
UNREFERENCED_PARAMETER(deferredContext);
UNREFERENCED_PARAMETER(systemArgument1);
UNREFERENCED_PARAMETER(systemArgument2);
if (!ExAcquireRundownProtection(&g_controlServiceRundown))
return;
PDEVICE_OBJECT controlDevice = LoadControlDevice();
ULONG serviced = 0;
if (controlDevice)
{
BrokerControlExtension *extension =
GetControlExtension(controlDevice);
while (serviced < kReadServiceBatch)
{
PIRP irp = IoCsqRemoveNextIrp(&extension->readCsq, nullptr);
if (!irp)
break;
if (!ServiceReadIrp(extension, irp))
break;
++serviced;
}
if (serviced == kReadServiceBatch &&
HasPendingReads(extension))
{
KeInsertQueueDpc(&g_readServiceDpc, nullptr, nullptr);
}
}
ExReleaseRundownProtection(&g_controlServiceRundown);
}
NTSTATUS DispatchWrite(
_In_ PDEVICE_OBJECT deviceObject,
_Inout_ PIRP irp)
{
PIO_STACK_LOCATION stack = IoGetCurrentIrpStackLocation(irp);
const ULONG length = stack->Parameters.Write.Length;
if (length <= sizeof(LGAudioBrokerPacketHeader) ||
length > sizeof(LGAudioBrokerPacketHeader) +
LGAUDIO_BROKER_MAX_PAYLOAD_BYTES)
{
return CompleteIrp(irp, STATUS_INVALID_PARAMETER);
}
const auto *mapped = static_cast<const BYTE *>(
MapDirectBuffer(irp, length));
if (!mapped)
return CompleteIrp(irp, STATUS_INVALID_PARAMETER);
LGAudioBrokerPacketHeader packet{};
RtlCopyMemory(&packet, mapped, sizeof(packet));
FormatFingerprint format{};
const UINT32 allowedFlags =
LGAUDIO_BROKER_PACKET_DISCONTINUITY;
if (packet.size != sizeof(packet) ||
packet.version != LGAUDIO_BROKER_ABI_VERSION ||
(packet.flags & ~allowedFlags) != 0 ||
packet.direction != LGAUDIO_BROKER_PACKET_CAPTURE ||
packet.reserved[0] || packet.reserved[1] ||
!packet.payloadBytes ||
packet.payloadBytes > LGAUDIO_BROKER_MAX_PAYLOAD_BYTES ||
packet.payloadBytes != length - sizeof(packet) ||
!ValidatePacketFormat(packet.format, &format) ||
!packet.frameCount ||
(packet.payloadBytes % format.blockAlign) != 0 ||
packet.frameCount != packet.payloadBytes / format.blockAlign)
{
return CompleteIrp(irp, STATUS_INVALID_PARAMETER);
}
BrokerControlExtension *extension =
GetControlExtension(deviceObject);
ExAcquireFastMutex(&extension->captureWriteLock);
BindingSnapshot binding{};
NTSTATUS status = AcquireBinding(extension, stack->FileObject,
&binding);
if (NT_SUCCESS(status) &&
(packet.adapterGeneration != binding.adapterGeneration ||
packet.bindingGeneration != binding.bindingGeneration))
{
status = STATUS_RETRY;
}
if (NT_SUCCESS(status) && !IsCurrentAdapter(binding))
status = STATUS_DEVICE_NOT_CONNECTED;
AudioStreamSnapshot capture{};
if (NT_SUCCESS(status))
{
binding.adapter->GetStreamSnapshot(EndpointKind::Capture,
&capture);
if (!capture.formatValid || capture.state != KSSTATE_RUN)
status = STATUS_DEVICE_NOT_READY;
else if (packet.streamGeneration != capture.generation ||
!EqualFormatFingerprint(format, capture.format))
status = STATUS_RETRY;
}
if (NT_SUCCESS(status))
{
KIRQL oldIrql;
KeAcquireSpinLock(&extension->stateLock, &oldIrql);
if (!extension->file.bound ||
extension->file.adapter != binding.adapter ||
extension->file.bindingGeneration !=
binding.bindingGeneration)
{
status = STATUS_DEVICE_NOT_READY;
}
else if (extension->file.captureStreamGeneration !=
capture.generation)
{
if (packet.sequence != 0)
status = STATUS_RETRY;
}
else if (packet.sequence !=
extension->file.nextCaptureSequence)
{
status = STATUS_RETRY;
}
KeReleaseSpinLock(&extension->stateLock, oldIrql);
}
ULONG droppedBytes = 0;
if (NT_SUCCESS(status))
{
const BYTE *payload =
mapped + sizeof(LGAudioBrokerPacketHeader);
status = binding.adapter->WriteCaptureFrames(format,
capture.generation,
(packet.flags & LGAUDIO_BROKER_PACKET_DISCONTINUITY) != 0,
payload, packet.payloadBytes, &droppedBytes);
}
if (NT_SUCCESS(status))
{
KIRQL oldIrql;
KeAcquireSpinLock(&extension->stateLock, &oldIrql);
if (!extension->file.bound ||
extension->file.bindingGeneration !=
binding.bindingGeneration)
{
status = STATUS_DEVICE_NOT_READY;
}
else
{
extension->file.captureStreamGeneration = capture.generation;
extension->file.nextCaptureSequence = packet.sequence + 1;
}
KeReleaseSpinLock(&extension->stateLock, oldIrql);
}
if (NT_SUCCESS(status))
{
InterlockedIncrement64(&g_capturePacketCount);
AddCounter(&g_captureByteCount, packet.payloadBytes);
AddCounter(&g_captureDropBytes, droppedBytes);
}
ExReleaseFastMutex(&extension->captureWriteLock);
const NTSTATUS completedStatus = CompleteIrp(irp, status,
NT_SUCCESS(status) ? length : 0);
if (binding.adapter)
ExReleaseRundownProtection(&extension->bindingRundown);
return completedStatus;
}
NTSTATUS DispatchDeviceControl(
_In_ PDEVICE_OBJECT deviceObject,
_Inout_ PIRP irp)
{
PIO_STACK_LOCATION stack = IoGetCurrentIrpStackLocation(irp);
PFILE_OBJECT fileObject = stack->FileObject;
BrokerControlExtension *extension =
GetControlExtension(deviceObject);
KIRQL oldIrql;
KeAcquireSpinLock(&extension->stateLock, &oldIrql);
const bool owner = IsOwnedFile(extension, fileObject);
KeReleaseSpinLock(&extension->stateLock, oldIrql);
if (!owner)
{
InterlockedIncrement64(&g_rejectedRequestCount);
return CompleteIrp(irp, STATUS_INVALID_HANDLE);
}
const ULONG inputLength =
stack->Parameters.DeviceIoControl.InputBufferLength;
const ULONG outputLength =
stack->Parameters.DeviceIoControl.OutputBufferLength;
NTSTATUS status;
switch (stack->Parameters.DeviceIoControl.IoControlCode)
{
case IOCTL_LGAUDIO_BROKER_QUERY_CAPS:
status = QueryCaps(irp, outputLength);
break;
case IOCTL_LGAUDIO_BROKER_QUERY_STATE:
status = QueryState(extension, fileObject, irp, outputLength);
break;
case IOCTL_LGAUDIO_BROKER_BIND:
status = Bind(extension, fileObject, irp, inputLength,
outputLength);
break;
case IOCTL_LGAUDIO_BROKER_UNBIND:
status = Unbind(extension, fileObject, irp, inputLength);
break;
case IOCTL_LGAUDIO_BROKER_QUERY_STATS:
status = QueryStats(irp, outputLength);
break;
case IOCTL_LGAUDIO_BROKER_SET_CLOCK_CONTROL:
status = SetClockControl(extension, fileObject, irp, inputLength);
break;
default:
status = CompleteIrp(irp, STATUS_INVALID_DEVICE_REQUEST);
break;
}
if (!NT_SUCCESS(status))
InterlockedIncrement64(&g_rejectedRequestCount);
return status;
}
}
_IRQL_requires_max_(PASSIVE_LEVEL)
NTSTATUS BrokerDeviceInitialize(_In_ PDRIVER_OBJECT driverObject)
{
if (!driverObject)
return STATUS_INVALID_PARAMETER;
if (InterlockedCompareExchange(&g_initialized, 1, 0) != 0)
return STATUS_SUCCESS;
ExInitializeFastMutex(&g_controlLifecycleLock);
ExInitializeFastMutex(&g_adapterLifecycleLock);
KeInitializeSpinLock(&g_adapterStateLock);
ExInitializeRundownProtection(&g_adapterRundown);
ExInitializeRundownProtection(&g_controlServiceRundown);
KeInitializeDpc(&g_readServiceDpc, ReadServiceDpc, nullptr);
g_driverObject = driverObject;
ExAcquireFastMutex(&g_controlLifecycleLock);
const NTSTATUS status = CreateControlDeviceLocked();
ExReleaseFastMutex(&g_controlLifecycleLock);
return status;
}
_IRQL_requires_max_(PASSIVE_LEVEL)
void BrokerDeviceShutdown()
{
if (InterlockedCompareExchange(&g_initialized, 0, 1) != 1)
return;
InvalidateControlBinding();
ExAcquireFastMutex(&g_adapterLifecycleLock);
bool waitForRundown = false;
KIRQL oldIrql;
KeAcquireSpinLock(&g_adapterStateLock, &oldIrql);
if (g_adapter)
{
g_adapter = nullptr;
g_adapterDevice = nullptr;
InterlockedIncrement64(&g_adapterGeneration);
IncrementEventSequence();
waitForRundown = true;
}
KeReleaseSpinLock(&g_adapterStateLock, oldIrql);
if (waitForRundown)
{
ExWaitForRundownProtectionRelease(&g_adapterRundown);
g_rundownCompleted = true;
}
ExReleaseFastMutex(&g_adapterLifecycleLock);
PDEVICE_OBJECT deleteDevice = nullptr;
ExAcquireFastMutex(&g_controlLifecycleLock);
SetControlRetiringLocked(true);
DeleteSymbolicLinkLocked();
StopControlServiceLocked();
deleteDevice = TakeRetiredControlDeviceLocked();
NT_ASSERT(deleteDevice || !LoadControlDevice());
ExReleaseFastMutex(&g_controlLifecycleLock);
if (deleteDevice)
IoDeleteDevice(deleteDevice);
g_driverObject = nullptr;
}
_IRQL_requires_max_(DISPATCH_LEVEL)
bool BrokerDeviceIsControlDevice(_In_ PDEVICE_OBJECT deviceObject)
{
return deviceObject && deviceObject == LoadControlDevice();
}
_IRQL_requires_max_(APC_LEVEL)
NTSTATUS BrokerDeviceDispatch(
_In_ PDEVICE_OBJECT deviceObject,
_Inout_ PIRP irp)
{
if (!deviceObject || !irp)
return STATUS_INVALID_PARAMETER;
const UCHAR major = IoGetCurrentIrpStackLocation(irp)->MajorFunction;
switch (major)
{
case IRP_MJ_CREATE:
return DispatchCreate(deviceObject, irp);
case IRP_MJ_CLEANUP:
return DispatchCleanup(deviceObject, irp);
case IRP_MJ_CLOSE:
return DispatchClose(deviceObject, irp);
case IRP_MJ_DEVICE_CONTROL:
return DispatchDeviceControl(deviceObject, irp);
case IRP_MJ_FLUSH_BUFFERS:
return CompleteIrp(irp, STATUS_SUCCESS);
case IRP_MJ_READ:
{
const NTSTATUS status = DispatchRead(deviceObject, irp);
if (!NT_SUCCESS(status))
InterlockedIncrement64(&g_rejectedRequestCount);
return status;
}
case IRP_MJ_WRITE:
{
const NTSTATUS status = DispatchWrite(deviceObject, irp);
if (!NT_SUCCESS(status))
InterlockedIncrement64(&g_rejectedRequestCount);
return status;
}
default:
InterlockedIncrement64(&g_rejectedRequestCount);
return CompleteIrp(irp, STATUS_INVALID_DEVICE_REQUEST);
}
}
_IRQL_requires_max_(PASSIVE_LEVEL)
void BrokerDeviceNotifyPnpDeviceAdded()
{
const LONG count = InterlockedIncrement(&g_pnpDeviceCount);
if (count != 1 || !g_initialized)
return;
ExAcquireFastMutex(&g_controlLifecycleLock);
if (!LoadControlDevice())
{
const NTSTATUS status = CreateControlDeviceLocked();
if (!NT_SUCCESS(status))
{
LGAUDIO_TRACE_ERROR("broker control device unavailable: 0x%08X",
status);
}
}
else if (InterlockedCompareExchange(&g_retiring, 0, 0) != 0)
{
UNICODE_STRING ntName =
RTL_CONSTANT_STRING(LGAUDIO_BROKER_NT_DEVICE_NAME);
UNICODE_STRING dosName =
RTL_CONSTANT_STRING(LGAUDIO_BROKER_DOS_DEVICE_NAME);
const NTSTATUS status = IoCreateSymbolicLink(&dosName, &ntName);
if (NT_SUCCESS(status))
{
g_linkCreated = true;
StartControlServiceLocked();
SetControlRetiringLocked(false);
}
else
{
LGAUDIO_TRACE_ERROR("broker link unavailable: 0x%08X", status);
}
}
ExReleaseFastMutex(&g_controlLifecycleLock);
}
_IRQL_requires_max_(PASSIVE_LEVEL)
void BrokerDeviceNotifyPnpDeviceRemoved()
{
LONG count = InterlockedDecrement(&g_pnpDeviceCount);
if (count > 0)
return;
if (count < 0)
InterlockedExchange(&g_pnpDeviceCount, 0);
InvalidateControlBinding();
PDEVICE_OBJECT deleteDevice = nullptr;
ExAcquireFastMutex(&g_controlLifecycleLock);
SetControlRetiringLocked(true);
DeleteSymbolicLinkLocked();
StopControlServiceLocked();
deleteDevice = TakeRetiredControlDeviceLocked();
ExReleaseFastMutex(&g_controlLifecycleLock);
if (deleteDevice)
IoDeleteDevice(deleteDevice);
}
_IRQL_requires_max_(PASSIVE_LEVEL)
NTSTATUS BrokerDeviceAttachAdapter(
_In_ PDEVICE_OBJECT deviceObject,
_In_ CAudioAdapter *adapter)
{
if (!deviceObject || !adapter || !g_initialized)
return STATUS_INVALID_PARAMETER;
LGAudioPortClsDeviceContext *deviceContext =
GetLGAudioDeviceContext(deviceObject);
adapter->AddRef();
if (InterlockedCompareExchangePointer(&deviceContext->adapter,
adapter, nullptr) != nullptr)
{
adapter->Release();
return STATUS_DEVICE_BUSY;
}
NTSTATUS status = STATUS_SUCCESS;
ExAcquireFastMutex(&g_adapterLifecycleLock);
if (g_adapter)
status = STATUS_DEVICE_BUSY;
else
{
if (g_rundownCompleted)
{
ExReInitializeRundownProtection(&g_adapterRundown);
g_rundownCompleted = false;
}
KIRQL oldIrql;
KeAcquireSpinLock(&g_adapterStateLock, &oldIrql);
g_adapter = adapter;
g_adapterDevice = deviceObject;
InterlockedIncrement64(&g_adapterGeneration);
IncrementEventSequence();
KeReleaseSpinLock(&g_adapterStateLock, oldIrql);
}
ExReleaseFastMutex(&g_adapterLifecycleLock);
if (!NT_SUCCESS(status))
{
InterlockedExchangePointer(&deviceContext->adapter, nullptr);
adapter->Release();
}
return status;
}
_IRQL_requires_max_(PASSIVE_LEVEL)
void BrokerDeviceDetachAdapter(_In_ PDEVICE_OBJECT deviceObject)
{
if (!deviceObject || !g_initialized)
return;
LGAudioPortClsDeviceContext *deviceContext =
GetLGAudioDeviceContext(deviceObject);
auto *adapter = static_cast<CAudioAdapter *>(
InterlockedExchangePointer(&deviceContext->adapter, nullptr));
if (!adapter)
return;
bool detached = false;
ExAcquireFastMutex(&g_adapterLifecycleLock);
KIRQL oldIrql;
KeAcquireSpinLock(&g_adapterStateLock, &oldIrql);
if (g_adapter == adapter && g_adapterDevice == deviceObject)
{
g_adapter = nullptr;
g_adapterDevice = nullptr;
InterlockedIncrement64(&g_adapterGeneration);
IncrementEventSequence();
detached = true;
}
KeReleaseSpinLock(&g_adapterStateLock, oldIrql);
if (detached)
{
ExWaitForRundownProtectionRelease(&g_adapterRundown);
g_rundownCompleted = true;
InvalidateControlBinding();
}
ExReleaseFastMutex(&g_adapterLifecycleLock);
adapter->Release();
}
_IRQL_requires_max_(DISPATCH_LEVEL)
CAudioAdapter *BrokerDeviceAcquireAdapter(
_Out_opt_ PULONGLONG generation)
{
if (generation)
*generation = 0;
if (!g_initialized ||
!ExAcquireRundownProtection(&g_adapterRundown))
return nullptr;
CAudioAdapter *adapter;
KIRQL oldIrql;
KeAcquireSpinLock(&g_adapterStateLock, &oldIrql);
adapter = g_adapter;
if (adapter && generation)
*generation = ReadCounter(&g_adapterGeneration);
KeReleaseSpinLock(&g_adapterStateLock, oldIrql);
if (!adapter)
ExReleaseRundownProtection(&g_adapterRundown);
return adapter;
}
_IRQL_requires_max_(DISPATCH_LEVEL)
void BrokerDeviceReleaseAdapter()
{
ExReleaseRundownProtection(&g_adapterRundown);
}
_IRQL_requires_max_(DISPATCH_LEVEL)
void BrokerDeviceNotifyStreamChanged(
_In_ CAudioAdapter *adapter,
_In_ EndpointKind kind)
{
bool current;
KIRQL oldIrql;
KeAcquireSpinLock(&g_adapterStateLock, &oldIrql);
current = adapter && g_adapter == adapter;
KeReleaseSpinLock(&g_adapterStateLock, oldIrql);
if (!current)
return;
IncrementEventSequence();
if (kind == EndpointKind::Render)
QueueReadService();
}
_IRQL_requires_max_(DISPATCH_LEVEL)
void BrokerDeviceNotifyRenderAvailable(
_In_ CAudioAdapter *adapter,
_In_ ULONG bytes,
_In_ ULONG droppedBytes)
{
UNREFERENCED_PARAMETER(bytes);
if (!adapter || LoadBoundAdapter() != adapter)
return;
AddCounter(&g_renderDropBytes, droppedBytes);
QueueReadService();
}
_IRQL_requires_max_(DISPATCH_LEVEL)
void BrokerDeviceRecordCaptureUnderrun(
_In_ CAudioAdapter *adapter,
_In_ ULONGLONG bytes)
{
if (adapter && LoadBoundAdapter() == adapter)
AddCounter(&g_captureUnderrunBytes, bytes);
}