UI/chardev-related patch queue
-----BEGIN PGP SIGNATURE----- iQJQBAABCAA6FiEEh6m9kz+HxgbSdvYt2ujhCXWWnOUFAmeg+mwcHG1hcmNhbmRy ZS5sdXJlYXVAcmVkaGF0LmNvbQAKCRDa6OEJdZac5X9JD/4ie4unhYkWEaJLR5ks eVRE2ZrwrO1HF2HkFHgs9UN/G6Pl4o/YaPzICQkManJOhbJvOcp8hReOrSETrOLg iiYHr3DH+H1nRzPgH+Nuvj3IRnl2EdypfgHbWVmvMQQ7u0vwpUiraTHEqy2PvTqO ougTl0lf4v4NB1CHWDTbs6IT4/hMwXM4/pP1ztXvdWeJxKUUTKb9SSOlmjkdT/Ou kZqDr/aonWxvQs6t3HeauNkiIIq21pVAIDUoDr338hTK4/EPhxOwaTpZ0b2RATA6 ldpcS7VNfsMe8aJI3nsRaRz5NkWNDnQgejGkIxxXo3xj8c/rhZMyqrrqYaqFleVW 0ahh6eY0qxc+Z7HJ+SxU8oDUzNjOw+14NeUlHTd+qRnZVasWXZlB7wYTxlbLKCHP KtbAm8KsdWrKokMkupRCHiI0je3QXlhX3TGEUS5HHcknjhvmkEzCcEYy0gYuyLRq +e79xdC/IyylZvzM/SXQXWEtb3GmBhi5pQmcRftTgISNxryXFfYXeOOQhgvJQS2L 8/Ul/rIEvhecj1me/wzOK1bDGzFae8xYSM2z7v/EAm4I59N8N8aomnN3sHeaeLlG UwWGpq9Z3igoWaM88/h8EktA0Kk8s9YBXZoKvGwVQPglEqEeWEwvrGKEM2Le7kYF eHM+osrJFf2iD42v6AnYVARhIA== =1pl1 -----END PGP SIGNATURE----- Merge tag 'ui-pull-request' of https://gitlab.com/marcandre.lureau/qemu into staging UI/chardev-related patch queue # -----BEGIN PGP SIGNATURE----- # # iQJQBAABCAA6FiEEh6m9kz+HxgbSdvYt2ujhCXWWnOUFAmeg+mwcHG1hcmNhbmRy # ZS5sdXJlYXVAcmVkaGF0LmNvbQAKCRDa6OEJdZac5X9JD/4ie4unhYkWEaJLR5ks # eVRE2ZrwrO1HF2HkFHgs9UN/G6Pl4o/YaPzICQkManJOhbJvOcp8hReOrSETrOLg # iiYHr3DH+H1nRzPgH+Nuvj3IRnl2EdypfgHbWVmvMQQ7u0vwpUiraTHEqy2PvTqO # ougTl0lf4v4NB1CHWDTbs6IT4/hMwXM4/pP1ztXvdWeJxKUUTKb9SSOlmjkdT/Ou # kZqDr/aonWxvQs6t3HeauNkiIIq21pVAIDUoDr338hTK4/EPhxOwaTpZ0b2RATA6 # ldpcS7VNfsMe8aJI3nsRaRz5NkWNDnQgejGkIxxXo3xj8c/rhZMyqrrqYaqFleVW # 0ahh6eY0qxc+Z7HJ+SxU8oDUzNjOw+14NeUlHTd+qRnZVasWXZlB7wYTxlbLKCHP # KtbAm8KsdWrKokMkupRCHiI0je3QXlhX3TGEUS5HHcknjhvmkEzCcEYy0gYuyLRq # +e79xdC/IyylZvzM/SXQXWEtb3GmBhi5pQmcRftTgISNxryXFfYXeOOQhgvJQS2L # 8/Ul/rIEvhecj1me/wzOK1bDGzFae8xYSM2z7v/EAm4I59N8N8aomnN3sHeaeLlG # UwWGpq9Z3igoWaM88/h8EktA0Kk8s9YBXZoKvGwVQPglEqEeWEwvrGKEM2Le7kYF # eHM+osrJFf2iD42v6AnYVARhIA== # =1pl1 # -----END PGP SIGNATURE----- # gpg: Signature made Mon 03 Feb 2025 12:18:36 EST # gpg: using RSA key 87A9BD933F87C606D276F62DDAE8E10975969CE5 # gpg: issuer "marcandre.lureau@redhat.com" # gpg: Good signature from "Marc-André Lureau <marcandre.lureau@redhat.com>" [full] # gpg: aka "Marc-André Lureau <marcandre.lureau@gmail.com>" [full] # Primary key fingerprint: 87A9 BD93 3F87 C606 D276 F62D DAE8 E109 7596 9CE5 * tag 'ui-pull-request' of https://gitlab.com/marcandre.lureau/qemu: dbus: add -audio dbus nsamples option plugins: fix -Werror=maybe-uninitialized false-positive ui/dbus: clarify the kind of win32 handle that is shared ui/dbus: on win32, allow ANONYMOUS with p2p qemu-options.hx: describe hub chardev and aggregation of several backends tests/unit/test-char: add unit tests for hub chardev backend chardev/char-hub: implement backend chardev aggregator chardev/char-pty: send CHR_EVENT_CLOSED on disconnect Signed-off-by: Stefan Hajnoczi <stefanha@redhat.com>
This commit is contained in:
commit
d922088eb4
@ -43,9 +43,10 @@
|
||||
|
||||
#define DBUS_DISPLAY1_AUDIO_PATH DBUS_DISPLAY1_ROOT "/Audio"
|
||||
|
||||
#define DBUS_AUDIO_NSAMPLES 1024 /* could be configured? */
|
||||
#define DBUS_DEFAULT_AUDIO_NSAMPLES 480
|
||||
|
||||
typedef struct DBusAudio {
|
||||
Audiodev *dev;
|
||||
GDBusObjectManagerServer *server;
|
||||
bool p2p;
|
||||
GDBusObjectSkeleton *audio;
|
||||
@ -151,6 +152,18 @@ dbus_init_out_listener(QemuDBusDisplay1AudioOutListener *listener,
|
||||
G_DBUS_CALL_FLAGS_NONE, -1, NULL, NULL, NULL);
|
||||
}
|
||||
|
||||
static guint
|
||||
dbus_audio_get_nsamples(DBusAudio *da)
|
||||
{
|
||||
AudiodevDBusOptions *opts = &da->dev->u.dbus;
|
||||
|
||||
if (opts->has_nsamples && opts->nsamples) {
|
||||
return opts->nsamples;
|
||||
} else {
|
||||
return DBUS_DEFAULT_AUDIO_NSAMPLES;
|
||||
}
|
||||
}
|
||||
|
||||
static int
|
||||
dbus_init_out(HWVoiceOut *hw, struct audsettings *as, void *drv_opaque)
|
||||
{
|
||||
@ -160,7 +173,7 @@ dbus_init_out(HWVoiceOut *hw, struct audsettings *as, void *drv_opaque)
|
||||
QemuDBusDisplay1AudioOutListener *listener = NULL;
|
||||
|
||||
audio_pcm_init_info(&hw->info, as);
|
||||
hw->samples = DBUS_AUDIO_NSAMPLES;
|
||||
hw->samples = dbus_audio_get_nsamples(da);
|
||||
audio_rate_start(&vo->rate);
|
||||
|
||||
g_hash_table_iter_init(&iter, da->out_listeners);
|
||||
@ -274,7 +287,7 @@ dbus_init_in(HWVoiceIn *hw, struct audsettings *as, void *drv_opaque)
|
||||
QemuDBusDisplay1AudioInListener *listener = NULL;
|
||||
|
||||
audio_pcm_init_info(&hw->info, as);
|
||||
hw->samples = DBUS_AUDIO_NSAMPLES;
|
||||
hw->samples = dbus_audio_get_nsamples(da);
|
||||
audio_rate_start(&vo->rate);
|
||||
|
||||
g_hash_table_iter_init(&iter, da->in_listeners);
|
||||
@ -399,6 +412,7 @@ dbus_audio_init(Audiodev *dev, Error **errp)
|
||||
{
|
||||
DBusAudio *da = g_new0(DBusAudio, 1);
|
||||
|
||||
da->dev = dev;
|
||||
da->out_listeners = g_hash_table_new_full(g_str_hash, g_str_equal,
|
||||
g_free, g_object_unref);
|
||||
da->in_listeners = g_hash_table_new_full(g_str_hash, g_str_equal,
|
||||
@ -524,11 +538,17 @@ dbus_audio_register_listener(AudioState *s,
|
||||
);
|
||||
}
|
||||
|
||||
GDBusConnectionFlags flags =
|
||||
G_DBUS_CONNECTION_FLAGS_AUTHENTICATION_SERVER;
|
||||
#ifdef WIN32
|
||||
flags |= G_DBUS_CONNECTION_FLAGS_AUTHENTICATION_ALLOW_ANONYMOUS;
|
||||
#endif
|
||||
|
||||
listener_conn =
|
||||
g_dbus_connection_new_sync(
|
||||
G_IO_STREAM(socket_conn),
|
||||
guid,
|
||||
G_DBUS_CONNECTION_FLAGS_AUTHENTICATION_SERVER,
|
||||
flags,
|
||||
NULL, NULL, &err);
|
||||
if (err) {
|
||||
error_report("Failed to setup peer connection: %s", err->message);
|
||||
@ -646,6 +666,7 @@ dbus_audio_set_server(AudioState *s, GDBusObjectManagerServer *server, bool p2p)
|
||||
"swapped-signal::handle-register-out-listener",
|
||||
dbus_audio_register_out_listener, s,
|
||||
NULL);
|
||||
qemu_dbus_display1_audio_set_nsamples(da->iface, dbus_audio_get_nsamples(da));
|
||||
|
||||
g_dbus_object_skeleton_add_interface(G_DBUS_OBJECT_SKELETON(da->audio),
|
||||
G_DBUS_INTERFACE_SKELETON(da->iface));
|
||||
|
301
chardev/char-hub.c
Normal file
301
chardev/char-hub.c
Normal file
@ -0,0 +1,301 @@
|
||||
/*
|
||||
* QEMU Character Hub Device
|
||||
*
|
||||
* 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/option.h"
|
||||
#include "chardev/char.h"
|
||||
#include "chardev-internal.h"
|
||||
|
||||
/*
|
||||
* Character hub device aggregates input from multiple backend devices
|
||||
* and forwards it to a single frontend device. Additionally, hub
|
||||
* device takes the output from the frontend device and sends it back
|
||||
* to all the connected 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.
|
||||
* Called with chr_write_lock held.
|
||||
*/
|
||||
static int hub_chr_write(Chardev *chr, const uint8_t *buf, int len)
|
||||
{
|
||||
HubChardev *d = HUB_CHARDEV(chr);
|
||||
int r, i, ret = len;
|
||||
unsigned int written;
|
||||
|
||||
/* Invalidate index on every write */
|
||||
d->be_eagain_ind = -1;
|
||||
|
||||
for (i = 0; i < d->be_cnt; i++) {
|
||||
if (!d->backends[i].be.chr->be_open) {
|
||||
/* Skip closed backend */
|
||||
continue;
|
||||
}
|
||||
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].be, buf, len);
|
||||
if (r < 0) {
|
||||
if (errno == EAGAIN) {
|
||||
/* Set index and expect to be called soon on watch wake up */
|
||||
d->be_eagain_ind = i;
|
||||
}
|
||||
return r;
|
||||
}
|
||||
d->be_written[i] += r;
|
||||
ret = MIN(r, ret);
|
||||
}
|
||||
d->be_min_written += ret;
|
||||
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int hub_chr_can_read(void *opaque)
|
||||
{
|
||||
HubCharBackend *backend = opaque;
|
||||
CharBackend *fe = backend->hub->parent.be;
|
||||
|
||||
if (fe && fe->chr_can_read) {
|
||||
return fe->chr_can_read(fe->opaque);
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void hub_chr_read(void *opaque, const uint8_t *buf, int size)
|
||||
{
|
||||
HubCharBackend *backend = opaque;
|
||||
CharBackend *fe = backend->hub->parent.be;
|
||||
|
||||
if (fe && fe->chr_read) {
|
||||
fe->chr_read(fe->opaque, buf, size);
|
||||
}
|
||||
}
|
||||
|
||||
static void hub_chr_event(void *opaque, QEMUChrEvent event)
|
||||
{
|
||||
HubCharBackend *backend = opaque;
|
||||
HubChardev *d = backend->hub;
|
||||
CharBackend *fe = d->parent.be;
|
||||
|
||||
if (event == CHR_EVENT_OPENED) {
|
||||
/*
|
||||
* Catch up with what was already written while this backend
|
||||
* was closed
|
||||
*/
|
||||
d->be_written[backend->be_ind] = d->be_min_written;
|
||||
|
||||
if (d->be_event_opened_cnt++) {
|
||||
/* Ignore subsequent open events from other backends */
|
||||
return;
|
||||
}
|
||||
} else if (event == CHR_EVENT_CLOSED) {
|
||||
if (!d->be_event_opened_cnt) {
|
||||
/* Don't go below zero. Probably assert is better */
|
||||
return;
|
||||
}
|
||||
if (--d->be_event_opened_cnt) {
|
||||
/* Serve only the last one close event */
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if (fe && fe->chr_event) {
|
||||
fe->chr_event(fe->opaque, event);
|
||||
}
|
||||
}
|
||||
|
||||
static GSource *hub_chr_add_watch(Chardev *s, GIOCondition cond)
|
||||
{
|
||||
HubChardev *d = HUB_CHARDEV(s);
|
||||
Chardev *chr;
|
||||
ChardevClass *cc;
|
||||
|
||||
if (d->be_eagain_ind == -1) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
assert(d->be_eagain_ind < d->be_cnt);
|
||||
chr = qemu_chr_fe_get_driver(&d->backends[d->be_eagain_ind].be);
|
||||
cc = CHARDEV_GET_CLASS(chr);
|
||||
if (!cc->chr_add_watch) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
return cc->chr_add_watch(chr, cond);
|
||||
}
|
||||
|
||||
static bool hub_chr_attach_chardev(HubChardev *d, Chardev *chr,
|
||||
Error **errp)
|
||||
{
|
||||
bool ret;
|
||||
|
||||
if (d->be_cnt >= MAX_HUB) {
|
||||
error_setg(errp, "hub: too many uses of chardevs '%s'"
|
||||
" (maximum is " stringify(MAX_HUB) ")",
|
||||
d->parent.label);
|
||||
return false;
|
||||
}
|
||||
ret = qemu_chr_fe_init(&d->backends[d->be_cnt].be, chr, errp);
|
||||
if (ret) {
|
||||
d->backends[d->be_cnt].hub = d;
|
||||
d->backends[d->be_cnt].be_ind = d->be_cnt;
|
||||
d->be_cnt += 1;
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static void char_hub_finalize(Object *obj)
|
||||
{
|
||||
HubChardev *d = HUB_CHARDEV(obj);
|
||||
int i;
|
||||
|
||||
for (i = 0; i < d->be_cnt; i++) {
|
||||
qemu_chr_fe_deinit(&d->backends[i].be, false);
|
||||
}
|
||||
}
|
||||
|
||||
static void hub_chr_update_read_handlers(Chardev *chr)
|
||||
{
|
||||
HubChardev *d = HUB_CHARDEV(chr);
|
||||
int i;
|
||||
|
||||
for (i = 0; i < d->be_cnt; i++) {
|
||||
qemu_chr_fe_set_handlers_full(&d->backends[i].be,
|
||||
hub_chr_can_read,
|
||||
hub_chr_read,
|
||||
hub_chr_event,
|
||||
NULL,
|
||||
&d->backends[i],
|
||||
chr->gcontext, true, false);
|
||||
}
|
||||
}
|
||||
|
||||
static void qemu_chr_open_hub(Chardev *chr,
|
||||
ChardevBackend *backend,
|
||||
bool *be_opened,
|
||||
Error **errp)
|
||||
{
|
||||
ChardevHub *hub = backend->u.hub.data;
|
||||
HubChardev *d = HUB_CHARDEV(chr);
|
||||
strList *list = hub->chardevs;
|
||||
|
||||
d->be_eagain_ind = -1;
|
||||
|
||||
if (list == NULL) {
|
||||
error_setg(errp, "hub: 'chardevs' list is not defined");
|
||||
return;
|
||||
}
|
||||
|
||||
while (list) {
|
||||
Chardev *s;
|
||||
|
||||
s = qemu_chr_find(list->value);
|
||||
if (s == NULL) {
|
||||
error_setg(errp, "hub: chardev can't be found by id '%s'",
|
||||
list->value);
|
||||
return;
|
||||
}
|
||||
if (CHARDEV_IS_HUB(s) || CHARDEV_IS_MUX(s)) {
|
||||
error_setg(errp, "hub: multiplexers and hub devices can't be "
|
||||
"stacked, check chardev '%s', chardev should not "
|
||||
"be a hub device or have 'mux=on' enabled",
|
||||
list->value);
|
||||
return;
|
||||
}
|
||||
if (!hub_chr_attach_chardev(d, s, errp)) {
|
||||
return;
|
||||
}
|
||||
list = list->next;
|
||||
}
|
||||
|
||||
/* Closed until an explicit event from backend */
|
||||
*be_opened = false;
|
||||
}
|
||||
|
||||
static void qemu_chr_parse_hub(QemuOpts *opts, ChardevBackend *backend,
|
||||
Error **errp)
|
||||
{
|
||||
ChardevHub *hub;
|
||||
strList **tail;
|
||||
int i;
|
||||
|
||||
backend->type = CHARDEV_BACKEND_KIND_HUB;
|
||||
hub = backend->u.hub.data = g_new0(ChardevHub, 1);
|
||||
qemu_chr_parse_common(opts, qapi_ChardevHub_base(hub));
|
||||
|
||||
tail = &hub->chardevs;
|
||||
|
||||
for (i = 0; i < MAX_HUB; i++) {
|
||||
char optbuf[16];
|
||||
const char *dev;
|
||||
|
||||
snprintf(optbuf, sizeof(optbuf), "chardevs.%u", i);
|
||||
dev = qemu_opt_get(opts, optbuf);
|
||||
if (!dev) {
|
||||
break;
|
||||
}
|
||||
|
||||
QAPI_LIST_APPEND(tail, g_strdup(dev));
|
||||
}
|
||||
}
|
||||
|
||||
static void char_hub_class_init(ObjectClass *oc, void *data)
|
||||
{
|
||||
ChardevClass *cc = CHARDEV_CLASS(oc);
|
||||
|
||||
cc->parse = qemu_chr_parse_hub;
|
||||
cc->open = qemu_chr_open_hub;
|
||||
cc->chr_write = hub_chr_write;
|
||||
cc->chr_add_watch = hub_chr_add_watch;
|
||||
/* We handle events from backends only */
|
||||
cc->chr_be_event = NULL;
|
||||
cc->chr_update_read_handler = hub_chr_update_read_handlers;
|
||||
}
|
||||
|
||||
static const TypeInfo char_hub_type_info = {
|
||||
.name = TYPE_CHARDEV_HUB,
|
||||
.parent = TYPE_CHARDEV,
|
||||
.class_init = char_hub_class_init,
|
||||
.instance_size = sizeof(HubChardev),
|
||||
.instance_finalize = char_hub_finalize,
|
||||
};
|
||||
|
||||
static void register_types(void)
|
||||
{
|
||||
type_register_static(&char_hub_type_info);
|
||||
}
|
||||
|
||||
type_init(register_types);
|
@ -181,6 +181,9 @@ static void pty_chr_state(Chardev *chr, int connected)
|
||||
|
||||
if (!connected) {
|
||||
remove_fd_in_watch(chr);
|
||||
if (s->connected) {
|
||||
qemu_chr_be_event(chr, CHR_EVENT_CLOSED);
|
||||
}
|
||||
s->connected = 0;
|
||||
/* (re-)connect poll interval for idle guests: once per second.
|
||||
* We check more frequently in case the guests sends data to
|
||||
@ -215,7 +218,6 @@ static void char_pty_finalize(Object *obj)
|
||||
pty_chr_state(chr, 0);
|
||||
object_unref(OBJECT(s->ioc));
|
||||
pty_chr_timer_cancel(s);
|
||||
qemu_chr_be_event(chr, CHR_EVENT_CLOSED);
|
||||
}
|
||||
|
||||
#if defined HAVE_PTY_H
|
||||
|
@ -943,7 +943,26 @@ QemuOptsList qemu_chardev_opts = {
|
||||
},{
|
||||
.name = "chardev",
|
||||
.type = QEMU_OPT_STRING,
|
||||
},
|
||||
/*
|
||||
* Multiplexer options. Follows QAPI array syntax.
|
||||
* See MAX_HUB macro to obtain array capacity.
|
||||
*/
|
||||
{
|
||||
.name = "chardevs.0",
|
||||
.type = QEMU_OPT_STRING,
|
||||
},{
|
||||
.name = "chardevs.1",
|
||||
.type = QEMU_OPT_STRING,
|
||||
},{
|
||||
.name = "chardevs.2",
|
||||
.type = QEMU_OPT_STRING,
|
||||
},{
|
||||
.name = "chardevs.3",
|
||||
.type = QEMU_OPT_STRING,
|
||||
},
|
||||
|
||||
{
|
||||
.name = "append",
|
||||
.type = QEMU_OPT_BOOL,
|
||||
},{
|
||||
@ -1106,8 +1125,8 @@ ChardevReturn *qmp_chardev_change(const char *id, ChardevBackend *backend,
|
||||
return NULL;
|
||||
}
|
||||
|
||||
if (CHARDEV_IS_MUX(chr)) {
|
||||
error_setg(errp, "Mux device hotswap not supported yet");
|
||||
if (CHARDEV_IS_MUX(chr) || CHARDEV_IS_HUB(chr)) {
|
||||
error_setg(errp, "For mux or hub device hotswap is not supported yet");
|
||||
return NULL;
|
||||
}
|
||||
|
||||
|
@ -29,13 +29,16 @@
|
||||
#include "chardev/char-fe.h"
|
||||
#include "qom/object.h"
|
||||
|
||||
#define MAX_HUB 4
|
||||
#define MAX_MUX 4
|
||||
#define MUX_BUFFER_SIZE 32 /* Must be a power of 2. */
|
||||
#define MUX_BUFFER_MASK (MUX_BUFFER_SIZE - 1)
|
||||
|
||||
struct MuxChardev {
|
||||
Chardev parent;
|
||||
/* Linked frontends */
|
||||
CharBackend *backends[MAX_MUX];
|
||||
/* Linked backend */
|
||||
CharBackend chr;
|
||||
unsigned long mux_bitset;
|
||||
int focus;
|
||||
@ -53,11 +56,57 @@ struct MuxChardev {
|
||||
int64_t timestamps_start;
|
||||
};
|
||||
typedef struct MuxChardev MuxChardev;
|
||||
typedef struct HubChardev HubChardev;
|
||||
typedef struct HubCharBackend HubCharBackend;
|
||||
|
||||
/*
|
||||
* Back-pointer on a hub, actual backend and its index in
|
||||
* `hub->backends` array
|
||||
*/
|
||||
struct HubCharBackend {
|
||||
HubChardev *hub;
|
||||
CharBackend be;
|
||||
unsigned int be_ind;
|
||||
};
|
||||
|
||||
struct HubChardev {
|
||||
Chardev parent;
|
||||
/* Linked backends */
|
||||
HubCharBackend backends[MAX_HUB];
|
||||
/*
|
||||
* Number of backends attached to this hub. Once attached, a
|
||||
* backend can't be detached, so the counter is only increasing.
|
||||
* To safely remove a backend, hub has to be removed first.
|
||||
*/
|
||||
unsigned int be_cnt;
|
||||
/*
|
||||
* Number of CHR_EVEN_OPENED events from all backends. Needed to
|
||||
* send CHR_EVEN_CLOSED only when counter goes to zero.
|
||||
*/
|
||||
unsigned int be_event_opened_cnt;
|
||||
/*
|
||||
* Counters of written bytes from a single frontend device
|
||||
* to multiple backend devices.
|
||||
*/
|
||||
unsigned int be_written[MAX_HUB];
|
||||
unsigned int be_min_written;
|
||||
/*
|
||||
* Index of a backend device which got EAGAIN on last write,
|
||||
* -1 is invalid index.
|
||||
*/
|
||||
int be_eagain_ind;
|
||||
};
|
||||
typedef struct HubChardev HubChardev;
|
||||
|
||||
DECLARE_INSTANCE_CHECKER(MuxChardev, MUX_CHARDEV,
|
||||
TYPE_CHARDEV_MUX)
|
||||
DECLARE_INSTANCE_CHECKER(HubChardev, HUB_CHARDEV,
|
||||
TYPE_CHARDEV_HUB)
|
||||
|
||||
#define CHARDEV_IS_MUX(chr) \
|
||||
object_dynamic_cast(OBJECT(chr), TYPE_CHARDEV_MUX)
|
||||
#define CHARDEV_IS_HUB(chr) \
|
||||
object_dynamic_cast(OBJECT(chr), TYPE_CHARDEV_HUB)
|
||||
|
||||
bool mux_chr_attach_frontend(MuxChardev *d, CharBackend *b,
|
||||
unsigned int *tag, Error **errp);
|
||||
|
@ -3,6 +3,7 @@ chardev_ss.add(files(
|
||||
'char-file.c',
|
||||
'char-io.c',
|
||||
'char-mux.c',
|
||||
'char-hub.c',
|
||||
'char-null.c',
|
||||
'char-pipe.c',
|
||||
'char-ringbuf.c',
|
||||
|
@ -603,7 +603,7 @@ static int l2_cmp(gconstpointer a, gconstpointer b)
|
||||
static void log_stats(void)
|
||||
{
|
||||
int i;
|
||||
Cache *icache, *dcache, *l2_cache;
|
||||
Cache *icache, *dcache, *l2_cache = NULL;
|
||||
|
||||
g_autoptr(GString) rep = g_string_new("core #, data accesses, data misses,"
|
||||
" dmiss rate, insn accesses,"
|
||||
|
@ -232,6 +232,7 @@ OBJECT_DECLARE_TYPE(Chardev, ChardevClass, CHARDEV)
|
||||
|
||||
#define TYPE_CHARDEV_NULL "chardev-null"
|
||||
#define TYPE_CHARDEV_MUX "chardev-mux"
|
||||
#define TYPE_CHARDEV_HUB "chardev-hub"
|
||||
#define TYPE_CHARDEV_RINGBUF "chardev-ringbuf"
|
||||
#define TYPE_CHARDEV_PTY "chardev-pty"
|
||||
#define TYPE_CHARDEV_CONSOLE "chardev-console"
|
||||
|
@ -65,6 +65,26 @@
|
||||
'*in': 'AudiodevPerDirectionOptions',
|
||||
'*out': 'AudiodevPerDirectionOptions' } }
|
||||
|
||||
##
|
||||
# @AudiodevDBusOptions:
|
||||
#
|
||||
# Options of the D-Bus audio backend.
|
||||
#
|
||||
# @in: options of the capture stream
|
||||
#
|
||||
# @out: options of the playback stream
|
||||
#
|
||||
# @nsamples: set the number of samples per read/write calls (default to 480,
|
||||
# 10ms at 48kHz).
|
||||
#
|
||||
# Since: 10.0
|
||||
##
|
||||
{ 'struct': 'AudiodevDBusOptions',
|
||||
'data': {
|
||||
'*in': 'AudiodevPerDirectionOptions',
|
||||
'*out': 'AudiodevPerDirectionOptions',
|
||||
'*nsamples': 'uint32'} }
|
||||
|
||||
##
|
||||
# @AudiodevAlsaPerDirectionOptions:
|
||||
#
|
||||
@ -490,7 +510,7 @@
|
||||
'if': 'CONFIG_AUDIO_ALSA' },
|
||||
'coreaudio': { 'type': 'AudiodevCoreaudioOptions',
|
||||
'if': 'CONFIG_AUDIO_COREAUDIO' },
|
||||
'dbus': { 'type': 'AudiodevGenericOptions',
|
||||
'dbus': { 'type': 'AudiodevDBusOptions',
|
||||
'if': 'CONFIG_DBUS_DISPLAY' },
|
||||
'dsound': { 'type': 'AudiodevDsoundOptions',
|
||||
'if': 'CONFIG_AUDIO_DSOUND' },
|
||||
|
@ -332,6 +332,19 @@
|
||||
'data': { 'chardev': 'str' },
|
||||
'base': 'ChardevCommon' }
|
||||
|
||||
##
|
||||
# @ChardevHub:
|
||||
#
|
||||
# Configuration info for hub chardevs.
|
||||
#
|
||||
# @chardevs: List of chardev IDs, which should be added to this hub
|
||||
#
|
||||
# Since: 10.0
|
||||
##
|
||||
{ 'struct': 'ChardevHub',
|
||||
'data': { 'chardevs': ['str'] },
|
||||
'base': 'ChardevCommon' }
|
||||
|
||||
##
|
||||
# @ChardevStdio:
|
||||
#
|
||||
@ -479,6 +492,8 @@
|
||||
#
|
||||
# @mux: (since 1.5)
|
||||
#
|
||||
# @hub: (since 10.0)
|
||||
#
|
||||
# @msmouse: emulated Microsoft serial mouse (since 1.5)
|
||||
#
|
||||
# @wctablet: emulated Wacom Penpartner serial tablet (since 2.9)
|
||||
@ -521,6 +536,7 @@
|
||||
'pty',
|
||||
'null',
|
||||
'mux',
|
||||
'hub',
|
||||
'msmouse',
|
||||
'wctablet',
|
||||
{ 'name': 'braille', 'if': 'CONFIG_BRLAPI' },
|
||||
@ -595,6 +611,16 @@
|
||||
{ 'struct': 'ChardevMuxWrapper',
|
||||
'data': { 'data': 'ChardevMux' } }
|
||||
|
||||
##
|
||||
# @ChardevHubWrapper:
|
||||
#
|
||||
# @data: Configuration info for hub chardevs
|
||||
#
|
||||
# Since: 10.0
|
||||
##
|
||||
{ 'struct': 'ChardevHubWrapper',
|
||||
'data': { 'data': 'ChardevHub' } }
|
||||
|
||||
##
|
||||
# @ChardevStdioWrapper:
|
||||
#
|
||||
@ -703,6 +729,7 @@
|
||||
'pty': 'ChardevPtyWrapper',
|
||||
'null': 'ChardevCommonWrapper',
|
||||
'mux': 'ChardevMuxWrapper',
|
||||
'hub': 'ChardevHubWrapper',
|
||||
'msmouse': 'ChardevCommonWrapper',
|
||||
'wctablet': 'ChardevCommonWrapper',
|
||||
'braille': { 'type': 'ChardevCommonWrapper',
|
||||
|
@ -3733,7 +3733,7 @@ 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``,
|
||||
Backend is one of: ``null``, ``socket``, ``udp``, ``msmouse``, ``hub``,
|
||||
``vc``, ``ringbuf``, ``file``, ``pipe``, ``console``, ``serial``,
|
||||
``pty``, ``stdio``, ``braille``, ``parallel``,
|
||||
``spicevmc``, ``spiceport``. The specific backend will determine the
|
||||
@ -3790,9 +3790,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 aggregate data in the opposite direction (where one
|
||||
QEMU frontend interface receives input and output from multiple
|
||||
backend chardev devices), please refer to the paragraph below
|
||||
regarding chardev ``hub`` aggregator device configuration.
|
||||
|
||||
Every backend supports the ``logfile`` option, which supplies the
|
||||
path to a file to record all data transmitted via the backend. The
|
||||
@ -3892,6 +3893,46 @@ The available backends are:
|
||||
Forward QEMU's emulated msmouse events to the guest. ``msmouse``
|
||||
does not take any options.
|
||||
|
||||
``-chardev hub,id=id,chardevs.0=id[,chardevs.N=id]``
|
||||
Explicitly create chardev backend hub device with the possibility
|
||||
to aggregate input from multiple backend devices and forward it to
|
||||
a single frontend device. Additionally, ``hub`` device takes the
|
||||
output from the frontend device and sends it back to all the
|
||||
connected backend devices. This allows for seamless interaction
|
||||
between different backend devices and a single frontend
|
||||
interface. Aggregation supported for up to 4 chardev
|
||||
devices. (Since 10.0)
|
||||
|
||||
For example, the following is a use case of 2 backend devices:
|
||||
virtual console ``vc0`` and a pseudo TTY ``pty0`` connected to
|
||||
a single virtio hvc console frontend device with a hub ``hub0``
|
||||
help. Virtual console renders text to an image, which can be
|
||||
shared over the VNC protocol. In turn, pty backend provides
|
||||
bidirectional communication to the virtio hvc console over the
|
||||
pseudo TTY file. The example configuration can be as follows:
|
||||
|
||||
::
|
||||
|
||||
-chardev pty,path=/tmp/pty,id=pty0 \
|
||||
-chardev vc,id=vc0 \
|
||||
-chardev hub,id=hub0,chardevs.0=pty0,chardevs.1=vc0 \
|
||||
-device virtconsole,chardev=hub0 \
|
||||
-vnc 0.0.0.0:0
|
||||
|
||||
Once QEMU starts VNC client and any TTY emulator can be used to
|
||||
control a single hvc console:
|
||||
|
||||
::
|
||||
|
||||
# Start TTY emulator
|
||||
tio /tmp/pty
|
||||
|
||||
# Start VNC client and switch to virtual console Ctrl-Alt-2
|
||||
vncviewer :0
|
||||
|
||||
Several frontend devices is not supported. Stacking of multiplexers
|
||||
and hub devices is not supported as well.
|
||||
|
||||
``-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.
|
||||
|
@ -359,6 +359,403 @@ static void char_mux_test(void)
|
||||
qmp_chardev_remove("mux-label", &error_abort);
|
||||
}
|
||||
|
||||
static void char_hub_test(void)
|
||||
{
|
||||
QemuOpts *opts;
|
||||
Chardev *hub, *chr1, *chr2, *base;
|
||||
char *data;
|
||||
FeHandler h = { 0, false, 0, false, };
|
||||
Error *error = NULL;
|
||||
CharBackend chr_be;
|
||||
int ret, i;
|
||||
|
||||
#define RB_SIZE 128
|
||||
|
||||
/*
|
||||
* Create invalid hub
|
||||
* 1. Create hub without a 'chardevs.N' defined (expect error)
|
||||
*/
|
||||
opts = qemu_opts_create(qemu_find_opts("chardev"), "hub0",
|
||||
1, &error_abort);
|
||||
qemu_opt_set(opts, "backend", "hub", &error_abort);
|
||||
hub = qemu_chr_new_from_opts(opts, NULL, &error);
|
||||
g_assert_cmpstr(error_get_pretty(error), ==,
|
||||
"hub: 'chardevs' list is not defined");
|
||||
error_free(error);
|
||||
error = NULL;
|
||||
qemu_opts_del(opts);
|
||||
|
||||
/*
|
||||
* Create invalid hub
|
||||
* 1. Create chardev with embedded mux: 'mux=on'
|
||||
* 2. Create hub which refers mux
|
||||
* 3. Create hub which refers chardev already attached
|
||||
* to the mux (already in use, expect error)
|
||||
*/
|
||||
opts = qemu_opts_create(qemu_find_opts("chardev"), "chr0",
|
||||
1, &error_abort);
|
||||
qemu_opt_set(opts, "mux", "on", &error_abort);
|
||||
qemu_opt_set(opts, "backend", "ringbuf", &error_abort);
|
||||
qemu_opt_set(opts, "size", stringify(RB_SIZE), &error_abort);
|
||||
base = qemu_chr_new_from_opts(opts, NULL, &error_abort);
|
||||
g_assert_nonnull(base);
|
||||
qemu_opts_del(opts);
|
||||
|
||||
opts = qemu_opts_create(qemu_find_opts("chardev"), "hub0",
|
||||
1, &error_abort);
|
||||
qemu_opt_set(opts, "backend", "hub", &error_abort);
|
||||
qemu_opt_set(opts, "chardevs.0", "chr0", &error_abort);
|
||||
hub = qemu_chr_new_from_opts(opts, NULL, &error);
|
||||
g_assert_cmpstr(error_get_pretty(error), ==,
|
||||
"hub: multiplexers and hub devices can't be "
|
||||
"stacked, check chardev 'chr0', chardev should "
|
||||
"not be a hub device or have 'mux=on' enabled");
|
||||
error_free(error);
|
||||
error = NULL;
|
||||
qemu_opts_del(opts);
|
||||
|
||||
opts = qemu_opts_create(qemu_find_opts("chardev"), "hub0",
|
||||
1, &error_abort);
|
||||
qemu_opt_set(opts, "backend", "hub", &error_abort);
|
||||
qemu_opt_set(opts, "chardevs.0", "chr0-base", &error_abort);
|
||||
hub = qemu_chr_new_from_opts(opts, NULL, &error);
|
||||
g_assert_cmpstr(error_get_pretty(error), ==,
|
||||
"chardev 'chr0-base' is already in use");
|
||||
error_free(error);
|
||||
error = NULL;
|
||||
qemu_opts_del(opts);
|
||||
|
||||
/* Finalize chr0 */
|
||||
qmp_chardev_remove("chr0", &error_abort);
|
||||
|
||||
/*
|
||||
* Create invalid hub with more than maximum allowed backends
|
||||
* 1. Create more than maximum allowed 'chardevs.%d' options for
|
||||
* hub (expect error)
|
||||
*/
|
||||
opts = qemu_opts_create(qemu_find_opts("chardev"), "hub0",
|
||||
1, &error_abort);
|
||||
for (i = 0; i < 10; i++) {
|
||||
char key[32], val[32];
|
||||
|
||||
snprintf(key, sizeof(key), "chardevs.%d", i);
|
||||
snprintf(val, sizeof(val), "chr%d", i);
|
||||
qemu_opt_set(opts, key, val, &error);
|
||||
if (error) {
|
||||
char buf[64];
|
||||
|
||||
snprintf(buf, sizeof(buf), "Invalid parameter 'chardevs.%d'", i);
|
||||
g_assert_cmpstr(error_get_pretty(error), ==, buf);
|
||||
error_free(error);
|
||||
break;
|
||||
}
|
||||
}
|
||||
g_assert_nonnull(error);
|
||||
error = NULL;
|
||||
qemu_opts_del(opts);
|
||||
|
||||
/*
|
||||
* Create hub with 2 backend chardevs and 1 frontend and perform
|
||||
* data aggregation
|
||||
* 1. Create 2 ringbuf backend chardevs
|
||||
* 2. Create 1 frontend
|
||||
* 3. Create hub which refers 2 backend chardevs
|
||||
* 4. Attach hub to a frontend
|
||||
* 5. Attach hub to a frontend second time (expect error)
|
||||
* 6. Perform data aggregation
|
||||
* 7. Remove chr1 ("chr1 is busy", expect error)
|
||||
* 8. Remove hub0 ("hub0 is busy", expect error);
|
||||
* 9. Finilize frontend, hub and backend chardevs in correct order
|
||||
*/
|
||||
|
||||
/* 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);
|
||||
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);
|
||||
chr2 = qemu_chr_new_from_opts(opts, NULL, &error_abort);
|
||||
g_assert_nonnull(chr2);
|
||||
qemu_opts_del(opts);
|
||||
|
||||
/* Create hub0 and refer 2 backend chardevs */
|
||||
opts = qemu_opts_create(qemu_find_opts("chardev"), "hub0",
|
||||
1, &error_abort);
|
||||
qemu_opt_set(opts, "backend", "hub", &error_abort);
|
||||
qemu_opt_set(opts, "chardevs.0", "chr1", &error_abort);
|
||||
qemu_opt_set(opts, "chardevs.1", "chr2", &error_abort);
|
||||
hub = qemu_chr_new_from_opts(opts, NULL, &error_abort);
|
||||
g_assert_nonnull(hub);
|
||||
qemu_opts_del(opts);
|
||||
|
||||
/* Attach hub to a frontend */
|
||||
qemu_chr_fe_init(&chr_be, hub, &error_abort);
|
||||
qemu_chr_fe_set_handlers(&chr_be,
|
||||
fe_can_read,
|
||||
fe_read,
|
||||
fe_event,
|
||||
NULL,
|
||||
&h,
|
||||
NULL, true);
|
||||
|
||||
/* Fails second time */
|
||||
qemu_chr_fe_init(&chr_be, hub, &error);
|
||||
g_assert_cmpstr(error_get_pretty(error), ==, "chardev 'hub0' is already in use");
|
||||
error_free(error);
|
||||
error = NULL;
|
||||
|
||||
/* 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);
|
||||
|
||||
/* Can't be removed, depends on hub0 */
|
||||
qmp_chardev_remove("chr1", &error);
|
||||
g_assert_cmpstr(error_get_pretty(error), ==, "Chardev 'chr1' is busy");
|
||||
error_free(error);
|
||||
error = NULL;
|
||||
|
||||
/* Can't be removed, depends on frontend chr_be */
|
||||
qmp_chardev_remove("hub0", &error);
|
||||
g_assert_cmpstr(error_get_pretty(error), ==, "Chardev 'hub0' is busy");
|
||||
error_free(error);
|
||||
error = NULL;
|
||||
|
||||
/* Finalize frontend */
|
||||
qemu_chr_fe_deinit(&chr_be, false);
|
||||
|
||||
/* Finalize hub0 */
|
||||
qmp_chardev_remove("hub0", &error_abort);
|
||||
|
||||
/* Finalize backend chardevs */
|
||||
qmp_chardev_remove("chr1", &error_abort);
|
||||
qmp_chardev_remove("chr2", &error_abort);
|
||||
|
||||
#ifndef _WIN32
|
||||
/*
|
||||
* Create 3 backend chardevs to simulate EAGAIN and watcher.
|
||||
* Mainly copied from char_pipe_test().
|
||||
* 1. Create 2 ringbuf backend chardevs
|
||||
* 2. Create 1 pipe backend chardev
|
||||
* 3. Create 1 frontend
|
||||
* 4. Create hub which refers 2 backend chardevs
|
||||
* 5. Attach hub to a frontend
|
||||
* 6. Perform data aggregation and check watcher
|
||||
* 7. Finilize frontend, hub and backend chardevs in correct order
|
||||
*/
|
||||
{
|
||||
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();
|
||||
}
|
||||
|
||||
/* 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);
|
||||
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);
|
||||
chr2 = qemu_chr_new_from_opts(opts, NULL, &error_abort);
|
||||
g_assert_nonnull(chr2);
|
||||
qemu_opts_del(opts);
|
||||
|
||||
/* Create third chardev */
|
||||
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);
|
||||
chr3 = qemu_chr_new_from_opts(opts, NULL, &error_abort);
|
||||
g_assert_nonnull(chr3);
|
||||
|
||||
/* Create hub0 and refer 3 backend chardevs */
|
||||
opts = qemu_opts_create(qemu_find_opts("chardev"), "hub0",
|
||||
1, &error_abort);
|
||||
qemu_opt_set(opts, "backend", "hub", &error_abort);
|
||||
qemu_opt_set(opts, "chardevs.0", "chr1", &error_abort);
|
||||
qemu_opt_set(opts, "chardevs.1", "chr2", &error_abort);
|
||||
qemu_opt_set(opts, "chardevs.2", "chr3", &error_abort);
|
||||
hub = qemu_chr_new_from_opts(opts, NULL, &error_abort);
|
||||
g_assert_nonnull(hub);
|
||||
qemu_opts_del(opts);
|
||||
|
||||
/* Attach hub to a frontend */
|
||||
qemu_chr_fe_init(&chr_be, hub, &error_abort);
|
||||
qemu_chr_fe_set_handlers(&chr_be,
|
||||
fe_can_read,
|
||||
fe_read,
|
||||
fe_event,
|
||||
NULL,
|
||||
&h,
|
||||
NULL, true);
|
||||
|
||||
/* 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);
|
||||
|
||||
g_free(in);
|
||||
g_free(out);
|
||||
g_free(tmp_path);
|
||||
g_free(pipe);
|
||||
|
||||
/* Finalize frontend */
|
||||
qemu_chr_fe_deinit(&chr_be, false);
|
||||
|
||||
/* Finalize hub0 */
|
||||
qmp_chardev_remove("hub0", &error_abort);
|
||||
|
||||
/* Finalize backend chardevs */
|
||||
qmp_chardev_remove("chr1", &error_abort);
|
||||
qmp_chardev_remove("chr2", &error_abort);
|
||||
qmp_chardev_remove("chr3", &error_abort);
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
static void websock_server_read(void *opaque, const uint8_t *buf, int size)
|
||||
{
|
||||
@ -1507,6 +1904,7 @@ int main(int argc, char **argv)
|
||||
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/hub", char_hub_test);
|
||||
#ifdef _WIN32
|
||||
g_test_add_func("/char/console/subprocess", char_console_test_subprocess);
|
||||
g_test_add_func("/char/console", char_console_test);
|
||||
|
@ -305,10 +305,16 @@ dbus_console_register_listener(DBusDisplayConsole *ddc,
|
||||
#endif
|
||||
);
|
||||
|
||||
GDBusConnectionFlags flags =
|
||||
G_DBUS_CONNECTION_FLAGS_AUTHENTICATION_SERVER;
|
||||
#ifdef WIN32
|
||||
flags |= G_DBUS_CONNECTION_FLAGS_AUTHENTICATION_ALLOW_ANONYMOUS;
|
||||
#endif
|
||||
|
||||
listener_conn = g_dbus_connection_new_sync(
|
||||
G_IO_STREAM(socket_conn),
|
||||
guid,
|
||||
G_DBUS_CONNECTION_FLAGS_AUTHENTICATION_SERVER,
|
||||
flags,
|
||||
NULL, NULL, &err);
|
||||
if (err) {
|
||||
error_report("Failed to setup peer connection: %s", err->message);
|
||||
|
@ -527,14 +527,14 @@
|
||||
<interface name="org.qemu.Display1.Listener.Win32.Map">
|
||||
<!--
|
||||
ScanoutMap:
|
||||
@handle: the shared map handle value.
|
||||
@handle: the shared file mapping handle value (not a file handle)
|
||||
@offset: mapping offset.
|
||||
@width: display width, in pixels.
|
||||
@height: display height, in pixels.
|
||||
@stride: stride, in bytes.
|
||||
@pixman_format: image format (ex: ``PIXMAN_X8R8G8B8``).
|
||||
|
||||
Resize and update the display content with a shared map.
|
||||
Resize and update the display content with a shared file mapping object.
|
||||
-->
|
||||
<method name="ScanoutMap">
|
||||
<arg type="t" name="handle" direction="in"/>
|
||||
@ -773,6 +773,18 @@
|
||||
<?endif?>
|
||||
</method>
|
||||
|
||||
<!--
|
||||
NSamples:
|
||||
|
||||
The number of samples per read/write frames. (for example the default is
|
||||
480, or 10ms at 48kHz)
|
||||
|
||||
(earlier version of the display interface do not provide this property)
|
||||
-->
|
||||
<property name="NSamples" type="u" access="read">
|
||||
<annotation name="org.freedesktop.DBus.Property.EmitsChangedSignal" value="const"/>
|
||||
</property>
|
||||
|
||||
<!--
|
||||
Interfaces:
|
||||
|
||||
|
10
ui/dbus.c
10
ui/dbus.c
@ -317,11 +317,17 @@ dbus_display_add_client(int csock, Error **errp)
|
||||
conn = g_socket_connection_factory_create_connection(socket);
|
||||
|
||||
dbus_display->add_client_cancellable = g_cancellable_new();
|
||||
GDBusConnectionFlags flags =
|
||||
G_DBUS_CONNECTION_FLAGS_AUTHENTICATION_SERVER |
|
||||
G_DBUS_CONNECTION_FLAGS_DELAY_MESSAGE_PROCESSING;
|
||||
|
||||
#ifdef WIN32
|
||||
flags |= G_DBUS_CONNECTION_FLAGS_AUTHENTICATION_ALLOW_ANONYMOUS;
|
||||
#endif
|
||||
|
||||
g_dbus_connection_new(G_IO_STREAM(conn),
|
||||
guid,
|
||||
G_DBUS_CONNECTION_FLAGS_AUTHENTICATION_SERVER |
|
||||
G_DBUS_CONNECTION_FLAGS_DELAY_MESSAGE_PROCESSING,
|
||||
flags,
|
||||
NULL,
|
||||
dbus_display->add_client_cancellable,
|
||||
dbus_display_add_client_ready,
|
||||
|
Loading…
x
Reference in New Issue
Block a user