mirror of
https://gitlab.com/libvirt/libvirt.git
synced 2025-01-05 20:45:18 +00:00
6d9542e340
The qemuFirmwareFetchConfigs() function is supposed to fetch all firmware descriptions from paths defined by firmware.json specification. This includes user's $HOME directory. However, it was agreed that if libvirtd is running as privileged user then his $HOME is ignored (thus $HOME is included in the search only for regular users). Well, I got the condition wrong - it should have been reversed. Signed-off-by: Michal Privoznik <mprivozn@redhat.com> Reviewed-by: Andrea Bolognani <abologna@redhat.com>
1378 lines
36 KiB
C
1378 lines
36 KiB
C
/*
|
|
* qemu_firmware.c: QEMU firmware
|
|
*
|
|
* Copyright (C) 2019 Red Hat, Inc.
|
|
*
|
|
* This library is free software; you can redistribute it and/or
|
|
* modify it under the terms of the GNU Lesser General Public
|
|
* License as published by the Free Software Foundation; either
|
|
* version 2.1 of the License, or (at your option) any later version.
|
|
*
|
|
* This library is distributed in the hope that it will be useful,
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
|
* Lesser General Public License for more details.
|
|
*
|
|
* You should have received a copy of the GNU Lesser General Public
|
|
* License along with this library. If not, see
|
|
* <http://www.gnu.org/licenses/>.
|
|
*/
|
|
|
|
#include <config.h>
|
|
|
|
#include <fnmatch.h>
|
|
|
|
#include "qemu_firmware.h"
|
|
#include "configmake.h"
|
|
#include "qemu_capabilities.h"
|
|
#include "qemu_domain.h"
|
|
#include "qemu_process.h"
|
|
#include "virarch.h"
|
|
#include "virfile.h"
|
|
#include "virhash.h"
|
|
#include "virjson.h"
|
|
#include "virlog.h"
|
|
#include "virstring.h"
|
|
|
|
#define VIR_FROM_THIS VIR_FROM_QEMU
|
|
|
|
VIR_LOG_INIT("qemu.qemu_firmware");
|
|
|
|
|
|
typedef enum {
|
|
QEMU_FIRMWARE_OS_INTERFACE_NONE = 0,
|
|
QEMU_FIRMWARE_OS_INTERFACE_BIOS,
|
|
QEMU_FIRMWARE_OS_INTERFACE_OPENFIRMWARE,
|
|
QEMU_FIRMWARE_OS_INTERFACE_UBOOT,
|
|
QEMU_FIRMWARE_OS_INTERFACE_UEFI,
|
|
|
|
QEMU_FIRMWARE_OS_INTERFACE_LAST
|
|
} qemuFirmwareOSInterface;
|
|
|
|
VIR_ENUM_DECL(qemuFirmwareOSInterface);
|
|
VIR_ENUM_IMPL(qemuFirmwareOSInterface,
|
|
QEMU_FIRMWARE_OS_INTERFACE_LAST,
|
|
"",
|
|
"bios",
|
|
"openfirmware",
|
|
"uboot",
|
|
"uefi",
|
|
);
|
|
|
|
|
|
typedef struct _qemuFirmwareFlashFile qemuFirmwareFlashFile;
|
|
typedef qemuFirmwareFlashFile *qemuFirmwareFlashFilePtr;
|
|
struct _qemuFirmwareFlashFile {
|
|
char *filename;
|
|
char *format;
|
|
};
|
|
|
|
|
|
typedef struct _qemuFirmwareMappingFlash qemuFirmwareMappingFlash;
|
|
typedef qemuFirmwareMappingFlash *qemuFirmwareMappingFlashPtr;
|
|
struct _qemuFirmwareMappingFlash {
|
|
qemuFirmwareFlashFile executable;
|
|
qemuFirmwareFlashFile nvram_template;
|
|
};
|
|
|
|
|
|
typedef struct _qemuFirmwareMappingKernel qemuFirmwareMappingKernel;
|
|
typedef qemuFirmwareMappingKernel *qemuFirmwareMappingKernelPtr;
|
|
struct _qemuFirmwareMappingKernel {
|
|
char *filename;
|
|
};
|
|
|
|
|
|
typedef struct _qemuFirmwareMappingMemory qemuFirmwareMappingMemory;
|
|
typedef qemuFirmwareMappingMemory *qemuFirmwareMappingMemoryPtr;
|
|
struct _qemuFirmwareMappingMemory {
|
|
char *filename;
|
|
};
|
|
|
|
|
|
typedef enum {
|
|
QEMU_FIRMWARE_DEVICE_NONE = 0,
|
|
QEMU_FIRMWARE_DEVICE_FLASH,
|
|
QEMU_FIRMWARE_DEVICE_KERNEL,
|
|
QEMU_FIRMWARE_DEVICE_MEMORY,
|
|
|
|
QEMU_FIRMWARE_DEVICE_LAST
|
|
} qemuFirmwareDevice;
|
|
|
|
VIR_ENUM_DECL(qemuFirmwareDevice);
|
|
VIR_ENUM_IMPL(qemuFirmwareDevice,
|
|
QEMU_FIRMWARE_DEVICE_LAST,
|
|
"",
|
|
"flash",
|
|
"kernel",
|
|
"memory",
|
|
);
|
|
|
|
|
|
typedef struct _qemuFirmwareMapping qemuFirmwareMapping;
|
|
typedef qemuFirmwareMapping *qemuFirmwareMappingPtr;
|
|
struct _qemuFirmwareMapping {
|
|
qemuFirmwareDevice device;
|
|
|
|
union {
|
|
qemuFirmwareMappingFlash flash;
|
|
qemuFirmwareMappingKernel kernel;
|
|
qemuFirmwareMappingMemory memory;
|
|
} data;
|
|
};
|
|
|
|
|
|
typedef struct _qemuFirmwareTarget qemuFirmwareTarget;
|
|
typedef qemuFirmwareTarget *qemuFirmwareTargetPtr;
|
|
struct _qemuFirmwareTarget {
|
|
virArch architecture;
|
|
size_t nmachines;
|
|
char **machines;
|
|
};
|
|
|
|
|
|
typedef enum {
|
|
QEMU_FIRMWARE_FEATURE_NONE = 0,
|
|
QEMU_FIRMWARE_FEATURE_ACPI_S3,
|
|
QEMU_FIRMWARE_FEATURE_ACPI_S4,
|
|
QEMU_FIRMWARE_FEATURE_AMD_SEV,
|
|
QEMU_FIRMWARE_FEATURE_ENROLLED_KEYS,
|
|
QEMU_FIRMWARE_FEATURE_REQUIRES_SMM,
|
|
QEMU_FIRMWARE_FEATURE_SECURE_BOOT,
|
|
QEMU_FIRMWARE_FEATURE_VERBOSE_DYNAMIC,
|
|
QEMU_FIRMWARE_FEATURE_VERBOSE_STATIC,
|
|
|
|
QEMU_FIRMWARE_FEATURE_LAST
|
|
} qemuFirmwareFeature;
|
|
|
|
VIR_ENUM_DECL(qemuFirmwareFeature);
|
|
VIR_ENUM_IMPL(qemuFirmwareFeature,
|
|
QEMU_FIRMWARE_FEATURE_LAST,
|
|
"",
|
|
"acpi-s3",
|
|
"acpi-s4",
|
|
"amd-sev",
|
|
"enrolled-keys",
|
|
"requires-smm",
|
|
"secure-boot",
|
|
"verbose-dynamic",
|
|
"verbose-static"
|
|
);
|
|
|
|
|
|
struct _qemuFirmware {
|
|
/* Description intentionally not parsed. */
|
|
|
|
size_t ninterfaces;
|
|
qemuFirmwareOSInterface *interfaces;
|
|
|
|
qemuFirmwareMapping mapping;
|
|
|
|
size_t ntargets;
|
|
qemuFirmwareTargetPtr *targets;
|
|
|
|
size_t nfeatures;
|
|
qemuFirmwareFeature *features;
|
|
|
|
/* Tags intentionally not parsed. */
|
|
};
|
|
|
|
|
|
static void
|
|
qemuFirmwareOSInterfaceFree(qemuFirmwareOSInterface *interfaces)
|
|
{
|
|
VIR_FREE(interfaces);
|
|
}
|
|
|
|
|
|
VIR_DEFINE_AUTOPTR_FUNC(qemuFirmwareOSInterface, qemuFirmwareOSInterfaceFree);
|
|
|
|
|
|
static void
|
|
qemuFirmwareFlashFileFree(qemuFirmwareFlashFile flash)
|
|
{
|
|
VIR_FREE(flash.filename);
|
|
VIR_FREE(flash.format);
|
|
}
|
|
|
|
|
|
static void
|
|
qemuFirmwareMappingFlashFree(qemuFirmwareMappingFlash flash)
|
|
{
|
|
qemuFirmwareFlashFileFree(flash.executable);
|
|
qemuFirmwareFlashFileFree(flash.nvram_template);
|
|
}
|
|
|
|
|
|
static void
|
|
qemuFirmwareMappingKernelFree(qemuFirmwareMappingKernel kernel)
|
|
{
|
|
VIR_FREE(kernel.filename);
|
|
}
|
|
|
|
|
|
static void
|
|
qemuFirmwareMappingMemoryFree(qemuFirmwareMappingMemory memory)
|
|
{
|
|
VIR_FREE(memory.filename);
|
|
}
|
|
|
|
|
|
static void
|
|
qemuFirmwareMappingFree(qemuFirmwareMapping mapping)
|
|
{
|
|
switch (mapping.device) {
|
|
case QEMU_FIRMWARE_DEVICE_FLASH:
|
|
qemuFirmwareMappingFlashFree(mapping.data.flash);
|
|
break;
|
|
case QEMU_FIRMWARE_DEVICE_KERNEL:
|
|
qemuFirmwareMappingKernelFree(mapping.data.kernel);
|
|
break;
|
|
case QEMU_FIRMWARE_DEVICE_MEMORY:
|
|
qemuFirmwareMappingMemoryFree(mapping.data.memory);
|
|
break;
|
|
case QEMU_FIRMWARE_DEVICE_NONE:
|
|
case QEMU_FIRMWARE_DEVICE_LAST:
|
|
break;
|
|
}
|
|
}
|
|
|
|
|
|
static void
|
|
qemuFirmwareTargetFree(qemuFirmwareTargetPtr target)
|
|
{
|
|
if (!target)
|
|
return;
|
|
|
|
virStringListFreeCount(target->machines, target->nmachines);
|
|
|
|
VIR_FREE(target);
|
|
}
|
|
|
|
|
|
VIR_DEFINE_AUTOPTR_FUNC(qemuFirmwareTarget, qemuFirmwareTargetFree);
|
|
|
|
|
|
static void
|
|
qemuFirmwareFeatureFree(qemuFirmwareFeature *features)
|
|
{
|
|
VIR_FREE(features);
|
|
}
|
|
|
|
|
|
VIR_DEFINE_AUTOPTR_FUNC(qemuFirmwareFeature, qemuFirmwareFeatureFree);
|
|
|
|
|
|
void
|
|
qemuFirmwareFree(qemuFirmwarePtr fw)
|
|
{
|
|
size_t i;
|
|
|
|
if (!fw)
|
|
return;
|
|
|
|
qemuFirmwareOSInterfaceFree(fw->interfaces);
|
|
qemuFirmwareMappingFree(fw->mapping);
|
|
for (i = 0; i < fw->ntargets; i++)
|
|
qemuFirmwareTargetFree(fw->targets[i]);
|
|
VIR_FREE(fw->targets);
|
|
qemuFirmwareFeatureFree(fw->features);
|
|
|
|
VIR_FREE(fw);
|
|
}
|
|
|
|
|
|
static int
|
|
qemuFirmwareInterfaceParse(const char *path,
|
|
virJSONValuePtr doc,
|
|
qemuFirmwarePtr fw)
|
|
{
|
|
virJSONValuePtr interfacesJSON;
|
|
VIR_AUTOPTR(qemuFirmwareOSInterface) interfaces = NULL;
|
|
VIR_AUTOCLEAN(virBuffer) buf = VIR_BUFFER_INITIALIZER;
|
|
size_t ninterfaces;
|
|
size_t i;
|
|
|
|
if (!(interfacesJSON = virJSONValueObjectGetArray(doc, "interface-types"))) {
|
|
virReportError(VIR_ERR_INTERNAL_ERROR,
|
|
_("failed to get interface-types from '%s'"),
|
|
path);
|
|
return -1;
|
|
}
|
|
|
|
ninterfaces = virJSONValueArraySize(interfacesJSON);
|
|
|
|
if (VIR_ALLOC_N(interfaces, ninterfaces) < 0)
|
|
return -1;
|
|
|
|
for (i = 0; i < ninterfaces; i++) {
|
|
virJSONValuePtr item = virJSONValueArrayGet(interfacesJSON, i);
|
|
const char *tmpStr = virJSONValueGetString(item);
|
|
int tmp;
|
|
|
|
if ((tmp = qemuFirmwareOSInterfaceTypeFromString(tmpStr)) <= 0) {
|
|
virReportError(VIR_ERR_INTERNAL_ERROR,
|
|
_("unknown interface type: '%s'"),
|
|
tmpStr);
|
|
return -1;
|
|
}
|
|
|
|
virBufferAsprintf(&buf, " %s", tmpStr);
|
|
interfaces[i] = tmp;
|
|
}
|
|
|
|
VIR_DEBUG("firmware description path '%s' supported interfaces: %s",
|
|
path, NULLSTR_MINUS(virBufferCurrentContent(&buf)));
|
|
|
|
VIR_STEAL_PTR(fw->interfaces, interfaces);
|
|
fw->ninterfaces = ninterfaces;
|
|
return 0;
|
|
}
|
|
|
|
|
|
static int
|
|
qemuFirmwareFlashFileParse(const char *path,
|
|
virJSONValuePtr doc,
|
|
qemuFirmwareFlashFilePtr flash)
|
|
{
|
|
const char *filename;
|
|
const char *format;
|
|
|
|
if (!(filename = virJSONValueObjectGetString(doc, "filename"))) {
|
|
virReportError(VIR_ERR_INTERNAL_ERROR,
|
|
_("missing 'filename' in '%s'"),
|
|
path);
|
|
return -1;
|
|
}
|
|
|
|
if (VIR_STRDUP(flash->filename, filename) < 0)
|
|
return -1;
|
|
|
|
if (!(format = virJSONValueObjectGetString(doc, "format"))) {
|
|
virReportError(VIR_ERR_INTERNAL_ERROR,
|
|
_("missing 'format' in '%s'"),
|
|
path);
|
|
return -1;
|
|
}
|
|
|
|
if (VIR_STRDUP(flash->format, format) < 0)
|
|
return -1;
|
|
|
|
return 0;
|
|
}
|
|
|
|
|
|
static int
|
|
qemuFirmwareMappingFlashParse(const char *path,
|
|
virJSONValuePtr doc,
|
|
qemuFirmwareMappingFlashPtr flash)
|
|
{
|
|
virJSONValuePtr executable;
|
|
virJSONValuePtr nvram_template;
|
|
|
|
if (!(executable = virJSONValueObjectGet(doc, "executable"))) {
|
|
virReportError(VIR_ERR_INTERNAL_ERROR,
|
|
_("missing 'executable' in '%s'"),
|
|
path);
|
|
return -1;
|
|
}
|
|
|
|
if (qemuFirmwareFlashFileParse(path, executable, &flash->executable) < 0)
|
|
return -1;
|
|
|
|
if (!(nvram_template = virJSONValueObjectGet(doc, "nvram-template"))) {
|
|
virReportError(VIR_ERR_INTERNAL_ERROR,
|
|
_("missing 'nvram-template' in '%s'"),
|
|
path);
|
|
return -1;
|
|
}
|
|
|
|
if (qemuFirmwareFlashFileParse(path, nvram_template, &flash->nvram_template) < 0)
|
|
return -1;
|
|
|
|
return 0;
|
|
}
|
|
|
|
|
|
static int
|
|
qemuFirmwareMappingKernelParse(const char *path,
|
|
virJSONValuePtr doc,
|
|
qemuFirmwareMappingKernelPtr kernel)
|
|
{
|
|
const char *filename;
|
|
|
|
if (!(filename = virJSONValueObjectGetString(doc, "filename"))) {
|
|
virReportError(VIR_ERR_INTERNAL_ERROR,
|
|
_("missing 'filename' in '%s'"),
|
|
path);
|
|
}
|
|
|
|
if (VIR_STRDUP(kernel->filename, filename) < 0)
|
|
return -1;
|
|
|
|
return 0;
|
|
}
|
|
|
|
|
|
static int
|
|
qemuFirmwareMappingMemoryParse(const char *path,
|
|
virJSONValuePtr doc,
|
|
qemuFirmwareMappingMemoryPtr memory)
|
|
{
|
|
const char *filename;
|
|
|
|
if (!(filename = virJSONValueObjectGetString(doc, "filename"))) {
|
|
virReportError(VIR_ERR_INTERNAL_ERROR,
|
|
_("missing 'filename' in '%s'"),
|
|
path);
|
|
}
|
|
|
|
if (VIR_STRDUP(memory->filename, filename) < 0)
|
|
return -1;
|
|
|
|
return 0;
|
|
}
|
|
|
|
|
|
static int
|
|
qemuFirmwareMappingParse(const char *path,
|
|
virJSONValuePtr doc,
|
|
qemuFirmwarePtr fw)
|
|
{
|
|
virJSONValuePtr mapping = virJSONValueObjectGet(doc, "mapping");
|
|
const char *deviceStr;
|
|
int tmp;
|
|
|
|
if (!(deviceStr = virJSONValueObjectGetString(mapping, "device"))) {
|
|
virReportError(VIR_ERR_INTERNAL_ERROR,
|
|
_("missing device type in '%s'"),
|
|
path);
|
|
return -1;
|
|
}
|
|
|
|
if ((tmp = qemuFirmwareDeviceTypeFromString(deviceStr)) <= 0) {
|
|
virReportError(VIR_ERR_INTERNAL_ERROR,
|
|
_("unknown device type in '%s'"),
|
|
path);
|
|
return -1;
|
|
}
|
|
|
|
fw->mapping.device = tmp;
|
|
|
|
switch (fw->mapping.device) {
|
|
case QEMU_FIRMWARE_DEVICE_FLASH:
|
|
if (qemuFirmwareMappingFlashParse(path, mapping, &fw->mapping.data.flash) < 0)
|
|
return -1;
|
|
break;
|
|
case QEMU_FIRMWARE_DEVICE_KERNEL:
|
|
if (qemuFirmwareMappingKernelParse(path, mapping, &fw->mapping.data.kernel) < 0)
|
|
return -1;
|
|
break;
|
|
case QEMU_FIRMWARE_DEVICE_MEMORY:
|
|
if (qemuFirmwareMappingMemoryParse(path, mapping, &fw->mapping.data.memory) < 0)
|
|
return -1;
|
|
break;
|
|
|
|
case QEMU_FIRMWARE_DEVICE_NONE:
|
|
case QEMU_FIRMWARE_DEVICE_LAST:
|
|
break;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
|
|
static int
|
|
qemuFirmwareTargetParse(const char *path,
|
|
virJSONValuePtr doc,
|
|
qemuFirmwarePtr fw)
|
|
{
|
|
virJSONValuePtr targetsJSON;
|
|
qemuFirmwareTargetPtr *targets = NULL;
|
|
size_t ntargets;
|
|
size_t i;
|
|
int ret = -1;
|
|
|
|
if (!(targetsJSON = virJSONValueObjectGetArray(doc, "targets"))) {
|
|
virReportError(VIR_ERR_INTERNAL_ERROR,
|
|
_("failed to get targets from '%s'"),
|
|
path);
|
|
return -1;
|
|
}
|
|
|
|
ntargets = virJSONValueArraySize(targetsJSON);
|
|
|
|
if (VIR_ALLOC_N(targets, ntargets) < 0)
|
|
return -1;
|
|
|
|
for (i = 0; i < ntargets; i++) {
|
|
virJSONValuePtr item = virJSONValueArrayGet(targetsJSON, i);
|
|
virJSONValuePtr machines;
|
|
VIR_AUTOPTR(qemuFirmwareTarget) t = NULL;
|
|
const char *architectureStr = NULL;
|
|
size_t nmachines;
|
|
size_t j;
|
|
|
|
if (VIR_ALLOC(t) < 0)
|
|
goto cleanup;
|
|
|
|
if (!(architectureStr = virJSONValueObjectGetString(item, "architecture"))) {
|
|
virReportError(VIR_ERR_INTERNAL_ERROR,
|
|
_("missing 'architecture' in '%s'"),
|
|
path);
|
|
goto cleanup;
|
|
}
|
|
|
|
if ((t->architecture = virQEMUCapsArchFromString(architectureStr)) == VIR_ARCH_NONE) {
|
|
virReportError(VIR_ERR_INTERNAL_ERROR,
|
|
_("unknown architecture '%s'"),
|
|
architectureStr);
|
|
goto cleanup;
|
|
}
|
|
|
|
if (!(machines = virJSONValueObjectGetArray(item, "machines"))) {
|
|
virReportError(VIR_ERR_INTERNAL_ERROR,
|
|
_("missing 'machines' in '%s'"),
|
|
path);
|
|
goto cleanup;
|
|
}
|
|
|
|
nmachines = virJSONValueArraySize(machines);
|
|
|
|
if (VIR_ALLOC_N(t->machines, nmachines) < 0)
|
|
goto cleanup;
|
|
|
|
for (j = 0; j < nmachines; j++) {
|
|
virJSONValuePtr machine = virJSONValueArrayGet(machines, j);
|
|
VIR_AUTOFREE(char *) machineStr = NULL;
|
|
|
|
if (VIR_STRDUP(machineStr, virJSONValueGetString(machine)) < 0)
|
|
goto cleanup;
|
|
|
|
VIR_APPEND_ELEMENT_INPLACE(t->machines, t->nmachines, machineStr);
|
|
}
|
|
|
|
VIR_STEAL_PTR(targets[i], t);
|
|
}
|
|
|
|
VIR_STEAL_PTR(fw->targets, targets);
|
|
fw->ntargets = ntargets;
|
|
ntargets = 0;
|
|
ret = 0;
|
|
|
|
cleanup:
|
|
for (i = 0; i < ntargets; i++)
|
|
qemuFirmwareTargetFree(targets[i]);
|
|
VIR_FREE(targets);
|
|
return ret;
|
|
}
|
|
|
|
|
|
static int
|
|
qemuFirmwareFeatureParse(const char *path,
|
|
virJSONValuePtr doc,
|
|
qemuFirmwarePtr fw)
|
|
{
|
|
virJSONValuePtr featuresJSON;
|
|
VIR_AUTOPTR(qemuFirmwareFeature) features = NULL;
|
|
size_t nfeatures;
|
|
size_t i;
|
|
|
|
if (!(featuresJSON = virJSONValueObjectGetArray(doc, "features"))) {
|
|
virReportError(VIR_ERR_INTERNAL_ERROR,
|
|
_("failed to get features from '%s'"),
|
|
path);
|
|
return -1;
|
|
}
|
|
|
|
nfeatures = virJSONValueArraySize(featuresJSON);
|
|
|
|
if (VIR_ALLOC_N(features, nfeatures) < 0)
|
|
return -1;
|
|
|
|
fw->nfeatures = nfeatures;
|
|
|
|
for (i = 0; i < nfeatures; i++) {
|
|
virJSONValuePtr item = virJSONValueArrayGet(featuresJSON, i);
|
|
const char *tmpStr = virJSONValueGetString(item);
|
|
int tmp;
|
|
|
|
if ((tmp = qemuFirmwareFeatureTypeFromString(tmpStr)) <= 0) {
|
|
virReportError(VIR_ERR_INTERNAL_ERROR,
|
|
_("unknown feature %s"),
|
|
tmpStr);
|
|
return -1;
|
|
}
|
|
|
|
features[i] = tmp;
|
|
}
|
|
|
|
VIR_STEAL_PTR(fw->features, features);
|
|
fw->nfeatures = nfeatures;
|
|
return 0;
|
|
}
|
|
|
|
|
|
/* 1MiB should be enough for everybody (TM) */
|
|
#define DOCUMENT_SIZE (1024 * 1024)
|
|
|
|
qemuFirmwarePtr
|
|
qemuFirmwareParse(const char *path)
|
|
{
|
|
VIR_AUTOFREE(char *) cont = NULL;
|
|
VIR_AUTOPTR(virJSONValue) doc = NULL;
|
|
VIR_AUTOPTR(qemuFirmware) fw = NULL;
|
|
qemuFirmwarePtr ret = NULL;
|
|
|
|
if (virFileReadAll(path, DOCUMENT_SIZE, &cont) < 0)
|
|
return NULL;
|
|
|
|
if (!(doc = virJSONValueFromString(cont))) {
|
|
virReportError(VIR_ERR_INTERNAL_ERROR,
|
|
_("unable to parse json file '%s'"),
|
|
path);
|
|
return NULL;
|
|
}
|
|
|
|
if (VIR_ALLOC(fw) < 0)
|
|
return NULL;
|
|
|
|
if (qemuFirmwareInterfaceParse(path, doc, fw) < 0)
|
|
return NULL;
|
|
|
|
if (qemuFirmwareMappingParse(path, doc, fw) < 0)
|
|
return NULL;
|
|
|
|
if (qemuFirmwareTargetParse(path, doc, fw) < 0)
|
|
return NULL;
|
|
|
|
if (qemuFirmwareFeatureParse(path, doc, fw) < 0)
|
|
return NULL;
|
|
|
|
VIR_STEAL_PTR(ret, fw);
|
|
return ret;
|
|
}
|
|
|
|
|
|
static int
|
|
qemuFirmwareInterfaceFormat(virJSONValuePtr doc,
|
|
qemuFirmwarePtr fw)
|
|
{
|
|
VIR_AUTOPTR(virJSONValue) interfacesJSON = NULL;
|
|
size_t i;
|
|
|
|
if (!(interfacesJSON = virJSONValueNewArray()))
|
|
return -1;
|
|
|
|
for (i = 0; i < fw->ninterfaces; i++) {
|
|
if (virJSONValueArrayAppendString(interfacesJSON,
|
|
qemuFirmwareOSInterfaceTypeToString(fw->interfaces[i])) < 0)
|
|
return -1;
|
|
}
|
|
|
|
if (virJSONValueObjectAppend(doc,
|
|
"interface-types",
|
|
interfacesJSON) < 0)
|
|
return -1;
|
|
|
|
interfacesJSON = NULL;
|
|
return 0;
|
|
}
|
|
|
|
|
|
static virJSONValuePtr
|
|
qemuFirmwareFlashFileFormat(qemuFirmwareFlashFile flash)
|
|
{
|
|
VIR_AUTOPTR(virJSONValue) json = NULL;
|
|
virJSONValuePtr ret;
|
|
|
|
if (!(json = virJSONValueNewObject()))
|
|
return NULL;
|
|
|
|
if (virJSONValueObjectAppendString(json,
|
|
"filename",
|
|
flash.filename) < 0)
|
|
return NULL;
|
|
|
|
if (virJSONValueObjectAppendString(json,
|
|
"format",
|
|
flash.format) < 0)
|
|
return NULL;
|
|
|
|
VIR_STEAL_PTR(ret, json);
|
|
return ret;
|
|
}
|
|
|
|
|
|
static int
|
|
qemuFirmwareMappingFlashFormat(virJSONValuePtr mapping,
|
|
qemuFirmwareMappingFlashPtr flash)
|
|
{
|
|
VIR_AUTOPTR(virJSONValue) executable = NULL;
|
|
VIR_AUTOPTR(virJSONValue) nvram_template = NULL;
|
|
|
|
if (!(executable = qemuFirmwareFlashFileFormat(flash->executable)))
|
|
return -1;
|
|
|
|
if (!(nvram_template = qemuFirmwareFlashFileFormat(flash->nvram_template)))
|
|
return -1;
|
|
|
|
if (virJSONValueObjectAppend(mapping,
|
|
"executable",
|
|
executable) < 0)
|
|
return -1;
|
|
|
|
executable = NULL;
|
|
|
|
if (virJSONValueObjectAppend(mapping,
|
|
"nvram-template",
|
|
nvram_template) < 0)
|
|
return -1;
|
|
|
|
nvram_template = NULL;
|
|
|
|
return 0;
|
|
}
|
|
|
|
|
|
static int
|
|
qemuFirmwareMappingKernelFormat(virJSONValuePtr mapping,
|
|
qemuFirmwareMappingKernelPtr kernel)
|
|
{
|
|
if (virJSONValueObjectAppendString(mapping,
|
|
"filename",
|
|
kernel->filename) < 0)
|
|
return -1;
|
|
|
|
return 0;
|
|
}
|
|
|
|
|
|
static int
|
|
qemuFirmwareMappingMemoryFormat(virJSONValuePtr mapping,
|
|
qemuFirmwareMappingMemoryPtr memory)
|
|
{
|
|
if (virJSONValueObjectAppendString(mapping,
|
|
"filename",
|
|
memory->filename) < 0)
|
|
return -1;
|
|
|
|
return 0;
|
|
}
|
|
|
|
|
|
static int
|
|
qemuFirmwareMappingFormat(virJSONValuePtr doc,
|
|
qemuFirmwarePtr fw)
|
|
{
|
|
VIR_AUTOPTR(virJSONValue) mapping = NULL;
|
|
|
|
if (!(mapping = virJSONValueNewObject()))
|
|
return -1;
|
|
|
|
if (virJSONValueObjectAppendString(mapping,
|
|
"device",
|
|
qemuFirmwareDeviceTypeToString(fw->mapping.device)) < 0)
|
|
return -1;
|
|
|
|
switch (fw->mapping.device) {
|
|
case QEMU_FIRMWARE_DEVICE_FLASH:
|
|
if (qemuFirmwareMappingFlashFormat(mapping, &fw->mapping.data.flash) < 0)
|
|
return -1;
|
|
break;
|
|
case QEMU_FIRMWARE_DEVICE_KERNEL:
|
|
if (qemuFirmwareMappingKernelFormat(mapping, &fw->mapping.data.kernel) < 0)
|
|
return -1;
|
|
break;
|
|
case QEMU_FIRMWARE_DEVICE_MEMORY:
|
|
if (qemuFirmwareMappingMemoryFormat(mapping, &fw->mapping.data.memory) < 0)
|
|
return -1;
|
|
break;
|
|
|
|
case QEMU_FIRMWARE_DEVICE_NONE:
|
|
case QEMU_FIRMWARE_DEVICE_LAST:
|
|
break;
|
|
}
|
|
|
|
if (virJSONValueObjectAppend(doc, "mapping", mapping) < 0)
|
|
return -1;
|
|
|
|
mapping = NULL;
|
|
return 0;
|
|
}
|
|
|
|
|
|
static int
|
|
qemuFirmwareTargetFormat(virJSONValuePtr doc,
|
|
qemuFirmwarePtr fw)
|
|
{
|
|
VIR_AUTOPTR(virJSONValue) targetsJSON = NULL;
|
|
size_t i;
|
|
|
|
if (!(targetsJSON = virJSONValueNewArray()))
|
|
return -1;
|
|
|
|
for (i = 0; i < fw->ntargets; i++) {
|
|
qemuFirmwareTargetPtr t = fw->targets[i];
|
|
VIR_AUTOPTR(virJSONValue) target = NULL;
|
|
VIR_AUTOPTR(virJSONValue) machines = NULL;
|
|
size_t j;
|
|
|
|
if (!(target = virJSONValueNewObject()))
|
|
return -1;
|
|
|
|
if (virJSONValueObjectAppendString(target,
|
|
"architecture",
|
|
virQEMUCapsArchToString(t->architecture)) < 0)
|
|
return -1;
|
|
|
|
if (!(machines = virJSONValueNewArray()))
|
|
return -1;
|
|
|
|
for (j = 0; j < t->nmachines; j++) {
|
|
if (virJSONValueArrayAppendString(machines,
|
|
t->machines[j]) < 0)
|
|
return -1;
|
|
}
|
|
|
|
if (virJSONValueObjectAppend(target, "machines", machines) < 0)
|
|
return -1;
|
|
|
|
machines = NULL;
|
|
|
|
if (virJSONValueArrayAppend(targetsJSON, target) < 0)
|
|
return -1;
|
|
|
|
target = NULL;
|
|
}
|
|
|
|
if (virJSONValueObjectAppend(doc, "targets", targetsJSON) < 0)
|
|
return -1;
|
|
|
|
targetsJSON = NULL;
|
|
return 0;
|
|
}
|
|
|
|
|
|
static int
|
|
qemuFirmwareFeatureFormat(virJSONValuePtr doc,
|
|
qemuFirmwarePtr fw)
|
|
{
|
|
VIR_AUTOPTR(virJSONValue) featuresJSON = NULL;
|
|
size_t i;
|
|
|
|
if (!(featuresJSON = virJSONValueNewArray()))
|
|
return -1;
|
|
|
|
for (i = 0; i < fw->nfeatures; i++) {
|
|
if (virJSONValueArrayAppendString(featuresJSON,
|
|
qemuFirmwareFeatureTypeToString(fw->features[i])) < 0)
|
|
return -1;
|
|
}
|
|
|
|
if (virJSONValueObjectAppend(doc,
|
|
"features",
|
|
featuresJSON) < 0)
|
|
return -1;
|
|
|
|
featuresJSON = NULL;
|
|
return 0;
|
|
}
|
|
|
|
|
|
char *
|
|
qemuFirmwareFormat(qemuFirmwarePtr fw)
|
|
{
|
|
VIR_AUTOPTR(virJSONValue) doc = NULL;
|
|
|
|
if (!fw)
|
|
return NULL;
|
|
|
|
if (!(doc = virJSONValueNewObject()))
|
|
return NULL;
|
|
|
|
if (qemuFirmwareInterfaceFormat(doc, fw) < 0)
|
|
return NULL;
|
|
|
|
if (qemuFirmwareMappingFormat(doc, fw) < 0)
|
|
return NULL;
|
|
|
|
if (qemuFirmwareTargetFormat(doc, fw) < 0)
|
|
return NULL;
|
|
|
|
if (qemuFirmwareFeatureFormat(doc, fw) < 0)
|
|
return NULL;
|
|
|
|
return virJSONValueToString(doc, true);
|
|
}
|
|
|
|
|
|
static int
|
|
qemuFirmwareBuildFileList(virHashTablePtr files, const char *dir)
|
|
{
|
|
DIR *dirp;
|
|
struct dirent *ent = NULL;
|
|
int rc;
|
|
int ret = -1;
|
|
|
|
if ((rc = virDirOpenIfExists(&dirp, dir)) < 0)
|
|
return -1;
|
|
|
|
if (rc == 0)
|
|
return 0;
|
|
|
|
while ((rc = virDirRead(dirp, &ent, dir)) > 0) {
|
|
VIR_AUTOFREE(char *) filename = NULL;
|
|
VIR_AUTOFREE(char *) path = NULL;
|
|
|
|
if (ent->d_type != DT_REG && ent->d_type != DT_LNK)
|
|
continue;
|
|
|
|
if (STRPREFIX(ent->d_name, "."))
|
|
continue;
|
|
|
|
if (VIR_STRDUP(filename, ent->d_name) < 0)
|
|
goto cleanup;
|
|
|
|
if (virAsprintf(&path, "%s/%s", dir, filename) < 0)
|
|
goto cleanup;
|
|
|
|
if (virHashUpdateEntry(files, filename, path) < 0)
|
|
goto cleanup;
|
|
|
|
path = NULL;
|
|
}
|
|
|
|
if (rc < 0)
|
|
goto cleanup;
|
|
|
|
ret = 0;
|
|
cleanup:
|
|
virDirClose(&dirp);
|
|
return ret;
|
|
}
|
|
|
|
static int
|
|
qemuFirmwareFilesSorter(const virHashKeyValuePair *a,
|
|
const virHashKeyValuePair *b)
|
|
{
|
|
return strcmp(a->key, b->key);
|
|
}
|
|
|
|
#define QEMU_FIRMWARE_SYSTEM_LOCATION PREFIX "/share/qemu/firmware"
|
|
#define QEMU_FIRMWARE_ETC_LOCATION SYSCONFDIR "/qemu/firmware"
|
|
|
|
int
|
|
qemuFirmwareFetchConfigs(char ***firmwares,
|
|
bool privileged)
|
|
{
|
|
VIR_AUTOPTR(virHashTable) files = NULL;
|
|
VIR_AUTOFREE(char *) homeConfig = NULL;
|
|
VIR_AUTOFREE(char *) xdgConfig = NULL;
|
|
VIR_AUTOFREE(virHashKeyValuePairPtr) pairs = NULL;
|
|
virHashKeyValuePairPtr tmp = NULL;
|
|
|
|
*firmwares = NULL;
|
|
|
|
if (!privileged) {
|
|
/* This is a slight divergence from the specification.
|
|
* Since the system daemon runs as root, it doesn't make
|
|
* much sense to parse files in root's home directory. It
|
|
* makes sense only for session daemon which runs under
|
|
* regular user. */
|
|
if (VIR_STRDUP(xdgConfig, virGetEnvBlockSUID("XDG_CONFIG_HOME")) < 0)
|
|
return -1;
|
|
|
|
if (!xdgConfig) {
|
|
VIR_AUTOFREE(char *) home = virGetUserDirectory();
|
|
|
|
if (!home)
|
|
return -1;
|
|
|
|
if (virAsprintf(&xdgConfig, "%s/.config", home) < 0)
|
|
return -1;
|
|
}
|
|
|
|
if (virAsprintf(&homeConfig, "%s/qemu/firmware", xdgConfig) < 0)
|
|
return -1;
|
|
}
|
|
|
|
if (!(files = virHashCreate(10, virHashValueFree)))
|
|
return -1;
|
|
|
|
if (qemuFirmwareBuildFileList(files, QEMU_FIRMWARE_SYSTEM_LOCATION) < 0)
|
|
return -1;
|
|
|
|
if (qemuFirmwareBuildFileList(files, QEMU_FIRMWARE_ETC_LOCATION) < 0)
|
|
return -1;
|
|
|
|
if (homeConfig &&
|
|
qemuFirmwareBuildFileList(files, homeConfig) < 0)
|
|
return -1;
|
|
|
|
/* At this point, the @files hash table contains unique set of filenames
|
|
* where each filename (as key) has the highest priority full pathname
|
|
* associated with it. */
|
|
|
|
if (virHashSize(files) == 0)
|
|
return 0;
|
|
|
|
if (!(pairs = virHashGetItems(files, qemuFirmwareFilesSorter)))
|
|
return -1;
|
|
|
|
for (tmp = pairs; tmp->key; tmp++) {
|
|
const char *path = tmp->value;
|
|
off_t len;
|
|
|
|
if ((len = virFileLength(path, -1)) < 0) {
|
|
virReportSystemError(errno,
|
|
_("unable to get size of '%s'"),
|
|
path);
|
|
return -1;
|
|
}
|
|
|
|
VIR_DEBUG("firmware description path '%s' len=%jd",
|
|
path, (intmax_t) len);
|
|
|
|
if (len == 0) {
|
|
/* Empty files are used to mask less specific instances
|
|
* of the same file. */
|
|
continue;
|
|
}
|
|
|
|
if (virStringListAdd(firmwares, path) < 0)
|
|
return -1;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
|
|
static bool
|
|
qemuFirmwareMatchDomain(const virDomainDef *def,
|
|
const qemuFirmware *fw,
|
|
const char *path)
|
|
{
|
|
size_t i;
|
|
bool supportsS3 = false;
|
|
bool supportsS4 = false;
|
|
bool requiresSMM = false;
|
|
bool supportsSEV = false;
|
|
|
|
for (i = 0; i < fw->ninterfaces; i++) {
|
|
if ((def->os.firmware == VIR_DOMAIN_OS_DEF_FIRMWARE_BIOS &&
|
|
fw->interfaces[i] == QEMU_FIRMWARE_OS_INTERFACE_BIOS) ||
|
|
(def->os.firmware == VIR_DOMAIN_OS_DEF_FIRMWARE_EFI &&
|
|
fw->interfaces[i] == QEMU_FIRMWARE_OS_INTERFACE_UEFI))
|
|
break;
|
|
}
|
|
|
|
if (i == fw->ninterfaces) {
|
|
VIR_DEBUG("No matching interface in '%s'", path);
|
|
return false;
|
|
}
|
|
|
|
for (i = 0; i < fw->ntargets; i++) {
|
|
size_t j;
|
|
|
|
if (def->os.arch != fw->targets[i]->architecture)
|
|
continue;
|
|
|
|
for (j = 0; j < fw->targets[i]->nmachines; j++) {
|
|
if (fnmatch(fw->targets[i]->machines[j], def->os.machine, 0) == 0)
|
|
break;
|
|
}
|
|
|
|
if (j == fw->targets[i]->nmachines)
|
|
continue;
|
|
|
|
break;
|
|
}
|
|
|
|
if (i == fw->ntargets) {
|
|
VIR_DEBUG("No matching machine type in '%s'", path);
|
|
return false;
|
|
}
|
|
|
|
for (i = 0; i < fw->nfeatures; i++) {
|
|
switch (fw->features[i]) {
|
|
case QEMU_FIRMWARE_FEATURE_ACPI_S3:
|
|
supportsS3 = true;
|
|
break;
|
|
case QEMU_FIRMWARE_FEATURE_ACPI_S4:
|
|
supportsS4 = true;
|
|
break;
|
|
case QEMU_FIRMWARE_FEATURE_AMD_SEV:
|
|
supportsSEV = true;
|
|
break;
|
|
case QEMU_FIRMWARE_FEATURE_REQUIRES_SMM:
|
|
requiresSMM = true;
|
|
break;
|
|
|
|
case QEMU_FIRMWARE_FEATURE_SECURE_BOOT:
|
|
case QEMU_FIRMWARE_FEATURE_ENROLLED_KEYS:
|
|
case QEMU_FIRMWARE_FEATURE_VERBOSE_DYNAMIC:
|
|
case QEMU_FIRMWARE_FEATURE_VERBOSE_STATIC:
|
|
case QEMU_FIRMWARE_FEATURE_NONE:
|
|
case QEMU_FIRMWARE_FEATURE_LAST:
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (def->pm.s3 == VIR_TRISTATE_BOOL_YES &&
|
|
!supportsS3) {
|
|
VIR_DEBUG("Domain requires S3, firmware '%s' doesn't support it", path);
|
|
return false;
|
|
}
|
|
|
|
if (def->pm.s4 == VIR_TRISTATE_BOOL_YES &&
|
|
!supportsS4) {
|
|
VIR_DEBUG("Domain requires S4, firmware '%s' doesn't support it", path);
|
|
return false;
|
|
}
|
|
|
|
if (def->os.loader &&
|
|
def->os.loader->secure == VIR_TRISTATE_BOOL_YES &&
|
|
!requiresSMM) {
|
|
VIR_DEBUG("Domain restricts pflash programming to SMM, "
|
|
"but firmware '%s' doesn't support SMM", path);
|
|
return false;
|
|
}
|
|
|
|
if (def->sev &&
|
|
def->sev->sectype == VIR_DOMAIN_LAUNCH_SECURITY_SEV &&
|
|
!supportsSEV) {
|
|
VIR_DEBUG("Domain requires SEV, firmware '%s' doesn't support it", path);
|
|
return false;
|
|
}
|
|
|
|
VIR_DEBUG("Firmware '%s' matches domain requirements", path);
|
|
return true;
|
|
}
|
|
|
|
|
|
static int
|
|
qemuFirmwareEnableFeatures(virQEMUDriverPtr driver,
|
|
virDomainDefPtr def,
|
|
const qemuFirmware *fw)
|
|
{
|
|
VIR_AUTOUNREF(virQEMUDriverConfigPtr) cfg = virQEMUDriverGetConfig(driver);
|
|
const qemuFirmwareMappingFlash *flash = &fw->mapping.data.flash;
|
|
const qemuFirmwareMappingKernel *kernel = &fw->mapping.data.kernel;
|
|
const qemuFirmwareMappingMemory *memory = &fw->mapping.data.memory;
|
|
size_t i;
|
|
|
|
switch (fw->mapping.device) {
|
|
case QEMU_FIRMWARE_DEVICE_FLASH:
|
|
if (!def->os.loader &&
|
|
VIR_ALLOC(def->os.loader) < 0)
|
|
return -1;
|
|
|
|
def->os.loader->type = VIR_DOMAIN_LOADER_TYPE_PFLASH;
|
|
def->os.loader->readonly = VIR_TRISTATE_BOOL_YES;
|
|
|
|
if (STRNEQ(flash->executable.format, "raw")) {
|
|
virReportError(VIR_ERR_OPERATION_UNSUPPORTED,
|
|
_("unsupported flash format '%s'"),
|
|
flash->executable.format);
|
|
return -1;
|
|
}
|
|
|
|
VIR_FREE(def->os.loader->path);
|
|
if (VIR_STRDUP(def->os.loader->path,
|
|
flash->executable.filename) < 0)
|
|
return -1;
|
|
|
|
if (STRNEQ(flash->nvram_template.format, "raw")) {
|
|
virReportError(VIR_ERR_OPERATION_UNSUPPORTED,
|
|
_("unsupported nvram template format '%s'"),
|
|
flash->nvram_template.format);
|
|
return -1;
|
|
}
|
|
|
|
VIR_FREE(def->os.loader->templt);
|
|
if (VIR_STRDUP(def->os.loader->templt,
|
|
flash->nvram_template.filename) < 0)
|
|
return -1;
|
|
|
|
if (qemuDomainNVRAMPathGenerate(cfg, def) < 0)
|
|
return -1;
|
|
|
|
VIR_DEBUG("decided on firmware '%s' varstore template '%s'",
|
|
def->os.loader->path,
|
|
def->os.loader->templt);
|
|
break;
|
|
|
|
case QEMU_FIRMWARE_DEVICE_KERNEL:
|
|
VIR_FREE(def->os.kernel);
|
|
if (VIR_STRDUP(def->os.kernel, kernel->filename) < 0)
|
|
return -1;
|
|
|
|
VIR_DEBUG("decided on kernel '%s'",
|
|
def->os.kernel);
|
|
break;
|
|
|
|
case QEMU_FIRMWARE_DEVICE_MEMORY:
|
|
if (!def->os.loader &&
|
|
VIR_ALLOC(def->os.loader) < 0)
|
|
return -1;
|
|
|
|
def->os.loader->type = VIR_DOMAIN_LOADER_TYPE_ROM;
|
|
if (VIR_STRDUP(def->os.loader->path, memory->filename) < 0)
|
|
return -1;
|
|
|
|
VIR_DEBUG("decided on loader '%s'",
|
|
def->os.loader->path);
|
|
break;
|
|
|
|
case QEMU_FIRMWARE_DEVICE_NONE:
|
|
case QEMU_FIRMWARE_DEVICE_LAST:
|
|
break;
|
|
}
|
|
|
|
for (i = 0; i < fw->nfeatures; i++) {
|
|
switch (fw->features[i]) {
|
|
case QEMU_FIRMWARE_FEATURE_REQUIRES_SMM:
|
|
switch (def->features[VIR_DOMAIN_FEATURE_SMM]) {
|
|
case VIR_TRISTATE_SWITCH_OFF:
|
|
virReportError(VIR_ERR_OPERATION_INVALID, "%s",
|
|
_("domain has SMM turned off "
|
|
"but chosen firmware requires it"));
|
|
return -1;
|
|
break;
|
|
case VIR_TRISTATE_SWITCH_ABSENT:
|
|
VIR_DEBUG("Enabling SMM feature");
|
|
def->features[VIR_DOMAIN_FEATURE_SMM] = VIR_TRISTATE_SWITCH_ON;
|
|
break;
|
|
|
|
case VIR_TRISTATE_SWITCH_ON:
|
|
case VIR_TRISTATE_SWITCH_LAST:
|
|
break;
|
|
}
|
|
break;
|
|
|
|
case QEMU_FIRMWARE_FEATURE_NONE:
|
|
case QEMU_FIRMWARE_FEATURE_ACPI_S3:
|
|
case QEMU_FIRMWARE_FEATURE_ACPI_S4:
|
|
case QEMU_FIRMWARE_FEATURE_AMD_SEV:
|
|
case QEMU_FIRMWARE_FEATURE_ENROLLED_KEYS:
|
|
case QEMU_FIRMWARE_FEATURE_SECURE_BOOT:
|
|
case QEMU_FIRMWARE_FEATURE_VERBOSE_DYNAMIC:
|
|
case QEMU_FIRMWARE_FEATURE_VERBOSE_STATIC:
|
|
case QEMU_FIRMWARE_FEATURE_LAST:
|
|
break;
|
|
}
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
|
|
static void
|
|
qemuFirmwareSanityCheck(const qemuFirmware *fw,
|
|
const char *filename)
|
|
{
|
|
size_t i;
|
|
bool requiresSMM = false;
|
|
bool supportsSecureBoot = false;
|
|
|
|
for (i = 0; i < fw->nfeatures; i++) {
|
|
switch (fw->features[i]) {
|
|
case QEMU_FIRMWARE_FEATURE_REQUIRES_SMM:
|
|
requiresSMM = true;
|
|
break;
|
|
case QEMU_FIRMWARE_FEATURE_SECURE_BOOT:
|
|
supportsSecureBoot = true;
|
|
break;
|
|
case QEMU_FIRMWARE_FEATURE_NONE:
|
|
case QEMU_FIRMWARE_FEATURE_ACPI_S3:
|
|
case QEMU_FIRMWARE_FEATURE_ACPI_S4:
|
|
case QEMU_FIRMWARE_FEATURE_AMD_SEV:
|
|
case QEMU_FIRMWARE_FEATURE_ENROLLED_KEYS:
|
|
case QEMU_FIRMWARE_FEATURE_VERBOSE_DYNAMIC:
|
|
case QEMU_FIRMWARE_FEATURE_VERBOSE_STATIC:
|
|
case QEMU_FIRMWARE_FEATURE_LAST:
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (supportsSecureBoot != requiresSMM) {
|
|
VIR_WARN("Firmware description '%s' has invalid set of features: "
|
|
"%s = %d, %s = %d",
|
|
filename,
|
|
qemuFirmwareFeatureTypeToString(QEMU_FIRMWARE_FEATURE_REQUIRES_SMM),
|
|
requiresSMM,
|
|
qemuFirmwareFeatureTypeToString(QEMU_FIRMWARE_FEATURE_SECURE_BOOT),
|
|
supportsSecureBoot);
|
|
}
|
|
}
|
|
|
|
|
|
int
|
|
qemuFirmwareFillDomain(virQEMUDriverPtr driver,
|
|
virDomainObjPtr vm,
|
|
unsigned int flags)
|
|
{
|
|
VIR_AUTOSTRINGLIST paths = NULL;
|
|
size_t npaths = 0;
|
|
qemuFirmwarePtr *firmwares = NULL;
|
|
size_t nfirmwares = 0;
|
|
const qemuFirmware *theone = NULL;
|
|
size_t i;
|
|
int ret = -1;
|
|
|
|
if (!(flags & VIR_QEMU_PROCESS_START_NEW))
|
|
return 0;
|
|
|
|
if (vm->def->os.firmware == VIR_DOMAIN_OS_DEF_FIRMWARE_NONE)
|
|
return 0;
|
|
|
|
if (qemuFirmwareFetchConfigs(&paths, driver->privileged) < 0)
|
|
return -1;
|
|
|
|
npaths = virStringListLength((const char **)paths);
|
|
|
|
if (VIR_ALLOC_N(firmwares, npaths) < 0)
|
|
return -1;
|
|
|
|
nfirmwares = npaths;
|
|
|
|
for (i = 0; i < nfirmwares; i++) {
|
|
if (!(firmwares[i] = qemuFirmwareParse(paths[i])))
|
|
goto cleanup;
|
|
}
|
|
|
|
for (i = 0; i < nfirmwares; i++) {
|
|
if (qemuFirmwareMatchDomain(vm->def, firmwares[i], paths[i])) {
|
|
theone = firmwares[i];
|
|
VIR_DEBUG("Found matching firmware (description path '%s')",
|
|
paths[i]);
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (!theone) {
|
|
virReportError(VIR_ERR_OPERATION_FAILED,
|
|
_("Unable to find any firmware to satisfy '%s'"),
|
|
virDomainOsDefFirmwareTypeToString(vm->def->os.firmware));
|
|
goto cleanup;
|
|
}
|
|
|
|
/* Firstly, let's do some sanity checks. If either of these
|
|
* fail we can still start the domain successfully, but it's
|
|
* likely that admin/FW manufacturer messed up. */
|
|
qemuFirmwareSanityCheck(theone, paths[i]);
|
|
|
|
if (qemuFirmwareEnableFeatures(driver, vm->def, theone) < 0)
|
|
goto cleanup;
|
|
|
|
vm->def->os.firmware = VIR_DOMAIN_OS_DEF_FIRMWARE_NONE;
|
|
|
|
ret = 0;
|
|
cleanup:
|
|
for (i = 0; i < nfirmwares; i++)
|
|
qemuFirmwareFree(firmwares[i]);
|
|
VIR_FREE(firmwares);
|
|
return ret;
|
|
}
|