All of lore.kernel.org
 help / color / mirror / Atom feed
* [PATCH v6 00/34] monitor: turn QMP and HMP into QOM objects
@ 2026-07-02 16:03 Daniel P. Berrangé
  2026-07-02 16:03 ` [PATCH v6 01/34] qom: replace 'can_be_deleted' with 'prepare_delete' Daniel P. Berrangé
                   ` (33 more replies)
  0 siblings, 34 replies; 49+ messages in thread
From: Daniel P. Berrangé @ 2026-07-02 16:03 UTC (permalink / raw)
  To: qemu-devel
  Cc: Paolo Bonzini, Dr. David Alan Gilbert, Markus Armbruster,
	Marc-André Lureau, Alex Bennée,
	Philippe Mathieu-Daudé, Christian Brauner, devel,
	Daniel P. Berrangé

Conceptually -object and object_add/object_del should be sufficient
for essentially all QEMU configuration....if only we ported all our
internal custom backends/devices/etc to QOM. That is of course a big
job which is why it hasn't happened.

This series started with the premise that the monitor is one of the
easier areas to convert since we have no more than three classes,
a common base, and QMP and HMP subclasses[1]. So why not give it a
go and thus unlock the ability to dynamically create/delete monitors
in QMP/HMP.

This series does the conversion in a great many small steps to better
understand the implications at each stage.

The high level outcome of this series is

 * HMP and QMP monitors are QOM objects, 'monitor-hmp' and
   'monitor-qmp' respectively

 * Both can be cold plugged and hot plugged. QMP only, can
   also be hot unplugged.

 * '-mon' is obsolete, deprecated and replaced by '-object',
   but -monitor, -qmp and kept  as high level syntax sugar

 * QMP gains a concept of "close-action" which makes it
   possible to mark a monitor for auto-delete.

The monitor hot-unplug code and the qtest and functional testing
code is heavily derived from a series sent by Christian Brauner
which proposed new monitor_add/monitor_del commands:

  https://lists.nongnu.org/archive/html/qemu-devel/2026-04/msg01349.html

I left Christian's authorship & SoB on the patches which were
derived from his code, though the code has been refactored quite
a bit in places, so bugs are quite possibly my own.

Note that Christian's series allowed the use of "monitor_del"
commands against the current monitor session. ie a client could
delete the very monitor it was using. This is an awkward concept
as it needs special casing to delay the deletion to happen in
the background, such that that the QMP response to 'monitor_del'
could still be sent back. This also left the chardev was orphaned
as there's no way to run 'chardev_dev' in that usage pattern.

To provide an alternative mechanism to address the same use case,
this series introduces the 'close-action' concept mentioned above,
that allows hotplugging a monitor to service a specific task,
with the monitor being purged when the script closes its connection.
This avoids the special casing that an explicit "self deletion"
paradigm required.

While supporting hotplug of HMP was trivial, I didn't do any work
to think about hotunplug of HMP, since IMHO it is of limited value
given HMP's typical use cases.

[1] ~~~ we did all this not because it was easy,
        but because we thought it would be easy ~~~

Changed in v6:

 - Present "ID" from QemuOpts to use for the QOM object
 - Use 'compat_monitorNNN' as  ID if QemuOpts lacks an
   ID, for compat with -monitor/-qmp arg naming
 - Mark the Monitor class abstract when first defined
 - Tweaks to formatting of QAPI docs
 - Expand CLI docs to describe relation between -monitor
   and -qmp and -object.
 - Present object ID in error messages for prepare_delete
   callback
 - Drop API which dupes object_get_canonical_path_component
 - Dropped R-B on patches 01, 05, 34

Changed in v5:

 - Address another race when looking up monitor by ID
 - Refer to "line editting" when talking about HMP
   readline feature
 - Misc typos
 - Fix mis-placed setting of 'delete_pending'
 - Add test case to validate reconnecting to QMP

Changed in v4:

 - Hold reference into BH to avoid race with auto-delete

Changed in v3:

 - Removed unused 'dead' struct field
 - Make use of 'setup_pending' struct field across BH
 - Add missing free of chardev_id
 - Add missnig ERRP_GUARD in monitor_new_qmp
 - Misc docs typos / rephrasing
 - Moved docs patch to the end
 - Fixed docs about the default QOM ID naming for legacy
   monitor syntax
 - Fixed random sleep time calculation in tests

Christian Brauner (6):
  monitor: convert from oneshot BH to persistent BH
  monitor: reject attempts to delete the current monitor
  monitor: protect qemu_chr_fe_accept_input with monitor lock
  monitor: implement support for deleting QMP objects
  tests/qtest: add tests for dynamic monitor add/remove
  tests/functional: add e2e test for dynamic QMP monitor hotplug

Daniel P. Berrangé (28):
  qom: replace 'can_be_deleted' with 'prepare_delete'
  monitor: replace 'common' with 'parent_obj' in MonitorHMP
  monitor: replace 'common' with 'parent_obj' in MonitorQMP
  monitor: rename monitor_init* to monitor_new*
  monitor: minimal conversion of monitors to QOM
  monitor: add 'chardev' property to Monitor base class
  monitor: add 'readline' property to HMP Monitor class
  monitor: add 'pretty' property to QMP Monitor class
  monitor: remove 'skip_flush' field
  monitor: move monitor_data_(init|destroy) into QOM init/finalize
  monitor: use class methods for monitor_vprintf
  monitor: use class methods for monitor_qapi_event_emit
  monitor: use class methods for monitor_accept_input
  monitor: use class method for I/O thread request
  monitor: use dynamic cast in monitor_qmp_requests_pop_any_with_lock
  util: use dynamic cast in error vreport
  monitor: drop unused monitor_cur_is_qmp
  monitor: use dynamic cast in QMP commands
  monitor: use dynamic cast in monitor_is_hmp_non_interactive
  monitor: drop unused monitor_is_qmp method
  monitor: eliminate monitor_is_hmp_non_interactive method
  monitor: implement "user creatable" interface for adding monitors
  tests/functional: add a stress test for monitor hot unplug
  qom: add trace events for user creatable create/delete APIs
  monitor: add support for auto-deleting monitors upon close
  tests: switch from -mon to -object monitor-qmp
  qemu-options: document new monitor-hmp and monitor-qmp objects
  docs: mark '-mon' as deprecated in favour of -object

 MAINTAINERS                                   |   1 +
 backends/cryptodev.c                          |  11 +-
 backends/hostmem.c                            |   6 +-
 backends/iommufd.c                            |  11 +-
 block/throttle-groups.c                       |  11 +-
 chardev/char.c                                |   3 +-
 docs/about/deprecated.rst                     |  10 +
 docs/devel/writing-monitor-commands.rst       |   4 +-
 docs/system/arm/xenpvh.rst                    |   4 +-
 docs/system/i386/xen.rst                      |   3 +-
 docs/system/i386/xenpvh.rst                   |   4 +-
 event-loop-base.c                             |   8 +-
 gdbstub/system.c                              |   4 +-
 include/monitor/monitor.h                     |  26 +-
 include/qom/object_interfaces.h               |  26 +-
 include/system/event-loop-base.h              |   2 +-
 migration/migration-hmp-cmds.c                |   5 +-
 monitor/hmp-cmds.c                            |   7 +-
 monitor/hmp.c                                 | 181 ++++++++--
 monitor/monitor-internal.h                    |  73 ++--
 monitor/monitor.c                             | 271 ++++++++-------
 monitor/qmp-cmds-control.c                    |  12 +-
 monitor/qmp-cmds.c                            |  14 +-
 monitor/qmp.c                                 | 323 +++++++++++++++---
 net/can/can_core.c                            |   6 +-
 python/qemu/machine/machine.py                |   4 +-
 qapi/qom.json                                 |  63 ++++
 qemu-options.hx                               |  65 +++-
 qom/object_interfaces.c                       |  21 +-
 qom/trace-events                              |   5 +
 storage-daemon/qemu-storage-daemon.c          |   2 +-
 stubs/monitor-core.c                          |   5 -
 stubs/monitor-internal.c                      |   4 +-
 system/vl.c                                   |  19 +-
 tests/functional/generic/meson.build          |   1 +
 .../generic/test_monitor_hotplug.py           | 305 +++++++++++++++++
 tests/qemu-iotests/245                        |   4 +-
 tests/qtest/libqtest.c                        |   2 +-
 tests/qtest/qmp-test.c                        | 174 ++++++++++
 tests/unit/test-util-sockets.c                |   1 -
 tools/qemu-vnc/stubs.c                        |   5 -
 ui/ui-hmp-cmds.c                              |   2 +-
 util/error-report.c                           |  13 +-
 util/main-loop.c                              |   6 +-
 44 files changed, 1394 insertions(+), 333 deletions(-)
 create mode 100755 tests/functional/generic/test_monitor_hotplug.py

-- 
2.54.0



^ permalink raw reply	[flat|nested] 49+ messages in thread

* [PATCH v6 01/34] qom: replace 'can_be_deleted' with 'prepare_delete'
  2026-07-02 16:03 [PATCH v6 00/34] monitor: turn QMP and HMP into QOM objects Daniel P. Berrangé
@ 2026-07-02 16:03 ` Daniel P. Berrangé
  2026-07-03  6:40   ` Markus Armbruster
  2026-07-02 16:03 ` [PATCH v6 02/34] monitor: replace 'common' with 'parent_obj' in MonitorHMP Daniel P. Berrangé
                   ` (32 subsequent siblings)
  33 siblings, 1 reply; 49+ messages in thread
From: Daniel P. Berrangé @ 2026-07-02 16:03 UTC (permalink / raw)
  To: qemu-devel
  Cc: Paolo Bonzini, Dr. David Alan Gilbert, Markus Armbruster,
	Marc-André Lureau, Alex Bennée,
	Philippe Mathieu-Daudé, Christian Brauner, devel,
	Daniel P. Berrangé

While most objects can perform all their cleanup in the finalizer
method, there can be interactions with other resources / subsystems
/ threads which require that some cleanup be performed on an user
creatable object before unparenting it and entering finalization.

The current 'can_be_deleted' method runs in the deletion path and
is intended to be used to block deletion. While it could be used
to perform cleanup tasks, its name suggests it should be free of
side-effects.

Generalize this by renaming it to 'prepare_delete', explicitly
allowing for cleanup to be provided. Existing users of 'can_be_deleted'
are re-written, which provides them with more detailed/tailored error
messages.

Signed-off-by: Daniel P. Berrangé <berrange@redhat.com>
---
 backends/cryptodev.c             | 11 ++++++++---
 backends/hostmem.c               |  6 ++++--
 backends/iommufd.c               | 11 ++++++++---
 block/throttle-groups.c          | 11 ++++++++---
 event-loop-base.c                |  8 ++++----
 include/qom/object_interfaces.h  | 26 +++++++++++++++++---------
 include/system/event-loop-base.h |  2 +-
 net/can/can_core.c               |  6 ++++--
 qom/object_interfaces.c          | 14 ++++++--------
 tests/qemu-iotests/245           |  4 ++--
 util/main-loop.c                 |  6 ++++--
 11 files changed, 66 insertions(+), 39 deletions(-)

diff --git a/backends/cryptodev.c b/backends/cryptodev.c
index 79f8882d3b..9e8e40ffc9 100644
--- a/backends/cryptodev.c
+++ b/backends/cryptodev.c
@@ -454,9 +454,14 @@ bool cryptodev_backend_is_ready(CryptoDevBackend *backend)
 }
 
 static bool
-cryptodev_backend_can_be_deleted(UserCreatable *uc)
+cryptodev_backend_prepare_delete(UserCreatable *uc, Error **errp)
 {
-    return !cryptodev_backend_is_used(CRYPTODEV_BACKEND(uc));
+    if (cryptodev_backend_is_used(CRYPTODEV_BACKEND(uc))) {
+        error_setg(errp, "Cryptodev backend '%s' is still in use",
+                   object_get_canonical_path_component(OBJECT(uc)));
+        return false;
+    }
+    return true;
 }
 
 static void cryptodev_backend_instance_init(Object *obj)
@@ -613,7 +618,7 @@ cryptodev_backend_class_init(ObjectClass *oc, const void *data)
     UserCreatableClass *ucc = USER_CREATABLE_CLASS(oc);
 
     ucc->complete = cryptodev_backend_complete;
-    ucc->can_be_deleted = cryptodev_backend_can_be_deleted;
+    ucc->prepare_delete = cryptodev_backend_prepare_delete;
 
     QTAILQ_INIT(&crypto_clients);
     object_class_property_add(oc, "queues", "uint32",
diff --git a/backends/hostmem.c b/backends/hostmem.c
index cd2085fb3c..7a79e69ab0 100644
--- a/backends/hostmem.c
+++ b/backends/hostmem.c
@@ -434,9 +434,11 @@ host_memory_backend_memory_complete(UserCreatable *uc, Error **errp)
 }
 
 static bool
-host_memory_backend_can_be_deleted(UserCreatable *uc)
+host_memory_backend_prepare_delete(UserCreatable *uc, Error **errp)
 {
     if (host_memory_backend_is_mapped(MEMORY_BACKEND(uc))) {
+        error_setg(errp, "Host memory backend '%s' is still mapped",
+                   object_get_canonical_path_component(OBJECT(uc)));
         return false;
     } else {
         return true;
@@ -508,7 +510,7 @@ host_memory_backend_class_init(ObjectClass *oc, const void *data)
     UserCreatableClass *ucc = USER_CREATABLE_CLASS(oc);
 
     ucc->complete = host_memory_backend_memory_complete;
-    ucc->can_be_deleted = host_memory_backend_can_be_deleted;
+    ucc->prepare_delete = host_memory_backend_prepare_delete;
 
     object_class_property_add_bool(oc, "merge",
         host_memory_backend_get_merge,
diff --git a/backends/iommufd.c b/backends/iommufd.c
index 09624cd652..a7fc0d5930 100644
--- a/backends/iommufd.c
+++ b/backends/iommufd.c
@@ -63,11 +63,16 @@ static void iommufd_backend_set_fd(Object *obj, const char *str, Error **errp)
     trace_iommu_backend_set_fd(be->fd);
 }
 
-static bool iommufd_backend_can_be_deleted(UserCreatable *uc)
+static bool iommufd_backend_prepare_delete(UserCreatable *uc, Error **errp)
 {
     IOMMUFDBackend *be = IOMMUFD_BACKEND(uc);
 
-    return !be->users;
+    if (be->users) {
+        error_setg(errp, "IOMMUFD backend '%s' still has %d users",
+                   object_get_canonical_path_component(OBJECT(uc)), be->users);
+        return false;
+    }
+    return true;
 }
 
 static void iommufd_backend_complete(UserCreatable *uc, Error **errp)
@@ -92,7 +97,7 @@ static void iommufd_backend_class_init(ObjectClass *oc, const void *data)
 {
     UserCreatableClass *ucc = USER_CREATABLE_CLASS(oc);
 
-    ucc->can_be_deleted = iommufd_backend_can_be_deleted;
+    ucc->prepare_delete = iommufd_backend_prepare_delete;
     ucc->complete = iommufd_backend_complete;
 
     object_class_property_add_str(oc, "fd", NULL, iommufd_backend_set_fd);
diff --git a/block/throttle-groups.c b/block/throttle-groups.c
index 4b1b1944c2..9515600ed8 100644
--- a/block/throttle-groups.c
+++ b/block/throttle-groups.c
@@ -960,9 +960,14 @@ static void throttle_group_get_limits(Object *obj, Visitor *v,
     visit_type_ThrottleLimits(v, name, &argp, errp);
 }
 
-static bool throttle_group_can_be_deleted(UserCreatable *uc)
+static bool throttle_group_prepare_delete(UserCreatable *uc, Error **errp)
 {
-    return OBJECT(uc)->ref == 1;
+    if (OBJECT(uc)->ref > 1) {
+        error_setg(errp, "Throttle group '%s' still has multiple references",
+                   object_get_canonical_path_component(OBJECT(uc)));
+        return false;
+    }
+    return true;
 }
 
 static void throttle_group_obj_class_init(ObjectClass *klass,
@@ -972,7 +977,7 @@ static void throttle_group_obj_class_init(ObjectClass *klass,
     UserCreatableClass *ucc = USER_CREATABLE_CLASS(klass);
 
     ucc->complete = throttle_group_obj_complete;
-    ucc->can_be_deleted = throttle_group_can_be_deleted;
+    ucc->prepare_delete = throttle_group_prepare_delete;
 
     /* individual properties */
     for (i = 0; i < sizeof(properties) / sizeof(ThrottleParamInfo); i++) {
diff --git a/event-loop-base.c b/event-loop-base.c
index 8ca143bea4..ee5987ddbf 100644
--- a/event-loop-base.c
+++ b/event-loop-base.c
@@ -85,13 +85,13 @@ static void event_loop_base_complete(UserCreatable *uc, Error **errp)
     }
 }
 
-static bool event_loop_base_can_be_deleted(UserCreatable *uc)
+static bool event_loop_base_prepare_delete(UserCreatable *uc, Error **errp)
 {
     EventLoopBaseClass *bc = EVENT_LOOP_BASE_GET_CLASS(uc);
     EventLoopBase *backend = EVENT_LOOP_BASE(uc);
 
-    if (bc->can_be_deleted) {
-        return bc->can_be_deleted(backend);
+    if (bc->prepare_delete) {
+        return bc->prepare_delete(backend, errp);
     }
 
     return true;
@@ -102,7 +102,7 @@ static void event_loop_base_class_init(ObjectClass *klass,
 {
     UserCreatableClass *ucc = USER_CREATABLE_CLASS(klass);
     ucc->complete = event_loop_base_complete;
-    ucc->can_be_deleted = event_loop_base_can_be_deleted;
+    ucc->prepare_delete = event_loop_base_prepare_delete;
 
     object_class_property_add(klass, "aio-max-batch", "int",
                               event_loop_base_get_param,
diff --git a/include/qom/object_interfaces.h b/include/qom/object_interfaces.h
index e2b8615617..afd0fb93b2 100644
--- a/include/qom/object_interfaces.h
+++ b/include/qom/object_interfaces.h
@@ -20,8 +20,10 @@ typedef struct UserCreatable UserCreatable;
  * UserCreatableClass:
  * @parent_class: the base class
  * @complete: callback to be called after @obj's properties are set.
- * @can_be_deleted: callback to be called before an object is removed
- * to check if @obj can be removed safely.
+ * @prepare_delete: to be called before an attempt to delete @obj
+ * to validate whether the object can be deleted and trigger any
+ * cleanup of any resources which have to be dealt with before the
+ * object is unparented and enters finalization.
  *
  * Interface is designed to work with -object/object-add/object_add
  * commands.
@@ -36,7 +38,9 @@ typedef struct UserCreatable UserCreatable;
  * For objects created without using -object/object-add/object_add,
  * @user_creatable_complete() wrapper should be called manually if
  * object's type implements USER_CREATABLE interface and needs
- * complete() callback to be called.
+ * complete() callback to be called. Similarly @user_creatable_prepare_delete()
+ * should be called manually prior to an attempt to the delete the
+ * object.
  */
 struct UserCreatableClass {
     /* <private> */
@@ -44,7 +48,7 @@ struct UserCreatableClass {
 
     /* <public> */
     void (*complete)(UserCreatable *uc, Error **errp);
-    bool (*can_be_deleted)(UserCreatable *uc);
+    bool (*prepare_delete)(UserCreatable *uc, Error **errp);
 };
 
 /**
@@ -61,13 +65,17 @@ struct UserCreatableClass {
 bool user_creatable_complete(UserCreatable *uc, Error **errp);
 
 /**
- * user_creatable_can_be_deleted:
- * @uc: the object whose can_be_deleted() method is called if implemented
+ * user_creatable_prepare_delete:
+ * @uc: the user-creatable object whose prepare_delete() method is called
+ * @errp: if an error occurs, a pointer to an area to store the error
+ *
+ * Wrapper to call prepare_delete() class method if defined, otherwise
+ * does nothing.
  *
- * Wrapper to call can_be_deleted() method if one of types it's inherited
- * from implements USER_CREATABLE interface.
+ * Returns: %true on success or if prepare_delete() is not defined,
+ *          %false on failure.
  */
-bool user_creatable_can_be_deleted(UserCreatable *uc);
+bool user_creatable_prepare_delete(UserCreatable *uc, Error **errp);
 
 /**
  * user_creatable_add_qapi:
diff --git a/include/system/event-loop-base.h b/include/system/event-loop-base.h
index 130629e7f3..1e1e427ac7 100644
--- a/include/system/event-loop-base.h
+++ b/include/system/event-loop-base.h
@@ -24,7 +24,7 @@ struct EventLoopBaseClass {
 
     void (*init)(EventLoopBase *base, Error **errp);
     void (*update_params)(EventLoopBase *base, Error **errp);
-    bool (*can_be_deleted)(EventLoopBase *base);
+    bool (*prepare_delete)(EventLoopBase *base, Error **errp);
 };
 
 struct EventLoopBase {
diff --git a/net/can/can_core.c b/net/can/can_core.c
index 77fe2b8ba4..6a56b33251 100644
--- a/net/can/can_core.c
+++ b/net/can/can_core.c
@@ -143,8 +143,10 @@ int can_bus_client_set_filters(CanBusClientState *client,
 }
 
 
-static bool can_bus_can_be_deleted(UserCreatable *uc)
+static bool can_bus_prepare_delete(UserCreatable *uc, Error **errp)
 {
+    error_setg(errp, "Deleting CAN bus device '%s' is not supported",
+               object_get_canonical_path_component(OBJECT(uc)));
     return false;
 }
 
@@ -153,7 +155,7 @@ static void can_bus_class_init(ObjectClass *klass,
 {
     UserCreatableClass *uc_klass = USER_CREATABLE_CLASS(klass);
 
-    uc_klass->can_be_deleted = can_bus_can_be_deleted;
+    uc_klass->prepare_delete = can_bus_prepare_delete;
 }
 
 static const TypeInfo can_bus_info = {
diff --git a/qom/object_interfaces.c b/qom/object_interfaces.c
index 7080f85f95..ac992aa786 100644
--- a/qom/object_interfaces.c
+++ b/qom/object_interfaces.c
@@ -32,16 +32,15 @@ bool user_creatable_complete(UserCreatable *uc, Error **errp)
     return !*errp;
 }
 
-bool user_creatable_can_be_deleted(UserCreatable *uc)
+bool user_creatable_prepare_delete(UserCreatable *uc, Error **errp)
 {
-
     UserCreatableClass *ucc = USER_CREATABLE_GET_CLASS(uc);
+    ERRP_GUARD();
 
-    if (ucc->can_be_deleted) {
-        return ucc->can_be_deleted(uc);
-    } else {
-        return true;
+    if (ucc->prepare_delete) {
+        return ucc->prepare_delete(uc, errp);
     }
+    return true;
 }
 
 void user_creatable_add_qapi(ObjectOptions *options, Error **errp)
@@ -253,8 +252,7 @@ bool user_creatable_del(const char *id, Error **errp)
         return false;
     }
 
-    if (!user_creatable_can_be_deleted(USER_CREATABLE(obj))) {
-        error_setg(errp, "object '%s' is in use, can not be deleted", id);
+    if (!user_creatable_prepare_delete(USER_CREATABLE(obj), errp)) {
         return false;
     }
 
diff --git a/tests/qemu-iotests/245 b/tests/qemu-iotests/245
index f96610f510..d6eaf30ed4 100755
--- a/tests/qemu-iotests/245
+++ b/tests/qemu-iotests/245
@@ -801,7 +801,7 @@ class TestBlockdevReopen(iotests.QMPTestCase):
         # Now group1 is in use, it cannot be deleted
         result = self.vm.qmp('object-del', id = 'group1')
         self.assert_qmp(result, 'error/class', 'GenericError')
-        self.assert_qmp(result, 'error/desc', "object 'group1' is in use, can not be deleted")
+        self.assert_qmp(result, 'error/desc', "Throttle group 'group1' still has multiple references")
 
         # Default options, this switches the group back to group0
         self.reopen(opts)
@@ -809,7 +809,7 @@ class TestBlockdevReopen(iotests.QMPTestCase):
         # So now we cannot delete group0
         result = self.vm.qmp('object-del', id = 'group0')
         self.assert_qmp(result, 'error/class', 'GenericError')
-        self.assert_qmp(result, 'error/desc', "object 'group0' is in use, can not be deleted")
+        self.assert_qmp(result, 'error/desc', "Throttle group 'group0' still has multiple references")
 
         # But group1 is free this time, and it can be deleted
         self.vm.cmd('object-del', id = 'group1')
diff --git a/util/main-loop.c b/util/main-loop.c
index ad8645c30a..65e5dbcc94 100644
--- a/util/main-loop.c
+++ b/util/main-loop.c
@@ -218,8 +218,10 @@ static void main_loop_init(EventLoopBase *base, Error **errp)
     mloop = m;
 }
 
-static bool main_loop_can_be_deleted(EventLoopBase *base)
+static bool main_loop_prepare_delete(EventLoopBase *base, Error **errp)
 {
+    error_setg(errp, "Deleting main loop '%s' is not supported",
+               object_get_canonical_path_component(OBJECT(base)));
     return false;
 }
 
@@ -229,7 +231,7 @@ static void main_loop_class_init(ObjectClass *oc, const void *class_data)
 
     bc->init = main_loop_init;
     bc->update_params = main_loop_update_params;
-    bc->can_be_deleted = main_loop_can_be_deleted;
+    bc->prepare_delete = main_loop_prepare_delete;
 }
 
 static const TypeInfo main_loop_info = {
-- 
2.54.0



^ permalink raw reply related	[flat|nested] 49+ messages in thread

* [PATCH v6 02/34] monitor: replace 'common' with 'parent_obj' in MonitorHMP
  2026-07-02 16:03 [PATCH v6 00/34] monitor: turn QMP and HMP into QOM objects Daniel P. Berrangé
  2026-07-02 16:03 ` [PATCH v6 01/34] qom: replace 'can_be_deleted' with 'prepare_delete' Daniel P. Berrangé
@ 2026-07-02 16:03 ` Daniel P. Berrangé
  2026-07-02 16:03 ` [PATCH v6 03/34] monitor: replace 'common' with 'parent_obj' in MonitorQMP Daniel P. Berrangé
                   ` (31 subsequent siblings)
  33 siblings, 0 replies; 49+ messages in thread
From: Daniel P. Berrangé @ 2026-07-02 16:03 UTC (permalink / raw)
  To: qemu-devel
  Cc: Paolo Bonzini, Dr. David Alan Gilbert, Markus Armbruster,
	Marc-André Lureau, Alex Bennée,
	Philippe Mathieu-Daudé, Christian Brauner, devel,
	Daniel P. Berrangé, Peter Krempa

The field name 'parent_obj' is standard practice for QOM structs
so align the HMP monitor.

Reviewed-by: Marc-André Lureau <marcandre.lureau@redhat.com>
Reviewed-by: Dr. David Alan Gilbert <dave@treblig.org>
Tested-by: Peter Krempa <pkrempa@redhat.com>
Signed-off-by: Daniel P. Berrangé <berrange@redhat.com>
---
 monitor/hmp-cmds.c         |  2 +-
 monitor/hmp.c              | 43 ++++++++++++++++++++------------------
 monitor/monitor-internal.h |  2 +-
 monitor/monitor.c          |  6 +++---
 monitor/qmp-cmds.c         | 10 ++++-----
 ui/ui-hmp-cmds.c           |  2 +-
 6 files changed, 34 insertions(+), 31 deletions(-)

diff --git a/monitor/hmp-cmds.c b/monitor/hmp-cmds.c
index 443b8c785d..e139caeba9 100644
--- a/monitor/hmp-cmds.c
+++ b/monitor/hmp-cmds.c
@@ -287,7 +287,7 @@ void hmp_info_sync_profile(Monitor *mon, const QDict *qdict)
 
 void hmp_info_history(Monitor *mon, const QDict *qdict)
 {
-    MonitorHMP *hmp_mon = container_of(mon, MonitorHMP, common);
+    MonitorHMP *hmp_mon = container_of(mon, MonitorHMP, parent_obj);
     int i;
     const char *str;
 
diff --git a/monitor/hmp.c b/monitor/hmp.c
index cc4390486e..3dde52ddbe 100644
--- a/monitor/hmp.c
+++ b/monitor/hmp.c
@@ -48,9 +48,9 @@ static void monitor_command_cb(void *opaque, const char *cmdline,
 {
     MonitorHMP *mon = opaque;
 
-    monitor_suspend(&mon->common);
+    monitor_suspend(&mon->parent_obj);
     handle_hmp_command(mon, cmdline);
-    monitor_resume(&mon->common);
+    monitor_resume(&mon->parent_obj);
 }
 
 void monitor_read_command(MonitorHMP *mon, int show_prompt)
@@ -73,7 +73,7 @@ int monitor_read_password(MonitorHMP *mon, ReadLineFunc *readline_func,
         /* prompt is printed on return from the command handler */
         return 0;
     } else {
-        monitor_printf(&mon->common,
+        monitor_printf(&mon->parent_obj,
                        "terminal does not support password prompting\n");
         return -ENOTTY;
     }
@@ -695,7 +695,7 @@ static const HMPCommand *monitor_parse_command(MonitorHMP *hmp_mon,
                                                const char **cmdp,
                                                HMPCommand *table)
 {
-    Monitor *mon = &hmp_mon->common;
+    Monitor *mon = &hmp_mon->parent_obj;
     const char *p;
     const HMPCommand *cmd;
     char cmdname[256];
@@ -1188,35 +1188,37 @@ void handle_hmp_command(MonitorHMP *mon, const char *cmdline)
 
     if (!cmd->cmd && !cmd->cmd_info_hrt) {
         /* FIXME: is it useful to try autoload modules here ??? */
-        monitor_printf(&mon->common, "Command \"%.*s\" is not available.\n",
+        monitor_printf(&mon->parent_obj, "Command \"%.*s\" is not available.\n",
                        (int)(cmdline - cmd_start), cmd_start);
         return;
     }
 
-    qdict = monitor_parse_arguments(&mon->common, &cmdline, cmd);
+    qdict = monitor_parse_arguments(&mon->parent_obj, &cmdline, cmd);
     if (!qdict) {
         while (cmdline > cmd_start && qemu_isspace(cmdline[-1])) {
             cmdline--;
         }
-        monitor_printf(&mon->common, "Try \"help %.*s\" for more information\n",
+        monitor_printf(&mon->parent_obj,
+                       "Try \"help %.*s\" for more information\n",
                        (int)(cmdline - cmd_start), cmd_start);
         return;
     }
 
     if (!cmd->coroutine) {
         /* old_mon is non-NULL when called from qmp_human_monitor_command() */
-        Monitor *old_mon = monitor_set_cur(qemu_coroutine_self(), &mon->common);
-        handle_hmp_command_exec(&mon->common, cmd, qdict);
+        Monitor *old_mon = monitor_set_cur(qemu_coroutine_self(),
+                                           &mon->parent_obj);
+        handle_hmp_command_exec(&mon->parent_obj, cmd, qdict);
         monitor_set_cur(qemu_coroutine_self(), old_mon);
     } else {
         HandleHmpCommandCo data = {
-            .mon = &mon->common,
+            .mon = &mon->parent_obj,
             .cmd = cmd,
             .qdict = qdict,
             .done = false,
         };
         Coroutine *co = qemu_coroutine_create(handle_hmp_command_co, &data);
-        monitor_set_cur(co, &mon->common);
+        monitor_set_cur(co, &mon->parent_obj);
         aio_co_enter(qemu_get_aio_context(), co);
         AIO_WAIT_WHILE_UNLOCKED(NULL, !data.done);
     }
@@ -1434,7 +1436,7 @@ cleanup:
 
 static void monitor_read(void *opaque, const uint8_t *buf, int size)
 {
-    MonitorHMP *mon = container_of(opaque, MonitorHMP, common);
+    MonitorHMP *mon = container_of(opaque, MonitorHMP, parent_obj);
     int i;
 
     if (mon->rs) {
@@ -1443,7 +1445,7 @@ static void monitor_read(void *opaque, const uint8_t *buf, int size)
         }
     } else {
         if (size == 0 || buf[size - 1] != 0) {
-            monitor_printf(&mon->common, "corrupted command\n");
+            monitor_printf(&mon->parent_obj, "corrupted command\n");
         } else {
             handle_hmp_command(mon, (char *)buf);
         }
@@ -1512,26 +1514,26 @@ static void G_GNUC_PRINTF(2, 3) monitor_readline_printf(void *opaque,
     MonitorHMP *mon = opaque;
     va_list ap;
     va_start(ap, fmt);
-    monitor_vprintf(&mon->common, fmt, ap);
+    monitor_vprintf(&mon->parent_obj, fmt, ap);
     va_end(ap);
 }
 
 static void monitor_readline_flush(void *opaque)
 {
     MonitorHMP *mon = opaque;
-    monitor_flush(&mon->common);
+    monitor_flush(&mon->parent_obj);
 }
 
 void monitor_init_hmp(Chardev *chr, bool use_readline, Error **errp)
 {
     MonitorHMP *mon = g_new0(MonitorHMP, 1);
 
-    if (!qemu_chr_fe_init(&mon->common.chr, chr, errp)) {
+    if (!qemu_chr_fe_init(&mon->parent_obj.chr, chr, errp)) {
         g_free(mon);
         return;
     }
 
-    monitor_data_init(&mon->common, false, false, false);
+    monitor_data_init(&mon->parent_obj, false, false, false);
 
     mon->use_readline = use_readline;
     if (mon->use_readline) {
@@ -1542,9 +1544,10 @@ void monitor_init_hmp(Chardev *chr, bool use_readline, Error **errp)
         monitor_read_command(mon, 0);
     }
 
-    qemu_chr_fe_set_handlers(&mon->common.chr, monitor_can_read, monitor_read,
-                             monitor_event, NULL, &mon->common, NULL, true);
-    monitor_list_append(&mon->common);
+    qemu_chr_fe_set_handlers(&mon->parent_obj.chr,
+                             monitor_can_read, monitor_read, monitor_event,
+                             NULL, &mon->parent_obj, NULL, true);
+    monitor_list_append(&mon->parent_obj);
 }
 
 /**
diff --git a/monitor/monitor-internal.h b/monitor/monitor-internal.h
index a5c4aba306..eb301ea796 100644
--- a/monitor/monitor-internal.h
+++ b/monitor/monitor-internal.h
@@ -128,7 +128,7 @@ struct Monitor {
 };
 
 struct MonitorHMP {
-    Monitor common;
+    Monitor parent_obj;
     bool use_readline;
     /*
      * State used only in the thread "owning" the monitor.
diff --git a/monitor/monitor.c b/monitor/monitor.c
index 00b93ed612..2a1b511ff4 100644
--- a/monitor/monitor.c
+++ b/monitor/monitor.c
@@ -132,7 +132,7 @@ static inline bool monitor_is_hmp_non_interactive(const Monitor *mon)
         return false;
     }
 
-    return !monitor_uses_readline(container_of(mon, MonitorHMP, common));
+    return !monitor_uses_readline(container_of(mon, MonitorHMP, parent_obj));
 }
 
 static gboolean monitor_unblocked(void *do_not_use, GIOCondition cond,
@@ -542,7 +542,7 @@ static void monitor_accept_input(void *opaque)
 
     qemu_mutex_lock(&mon->mon_lock);
     if (!monitor_is_qmp(mon) && mon->reset_seen) {
-        MonitorHMP *hmp_mon = container_of(mon, MonitorHMP, common);
+        MonitorHMP *hmp_mon = container_of(mon, MonitorHMP, parent_obj);
         assert(hmp_mon->rs);
         readline_restart(hmp_mon->rs);
         qemu_mutex_unlock(&mon->mon_lock);
@@ -627,7 +627,7 @@ void monitor_data_destroy(Monitor *mon)
     if (monitor_is_qmp(mon)) {
         monitor_data_destroy_qmp(container_of(mon, MonitorQMP, common));
     } else {
-        readline_free(container_of(mon, MonitorHMP, common)->rs);
+        readline_free(container_of(mon, MonitorHMP, parent_obj)->rs);
     }
     g_string_free(mon->outbuf, true);
     qemu_mutex_destroy(&mon->mon_lock);
diff --git a/monitor/qmp-cmds.c b/monitor/qmp-cmds.c
index 0c409c27dc..aa9ee8a391 100644
--- a/monitor/qmp-cmds.c
+++ b/monitor/qmp-cmds.c
@@ -168,10 +168,10 @@ char *qmp_human_monitor_command(const char *command_line, bool has_cpu_index,
     char *output = NULL;
     MonitorHMP hmp = {};
 
-    monitor_data_init(&hmp.common, false, true, false);
+    monitor_data_init(&hmp.parent_obj, false, true, false);
 
     if (has_cpu_index) {
-        int ret = monitor_set_cpu(&hmp.common, cpu_index);
+        int ret = monitor_set_cpu(&hmp.parent_obj, cpu_index);
         if (ret < 0) {
             error_setg(errp, QERR_INVALID_PARAMETER_VALUE, "cpu-index",
                        "a CPU number");
@@ -181,12 +181,12 @@ char *qmp_human_monitor_command(const char *command_line, bool has_cpu_index,
 
     handle_hmp_command(&hmp, command_line);
 
-    WITH_QEMU_LOCK_GUARD(&hmp.common.mon_lock) {
-        output = g_strdup(hmp.common.outbuf->str);
+    WITH_QEMU_LOCK_GUARD(&hmp.parent_obj.mon_lock) {
+        output = g_strdup(hmp.parent_obj.outbuf->str);
     }
 
 out:
-    monitor_data_destroy(&hmp.common);
+    monitor_data_destroy(&hmp.parent_obj);
     return output;
 }
 
diff --git a/ui/ui-hmp-cmds.c b/ui/ui-hmp-cmds.c
index ee3e731d07..06f4030ce4 100644
--- a/ui/ui-hmp-cmds.c
+++ b/ui/ui-hmp-cmds.c
@@ -343,7 +343,7 @@ void hmp_change_vnc(Monitor *mon, const char *device, const char *target,
         return;
     }
     if (!arg) {
-        MonitorHMP *hmp_mon = container_of(mon, MonitorHMP, common);
+        MonitorHMP *hmp_mon = container_of(mon, MonitorHMP, parent_obj);
         monitor_read_password(hmp_mon, hmp_change_read_arg, NULL);
     } else {
         qmp_change_vnc_password(arg, errp);
-- 
2.54.0



^ permalink raw reply related	[flat|nested] 49+ messages in thread

* [PATCH v6 03/34] monitor: replace 'common' with 'parent_obj' in MonitorQMP
  2026-07-02 16:03 [PATCH v6 00/34] monitor: turn QMP and HMP into QOM objects Daniel P. Berrangé
  2026-07-02 16:03 ` [PATCH v6 01/34] qom: replace 'can_be_deleted' with 'prepare_delete' Daniel P. Berrangé
  2026-07-02 16:03 ` [PATCH v6 02/34] monitor: replace 'common' with 'parent_obj' in MonitorHMP Daniel P. Berrangé
@ 2026-07-02 16:03 ` Daniel P. Berrangé
  2026-07-02 16:03 ` [PATCH v6 04/34] monitor: rename monitor_init* to monitor_new* Daniel P. Berrangé
                   ` (30 subsequent siblings)
  33 siblings, 0 replies; 49+ messages in thread
From: Daniel P. Berrangé @ 2026-07-02 16:03 UTC (permalink / raw)
  To: qemu-devel
  Cc: Paolo Bonzini, Dr. David Alan Gilbert, Markus Armbruster,
	Marc-André Lureau, Alex Bennée,
	Philippe Mathieu-Daudé, Christian Brauner, devel,
	Daniel P. Berrangé, Peter Krempa

The field name 'parent_obj' is standard practice for QOM structs
so align the QMP monitor.

Reviewed-by: Marc-André Lureau <marcandre.lureau@redhat.com>
Tested-by: Peter Krempa <pkrempa@redhat.com>
Signed-off-by: Daniel P. Berrangé <berrange@redhat.com>
---
 monitor/monitor-internal.h |  2 +-
 monitor/monitor.c          |  4 ++--
 monitor/qmp-cmds-control.c |  4 ++--
 monitor/qmp.c              | 40 +++++++++++++++++++-------------------
 4 files changed, 25 insertions(+), 25 deletions(-)

diff --git a/monitor/monitor-internal.h b/monitor/monitor-internal.h
index eb301ea796..4da2b2a677 100644
--- a/monitor/monitor-internal.h
+++ b/monitor/monitor-internal.h
@@ -141,7 +141,7 @@ struct MonitorHMP {
 };
 
 typedef struct {
-    Monitor common;
+    Monitor parent_obj;
     JSONMessageParser parser;
     bool pretty;
     /*
diff --git a/monitor/monitor.c b/monitor/monitor.c
index 2a1b511ff4..0b7b464ebe 100644
--- a/monitor/monitor.c
+++ b/monitor/monitor.c
@@ -308,7 +308,7 @@ static void monitor_qapi_event_emit(QAPIEvent event, QDict *qdict)
             continue;
         }
 
-        qmp_mon = container_of(mon, MonitorQMP, common);
+        qmp_mon = container_of(mon, MonitorQMP, parent_obj);
         {
             QEMU_LOCK_GUARD(&mon->mon_lock);
             if (qmp_mon->commands == &qmp_cap_negotiation_commands) {
@@ -625,7 +625,7 @@ void monitor_data_destroy(Monitor *mon)
     g_free(mon->mon_cpu_path);
     qemu_chr_fe_deinit(&mon->chr, false);
     if (monitor_is_qmp(mon)) {
-        monitor_data_destroy_qmp(container_of(mon, MonitorQMP, common));
+        monitor_data_destroy_qmp(container_of(mon, MonitorQMP, parent_obj));
     } else {
         readline_free(container_of(mon, MonitorHMP, parent_obj)->rs);
     }
diff --git a/monitor/qmp-cmds-control.c b/monitor/qmp-cmds-control.c
index 150ca9f5cb..af6a2a118b 100644
--- a/monitor/qmp-cmds-control.c
+++ b/monitor/qmp-cmds-control.c
@@ -76,7 +76,7 @@ void qmp_qmp_capabilities(bool has_enable, QMPCapabilityList *enable,
     MonitorQMP *mon;
 
     assert(monitor_is_qmp(cur_mon));
-    mon = container_of(cur_mon, MonitorQMP, common);
+    mon = container_of(cur_mon, MonitorQMP, parent_obj);
 
     if (mon->commands == &qmp_commands) {
         error_set(errp, ERROR_CLASS_COMMAND_NOT_FOUND,
@@ -126,7 +126,7 @@ CommandInfoList *qmp_query_commands(Error **errp)
     MonitorQMP *mon;
 
     assert(monitor_is_qmp(cur_mon));
-    mon = container_of(cur_mon, MonitorQMP, common);
+    mon = container_of(cur_mon, MonitorQMP, parent_obj);
 
     qmp_for_each_command(mon->commands, query_commands_cb, &list);
 
diff --git a/monitor/qmp.c b/monitor/qmp.c
index 687019811f..27934206db 100644
--- a/monitor/qmp.c
+++ b/monitor/qmp.c
@@ -80,7 +80,7 @@ static void monitor_qmp_caps_reset(MonitorQMP *mon)
 {
     memset(mon->capab_offered, 0, sizeof(mon->capab_offered));
     memset(mon->capab, 0, sizeof(mon->capab));
-    mon->capab_offered[QMP_CAPABILITY_OOB] = mon->common.use_io_thread;
+    mon->capab_offered[QMP_CAPABILITY_OOB] = mon->parent_obj.use_io_thread;
 }
 
 static void qmp_request_free(QMPRequest *req)
@@ -124,7 +124,7 @@ static void monitor_qmp_cleanup_queue_and_resume(MonitorQMP *mon)
          * when we get here while the monitor is suspended.  An
          * unfortunately timed CHR_EVENT_CLOSED can do the trick.
          */
-        monitor_resume(&mon->common);
+        monitor_resume(&mon->parent_obj);
     }
 
 }
@@ -139,7 +139,7 @@ void qmp_send_response(MonitorQMP *mon, const QDict *rsp)
     trace_monitor_qmp_respond(mon, json->str);
 
     g_string_append_c(json, '\n');
-    monitor_puts(&mon->common, json->str);
+    monitor_puts(&mon->parent_obj, json->str);
 
     g_string_free(json, true);
 }
@@ -166,7 +166,7 @@ static void monitor_qmp_dispatch(MonitorQMP *mon, QObject *req)
     QDict *error;
 
     rsp = qmp_dispatch(mon->commands, req, qmp_oob_enabled(mon),
-                       &mon->common);
+                       &mon->parent_obj);
 
     if (mon->commands == &qmp_cap_negotiation_commands) {
         error = qdict_get_qdict(rsp, "error");
@@ -207,7 +207,7 @@ static QMPRequest *monitor_qmp_requests_pop_any_with_lock(void)
             continue;
         }
 
-        qmp_mon = container_of(mon, MonitorQMP, common);
+        qmp_mon = container_of(mon, MonitorQMP, parent_obj);
         qemu_mutex_lock(&qmp_mon->qmp_queue_lock);
         req_obj = g_queue_pop_head(qmp_mon->qmp_requests);
         if (req_obj) {
@@ -302,7 +302,7 @@ void coroutine_fn monitor_qmp_dispatcher_co(void *data)
         oob_enabled = qmp_oob_enabled(mon);
         if (oob_enabled
             && mon->qmp_requests->length == QMP_REQ_QUEUE_LEN_MAX - 1) {
-            monitor_resume(&mon->common);
+            monitor_resume(&mon->parent_obj);
         }
 
         /*
@@ -343,7 +343,7 @@ void coroutine_fn monitor_qmp_dispatcher_co(void *data)
         }
 
         if (!oob_enabled) {
-            monitor_resume(&mon->common);
+            monitor_resume(&mon->parent_obj);
         }
 
         qmp_request_free(req_obj);
@@ -408,7 +408,7 @@ static void handle_qmp_command(void *opaque, QObject *req, Error *err)
          */
         if (!qmp_oob_enabled(mon) ||
             mon->qmp_requests->length == QMP_REQ_QUEUE_LEN_MAX - 1) {
-            monitor_suspend(&mon->common);
+            monitor_suspend(&mon->parent_obj);
         }
 
         /*
@@ -462,7 +462,7 @@ static void monitor_qmp_event(void *opaque, QEMUChrEvent event)
 
     switch (event) {
     case CHR_EVENT_OPENED:
-        WITH_QEMU_LOCK_GUARD(&mon->common.mon_lock) {
+        WITH_QEMU_LOCK_GUARD(&mon->parent_obj.mon_lock) {
             mon->commands = &qmp_cap_negotiation_commands;
             monitor_qmp_caps_reset(mon);
         }
@@ -504,27 +504,27 @@ static void monitor_qmp_setup_handlers_bh(void *opaque)
     MonitorQMP *mon = opaque;
     GMainContext *context;
 
-    assert(mon->common.use_io_thread);
+    assert(mon->parent_obj.use_io_thread);
     context = iothread_get_g_main_context(mon_iothread);
     assert(context);
-    qemu_chr_fe_set_handlers(&mon->common.chr, monitor_can_read,
+    qemu_chr_fe_set_handlers(&mon->parent_obj.chr, monitor_can_read,
                              monitor_qmp_read, monitor_qmp_event,
-                             NULL, &mon->common, context, true);
-    monitor_list_append(&mon->common);
+                             NULL, &mon->parent_obj, context, true);
+    monitor_list_append(&mon->parent_obj);
 }
 
 void monitor_init_qmp(Chardev *chr, bool pretty, Error **errp)
 {
     MonitorQMP *mon = g_new0(MonitorQMP, 1);
 
-    if (!qemu_chr_fe_init(&mon->common.chr, chr, errp)) {
+    if (!qemu_chr_fe_init(&mon->parent_obj.chr, chr, errp)) {
         g_free(mon);
         return;
     }
-    qemu_chr_fe_set_echo(&mon->common.chr, true);
+    qemu_chr_fe_set_echo(&mon->parent_obj.chr, true);
 
     /* Note: we run QMP monitor in I/O thread when @chr supports that */
-    monitor_data_init(&mon->common, true, false,
+    monitor_data_init(&mon->parent_obj, true, false,
                       qemu_chr_has_feature(chr, QEMU_CHAR_FEATURE_GCONTEXT));
 
     mon->pretty = pretty;
@@ -533,7 +533,7 @@ void monitor_init_qmp(Chardev *chr, bool pretty, Error **errp)
     mon->qmp_requests = g_queue_new();
 
     json_message_parser_init(&mon->parser, handle_qmp_command, mon, NULL);
-    if (mon->common.use_io_thread) {
+    if (mon->parent_obj.use_io_thread) {
         /*
          * Make sure the old iowatch is gone.  It's possible when
          * e.g. the chardev is in client mode, with wait=on.
@@ -553,9 +553,9 @@ void monitor_init_qmp(Chardev *chr, bool pretty, Error **errp)
                                 monitor_qmp_setup_handlers_bh, mon);
         /* The bottom half will add @mon to @mon_list */
     } else {
-        qemu_chr_fe_set_handlers(&mon->common.chr, monitor_can_read,
+        qemu_chr_fe_set_handlers(&mon->parent_obj.chr, monitor_can_read,
                                  monitor_qmp_read, monitor_qmp_event,
-                                 NULL, &mon->common, NULL, true);
-        monitor_list_append(&mon->common);
+                                 NULL, &mon->parent_obj, NULL, true);
+        monitor_list_append(&mon->parent_obj);
     }
 }
-- 
2.54.0



^ permalink raw reply related	[flat|nested] 49+ messages in thread

* [PATCH v6 04/34] monitor: rename monitor_init* to monitor_new*
  2026-07-02 16:03 [PATCH v6 00/34] monitor: turn QMP and HMP into QOM objects Daniel P. Berrangé
                   ` (2 preceding siblings ...)
  2026-07-02 16:03 ` [PATCH v6 03/34] monitor: replace 'common' with 'parent_obj' in MonitorQMP Daniel P. Berrangé
@ 2026-07-02 16:03 ` Daniel P. Berrangé
  2026-07-02 16:03 ` [PATCH v6 05/34] monitor: minimal conversion of monitors to QOM Daniel P. Berrangé
                   ` (29 subsequent siblings)
  33 siblings, 0 replies; 49+ messages in thread
From: Daniel P. Berrangé @ 2026-07-02 16:03 UTC (permalink / raw)
  To: qemu-devel
  Cc: Paolo Bonzini, Dr. David Alan Gilbert, Markus Armbruster,
	Marc-André Lureau, Alex Bennée,
	Philippe Mathieu-Daudé, Christian Brauner, devel,
	Daniel P. Berrangé, Peter Krempa

The current "monitor_init" functions will clash with the methods of the
same name that are required by QOM. To ease the transition to QOM,
rename them out of the way.

Reviewed-by: Marc-André Lureau <marcandre.lureau@redhat.com>
Reviewed-by: Dr. David Alan Gilbert <dave@treblig.org>
Tested-by: Peter Krempa <pkrempa@redhat.com>
Signed-off-by: Daniel P. Berrangé <berrange@redhat.com>
---
 chardev/char.c                       |  2 +-
 gdbstub/system.c                     |  2 +-
 include/monitor/monitor.h            |  8 ++++----
 monitor/hmp.c                        |  2 +-
 monitor/monitor.c                    | 10 +++++-----
 monitor/qmp.c                        |  2 +-
 storage-daemon/qemu-storage-daemon.c |  2 +-
 stubs/monitor-internal.c             |  2 +-
 system/vl.c                          |  2 +-
 9 files changed, 16 insertions(+), 16 deletions(-)

diff --git a/chardev/char.c b/chardev/char.c
index ca8b37ed8d..813c04d953 100644
--- a/chardev/char.c
+++ b/chardev/char.c
@@ -805,7 +805,7 @@ static Chardev *qemu_chr_new_from_name(const char *label, const char *filename,
 
     if (qemu_opt_get_bool(opts, "mux", 0)) {
         assert(permit_mux_mon);
-        monitor_init_hmp(chr, true, &err);
+        monitor_new_hmp(chr, true, &err);
         if (err) {
             error_report_err(err);
             object_unparent(OBJECT(chr));
diff --git a/gdbstub/system.c b/gdbstub/system.c
index 2063b63b2f..18f6a62b2e 100644
--- a/gdbstub/system.c
+++ b/gdbstub/system.c
@@ -389,7 +389,7 @@ bool gdbserver_start(const char *device, Error **errp)
         /* Initialize a monitor terminal for gdb */
         mon_chr = qemu_chardev_new(NULL, TYPE_CHARDEV_GDB,
                                    NULL, NULL, &error_abort);
-        monitor_init_hmp(mon_chr, false, &error_abort);
+        monitor_new_hmp(mon_chr, false, &error_abort);
     } else {
         qemu_chr_fe_deinit(&gdbserver_system_state.chr, true);
         mon_chr = gdbserver_system_state.mon_chr;
diff --git a/include/monitor/monitor.h b/include/monitor/monitor.h
index 55649a8664..b9642b58ba 100644
--- a/include/monitor/monitor.h
+++ b/include/monitor/monitor.h
@@ -19,10 +19,10 @@ bool monitor_cur_is_qmp(void);
 
 void monitor_init_globals(void);
 void monitor_init_globals_core(void);
-void monitor_init_qmp(Chardev *chr, bool pretty, Error **errp);
-void monitor_init_hmp(Chardev *chr, bool use_readline, Error **errp);
-int monitor_init(MonitorOptions *opts, bool allow_hmp, Error **errp);
-int monitor_init_opts(QemuOpts *opts, Error **errp);
+void monitor_new_qmp(Chardev *chr, bool pretty, Error **errp);
+void monitor_new_hmp(Chardev *chr, bool use_readline, Error **errp);
+int monitor_new(MonitorOptions *opts, bool allow_hmp, Error **errp);
+int monitor_new_opts(QemuOpts *opts, Error **errp);
 void monitor_cleanup(void);
 
 int monitor_suspend(Monitor *mon);
diff --git a/monitor/hmp.c b/monitor/hmp.c
index 3dde52ddbe..4e4468424a 100644
--- a/monitor/hmp.c
+++ b/monitor/hmp.c
@@ -1524,7 +1524,7 @@ static void monitor_readline_flush(void *opaque)
     monitor_flush(&mon->parent_obj);
 }
 
-void monitor_init_hmp(Chardev *chr, bool use_readline, Error **errp)
+void monitor_new_hmp(Chardev *chr, bool use_readline, Error **errp)
 {
     MonitorHMP *mon = g_new0(MonitorHMP, 1);
 
diff --git a/monitor/monitor.c b/monitor/monitor.c
index 0b7b464ebe..a87597e606 100644
--- a/monitor/monitor.c
+++ b/monitor/monitor.c
@@ -715,7 +715,7 @@ void monitor_init_globals(void)
     aio_co_schedule(iohandler_get_aio_context(), qmp_dispatcher_co);
 }
 
-int monitor_init(MonitorOptions *opts, bool allow_hmp, Error **errp)
+int monitor_new(MonitorOptions *opts, bool allow_hmp, Error **errp)
 {
     ERRP_GUARD();
     Chardev *chr;
@@ -732,7 +732,7 @@ int monitor_init(MonitorOptions *opts, bool allow_hmp, Error **errp)
 
     switch (opts->mode) {
     case MONITOR_MODE_CONTROL:
-        monitor_init_qmp(chr, opts->pretty, errp);
+        monitor_new_qmp(chr, opts->pretty, errp);
         break;
     case MONITOR_MODE_READLINE:
         if (!allow_hmp) {
@@ -743,7 +743,7 @@ int monitor_init(MonitorOptions *opts, bool allow_hmp, Error **errp)
             error_setg(errp, "'pretty' is not compatible with HMP monitors");
             return -1;
         }
-        monitor_init_hmp(chr, true, errp);
+        monitor_new_hmp(chr, true, errp);
         break;
     default:
         g_assert_not_reached();
@@ -752,7 +752,7 @@ int monitor_init(MonitorOptions *opts, bool allow_hmp, Error **errp)
     return *errp ? -1 : 0;
 }
 
-int monitor_init_opts(QemuOpts *opts, Error **errp)
+int monitor_new_opts(QemuOpts *opts, Error **errp)
 {
     Visitor *v;
     MonitorOptions *options;
@@ -765,7 +765,7 @@ int monitor_init_opts(QemuOpts *opts, Error **errp)
         return -1;
     }
 
-    ret = monitor_init(options, true, errp);
+    ret = monitor_new(options, true, errp);
     qapi_free_MonitorOptions(options);
     return ret;
 }
diff --git a/monitor/qmp.c b/monitor/qmp.c
index 27934206db..cb28a95efd 100644
--- a/monitor/qmp.c
+++ b/monitor/qmp.c
@@ -513,7 +513,7 @@ static void monitor_qmp_setup_handlers_bh(void *opaque)
     monitor_list_append(&mon->parent_obj);
 }
 
-void monitor_init_qmp(Chardev *chr, bool pretty, Error **errp)
+void monitor_new_qmp(Chardev *chr, bool pretty, Error **errp)
 {
     MonitorQMP *mon = g_new0(MonitorQMP, 1);
 
diff --git a/storage-daemon/qemu-storage-daemon.c b/storage-daemon/qemu-storage-daemon.c
index eb72561358..50dbfbd97a 100644
--- a/storage-daemon/qemu-storage-daemon.c
+++ b/storage-daemon/qemu-storage-daemon.c
@@ -330,7 +330,7 @@ static void process_options(int argc, char *argv[], bool pre_init_pass)
                 visit_free(v);
 
                 /* TODO Catch duplicate monitor IDs */
-                monitor_init(monitor, false, &error_fatal);
+                monitor_new(monitor, false, &error_fatal);
                 qapi_free_MonitorOptions(monitor);
                 break;
             }
diff --git a/stubs/monitor-internal.c b/stubs/monitor-internal.c
index 4fece49d53..23d58da184 100644
--- a/stubs/monitor-internal.c
+++ b/stubs/monitor-internal.c
@@ -8,6 +8,6 @@ int monitor_get_fd(Monitor *mon, const char *name, Error **errp)
     return -1;
 }
 
-void monitor_init_hmp(Chardev *chr, bool use_readline, Error **errp)
+void monitor_new_hmp(Chardev *chr, bool use_readline, Error **errp)
 {
 }
diff --git a/system/vl.c b/system/vl.c
index 1d14e2e207..6643242729 100644
--- a/system/vl.c
+++ b/system/vl.c
@@ -1247,7 +1247,7 @@ static int fsdev_init_func(void *opaque, QemuOpts *opts, Error **errp)
 
 static int mon_init_func(void *opaque, QemuOpts *opts, Error **errp)
 {
-    return monitor_init_opts(opts, errp);
+    return monitor_new_opts(opts, errp);
 }
 
 static void monitor_parse(const char *str, const char *mode, bool pretty)
-- 
2.54.0



^ permalink raw reply related	[flat|nested] 49+ messages in thread

* [PATCH v6 05/34] monitor: minimal conversion of monitors to QOM
  2026-07-02 16:03 [PATCH v6 00/34] monitor: turn QMP and HMP into QOM objects Daniel P. Berrangé
                   ` (3 preceding siblings ...)
  2026-07-02 16:03 ` [PATCH v6 04/34] monitor: rename monitor_init* to monitor_new* Daniel P. Berrangé
@ 2026-07-02 16:03 ` Daniel P. Berrangé
  2026-07-03 11:38   ` Markus Armbruster
  2026-07-03 12:48   ` Markus Armbruster
  2026-07-02 16:03 ` [PATCH v6 06/34] monitor: add 'chardev' property to Monitor base class Daniel P. Berrangé
                   ` (28 subsequent siblings)
  33 siblings, 2 replies; 49+ messages in thread
From: Daniel P. Berrangé @ 2026-07-02 16:03 UTC (permalink / raw)
  To: qemu-devel
  Cc: Paolo Bonzini, Dr. David Alan Gilbert, Markus Armbruster,
	Marc-André Lureau, Alex Bennée,
	Philippe Mathieu-Daudé, Christian Brauner, devel,
	Daniel P. Berrangé

This introduces a Monitor QOM object, with MonitorHMP and
MonitorQMP subclasses. This is the bare minimum conversion
of just the type declarations and replacing g_new/g_free
with object_new/object_unref. The Monitor base class is
abstract since only the HMP/QMP variants should ever be
created.

When created through the existing QemuOpts interfaces, the
new internal QOM object will get assigned a dynamic ID with
the format "compat_monitorNNN" which is the historical
QemuOpts ID naming pattern.

Signed-off-by: Daniel P. Berrangé <berrange@redhat.com>
---
 chardev/char.c             |  2 +-
 gdbstub/system.c           |  2 +-
 include/monitor/monitor.h  | 18 +++++++++++++++---
 monitor/hmp.c              | 31 ++++++++++++++++++++++++++++---
 monitor/monitor-internal.h | 18 ++++++++++++++++--
 monitor/monitor.c          | 31 +++++++++++++++++++++++++++----
 monitor/qmp-cmds.c         | 15 ++++++++-------
 monitor/qmp.c              | 31 ++++++++++++++++++++++++++++---
 stubs/monitor-internal.c   |  3 ++-
 system/vl.c                |  7 +++----
 10 files changed, 129 insertions(+), 29 deletions(-)

diff --git a/chardev/char.c b/chardev/char.c
index 813c04d953..c71ffa7963 100644
--- a/chardev/char.c
+++ b/chardev/char.c
@@ -805,7 +805,7 @@ static Chardev *qemu_chr_new_from_name(const char *label, const char *filename,
 
     if (qemu_opt_get_bool(opts, "mux", 0)) {
         assert(permit_mux_mon);
-        monitor_new_hmp(chr, true, &err);
+        monitor_new_hmp(NULL, chr, true, &err);
         if (err) {
             error_report_err(err);
             object_unparent(OBJECT(chr));
diff --git a/gdbstub/system.c b/gdbstub/system.c
index 18f6a62b2e..fda8ef9352 100644
--- a/gdbstub/system.c
+++ b/gdbstub/system.c
@@ -389,7 +389,7 @@ bool gdbserver_start(const char *device, Error **errp)
         /* Initialize a monitor terminal for gdb */
         mon_chr = qemu_chardev_new(NULL, TYPE_CHARDEV_GDB,
                                    NULL, NULL, &error_abort);
-        monitor_new_hmp(mon_chr, false, &error_abort);
+        monitor_new_hmp(NULL, mon_chr, false, &error_abort);
     } else {
         qemu_chr_fe_deinit(&gdbserver_system_state.chr, true);
         mon_chr = gdbserver_system_state.mon_chr;
diff --git a/include/monitor/monitor.h b/include/monitor/monitor.h
index b9642b58ba..83723f705a 100644
--- a/include/monitor/monitor.h
+++ b/include/monitor/monitor.h
@@ -5,8 +5,17 @@
 #include "qapi/qapi-types-misc.h"
 #include "qemu/readline.h"
 #include "exec/hwaddr.h"
+#include "qom/object.h"
+
+#define TYPE_MONITOR "monitor"
+OBJECT_DECLARE_TYPE(Monitor, MonitorClass, MONITOR);
+
+#define TYPE_MONITOR_HMP "monitor-hmp"
+OBJECT_DECLARE_TYPE(MonitorHMP, MonitorHMPClass, MONITOR_HMP);
+
+#define TYPE_MONITOR_QMP "monitor-qmp"
+OBJECT_DECLARE_TYPE(MonitorQMP, MonitorQMPClass, MONITOR_QMP);
 
-typedef struct MonitorHMP MonitorHMP;
 typedef struct MonitorOptions MonitorOptions;
 
 #define QMP_REQ_QUEUE_LEN_MAX 8
@@ -19,8 +28,11 @@ bool monitor_cur_is_qmp(void);
 
 void monitor_init_globals(void);
 void monitor_init_globals_core(void);
-void monitor_new_qmp(Chardev *chr, bool pretty, Error **errp);
-void monitor_new_hmp(Chardev *chr, bool use_readline, Error **errp);
+char *monitor_compat_id(void);
+void monitor_new_qmp(const char *id, Chardev *chr,
+                     bool pretty, Error **errp);
+void monitor_new_hmp(const char *id, Chardev *chr,
+                     bool use_readline, Error **errp);
 int monitor_new(MonitorOptions *opts, bool allow_hmp, Error **errp);
 int monitor_new_opts(QemuOpts *opts, Error **errp);
 void monitor_cleanup(void);
diff --git a/monitor/hmp.c b/monitor/hmp.c
index 4e4468424a..da11e56854 100644
--- a/monitor/hmp.c
+++ b/monitor/hmp.c
@@ -43,6 +43,20 @@
 #include "system/block-backend.h"
 #include "trace.h"
 
+OBJECT_DEFINE_TYPE(MonitorHMP, monitor_hmp, MONITOR_HMP, MONITOR);
+
+static void monitor_hmp_finalize(Object *obj)
+{
+}
+
+static void monitor_hmp_class_init(ObjectClass *cls, const void *data)
+{
+}
+
+static void monitor_hmp_init(Object *obj)
+{
+}
+
 static void monitor_command_cb(void *opaque, const char *cmdline,
                                void *readline_opaque)
 {
@@ -1524,12 +1538,23 @@ static void monitor_readline_flush(void *opaque)
     monitor_flush(&mon->parent_obj);
 }
 
-void monitor_new_hmp(Chardev *chr, bool use_readline, Error **errp)
+void monitor_new_hmp(const char *id, Chardev *chr,
+                     bool use_readline, Error **errp)
 {
-    MonitorHMP *mon = g_new0(MonitorHMP, 1);
+    MonitorHMP *mon;
+    g_autofree char *autoid = id ? NULL : monitor_compat_id();
+    Object *obj = object_new_with_props(TYPE_MONITOR_HMP,
+                                        object_get_objects_root(),
+                                        id ? id : autoid,
+                                        errp,
+                                        NULL);
+    if (!obj) {
+        return;
+    }
+    mon = MONITOR_HMP(obj);
 
     if (!qemu_chr_fe_init(&mon->parent_obj.chr, chr, errp)) {
-        g_free(mon);
+        object_unparent(OBJECT(mon));
         return;
     }
 
diff --git a/monitor/monitor-internal.h b/monitor/monitor-internal.h
index 4da2b2a677..05c1f2f5e0 100644
--- a/monitor/monitor-internal.h
+++ b/monitor/monitor-internal.h
@@ -101,7 +101,13 @@ typedef struct HMPCommand {
     bool coroutine;
 } HMPCommand;
 
+
+struct MonitorClass {
+    ObjectClass parent_class;
+};
+
 struct Monitor {
+    Object parent;
     CharFrontend chr;
     int suspend_cnt;            /* Needs to be accessed atomically */
     bool is_qmp;
@@ -127,6 +133,10 @@ struct Monitor {
     int reset_seen;
 };
 
+struct MonitorHMPClass {
+    MonitorClass parent_class;
+};
+
 struct MonitorHMP {
     Monitor parent_obj;
     bool use_readline;
@@ -140,7 +150,11 @@ struct MonitorHMP {
     ReadLineState *rs;
 };
 
-typedef struct {
+struct MonitorQMPClass {
+    MonitorClass parent_class;
+};
+
+struct MonitorQMP {
     Monitor parent_obj;
     JSONMessageParser parser;
     bool pretty;
@@ -160,7 +174,7 @@ typedef struct {
     QemuMutex qmp_queue_lock;
     /* Input queue that holds all the parsed QMP requests */
     GQueue *qmp_requests;
-} MonitorQMP;
+};
 
 /**
  * Is @mon a QMP monitor?
diff --git a/monitor/monitor.c b/monitor/monitor.c
index a87597e606..18a1d8ddde 100644
--- a/monitor/monitor.c
+++ b/monitor/monitor.c
@@ -73,6 +73,22 @@ static GHashTable *coroutine_mon; /* Maps Coroutine* to Monitor* */
 MonitorList mon_list;
 static bool monitor_destroyed;
 
+int monitor_device_index;
+
+OBJECT_DEFINE_ABSTRACT_TYPE(Monitor, monitor, MONITOR, OBJECT);
+
+static void monitor_finalize(Object *obj)
+{
+}
+
+static void monitor_class_init(ObjectClass *cls, const void *data)
+{
+}
+
+static void monitor_init(Object *obj)
+{
+}
+
 Monitor *monitor_cur(void)
 {
     Monitor *mon;
@@ -598,7 +614,7 @@ void monitor_list_append(Monitor *mon)
 
     if (mon) {
         monitor_data_destroy(mon);
-        g_free(mon);
+        object_unparent(OBJECT(mon));
     }
 }
 
@@ -680,7 +696,7 @@ void monitor_cleanup(void)
         monitor_flush(mon);
         monitor_data_destroy(mon);
         qemu_mutex_lock(&monitor_lock);
-        g_free(mon);
+        object_unparent(OBJECT(mon));
     }
     qemu_mutex_unlock(&monitor_lock);
 
@@ -715,6 +731,13 @@ void monitor_init_globals(void)
     aio_co_schedule(iohandler_get_aio_context(), qmp_dispatcher_co);
 }
 
+char *monitor_compat_id(void)
+{
+    static int monitor_device_index;
+
+    return g_strdup_printf("compat_monitor%d", monitor_device_index++);
+}
+
 int monitor_new(MonitorOptions *opts, bool allow_hmp, Error **errp)
 {
     ERRP_GUARD();
@@ -732,7 +755,7 @@ int monitor_new(MonitorOptions *opts, bool allow_hmp, Error **errp)
 
     switch (opts->mode) {
     case MONITOR_MODE_CONTROL:
-        monitor_new_qmp(chr, opts->pretty, errp);
+        monitor_new_qmp(opts->id, chr, opts->pretty, errp);
         break;
     case MONITOR_MODE_READLINE:
         if (!allow_hmp) {
@@ -743,7 +766,7 @@ int monitor_new(MonitorOptions *opts, bool allow_hmp, Error **errp)
             error_setg(errp, "'pretty' is not compatible with HMP monitors");
             return -1;
         }
-        monitor_new_hmp(chr, true, errp);
+        monitor_new_hmp(opts->id, chr, true, errp);
         break;
     default:
         g_assert_not_reached();
diff --git a/monitor/qmp-cmds.c b/monitor/qmp-cmds.c
index aa9ee8a391..bfde769ef0 100644
--- a/monitor/qmp-cmds.c
+++ b/monitor/qmp-cmds.c
@@ -166,12 +166,12 @@ char *qmp_human_monitor_command(const char *command_line, bool has_cpu_index,
                                 int64_t cpu_index, Error **errp)
 {
     char *output = NULL;
-    MonitorHMP hmp = {};
+    MonitorHMP *hmp = MONITOR_HMP(object_new(TYPE_MONITOR_HMP));
 
-    monitor_data_init(&hmp.parent_obj, false, true, false);
+    monitor_data_init(&hmp->parent_obj, false, true, false);
 
     if (has_cpu_index) {
-        int ret = monitor_set_cpu(&hmp.parent_obj, cpu_index);
+        int ret = monitor_set_cpu(&hmp->parent_obj, cpu_index);
         if (ret < 0) {
             error_setg(errp, QERR_INVALID_PARAMETER_VALUE, "cpu-index",
                        "a CPU number");
@@ -179,14 +179,15 @@ char *qmp_human_monitor_command(const char *command_line, bool has_cpu_index,
         }
     }
 
-    handle_hmp_command(&hmp, command_line);
+    handle_hmp_command(hmp, command_line);
 
-    WITH_QEMU_LOCK_GUARD(&hmp.parent_obj.mon_lock) {
-        output = g_strdup(hmp.parent_obj.outbuf->str);
+    WITH_QEMU_LOCK_GUARD(&hmp->parent_obj.mon_lock) {
+        output = g_strdup(hmp->parent_obj.outbuf->str);
     }
 
 out:
-    monitor_data_destroy(&hmp.parent_obj);
+    monitor_data_destroy(&hmp->parent_obj);
+    object_unref(hmp);
     return output;
 }
 
diff --git a/monitor/qmp.c b/monitor/qmp.c
index cb28a95efd..1ef09352fc 100644
--- a/monitor/qmp.c
+++ b/monitor/qmp.c
@@ -71,6 +71,20 @@ typedef struct QMPRequest QMPRequest;
 
 QmpCommandList qmp_commands, qmp_cap_negotiation_commands;
 
+OBJECT_DEFINE_TYPE(MonitorQMP, monitor_qmp, MONITOR_QMP, MONITOR);
+
+static void monitor_qmp_finalize(Object *obj)
+{
+}
+
+static void monitor_qmp_class_init(ObjectClass *cls, const void *data)
+{
+}
+
+static void monitor_qmp_init(Object *obj)
+{
+}
+
 static bool qmp_oob_enabled(MonitorQMP *mon)
 {
     return mon->capab[QMP_CAPABILITY_OOB];
@@ -513,12 +527,23 @@ static void monitor_qmp_setup_handlers_bh(void *opaque)
     monitor_list_append(&mon->parent_obj);
 }
 
-void monitor_new_qmp(Chardev *chr, bool pretty, Error **errp)
+void monitor_new_qmp(const char *id, Chardev *chr,
+                     bool pretty, Error **errp)
 {
-    MonitorQMP *mon = g_new0(MonitorQMP, 1);
+    MonitorQMP *mon;
+    g_autofree char *autoid = id ? NULL : monitor_compat_id();
+    Object *obj = object_new_with_props(TYPE_MONITOR_QMP,
+                                        object_get_objects_root(),
+                                        id ? id : autoid,
+                                        errp,
+                                        NULL);
+    if (!obj) {
+        return;
+    }
+    mon = MONITOR_QMP(obj);
 
     if (!qemu_chr_fe_init(&mon->parent_obj.chr, chr, errp)) {
-        g_free(mon);
+        object_unparent(OBJECT(mon));
         return;
     }
     qemu_chr_fe_set_echo(&mon->parent_obj.chr, true);
diff --git a/stubs/monitor-internal.c b/stubs/monitor-internal.c
index 23d58da184..20367b7e9a 100644
--- a/stubs/monitor-internal.c
+++ b/stubs/monitor-internal.c
@@ -8,6 +8,7 @@ int monitor_get_fd(Monitor *mon, const char *name, Error **errp)
     return -1;
 }
 
-void monitor_new_hmp(Chardev *chr, bool use_readline, Error **errp)
+void monitor_new_hmp(const char *id, Chardev *chr,
+                     bool use_readline, Error **errp)
 {
 }
diff --git a/system/vl.c b/system/vl.c
index 6643242729..4737c03872 100644
--- a/system/vl.c
+++ b/system/vl.c
@@ -1252,7 +1252,6 @@ static int mon_init_func(void *opaque, QemuOpts *opts, Error **errp)
 
 static void monitor_parse(const char *str, const char *mode, bool pretty)
 {
-    static int monitor_device_index = 0;
     QemuOpts *opts;
     const char *p;
     char label[32];
@@ -1260,8 +1259,9 @@ static void monitor_parse(const char *str, const char *mode, bool pretty)
     if (strstart(str, "chardev:", &p)) {
         snprintf(label, sizeof(label), "%s", p);
     } else {
-        snprintf(label, sizeof(label), "compat_monitor%d",
-                 monitor_device_index);
+        g_autofree char *id = monitor_compat_id();
+        assert(strlen(id) < sizeof(label));
+        memcpy(label, id, strlen(id) + 1);
         opts = qemu_chr_parse_compat(label, str, true);
         if (!opts) {
             error_report("parse error: %s", str);
@@ -1277,7 +1277,6 @@ static void monitor_parse(const char *str, const char *mode, bool pretty)
     } else {
         assert(pretty == false);
     }
-    monitor_device_index++;
 }
 
 struct device_config {
-- 
2.54.0



^ permalink raw reply related	[flat|nested] 49+ messages in thread

* [PATCH v6 06/34] monitor: add 'chardev' property to Monitor base class
  2026-07-02 16:03 [PATCH v6 00/34] monitor: turn QMP and HMP into QOM objects Daniel P. Berrangé
                   ` (4 preceding siblings ...)
  2026-07-02 16:03 ` [PATCH v6 05/34] monitor: minimal conversion of monitors to QOM Daniel P. Berrangé
@ 2026-07-02 16:03 ` Daniel P. Berrangé
  2026-07-03 13:17   ` Markus Armbruster
  2026-07-02 16:03 ` [PATCH v6 07/34] monitor: add 'readline' property to HMP Monitor class Daniel P. Berrangé
                   ` (27 subsequent siblings)
  33 siblings, 1 reply; 49+ messages in thread
From: Daniel P. Berrangé @ 2026-07-02 16:03 UTC (permalink / raw)
  To: qemu-devel
  Cc: Paolo Bonzini, Dr. David Alan Gilbert, Markus Armbruster,
	Marc-André Lureau, Alex Bennée,
	Philippe Mathieu-Daudé, Christian Brauner, devel,
	Daniel P. Berrangé, Peter Krempa

This is associates both QMP and HMP monitors with a character
device backend.

Reviewed-by: Marc-André Lureau <marcandre.lureau@redhat.com>
Tested-by: Peter Krempa <pkrempa@redhat.com>
Signed-off-by: Daniel P. Berrangé <berrange@redhat.com>
---
 chardev/char.c             |  3 ++-
 gdbstub/system.c           |  4 ++--
 include/monitor/monitor.h  |  4 ++--
 monitor/hmp.c              | 10 ++++++--
 monitor/monitor-internal.h |  2 ++
 monitor/monitor.c          | 47 ++++++++++++++++++++++++++++++--------
 monitor/qmp.c              | 18 ++++++++++-----
 stubs/monitor-internal.c   |  2 +-
 8 files changed, 67 insertions(+), 23 deletions(-)

diff --git a/chardev/char.c b/chardev/char.c
index c71ffa7963..22e5bae388 100644
--- a/chardev/char.c
+++ b/chardev/char.c
@@ -804,8 +804,9 @@ static Chardev *qemu_chr_new_from_name(const char *label, const char *filename,
     }
 
     if (qemu_opt_get_bool(opts, "mux", 0)) {
+        const char *chardev_id = qemu_opts_id(opts);
         assert(permit_mux_mon);
-        monitor_new_hmp(NULL, chr, true, &err);
+        monitor_new_hmp(NULL, chardev_id, true, &err);
         if (err) {
             error_report_err(err);
             object_unparent(OBJECT(chr));
diff --git a/gdbstub/system.c b/gdbstub/system.c
index fda8ef9352..d681476f5e 100644
--- a/gdbstub/system.c
+++ b/gdbstub/system.c
@@ -387,9 +387,9 @@ bool gdbserver_start(const char *device, Error **errp)
         qemu_add_vm_change_state_handler(gdb_vm_state_change, NULL);
 
         /* Initialize a monitor terminal for gdb */
-        mon_chr = qemu_chardev_new(NULL, TYPE_CHARDEV_GDB,
+        mon_chr = qemu_chardev_new("gdbchrdev0", TYPE_CHARDEV_GDB,
                                    NULL, NULL, &error_abort);
-        monitor_new_hmp(NULL, mon_chr, false, &error_abort);
+        monitor_new_hmp(NULL, "gdbchrdev0", false, &error_abort);
     } else {
         qemu_chr_fe_deinit(&gdbserver_system_state.chr, true);
         mon_chr = gdbserver_system_state.mon_chr;
diff --git a/include/monitor/monitor.h b/include/monitor/monitor.h
index 83723f705a..bfbeedec9b 100644
--- a/include/monitor/monitor.h
+++ b/include/monitor/monitor.h
@@ -29,9 +29,9 @@ bool monitor_cur_is_qmp(void);
 void monitor_init_globals(void);
 void monitor_init_globals_core(void);
 char *monitor_compat_id(void);
-void monitor_new_qmp(const char *id, Chardev *chr,
+void monitor_new_qmp(const char *id, const char *chardev_id,
                      bool pretty, Error **errp);
-void monitor_new_hmp(const char *id, Chardev *chr,
+void monitor_new_hmp(const char *id, const char *chardev_id,
                      bool use_readline, Error **errp);
 int monitor_new(MonitorOptions *opts, bool allow_hmp, Error **errp);
 int monitor_new_opts(QemuOpts *opts, Error **errp);
diff --git a/monitor/hmp.c b/monitor/hmp.c
index da11e56854..be59b6ca65 100644
--- a/monitor/hmp.c
+++ b/monitor/hmp.c
@@ -23,6 +23,7 @@
  */
 
 #include "qemu/osdep.h"
+#include "qapi/error.h"
 #include <dirent.h>
 #include "hw/core/qdev.h"
 #include "hw/core/sysemu-cpu-ops.h"
@@ -1538,22 +1539,27 @@ static void monitor_readline_flush(void *opaque)
     monitor_flush(&mon->parent_obj);
 }
 
-void monitor_new_hmp(const char *id, Chardev *chr,
+void monitor_new_hmp(const char *id, const char *chardev_id,
                      bool use_readline, Error **errp)
 {
+    ERRP_GUARD();
     MonitorHMP *mon;
     g_autofree char *autoid = id ? NULL : monitor_compat_id();
     Object *obj = object_new_with_props(TYPE_MONITOR_HMP,
                                         object_get_objects_root(),
                                         id ? id : autoid,
                                         errp,
+                                        "chardev", chardev_id,
                                         NULL);
+
     if (!obj) {
         return;
     }
+
     mon = MONITOR_HMP(obj);
 
-    if (!qemu_chr_fe_init(&mon->parent_obj.chr, chr, errp)) {
+    monitor_complete(MONITOR(mon), errp);
+    if (*errp) {
         object_unparent(OBJECT(mon));
         return;
     }
diff --git a/monitor/monitor-internal.h b/monitor/monitor-internal.h
index 05c1f2f5e0..145d52fd71 100644
--- a/monitor/monitor-internal.h
+++ b/monitor/monitor-internal.h
@@ -108,6 +108,7 @@ struct MonitorClass {
 
 struct Monitor {
     Object parent;
+    char *chardev_id;
     CharFrontend chr;
     int suspend_cnt;            /* Needs to be accessed atomically */
     bool is_qmp;
@@ -192,6 +193,7 @@ extern QmpCommandList qmp_commands, qmp_cap_negotiation_commands;
 extern QemuMutex monitor_lock;
 extern MonitorList mon_list;
 
+void monitor_complete(Monitor *mon, Error **errp);
 void monitor_data_init(Monitor *mon, bool is_qmp, bool skip_flush,
                        bool use_io_thread);
 void monitor_data_destroy(Monitor *mon);
diff --git a/monitor/monitor.c b/monitor/monitor.c
index 18a1d8ddde..1648946c92 100644
--- a/monitor/monitor.c
+++ b/monitor/monitor.c
@@ -79,10 +79,31 @@ OBJECT_DEFINE_ABSTRACT_TYPE(Monitor, monitor, MONITOR, OBJECT);
 
 static void monitor_finalize(Object *obj)
 {
+    Monitor *mon = MONITOR(obj);
+
+    g_free(mon->chardev_id);
+}
+
+static char *monitor_get_chardev_id(Object *obj, Error **errp)
+{
+    Monitor *mon = MONITOR(obj);
+
+    return g_strdup(mon->chardev_id);
+}
+
+static void monitor_set_chardev_id(Object *obj, const char *str, Error **errp)
+{
+    Monitor *mon = MONITOR(obj);
+
+    g_free(mon->chardev_id);
+    mon->chardev_id = g_strdup(str);
 }
 
 static void monitor_class_init(ObjectClass *cls, const void *data)
 {
+    object_class_property_add_str(cls, "chardev",
+                                  monitor_get_chardev_id,
+                                  monitor_set_chardev_id);
 }
 
 static void monitor_init(Object *obj)
@@ -738,16 +759,24 @@ char *monitor_compat_id(void)
     return g_strdup_printf("compat_monitor%d", monitor_device_index++);
 }
 
-int monitor_new(MonitorOptions *opts, bool allow_hmp, Error **errp)
+void monitor_complete(Monitor *mon, Error **errp)
 {
-    ERRP_GUARD();
-    Chardev *chr;
+    if (mon->chardev_id) {
+        Chardev *chr = qemu_chr_find(mon->chardev_id);
+        if (chr == NULL) {
+            error_setg(errp, "chardev \"%s\" not found", mon->chardev_id);
+            return;
+        }
 
-    chr = qemu_chr_find(opts->chardev);
-    if (chr == NULL) {
-        error_setg(errp, "chardev \"%s\" not found", opts->chardev);
-        return -1;
+        if (!qemu_chr_fe_init(&mon->chr, chr, errp)) {
+            return;
+        }
     }
+}
+
+int monitor_new(MonitorOptions *opts, bool allow_hmp, Error **errp)
+{
+    ERRP_GUARD();
 
     if (!opts->has_mode) {
         opts->mode = allow_hmp ? MONITOR_MODE_READLINE : MONITOR_MODE_CONTROL;
@@ -755,7 +784,7 @@ int monitor_new(MonitorOptions *opts, bool allow_hmp, Error **errp)
 
     switch (opts->mode) {
     case MONITOR_MODE_CONTROL:
-        monitor_new_qmp(opts->id, chr, opts->pretty, errp);
+        monitor_new_qmp(opts->id, opts->chardev, opts->pretty, errp);
         break;
     case MONITOR_MODE_READLINE:
         if (!allow_hmp) {
@@ -766,7 +795,7 @@ int monitor_new(MonitorOptions *opts, bool allow_hmp, Error **errp)
             error_setg(errp, "'pretty' is not compatible with HMP monitors");
             return -1;
         }
-        monitor_new_hmp(opts->id, chr, true, errp);
+        monitor_new_hmp(opts->id, opts->chardev, true, errp);
         break;
     default:
         g_assert_not_reached();
diff --git a/monitor/qmp.c b/monitor/qmp.c
index 1ef09352fc..df35b530ed 100644
--- a/monitor/qmp.c
+++ b/monitor/qmp.c
@@ -527,30 +527,36 @@ static void monitor_qmp_setup_handlers_bh(void *opaque)
     monitor_list_append(&mon->parent_obj);
 }
 
-void monitor_new_qmp(const char *id, Chardev *chr,
+void monitor_new_qmp(const char *id, const char *chardev_id,
                      bool pretty, Error **errp)
 {
+    ERRP_GUARD();
     MonitorQMP *mon;
     g_autofree char *autoid = id ? NULL : monitor_compat_id();
     Object *obj = object_new_with_props(TYPE_MONITOR_QMP,
                                         object_get_objects_root(),
                                         id ? id : autoid,
                                         errp,
+                                        "chardev", chardev_id,
                                         NULL);
+
     if (!obj) {
         return;
     }
-    mon = MONITOR_QMP(obj);
 
-    if (!qemu_chr_fe_init(&mon->parent_obj.chr, chr, errp)) {
+    mon = MONITOR_QMP(obj);
+    monitor_complete(MONITOR(mon), errp);
+    if (*errp) {
         object_unparent(OBJECT(mon));
         return;
     }
+
     qemu_chr_fe_set_echo(&mon->parent_obj.chr, true);
 
     /* Note: we run QMP monitor in I/O thread when @chr supports that */
     monitor_data_init(&mon->parent_obj, true, false,
-                      qemu_chr_has_feature(chr, QEMU_CHAR_FEATURE_GCONTEXT));
+                      qemu_chr_has_feature(mon->parent_obj.chr.chr,
+                                           QEMU_CHAR_FEATURE_GCONTEXT));
 
     mon->pretty = pretty;
 
@@ -563,12 +569,12 @@ void monitor_new_qmp(const char *id, Chardev *chr,
          * Make sure the old iowatch is gone.  It's possible when
          * e.g. the chardev is in client mode, with wait=on.
          */
-        remove_fd_in_watch(chr);
+        remove_fd_in_watch(mon->parent_obj.chr.chr);
         /*
          * Clean up listener IO sources early to prevent racy fd
          * handling between the main thread and the I/O thread.
          */
-        remove_listener_fd_in_watch(chr);
+        remove_listener_fd_in_watch(mon->parent_obj.chr.chr);
         /*
          * We can't call qemu_chr_fe_set_handlers() directly here
          * since chardev might be running in the monitor I/O
diff --git a/stubs/monitor-internal.c b/stubs/monitor-internal.c
index 20367b7e9a..f94b5e5c21 100644
--- a/stubs/monitor-internal.c
+++ b/stubs/monitor-internal.c
@@ -8,7 +8,7 @@ int monitor_get_fd(Monitor *mon, const char *name, Error **errp)
     return -1;
 }
 
-void monitor_new_hmp(const char *id, Chardev *chr,
+void monitor_new_hmp(const char *id, const char *chardev_id,
                      bool use_readline, Error **errp)
 {
 }
-- 
2.54.0



^ permalink raw reply related	[flat|nested] 49+ messages in thread

* [PATCH v6 07/34] monitor: add 'readline' property to HMP Monitor class
  2026-07-02 16:03 [PATCH v6 00/34] monitor: turn QMP and HMP into QOM objects Daniel P. Berrangé
                   ` (5 preceding siblings ...)
  2026-07-02 16:03 ` [PATCH v6 06/34] monitor: add 'chardev' property to Monitor base class Daniel P. Berrangé
@ 2026-07-02 16:03 ` Daniel P. Berrangé
  2026-07-02 16:03 ` [PATCH v6 08/34] monitor: add 'pretty' property to QMP " Daniel P. Berrangé
                   ` (26 subsequent siblings)
  33 siblings, 0 replies; 49+ messages in thread
From: Daniel P. Berrangé @ 2026-07-02 16:03 UTC (permalink / raw)
  To: qemu-devel
  Cc: Paolo Bonzini, Dr. David Alan Gilbert, Markus Armbruster,
	Marc-André Lureau, Alex Bennée,
	Philippe Mathieu-Daudé, Christian Brauner, devel,
	Daniel P. Berrangé, Peter Krempa

This determines whether a human monitor runs with readline for
interactive use, or without readline for non-interactive use by
the GDB stub.

Reviewed-by: Marc-André Lureau <marcandre.lureau@redhat.com>
Tested-by: Peter Krempa <pkrempa@redhat.com>
Signed-off-by: Daniel P. Berrangé <berrange@redhat.com>
---
 monitor/hmp.c | 27 ++++++++++++++++++++++++++-
 1 file changed, 26 insertions(+), 1 deletion(-)

diff --git a/monitor/hmp.c b/monitor/hmp.c
index be59b6ca65..260e4b40fe 100644
--- a/monitor/hmp.c
+++ b/monitor/hmp.c
@@ -50,12 +50,37 @@ static void monitor_hmp_finalize(Object *obj)
 {
 }
 
+static bool monitor_hmp_get_readline(Object *obj, Error **errp)
+{
+    MonitorHMP *mon = MONITOR_HMP(obj);
+
+    return mon->use_readline;
+}
+
+static void monitor_hmp_set_readline(Object *obj, bool val, Error **errp)
+{
+    MonitorHMP *mon = MONITOR_HMP(obj);
+
+    mon->use_readline = val;
+}
+
 static void monitor_hmp_class_init(ObjectClass *cls, const void *data)
 {
+    object_class_property_add_bool(cls, "readline",
+                                   monitor_hmp_get_readline,
+                                   monitor_hmp_set_readline);
 }
 
 static void monitor_hmp_init(Object *obj)
 {
+    MonitorHMP *hmp = MONITOR_HMP(obj);
+
+    /*
+     * Default to common case for external HMP use,
+     * as opposed to non-interactive internal use
+     * from gdbstub
+     */
+    hmp->use_readline = true;
 }
 
 static void monitor_command_cb(void *opaque, const char *cmdline,
@@ -1550,6 +1575,7 @@ void monitor_new_hmp(const char *id, const char *chardev_id,
                                         id ? id : autoid,
                                         errp,
                                         "chardev", chardev_id,
+                                        "readline", use_readline ? "yes" : "no",
                                         NULL);
 
     if (!obj) {
@@ -1566,7 +1592,6 @@ void monitor_new_hmp(const char *id, const char *chardev_id,
 
     monitor_data_init(&mon->parent_obj, false, false, false);
 
-    mon->use_readline = use_readline;
     if (mon->use_readline) {
         mon->rs = readline_init(monitor_readline_printf,
                                 monitor_readline_flush,
-- 
2.54.0



^ permalink raw reply related	[flat|nested] 49+ messages in thread

* [PATCH v6 08/34] monitor: add 'pretty' property to QMP Monitor class
  2026-07-02 16:03 [PATCH v6 00/34] monitor: turn QMP and HMP into QOM objects Daniel P. Berrangé
                   ` (6 preceding siblings ...)
  2026-07-02 16:03 ` [PATCH v6 07/34] monitor: add 'readline' property to HMP Monitor class Daniel P. Berrangé
@ 2026-07-02 16:03 ` Daniel P. Berrangé
  2026-07-02 16:03 ` [PATCH v6 09/34] monitor: remove 'skip_flush' field Daniel P. Berrangé
                   ` (25 subsequent siblings)
  33 siblings, 0 replies; 49+ messages in thread
From: Daniel P. Berrangé @ 2026-07-02 16:03 UTC (permalink / raw)
  To: qemu-devel
  Cc: Paolo Bonzini, Dr. David Alan Gilbert, Markus Armbruster,
	Marc-André Lureau, Alex Bennée,
	Philippe Mathieu-Daudé, Christian Brauner, devel,
	Daniel P. Berrangé, Peter Krempa

This determines whether the QMP JSON responses are pretty printed
with newlines and indentation, or compact with no extra whitespace.

Reviewed-by: Marc-André Lureau <marcandre.lureau@redhat.com>
Tested-by: Peter Krempa <pkrempa@redhat.com>
Signed-off-by: Daniel P. Berrangé <berrange@redhat.com>
---
 monitor/qmp.c | 20 ++++++++++++++++++--
 1 file changed, 18 insertions(+), 2 deletions(-)

diff --git a/monitor/qmp.c b/monitor/qmp.c
index df35b530ed..d7adb9ba8b 100644
--- a/monitor/qmp.c
+++ b/monitor/qmp.c
@@ -77,8 +77,25 @@ static void monitor_qmp_finalize(Object *obj)
 {
 }
 
+static bool monitor_qmp_get_pretty(Object *obj, Error **errp)
+{
+    MonitorQMP *mon = MONITOR_QMP(obj);
+
+    return mon->pretty;
+}
+
+static void monitor_qmp_set_pretty(Object *obj, bool val, Error **errp)
+{
+    MonitorQMP *mon = MONITOR_QMP(obj);
+
+    mon->pretty = val;
+}
+
 static void monitor_qmp_class_init(ObjectClass *cls, const void *data)
 {
+    object_class_property_add_bool(cls, "pretty",
+                                   monitor_qmp_get_pretty,
+                                   monitor_qmp_set_pretty);
 }
 
 static void monitor_qmp_init(Object *obj)
@@ -538,6 +555,7 @@ void monitor_new_qmp(const char *id, const char *chardev_id,
                                         id ? id : autoid,
                                         errp,
                                         "chardev", chardev_id,
+                                        "pretty", pretty ? "yes" : "no",
                                         NULL);
 
     if (!obj) {
@@ -558,8 +576,6 @@ void monitor_new_qmp(const char *id, const char *chardev_id,
                       qemu_chr_has_feature(mon->parent_obj.chr.chr,
                                            QEMU_CHAR_FEATURE_GCONTEXT));
 
-    mon->pretty = pretty;
-
     qemu_mutex_init(&mon->qmp_queue_lock);
     mon->qmp_requests = g_queue_new();
 
-- 
2.54.0



^ permalink raw reply related	[flat|nested] 49+ messages in thread

* [PATCH v6 09/34] monitor: remove 'skip_flush' field
  2026-07-02 16:03 [PATCH v6 00/34] monitor: turn QMP and HMP into QOM objects Daniel P. Berrangé
                   ` (7 preceding siblings ...)
  2026-07-02 16:03 ` [PATCH v6 08/34] monitor: add 'pretty' property to QMP " Daniel P. Berrangé
@ 2026-07-02 16:03 ` Daniel P. Berrangé
  2026-07-02 16:03 ` [PATCH v6 10/34] monitor: move monitor_data_(init|destroy) into QOM init/finalize Daniel P. Berrangé
                   ` (24 subsequent siblings)
  33 siblings, 0 replies; 49+ messages in thread
From: Daniel P. Berrangé @ 2026-07-02 16:03 UTC (permalink / raw)
  To: qemu-devel
  Cc: Paolo Bonzini, Dr. David Alan Gilbert, Markus Armbruster,
	Marc-André Lureau, Alex Bennée,
	Philippe Mathieu-Daudé, Christian Brauner, devel,
	Daniel P. Berrangé, Peter Krempa

The 'skip_flush' field is set on the dummy throwaway HMP monitor
object created by QMP's  'human-monitor-command', as an indication
not to try to write data to the chardev. Instead the QMP command
impl will grab the data straight out of the in-memory buffer.

The flag is redundant, however, as the monitor code could instead
simply check the 'fe_is_open' field on the CharFrontend, which
will be false in the same scenarios that 'skip_flush' is true.

Reviewed-by: Marc-André Lureau <marcandre.lureau@redhat.com>
Tested-by: Peter Krempa <pkrempa@redhat.com>
Signed-off-by: Daniel P. Berrangé <berrange@redhat.com>
---
 monitor/hmp.c              |  2 +-
 monitor/monitor-internal.h |  4 +---
 monitor/monitor.c          | 11 +++++++----
 monitor/qmp-cmds.c         |  2 +-
 monitor/qmp.c              |  2 +-
 5 files changed, 11 insertions(+), 10 deletions(-)

diff --git a/monitor/hmp.c b/monitor/hmp.c
index 260e4b40fe..dd53239478 100644
--- a/monitor/hmp.c
+++ b/monitor/hmp.c
@@ -1590,7 +1590,7 @@ void monitor_new_hmp(const char *id, const char *chardev_id,
         return;
     }
 
-    monitor_data_init(&mon->parent_obj, false, false, false);
+    monitor_data_init(&mon->parent_obj, false, false);
 
     if (mon->use_readline) {
         mon->rs = readline_init(monitor_readline_printf,
diff --git a/monitor/monitor-internal.h b/monitor/monitor-internal.h
index 145d52fd71..d34f9be139 100644
--- a/monitor/monitor-internal.h
+++ b/monitor/monitor-internal.h
@@ -112,7 +112,6 @@ struct Monitor {
     CharFrontend chr;
     int suspend_cnt;            /* Needs to be accessed atomically */
     bool is_qmp;
-    bool skip_flush;
     bool use_io_thread;
 
     char *mon_cpu_path;
@@ -194,8 +193,7 @@ extern QemuMutex monitor_lock;
 extern MonitorList mon_list;
 
 void monitor_complete(Monitor *mon, Error **errp);
-void monitor_data_init(Monitor *mon, bool is_qmp, bool skip_flush,
-                       bool use_io_thread);
+void monitor_data_init(Monitor *mon, bool is_qmp, bool use_io_thread);
 void monitor_data_destroy(Monitor *mon);
 int monitor_can_read(void *opaque);
 void monitor_list_append(Monitor *mon);
diff --git a/monitor/monitor.c b/monitor/monitor.c
index 1648946c92..db9243410a 100644
--- a/monitor/monitor.c
+++ b/monitor/monitor.c
@@ -190,7 +190,12 @@ void monitor_flush_locked(Monitor *mon)
     size_t len;
     const char *buf;
 
-    if (mon->skip_flush) {
+    /*
+     * When used by QMP human-monitor-command, no chardev
+     * will be connected, as we want to just collect the
+     * output in the buffer
+     */
+    if (!mon->chr.fe_is_open) {
         return;
     }
 
@@ -644,8 +649,7 @@ static void monitor_iothread_init(void)
     mon_iothread = iothread_create("mon_iothread", &error_abort);
 }
 
-void monitor_data_init(Monitor *mon, bool is_qmp, bool skip_flush,
-                       bool use_io_thread)
+void monitor_data_init(Monitor *mon, bool is_qmp, bool use_io_thread)
 {
     if (use_io_thread && !mon_iothread) {
         monitor_iothread_init();
@@ -653,7 +657,6 @@ void monitor_data_init(Monitor *mon, bool is_qmp, bool skip_flush,
     qemu_mutex_init(&mon->mon_lock);
     mon->is_qmp = is_qmp;
     mon->outbuf = g_string_new(NULL);
-    mon->skip_flush = skip_flush;
     mon->use_io_thread = use_io_thread;
 }
 
diff --git a/monitor/qmp-cmds.c b/monitor/qmp-cmds.c
index bfde769ef0..0d9adad288 100644
--- a/monitor/qmp-cmds.c
+++ b/monitor/qmp-cmds.c
@@ -168,7 +168,7 @@ char *qmp_human_monitor_command(const char *command_line, bool has_cpu_index,
     char *output = NULL;
     MonitorHMP *hmp = MONITOR_HMP(object_new(TYPE_MONITOR_HMP));
 
-    monitor_data_init(&hmp->parent_obj, false, true, false);
+    monitor_data_init(&hmp->parent_obj, false, false);
 
     if (has_cpu_index) {
         int ret = monitor_set_cpu(&hmp->parent_obj, cpu_index);
diff --git a/monitor/qmp.c b/monitor/qmp.c
index d7adb9ba8b..b300886f33 100644
--- a/monitor/qmp.c
+++ b/monitor/qmp.c
@@ -572,7 +572,7 @@ void monitor_new_qmp(const char *id, const char *chardev_id,
     qemu_chr_fe_set_echo(&mon->parent_obj.chr, true);
 
     /* Note: we run QMP monitor in I/O thread when @chr supports that */
-    monitor_data_init(&mon->parent_obj, true, false,
+    monitor_data_init(&mon->parent_obj, true,
                       qemu_chr_has_feature(mon->parent_obj.chr.chr,
                                            QEMU_CHAR_FEATURE_GCONTEXT));
 
-- 
2.54.0



^ permalink raw reply related	[flat|nested] 49+ messages in thread

* [PATCH v6 10/34] monitor: move monitor_data_(init|destroy) into QOM init/finalize
  2026-07-02 16:03 [PATCH v6 00/34] monitor: turn QMP and HMP into QOM objects Daniel P. Berrangé
                   ` (8 preceding siblings ...)
  2026-07-02 16:03 ` [PATCH v6 09/34] monitor: remove 'skip_flush' field Daniel P. Berrangé
@ 2026-07-02 16:03 ` Daniel P. Berrangé
  2026-07-02 16:03 ` [PATCH v6 11/34] monitor: use class methods for monitor_vprintf Daniel P. Berrangé
                   ` (23 subsequent siblings)
  33 siblings, 0 replies; 49+ messages in thread
From: Daniel P. Berrangé @ 2026-07-02 16:03 UTC (permalink / raw)
  To: qemu-devel
  Cc: Paolo Bonzini, Dr. David Alan Gilbert, Markus Armbruster,
	Marc-André Lureau, Alex Bennée,
	Philippe Mathieu-Daudé, Christian Brauner, devel,
	Daniel P. Berrangé, Peter Krempa

Start to take advantage of QOM, by using object init and finalize
methods to replace monitor_data_init and monitor_data_destroy.

A standalone helper is provided to enable the I/O thread for QMP
where appropriate for the chardev backend.

Reviewed-by: Marc-André Lureau <marcandre.lureau@redhat.com>
Tested-by: Peter Krempa <pkrempa@redhat.com>
Signed-off-by: Daniel P. Berrangé <berrange@redhat.com>
---
 monitor/hmp.c              |  6 +++--
 monitor/monitor-internal.h |  3 +--
 monitor/monitor.c          | 46 +++++++++++++-------------------------
 monitor/qmp-cmds.c         |  3 ---
 monitor/qmp.c              | 34 +++++++++++++++-------------
 5 files changed, 40 insertions(+), 52 deletions(-)

diff --git a/monitor/hmp.c b/monitor/hmp.c
index dd53239478..d4c2176437 100644
--- a/monitor/hmp.c
+++ b/monitor/hmp.c
@@ -48,6 +48,10 @@ OBJECT_DEFINE_TYPE(MonitorHMP, monitor_hmp, MONITOR_HMP, MONITOR);
 
 static void monitor_hmp_finalize(Object *obj)
 {
+    MonitorHMP *mon = MONITOR_HMP(obj);
+    if (mon->rs) {
+        readline_free(mon->rs);
+    }
 }
 
 static bool monitor_hmp_get_readline(Object *obj, Error **errp)
@@ -1590,8 +1594,6 @@ void monitor_new_hmp(const char *id, const char *chardev_id,
         return;
     }
 
-    monitor_data_init(&mon->parent_obj, false, false);
-
     if (mon->use_readline) {
         mon->rs = readline_init(monitor_readline_printf,
                                 monitor_readline_flush,
diff --git a/monitor/monitor-internal.h b/monitor/monitor-internal.h
index d34f9be139..6a4c3b0f37 100644
--- a/monitor/monitor-internal.h
+++ b/monitor/monitor-internal.h
@@ -193,8 +193,7 @@ extern QemuMutex monitor_lock;
 extern MonitorList mon_list;
 
 void monitor_complete(Monitor *mon, Error **errp);
-void monitor_data_init(Monitor *mon, bool is_qmp, bool use_io_thread);
-void monitor_data_destroy(Monitor *mon);
+void monitor_iothread_init(Monitor *mon);
 int monitor_can_read(void *opaque);
 void monitor_list_append(Monitor *mon);
 void monitor_fdsets_cleanup(void);
diff --git a/monitor/monitor.c b/monitor/monitor.c
index db9243410a..9fe131983d 100644
--- a/monitor/monitor.c
+++ b/monitor/monitor.c
@@ -82,6 +82,10 @@ static void monitor_finalize(Object *obj)
     Monitor *mon = MONITOR(obj);
 
     g_free(mon->chardev_id);
+    g_free(mon->mon_cpu_path);
+    qemu_chr_fe_deinit(&mon->chr, false);
+    g_string_free(mon->outbuf, true);
+    qemu_mutex_destroy(&mon->mon_lock);
 }
 
 static char *monitor_get_chardev_id(Object *obj, Error **errp)
@@ -108,6 +112,11 @@ static void monitor_class_init(ObjectClass *cls, const void *data)
 
 static void monitor_init(Object *obj)
 {
+    Monitor *mon = MONITOR(obj);
+
+    qemu_mutex_init(&mon->mon_lock);
+    mon->is_qmp = !!object_dynamic_cast(obj, TYPE_MONITOR_QMP);
+    mon->outbuf = g_string_new(NULL);
 }
 
 Monitor *monitor_cur(void)
@@ -639,38 +648,16 @@ void monitor_list_append(Monitor *mon)
     qemu_mutex_unlock(&monitor_lock);
 
     if (mon) {
-        monitor_data_destroy(mon);
         object_unparent(OBJECT(mon));
     }
 }
 
-static void monitor_iothread_init(void)
-{
-    mon_iothread = iothread_create("mon_iothread", &error_abort);
-}
-
-void monitor_data_init(Monitor *mon, bool is_qmp, bool use_io_thread)
+void monitor_iothread_init(Monitor *mon)
 {
-    if (use_io_thread && !mon_iothread) {
-        monitor_iothread_init();
+    if (!mon_iothread) {
+        mon_iothread = iothread_create("mon_iothread", &error_abort);
     }
-    qemu_mutex_init(&mon->mon_lock);
-    mon->is_qmp = is_qmp;
-    mon->outbuf = g_string_new(NULL);
-    mon->use_io_thread = use_io_thread;
-}
-
-void monitor_data_destroy(Monitor *mon)
-{
-    g_free(mon->mon_cpu_path);
-    qemu_chr_fe_deinit(&mon->chr, false);
-    if (monitor_is_qmp(mon)) {
-        monitor_data_destroy_qmp(container_of(mon, MonitorQMP, parent_obj));
-    } else {
-        readline_free(container_of(mon, MonitorHMP, parent_obj)->rs);
-    }
-    g_string_free(mon->outbuf, true);
-    qemu_mutex_destroy(&mon->mon_lock);
+    mon->use_io_thread = true;
 }
 
 void monitor_cleanup(void)
@@ -688,7 +675,7 @@ void monitor_cleanup(void)
      * Letting the iothread continue while shutting down the dispatcher
      * means that new requests may still be coming in. This is okay,
      * we'll just leave them in the queue without sending a response
-     * and monitor_data_destroy() will free them.
+     * and object finalization will free them.
      */
     WITH_QEMU_LOCK_GUARD(&monitor_lock) {
         qmp_dispatcher_co_shutdown = true;
@@ -702,8 +689,8 @@ void monitor_cleanup(void)
     /*
      * We need to explicitly stop the I/O thread (but not destroy it),
      * clean up the monitor resources, then destroy the I/O thread since
-     * we need to unregister from chardev below in
-     * monitor_data_destroy(), and chardev is not thread-safe yet
+     * we need to unregister from chardev below in object
+     * finalization, and chardev is not thread-safe yet
      */
     if (mon_iothread) {
         iothread_stop(mon_iothread);
@@ -718,7 +705,6 @@ void monitor_cleanup(void)
         /* Permit QAPI event emission from character frontend release */
         qemu_mutex_unlock(&monitor_lock);
         monitor_flush(mon);
-        monitor_data_destroy(mon);
         qemu_mutex_lock(&monitor_lock);
         object_unparent(OBJECT(mon));
     }
diff --git a/monitor/qmp-cmds.c b/monitor/qmp-cmds.c
index 0d9adad288..6cb0b587fb 100644
--- a/monitor/qmp-cmds.c
+++ b/monitor/qmp-cmds.c
@@ -168,8 +168,6 @@ char *qmp_human_monitor_command(const char *command_line, bool has_cpu_index,
     char *output = NULL;
     MonitorHMP *hmp = MONITOR_HMP(object_new(TYPE_MONITOR_HMP));
 
-    monitor_data_init(&hmp->parent_obj, false, false);
-
     if (has_cpu_index) {
         int ret = monitor_set_cpu(&hmp->parent_obj, cpu_index);
         if (ret < 0) {
@@ -186,7 +184,6 @@ char *qmp_human_monitor_command(const char *command_line, bool has_cpu_index,
     }
 
 out:
-    monitor_data_destroy(&hmp->parent_obj);
     object_unref(hmp);
     return output;
 }
diff --git a/monitor/qmp.c b/monitor/qmp.c
index b300886f33..3574b9a339 100644
--- a/monitor/qmp.c
+++ b/monitor/qmp.c
@@ -73,8 +73,16 @@ QmpCommandList qmp_commands, qmp_cap_negotiation_commands;
 
 OBJECT_DEFINE_TYPE(MonitorQMP, monitor_qmp, MONITOR_QMP, MONITOR);
 
+static void monitor_qmp_cleanup_req_queue_locked(MonitorQMP *mon);
+
 static void monitor_qmp_finalize(Object *obj)
 {
+    MonitorQMP *mon = MONITOR_QMP(obj);
+
+    json_message_parser_destroy(&mon->parser);
+    qemu_mutex_destroy(&mon->qmp_queue_lock);
+    monitor_qmp_cleanup_req_queue_locked(mon);
+    g_queue_free(mon->qmp_requests);
 }
 
 static bool monitor_qmp_get_pretty(Object *obj, Error **errp)
@@ -98,8 +106,15 @@ static void monitor_qmp_class_init(ObjectClass *cls, const void *data)
                                    monitor_qmp_set_pretty);
 }
 
+static void handle_qmp_command(void *opaque, QObject *req, Error *err);
 static void monitor_qmp_init(Object *obj)
 {
+    MonitorQMP *mon = MONITOR_QMP(obj);
+
+    qemu_mutex_init(&mon->qmp_queue_lock);
+    mon->qmp_requests = g_queue_new();
+
+    json_message_parser_init(&mon->parser, handle_qmp_command, mon, NULL);
 }
 
 static bool qmp_oob_enabled(MonitorQMP *mon)
@@ -522,14 +537,6 @@ static void monitor_qmp_event(void *opaque, QEMUChrEvent event)
     }
 }
 
-void monitor_data_destroy_qmp(MonitorQMP *mon)
-{
-    json_message_parser_destroy(&mon->parser);
-    qemu_mutex_destroy(&mon->qmp_queue_lock);
-    monitor_qmp_cleanup_req_queue_locked(mon);
-    g_queue_free(mon->qmp_requests);
-}
-
 static void monitor_qmp_setup_handlers_bh(void *opaque)
 {
     MonitorQMP *mon = opaque;
@@ -572,14 +579,11 @@ void monitor_new_qmp(const char *id, const char *chardev_id,
     qemu_chr_fe_set_echo(&mon->parent_obj.chr, true);
 
     /* Note: we run QMP monitor in I/O thread when @chr supports that */
-    monitor_data_init(&mon->parent_obj, true,
-                      qemu_chr_has_feature(mon->parent_obj.chr.chr,
-                                           QEMU_CHAR_FEATURE_GCONTEXT));
-
-    qemu_mutex_init(&mon->qmp_queue_lock);
-    mon->qmp_requests = g_queue_new();
+    if (qemu_chr_has_feature(mon->parent_obj.chr.chr,
+                             QEMU_CHAR_FEATURE_GCONTEXT)) {
+        monitor_iothread_init(&mon->parent_obj);
+    }
 
-    json_message_parser_init(&mon->parser, handle_qmp_command, mon, NULL);
     if (mon->parent_obj.use_io_thread) {
         /*
          * Make sure the old iowatch is gone.  It's possible when
-- 
2.54.0



^ permalink raw reply related	[flat|nested] 49+ messages in thread

* [PATCH v6 11/34] monitor: use class methods for monitor_vprintf
  2026-07-02 16:03 [PATCH v6 00/34] monitor: turn QMP and HMP into QOM objects Daniel P. Berrangé
                   ` (9 preceding siblings ...)
  2026-07-02 16:03 ` [PATCH v6 10/34] monitor: move monitor_data_(init|destroy) into QOM init/finalize Daniel P. Berrangé
@ 2026-07-02 16:03 ` Daniel P. Berrangé
  2026-07-02 16:03 ` [PATCH v6 12/34] monitor: use class methods for monitor_qapi_event_emit Daniel P. Berrangé
                   ` (22 subsequent siblings)
  33 siblings, 0 replies; 49+ messages in thread
From: Daniel P. Berrangé @ 2026-07-02 16:03 UTC (permalink / raw)
  To: qemu-devel
  Cc: Paolo Bonzini, Dr. David Alan Gilbert, Markus Armbruster,
	Marc-André Lureau, Alex Bennée,
	Philippe Mathieu-Daudé, Christian Brauner, devel,
	Daniel P. Berrangé, Peter Krempa

This removes the need for using monitor_is_qmp() to check the
subclass type, which is an anti-pattern.

Reviewed-by: Marc-André Lureau <marcandre.lureau@redhat.com>
Reviewed-by: Dr. David Alan Gilbert <dave@treblig.org>
Tested-by: Peter Krempa <pkrempa@redhat.com>
Signed-off-by: Daniel P. Berrangé <berrange@redhat.com>
---
 monitor/hmp.c              | 13 +++++++++++++
 monitor/monitor-internal.h |  7 +++++++
 monitor/monitor.c          | 11 ++++-------
 3 files changed, 24 insertions(+), 7 deletions(-)

diff --git a/monitor/hmp.c b/monitor/hmp.c
index d4c2176437..4ca70a2ab3 100644
--- a/monitor/hmp.c
+++ b/monitor/hmp.c
@@ -68,11 +68,18 @@ static void monitor_hmp_set_readline(Object *obj, bool val, Error **errp)
     mon->use_readline = val;
 }
 
+int monitor_hmp_vprintf(Monitor *mon, const char *fmt, va_list ap)
+    G_GNUC_PRINTF(2, 0);
+
 static void monitor_hmp_class_init(ObjectClass *cls, const void *data)
 {
+    MonitorClass *moncls = MONITOR_CLASS(cls);
+
     object_class_property_add_bool(cls, "readline",
                                    monitor_hmp_get_readline,
                                    monitor_hmp_set_readline);
+
+    moncls->vprintf = monitor_hmp_vprintf;
 }
 
 static void monitor_hmp_init(Object *obj)
@@ -87,6 +94,12 @@ static void monitor_hmp_init(Object *obj)
     hmp->use_readline = true;
 }
 
+int monitor_hmp_vprintf(Monitor *mon, const char *fmt, va_list ap)
+{
+    g_autofree char *buf = g_strdup_vprintf(fmt, ap);
+    return monitor_puts(mon, buf);
+}
+
 static void monitor_command_cb(void *opaque, const char *cmdline,
                                void *readline_opaque)
 {
diff --git a/monitor/monitor-internal.h b/monitor/monitor-internal.h
index 6a4c3b0f37..23cef68153 100644
--- a/monitor/monitor-internal.h
+++ b/monitor/monitor-internal.h
@@ -104,6 +104,13 @@ typedef struct HMPCommand {
 
 struct MonitorClass {
     ObjectClass parent_class;
+
+    /*
+     * If non-NULL, the monitor is able to print messages
+     * for attention of the client user
+     */
+    int (*vprintf)(Monitor *mon, const char *fmt, va_list ap)
+        G_GNUC_PRINTF(2, 0);
 };
 
 struct Monitor {
diff --git a/monitor/monitor.c b/monitor/monitor.c
index 9fe131983d..0cb0930bf4 100644
--- a/monitor/monitor.c
+++ b/monitor/monitor.c
@@ -264,21 +264,18 @@ int monitor_puts(Monitor *mon, const char *str)
 
 int monitor_vprintf(Monitor *mon, const char *fmt, va_list ap)
 {
-    char *buf;
-    int n;
+    MonitorClass *moncls;
 
     if (!mon) {
         return -1;
     }
 
-    if (monitor_is_qmp(mon)) {
+    moncls = MONITOR_GET_CLASS(mon);
+    if (!moncls->vprintf) {
         return -1;
     }
 
-    buf = g_strdup_vprintf(fmt, ap);
-    n = monitor_puts(mon, buf);
-    g_free(buf);
-    return n;
+    return moncls->vprintf(mon, fmt, ap);
 }
 
 int monitor_printf(Monitor *mon, const char *fmt, ...)
-- 
2.54.0



^ permalink raw reply related	[flat|nested] 49+ messages in thread

* [PATCH v6 12/34] monitor: use class methods for monitor_qapi_event_emit
  2026-07-02 16:03 [PATCH v6 00/34] monitor: turn QMP and HMP into QOM objects Daniel P. Berrangé
                   ` (10 preceding siblings ...)
  2026-07-02 16:03 ` [PATCH v6 11/34] monitor: use class methods for monitor_vprintf Daniel P. Berrangé
@ 2026-07-02 16:03 ` Daniel P. Berrangé
  2026-07-02 16:03 ` [PATCH v6 13/34] monitor: use class methods for monitor_accept_input Daniel P. Berrangé
                   ` (21 subsequent siblings)
  33 siblings, 0 replies; 49+ messages in thread
From: Daniel P. Berrangé @ 2026-07-02 16:03 UTC (permalink / raw)
  To: qemu-devel
  Cc: Paolo Bonzini, Dr. David Alan Gilbert, Markus Armbruster,
	Marc-André Lureau, Alex Bennée,
	Philippe Mathieu-Daudé, Christian Brauner, devel,
	Daniel P. Berrangé, Peter Krempa

This removes the need for using monitor_is_qmp() to check the
subclass type, which is an anti-pattern.

Reviewed-by: Marc-André Lureau <marcandre.lureau@redhat.com>
Tested-by: Peter Krempa <pkrempa@redhat.com>
Signed-off-by: Daniel P. Berrangé <berrange@redhat.com>
---
 include/monitor/monitor.h  |  1 +
 monitor/monitor-internal.h |  5 +++++
 monitor/monitor.c          | 15 +++------------
 monitor/qmp.c              | 20 ++++++++++++++++++++
 4 files changed, 29 insertions(+), 12 deletions(-)

diff --git a/include/monitor/monitor.h b/include/monitor/monitor.h
index bfbeedec9b..821465f17e 100644
--- a/include/monitor/monitor.h
+++ b/include/monitor/monitor.h
@@ -3,6 +3,7 @@
 
 #include "block/block.h"
 #include "qapi/qapi-types-misc.h"
+#include "qapi/qapi-emit-events.h"
 #include "qemu/readline.h"
 #include "exec/hwaddr.h"
 #include "qom/object.h"
diff --git a/monitor/monitor-internal.h b/monitor/monitor-internal.h
index 23cef68153..763b5c9625 100644
--- a/monitor/monitor-internal.h
+++ b/monitor/monitor-internal.h
@@ -111,6 +111,11 @@ struct MonitorClass {
      */
     int (*vprintf)(Monitor *mon, const char *fmt, va_list ap)
         G_GNUC_PRINTF(2, 0);
+    /*
+     * If non-NULL, the monitor is able to send event
+     * notifications back to the client
+     */
+    void (*emit_event)(Monitor *mon, QAPIEvent event, QDict *qdict);
 };
 
 struct Monitor {
diff --git a/monitor/monitor.c b/monitor/monitor.c
index 0cb0930bf4..90cfb59c66 100644
--- a/monitor/monitor.c
+++ b/monitor/monitor.c
@@ -348,22 +348,13 @@ static inline QEMUClockType monitor_get_event_clock(void)
 static void monitor_qapi_event_emit(QAPIEvent event, QDict *qdict)
 {
     Monitor *mon;
-    MonitorQMP *qmp_mon;
 
     trace_monitor_protocol_event_emit(event, qdict);
     QTAILQ_FOREACH(mon, &mon_list, entry) {
-        if (!monitor_is_qmp(mon)) {
-            continue;
+        MonitorClass *cls = MONITOR_GET_CLASS(mon);
+        if (cls->emit_event) {
+            cls->emit_event(mon, event, qdict);
         }
-
-        qmp_mon = container_of(mon, MonitorQMP, parent_obj);
-        {
-            QEMU_LOCK_GUARD(&mon->mon_lock);
-            if (qmp_mon->commands == &qmp_cap_negotiation_commands) {
-                continue;
-            }
-        }
-        qmp_send_response(qmp_mon, qdict);
     }
 }
 
diff --git a/monitor/qmp.c b/monitor/qmp.c
index 3574b9a339..d732fff9a3 100644
--- a/monitor/qmp.c
+++ b/monitor/qmp.c
@@ -99,11 +99,17 @@ static void monitor_qmp_set_pretty(Object *obj, bool val, Error **errp)
     mon->pretty = val;
 }
 
+static void monitor_qmp_emit_event(Monitor *mon, QAPIEvent event, QDict *qdict);
+
 static void monitor_qmp_class_init(ObjectClass *cls, const void *data)
 {
+    MonitorClass *moncls = MONITOR_CLASS(cls);
+
     object_class_property_add_bool(cls, "pretty",
                                    monitor_qmp_get_pretty,
                                    monitor_qmp_set_pretty);
+
+    moncls->emit_event = monitor_qmp_emit_event;
 }
 
 static void handle_qmp_command(void *opaque, QObject *req, Error *err);
@@ -117,6 +123,20 @@ static void monitor_qmp_init(Object *obj)
     json_message_parser_init(&mon->parser, handle_qmp_command, mon, NULL);
 }
 
+static void monitor_qmp_emit_event(Monitor *mon, QAPIEvent event, QDict *qdict)
+{
+    MonitorQMP *qmp = MONITOR_QMP(mon);
+
+    WITH_QEMU_LOCK_GUARD(&mon->mon_lock) {
+        if (qmp->commands == &qmp_cap_negotiation_commands) {
+            return;
+        }
+    }
+
+    qmp_send_response(qmp, qdict);
+}
+
+
 static bool qmp_oob_enabled(MonitorQMP *mon)
 {
     return mon->capab[QMP_CAPABILITY_OOB];
-- 
2.54.0



^ permalink raw reply related	[flat|nested] 49+ messages in thread

* [PATCH v6 13/34] monitor: use class methods for monitor_accept_input
  2026-07-02 16:03 [PATCH v6 00/34] monitor: turn QMP and HMP into QOM objects Daniel P. Berrangé
                   ` (11 preceding siblings ...)
  2026-07-02 16:03 ` [PATCH v6 12/34] monitor: use class methods for monitor_qapi_event_emit Daniel P. Berrangé
@ 2026-07-02 16:03 ` Daniel P. Berrangé
  2026-07-02 16:03 ` [PATCH v6 14/34] monitor: use class method for I/O thread request Daniel P. Berrangé
                   ` (20 subsequent siblings)
  33 siblings, 0 replies; 49+ messages in thread
From: Daniel P. Berrangé @ 2026-07-02 16:03 UTC (permalink / raw)
  To: qemu-devel
  Cc: Paolo Bonzini, Dr. David Alan Gilbert, Markus Armbruster,
	Marc-André Lureau, Alex Bennée,
	Philippe Mathieu-Daudé, Christian Brauner, devel,
	Daniel P. Berrangé, Peter Krempa

This removes the need for using monitor_is_qmp() to check the
subclass type, which is an anti-pattern.

Reviewed-by: Marc-André Lureau <marcandre.lureau@redhat.com>
Tested-by: Peter Krempa <pkrempa@redhat.com>
Signed-off-by: Daniel P. Berrangé <berrange@redhat.com>
---
 monitor/hmp.c              | 16 ++++++++++++++++
 monitor/monitor-internal.h |  5 +++++
 monitor/monitor.c          | 12 +++---------
 3 files changed, 24 insertions(+), 9 deletions(-)

diff --git a/monitor/hmp.c b/monitor/hmp.c
index 4ca70a2ab3..abff9b7bf3 100644
--- a/monitor/hmp.c
+++ b/monitor/hmp.c
@@ -70,6 +70,7 @@ static void monitor_hmp_set_readline(Object *obj, bool val, Error **errp)
 
 int monitor_hmp_vprintf(Monitor *mon, const char *fmt, va_list ap)
     G_GNUC_PRINTF(2, 0);
+static void monitor_hmp_accept_input(Monitor *mon);
 
 static void monitor_hmp_class_init(ObjectClass *cls, const void *data)
 {
@@ -80,6 +81,7 @@ static void monitor_hmp_class_init(ObjectClass *cls, const void *data)
                                    monitor_hmp_set_readline);
 
     moncls->vprintf = monitor_hmp_vprintf;
+    moncls->accept_input = monitor_hmp_accept_input;
 }
 
 static void monitor_hmp_init(Object *obj)
@@ -100,6 +102,20 @@ int monitor_hmp_vprintf(Monitor *mon, const char *fmt, va_list ap)
     return monitor_puts(mon, buf);
 }
 
+static void monitor_hmp_accept_input(Monitor *mon)
+{
+    qemu_mutex_lock(&mon->mon_lock);
+    if (mon->reset_seen) {
+        MonitorHMP *hmp = MONITOR_HMP(mon);
+        assert(hmp->rs);
+        readline_restart(hmp->rs);
+        qemu_mutex_unlock(&mon->mon_lock);
+        readline_show_prompt(hmp->rs);
+    } else {
+        qemu_mutex_unlock(&mon->mon_lock);
+    }
+}
+
 static void monitor_command_cb(void *opaque, const char *cmdline,
                                void *readline_opaque)
 {
diff --git a/monitor/monitor-internal.h b/monitor/monitor-internal.h
index 763b5c9625..592f146331 100644
--- a/monitor/monitor-internal.h
+++ b/monitor/monitor-internal.h
@@ -116,6 +116,11 @@ struct MonitorClass {
      * notifications back to the client
      */
     void (*emit_event)(Monitor *mon, QAPIEvent event, QDict *qdict);
+    /*
+     * If non-NULL, perform any actions needed to prepare
+     * the monitor to accept further client input
+     */
+    void (*accept_input)(Monitor *mon);
 };
 
 struct Monitor {
diff --git a/monitor/monitor.c b/monitor/monitor.c
index 90cfb59c66..6e63e7a5c7 100644
--- a/monitor/monitor.c
+++ b/monitor/monitor.c
@@ -578,16 +578,10 @@ int monitor_suspend(Monitor *mon)
 static void monitor_accept_input(void *opaque)
 {
     Monitor *mon = opaque;
+    MonitorClass *cls = MONITOR_GET_CLASS(mon);
 
-    qemu_mutex_lock(&mon->mon_lock);
-    if (!monitor_is_qmp(mon) && mon->reset_seen) {
-        MonitorHMP *hmp_mon = container_of(mon, MonitorHMP, parent_obj);
-        assert(hmp_mon->rs);
-        readline_restart(hmp_mon->rs);
-        qemu_mutex_unlock(&mon->mon_lock);
-        readline_show_prompt(hmp_mon->rs);
-    } else {
-        qemu_mutex_unlock(&mon->mon_lock);
+    if (cls->accept_input) {
+        cls->accept_input(mon);
     }
 
     qemu_chr_fe_accept_input(&mon->chr);
-- 
2.54.0



^ permalink raw reply related	[flat|nested] 49+ messages in thread

* [PATCH v6 14/34] monitor: use class method for I/O thread request
  2026-07-02 16:03 [PATCH v6 00/34] monitor: turn QMP and HMP into QOM objects Daniel P. Berrangé
                   ` (12 preceding siblings ...)
  2026-07-02 16:03 ` [PATCH v6 13/34] monitor: use class methods for monitor_accept_input Daniel P. Berrangé
@ 2026-07-02 16:03 ` Daniel P. Berrangé
  2026-07-02 16:03 ` [PATCH v6 15/34] monitor: use dynamic cast in monitor_qmp_requests_pop_any_with_lock Daniel P. Berrangé
                   ` (19 subsequent siblings)
  33 siblings, 0 replies; 49+ messages in thread
From: Daniel P. Berrangé @ 2026-07-02 16:03 UTC (permalink / raw)
  To: qemu-devel
  Cc: Paolo Bonzini, Dr. David Alan Gilbert, Markus Armbruster,
	Marc-André Lureau, Alex Bennée,
	Philippe Mathieu-Daudé, Christian Brauner, devel,
	Daniel P. Berrangé, Peter Krempa

Introducing a virtual "requires_iothread" method allows the code to
automatically initialize the I/O thread during object completion.

Reviewed-by: Marc-André Lureau <marcandre.lureau@redhat.com>
Tested-by: Peter Krempa <pkrempa@redhat.com>
Signed-off-by: Daniel P. Berrangé <berrange@redhat.com>
---
 monitor/monitor-internal.h | 14 +++++++++-----
 monitor/monitor.c          | 22 ++++++++++++----------
 monitor/qmp.c              | 20 +++++++++++---------
 3 files changed, 32 insertions(+), 24 deletions(-)

diff --git a/monitor/monitor-internal.h b/monitor/monitor-internal.h
index 592f146331..efd21b8912 100644
--- a/monitor/monitor-internal.h
+++ b/monitor/monitor-internal.h
@@ -121,6 +121,12 @@ struct MonitorClass {
      * the monitor to accept further client input
      */
     void (*accept_input)(Monitor *mon);
+
+    /*
+     * If non-NULL and returns true, then an I/O thread
+     * is required for processing the monitor
+     */
+    bool (*requires_iothread)(const Monitor *mon);
 };
 
 struct Monitor {
@@ -129,7 +135,6 @@ struct Monitor {
     CharFrontend chr;
     int suspend_cnt;            /* Needs to be accessed atomically */
     bool is_qmp;
-    bool use_io_thread;
 
     char *mon_cpu_path;
     QTAILQ_ENTRY(Monitor) entry;
@@ -159,9 +164,8 @@ struct MonitorHMP {
     bool use_readline;
     /*
      * State used only in the thread "owning" the monitor.
-     * If @use_io_thread, this is @mon_iothread. (This does not actually happen
-     * in the current state of the code.)
-     * Else, it's the main thread.
+     * This is currently always the main thread, since
+     * HMP does not allow use of the I/O thread at this time.
      * These members can be safely accessed without locks.
      */
     ReadLineState *rs;
@@ -210,7 +214,7 @@ extern QemuMutex monitor_lock;
 extern MonitorList mon_list;
 
 void monitor_complete(Monitor *mon, Error **errp);
-void monitor_iothread_init(Monitor *mon);
+bool monitor_requires_iothread(const Monitor *mon);
 int monitor_can_read(void *opaque);
 void monitor_list_append(Monitor *mon);
 void monitor_fdsets_cleanup(void);
diff --git a/monitor/monitor.c b/monitor/monitor.c
index 6e63e7a5c7..1b2bdcf68f 100644
--- a/monitor/monitor.c
+++ b/monitor/monitor.c
@@ -162,6 +162,12 @@ bool monitor_cur_is_qmp(void)
     return cur_mon && monitor_is_qmp(cur_mon);
 }
 
+bool monitor_requires_iothread(const Monitor *mon)
+{
+    MonitorClass *cls = MONITOR_GET_CLASS(mon);
+    return cls->requires_iothread && cls->requires_iothread(mon);
+}
+
 /**
  * Is @mon is using readline?
  * Note: not all HMP monitors use readline, e.g., gdbserver has a
@@ -563,7 +569,7 @@ int monitor_suspend(Monitor *mon)
 
     qatomic_inc(&mon->suspend_cnt);
 
-    if (mon->use_io_thread) {
+    if (monitor_requires_iothread(mon)) {
         /*
          * Kick I/O thread to make sure this takes effect.  It'll be
          * evaluated again in prepare() of the watch object.
@@ -596,7 +602,7 @@ void monitor_resume(Monitor *mon)
     if (qatomic_dec_fetch(&mon->suspend_cnt) == 0) {
         AioContext *ctx;
 
-        if (mon->use_io_thread) {
+        if (monitor_requires_iothread(mon)) {
             ctx = iothread_get_aio_context(mon_iothread);
         } else {
             ctx = qemu_get_aio_context();
@@ -634,14 +640,6 @@ void monitor_list_append(Monitor *mon)
     }
 }
 
-void monitor_iothread_init(Monitor *mon)
-{
-    if (!mon_iothread) {
-        mon_iothread = iothread_create("mon_iothread", &error_abort);
-    }
-    mon->use_io_thread = true;
-}
-
 void monitor_cleanup(void)
 {
     /*
@@ -743,6 +741,10 @@ void monitor_complete(Monitor *mon, Error **errp)
             return;
         }
     }
+
+    if (monitor_requires_iothread(mon) && !mon_iothread) {
+        mon_iothread = iothread_create("mon_iothread", &error_abort);
+    }
 }
 
 int monitor_new(MonitorOptions *opts, bool allow_hmp, Error **errp)
diff --git a/monitor/qmp.c b/monitor/qmp.c
index d732fff9a3..8bbe25d8de 100644
--- a/monitor/qmp.c
+++ b/monitor/qmp.c
@@ -100,6 +100,7 @@ static void monitor_qmp_set_pretty(Object *obj, bool val, Error **errp)
 }
 
 static void monitor_qmp_emit_event(Monitor *mon, QAPIEvent event, QDict *qdict);
+static bool monitor_qmp_requires_iothread(const Monitor *mon);
 
 static void monitor_qmp_class_init(ObjectClass *cls, const void *data)
 {
@@ -110,6 +111,7 @@ static void monitor_qmp_class_init(ObjectClass *cls, const void *data)
                                    monitor_qmp_set_pretty);
 
     moncls->emit_event = monitor_qmp_emit_event;
+    moncls->requires_iothread = monitor_qmp_requires_iothread;
 }
 
 static void handle_qmp_command(void *opaque, QObject *req, Error *err);
@@ -136,6 +138,11 @@ static void monitor_qmp_emit_event(Monitor *mon, QAPIEvent event, QDict *qdict)
     qmp_send_response(qmp, qdict);
 }
 
+static bool monitor_qmp_requires_iothread(const Monitor *mon)
+{
+    return qemu_chr_has_feature(mon->chr.chr,
+                                QEMU_CHAR_FEATURE_GCONTEXT);
+}
 
 static bool qmp_oob_enabled(MonitorQMP *mon)
 {
@@ -146,7 +153,8 @@ static void monitor_qmp_caps_reset(MonitorQMP *mon)
 {
     memset(mon->capab_offered, 0, sizeof(mon->capab_offered));
     memset(mon->capab, 0, sizeof(mon->capab));
-    mon->capab_offered[QMP_CAPABILITY_OOB] = mon->parent_obj.use_io_thread;
+    mon->capab_offered[QMP_CAPABILITY_OOB] =
+        monitor_requires_iothread(MONITOR(mon));
 }
 
 static void qmp_request_free(QMPRequest *req)
@@ -562,7 +570,7 @@ static void monitor_qmp_setup_handlers_bh(void *opaque)
     MonitorQMP *mon = opaque;
     GMainContext *context;
 
-    assert(mon->parent_obj.use_io_thread);
+    assert(monitor_requires_iothread(MONITOR(mon)));
     context = iothread_get_g_main_context(mon_iothread);
     assert(context);
     qemu_chr_fe_set_handlers(&mon->parent_obj.chr, monitor_can_read,
@@ -598,13 +606,7 @@ void monitor_new_qmp(const char *id, const char *chardev_id,
 
     qemu_chr_fe_set_echo(&mon->parent_obj.chr, true);
 
-    /* Note: we run QMP monitor in I/O thread when @chr supports that */
-    if (qemu_chr_has_feature(mon->parent_obj.chr.chr,
-                             QEMU_CHAR_FEATURE_GCONTEXT)) {
-        monitor_iothread_init(&mon->parent_obj);
-    }
-
-    if (mon->parent_obj.use_io_thread) {
+    if (monitor_requires_iothread(MONITOR(mon))) {
         /*
          * Make sure the old iowatch is gone.  It's possible when
          * e.g. the chardev is in client mode, with wait=on.
-- 
2.54.0



^ permalink raw reply related	[flat|nested] 49+ messages in thread

* [PATCH v6 15/34] monitor: use dynamic cast in monitor_qmp_requests_pop_any_with_lock
  2026-07-02 16:03 [PATCH v6 00/34] monitor: turn QMP and HMP into QOM objects Daniel P. Berrangé
                   ` (13 preceding siblings ...)
  2026-07-02 16:03 ` [PATCH v6 14/34] monitor: use class method for I/O thread request Daniel P. Berrangé
@ 2026-07-02 16:03 ` Daniel P. Berrangé
  2026-07-02 16:03 ` [PATCH v6 16/34] util: use dynamic cast in error vreport Daniel P. Berrangé
                   ` (18 subsequent siblings)
  33 siblings, 0 replies; 49+ messages in thread
From: Daniel P. Berrangé @ 2026-07-02 16:03 UTC (permalink / raw)
  To: qemu-devel
  Cc: Paolo Bonzini, Dr. David Alan Gilbert, Markus Armbruster,
	Marc-André Lureau, Alex Bennée,
	Philippe Mathieu-Daudé, Christian Brauner, devel,
	Daniel P. Berrangé, Peter Krempa

This eliminates a use of monitor_is_qmp() from the QMP coroutine
dispatch path.

Reviewed-by: Marc-André Lureau <marcandre.lureau@redhat.com>
Tested-by: Peter Krempa <pkrempa@redhat.com>
Signed-off-by: Daniel P. Berrangé <berrange@redhat.com>
---
 monitor/qmp.c | 5 +++--
 1 file changed, 3 insertions(+), 2 deletions(-)

diff --git a/monitor/qmp.c b/monitor/qmp.c
index 8bbe25d8de..bd3e6f8375 100644
--- a/monitor/qmp.c
+++ b/monitor/qmp.c
@@ -277,11 +277,12 @@ static QMPRequest *monitor_qmp_requests_pop_any_with_lock(void)
     MonitorQMP *qmp_mon;
 
     QTAILQ_FOREACH(mon, &mon_list, entry) {
-        if (!monitor_is_qmp(mon)) {
+        qmp_mon = MONITOR_QMP(
+            object_dynamic_cast(OBJECT(mon), TYPE_MONITOR_QMP));
+        if (!qmp_mon) {
             continue;
         }
 
-        qmp_mon = container_of(mon, MonitorQMP, parent_obj);
         qemu_mutex_lock(&qmp_mon->qmp_queue_lock);
         req_obj = g_queue_pop_head(qmp_mon->qmp_requests);
         if (req_obj) {
-- 
2.54.0



^ permalink raw reply related	[flat|nested] 49+ messages in thread

* [PATCH v6 16/34] util: use dynamic cast in error vreport
  2026-07-02 16:03 [PATCH v6 00/34] monitor: turn QMP and HMP into QOM objects Daniel P. Berrangé
                   ` (14 preceding siblings ...)
  2026-07-02 16:03 ` [PATCH v6 15/34] monitor: use dynamic cast in monitor_qmp_requests_pop_any_with_lock Daniel P. Berrangé
@ 2026-07-02 16:03 ` Daniel P. Berrangé
  2026-07-02 16:03 ` [PATCH v6 17/34] monitor: drop unused monitor_cur_is_qmp Daniel P. Berrangé
                   ` (17 subsequent siblings)
  33 siblings, 0 replies; 49+ messages in thread
From: Daniel P. Berrangé @ 2026-07-02 16:03 UTC (permalink / raw)
  To: qemu-devel
  Cc: Paolo Bonzini, Dr. David Alan Gilbert, Markus Armbruster,
	Marc-André Lureau, Alex Bennée,
	Philippe Mathieu-Daudé, Christian Brauner, devel,
	Daniel P. Berrangé, Peter Krempa

This eliminates a use of monitor_is_qmp() from the error reporting
path.

Reviewed-by: Marc-André Lureau <marcandre.lureau@redhat.com>
Tested-by: Peter Krempa <pkrempa@redhat.com>
Signed-off-by: Daniel P. Berrangé <berrange@redhat.com>
---
 util/error-report.c | 13 ++++++-------
 1 file changed, 6 insertions(+), 7 deletions(-)

diff --git a/util/error-report.c b/util/error-report.c
index f832ad9b6b..f333af9249 100644
--- a/util/error-report.c
+++ b/util/error-report.c
@@ -231,16 +231,15 @@ char *real_time_iso8601(void)
 G_GNUC_PRINTF(2, 0)
 static void vreport(report_type type, const char *fmt, va_list ap)
 {
-    Monitor *cur = monitor_cur();
-    gchar *timestr;
-
     /*
      * When current monitor is QMP, messages must go to stderr
-     * and have prefixes added
+     * and have prefixes added, so we cast to HMP, leaving 'cur'
+     * as NULL in QMP case
      */
-    if (monitor_cur_is_qmp()) {
-        cur = NULL;
-    }
+    Monitor *cur = MONITOR(
+        object_dynamic_cast(OBJECT(monitor_cur()), TYPE_MONITOR_HMP));
+    gchar *timestr;
+
     if (!cur) {
         qemu_flockfile(stderr);
     }
-- 
2.54.0



^ permalink raw reply related	[flat|nested] 49+ messages in thread

* [PATCH v6 17/34] monitor: drop unused monitor_cur_is_qmp
  2026-07-02 16:03 [PATCH v6 00/34] monitor: turn QMP and HMP into QOM objects Daniel P. Berrangé
                   ` (15 preceding siblings ...)
  2026-07-02 16:03 ` [PATCH v6 16/34] util: use dynamic cast in error vreport Daniel P. Berrangé
@ 2026-07-02 16:03 ` Daniel P. Berrangé
  2026-07-02 16:03 ` [PATCH v6 18/34] monitor: use dynamic cast in QMP commands Daniel P. Berrangé
                   ` (16 subsequent siblings)
  33 siblings, 0 replies; 49+ messages in thread
From: Daniel P. Berrangé @ 2026-07-02 16:03 UTC (permalink / raw)
  To: qemu-devel
  Cc: Paolo Bonzini, Dr. David Alan Gilbert, Markus Armbruster,
	Marc-André Lureau, Alex Bennée,
	Philippe Mathieu-Daudé, Christian Brauner, devel,
	Daniel P. Berrangé, Peter Krempa

The previous patch dropped the only remaining use of
monitor_cur_is_qmp.

Reviewed-by: Marc-André Lureau <marcandre.lureau@redhat.com>
Tested-by: Peter Krempa <pkrempa@redhat.com>
Signed-off-by: Daniel P. Berrangé <berrange@redhat.com>
---
 include/monitor/monitor.h      |  1 -
 monitor/monitor.c              | 10 ----------
 stubs/monitor-core.c           |  5 -----
 tests/unit/test-util-sockets.c |  1 -
 tools/qemu-vnc/stubs.c         |  5 -----
 5 files changed, 22 deletions(-)

diff --git a/include/monitor/monitor.h b/include/monitor/monitor.h
index 821465f17e..e802e8dfb2 100644
--- a/include/monitor/monitor.h
+++ b/include/monitor/monitor.h
@@ -25,7 +25,6 @@ extern QemuOptsList qemu_mon_opts;
 
 Monitor *monitor_cur(void);
 Monitor *monitor_set_cur(Coroutine *co, Monitor *mon);
-bool monitor_cur_is_qmp(void);
 
 void monitor_init_globals(void);
 void monitor_init_globals_core(void);
diff --git a/monitor/monitor.c b/monitor/monitor.c
index 1b2bdcf68f..a818659392 100644
--- a/monitor/monitor.c
+++ b/monitor/monitor.c
@@ -152,16 +152,6 @@ Monitor *monitor_set_cur(Coroutine *co, Monitor *mon)
     return old_monitor;
 }
 
-/**
- * Is the current monitor, if any, a QMP monitor?
- */
-bool monitor_cur_is_qmp(void)
-{
-    Monitor *cur_mon = monitor_cur();
-
-    return cur_mon && monitor_is_qmp(cur_mon);
-}
-
 bool monitor_requires_iothread(const Monitor *mon)
 {
     MonitorClass *cls = MONITOR_GET_CLASS(mon);
diff --git a/stubs/monitor-core.c b/stubs/monitor-core.c
index 078a5012e9..a7c32297c9 100644
--- a/stubs/monitor-core.c
+++ b/stubs/monitor-core.c
@@ -7,11 +7,6 @@ Monitor *monitor_cur(void)
     return NULL;
 }
 
-bool monitor_cur_is_qmp(void)
-{
-    return false;
-}
-
 Monitor *monitor_set_cur(Coroutine *co, Monitor *mon)
 {
     return NULL;
diff --git a/tests/unit/test-util-sockets.c b/tests/unit/test-util-sockets.c
index b9f2453e29..ee66d727c3 100644
--- a/tests/unit/test-util-sockets.c
+++ b/tests/unit/test-util-sockets.c
@@ -74,7 +74,6 @@ int monitor_get_fd(Monitor *mon, const char *fdname, Error **errp)
 Monitor *monitor_cur(void) { return cur_mon; }
 Monitor *monitor_set_cur(Coroutine *co, Monitor *mon) { abort(); }
 int monitor_vprintf(Monitor *mon, const char *fmt, va_list ap) { abort(); }
-bool monitor_cur_is_qmp(void) { abort(); };
 
 #ifndef _WIN32
 static void test_socket_fd_pass_name_good(void)
diff --git a/tools/qemu-vnc/stubs.c b/tools/qemu-vnc/stubs.c
index a865ce85f0..1c82d8cff4 100644
--- a/tools/qemu-vnc/stubs.c
+++ b/tools/qemu-vnc/stubs.c
@@ -36,11 +36,6 @@ Monitor *monitor_cur(void)
     return NULL;
 }
 
-bool monitor_cur_is_qmp(void)
-{
-    return false;
-}
-
 Monitor *monitor_set_cur(Coroutine *co, Monitor *mon)
 {
     return NULL;
-- 
2.54.0



^ permalink raw reply related	[flat|nested] 49+ messages in thread

* [PATCH v6 18/34] monitor: use dynamic cast in QMP commands
  2026-07-02 16:03 [PATCH v6 00/34] monitor: turn QMP and HMP into QOM objects Daniel P. Berrangé
                   ` (16 preceding siblings ...)
  2026-07-02 16:03 ` [PATCH v6 17/34] monitor: drop unused monitor_cur_is_qmp Daniel P. Berrangé
@ 2026-07-02 16:03 ` Daniel P. Berrangé
  2026-07-02 16:03 ` [PATCH v6 19/34] monitor: use dynamic cast in monitor_is_hmp_non_interactive Daniel P. Berrangé
                   ` (15 subsequent siblings)
  33 siblings, 0 replies; 49+ messages in thread
From: Daniel P. Berrangé @ 2026-07-02 16:03 UTC (permalink / raw)
  To: qemu-devel
  Cc: Paolo Bonzini, Dr. David Alan Gilbert, Markus Armbruster,
	Marc-André Lureau, Alex Bennée,
	Philippe Mathieu-Daudé, Christian Brauner, devel,
	Daniel P. Berrangé, Peter Krempa

Rather than asserting monitor_is_qmp(), use a QOM cast via
MONITOR_QMP which performs an assert already.

Reviewed-by: Marc-André Lureau <marcandre.lureau@redhat.com>
Tested-by: Peter Krempa <pkrempa@redhat.com>
Signed-off-by: Daniel P. Berrangé <berrange@redhat.com>
---
 monitor/qmp-cmds-control.c | 12 ++----------
 1 file changed, 2 insertions(+), 10 deletions(-)

diff --git a/monitor/qmp-cmds-control.c b/monitor/qmp-cmds-control.c
index af6a2a118b..8fe0876a3d 100644
--- a/monitor/qmp-cmds-control.c
+++ b/monitor/qmp-cmds-control.c
@@ -72,11 +72,7 @@ static bool qmp_caps_accept(MonitorQMP *mon, QMPCapabilityList *list,
 void qmp_qmp_capabilities(bool has_enable, QMPCapabilityList *enable,
                           Error **errp)
 {
-    Monitor *cur_mon = monitor_cur();
-    MonitorQMP *mon;
-
-    assert(monitor_is_qmp(cur_mon));
-    mon = container_of(cur_mon, MonitorQMP, parent_obj);
+    MonitorQMP *mon = MONITOR_QMP(monitor_cur());
 
     if (mon->commands == &qmp_commands) {
         error_set(errp, ERROR_CLASS_COMMAND_NOT_FOUND,
@@ -122,11 +118,7 @@ static void query_commands_cb(const QmpCommand *cmd, void *opaque)
 CommandInfoList *qmp_query_commands(Error **errp)
 {
     CommandInfoList *list = NULL;
-    Monitor *cur_mon = monitor_cur();
-    MonitorQMP *mon;
-
-    assert(monitor_is_qmp(cur_mon));
-    mon = container_of(cur_mon, MonitorQMP, parent_obj);
+    MonitorQMP *mon = MONITOR_QMP(monitor_cur());
 
     qmp_for_each_command(mon->commands, query_commands_cb, &list);
 
-- 
2.54.0



^ permalink raw reply related	[flat|nested] 49+ messages in thread

* [PATCH v6 19/34] monitor: use dynamic cast in monitor_is_hmp_non_interactive
  2026-07-02 16:03 [PATCH v6 00/34] monitor: turn QMP and HMP into QOM objects Daniel P. Berrangé
                   ` (17 preceding siblings ...)
  2026-07-02 16:03 ` [PATCH v6 18/34] monitor: use dynamic cast in QMP commands Daniel P. Berrangé
@ 2026-07-02 16:03 ` Daniel P. Berrangé
  2026-07-02 16:03 ` [PATCH v6 20/34] monitor: drop unused monitor_is_qmp method Daniel P. Berrangé
                   ` (14 subsequent siblings)
  33 siblings, 0 replies; 49+ messages in thread
From: Daniel P. Berrangé @ 2026-07-02 16:03 UTC (permalink / raw)
  To: qemu-devel
  Cc: Paolo Bonzini, Dr. David Alan Gilbert, Markus Armbruster,
	Marc-André Lureau, Alex Bennée,
	Philippe Mathieu-Daudé, Christian Brauner, devel,
	Daniel P. Berrangé, Peter Krempa

Rather than checking !monitor_is_qmp(), use a dynamic cast to
check for HMP.

Reviewed-by: Marc-André Lureau <marcandre.lureau@redhat.com>
Tested-by: Peter Krempa <pkrempa@redhat.com>
Signed-off-by: Daniel P. Berrangé <berrange@redhat.com>
---
 monitor/monitor.c | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/monitor/monitor.c b/monitor/monitor.c
index a818659392..6fdd0255f4 100644
--- a/monitor/monitor.c
+++ b/monitor/monitor.c
@@ -170,7 +170,7 @@ static inline bool monitor_uses_readline(const MonitorHMP *mon)
 
 static inline bool monitor_is_hmp_non_interactive(const Monitor *mon)
 {
-    if (monitor_is_qmp(mon)) {
+    if (!object_dynamic_cast(OBJECT(mon), TYPE_MONITOR_HMP)) {
         return false;
     }
 
-- 
2.54.0



^ permalink raw reply related	[flat|nested] 49+ messages in thread

* [PATCH v6 20/34] monitor: drop unused monitor_is_qmp method
  2026-07-02 16:03 [PATCH v6 00/34] monitor: turn QMP and HMP into QOM objects Daniel P. Berrangé
                   ` (18 preceding siblings ...)
  2026-07-02 16:03 ` [PATCH v6 19/34] monitor: use dynamic cast in monitor_is_hmp_non_interactive Daniel P. Berrangé
@ 2026-07-02 16:03 ` Daniel P. Berrangé
  2026-07-02 16:03 ` [PATCH v6 21/34] monitor: eliminate monitor_is_hmp_non_interactive method Daniel P. Berrangé
                   ` (13 subsequent siblings)
  33 siblings, 0 replies; 49+ messages in thread
From: Daniel P. Berrangé @ 2026-07-02 16:03 UTC (permalink / raw)
  To: qemu-devel
  Cc: Paolo Bonzini, Dr. David Alan Gilbert, Markus Armbruster,
	Marc-André Lureau, Alex Bennée,
	Philippe Mathieu-Daudé, Christian Brauner, devel,
	Daniel P. Berrangé, Peter Krempa

The previous patch dropped the only remaining use of
monitor_is_qmp.

Reviewed-by: Marc-André Lureau <marcandre.lureau@redhat.com>
Tested-by: Peter Krempa <pkrempa@redhat.com>
Signed-off-by: Daniel P. Berrangé <berrange@redhat.com>
---
 monitor/monitor-internal.h | 9 ---------
 monitor/monitor.c          | 1 -
 2 files changed, 10 deletions(-)

diff --git a/monitor/monitor-internal.h b/monitor/monitor-internal.h
index efd21b8912..fe5703af6d 100644
--- a/monitor/monitor-internal.h
+++ b/monitor/monitor-internal.h
@@ -134,7 +134,6 @@ struct Monitor {
     char *chardev_id;
     CharFrontend chr;
     int suspend_cnt;            /* Needs to be accessed atomically */
-    bool is_qmp;
 
     char *mon_cpu_path;
     QTAILQ_ENTRY(Monitor) entry;
@@ -197,14 +196,6 @@ struct MonitorQMP {
     GQueue *qmp_requests;
 };
 
-/**
- * Is @mon a QMP monitor?
- */
-static inline bool monitor_is_qmp(const Monitor *mon)
-{
-    return mon->is_qmp;
-}
-
 typedef QTAILQ_HEAD(MonitorList, Monitor) MonitorList;
 extern IOThread *mon_iothread;
 extern Coroutine *qmp_dispatcher_co;
diff --git a/monitor/monitor.c b/monitor/monitor.c
index 6fdd0255f4..9b62716033 100644
--- a/monitor/monitor.c
+++ b/monitor/monitor.c
@@ -115,7 +115,6 @@ static void monitor_init(Object *obj)
     Monitor *mon = MONITOR(obj);
 
     qemu_mutex_init(&mon->mon_lock);
-    mon->is_qmp = !!object_dynamic_cast(obj, TYPE_MONITOR_QMP);
     mon->outbuf = g_string_new(NULL);
 }
 
-- 
2.54.0



^ permalink raw reply related	[flat|nested] 49+ messages in thread

* [PATCH v6 21/34] monitor: eliminate monitor_is_hmp_non_interactive method
  2026-07-02 16:03 [PATCH v6 00/34] monitor: turn QMP and HMP into QOM objects Daniel P. Berrangé
                   ` (19 preceding siblings ...)
  2026-07-02 16:03 ` [PATCH v6 20/34] monitor: drop unused monitor_is_qmp method Daniel P. Berrangé
@ 2026-07-02 16:03 ` Daniel P. Berrangé
  2026-07-02 16:03 ` [PATCH v6 22/34] monitor: implement "user creatable" interface for adding monitors Daniel P. Berrangé
                   ` (12 subsequent siblings)
  33 siblings, 0 replies; 49+ messages in thread
From: Daniel P. Berrangé @ 2026-07-02 16:03 UTC (permalink / raw)
  To: qemu-devel
  Cc: Paolo Bonzini, Dr. David Alan Gilbert, Markus Armbruster,
	Marc-André Lureau, Alex Bennée,
	Philippe Mathieu-Daudé, Christian Brauner, devel,
	Daniel P. Berrangé, Peter Krempa

The monitor_is_hmp_non_interactive method is used by
monitor_suspend and monitor_resume, to make them a no-op
if the HMP does not use readline.

There are only a handful of callers of suspend/resume and
they can be made to skip the call when readline is not
present.

Reviewed-by: Marc-André Lureau <marcandre.lureau@redhat.com>
Tested-by: Peter Krempa <pkrempa@redhat.com>
Signed-off-by: Daniel P. Berrangé <berrange@redhat.com>
---
 include/monitor/monitor.h      |  2 +-
 migration/migration-hmp-cmds.c |  5 ++++-
 monitor/hmp-cmds.c             |  5 ++++-
 monitor/hmp.c                  | 11 ++++++++---
 monitor/monitor.c              | 30 +-----------------------------
 5 files changed, 18 insertions(+), 35 deletions(-)

diff --git a/include/monitor/monitor.h b/include/monitor/monitor.h
index e802e8dfb2..890497b0d2 100644
--- a/include/monitor/monitor.h
+++ b/include/monitor/monitor.h
@@ -37,7 +37,7 @@ int monitor_new(MonitorOptions *opts, bool allow_hmp, Error **errp);
 int monitor_new_opts(QemuOpts *opts, Error **errp);
 void monitor_cleanup(void);
 
-int monitor_suspend(Monitor *mon);
+void monitor_suspend(Monitor *mon);
 void monitor_resume(Monitor *mon);
 
 int monitor_get_fd(Monitor *mon, const char *fdname, Error **errp);
diff --git a/migration/migration-hmp-cmds.c b/migration/migration-hmp-cmds.c
index 8b385f560e..502ca704da 100644
--- a/migration/migration-hmp-cmds.c
+++ b/migration/migration-hmp-cmds.c
@@ -19,6 +19,7 @@
 #include "monitor/hmp.h"
 #include "monitor/hmp-completion.h"
 #include "monitor/monitor.h"
+#include "monitor/monitor-internal.h"
 #include "qapi/error.h"
 #include "qapi/qapi-commands-migration.h"
 #include "qapi/qapi-visit-migration.h"
@@ -853,12 +854,14 @@ void hmp_migrate(Monitor *mon, const QDict *qdict)
 
     if (!detach) {
         HMPMigrationStatus *status;
+        MonitorHMP *hmp = MONITOR_HMP(mon);
 
-        if (monitor_suspend(mon) < 0) {
+        if (!hmp->use_readline) {
             monitor_printf(mon, "terminal does not allow synchronous "
                            "migration, continuing detached\n");
             return;
         }
+        monitor_suspend(mon);
 
         status = g_malloc0(sizeof(*status));
         status->mon = mon;
diff --git a/monitor/hmp-cmds.c b/monitor/hmp-cmds.c
index e139caeba9..e9fb8d827a 100644
--- a/monitor/hmp-cmds.c
+++ b/monitor/hmp-cmds.c
@@ -129,7 +129,10 @@ void hmp_info_version(Monitor *mon, const QDict *qdict)
 
 void hmp_quit(Monitor *mon, const QDict *qdict)
 {
-    monitor_suspend(mon);
+    MonitorHMP *hmp = MONITOR_HMP(mon);
+    if (hmp->use_readline) {
+        monitor_suspend(mon);
+    }
     qmp_quit(NULL);
 }
 
diff --git a/monitor/hmp.c b/monitor/hmp.c
index abff9b7bf3..ed903d40d5 100644
--- a/monitor/hmp.c
+++ b/monitor/hmp.c
@@ -1528,13 +1528,16 @@ static void monitor_read(void *opaque, const uint8_t *buf, int size)
 static void monitor_event(void *opaque, QEMUChrEvent event)
 {
     Monitor *mon = opaque;
+    MonitorHMP *hmp = MONITOR_HMP(mon);
 
     switch (event) {
     case CHR_EVENT_MUX_IN:
         qemu_mutex_lock(&mon->mon_lock);
         if (mon->mux_out) {
             mon->mux_out = 0;
-            monitor_resume(mon);
+            if (hmp->use_readline) {
+                monitor_resume(mon);
+            }
         }
         qemu_mutex_unlock(&mon->mon_lock);
         break;
@@ -1547,7 +1550,9 @@ static void monitor_event(void *opaque, QEMUChrEvent event)
             } else {
                 monitor_flush_locked(mon);
             }
-            monitor_suspend(mon);
+            if (hmp->use_readline) {
+                monitor_suspend(mon);
+            }
             mon->mux_out = 1;
         }
         qemu_mutex_unlock(&mon->mon_lock);
@@ -1558,7 +1563,7 @@ static void monitor_event(void *opaque, QEMUChrEvent event)
                        "information\n", QEMU_VERSION);
         qemu_mutex_lock(&mon->mon_lock);
         mon->reset_seen = 1;
-        if (!mon->mux_out) {
+        if (!mon->mux_out && hmp->use_readline) {
             /* Suspend-resume forces the prompt to be printed.  */
             monitor_suspend(mon);
             monitor_resume(mon);
diff --git a/monitor/monitor.c b/monitor/monitor.c
index 9b62716033..f1b189c1c2 100644
--- a/monitor/monitor.c
+++ b/monitor/monitor.c
@@ -157,25 +157,6 @@ bool monitor_requires_iothread(const Monitor *mon)
     return cls->requires_iothread && cls->requires_iothread(mon);
 }
 
-/**
- * Is @mon is using readline?
- * Note: not all HMP monitors use readline, e.g., gdbserver has a
- * non-interactive HMP monitor, so readline is not used there.
- */
-static inline bool monitor_uses_readline(const MonitorHMP *mon)
-{
-    return mon->use_readline;
-}
-
-static inline bool monitor_is_hmp_non_interactive(const Monitor *mon)
-{
-    if (!object_dynamic_cast(OBJECT(mon), TYPE_MONITOR_HMP)) {
-        return false;
-    }
-
-    return !monitor_uses_readline(container_of(mon, MonitorHMP, parent_obj));
-}
-
 static gboolean monitor_unblocked(void *do_not_use, GIOCondition cond,
                                   void *opaque)
 {
@@ -550,12 +531,8 @@ static gboolean qapi_event_throttle_equal(const void *a, const void *b)
     return TRUE;
 }
 
-int monitor_suspend(Monitor *mon)
+void monitor_suspend(Monitor *mon)
 {
-    if (monitor_is_hmp_non_interactive(mon)) {
-        return -ENOTTY;
-    }
-
     qatomic_inc(&mon->suspend_cnt);
 
     if (monitor_requires_iothread(mon)) {
@@ -567,7 +544,6 @@ int monitor_suspend(Monitor *mon)
     }
 
     trace_monitor_suspend(mon, 1);
-    return 0;
 }
 
 static void monitor_accept_input(void *opaque)
@@ -584,10 +560,6 @@ static void monitor_accept_input(void *opaque)
 
 void monitor_resume(Monitor *mon)
 {
-    if (monitor_is_hmp_non_interactive(mon)) {
-        return;
-    }
-
     if (qatomic_dec_fetch(&mon->suspend_cnt) == 0) {
         AioContext *ctx;
 
-- 
2.54.0



^ permalink raw reply related	[flat|nested] 49+ messages in thread

* [PATCH v6 22/34] monitor: implement "user creatable" interface for adding monitors
  2026-07-02 16:03 [PATCH v6 00/34] monitor: turn QMP and HMP into QOM objects Daniel P. Berrangé
                   ` (20 preceding siblings ...)
  2026-07-02 16:03 ` [PATCH v6 21/34] monitor: eliminate monitor_is_hmp_non_interactive method Daniel P. Berrangé
@ 2026-07-02 16:03 ` Daniel P. Berrangé
  2026-07-02 16:03 ` [PATCH v6 23/34] monitor: convert from oneshot BH to persistent BH Daniel P. Berrangé
                   ` (11 subsequent siblings)
  33 siblings, 0 replies; 49+ messages in thread
From: Daniel P. Berrangé @ 2026-07-02 16:03 UTC (permalink / raw)
  To: qemu-devel
  Cc: Paolo Bonzini, Dr. David Alan Gilbert, Markus Armbruster,
	Marc-André Lureau, Alex Bennée,
	Philippe Mathieu-Daudé, Christian Brauner, devel,
	Daniel P. Berrangé, Peter Krempa

Implement the user creatable QOM interface and define the monitor-qmp
and monitor-hmp types in QAPI. This unlocks the ability to create them
on the command line with -object or in HMP/QMP with object_add.

For example:

  $QEMU -chardev stdio,id=monchr0 -object monitor-hmp,id=mon0,chrdev=monchr0

Initially the "prepare_delete" callback is hardcoded to return an error
which means -object and object_add can be used, but object_del will fail.
Support for deleting monitors will be introduced in subsequent commits.

Reviewed-by: Marc-André Lureau <marcandre.lureau@redhat.com>
Tested-by: Peter Krempa <pkrempa@redhat.com>
Acked-by: Markus Armbruster <armbru@redhat.com>
Signed-off-by: Daniel P. Berrangé <berrange@redhat.com>
---
 monitor/hmp.c              | 70 ++++++++++++++++++++++++--------------
 monitor/monitor-internal.h |  1 -
 monitor/monitor.c          | 14 ++++++--
 monitor/qmp.c              | 47 ++++++++++++++++---------
 qapi/qom.json              | 43 +++++++++++++++++++++++
 stubs/monitor-internal.c   |  1 +
 system/vl.c                |  8 ++++-
 7 files changed, 138 insertions(+), 46 deletions(-)

diff --git a/monitor/hmp.c b/monitor/hmp.c
index ed903d40d5..d0d298c5d3 100644
--- a/monitor/hmp.c
+++ b/monitor/hmp.c
@@ -40,6 +40,7 @@
 #include "qemu/base-arch-defs.h"
 #include "qemu/target-info.h"
 #include "qemu/units.h"
+#include "qom/object_interfaces.h"
 #include "exec/gdbstub.h"
 #include "system/block-backend.h"
 #include "trace.h"
@@ -71,10 +72,13 @@ static void monitor_hmp_set_readline(Object *obj, bool val, Error **errp)
 int monitor_hmp_vprintf(Monitor *mon, const char *fmt, va_list ap)
     G_GNUC_PRINTF(2, 0);
 static void monitor_hmp_accept_input(Monitor *mon);
+static void monitor_hmp_complete(UserCreatable *uc, Error **errp);
+static bool monitor_hmp_prepare_delete(UserCreatable *uc, Error **errp);
 
 static void monitor_hmp_class_init(ObjectClass *cls, const void *data)
 {
     MonitorClass *moncls = MONITOR_CLASS(cls);
+    UserCreatableClass *ucc = USER_CREATABLE_CLASS(cls);
 
     object_class_property_add_bool(cls, "readline",
                                    monitor_hmp_get_readline,
@@ -82,6 +86,9 @@ static void monitor_hmp_class_init(ObjectClass *cls, const void *data)
 
     moncls->vprintf = monitor_hmp_vprintf;
     moncls->accept_input = monitor_hmp_accept_input;
+
+    ucc->complete = monitor_hmp_complete;
+    ucc->prepare_delete = monitor_hmp_prepare_delete;
 }
 
 static void monitor_hmp_init(Object *obj)
@@ -1605,41 +1612,52 @@ static void monitor_readline_flush(void *opaque)
 void monitor_new_hmp(const char *id, const char *chardev_id,
                      bool use_readline, Error **errp)
 {
-    ERRP_GUARD();
-    MonitorHMP *mon;
     g_autofree char *autoid = id ? NULL : monitor_compat_id();
-    Object *obj = object_new_with_props(TYPE_MONITOR_HMP,
-                                        object_get_objects_root(),
-                                        id ? id : autoid,
-                                        errp,
-                                        "chardev", chardev_id,
-                                        "readline", use_readline ? "yes" : "no",
-                                        NULL);
-
-    if (!obj) {
-        return;
-    }
+    object_new_with_props(TYPE_MONITOR_HMP,
+                          object_get_objects_root(),
+                          id ? id : autoid,
+                          errp,
+                          "chardev", chardev_id,
+                          "readline", use_readline ? "yes" : "no",
+                          NULL);
+}
 
-    mon = MONITOR_HMP(obj);
+static void monitor_hmp_complete(UserCreatable *uc, Error **errp)
+{
+    MonitorHMP *mon = MONITOR_HMP(uc);
+    UserCreatableClass *ucc_parent =
+        USER_CREATABLE_CLASS(
+            object_class_get_parent(
+                OBJECT_CLASS(MONITOR_HMP_GET_CLASS(mon))));
+    ERRP_GUARD();
 
-    monitor_complete(MONITOR(mon), errp);
+    ucc_parent->complete(uc, errp);
     if (*errp) {
-        object_unparent(OBJECT(mon));
         return;
     }
 
-    if (mon->use_readline) {
-        mon->rs = readline_init(monitor_readline_printf,
-                                monitor_readline_flush,
-                                mon,
-                                monitor_find_completion);
-        monitor_read_command(mon, 0);
+    if (mon->parent_obj.chardev_id) {
+        if (mon->use_readline) {
+            mon->rs = readline_init(monitor_readline_printf,
+                                    monitor_readline_flush,
+                                    mon,
+                                    monitor_find_completion);
+            monitor_read_command(mon, 0);
+        }
+
+        qemu_chr_fe_set_handlers(&mon->parent_obj.chr,
+                                 monitor_can_read,
+                                 monitor_read,
+                                 monitor_event, NULL,
+                                 &mon->parent_obj, NULL, true);
+        monitor_list_append(&mon->parent_obj);
     }
+}
 
-    qemu_chr_fe_set_handlers(&mon->parent_obj.chr,
-                             monitor_can_read, monitor_read, monitor_event,
-                             NULL, &mon->parent_obj, NULL, true);
-    monitor_list_append(&mon->parent_obj);
+static bool monitor_hmp_prepare_delete(UserCreatable *uc, Error **errp)
+{
+    error_setg(errp, "Deleting HMP monitors is not supported");
+    return false;
 }
 
 /**
diff --git a/monitor/monitor-internal.h b/monitor/monitor-internal.h
index fe5703af6d..012d442a20 100644
--- a/monitor/monitor-internal.h
+++ b/monitor/monitor-internal.h
@@ -204,7 +204,6 @@ extern QmpCommandList qmp_commands, qmp_cap_negotiation_commands;
 extern QemuMutex monitor_lock;
 extern MonitorList mon_list;
 
-void monitor_complete(Monitor *mon, Error **errp);
 bool monitor_requires_iothread(const Monitor *mon);
 int monitor_can_read(void *opaque);
 void monitor_list_append(Monitor *mon);
diff --git a/monitor/monitor.c b/monitor/monitor.c
index f1b189c1c2..f0ca6b04ed 100644
--- a/monitor/monitor.c
+++ b/monitor/monitor.c
@@ -29,6 +29,7 @@
 #include "qapi/qapi-emit-events.h"
 #include "qapi/qapi-visit-control.h"
 #include "qobject/qdict.h"
+#include "qom/object_interfaces.h"
 #include "qemu/error-report.h"
 #include "qemu/option.h"
 #include "system/qtest.h"
@@ -75,7 +76,8 @@ static bool monitor_destroyed;
 
 int monitor_device_index;
 
-OBJECT_DEFINE_ABSTRACT_TYPE(Monitor, monitor, MONITOR, OBJECT);
+OBJECT_DEFINE_TYPE_EXTENDED(Monitor, monitor, MONITOR, OBJECT, true,
+                            { TYPE_USER_CREATABLE }, {});
 
 static void monitor_finalize(Object *obj)
 {
@@ -103,11 +105,17 @@ static void monitor_set_chardev_id(Object *obj, const char *str, Error **errp)
     mon->chardev_id = g_strdup(str);
 }
 
+static void monitor_complete(UserCreatable *uc, Error **errp);
+
 static void monitor_class_init(ObjectClass *cls, const void *data)
 {
+    UserCreatableClass *ucc = USER_CREATABLE_CLASS(cls);
+
     object_class_property_add_str(cls, "chardev",
                                   monitor_get_chardev_id,
                                   monitor_set_chardev_id);
+
+    ucc->complete = monitor_complete;
 }
 
 static void monitor_init(Object *obj)
@@ -689,8 +697,10 @@ char *monitor_compat_id(void)
     return g_strdup_printf("compat_monitor%d", monitor_device_index++);
 }
 
-void monitor_complete(Monitor *mon, Error **errp)
+static void monitor_complete(UserCreatable *uc, Error **errp)
 {
+    Monitor *mon = MONITOR(uc);
+
     if (mon->chardev_id) {
         Chardev *chr = qemu_chr_find(mon->chardev_id);
         if (chr == NULL) {
diff --git a/monitor/qmp.c b/monitor/qmp.c
index bd3e6f8375..67ee491f5a 100644
--- a/monitor/qmp.c
+++ b/monitor/qmp.c
@@ -31,6 +31,7 @@
 #include "qobject/qdict.h"
 #include "qobject/qjson.h"
 #include "qobject/qlist.h"
+#include "qom/object_interfaces.h"
 #include "trace.h"
 
 /*
@@ -101,10 +102,13 @@ static void monitor_qmp_set_pretty(Object *obj, bool val, Error **errp)
 
 static void monitor_qmp_emit_event(Monitor *mon, QAPIEvent event, QDict *qdict);
 static bool monitor_qmp_requires_iothread(const Monitor *mon);
+static void monitor_qmp_complete(UserCreatable *uc, Error **errp);
+static bool monitor_qmp_prepare_delete(UserCreatable *uc, Error **errp);
 
 static void monitor_qmp_class_init(ObjectClass *cls, const void *data)
 {
     MonitorClass *moncls = MONITOR_CLASS(cls);
+    UserCreatableClass *ucc = USER_CREATABLE_CLASS(cls);
 
     object_class_property_add_bool(cls, "pretty",
                                    monitor_qmp_get_pretty,
@@ -112,6 +116,9 @@ static void monitor_qmp_class_init(ObjectClass *cls, const void *data)
 
     moncls->emit_event = monitor_qmp_emit_event;
     moncls->requires_iothread = monitor_qmp_requires_iothread;
+
+    ucc->complete = monitor_qmp_complete;
+    ucc->prepare_delete = monitor_qmp_prepare_delete;
 }
 
 static void handle_qmp_command(void *opaque, QObject *req, Error *err);
@@ -583,25 +590,27 @@ static void monitor_qmp_setup_handlers_bh(void *opaque)
 void monitor_new_qmp(const char *id, const char *chardev_id,
                      bool pretty, Error **errp)
 {
-    ERRP_GUARD();
-    MonitorQMP *mon;
     g_autofree char *autoid = id ? NULL : monitor_compat_id();
-    Object *obj = object_new_with_props(TYPE_MONITOR_QMP,
-                                        object_get_objects_root(),
-                                        id ? id : autoid,
-                                        errp,
-                                        "chardev", chardev_id,
-                                        "pretty", pretty ? "yes" : "no",
-                                        NULL);
-
-    if (!obj) {
-        return;
-    }
+    object_new_with_props(TYPE_MONITOR_QMP,
+                          object_get_objects_root(),
+                          id ? id : autoid,
+                          errp,
+                          "chardev", chardev_id,
+                          "pretty", pretty ? "yes" : "no",
+                          NULL);
+}
 
-    mon = MONITOR_QMP(obj);
-    monitor_complete(MONITOR(mon), errp);
+static void monitor_qmp_complete(UserCreatable *uc, Error **errp)
+{
+    MonitorQMP *mon = MONITOR_QMP(uc);
+    UserCreatableClass *ucc_parent =
+        USER_CREATABLE_CLASS(
+            object_class_get_parent(
+                OBJECT_CLASS(MONITOR_QMP_GET_CLASS(mon))));
+    ERRP_GUARD();
+
+    ucc_parent->complete(uc, errp);
     if (*errp) {
-        object_unparent(OBJECT(mon));
         return;
     }
 
@@ -633,3 +642,9 @@ void monitor_new_qmp(const char *id, const char *chardev_id,
         monitor_list_append(&mon->parent_obj);
     }
 }
+
+static bool monitor_qmp_prepare_delete(UserCreatable *uc, Error **errp)
+{
+    error_setg(errp, "Deleting QMP monitors is not supported");
+    return false;
+}
diff --git a/qapi/qom.json b/qapi/qom.json
index dd45ac1087..33a827b9fa 100644
--- a/qapi/qom.json
+++ b/qapi/qom.json
@@ -1187,6 +1187,45 @@
   'data': { '*cpu-affinity': ['uint16'],
             '*node-affinity': ['uint16'] } }
 
+##
+# @MonitorProperties:
+#
+# Properties for all monitors
+#
+# @chardev: ID of the character device providing the monitor transport
+#
+# Since: 11.1
+##
+{ 'struct': 'MonitorProperties',
+  'data': { 'chardev': 'str' }}
+
+##
+# @MonitorHMPProperties:
+#
+# Properties for the HMP monitor
+#
+# @readline: whether to enable readline for line editing
+#      (default: true)
+#
+# Since: 11.1
+##
+{ 'struct': 'MonitorHMPProperties',
+  'base': 'MonitorProperties',
+  'data': { '*readline': 'bool' } }
+
+##
+# @MonitorQMPProperties:
+#
+# Properties for the QMP monitor
+#
+# @pretty: whether to pretty print JSON responses (default: false)
+#
+# Since: 11.1
+##
+{ 'struct': 'MonitorQMPProperties',
+  'base': 'MonitorProperties',
+  'data': { '*pretty': 'bool' } }
+
 ##
 # @ObjectType:
 #
@@ -1237,6 +1276,8 @@
     'memory-backend-ram',
     { 'name': 'memory-backend-shm',
       'if': 'CONFIG_POSIX' },
+    'monitor-hmp',
+    'monitor-qmp',
     'pef-guest',
     { 'name': 'pr-manager-helper',
       'if': 'CONFIG_LINUX' },
@@ -1315,6 +1356,8 @@
       'memory-backend-ram':         'MemoryBackendProperties',
       'memory-backend-shm':         { 'type': 'MemoryBackendShmProperties',
                                       'if': 'CONFIG_POSIX' },
+      'monitor-hmp':                'MonitorHMPProperties',
+      'monitor-qmp':                'MonitorQMPProperties',
       'pr-manager-helper':          { 'type': 'PrManagerHelperProperties',
                                       'if': 'CONFIG_LINUX' },
       'qtest':                      'QtestProperties',
diff --git a/stubs/monitor-internal.c b/stubs/monitor-internal.c
index f94b5e5c21..731fad221e 100644
--- a/stubs/monitor-internal.c
+++ b/stubs/monitor-internal.c
@@ -11,4 +11,5 @@ int monitor_get_fd(Monitor *mon, const char *name, Error **errp)
 void monitor_new_hmp(const char *id, const char *chardev_id,
                      bool use_readline, Error **errp)
 {
+    g_assert_not_reached();
 }
diff --git a/system/vl.c b/system/vl.c
index 4737c03872..63bdd06dfa 100644
--- a/system/vl.c
+++ b/system/vl.c
@@ -1829,6 +1829,10 @@ static void object_option_add_visitor(Visitor *v)
 {
     ObjectOption *opt = g_new0(ObjectOption, 1);
     visit_type_ObjectOptions(v, NULL, &opt->opts, &error_fatal);
+    if (opt->opts->qom_type == OBJECT_TYPE_MONITOR_HMP ||
+        opt->opts->qom_type == OBJECT_TYPE_MONITOR_QMP) {
+        default_monitor = 0;
+    }
     QTAILQ_INSERT_TAIL(&object_opts, opt, next);
 }
 
@@ -1970,7 +1974,9 @@ static bool object_create_early(const char *type)
 
     /* Reason: property "chardev" */
     if (g_str_equal(type, "rng-egd") ||
-        g_str_equal(type, "qtest")) {
+        g_str_equal(type, "qtest") ||
+        g_str_equal(type, "monitor-hmp") ||
+        g_str_equal(type, "monitor-qmp")) {
         return false;
     }
 
-- 
2.54.0



^ permalink raw reply related	[flat|nested] 49+ messages in thread

* [PATCH v6 23/34] monitor: convert from oneshot BH to persistent BH
  2026-07-02 16:03 [PATCH v6 00/34] monitor: turn QMP and HMP into QOM objects Daniel P. Berrangé
                   ` (21 preceding siblings ...)
  2026-07-02 16:03 ` [PATCH v6 22/34] monitor: implement "user creatable" interface for adding monitors Daniel P. Berrangé
@ 2026-07-02 16:03 ` Daniel P. Berrangé
  2026-07-02 16:03 ` [PATCH v6 24/34] monitor: reject attempts to delete the current monitor Daniel P. Berrangé
                   ` (10 subsequent siblings)
  33 siblings, 0 replies; 49+ messages in thread
From: Daniel P. Berrangé @ 2026-07-02 16:03 UTC (permalink / raw)
  To: qemu-devel
  Cc: Paolo Bonzini, Dr. David Alan Gilbert, Markus Armbruster,
	Marc-André Lureau, Alex Bennée,
	Philippe Mathieu-Daudé, Christian Brauner, devel,
	Daniel P. Berrangé, Peter Krempa

From: Christian Brauner <brauner@kernel.org>

Convert monitor_accept_input from a oneshot BH (aio_bh_schedule_oneshot)
to a persistent BH (aio_bh_new + qemu_bh_schedule).  Oneshot BHs cannot
be cancelled, so monitor_resume() racing with destruction would schedule
a callback against memory that monitor_qmp_destroy() is about to free.
A persistent BH can be deleted during destruction, cancelling any
pending schedule.

Signed-off-by: Christian Brauner (Amutable) <brauner@kernel.org>
[DB: extracted oneshot BH conversion from larger commit]
Reviewed-by: Marc-André Lureau <marcandre.lureau@redhat.com>
Tested-by: Peter Krempa <pkrempa@redhat.com>
Signed-off-by: Daniel P. Berrangé <berrange@redhat.com>
---
 monitor/monitor-internal.h |  2 +-
 monitor/monitor.c          | 25 ++++++++++++++-----------
 2 files changed, 15 insertions(+), 12 deletions(-)

diff --git a/monitor/monitor-internal.h b/monitor/monitor-internal.h
index 012d442a20..a82e1aacb6 100644
--- a/monitor/monitor-internal.h
+++ b/monitor/monitor-internal.h
@@ -134,7 +134,7 @@ struct Monitor {
     char *chardev_id;
     CharFrontend chr;
     int suspend_cnt;            /* Needs to be accessed atomically */
-
+    QEMUBH *accept_input_bh;    /* persistent BH for monitor_accept_input */
     char *mon_cpu_path;
     QTAILQ_ENTRY(Monitor) entry;
 
diff --git a/monitor/monitor.c b/monitor/monitor.c
index f0ca6b04ed..de1f761041 100644
--- a/monitor/monitor.c
+++ b/monitor/monitor.c
@@ -83,6 +83,9 @@ static void monitor_finalize(Object *obj)
 {
     Monitor *mon = MONITOR(obj);
 
+    if (mon->accept_input_bh) {
+        qemu_bh_delete(mon->accept_input_bh);
+    }
     g_free(mon->chardev_id);
     g_free(mon->mon_cpu_path);
     qemu_chr_fe_deinit(&mon->chr, false);
@@ -569,15 +572,7 @@ static void monitor_accept_input(void *opaque)
 void monitor_resume(Monitor *mon)
 {
     if (qatomic_dec_fetch(&mon->suspend_cnt) == 0) {
-        AioContext *ctx;
-
-        if (monitor_requires_iothread(mon)) {
-            ctx = iothread_get_aio_context(mon_iothread);
-        } else {
-            ctx = qemu_get_aio_context();
-        }
-
-        aio_bh_schedule_oneshot(ctx, monitor_accept_input, mon);
+        qemu_bh_schedule(mon->accept_input_bh);
     }
 
     trace_monitor_suspend(mon, -1);
@@ -700,6 +695,7 @@ char *monitor_compat_id(void)
 static void monitor_complete(UserCreatable *uc, Error **errp)
 {
     Monitor *mon = MONITOR(uc);
+    AioContext *ctx;
 
     if (mon->chardev_id) {
         Chardev *chr = qemu_chr_find(mon->chardev_id);
@@ -713,9 +709,16 @@ static void monitor_complete(UserCreatable *uc, Error **errp)
         }
     }
 
-    if (monitor_requires_iothread(mon) && !mon_iothread) {
-        mon_iothread = iothread_create("mon_iothread", &error_abort);
+    if (monitor_requires_iothread(mon)) {
+        if (!mon_iothread) {
+            mon_iothread = iothread_create("mon_iothread", &error_abort);
+        }
+
+        ctx = iothread_get_aio_context(mon_iothread);
+    } else {
+        ctx = qemu_get_aio_context();
     }
+    mon->accept_input_bh = aio_bh_new(ctx, monitor_accept_input, mon);
 }
 
 int monitor_new(MonitorOptions *opts, bool allow_hmp, Error **errp)
-- 
2.54.0



^ permalink raw reply related	[flat|nested] 49+ messages in thread

* [PATCH v6 24/34] monitor: reject attempts to delete the current monitor
  2026-07-02 16:03 [PATCH v6 00/34] monitor: turn QMP and HMP into QOM objects Daniel P. Berrangé
                   ` (22 preceding siblings ...)
  2026-07-02 16:03 ` [PATCH v6 23/34] monitor: convert from oneshot BH to persistent BH Daniel P. Berrangé
@ 2026-07-02 16:03 ` Daniel P. Berrangé
  2026-07-02 16:03 ` [PATCH v6 25/34] monitor: protect qemu_chr_fe_accept_input with monitor lock Daniel P. Berrangé
                   ` (9 subsequent siblings)
  33 siblings, 0 replies; 49+ messages in thread
From: Daniel P. Berrangé @ 2026-07-02 16:03 UTC (permalink / raw)
  To: qemu-devel
  Cc: Paolo Bonzini, Dr. David Alan Gilbert, Markus Armbruster,
	Marc-André Lureau, Alex Bennée,
	Philippe Mathieu-Daudé, Christian Brauner, devel,
	Daniel P. Berrangé, Peter Krempa

From: Christian Brauner <brauner@kernel.org>

If an 'object_del' command for a QMP monitor arrives targetting the
current monitor, reject this request. If the current monitor is
deleted, it will be impossible to send any reply and the client won't
be able to remove the corresponding chardev backend.

Note, it is not possible to rely on checking monitor_cur() because
if 'object_del' is called via human-monitor-command, monitor_cur()
will reflect the temporary HMP, not the QMP target that needs to
be checked.

Signed-off-by: Christian Brauner (Amutable) <brauner@kernel.org>
[DB: extracted monitor tracking from larger commit; added logic
     to monitor_qmp_prepare_delete to reject request]
Reviewed-by: Marc-André Lureau <marcandre.lureau@redhat.com>
Tested-by: Peter Krempa <pkrempa@redhat.com>
Signed-off-by: Daniel P. Berrangé <berrange@redhat.com>
---
 monitor/qmp.c | 17 +++++++++++++++++
 1 file changed, 17 insertions(+)

diff --git a/monitor/qmp.c b/monitor/qmp.c
index 67ee491f5a..1c67f40e4a 100644
--- a/monitor/qmp.c
+++ b/monitor/qmp.c
@@ -72,6 +72,9 @@ typedef struct QMPRequest QMPRequest;
 
 QmpCommandList qmp_commands, qmp_cap_negotiation_commands;
 
+/* Monitor being serviced by the dispatcher.  Protected by BQL. */
+static MonitorQMP *qmp_dispatcher_current_mon;
+
 OBJECT_DEFINE_TYPE(MonitorQMP, monitor_qmp, MONITOR_QMP, MONITOR);
 
 static void monitor_qmp_cleanup_req_queue_locked(MonitorQMP *mon);
@@ -369,6 +372,7 @@ void coroutine_fn monitor_qmp_dispatcher_co(void *data)
          */
 
         mon = req_obj->mon;
+        qmp_dispatcher_current_mon = mon;
 
         /*
          * We need to resume the monitor if handle_qmp_command()
@@ -429,6 +433,7 @@ void coroutine_fn monitor_qmp_dispatcher_co(void *data)
         }
 
         qmp_request_free(req_obj);
+        qmp_dispatcher_current_mon = NULL;
     }
     qatomic_set(&qmp_dispatcher_co, NULL);
 }
@@ -573,6 +578,11 @@ static void monitor_qmp_event(void *opaque, QEMUChrEvent event)
     }
 }
 
+static bool monitor_qmp_dispatcher_is_servicing(MonitorQMP *mon)
+{
+    return qmp_dispatcher_current_mon == mon;
+}
+
 static void monitor_qmp_setup_handlers_bh(void *opaque)
 {
     MonitorQMP *mon = opaque;
@@ -645,6 +655,13 @@ static void monitor_qmp_complete(UserCreatable *uc, Error **errp)
 
 static bool monitor_qmp_prepare_delete(UserCreatable *uc, Error **errp)
 {
+    MonitorQMP *qmp = MONITOR_QMP(uc);
+
+    if (monitor_qmp_dispatcher_is_servicing(qmp)) {
+        error_setg(errp, "Cannot delete the current QMP monitor");
+        return false;
+    }
+
     error_setg(errp, "Deleting QMP monitors is not supported");
     return false;
 }
-- 
2.54.0



^ permalink raw reply related	[flat|nested] 49+ messages in thread

* [PATCH v6 25/34] monitor: protect qemu_chr_fe_accept_input with monitor lock
  2026-07-02 16:03 [PATCH v6 00/34] monitor: turn QMP and HMP into QOM objects Daniel P. Berrangé
                   ` (23 preceding siblings ...)
  2026-07-02 16:03 ` [PATCH v6 24/34] monitor: reject attempts to delete the current monitor Daniel P. Berrangé
@ 2026-07-02 16:03 ` Daniel P. Berrangé
  2026-07-02 16:03 ` [PATCH v6 26/34] monitor: implement support for deleting QMP objects Daniel P. Berrangé
                   ` (8 subsequent siblings)
  33 siblings, 0 replies; 49+ messages in thread
From: Daniel P. Berrangé @ 2026-07-02 16:03 UTC (permalink / raw)
  To: qemu-devel
  Cc: Paolo Bonzini, Dr. David Alan Gilbert, Markus Armbruster,
	Marc-André Lureau, Alex Bennée,
	Philippe Mathieu-Daudé, Christian Brauner, devel,
	Daniel P. Berrangé, Peter Krempa

From: Christian Brauner <brauner@kernel.org>

The monitor_accept_input API is called from a bottom half, and will
invoke qemu_chr_fe_accept_input().

When a following patch introduces the ability to delete monitors, it
will be neccesary to delete the bottom half. Protecting the call to
qemu_chr_fe_accept_input with the monitor lock will allow for
synchronization with the deletion process.

Signed-off-by: Christian Brauner (Amutable) <brauner@kernel.org>
[DB: extracted from a larger commit and refactored to apply
     to the new monitor class structure]
Reviewed-by: Marc-André Lureau <marcandre.lureau@redhat.com>
Tested-by: Peter Krempa <pkrempa@redhat.com>
Signed-off-by: Daniel P. Berrangé <berrange@redhat.com>
---
 monitor/hmp.c     | 2 ++
 monitor/monitor.c | 6 +-----
 monitor/qmp.c     | 9 +++++++++
 3 files changed, 12 insertions(+), 5 deletions(-)

diff --git a/monitor/hmp.c b/monitor/hmp.c
index d0d298c5d3..375f65eff4 100644
--- a/monitor/hmp.c
+++ b/monitor/hmp.c
@@ -116,9 +116,11 @@ static void monitor_hmp_accept_input(Monitor *mon)
         MonitorHMP *hmp = MONITOR_HMP(mon);
         assert(hmp->rs);
         readline_restart(hmp->rs);
+        qemu_chr_fe_accept_input(&mon->chr);
         qemu_mutex_unlock(&mon->mon_lock);
         readline_show_prompt(hmp->rs);
     } else {
+        qemu_chr_fe_accept_input(&mon->chr);
         qemu_mutex_unlock(&mon->mon_lock);
     }
 }
diff --git a/monitor/monitor.c b/monitor/monitor.c
index de1f761041..4a45c15a36 100644
--- a/monitor/monitor.c
+++ b/monitor/monitor.c
@@ -562,11 +562,7 @@ static void monitor_accept_input(void *opaque)
     Monitor *mon = opaque;
     MonitorClass *cls = MONITOR_GET_CLASS(mon);
 
-    if (cls->accept_input) {
-        cls->accept_input(mon);
-    }
-
-    qemu_chr_fe_accept_input(&mon->chr);
+    cls->accept_input(mon);
 }
 
 void monitor_resume(Monitor *mon)
diff --git a/monitor/qmp.c b/monitor/qmp.c
index 1c67f40e4a..dc15809145 100644
--- a/monitor/qmp.c
+++ b/monitor/qmp.c
@@ -107,6 +107,7 @@ static void monitor_qmp_emit_event(Monitor *mon, QAPIEvent event, QDict *qdict);
 static bool monitor_qmp_requires_iothread(const Monitor *mon);
 static void monitor_qmp_complete(UserCreatable *uc, Error **errp);
 static bool monitor_qmp_prepare_delete(UserCreatable *uc, Error **errp);
+static void monitor_qmp_accept_input(Monitor *mon);
 
 static void monitor_qmp_class_init(ObjectClass *cls, const void *data)
 {
@@ -119,6 +120,7 @@ static void monitor_qmp_class_init(ObjectClass *cls, const void *data)
 
     moncls->emit_event = monitor_qmp_emit_event;
     moncls->requires_iothread = monitor_qmp_requires_iothread;
+    moncls->accept_input = monitor_qmp_accept_input;
 
     ucc->complete = monitor_qmp_complete;
     ucc->prepare_delete = monitor_qmp_prepare_delete;
@@ -665,3 +667,10 @@ static bool monitor_qmp_prepare_delete(UserCreatable *uc, Error **errp)
     error_setg(errp, "Deleting QMP monitors is not supported");
     return false;
 }
+
+static void monitor_qmp_accept_input(Monitor *mon)
+{
+    WITH_QEMU_LOCK_GUARD(&mon->mon_lock) {
+        qemu_chr_fe_accept_input(&mon->chr);
+    }
+}
-- 
2.54.0



^ permalink raw reply related	[flat|nested] 49+ messages in thread

* [PATCH v6 26/34] monitor: implement support for deleting QMP objects
  2026-07-02 16:03 [PATCH v6 00/34] monitor: turn QMP and HMP into QOM objects Daniel P. Berrangé
                   ` (24 preceding siblings ...)
  2026-07-02 16:03 ` [PATCH v6 25/34] monitor: protect qemu_chr_fe_accept_input with monitor lock Daniel P. Berrangé
@ 2026-07-02 16:03 ` Daniel P. Berrangé
  2026-07-02 16:03 ` [PATCH v6 27/34] tests/qtest: add tests for dynamic monitor add/remove Daniel P. Berrangé
                   ` (7 subsequent siblings)
  33 siblings, 0 replies; 49+ messages in thread
From: Daniel P. Berrangé @ 2026-07-02 16:03 UTC (permalink / raw)
  To: qemu-devel
  Cc: Paolo Bonzini, Dr. David Alan Gilbert, Markus Armbruster,
	Marc-André Lureau, Alex Bennée,
	Philippe Mathieu-Daudé, Christian Brauner, devel,
	Daniel P. Berrangé, Peter Krempa

From: Christian Brauner <brauner@kernel.org>

The removal sequence is:

 1. Remove from mon_list under monitor_lock.  This must happen
    before disconnecting chardev handlers to prevent event
    broadcast from calling monitor_flush_locked() after the
    gcontext reset, which would create an out_watch on the wrong
    GMainContext (see monitor_cancel_out_watch()).
 2. Cancel any pending out_watch while gcontext still points to the
    correct context.
 3. Disconnect chardev handlers, passing context=NULL and close
    the connection.
 4. Drain pending requests from any in-flight monitor_qmp_read().
 5. Destroy the monitor object

Signed-off-by: Christian Brauner (Amutable) <brauner@kernel.org>
[DB: extracted from a larger commit and refactored to apply
     to the new monitor class structure. Remove 'self delete'
     feature which requires complex special-case code]
Reviewed-by: Marc-André Lureau <marcandre.lureau@redhat.com>
Tested-by: Peter Krempa <pkrempa@redhat.com>
Signed-off-by: Daniel P. Berrangé <berrange@redhat.com>
---
 monitor/monitor-internal.h |  2 ++
 monitor/monitor.c          | 22 ++++++++++++++++
 monitor/qmp.c              | 54 ++++++++++++++++++++++++++++++++++++--
 3 files changed, 76 insertions(+), 2 deletions(-)

diff --git a/monitor/monitor-internal.h b/monitor/monitor-internal.h
index a82e1aacb6..5522e05464 100644
--- a/monitor/monitor-internal.h
+++ b/monitor/monitor-internal.h
@@ -178,6 +178,7 @@ struct MonitorQMP {
     Monitor parent_obj;
     JSONMessageParser parser;
     bool pretty;
+    bool setup_pending; /* iothread BH has not yet set up chardev handlers */
     /*
      * When a client connects, we're in capabilities negotiation mode.
      * @commands is &qmp_cap_negotiation_commands then.  When command
@@ -206,6 +207,7 @@ extern MonitorList mon_list;
 
 bool monitor_requires_iothread(const Monitor *mon);
 int monitor_can_read(void *opaque);
+void monitor_cancel_out_watch(Monitor *mon);
 void monitor_list_append(Monitor *mon);
 void monitor_fdsets_cleanup(void);
 int monitor_set_cpu(Monitor *mon, int cpu_index);
diff --git a/monitor/monitor.c b/monitor/monitor.c
index 4a45c15a36..ed195fd97b 100644
--- a/monitor/monitor.c
+++ b/monitor/monitor.c
@@ -179,6 +179,28 @@ static gboolean monitor_unblocked(void *do_not_use, GIOCondition cond,
     return G_SOURCE_REMOVE;
 }
 
+/* Cancel a pending out_watch GSource.  Caller must hold mon_lock. */
+void monitor_cancel_out_watch(Monitor *mon)
+{
+    if (mon->out_watch) {
+        GMainContext *ctx = NULL;
+        GSource *src;
+
+        if (monitor_requires_iothread(mon)) {
+            ctx = iothread_get_g_main_context(mon_iothread);
+        }
+        src = g_main_context_find_source_by_id(ctx, mon->out_watch);
+        if (!src && ctx) {
+            /* Handler disconnect may have reset gcontext to NULL. */
+            src = g_main_context_find_source_by_id(NULL, mon->out_watch);
+        }
+        if (src) {
+            g_source_destroy(src);
+        }
+        mon->out_watch = 0;
+    }
+}
+
 /* Caller must hold mon->mon_lock */
 void monitor_flush_locked(Monitor *mon)
 {
diff --git a/monitor/qmp.c b/monitor/qmp.c
index dc15809145..2bec0f3c96 100644
--- a/monitor/qmp.c
+++ b/monitor/qmp.c
@@ -184,6 +184,12 @@ static void monitor_qmp_cleanup_req_queue_locked(MonitorQMP *mon)
     }
 }
 
+static void monitor_qmp_drain_queue(MonitorQMP *mon)
+{
+    QEMU_LOCK_GUARD(&mon->qmp_queue_lock);
+    monitor_qmp_cleanup_req_queue_locked(mon);
+}
+
 static void monitor_qmp_cleanup_queue_and_resume(MonitorQMP *mon)
 {
     QEMU_LOCK_GUARD(&mon->qmp_queue_lock);
@@ -597,6 +603,7 @@ static void monitor_qmp_setup_handlers_bh(void *opaque)
                              monitor_qmp_read, monitor_qmp_event,
                              NULL, &mon->parent_obj, context, true);
     monitor_list_append(&mon->parent_obj);
+    qatomic_set(&mon->setup_pending, false);
 }
 
 void monitor_new_qmp(const char *id, const char *chardev_id,
@@ -644,6 +651,7 @@ static void monitor_qmp_complete(UserCreatable *uc, Error **errp)
          * since chardev might be running in the monitor I/O
          * thread.  Schedule a bottom half.
          */
+        mon->setup_pending = true;
         aio_bh_schedule_oneshot(iothread_get_aio_context(mon_iothread),
                                 monitor_qmp_setup_handlers_bh, mon);
         /* The bottom half will add @mon to @mon_list */
@@ -655,8 +663,14 @@ static void monitor_qmp_complete(UserCreatable *uc, Error **errp)
     }
 }
 
+static void monitor_qmp_iothread_quiesce(void *opaque)
+{
+    /* No-op: synchronization point only */
+}
+
 static bool monitor_qmp_prepare_delete(UserCreatable *uc, Error **errp)
 {
+    Monitor *mon = MONITOR(uc);
     MonitorQMP *qmp = MONITOR_QMP(uc);
 
     if (monitor_qmp_dispatcher_is_servicing(qmp)) {
@@ -664,8 +678,44 @@ static bool monitor_qmp_prepare_delete(UserCreatable *uc, Error **errp)
         return false;
     }
 
-    error_setg(errp, "Deleting QMP monitors is not supported");
-    return false;
+    if (qatomic_read(&qmp->setup_pending)) {
+        error_setg(errp, "monitor is still initializing");
+        return false;
+    }
+
+    /* Remove from mon_list before chardev disconnect. */
+    WITH_QEMU_LOCK_GUARD(&monitor_lock) {
+        QTAILQ_REMOVE(&mon_list, mon, entry);
+    }
+
+    /* Cancel out_watch while gcontext still points to the right ctx. */
+    WITH_QEMU_LOCK_GUARD(&mon->mon_lock) {
+        monitor_cancel_out_watch(mon);
+    }
+
+    qemu_chr_fe_set_handlers(&mon->chr, NULL, NULL, NULL, NULL,
+                             NULL, NULL, true);
+
+    /* Drain requests from any in-flight monitor_qmp_read(). */
+    monitor_qmp_drain_queue(qmp);
+
+    WITH_QEMU_LOCK_GUARD(&mon->mon_lock) {
+        /* Disable flushes before cancel -- gcontext is already wrong. */
+        qemu_chr_fe_set_open(&mon->chr, false);
+        monitor_cancel_out_watch(mon);
+    }
+
+    /* Synchronize with in-flight iothread callbacks. */
+    if (monitor_requires_iothread(mon)) {
+        aio_wait_bh_oneshot(iothread_get_aio_context(mon_iothread),
+                            monitor_qmp_iothread_quiesce, NULL);
+    }
+
+    /* Catch requests from a racing monitor_qmp_read(). */
+    monitor_qmp_drain_queue(qmp);
+    monitor_fdsets_cleanup();
+
+    return true;
 }
 
 static void monitor_qmp_accept_input(Monitor *mon)
-- 
2.54.0



^ permalink raw reply related	[flat|nested] 49+ messages in thread

* [PATCH v6 27/34] tests/qtest: add tests for dynamic monitor add/remove
  2026-07-02 16:03 [PATCH v6 00/34] monitor: turn QMP and HMP into QOM objects Daniel P. Berrangé
                   ` (25 preceding siblings ...)
  2026-07-02 16:03 ` [PATCH v6 26/34] monitor: implement support for deleting QMP objects Daniel P. Berrangé
@ 2026-07-02 16:03 ` Daniel P. Berrangé
  2026-07-02 16:03 ` [PATCH v6 28/34] tests/functional: add e2e test for dynamic QMP monitor hotplug Daniel P. Berrangé
                   ` (6 subsequent siblings)
  33 siblings, 0 replies; 49+ messages in thread
From: Daniel P. Berrangé @ 2026-07-02 16:03 UTC (permalink / raw)
  To: qemu-devel
  Cc: Paolo Bonzini, Dr. David Alan Gilbert, Markus Armbruster,
	Marc-André Lureau, Alex Bennée,
	Philippe Mathieu-Daudé, Christian Brauner, devel,
	Daniel P. Berrangé, Peter Krempa

From: Christian Brauner <brauner@kernel.org>

Test the object-add/object-del QMP commands with the monitor-qmp
object type.

- Basic lifecycle: chardev-add -> object-add -> object-del -> chardev-remove
- Error: object-add with nonexistent chardev
- Error: second monitor on same chardev (chardev already in use)
- Removal of CLI-created QMP monitor succeeds
- Error: object-remove on HMP monitor
- Re-add after remove: same id and chardev reusable after removal

Signed-off-by: Christian Brauner (Amutable) <brauner@kernel.org>
[DB: modified to use object-add/object-del, removing redundant
     scenarios already handled by object-add/del code]
Reviewed-by: Marc-André Lureau <marcandre.lureau@redhat.com>
Tested-by: Peter Krempa <pkrempa@redhat.com>
Signed-off-by: Daniel P. Berrangé <berrange@redhat.com>
---
 tests/qtest/qmp-test.c | 174 +++++++++++++++++++++++++++++++++++++++++
 1 file changed, 174 insertions(+)

diff --git a/tests/qtest/qmp-test.c b/tests/qtest/qmp-test.c
index edf0886787..3a325a04e0 100644
--- a/tests/qtest/qmp-test.c
+++ b/tests/qtest/qmp-test.c
@@ -337,6 +337,174 @@ static void test_qmp_missing_any_arg(void)
     qtest_quit(qts);
 }
 
+static void test_qmp_monitor_add_remove(void)
+{
+    QTestState *qts;
+    QDict *resp;
+
+    qts = qtest_init(common_args);
+
+    /* Create a null chardev for the dynamic monitor */
+    resp = qtest_qmp(qts,
+                     "{'execute': 'chardev-add',"
+                     " 'arguments': {'id': 'monitor-chardev',"
+                     "               'backend': {'type': 'null',"
+                     "                           'data': {}}}}");
+    g_assert(qdict_haskey(resp, "return"));
+    qobject_unref(resp);
+
+    /* Add a dynamic monitor */
+    resp = qtest_qmp(qts,
+                     "{'execute': 'object-add',"
+                     " 'arguments': {'qom-type': 'monitor-qmp',"
+                     "               'id': 'dyn-mon',"
+                     "               'chardev': 'monitor-chardev'}}");
+    g_assert(qdict_haskey(resp, "return"));
+    qobject_unref(resp);
+
+    /* Remove the dynamic monitor */
+    resp = qtest_qmp(qts,
+                     "{'execute': 'object-del',"
+                     " 'arguments': {'id': 'dyn-mon'}}");
+    g_assert(qdict_haskey(resp, "return"));
+    qobject_unref(resp);
+
+    /* Add again after remove -- same id and chardev should work */
+    resp = qtest_qmp(qts,
+                     "{'execute': 'object-add',"
+                     " 'arguments': {'qom-type': 'monitor-qmp',"
+                     "               'id': 'dyn-mon',"
+                     "               'chardev': 'monitor-chardev'}}");
+    g_assert(qdict_haskey(resp, "return"));
+    qobject_unref(resp);
+
+    /* Clean up */
+    resp = qtest_qmp(qts,
+                     "{'execute': 'object-del',"
+                     " 'arguments': {'id': 'dyn-mon'}}");
+    g_assert(qdict_haskey(resp, "return"));
+    qobject_unref(resp);
+
+    resp = qtest_qmp(qts,
+                     "{'execute': 'chardev-remove',"
+                     " 'arguments': {'id': 'monitor-chardev'}}");
+    g_assert(qdict_haskey(resp, "return"));
+    qobject_unref(resp);
+
+    qtest_quit(qts);
+}
+
+static void test_qmp_monitor_error_paths(void)
+{
+    QTestState *qts;
+    QDict *resp;
+
+    qts = qtest_init(common_args);
+
+    /* Error: chardev does not exist */
+    resp = qtest_qmp(qts,
+                     "{'execute': 'object-add',"
+                     " 'arguments': {'qom-type': 'monitor-qmp',"
+                     "               'id': 'bad-mon',"
+                     "               'chardev': 'nonexistent'}}");
+    qmp_expect_error_and_unref(resp, "GenericError");
+
+    qtest_quit(qts);
+}
+
+static void test_qmp_monitor_chardev_in_use(void)
+{
+    QTestState *qts;
+    QDict *resp;
+
+    qts = qtest_init(common_args);
+
+    /* Create a null chardev */
+    resp = qtest_qmp(qts,
+                     "{'execute': 'chardev-add',"
+                     " 'arguments': {'id': 'shared-chr',"
+                     "               'backend': {'type': 'null',"
+                     "                           'data': {}}}}");
+    g_assert(qdict_haskey(resp, "return"));
+    qobject_unref(resp);
+
+    /* Attach first monitor */
+    resp = qtest_qmp(qts,
+                     "{'execute': 'object-add',"
+                     " 'arguments': {'qom-type': 'monitor-qmp',"
+                     "               'id': 'mon-1',"
+                     "               'chardev': 'shared-chr'}}");
+    g_assert(qdict_haskey(resp, "return"));
+    qobject_unref(resp);
+
+    /* Error: second monitor on the same chardev */
+    resp = qtest_qmp(qts,
+                     "{'execute': 'object-add',"
+                     " 'arguments': {'qom-type': 'monitor-qmp',"
+                     "               'id': 'mon-2',"
+                     "               'chardev': 'shared-chr'}}");
+    qmp_expect_error_and_unref(resp, "GenericError");
+
+    /* Clean up */
+    resp = qtest_qmp(qts,
+                     "{'execute': 'object-del',"
+                     " 'arguments': {'id': 'mon-1'}}");
+    g_assert(qdict_haskey(resp, "return"));
+    qobject_unref(resp);
+
+    resp = qtest_qmp(qts,
+                     "{'execute': 'chardev-remove',"
+                     " 'arguments': {'id': 'shared-chr'}}");
+    g_assert(qdict_haskey(resp, "return"));
+    qobject_unref(resp);
+
+    qtest_quit(qts);
+}
+
+static void test_qmp_monitor_remove_cli(void)
+{
+    QTestState *qts;
+    QDict *resp;
+
+    /* Launch with a named CLI monitor on a null chardev */
+    qts = qtest_initf("%s -chardev null,id=cli-chr"
+                      " -object monitor-qmp,id=cli-mon,chardev=cli-chr",
+                      common_args);
+
+    /* CLI-created QMP monitors can be removed */
+    resp = qtest_qmp(qts,
+                     "{'execute': 'object-del',"
+                     " 'arguments': {'id': 'cli-mon'}}");
+    g_assert(qdict_haskey(resp, "return"));
+    qobject_unref(resp);
+
+    resp = qtest_qmp(qts,
+                     "{'execute': 'chardev-remove',"
+                     " 'arguments': {'id': 'cli-chr'}}");
+    g_assert(qdict_haskey(resp, "return"));
+    qobject_unref(resp);
+
+    qtest_quit(qts);
+}
+
+static void test_qmp_monitor_remove_hmp(void)
+{
+    QTestState *qts;
+    QDict *resp;
+
+    qts = qtest_initf("%s -chardev null,id=hmp-chr"
+                      " -object monitor-hmp,id=hmp-mon,chardev=hmp-chr",
+                      common_args);
+
+    /* Error: object_del must reject HMP monitors */
+    resp = qtest_qmp(qts,
+                     "{'execute': 'object-del',"
+                     " 'arguments': {'id': 'hmp-mon'}}");
+    qmp_expect_error_and_unref(resp, "GenericError");
+
+    qtest_quit(qts);
+}
+
 int main(int argc, char *argv[])
 {
     g_test_init(&argc, &argv, NULL);
@@ -348,6 +516,12 @@ int main(int argc, char *argv[])
 #endif
     qtest_add_func("qmp/preconfig", test_qmp_preconfig);
     qtest_add_func("qmp/missing-any-arg", test_qmp_missing_any_arg);
+    qtest_add_func("qmp/monitor-add-remove", test_qmp_monitor_add_remove);
+    qtest_add_func("qmp/monitor-error-paths", test_qmp_monitor_error_paths);
+    qtest_add_func("qmp/monitor-chardev-in-use",
+                    test_qmp_monitor_chardev_in_use);
+    qtest_add_func("qmp/monitor-remove-cli", test_qmp_monitor_remove_cli);
+    qtest_add_func("qmp/monitor-remove-hmp", test_qmp_monitor_remove_hmp);
 
     return g_test_run();
 }
-- 
2.54.0



^ permalink raw reply related	[flat|nested] 49+ messages in thread

* [PATCH v6 28/34] tests/functional: add e2e test for dynamic QMP monitor hotplug
  2026-07-02 16:03 [PATCH v6 00/34] monitor: turn QMP and HMP into QOM objects Daniel P. Berrangé
                   ` (26 preceding siblings ...)
  2026-07-02 16:03 ` [PATCH v6 27/34] tests/qtest: add tests for dynamic monitor add/remove Daniel P. Berrangé
@ 2026-07-02 16:03 ` Daniel P. Berrangé
  2026-07-02 16:03 ` [PATCH v6 29/34] tests/functional: add a stress test for monitor hot unplug Daniel P. Berrangé
                   ` (5 subsequent siblings)
  33 siblings, 0 replies; 49+ messages in thread
From: Daniel P. Berrangé @ 2026-07-02 16:03 UTC (permalink / raw)
  To: qemu-devel
  Cc: Paolo Bonzini, Dr. David Alan Gilbert, Markus Armbruster,
	Marc-André Lureau, Alex Bennée,
	Philippe Mathieu-Daudé, Christian Brauner, devel,
	Daniel P. Berrangé, Peter Krempa

From: Christian Brauner <brauner@kernel.org>

Add functional tests that exercise dynamic monitor hotplug with real
socket connections:

- Hotplug cycle: chardev-add a unix socket, object-add, connect to the
  socket, receive the QMP greeting, negotiate capabilities, send
  query-version, disconnect, remove the monitor and chardev, then repeat
  the entire cycle a second time to verify cleanup and reuse.

- Self-removal: a dynamically-added monitor sends object-del
  targeting itself, verifying that the request is rejected

- Large response: send query-qmp-schema on a dynamic monitor to
  exercise the output buffer flush path with a large response payload.

- Events after negotiation: trigger STOP/RESUME events via the main
  monitor and verify they are delivered on the dynamic monitor.

This complements the qtest unit tests by verifying that a real QMP
client can connect to a dynamically-added monitor and exchange messages.

Signed-off-by: Christian Brauner (Amutable) <brauner@kernel.org>
[DB: modified to use object-add/object-del; adjust self-removal test
 to validate rejection of request]
Reviewed-by: Marc-André Lureau <marcandre.lureau@redhat.com>
Tested-by: Peter Krempa <pkrempa@redhat.com>
Signed-off-by: Daniel P. Berrangé <berrange@redhat.com>
---
 MAINTAINERS                                   |   1 +
 tests/functional/generic/meson.build          |   1 +
 .../generic/test_monitor_hotplug.py           | 168 ++++++++++++++++++
 3 files changed, 170 insertions(+)
 create mode 100755 tests/functional/generic/test_monitor_hotplug.py

diff --git a/MAINTAINERS b/MAINTAINERS
index 2ecfd7159d..a0166f8257 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -3638,6 +3638,7 @@ F: docs/interop/*qmp-*
 F: scripts/qmp/
 F: tests/qtest/qmp-test.c
 F: tests/qtest/qmp-cmd-test.c
+F: tests/functional/generic/test_monitor_hotplug.py
 T: git https://repo.or.cz/qemu/armbru.git qapi-next
 
 qtest
diff --git a/tests/functional/generic/meson.build b/tests/functional/generic/meson.build
index 09763c5d22..c94105c62e 100644
--- a/tests/functional/generic/meson.build
+++ b/tests/functional/generic/meson.build
@@ -4,6 +4,7 @@ tests_generic_system = [
   'empty_cpu_model',
   'info_usernet',
   'linters',
+  'monitor_hotplug',
   'version',
   'vnc',
 ]
diff --git a/tests/functional/generic/test_monitor_hotplug.py b/tests/functional/generic/test_monitor_hotplug.py
new file mode 100755
index 0000000000..5d8a159eb0
--- /dev/null
+++ b/tests/functional/generic/test_monitor_hotplug.py
@@ -0,0 +1,168 @@
+#!/usr/bin/env python3
+#
+# SPDX-License-Identifier: GPL-2.0-or-later
+#
+# Functional test for dynamic QMP monitor hotplug
+#
+# Copyright (c) 2026 Christian Brauner
+
+import os
+
+from qemu_test import QemuSystemTest
+
+from qemu.qmp.legacy import QEMUMonitorProtocol
+
+
+class MonitorHotplug(QemuSystemTest):
+
+    def setUp(self):
+        super().setUp()
+        sock_dir = self.socket_dir()
+        self._sock_path = os.path.join(sock_dir.name, 'hotplug.sock')
+
+    def _add_monitor(self):
+        """Create a chardev + monitor and return the socket path."""
+        sock = self._sock_path
+        self.vm.cmd('chardev-add', id='hotplug-chr', backend={
+            'type': 'socket',
+            'data': {
+                'addr': {
+                    'type': 'unix',
+                    'data': {'path': sock}
+                },
+                'server': True,
+                'wait': False,
+            }
+        })
+        self.vm.cmd('object-add', id='hotplug-mon',
+                    qom_type='monitor-qmp',
+                    chardev='hotplug-chr')
+        return sock
+
+    def _remove_monitor(self):
+        """Remove the monitor + chardev."""
+        self.vm.cmd('object-del', id='hotplug-mon')
+        self.vm.cmd('chardev-remove', id='hotplug-chr')
+
+    def _connect_and_handshake(self, sock_path):
+        """
+        Connect to the dynamic monitor socket, perform the QMP
+        greeting and capability negotiation, send a command, then
+        disconnect.
+        """
+        qmp = QEMUMonitorProtocol(sock_path)
+
+        # connect(negotiate=True) receives the greeting, validates it,
+        # and sends qmp_capabilities automatically.
+        greeting = qmp.connect(negotiate=True)
+        self.assertIn('QMP', greeting)
+        self.assertIn('version', greeting['QMP'])
+        self.assertIn('capabilities', greeting['QMP'])
+
+        # Send a real command to prove the session is fully functional
+        resp = qmp.cmd_obj({'execute': 'query-version'})
+        self.assertIn('return', resp)
+        self.assertIn('qemu', resp['return'])
+
+        qmp.close()
+
+    def test_hotplug_cycle(self):
+        """
+        Hotplug a monitor, do the full QMP handshake, unplug it,
+        then repeat the whole cycle a second time.
+        """
+        self.set_machine('none')
+        self.vm.add_args('-nodefaults')
+        self.vm.launch()
+
+        # First cycle
+        sock = self._add_monitor()
+        self._connect_and_handshake(sock)
+        self._remove_monitor()
+
+        # Second cycle -- same ids, same path, must work
+        sock = self._add_monitor()
+        self._connect_and_handshake(sock)
+        self._remove_monitor()
+
+    def test_self_removal(self):
+        """
+        A dynamically-added monitor sends object-del targeting
+        itself.  Verify the request is rejected, but the monitor
+        can still be deleted from outside its own context.
+        """
+        self.set_machine('none')
+        self.vm.add_args('-nodefaults')
+        self.vm.launch()
+
+        sock = self._add_monitor()
+
+        qmp = QEMUMonitorProtocol(sock)
+        greeting = qmp.connect(negotiate=True)
+        self.assertIn('QMP', greeting)
+
+        # Self-removal: the dynamic monitor raises error
+        resp = qmp.cmd_obj({'execute': 'object-del',
+                            'arguments': {'id': 'hotplug-mon'}})
+        self.assertIn('error', resp)
+
+        qmp.close()
+
+        resp = self.vm.cmd('object-del', id='hotplug-mon')
+
+        # Clean up the chardev
+        self.vm.cmd('chardev-remove', id='hotplug-chr')
+
+    def test_large_response(self):
+        """
+        Send a command with a large response (query-qmp-schema) on a
+        dynamically-added monitor to exercise the output buffer flush
+        path.
+        """
+        self.set_machine('none')
+        self.vm.add_args('-nodefaults')
+        self.vm.launch()
+
+        sock = self._add_monitor()
+
+        qmp = QEMUMonitorProtocol(sock)
+        qmp.connect(negotiate=True)
+
+        resp = qmp.cmd_obj({'execute': 'query-qmp-schema'})
+        self.assertIn('return', resp)
+        self.assertIsInstance(resp['return'], list)
+        self.assertGreater(len(resp['return']), 0)
+
+        qmp.close()
+        self._remove_monitor()
+
+    def test_events_after_negotiation(self):
+        """
+        Verify that QMP events are delivered on a dynamically-added
+        monitor after capability negotiation completes.
+        """
+        self.set_machine('none')
+        self.vm.add_args('-nodefaults')
+        self.vm.launch()
+
+        sock = self._add_monitor()
+
+        qmp = QEMUMonitorProtocol(sock)
+        qmp.connect(negotiate=True)
+
+        # Trigger a STOP event via the main monitor, then read it
+        # from the dynamic monitor.
+        self.vm.cmd('stop')
+        resp = qmp.pull_event(wait=True)
+        self.assertEqual(resp['event'], 'STOP')
+
+        self.vm.cmd('cont')
+        resp = qmp.pull_event(wait=True)
+        self.assertEqual(resp['event'], 'RESUME')
+
+        qmp.close()
+        self._remove_monitor()
+
+
+if __name__ == '__main__':
+    QemuSystemTest.main()
-- 
2.54.0



^ permalink raw reply related	[flat|nested] 49+ messages in thread

* [PATCH v6 29/34] tests/functional: add a stress test for monitor hot unplug
  2026-07-02 16:03 [PATCH v6 00/34] monitor: turn QMP and HMP into QOM objects Daniel P. Berrangé
                   ` (27 preceding siblings ...)
  2026-07-02 16:03 ` [PATCH v6 28/34] tests/functional: add e2e test for dynamic QMP monitor hotplug Daniel P. Berrangé
@ 2026-07-02 16:03 ` Daniel P. Berrangé
  2026-07-02 16:03 ` [PATCH v6 30/34] qom: add trace events for user creatable create/delete APIs Daniel P. Berrangé
                   ` (4 subsequent siblings)
  33 siblings, 0 replies; 49+ messages in thread
From: Daniel P. Berrangé @ 2026-07-02 16:03 UTC (permalink / raw)
  To: qemu-devel
  Cc: Paolo Bonzini, Dr. David Alan Gilbert, Markus Armbruster,
	Marc-André Lureau, Alex Bennée,
	Philippe Mathieu-Daudé, Christian Brauner, devel,
	Daniel P. Berrangé, Peter Krempa

When unplugging a monitor there is a careful synchronization dance
between the monitor handling the "object-del" command and the
command processing for the monitor being deleted.

The stress test runs a busy loop of 'query-qmp-schema' on a second
monitor, while the primary monitor requests its deletion.

Reviewed-by: Marc-André Lureau <marcandre.lureau@redhat.com>
Tested-by: Peter Krempa <pkrempa@redhat.com>
Signed-off-by: Daniel P. Berrangé <berrange@redhat.com>
---
 .../generic/test_monitor_hotplug.py           | 62 +++++++++++++++++++
 1 file changed, 62 insertions(+)

diff --git a/tests/functional/generic/test_monitor_hotplug.py b/tests/functional/generic/test_monitor_hotplug.py
index 5d8a159eb0..55af8a238f 100755
--- a/tests/functional/generic/test_monitor_hotplug.py
+++ b/tests/functional/generic/test_monitor_hotplug.py
@@ -6,11 +6,16 @@
 #
 # Copyright (c) 2026 Christian Brauner
 
+import asyncio
 import os
+import random
+import threading
+import time
 
 from qemu_test import QemuSystemTest
 
 from qemu.qmp.legacy import QEMUMonitorProtocol
+from qemu.qmp import QMPClient
 
 
 class MonitorHotplug(QemuSystemTest):
@@ -163,6 +168,63 @@ def test_events_after_negotiation(self):
         qmp.close()
         self._remove_monitor()
 
+    def stress_mon(self, sock):
+        async def main():
+            qmp = QMPClient('testvm')
+            await qmp.connect(sock)
+            # Run query-version in a tight loop so that the
+            # monitor thread/dispatcher is very busy at the
+            # time we try to delete the monitor
+            while True:
+                try:
+                    # A command which returns a lot of data to make
+                    # it more likely we're in the I/O reply path
+                    # when deleting the monitor
+                    res = await qmp.execute('query-qmp-schema')
+                    # Some commands which generate async events
+                    # as those can trigger different code paths
+                    res = await qmp.execute('stop')
+                    res = await qmp.execute('cont')
+                except:
+                    # we'll get here if the monitor is terminated
+                    # by QEMU in which case we must disconnect
+                    # out side, but....
+                    try:
+                        await qmp.disconnect()
+                    except (ConnectionResetError, EOFError, BrokenPipeError):
+                        # ... disconnect() will probably see
+                        # errors too, but we must try to call it
+                        # regardless to cleanup asyncio state
+                        # and prevent python warnings at GC time
+                        pass
+                    return
+        asyncio.run(main())
+
+    def test_hotplug_stress(self):
+        """
+        Repeatedly hotplug and unplug a monitor, while another thread
+        concurrently issues commands on that monitor. This stresses
+        the synchronization with the monitor thread during cleanup
+        """
+        self.set_machine('none')
+        self.vm.add_args('-nodefaults')
+        self.vm.launch()
+
+        # Each loop sleeps at most 0.5 seconds, so this should
+        # give an upper bound of approx 5 seconds execution
+        # time which is reasonable to run by default
+        repeat = 10
+        for i in range(repeat):
+            # First cycle
+            sock = self._add_monitor()
+            print ("# stress cycle %02d/%02d" % (i, repeat))
+            stress = threading.Thread(target=self.stress_mon, args=[sock])
+            stress.start()
+            # Sleep upto 1/2 second to vary the races
+            time.sleep(random.random() / 2)
+            self._remove_monitor()
+            stress.join()
+
 
 if __name__ == '__main__':
     QemuSystemTest.main()
-- 
2.54.0



^ permalink raw reply related	[flat|nested] 49+ messages in thread

* [PATCH v6 30/34] qom: add trace events for user creatable create/delete APIs
  2026-07-02 16:03 [PATCH v6 00/34] monitor: turn QMP and HMP into QOM objects Daniel P. Berrangé
                   ` (28 preceding siblings ...)
  2026-07-02 16:03 ` [PATCH v6 29/34] tests/functional: add a stress test for monitor hot unplug Daniel P. Berrangé
@ 2026-07-02 16:03 ` Daniel P. Berrangé
  2026-07-02 16:03 ` [PATCH v6 31/34] monitor: add support for auto-deleting monitors upon close Daniel P. Berrangé
                   ` (3 subsequent siblings)
  33 siblings, 0 replies; 49+ messages in thread
From: Daniel P. Berrangé @ 2026-07-02 16:03 UTC (permalink / raw)
  To: qemu-devel
  Cc: Paolo Bonzini, Dr. David Alan Gilbert, Markus Armbruster,
	Marc-André Lureau, Alex Bennée,
	Philippe Mathieu-Daudé, Christian Brauner, devel,
	Daniel P. Berrangé, Peter Krempa

Reviewed-by: Marc-André Lureau <marcandre.lureau@redhat.com>
Tested-by: Peter Krempa <pkrempa@redhat.com>
Signed-off-by: Daniel P. Berrangé <berrange@redhat.com>
---
 qom/object_interfaces.c | 11 +++++++++--
 qom/trace-events        |  5 +++++
 2 files changed, 14 insertions(+), 2 deletions(-)

diff --git a/qom/object_interfaces.c b/qom/object_interfaces.c
index ac992aa786..69bdb1807f 100644
--- a/qom/object_interfaces.c
+++ b/qom/object_interfaces.c
@@ -20,12 +20,14 @@
 #include "qapi/opts-visitor.h"
 #include "qemu/config-file.h"
 #include "qemu/keyval.h"
+#include "trace.h"
 
 bool user_creatable_complete(UserCreatable *uc, Error **errp)
 {
     UserCreatableClass *ucc = USER_CREATABLE_GET_CLASS(uc);
     ERRP_GUARD();
 
+    trace_user_creatable_complete(uc, object_get_typename(OBJECT(uc)));
     if (ucc->complete) {
         ucc->complete(uc, errp);
     }
@@ -36,11 +38,16 @@ bool user_creatable_prepare_delete(UserCreatable *uc, Error **errp)
 {
     UserCreatableClass *ucc = USER_CREATABLE_GET_CLASS(uc);
     ERRP_GUARD();
+    bool ret = true;
 
+    trace_user_creatable_prepare_delete(uc, object_get_typename(OBJECT(uc)));
     if (ucc->prepare_delete) {
-        return ucc->prepare_delete(uc, errp);
+        ret = ucc->prepare_delete(uc, errp);
     }
-    return true;
+    trace_user_creatable_prepare_delete_result(
+        uc, object_get_typename(OBJECT(uc)),
+        *errp ? error_get_pretty(*errp) : NULL);
+    return ret;
 }
 
 void user_creatable_add_qapi(ObjectOptions *options, Error **errp)
diff --git a/qom/trace-events b/qom/trace-events
index 44c63e72af..bac8472618 100644
--- a/qom/trace-events
+++ b/qom/trace-events
@@ -11,3 +11,8 @@ object_property_del_child(void *obj, const char *type, void *child, const char *
 object_property_parse(void *obj, const char *type, const char *name, const char *value) "obj=%p type=%s prop=%s value=%s"
 object_class_dynamic_cast_assert(const char *type, const char *target, const char *file, int line, const char *func) "type=%s->%s (%s:%d:%s)"
 object_class_property_add(const char *type, const char *name, void *value) "type=%s name=%s value=%p"
+
+# object_interfaces.c
+user_creatable_complete(void *obj, const char *type) "obj=%p type=%s"
+user_creatable_prepare_delete(void *obj, const char *type) "obj=%p type=%s"
+user_creatable_prepare_delete_result(void *obj, const char *type, const char *msg) "obj=%p type=%s msg=%s"
-- 
2.54.0



^ permalink raw reply related	[flat|nested] 49+ messages in thread

* [PATCH v6 31/34] monitor: add support for auto-deleting monitors upon close
  2026-07-02 16:03 [PATCH v6 00/34] monitor: turn QMP and HMP into QOM objects Daniel P. Berrangé
                   ` (29 preceding siblings ...)
  2026-07-02 16:03 ` [PATCH v6 30/34] qom: add trace events for user creatable create/delete APIs Daniel P. Berrangé
@ 2026-07-02 16:03 ` Daniel P. Berrangé
  2026-07-03 13:42   ` Markus Armbruster
  2026-07-02 16:03 ` [PATCH v6 32/34] tests: switch from -mon to -object monitor-qmp Daniel P. Berrangé
                   ` (2 subsequent siblings)
  33 siblings, 1 reply; 49+ messages in thread
From: Daniel P. Berrangé @ 2026-07-02 16:03 UTC (permalink / raw)
  To: qemu-devel
  Cc: Paolo Bonzini, Dr. David Alan Gilbert, Markus Armbruster,
	Marc-André Lureau, Alex Bennée,
	Philippe Mathieu-Daudé, Christian Brauner, devel,
	Daniel P. Berrangé, Peter Krempa

The default monitor is usually a long lived object that will exist for
the entire lifetime of the VM. A monitor can only service a single
client at a time though, and so it might be desirable to hotplug
additional monitors at runtime for specific tasks. If doing that,
however, there is a need to remove the monitor when it is no longer
needed.

Allowing a client to run "object-del" against its own monitor adds
complex edge cases, as it would be desirable to send the QMP response
despite the monitor sending it being deleted. Doing "object-del" alone
will also result in orphaning a character device backend instance, as
there is no opportunity to run the companion "chardev-del" command.

A simpler way to ensure cleanup is to add the concept of auto-deleting
monitor objects. Specifically when the "CHR_EVENT_CLOSED" event is
emitted, the equivalent of "object-del" + "chardev-del" can be run
internally. Since the transient client has already droppped its
monitor connection, there is no synchronization to be concerned about
with sending QMP replies. There is still some internal synchronization
needed, however, between the character device event callback and the
bottom-half that runs the delete. There is a chance that an incoming
client connection may arise before the bottom-half runs, which has
to be checked. Once the monitor object is deleted, the event callback
is unregistered from the character device, eliminating any further
races before the character device is fully deleted.

This is implemented via a new "close-action=none|delete" property on
the 'monitor-qmp' object. This concept could be extended with further
actions in future, for example:

 * close-action=shutdown - graceful guest shutdown
 * close-action=terminate - immediate guest poweroff
 * close-action=stop - pause guest CPUs while the monitor is not
                       connected to any client

This is left as an exercise for future interested contributors.

Tested-by: Peter Krempa <pkrempa@redhat.com>
Reviewed-by: Marc-André Lureau <marcandre.lureau@redhat.com>
Signed-off-by: Daniel P. Berrangé <berrange@redhat.com>
---
 monitor/monitor-internal.h                    |  3 +
 monitor/qmp.c                                 | 80 ++++++++++++++++++
 qapi/qom.json                                 | 22 ++++-
 .../generic/test_monitor_hotplug.py           | 83 ++++++++++++++++++-
 4 files changed, 183 insertions(+), 5 deletions(-)

diff --git a/monitor/monitor-internal.h b/monitor/monitor-internal.h
index 5522e05464..23829f32f9 100644
--- a/monitor/monitor-internal.h
+++ b/monitor/monitor-internal.h
@@ -28,6 +28,7 @@
 #include "chardev/char-fe.h"
 #include "monitor/monitor.h"
 #include "qapi/qapi-types-control.h"
+#include "qapi/qapi-types-qom.h"
 #include "qapi/qmp-registry.h"
 #include "qobject/json-parser.h"
 #include "qemu/readline.h"
@@ -178,7 +179,9 @@ struct MonitorQMP {
     Monitor parent_obj;
     JSONMessageParser parser;
     bool pretty;
+    MonitorQMPCloseAction close_action;
     bool setup_pending; /* iothread BH has not yet set up chardev handlers */
+    bool delete_pending; /* close_action has started 'delete' process */
     /*
      * When a client connects, we're in capabilities negotiation mode.
      * @commands is &qmp_cap_negotiation_commands then.  When command
diff --git a/monitor/qmp.c b/monitor/qmp.c
index 2bec0f3c96..223e0643c2 100644
--- a/monitor/qmp.c
+++ b/monitor/qmp.c
@@ -28,6 +28,7 @@
 #include "monitor-internal.h"
 #include "qapi/error.h"
 #include "qapi/qapi-commands-control.h"
+#include "qapi/qapi-commands-char.h"
 #include "qobject/qdict.h"
 #include "qobject/qjson.h"
 #include "qobject/qlist.h"
@@ -103,6 +104,20 @@ static void monitor_qmp_set_pretty(Object *obj, bool val, Error **errp)
     mon->pretty = val;
 }
 
+static int monitor_qmp_get_close_action(Object *obj, Error **errp)
+{
+    MonitorQMP *mon = MONITOR_QMP(obj);
+
+    return mon->close_action;
+}
+
+static void monitor_qmp_set_close_action(Object *obj, int val, Error **errp)
+{
+    MonitorQMP *mon = MONITOR_QMP(obj);
+
+    mon->close_action = val;
+}
+
 static void monitor_qmp_emit_event(Monitor *mon, QAPIEvent event, QDict *qdict);
 static bool monitor_qmp_requires_iothread(const Monitor *mon);
 static void monitor_qmp_complete(UserCreatable *uc, Error **errp);
@@ -117,6 +132,11 @@ static void monitor_qmp_class_init(ObjectClass *cls, const void *data)
     object_class_property_add_bool(cls, "pretty",
                                    monitor_qmp_get_pretty,
                                    monitor_qmp_set_pretty);
+    object_class_property_add_enum(cls, "close-action",
+                                   "MonitorQMPCloseAction",
+                                   &MonitorQMPCloseAction_lookup,
+                                   monitor_qmp_get_close_action,
+                                   monitor_qmp_set_close_action);
 
     moncls->emit_event = monitor_qmp_emit_event;
     moncls->requires_iothread = monitor_qmp_requires_iothread;
@@ -550,11 +570,49 @@ static QDict *qmp_greeting(MonitorQMP *mon)
         ver, cap_list);
 }
 
+static void monitor_qmp_self_delete_bh(void *opaque)
+{
+    MonitorQMP *mon = opaque;
+    const char *mon_id = object_get_canonical_path_component(
+        OBJECT(mon));
+    g_autofree char *chardev_id = g_strdup(mon->parent_obj.chardev_id);
+    Error *local_error = NULL;
+
+    if (!mon_id) {
+        /*
+         * Another monitor raced & ran 'object-del' on 'mon'
+         * before this BH got scheduled, so we have a ref on
+         * mon from monitor_qmp_event but it is already
+         * unparented.
+         */
+        object_unref(mon);
+        return;
+    }
+
+    user_creatable_del(mon_id, &local_error);
+    /* Pairs with ref from monitor_qmp_event */
+    object_unref(mon);
+    if (local_error != NULL) {
+        error_report_err(local_error);
+    } else {
+        qmp_chardev_remove(chardev_id, NULL);
+    }
+}
+
 static void monitor_qmp_event(void *opaque, QEMUChrEvent event)
 {
     QDict *data;
     MonitorQMP *mon = opaque;
 
+    /*
+     * Protect against race if a client drops & quickly
+     * reconnects - we'll have the delete BH scheduled
+     * so must not honour a new open request
+     */
+    if (mon->delete_pending) {
+        return;
+    }
+
     switch (event) {
     case CHR_EVENT_OPENED:
         WITH_QEMU_LOCK_GUARD(&mon->parent_obj.mon_lock) {
@@ -577,6 +635,28 @@ static void monitor_qmp_event(void *opaque, QEMUChrEvent event)
         json_message_parser_init(&mon->parser, handle_qmp_command,
                                  mon, NULL);
         monitor_fdsets_cleanup();
+        switch (mon->close_action) {
+        case MONITOR_QMP_CLOSE_ACTION_NONE:
+            break;
+        case MONITOR_QMP_CLOSE_ACTION_DELETE:
+            mon->delete_pending = true;
+            /*
+             * Do NOT run in the AIO context associated with the
+             * monitor. We need to run in the default AIO context
+             * which is the same context in which 'qmp_object_del'
+             * will execute
+             *
+             * Hold an extra ref in case a separate monitor races
+             * with the BH by processing an explicit 'object-del'.
+             * Will be released by monitor_qmp_self_delete_bh
+             */
+            object_ref(mon);
+            aio_bh_schedule_oneshot(qemu_get_aio_context(),
+                                    monitor_qmp_self_delete_bh, mon);
+            break;
+        default:
+            g_assert_not_reached();
+        }
         break;
     case CHR_EVENT_BREAK:
     case CHR_EVENT_MUX_IN:
diff --git a/qapi/qom.json b/qapi/qom.json
index 33a827b9fa..d96cc5aa21 100644
--- a/qapi/qom.json
+++ b/qapi/qom.json
@@ -1213,6 +1213,22 @@
   'base': 'MonitorProperties',
   'data': { '*readline': 'bool' } }
 
+
+##
+# @MonitorQMPCloseAction:
+#
+# Action to take when the character device backend is closed.
+#
+# @none: take no action
+#
+# @delete: delete both the 'monitor-qmp' object and its associated
+#     character device backend object
+#
+# Since: 11.1
+##
+{ 'enum': 'MonitorQMPCloseAction',
+  'data': ['none', 'delete'] }
+
 ##
 # @MonitorQMPProperties:
 #
@@ -1220,11 +1236,15 @@
 #
 # @pretty: whether to pretty print JSON responses (default: false)
 #
+# @close-action: action to take when the character device backend is
+#     closed (default: none)
+#
 # Since: 11.1
 ##
 { 'struct': 'MonitorQMPProperties',
   'base': 'MonitorProperties',
-  'data': { '*pretty': 'bool' } }
+  'data': { '*pretty': 'bool',
+            '*close-action': 'MonitorQMPCloseAction' } }
 
 ##
 # @ObjectType:
diff --git a/tests/functional/generic/test_monitor_hotplug.py b/tests/functional/generic/test_monitor_hotplug.py
index 55af8a238f..86092e781c 100755
--- a/tests/functional/generic/test_monitor_hotplug.py
+++ b/tests/functional/generic/test_monitor_hotplug.py
@@ -25,7 +25,7 @@ def setUp(self):
         sock_dir = self.socket_dir()
         self._sock_path = os.path.join(sock_dir.name, 'hotplug.sock')
 
-    def _add_monitor(self):
+    def _add_monitor(self, autodelete=False):
         """Create a chardev + monitor and return the socket path."""
         sock = self._sock_path
         self.vm.cmd('chardev-add', id='hotplug-chr', backend={
@@ -39,9 +39,15 @@ def _add_monitor(self):
                 'wait': False,
             }
         })
-        self.vm.cmd('object-add', id='hotplug-mon',
-                    qom_type='monitor-qmp',
-                    chardev='hotplug-chr')
+        if autodelete:
+            self.vm.cmd('object-add', id='hotplug-mon',
+                        qom_type='monitor-qmp',
+                        chardev='hotplug-chr',
+                        close_action='delete')
+        else:
+            self.vm.cmd('object-add', id='hotplug-mon',
+                        qom_type='monitor-qmp',
+                        chardev='hotplug-chr')
         return sock
 
     def _remove_monitor(self):
@@ -118,6 +124,75 @@ def test_self_removal(self):
         # Clean up the chardev
         self.vm.cmd('chardev-remove', id='hotplug-chr')
 
+    def test_auto_delete(self):
+        """
+        A dynamically-added monitor configured with 'close-action=delete'
+        should see itself deleted when the client is closed.
+        """
+        self.set_machine('none')
+        self.vm.add_args('-nodefaults')
+        self.vm.launch()
+
+        sock = self._add_monitor(autodelete=True)
+
+        cdevs = [c["label"] for c in self.vm.cmd('query-chardev')]
+        objs = [o["name"] for o in self.vm.cmd('qom-list', path='/objects')]
+        assert ('hotplug-chr' in cdevs)
+        assert ('hotplug-mon' in objs)
+
+        qmp = QEMUMonitorProtocol(sock)
+        greeting = qmp.connect(negotiate=True)
+        self.assertIn('QMP', greeting)
+
+        cdevs = [c["label"] for c in self.vm.cmd('query-chardev')]
+        objs = [o["name"] for o in self.vm.cmd('qom-list', path='/objects')]
+        assert ('hotplug-chr' in cdevs)
+        assert ('hotplug-mon' in objs)
+
+        qmp.close()
+
+        # Wait upto 10 seconds max for chardev to auto-delete, which
+        # is hopefully enough for reliability under high load
+        for i in range(int(10 / 0.2)):
+            cdevs = [c["label"] for c in self.vm.cmd('query-chardev')]
+            if 'hotplug-chr' not in cdevs:
+                break
+            # Wait a little more then try again
+            time.sleep(0.2)
+
+        cdevs = [c["label"] for c in self.vm.cmd('query-chardev')]
+        objs = [o["name"] for o in self.vm.cmd('qom-list', path='/objects')]
+        assert ('hotplug-chr' not in cdevs)
+        assert ('hotplug-mon' not in objs)
+
+    def test_reconnect(self):
+        """
+        A dynamically-added monitor configured without 'close-action'
+        should allow reconnects after the client is closed.
+        """
+        self.set_machine('none')
+        self.vm.add_args('-nodefaults')
+        self.vm.launch()
+
+        sock = self._add_monitor()
+
+        qmp = QEMUMonitorProtocol(sock)
+        qmp.connect(negotiate=True)
+
+        resp = qmp.cmd_obj({'execute': 'query-chardev'})
+        self.assertIn('return', resp)
+
+        qmp.close()
+
+        qmp = QEMUMonitorProtocol(sock)
+        qmp.connect(negotiate=True)
+
+        resp = qmp.cmd_obj({'execute': 'query-chardev'})
+        self.assertIn('return', resp)
+
+        qmp.close()
+        self._remove_monitor()
+
     def test_large_response(self):
         """
         Send a command with a large response (query-qmp-schema) on a
-- 
2.54.0



^ permalink raw reply related	[flat|nested] 49+ messages in thread

* [PATCH v6 32/34] tests: switch from -mon to -object monitor-qmp
  2026-07-02 16:03 [PATCH v6 00/34] monitor: turn QMP and HMP into QOM objects Daniel P. Berrangé
                   ` (30 preceding siblings ...)
  2026-07-02 16:03 ` [PATCH v6 31/34] monitor: add support for auto-deleting monitors upon close Daniel P. Berrangé
@ 2026-07-02 16:03 ` Daniel P. Berrangé
  2026-07-02 16:03 ` [PATCH v6 33/34] qemu-options: document new monitor-hmp and monitor-qmp objects Daniel P. Berrangé
  2026-07-02 16:03 ` [PATCH v6 34/34] docs: mark '-mon' as deprecated in favour of -object Daniel P. Berrangé
  33 siblings, 0 replies; 49+ messages in thread
From: Daniel P. Berrangé @ 2026-07-02 16:03 UTC (permalink / raw)
  To: qemu-devel
  Cc: Paolo Bonzini, Dr. David Alan Gilbert, Markus Armbruster,
	Marc-André Lureau, Alex Bennée,
	Philippe Mathieu-Daudé, Christian Brauner, devel,
	Daniel P. Berrangé, Peter Krempa

Use the new preferred low level option for configuring the
QMP service in libqtest and the python Machine class used
by tests. This will avoid triggering deprecation warnings
after the subsequent commit.

Reviewed-by: Marc-André Lureau <marcandre.lureau@redhat.com>
Tested-by: Peter Krempa <pkrempa@redhat.com>
Signed-off-by: Daniel P. Berrangé <berrange@redhat.com>
---
 python/qemu/machine/machine.py | 4 ++--
 tests/qtest/libqtest.c         | 2 +-
 2 files changed, 3 insertions(+), 3 deletions(-)

diff --git a/python/qemu/machine/machine.py b/python/qemu/machine/machine.py
index 3670c8cd39..bfb59d4c0b 100644
--- a/python/qemu/machine/machine.py
+++ b/python/qemu/machine/machine.py
@@ -304,8 +304,8 @@ def _harness_args(self) -> List[str]:
                 )
             else:
                 moncdev = f"socket,id=mon,path={self._monitor_address}"
-            args.extend(['-chardev', moncdev, '-mon',
-                         'chardev=mon,mode=control'])
+            args.extend(['-chardev', moncdev, '-object',
+                         'monitor-qmp,id=qmp,chardev=mon'])
         return args
 
     def _console_args(self, interactive: bool = False) -> List[str]:
diff --git a/tests/qtest/libqtest.c b/tests/qtest/libqtest.c
index 4e22c66b75..c33c799c92 100644
--- a/tests/qtest/libqtest.c
+++ b/tests/qtest/libqtest.c
@@ -460,7 +460,7 @@ gchar *qtest_qemu_args(const char *extra_args)
                       "-qtest unix:%s "
                       "-qtest-log %s "
                       "-chardev socket,path=%s,id=char0 "
-                      "-mon chardev=char0,mode=control "
+                      "-object monitor-qmp,id=qmp0,chardev=char0 "
                       "-display none "
                       "-audio none "
                       "%s "
-- 
2.54.0



^ permalink raw reply related	[flat|nested] 49+ messages in thread

* [PATCH v6 33/34] qemu-options: document new monitor-hmp and monitor-qmp objects
  2026-07-02 16:03 [PATCH v6 00/34] monitor: turn QMP and HMP into QOM objects Daniel P. Berrangé
                   ` (31 preceding siblings ...)
  2026-07-02 16:03 ` [PATCH v6 32/34] tests: switch from -mon to -object monitor-qmp Daniel P. Berrangé
@ 2026-07-02 16:03 ` Daniel P. Berrangé
  2026-07-06 10:56   ` Markus Armbruster
  2026-07-02 16:03 ` [PATCH v6 34/34] docs: mark '-mon' as deprecated in favour of -object Daniel P. Berrangé
  33 siblings, 1 reply; 49+ messages in thread
From: Daniel P. Berrangé @ 2026-07-02 16:03 UTC (permalink / raw)
  To: qemu-devel
  Cc: Paolo Bonzini, Dr. David Alan Gilbert, Markus Armbruster,
	Marc-André Lureau, Alex Bennée,
	Philippe Mathieu-Daudé, Christian Brauner, devel,
	Daniel P. Berrangé

Add new docs for the `-object monitor-hmp` and `-object monitor-qmp`
options, updating `-mon` to state that it is legacy syntax sugar
for the new `-object` args.

Signed-off-by: Daniel P. Berrangé <berrange@redhat.com>
---
 qemu-options.hx | 61 ++++++++++++++++++++++++++++++++++++++++++++++---
 1 file changed, 58 insertions(+), 3 deletions(-)

diff --git a/qemu-options.hx b/qemu-options.hx
index e44b47de68..1aa548c39b 100644
--- a/qemu-options.hx
+++ b/qemu-options.hx
@@ -4955,6 +4955,11 @@ SRST
     port). The default device is ``vc`` in graphical mode and ``stdio``
     in non graphical mode. Use ``-monitor none`` to disable the default
     monitor.
+
+    The use of ``-monitor dev`` is syntax sugar for creating a character
+    device from ``dev`` and pairing it with ``-object monitor-hmp``.
+    Both the character device and monitor object will be given an ID
+    ``compat_monitorNNN`` where ``NNN`` is a counter starting from 0.
 ERST
 DEF("qmp", HAS_ARG, QEMU_OPTION_qmp, \
     "-qmp dev        like -monitor but opens in 'control' mode\n",
@@ -4966,9 +4971,10 @@ SRST
 
         -qmp tcp:localhost:4444,server=on,wait=off
 
-    Not all options are configurable via this syntax; for maximum
-    flexibility use the ``-mon`` option and an accompanying ``-chardev``.
-
+    The use of ``-monitor dev`` is syntax sugar for creating a character
+    device from ``dev`` and pairing it with ``-object monitor-qmp``.
+    Both the character device and monitor object will be given an ID
+    ``compat_monitorNNN`` where ``NNN`` is a counter starting from 0.
 ERST
 DEF("qmp-pretty", HAS_ARG, QEMU_OPTION_qmp_pretty, \
     "-qmp-pretty dev like -qmp but uses pretty JSON formatting\n",
@@ -4997,6 +5003,16 @@ SRST
       -mon chardev=mon1,mode=control,pretty=on
 
     enables the QMP monitor on localhost port 4444 with pretty-printing.
+
+    The use of ``-mon mode=readline`` is historical syntax sugar
+    for the new ``-object monitor-hmp`` option, each use of which
+    creates an object with the ID ``compat_monitorNNN`` where ``NNN`` is
+    a counter starting from 0.
+
+    The use of ``-mon mode=control`` is historical syntax sugar
+    for the new ``-object monitor-qmp`` option, each use of which
+    creates an object with the ID ``compat_monitorNNN`` where ``NNN`` is
+    a counter starting from 0.
 ERST
 
 DEF("debugcon", HAS_ARG, QEMU_OPTION_debugcon, \
@@ -5747,6 +5763,45 @@ SRST
     they are specified. Note that the 'id' property must be set. These
     objects are placed in the '/objects' path.
 
+    ``-object monitor-hmp,id=id,chardev=chardev_id,readline=on|off``
+        Set up a monitor running the Human Monitor Protocol,
+        connected to the chardev ``chardev_id``.
+
+        The ``id`` parameter is a unique ID that can be used
+        to dynamically delete the monitor at runtime. Note
+        that monitors created using the historical syntax
+        will be allocated IDs following the pattern ``hmpcompatNN``.
+        Mixing ``-object`` with the historical monitor syntax is
+        discouraged.
+
+        The ``readline`` parameter, which defaults to ``on``,
+        controls whether the monitor provides line editing.
+
+    ``-object monitor-qmp,id=id,chardev=chardev_id,pretty=on|off,close-action=none|delete``
+        Set up a monitor running the QEMU Monitor Protocol,
+        connected to the chardev ``chardev_id``.
+
+        The ``id`` parameter is a unique ID that can be used
+        to dynamically delete the monitor at runtime. Note
+        that monitors created using the historical syntax
+        will be allocated IDs following the pattern ``qmpcompatNN``.
+        Mixing ``-object`` with the historical monitor syntax is
+        discouraged.
+
+        The ``pretty`` parameter, which defaults to ``off``,
+        controls whether the monitor responses are pretty
+        printed as multi-line indented JSON, as opposed to
+        constrained to a single line without extraneous
+        whitespace.
+
+        The ``close-action`` parameter, which defaults to ``none``,
+        controls what happens when the connection to the monitor
+        is terminated by the user. If set to ``delete``, then the
+        ``monitor-qmp`` object and its associated character
+        device are both immediately deleted. This can be useful
+        if an extra monitor was hotplugged for a specific task
+        and should be unplugged when completed.
+
     ``-object memory-backend-file,id=id,size=size,mem-path=dir,share=on|off,discard-data=on|off,merge=on|off,dump=on|off,prealloc=on|off,host-nodes=host-nodes,policy=default|preferred|bind|interleave,align=align,offset=offset,readonly=on|off,rom=on|off|auto``
         Creates a memory file backend object, which can be used to back
         the guest RAM with huge pages.
-- 
2.54.0



^ permalink raw reply related	[flat|nested] 49+ messages in thread

* [PATCH v6 34/34] docs: mark '-mon' as deprecated in favour of -object
  2026-07-02 16:03 [PATCH v6 00/34] monitor: turn QMP and HMP into QOM objects Daniel P. Berrangé
                   ` (32 preceding siblings ...)
  2026-07-02 16:03 ` [PATCH v6 33/34] qemu-options: document new monitor-hmp and monitor-qmp objects Daniel P. Berrangé
@ 2026-07-02 16:03 ` Daniel P. Berrangé
  2026-07-06 11:00   ` Markus Armbruster
  33 siblings, 1 reply; 49+ messages in thread
From: Daniel P. Berrangé @ 2026-07-02 16:03 UTC (permalink / raw)
  To: qemu-devel
  Cc: Paolo Bonzini, Dr. David Alan Gilbert, Markus Armbruster,
	Marc-André Lureau, Alex Bennée,
	Philippe Mathieu-Daudé, Christian Brauner, devel,
	Daniel P. Berrangé, Peter Krempa

The high level `-qmp` and `-monitor` options can remain as convenience
wrappers, but the low level `-mon` is completed obsoleted by the new
`-object` support with 'monitor-qmp' and 'monitor-hmp' types.

Reviewed-by: Marc-André Lureau <marcandre.lureau@redhat.com>
Tested-by: Peter Krempa <pkrempa@redhat.com>
Signed-off-by: Daniel P. Berrangé <berrange@redhat.com>
---
 docs/about/deprecated.rst               | 10 ++++++++++
 docs/devel/writing-monitor-commands.rst |  4 ++--
 docs/system/arm/xenpvh.rst              |  4 ++--
 docs/system/i386/xen.rst                |  3 ++-
 docs/system/i386/xenpvh.rst             |  4 ++--
 qemu-options.hx                         |  8 ++++----
 system/vl.c                             |  2 ++
 7 files changed, 24 insertions(+), 11 deletions(-)

diff --git a/docs/about/deprecated.rst b/docs/about/deprecated.rst
index cf3e1a007c..ed2e0f8b4a 100644
--- a/docs/about/deprecated.rst
+++ b/docs/about/deprecated.rst
@@ -61,6 +61,16 @@ The ``debug-threads`` option of the ``-name`` argument is now
 ignored. Thread naming is unconditionally enabled for all platforms
 where it is supported.
 
+``-mon`` option (since 11.1)
+''''''''''''''''''''''''''''
+
+The ``-mon`` option was the generic mechanism for creating monitor
+objects if the convenience ``-qmp`` or ``-monitor`` options were not
+flexible enough. The monitor objects have been converted to QOM, so
+``-mon mode=readline`` is replaced by ``-object monitor-hmp`` and
+``-mon mode=control`` is replaced by ``-object monitor-qmp``. The
+short convenience options are not deprecated, only ``-mon``.
+
 QEMU Machine Protocol (QMP) commands
 ------------------------------------
 
diff --git a/docs/devel/writing-monitor-commands.rst b/docs/devel/writing-monitor-commands.rst
index 930da5cd06..7ae7efe327 100644
--- a/docs/devel/writing-monitor-commands.rst
+++ b/docs/devel/writing-monitor-commands.rst
@@ -52,8 +52,8 @@ shown here.
 First, QEMU should be started like this::
 
  # qemu-system-TARGET [...] \
-     -chardev socket,id=qmp,port=4444,host=localhost,server=on \
-     -mon chardev=qmp,mode=control,pretty=on
+     -chardev socket,id=chrqmp0,port=4444,host=localhost,server=on \
+     -object monitor-qmp,chardev=chrqmp0,pretty=on,id=qmp0
 
 Then, in a different terminal::
 
diff --git a/docs/system/arm/xenpvh.rst b/docs/system/arm/xenpvh.rst
index 430ac2c02e..511af63e3d 100644
--- a/docs/system/arm/xenpvh.rst
+++ b/docs/system/arm/xenpvh.rst
@@ -27,9 +27,9 @@ Sample QEMU xenpvh commands for running and connecting with Xen:
 
     qemu-system-aarch64 -xen-domid 1 \
       -chardev socket,id=libxl-cmd,path=qmp-libxl-1,server=on,wait=off \
-      -mon chardev=libxl-cmd,mode=control \
+      -object monitor-qmp,id=qmp0,chardev=libxl-cmd \
       -chardev socket,id=libxenstat-cmd,path=qmp-libxenstat-1,server=on,wait=off \
-      -mon chardev=libxenstat-cmd,mode=control \
+      -object monitor-qmp,id=qmp1,chardev=libxenstat-cmd \
       -xen-attach -name guest0 -vnc none -display none -nographic \
       -machine xenpvh -m 1301 \
       -chardev socket,id=chrtpm,path=tmp/vtpm2/swtpm-sock \
diff --git a/docs/system/i386/xen.rst b/docs/system/i386/xen.rst
index 46db5f34c1..6ff5cdb1e4 100644
--- a/docs/system/i386/xen.rst
+++ b/docs/system/i386/xen.rst
@@ -79,7 +79,8 @@ of type ``xen-console`` to connect to it. For the Xen console equivalent of
 the handy ``-serial mon:stdio`` option, for example:
 
 .. parsed-literal::
-   -chardev stdio,mux=on,id=char0,signal=off -mon char0 \\
+   -chardev stdio,mux=on,id=char0,signal=off \\
+   -object monitor-hmp,chardev=char0,id=hmp0 \\
    -device xen-console,chardev=char0
 
 The Xen network device is ``xen-net-device``, which becomes the default NIC
diff --git a/docs/system/i386/xenpvh.rst b/docs/system/i386/xenpvh.rst
index 354250f073..904778e3f5 100644
--- a/docs/system/i386/xenpvh.rst
+++ b/docs/system/i386/xenpvh.rst
@@ -33,9 +33,9 @@ case you need to construct one manually:
 
     qemu-system-i386 -xen-domid 3 -no-shutdown        \
       -chardev socket,id=libxl-cmd,path=/var/run/xen/qmp-libxl-3,server=on,wait=off \
-      -mon chardev=libxl-cmd,mode=control             \
+      -object monitor-qmp,id=qmp0,chardev=libxl-cmd   \
       -chardev socket,id=libxenstat-cmd,path=/var/run/xen/qmp-libxenstat-3,server=on,wait=off \
-      -mon chardev=libxenstat-cmd,mode=control        \
+      -object monitor-qmp,id=qmp1,chardev=libxenstat-cmd \
       -nodefaults                                     \
       -no-user-config                                 \
       -xen-attach -name g0                            \
diff --git a/qemu-options.hx b/qemu-options.hx
index 1aa548c39b..8f72b709be 100644
--- a/qemu-options.hx
+++ b/qemu-options.hx
@@ -4154,7 +4154,7 @@ The general form of a character device option is:
     ::
 
         -chardev stdio,mux=on,id=char0 \
-        -mon chardev=char0,mode=readline \
+        -object monitor-hmp,id=hmp0,chardev=char0 \
         -serial chardev:char0 \
         -serial chardev:char0
 
@@ -4166,7 +4166,7 @@ The general form of a character device option is:
     ::
 
         -chardev stdio,mux=on,id=char0 \
-        -mon chardev=char0,mode=readline \
+        -object monitor-hmp,id=hmp0,chardev=char0 \
         -parallel chardev:char0 \
         -chardev tcp,...,mux=on,id=char1 \
         -serial chardev:char1 \
@@ -5004,12 +5004,12 @@ SRST
 
     enables the QMP monitor on localhost port 4444 with pretty-printing.
 
-    The use of ``-mon mode=readline`` is historical syntax sugar
+    The use of ``-mon mode=readline`` is deprecated syntax sugar
     for the new ``-object monitor-hmp`` option, each use of which
     creates an object with the ID ``compat_monitorNNN`` where ``NNN`` is
     a counter starting from 0.
 
-    The use of ``-mon mode=control`` is historical syntax sugar
+    The use of ``-mon mode=control`` is deprecated syntax sugar
     for the new ``-object monitor-qmp`` option, each use of which
     creates an object with the ID ``compat_monitorNNN`` where ``NNN`` is
     a counter starting from 0.
diff --git a/system/vl.c b/system/vl.c
index 63bdd06dfa..061cbdf860 100644
--- a/system/vl.c
+++ b/system/vl.c
@@ -3239,6 +3239,8 @@ void qemu_init(int argc, char **argv)
                 default_monitor = 0;
                 break;
             case QEMU_OPTION_mon:
+                warn_report_once("'-mon' is deprecated, use '-object' with "
+                                 "'monitor-hmp' or 'monitor-qmp' types instead");
                 if (!qemu_opts_parse_noisily(qemu_find_opts("mon"), optarg,
                                              true)) {
                     exit(1);
-- 
2.54.0



^ permalink raw reply related	[flat|nested] 49+ messages in thread

* Re: [PATCH v6 01/34] qom: replace 'can_be_deleted' with 'prepare_delete'
  2026-07-02 16:03 ` [PATCH v6 01/34] qom: replace 'can_be_deleted' with 'prepare_delete' Daniel P. Berrangé
@ 2026-07-03  6:40   ` Markus Armbruster
  2026-07-06 13:00     ` Daniel P. Berrangé
  0 siblings, 1 reply; 49+ messages in thread
From: Markus Armbruster @ 2026-07-03  6:40 UTC (permalink / raw)
  To: Daniel P. Berrangé
  Cc: qemu-devel, Paolo Bonzini, Dr. David Alan Gilbert,
	Marc-André Lureau, Alex Bennée,
	Philippe Mathieu-Daudé, Christian Brauner, devel

Daniel P. Berrangé <berrange@redhat.com> writes:

> While most objects can perform all their cleanup in the finalizer
> method, there can be interactions with other resources / subsystems
> / threads which require that some cleanup be performed on an user
> creatable object before unparenting it and entering finalization.
>
> The current 'can_be_deleted' method runs in the deletion path and
> is intended to be used to block deletion. While it could be used
> to perform cleanup tasks, its name suggests it should be free of
> side-effects.
>
> Generalize this by renaming it to 'prepare_delete', explicitly
> allowing for cleanup to be provided. Existing users of 'can_be_deleted'
> are re-written, which provides them with more detailed/tailored error
> messages.

Old message:

        error_setg(errp, "object '%s' is in use, can not be deleted", id);

New messages:

        error_setg(errp, "Cryptodev backend '%s' is still in use",
        error_setg(errp, "Host memory backend '%s' is still mapped",
        error_setg(errp, "IOMMUFD backend '%s' still has %d users",
        error_setg(errp, "Throttle group '%s' still has multiple references",
    error_setg(errp, "Deleting CAN bus device '%s' is not supported",
    error_setg(errp, "Deleting main loop '%s' is not supported",

All now mention what kind of object it is.  Users could probably figure
this out from context easily.  Welcome small improvement.

The two unconditional ones become more accurate: they now report "not
supported" instead of "in use".

The other four lose "can not be deleted".  Users can probably figure
this out from context.  Still, I lean towards error messages like "Can't
DO-SOMETHING because REASON" until that becomes overly verbose.  What do
you think?

> Signed-off-by: Daniel P. Berrangé <berrange@redhat.com>

[...]

> diff --git a/qom/object_interfaces.c b/qom/object_interfaces.c
> index 7080f85f95..ac992aa786 100644
> --- a/qom/object_interfaces.c
> +++ b/qom/object_interfaces.c
> @@ -32,16 +32,15 @@ bool user_creatable_complete(UserCreatable *uc, Error **errp)
>      return !*errp;
>  }
>  
> -bool user_creatable_can_be_deleted(UserCreatable *uc)
> +bool user_creatable_prepare_delete(UserCreatable *uc, Error **errp)
>  {
> -
>      UserCreatableClass *ucc = USER_CREATABLE_GET_CLASS(uc);
> +    ERRP_GUARD();

No longer necessary.

>  
> -    if (ucc->can_be_deleted) {
> -        return ucc->can_be_deleted(uc);
> -    } else {
> -        return true;
> +    if (ucc->prepare_delete) {
> +        return ucc->prepare_delete(uc, errp);
>      }
> +    return true;
>  }
>  
>  void user_creatable_add_qapi(ObjectOptions *options, Error **errp)
> @@ -253,8 +252,7 @@ bool user_creatable_del(const char *id, Error **errp)
>          return false;
>      }
>  
> -    if (!user_creatable_can_be_deleted(USER_CREATABLE(obj))) {
> -        error_setg(errp, "object '%s' is in use, can not be deleted", id);
> +    if (!user_creatable_prepare_delete(USER_CREATABLE(obj), errp)) {
>          return false;
>      }
>  

[...]



^ permalink raw reply	[flat|nested] 49+ messages in thread

* Re: [PATCH v6 05/34] monitor: minimal conversion of monitors to QOM
  2026-07-02 16:03 ` [PATCH v6 05/34] monitor: minimal conversion of monitors to QOM Daniel P. Berrangé
@ 2026-07-03 11:38   ` Markus Armbruster
  2026-07-03 11:42     ` Daniel P. Berrangé
  2026-07-03 12:48   ` Markus Armbruster
  1 sibling, 1 reply; 49+ messages in thread
From: Markus Armbruster @ 2026-07-03 11:38 UTC (permalink / raw)
  To: Daniel P. Berrangé
  Cc: qemu-devel, Paolo Bonzini, Dr. David Alan Gilbert,
	Marc-André Lureau, Alex Bennée,
	Philippe Mathieu-Daudé, Christian Brauner, devel

Daniel P. Berrangé <berrange@redhat.com> writes:

> This introduces a Monitor QOM object, with MonitorHMP and
> MonitorQMP subclasses. This is the bare minimum conversion
> of just the type declarations and replacing g_new/g_free
> with object_new/object_unref. The Monitor base class is
> abstract since only the HMP/QMP variants should ever be
> created.
>
> When created through the existing QemuOpts interfaces, the
> new internal QOM object will get assigned a dynamic ID with
> the format "compat_monitorNNN" which is the historical
> QemuOpts ID naming pattern.

Uh, sometimes the name isn't "compat_monitorNNN".

I'm not sure whether talking about QemuOpts helps or hurts here.  Let me
try not to talk about it:

  This introduces abstract QOM type "monitor", with concrete subtypes
  "monitor-hmp" and "monitor-qmp". This is the bare minimum conversion
  of just the type declarations and replacing g_new/g_free with
  object_new/object_unref.

  Command line option -monitor now creates a monitor-hmp object
  /objects/compat_monitorNNN in addition to the character device
  /chardevs/compat_monitorNNN. NNN counts up from zero.

  Exception: -monitor chardev:ID creates a monitor-hmp object
  /objects/ID, and does not create a character device.

  -qmp and -qmp-pretty work the same, except they create a monitor-qmp
  object.

  -mon now creates a monitor-hmp or monitor-qmp object /objects/ID if
  the option argument provides an ID, else /objects/compat_monitorNNN.

If you want to make the connection to QemuOpts ID, add

  Note that the object's name in /objects/ matches the QemuOpts ID when
  it exists.  The only case where it doesn't exist is -mon without ID.

Finally, point to what's coming:

  A future patch will make "monitor-hmp" and "monitor-qmp" work with
  -object and object-add.

> Signed-off-by: Daniel P. Berrangé <berrange@redhat.com>



^ permalink raw reply	[flat|nested] 49+ messages in thread

* Re: [PATCH v6 05/34] monitor: minimal conversion of monitors to QOM
  2026-07-03 11:38   ` Markus Armbruster
@ 2026-07-03 11:42     ` Daniel P. Berrangé
  0 siblings, 0 replies; 49+ messages in thread
From: Daniel P. Berrangé @ 2026-07-03 11:42 UTC (permalink / raw)
  To: Markus Armbruster
  Cc: qemu-devel, Paolo Bonzini, Dr. David Alan Gilbert,
	Marc-André Lureau, Alex Bennée,
	Philippe Mathieu-Daudé, Christian Brauner, devel

On Fri, Jul 03, 2026 at 01:38:31PM +0200, Markus Armbruster wrote:
> Daniel P. Berrangé <berrange@redhat.com> writes:
> 
> > This introduces a Monitor QOM object, with MonitorHMP and
> > MonitorQMP subclasses. This is the bare minimum conversion
> > of just the type declarations and replacing g_new/g_free
> > with object_new/object_unref. The Monitor base class is
> > abstract since only the HMP/QMP variants should ever be
> > created.
> >
> > When created through the existing QemuOpts interfaces, the
> > new internal QOM object will get assigned a dynamic ID with
> > the format "compat_monitorNNN" which is the historical
> > QemuOpts ID naming pattern.
> 
> Uh, sometimes the name isn't "compat_monitorNNN".
> 
> I'm not sure whether talking about QemuOpts helps or hurts here.  Let me
> try not to talk about it:
> 
>   This introduces abstract QOM type "monitor", with concrete subtypes
>   "monitor-hmp" and "monitor-qmp". This is the bare minimum conversion
>   of just the type declarations and replacing g_new/g_free with
>   object_new/object_unref.
> 
>   Command line option -monitor now creates a monitor-hmp object
>   /objects/compat_monitorNNN in addition to the character device
>   /chardevs/compat_monitorNNN. NNN counts up from zero.
> 
>   Exception: -monitor chardev:ID creates a monitor-hmp object
>   /objects/ID, and does not create a character device.
> 
>   -qmp and -qmp-pretty work the same, except they create a monitor-qmp
>   object.
> 
>   -mon now creates a monitor-hmp or monitor-qmp object /objects/ID if
>   the option argument provides an ID, else /objects/compat_monitorNNN.
> 
> If you want to make the connection to QemuOpts ID, add
> 
>   Note that the object's name in /objects/ matches the QemuOpts ID when
>   it exists.  The only case where it doesn't exist is -mon without ID.
> 
> Finally, point to what's coming:
> 
>   A future patch will make "monitor-hmp" and "monitor-qmp" work with
>   -object and object-add.

Ok, that's all fine with me.

> 
> > Signed-off-by: Daniel P. Berrangé <berrange@redhat.com>
> 

With regards,
Daniel
-- 
|: https://berrange.com       ~~        https://hachyderm.io/@berrange :|
|: https://libvirt.org          ~~          https://entangle-photo.org :|
|: https://pixelfed.art/berrange   ~~    https://fstop138.berrange.com :|



^ permalink raw reply	[flat|nested] 49+ messages in thread

* Re: [PATCH v6 05/34] monitor: minimal conversion of monitors to QOM
  2026-07-02 16:03 ` [PATCH v6 05/34] monitor: minimal conversion of monitors to QOM Daniel P. Berrangé
  2026-07-03 11:38   ` Markus Armbruster
@ 2026-07-03 12:48   ` Markus Armbruster
  2026-07-06 13:09     ` Daniel P. Berrangé
  1 sibling, 1 reply; 49+ messages in thread
From: Markus Armbruster @ 2026-07-03 12:48 UTC (permalink / raw)
  To: Daniel P. Berrangé
  Cc: qemu-devel, Paolo Bonzini, Dr. David Alan Gilbert,
	Marc-André Lureau, Alex Bennée,
	Philippe Mathieu-Daudé, Christian Brauner, devel

Daniel P. Berrangé <berrange@redhat.com> writes:

> This introduces a Monitor QOM object, with MonitorHMP and
> MonitorQMP subclasses. This is the bare minimum conversion
> of just the type declarations and replacing g_new/g_free
> with object_new/object_unref. The Monitor base class is
> abstract since only the HMP/QMP variants should ever be
> created.
>
> When created through the existing QemuOpts interfaces, the
> new internal QOM object will get assigned a dynamic ID with
> the format "compat_monitorNNN" which is the historical
> QemuOpts ID naming pattern.
>
> Signed-off-by: Daniel P. Berrangé <berrange@redhat.com>
> ---
>  chardev/char.c             |  2 +-
>  gdbstub/system.c           |  2 +-
>  include/monitor/monitor.h  | 18 +++++++++++++++---
>  monitor/hmp.c              | 31 ++++++++++++++++++++++++++++---
>  monitor/monitor-internal.h | 18 ++++++++++++++++--
>  monitor/monitor.c          | 31 +++++++++++++++++++++++++++----
>  monitor/qmp-cmds.c         | 15 ++++++++-------
>  monitor/qmp.c              | 31 ++++++++++++++++++++++++++++---
>  stubs/monitor-internal.c   |  3 ++-
>  system/vl.c                |  7 +++----
>  10 files changed, 129 insertions(+), 29 deletions(-)
>
> diff --git a/chardev/char.c b/chardev/char.c
> index 813c04d953..c71ffa7963 100644
> --- a/chardev/char.c
> +++ b/chardev/char.c
> @@ -805,7 +805,7 @@ static Chardev *qemu_chr_new_from_name(const char *label, const char *filename,
>  
>      if (qemu_opt_get_bool(opts, "mux", 0)) {
>          assert(permit_mux_mon);
> -        monitor_new_hmp(chr, true, &err);
> +        monitor_new_hmp(NULL, chr, true, &err);

Yet another way to create a monitor, not covered by the commit message.

We get here for legacy chardev syntax "mon:...", I think.  Where exactly
in the external interface this is accepted I'm not sure.  It's
documented for -serial.  See qemu-options.hx.

There is no "mon" QemuOpts.

You pass NULL to monitor_new_hmp(), which makes it create the object as
/objects/compat_monitorNNN.


>          if (err) {
>              error_report_err(err);
>              object_unparent(OBJECT(chr));
> diff --git a/gdbstub/system.c b/gdbstub/system.c
> index 18f6a62b2e..fda8ef9352 100644
> --- a/gdbstub/system.c
> +++ b/gdbstub/system.c
> @@ -389,7 +389,7 @@ bool gdbserver_start(const char *device, Error **errp)
>          /* Initialize a monitor terminal for gdb */
>          mon_chr = qemu_chardev_new(NULL, TYPE_CHARDEV_GDB,
>                                     NULL, NULL, &error_abort);
> -        monitor_new_hmp(mon_chr, false, &error_abort);
> +        monitor_new_hmp(NULL, mon_chr, false, &error_abort);

And yet another, also no "mon" QemuOpts, and also
/objects/compat_monitorNNN.

>      } else {
>          qemu_chr_fe_deinit(&gdbserver_system_state.chr, true);
>          mon_chr = gdbserver_system_state.mon_chr;
> diff --git a/include/monitor/monitor.h b/include/monitor/monitor.h
> index b9642b58ba..83723f705a 100644
> --- a/include/monitor/monitor.h
> +++ b/include/monitor/monitor.h
> @@ -5,8 +5,17 @@
>  #include "qapi/qapi-types-misc.h"
>  #include "qemu/readline.h"
>  #include "exec/hwaddr.h"
> +#include "qom/object.h"
> +
> +#define TYPE_MONITOR "monitor"
> +OBJECT_DECLARE_TYPE(Monitor, MonitorClass, MONITOR);
> +
> +#define TYPE_MONITOR_HMP "monitor-hmp"
> +OBJECT_DECLARE_TYPE(MonitorHMP, MonitorHMPClass, MONITOR_HMP);
> +
> +#define TYPE_MONITOR_QMP "monitor-qmp"
> +OBJECT_DECLARE_TYPE(MonitorQMP, MonitorQMPClass, MONITOR_QMP);
>  
> -typedef struct MonitorHMP MonitorHMP;
>  typedef struct MonitorOptions MonitorOptions;
>  
>  #define QMP_REQ_QUEUE_LEN_MAX 8
> @@ -19,8 +28,11 @@ bool monitor_cur_is_qmp(void);
>  
>  void monitor_init_globals(void);
>  void monitor_init_globals_core(void);
> -void monitor_new_qmp(Chardev *chr, bool pretty, Error **errp);
> -void monitor_new_hmp(Chardev *chr, bool use_readline, Error **errp);
> +char *monitor_compat_id(void);
> +void monitor_new_qmp(const char *id, Chardev *chr,
> +                     bool pretty, Error **errp);
> +void monitor_new_hmp(const char *id, Chardev *chr,
> +                     bool use_readline, Error **errp);
>  int monitor_new(MonitorOptions *opts, bool allow_hmp, Error **errp);
>  int monitor_new_opts(QemuOpts *opts, Error **errp);
>  void monitor_cleanup(void);
> diff --git a/monitor/hmp.c b/monitor/hmp.c
> index 4e4468424a..da11e56854 100644
> --- a/monitor/hmp.c
> +++ b/monitor/hmp.c
> @@ -43,6 +43,20 @@
>  #include "system/block-backend.h"
>  #include "trace.h"
>  
> +OBJECT_DEFINE_TYPE(MonitorHMP, monitor_hmp, MONITOR_HMP, MONITOR);
> +
> +static void monitor_hmp_finalize(Object *obj)
> +{
> +}
> +
> +static void monitor_hmp_class_init(ObjectClass *cls, const void *data)
> +{
> +}
> +
> +static void monitor_hmp_init(Object *obj)
> +{
> +}
> +
>  static void monitor_command_cb(void *opaque, const char *cmdline,
>                                 void *readline_opaque)
>  {
> @@ -1524,12 +1538,23 @@ static void monitor_readline_flush(void *opaque)
>      monitor_flush(&mon->parent_obj);
>  }
>  
> -void monitor_new_hmp(Chardev *chr, bool use_readline, Error **errp)
> +void monitor_new_hmp(const char *id, Chardev *chr,
> +                     bool use_readline, Error **errp)
>  {
> -    MonitorHMP *mon = g_new0(MonitorHMP, 1);
> +    MonitorHMP *mon;
> +    g_autofree char *autoid = id ? NULL : monitor_compat_id();
> +    Object *obj = object_new_with_props(TYPE_MONITOR_HMP,
> +                                        object_get_objects_root(),
> +                                        id ? id : autoid,
> +                                        errp,
> +                                        NULL);
> +    if (!obj) {
> +        return;
> +    }
> +    mon = MONITOR_HMP(obj);
>  
>      if (!qemu_chr_fe_init(&mon->parent_obj.chr, chr, errp)) {
> -        g_free(mon);
> +        object_unparent(OBJECT(mon));
>          return;
>      }
>  
> diff --git a/monitor/monitor-internal.h b/monitor/monitor-internal.h
> index 4da2b2a677..05c1f2f5e0 100644
> --- a/monitor/monitor-internal.h
> +++ b/monitor/monitor-internal.h
> @@ -101,7 +101,13 @@ typedef struct HMPCommand {
>      bool coroutine;
>  } HMPCommand;
>  
> +
> +struct MonitorClass {
> +    ObjectClass parent_class;
> +};
> +
>  struct Monitor {
> +    Object parent;
>      CharFrontend chr;
>      int suspend_cnt;            /* Needs to be accessed atomically */
>      bool is_qmp;
> @@ -127,6 +133,10 @@ struct Monitor {
>      int reset_seen;
>  };
>  
> +struct MonitorHMPClass {
> +    MonitorClass parent_class;
> +};
> +
>  struct MonitorHMP {
>      Monitor parent_obj;
>      bool use_readline;
> @@ -140,7 +150,11 @@ struct MonitorHMP {
>      ReadLineState *rs;
>  };
>  
> -typedef struct {
> +struct MonitorQMPClass {
> +    MonitorClass parent_class;
> +};
> +
> +struct MonitorQMP {
>      Monitor parent_obj;
>      JSONMessageParser parser;
>      bool pretty;
> @@ -160,7 +174,7 @@ typedef struct {
>      QemuMutex qmp_queue_lock;
>      /* Input queue that holds all the parsed QMP requests */
>      GQueue *qmp_requests;
> -} MonitorQMP;
> +};
>  
>  /**
>   * Is @mon a QMP monitor?
> diff --git a/monitor/monitor.c b/monitor/monitor.c
> index a87597e606..18a1d8ddde 100644
> --- a/monitor/monitor.c
> +++ b/monitor/monitor.c
> @@ -73,6 +73,22 @@ static GHashTable *coroutine_mon; /* Maps Coroutine* to Monitor* */
>  MonitorList mon_list;
>  static bool monitor_destroyed;
>  
> +int monitor_device_index;
> +
> +OBJECT_DEFINE_ABSTRACT_TYPE(Monitor, monitor, MONITOR, OBJECT);
> +
> +static void monitor_finalize(Object *obj)
> +{
> +}
> +
> +static void monitor_class_init(ObjectClass *cls, const void *data)
> +{
> +}
> +
> +static void monitor_init(Object *obj)
> +{
> +}
> +
>  Monitor *monitor_cur(void)
>  {
>      Monitor *mon;
> @@ -598,7 +614,7 @@ void monitor_list_append(Monitor *mon)
>  
>      if (mon) {
>          monitor_data_destroy(mon);
> -        g_free(mon);
> +        object_unparent(OBJECT(mon));
>      }
>  }
>  
> @@ -680,7 +696,7 @@ void monitor_cleanup(void)
>          monitor_flush(mon);
>          monitor_data_destroy(mon);
>          qemu_mutex_lock(&monitor_lock);
> -        g_free(mon);
> +        object_unparent(OBJECT(mon));
>      }
>      qemu_mutex_unlock(&monitor_lock);
>  
> @@ -715,6 +731,13 @@ void monitor_init_globals(void)
>      aio_co_schedule(iohandler_get_aio_context(), qmp_dispatcher_co);
>  }
>  
> +char *monitor_compat_id(void)
> +{
> +    static int monitor_device_index;
> +
> +    return g_strdup_printf("compat_monitor%d", monitor_device_index++);
> +}
> +
>  int monitor_new(MonitorOptions *opts, bool allow_hmp, Error **errp)
>  {
>      ERRP_GUARD();
> @@ -732,7 +755,7 @@ int monitor_new(MonitorOptions *opts, bool allow_hmp, Error **errp)
>  
>      switch (opts->mode) {
>      case MONITOR_MODE_CONTROL:
> -        monitor_new_qmp(chr, opts->pretty, errp);
> +        monitor_new_qmp(opts->id, chr, opts->pretty, errp);
>          break;
>      case MONITOR_MODE_READLINE:
>          if (!allow_hmp) {
> @@ -743,7 +766,7 @@ int monitor_new(MonitorOptions *opts, bool allow_hmp, Error **errp)
>              error_setg(errp, "'pretty' is not compatible with HMP monitors");
>              return -1;
>          }
> -        monitor_new_hmp(chr, true, errp);
> +        monitor_new_hmp(opts->id, chr, true, errp);
>          break;
>      default:
>          g_assert_not_reached();
> diff --git a/monitor/qmp-cmds.c b/monitor/qmp-cmds.c
> index aa9ee8a391..bfde769ef0 100644
> --- a/monitor/qmp-cmds.c
> +++ b/monitor/qmp-cmds.c
> @@ -166,12 +166,12 @@ char *qmp_human_monitor_command(const char *command_line, bool has_cpu_index,
>                                  int64_t cpu_index, Error **errp)
>  {
>      char *output = NULL;
> -    MonitorHMP hmp = {};
> +    MonitorHMP *hmp = MONITOR_HMP(object_new(TYPE_MONITOR_HMP));
>  
> -    monitor_data_init(&hmp.parent_obj, false, true, false);
> +    monitor_data_init(&hmp->parent_obj, false, true, false);
>  
>      if (has_cpu_index) {
> -        int ret = monitor_set_cpu(&hmp.parent_obj, cpu_index);
> +        int ret = monitor_set_cpu(&hmp->parent_obj, cpu_index);
>          if (ret < 0) {
>              error_setg(errp, QERR_INVALID_PARAMETER_VALUE, "cpu-index",
>                         "a CPU number");
> @@ -179,14 +179,15 @@ char *qmp_human_monitor_command(const char *command_line, bool has_cpu_index,
>          }
>      }
>  
> -    handle_hmp_command(&hmp, command_line);
> +    handle_hmp_command(hmp, command_line);
>  
> -    WITH_QEMU_LOCK_GUARD(&hmp.parent_obj.mon_lock) {
> -        output = g_strdup(hmp.parent_obj.outbuf->str);
> +    WITH_QEMU_LOCK_GUARD(&hmp->parent_obj.mon_lock) {
> +        output = g_strdup(hmp->parent_obj.outbuf->str);
>      }
>  
>  out:
> -    monitor_data_destroy(&hmp.parent_obj);
> +    monitor_data_destroy(&hmp->parent_obj);
> +    object_unref(hmp);
>      return output;
>  }
>  
> diff --git a/monitor/qmp.c b/monitor/qmp.c
> index cb28a95efd..1ef09352fc 100644
> --- a/monitor/qmp.c
> +++ b/monitor/qmp.c
> @@ -71,6 +71,20 @@ typedef struct QMPRequest QMPRequest;
>  
>  QmpCommandList qmp_commands, qmp_cap_negotiation_commands;
>  
> +OBJECT_DEFINE_TYPE(MonitorQMP, monitor_qmp, MONITOR_QMP, MONITOR);
> +
> +static void monitor_qmp_finalize(Object *obj)
> +{
> +}
> +
> +static void monitor_qmp_class_init(ObjectClass *cls, const void *data)
> +{
> +}
> +
> +static void monitor_qmp_init(Object *obj)
> +{
> +}
> +
>  static bool qmp_oob_enabled(MonitorQMP *mon)
>  {
>      return mon->capab[QMP_CAPABILITY_OOB];
> @@ -513,12 +527,23 @@ static void monitor_qmp_setup_handlers_bh(void *opaque)
>      monitor_list_append(&mon->parent_obj);
>  }
>  
> -void monitor_new_qmp(Chardev *chr, bool pretty, Error **errp)
> +void monitor_new_qmp(const char *id, Chardev *chr,
> +                     bool pretty, Error **errp)
>  {
> -    MonitorQMP *mon = g_new0(MonitorQMP, 1);
> +    MonitorQMP *mon;
> +    g_autofree char *autoid = id ? NULL : monitor_compat_id();
> +    Object *obj = object_new_with_props(TYPE_MONITOR_QMP,
> +                                        object_get_objects_root(),
> +                                        id ? id : autoid,
> +                                        errp,
> +                                        NULL);
> +    if (!obj) {
> +        return;
> +    }
> +    mon = MONITOR_QMP(obj);
>  
>      if (!qemu_chr_fe_init(&mon->parent_obj.chr, chr, errp)) {
> -        g_free(mon);
> +        object_unparent(OBJECT(mon));
>          return;
>      }
>      qemu_chr_fe_set_echo(&mon->parent_obj.chr, true);
> diff --git a/stubs/monitor-internal.c b/stubs/monitor-internal.c
> index 23d58da184..20367b7e9a 100644
> --- a/stubs/monitor-internal.c
> +++ b/stubs/monitor-internal.c
> @@ -8,6 +8,7 @@ int monitor_get_fd(Monitor *mon, const char *name, Error **errp)
>      return -1;
>  }
>  
> -void monitor_new_hmp(Chardev *chr, bool use_readline, Error **errp)
> +void monitor_new_hmp(const char *id, Chardev *chr,
> +                     bool use_readline, Error **errp)
>  {
>  }
> diff --git a/system/vl.c b/system/vl.c
> index 6643242729..4737c03872 100644
> --- a/system/vl.c
> +++ b/system/vl.c
> @@ -1252,7 +1252,6 @@ static int mon_init_func(void *opaque, QemuOpts *opts, Error **errp)
>  
>  static void monitor_parse(const char *str, const char *mode, bool pretty)
>  {
> -    static int monitor_device_index = 0;
>      QemuOpts *opts;
>      const char *p;
>      char label[32];
> @@ -1260,8 +1259,9 @@ static void monitor_parse(const char *str, const char *mode, bool pretty)
>      if (strstart(str, "chardev:", &p)) {
>          snprintf(label, sizeof(label), "%s", p);
>      } else {
> -        snprintf(label, sizeof(label), "compat_monitor%d",
> -                 monitor_device_index);
> +        g_autofree char *id = monitor_compat_id();
> +        assert(strlen(id) < sizeof(label));
> +        memcpy(label, id, strlen(id) + 1);
>          opts = qemu_chr_parse_compat(label, str, true);
>          if (!opts) {
>              error_report("parse error: %s", str);
> @@ -1277,7 +1277,6 @@ static void monitor_parse(const char *str, const char *mode, bool pretty)
>      } else {
>          assert(pretty == false);
>      }
> -    monitor_device_index++;

Before the patch, we increment the NNN in "compat_monitorNNN" exactly
here.

Afterwards, we increment it in monitor_compat_id(), which gets called
from here and several additional places.

If any of the additional places execute before an execution of the
original place, NNN change.

Feels tolerable, but needs a mention in the commit message.

>  }
>  
>  struct device_config {



^ permalink raw reply	[flat|nested] 49+ messages in thread

* Re: [PATCH v6 06/34] monitor: add 'chardev' property to Monitor base class
  2026-07-02 16:03 ` [PATCH v6 06/34] monitor: add 'chardev' property to Monitor base class Daniel P. Berrangé
@ 2026-07-03 13:17   ` Markus Armbruster
  2026-07-06 13:19     ` Daniel P. Berrangé
  0 siblings, 1 reply; 49+ messages in thread
From: Markus Armbruster @ 2026-07-03 13:17 UTC (permalink / raw)
  To: Daniel P. Berrangé
  Cc: qemu-devel, Paolo Bonzini, Dr. David Alan Gilbert,
	Markus Armbruster, Marc-André Lureau, Alex Bennée,
	Philippe Mathieu-Daudé, Christian Brauner, devel,
	Peter Krempa

Daniel P. Berrangé <berrange@redhat.com> writes:

> This is associates both QMP and HMP monitors with a character
> device backend.
>
> Reviewed-by: Marc-André Lureau <marcandre.lureau@redhat.com>
> Tested-by: Peter Krempa <pkrempa@redhat.com>
> Signed-off-by: Daniel P. Berrangé <berrange@redhat.com>
> ---
>  chardev/char.c             |  3 ++-
>  gdbstub/system.c           |  4 ++--
>  include/monitor/monitor.h  |  4 ++--
>  monitor/hmp.c              | 10 ++++++--
>  monitor/monitor-internal.h |  2 ++
>  monitor/monitor.c          | 47 ++++++++++++++++++++++++++++++--------
>  monitor/qmp.c              | 18 ++++++++++-----
>  stubs/monitor-internal.c   |  2 +-
>  8 files changed, 67 insertions(+), 23 deletions(-)
>
> diff --git a/chardev/char.c b/chardev/char.c
> index c71ffa7963..22e5bae388 100644
> --- a/chardev/char.c
> +++ b/chardev/char.c
> @@ -804,8 +804,9 @@ static Chardev *qemu_chr_new_from_name(const char *label, const char *filename,
>      }
>  
>      if (qemu_opt_get_bool(opts, "mux", 0)) {
> +        const char *chardev_id = qemu_opts_id(opts);
>          assert(permit_mux_mon);
> -        monitor_new_hmp(NULL, chr, true, &err);
> +        monitor_new_hmp(NULL, chardev_id, true, &err);
>          if (err) {
>              error_report_err(err);
>              object_unparent(OBJECT(chr));
> diff --git a/gdbstub/system.c b/gdbstub/system.c
> index fda8ef9352..d681476f5e 100644
> --- a/gdbstub/system.c
> +++ b/gdbstub/system.c
> @@ -387,9 +387,9 @@ bool gdbserver_start(const char *device, Error **errp)
>          qemu_add_vm_change_state_handler(gdb_vm_state_change, NULL);
>  
>          /* Initialize a monitor terminal for gdb */
> -        mon_chr = qemu_chardev_new(NULL, TYPE_CHARDEV_GDB,
> +        mon_chr = qemu_chardev_new("gdbchrdev0", TYPE_CHARDEV_GDB,
>                                     NULL, NULL, &error_abort);

This chardev now gets ID "gdbchrdev0" instead of id_generate(ID_CHR).

If you truly need this, you have to mention it in the commit message.

> -        monitor_new_hmp(NULL, mon_chr, false, &error_abort);
> +        monitor_new_hmp(NULL, "gdbchrdev0", false, &error_abort);

If you don't, then passing mon_chr->label here should do the trick.

>      } else {
>          qemu_chr_fe_deinit(&gdbserver_system_state.chr, true);
>          mon_chr = gdbserver_system_state.mon_chr;
> diff --git a/include/monitor/monitor.h b/include/monitor/monitor.h
> index 83723f705a..bfbeedec9b 100644
> --- a/include/monitor/monitor.h
> +++ b/include/monitor/monitor.h
> @@ -29,9 +29,9 @@ bool monitor_cur_is_qmp(void);
>  void monitor_init_globals(void);
>  void monitor_init_globals_core(void);
>  char *monitor_compat_id(void);
> -void monitor_new_qmp(const char *id, Chardev *chr,
> +void monitor_new_qmp(const char *id, const char *chardev_id,
>                       bool pretty, Error **errp);
> -void monitor_new_hmp(const char *id, Chardev *chr,
> +void monitor_new_hmp(const char *id, const char *chardev_id,
>                       bool use_readline, Error **errp);
>  int monitor_new(MonitorOptions *opts, bool allow_hmp, Error **errp);
>  int monitor_new_opts(QemuOpts *opts, Error **errp);
> diff --git a/monitor/hmp.c b/monitor/hmp.c
> index da11e56854..be59b6ca65 100644
> --- a/monitor/hmp.c
> +++ b/monitor/hmp.c
> @@ -23,6 +23,7 @@
>   */
>  
>  #include "qemu/osdep.h"
> +#include "qapi/error.h"
>  #include <dirent.h>
>  #include "hw/core/qdev.h"
>  #include "hw/core/sysemu-cpu-ops.h"
> @@ -1538,22 +1539,27 @@ static void monitor_readline_flush(void *opaque)
>      monitor_flush(&mon->parent_obj);
>  }
>  
> -void monitor_new_hmp(const char *id, Chardev *chr,
> +void monitor_new_hmp(const char *id, const char *chardev_id,
>                       bool use_readline, Error **errp)

You now pass a chardev ID instead of a Chardev *.

I *guess* that's possible because chardev_new() asserts it's passed an
ID.

I should now check all callers of monitor_new_hmp() to make sure they
pass the correct ID.  On a Friday afternoon.  I hoist the white flag.

Such refactorings should be mentioned in the commit message.  Even
better would be a separate patch.

>  {
> +    ERRP_GUARD();
>      MonitorHMP *mon;
>      g_autofree char *autoid = id ? NULL : monitor_compat_id();
>      Object *obj = object_new_with_props(TYPE_MONITOR_HMP,
>                                          object_get_objects_root(),
>                                          id ? id : autoid,
>                                          errp,
> +                                        "chardev", chardev_id,
>                                          NULL);
> +
>      if (!obj) {
>          return;
>      }
> +
>      mon = MONITOR_HMP(obj);
>  
> -    if (!qemu_chr_fe_init(&mon->parent_obj.chr, chr, errp)) {
> +    monitor_complete(MONITOR(mon), errp);
> +    if (*errp) {
>          object_unparent(OBJECT(mon));
>          return;
>      }

[...]



^ permalink raw reply	[flat|nested] 49+ messages in thread

* Re: [PATCH v6 31/34] monitor: add support for auto-deleting monitors upon close
  2026-07-02 16:03 ` [PATCH v6 31/34] monitor: add support for auto-deleting monitors upon close Daniel P. Berrangé
@ 2026-07-03 13:42   ` Markus Armbruster
  2026-07-06 13:32     ` Daniel P. Berrangé
  0 siblings, 1 reply; 49+ messages in thread
From: Markus Armbruster @ 2026-07-03 13:42 UTC (permalink / raw)
  To: Daniel P. Berrangé
  Cc: qemu-devel, Paolo Bonzini, Dr. David Alan Gilbert,
	Marc-André Lureau, Alex Bennée,
	Philippe Mathieu-Daudé, Christian Brauner, devel,
	Peter Krempa

Daniel P. Berrangé <berrange@redhat.com> writes:

> The default monitor is usually a long lived object that will exist for
> the entire lifetime of the VM. A monitor can only service a single
> client at a time though, and so it might be desirable to hotplug
> additional monitors at runtime for specific tasks. If doing that,
> however, there is a need to remove the monitor when it is no longer
> needed.

I challenged this in review of v5, and you replied with a convincing use
case: management application hands a monitor to its client.  Would be
nice to have in the commit message.  Up to you.

> Allowing a client to run "object-del" against its own monitor adds
> complex edge cases, as it would be desirable to send the QMP response
> despite the monitor sending it being deleted. Doing "object-del" alone
> will also result in orphaning a character device backend instance, as
> there is no opportunity to run the companion "chardev-del" command.
>
> A simpler way to ensure cleanup is to add the concept of auto-deleting
> monitor objects. Specifically when the "CHR_EVENT_CLOSED" event is
> emitted, the equivalent of "object-del" + "chardev-del" can be run
> internally. Since the transient client has already droppped its
> monitor connection, there is no synchronization to be concerned about
> with sending QMP replies. There is still some internal synchronization
> needed, however, between the character device event callback and the
> bottom-half that runs the delete. There is a chance that an incoming
> client connection may arise before the bottom-half runs, which has
> to be checked. Once the monitor object is deleted, the event callback
> is unregistered from the character device, eliminating any further
> races before the character device is fully deleted.
>
> This is implemented via a new "close-action=none|delete" property on
> the 'monitor-qmp' object. This concept could be extended with further
> actions in future, for example:
>
>  * close-action=shutdown - graceful guest shutdown
>  * close-action=terminate - immediate guest poweroff
>  * close-action=stop - pause guest CPUs while the monitor is not
>                        connected to any client
>
> This is left as an exercise for future interested contributors.
>
> Tested-by: Peter Krempa <pkrempa@redhat.com>
> Reviewed-by: Marc-André Lureau <marcandre.lureau@redhat.com>
> Signed-off-by: Daniel P. Berrangé <berrange@redhat.com>

QAPI schema
Acked-by: Markus Armbruster <armbru@redhat.com>



^ permalink raw reply	[flat|nested] 49+ messages in thread

* Re: [PATCH v6 33/34] qemu-options: document new monitor-hmp and monitor-qmp objects
  2026-07-02 16:03 ` [PATCH v6 33/34] qemu-options: document new monitor-hmp and monitor-qmp objects Daniel P. Berrangé
@ 2026-07-06 10:56   ` Markus Armbruster
  2026-07-06 13:38     ` Daniel P. Berrangé
  0 siblings, 1 reply; 49+ messages in thread
From: Markus Armbruster @ 2026-07-06 10:56 UTC (permalink / raw)
  To: Daniel P. Berrangé
  Cc: qemu-devel, Paolo Bonzini, Dr. David Alan Gilbert,
	Markus Armbruster, Marc-André Lureau, Alex Bennée,
	Philippe Mathieu-Daudé, Christian Brauner, devel

Daniel P. Berrangé <berrange@redhat.com> writes:

> Add new docs for the `-object monitor-hmp` and `-object monitor-qmp`
> options, updating `-mon` to state that it is legacy syntax sugar
> for the new `-object` args.
>
> Signed-off-by: Daniel P. Berrangé <berrange@redhat.com>
> ---
>  qemu-options.hx | 61 ++++++++++++++++++++++++++++++++++++++++++++++---
>  1 file changed, 58 insertions(+), 3 deletions(-)
>
> diff --git a/qemu-options.hx b/qemu-options.hx
> index e44b47de68..1aa548c39b 100644
> --- a/qemu-options.hx
> +++ b/qemu-options.hx
> @@ -4955,6 +4955,11 @@ SRST
>      port). The default device is ``vc`` in graphical mode and ``stdio``
>      in non graphical mode. Use ``-monitor none`` to disable the default
>      monitor.
> +
> +    The use of ``-monitor dev`` is syntax sugar for creating a character

For what it's worth, Wikipedia redirects "syntax sugar" to "syntactic
sugar", and uses only the latter in the article.

Could also use "shorthand".  Matter of taste.

> +    device from ``dev`` and pairing it with ``-object monitor-hmp``.
> +    Both the character device and monitor object will be given an ID
> +    ``compat_monitorNNN`` where ``NNN`` is a counter starting from 0.
>  ERST
>  DEF("qmp", HAS_ARG, QEMU_OPTION_qmp, \
>      "-qmp dev        like -monitor but opens in 'control' mode\n",
> @@ -4966,9 +4971,10 @@ SRST
>  
>          -qmp tcp:localhost:4444,server=on,wait=off
>  
> -    Not all options are configurable via this syntax; for maximum
> -    flexibility use the ``-mon`` option and an accompanying ``-chardev``.
> -
> +    The use of ``-monitor dev`` is syntax sugar for creating a character

Pasto, you mean ``-qmp dev``.

> +    device from ``dev`` and pairing it with ``-object monitor-qmp``.
> +    Both the character device and monitor object will be given an ID
> +    ``compat_monitorNNN`` where ``NNN`` is a counter starting from 0.
>  ERST
>  DEF("qmp-pretty", HAS_ARG, QEMU_OPTION_qmp_pretty, \
>      "-qmp-pretty dev like -qmp but uses pretty JSON formatting\n",
> @@ -4997,6 +5003,16 @@ SRST
>        -mon chardev=mon1,mode=control,pretty=on
>  
>      enables the QMP monitor on localhost port 4444 with pretty-printing.
> +
> +    The use of ``-mon mode=readline`` is historical syntax sugar

What does "historical" mean?  The next patch replaces it by
"deprecated".  Suggest to scratch it here.

> +    for the new ``-object monitor-hmp`` option, each use of which
> +    creates an object with the ID ``compat_monitorNNN`` where ``NNN`` is
> +    a counter starting from 0.
> +
> +    The use of ``-mon mode=control`` is historical syntax sugar
> +    for the new ``-object monitor-qmp`` option, each use of which
> +    creates an object with the ID ``compat_monitorNNN`` where ``NNN`` is
> +    a counter starting from 0.
>  ERST
>  
>  DEF("debugcon", HAS_ARG, QEMU_OPTION_debugcon, \
> @@ -5747,6 +5763,45 @@ SRST
>      they are specified. Note that the 'id' property must be set. These
>      objects are placed in the '/objects' path.
>  
> +    ``-object monitor-hmp,id=id,chardev=chardev_id,readline=on|off``
> +        Set up a monitor running the Human Monitor Protocol,
> +        connected to the chardev ``chardev_id``.
> +
> +        The ``id`` parameter is a unique ID that can be used
> +        to dynamically delete the monitor at runtime. Note
> +        that monitors created using the historical syntax
> +        will be allocated IDs following the pattern ``hmpcompatNN``.

``compat_monitorNNN``

> +        Mixing ``-object`` with the historical monitor syntax is
> +        discouraged.

Clearer, I think: "with -monitor and -mon".

The next patch deprecates -mon, and then this could become just "with
-monitor".

> +
> +        The ``readline`` parameter, which defaults to ``on``,
> +        controls whether the monitor provides line editing.
> +
> +    ``-object monitor-qmp,id=id,chardev=chardev_id,pretty=on|off,close-action=none|delete``
> +        Set up a monitor running the QEMU Monitor Protocol,
> +        connected to the chardev ``chardev_id``.
> +
> +        The ``id`` parameter is a unique ID that can be used
> +        to dynamically delete the monitor at runtime. Note
> +        that monitors created using the historical syntax
> +        will be allocated IDs following the pattern ``qmpcompatNN``.

``compat_monitorNNN``

> +        Mixing ``-object`` with the historical monitor syntax is
> +        discouraged.

"with -qmp, -qmp-pretty, and -mon".

The next patch deprecates -mon, and then this could become just "with
-qmp and -qmp-pretty".

> +
> +        The ``pretty`` parameter, which defaults to ``off``,
> +        controls whether the monitor responses are pretty
> +        printed as multi-line indented JSON, as opposed to
> +        constrained to a single line without extraneous
> +        whitespace.
> +
> +        The ``close-action`` parameter, which defaults to ``none``,
> +        controls what happens when the connection to the monitor
> +        is terminated by the user. If set to ``delete``, then the
> +        ``monitor-qmp`` object and its associated character
> +        device are both immediately deleted. This can be useful
> +        if an extra monitor was hotplugged for a specific task

Comma after task?

> +        and should be unplugged when completed.
> +
>      ``-object memory-backend-file,id=id,size=size,mem-path=dir,share=on|off,discard-data=on|off,merge=on|off,dump=on|off,prealloc=on|off,host-nodes=host-nodes,policy=default|preferred|bind|interleave,align=align,offset=offset,readonly=on|off,rom=on|off|auto``
>          Creates a memory file backend object, which can be used to back
>          the guest RAM with huge pages.



^ permalink raw reply	[flat|nested] 49+ messages in thread

* Re: [PATCH v6 34/34] docs: mark '-mon' as deprecated in favour of -object
  2026-07-02 16:03 ` [PATCH v6 34/34] docs: mark '-mon' as deprecated in favour of -object Daniel P. Berrangé
@ 2026-07-06 11:00   ` Markus Armbruster
  0 siblings, 0 replies; 49+ messages in thread
From: Markus Armbruster @ 2026-07-06 11:00 UTC (permalink / raw)
  To: Daniel P. Berrangé
  Cc: qemu-devel, Paolo Bonzini, Dr. David Alan Gilbert,
	Marc-André Lureau, Alex Bennée,
	Philippe Mathieu-Daudé, Christian Brauner, devel,
	Peter Krempa

Daniel P. Berrangé <berrange@redhat.com> writes:

> The high level `-qmp` and `-monitor` options can remain as convenience
> wrappers, but the low level `-mon` is completed obsoleted by the new
> `-object` support with 'monitor-qmp' and 'monitor-hmp' types.
>
> Reviewed-by: Marc-André Lureau <marcandre.lureau@redhat.com>
> Tested-by: Peter Krempa <pkrempa@redhat.com>
> Signed-off-by: Daniel P. Berrangé <berrange@redhat.com>

Reviewed-by: Markus Armbruster <armbru@redhat.com>



^ permalink raw reply	[flat|nested] 49+ messages in thread

* Re: [PATCH v6 01/34] qom: replace 'can_be_deleted' with 'prepare_delete'
  2026-07-03  6:40   ` Markus Armbruster
@ 2026-07-06 13:00     ` Daniel P. Berrangé
  2026-07-06 13:49       ` Markus Armbruster
  0 siblings, 1 reply; 49+ messages in thread
From: Daniel P. Berrangé @ 2026-07-06 13:00 UTC (permalink / raw)
  To: Markus Armbruster
  Cc: qemu-devel, Paolo Bonzini, Dr. David Alan Gilbert,
	Marc-André Lureau, Alex Bennée,
	Philippe Mathieu-Daudé, Christian Brauner, devel

On Fri, Jul 03, 2026 at 08:40:28AM +0200, Markus Armbruster wrote:
> Daniel P. Berrangé <berrange@redhat.com> writes:
> 
> > While most objects can perform all their cleanup in the finalizer
> > method, there can be interactions with other resources / subsystems
> > / threads which require that some cleanup be performed on an user
> > creatable object before unparenting it and entering finalization.
> >
> > The current 'can_be_deleted' method runs in the deletion path and
> > is intended to be used to block deletion. While it could be used
> > to perform cleanup tasks, its name suggests it should be free of
> > side-effects.
> >
> > Generalize this by renaming it to 'prepare_delete', explicitly
> > allowing for cleanup to be provided. Existing users of 'can_be_deleted'
> > are re-written, which provides them with more detailed/tailored error
> > messages.
> 
> Old message:
> 
>         error_setg(errp, "object '%s' is in use, can not be deleted", id);
> 
> New messages:
> 
>         error_setg(errp, "Cryptodev backend '%s' is still in use",
>         error_setg(errp, "Host memory backend '%s' is still mapped",
>         error_setg(errp, "IOMMUFD backend '%s' still has %d users",
>         error_setg(errp, "Throttle group '%s' still has multiple references",
>     error_setg(errp, "Deleting CAN bus device '%s' is not supported",
>     error_setg(errp, "Deleting main loop '%s' is not supported",
> 
> All now mention what kind of object it is.  Users could probably figure
> this out from context easily.  Welcome small improvement.
> 
> The two unconditional ones become more accurate: they now report "not
> supported" instead of "in use".
> 
> The other four lose "can not be deleted".  Users can probably figure
> this out from context.  Still, I lean towards error messages like "Can't
> DO-SOMETHING because REASON" until that becomes overly verbose.  What do
> you think?

I'll change them as follows:

  -        error_setg(errp, "Cryptodev backend '%s' is still in use",
  +        error_setg(errp,
  +                   "Cannot delete cryptodev backend '%s' that is in use",


  -        error_setg(errp, "Host memory backend '%s' is still mapped",
  +        error_setg(errp,
  +                   "Cannot delete host memory backend '%s' which is mapped",


  -        error_setg(errp, "IOMMUFD backend '%s' still has %d users",
  +        error_setg(errp, "Can not delete IOMMUFD backend '%s' with %d users",


  -        error_setg(errp, "Throttle group '%s' still has multiple references",
  +        error_setg(errp,
  +                   "Cannot delete throttle group '%s' with active references",


With regards,
Daniel
-- 
|: https://berrange.com       ~~        https://hachyderm.io/@berrange :|
|: https://libvirt.org          ~~          https://entangle-photo.org :|
|: https://pixelfed.art/berrange   ~~    https://fstop138.berrange.com :|



^ permalink raw reply	[flat|nested] 49+ messages in thread

* Re: [PATCH v6 05/34] monitor: minimal conversion of monitors to QOM
  2026-07-03 12:48   ` Markus Armbruster
@ 2026-07-06 13:09     ` Daniel P. Berrangé
  0 siblings, 0 replies; 49+ messages in thread
From: Daniel P. Berrangé @ 2026-07-06 13:09 UTC (permalink / raw)
  To: Markus Armbruster
  Cc: qemu-devel, Paolo Bonzini, Dr. David Alan Gilbert,
	Marc-André Lureau, Alex Bennée,
	Philippe Mathieu-Daudé, Christian Brauner, devel

On Fri, Jul 03, 2026 at 02:48:14PM +0200, Markus Armbruster wrote:
> Daniel P. Berrangé <berrange@redhat.com> writes:
> 
> > This introduces a Monitor QOM object, with MonitorHMP and
> > MonitorQMP subclasses. This is the bare minimum conversion
> > of just the type declarations and replacing g_new/g_free
> > with object_new/object_unref. The Monitor base class is
> > abstract since only the HMP/QMP variants should ever be
> > created.
> >
> > When created through the existing QemuOpts interfaces, the
> > new internal QOM object will get assigned a dynamic ID with
> > the format "compat_monitorNNN" which is the historical
> > QemuOpts ID naming pattern.
> >
> > Signed-off-by: Daniel P. Berrangé <berrange@redhat.com>
> > ---
> >  chardev/char.c             |  2 +-
> >  gdbstub/system.c           |  2 +-
> >  include/monitor/monitor.h  | 18 +++++++++++++++---
> >  monitor/hmp.c              | 31 ++++++++++++++++++++++++++++---
> >  monitor/monitor-internal.h | 18 ++++++++++++++++--
> >  monitor/monitor.c          | 31 +++++++++++++++++++++++++++----
> >  monitor/qmp-cmds.c         | 15 ++++++++-------
> >  monitor/qmp.c              | 31 ++++++++++++++++++++++++++++---
> >  stubs/monitor-internal.c   |  3 ++-
> >  system/vl.c                |  7 +++----
> >  10 files changed, 129 insertions(+), 29 deletions(-)
> >
> > diff --git a/chardev/char.c b/chardev/char.c
> > index 813c04d953..c71ffa7963 100644
> > --- a/chardev/char.c
> > +++ b/chardev/char.c
> > @@ -805,7 +805,7 @@ static Chardev *qemu_chr_new_from_name(const char *label, const char *filename,
> >  
> >      if (qemu_opt_get_bool(opts, "mux", 0)) {
> >          assert(permit_mux_mon);
> > -        monitor_new_hmp(chr, true, &err);
> > +        monitor_new_hmp(NULL, chr, true, &err);
> 
> Yet another way to create a monitor, not covered by the commit message.
>
> We get here for legacy chardev syntax "mon:...", I think.  Where exactly
> in the external interface this is accepted I'm not sure.  It's
> documented for -serial.  See qemu-options.hx.

Yes, I confirmed this codepath hits for -serial (which means it will
apply to -parallel too, but no one cares about that )

> There is no "mon" QemuOpts.
> 
> You pass NULL to monitor_new_hmp(), which makes it create the object as
> /objects/compat_monitorNNN.

Yes.

> > diff --git a/gdbstub/system.c b/gdbstub/system.c
> > index 18f6a62b2e..fda8ef9352 100644
> > --- a/gdbstub/system.c
> > +++ b/gdbstub/system.c
> > @@ -389,7 +389,7 @@ bool gdbserver_start(const char *device, Error **errp)
> >          /* Initialize a monitor terminal for gdb */
> >          mon_chr = qemu_chardev_new(NULL, TYPE_CHARDEV_GDB,
> >                                     NULL, NULL, &error_abort);
> > -        monitor_new_hmp(mon_chr, false, &error_abort);
> > +        monitor_new_hmp(NULL, mon_chr, false, &error_abort);
> 
> And yet another, also no "mon" QemuOpts, and also
> /objects/compat_monitorNNN.

Yep, triggered with -gdbstub  arg


> > diff --git a/system/vl.c b/system/vl.c
> > index 6643242729..4737c03872 100644
> > --- a/system/vl.c
> > +++ b/system/vl.c
> > @@ -1252,7 +1252,6 @@ static int mon_init_func(void *opaque, QemuOpts *opts, Error **errp)
> >  
> >  static void monitor_parse(const char *str, const char *mode, bool pretty)
> >  {
> > -    static int monitor_device_index = 0;
> >      QemuOpts *opts;
> >      const char *p;
> >      char label[32];
> > @@ -1260,8 +1259,9 @@ static void monitor_parse(const char *str, const char *mode, bool pretty)
> >      if (strstart(str, "chardev:", &p)) {
> >          snprintf(label, sizeof(label), "%s", p);
> >      } else {
> > -        snprintf(label, sizeof(label), "compat_monitor%d",
> > -                 monitor_device_index);
> > +        g_autofree char *id = monitor_compat_id();
> > +        assert(strlen(id) < sizeof(label));
> > +        memcpy(label, id, strlen(id) + 1);
> >          opts = qemu_chr_parse_compat(label, str, true);
> >          if (!opts) {
> >              error_report("parse error: %s", str);
> > @@ -1277,7 +1277,6 @@ static void monitor_parse(const char *str, const char *mode, bool pretty)
> >      } else {
> >          assert(pretty == false);
> >      }
> > -    monitor_device_index++;
> 
> Before the patch, we increment the NNN in "compat_monitorNNN" exactly
> here.
> 
> Afterwards, we increment it in monitor_compat_id(), which gets called
> from here and several additional places.
> 
> If any of the additional places execute before an execution of the
> original place, NNN change.
> 
> Feels tolerable, but needs a mention in the commit message.

Yes, will mention the change in NNN incrementing.

With regards,
Daniel
-- 
|: https://berrange.com       ~~        https://hachyderm.io/@berrange :|
|: https://libvirt.org          ~~          https://entangle-photo.org :|
|: https://pixelfed.art/berrange   ~~    https://fstop138.berrange.com :|



^ permalink raw reply	[flat|nested] 49+ messages in thread

* Re: [PATCH v6 06/34] monitor: add 'chardev' property to Monitor base class
  2026-07-03 13:17   ` Markus Armbruster
@ 2026-07-06 13:19     ` Daniel P. Berrangé
  0 siblings, 0 replies; 49+ messages in thread
From: Daniel P. Berrangé @ 2026-07-06 13:19 UTC (permalink / raw)
  To: Markus Armbruster
  Cc: qemu-devel, Paolo Bonzini, Dr. David Alan Gilbert,
	Marc-André Lureau, Alex Bennée,
	Philippe Mathieu-Daudé, Christian Brauner, devel,
	Peter Krempa

On Fri, Jul 03, 2026 at 03:17:11PM +0200, Markus Armbruster wrote:
> Daniel P. Berrangé <berrange@redhat.com> writes:
> 
> > This is associates both QMP and HMP monitors with a character
> > device backend.
> >
> > Reviewed-by: Marc-André Lureau <marcandre.lureau@redhat.com>
> > Tested-by: Peter Krempa <pkrempa@redhat.com>
> > Signed-off-by: Daniel P. Berrangé <berrange@redhat.com>
> > ---
> >  chardev/char.c             |  3 ++-
> >  gdbstub/system.c           |  4 ++--
> >  include/monitor/monitor.h  |  4 ++--
> >  monitor/hmp.c              | 10 ++++++--
> >  monitor/monitor-internal.h |  2 ++
> >  monitor/monitor.c          | 47 ++++++++++++++++++++++++++++++--------
> >  monitor/qmp.c              | 18 ++++++++++-----
> >  stubs/monitor-internal.c   |  2 +-
> >  8 files changed, 67 insertions(+), 23 deletions(-)
> >
> > diff --git a/chardev/char.c b/chardev/char.c
> > index c71ffa7963..22e5bae388 100644
> > --- a/chardev/char.c
> > +++ b/chardev/char.c
> > @@ -804,8 +804,9 @@ static Chardev *qemu_chr_new_from_name(const char *label, const char *filename,
> >      }
> >  
> >      if (qemu_opt_get_bool(opts, "mux", 0)) {
> > +        const char *chardev_id = qemu_opts_id(opts);
> >          assert(permit_mux_mon);
> > -        monitor_new_hmp(NULL, chr, true, &err);
> > +        monitor_new_hmp(NULL, chardev_id, true, &err);
> >          if (err) {
> >              error_report_err(err);
> >              object_unparent(OBJECT(chr));
> > diff --git a/gdbstub/system.c b/gdbstub/system.c
> > index fda8ef9352..d681476f5e 100644
> > --- a/gdbstub/system.c
> > +++ b/gdbstub/system.c
> > @@ -387,9 +387,9 @@ bool gdbserver_start(const char *device, Error **errp)
> >          qemu_add_vm_change_state_handler(gdb_vm_state_change, NULL);
> >  
> >          /* Initialize a monitor terminal for gdb */
> > -        mon_chr = qemu_chardev_new(NULL, TYPE_CHARDEV_GDB,
> > +        mon_chr = qemu_chardev_new("gdbchrdev0", TYPE_CHARDEV_GDB,
> >                                     NULL, NULL, &error_abort);
> 
> This chardev now gets ID "gdbchrdev0" instead of id_generate(ID_CHR).
> 
> If you truly need this, you have to mention it in the commit message.

Not required.

> 
> > -        monitor_new_hmp(NULL, mon_chr, false, &error_abort);
> > +        monitor_new_hmp(NULL, "gdbchrdev0", false, &error_abort);
> 
> If you don't, then passing mon_chr->label here should do the trick.

Yes.



> > @@ -1538,22 +1539,27 @@ static void monitor_readline_flush(void *opaque)
> >      monitor_flush(&mon->parent_obj);
> >  }
> >  
> > -void monitor_new_hmp(const char *id, Chardev *chr,
> > +void monitor_new_hmp(const char *id, const char *chardev_id,
> >                       bool use_readline, Error **errp)
> 
> You now pass a chardev ID instead of a Chardev *.
> 
> I *guess* that's possible because chardev_new() asserts it's passed an
> ID.
> 
> I should now check all callers of monitor_new_hmp() to make sure they
> pass the correct ID.  On a Friday afternoon.  I hoist the white flag.
> 
> Such refactorings should be mentioned in the commit message.  Even
> better would be a separate patch.

I'll switch to a separate commit for this.


With regards,
Daniel
-- 
|: https://berrange.com       ~~        https://hachyderm.io/@berrange :|
|: https://libvirt.org          ~~          https://entangle-photo.org :|
|: https://pixelfed.art/berrange   ~~    https://fstop138.berrange.com :|



^ permalink raw reply	[flat|nested] 49+ messages in thread

* Re: [PATCH v6 31/34] monitor: add support for auto-deleting monitors upon close
  2026-07-03 13:42   ` Markus Armbruster
@ 2026-07-06 13:32     ` Daniel P. Berrangé
  0 siblings, 0 replies; 49+ messages in thread
From: Daniel P. Berrangé @ 2026-07-06 13:32 UTC (permalink / raw)
  To: Markus Armbruster
  Cc: qemu-devel, Paolo Bonzini, Dr. David Alan Gilbert,
	Marc-André Lureau, Alex Bennée,
	Philippe Mathieu-Daudé, Christian Brauner, devel,
	Peter Krempa

On Fri, Jul 03, 2026 at 03:42:46PM +0200, Markus Armbruster wrote:
> Daniel P. Berrangé <berrange@redhat.com> writes:
> 
> > The default monitor is usually a long lived object that will exist for
> > the entire lifetime of the VM. A monitor can only service a single
> > client at a time though, and so it might be desirable to hotplug
> > additional monitors at runtime for specific tasks. If doing that,
> > however, there is a need to remove the monitor when it is no longer
> > needed.
> 
> I challenged this in review of v5, and you replied with a convincing use
> case: management application hands a monitor to its client.  Would be
> nice to have in the commit message.  Up to you.

Yes, will add it.


With regards,
Daniel
-- 
|: https://berrange.com       ~~        https://hachyderm.io/@berrange :|
|: https://libvirt.org          ~~          https://entangle-photo.org :|
|: https://pixelfed.art/berrange   ~~    https://fstop138.berrange.com :|



^ permalink raw reply	[flat|nested] 49+ messages in thread

* Re: [PATCH v6 33/34] qemu-options: document new monitor-hmp and monitor-qmp objects
  2026-07-06 10:56   ` Markus Armbruster
@ 2026-07-06 13:38     ` Daniel P. Berrangé
  0 siblings, 0 replies; 49+ messages in thread
From: Daniel P. Berrangé @ 2026-07-06 13:38 UTC (permalink / raw)
  To: Markus Armbruster
  Cc: qemu-devel, Paolo Bonzini, Dr. David Alan Gilbert,
	Marc-André Lureau, Alex Bennée,
	Philippe Mathieu-Daudé, Christian Brauner, devel

On Mon, Jul 06, 2026 at 12:56:35PM +0200, Markus Armbruster wrote:
> Daniel P. Berrangé <berrange@redhat.com> writes:
> 
> > Add new docs for the `-object monitor-hmp` and `-object monitor-qmp`
> > options, updating `-mon` to state that it is legacy syntax sugar
> > for the new `-object` args.
> >
> > Signed-off-by: Daniel P. Berrangé <berrange@redhat.com>
> > ---
> >  qemu-options.hx | 61 ++++++++++++++++++++++++++++++++++++++++++++++---
> >  1 file changed, 58 insertions(+), 3 deletions(-)
> >
> > diff --git a/qemu-options.hx b/qemu-options.hx
> > index e44b47de68..1aa548c39b 100644
> > --- a/qemu-options.hx
> > +++ b/qemu-options.hx
> > @@ -4955,6 +4955,11 @@ SRST
> >      port). The default device is ``vc`` in graphical mode and ``stdio``
> >      in non graphical mode. Use ``-monitor none`` to disable the default
> >      monitor.
> > +
> > +    The use of ``-monitor dev`` is syntax sugar for creating a character
> 
> For what it's worth, Wikipedia redirects "syntax sugar" to "syntactic
> sugar", and uses only the latter in the article.

Ok will use that

> 
> Could also use "shorthand".  Matter of taste.
> 
> > +    device from ``dev`` and pairing it with ``-object monitor-hmp``.
> > +    Both the character device and monitor object will be given an ID
> > +    ``compat_monitorNNN`` where ``NNN`` is a counter starting from 0.
> >  ERST
> >  DEF("qmp", HAS_ARG, QEMU_OPTION_qmp, \
> >      "-qmp dev        like -monitor but opens in 'control' mode\n",
> > @@ -4966,9 +4971,10 @@ SRST
> >  
> >          -qmp tcp:localhost:4444,server=on,wait=off
> >  
> > -    Not all options are configurable via this syntax; for maximum
> > -    flexibility use the ``-mon`` option and an accompanying ``-chardev``.
> > -
> > +    The use of ``-monitor dev`` is syntax sugar for creating a character
> 
> Pasto, you mean ``-qmp dev``.

/face-palm

> 
> > +    device from ``dev`` and pairing it with ``-object monitor-qmp``.
> > +    Both the character device and monitor object will be given an ID
> > +    ``compat_monitorNNN`` where ``NNN`` is a counter starting from 0.
> >  ERST
> >  DEF("qmp-pretty", HAS_ARG, QEMU_OPTION_qmp_pretty, \
> >      "-qmp-pretty dev like -qmp but uses pretty JSON formatting\n",
> > @@ -4997,6 +5003,16 @@ SRST
> >        -mon chardev=mon1,mode=control,pretty=on
> >  
> >      enables the QMP monitor on localhost port 4444 with pretty-printing.
> > +
> > +    The use of ``-mon mode=readline`` is historical syntax sugar
> 
> What does "historical" mean?  The next patch replaces it by
> "deprecated".  Suggest to scratch it here.

ok.


> > @@ -5747,6 +5763,45 @@ SRST
> >      they are specified. Note that the 'id' property must be set. These
> >      objects are placed in the '/objects' path.
> >  
> > +    ``-object monitor-hmp,id=id,chardev=chardev_id,readline=on|off``
> > +        Set up a monitor running the Human Monitor Protocol,
> > +        connected to the chardev ``chardev_id``.
> > +
> > +        The ``id`` parameter is a unique ID that can be used
> > +        to dynamically delete the monitor at runtime. Note
> > +        that monitors created using the historical syntax
> > +        will be allocated IDs following the pattern ``hmpcompatNN``.
> 
> ``compat_monitorNNN``
> 
> > +        Mixing ``-object`` with the historical monitor syntax is
> > +        discouraged.
> 
> Clearer, I think: "with -monitor and -mon".
> 
> The next patch deprecates -mon, and then this could become just "with
> -monitor".

ok

> 
> > +
> > +        The ``readline`` parameter, which defaults to ``on``,
> > +        controls whether the monitor provides line editing.
> > +
> > +    ``-object monitor-qmp,id=id,chardev=chardev_id,pretty=on|off,close-action=none|delete``
> > +        Set up a monitor running the QEMU Monitor Protocol,
> > +        connected to the chardev ``chardev_id``.
> > +
> > +        The ``id`` parameter is a unique ID that can be used
> > +        to dynamically delete the monitor at runtime. Note
> > +        that monitors created using the historical syntax
> > +        will be allocated IDs following the pattern ``qmpcompatNN``.
> 
> ``compat_monitorNNN``
> 
> > +        Mixing ``-object`` with the historical monitor syntax is
> > +        discouraged.
> 
> "with -qmp, -qmp-pretty, and -mon".
> 
> The next patch deprecates -mon, and then this could become just "with
> -qmp and -qmp-pretty".

ok

> 
> > +
> > +        The ``pretty`` parameter, which defaults to ``off``,
> > +        controls whether the monitor responses are pretty
> > +        printed as multi-line indented JSON, as opposed to
> > +        constrained to a single line without extraneous
> > +        whitespace.
> > +
> > +        The ``close-action`` parameter, which defaults to ``none``,
> > +        controls what happens when the connection to the monitor
> > +        is terminated by the user. If set to ``delete``, then the
> > +        ``monitor-qmp`` object and its associated character
> > +        device are both immediately deleted. This can be useful
> > +        if an extra monitor was hotplugged for a specific task
> 
> Comma after task?

I don't think its needed, but people are horribly inconsistent in
use of , before and in English no matter what.

> 
> > +        and should be unplugged when completed.
> > +
> >      ``-object memory-backend-file,id=id,size=size,mem-path=dir,share=on|off,discard-data=on|off,merge=on|off,dump=on|off,prealloc=on|off,host-nodes=host-nodes,policy=default|preferred|bind|interleave,align=align,offset=offset,readonly=on|off,rom=on|off|auto``
> >          Creates a memory file backend object, which can be used to back
> >          the guest RAM with huge pages.
> 

With regards,
Daniel
-- 
|: https://berrange.com       ~~        https://hachyderm.io/@berrange :|
|: https://libvirt.org          ~~          https://entangle-photo.org :|
|: https://pixelfed.art/berrange   ~~    https://fstop138.berrange.com :|



^ permalink raw reply	[flat|nested] 49+ messages in thread

* Re: [PATCH v6 01/34] qom: replace 'can_be_deleted' with 'prepare_delete'
  2026-07-06 13:00     ` Daniel P. Berrangé
@ 2026-07-06 13:49       ` Markus Armbruster
  0 siblings, 0 replies; 49+ messages in thread
From: Markus Armbruster @ 2026-07-06 13:49 UTC (permalink / raw)
  To: Daniel P. Berrangé
  Cc: Markus Armbruster, qemu-devel, Paolo Bonzini,
	Dr. David Alan Gilbert, Marc-André Lureau, Alex Bennée,
	Philippe Mathieu-Daudé, Christian Brauner, devel

Daniel P. Berrangé <berrange@redhat.com> writes:

> On Fri, Jul 03, 2026 at 08:40:28AM +0200, Markus Armbruster wrote:
>> Daniel P. Berrangé <berrange@redhat.com> writes:
>> 
>> > While most objects can perform all their cleanup in the finalizer
>> > method, there can be interactions with other resources / subsystems
>> > / threads which require that some cleanup be performed on an user
>> > creatable object before unparenting it and entering finalization.
>> >
>> > The current 'can_be_deleted' method runs in the deletion path and
>> > is intended to be used to block deletion. While it could be used
>> > to perform cleanup tasks, its name suggests it should be free of
>> > side-effects.
>> >
>> > Generalize this by renaming it to 'prepare_delete', explicitly
>> > allowing for cleanup to be provided. Existing users of 'can_be_deleted'
>> > are re-written, which provides them with more detailed/tailored error
>> > messages.
>> 
>> Old message:
>> 
>>         error_setg(errp, "object '%s' is in use, can not be deleted", id);
>> 
>> New messages:
>> 
>>         error_setg(errp, "Cryptodev backend '%s' is still in use",
>>         error_setg(errp, "Host memory backend '%s' is still mapped",
>>         error_setg(errp, "IOMMUFD backend '%s' still has %d users",
>>         error_setg(errp, "Throttle group '%s' still has multiple references",
>>     error_setg(errp, "Deleting CAN bus device '%s' is not supported",
>>     error_setg(errp, "Deleting main loop '%s' is not supported",
>> 
>> All now mention what kind of object it is.  Users could probably figure
>> this out from context easily.  Welcome small improvement.
>> 
>> The two unconditional ones become more accurate: they now report "not
>> supported" instead of "in use".
>> 
>> The other four lose "can not be deleted".  Users can probably figure
>> this out from context.  Still, I lean towards error messages like "Can't
>> DO-SOMETHING because REASON" until that becomes overly verbose.  What do
>> you think?
>
> I'll change them as follows:
>
>   -        error_setg(errp, "Cryptodev backend '%s' is still in use",
>   +        error_setg(errp,
>   +                   "Cannot delete cryptodev backend '%s' that is in use",
>
>
>   -        error_setg(errp, "Host memory backend '%s' is still mapped",
>   +        error_setg(errp,
>   +                   "Cannot delete host memory backend '%s' which is mapped",
>
>
>   -        error_setg(errp, "IOMMUFD backend '%s' still has %d users",
>   +        error_setg(errp, "Can not delete IOMMUFD backend '%s' with %d users",
>
>
>   -        error_setg(errp, "Throttle group '%s' still has multiple references",
>   +        error_setg(errp,
>   +                   "Cannot delete throttle group '%s' with active references",

Works for me, thanks!



^ permalink raw reply	[flat|nested] 49+ messages in thread

end of thread, other threads:[~2026-07-06 13:52 UTC | newest]

Thread overview: 49+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-07-02 16:03 [PATCH v6 00/34] monitor: turn QMP and HMP into QOM objects Daniel P. Berrangé
2026-07-02 16:03 ` [PATCH v6 01/34] qom: replace 'can_be_deleted' with 'prepare_delete' Daniel P. Berrangé
2026-07-03  6:40   ` Markus Armbruster
2026-07-06 13:00     ` Daniel P. Berrangé
2026-07-06 13:49       ` Markus Armbruster
2026-07-02 16:03 ` [PATCH v6 02/34] monitor: replace 'common' with 'parent_obj' in MonitorHMP Daniel P. Berrangé
2026-07-02 16:03 ` [PATCH v6 03/34] monitor: replace 'common' with 'parent_obj' in MonitorQMP Daniel P. Berrangé
2026-07-02 16:03 ` [PATCH v6 04/34] monitor: rename monitor_init* to monitor_new* Daniel P. Berrangé
2026-07-02 16:03 ` [PATCH v6 05/34] monitor: minimal conversion of monitors to QOM Daniel P. Berrangé
2026-07-03 11:38   ` Markus Armbruster
2026-07-03 11:42     ` Daniel P. Berrangé
2026-07-03 12:48   ` Markus Armbruster
2026-07-06 13:09     ` Daniel P. Berrangé
2026-07-02 16:03 ` [PATCH v6 06/34] monitor: add 'chardev' property to Monitor base class Daniel P. Berrangé
2026-07-03 13:17   ` Markus Armbruster
2026-07-06 13:19     ` Daniel P. Berrangé
2026-07-02 16:03 ` [PATCH v6 07/34] monitor: add 'readline' property to HMP Monitor class Daniel P. Berrangé
2026-07-02 16:03 ` [PATCH v6 08/34] monitor: add 'pretty' property to QMP " Daniel P. Berrangé
2026-07-02 16:03 ` [PATCH v6 09/34] monitor: remove 'skip_flush' field Daniel P. Berrangé
2026-07-02 16:03 ` [PATCH v6 10/34] monitor: move monitor_data_(init|destroy) into QOM init/finalize Daniel P. Berrangé
2026-07-02 16:03 ` [PATCH v6 11/34] monitor: use class methods for monitor_vprintf Daniel P. Berrangé
2026-07-02 16:03 ` [PATCH v6 12/34] monitor: use class methods for monitor_qapi_event_emit Daniel P. Berrangé
2026-07-02 16:03 ` [PATCH v6 13/34] monitor: use class methods for monitor_accept_input Daniel P. Berrangé
2026-07-02 16:03 ` [PATCH v6 14/34] monitor: use class method for I/O thread request Daniel P. Berrangé
2026-07-02 16:03 ` [PATCH v6 15/34] monitor: use dynamic cast in monitor_qmp_requests_pop_any_with_lock Daniel P. Berrangé
2026-07-02 16:03 ` [PATCH v6 16/34] util: use dynamic cast in error vreport Daniel P. Berrangé
2026-07-02 16:03 ` [PATCH v6 17/34] monitor: drop unused monitor_cur_is_qmp Daniel P. Berrangé
2026-07-02 16:03 ` [PATCH v6 18/34] monitor: use dynamic cast in QMP commands Daniel P. Berrangé
2026-07-02 16:03 ` [PATCH v6 19/34] monitor: use dynamic cast in monitor_is_hmp_non_interactive Daniel P. Berrangé
2026-07-02 16:03 ` [PATCH v6 20/34] monitor: drop unused monitor_is_qmp method Daniel P. Berrangé
2026-07-02 16:03 ` [PATCH v6 21/34] monitor: eliminate monitor_is_hmp_non_interactive method Daniel P. Berrangé
2026-07-02 16:03 ` [PATCH v6 22/34] monitor: implement "user creatable" interface for adding monitors Daniel P. Berrangé
2026-07-02 16:03 ` [PATCH v6 23/34] monitor: convert from oneshot BH to persistent BH Daniel P. Berrangé
2026-07-02 16:03 ` [PATCH v6 24/34] monitor: reject attempts to delete the current monitor Daniel P. Berrangé
2026-07-02 16:03 ` [PATCH v6 25/34] monitor: protect qemu_chr_fe_accept_input with monitor lock Daniel P. Berrangé
2026-07-02 16:03 ` [PATCH v6 26/34] monitor: implement support for deleting QMP objects Daniel P. Berrangé
2026-07-02 16:03 ` [PATCH v6 27/34] tests/qtest: add tests for dynamic monitor add/remove Daniel P. Berrangé
2026-07-02 16:03 ` [PATCH v6 28/34] tests/functional: add e2e test for dynamic QMP monitor hotplug Daniel P. Berrangé
2026-07-02 16:03 ` [PATCH v6 29/34] tests/functional: add a stress test for monitor hot unplug Daniel P. Berrangé
2026-07-02 16:03 ` [PATCH v6 30/34] qom: add trace events for user creatable create/delete APIs Daniel P. Berrangé
2026-07-02 16:03 ` [PATCH v6 31/34] monitor: add support for auto-deleting monitors upon close Daniel P. Berrangé
2026-07-03 13:42   ` Markus Armbruster
2026-07-06 13:32     ` Daniel P. Berrangé
2026-07-02 16:03 ` [PATCH v6 32/34] tests: switch from -mon to -object monitor-qmp Daniel P. Berrangé
2026-07-02 16:03 ` [PATCH v6 33/34] qemu-options: document new monitor-hmp and monitor-qmp objects Daniel P. Berrangé
2026-07-06 10:56   ` Markus Armbruster
2026-07-06 13:38     ` Daniel P. Berrangé
2026-07-02 16:03 ` [PATCH v6 34/34] docs: mark '-mon' as deprecated in favour of -object Daniel P. Berrangé
2026-07-06 11:00   ` Markus Armbruster

This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.