diff --git a/backends/baum.c b/backends/baum.c
index 4cba79f..62aa784 100644
--- a/backends/baum.c
+++ b/backends/baum.c
@@ -611,8 +611,6 @@ CharDriverState *chr_baum_init(void)
 
     qemu_set_fd_handler(baum->brlapi_fd, baum_chr_read, NULL, baum);
 
-    qemu_chr_be_generic_open(chr);
-
     return chr;
 
 fail:
diff --git a/backends/msmouse.c b/backends/msmouse.c
index 0ac05a0..c0dbfcd 100644
--- a/backends/msmouse.c
+++ b/backends/msmouse.c
@@ -70,6 +70,7 @@ CharDriverState *qemu_chr_open_msmouse(void)
     chr = g_malloc0(sizeof(CharDriverState));
     chr->chr_write = msmouse_chr_write;
     chr->chr_close = msmouse_chr_close;
+    chr->explicit_be_open = true;
 
     qemu_add_mouse_event_handler(msmouse_event, chr, 0, "QEMU Microsoft Mouse");
 
diff --git a/include/sysemu/char.h b/include/sysemu/char.h
index 5e42c90..066c216 100644
--- a/include/sysemu/char.h
+++ b/include/sysemu/char.h
@@ -70,12 +70,12 @@ struct CharDriverState {
     void (*chr_set_echo)(struct CharDriverState *chr, bool echo);
     void (*chr_set_fe_open)(struct CharDriverState *chr, int fe_open);
     void *opaque;
-    int idle_tag;
     char *label;
     char *filename;
     int be_open;
     int fe_open;
     int explicit_fe_open;
+    int explicit_be_open;
     int avail_connections;
     QemuOpts *opts;
     QTAILQ_ENTRY(CharDriverState) next;
diff --git a/qemu-char.c b/qemu-char.c
index d04b429..0b6ae95 100644
--- a/qemu-char.c
+++ b/qemu-char.c
@@ -110,19 +110,9 @@ void qemu_chr_be_event(CharDriverState *s, int event)
     s->chr_event(s->handler_opaque, event);
 }
 
-static gboolean qemu_chr_be_generic_open_bh(gpointer opaque)
-{
-    CharDriverState *s = opaque;
-    qemu_chr_be_event(s, CHR_EVENT_OPENED);
-    s->idle_tag = 0;
-    return FALSE;
-}
-
 void qemu_chr_be_generic_open(CharDriverState *s)
 {
-    if (s->idle_tag == 0) {
-        s->idle_tag = g_idle_add(qemu_chr_be_generic_open_bh, s);
-    }
+    qemu_chr_be_event(s, CHR_EVENT_OPENED);
 }
 
 int qemu_chr_fe_write(CharDriverState *s, const uint8_t *buf, int len)
@@ -247,6 +237,7 @@ static CharDriverState *qemu_chr_open_null(void)
 
     chr = g_malloc0(sizeof(CharDriverState));
     chr->chr_write = null_chr_write;
+    chr->explicit_be_open = true;
     return chr;
 }
 
@@ -504,9 +495,6 @@ static CharDriverState *qemu_chr_open_mux(CharDriverState *drv)
     /* Frontend guest-open / -close notification is not support with muxes */
     chr->chr_set_fe_open = NULL;
 
-    /* Muxes are always open on creation */
-    qemu_chr_be_generic_open(chr);
-
     return chr;
 }
 
@@ -883,8 +871,6 @@ static CharDriverState *qemu_chr_open_fd(int fd_in, int fd_out)
     chr->chr_update_read_handler = fd_chr_update_read_handler;
     chr->chr_close = fd_chr_close;
 
-    qemu_chr_be_generic_open(chr);
-
     return chr;
 }
 
@@ -1243,6 +1229,7 @@ static CharDriverState *qemu_chr_open_pty(const char *id,
     chr->chr_update_read_handler = pty_chr_update_read_handler;
     chr->chr_close = pty_chr_close;
     chr->chr_add_watch = pty_chr_add_watch;
+    chr->explicit_be_open = true;
 
     s->fd = io_channel_from_fd(master_fd);
     s->timer_tag = 0;
@@ -1595,8 +1582,6 @@ static CharDriverState *qemu_chr_open_pp_fd(int fd)
     chr->chr_close = pp_close;
     chr->opaque = drv;
 
-    qemu_chr_be_generic_open(chr);
-
     return chr;
 }
 #endif /* __linux__ */
@@ -1650,6 +1635,7 @@ static CharDriverState *qemu_chr_open_pp_fd(int fd)
     chr->opaque = (void *)(intptr_t)fd;
     chr->chr_write = null_chr_write;
     chr->chr_ioctl = pp_ioctl;
