qemu-devel.nongnu.org archive mirror
 help / color / mirror / Atom feed
* [PATCH v1 1/1] chardev: enable guest socket status/crontrol via DTR and DCD
@ 2020-12-16 22:06 Darrin M. Gorski
  2020-12-17 14:16 ` Marc-André Lureau
                   ` (2 more replies)
  0 siblings, 3 replies; 8+ messages in thread
From: Darrin M. Gorski @ 2020-12-16 22:06 UTC (permalink / raw)
  To: qemu-devel; +Cc: Marc-André Lureau

[-- Attachment #1: Type: text/plain, Size: 7893 bytes --]

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 <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 @@
 #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"

[-- Attachment #2: Type: text/html, Size: 10502 bytes --]

^ permalink raw reply related	[flat|nested] 8+ messages in thread

* Re: [PATCH v1 1/1] chardev: enable guest socket status/crontrol via DTR and DCD
  2020-12-16 22:06 [PATCH v1 1/1] chardev: enable guest socket status/crontrol via DTR and DCD Darrin M. Gorski
@ 2020-12-17 14:16 ` Marc-André Lureau
  2020-12-17 17:54   ` Darrin M. Gorski
  2020-12-23 22:13 ` Eric Blake
  2022-01-12 15:20 ` Aaro Koskinen
  2 siblings, 1 reply; 8+ messages in thread
From: Marc-André Lureau @ 2020-12-17 14:16 UTC (permalink / raw)
  To: Darrin M. Gorski, Paolo Bonzini; +Cc: QEMU

[-- Attachment #1: Type: text/plain, Size: 8901 bytes --]

Hi

On Thu, Dec 17, 2020 at 2:54 AM Darrin M. Gorski <darrin@gorski.net> wrote:

>
> 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
>

That sounds like a good idea to me, but others may have different opinions.


> Buglink: https://bugs.launchpad.net/qemu/+bug/1213196
>
> 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 @@
>  #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;
>

Can we make this generic over all chardevs by moving it to the base class?

 };
>  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"
>

This feature will need some new tests in tests/test-char.c.

-- 
Marc-André Lureau

[-- Attachment #2: Type: text/html, Size: 11668 bytes --]

^ permalink raw reply	[flat|nested] 8+ messages in thread

* Re: [PATCH v1 1/1] chardev: enable guest socket status/crontrol via DTR and DCD
  2020-12-17 14:16 ` Marc-André Lureau
@ 2020-12-17 17:54   ` Darrin M. Gorski
  2020-12-18 11:57     ` Marc-André Lureau
  0 siblings, 1 reply; 8+ messages in thread
From: Darrin M. Gorski @ 2020-12-17 17:54 UTC (permalink / raw)
  To: Marc-André Lureau; +Cc: Paolo Bonzini, QEMU

[-- Attachment #1: Type: text/plain, Size: 10142 bytes --]

> That sounds like a good idea to me, but others may have different
opinions.

My original idea was simply to allow DCD to track socket state, because
this is what I need.  The DTR idea came from the referenced bug.  The
feature defaults to disabled like many of the other edge cases (like telnet
and tn3270), and it's a reasonably small non-intrusive change.

> Can we make this generic over all chardevs by moving it to the base class?

I'll take a closer look at the base class. I admit I did not spend much
time studying it.  This seemed like a socket specific feature to me.  It
seems like it might conflict with other chardevs like pty and serial as
they are already using ioctl for interaction with real tty devices.

> This feature will need some new tests in tests/test-char.c.

I would be happy to add tests but would need some guidance.

- Darrin

On Thu, Dec 17, 2020 at 9:16 AM Marc-André Lureau <
marcandre.lureau@gmail.com> wrote:

> Hi
>
> On Thu, Dec 17, 2020 at 2:54 AM Darrin M. Gorski <darrin@gorski.net>
> wrote:
>
>>
>> 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
>>
>
> That sounds like a good idea to me, but others may have different opinions.
>
>
>> Buglink: https://bugs.launchpad.net/qemu/+bug/1213196
>>
>> 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 @@
>>  #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;
>>
>
> Can we make this generic over all chardevs by moving it to the base class?
>
>  };
>>  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"
>>
>
> This feature will need some new tests in tests/test-char.c.
>
> --
> Marc-André Lureau
>

[-- Attachment #2: Type: text/html, Size: 13129 bytes --]

^ permalink raw reply	[flat|nested] 8+ messages in thread

* Re: [PATCH v1 1/1] chardev: enable guest socket status/crontrol via DTR and DCD
  2020-12-17 17:54   ` Darrin M. Gorski
@ 2020-12-18 11:57     ` Marc-André Lureau
  2020-12-19  5:34       ` Darrin M. Gorski
  0 siblings, 1 reply; 8+ messages in thread
From: Marc-André Lureau @ 2020-12-18 11:57 UTC (permalink / raw)
  To: Darrin M. Gorski; +Cc: Paolo Bonzini, QEMU

[-- Attachment #1: Type: text/plain, Size: 11065 bytes --]

Hi

On Thu, Dec 17, 2020 at 9:54 PM Darrin M. Gorski <darrin@gorski.net> wrote:

>
> > That sounds like a good idea to me, but others may have different
> opinions.
>
> My original idea was simply to allow DCD to track socket state, because
> this is what I need.  The DTR idea came from the referenced bug.  The
> feature defaults to disabled like many of the other edge cases (like telnet
> and tn3270), and it's a reasonably small non-intrusive change.
>
> > Can we make this generic over all chardevs by moving it to the base
> class?
>
> I'll take a closer look at the base class. I admit I did not spend much
> time studying it.  This seemed like a socket specific feature to me.  It
> seems like it might conflict with other chardevs like pty and serial as
> they are already using ioctl for interaction with real tty devices.
>

Yes, but they can override the default behaviour, or just reuse some code.

If it doesn't fit, don't worry about it, we can just start with socket.


> > This feature will need some new tests in tests/test-char.c.
>
> I would be happy to add tests but would need some guidance.
>

You can look at char_socket_server_test. You can extend
CharSocketServerTestConfig to check for modemctl behaviour. It will need to
send the ioctl with qemu_chr_fe_ioctl (I admit it wasn't tested so far,
because only serial and parallel chardev did implement it, and it's not
easy to test those)

Let me know if you need more help


> - Darrin
>
> On Thu, Dec 17, 2020 at 9:16 AM Marc-André Lureau <
> marcandre.lureau@gmail.com> wrote:
>
>> Hi
>>
>> On Thu, Dec 17, 2020 at 2:54 AM Darrin M. Gorski <darrin@gorski.net>
>> wrote:
>>
>>>
>>> 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
>>>
>>
>> That sounds like a good idea to me, but others may have different
>> opinions.
>>
>>
>>> Buglink: https://bugs.launchpad.net/qemu/+bug/1213196
>>>
>>> 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 @@
>>>  #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;
>>>
>>
>> Can we make this generic over all chardevs by moving it to the base class?
>>
>>  };
>>>  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"
>>>
>>
>> This feature will need some new tests in tests/test-char.c.
>>
>> --
>> Marc-André Lureau
>>
>

-- 
Marc-André Lureau

[-- Attachment #2: Type: text/html, Size: 14540 bytes --]

^ permalink raw reply	[flat|nested] 8+ messages in thread

* Re: [PATCH v1 1/1] chardev: enable guest socket status/crontrol via DTR and DCD
  2020-12-18 11:57     ` Marc-André Lureau
@ 2020-12-19  5:34       ` Darrin M. Gorski
  0 siblings, 0 replies; 8+ messages in thread
From: Darrin M. Gorski @ 2020-12-19  5:34 UTC (permalink / raw)
  To: Marc-André Lureau; +Cc: Paolo Bonzini, QEMU

[-- Attachment #1: Type: text/plain, Size: 12048 bytes --]

>> Yes, but they can override the default behaviour, or just reuse some
code.
>> If it doesn't fit, don't worry about it, we can just start with socket.

Whew.  I spent several hours yesterday trying to figure out how to alter
the base class to support this - DCD could be reasonably simple, but DTR is
another matter.  I will continue to look at that, but I do think moving
forward with sockets (since this patch already works that way) is a good
compromise.

> You can look at char_socket_server_test.

Will do.

- Darrin

On Fri, Dec 18, 2020 at 6:57 AM Marc-André Lureau <
marcandre.lureau@gmail.com> wrote:

> Hi
>
> On Thu, Dec 17, 2020 at 9:54 PM Darrin M. Gorski <darrin@gorski.net>
> wrote:
>
>>
>> > That sounds like a good idea to me, but others may have different
>> opinions.
>>
>> My original idea was simply to allow DCD to track socket state, because
>> this is what I need.  The DTR idea came from the referenced bug.  The
>> feature defaults to disabled like many of the other edge cases (like telnet
>> and tn3270), and it's a reasonably small non-intrusive change.
>>
>> > Can we make this generic over all chardevs by moving it to the base
>> class?
>>
>> I'll take a closer look at the base class. I admit I did not spend much
>> time studying it.  This seemed like a socket specific feature to me.  It
>> seems like it might conflict with other chardevs like pty and serial as
>> they are already using ioctl for interaction with real tty devices.
>>
>
> Yes, but they can override the default behaviour, or just reuse some code.
>
> If it doesn't fit, don't worry about it, we can just start with socket.
>
>
>> > This feature will need some new tests in tests/test-char.c.
>>
>> I would be happy to add tests but would need some guidance.
>>
>
> You can look at char_socket_server_test. You can extend
> CharSocketServerTestConfig to check for modemctl behaviour. It will need to
> send the ioctl with qemu_chr_fe_ioctl (I admit it wasn't tested so far,
> because only serial and parallel chardev did implement it, and it's not
> easy to test those)
>
> Let me know if you need more help
>
>
>> - Darrin
>>
>> On Thu, Dec 17, 2020 at 9:16 AM Marc-André Lureau <
>> marcandre.lureau@gmail.com> wrote:
>>
>>> Hi
>>>
>>> On Thu, Dec 17, 2020 at 2:54 AM Darrin M. Gorski <darrin@gorski.net>
>>> wrote:
>>>
>>>>
>>>> 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
>>>>
>>>
>>> That sounds like a good idea to me, but others may have different
>>> opinions.
>>>
>>>
>>>> Buglink: https://bugs.launchpad.net/qemu/+bug/1213196
>>>>
>>>> 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 @@
>>>>  #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;
>>>>
>>>
>>> Can we make this generic over all chardevs by moving it to the base
>>> class?
>>>
>>>  };
>>>>  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"
>>>>
>>>
>>> This feature will need some new tests in tests/test-char.c.
>>>
>>> --
>>> Marc-André Lureau
>>>
>>
>
> --
> Marc-André Lureau
>

[-- Attachment #2: Type: text/html, Size: 15589 bytes --]

^ permalink raw reply	[flat|nested] 8+ messages in thread

* Re: [PATCH v1 1/1] chardev: enable guest socket status/crontrol via DTR and DCD
  2020-12-16 22:06 [PATCH v1 1/1] chardev: enable guest socket status/crontrol via DTR and DCD Darrin M. Gorski
  2020-12-17 14:16 ` Marc-André Lureau
@ 2020-12-23 22:13 ` Eric Blake
  2022-01-12 15:20 ` Aaro Koskinen
  2 siblings, 0 replies; 8+ messages in thread
From: Eric Blake @ 2020-12-23 22:13 UTC (permalink / raw)
  To: Darrin M. Gorski, qemu-devel; +Cc: Marc-André Lureau

On 12/16/20 4:06 PM, Darrin M. Gorski wrote:
> 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 <darrin@gorski.net>
> 
> 
> diff --git a/chardev/char-socket.c b/chardev/char-socket.c

Hmm - your workflow did not produce the usual --- marker and a diffstat
of which files were in the patch; this makes it easier for reviewers to
see at a glance what the rest of the email will contain.  It's not
essential, but it does help.


> +++ 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)

The next release will by 6.0, not 5.2.

-- 
Eric Blake, Principal Software Engineer
Red Hat, Inc.           +1-919-301-3226
Virtualization:  qemu.org | libvirt.org



^ permalink raw reply	[flat|nested] 8+ messages in thread

* Re: [PATCH v1 1/1] chardev: enable guest socket status/crontrol via DTR and DCD
  2020-12-16 22:06 [PATCH v1 1/1] chardev: enable guest socket status/crontrol via DTR and DCD Darrin M. Gorski
  2020-12-17 14:16 ` Marc-André Lureau
  2020-12-23 22:13 ` Eric Blake
@ 2022-01-12 15:20 ` Aaro Koskinen
  2022-01-12 18:50   ` Darrin M. Gorski
  2 siblings, 1 reply; 8+ messages in thread
From: Aaro Koskinen @ 2022-01-12 15:20 UTC (permalink / raw)
  To: Darrin M. Gorski; +Cc: Marc-André Lureau, qemu-devel

Hi,

On Wed, Dec 16, 2020 at 05:06:51PM -0500, Darrin M. Gorski wrote:
> 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 <darrin@gorski.net>

Is there any plans to continue working with this patch? Having the DCD
status would be very useful.

A.


^ permalink raw reply	[flat|nested] 8+ messages in thread

* Re: [PATCH v1 1/1] chardev: enable guest socket status/crontrol via DTR and DCD
  2022-01-12 15:20 ` Aaro Koskinen
@ 2022-01-12 18:50   ` Darrin M. Gorski
  0 siblings, 0 replies; 8+ messages in thread
From: Darrin M. Gorski @ 2022-01-12 18:50 UTC (permalink / raw)
  To: Aaro Koskinen; +Cc: Marc-André Lureau, qemu-devel

[-- Attachment #1: Type: text/plain, Size: 1429 bytes --]

Unfortunately I ran out of cycles at the time.  Adding test cases seems
like it was the roadblock - I don't think I ever figured out how to
implement the needed build tests for this additional feature in chardev.
I'm not that strong of a C developer, unfortunately.

I haven't looked at picking this up in probably a year, so I don't even
remember if I was able to get *anywhere* with it.

I did end up using a patched build myself,  and it worked fine for what I
was doing which is probably why I didn't get back to it.

Sorry, I'm sure that's not the answer you were hoping for.  But the day job
takes priority ;)

- Darrin


On Wed, Jan 12, 2022 at 10:20 AM Aaro Koskinen <aaro.koskinen@iki.fi> wrote:

> Hi,
>
> On Wed, Dec 16, 2020 at 05:06:51PM -0500, Darrin M. Gorski wrote:
> > 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 <darrin@gorski.net>
>
> Is there any plans to continue working with this patch? Having the DCD
> status would be very useful.
>
> A.
>

[-- Attachment #2: Type: text/html, Size: 2136 bytes --]

^ permalink raw reply	[flat|nested] 8+ messages in thread

end of thread, other threads:[~2022-01-12 18:55 UTC | newest]

Thread overview: 8+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2020-12-16 22:06 [PATCH v1 1/1] chardev: enable guest socket status/crontrol via DTR and DCD Darrin M. Gorski
2020-12-17 14:16 ` Marc-André Lureau
2020-12-17 17:54   ` Darrin M. Gorski
2020-12-18 11:57     ` Marc-André Lureau
2020-12-19  5:34       ` Darrin M. Gorski
2020-12-23 22:13 ` Eric Blake
2022-01-12 15:20 ` Aaro Koskinen
2022-01-12 18:50   ` Darrin M. Gorski

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for NNTP newsgroup(s).