diff --git a/SarTest/SarTest.cpp b/SarTest/SarTest.cpp index 7639651..fbb19e4 100644 --- a/SarTest/SarTest.cpp +++ b/SarTest/SarTest.cpp @@ -68,6 +68,7 @@ int usage() " --id-prefix (endpoint IDs; default: the name prefix)\n" "Other options: --results --phase-timeout S --no-sarasio-log\n" " --keep-config (host/run: do not rewrite default.json)\n" + " --app-routing (turn on application routing in default.json)\n" " --registered (use the COM-registered SarAsio instead of a path)\n" " --wait S --wait-gone S --restart-delay MS --rate HZ\n" " --min-valid RATIO --max-discontinuities N\n" @@ -145,7 +146,7 @@ bool prepareConfig(const Args& args, const EndpointLayout& layout, } if (!writeDriverConfig(layout, driverClsid, - args.getInt(L"wavert-min-frames", 0), path)) { + args.getInt(L"wavert-min-frames", 0), path, args.has(L"app-routing"))) { return false; } @@ -211,7 +212,7 @@ int cmdConfig(const Args& args) std::wstring out = args.get(L"out", configurationPath().c_str()); if (!writeDriverConfig(layout, SAR_TEST_CLOCK_CLSID_STR, - args.getInt(L"wavert-min-frames", 0), out)) { + args.getInt(L"wavert-min-frames", 0), out, args.has(L"app-routing"))) { return 1; } diff --git a/SarTest/install.cpp b/SarTest/install.cpp index 18e1957..0d3986b 100644 --- a/SarTest/install.cpp +++ b/SarTest/install.cpp @@ -58,7 +58,8 @@ std::wstring loggingPath() bool writeDriverConfig( const EndpointLayout& layout, const std::wstring& driverClsid, - int waveRtMinimumFrames, const std::wstring& path) + int waveRtMinimumFrames, const std::wstring& path, + bool applicationRouting) { std::vector endpoints; @@ -80,7 +81,7 @@ bool writeDriverConfig( JsonObject root; root.setString("driverClsid", narrow(driverClsid)) - .setBool("enableApplicationRouting", false) + .setBool("enableApplicationRouting", applicationRouting) .setInt("waveRtMinimumFrames", waveRtMinimumFrames) .setRaw("endpoints", jsonArray(endpoints)) .setRaw("applications", "[]"); diff --git a/SarTest/install.h b/SarTest/install.h index a029223..748dd86 100644 --- a/SarTest/install.h +++ b/SarTest/install.h @@ -28,10 +28,12 @@ std::wstring configurationPath(); std::wstring loggingPath(); // Writes a SarAsio default.json describing the layout, with the given ASIO -// driver CLSID as the physical interface and application routing disabled. +// driver CLSID as the physical interface. Application routing (no rules, so +// only the redirection of the audio device enumerator) is off unless asked. bool writeDriverConfig( const EndpointLayout& layout, const std::wstring& driverClsid, - int waveRtMinimumFrames, const std::wstring& path); + int waveRtMinimumFrames, const std::wstring& path, + bool applicationRouting = false); // Software device node for the SAR driver, matching the INF's hardware ID. bool deviceNodeExists(); diff --git a/SynchronousAudioRouter/entry.cpp b/SynchronousAudioRouter/entry.cpp index 91d4049..62f9936 100644 --- a/SynchronousAudioRouter/entry.cpp +++ b/SynchronousAudioRouter/entry.cpp @@ -718,7 +718,6 @@ NTSTATUS SarIrpDeviceControl(PDEVICE_OBJECT deviceObject, PIRP irp) VOID SarUnload(PDRIVER_OBJECT driverObject) { SAR_INFO("SAR is unloading"); - KIRQL irql; SarDriverExtension *extension = (SarDriverExtension *)IoGetDriverObjectExtension( driverObject, DriverEntry); @@ -731,12 +730,15 @@ VOID SarUnload(PDRIVER_OBJECT driverObject) CmUnRegisterCallback(extension->filterCookie); } - irql = ExAcquireSpinLockExclusive(&extension->registryRedirectLock); + KeEnterCriticalRegion(); + ExAcquireResourceExclusiveLite(&extension->registryRedirectLock, TRUE); SarClearStringTable( &extension->registryRedirectTableWow64, SarDeleteRegistryRedirect); SarClearStringTable( &extension->registryRedirectTable, SarDeleteRegistryRedirect); - ExReleaseSpinLockExclusive(&extension->registryRedirectLock, irql); + ExReleaseResourceLite(&extension->registryRedirectLock); + KeLeaveCriticalRegion(); + ExDeleteResourceLite(&extension->registryRedirectLock); if (extension->filterUser) { // Allocated by SeQueryInformationToken @@ -768,9 +770,6 @@ BOOL SarFilterMatchesPath( { PRTL_AVL_TABLE table = &extension->registryRedirectTable; PVOID entry = nullptr; - KIRQL irql; - UNICODE_STRING nonpagedPath; - NTSTATUS status = STATUS_SUCCESS; if (!path) { return FALSE; @@ -782,24 +781,20 @@ BOOL SarFilterMatchesPath( } #endif - // TODO: we get a path that's in paged memory from the configuration manager - // so we can't access it with IRQL raised to dispatch level by the spinlock. - // Probably better to just use an ERESOURCE or something here instead. - status = SarStringDuplicate(&nonpagedPath, path); - - if (!NT_SUCCESS(status)) { - return FALSE; - } - - irql = ExAcquireSpinLockShared(&extension->registryRedirectLock); - entry = SarGetStringTableEntry(table, &nonpagedPath); + // Stay at PASSIVE_LEVEL: the path from the configuration manager is in + // paged memory and the table's string comparison is pageable code. Under + // a spin lock this bugchecked (IRQL_NOT_LESS_OR_EQUAL) whenever that code + // was paged out. + KeEnterCriticalRegion(); + ExAcquireResourceSharedLite(&extension->registryRedirectLock, TRUE); + entry = SarGetStringTableEntry(table, path); if (entry) { *redirectPath = *(PCUNICODE_STRING)entry; } - ExReleaseSpinLockShared(&extension->registryRedirectLock, irql); - SarStringFree(&nonpagedPath); + ExReleaseResourceLite(&extension->registryRedirectLock); + KeLeaveCriticalRegion(); return entry != nullptr; } @@ -1129,6 +1124,7 @@ extern "C" NTSTATUS DriverEntry( RtlZeroMemory(extension, sizeof(SarDriverExtension)); ExInitializeFastMutex(&extension->mutex); + ExInitializeResourceLite(&extension->registryRedirectLock); SarInitializeTable(&extension->controlContextTable); SarInitializeStringTable(&extension->registryRedirectTableWow64); SarInitializeStringTable(&extension->registryRedirectTable); diff --git a/SynchronousAudioRouter/sar.h b/SynchronousAudioRouter/sar.h index d0733d6..f24fc92 100644 --- a/SynchronousAudioRouter/sar.h +++ b/SynchronousAudioRouter/sar.h @@ -210,11 +210,12 @@ typedef struct SarDriverExtension // This shouldn't really be needed as long as CmUnRegisterCallback can't // complete while one of our callback routines is still running, but there // is no explicit documentation of that, so we protect the registry redirect - // tables with a reader writer spinlock "just in case." Note that + // tables with a reader writer lock "just in case." Note that // RTL_AVL_TABLE is used here instead of RTL_GENERIC_TABLE to avoid - // splaying which would be unsafe with a read-only lock. All related - // structures must be in nonpaged memory. - EX_SPIN_LOCK registryRedirectLock; + // splaying which would be unsafe with a read-only lock. It is a resource, + // not a spin lock: lookups compare strings with RtlCompareUnicodeString, + // which is pageable, and the registry callback runs at PASSIVE_LEVEL. + ERESOURCE registryRedirectLock; RTL_AVL_TABLE registryRedirectTableWow64; RTL_AVL_TABLE registryRedirectTable; LARGE_INTEGER filterCookie; diff --git a/tools/test/Invoke-SarTest.ps1 b/tools/test/Invoke-SarTest.ps1 index 45d598a..1cf8f9c 100644 --- a/tools/test/Invoke-SarTest.ps1 +++ b/tools/test/Invoke-SarTest.ps1 @@ -109,6 +109,16 @@ function Invoke-Scenario { return $record } +# Registers or unregisters SarAsio's COM classes. The driver's application +# routing only redirects to SarAsio when they are registered. +function Set-SarAsioRegistered { + param([bool]$Registered) + + $regArgs = @('/s') + $(if ($Registered) { @() } else { @('/u') }) + @("`"$sarAsio`"") + $proc = Start-Process regsvr32.exe -ArgumentList $regArgs -Wait -PassThru + if ($proc.ExitCode -ne 0) { Write-Warning "regsvr32 $($regArgs -join ' ') exited with $($proc.ExitCode)" } +} + # Windows Server images ship with the audio stack disabled. foreach ($service in 'AudioEndpointBuilder', 'Audiosrv') { try { @@ -146,7 +156,7 @@ if (-not $SkipInstall) { $common = @('--sarasio', "`"$sarAsio`"", '--channels', $Channels) if ($Scenarios -contains 'issues') { - # Regression checks for specific GitHub issues, one short host each. + # Regression checks, one short host each, most for a GitHub issue. $summary.scenarios += Invoke-Scenario 'issue-133-control-panel' ( @('control-panel', '--endpoints', 1) + $common) -Timeout 120 $summary.scenarios += Invoke-Scenario 'issue-54-no-interface' ( @@ -158,6 +168,14 @@ if ($Scenarios -contains 'issues') { $summary.scenarios += Invoke-Scenario 'long-endpoint-name' ( @('host', '--endpoints', 1, '--duration', 2, '--id-prefix', 'SarTest-long', '--prefix', ('SarTestLongEndpointName' * 4)) + $common) -Timeout 120 + # Application routing on: from then on the driver's registry filter sees + # every registry value query on the machine. With SarAsio's COM classes + # registered, the harness's own WASAPI client also goes through SarAsio's + # device enumerator wrapper. + Set-SarAsioRegistered $true + $summary.scenarios += Invoke-Scenario 'app-routing' ( + @('run', '--endpoints', 1, '--iterations', 1, '--duration', $Duration, '--app-routing') + $common) + Set-SarAsioRegistered $false } if ($Scenarios -contains 'matrix') { diff --git a/tools/test/README.md b/tools/test/README.md index e677faf..d6d668b 100644 --- a/tools/test/README.md +++ b/tools/test/README.md @@ -92,7 +92,9 @@ configured and checks that it still offers a stereo input and output. then gives it a new ID, and checks that the new ID shows the new name; the results record what Windows shows for the reused ID. `long-endpoint-name` runs a host whose endpoint names are longer than the driver's 63-character -limit. +limit. `app-routing` runs a loopback with application routing on and +SarAsio's COM classes registered, which turns on the driver's registry +filter. `Invoke-SarTest.ps1 -Scenarios` selects any of `issues`, `matrix`, `race` and `kill`.