mirror of
https://gitlab.com/qemu-project/qemu
synced 2024-09-06 18:05:37 +00:00
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',
|
||||
'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:
|
||||
#
|
||||
|
@ -769,6 +784,7 @@
|
|||
'memory-backend-ram',
|
||||
'pef-guest',
|
||||
'pr-manager-helper',
|
||||
'qtest',
|
||||
'rng-builtin',
|
||||
'rng-egd',
|
||||
'rng-random',
|
||||
|
@ -825,6 +841,7 @@
|
|||
'if': 'defined(CONFIG_LINUX)' },
|
||||
'memory-backend-ram': 'MemoryBackendProperties',
|
||||
'pr-manager-helper': 'PrManagerHelperProperties',
|
||||
'qtest': 'QtestProperties',
|
||||
'rng-builtin': 'RngProperties',
|
||||
'rng-egd': 'RngEgdProperties',
|
||||
'rng-random': 'RngRandomProperties',
|
||||
|
|
185
softmmu/qtest.c
185
softmmu/qtest.c
|
@ -27,6 +27,8 @@
|
|||
#include "qemu/error-report.h"
|
||||
#include "qemu/module.h"
|
||||
#include "qemu/cutils.h"
|
||||
#include "qapi/qmp/qerror.h"
|
||||
#include "qom/object_interfaces.h"
|
||||
#include CONFIG_DEVICES
|
||||
#ifdef CONFIG_PSERIES
|
||||
#include "hw/ppc/spapr_rtas.h"
|
||||
|
@ -34,11 +36,25 @@
|
|||
|
||||
#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;
|
||||
|
||||
static DeviceState *irq_intercept_dev;
|
||||
static FILE *qtest_log_fp;
|
||||
static CharBackend qtest_chr;
|
||||
static QTest *qtest;
|
||||
static GString *inbuf;
|
||||
static int irq_levels[MAX_IRQ];
|
||||
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);
|
||||
|
||||
if (irq_levels[n] != level) {
|
||||
CharBackend *chr = &qtest_chr;
|
||||
CharBackend *chr = &qtest->qtest_chr;
|
||||
irq_levels[n] = level;
|
||||
qtest_send_prefix(chr);
|
||||
qtest_sendf(chr, "IRQ %s %d\n",
|
||||
|
@ -849,18 +865,39 @@ static void qtest_event(void *opaque, QEMUChrEvent event)
|
|||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void qtest_server_init(const char *qtest_chrdev, const char *qtest_log, Error **errp)
|
||||
{
|
||||
ERRP_GUARD();
|
||||
Chardev *chr;
|
||||
Object *qtest;
|
||||
|
||||
chr = qemu_chr_new("qtest", qtest_chrdev, NULL);
|
||||
|
||||
if (chr == NULL) {
|
||||
error_setg(errp, "Failed to initialize device for qtest: \"%s\"",
|
||||
qtest_chrdev);
|
||||
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 (strcmp(qtest_log, "none") != 0) {
|
||||
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;
|
||||
}
|
||||
|
||||
qemu_chr_fe_init(&qtest_chr, chr, errp);
|
||||
qemu_chr_fe_set_handlers(&qtest_chr, qtest_can_read, qtest_read,
|
||||
qtest_event, NULL, &qtest_chr, NULL, true);
|
||||
qemu_chr_fe_set_echo(&qtest_chr, true);
|
||||
if (!qemu_chr_fe_init(&q->qtest_chr, chr, errp)) {
|
||||
return false;
|
||||
}
|
||||
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("");
|
||||
|
||||
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*),
|
||||
|
@ -890,7 +931,7 @@ void qtest_server_set_send_handler(void (*send)(void*, const char*),
|
|||
|
||||
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)
|
||||
|
@ -905,3 +946,129 @@ void qtest_server_inproc_recv(void *dummy, const char *buf)
|
|||
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!
|
||||
*/
|
||||
|
||||
/* Reason: rng-egd property "chardev" */
|
||||
if (g_str_equal(type, "rng-egd")) {
|
||||
/* Reason: property "chardev" */
|
||||
if (g_str_equal(type, "rng-egd") ||
|
||||
g_str_equal(type, "qtest")) {
|
||||
return false;
|
||||
}
|
||||
|
||||
|
|
Loading…
Reference in a new issue