* [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
* 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
* [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