+    chr->explicit_be_open = true;
     return chr;
 }
 #endif
@@ -1880,7 +1866,6 @@ static CharDriverState *qemu_chr_open_win_path(const char *filename)
         g_free(chr);
         return NULL;
     }
-    qemu_chr_be_generic_open(chr);
     return chr;
 }
 
@@ -1980,7 +1965,6 @@ static CharDriverState *qemu_chr_open_pipe(ChardevHostdev *opts)
         g_free(chr);
         return NULL;
     }
-    qemu_chr_be_generic_open(chr);
     return chr;
 }
 
@@ -1994,7 +1978,6 @@ static CharDriverState *qemu_chr_open_win_file(HANDLE fd_out)
     s->hcom = fd_out;
     chr->opaque = s;
     chr->chr_write = win_chr_write;
-    qemu_chr_be_generic_open(chr);
     return chr;
 }
 
@@ -2329,6 +2312,8 @@ static CharDriverState *qemu_chr_open_udp_fd(int fd)
     chr->chr_write = udp_chr_write;
     chr->chr_update_read_handler = udp_chr_update_read_handler;
     chr->chr_close = udp_chr_close;
+    /* be isn't opened until we get a connection */
+    chr->explicit_be_open = true;
     return chr;
 }
 
@@ -2731,6 +2716,8 @@ static CharDriverState *qemu_chr_open_socket_fd(int fd, bool do_nodelay,
     chr->get_msgfd = tcp_get_msgfd;
     chr->chr_add_client = tcp_chr_add_client;
     chr->chr_add_watch = tcp_chr_add_watch;
+    /* be isn't opened until we get a connection */
+    chr->explicit_be_open = true;
 
     if (is_listen) {
         s->listen_fd = fd;
@@ -3325,6 +3312,12 @@ CharDriverState *qemu_chr_new_from_opts(QemuOpts *opts,
     if (!chr->filename)
         chr->filename = g_strdup(qemu_opt_get(opts, "backend"));
     chr->init = init;
+    /* if we didn't create the chardev via qmp_chardev_add, we
+     * need to send the OPENED event here
+     */
+    if (!chr->explicit_be_open) {
+        qemu_chr_be_event(chr, CHR_EVENT_OPENED);
+    }
     QTAILQ_INSERT_TAIL(&chardevs, chr, next);
 
     if (qemu_opt_get_bool(opts, "mux", 0)) {
@@ -3804,6 +3797,9 @@ ChardevReturn *qmp_chardev_add(const char *id, ChardevBackend *backend,
         if (!chr->filename) {
             chr->filename = g_strdup(ChardevBackendKind_lookup[backend->kind]);
         }
+        if (!chr->explicit_be_open) {
+            qemu_chr_be_event(chr, CHR_EVENT_OPENED);
+        }
         QTAILQ_INSERT_TAIL(&chardevs, chr, next);
         return ret;
     } else {
diff --git a/spice-qemu-char.c b/spice-qemu-char.c
index f10970c..6d147a7 100644
--- a/spice-qemu-char.c
+++ b/spice-qemu-char.c
@@ -255,6 +255,7 @@ static CharDriverState *chr_open(const char *subtype)
     chr->chr_add_watch = spice_chr_add_watch;
     chr->chr_close = spice_chr_close;
     chr->chr_set_fe_open = spice_chr_set_fe_open;
+    chr->explicit_be_open = true;
 
     QLIST_INSERT_HEAD(&spice_chars, s, next);
 
diff --git a/ui/console.c b/ui/console.c
index b30853f..28bba6d 100644
--- a/ui/console.c
+++ b/ui/console.c
@@ -1788,6 +1788,10 @@ static CharDriverState *text_console_init(ChardevVC *vc)
     s->chr = chr;
     chr->opaque = s;
     chr->chr_set_echo = text_console_set_echo;
+    /* console/chardev init sometimes completes elsewhere in a 2nd
+     * stage, so defer OPENED events until they are fully initialized
+     */
+    chr->explicit_be_open = true;
 
     if (display_state) {
         text_console_do_init(chr, display_state);
diff --git a/ui/gtk.c b/ui/gtk.c
index 3bc2842..1fff798 100644
--- a/ui/gtk.c
+++ b/ui/gtk.c
@@ -1119,6 +1119,8 @@ static CharDriverState *gd_vc_handler(ChardevVC *unused)
 
     chr = g_malloc0(sizeof(*chr));
     chr->chr_write = gd_vc_chr_write;
+    /* defer OPENED events until our vc is fully initialized */
+    chr->explicit_be_open = true;
 
     vcs[nb_vcs++] = chr;
 
