>> 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.lur...@gmail.com> wrote:

> Hi
>
> On Thu, Dec 17, 2020 at 9:54 PM Darrin M. Gorski <dar...@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.lur...@gmail.com> wrote:
>>
>>> 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
>>>
>>
>
> --
> Marc-André Lureau
>

Reply via email to