Hi

On Tue, Sep 17, 2024 at 6:17 PM Roman Penyaev <r.peni...@gmail.com> wrote:

> Hi Marc-André,
>
> On Tue, Sep 17, 2024 at 2:31 PM Marc-André Lureau
> <marcandre.lur...@redhat.com> wrote:
> >
> > Hi Roman
> >
> > On Fri, Sep 13, 2024 at 8:37 PM Roman Penyaev <r.peni...@gmail.com>
> wrote:
> > >
> > > 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.
> > >
> > > The following is QEMU command line example:
> > >
> > >   -chardev socket,path=/tmp/sock,server=on,wait=off,id=sock0 \
> > >   -chardev vc,id=vc0 \
> > >   -chardev mux,id=mux0,chardev=vc0,,sock0 \
> > >   -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 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.
> >
> > I think I would rather implement a new mux for this purpose, like
> > "mux-be" perhaps?
>
> Do you mean a completely different implementation or just an alias
> for the cli needs? Because code of the backend multiplexing nicely fits
> current mux and I tried to avoid code duplication.
>

It's not the same behaviour though, and discoverability/compatibility would
be easier to handle. There is also various code in mux that isn't needed.

You can still factorize common code, or have a common base class.
Eventually, the two chardev can be from the same file. I don't know if that
makes sense.


> >
> > "mux" has been a bit hidden (behind mux=on) for various reasons, and
> > is probably not at production quality level.
>
> Well, indeed creating "chardev mux" is not described in the doc, but you
> can do this anyway :) Here I just followed the same pattern as we do for
> other devices: "chardev NAME". Having a "mux-be" (or any other) alias
> won't expose the default "mux", so configurations can be separated.
> Is that what you meant?
>

yes, this would help in general misconfiguration (although -chardev option
handling has a lot to be desired)


>
> Also, mux is used implicitly for various of configurations, this is
> described in the manual:
>
>    Note that some other command line options may implicitly create
>    multiplexed character backends; for instance -serial mon:stdio creates
>    a multiplexed stdio backend connected to the serial port and the QEMU
>    monitor, and -nographic also multiplexes the console and the
> monitor to stdio.
>
> So it seems to me mux is used (tested) quite extensively.
>

kinda, it's used by end-users who run qemu from the CLI. Probably less in
production systems.


>
> >
> > I am not sure how CLI should handle option arrays these days
> > (especially with -chardev CLI not being json-friendly).
>
> By CLI do you mean the default QEMU command line interface?
>

yes


> The command line with arrays (double commas ",,") I specified above
> works fine. Can you suggest any other tool (or json formatting) you have
> in mind so I can verify?
>

Indeed, this double commas stuff is weird, I don't know that "trick". Is it
used elsewhere and documented? I don't see a test in test-qemu-opts.c
either.

There is another ongoing discussion about json support for -chardev:
"-chardev with a JSON argument".


