@@ -10,6 +10,12 @@ typedef int BackendChangeHandler(void *opaque);
* Chardev */
struct CharBackend {
Chardev *chr;
+ /* chr_lock is used to ensure thread-safety of operations with
+ * the associated Chardev.
+ * There is no guarantee otherwise, generally, that *chr won't become
+ * invalid.
+ */
+ QemuMutex chr_lock;
IOEventHandler *chr_event;
IOCanReadHandler *chr_can_read;
IOReadHandler *chr_read;
@@ -42,6 +48,17 @@ bool qemu_chr_fe_init(CharBackend *b, Chardev *s, Error **errp);
void qemu_chr_fe_deinit(CharBackend *b, bool del);
/**
+ * @qemu_chr_fe_connect:
+ *
+ * Connects the already initialized front end with a given Chardev.
+ * Call qemu_chr_fe_deinit() to remove the association and
+ * release the driver.
+ *
+ * Returns: false on error.
+ */
+bool qemu_chr_fe_connect(CharBackend *b, Chardev *s, Error **errp);
+
+/**
* @qemu_chr_fe_get_driver:
*
* Returns the driver associated with a CharBackend or NULL if no
@@ -93,6 +93,15 @@ void qemu_chr_parse_common(QemuOpts *opts, ChardevCommon *backend);
Chardev *qemu_chr_new(const char *label, const char *filename);
/**
+ * @qemu_chr_change:
+ *
+ * Change an existing character backend
+ *
+ * @opts the new backend options
+ */
+void qemu_chr_change(QemuOpts *opts, Error **errp);
+
+/**
* @qemu_chr_cleanup:
*
* Delete all chardevs (when leaving qemu)
@@ -33,24 +33,28 @@
int qemu_chr_fe_write(CharBackend *be, const uint8_t *buf, int len)
{
- Chardev *s = be->chr;
+ int ret = 0;
- if (!s) {
- return 0;
+ qemu_mutex_lock(&be->chr_lock);
+ if (be->chr) {
+ ret = qemu_chr_write(be->chr, buf, len, false);
}
+ qemu_mutex_unlock(&be->chr_lock);
- return qemu_chr_write(s, buf, len, false);
+ return ret;
}
int qemu_chr_fe_write_all(CharBackend *be, const uint8_t *buf, int len)
{
- Chardev *s = be->chr;
+ int ret = 0;
- if (!s) {
- return 0;
+ qemu_mutex_lock(&be->chr_lock);
+ if (be->chr) {
+ ret = qemu_chr_write(be->chr, buf, len, true);
}
+ qemu_mutex_unlock(&be->chr_lock);
- return qemu_chr_write(s, buf, len, true);
+ return ret;
}
int qemu_chr_fe_read_all(CharBackend *be, uint8_t *buf, int len)
@@ -182,7 +186,7 @@ Chardev *qemu_chr_fe_get_driver(CharBackend *be)
return be->chr;
}
-bool qemu_chr_fe_init(CharBackend *b, Chardev *s, Error **errp)
+bool qemu_chr_fe_connect(CharBackend *b, Chardev *s, Error **errp)
{
int tag = 0;
@@ -211,6 +215,16 @@ unavailable:
return false;
}
+bool qemu_chr_fe_init(CharBackend *b, Chardev *s, Error **errp)
+{
+ if (!qemu_chr_fe_connect(b, s, errp)) {
+ return false;
+ }
+
+ qemu_mutex_init(&b->chr_lock);
+ return true;
+}
+
void qemu_chr_fe_deinit(CharBackend *b, bool del)
{
assert(b);
@@ -228,6 +242,7 @@ void qemu_chr_fe_deinit(CharBackend *b, bool del)
object_unparent(OBJECT(b->chr));
}
b->chr = NULL;
+ qemu_mutex_destroy(&b->chr_lock);
}
}
@@ -951,6 +951,92 @@ ChardevReturn *qmp_chardev_add(const char *id, ChardevBackend *backend,
return ret;
}
+ChardevReturn *qmp_chardev_change(const char *id, ChardevBackend *backend,
+ Error **errp)
+{
+ CharBackend *be;
+ const ChardevClass *cc;
+ Chardev *chr, *chr_new;
+ bool closed_sent = false;
+ ChardevReturn *ret;
+
+ chr = qemu_chr_find(id);
+ if (!chr) {
+ error_setg(errp, "Chardev '%s' does not exist", id);
+ return NULL;
+ }
+
+ if (CHARDEV_IS_MUX(chr)) {
+ error_setg(errp, "Mux device hotswap not supported yet");
+ return NULL;
+ }
+
+ if (qemu_chr_replay(chr)) {
+ error_setg(errp,
+ "Chardev '%s' cannot be changed in record/replay mode", id);
+ return NULL;
+ }
+
+ be = chr->be;
+ if (!be) {
+ /* easy case */
+ object_unparent(OBJECT(chr));
+ return qmp_chardev_add(id, backend, errp);
+ }
+
+ if (!be->chr_be_change) {
+ error_setg(errp, "Chardev user does not support chardev hotswap");
+ return NULL;
+ }
+
+ cc = char_get_class(ChardevBackendKind_lookup[backend->type], errp);
+ if (!cc) {
+ return NULL;
+ }
+
+ chr_new = qemu_chardev_new(NULL, object_class_get_name(OBJECT_CLASS(cc)),
+ backend, errp);
+ if (!chr_new) {
+ return NULL;
+ }
+ chr_new->label = g_strdup(id);
+
+ if (chr->be_open && !chr_new->be_open) {
+ qemu_chr_be_event(chr, CHR_EVENT_CLOSED);
+ closed_sent = true;
+ }
+
+ qemu_mutex_lock(&be->chr_lock);
+ chr->be = NULL;
+ qemu_chr_fe_connect(be, chr_new, &error_abort);
+
+ if (be->chr_be_change(be->opaque) < 0) {
+ error_setg(errp, "Chardev '%s' change failed", chr_new->label);
+ chr_new->be = NULL;
+ qemu_chr_fe_connect(be, chr, &error_abort);
+ qemu_mutex_unlock(&be->chr_lock);
+ if (closed_sent) {
+ qemu_chr_be_event(chr, CHR_EVENT_OPENED);
+ }
+ object_unref(OBJECT(chr_new));
+ return NULL;
+ }
+ qemu_mutex_unlock(&be->chr_lock);
+
+ object_unparent(OBJECT(chr));
+ object_property_add_child(get_chardevs_root(), chr_new->label,
+ OBJECT(chr_new), &error_abort);
+ object_unref(OBJECT(chr_new));
+
+ ret = g_new0(ChardevReturn, 1);
+ if (CHARDEV_IS_PTY(chr_new)) {
+ ret->pty = g_strdup(chr_new->filename + 4);
+ ret->has_pty = true;
+ }
+
+ return ret;
+}
+
void qmp_chardev_remove(const char *id, Error **errp)
{
Chardev *chr;
@@ -5095,6 +5095,46 @@
'returns': 'ChardevReturn' }
##
+# @chardev-change:
+#
+# Change a character device backend
+#
+# @id: the chardev's ID, must exist
+# @backend: new backend type and parameters
+#
+# Returns: ChardevReturn.
+#
+# Since: 2.10
+#
+# Example:
+#
+# -> { "execute" : "chardev-change",
+# "arguments" : { "id" : "baz",
+# "backend" : { "type" : "pty", "data" : {} } } }
+# <- { "return": { "pty" : "/dev/pty/42" } }
+#
+# -> {"execute" : "chardev-change",
+# "arguments" : {
+# "id" : "charchannel2",
+# "backend" : {
+# "type" : "socket",
+# "data" : {
+# "addr" : {
+# "type" : "unix" ,
+# "data" : {
+# "path" : "/tmp/charchannel2.socket"
+# }
+# },
+# "server" : true,
+# "wait" : false }}}}
+# <- {"return": {}}
+#
+##
+{ 'command': 'chardev-change', 'data': {'id' : 'str',
+ 'backend' : 'ChardevBackend' },
+ 'returns': 'ChardevReturn' }
+
+##
# @chardev-remove:
#
# Remove a character device backend