Hi

On Thu, Dec 17, 2020 at 2:54 AM Darrin M. Gorski <dar...@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 <dar...@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

Reply via email to