qemu-devel.nongnu.org archive mirror
 help / color / mirror / Atom feed
* [PATCH v3 1/7] chardev/char: rename `char-mux.c` to `char-mux-fe.c`
       [not found] <20241010101838.331032-1-r.peniaev@gmail.com>
@ 2024-10-10 10:18 ` Roman Penyaev
  2024-10-14 13:57   ` Marc-André Lureau
  2024-10-10 10:18 ` [PATCH v3 2/7] chardev/char: move away mux suspend/resume calls Roman Penyaev
                   ` (5 subsequent siblings)
  6 siblings, 1 reply; 9+ messages in thread
From: Roman Penyaev @ 2024-10-10 10:18 UTC (permalink / raw)
  Cc: Roman Penyaev, Marc-André Lureau, qemu-devel

In the following patches backend multiplexer will be
introduced and the implementation will be named as
follows: `char-mux-be.c`. This patch renames the
frontend multiplexer from `char-mux.c` to
`char-mux-fe.c`.

Signed-off-by: Roman Penyaev <r.peniaev@gmail.com>
Cc: "Marc-André Lureau" <marcandre.lureau@redhat.com>
Cc: qemu-devel@nongnu.org
---
 chardev/{char-mux.c => char-mux-fe.c} | 0
 chardev/meson.build                   | 2 +-
 2 files changed, 1 insertion(+), 1 deletion(-)
 rename chardev/{char-mux.c => char-mux-fe.c} (100%)

