master
cpp 683 lines 26.3 KB
Raw
1 // Copyright (C) Microsoft Corporation. All rights reserved.
2
3 #include "precomp.h"
4 #include "DeviceHostProxy.h"
5 #include "WslSecurity.h"
6
7 // This template works around a limitation with decltype on overloaded functions. It will be able
8 // to get the correct version of GetVmWorkerProcess based on the provided type arguments. By
9 // doing it this way, a compiler error will be generated if someone changes the signature of
10 // GetVmWorkerProcess.
11 //
12 // The way this works: decltype(GetVmWorkerProcess) does not work because it's overloaded.
13 // decltype(GetVmWorkerProcess(arg1, ...)) works to select an overload if you have values of the
14 // correct type (std::declval<T>() generates a value of the specified type), however the result
15 // of that is the function's return type, not the function's type, so the argument types must
16 // be repeated to reconstruct the function type.
17 template <typename... Args>
18 using GetVmWorkerProcessType = decltype(GetVmWorkerProcess(std::declval<Args>()...))(Args...);
19
20 // Limit the number of allowed doorbells registered by an external HDV vdev. Currently virtio-9p only uses
21 // one doorbell and wsldevicehost uses only two.
22 #define DEVICE_HOST_PROXY_DOORBELL_LIMIT 8
23
24 using namespace wsl::windows::common::hcs;
25
26 namespace {
27 constexpr GUID c_virtioFsDeviceId{0x872270E1, 0xA899, 0x4AF6, {0xB4, 0x54, 0x71, 0x93, 0x63, 0x44, 0x35, 0xAD}};
28 constexpr GUID c_virtioNetDeviceId{0xF07010D0, 0x0EA9, 0x447F, {0x88, 0xEF, 0xBD, 0x95, 0x2A, 0x4D, 0x2F, 0x14}};
29 constexpr GUID c_virtioPmemDeviceId{0xEDBB24BB, 0x5E19, 0x40F4, {0x8A, 0x0F, 0x82, 0x24, 0x31, 0x30, 0x64, 0xFD}};
30 } // namespace
31
32 DeviceHostProxy::DeviceHostProxy(const std::wstring& VmId, const GUID& RuntimeId, bool EnableTelemetry) :
33 m_systemId{VmId},
34 m_runtimeId{RuntimeId},
35 m_enableTelemetry{EnableTelemetry},
36 m_system{wsl::windows::common::hcs::OpenComputeSystem(VmId.c_str(), GENERIC_ALL)},
37 m_shutdown{false}
38 {
39 m_devicesShutdown = false;
40 m_git = wil::CoCreateInstance<IGlobalInterfaceTable>(CLSID_StdGlobalInterfaceTable, CLSCTX_INPROC_SERVER);
41 }
42
43 GUID DeviceHostProxy::AddNewDevice(const GUID& Type, const wil::com_ptr<IPlan9FileSystem>& Plan9Fs, const std::wstring& VirtIoTag)
44 {
45 std::lock_guard lifecycleLock(m_deviceLifecycleLock);
46
47 const wrl::ComPtr<IUnknown> thisUnknown{CastToUnknown()};
48 GUID instanceId{};
49 THROW_IF_FAILED(UuidCreate(&instanceId));
50 // Tell the device host to create the device.
51 THROW_IF_FAILED(Plan9Fs->CreateVirtioDevice(m_systemId.c_str(), thisUnknown.Get(), VirtIoTag.c_str(), &instanceId));
52
53 // Add the instance ID to the list of known devices. This must be done before the device is
54 // added to the system, because doing that can cause the register doorbell function to be
55 // called.
56 // N.B. It will be removed if there is a failure.
57 {
58 auto lock = m_devicesLock.lock_exclusive();
59 THROW_HR_IF(E_CHANGED_STATE, m_devicesShutdown);
60
61 m_devices.emplace(instanceId, DeviceHostProxyEntry{.Type = Type});
62 }
63
64 auto removeOnFailure = wil::scope_exit_log(WI_DIAGNOSTICS_INFO, [&]() {
65 auto lock = m_devicesLock.lock_exclusive();
66 m_devices.erase(instanceId);
67 });
68
69 // Add the device to the compute system on behalf of the device host.
70 AddFlexibleIoDevice(Type, instanceId);
71 removeOnFailure.release();
72 return instanceId;
73 }
74
75 GUID DeviceHostProxy::AddVirtioNetDevice(_In_ HANDLE UserToken, const WslVirtioNetConfig& Config, const std::vector<IpAddress>& Nameservers)
76 {
77 std::lock_guard lifecycleLock(m_deviceLifecycleLock);
78
79 GUID instanceId{};
80 THROW_IF_FAILED(UuidCreate(&instanceId));
81
82 {
83 auto lock = m_devicesLock.lock_exclusive();
84 THROW_HR_IF(E_CHANGED_STATE, m_devicesShutdown);
85 m_devices.emplace(instanceId, DeviceHostProxyEntry{.Type = c_virtioNetDeviceId});
86 }
87
88 wil::com_ptr<IUnknown> device;
89 auto removeOnFailure = wil::scope_exit_log(WI_DIAGNOSTICS_INFO, [&]() {
90 TeardownDevice(device);
91 auto lock = m_devicesLock.lock_exclusive();
92 m_devices.erase(instanceId);
93 });
94 wil::com_ptr<IWslVirtioNetDevice> netDevice;
95 {
96 auto instanceIdForCall = instanceId;
97 auto config = Config;
98 auto nameservers = Nameservers;
99 IpAddress emptyNameserver{};
100 auto* nameserversData = nameservers.empty() ? &emptyNameserver : nameservers.data();
101 THROW_IF_FAILED(GetWslVm(UserToken)->CreateVirtioNetDevice(
102 &instanceIdForCall,
103 GetCallback().get(),
104 &config,
105 gsl::narrow_cast<UINT32>(nameservers.size()),
106 nameserversData,
107 netDevice.put()));
108 }
109 device = netDevice.query<IUnknown>();
110
111 {
112 auto lock = m_devicesLock.lock_exclusive();
113 const auto entry = m_devices.find(instanceId);
114 THROW_HR_IF(E_CHANGED_STATE, m_devicesShutdown || entry == m_devices.end());
115 entry->second.Device = device;
116 }
117
118 AddFlexibleIoDevice(c_virtioNetDeviceId, instanceId);
119 removeOnFailure.release();
120 return instanceId;
121 }
122
123 GUID DeviceHostProxy::AddVirtiofsDevice(
124 _In_ HANDLE UserToken, const std::wstring& Label, const std::wstring& RootPath, VirtiofsShareKind Kind, UINT32 ShmemSizeMb, const std::wstring& MountOptions)
125 {
126 std::lock_guard lifecycleLock(m_deviceLifecycleLock);
127
128 GUID instanceId{};
129 THROW_IF_FAILED(UuidCreate(&instanceId));
130
131 {
132 auto lock = m_devicesLock.lock_exclusive();
133 THROW_HR_IF(E_CHANGED_STATE, m_devicesShutdown);
134 m_devices.emplace(instanceId, DeviceHostProxyEntry{.Type = c_virtioFsDeviceId});
135 }
136
137 wil::com_ptr<IUnknown> device;
138 auto removeOnFailure = wil::scope_exit_log(WI_DIAGNOSTICS_INFO, [&]() {
139 TeardownDevice(device);
140 auto lock = m_devicesLock.lock_exclusive();
141 m_devices.erase(instanceId);
142 });
143 wil::com_ptr<IWslVirtiofsDevice> virtiofsDevice;
144 {
145 auto instanceIdForCall = instanceId;
146 const auto label = wil::make_bstr(Label.c_str());
147 const auto rootPath = wil::make_bstr(RootPath.c_str());
148 const auto mountOptions = wil::make_bstr(MountOptions.c_str());
149
150 // Only a few aggregate devices exist per VM, so they can afford multiple queues. Per-share
151 // devices stay at one queue to avoid exhausting the memory aperture.
152 const UINT32 queueCount = (Kind == VirtiofsShareKind_Aggregate) ? 4 : 1;
153
154 WslVirtiofsConfig config{
155 .label = label.get(),
156 .rootPath = rootPath.get(),
157 .kind = Kind,
158 .shmemSizeMb = ShmemSizeMb,
159 .queueCount = queueCount,
160 .mountOptions = mountOptions.get()};
161 THROW_IF_FAILED(GetWslVm(UserToken)->CreateVirtiofsDevice(&instanceIdForCall, GetCallback().get(), &config, virtiofsDevice.put()));
162 }
163 device = virtiofsDevice.query<IUnknown>();
164
165 {
166 auto lock = m_devicesLock.lock_exclusive();
167 const auto entry = m_devices.find(instanceId);
168 THROW_HR_IF(E_CHANGED_STATE, m_devicesShutdown || entry == m_devices.end());
169 entry->second.Device = device;
170 }
171
172 AddFlexibleIoDevice(c_virtioFsDeviceId, instanceId);
173 removeOnFailure.release();
174 return instanceId;
175 }
176
177 void DeviceHostProxy::AddVirtiofsChild(const GUID& InstanceId, const std::wstring& Name, const std::wstring& RootPath, const std::wstring& MountOptions)
178 {
179 std::lock_guard lifecycleLock(m_deviceLifecycleLock);
180
181 const auto name = wil::make_bstr(Name.c_str());
182 const auto rootPath = wil::make_bstr(RootPath.c_str());
183 const auto mountOptions = wil::make_bstr(MountOptions.c_str());
184 THROW_IF_FAILED(GetVirtiofsDevice(InstanceId)->AddChild(name.get(), rootPath.get(), mountOptions.get()));
185 }
186
187 void DeviceHostProxy::RemoveVirtiofsChild(const GUID& InstanceId, const std::wstring& Name)
188 {
189 std::lock_guard lifecycleLock(m_deviceLifecycleLock);
190
191 const auto name = wil::make_bstr(Name.c_str());
192 THROW_IF_FAILED(GetVirtiofsDevice(InstanceId)->RemoveChild(name.get()));
193 }
194
195 GUID DeviceHostProxy::AddVirtioPmemDevice(_In_ HANDLE UserToken, const std::wstring& Path, bool Writable)
196 {
197 std::lock_guard lifecycleLock(m_deviceLifecycleLock);
198
199 GUID instanceId{};
200 THROW_IF_FAILED(UuidCreate(&instanceId));
201
202 {
203 auto lock = m_devicesLock.lock_exclusive();
204 THROW_HR_IF(E_CHANGED_STATE, m_devicesShutdown);
205 m_devices.emplace(instanceId, DeviceHostProxyEntry{.Type = c_virtioPmemDeviceId});
206 }
207
208 wil::com_ptr<IUnknown> device;
209 auto removeOnFailure = wil::scope_exit_log(WI_DIAGNOSTICS_INFO, [&]() {
210 TeardownDevice(device);
211 auto lock = m_devicesLock.lock_exclusive();
212 m_devices.erase(instanceId);
213 });
214 wil::com_ptr<IWslVirtioPmemDevice> pmemDevice;
215 {
216 auto instanceIdForCall = instanceId;
217 const auto path = wil::make_bstr(Path.c_str());
218 WslVirtioPmemConfig config{.path = path.get(), .writable = Writable};
219 THROW_IF_FAILED(GetWslVm(UserToken)->CreateVirtioPmemDevice(&instanceIdForCall, GetCallback().get(), &config, pmemDevice.put()));
220 }
221 device = pmemDevice.query<IUnknown>();
222
223 {
224 auto lock = m_devicesLock.lock_exclusive();
225 const auto entry = m_devices.find(instanceId);
226 THROW_HR_IF(E_CHANGED_STATE, m_devicesShutdown || entry == m_devices.end());
227 entry->second.Device = device;
228 }
229
230 AddFlexibleIoDevice(c_virtioPmemDeviceId, instanceId);
231 removeOnFailure.release();
232 return instanceId;
233 }
234
235 void DeviceHostProxy::AddFlexibleIoDevice(const GUID& Type, const GUID& InstanceId)
236 {
237 ModifySettingRequest<FlexibleIoDevice> request;
238 request.RequestType = ModifyRequestType::Add;
239 request.ResourcePath = L"VirtualMachine/Devices/FlexibleIov/";
240 request.ResourcePath += wsl::shared::string::GuidToString<wchar_t>(InstanceId, wsl::shared::string::GuidToStringFlags::None);
241 request.Settings.EmulatorId = Type;
242 request.Settings.HostingModel = FlexibleIoDeviceHostingModel::ExternalRestricted;
243 wsl::windows::common::hcs::ModifyComputeSystem(m_system.get(), wsl::shared::ToJsonW(request).c_str());
244 }
245
246 void DeviceHostProxy::RemoveDevice(const GUID& InstanceId)
247 {
248 std::lock_guard lifecycleLock(m_deviceLifecycleLock);
249 wil::com_ptr<IUnknown> device;
250 GUID type{};
251
252 {
253 auto lock = m_devicesLock.lock_exclusive();
254 const auto entry = m_devices.find(InstanceId);
255 THROW_HR_IF(E_CHANGED_STATE, m_devicesShutdown);
256 THROW_HR_IF(E_INVALIDARG, entry == m_devices.end());
257 entry->second.ShuttingDown = true;
258 device = entry->second.Device;
259 type = entry->second.Type;
260 }
261
262 TeardownDevice(device);
263
264 {
265 auto lock = m_devicesLock.lock_exclusive();
266 m_devices.erase(InstanceId);
267 }
268
269 // N.B. Removing the FlexIov device is best effort since not all versions of Windows support it.
270 try
271 {
272 ModifySettingRequest<FlexibleIoDevice> request;
273 request.RequestType = ModifyRequestType::Remove;
274 request.ResourcePath = L"VirtualMachine/Devices/FlexibleIov/";
275 request.ResourcePath += wsl::shared::string::GuidToString<wchar_t>(InstanceId, wsl::shared::string::GuidToStringFlags::None);
276 request.Settings.EmulatorId = type;
277 request.Settings.HostingModel = FlexibleIoDeviceHostingModel::ExternalRestricted;
278 wsl::windows::common::hcs::ModifyComputeSystem(m_system.get(), wsl::shared::ToJsonW(request).c_str());
279 }
280 CATCH_LOG()
281 }
282
283 void DeviceHostProxy::AddRemoteFileSystem(const GUID& ImplementationClsid, const std::wstring& Tag, const wil::com_ptr<IPlan9FileSystem>& Plan9Fs)
284 {
285 auto lock = m_lock.lock_exclusive();
286 THROW_HR_IF(E_CHANGED_STATE, m_shutdown);
287
288 // Make sure there are no duplicate tags.
289 for (auto& entry : m_fileSystems)
290 {
291 THROW_HR_IF(E_INVALIDARG, entry.ImplementationClsid == ImplementationClsid && entry.Tag == Tag);
292 }
293
294 m_fileSystems.emplace_back(ImplementationClsid, Tag, Plan9Fs, m_git.get());
295 }
296
297 wil::com_ptr<IPlan9FileSystem> DeviceHostProxy::GetRemoteFileSystem(const GUID& ImplementationClsid, std::wstring_view Tag)
298 {
299 auto lock = m_lock.lock_shared();
300 THROW_HR_IF(E_CHANGED_STATE, m_shutdown);
301
302 for (auto& entry : m_fileSystems)
303 {
304 if (entry.ImplementationClsid == ImplementationClsid && entry.Tag == Tag)
305 {
306 // Retrieve the instance from the global interface table to ensure the correct apartment/thread affinity.
307 // This is required because we might be running under MTA or NA depending on which class we were called from.
308
309 wil::com_ptr<IPlan9FileSystem> instance;
310 THROW_IF_FAILED(
311 m_git->GetInterfaceFromGlobal(entry.Cookie, __uuidof(IPlan9FileSystem), reinterpret_cast<void**>(instance.put())));
312 return instance;
313 }
314 }
315
316 return {};
317 }
318
319 wil::com_ptr<IWslVirtioNetDevice> DeviceHostProxy::GetVirtioNetDevice(const GUID& InstanceId)
320 {
321 auto lock = m_devicesLock.lock_shared();
322 THROW_HR_IF(E_CHANGED_STATE, m_devicesShutdown);
323
324 const auto device = m_devices.find(InstanceId);
325 THROW_HR_IF(E_NOT_SET, device == m_devices.end() || device->second.ShuttingDown || !device->second.Device);
326 return device->second.Device.query<IWslVirtioNetDevice>();
327 }
328
329 wil::com_ptr<IWslVirtiofsDevice> DeviceHostProxy::GetVirtiofsDevice(const GUID& InstanceId)
330 {
331 auto lock = m_devicesLock.lock_shared();
332 THROW_HR_IF(E_CHANGED_STATE, m_devicesShutdown);
333
334 const auto device = m_devices.find(InstanceId);
335 THROW_HR_IF(E_NOT_SET, device == m_devices.end() || device->second.ShuttingDown || !device->second.Device);
336 return device->second.Device.query<IWslVirtiofsDevice>();
337 }
338
339 void DeviceHostProxy::SetSwiotlb(UINT64 GpaBase, UINT64 SizeBytes)
340 {
341 if (GpaBase == 0 && SizeBytes == 0)
342 {
343 return;
344 }
345
346 auto lock = m_lock.lock_exclusive();
347 THROW_HR_IF(E_CHANGED_STATE, m_shutdown);
348
349 SwiotlbConfig config{.gpaBase = GpaBase, .sizeBytes = SizeBytes};
350 if (m_swiotlbConfigured)
351 {
352 THROW_HR_IF(
353 HRESULT_FROM_WIN32(ERROR_ALREADY_EXISTS),
354 m_swiotlbConfig.gpaBase != config.gpaBase || m_swiotlbConfig.sizeBytes != config.sizeBytes);
355 }
356 else
357 {
358 m_swiotlbConfig = config;
359 m_swiotlbConfigured = true;
360 }
361
362 ConfigureSwiotlb(m_wslVm, m_wslVmSwiotlbConfigured);
363 ConfigureSwiotlb(m_adminWslVm, m_adminWslVmSwiotlbConfigured);
364 }
365
366 void DeviceHostProxy::Shutdown()
367 {
368 std::lock_guard lifecycleLock(m_deviceLifecycleLock);
369
370 {
371 auto lock = m_lock.lock_exclusive();
372 m_fileSystems.clear();
373 m_shutdown = true;
374 }
375
376 std::vector<wil::com_ptr<IUnknown>> devices;
377 {
378 auto lock = m_devicesLock.lock_exclusive();
379
380 // Block device retrieval and new registrations while retaining the entries needed by
381 // Teardown() callbacks to unregister doorbells and destroy mapped ranges.
382 m_devicesShutdown = true;
383 devices.reserve(m_devices.size());
384 for (auto& device : m_devices)
385 {
386 device.second.ShuttingDown = true;
387 devices.emplace_back(device.second.Device);
388 }
389 }
390
391 for (const auto& device : devices)
392 {
393 TeardownDevice(device);
394 }
395
396 {
397 auto lock = m_devicesLock.lock_exclusive();
398 m_devices.clear();
399 }
400 }
401
402 wil::com_ptr<IWslVm> DeviceHostProxy::GetWslVm(_In_ HANDLE UserToken)
403 {
404 auto lock = m_lock.lock_exclusive();
405 THROW_HR_IF(E_CHANGED_STATE, m_shutdown);
406
407 const auto elevated = wsl::windows::common::security::IsTokenElevated(UserToken);
408 auto& cachedVm = elevated ? m_adminWslVm : m_wslVm;
409 auto& swiotlbConfigured = elevated ? m_adminWslVmSwiotlbConfigured : m_wslVmSwiotlbConfigured;
410 if (!cachedVm)
411 {
412 auto revert = wil::impersonate_token(UserToken);
413 const auto& clsid = m_enableTelemetry
414 ? (elevated ? CLSID_WSL_DEVICE_HOST_ADMIN : CLSID_WSL_DEVICE_HOST)
415 : (elevated ? CLSID_WSL_DEVICE_HOST_NO_TELEMETRY_ADMIN : CLSID_WSL_DEVICE_HOST_NO_TELEMETRY);
416 const auto host = wil::CoCreateInstance<IWslDeviceHost>(clsid, CLSCTX_LOCAL_SERVER | CLSCTX_ENABLE_CLOAKING | CLSCTX_ENABLE_AAA);
417 auto vmId = m_runtimeId;
418 wil::com_ptr<IWslVm> vm;
419 THROW_IF_FAILED(host->OpenVm(&vmId, vm.put()));
420 cachedVm = std::move(vm);
421 }
422
423 ConfigureSwiotlb(cachedVm, swiotlbConfigured);
424 return cachedVm;
425 }
426
427 _Requires_lock_held_(m_lock)
428 void DeviceHostProxy::ConfigureSwiotlb(const wil::com_ptr<IWslVm>& Vm, bool& Configured)
429 {
430 if (Vm && m_swiotlbConfigured && !Configured)
431 {
432 THROW_IF_FAILED(Vm->SetSwiotlb(&m_swiotlbConfig));
433 Configured = true;
434 }
435 }
436
437 wil::com_ptr<IWslDeviceHostCallback> DeviceHostProxy::GetCallback()
438 {
439 wil::com_ptr<IWslDeviceHostCallback> callback;
440 THROW_IF_FAILED(CastToUnknown()->QueryInterface(IID_PPV_ARGS(callback.put())));
441 return callback;
442 }
443
444 void DeviceHostProxy::TeardownDevice(const wil::com_ptr<IUnknown>& Device) noexcept
445 {
446 if (!Device)
447 {
448 return;
449 }
450
451 if (const auto netDevice = Device.try_query<IWslVirtioNetDevice>())
452 {
453 LOG_IF_FAILED(netDevice->Teardown());
454 }
455 else if (const auto virtiofsDevice = Device.try_query<IWslVirtiofsDevice>())
456 {
457 LOG_IF_FAILED(virtiofsDevice->Teardown());
458 }
459 else if (const auto pmemDevice = Device.try_query<IWslVirtioPmemDevice>())
460 {
461 LOG_IF_FAILED(pmemDevice->Teardown());
462 }
463 }
464
465 HRESULT
466 DeviceHostProxy::RegisterDeviceHost(_In_ IVmDeviceHost* DeviceHost, _In_ DWORD ProcessId, _Out_ UINT64* IpcSectionHandle)
467 try
468 {
469 //
470 // Because HdvProxyDeviceHost is not part of the API set, it is loaded here dynamically.
471 //
472
473 static LxssDynamicFunction<decltype(HdvProxyDeviceHost)> proxyDeviceHost{c_hdvModuleName, "HdvProxyDeviceHost"};
474 const wil::com_ptr<IVmDeviceHost> remoteHost = DeviceHost;
475 const wil::com_ptr<IUnknown> unknown = remoteHost.query<IUnknown>();
476 THROW_IF_FAILED(proxyDeviceHost(m_system.get(), unknown.get(), ProcessId, IpcSectionHandle));
477
478 // Assign the device host process to a fresh kill-on-close job so it is terminated when the VM
479 // shuts down. Each process needs its own job: a process the system has already placed in a job
480 // cannot be assigned to a job that already owns a different process (ERROR_ACCESS_DENIED).
481 {
482 auto lock = m_devicesLock.lock_exclusive();
483 if (!m_devicesShutdown)
484 {
485 wil::unique_handle process(OpenProcess(PROCESS_SET_QUOTA | PROCESS_TERMINATE, FALSE, ProcessId));
486 LOG_LAST_ERROR_IF_MSG(!process, "Failed to open device host process %u for job assignment", ProcessId);
487 if (process)
488 {
489 wil::unique_handle job = wsl::windows::common::helpers::CreateKillOnCloseJob();
490 if (AssignProcessToJobObject(job.get(), process.get()))
491 {
492 m_processJobs.emplace_back(std::move(job));
493 }
494 else
495 {
496 LOG_LAST_ERROR_MSG("Failed to assign device host process %u to job object", ProcessId);
497 }
498 }
499 }
500 }
501
502 return S_OK;
503 }
504 CATCH_RETURN()
505
506 HRESULT
507 DeviceHostProxy::NotifyAllDevicesInUse(_In_ LPCWSTR Tag)
508 try
509 {
510 //
511 // Add another Plan9 virtio device to the guest so additional mount commands will be possible.
512 // This callback should be unused by virtiofs devices because a device is created for every
513 // AddSharePath call.
514 //
515 auto p9fs = GetRemoteFileSystem(__uuidof(p9fs::Plan9FileSystem), Tag);
516 THROW_HR_IF(E_NOT_SET, !p9fs);
517 (void)AddNewDevice(VIRTIO_PLAN9_DEVICE_ID, p9fs, Tag);
518 return S_OK;
519 }
520 CATCH_RETURN()
521
522 HRESULT
523 DeviceHostProxy::RegisterDoorbell(const GUID& InstanceId, UINT8 BarIndex, UINT64 Offset, UINT64 TriggerValue, UINT64 Flags, HANDLE Event)
524 {
525 return RegisterDoorbellImpl(InstanceId, BarIndex, Offset, TriggerValue, Flags, Event);
526 }
527
528 HRESULT
529 DeviceHostProxy::RegisterDoorbell(GUID InstanceId, BYTE BarIndex, UINT64 Offset, UINT64 TriggerValue, UINT64 Flags, HANDLE Event)
530 {
531 return RegisterDoorbellImpl(InstanceId, BarIndex, Offset, TriggerValue, Flags, Event);
532 }
533
534 HRESULT DeviceHostProxy::RegisterDoorbellImpl(const GUID& InstanceId, UINT8 BarIndex, UINT64 Offset, UINT64 TriggerValue, UINT64 Flags, HANDLE Event) noexcept
535 try
536 {
537 auto lock = m_devicesLock.lock_exclusive();
538 RETURN_HR_IF(E_CHANGED_STATE, m_devicesShutdown);
539
540 // Check if the device is one of the known devices that doorbells can be registered for, and
541 // if the device has not already registered a doorbell.
542 // N.B. For security it is enforced that each device can only register a small number of doorbells.
543 // Currently virtio-9p only uses one and the external virtio device uses two.
544 const auto knownDevice = m_devices.find(InstanceId);
545 RETURN_HR_IF(E_ACCESSDENIED, knownDevice == m_devices.end() || knownDevice->second.ShuttingDown || knownDevice->second.DoorbellCount == DEVICE_HOST_PROXY_DOORBELL_LIMIT);
546
547 if (!knownDevice->second.MemoryNotification)
548 {
549 // Get an interface to the worker process to query devices.
550 if (!m_deviceAccess)
551 {
552 static LxssDynamicFunction<GetVmWorkerProcessType<REFGUID, REFIID, IUnknown**>> getVmWorker{
553 c_vmwpctrlModuleName, "GetVmWorkerProcess"};
554
555 RETURN_IF_FAILED(getVmWorker(m_runtimeId, __uuidof(*m_deviceAccess), reinterpret_cast<IUnknown**>(&m_deviceAccess)));
556 }
557
558 RETURN_HR_IF(E_NOINTERFACE, !m_deviceAccess);
559
560 // Retrieve the device's memory notification interface to register the doorbell, and store it
561 // to be used during unregistration.
562 wil::com_ptr<IUnknown> device;
563 RETURN_IF_FAILED(m_deviceAccess->GetDevice(FLEXIO_DEVICE_ID, InstanceId, &device));
564 knownDevice->second.MemoryNotification = device.query<IVmFiovGuestMemoryFastNotification>();
565 }
566
567 const auto result = knownDevice->second.MemoryNotification->RegisterDoorbell(
568 static_cast<FIOV_BAR_SELECTOR>(BarIndex), Offset, TriggerValue, Flags, Event);
569
570 if (SUCCEEDED(result))
571 {
572 ++knownDevice->second.DoorbellCount;
573 }
574
575 return result;
576 }
577 CATCH_RETURN()
578
579 HRESULT
580 DeviceHostProxy::UnregisterDoorbell(const GUID& InstanceId, UINT8 BarIndex, UINT64 Offset, UINT64 TriggerValue, UINT64 Flags)
581 {
582 return UnregisterDoorbellImpl(InstanceId, BarIndex, Offset, TriggerValue, Flags);
583 }
584
585 HRESULT
586 DeviceHostProxy::UnregisterDoorbell(GUID InstanceId, BYTE BarIndex, UINT64 Offset, UINT64 TriggerValue, UINT64 Flags)
587 {
588 return UnregisterDoorbellImpl(InstanceId, BarIndex, Offset, TriggerValue, Flags);
589 }
590
591 HRESULT DeviceHostProxy::UnregisterDoorbellImpl(const GUID& InstanceId, UINT8 BarIndex, UINT64 Offset, UINT64 TriggerValue, UINT64 Flags) noexcept
592 try
593 {
594 auto lock = m_devicesLock.lock_exclusive();
595
596 // Check if the device is a known device and has registered a doorbell.
597 // N.B. If the device is being removed, the device can't be retrieved from the worker process
598 // so it's necessary to use the stored COM pointer.
599 const auto device = m_devices.find(InstanceId);
600 RETURN_HR_IF(E_ACCESSDENIED, device == m_devices.end() || device->second.DoorbellCount == 0);
601 RETURN_IF_FAILED(device->second.MemoryNotification->UnregisterDoorbell(static_cast<FIOV_BAR_SELECTOR>(BarIndex), Offset, TriggerValue, Flags));
602
603 if (--device->second.DoorbellCount == 0)
604 {
605 device->second.MemoryNotification.reset();
606 }
607
608 return S_OK;
609 }
610 CATCH_RETURN()
611
612 HRESULT
613 DeviceHostProxy::CreateSectionBackedMmioRange(
614 const GUID& InstanceId, UINT8 BarIndex, UINT64 BarOffsetInPages, UINT64 PageCount, UINT64 MappingFlags, HANDLE SectionHandle, UINT64 SectionOffsetInPages)
615 {
616 return CreateSectionBackedMmioRangeImpl(InstanceId, BarIndex, BarOffsetInPages, PageCount, MappingFlags, SectionHandle, SectionOffsetInPages);
617 }
618
619 HRESULT
620 DeviceHostProxy::CreateSectionBackedMmioRange(
621 GUID InstanceId, BYTE BarIndex, UINT64 BarOffsetInPages, UINT64 PageCount, UINT64 MappingFlags, HANDLE SectionHandle, UINT64 SectionOffsetInPages)
622 {
623 return CreateSectionBackedMmioRangeImpl(InstanceId, BarIndex, BarOffsetInPages, PageCount, MappingFlags, SectionHandle, SectionOffsetInPages);
624 }
625
626 HRESULT DeviceHostProxy::CreateSectionBackedMmioRangeImpl(
627 const GUID& InstanceId, UINT8 BarIndex, UINT64 BarOffsetInPages, UINT64 PageCount, UINT64 MappingFlags, HANDLE SectionHandle, UINT64 SectionOffsetInPages) noexcept
628 try
629 {
630 auto lock = m_devicesLock.lock_exclusive();
631 RETURN_HR_IF(E_CHANGED_STATE, m_devicesShutdown);
632
633 // Check if the device is one of the known devices.
634 const auto knownDevice = m_devices.find(InstanceId);
635 THROW_HR_IF(E_ACCESSDENIED, knownDevice == m_devices.end() || knownDevice->second.ShuttingDown);
636
637 if (!knownDevice->second.MemoryMapping)
638 {
639 // Get an interface to the worker process to query devices.
640 if (!m_deviceAccess)
641 {
642 static LxssDynamicFunction<GetVmWorkerProcessType<REFGUID, REFIID, IUnknown**>> getVmWorker{
643 c_vmwpctrlModuleName, "GetVmWorkerProcess"};
644 THROW_IF_FAILED(getVmWorker(m_runtimeId, __uuidof(*m_deviceAccess), reinterpret_cast<IUnknown**>(&m_deviceAccess)));
645 }
646
647 THROW_HR_IF(E_NOINTERFACE, !m_deviceAccess);
648
649 // Retrieve the device specific interface to manage mapped sections.
650 wil::com_ptr<IUnknown> device;
651 THROW_IF_FAILED(m_deviceAccess->GetDevice(FLEXIO_DEVICE_ID, InstanceId, &device));
652 knownDevice->second.MemoryMapping = device.query<IVmFiovGuestMmioMappings>();
653 }
654
655 THROW_IF_FAILED(knownDevice->second.MemoryMapping->CreateSectionBackedMmioRange(
656 static_cast<FIOV_BAR_SELECTOR>(BarIndex), BarOffsetInPages, PageCount, static_cast<FiovMmioMappingFlags>(MappingFlags), SectionHandle, SectionOffsetInPages));
657
658 return S_OK;
659 }
660 CATCH_RETURN()
661
662 HRESULT
663 DeviceHostProxy::DestroySectionBackedMmioRange(const GUID& InstanceId, UINT8 BarIndex, UINT64 BarOffsetInPages)
664 {
665 return DestroySectionBackedMmioRangeImpl(InstanceId, BarIndex, BarOffsetInPages);
666 }
667
668 HRESULT
669 DeviceHostProxy::DestroySectionBackedMmioRange(GUID InstanceId, BYTE BarIndex, UINT64 BarOffsetInPages)
670 {
671 return DestroySectionBackedMmioRangeImpl(InstanceId, BarIndex, BarOffsetInPages);
672 }
673
674 HRESULT DeviceHostProxy::DestroySectionBackedMmioRangeImpl(const GUID& InstanceId, UINT8 BarIndex, UINT64 BarOffsetInPages) noexcept
675 try
676 {
677 auto lock = m_devicesLock.lock_exclusive();
678 const auto device = m_devices.find(InstanceId);
679 RETURN_HR_IF(E_ACCESSDENIED, device == m_devices.end() || !device->second.MemoryMapping);
680 RETURN_IF_FAILED(device->second.MemoryMapping->DestroySectionBackedMmioRange(static_cast<FIOV_BAR_SELECTOR>(BarIndex), BarOffsetInPages));
681 return S_OK;
682 }
683 CATCH_RETURN()