mirror of
https://github.com/xemu-project/xemu.git
synced 2024-11-24 03:59:52 +00:00
8487ce45f8
We can call qemu_chr_fe_set_handlers() to add/remove fd been watched in 'context' which can be either default main context or other explicit context. But the original logic is not correct, we didn't remove the right fd because we call g_main_context_find_source_by_id(NULL, tag) which always try to find the Gsource from default context. Fix it by passing the right context to g_main_context_find_source_by_id(). Cc: Paolo Bonzini <pbonzini@redhat.com> Cc: Marc-André Lureau <marcandre.lureau@redhat.com> Signed-off-by: zhanghailiang <zhang.zhanghailiang@huawei.com> Reviewed-by: Marc-André Lureau <marcandre.lureau@redhat.com> Signed-off-by: Jason Wang <jasowang@redhat.com>
1336 lines
33 KiB
C
1336 lines
33 KiB
C
/*
|
|
* QEMU System Emulator
|
|
*
|
|
* Copyright (c) 2003-2008 Fabrice Bellard
|
|
*
|
|
* 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 "qemu-common.h"
|
|
#include "qemu/cutils.h"
|
|
#include "monitor/monitor.h"
|
|
#include "sysemu/sysemu.h"
|
|
#include "qemu/config-file.h"
|
|
#include "qemu/error-report.h"
|
|
#include "sysemu/char.h"
|
|
#include "qmp-commands.h"
|
|
#include "qapi-visit.h"
|
|
#include "sysemu/replay.h"
|
|
#include "qemu/help_option.h"
|
|
|
|
#include "char-mux.h"
|
|
#include "char-io.h"
|
|
#include "char-parallel.h"
|
|
#include "char-serial.h"
|
|
|
|
/***********************************************************/
|
|
/* character device */
|
|
|
|
static QTAILQ_HEAD(ChardevHead, Chardev) chardevs =
|
|
QTAILQ_HEAD_INITIALIZER(chardevs);
|
|
|
|
void qemu_chr_be_event(Chardev *s, int event)
|
|
{
|
|
CharBackend *be = s->be;
|
|
|
|
/* Keep track if the char device is open */
|
|
switch (event) {
|
|
case CHR_EVENT_OPENED:
|
|
s->be_open = 1;
|
|
break;
|
|
case CHR_EVENT_CLOSED:
|
|
s->be_open = 0;
|
|
break;
|
|
}
|
|
|
|
if (!be || !be->chr_event) {
|
|
return;
|
|
}
|
|
|
|
be->chr_event(be->opaque, event);
|
|
}
|
|
|
|
void qemu_chr_be_generic_open(Chardev *s)
|
|
{
|
|
qemu_chr_be_event(s, CHR_EVENT_OPENED);
|
|
}
|
|
|
|
|
|
/* Not reporting errors from writing to logfile, as logs are
|
|
* defined to be "best effort" only */
|
|
static void qemu_chr_fe_write_log(Chardev *s,
|
|
const uint8_t *buf, size_t len)
|
|
{
|
|
size_t done = 0;
|
|
ssize_t ret;
|
|
|
|
if (s->logfd < 0) {
|
|
return;
|
|
}
|
|
|
|
while (done < len) {
|
|
retry:
|
|
ret = write(s->logfd, buf + done, len - done);
|
|
if (ret == -1 && errno == EAGAIN) {
|
|
g_usleep(100);
|
|
goto retry;
|
|
}
|
|
|
|
if (ret <= 0) {
|
|
return;
|
|
}
|
|
done += ret;
|
|
}
|
|
}
|
|
|
|
static int qemu_chr_fe_write_buffer(Chardev *s,
|
|
const uint8_t *buf, int len, int *offset)
|
|
{
|
|
ChardevClass *cc = CHARDEV_GET_CLASS(s);
|
|
int res = 0;
|
|
*offset = 0;
|
|
|
|
qemu_mutex_lock(&s->chr_write_lock);
|
|
while (*offset < len) {
|
|
retry:
|
|
res = cc->chr_write(s, buf + *offset, len - *offset);
|
|
if (res < 0 && errno == EAGAIN) {
|
|
g_usleep(100);
|
|
goto retry;
|
|
}
|
|
|
|
if (res <= 0) {
|
|
break;
|
|
}
|
|
|
|
*offset += res;
|
|
}
|
|
if (*offset > 0) {
|
|
qemu_chr_fe_write_log(s, buf, *offset);
|
|
}
|
|
qemu_mutex_unlock(&s->chr_write_lock);
|
|
|
|
return res;
|
|
}
|
|
|
|
static bool qemu_chr_replay(Chardev *chr)
|
|
{
|
|
return qemu_chr_has_feature(chr, QEMU_CHAR_FEATURE_REPLAY);
|
|
}
|
|
|
|
int qemu_chr_fe_write(CharBackend *be, const uint8_t *buf, int len)
|
|
{
|
|
Chardev *s = be->chr;
|
|
ChardevClass *cc;
|
|
int ret;
|
|
|
|
if (!s) {
|
|
return 0;
|
|
}
|
|
|
|
if (qemu_chr_replay(s) && replay_mode == REPLAY_MODE_PLAY) {
|
|
int offset;
|
|
replay_char_write_event_load(&ret, &offset);
|
|
assert(offset <= len);
|
|
qemu_chr_fe_write_buffer(s, buf, offset, &offset);
|
|
return ret;
|
|
}
|
|
|
|
cc = CHARDEV_GET_CLASS(s);
|
|
qemu_mutex_lock(&s->chr_write_lock);
|
|
ret = cc->chr_write(s, buf, len);
|
|
|
|
if (ret > 0) {
|
|
qemu_chr_fe_write_log(s, buf, ret);
|
|
}
|
|
|
|
qemu_mutex_unlock(&s->chr_write_lock);
|
|
|
|
if (qemu_chr_replay(s) && replay_mode == REPLAY_MODE_RECORD) {
|
|
replay_char_write_event_save(ret, ret < 0 ? 0 : ret);
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
int qemu_chr_write_all(Chardev *s, const uint8_t *buf, int len)
|
|
{
|
|
int offset;
|
|
int res;
|
|
|
|
if (qemu_chr_replay(s) && replay_mode == REPLAY_MODE_PLAY) {
|
|
replay_char_write_event_load(&res, &offset);
|
|
assert(offset <= len);
|
|
qemu_chr_fe_write_buffer(s, buf, offset, &offset);
|
|
return res;
|
|
}
|
|
|
|
res = qemu_chr_fe_write_buffer(s, buf, len, &offset);
|
|
|
|
if (qemu_chr_replay(s) && replay_mode == REPLAY_MODE_RECORD) {
|
|
replay_char_write_event_save(res, offset);
|
|
}
|
|
|
|
if (res < 0) {
|
|
return res;
|
|
}
|
|
return offset;
|
|
}
|
|
|
|
int qemu_chr_fe_write_all(CharBackend *be, const uint8_t *buf, int len)
|
|
{
|
|
Chardev *s = be->chr;
|
|
|
|
if (!s) {
|
|
return 0;
|
|
}
|
|
|
|
return qemu_chr_write_all(s, buf, len);
|
|
}
|
|
|
|
int qemu_chr_fe_read_all(CharBackend *be, uint8_t *buf, int len)
|
|
{
|
|
Chardev *s = be->chr;
|
|
int offset = 0, counter = 10;
|
|
int res;
|
|
|
|
if (!s || !CHARDEV_GET_CLASS(s)->chr_sync_read) {
|
|
return 0;
|
|
}
|
|
|
|
if (qemu_chr_replay(s) && replay_mode == REPLAY_MODE_PLAY) {
|
|
return replay_char_read_all_load(buf);
|
|
}
|
|
|
|
while (offset < len) {
|
|
retry:
|
|
res = CHARDEV_GET_CLASS(s)->chr_sync_read(s, buf + offset,
|
|
len - offset);
|
|
if (res == -1 && errno == EAGAIN) {
|
|
g_usleep(100);
|
|
goto retry;
|
|
}
|
|
|
|
if (res == 0) {
|
|
break;
|
|
}
|
|
|
|
if (res < 0) {
|
|
if (qemu_chr_replay(s) && replay_mode == REPLAY_MODE_RECORD) {
|
|
replay_char_read_all_save_error(res);
|
|
}
|
|
return res;
|
|
}
|
|
|
|
offset += res;
|
|
|
|
if (!counter--) {
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (qemu_chr_replay(s) && replay_mode == REPLAY_MODE_RECORD) {
|
|
replay_char_read_all_save_buf(buf, offset);
|
|
}
|
|
return offset;
|
|
}
|
|
|
|
int qemu_chr_fe_ioctl(CharBackend *be, int cmd, void *arg)
|
|
{
|
|
Chardev *s = be->chr;
|
|
int res;
|
|
|
|
if (!s || !CHARDEV_GET_CLASS(s)->chr_ioctl || qemu_chr_replay(s)) {
|
|
res = -ENOTSUP;
|
|
} else {
|
|
res = CHARDEV_GET_CLASS(s)->chr_ioctl(s, cmd, arg);
|
|
}
|
|
|
|
return res;
|
|
}
|
|
|
|
int qemu_chr_be_can_write(Chardev *s)
|
|
{
|
|
CharBackend *be = s->be;
|
|
|
|
if (!be || !be->chr_can_read) {
|
|
return 0;
|
|
}
|
|
|
|
return be->chr_can_read(be->opaque);
|
|
}
|
|
|
|
void qemu_chr_be_write_impl(Chardev *s, uint8_t *buf, int len)
|
|
{
|
|
CharBackend *be = s->be;
|
|
|
|
if (be && be->chr_read) {
|
|
be->chr_read(be->opaque, buf, len);
|
|
}
|
|
}
|
|
|
|
void qemu_chr_be_write(Chardev *s, uint8_t *buf, int len)
|
|
{
|
|
if (qemu_chr_replay(s)) {
|
|
if (replay_mode == REPLAY_MODE_PLAY) {
|
|
return;
|
|
}
|
|
replay_chr_be_write(s, buf, len);
|
|
} else {
|
|
qemu_chr_be_write_impl(s, buf, len);
|
|
}
|
|
}
|
|
|
|
int qemu_chr_fe_get_msgfd(CharBackend *be)
|
|
{
|
|
Chardev *s = be->chr;
|
|
int fd;
|
|
int res = (qemu_chr_fe_get_msgfds(be, &fd, 1) == 1) ? fd : -1;
|
|
if (s && qemu_chr_replay(s)) {
|
|
error_report("Replay: get msgfd is not supported "
|
|
"for serial devices yet");
|
|
exit(1);
|
|
}
|
|
return res;
|
|
}
|
|
|
|
int qemu_chr_fe_get_msgfds(CharBackend *be, int *fds, int len)
|
|
{
|
|
Chardev *s = be->chr;
|
|
|
|
if (!s) {
|
|
return -1;
|
|
}
|
|
|
|
return CHARDEV_GET_CLASS(s)->get_msgfds ?
|
|
CHARDEV_GET_CLASS(s)->get_msgfds(s, fds, len) : -1;
|
|
}
|
|
|
|
int qemu_chr_fe_set_msgfds(CharBackend *be, int *fds, int num)
|
|
{
|
|
Chardev *s = be->chr;
|
|
|
|
if (!s) {
|
|
return -1;
|
|
}
|
|
|
|
return CHARDEV_GET_CLASS(s)->set_msgfds ?
|
|
CHARDEV_GET_CLASS(s)->set_msgfds(s, fds, num) : -1;
|
|
}
|
|
|
|
int qemu_chr_add_client(Chardev *s, int fd)
|
|
{
|
|
return CHARDEV_GET_CLASS(s)->chr_add_client ?
|
|
CHARDEV_GET_CLASS(s)->chr_add_client(s, fd) : -1;
|
|
}
|
|
|
|
void qemu_chr_fe_accept_input(CharBackend *be)
|
|
{
|
|
Chardev *s = be->chr;
|
|
|
|
if (!s) {
|
|
return;
|
|
}
|
|
|
|
if (CHARDEV_GET_CLASS(s)->chr_accept_input) {
|
|
CHARDEV_GET_CLASS(s)->chr_accept_input(s);
|
|
}
|
|
qemu_notify_event();
|
|
}
|
|
|
|
void qemu_chr_fe_printf(CharBackend *be, const char *fmt, ...)
|
|
{
|
|
char buf[CHR_READ_BUF_LEN];
|
|
va_list ap;
|
|
va_start(ap, fmt);
|
|
vsnprintf(buf, sizeof(buf), fmt, ap);
|
|
/* XXX this blocks entire thread. Rewrite to use
|
|
* qemu_chr_fe_write and background I/O callbacks */
|
|
qemu_chr_fe_write_all(be, (uint8_t *)buf, strlen(buf));
|
|
va_end(ap);
|
|
}
|
|
|
|
static void qemu_char_open(Chardev *chr, ChardevBackend *backend,
|
|
bool *be_opened, Error **errp)
|
|
{
|
|
ChardevClass *cc = CHARDEV_GET_CLASS(chr);
|
|
/* Any ChardevCommon member would work */
|
|
ChardevCommon *common = backend ? backend->u.null.data : NULL;
|
|
|
|
if (common && common->has_logfile) {
|
|
int flags = O_WRONLY | O_CREAT;
|
|
if (common->has_logappend &&
|
|
common->logappend) {
|
|
flags |= O_APPEND;
|
|
} else {
|
|
flags |= O_TRUNC;
|
|
}
|
|
chr->logfd = qemu_open(common->logfile, flags, 0666);
|
|
if (chr->logfd < 0) {
|
|
error_setg_errno(errp, errno,
|
|
"Unable to open logfile %s",
|
|
common->logfile);
|
|
return;
|
|
}
|
|
}
|
|
|
|
if (cc->open) {
|
|
cc->open(chr, backend, be_opened, errp);
|
|
}
|
|
}
|
|
|
|
static void char_init(Object *obj)
|
|
{
|
|
Chardev *chr = CHARDEV(obj);
|
|
|
|
chr->logfd = -1;
|
|
qemu_mutex_init(&chr->chr_write_lock);
|
|
}
|
|
|
|
static int null_chr_write(Chardev *chr, const uint8_t *buf, int len)
|
|
{
|
|
return len;
|
|
}
|
|
|
|
static void char_class_init(ObjectClass *oc, void *data)
|
|
{
|
|
ChardevClass *cc = CHARDEV_CLASS(oc);
|
|
|
|
cc->chr_write = null_chr_write;
|
|
}
|
|
|
|
static void char_finalize(Object *obj)
|
|
{
|
|
Chardev *chr = CHARDEV(obj);
|
|
|
|
if (chr->be) {
|
|
chr->be->chr = NULL;
|
|
}
|
|
g_free(chr->filename);
|
|
g_free(chr->label);
|
|
if (chr->logfd != -1) {
|
|
close(chr->logfd);
|
|
}
|
|
qemu_mutex_destroy(&chr->chr_write_lock);
|
|
}
|
|
|
|
static const TypeInfo char_type_info = {
|
|
.name = TYPE_CHARDEV,
|
|
.parent = TYPE_OBJECT,
|
|
.instance_size = sizeof(Chardev),
|
|
.instance_init = char_init,
|
|
.instance_finalize = char_finalize,
|
|
.abstract = true,
|
|
.class_size = sizeof(ChardevClass),
|
|
.class_init = char_class_init,
|
|
};
|
|
|
|
/**
|
|
* 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 muxes_realize_done(Notifier *notifier, void *unused)
|
|
{
|
|
Chardev *chr;
|
|
|
|
QTAILQ_FOREACH(chr, &chardevs, next) {
|
|
if (CHARDEV_IS_MUX(chr)) {
|
|
MuxChardev *d = MUX_CHARDEV(chr);
|
|
int i;
|
|
|
|
/* send OPENED to all already-attached FEs */
|
|
for (i = 0; i < d->mux_cnt; i++) {
|
|
mux_chr_send_event(d, i, CHR_EVENT_OPENED);
|
|
}
|
|
/* mark mux as OPENED so any new FEs will immediately receive
|
|
* OPENED event
|
|
*/
|
|
qemu_chr_be_generic_open(chr);
|
|
}
|
|
}
|
|
muxes_realized = true;
|
|
}
|
|
|
|
static Notifier muxes_realize_notify = {
|
|
.notify = muxes_realize_done,
|
|
};
|
|
|
|
Chardev *qemu_chr_fe_get_driver(CharBackend *be)
|
|
{
|
|
return be->chr;
|
|
}
|
|
|
|
bool qemu_chr_fe_init(CharBackend *b, Chardev *s, Error **errp)
|
|
{
|
|
int tag = 0;
|
|
|
|
if (CHARDEV_IS_MUX(s)) {
|
|
MuxChardev *d = MUX_CHARDEV(s);
|
|
|
|
if (d->mux_cnt >= MAX_MUX) {
|
|
goto unavailable;
|
|
}
|
|
|
|
d->backends[d->mux_cnt] = b;
|
|
tag = d->mux_cnt++;
|
|
} else if (s->be) {
|
|
goto unavailable;
|
|
} else {
|
|
s->be = b;
|
|
}
|
|
|
|
b->fe_open = false;
|
|
b->tag = tag;
|
|
b->chr = s;
|
|
return true;
|
|
|
|
unavailable:
|
|
error_setg(errp, QERR_DEVICE_IN_USE, s->label);
|
|
return false;
|
|
}
|
|
|
|
static bool qemu_chr_is_busy(Chardev *s)
|
|
{
|
|
if (CHARDEV_IS_MUX(s)) {
|
|
MuxChardev *d = MUX_CHARDEV(s);
|
|
return d->mux_cnt >= 0;
|
|
} else {
|
|
return s->be != NULL;
|
|
}
|
|
}
|
|
|
|
void qemu_chr_fe_deinit(CharBackend *b)
|
|
{
|
|
assert(b);
|
|
|
|
if (b->chr) {
|
|
qemu_chr_fe_set_handlers(b, NULL, NULL, NULL, NULL, NULL, true);
|
|
if (b->chr->be == b) {
|
|
b->chr->be = NULL;
|
|
}
|
|
if (CHARDEV_IS_MUX(b->chr)) {
|
|
MuxChardev *d = MUX_CHARDEV(b->chr);
|
|
d->backends[b->tag] = NULL;
|
|
}
|
|
b->chr = NULL;
|
|
}
|
|
}
|
|
|
|
void qemu_chr_fe_set_handlers(CharBackend *b,
|
|
IOCanReadHandler *fd_can_read,
|
|
IOReadHandler *fd_read,
|
|
IOEventHandler *fd_event,
|
|
void *opaque,
|
|
GMainContext *context,
|
|
bool set_open)
|
|
{
|
|
Chardev *s;
|
|
ChardevClass *cc;
|
|
int fe_open;
|
|
|
|
s = b->chr;
|
|
if (!s) {
|
|
return;
|
|
}
|
|
|
|
cc = CHARDEV_GET_CLASS(s);
|
|
if (!opaque && !fd_can_read && !fd_read && !fd_event) {
|
|
fe_open = 0;
|
|
remove_fd_in_watch(s, context);
|
|
} else {
|
|
fe_open = 1;
|
|
}
|
|
b->chr_can_read = fd_can_read;
|
|
b->chr_read = fd_read;
|
|
b->chr_event = fd_event;
|
|
b->opaque = opaque;
|
|
if (cc->chr_update_read_handler) {
|
|
cc->chr_update_read_handler(s, context);
|
|
}
|
|
|
|
if (set_open) {
|
|
qemu_chr_fe_set_open(b, fe_open);
|
|
}
|
|
|
|
if (fe_open) {
|
|
qemu_chr_fe_take_focus(b);
|
|
/* We're connecting to an already opened device, so let's make sure we
|
|
also get the open event */
|
|
if (s->be_open) {
|
|
qemu_chr_be_generic_open(s);
|
|
}
|
|
}
|
|
|
|
if (CHARDEV_IS_MUX(s)) {
|
|
mux_chr_set_handlers(s, context);
|
|
}
|
|
}
|
|
|
|
void qemu_chr_fe_take_focus(CharBackend *b)
|
|
{
|
|
if (!b->chr) {
|
|
return;
|
|
}
|
|
|
|
if (CHARDEV_IS_MUX(b->chr)) {
|
|
mux_set_focus(b->chr, b->tag);
|
|
}
|
|
}
|
|
|
|
int qemu_chr_wait_connected(Chardev *chr, Error **errp)
|
|
{
|
|
ChardevClass *cc = CHARDEV_GET_CLASS(chr);
|
|
|
|
if (cc->chr_wait_connected) {
|
|
return cc->chr_wait_connected(chr, errp);
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
int qemu_chr_fe_wait_connected(CharBackend *be, Error **errp)
|
|
{
|
|
if (!be->chr) {
|
|
error_setg(errp, "missing associated backend");
|
|
return -1;
|
|
}
|
|
|
|
return qemu_chr_wait_connected(be->chr, errp);
|
|
}
|
|
|
|
QemuOpts *qemu_chr_parse_compat(const char *label, const char *filename)
|
|
{
|
|
char host[65], port[33], width[8], height[8];
|
|
int pos;
|
|
const char *p;
|
|
QemuOpts *opts;
|
|
Error *local_err = NULL;
|
|
|
|
opts = qemu_opts_create(qemu_find_opts("chardev"), label, 1, &local_err);
|
|
if (local_err) {
|
|
error_report_err(local_err);
|
|
return NULL;
|
|
}
|
|
|
|
if (strstart(filename, "mon:", &p)) {
|
|
filename = p;
|
|
qemu_opt_set(opts, "mux", "on", &error_abort);
|
|
if (strcmp(filename, "stdio") == 0) {
|
|
/* Monitor is muxed to stdio: do not exit on Ctrl+C by default
|
|
* but pass it to the guest. Handle this only for compat syntax,
|
|
* for -chardev syntax we have special option for this.
|
|
* This is what -nographic did, redirecting+muxing serial+monitor
|
|
* to stdio causing Ctrl+C to be passed to guest. */
|
|
qemu_opt_set(opts, "signal", "off", &error_abort);
|
|
}
|
|
}
|
|
|
|
if (strcmp(filename, "null") == 0 ||
|
|
strcmp(filename, "pty") == 0 ||
|
|
strcmp(filename, "msmouse") == 0 ||
|
|
strcmp(filename, "wctablet") == 0 ||
|
|
strcmp(filename, "braille") == 0 ||
|
|
strcmp(filename, "testdev") == 0 ||
|
|
strcmp(filename, "stdio") == 0) {
|
|
qemu_opt_set(opts, "backend", filename, &error_abort);
|
|
return opts;
|
|
}
|
|
if (strstart(filename, "vc", &p)) {
|
|
qemu_opt_set(opts, "backend", "vc", &error_abort);
|
|
if (*p == ':') {
|
|
if (sscanf(p+1, "%7[0-9]x%7[0-9]", width, height) == 2) {
|
|
/* pixels */
|
|
qemu_opt_set(opts, "width", width, &error_abort);
|
|
qemu_opt_set(opts, "height", height, &error_abort);
|
|
} else if (sscanf(p+1, "%7[0-9]Cx%7[0-9]C", width, height) == 2) {
|
|
/* chars */
|
|
qemu_opt_set(opts, "cols", width, &error_abort);
|
|
qemu_opt_set(opts, "rows", height, &error_abort);
|
|
} else {
|
|
goto fail;
|
|
}
|
|
}
|
|
return opts;
|
|
}
|
|
if (strcmp(filename, "con:") == 0) {
|
|
qemu_opt_set(opts, "backend", "console", &error_abort);
|
|
return opts;
|
|
}
|
|
if (strstart(filename, "COM", NULL)) {
|
|
qemu_opt_set(opts, "backend", "serial", &error_abort);
|
|
qemu_opt_set(opts, "path", filename, &error_abort);
|
|
return opts;
|
|
}
|
|
if (strstart(filename, "file:", &p)) {
|
|
qemu_opt_set(opts, "backend", "file", &error_abort);
|
|
qemu_opt_set(opts, "path", p, &error_abort);
|
|
return opts;
|
|
}
|
|
if (strstart(filename, "pipe:", &p)) {
|
|
qemu_opt_set(opts, "backend", "pipe", &error_abort);
|
|
qemu_opt_set(opts, "path", p, &error_abort);
|
|
return opts;
|
|
}
|
|
if (strstart(filename, "tcp:", &p) ||
|
|
strstart(filename, "telnet:", &p)) {
|
|
if (sscanf(p, "%64[^:]:%32[^,]%n", host, port, &pos) < 2) {
|
|
host[0] = 0;
|
|
if (sscanf(p, ":%32[^,]%n", port, &pos) < 1)
|
|
goto fail;
|
|
}
|
|
qemu_opt_set(opts, "backend", "socket", &error_abort);
|
|
qemu_opt_set(opts, "host", host, &error_abort);
|
|
qemu_opt_set(opts, "port", port, &error_abort);
|
|
if (p[pos] == ',') {
|
|
qemu_opts_do_parse(opts, p+pos+1, NULL, &local_err);
|
|
if (local_err) {
|
|
error_report_err(local_err);
|
|
goto fail;
|
|
}
|
|
}
|
|
if (strstart(filename, "telnet:", &p))
|
|
qemu_opt_set(opts, "telnet", "on", &error_abort);
|
|
return opts;
|
|
}
|
|
if (strstart(filename, "udp:", &p)) {
|
|
qemu_opt_set(opts, "backend", "udp", &error_abort);
|
|
if (sscanf(p, "%64[^:]:%32[^@,]%n", host, port, &pos) < 2) {
|
|
host[0] = 0;
|
|
if (sscanf(p, ":%32[^@,]%n", port, &pos) < 1) {
|
|
goto fail;
|
|
}
|
|
}
|
|
qemu_opt_set(opts, "host", host, &error_abort);
|
|
qemu_opt_set(opts, "port", port, &error_abort);
|
|
if (p[pos] == '@') {
|
|
p += pos + 1;
|
|
if (sscanf(p, "%64[^:]:%32[^,]%n", host, port, &pos) < 2) {
|
|
host[0] = 0;
|
|
if (sscanf(p, ":%32[^,]%n", port, &pos) < 1) {
|
|
goto fail;
|
|
}
|
|
}
|
|
qemu_opt_set(opts, "localaddr", host, &error_abort);
|
|
qemu_opt_set(opts, "localport", port, &error_abort);
|
|
}
|
|
return opts;
|
|
}
|
|
if (strstart(filename, "unix:", &p)) {
|
|
qemu_opt_set(opts, "backend", "socket", &error_abort);
|
|
qemu_opts_do_parse(opts, p, "path", &local_err);
|
|
if (local_err) {
|
|
error_report_err(local_err);
|
|
goto fail;
|
|
}
|
|
return opts;
|
|
}
|
|
if (strstart(filename, "/dev/parport", NULL) ||
|
|
strstart(filename, "/dev/ppi", NULL)) {
|
|
qemu_opt_set(opts, "backend", "parport", &error_abort);
|
|
qemu_opt_set(opts, "path", filename, &error_abort);
|
|
return opts;
|
|
}
|
|
if (strstart(filename, "/dev/", NULL)) {
|
|
qemu_opt_set(opts, "backend", "tty", &error_abort);
|
|
qemu_opt_set(opts, "path", filename, &error_abort);
|
|
return opts;
|
|
}
|
|
|
|
fail:
|
|
qemu_opts_del(opts);
|
|
return NULL;
|
|
}
|
|
|
|
void qemu_chr_parse_common(QemuOpts *opts, ChardevCommon *backend)
|
|
{
|
|
const char *logfile = qemu_opt_get(opts, "logfile");
|
|
|
|
backend->has_logfile = logfile != NULL;
|
|
backend->logfile = logfile ? g_strdup(logfile) : NULL;
|
|
|
|
backend->has_logappend = true;
|
|
backend->logappend = qemu_opt_get_bool(opts, "logappend", false);
|
|
}
|
|
|
|
static const ChardevClass *char_get_class(const char *driver, Error **errp)
|
|
{
|
|
ObjectClass *oc;
|
|
const ChardevClass *cc;
|
|
char *typename = g_strdup_printf("chardev-%s", driver);
|
|
|
|
oc = object_class_by_name(typename);
|
|
g_free(typename);
|
|
|
|
if (!object_class_dynamic_cast(oc, TYPE_CHARDEV)) {
|
|
error_setg(errp, "'%s' is not a valid char driver name", driver);
|
|
return NULL;
|
|
}
|
|
|
|
if (object_class_is_abstract(oc)) {
|
|
error_setg(errp, QERR_INVALID_PARAMETER_VALUE, "driver",
|
|
"abstract device type");
|
|
return NULL;
|
|
}
|
|
|
|
cc = CHARDEV_CLASS(oc);
|
|
if (cc->internal) {
|
|
error_setg(errp, "'%s' is not a valid char driver name", driver);
|
|
return NULL;
|
|
}
|
|
|
|
return cc;
|
|
}
|
|
|
|
static Chardev *qemu_chardev_add(const char *id, const char *typename,
|
|
ChardevBackend *backend, Error **errp)
|
|
{
|
|
Chardev *chr;
|
|
|
|
chr = qemu_chr_find(id);
|
|
if (chr) {
|
|
error_setg(errp, "Chardev '%s' already exists", id);
|
|
return NULL;
|
|
}
|
|
|
|
chr = qemu_chardev_new(id, typename, backend, errp);
|
|
if (!chr) {
|
|
return NULL;
|
|
}
|
|
|
|
QTAILQ_INSERT_TAIL(&chardevs, chr, next);
|
|
return chr;
|
|
}
|
|
|
|
static const struct ChardevAlias {
|
|
const char *typename;
|
|
const char *alias;
|
|
} chardev_alias_table[] = {
|
|
#ifdef HAVE_CHARDEV_PARPORT
|
|
{ "parallel", "parport" },
|
|
#endif
|
|
#ifdef HAVE_CHARDEV_SERIAL
|
|
{ "serial", "tty" },
|
|
#endif
|
|
};
|
|
|
|
typedef struct ChadevClassFE {
|
|
void (*fn)(const char *name, void *opaque);
|
|
void *opaque;
|
|
} ChadevClassFE;
|
|
|
|
static void
|
|
chardev_class_foreach(ObjectClass *klass, void *opaque)
|
|
{
|
|
ChadevClassFE *fe = opaque;
|
|
|
|
assert(g_str_has_prefix(object_class_get_name(klass), "chardev-"));
|
|
if (CHARDEV_CLASS(klass)->internal) {
|
|
return;
|
|
}
|
|
|
|
fe->fn(object_class_get_name(klass) + 8, fe->opaque);
|
|
}
|
|
|
|
static void
|
|
chardev_name_foreach(void (*fn)(const char *name, void *opaque), void *opaque)
|
|
{
|
|
ChadevClassFE fe = { .fn = fn, .opaque = opaque };
|
|
int i;
|
|
|
|
object_class_foreach(chardev_class_foreach, TYPE_CHARDEV, false, &fe);
|
|
|
|
for (i = 0; i < ARRAY_SIZE(chardev_alias_table); i++) {
|
|
fn(chardev_alias_table[i].alias, opaque);
|
|
}
|
|
}
|
|
|
|
static void
|
|
help_string_append(const char *name, void *opaque)
|
|
{
|
|
GString *str = opaque;
|
|
|
|
g_string_append_printf(str, "\n%s", name);
|
|
}
|
|
|
|
Chardev *qemu_chr_new_from_opts(QemuOpts *opts,
|
|
Error **errp)
|
|
{
|
|
Error *local_err = NULL;
|
|
const ChardevClass *cc;
|
|
Chardev *chr;
|
|
int i;
|
|
ChardevBackend *backend = NULL;
|
|
const char *name = qemu_opt_get(opts, "backend");
|
|
const char *id = qemu_opts_id(opts);
|
|
char *bid = NULL;
|
|
|
|
if (name == NULL) {
|
|
error_setg(errp, "chardev: \"%s\" missing backend",
|
|
qemu_opts_id(opts));
|
|
return NULL;
|
|
}
|
|
|
|
if (is_help_option(name)) {
|
|
GString *str = g_string_new("");
|
|
|
|
chardev_name_foreach(help_string_append, str);
|
|
|
|
error_report("Available chardev backend types: %s", str->str);
|
|
g_string_free(str, true);
|
|
exit(0);
|
|
}
|
|
|
|
if (id == NULL) {
|
|
error_setg(errp, "chardev: no id specified");
|
|
return NULL;
|
|
}
|
|
|
|
for (i = 0; i < ARRAY_SIZE(chardev_alias_table); i++) {
|
|
if (g_strcmp0(chardev_alias_table[i].alias, name) == 0) {
|
|
name = chardev_alias_table[i].typename;
|
|
break;
|
|
}
|
|
}
|
|
|
|
cc = char_get_class(name, errp);
|
|
if (cc == NULL) {
|
|
return NULL;
|
|
}
|
|
|
|
backend = g_new0(ChardevBackend, 1);
|
|
backend->type = CHARDEV_BACKEND_KIND_NULL;
|
|
|
|
if (qemu_opt_get_bool(opts, "mux", 0)) {
|
|
bid = g_strdup_printf("%s-base", id);
|
|
}
|
|
|
|
chr = NULL;
|
|
if (cc->parse) {
|
|
cc->parse(opts, backend, &local_err);
|
|
if (local_err) {
|
|
error_propagate(errp, local_err);
|
|
goto out;
|
|
}
|
|
} else {
|
|
ChardevCommon *ccom = g_new0(ChardevCommon, 1);
|
|
qemu_chr_parse_common(opts, ccom);
|
|
backend->u.null.data = ccom; /* Any ChardevCommon member would work */
|
|
}
|
|
|
|
chr = qemu_chardev_add(bid ? bid : id,
|
|
object_class_get_name(OBJECT_CLASS(cc)),
|
|
backend, errp);
|
|
if (chr == NULL) {
|
|
goto out;
|
|
}
|
|
|
|
if (bid) {
|
|
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);
|
|
mux = qemu_chardev_add(id, TYPE_CHARDEV_MUX, backend, errp);
|
|
if (mux == NULL) {
|
|
qemu_chr_delete(chr);
|
|
chr = NULL;
|
|
goto out;
|
|
}
|
|
chr = mux;
|
|
}
|
|
|
|
out:
|
|
qapi_free_ChardevBackend(backend);
|
|
g_free(bid);
|
|
return chr;
|
|
}
|
|
|
|
Chardev *qemu_chr_new_noreplay(const char *label, const char *filename)
|
|
{
|
|
const char *p;
|
|
Chardev *chr;
|
|
QemuOpts *opts;
|
|
Error *err = NULL;
|
|
|
|
if (strstart(filename, "chardev:", &p)) {
|
|
return qemu_chr_find(p);
|
|
}
|
|
|
|
opts = qemu_chr_parse_compat(label, filename);
|
|
if (!opts)
|
|
return NULL;
|
|
|
|
chr = qemu_chr_new_from_opts(opts, &err);
|
|
if (err) {
|
|
error_report_err(err);
|
|
}
|
|
if (chr && qemu_opt_get_bool(opts, "mux", 0)) {
|
|
monitor_init(chr, MONITOR_USE_READLINE);
|
|
}
|
|
qemu_opts_del(opts);
|
|
return chr;
|
|
}
|
|
|
|
Chardev *qemu_chr_new(const char *label, const char *filename)
|
|
{
|
|
Chardev *chr;
|
|
chr = qemu_chr_new_noreplay(label, filename);
|
|
if (chr) {
|
|
if (replay_mode != REPLAY_MODE_NONE) {
|
|
qemu_chr_set_feature(chr, QEMU_CHAR_FEATURE_REPLAY);
|
|
}
|
|
if (qemu_chr_replay(chr) && CHARDEV_GET_CLASS(chr)->chr_ioctl) {
|
|
error_report("Replay: ioctl is not supported "
|
|
"for serial devices yet");
|
|
}
|
|
replay_register_char_driver(chr);
|
|
}
|
|
return chr;
|
|
}
|
|
|
|
void qemu_chr_fe_set_echo(CharBackend *be, bool echo)
|
|
{
|
|
Chardev *chr = be->chr;
|
|
|
|
if (chr && CHARDEV_GET_CLASS(chr)->chr_set_echo) {
|
|
CHARDEV_GET_CLASS(chr)->chr_set_echo(chr, echo);
|
|
}
|
|
}
|
|
|
|
void qemu_chr_fe_set_open(CharBackend *be, int fe_open)
|
|
{
|
|
Chardev *chr = be->chr;
|
|
|
|
if (!chr) {
|
|
return;
|
|
}
|
|
|
|
if (be->fe_open == fe_open) {
|
|
return;
|
|
}
|
|
be->fe_open = fe_open;
|
|
if (CHARDEV_GET_CLASS(chr)->chr_set_fe_open) {
|
|
CHARDEV_GET_CLASS(chr)->chr_set_fe_open(chr, fe_open);
|
|
}
|
|
}
|
|
|
|
guint qemu_chr_fe_add_watch(CharBackend *be, GIOCondition cond,
|
|
GIOFunc func, void *user_data)
|
|
{
|
|
Chardev *s = be->chr;
|
|
GSource *src;
|
|
guint tag;
|
|
|
|
if (!s || CHARDEV_GET_CLASS(s)->chr_add_watch == NULL) {
|
|
return 0;
|
|
}
|
|
|
|
src = CHARDEV_GET_CLASS(s)->chr_add_watch(s, cond);
|
|
if (!src) {
|
|
return 0;
|
|
}
|
|
|
|
g_source_set_callback(src, (GSourceFunc)func, user_data, NULL);
|
|
tag = g_source_attach(src, NULL);
|
|
g_source_unref(src);
|
|
|
|
return tag;
|
|
}
|
|
|
|
void qemu_chr_fe_disconnect(CharBackend *be)
|
|
{
|
|
Chardev *chr = be->chr;
|
|
|
|
if (chr && CHARDEV_GET_CLASS(chr)->chr_disconnect) {
|
|
CHARDEV_GET_CLASS(chr)->chr_disconnect(chr);
|
|
}
|
|
}
|
|
|
|
void qemu_chr_delete(Chardev *chr)
|
|
{
|
|
QTAILQ_REMOVE(&chardevs, chr, next);
|
|
object_unref(OBJECT(chr));
|
|
}
|
|
|
|
ChardevInfoList *qmp_query_chardev(Error **errp)
|
|
{
|
|
ChardevInfoList *chr_list = NULL;
|
|
Chardev *chr;
|
|
|
|
QTAILQ_FOREACH(chr, &chardevs, next) {
|
|
ChardevInfoList *info = g_malloc0(sizeof(*info));
|
|
info->value = g_malloc0(sizeof(*info->value));
|
|
info->value->label = g_strdup(chr->label);
|
|
info->value->filename = g_strdup(chr->filename);
|
|
info->value->frontend_open = chr->be && chr->be->fe_open;
|
|
|
|
info->next = chr_list;
|
|
chr_list = info;
|
|
}
|
|
|
|
return chr_list;
|
|
}
|
|
|
|
static void
|
|
qmp_prepend_backend(const char *name, void *opaque)
|
|
{
|
|
ChardevBackendInfoList **list = opaque;
|
|
ChardevBackendInfoList *info = g_malloc0(sizeof(*info));
|
|
|
|
info->value = g_malloc0(sizeof(*info->value));
|
|
info->value->name = g_strdup(name);
|
|
info->next = *list;
|
|
*list = info;
|
|
}
|
|
|
|
ChardevBackendInfoList *qmp_query_chardev_backends(Error **errp)
|
|
{
|
|
ChardevBackendInfoList *backend_list = NULL;
|
|
|
|
chardev_name_foreach(qmp_prepend_backend, &backend_list);
|
|
|
|
return backend_list;
|
|
}
|
|
|
|
Chardev *qemu_chr_find(const char *name)
|
|
{
|
|
Chardev *chr;
|
|
|
|
QTAILQ_FOREACH(chr, &chardevs, next) {
|
|
if (strcmp(chr->label, name) != 0)
|
|
continue;
|
|
return chr;
|
|
}
|
|
return NULL;
|
|
}
|
|
|
|
QemuOptsList qemu_chardev_opts = {
|
|
.name = "chardev",
|
|
.implied_opt_name = "backend",
|
|
.head = QTAILQ_HEAD_INITIALIZER(qemu_chardev_opts.head),
|
|
.desc = {
|
|
{
|
|
.name = "backend",
|
|
.type = QEMU_OPT_STRING,
|
|
},{
|
|
.name = "path",
|
|
.type = QEMU_OPT_STRING,
|
|
},{
|
|
.name = "host",
|
|
.type = QEMU_OPT_STRING,
|
|
},{
|
|
.name = "port",
|
|
.type = QEMU_OPT_STRING,
|
|
},{
|
|
.name = "localaddr",
|
|
.type = QEMU_OPT_STRING,
|
|
},{
|
|
.name = "localport",
|
|
.type = QEMU_OPT_STRING,
|
|
},{
|
|
.name = "to",
|
|
.type = QEMU_OPT_NUMBER,
|
|
},{
|
|
.name = "ipv4",
|
|
.type = QEMU_OPT_BOOL,
|
|
},{
|
|
.name = "ipv6",
|
|
.type = QEMU_OPT_BOOL,
|
|
},{
|
|
.name = "wait",
|
|
.type = QEMU_OPT_BOOL,
|
|
},{
|
|
.name = "server",
|
|
.type = QEMU_OPT_BOOL,
|
|
},{
|
|
.name = "delay",
|
|
.type = QEMU_OPT_BOOL,
|
|
},{
|
|
.name = "reconnect",
|
|
.type = QEMU_OPT_NUMBER,
|
|
},{
|
|
.name = "telnet",
|
|
.type = QEMU_OPT_BOOL,
|
|
},{
|
|
.name = "tls-creds",
|
|
.type = QEMU_OPT_STRING,
|
|
},{
|
|
.name = "width",
|
|
.type = QEMU_OPT_NUMBER,
|
|
},{
|
|
.name = "height",
|
|
.type = QEMU_OPT_NUMBER,
|
|
},{
|
|
.name = "cols",
|
|
.type = QEMU_OPT_NUMBER,
|
|
},{
|
|
.name = "rows",
|
|
.type = QEMU_OPT_NUMBER,
|
|
},{
|
|
.name = "mux",
|
|
.type = QEMU_OPT_BOOL,
|
|
},{
|
|
.name = "signal",
|
|
.type = QEMU_OPT_BOOL,
|
|
},{
|
|
.name = "name",
|
|
.type = QEMU_OPT_STRING,
|
|
},{
|
|
.name = "debug",
|
|
.type = QEMU_OPT_NUMBER,
|
|
},{
|
|
.name = "size",
|
|
.type = QEMU_OPT_SIZE,
|
|
},{
|
|
.name = "chardev",
|
|
.type = QEMU_OPT_STRING,
|
|
},{
|
|
.name = "append",
|
|
.type = QEMU_OPT_BOOL,
|
|
},{
|
|
.name = "logfile",
|
|
.type = QEMU_OPT_STRING,
|
|
},{
|
|
.name = "logappend",
|
|
.type = QEMU_OPT_BOOL,
|
|
},
|
|
{ /* end of list */ }
|
|
},
|
|
};
|
|
|
|
bool qemu_chr_has_feature(Chardev *chr,
|
|
ChardevFeature feature)
|
|
{
|
|
return test_bit(feature, chr->features);
|
|
}
|
|
|
|
void qemu_chr_set_feature(Chardev *chr,
|
|
ChardevFeature feature)
|
|
{
|
|
return set_bit(feature, chr->features);
|
|
}
|
|
|
|
Chardev *qemu_chardev_new(const char *id, const char *typename,
|
|
ChardevBackend *backend, Error **errp)
|
|
{
|
|
Chardev *chr = NULL;
|
|
Error *local_err = NULL;
|
|
bool be_opened = true;
|
|
|
|
assert(g_str_has_prefix(typename, "chardev-"));
|
|
|
|
chr = CHARDEV(object_new(typename));
|
|
chr->label = g_strdup(id);
|
|
|
|
qemu_char_open(chr, backend, &be_opened, &local_err);
|
|
if (local_err) {
|
|
error_propagate(errp, local_err);
|
|
object_unref(OBJECT(chr));
|
|
return NULL;
|
|
}
|
|
|
|
if (!chr->filename) {
|
|
chr->filename = g_strdup(typename + 8);
|
|
}
|
|
if (be_opened) {
|
|
qemu_chr_be_event(chr, CHR_EVENT_OPENED);
|
|
}
|
|
|
|
return chr;
|
|
}
|
|
|
|
ChardevReturn *qmp_chardev_add(const char *id, ChardevBackend *backend,
|
|
Error **errp)
|
|
{
|
|
const ChardevClass *cc;
|
|
ChardevReturn *ret;
|
|
Chardev *chr;
|
|
|
|
cc = char_get_class(ChardevBackendKind_lookup[backend->type], errp);
|
|
if (!cc) {
|
|
return NULL;
|
|
}
|
|
|
|
chr = qemu_chardev_add(id, object_class_get_name(OBJECT_CLASS(cc)),
|
|
backend, errp);
|
|
if (!chr) {
|
|
return NULL;
|
|
}
|
|
|
|
ret = g_new0(ChardevReturn, 1);
|
|
if (CHARDEV_IS_PTY(chr)) {
|
|
ret->pty = g_strdup(chr->filename + 4);
|
|
ret->has_pty = true;
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
void qmp_chardev_remove(const char *id, Error **errp)
|
|
{
|
|
Chardev *chr;
|
|
|
|
chr = qemu_chr_find(id);
|
|
if (chr == NULL) {
|
|
error_setg(errp, "Chardev '%s' not found", id);
|
|
return;
|
|
}
|
|
if (qemu_chr_is_busy(chr)) {
|
|
error_setg(errp, "Chardev '%s' is busy", id);
|
|
return;
|
|
}
|
|
if (qemu_chr_replay(chr)) {
|
|
error_setg(errp,
|
|
"Chardev '%s' cannot be unplugged in record/replay mode", id);
|
|
return;
|
|
}
|
|
qemu_chr_delete(chr);
|
|
}
|
|
|
|
void qemu_chr_cleanup(void)
|
|
{
|
|
Chardev *chr, *tmp;
|
|
|
|
QTAILQ_FOREACH_SAFE(chr, &chardevs, next, tmp) {
|
|
qemu_chr_delete(chr);
|
|
}
|
|
}
|
|
|
|
static void register_types(void)
|
|
{
|
|
type_register_static(&char_type_info);
|
|
|
|
/* this must be done after machine init, since we register FEs with muxes
|
|
* as part of realize functions like serial_isa_realizefn when -nographic
|
|
* is specified
|
|
*/
|
|
qemu_add_machine_init_done_notifier(&muxes_realize_notify);
|
|
}
|
|
|
|
type_init(register_types);
|