From mboxrd@z Thu Jan 1 00:00:00 1970 Return-Path: X-Spam-Checker-Version: SpamAssassin 3.4.0 (2014-02-07) on aws-us-west-2-korg-lkml-1.web.codeaurora.org X-Spam-Level: X-Spam-Status: No, score=-10.8 required=3.0 tests=BAYES_00,DKIM_SIGNED, DKIM_VALID,HEADER_FROM_DIFFERENT_DOMAINS,HTML_MESSAGE,INCLUDES_CR_TRAILER, INCLUDES_PATCH,MAILING_LIST_MULTI,SPF_HELO_NONE,SPF_PASS,URIBL_BLOCKED autolearn=ham autolearn_force=no version=3.4.0 Received: from mail.kernel.org (mail.kernel.org [198.145.29.99]) by smtp.lore.kernel.org (Postfix) with ESMTP id 897A0C4361B for ; Wed, 16 Dec 2020 22:55:22 +0000 (UTC) Received: from lists.gnu.org (lists.gnu.org [209.51.188.17]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by mail.kernel.org (Postfix) with ESMTPS id E949F23716 for ; Wed, 16 Dec 2020 22:55:21 +0000 (UTC) DMARC-Filter: OpenDMARC Filter v1.3.2 mail.kernel.org E949F23716 Authentication-Results: mail.kernel.org; dmarc=none (p=none dis=none) header.from=gorski.net Authentication-Results: mail.kernel.org; spf=pass smtp.mailfrom=qemu-devel-bounces+qemu-devel=archiver.kernel.org@nongnu.org Received: from localhost ([::1]:54750 helo=lists1p.gnu.org) by lists.gnu.org with esmtp (Exim 4.90_1) (envelope-from ) id 1kpfhY-0000r6-GZ for qemu-devel@archiver.kernel.org; Wed, 16 Dec 2020 17:55:20 -0500 Received: from eggs.gnu.org ([2001:470:142:3::10]:58700) by lists.gnu.org with esmtps (TLS1.2:ECDHE_RSA_AES_256_GCM_SHA384:256) (Exim 4.90_1) (envelope-from ) id 1kpewu-0001mG-A2 for qemu-devel@nongnu.org; Wed, 16 Dec 2020 17:07:08 -0500 Received: from mail-ed1-x536.google.com ([2a00:1450:4864:20::536]:39647) by eggs.gnu.org with esmtps (TLS1.2:ECDHE_RSA_AES_128_GCM_SHA256:128) (Exim 4.90_1) (envelope-from ) id 1kpewr-0005PW-Hn for qemu-devel@nongnu.org; Wed, 16 Dec 2020 17:07:08 -0500 Received: by mail-ed1-x536.google.com with SMTP id c7so26529512edv.6 for ; Wed, 16 Dec 2020 14:07:04 -0800 (PST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gorski-net.20150623.gappssmtp.com; s=20150623; h=mime-version:from:date:message-id:subject:to:cc; bh=GKAhj4tB5HeYcFNc/jbem46mrLhdJ63kPO0cbvcjq3o=; b=GFkNyj8Ier6d3+0Dlh56QoGcpVQ/mFENVfQ8iJs1TizCbvEBUlqhUdPzkoyAHSYmEr bKcbDvzkgk6I32jEv/kJhvPYD0sp7ZjaQ7AsaOJFB4LUY95eNHYhvg25ccXOuuij1QHc +tjz0doaPi4JKTuUouq38xLny55lC9vWT+2Nv/a+p8RVDrj/DMcjKz9tw6Q48cXF8W9v qE8y3bMFOmIs0QCSv/AWflmQYNg1+rZ/pzfTfUAVvYlRbNyTgDeGzI7NP0Cgbiea1rFl 6Vctji9+G09SXjdmxOaE9Iz344D1VMQyjdp4PXdvjhLkgAZ8KXynWLo5DXk2cSSWnMwS d4tg== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20161025; h=x-gm-message-state:mime-version:from:date:message-id:subject:to:cc; bh=GKAhj4tB5HeYcFNc/jbem46mrLhdJ63kPO0cbvcjq3o=; b=hqC0FLBWRxrA3rsdfoemTz3wxSDeGChRud+hldqW+3NmYPpHbZrXQRhg8Fu2xmk06z /zSTTnxAP2lcJ0g4elbYPQGFgXqMEPKzfInNIJjdtwU9kIHNuGlHbc8DCwgJsrzrM/sV 562l9Fcy5YDI/FbNCsR3BgvJlOEK+xMKNd4HYHbcK6d3EHxqMkjl2O95z5Om4ilcdX8l kxECoR+f6gcbM230URhTwR3vJ1W7AfqXrW8A6QVrkoIgJP8OC9+XS4gBzJ5cxXzKe/V1 e21d/b5PnIaqN0kDA4JIHmwizFxgtPb5WG57MNh+pXTLD3xaYKUWnXuYyURndJa40AZ1 CznQ== X-Gm-Message-State: AOAM533wsDEi7FjoWcRrJmNVl/wUBKGDFSBpWnbb73fs+NrlJQ6N2aeV NdiihL5n7U3652wB52GJjVLGzA9nlWKfAqdk01euGelHogEO0RF3 X-Google-Smtp-Source: ABdhPJwBS3yHqViqBlJP0ZpMpE6DbkE4fUbIXnoNyIyWHDygZtzHHRGaNmnElETfuSmzSC1vLaX3CnYUOX4ewbpQ7sM= X-Received: by 2002:a05:6402:1684:: with SMTP id a4mr19035413edv.348.1608156422817; Wed, 16 Dec 2020 14:07:02 -0800 (PST) MIME-Version: 1.0 From: "Darrin M. Gorski" Date: Wed, 16 Dec 2020 17:06:51 -0500 Message-ID: Subject: [PATCH v1 1/1] chardev: enable guest socket status/crontrol via DTR and DCD To: qemu-devel@nongnu.org Content-Type: multipart/alternative; boundary="000000000000535ef605b69c1850" Received-SPF: pass client-ip=2a00:1450:4864:20::536; envelope-from=darrin@gorski.net; helo=mail-ed1-x536.google.com X-Spam_score_int: -18 X-Spam_score: -1.9 X-Spam_bar: - X-Spam_report: (-1.9 / 5.0 requ) BAYES_00=-1.9, DKIM_SIGNED=0.1, DKIM_VALID=-0.1, HTML_MESSAGE=0.001, RCVD_IN_DNSWL_NONE=-0.0001, SPF_HELO_NONE=0.001, SPF_PASS=-0.001 autolearn=ham autolearn_force=no X-Spam_action: no action X-Mailman-Approved-At: Wed, 16 Dec 2020 17:53:48 -0500 X-BeenThere: qemu-devel@nongnu.org X-Mailman-Version: 2.1.23 Precedence: list List-Id: List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Cc: =?UTF-8?B?TWFyYy1BbmRyw6kgTHVyZWF1?= Errors-To: qemu-devel-bounces+qemu-devel=archiver.kernel.org@nongnu.org Sender: "Qemu-devel" --000000000000535ef605b69c1850 Content-Type: text/plain; charset="UTF-8" This patch adds a 'modemctl' option to "-chardev socket" to enable control of the socket via the guest serial port. The default state of the option is disabled. 1. disconnect a connected socket when DTR transitions to low, also reject new connections while DTR is low. 2. provide socket connection status through the carrier detect line (CD or DCD) on the guest serial port Buglink: https://bugs.launchpad.net/qemu/+bug/1213196 Signed-off-by: Darrin M. Gorski diff --git a/chardev/char-socket.c b/chardev/char-socket.c index 213a4c8dd0..94dd28e0cd 100644 --- a/chardev/char-socket.c +++ b/chardev/char-socket.c @@ -36,6 +36,7 @@ #include "qapi/qapi-visit-sockets.h" #include "chardev/char-io.h" +#include "chardev/char-serial.h" #include "qom/object.h" /***********************************************************/ @@ -85,6 +86,9 @@ struct SocketChardev { bool connect_err_reported; QIOTask *connect_task; + + bool is_modemctl; + int modem_state; }; typedef struct SocketChardev SocketChardev; @@ -98,12 +102,18 @@ static void tcp_chr_change_state(SocketChardev *s, TCPChardevState state) { switch (state) { case TCP_CHARDEV_STATE_DISCONNECTED: + if(s->is_modemctl) { + s->modem_state &= ~(CHR_TIOCM_CAR); + } break; case TCP_CHARDEV_STATE_CONNECTING: assert(s->state == TCP_CHARDEV_STATE_DISCONNECTED); break; case TCP_CHARDEV_STATE_CONNECTED: assert(s->state == TCP_CHARDEV_STATE_CONNECTING); + if(s->is_modemctl) { + s->modem_state |= CHR_TIOCM_CAR; + } break; } s->state = state; @@ -947,6 +957,12 @@ static void tcp_chr_accept(QIONetListener *listener, tcp_chr_change_state(s, TCP_CHARDEV_STATE_CONNECTING); tcp_chr_set_client_ioc_name(chr, cioc); tcp_chr_new_client(chr, cioc); + + if(s->is_modemctl) { + if(!(s->modem_state & CHR_TIOCM_DTR)) { + tcp_chr_disconnect(chr); /* disconnect if DTR is low */ + } + } } @@ -1322,12 +1338,17 @@ static void qmp_chardev_open_socket(Chardev *chr, bool is_tn3270 = sock->has_tn3270 ? sock->tn3270 : false; bool is_waitconnect = sock->has_wait ? sock->wait : false; bool is_websock = sock->has_websocket ? sock->websocket : false; + bool is_modemctl = sock->has_modemctl ? sock->modemctl : false; int64_t reconnect = sock->has_reconnect ? sock->reconnect : 0; SocketAddress *addr; s->is_listen = is_listen; s->is_telnet = is_telnet; s->is_tn3270 = is_tn3270; + s->is_modemctl = is_modemctl; + if(is_modemctl) { + s->modem_state = CHR_TIOCM_CTS | CHR_TIOCM_DSR; + } s->is_websock = is_websock; s->do_nodelay = do_nodelay; if (sock->tls_creds) { @@ -1448,6 +1469,8 @@ static void qemu_chr_parse_socket(QemuOpts *opts, ChardevBackend *backend, sock->tls_creds = g_strdup(qemu_opt_get(opts, "tls-creds")); sock->has_tls_authz = qemu_opt_get(opts, "tls-authz"); sock->tls_authz = g_strdup(qemu_opt_get(opts, "tls-authz")); + sock->has_modemctl = qemu_opt_get(opts, "modemctl"); + sock->modemctl = qemu_opt_get_bool(opts, "modemctl", false); addr = g_new0(SocketAddressLegacy, 1); if (path) { @@ -1501,6 +1524,51 @@ char_socket_get_connected(Object *obj, Error **errp) return s->state == TCP_CHARDEV_STATE_CONNECTED; } +/* ioctl support: allow guest to control/track socket state + * via modem control lines (DCD/DTR) + */ +static int +char_socket_ioctl(Chardev *chr, int cmd, void *arg) +{ + SocketChardev *s = SOCKET_CHARDEV(chr); + + if(!(s->is_modemctl)) { + return -ENOTSUP; + } + + switch (cmd) { + case CHR_IOCTL_SERIAL_GET_TIOCM: + { + int *targ = (int *)arg; + *targ = s->modem_state; + } + break; + case CHR_IOCTL_SERIAL_SET_TIOCM: + { + int sarg = *(int *)arg; + if (sarg & CHR_TIOCM_RTS) { + s->modem_state |= CHR_TIOCM_RTS; + } else { + s->modem_state &= ~(CHR_TIOCM_RTS); + } + if (sarg & CHR_TIOCM_DTR) { + s->modem_state |= CHR_TIOCM_DTR; + } else { + s->modem_state &= ~(CHR_TIOCM_DTR); + /* disconnect if DTR goes low */ + if(s->state == TCP_CHARDEV_STATE_CONNECTED) { + tcp_chr_disconnect(chr); + } + } + } + break; + default: + return -ENOTSUP; + } + + return 0; +} + static void char_socket_class_init(ObjectClass *oc, void *data) { ChardevClass *cc = CHARDEV_CLASS(oc); @@ -1516,6 +1584,7 @@ static void char_socket_class_init(ObjectClass *oc, void *data) cc->chr_add_client = tcp_chr_add_client; cc->chr_add_watch = tcp_chr_add_watch; cc->chr_update_read_handler = tcp_chr_update_read_handler; + cc->chr_ioctl = char_socket_ioctl; object_class_property_add(oc, "addr", "SocketAddress", char_socket_get_addr, NULL, diff --git a/chardev/char.c b/chardev/char.c index a9b8c5a9aa..601d818f81 100644 --- a/chardev/char.c +++ b/chardev/char.c @@ -928,6 +928,9 @@ QemuOptsList qemu_chardev_opts = { },{ .name = "logappend", .type = QEMU_OPT_BOOL, + },{ + .name = "modemctl", + .type = QEMU_OPT_BOOL, #ifdef CONFIG_LINUX },{ .name = "tight", diff --git a/qapi/char.json b/qapi/char.json index 58338ed62d..f352bd6350 100644 --- a/qapi/char.json +++ b/qapi/char.json @@ -271,6 +271,9 @@ # then attempt a reconnect after the given number of seconds. # Setting this to zero disables this function. (default: 0) # (Since: 2.2) +# @modemctl: allow guest to use modem control signals to control/monitor +# the socket state (CD follows is_connected, DTR influences +# connect/accept) (default: false) (Since: 5.2) # # Since: 1.4 ## @@ -284,6 +287,7 @@ '*telnet': 'bool', '*tn3270': 'bool', '*websocket': 'bool', + '*modemctl': 'bool', '*reconnect': 'int' }, 'base': 'ChardevCommon' } diff --git a/qemu-options.hx b/qemu-options.hx index 459c916d3d..f09072afbf 100644 --- a/qemu-options.hx +++ b/qemu-options.hx @@ -2991,11 +2991,13 @@ DEFHEADING(Character device options:) DEF("chardev", HAS_ARG, QEMU_OPTION_chardev, "-chardev help\n" "-chardev null,id=id[,mux=on|off][,logfile=PATH][,logappend=on|off]\n" - "-chardev socket,id=id[,host=host],port=port[,to=to][,ipv4][,ipv6][,nodelay][,reconnect=seconds]\n" - " [,server][,nowait][,telnet][,websocket][,reconnect=seconds][,mux=on|off]\n" - " [,logfile=PATH][,logappend=on|off][,tls-creds=ID][,tls-authz=ID] (tcp)\n" - "-chardev socket,id=id,path=path[,server][,nowait][,telnet][,websocket][,reconnect=seconds]\n" - " [,mux=on|off][,logfile=PATH][,logappend=on|off][,abstract=on|off][,tight=on|off] (unix)\n" + "-chardev socket,id=id[,host=host],port=port[,to=to][,ipv4][,ipv6][,nodelay]\n" + " [,reconnect=seconds][,server][,nowait][,telnet][,websocket]\n" + " [,modemctl][,mux=on|off][,logfile=PATH][,logappend=on|off]\n" + " [,tls-creds=ID][,tls-authz=ID] (tcp)\n" + "-chardev socket,id=id,path=path[,server][,nowait][,telnet][,websocket]\n" + " [,reconnect=seconds][,modemctl][,mux=on|off][,logfile=PATH]\n" + " [,logappend=on|off][,abstract=on|off][,tight=on|off] (unix)\n" "-chardev udp,id=id[,host=host],port=port[,localaddr=localaddr]\n" " [,localport=localport][,ipv4][,ipv6][,mux=on|off]\n" " [,logfile=PATH][,logappend=on|off]\n" --000000000000535ef605b69c1850 Content-Type: text/html; charset="UTF-8" Content-Transfer-Encoding: quoted-printable

This patch adds a 'modemctl' option to "-ch= ardev socket" to enable control of the socket via the guest serial por= t.
The default state of the = option is disabled.

1. disconne= ct a connected socket when DTR transitions to low, also reject new connecti= ons while DTR is low.
2.= provide socket connection status through the carrier detect line (CD or DC= D) on the guest serial port

Buglink: https://bugs.launchpad.net/qemu/+bug/12=
13196
Signed-off-by: Darrin M. Gorski <darrin@gorski.net>

diff --git a/chardev/char-socket.c b/chardev= /char-socket.c
index 213a4c8dd0..94dd28e0cd 100644
--- a/chardev/char= -socket.c
+++ b/chardev/char-socket.c
@@ -36,6 +36,7 @@
=C2=A0#inc= lude "qapi/qapi-visit-sockets.h"

=C2=A0#include "char= dev/char-io.h"
+#include "chardev/char-serial.h"
=C2= =A0#include "qom/object.h"

=C2=A0/************************= ***********************************/
@@ -85,6 +86,9 @@ struct SocketChar= dev {
=C2=A0 =C2=A0 =C2=A0bool connect_err_reported;

=C2=A0 =C2= =A0 =C2=A0QIOTask *connect_task;
+
+ =C2=A0 =C2=A0bool is_modemctl;+ =C2=A0 =C2=A0int modem_state;
=C2=A0};
=C2=A0typedef struct Socke= tChardev SocketChardev;

@@ -98,12 +102,18 @@ static void tcp_chr_cha= nge_state(SocketChardev *s, TCPChardevState state)
=C2=A0{
=C2=A0 =C2= =A0 =C2=A0switch (state) {
=C2=A0 =C2=A0 =C2=A0case TCP_CHARDEV_STATE_DI= SCONNECTED:
+ =C2=A0 =C2=A0 =C2=A0 =C2=A0if(s->is_modemctl) {
+ = =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0s->modem_state &=3D ~(CHR_T= IOCM_CAR);
+ =C2=A0 =C2=A0 =C2=A0 =C2=A0}
=C2=A0 =C2=A0 =C2=A0 =C2=A0= =C2=A0break;
=C2=A0 =C2=A0 =C2=A0case TCP_CHARDEV_STATE_CONNECTING:
= =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0assert(s->state =3D=3D TCP_CHARDEV_STA= TE_DISCONNECTED);
=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0break;
=C2=A0 =C2= =A0 =C2=A0case TCP_CHARDEV_STATE_CONNECTED:
=C2=A0 =C2=A0 =C2=A0 =C2=A0 = =C2=A0assert(s->state =3D=3D TCP_CHARDEV_STATE_CONNECTING);
+ =C2=A0 = =C2=A0 =C2=A0 =C2=A0if(s->is_modemctl) {
+ =C2=A0 =C2=A0 =C2=A0 =C2= =A0 =C2=A0 =C2=A0s->modem_state |=3D CHR_TIOCM_CAR;
+ =C2=A0 =C2=A0 = =C2=A0 =C2=A0}
=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0break;
=C2=A0 =C2=A0= =C2=A0}
=C2=A0 =C2=A0 =C2=A0s->state =3D state;
@@ -947,6 +957,12= @@ static void tcp_chr_accept(QIONetListener *listener,
=C2=A0 =C2=A0 = =C2=A0tcp_chr_change_state(s, TCP_CHARDEV_STATE_CONNECTING);
=C2=A0 =C2= =A0 =C2=A0tcp_chr_set_client_ioc_name(chr, cioc);
=C2=A0 =C2=A0 =C2=A0tc= p_chr_new_client(chr, cioc);
+
+ =C2=A0 =C2=A0if(s->is_modemctl) {=
+ =C2=A0 =C2=A0 =C2=A0 =C2=A0if(!(s->modem_state & CHR_TIOCM_DTR= )) {
+ =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0tcp_chr_disconnect(chr);= /* disconnect if DTR is low */
+ =C2=A0 =C2=A0 =C2=A0 =C2=A0}
+ =C2= =A0 =C2=A0}
=C2=A0}


@@ -1322,12 +1338,17 @@ static void qmp_c= hardev_open_socket(Chardev *chr,
=C2=A0 =C2=A0 =C2=A0bool is_tn3270 =C2= =A0 =C2=A0 =C2=A0=3D sock->has_tn3270 =C2=A0? sock->tn3270 =C2=A0: fa= lse;
=C2=A0 =C2=A0 =C2=A0bool is_waitconnect =3D sock->has_wait =C2= =A0 =C2=A0? sock->wait =C2=A0 =C2=A0: false;
=C2=A0 =C2=A0 =C2=A0bool= is_websock =C2=A0 =C2=A0 =3D sock->has_websocket ? sock->websocket := false;
+ =C2=A0 =C2=A0bool is_modemctl =C2=A0 =C2=A0 =3D sock->has_m= odemctl ? sock->modemctl : false;
=C2=A0 =C2=A0 =C2=A0int64_t reconne= ct =C2=A0 =3D sock->has_reconnect ? sock->reconnect : 0;
=C2=A0 = =C2=A0 =C2=A0SocketAddress *addr;

=C2=A0 =C2=A0 =C2=A0s->is_liste= n =3D is_listen;
=C2=A0 =C2=A0 =C2=A0s->is_telnet =3D is_telnet;
= =C2=A0 =C2=A0 =C2=A0s->is_tn3270 =3D is_tn3270;
+ =C2=A0 =C2=A0s->= is_modemctl =3D is_modemctl;
+ =C2=A0 =C2=A0if(is_modemctl) {
+ =C2= =A0 =C2=A0 =C2=A0s->modem_state =3D CHR_TIOCM_CTS | CHR_TIOCM_DSR;
+ = =C2=A0 =C2=A0}
=C2=A0 =C2=A0 =C2=A0s->is_websock =3D is_websock;
= =C2=A0 =C2=A0 =C2=A0s->do_nodelay =3D do_nodelay;
=C2=A0 =C2=A0 =C2= =A0if (sock->tls_creds) {
@@ -1448,6 +1469,8 @@ static void qemu_chr_= parse_socket(QemuOpts *opts, ChardevBackend *backend,
=C2=A0 =C2=A0 =C2= =A0sock->tls_creds =3D g_strdup(qemu_opt_get(opts, "tls-creds"= ));
=C2=A0 =C2=A0 =C2=A0sock->has_tls_authz =3D qemu_opt_get(opts, &q= uot;tls-authz");
=C2=A0 =C2=A0 =C2=A0sock->tls_authz =3D g_strdu= p(qemu_opt_get(opts, "tls-authz"));
+ =C2=A0 =C2=A0sock->ha= s_modemctl =3D qemu_opt_get(opts, "modemctl");
+ =C2=A0 =C2=A0= sock->modemctl =3D qemu_opt_get_bool(opts, "modemctl", false);=

=C2=A0 =C2=A0 =C2=A0addr =3D g_new0(SocketAddressLegacy, 1);
=C2= =A0 =C2=A0 =C2=A0if (path) {
@@ -1501,6 +1524,51 @@ char_socket_get_conn= ected(Object *obj, Error **errp)
=C2=A0 =C2=A0 =C2=A0return s->state = =3D=3D TCP_CHARDEV_STATE_CONNECTED;
=C2=A0}

+/* ioctl support: al= low guest to control/track socket state
+ * via modem control lines (DCD= /DTR)
+ */
+static int
+char_socket_ioctl(Chardev *chr, int cmd, v= oid *arg)
+{
+ =C2=A0 =C2=A0SocketChardev *s =3D SOCKET_CHARDEV(chr);=
+
+ =C2=A0 =C2=A0if(!(s->is_modemctl)) {
+ =C2=A0 =C2=A0 =C2= =A0 =C2=A0return -ENOTSUP;
+ =C2=A0 =C2=A0}
+
+ =C2=A0 =C2=A0switc= h (cmd) {
+ =C2=A0 =C2=A0 =C2=A0 =C2=A0case CHR_IOCTL_SERIAL_GET_TIOCM:<= br>+ =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0{
+ =C2=A0 =C2=A0 =C2=A0 = =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0int *targ =3D (int *)arg;
+ =C2=A0 =C2= =A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0*targ =3D s->modem_state;+ =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0}
+ =C2=A0 =C2=A0 =C2=A0 = =C2=A0 =C2=A0 =C2=A0break;
+ =C2=A0 =C2=A0 =C2=A0 =C2=A0case CHR_IOCTL_S= ERIAL_SET_TIOCM:
+ =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0{
+ =C2= =A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0int sarg =3D *(int *)ar= g;
+ =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0if (sarg &am= p; CHR_TIOCM_RTS) {
+ =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 = =C2=A0 =C2=A0 =C2=A0s->modem_state |=3D CHR_TIOCM_RTS;
+ =C2=A0 =C2= =A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0} else {
+ =C2=A0 =C2=A0 = =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0s->modem_state &a= mp;=3D ~(CHR_TIOCM_RTS);
+ =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2= =A0 =C2=A0}
+ =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0if = (sarg & CHR_TIOCM_DTR) {
+ =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0= =C2=A0 =C2=A0 =C2=A0 =C2=A0s->modem_state |=3D CHR_TIOCM_DTR;
+ =C2= =A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0} else {
+ =C2=A0 = =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0s->modem_s= tate &=3D ~(CHR_TIOCM_DTR);
+ =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2= =A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0/* disconnect if DTR goes low */
+ =C2=A0= =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0if(s->sta= te =3D=3D TCP_CHARDEV_STATE_CONNECTED) {
+ =C2=A0 =C2=A0 =C2=A0 =C2=A0 = =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0tcp_chr_disconnect(c= hr);
+ =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 = =C2=A0}
+ =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0}
+ = =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0}
+ =C2=A0 =C2=A0 =C2=A0 =C2=A0= =C2=A0 =C2=A0break;
+ =C2=A0 =C2=A0 =C2=A0 =C2=A0default:
+ =C2=A0 = =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0return -ENOTSUP;
+ =C2=A0 =C2=A0}
+=
+ =C2=A0 =C2=A0return 0;
+}
+
=C2=A0static void char_socket_cl= ass_init(ObjectClass *oc, void *data)
=C2=A0{
=C2=A0 =C2=A0 =C2=A0Cha= rdevClass *cc =3D CHARDEV_CLASS(oc);
@@ -1516,6 +1584,7 @@ static void c= har_socket_class_init(ObjectClass *oc, void *data)
=C2=A0 =C2=A0 =C2=A0c= c->chr_add_client =3D tcp_chr_add_client;
=C2=A0 =C2=A0 =C2=A0cc->= chr_add_watch =3D tcp_chr_add_watch;
=C2=A0 =C2=A0 =C2=A0cc->chr_upda= te_read_handler =3D tcp_chr_update_read_handler;
+ =C2=A0 =C2=A0cc->c= hr_ioctl =3D char_socket_ioctl;

=C2=A0 =C2=A0 =C2=A0object_class_pro= perty_add(oc, "addr", "SocketAddress",
=C2=A0 =C2=A0= =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2= =A0 =C2=A0 =C2=A0 =C2=A0char_socket_get_addr, NULL,
diff --git a/chardev= /char.c b/chardev/char.c
index a9b8c5a9aa..601d818f81 100644
--- a/ch= ardev/char.c
+++ b/chardev/char.c
@@ -928,6 +928,9 @@ QemuOptsList qe= mu_chardev_opts =3D {
=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0},{
=C2=A0 = =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0.name =3D "logappend",=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0.type =3D QEMU_OPT_BOOL,<= br>+ =C2=A0 =C2=A0 =C2=A0 =C2=A0},{
+ =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0= =C2=A0.name =3D "modemctl",
+ =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2= =A0 =C2=A0.type =3D QEMU_OPT_BOOL,
=C2=A0#ifdef CONFIG_LINUX
=C2=A0 = =C2=A0 =C2=A0 =C2=A0 =C2=A0},{
=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0= =C2=A0.name =3D "tight",
diff --git a/qapi/char.json b/qapi/c= har.json
index 58338ed62d..f352bd6350 100644
--- a/qapi/char.json
= +++ b/qapi/char.json
@@ -271,6 +271,9 @@
=C2=A0# =C2=A0 =C2=A0 =C2=A0= =C2=A0 =C2=A0 =C2=A0 then attempt a reconnect after the given number of se= conds.
=C2=A0# =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 Setting this to= zero disables this function. (default: 0)
=C2=A0# =C2=A0 =C2=A0 =C2=A0 = =C2=A0 =C2=A0 =C2=A0 (Since: 2.2)
+# @modemctl: allow guest to use modem= control signals to control/monitor
+# =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2= =A0 =C2=A0the socket state (CD follows is_connected, DTR influences
+# = =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0connect/accept) (default: false) (= Since: 5.2)
=C2=A0#
=C2=A0# Since: 1.4
=C2=A0##
@@ -284,6 +287,= 7 @@
=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0'*telnet': = 'bool',
=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0'*tn= 3270': 'bool',
=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2= =A0'*websocket': 'bool',
+ =C2=A0 =C2=A0 =C2=A0 =C2=A0 = =C2=A0 =C2=A0'*modemctl': 'bool',
=C2=A0 =C2=A0 =C2=A0 = =C2=A0 =C2=A0 =C2=A0 =C2=A0'*reconnect': 'int' },
=C2=A0= =C2=A0'base': 'ChardevCommon' }

diff --git a/qemu-o= ptions.hx b/qemu-options.hx
index 459c916d3d..f09072afbf 100644
--- a= /qemu-options.hx
+++ b/qemu-options.hx
@@ -2991,11 +2991,13 @@ DEFHEA= DING(Character device options:)
=C2=A0DEF("chardev", HAS_ARG, = QEMU_OPTION_chardev,
=C2=A0 =C2=A0 =C2=A0"-chardev help\n"
= =C2=A0 =C2=A0 =C2=A0"-chardev null,id=3Did[,mux=3Don|off][,logfile=3DP= ATH][,logappend=3Don|off]\n"
- =C2=A0 =C2=A0"-chardev socket,i= d=3Did[,host=3Dhost],port=3Dport[,to=3Dto][,ipv4][,ipv6][,nodelay][,reconne= ct=3Dseconds]\n"
- =C2=A0 =C2=A0" =C2=A0 =C2=A0 =C2=A0 =C2=A0 = [,server][,nowait][,telnet][,websocket][,reconnect=3Dseconds][,mux=3Don|off= ]\n"
- =C2=A0 =C2=A0" =C2=A0 =C2=A0 =C2=A0 =C2=A0 [,logfile=3D= PATH][,logappend=3Don|off][,tls-creds=3DID][,tls-authz=3DID] (tcp)\n"<= br>- =C2=A0 =C2=A0"-chardev socket,id=3Did,path=3Dpath[,server][,nowai= t][,telnet][,websocket][,reconnect=3Dseconds]\n"
- =C2=A0 =C2=A0&qu= ot; =C2=A0 =C2=A0 =C2=A0 =C2=A0 [,mux=3Don|off][,logfile=3DPATH][,logappend= =3Don|off][,abstract=3Don|off][,tight=3Don|off] (unix)\n"
+ =C2=A0 = =C2=A0"-chardev socket,id=3Did[,host=3Dhost],port=3Dport[,to=3Dto][,ip= v4][,ipv6][,nodelay]\n"
+ =C2=A0 =C2=A0" =C2=A0 =C2=A0 =C2=A0 = =C2=A0 [,reconnect=3Dseconds][,server][,nowait][,telnet][,websocket]\n"= ;
+ =C2=A0 =C2=A0" =C2=A0 =C2=A0 =C2=A0 =C2=A0 [,modemctl][,mux=3Do= n|off][,logfile=3DPATH][,logappend=3Don|off]\n"
+ =C2=A0 =C2=A0&quo= t; =C2=A0 =C2=A0 =C2=A0 =C2=A0 [,tls-creds=3DID][,tls-authz=3DID] (tcp)\n&q= uot;
+ =C2=A0 =C2=A0"-chardev socket,id=3Did,path=3Dpath[,server][,= nowait][,telnet][,websocket]\n"
+ =C2=A0 =C2=A0" =C2=A0 =C2=A0= =C2=A0 =C2=A0 [,reconnect=3Dseconds][,modemctl][,mux=3Don|off][,logfile=3D= PATH]\n"
+ =C2=A0 =C2=A0" =C2=A0 =C2=A0 =C2=A0 =C2=A0 [,logapp= end=3Don|off][,abstract=3Don|off][,tight=3Don|off] (unix)\n"
=C2=A0= =C2=A0 =C2=A0"-chardev udp,id=3Did[,host=3Dhost],port=3Dport[,localad= dr=3Dlocaladdr]\n"
=C2=A0 =C2=A0 =C2=A0" =C2=A0 =C2=A0 =C2=A0 = =C2=A0 [,localport=3Dlocalport][,ipv4][,ipv6][,mux=3Don|off]\n"
=C2= =A0 =C2=A0 =C2=A0" =C2=A0 =C2=A0 =C2=A0 =C2=A0 [,logfile=3DPATH][,loga= ppend=3Don|off]\n"

--000000000000535ef605b69c1850--