qtest: add a QOM object for qtest
The qtest server right now can only be created using the -qtest and -qtest-log options. Allow an alternative way to create it using "-object qtest,chardev=...,log=...". This is part of the long term plan to make more (or all) of QEMU configurable through QMP and preconfig mode. Signed-off-by: Paolo Bonzini <pbonzini@redhat.com>
This commit is contained in:
parent
9e33013bd4
commit
6ba7ada355
|
@ -644,6 +644,21 @@
|
||||||
{ 'struct': 'PrManagerHelperProperties',
|
{ 'struct': 'PrManagerHelperProperties',
|
||||||
'data': { 'path': 'str' } }
|
'data': { 'path': 'str' } }
|
||||||
|
|
||||||
|
##
|
||||||
|
# @QtestProperties:
|
||||||
|
#
|
||||||
|
# Properties for qtest objects.
|
||||||
|
#
|
||||||
|
# @chardev: the chardev to be used to receive qtest commands on.
|
||||||
|
#
|
||||||
|
# @log: the path to a log file
|
||||||
|
#
|
||||||
|
# Since: 6.0
|
||||||
|
##
|
||||||
|
{ 'struct': 'QtestProperties',
|
||||||
|
'data': { 'chardev': 'str',
|
||||||
|
'*log': 'str' } }
|
||||||
|
|
||||||
##
|
##
|
||||||
# @RemoteObjectProperties:
|
# @RemoteObjectProperties:
|
||||||
#
|
#
|
||||||
|
@ -769,6 +784,7 @@
|
||||||
'memory-backend-ram',
|
'memory-backend-ram',
|
||||||
'pef-guest',
|
'pef-guest',
|
||||||
'pr-manager-helper',
|
'pr-manager-helper',
|
||||||
|
'qtest',
|
||||||
'rng-builtin',
|
'rng-builtin',
|
||||||
'rng-egd',
|
'rng-egd',
|
||||||
'rng-random',
|
'rng-random',
|
||||||
|
@ -825,6 +841,7 @@
|
||||||
'if': 'defined(CONFIG_LINUX)' },
|
'if': 'defined(CONFIG_LINUX)' },
|
||||||
'memory-backend-ram': 'MemoryBackendProperties',
|
'memory-backend-ram': 'MemoryBackendProperties',
|
||||||
'pr-manager-helper': 'PrManagerHelperProperties',
|
'pr-manager-helper': 'PrManagerHelperProperties',
|
||||||
|
'qtest': 'QtestProperties',
|
||||||
'rng-builtin': 'RngProperties',
|
'rng-builtin': 'RngProperties',
|
||||||
'rng-egd': 'RngEgdProperties',
|
'rng-egd': 'RngEgdProperties',
|
||||||
'rng-random': 'RngRandomProperties',
|
'rng-random': 'RngRandomProperties',
|
||||||
|
|
185
softmmu/qtest.c
185
softmmu/qtest.c
|
@ -27,6 +27,8 @@
|
||||||
#include "qemu/error-report.h"
|
#include "qemu/error-report.h"
|
||||||
#include "qemu/module.h"
|
#include "qemu/module.h"
|
||||||
#include "qemu/cutils.h"
|
#include "qemu/cutils.h"
|
||||||
|
#include "qapi/qmp/qerror.h"
|
||||||
|
#include "qom/object_interfaces.h"
|
||||||
#include CONFIG_DEVICES
|
#include CONFIG_DEVICES
|
||||||
#ifdef CONFIG_PSERIES
|
#ifdef CONFIG_PSERIES
|
||||||
#include "hw/ppc/spapr_rtas.h"
|
#include "hw/ppc/spapr_rtas.h"
|
||||||
|
@ -34,11 +36,25 @@
|
||||||
|
|
||||||
#define MAX_IRQ 256
|
#define MAX_IRQ 256
|
||||||
|
|
||||||
|
#define TYPE_QTEST "qtest"
|
||||||
|
|
||||||
|
OBJECT_DECLARE_SIMPLE_TYPE(QTest, QTEST)
|
||||||
|
|
||||||
|
struct QTest {
|
||||||
|
Object parent;
|
||||||
|
|
||||||
|
bool has_machine_link;
|
||||||
|
char *chr_name;
|
||||||
|
Chardev *chr;
|
||||||
|
CharBackend qtest_chr;
|
||||||
|
char *log;
|
||||||
|
};
|
||||||
|
|
||||||
bool qtest_allowed;
|
bool qtest_allowed;
|
||||||
|
|
||||||
static DeviceState *irq_intercept_dev;
|
static DeviceState *irq_intercept_dev;
|
||||||
static FILE *qtest_log_fp;
|
static FILE *qtest_log_fp;
|
||||||
static CharBackend qtest_chr;
|
static QTest *qtest;
|
||||||
static GString *inbuf;
|
static GString *inbuf;
|
||||||
static int irq_levels[MAX_IRQ];
|
static int irq_levels[MAX_IRQ];
|
||||||
static qemu_timeval start_time;
|
static qemu_timeval start_time;
|
||||||
|
@ -320,7 +336,7 @@ static void qtest_irq_handler(void *opaque, int n, int level)
|
||||||
qemu_set_irq(old_irq, level);
|
qemu_set_irq(old_irq, level);
|
||||||
|
|
||||||
if (irq_levels[n] != level) {
|
if (irq_levels[n] != level) {
|
||||||
CharBackend *chr = &qtest_chr;
|
CharBackend *chr = &qtest->qtest_chr;
|
||||||
irq_levels[n] = level;
|
irq_levels[n] = level;
|
||||||
qtest_send_prefix(chr);
|
qtest_send_prefix(chr);
|
||||||
qtest_sendf(chr, "IRQ %s %d\n",
|
qtest_sendf(chr, "IRQ %s %d\n",
|
||||||
|
@ -849,18 +865,39 @@ static void qtest_event(void *opaque, QEMUChrEvent event)
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void qtest_server_init(const char *qtest_chrdev, const char *qtest_log, Error **errp)
|
void qtest_server_init(const char *qtest_chrdev, const char *qtest_log, Error **errp)
|
||||||
{
|
{
|
||||||
|
ERRP_GUARD();
|
||||||
Chardev *chr;
|
Chardev *chr;
|
||||||
|
Object *qtest;
|
||||||
|
|
||||||
chr = qemu_chr_new("qtest", qtest_chrdev, NULL);
|
chr = qemu_chr_new("qtest", qtest_chrdev, NULL);
|
||||||
|
|
||||||
if (chr == NULL) {
|
if (chr == NULL) {
|
||||||
error_setg(errp, "Failed to initialize device for qtest: \"%s\"",
|
error_setg(errp, "Failed to initialize device for qtest: \"%s\"",
|
||||||
qtest_chrdev);
|
qtest_chrdev);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
qtest = object_new(TYPE_QTEST);
|
||||||
|
object_property_set_str(qtest, "chardev", "qtest", &error_abort);
|
||||||
|
if (qtest_log) {
|
||||||
|
object_property_set_str(qtest, "log", qtest_log, &error_abort);
|
||||||
|
}
|
||||||
|
object_property_add_child(qdev_get_machine(), "qtest", qtest);
|
||||||
|
user_creatable_complete(USER_CREATABLE(qtest), errp);
|
||||||
|
if (*errp) {
|
||||||
|
object_unparent(qtest);
|
||||||
|
}
|
||||||
|
object_unref(OBJECT(chr));
|
||||||
|
object_unref(qtest);
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool qtest_server_start(QTest *q, Error **errp)
|
||||||
|
{
|
||||||
|
Chardev *chr = q->chr;
|
||||||
|
const char *qtest_log = q->log;
|
||||||
|
|
||||||
if (qtest_log) {
|
if (qtest_log) {
|
||||||
if (strcmp(qtest_log, "none") != 0) {
|
if (strcmp(qtest_log, "none") != 0) {
|
||||||
qtest_log_fp = fopen(qtest_log, "w+");
|
qtest_log_fp = fopen(qtest_log, "w+");
|
||||||
|
@ -869,16 +906,20 @@ void qtest_server_init(const char *qtest_chrdev, const char *qtest_log, Error **
|
||||||
qtest_log_fp = stderr;
|
qtest_log_fp = stderr;
|
||||||
}
|
}
|
||||||
|
|
||||||
qemu_chr_fe_init(&qtest_chr, chr, errp);
|
if (!qemu_chr_fe_init(&q->qtest_chr, chr, errp)) {
|
||||||
qemu_chr_fe_set_handlers(&qtest_chr, qtest_can_read, qtest_read,
|
return false;
|
||||||
qtest_event, NULL, &qtest_chr, NULL, true);
|
}
|
||||||
qemu_chr_fe_set_echo(&qtest_chr, true);
|
qemu_chr_fe_set_handlers(&q->qtest_chr, qtest_can_read, qtest_read,
|
||||||
|
qtest_event, NULL, &q->qtest_chr, NULL, true);
|
||||||
|
qemu_chr_fe_set_echo(&q->qtest_chr, true);
|
||||||
|
|
||||||
inbuf = g_string_new("");
|
inbuf = g_string_new("");
|
||||||
|
|
||||||
if (!qtest_server_send) {
|
if (!qtest_server_send) {
|
||||||
qtest_server_set_send_handler(qtest_server_char_be_send, &qtest_chr);
|
qtest_server_set_send_handler(qtest_server_char_be_send, &q->qtest_chr);
|
||||||
}
|
}
|
||||||
|
qtest = q;
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
void qtest_server_set_send_handler(void (*send)(void*, const char*),
|
void qtest_server_set_send_handler(void (*send)(void*, const char*),
|
||||||
|
@ -890,7 +931,7 @@ void qtest_server_set_send_handler(void (*send)(void*, const char*),
|
||||||
|
|
||||||
bool qtest_driver(void)
|
bool qtest_driver(void)
|
||||||
{
|
{
|
||||||
return qtest_chr.chr != NULL;
|
return qtest && qtest->qtest_chr.chr != NULL;
|
||||||
}
|
}
|
||||||
|
|
||||||
void qtest_server_inproc_recv(void *dummy, const char *buf)
|
void qtest_server_inproc_recv(void *dummy, const char *buf)
|
||||||
|
@ -905,3 +946,129 @@ void qtest_server_inproc_recv(void *dummy, const char *buf)
|
||||||
g_string_truncate(gstr, 0);
|
g_string_truncate(gstr, 0);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static void qtest_complete(UserCreatable *uc, Error **errp)
|
||||||
|
{
|
||||||
|
QTest *q = QTEST(uc);
|
||||||
|
if (qtest) {
|
||||||
|
error_setg(errp, "Only one instance of qtest can be created");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (!q->chr_name) {
|
||||||
|
error_setg(errp, "No backend specified");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (OBJECT(uc)->parent != qdev_get_machine()) {
|
||||||
|
q->has_machine_link = true;
|
||||||
|
object_property_add_const_link(qdev_get_machine(), "qtest", OBJECT(uc));
|
||||||
|
} else {
|
||||||
|
/* -qtest was used. */
|
||||||
|
}
|
||||||
|
|
||||||
|
qtest_server_start(q, errp);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void qtest_unparent(Object *obj)
|
||||||
|
{
|
||||||
|
QTest *q = QTEST(obj);
|
||||||
|
|
||||||
|
if (qtest == q) {
|
||||||
|
qemu_chr_fe_disconnect(&q->qtest_chr);
|
||||||
|
assert(!qtest_opened);
|
||||||
|
qemu_chr_fe_deinit(&q->qtest_chr, false);
|
||||||
|
if (qtest_log_fp) {
|
||||||
|
fclose(qtest_log_fp);
|
||||||
|
qtest_log_fp = NULL;
|
||||||
|
}
|
||||||
|
qtest = NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (q->has_machine_link) {
|
||||||
|
object_property_del(qdev_get_machine(), "qtest");
|
||||||
|
q->has_machine_link = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static void qtest_set_log(Object *obj, const char *value, Error **errp)
|
||||||
|
{
|
||||||
|
QTest *q = QTEST(obj);
|
||||||
|
|
||||||
|
if (qtest == q) {
|
||||||
|
error_setg(errp, QERR_PERMISSION_DENIED);
|
||||||
|
} else {
|
||||||
|
g_free(q->log);
|
||||||
|
q->log = g_strdup(value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static char *qtest_get_log(Object *obj, Error **errp)
|
||||||
|
{
|
||||||
|
QTest *q = QTEST(obj);
|
||||||
|
|
||||||
|
return g_strdup(q->log);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void qtest_set_chardev(Object *obj, const char *value, Error **errp)
|
||||||
|
{
|
||||||
|
QTest *q = QTEST(obj);
|
||||||
|
Chardev *chr;
|
||||||
|
|
||||||
|
if (qtest == q) {
|
||||||
|
error_setg(errp, QERR_PERMISSION_DENIED);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
chr = qemu_chr_find(value);
|
||||||
|
if (!chr) {
|
||||||
|
error_setg(errp, "Cannot find character device '%s'", value);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
g_free(q->chr_name);
|
||||||
|
q->chr_name = g_strdup(value);
|
||||||
|
|
||||||
|
if (q->chr) {
|
||||||
|
object_unref(q->chr);
|
||||||
|
}
|
||||||
|
q->chr = chr;
|
||||||
|
object_ref(chr);
|
||||||
|
}
|
||||||
|
|
||||||
|
static char *qtest_get_chardev(Object *obj, Error **errp)
|
||||||
|
{
|
||||||
|
QTest *q = QTEST(obj);
|
||||||
|
|
||||||
|
return g_strdup(q->chr_name);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void qtest_class_init(ObjectClass *oc, void *data)
|
||||||
|
{
|
||||||
|
UserCreatableClass *ucc = USER_CREATABLE_CLASS(oc);
|
||||||
|
|
||||||
|
oc->unparent = qtest_unparent;
|
||||||
|
ucc->complete = qtest_complete;
|
||||||
|
|
||||||
|
object_class_property_add_str(oc, "chardev",
|
||||||
|
qtest_get_chardev, qtest_set_chardev);
|
||||||
|
object_class_property_add_str(oc, "log",
|
||||||
|
qtest_get_log, qtest_set_log);
|
||||||
|
}
|
||||||
|
|
||||||
|
static const TypeInfo qtest_info = {
|
||||||
|
.name = TYPE_QTEST,
|
||||||
|
.parent = TYPE_OBJECT,
|
||||||
|
.class_init = qtest_class_init,
|
||||||
|
.instance_size = sizeof(QTest),
|
||||||
|
.interfaces = (InterfaceInfo[]) {
|
||||||
|
{ TYPE_USER_CREATABLE },
|
||||||
|
{ }
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
static void register_types(void)
|
||||||
|
{
|
||||||
|
type_register_static(&qtest_info);
|
||||||
|
}
|
||||||
|
|
||||||
|
type_init(register_types);
|
||||||
|
|
|
@ -1758,8 +1758,9 @@ static bool object_create_early(const char *type)
|
||||||
* add one, state the reason in a comment!
|
* add one, state the reason in a comment!
|
||||||
*/
|
*/
|
||||||
|
|
||||||
/* Reason: rng-egd property "chardev" */
|
/* Reason: property "chardev" */
|
||||||
if (g_str_equal(type, "rng-egd")) {
|
if (g_str_equal(type, "rng-egd") ||
|
||||||
|
g_str_equal(type, "qtest")) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue