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