mirror of https://github.com/microsoft/WSL
285 lines
11 KiB
C++
285 lines
11 KiB
C++
// Copyright (C) Microsoft Corporation. All rights reserved.
|
|
|
|
#include "precomp.h"
|
|
#include "DeviceHostProxy.h"
|
|
|
|
// This template works around a limitation with decltype on overloaded functions. It will be able
|
|
// to get the correct version of GetVmWorkerProcess based on the provided type arguments. By
|
|
// doing it this way, a compiler error will be generated if someone changes the signature of
|
|
// GetVmWorkerProcess.
|
|
//
|
|
// The way this works: decltype(GetVmWorkerProcess) does not work because it's overloaded.
|
|
// decltype(GetVmWorkerProcess(arg1, ...)) works to select an overload if you have values of the
|
|
// correct type (std::declval<T>() generates a value of the specified type), however the result
|
|
// of that is the function's return type, not the function's type, so the argument types must
|
|
// be repeated to reconstruct the function type.
|
|
template <typename... Args>
|
|
using GetVmWorkerProcessType = decltype(GetVmWorkerProcess(std::declval<Args>()...))(Args...);
|
|
|
|
// Limit the number of allowed doorbells registered by an external HDV vdev. Currently virtio-9p only uses
|
|
// one doorbell and wsldevicehost uses only two.
|
|
#define DEVICE_HOST_PROXY_DOORBELL_LIMIT 8
|
|
|
|
using namespace wsl::windows::common::hcs;
|
|
|
|
DeviceHostProxy::DeviceHostProxy(const std::wstring& VmId, const GUID& RuntimeId) :
|
|
m_systemId{VmId}, m_runtimeId{RuntimeId}, m_system{wsl::windows::common::hcs::OpenComputeSystem(VmId.c_str(), GENERIC_ALL)}, m_shutdown{false}
|
|
{
|
|
m_devicesShutdown = false;
|
|
}
|
|
|
|
GUID DeviceHostProxy::AddNewDevice(const GUID& Type, const wil::com_ptr<IPlan9FileSystem>& Plan9Fs, const std::wstring& VirtIoTag)
|
|
{
|
|
const wrl::ComPtr<IUnknown> thisUnknown{CastToUnknown()};
|
|
GUID instanceId{};
|
|
THROW_IF_FAILED(UuidCreate(&instanceId));
|
|
// Tell the device host to create the device.
|
|
THROW_IF_FAILED(Plan9Fs->CreateVirtioDevice(m_systemId.c_str(), thisUnknown.Get(), VirtIoTag.c_str(), &instanceId));
|
|
|
|
// Add the instance ID to the list of known devices. This must be done before the device is
|
|
// added to the system, because doing that can cause the register doorbell function to be
|
|
// called.
|
|
// N.B. It will be removed if there is a failure.
|
|
{
|
|
auto lock = m_devicesLock.lock_exclusive();
|
|
THROW_HR_IF(E_CHANGED_STATE, m_devicesShutdown);
|
|
|
|
m_devices.emplace(instanceId, DeviceHostProxyEntry{});
|
|
}
|
|
|
|
auto removeOnFailure = wil::scope_exit_log(WI_DIAGNOSTICS_INFO, [&]() {
|
|
auto lock = m_devicesLock.lock_exclusive();
|
|
m_devices.erase(instanceId);
|
|
});
|
|
|
|
// Add the device to the compute system on behalf of the device host.
|
|
ModifySettingRequest<FlexibleIoDevice> request;
|
|
request.RequestType = ModifyRequestType::Add;
|
|
request.ResourcePath = L"VirtualMachine/Devices/FlexibleIov/";
|
|
request.ResourcePath += wsl::shared::string::GuidToString<wchar_t>(instanceId, wsl::shared::string::GuidToStringFlags::None);
|
|
request.Settings.EmulatorId = Type;
|
|
request.Settings.HostingModel = FlexibleIoDeviceHostingModel::ExternalRestricted;
|
|
wsl::windows::common::hcs::ModifyComputeSystem(m_system.get(), wsl::shared::ToJsonW(request).c_str());
|
|
removeOnFailure.release();
|
|
return instanceId;
|
|
}
|
|
|
|
void DeviceHostProxy::RemoveDevice(const GUID& Type, const GUID& InstanceId)
|
|
{
|
|
{
|
|
auto lock = m_devicesLock.lock_exclusive();
|
|
THROW_HR_IF(E_CHANGED_STATE, m_devicesShutdown);
|
|
THROW_HR_IF(E_INVALIDARG, m_devices.find(InstanceId) == m_devices.end());
|
|
|
|
m_devices.erase(InstanceId);
|
|
}
|
|
|
|
// N.B. Removing the FlexIov device is best effort since not all versions of Windows support it.
|
|
try
|
|
{
|
|
ModifySettingRequest<FlexibleIoDevice> request;
|
|
request.RequestType = ModifyRequestType::Remove;
|
|
request.ResourcePath = L"VirtualMachine/Devices/FlexibleIov/";
|
|
request.ResourcePath += wsl::shared::string::GuidToString<wchar_t>(InstanceId, wsl::shared::string::GuidToStringFlags::None);
|
|
request.Settings.EmulatorId = Type;
|
|
request.Settings.HostingModel = FlexibleIoDeviceHostingModel::ExternalRestricted;
|
|
wsl::windows::common::hcs::ModifyComputeSystem(m_system.get(), wsl::shared::ToJsonW(request).c_str());
|
|
}
|
|
CATCH_LOG()
|
|
}
|
|
|
|
void DeviceHostProxy::AddRemoteFileSystem(const GUID& ImplementationClsid, const std::wstring& Tag, const wil::com_ptr<IPlan9FileSystem>& Plan9Fs)
|
|
{
|
|
auto lock = m_lock.lock_exclusive();
|
|
THROW_HR_IF(E_CHANGED_STATE, m_shutdown);
|
|
|
|
// Make sure there are no duplicate tags.
|
|
for (auto& entry : m_fileSystems)
|
|
{
|
|
THROW_HR_IF(E_INVALIDARG, entry.ImplementationClsid == ImplementationClsid && entry.Tag == Tag);
|
|
}
|
|
|
|
m_fileSystems.emplace_back(ImplementationClsid, Tag, Plan9Fs);
|
|
}
|
|
|
|
wil::com_ptr<IPlan9FileSystem> DeviceHostProxy::GetRemoteFileSystem(const GUID& ImplementationClsid, std::wstring_view Tag)
|
|
{
|
|
auto lock = m_lock.lock_shared();
|
|
THROW_HR_IF(E_CHANGED_STATE, m_shutdown);
|
|
|
|
for (auto& entry : m_fileSystems)
|
|
{
|
|
if (entry.ImplementationClsid == ImplementationClsid && entry.Tag == Tag)
|
|
{
|
|
return entry.Instance;
|
|
}
|
|
}
|
|
|
|
return {};
|
|
}
|
|
|
|
void DeviceHostProxy::Shutdown()
|
|
{
|
|
{
|
|
auto lock = m_lock.lock_exclusive();
|
|
m_fileSystems.clear();
|
|
m_shutdown = true;
|
|
}
|
|
|
|
{
|
|
auto lock = m_devicesLock.lock_exclusive();
|
|
m_devices.clear();
|
|
m_devicesShutdown = true;
|
|
}
|
|
}
|
|
|
|
HRESULT
|
|
DeviceHostProxy::RegisterDeviceHost(_In_ IVmDeviceHost* DeviceHost, _In_ DWORD ProcessId, _Out_ UINT64* IpcSectionHandle)
|
|
try
|
|
{
|
|
//
|
|
// Because HdvProxyDeviceHost is not part of the API set, it is loaded here dynamically.
|
|
//
|
|
|
|
static LxssDynamicFunction<decltype(HdvProxyDeviceHost)> proxyDeviceHost{c_hdvModuleName, "HdvProxyDeviceHost"};
|
|
const wil::com_ptr<IVmDeviceHost> remoteHost = DeviceHost;
|
|
const wil::com_ptr<IUnknown> unknown = remoteHost.query<IUnknown>();
|
|
THROW_IF_FAILED(proxyDeviceHost(m_system.get(), unknown.get(), ProcessId, IpcSectionHandle));
|
|
return S_OK;
|
|
}
|
|
CATCH_RETURN()
|
|
|
|
HRESULT
|
|
DeviceHostProxy::NotifyAllDevicesInUse(_In_ LPCWSTR Tag)
|
|
try
|
|
{
|
|
//
|
|
// Add another Plan9 virtio device to the guest so additional mount commands will be possible.
|
|
// This callback should be unused by virtiofs devices because a device is created for every
|
|
// AddSharePath call.
|
|
//
|
|
auto p9fs = GetRemoteFileSystem(__uuidof(p9fs::Plan9FileSystem), Tag);
|
|
THROW_HR_IF(E_NOT_SET, !p9fs);
|
|
(void)AddNewDevice(VIRTIO_PLAN9_DEVICE_ID, p9fs, Tag);
|
|
return S_OK;
|
|
}
|
|
CATCH_RETURN()
|
|
|
|
HRESULT
|
|
DeviceHostProxy::RegisterDoorbell(const GUID& InstanceId, UINT8 BarIndex, UINT64 Offset, UINT64 TriggerValue, UINT64 Flags, HANDLE Event)
|
|
try
|
|
{
|
|
auto lock = m_devicesLock.lock_exclusive();
|
|
RETURN_HR_IF(E_CHANGED_STATE, m_devicesShutdown);
|
|
|
|
// Check if the device is one of the known devices that doorbells can be registered for, and
|
|
// if the device has not already registered a doorbell.
|
|
// N.B. For security it is enforced that each device can only register a small number of doorbells.
|
|
// Currently virtio-9p only uses one and the external virtio device uses two.
|
|
const auto knownDevice = m_devices.find(InstanceId);
|
|
RETURN_HR_IF(E_ACCESSDENIED, knownDevice == m_devices.end() || knownDevice->second.DoorbellCount == DEVICE_HOST_PROXY_DOORBELL_LIMIT);
|
|
|
|
if (!knownDevice->second.MemoryNotification)
|
|
{
|
|
// Get an interface to the worker process to query devices.
|
|
if (!m_deviceAccess)
|
|
{
|
|
static LxssDynamicFunction<GetVmWorkerProcessType<REFGUID, REFIID, IUnknown**>> getVmWorker{
|
|
c_vmwpctrlModuleName, "GetVmWorkerProcess"};
|
|
|
|
RETURN_IF_FAILED(getVmWorker(m_runtimeId, __uuidof(*m_deviceAccess), reinterpret_cast<IUnknown**>(&m_deviceAccess)));
|
|
}
|
|
|
|
RETURN_HR_IF(E_NOINTERFACE, !m_deviceAccess);
|
|
|
|
// Retrieve the device's memory notification interface to register the doorbell, and store it
|
|
// to be used during unregistration.
|
|
wil::com_ptr<IUnknown> device;
|
|
RETURN_IF_FAILED(m_deviceAccess->GetDevice(FLEXIO_DEVICE_ID, InstanceId, &device));
|
|
knownDevice->second.MemoryNotification = device.query<IVmFiovGuestMemoryFastNotification>();
|
|
}
|
|
|
|
const auto result = knownDevice->second.MemoryNotification->RegisterDoorbell(
|
|
static_cast<FIOV_BAR_SELECTOR>(BarIndex), Offset, TriggerValue, Flags, Event);
|
|
|
|
if (SUCCEEDED(result))
|
|
{
|
|
++knownDevice->second.DoorbellCount;
|
|
}
|
|
|
|
return result;
|
|
}
|
|
CATCH_RETURN()
|
|
|
|
HRESULT
|
|
DeviceHostProxy::UnregisterDoorbell(const GUID& InstanceId, UINT8 BarIndex, UINT64 Offset, UINT64 TriggerValue, UINT64 Flags)
|
|
try
|
|
{
|
|
auto lock = m_devicesLock.lock_exclusive();
|
|
RETURN_HR_IF(E_CHANGED_STATE, m_devicesShutdown);
|
|
|
|
// Check if the device is a known device and has registered a doorbell.
|
|
// N.B. If the device is being removed, the device can't be retrieved from the worker process
|
|
// so it's necessary to use the stored COM pointer.
|
|
const auto device = m_devices.find(InstanceId);
|
|
RETURN_HR_IF(E_ACCESSDENIED, device == m_devices.end() || device->second.DoorbellCount == 0);
|
|
RETURN_IF_FAILED(device->second.MemoryNotification->UnregisterDoorbell(static_cast<FIOV_BAR_SELECTOR>(BarIndex), Offset, TriggerValue, Flags));
|
|
|
|
if (--device->second.DoorbellCount == 0)
|
|
{
|
|
device->second.MemoryNotification.reset();
|
|
}
|
|
|
|
return S_OK;
|
|
}
|
|
CATCH_RETURN()
|
|
|
|
HRESULT
|
|
DeviceHostProxy::CreateSectionBackedMmioRange(
|
|
const GUID& InstanceId, UINT8 BarIndex, UINT64 BarOffsetInPages, UINT64 PageCount, UINT64 MappingFlags, HANDLE SectionHandle, UINT64 SectionOffsetInPages)
|
|
try
|
|
{
|
|
auto lock = m_devicesLock.lock_exclusive();
|
|
RETURN_HR_IF(E_CHANGED_STATE, m_devicesShutdown);
|
|
|
|
// Check if the device is one of the known devices.
|
|
const auto knownDevice = m_devices.find(InstanceId);
|
|
THROW_HR_IF(E_ACCESSDENIED, knownDevice == m_devices.end());
|
|
|
|
if (!knownDevice->second.MemoryMapping)
|
|
{
|
|
// Get an interface to the worker process to query devices.
|
|
if (!m_deviceAccess)
|
|
{
|
|
static LxssDynamicFunction<GetVmWorkerProcessType<REFGUID, REFIID, IUnknown**>> getVmWorker{
|
|
c_vmwpctrlModuleName, "GetVmWorkerProcess"};
|
|
THROW_IF_FAILED(getVmWorker(m_runtimeId, __uuidof(*m_deviceAccess), reinterpret_cast<IUnknown**>(&m_deviceAccess)));
|
|
}
|
|
|
|
THROW_HR_IF(E_NOINTERFACE, !m_deviceAccess);
|
|
|
|
// Retrieve the device specific interface to manage mapped sections.
|
|
wil::com_ptr<IUnknown> device;
|
|
THROW_IF_FAILED(m_deviceAccess->GetDevice(FLEXIO_DEVICE_ID, InstanceId, &device));
|
|
knownDevice->second.MemoryMapping = device.query<IVmFiovGuestMmioMappings>();
|
|
}
|
|
|
|
THROW_IF_FAILED(knownDevice->second.MemoryMapping->CreateSectionBackedMmioRange(
|
|
static_cast<FIOV_BAR_SELECTOR>(BarIndex), BarOffsetInPages, PageCount, static_cast<FiovMmioMappingFlags>(MappingFlags), SectionHandle, SectionOffsetInPages));
|
|
|
|
return S_OK;
|
|
}
|
|
CATCH_RETURN()
|
|
|
|
HRESULT
|
|
DeviceHostProxy::DestroySectionBackedMmioRange(const GUID& InstanceId, UINT8 BarIndex, UINT64 BarOffsetInPages)
|
|
try
|
|
{
|
|
auto lock = m_devicesLock.lock_exclusive();
|
|
RETURN_HR_IF(E_CHANGED_STATE, m_devicesShutdown);
|
|
const auto device = m_devices.find(InstanceId);
|
|
RETURN_HR_IF(E_ACCESSDENIED, device == m_devices.end() || !device->second.MemoryMapping);
|
|
RETURN_IF_FAILED(device->second.MemoryMapping->DestroySectionBackedMmioRange(static_cast<FIOV_BAR_SELECTOR>(BarIndex), BarOffsetInPages));
|
|
return S_OK;
|
|
}
|
|
CATCH_RETURN() |