diff --git a/chardev/char-mux.c b/chardev/char-mux-fe.c
similarity index 100%
rename from chardev/char-mux.c
rename to chardev/char-mux-fe.c
diff --git a/chardev/meson.build b/chardev/meson.build
index 70070a8279a9..778444a00ca6 100644
--- a/chardev/meson.build
+++ b/chardev/meson.build
@@ -2,7 +2,7 @@ chardev_ss.add(files(
   'char-fe.c',
   'char-file.c',
   'char-io.c',
-  'char-mux.c',
+  'char-mux-fe.c',
   'char-null.c',
   'char-pipe.c',
   'char-ringbuf.c',
-- 
2.34.1



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

* [PATCH v3 2/7] chardev/char: move away mux suspend/resume calls
       [not found] <20241010101838.331032-1-r.peniaev@gmail.com>
  2024-10-10 10:18 ` [PATCH v3 1/7] chardev/char: rename `char-mux.c` to `char-mux-fe.c` Roman Penyaev
@ 2024-10-10 10:18 ` Roman Penyaev
  2024-10-10 10:18 ` [PATCH v3 3/7] chardev/char: rename frontend mux calls Roman Penyaev
                   ` (4 subsequent siblings)
  6 siblings, 0 replies; 9+ messages in thread
From: Roman Penyaev @ 2024-10-10 10:18 UTC (permalink / raw)
  Cc: Roman Penyaev, Marc-André Lureau, qemu-devel

The suspend/resume open multiplexer calls are generic
and will be used for frontend (current mux) and backend
(will follow) implementations. Move them away from the
`char-mux-fe.c` to more generic `char.c` file. Also
for the sake of clarity these renames were made:

  s/suspend_mux_open/mux_suspend_open/g
  s/resume_mux_open/mux_resume_open/g

No functional changes are made.

Signed-off-by: Roman Penyaev <r.peniaev@gmail.com>
Cc: "Marc-André Lureau" <marcandre.lureau@redhat.com>
Cc: qemu-devel@nongnu.org
---
 chardev/char-mux-fe.c      | 63 ++-------------------------------
 chardev/char.c             | 72 ++++++++++++++++++++++++++++++++++++++
 chardev/chardev-internal.h |  3 ++
 include/chardev/char.h     |  5 +--
 system/vl.c                |  4 +--
 5 files changed, 82 insertions(+), 65 deletions(-)

diff --git a/chardev/char-mux-fe.c b/chardev/char-mux-fe.c
index 673971ca1798..cd9ff0c5dc12 100644
--- a/chardev/char-mux-fe.c
+++ b/chardev/char-mux-fe.c
@@ -33,13 +33,6 @@
 
 /* MUX driver for serial I/O splitting */
 
-/*
- * Set to false by suspend_mux_open.  Open events are delayed until
- * resume_mux_open.  Usually suspend_mux_open is called before
- * command line processing and resume_mux_open afterwards.
- */
-static bool muxes_opened = true;
-
 /* Called with chr_write_lock held.  */
 static int mux_chr_write(Chardev *chr, const uint8_t *buf, int len)
 {
@@ -239,15 +232,10 @@ static void mux_chr_read(void *opaque, const uint8_t *buf, int size)
         }
 }
 
-void mux_chr_send_all_event(Chardev *chr, QEMUChrEvent event)
+void mux_fe_chr_send_all_event(MuxFeChardev *d, QEMUChrEvent event)
 {
-    MuxFeChardev *d = MUX_FE_CHARDEV(chr);
     int i;
 
-    if (!muxes_opened) {
-        return;
-    }
-
     /* Send the event to all registered listeners */
     for (i = 0; i < d->mux_cnt; i++) {
         mux_chr_send_event(d, i, event);
@@ -335,7 +323,7 @@ static void qemu_chr_open_mux(Chardev *chr,
     /* only default to opened state if we've realized the initial
      * set of muxes
      */
-    *be_opened = muxes_opened;
+    *be_opened = mux_is_opened();
     qemu_chr_fe_init(&d->chr, drv, errp);
 }
 
@@ -355,53 +343,6 @@ static void qemu_chr_parse_mux(QemuOpts *opts, ChardevBackend *backend,
     mux->chardev = g_strdup(chardev);
 }
 
-/**
- * Called after processing of default and command-line-specified
- * chardevs to deliver CHR_EVENT_OPENED events to any FEs attached
- * to a mux chardev. This is done here to ensure that
- * output/prompts/banners are only displayed for the FE that has
- * focus when initial command-line processing/machine init is
- * completed.
- *
- * After this point, any new FE attached to any new or existing
- * mux will receive CHR_EVENT_OPENED notifications for the BE
- * immediately.
- */
-static void open_muxes(Chardev *chr)
-{
-    /* send OPENED to all already-attached FEs */
-    mux_chr_send_all_event(chr, CHR_EVENT_OPENED);
-
-    /*
-     * mark mux as OPENED so any new FEs will immediately receive
-     * OPENED event
-     */
-    chr->be_open = 1;
-}
-
-void suspend_mux_open(void)
-{
-    muxes_opened = false;
-}
-
-static int chardev_options_parsed_cb(Object *child, void *opaque)
-{
-    Chardev *chr = (Chardev *)child;
-
-    if (!chr->be_open && CHARDEV_IS_MUX_FE(chr)) {
-        open_muxes(chr);
-    }
-
-    return 0;
-}
-
-void resume_mux_open(void)
-{
-    muxes_opened = true;
-    object_child_foreach(get_chardevs_root(),
-                         chardev_options_parsed_cb, NULL);
-}
-
 static void char_mux_class_init(ObjectClass *oc, void *data)
 {
     ChardevClass *cc = CHARDEV_CLASS(oc);
diff --git a/chardev/char.c b/chardev/char.c
index 9d180f996dfb..d38a0c76a51e 100644
--- a/chardev/char.c
+++ b/chardev/char.c
@@ -43,6 +43,13 @@
 
 #include "chardev-internal.h"
 
+/*
+ * Set to false by mux_suspend_open().  Open events are delayed until
+ * mux_resume_open().  Usually mux_suspend_open() is called before
+ * command line processing and mux_resume_open() afterwards.
+ */
+static bool muxes_opened = true;
+
 /***********************************************************/
 /* character device */
 
@@ -1259,6 +1266,71 @@ void qemu_chr_cleanup(void)
     object_unparent(get_chardevs_root());
 }
 
+/**
+ * Called after processing of default and command-line-specified
+ * chardevs to deliver CHR_EVENT_OPENED events to any FEs attached
+ * to a mux chardev. This is done here to ensure that
+ * output/prompts/banners are only displayed for the FE that has
+ * focus when initial command-line processing/machine init is
+ * completed.
+ *
+ * After this point, any new FE attached to any new or existing
+ * mux will receive CHR_EVENT_OPENED notifications for the BE
+ * immediately.
+ */
+static void open_muxes(Chardev *chr)
+{
+    /* send OPENED to all already-attached FEs */
+    mux_chr_send_all_event(chr, CHR_EVENT_OPENED);
+
+    /*
+     * mark mux as OPENED so any new FEs will immediately receive
+     * OPENED event
+     */
+    chr->be_open = 1;
+}
+
+void mux_suspend_open(void)
+{
+    muxes_opened = false;
+}
+
+static int chardev_options_parsed_cb(Object *child, void *opaque)
+{
+    Chardev *chr = (Chardev *)child;
+
+    if (!chr->be_open && CHARDEV_IS_MUX_FE(chr)) {
+        open_muxes(chr);
+    }
+
+    return 0;
+}
+
+void mux_resume_open(void)
+{
+    muxes_opened = true;
+    object_child_foreach(get_chardevs_root(),
+                         chardev_options_parsed_cb, NULL);
+}
+
+bool mux_is_opened(void)
+{
+    return muxes_opened;
+}
+
+void mux_chr_send_all_event(Chardev *chr, QEMUChrEvent event)
+{
+    if (!mux_is_opened()) {
+        return;
+    }
+
+    if (CHARDEV_IS_MUX_FE(chr)) {
+        MuxFeChardev *d = MUX_FE_CHARDEV(chr);
+
+        mux_fe_chr_send_all_event(d, event);
+    }
+}
+
 static void register_types(void)
 {
     type_register_static(&char_type_info);
diff --git a/chardev/chardev-internal.h b/chardev/chardev-internal.h
index 1b943c81bcd8..a77f0bdaccfa 100644
--- a/chardev/chardev-internal.h
+++ b/chardev/chardev-internal.h
@@ -63,6 +63,9 @@ DECLARE_INSTANCE_CHECKER(MuxFeChardev, MUX_FE_CHARDEV,
 void mux_set_focus(Chardev *chr, int focus);
 void mux_chr_send_all_event(Chardev *chr, QEMUChrEvent event);
 
+/* Mux type dependent calls */
+void mux_fe_chr_send_all_event(MuxFeChardev *d, QEMUChrEvent event);
+
 Object *get_chardevs_root(void);
 
 #endif /* CHARDEV_INTERNAL_H */
diff --git a/include/chardev/char.h b/include/chardev/char.h
index d9d23b6232db..0bec974f9d73 100644
--- a/include/chardev/char.h
+++ b/include/chardev/char.h
@@ -317,7 +317,8 @@ extern int term_escape_char;
 GSource *qemu_chr_timeout_add_ms(Chardev *chr, guint ms,
                                  GSourceFunc func, void *private);
 
-void suspend_mux_open(void);
-void resume_mux_open(void);
+bool mux_is_opened(void);
+void mux_suspend_open(void);
+void mux_resume_open(void);
 
 #endif
diff --git a/system/vl.c b/system/vl.c
index fe547ca47c27..6222e0625b1e 100644
--- a/system/vl.c
+++ b/system/vl.c
@@ -3690,7 +3690,7 @@ void qemu_init(int argc, char **argv)
 
     qemu_create_machine(machine_opts_dict);
 
-    suspend_mux_open();
+    mux_suspend_open();
 
     qemu_disable_default_devices();
     qemu_setup_display();
@@ -3768,5 +3768,5 @@ void qemu_init(int argc, char **argv)
     qemu_init_displays();
     accel_setup_post(current_machine);
     os_setup_post();
-    resume_mux_open();
+    mux_resume_open();
 }
-- 
2.34.1



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

* [PATCH v3 3/7] chardev/char: rename frontend mux calls
       [not found] <20241010101838.331032-1-r.peniaev@gmail.com>
  2024-10-10 10:18 ` [PATCH v3 1/7] chardev/char: rename `char-mux.c` to `char-mux-fe.c` Roman Penyaev
  2024-10-10 10:18 ` [PATCH v3 2/7] chardev/char: move away mux suspend/resume calls Roman Penyaev
@ 2024-10-10 10:18 ` Roman Penyaev
  2024-10-10 10:18 ` [PATCH v3 4/7] chardev/char: introduce `mux-be-id=ID` option Roman Penyaev
                   ` (3 subsequent siblings)
  6 siblings, 0 replies; 9+ messages in thread
From: Roman Penyaev @ 2024-10-10 10:18 UTC (permalink / raw)
  Cc: Roman Penyaev, Marc-André Lureau, qemu-devel

This patch renames calls in the frontend mux implementation
to reflect its frontend nature. Patch does the following:

  s/mux_chr/mux_fe_chr/g

No functional changes are made.

Signed-off-by: Roman Penyaev <r.peniaev@gmail.com>
Cc: "Marc-André Lureau" <marcandre.lureau@redhat.com>
Cc: qemu-devel@nongnu.org
---
 chardev/char-mux-fe.c | 57 ++++++++++++++++++++++---------------------
 1 file changed, 29 insertions(+), 28 deletions(-)

diff --git a/chardev/char-mux-fe.c b/chardev/char-mux-fe.c
index cd9ff0c5dc12..5a8860b4310e 100644
--- a/chardev/char-mux-fe.c
+++ b/chardev/char-mux-fe.c
@@ -34,7 +34,7 @@
 /* MUX driver for serial I/O splitting */
 
 /* Called with chr_write_lock held.  */
-static int mux_chr_write(Chardev *chr, const uint8_t *buf, int len)
+static int mux_fe_chr_write(Chardev *chr, const uint8_t *buf, int len)
 {
     MuxFeChardev *d = MUX_FE_CHARDEV(chr);
     int ret;
@@ -117,7 +117,8 @@ static void mux_print_help(Chardev *chr)
     }
 }
 
-static void mux_chr_send_event(MuxFeChardev *d, int mux_nr, QEMUChrEvent event)
+static void mux_fe_chr_send_event(MuxFeChardev *d, int mux_nr,
+                                  QEMUChrEvent event)
 {
     CharBackend *be = d->backends[mux_nr];
 
@@ -126,12 +127,12 @@ static void mux_chr_send_event(MuxFeChardev *d, int mux_nr, QEMUChrEvent event)
     }
 }
 
-static void mux_chr_be_event(Chardev *chr, QEMUChrEvent event)
+static void mux_fe_chr_be_event(Chardev *chr, QEMUChrEvent event)
 {
     MuxFeChardev *d = MUX_FE_CHARDEV(chr);
 
     if (d->focus != -1) {
-        mux_chr_send_event(d, d->focus, event);
+        mux_fe_chr_send_event(d, d->focus, event);
     }
 }
 
@@ -180,7 +181,7 @@ static int mux_proc_byte(Chardev *chr, MuxFeChardev *d, int ch)
     return 0;
 }
 
-static void mux_chr_accept_input(Chardev *chr)
+static void mux_fe_chr_accept_input(Chardev *chr)
 {
     MuxFeChardev *d = MUX_FE_CHARDEV(chr);
     int m = d->focus;
@@ -193,7 +194,7 @@ static void mux_chr_accept_input(Chardev *chr)
     }
 }
 
-static int mux_chr_can_read(void *opaque)
+static int mux_fe_chr_can_read(void *opaque)
 {
     MuxFeChardev *d = MUX_FE_CHARDEV(opaque);
     int m = d->focus;
@@ -210,7 +211,7 @@ static int mux_chr_can_read(void *opaque)
     return 0;
 }
 
-static void mux_chr_read(void *opaque, const uint8_t *buf, int size)
+static void mux_fe_chr_read(void *opaque, const uint8_t *buf, int size)
 {
     Chardev *chr = CHARDEV(opaque);
     MuxFeChardev *d = MUX_FE_CHARDEV(opaque);
@@ -218,7 +219,7 @@ static void mux_chr_read(void *opaque, const uint8_t *buf, int size)
     CharBackend *be = d->backends[m];
     int i;
 
-    mux_chr_accept_input(opaque);
+    mux_fe_chr_accept_input(opaque);
 
     for (i = 0; i < size; i++)
         if (mux_proc_byte(chr, d, buf[i])) {
@@ -238,16 +239,16 @@ void mux_fe_chr_send_all_event(MuxFeChardev *d, QEMUChrEvent event)
 
     /* Send the event to all registered listeners */
     for (i = 0; i < d->mux_cnt; i++) {
-        mux_chr_send_event(d, i, event);
+        mux_fe_chr_send_event(d, i, event);
     }
 }
 
-static void mux_chr_event(void *opaque, QEMUChrEvent event)
+static void mux_fe_chr_event(void *opaque, QEMUChrEvent event)
 {
     mux_chr_send_all_event(CHARDEV(opaque), event);
 }
 
-static GSource *mux_chr_add_watch(Chardev *s, GIOCondition cond)
+static GSource *mux_fe_chr_add_watch(Chardev *s, GIOCondition cond)
 {
     MuxFeChardev *d = MUX_FE_CHARDEV(s);
     Chardev *chr = qemu_chr_fe_get_driver(&d->chr);
@@ -260,7 +261,7 @@ static GSource *mux_chr_add_watch(Chardev *s, GIOCondition cond)
     return cc->chr_add_watch(chr, cond);
 }
 
-static void char_mux_finalize(Object *obj)
+static void char_mux_fe_finalize(Object *obj)
 {
     MuxFeChardev *d = MUX_FE_CHARDEV(obj);
     int i;
@@ -274,15 +275,15 @@ static void char_mux_finalize(Object *obj)
     qemu_chr_fe_deinit(&d->chr, false);
 }
 
-static void mux_chr_update_read_handlers(Chardev *chr)
+static void mux_fe_chr_update_read_handlers(Chardev *chr)
 {
     MuxFeChardev *d = MUX_FE_CHARDEV(chr);
 
     /* Fix up the real driver with mux routines */
     qemu_chr_fe_set_handlers_full(&d->chr,
-                                  mux_chr_can_read,
-                                  mux_chr_read,
-                                  mux_chr_event,
+                                  mux_fe_chr_can_read,
+                                  mux_fe_chr_read,
+                                  mux_fe_chr_event,
                                   NULL,
                                   chr,
                                   chr->gcontext, true, false);
@@ -296,12 +297,12 @@ void mux_set_focus(Chardev *chr, int focus)
     assert(focus < d->mux_cnt);
 
     if (d->focus != -1) {
-        mux_chr_send_event(d, d->focus, CHR_EVENT_MUX_OUT);
+        mux_fe_chr_send_event(d, d->focus, CHR_EVENT_MUX_OUT);
     }
 
     d->focus = focus;
     chr->be = d->backends[focus];
-    mux_chr_send_event(d, d->focus, CHR_EVENT_MUX_IN);
+    mux_fe_chr_send_event(d, d->focus, CHR_EVENT_MUX_IN);
 }
 
 static void qemu_chr_open_mux(Chardev *chr,
@@ -343,30 +344,30 @@ static void qemu_chr_parse_mux(QemuOpts *opts, ChardevBackend *backend,
     mux->chardev = g_strdup(chardev);
 }
 
-static void char_mux_class_init(ObjectClass *oc, void *data)
+static void char_mux_fe_class_init(ObjectClass *oc, void *data)
 {
     ChardevClass *cc = CHARDEV_CLASS(oc);
 
     cc->parse = qemu_chr_parse_mux;
     cc->open = qemu_chr_open_mux;
-    cc->chr_write = mux_chr_write;
-    cc->chr_accept_input = mux_chr_accept_input;
-    cc->chr_add_watch = mux_chr_add_watch;
-    cc->chr_be_event = mux_chr_be_event;
-    cc->chr_update_read_handler = mux_chr_update_read_handlers;
+    cc->chr_write = mux_fe_chr_write;
+    cc->chr_accept_input = mux_fe_chr_accept_input;
+    cc->chr_add_watch = mux_fe_chr_add_watch;
+    cc->chr_be_event = mux_fe_chr_be_event;
+    cc->chr_update_read_handler = mux_fe_chr_update_read_handlers;
 }
 
-static const TypeInfo char_mux_type_info = {
+static const TypeInfo char_mux_fe_type_info = {
     .name = TYPE_CHARDEV_MUX_FE,
     .parent = TYPE_CHARDEV,
-    .class_init = char_mux_class_init,
+    .class_init = char_mux_fe_class_init,
     .instance_size = sizeof(MuxFeChardev),
-    .instance_finalize = char_mux_finalize,
+    .instance_finalize = char_mux_fe_finalize,
 };
 
 static void register_types(void)
 {
-    type_register_static(&char_mux_type_info);
+    type_register_static(&char_mux_fe_type_info);
 }
 
 type_init(register_types);
-- 
2.34.1



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

* [PATCH v3 4/7] chardev/char: introduce `mux-be-id=ID` option
       [not found] <20241010101838.331032-1-r.peniaev@gmail.com>
                   ` (2 preceding siblings ...)
  2024-10-10 10:18 ` [PATCH v3 3/7] chardev/char: rename frontend mux calls Roman Penyaev
@ 2024-10-10 10:18 ` Roman Penyaev
  2024-10-10 10:18 ` [PATCH v3 5/7] chardev/char-mux: implement backend chardev multiplexing Roman Penyaev
                   ` (2 subsequent siblings)
  6 siblings, 0 replies; 9+ messages in thread
From: Roman Penyaev @ 2024-10-10 10:18 UTC (permalink / raw)
  Cc: Roman Penyaev, Marc-André Lureau, qemu-devel

Patch introduces `mux-be-id=ID` option for all chardev devices.
This is necessary to attach chardev to `mux-be` for backend
multiplexing. Actual implementation wimplementation will follow.

Signed-off-by: Roman Penyaev <r.peniaev@gmail.com>
Cc: "Marc-André Lureau" <marcandre.lureau@redhat.com>
Cc: qemu-devel@nongnu.org
---
 chardev/char.c | 3 +++
 qapi/char.json | 6 +++++-
 2 files changed, 8 insertions(+), 1 deletion(-)

diff --git a/chardev/char.c b/chardev/char.c
index d38a0c76a51e..ec9a6c4d2499 100644
--- a/chardev/char.c
+++ b/chardev/char.c
@@ -933,6 +933,9 @@ QemuOptsList qemu_chardev_opts = {
         },{
             .name = "mux",
             .type = QEMU_OPT_BOOL,
+        },{
+            .name = "mux-be-id",
+            .type = QEMU_OPT_STRING,
         },{
             .name = "signal",
             .type = QEMU_OPT_BOOL,
diff --git a/qapi/char.json b/qapi/char.json
index e04535435034..fb0dedb24383 100644
--- a/qapi/char.json
+++ b/qapi/char.json
@@ -199,11 +199,15 @@
 # @logappend: true to append instead of truncate (default to false to
 #     truncate)
 #
+# @mux-be-id: id of the mux-be device for backend multiplexing
+#     (since: 9.2)
+#
 # Since: 2.6
 ##
 { 'struct': 'ChardevCommon',
   'data': { '*logfile': 'str',
-            '*logappend': 'bool' } }
+            '*logappend': 'bool',
+            '*mux-be-id': 'str' } }
 
 ##
 # @ChardevFile:
-- 
2.34.1



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

* [PATCH v3 5/7] chardev/char-mux: implement backend chardev multiplexing
       [not found] <20241010101838.331032-1-r.peniaev@gmail.com>
                   ` (3 preceding siblings ...)
  2024-10-10 10:18 ` [PATCH v3 4/7] chardev/char: introduce `mux-be-id=ID` option Roman Penyaev
@ 2024-10-10 10:18 ` Roman Penyaev
  2024-10-10 10:18 ` [PATCH v3 6/7] tests/unit/test-char: add unit test for the `mux-be` multiplexer Roman Penyaev
  2024-10-10 10:18 ` [PATCH v3 7/7] qemu-options.hx: describe multiplexing of several backend devices Roman Penyaev
  6 siblings, 0 replies; 9+ messages in thread
From: Roman Penyaev @ 2024-10-10 10:18 UTC (permalink / raw)
  Cc: Roman Penyaev, Marc-André Lureau, qemu-devel

This patch implements multiplexing capability of several backend
devices, which opens up an opportunity to use a single frontend
device on the guest, which can be manipulated from several
backend devices.

The idea of the change is trivial: keep list of backend devices
(up to 4), init them on demand and forward data buffer back and
forth.

Patch implements another multiplexer type `mux-be`. The following
is QEMU command line example:

   -chardev mux-be,id=mux0 \
   -chardev socket,path=/tmp/sock,server=on,wait=off,id=sock0,mux-be-id=mux0 \
   -chardev vc,id=vc0,mux-be-id=mux0 \
   -device virtconsole,chardev=mux0 \
   -vnc 0.0.0.0:0

which creates 2 backend devices: text virtual console (`vc0`) and a
socket (`sock0`) connected to the single virtio hvc console with the
backend multiplexer (`mux0`) help. `vc0` renders text to an image,
which can be shared over the VNC protocol.  `sock0` is a socket
backend which provides biderectional communication to the virtio hvc
console.

Signed-off-by: Roman Penyaev <r.peniaev@gmail.com>
Cc: "Marc-André Lureau" <marcandre.lureau@redhat.com>
Cc: qemu-devel@nongnu.org
---
 chardev/char-fe.c          |  14 ++
 chardev/char-mux-be.c      | 272 +++++++++++++++++++++++++++++++++++++
 chardev/char.c             |  56 ++++++--
 chardev/chardev-internal.h |  27 +++-
 chardev/meson.build        |   1 +
 include/chardev/char.h     |   1 +
 qapi/char.json             |  25 ++++
 7 files changed, 383 insertions(+), 13 deletions(-)
 create mode 100644 chardev/char-mux-be.c

diff --git a/chardev/char-fe.c b/chardev/char-fe.c
index 71d6212b13f8..33f7a04b3c60 100644
--- a/chardev/char-fe.c
+++ b/chardev/char-fe.c
@@ -207,6 +207,17 @@ bool qemu_chr_fe_init(CharBackend *b, Chardev *s, Error **errp)
 
             d->backends[d->mux_cnt] = b;
             tag = d->mux_cnt++;
+        } else if (CHARDEV_IS_MUX_BE(s)) {
+            MuxBeChardev *d = MUX_BE_CHARDEV(s);
+
+            if (d->frontend) {
+                error_setg(errp,
+                           "multiplexed chardev '%s' is already used "
+                           "for multiplexing", s->label);
+                return false;
+            }
+            d->frontend = b;
+
         } else if (s->be) {
             error_setg(errp, "chardev '%s' is already in use", s->label);
             return false;
@@ -233,6 +244,9 @@ void qemu_chr_fe_deinit(CharBackend *b, bool del)
         if (CHARDEV_IS_MUX_FE(b->chr)) {
             MuxFeChardev *d = MUX_FE_CHARDEV(b->chr);
             d->backends[b->tag] = NULL;
+        } else if (CHARDEV_IS_MUX_BE(b->chr)) {
+            MuxBeChardev *d = MUX_BE_CHARDEV(b->chr);
+            d->frontend = NULL;
         }
         if (del) {
             Object *obj = OBJECT(b->chr);
diff --git a/chardev/char-mux-be.c b/chardev/char-mux-be.c
new file mode 100644
index 000000000000..2f8a55b0dcce
--- /dev/null
+++ b/chardev/char-mux-be.c
@@ -0,0 +1,272 @@
+/*
+ * QEMU Character Backend Multiplexer
+ *
+ * Author: Roman Penyaev <r.peniaev@gmail.com>
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+
+#include "qemu/osdep.h"
+#include "qapi/error.h"
+#include "qemu/module.h"
+#include "qemu/option.h"
+#include "qemu/cutils.h"
+#include "chardev/char.h"
+#include "sysemu/block-backend.h"
+#include "qapi/qapi-commands-control.h"
+#include "qapi/clone-visitor.h"
+#include "qapi/qapi-builtin-visit.h"
+#include "chardev-internal.h"
+
+/*
+ * MUX-BE driver for multiplexing 1 frontend device with N backend devices
+ */
+
+/*
+ * Write to all backends. Different backend devices accept data with
+ * various rate, so it is quite possible that one device returns less,
+ * then others. In this case we return minimum to the caller,
+ * expecting caller will repeat operation soon. When repeat happens
+ * send to the devices which consume data faster must be avoided
+ * for obvious reasons not to send data, which was already sent.
+ */
+static int mux_be_chr_write_to_all(MuxBeChardev *d, const uint8_t *buf, int len)
+{
+    int r, i, ret = len;
+    unsigned int written;
+
+    for (i = 0; i < d->mux_cnt; i++) {
+        written = d->be_written[i] - d->be_min_written;
+        if (written) {
+            /* Written in the previous call so take into account */
+            ret = MIN(written, ret);
+            continue;
+        }
+        r = qemu_chr_fe_write(&d->backends[i], buf, len);
+        if (r < 0 && errno == EAGAIN) {
+            /*
+             * Fail immediately if write would block. Expect to be called
+             * soon on watch wake up.
+             */
+            return r;
+        } else if (r < 0) {
+            /*
+             * Ignore all other errors and pretend the entire buffer is
+             * written to avoid this chardev being watched. This device
+             * becomes disabled until the following write succeeds, but
+             * writing continues to others.
+             */
+            r = len;
+        }
+        d->be_written[i] += r;
+        ret = MIN(r, ret);
+    }
+    d->be_min_written += ret;
+
+    return ret;
+}
+
+/* Called with chr_write_lock held.  */
+static int mux_be_chr_write(Chardev *chr, const uint8_t *buf, int len)
+{
+    MuxBeChardev *d = MUX_BE_CHARDEV(chr);
+    return mux_be_chr_write_to_all(d, buf, len);
+}
+
+static void mux_be_chr_send_event(MuxBeChardev *d, QEMUChrEvent event)
+{
+    CharBackend *fe = d->frontend;
+
+    if (fe && fe->chr_event) {
+        fe->chr_event(fe->opaque, event);
+    }
+}
+
+static void mux_be_chr_be_event(Chardev *chr, QEMUChrEvent event)
+{
+    MuxBeChardev *d = MUX_BE_CHARDEV(chr);
+
+    mux_be_chr_send_event(d, event);
+}
+
+static int mux_be_chr_can_read(void *opaque)
+{
+    MuxBeChardev *d = MUX_BE_CHARDEV(opaque);
+    CharBackend *fe = d->frontend;
+
+    if (fe && fe->chr_can_read) {
+        return fe->chr_can_read(fe->opaque);
+    }
+
+    return 0;
+}
+
+static void mux_be_chr_read(void *opaque, const uint8_t *buf, int size)
+{
+    MuxBeChardev *d = MUX_BE_CHARDEV(opaque);
+    CharBackend *fe = d->frontend;
+
+    if (fe && fe->chr_read) {
+        fe->chr_read(fe->opaque, buf, size);
+    }
+}
+
+void mux_be_chr_send_all_event(MuxBeChardev *d, QEMUChrEvent event)
+{
+    mux_be_chr_send_event(d, event);
+}
+
+static void mux_be_chr_event(void *opaque, QEMUChrEvent event)
+{
+    mux_chr_send_all_event(CHARDEV(opaque), event);
+}
+
+static GSource *mux_be_chr_add_watch(Chardev *s, GIOCondition cond)
+{
+    MuxBeChardev *d = MUX_BE_CHARDEV(s);
+    Chardev *chr;
+    ChardevClass *cc;
+    unsigned int written;
+    int i;
+
+    for (i = 0; i < d->mux_cnt; i++) {
+        written = d->be_written[i] - d->be_min_written;
+        if (written) {
+            /* We skip the device with already written buffer */
+            continue;
+        }
+
+        /*
+         * The first device that has no data written to it must be
+         * the device that recently returned EAGAIN and should be
+         * watched.
+         */
+
+        chr = qemu_chr_fe_get_driver(&d->backends[i]);
+        cc = CHARDEV_GET_CLASS(chr);
+
+        if (!cc->chr_add_watch) {
+            return NULL;
+        }
+
+        return cc->chr_add_watch(chr, cond);
+    }
+
+    return NULL;
+}
+
+bool mux_be_chr_attach_chardev(MuxBeChardev *d, Chardev *chr, Error **errp)
+{
+    bool ret;
+
+    if (d->mux_cnt >= MAX_MUX) {
+        error_setg(errp, "too many uses of multiplexed chardev '%s'"
+                   " (maximum is " stringify(MAX_MUX) ")",
+                   d->parent.label);
+        return false;
+    }
+    ret = qemu_chr_fe_init(&d->backends[d->mux_cnt], chr, errp);
+    if (ret) {
+        /* Catch up with what was already written */
+        d->be_written[d->mux_cnt] = d->be_min_written;
+        d->mux_cnt += 1;
+    }
+
+    return ret;
+}
+
+static void char_mux_be_finalize(Object *obj)
+{
+    MuxBeChardev *d = MUX_BE_CHARDEV(obj);
+    CharBackend *fe = d->frontend;
+    int i;
+
+    if (fe) {
+        fe->chr = NULL;
+    }
+    for (i = 0; i < d->mux_cnt; i++) {
+        qemu_chr_fe_deinit(&d->backends[i], false);
+    }
+}
+
+static void mux_be_chr_update_read_handlers(Chardev *chr)
+{
+    MuxBeChardev *d = MUX_BE_CHARDEV(chr);
+    int i;
+
+    for (i = 0; i < d->mux_cnt; i++) {
+        /* Fix up the real driver with mux routines */
+        qemu_chr_fe_set_handlers_full(&d->backends[i],
+                                      mux_be_chr_can_read,
+                                      mux_be_chr_read,
+                                      mux_be_chr_event,
+                                      NULL,
+                                      chr,
+                                      chr->gcontext, true, false);
+    }
+}
+
+static void qemu_chr_open_mux_be(Chardev *chr,
+                                 ChardevBackend *backend,
+                                 bool *be_opened,
+                                 Error **errp)
+{
+    /*
+     * Only default to opened state if we've realized the initial
+     * set of muxes
+     */
+    *be_opened = mux_is_opened();
+}
+
+static void qemu_chr_parse_mux_be(QemuOpts *opts, ChardevBackend *backend,
+                                  Error **errp)
+{
+    ChardevMuxBe *mux;
+
+    backend->type = CHARDEV_BACKEND_KIND_MUX_BE;
+    mux = backend->u.mux_be.data = g_new0(ChardevMuxBe, 1);
+    qemu_chr_parse_common(opts, qapi_ChardevMuxBe_base(mux));
+}
+
+static void char_mux_be_class_init(ObjectClass *oc, void *data)
+{
+    ChardevClass *cc = CHARDEV_CLASS(oc);
+
+    cc->parse = qemu_chr_parse_mux_be;
+    cc->open = qemu_chr_open_mux_be;
+    cc->chr_write = mux_be_chr_write;
+    cc->chr_add_watch = mux_be_chr_add_watch;
+    cc->chr_be_event = mux_be_chr_be_event;
+    cc->chr_update_read_handler = mux_be_chr_update_read_handlers;
+}
+
+static const TypeInfo char_mux_be_type_info = {
+    .name = TYPE_CHARDEV_MUX_BE,
+    .parent = TYPE_CHARDEV,
+    .class_init = char_mux_be_class_init,
+    .instance_size = sizeof(MuxBeChardev),
+    .instance_finalize = char_mux_be_finalize,
+};
+
+static void register_types(void)
+{
+    type_register_static(&char_mux_be_type_info);
+}
+
+type_init(register_types);
diff --git a/chardev/char.c b/chardev/char.c
index ec9a6c4d2499..c010679b64f7 100644
--- a/chardev/char.c
+++ b/chardev/char.c
@@ -341,6 +341,9 @@ static bool qemu_chr_is_busy(Chardev *s)
     if (CHARDEV_IS_MUX_FE(s)) {
         MuxFeChardev *d = MUX_FE_CHARDEV(s);
         return d->mux_cnt > 0;
+    } else if (CHARDEV_IS_MUX_BE(s)) {
+        MuxBeChardev *d = MUX_BE_CHARDEV(s);
+        return d->mux_cnt > 0;
     } else {
         return s->be != NULL;
     }
@@ -648,7 +651,8 @@ static Chardev *__qemu_chr_new_from_opts(QemuOpts *opts, GMainContext *context,
     ChardevBackend *backend = NULL;
     const char *name = qemu_opt_get(opts, "backend");
     const char *id = qemu_opts_id(opts);
-    char *bid = NULL;
+    const char *mux_be_id = NULL;
+    char *mux_fe_id = NULL;
 
     if (name && is_help_option(name)) {
         GString *str = g_string_new("");
@@ -676,10 +680,16 @@ static Chardev *__qemu_chr_new_from_opts(QemuOpts *opts, GMainContext *context,
     }
 
     if (qemu_opt_get_bool(opts, "mux", 0)) {
-        bid = g_strdup_printf("%s-base", id);
+        mux_fe_id = g_strdup_printf("%s-base", id);
+    }
+    mux_be_id = qemu_opt_get(opts, "mux-be-id");
+    if (mux_be_id && mux_fe_id) {
+        error_setg(errp, "chardev: mux and mux-be can't be used for the same "
+                   "device");
+        goto out;
     }
 
-    chr = qemu_chardev_new(bid ? bid : id,
+    chr = qemu_chardev_new(mux_fe_id ? mux_fe_id : id,
                            object_class_get_name(OBJECT_CLASS(cc)),
                            backend, context, errp);
     if (chr == NULL) {
@@ -687,25 +697,40 @@ static Chardev *__qemu_chr_new_from_opts(QemuOpts *opts, GMainContext *context,
     }
 
     base = chr;
-    if (bid) {
+    if (mux_fe_id) {
         Chardev *mux;
         qapi_free_ChardevBackend(backend);
         backend = g_new0(ChardevBackend, 1);
         backend->type = CHARDEV_BACKEND_KIND_MUX;
         backend->u.mux.data = g_new0(ChardevMux, 1);
-        backend->u.mux.data->chardev = g_strdup(bid);
+        backend->u.mux.data->chardev = g_strdup(mux_fe_id);
         mux = qemu_chardev_new(id, TYPE_CHARDEV_MUX_FE, backend, context, errp);
         if (mux == NULL) {
-            object_unparent(OBJECT(chr));
-            chr = NULL;
-            goto out;
+            goto unparent_and_out;
         }
         chr = mux;
+    } else if (mux_be_id) {
+        Chardev *s;
+
+        s = qemu_chr_find(mux_be_id);
+        if (!s) {
+            error_setg(errp, "chardev: mux-be device can't be found by id '%s'",
+                       mux_be_id);
+            goto unparent_and_out;
+        }
+        if (!CHARDEV_IS_MUX_BE(s)) {
+            error_setg(errp, "chardev: device '%s' is not a multiplexer device"
+                       " of 'mux-de' type", mux_be_id);
+            goto unparent_and_out;
+        }
+        if (!mux_be_chr_attach_chardev(MUX_BE_CHARDEV(s), chr, errp)) {
+            goto unparent_and_out;
+        }
     }
 
 out:
     qapi_free_ChardevBackend(backend);
-    g_free(bid);
+    g_free(mux_fe_id);
 
     if (replay && base) {
         /* RR should be set on the base device, not the mux */
@@ -713,6 +738,11 @@ out:
     }
 
     return chr;
+
+unparent_and_out:
+    object_unparent(OBJECT(chr));
+    chr = NULL;
+    goto out;
 }
 
 Chardev *qemu_chr_new_from_opts(QemuOpts *opts, GMainContext *context,
@@ -1114,7 +1144,7 @@ ChardevReturn *qmp_chardev_change(const char *id, ChardevBackend *backend,
         return NULL;
     }
 
-    if (CHARDEV_IS_MUX_FE(chr)) {
+    if (CHARDEV_IS_MUX_FE(chr) || CHARDEV_IS_MUX_BE(chr)) {
         error_setg(errp, "Mux device hotswap not supported yet");
         return NULL;
     }
@@ -1302,7 +1332,7 @@ static int chardev_options_parsed_cb(Object *child, void *opaque)
 {
     Chardev *chr = (Chardev *)child;
 
-    if (!chr->be_open && CHARDEV_IS_MUX_FE(chr)) {
+    if (!chr->be_open && (CHARDEV_IS_MUX_FE(chr) || CHARDEV_IS_MUX_BE(chr))) {
         open_muxes(chr);
     }
 
@@ -1329,8 +1359,10 @@ void mux_chr_send_all_event(Chardev *chr, QEMUChrEvent event)
 
     if (CHARDEV_IS_MUX_FE(chr)) {
         MuxFeChardev *d = MUX_FE_CHARDEV(chr);
-
         mux_fe_chr_send_all_event(d, event);
+    } else if (CHARDEV_IS_MUX_BE(chr)) {
+        MuxBeChardev *d = MUX_BE_CHARDEV(chr);
+        mux_be_chr_send_all_event(d, event);
     }
 }
 
diff --git a/chardev/chardev-internal.h b/chardev/chardev-internal.h
index a77f0bdaccfa..60b41048ddd7 100644
--- a/chardev/chardev-internal.h
+++ b/chardev/chardev-internal.h
@@ -35,7 +35,9 @@
 
 struct MuxFeChardev {
     Chardev parent;
+    /* Linked frontends */
     CharBackend *backends[MAX_MUX];
+    /* Linked backend */
     CharBackend chr;
     int focus;
     int mux_cnt;
@@ -55,16 +57,39 @@ struct MuxFeChardev {
 };
 typedef struct MuxFeChardev MuxFeChardev;
 
+struct MuxBeChardev {
+    Chardev parent;
+    /* Linked frontend */
+    CharBackend *frontend;
+    /* Linked backends */
+    CharBackend backends[MAX_MUX];
+    unsigned int mux_cnt;
+    /*
+     * Counters of written bytes from a single frontend device
+     * to multiple backend devices.
+     */
+    unsigned int be_written[MAX_MUX];
+    unsigned int be_min_written;
+};
+typedef struct MuxBeChardev MuxBeChardev;
+
 DECLARE_INSTANCE_CHECKER(MuxFeChardev, MUX_FE_CHARDEV,
                          TYPE_CHARDEV_MUX_FE)
-#define CHARDEV_IS_MUX_FE(chr)                             \
+DECLARE_INSTANCE_CHECKER(MuxBeChardev, MUX_BE_CHARDEV,
+                         TYPE_CHARDEV_MUX_BE)
+
+#define CHARDEV_IS_MUX_FE(chr)                              \
     object_dynamic_cast(OBJECT(chr), TYPE_CHARDEV_MUX_FE)
+#define CHARDEV_IS_MUX_BE(chr)                              \
+    object_dynamic_cast(OBJECT(chr), TYPE_CHARDEV_MUX_BE)
 
 void mux_set_focus(Chardev *chr, int focus);
 void mux_chr_send_all_event(Chardev *chr, QEMUChrEvent event);
 
 /* Mux type dependent calls */
 void mux_fe_chr_send_all_event(MuxFeChardev *d, QEMUChrEvent event);
+void mux_be_chr_send_all_event(MuxBeChardev *d, QEMUChrEvent event);
+bool mux_be_chr_attach_chardev(MuxBeChardev *d, Chardev *chr, Error **errp);
 
 Object *get_chardevs_root(void);
 
diff --git a/chardev/meson.build b/chardev/meson.build
index 778444a00ca6..3a9f5565372b 100644
--- a/chardev/meson.build
+++ b/chardev/meson.build
@@ -3,6 +3,7 @@ chardev_ss.add(files(
   'char-file.c',
   'char-io.c',
   'char-mux-fe.c',
+  'char-mux-be.c',
   'char-null.c',
   'char-pipe.c',
   'char-ringbuf.c',
diff --git a/include/chardev/char.h b/include/chardev/char.h
index 0bec974f9d73..c58c11c4eeaf 100644
--- a/include/chardev/char.h
+++ b/include/chardev/char.h
@@ -232,6 +232,7 @@ OBJECT_DECLARE_TYPE(Chardev, ChardevClass, CHARDEV)
 
 #define TYPE_CHARDEV_NULL "chardev-null"
 #define TYPE_CHARDEV_MUX_FE "chardev-mux"
+#define TYPE_CHARDEV_MUX_BE "chardev-mux-be"
 #define TYPE_CHARDEV_RINGBUF "chardev-ringbuf"
 #define TYPE_CHARDEV_PTY "chardev-pty"
 #define TYPE_CHARDEV_CONSOLE "chardev-console"
diff --git a/qapi/char.json b/qapi/char.json
index fb0dedb24383..cdec8f9cf4e2 100644
--- a/qapi/char.json
+++ b/qapi/char.json
@@ -336,6 +336,17 @@
   'data': { 'chardev': 'str' },
   'base': 'ChardevCommon' }
 
+##
+# @ChardevMuxBe:
+#
+# Configuration info for mux-be chardevs.
+#
+# Since: 9.2
+##
+{ 'struct': 'ChardevMuxBe',
+  'data': { },
+  'base': 'ChardevCommon' }
+
 ##
 # @ChardevStdio:
 #
@@ -483,6 +494,8 @@
 #
 # @mux: (since 1.5)
 #
+# @mux-be: (since 9.2)
+#
 # @msmouse: emulated Microsoft serial mouse (since 1.5)
 #
 # @wctablet: emulated Wacom Penpartner serial tablet (since 2.9)
@@ -525,6 +538,7 @@
             'pty',
             'null',
             'mux',
+            'mux-be',
             'msmouse',
             'wctablet',
             { 'name': 'braille', 'if': 'CONFIG_BRLAPI' },
@@ -599,6 +613,16 @@
 { 'struct': 'ChardevMuxWrapper',
   'data': { 'data': 'ChardevMux' } }
 
+##
+# @ChardevMuxBeWrapper:
+#
+# @data: Configuration info for mux-be chardevs
+#
+# Since: 9.2
+##
+{ 'struct': 'ChardevMuxBeWrapper',
+  'data': { 'data': 'ChardevMuxBe' } }
+
 ##
 # @ChardevStdioWrapper:
 #
@@ -707,6 +731,7 @@
             'pty': 'ChardevPtyWrapper',
             'null': 'ChardevCommonWrapper',
             'mux': 'ChardevMuxWrapper',
+            'mux-be': 'ChardevMuxBeWrapper',
             'msmouse': 'ChardevCommonWrapper',
             'wctablet': 'ChardevCommonWrapper',
             'braille': { 'type': 'ChardevCommonWrapper',
-- 
2.34.1



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

* [PATCH v3 6/7] tests/unit/test-char: add unit test for the `mux-be` multiplexer
       [not found] <20241010101838.331032-1-r.peniaev@gmail.com>
                   ` (4 preceding siblings ...)
  2024-10-10 10:18 ` [PATCH v3 5/7] chardev/char-mux: implement backend chardev multiplexing Roman Penyaev
@ 2024-10-10 10:18 ` Roman Penyaev
  2024-10-10 10:18 ` [PATCH v3 7/7] qemu-options.hx: describe multiplexing of several backend devices Roman Penyaev
  6 siblings, 0 replies; 9+ messages in thread
From: Roman Penyaev @ 2024-10-10 10:18 UTC (permalink / raw)
  Cc: Roman Penyaev, Marc-André Lureau, qemu-devel

The test is trivial: several backends, 1 `mux-be`, 1 frontend
do the buffer write and read. Pipe is used for EAGAIN verification.

Signed-off-by: Roman Penyaev <r.peniaev@gmail.com>
Cc: "Marc-André Lureau" <marcandre.lureau@redhat.com>
Cc: qemu-devel@nongnu.org
---
 tests/unit/test-char.c | 218 ++++++++++++++++++++++++++++++++++++++++-
 1 file changed, 216 insertions(+), 2 deletions(-)

diff --git a/tests/unit/test-char.c b/tests/unit/test-char.c
index f273ce522612..a376ea465eb8 100644
--- a/tests/unit/test-char.c
+++ b/tests/unit/test-char.c
@@ -177,7 +177,7 @@ static void char_ringbuf_test(void)
     qemu_opts_del(opts);
 }
 
-static void char_mux_test(void)
+static void char_mux_fe_test(void)
 {
     QemuOpts *opts;
     Chardev *chr, *base;
@@ -337,6 +337,219 @@ static void char_mux_test(void)
     qemu_chr_fe_deinit(&chr_be2, true);
 }
 
+static void char_mux_be_test(void)
+{
+    QemuOpts *opts;
+    Chardev *mux_be, *chr1, *chr2, *base;
+    char *data;
+    FeHandler h = { 0, false, 0, false, };
+    CharBackend chr_be;
+    int ret;
+
+#define RB_SIZE 128
+
+    /* Create mux-be */
+    opts = qemu_opts_create(qemu_find_opts("chardev"), "mux0",
+                            1, &error_abort);
+    qemu_opt_set(opts, "backend", "mux-be", &error_abort);
+    mux_be = qemu_chr_new_from_opts(opts, NULL, &error_abort);
+    g_assert_nonnull(mux_be);
+    qemu_opts_del(opts);
+
+    /* Create first chardev */
+    opts = qemu_opts_create(qemu_find_opts("chardev"), "chr1",
+                            1, &error_abort);
+    qemu_opt_set(opts, "backend", "ringbuf", &error_abort);
+    qemu_opt_set(opts, "size", stringify(RB_SIZE), &error_abort);
+    qemu_opt_set(opts, "mux-be-id", "mux0", &error_abort);
+    chr1 = qemu_chr_new_from_opts(opts, NULL, &error_abort);
+    g_assert_nonnull(chr1);
+    qemu_opts_del(opts);
+
+    /* Create second chardev */
+    opts = qemu_opts_create(qemu_find_opts("chardev"), "chr2",
+                            1, &error_abort);
+    qemu_opt_set(opts, "backend", "ringbuf", &error_abort);
+    qemu_opt_set(opts, "size", stringify(RB_SIZE), &error_abort);
+    qemu_opt_set(opts, "mux-be-id", "mux0", &error_abort);
+    chr2 = qemu_chr_new_from_opts(opts, NULL, &error_abort);
+    g_assert_nonnull(chr2);
+    qemu_opts_del(opts);
+
+    /* Attach mux-be to a frontend */
+    qemu_chr_fe_init(&chr_be, mux_be, &error_abort);
+    qemu_chr_fe_set_handlers(&chr_be,
+                             fe_can_read,
+                             fe_read,
+                             fe_event,
+                             NULL,
+                             &h,
+                             NULL, true);
+
+    /* Write to backend, chr1 */
+    base = qemu_chr_find("chr1");
+    g_assert_cmpint(qemu_chr_be_can_write(base), !=, 0);
+
+    qemu_chr_be_write(base, (void *)"hello", 6);
+    g_assert_cmpint(h.read_count, ==, 6);
+    g_assert_cmpstr(h.read_buf, ==, "hello");
+    h.read_count = 0;
+
+    /* Write to backend, chr2 */
+    base = qemu_chr_find("chr2");
+    g_assert_cmpint(qemu_chr_be_can_write(base), !=, 0);
+
+    qemu_chr_be_write(base, (void *)"olleh", 6);
+    g_assert_cmpint(h.read_count, ==, 6);
+    g_assert_cmpstr(h.read_buf, ==, "olleh");
+    h.read_count = 0;
+
+    /* Write to frontend, chr_be */
+    ret = qemu_chr_fe_write(&chr_be, (void *)"heyhey", 6);
+    g_assert_cmpint(ret, ==, 6);
+
+    data = qmp_ringbuf_read("chr1", RB_SIZE, false, 0, &error_abort);
+    g_assert_cmpint(strlen(data), ==, 6);
+    g_assert_cmpstr(data, ==, "heyhey");
+    g_free(data);
+
+    data = qmp_ringbuf_read("chr2", RB_SIZE, false, 0, &error_abort);
+    g_assert_cmpint(strlen(data), ==, 6);
+    g_assert_cmpstr(data, ==, "heyhey");
+    g_free(data);
+
+
+#ifndef _WIN32
+    /*
+     * Create third chardev to simulate EAGAIN and watcher.
+     * Mainly copied from char_pipe_test().
+     */
+    {
+        gchar *tmp_path = g_dir_make_tmp("qemu-test-char.XXXXXX", NULL);
+        gchar *in, *out, *pipe = g_build_filename(tmp_path, "pipe", NULL);
+        Chardev *chr3;
+        int fd, len;
+        char buf[128];
+
+        in = g_strdup_printf("%s.in", pipe);
+        if (mkfifo(in, 0600) < 0) {
+            abort();
+        }
+        out = g_strdup_printf("%s.out", pipe);
+        if (mkfifo(out, 0600) < 0) {
+            abort();
+        }
+
+        opts = qemu_opts_create(qemu_find_opts("chardev"), "chr3",
+                                1, &error_abort);
+        qemu_opt_set(opts, "backend", "pipe", &error_abort);
+        qemu_opt_set(opts, "path", pipe, &error_abort);
+        qemu_opt_set(opts, "mux-be-id", "mux0", &error_abort);
+        chr3 = qemu_chr_new_from_opts(opts, NULL, &error_abort);
+        g_assert_nonnull(chr3);
+
+        /* Write to frontend, chr_be */
+        ret = qemu_chr_fe_write(&chr_be, (void *)"thisis", 6);
+        g_assert_cmpint(ret, ==, 6);
+
+        data = qmp_ringbuf_read("chr1", RB_SIZE, false, 0, &error_abort);
+        g_assert_cmpint(strlen(data), ==, 6);
+        g_assert_cmpstr(data, ==, "thisis");
+        g_free(data);
+
+        data = qmp_ringbuf_read("chr2", RB_SIZE, false, 0, &error_abort);
+        g_assert_cmpint(strlen(data), ==, 6);
+        g_assert_cmpstr(data, ==, "thisis");
+        g_free(data);
+
+        fd = open(out, O_RDWR);
+        ret = read(fd, buf, sizeof(buf));
+        g_assert_cmpint(ret, ==, 6);
+        buf[ret] = 0;
+        g_assert_cmpstr(buf, ==, "thisis");
+        close(fd);
+
+        /* Add watch. 0 indicates no watches if nothing to wait for */
+        ret = qemu_chr_fe_add_watch(&chr_be, G_IO_OUT | G_IO_HUP,
+                                    NULL, NULL);
+        g_assert_cmpint(ret, ==, 0);
+
+        /*
+         * Write to frontend, chr_be, until EAGAIN. Make sure length is
+         * power of two to fit nicely the whole pipe buffer.
+         */
+        len = 0;
+        while ((ret = qemu_chr_fe_write(&chr_be, (void *)"thisisit", 8))
+               != -1) {
+            len += ret;
+        }
+        g_assert_cmpint(errno, ==, EAGAIN);
+
+        /* Further all writes should cause EAGAIN */
+        ret = qemu_chr_fe_write(&chr_be, (void *)"b", 1);
+        g_assert_cmpint(ret, ==, -1);
+        g_assert_cmpint(errno, ==, EAGAIN);
+
+        /*
+         * Add watch. Non 0 indicates we have a blocked chardev, which
+         * can wakes us up when write is possible.
+         */
+        ret = qemu_chr_fe_add_watch(&chr_be, G_IO_OUT | G_IO_HUP,
+                                    NULL, NULL);
+        g_assert_cmpint(ret, !=, 0);
+        g_source_remove(ret);
+
+        /* Drain pipe and ring buffers */
+        fd = open(out, O_RDWR);
+        while ((ret = read(fd, buf, MIN(sizeof(buf), len))) != -1 && len > 0) {
+            len -= ret;
+        }
+        close(fd);
+
+        data = qmp_ringbuf_read("chr1", RB_SIZE, false, 0, &error_abort);
+        g_assert_cmpint(strlen(data), ==, 128);
+        g_free(data);
+
+        data = qmp_ringbuf_read("chr2", RB_SIZE, false, 0, &error_abort);
+        g_assert_cmpint(strlen(data), ==, 128);
+        g_free(data);
+
+        /*
+         * Now we are good to go, first repeat "lost" sequence, which
+         * was already consumed and drained by the ring buffers, but
+         * pipe have not recieved that yet.
+         */
+        ret = qemu_chr_fe_write(&chr_be, (void *)"thisisit", 8);
+        g_assert_cmpint(ret, ==, 8);
+
+        ret = qemu_chr_fe_write(&chr_be, (void *)"streamisrestored", 16);
+        g_assert_cmpint(ret, ==, 16);
+
+        data = qmp_ringbuf_read("chr1", RB_SIZE, false, 0, &error_abort);
+        g_assert_cmpint(strlen(data), ==, 16);
+        /* Only last 16 bytes, see big comment above */
+        g_assert_cmpstr(data, ==, "streamisrestored");
+        g_free(data);
+
+        data = qmp_ringbuf_read("chr2", RB_SIZE, false, 0, &error_abort);
+        g_assert_cmpint(strlen(data), ==, 16);
+        /* Only last 16 bytes, see big comment above */
+        g_assert_cmpstr(data, ==, "streamisrestored");
+        g_free(data);
+
+        fd = open(out, O_RDWR);
+        ret = read(fd, buf, sizeof(buf));
+        g_assert_cmpint(ret, ==, 24);
+        buf[ret] = 0;
+        /* Both 8 and 16 bytes */
+        g_assert_cmpstr(buf, ==, "thisisitstreamisrestored");
+        close(fd);
+    }
+#endif
+
+    /* Finalize */
+    qemu_chr_fe_deinit(&chr_be, false);
+}
 
 static void websock_server_read(void *opaque, const uint8_t *buf, int size)
 {
@@ -1484,7 +1697,8 @@ int main(int argc, char **argv)
     g_test_add_func("/char/null", char_null_test);
     g_test_add_func("/char/invalid", char_invalid_test);
     g_test_add_func("/char/ringbuf", char_ringbuf_test);
-    g_test_add_func("/char/mux", char_mux_test);
+    g_test_add_func("/char/mux", char_mux_fe_test);
+    g_test_add_func("/char/mux-be", char_mux_be_test);
 #ifdef _WIN32
     g_test_add_func("/char/console/subprocess", char_console_test_subprocess);
     g_test_add_func("/char/console", char_console_test);
-- 
2.34.1



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

* [PATCH v3 7/7] qemu-options.hx: describe multiplexing of several backend devices
       [not found] <20241010101838.331032-1-r.peniaev@gmail.com>
                   ` (5 preceding siblings ...)
  2024-10-10 10:18 ` [PATCH v3 6/7] tests/unit/test-char: add unit test for the `mux-be` multiplexer Roman Penyaev
@ 2024-10-10 10:18 ` Roman Penyaev
  6 siblings, 0 replies; 9+ messages in thread
From: Roman Penyaev @ 2024-10-10 10:18 UTC (permalink / raw)
  Cc: Roman Penyaev, Marc-André Lureau, qemu-devel

This adds a few lines describing `mux-be` multiplexer configuration
for multiplexing several backend devices with a single frontend
device.

Signed-off-by: Roman Penyaev <r.peniaev@gmail.com>
Cc: "Marc-André Lureau" <marcandre.lureau@redhat.com>
Cc: qemu-devel@nongnu.org
---
 qemu-options.hx | 78 ++++++++++++++++++++++++++++++++++++-------------
 1 file changed, 58 insertions(+), 20 deletions(-)

diff --git a/qemu-options.hx b/qemu-options.hx
index d5afefe5b63c..22490c529a50 100644
--- a/qemu-options.hx
+++ b/qemu-options.hx
@@ -3693,37 +3693,37 @@ DEFHEADING(Character device options:)
 
 DEF("chardev", HAS_ARG, QEMU_OPTION_chardev,
     "-chardev help\n"
-    "-chardev null,id=id[,mux=on|off][,logfile=PATH][,logappend=on|off]\n"
+    "-chardev null,id=id[,mux=on|off][,mux-be-id=id][,logfile=PATH][,logappend=on|off]\n"
     "-chardev socket,id=id[,host=host],port=port[,to=to][,ipv4=on|off][,ipv6=on|off][,nodelay=on|off]\n"
-    "         [,server=on|off][,wait=on|off][,telnet=on|off][,websocket=on|off][,reconnect=seconds][,mux=on|off]\n"
+    "         [,server=on|off][,wait=on|off][,telnet=on|off][,websocket=on|off][,reconnect=seconds][,mux=on|off][,mux-be-id=id]\n"
     "         [,logfile=PATH][,logappend=on|off][,tls-creds=ID][,tls-authz=ID] (tcp)\n"
     "-chardev socket,id=id,path=path[,server=on|off][,wait=on|off][,telnet=on|off][,websocket=on|off][,reconnect=seconds]\n"
-    "         [,mux=on|off][,logfile=PATH][,logappend=on|off][,abstract=on|off][,tight=on|off] (unix)\n"
+    "         [,mux=on|off][,mux-be-id=id][,logfile=PATH][,logappend=on|off][,abstract=on|off][,tight=on|off] (unix)\n"
     "-chardev udp,id=id[,host=host],port=port[,localaddr=localaddr]\n"
-    "         [,localport=localport][,ipv4=on|off][,ipv6=on|off][,mux=on|off]\n"
+    "         [,localport=localport][,ipv4=on|off][,ipv6=on|off][,mux=on|off][,mux-be-id=id]\n"
     "         [,logfile=PATH][,logappend=on|off]\n"
-    "-chardev msmouse,id=id[,mux=on|off][,logfile=PATH][,logappend=on|off]\n"
+    "-chardev msmouse,id=id[,mux=on|off][,mux-be-id=id][,logfile=PATH][,logappend=on|off]\n"
     "-chardev vc,id=id[[,width=width][,height=height]][[,cols=cols][,rows=rows]]\n"
-    "         [,mux=on|off][,logfile=PATH][,logappend=on|off]\n"
+    "         [,mux=on|off][,mux-be-id=id][,logfile=PATH][,logappend=on|off]\n"
     "-chardev ringbuf,id=id[,size=size][,logfile=PATH][,logappend=on|off]\n"
-    "-chardev file,id=id,path=path[,input-path=input-file][,mux=on|off][,logfile=PATH][,logappend=on|off]\n"
-    "-chardev pipe,id=id,path=path[,mux=on|off][,logfile=PATH][,logappend=on|off]\n"
+    "-chardev file,id=id,path=path[,input-path=input-file][,mux=on|off][,mux-be-id=id][,logfile=PATH][,logappend=on|off]\n"
+    "-chardev pipe,id=id,path=path[,mux=on|off][,mux-be-id=id][,logfile=PATH][,logappend=on|off]\n"
 #ifdef _WIN32
-    "-chardev console,id=id[,mux=on|off][,logfile=PATH][,logappend=on|off]\n"
-    "-chardev serial,id=id,path=path[,mux=on|off][,logfile=PATH][,logappend=on|off]\n"
+    "-chardev console,id=id[,mux=on|off][,mux-be-id=id][,logfile=PATH][,logappend=on|off]\n"
+    "-chardev serial,id=id,path=path[,mux=on|off][,mux-be-id=id][,logfile=PATH][,logappend=on|off]\n"
 #else
-    "-chardev pty,id=id[,path=path][,mux=on|off][,logfile=PATH][,logappend=on|off]\n"
-    "-chardev stdio,id=id[,mux=on|off][,signal=on|off][,logfile=PATH][,logappend=on|off]\n"
+    "-chardev pty,id=id[,path=path][,mux=on|off][,mux-be-id=id][,logfile=PATH][,logappend=on|off]\n"
+    "-chardev stdio,id=id[,mux=on|off][,mux-be-id=id][,signal=on|off][,logfile=PATH][,logappend=on|off]\n"
 #endif
 #ifdef CONFIG_BRLAPI
-    "-chardev braille,id=id[,mux=on|off][,logfile=PATH][,logappend=on|off]\n"
+    "-chardev braille,id=id[,mux=on|off][,mux-be-id=id][,logfile=PATH][,logappend=on|off]\n"
 #endif
 #if defined(__linux__) || defined(__sun__) || defined(__FreeBSD__) \
         || defined(__NetBSD__) || defined(__OpenBSD__) || defined(__DragonFly__)
-    "-chardev serial,id=id,path=path[,mux=on|off][,logfile=PATH][,logappend=on|off]\n"
+    "-chardev serial,id=id,path=path[,mux=on|off][,mux-be-id=id][,logfile=PATH][,logappend=on|off]\n"
 #endif
 #if defined(__linux__) || defined(__FreeBSD__) || defined(__DragonFly__)
-    "-chardev parallel,id=id,path=path[,mux=on|off][,logfile=PATH][,logappend=on|off]\n"
+    "-chardev parallel,id=id,path=path[,mux=on|off][,mux-be-id=id][,logfile=PATH][,logappend=on|off]\n"
 #endif
 #if defined(CONFIG_SPICE)
     "-chardev spicevmc,id=id,name=name[,debug=debug][,logfile=PATH][,logappend=on|off]\n"
@@ -3735,8 +3735,8 @@ DEF("chardev", HAS_ARG, QEMU_OPTION_chardev,
 SRST
 The general form of a character device option is:
 
-``-chardev backend,id=id[,mux=on|off][,options]``
-    Backend is one of: ``null``, ``socket``, ``udp``, ``msmouse``,
+``-chardev backend,id=id[,mux=on|off][,mux-be-id=id][,options]``
+    Backend is one of: ``null``, ``socket``, ``udp``, ``msmouse``, ``mux-be``,
     ``vc``, ``ringbuf``, ``file``, ``pipe``, ``console``, ``serial``,
     ``pty``, ``stdio``, ``braille``, ``parallel``,
     ``spicevmc``, ``spiceport``. The specific backend will determine the
@@ -3793,9 +3793,10 @@ The general form of a character device option is:
     the QEMU monitor, and ``-nographic`` also multiplexes the console
     and the monitor to stdio.
 
-    There is currently no support for multiplexing in the other
-    direction (where a single QEMU front end takes input and output from
-    multiple chardevs).
+    If you need to multiplex in the opposite direction (where one QEMU
+    interface receives input and output from multiple chardev devices),
+    each character device needs ``mux-be-id=id`` option. Please refer
+    to the paragraph below regarding chardev ``mux-be`` configuration.
 
     Every backend supports the ``logfile`` option, which supplies the
     path to a file to record all data transmitted via the backend. The
@@ -3895,6 +3896,43 @@ The available backends are:
     Forward QEMU's emulated msmouse events to the guest. ``msmouse``
     does not take any options.
 
+``-chardev mux-be,id=id``
+    Explicitly create chardev backend multiplexer with possibility to
+    multiplex in the opposite direction, where one QEMU interface
+    (frontend device) receives input and output from multiple chardev
+    backend devices.
+
+    For example the following is a use case of 2 backend devices: text
+    virtual console ``vc0`` and a socket ``sock0`` connected
+    to a single virtio hvc console frontend device with multiplexer
+    ``mux0`` help. Virtual console renders text to an image, which
+    can be shared over the VNC protocol, in turn socket backend provides
+    biderectional communication to the virtio hvc console over socket.
+    The example configuration can be the following:
+
+    ::
+
+       -chardev mux-be,id=mux0 \
+       -chardev socket,path=/tmp/sock,server=on,wait=off,id=sock0,mux-be-id=mux0 \
+       -chardev vc,id=vc0,mux-be-id=mux0 \
+       -device virtconsole,chardev=mux0 \
+       -vnc 0.0.0.0:0
+
+    Once QEMU starts VNC client and any TTY emulator can be used to
+    control a single hvc console:
+
+    ::
+
+       # VNC client
+       vncviewer :0
+
+       # TTY emulator
+       socat unix-connect:/tmp/sock pty,link=/tmp/pty & \
+       tio /tmp/pty
+
+    Multiplexing of several backend devices with serveral frontend devices
+    is not supported.
+
 ``-chardev vc,id=id[[,width=width][,height=height]][[,cols=cols][,rows=rows]]``
     Connect to a QEMU text console. ``vc`` may optionally be given a
     specific size.
-- 
2.34.1



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

* Re: [PATCH v3 1/7] chardev/char: rename `char-mux.c` to `char-mux-fe.c`
  2024-10-10 10:18 ` [PATCH v3 1/7] chardev/char: rename `char-mux.c` to `char-mux-fe.c` Roman Penyaev
@ 2024-10-14 13:57   ` Marc-André Lureau
  2024-10-14 14:08     ` Roman Penyaev
  0 siblings, 1 reply; 9+ messages in thread
From: Marc-André Lureau @ 2024-10-14 13:57 UTC (permalink / raw)
  To: Roman Penyaev; +Cc: qemu-devel

[-- Attachment #1: Type: text/plain, Size: 1478 bytes --]

Hi Roman,

On Thu, Oct 10, 2024 at 2:21 PM Roman Penyaev <r.peniaev@gmail.com> wrote:

> In the following patches backend multiplexer will be
> introduced and the implementation will be named as
> follows: `char-mux-be.c`. This patch renames the
> frontend multiplexer from `char-mux.c` to
> `char-mux-fe.c`.
>
> Signed-off-by: Roman Penyaev <r.peniaev@gmail.com>
> Cc: "Marc-André Lureau" <marcandre.lureau@redhat.com>
> Cc: qemu-devel@nongnu.org


thanks, it looks like the series is missing a cover letter (and thus not
handled by patchew)

Also it seems to fail to apply on master, and I wonder if it will conflict
with your other mux cleanup series.

thanks

>
> ---
>  chardev/{char-mux.c => char-mux-fe.c} | 0
>  chardev/meson.build                   | 2 +-
>  2 files changed, 1 insertion(+), 1 deletion(-)
>  rename chardev/{char-mux.c => char-mux-fe.c} (100%)
>
> diff --git a/chardev/char-mux.c b/chardev/char-mux-fe.c
> similarity index 100%
> rename from chardev/char-mux.c
> rename to chardev/char-mux-fe.c
> diff --git a/chardev/meson.build b/chardev/meson.build
> index 70070a8279a9..778444a00ca6 100644
> --- a/chardev/meson.build
> +++ b/chardev/meson.build
> @@ -2,7 +2,7 @@ chardev_ss.add(files(
>    'char-fe.c',
>    'char-file.c',
>    'char-io.c',
> -  'char-mux.c',
> +  'char-mux-fe.c',
>    'char-null.c',
>    'char-pipe.c',
>    'char-ringbuf.c',
> --
> 2.34.1
>
>
>

-- 
Marc-André Lureau

[-- Attachment #2: Type: text/html, Size: 2460 bytes --]

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

* Re: [PATCH v3 1/7] chardev/char: rename `char-mux.c` to `char-mux-fe.c`
  2024-10-14 13:57   ` Marc-André Lureau
@ 2024-10-14 14:08     ` Roman Penyaev
  0 siblings, 0 replies; 9+ messages in thread
From: Roman Penyaev @ 2024-10-14 14:08 UTC (permalink / raw)
  To: Marc-André Lureau; +Cc: qemu-devel

On Mon, Oct 14, 2024 at 3:57 PM Marc-André Lureau
<marcandre.lureau@gmail.com> wrote:
>
> Hi Roman,
>
> On Thu, Oct 10, 2024 at 2:21 PM Roman Penyaev <r.peniaev@gmail.com> wrote:
>>
>> In the following patches backend multiplexer will be
>> introduced and the implementation will be named as
>> follows: `char-mux-be.c`. This patch renames the
>> frontend multiplexer from `char-mux.c` to
>> `char-mux-fe.c`.
>>
>> Signed-off-by: Roman Penyaev <r.peniaev@gmail.com>
>> Cc: "Marc-André Lureau" <marcandre.lureau@redhat.com>
>> Cc: qemu-devel@nongnu.org
>
>
> thanks, it looks like the series is missing a cover letter (and thus not handled by patchew)

Bummer, indeed, sent only to myself.

>
> Also it seems to fail to apply on master, and I wonder if it will conflict with your other mux cleanup series.

That's because of the "chardev/char: fix qemu_chr_is_busy() check".
In the cover letter (which was never delivered) I specified that the whole
series is based on this patch :)

Let me rebase this series once we finish with the frontend detach patchset
(sent today), so I'll rebase everything from here and mark it with version "v4".

Thanks.

--
Roman


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

end of thread, other threads:[~2024-10-14 14:11 UTC | newest]

Thread overview: 9+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
     [not found] <20241010101838.331032-1-r.peniaev@gmail.com>
2024-10-10 10:18 ` [PATCH v3 1/7] chardev/char: rename `char-mux.c` to `char-mux-fe.c` Roman Penyaev
2024-10-14 13:57   ` Marc-André Lureau
2024-10-14 14:08     ` Roman Penyaev
2024-10-10 10:18 ` [PATCH v3 2/7] chardev/char: move away mux suspend/resume calls Roman Penyaev
2024-10-10 10:18 ` [PATCH v3 3/7] chardev/char: rename frontend mux calls Roman Penyaev
2024-10-10 10:18 ` [PATCH v3 4/7] chardev/char: introduce `mux-be-id=ID` option Roman Penyaev
2024-10-10 10:18 ` [PATCH v3 5/7] chardev/char-mux: implement backend chardev multiplexing Roman Penyaev
2024-10-10 10:18 ` [PATCH v3 6/7] tests/unit/test-char: add unit test for the `mux-be` multiplexer Roman Penyaev
2024-10-10 10:18 ` [PATCH v3 7/7] qemu-options.hx: describe multiplexing of several backend devices Roman Penyaev

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for NNTP newsgroup(s).