Starting with the edk2-stable202508 tag OVMF (and ArmVirt too) have optional support for logging to a memory buffer. There is guest side support -- for example in linux kernels v6.17+ -- to read that buffer. But that might not helpful if your guest stops booting early enough that guest tooling can not be used yet. So host side support to read that log buffer is a useful thing to have. This patch implements the query-firmware-log qmp monitor command to read the firmware log. Reviewed-by: Markus Armbruster <armbru@redhat.com> Signed-off-by: Gerd Hoffmann <kraxel@redhat.com> Message-ID: <20251017115006.2696991-2-kraxel@redhat.com>
363 lines
11 KiB
C
363 lines
11 KiB
C
/*
|
|
* QMP command test cases
|
|
*
|
|
* Copyright (c) 2017 Red Hat Inc.
|
|
*
|
|
* Authors:
|
|
* Markus Armbruster <armbru@redhat.com>
|
|
*
|
|
* This work is licensed under the terms of the GNU GPL, version 2 or later.
|
|
* See the COPYING file in the top-level directory.
|
|
*/
|
|
|
|
#include "qemu/osdep.h"
|
|
#include "libqtest.h"
|
|
#include "qapi/error.h"
|
|
#include "qapi/qapi-visit-introspect.h"
|
|
#include "qobject/qdict.h"
|
|
#include "qapi/qobject-input-visitor.h"
|
|
|
|
const char common_args[] = "-nodefaults -machine none";
|
|
|
|
/* Query smoke tests */
|
|
|
|
static int query_error_class(const char *cmd)
|
|
{
|
|
static struct {
|
|
const char *cmd;
|
|
int err_class;
|
|
} fails[] = {
|
|
/* Success depends on build configuration: */
|
|
#ifndef CONFIG_SPICE
|
|
{ "query-spice", ERROR_CLASS_COMMAND_NOT_FOUND },
|
|
#endif
|
|
#ifndef CONFIG_TCG
|
|
{ "query-replay", ERROR_CLASS_COMMAND_NOT_FOUND },
|
|
#endif
|
|
#ifndef CONFIG_VNC
|
|
{ "query-vnc", ERROR_CLASS_GENERIC_ERROR },
|
|
{ "query-vnc-servers", ERROR_CLASS_GENERIC_ERROR },
|
|
#endif
|
|
#ifndef CONFIG_REPLICATION
|
|
{ "query-xen-replication-status", ERROR_CLASS_COMMAND_NOT_FOUND },
|
|
#endif
|
|
/* Likewise, and require special QEMU command-line arguments: */
|
|
{ "query-acpi-ospm-status", ERROR_CLASS_GENERIC_ERROR },
|
|
{ "query-balloon", ERROR_CLASS_DEVICE_NOT_ACTIVE },
|
|
{ "query-hotpluggable-cpus", ERROR_CLASS_GENERIC_ERROR },
|
|
{ "query-hv-balloon-status-report", ERROR_CLASS_GENERIC_ERROR },
|
|
{ "query-vm-generation-id", ERROR_CLASS_GENERIC_ERROR },
|
|
/* Only valid with a USB bus added */
|
|
{ "x-query-usb", ERROR_CLASS_GENERIC_ERROR },
|
|
/* Only valid with accel=tcg */
|
|
{ "x-query-jit", ERROR_CLASS_GENERIC_ERROR },
|
|
{ "xen-event-list", ERROR_CLASS_GENERIC_ERROR },
|
|
/* requires firmware with memory buffer logging support */
|
|
{ "query-firmware-log", ERROR_CLASS_GENERIC_ERROR },
|
|
{ NULL, -1 }
|
|
};
|
|
int i;
|
|
|
|
for (i = 0; fails[i].cmd; i++) {
|
|
if (!strcmp(cmd, fails[i].cmd)) {
|
|
return fails[i].err_class;
|
|
}
|
|
}
|
|
return -1;
|
|
}
|
|
|
|
static void test_query(const void *data)
|
|
{
|
|
const char *cmd = data;
|
|
int expected_error_class = query_error_class(cmd);
|
|
QDict *resp, *error;
|
|
const char *error_class;
|
|
QTestState *qts;
|
|
|
|
qts = qtest_init(common_args);
|
|
|
|
resp = qtest_qmp(qts, "{ 'execute': %s }", cmd);
|
|
error = qdict_get_qdict(resp, "error");
|
|
error_class = error ? qdict_get_str(error, "class") : NULL;
|
|
|
|
if (expected_error_class < 0) {
|
|
g_assert(qdict_haskey(resp, "return"));
|
|
} else {
|
|
g_assert(error);
|
|
g_assert_cmpint(qapi_enum_parse(&QapiErrorClass_lookup, error_class,
|
|
-1, &error_abort),
|
|
==, expected_error_class);
|
|
}
|
|
qobject_unref(resp);
|
|
|
|
qtest_quit(qts);
|
|
}
|
|
|
|
static bool query_is_ignored(const char *cmd)
|
|
{
|
|
const char *ignored[] = {
|
|
/* Not actually queries: */
|
|
"add-fd",
|
|
/* Success depends on target arch: */
|
|
"query-cpu-definitions", /* arm, i386, ppc, s390x */
|
|
"query-gic-capabilities", /* arm */
|
|
"query-s390x-cpu-polarization", /* s390x */
|
|
/* Success depends on target-specific build configuration: */
|
|
"query-pci", /* CONFIG_PCI */
|
|
"x-query-virtio", /* CONFIG_VIRTIO */
|
|
/* Success depends on launching SEV guest */
|
|
"query-sev-launch-measure",
|
|
/* Success depends on Host or Hypervisor SEV support */
|
|
"query-sev",
|
|
"query-sev-capabilities",
|
|
"query-sgx",
|
|
"query-sgx-capabilities",
|
|
/* Success depends on enabling dirty page rate limit */
|
|
"query-vcpu-dirty-limit",
|
|
NULL
|
|
};
|
|
int i;
|
|
|
|
for (i = 0; ignored[i]; i++) {
|
|
if (!strcmp(cmd, ignored[i])) {
|
|
return true;
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
typedef struct {
|
|
SchemaInfoList *list;
|
|
GHashTable *hash;
|
|
} QmpSchema;
|
|
|
|
static void qmp_schema_init(QmpSchema *schema)
|
|
{
|
|
QDict *resp;
|
|
Visitor *qiv;
|
|
SchemaInfoList *tail;
|
|
QTestState *qts;
|
|
|
|
qts = qtest_init(common_args);
|
|
|
|
resp = qtest_qmp(qts, "{ 'execute': 'query-qmp-schema' }");
|
|
|
|
qiv = qobject_input_visitor_new(qdict_get(resp, "return"));
|
|
visit_type_SchemaInfoList(qiv, NULL, &schema->list, &error_abort);
|
|
visit_free(qiv);
|
|
|
|
qobject_unref(resp);
|
|
qtest_quit(qts);
|
|
|
|
schema->hash = g_hash_table_new(g_str_hash, g_str_equal);
|
|
|
|
/* Build @schema: hash table mapping entity name to SchemaInfo */
|
|
for (tail = schema->list; tail; tail = tail->next) {
|
|
g_hash_table_insert(schema->hash, tail->value->name, tail->value);
|
|
}
|
|
}
|
|
|
|
static SchemaInfo *qmp_schema_lookup(QmpSchema *schema, const char *name)
|
|
{
|
|
return g_hash_table_lookup(schema->hash, name);
|
|
}
|
|
|
|
static void qmp_schema_cleanup(QmpSchema *schema)
|
|
{
|
|
qapi_free_SchemaInfoList(schema->list);
|
|
g_hash_table_destroy(schema->hash);
|
|
}
|
|
|
|
static bool object_type_has_mandatory_members(SchemaInfo *type)
|
|
{
|
|
SchemaInfoObjectMemberList *tail;
|
|
|
|
g_assert(type->meta_type == SCHEMA_META_TYPE_OBJECT);
|
|
|
|
for (tail = type->u.object.members; tail; tail = tail->next) {
|
|
if (!tail->value->q_default) {
|
|
return true;
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
static void add_query_tests(QmpSchema *schema)
|
|
{
|
|
SchemaInfoList *tail;
|
|
SchemaInfo *si, *arg_type, *ret_type;
|
|
char *test_name;
|
|
|
|
/* Test the query-like commands */
|
|
for (tail = schema->list; tail; tail = tail->next) {
|
|
si = tail->value;
|
|
if (si->meta_type != SCHEMA_META_TYPE_COMMAND) {
|
|
continue;
|
|
}
|
|
|
|
if (query_is_ignored(si->name)) {
|
|
continue;
|
|
}
|
|
|
|
arg_type = qmp_schema_lookup(schema, si->u.command.arg_type);
|
|
if (object_type_has_mandatory_members(arg_type)) {
|
|
continue;
|
|
}
|
|
|
|
ret_type = qmp_schema_lookup(schema, si->u.command.ret_type);
|
|
if (ret_type->meta_type == SCHEMA_META_TYPE_OBJECT
|
|
&& !ret_type->u.object.members) {
|
|
continue;
|
|
}
|
|
|
|
test_name = g_strdup_printf("qmp/%s", si->name);
|
|
qtest_add_data_func(test_name, si->name, test_query);
|
|
g_free(test_name);
|
|
}
|
|
}
|
|
|
|
static void test_object_add_failure_modes(void)
|
|
{
|
|
QTestState *qts;
|
|
QDict *resp;
|
|
|
|
/* attempt to create an object without props */
|
|
qts = qtest_init(common_args);
|
|
resp = qtest_qmp(qts, "{'execute': 'object-add', 'arguments':"
|
|
" {'qom-type': 'memory-backend-ram', 'id': 'ram1' } }");
|
|
g_assert_nonnull(resp);
|
|
qmp_expect_error_and_unref(resp, "GenericError");
|
|
|
|
/* attempt to create an object without qom-type */
|
|
resp = qtest_qmp(qts, "{'execute': 'object-add', 'arguments':"
|
|
" {'id': 'ram1' } }");
|
|
g_assert_nonnull(resp);
|
|
qmp_expect_error_and_unref(resp, "GenericError");
|
|
|
|
/* attempt to delete an object that does not exist */
|
|
resp = qtest_qmp(qts, "{'execute': 'object-del', 'arguments':"
|
|
" {'id': 'ram1' } }");
|
|
g_assert_nonnull(resp);
|
|
qmp_expect_error_and_unref(resp, "GenericError");
|
|
|
|
/* attempt to create 2 objects with duplicate id */
|
|
resp = qtest_qmp(qts, "{'execute': 'object-add', 'arguments':"
|
|
" {'qom-type': 'memory-backend-ram', 'id': 'ram1',"
|
|
" 'size': 1048576 } }");
|
|
g_assert_nonnull(resp);
|
|
g_assert(qdict_haskey(resp, "return"));
|
|
qobject_unref(resp);
|
|
|
|
resp = qtest_qmp(qts, "{'execute': 'object-add', 'arguments':"
|
|
" {'qom-type': 'memory-backend-ram', 'id': 'ram1',"
|
|
" 'size': 1048576 } }");
|
|
g_assert_nonnull(resp);
|
|
qmp_expect_error_and_unref(resp, "GenericError");
|
|
|
|
/* delete ram1 object */
|
|
resp = qtest_qmp(qts, "{'execute': 'object-del', 'arguments':"
|
|
" {'id': 'ram1' } }");
|
|
g_assert_nonnull(resp);
|
|
g_assert(qdict_haskey(resp, "return"));
|
|
qobject_unref(resp);
|
|
|
|
/* attempt to create an object with a property of a wrong type */
|
|
resp = qtest_qmp(qts, "{'execute': 'object-add', 'arguments':"
|
|
" {'qom-type': 'memory-backend-ram', 'id': 'ram1',"
|
|
" 'size': '1048576' } }");
|
|
g_assert_nonnull(resp);
|
|
/* now do it right */
|
|
qmp_expect_error_and_unref(resp, "GenericError");
|
|
|
|
resp = qtest_qmp(qts, "{'execute': 'object-add', 'arguments':"
|
|
" {'qom-type': 'memory-backend-ram', 'id': 'ram1',"
|
|
" 'size': 1048576 } }");
|
|
g_assert_nonnull(resp);
|
|
g_assert(qdict_haskey(resp, "return"));
|
|
qobject_unref(resp);
|
|
|
|
/* delete ram1 object */
|
|
resp = qtest_qmp(qts, "{'execute': 'object-del', 'arguments':"
|
|
" {'id': 'ram1' } }");
|
|
g_assert_nonnull(resp);
|
|
g_assert(qdict_haskey(resp, "return"));
|
|
qobject_unref(resp);
|
|
|
|
/* attempt to create an object without the id */
|
|
resp = qtest_qmp(qts, "{'execute': 'object-add', 'arguments':"
|
|
" {'qom-type': 'memory-backend-ram',"
|
|
" 'size': 1048576 } }");
|
|
g_assert_nonnull(resp);
|
|
qmp_expect_error_and_unref(resp, "GenericError");
|
|
|
|
/* now do it right */
|
|
resp = qtest_qmp(qts, "{'execute': 'object-add', 'arguments':"
|
|
" {'qom-type': 'memory-backend-ram', 'id': 'ram1',"
|
|
" 'size': 1048576 } }");
|
|
g_assert_nonnull(resp);
|
|
g_assert(qdict_haskey(resp, "return"));
|
|
qobject_unref(resp);
|
|
|
|
/* delete ram1 object */
|
|
resp = qtest_qmp(qts, "{'execute': 'object-del', 'arguments':"
|
|
" {'id': 'ram1' } }");
|
|
g_assert_nonnull(resp);
|
|
g_assert(qdict_haskey(resp, "return"));
|
|
qobject_unref(resp);
|
|
|
|
/* attempt to set a non existing property */
|
|
resp = qtest_qmp(qts, "{'execute': 'object-add', 'arguments':"
|
|
" {'qom-type': 'memory-backend-ram', 'id': 'ram1',"
|
|
" 'sized': 1048576 } }");
|
|
g_assert_nonnull(resp);
|
|
qmp_expect_error_and_unref(resp, "GenericError");
|
|
|
|
/* now do it right */
|
|
resp = qtest_qmp(qts, "{'execute': 'object-add', 'arguments':"
|
|
" {'qom-type': 'memory-backend-ram', 'id': 'ram1',"
|
|
" 'size': 1048576 } }");
|
|
g_assert_nonnull(resp);
|
|
g_assert(qdict_haskey(resp, "return"));
|
|
qobject_unref(resp);
|
|
|
|
/* delete ram1 object without id */
|
|
resp = qtest_qmp(qts, "{'execute': 'object-del', 'arguments':"
|
|
" {'ida': 'ram1' } }");
|
|
g_assert_nonnull(resp);
|
|
qobject_unref(resp);
|
|
|
|
/* delete ram1 object */
|
|
resp = qtest_qmp(qts, "{'execute': 'object-del', 'arguments':"
|
|
" {'id': 'ram1' } }");
|
|
g_assert_nonnull(resp);
|
|
g_assert(qdict_haskey(resp, "return"));
|
|
qobject_unref(resp);
|
|
|
|
/* delete ram1 object that does not exist anymore*/
|
|
resp = qtest_qmp(qts, "{'execute': 'object-del', 'arguments':"
|
|
" {'id': 'ram1' } }");
|
|
g_assert_nonnull(resp);
|
|
qmp_expect_error_and_unref(resp, "GenericError");
|
|
|
|
qtest_quit(qts);
|
|
}
|
|
|
|
int main(int argc, char *argv[])
|
|
{
|
|
QmpSchema schema;
|
|
int ret;
|
|
|
|
g_test_init(&argc, &argv, NULL);
|
|
|
|
qmp_schema_init(&schema);
|
|
add_query_tests(&schema);
|
|
|
|
qtest_add_func("qmp/object-add-failure-modes",
|
|
test_object_add_failure_modes);
|
|
|
|
ret = g_test_run();
|
|
|
|
qmp_schema_cleanup(&schema);
|
|
return ret;
|
|
}
|