Signed-off-by: Matt Coleman <matt@xxxxxxxxx> --- src/hyperv/hyperv_driver.c | 207 ++++++++++++++++++++++++++ src/hyperv/hyperv_wmi_classes.h | 21 +++ src/hyperv/hyperv_wmi_generator.input | 163 ++++++++++++++++++++ 3 files changed, 391 insertions(+) diff --git a/src/hyperv/hyperv_driver.c b/src/hyperv/hyperv_driver.c index c99d2a4e79..419ca96828 100644 --- a/src/hyperv/hyperv_driver.c +++ b/src/hyperv/hyperv_driver.c @@ -22,6 +22,8 @@ #include <config.h> +#include <fcntl.h> + #include "internal.h" #include "datatypes.h" #include "virdomainobjlist.h" @@ -38,6 +40,8 @@ #include "virstring.h" #include "virkeycode.h" #include "domain_conf.h" +#include "virfdstream.h" +#include "virfile.h" #define VIR_FROM_THIS VIR_FROM_HYPERV @@ -294,6 +298,73 @@ hypervCapsInit(hypervPrivate *priv) return NULL; } + +static int +hypervGetVideoResolution(hypervPrivate *priv, + char *vm_uuid, + int *xRes, + int *yRes, + bool fallback) +{ + g_auto(virBuffer) query = VIR_BUFFER_INITIALIZER; + g_autoptr(Msvm_S3DisplayController) s3Display = NULL; + g_autoptr(Msvm_SyntheticDisplayController) synthetic = NULL; + g_autoptr(Msvm_VideoHead) heads = NULL; + const char *wmiClass = NULL; + char *deviceId = NULL; + g_autofree char *enabledStateString = NULL; + + if (fallback) { + wmiClass = "Msvm_S3DisplayController"; + + virBufferEscapeSQL(&query, + MSVM_S3DISPLAYCONTROLLER_WQL_SELECT "WHERE SystemName = '%s'", + vm_uuid); + + if (hypervGetWmiClass(Msvm_S3DisplayController, &s3Display) < 0 || !s3Display) + return -1; + + deviceId = s3Display->data->DeviceID; + } else { + wmiClass = "Msvm_SyntheticDisplayController"; + + virBufferEscapeSQL(&query, + MSVM_SYNTHETICDISPLAYCONTROLLER_WQL_SELECT "WHERE SystemName = '%s'", + vm_uuid); + + if (hypervGetWmiClass(Msvm_SyntheticDisplayController, &synthetic) < 0 || !synthetic) + return -1; + + deviceId = synthetic->data->DeviceID; + } + + virBufferFreeAndReset(&query); + + virBufferAsprintf(&query, + "ASSOCIATORS OF {%s." + "CreationClassName='%s'," + "DeviceID='%s'," + "SystemCreationClassName='Msvm_ComputerSystem'," + "SystemName='%s'" + "} WHERE AssocClass = Msvm_VideoHeadOnController " + "ResultClass = Msvm_VideoHead", + wmiClass, wmiClass, deviceId, vm_uuid); + + if (hypervGetWmiClass(Msvm_VideoHead, &heads) < 0) + return -1; + + enabledStateString = g_strdup_printf("%d", CIM_ENABLEDLOGICALELEMENT_ENABLEDSTATE_ENABLED); + if (heads && STREQ(heads->data->EnabledState, enabledStateString)) { + *xRes = heads->data->CurrentHorizontalResolution; + *yRes = heads->data->CurrentVerticalResolution; + + return 0; + } + + return -1; +} + + /* * Virtual device functions */ @@ -2313,6 +2384,141 @@ hypervDomainGetState(virDomainPtr domain, int *state, int *reason, } +static char * +hypervDomainScreenshot(virDomainPtr domain, + virStreamPtr stream, + unsigned int screen G_GNUC_UNUSED, + unsigned int flags) +{ + char uuid_string[VIR_UUID_STRING_BUFLEN]; + hypervPrivate *priv = domain->conn->privateData; + g_auto(virBuffer) query = VIR_BUFFER_INITIALIZER; + g_autoptr(hypervInvokeParamsList) params = NULL; + g_auto(WsXmlDocH) ret_doc = NULL; + int xRes = 640; + int yRes = 480; + g_autofree char *width = NULL; + g_autofree char *height = NULL; + g_autofree char *imageDataText = NULL; + g_autofree unsigned char *imageDataBuffer = NULL; + size_t imageDataBufferSize; + const char *temporaryDirectory = NULL; + g_autofree char *temporaryFile = NULL; + g_autofree uint8_t *ppmBuffer = NULL; + char *result = NULL; + const char *xpath = "/s:Envelope/s:Body/p:GetVirtualSystemThumbnailImage_OUTPUT/p:ImageData"; + int pixelCount; + int pixelByteCount; + size_t i = 0; + int fd = -1; + bool unlinkTemporaryFile = false; + + virCheckFlags(0, NULL); + + virUUIDFormat(domain->uuid, uuid_string); + + /* Hyper-V Generation 1 VMs use two video heads: + * - S3DisplayController is used for early boot screens. + * - SyntheticDisplayController takes over when the guest OS initializes its video driver. + * + * This attempts to get the resolution from the SyntheticDisplayController first. + * If that fails, it falls back to S3DisplayController. */ + if (hypervGetVideoResolution(priv, uuid_string, &xRes, &yRes, false) < 0) { + if (hypervGetVideoResolution(priv, uuid_string, &xRes, &yRes, true) < 0) + goto cleanup; + } + + /* prepare params */ + params = hypervCreateInvokeParamsList("GetVirtualSystemThumbnailImage", + MSVM_VIRTUALSYSTEMMANAGEMENTSERVICE_SELECTOR, + Msvm_VirtualSystemManagementService_WmiInfo); + if (!params) + goto cleanup; + + width = g_strdup_printf("%d", xRes); + hypervAddSimpleParam(params, "WidthPixels", width); + + height = g_strdup_printf("%d", yRes); + hypervAddSimpleParam(params, "HeightPixels", height); + + virBufferAsprintf(&query, + "ASSOCIATORS OF " + "{Msvm_ComputerSystem.CreationClassName='Msvm_ComputerSystem',Name='%s'} " + "WHERE ResultClass = Msvm_VirtualSystemSettingData", + uuid_string); + hypervAddEprParam(params, "TargetSystem", &query, Msvm_VirtualSystemSettingData_WmiInfo); + + /* capture and parse the screenshot */ + if (hypervInvokeMethod(priv, ¶ms, &ret_doc) < 0) + goto cleanup; + + if (!ws_xml_get_soap_envelope(ret_doc)) { + virReportError(VIR_ERR_INTERNAL_ERROR, "%s", _("Could not retrieve screenshot")); + goto cleanup; + } + + imageDataText = ws_xml_get_xpath_value(ret_doc, (char *)xpath); + + if (!imageDataText) { + virReportError(VIR_ERR_INTERNAL_ERROR, "%s", _("Failed to retrieve image data")); + goto cleanup; + } + + imageDataBuffer = g_base64_decode(imageDataText, &imageDataBufferSize); + + pixelCount = imageDataBufferSize / 2; + pixelByteCount = pixelCount * 3; + + ppmBuffer = g_new0(uint8_t, pixelByteCount); + + /* convert rgb565 to rgb888 */ + for (i = 0; i < pixelCount; i++) { + const uint16_t pixel = imageDataBuffer[i * 2] + (imageDataBuffer[i * 2 + 1] << 8); + const uint16_t redMask = 0xF800; + const uint16_t greenMask = 0x7E0; + const uint16_t blueMask = 0x1F; + const uint8_t redFive = (pixel & redMask) >> 11; + const uint8_t greenSix = (pixel & greenMask) >> 5; + const uint8_t blueFive = pixel & blueMask; + ppmBuffer[i * 3] = (redFive * 527 + 23) >> 6; + ppmBuffer[i * 3 + 1] = (greenSix * 259 + 33) >> 6; + ppmBuffer[i * 3 + 2] = (blueFive * 527 + 23) >> 6; + } + + temporaryDirectory = getenv("TMPDIR"); + if (!temporaryDirectory) + temporaryDirectory = "/tmp"; + temporaryFile = g_strdup_printf("%s/libvirt.hyperv.screendump.XXXXXX", temporaryDirectory); + if ((fd = g_mkstemp_full(temporaryFile, O_RDWR | O_CLOEXEC, S_IRUSR | S_IWUSR)) == -1) { + virReportSystemError(errno, _("g_mkstemp(\"%s\") failed"), temporaryFile); + goto cleanup; + } + unlinkTemporaryFile = true; + + /* write image data */ + dprintf(fd, "P6\n%d %d\n255\n", xRes, yRes); + if (safewrite(fd, ppmBuffer, pixelByteCount) != pixelByteCount) { + virReportError(VIR_ERR_INTERNAL_ERROR, "%s", _("Failed to write pixel data")); + goto cleanup; + } + + if (VIR_CLOSE(fd) < 0) + virReportSystemError(errno, "%s", _("failed to close screenshot file")); + + if (virFDStreamOpenFile(stream, temporaryFile, 0, 0, O_RDONLY) < 0) + goto cleanup; + + result = g_strdup("image/x-portable-pixmap"); + + cleanup: + VIR_FORCE_CLOSE(fd); + if (unlinkTemporaryFile) + unlink(temporaryFile); + + return result; +} + + static int hypervDomainSetVcpusFlags(virDomainPtr domain, unsigned int nvcpus, @@ -3474,6 +3680,7 @@ static virHypervisorDriver hypervHypervisorDriver = { .domainSetMemoryFlags = hypervDomainSetMemoryFlags, /* 3.6.0 */ .domainGetInfo = hypervDomainGetInfo, /* 0.9.5 */ .domainGetState = hypervDomainGetState, /* 0.9.5 */ + .domainScreenshot = hypervDomainScreenshot, /* 7.1.0 */ .domainSetVcpus = hypervDomainSetVcpus, /* 6.10.0 */ .domainSetVcpusFlags = hypervDomainSetVcpusFlags, /* 6.10.0 */ .domainGetVcpusFlags = hypervDomainGetVcpusFlags, /* 6.10.0 */ diff --git a/src/hyperv/hyperv_wmi_classes.h b/src/hyperv/hyperv_wmi_classes.h index 86a7124799..faf98077eb 100644 --- a/src/hyperv/hyperv_wmi_classes.h +++ b/src/hyperv/hyperv_wmi_classes.h @@ -133,6 +133,27 @@ enum _Msvm_EthernetPortAllocationSettingData_EnabledState { +/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + * CIM_EnabledLogicalElement + */ + +/* https://docs.microsoft.com/en-us/windows/win32/hyperv_v2/cim-enabledlogicalelement#Unknown */ +enum _CIM_EnabledLogicalElement_EnabledState { + CIM_ENABLEDLOGICALELEMENT_ENABLEDSTATE_UNKNOWN = 0, + CIM_ENABLEDLOGICALELEMENT_ENABLEDSTATE_OTHER = 1, + CIM_ENABLEDLOGICALELEMENT_ENABLEDSTATE_ENABLED = 2, + CIM_ENABLEDLOGICALELEMENT_ENABLEDSTATE_DISABLED = 3, + CIM_ENABLEDLOGICALELEMENT_ENABLEDSTATE_SHUTTING_DOWN = 4, + CIM_ENABLEDLOGICALELEMENT_ENABLEDSTATE_NOT_APPLICABLE = 5, + CIM_ENABLEDLOGICALELEMENT_ENABLEDSTATE_ENABLED_BUT_OFFLINE = 6, + CIM_ENABLEDLOGICALELEMENT_ENABLEDSTATE_IN_TEST = 7, + CIM_ENABLEDLOGICALELEMENT_ENABLEDSTATE_DEFERRED = 8, + CIM_ENABLEDLOGICALELEMENT_ENABLEDSTATE_QUIESCE = 9, + CIM_ENABLEDLOGICALELEMENT_ENABLEDSTATE_STARTING = 10, +}; + + + /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * WMI */ diff --git a/src/hyperv/hyperv_wmi_generator.input b/src/hyperv/hyperv_wmi_generator.input index f9d486bd4c..0b342cbfa6 100644 --- a/src/hyperv/hyperv_wmi_generator.input +++ b/src/hyperv/hyperv_wmi_generator.input @@ -968,3 +968,166 @@ class Msvm_VirtualEthernetSwitchManagementService string StartMode boolean Started end + + +class Msvm_SyntheticDisplayController + string InstanceID + string Caption + string Description + string ElementName + datetime InstallDate + string Name + uint16 OperationalStatus[] + string StatusDescriptions[] + string Status + uint16 HealthState + uint16 CommunicationStatus + uint16 DetailedStatus + uint16 OperatingStatus + uint16 PrimaryStatus + string EnabledState + string OtherEnabledState + uint16 RequestedState + uint16 EnabledDefault + datetime TimeOfLastStateChange + uint16 AvailableRequestedStates[] + uint16 TransitioningToState + string SystemCreationClassName + string SystemName + string CreationClassName + string DeviceID + boolean PowerManagementSupported + uint16 PowerManagementCapabilities[] + uint16 Availability + uint16 StatusInfo + uint32 LastErrorCode + string ErrorDescription + boolean ErrorCleared + uint64 PowerOnHours + uint64 TotalPowerOnHours + string OtherIdentifyingInfo[] + string IdentifyingDescriptions[] + uint16 AdditionalAvailability[] + uint64 MaxQuiesceTime + datetime TimeOfLastReset + uint16 ProtocolSupported + uint32 MaxNumberControlled + string ProtocolDescription + string VideoProcessor + uint16 VideoMemoryType + string OtherVideoMemoryType + uint32 NumberOfVideoPages + uint32 MaxMemorySupported + uint16 AcceleratorCapabilities[] + string CapabilityDescriptions[] + string OtherVideoArchitecture + uint16 VideoArchitecture +end + + +class Msvm_S3DisplayController + string InstanceID + string Caption + string Description + string ElementName + datetime InstallDate + string Name + uint16 OperationalStatus[] + string StatusDescriptions[] + string Status + uint16 HealthState + uint16 CommunicationStatus + uint16 DetailedStatus + uint16 OperatingStatus + uint16 PrimaryStatus + string EnabledState + string OtherEnabledState + uint16 RequestedState + uint16 EnabledDefault + datetime TimeOfLastStateChange + uint16 AvailableRequestedStates[] + uint16 TransitioningToState + string SystemCreationClassName + string SystemName + string CreationClassName + string DeviceID + boolean PowerManagementSupported + uint16 PowerManagementCapabilities[] + uint16 Availability + uint16 StatusInfo + uint32 LastErrorCode + string ErrorDescription + boolean ErrorCleared + string OtherIdentifyingInfo[] + uint64 PowerOnHours + uint64 TotalPowerOnHours + string IdentifyingDescriptions[] + uint16 AdditionalAvailability[] + uint64 MaxQuiesceTime + datetime TimeOfLastReset + uint16 ProtocolSupported + uint32 MaxNumberControlled + string ProtocolDescription + string VideoProcessor + uint16 VideoMemoryType + string OtherVideoMemoryType + uint32 NumberOfVideoPages + uint32 MaxMemorySupported + uint16 AcceleratorCapabilities[] + string CapabilityDescriptions[] + string OtherVideoArchitecture + uint16 VideoArchitecture +end + + +class Msvm_VideoHead + string InstanceID + string Caption + string Description + string ElementName + datetime InstallDate + string Name + uint16 OperationalStatus[] + string StatusDescriptions[] + string Status + uint16 HealthState + uint16 CommunicationStatus + uint16 DetailedStatus + uint16 OperatingStatus + uint16 PrimaryStatus + string EnabledState + string OtherEnabledState + uint16 RequestedState + uint16 EnabledDefault + datetime TimeOfLastStateChange + uint16 AvailableRequestedStates[] + uint16 TransitioningToState + string SystemCreationClassName + string SystemName + string CreationClassName + string DeviceID + boolean PowerManagementSupported + uint16 PowerManagementCapabilities[] + uint16 Availability + uint16 StatusInfo + uint32 LastErrorCode + string ErrorDescription + boolean ErrorCleared + string OtherIdentifyingInfo[] + uint64 PowerOnHours + uint64 TotalPowerOnHours + string IdentifyingDescriptions[] + uint16 AdditionalAvailability[] + uint64 MaxQuiesceTime + uint32 CurrentBitsPerPixel + uint32 CurrentHorizontalResolution + uint32 CurrentVerticalResolution + uint32 MaxRefreshRate + uint32 MinRefreshRate + uint32 CurrentRefreshRate + uint16 CurrentScanMode + string OtherCurrentScanMode + uint32 CurrentNumberOfRows + uint32 CurrentNumberOfColumns + uint64 CurrentNumberOfColors +end -- 2.30.0