> >
> > >
> > > Signed-off-by: Roman Penyaev <r.peni...@gmail.com>
> > > Cc: "Marc-André Lureau" <marcandre.lur...@redhat.com>
> > > Cc: Paolo Bonzini <pbonz...@redhat.com>
> > > Cc: qemu-devel@nongnu.org
> > > ---
> > >  chardev/char-fe.c          |  14 +++--
> > >  chardev/char-mux.c         | 120 +++++++++++++++++++++++++++++--------
> > >  chardev/char.c             |   2 +-
> > >  chardev/chardev-internal.h |   7 ++-
> > >  4 files changed, 111 insertions(+), 32 deletions(-)
> > >
> > > diff --git a/chardev/char-fe.c b/chardev/char-fe.c
> > > index b214ba3802b1..d1f67338084d 100644
> > > --- a/chardev/char-fe.c
> > > +++ b/chardev/char-fe.c
> > > @@ -197,16 +197,22 @@ bool qemu_chr_fe_init(CharBackend *b, Chardev
> *s, Error **errp)
> > >          if (CHARDEV_IS_MUX(s)) {
> > >              MuxChardev *d = MUX_CHARDEV(s);
> > >
> > > -            if (d->mux_cnt >= MAX_MUX) {
> > > +            if (d->fe_cnt >= MAX_MUX) {
> > >                  error_setg(errp,
> > >                             "too many uses of multiplexed chardev '%s'"
> > >                             " (maximum is " stringify(MAX_MUX) ")",
> > >                             s->label);
> > >                  return false;
> > >              }
> > > -
> > > -            d->backends[d->mux_cnt] = b;
> > > -            tag = d->mux_cnt++;
> > > +            if (d->fe_cnt > 0 && d->be_cnt > 1) {
> > > +                error_setg(errp,
> > > +                           "multiplexed chardev '%s' is already used "
> > > +                           "for backend multiplexing",
> > > +                           s->label);
> > > +                return false;
> > > +            }
> > > +            d->backends[d->fe_cnt] = b;
> > > +            tag = d->fe_cnt++;
> > >          } else if (s->be) {
> > >              error_setg(errp, "chardev '%s' is already in use",
> s->label);
> > >              return false;
> > > diff --git a/chardev/char-mux.c b/chardev/char-mux.c
> > > index ee2d47b20d9b..82f728b5caf8 100644
> > > --- a/chardev/char-mux.c
> > > +++ b/chardev/char-mux.c
> > > @@ -26,6 +26,7 @@
> > >  #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"
> > > @@ -40,13 +41,39 @@
> > >   */
> > >  static bool muxes_opened = true;
> > >
> > > +/* Write to all backends  */
> > > +static int mux_chr_fe_write(MuxChardev *mux, const uint8_t *buf, int
> len)
> > > +{
> > > +        int r, ret = -1, i;
> > > +
> > > +        for (i = 0; i < mux->be_cnt; i++) {
> > > +                r = qemu_chr_fe_write(&mux->chrs[i], buf, len);
> > > +                ret = ret < 0 ? r : MAX(r, ret);
> >
> > I think it should be conservative and fail early if one of the backend
> > fails. Also if different frontends can write different amounts, there
> > needs to be some buffering... or it should always use write_all()
> > which has also a shortcoming since it blocks the thread.
>
> Yes, early fail and buffers make sense.
>
> >
> > > +        }
> > > +
> > > +        return ret;
> > > +}
> > > +
> > > +/* Write to all backends  */
> > > +static int mux_chr_fe_write_all(MuxChardev *mux, const uint8_t *buf,
> int len)
> > > +{
> > > +        int r, ret = -1, i;
> > > +
> > > +        for (i = 0; i < mux->be_cnt; i++) {
> > > +                r = qemu_chr_fe_write_all(&mux->chrs[i], buf, len);
> > > +                ret = ret < 0 ? r : MAX(r, ret);
> > > +        }
> > > +
> > > +        return ret;
> > > +}
> > > +
> > >  /* Called with chr_write_lock held.  */
> > >  static int mux_chr_write(Chardev *chr, const uint8_t *buf, int len)
> > >  {
> > >      MuxChardev *d = MUX_CHARDEV(chr);
> > >      int ret;
> > >      if (!d->timestamps) {
> > > -        ret = qemu_chr_fe_write(&d->chr, buf, len);
> > > +        ret = mux_chr_fe_write(d, buf, len);
> > >      } else {
> > >          int i;
> > >
> > > @@ -71,11 +98,11 @@ static int mux_chr_write(Chardev *chr, const
> uint8_t *buf, int len)
> > >                           (int)(ti % 1000));
> > >                  /* XXX this blocks entire thread. Rewrite to use
> > >                   * qemu_chr_fe_write and background I/O callbacks */
> > > -                qemu_chr_fe_write_all(&d->chr,
> > > +                mux_chr_fe_write_all(d,
> > >                                        (uint8_t *)buf1, strlen(buf1));
> > >                  d->linestart = 0;
> > >              }
> > > -            ret += qemu_chr_fe_write(&d->chr, buf + i, 1);
> > > +            ret += mux_chr_fe_write(d, buf + i, 1);
> > >              if (buf[i] == '\n') {
> > >                  d->linestart = 1;
> > >              }
> > > @@ -168,9 +195,9 @@ static int mux_proc_byte(Chardev *chr, MuxChardev
> *d, int ch)
> > >              qemu_chr_be_event(chr, CHR_EVENT_BREAK);
> > >              break;
> > >          case 'c':
> > > -            assert(d->mux_cnt > 0); /* handler registered with first
> fe */
> > > +            assert(d->fe_cnt > 0); /* handler registered with first
> fe */
> > >              /* Switch to the next registered device */
> > > -            mux_set_focus(chr, (d->focus + 1) % d->mux_cnt);
> > > +            mux_set_focus(chr, (d->focus + 1) % d->fe_cnt);
> > >              break;
> > >          case 't':
> > >              d->timestamps = !d->timestamps;
> > > @@ -248,8 +275,8 @@ void mux_chr_send_all_event(Chardev *chr,
> QEMUChrEvent event)
> > >          return;
> > >      }
> > >
> > > -    /* Send the event to all registered listeners */
> > > -    for (i = 0; i < d->mux_cnt; i++) {
> > > +    /* Send the event to all registered frontend listeners */
> > > +    for (i = 0; i < d->fe_cnt; i++) {
> > >          mux_chr_send_event(d, i, event);
> > >      }
> > >  }
> > > @@ -262,8 +289,16 @@ static void mux_chr_event(void *opaque,
> QEMUChrEvent event)
> > >  static GSource *mux_chr_add_watch(Chardev *s, GIOCondition cond)
> > >  {
> > >      MuxChardev *d = MUX_CHARDEV(s);
> > > -    Chardev *chr = qemu_chr_fe_get_driver(&d->chr);
> > > -    ChardevClass *cc = CHARDEV_GET_CLASS(chr);
> > > +    Chardev *chr;
> > > +    ChardevClass *cc;
> > > +
> > > +    if (d->be_cnt > 1) {
> > > +            /* TODO: multiple backends have to be combined on a
> single watch */
> >
> > I think this must be done, otherwise there is a severe limitation.
>
> Ok.
>
> >
> > > +            return NULL;
> > > +    }
> > > +
> > > +    chr = qemu_chr_fe_get_driver(&d->chrs[0]);
> > > +    cc = CHARDEV_GET_CLASS(chr);
> > >
> > >      if (!cc->chr_add_watch) {
> > >          return NULL;
> > > @@ -277,27 +312,32 @@ static void char_mux_finalize(Object *obj)
> > >      MuxChardev *d = MUX_CHARDEV(obj);
> > >      int i;
> > >
> > > -    for (i = 0; i < d->mux_cnt; i++) {
> > > +    for (i = 0; i < d->fe_cnt; i++) {
> > >          CharBackend *be = d->backends[i];
> > >          if (be) {
> > >              be->chr = NULL;
> > >          }
> > >      }
> > > -    qemu_chr_fe_deinit(&d->chr, false);
> > > +    for (i = 0; i < d->be_cnt; i++) {
> > > +        qemu_chr_fe_deinit(&d->chrs[i], false);
> > > +    }
> > >  }
> > >
> > >  static void mux_chr_update_read_handlers(Chardev *chr)
> > >  {
> > >      MuxChardev *d = MUX_CHARDEV(chr);
> > > +    int i;
> > >
> > > -    /* 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,
> > > -                                  NULL,
> > > -                                  chr,
> > > -                                  chr->gcontext, true, false);
> > > +    for (i = 0; i < d->be_cnt; i++) {
> > > +        /* Fix up the real driver with mux routines */
> > > +        qemu_chr_fe_set_handlers_full(&d->chrs[i],
> > > +                                      mux_chr_can_read,
> > > +                                      mux_chr_read,
> > > +                                      mux_chr_event,
> > > +                                      NULL,
> > > +                                      chr,
> > > +                                      chr->gcontext, true, false);
> > > +    }
> > >  }
> > >
> > >  void mux_set_focus(Chardev *chr, int focus)
> > > @@ -305,7 +345,7 @@ void mux_set_focus(Chardev *chr, int focus)
> > >      MuxChardev *d = MUX_CHARDEV(chr);
> > >
> > >      assert(focus >= 0);
> > > -    assert(focus < d->mux_cnt);
> > > +    assert(focus < d->fe_cnt);
> > >
> > >      if (d->focus != -1) {
> > >          mux_chr_send_event(d, d->focus, CHR_EVENT_MUX_OUT);
> > > @@ -324,19 +364,49 @@ static void qemu_chr_open_mux(Chardev *chr,
> > >      ChardevMux *mux = backend->u.mux.data;
> > >      Chardev *drv;
> > >      MuxChardev *d = MUX_CHARDEV(chr);
> > > -
> > > -    drv = qemu_chr_find(mux->chardev);
> > > -    if (drv == NULL) {
> > > -        error_setg(errp, "mux: base chardev %s not found",
> mux->chardev);
> > > +    const char *offset, *chardevs;
> > > +    int length, i;
> > > +
> > > +    if (d->fe_cnt > 1) {
> > > +        error_setg(errp,
> > > +                   "multiplexed chardev '%s' is already used "
> > > +                   "for frontend multiplexing",
> > > +                   chr->label);
> > >          return;
> > >      }
> > >
> > > +    chardevs = mux->chardev;
> > > +    for (i = 0; i < MAX_MUX; i++) {
> > > +        char *chardev;
> > > +
> > > +        offset = qemu_strchrnul(chardevs, ',');
> > > +        length = offset - chardevs;
> > > +        if (!length) {
> > > +            break;
> > > +        }
> > > +        chardev = strndupa(chardevs, length);
> > > +        chardevs += length + 1;
> > > +        drv = qemu_chr_find(chardev);
> > > +        if (drv == NULL) {
> > > +            error_setg(errp, "mux: base chardev %s not found",
> chardev);
> > > +            goto deinit_on_error;
> > > +        }
> > > +        qemu_chr_fe_init(&d->chrs[i], drv, errp);
> > > +        d->be_cnt += 1;
> > > +    }
> > >      d->focus = -1;
> > >      /* only default to opened state if we've realized the initial
> > >       * set of muxes
> > >       */
> > >      *be_opened = muxes_opened;
> > > -    qemu_chr_fe_init(&d->chr, drv, errp);
> > > +
> > > +    return;
> > > +
> > > +deinit_on_error:
> > > +    for (i = 0; i < d->be_cnt; i++) {
> > > +        qemu_chr_fe_deinit(&d->chrs[i], false);
> > > +    }
> > > +    d->be_cnt = 0;
> > >  }
> > >
> > >  static void qemu_chr_parse_mux(QemuOpts *opts, ChardevBackend
> *backend,
> > > diff --git a/chardev/char.c b/chardev/char.c
> > > index ba847b6e9eff..2643c79e5749 100644
> > > --- a/chardev/char.c
> > > +++ b/chardev/char.c
> > > @@ -333,7 +333,7 @@ static bool qemu_chr_is_busy(Chardev *s)
> > >  {
> > >      if (CHARDEV_IS_MUX(s)) {
> > >          MuxChardev *d = MUX_CHARDEV(s);
> > > -        return d->mux_cnt >= 0;
> > > +        return d->fe_cnt >= 0;
> > >      } else {
> > >          return s->be != NULL;
> > >      }
> > > diff --git a/chardev/chardev-internal.h b/chardev/chardev-internal.h
> > > index 4e03af31476c..72c2e4da7552 100644
> > > --- a/chardev/chardev-internal.h
> > > +++ b/chardev/chardev-internal.h
> > > @@ -35,10 +35,13 @@
> > >
> > >  struct MuxChardev {
> > >      Chardev parent;
> > > +    /* Linked frontends */
> > >      CharBackend *backends[MAX_MUX];
> > > -    CharBackend chr;
> > > +    /* Linked backends */
> > > +    CharBackend chrs[MAX_MUX];
> > >      int focus;
> > > -    int mux_cnt;
> > > +    int fe_cnt;
> > > +    int be_cnt;
> > >      int term_got_escape;
> > >      int max_size;
> > >      /* Intermediate input buffer catches escape sequences even if the
> > > --
> > > 2.34.1
> > >
> >
> >
> > It would also require some tests and QAPI support.
>
> I will take a look at tests and will try to come up with some extension
> for backend multiplexing.
>
> Since the mux object API was not changed and the current change heavily
> relies on what is already in the code, do you think there should be any
> QAPI related change? In my understanding this should work out of the box
> (not tested though).
>
> --
> Roman
>
>

-- 
Marc-André Lureau

Reply via email to