Hi On Thu, Dec 17, 2020 at 2:54 AM 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 > 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 > > > 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