From: Stefan Hajnoczi <stefanha@redhat.com>
To: qemu-devel@nongnu.org
Cc: "Dr. David Alan Gilbert" <dave@treblig.org>,
"Eduardo Habkost" <eduardo@habkost.net>,
pbonzini@redhat.com, "Markus Armbruster" <armbru@redhat.com>,
"Eric Blake" <eblake@redhat.com>,
kwolf@redhat.com, "Maxim Levitsky" <mlevitsk@redhat.com>,
"Daniel P. Berrangé" <berrange@redhat.com>,
"Stefan Hajnoczi" <stefanha@redhat.com>
Subject: [RFC 3/3] qmp: make qmp_device_add() a coroutine
Date: Wed, 6 Sep 2023 15:01:41 -0400 [thread overview]
Message-ID: <20230906190141.1286893-4-stefanha@redhat.com> (raw)
In-Reply-To: <20230906190141.1286893-1-stefanha@redhat.com>
It is not safe to call drain_call_rcu() from qmp_device_add() because
some call stacks are not prepared for drain_call_rcu() to drop the Big
QEMU Lock (BQL).
For example, device emulation code is protected by the BQL but when it
calls aio_poll() -> ... -> qmp_device_add() -> drain_call_rcu() then the
BQL is dropped. See bz#2215192 below for a concrete bug of this type.
Another limitation of drain_call_rcu() is that it cannot be invoked
within an RCU read-side critical section since the reclamation phase
cannot complete until the end of the critical section. Unfortunately,
call stacks have been seen where this happens (see bz#2214985 below).
Switch to call_drain_rcu_co() to avoid these problems. This requires
making qmp_device_add() a coroutine. qdev_device_add() is not designed
to be called from coroutines, so it must be invoked from a BH and then
switch back to the coroutine.
Fixes: 7bed89958bfbf40df9ca681cefbdca63abdde39d ("device_core: use drain_call_rcu in in qmp_device_add")
Buglink: https://bugzilla.redhat.com/show_bug.cgi?id=2215192
Buglink: https://bugzilla.redhat.com/show_bug.cgi?id=2214985
Signed-off-by: Stefan Hajnoczi <stefanha@redhat.com>
---
qapi/qdev.json | 1 +
include/monitor/qdev.h | 3 ++-
monitor/qmp-cmds.c | 2 +-
softmmu/qdev-monitor.c | 34 ++++++++++++++++++++++++++++++----
hmp-commands.hx | 1 +
5 files changed, 35 insertions(+), 6 deletions(-)
diff --git a/qapi/qdev.json b/qapi/qdev.json
index 6bc5a733b8..78e9d7f7b8 100644
--- a/qapi/qdev.json
+++ b/qapi/qdev.json
@@ -79,6 +79,7 @@
##
{ 'command': 'device_add',
'data': {'driver': 'str', '*bus': 'str', '*id': 'str'},
+ 'coroutine': true,
'gen': false, # so we can get the additional arguments
'features': ['json-cli', 'json-cli-hotplug'] }
diff --git a/include/monitor/qdev.h b/include/monitor/qdev.h
index 1d57bf6577..1fed9eb9ea 100644
--- a/include/monitor/qdev.h
+++ b/include/monitor/qdev.h
@@ -5,7 +5,8 @@
void hmp_info_qtree(Monitor *mon, const QDict *qdict);
void hmp_info_qdm(Monitor *mon, const QDict *qdict);
-void qmp_device_add(QDict *qdict, QObject **ret_data, Error **errp);
+void coroutine_fn
+qmp_device_add(QDict *qdict, QObject **ret_data, Error **errp);
int qdev_device_help(QemuOpts *opts);
DeviceState *qdev_device_add(QemuOpts *opts, Error **errp);
diff --git a/monitor/qmp-cmds.c b/monitor/qmp-cmds.c
index b0f948d337..a7419226fe 100644
--- a/monitor/qmp-cmds.c
+++ b/monitor/qmp-cmds.c
@@ -202,7 +202,7 @@ static void __attribute__((__constructor__)) monitor_init_qmp_commands(void)
qmp_init_marshal(&qmp_commands);
qmp_register_command(&qmp_commands, "device_add",
- qmp_device_add, 0, 0);
+ qmp_device_add, QCO_COROUTINE, 0);
QTAILQ_INIT(&qmp_cap_negotiation_commands);
qmp_register_command(&qmp_cap_negotiation_commands, "qmp_capabilities",
diff --git a/softmmu/qdev-monitor.c b/softmmu/qdev-monitor.c
index 74f4e41338..85ae62f7cf 100644
--- a/softmmu/qdev-monitor.c
+++ b/softmmu/qdev-monitor.c
@@ -839,8 +839,28 @@ void hmp_info_qdm(Monitor *mon, const QDict *qdict)
qdev_print_devinfos(true);
}
-void qmp_device_add(QDict *qdict, QObject **ret_data, Error **errp)
+typedef struct {
+ Coroutine *co;
+ QemuOpts *opts;
+ Error **errp;
+ DeviceState *dev;
+} QmpDeviceAdd;
+
+static void qmp_device_add_bh(void *opaque)
{
+ QmpDeviceAdd *data = opaque;
+
+ data->dev = qdev_device_add(data->opts, data->errp);
+ aio_co_wake(data->co);
+}
+
+void coroutine_fn
+qmp_device_add(QDict *qdict, QObject **ret_data, Error **errp)
+{
+ QmpDeviceAdd data = {
+ .co = qemu_coroutine_self(),
+ .errp = errp,
+ };
QemuOpts *opts;
DeviceState *dev;
@@ -852,7 +872,13 @@ void qmp_device_add(QDict *qdict, QObject **ret_data, Error **errp)
qemu_opts_del(opts);
return;
}
- dev = qdev_device_add(opts, errp);
+
+ /* Perform qdev_device_add() call outside coroutine context */
+ data.opts = opts;
+ aio_bh_schedule_oneshot(qemu_coroutine_get_aio_context(data.co),
+ qmp_device_add_bh, &data);
+ qemu_coroutine_yield();
+ dev = data.dev;
/*
* Drain all pending RCU callbacks. This is done because
@@ -863,7 +889,7 @@ void qmp_device_add(QDict *qdict, QObject **ret_data, Error **errp)
* will finish its job completely once qmp command returns result
* to the user
*/
- drain_call_rcu();
+ drain_call_rcu_co();
if (!dev) {
qemu_opts_del(opts);
@@ -956,7 +982,7 @@ void qmp_device_del(const char *id, Error **errp)
}
}
-void hmp_device_add(Monitor *mon, const QDict *qdict)
+void coroutine_fn hmp_device_add(Monitor *mon, const QDict *qdict)
{
Error *err = NULL;
diff --git a/hmp-commands.hx b/hmp-commands.hx
index 2cbd0f77a0..c737d1fd64 100644
--- a/hmp-commands.hx
+++ b/hmp-commands.hx
@@ -695,6 +695,7 @@ ERST
.params = "driver[,prop=value][,...]",
.help = "add device, like -device on the command line",
.cmd = hmp_device_add,
+ .coroutine = true,
.command_completion = device_add_completion,
},
--
2.41.0
next prev parent reply other threads:[~2023-09-06 19:02 UTC|newest]
Thread overview: 20+ messages / expand[flat|nested] mbox.gz Atom feed top
2023-09-06 19:01 [RFC 0/3] qmp: make qmp_device_add() a coroutine Stefan Hajnoczi
2023-09-06 19:01 ` [RFC 1/3] hmp: avoid the nested event loop in handle_hmp_command() Stefan Hajnoczi
2023-09-07 1:06 ` Dr. David Alan Gilbert
2023-09-07 14:04 ` Stefan Hajnoczi
2023-09-07 14:07 ` Dr. David Alan Gilbert
2023-09-07 15:34 ` Stefan Hajnoczi
2023-09-07 20:53 ` Dr. David Alan Gilbert
2023-09-07 21:25 ` Stefan Hajnoczi
2023-09-06 19:01 ` [RFC 2/3] rcu: add drain_call_rcu_co() API Stefan Hajnoczi
2023-09-12 16:36 ` Kevin Wolf
2023-09-12 16:52 ` Stefan Hajnoczi
2023-09-06 19:01 ` Stefan Hajnoczi [this message]
2023-09-12 16:47 ` [RFC 3/3] qmp: make qmp_device_add() a coroutine Kevin Wolf
2023-09-12 17:04 ` Stefan Hajnoczi
2023-09-07 11:28 ` [RFC 0/3] " Paolo Bonzini
2023-09-07 14:00 ` Stefan Hajnoczi
2023-09-07 14:25 ` Paolo Bonzini
2023-09-07 15:29 ` Stefan Hajnoczi
2023-09-12 17:08 ` Kevin Wolf
2023-09-13 11:38 ` Paolo Bonzini
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
Avoid top-posting and favor interleaved quoting:
https://en.wikipedia.org/wiki/Posting_style#Interleaved_style
* Reply using the --to, --cc, and --in-reply-to
switches of git-send-email(1):
git send-email \
--in-reply-to=20230906190141.1286893-4-stefanha@redhat.com \
--to=stefanha@redhat.com \
--cc=armbru@redhat.com \
--cc=berrange@redhat.com \
--cc=dave@treblig.org \
--cc=eblake@redhat.com \
--cc=eduardo@habkost.net \
--cc=kwolf@redhat.com \
--cc=mlevitsk@redhat.com \
--cc=pbonzini@redhat.com \
--cc=qemu-devel@nongnu.org \
/path/to/YOUR_REPLY
https://kernel.org/pub/software/scm/git/docs/git-send-email.html
* If your mail client supports setting the In-Reply-To header
via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line
before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for NNTP newsgroup(s).