diff --git a/idd/LGAudio/Adapter.cpp b/idd/LGAudio/Adapter.cpp index 6ccd20ca..225cef1c 100644 --- a/idd/LGAudio/Adapter.cpp +++ b/idd/LGAudio/Adapter.cpp @@ -19,6 +19,7 @@ */ #include "Adapter.h" +#include "BrokerDevice.h" #include "Formats.h" #include "MiniportTopology.h" #include "MiniportWaveRT.h" @@ -237,6 +238,17 @@ NTSTATUS StartAudioDevice( status = PcRegisterPhysicalConnection(deviceObject, topologyCapture, kCaptureTopologyBridgePin, waveCapture, kCaptureWaveBridgePin); + if (NT_SUCCESS(status)) + { + const NTSTATUS brokerStatus = + BrokerDeviceAttachAdapter(deviceObject, adapter); + if (!NT_SUCCESS(brokerStatus)) + { + LGAUDIO_TRACE_ERROR("broker adapter unavailable: 0x%08X", + brokerStatus); + } + } + SafeRelease(waveCapture); SafeRelease(topologyCapture); SafeRelease(waveRender); diff --git a/idd/LGAudio/BrokerDevice.cpp b/idd/LGAudio/BrokerDevice.cpp new file mode 100644 index 00000000..3ef147c3 --- /dev/null +++ b/idd/LGAudio/BrokerDevice.cpp @@ -0,0 +1,925 @@ +/** + * 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 +} + +namespace +{ + constexpr ULONG kBrokerFileSignature = 'fBGL'; + + struct BrokerFileContext + { + ULONG signature; + bool bound; + UCHAR reserved[3]; + ULONGLONG adapterGeneration; + ULONGLONG bindingGeneration; + }; + + struct BrokerControlExtension + { + KSPIN_LOCK stateLock; + PFILE_OBJECT owner; + BrokerFileContext file; + }; + + struct AdapterSnapshot + { + bool present; + ULONGLONG generation; + ULONGLONG eventSequence; + }; + + FAST_MUTEX g_controlLifecycleLock; + FAST_MUTEX g_adapterLifecycleLock; + KSPIN_LOCK g_adapterStateLock; + EX_RUNDOWN_REF g_adapterRundown; + + PDRIVER_OBJECT g_driverObject; + PDEVICE_OBJECT g_controlDevice; + CAudioAdapter *g_adapter; + PDEVICE_OBJECT g_adapterDevice; + + volatile LONG g_initialized; + volatile LONG g_retiring; + volatile LONG g_pnpDeviceCount; + bool g_linkCreated; + bool g_rundownCompleted; + + 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; + + BrokerControlExtension *GetControlExtension( + _In_ PDEVICE_OBJECT deviceObject) + { + return static_cast( + deviceObject->DeviceExtension); + } + + ULONGLONG ReadCounter(_In_ volatile LONG64 *counter) + { + return static_cast( + InterlockedCompareExchange64(counter, 0, 0)); + } + + void IncrementEventSequence() + { + InterlockedIncrement64(&g_eventSequence); + } + + PDEVICE_OBJECT LoadControlDevice() + { + return static_cast( + InterlockedCompareExchangePointer( + reinterpret_cast(&g_controlDevice), + nullptr, nullptr)); + } + + PDEVICE_OBJECT ExchangeControlDevice( + _In_opt_ PDEVICE_OBJECT deviceObject) + { + return static_cast(InterlockedExchangePointer( + reinterpret_cast(&g_controlDevice), + deviceObject)); + } + + 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; + } + + AdapterSnapshot GetAdapterSnapshot() + { + AdapterSnapshot snapshot{}; + + KIRQL oldIrql; + KeAcquireSpinLock(&g_adapterStateLock, &oldIrql); + snapshot.present = g_adapter != nullptr; + snapshot.generation = ReadCounter(&g_adapterGeneration); + snapshot.eventSequence = ReadCounter(&g_eventSequence); + KeReleaseSpinLock(&g_adapterStateLock, oldIrql); + 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; + } + + bool UnbindFileLocked(_In_ BrokerControlExtension *extension) + { + if (!extension->file.bound) + return false; + + extension->file.bound = false; + extension->file.adapterGeneration = 0; + extension->file.bindingGeneration = 0; + InterlockedIncrement64(&g_bindingGeneration); + return true; + } + + _IRQL_requires_max_(PASSIVE_LEVEL) + void InvalidateControlBinding() + { + ExAcquireFastMutex(&g_controlLifecycleLock); + PDEVICE_OBJECT controlDevice = LoadControlDevice(); + if (!controlDevice) + { + ExReleaseFastMutex(&g_controlLifecycleLock); + return; + } + + BrokerControlExtension *extension = + GetControlExtension(controlDevice); + bool unbound; + + KIRQL oldIrql; + KeAcquireSpinLock(&extension->stateLock, &oldIrql); + unbound = UnbindFileLocked(extension); + KeReleaseSpinLock(&extension->stateLock, oldIrql); + ExReleaseFastMutex(&g_controlLifecycleLock); + + if (unbound) + { + InterlockedIncrement64(&g_unbindCount); + IncrementEventSequence(); + } + } + + void DeleteSymbolicLinkLocked() + { + if (!g_linkCreated) + return; + + UNICODE_STRING dosName = + RTL_CONSTANT_STRING(LGAUDIO_BROKER_DOS_DEVICE_NAME); + IoDeleteSymbolicLink(&dosName); + g_linkCreated = false; + } + + 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); + + deviceObject->Flags |= DO_DIRECT_IO; + + InterlockedExchange(&g_retiring, 0); + const PDEVICE_OBJECT previous = static_cast( + InterlockedCompareExchangePointer( + reinterpret_cast(&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) + { + 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; + 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); + bool unbound = false; + + KIRQL oldIrql; + KeAcquireSpinLock(&extension->stateLock, &oldIrql); + if (IsOwnedFile(extension, stack->FileObject)) + unbound = UnbindFileLocked(extension); + KeReleaseSpinLock(&extension->stateLock, oldIrql); + + InterlockedIncrement64(&g_cleanupCount); + if (unbound) + { + InterlockedIncrement64(&g_unbindCount); + IncrementEventSequence(); + } + 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); + bool unbound = false; + + KIRQL oldIrql; + KeAcquireSpinLock(&extension->stateLock, &oldIrql); + if (IsOwnedFile(extension, fileObject)) + { + unbound = UnbindFileLocked(extension); + extension->owner = nullptr; + extension->file.signature = 0; + if (fileObject) + fileObject->FsContext = nullptr; + } + KeReleaseSpinLock(&extension->stateLock, oldIrql); + + if (unbound) + { + InterlockedIncrement64(&g_unbindCount); + IncrementEventSequence(); + } + + 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(input); + return header->size >= requiredSize && + header->size <= inputLength && + header->version == LGAUDIO_BROKER_ABI_VERSION; + } + + 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( + 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; + 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; + } + KeReleaseSpinLock(&extension->stateLock, oldIrql); + + auto *state = static_cast( + 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; + state->render.state = LGAUDIO_BROKER_STREAM_UNAVAILABLE; + state->capture.state = LGAUDIO_BROKER_STREAM_UNAVAILABLE; + if (snapshot.present) + state->flags |= LGAUDIO_BROKER_STATE_ADAPTER_PRESENT; + if (bound) + state->flags |= LGAUDIO_BROKER_STATE_BOUND; + + 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( + 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 + { + 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 + { + const ULONGLONG bindingGeneration = static_cast( + InterlockedIncrement64(&g_bindingGeneration)); + extension->file.bound = true; + extension->file.adapterGeneration = generation; + extension->file.bindingGeneration = bindingGeneration; + bind->bindingGeneration = bindingGeneration; + } + KeReleaseSpinLock(&extension->stateLock, oldIrql); + } + + 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); + + bool unbound = false; + NTSTATUS status = STATUS_SUCCESS; + KIRQL oldIrql; + KeAcquireSpinLock(&extension->stateLock, &oldIrql); + if (!IsOwnedFile(extension, fileObject)) + status = STATUS_INVALID_HANDLE; + else + unbound = UnbindFileLocked(extension); + KeReleaseSpinLock(&extension->stateLock, oldIrql); + + if (unbound) + { + InterlockedIncrement64(&g_unbindCount); + IncrementEventSequence(); + } + 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( + 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); + return CompleteIrp(irp, STATUS_SUCCESS, sizeof(*stats)); + } + + 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; + + 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); + 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(); + 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: + case IRP_MJ_WRITE: + InterlockedIncrement64(&g_rejectedRequestCount); + return CompleteIrp(irp, STATUS_NOT_SUPPORTED); + + 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; + 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(); + 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( + 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); +} diff --git a/idd/LGAudio/BrokerDevice.h b/idd/LGAudio/BrokerDevice.h new file mode 100644 index 00000000..f78aaaa3 --- /dev/null +++ b/idd/LGAudio/BrokerDevice.h @@ -0,0 +1,77 @@ +/** + * 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 + */ + +#pragma once + +#include "Common.h" + +class CAudioAdapter; + +struct LGAudioPortClsDeviceContext +{ + PVOID volatile adapter; +}; + +constexpr ULONG kLGAudioPortClsDeviceExtensionSize = + PORT_CLASS_DEVICE_EXTENSION_SIZE + + sizeof(LGAudioPortClsDeviceContext); + +inline LGAudioPortClsDeviceContext *GetLGAudioDeviceContext( + _In_ PDEVICE_OBJECT deviceObject) +{ + return reinterpret_cast( + static_cast(deviceObject->DeviceExtension) + + PORT_CLASS_DEVICE_EXTENSION_SIZE); +} + +_IRQL_requires_max_(PASSIVE_LEVEL) +NTSTATUS BrokerDeviceInitialize(_In_ PDRIVER_OBJECT driverObject); + +_IRQL_requires_max_(PASSIVE_LEVEL) +void BrokerDeviceShutdown(); + +_IRQL_requires_max_(DISPATCH_LEVEL) +bool BrokerDeviceIsControlDevice(_In_ PDEVICE_OBJECT deviceObject); + +_IRQL_requires_max_(APC_LEVEL) +NTSTATUS BrokerDeviceDispatch( + _In_ PDEVICE_OBJECT deviceObject, + _Inout_ PIRP irp); + +_IRQL_requires_max_(PASSIVE_LEVEL) +void BrokerDeviceNotifyPnpDeviceAdded(); + +_IRQL_requires_max_(PASSIVE_LEVEL) +void BrokerDeviceNotifyPnpDeviceRemoved(); + +_IRQL_requires_max_(PASSIVE_LEVEL) +NTSTATUS BrokerDeviceAttachAdapter( + _In_ PDEVICE_OBJECT deviceObject, + _In_ CAudioAdapter *adapter); + +_IRQL_requires_max_(PASSIVE_LEVEL) +void BrokerDeviceDetachAdapter(_In_ PDEVICE_OBJECT deviceObject); + +_IRQL_requires_max_(DISPATCH_LEVEL) +CAudioAdapter *BrokerDeviceAcquireAdapter( + _Out_opt_ PULONGLONG generation); + +_IRQL_requires_max_(DISPATCH_LEVEL) +void BrokerDeviceReleaseAdapter(); diff --git a/idd/LGAudio/Driver.cpp b/idd/LGAudio/Driver.cpp index 09afb44a..5c2444e3 100644 --- a/idd/LGAudio/Driver.cpp +++ b/idd/LGAudio/Driver.cpp @@ -20,18 +20,77 @@ #include "Common.h" #include "Adapter.h" +#include "BrokerDevice.h" extern "C" DRIVER_INITIALIZE DriverEntry; namespace { +PDRIVER_UNLOAD g_portClsUnload = nullptr; + NTSTATUS AddAudioDevice( _In_ PDRIVER_OBJECT driverObject, _In_ PDEVICE_OBJECT physicalDeviceObject) { constexpr ULONG kSubdeviceCount = 4; - return PcAddAdapterDevice(driverObject, physicalDeviceObject, - StartAudioDevice, kSubdeviceCount, 0); + const NTSTATUS status = PcAddAdapterDevice(driverObject, + physicalDeviceObject, StartAudioDevice, kSubdeviceCount, + kLGAudioPortClsDeviceExtensionSize); + if (NT_SUCCESS(status)) + BrokerDeviceNotifyPnpDeviceAdded(); + return status; +} + +NTSTATUS DispatchIrp( + _In_ PDEVICE_OBJECT deviceObject, + _Inout_ PIRP irp) +{ + if (BrokerDeviceIsControlDevice(deviceObject)) + return BrokerDeviceDispatch(deviceObject, irp); + + PIO_STACK_LOCATION stack = IoGetCurrentIrpStackLocation(irp); + const bool removeDevice = + stack->MajorFunction == IRP_MJ_PNP && + stack->MinorFunction == IRP_MN_REMOVE_DEVICE; + if (stack->MajorFunction == IRP_MJ_PNP && + (removeDevice || + stack->MinorFunction == IRP_MN_STOP_DEVICE || + stack->MinorFunction == IRP_MN_SURPRISE_REMOVAL)) + BrokerDeviceDetachAdapter(deviceObject); + + const NTSTATUS status = PcDispatchIrp(deviceObject, irp); + if (removeDevice) + BrokerDeviceNotifyPnpDeviceRemoved(); + return status; +} + +void DriverUnload(_In_ PDRIVER_OBJECT driverObject) +{ + BrokerDeviceShutdown(); + if (g_portClsUnload) + g_portClsUnload(driverObject); +} + +void InstallDispatchHooks(_In_ PDRIVER_OBJECT driverObject) +{ + constexpr UCHAR majors[] = + { + IRP_MJ_CLOSE, + IRP_MJ_CREATE, + IRP_MJ_DEVICE_CONTROL, + IRP_MJ_FLUSH_BUFFERS, + IRP_MJ_PNP, + IRP_MJ_POWER, + IRP_MJ_QUERY_SECURITY, + IRP_MJ_READ, + IRP_MJ_SET_SECURITY, + IRP_MJ_SYSTEM_CONTROL, + IRP_MJ_WRITE, + IRP_MJ_CLEANUP + }; + + for (const UCHAR major : majors) + driverObject->MajorFunction[major] = DispatchIrp; } } @@ -39,8 +98,22 @@ extern "C" NTSTATUS DriverEntry( _In_ PDRIVER_OBJECT driverObject, _In_ PUNICODE_STRING registryPath) { - return PcInitializeAdapterDriver(driverObject, registryPath, + NTSTATUS status = PcInitializeAdapterDriver(driverObject, registryPath, AddAudioDevice); + if (!NT_SUCCESS(status)) + return status; + + g_portClsUnload = driverObject->DriverUnload; + InstallDispatchHooks(driverObject); + driverObject->DriverUnload = DriverUnload; + + const NTSTATUS brokerStatus = BrokerDeviceInitialize(driverObject); + if (!NT_SUCCESS(brokerStatus)) + { + LGAUDIO_TRACE_ERROR("broker initialization unavailable: 0x%08X", + brokerStatus); + } + return STATUS_SUCCESS; } #pragma warning(push) diff --git a/idd/LGAudio/LGAudio.vcxproj b/idd/LGAudio/LGAudio.vcxproj index 9e6cfb16..51387873 100644 --- a/idd/LGAudio/LGAudio.vcxproj +++ b/idd/LGAudio/LGAudio.vcxproj @@ -54,7 +54,7 @@ - $(ProjectDir);$(DDK_INC_PATH);%(AdditionalIncludeDirectories) + $(ProjectDir);$(ProjectDir)..\LGCommon;$(DDK_INC_PATH);%(AdditionalIncludeDirectories) _WIN32;_WIN32_WINNT=0x0A00;UNICODE;_UNICODE;PC_IMPLEMENTATION;_USE_WAVERT_;_NEW_DELETE_OPERATORS_;%(PreprocessorDefinitions) stdcpp17 Level4 @@ -66,7 +66,7 @@ _WIN32;_WIN32_WINNT=0x0A00;UNICODE;_UNICODE;PC_IMPLEMENTATION;_USE_WAVERT_;%(PreprocessorDefinitions) - portcls.lib;stdunk.lib;libcntpr.lib;%(AdditionalDependencies) + portcls.lib;stdunk.lib;libcntpr.lib;wdmsec.lib;%(AdditionalDependencies) SHA256 @@ -75,6 +75,7 @@ + @@ -86,6 +87,7 @@ + @@ -93,6 +95,7 @@ + diff --git a/idd/LGAudio/LGAudio.vcxproj.filters b/idd/LGAudio/LGAudio.vcxproj.filters index 04b66721..7be7ba55 100644 --- a/idd/LGAudio/LGAudio.vcxproj.filters +++ b/idd/LGAudio/LGAudio.vcxproj.filters @@ -18,6 +18,7 @@ Driver Driver + Driver Driver Driver Miniport @@ -29,6 +30,7 @@ Driver Driver + Driver Driver Driver Miniport @@ -36,6 +38,9 @@ Miniport Utilities Utilities + + Driver + Package diff --git a/idd/LGCommon/LGAudioBroker.h b/idd/LGCommon/LGAudioBroker.h new file mode 100644 index 00000000..9dff30fc --- /dev/null +++ b/idd/LGCommon/LGAudioBroker.h @@ -0,0 +1,181 @@ +/** + * 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 + */ + +#pragma once + +#if defined(_KERNEL_MODE) || defined(_NTDDK_) || defined(_WDMDDK_) +extern "C" +{ +#include +} +#else +#include +#include +#endif + +static constexpr wchar_t LGAUDIO_BROKER_NT_DEVICE_NAME[] = + L"\\Device\\LookingGlassAudioBroker"; +static constexpr wchar_t LGAUDIO_BROKER_DOS_DEVICE_NAME[] = + L"\\DosDevices\\LookingGlassAudioBroker"; +static constexpr wchar_t LGAUDIO_BROKER_USER_DEVICE_PATH[] = + L"\\\\.\\LookingGlassAudioBroker"; + +// This GUID identifies the security policy for the named control device. It +// is not a PnP device-interface class. +static const GUID LGAUDIO_BROKER_DEVICE_CLASS_GUID = + { 0x98f877f1, 0x73d6, 0x4429, + { 0xb4, 0x90, 0x28, 0xc5, 0x30, 0xd2, 0x38, 0xcd } }; + +static constexpr UINT32 LGAUDIO_BROKER_ABI_VERSION = 1U; + +enum LGAudioBrokerCapability : UINT32 +{ + LGAUDIO_BROKER_CAP_CONTROL = 1U << 0, + // These remain clear until the corresponding data plane is implemented. + LGAUDIO_BROKER_CAP_RENDER_READ = 1U << 1, + LGAUDIO_BROKER_CAP_CAPTURE_WRITE = 1U << 2, + LGAUDIO_BROKER_CAP_CLOCK_CONTROL = 1U << 3 +}; + +enum LGAudioBrokerStateFlag : UINT32 +{ + LGAUDIO_BROKER_STATE_ADAPTER_PRESENT = 1U << 0, + LGAUDIO_BROKER_STATE_BOUND = 1U << 1, + LGAUDIO_BROKER_STATE_DATA_PLANE_ACTIVE = 1U << 2 +}; + +enum LGAudioBrokerStreamStateValue : UINT32 +{ + LGAUDIO_BROKER_STREAM_UNAVAILABLE = 0, + LGAUDIO_BROKER_STREAM_STOPPED = 1, + LGAUDIO_BROKER_STREAM_ACQUIRED = 2, + LGAUDIO_BROKER_STREAM_PAUSED = 3, + LGAUDIO_BROKER_STREAM_RUNNING = 4 +}; + +static constexpr UINT32 IOCTL_LGAUDIO_BROKER_QUERY_CAPS = + CTL_CODE(FILE_DEVICE_UNKNOWN, 0x900, METHOD_BUFFERED, + FILE_READ_ACCESS | FILE_WRITE_ACCESS); +static constexpr UINT32 IOCTL_LGAUDIO_BROKER_QUERY_STATE = + CTL_CODE(FILE_DEVICE_UNKNOWN, 0x901, METHOD_BUFFERED, + FILE_READ_ACCESS | FILE_WRITE_ACCESS); +static constexpr UINT32 IOCTL_LGAUDIO_BROKER_BIND = + CTL_CODE(FILE_DEVICE_UNKNOWN, 0x902, METHOD_BUFFERED, + FILE_READ_ACCESS | FILE_WRITE_ACCESS); +static constexpr UINT32 IOCTL_LGAUDIO_BROKER_UNBIND = + CTL_CODE(FILE_DEVICE_UNKNOWN, 0x903, METHOD_BUFFERED, + FILE_READ_ACCESS | FILE_WRITE_ACCESS); +static constexpr UINT32 IOCTL_LGAUDIO_BROKER_QUERY_STATS = + CTL_CODE(FILE_DEVICE_UNKNOWN, 0x904, METHOD_BUFFERED, + FILE_READ_ACCESS | FILE_WRITE_ACCESS); + +#pragma pack(push, 8) + +struct LGAudioBrokerHeader +{ + UINT32 size; + UINT32 version; +}; + +struct LGAudioBrokerFormat +{ + UINT32 sampleRate; + UINT32 channelMask; + UINT16 channels; + UINT16 containerBits; + UINT16 validBits; + UINT16 blockAlign; +}; + +struct LGAudioBrokerStreamState +{ + UINT64 generation; + UINT32 state; + UINT32 flags; + LGAudioBrokerFormat format; +}; + +struct LGAudioBrokerCaps +{ + UINT32 size; + UINT32 version; + UINT32 minimumVersion; + UINT32 maximumVersion; + UINT32 capabilities; + UINT32 maximumRenderTransfer; + UINT32 maximumCaptureTransfer; + UINT32 reserved0; + UINT64 reserved[4]; +}; + +struct LGAudioBrokerState +{ + UINT32 size; + UINT32 version; + UINT32 flags; + UINT32 reserved0; + UINT64 adapterGeneration; + UINT64 eventSequence; + UINT64 bindingGeneration; + LGAudioBrokerStreamState render; + LGAudioBrokerStreamState capture; +}; + +struct LGAudioBrokerBind +{ + UINT32 size; + UINT32 version; + UINT32 flags; + UINT32 reserved0; + UINT64 adapterGeneration; + UINT64 bindingGeneration; + UINT64 reserved[2]; +}; + +struct LGAudioBrokerStats +{ + UINT32 size; + UINT32 version; + UINT64 adapterGeneration; + UINT64 eventSequence; + UINT64 bindingGeneration; + UINT64 bindCount; + UINT64 unbindCount; + UINT64 cleanupCount; + UINT64 rejectedRequestCount; + UINT64 reserved[2]; +}; + +#pragma pack(pop) + +static_assert(sizeof(LGAudioBrokerHeader) == 8, + "LGAudio broker header layout changed"); +static_assert(sizeof(LGAudioBrokerFormat) == 16, + "LGAudio broker format layout changed"); +static_assert(sizeof(LGAudioBrokerStreamState) == 32, + "LGAudio broker stream-state layout changed"); +static_assert(sizeof(LGAudioBrokerCaps) == 64, + "LGAudio broker capabilities layout changed"); +static_assert(sizeof(LGAudioBrokerState) == 104, + "LGAudio broker state layout changed"); +static_assert(sizeof(LGAudioBrokerBind) == 48, + "LGAudio broker binding layout changed"); +static_assert(sizeof(LGAudioBrokerStats) == 80, + "LGAudio broker statistics layout changed");