@samitouri / QOSAMI-WSL / commits / c4ef2c7d

virtio: improve virtiofs and VirtioProxy performance with per-device SWIOTLB pool (#40654)

Improves virtiofs and VirtioProxy performance by giving each virtio device its own SWIOTLB aperture instead of sharing a single global pool. The guest kernel reserves a contiguous physical range at boot, publishes the (base, size), and the host programs a matching per-device aperture in wsldevicehost. 1. The WSL kernel allocates a contiguous range at boot (alloc_contig_pages with __GFP_DMA32 | __GFP_ZERO) and exposes the chosen physical (base, size) under /sys/bus/vmbus/drivers/hv_pci/swiotlb_{base,size} 2. mini_init (WSL2) and the WSLC init handler read those sysfs files and return the values in LX_INIT_GUEST_CAPABILITIES and WSLC_GET_GUEST_CAPABILITIES_RESULT respectively. 3. WslCoreVm::ReadGuestCapabilities and WSLCVirtualMachine::ReadGuestCapabilities capture the values. WSLC forwards them to wslservice via the new HcsVirtualMachine::ApplyGuestCapabilities IDL method (with a WSLCGuestCapabilities struct so future kernel-published values can be added without bumping the interface IID). 4. Both VM owners format "swiotlb=0x{base:x},{size}" once into m_swiotlbOption and pass it verbatim to AddGuestDevice / AddSharePath for every virtiofs share and virtio-net adapter (VirtioProxy networking). wsldevicehost consumes the token and creates the per-device SWIOTLB aperture. If the kernel does not publish the sysfs files (older kernel) both values come back as zero, the host omits the device-options token, and the WSL2 path emits a one-time user warning via MessageSwiotlbKernelUnsupported so users understand why performance is degraded. (The WSLC path always uses the bundled kernel, so the warning does not apply there.) Other changes: * Bump Microsoft.WSL.Kernel to 6.18.26.3-1, which is the first official kernel that publishes the hv_pci swiotlb_{base,size} sysfs files this PR consumes. * Bump Microsoft.WSL.DeviceHost to 1.2.29-0 for the device-side SWIOTLB aperture support. * Default pool sizing moves to helpers::ComputeDefaultSwiotlbConfig and is only requested on the kernel command line when a virtio device that needs bounce buffers (VirtioFs / Virtio9p / VirtioProxy) is in use. * Telemetry: emit GuestKernelInfo / WSLCReadGuestCapabilities / WSLCApplyGuestCapabilities events with the kernel-chosen base and size so we can validate the handshake in CI. Co-authored-by: Ben Hillis <benhill@ntdev.microsoft.com> Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

Ben Hillis committed May 27, 2026 at 18:27 UTC c4ef2c7dd946e498ba2fdfb36ae061607aad81c1
22 files changed +350 -63
localization/strings/en-US/Resources.resw
+8
@@ -1126,6 +1126,14 @@ Attempting web download...</value>
1126 <data name="MessageConfigVirtio9pDisabled" xml:space="preserve">
1127 <value>wsl2.virtio9p is disabled, falling back to 9p with vsock transport.</value>
1128 </data>
1129 + <data name="MessageVirtioProxyRequiresVirtio" xml:space="preserve">
1130 + <value>VirtioProxy networking is not supported when wsl2.virtio is disabled; falling back to networkingMode {}.</value>
1131 + <comment>{Locked="wsl2.virtio"}{Locked="VirtioProxy"}{FixedPlaceholder="{}"}Command line arguments, file names and string inserts should not be translated</comment>
1132 + </data>
1133 + <data name="MessageSwiotlbKernelUnsupported" xml:space="preserve">
1134 + <value>The running kernel is missing a patch that significantly improves virtio device performance. Update to a more recent WSL kernel to enable this optimization.</value>
1135 + <comment>{Locked="virtio"}</comment>
1136 + </data>
1137 <data name="MessageInstallationCorrupted" xml:space="preserve">
1138 <value>WSL installation appears to be corrupted (Error code: {}).
1139 Press any key to repair WSL, or CTRL-C to cancel.
packages.config
+2 -2
@@ -19,8 +19,8 @@
19 <package id="Microsoft.WSL.bsdtar" version="0.0.2-2" />
20 <package id="Microsoft.WSL.Dependencies.amd64fre" version="10.0.27820.1000-250318-1700.rs-base2-hyp" targetFramework="native" />
21 <package id="Microsoft.WSL.Dependencies.arm64fre" version="10.0.27820.1000-250318-1700.rs-base2-hyp" targetFramework="native" />
22 - <package id="Microsoft.WSL.DeviceHost" version="1.2.23-0" />
23 - <package id="Microsoft.WSL.Kernel" version="6.18.26.1-1" targetFramework="native" />
22 + <package id="Microsoft.WSL.DeviceHost" version="1.2.29-0" />
23 + <package id="Microsoft.WSL.Kernel" version="6.18.26.3-1" targetFramework="native" />
24 <package id="Microsoft.WSL.LinuxSdk" version="1.20.0" targetFramework="native" />
25 <package id="Microsoft.WSL.TestData" version="0.4.0" />
26 <package id="Microsoft.WSL.TestDistro" version="2.7.1-1" />
src/linux/init/WSLCInit.cpp
+18 -1
@@ -105,6 +105,23 @@ void HandleMessageImpl(
105 Transaction.Send<WSLC_GET_DISK::TResponse>(writer.Span());
106 }
107
108 +void HandleMessageImpl(
109 + wsl::shared::SocketChannel& Channel,
110 + wsl::shared::Transaction& Transaction,
111 + const WSLC_GET_GUEST_CAPABILITIES& Message,
112 + const gsl::span<gsl::byte>& Buffer)
113 +{
114 + WSLC_GET_GUEST_CAPABILITIES_RESULT response{};
115 + response.Header.MessageType = WSLC_GET_GUEST_CAPABILITIES_RESULT::Type;
116 + response.Header.MessageSize = sizeof(response);
117 +
118 + auto pool = UtilReadHvPciSwiotlbPool();
119 + response.HvPciSwiotlbBase = pool.Base;
120 + response.HvPciSwiotlbSize = pool.Size;
121 +
122 + Transaction.Send<WSLC_GET_GUEST_CAPABILITIES_RESULT>(response);
123 +}
124 +
125 void HandleMessageImpl(
126 wsl::shared::SocketChannel& Channel, wsl::shared::Transaction& Transaction, const WSLC_ACCEPT& Message, const gsl::span<gsl::byte>& Buffer)
127 {
@@ -836,7 +853,7 @@ void ProcessMessage(wsl::shared::SocketChannel& Channel, wsl::shared::Transactio
853 {
854 try
855 {
839 - HandleMessage<WSLC_GET_DISK, WSLC_MOUNT, WSLC_EXEC, WSLC_FORK, WSLC_CONNECT, WSLC_SIGNAL, WSLC_TTY_RELAY, WSLC_PORT_RELAY, WSLC_UNMOUNT, WSLC_DETACH, WSLC_ACCEPT, WSLC_WATCH_PROCESSES, WSLC_UNIX_CONNECT>(
856 + HandleMessage<WSLC_GET_DISK, WSLC_MOUNT, WSLC_EXEC, WSLC_FORK, WSLC_CONNECT, WSLC_SIGNAL, WSLC_TTY_RELAY, WSLC_PORT_RELAY, WSLC_UNMOUNT, WSLC_DETACH, WSLC_ACCEPT, WSLC_WATCH_PROCESSES, WSLC_UNIX_CONNECT, WSLC_GET_GUEST_CAPABILITIES>(
857 Channel, Transaction, Type, Buffer);
858 }
859 catch (...)
src/linux/init/main.cpp
+5 -4
@@ -3391,15 +3391,16 @@ try
3391 wsl::shared::MessageWriter<LX_INIT_GUEST_CAPABILITIES> Message(LxMiniInitMessageGuestCapabilities);
3392 Message.WriteString(Version.release);
3393
3394 - //
3394 // SECCOMP_USER_NOTIF_FLAG_CONTINUE is the latest flag that flow steering needs
3395 // but there's no way to test for its presence. The assumption is that if seccomp is available
3397 - // and the kernel version is >= 5.10, then SECCOMP_USER_NOTIF_FLAG_CONTINUE is available
3398 - //
3399 -
3396 + // and the kernel version is >= 5.10, then SECCOMP_USER_NOTIF_FLAG_CONTINUE is available.
3397 uint32_t SeccompFlag = SECCOMP_RET_USER_NOTIF;
3398 Message->SeccompAvailable = syscall(__NR_seccomp, SECCOMP_GET_ACTION_AVAIL, 0, &SeccompFlag) == 0;
3399
3400 + auto pool = UtilReadHvPciSwiotlbPool();
3401 + Message->HvPciSwiotlbBase = pool.Base;
3402 + Message->HvPciSwiotlbSize = pool.Size;
3403 +
3404 Channel.SendMessage<LX_INIT_GUEST_CAPABILITIES>(Message.Span());
3405 return 0;
3406 }
src/linux/init/util.cpp
+16
@@ -3326,6 +3326,22 @@ std::string UtilReadFileContent(std::string_view path)
3326 return {std::istreambuf_iterator<char>(file), {}};
3327 }
3328
3329 +HvPciSwiotlbPool UtilReadHvPciSwiotlbPool()
3330 +{
3331 + HvPciSwiotlbPool pool{};
3332 + try
3333 + {
3334 + pool.Base = std::stoull(UtilReadFileContent("/sys/bus/vmbus/drivers/hv_pci/swiotlb_base"), nullptr, 0);
3335 + pool.Size = std::stoull(UtilReadFileContent("/sys/bus/vmbus/drivers/hv_pci/swiotlb_size"), nullptr, 0);
3336 + }
3337 + catch (...)
3338 + {
3339 + pool = {};
3340 + }
3341 +
3342 + return pool;
3343 +}
3344 +
3345 uint16_t UtilWinAfToLinuxAf(uint16_t WinAddressFamily)
3346 {
3347 uint16_t LinuxAddressFamily = AF_UNSPEC;
src/linux/init/util.h
+11
@@ -311,6 +311,17 @@ std::wstring UtilReadFileContentW(std::string_view path);
311
312 std::string UtilReadFileContent(std::string_view path);
313
314 +// Holds the hv_pci swiotlb pool the WSL kernel reserved at boot and published
315 +// under /sys/bus/vmbus/drivers/hv_pci/swiotlb_{base,size}. Both fields are zero
316 +// when running on a kernel that does not publish these files.
317 +struct HvPciSwiotlbPool
318 +{
319 + uint64_t Base = 0;
320 + uint64_t Size = 0;
321 +};
322 +
323 +HvPciSwiotlbPool UtilReadHvPciSwiotlbPool();
324 +
325 uint16_t UtilWinAfToLinuxAf(uint16_t AddressFamily);
326
327 int WriteToFile(const char* Path, const char* Content, int permissions = 0644);
src/shared/inc/lxinitshared.h
+30 -1
@@ -403,6 +403,8 @@ typedef enum _LX_MESSAGE_TYPE
403 LxMessageWSLCWatchProcesses,
404 LxMessageWSLCProcessExited,
405 LxMessageWSLCUnixConnect,
406 + LxMessageWSLCGetGuestCapabilities,
407 + LxMessageWSLCGetGuestCapabilitiesResult,
408 } LX_MESSAGE_TYPE,
409 *PLX_MESSAGE_TYPE;
410
@@ -513,6 +515,8 @@ inline auto ToString(LX_MESSAGE_TYPE messageType)
515 X(LxMessageWSLCWatchProcesses)
516 X(LxMessageWSLCProcessExited)
517 X(LxMessageWSLCUnixConnect)
518 + X(LxMessageWSLCGetGuestCapabilities)
519 + X(LxMessageWSLCGetGuestCapabilitiesResult)
520
521 default:
522 return "<unexpected LX_MESSAGE_TYPE>";
@@ -1430,9 +1434,11 @@ typedef struct _LX_INIT_GUEST_CAPABILITIES
1434
1435 MESSAGE_HEADER Header;
1436 bool SeccompAvailable;
1437 + uint64_t HvPciSwiotlbBase;
1438 + uint64_t HvPciSwiotlbSize;
1439 char Buffer[]; // Contains the kernel version string
1440
1435 - PRETTY_PRINT(FIELD(Header), FIELD(SeccompAvailable), BUFFER_FIELD(Buffer));
1441 + PRETTY_PRINT(FIELD(Header), FIELD(SeccompAvailable), FIELD(HvPciSwiotlbBase), FIELD(HvPciSwiotlbSize), BUFFER_FIELD(Buffer));
1442 } LX_INIT_GUEST_CAPABILITIES, *PLX_INIT_GUEST_CAPABILITIES;
1443
1444 typedef struct _LX_MINI_INIT_WAIT_FOR_PMEM_DEVICE_MESSAGE
@@ -1836,6 +1842,29 @@ struct WSLC_UNIX_CONNECT
1842 PRETTY_PRINT(FIELD(Header), STRING_FIELD(PathOffset));
1843 };
1844
1845 +struct WSLC_GET_GUEST_CAPABILITIES_RESULT
1846 +{
1847 + static inline auto Type = LxMessageWSLCGetGuestCapabilitiesResult;
1848 +
1849 + MESSAGE_HEADER Header{};
1850 + uint64_t HvPciSwiotlbBase{};
1851 + uint64_t HvPciSwiotlbSize{};
1852 +
1853 + PRETTY_PRINT(FIELD(Header), FIELD(HvPciSwiotlbBase), FIELD(HvPciSwiotlbSize));
1854 +};
1855 +
1856 +struct WSLC_GET_GUEST_CAPABILITIES
1857 +{
1858 + static inline auto Type = LxMessageWSLCGetGuestCapabilities;
1859 + using TResponse = WSLC_GET_GUEST_CAPABILITIES_RESULT;
1860 +
1861 + DECLARE_MESSAGE_CTOR(WSLC_GET_GUEST_CAPABILITIES);
1862 +
1863 + MESSAGE_HEADER Header{};
1864 +
1865 + PRETTY_PRINT(FIELD(Header));
1866 +};
1867 +
1868 typedef struct _LX_MINI_INIT_IMPORT_RESULT
1869 {
1870 static inline auto Type = LxMiniInitMessageImportResult;
src/windows/common/GuestDeviceManager.cpp
+4 -1
@@ -35,13 +35,16 @@ GUID GuestDeviceManager::AddHdvShareWithOptions(
35 // Options are appended to the name with a semi-colon separator.
36 // "name;key1=value1;key2=value2"
37 // The AddSharePath implementation is responsible for separating them out and interpreting them.
38 + // N.B. A ";vm_id=<guid>" option is always appended so the device host can identify the owning VM.
39 std::wstring nameWithOptions{AccessName};
39 - if (ARGUMENT_PRESENT(Options))
40 + if (ARGUMENT_PRESENT(Options) && Options[0] != L'\0')
41 {
42 nameWithOptions += L";";
43 nameWithOptions += Options;
44 }
45
46 + nameWithOptions += std::format(L";vm_id={}", m_machineId);
47 +
48 {
49 auto revert = wil::impersonate_token(UserToken);
50
src/windows/common/VirtioNetworking.cpp
+16 -4
@@ -15,12 +15,18 @@ static constexpr auto c_eth0DeviceName = L"eth0";
15 static constexpr auto c_loopbackDeviceName = TEXT(LX_INIT_LOOPBACK_DEVICE_NAME);
16
17 VirtioNetworking::VirtioNetworking(
18 - GnsChannel&& gnsChannel, VirtioNetworkingFlags flags, LPCWSTR dnsOptions, std::shared_ptr<GuestDeviceManager> guestDeviceManager, wil::shared_handle userToken) :
18 + GnsChannel&& gnsChannel,
19 + VirtioNetworkingFlags flags,
20 + LPCWSTR dnsOptions,
21 + std::shared_ptr<GuestDeviceManager> guestDeviceManager,
22 + wil::shared_handle userToken,
23 + std::wstring swiotlbConfig) :
24 m_guestDeviceManager(std::move(guestDeviceManager)),
25 m_userToken(std::move(userToken)),
26 m_gnsChannel(std::move(gnsChannel)),
27 m_flags(flags),
23 - m_dnsOptions(dnsOptions)
28 + m_dnsOptions(dnsOptions),
29 + m_swiotlbOption(std::move(swiotlbConfig))
30 {
31 }
32
@@ -210,7 +216,13 @@ void VirtioNetworking::RefreshGuestConnection()
216 if (!m_adapterId.has_value())
217 {
218 m_adapterId = m_guestDeviceManager->AddGuestDevice(
213 - VIRTIO_NET_DEVICE_ID, VIRTIO_NET_CLASS_ID, c_eth0DeviceName, nullptr, device_options.c_str(), 0, m_userToken.get());
219 + VIRTIO_NET_DEVICE_ID,
220 + VIRTIO_NET_CLASS_ID,
221 + c_eth0DeviceName,
222 + m_swiotlbOption.c_str(),
223 + device_options.c_str(),
224 + 0,
225 + m_userToken.get());
226 }
227 else
228 {
@@ -244,7 +256,7 @@ void VirtioNetworking::SetupLoopbackDevice()
256 VIRTIO_NET_DEVICE_ID,
257 VIRTIO_NET_CLASS_ID,
258 c_loopbackDeviceName,
247 - nullptr,
259 + m_swiotlbOption.c_str(),
260 L"client_ip=127.0.0.1;client_mac=00:11:22:33:44:55",
261 0,
262 m_userToken.get());
src/windows/common/VirtioNetworking.h
+8 -1
@@ -22,7 +22,13 @@ DEFINE_ENUM_FLAG_OPERATORS(VirtioNetworkingFlags);
22 class VirtioNetworking : public INetworkingEngine
23 {
24 public:
25 - VirtioNetworking(GnsChannel&& gnsChannel, VirtioNetworkingFlags flags, LPCWSTR dnsOptions, std::shared_ptr<GuestDeviceManager> guestDeviceManager, wil::shared_handle userToken);
25 + VirtioNetworking(
26 + GnsChannel&& gnsChannel,
27 + VirtioNetworkingFlags flags,
28 + LPCWSTR dnsOptions,
29 + std::shared_ptr<GuestDeviceManager> guestDeviceManager,
30 + wil::shared_handle userToken,
31 + std::wstring swiotlbConfig);
32
33 ~VirtioNetworking();
34
@@ -62,6 +68,7 @@ private:
68 std::shared_ptr<networking::NetworkSettings> m_networkSettings;
69 VirtioNetworkingFlags m_flags = VirtioNetworkingFlags::None;
70 LPCWSTR m_dnsOptions = nullptr;
71 + std::wstring m_swiotlbOption;
72 std::optional<GUID> m_localhostAdapterId;
73 std::optional<GUID> m_adapterId;
74
src/windows/common/WslCoreConfig.cpp
+34 -17
@@ -14,6 +14,7 @@ Abstract:
14
15 #include "precomp.h"
16 #include "WslCoreConfig.h"
17 +#include "helpers.hpp"
18 #include "Localization.h"
19 #include "WslCoreFirewallSupport.h"
20 #include "WslCoreNetworkingSupport.h"
@@ -124,7 +125,8 @@ void wsl::core::Config::ParseConfigFile(_In_opt_ LPCWSTR ConfigFilePath, _In_opt
125 ConfigKey(ConfigSetting::Experimental::InitialAutoProxyTimeout, InitialAutoProxyTimeout),
126 ConfigKey(ConfigSetting::Experimental::IgnoredPorts, std::move(parseIgnoredPorts)),
127 ConfigKey(ConfigSetting::Experimental::HostAddressLoopback, EnableHostAddressLoopback),
127 - ConfigKey(ConfigSetting::Experimental::SetVersionDebug, SetVersionDebug)};
128 + ConfigKey(ConfigSetting::Experimental::SetVersionDebug, SetVersionDebug),
129 + ConfigKey(ConfigSetting::Experimental::Swiotlb, MemoryString(SwiotlbSizeBytes))};
130
131 wil::unique_file ConfigFile;
132 if (ConfigFilePath != nullptr)
@@ -398,22 +400,6 @@ void wsl::core::Config::Initialize(_In_opt_ HANDLE UserToken)
400 }
401 }
402
401 - // Load NAT configuration from the registry.
402 - if (NetworkingMode == wsl::core::NetworkingMode::Nat)
403 - {
404 - try
405 - {
406 - const auto machineKey = wsl::windows::common::registry::OpenLxssMachineKey();
407 - NatGateway = wsl::windows::common::registry::ReadString(machineKey.get(), nullptr, c_natGatewayAddress, L"");
408 - NatNetwork = wsl::windows::common::registry::ReadString(machineKey.get(), nullptr, c_natNetwork, L"");
409 -
410 - auto runAsUser = wil::impersonate_token(UserToken);
411 - const auto userKey = wsl::windows::common::registry::OpenLxssUserKey();
412 - NatIpAddress = wsl::windows::common::registry::ReadString(userKey.get(), nullptr, c_natIpAddress, L"");
413 - }
414 - CATCH_LOG()
415 - }
416 -
403 // Due to an issue with Global Secure Access Client, do not use DNS tunneling if the service is present.
404 if (EnableDnsTunneling)
405 {
@@ -454,6 +440,13 @@ void wsl::core::Config::Initialize(_In_opt_ HANDLE UserToken)
440 {
441 VALIDATE_CONFIG_OPTION(!EnableVirtio, EnableVirtio9p, false);
442 VALIDATE_CONFIG_OPTION(!EnableVirtio, EnableVirtioFs, false);
443 + VALIDATE_CONFIG_OPTION(!EnableVirtio, SwiotlbSizeBytes, 0);
444 +
445 + if (NetworkingMode == NetworkingMode::VirtioProxy)
446 + {
447 + NetworkingMode = (defaultNetworkingMode == NetworkingMode::VirtioProxy) ? NetworkingMode::None : NetworkingMode::Nat;
448 + EMIT_USER_WARNING(wsl::shared::Localization::MessageVirtioProxyRequiresVirtio(ToString(NetworkingMode)));
449 + }
450 }
451
452 if (EnableVirtio9p)
@@ -462,6 +455,13 @@ void wsl::core::Config::Initialize(_In_opt_ HANDLE UserToken)
455 EnableVirtio9p = false;
456 }
457
458 + // Compute a default swiotlb config only when a virtio device that requires bounce buffers is present.
459 + // N.B. Must run after policy overrides so networking/fs modes reflect final values.
460 + if (SwiotlbSizeBytes == 0 && (EnableVirtioFs || EnableVirtio9p || (NetworkingMode == NetworkingMode::VirtioProxy)))
461 + {
462 + SwiotlbSizeBytes = wsl::windows::common::helpers::ComputeDefaultSwiotlbConfig(MemorySizeBytes);
463 + }
464 +
465 if (NetworkingMode != NetworkingMode::Nat && NetworkingMode != NetworkingMode::Mirrored && NetworkingMode != NetworkingMode::VirtioProxy)
466 {
467 VALIDATE_CONFIG_OPTION(
@@ -481,6 +481,23 @@ void wsl::core::Config::Initialize(_In_opt_ HANDLE UserToken)
481 VALIDATE_CONFIG_OPTION((NetworkingMode != NetworkingMode::Mirrored), IgnoredPorts, std::set<uint16_t>{});
482 VALIDATE_CONFIG_OPTION((NetworkingMode != NetworkingMode::Mirrored), EnableHostAddressLoopback, false);
483 }
484 +
485 + // Load NAT configuration from the registry.
486 + // N.B. This must be done after all networking mode adjustments (e.g. VirtioProxy -> NAT fallback).
487 + if (NetworkingMode == wsl::core::NetworkingMode::Nat)
488 + {
489 + try
490 + {
491 + const auto machineKey = wsl::windows::common::registry::OpenLxssMachineKey();
492 + NatGateway = wsl::windows::common::registry::ReadString(machineKey.get(), nullptr, c_natGatewayAddress, L"");
493 + NatNetwork = wsl::windows::common::registry::ReadString(machineKey.get(), nullptr, c_natNetwork, L"");
494 +
495 + auto runAsUser = wil::impersonate_token(UserToken);
496 + const auto userKey = wsl::windows::common::registry::OpenLxssUserKey();
497 + NatIpAddress = wsl::windows::common::registry::ReadString(userKey.get(), nullptr, c_natIpAddress, L"");
498 + }
499 + CATCH_LOG()
500 + }
501 }
502
503 GUID wsl::core::Config::NatNetworkId() const noexcept
src/windows/common/WslCoreConfig.h
+7 -5
@@ -27,11 +27,11 @@ Abstract:
27 T_VALUE(c, EnableHostAddressLoopback), T_VALUE(c, EnableHostFileSystemAccess), T_VALUE(c, EnableIpv6), \
28 T_VALUE(c, EnableLocalhostRelay), T_VALUE(c, EnableNestedVirtualization), T_VALUE(c, EnableSafeMode), \
29 T_VALUE(c, EnableSparseVhd), T_VALUE(c, EnableVirtio), T_VALUE(c, EnableVirtio9p), T_VALUE(c, EnableVirtioFs), \
30 - T_ENUM(c, FirewallConfigPresence), T_VALUE(c, KernelBootTimeout), T_SET(c, KernelCommandLine), \
31 - T_VALUE(c, KernelDebugPort), T_SET(c, KernelModulesPath), T_STRING(c, KernelModulesList), T_SET(c, KernelPath), \
32 - T_VALUE(c, LoadDefaultKernelModules), T_PRESENT(c, LoadKernelModulesPresence), T_VALUE(c, MaximumMemorySizeBytes), \
33 - T_VALUE(c, MaximumProcessorCount), T_ENUM(c, MemoryReclaim), T_VALUE(c, MemorySizeBytes), T_VALUE(c, MountDeviceTimeout), \
34 - T_ENUM(c, NetworkingMode), T_VALUE(c, ProcessorCount), T_SET(c, SwapFilePath), T_VALUE(c, SwapSizeBytes), \
30 + T_ENUM(c, FirewallConfigPresence), T_VALUE(c, KernelBootTimeout), T_SET(c, KernelCommandLine), T_VALUE(c, KernelDebugPort), \
31 + T_STRING(c, KernelModulesList), T_SET(c, KernelModulesPath), T_SET(c, KernelPath), T_VALUE(c, LoadDefaultKernelModules), \
32 + T_PRESENT(c, LoadKernelModulesPresence), T_VALUE(c, MaximumMemorySizeBytes), T_VALUE(c, MaximumProcessorCount), \
33 + T_ENUM(c, MemoryReclaim), T_VALUE(c, MemorySizeBytes), T_VALUE(c, MountDeviceTimeout), T_ENUM(c, NetworkingMode), \
34 + T_VALUE(c, ProcessorCount), T_SET(c, SwapFilePath), T_VALUE(c, SwapSizeBytes), T_VALUE(c, SwiotlbSizeBytes), \
35 T_SET(c, SystemDistroPath), T_VALUE(c, VhdSizeBytes), T_VALUE(c, VmIdleTimeout), T_SET(c, VmSwitch)
36
37 namespace wsl::core {
@@ -289,6 +289,7 @@ namespace ConfigSetting {
289 static constexpr auto IgnoredPorts = "experimental.ignoredPorts";
290 static constexpr auto HostAddressLoopback = "experimental.hostAddressLoopback";
291 static constexpr auto SetVersionDebug = "experimental.setVersionDebug";
292 + static constexpr auto Swiotlb = "experimental.swiotlb";
293
294 } // namespace Experimental
295 } // namespace ConfigSetting
@@ -375,6 +376,7 @@ struct Config
376 bool EnableHostAddressLoopback = false;
377 std::filesystem::path CrashDumpFolder;
378 int MaxCrashDumpCount = 10;
379 + UINT64 SwiotlbSizeBytes = 0;
380
381 // Temporary config value to help root cause the truncated archive errors in SetVersion()
382 bool SetVersionDebug = false;
src/windows/common/helpers.cpp
+21 -1
@@ -762,7 +762,7 @@ try
762 }
763 CATCH_LOG()
764
765 -void wsl::windows::common::helpers::AppendCommonKernelCommandLine(_Inout_ std::wstring& kernelCmdLine, _In_ int pageReportingOrder)
765 +void wsl::windows::common::helpers::AppendCommonKernelCommandLine(_Inout_ std::wstring& kernelCmdLine, _In_ int pageReportingOrder, _In_ ULONG64 swiotlbSizeBytes)
766 {
767 // Enable timesync workaround to sync on resume from sleep in modern standby.
768 kernelCmdLine += L" hv_utils.timesync_implicit=1";
@@ -772,4 +772,24 @@ void wsl::windows::common::helpers::AppendCommonKernelCommandLine(_Inout_ std::w
772
773 // Configure page reporting order - minimum order of pages reported as free to the hypervisor.
774 kernelCmdLine += std::format(L" page_reporting.page_reporting_order={}", pageReportingOrder);
775 +
776 + // Reserve a swiotlb bounce buffer for virtio devices.
777 + if (swiotlbSizeBytes != 0)
778 + {
779 + kernelCmdLine += std::format(L" swiotlb=force hv_pci_swiotlb={}", swiotlbSizeBytes);
780 + }
781 +}
782 +
783 +UINT64 wsl::windows::common::helpers::ComputeDefaultSwiotlbConfig(_In_ UINT64 memoryBytes)
784 +{
785 + constexpr UINT64 c_swiotlbSize = 64 * _1MB;
786 +
787 + // Skip swiotlb on VMs that cannot fit the reserved buffer. Users can still opt in explicitly
788 + // via experimental.swiotlb in .wslconfig.
789 + if (memoryBytes < _1GB + c_swiotlbSize)
790 + {
791 + return {};
792 + }
793 +
794 + return c_swiotlbSize;
795 }
src/windows/common/helpers.hpp
+3 -1
@@ -205,6 +205,8 @@ bool TryAttachConsole();
205
206 void RegisterWithDcat(_In_ bool IncludeVersionNumber = true);
207
208 -void AppendCommonKernelCommandLine(_Inout_ std::wstring& kernelCmdLine, _In_ int pageReportingOrder);
208 +void AppendCommonKernelCommandLine(_Inout_ std::wstring& kernelCmdLine, _In_ int pageReportingOrder, _In_ ULONG64 swiotlbSizeBytes);
209 +
210 +UINT64 ComputeDefaultSwiotlbConfig(_In_ UINT64 memoryBytes);
211
212 } // namespace wsl::windows::common::helpers
src/windows/service/exe/HcsVirtualMachine.cpp
+46 -5
@@ -134,12 +134,19 @@ HcsVirtualMachine::HcsVirtualMachine(_In_ const WSLCSessionSettings* Settings)
134
135 #endif
136
137 + // Compute a swiotlb device-options token sized to fit this VM's RAM, used by the kernel
138 + // command line, virtiofs shares, and the VirtioProxy virtio-net adapter.
139 + // Only needed when a virtio device that requires bounce buffers will be attached.
140 + ULONG64 swiotlbSizeBytes = 0;
141 + if (FeatureEnabled(WslcFeatureFlagsVirtioFs) || m_networkingMode == WSLCNetworkingModeVirtioProxy)
142 + {
143 + swiotlbSizeBytes = helpers::ComputeDefaultSwiotlbConfig(static_cast<UINT64>(Settings->MemoryMb) * _1MB);
144 + }
145 +
146 // Initialize kernel command line.
147 std::wstring kernelCmdLine = L"initrd=\\" LXSS_VM_MODE_INITRD_NAME L" " TEXT(WSLC_ROOT_INIT_ENV) L"=1 panic=-1";
148 kernelCmdLine += std::format(L" nr_cpus={}", Settings->CpuCount);
140 -
141 - // Append common kernel parameters shared between WSL2 and WSLC.
142 - helpers::AppendCommonKernelCommandLine(kernelCmdLine, pageReportingOrder);
149 + helpers::AppendCommonKernelCommandLine(kernelCmdLine, pageReportingOrder, swiotlbSizeBytes);
150
151 // Setup dmesg collector with optional DmesgOutput handle.
152 // TODO: move dmesg collector to user session process.
@@ -456,7 +463,7 @@ try
463 }
464
465 m_networkEngine = std::make_unique<wsl::core::VirtioNetworking>(
459 - wsl::core::GnsChannel(std::move(gnsSocketHandle)), flags, nullptr, m_guestDeviceManager, m_userToken);
466 + wsl::core::GnsChannel(std::move(gnsSocketHandle)), flags, nullptr, m_guestDeviceManager, m_userToken, m_swiotlbOption);
467 }
468 else
469 {
@@ -573,11 +580,22 @@ try
580 }
581 else
582 {
583 + std::wstring options = ReadOnly ? L"ro" : L"";
584 + if (!m_swiotlbOption.empty())
585 + {
586 + if (!options.empty())
587 + {
588 + options += L";";
589 + }
590 +
591 + options += m_swiotlbOption;
592 + }
593 +
594 it->second = m_guestDeviceManager->AddGuestDevice(
595 VIRTIO_FS_DEVICE_ID,
596 m_virtioFsClassId,
597 shareName.c_str(),
580 - ReadOnly ? L"ro" : L"",
598 + options.c_str(),
599 WindowsPath,
600 VIRTIO_FS_FLAGS_TYPE_FILES,
601 m_userToken.get());
@@ -614,6 +632,29 @@ try
632 }
633 CATCH_RETURN()
634
635 +HRESULT HcsVirtualMachine::ApplyGuestCapabilities(_In_ const WSLCGuestCapabilities* Capabilities)
636 +try
637 +{
638 + RETURN_HR_IF_NULL(E_POINTER, Capabilities);
639 +
640 + std::lock_guard lock(m_lock);
641 +
642 + THROW_HR_IF(E_INVALIDARG, !m_swiotlbOption.empty());
643 +
644 + if (Capabilities->HvPciSwiotlbBase != 0 && Capabilities->HvPciSwiotlbSize != 0)
645 + {
646 + m_swiotlbOption = std::format(L"swiotlb=0x{:x},{}", Capabilities->HvPciSwiotlbBase, Capabilities->HvPciSwiotlbSize);
647 + }
648 +
649 + WSL_LOG(
650 + "WSLCApplyGuestCapabilities",
651 + TraceLoggingValue(Capabilities->HvPciSwiotlbBase, "HvPciSwiotlbBase"),
652 + TraceLoggingValue(Capabilities->HvPciSwiotlbSize, "HvPciSwiotlbSize"));
653 +
654 + return S_OK;
655 +}
656 +CATCH_RETURN()
657 +
658 HRESULT HcsVirtualMachine::GetTerminationEvent(_Out_ HANDLE* Event)
659 try
660 {
src/windows/service/exe/HcsVirtualMachine.h
+4
@@ -44,6 +44,7 @@ public:
44 IFACEMETHOD(DetachDisk)(_In_ ULONG Lun) override;
45 IFACEMETHOD(AddShare)(_In_ LPCWSTR WindowsPath, _In_ BOOL ReadOnly, _Out_ GUID* ShareId) override;
46 IFACEMETHOD(RemoveShare)(_In_ REFGUID ShareId) override;
47 + IFACEMETHOD(ApplyGuestCapabilities)(_In_ const WSLCGuestCapabilities* Capabilities) override;
48 IFACEMETHOD(GetTerminationEvent)(_Out_ HANDLE* Event) override;
49
50 private:
@@ -79,6 +80,9 @@ private:
80 WSLCFeatureFlags m_featureFlags{};
81 WSLCNetworkingMode m_networkingMode{};
82
83 + // Swiotlb device-options token sized to fit inside the VM's RAM (empty when too small).
84 + std::wstring m_swiotlbOption;
85 +
86 wil::unique_socket m_listenSocket;
87 std::shared_ptr<DmesgCollector> m_dmesgCollector;
88 std::shared_ptr<GuestDeviceManager> m_guestDeviceManager;
src/windows/service/exe/WslCoreVm.cpp
+44 -20
@@ -415,6 +415,28 @@ void WslCoreVm::Initialize(const GUID& VmId, const wil::shared_handle& UserToken
415 addShare(TEXT(LXSS_GPU_PACKAGED_LIB_SHARE), path.c_str());
416 }
417
418 + // Accept a connection from mini_init with a receive timeout so the service does not get stuck waiting for a response from the VM.
419 + m_miniInitChannel =
420 + wsl::shared::SocketChannel{AcceptConnection(m_vmConfig.KernelBootTimeout), "mini_init", {m_terminatingEvent.get()}};
421 +
422 + // Accept the connection from the Linux guest for notifications.
423 + m_notifyChannel = AcceptConnection(m_vmConfig.KernelBootTimeout);
424 +
425 + // Receive and parse the guest kernel version
426 + ReadGuestCapabilities();
427 +
428 + // Cache the effective swiotlb configuration. The kernel picks a valid GPA, allocates the pool,
429 + // and publishes the actual (base, size) via sysfs. Only warn when swiotlb was actually
430 + // requested via the kernel command line; otherwise the kernel correctly doesn't allocate.
431 + if (m_hvPciSwiotlbBase != 0 && m_hvPciSwiotlbSize != 0)
432 + {
433 + m_swiotlbOption = std::format(L"swiotlb=0x{:x},{}", m_hvPciSwiotlbBase, m_hvPciSwiotlbSize);
434 + }
435 + else if (m_vmConfig.SwiotlbSizeBytes != 0)
436 + {
437 + EMIT_USER_WARNING(wsl::shared::Localization::MessageSwiotlbKernelUnsupported());
438 + }
439 +
440 // Asynchronously add drvfs devices if supported.
441 if (m_vmConfig.EnableHostFileSystemAccess)
442 {
@@ -438,16 +460,6 @@ void WslCoreVm::Initialize(const GUID& VmId, const wil::shared_handle& UserToken
460 }).detach();
461 }
462
441 - // Accept a connection from mini_init with a receive timeout so the service does not get stuck waiting for a response from the VM.
442 - m_miniInitChannel =
443 - wsl::shared::SocketChannel{AcceptConnection(m_vmConfig.KernelBootTimeout), "mini_init", {m_terminatingEvent.get()}};
444 -
445 - // Accept the connection from the Linux guest for notifications.
446 - m_notifyChannel = AcceptConnection(m_vmConfig.KernelBootTimeout);
447 -
448 - // Receive and parse the guest kernel version
449 - ReadGuestCapabilities();
450 -
463 // Mount the system distro.
464 // N.B. If using SCSI, the system distro is added during VM creation.
465 switch (m_systemDistroDeviceType)
@@ -586,8 +598,9 @@ void WslCoreVm::Initialize(const GUID& VmId, const wil::shared_handle& UserToken
598 WI_SetFlagIf(flags, wsl::core::VirtioNetworkingFlags::DnsTunneling, m_vmConfig.EnableDnsTunneling);
599 // NAT may have fallen back to virtio proxy after the early-config message; drop the unused DNS hvsocket.
600 dnsTunnelingSocket.reset();
601 +
602 m_networkingEngine = std::make_unique<wsl::core::VirtioNetworking>(
590 - std::move(gnsChannel), flags, LX_INIT_RESOLVCONF_FULL_HEADER, m_guestDeviceManager, m_userToken);
603 + std::move(gnsChannel), flags, LX_INIT_RESOLVCONF_FULL_HEADER, m_guestDeviceManager, m_userToken, m_swiotlbOption);
604 }
605 else if (m_vmConfig.NetworkingMode == NetworkingMode::Bridged)
606 {
@@ -1539,13 +1552,7 @@ std::wstring WslCoreVm::GenerateConfigJson()
1552 kernelCmdLine += std::format(L" nr_cpus={}", m_vmConfig.ProcessorCount);
1553
1554 // Append common kernel parameters shared between WSL2 and WSLC.
1542 - helpers::AppendCommonKernelCommandLine(kernelCmdLine, m_pageReportingOrder);
1543 -
1544 - // If using virtio features, enable SWIOTLB as a perf optimization (will cause VM to consume 64MB more memory).
1545 - if (m_vmConfig.EnableVirtio9p || m_vmConfig.EnableVirtioFs || m_vmConfig.NetworkingMode == NetworkingMode::VirtioProxy)
1546 - {
1547 - kernelCmdLine += L" swiotlb=force";
1548 - }
1555 + helpers::AppendCommonKernelCommandLine(kernelCmdLine, m_pageReportingOrder, m_vmConfig.SwiotlbSizeBytes);
1556
1557 if (m_vmConfig.EnableVirtio && helpers::IsVirtioSerialConsoleSupported())
1558 {
@@ -2162,10 +2169,23 @@ std::pair<std::wstring, std::wstring> WslCoreVm::AddVirtioFsShare(_In_ bool Admi
2169
2170 sharePath = std::filesystem::weakly_canonical(sharePath).wstring();
2171
2172 + // Append the swiotlb token here so it covers fixed-drive, dynamic add, and remount paths.
2173 + // Duplicate swiotlb tokens are harmless: VirtioFsShare parses options into a map.
2174 + std::wstring effectiveOptions(Options);
2175 + if (!m_swiotlbOption.empty())
2176 + {
2177 + if (!effectiveOptions.empty())
2178 + {
2179 + effectiveOptions += L';';
2180 + }
2181 +
2182 + effectiveOptions += m_swiotlbOption;
2183 + }
2184 +
2185 // Check if a matching share already exists.
2186 bool created = false;
2187 std::wstring tag;
2168 - VirtioFsShare key(sharePath.c_str(), Options, Admin);
2188 + VirtioFsShare key(sharePath.c_str(), effectiveOptions.c_str(), Admin);
2189 if (!m_virtioFsShares.contains(key))
2190 {
2191 // Generate a new unique tag for the share.
@@ -2198,7 +2218,7 @@ std::pair<std::wstring, std::wstring> WslCoreVm::AddVirtioFsShare(_In_ bool Admi
2218 "WslCoreVmAddVirtioFsShare",
2219 TraceLoggingValue(Admin, "admin"),
2220 TraceLoggingValue(sharePath.c_str(), "path"),
2201 - TraceLoggingValue(Options, "options"),
2221 + TraceLoggingValue(effectiveOptions.c_str(), "options"),
2222 TraceLoggingValue(tag.c_str(), "tag"),
2223 TraceLoggingValue(created, "created"),
2224 TraceLoggingValue(m_virtioFsShares.size(), "shareCount"));
@@ -2309,9 +2329,13 @@ void WslCoreVm::ReadGuestCapabilities()
2329 }
2330
2331 m_seccompAvailable = info.SeccompAvailable;
2332 + m_hvPciSwiotlbBase = info.HvPciSwiotlbBase;
2333 + m_hvPciSwiotlbSize = info.HvPciSwiotlbSize;
2334 WSL_LOG(
2335 "GuestKernelInfo",
2336 TraceLoggingValue(m_seccompAvailable, "SeccompAvailable"),
2337 + TraceLoggingValue(m_hvPciSwiotlbBase, "HvPciSwiotlbBase"),
2338 + TraceLoggingValue(m_hvPciSwiotlbSize, "HvPciSwiotlbSize"),
2339 TraceLoggingValue(std::get<0>(m_kernelVersion), "Version"),
2340 TraceLoggingValue(std::get<1>(m_kernelVersion), "Revision"),
2341 TraceLoggingValue(std::get<2>(m_kernelVersion), "Minor"));
src/windows/service/exe/WslCoreVm.h
+3
@@ -304,6 +304,9 @@ private:
304 std::tuple<std::uint32_t, std::uint32_t, std::uint32_t> m_kernelVersion;
305 std::wstring m_kernelVersionString;
306 bool m_seccompAvailable;
307 + uint64_t m_hvPciSwiotlbBase = 0;
308 + uint64_t m_hvPciSwiotlbSize = 0;
309 + std::wstring m_swiotlbOption;
310 std::wstring m_sharedMemoryRoot;
311 std::filesystem::path m_installPath;
312 std::wstring m_userProfile;
src/windows/service/inc/wslc.idl
+23
@@ -439,6 +439,21 @@ cpp_quote("#define WSLCFeatureFlagsValid (WslcFeatureFlagsDnsTunneling | WslcFea
439
440 cpp_quote("DEFINE_ENUM_FLAG_OPERATORS(WSLCFeatureFlags);")
441
442 +//
443 +// Values discovered from the guest kernel after the VM has booted, forwarded
444 +// from wslcsession via IWSLCVirtualMachine::ApplyGuestCapabilities. Add new
445 +// fields here instead of new IDL methods so the interface does not need a new
446 +// IID for each kernel-published value.
447 +//
448 +typedef struct _WSLCGuestCapabilities
449 +{
450 + // (base, size) of the hv_pci swiotlb pool the kernel reserved and
451 + // published under /sys/bus/vmbus/drivers/hv_pci/swiotlb_{base,size}.
452 + // Both zero means the running kernel does not support hv_pci swiotlb.
453 + UINT64 HvPciSwiotlbBase;
454 + UINT64 HvPciSwiotlbSize;
455 +} WSLCGuestCapabilities;
456 +
457 //
458 // IWSLCVirtualMachine - Interface representing a single VM instance.
459 // Operations are scoped to this VM. The VM ID is stored internally,
@@ -479,6 +494,14 @@ interface IWSLCVirtualMachine : IUnknown
494 // Removes a previously added filesystem share.
495 HRESULT RemoveShare([in] REFGUID ShareId);
496
497 + // Configures the per-VM state discovered from the guest kernel after boot
498 + // (currently the hv_pci swiotlb pool). Subsequent calls to AddShare and
499 + // ConfigureNetworking forward these values to wsldevicehost via the
500 + // swiotlb device-options token. A capabilities struct whose fields are
501 + // all zero means the guest kernel does not support the feature; the
502 + // token is then omitted.
503 + HRESULT ApplyGuestCapabilities([in] const WSLCGuestCapabilities* Capabilities);
504 +
505 // Returns an event that is signaled when the VM exits (graceful or forced).
506 HRESULT GetTerminationEvent([out, system_handle(sh_event)] HANDLE* Event);
507 }
src/windows/wslcsession/WSLCVirtualMachine.cpp
+27
@@ -298,6 +298,11 @@ void WSLCVirtualMachine::Initialize()
298 const auto modulesDevice = GetVhdDevicePath(1);
299 Mount(m_initChannel, modulesDevice.c_str(), "", "ext4", "ro", WSLC_MOUNT::KernelModules);
300
301 + // Discover the per-VM guest capabilities (currently the hv_pci swiotlb pool) and forward them
302 + // to the service so virtio device-options (virtiofs shares, VirtioProxy networking) can include
303 + // the swiotlb token.
304 + ReadGuestCapabilities();
305 +
306 // Configure GPU mounts if enabled
307 MountGpuLibraries(c_gpuLibrariesPath, c_gpuDriversPath);
308
@@ -400,6 +405,28 @@ void WSLCVirtualMachine::ConfigureNetworking()
405 LaunchPortRelay();
406 }
407
408 +void WSLCVirtualMachine::ReadGuestCapabilities()
409 +{
410 + WSLC_GET_GUEST_CAPABILITIES message{};
411 + const auto& response = m_initChannel.Transaction(message, nullptr, m_initChannelTimeout);
412 +
413 + m_hvPciSwiotlbBase = response.HvPciSwiotlbBase;
414 + m_hvPciSwiotlbSize = response.HvPciSwiotlbSize;
415 +
416 + WSL_LOG(
417 + "WSLCReadGuestCapabilities",
418 + TraceLoggingValue(m_hvPciSwiotlbBase, "HvPciSwiotlbBase"),
419 + TraceLoggingValue(m_hvPciSwiotlbSize, "HvPciSwiotlbSize"));
420 +
421 + // Forward the values to the service so AddShare and ConfigureNetworking can include the
422 + // swiotlb device-options token. Passing zero for both means the guest kernel does not
423 + // support hv_pci swiotlb; the service then omits the token.
424 + WSLCGuestCapabilities capabilities{};
425 + capabilities.HvPciSwiotlbBase = m_hvPciSwiotlbBase;
426 + capabilities.HvPciSwiotlbSize = m_hvPciSwiotlbSize;
427 + THROW_IF_FAILED(m_vm->ApplyGuestCapabilities(&capabilities));
428 +}
429 +
430 bool WSLCVirtualMachine::FeatureEnabled(WSLCFeatureFlags Value) const
431 {
432 return static_cast<ULONG>(m_featureFlags) & static_cast<ULONG>(Value);
src/windows/wslcsession/WSLCVirtualMachine.h
+9
@@ -175,6 +175,11 @@ private:
175 // Initial setup during Connect()
176 void ConfigureNetworking();
177
178 + // Queries the guest kernel for per-VM capabilities (currently the hv_pci swiotlb pool
179 + // reserved at boot) and forwards them to the service so that subsequent virtio device-options
180 + // can include the swiotlb token. Called after the root filesystem is mounted.
181 + void ReadGuestCapabilities();
182 +
183 static void Mount(wsl::shared::SocketChannel& Channel, LPCSTR Source, _In_ LPCSTR Target, _In_ LPCSTR Type, _In_ LPCSTR Options, _In_ ULONG Flags);
184 void MountGpuLibraries(_In_ LPCSTR LibrariesMountPoint, _In_ LPCSTR DriversMountpoint);
185
@@ -230,6 +235,10 @@ private:
235 wsl::shared::SocketChannel m_initChannel;
236 DWORD m_initChannelTimeout = 30 * 1000;
237
238 + // Swiotlb pool reserved by the guest kernel (zero when the kernel lacks the WSL patch).
239 + uint64_t m_hvPciSwiotlbBase = 0;
240 + uint64_t m_hvPciSwiotlbSize = 0;
241 +
242 // Job object that terminates child processes (wslrelay.exe) when the VM shuts down.
243 // Declared before the port relay pipes so it is destroyed after them: any remaining
244 // wslrelay.exe is given the chance to exit via the closed pipes / signaled terminating
test/windows/UnitTests.cpp
+11
@@ -2231,6 +2231,17 @@ Error code: Wsl/InstallDistro/WSL_E_DISTRO_NOT_FOUND
2231 L"[experimental]\nignoredPorts=65536",
2232 std::format(L"wsl: Invalid integer value '65536' for key 'experimental.ignoredPorts' in {}:22\r\n", wslConfigPath));
2233
2234 + // Verify experimental.swiotlb parsing and validation.
2235 + //
2236 + // With wsl2.virtio enabled (the default), a valid swiotlb value is accepted silently.
2237 + validateWarnings(L"[experimental]\nswiotlb=64M", L"");
2238 + validateWarnings(L"[experimental]\nswiotlb=4096K", L"");
2239 +
2240 + // Malformed values are rejected by the parser; only the parser warning is reported.
2241 + validateWarnings(
2242 + L"[experimental]\nswiotlb=garbage",
2243 + std::format(L"wsl: Invalid memory string 'garbage' for .wslconfig entry 'experimental.swiotlb' in {}:22\r\n", wslConfigPath));
2244 +
2245 // Verify that the vhdSize setting is parsed correctly.
2246 validateWarnings(L"[wsl2]\ndefaultVhdSize=64GB\n", L"");
2247